@atlaskit/top-layer 3.1.0 → 4.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 (38) hide show
  1. package/AGENTS.md +3 -1
  2. package/CHANGELOG.md +26 -0
  3. package/README.md +0 -14
  4. package/dist/cjs/focus/is-in-same-layer.js +1 -7
  5. package/dist/cjs/focus/selectors.js +1 -1
  6. package/dist/cjs/internal/get-aria-for-trigger.js +23 -9
  7. package/dist/cjs/internal/role-types.js +1 -64
  8. package/dist/cjs/internal/use-anchor-position.js +53 -19
  9. package/dist/cjs/popover/popover.js +2 -2
  10. package/dist/es2019/focus/is-in-same-layer.js +1 -7
  11. package/dist/es2019/focus/selectors.js +1 -1
  12. package/dist/es2019/internal/get-aria-for-trigger.js +23 -10
  13. package/dist/es2019/internal/role-types.js +0 -59
  14. package/dist/es2019/internal/use-anchor-position.js +53 -19
  15. package/dist/es2019/popover/popover.js +1 -1
  16. package/dist/esm/focus/is-in-same-layer.js +1 -7
  17. package/dist/esm/focus/selectors.js +1 -1
  18. package/dist/esm/internal/get-aria-for-trigger.js +23 -10
  19. package/dist/esm/internal/role-types.js +0 -58
  20. package/dist/esm/internal/use-anchor-position.js +53 -19
  21. package/dist/esm/popover/popover.js +1 -1
  22. package/dist/types/internal/get-aria-for-trigger.d.ts +21 -7
  23. package/dist/types/internal/role-types.d.ts +0 -32
  24. package/dist/types/popover/types.d.ts +1 -1
  25. package/package.json +3 -30
  26. package/animations/package.json +0 -10
  27. package/dist/cjs/entry-points/animations.js +0 -1
  28. package/dist/cjs/internal/role-to-aria-has-popup.js +0 -37
  29. package/dist/cjs/internal/slot.js +0 -65
  30. package/dist/es2019/entry-points/animations.js +0 -0
  31. package/dist/es2019/internal/role-to-aria-has-popup.js +0 -32
  32. package/dist/es2019/internal/slot.js +0 -57
  33. package/dist/esm/entry-points/animations.js +0 -0
  34. package/dist/esm/internal/role-to-aria-has-popup.js +0 -31
  35. package/dist/esm/internal/slot.js +0 -59
  36. package/dist/types/entry-points/animations.d.ts +0 -0
  37. package/dist/types/internal/role-to-aria-has-popup.d.ts +0 -13
  38. package/dist/types/internal/slot.d.ts +0 -29
package/AGENTS.md CHANGED
@@ -110,4 +110,6 @@ const [state, setState] = useState<TState>({ type: 'idle' });
110
110
 
111
111
  ## Versioning
112
112
 
113
- This package is still on `0.x` releases. All changes should be a `minor` (`0.x`) release.
113
+ This package is on stable releases. Follow semantic versioning: use `major` for public API removals
114
+ or incompatible behavior changes, `minor` for public API additions, and `patch` for compatible fixes
115
+ and internal implementation changes.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,31 @@
1
1
  # @atlaskit/top-layer
2
2
 
3
+ ## 4.0.0
4
+
5
+ ### Major Changes
6
+
7
+ - [`eff4aad608ff2`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/eff4aad608ff2) -
8
+ Remove the obsolete `@atlaskit/top-layer/animations` entry point and simplify internal helpers.
9
+ Use the built-in `shouldAnimate` behavior on `Popover` and `Dialog`, with phase-specific xcss
10
+ overrides when customization is required.
11
+
12
+ ## 3.2.0
13
+
14
+ ### Minor Changes
15
+
16
+ - [`2857e277050c6`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/2857e277050c6) -
17
+ Behind the `platform-dst-top-layer` feature gate, `@atlaskit/popper/unsafe-imperative`'s
18
+ `createPopper` now renders and positions in the browser top layer via `@atlaskit/top-layer`
19
+ instead of running the Popper.js engine. Positioning and teardown are applied asynchronously, as
20
+ Popper.js' own first update is. Flag-off behaviour is unchanged.
21
+
22
+ `@atlaskit/top-layer`: the JavaScript positioning fallback no longer clears a consumer's own
23
+ inline positioning and visibility styles.
24
+
25
+ ### Patch Changes
26
+
27
+ - Updated dependencies
28
+
3
29
  ## 3.1.0
4
30
 
5
31
  ### Minor Changes
package/README.md CHANGED
@@ -92,20 +92,6 @@ for entry and
92
92
  [`allow-discrete`](https://developer.mozilla.org/en-US/docs/Web/CSS/transition-behavior) on
93
93
  `display`/`overlay` for exit — no JavaScript animation coordination required.
94
94
 
95
- Presets are available from `@atlaskit/top-layer/animations`:
96
-
97
- ```tsx
98
- import {
99
- // Popover presets
100
- slideAndFade,
101
- fade,
102
- scaleAndFade,
103
- // Dialog presets
104
- dialogSlideUpAndFade,
105
- dialogFade,
106
- } from '@atlaskit/top-layer/animations';
107
- ```
108
-
109
95
  ### Popover animation
110
96
 
111
97
  Pass `shouldAnimate` to enable the default popover entry and exit animation:
@@ -13,11 +13,5 @@ var topLayerSelector = '[popover], dialog, [role="dialog"], [role="alertdialog"]
13
13
  function isInSameLayer(_ref) {
14
14
  var element = _ref.element,
15
15
  container = _ref.container;
16
- var elementLayer = element.closest(topLayerSelector);
17
- var containerLayer = container.closest(topLayerSelector);
18
- // Both elements are in the "document" layer
19
- if (!elementLayer && !containerLayer) {
20
- return true;
21
- }
22
- return elementLayer === containerLayer;
16
+ return element.closest(topLayerSelector) === container.closest(topLayerSelector);
23
17
  }
@@ -8,7 +8,7 @@ var selectors = exports.selectors = function () {
8
8
  // Common exclusion filter applied to every selector.
9
9
  var not = ':not([tabindex="-1"]):not([aria-disabled="true"]):not([aria-hidden="true"])';
10
10
  var notDisabled = ":not([disabled])".concat(not);
11
- 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)];
11
+ 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)];
12
12
  return {
13
13
  focusable: focusable.join(','),
14
14
  // Build the `:focus` selector
@@ -4,7 +4,6 @@ Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
6
  exports.getAriaForTrigger = getAriaForTrigger;
7
- var _roleToAriaHasPopup = require("./role-to-aria-has-popup");
8
7
  /**
9
8
  * Roles accepted by `getAriaForTrigger`.
10
9
  *
@@ -14,12 +13,34 @@ var _roleToAriaHasPopup = require("./role-to-aria-has-popup");
14
13
  * manually for tooltip triggers.
15
14
  */
16
15
 
16
+ /**
17
+ * Valid values for the `aria-haspopup` attribute.
18
+ *
19
+ * Derived from the HTML spec - maps popover roles to what the trigger
20
+ * announces. `undefined` means the attribute is omitted entirely (used for
21
+ * non-popup roles like `tooltip`/`status`/`alert`/`note`/`log` where
22
+ * `aria-haspopup` would be misleading).
23
+ *
24
+ * `true` is intentionally NOT in the union - the runtime only ever produces
25
+ * the explicit string forms, so widening the type would invite consumers to
26
+ * pass `true` and get an `aria-haspopup="true"` serialisation that the
27
+ * runtime no longer emits.
28
+ */
29
+
17
30
  /**
18
31
  * Non-undefined subset of `TAriaHasPopupValue`. Every role accepted by
19
32
  * `getAriaForTrigger` maps to a concrete `aria-haspopup` string, so the
20
33
  * trigger always receives a defined attribute value.
21
34
  */
22
35
 
36
+ var roleToAriaHasPopup = {
37
+ dialog: 'dialog',
38
+ alertdialog: 'dialog',
39
+ menu: 'menu',
40
+ listbox: 'listbox',
41
+ tree: 'tree',
42
+ grid: 'grid'
43
+ };
23
44
  /**
24
45
  * Returns the ARIA attributes to spread onto a click-activated popover trigger.
25
46
  *
@@ -71,13 +92,6 @@ function getAriaForTrigger(_ref) {
71
92
  var role = _ref.role,
72
93
  isOpen = _ref.isOpen,
73
94
  popoverId = _ref.popoverId;
74
- // `roleToAriaHasPopup` accepts a wider role union (including tooltip-family
75
- // roles that map to `undefined`). `TAriaForTriggerRole` excludes those, so
76
- // the returned value is guaranteed to be a defined string. The cast narrows
77
- // the wider helper signature to match.
78
- var ariaHasPopup = (0, _roleToAriaHasPopup.roleToAriaHasPopup)({
79
- role: role
80
- });
81
95
  // `aria-controls` is set to `undefined` while closed. The `Popover` /
82
96
  // `Dialog` primitives unmount their host element after the exit
83
97
  // animation finishes, so a closed-state `aria-controls` would point
@@ -86,7 +100,7 @@ function getAriaForTrigger(_ref) {
86
100
  // until the target exists, avoiding the dangling reference while
87
101
  // keeping the relationship live whenever it is meaningful.
88
102
  return {
89
- 'aria-haspopup': ariaHasPopup,
103
+ 'aria-haspopup': roleToAriaHasPopup[role],
90
104
  'aria-expanded': isOpen,
91
105
  'aria-controls': isOpen ? popoverId : undefined
92
106
  };
@@ -1,64 +1 @@
1
- "use strict";
2
-
3
- Object.defineProperty(exports, "__esModule", {
4
- value: true
5
- });
6
- exports.shouldFocusIntoPopover = shouldFocusIntoPopover;
7
- /**
8
- * Valid values for the `aria-haspopup` attribute.
9
- *
10
- * Derived from the HTML spec - maps popover roles to what the trigger
11
- * announces. `undefined` means the attribute is omitted entirely (used for
12
- * non-popup roles like `tooltip`/`status`/`alert`/`note`/`log` where
13
- * `aria-haspopup` would be misleading).
14
- *
15
- * `true` is intentionally NOT in the union - the runtime only ever produces
16
- * the explicit string forms, so widening the type would invite consumers to
17
- * pass `true` and get an `aria-haspopup="true"` serialisation that the
18
- * runtime no longer emits.
19
- */
20
-
21
- /**
22
- * Roles that require an accessible name (`label` or `labelledBy`) per WCAG 4.1.2.
23
- * Without a name, assistive technology cannot identify these landmarks.
24
- */
25
-
26
- /**
27
- * Roles where the accessible name is derived from content or provided externally
28
- * (e.g. a combobox labels its listbox, tooltip content IS the accessible name).
29
- */
30
-
31
- // Shared ARIA role discrimination types
32
- //
33
- // These encode the WCAG 4.1.2 constraint: roles like `dialog`, `alertdialog`,
34
- // and `menu` MUST have an accessible name (either `label` or `labelledBy`).
35
- // TypeScript enforces this at compile time via discriminated unions.
36
-
37
- /**
38
- * ARIA role props where a role is always required.
39
- *
40
- * Used by `TPopupContentProps`. Popup content must always declare
41
- * its role so assistive technology can identify the layer.
42
- */
43
-
44
- /**
45
- * ARIA role props where a role is optional.
46
- *
47
- * Used by `TPopoverProps`. The low-level primitive allows omitting
48
- * the role for cases where it is set externally or not needed.
49
- */
50
-
51
- var rolesThatMoveFocus = new Set(['dialog', 'alertdialog', 'menu', 'listbox', 'tree', 'grid']);
52
-
53
- /**
54
- * Returns `true` when the given popover role moves focus into the popover
55
- * on open, and `false` otherwise (including when `role` is `undefined`).
56
- */
57
- // eslint-disable-next-line @atlaskit/volt-strict-mode/no-multiple-exports
58
- function shouldFocusIntoPopover(_ref) {
59
- var role = _ref.role;
60
- if (!role) {
61
- return false;
62
- }
63
- return rolesThatMoveFocus.has(role);
64
- }
1
+ "use strict";
@@ -324,11 +324,10 @@ function useAnchorPosition(_ref3) {
324
324
  * See `notes/decisions/anchor-name-lifetime.md`
325
325
  */
326
326
  trigger.style.setProperty('anchor-name', anchorName);
327
- var _undoPositioning = (0, _combine.combine)((0, _setStyle.setStyle)({
327
+ return (0, _setStyle.setStyle)({
328
328
  element: popover,
329
329
  styles: popoverStyles
330
- }));
331
- return _undoPositioning;
330
+ });
332
331
  }
333
332
 
334
333
  // JS fallback. The popover is already in the top layer via
@@ -344,6 +343,38 @@ function useAnchorPosition(_ref3) {
344
343
  value: 'auto'
345
344
  }]
346
345
  });
346
+
347
+ // The popover can be consumer-owned (`@atlaskit/popper`'s `createPopper`),
348
+ // so restore prior inline values rather than removing ours.
349
+ var undoPosition;
350
+ var undoHide;
351
+
352
+ // `opacity: 0` not `visibility: hidden`: Firefox skips visibility-hidden
353
+ // elements during `<dialog>` initial-focus traversal.
354
+ function hideUntilPositioned() {
355
+ // Keep the first snapshot, else we capture our own `opacity: 0`.
356
+ // (`!popover` is for TS: narrowing does not reach nested functions.)
357
+ if (!popover || undoHide) {
358
+ return;
359
+ }
360
+ undoHide = (0, _setStyle.setStyle)({
361
+ element: popover,
362
+ styles: [{
363
+ property: 'opacity',
364
+ value: '0'
365
+ }]
366
+ });
367
+ }
368
+ function reveal() {
369
+ var _undoHide;
370
+ (_undoHide = undoHide) === null || _undoHide === void 0 || _undoHide();
371
+ undoHide = undefined;
372
+ }
373
+ function restorePosition() {
374
+ var _undoPosition;
375
+ (_undoPosition = undoPosition) === null || _undoPosition === void 0 || _undoPosition();
376
+ undoPosition = undefined;
377
+ }
347
378
  function update() {
348
379
  if (!trigger || !popover) {
349
380
  return;
@@ -382,11 +413,20 @@ function useAnchorPosition(_ref3) {
382
413
  }),
383
414
  top = _computeFallbackPosit.top,
384
415
  left = _computeFallbackPosit.left;
385
- popover.style.setProperty('top', "".concat(top, "px"));
386
- popover.style.setProperty('left', "".concat(left, "px"));
387
- // Reveal the popover only after it has been positioned.
388
- // See the toggle listener below for why we hide on open.
389
- popover.style.removeProperty('opacity');
416
+
417
+ // Restore first so the snapshot stays the consumer's value.
418
+ restorePosition();
419
+ undoPosition = (0, _setStyle.setStyle)({
420
+ element: popover,
421
+ styles: [{
422
+ property: 'top',
423
+ value: "".concat(top, "px")
424
+ }, {
425
+ property: 'left',
426
+ value: "".concat(left, "px")
427
+ }]
428
+ });
429
+ reveal();
390
430
  }
391
431
 
392
432
  // Throttle scroll/resize updates to one per animation frame
@@ -420,22 +460,17 @@ function useAnchorPosition(_ref3) {
420
460
  // event has already fired and our listener below would miss it. Start
421
461
  // observing immediately so the first measurement still happens.
422
462
  if (isPopoverOpen(popover)) {
423
- popover.style.setProperty('opacity', '0');
463
+ hideUntilPositioned();
424
464
  resizeObserver.observe(popover);
425
465
  }
426
466
  var undoPositioning = (0, _combine.combine)(cleanupBaseStyles,
427
- // On every open: hide synchronously so the user never sees
428
- // the UA-default position, then re-observe for a fresh
429
- // measurement. We use `opacity: 0` rather than
430
- // `visibility: hidden` because Firefox skips visibility-hidden
431
- // elements during `<dialog>` initial-focus traversal. See
432
- // `form-in-popup.spec.tsx` on `desktop-firefox`.
467
+ // Each open needs a fresh measurement, so hide and re-observe.
433
468
  (0, _bindEventListener.bind)(popover, {
434
469
  type: 'toggle',
435
470
  listener: function listener(event) {
436
471
  var toggleEvent = event;
437
472
  if (toggleEvent.newState === 'open') {
438
- popover.style.setProperty('opacity', '0');
473
+ hideUntilPositioned();
439
474
  resizeObserver.observe(popover);
440
475
  }
441
476
  }
@@ -456,9 +491,8 @@ function useAnchorPosition(_ref3) {
456
491
  }
457
492
  }), function () {
458
493
  scheduledUpdate.cancel();
459
- popover.style.removeProperty('top');
460
- popover.style.removeProperty('left');
461
- popover.style.removeProperty('opacity');
494
+ restorePosition();
495
+ reveal();
462
496
  });
463
497
  return undoPositioning;
464
498
  }, [anchorRef, popoverRef, stablePlacement, forceFallbackPositioning, isEnabled, id, isOpen]);
@@ -16,7 +16,7 @@ var _mergeRefs = _interopRequireDefault(require("@atlaskit/ds-lib/merge-refs"));
16
16
  var _once = _interopRequireDefault(require("@atlaskit/ds-lib/once"));
17
17
  var _useNotifyOpenLayerObserver = require("@atlaskit/layering/use-notify-open-layer-observer");
18
18
  var _resolvePlacement2 = require("../internal/resolve-placement");
19
- var _roleTypes = require("../internal/role-types");
19
+ var _shouldFocusIntoPopover = require("../internal/should-focus-into-popover");
20
20
  var _useAnimatedVisibility = require("../internal/use-animated-visibility");
21
21
  var _useFocusWrap = require("../internal/use-focus-wrap");
22
22
  var _useInitialFocus = require("../internal/use-initial-focus");
@@ -234,7 +234,7 @@ var Popover = exports.Popover = /*#__PURE__*/(0, _react.forwardRef)(function Pop
234
234
  var previouslyFocused = previouslyFocusedElementRef.current;
235
235
  var _reason = closeReasonRef.current;
236
236
  var isProgrammatic = programmaticCloseRef.current;
237
- var shouldRestore = previouslyFocused !== null && previouslyFocused.isConnected && (0, _roleTypes.shouldFocusIntoPopover)({
237
+ var shouldRestore = previouslyFocused !== null && previouslyFocused.isConnected && (0, _shouldFocusIntoPopover.shouldFocusIntoPopover)({
238
238
  role: role
239
239
  }) && (_reason === 'escape' || isProgrammatic);
240
240
  if (shouldRestore && previouslyFocused) {
@@ -8,11 +8,5 @@ export function isInSameLayer({
8
8
  element,
9
9
  container
10
10
  }) {
11
- const elementLayer = element.closest(topLayerSelector);
12
- const containerLayer = container.closest(topLayerSelector);
13
- // Both elements are in the "document" layer
14
- if (!elementLayer && !containerLayer) {
15
- return true;
16
- }
17
- return elementLayer === containerLayer;
11
+ return element.closest(topLayerSelector) === container.closest(topLayerSelector);
18
12
  }
@@ -2,7 +2,7 @@ export const selectors = (() => {
2
2
  // Common exclusion filter applied to every selector.
3
3
  const not = ':not([tabindex="-1"]):not([aria-disabled="true"]):not([aria-hidden="true"])';
4
4
  const notDisabled = `:not([disabled])${not}`;
5
- const focusable = [`a[href]${not}`, `area[href]${not}`, `input${notDisabled}:not([type="hidden"])`, `select${notDisabled}`, `textarea${notDisabled}`, `button${notDisabled}`, `iframe${not}`, `audio[controls]${not}`, `video[controls]${not}`, `[contenteditable]:not([contenteditable="false"])${not}`, `[tabindex]${notDisabled}${not}`];
5
+ const focusable = [`a[href]${not}`, `area[href]${not}`, `input${notDisabled}:not([type="hidden"])`, `select${notDisabled}`, `textarea${notDisabled}`, `button${notDisabled}`, `iframe${not}`, `audio[controls]${not}`, `video[controls]${not}`, `[contenteditable]:not([contenteditable="false"])${not}`, `[tabindex]${notDisabled}`];
6
6
  return {
7
7
  focusable: focusable.join(','),
8
8
  // Build the `:focus` selector
@@ -1,5 +1,3 @@
1
- import { roleToAriaHasPopup } from './role-to-aria-has-popup';
2
-
3
1
  /**
4
2
  * Roles accepted by `getAriaForTrigger`.
5
3
  *
@@ -9,12 +7,34 @@ import { roleToAriaHasPopup } from './role-to-aria-has-popup';
9
7
  * manually for tooltip triggers.
10
8
  */
11
9
 
10
+ /**
11
+ * Valid values for the `aria-haspopup` attribute.
12
+ *
13
+ * Derived from the HTML spec - maps popover roles to what the trigger
14
+ * announces. `undefined` means the attribute is omitted entirely (used for
15
+ * non-popup roles like `tooltip`/`status`/`alert`/`note`/`log` where
16
+ * `aria-haspopup` would be misleading).
17
+ *
18
+ * `true` is intentionally NOT in the union - the runtime only ever produces
19
+ * the explicit string forms, so widening the type would invite consumers to
20
+ * pass `true` and get an `aria-haspopup="true"` serialisation that the
21
+ * runtime no longer emits.
22
+ */
23
+
12
24
  /**
13
25
  * Non-undefined subset of `TAriaHasPopupValue`. Every role accepted by
14
26
  * `getAriaForTrigger` maps to a concrete `aria-haspopup` string, so the
15
27
  * trigger always receives a defined attribute value.
16
28
  */
17
29
 
30
+ const roleToAriaHasPopup = {
31
+ dialog: 'dialog',
32
+ alertdialog: 'dialog',
33
+ menu: 'menu',
34
+ listbox: 'listbox',
35
+ tree: 'tree',
36
+ grid: 'grid'
37
+ };
18
38
  /**
19
39
  * Returns the ARIA attributes to spread onto a click-activated popover trigger.
20
40
  *
@@ -67,13 +87,6 @@ export function getAriaForTrigger({
67
87
  isOpen,
68
88
  popoverId
69
89
  }) {
70
- // `roleToAriaHasPopup` accepts a wider role union (including tooltip-family
71
- // roles that map to `undefined`). `TAriaForTriggerRole` excludes those, so
72
- // the returned value is guaranteed to be a defined string. The cast narrows
73
- // the wider helper signature to match.
74
- const ariaHasPopup = roleToAriaHasPopup({
75
- role
76
- });
77
90
  // `aria-controls` is set to `undefined` while closed. The `Popover` /
78
91
  // `Dialog` primitives unmount their host element after the exit
79
92
  // animation finishes, so a closed-state `aria-controls` would point
@@ -82,7 +95,7 @@ export function getAriaForTrigger({
82
95
  // until the target exists, avoiding the dangling reference while
83
96
  // keeping the relationship live whenever it is meaningful.
84
97
  return {
85
- 'aria-haspopup': ariaHasPopup,
98
+ 'aria-haspopup': roleToAriaHasPopup[role],
86
99
  'aria-expanded': isOpen,
87
100
  'aria-controls': isOpen ? popoverId : undefined
88
101
  };
@@ -1,59 +0,0 @@
1
- /**
2
- * Valid values for the `aria-haspopup` attribute.
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.
13
- */
14
-
15
- /**
16
- * Roles that require an accessible name (`label` or `labelledBy`) per WCAG 4.1.2.
17
- * Without a name, assistive technology cannot identify these landmarks.
18
- */
19
-
20
- /**
21
- * Roles where the accessible name is derived from content or provided externally
22
- * (e.g. a combobox labels its listbox, tooltip content IS the accessible name).
23
- */
24
-
25
- // Shared ARIA role discrimination types
26
- //
27
- // These encode the WCAG 4.1.2 constraint: roles like `dialog`, `alertdialog`,
28
- // and `menu` MUST have an accessible name (either `label` or `labelledBy`).
29
- // TypeScript enforces this at compile time via discriminated unions.
30
-
31
- /**
32
- * ARIA role props where a role is always required.
33
- *
34
- * Used by `TPopupContentProps`. Popup content must always declare
35
- * its role so assistive technology can identify the layer.
36
- */
37
-
38
- /**
39
- * ARIA role props where a role is optional.
40
- *
41
- * Used by `TPopoverProps`. The low-level primitive allows omitting
42
- * the role for cases where it is set externally or not needed.
43
- */
44
-
45
- const rolesThatMoveFocus = new Set(['dialog', 'alertdialog', 'menu', 'listbox', 'tree', 'grid']);
46
-
47
- /**
48
- * Returns `true` when the given popover role moves focus into the popover
49
- * on open, and `false` otherwise (including when `role` is `undefined`).
50
- */
51
- // eslint-disable-next-line @atlaskit/volt-strict-mode/no-multiple-exports
52
- export function shouldFocusIntoPopover({
53
- role
54
- }) {
55
- if (!role) {
56
- return false;
57
- }
58
- return rolesThatMoveFocus.has(role);
59
- }
@@ -307,11 +307,10 @@ export function useAnchorPosition({
307
307
  * See `notes/decisions/anchor-name-lifetime.md`
308
308
  */
309
309
  trigger.style.setProperty('anchor-name', anchorName);
310
- const undoPositioning = combine(setStyle({
310
+ return setStyle({
311
311
  element: popover,
312
312
  styles: popoverStyles
313
- }));
314
- return undoPositioning;
313
+ });
315
314
  }
316
315
 
317
316
  // JS fallback. The popover is already in the top layer via
@@ -327,6 +326,38 @@ export function useAnchorPosition({
327
326
  value: 'auto'
328
327
  }]
329
328
  });
329
+
330
+ // The popover can be consumer-owned (`@atlaskit/popper`'s `createPopper`),
331
+ // so restore prior inline values rather than removing ours.
332
+ let undoPosition;
333
+ let undoHide;
334
+
335
+ // `opacity: 0` not `visibility: hidden`: Firefox skips visibility-hidden
336
+ // elements during `<dialog>` initial-focus traversal.
337
+ function hideUntilPositioned() {
338
+ // Keep the first snapshot, else we capture our own `opacity: 0`.
339
+ // (`!popover` is for TS: narrowing does not reach nested functions.)
340
+ if (!popover || undoHide) {
341
+ return;
342
+ }
343
+ undoHide = setStyle({
344
+ element: popover,
345
+ styles: [{
346
+ property: 'opacity',
347
+ value: '0'
348
+ }]
349
+ });
350
+ }
351
+ function reveal() {
352
+ var _undoHide;
353
+ (_undoHide = undoHide) === null || _undoHide === void 0 ? void 0 : _undoHide();
354
+ undoHide = undefined;
355
+ }
356
+ function restorePosition() {
357
+ var _undoPosition;
358
+ (_undoPosition = undoPosition) === null || _undoPosition === void 0 ? void 0 : _undoPosition();
359
+ undoPosition = undefined;
360
+ }
330
361
  function update() {
331
362
  if (!trigger || !popover) {
332
363
  return;
@@ -366,11 +397,20 @@ export function useAnchorPosition({
366
397
  direction: stablePlacement.offset.crossAxisShift.direction
367
398
  }
368
399
  });
369
- popover.style.setProperty('top', `${top}px`);
370
- popover.style.setProperty('left', `${left}px`);
371
- // Reveal the popover only after it has been positioned.
372
- // See the toggle listener below for why we hide on open.
373
- popover.style.removeProperty('opacity');
400
+
401
+ // Restore first so the snapshot stays the consumer's value.
402
+ restorePosition();
403
+ undoPosition = setStyle({
404
+ element: popover,
405
+ styles: [{
406
+ property: 'top',
407
+ value: `${top}px`
408
+ }, {
409
+ property: 'left',
410
+ value: `${left}px`
411
+ }]
412
+ });
413
+ reveal();
374
414
  }
375
415
 
376
416
  // Throttle scroll/resize updates to one per animation frame
@@ -404,22 +444,17 @@ export function useAnchorPosition({
404
444
  // event has already fired and our listener below would miss it. Start
405
445
  // observing immediately so the first measurement still happens.
406
446
  if (isPopoverOpen(popover)) {
407
- popover.style.setProperty('opacity', '0');
447
+ hideUntilPositioned();
408
448
  resizeObserver.observe(popover);
409
449
  }
410
450
  const undoPositioning = combine(cleanupBaseStyles,
411
- // On every open: hide synchronously so the user never sees
412
- // the UA-default position, then re-observe for a fresh
413
- // measurement. We use `opacity: 0` rather than
414
- // `visibility: hidden` because Firefox skips visibility-hidden
415
- // elements during `<dialog>` initial-focus traversal. See
416
- // `form-in-popup.spec.tsx` on `desktop-firefox`.
451
+ // Each open needs a fresh measurement, so hide and re-observe.
417
452
  bind(popover, {
418
453
  type: 'toggle',
419
454
  listener: event => {
420
455
  const toggleEvent = event;
421
456
  if (toggleEvent.newState === 'open') {
422
- popover.style.setProperty('opacity', '0');
457
+ hideUntilPositioned();
423
458
  resizeObserver.observe(popover);
424
459
  }
425
460
  }
@@ -438,9 +473,8 @@ export function useAnchorPosition({
438
473
  }
439
474
  }), () => {
440
475
  scheduledUpdate.cancel();
441
- popover.style.removeProperty('top');
442
- popover.style.removeProperty('left');
443
- popover.style.removeProperty('opacity');
476
+ restorePosition();
477
+ reveal();
444
478
  });
445
479
  return undoPositioning;
446
480
  }, [anchorRef, popoverRef, stablePlacement, forceFallbackPositioning, isEnabled, id, isOpen]);
@@ -8,7 +8,7 @@ import mergeRefs from '@atlaskit/ds-lib/merge-refs';
8
8
  import once from '@atlaskit/ds-lib/once';
9
9
  import { useNotifyOpenLayerObserver } from '@atlaskit/layering/use-notify-open-layer-observer';
10
10
  import { resolvePlacement } from '../internal/resolve-placement';
11
- import { shouldFocusIntoPopover } from '../internal/role-types';
11
+ import { shouldFocusIntoPopover } from '../internal/should-focus-into-popover';
12
12
  import { useAnimatedVisibility } from '../internal/use-animated-visibility';
13
13
  import { useFocusWrap } from '../internal/use-focus-wrap';
14
14
  import { useInitialFocus } from '../internal/use-initial-focus';
@@ -7,11 +7,5 @@ var topLayerSelector = '[popover], dialog, [role="dialog"], [role="alertdialog"]
7
7
  export function isInSameLayer(_ref) {
8
8
  var element = _ref.element,
9
9
  container = _ref.container;
10
- var elementLayer = element.closest(topLayerSelector);
11
- var containerLayer = container.closest(topLayerSelector);
12
- // Both elements are in the "document" layer
13
- if (!elementLayer && !containerLayer) {
14
- return true;
15
- }
16
- return elementLayer === containerLayer;
10
+ return element.closest(topLayerSelector) === container.closest(topLayerSelector);
17
11
  }
@@ -2,7 +2,7 @@ export var selectors = function () {
2
2
  // Common exclusion filter applied to every selector.
3
3
  var not = ':not([tabindex="-1"]):not([aria-disabled="true"]):not([aria-hidden="true"])';
4
4
  var notDisabled = ":not([disabled])".concat(not);
5
- 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)];
5
+ 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)];
6
6
  return {
7
7
  focusable: focusable.join(','),
8
8
  // Build the `:focus` selector