Skip to content

fix(Tooltip): Space on keyup and a press outside in another document, with triggerBehavior evidence - #9406

Merged
cixzhang merged 1 commit into
meta:mainfrom
vjeux:test/tooltip-trigger-behavior-matrix
Nov 3, 2026
Merged

cixzhang merged 1 commit into
meta:mainfrom
vjeux:test/tooltip-trigger-behavior-matrix

Conversation

@vjeux

@vjeux vjeux commented Nov 2, 2026 •

Copy link
Copy Markdown
Contributor

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-spec at 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 triggerBehavior verification row.

User impact

  • A 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.
  • A pinned Tooltip whose trigger renders in another document, such as an iframe, stayed open on a press outside in that document. So did a tap-opened Tooltip or HoverCard there, on a scroll starting outside, and on a tap outside when its trigger takes no focus. (A trigger that takes focus already closed on that tap, because focus left it.) The shared touch trigger's outside dismissal listened only on the main window's document.

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:Tooltip FR9: Enter or Space on a button trigger pins the tooltip, and the same press closes it; a press outside closes a pin. component:Tooltip FR5 and component:HoverCard FR5: a tap outside, or a scroll starting outside, closes a tap-opened layer. FR5 and FR9's triggerBehavior verification row names the evidence below.

Smallest restoration

  • Space on keyup: on a trigger that is not a native button, Enter presses on keydown, and Space on keyup. The Space keydown still prevents the page scroll, and it holds a flag. A click with no pointer while the flag is held, or in a key press's task, is that same press. A keyup with no keydown on the trigger does nothing, and focus leaving clears the flag.
  • Another document: the outside dismissal listens on the page's document and, when the trigger renders in another document, on that one too. It finds the layer in either.
  • The changeset states the released part: tap-opened layers in another document. The toggle itself ships unreleased with feat(Tooltip): triggerBehavior="toggle" pins the tooltip on a press #9232 in the same release, so its two refinements need no entry.
  • FR9's sentence recording the pin's press-outside gap in another document goes, with its current-state item, since this change closes it. The compatibility row's count drops to twelve.

Change (evidence)

  • Tooltip.test.tsx: a triggerBehavior on every input suite of 28 cases. adaptive by default and explicitly, hover, and toggle each 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 put touchTrigger beside the values: none and tap beside adaptive decide, while tap beside hover and none beside toggle change nothing.
  • TouchTrigger.a11y.chromium.spec.ts: a real finger (touch events) and a pen-typed press on the new TriggerBehaviors story: a tap never opens hover, opens adaptive, and pins toggle. The story's three tooltips open with no delay, so a wrong open shows at once.
  • 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.
  • Tooltip.spec.md: that verification row names this evidence in place of its gap, plus usePressPin.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

  • Reproduction before the change: With main's press pin (feat(Tooltip): triggerBehavior="toggle" pins the tooltip on a press #9232, merged), 2 of the 3 Space cases fail: Space on keyup, and a trigger that clicks itself on Space keyup. The self-click on Space keydown passes there too; it guards the keydown path. With main's touch trigger, the 3 press-outside cases in another document fail: the pin, the tap-opened tooltip (with its scroll step), and the tap-opened HoverCard. In the matrix, without feat(Tooltip): triggerBehavior="toggle" pins the tooltip on a press #9232's hover mapping 3 of 28 cases fail, and with adaptive pinning a button 5 of 28 fail (measured at 15ba105b29, with the same matrix and mapping). In Chromium, without the hover mapping, the finger and pen cases fail (6 of 6 runs, measured at dc88098; that mapping hasn't changed since).
  • Result after the change: every case passes; see Testing.
  • Representative unchanged path: a native <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.
  • Regression tests: the six new usePressPin.test.tsx cases, and the 28-case matrix with its two mutations.

Scope

  • One refinement of the unreleased press pin (Space on keyup) and one fix to the released shared touch trigger (outside dismissal in another document), with the evidence for triggerBehavior.
  • No new public API, visual representation, default, or interaction model is hidden under the bug-fix label.
  • Public text and artifacts contain no internal Meta context.

Writing rubric results

The rubric in docs/contributing/spec-writing.md, over the changed row of Tooltip.spec.md:

  • R1 narration: none; the cell names test files.
  • R2 research in the record: none.
  • R3 evidence naming a change: none.
  • R4 rejected alternatives over one line or outside a decision: none.
  • R5 pull request or issue references remaining: none.
  • R6 ruled open questions still listing options: none.

Testing

Local gates at this head, c0b401d (on main 1d04d40):

  • These pass: pnpm check:knowledge, pnpm check:changesets (with this change's changeset), pnpm check:i18n-runtime, core tsc (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.ts in Chromium twice, and Tooltip's WebKit specs ten times each in the CI profile: running at this head.
  • Before the rebase, at d46fae8 with the same own diff apart from two test lines and a comment, the reduced gate passed: 551 Layer, Tooltip, and HoverCard tests, Chromium 21 of 21 twice, and WebKit 220 of 220.

@vercel

vercel Bot commented Nov 2, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
astryx Ready Ready Preview Nov 3, 2026 12:43am UTC

Request Review

@meta-cla meta-cla Bot added the CLA Signed This label is managed by the Meta Open Source bot. label Nov 2, 2026
@astracat-bot

astracat-bot Bot commented Nov 2, 2026 •

Copy link
Copy Markdown

AI review status for this pull request.

Review status Updated
🟢 Reviewed (for maintainers only) Nov 3, 2026, 12:57 AM UTC

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 triggerBehavior feature commit, which #9232 (still at pressTrigger) 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 adaptive and hover cases of "only the toggle pins it" press a <span>, which no value can pin, so adaptive pinning a button would pass all 20 cases; press a button trigger in those rows.
  • The branch conflicts with main in Tooltip.spec.md, useTooltip.tsx and useTooltip.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(

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 |

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@vjeux
vjeux force-pushed the test/tooltip-trigger-behavior-matrix branch from 169e82b to dc88098 Compare November 2, 2026 05:37
@vjeux

vjeux commented Nov 2, 2026

Copy link
Copy Markdown
Contributor Author

Updated in dc88098:

@github-actions

github-actions Bot commented Nov 2, 2026 •

Copy link
Copy Markdown
Contributor

PR Analysis Report

🔎 Preview · c0b401d

Storybook · Sandbox · built from this PR's head

Modified components

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

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 |

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 |

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@vjeux
vjeux force-pushed the test/tooltip-trigger-behavior-matrix branch from ab1a1fe to ab9581a Compare November 2, 2026 21:57
@vjeux vjeux changed the title test(Tooltip): triggerBehavior on every input fix(Tooltip): Space on keyup and a press outside in another document, with triggerBehavior evidence Nov 2, 2026
@vjeux

vjeux commented Nov 2, 2026

Copy link
Copy Markdown
Contributor Author

Updated in ab9581a, restacked on #9232's 724181f.

  • Moved back to feat(Tooltip): triggerBehavior="toggle" pins the tooltip on a press #9232, by the maintainers' pick: the focus-leave check that uses the trigger's own window.
  • Carried here: Space on a role="button" trigger acts on keyup. The shared touch trigger's outside dismissal also listens on the trigger's own document, so a tap-opened or pinned Tooltip, and a tap-opened HoverCard, whose trigger is in an iframe closes on a tap or press outside there, or a scroll starting outside (Tooltip FR5 and FR9, HoverCard FR5). Both come with red-first cases and a changeset. Escape in another document stays FR6's recorded gap.
  • Approval: this pull request's own record change is FR5 and FR9's triggerBehavior verification row. It needs a spec owner's /approve-spec ab9581ae88479fcd09bb60a3cb7b67d36c646b43 here. FR9's text is byte-identical to feat(Tooltip): triggerBehavior="toggle" pins the tooltip on a press #9232's.
  • Button rows: the press rows press a <button> under every value. adaptive pinning a button fails 5 of the 28 cases.
  • Readiness count: the row keeps feat(Tooltip): triggerBehavior="toggle" pins the tooltip on a press #9232's "eleven further implementation gaps", against its twelve current-state items.

@cixzhang

cixzhang commented Nov 2, 2026

Copy link
Copy Markdown
Contributor

/approve-spec ab9581a

@github-actions github-actions Bot removed the needs:spec-owner-review Current knowledge records await owner approval label Nov 2, 2026

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.md files a [fix] for triggerBehavior="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.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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."

@vjeux
vjeux force-pushed the test/tooltip-trigger-behavior-matrix branch from ab9581a to 782dfad Compare November 2, 2026 23:09
@vjeux

vjeux commented Nov 2, 2026

Copy link
Copy Markdown
Contributor Author

Updated in 782dfad, restacked on #9232's 56fa37a.

@github-actions github-actions Bot added the needs:spec-owner-review Current knowledge records await owner approval label Nov 2, 2026
@cixzhang

cixzhang commented Nov 2, 2026

Copy link
Copy Markdown
Contributor

/approve-spec 782dfad

@github-actions github-actions Bot removed the needs:spec-owner-review Current knowledge records await owner approval label Nov 2, 2026

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 main so 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.md and point the jsdom tap cases at a trigger that takes no focus: 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, 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.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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>

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@vjeux
vjeux force-pushed the test/tooltip-trigger-behavior-matrix branch from 782dfad to d46fae8 Compare November 2, 2026 23:46
@vjeux

vjeux commented Nov 2, 2026

Copy link
Copy Markdown
Contributor Author

Updated in d46fae8: restacked on #9232's 11693c4 (its shared-stop helper and docs wording), with no change to this pull request's own diff.

This head needs a spec owner's /approve-spec d46fae87ca9d17077fd48c045824f42cb0922f86.

@cixzhang

cixzhang commented Nov 2, 2026

Copy link
Copy Markdown
Contributor

/approve-spec d46fae8

@vjeux
vjeux force-pushed the test/tooltip-trigger-behavior-matrix branch from d46fae8 to 795ae5a Compare November 2, 2026 23:54
@vjeux

vjeux commented Nov 2, 2026

Copy link
Copy Markdown
Contributor Author

Updated in 795ae5a, on #9232's 11693c4.

This head needs a spec owner's /approve-spec 795ae5a813629156290723c9b870f8738792d8a7.

@cixzhang

cixzhang commented Nov 2, 2026

Copy link
Copy Markdown
Contributor

/approve-spec 795ae5a

@cixzhang cixzhang added the ci:run A maintainer lets this outside contributor's pull request run CI (AST-030 FR17) label Nov 2, 2026
@cixzhang
cixzhang marked this pull request as ready for review November 2, 2026 23:58

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 main so 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]

Comment thread packages/core/src/Layer/usePressPin.ts Outdated
setIsPinnedState(next);
}, []);

// Refs hold the wired trigger and last offered element for a later rewire.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.
@vjeux
vjeux force-pushed the test/tooltip-trigger-behavior-matrix branch from 795ae5a to c0b401d Compare November 3, 2026 00:32
@vjeux

vjeux commented Nov 3, 2026

Copy link
Copy Markdown
Contributor Author

Rebased onto main 1d04d40, which carries #9232, so this branch now carries only its own commit: c0b401d, 9 files, +511/−46.

  • Suggestions taken: the pin's press-outside case in an iframe presses a <p> that takes no focus, and the usePressPin refs comment now states only the reason.
  • Red at this head: with main's press pin, the 2 Space keyup cases fail; with main's touch trigger, the 3 other-document cases fail. All 39 pass here.

This head needs a spec owner's /approve-spec c0b401d68d70c8ab4e01a47ab9890cb3a3cb7e83.

@cixzhang

cixzhang commented Nov 3, 2026

Copy link
Copy Markdown
Contributor

/approve-spec c0b401d

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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]

@cixzhang
cixzhang merged commit 9ae589b into meta:main Nov 3, 2026
61 checks passed
@vjeux
vjeux deleted the test/tooltip-trigger-behavior-matrix branch November 3, 2026 01:03
vjeux added a commit to vjeux/astryx that referenced this pull request Nov 3, 2026
`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.
vjeux added a commit to vjeux/astryx that referenced this pull request Nov 3, 2026
`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.
vjeux added a commit to vjeux/astryx that referenced this pull request Nov 3, 2026
`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.

This branch was successfully deployed

1 active deployment
Preview — c0b401d6 Deployed Nov 3, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ci:run A maintainer lets this outside contributor's pull request run CI (AST-030 FR17) CLA Signed This label is managed by the Meta Open Source bot.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants