Repository navigation
fix(Tooltip): Space on keyup and a press outside in another document, with triggerBehavior evidence - #9406
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
AI review status for this pull request.
|
There was a problem hiding this comment.
Approval waits on an engineering owner's decision: this head adds a sentence to component:Tooltip FR9 that the current record does not state, letting an anchorRef or useTooltip trigger pass the pinning press to a plain onClick element around it. An engineering owner records that decision by commenting /approve-spec 169e82bcca2acb85cf2a9dd40f7b7ab49cd54705 on this pull request, and the author may do so if they are an engineering owner; otherwise the anchored path should stop the press as FR9 says today. Whatever the ruling, these need changes:
- The branch carries the 1,998-line
triggerBehaviorfeature commit, which #9232 (still atpressTrigger) does not contain; put that commit up for review on its own and rebase this pull request onto it, so only the matrix remains. - The
adaptiveandhovercases of "only the toggle pins it" press a<span>, which no value can pin, soadaptivepinning a button would pass all 20 cases; press a button trigger in those rows. - The branch conflicts with main in
Tooltip.spec.md,useTooltip.tsxanduseTooltip.doc.mjs; after the rebase, the readiness row's "eleven further implementation gaps" becomes ten.
[Automated review]
| tap(trigger, pointerType); | ||
| await settle(); | ||
| expect(opened()).toBe(tapOpens); | ||
| expect(trigger.getAttribute('aria-expanded')).toBe( |
There was a problem hiding this comment.
Under adaptive and hover, this checks aria-expanded on a <span>, which no triggerBehavior can pin. It still passes if adaptive starts pinning a button trigger: all 20 cases stay green under that change. Pressing a <button> trigger in the adaptive and hover rows would make "only the toggle pins it" something the test can fail on.
| | FR6 | Escape closes a visible tooltip; Escape during text composition is ignored. Escape routing follows `family:overlay-dismissal`: a visible topmost tooltip in the page handles that Escape, so a layer underneath stays open for that press. Implementation gap: a tooltip 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). `spec:AST-003` FR4, accepted and not yet implemented, makes an unpinned tooltip passive and gives a pinned one Escape first (Current-state impact). Outside `triggerBehavior="toggle"`, pressing an uncontrolled tooltip's trigger closes it; a press that focuses a text-entry trigger reopens it through FR4. | WCAG 1.4.13; tests | | ||
| | FR7 | `isOpen` forces the tooltip shown or hidden, and `isOpen={true}` suppresses hover and focus hiding; a controlled tooltip answers Escape by calling `onOpenChange(false)` without hiding, and is not closed by pressing the trigger. `isDefaultOpen` opens it on mount, still dismissible. Implementation gap: with `isOpen={false}`, any focus FR4 opens on, keyboard focus or a click into a text-field trigger, still opens the tooltip and reports `onOpenChange(true)`, and Escape then only reports `onOpenChange(false)`, so it stays until focus or the pointer leaves the trigger or the tooltip, or the caller re-renders with a new `onOpenChange` (Current-state impact). `isEnabled={false}` disables triggers and drops a pending open. | Current source and tests | | ||
| | FR8 | `onOpenChange` reports each show and hide. `hasHoverIndication` (`auto`: a single string or number child) adds a hover cue to the inline trigger. | Current source and tests | | ||
| | FR9 | With `triggerBehavior="toggle"` and a trigger whose role is `button` (a `<button>` or `role="button"`), a click, a completed tap, Enter, or Space on the trigger pins the tooltip open, and the same press on a pinned tooltip closes it. Hover and keyboard focus still preview it per FR3 and FR4; on touch no preview opens and a tap pins it, while a touch that becomes a scroll pins nothing. A pinned tooltip stays open when the pointer leaves the trigger and the surface; Escape (FR6), a second press, a press outside the trigger and the surface, or focus moving outside them closes it. In a controlled tooltip a press reports the toggled value through `onOpenChange` and does not change the state itself (FR7). A press that pins or closes the layer stops at its trigger: it does not reach a clickable row, card, or container around it, or the control a label names. With `anchorRef`, or `useTooltip` with a caller's trigger, the caller's own handlers still receive the press: the anchor's `onClick` runs, and a plain `onClick` element around the anchor receives it unless the anchor's `onClick` stops it; Astryx clickable rows, cards, and containers ignore it. A trigger whose role is not `button` gets a development warning and keeps tap-only opening: every completed tap opens the tooltip, and no press pins it. A trigger whose tooltip repeats its own visible text, such as a truncated Text that is its own tab stop, may take `triggerBehavior="toggle"` without being a button: a press pins it as on a button trigger, and it gets no development warning, no `aria-expanded` or `aria-controls`, and no announcement, since its text is already its content. Implementation gap: the own-text clause is not built in Core yet (Current-state impact). | DEC-1; WCAG 1.4.13, 2.1.1 | |
There was a problem hiding this comment.
The anchorRef sentence added here (a plain onClick element around the anchor receives the pinning press) is not in the current FR9, which says the press does not reach a clickable row, card or container around the trigger. It needs an engineering owner's exact-head /approve-spec before it can govern the anchored path.
169e82b to
dc88098
Compare
|
Updated in dc88098:
|
PR Analysis Report🔎 Preview ·
|
| Component | Size | LoC | Visual · A11y · RTL |
|---|---|---|---|
| LayerProvider | 62.7 kB (+151 B) · 10.5 kB CSS (0) | — | ✓ · ✓ · ✗ |
| lab/CommentThread | 226.5 kB (+178 B) · 2.4 kB CSS (0) | — | — · — · — |
| MoreMenu | 73.0 kB (+177 B) · 15.4 kB CSS (0) | — | — · — · — |
111 more components
| Component | Size | LoC | Visual · A11y · RTL |
|---|---|---|---|
| lab/TourStep | 55.2 kB (+176 B) · 590 B CSS (0) | — | — · — · — |
| TopNav | 51.8 kB (+173 B) · 10.7 kB CSS (0) | — | — · — · — |
| AppShell | 68.5 kB (+172 B) · 12.2 kB CSS (0) | — | — · — · — |
| DropdownMenu | 72.7 kB (+171 B) · 15.4 kB CSS (0) | — | — · — · — |
| MobileNav | 50.2 kB (+170 B) · 10.4 kB CSS (0) | — | — · — · — |
| lab/Schedule | 61.3 kB (+170 B) · 3.9 kB CSS (0) | — | — · — · — |
| BaseTypeahead | 54.8 kB (+168 B) · 10.2 kB CSS (0) | — | — · — · — |
| Popover | 49.8 kB (+165 B) · 12.4 kB CSS (0) | — | — · — · — |
| Lightbox | 50.4 kB (+163 B) · 10.1 kB CSS (0) | — | — · — · — |
| TableSelectionToolbar | 52.2 kB (+161 B) · 11.5 kB CSS (0) | — | — · — · — |
| AlertDialog | 56.1 kB (+159 B) · 12.7 kB CSS (0) | — | — · — · — |
| lab/Comments | 231.8 kB (+159 B) · 2.5 kB CSS (0) | — | — · — · — |
| NumberInput | 54.9 kB (+158 B) · 11.2 kB CSS (0) | — | — · — · — |
| lab/MobilePowerSearch | 168.0 kB (+158 B) · 1.2 kB CSS (0) | — | — · — · — |
| ChatComposer | 60.8 kB (+157 B) · 11.2 kB CSS (0) | — | — · — · — |
| lab/DiffViewer | 70.7 kB (+157 B) · 2.1 kB CSS (0) | — | — · — · — |
| charts/Chart | 62.8 kB (+156 B) · 20 B CSS (0) | — | — · — · — |
| charts/ChartTooltip | 52.3 kB (+156 B) · 20 B CSS (0) | — | — · — · — |
| Avatar | 35.0 kB (+156 B) · 6.5 kB CSS (0) | — | — · — · — |
| Tokenizer | 69.9 kB (+156 B) · 17.1 kB CSS (0) | — | — · — · — |
| TopNavMegaMenu | 54.1 kB (+156 B) · 11.2 kB CSS (0) | — | — · — · — |
| lab/CommentComposer | 102.1 kB (+155 B) · 1.9 kB CSS (0) | — | — · — · — |
| FieldLabel | 36.0 kB (+154 B) · 5.9 kB CSS (0) | — | — · — · — |
| ChatComposerInput | 55.9 kB (+153 B) · 10.4 kB CSS (0) | — | — · — · — |
| ChatComposerTokenElement | 55.9 kB (+153 B) · 10.4 kB CSS (0) | — | — · — · — |
| ChatLayout | 48.4 kB (+153 B) · 10.3 kB CSS (0) | — | — · — · — |
| SideNav | 65.7 kB (+153 B) · 10.9 kB CSS (0) | — | — · — · — |
| Tooltip · Storybook | 21.0 kB (+153 B) · 5.0 kB CSS (0) | 644 (0) | ✓ · ✓ · ✓ |
| Toast | 59.4 kB (+152 B) · 10.1 kB CSS (0) | — | — · — · — |
| Calendar | 54.2 kB (+151 B) · 10.3 kB CSS (0) | — | — · — · — |
| ChatDictationButton | 45.9 kB (+151 B) · 9.8 kB CSS (0) | — | — · — · — |
| AvatarGroup | 37.7 kB (+150 B) · 6.6 kB CSS (0) | — | — · — · — |
| ChatLayoutScrollButton | 45.8 kB (+150 B) · 9.9 kB CSS (0) | — | — · — · — |
| StatusDot | 23.2 kB (+150 B) · 6.3 kB CSS (0) | — | — · — · — |
| Banner | 47.3 kB (+149 B) · 10.3 kB CSS (0) | — | — · — · — |
| TopNavMenu | 58.0 kB (+149 B) · 10.5 kB CSS (0) | — | — · — · — |
| AvatarGroupOverflow | 38.9 kB (+148 B) · 6.5 kB CSS (0) | — | — · — · — |
| CheckboxInput | 48.7 kB (+148 B) · 9.7 kB CSS (0) | — | — · — · — |
| Link | 42.8 kB (+148 B) · 7.9 kB CSS (0) | — | — · — · — |
| MobileNavToggle | 45.7 kB (+148 B) · 9.7 kB CSS (0) | — | — · — · — |
| TextArea | 47.2 kB (+148 B) · 9.8 kB CSS (0) | — | — · — · — |
| Thumbnail | 49.5 kB (+148 B) · 11.3 kB CSS (0) | — | — · — · — |
| BreadcrumbItem | 66.9 kB (+147 B) · 11.4 kB CSS (0) | — | — · — · — |
| Button | 42.3 kB (+147 B) · 9.5 kB CSS (0) | — | — · — · — |
| SideNavCollapseButton | 46.1 kB (+147 B) · 9.8 kB CSS (0) | — | — · — · — |
| Switch | 47.1 kB (+147 B) · 9.7 kB CSS (0) | — | — · — · — |
| ToggleButton | 43.2 kB (+147 B) · 9.6 kB CSS (0) | — | — · — · — |
| CodeBlock | 59.1 kB (+146 B) · 11.1 kB CSS (0) | — | — · — · — |
| Slider | 88.4 kB (+146 B) · 9.4 kB CSS (0) | — | — · — · — |
| DialogHeader | 48.4 kB (+145 B) · 10.2 kB CSS (0) | — | — · — · — |
| DrawerHeader | 48.4 kB (+145 B) · 11.6 kB CSS (0) | — | — · — · — |
| FileInput | 53.6 kB (+145 B) · 10.7 kB CSS (0) | — | — · — · — |
| TabMenu | 55.1 kB (+145 B) · 10.4 kB CSS (0) | — | — · — · — |
| IconButton | 42.3 kB (+144 B) · 9.5 kB CSS (0) | — | — · — · — |
| SideNavHeading | 58.5 kB (+144 B) · 10.6 kB CSS (0) | — | — · — · — |
| TextInput | 52.8 kB (+144 B) · 10.9 kB CSS (0) | — | — · — · — |
| TopNavHeading | 58.0 kB (+144 B) · 10.4 kB CSS (0) | — | — · — · — |
| RadioListItem | 51.2 kB (+143 B) · 10.1 kB CSS (0) | — | — · — · — |
| ChatSendButton | 45.1 kB (+142 B) · 9.5 kB CSS (0) | — | — · — · — |
| CheckboxList | 43.3 kB (+142 B) · 8.6 kB CSS (0) | — | — · — · — |
| Field | 41.4 kB (+142 B) · 8.3 kB CSS (0) | — | — · — · — |
| SideNavItem | 54.4 kB (+142 B) · 10.4 kB CSS (0) | — | — · — · — |
| TimeInput | 88.5 kB (+142 B) · 14.3 kB CSS (0) | — | — · — · — |
| charts/ChartLegend | 52.3 kB (+141 B) · 20 B CSS (0) | — | — · — · — |
| InputGroup | 42.0 kB (+141 B) · 8.4 kB CSS (0) | — | — · — · — |
| ComplexSelector | 55.3 kB (+140 B) · 11.1 kB CSS (0) | — | — · — · — |
| DateTimeInput | 114.3 kB (+140 B) · 15.8 kB CSS (0) | — | — · — · — |
| MultiSelector | 72.5 kB (+140 B) · 15.2 kB CSS (0) | — | — · — · — |
| Stepper | 46.9 kB (+140 B) · 9.9 kB CSS (0) | — | — · — · — |
| lab/InfoTip | 26.1 kB (+140 B) · 458 B CSS (0) | — | — · — · — |
| Pagination | 85.6 kB (+139 B) · 16.0 kB CSS (0) | — | — · — · — |
| PowerSearch | 183.0 kB (+139 B) · 20.6 kB CSS (0) | — | — · — · — |
| lab/MobileTokenizer | 100.7 kB (+139 B) · 880 B CSS (0) | — | — · — · — |
| lab/TransferListSelector | 80.5 kB (+139 B) · 1.9 kB CSS (0) | — | — · — · — |
| Selector | 76.7 kB (+138 B) · 15.7 kB CSS (0) | — | — · — · — |
| Typeahead | 62.5 kB (+138 B) · 11.8 kB CSS (0) | — | — · — · — |
| lab/TransferList | 74.3 kB (+138 B) · 1.7 kB CSS (0) | — | — · — · — |
| richtext/RichTextEditorToolbar | 221.4 kB (+138 B) · 454 B CSS (0) | — | — · — · — |
| ListItem | 52.7 kB (+137 B) · 11.0 kB CSS (0) | — | — · — · — |
| lab/ChatEmojiPicker | 64.8 kB (+137 B) · 773 B CSS (0) | — | — · — · — |
| richtext/RichTextView | 258.1 kB (+137 B) · 2.3 kB CSS (0) | — | — · — · — |
| Carousel | 50.6 kB (+136 B) · 10.5 kB CSS (0) | — | — · — · — |
| ContextMenu | 75.0 kB (+135 B) · 15.3 kB CSS (0) | — | — · — · — |
| RadioList | 42.9 kB (+135 B) · 8.3 kB CSS (0) | — | — · — · — |
| SegmentedControl | 36.7 kB (+135 B) · 6.5 kB CSS (0) | — | — · — · — |
| lab/ChatReactionBar | 65.4 kB (+135 B) · 1.1 kB CSS (0) | — | — · — · — |
| CheckboxListItem | 53.3 kB (+134 B) · 11.0 kB CSS (0) | — | — · — · — |
| DateInput | 102.5 kB (+134 B) · 15.3 kB CSS (0) | — | — · — · — |
| TableCell | 76.2 kB (+134 B) · 15.8 kB CSS (0) | — | — · — · — |
| TableHeaderCell | 75.8 kB (+134 B) · 15.5 kB CSS (0) | — | — · — · — |
| lab/CommentPin | 47.1 kB (+134 B) · 1.2 kB CSS (0) | — | — · — · — |
| lab/ListInput | 81.4 kB (+130 B) · 1.6 kB CSS (0) | — | — · — · — |
| DateRangeInput | 69.5 kB (+129 B) · 11.7 kB CSS (0) | — | — · — · — |
| lab/EmojiPicker | 70.1 kB (+129 B) · 722 B CSS (0) | — | — · — · — |
| Table | 84.3 kB (+125 B) · 16.0 kB CSS (0) | — | — · — · — |
| Markdown | 145.0 kB (+124 B) · 18.3 kB CSS (0) | — | — · — · — |
| richtext/RichTextEditor | 277.6 kB (+123 B) · 2.6 kB CSS (0) | — | — · — · — |
| HoverCard | 18.6 kB (+65 B) · 5.0 kB CSS (0) | — | — · — · — |
| CommandPalette | 48.5 kB (+1 B) · 12.2 kB CSS (0) | — | — · — · — |
| ChatMessageList | 22.8 kB (0) · 7.9 kB CSS (0) | — | — · — · — |
| ChatToolCalls | 25.8 kB (0) · 9.3 kB CSS (0) | — | — · — · — |
| CommandPaletteInput | 26.8 kB (0) · 8.5 kB CSS (0) | — | — · — · — |
| DropdownMenuSubMenu | 55.9 kB (0) · 9.9 kB CSS (0) | — | — · — · — |
| Heading | 7.9 kB (0) · 6.0 kB CSS (0) | — | — · — · — |
| ProgressBar | 10.5 kB (0) · 7.2 kB CSS (0) | — | — · — · — |
| Spinner | 18.5 kB (0) · 6.7 kB CSS (0) | — | — · — · — |
| Text | 7.4 kB (0) · 6.0 kB CSS (0) | — | — · — · — |
| Timer | 9.6 kB (0) · 7.0 kB CSS (0) | — | — · — · — |
| Timestamp | 21.3 kB (−1 B) · 9.9 kB CSS (0) | — | — · — · — |
| layer | — | — | — · ✓ · — |
| Layer | — | — | ✓ · ✓ · — |
Package size: @astryxdesign/core 5.95 MB packed (+649 B); no new dependencies.
API changes: none in this pull request
Committed API report behind this build: core 1 row; run pnpm api:report.
Touched components checked; add ci:run-visual, ci:run-a11y or ci:run-rtl to run the full suites.
Generated by PR Enrichment workflow | View full report
There was a problem hiding this comment.
Approval waits on an engineering owner's decision: this head adds a sentence to component:Tooltip FR9 that the current record does not state, letting a plain onClick element around an anchorRef or useTooltip trigger receive the pinning press. An engineering owner records that decision by commenting /approve-spec dc88098ad2acc2aa04b3c675e5fbee58445039f7 on this pull request, and the author may do so if they are an engineering owner; an approval given on #9232 for a different commit does not carry over, and if the direction is not intended, the anchored path should stop the press at the trigger as FR9 says today. Whatever the ruling, the branch still carries the 2,000-line triggerBehavior feature commit, which #9232's open head does not yet contain in this form, so land it through #9232 and rebase until this pull request is only its 251-line test change. The matrix itself now fails when adaptive pins a button or hover opens on a tap, so the earlier evidence gap is closed.
[Automated review]
| | FR6 | Escape closes a visible tooltip; Escape during text composition is ignored. Escape routing follows `family:overlay-dismissal`: a visible topmost tooltip in the page handles that Escape, so a layer underneath stays open for that press. Implementation gap: a tooltip 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). `spec:AST-003` FR4, accepted and not yet implemented, makes an unpinned tooltip passive and gives a pinned one Escape first (Current-state impact). Outside `triggerBehavior="toggle"`, pressing an uncontrolled tooltip's trigger closes it; a press that focuses a text-entry trigger reopens it through FR4. | WCAG 1.4.13; tests | | ||
| | FR7 | `isOpen` forces the tooltip shown or hidden, and `isOpen={true}` suppresses hover and focus hiding; a controlled tooltip answers Escape by calling `onOpenChange(false)` without hiding, and is not closed by pressing the trigger. `isDefaultOpen` opens it on mount, still dismissible. Implementation gap: with `isOpen={false}`, any focus FR4 opens on, keyboard focus or a click into a text-field trigger, still opens the tooltip and reports `onOpenChange(true)`, and Escape then only reports `onOpenChange(false)`, so it stays until focus or the pointer leaves the trigger or the tooltip, or the caller re-renders with a new `onOpenChange` (Current-state impact). `isEnabled={false}` disables triggers and drops a pending open. | Current source and tests | | ||
| | FR8 | `onOpenChange` reports each show and hide. `hasHoverIndication` (`auto`: a single string or number child) adds a hover cue to the inline trigger. | Current source and tests | | ||
| | FR9 | With `triggerBehavior="toggle"` and a trigger whose role is `button` (a `<button>` or `role="button"`), a click, a completed tap, Enter, or Space on the trigger pins the tooltip open, and the same press on a pinned tooltip closes it. Hover and keyboard focus still preview it per FR3 and FR4; on touch no preview opens and a tap pins it, while a touch that becomes a scroll pins nothing. A pinned tooltip stays open when the pointer leaves the trigger and the surface; Escape (FR6), a second press, a press outside the trigger and the surface, or focus moving outside them closes it. In a controlled tooltip a press reports the toggled value through `onOpenChange` and does not change the state itself (FR7). A press that pins or closes the layer stops at its trigger: it does not reach a clickable row, card, or container around it, or the control a label names. With `anchorRef`, or `useTooltip` with a caller's trigger, the caller's own handlers still receive the press: the anchor's `onClick` runs, and a plain `onClick` element around the anchor receives it unless the anchor's `onClick` stops it; Astryx clickable rows, cards, and containers ignore it. A trigger whose role is not `button` gets a development warning and keeps tap-only opening: every completed tap opens the tooltip, and no press pins it. A trigger whose tooltip repeats its own visible text, such as a truncated Text that is its own tab stop, may take `triggerBehavior="toggle"` without being a button: a press pins it as on a button trigger, and it gets no development warning, no `aria-expanded` or `aria-controls`, and no announcement, since its text is already its content. Implementation gap: the own-text clause is not built in Core yet (Current-state impact). | DEC-1; WCAG 1.4.13, 2.1.1 | |
There was a problem hiding this comment.
The sentence starting "With anchorRef, or useTooltip with a caller's trigger" is not in the current FR9, which says a pinning press "does not reach a clickable row, card, or container around it". It needs an engineering owner's /approve-spec at this pull request's head before it can govern the anchored path; the approval on #9232 was for a different commit.
dc88098 to
ab1a1fe
Compare
There was a problem hiding this comment.
Approval waits on an engineering owner's decision: this head adds a sentence to component:Tooltip FR9 that the current record does not state, letting a plain onClick element around an anchorRef or useTooltip trigger receive the pinning press, where FR9 today stops that press at the trigger. An engineering owner records that decision by commenting /approve-spec ab1a1febc9ef392d78c6080ea74104c44a3447e2 on this pull request, and the author may do so as an engineering owner; approvals given on #9232 name that pull request's commits and do not carry over. If that direction is not intended, the anchored path should stop the press at the trigger as FR9 says today. Whatever the ruling, the branch still carries the 2,000-line triggerBehavior feature commit, so land it through #9232 and rebase until this pull request is only its 251-line test change.
[Automated review]
| | FR6 | Escape closes a visible tooltip; Escape during text composition is ignored. Escape routing follows `family:overlay-dismissal`: a visible topmost tooltip in the page handles that Escape, so a layer underneath stays open for that press. Implementation gap: a tooltip 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). `spec:AST-003` FR4, accepted and not yet implemented, makes an unpinned tooltip passive and gives a pinned one Escape first (Current-state impact). Outside `triggerBehavior="toggle"`, pressing an uncontrolled tooltip's trigger closes it; a press that focuses a text-entry trigger reopens it through FR4. | WCAG 1.4.13; tests | | ||
| | FR7 | `isOpen` forces the tooltip shown or hidden, and `isOpen={true}` suppresses hover and focus hiding; a controlled tooltip answers Escape by calling `onOpenChange(false)` without hiding, and is not closed by pressing the trigger. `isDefaultOpen` opens it on mount, still dismissible. Implementation gap: with `isOpen={false}`, any focus FR4 opens on, keyboard focus or a click into a text-field trigger, still opens the tooltip and reports `onOpenChange(true)`, and Escape then only reports `onOpenChange(false)`, so it stays until focus or the pointer leaves the trigger or the tooltip, or the caller re-renders with a new `onOpenChange` (Current-state impact). `isEnabled={false}` disables triggers and drops a pending open. | Current source and tests | | ||
| | FR8 | `onOpenChange` reports each show and hide. `hasHoverIndication` (`auto`: a single string or number child) adds a hover cue to the inline trigger. | Current source and tests | | ||
| | FR9 | With `triggerBehavior="toggle"` and a trigger whose role is `button` (a `<button>` or `role="button"`), a click, a completed tap, Enter, or Space on the trigger pins the tooltip open, and the same press on a pinned tooltip closes it. Hover and keyboard focus still preview it per FR3 and FR4; on touch no preview opens and a tap pins it, while a touch that becomes a scroll pins nothing. A pinned tooltip stays open when the pointer leaves the trigger and the surface; Escape (FR6), a second press, a press outside the trigger and the surface, or focus moving outside them closes it. In a controlled tooltip a press reports the toggled value through `onOpenChange` and does not change the state itself (FR7). A press that pins or closes the layer stops at its trigger: it does not reach a clickable row, card, or container around it, or the control a label names. With `anchorRef`, or `useTooltip` with a caller's trigger, the caller's own handlers still receive the press: the anchor's `onClick` runs, and a plain `onClick` element around the anchor receives it unless the anchor's `onClick` stops it; Astryx clickable rows, cards, and containers ignore it. A trigger whose role is not `button` gets a development warning and keeps tap-only opening: every completed tap opens the tooltip, and no press pins it. A trigger whose tooltip repeats its own visible text, such as a truncated Text that is its own tab stop, may take `triggerBehavior="toggle"` without being a button: a press pins it as on a button trigger, and it gets no development warning, no `aria-expanded` or `aria-controls`, and no announcement, since its text is already its content. Implementation gap: the own-text clause is not built in Core yet (Current-state impact). | DEC-1; WCAG 1.4.13, 2.1.1 | |
There was a problem hiding this comment.
The sentence starting "With anchorRef, or useTooltip with a caller's trigger" is not in the current FR9, which says a pinning press "does not reach a clickable row, card, or container around it". It needs an engineering owner's /approve-spec ab1a1febc9ef392d78c6080ea74104c44a3447e2 on this pull request before it governs the anchored path; otherwise the anchored path has to stop the press at the trigger.
ab1a1fe to
ab9581a
Compare
|
Updated in ab9581a, restacked on #9232's 724181f.
|
|
/approve-spec ab9581a |
There was a problem hiding this comment.
Changes requested. The record changes are owner-approved at this head, and the Space-keyup and other-document fixes check out against Tooltip FR5/FR9 and HoverCard FR5, but the branch still carries #9232's 1,998-line feature commit, which puts it at 2,475 authored lines; once #9232 merges, rebase so only this change remains.
.changeset/tooltip-toggle-space-keyup.mdfiles a[fix]fortriggerBehavior="toggle", which this same branch introduces in.changeset/trigger-behavior-toggle.md; unless a release ships #9232 first, drop that sentence and the "or pinned" wording, or fold the keyup timing into the feature changeset.
[Automated review]
| '@astryxdesign/core': patch | ||
| --- | ||
|
|
||
| [fix] Tooltip: under `triggerBehavior="toggle"`, Space on a `role="button"` trigger pins on keyup, as on a native button, so a trigger that clicks itself on Space toggles once. A tap-opened or pinned Tooltip or HoverCard whose trigger renders in another document, such as an iframe, now closes when a tap or press outside, or a scroll starting outside, happens in that document. |
There was a problem hiding this comment.
triggerBehavior is introduced by .changeset/trigger-behavior-toggle.md in this same branch, so this [fix] describes a toggle defect no release had (and so does "or pinned"). Unless a release ships #9232 first, consider dropping the first sentence, or folding the keyup timing into the feature changeset, and keeping: "A tap-opened Tooltip or HoverCard whose trigger renders in another document, such as an iframe, now closes when a tap outside, or a scroll starting outside, happens in that document."
ab9581a to
782dfad
Compare
|
Updated in 782dfad, restacked on #9232's 56fa37a.
|
|
/approve-spec 782dfad |
There was a problem hiding this comment.
Changes requested. The record changes are owner-approved at this head, and the Space-keyup and other-document fixes match Tooltip FR5/FR9 and HoverCard FR5, with tests that fail without them and a passing Chromium run.
- Once #9232 merges, rebase this branch onto
mainso it carries only its own commit: today it also carries #9232's 1,996-line feature commit, which puts it at 2,470 authored lines. - Reword
.changeset/tooltip-toggle-space-keyup.mdand point the jsdom tap cases at a trigger that takes no focus: in Chromium onmain, a tap-opened Tooltip or HoverCard in an iframe whose trigger takes focus already closes on a tap outside because focus leaves the trigger, so what this change adds is the close on a scroll starting outside and on a tap outside when the trigger takes no focus.
[Automated review]
| '@astryxdesign/core': patch | ||
| --- | ||
|
|
||
| [fix] Tooltip, HoverCard: a tap-opened tooltip or hover card whose trigger renders in another document, such as an iframe, now closes when a tap outside, or a scroll starting outside, happens in that document. |
There was a problem hiding this comment.
In Chromium on main, a tap-opened Tooltip or HoverCard in an iframe whose trigger takes focus already closes on a tap outside, because focus leaves the trigger. Only a trigger that takes no focus, and a scroll starting outside, stayed open. Suggested wording: "…now closes on a scroll that starts outside in that document, and on a tap outside there when the trigger takes no focus."
| const {view, frame, done} = renderInFrame( | ||
| <> | ||
| <Tooltip content={TIP}> | ||
| <span tabIndex={0}>Billing date</span> |
There was a problem hiding this comment.
This trigger takes focus, so in a real engine on main the tap outside already closes the tooltip through focus leaving; the case fails on #9232's hook only because jsdom moves no focus on pointer events. A trigger without tabIndex pins the case that was broken (the HoverCard case at line 881 has the same trigger). The pointerCancel step below already pins a real failure.
782dfad to
d46fae8
Compare
|
/approve-spec d46fae8 |
d46fae8 to
795ae5a
Compare
|
Updated in 795ae5a, on #9232's 11693c4.
This head needs a spec owner's |
|
/approve-spec 795ae5a |
There was a problem hiding this comment.
Changes requested. The record changes are owner-approved at this head, and the Space-keyup and other-document fixes match Tooltip FR5/FR9 and HoverCard FR5, with new cases that fail on #9232's hooks and pass here; the changeset now states only what changed for released Tooltip and HoverCard.
- Once #9232 merges, rebase this branch onto
mainso it carries only its own commit: today it also carries #9232's 1,999-line feature commit, which puts it at 2,475 authored lines, over the 2,000-line limit.
Two small non-blocking suggestions are inline.
[Automated review]
| setIsPinnedState(next); | ||
| }, []); | ||
|
|
||
| // Refs hold the wired trigger and last offered element for a later rewire. |
There was a problem hiding this comment.
Non-blocking: this restates triggerRef and offeredRef. Keeping only the reason reads better, for example: "The last offered element lets a triggerBehavior change rewire it."
| click(trigger); | ||
| expect(trigger).toHaveAttribute('aria-expanded', 'true'); | ||
| const next = view.getByRole('button', {name: 'Next'}); | ||
| fireEvent.pointerDown(next); |
There was a problem hiding this comment.
Non-blocking: "Next" takes focus, so in Chromium this press already closes the pin through focus leaving, without the new listener. Pressing a non-focusable element, such as the <p> the tap cases use, would isolate the listener in every engine.
… with triggerBehavior evidence Two refinements of the toggle's press pin, carried here by the maintainers' ruling so meta#9232 stays under 2,000 lines: - Under `triggerBehavior="toggle"`, Space on a `role="button"` trigger acts on keyup, as on a native button. The keydown still prevents the page scroll. A click with no pointer while Space is down, or in a key press's task, is that same press, so a trigger that clicks itself on Space keydown or keyup pins once. A keyup with no keydown on the trigger does nothing. - The shared touch trigger's outside dismissal also listens on the trigger's own document. So a pinned Tooltip whose trigger renders in an iframe closes on a press outside there, and a tap-opened Tooltip or HoverCard closes on a scroll starting outside there, and on a tap outside when its trigger takes no focus (`component:Tooltip` FR5 and FR9, `component:HoverCard` FR5). FR9's gap sentence about the pin in another document goes. The evidence for `component:Tooltip` FR5 and FR9's `triggerBehavior` verification row: - Tooltip.test.tsx: `adaptive` by default and explicitly, `hover`, and `toggle`, each under a mouse hover and keyboard focus (both preview it), a touch and a pen tap (which opens it, never opens it, or pins it), and a mouse click and a tap on a button trigger, which only the toggle pins; an explicit `touchTrigger` decides taps beside `adaptive` and is ignored beside `hover` and `toggle`. - TouchTrigger.a11y.chromium.spec.ts: a real finger and a pen-typed press on the new TriggerBehaviors story: a tap never opens `hover`, opens `adaptive`, and pins `toggle`. - useTooltip.test.tsx: FR9's `useTooltip` clause with a caller's trigger under the toggle: the trigger's own onClick runs and an Astryx ClickableCard around it does not activate; a plain onClick element around it receives the press unless the trigger's onClick stops it. - The Tooltip record's `triggerBehavior` verification row names this evidence in place of its gap.
795ae5a to
c0b401d
Compare
|
Rebased onto main 1d04d40, which carries #9232, so this branch now carries only its own commit: c0b401d, 9 files, +511/−46.
This head needs a spec owner's |
|
/approve-spec c0b401d |
There was a problem hiding this comment.
This looks ready to approve.
Both fixes match the Tooltip and HoverCard specs: Space on a role="button" toggle trigger now pins once, on release, as a native button does, and a tap-opened or pinned layer whose trigger renders in an iframe now closes on a tap or a scroll that starts elsewhere in that iframe. The new cases fail without the fix and pass with it, and the record change is approved at this commit. Thanks for rebasing so the branch carries only this change.
[Automated review]
`component:HoverCard` FR9, FR10 and AR3, on the shared press pin (`Layer/usePressPin.ts`): - `triggerBehavior` on HoverCard and useHoverCard, with the closed-union type HoverCardTriggerBehavior (exported): `adaptive`, the default and the released behavior (a tap opens the card unless the trigger acts; an explicit `touchTrigger` still decides taps under it until the next minor); `hover` (touch never opens it); or `toggle`. Hover and focus preview it under every value. With `toggle` on a button trigger, a click, a tap, Enter, or Space pins the card and the same press closes it. - A pinned card stays until Escape, a second press, a press or scroll outside, or focus moving onto an element outside the trigger and the card (the pin listens for it on the trigger, whatever `focusTrigger` is); a press on its own content keeps it. It takes Escape before the layer beneath it, controlled or not. useHoverCard makes one usePressPin call, and a card whose `triggerBehavior` turns to `adaptive` while pinned closes on leave again. - The press stops at its trigger. The trigger carries aria-expanded and aria-controls on a button trigger; on any other trigger a labelled card keeps its own (FR3). When a press pins a card with a `label`, the press handler says the label once through useAnnounce (a controlled card says it once its owner shows it pinned, and nothing if the owner keeps it closed); otherwise the card's rendered text is announced once through useAnnounceRenderedText. - HoverCard.test.tsx: `adaptive` by default and explicitly, `hover`, and `toggle`, each under a mouse hover, focus, a tap, and a mouse click and a tap on a button trigger (which only the toggle pins), with `touchTrigger` beside each value (HoverCard FR5 and FR9's `triggerBehavior` row); and `useHoverCard` with a caller's trigger: its own onClick runs, an Astryx ClickableCard around it does not activate, and a plain onClick element around it receives the press unless the trigger's onClick stops it. - HoverCard FR9 states that `useHoverCard` clause, as Tooltip FR9 does for `anchorRef` and `useTooltip`; the changeset scopes the stop at the trigger to the HoverCard component. - HoverCard.test.tsx also stops a toggle press at its trigger inside an Astryx ClickableCard, an Item row, and a `<label>`. The HoverCard record links `Layer/usePressPin.ts` and `spec:AST-086` FR12 and FR14. - The HoverCard Space test presses Space down and up, as meta#9406's keyup timing needs. The API report lists HoverCard's three new rows.
`component:HoverCard` FR9, FR10 and AR3, on the shared press pin (`Layer/usePressPin.ts`): - `triggerBehavior` on HoverCard and useHoverCard, with the closed-union type HoverCardTriggerBehavior (exported): `adaptive`, the default and the released behavior (a tap opens the card unless the trigger acts; an explicit `touchTrigger` still decides taps under it until the next minor); `hover` (touch never opens it); or `toggle`. Hover and focus preview it under every value. With `toggle` on a button trigger, a click, a tap, Enter, or Space pins the card and the same press closes it. - A pinned card stays until Escape, a second press, a press or scroll outside, or focus moving onto an element outside the trigger and the card (the pin listens for it on the trigger, whatever `focusTrigger` is); a press on its own content keeps it. It takes Escape before the layer beneath it, controlled or not. useHoverCard makes one usePressPin call, and a card whose `triggerBehavior` turns to `adaptive` while pinned closes on leave again. - The press stops at its trigger. The trigger carries aria-expanded and aria-controls on a button trigger; on any other trigger a labelled card keeps its own (FR3). When a press pins a card with a `label`, the press handler says the label once through useAnnounce (a controlled card says it each time its owner shows it pinned, as an unlabelled card's text is said, and nothing for a press the owner declines); otherwise the card's rendered text is announced once through useAnnounceRenderedText. - HoverCard.test.tsx: `adaptive` by default and explicitly, `hover`, and `toggle`, each under a mouse hover, focus, a tap, and a mouse click and a tap on a button trigger (which only the toggle pins), with `touchTrigger` beside each value (HoverCard FR5 and FR9's `triggerBehavior` row); and `useHoverCard` with a caller's trigger: its own onClick runs, an Astryx ClickableCard around it does not activate, and a plain onClick element around it receives the press unless the trigger's onClick stops it. - HoverCard FR9 states that `useHoverCard` clause, as Tooltip FR9 does for `anchorRef` and `useTooltip`; the changeset scopes the stop at the trigger to the HoverCard component. - HoverCard.test.tsx also stops a toggle press at its trigger inside an Astryx ClickableCard, an Item row, and a `<label>`. The HoverCard record links `Layer/usePressPin.ts` and `spec:AST-086` FR12 and FR14. - The HoverCard Space test presses Space down and up, as meta#9406's keyup timing needs. The API report lists HoverCard's three new rows.
`component:HoverCard` FR9, FR10 and AR3, on the shared press pin (`Layer/usePressPin.ts`): - `triggerBehavior` on HoverCard and useHoverCard, with the closed-union type HoverCardTriggerBehavior (exported): `adaptive`, the default and the released behavior (a tap opens the card unless the trigger acts; an explicit `touchTrigger` still decides taps under it until the next minor); `hover` (touch never opens it); or `toggle`. Hover and focus preview it under every value. With `toggle` on a button trigger, a click, a tap, Enter, or Space pins the card and the same press closes it. - A pinned card stays until Escape, a second press, a press or scroll outside, or focus moving onto an element outside the trigger and the card (the pin listens for it on the trigger, whatever `focusTrigger` is); a press on its own content keeps it. It takes Escape before the layer beneath it, controlled or not. useHoverCard makes one usePressPin call, and a card whose `triggerBehavior` turns to `adaptive` while pinned closes on leave again. - The press stops at its trigger. The trigger carries aria-expanded and aria-controls on a button trigger; on any other trigger a labelled card keeps its own (FR3). When a press pins a card with a `label`, the press handler says the label once through useAnnounce (a controlled card says it each time its owner shows it pinned, as an unlabelled card's text is said, and nothing for a press the owner declines); otherwise the card's rendered text is announced once through useAnnounceRenderedText. - HoverCard.test.tsx: `adaptive` by default and explicitly, `hover`, and `toggle`, each under a mouse hover, focus, a tap, and a mouse click and a tap on a button trigger (which only the toggle pins), with `touchTrigger` beside each value (HoverCard FR5 and FR9's `triggerBehavior` row); and `useHoverCard` with a caller's trigger: its own onClick runs, an Astryx ClickableCard around it does not activate, and a plain onClick element around it receives the press unless the trigger's onClick stops it. - HoverCard FR9 states that `useHoverCard` clause, as Tooltip FR9 does for `anchorRef` and `useTooltip`; the changeset scopes the stop at the trigger to the HoverCard component. - HoverCard.test.tsx also stops a toggle press at its trigger inside an Astryx ClickableCard, an Item row, and a `<label>`. The HoverCard record links `Layer/usePressPin.ts` and `spec:AST-086` FR12 and FR14. - The HoverCard Space test presses Space down and up, as meta#9406's keyup timing needs. The API report lists HoverCard's three new rows.
Status: draft, on main 1d04d40, which carries #9232. This branch now carries only this change (c0b401d). It edits
Tooltip.spec.md(FR9's gap sentence and one verification row), so it needs its own/approve-specat this head (pending). Gates under Testing ran at this head; the browser runs still running say so.Review this pull request's change: https://github.com/meta/astryx/pull/9406/files
By the maintainers' ruling, this pull request carries two refinements of #9232's press pin, so that #9232 stays under 2,000 lines. It also carries the evidence for FR5 and FR9's
triggerBehaviorverification row.User impact
role="button"toggle trigger acted on Space keydown. If the trigger's own handler clicked it on Space keyup, as many button libraries do, the tooltip pinned and then unpinned at once. A native button acts on Space keyup.Problem and solution fit
The key timing now follows a native button, in the pin's own key handlers (
usePressPin.ts). The shared touch trigger's outside dismissal (useTouchTrigger.ts) also listens on the trigger's own document, so a press there reaches it. Tooltip and HoverCard both get that fix.Expected behavior and authority
component:TooltipFR9: Enter or Space on a button trigger pins the tooltip, and the same press closes it; a press outside closes a pin.component:TooltipFR5 andcomponent:HoverCardFR5: a tap outside, or a scroll starting outside, closes a tap-opened layer. FR5 and FR9'striggerBehaviorverification row names the evidence below.Smallest restoration
Change (evidence)
Tooltip.test.tsx: atriggerBehavioron every input suite of 28 cases.adaptiveby default and explicitly,hover, andtoggleeach get a mouse hover and keyboard focus (both preview it), a touch tap and a landed pen's tap on an info-only trigger (which opens it or never opens it), and a mouse click and a tap on a button trigger, which only the toggle pins (aria-expanded). Four more cases puttouchTriggerbeside the values:noneandtapbesideadaptivedecide, whiletapbesidehoverandnonebesidetogglechange nothing.TouchTrigger.a11y.chromium.spec.ts: a real finger (touch events) and a pen-typed press on the newTriggerBehaviorsstory: a tap never openshover, opensadaptive, and pinstoggle. The story's three tooltips open with no delay, so a wrong open shows at once.useTooltip.test.tsx: FR9'suseTooltipclause, with a caller's trigger under the toggle. The trigger's ownonClickruns, and an AstryxClickableCardaround it does not activate. A plainonClickelement around it receives the press unless the trigger'sonClickstops it.Tooltip.spec.md: that verification row names this evidence in place of its gap, plususePressPin.test.tsx's keyboard and close cases.usePressPin.test.tsx: Space presses on keyup (the keydown scrolls nothing and pins nothing), a trigger that clicks itself on Space keydown or keyup pins once, and a pin, and a tap-opened tooltip and HoverCard whose triggers take no focus, all in an iframe, close on a press outside there, and the tooltip also closes on a scroll starting outside.Evidence
hovermapping 3 of 28 cases fail, and withadaptivepinning a button 5 of 28 fail (measured at 15ba105b29, with the same matrix and mapping). In Chromium, without thehovermapping, the finger and pen cases fail (6 of 6 runs, measured at dc88098; that mapping hasn't changed since).<button>gets no key listener, so its own click on Enter or Space pins as before. A press outside in the page's own document closes as before.usePressPin.test.tsxcases, and the 28-case matrix with its two mutations.Scope
triggerBehavior.Writing rubric results
The rubric in
docs/contributing/spec-writing.md, over the changed row ofTooltip.spec.md:Testing
Local gates at this head, c0b401d (on main 1d04d40):
pnpm check:knowledge,pnpm check:changesets(with this change's changeset),pnpm check:i18n-runtime, coretsc(0 errors), strict ESLint and Prettier on the touched files, and the comment-length check.usePressPin.test.tsx: 39 of 39. With main's press pin, the 2 Space keyup cases fail; with main's touch trigger, the 3 other-document press and tap cases fail.ASTRYX_STRICT_LINT=1 pnpm lint, the Layer, Tooltip, and HoverCard suites,pnpm test:ui, the Storybook build,TouchTrigger.a11y.chromium.spec.tsin Chromium twice, and Tooltip's WebKit specs ten times each in the CI profile: running at this head.