Skip to content

fix(SelectMenu/InputDate/InputTime/InputMenu): three upstream component fixes (nuxt/ui@aab2d10, nuxt/ui@8e4546e, nuxt/ui@b3d4342) - #609

Open
IgorShevchik wants to merge 1 commit into
mainfrom
sync/nuxt-aab2d10d
Open

IgorShevchik wants to merge 1 commit into
mainfrom
sync/nuxt-aab2d10d

Conversation

@IgorShevchik

Copy link
Copy Markdown
Collaborator

Ports three contiguous upstream fixes in one PR, per .sync/PORTING.md §6 step 4b — different components with non-overlapping tests, so a failure still names the one that broke.

  • aab2d10dfix(SelectMenu): remove the inherited "Show popup" aria-label · journal
  • 8e4546e3fix(InputDate/InputTime): emit focus and blur when focus enters or leaves the field · journal
  • b3d4342dfix(InputMenu): prevent tags input from adding the search term on enter · journal

SelectMenu — an accessible name that was being shadowed

reka's ComboboxTrigger hard-codes aria-label="Show popup", and an aria-label
beats every other naming mechanism — so a screen reader announced "Show popup"
instead of the B24FormField label, on every SelectMenu in the library.
Measurably present here: 53 occurrences in each of the two snapshot files.

-v-bind="{ ...$attrs, ...ariaAttrs }"
+v-bind="{ 'aria-label': undefined, ...$attrs, ...ariaAttrs }"

undefined goes before the spreads so a caller-supplied label still wins — it
clears a default rather than blocking the prop, which is exactly what upstream's
second test pins.

106 snapshots updated, and every pair compared rather than accepted
test:update rewrites the whole suite, which is how an unrelated drift gets
absorbed:

pairs: 106
pairs whose ONLY difference is the removed aria-label: 106
pairs differing by anything else: 0

aria-label="Show popup" now appears 0 times in either file.

InputDate / InputTime — blur fired between segments

A date field is several focusable segments. @blur and @focus fire on each,
so moving from day to month emitted blur+focus and a form validating on blur ran
mid-entry, against a value the user had not finished typing.

Now @focusout/@focusin — which bubble — with a guard that ignores movement
inside the field. test/utils/form.ts registers B24InputDate and B24InputTime
so renderForm can mount them.

Each half pinned by a different test:

mutation fails
drop the containment guard …not between segments, validate on blur ignores focus moving between segments
revert to @blur/@focus focus and blur events…, validate on blur works

InputMenu — and the part that needed more than a faithful port

In multiple mode TagsInputInput adds the search term as a tag on Enter, but
TagsInputRoot is driven by the combobox — the tag never reaches modelValue, so
the user sees a chip that is not selected and cannot be acted on.

Upstream's two tests both pin real behaviour (no stray chip; a highlighted item is
still selected, so the fix does not over-block). But:

Removing the isComposing || !searchTerm guard left all 166 tests green
upstream's included.

A line nothing checks is a line a later cleanup deletes with a green suite. The
!searchTerm half is now pinned by a test that is red without the guard and green
with it, and is the only test that moves: with nothing typed there is no tag to
add, and preventing the key would also swallow the implicit form submission an
empty field allows.

event.isComposing is left uncovered, and the failed attempt is recorded rather
than papered over.
A probe building new KeyboardEvent('keydown', { isComposing: true }) reads the flag back as true, but the event is reported prevented anyway
defaultPrevented there is influenced by other handlers on the same key, so the
probe cannot attribute the outcome to this guard. A test asserting on it would
pass for a reason unrelated to the line it claims to cover. What the flag is for:
during IME composition Enter confirms the candidate character, and swallowing it
would break text entry for Chinese, Japanese and Korean users.

One subtlety carried over

The InputDate/InputTime tests reset the fake clock before dispatching, with an
afterEach restoring it:

// Vue skips native events stamped at the exact time their listener was attached
vi.setSystemTime(new Date(date.getTime() + 1000))

Without it the dispatched events are silently ignored and the tests pass for the
wrong reason. Worth knowing before anyone simplifies the clock handling out.

Gate

dev:prepare · lint · typecheck · build · test:coverage (355 files,
8103 passed — 24 more than before, the new tests across both environments) ·
test:module — green.

Cursor advances 60db60e0aab2d10d8e4546e3b3d4342d, which is
upstream v4's current HEAD
; the a3c3ff34/60db60e0 entries are reconciled
with #608 / 0fe8ece3. A closing bookkeeping PR follows.

🤖 Generated with Claude Code

https://claude.ai/code/session_01JS8ypVfQSFzYVZzkTHhURb


Generated by Claude Code

…nt fixes (nuxt/ui@aab2d10, nuxt/ui@8e4546e, nuxt/ui@b3d4342)

Three contiguous upstream commits in one PR, per §6 step 4b. Different
components, non-overlapping tests, so a failure still names the one that broke.

SelectMenu — reka's `ComboboxTrigger` hard-codes `aria-label="Show popup"`, which
beats every other naming mechanism, so a screen reader announced it instead of the
`B24FormField` label. Measurably present: 53 occurrences in each of the two
snapshot files. Fixed with `'aria-label': undefined` placed BEFORE the spreads, so
a caller-supplied label still wins — it clears a default rather than blocking the
prop, which is what upstream's second test pins. 106 snapshots updated, and every
pair was compared rather than accepted: 106 of 106 differ only by the removed
attribute, 0 differ by anything else, and the string is now absent from both files.

InputDate/InputTime — a date field is several focusable segments, and `@blur` /
`@focus` fire on each, so moving from day to month emitted blur+focus and a form
validating on blur ran mid-entry. Now `@focusout`/`@focusin`, which bubble, with a
guard ignoring movement inside the field. `test/utils/form.ts` registers
`B24InputDate` and `B24InputTime` so `renderForm` can mount them. Each half is
pinned by a different test: dropping the guard fails the "between segments" pair,
reverting the listeners fails "focus and blur events" and "validate on blur works".

InputMenu — in `multiple` mode `TagsInputInput` adds the search term as a tag on
Enter, but `TagsInputRoot` is driven by the combobox, so the tag never reaches
`modelValue` and renders a chip that is not selected.

That last one needed more than a faithful port. Upstream's two tests both pin real
behaviour, but removing the `isComposing || !searchTerm` guard left all 166 tests
green — upstream's included. A line nothing checks is a line a later cleanup
deletes with a green suite, so the `!searchTerm` half is now pinned by a test that
is red without the guard and green with it: with nothing typed there is no tag to
add, and preventing the key would also swallow the implicit form submission an
empty field allows.

`event.isComposing` is left uncovered, and the failed attempt is recorded rather
than papered over. A probe building `new KeyboardEvent('keydown', { isComposing:
true })` reads the flag back as true, but the event is reported prevented anyway —
`defaultPrevented` there is influenced by other handlers on the same key, so the
probe cannot attribute the outcome to this guard, and a test asserting on it would
pass for a reason unrelated to the line it claims to cover. What the flag is for:
during IME composition Enter confirms the candidate character, and swallowing it
would break text entry for Chinese, Japanese and Korean users.

Also reconciles the `a3c3ff34` and `60db60e0` entries with #608 / 0fe8ece.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JS8ypVfQSFzYVZzkTHhURb
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants