Skip to content

feat(react-headless-components-preview): adopt Interest Invokers (interestfor) for Tooltip intent#36348

Open
micahgodbolt wants to merge 1 commit into
microsoft:masterfrom
micahgodbolt:feat/headless-tooltip-interest-invoker
Open

feat(react-headless-components-preview): adopt Interest Invokers (interestfor) for Tooltip intent#36348
micahgodbolt wants to merge 1 commit into
microsoft:masterfrom
micahgodbolt:feat/headless-tooltip-interest-invoker

Conversation

@micahgodbolt

Copy link
Copy Markdown
Member

Summary

Stretch proposal from #36346. Adopts the native Interest Invokers API (interestfor + interest-delay) so the headless Tooltip's popover="hint" opens on hover/focus declaratively, with no JS in supporting browsers — while keeping the existing JS timer engine as a graceful fallback everywhere else.

This complements (and is independent of) the minimal anchor-name fix in #36347.

What changed

packages/react-components/react-headless-components-preview/library/src/components/Tooltip/useTooltip.ts:

  • Wire interestfor on the trigger → the tooltip content id. The attribute is harmlessly ignored where unsupported, and is placed before the child's own props so an author can override it.
  • Feature-gate the JS timers: onEnterTrigger / onLeaveTrigger early-return when CSS.supports('interest-delay: 0s'), so we don't double-drive show/hide when the platform is already handling intent.
  • Sync state on native opens: onToggle now mirrors React state for both open and closed newState, so a browser-driven interest open keeps visible in sync (previously only closed was handled).
  • Idempotent show/hide effect: only showPopover() when not already :popover-open, and only hidePopover() when it is — so the effect never fights the platform (and never throws InvalidStateError on a redundant showPopover()).

Detection is client-only (inside the handlers); the interestfor attribute is rendered deterministically, so there's no SSR/hydration mismatch.

Why progressive enhancement

  • Supported browsers: zero JS for tooltip intent — the platform handles hover/focus, delay, and light-dismiss. Forward-compatible with the standard.
  • Unsupported browsers: identical behavior to today via the existing timer engine.

Tests

Tooltip.test.tsx:

  • interestfor on the trigger equals the tooltip content id.
  • JS fallback still opens the tooltip on hover when Interest Invokers are unsupported (jsdom default).
  • When CSS.supports('interest-delay: 0s') is stubbed true, the JS timer is skipped (the platform would open it instead).

All 19 Tooltip tests pass.

Notes / follow-ups

  • interest-delay matching: I prototyped setting interest-delay: <showDelay> <hideDelay> to match the configured JS cadence, but the positioning engine rewrites the content element's inline style, so the value was clobbered. Matching the delay cleanly belongs in the positioning style writer — happy to follow up if maintainers want it. Browsers fall back to the spec default delay in the meantime.
  • TypeScript doesn't yet ship interestfor in its DOM/React types; the attribute is plain lowercase so React passes it through to the DOM as-is.

Related: #36346 (root cause + proposals), #36347 (minimal anchor-name fix).

…erestfor) for Tooltip intent

Wire the trigger to the hint popover via the native `interestfor` attribute so
supported browsers open/close the tooltip on hover/focus with no JS. The existing
JS timer engine remains as a fallback and is skipped when CSS.supports('interest-delay: 0s')
is true to avoid double-driving. onToggle now syncs React state for native opens as
well as closes, and the show/hide effect is made idempotent against :popover-open.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@micahgodbolt micahgodbolt requested a review from a team as a code owner June 24, 2026 18:27
@github-actions

Copy link
Copy Markdown

Pull request demo site: URL

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant