@domphy/ui 0.21.5 → 0.22.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,37 @@
1
1
  # @domphy/ui Changelog
2
2
 
3
+ ## 0.22.0
4
+
5
+ - fix(overlays): a floating panel opened outside a `<dialog>` no longer mounts inside it. Panels opened *within* a dialog portal into the dialog's own overlay container (top-layer sharing), which used the same `id="domphy-floating"` as the app-level one, so the root's descendant lookup matched the dialog's copy first: after one in-dialog dropdown, every later popover/tooltip/select in the app was mounted inside a closed, `display:none` dialog — `aria-expanded="true"` with a 0x0, unfocusable panel. The container is now marked with a `data-domphy-floating` **attribute** (a document can legitimately hold several, so a fixed `id` was duplicate-id invalid HTML) and the root lookup is `:scope > [data-domphy-floating]`.
6
+ - fix(dialog, drawer): Escape dismisses only the topmost layer. While a `popover`/`selectBox`/`combobox`/`datePicker` panel is open inside the modal, the native `cancel` event is ignored so the panel closes and the dialog stays open (Radix `DismissableLayer` / React Aria overlay-stack parity).
7
+ - fix(dialog): initial focus honours `[autofocus]` before falling back to the first focusable element (HTML dialog focusing steps; Radix / MUI / Mantine parity).
8
+ - fix(toast): auto-dismiss (`duration`/`onDismiss`) is actually wired now — the `behavior()` instance that implements it (arm/pause/resume timer, hover/focus pause via `pointerenter`/`pointerleave`/`focusin`/`focusout`) was previously defined but never attached to the returned element, so both props were silently accepted and ignored (the toast never dismissed itself no matter what `duration` was passed). Covered by 3 new tests in `tests/production-readiness.test.ts` against real timers.
9
+ - fix(link): `href` no longer strips a caller's own explicit `role`/`tabIndex` override — only this patch's own default (`role: "link"` / `tabIndex: 0`) is removed once a native `href` makes them redundant. Previously any element composing `$: [link()]` while also declaring its own `role` (e.g. a search-result row that must be `role="option"` inside a `role="listbox"`) had that role silently deleted on mount. Found via `@domphy/press`'s `searchWidget()` (which composes `link()` on each result row), fixed at the root in `@domphy/ui` since every composer benefits.
10
+ - fix(pagination): the current page keeps `aria-current="page"` but is no longer `disabled`. Disabling the just-clicked button removed it from the tab order, so the browser dropped focus to `<body>` on every page change (WCAG 2.4.3). shadcn/ui and MUI mark the current page with `aria-current` only.
11
+ - fix(menu): roving `tabindex` per the WAI-ARIA APG menu pattern — exactly one item is in the page tab order, so Tab leaves the menu instead of stepping through every item. Arrow/Home/End resolve the target through the event's own `[role=menu]` instead of `document.getElementById`, so a menu inside a shadow root is navigable.
12
+ - fix(tabs): every `[role=tabpanel]` gets `tabindex="0"` so panel content with no focusable element is reachable and scrollable by keyboard (APG tabs pattern; Radix `Tabs.Content` parity).
13
+ - fix(details): the `summary` band uses `shift-11` on its `shift-2` background. `shift-10` was a gap of 8 and measured 4.33:1 (`#636363` on `#dbdbdb`) — an axe `color-contrast` WCAG AA failure.
14
+ - fix(transitionGroup): honours `prefers-reduced-motion: reduce` — the FLIP reorder is applied instantly, matching `motion` (WCAG 2.3.3).
15
+ - feat(splitterHandle): drag works with touch and pen (Pointer Events + `touch-action: none`), and the separator carries an accessible name via a new optional `label` prop (default `"Resize"`, WCAG 4.1.2).
16
+ - fix(motion): the patch's JSDoc sat above an internal `prefersReducedMotion()` helper wedged between the comment and `function motion`, so every `motion()` prop shipped with an empty `doc` in `manifest.json` and on the docs site. The helper moved to `utils/reducedMotion.ts`, now shared with `transitionGroup` instead of duplicated.
17
+ - feat(selectBox): ArrowDown/ArrowUp/Home/End open the dropdown and move focus into the listbox (WAI-ARIA APG Select-Only Combobox). With the panel open every arrow key was swallowed by the typeahead's `key.length === 1` guard, so the dropdown could only be driven by mouse or by guessing an option's first letter.
18
+ - fix(combobox): ArrowDown/ArrowUp from the input reach an option on the FIRST press. The move was scheduled on a single `requestAnimationFrame`, but `show()` is debounced by 100ms — the panel did not exist yet, and the panel is still `visibility: hidden` at the instant `open` flips, where `focus()` is a no-op. Both are now waited out.
19
+ - fix(tag): `removable` inserts its × button with `updateDom`, so the button exists after hydration and on a fresh render. It relied on `_onMount` firing before the children walk; `@domphy/core` now fires Mount bottom-up (React/Vue parity), which left the button in the node list with no DOM node.
20
+ - fix(buttonGhost, buttonSwitch, tag): `width: fit-content`. `display: inline-flex` is blockified by a flex or grid parent, so inside a `stack()` a ghost button stretched to 1232px next to an 88px outline button, and the 48px switch pill stretched with its thumb stranded at one end.
21
+ - fix(inputCheckbox, inputRadio): the disabled state keeps the control's outline and only drops the colour family to neutral. Filling `::before` with flat grey and dropping its outline made a disabled UNCHECKED box read as filled — more "on" than an enabled unchecked box, and indistinguishable from the disabled checked one.
22
+ - fix(inputSwitch): the OFF track stays on the surface tone and carries its 3:1 (WCAG 2.1 SC 1.4.11) through `"shift-7"` outlines on the track and knob, like `buttonSwitch`. Darkening the off-track fill to `"shift-7"` cleared the contrast floor but made the OFF state read as ON; Radix, shadcn/ui and MUI all keep the off-track muted.
23
+ - fix(overlays): outside-click and document-level Escape dismissal survive the first close. The panel node carried a borrowed registration of the ANCHOR's `behavior()` instance (so panel-originated events could resolve it), and `ElementNode` teardown destroys every instance in a removed node's map — so closing any `popover`/`menu`/`tooltip`/`selectBox`/`combobox`/`datePicker` once destroyed its anchor's behavior. Measured in Chromium: from the SECOND open onward the panel could only be closed by clicking its own trigger again. The registration is now withdrawn before the panel is removed.
24
+ - fix(overlays): dismissing a panel while the focus is INSIDE it returns the focus to the trigger (WCAG 2.4.3; Radix `DismissableLayer` + `FocusScope` parity). Measured: selectBox trigger → ArrowDown → Escape left `document.activeElement === document.body`. Focus is left alone when the dismissal came from the user clicking or tabbing elsewhere, and when the anchor is a wrapper (combobox) the focus goes to its first tab stop.
25
+ - fix(overlays): a document-level Escape closes only the TOP layer — the last panel in the shared `[data-domphy-floating]` overlay — instead of every open panel at once. With a popover opened from inside another popover, one Escape removed both, and the outer's teardown ran first, tearing out the inner's trigger so the restored focus fell through to `<body>`.
26
+ - fix(combobox): the popup opens on click / typing / ArrowDown, not on plain focus. Since Escape now returns focus to the input, an open-on-focus popup re-opened within 100ms of every Escape and could never be dismissed (MUI Autocomplete defaults `openOnFocus` to false for the same reason).
27
+ - fix(popover): the trigger's `aria-haspopup` names the role the panel ACTUALLY has. It was pinned to `"dialog"`, but content that owns its own surface keeps its own role — a `menu()` panel is `role="menu"`, a `selectList()` panel is `role="listbox"` — so the menubar recipe announced a dialog and opened a menu. Resolved from the content element and its `$` patches with core's own precedence (later patch wins, the element's own key wins over all), mapping `menubar`→`menu`, `treegrid`→`grid`, `alertdialog`→`dialog`, and falling back to `"dialog"` for a role with no matching value.
28
+ - fix(datePicker): the calendar opens on click / ArrowDown / Enter, not on plain focus — same reason as `combobox`, plus a keyboard user tabbing through a form no longer gets a calendar over the next fields (WAI-ARIA APG Date Picker Combobox, React Aria and MUI all open on press).
29
+
30
+ ## 0.21.6
31
+
32
+ - fix(popover, selectBox, combobox, datePicker): dropdown panels use the page surface (`shift-0`), same as `menu` / `selectList` / `dialog`. `shift-14` inverted the panel in light theme, so the menubar recipe (`popover` + `menu`) rendered as a dark card wrapping a light list. Tooltip / toast stay inverted (`shift-17`).
33
+ - fix(popover): do not stamp panel chrome / `role=dialog` onto content that already owns a surface (`dataTone` on the element or a `$` patch). `menu()` already sets `role=menu`, `shift-0`, and elevation; a second stamp overwrote the role and comma-joined a second `boxShadow`.
34
+
3
35
  ## 0.21.5
4
36
 
5
37
  - fix(overlays): `open` (`dialog`/`drawer`/`popover`/`tooltip`/`combobox`/`selectBox`/`createFloating`) accepts `Computed`/`ReadableState`. Subscribe with `addListener` on writable `State`, `effect()` on read-only (Computed has no `addListener`/`set`). Dismiss via optional `onDismiss`; `.set(false)` only when writable.