@cdx-ui/primitives 0.0.1-beta.159 → 0.0.1-beta.160

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.
Files changed (233) hide show
  1. package/lib/commonjs/dialog/DialogContent.js +17 -3
  2. package/lib/commonjs/dialog/DialogContent.js.map +1 -1
  3. package/lib/commonjs/dialog/DialogPopup.js +5 -1
  4. package/lib/commonjs/dialog/DialogPopup.js.map +1 -1
  5. package/lib/commonjs/index.js +54 -0
  6. package/lib/commonjs/index.js.map +1 -1
  7. package/lib/commonjs/menu/MenuCheckboxItem.js +42 -0
  8. package/lib/commonjs/menu/MenuCheckboxItem.js.map +1 -0
  9. package/lib/commonjs/menu/MenuItem.js +142 -0
  10. package/lib/commonjs/menu/MenuItem.js.map +1 -0
  11. package/lib/commonjs/menu/MenuOverlayRoot.js +80 -0
  12. package/lib/commonjs/menu/MenuOverlayRoot.js.map +1 -0
  13. package/lib/commonjs/menu/MenuPopup.js +78 -0
  14. package/lib/commonjs/menu/MenuPopup.js.map +1 -0
  15. package/lib/commonjs/menu/MenuRadioGroup.js +48 -0
  16. package/lib/commonjs/menu/MenuRadioGroup.js.map +1 -0
  17. package/lib/commonjs/menu/MenuRadioItem.js +32 -0
  18. package/lib/commonjs/menu/MenuRadioItem.js.map +1 -0
  19. package/lib/commonjs/menu/MenuRoot.js +139 -0
  20. package/lib/commonjs/menu/MenuRoot.js.map +1 -0
  21. package/lib/commonjs/menu/MenuTrigger.js +59 -0
  22. package/lib/commonjs/menu/MenuTrigger.js.map +1 -0
  23. package/lib/commonjs/menu/context.js +36 -0
  24. package/lib/commonjs/menu/context.js.map +1 -0
  25. package/lib/commonjs/menu/index.js +118 -0
  26. package/lib/commonjs/menu/index.js.map +1 -0
  27. package/lib/commonjs/menu/overlayContext.js +30 -0
  28. package/lib/commonjs/menu/overlayContext.js.map +1 -0
  29. package/lib/commonjs/menu/types.js +6 -0
  30. package/lib/commonjs/menu/types.js.map +1 -0
  31. package/lib/commonjs/menu/useMenuNavigation.js +87 -0
  32. package/lib/commonjs/menu/useMenuNavigation.js.map +1 -0
  33. package/lib/commonjs/menu/useMenuOverlayLifecycle.js +68 -0
  34. package/lib/commonjs/menu/useMenuOverlayLifecycle.js.map +1 -0
  35. package/lib/commonjs/menu/useMenuTrigger.js +115 -0
  36. package/lib/commonjs/menu/useMenuTrigger.js.map +1 -0
  37. package/lib/commonjs/overlay/OverlayContainer.js +2 -3
  38. package/lib/commonjs/overlay/OverlayContainer.js.map +1 -1
  39. package/lib/commonjs/overlay/OverlayStack.js +125 -0
  40. package/lib/commonjs/overlay/OverlayStack.js.map +1 -0
  41. package/lib/commonjs/overlay/constants.js +7 -1
  42. package/lib/commonjs/overlay/constants.js.map +1 -1
  43. package/lib/commonjs/overlay/getAnchoredOverlayPosition.js +188 -0
  44. package/lib/commonjs/overlay/getAnchoredOverlayPosition.js.map +1 -0
  45. package/lib/commonjs/overlay/index.js +39 -0
  46. package/lib/commonjs/overlay/index.js.map +1 -1
  47. package/lib/commonjs/overlay/useAnchoredOverlay.js +127 -0
  48. package/lib/commonjs/overlay/useAnchoredOverlay.js.map +1 -0
  49. package/lib/commonjs/overlay/useDismissOverlay.js +8 -1
  50. package/lib/commonjs/overlay/useDismissOverlay.js.map +1 -1
  51. package/lib/commonjs/overlay/useDismissOverlay.web.js +11 -3
  52. package/lib/commonjs/overlay/useDismissOverlay.web.js.map +1 -1
  53. package/lib/commonjs/overlay/useEscapeKey.js +8 -1
  54. package/lib/commonjs/overlay/useEscapeKey.js.map +1 -1
  55. package/lib/commonjs/overlay/useEscapeKey.web.js +11 -3
  56. package/lib/commonjs/overlay/useEscapeKey.web.js.map +1 -1
  57. package/lib/commonjs/overlay/useOverlayPosition.js +16 -7
  58. package/lib/commonjs/overlay/useOverlayPosition.js.map +1 -1
  59. package/lib/commonjs/portal/portal.js +86 -63
  60. package/lib/commonjs/portal/portal.js.map +1 -1
  61. package/lib/commonjs/select/SelectContent.js +23 -11
  62. package/lib/commonjs/select/SelectContent.js.map +1 -1
  63. package/lib/commonjs/select/useListboxNavigation.js +10 -11
  64. package/lib/commonjs/select/useListboxNavigation.js.map +1 -1
  65. package/lib/commonjs/tooltip/TooltipContent.js +25 -24
  66. package/lib/commonjs/tooltip/TooltipContent.js.map +1 -1
  67. package/lib/commonjs/tooltip/getTooltipPosition.js +37 -84
  68. package/lib/commonjs/tooltip/getTooltipPosition.js.map +1 -1
  69. package/lib/module/dialog/DialogContent.js +18 -4
  70. package/lib/module/dialog/DialogContent.js.map +1 -1
  71. package/lib/module/dialog/DialogPopup.js +5 -1
  72. package/lib/module/dialog/DialogPopup.js.map +1 -1
  73. package/lib/module/index.js +2 -1
  74. package/lib/module/index.js.map +1 -1
  75. package/lib/module/menu/MenuCheckboxItem.js +38 -0
  76. package/lib/module/menu/MenuCheckboxItem.js.map +1 -0
  77. package/lib/module/menu/MenuItem.js +138 -0
  78. package/lib/module/menu/MenuItem.js.map +1 -0
  79. package/lib/module/menu/MenuOverlayRoot.js +76 -0
  80. package/lib/module/menu/MenuOverlayRoot.js.map +1 -0
  81. package/lib/module/menu/MenuPopup.js +74 -0
  82. package/lib/module/menu/MenuPopup.js.map +1 -0
  83. package/lib/module/menu/MenuRadioGroup.js +44 -0
  84. package/lib/module/menu/MenuRadioGroup.js.map +1 -0
  85. package/lib/module/menu/MenuRadioItem.js +28 -0
  86. package/lib/module/menu/MenuRadioItem.js.map +1 -0
  87. package/lib/module/menu/MenuRoot.js +135 -0
  88. package/lib/module/menu/MenuRoot.js.map +1 -0
  89. package/lib/module/menu/MenuTrigger.js +55 -0
  90. package/lib/module/menu/MenuTrigger.js.map +1 -0
  91. package/lib/module/menu/context.js +27 -0
  92. package/lib/module/menu/context.js.map +1 -0
  93. package/lib/module/menu/index.js +13 -0
  94. package/lib/module/menu/index.js.map +1 -0
  95. package/lib/module/menu/overlayContext.js +24 -0
  96. package/lib/module/menu/overlayContext.js.map +1 -0
  97. package/lib/module/menu/types.js +4 -0
  98. package/lib/module/menu/types.js.map +1 -0
  99. package/lib/module/menu/useMenuNavigation.js +84 -0
  100. package/lib/module/menu/useMenuNavigation.js.map +1 -0
  101. package/lib/module/menu/useMenuOverlayLifecycle.js +63 -0
  102. package/lib/module/menu/useMenuOverlayLifecycle.js.map +1 -0
  103. package/lib/module/menu/useMenuTrigger.js +111 -0
  104. package/lib/module/menu/useMenuTrigger.js.map +1 -0
  105. package/lib/module/overlay/OverlayContainer.js +2 -3
  106. package/lib/module/overlay/OverlayContainer.js.map +1 -1
  107. package/lib/module/overlay/OverlayStack.js +119 -0
  108. package/lib/module/overlay/OverlayStack.js.map +1 -0
  109. package/lib/module/overlay/constants.js +6 -0
  110. package/lib/module/overlay/constants.js.map +1 -1
  111. package/lib/module/overlay/getAnchoredOverlayPosition.js +183 -0
  112. package/lib/module/overlay/getAnchoredOverlayPosition.js.map +1 -0
  113. package/lib/module/overlay/index.js +4 -1
  114. package/lib/module/overlay/index.js.map +1 -1
  115. package/lib/module/overlay/useAnchoredOverlay.js +123 -0
  116. package/lib/module/overlay/useAnchoredOverlay.js.map +1 -0
  117. package/lib/module/overlay/useDismissOverlay.js +9 -1
  118. package/lib/module/overlay/useDismissOverlay.js.map +1 -1
  119. package/lib/module/overlay/useDismissOverlay.web.js +11 -3
  120. package/lib/module/overlay/useDismissOverlay.web.js.map +1 -1
  121. package/lib/module/overlay/useEscapeKey.js +9 -1
  122. package/lib/module/overlay/useEscapeKey.js.map +1 -1
  123. package/lib/module/overlay/useEscapeKey.web.js +11 -3
  124. package/lib/module/overlay/useEscapeKey.web.js.map +1 -1
  125. package/lib/module/overlay/useOverlayPosition.js +16 -7
  126. package/lib/module/overlay/useOverlayPosition.js.map +1 -1
  127. package/lib/module/portal/portal.js +86 -64
  128. package/lib/module/portal/portal.js.map +1 -1
  129. package/lib/module/select/SelectContent.js +25 -13
  130. package/lib/module/select/SelectContent.js.map +1 -1
  131. package/lib/module/select/useListboxNavigation.js +10 -11
  132. package/lib/module/select/useListboxNavigation.js.map +1 -1
  133. package/lib/module/tooltip/TooltipContent.js +30 -29
  134. package/lib/module/tooltip/TooltipContent.js.map +1 -1
  135. package/lib/module/tooltip/getTooltipPosition.js +37 -84
  136. package/lib/module/tooltip/getTooltipPosition.js.map +1 -1
  137. package/lib/typescript/dialog/DialogContent.d.ts.map +1 -1
  138. package/lib/typescript/dialog/DialogPopup.d.ts.map +1 -1
  139. package/lib/typescript/index.d.ts +2 -1
  140. package/lib/typescript/index.d.ts.map +1 -1
  141. package/lib/typescript/menu/MenuCheckboxItem.d.ts +6 -0
  142. package/lib/typescript/menu/MenuCheckboxItem.d.ts.map +1 -0
  143. package/lib/typescript/menu/MenuItem.d.ts +8 -0
  144. package/lib/typescript/menu/MenuItem.d.ts.map +1 -0
  145. package/lib/typescript/menu/MenuOverlayRoot.d.ts +8 -0
  146. package/lib/typescript/menu/MenuOverlayRoot.d.ts.map +1 -0
  147. package/lib/typescript/menu/MenuPopup.d.ts +8 -0
  148. package/lib/typescript/menu/MenuPopup.d.ts.map +1 -0
  149. package/lib/typescript/menu/MenuRadioGroup.d.ts +8 -0
  150. package/lib/typescript/menu/MenuRadioGroup.d.ts.map +1 -0
  151. package/lib/typescript/menu/MenuRadioItem.d.ts +6 -0
  152. package/lib/typescript/menu/MenuRadioItem.d.ts.map +1 -0
  153. package/lib/typescript/menu/MenuRoot.d.ts +8 -0
  154. package/lib/typescript/menu/MenuRoot.d.ts.map +1 -0
  155. package/lib/typescript/menu/MenuTrigger.d.ts +7 -0
  156. package/lib/typescript/menu/MenuTrigger.d.ts.map +1 -0
  157. package/lib/typescript/menu/context.d.ts +15 -0
  158. package/lib/typescript/menu/context.d.ts.map +1 -0
  159. package/lib/typescript/menu/index.d.ts +12 -0
  160. package/lib/typescript/menu/index.d.ts.map +1 -0
  161. package/lib/typescript/menu/overlayContext.d.ts +35 -0
  162. package/lib/typescript/menu/overlayContext.d.ts.map +1 -0
  163. package/lib/typescript/menu/types.d.ts +104 -0
  164. package/lib/typescript/menu/types.d.ts.map +1 -0
  165. package/lib/typescript/menu/useMenuNavigation.d.ts +16 -0
  166. package/lib/typescript/menu/useMenuNavigation.d.ts.map +1 -0
  167. package/lib/typescript/menu/useMenuOverlayLifecycle.d.ts +15 -0
  168. package/lib/typescript/menu/useMenuOverlayLifecycle.d.ts.map +1 -0
  169. package/lib/typescript/menu/useMenuTrigger.d.ts +35 -0
  170. package/lib/typescript/menu/useMenuTrigger.d.ts.map +1 -0
  171. package/lib/typescript/overlay/OverlayContainer.d.ts.map +1 -1
  172. package/lib/typescript/overlay/OverlayStack.d.ts +46 -0
  173. package/lib/typescript/overlay/OverlayStack.d.ts.map +1 -0
  174. package/lib/typescript/overlay/constants.d.ts +5 -0
  175. package/lib/typescript/overlay/constants.d.ts.map +1 -1
  176. package/lib/typescript/overlay/getAnchoredOverlayPosition.d.ts +45 -0
  177. package/lib/typescript/overlay/getAnchoredOverlayPosition.d.ts.map +1 -0
  178. package/lib/typescript/overlay/index.d.ts +5 -2
  179. package/lib/typescript/overlay/index.d.ts.map +1 -1
  180. package/lib/typescript/overlay/useAnchoredOverlay.d.ts +15 -0
  181. package/lib/typescript/overlay/useAnchoredOverlay.d.ts.map +1 -0
  182. package/lib/typescript/overlay/useDismissOverlay.d.ts +2 -1
  183. package/lib/typescript/overlay/useDismissOverlay.d.ts.map +1 -1
  184. package/lib/typescript/overlay/useDismissOverlay.web.d.ts +2 -1
  185. package/lib/typescript/overlay/useDismissOverlay.web.d.ts.map +1 -1
  186. package/lib/typescript/overlay/useEscapeKey.d.ts +2 -1
  187. package/lib/typescript/overlay/useEscapeKey.d.ts.map +1 -1
  188. package/lib/typescript/overlay/useEscapeKey.web.d.ts +2 -1
  189. package/lib/typescript/overlay/useEscapeKey.web.d.ts.map +1 -1
  190. package/lib/typescript/overlay/useOverlayPosition.d.ts +5 -1
  191. package/lib/typescript/overlay/useOverlayPosition.d.ts.map +1 -1
  192. package/lib/typescript/portal/portal.d.ts +3 -0
  193. package/lib/typescript/portal/portal.d.ts.map +1 -1
  194. package/lib/typescript/select/SelectContent.d.ts.map +1 -1
  195. package/lib/typescript/select/useListboxNavigation.d.ts.map +1 -1
  196. package/lib/typescript/tooltip/TooltipContent.d.ts.map +1 -1
  197. package/lib/typescript/tooltip/getTooltipPosition.d.ts +6 -8
  198. package/lib/typescript/tooltip/getTooltipPosition.d.ts.map +1 -1
  199. package/package.json +3 -3
  200. package/src/dialog/DialogContent.tsx +16 -4
  201. package/src/dialog/DialogPopup.tsx +5 -1
  202. package/src/index.ts +23 -0
  203. package/src/menu/MenuCheckboxItem.tsx +51 -0
  204. package/src/menu/MenuItem.tsx +197 -0
  205. package/src/menu/MenuOverlayRoot.tsx +100 -0
  206. package/src/menu/MenuPopup.tsx +81 -0
  207. package/src/menu/MenuRadioGroup.tsx +48 -0
  208. package/src/menu/MenuRadioItem.tsx +32 -0
  209. package/src/menu/MenuRoot.tsx +190 -0
  210. package/src/menu/MenuTrigger.tsx +57 -0
  211. package/src/menu/context.tsx +38 -0
  212. package/src/menu/index.ts +36 -0
  213. package/src/menu/overlayContext.tsx +61 -0
  214. package/src/menu/types.ts +119 -0
  215. package/src/menu/useMenuNavigation.ts +102 -0
  216. package/src/menu/useMenuOverlayLifecycle.ts +77 -0
  217. package/src/menu/useMenuTrigger.ts +161 -0
  218. package/src/overlay/OverlayContainer.tsx +3 -1
  219. package/src/overlay/OverlayStack.tsx +202 -0
  220. package/src/overlay/constants.ts +6 -0
  221. package/src/overlay/getAnchoredOverlayPosition.ts +326 -0
  222. package/src/overlay/index.ts +29 -1
  223. package/src/overlay/useAnchoredOverlay.ts +153 -0
  224. package/src/overlay/useDismissOverlay.ts +10 -2
  225. package/src/overlay/useDismissOverlay.web.ts +9 -2
  226. package/src/overlay/useEscapeKey.ts +13 -1
  227. package/src/overlay/useEscapeKey.web.ts +13 -3
  228. package/src/overlay/useOverlayPosition.ts +35 -8
  229. package/src/portal/portal.tsx +109 -69
  230. package/src/select/SelectContent.tsx +29 -17
  231. package/src/select/useListboxNavigation.ts +12 -9
  232. package/src/tooltip/TooltipContent.tsx +32 -34
  233. package/src/tooltip/getTooltipPosition.ts +58 -125
@@ -1,13 +1,8 @@
1
1
  import React, { forwardRef, useCallback, useRef, type ComponentRef } from 'react';
2
- import { Platform, ScrollView, View } from 'react-native';
2
+ import { Platform, View } from 'react-native';
3
3
  import { FadeIn, FadeOut } from 'react-native-reanimated';
4
4
  import { composeEventHandlers, mergeRefs } from '@cdx-ui/utils';
5
- import {
6
- OverlayContainer,
7
- useAnchorPosition,
8
- useDismissOverlay,
9
- useOverlayPosition,
10
- } from '../overlay';
5
+ import { OverlayContainer, useAnchoredOverlay, useDismissOverlay } from '../overlay';
11
6
  import { Slot } from '../slot';
12
7
  import { dataAttributes, mergeDataAttributes } from '../utils/dataAttributes';
13
8
  import { useSelectContext } from './context';
@@ -21,7 +16,7 @@ export interface SelectContentProps extends ISelectContentProps {
21
16
  }
22
17
 
23
18
  export const SelectContent = forwardRef<ComponentRef<typeof View>, SelectContentProps>(
24
- ({ asChild, children, enteringAnimation, exitingAnimation, ...props }, ref) => {
19
+ ({ asChild, children, enteringAnimation, exitingAnimation, onLayout, style, ...props }, ref) => {
25
20
  const {
26
21
  open,
27
22
  setOpen,
@@ -49,14 +44,23 @@ export const SelectContent = forwardRef<ComponentRef<typeof View>, SelectContent
49
44
  }
50
45
  }, []);
51
46
 
52
- const mergedRef = mergeRefs(ref, focusOnMount as unknown as React.Ref<View>);
53
-
54
47
  const dismiss = useCallback(() => setOpen(false), [setOpen]);
55
48
 
56
- const anchorLayout = useAnchorPosition(triggerRef, open);
57
- const position = useOverlayPosition(anchorLayout);
49
+ const overlayOpen = open && !native;
50
+ const {
51
+ floatingRef,
52
+ onFloatingLayout,
53
+ style: overlayStyle,
54
+ position,
55
+ } = useAnchoredOverlay(triggerRef, overlayOpen, {
56
+ placement: 'bottom-start',
57
+ offset: 4,
58
+ collisionBehavior: 'flip-shift',
59
+ width: 'anchor',
60
+ });
61
+ const mergedRef = mergeRefs(ref, focusOnMount as unknown as React.Ref<View>, floatingRef);
58
62
 
59
- useDismissOverlay(open, contentRef, [triggerRef], dismiss);
63
+ useDismissOverlay(overlayOpen, contentRef, [triggerRef], dismiss);
60
64
 
61
65
  const { handleKeyDown } = useListboxNavigation({
62
66
  items,
@@ -77,7 +81,7 @@ export const SelectContent = forwardRef<ComponentRef<typeof View>, SelectContent
77
81
  setActiveValue,
78
82
  });
79
83
 
80
- if (native || !open || !position) {
84
+ if (!overlayOpen) {
81
85
  return null;
82
86
  }
83
87
 
@@ -99,18 +103,26 @@ export const SelectContent = forwardRef<ComponentRef<typeof View>, SelectContent
99
103
  onDismiss={dismiss}
100
104
  entering={enteringAnimation ?? FadeIn.duration(150)}
101
105
  exiting={exitingAnimation ?? FadeOut.duration(100)}
102
- style={position.style}
103
106
  >
104
107
  <Comp
105
108
  ref={mergedRef}
109
+ onLayout={composeEventHandlers(onLayout, onFloatingLayout)}
110
+ style={[style, overlayStyle]}
106
111
  role="listbox"
107
112
  id={contentId}
108
113
  aria-labelledby={triggerId}
109
114
  {...props}
110
- {...mergeDataAttributes(props, dataAttributes({ state: 'open' }))}
115
+ {...mergeDataAttributes(
116
+ props,
117
+ dataAttributes({
118
+ state: 'open',
119
+ side: position?.resolvedPlacement.side,
120
+ align: position?.resolvedPlacement.align,
121
+ }),
122
+ )}
111
123
  {...webProps}
112
124
  >
113
- <ScrollView scrollEventThrottle={16}>{children}</ScrollView>
125
+ {children}
114
126
  </Comp>
115
127
  </OverlayContainer>
116
128
  );
@@ -28,6 +28,18 @@ export function useListboxNavigation({
28
28
 
29
29
  const handleKeyDown = useCallback(
30
30
  (e: any) => {
31
+ if (e.key === 'Escape') {
32
+ e.preventDefault();
33
+ e.stopPropagation?.();
34
+ onDismissRef.current();
35
+ return;
36
+ }
37
+
38
+ if (e.key === 'Tab') {
39
+ onDismissRef.current();
40
+ return;
41
+ }
42
+
31
43
  const enabledItems = getEnabledItems();
32
44
  if (enabledItems.length === 0) {
33
45
  return;
@@ -67,15 +79,6 @@ export function useListboxNavigation({
67
79
  }
68
80
  break;
69
81
  }
70
- case 'Escape': {
71
- e.preventDefault();
72
- onDismissRef.current();
73
- break;
74
- }
75
- case 'Tab': {
76
- onDismissRef.current();
77
- break;
78
- }
79
82
  }
80
83
  },
81
84
  [activeValue, getEnabledItems, setActiveValue],
@@ -1,26 +1,19 @@
1
- import {
2
- forwardRef,
3
- useCallback,
4
- useLayoutEffect,
5
- useMemo,
6
- useRef,
7
- type ComponentRef,
8
- } from 'react';
9
- import { Platform, StyleSheet, View, useWindowDimensions } from 'react-native';
10
- import { mergeRefs } from '@cdx-ui/utils';
1
+ import { forwardRef, useCallback, useLayoutEffect, type ComponentRef } from 'react';
2
+ import { Platform, StyleSheet, View } from 'react-native';
3
+ import { composeEventHandlers, mergeRefs } from '@cdx-ui/utils';
11
4
  import { Portal } from '../portal';
12
- import { useAnchorPosition, useDismissOverlay, useEscapeKey } from '../overlay';
5
+ import { useAnchoredOverlay, useDismissOverlay, useEscapeKey } from '../overlay';
13
6
  import { createPortalFn } from '../utils/createPortal';
14
7
  import { Slot } from '../slot';
15
8
  import { dataAttributes, mergeDataAttributes } from '../utils/dataAttributes';
16
9
  import { TooltipProvider, useTooltipContext } from './context';
17
- import { getTooltipAnchorStyle } from './getTooltipPosition';
10
+ import { TOOLTIP_Z_INDEX } from './getTooltipPosition';
18
11
  import type { ITooltipContentProps } from './types';
19
12
 
20
13
  export interface TooltipContentProps extends ITooltipContentProps {}
21
14
 
22
15
  export const TooltipContent = forwardRef<ComponentRef<typeof View>, TooltipContentProps>(
23
- ({ asChild, children, ...props }, ref) => {
16
+ ({ asChild, children, onLayout, ...props }, ref) => {
24
17
  const contextValue = useTooltipContext();
25
18
  const {
26
19
  open,
@@ -31,11 +24,22 @@ export const TooltipContent = forwardRef<ComponentRef<typeof View>, TooltipConte
31
24
  trigger,
32
25
  triggerRef,
33
26
  contentId,
27
+ resolvedPlacement,
34
28
  setResolvedPlacement,
35
29
  } = contextValue;
36
30
 
37
- const contentRef = useRef<ComponentRef<typeof View>>(null);
38
- const mergedRef = mergeRefs(ref, contentRef);
31
+ const {
32
+ floatingRef,
33
+ onFloatingLayout,
34
+ style: overlayStyle,
35
+ position,
36
+ } = useAnchoredOverlay(triggerRef, open && !isDisabled, {
37
+ placement,
38
+ offset,
39
+ collisionBehavior: 'none',
40
+ width: 'content',
41
+ });
42
+ const mergedRef = mergeRefs(ref, floatingRef);
39
43
 
40
44
  const handleDismiss = useCallback(() => {
41
45
  setOpen(false);
@@ -44,27 +48,20 @@ export const TooltipContent = forwardRef<ComponentRef<typeof View>, TooltipConte
44
48
  const shouldDismissOnInteraction =
45
49
  trigger === 'click' && Platform.OS === 'web' && open && !isDisabled;
46
50
 
47
- useDismissOverlay(shouldDismissOnInteraction, contentRef, [triggerRef], handleDismiss);
51
+ useDismissOverlay(shouldDismissOnInteraction, floatingRef, [triggerRef], handleDismiss);
48
52
  useEscapeKey(shouldDismissOnInteraction, handleDismiss);
49
53
 
50
- const { width, height } = useWindowDimensions();
51
- const anchorLayout = useAnchorPosition(triggerRef, open && !isDisabled);
52
-
53
- const position = useMemo(() => {
54
- if (!anchorLayout) {
55
- return null;
56
- }
57
-
58
- return getTooltipAnchorStyle(anchorLayout, placement, offset, { width, height });
59
- }, [anchorLayout, height, offset, placement, width]);
60
-
61
54
  useLayoutEffect(() => {
62
- if (position) {
63
- setResolvedPlacement(position.resolved);
55
+ if (
56
+ position &&
57
+ (resolvedPlacement?.side !== position.resolvedPlacement.side ||
58
+ resolvedPlacement.align !== position.resolvedPlacement.align)
59
+ ) {
60
+ setResolvedPlacement(position.resolvedPlacement);
64
61
  }
65
- }, [position, setResolvedPlacement]);
62
+ }, [position, resolvedPlacement, setResolvedPlacement]);
66
63
 
67
- if (!open || isDisabled || !position) {
64
+ if (!open || isDisabled) {
68
65
  return null;
69
66
  }
70
67
 
@@ -82,16 +79,17 @@ export const TooltipContent = forwardRef<ComponentRef<typeof View>, TooltipConte
82
79
 
83
80
  const popup = (
84
81
  <TooltipProvider value={contextValue}>
85
- <View pointerEvents="none" style={position.style}>
82
+ <View pointerEvents="none" style={[overlayStyle, { zIndex: TOOLTIP_Z_INDEX }]}>
86
83
  <Comp
87
84
  ref={mergedRef}
85
+ onLayout={composeEventHandlers(onLayout, onFloatingLayout)}
88
86
  {...props}
89
87
  {...webProps}
90
88
  {...mergeDataAttributes(
91
89
  props,
92
90
  dataAttributes({
93
- side: position.resolved.side,
94
- align: position.resolved.align,
91
+ side: position?.resolvedPlacement.side,
92
+ align: position?.resolvedPlacement.align,
95
93
  state: 'open',
96
94
  }),
97
95
  )}
@@ -1,88 +1,57 @@
1
1
  import { Platform, type ViewStyle } from 'react-native';
2
2
  import type { AnchorLayout } from '../overlay/useAnchorPosition';
3
-
4
- export const TOOLTIP_PLACEMENTS = [
5
- 'auto',
6
- 'auto-start',
7
- 'auto-end',
8
- 'top',
9
- 'top-start',
10
- 'top-end',
11
- 'bottom',
12
- 'bottom-start',
13
- 'bottom-end',
14
- 'left',
15
- 'left-start',
16
- 'left-end',
17
- 'right',
18
- 'right-start',
19
- 'right-end',
20
- ] as const;
21
-
22
- export type TooltipPlacement = (typeof TOOLTIP_PLACEMENTS)[number];
3
+ import {
4
+ getAnchoredOverlayPosition,
5
+ OVERLAY_PLACEMENTS,
6
+ type OverlayAlign,
7
+ type OverlayPlacement,
8
+ type OverlaySide,
9
+ type ResolvedOverlayPlacement,
10
+ } from '../overlay/getAnchoredOverlayPosition';
11
+
12
+ export const TOOLTIP_PLACEMENTS = OVERLAY_PLACEMENTS;
13
+
14
+ export type TooltipPlacement = OverlayPlacement;
23
15
  export type TooltipTriggerMode = 'hover' | 'focus' | 'click';
24
- export type TooltipSide = 'top' | 'bottom' | 'left' | 'right';
25
- export type TooltipAlign = 'start' | 'center' | 'end';
26
-
27
- export interface ResolvedTooltipPlacement {
28
- readonly side: TooltipSide;
29
- readonly align: TooltipAlign;
30
- }
16
+ export type TooltipSide = OverlaySide;
17
+ export type TooltipAlign = OverlayAlign;
18
+ export type ResolvedTooltipPlacement = Readonly<ResolvedOverlayPlacement>;
31
19
 
32
20
  export const TOOLTIP_Z_INDEX = 9999;
33
- const SCREEN_PADDING = 8;
34
21
 
35
- function parsePlacement(placement: TooltipPlacement): ResolvedTooltipPlacement | 'auto' {
36
- if (placement === 'auto' || placement === 'auto-start' || placement === 'auto-end') {
37
- return 'auto';
22
+ function getCompatibilityTransform({
23
+ side,
24
+ align,
25
+ }: ResolvedTooltipPlacement): ViewStyle['transform'] {
26
+ if (side === 'top') {
27
+ return align === 'center'
28
+ ? ([{ translateX: '-50%' }, { translateY: '-100%' }] as ViewStyle['transform'])
29
+ : align === 'end'
30
+ ? ([{ translateX: '-100%' }, { translateY: '-100%' }] as ViewStyle['transform'])
31
+ : ([{ translateY: '-100%' }] as ViewStyle['transform']);
38
32
  }
39
33
 
40
- const segments = placement.split('-');
41
- const side = segments[0] as TooltipSide;
42
-
43
- if (segments.length === 1) {
44
- return { side, align: 'center' };
34
+ if (side === 'bottom') {
35
+ return align === 'center'
36
+ ? ([{ translateX: '-50%' }] as ViewStyle['transform'])
37
+ : align === 'end'
38
+ ? ([{ translateX: '-100%' }] as ViewStyle['transform'])
39
+ : undefined;
45
40
  }
46
41
 
47
- return { side, align: segments[1] as TooltipAlign };
48
- }
49
-
50
- function resolveAutoPlacement(
51
- anchor: AnchorLayout,
52
- viewport: { width: number; height: number },
53
- alignPreference: TooltipAlign,
54
- ): ResolvedTooltipPlacement {
55
- const spaceTop = anchor.y - SCREEN_PADDING;
56
- const spaceBottom = viewport.height - anchor.y - anchor.height - SCREEN_PADDING;
57
- const spaceLeft = anchor.x - SCREEN_PADDING;
58
- const spaceRight = viewport.width - anchor.x - anchor.width - SCREEN_PADDING;
59
-
60
- const spaces: { side: TooltipSide; value: number }[] = [
61
- { side: 'top', value: spaceTop },
62
- { side: 'bottom', value: spaceBottom },
63
- { side: 'left', value: spaceLeft },
64
- { side: 'right', value: spaceRight },
65
- ];
66
-
67
- const best = spaces.reduce((current, next) => (next.value > current.value ? next : current));
68
-
69
- return { side: best.side, align: alignPreference };
70
- }
71
-
72
- function getAlignOffset(
73
- anchor: AnchorLayout,
74
- align: TooltipAlign,
75
- axis: 'horizontal' | 'vertical',
76
- ): number {
77
- if (align === 'start') {
78
- return axis === 'horizontal' ? anchor.x : anchor.y;
79
- }
80
-
81
- if (align === 'end') {
82
- return axis === 'horizontal' ? anchor.x + anchor.width : anchor.y + anchor.height;
42
+ if (side === 'left') {
43
+ return align === 'center'
44
+ ? ([{ translateX: '-100%' }, { translateY: '-50%' }] as ViewStyle['transform'])
45
+ : align === 'end'
46
+ ? ([{ translateX: '-100%' }, { translateY: '-100%' }] as ViewStyle['transform'])
47
+ : ([{ translateX: '-100%' }] as ViewStyle['transform']);
83
48
  }
84
49
 
85
- return axis === 'horizontal' ? anchor.x + anchor.width / 2 : anchor.y + anchor.height / 2;
50
+ return align === 'center'
51
+ ? ([{ translateY: '-50%' }] as ViewStyle['transform'])
52
+ : align === 'end'
53
+ ? ([{ translateY: '-100%' }] as ViewStyle['transform'])
54
+ : undefined;
86
55
  }
87
56
 
88
57
  export function getTooltipAnchorStyle(
@@ -91,57 +60,21 @@ export function getTooltipAnchorStyle(
91
60
  offset: number,
92
61
  viewport: { width: number; height: number },
93
62
  ): { style: ViewStyle; resolved: ResolvedTooltipPlacement } {
94
- const parsed = parsePlacement(placement);
95
- const resolved =
96
- parsed === 'auto'
97
- ? resolveAutoPlacement(
98
- anchor,
99
- viewport,
100
- placement === 'auto-start' ? 'start' : placement === 'auto-end' ? 'end' : 'center',
101
- )
102
- : parsed;
103
-
63
+ const result = getAnchoredOverlayPosition(anchor, { width: 0, height: 0 }, viewport, {
64
+ placement,
65
+ offset,
66
+ collisionBehavior: 'none',
67
+ });
68
+ const resolved = result.resolvedPlacement;
104
69
  const position = (Platform.OS === 'web' ? 'fixed' : 'absolute') as ViewStyle['position'];
105
- const style: ViewStyle = { position, zIndex: TOOLTIP_Z_INDEX };
106
- const { side, align } = resolved;
107
-
108
- if (side === 'top') {
109
- style.top = anchor.y - offset;
110
- style.left = getAlignOffset(anchor, align, 'horizontal');
111
- style.transform =
112
- align === 'center'
113
- ? ([{ translateX: '-50%' }, { translateY: '-100%' }] as ViewStyle['transform'])
114
- : align === 'end'
115
- ? ([{ translateX: '-100%' }, { translateY: '-100%' }] as ViewStyle['transform'])
116
- : ([{ translateY: '-100%' }] as ViewStyle['transform']);
117
- } else if (side === 'bottom') {
118
- style.top = anchor.y + anchor.height + offset;
119
- style.left = getAlignOffset(anchor, align, 'horizontal');
120
- style.transform =
121
- align === 'center'
122
- ? ([{ translateX: '-50%' }] as ViewStyle['transform'])
123
- : align === 'end'
124
- ? ([{ translateX: '-100%' }] as ViewStyle['transform'])
125
- : undefined;
126
- } else if (side === 'left') {
127
- style.left = anchor.x - offset;
128
- style.top = getAlignOffset(anchor, align, 'vertical');
129
- style.transform =
130
- align === 'center'
131
- ? ([{ translateX: '-100%' }, { translateY: '-50%' }] as ViewStyle['transform'])
132
- : align === 'end'
133
- ? ([{ translateX: '-100%' }, { translateY: '-100%' }] as ViewStyle['transform'])
134
- : ([{ translateX: '-100%' }] as ViewStyle['transform']);
135
- } else {
136
- style.left = anchor.x + anchor.width + offset;
137
- style.top = getAlignOffset(anchor, align, 'vertical');
138
- style.transform =
139
- align === 'center'
140
- ? ([{ translateY: '-50%' }] as ViewStyle['transform'])
141
- : align === 'end'
142
- ? ([{ translateY: '-100%' }] as ViewStyle['transform'])
143
- : undefined;
144
- }
145
-
146
- return { style, resolved };
70
+ return {
71
+ style: {
72
+ position,
73
+ left: result.style.left,
74
+ top: result.style.top,
75
+ zIndex: TOOLTIP_Z_INDEX,
76
+ transform: getCompatibilityTransform(resolved),
77
+ },
78
+ resolved,
79
+ };
147
80
  }