@atlaskit/top-layer 0.1.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 (300) hide show
  1. package/CHANGELOG.md +44 -0
  2. package/README.md +151 -0
  3. package/animations/package.json +17 -0
  4. package/arrow/package.json +17 -0
  5. package/create-close-event/package.json +17 -0
  6. package/dialog/package.json +17 -0
  7. package/dialog-scroll-lock/package.json +17 -0
  8. package/dist/cjs/animations/presets.js +199 -0
  9. package/dist/cjs/animations/types.js +5 -0
  10. package/dist/cjs/arrow/index.js +130 -0
  11. package/dist/cjs/arrow/types.js +5 -0
  12. package/dist/cjs/dialog/create-close-event.js +39 -0
  13. package/dist/cjs/dialog/dialog-content.compiled.css +14 -0
  14. package/dist/cjs/dialog/dialog-content.js +144 -0
  15. package/dist/cjs/dialog/index.js +19 -0
  16. package/dist/cjs/dialog/types.js +5 -0
  17. package/dist/cjs/dialog-scroll-lock/index.js +46 -0
  18. package/dist/cjs/entry-points/animations.js +36 -0
  19. package/dist/cjs/entry-points/arrow.js +12 -0
  20. package/dist/cjs/entry-points/create-close-event.js +19 -0
  21. package/dist/cjs/entry-points/dialog-scroll-lock.js +12 -0
  22. package/dist/cjs/entry-points/dialog.js +12 -0
  23. package/dist/cjs/entry-points/focus.js +24 -0
  24. package/dist/cjs/entry-points/placement-map.js +18 -0
  25. package/dist/cjs/entry-points/popover.js +12 -0
  26. package/dist/cjs/entry-points/popup-surface.js +12 -0
  27. package/dist/cjs/entry-points/popup.js +12 -0
  28. package/dist/cjs/entry-points/use-anchor-position.js +12 -0
  29. package/dist/cjs/entry-points/use-arrow-navigation.js +18 -0
  30. package/dist/cjs/entry-points/use-simple-light-dismiss.js +12 -0
  31. package/dist/cjs/focus/focus.js +115 -0
  32. package/dist/cjs/focus/index.js +24 -0
  33. package/dist/cjs/index.js +8 -0
  34. package/dist/cjs/internal/anchor-positioning-fallback.js +154 -0
  35. package/dist/cjs/internal/combine.js +29 -0
  36. package/dist/cjs/internal/ensure-preset-styles.js +34 -0
  37. package/dist/cjs/internal/reduced-motion.js +27 -0
  38. package/dist/cjs/internal/resolve-placement.js +24 -0
  39. package/dist/cjs/internal/role-types.js +66 -0
  40. package/dist/cjs/internal/set-style.js +42 -0
  41. package/dist/cjs/internal/slot.js +65 -0
  42. package/dist/cjs/internal/use-anchor-positioning.js +336 -0
  43. package/dist/cjs/internal/use-animated-visibility.js +169 -0
  44. package/dist/cjs/internal/use-animation-preset.js +24 -0
  45. package/dist/cjs/internal/use-focus-wrap.js +94 -0
  46. package/dist/cjs/internal/use-initial-focus.js +112 -0
  47. package/dist/cjs/internal/use-preset-styles.js +27 -0
  48. package/dist/cjs/placement-map/index.js +147 -0
  49. package/dist/cjs/popover/create-close-event.js +44 -0
  50. package/dist/cjs/popover/index.js +19 -0
  51. package/dist/cjs/popover/popover.compiled.css +13 -0
  52. package/dist/cjs/popover/popover.js +251 -0
  53. package/dist/cjs/popover/types.js +5 -0
  54. package/dist/cjs/popup/index.js +37 -0
  55. package/dist/cjs/popup/popup-content.js +201 -0
  56. package/dist/cjs/popup/popup-context.js +42 -0
  57. package/dist/cjs/popup/popup-surface.compiled.css +5 -0
  58. package/dist/cjs/popup/popup-surface.js +30 -0
  59. package/dist/cjs/popup/popup-trigger-function.js +74 -0
  60. package/dist/cjs/popup/popup-trigger.js +82 -0
  61. package/dist/cjs/popup/popup.js +70 -0
  62. package/dist/cjs/popup/types.js +5 -0
  63. package/dist/cjs/use-arrow-navigation/index.js +18 -0
  64. package/dist/cjs/use-arrow-navigation/use-arrow-navigation.js +245 -0
  65. package/dist/cjs/use-simple-light-dismiss/index.js +84 -0
  66. package/dist/es2019/animations/presets.js +375 -0
  67. package/dist/es2019/animations/types.js +1 -0
  68. package/dist/es2019/arrow/index.js +249 -0
  69. package/dist/es2019/arrow/types.js +1 -0
  70. package/dist/es2019/dialog/create-close-event.js +34 -0
  71. package/dist/es2019/dialog/dialog-content.compiled.css +14 -0
  72. package/dist/es2019/dialog/dialog-content.js +138 -0
  73. package/dist/es2019/dialog/index.js +2 -0
  74. package/dist/es2019/dialog/types.js +1 -0
  75. package/dist/es2019/dialog-scroll-lock/index.js +41 -0
  76. package/dist/es2019/entry-points/animations.js +1 -0
  77. package/dist/es2019/entry-points/arrow.js +1 -0
  78. package/dist/es2019/entry-points/create-close-event.js +2 -0
  79. package/dist/es2019/entry-points/dialog-scroll-lock.js +1 -0
  80. package/dist/es2019/entry-points/dialog.js +1 -0
  81. package/dist/es2019/entry-points/focus.js +1 -0
  82. package/dist/es2019/entry-points/placement-map.js +1 -0
  83. package/dist/es2019/entry-points/popover.js +1 -0
  84. package/dist/es2019/entry-points/popup-surface.js +1 -0
  85. package/dist/es2019/entry-points/popup.js +1 -0
  86. package/dist/es2019/entry-points/use-anchor-position.js +1 -0
  87. package/dist/es2019/entry-points/use-arrow-navigation.js +1 -0
  88. package/dist/es2019/entry-points/use-simple-light-dismiss.js +1 -0
  89. package/dist/es2019/focus/focus.js +107 -0
  90. package/dist/es2019/focus/index.js +1 -0
  91. package/dist/es2019/index.js +7 -0
  92. package/dist/es2019/internal/anchor-positioning-fallback.js +153 -0
  93. package/dist/es2019/internal/combine.js +18 -0
  94. package/dist/es2019/internal/ensure-preset-styles.js +29 -0
  95. package/dist/es2019/internal/reduced-motion.js +21 -0
  96. package/dist/es2019/internal/resolve-placement.js +19 -0
  97. package/dist/es2019/internal/role-types.js +61 -0
  98. package/dist/es2019/internal/set-style.js +39 -0
  99. package/dist/es2019/internal/slot.js +57 -0
  100. package/dist/es2019/internal/use-anchor-positioning.js +330 -0
  101. package/dist/es2019/internal/use-animated-visibility.js +159 -0
  102. package/dist/es2019/internal/use-animation-preset.js +18 -0
  103. package/dist/es2019/internal/use-focus-wrap.js +90 -0
  104. package/dist/es2019/internal/use-initial-focus.js +109 -0
  105. package/dist/es2019/internal/use-preset-styles.js +22 -0
  106. package/dist/es2019/placement-map/index.js +142 -0
  107. package/dist/es2019/popover/create-close-event.js +39 -0
  108. package/dist/es2019/popover/index.js +2 -0
  109. package/dist/es2019/popover/popover.compiled.css +13 -0
  110. package/dist/es2019/popover/popover.js +240 -0
  111. package/dist/es2019/popover/types.js +1 -0
  112. package/dist/es2019/popup/index.js +33 -0
  113. package/dist/es2019/popup/popup-content.js +192 -0
  114. package/dist/es2019/popup/popup-context.js +34 -0
  115. package/dist/es2019/popup/popup-surface.compiled.css +5 -0
  116. package/dist/es2019/popup/popup-surface.js +24 -0
  117. package/dist/es2019/popup/popup-trigger-function.js +69 -0
  118. package/dist/es2019/popup/popup-trigger.js +76 -0
  119. package/dist/es2019/popup/popup.js +56 -0
  120. package/dist/es2019/popup/types.js +1 -0
  121. package/dist/es2019/use-arrow-navigation/index.js +1 -0
  122. package/dist/es2019/use-arrow-navigation/use-arrow-navigation.js +238 -0
  123. package/dist/es2019/use-simple-light-dismiss/index.js +79 -0
  124. package/dist/esm/animations/presets.js +190 -0
  125. package/dist/esm/animations/types.js +1 -0
  126. package/dist/esm/arrow/index.js +123 -0
  127. package/dist/esm/arrow/types.js +1 -0
  128. package/dist/esm/dialog/create-close-event.js +33 -0
  129. package/dist/esm/dialog/dialog-content.compiled.css +14 -0
  130. package/dist/esm/dialog/dialog-content.js +135 -0
  131. package/dist/esm/dialog/index.js +2 -0
  132. package/dist/esm/dialog/types.js +1 -0
  133. package/dist/esm/dialog-scroll-lock/index.js +41 -0
  134. package/dist/esm/entry-points/animations.js +1 -0
  135. package/dist/esm/entry-points/arrow.js +1 -0
  136. package/dist/esm/entry-points/create-close-event.js +2 -0
  137. package/dist/esm/entry-points/dialog-scroll-lock.js +1 -0
  138. package/dist/esm/entry-points/dialog.js +1 -0
  139. package/dist/esm/entry-points/focus.js +1 -0
  140. package/dist/esm/entry-points/placement-map.js +1 -0
  141. package/dist/esm/entry-points/popover.js +1 -0
  142. package/dist/esm/entry-points/popup-surface.js +1 -0
  143. package/dist/esm/entry-points/popup.js +1 -0
  144. package/dist/esm/entry-points/use-anchor-position.js +1 -0
  145. package/dist/esm/entry-points/use-arrow-navigation.js +1 -0
  146. package/dist/esm/entry-points/use-simple-light-dismiss.js +1 -0
  147. package/dist/esm/focus/focus.js +107 -0
  148. package/dist/esm/focus/index.js +1 -0
  149. package/dist/esm/index.js +7 -0
  150. package/dist/esm/internal/anchor-positioning-fallback.js +147 -0
  151. package/dist/esm/internal/combine.js +23 -0
  152. package/dist/esm/internal/ensure-preset-styles.js +28 -0
  153. package/dist/esm/internal/reduced-motion.js +21 -0
  154. package/dist/esm/internal/resolve-placement.js +18 -0
  155. package/dist/esm/internal/role-types.js +60 -0
  156. package/dist/esm/internal/set-style.js +36 -0
  157. package/dist/esm/internal/slot.js +59 -0
  158. package/dist/esm/internal/use-anchor-positioning.js +323 -0
  159. package/dist/esm/internal/use-animated-visibility.js +162 -0
  160. package/dist/esm/internal/use-animation-preset.js +18 -0
  161. package/dist/esm/internal/use-focus-wrap.js +89 -0
  162. package/dist/esm/internal/use-initial-focus.js +107 -0
  163. package/dist/esm/internal/use-preset-styles.js +21 -0
  164. package/dist/esm/placement-map/index.js +141 -0
  165. package/dist/esm/popover/create-close-event.js +38 -0
  166. package/dist/esm/popover/index.js +2 -0
  167. package/dist/esm/popover/popover.compiled.css +13 -0
  168. package/dist/esm/popover/popover.js +242 -0
  169. package/dist/esm/popover/types.js +1 -0
  170. package/dist/esm/popup/index.js +33 -0
  171. package/dist/esm/popup/popup-content.js +192 -0
  172. package/dist/esm/popup/popup-context.js +34 -0
  173. package/dist/esm/popup/popup-surface.compiled.css +5 -0
  174. package/dist/esm/popup/popup-surface.js +23 -0
  175. package/dist/esm/popup/popup-trigger-function.js +69 -0
  176. package/dist/esm/popup/popup-trigger.js +74 -0
  177. package/dist/esm/popup/popup.js +61 -0
  178. package/dist/esm/popup/types.js +1 -0
  179. package/dist/esm/use-arrow-navigation/index.js +1 -0
  180. package/dist/esm/use-arrow-navigation/use-arrow-navigation.js +238 -0
  181. package/dist/esm/use-simple-light-dismiss/index.js +78 -0
  182. package/dist/types/animations/presets.d.ts +94 -0
  183. package/dist/types/animations/types.d.ts +42 -0
  184. package/dist/types/arrow/index.d.ts +24 -0
  185. package/dist/types/arrow/types.d.ts +29 -0
  186. package/dist/types/dialog/create-close-event.d.ts +24 -0
  187. package/dist/types/dialog/dialog-content.d.ts +20 -0
  188. package/dist/types/dialog/index.d.ts +3 -0
  189. package/dist/types/dialog/types.d.ts +92 -0
  190. package/dist/types/dialog-scroll-lock/index.d.ts +24 -0
  191. package/dist/types/entry-points/animations.d.ts +2 -0
  192. package/dist/types/entry-points/arrow.d.ts +2 -0
  193. package/dist/types/entry-points/create-close-event.d.ts +4 -0
  194. package/dist/types/entry-points/dialog-scroll-lock.d.ts +1 -0
  195. package/dist/types/entry-points/dialog.d.ts +2 -0
  196. package/dist/types/entry-points/focus.d.ts +2 -0
  197. package/dist/types/entry-points/placement-map.d.ts +2 -0
  198. package/dist/types/entry-points/popover.d.ts +2 -0
  199. package/dist/types/entry-points/popup-surface.d.ts +2 -0
  200. package/dist/types/entry-points/popup.d.ts +2 -0
  201. package/dist/types/entry-points/use-anchor-position.d.ts +1 -0
  202. package/dist/types/entry-points/use-arrow-navigation.d.ts +2 -0
  203. package/dist/types/entry-points/use-simple-light-dismiss.d.ts +2 -0
  204. package/dist/types/focus/focus.d.ts +42 -0
  205. package/dist/types/focus/index.d.ts +2 -0
  206. package/dist/types/index.d.ts +0 -0
  207. package/dist/types/internal/anchor-positioning-fallback.d.ts +24 -0
  208. package/dist/types/internal/combine.d.ts +14 -0
  209. package/dist/types/internal/ensure-preset-styles.d.ts +6 -0
  210. package/dist/types/internal/reduced-motion.d.ts +14 -0
  211. package/dist/types/internal/resolve-placement.d.ts +16 -0
  212. package/dist/types/internal/role-types.d.ts +62 -0
  213. package/dist/types/internal/set-style.d.ts +28 -0
  214. package/dist/types/internal/slot.d.ts +29 -0
  215. package/dist/types/internal/use-anchor-positioning.d.ts +95 -0
  216. package/dist/types/internal/use-animated-visibility.d.ts +74 -0
  217. package/dist/types/internal/use-animation-preset.d.ts +11 -0
  218. package/dist/types/internal/use-focus-wrap.d.ts +20 -0
  219. package/dist/types/internal/use-initial-focus.d.ts +21 -0
  220. package/dist/types/internal/use-preset-styles.d.ts +15 -0
  221. package/dist/types/placement-map/index.d.ts +55 -0
  222. package/dist/types/popover/create-close-event.d.ts +26 -0
  223. package/dist/types/popover/index.d.ts +3 -0
  224. package/dist/types/popover/popover.d.ts +22 -0
  225. package/dist/types/popover/types.d.ts +153 -0
  226. package/dist/types/popup/index.d.ts +34 -0
  227. package/dist/types/popup/popup-content.d.ts +14 -0
  228. package/dist/types/popup/popup-context.d.ts +96 -0
  229. package/dist/types/popup/popup-surface.d.ts +16 -0
  230. package/dist/types/popup/popup-trigger-function.d.ts +108 -0
  231. package/dist/types/popup/popup-trigger.d.ts +23 -0
  232. package/dist/types/popup/popup.d.ts +14 -0
  233. package/dist/types/popup/types.d.ts +289 -0
  234. package/dist/types/use-arrow-navigation/index.d.ts +2 -0
  235. package/dist/types/use-arrow-navigation/use-arrow-navigation.d.ts +136 -0
  236. package/dist/types/use-simple-light-dismiss/index.d.ts +45 -0
  237. package/dist/types-ts4.5/animations/presets.d.ts +94 -0
  238. package/dist/types-ts4.5/animations/types.d.ts +42 -0
  239. package/dist/types-ts4.5/arrow/index.d.ts +24 -0
  240. package/dist/types-ts4.5/arrow/types.d.ts +29 -0
  241. package/dist/types-ts4.5/dialog/create-close-event.d.ts +24 -0
  242. package/dist/types-ts4.5/dialog/dialog-content.d.ts +20 -0
  243. package/dist/types-ts4.5/dialog/index.d.ts +3 -0
  244. package/dist/types-ts4.5/dialog/types.d.ts +92 -0
  245. package/dist/types-ts4.5/dialog-scroll-lock/index.d.ts +24 -0
  246. package/dist/types-ts4.5/entry-points/animations.d.ts +2 -0
  247. package/dist/types-ts4.5/entry-points/arrow.d.ts +2 -0
  248. package/dist/types-ts4.5/entry-points/create-close-event.d.ts +4 -0
  249. package/dist/types-ts4.5/entry-points/dialog-scroll-lock.d.ts +1 -0
  250. package/dist/types-ts4.5/entry-points/dialog.d.ts +2 -0
  251. package/dist/types-ts4.5/entry-points/focus.d.ts +2 -0
  252. package/dist/types-ts4.5/entry-points/placement-map.d.ts +2 -0
  253. package/dist/types-ts4.5/entry-points/popover.d.ts +2 -0
  254. package/dist/types-ts4.5/entry-points/popup-surface.d.ts +2 -0
  255. package/dist/types-ts4.5/entry-points/popup.d.ts +2 -0
  256. package/dist/types-ts4.5/entry-points/use-anchor-position.d.ts +1 -0
  257. package/dist/types-ts4.5/entry-points/use-arrow-navigation.d.ts +2 -0
  258. package/dist/types-ts4.5/entry-points/use-simple-light-dismiss.d.ts +2 -0
  259. package/dist/types-ts4.5/focus/focus.d.ts +42 -0
  260. package/dist/types-ts4.5/focus/index.d.ts +2 -0
  261. package/dist/types-ts4.5/index.d.ts +0 -0
  262. package/dist/types-ts4.5/internal/anchor-positioning-fallback.d.ts +24 -0
  263. package/dist/types-ts4.5/internal/combine.d.ts +14 -0
  264. package/dist/types-ts4.5/internal/ensure-preset-styles.d.ts +6 -0
  265. package/dist/types-ts4.5/internal/reduced-motion.d.ts +14 -0
  266. package/dist/types-ts4.5/internal/resolve-placement.d.ts +16 -0
  267. package/dist/types-ts4.5/internal/role-types.d.ts +62 -0
  268. package/dist/types-ts4.5/internal/set-style.d.ts +28 -0
  269. package/dist/types-ts4.5/internal/slot.d.ts +29 -0
  270. package/dist/types-ts4.5/internal/use-anchor-positioning.d.ts +95 -0
  271. package/dist/types-ts4.5/internal/use-animated-visibility.d.ts +74 -0
  272. package/dist/types-ts4.5/internal/use-animation-preset.d.ts +11 -0
  273. package/dist/types-ts4.5/internal/use-focus-wrap.d.ts +20 -0
  274. package/dist/types-ts4.5/internal/use-initial-focus.d.ts +21 -0
  275. package/dist/types-ts4.5/internal/use-preset-styles.d.ts +15 -0
  276. package/dist/types-ts4.5/placement-map/index.d.ts +55 -0
  277. package/dist/types-ts4.5/popover/create-close-event.d.ts +26 -0
  278. package/dist/types-ts4.5/popover/index.d.ts +3 -0
  279. package/dist/types-ts4.5/popover/popover.d.ts +22 -0
  280. package/dist/types-ts4.5/popover/types.d.ts +153 -0
  281. package/dist/types-ts4.5/popup/index.d.ts +34 -0
  282. package/dist/types-ts4.5/popup/popup-content.d.ts +14 -0
  283. package/dist/types-ts4.5/popup/popup-context.d.ts +96 -0
  284. package/dist/types-ts4.5/popup/popup-surface.d.ts +16 -0
  285. package/dist/types-ts4.5/popup/popup-trigger-function.d.ts +108 -0
  286. package/dist/types-ts4.5/popup/popup-trigger.d.ts +23 -0
  287. package/dist/types-ts4.5/popup/popup.d.ts +14 -0
  288. package/dist/types-ts4.5/popup/types.d.ts +289 -0
  289. package/dist/types-ts4.5/use-arrow-navigation/index.d.ts +2 -0
  290. package/dist/types-ts4.5/use-arrow-navigation/use-arrow-navigation.d.ts +136 -0
  291. package/dist/types-ts4.5/use-simple-light-dismiss/index.d.ts +45 -0
  292. package/focus/package.json +17 -0
  293. package/package.json +95 -0
  294. package/placement-map/package.json +17 -0
  295. package/popover/package.json +17 -0
  296. package/popup/package.json +17 -0
  297. package/popup-surface/package.json +17 -0
  298. package/use-anchor-position/package.json +17 -0
  299. package/use-arrow-navigation/package.json +17 -0
  300. package/use-simple-light-dismiss/package.json +17 -0
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Sets inline styles on an element and returns a cleanup function
3
+ * that removes only the styles that were applied.
4
+ *
5
+ * Uses `style.setProperty()` / `style.removeProperty()` so that
6
+ * hyphenated CSS property names (e.g. `position-area`, `anchor-name`)
7
+ * work correctly. These newer properties do not have camelCase
8
+ * mappings in `CSSStyleDeclaration` yet.
9
+ *
10
+ * @example
11
+ * ```ts
12
+ * const cleanup = setStyle({ el: popover, styles: [
13
+ * { property: 'position-anchor', value: '--my-anchor' },
14
+ * { property: 'position-area', value: 'block-end' },
15
+ * { property: 'margin', value: '0' },
16
+ * ] });
17
+ *
18
+ * // Later: removes all three properties
19
+ * cleanup();
20
+ * ```
21
+ */
22
+ export function setStyle(_ref) {
23
+ var el = _ref.el,
24
+ styles = _ref.styles;
25
+ styles.forEach(function (_ref2) {
26
+ var property = _ref2.property,
27
+ value = _ref2.value;
28
+ el.style.setProperty(property, value);
29
+ });
30
+ return function cleanup() {
31
+ styles.forEach(function (_ref3) {
32
+ var property = _ref3.property;
33
+ el.style.removeProperty(property);
34
+ });
35
+ };
36
+ }
@@ -0,0 +1,59 @@
1
+ import _defineProperty from "@babel/runtime/helpers/defineProperty";
2
+ import _objectWithoutProperties from "@babel/runtime/helpers/objectWithoutProperties";
3
+ var _excluded = ["children"];
4
+ function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
5
+ function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
6
+ import { cloneElement, forwardRef, isValidElement } from 'react';
7
+ import mergeRefs from '@atlaskit/ds-lib/merge-refs';
8
+
9
+ /**
10
+ * Extra DOM/event props are passed through to the cloned child.
11
+ */
12
+
13
+ /**
14
+ * Single element child: merge the caller ref with `child.ref`.
15
+ *
16
+ * Do not read `child.props.ref`: React keeps a `ref` key on `props` for
17
+ * host elements but accessing it logs a warning and returns `undefined`
18
+ * (ref lives on the element as `child.ref`).
19
+ */
20
+
21
+ function isSlotChild(child) {
22
+ return /*#__PURE__*/isValidElement(child);
23
+ }
24
+
25
+ /**
26
+ * Merges props (including ref) onto a single React element child.
27
+ *
28
+ * A safer alternative to raw `cloneElement` that:
29
+ * 1. Guards with `isValidElement` — non-element children (strings,
30
+ * fragments, null) are returned as-is instead of crashing.
31
+ * 2. Merges refs via `mergeRefs` so both the caller's ref and the
32
+ * child's existing ref are honoured.
33
+ * 3. Spreads any additional props (ARIA attributes, event handlers, etc.)
34
+ * onto the child element.
35
+ *
36
+ * Event handlers (e.g. `onClick`) are **not** auto-merged — the caller's
37
+ * handler replaces the child's. Compose handlers before passing them in.
38
+ *
39
+ * @example
40
+ * ```tsx
41
+ * <Slot ref={triggerRef} onClick={handleClick} aria-expanded={isOpen}>
42
+ * <button>Open</button>
43
+ * </Slot>
44
+ * ```
45
+ */
46
+ export var Slot = /*#__PURE__*/forwardRef(function Slot(slotProps, ref) {
47
+ var rawChildren = slotProps.children,
48
+ props = _objectWithoutProperties(slotProps, _excluded);
49
+ // `SlotProps` intersects `Record<string, unknown>`, so `children` is typed as `unknown`.
50
+ var children = rawChildren;
51
+ if (!isSlotChild(children)) {
52
+ return children;
53
+ }
54
+
55
+ // eslint-disable-next-line @repo/internal/react/no-clone-element -- Slot is a controlled wrapper around cloneElement with isValidElement guard
56
+ return /*#__PURE__*/cloneElement(children, _objectSpread(_objectSpread({}, props), {}, {
57
+ ref: mergeRefs([ref, children.ref])
58
+ }));
59
+ });
@@ -0,0 +1,323 @@
1
+ import { useId, useLayoutEffect } from 'react';
2
+ import { bind } from 'bind-event-listener';
3
+ import rafSchedule from 'raf-schd';
4
+ import once from '@atlaskit/ds-lib/once';
5
+ import { computeFallbackPosition } from './anchor-positioning-fallback';
6
+ import { combine } from './combine';
7
+ import { getPlacement } from './resolve-placement';
8
+ import { setStyle } from './set-style';
9
+ export { getPlacement } from './resolve-placement';
10
+
11
+ /**
12
+ * Resolves placement options to CSS `position-area` syntax.
13
+ *
14
+ * The `align` field represents the *visual* alignment:
15
+ * - `align: 'start'` means "aligned to the trigger's start edge" (left in LTR for block axis)
16
+ * - `align: 'end'` means "aligned to the trigger's end edge"
17
+ * - `align: 'center'` means centered (single-axis position-area)
18
+ *
19
+ * CSS `position-area` uses `span-*` keywords which indicate the *expansion direction*.
20
+ * Visual start-alignment requires `span-{cross-axis}-end` (expand toward end = start-aligned).
21
+ * This function handles the inversion internally.
22
+ *
23
+ * @example
24
+ * placementToPositionArea({ placement: { axis: 'block', edge: 'end', align: 'center' } }) // → 'block-end'
25
+ * placementToPositionArea({ placement: { axis: 'block', edge: 'end', align: 'start' } }) // → 'block-end span-inline-end'
26
+ * placementToPositionArea({ placement: { axis: 'block', edge: 'end', align: 'end' } }) // → 'block-end span-inline-start'
27
+ */
28
+ export function placementToPositionArea(_ref) {
29
+ var placement = _ref.placement;
30
+ var _getPlacement = getPlacement({
31
+ placement: placement
32
+ }),
33
+ axis = _getPlacement.axis,
34
+ edge = _getPlacement.edge,
35
+ align = _getPlacement.align;
36
+ var edgeValue = "".concat(axis, "-").concat(edge);
37
+ if (align === 'center') {
38
+ return edgeValue;
39
+ }
40
+
41
+ // Cross-axis name: block ↔ inline
42
+ var crossAxis = axis === 'block' ? 'inline' : 'block';
43
+ // Invert align → CSS span direction: visual 'start' → span toward 'end'
44
+ var spanDir = align === 'start' ? 'end' : 'start';
45
+ return "".concat(edgeValue, " span-").concat(crossAxis, "-").concat(spanDir);
46
+ }
47
+
48
+ /**
49
+ * Returns `position-try-fallbacks` value based on placement.
50
+ *
51
+ * For centered placements, includes cross-axis aligned position-area
52
+ * values so the browser can shift the popover along the cross-axis
53
+ * when the centered position would overflow the viewport edge.
54
+ *
55
+ * Fallback order for centered placements (e.g. `block-end`):
56
+ * 1. Same edge, start-aligned (shift for near-edge overflow)
57
+ * 2. Same edge, end-aligned (shift for far-edge overflow)
58
+ * 3. Flipped edge, centered
59
+ * 4. Flipped edge, start-aligned
60
+ * 5. Flipped edge, end-aligned
61
+ *
62
+ * For aligned placements (start/end), includes the same-edge centered
63
+ * position as fallbacks so the browser can try centering when the aligned
64
+ * position would overflow, before flipping to the opposite edge.
65
+ */
66
+ export function placementToTryFallbacks(_ref2) {
67
+ var placement = _ref2.placement;
68
+ var _getPlacement2 = getPlacement({
69
+ placement: placement
70
+ }),
71
+ axis = _getPlacement2.axis,
72
+ edge = _getPlacement2.edge,
73
+ align = _getPlacement2.align;
74
+ var crossAxis = axis === 'block' ? 'inline' : 'block';
75
+ var flippedEdge = edge === 'start' ? 'end' : 'start';
76
+ if (align !== 'center') {
77
+ var _sameEdge = "".concat(axis, "-").concat(edge);
78
+ var _oppositeEdge = "".concat(axis, "-").concat(flippedEdge);
79
+ var _flipKeyword = axis === 'block' ? 'flip-block' : 'flip-inline';
80
+ // Legacy Popper fallbacks for e.g. bottom-start: [bottom, bottom-end, top-start, top, top-end, auto].
81
+ // Include same-edge centered so the browser can try center when start/end would overflow.
82
+ return ["".concat(_sameEdge, " span-").concat(crossAxis, "-").concat(align === 'start' ? 'end' : 'start'), _sameEdge, "".concat(_sameEdge, " span-").concat(crossAxis, "-").concat(align === 'start' ? 'start' : 'end'), _flipKeyword, "".concat(_oppositeEdge, " span-").concat(crossAxis, "-").concat(align === 'start' ? 'end' : 'start'), _oppositeEdge, "".concat(_oppositeEdge, " span-").concat(crossAxis, "-").concat(align === 'start' ? 'start' : 'end')].join(', ');
83
+ }
84
+ var sameEdge = "".concat(axis, "-").concat(edge);
85
+ var oppositeEdge = "".concat(axis, "-").concat(flippedEdge);
86
+ var flipKeyword = axis === 'block' ? 'flip-block' : 'flip-inline';
87
+ return ["".concat(sameEdge, " span-").concat(crossAxis, "-end"), "".concat(sameEdge, " span-").concat(crossAxis, "-start"), flipKeyword, "".concat(oppositeEdge, " span-").concat(crossAxis, "-end"), "".concat(oppositeEdge, " span-").concat(crossAxis, "-start")].join(', ');
88
+ }
89
+
90
+ /**
91
+ * Detects whether the browser supports CSS Anchor Positioning.
92
+ * Uses `once()` for lazy evaluation — safe for SSR and avoids hydration mismatches.
93
+ */
94
+ var supportsAnchorPositioning = once(function () {
95
+ if (typeof window === 'undefined' || typeof CSS === 'undefined' || typeof CSS.supports !== 'function') {
96
+ return false;
97
+ }
98
+ return CSS.supports('anchor-name', '--a');
99
+ });
100
+
101
+ /**
102
+ * Default gap between the popover and its trigger (in pixels).
103
+ * Maps to `space.100` (8px) in the design token scale.
104
+ */
105
+ var DEFAULT_OFFSET = 8;
106
+
107
+ /**
108
+ * Returns the CSS margin declaration that creates a gap between the
109
+ * popover and its trigger on the side facing the anchor.
110
+ *
111
+ * For example, a `block-end` placement (popover below) gets
112
+ * `margin-block-start: 8px` to push it away from the trigger's bottom edge.
113
+ */
114
+ function edgeMargin(_ref3) {
115
+ var placement = _ref3.placement,
116
+ offset = _ref3.offset;
117
+ var axis = placement.axis,
118
+ edge = placement.edge;
119
+ var value = "".concat(offset, "px");
120
+ if (axis === 'block' && edge === 'end') {
121
+ return {
122
+ property: 'margin-block-start',
123
+ value: value
124
+ };
125
+ }
126
+ if (axis === 'block' && edge === 'start') {
127
+ return {
128
+ property: 'margin-block-end',
129
+ value: value
130
+ };
131
+ }
132
+ if (axis === 'inline' && edge === 'end') {
133
+ return {
134
+ property: 'margin-inline-start',
135
+ value: value
136
+ };
137
+ }
138
+ if (axis === 'inline' && edge === 'start') {
139
+ return {
140
+ property: 'margin-inline-end',
141
+ value: value
142
+ };
143
+ }
144
+ return {
145
+ property: 'margin-block-start',
146
+ value: value
147
+ };
148
+ }
149
+
150
+ /**
151
+ * Hook that positions an element relative to an anchor element using
152
+ * CSS Anchor Positioning (with a JS fallback for older browsers).
153
+ *
154
+ * This hook is the positioning primitive — it has no knowledge of popovers,
155
+ * visibility, or animation. Compose it with `Popover` for anchor-positioned
156
+ * top-layer content.
157
+ *
158
+ * When CSS Anchor Positioning is supported, it sets CSS properties
159
+ * (`anchor-name`, `position-anchor`, `position-area`, `position-try-fallbacks`)
160
+ * directly on the elements via `el.style.setProperty()`.
161
+ *
162
+ * When not supported, it falls back to JavaScript-based positioning using
163
+ * `position: fixed` with measured coordinates, re-running on scroll (capture)
164
+ * and resize events.
165
+ */
166
+ export function useAnchorPosition(_ref4) {
167
+ var anchorRef = _ref4.anchorRef,
168
+ popoverRef = _ref4.popoverRef,
169
+ placement = _ref4.placement,
170
+ _ref4$offset = _ref4.offset,
171
+ offset = _ref4$offset === void 0 ? DEFAULT_OFFSET : _ref4$offset,
172
+ _ref4$forceFallbackPo = _ref4.forceFallbackPositioning,
173
+ forceFallbackPositioning = _ref4$forceFallbackPo === void 0 ? false : _ref4$forceFallbackPo,
174
+ arrow = _ref4.arrow;
175
+ var id = useId();
176
+ var anchorName = "--anchor-".concat(id.replace(/:/g, ''));
177
+ useLayoutEffect(function () {
178
+ var trigger = anchorRef.current;
179
+ var popover = popoverRef.current;
180
+ if (!trigger || !popover) {
181
+ return;
182
+ }
183
+ var resolvedPlacement = getPlacement({
184
+ placement: placement
185
+ });
186
+ var shouldForceFallback = Boolean(forceFallbackPositioning);
187
+ if (supportsAnchorPositioning() && !shouldForceFallback) {
188
+ var gap = edgeMargin({
189
+ placement: resolvedPlacement,
190
+ offset: offset
191
+ });
192
+ var popoverStyles = [{
193
+ property: 'position-anchor',
194
+ value: anchorName
195
+ }, {
196
+ property: 'position-area',
197
+ value: placementToPositionArea({
198
+ placement: resolvedPlacement
199
+ })
200
+ }, {
201
+ property: 'position-try-fallbacks',
202
+ value: arrow ? arrow.getTryFallbacks({
203
+ placement: resolvedPlacement
204
+ }) : placementToTryFallbacks({
205
+ placement: resolvedPlacement
206
+ })
207
+ },
208
+ // Reset browser default popover positioning that conflicts
209
+ // with anchor positioning (UA: `inset: 0; margin: auto;`)
210
+ {
211
+ property: 'margin',
212
+ value: '0'
213
+ }, {
214
+ property: 'inset',
215
+ value: 'auto'
216
+ },
217
+ // Gap between the popover and its trigger
218
+ {
219
+ property: gap.property,
220
+ value: gap.value
221
+ }];
222
+ if (arrow) {
223
+ // Set the arrow size variable used by the injected arrow CSS
224
+ // and the @position-try rules for margin values.
225
+ popoverStyles.push({
226
+ property: '--ds-arrow-size',
227
+ value: "".concat(offset, "px")
228
+ });
229
+ }
230
+ var _undoPositioning = combine(setStyle({
231
+ el: trigger,
232
+ styles: [{
233
+ property: 'anchor-name',
234
+ value: anchorName
235
+ }]
236
+ }), setStyle({
237
+ el: popover,
238
+ styles: popoverStyles
239
+ }));
240
+ return _undoPositioning;
241
+ }
242
+
243
+ // ── JS fallback ──
244
+ // The popover is already in the browser's top layer via popover="auto",
245
+ // so it does not need position:fixed. We only reset the UA defaults
246
+ // (inset: 0; margin: auto) and set top/left based on measurements.
247
+ var cleanupBaseStyles = setStyle({
248
+ el: popover,
249
+ styles: [{
250
+ property: 'margin',
251
+ value: '0'
252
+ }, {
253
+ property: 'inset',
254
+ value: 'auto'
255
+ }]
256
+ });
257
+ function update() {
258
+ if (!trigger || !popover) {
259
+ return;
260
+ }
261
+ var triggerRect = trigger.getBoundingClientRect();
262
+ var viewport = {
263
+ width: window.innerWidth,
264
+ height: window.innerHeight
265
+ };
266
+ var _computeFallbackPosit = computeFallbackPosition({
267
+ triggerRect: triggerRect,
268
+ popoverEl: popover,
269
+ placement: resolvedPlacement,
270
+ viewport: viewport,
271
+ offset: offset
272
+ }),
273
+ top = _computeFallbackPosit.top,
274
+ left = _computeFallbackPosit.left;
275
+ popover.style.setProperty('top', "".concat(top, "px"));
276
+ popover.style.setProperty('left', "".concat(left, "px"));
277
+ }
278
+
279
+ // Throttle scroll/resize updates to one per animation frame
280
+ var scheduledUpdate = rafSchedule(update);
281
+ var pendingFrame = null;
282
+ var undoPositioning = combine(cleanupBaseStyles,
283
+ // Position when the popover transitions to visible.
284
+ // The popover starts hidden (`display: none` per the popover
285
+ // UA stylesheet), so `offsetWidth`/`offsetHeight` are 0 until
286
+ // `showPopover()` is called. We defer positioning to the next
287
+ // animation frame because some browsers (Firefox) fire the
288
+ // `toggle` event synchronously during `showPopover()`, before
289
+ // the element has been laid out — so dimensions are still 0.
290
+ // Waiting one frame guarantees layout has run and measurements
291
+ // are accurate.
292
+ bind(popover, {
293
+ type: 'toggle',
294
+ listener: function listener(event) {
295
+ var toggleEvent = event;
296
+ if (toggleEvent.newState === 'open') {
297
+ pendingFrame = requestAnimationFrame(update);
298
+ }
299
+ }
300
+ }), bind(window, {
301
+ type: 'scroll',
302
+ listener: scheduledUpdate,
303
+ options: {
304
+ capture: true,
305
+ passive: true
306
+ }
307
+ }), bind(window, {
308
+ type: 'resize',
309
+ listener: scheduledUpdate,
310
+ options: {
311
+ passive: true
312
+ }
313
+ }), function () {
314
+ scheduledUpdate.cancel();
315
+ if (pendingFrame !== null) {
316
+ cancelAnimationFrame(pendingFrame);
317
+ }
318
+ popover.style.removeProperty('top');
319
+ popover.style.removeProperty('left');
320
+ });
321
+ return undoPositioning;
322
+ }, [anchorRef, popoverRef, placement, anchorName, offset, forceFallbackPositioning, arrow]);
323
+ }
@@ -0,0 +1,162 @@
1
+ import _slicedToArray from "@babel/runtime/helpers/slicedToArray";
2
+ import { useEffect, useRef, useState } from 'react';
3
+ import { bind } from 'bind-event-listener';
4
+ import { prefersReducedMotion } from './reduced-motion';
5
+ import { usePresetStyles } from './use-preset-styles';
6
+ /**
7
+ * Manages the children mount/unmount lifecycle around CSS exit transitions.
8
+ *
9
+ * Used by both `Popover` and `Dialog` to share the same animation lifecycle
10
+ * logic. Those components own their own show/hide mechanisms (showPopover /
11
+ * hidePopover vs showModal / close) and event handling — this hook only
12
+ * manages the relationship between `isOpen` and when children are rendered.
13
+ *
14
+ * ## Problem
15
+ *
16
+ * We want to delay the unmount of children until a CSS exit transition has
17
+ * finished playing. If we unmount children the moment `isOpen` becomes
18
+ * `false`, the exit animation is never visible — the content just disappears.
19
+ *
20
+ * ## How it works
21
+ *
22
+ * `showChildren` tracks whether the consumer's children should be in the DOM.
23
+ * It does not mirror `isOpen` exactly — it intentionally lags behind during
24
+ * exit animations:
25
+ *
26
+ * ```
27
+ * isOpen: true ──────────────── false
28
+ * showChildren: true ──────────────── true ─── (exit animation) ─── false
29
+ * ```
30
+ *
31
+ * Two close paths exist:
32
+ *
33
+ * 1. **Animated close** (`willAnimate === true`):
34
+ * `isOpen` goes `false` → `showChildren` stays `true` → the CSS exit
35
+ * transition plays → `transitionend` fires → `showChildren` → `false`
36
+ * → `onExitFinish` fires. A timeout fallback ensures we still unmount
37
+ * if `transitionend` never fires (e.g. browser quirks, zero-duration).
38
+ *
39
+ * 2. **Non-animated close** (`willAnimate === false`):
40
+ * `isOpen` goes `false` → `showChildren` → `false` synchronously during
41
+ * the same render → `onExitFinish` fires in a follow-up effect.
42
+ */
43
+ export function useAnimatedVisibility(_ref) {
44
+ var isOpen = _ref.isOpen,
45
+ animate = _ref.animate,
46
+ elementRef = _ref.elementRef,
47
+ onExitFinish = _ref.onExitFinish;
48
+ // Resolve the animation preset and inject its CSS (idempotent).
49
+ // Returns `null` when `animate` is falsy.
50
+ var preset = usePresetStyles({
51
+ preset: animate
52
+ });
53
+
54
+ // Whether we should play CSS transitions. False when there is no preset
55
+ // or the user has enabled the "prefers reduced motion" OS setting.
56
+ var willAnimate = Boolean(animate) && !prefersReducedMotion();
57
+
58
+ // ── showChildren state ──
59
+ //
60
+ // This is the core of the hook. `showChildren` controls whether the
61
+ // consumer's children are in the DOM. The two `if` blocks below are
62
+ // intentionally written as synchronous state updates during render
63
+ // (React supports this pattern — setState during render is treated as
64
+ // a synchronous re-render before the browser paints).
65
+ //
66
+ // Mount path: isOpen went true but showChildren is still false → mount.
67
+ // Unmount path: isOpen went false, showChildren is still true, and we
68
+ // are NOT animating → unmount immediately (no transition
69
+ // to wait for). If we ARE animating, showChildren stays
70
+ // true here and the effect below will set it to false
71
+ // after `transitionend`.
72
+ var _useState = useState(isOpen),
73
+ _useState2 = _slicedToArray(_useState, 2),
74
+ showChildren = _useState2[0],
75
+ setShowChildren = _useState2[1];
76
+ if (isOpen && !showChildren) {
77
+ setShowChildren(true);
78
+ }
79
+ if (!isOpen && showChildren && !willAnimate) {
80
+ setShowChildren(false);
81
+ }
82
+
83
+ // Keep a stable ref to the latest `onExitFinish` so the effect and
84
+ // timeout closures below always call the current callback without
85
+ // needing `onExitFinish` in dependency arrays (which would reset
86
+ // the timeout on every new closure).
87
+ var onExitFinishRef = useRef(onExitFinish);
88
+ onExitFinishRef.current = onExitFinish;
89
+
90
+ // ── Animated exit effect ──
91
+ //
92
+ // Runs when all three conditions are true:
93
+ // • `isOpen` is false (consumer wants to close)
94
+ // • `showChildren` is true (children are still mounted for exit anim)
95
+ // • `willAnimate` is true (we have a preset and reduced motion is off)
96
+ //
97
+ // Listens for `transitionend` on the element to know when the CSS exit
98
+ // transition has finished, then unmounts children and fires onExitFinish.
99
+ //
100
+ // Fallback timeout: `transitionend` can fail to fire in edge cases
101
+ // (e.g. element removed from top layer by browser, zero-duration
102
+ // transition, interrupted animation). The timeout is set to the
103
+ // preset's exit duration + 50ms buffer to guarantee we always unmount.
104
+ useEffect(function () {
105
+ var _preset$exitDurationM;
106
+ // Guard: only run during the "exiting" window
107
+ if (isOpen || !showChildren || !willAnimate) {
108
+ return;
109
+ }
110
+ var el = elementRef.current;
111
+ if (!el) {
112
+ return;
113
+ }
114
+ function finish() {
115
+ var _onExitFinishRef$curr;
116
+ clearTimeout(fallbackId);
117
+ setShowChildren(false);
118
+ (_onExitFinishRef$curr = onExitFinishRef.current) === null || _onExitFinishRef$curr === void 0 || _onExitFinishRef$curr.call(onExitFinishRef);
119
+ }
120
+
121
+ // Fallback: unmount even if transitionend never fires
122
+ var fallbackId = setTimeout(finish, ((_preset$exitDurationM = preset === null || preset === void 0 ? void 0 : preset.exitDurationMs) !== null && _preset$exitDurationM !== void 0 ? _preset$exitDurationM : 0) + 50);
123
+
124
+ // Primary: listen for the CSS transition to complete
125
+ var unbind = bind(el, {
126
+ type: 'transitionend',
127
+ listener: finish,
128
+ options: {
129
+ once: true
130
+ }
131
+ });
132
+
133
+ // Cleanup on re-render or unmount: cancel pending timeout and listener
134
+ return function () {
135
+ clearTimeout(fallbackId);
136
+ unbind();
137
+ };
138
+ }, [isOpen, showChildren, willAnimate, preset === null || preset === void 0 ? void 0 : preset.exitDurationMs, elementRef]);
139
+
140
+ // ── Non-animated exit callback ──
141
+ //
142
+ // When there is no animation, children unmount synchronously (handled
143
+ // by the `if` block above). But `onExitFinish` still needs to fire —
144
+ // we do that in a follow-up effect so the consumer can react to it.
145
+ //
146
+ // Uses a previous-value ref (`wasOpenRef`) to detect the exact
147
+ // true→false transition of `isOpen`, avoiding false positives on
148
+ // mount or re-renders where `isOpen` was already false.
149
+ var wasOpenRef = useRef(isOpen);
150
+ useEffect(function () {
151
+ var wasOpen = wasOpenRef.current;
152
+ wasOpenRef.current = isOpen;
153
+ if (wasOpen && !isOpen && !willAnimate) {
154
+ var _onExitFinishRef$curr2;
155
+ (_onExitFinishRef$curr2 = onExitFinishRef.current) === null || _onExitFinishRef$curr2 === void 0 || _onExitFinishRef$curr2.call(onExitFinishRef);
156
+ }
157
+ }, [isOpen, willAnimate]);
158
+ return {
159
+ showChildren: showChildren,
160
+ preset: preset
161
+ };
162
+ }
@@ -0,0 +1,18 @@
1
+ import { useInsertionEffect } from 'react';
2
+ import { ensurePresetStyles } from './ensure-preset-styles';
3
+ /**
4
+ * Normalizes the `animate` prop to a preset (or null) and ensures its CSS
5
+ * is injected once into document.head. Used by PopupContent, Popover, and
6
+ * DialogContent so they don't duplicate this logic.
7
+ */
8
+ export function useAnimationPreset(animate) {
9
+ var preset = animate != null && animate !== false ? animate : null;
10
+ useInsertionEffect(function () {
11
+ if (preset) {
12
+ ensurePresetStyles({
13
+ preset: preset
14
+ });
15
+ }
16
+ }, [preset]);
17
+ return preset;
18
+ }
@@ -0,0 +1,89 @@
1
+ import { useEffect } from 'react';
2
+ import { bind } from 'bind-event-listener';
3
+ import { getFirstFocusable, getLastFocusable, getNextFocusable } from '../focus/focus';
4
+
5
+ /**
6
+ * Roles that require focus wrapping per WAI-ARIA APG.
7
+ *
8
+ * - `dialog`: https://www.w3.org/WAI/ARIA/apg/patterns/dialog-modal/
9
+ * - `alertdialog`: same keyboard pattern as dialog
10
+ *
11
+ * `menu` is intentionally excluded. Menus are composite widgets
12
+ * (https://www.w3.org/WAI/ARIA/apg/practices/keyboard-interface/#kbd_general_within)
13
+ * that manage their own internal keyboard navigation via arrow keys,
14
+ * not Tab. Tab/Shift+Tab should move focus OUT of a menu entirely.
15
+ *
16
+ * Menu keyboard behavior (arrow keys, Home/End, type-ahead, Enter/Space)
17
+ * is context-dependent — it varies by orientation, nesting depth, item
18
+ * type, and control pattern (menu button vs menubar). This makes it
19
+ * impossible to implement generically here. The consumer component
20
+ * (e.g. `dropdown-menu`) owns all menu keyboard navigation.
21
+ *
22
+ * See: notes/outputs/menu-keyboard-decision.md
23
+ */
24
+ function roleRequiresFocusWrap(role) {
25
+ return role === 'dialog' || role === 'alertdialog';
26
+ }
27
+
28
+ /**
29
+ * Wraps Tab/Shift+Tab focus within a popover element when its role
30
+ * semantically requires it (dialog, alertdialog).
31
+ *
32
+ * While the popover is open, Tab and Shift+Tab are intercepted
33
+ * (via `preventDefault`) and remapped to cycle through focusable
34
+ * elements within the container using wrapping navigation.
35
+ *
36
+ * Light dismiss (Escape, click outside) continues to work natively
37
+ * via `popover="auto"` — this hook only intercepts Tab.
38
+ *
39
+ * Always call this hook unconditionally. The listener is only
40
+ * attached when the role requires focus wrapping, and is cleaned
41
+ * up and re-evaluated when the role changes.
42
+ */
43
+ export function useFocusWrap(_ref) {
44
+ var elementRef = _ref.elementRef,
45
+ role = _ref.role;
46
+ useEffect(function () {
47
+ var el = elementRef.current;
48
+ if (!el || !roleRequiresFocusWrap(role)) {
49
+ return;
50
+ }
51
+ var unbind = bind(el, {
52
+ type: 'keydown',
53
+ listener: function listener(event) {
54
+ if (event.key !== 'Tab') {
55
+ return;
56
+ }
57
+ event.preventDefault();
58
+ var direction = event.shiftKey ? 'backwards' : 'forwards';
59
+
60
+ // Try to move to the next/previous focusable element relative
61
+ // to the currently focused element within the container.
62
+ var next = getNextFocusable({
63
+ container: el,
64
+ direction: direction
65
+ });
66
+ if (next) {
67
+ next.focus();
68
+ return;
69
+ }
70
+
71
+ // Nothing is currently focused inside the container (or the
72
+ // focused element isn't in the focusable list). Fall back to
73
+ // the first or last focusable element depending on direction.
74
+ var fallback = direction === 'forwards' ? getFirstFocusable({
75
+ container: el
76
+ }) : getLastFocusable({
77
+ container: el
78
+ });
79
+ if (fallback) {
80
+ fallback.focus();
81
+ }
82
+ },
83
+ options: {
84
+ capture: true
85
+ }
86
+ });
87
+ return unbind;
88
+ }, [elementRef, role]);
89
+ }