fix(web): repair merge tails — todo-row selector and composer handler clone
The #921 selector change and the duplication gate both first ran against this branch after the merge made it MERGEABLE: assembly-surfaces still queried the retired data-sample="todo-row" hook (the composed TodoRow carries ToolRow's data-tool attribute instead), and the redesigned QuestionComposer duplicated the custom-draft onChange/onKeyDown pair across its inline input and optionless textarea. The spec now anchors on data-tool="todo_write", and the composer shares one draftCustom / continueFromCustom handler pair (Enter continues, Shift+Enter stays a newline, IME composition stays inert).
This commit is contained in:
@@ -97,7 +97,7 @@ describe('todo_write assembly (product registrations, no outlet twins)', () => {
|
|||||||
const view = runtime.renderRoot()
|
const view = runtime.renderRoot()
|
||||||
|
|
||||||
// Keyed toolview registration took the row (summary derived from args).
|
// Keyed toolview registration took the row (summary derived from args).
|
||||||
const row = view.container.querySelector('[data-sample="todo-row"]')
|
const row = view.container.querySelector('[data-tool="todo_write"]')
|
||||||
expect(row).not.toBeNull()
|
expect(row).not.toBeNull()
|
||||||
expect(row!.textContent).toContain('1/3 已完成 · 实现 fixture 样本')
|
expect(row!.textContent).toContain('1/3 已完成 · 实现 fixture 样本')
|
||||||
|
|
||||||
@@ -117,7 +117,7 @@ describe('todo_write assembly (product registrations, no outlet twins)', () => {
|
|||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(view.container.querySelector('[data-testid="todo-panel"]')).toBeNull()
|
expect(view.container.querySelector('[data-testid="todo-panel"]')).toBeNull()
|
||||||
})
|
})
|
||||||
expect(view.container.querySelector('[data-sample="todo-row"]')).not.toBeNull()
|
expect(view.container.querySelector('[data-tool="todo_write"]')).not.toBeNull()
|
||||||
await runtime.dispose()
|
await runtime.dispose()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { useMemo, useState, type KeyboardEvent } from 'react'
|
import { useMemo, useState, type ChangeEvent, type KeyboardEvent } from 'react'
|
||||||
import clsx from 'clsx'
|
import clsx from 'clsx'
|
||||||
import {
|
import {
|
||||||
Button, IconCheckOutline14, IconChevronLeftOutline14, IconChevronRightOutline14,
|
Button, IconCheckOutline14, IconChevronLeftOutline14, IconChevronRightOutline14,
|
||||||
@@ -149,6 +149,23 @@ function QuestionFlow({ pending, t, useLocale }: {
|
|||||||
submitDrafts(drafts)
|
submitDrafts(drafts)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Shared by the inline custom input and the optionless textarea: typing a
|
||||||
|
// custom draft clears any selection, and Enter continues the flow
|
||||||
|
// (Shift+Enter stays a newline in the textarea; on the single-line input it
|
||||||
|
// is inert either way).
|
||||||
|
const draftCustom = (event: ChangeEvent<HTMLInputElement | HTMLTextAreaElement>): void => {
|
||||||
|
const value = event.target.value
|
||||||
|
updateDraft(current => ({
|
||||||
|
...current, selected: [], custom: value, skipped: false,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
const continueFromCustom = (event: KeyboardEvent<HTMLInputElement | HTMLTextAreaElement>): void => {
|
||||||
|
if (event.key !== 'Enter' || event.shiftKey || isComposing(event)) return
|
||||||
|
event.preventDefault()
|
||||||
|
continueFlow()
|
||||||
|
}
|
||||||
|
|
||||||
const skipQuestion = (): void => {
|
const skipQuestion = (): void => {
|
||||||
const nextDrafts = drafts.map((item, itemIndex) => itemIndex === index
|
const nextDrafts = drafts.map((item, itemIndex) => itemIndex === index
|
||||||
? { selected: [], custom: '', skipped: true }
|
? { selected: [], custom: '', skipped: true }
|
||||||
@@ -247,18 +264,8 @@ function QuestionFlow({ pending, t, useLocale }: {
|
|||||||
value={draft.custom}
|
value={draft.custom}
|
||||||
disabled={busy !== null}
|
disabled={busy !== null}
|
||||||
placeholder={t('custom.placeholder')}
|
placeholder={t('custom.placeholder')}
|
||||||
onChange={(event) => {
|
onChange={draftCustom}
|
||||||
const value = event.target.value
|
onKeyDown={continueFromCustom}
|
||||||
updateDraft(current => ({
|
|
||||||
...current, selected: [], custom: value, skipped: false,
|
|
||||||
}))
|
|
||||||
}}
|
|
||||||
onKeyDown={(event) => {
|
|
||||||
if (event.key === 'Enter' && !isComposing(event)) {
|
|
||||||
event.preventDefault()
|
|
||||||
continueFlow()
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
@@ -270,18 +277,8 @@ function QuestionFlow({ pending, t, useLocale }: {
|
|||||||
disabled={busy !== null}
|
disabled={busy !== null}
|
||||||
rows={2}
|
rows={2}
|
||||||
placeholder={t('custom.placeholder')}
|
placeholder={t('custom.placeholder')}
|
||||||
onChange={(event) => {
|
onChange={draftCustom}
|
||||||
const value = event.target.value
|
onKeyDown={continueFromCustom}
|
||||||
updateDraft(current => ({
|
|
||||||
...current, selected: [], custom: value, skipped: false,
|
|
||||||
}))
|
|
||||||
}}
|
|
||||||
onKeyDown={(event) => {
|
|
||||||
if (event.key === 'Enter' && !event.shiftKey && !isComposing(event)) {
|
|
||||||
event.preventDefault()
|
|
||||||
continueFlow()
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user