@atlaskit/top-layer 0.9.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 (127) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/README.md +5 -1
  3. package/dialog/create-close-event/package.json +17 -0
  4. package/dist/cjs/animations/presets.js +7 -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 +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 +6 -3
  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 +6 -3
  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
package/CHANGELOG.md CHANGED
@@ -1,5 +1,31 @@
1
1
  # @atlaskit/top-layer
2
2
 
3
+ ## 0.10.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [`dec4f3444bce8`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/dec4f3444bce8) -
8
+ Hardens Popup, Popover, and Dialog through stricter typed contracts, focus-management fixes, and
9
+ clearer entry points.
10
+ - **Stricter API contracts.** Popup, Popover, and Dialog props are now discriminated unions, so
11
+ misuse (manual mode with `onClose`, dialogs without a label, popovers without an accessible name
12
+ for their role) fails at compile time.
13
+ - **Better defaults.** `Popup`'s `placement` prop now defaults to "below trigger, centered, with
14
+ `space.100` gap", removing the awkward `placement={{}}` boilerplate.
15
+ - **Focus-management fixes.** Empty/loading dialogs no longer trap Tab on `<body>`; rapid
16
+ open/close/open sequences no longer focus a torn-down popover; nested top-layer popovers stay in
17
+ their own focus scope.
18
+ - **Cleaner entry points.** `createCloseEvent` is now available as per-primitive subpaths
19
+ (`@atlaskit/top-layer/dialog/create-close-event` and
20
+ `@atlaskit/top-layer/popover/create-close-event`); the combined entry point is deprecated. The
21
+ placement-map entry point now exposes a public `LEGACY_PLACEMENTS` const for migration
22
+ consumers.
23
+ - **Per-primitive data attributes.** Animation hooks now emit `data-ds-popover-*` for Popover and
24
+ `data-ds-dialog-*` for Dialog, preventing cross-primitive selector collisions.
25
+ - **Performance and internal correctness.** `setStyle` snapshots and restores prior inline values;
26
+ CSS-length resolution is memoised and probes inside the popover for correct token scope;
27
+ placement resolution is stable across renders.
28
+
3
29
  ## 0.9.0
4
30
 
5
31
  ### Minor Changes
package/README.md CHANGED
@@ -11,7 +11,7 @@ Placement is owned by this compound: root provides it via context, Content appli
11
11
  Positioning or JS fallback).
12
12
 
13
13
  ```tsx
14
- <Popup placement="block-end" onClose={handleClose}>
14
+ <Popup onClose={handleClose}>
15
15
  <Popup.Trigger>
16
16
  <button>Open</button>
17
17
  </Popup.Trigger>
@@ -21,6 +21,10 @@ Positioning or JS fallback).
21
21
  </Popup>
22
22
  ```
23
23
 
24
+ `placement` defaults to `{}` - "below trigger, centered, with `space.100` gap".
25
+ Pass an explicit object to override (e.g. `placement={{ axis: 'inline', edge: 'end' }}`
26
+ for a popover anchored to the trigger's right edge).
27
+
24
28
  For default overlay styling (background, shadow, border-radius), see `PopupSurface` in the package
25
29
  examples (not currently a public API; we may expose it in a future release).
26
30
 
@@ -0,0 +1,17 @@
1
+ {
2
+ "name": "@atlaskit/top-layer/dialog/create-close-event",
3
+ "main": "../../dist/cjs/entry-points/dialog/create-close-event.js",
4
+ "module": "../../dist/esm/entry-points/dialog/create-close-event.js",
5
+ "module:es2019": "../../dist/es2019/entry-points/dialog/create-close-event.js",
6
+ "sideEffects": [
7
+ "*.compiled.css"
8
+ ],
9
+ "types": "../../dist/types/entry-points/dialog/create-close-event.d.ts",
10
+ "typesVersions": {
11
+ ">=4.5 <5.9": {
12
+ "*": [
13
+ "../../dist/types-ts4.5/entry-points/dialog/create-close-event.d.ts"
14
+ ]
15
+ }
16
+ }
17
+ }
@@ -8,7 +8,7 @@ exports.dialogSlideUpAndFade = dialogSlideUpAndFade;
8
8
  exports.fade = fade;
9
9
  exports.scaleAndFade = scaleAndFade;
10
10
  exports.slideAndFade = slideAndFade;
11
- var _useAnchorPosition = require("../internal/use-anchor-position");
11
+ var _resolvePlacement = require("../internal/resolve-placement");
12
12
  // ── slideAndFade ──
13
13
 
14
14
  function getSlideAndFadeProperties(_ref) {
@@ -16,7 +16,7 @@ function getSlideAndFadeProperties(_ref) {
16
16
  distance = _ref.distance;
17
17
  var pos = "".concat(distance, "px");
18
18
  var neg = "-".concat(distance, "px");
19
- var _getPlacement = (0, _useAnchorPosition.getPlacement)({
19
+ var _getPlacement = (0, _resolvePlacement.getPlacement)({
20
20
  placement: placement
21
21
  }),
22
22
  axis = _getPlacement.axis,
@@ -168,8 +168,10 @@ var DIALOG_SLIDE_UP_AND_FADE_CSS = "\n[data-ds-dialog-slide-up-and-fade] {\n op
168
168
  function dialogSlideUpAndFade(options) {
169
169
  var _options$distance2;
170
170
  var distance = (_options$distance2 = options === null || options === void 0 ? void 0 : options.distance) !== null && _options$distance2 !== void 0 ? _options$distance2 : 12;
171
+
172
+ // Bare preset name; `Dialog` prefixes it via `data-ds-dialog-{name}`.
171
173
  return {
172
- name: distance === 12 ? 'dialog-slide-up-and-fade' : "dialog-slide-up-and-fade-".concat(distance),
174
+ name: distance === 12 ? 'slide-up-and-fade' : "slide-up-and-fade-".concat(distance),
173
175
  css: distance === 12 ? DIALOG_SLIDE_UP_AND_FADE_CSS : DIALOG_SLIDE_UP_AND_FADE_CSS.replace(/12px/g, "".concat(distance, "px")),
174
176
  exitDurationMs: 175
175
177
  };
@@ -195,8 +197,9 @@ var DIALOG_FADE_CSS = "\n[data-ds-dialog-fade] {\n opacity: 0;\n transition:\n
195
197
  */
196
198
  // eslint-disable-next-line @atlaskit/volt-strict-mode/no-multiple-exports
197
199
  function dialogFade() {
200
+ // Bare preset name; `Dialog` prefixes it via `data-ds-dialog-{name}`.
198
201
  return {
199
- name: 'dialog-fade',
202
+ name: 'fade',
200
203
  css: DIALOG_FADE_CSS,
201
204
  exitDurationMs: 175
202
205
  };
@@ -0,0 +1,131 @@
1
+ "use strict";
2
+
3
+ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
4
+ Object.defineProperty(exports, "__esModule", {
5
+ value: true
6
+ });
7
+ exports.arrow = void 0;
8
+ var _once = _interopRequireDefault(require("@atlaskit/ds-lib/once"));
9
+ var _resolvePlacement = require("../internal/resolve-placement");
10
+ var _useAnchorPosition = require("../internal/use-anchor-position");
11
+ /**
12
+ * CSS for popover arrows using the `clip-path: inset() margin-box` technique.
13
+ *
14
+ * How it works:
15
+ * 1. Four arrow shapes exist simultaneously as `::before` (top/bottom) and
16
+ * `::after` (left/right) hexagonal pseudo-elements.
17
+ * 2. `clip-path: inset(0) margin-box` clips the popover to its margin-box.
18
+ * 3. Margin on the anchor-facing side pushes the popover away, creating space
19
+ * where one arrow "escapes" the clip boundary and becomes visible.
20
+ * 4. `@position-try` rules change both `position-area` and `margin` when
21
+ * flipping, ensuring the correct arrow is always shown.
22
+ *
23
+ * Constraints:
24
+ * - `box-shadow` must be `none` (interferes with `clip-path: inset() margin-box`)
25
+ * - `::before` and `::after` are consumed - consumers cannot use them
26
+ * - Arrow pseudo-elements use `background: inherit` from the popover element
27
+ *
28
+ * @see https://goulet.dev/posts/tooltip-with-popover-and-anchor-positioning/
29
+ */
30
+ 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";
31
+
32
+ // ── Placement utilities ──
33
+
34
+ /**
35
+ * Converts a `position-area` value to a CSS rule name by replacing spaces with hyphens
36
+ * and prepending `--ds-arrow-`.
37
+ *
38
+ * @example positionAreaToRuleName('block-end span-inline-start') → '--ds-arrow-block-end-span-inline-start'
39
+ */
40
+ function positionAreaToRuleName(_ref) {
41
+ var positionArea = _ref.positionArea;
42
+ return "--ds-arrow-".concat(positionArea.replace(/ /g, '-'));
43
+ }
44
+
45
+ /**
46
+ * Returns `position-try-fallbacks` using named `@position-try` rules
47
+ * that change both `position-area` and `margin` when flipping.
48
+ *
49
+ * This is required for the arrow technique because built-in keywords
50
+ * (`flip-block`, `flip-inline`) only flip `position-area` - they do
51
+ * not update the margin direction needed to reveal the correct arrow.
52
+ */
53
+ function getTryFallbacks(_ref2) {
54
+ var placement = _ref2.placement;
55
+ var _getPlacement = (0, _resolvePlacement.getPlacement)({
56
+ placement: placement
57
+ }),
58
+ axis = _getPlacement.axis,
59
+ edge = _getPlacement.edge,
60
+ align = _getPlacement.align;
61
+ if (align === 'center') {
62
+ // Single-axis: only flip the edge
63
+ var _flippedEdge = edge === 'start' ? 'end' : 'start';
64
+ return positionAreaToRuleName({
65
+ positionArea: (0, _useAnchorPosition.placementToPositionArea)({
66
+ placement: {
67
+ axis: axis,
68
+ edge: _flippedEdge
69
+ }
70
+ })
71
+ });
72
+ }
73
+
74
+ // Compound placement: try flipping edge, alignment, and both
75
+ var flippedEdge = edge === 'start' ? 'end' : 'start';
76
+ var flippedAlign = align === 'start' ? 'end' : 'start';
77
+ return [positionAreaToRuleName({
78
+ positionArea: (0, _useAnchorPosition.placementToPositionArea)({
79
+ placement: {
80
+ axis: axis,
81
+ edge: flippedEdge,
82
+ align: align
83
+ }
84
+ })
85
+ }), positionAreaToRuleName({
86
+ positionArea: (0, _useAnchorPosition.placementToPositionArea)({
87
+ placement: {
88
+ axis: axis,
89
+ edge: edge,
90
+ align: flippedAlign
91
+ }
92
+ })
93
+ }), positionAreaToRuleName({
94
+ positionArea: (0, _useAnchorPosition.placementToPositionArea)({
95
+ placement: {
96
+ axis: axis,
97
+ edge: flippedEdge,
98
+ align: flippedAlign
99
+ }
100
+ })
101
+ })].join(', ');
102
+ }
103
+
104
+ /**
105
+ * Creates an arrow preset for popover content.
106
+ *
107
+ * The returned object is passed to `PopoverContent`'s `arrow` prop.
108
+ * It carries the CSS string and fallback logic so that consumers who
109
+ * do not use arrows pay no bundle cost.
110
+ *
111
+ * The arrows inherit their background from the popover element.
112
+ * When `arrow` is active, `PopoverContent` does not reset `background`
113
+ * to `transparent`, allowing the UA default (`canvas`) or a consumer-set
114
+ * background to show through.
115
+ *
116
+ * @example
117
+ * ```tsx
118
+ * import { arrow } from '@atlaskit/top-layer/arrow';
119
+ *
120
+ * const myArrow = arrow();
121
+ *
122
+ * <Popup.Content arrow={myArrow} />
123
+ * ```
124
+ */
125
+ var arrow = exports.arrow = (0, _once.default)(function () {
126
+ return {
127
+ name: 'arrow',
128
+ css: ARROW_CSS,
129
+ getTryFallbacks: getTryFallbacks
130
+ };
131
+ });
@@ -0,0 +1,5 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
@@ -25,7 +25,7 @@ var styles = {
25
25
  };
26
26
 
27
27
  /**
28
- * Low-level `<dialog>` primitive. No visual opinions — no width, height,
28
+ * Low-level `<dialog>` primitive. No visual opinions - no width, height,
29
29
  * background, border-radius, or layout. Consumers provide their own styling.
30
30
  *
31
31
  * Visibility is controlled declaratively via `isOpen`:
@@ -36,6 +36,10 @@ var styles = {
36
36
  *
37
37
  * Close flow: we never call `dialog.close()` from event handlers. We always call
38
38
  * `onClose`; the consumer decides whether to set `isOpen={false}`.
39
+ *
40
+ * Accessibility: render at least one focusable element (typically a close
41
+ * button) when `isOpen` becomes `true`. Tab is always trapped inside the
42
+ * dialog, so keyboard users need somewhere to land.
39
43
  */
40
44
  var Dialog = exports.Dialog = /*#__PURE__*/(0, _react.forwardRef)(function Dialog(_ref, ref) {
41
45
  var children = _ref.children,
@@ -141,18 +145,19 @@ var Dialog = exports.Dialog = /*#__PURE__*/(0, _react.forwardRef)(function Dialo
141
145
  return /*#__PURE__*/_react.default.createElement("dialog", (0, _extends2.default)({
142
146
  ref: combinedRef,
143
147
  id: dialogId
144
- // Modern browsers infer modal semantics from `.showModal()` but
145
- // some assistive tech still keys off `aria-modal="true"`. Setting
146
- // it explicitly is recommended by WAI-ARIA APG dialog pattern.
148
+ // `aria-modal` is intentionally NOT set: native `<dialog>.showModal()`
149
+ // already conveys modal semantics to assistive tech, and double-
150
+ // declaring it forecloses non-modal use cases (consumers calling
151
+ // `.show()` would still appear modal). Modern AT (NVDA / JAWS /
152
+ // VoiceOver) infer modality from the platform accessibility API.
147
153
  ,
148
- "aria-modal": "true",
149
154
  "aria-label": label,
150
155
  "aria-labelledby": label ? undefined : labelledBy,
151
156
  // eslint-disable-next-line @atlaskit/ui-styling-standard/enforce-style-prop
152
157
  style: style,
153
158
  onCancel: handleCancel,
154
159
  "data-testid": testId
155
- }, preset ? (0, _defineProperty2.default)({}, "data-ds-".concat(preset.name), '') : undefined, {
160
+ }, preset ? (0, _defineProperty2.default)({}, "data-ds-dialog-".concat(preset.name), '') : undefined, {
156
161
  className: (0, _runtime.ax)([styles.dialog])
157
162
  }), shouldHideBackdrop &&
158
163
  /*#__PURE__*/
@@ -0,0 +1,12 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ Object.defineProperty(exports, "createCloseEvent", {
7
+ enumerable: true,
8
+ get: function get() {
9
+ return _createCloseEvent.createCloseEvent;
10
+ }
11
+ });
12
+ var _createCloseEvent = require("../../dialog/create-close-event");
@@ -3,16 +3,17 @@
3
3
  Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
- Object.defineProperty(exports, "fromLegacyPlacement", {
6
+ Object.defineProperty(exports, "LEGACY_PLACEMENTS", {
7
7
  enumerable: true,
8
8
  get: function get() {
9
- return _placementMap.fromLegacyPlacement;
9
+ return _legacyPlacements.LEGACY_PLACEMENTS;
10
10
  }
11
11
  });
12
- Object.defineProperty(exports, "placementMapping", {
12
+ Object.defineProperty(exports, "fromLegacyPlacement", {
13
13
  enumerable: true,
14
14
  get: function get() {
15
- return _placementMap.placementMapping;
15
+ return _placementMap.fromLegacyPlacement;
16
16
  }
17
17
  });
18
- var _placementMap = require("../placement-map");
18
+ var _placementMap = require("../placement-map");
19
+ var _legacyPlacements = require("../placement-map/legacy-placements");
@@ -0,0 +1,12 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ Object.defineProperty(exports, "createPopoverCloseEvent", {
7
+ enumerable: true,
8
+ get: function get() {
9
+ return _createCloseEvent.createPopoverCloseEvent;
10
+ }
11
+ });
12
+ var _createCloseEvent = require("../../popover/create-close-event");
@@ -6,6 +6,8 @@ Object.defineProperty(exports, "__esModule", {
6
6
  exports.getFirstFocusable = getFirstFocusable;
7
7
  exports.getLastFocusable = getLastFocusable;
8
8
  exports.getNextFocusable = getNextFocusable;
9
+ exports.isNestedLayerFocused = isNestedLayerFocused;
10
+ var _browserApis = require("@atlaskit/browser-apis");
9
11
  /**
10
12
  * Focusable element selectors and navigation helpers.
11
13
  *
@@ -25,9 +27,54 @@ var selectors = function () {
25
27
  var focusable = ["a[href]".concat(not), "area[href]".concat(not), "input".concat(notDisabled, ":not([type=\"hidden\"])"), "select".concat(notDisabled), "textarea".concat(notDisabled), "button".concat(notDisabled), "iframe".concat(not), "audio[controls]".concat(not), "video[controls]".concat(not), "[contenteditable]:not([contenteditable=\"false\"])".concat(not), "[tabindex]".concat(notDisabled).concat(not)];
26
28
  return {
27
29
  focusable: focusable.join(','),
28
- focused: focusable.join(':focus,') + ':focus'
30
+ // Build the `:focus` selector
31
+ focused: focusable.map(function (selector) {
32
+ return "".concat(selector, ":focus");
33
+ }).join(',')
29
34
  };
30
35
  }();
36
+ var topLayerSelector = '[popover], dialog, [role="dialog"], [role="alertdialog"]';
37
+
38
+ /**
39
+ * Returns true when `element` and `container` resolve to the same nearest
40
+ * top-layer host (or both resolve to `null`, meaning the document layer).
41
+ */
42
+ function isInSameLayer(_ref) {
43
+ var element = _ref.element,
44
+ container = _ref.container;
45
+ var elementLayer = element.closest(topLayerSelector);
46
+ var containerLayer = container.closest(topLayerSelector);
47
+ // Both elements are in the "document" layer
48
+ if (!elementLayer && !containerLayer) {
49
+ return true;
50
+ }
51
+ return elementLayer === containerLayer;
52
+ }
53
+
54
+ /**
55
+ * Returns `true` when `document.activeElement` is inside a nested top-layer
56
+ * descendant of `container` (a `[popover]`, `<dialog>`, `[role="dialog"]` or
57
+ * `[role="alertdialog"]` that is itself a descendant of `container`).
58
+ */
59
+ function isNestedLayerFocused(_ref2) {
60
+ var _getDocument;
61
+ var container = _ref2.container;
62
+ var focused = (_getDocument = (0, _browserApis.getDocument)()) === null || _getDocument === void 0 ? void 0 : _getDocument.activeElement;
63
+ if (!(focused instanceof HTMLElement)) {
64
+ return false;
65
+ }
66
+
67
+ // Cannot be in a nested layer if it is not in the container
68
+ if (!container.contains(focused)) {
69
+ return false;
70
+ }
71
+
72
+ // If not in the same layer, must be in a nested layer
73
+ return !isInSameLayer({
74
+ element: focused,
75
+ container: container
76
+ });
77
+ }
31
78
 
32
79
  /**
33
80
  * An optional callback to restrict which focusable elements are included.
@@ -37,17 +84,21 @@ var selectors = function () {
37
84
  /**
38
85
  * Returns all focusable HTMLElements within the container, optionally filtered.
39
86
  *
40
- * Note: this searches the full subtree and does not stop at nested
41
- * popover/dialog boundaries. In practice this is fine because nested
42
- * popovers are not rendered until they are opened, so their content
43
- * is not in the DOM when the parent's initial focus runs. If that
44
- * assumption changes, this would need to be scoped to the current layer.
87
+ * Focusables that belong to a nested top-layer scope (a `[popover]` or
88
+ * `<dialog>` descendant of the container) are excluded - those elements
89
+ * are owned by the inner layer's focus management. The container itself
90
+ * is allowed to be a popover/dialog; only nested ones are filtered.
45
91
  */
46
- function getFocusables(_ref) {
47
- var container = _ref.container,
48
- filter = _ref.filter;
92
+ function getFocusables(_ref3) {
93
+ var container = _ref3.container,
94
+ filter = _ref3.filter;
49
95
  return Array.from(container.querySelectorAll(selectors.focusable)).filter(function (element) {
50
96
  return element instanceof HTMLElement;
97
+ }).filter(function (element) {
98
+ return isInSameLayer({
99
+ element: element,
100
+ container: container
101
+ });
51
102
  }).filter(function (element) {
52
103
  return !filter || filter(element, container);
53
104
  });
@@ -56,9 +107,10 @@ function getFocusables(_ref) {
56
107
  /**
57
108
  * Returns the first focusable element within the container.
58
109
  */
59
- function getFirstFocusable(_ref2) {
60
- var container = _ref2.container,
61
- filter = _ref2.filter;
110
+ // eslint-disable-next-line @atlaskit/volt-strict-mode/no-multiple-exports
111
+ function getFirstFocusable(_ref4) {
112
+ var container = _ref4.container,
113
+ filter = _ref4.filter;
62
114
  var items = getFocusables({
63
115
  container: container,
64
116
  filter: filter
@@ -70,9 +122,9 @@ function getFirstFocusable(_ref2) {
70
122
  * Returns the last focusable element within the container.
71
123
  */
72
124
  // eslint-disable-next-line @atlaskit/volt-strict-mode/no-multiple-exports
73
- function getLastFocusable(_ref3) {
74
- var container = _ref3.container,
75
- filter = _ref3.filter;
125
+ function getLastFocusable(_ref5) {
126
+ var container = _ref5.container,
127
+ filter = _ref5.filter;
76
128
  var items = getFocusables({
77
129
  container: container,
78
130
  filter: filter
@@ -88,11 +140,11 @@ function getLastFocusable(_ref3) {
88
140
  * the focused element is not in the list of focusable elements.
89
141
  */
90
142
  // eslint-disable-next-line @atlaskit/volt-strict-mode/no-multiple-exports
91
- function getNextFocusable(_ref4) {
143
+ function getNextFocusable(_ref6) {
92
144
  var _all$nextIndex;
93
- var container = _ref4.container,
94
- direction = _ref4.direction,
95
- filter = _ref4.filter;
145
+ var container = _ref6.container,
146
+ direction = _ref6.direction,
147
+ filter = _ref6.filter;
96
148
  var all = getFocusables({
97
149
  container: container,
98
150
  filter: filter
package/dist/cjs/index.js CHANGED
@@ -1,8 +1,7 @@
1
1
  // This file exists for module resolution purposes.
2
2
  // Not exporting anything from the root entry point.
3
- // Please use individual entry points for minimum bundle size:
4
3
  //
5
- // import { Popup } from '@atlaskit/top-layer/popup';
6
- // import { Dialog } from '@atlaskit/top-layer/dialog';
7
- // import { Popover } from '@atlaskit/top-layer/popover';
4
+ // Use individual entry points for minimum bundle size - the full list of
5
+ // available subpaths is the `"exports"` field in this package's
6
+ // `package.json`. The README documents the most common ones.
8
7
  "use strict";
@@ -4,6 +4,35 @@ Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
6
  exports.resolveCssLengthToPixels = resolveCssLengthToPixels;
7
+ var elementCaches = new WeakMap();
8
+ function canCacheResolvedLength(value) {
9
+ // Custom-property values can be retargeted by inline overrides on the
10
+ // consumer's wrapper, so we cannot prove a cached pixel value is still valid.
11
+ return !value.includes('var(');
12
+ }
13
+ function getCachedResolvedLength(_ref) {
14
+ var _cacheForElement$get;
15
+ var element = _ref.element,
16
+ value = _ref.value;
17
+ if (!canCacheResolvedLength(value)) {
18
+ return null;
19
+ }
20
+ var cacheForElement = elementCaches.get(element);
21
+ return (_cacheForElement$get = cacheForElement === null || cacheForElement === void 0 ? void 0 : cacheForElement.get(value)) !== null && _cacheForElement$get !== void 0 ? _cacheForElement$get : null;
22
+ }
23
+ function cacheResolvedLength(_ref2) {
24
+ var _elementCaches$get;
25
+ var element = _ref2.element,
26
+ value = _ref2.value,
27
+ result = _ref2.result;
28
+ if (!canCacheResolvedLength(value)) {
29
+ return;
30
+ }
31
+ var cacheForElement = (_elementCaches$get = elementCaches.get(element)) !== null && _elementCaches$get !== void 0 ? _elementCaches$get : new Map();
32
+ cacheForElement.set(value, result);
33
+ elementCaches.set(element, cacheForElement);
34
+ }
35
+
7
36
  /**
8
37
  * Resolves a CSS length value to a pixel number using a hidden DOM probe.
9
38
  *
@@ -14,27 +43,39 @@ exports.resolveCssLengthToPixels = resolveCssLengthToPixels;
14
43
  * the browser do the math.
15
44
  *
16
45
  * The probe is appended to `container` so it inherits the same containing
17
- * block, font size, and custom-property scope as the popover. We use `width`
18
- * because it accepts any CSS length, including negative `calc()` results
19
- * (negative widths are clamped to 0 — see note below).
46
+ * block, font size, and custom-property scope as the popover.
20
47
  *
21
- * Cost: one synchronous reflow per call. The fallback only resolves on
22
- * placement/offset changes, not on every scroll/resize update, so this is
23
- * not a hot path.
48
+ * Cost: one synchronous reflow on first cacheable resolution per
49
+ * (container, value) pair, then a `WeakMap` lookup. Resolutions involving
50
+ * `var(` are intentionally not cached.
24
51
  */
25
- function resolveCssLengthToPixels(_ref) {
26
- var value = _ref.value,
27
- container = _ref.container;
52
+ function resolveCssLengthToPixels(_ref3) {
53
+ var value = _ref3.value,
54
+ container = _ref3.container;
28
55
  if (typeof value === 'number') {
29
56
  return value;
30
57
  }
31
58
 
59
+ // Fast path: unitless zero is universally zero pixels - accept `'0'` and
60
+ // `'-0'` without a probe. (Consumers occasionally pass `'0'` instead of
61
+ // the number `0`.)
62
+ if (value === '0' || value === '-0') {
63
+ return 0;
64
+ }
65
+
32
66
  // Fast path: bare `${n}px` strings come from the consumer normalisation
33
67
  // in `getPlacement`. Avoid a reflow when we can.
34
68
  var pxMatch = value.match(/^(-?\d+(?:\.\d+)?)px$/);
35
69
  if (pxMatch) {
36
70
  return Number(pxMatch[1]);
37
71
  }
72
+ var cached = getCachedResolvedLength({
73
+ element: container,
74
+ value: value
75
+ });
76
+ if (cached !== null) {
77
+ return cached;
78
+ }
38
79
  var probe = container.ownerDocument.createElement('div');
39
80
  probe.style.position = 'absolute';
40
81
  probe.style.visibility = 'hidden';
@@ -46,8 +87,18 @@ function resolveCssLengthToPixels(_ref) {
46
87
  // preserved. `getComputedStyle` returns the resolved pixel value for
47
88
  // margin-* properties even when the source was a token / calc / var.
48
89
  probe.style.marginLeft = value;
90
+ // Hide the probe from assistive tech and event targeting; defensive
91
+ // against `getElementsByTagName('div')` walks in consumer code.
92
+ probe.setAttribute('aria-hidden', 'true');
93
+ probe.setAttribute('inert', '');
49
94
  container.appendChild(probe);
50
95
  var resolved = parseFloat(getComputedStyle(probe).marginLeft);
51
96
  container.removeChild(probe);
52
- return Number.isFinite(resolved) ? resolved : 0;
97
+ var result = Number.isFinite(resolved) ? resolved : 0;
98
+ cacheResolvedLength({
99
+ element: container,
100
+ value: value,
101
+ result: result
102
+ });
103
+ return result;
53
104
  }