-
Notifications
You must be signed in to change notification settings - Fork 2.9k
feat(react-headless-components-preview): adopt Interest Invokers (interestfor) for Tooltip intent #36348
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
micahgodbolt
wants to merge
1
commit into
microsoft:master
Choose a base branch
from
micahgodbolt:feat/headless-tooltip-interest-invoker
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+105
−6
Open
feat(react-headless-components-preview): adopt Interest Invokers (interestfor) for Tooltip intent #36348
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
7 changes: 7 additions & 0 deletions
7
change/@fluentui-react-headless-components-preview-fdcfd9eb-8a65-41a2-adbd-d1ec930e0a27.json
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| { | ||
| "type": "minor", | ||
| "comment": "Adopt native Interest Invokers (interestfor) for Tooltip hover/focus intent, with the existing JS engine as a graceful fallback", | ||
| "packageName": "@fluentui/react-headless-components-preview", | ||
| "email": "mgodbolt+microsoft@microsoft.com", | ||
| "dependentChangeType": "patch" | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -22,6 +22,17 @@ import { KEYBORG_FOCUSIN, useIsNavigatingWithKeyboard } from '@fluentui/react-ta | |
| import type { OnVisibleChangeData, TooltipProps, TooltipState, TooltipTriggerProps } from './Tooltip.types'; | ||
| import { resolvePositioningShorthand, usePositioning } from '../../positioning'; | ||
|
|
||
| /** | ||
| * Feature detection for the Interest Invokers API (`interestfor` + `interest-delay`), | ||
| * which lets the platform open a `popover=hint` on hover/focus declaratively, with no JS. | ||
| * When supported we let the browser drive show/hide and keep the JS handlers below as a | ||
| * fallback for browsers that don't support it. | ||
| * | ||
| * @see https://open-ui.org/components/interest-invokers.explainer/ | ||
| */ | ||
| const supportsInterestInvokers = () => | ||
| typeof CSS !== 'undefined' && typeof CSS.supports === 'function' && CSS.supports('interest-delay: 0s'); | ||
|
|
||
| /** | ||
| * Create the state required to render Tooltip. | ||
| * | ||
|
|
@@ -89,9 +100,10 @@ export const useTooltip = (props: TooltipProps): TooltipState => { | |
| ); | ||
|
|
||
| const onToggle = useEventCallback((event: Event) => { | ||
| if ((event as ToggleEvent).newState === 'closed') { | ||
| setVisible(undefined, { visible: false }); | ||
| } | ||
| // Keep React state in sync with the native popover regardless of what opened or | ||
| // closed it — our own JS handlers, the browser's light-dismiss, or (when supported) | ||
| // an Interest Invoker driving the `popover=hint` declaratively via `interestfor`. | ||
| setVisible(undefined, { visible: (event as ToggleEvent).newState === 'open' }); | ||
| }); | ||
|
|
||
| // Keep the tooltip in sync with the state when it is changed programmatically. | ||
|
|
@@ -105,9 +117,9 @@ export const useTooltip = (props: TooltipProps): TooltipState => { | |
| el.addEventListener('toggle', onToggle); | ||
|
|
||
| try { | ||
| if (visible) { | ||
| if (visible && !el.matches(':popover-open')) { | ||
| el.showPopover(); | ||
| } else if (el.matches(':popover-open')) { | ||
| } else if (!visible && el.matches(':popover-open')) { | ||
| el.hidePopover(); | ||
| } | ||
| } catch (error) { | ||
|
|
@@ -136,6 +148,12 @@ export const useTooltip = (props: TooltipProps): TooltipState => { | |
| const onEnterTrigger = React.useCallback( | ||
| // eslint-disable-next-line react-hooks/preserve-manual-memoization | ||
| (ev: React.PointerEvent<HTMLElement> | React.FocusEvent<HTMLElement>) => { | ||
| // When Interest Invokers are supported the browser shows the hint popover on | ||
| // hover/focus via `interestfor`; skip the JS timer so we don't double-drive it. | ||
| if (supportsInterestInvokers()) { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We should factor this out into module scope (or // module scope
const SUPPORTS_INTEREST_INVOKERS = supportsInterestInvokers();or // Component scope
const supportsInvokers = useMemo(() => supportsInterestInvokers(), []);This will always be true for the life of the application. No reason to recompute it constantly. |
||
| return; | ||
| } | ||
|
|
||
| if (ev.type === 'focus' && ignoreNextFocusEventRef.current) { | ||
| ignoreNextFocusEventRef.current = false; | ||
| return; | ||
|
|
@@ -179,6 +197,12 @@ export const useTooltip = (props: TooltipProps): TooltipState => { | |
| const onLeaveTrigger = React.useCallback( | ||
| // eslint-disable-next-line react-hooks/preserve-manual-memoization | ||
| (ev: React.PointerEvent<HTMLElement> | React.FocusEvent<HTMLElement>) => { | ||
| // When Interest Invokers are supported the browser hides the hint popover when | ||
| // interest is lost; skip the JS timer so we don't double-drive it. | ||
| if (supportsInterestInvokers()) { | ||
| return; | ||
| } | ||
|
|
||
| let delay = state.hideDelay; | ||
|
|
||
| if (ev.type === 'blur') { | ||
|
|
@@ -248,6 +272,10 @@ export const useTooltip = (props: TooltipProps): TooltipState => { | |
| // eslint-disable-next-line react-hooks/immutability | ||
| state.children = applyTriggerPropsToChildren(children, { | ||
| ...triggerAriaProps, | ||
| // Declarative hover/focus intent via the Interest Invokers API. The attribute is | ||
| // harmlessly ignored where unsupported, and the JS handlers below act as the fallback. | ||
| // Placed before the child's own props so an author can still override it. | ||
| interestfor: state.content.id, | ||
| ...child?.props, | ||
| ref: useMergedRefs( | ||
| getReactElementRef<HTMLButtonElement>(child), | ||
|
|
||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there an SSR test? This may fail.