mirror of
https://github.com/stablyai/orca.git
synced 2026-11-15 02:17:31 +00:00
feat(native-chat): step between a question card's questions with Left and Right (#38335)
On a multi-question card only a click on a step tab changed question. Left and Right now step to the previous or next question while an option row or a step tab has focus, keep every answer, and stop at the first and last question. Inside the answer field they still move the caret. Co-authored-by: Michael Schaecher <276865230+big-sheq@users.noreply.github.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Michael Schaecher
Claude Opus 5.5
parent
8391d2e42f
commit
caa2f6a7c2
@@ -304,6 +304,34 @@ describe('NativeChatQuestionCard', () => {
|
||||
])
|
||||
})
|
||||
|
||||
it('moves between questions with Left and Right from an option or step tab, stopping at the ends', () => {
|
||||
vi.useFakeTimers()
|
||||
container.setAttribute('data-native-chat-root', 'true')
|
||||
render(indentThenFruits, vi.fn(), true, true)
|
||||
clickOption('Spaces')
|
||||
|
||||
expect(pressKey({ key: 'ArrowLeft' }, document.activeElement!).defaultPrevented).toBe(false)
|
||||
expect(cardTitle()).toBe('Do you prefer tabs or spaces?')
|
||||
expect(pressKey({ key: 'ArrowRight' }, document.activeElement!).defaultPrevented).toBe(true)
|
||||
expect(cardTitle()).toBe('Which fruit?')
|
||||
expect(document.activeElement?.textContent).toContain('Apple')
|
||||
expect(pressKey({ key: 'ArrowRight' }, document.activeElement!).defaultPrevented).toBe(false)
|
||||
expect(cardTitle()).toBe('Which fruit?')
|
||||
|
||||
const cancel = container.querySelector<HTMLButtonElement>('button[aria-label="Cancel"]')!
|
||||
act(() => cancel.focus())
|
||||
expect(pressKey({ key: 'ArrowLeft' }, cancel).defaultPrevented).toBe(false)
|
||||
expect(cardTitle()).toBe('Which fruit?')
|
||||
|
||||
const stepTab = [...container.querySelectorAll('button')].find((b) =>
|
||||
b.textContent?.includes('Step 1')
|
||||
)!
|
||||
act(() => stepTab.focus())
|
||||
expect(pressKey({ key: 'ArrowLeft' }, stepTab).defaultPrevented).toBe(true)
|
||||
expect(cardTitle()).toBe('Do you prefer tabs or spaces?')
|
||||
expect(optionPressed('Spaces')).toBe('true')
|
||||
})
|
||||
|
||||
it('sends nothing when the question is cancelled before the pick moves on', () => {
|
||||
vi.useFakeTimers()
|
||||
const onAnswer = vi.fn()
|
||||
|
||||
@@ -166,7 +166,6 @@ export function NativeChatQuestionCard({
|
||||
autoAdvance.schedule(() => advanceOrSubmit(next, otherText))
|
||||
}
|
||||
}
|
||||
useNativeChatQuestionKeys(cardRef, shouldFocus && !isSubmitting, q.options.length, pickOption)
|
||||
|
||||
const confirm = (fromKeyboard = false): void => {
|
||||
autoAdvance.cancel()
|
||||
@@ -184,6 +183,20 @@ export function NativeChatQuestionCard({
|
||||
autoAdvance.cancel()
|
||||
setIndex(questionIndex)
|
||||
}
|
||||
useNativeChatQuestionKeys(
|
||||
cardRef,
|
||||
shouldFocus && !isSubmitting,
|
||||
q.options.length,
|
||||
pickOption,
|
||||
(delta) => {
|
||||
const next = index + delta
|
||||
if (next < 0 || next >= total) {
|
||||
return false
|
||||
}
|
||||
goTo(next)
|
||||
return true
|
||||
}
|
||||
)
|
||||
const cancel = (): void => {
|
||||
autoAdvance.cancel()
|
||||
onCancel()
|
||||
@@ -226,6 +239,7 @@ export function NativeChatQuestionCard({
|
||||
<button
|
||||
key={i}
|
||||
type="button"
|
||||
data-native-chat-question-step
|
||||
disabled={isSubmitting}
|
||||
onClick={() => goTo(i)}
|
||||
className={cn(
|
||||
|
||||
@@ -7,7 +7,8 @@ export function useNativeChatQuestionKeys(
|
||||
cardRef: RefObject<HTMLElement | null>,
|
||||
enabled: boolean,
|
||||
optionCount: number,
|
||||
onPick: (optionIndex: number) => void
|
||||
onPick: (optionIndex: number) => void,
|
||||
onStep: (delta: 1 | -1) => boolean
|
||||
): void {
|
||||
const onKeyDown = useEffectEvent((event: KeyboardEvent): void => {
|
||||
const target = event.target instanceof Element ? event.target : null
|
||||
@@ -72,6 +73,13 @@ export function useNativeChatQuestionKeys(
|
||||
if (field && next === field) {
|
||||
field.setSelectionRange(field.value.length, field.value.length)
|
||||
}
|
||||
} else if (
|
||||
(event.key === 'ArrowLeft' || event.key === 'ArrowRight') &&
|
||||
(focusedIndex !== -1 || target?.closest('[data-native-chat-question-step]'))
|
||||
) {
|
||||
if (onStep(event.key === 'ArrowRight' ? 1 : -1)) {
|
||||
event.preventDefault()
|
||||
}
|
||||
} else if (event.key === 'Enter' && focusedIndex !== -1) {
|
||||
event.preventDefault()
|
||||
onPick(focusedIndex)
|
||||
|
||||
Reference in New Issue
Block a user