@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.
- package/AGENTS.md +3 -1
- package/CHANGELOG.md +26 -0
- package/README.md +0 -14
- package/dist/cjs/focus/is-in-same-layer.js +1 -7
- package/dist/cjs/focus/selectors.js +1 -1
- package/dist/cjs/internal/get-aria-for-trigger.js +23 -9
- package/dist/cjs/internal/role-types.js +1 -64
- package/dist/cjs/internal/use-anchor-position.js +53 -19
- package/dist/cjs/popover/popover.js +2 -2
- package/dist/es2019/focus/is-in-same-layer.js +1 -7
- package/dist/es2019/focus/selectors.js +1 -1
- package/dist/es2019/internal/get-aria-for-trigger.js +23 -10
- package/dist/es2019/internal/role-types.js +0 -59
- package/dist/es2019/internal/use-anchor-position.js +53 -19
- package/dist/es2019/popover/popover.js +1 -1
- package/dist/esm/focus/is-in-same-layer.js +1 -7
- package/dist/esm/focus/selectors.js +1 -1
- package/dist/esm/internal/get-aria-for-trigger.js +23 -10
- package/dist/esm/internal/role-types.js +0 -58
- package/dist/esm/internal/use-anchor-position.js +53 -19
- package/dist/esm/popover/popover.js +1 -1
- package/dist/types/internal/get-aria-for-trigger.d.ts +21 -7
- package/dist/types/internal/role-types.d.ts +0 -32
- package/dist/types/popover/types.d.ts +1 -1
- package/package.json +3 -30
- package/animations/package.json +0 -10
- package/dist/cjs/entry-points/animations.js +0 -1
- package/dist/cjs/internal/role-to-aria-has-popup.js +0 -37
- package/dist/cjs/internal/slot.js +0 -65
- package/dist/es2019/entry-points/animations.js +0 -0
- package/dist/es2019/internal/role-to-aria-has-popup.js +0 -32
- package/dist/es2019/internal/slot.js +0 -57
- package/dist/esm/entry-points/animations.js +0 -0
- package/dist/esm/internal/role-to-aria-has-popup.js +0 -31
- package/dist/esm/internal/slot.js +0 -59
- package/dist/types/entry-points/animations.d.ts +0 -0
- package/dist/types/internal/role-to-aria-has-popup.d.ts +0 -13
- 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
|
|
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
|
-
|
|
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)
|
|
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':
|
|
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
|
-
|
|
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
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
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
|
-
|
|
463
|
+
hideUntilPositioned();
|
|
424
464
|
resizeObserver.observe(popover);
|
|
425
465
|
}
|
|
426
466
|
var undoPositioning = (0, _combine.combine)(cleanupBaseStyles,
|
|
427
|
-
//
|
|
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
|
-
|
|
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
|
-
|
|
460
|
-
|
|
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
|
|
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,
|
|
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
|
-
|
|
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}
|
|
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':
|
|
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
|
-
|
|
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
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
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
|
-
|
|
447
|
+
hideUntilPositioned();
|
|
408
448
|
resizeObserver.observe(popover);
|
|
409
449
|
}
|
|
410
450
|
const undoPositioning = combine(cleanupBaseStyles,
|
|
411
|
-
//
|
|
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
|
-
|
|
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
|
-
|
|
442
|
-
|
|
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/
|
|
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
|
-
|
|
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)
|
|
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
|