@atlaskit/top-layer 0.17.0 → 1.0.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 (132) hide show
  1. package/AGENTS.md +113 -0
  2. package/CHANGELOG.md +33 -0
  3. package/animations/package.json +1 -8
  4. package/dialog/package.json +1 -8
  5. package/dialog-scroll-lock/package.json +1 -8
  6. package/dist/cjs/dialog/dialog-content.js +12 -30
  7. package/dist/cjs/internal/set-style.js +5 -5
  8. package/dist/cjs/internal/slot.js +1 -1
  9. package/dist/cjs/internal/use-anchor-position-at-point.js +8 -8
  10. package/dist/cjs/internal/use-anchor-position.js +19 -40
  11. package/dist/cjs/internal/use-animated-visibility.js +31 -61
  12. package/dist/cjs/internal/use-initial-focus.js +9 -13
  13. package/dist/cjs/internal/use-width-from-anchor.js +6 -6
  14. package/dist/cjs/popover/popover.js +30 -58
  15. package/dist/cjs/popover-surface/popover-surface.js +4 -3
  16. package/dist/cjs/testing/polyfill.js +9 -9
  17. package/dist/cjs/use-simple-light-dismiss/index.js +3 -3
  18. package/dist/es2019/dialog/dialog-content.js +12 -30
  19. package/dist/es2019/internal/set-style.js +5 -5
  20. package/dist/es2019/internal/slot.js +1 -1
  21. package/dist/es2019/internal/use-anchor-position-at-point.js +8 -8
  22. package/dist/es2019/internal/use-anchor-position.js +19 -40
  23. package/dist/es2019/internal/use-animated-visibility.js +31 -61
  24. package/dist/es2019/internal/use-initial-focus.js +9 -13
  25. package/dist/es2019/internal/use-width-from-anchor.js +6 -6
  26. package/dist/es2019/popover/popover.js +30 -58
  27. package/dist/es2019/popover-surface/popover-surface.js +1 -1
  28. package/dist/es2019/testing/polyfill.js +7 -11
  29. package/dist/es2019/use-simple-light-dismiss/index.js +3 -3
  30. package/dist/esm/dialog/dialog-content.js +12 -30
  31. package/dist/esm/internal/set-style.js +5 -5
  32. package/dist/esm/internal/slot.js +1 -1
  33. package/dist/esm/internal/use-anchor-position-at-point.js +8 -8
  34. package/dist/esm/internal/use-anchor-position.js +19 -40
  35. package/dist/esm/internal/use-animated-visibility.js +31 -61
  36. package/dist/esm/internal/use-initial-focus.js +9 -13
  37. package/dist/esm/internal/use-width-from-anchor.js +6 -6
  38. package/dist/esm/popover/popover.js +30 -58
  39. package/dist/esm/popover-surface/popover-surface.js +1 -1
  40. package/dist/esm/testing/polyfill.js +9 -9
  41. package/dist/esm/use-simple-light-dismiss/index.js +3 -3
  42. package/dist/types/internal/set-style.d.ts +2 -2
  43. package/dist/types/internal/slot.d.ts +2 -2
  44. package/dist/types/internal/use-anchor-position-at-point.d.ts +2 -2
  45. package/dist/types/internal/use-anchor-position.d.ts +0 -4
  46. package/dist/types/internal/use-initial-focus.d.ts +1 -1
  47. package/dist/types/popover/types.d.ts +5 -7
  48. package/dist/types/popover-surface/popover-surface.d.ts +2 -2
  49. package/focus/package.json +1 -8
  50. package/get-aria-for-trigger/package.json +1 -8
  51. package/open-layer-observer/package.json +1 -8
  52. package/package.json +11 -19
  53. package/placement-map/package.json +1 -8
  54. package/popover/package.json +1 -8
  55. package/popover-surface/package.json +1 -8
  56. package/testing/polyfill/package.json +1 -8
  57. package/testing/to-be-visible/package.json +1 -8
  58. package/use-anchor-position/package.json +1 -8
  59. package/use-anchor-position-at-point/package.json +1 -8
  60. package/use-arrow-navigation/package.json +1 -8
  61. package/use-popover-id/package.json +1 -8
  62. package/use-simple-light-dismiss/package.json +1 -8
  63. package/use-width-from-anchor/package.json +1 -8
  64. package/dist/types-ts4.5/animations/presets.d.ts +0 -110
  65. package/dist/types-ts4.5/animations/types.d.ts +0 -38
  66. package/dist/types-ts4.5/dialog/create-close-event.d.ts +0 -23
  67. package/dist/types-ts4.5/dialog/dialog-content.d.ts +0 -24
  68. package/dist/types-ts4.5/dialog/index.d.ts +0 -3
  69. package/dist/types-ts4.5/dialog/types.d.ts +0 -126
  70. package/dist/types-ts4.5/dialog-scroll-lock/index.d.ts +0 -40
  71. package/dist/types-ts4.5/entry-points/animations.d.ts +0 -2
  72. package/dist/types-ts4.5/entry-points/dialog-scroll-lock.d.ts +0 -1
  73. package/dist/types-ts4.5/entry-points/dialog.d.ts +0 -2
  74. package/dist/types-ts4.5/entry-points/focus.d.ts +0 -4
  75. package/dist/types-ts4.5/entry-points/get-aria-for-trigger.d.ts +0 -1
  76. package/dist/types-ts4.5/entry-points/open-layer-observer.d.ts +0 -5
  77. package/dist/types-ts4.5/entry-points/placement-map.d.ts +0 -3
  78. package/dist/types-ts4.5/entry-points/popover-surface.d.ts +0 -2
  79. package/dist/types-ts4.5/entry-points/popover.d.ts +0 -2
  80. package/dist/types-ts4.5/entry-points/use-anchor-position-at-point.d.ts +0 -1
  81. package/dist/types-ts4.5/entry-points/use-anchor-position.d.ts +0 -1
  82. package/dist/types-ts4.5/entry-points/use-arrow-navigation.d.ts +0 -3
  83. package/dist/types-ts4.5/entry-points/use-popover-id.d.ts +0 -11
  84. package/dist/types-ts4.5/entry-points/use-simple-light-dismiss.d.ts +0 -2
  85. package/dist/types-ts4.5/entry-points/use-width-from-anchor.d.ts +0 -1
  86. package/dist/types-ts4.5/focus/get-first-focusable.d.ts +0 -8
  87. package/dist/types-ts4.5/focus/get-focusables.d.ts +0 -13
  88. package/dist/types-ts4.5/focus/get-last-focusable.d.ts +0 -8
  89. package/dist/types-ts4.5/focus/get-next-focusable.d.ts +0 -13
  90. package/dist/types-ts4.5/focus/is-in-same-layer.d.ts +0 -8
  91. package/dist/types-ts4.5/focus/is-nested-layer-focused.d.ts +0 -8
  92. package/dist/types-ts4.5/focus/selectors.d.ts +0 -4
  93. package/dist/types-ts4.5/focus/types.d.ts +0 -5
  94. package/dist/types-ts4.5/index.d.ts +0 -0
  95. package/dist/types-ts4.5/internal/anchor-positioning-fallback.d.ts +0 -26
  96. package/dist/types-ts4.5/internal/combine.d.ts +0 -14
  97. package/dist/types-ts4.5/internal/ensure-preset-styles.d.ts +0 -6
  98. package/dist/types-ts4.5/internal/get-aria-for-trigger.d.ts +0 -93
  99. package/dist/types-ts4.5/internal/placement-to-position-area.d.ts +0 -21
  100. package/dist/types-ts4.5/internal/placement-to-try-fallbacks.d.ts +0 -36
  101. package/dist/types-ts4.5/internal/reduced-motion.d.ts +0 -14
  102. package/dist/types-ts4.5/internal/resolve-css-length-to-pixels.d.ts +0 -20
  103. package/dist/types-ts4.5/internal/resolve-css-length.d.ts +0 -8
  104. package/dist/types-ts4.5/internal/resolve-placement.d.ts +0 -58
  105. package/dist/types-ts4.5/internal/role-to-aria-has-popup.d.ts +0 -13
  106. package/dist/types-ts4.5/internal/role-types.d.ts +0 -71
  107. package/dist/types-ts4.5/internal/set-style.d.ts +0 -11
  108. package/dist/types-ts4.5/internal/should-focus-into-popover.d.ts +0 -8
  109. package/dist/types-ts4.5/internal/slot.d.ts +0 -29
  110. package/dist/types-ts4.5/internal/use-anchor-position-at-point.d.ts +0 -67
  111. package/dist/types-ts4.5/internal/use-anchor-position.d.ts +0 -91
  112. package/dist/types-ts4.5/internal/use-animated-visibility.d.ts +0 -109
  113. package/dist/types-ts4.5/internal/use-animation-preset.d.ts +0 -11
  114. package/dist/types-ts4.5/internal/use-focus-wrap.d.ts +0 -25
  115. package/dist/types-ts4.5/internal/use-initial-focus.d.ts +0 -73
  116. package/dist/types-ts4.5/internal/use-preset-styles.d.ts +0 -15
  117. package/dist/types-ts4.5/internal/use-width-from-anchor.d.ts +0 -21
  118. package/dist/types-ts4.5/placement-map/index.d.ts +0 -51
  119. package/dist/types-ts4.5/placement-map/legacy-placements.d.ts +0 -27
  120. package/dist/types-ts4.5/placement-map/placement-mapping.d.ts +0 -13
  121. package/dist/types-ts4.5/popover/create-close-event.d.ts +0 -25
  122. package/dist/types-ts4.5/popover/index.d.ts +0 -3
  123. package/dist/types-ts4.5/popover/popover.d.ts +0 -17
  124. package/dist/types-ts4.5/popover/types.d.ts +0 -193
  125. package/dist/types-ts4.5/popover-surface/index.d.ts +0 -2
  126. package/dist/types-ts4.5/popover-surface/popover-surface.d.ts +0 -21
  127. package/dist/types-ts4.5/testing/polyfill.d.ts +0 -25
  128. package/dist/types-ts4.5/testing/to-be-visible.d.ts +0 -23
  129. package/dist/types-ts4.5/use-arrow-navigation/index.d.ts +0 -3
  130. package/dist/types-ts4.5/use-arrow-navigation/is-at-current-menu-level.d.ts +0 -48
  131. package/dist/types-ts4.5/use-arrow-navigation/use-arrow-navigation.d.ts +0 -102
  132. package/dist/types-ts4.5/use-simple-light-dismiss/index.d.ts +0 -45
@@ -6,7 +6,7 @@ import { useAnchorPosition } from './use-anchor-position';
6
6
  * anchor should be placed.
7
7
  *
8
8
  * Axis-named (`x`/`y`) rather than edge-named (`left`/`top`) so the
9
- * shape is unambiguous in RTL writing modes — there is no ambiguity
9
+ * shape is unambiguous in RTL writing modes - there is no ambiguity
10
10
  * about whether a coordinate is logical (`inline-start`/`block-start`)
11
11
  * or physical (`left`/`top`). Viewport coordinates are always
12
12
  * physical, so we name them with the physical axis labels.
@@ -17,19 +17,19 @@ import { useAnchorPosition } from './use-anchor-position';
17
17
  * Dynamic `inset-*` overrides are written by the positioning effect.
18
18
  *
19
19
  * Uses `Object.assign` rather than Compiled CSS (element is outside the React tree)
20
- * or the internal `setStyle` helper (we own this element entirely — it is created
21
- * and removed by this hook — so the snapshot/restore semantics `setStyle`
20
+ * or the internal `setStyle` helper (we own this element entirely - it is created
21
+ * and removed by this hook - so the snapshot/restore semantics `setStyle`
22
22
  * provides for caller-owned elements would be dead work here).
23
23
  */
24
- function applyStaticAnchorStyles(el) {
25
- Object.assign(el.style, {
24
+ function applyStaticAnchorStyles(element) {
25
+ Object.assign(element.style, {
26
26
  position: 'fixed',
27
27
  // `top`/`left` rather than `inset-block-start`/`inset-inline-start`
28
28
  // for consistency with the dynamic positioning effect below, which
29
29
  // uses physical properties (see comment there for the rationale).
30
30
  top: '0',
31
31
  left: '0',
32
- // Zero size — placement decides which side the popover sits on.
32
+ // Zero size - placement decides which side the popover sits on.
33
33
  width: '0',
34
34
  height: '0',
35
35
  // Prevent the overlay from intercepting pointer events.
@@ -75,7 +75,7 @@ export function useAnchorPositionAtPoint({
75
75
  const syntheticAnchorRef = useRef(null);
76
76
 
77
77
  // `getPoint` is kept in a ref so the layout effect can call the
78
- // latest version without listing it as a dep — that would re-fire
78
+ // latest version without listing it as a dep - that would re-fire
79
79
  // on every render and re-latch the point.
80
80
  const getPointRef = useRef(getPoint);
81
81
  getPointRef.current = getPoint;
@@ -111,7 +111,7 @@ export function useAnchorPositionAtPoint({
111
111
  };
112
112
  }, [isEnabled]);
113
113
 
114
- // Mirror `isEnabled` so both hooks stay in sync — when disabled,
114
+ // Mirror `isEnabled` so both hooks stay in sync - when disabled,
115
115
  // neither this hook nor the inner one touches the popover, leaving
116
116
  // any other positioning the consumer has set up free to take effect.
117
117
  useAnchorPosition({
@@ -139,17 +139,11 @@ function crossAxisShiftMargin({
139
139
  }
140
140
 
141
141
  /**
142
- * Returns a referentially-stable, fully-resolved `TPlacement`. `useMemo` is
143
- * keyed on the primitive fields of the input so a fresh inline `placement`
144
- * object on every render does not produce a fresh resolved object. Both
145
- * `getPlacement` and the equality check are skipped when the inputs are
146
- * unchanged.
142
+ * Returns a referentially-stable, fully-resolved `TPlacement`. The memo is
143
+ * keyed on the resolved primitive fields so a fresh inline `placement` object
144
+ * (or undefined fields vs explicit defaults) does not produce a fresh result.
147
145
  */
148
146
  function useStablePlacement(placement) {
149
- // Resolve to the fully-defaulted primitives BEFORE the memo so the deps
150
- // array sees the same key for `{}`, `undefined` fields, and explicit
151
- // defaults. Otherwise `axis: undefined` and `axis: 'block'` would be
152
- // treated as different inputs and re-run the downstream effect.
153
147
  const resolved = getPlacement({
154
148
  placement
155
149
  });
@@ -159,10 +153,8 @@ function useStablePlacement(placement) {
159
153
  const gap = resolved.offset.gap;
160
154
  const shiftValue = resolved.offset.crossAxisShift.value;
161
155
  const shiftDirection = resolved.offset.crossAxisShift.direction;
162
- // Rebuild the placement object inside the memo from the resolved primitive
163
- // deps so the factory does not close over the (potentially fresh) outer
164
- // `resolved` reference, and `react-hooks/exhaustive-deps` can verify the
165
- // deps array.
156
+ // Rebuild inside the memo from primitive deps so the factory does not
157
+ // close over the outer `resolved` reference and exhaustive-deps can verify.
166
158
  return useMemo(() => ({
167
159
  axis,
168
160
  edge,
@@ -192,10 +184,6 @@ function useStablePlacement(placement) {
192
184
  * When not supported, it falls back to JavaScript-based positioning using
193
185
  * `position: fixed` with measured coordinates, re-running on scroll (capture)
194
186
  * and resize events.
195
- *
196
- * `placement` may be a fresh inline object on every render: a structural
197
- * comparison is used internally so re-runs are skipped when the resolved
198
- * placement shape is unchanged.
199
187
  */
200
188
  // eslint-disable-next-line @atlaskit/volt-strict-mode/no-multiple-exports
201
189
  export function useAnchorPosition({
@@ -207,8 +195,6 @@ export function useAnchorPosition({
207
195
  isOpen
208
196
  }) {
209
197
  const id = useId();
210
- // Stabilize `placement` object so the same object literal
211
- // value returns an object with the same reference
212
198
  const stablePlacement = useStablePlacement(placement);
213
199
  useLayoutEffect(() => {
214
200
  if (!isEnabled) {
@@ -230,9 +216,9 @@ export function useAnchorPosition({
230
216
  placement: stablePlacement,
231
217
  offset: gapCssValue
232
218
  });
233
- // Hoist crossAxisShift to a single computation; reused below to
234
- // populate the active `--ds-cross-axis-shift-margin-*` custom
235
- // property for the named arrow @position-try rules.
219
+ // Computed once; reused below to populate the active
220
+ // `--ds-cross-axis-shift-margin-*` custom property for the
221
+ // named arrow @position-try rules.
236
222
  const crossAxisShift = crossAxisShiftMargin({
237
223
  placement: stablePlacement,
238
224
  crossAxisShiftCssValue,
@@ -260,22 +246,17 @@ export function useAnchorPosition({
260
246
  }, {
261
247
  property: 'inset',
262
248
  value: 'auto'
263
- },
264
- // Main-axis gap between the popover and its trigger
265
- {
249
+ }, {
266
250
  property: gap.property,
267
251
  value: gap.value
268
- },
269
- // Cross-axis shift
270
- {
252
+ }, {
271
253
  property: crossAxisShift.property,
272
254
  value: crossAxisShift.value
273
255
  }];
274
256
 
275
- // Surface the active cross-axis shift value as a custom property so
276
- // each named arrow @position-try rule can re-apply it after a flip.
277
- // The four properties cover (cross-axis, side); only one is non-zero
278
- // at a time.
257
+ // Expose the active cross-axis shift via custom properties so each
258
+ // named arrow @position-try rule can re-apply it after a flip.
259
+ // One of the four (cross-axis, side) properties is non-zero at a time.
279
260
  popoverStyles.push({
280
261
  property: '--ds-cross-axis-shift-margin-start',
281
262
  value: '0px'
@@ -292,9 +273,8 @@ export function useAnchorPosition({
292
273
  property: '--ds-cross-axis-shift-margin-block-end',
293
274
  value: '0px'
294
275
  });
295
- // `crossAxisShift` IS the active margin - reuse instead of recomputing.
276
+ // `crossAxisShift` IS the active margin; reuse instead of recomputing.
296
277
  const crossAxisShiftActive = crossAxisShift;
297
- // Map margin property to its corresponding custom property name.
298
278
  const customPropertyByMarginProperty = {
299
279
  'margin-inline-start': '--ds-cross-axis-shift-margin-start',
300
280
  'margin-inline-end': '--ds-cross-axis-shift-margin-end',
@@ -328,18 +308,17 @@ export function useAnchorPosition({
328
308
  */
329
309
  trigger.style.setProperty('anchor-name', anchorName);
330
310
  const undoPositioning = combine(setStyle({
331
- el: popover,
311
+ element: popover,
332
312
  styles: popoverStyles
333
313
  }));
334
314
  return undoPositioning;
335
315
  }
336
316
 
337
- // JS fallback
338
- // The popover is already in the browser's top layer via popover="auto",
339
- // so it does not need position:fixed. We only reset the UA defaults
340
- // (inset: 0; margin: auto) and set top/left based on measurements.
317
+ // JS fallback. The popover is already in the top layer via
318
+ // popover="auto", so we only reset UA defaults and set top/left
319
+ // based on measurements.
341
320
  const cleanupBaseStyles = setStyle({
342
- el: popover,
321
+ element: popover,
343
322
  styles: [{
344
323
  property: 'margin',
345
324
  value: '0'
@@ -12,14 +12,16 @@ function runOnTransitionEndOrTimeout({
12
12
  safetyNetMs,
13
13
  onSettled
14
14
  }) {
15
- let isSettled = false;
15
+ const state = {
16
+ isSettled: false
17
+ };
16
18
  function teardown() {
17
- isSettled = true;
19
+ state.isSettled = true;
18
20
  clearTimeout(safetyNetId);
19
21
  unbind();
20
22
  }
21
23
  function settle() {
22
- if (isSettled) {
24
+ if (state.isSettled) {
23
25
  return;
24
26
  }
25
27
  teardown();
@@ -104,22 +106,16 @@ export function useAnimatedVisibility({
104
106
  onEnterFinish,
105
107
  onExitFinish
106
108
  }) {
107
- // Resolve the animation preset and inject its CSS (idempotent).
108
- // Returns `null` when `animate` is falsy.
109
109
  const preset = usePresetStyles({
110
110
  preset: animate
111
111
  });
112
112
 
113
- // Whether we should play CSS transitions. False when there is no preset
114
- // or the user has enabled the "prefers reduced motion" OS setting.
113
+ // False when there is no preset or `prefers-reduced-motion` is set.
115
114
  const willAnimate = Boolean(animate) && !prefersReducedMotion();
116
115
 
117
- // `phase` is the single source of truth for the visibility lifecycle.
118
- // We promote `closed → entering` (animated) or `closed → open`
119
- // (non-animated) synchronously during render so the host element mounts
120
- // in the same commit as the open intent (React supports a single
121
- // setState during render as a synchronous re-render before paint).
122
- // All other transitions are driven by phase-specific effects below.
116
+ // Promote `closed → entering`/`closed → open` synchronously during
117
+ // render so the host element mounts in the same commit as the open
118
+ // intent. All other transitions are driven by phase-specific effects.
123
119
  const [phase, setPhase] = useState(() => {
124
120
  if (!isOpen) {
125
121
  return 'closed';
@@ -151,10 +147,8 @@ export function useAnimatedVisibility({
151
147
  const onExitFinishRef = useRef(onExitFinish);
152
148
  onExitFinishRef.current = onExitFinish;
153
149
 
154
- // `entering` (animation enabled):
155
- // Wait for the entry transition to settle, then move to `open` and fire
156
- // `onEnterFinish`. A fallback timeout (enterDurationMs + 50ms buffer)
157
- // guarantees the settle path runs even if `transitionend` is swallowed
150
+ // Animated entry: wait for `transitionend` (or a safety-net timeout) and
151
+ // move to `open`. The fallback guarantees settle on swallowed events
158
152
  // (zero-duration, browser quirk, mid-transition unmount).
159
153
  useEffect(() => {
160
154
  var _preset$enterDuration;
@@ -180,12 +174,9 @@ export function useAnimatedVisibility({
180
174
  });
181
175
  }, [phase, preset === null || preset === void 0 ? void 0 : preset.enterDurationMs, elementRef]);
182
176
 
183
- // Non-animated entry callback:
184
- // When animation is disabled, the lifecycle goes `closed → open`
185
- // directly with no `entering` phase. Fire `onEnterFinish` on the
186
- // transition into `open` from the non-animated path. We detect this
187
- // via a previous-phase ref to avoid firing on the animated path's
188
- // `entering → open` transition (which has its own callback above).
177
+ // Non-animated entry callback. The lifecycle goes `closed → open`
178
+ // directly with no `entering` phase; fire `onEnterFinish` on that jump.
179
+ // A previous-phase ref avoids double-firing on the animated path.
189
180
  const prevPhaseForEntryRef = useRef('closed');
190
181
  useEffect(() => {
191
182
  const prevPhase = prevPhaseForEntryRef.current;
@@ -199,12 +190,8 @@ export function useAnimatedVisibility({
199
190
  }
200
191
  }, [phase]);
201
192
 
202
- // `exiting` (animation enabled):
203
- // Wait for the exit transition to settle, then unmount the host element
204
- // (set phase back to `closed`) and fire `onExitFinish`. The fallback
205
- // timeout (exitDurationMs + 50ms buffer) guarantees we always unmount
206
- // even if `transitionend` is never emitted (element removed from the
207
- // top layer, zero-duration transition, interrupted animation).
193
+ // Animated exit: wait for `transitionend` (or a safety-net timeout),
194
+ // fire `onExitFinish`, then move to `closed` to unmount the host.
208
195
  useEffect(() => {
209
196
  var _preset$exitDurationM;
210
197
  if (phase !== 'exiting' || !willAnimate) {
@@ -231,33 +218,20 @@ export function useAnimatedVisibility({
231
218
  });
232
219
  }, [phase, willAnimate, preset === null || preset === void 0 ? void 0 : preset.exitDurationMs, elementRef]);
233
220
 
234
- // `exiting` (animation disabled):
235
- // Wait for the browser's `toggle` (closed) event (for `<div popover>`)
236
- // or `close` event (for `<dialog>`) to fire before unmounting. This
237
- // guarantees:
238
- // - the consumer's toggle/close listener (close-reason capture,
239
- // nested focus restoration, `onClose` notification) runs against
240
- // the still-attached element,
241
- // - the browser's native focus restoration triggered by
242
- // `hidePopover()` / `<dialog>.close()` has fired,
243
- // before React unmounts the node. A naive `setTimeout(0)` race
244
- // occasionally beats the `toggle` task in Chromium, causing focus
245
- // restoration to drop (the toggle event arrives at a detached element).
221
+ // Non-animated exit: wait for the browser's `toggle` (closed) or
222
+ // `close` event before unmounting, so the consumer's listener (close
223
+ // reason, nested focus restoration, `onClose`) and the browser's
224
+ // native focus restoration both run against the still-attached
225
+ // element. A naive `setTimeout(0)` occasionally beats the `toggle`
226
+ // task in Chromium and drops focus restoration.
246
227
  //
247
- // `useLayoutEffect` (not `useEffect`) so the listener is bound in the
248
- // same commit as the consumer's show/hide layout effect that calls
249
- // `hidePopover()` / `dialog.close()`. The browser queues the resulting
250
- // `toggle`/`close` event as a task, which cannot run until the JS
251
- // frame (including all effect flushes) completes, so the listener is
252
- // always attached before the event fires. Promotion to layout timing
253
- // removes any theoretical gap and future-proofs the ordering against
254
- // effect reordering. Safe under SSR: `phase` starts at `'closed'`, so
255
- // the body short-circuits and no DOM access happens on the server.
228
+ // `useLayoutEffect` so the listener binds in the same commit as the
229
+ // show/hide layout effect that calls `hidePopover()` / `dialog.close()`.
230
+ // The toggle/close event is queued as a task and cannot run until the
231
+ // frame completes. SSR-safe: `phase` starts at `'closed'`.
256
232
  //
257
- // `onExitFinish` is NOT called here. It is decoupled from the unmount
258
- // handshake and fired by the dedicated non-animated exit-callback
259
- // effect below, so consumers observe a synchronous "exit finished"
260
- // signal that matches `isOpen` flipping to `false`.
233
+ // `onExitFinish` is fired separately (see effect below) so consumers
234
+ // observe it the moment `isOpen` flips to `false`.
261
235
  useLayoutEffect(() => {
262
236
  if (phase !== 'exiting' || willAnimate) {
263
237
  return;
@@ -282,13 +256,9 @@ export function useAnimatedVisibility({
282
256
  }]);
283
257
  }, [phase, willAnimate, elementRef]);
284
258
 
285
- // Non-animated exit callback:
286
- // When animation is disabled there is no `transitionend` to wait for,
287
- // so fire `onExitFinish` on the transition into `exiting`. The actual
288
- // host-element unmount is gated on the browser `toggle`/`close` event
289
- // by the effect above; `onExitFinish` is observably decoupled from
290
- // that handshake so consumers see the callback fire at the same time
291
- // `isOpen` flipped to `false`.
259
+ // Non-animated exit callback. No `transitionend` to wait for, so fire
260
+ // `onExitFinish` on the transition into `exiting` (the unmount itself
261
+ // is gated by the toggle/close handshake above).
292
262
  const prevPhaseForExitRef = useRef('closed');
293
263
  useEffect(() => {
294
264
  const prevPhase = prevPhaseForExitRef.current;
@@ -128,8 +128,6 @@ function getInitialFocusTarget({
128
128
  });
129
129
  }
130
130
  if (role === 'menu') {
131
- // ARIA Combobox Pattern: if the popup is owned by a focused combobox,
132
- // leave focus on the textbox. Navigation is proxied via the combobox.
133
131
  if (isComboboxControllingPopup({
134
132
  container
135
133
  })) {
@@ -146,9 +144,9 @@ function getInitialFocusTarget({
146
144
  });
147
145
  }
148
146
  if (role === 'listbox') {
149
- // ARIA Combobox Pattern: if the popup is owned by a focused
150
- // combobox-like textbox (in-popup or external e.g. react-select),
151
- // leave focus on the textbox. Navigation is proxied via the combobox.
147
+ // ARIA Combobox Pattern: if a combobox-like textbox owns the popup
148
+ // (in-popup or external e.g. react-select), leave focus on the
149
+ // textbox - navigation is proxied through it.
152
150
  if (isComboboxControllingPopup({
153
151
  container
154
152
  })) {
@@ -210,7 +208,7 @@ function getInitialFocusTarget({
210
208
  * `<dialog>.close()`. If the consumer reopens before the exit transition
211
209
  * has settled, the phase machine jumps `exiting → entering` without
212
210
  * passing through `closed`, so without this branch the popup would be
213
- * visible again with focus stranded on the trigger — a keyboard usability
211
+ * visible again with focus stranded on the trigger - a keyboard usability
214
212
  * gap for menu/dialog/listbox roles.
215
213
  *
216
214
  * Note: there is a rare programmatic path where a consumer flips `isOpen`
@@ -252,22 +250,20 @@ export function useInitialFocus({
252
250
  const prevPhase = prevPhaseRef.current;
253
251
  prevPhaseRef.current = phase;
254
252
 
255
- // Skip the `phase === 'closed'` transitions (host is going away,
256
- // nothing to focus) and any transition whose previous phase was
257
- // neither `'closed'` (fresh open) nor `'exiting'` (mid-exit
258
- // reopen, focus already on the trigger).
253
+ // Only focus on transitions into a visible phase from `'closed'`
254
+ // (fresh open) or `'exiting'` (mid-exit reopen).
259
255
  if (phase === 'closed') {
260
256
  return;
261
257
  }
262
258
  if (prevPhase !== 'closed' && prevPhase !== 'exiting') {
263
259
  return;
264
260
  }
265
- const el = elementRef.current;
266
- if (!el) {
261
+ const element = elementRef.current;
262
+ if (!element) {
267
263
  return;
268
264
  }
269
265
  (_getInitialFocusTarge = getInitialFocusTarget({
270
- container: el,
266
+ container: element,
271
267
  role
272
268
  })) === null || _getInitialFocusTarge === void 0 ? void 0 : _getInitialFocusTarge.focus();
273
269
  }, [elementRef, phase, role]);
@@ -36,7 +36,7 @@ export function useWidthFromAnchor({
36
36
  // explicit width constraint, allowing the popover to grow past the anchor's width.
37
37
  if (supportsAnchorSize) {
38
38
  return setStyle({
39
- el: node,
39
+ element: node,
40
40
  styles: [{
41
41
  property: 'width',
42
42
  value: 'anchor-size(width)'
@@ -48,7 +48,7 @@ export function useWidthFromAnchor({
48
48
  const anchor = anchorRef.current;
49
49
  if (anchor) {
50
50
  return setStyle({
51
- el: node,
51
+ element: node,
52
52
  styles: [{
53
53
  property: 'width',
54
54
  value: `${anchor.offsetWidth}px`
@@ -70,7 +70,7 @@ export function useWidthFromAnchor({
70
70
  // if its content requires it.
71
71
  if (supportsAnchorSize) {
72
72
  return setStyle({
73
- el: node,
73
+ element: node,
74
74
  styles: [minInlineSizeStyle, {
75
75
  property: 'min-width',
76
76
  value: 'anchor-size(width)'
@@ -80,7 +80,7 @@ export function useWidthFromAnchor({
80
80
  const anchor = anchorRef.current;
81
81
  if (anchor) {
82
82
  return setStyle({
83
- el: node,
83
+ element: node,
84
84
  styles: [minInlineSizeStyle, {
85
85
  property: 'min-width',
86
86
  value: `${anchor.offsetWidth}px`
@@ -90,14 +90,14 @@ export function useWidthFromAnchor({
90
90
 
91
91
  // No anchor available in JS fallback - still apply min-inline-size.
92
92
  return setStyle({
93
- el: node,
93
+ element: node,
94
94
  styles: [minInlineSizeStyle]
95
95
  });
96
96
  }
97
97
 
98
98
  // 'none' mode: no explicit width, but still apply `min-inline-size: max-content`.
99
99
  return setStyle({
100
- el: node,
100
+ element: node,
101
101
  styles: [minInlineSizeStyle]
102
102
  });
103
103
  }, [mode, popoverRef, anchorRef, isOpen]);
@@ -19,9 +19,9 @@ const supportsPopoverHint = once(() => {
19
19
  return false;
20
20
  }
21
21
  // eslint-disable-next-line @atlaskit/platform/no-direct-document-usage -- feature detection
22
- const el = document.createElement('div');
23
- el.setAttribute('popover', 'hint');
24
- return el.popover === 'hint';
22
+ const element = document.createElement('div');
23
+ element.setAttribute('popover', 'hint');
24
+ return element.popover === 'hint';
25
25
  });
26
26
 
27
27
  // eslint-disable-next-line @atlaskit/ui-styling-standard/no-unsafe-values -- popover UA reset requires values not in cssMap's type union
@@ -65,9 +65,7 @@ export const Popover = /*#__PURE__*/forwardRef(function Popover({
65
65
  const autoId = useId();
66
66
  const ownRef = useRef(null);
67
67
  const combinedRef = mergeRefs([ownRef, ref].filter(Boolean));
68
- // React 18 `useId()` returns IDs with colons (invalid in CSS selectors
69
- // and popover target attributes). Strip them; remove once React 18 is
70
- // dropped (React 19.2 uses underscores).
68
+ // `useId()` colons are invalid in CSS selectors and popover target attributes.
71
69
  const popoverId = idProp !== null && idProp !== void 0 ? idProp : `popover-${autoId.replace(/:/g, '')}`;
72
70
  const {
73
71
  phase,
@@ -80,21 +78,15 @@ export const Popover = /*#__PURE__*/forwardRef(function Popover({
80
78
  onExitFinish
81
79
  });
82
80
 
83
- // Pre-computed phase predicate. `isVisible` mirrors "host element is
84
- // mounted and on screen" (any phase except `closed`); used as a dep
85
- // signal for listener-re-bind effects so they re-attach to the fresh
86
- // host element after a close → reopen unmount/remount cycle.
81
+ // True while the host element is mounted (any phase except `closed`).
82
+ // Used as a dep so listener-rebind effects re-attach after a remount.
87
83
  const isVisible = phase !== 'closed';
88
84
 
89
85
  // Focus management: initial focus on entry (role-dependent), Tab cycling
90
86
  // for dialog roles. Restoration is native for outermost popovers; nested
91
- // focus-capturing roles are handled below via `beforetoggle` snapshot.
92
- // See: notes/architecture/focus-restoration.md
93
- //
94
- // Both hooks take `phase` directly: `useFocusWrap` keeps its listener
95
- // attached while `phase !== 'closed'` (so focus stays trapped through
96
- // the animated-exit window — WCAG 2.4.3 regression guard);
97
- // `useInitialFocus` moves focus on the transition into `'open'`.
87
+ // focus-capturing roles are handled below via the `beforetoggle` snapshot.
88
+ // Passing `phase` keeps `useFocusWrap` attached through the animated-exit
89
+ // window (WCAG 2.4.3 guard). See notes/architecture/focus-restoration.md.
98
90
  useFocusWrap({
99
91
  elementRef: ownRef,
100
92
  role,
@@ -191,7 +183,6 @@ export const Popover = /*#__PURE__*/forwardRef(function Popover({
191
183
 
192
184
  // Browser dismiss (Escape/click-outside).
193
185
  const reason = closeReasonRef.current;
194
- // reset for next dismiss
195
186
  closeReasonRef.current = 'light-dismiss';
196
187
  // Optional: `manual` mode has no `onClose` in the forwarded type.
197
188
  onClose === null || onClose === void 0 ? void 0 : onClose({
@@ -200,21 +191,12 @@ export const Popover = /*#__PURE__*/forwardRef(function Popover({
200
191
  }
201
192
  }, [onClose, onOpenChange, role]);
202
193
 
203
- // Bind toggle/beforetoggle/keydown listeners via `useLayoutEffect` so
204
- // they are attached BEFORE the show/hide layout effect calls
205
- // `el.showPopover()`. `showPopover()` dispatches `beforetoggle`
206
- // synchronously; binding the listener in a regular `useEffect`
207
- // (which runs after layout effects) misses that first dispatch and
208
- // the `previouslyFocusedElementRef` snapshot never gets taken — which
209
- // breaks nested-popover focus restoration on close.
210
- //
211
- // `isVisible` is in deps so the listeners re-bind to the new host
212
- // element after a host unmount/remount cycle. The cleanup detaches
213
- // the old listeners.
214
- //
215
- // This layout effect MUST appear in the file before the show/hide
216
- // layout effect — React runs layout effects in source order on the
217
- // same commit, and we need listeners attached first.
194
+ // Bind via `useLayoutEffect`, and BEFORE the show/hide layout effect
195
+ // below, so listeners are attached when `showPopover()` synchronously
196
+ // dispatches `beforetoggle`. A regular `useEffect` would miss that
197
+ // first dispatch, dropping the `previouslyFocusedElementRef` snapshot
198
+ // and breaking nested-popover focus restoration on close. React runs
199
+ // layout effects in source order on the same commit.
218
200
  useLayoutEffect(() => {
219
201
  const element = ownRef.current;
220
202
  if (!element) {
@@ -250,20 +232,17 @@ export const Popover = /*#__PURE__*/forwardRef(function Popover({
250
232
  }, [handleToggle, handleBeforeToggle, isVisible]);
251
233
 
252
234
  // Placement-dependent animation CSS vars. Kept separate from show/hide
253
- // so preset reference changes do not re-trigger visibility. `isVisible`
254
- // is in deps so the CSS vars are re-applied to the new host element after
255
- // a remount cycle (otherwise the freshly mounted element would have no
256
- // transform / placement vars set).
235
+ // so preset reference changes do not re-trigger visibility.
257
236
  useLayoutEffect(() => {
258
- const el = ownRef.current;
259
- if (!el || !(preset !== null && preset !== void 0 && preset.getProperties) || !placement) {
237
+ const element = ownRef.current;
238
+ if (!element || !(preset !== null && preset !== void 0 && preset.getProperties) || !placement) {
260
239
  return;
261
240
  }
262
241
  const props = preset.getProperties({
263
242
  placement
264
243
  });
265
244
  Object.entries(props).forEach(([key, value]) => {
266
- el.style.setProperty(key, String(value));
245
+ element.style.setProperty(key, String(value));
267
246
  });
268
247
  }, [preset, placement, isVisible]);
269
248
 
@@ -271,42 +250,35 @@ export const Popover = /*#__PURE__*/forwardRef(function Popover({
271
250
  // already in the target state. Try/catch guards `InvalidStateError` on
272
251
  // disconnected elements (StrictMode/concurrent/unmount edge cases).
273
252
  useLayoutEffect(() => {
274
- const el = ownRef.current;
275
- if (!el) {
253
+ const element = ownRef.current;
254
+ if (!element) {
276
255
  return;
277
256
  }
278
257
  if (isOpen) {
279
258
  programmaticCloseRef.current = false;
280
- // Reset the close-reason snapshot at the start of every open
281
- // cycle. The ref outlives the host element (lives on the
282
- // component, not the DOM), so a stale 'escape' from a prior
283
- // cycle would otherwise leak into the next close — especially
284
- // in the no-animation path where the host unmounts before
285
- // `handleToggle` has a chance to reset the ref itself.
259
+ // Clear stale 'escape' from a prior cycle: the ref outlives
260
+ // the host element, and the no-animation path unmounts before
261
+ // `handleToggle` can reset it.
286
262
  closeReasonRef.current = 'light-dismiss';
287
263
  try {
288
- el.showPopover();
264
+ element.showPopover();
289
265
  } catch {}
290
266
  return () => {
291
267
  programmaticCloseRef.current = true;
292
268
  try {
293
- el.hidePopover();
269
+ element.hidePopover();
294
270
  } catch {}
295
271
  };
296
272
  }
297
273
  programmaticCloseRef.current = true;
298
274
  try {
299
- el.hidePopover();
275
+ element.hidePopover();
300
276
  } catch {}
301
277
  }, [isOpen]);
302
278
 
303
- // The host element is only rendered while the popover is open or its exit
304
- // animation is playing. Once the phase returns to `closed`, we unmount
305
- // the host entirely so it does not leave an empty `role="..."` /
306
- // `popover` element in the accessibility tree. The element re-mounts on
307
- // the next `isOpen=true` commit; refs and event listeners re-bind via
308
- // the existing layout effect and `useEffect`. The popover `id` is stable
309
- // across opens (derived from `useId()` or the consumer-supplied `idProp`).
279
+ // Unmount the host once exit completes so it does not leave an empty
280
+ // `role`/`popover` element in the accessibility tree. The element
281
+ // remounts on the next open; the `popoverId` is stable across opens.
310
282
  if (!isVisible) {
311
283
  return null;
312
284
  }
@@ -1,7 +1,7 @@
1
1
  /* popover-surface.tsx generated by @compiled/babel-plugin v0.39.1 */
2
2
  import "./popover-surface.compiled.css";
3
+ import * as React from 'react';
3
4
  import { ax, ix } from "@compiled/react/runtime";
4
- import React from 'react';
5
5
  const styles = {
6
6
  root: "_2rkofajl _1reo1wug _18m91wug _bfhk1bhr _16qs130s"
7
7
  };
@@ -367,17 +367,13 @@ function hidePopoversInStackNotInChainTo({
367
367
  stack,
368
368
  target
369
369
  }) {
370
- const snapshot = stack.slice();
371
- for (let i = snapshot.length - 1; i >= 0; i -= 1) {
372
- const popover = snapshot[i];
373
- if (isInChainTo({
374
- candidate: popover,
375
- target
376
- })) {
377
- break;
378
- }
379
- popover.hidePopover();
380
- }
370
+ const snapshot = stack.slice().reverse();
371
+ const stopIndex = snapshot.findIndex(popover => isInChainTo({
372
+ candidate: popover,
373
+ target
374
+ }));
375
+ const toHide = stopIndex === -1 ? snapshot : snapshot.slice(0, stopIndex);
376
+ toHide.forEach(popover => popover.hidePopover());
381
377
  }
382
378
 
383
379
  // Two-phase light-dismiss: pointerdown stores the nearest open popover for