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