Repository navigation
feat(Tooltip, HoverCard, cli): deprecate touchTrigger="tap" (DEP-0016), with its upgrade codemod - #9246
feat(Tooltip, HoverCard, cli): deprecate touchTrigger="tap" (DEP-0016), with its upgrade codemod#9246vjeux wants to merge 3 commits into
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
AI review status for this pull request.
|
PR Analysis Report
Generated by PR Enrichment workflow | View current CI run |
There was a problem hiding this comment.
Changes requested: the deprecation overloads and the upgrade listing work as described, but the codemod and the DEP-0016 record need fixes, and the diff should be rebased onto #9232 once it merges (2,846 authored lines today).
- The codemod rewrites
<span role="button">,<svg role="button">and<Icon role="button">triggers topressTrigger="toggle", which changes what a click, Enter or Space does; leave those with a TODO and list them. - DEP-0016 is marked
deprecatedwith its not-built note removed whileFieldLabel, LabInfoTipand theTouchTriggersstory still passtouchTrigger="tap"; migrate Lab InfoTip here and keep a current-state note for the rest. - A
pressTriggerthat changes fromnonetotoggleafter mount never wires the trigger. usePressPinkeeps the trigger element in React state, anduseTooltipanduseHoverCarduse an Effect only to copy callbacks into a ref.
[Automated review]
| if (icons.has(name.name) || name.name === 'svg') { | ||
| return true; | ||
| } | ||
| if (name.name !== 'span') { | ||
| return false; | ||
| } |
There was a problem hiding this comment.
This accepts any Core Icon, <svg> or <span> without reading its attributes, so <span role="button" tabIndex={0} onClick={open}>, <svg role="button"> and <Icon role="button" /> are rewritten to pressTrigger='toggle' with no TODO. FR9 pins a role="button" trigger, so after the upgrade a click, Enter or Space pins the layer and the trigger gains aria-expanded, unlike tap. Leaving an element with a role, tabIndex, event handler or spread for a person keeps the rewrite behavior-preserving (DEP-0016 codemod row).
| @@ -96,7 +98,7 @@ in `Tooltip.doc.mjs`. | |||
| | codemod | An `astryx upgrade` codemod staged in `packages/cli/assets/codemods/transforms/next/`, with tests and fixtures, checked by the codemod verify workflow, and available in the release that deprecates `tap`: it rewrites static `touchTrigger="tap"` to `pressTrigger="toggle"` only where the trigger is statically a non-interactive icon or span, which FR9 keeps on tap-only opening, reports every other `touchTrigger="tap"` site (an own-text trigger, a button, a link, or an unknown element) with a manual-migration note and leaves it unchanged, removes static `touchTrigger="auto"`, and reports spreads and dynamic values it cannot prove (`spec:AST-017` FR29c, FR29d) | | |||
| | downstream | In the deprecating release, Core FieldLabel's info icon moves from `touchTrigger="tap"` to `component:InfoTip` with `pressTrigger="toggle"` (`component:FieldLabel` FR9), and Lab InfoTip and the CLI page templates migrate too, the templates to a button trigger with `pressTrigger="toggle"` so they carry no development warning (`spec:AST-017` FR29c); docs, stories, templates, and examples teach only `pressTrigger` | | |||
| | transition | no prior transition | | |||
| | state | `proposed` | | |||
| | state | `deprecated` | | |||
There was a problem hiding this comment.
FieldLabel.tsx:316, packages/lab/src/InfoTip/InfoTip.tsx:117 and apps/storybook/stories/Tooltip.stories.tsx:373 still pass touchTrigger="tap", which DEP-0016's downstream row moves in the deprecating release. With the not-built item deleted, nothing in the record says so; keep a current-state item for the sites that cannot move here.
| }, []); | ||
|
|
||
| // The wired trigger, as state: its ARIA follows the pin through effects. | ||
| const [trigger, setTrigger] = useState<HTMLElement | null>(null); |
There was a problem hiding this comment.
architecture:react-component-runtime INV1 keeps DOM nodes out of React state. triggerRef already holds the node; a boolean (wired or not) in state is enough for the ARIA Effects.
| if (element == null || current.pressTrigger !== 'toggle') { | ||
| setTrigger(null); | ||
| return; |
There was a problem hiding this comment.
pressTrigger is read only when the trigger ref callback runs, and that callback does not depend on it, so a Tooltip re-rendered from none to toggle never wires its button (no pin, no aria-expanded on click).
| useEffect(() => { | ||
| touchDismissRef.current = {arm: armOutsideDismiss, disarm: clearTapOpen}; | ||
| }, [armOutsideDismiss, clearTapOpen]); |
There was a problem hiding this comment.
This Effect synchronizes no external system (INV3); it only copies two callbacks into a ref read from handlers. Assigning the ref during render, as usePressPin does with optionsRef, removes it (same in useHoverCard.tsx:463).
b9be726 to
e1e56b2
Compare
|
Fixed in e1e56b2:
Not changed:
|
There was a problem hiding this comment.
Changes requested: the codemod and record fixes from the last round hold, but a few items remain. Please land #9232 first and rebase, since the combined diff is 3,451 lines, and then address these:
- Keep an implementation-gap note for FR9's own-text pin in
Tooltip.spec.md: no component or public option reaches it yet, and<Tooltip pressTrigger="toggle">on a text trigger warns instead of pinning. - Reword the changeset's "where nothing else changes": each trigger the codemod rewrites logs FR9's development warning.
- In
usePressPin.ts, stop thepressTriggerEffect from re-wiring on mount (it logs that warning twice per trigger), warn once per trigger, and drop the unmount-onlyunwireEffect that the trigger's ref already covers.
[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 `pressTrigger="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 `pressTrigger="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. A trigger whose role is not `button` gets a development warning and keeps tap-only opening: a tap opens the tooltip as `touchTrigger="tap"` does, 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 `pressTrigger="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. | DEC-1; WCAG 1.4.13, 2.1.1 | |
There was a problem hiding this comment.
This row no longer says FR9 is unbuilt, but its own-text clause (a trigger whose tooltip repeats its text pins without being a button, with no warning) is reachable only through repeatsTriggerText on the internal useTooltipInternal, and no component passes it. <Tooltip content="Quarterly revenue by region" pressTrigger="toggle">Quarterly revenue by region</Tooltip> warns and does not pin. Please keep an implementation-gap note for that clause until something reaches it.
| '@astryxdesign/cli': patch | ||
| --- | ||
|
|
||
| [feat] `touchTrigger="tap"` on Tooltip, HoverCard, `useTooltip` and `useHoverCard` is deprecated in favor of `pressTrigger="toggle"` (DEP-0016); it keeps working as released, and beside `pressTrigger="toggle"` it is ignored. A `tap` call resolves to a `@deprecated` signature, so editors mark it with its replacement, and the docs name it. `astryx upgrade` rewrites static `touchTrigger="tap"` to `pressTrigger="toggle"` on an icon or span trigger with no `role`, `tabIndex`, event handler, or spread props, where nothing else changes, and removes static `touchTrigger="auto"`. Every other `tap` site keeps its prop with a TODO naming the manual migration, and the upgrade lists each one with its file and line among the manual migrations it cannot make: a button, a link, an element with a `role`, `tabIndex`, event handler, or spread props, a trigger whose layer repeats its own text, a `useTooltip` or `useHoverCard` options object, an element it cannot identify, a computed value, or spread props. A core codemod reports such a site by passing a `ManualMigrationRequired` to `api.report`, and still keeps its changes to the file. The table-comparison page template now uses the toggle; its modality rail becomes a button so it can pin its explanation. |
There was a problem hiding this comment.
"where nothing else changes": every trigger this rewrites is a non-button, so in development each now logs FR9's warning (twice per mount at this head). DEP-0016's migration row says production behaviour is kept and the warning appears in development; the release note should say the same.
| useEffect(() => { | ||
| wire(offeredRef.current); | ||
| }, [pressTrigger, wire]); |
There was a problem hiding this comment.
This also runs on mount, after the host's ref already called attach, so a non-button trigger reaches devWarn twice (and again on each re-attach). Skipping the call when pressTrigger matches the mode the trigger was last wired for, and warning once per trigger (useDevWarning or warnOnce), would keep the post-mount rewiring without the repeat (INV3).
| writeAttribute(trigger, 'aria-controls', isLayerOpen ? layerId : null); | ||
| }); | ||
|
|
||
| useEffect(() => unwire, [unwire]); |
There was a problem hiding this comment.
The hosts' ref already unwires on unmount: React calls it with null, and attachPressPin(null) reaches unwire through wire(null). This Effect names no system and repeats that detach, so it can go (INV3, INV6).
e1e56b2 to
c21b1fb
Compare
|
Fixed in c21b1fb:
|
There was a problem hiding this comment.
Changes requested: the deprecation metadata, the codemod and the upgrade listing match DEP-0016, but the diff still carries the stacked Tooltip and HoverCard pressTrigger commits (3,546 lines in all), and the codemod mishandles spread props. Please land #9232 and #9291 first and rebase so only the deprecation commit remains, then fix these two cases in decide() and add them to the codemod tests:
pressTrigger="toggle"followed by{...rest}andtouchTrigger="tap"losestouchTriggerwithout a TODO or listing, although a spreadpressTriggerwould override the static toggle.- A spread
touchTrigger="auto"is listed with a note telling the caller to passpressTrigger="toggle", which would change what a press does on a button trigger.
[Automated review]
| * @returns {'rewrite' | 'remove' | 'keep' | 'value' | 'press' | 'spread'} | ||
| */ | ||
| function decide(value, press, spreads) { | ||
| if (press === 'toggle') { |
There was a problem hiding this comment.
This branch runs before the spreads check, so <Tooltip pressTrigger="toggle" {...rest} touchTrigger="tap"> (and the same hook options) loses touchTrigger with no TODO or listing, although a pressTrigger in rest would override the static toggle. Checking spreads first keeps the header's promise that spread sites are reported. A spread touchTrigger="auto" also falls into SPREAD_COMMENT, whose note tells the caller to pass pressTrigger="toggle"; it needs its own outcome.
`component:Tooltip` FR9 and FR10, `spec:AST-003` FR4: - `pressTrigger` (`none`, the default, or `toggle`) on Tooltip and useTooltip, with the type TooltipPressTrigger. With `toggle` on a button trigger, a click, a tap, Enter, or Space pins the tooltip and the same press closes it; hover and focus still preview it. - A pinned tooltip stays until Escape, a second press, a press or scroll outside (the touch trigger's outside dismissal, which the pin arms: the pin owns the layer's touch trigger, so useTooltip makes one usePressPin call), or focus moving onto an element outside the trigger and the tooltip (the pin listens for it on the trigger, whatever `focusTrigger` is); a press on the tooltip's own text keeps it. It takes Escape before the layer beneath it. A `pressTrigger` that changes after mount wires or unwires the trigger, and a tooltip whose `pressTrigger` turns to `none` while pinned closes on leave again. - The press stops at its trigger. With `anchorRef`, 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 (FR9). The trigger carries aria-expanded and aria-controls on a button trigger, and the tooltip's rendered text is announced once when it pins, through useAnnounceRenderedText (`spec:AST-086` FR12). - A non-button trigger gets one development warning and keeps tap-only opening; a trigger whose tooltip repeats its own text pins without toggle state (`Layer/usePressPin.ts`, the hover layers' shared press pin). - LayerPressTrigger and the touch trigger's armOutsideDismiss (useTouchTriggerInternal) stay internal: neither is exported from the Layer entry point, and the public useTouchTrigger return is unchanged.
`component:HoverCard` FR9, FR10 and AR3, on the shared press pin (`Layer/usePressPin.ts`): - `pressTrigger` (`none`, the default, or `toggle`) on HoverCard and useHoverCard, with the type HoverCardPressTrigger. With `toggle` on a button trigger, a click, a tap, Enter, or Space pins the card and the same press closes it; hover and focus still preview 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 `pressTrigger` turns to `none` 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; otherwise the card's rendered text is announced once through useAnnounceRenderedText.
…), with its upgrade codemod `component:Tooltip` DEP-0016, `spec:AST-017` FR29a and FR29c: - `touchTrigger="tap"` resolves to a `@deprecated` overload of Tooltip, HoverCard, useTooltip and useHoverCard, so editors mark it with its replacement; the docs and JSDoc name DEP-0016. It keeps working as released. - The `astryx upgrade` codemod rewrites static `touchTrigger="tap"` to `pressTrigger="toggle"` on an icon or span trigger with no role, tabIndex, event handler, or spread props, and removes static `touchTrigger="auto"` where no props are spread; every other tap site, one beside spread props and a static toggle included, keeps its prop with a TODO and a row in the upgrade's manual-migration listing, with its file and line. A core codemod reports such a site by passing a ManualMigrationRequired to api.report, keeping its changes to the file. - The table-comparison page template's modality rail is a button with the toggle.
c21b1fb to
7290cb2
Compare
|
Fixed in 7290cb2:
Both cases are in the codemod tests, and both fail on c21b1fb. |
There was a problem hiding this comment.
Approval waits on a spec decision: Tooltip.spec.md FR9 now says a pinning press on an anchorRef trigger also reaches a plain onClick element around it, and useTooltip and useHoverCard triggers behave the same way, while the current FR9 in both the Tooltip and HoverCard records says the press stops at its trigger. An engineering owner records the decision by commenting /approve-spec <full head SHA> on this pull request (the author may do so if they are an engineering owner), or the press should stop in those paths too. Whatever is decided, these changes are still needed:
- Land #9232 and #9291 first and rebase, since the combined diff is 3,858 lines.
- Scope the "press stops at its trigger" sentence in
.changeset/press-trigger-toggle.mdand.changeset/hover-card-press-trigger.mdto the components, because a plainonClickrow around auseTooltiporuseHoverCardtrigger still receives the press. - Use
useDevWarningfor the non-button warning inusePressPin.tsinstead of the module-levelWeakSet.
[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 `pressTrigger="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 `pressTrigger="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`, 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: a tap opens the tooltip as `touchTrigger="tap"` does, 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 `pressTrigger="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: no component reaches the own-text clause yet (Current-state impact). | DEC-1; WCAG 1.4.13, 2.1.1 | |
There was a problem hiding this comment.
This adds an exception the current FR9 does not have: with anchorRef, "a plain onClick element around the anchor receives" the pinning press, where current FR9 says the press "does not reach a clickable row, card, or container around it". useTooltip and useHoverCard callers get the same behaviour with no record line. This needs an engineering owner's /approve-spec, or the press should stop in these paths too.
| '@astryxdesign/core': patch | ||
| --- | ||
|
|
||
| [feat] Tooltip (and `useTooltip`) take `pressTrigger` (type `TooltipPressTrigger`): `none` (the default, unchanged) or `toggle`. With `toggle` on a button trigger, a click, a tap, Enter, or Space pins the tooltip open and the same press closes it, while hover and focus still preview it. A pinned tooltip stays until Escape, a second press, a press outside, or focus leaving, and it takes Escape before the layer beneath it. The press stops at its trigger, so a clickable row, card, or label around it does nothing, and the trigger's own handlers still run. With `anchorRef`, Tooltip renders nothing around the trigger to stop it: the anchor's own `onClick` runs, Astryx clickable rows, cards, and containers ignore the press, and a plain `onClick` element around the anchor receives it unless the anchor's `onClick` stops it. The trigger reports `aria-expanded` and `aria-controls`, and the tooltip's text is announced once when it pins, inside a modal too (`component:Tooltip` FR9 and FR10). A trigger that is not a button gets a development warning and keeps tap-only opening; beside `pressTrigger="toggle"`, `touchTrigger` is ignored. |
There was a problem hiding this comment.
"Tooltip (and useTooltip) … The press stops at its trigger, so a clickable row, card, or label around it does nothing" is not true for useTooltip: a <div onClick> row around a hook trigger receives the pinning press (the hook's JSDoc says so). Scope the sentence to the component and say a hook caller's plain onClick ancestor still receives the press, as the anchorRef sentence does.
| '@astryxdesign/core': patch | ||
| --- | ||
|
|
||
| [feat] HoverCard (and `useHoverCard`) take `pressTrigger` (type `HoverCardPressTrigger`): `none` (the default, unchanged) or `toggle`. With `toggle` on a button trigger, a click, a tap, Enter, or Space pins the card open and the same press closes it, while hover and focus still preview it. A pinned card stays until Escape, a second press, a press outside, or focus leaving; a press on its own content keeps it, and it takes Escape before the layer beneath it. The press stops at its trigger, so a clickable row, card, or label around it does nothing, and the trigger's own handlers still run. The trigger reports `aria-expanded` and `aria-controls`, and the card's `label` (else its text) is announced once when it pins, inside a modal too (`component:HoverCard` FR9 and FR10). On a trigger that is not a button, a labelled card keeps its own popup state and tap-only opening; beside `pressTrigger="toggle"`, `touchTrigger` is ignored. |
There was a problem hiding this comment.
Same as the Tooltip changeset: the stop applies to the HoverCard component only; a plain onClick row around a useHoverCard trigger receives the pinning press.
| export type LayerPressTrigger = 'none' | 'toggle'; | ||
|
|
||
| /** Triggers already warned that a press cannot pin them: one warning each. */ | ||
| const warnedTriggers = new WeakSet<Element>(); |
There was a problem hiding this comment.
utils/devWarning.ts routes per-instance warnings to useDevWarning. usePressPin already has offeredKind, isToggleMode and wiresState, so useDevWarning(options.component, message, isToggleMode && offeredKind === 1 && wiresState) can replace this set and the devWarn call in wire().
Status: draft, stacked on #9232 and the HoverCard pull request (#9291). It contains their commits (26d7af9, 5015adf) and this change (7290cb2) until they merge. Every local gate has finished at 7290cb2; results under Testing.
Review only this pull request's change: vjeux/astryx@5015adf...7290cb2
User need
DEP-0016 deprecates
touchTrigger="tap"in favor ofpressTrigger="toggle", which #9232 (Tooltip) and the HoverCard pull request (#9291) add. Callers need three things to move: to see the deprecation where they writetap, a mechanical migration for the sites where nothing else changes, and a list of every site they must migrate by hand. Without those, a release would ship the new option with no deprecation plan.Problem and solution fit
touchTrigger="tap"call resolves to a@deprecatedsignature ofTooltip,HoverCard,useTooltipanduseHoverCard, so editors mark it with its replacement.auto,none, no value, and props a wrapper forwards resolve to signatures that are not deprecated. The docs (English and Chinese) and the JSDoc name DEP-0016.astryx upgradecodemod rewrites statictouchTrigger="tap"topressTrigger="toggle"only on an icon or span trigger (Core'sIcon, an<svg>, a<span>of text and icons, or a lone string the layer wraps in a span), with norole,tabIndex,contentEditable, event handler or spread props on it or inside it. There the result is observably the same, since FR9 keeps a non-button toggle trigger on tap-only opening. It also removes statictouchTrigger="auto"where no props are spread; beside spread props, which may set either prop, it leavesautoas it is.tapsite keeps its prop with a TODO naming the manual migration, andastryx upgradelists each one with its file and line among the manual migrations it cannot make (spec:AST-017FR29c). That covers a button, a link, an element with arole,tabIndex, event handler or spread props (FR9 would pin arole="button"one), a trigger whose layer repeats its own text, auseTooltip/useHoverCardoptions object (its trigger isn't visible there), an element it cannot identify, a computed value, and spread props, even beside a staticpressTrigger="toggle", which a spreadpressTriggerwould override. The listing repeats on every run until the site is migrated.ManualMigrationRequiredtoapi.reportand keep its changes to the file. Before, the only way to report one was to throw, which left the file unchanged and would have dropped the same file's rewrites.<button type="button" aria-label="Model modalities">around its icons, withpressTrigger="toggle", so it pins its explanation and carries no development warning.Current specification
component:TooltipDEP-0016 (its warning, migration, codemod and downstream rows),component:HoverCard(which applies DEP-0016), andspec:AST-017FR29a, FR29c and FR29d. All are current at 1d8696a (#9175).Public delta and caller burden
touchTrigger="tap"keeps working as released and is ignored besidepressTrigger="toggle". There's no runtime warning (spec:AST-017FR29, FR29a).astryx upgrade, then migrate each listed site by hand:pressTrigger="toggle"on a button whose only job is the layer; otherwise keeptapuntil the next minor (CLN-0024).API changes
@astryxdesign/coreTooltip,HoverCard,useTooltip,useHoverCard: three overload signatures each.touchTrigger?: 'auto' | 'none';touchTrigger: 'tap'is@deprecated(DEP-0016); the full props or options type for forwarded values. The implementation and every accepted value are unchanged.<Tooltip touchTrigger="tap" …>is marked deprecated in editors@astryxdesign/climigrate-touch-trigger-tap-to-press-triggerin the next release'sastryx upgradeastryx upgrade --from <version> --apply@astryxdesign/cliCodemodTransformApi.report/AstryxCodemodApi.report(signature unchanged): underastryx upgrade, a core transform that passes aManualMigrationRequiredreports a manual migration the upgrade lists, and the file keeps the transform's resultapi.report(new ManualMigrationRequired('Pass pressTrigger="toggle" by hand.', {line}))Design and alternatives
@deprecatedfor a single literal of a union, so the metadata rides an overload:tapresolves to a deprecated signature, and the overload that takes the full props keeps forwarded values unmarked and type-correct.api.report, so no new channel or receipt field is added.Evidence
tapbeside a static toggle and spread props was dropped with no TODO or listing, and atouchTrigger="auto"beside spread props was listed with a note to pass the toggle.Layer/__tests__/touchTriggerTap.deprecated.test.ts: through the TypeScript language service, everytapcall (both components, both hooks) is marked deprecated, andauto,none, no value, and forwarded props are not, with no type errors.Icon;autoremoved;nonekept;pressTriggerwins; computed, competing and spread TODOs; hook reports; idempotent;role,tabIndex, handler or spread props, or one inside it, is reported, not rewritten;tapbeside spread props is reported even next to a static toggle, which a spreadpressTriggerwould override, andtouchTrigger="auto"beside spread props is left as it is, with no TODO or listing;ManualMigrationRequiredwith its line, on every run.api/upgrade/run/outcomes.test.mjs: manual migrations a transform reports throughapi.reportare listed with their lines (src/sites.ts:2 - manual migration needed (…)), and the file keeps the transform's change.deprecated.tapuntil their own changes in the same release move them: Core FieldLabel's info icon, Lab InfoTip (its promotion to Core, feat(core): InfoTip moves to Core as the info preset over Tooltip's press toggle #9238, uses the toggle), and the Tooltip touch-triggers story.Scope
Writing rubric results
The rubric in
docs/contributing/spec-writing.md, over the changed parts ofTooltip.spec.md:Testing
Local gates at 7290cb2 (on #9291's 5015adf, on #9232's 26d7af9, on main a55352c):
pnpm check:repo,pnpm check:knowledge,pnpm check:changesets,pnpm typecheck(0 errors),pnpm lint(0 errors), CI-strict ESLint on the changed files, the private-attribute baseline check, the root build, and the Storybook build..doc.mjsfiles, which are unformatted on main too and outside the repository's formatting scope.pnpm test:ui: 17,445 tests pass.pnpm test:node: 15,118 pass. Its 2 failures, in the releasecache.test.mjs, are independent of this change (fix(cli): a crashed run's release install is recognized by the Node it ran on, whatever its name #8520).