Skip to content

fix(Dropdown): keyboard, focus and ARIA parity - #283

Merged
xidedix merged 17 commits into
v5.7from
fix/dropdown-keyboard
Oct 7, 2026
Merged

xidedix merged 17 commits into
v5.7from
fix/dropdown-keyboard

Conversation

@xidedix

@xidedix xidedix commented Oct 7, 2026 •

Copy link
Copy Markdown
Member
  • ArrowDown/ArrowUp on the toggle open the menu and focus the first/last item; Escape returns focus to the toggle; Tab in the menu is native
  • Space reaches buttons in the menu; keys inside inputs, selects, textareas and contenteditables stay with the control
  • Enter on a native-click item toggles once; toggle click focuses the toggle (Safari)
  • aria-expanded follows visible from the first render; anchor toggle gets role="button"; items have no default role
  • toggle wired with [dropdownComponent] gets listeners and focus back on Escape
  • replayed events after hydration are ignored

xidedix added 17 commits October 7, 2026 17:35
Space preventDefault only on targets without native Space handling (links, menu itself); buttons and inputs activate/type again.
Read from dropdown visible() signal; visibleChange subscription in ngAfterViewInit missed initial emit.
Native click already handles Enter on <button> and <a href>; keyup toggle only for other items. Menu reopened after Enter before.
- ArrowDown/ArrowUp on toggle open menu, focus first/last item; skipped from inputs and disabled toggle
- native Tab in menu, arrows continue from focused item
- Escape from menu returns focus to toggle
Safari does not focus buttons on click; focus fell to body. Skipped when focus already inside toggle.
<a cDropdownToggle> gets role=button, tabindex=0 without href; Enter/Space toggle it. Explicit role/tabindex kept.
role="list-item" is not a valid ARIA role and hid link/button semantics; role input still overrides.
RouterLink binds href during change detection, after the directive constructor; tabindex fallback moved to afterNextRender so a routerLink toggle gets no redundant tabindex.
…ggle

- menu and toggle ignore every key from input, select, textarea and contenteditable
- shared clicksOnEnter/clicksOnSpace: input buttons and summary count as native click
- anchor toggle without href is not made focusable; needs an href like other anchors
- toggle wired with [dropdownComponent] gets listeners and focus back on Escape
- one content query for menu items; spec Renderer2 stub
- input buttons are not editable: arrows and Space work on an input button item
- anchor toggle ignores repeated Space/Enter; role=button only without interactive content
- external toggle: document click exempts it, Escape on it closes, Tab from it does not
- Escape focus return falls back to the first interactive element in a non-focusable toggle host
- item keyup judges the event target, not the host
- Escape listener back on the dropdown host (nested dropdowns, ancestors, shadow DOM); an external toggle gets its own
- Tab close check reads composedPath
- anchor toggle handles Enter/Space only when the key targets the anchor; role=button on every anchor toggle
- Escape focus return skips hidden, disabled, aria-hidden and tabindex=-1 candidates
- item keyup leaves Enter to an editable target
- Tab close check walks composedPath, Escape focus return works inside a shadow root
- focus return skips tabindex=-1 and aria-hidden candidates, stops at the first that takes focus
- contenteditable=false is not editable
- a native-click control inside the toggle keeps its click; disabled toggle gets aria-disabled
- toggle click contract back to base: any click bubbling to the toggle toggles, no aria-disabled from the input
- editable target: nearest contenteditable ancestor decides, value compared case-insensitively
- shadow spec asserts Tab-out closes
- contenteditable ancestors walked until a valid value; invalid values inherit
- key target read from composedPath, so a field inside a shadow root keeps its keys
- key target read from event.target again: composedPath throws during Angular event replay; it stays only in the document Tab listener
- specs: contenteditable TRUE, plaintext-only and a False island under an editor; autoClose false keeps Escape and Tab inert
- replayed events (Angular event replay after hydration) are ignored on the toggle and the menu, nothing can be prevented after dispatch
- toggle keydown stops propagation for the keys it handles, so a toggle inside another menu does not move that menu
- toggle keydown no longer stops propagation: a nested toggle lets the parent menu move as in vanilla and React
- replay spec watches ErrorHandler and the service instead of the swallowed listener throw
@xidedix
xidedix merged commit ef85c7b into v5.7 Oct 7, 2026
9 checks passed
@xidedix
xidedix deleted the fix/dropdown-keyboard branch October 7, 2026 17:47
@xidedix xidedix changed the title fix(Dropdown): keyboard, focus and ARIA parity with the other editions fix(Dropdown): keyboard, focus and ARIA parity Oct 7, 2026
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.

1 participant