@fluentui/react-tooltip 9.8.12 → 9.72.9-experimental.component-base-hooks.20260122-49fc330360.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,12 +1,29 @@
1
1
  # Change Log - @fluentui/react-tooltip
2
2
 
3
- This log was last generated on Wed, 17 Dec 2025 18:06:04 GMT and should not be manually modified.
3
+ This log was last generated on Thu, 22 Jan 2026 16:12:52 GMT and should not be manually modified.
4
4
 
5
5
  <!-- Start content -->
6
6
 
7
+ ## [9.72.9-experimental.component-base-hooks.20260122-49fc330360.0](https://github.com/microsoft/fluentui/tree/@fluentui/react-tooltip_v9.72.9-experimental.component-base-hooks.20260122-49fc330360.0)
8
+
9
+ Thu, 22 Jan 2026 16:12:52 GMT
10
+ [Compare changes](https://github.com/microsoft/fluentui/compare/@fluentui/react-tooltip_v9.8.12..@fluentui/react-tooltip_v9.72.9-experimental.component-base-hooks.20260122-49fc330360.0)
11
+
12
+ ### Changes
13
+
14
+ - Release 9.72.9-experimental.component-base-hooks.20260122-49fc330360 ([commit](https://github.com/microsoft/fluentui/commit/not available) by fluentui-internal@service.microsoft.com)
15
+ - Bump @fluentui/keyboard-keys to v9.72.9-experimental.component-base-hooks.20260122-49fc330360.0 ([commit](https://github.com/microsoft/fluentui/commit/373001c3e19d022ef1fcf745ea96375cf4f3aff1) by beachball)
16
+ - Bump @fluentui/react-jsx-runtime to v9.72.9-experimental.component-base-hooks.20260122-49fc330360.0 ([commit](https://github.com/microsoft/fluentui/commit/373001c3e19d022ef1fcf745ea96375cf4f3aff1) by beachball)
17
+ - Bump @fluentui/react-portal to v9.72.9-experimental.component-base-hooks.20260122-49fc330360.0 ([commit](https://github.com/microsoft/fluentui/commit/373001c3e19d022ef1fcf745ea96375cf4f3aff1) by beachball)
18
+ - Bump @fluentui/react-positioning to v9.72.9-experimental.component-base-hooks.20260122-49fc330360.0 ([commit](https://github.com/microsoft/fluentui/commit/373001c3e19d022ef1fcf745ea96375cf4f3aff1) by beachball)
19
+ - Bump @fluentui/react-shared-contexts to v9.72.9-experimental.component-base-hooks.20260122-49fc330360.0 ([commit](https://github.com/microsoft/fluentui/commit/373001c3e19d022ef1fcf745ea96375cf4f3aff1) by beachball)
20
+ - Bump @fluentui/react-tabster to v9.72.9-experimental.component-base-hooks.20260122-49fc330360.0 ([commit](https://github.com/microsoft/fluentui/commit/373001c3e19d022ef1fcf745ea96375cf4f3aff1) by beachball)
21
+ - Bump @fluentui/react-theme to v9.72.9-experimental.component-base-hooks.20260122-49fc330360.0 ([commit](https://github.com/microsoft/fluentui/commit/373001c3e19d022ef1fcf745ea96375cf4f3aff1) by beachball)
22
+ - Bump @fluentui/react-utilities to v9.72.9-experimental.component-base-hooks.20260122-49fc330360.0 ([commit](https://github.com/microsoft/fluentui/commit/373001c3e19d022ef1fcf745ea96375cf4f3aff1) by beachball)
23
+
7
24
  ## [9.8.12](https://github.com/microsoft/fluentui/tree/@fluentui/react-tooltip_v9.8.12)
8
25
 
9
- Wed, 17 Dec 2025 18:06:04 GMT
26
+ Wed, 17 Dec 2025 18:10:11 GMT
10
27
  [Compare changes](https://github.com/microsoft/fluentui/compare/@fluentui/react-tooltip_v9.8.11..@fluentui/react-tooltip_v9.8.12)
11
28
 
12
29
  ### Patches
package/lib/Tooltip.js CHANGED
@@ -1 +1 @@
1
- export { Tooltip, renderTooltip_unstable, tooltipClassNames, useTooltipStyles_unstable, useTooltip_unstable } from './components/Tooltip/index';
1
+ export { Tooltip, renderTooltip_unstable, tooltipClassNames, useTooltipStyles_unstable, useTooltip_unstable, useTooltipBase_unstable } from './components/Tooltip/index';
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/Tooltip.ts"],"sourcesContent":["export type {\n OnVisibleChangeData,\n TooltipChildProps,\n TooltipProps,\n TooltipSlots,\n TooltipState,\n} from './components/Tooltip/index';\nexport {\n Tooltip,\n renderTooltip_unstable,\n tooltipClassNames,\n useTooltipStyles_unstable,\n useTooltip_unstable,\n} from './components/Tooltip/index';\n"],"names":["Tooltip","renderTooltip_unstable","tooltipClassNames","useTooltipStyles_unstable","useTooltip_unstable"],"mappings":"AAOA,SACEA,OAAO,EACPC,sBAAsB,EACtBC,iBAAiB,EACjBC,yBAAyB,EACzBC,mBAAmB,QACd,6BAA6B"}
1
+ {"version":3,"sources":["../src/Tooltip.ts"],"sourcesContent":["export type {\n OnVisibleChangeData,\n TooltipChildProps,\n TooltipBaseProps,\n TooltipProps,\n TooltipSlots,\n TooltipBaseState,\n TooltipState,\n} from './components/Tooltip/index';\nexport {\n Tooltip,\n renderTooltip_unstable,\n tooltipClassNames,\n useTooltipStyles_unstable,\n useTooltip_unstable,\n useTooltipBase_unstable,\n} from './components/Tooltip/index';\n"],"names":["Tooltip","renderTooltip_unstable","tooltipClassNames","useTooltipStyles_unstable","useTooltip_unstable","useTooltipBase_unstable"],"mappings":"AASA,SACEA,OAAO,EACPC,sBAAsB,EACtBC,iBAAiB,EACjBC,yBAAyB,EACzBC,mBAAmB,EACnBC,uBAAuB,QAClB,6BAA6B"}
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/components/Tooltip/Tooltip.types.ts"],"sourcesContent":["import * as React from 'react';\nimport type { PositioningShorthand } from '@fluentui/react-positioning';\nimport type { ComponentProps, ComponentState, JSXElement, Slot, TriggerProps } from '@fluentui/react-utilities';\nimport type { PortalProps } from '@fluentui/react-portal';\n\n/**\n * Slot properties for Tooltip\n */\nexport type TooltipSlots = {\n /**\n * The text or JSX content of the tooltip.\n */\n content: NonNullable<Slot<'div'>>;\n};\n\n/**\n * The properties that are added to the child of the Tooltip\n */\nexport type TooltipChildProps = {\n ref?: React.Ref<unknown>;\n} & Pick<\n React.HTMLAttributes<HTMLElement>,\n | 'aria-describedby'\n | 'aria-label'\n | 'aria-labelledby'\n | 'onBlur'\n | 'onFocus'\n | 'onPointerEnter'\n | 'onPointerLeave'\n | 'aria-haspopup'\n | 'aria-expanded'\n>;\n\n/**\n * Data for the Tooltip's onVisibleChange event.\n */\nexport type OnVisibleChangeData = {\n visible: boolean;\n\n /**\n * The event object, if this visibility change was triggered by a keyboard event on the document element\n * (such as Escape to hide the visible tooltip). Otherwise undefined.\n */\n documentKeyboardEvent?: KeyboardEvent;\n};\n\n/**\n * Properties for Tooltip\n */\nexport type TooltipProps = ComponentProps<TooltipSlots> &\n TriggerProps<TooltipChildProps> &\n Pick<PortalProps, 'mountNode'> & {\n /**\n * The tooltip's visual appearance.\n * * `normal` - Uses the theme's background and text colors.\n * * `inverted` - Higher contrast variant that uses the theme's inverted colors.\n *\n * @default normal\n */\n appearance?: 'normal' | 'inverted';\n /**\n * Delay before the tooltip is hidden, in milliseconds.\n *\n * @default 250\n */\n hideDelay?: number;\n\n /**\n * Notification when the visibility of the tooltip is changing.\n *\n * **Note**: for backwards compatibility, `event` will be undefined if this was triggered by a keyboard event on\n * the document element. Use `data.documentKeyboardEvent` if the keyboard event object is needed.\n */\n // eslint-disable-next-line @nx/workspace-consistent-callback-type -- can't change type of existing callback\n onVisibleChange?: (\n event: React.PointerEvent<HTMLElement> | React.FocusEvent<HTMLElement> | undefined,\n data: OnVisibleChangeData,\n ) => void;\n\n /**\n * Configure the positioning of the tooltip\n *\n * @default above\n */\n positioning?: PositioningShorthand;\n\n /**\n * (Required) Specifies whether this tooltip is acting as the description or label of its trigger element.\n *\n * * `label` - The tooltip sets the trigger's aria-label or aria-labelledby attribute. This is useful for buttons\n * displaying only an icon, for example.\n * * `description` - The tooltip sets the trigger's aria-description or aria-describedby attribute.\n * * `inaccessible` - No aria attributes are set on the trigger. This makes the tooltip's content inaccessible to\n * screen readers, and should only be used if the tooltip's text is available by some other means.\n */\n relationship: 'label' | 'description' | 'inaccessible';\n\n /**\n * Delay before the tooltip is shown, in milliseconds.\n *\n * @default 250\n */\n showDelay?: number;\n\n /**\n * Control the tooltip's visibility programatically.\n *\n * This can be used in conjunction with onVisibleChange to modify the tooltip's show and hide behavior.\n *\n * If not provided, the visibility will be controlled by the tooltip itself, based on hover and focus events on the\n * trigger (child) element.\n *\n * @default false\n */\n visible?: boolean;\n\n /**\n * Render an arrow pointing to the target element\n *\n * @default false\n */\n withArrow?: boolean;\n };\n\n/**\n * State used in rendering Tooltip\n */\nexport type TooltipState = ComponentState<TooltipSlots> &\n Pick<TooltipProps, 'mountNode' | 'relationship'> &\n Required<Pick<TooltipProps, 'appearance' | 'hideDelay' | 'positioning' | 'showDelay' | 'visible' | 'withArrow'>> & {\n children?: JSXElement | null;\n\n /**\n * Whether the tooltip should be rendered to the DOM.\n */\n shouldRenderTooltip?: boolean;\n\n /**\n * Ref to the arrow element\n */\n arrowRef?: React.Ref<HTMLDivElement>;\n\n /**\n * CSS class for the arrow element\n */\n arrowClassName?: string;\n };\n"],"names":["React"],"mappings":"AAAA,YAAYA,WAAW,QAAQ"}
1
+ {"version":3,"sources":["../src/components/Tooltip/Tooltip.types.ts"],"sourcesContent":["import * as React from 'react';\nimport type { PositioningShorthand } from '@fluentui/react-positioning';\nimport type { ComponentProps, ComponentState, JSXElement, Slot, TriggerProps } from '@fluentui/react-utilities';\nimport type { PortalProps } from '@fluentui/react-portal';\n\n/**\n * Slot properties for Tooltip\n */\nexport type TooltipSlots = {\n /**\n * The text or JSX content of the tooltip.\n */\n content: NonNullable<Slot<'div'>>;\n};\n\n/**\n * The properties that are added to the child of the Tooltip\n */\nexport type TooltipChildProps = {\n ref?: React.Ref<unknown>;\n} & Pick<\n React.HTMLAttributes<HTMLElement>,\n | 'aria-describedby'\n | 'aria-label'\n | 'aria-labelledby'\n | 'onBlur'\n | 'onFocus'\n | 'onPointerEnter'\n | 'onPointerLeave'\n | 'aria-haspopup'\n | 'aria-expanded'\n>;\n\n/**\n * Data for the Tooltip's onVisibleChange event.\n */\nexport type OnVisibleChangeData = {\n visible: boolean;\n\n /**\n * The event object, if this visibility change was triggered by a keyboard event on the document element\n * (such as Escape to hide the visible tooltip). Otherwise undefined.\n */\n documentKeyboardEvent?: KeyboardEvent;\n};\n\n/**\n * Properties for Tooltip\n */\nexport type TooltipProps = ComponentProps<TooltipSlots> &\n TriggerProps<TooltipChildProps> &\n Pick<PortalProps, 'mountNode'> & {\n /**\n * The tooltip's visual appearance.\n * * `normal` - Uses the theme's background and text colors.\n * * `inverted` - Higher contrast variant that uses the theme's inverted colors.\n *\n * @default normal\n */\n appearance?: 'normal' | 'inverted';\n /**\n * Delay before the tooltip is hidden, in milliseconds.\n *\n * @default 250\n */\n hideDelay?: number;\n\n /**\n * Notification when the visibility of the tooltip is changing.\n *\n * **Note**: for backwards compatibility, `event` will be undefined if this was triggered by a keyboard event on\n * the document element. Use `data.documentKeyboardEvent` if the keyboard event object is needed.\n */\n // eslint-disable-next-line @nx/workspace-consistent-callback-type -- can't change type of existing callback\n onVisibleChange?: (\n event: React.PointerEvent<HTMLElement> | React.FocusEvent<HTMLElement> | undefined,\n data: OnVisibleChangeData,\n ) => void;\n\n /**\n * Configure the positioning of the tooltip\n *\n * @default above\n */\n positioning?: PositioningShorthand;\n\n /**\n * (Required) Specifies whether this tooltip is acting as the description or label of its trigger element.\n *\n * * `label` - The tooltip sets the trigger's aria-label or aria-labelledby attribute. This is useful for buttons\n * displaying only an icon, for example.\n * * `description` - The tooltip sets the trigger's aria-description or aria-describedby attribute.\n * * `inaccessible` - No aria attributes are set on the trigger. This makes the tooltip's content inaccessible to\n * screen readers, and should only be used if the tooltip's text is available by some other means.\n */\n relationship: 'label' | 'description' | 'inaccessible';\n\n /**\n * Delay before the tooltip is shown, in milliseconds.\n *\n * @default 250\n */\n showDelay?: number;\n\n /**\n * Control the tooltip's visibility programatically.\n *\n * This can be used in conjunction with onVisibleChange to modify the tooltip's show and hide behavior.\n *\n * If not provided, the visibility will be controlled by the tooltip itself, based on hover and focus events on the\n * trigger (child) element.\n *\n * @default false\n */\n visible?: boolean;\n\n /**\n * Render an arrow pointing to the target element\n *\n * @default false\n */\n withArrow?: boolean;\n };\n\nexport type TooltipBaseProps = Omit<TooltipProps, 'appearance'>;\n\n/**\n * State used in rendering Tooltip\n */\nexport type TooltipState = ComponentState<TooltipSlots> &\n Pick<TooltipProps, 'mountNode' | 'relationship'> &\n Required<Pick<TooltipProps, 'appearance' | 'hideDelay' | 'positioning' | 'showDelay' | 'visible' | 'withArrow'>> & {\n children?: JSXElement | null;\n\n /**\n * Whether the tooltip should be rendered to the DOM.\n */\n shouldRenderTooltip?: boolean;\n\n /**\n * Ref to the arrow element\n */\n arrowRef?: React.Ref<HTMLDivElement>;\n\n /**\n * CSS class for the arrow element\n */\n arrowClassName?: string;\n };\n\nexport type TooltipBaseState = Omit<TooltipState, 'appearance'>;\n"],"names":["React"],"mappings":"AAAA,YAAYA,WAAW,QAAQ"}
@@ -1,4 +1,5 @@
1
1
  export { Tooltip } from './Tooltip';
2
2
  export { renderTooltip_unstable } from './renderTooltip';
3
3
  export { useTooltip_unstable } from './useTooltip';
4
+ export { useTooltipBase_unstable } from './useTooltipBase';
4
5
  export { tooltipClassNames, useTooltipStyles_unstable } from './useTooltipStyles.styles';
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/components/Tooltip/index.ts"],"sourcesContent":["export { Tooltip } from './Tooltip';\nexport type { OnVisibleChangeData, TooltipChildProps, TooltipProps, TooltipSlots, TooltipState } from './Tooltip.types';\nexport { renderTooltip_unstable } from './renderTooltip';\nexport { useTooltip_unstable } from './useTooltip';\nexport { tooltipClassNames, useTooltipStyles_unstable } from './useTooltipStyles.styles';\n"],"names":["Tooltip","renderTooltip_unstable","useTooltip_unstable","tooltipClassNames","useTooltipStyles_unstable"],"mappings":"AAAA,SAASA,OAAO,QAAQ,YAAY;AAEpC,SAASC,sBAAsB,QAAQ,kBAAkB;AACzD,SAASC,mBAAmB,QAAQ,eAAe;AACnD,SAASC,iBAAiB,EAAEC,yBAAyB,QAAQ,4BAA4B"}
1
+ {"version":3,"sources":["../src/components/Tooltip/index.ts"],"sourcesContent":["export { Tooltip } from './Tooltip';\nexport type {\n OnVisibleChangeData,\n TooltipChildProps,\n TooltipBaseProps,\n TooltipProps,\n TooltipSlots,\n TooltipBaseState,\n TooltipState,\n} from './Tooltip.types';\nexport { renderTooltip_unstable } from './renderTooltip';\nexport { useTooltip_unstable } from './useTooltip';\nexport { useTooltipBase_unstable } from './useTooltipBase';\nexport { tooltipClassNames, useTooltipStyles_unstable } from './useTooltipStyles.styles';\n"],"names":["Tooltip","renderTooltip_unstable","useTooltip_unstable","useTooltipBase_unstable","tooltipClassNames","useTooltipStyles_unstable"],"mappings":"AAAA,SAASA,OAAO,QAAQ,YAAY;AAUpC,SAASC,sBAAsB,QAAQ,kBAAkB;AACzD,SAASC,mBAAmB,QAAQ,eAAe;AACnD,SAASC,uBAAuB,QAAQ,mBAAmB;AAC3D,SAASC,iBAAiB,EAAEC,yBAAyB,QAAQ,4BAA4B"}
@@ -1,12 +1,5 @@
1
1
  'use client';
2
- import * as React from 'react';
3
- import { mergeArrowOffset, resolvePositioningShorthand, usePositioning } from '@fluentui/react-positioning';
4
- import { useTooltipVisibility_unstable as useTooltipVisibility, useFluent_unstable as useFluent } from '@fluentui/react-shared-contexts';
5
- import { KEYBORG_FOCUSIN, useIsNavigatingWithKeyboard } from '@fluentui/react-tabster';
6
- import { applyTriggerPropsToChildren, useControllableState, useId, useIsomorphicLayoutEffect, useIsSSR, useMergedRefs, getTriggerChild, mergeCallbacks, useEventCallback, slot, getReactElementRef } from '@fluentui/react-utilities';
7
- import { arrowHeight, tooltipBorderRadius } from './private/constants';
8
- import { useTooltipTimeout } from './private/useTooltipTimeout';
9
- import { Escape } from '@fluentui/keyboard-keys';
2
+ import { useTooltipBase_unstable } from './useTooltipBase';
10
3
  /**
11
4
  * Create the state required to render Tooltip.
12
5
  *
@@ -16,215 +9,10 @@ import { Escape } from '@fluentui/keyboard-keys';
16
9
  * @param props - props from this instance of Tooltip
17
10
  */ export const useTooltip_unstable = (props)=>{
18
11
  'use no memo';
19
- var _child_props, _child_props1, _child_props2, _child_props3, _child_props4, _child_props5, _child_props6;
20
- const context = useTooltipVisibility();
21
- const isServerSideRender = useIsSSR();
22
- const { targetDocument } = useFluent();
23
- const [visible, setVisibleInternal] = useControllableState({
24
- state: props.visible,
25
- initialState: false
26
- });
27
- const { appearance = 'normal', children, content, withArrow = false, positioning = 'above', onVisibleChange, relationship, showDelay = 250, hideDelay = 250, mountNode } = props;
28
- const state = {
29
- withArrow,
30
- positioning,
31
- showDelay,
32
- hideDelay,
33
- relationship,
34
- visible,
35
- shouldRenderTooltip: visible,
12
+ const { appearance = 'normal' } = props;
13
+ const state = useTooltipBase_unstable(props);
14
+ return {
36
15
  appearance,
37
- mountNode,
38
- // Slots
39
- components: {
40
- content: 'div'
41
- },
42
- content: slot.always(content, {
43
- defaultProps: {
44
- role: 'tooltip'
45
- },
46
- elementType: 'div'
47
- })
16
+ ...state
48
17
  };
49
- state.content.id = useId('tooltip-', state.content.id);
50
- const positioningOptions = {
51
- enabled: state.visible,
52
- arrowPadding: 2 * tooltipBorderRadius,
53
- position: 'above',
54
- align: 'center',
55
- offset: 4,
56
- ...resolvePositioningShorthand(state.positioning)
57
- };
58
- if (state.withArrow) {
59
- positioningOptions.offset = mergeArrowOffset(positioningOptions.offset, arrowHeight);
60
- }
61
- const { targetRef, containerRef, arrowRef } = usePositioning(positioningOptions);
62
- const [setDelayTimeout, clearDelayTimeout] = useTooltipTimeout(containerRef);
63
- const setVisible = React.useCallback((ev, data)=>{
64
- clearDelayTimeout();
65
- setVisibleInternal((oldVisible)=>{
66
- if (data.visible !== oldVisible) {
67
- onVisibleChange === null || onVisibleChange === void 0 ? void 0 : onVisibleChange(ev, data);
68
- }
69
- return data.visible;
70
- });
71
- }, [
72
- clearDelayTimeout,
73
- setVisibleInternal,
74
- onVisibleChange
75
- ]);
76
- state.content.ref = useMergedRefs(state.content.ref, containerRef);
77
- state.arrowRef = arrowRef;
78
- // When this tooltip is visible, hide any other tooltips, and register it
79
- // as the visibleTooltip with the TooltipContext.
80
- // Also add a listener on document to hide the tooltip if Escape is pressed
81
- useIsomorphicLayoutEffect(()=>{
82
- if (visible) {
83
- var _context_visibleTooltip;
84
- const thisTooltip = {
85
- hide: (ev)=>setVisible(undefined, {
86
- visible: false,
87
- documentKeyboardEvent: ev
88
- })
89
- };
90
- (_context_visibleTooltip = context.visibleTooltip) === null || _context_visibleTooltip === void 0 ? void 0 : _context_visibleTooltip.hide();
91
- context.visibleTooltip = thisTooltip;
92
- const onDocumentKeyDown = (ev)=>{
93
- if (ev.key === Escape && !ev.defaultPrevented) {
94
- thisTooltip.hide(ev);
95
- // stop propagation to avoid conflicting with other elements that listen for `Escape`
96
- // e,g: Dialog, Popover, Menu and Tooltip
97
- ev.preventDefault();
98
- }
99
- };
100
- targetDocument === null || targetDocument === void 0 ? void 0 : targetDocument.addEventListener('keydown', onDocumentKeyDown, {
101
- // As this event is added at targeted document,
102
- // we need to capture the event to be sure keydown handling from tooltip happens first
103
- capture: true
104
- });
105
- return ()=>{
106
- if (context.visibleTooltip === thisTooltip) {
107
- context.visibleTooltip = undefined;
108
- }
109
- targetDocument === null || targetDocument === void 0 ? void 0 : targetDocument.removeEventListener('keydown', onDocumentKeyDown, {
110
- capture: true
111
- });
112
- };
113
- }
114
- }, [
115
- context,
116
- targetDocument,
117
- visible,
118
- setVisible
119
- ]);
120
- // Used to skip showing the tooltip in certain situations when the trigger is focused.
121
- // See comments where this is set for more info.
122
- const ignoreNextFocusEventRef = React.useRef(false);
123
- // Listener for onPointerEnter and onFocus on the trigger element
124
- const onEnterTrigger = React.useCallback((ev)=>{
125
- if (ev.type === 'focus' && ignoreNextFocusEventRef.current) {
126
- ignoreNextFocusEventRef.current = false;
127
- return;
128
- }
129
- // Show immediately if another tooltip is already visible
130
- const delay = context.visibleTooltip ? 0 : state.showDelay;
131
- setDelayTimeout(()=>{
132
- setVisible(ev, {
133
- visible: true
134
- });
135
- }, delay);
136
- ev.persist(); // Persist the event since the setVisible call will happen asynchronously
137
- }, [
138
- setDelayTimeout,
139
- setVisible,
140
- state.showDelay,
141
- context
142
- ]);
143
- const isNavigatingWithKeyboard = useIsNavigatingWithKeyboard();
144
- // Callback ref that attaches a keyborg:focusin event listener.
145
- const [keyborgListenerCallbackRef] = React.useState(()=>{
146
- const onKeyborgFocusIn = (ev)=>{
147
- var _ev_detail;
148
- // Skip showing the tooltip if focus moved programmatically.
149
- // For example, we don't want to show the tooltip when a dialog is closed
150
- // and Tabster programmatically restores focus to the trigger button.
151
- // See https://github.com/microsoft/fluentui/issues/27576
152
- if (((_ev_detail = ev.detail) === null || _ev_detail === void 0 ? void 0 : _ev_detail.isFocusedProgrammatically) && !isNavigatingWithKeyboard()) {
153
- ignoreNextFocusEventRef.current = true;
154
- }
155
- };
156
- // Save the current element to remove the listener when the ref changes
157
- let current = null;
158
- // Callback ref that attaches the listener to the element
159
- return (element)=>{
160
- current === null || current === void 0 ? void 0 : current.removeEventListener(KEYBORG_FOCUSIN, onKeyborgFocusIn);
161
- element === null || element === void 0 ? void 0 : element.addEventListener(KEYBORG_FOCUSIN, onKeyborgFocusIn);
162
- current = element;
163
- };
164
- });
165
- // Listener for onPointerLeave and onBlur on the trigger element
166
- const onLeaveTrigger = React.useCallback((ev)=>{
167
- let delay = state.hideDelay;
168
- if (ev.type === 'blur') {
169
- // Hide immediately when losing focus
170
- delay = 0;
171
- // The focused element gets a blur event when the document loses focus
172
- // (e.g. switching tabs in the browser), but we don't want to show the
173
- // tooltip again when the document gets focus back. Handle this case by
174
- // checking if the blurred element is still the document's activeElement.
175
- // See https://github.com/microsoft/fluentui/issues/13541
176
- ignoreNextFocusEventRef.current = (targetDocument === null || targetDocument === void 0 ? void 0 : targetDocument.activeElement) === ev.target;
177
- }
178
- setDelayTimeout(()=>{
179
- setVisible(ev, {
180
- visible: false
181
- });
182
- }, delay);
183
- ev.persist(); // Persist the event since the setVisible call will happen asynchronously
184
- }, [
185
- setDelayTimeout,
186
- setVisible,
187
- state.hideDelay,
188
- targetDocument
189
- ]);
190
- // Cancel the hide timer when the mouse or focus enters the tooltip, and restart it when the mouse or focus leaves.
191
- // This keeps the tooltip visible when the mouse is moved over it, or it has focus within.
192
- state.content.onPointerEnter = mergeCallbacks(state.content.onPointerEnter, clearDelayTimeout);
193
- state.content.onPointerLeave = mergeCallbacks(state.content.onPointerLeave, onLeaveTrigger);
194
- state.content.onFocus = mergeCallbacks(state.content.onFocus, clearDelayTimeout);
195
- state.content.onBlur = mergeCallbacks(state.content.onBlur, onLeaveTrigger);
196
- const child = getTriggerChild(children);
197
- const triggerAriaProps = {};
198
- const isPopupExpanded = (child === null || child === void 0 ? void 0 : (_child_props = child.props) === null || _child_props === void 0 ? void 0 : _child_props['aria-haspopup']) && ((child === null || child === void 0 ? void 0 : (_child_props1 = child.props) === null || _child_props1 === void 0 ? void 0 : _child_props1['aria-expanded']) === true || (child === null || child === void 0 ? void 0 : (_child_props2 = child.props) === null || _child_props2 === void 0 ? void 0 : _child_props2['aria-expanded']) === 'true');
199
- if (relationship === 'label') {
200
- // aria-label only works if the content is a string. Otherwise, need to use aria-labelledby.
201
- if (typeof state.content.children === 'string') {
202
- triggerAriaProps['aria-label'] = state.content.children;
203
- } else {
204
- triggerAriaProps['aria-labelledby'] = state.content.id;
205
- // Always render the tooltip even if hidden, so that aria-labelledby refers to a valid element
206
- state.shouldRenderTooltip = true;
207
- }
208
- } else if (relationship === 'description') {
209
- triggerAriaProps['aria-describedby'] = state.content.id;
210
- // Always render the tooltip even if hidden, so that aria-describedby refers to a valid element
211
- state.shouldRenderTooltip = true;
212
- }
213
- // Case 1: Don't render the Tooltip in SSR to avoid hydration errors
214
- // Case 2: Don't render the Tooltip, if it triggers Menu or another popup and it's already opened
215
- if (isServerSideRender || isPopupExpanded) {
216
- state.shouldRenderTooltip = false;
217
- }
218
- // Apply the trigger props to the child, either by calling the render function, or cloning with the new props
219
- state.children = applyTriggerPropsToChildren(children, {
220
- ...triggerAriaProps,
221
- ...child === null || child === void 0 ? void 0 : child.props,
222
- ref: useMergedRefs(getReactElementRef(child), keyborgListenerCallbackRef, // If the target prop is not provided, attach targetRef to the trigger element's ref prop
223
- positioningOptions.target === undefined ? targetRef : undefined),
224
- onPointerEnter: useEventCallback(mergeCallbacks(child === null || child === void 0 ? void 0 : (_child_props3 = child.props) === null || _child_props3 === void 0 ? void 0 : _child_props3.onPointerEnter, onEnterTrigger)),
225
- onPointerLeave: useEventCallback(mergeCallbacks(child === null || child === void 0 ? void 0 : (_child_props4 = child.props) === null || _child_props4 === void 0 ? void 0 : _child_props4.onPointerLeave, onLeaveTrigger)),
226
- onFocus: useEventCallback(mergeCallbacks(child === null || child === void 0 ? void 0 : (_child_props5 = child.props) === null || _child_props5 === void 0 ? void 0 : _child_props5.onFocus, onEnterTrigger)),
227
- onBlur: useEventCallback(mergeCallbacks(child === null || child === void 0 ? void 0 : (_child_props6 = child.props) === null || _child_props6 === void 0 ? void 0 : _child_props6.onBlur, onLeaveTrigger))
228
- });
229
- return state;
230
18
  };
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/components/Tooltip/useTooltip.tsx"],"sourcesContent":["'use client';\n\nimport * as React from 'react';\nimport { mergeArrowOffset, resolvePositioningShorthand, usePositioning } from '@fluentui/react-positioning';\nimport {\n useTooltipVisibility_unstable as useTooltipVisibility,\n useFluent_unstable as useFluent,\n} from '@fluentui/react-shared-contexts';\nimport type { KeyborgFocusInEvent } from '@fluentui/react-tabster';\nimport { KEYBORG_FOCUSIN, useIsNavigatingWithKeyboard } from '@fluentui/react-tabster';\nimport {\n applyTriggerPropsToChildren,\n useControllableState,\n useId,\n useIsomorphicLayoutEffect,\n useIsSSR,\n useMergedRefs,\n getTriggerChild,\n mergeCallbacks,\n useEventCallback,\n slot,\n getReactElementRef,\n} from '@fluentui/react-utilities';\nimport type { TooltipProps, TooltipState, TooltipChildProps, OnVisibleChangeData } from './Tooltip.types';\nimport { arrowHeight, tooltipBorderRadius } from './private/constants';\nimport { useTooltipTimeout } from './private/useTooltipTimeout';\nimport { Escape } from '@fluentui/keyboard-keys';\n\n/**\n * Create the state required to render Tooltip.\n *\n * The returned state can be modified with hooks such as useTooltipStyles_unstable,\n * before being passed to renderTooltip_unstable.\n *\n * @param props - props from this instance of Tooltip\n */\nexport const useTooltip_unstable = (props: TooltipProps): TooltipState => {\n 'use no memo';\n\n const context = useTooltipVisibility();\n const isServerSideRender = useIsSSR();\n const { targetDocument } = useFluent();\n\n const [visible, setVisibleInternal] = useControllableState({ state: props.visible, initialState: false });\n\n const {\n appearance = 'normal',\n children,\n content,\n withArrow = false,\n positioning = 'above',\n onVisibleChange,\n relationship,\n showDelay = 250,\n hideDelay = 250,\n mountNode,\n } = props;\n\n const state: TooltipState = {\n withArrow,\n positioning,\n showDelay,\n hideDelay,\n relationship,\n visible,\n shouldRenderTooltip: visible,\n appearance,\n mountNode,\n // Slots\n components: {\n content: 'div',\n },\n content: slot.always(content, {\n defaultProps: {\n role: 'tooltip',\n },\n elementType: 'div',\n }),\n };\n\n state.content.id = useId('tooltip-', state.content.id);\n\n const positioningOptions = {\n enabled: state.visible,\n arrowPadding: 2 * tooltipBorderRadius,\n position: 'above' as const,\n align: 'center' as const,\n offset: 4,\n ...resolvePositioningShorthand(state.positioning),\n };\n\n if (state.withArrow) {\n positioningOptions.offset = mergeArrowOffset(positioningOptions.offset, arrowHeight);\n }\n\n const {\n targetRef,\n containerRef,\n arrowRef,\n }: {\n // eslint-disable-next-line @typescript-eslint/no-deprecated\n targetRef: React.MutableRefObject<unknown>;\n // eslint-disable-next-line @typescript-eslint/no-deprecated\n containerRef: React.MutableRefObject<HTMLDivElement>;\n // eslint-disable-next-line @typescript-eslint/no-deprecated\n arrowRef: React.MutableRefObject<HTMLDivElement>;\n } = usePositioning(positioningOptions);\n\n const [setDelayTimeout, clearDelayTimeout] = useTooltipTimeout(containerRef);\n\n const setVisible = React.useCallback(\n (ev: React.PointerEvent<HTMLElement> | React.FocusEvent<HTMLElement> | undefined, data: OnVisibleChangeData) => {\n clearDelayTimeout();\n setVisibleInternal(oldVisible => {\n if (data.visible !== oldVisible) {\n onVisibleChange?.(ev, data);\n }\n return data.visible;\n });\n },\n [clearDelayTimeout, setVisibleInternal, onVisibleChange],\n );\n\n state.content.ref = useMergedRefs(state.content.ref, containerRef);\n state.arrowRef = arrowRef;\n\n // When this tooltip is visible, hide any other tooltips, and register it\n // as the visibleTooltip with the TooltipContext.\n // Also add a listener on document to hide the tooltip if Escape is pressed\n useIsomorphicLayoutEffect(() => {\n if (visible) {\n const thisTooltip = {\n hide: (ev?: KeyboardEvent) => setVisible(undefined, { visible: false, documentKeyboardEvent: ev }),\n };\n\n context.visibleTooltip?.hide();\n context.visibleTooltip = thisTooltip;\n\n const onDocumentKeyDown = (ev: KeyboardEvent) => {\n if (ev.key === Escape && !ev.defaultPrevented) {\n thisTooltip.hide(ev);\n // stop propagation to avoid conflicting with other elements that listen for `Escape`\n // e,g: Dialog, Popover, Menu and Tooltip\n ev.preventDefault();\n }\n };\n\n targetDocument?.addEventListener('keydown', onDocumentKeyDown, {\n // As this event is added at targeted document,\n // we need to capture the event to be sure keydown handling from tooltip happens first\n capture: true,\n });\n\n return () => {\n if (context.visibleTooltip === thisTooltip) {\n context.visibleTooltip = undefined;\n }\n\n targetDocument?.removeEventListener('keydown', onDocumentKeyDown, { capture: true });\n };\n }\n }, [context, targetDocument, visible, setVisible]);\n\n // Used to skip showing the tooltip in certain situations when the trigger is focused.\n // See comments where this is set for more info.\n const ignoreNextFocusEventRef = React.useRef(false);\n\n // Listener for onPointerEnter and onFocus on the trigger element\n const onEnterTrigger = React.useCallback(\n (ev: React.PointerEvent<HTMLElement> | React.FocusEvent<HTMLElement>) => {\n if (ev.type === 'focus' && ignoreNextFocusEventRef.current) {\n ignoreNextFocusEventRef.current = false;\n return;\n }\n\n // Show immediately if another tooltip is already visible\n const delay = context.visibleTooltip ? 0 : state.showDelay;\n\n setDelayTimeout(() => {\n setVisible(ev, { visible: true });\n }, delay);\n\n ev.persist(); // Persist the event since the setVisible call will happen asynchronously\n },\n [setDelayTimeout, setVisible, state.showDelay, context],\n );\n\n const isNavigatingWithKeyboard = useIsNavigatingWithKeyboard();\n\n // Callback ref that attaches a keyborg:focusin event listener.\n const [keyborgListenerCallbackRef] = React.useState(() => {\n const onKeyborgFocusIn = ((ev: KeyborgFocusInEvent) => {\n // Skip showing the tooltip if focus moved programmatically.\n // For example, we don't want to show the tooltip when a dialog is closed\n // and Tabster programmatically restores focus to the trigger button.\n // See https://github.com/microsoft/fluentui/issues/27576\n if (ev.detail?.isFocusedProgrammatically && !isNavigatingWithKeyboard()) {\n ignoreNextFocusEventRef.current = true;\n }\n }) as EventListener;\n\n // Save the current element to remove the listener when the ref changes\n let current: Element | null = null;\n\n // Callback ref that attaches the listener to the element\n return (element: Element | null) => {\n current?.removeEventListener(KEYBORG_FOCUSIN, onKeyborgFocusIn);\n element?.addEventListener(KEYBORG_FOCUSIN, onKeyborgFocusIn);\n current = element;\n };\n });\n\n // Listener for onPointerLeave and onBlur on the trigger element\n const onLeaveTrigger = React.useCallback(\n (ev: React.PointerEvent<HTMLElement> | React.FocusEvent<HTMLElement>) => {\n let delay = state.hideDelay;\n\n if (ev.type === 'blur') {\n // Hide immediately when losing focus\n delay = 0;\n\n // The focused element gets a blur event when the document loses focus\n // (e.g. switching tabs in the browser), but we don't want to show the\n // tooltip again when the document gets focus back. Handle this case by\n // checking if the blurred element is still the document's activeElement.\n // See https://github.com/microsoft/fluentui/issues/13541\n ignoreNextFocusEventRef.current = targetDocument?.activeElement === ev.target;\n }\n\n setDelayTimeout(() => {\n setVisible(ev, { visible: false });\n }, delay);\n\n ev.persist(); // Persist the event since the setVisible call will happen asynchronously\n },\n [setDelayTimeout, setVisible, state.hideDelay, targetDocument],\n );\n\n // Cancel the hide timer when the mouse or focus enters the tooltip, and restart it when the mouse or focus leaves.\n // This keeps the tooltip visible when the mouse is moved over it, or it has focus within.\n state.content.onPointerEnter = mergeCallbacks(state.content.onPointerEnter, clearDelayTimeout);\n state.content.onPointerLeave = mergeCallbacks(state.content.onPointerLeave, onLeaveTrigger);\n state.content.onFocus = mergeCallbacks(state.content.onFocus, clearDelayTimeout);\n state.content.onBlur = mergeCallbacks(state.content.onBlur, onLeaveTrigger);\n\n const child = getTriggerChild(children);\n\n const triggerAriaProps: Pick<TooltipChildProps, 'aria-label' | 'aria-labelledby' | 'aria-describedby'> = {};\n const isPopupExpanded =\n child?.props?.['aria-haspopup'] &&\n (child?.props?.['aria-expanded'] === true || child?.props?.['aria-expanded'] === 'true');\n\n if (relationship === 'label') {\n // aria-label only works if the content is a string. Otherwise, need to use aria-labelledby.\n if (typeof state.content.children === 'string') {\n triggerAriaProps['aria-label'] = state.content.children;\n } else {\n triggerAriaProps['aria-labelledby'] = state.content.id;\n // Always render the tooltip even if hidden, so that aria-labelledby refers to a valid element\n state.shouldRenderTooltip = true;\n }\n } else if (relationship === 'description') {\n triggerAriaProps['aria-describedby'] = state.content.id;\n // Always render the tooltip even if hidden, so that aria-describedby refers to a valid element\n state.shouldRenderTooltip = true;\n }\n\n // Case 1: Don't render the Tooltip in SSR to avoid hydration errors\n // Case 2: Don't render the Tooltip, if it triggers Menu or another popup and it's already opened\n if (isServerSideRender || isPopupExpanded) {\n state.shouldRenderTooltip = false;\n }\n\n // Apply the trigger props to the child, either by calling the render function, or cloning with the new props\n state.children = applyTriggerPropsToChildren(children, {\n ...triggerAriaProps,\n ...child?.props,\n ref: useMergedRefs(\n getReactElementRef<HTMLButtonElement>(child),\n keyborgListenerCallbackRef,\n // If the target prop is not provided, attach targetRef to the trigger element's ref prop\n positioningOptions.target === undefined ? targetRef : undefined,\n ),\n onPointerEnter: useEventCallback(mergeCallbacks(child?.props?.onPointerEnter, onEnterTrigger)),\n onPointerLeave: useEventCallback(mergeCallbacks(child?.props?.onPointerLeave, onLeaveTrigger)),\n onFocus: useEventCallback(mergeCallbacks(child?.props?.onFocus, onEnterTrigger)),\n onBlur: useEventCallback(mergeCallbacks(child?.props?.onBlur, onLeaveTrigger)),\n });\n\n return state;\n};\n"],"names":["React","mergeArrowOffset","resolvePositioningShorthand","usePositioning","useTooltipVisibility_unstable","useTooltipVisibility","useFluent_unstable","useFluent","KEYBORG_FOCUSIN","useIsNavigatingWithKeyboard","applyTriggerPropsToChildren","useControllableState","useId","useIsomorphicLayoutEffect","useIsSSR","useMergedRefs","getTriggerChild","mergeCallbacks","useEventCallback","slot","getReactElementRef","arrowHeight","tooltipBorderRadius","useTooltipTimeout","Escape","useTooltip_unstable","props","child","context","isServerSideRender","targetDocument","visible","setVisibleInternal","state","initialState","appearance","children","content","withArrow","positioning","onVisibleChange","relationship","showDelay","hideDelay","mountNode","shouldRenderTooltip","components","always","defaultProps","role","elementType","id","positioningOptions","enabled","arrowPadding","position","align","offset","targetRef","containerRef","arrowRef","setDelayTimeout","clearDelayTimeout","setVisible","useCallback","ev","data","oldVisible","ref","thisTooltip","hide","undefined","documentKeyboardEvent","visibleTooltip","onDocumentKeyDown","key","defaultPrevented","preventDefault","addEventListener","capture","removeEventListener","ignoreNextFocusEventRef","useRef","onEnterTrigger","type","current","delay","persist","isNavigatingWithKeyboard","keyborgListenerCallbackRef","useState","onKeyborgFocusIn","detail","isFocusedProgrammatically","element","onLeaveTrigger","activeElement","target","onPointerEnter","onPointerLeave","onFocus","onBlur","triggerAriaProps","isPopupExpanded"],"mappings":"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"}
1
+ {"version":3,"sources":["../src/components/Tooltip/useTooltip.tsx"],"sourcesContent":["'use client';\n\nimport type { TooltipProps, TooltipState } from './Tooltip.types';\nimport { useTooltipBase_unstable } from './useTooltipBase';\n\n/**\n * Create the state required to render Tooltip.\n *\n * The returned state can be modified with hooks such as useTooltipStyles_unstable,\n * before being passed to renderTooltip_unstable.\n *\n * @param props - props from this instance of Tooltip\n */\nexport const useTooltip_unstable = (props: TooltipProps): TooltipState => {\n 'use no memo';\n\n const { appearance = 'normal' } = props;\n\n const state = useTooltipBase_unstable(props);\n\n return {\n appearance,\n ...state,\n };\n};\n"],"names":["useTooltipBase_unstable","useTooltip_unstable","props","appearance","state"],"mappings":"AAAA;AAGA,SAASA,uBAAuB,QAAQ,mBAAmB;AAE3D;;;;;;;CAOC,GACD,OAAO,MAAMC,sBAAsB,CAACC;IAClC;IAEA,MAAM,EAAEC,aAAa,QAAQ,EAAE,GAAGD;IAElC,MAAME,QAAQJ,wBAAwBE;IAEtC,OAAO;QACLC;QACA,GAAGC,KAAK;IACV;AACF,EAAE"}
@@ -0,0 +1,229 @@
1
+ 'use client';
2
+ import * as React from 'react';
3
+ import { mergeArrowOffset, resolvePositioningShorthand, usePositioning } from '@fluentui/react-positioning';
4
+ import { useTooltipVisibility_unstable as useTooltipVisibility, useFluent_unstable as useFluent } from '@fluentui/react-shared-contexts';
5
+ import { KEYBORG_FOCUSIN, useIsNavigatingWithKeyboard } from '@fluentui/react-tabster';
6
+ import { applyTriggerPropsToChildren, useControllableState, useId, useIsomorphicLayoutEffect, useIsSSR, useMergedRefs, getTriggerChild, mergeCallbacks, useEventCallback, slot, getReactElementRef } from '@fluentui/react-utilities';
7
+ import { arrowHeight, tooltipBorderRadius } from './private/constants';
8
+ import { useTooltipTimeout } from './private/useTooltipTimeout';
9
+ import { Escape } from '@fluentui/keyboard-keys';
10
+ /**
11
+ * Create the state required to render Tooltip.
12
+ *
13
+ * The returned state can be modified with hooks such as useTooltipStyles_unstable,
14
+ * before being passed to renderTooltip_unstable.
15
+ *
16
+ * @param props - props from this instance of Tooltip
17
+ */ export const useTooltipBase_unstable = (props)=>{
18
+ 'use no memo';
19
+ var _child_props, _child_props1, _child_props2, _child_props3, _child_props4, _child_props5, _child_props6;
20
+ const context = useTooltipVisibility();
21
+ const isServerSideRender = useIsSSR();
22
+ const { targetDocument } = useFluent();
23
+ const [visible, setVisibleInternal] = useControllableState({
24
+ state: props.visible,
25
+ initialState: false
26
+ });
27
+ const { children, content, withArrow = false, positioning = 'above', onVisibleChange, relationship, showDelay = 250, hideDelay = 250, mountNode } = props;
28
+ const state = {
29
+ withArrow,
30
+ positioning,
31
+ showDelay,
32
+ hideDelay,
33
+ relationship,
34
+ visible,
35
+ shouldRenderTooltip: visible,
36
+ mountNode,
37
+ // Slots
38
+ components: {
39
+ content: 'div'
40
+ },
41
+ content: slot.always(content, {
42
+ defaultProps: {
43
+ role: 'tooltip'
44
+ },
45
+ elementType: 'div'
46
+ })
47
+ };
48
+ state.content.id = useId('tooltip-', state.content.id);
49
+ const positioningOptions = {
50
+ enabled: state.visible,
51
+ arrowPadding: 2 * tooltipBorderRadius,
52
+ position: 'above',
53
+ align: 'center',
54
+ offset: 4,
55
+ ...resolvePositioningShorthand(state.positioning)
56
+ };
57
+ if (state.withArrow) {
58
+ positioningOptions.offset = mergeArrowOffset(positioningOptions.offset, arrowHeight);
59
+ }
60
+ const { targetRef, containerRef, arrowRef } = usePositioning(positioningOptions);
61
+ const [setDelayTimeout, clearDelayTimeout] = useTooltipTimeout(containerRef);
62
+ const setVisible = React.useCallback((ev, data)=>{
63
+ clearDelayTimeout();
64
+ setVisibleInternal((oldVisible)=>{
65
+ if (data.visible !== oldVisible) {
66
+ onVisibleChange === null || onVisibleChange === void 0 ? void 0 : onVisibleChange(ev, data);
67
+ }
68
+ return data.visible;
69
+ });
70
+ }, [
71
+ clearDelayTimeout,
72
+ setVisibleInternal,
73
+ onVisibleChange
74
+ ]);
75
+ state.content.ref = useMergedRefs(state.content.ref, containerRef);
76
+ state.arrowRef = arrowRef;
77
+ // When this tooltip is visible, hide any other tooltips, and register it
78
+ // as the visibleTooltip with the TooltipContext.
79
+ // Also add a listener on document to hide the tooltip if Escape is pressed
80
+ useIsomorphicLayoutEffect(()=>{
81
+ if (visible) {
82
+ var _context_visibleTooltip;
83
+ const thisTooltip = {
84
+ hide: (ev)=>setVisible(undefined, {
85
+ visible: false,
86
+ documentKeyboardEvent: ev
87
+ })
88
+ };
89
+ (_context_visibleTooltip = context.visibleTooltip) === null || _context_visibleTooltip === void 0 ? void 0 : _context_visibleTooltip.hide();
90
+ context.visibleTooltip = thisTooltip;
91
+ const onDocumentKeyDown = (ev)=>{
92
+ if (ev.key === Escape && !ev.defaultPrevented) {
93
+ thisTooltip.hide(ev);
94
+ // stop propagation to avoid conflicting with other elements that listen for `Escape`
95
+ // e,g: Dialog, Popover, Menu and Tooltip
96
+ ev.preventDefault();
97
+ }
98
+ };
99
+ targetDocument === null || targetDocument === void 0 ? void 0 : targetDocument.addEventListener('keydown', onDocumentKeyDown, {
100
+ // As this event is added at targeted document,
101
+ // we need to capture the event to be sure keydown handling from tooltip happens first
102
+ capture: true
103
+ });
104
+ return ()=>{
105
+ if (context.visibleTooltip === thisTooltip) {
106
+ context.visibleTooltip = undefined;
107
+ }
108
+ targetDocument === null || targetDocument === void 0 ? void 0 : targetDocument.removeEventListener('keydown', onDocumentKeyDown, {
109
+ capture: true
110
+ });
111
+ };
112
+ }
113
+ }, [
114
+ context,
115
+ targetDocument,
116
+ visible,
117
+ setVisible
118
+ ]);
119
+ // Used to skip showing the tooltip in certain situations when the trigger is focused.
120
+ // See comments where this is set for more info.
121
+ const ignoreNextFocusEventRef = React.useRef(false);
122
+ // Listener for onPointerEnter and onFocus on the trigger element
123
+ const onEnterTrigger = React.useCallback((ev)=>{
124
+ if (ev.type === 'focus' && ignoreNextFocusEventRef.current) {
125
+ ignoreNextFocusEventRef.current = false;
126
+ return;
127
+ }
128
+ // Show immediately if another tooltip is already visible
129
+ const delay = context.visibleTooltip ? 0 : state.showDelay;
130
+ setDelayTimeout(()=>{
131
+ setVisible(ev, {
132
+ visible: true
133
+ });
134
+ }, delay);
135
+ ev.persist(); // Persist the event since the setVisible call will happen asynchronously
136
+ }, [
137
+ setDelayTimeout,
138
+ setVisible,
139
+ state.showDelay,
140
+ context
141
+ ]);
142
+ const isNavigatingWithKeyboard = useIsNavigatingWithKeyboard();
143
+ // Callback ref that attaches a keyborg:focusin event listener.
144
+ const [keyborgListenerCallbackRef] = React.useState(()=>{
145
+ const onKeyborgFocusIn = (ev)=>{
146
+ var _ev_detail;
147
+ // Skip showing the tooltip if focus moved programmatically.
148
+ // For example, we don't want to show the tooltip when a dialog is closed
149
+ // and Tabster programmatically restores focus to the trigger button.
150
+ // See https://github.com/microsoft/fluentui/issues/27576
151
+ if (((_ev_detail = ev.detail) === null || _ev_detail === void 0 ? void 0 : _ev_detail.isFocusedProgrammatically) && !isNavigatingWithKeyboard()) {
152
+ ignoreNextFocusEventRef.current = true;
153
+ }
154
+ };
155
+ // Save the current element to remove the listener when the ref changes
156
+ let current = null;
157
+ // Callback ref that attaches the listener to the element
158
+ return (element)=>{
159
+ current === null || current === void 0 ? void 0 : current.removeEventListener(KEYBORG_FOCUSIN, onKeyborgFocusIn);
160
+ element === null || element === void 0 ? void 0 : element.addEventListener(KEYBORG_FOCUSIN, onKeyborgFocusIn);
161
+ current = element;
162
+ };
163
+ });
164
+ // Listener for onPointerLeave and onBlur on the trigger element
165
+ const onLeaveTrigger = React.useCallback((ev)=>{
166
+ let delay = state.hideDelay;
167
+ if (ev.type === 'blur') {
168
+ // Hide immediately when losing focus
169
+ delay = 0;
170
+ // The focused element gets a blur event when the document loses focus
171
+ // (e.g. switching tabs in the browser), but we don't want to show the
172
+ // tooltip again when the document gets focus back. Handle this case by
173
+ // checking if the blurred element is still the document's activeElement.
174
+ // See https://github.com/microsoft/fluentui/issues/13541
175
+ ignoreNextFocusEventRef.current = (targetDocument === null || targetDocument === void 0 ? void 0 : targetDocument.activeElement) === ev.target;
176
+ }
177
+ setDelayTimeout(()=>{
178
+ setVisible(ev, {
179
+ visible: false
180
+ });
181
+ }, delay);
182
+ ev.persist(); // Persist the event since the setVisible call will happen asynchronously
183
+ }, [
184
+ setDelayTimeout,
185
+ setVisible,
186
+ state.hideDelay,
187
+ targetDocument
188
+ ]);
189
+ // Cancel the hide timer when the mouse or focus enters the tooltip, and restart it when the mouse or focus leaves.
190
+ // This keeps the tooltip visible when the mouse is moved over it, or it has focus within.
191
+ state.content.onPointerEnter = mergeCallbacks(state.content.onPointerEnter, clearDelayTimeout);
192
+ state.content.onPointerLeave = mergeCallbacks(state.content.onPointerLeave, onLeaveTrigger);
193
+ state.content.onFocus = mergeCallbacks(state.content.onFocus, clearDelayTimeout);
194
+ state.content.onBlur = mergeCallbacks(state.content.onBlur, onLeaveTrigger);
195
+ const child = getTriggerChild(children);
196
+ const triggerAriaProps = {};
197
+ const isPopupExpanded = (child === null || child === void 0 ? void 0 : (_child_props = child.props) === null || _child_props === void 0 ? void 0 : _child_props['aria-haspopup']) && ((child === null || child === void 0 ? void 0 : (_child_props1 = child.props) === null || _child_props1 === void 0 ? void 0 : _child_props1['aria-expanded']) === true || (child === null || child === void 0 ? void 0 : (_child_props2 = child.props) === null || _child_props2 === void 0 ? void 0 : _child_props2['aria-expanded']) === 'true');
198
+ if (relationship === 'label') {
199
+ // aria-label only works if the content is a string. Otherwise, need to use aria-labelledby.
200
+ if (typeof state.content.children === 'string') {
201
+ triggerAriaProps['aria-label'] = state.content.children;
202
+ } else {
203
+ triggerAriaProps['aria-labelledby'] = state.content.id;
204
+ // Always render the tooltip even if hidden, so that aria-labelledby refers to a valid element
205
+ state.shouldRenderTooltip = true;
206
+ }
207
+ } else if (relationship === 'description') {
208
+ triggerAriaProps['aria-describedby'] = state.content.id;
209
+ // Always render the tooltip even if hidden, so that aria-describedby refers to a valid element
210
+ state.shouldRenderTooltip = true;
211
+ }
212
+ // Case 1: Don't render the Tooltip in SSR to avoid hydration errors
213
+ // Case 2: Don't render the Tooltip, if it triggers Menu or another popup and it's already opened
214
+ if (isServerSideRender || isPopupExpanded) {
215
+ state.shouldRenderTooltip = false;
216
+ }
217
+ // Apply the trigger props to the child, either by calling the render function, or cloning with the new props
218
+ state.children = applyTriggerPropsToChildren(children, {
219
+ ...triggerAriaProps,
220
+ ...child === null || child === void 0 ? void 0 : child.props,
221
+ ref: useMergedRefs(getReactElementRef(child), keyborgListenerCallbackRef, // If the target prop is not provided, attach targetRef to the trigger element's ref prop
222
+ positioningOptions.target === undefined ? targetRef : undefined),
223
+ onPointerEnter: useEventCallback(mergeCallbacks(child === null || child === void 0 ? void 0 : (_child_props3 = child.props) === null || _child_props3 === void 0 ? void 0 : _child_props3.onPointerEnter, onEnterTrigger)),
224
+ onPointerLeave: useEventCallback(mergeCallbacks(child === null || child === void 0 ? void 0 : (_child_props4 = child.props) === null || _child_props4 === void 0 ? void 0 : _child_props4.onPointerLeave, onLeaveTrigger)),
225
+ onFocus: useEventCallback(mergeCallbacks(child === null || child === void 0 ? void 0 : (_child_props5 = child.props) === null || _child_props5 === void 0 ? void 0 : _child_props5.onFocus, onEnterTrigger)),
226
+ onBlur: useEventCallback(mergeCallbacks(child === null || child === void 0 ? void 0 : (_child_props6 = child.props) === null || _child_props6 === void 0 ? void 0 : _child_props6.onBlur, onLeaveTrigger))
227
+ });
228
+ return state;
229
+ };