From 390777f085e1c44722db186da1fc607607c098e3 Mon Sep 17 00:00:00 2001 From: Cindy Zhang Date: Sat, 31 Oct 2026 10:43:43 -0700 Subject: [PATCH 01/17] spec: Tooltip and HoverCard pin on press; InfoTip is the info preset --- docs/specs/AST-003/spec.md | 55 +++-- packages/core/src/HoverCard/HoverCard.spec.md | 111 ++++++---- packages/core/src/InfoTip/InfoTip.spec.md | 189 ++++++++++++++++++ packages/core/src/Tooltip/Tooltip.spec.md | 150 ++++++++++---- 4 files changed, 407 insertions(+), 98 deletions(-) create mode 100644 packages/core/src/InfoTip/InfoTip.spec.md diff --git a/docs/specs/AST-003/spec.md b/docs/specs/AST-003/spec.md index ef1511eb20944..982b01e6ae246 100644 --- a/docs/specs/AST-003/spec.md +++ b/docs/specs/AST-003/spec.md @@ -29,7 +29,7 @@ affects_consumer_docs: [] { "scope": "global", "triggers": { - "layering": ["FR10", "DEC-1", "DEC-2", "DEC-3", "DEC-5"] + "layering": ["FR4", "FR10", "DEC-1", "DEC-2", "DEC-3", "DEC-5", "DEC-6"] } } ``` @@ -69,9 +69,12 @@ browsers receive a usable reduced fallback, not equivalent behavior. - **FR3 — Blocking owners consume without closing.** A blocking owner prevents a channel from reaching an owner behind it. Required Dialog is the representative Escape/platform case. -- **FR4 — Passive dependents never shield owners.** Tooltip is passive. It does - not win or block dismissal and disappears as a consequence when its associated - owner or trigger interaction closes. +- **FR4 — Passive dependents never shield owners.** An unpinned Tooltip is + passive. It does not win or block dismissal and disappears as a consequence + when its associated owner or trigger interaction closes. A Tooltip or + HoverCard that a press pinned open (`component:Tooltip` FR9, + `component:HoverCard` FR9) is an active owner: it takes Escape first and + closes, and the owner beneath it stays open for that press (DEC-6). - **FR5 — Associations are explicit.** Every active or blocking owner records an optional parent owner and an interaction-branch root. A passive dependent records its associated owner or trigger without creating an owner node or @@ -187,6 +190,8 @@ Current `main` does not satisfy this spec completely: - the stack has `close` and `block` but no passive role; Tooltip currently consumes Escape ahead of associated owners; +- no Tooltip or HoverCard pins open on a press yet (`component:Tooltip` FR9, + `component:HoverCard` FR9); - no shared owner/branch association graph or outside-branch resolver exists; - DropdownMenuSubMenu has a local parent-close chain, but outside channels do not consume shared branch identity; @@ -205,8 +210,8 @@ Current `main` does not satisfy this spec completely: and passive roles plus parent-owner and branch-root association. Add pure eligible-owner and outside-branch resolvers. Preserve the current Escape/platform contract for existing active/blocking members while migrating. -2. **Eligibility migration.** Make Tooltip passive and update Tooltip-over-owner - tests. Keep HoverCard active because it can own interactive content. Migrate +2. **Eligibility migration.** Make an unpinned Tooltip passive and a pinned + Tooltip active, and update Tooltip-over-owner tests. Keep HoverCard active because it can own interactive content. Migrate CommandPalette and the local Escape implementations tracked by `family:overlay-dismissal` onto eligible-owner delivery. 3. **Branch migration.** Project DropdownMenuContext's root/submenu chain into a @@ -233,21 +238,22 @@ Interaction modality continues to follow `architecture:interaction-modality`. ## Verification -| Contract | Verification | Representative states | Mutation or failure expectation | -| --------- | ---------------------------------------------------------- | ----------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------- | -| FR1–FR6 | Pure registry/resolver tests plus family interaction tests | active, blocking, passive Tooltip, visual-only, controlled owner | Passive surface wins, blocker leaks, or wrong owner closes | -| FR7–FR10 | Branch resolver tests and real-browser outside interaction | root menu with two submenu levels; parent + child Popover; associated trigger | Outside root leaves submenu open, or child-only outside closes parent | -| FR11–FR14 | Gesture-counter and browser event-order tests | native light dismiss before/after click; menu button; hover-open; touch; backdrop; swipe | Same gesture reopens any associated trigger or exposed hover reopens panel | -| FR15–FR20 | Chromium/WebKit native host tests | Toast at root; Toast through one/two modals; CommandPalette above modal; ordinary local Popover | Global surface is behind/inert, loses state, or ordinary overlay becomes global | -| FR21–FR23 | Supported/unsupported browser matrix and public-doc checks | anchor/fixed/custom; auto/manual; explicit controls; absent feature APIs | Docs promise equivalence or reduced fallback is unusable | -| IR1–IR3 | API/type tests and mutation tests for resolvers/claims | one-at-a-time channel versus branch channel | One topmost query is reused for outside branches or timestamps replace identity | -| IR4, IR5 | Public-surface snapshot and atomic change review | local Popover, global Toast, global CommandPalette | Generic global prop appears or unrelated behaviors are coupled in one change | +| Contract | Verification | Representative states | Mutation or failure expectation | +| --------- | ---------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------- | +| FR1–FR6 | Pure registry/resolver tests plus family interaction tests | active, blocking, passive Tooltip, pinned Tooltip and HoverCard over a Dialog, visual-only, controlled owner | Passive surface wins, a pinned surface lets Escape reach the owner beneath, blocker leaks, or wrong owner closes | +| FR7–FR10 | Branch resolver tests and real-browser outside interaction | root menu with two submenu levels; parent + child Popover; associated trigger | Outside root leaves submenu open, or child-only outside closes parent | +| FR11–FR14 | Gesture-counter and browser event-order tests | native light dismiss before/after click; menu button; hover-open; touch; backdrop; swipe | Same gesture reopens any associated trigger or exposed hover reopens panel | +| FR15–FR20 | Chromium/WebKit native host tests | Toast at root; Toast through one/two modals; CommandPalette above modal; ordinary local Popover | Global surface is behind/inert, loses state, or ordinary overlay becomes global | +| FR21–FR23 | Supported/unsupported browser matrix and public-doc checks | anchor/fixed/custom; auto/manual; explicit controls; absent feature APIs | Docs promise equivalence or reduced fallback is unusable | +| IR1–IR3 | API/type tests and mutation tests for resolvers/claims | one-at-a-time channel versus branch channel | One topmost query is reused for outside branches or timestamps replace identity | +| IR4, IR5 | Public-surface snapshot and atomic change review | local Popover, global Toast, global CommandPalette | Generic global prop appears or unrelated behaviors are coupled in one change | ### Completion criteria This spec is complete only when: -- Tooltip is passive in Escape/platform and outside-branch coordination; +- an unpinned Tooltip is passive in Escape/platform and outside-branch + coordination, and a pinned Tooltip or HoverCard takes Escape first; - eligible-owner and associated-branch operations are shared and adopted by all members named by `family:overlay-dismissal` for their relevant channels; - outside a dropdown/menu root closes its entire open submenu cascade, while @@ -273,8 +279,9 @@ This spec is complete only when: **Decider:** `cixzhang`, `2026-08-30` Escape and platform close select the topmost eligible active or blocking owner, -not the topmost visible or registered surface. Tooltip is passive and cannot -shield a Popover, menu, Dialog, or associated interactive element beneath it. +not the topmost visible or registered surface. An unpinned Tooltip is passive +and cannot shield a Popover, menu, Dialog, or associated interactive element +beneath it; a pinned one is the person's own interaction (DEC-6). Rejected: letting every visible transient layer consume one Escape. That makes supplementary information block the interaction the user is trying to dismiss. @@ -329,6 +336,18 @@ not equivalent positioning, stacking, association, or dismissal behavior. Rejected: describing the current `display` fallback as a polyfill. It does not implement the missing browser systems. +### DEC-6 — A pinned layer is the person's own interaction + +**Reference:** `spec:AST-003/DEC-6` +**Decider:** `cixzhang`, `2026-10-31` + +A Tooltip or HoverCard that a press pinned open is something the person asked +to keep, so it is an active owner and Escape closes it before the owner beneath +it. A Tooltip that only previews on hover or focus stays passive. + +Rejected: keeping every Tooltip passive, which lets Escape close a Dialog behind +a tip the person pinned. + ## Open questions None. diff --git a/packages/core/src/HoverCard/HoverCard.spec.md b/packages/core/src/HoverCard/HoverCard.spec.md index a35bbece504ab..47138e3730925 100644 --- a/packages/core/src/HoverCard/HoverCard.spec.md +++ b/packages/core/src/HoverCard/HoverCard.spec.md @@ -9,7 +9,7 @@ superseded_by: null approved_by: cixzhang approved_at: 2026-10-28 owners: [cixzhang] -review_triggers: [behavior, theming, accessibility] +review_triggers: [public-api, behavior, theming, accessibility] verified_by: [ packages/core/src/HoverCard/HoverCard.test.tsx, @@ -25,24 +25,25 @@ architecture: architecture:component-theming-surface, architecture:interaction-modality, architecture:layer-runtime, + architecture:public-component-api, ] contributing: [] -system_specs: [] +system_specs: [spec:AST-003, spec:AST-017] --- # HoverCard component contract ## Contract at a glance -| Area | Contract | -| ----------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| Public contract | `HoverCard` with `children` (trigger), `content`, `label`, `placement` (`above`), `alignment` (`center`), `delay` (300), `hideDelay` (200), `focusTrigger`, `touchTrigger`, `isEnabled`, `isOpen`, `isDefaultOpen`, `onOpenChange`, and `hasHoverIndication`; target `hover-card`. | -| Behavior | A floating card of rich, possibly interactive content anchored to a trigger. It opens on hover after `delay`, on focus, and on a qualifying tap, stays open while the pointer is on the trigger or card, and closes on leave, Escape, or a tap outside. With `label` it is a named dialog the trigger advertises; without, a group that describes the trigger. | -| End-user impact | Mouse, keyboard, and touch users can open the preview, move into it to use its actions, and dismiss it; screen-reader users hear it as a named popup dialog or as the trigger's description. | -| Builder impact | None. | -| Compatibility/readiness | Records shipped behavior; no public change. Seven implementation gaps (see Current-state impact). | -| Review checks | Reject an unnamed `dialog`, a card that cannot be hovered or dismissed (WCAG 1.4.13), Escape in the page that also closes a layer underneath, or `aria-expanded` set on a trigger whose role does not support it. | -| Governing rules | WCAG 1.4.13; `family:overlay-dismissal`; `architecture:layer-runtime`; `architecture:interaction-modality`; `architecture:component-theming-surface`. | +| Area | Contract | +| ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| Public contract | `HoverCard` with `children` (trigger), `content`, `label`, `placement` (`above`), `alignment` (`center`), `delay` (300), `hideDelay` (200), `focusTrigger`, `pressTrigger` (`none`), `touchTrigger` (`tap` deprecated by `component:Tooltip` DEP-0016), `isEnabled`, `isOpen`, `isDefaultOpen`, `onOpenChange`, and `hasHoverIndication`; target `hover-card`. `useHoverCard` takes the same `pressTrigger` and `touchTrigger`. | +| Behavior | A floating card of rich, possibly interactive content anchored to a trigger. It opens on hover after `delay`, on focus, and on a qualifying tap, stays open while the pointer is on the trigger or card, and closes on leave, Escape, or a tap outside. With `label` it is a named dialog the trigger advertises; without, a group that describes the trigger. With `pressTrigger="toggle"`, hover and focus preview it, and a click, tap, Enter, or Space on a button trigger pins it open until Escape, a second press, a press outside, or focus leaving. | +| End-user impact | Mouse, keyboard, and touch users can open the preview, move into it to use its actions, and dismiss it; screen-reader users hear it as a named popup dialog or as the trigger's description. With the toggle, a pinned card stays open while people read or use it, whatever the pointer does. | +| Builder impact | A trigger whose information lives only in the card passes `pressTrigger="toggle"` and a button trigger (DEC-1). `touchTrigger="tap"` callers move to `pressTrigger="toggle"`; a codemod rewrites them (`component:Tooltip` DEP-0016). | +| Compatibility/readiness | Additive `pressTrigger`, default `none`; `touchTrigger="tap"` deprecated and `touchTrigger` folded into `pressTrigger` at the next minor (`component:Tooltip` DEP-0016, CLN-0024). FR9, FR10, and AR3 are not yet built; seven further implementation gaps (see Current-state impact). | +| Review checks | Reject an unnamed `dialog`, a card that cannot be hovered or dismissed (WCAG 1.4.13), Escape in the page that also closes a layer underneath, or `aria-expanded` set on a trigger whose role does not support it, a pinned card that closes on pointer leave, or a non-button toggle trigger without the development warning. | +| Governing rules | WCAG 1.4.13, 2.1.1; `family:overlay-dismissal`; `spec:AST-003` FR4; `spec:AST-017` (deprecation); `architecture:public-component-api` INV9, INV21; `architecture:layer-runtime`; `architecture:interaction-modality`; `architecture:component-theming-surface`. | This table is a review projection; the body below is authoritative. @@ -55,17 +56,26 @@ Consumer usage lives in `HoverCard.doc.mjs`. ## Compatibility and migration -- Released default preserved: `yes` -- Compatibility class: records shipped behavior; no public change +- Released default preserved: `yes`; `pressTrigger` defaults to `none`, which + keeps every shipped behavior +- Compatibility class: additive `pressTrigger`; `touchTrigger="tap"` is + deprecated; without `pressTrigger`, no runtime, DOM, or styling change - Controlled/uncontrolled behavior: uncontrolled by default; `isOpen` forces the state, and a controlled card reports close requests through - `onOpenChange(false)` without closing itself (FR7) -- Migration decision: none + `onOpenChange(false)` without closing itself (FR7); under + `pressTrigger="toggle"` a press on a controlled card reports the toggled + value through `onOpenChange` and does not change the state itself (FR9) +- Migration decision: `component:Tooltip` DEP-0016, then CLN-0024 in the next + minor; both cover HoverCard's and `useHoverCard`'s `touchTrigger`, with the + same codemods Consumer migration instructions belong in consumer docs and release notes. ## Current-state impact +- **Press-to-pin is not built.** `pressTrigger`, its toggle (FR9), and the + trigger wiring and announcement (FR10) are specified and not implemented. + `touchTrigger="tap"` is the shipped way to open on every tap. - **A held-closed card still opens on focus.** With `isOpen={false}`, the hover path stays shut but focus on the trigger opens the card, so a caller cannot hold it closed. Escape then only reports `onOpenChange(false)`, which the @@ -96,6 +106,7 @@ Consumer migration instructions belong in consumer docs and release notes. focus, and a hover-opened card the user has tabbed into closes when the pointer leaves the trigger, dropping focus to the page. WCAG 1.4.13 requires content shown on focus to persist until focus moves or the user dismisses it. + A card pinned by a press (FR9) is held whatever the pointer does. - **Mixed text children get no trigger.** Text children that reach React as several strings, such as `Edited {when}`, have no element to attach to: no focusable span or popup attributes are added, and hover and focus never open @@ -106,7 +117,8 @@ Consumer migration instructions belong in consumer docs and release notes. **Owns** - Trigger wiring, open and close behavior per input, card semantics, and the - `hover-card` surface (FR1–FR8). + `hover-card` surface (FR1–FR8), including the press toggle and the trigger + state it sets (FR9, FR10). **Does not own / non-goals** @@ -119,24 +131,27 @@ Consumer migration instructions belong in consumer docs and release notes. ## Public concepts -| Concept | Closed values or states | Meaning | Availability by variant/orientation/state | Default | Owner | Stability | Invalid-value behavior | -| ------------- | ------------------------- | ---------------------------------------------------------------------------------- | ----------------------------------------- | ------- | --------------------- | --------- | -------------------------------- | -| Popup role | named dialog, group | `label` makes the card a named `dialog`; without it the card is a `group`. | Every card. | group | `component:HoverCard` | Stable | Not applicable. | -| Focus trigger | `auto`, `always`, `never` | Whether focus opens the card: on focusable triggers (FR4's gap), always, or never. | Every trigger. | `auto` | `component:HoverCard` | Stable | TypeScript rejects other values. | -| Touch trigger | `auto`, `tap`, `none` | Whether a tap opens it: unless the trigger performs its own action, always, never. | Touch without hover. | `auto` | `component:HoverCard` | Stable | TypeScript rejects other values. | +| Concept | Closed values or states | Meaning | Availability by variant/orientation/state | Default | Owner | Stability | Invalid-value behavior | +| ------------- | ------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------- | ------- | --------------------- | --------------------------------------------------------------------------------------------- | -------------------------------- | +| Popup role | named dialog, group | `label` makes the card a named `dialog`; without it the card is a `group`. | Every card. | group | `component:HoverCard` | Stable | Not applicable. | +| Focus trigger | `auto`, `always`, `never` | Whether focus opens the card: on focusable triggers (FR4's gap), always, or never. | Every trigger. | `auto` | `component:HoverCard` | Stable | TypeScript rejects other values. | +| Press trigger | `none`, `toggle` | Whether a click, tap, Enter, or Space on the trigger pins the card open: never, leaving taps to `touchTrigger`, or on every press (FR9). | Button triggers; a non-button trigger keeps tap-only opening (FR9). | `none` | `component:HoverCard` | Stable | TypeScript rejects other values. | +| Touch trigger | `auto`, `tap`, `none` | Whether a tap opens it: unless the trigger performs its own action, always, never. Ignored under `pressTrigger="toggle"`. | Touch without hover. | `auto` | `component:HoverCard` | Stable; `tap` deprecated (`component:Tooltip` DEP-0016); folds into `pressTrigger` (CLN-0024) | TypeScript rejects other values. | ## Behavioral and layout contract -| ID | Invariant | Basis | -| --- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------ | -| FR1 | The open card is one surface carrying only the `hover-card` target, padded on every logical edge, with `content` inside and the theme's body font. `xstyle`, `className`, and `style` apply to the card. | Current source and tests | -| FR2 | Trigger wiring: a single string or number child is wrapped in a focusable inline span; element `children` attach to their first element inside an inline-safe wrapper. | Current source and tests | -| FR3 | With `label`, the card is `role="dialog"` named by `label`; the trigger gets `aria-haspopup="dialog"` and, only when its role supports it, `aria-expanded`, each replacing the trigger's own value while the card is wired, plus `aria-controls` naming the card while open, merged with the trigger's own; the trigger's own values return on cleanup. Without `label`, the card is `role="group"` and the trigger's `aria-describedby` gains the card's id. | Current source and tests | -| FR4 | On a hover-capable pointer, entering the trigger opens the card after `delay` ms; leaving the trigger or the card closes it after `hideDelay` ms, and moving between them keeps it open. Any focus on the trigger, keyboard, pointer, or programmatic, opens it per `focusTrigger`, except the focus a tap leaves behind, the refocus after an Escape dismiss (FR6), and the refocus after a controlled close (FR7). Implementation gap: under `auto` the tab order is read only when the trigger's listeners attach, on mount and on a re-render that re-attaches them, so a trigger rendered with `tabindex="-1"` does not open on focus until such a re-render follows its move into the tab order (Current-state impact). | WCAG 1.4.13; tests | -| FR5 | On touch, a completed tap opens it per `touchTrigger` when the finger lifts; a touch that becomes a scroll does not. A tap on the card's own content keeps it open; a second tap on the trigger, a tap outside, or a scroll starting outside closes it. | Current source and tests | -| FR6 | Escape closes a visible card through the shared dismissal stack when it is the topmost layer in the page, so a layer underneath stays open. Implementation gap: a card rendered into another document, such as an iframe or a Picture-in-Picture window, with no layer beneath it is never closed by Escape (Current-state impact). When focus was inside the card, it returns to the trigger, and that refocus does not reopen the card. | WCAG 1.4.13; tests | -| FR7 | `isOpen` forces the card shown or hidden, and `isOpen={true}` suppresses hover and focus hiding; a controlled card answers Escape by calling `onOpenChange(false)` without hiding, and setting `isOpen={false}` while focus is inside the card returns focus to the trigger. `isDefaultOpen` opens it on mount, still dismissible. Implementation gap: with `isOpen={false}`, focus on the trigger still opens the card, and Escape then only reports `onOpenChange(false)` (Current-state impact). `isEnabled={false}` disables the triggers. `onOpenChange` reports each show and hide. | Current source and tests | -| FR8 | Content renders only while open; server markup and the first client render match, including a default-open card. `hasHoverIndication` (`auto`: a single string or number child) adds a hover cue to the inline trigger. | Current source and tests | +| ID | Invariant | Basis | +| ---- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------- | +| FR1 | The open card is one surface carrying only the `hover-card` target, padded on every logical edge, with `content` inside and the theme's body font. `xstyle`, `className`, and `style` apply to the card. | Current source and tests | +| FR2 | Trigger wiring: a single string or number child is wrapped in a focusable inline span; element `children` attach to their first element inside an inline-safe wrapper. | Current source and tests | +| FR3 | With `label`, the card is `role="dialog"` named by `label`; the trigger gets `aria-haspopup="dialog"` and, only when its role supports it, `aria-expanded`, each replacing the trigger's own value while the card is wired, plus `aria-controls` naming the card while open, merged with the trigger's own; the trigger's own values return on cleanup. Without `label`, the card is `role="group"` and the trigger's `aria-describedby` gains the card's id. | Current source and tests | +| FR4 | On a hover-capable pointer, entering the trigger opens the card after `delay` ms; leaving the trigger or the card closes it after `hideDelay` ms, and moving between them keeps it open. Any focus on the trigger, keyboard, pointer, or programmatic, opens it per `focusTrigger`, except the focus a tap leaves behind, the refocus after an Escape dismiss (FR6), and the refocus after a controlled close (FR7). Implementation gap: under `auto` the tab order is read only when the trigger's listeners attach, on mount and on a re-render that re-attaches them, so a trigger rendered with `tabindex="-1"` does not open on focus until such a re-render follows its move into the tab order (Current-state impact). | WCAG 1.4.13; tests | +| FR5 | On touch, a completed tap opens it per `touchTrigger` when the finger lifts; a touch that becomes a scroll does not. A tap on the card's own content keeps it open; a second tap on the trigger, a tap outside, or a scroll starting outside closes it. Under `pressTrigger="toggle"`, FR9 decides what a tap does and `touchTrigger` is ignored. | Current source and tests | +| FR6 | Escape closes a visible card through the shared dismissal stack when it is the topmost layer in the page, so a layer underneath stays open. Implementation gap: a card rendered into another document, such as an iframe or a Picture-in-Picture window, with no layer beneath it is never closed by Escape (Current-state impact). When focus was inside the card, it returns to the trigger, and that refocus does not reopen the card. | WCAG 1.4.13; tests | +| FR7 | `isOpen` forces the card shown or hidden, and `isOpen={true}` suppresses hover and focus hiding; a controlled card answers Escape by calling `onOpenChange(false)` without hiding, and setting `isOpen={false}` while focus is inside the card returns focus to the trigger. `isDefaultOpen` opens it on mount, still dismissible. Implementation gap: with `isOpen={false}`, focus on the trigger still opens the card, and Escape then only reports `onOpenChange(false)` (Current-state impact). `isEnabled={false}` disables the triggers. `onOpenChange` reports each show and hide. | Current source and tests | +| FR8 | Content renders only while open; server markup and the first client render match, including a default-open card. `hasHoverIndication` (`auto`: a single string or number child) adds a hover cue to the inline trigger. | Current source and tests | +| FR9 | With `pressTrigger="toggle"` and a trigger whose role is `button` (a `