@atlaskit/top-layer 0.12.2 → 0.14.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.
Files changed (245) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/README.md +61 -34
  3. package/dist/cjs/animations/presets.js +4 -4
  4. package/dist/cjs/entry-points/focus.js +6 -4
  5. package/dist/cjs/entry-points/get-aria-for-trigger.js +12 -0
  6. package/dist/cjs/entry-points/popover-surface.js +12 -0
  7. package/dist/cjs/entry-points/use-anchor-position-at-point.js +12 -0
  8. package/dist/cjs/entry-points/use-popover-id.js +21 -0
  9. package/dist/cjs/focus/get-first-focusable.js +19 -0
  10. package/dist/cjs/focus/get-focusables.js +30 -0
  11. package/dist/cjs/focus/get-last-focusable.js +19 -0
  12. package/dist/cjs/focus/get-next-focusable.js +43 -0
  13. package/dist/cjs/focus/is-in-same-layer.js +23 -0
  14. package/dist/cjs/focus/is-nested-layer-focused.js +32 -0
  15. package/dist/cjs/focus/selectors.js +19 -0
  16. package/dist/cjs/focus/types.js +1 -0
  17. package/dist/cjs/internal/get-aria-for-trigger.js +88 -0
  18. package/dist/cjs/internal/role-to-aria-has-popup.js +37 -0
  19. package/dist/cjs/internal/role-types.js +2 -35
  20. package/dist/cjs/internal/should-focus-into-popover.js +19 -0
  21. package/dist/cjs/internal/slot.js +1 -1
  22. package/dist/cjs/internal/use-anchor-position-at-point.js +127 -0
  23. package/dist/cjs/internal/use-anchor-position.js +36 -16
  24. package/dist/cjs/internal/use-focus-wrap.js +8 -5
  25. package/dist/cjs/internal/use-initial-focus.js +4 -4
  26. package/dist/cjs/placement-map/index.js +2 -2
  27. package/dist/cjs/placement-map/legacy-placements.js +2 -101
  28. package/dist/cjs/placement-map/placement-mapping.js +103 -0
  29. package/dist/cjs/popover/popover.js +79 -80
  30. package/dist/cjs/popover-surface/index.js +12 -0
  31. package/dist/cjs/{popup/popup-surface.js → popover-surface/popover-surface.js} +9 -12
  32. package/dist/cjs/testing/polyfill.js +9 -6
  33. package/dist/cjs/use-arrow-navigation/use-arrow-navigation.js +7 -5
  34. package/dist/es2019/animations/presets.js +4 -4
  35. package/dist/es2019/entry-points/focus.js +3 -1
  36. package/dist/es2019/entry-points/get-aria-for-trigger.js +1 -0
  37. package/dist/es2019/entry-points/popover-surface.js +1 -0
  38. package/dist/es2019/entry-points/use-anchor-position-at-point.js +1 -0
  39. package/dist/es2019/entry-points/use-popover-id.js +15 -0
  40. package/dist/es2019/focus/get-first-focusable.js +14 -0
  41. package/dist/es2019/focus/get-focusables.js +19 -0
  42. package/dist/es2019/focus/get-last-focusable.js +14 -0
  43. package/dist/es2019/focus/get-next-focusable.js +38 -0
  44. package/dist/es2019/focus/is-in-same-layer.js +18 -0
  45. package/dist/es2019/focus/is-nested-layer-focused.js +28 -0
  46. package/dist/es2019/focus/selectors.js +11 -0
  47. package/dist/es2019/focus/types.js +0 -0
  48. package/dist/es2019/internal/get-aria-for-trigger.js +84 -0
  49. package/dist/es2019/internal/role-to-aria-has-popup.js +32 -0
  50. package/dist/es2019/internal/role-types.js +0 -33
  51. package/dist/es2019/internal/should-focus-into-popover.js +14 -0
  52. package/dist/es2019/internal/slot.js +1 -1
  53. package/dist/es2019/internal/use-anchor-position-at-point.js +122 -0
  54. package/dist/es2019/internal/use-anchor-position.js +21 -14
  55. package/dist/es2019/internal/use-focus-wrap.js +4 -1
  56. package/dist/es2019/internal/use-initial-focus.js +1 -1
  57. package/dist/es2019/placement-map/index.js +1 -1
  58. package/dist/es2019/placement-map/legacy-placements.js +1 -100
  59. package/dist/es2019/placement-map/placement-mapping.js +97 -0
  60. package/dist/es2019/popover/popover.js +79 -80
  61. package/dist/es2019/popover-surface/index.js +1 -0
  62. package/dist/es2019/popover-surface/popover-surface.js +27 -0
  63. package/dist/es2019/testing/polyfill.js +9 -6
  64. package/dist/es2019/use-arrow-navigation/use-arrow-navigation.js +3 -1
  65. package/dist/esm/animations/presets.js +4 -4
  66. package/dist/esm/entry-points/focus.js +3 -1
  67. package/dist/esm/entry-points/get-aria-for-trigger.js +1 -0
  68. package/dist/esm/entry-points/popover-surface.js +1 -0
  69. package/dist/esm/entry-points/use-anchor-position-at-point.js +1 -0
  70. package/dist/esm/entry-points/use-popover-id.js +15 -0
  71. package/dist/esm/focus/get-first-focusable.js +13 -0
  72. package/dist/esm/focus/get-focusables.js +24 -0
  73. package/dist/esm/focus/get-last-focusable.js +13 -0
  74. package/dist/esm/focus/get-next-focusable.js +37 -0
  75. package/dist/esm/focus/is-in-same-layer.js +17 -0
  76. package/dist/esm/focus/is-nested-layer-focused.js +27 -0
  77. package/dist/esm/focus/selectors.js +13 -0
  78. package/dist/esm/focus/types.js +0 -0
  79. package/dist/esm/internal/get-aria-for-trigger.js +83 -0
  80. package/dist/esm/internal/role-to-aria-has-popup.js +31 -0
  81. package/dist/esm/internal/role-types.js +2 -34
  82. package/dist/esm/internal/should-focus-into-popover.js +13 -0
  83. package/dist/esm/internal/slot.js +1 -1
  84. package/dist/esm/internal/use-anchor-position-at-point.js +122 -0
  85. package/dist/esm/internal/use-anchor-position.js +36 -16
  86. package/dist/esm/internal/use-focus-wrap.js +4 -1
  87. package/dist/esm/internal/use-initial-focus.js +1 -1
  88. package/dist/esm/placement-map/index.js +1 -1
  89. package/dist/esm/placement-map/legacy-placements.js +1 -100
  90. package/dist/esm/placement-map/placement-mapping.js +97 -0
  91. package/dist/esm/popover/popover.js +79 -80
  92. package/dist/esm/popover-surface/index.js +1 -0
  93. package/dist/esm/popover-surface/popover-surface.js +26 -0
  94. package/dist/esm/testing/polyfill.js +9 -6
  95. package/dist/esm/use-arrow-navigation/use-arrow-navigation.js +3 -1
  96. package/dist/types/animations/presets.d.ts +4 -4
  97. package/dist/types/animations/types.d.ts +1 -1
  98. package/dist/types/dialog/types.d.ts +0 -8
  99. package/dist/types/entry-points/focus.d.ts +4 -2
  100. package/dist/types/entry-points/get-aria-for-trigger.d.ts +1 -0
  101. package/dist/types/entry-points/popover-surface.d.ts +2 -0
  102. package/dist/types/entry-points/popover.d.ts +1 -1
  103. package/dist/types/entry-points/use-anchor-position-at-point.d.ts +1 -0
  104. package/dist/types/entry-points/use-popover-id.d.ts +11 -0
  105. package/dist/types/focus/get-first-focusable.d.ts +8 -0
  106. package/dist/types/focus/get-focusables.d.ts +13 -0
  107. package/dist/types/focus/get-last-focusable.d.ts +8 -0
  108. package/dist/types/focus/get-next-focusable.d.ts +13 -0
  109. package/dist/types/focus/is-in-same-layer.d.ts +8 -0
  110. package/dist/types/focus/is-nested-layer-focused.d.ts +8 -0
  111. package/dist/types/focus/selectors.d.ts +4 -0
  112. package/dist/types/focus/types.d.ts +5 -0
  113. package/dist/types/internal/get-aria-for-trigger.d.ts +86 -0
  114. package/dist/types/internal/role-to-aria-has-popup.d.ts +13 -0
  115. package/dist/types/internal/role-types.d.ts +0 -12
  116. package/dist/types/internal/should-focus-into-popover.d.ts +8 -0
  117. package/dist/types/internal/slot.d.ts +2 -2
  118. package/dist/types/internal/use-anchor-position-at-point.d.ts +62 -0
  119. package/dist/types/internal/use-anchor-position.d.ts +6 -1
  120. package/dist/types/placement-map/index.d.ts +1 -1
  121. package/dist/types/placement-map/legacy-placements.d.ts +0 -12
  122. package/dist/types/placement-map/placement-mapping.d.ts +13 -0
  123. package/dist/types/popover/index.d.ts +1 -1
  124. package/dist/types/popover/popover.d.ts +6 -13
  125. package/dist/types/popover/types.d.ts +6 -5
  126. package/dist/types/popover-surface/index.d.ts +2 -0
  127. package/dist/types/popover-surface/popover-surface.d.ts +21 -0
  128. package/dist/types/use-arrow-navigation/use-arrow-navigation.d.ts +1 -1
  129. package/dist/types-ts4.5/animations/presets.d.ts +4 -4
  130. package/dist/types-ts4.5/animations/types.d.ts +1 -1
  131. package/dist/types-ts4.5/dialog/types.d.ts +0 -8
  132. package/dist/types-ts4.5/entry-points/focus.d.ts +4 -2
  133. package/dist/types-ts4.5/entry-points/get-aria-for-trigger.d.ts +1 -0
  134. package/dist/types-ts4.5/entry-points/popover-surface.d.ts +2 -0
  135. package/dist/types-ts4.5/entry-points/popover.d.ts +1 -1
  136. package/dist/types-ts4.5/entry-points/use-anchor-position-at-point.d.ts +1 -0
  137. package/dist/types-ts4.5/entry-points/use-popover-id.d.ts +11 -0
  138. package/dist/types-ts4.5/focus/get-first-focusable.d.ts +8 -0
  139. package/dist/types-ts4.5/focus/get-focusables.d.ts +13 -0
  140. package/dist/types-ts4.5/focus/get-last-focusable.d.ts +8 -0
  141. package/dist/types-ts4.5/focus/get-next-focusable.d.ts +13 -0
  142. package/dist/types-ts4.5/focus/is-in-same-layer.d.ts +8 -0
  143. package/dist/types-ts4.5/focus/is-nested-layer-focused.d.ts +8 -0
  144. package/dist/types-ts4.5/focus/selectors.d.ts +4 -0
  145. package/dist/types-ts4.5/focus/types.d.ts +5 -0
  146. package/dist/types-ts4.5/internal/get-aria-for-trigger.d.ts +86 -0
  147. package/dist/types-ts4.5/internal/role-to-aria-has-popup.d.ts +13 -0
  148. package/dist/types-ts4.5/internal/role-types.d.ts +0 -12
  149. package/dist/types-ts4.5/internal/should-focus-into-popover.d.ts +8 -0
  150. package/dist/types-ts4.5/internal/slot.d.ts +2 -2
  151. package/dist/types-ts4.5/internal/use-anchor-position-at-point.d.ts +62 -0
  152. package/dist/types-ts4.5/internal/use-anchor-position.d.ts +6 -1
  153. package/dist/types-ts4.5/placement-map/index.d.ts +1 -1
  154. package/dist/types-ts4.5/placement-map/legacy-placements.d.ts +0 -12
  155. package/dist/types-ts4.5/placement-map/placement-mapping.d.ts +13 -0
  156. package/dist/types-ts4.5/popover/index.d.ts +1 -1
  157. package/dist/types-ts4.5/popover/popover.d.ts +6 -13
  158. package/dist/types-ts4.5/popover/types.d.ts +6 -5
  159. package/dist/types-ts4.5/popover-surface/index.d.ts +2 -0
  160. package/dist/types-ts4.5/popover-surface/popover-surface.d.ts +21 -0
  161. package/dist/types-ts4.5/use-arrow-navigation/use-arrow-navigation.d.ts +1 -1
  162. package/get-aria-for-trigger/package.json +17 -0
  163. package/package.json +3 -3
  164. package/popover-surface/package.json +17 -0
  165. package/use-anchor-position-at-point/package.json +17 -0
  166. package/use-popover-id/package.json +17 -0
  167. package/dist/cjs/entry-points/popup-surface.js +0 -12
  168. package/dist/cjs/entry-points/popup.js +0 -12
  169. package/dist/cjs/focus/focus.js +0 -169
  170. package/dist/cjs/focus/index.js +0 -24
  171. package/dist/cjs/popup/index.js +0 -37
  172. package/dist/cjs/popup/popup-content.js +0 -214
  173. package/dist/cjs/popup/popup-context.js +0 -25
  174. package/dist/cjs/popup/popup-provider.js +0 -15
  175. package/dist/cjs/popup/popup-trigger-function.js +0 -80
  176. package/dist/cjs/popup/popup-trigger.js +0 -66
  177. package/dist/cjs/popup/popup.js +0 -81
  178. package/dist/cjs/popup/types.js +0 -5
  179. package/dist/cjs/popup/use-maybe-popup-context.js +0 -17
  180. package/dist/cjs/popup/use-popup-context.js +0 -20
  181. package/dist/es2019/entry-points/popup-surface.js +0 -1
  182. package/dist/es2019/entry-points/popup.js +0 -1
  183. package/dist/es2019/focus/focus.js +0 -158
  184. package/dist/es2019/focus/index.js +0 -1
  185. package/dist/es2019/popup/index.js +0 -33
  186. package/dist/es2019/popup/popup-content.js +0 -206
  187. package/dist/es2019/popup/popup-context.js +0 -20
  188. package/dist/es2019/popup/popup-provider.js +0 -10
  189. package/dist/es2019/popup/popup-surface.js +0 -30
  190. package/dist/es2019/popup/popup-trigger-function.js +0 -75
  191. package/dist/es2019/popup/popup-trigger.js +0 -61
  192. package/dist/es2019/popup/popup.js +0 -67
  193. package/dist/es2019/popup/types.js +0 -1
  194. package/dist/es2019/popup/use-maybe-popup-context.js +0 -12
  195. package/dist/es2019/popup/use-popup-context.js +0 -15
  196. package/dist/esm/entry-points/popup-surface.js +0 -1
  197. package/dist/esm/entry-points/popup.js +0 -1
  198. package/dist/esm/focus/focus.js +0 -160
  199. package/dist/esm/focus/index.js +0 -1
  200. package/dist/esm/popup/index.js +0 -33
  201. package/dist/esm/popup/popup-content.js +0 -206
  202. package/dist/esm/popup/popup-context.js +0 -20
  203. package/dist/esm/popup/popup-provider.js +0 -10
  204. package/dist/esm/popup/popup-surface.js +0 -29
  205. package/dist/esm/popup/popup-trigger-function.js +0 -75
  206. package/dist/esm/popup/popup-trigger.js +0 -59
  207. package/dist/esm/popup/popup.js +0 -72
  208. package/dist/esm/popup/types.js +0 -1
  209. package/dist/esm/popup/use-maybe-popup-context.js +0 -12
  210. package/dist/esm/popup/use-popup-context.js +0 -15
  211. package/dist/types/entry-points/popup-surface.d.ts +0 -2
  212. package/dist/types/entry-points/popup.d.ts +0 -2
  213. package/dist/types/focus/focus.d.ts +0 -50
  214. package/dist/types/focus/index.d.ts +0 -2
  215. package/dist/types/popup/index.d.ts +0 -34
  216. package/dist/types/popup/popup-content.d.ts +0 -14
  217. package/dist/types/popup/popup-context.d.ts +0 -96
  218. package/dist/types/popup/popup-provider.d.ts +0 -10
  219. package/dist/types/popup/popup-surface.d.ts +0 -22
  220. package/dist/types/popup/popup-trigger-function.d.ts +0 -112
  221. package/dist/types/popup/popup-trigger.d.ts +0 -14
  222. package/dist/types/popup/popup.d.ts +0 -14
  223. package/dist/types/popup/types.d.ts +0 -321
  224. package/dist/types/popup/use-maybe-popup-context.d.ts +0 -8
  225. package/dist/types/popup/use-popup-context.d.ts +0 -7
  226. package/dist/types-ts4.5/entry-points/popup-surface.d.ts +0 -2
  227. package/dist/types-ts4.5/entry-points/popup.d.ts +0 -2
  228. package/dist/types-ts4.5/focus/focus.d.ts +0 -50
  229. package/dist/types-ts4.5/focus/index.d.ts +0 -2
  230. package/dist/types-ts4.5/popup/index.d.ts +0 -34
  231. package/dist/types-ts4.5/popup/popup-content.d.ts +0 -14
  232. package/dist/types-ts4.5/popup/popup-context.d.ts +0 -96
  233. package/dist/types-ts4.5/popup/popup-provider.d.ts +0 -10
  234. package/dist/types-ts4.5/popup/popup-surface.d.ts +0 -22
  235. package/dist/types-ts4.5/popup/popup-trigger-function.d.ts +0 -112
  236. package/dist/types-ts4.5/popup/popup-trigger.d.ts +0 -14
  237. package/dist/types-ts4.5/popup/popup.d.ts +0 -14
  238. package/dist/types-ts4.5/popup/types.d.ts +0 -321
  239. package/dist/types-ts4.5/popup/use-maybe-popup-context.d.ts +0 -8
  240. package/dist/types-ts4.5/popup/use-popup-context.d.ts +0 -7
  241. package/popup/package.json +0 -17
  242. package/popup-surface/package.json +0 -17
  243. /package/dist/cjs/{popup/popup-surface.compiled.css → popover-surface/popover-surface.compiled.css} +0 -0
  244. /package/dist/es2019/{popup/popup-surface.compiled.css → popover-surface/popover-surface.compiled.css} +0 -0
  245. /package/dist/esm/{popup/popup-surface.compiled.css → popover-surface/popover-surface.compiled.css} +0 -0
@@ -0,0 +1,11 @@
1
+ export const selectors = (() => {
2
+ // Common exclusion filter applied to every selector.
3
+ const not = ':not([tabindex="-1"]):not([aria-disabled="true"]):not([aria-hidden="true"])';
4
+ const notDisabled = `:not([disabled])${not}`;
5
+ const focusable = [`a[href]${not}`, `area[href]${not}`, `input${notDisabled}:not([type="hidden"])`, `select${notDisabled}`, `textarea${notDisabled}`, `button${notDisabled}`, `iframe${not}`, `audio[controls]${not}`, `video[controls]${not}`, `[contenteditable]:not([contenteditable="false"])${not}`, `[tabindex]${notDisabled}${not}`];
6
+ return {
7
+ focusable: focusable.join(','),
8
+ // Build the `:focus` selector
9
+ focused: focusable.map(selector => `${selector}:focus`).join(',')
10
+ };
11
+ })();
File without changes
@@ -0,0 +1,84 @@
1
+ import { roleToAriaHasPopup } from './role-to-aria-has-popup';
2
+
3
+ /**
4
+ * Roles accepted by `getAriaForTrigger`.
5
+ *
6
+ * Excludes tooltip-family roles (`tooltip`, `note`, `status`, `alert`, `log`)
7
+ * because the correct trigger wiring for those is `aria-describedby`, not
8
+ * `aria-haspopup` / `aria-expanded` / `aria-controls`. Use `aria-describedby`
9
+ * manually for tooltip triggers.
10
+ */
11
+
12
+ /**
13
+ * Non-undefined subset of `TAriaHasPopupValue`. Every role accepted by
14
+ * `getAriaForTrigger` maps to a concrete `aria-haspopup` string, so the
15
+ * trigger always receives a defined attribute value.
16
+ */
17
+
18
+ /**
19
+ * Returns the ARIA attributes to spread onto a click-activated popover trigger.
20
+ *
21
+ * Centralises three attributes that are easy to forget or get wrong individually:
22
+ *
23
+ * - `aria-haspopup` - derived from the popover's role (e.g. `dialog → "dialog"`,
24
+ * `menu → "menu"`, `tooltip → undefined`). React omits the attribute when
25
+ * the value is `undefined`.
26
+ * - `aria-expanded` - reflects the current open state.
27
+ * - `aria-controls` - references the popover element by its id, even
28
+ * while the popover is closed. The relationship is stable; `aria-expanded`
29
+ * carries the current visibility state.
30
+ *
31
+ * **This function is for click/keyboard-activated popovers only.** For
32
+ * hover-driven tooltips, the trigger should use `aria-describedby` instead.
33
+ *
34
+ * @example
35
+ * ```tsx
36
+ * import { getAriaForTrigger } from '@atlaskit/top-layer/get-aria-for-trigger';
37
+ * import { usePopoverId } from '@atlaskit/top-layer/use-popover-id';
38
+ *
39
+ * const popoverId = usePopoverId();
40
+ * const [isOpen, setIsOpen] = useState(false);
41
+ *
42
+ * return (
43
+ * <>
44
+ * <button
45
+ * ref={triggerRef}
46
+ * onClick={() => setIsOpen((previous) => !previous)}
47
+ * {...getAriaForTrigger({ role: 'dialog', isOpen, popoverId })}
48
+ * >
49
+ * Open
50
+ * </button>
51
+ * <Popover
52
+ * id={popoverId}
53
+ * role="dialog"
54
+ * label="My dialog"
55
+ * isOpen={isOpen}
56
+ * triggerRef={triggerRef}
57
+ * onClose={() => setIsOpen(false)}
58
+ * >
59
+ * …
60
+ * </Popover>
61
+ * </>
62
+ * );
63
+ * ```
64
+ */
65
+ export function getAriaForTrigger({
66
+ role,
67
+ isOpen,
68
+ popoverId
69
+ }) {
70
+ // `roleToAriaHasPopup` accepts a wider role union (including tooltip-family
71
+ // roles that map to `undefined`). `TAriaForTriggerRole` excludes those, so
72
+ // the returned value is guaranteed to be a defined string. The cast narrows
73
+ // the wider helper signature to match.
74
+ const ariaHasPopup = roleToAriaHasPopup({
75
+ role
76
+ });
77
+ return {
78
+ 'aria-haspopup': ariaHasPopup,
79
+ 'aria-expanded': isOpen,
80
+ // Keep the trigger-to-popover relationship stable. `aria-expanded`
81
+ // tells assistive technology whether the controlled popover is visible.
82
+ 'aria-controls': popoverId
83
+ };
84
+ }
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Maps a popover content role to the correct `aria-haspopup` value
3
+ * for its trigger element.
4
+ *
5
+ * Returns `undefined` for roles that are not popups in the ARIA sense
6
+ * (`tooltip`, `note`, `status`, `alert`, `log`). Triggers for those roles
7
+ * should not announce a popup at all - emitting `aria-haspopup="dialog"`
8
+ * on a tooltip trigger would mislead assistive technology.
9
+ */
10
+ export function roleToAriaHasPopup({
11
+ role
12
+ }) {
13
+ if (role === 'menu') {
14
+ return 'menu';
15
+ }
16
+ if (role === 'listbox') {
17
+ return 'listbox';
18
+ }
19
+ if (role === 'tree') {
20
+ return 'tree';
21
+ }
22
+ if (role === 'grid') {
23
+ return 'grid';
24
+ }
25
+ if (role === 'dialog' || role === 'alertdialog') {
26
+ return 'dialog';
27
+ }
28
+ // Non-popup roles (tooltip, note, status, alert, log) and the
29
+ // no-role case: omit `aria-haspopup` entirely. React drops the
30
+ // attribute when the value is `undefined`.
31
+ return undefined;
32
+ }
@@ -42,39 +42,6 @@
42
42
  * the role for cases where it is set externally or not needed.
43
43
  */
44
44
 
45
- /**
46
- * Maps a popover content role to the correct `aria-haspopup` value
47
- * for its trigger element.
48
- *
49
- * Returns `undefined` for roles that are not popups in the ARIA sense
50
- * (`tooltip`, `note`, `status`, `alert`, `log`). Triggers for those roles
51
- * should not announce a popup at all - emitting `aria-haspopup="dialog"`
52
- * on a tooltip trigger would mislead assistive technology.
53
- */
54
- // eslint-disable-next-line @atlaskit/volt-strict-mode/no-multiple-exports
55
- export function roleToAriaHasPopup({
56
- role
57
- }) {
58
- if (role === 'menu') {
59
- return 'menu';
60
- }
61
- if (role === 'listbox') {
62
- return 'listbox';
63
- }
64
- if (role === 'tree') {
65
- return 'tree';
66
- }
67
- if (role === 'grid') {
68
- return 'grid';
69
- }
70
- if (role === 'dialog' || role === 'alertdialog') {
71
- return 'dialog';
72
- }
73
- // Non-popup roles (tooltip, note, status, alert, log) and the
74
- // no-role case: omit `aria-haspopup` entirely. React drops the
75
- // attribute when the value is `undefined`.
76
- return undefined;
77
- }
78
45
  const rolesThatMoveFocus = new Set(['dialog', 'alertdialog', 'menu', 'listbox', 'tree', 'grid']);
79
46
 
80
47
  /**
@@ -0,0 +1,14 @@
1
+ const rolesThatMoveFocus = new Set(['dialog', 'alertdialog', 'menu', 'listbox', 'tree', 'grid']);
2
+
3
+ /**
4
+ * Returns `true` when the given popover role moves focus into the popover
5
+ * on open, and `false` otherwise (including when `role` is `undefined`).
6
+ */
7
+ export function shouldFocusIntoPopover({
8
+ role
9
+ }) {
10
+ if (!role) {
11
+ return false;
12
+ }
13
+ return rolesThatMoveFocus.has(role);
14
+ }
@@ -43,7 +43,7 @@ export const Slot = /*#__PURE__*/forwardRef(function Slot(slotProps, ref) {
43
43
  children: rawChildren,
44
44
  ...props
45
45
  } = slotProps;
46
- // `TSlotProps` intersects `Record<string, unknown>`, so `children` is typed as `unknown`.
46
+ // `SlotProps` intersects `Record<string, unknown>`, so `children` is typed as `unknown`.
47
47
  const children = rawChildren;
48
48
  if (!isSlotChild(children)) {
49
49
  return children;
@@ -0,0 +1,122 @@
1
+ import { useLayoutEffect, useRef } from 'react';
2
+ import { useAnchorPosition } from './use-anchor-position';
3
+
4
+ /**
5
+ * A point in viewport coordinates (CSS pixels) at which the synthetic
6
+ * anchor should be placed.
7
+ *
8
+ * Axis-named (`x`/`y`) rather than edge-named (`left`/`top`) so the
9
+ * shape is unambiguous in RTL writing modes — there is no ambiguity
10
+ * about whether a coordinate is logical (`inline-start`/`block-start`)
11
+ * or physical (`left`/`top`). Viewport coordinates are always
12
+ * physical, so we name them with the physical axis labels.
13
+ */
14
+
15
+ /**
16
+ * Styles the synthetic anchor as an invisible, zero-size point.
17
+ * Dynamic `inset-*` overrides are written by the positioning effect.
18
+ *
19
+ * Uses `Object.assign` rather than Compiled CSS (element is outside the React tree)
20
+ * or the internal `setStyle` helper (we own this element entirely — it is created
21
+ * and removed by this hook — so the snapshot/restore semantics `setStyle`
22
+ * provides for caller-owned elements would be dead work here).
23
+ */
24
+ function applyStaticAnchorStyles(el) {
25
+ Object.assign(el.style, {
26
+ position: 'fixed',
27
+ // `top`/`left` rather than `inset-block-start`/`inset-inline-start`
28
+ // for consistency with the dynamic positioning effect below, which
29
+ // uses physical properties (see comment there for the rationale).
30
+ top: '0',
31
+ left: '0',
32
+ // Zero size — placement decides which side the popover sits on.
33
+ width: '0',
34
+ height: '0',
35
+ // Prevent the overlay from intercepting pointer events.
36
+ pointerEvents: 'none'
37
+ });
38
+ }
39
+
40
+ // `useAnchorPosition` requires a real DOM element. When the caller only
41
+ // has a viewport coordinate (e.g. cursor at hover time), this hook
42
+ // manages a hidden `<div>` at that point and delegates the rest.
43
+ //
44
+ // The `<div>` is appended directly to `document.body` to escape any
45
+ // transformed ancestor, which would otherwise break `position: fixed`
46
+ // viewport-relative resolution.
47
+
48
+ /**
49
+ * Positions a popover at a consumer-supplied viewport coordinate using a
50
+ * hidden synthetic anchor.
51
+ *
52
+ * @example
53
+ * ```tsx
54
+ * function PointPositionedPopover({ popoverRef, placement, mousePos }) {
55
+ * useAnchorPositionAtPoint({
56
+ * popoverRef,
57
+ * placement,
58
+ * isEnabled: Boolean(mousePos),
59
+ * getPoint: () => {
60
+ * if (!mousePos) return null;
61
+ * return { x: mousePos.clientX, y: mousePos.clientY };
62
+ * },
63
+ * });
64
+ * return <Popover ref={popoverRef}>...</Popover>;
65
+ * }
66
+ * ```
67
+ */
68
+ export function useAnchorPositionAtPoint({
69
+ popoverRef,
70
+ placement,
71
+ getPoint,
72
+ isEnabled = true
73
+ }) {
74
+ const syntheticAnchorRef = useRef(null);
75
+
76
+ // `getPoint` is kept in a ref so the layout effect can call the
77
+ // latest version without listing it as a dep — that would re-fire
78
+ // on every render and re-latch the point.
79
+ const getPointRef = useRef(getPoint);
80
+ getPointRef.current = getPoint;
81
+
82
+ // Lazy element creation: the `<div>` is only created when both
83
+ // `isEnabled` is true AND `getPoint()` returns a non-null point.
84
+ // `useLayoutEffect` ensures the element exists before the inner
85
+ // `useAnchorPosition` (also a layout effect) reads the ref.
86
+ useLayoutEffect(() => {
87
+ if (!isEnabled) {
88
+ return;
89
+ }
90
+ const point = getPointRef.current();
91
+ if (!point) {
92
+ return;
93
+ }
94
+ const anchor = document.createElement('div');
95
+ anchor.setAttribute('aria-hidden', 'true');
96
+ applyStaticAnchorStyles(anchor);
97
+ // `point.x`/`y` are physical viewport coordinates (from
98
+ // `MouseEvent.clientX/Y`), so map directly onto physical
99
+ // `left`/`top`. This is RTL-safe: `position: fixed` + physical
100
+ // insets is direction-independent. Using logical
101
+ // `inset-inline-start` would need `viewportWidth - point.x` in
102
+ // RTL to land at the same physical pixel.
103
+ anchor.style.top = `${point.y}px`;
104
+ anchor.style.left = `${point.x}px`;
105
+ document.body.appendChild(anchor);
106
+ syntheticAnchorRef.current = anchor;
107
+ return function cleanup() {
108
+ anchor.remove();
109
+ syntheticAnchorRef.current = null;
110
+ };
111
+ }, [isEnabled]);
112
+
113
+ // Mirror `isEnabled` so both hooks stay in sync — when disabled,
114
+ // neither this hook nor the inner one touches the popover, leaving
115
+ // any other positioning the consumer has set up free to take effect.
116
+ useAnchorPosition({
117
+ anchorRef: syntheticAnchorRef,
118
+ popoverRef,
119
+ placement,
120
+ isEnabled
121
+ });
122
+ }
@@ -28,13 +28,13 @@ import { setStyle } from './set-style';
28
28
  // from `./resolve-placement` so search-and-jump lands at the source of truth.
29
29
 
30
30
  /**
31
- * Module-scope no-op `ResizeObserver` stub used in non-DOM jest environments where
31
+ * Module-scope no-op `ResizeObserver` used in non-DOM jest environments where
32
32
  * `ResizeObserver` is `undefined`. Hoisted out of the effect to avoid
33
- * re-allocating on every run.
33
+ * allocating a new class declaration on every run.
34
34
  */
35
- const noopResizeObserver = {
36
- observe() {},
37
- unobserve() {},
35
+ const NoopResizeObserver = class {
36
+ observe() {}
37
+ unobserve() {}
38
38
  disconnect() {}
39
39
  };
40
40
 
@@ -202,13 +202,17 @@ export function useAnchorPosition({
202
202
  anchorRef,
203
203
  popoverRef,
204
204
  placement = {},
205
- forceFallbackPositioning = false
205
+ forceFallbackPositioning = false,
206
+ isEnabled = true
206
207
  }) {
207
208
  const id = useId();
208
209
  // Stabilize `placement` object so the same object literal
209
210
  // value returns an object with the same reference
210
211
  const stablePlacement = useStablePlacement(placement);
211
212
  useLayoutEffect(() => {
213
+ if (!isEnabled) {
214
+ return;
215
+ }
212
216
  const trigger = anchorRef.current;
213
217
  const popover = popoverRef.current;
214
218
  if (!trigger || !popover) {
@@ -399,17 +403,20 @@ export function useAnchorPosition({
399
403
  // Self-disconnects after one valid measurement; ongoing
400
404
  // scroll/resize is handled by the window listeners below.
401
405
  // `ResizeObserver` is missing in some non-DOM jest environments
402
- // (e.g. post-office's `node` environment). Fall back to the
403
- // module-scope `noopResizeObserver` stub there. The scroll/resize
404
- // listeners below still keep the popover positioned in the rare
405
- // case the consumer also polyfilled `showPopover` but not
406
- // `ResizeObserver`. Real browsers always have it.
407
- const resizeObserver = typeof ResizeObserver !== 'undefined' ? new ResizeObserver(() => {
406
+ // (e.g. post-office's `node` environment). Fall back to a no-op
407
+ // observer there. The scroll/resize listeners below still keep
408
+ // the popover positioned in the rare case the consumer also
409
+ // polyfilled `showPopover` but not `ResizeObserver`. Real
410
+ // browsers always have it. The `NoopResizeObserver` class is
411
+ // hoisted to module scope (see top of file) so we do not allocate
412
+ // a new constructor on every effect run.
413
+ const ResizeObserverImpl = typeof ResizeObserver !== 'undefined' ? ResizeObserver : NoopResizeObserver;
414
+ const resizeObserver = new ResizeObserverImpl(() => {
408
415
  if (popover.offsetWidth > 0 && popover.offsetHeight > 0) {
409
416
  update();
410
417
  resizeObserver.disconnect();
411
418
  }
412
- }) : noopResizeObserver;
419
+ });
413
420
 
414
421
  // If the popover is already open by the time this effect runs (for
415
422
  // example, the parent `useAnchorPosition` effect runs after the child
@@ -456,5 +463,5 @@ export function useAnchorPosition({
456
463
  popover.style.removeProperty('opacity');
457
464
  });
458
465
  return undoPositioning;
459
- }, [anchorRef, popoverRef, stablePlacement, forceFallbackPositioning, id]);
466
+ }, [anchorRef, popoverRef, stablePlacement, forceFallbackPositioning, isEnabled, id]);
460
467
  }
@@ -1,6 +1,9 @@
1
1
  import { useEffect } from 'react';
2
2
  import { bind } from 'bind-event-listener';
3
- import { getFirstFocusable, getLastFocusable, getNextFocusable, isNestedLayerFocused } from '../focus/focus';
3
+ import { getFirstFocusable } from '../focus/get-first-focusable';
4
+ import { getLastFocusable } from '../focus/get-last-focusable';
5
+ import { getNextFocusable } from '../focus/get-next-focusable';
6
+ import { isNestedLayerFocused } from '../focus/is-nested-layer-focused';
4
7
 
5
8
  /**
6
9
  * Roles that require focus wrapping per WAI-ARIA APG.
@@ -1,5 +1,5 @@
1
1
  import { useEffect, useRef } from 'react';
2
- import { getFirstFocusable } from '../focus/focus';
2
+ import { getFirstFocusable } from '../focus/get-first-focusable';
3
3
 
4
4
  /**
5
5
  * Determines the correct element to receive initial focus within a popover,
@@ -1,4 +1,4 @@
1
- import { placementMapping } from './legacy-placements';
1
+ import { placementMapping } from './placement-mapping';
2
2
 
3
3
  /**
4
4
  * Maps legacy Popper.js placement values to the new object-based
@@ -5,103 +5,4 @@
5
5
  * to enumerate every placement (e.g. for a settings dropdown). New code
6
6
  * should use the object-based `TPlacementOptions` directly.
7
7
  */
8
- // eslint-disable-next-line @atlaskit/volt-strict-mode/no-multiple-exports
9
- export const LEGACY_PLACEMENTS = ['auto', 'auto-start', 'auto-end', 'top', 'top-start', 'top-center', 'top-end', 'bottom', 'bottom-start', 'bottom-center', 'bottom-end', 'right', 'right-start', 'right-end', 'left', 'left-start', 'left-end'];
10
- /**
11
- * The full mapping record. Internal - use `fromLegacyPlacement` from the
12
- * public entry-point so the offset-tuple translation is applied consistently.
13
- *
14
- * Note: `auto`, `auto-start`, `auto-end` map to `block-end` placements. This
15
- * is a reduction in expressiveness vs Popper.js (which picked the best
16
- * placement at runtime). CSS `position-try-fallbacks` flips at the edge but
17
- * the initial placement is fixed. Migration consumers who relied on Popper's
18
- * dynamic auto-flip should pick an explicit placement instead.
19
- */
20
- // eslint-disable-next-line @atlaskit/volt-strict-mode/no-multiple-exports
21
- export const placementMapping = {
22
- top: {
23
- axis: 'block',
24
- edge: 'start',
25
- align: 'center'
26
- },
27
- 'top-start': {
28
- axis: 'block',
29
- edge: 'start',
30
- align: 'start'
31
- },
32
- 'top-center': {
33
- axis: 'block',
34
- edge: 'start',
35
- align: 'center'
36
- },
37
- 'top-end': {
38
- axis: 'block',
39
- edge: 'start',
40
- align: 'end'
41
- },
42
- bottom: {
43
- axis: 'block',
44
- edge: 'end',
45
- align: 'center'
46
- },
47
- 'bottom-start': {
48
- axis: 'block',
49
- edge: 'end',
50
- align: 'start'
51
- },
52
- 'bottom-center': {
53
- axis: 'block',
54
- edge: 'end',
55
- align: 'center'
56
- },
57
- 'bottom-end': {
58
- axis: 'block',
59
- edge: 'end',
60
- align: 'end'
61
- },
62
- right: {
63
- axis: 'inline',
64
- edge: 'end',
65
- align: 'center'
66
- },
67
- 'right-start': {
68
- axis: 'inline',
69
- edge: 'end',
70
- align: 'start'
71
- },
72
- 'right-end': {
73
- axis: 'inline',
74
- edge: 'end',
75
- align: 'end'
76
- },
77
- left: {
78
- axis: 'inline',
79
- edge: 'start',
80
- align: 'center'
81
- },
82
- 'left-start': {
83
- axis: 'inline',
84
- edge: 'start',
85
- align: 'start'
86
- },
87
- 'left-end': {
88
- axis: 'inline',
89
- edge: 'start',
90
- align: 'end'
91
- },
92
- auto: {
93
- axis: 'block',
94
- edge: 'end',
95
- align: 'center'
96
- },
97
- 'auto-start': {
98
- axis: 'block',
99
- edge: 'end',
100
- align: 'start'
101
- },
102
- 'auto-end': {
103
- axis: 'block',
104
- edge: 'end',
105
- align: 'end'
106
- }
107
- };
8
+ export const LEGACY_PLACEMENTS = ['auto', 'auto-start', 'auto-end', 'top', 'top-start', 'top-center', 'top-end', 'bottom', 'bottom-start', 'bottom-center', 'bottom-end', 'right', 'right-start', 'right-end', 'left', 'left-start', 'left-end'];
@@ -0,0 +1,97 @@
1
+ /**
2
+ * The full mapping record. Internal - use `fromLegacyPlacement` from the
3
+ * public entry-point so the offset-tuple translation is applied consistently.
4
+ *
5
+ * Note: `auto`, `auto-start`, `auto-end` map to `block-end` placements. This
6
+ * is a reduction in expressiveness vs Popper.js (which picked the best
7
+ * placement at runtime). CSS `position-try-fallbacks` flips at the edge but
8
+ * the initial placement is fixed. Migration consumers who relied on Popper's
9
+ * dynamic auto-flip should pick an explicit placement instead.
10
+ */
11
+ export const placementMapping = {
12
+ top: {
13
+ axis: 'block',
14
+ edge: 'start',
15
+ align: 'center'
16
+ },
17
+ 'top-start': {
18
+ axis: 'block',
19
+ edge: 'start',
20
+ align: 'start'
21
+ },
22
+ 'top-center': {
23
+ axis: 'block',
24
+ edge: 'start',
25
+ align: 'center'
26
+ },
27
+ 'top-end': {
28
+ axis: 'block',
29
+ edge: 'start',
30
+ align: 'end'
31
+ },
32
+ bottom: {
33
+ axis: 'block',
34
+ edge: 'end',
35
+ align: 'center'
36
+ },
37
+ 'bottom-start': {
38
+ axis: 'block',
39
+ edge: 'end',
40
+ align: 'start'
41
+ },
42
+ 'bottom-center': {
43
+ axis: 'block',
44
+ edge: 'end',
45
+ align: 'center'
46
+ },
47
+ 'bottom-end': {
48
+ axis: 'block',
49
+ edge: 'end',
50
+ align: 'end'
51
+ },
52
+ right: {
53
+ axis: 'inline',
54
+ edge: 'end',
55
+ align: 'center'
56
+ },
57
+ 'right-start': {
58
+ axis: 'inline',
59
+ edge: 'end',
60
+ align: 'start'
61
+ },
62
+ 'right-end': {
63
+ axis: 'inline',
64
+ edge: 'end',
65
+ align: 'end'
66
+ },
67
+ left: {
68
+ axis: 'inline',
69
+ edge: 'start',
70
+ align: 'center'
71
+ },
72
+ 'left-start': {
73
+ axis: 'inline',
74
+ edge: 'start',
75
+ align: 'start'
76
+ },
77
+ 'left-end': {
78
+ axis: 'inline',
79
+ edge: 'start',
80
+ align: 'end'
81
+ },
82
+ auto: {
83
+ axis: 'block',
84
+ edge: 'end',
85
+ align: 'center'
86
+ },
87
+ 'auto-start': {
88
+ axis: 'block',
89
+ edge: 'end',
90
+ align: 'start'
91
+ },
92
+ 'auto-end': {
93
+ axis: 'block',
94
+ edge: 'end',
95
+ align: 'end'
96
+ }
97
+ };