@atlaskit/top-layer 0.9.0 → 0.11.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 (127) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/README.md +5 -1
  3. package/dialog/create-close-event/package.json +17 -0
  4. package/dist/cjs/animations/presets.js +10 -5
  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 +11 -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 +71 -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 +107 -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 -111
  20. package/dist/cjs/placement-map/legacy-placements.js +113 -0
  21. package/dist/cjs/popover/popover.js +14 -12
  22. package/dist/cjs/popup/popup-content.js +19 -13
  23. package/dist/cjs/popup/popup-surface.js +6 -0
  24. package/dist/cjs/popup/popup-trigger-function.js +1 -1
  25. package/dist/cjs/popup/popup-trigger.js +17 -37
  26. package/dist/cjs/popup/popup.js +21 -10
  27. package/dist/cjs/use-arrow-navigation/use-arrow-navigation.js +26 -10
  28. package/dist/es2019/animations/presets.js +20 -5
  29. package/dist/es2019/arrow/index.js +269 -0
  30. package/dist/es2019/arrow/types.js +1 -0
  31. package/dist/es2019/dialog/dialog-content.js +11 -6
  32. package/dist/es2019/entry-points/create-close-event.js +7 -0
  33. package/dist/es2019/entry-points/dialog/create-close-event.js +1 -0
  34. package/dist/es2019/entry-points/placement-map.js +2 -1
  35. package/dist/es2019/entry-points/popover/create-close-event.js +1 -0
  36. package/dist/es2019/focus/focus.js +56 -7
  37. package/dist/es2019/index.js +3 -4
  38. package/dist/es2019/internal/resolve-css-length-to-pixels.js +60 -7
  39. package/dist/es2019/internal/role-types.js +40 -9
  40. package/dist/es2019/internal/set-style.js +19 -22
  41. package/dist/es2019/internal/use-anchor-position.js +95 -54
  42. package/dist/es2019/internal/use-focus-wrap.js +14 -3
  43. package/dist/es2019/internal/use-initial-focus.js +32 -11
  44. package/dist/es2019/placement-map/index.js +8 -109
  45. package/dist/es2019/placement-map/legacy-placements.js +107 -0
  46. package/dist/es2019/popover/popover.js +14 -11
  47. package/dist/es2019/popup/popup-content.js +20 -14
  48. package/dist/es2019/popup/popup-surface.js +6 -0
  49. package/dist/es2019/popup/popup-trigger-function.js +1 -1
  50. package/dist/es2019/popup/popup-trigger.js +17 -37
  51. package/dist/es2019/popup/popup.js +20 -9
  52. package/dist/es2019/use-arrow-navigation/use-arrow-navigation.js +26 -10
  53. package/dist/esm/animations/presets.js +9 -4
  54. package/dist/esm/arrow/index.js +124 -0
  55. package/dist/esm/arrow/types.js +1 -0
  56. package/dist/esm/dialog/dialog-content.js +11 -6
  57. package/dist/esm/entry-points/create-close-event.js +7 -0
  58. package/dist/esm/entry-points/dialog/create-close-event.js +1 -0
  59. package/dist/esm/entry-points/placement-map.js +2 -1
  60. package/dist/esm/entry-points/popover/create-close-event.js +1 -0
  61. package/dist/esm/focus/focus.js +70 -19
  62. package/dist/esm/index.js +3 -4
  63. package/dist/esm/internal/resolve-css-length-to-pixels.js +61 -10
  64. package/dist/esm/internal/role-types.js +39 -9
  65. package/dist/esm/internal/set-style.js +23 -25
  66. package/dist/esm/internal/use-anchor-position.js +108 -64
  67. package/dist/esm/internal/use-focus-wrap.js +14 -3
  68. package/dist/esm/internal/use-initial-focus.js +32 -11
  69. package/dist/esm/placement-map/index.js +8 -109
  70. package/dist/esm/placement-map/legacy-placements.js +107 -0
  71. package/dist/esm/popover/popover.js +14 -12
  72. package/dist/esm/popup/popup-content.js +20 -14
  73. package/dist/esm/popup/popup-surface.js +6 -0
  74. package/dist/esm/popup/popup-trigger-function.js +1 -1
  75. package/dist/esm/popup/popup-trigger.js +17 -37
  76. package/dist/esm/popup/popup.js +21 -10
  77. package/dist/esm/use-arrow-navigation/use-arrow-navigation.js +26 -10
  78. package/dist/types/arrow/index.d.ts +24 -0
  79. package/dist/types/arrow/types.d.ts +29 -0
  80. package/dist/types/dialog/dialog-content.d.ts +5 -1
  81. package/dist/types/dialog/types.d.ts +50 -24
  82. package/dist/types/entry-points/create-close-event.d.ts +7 -0
  83. package/dist/types/entry-points/dialog/create-close-event.d.ts +2 -0
  84. package/dist/types/entry-points/placement-map.d.ts +2 -1
  85. package/dist/types/entry-points/popover/create-close-event.d.ts +2 -0
  86. package/dist/types/focus/focus.d.ts +8 -0
  87. package/dist/types/internal/resolve-css-length-to-pixels.d.ts +4 -6
  88. package/dist/types/internal/role-types.d.ts +25 -4
  89. package/dist/types/internal/set-style.d.ts +2 -19
  90. package/dist/types/internal/use-anchor-position.d.ts +2 -3
  91. package/dist/types/internal/use-initial-focus.d.ts +5 -4
  92. package/dist/types/placement-map/index.d.ts +8 -21
  93. package/dist/types/placement-map/legacy-placements.d.ts +21 -0
  94. package/dist/types/popover/popover.d.ts +1 -1
  95. package/dist/types/popover/types.d.ts +17 -17
  96. package/dist/types/popup/popup-surface.d.ts +6 -0
  97. package/dist/types/popup/popup-trigger-function.d.ts +10 -6
  98. package/dist/types/popup/popup-trigger.d.ts +8 -17
  99. package/dist/types/popup/popup.d.ts +2 -2
  100. package/dist/types/popup/types.d.ts +68 -22
  101. package/dist/types/use-arrow-navigation/use-arrow-navigation.d.ts +19 -5
  102. package/dist/types-ts4.5/arrow/index.d.ts +24 -0
  103. package/dist/types-ts4.5/arrow/types.d.ts +29 -0
  104. package/dist/types-ts4.5/dialog/dialog-content.d.ts +5 -1
  105. package/dist/types-ts4.5/dialog/types.d.ts +50 -24
  106. package/dist/types-ts4.5/entry-points/create-close-event.d.ts +7 -0
  107. package/dist/types-ts4.5/entry-points/dialog/create-close-event.d.ts +2 -0
  108. package/dist/types-ts4.5/entry-points/placement-map.d.ts +2 -1
  109. package/dist/types-ts4.5/entry-points/popover/create-close-event.d.ts +2 -0
  110. package/dist/types-ts4.5/focus/focus.d.ts +8 -0
  111. package/dist/types-ts4.5/internal/resolve-css-length-to-pixels.d.ts +4 -6
  112. package/dist/types-ts4.5/internal/role-types.d.ts +25 -4
  113. package/dist/types-ts4.5/internal/set-style.d.ts +2 -19
  114. package/dist/types-ts4.5/internal/use-anchor-position.d.ts +2 -3
  115. package/dist/types-ts4.5/internal/use-initial-focus.d.ts +5 -4
  116. package/dist/types-ts4.5/placement-map/index.d.ts +8 -21
  117. package/dist/types-ts4.5/placement-map/legacy-placements.d.ts +39 -0
  118. package/dist/types-ts4.5/popover/popover.d.ts +1 -1
  119. package/dist/types-ts4.5/popover/types.d.ts +17 -17
  120. package/dist/types-ts4.5/popup/popup-surface.d.ts +6 -0
  121. package/dist/types-ts4.5/popup/popup-trigger-function.d.ts +10 -6
  122. package/dist/types-ts4.5/popup/popup-trigger.d.ts +8 -17
  123. package/dist/types-ts4.5/popup/popup.d.ts +2 -2
  124. package/dist/types-ts4.5/popup/types.d.ts +68 -22
  125. package/dist/types-ts4.5/use-arrow-navigation/use-arrow-navigation.d.ts +19 -5
  126. package/package.json +1 -1
  127. package/popover/create-close-event/package.json +17 -0
@@ -7,16 +7,16 @@ var 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 @@ var 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 @@ var menuScope = '[role="menu"]';
55
55
  export function isAtCurrentMenuLevel(element, container) {
56
56
  var 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
  var 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
  *
@@ -215,7 +231,7 @@ export function useArrowNavigation(_ref) {
215
231
  if (onNestedClose) {
216
232
  var _container$parentElem;
217
233
  // Only close if this container is inside another role="menu"
218
- // (i.e. it's a submenu, not the top-level menu).
234
+ // (i.e. it is a submenu, not the top-level menu).
219
235
  // Walk up from the parent to avoid matching the container itself.
220
236
  var isNested = ((_container$parentElem = container.parentElement) === null || _container$parentElem === void 0 ? void 0 : _container$parentElem.closest(menuScope)) !== null;
221
237
  if (isNested) {
@@ -0,0 +1,24 @@
1
+ import { type TArrowPreset } from './types';
2
+ /**
3
+ * Creates an arrow preset for popover content.
4
+ *
5
+ * The returned object is passed to `PopoverContent`'s `arrow` prop.
6
+ * It carries the CSS string and fallback logic so that consumers who
7
+ * do not use arrows pay no bundle cost.
8
+ *
9
+ * The arrows inherit their background from the popover element.
10
+ * When `arrow` is active, `PopoverContent` does not reset `background`
11
+ * to `transparent`, allowing the UA default (`canvas`) or a consumer-set
12
+ * background to show through.
13
+ *
14
+ * @example
15
+ * ```tsx
16
+ * import { arrow } from '@atlaskit/top-layer/arrow';
17
+ *
18
+ * const myArrow = arrow();
19
+ *
20
+ * <Popup.Content arrow={myArrow} />
21
+ * ```
22
+ */
23
+ export declare const arrow: () => TArrowPreset;
24
+ export type { TArrowPreset } from './types';
@@ -0,0 +1,29 @@
1
+ import type { TPlacementOptions } from '../popup/types';
2
+ /**
3
+ * A self-contained arrow preset for PopoverContent.
4
+ *
5
+ * Contains the CSS rules (injected once into `<head>`), a data-attribute name
6
+ * for CSS targeting, and a function to compute `position-try-fallbacks` values
7
+ * that change both `position-area` and `margin` when the popover flips.
8
+ *
9
+ * Imported from `@atlaskit/top-layer/arrow` so that consumers who do not use
10
+ * arrows pay no bundle cost for the CSS string or fallback logic.
11
+ */
12
+ export type TArrowPreset = {
13
+ /**
14
+ * CSS to inject once into `document.head`, scoped to `[data-ds-popover-{name}]`.
15
+ */
16
+ css: string;
17
+ /**
18
+ * Data attribute suffix for CSS targeting (e.g. `'arrow'`).
19
+ */
20
+ name: string;
21
+ /**
22
+ * Returns the `position-try-fallbacks` value for a given placement.
23
+ * Uses named `@position-try` rules that update both `position-area`
24
+ * and `margin` so the correct arrow is revealed after a flip.
25
+ */
26
+ getTryFallbacks: (args: {
27
+ placement: TPlacementOptions;
28
+ }) => string;
29
+ };
@@ -5,7 +5,7 @@
5
5
  import React from 'react';
6
6
  import { type TDialogProps } from './types';
7
7
  /**
8
- * Low-level `<dialog>` primitive. No visual opinions — no width, height,
8
+ * Low-level `<dialog>` primitive. No visual opinions - no width, height,
9
9
  * background, border-radius, or layout. Consumers provide their own styling.
10
10
  *
11
11
  * Visibility is controlled declaratively via `isOpen`:
@@ -16,5 +16,9 @@ import { type TDialogProps } from './types';
16
16
  *
17
17
  * Close flow: we never call `dialog.close()` from event handlers. We always call
18
18
  * `onClose`; the consumer decides whether to set `isOpen={false}`.
19
+ *
20
+ * Accessibility: render at least one focusable element (typically a close
21
+ * button) when `isOpen` becomes `true`. Tab is always trapped inside the
22
+ * dialog, so keyboard users need somewhere to land.
19
23
  */
20
24
  export declare const Dialog: React.ForwardRefExoticComponent<TDialogProps & React.RefAttributes<HTMLDialogElement>>;
@@ -3,23 +3,21 @@ import { type TAnimationPreset } from '../animations/types';
3
3
  /**
4
4
  * Close reasons produced by the Dialog primitive.
5
5
  *
6
- * - `'escape'` — user pressed Escape (native `cancel` event on `<dialog>`).
7
- * - `'overlay-click'` — user clicked the backdrop / overlay area.
6
+ * - `'escape'`: user pressed Escape (native `cancel` event on `<dialog>`).
7
+ * - `'overlay-click'`: user clicked the backdrop / overlay area.
8
8
  *
9
9
  * Consumer-initiated ("programmatic") close is not a reason the primitive
10
- * produces — the consumer simply sets `isOpen={false}`.
10
+ * produces. The consumer simply sets `isOpen={false}`.
11
11
  */
12
12
  export type TDialogCloseReason = 'escape' | 'overlay-click';
13
13
  /**
14
- * Props for the `Dialog` primitive.
14
+ * Props shared across `TDialogProps` variants.
15
15
  *
16
- * Low-level wrapper around the native `<dialog>` element.
17
- * Manages visibility (`showModal()` / `close()`), animation, Escape handling,
18
- * and backdrop-click detection. Has no visual opinions — no width, height,
19
- * background, border-radius, or layout. Consumers (e.g. `@atlaskit/modal-dialog`)
20
- * provide their own visual styling.
16
+ * The accessible-name requirement (one of `label` or `labelledBy`) is encoded
17
+ * in the discriminated union below - WCAG 4.1.2 violations are caught at
18
+ * compile time instead of in production audit reports.
21
19
  */
22
- export type TDialogProps = {
20
+ type TDialogBaseProps = {
23
21
  children: ReactNode;
24
22
  /**
25
23
  * Whether the dialog is open.
@@ -28,13 +26,13 @@ export type TDialogProps = {
28
26
  * - `false`: calls `close()`. When an `animate` preset is provided, the exit
29
27
  * animation plays (via `allow-discrete`) before the dialog becomes logically closed.
30
28
  *
31
- * The element stays mounted in the DOM — visibility is controlled declaratively.
29
+ * The element stays mounted in the DOM. Visibility is controlled declaratively.
32
30
  */
33
31
  isOpen: boolean;
34
32
  /**
35
33
  * Called when the user triggers close via Escape or backdrop click.
36
34
  *
37
- * The dialog does **not** close itself — the consumer decides whether to
35
+ * The dialog does **not** close itself. The consumer decides whether to
38
36
  * set `isOpen={false}` (e.g. gate by reason).
39
37
  */
40
38
  onClose: (args: {
@@ -61,32 +59,60 @@ export type TDialogProps = {
61
59
  * HTML `id` attribute for the `<dialog>` element.
62
60
  */
63
61
  id?: string;
62
+ /**
63
+ * When true, the `::backdrop` pseudo-element is rendered transparent.
64
+ *
65
+ * Useful for stacked dialogs where only the foreground dialog should
66
+ * show a visible backdrop, or when the consumer wants no overlay.
67
+ */
68
+ shouldHideBackdrop?: boolean;
69
+ /**
70
+ * Called after the exit animation completes (or immediately on close when
71
+ * there is no animation or reduced motion is active).
72
+ *
73
+ * Use this for external lifecycle coordination, e.g. firing an
74
+ * `onCloseComplete` callback or notifying ExitingPersistence.
75
+ */
76
+ onExitFinish?: () => void;
77
+ };
78
+ /**
79
+ * Props for the `Dialog` primitive.
80
+ *
81
+ * Low-level wrapper around the native `<dialog>` element.
82
+ * Manages visibility (`showModal()` / `close()`), animation, Escape handling,
83
+ * and backdrop-click detection. Has no visual opinions - no width, height,
84
+ * background, border-radius, or layout. Consumers (e.g. `@atlaskit/modal-dialog`)
85
+ * provide their own visual styling.
86
+ *
87
+ * Discriminated on the accessible-name source: at least one of `label`
88
+ * (aria-label) or `labelledBy` (aria-labelledby) must be provided. This
89
+ * enforces WCAG 4.1.2 at the type level - an unlabelled dialog landmark
90
+ * is not constructible. Mirrors the pattern used by `TPopupContentProps`.
91
+ */
92
+ export type TDialogProps = TDialogBaseProps & ({
64
93
  /**
65
94
  * Accessible label (`aria-label`).
66
95
  *
67
96
  * When provided, `aria-labelledby` is not set.
68
97
  * Use when there is no visible title element to reference.
69
98
  */
70
- label?: string;
99
+ label: string;
71
100
  /**
72
101
  * ID of the element that labels the dialog (`aria-labelledby`).
73
102
  *
74
103
  * Ignored when `label` is provided.
75
104
  */
76
105
  labelledBy?: string;
106
+ } | {
77
107
  /**
78
- * When true, the `::backdrop` pseudo-element is rendered transparent.
108
+ * Accessible label (`aria-label`).
79
109
  *
80
- * Useful for stacked dialogs where only the foreground dialog should
81
- * show a visible backdrop, or when the consumer wants no overlay.
110
+ * Optional when `labelledBy` is provided.
82
111
  */
83
- shouldHideBackdrop?: boolean;
112
+ label?: string;
84
113
  /**
85
- * Called after the exit animation completes (or immediately on close when
86
- * there is no animation or reduced motion is active).
87
- *
88
- * Use this for external lifecycle coordination — e.g. firing an
89
- * `onCloseComplete` callback or notifying ExitingPersistence.
114
+ * ID of the element that labels the dialog (`aria-labelledby`).
90
115
  */
91
- onExitFinish?: () => void;
92
- };
116
+ labelledBy: string;
117
+ });
118
+ export {};
@@ -1,3 +1,10 @@
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';
3
10
  export type { TDialogCloseReason } from '../dialog';
@@ -0,0 +1,2 @@
1
+ export { createCloseEvent } from '../../dialog/create-close-event';
2
+ export type { TDialogCloseReason } from '../../dialog/types';
@@ -1,2 +1,3 @@
1
- export { fromLegacyPlacement, placementMapping } from '../placement-map';
1
+ export { fromLegacyPlacement } from '../placement-map';
2
+ export { LEGACY_PLACEMENTS } from '../placement-map/legacy-placements';
2
3
  export type { TLegacyPlacement, TPlacementOptions } from '../placement-map';
@@ -0,0 +1,2 @@
1
+ export { createPopoverCloseEvent } from '../../popover/create-close-event';
2
+ export type { TPopoverCloseReason } from '../../popover/types';
@@ -9,6 +9,14 @@
9
9
  * All public functions accept an optional `filter` callback to further
10
10
  * restrict which focusable elements are included.
11
11
  */
12
+ /**
13
+ * Returns `true` when `document.activeElement` is inside a nested top-layer
14
+ * descendant of `container` (a `[popover]`, `<dialog>`, `[role="dialog"]` or
15
+ * `[role="alertdialog"]` that is itself a descendant of `container`).
16
+ */
17
+ export declare function isNestedLayerFocused({ container }: {
18
+ container: HTMLElement;
19
+ }): boolean;
12
20
  /**
13
21
  * An optional callback to restrict which focusable elements are included.
14
22
  * Return `true` to include the element, `false` to skip it.
@@ -8,13 +8,11 @@
8
8
  * the browser do the math.
9
9
  *
10
10
  * The probe is appended to `container` so it inherits the same containing
11
- * block, font size, and custom-property scope as the popover. We use `width`
12
- * because it accepts any CSS length, including negative `calc()` results
13
- * (negative widths are clamped to 0 — see note below).
11
+ * block, font size, and custom-property scope as the popover.
14
12
  *
15
- * Cost: one synchronous reflow per call. The fallback only resolves on
16
- * placement/offset changes, not on every scroll/resize update, so this is
17
- * not a hot path.
13
+ * Cost: one synchronous reflow on first cacheable resolution per
14
+ * (container, value) pair, then a `WeakMap` lookup. Resolutions involving
15
+ * `var(` are intentionally not cached.
18
16
  */
19
17
  export declare function resolveCssLengthToPixels({ value, container, }: {
20
18
  value: number | string;
@@ -1,8 +1,17 @@
1
1
  /**
2
2
  * Valid values for the `aria-haspopup` attribute.
3
- * Derived from the HTML spec — maps popover roles to what the trigger announces.
3
+ *
4
+ * Derived from the HTML spec - maps popover roles to what the trigger
5
+ * announces. `undefined` means the attribute is omitted entirely (used for
6
+ * non-popup roles like `tooltip`/`status`/`alert`/`note`/`log` where
7
+ * `aria-haspopup` would be misleading).
8
+ *
9
+ * `true` is intentionally NOT in the union - the runtime only ever produces
10
+ * the explicit string forms, so widening the type would invite consumers to
11
+ * pass `true` and get an `aria-haspopup="true"` serialisation that the
12
+ * runtime no longer emits.
4
13
  */
5
- export type TAriaHasPopupValue = 'dialog' | 'menu' | 'listbox' | 'tree' | 'grid' | true;
14
+ export type TAriaHasPopupValue = 'dialog' | 'menu' | 'listbox' | 'tree' | 'grid' | undefined;
6
15
  /**
7
16
  * Roles that require an accessible name (`label` or `labelledBy`) per WCAG 4.1.2.
8
17
  * Without a name, assistive technology cannot identify these landmarks.
@@ -33,7 +42,7 @@ type TAccessibleNameOptional = {
33
42
  /**
34
43
  * ARIA role props where a role is always required.
35
44
  *
36
- * Used by `TPopupContentProps` — popup content must always declare
45
+ * Used by `TPopupContentProps`. Popup content must always declare
37
46
  * its role so assistive technology can identify the layer.
38
47
  */
39
48
  export type TAriaRoleRequired = ({
@@ -44,7 +53,7 @@ export type TAriaRoleRequired = ({
44
53
  /**
45
54
  * ARIA role props where a role is optional.
46
55
  *
47
- * Used by `TPopoverProps` — the low-level primitive allows omitting
56
+ * Used by `TPopoverProps`. The low-level primitive allows omitting
48
57
  * the role for cases where it is set externally or not needed.
49
58
  */
50
59
  export type TAriaRoleOptional = ({
@@ -55,8 +64,20 @@ export type TAriaRoleOptional = ({
55
64
  /**
56
65
  * Maps a popover content role to the correct `aria-haspopup` value
57
66
  * for its trigger element.
67
+ *
68
+ * Returns `undefined` for roles that are not popups in the ARIA sense
69
+ * (`tooltip`, `note`, `status`, `alert`, `log`). Triggers for those roles
70
+ * should not announce a popup at all - emitting `aria-haspopup="dialog"`
71
+ * on a tooltip trigger would mislead assistive technology.
58
72
  */
59
73
  export declare function roleToAriaHasPopup({ role, }: {
60
74
  role: TRoleRequiringAccessibleName | TRoleWithImplicitName | undefined;
61
75
  }): TAriaHasPopupValue;
76
+ /**
77
+ * Returns `true` when the given popover role moves focus into the popover
78
+ * on open, and `false` otherwise (including when `role` is `undefined`).
79
+ */
80
+ export declare function shouldFocusIntoPopover({ role, }: {
81
+ role: TRoleRequiringAccessibleName | TRoleWithImplicitName | undefined;
82
+ }): boolean;
62
83
  export {};
@@ -1,23 +1,6 @@
1
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
- * ```
2
+ * Sets inline styles on an element and returns a cleanup function that
3
+ * restores the prior inline values (so we do not stomp consumer styles).
21
4
  */
22
5
  export declare function setStyle({ el, styles, }: {
23
6
  el: HTMLElement;
@@ -14,7 +14,6 @@
14
14
  */
15
15
  import { type RefObject } from 'react';
16
16
  import { type TPlacementOptions } from '../internal/resolve-placement';
17
- export { getPlacement } from './resolve-placement';
18
17
  /**
19
18
  * Resolves placement options to CSS `position-area` syntax.
20
19
  *
@@ -59,7 +58,7 @@ export declare function placementToPositionArea({ placement }: {
59
58
  * top-right), flipping only the primary axis (`flip-inline`) moves the popup
60
59
  * to the left but keeps the same cross-axis span direction (e.g. upward),
61
60
  * which is still off-screen. The combined keyword `flip-inline flip-block`
62
- * mirrors the popup diagonally — it flips BOTH the position-area AND both
61
+ * mirrors the popup diagonally. It flips BOTH the position-area AND both
63
62
  * inline and block margins simultaneously, placing the popup at the
64
63
  * diagonally opposite corner where there is always space.
65
64
  *
@@ -74,7 +73,7 @@ export declare function placementToTryFallbacks({ placement }: {
74
73
  * Hook that positions an element relative to an anchor element using
75
74
  * CSS Anchor Positioning (with a JS fallback for older browsers).
76
75
  *
77
- * This hook is the positioning primitive — it has no knowledge of popovers,
76
+ * This hook is the positioning primitive. It has no knowledge of popovers,
78
77
  * visibility, or animation. Compose it with `Popover` for anchor-positioned
79
78
  * top-layer content.
80
79
  *
@@ -2,14 +2,15 @@ import { type RefObject } from 'react';
2
2
  /**
3
3
  * Moves focus into a popover element when it opens, based on its ARIA role.
4
4
  *
5
- * This implements WCAG 2.4.3 (Focus Order) initial focus placement:
5
+ * Follows the WAI-ARIA APG initial-focus patterns for each role:
6
6
  * - `dialog` / `alertdialog` → focus first focusable (or `[autofocus]`)
7
7
  * - `menu` → focus first menu item
8
8
  * - `listbox` → focus selected or first option
9
- * - `tooltip` / no role → no focus movement
9
+ * - `tooltip` / no role → no focus movement (focus must remain on trigger;
10
+ * moving focus into the tooltip would dismiss it)
10
11
  *
11
- * The role is read from the element's `role` attribute (not passed as a prop),
12
- * following the same pattern as `useFocusWrap`.
12
+ * Role is taken from the typed `role` prop - this is the consumer's contract
13
+ * and is not subject to drift from a directly-mutated DOM attribute.
13
14
  *
14
15
  * Always call this hook unconditionally. Focus is only moved when `isOpen`
15
16
  * transitions to `true` and the role requires it.
@@ -1,9 +1,5 @@
1
1
  import { type TPlacementOptions } from '../internal/resolve-placement';
2
- /**
3
- * Legacy Popper.js placement values (used by `@atlaskit/popper`, `@atlaskit/popup`,
4
- * `@atlaskit/tooltip`, etc.).
5
- */
6
- export type TLegacyPlacement = 'auto' | 'auto-start' | 'auto-end' | 'top' | 'top-start' | 'top-center' | 'top-end' | 'bottom' | 'bottom-start' | 'bottom-center' | 'bottom-end' | 'right' | 'right-start' | 'right-end' | 'left' | 'left-start' | 'left-end';
2
+ import { type TLegacyPlacement } from './legacy-placements';
7
3
  /**
8
4
  * Maps legacy Popper.js placement values to the new object-based
9
5
  * `Placement` values used by `@atlaskit/top-layer/popover`.
@@ -14,9 +10,11 @@ export type TLegacyPlacement = 'auto' | 'auto-start' | 'auto-end' | 'top' | 'top
14
10
  * | ------------------- | --------------------------------------------------- |
15
11
  * | `top` | `{ axis: 'block', edge: 'start', align: 'center' }` |
16
12
  * | `top-start` | `{ axis: 'block', edge: 'start', align: 'start' }` |
13
+ * | `top-center` | `{ axis: 'block', edge: 'start', align: 'center' }` |
17
14
  * | `top-end` | `{ axis: 'block', edge: 'start', align: 'end' }` |
18
15
  * | `bottom` | `{ axis: 'block', edge: 'end', align: 'center' }` |
19
16
  * | `bottom-start` | `{ axis: 'block', edge: 'end', align: 'start' }` |
17
+ * | `bottom-center` | `{ axis: 'block', edge: 'end', align: 'center' }` |
20
18
  * | `bottom-end` | `{ axis: 'block', edge: 'end', align: 'end' }` |
21
19
  * | `right` | `{ axis: 'inline', edge: 'end', align: 'center' }` |
22
20
  * | `right-start` | `{ axis: 'inline', edge: 'end', align: 'start' }` |
@@ -28,7 +26,10 @@ export type TLegacyPlacement = 'auto' | 'auto-start' | 'auto-end' | 'top' | 'top
28
26
  * | `auto-start` | `{ axis: 'block', edge: 'end', align: 'start' }` |
29
27
  * | `auto-end` | `{ axis: 'block', edge: 'end', align: 'end' }` |
30
28
  *
31
- * The `position-try-fallbacks` CSS property handles flipping automatically.
29
+ * `auto*` is mapped to `block-end` (a reduction in expressiveness vs Popper's
30
+ * runtime auto-flip). CSS `position-try-fallbacks` provides flipping at the
31
+ * edge - but the initial placement is fixed. If migrating away from Popper's
32
+ * dynamic auto, prefer an explicit placement.
32
33
  *
33
34
  * @example
34
35
  * ```ts
@@ -40,22 +41,8 @@ export type TLegacyPlacement = 'auto' | 'auto-start' | 'auto-end' | 'top' | 'top
40
41
  * // newPlacement === { axis: 'block', edge: 'end', align: 'start' }
41
42
  * ```
42
43
  */
43
- declare const placementMapping: Record<TLegacyPlacement, TPlacementOptions>;
44
- /**
45
- * Convert a legacy Popper.js placement to the new `position-area`-based Placement.
46
- * Optional `offset` is the legacy Popper `[along, away]` tuple (also known as
47
- * `[skidding, distance]`); negative `along` becomes `direction: 'backwards'`.
48
- *
49
- * The tuple keeps the historical popper names so call sites that already pass
50
- * `[along, away]` migrate without thinking; the new internal field names
51
- * (`gap` / `crossAxisShift`) are produced inside this adapter.
52
- */
53
44
  export declare function fromLegacyPlacement({ legacy, offset, }: {
54
45
  legacy: TLegacyPlacement;
55
46
  offset?: [along: number, away: number];
56
47
  }): TPlacementOptions;
57
- /**
58
- * The full mapping record, exposed for consumers that need to iterate or introspect.
59
- */
60
- export { placementMapping };
61
- export type { TPlacementOptions };
48
+ export type { TLegacyPlacement, TPlacementOptions };
@@ -0,0 +1,21 @@
1
+ import { type TPlacementOptions } from '../internal/resolve-placement';
2
+ /**
3
+ * Legacy Popper.js placement values (used by `@atlaskit/popper`,
4
+ * `@atlaskit/popup`, `@atlaskit/tooltip`, etc.). Re-exported from
5
+ * `@atlaskit/top-layer/placement-map` for migration consumers that need
6
+ * to enumerate every placement (e.g. for a settings dropdown). New code
7
+ * should use the object-based `TPlacementOptions` directly.
8
+ */
9
+ export declare const LEGACY_PLACEMENTS: readonly ["auto", "auto-start", "auto-end", "top", "top-start", "top-center", "top-end", "bottom", "bottom-start", "bottom-center", "bottom-end", "right", "right-start", "right-end", "left", "left-start", "left-end"];
10
+ export type TLegacyPlacement = (typeof LEGACY_PLACEMENTS)[number];
11
+ /**
12
+ * The full mapping record. Internal - use `fromLegacyPlacement` from the
13
+ * public entry-point so the offset-tuple translation is applied consistently.
14
+ *
15
+ * Note: `auto`, `auto-start`, `auto-end` map to `block-end` placements. This
16
+ * is a reduction in expressiveness vs Popper.js (which picked the best
17
+ * placement at runtime). CSS `position-try-fallbacks` flips at the edge but
18
+ * the initial placement is fixed. Migration consumers who relied on Popper's
19
+ * dynamic auto-flip should pick an explicit placement instead.
20
+ */
21
+ export declare const placementMapping: Record<TLegacyPlacement, TPlacementOptions>;
@@ -18,7 +18,7 @@ import { type TPopoverForwardedProps } from './types';
18
18
  *
19
19
  * For `mode="auto"`, the browser can dismiss the popover via light dismiss
20
20
  * (Escape, click outside). When that happens, `onClose` is called and the
21
- * consumer must set `isOpen` to `false`. The DOM owns the dismiss — React
21
+ * consumer must set `isOpen` to `false`. The DOM owns the dismiss; React
22
22
  * must follow.
23
23
  */
24
24
  export declare const Popover: React.ForwardRefExoticComponent<TPopoverForwardedProps & React.RefAttributes<HTMLDivElement>>;