@atlaskit/top-layer 0.8.0 → 0.10.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 (130) hide show
  1. package/CHANGELOG.md +41 -0
  2. package/README.md +5 -1
  3. package/dialog/create-close-event/package.json +17 -0
  4. package/dist/cjs/animations/presets.js +11 -4
  5. package/dist/cjs/arrow/index.js +131 -0
  6. package/dist/cjs/arrow/types.js +5 -0
  7. package/dist/cjs/dialog/dialog-content.js +23 -6
  8. package/dist/cjs/entry-points/dialog/create-close-event.js +12 -0
  9. package/dist/cjs/entry-points/placement-map.js +6 -5
  10. package/dist/cjs/entry-points/popover/create-close-event.js +12 -0
  11. package/dist/cjs/focus/focus.js +73 -19
  12. package/dist/cjs/index.js +3 -4
  13. package/dist/cjs/internal/resolve-css-length-to-pixels.js +61 -10
  14. package/dist/cjs/internal/role-types.js +40 -9
  15. package/dist/cjs/internal/set-style.js +23 -25
  16. package/dist/cjs/internal/use-anchor-position.js +109 -68
  17. package/dist/cjs/internal/use-focus-wrap.js +13 -2
  18. package/dist/cjs/internal/use-initial-focus.js +31 -10
  19. package/dist/cjs/placement-map/index.js +9 -110
  20. package/dist/cjs/placement-map/legacy-placements.js +113 -0
  21. package/dist/cjs/popover/popover.js +44 -11
  22. package/dist/cjs/popup/popup-content.js +19 -13
  23. package/dist/cjs/popup/popup-context.js +2 -0
  24. package/dist/cjs/popup/popup-surface.js +6 -0
  25. package/dist/cjs/popup/popup-trigger-function.js +1 -1
  26. package/dist/cjs/popup/popup-trigger.js +17 -37
  27. package/dist/cjs/popup/popup.js +21 -10
  28. package/dist/cjs/use-arrow-navigation/use-arrow-navigation.js +27 -10
  29. package/dist/es2019/animations/presets.js +10 -3
  30. package/dist/es2019/arrow/index.js +269 -0
  31. package/dist/es2019/arrow/types.js +1 -0
  32. package/dist/es2019/dialog/dialog-content.js +23 -6
  33. package/dist/es2019/entry-points/create-close-event.js +7 -0
  34. package/dist/es2019/entry-points/dialog/create-close-event.js +1 -0
  35. package/dist/es2019/entry-points/placement-map.js +2 -1
  36. package/dist/es2019/entry-points/popover/create-close-event.js +1 -0
  37. package/dist/es2019/focus/focus.js +58 -7
  38. package/dist/es2019/index.js +3 -4
  39. package/dist/es2019/internal/resolve-css-length-to-pixels.js +60 -7
  40. package/dist/es2019/internal/role-types.js +40 -9
  41. package/dist/es2019/internal/set-style.js +19 -22
  42. package/dist/es2019/internal/use-anchor-position.js +97 -54
  43. package/dist/es2019/internal/use-focus-wrap.js +14 -3
  44. package/dist/es2019/internal/use-initial-focus.js +32 -11
  45. package/dist/es2019/placement-map/index.js +8 -108
  46. package/dist/es2019/placement-map/legacy-placements.js +107 -0
  47. package/dist/es2019/popover/popover.js +44 -10
  48. package/dist/es2019/popup/popup-content.js +20 -14
  49. package/dist/es2019/popup/popup-context.js +2 -0
  50. package/dist/es2019/popup/popup-surface.js +6 -0
  51. package/dist/es2019/popup/popup-trigger-function.js +1 -1
  52. package/dist/es2019/popup/popup-trigger.js +17 -37
  53. package/dist/es2019/popup/popup.js +20 -9
  54. package/dist/es2019/use-arrow-navigation/use-arrow-navigation.js +27 -10
  55. package/dist/esm/animations/presets.js +10 -3
  56. package/dist/esm/arrow/index.js +124 -0
  57. package/dist/esm/arrow/types.js +1 -0
  58. package/dist/esm/dialog/dialog-content.js +23 -6
  59. package/dist/esm/entry-points/create-close-event.js +7 -0
  60. package/dist/esm/entry-points/dialog/create-close-event.js +1 -0
  61. package/dist/esm/entry-points/placement-map.js +2 -1
  62. package/dist/esm/entry-points/popover/create-close-event.js +1 -0
  63. package/dist/esm/focus/focus.js +72 -19
  64. package/dist/esm/index.js +3 -4
  65. package/dist/esm/internal/resolve-css-length-to-pixels.js +61 -10
  66. package/dist/esm/internal/role-types.js +39 -9
  67. package/dist/esm/internal/set-style.js +23 -25
  68. package/dist/esm/internal/use-anchor-position.js +110 -64
  69. package/dist/esm/internal/use-focus-wrap.js +14 -3
  70. package/dist/esm/internal/use-initial-focus.js +32 -11
  71. package/dist/esm/placement-map/index.js +8 -108
  72. package/dist/esm/placement-map/legacy-placements.js +107 -0
  73. package/dist/esm/popover/popover.js +44 -11
  74. package/dist/esm/popup/popup-content.js +20 -14
  75. package/dist/esm/popup/popup-context.js +2 -0
  76. package/dist/esm/popup/popup-surface.js +6 -0
  77. package/dist/esm/popup/popup-trigger-function.js +1 -1
  78. package/dist/esm/popup/popup-trigger.js +17 -37
  79. package/dist/esm/popup/popup.js +21 -10
  80. package/dist/esm/use-arrow-navigation/use-arrow-navigation.js +27 -10
  81. package/dist/types/arrow/index.d.ts +24 -0
  82. package/dist/types/arrow/types.d.ts +29 -0
  83. package/dist/types/dialog/dialog-content.d.ts +5 -1
  84. package/dist/types/dialog/types.d.ts +50 -24
  85. package/dist/types/entry-points/create-close-event.d.ts +7 -0
  86. package/dist/types/entry-points/dialog/create-close-event.d.ts +2 -0
  87. package/dist/types/entry-points/placement-map.d.ts +2 -1
  88. package/dist/types/entry-points/popover/create-close-event.d.ts +2 -0
  89. package/dist/types/focus/focus.d.ts +8 -0
  90. package/dist/types/internal/resolve-css-length-to-pixels.d.ts +4 -6
  91. package/dist/types/internal/role-types.d.ts +25 -4
  92. package/dist/types/internal/set-style.d.ts +2 -19
  93. package/dist/types/internal/use-anchor-position.d.ts +2 -3
  94. package/dist/types/internal/use-initial-focus.d.ts +5 -4
  95. package/dist/types/placement-map/index.d.ts +8 -21
  96. package/dist/types/placement-map/legacy-placements.d.ts +21 -0
  97. package/dist/types/popover/popover.d.ts +1 -1
  98. package/dist/types/popover/types.d.ts +20 -19
  99. package/dist/types/popup/popup-surface.d.ts +6 -0
  100. package/dist/types/popup/popup-trigger-function.d.ts +10 -6
  101. package/dist/types/popup/popup-trigger.d.ts +8 -17
  102. package/dist/types/popup/popup.d.ts +2 -2
  103. package/dist/types/popup/types.d.ts +68 -22
  104. package/dist/types/use-arrow-navigation/use-arrow-navigation.d.ts +19 -5
  105. package/dist/types-ts4.5/arrow/index.d.ts +24 -0
  106. package/dist/types-ts4.5/arrow/types.d.ts +29 -0
  107. package/dist/types-ts4.5/dialog/dialog-content.d.ts +5 -1
  108. package/dist/types-ts4.5/dialog/types.d.ts +50 -24
  109. package/dist/types-ts4.5/entry-points/create-close-event.d.ts +7 -0
  110. package/dist/types-ts4.5/entry-points/dialog/create-close-event.d.ts +2 -0
  111. package/dist/types-ts4.5/entry-points/placement-map.d.ts +2 -1
  112. package/dist/types-ts4.5/entry-points/popover/create-close-event.d.ts +2 -0
  113. package/dist/types-ts4.5/focus/focus.d.ts +8 -0
  114. package/dist/types-ts4.5/internal/resolve-css-length-to-pixels.d.ts +4 -6
  115. package/dist/types-ts4.5/internal/role-types.d.ts +25 -4
  116. package/dist/types-ts4.5/internal/set-style.d.ts +2 -19
  117. package/dist/types-ts4.5/internal/use-anchor-position.d.ts +2 -3
  118. package/dist/types-ts4.5/internal/use-initial-focus.d.ts +5 -4
  119. package/dist/types-ts4.5/placement-map/index.d.ts +8 -21
  120. package/dist/types-ts4.5/placement-map/legacy-placements.d.ts +39 -0
  121. package/dist/types-ts4.5/popover/popover.d.ts +1 -1
  122. package/dist/types-ts4.5/popover/types.d.ts +20 -19
  123. package/dist/types-ts4.5/popup/popup-surface.d.ts +6 -0
  124. package/dist/types-ts4.5/popup/popup-trigger-function.d.ts +10 -6
  125. package/dist/types-ts4.5/popup/popup-trigger.d.ts +8 -17
  126. package/dist/types-ts4.5/popup/popup.d.ts +2 -2
  127. package/dist/types-ts4.5/popup/types.d.ts +68 -22
  128. package/dist/types-ts4.5/use-arrow-navigation/use-arrow-navigation.d.ts +19 -5
  129. package/package.json +3 -1
  130. package/popover/create-close-event/package.json +17 -0
@@ -10,24 +10,15 @@ var _slot = require("../internal/slot");
10
10
  var _popupContext = require("./popup-context");
11
11
  function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function _interopRequireWildcard(e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != _typeof(e) && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (var _t in e) "default" !== _t && {}.hasOwnProperty.call(e, _t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, _t)) && (i.get || i.set) ? o(f, _t, i) : f[_t] = e[_t]); return f; })(e, t); }
12
12
  /**
13
- * Wraps the trigger element. Attaches aria attributes and handles
14
- * click to toggle the popover. Sets the anchor-name on the trigger
15
- * for CSS Anchor Positioning.
13
+ * Wraps a single trigger element via `cloneElement`. Attaches aria
14
+ * attributes, the toggle handler, and the anchor-name. `aria-haspopup`
15
+ * is derived from `Popup.Content`'s role via context.
16
16
  *
17
- * `aria-haspopup` is derived from the content's role via context
18
- * (set by `Popup.Content`). This ensures the trigger always matches
19
- * the content's semantic role without the consumer specifying it
20
- * in two places.
21
- *
22
- * **Limitation:** Uses `cloneElement` internally (via `Slot`), which is
23
- * incompatible with Compiled's `css` prop on the direct child element.
24
- * The `@jsx jsx` pragma transforms the child before `cloneElement` can
25
- * inject `onClick` and `ref` onto the DOM node, preventing the popover
26
- * from opening.
27
- *
28
- * If you need Compiled `css` on the trigger, use `Popup.TriggerFunction`
29
- * instead - it hands you raw props via a render-prop and avoids
30
- * `cloneElement` entirely.
17
+ * Child constraints: must be a single ref-forwarding element. The child's
18
+ * `onClick` is composed (runs after); other event handlers are NOT.
19
+ * Children using Compiled's `css` prop swallow injected props - use
20
+ * `Popup.TriggerFunction` for these cases or any time you need handler
21
+ * composition.
31
22
  */
32
23
  function PopupTrigger(_ref) {
33
24
  var children = _ref.children;
@@ -39,35 +30,24 @@ function PopupTrigger(_ref) {
39
30
  ariaHasPopup = _usePopupContext.ariaHasPopup;
40
31
  var childOnClick = /*#__PURE__*/(0, _react.isValidElement)(children) ? children.props.onClick : undefined;
41
32
 
42
- // Use a ref to track the latest isOpen value so the click handler
43
- // does not need isOpen in its dependency array (which would recreate
44
- // the handler on every open/close, breaking cloneElement identity).
33
+ // Track latest isOpen via ref so the click handler keeps a stable
34
+ // identity (cloneElement relies on it).
45
35
  var isOpenRef = _react.default.useRef(isOpen);
46
36
  isOpenRef.current = isOpen;
47
37
  var handleClick = (0, _react.useCallback)(function (event) {
48
38
  var popoverEl = popoverRef.current;
49
39
  if (popoverEl) {
50
- // For popover="auto", clicking the trigger (which is outside
51
- // the popover) can cause the browser's built-in light-dismiss
52
- // to fire before our click handler runs. This means by the time
53
- // we get here, the DOM may have already closed the popover.
54
- //
55
- // We use React's `isOpen` state (via ref) rather than the DOM
56
- // state to determine intent:
57
- // - If React thinks it was open → the user wants to close.
58
- // The browser already did this via light-dismiss, so do nothing.
59
- // - If React thinks it was closed → the user wants to open.
60
- // Call showPopover().
40
+ // `popover="auto"` light-dismiss can close the popover BEFORE
41
+ // our click handler runs, so trust React's isOpen rather than
42
+ // DOM state. If React still thinks it is closed, open it; if
43
+ // it was open, the browser already closed it.
61
44
  if (!isOpenRef.current) {
62
- // Defensive: element may be disconnected or in an unexpected state.
63
45
  try {
64
46
  popoverEl.showPopover();
65
- } catch (_unused) {}
47
+ } catch (_unused) {
48
+ // Element may be disconnected; ignore.
49
+ }
66
50
  }
67
- // When isOpenRef.current is true, the browser's light-dismiss
68
- // already closed the popover and fired the toggle event, which
69
- // will update React state via handleToggle → setIsOpen(false).
70
- // No action needed here.
71
51
  }
72
52
  childOnClick === null || childOnClick === void 0 || childOnClick(event);
73
53
  }, [popoverRef, childOnClick]);
@@ -11,10 +11,10 @@ var _react = _interopRequireWildcard(require("react"));
11
11
  var _popupContext = require("./popup-context");
12
12
  function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function _interopRequireWildcard(e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != _typeof(e) && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (var _t in e) "default" !== _t && {}.hasOwnProperty.call(e, _t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, _t)) && (i.get || i.set) ? o(f, _t, i) : f[_t] = e[_t]); return f; })(e, t); }
13
13
  /**
14
- * Popup compound — trigger + popover content.
14
+ * Popup compound: trigger + popover content.
15
15
  *
16
16
  * Use when a button opens anchored content (dropdown, menu, dialog).
17
- * The browser manages visibility via `togglePopover()` — the consumer
17
+ * The browser manages visibility via `togglePopover()`; the consumer
18
18
  * never manages `isOpen`. For custom trigger lifecycles (hover, timers,
19
19
  * external state), use `Popover` directly instead.
20
20
  *
@@ -22,18 +22,24 @@ function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r
22
22
  * `Popup.Content` reads and forwards to the underlying `Popover`.
23
23
  */
24
24
  function PopupRoot(props) {
25
- var _props$mode;
26
- var placement = props.placement,
27
- children = props.children,
25
+ var _props$placement, _props$mode;
26
+ var children = props.children,
28
27
  testId = props.testId,
29
28
  forceFallbackPositioning = props.forceFallbackPositioning,
30
29
  onOpenChange = props.onOpenChange;
30
+ // `{}` resolves to "below the trigger, centered, with `space.100` gap"
31
+ // inside `useAnchorPosition`, which is the common case.
32
+ var placement = (_props$placement = props.placement) !== null && _props$placement !== void 0 ? _props$placement : {};
31
33
  var mode = (_props$mode = props.mode) !== null && _props$mode !== void 0 ? _props$mode : 'auto';
32
- var onClose = mode === 'manual' ? null : props.onClose;
34
+
35
+ // `TPopupProps` discriminates on `mode`: `manual` types `onClose` as
36
+ // `never`, so for manual consumers `props.onClose` is always `undefined`
37
+ // here. No runtime guard needed; the type contract carries the rule.
38
+ var onClose = props.onClose;
33
39
  var id = (0, _react.useId)();
34
- // Regex is intentional — `replaceAll` is not available in our supported
35
- // browser matrix. `useId()` returns IDs containing colons (e.g. `:r1:`)
36
- // which are invalid in CSS selectors and `popover` target attributes.
40
+ // `useId()` returns IDs containing colons (`:r1:`), which are invalid
41
+ // in CSS selectors and `popover` target attributes. `replaceAll` is
42
+ // not in our browser matrix.
37
43
  var sanitizedId = id.replace(/:/g, '');
38
44
  var popoverId = "popover-".concat(sanitizedId);
39
45
  var triggerRef = (0, _react.useRef)(null);
@@ -42,6 +48,8 @@ function PopupRoot(props) {
42
48
  _useState2 = (0, _slicedToArray2.default)(_useState, 2),
43
49
  isOpen = _useState2[0],
44
50
  setIsOpen = _useState2[1];
51
+ // Initial seed before `Popup.Content`'s layout effect resolves the
52
+ // real role on first paint.
45
53
  var _useState3 = (0, _react.useState)('dialog'),
46
54
  _useState4 = (0, _slicedToArray2.default)(_useState3, 2),
47
55
  ariaHasPopup = _useState4[0],
@@ -52,7 +60,10 @@ function PopupRoot(props) {
52
60
  var contextValue = {
53
61
  popoverId: popoverId,
54
62
  placement: placement,
55
- onClose: mode === 'manual' ? null : handleClose,
63
+ // `null` when the consumer did not supply a handler (manual mode, or
64
+ // auto/hint without a light-dismiss handler). `Popup.Content` reads
65
+ // this to decide whether to forward an `onClose` to `Popover`.
66
+ onClose: onClose ? handleClose : null,
56
67
  triggerRef: triggerRef,
57
68
  popoverRef: popoverRef,
58
69
  isOpen: isOpen,
@@ -14,16 +14,16 @@ var menuScope = '[role="menu"]';
14
14
 
15
15
  /**
16
16
  * Returns true if the element belongs to the current menu level of the
17
- * container — i.e. it is NOT inside a nested `role="menu"` sub-container.
17
+ * container, i.e. it is NOT inside a nested `role="menu"` sub-container.
18
18
  *
19
19
  * Uses `element.closest('[role="menu"]')` to determine the nearest menu
20
20
  * scope. If the nearest scope is the container's own `role="menu"` element,
21
- * the element belongs to this level. If it's a different (nested) menu,
21
+ * the element belongs to this level. If it is a different (nested) menu,
22
22
  * the element belongs to a child level and should be excluded.
23
23
  *
24
24
  * Supports two container patterns:
25
25
  *
26
- * **1. Container has `role="menu"` directly** (preferred — used by avatar-group):
26
+ * **1. Container has `role="menu"` directly** (preferred, used by avatar-group):
27
27
  * ```
28
28
  * <div ref={containerRef} role="menu">
29
29
  * <button role="menuitem" /> ← isAtCurrentMenuLevel → true
@@ -35,7 +35,7 @@ var menuScope = '[role="menu"]';
35
35
  * Uses the simple check: `element.closest('[role="menu"]') === container`
36
36
  *
37
37
  * **2. Container is a wrapper without `role="menu"`** (used by dropdown-menu,
38
- * where `MenuGroup` owns `role="menu"` but doesn't accept a ref):
38
+ * where `MenuGroup` owns `role="menu"` but does not accept a ref):
39
39
  * ```
40
40
  * <div ref={containerRef}> ← wrapper (no role="menu")
41
41
  * <MenuGroup role="menu">
@@ -62,7 +62,7 @@ var menuScope = '[role="menu"]';
62
62
  function isAtCurrentMenuLevel(element, container) {
63
63
  var closestMenu = element.closest(menuScope);
64
64
 
65
- // Container itself has role="menu" — element belongs to this level
65
+ // Container itself has role="menu". Element belongs to this level
66
66
  // if its closest menu is the container.
67
67
  if (container.getAttribute('role') === 'menu') {
68
68
  return closestMenu === container;
@@ -71,18 +71,18 @@ function isAtCurrentMenuLevel(element, container) {
71
71
  // Container is a wrapper without role="menu" (e.g. dropdown-menu
72
72
  // wraps a MenuGroup). The element belongs to the top level if its
73
73
  // closest role="menu" ancestor is a direct child of the container,
74
- // i.e. there's no nested role="menu" between them.
74
+ // i.e. there is no nested role="menu" between them.
75
75
  if (closestMenu === null) {
76
76
  return true;
77
77
  }
78
78
 
79
- // Walk up from the closest menu to see if it's "owned" by this
79
+ // Walk up from the closest menu to see if it is "owned" by this
80
80
  // container without passing through another role="menu" first.
81
81
  var parent = closestMenu.parentElement;
82
82
  while (parent && parent !== container) {
83
83
  if (parent.getAttribute('role') === 'menu') {
84
- // There's a parent menu between the closest menu and the
85
- // container — the element is in a nested sub-menu.
84
+ // There is a parent menu between the closest menu and the
85
+ // container, so the element is in a nested sub-menu.
86
86
  return false;
87
87
  }
88
88
  parent = parent.parentElement;
@@ -91,6 +91,22 @@ function isAtCurrentMenuLevel(element, container) {
91
91
  // The closest menu is directly inside the container (no nesting).
92
92
  return parent === container;
93
93
  }
94
+
95
+ /**
96
+ * Args for `useArrowNavigation`.
97
+ *
98
+ * Keyboard contract:
99
+ * - `ArrowDown` / `ArrowUp` move within the current menu level.
100
+ * - `Home` / `End` jump to first / last.
101
+ * - `ArrowRight` / `ArrowLeft` opens / closes nested submenus.
102
+ * - `Tab` / `Shift+Tab` dismiss the menu (matching the ARIA APG menu pattern,
103
+ * plus Radix UI / Headless UI / React Aria / Floating UI conventions).
104
+ * Symmetric in both directions: there is no special-case for the last
105
+ * item that lets Tab leave the menu without dismissing it.
106
+ * - `Page Up` / `Page Down`: NOT handled - extend via your own listener if
107
+ * your menu needs page-style scrolling.
108
+ */
109
+
94
110
  /**
95
111
  * Provides arrow key navigation for menu containers.
96
112
  *
@@ -126,6 +142,7 @@ function isAtCurrentMenuLevel(element, container) {
126
142
  * });
127
143
  * ```
128
144
  */
145
+ // eslint-disable-next-line @atlaskit/volt-strict-mode/no-multiple-exports
129
146
  function useArrowNavigation(_ref) {
130
147
  var containerRef = _ref.containerRef,
131
148
  onClose = _ref.onClose,
@@ -221,7 +238,7 @@ function useArrowNavigation(_ref) {
221
238
  if (onNestedClose) {
222
239
  var _container$parentElem;
223
240
  // Only close if this container is inside another role="menu"
224
- // (i.e. it's a submenu, not the top-level menu).
241
+ // (i.e. it is a submenu, not the top-level menu).
225
242
  // Walk up from the parent to avoid matching the container itself.
226
243
  var isNested = ((_container$parentElem = container.parentElement) === null || _container$parentElem === void 0 ? void 0 : _container$parentElem.closest(menuScope)) !== null;
227
244
  if (isNested) {
@@ -1,4 +1,4 @@
1
- import { getPlacement } from '../internal/use-anchor-position';
1
+ import { getPlacement } from '../internal/resolve-placement';
2
2
 
3
3
  // ── slideAndFade ──
4
4
 
@@ -152,6 +152,7 @@ const FADE_CSS = `
152
152
  * <Popup.Content animate={fade()} />
153
153
  * ```
154
154
  */
155
+ // eslint-disable-next-line @atlaskit/volt-strict-mode/no-multiple-exports
155
156
  export function fade() {
156
157
  return {
157
158
  name: 'fade',
@@ -206,6 +207,7 @@ const SCALE_AND_FADE_CSS = `
206
207
  * <Popup.Content animate={scaleAndFade()} />
207
208
  * ```
208
209
  */
210
+ // eslint-disable-next-line @atlaskit/volt-strict-mode/no-multiple-exports
209
211
  export function scaleAndFade() {
210
212
  return {
211
213
  name: 'scale-and-fade',
@@ -291,11 +293,14 @@ const DIALOG_SLIDE_UP_AND_FADE_CSS = `
291
293
  * </Dialog>
292
294
  * ```
293
295
  */
296
+ // eslint-disable-next-line @atlaskit/volt-strict-mode/no-multiple-exports
294
297
  export function dialogSlideUpAndFade(options) {
295
298
  var _options$distance2;
296
299
  const distance = (_options$distance2 = options === null || options === void 0 ? void 0 : options.distance) !== null && _options$distance2 !== void 0 ? _options$distance2 : 12;
300
+
301
+ // Bare preset name; `Dialog` prefixes it via `data-ds-dialog-{name}`.
297
302
  return {
298
- name: distance === 12 ? 'dialog-slide-up-and-fade' : `dialog-slide-up-and-fade-${distance}`,
303
+ name: distance === 12 ? 'slide-up-and-fade' : `slide-up-and-fade-${distance}`,
299
304
  css: distance === 12 ? DIALOG_SLIDE_UP_AND_FADE_CSS : DIALOG_SLIDE_UP_AND_FADE_CSS.replace(/12px/g, `${distance}px`),
300
305
  exitDurationMs: 175
301
306
  };
@@ -366,9 +371,11 @@ const DIALOG_FADE_CSS = `
366
371
  * </Dialog>
367
372
  * ```
368
373
  */
374
+ // eslint-disable-next-line @atlaskit/volt-strict-mode/no-multiple-exports
369
375
  export function dialogFade() {
376
+ // Bare preset name; `Dialog` prefixes it via `data-ds-dialog-{name}`.
370
377
  return {
371
- name: 'dialog-fade',
378
+ name: 'fade',
372
379
  css: DIALOG_FADE_CSS,
373
380
  exitDurationMs: 175
374
381
  };
@@ -0,0 +1,269 @@
1
+ import once from '@atlaskit/ds-lib/once';
2
+ import { getPlacement } from '../internal/resolve-placement';
3
+ import { placementToPositionArea } from '../internal/use-anchor-position';
4
+ /**
5
+ * CSS for popover arrows using the `clip-path: inset() margin-box` technique.
6
+ *
7
+ * How it works:
8
+ * 1. Four arrow shapes exist simultaneously as `::before` (top/bottom) and
9
+ * `::after` (left/right) hexagonal pseudo-elements.
10
+ * 2. `clip-path: inset(0) margin-box` clips the popover to its margin-box.
11
+ * 3. Margin on the anchor-facing side pushes the popover away, creating space
12
+ * where one arrow "escapes" the clip boundary and becomes visible.
13
+ * 4. `@position-try` rules change both `position-area` and `margin` when
14
+ * flipping, ensuring the correct arrow is always shown.
15
+ *
16
+ * Constraints:
17
+ * - `box-shadow` must be `none` (interferes with `clip-path: inset() margin-box`)
18
+ * - `::before` and `::after` are consumed - consumers cannot use them
19
+ * - Arrow pseudo-elements use `background: inherit` from the popover element
20
+ *
21
+ * @see https://goulet.dev/posts/tooltip-with-popover-and-anchor-positioning/
22
+ */
23
+ const ARROW_CSS = `
24
+ [data-ds-popover-arrow] {
25
+ clip-path: inset(var(--ds-arrow-offset, 0px)) margin-box;
26
+ box-shadow: none;
27
+ }
28
+
29
+ /* Top and Bottom arrows (vertical hexagon) */
30
+ [data-ds-popover-arrow]::before {
31
+ content: "";
32
+ position: absolute;
33
+ z-index: -1;
34
+ background: inherit;
35
+ left: 50%;
36
+ transform: translateX(-50%);
37
+ width: calc(var(--ds-arrow-size, 8px) * 2);
38
+ height: calc(100% + var(--ds-arrow-size, 8px) * 2);
39
+ top: calc(var(--ds-arrow-size, 8px) * -1);
40
+ clip-path: polygon(
41
+ 0 var(--ds-arrow-size, 8px),
42
+ 50% 0,
43
+ 100% var(--ds-arrow-size, 8px),
44
+ 100% calc(100% - var(--ds-arrow-size, 8px)),
45
+ 50% 100%,
46
+ 0 calc(100% - var(--ds-arrow-size, 8px))
47
+ );
48
+ }
49
+
50
+ /* Left and Right arrows (horizontal hexagon) */
51
+ [data-ds-popover-arrow]::after {
52
+ content: "";
53
+ position: absolute;
54
+ z-index: -1;
55
+ background: inherit;
56
+ top: 50%;
57
+ transform: translateY(-50%);
58
+ height: calc(var(--ds-arrow-size, 8px) * 2);
59
+ width: calc(100% + var(--ds-arrow-size, 8px) * 2);
60
+ left: calc(var(--ds-arrow-size, 8px) * -1);
61
+ clip-path: polygon(
62
+ var(--ds-arrow-size, 8px) 0,
63
+ calc(100% - var(--ds-arrow-size, 8px)) 0,
64
+ 100% 50%,
65
+ calc(100% - var(--ds-arrow-size, 8px)) 100%,
66
+ var(--ds-arrow-size, 8px) 100%,
67
+ 0 50%
68
+ );
69
+ }
70
+
71
+ /* ── @position-try rules ──
72
+ * One rule per possible position-area value.
73
+ * Each sets the position-area and the correct margin direction
74
+ * so the arrow on the anchor-facing side is revealed.
75
+ *
76
+ * Margin direction per edge:
77
+ * block-start (above anchor) → margin-block-end (bottom, facing anchor)
78
+ * block-end (below anchor) → margin-block-start (top, facing anchor)
79
+ * inline-start (left of anchor) → margin-inline-end (right, facing anchor)
80
+ * inline-end (right of anchor) → margin-inline-start (left, facing anchor)
81
+ */
82
+
83
+ /* Each rule sets two margins: the anchor-facing margin for the gap
84
+ (via --ds-arrow-size) and one cross-axis margin for the shift
85
+ (via one of --ds-cross-axis-shift-margin-start, --ds-cross-axis-shift-margin-end,
86
+ --ds-cross-axis-shift-margin-block-start, or --ds-cross-axis-shift-margin-block-end). The
87
+ side matches the popover's anchored cross-axis edge per align. Only
88
+ one of the four properties is non-zero at a time; useAnchorPosition
89
+ picks it. See notes/architecture/positioning.md for details. */
90
+ /* Single-axis centered (align: center) */
91
+ @position-try --ds-arrow-block-start {
92
+ position-area: block-start;
93
+ margin: 0;
94
+ margin-block-end: var(--ds-arrow-size, 8px);
95
+ margin-inline-start: var(--ds-cross-axis-shift-margin-start, 0px);
96
+ }
97
+ @position-try --ds-arrow-block-end {
98
+ position-area: block-end;
99
+ margin: 0;
100
+ margin-block-start: var(--ds-arrow-size, 8px);
101
+ margin-inline-start: var(--ds-cross-axis-shift-margin-start, 0px);
102
+ }
103
+ @position-try --ds-arrow-inline-start {
104
+ position-area: inline-start;
105
+ margin: 0;
106
+ margin-inline-end: var(--ds-arrow-size, 8px);
107
+ margin-block-start: var(--ds-cross-axis-shift-margin-block-start, 0px);
108
+ }
109
+ @position-try --ds-arrow-inline-end {
110
+ position-area: inline-end;
111
+ margin: 0;
112
+ margin-inline-start: var(--ds-arrow-size, 8px);
113
+ margin-block-start: var(--ds-cross-axis-shift-margin-block-start, 0px);
114
+ }
115
+
116
+ /* Block edge + inline span (align: start uses span-inline-end; align: end uses span-inline-start) */
117
+ @position-try --ds-arrow-block-start-span-inline-start {
118
+ position-area: block-start span-inline-start;
119
+ margin: 0;
120
+ margin-block-end: var(--ds-arrow-size, 8px);
121
+ margin-inline-end: var(--ds-cross-axis-shift-margin-end, 0px);
122
+ }
123
+ @position-try --ds-arrow-block-start-span-inline-end {
124
+ position-area: block-start span-inline-end;
125
+ margin: 0;
126
+ margin-block-end: var(--ds-arrow-size, 8px);
127
+ margin-inline-start: var(--ds-cross-axis-shift-margin-start, 0px);
128
+ }
129
+ @position-try --ds-arrow-block-end-span-inline-start {
130
+ position-area: block-end span-inline-start;
131
+ margin: 0;
132
+ margin-block-start: var(--ds-arrow-size, 8px);
133
+ margin-inline-end: var(--ds-cross-axis-shift-margin-end, 0px);
134
+ }
135
+ @position-try --ds-arrow-block-end-span-inline-end {
136
+ position-area: block-end span-inline-end;
137
+ margin: 0;
138
+ margin-block-start: var(--ds-arrow-size, 8px);
139
+ margin-inline-start: var(--ds-cross-axis-shift-margin-start, 0px);
140
+ }
141
+
142
+ /* Inline edge + block span (align: start uses span-block-end; align: end uses span-block-start) */
143
+ @position-try --ds-arrow-inline-start-span-block-start {
144
+ position-area: inline-start span-block-start;
145
+ margin: 0;
146
+ margin-inline-end: var(--ds-arrow-size, 8px);
147
+ margin-block-end: var(--ds-cross-axis-shift-margin-block-end, 0px);
148
+ }
149
+ @position-try --ds-arrow-inline-start-span-block-end {
150
+ position-area: inline-start span-block-end;
151
+ margin: 0;
152
+ margin-inline-end: var(--ds-arrow-size, 8px);
153
+ margin-block-start: var(--ds-cross-axis-shift-margin-block-start, 0px);
154
+ }
155
+ @position-try --ds-arrow-inline-end-span-block-start {
156
+ position-area: inline-end span-block-start;
157
+ margin: 0;
158
+ margin-inline-start: var(--ds-arrow-size, 8px);
159
+ margin-block-end: var(--ds-cross-axis-shift-margin-block-end, 0px);
160
+ }
161
+ @position-try --ds-arrow-inline-end-span-block-end {
162
+ position-area: inline-end span-block-end;
163
+ margin: 0;
164
+ margin-inline-start: var(--ds-arrow-size, 8px);
165
+ margin-block-start: var(--ds-cross-axis-shift-margin-block-start, 0px);
166
+ }
167
+ `;
168
+
169
+ // ── Placement utilities ──
170
+
171
+ /**
172
+ * Converts a `position-area` value to a CSS rule name by replacing spaces with hyphens
173
+ * and prepending `--ds-arrow-`.
174
+ *
175
+ * @example positionAreaToRuleName('block-end span-inline-start') → '--ds-arrow-block-end-span-inline-start'
176
+ */
177
+ function positionAreaToRuleName({
178
+ positionArea
179
+ }) {
180
+ return `--ds-arrow-${positionArea.replace(/ /g, '-')}`;
181
+ }
182
+
183
+ /**
184
+ * Returns `position-try-fallbacks` using named `@position-try` rules
185
+ * that change both `position-area` and `margin` when flipping.
186
+ *
187
+ * This is required for the arrow technique because built-in keywords
188
+ * (`flip-block`, `flip-inline`) only flip `position-area` - they do
189
+ * not update the margin direction needed to reveal the correct arrow.
190
+ */
191
+ function getTryFallbacks({
192
+ placement
193
+ }) {
194
+ const {
195
+ axis,
196
+ edge,
197
+ align
198
+ } = getPlacement({
199
+ placement
200
+ });
201
+ if (align === 'center') {
202
+ // Single-axis: only flip the edge
203
+ const flippedEdge = edge === 'start' ? 'end' : 'start';
204
+ return positionAreaToRuleName({
205
+ positionArea: placementToPositionArea({
206
+ placement: {
207
+ axis,
208
+ edge: flippedEdge
209
+ }
210
+ })
211
+ });
212
+ }
213
+
214
+ // Compound placement: try flipping edge, alignment, and both
215
+ const flippedEdge = edge === 'start' ? 'end' : 'start';
216
+ const flippedAlign = align === 'start' ? 'end' : 'start';
217
+ return [positionAreaToRuleName({
218
+ positionArea: placementToPositionArea({
219
+ placement: {
220
+ axis,
221
+ edge: flippedEdge,
222
+ align
223
+ }
224
+ })
225
+ }), positionAreaToRuleName({
226
+ positionArea: placementToPositionArea({
227
+ placement: {
228
+ axis,
229
+ edge,
230
+ align: flippedAlign
231
+ }
232
+ })
233
+ }), positionAreaToRuleName({
234
+ positionArea: placementToPositionArea({
235
+ placement: {
236
+ axis,
237
+ edge: flippedEdge,
238
+ align: flippedAlign
239
+ }
240
+ })
241
+ })].join(', ');
242
+ }
243
+
244
+ /**
245
+ * Creates an arrow preset for popover content.
246
+ *
247
+ * The returned object is passed to `PopoverContent`'s `arrow` prop.
248
+ * It carries the CSS string and fallback logic so that consumers who
249
+ * do not use arrows pay no bundle cost.
250
+ *
251
+ * The arrows inherit their background from the popover element.
252
+ * When `arrow` is active, `PopoverContent` does not reset `background`
253
+ * to `transparent`, allowing the UA default (`canvas`) or a consumer-set
254
+ * background to show through.
255
+ *
256
+ * @example
257
+ * ```tsx
258
+ * import { arrow } from '@atlaskit/top-layer/arrow';
259
+ *
260
+ * const myArrow = arrow();
261
+ *
262
+ * <Popup.Content arrow={myArrow} />
263
+ * ```
264
+ */
265
+ export const arrow = once(() => ({
266
+ name: 'arrow',
267
+ css: ARROW_CSS,
268
+ getTryFallbacks
269
+ }));
@@ -0,0 +1 @@
1
+ export {};
@@ -5,6 +5,8 @@ import { ax, ix } from "@compiled/react/runtime";
5
5
  import React, { forwardRef, useCallback, useEffect, useId, useLayoutEffect, useRef } from 'react';
6
6
  import { bind } from 'bind-event-listener';
7
7
  import mergeRefs from '@atlaskit/ds-lib/merge-refs';
8
+ import noop from '@atlaskit/ds-lib/noop';
9
+ import { useNotifyOpenLayerObserver } from '@atlaskit/layering/experimental/open-layer-observer';
8
10
  import { useAnimatedVisibility } from '../internal/use-animated-visibility';
9
11
  import { useFocusWrap } from '../internal/use-focus-wrap';
10
12
  // eslint-disable-next-line @atlaskit/ui-styling-standard/no-unsafe-values -- dialog reset requires values not in cssMap's type union
@@ -13,7 +15,7 @@ const styles = {
13
15
  };
14
16
 
15
17
  /**
16
- * Low-level `<dialog>` primitive. No visual opinions — no width, height,
18
+ * Low-level `<dialog>` primitive. No visual opinions - no width, height,
17
19
  * background, border-radius, or layout. Consumers provide their own styling.
18
20
  *
19
21
  * Visibility is controlled declaratively via `isOpen`:
@@ -24,6 +26,10 @@ const styles = {
24
26
  *
25
27
  * Close flow: we never call `dialog.close()` from event handlers. We always call
26
28
  * `onClose`; the consumer decides whether to set `isOpen={false}`.
29
+ *
30
+ * Accessibility: render at least one focusable element (typically a close
31
+ * button) when `isOpen` becomes `true`. Tab is always trapped inside the
32
+ * dialog, so keyboard users need somewhere to land.
27
33
  */
28
34
  export const Dialog = /*#__PURE__*/forwardRef(function Dialog({
29
35
  children,
@@ -63,6 +69,16 @@ export const Dialog = /*#__PURE__*/forwardRef(function Dialog({
63
69
  role: 'dialog'
64
70
  });
65
71
 
72
+ // ── Open layer observer registration ──
73
+ // Notifies the open layer observer so app-coordination features
74
+ // (open-count subscriptions) work with top-layer dialogs.
75
+ useNotifyOpenLayerObserver({
76
+ type: 'modal',
77
+ isOpen,
78
+ // No-op: no current use case for programmatic close via OpenLayerObserver.
79
+ onClose: noop
80
+ });
81
+
66
82
  // Show/hide the dialog in response to isOpen changes.
67
83
  useLayoutEffect(() => {
68
84
  const dialog = ownRef.current;
@@ -121,11 +137,12 @@ export const Dialog = /*#__PURE__*/forwardRef(function Dialog({
121
137
  return /*#__PURE__*/React.createElement("dialog", _extends({
122
138
  ref: combinedRef,
123
139
  id: dialogId
124
- // Modern browsers infer modal semantics from `.showModal()` but
125
- // some assistive tech still keys off `aria-modal="true"`. Setting
126
- // it explicitly is recommended by WAI-ARIA APG dialog pattern.
140
+ // `aria-modal` is intentionally NOT set: native `<dialog>.showModal()`
141
+ // already conveys modal semantics to assistive tech, and double-
142
+ // declaring it forecloses non-modal use cases (consumers calling
143
+ // `.show()` would still appear modal). Modern AT (NVDA / JAWS /
144
+ // VoiceOver) infer modality from the platform accessibility API.
127
145
  ,
128
- "aria-modal": "true",
129
146
  "aria-label": label,
130
147
  "aria-labelledby": label ? undefined : labelledBy,
131
148
  // eslint-disable-next-line @atlaskit/ui-styling-standard/enforce-style-prop
@@ -133,7 +150,7 @@ export const Dialog = /*#__PURE__*/forwardRef(function Dialog({
133
150
  onCancel: handleCancel,
134
151
  "data-testid": testId
135
152
  }, preset ? {
136
- [`data-ds-${preset.name}`]: ''
153
+ [`data-ds-dialog-${preset.name}`]: ''
137
154
  } : undefined, {
138
155
  className: ax([styles.dialog])
139
156
  }), shouldHideBackdrop &&
@@ -1,2 +1,9 @@
1
+ /**
2
+ * @deprecated Combined entry-point retained for backwards-compatibility.
3
+ * Prefer the disjoint subpaths so that bundle analysis attributes the cost
4
+ * to the right consumer:
5
+ * - `@atlaskit/top-layer/dialog/create-close-event` for `createCloseEvent`
6
+ * - `@atlaskit/top-layer/popover/create-close-event` for `createPopoverCloseEvent`
7
+ */
1
8
  export { createCloseEvent } from '../dialog';
2
9
  export { createPopoverCloseEvent } from '../popover/create-close-event';
@@ -0,0 +1 @@
1
+ export { createCloseEvent } from '../../dialog/create-close-event';
@@ -1 +1,2 @@
1
- export { fromLegacyPlacement, placementMapping } from '../placement-map';
1
+ export { fromLegacyPlacement } from '../placement-map';
2
+ export { LEGACY_PLACEMENTS } from '../placement-map/legacy-placements';
@@ -0,0 +1 @@
1
+ export { createPopoverCloseEvent } from '../../popover/create-close-event';