@atlaskit/top-layer 0.15.0 → 0.17.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/CHANGELOG.md +45 -0
- package/README.md +4 -4
- package/dist/cjs/animations/presets.js +96 -9
- package/dist/cjs/dialog/dialog-content.js +33 -4
- package/dist/cjs/dialog-scroll-lock/index.js +17 -7
- package/dist/cjs/entry-points/animations.js +12 -0
- package/dist/cjs/entry-points/use-popover-id.js +3 -1
- package/dist/cjs/internal/get-aria-for-trigger.js +8 -3
- package/dist/cjs/internal/use-anchor-position-at-point.js +4 -2
- package/dist/cjs/internal/use-anchor-position.js +3 -2
- package/dist/cjs/internal/use-animated-visibility.js +233 -155
- package/dist/cjs/internal/use-focus-wrap.js +5 -3
- package/dist/cjs/internal/use-initial-focus.js +196 -50
- package/dist/cjs/internal/use-width-from-anchor.js +5 -4
- package/dist/cjs/popover/popover.js +58 -9
- package/dist/es2019/animations/presets.js +145 -2
- package/dist/es2019/dialog/dialog-content.js +33 -4
- package/dist/es2019/dialog-scroll-lock/index.js +18 -8
- package/dist/es2019/entry-points/animations.js +1 -1
- package/dist/es2019/entry-points/use-popover-id.js +3 -1
- package/dist/es2019/internal/get-aria-for-trigger.js +8 -3
- package/dist/es2019/internal/use-anchor-position-at-point.js +4 -2
- package/dist/es2019/internal/use-anchor-position.js +3 -2
- package/dist/es2019/internal/use-animated-visibility.js +222 -149
- package/dist/es2019/internal/use-focus-wrap.js +5 -4
- package/dist/es2019/internal/use-initial-focus.js +189 -45
- package/dist/es2019/internal/use-width-from-anchor.js +5 -4
- package/dist/es2019/popover/popover.js +59 -10
- package/dist/esm/animations/presets.js +93 -9
- package/dist/esm/dialog/dialog-content.js +33 -4
- package/dist/esm/dialog-scroll-lock/index.js +17 -8
- package/dist/esm/entry-points/animations.js +1 -1
- package/dist/esm/entry-points/use-popover-id.js +3 -1
- package/dist/esm/internal/get-aria-for-trigger.js +8 -3
- package/dist/esm/internal/use-anchor-position-at-point.js +4 -2
- package/dist/esm/internal/use-anchor-position.js +3 -2
- package/dist/esm/internal/use-animated-visibility.js +235 -157
- package/dist/esm/internal/use-focus-wrap.js +5 -4
- package/dist/esm/internal/use-initial-focus.js +196 -51
- package/dist/esm/internal/use-width-from-anchor.js +5 -4
- package/dist/esm/popover/popover.js +59 -10
- package/dist/types/animations/presets.d.ts +16 -0
- package/dist/types/dialog/types.d.ts +9 -1
- package/dist/types/dialog-scroll-lock/index.d.ts +22 -6
- package/dist/types/entry-points/animations.d.ts +1 -1
- package/dist/types/internal/get-aria-for-trigger.d.ts +8 -1
- package/dist/types/internal/use-anchor-position-at-point.d.ts +6 -1
- package/dist/types/internal/use-anchor-position.d.ts +14 -1
- package/dist/types/internal/use-animated-visibility.d.ts +48 -19
- package/dist/types/internal/use-focus-wrap.d.ts +6 -1
- package/dist/types/internal/use-initial-focus.d.ts +55 -4
- package/dist/types/internal/use-width-from-anchor.d.ts +3 -2
- package/dist/types/popover/types.d.ts +13 -2
- package/dist/types-ts4.5/animations/presets.d.ts +16 -0
- package/dist/types-ts4.5/dialog/types.d.ts +9 -1
- package/dist/types-ts4.5/dialog-scroll-lock/index.d.ts +22 -6
- package/dist/types-ts4.5/entry-points/animations.d.ts +1 -1
- package/dist/types-ts4.5/internal/get-aria-for-trigger.d.ts +8 -1
- package/dist/types-ts4.5/internal/use-anchor-position-at-point.d.ts +6 -1
- package/dist/types-ts4.5/internal/use-anchor-position.d.ts +14 -1
- package/dist/types-ts4.5/internal/use-animated-visibility.d.ts +48 -19
- package/dist/types-ts4.5/internal/use-focus-wrap.d.ts +6 -1
- package/dist/types-ts4.5/internal/use-initial-focus.d.ts +55 -4
- package/dist/types-ts4.5/internal/use-width-from-anchor.d.ts +3 -2
- package/dist/types-ts4.5/popover/types.d.ts +13 -2
- package/package.json +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,50 @@
|
|
|
1
1
|
# @atlaskit/top-layer
|
|
2
2
|
|
|
3
|
+
## 0.17.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- [`2fd74328732bf`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/2fd74328732bf) -
|
|
8
|
+
Add dialogMotion animation preset with token-based motion values and backdrop animation
|
|
9
|
+
|
|
10
|
+
## 0.16.0
|
|
11
|
+
|
|
12
|
+
### Minor Changes
|
|
13
|
+
|
|
14
|
+
- [`6d0485dce81c4`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/6d0485dce81c4) -
|
|
15
|
+
`Popover` and `Dialog` now unmount their host element after the exit animation completes, instead
|
|
16
|
+
of leaving an empty `role` / `aria-*` element in the accessibility tree.
|
|
17
|
+
|
|
18
|
+
Other small changes that fall out of this:
|
|
19
|
+
- The popover/dialog `id` stays stable across opens.
|
|
20
|
+
- `ref.current` is only populated while the host element is rendered. Read the ref inside
|
|
21
|
+
`onOpenChange` / `onEnterFinish`, or gate the read on `isOpen`.
|
|
22
|
+
- `onEnterFinish` and `onExitFinish` now fire before the internal phase transition that drives the
|
|
23
|
+
unmount, so consumers reading the host element (or a forwarded ref) inside those callbacks
|
|
24
|
+
always see it attached to the DOM, regardless of React batching semantics.
|
|
25
|
+
- `getAriaForTrigger` returns `aria-controls: undefined` while `isOpen` is `false`; spreading the
|
|
26
|
+
result onto a JSX element strips the attribute from the DOM and avoids a dangling reference.
|
|
27
|
+
- `useAnchorPosition`, `useAnchorPositionAtPoint`, and `useWidthFromAnchor` now require `isOpen`
|
|
28
|
+
so positioning re-runs against the freshly mounted host element on each open.
|
|
29
|
+
`useWidthFromAnchor` also requires `anchorRef`.
|
|
30
|
+
- `DialogScrollLock` now requires an `isOpen` prop so the scroll lock is released the moment
|
|
31
|
+
`isOpen` flips to `false`, regardless of React's unmount timing.
|
|
32
|
+
- `useInitialFocus` now follows the WAI-ARIA APG Combobox Pattern: when a `menu` or `listbox`
|
|
33
|
+
popup is controlled by a focused combobox-like textbox whose `aria-controls` (or legacy
|
|
34
|
+
`aria-owns`) references the popup, initial focus stays on the textbox instead of moving into the
|
|
35
|
+
popup. Navigation is expected to be proxied via `aria-activedescendant`. Both variants are
|
|
36
|
+
detected:
|
|
37
|
+
- **In-popup combobox**: the focused textbox lives inside the popup.
|
|
38
|
+
- **External combobox**: the focused textbox is rendered OUTSIDE the popup (for example
|
|
39
|
+
`react-select`-style menus that portal the listbox). The carve-out also accepts the case where
|
|
40
|
+
the textbox's `aria-controls` references a DESCENDANT of the popup host (such as
|
|
41
|
+
react-select's inner `MenuList` element). Without this carve-out, moving focus into the
|
|
42
|
+
listbox would blur the external textbox and cause libraries like `react-select` to close the
|
|
43
|
+
menu on the same interaction that opened it.
|
|
44
|
+
|
|
45
|
+
Popups that are not opened from a combobox-like trigger continue to receive their
|
|
46
|
+
role-appropriate initial focus.
|
|
47
|
+
|
|
3
48
|
## 0.15.0
|
|
4
49
|
|
|
5
50
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -55,8 +55,8 @@ function MyPopup({ isOpen, onClose }: { isOpen: boolean; onClose: () => void })
|
|
|
55
55
|
}
|
|
56
56
|
```
|
|
57
57
|
|
|
58
|
-
- Focus restoration is automatic. The browser handles it for outermost popovers; `Popover`
|
|
59
|
-
|
|
58
|
+
- Focus restoration is automatic. The browser handles it for outermost popovers; `Popover` snapshots
|
|
59
|
+
`document.activeElement` on open and restores it on close for nested cases.
|
|
60
60
|
- For default overlay styling (background, shadow, border-radius), wrap content in `PopoverSurface`
|
|
61
61
|
from `@atlaskit/top-layer/popover-surface`.
|
|
62
62
|
- For trigger-less or custom-positioned UI (flag, tooltip, react-select menu portal), use `Popover`
|
|
@@ -88,8 +88,8 @@ Compound component for modal dialogs using the native `<dialog>` element with `.
|
|
|
88
88
|
## Animations
|
|
89
89
|
|
|
90
90
|
Both `Popover` and `Dialog` support CSS-based entry/exit animations via the `animate` prop.
|
|
91
|
-
Animations use
|
|
92
|
-
|
|
91
|
+
Animations use [`@starting-style`](https://developer.mozilla.org/en-US/docs/Web/CSS/@starting-style)
|
|
92
|
+
for entry and
|
|
93
93
|
[`allow-discrete`](https://developer.mozilla.org/en-US/docs/Web/CSS/transition-behavior) on
|
|
94
94
|
`display`/`overlay` for exit — no JavaScript animation coordination required.
|
|
95
95
|
|
|
@@ -4,23 +4,80 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
4
4
|
value: true
|
|
5
5
|
});
|
|
6
6
|
exports.dialogFade = dialogFade;
|
|
7
|
+
exports.dialogMotion = dialogMotion;
|
|
7
8
|
exports.dialogSlideUpAndFade = dialogSlideUpAndFade;
|
|
8
9
|
exports.fade = fade;
|
|
10
|
+
exports.popupMotion = popupMotion;
|
|
9
11
|
exports.scaleAndFade = scaleAndFade;
|
|
10
12
|
exports.slideAndFade = slideAndFade;
|
|
11
13
|
var _resolvePlacement = require("../internal/resolve-placement");
|
|
12
|
-
//
|
|
14
|
+
// ══════════════════════════════════════════════════════════════════════════════
|
|
15
|
+
// Popover presets
|
|
16
|
+
// ══════════════════════════════════════════════════════════════════════════════
|
|
13
17
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
var
|
|
18
|
+
// ── popupMotion ──
|
|
19
|
+
|
|
20
|
+
var POPUP_MOTION_CSS = "\n [data-ds-popover-popup-motion] {\n animation: var(--ds-popover-motion-exit);\n animation-fill-mode: forwards;\n transition:\n overlay 150ms allow-discrete,\n display 150ms allow-discrete;\n }\n\n [data-ds-popover-popup-motion]:popover-open {\n animation: var(--ds-popover-motion-enter);\n animation-fill-mode: backwards;\n }\n\n @media (prefers-reduced-motion: reduce) {\n [data-ds-popover-popup-motion],\n [data-ds-popover-popup-motion]:popover-open {\n animation: none;\n transition-duration: 0s;\n }\n }\n";
|
|
21
|
+
function getPopupMotionProperties(_ref) {
|
|
22
|
+
var placement = _ref.placement;
|
|
19
23
|
var _getPlacement = (0, _resolvePlacement.getPlacement)({
|
|
20
24
|
placement: placement
|
|
21
25
|
}),
|
|
22
26
|
axis = _getPlacement.axis,
|
|
23
27
|
edge = _getPlacement.edge;
|
|
28
|
+
if (axis === 'block' && edge === 'start') {
|
|
29
|
+
return {
|
|
30
|
+
'--ds-popover-motion-enter': "var(--ds-popup-enter-top, 150ms cubic-bezier(0.4, 1, 0.6, 1) SlideInTop8px, 150ms cubic-bezier(0.4, 1, 0.6, 1) FadeIn0to100)",
|
|
31
|
+
'--ds-popover-motion-exit': "var(--ds-popup-exit-top, 100ms cubic-bezier(0.6, 0, 0.8, 0.6) SlideOutTop8px, 100ms cubic-bezier(0.6, 0, 0.8, 0.6) FadeOut100to0)"
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
if (axis === 'inline' && edge === 'start') {
|
|
35
|
+
return {
|
|
36
|
+
'--ds-popover-motion-enter': "var(--ds-popup-enter-left, 150ms cubic-bezier(0.4, 1, 0.6, 1) SlideInLeft8px, 150ms cubic-bezier(0.4, 1, 0.6, 1) FadeIn0to100)",
|
|
37
|
+
'--ds-popover-motion-exit': "var(--ds-popup-exit-left, 100ms cubic-bezier(0.6, 0, 0.8, 0.6) SlideOutLeft8px, 100ms cubic-bezier(0.6, 0, 0.8, 0.6) FadeOut100to0)"
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
if (axis === 'inline' && edge === 'end') {
|
|
41
|
+
return {
|
|
42
|
+
'--ds-popover-motion-enter': "var(--ds-popup-enter-right, 150ms cubic-bezier(0.4, 1, 0.6, 1) SlideInRight8px, 150ms cubic-bezier(0.4, 1, 0.6, 1) FadeIn0to100)",
|
|
43
|
+
'--ds-popover-motion-exit': "var(--ds-popup-exit-right, 100ms cubic-bezier(0.6, 0, 0.8, 0.6) SlideOutRight8px, 100ms cubic-bezier(0.6, 0, 0.8, 0.6) FadeOut100to0)"
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
// Default: block/end (popover below trigger)
|
|
47
|
+
return {
|
|
48
|
+
'--ds-popover-motion-enter': "var(--ds-popup-enter-bottom, 150ms cubic-bezier(0.4, 1, 0.6, 1) SlideInBottom8px, 150ms cubic-bezier(0.4, 1, 0.6, 1) FadeIn0to100)",
|
|
49
|
+
'--ds-popover-motion-exit': "var(--ds-popup-exit-bottom, 100ms cubic-bezier(0.6, 0, 0.8, 0.6) SlideOutBottom8px, 100ms cubic-bezier(0.6, 0, 0.8, 0.6) FadeOut100to0)"
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// eslint-disable-next-line @atlaskit/volt-strict-mode/no-multiple-exports
|
|
54
|
+
function popupMotion() {
|
|
55
|
+
return {
|
|
56
|
+
name: 'popup-motion',
|
|
57
|
+
css: POPUP_MOTION_CSS,
|
|
58
|
+
enterDurationMs: 150,
|
|
59
|
+
exitDurationMs: 100,
|
|
60
|
+
getProperties: function getProperties(_ref2) {
|
|
61
|
+
var placement = _ref2.placement;
|
|
62
|
+
return getPopupMotionProperties({
|
|
63
|
+
placement: placement
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// ── slideAndFade ──
|
|
70
|
+
|
|
71
|
+
function getSlideAndFadeProperties(_ref3) {
|
|
72
|
+
var placement = _ref3.placement,
|
|
73
|
+
distance = _ref3.distance;
|
|
74
|
+
var pos = "".concat(distance, "px");
|
|
75
|
+
var neg = "-".concat(distance, "px");
|
|
76
|
+
var _getPlacement2 = (0, _resolvePlacement.getPlacement)({
|
|
77
|
+
placement: placement
|
|
78
|
+
}),
|
|
79
|
+
axis = _getPlacement2.axis,
|
|
80
|
+
edge = _getPlacement2.edge;
|
|
24
81
|
if (axis === 'block' && edge === 'start') {
|
|
25
82
|
return {
|
|
26
83
|
'--ds-popover-tx': '0',
|
|
@@ -73,14 +130,15 @@ var SLIDE_AND_FADE_CSS = "\n[data-ds-popover-slide-and-fade] {\n opacity: 0;\n
|
|
|
73
130
|
* <Popover animate={slideAndFade({ distance: 8 })} />
|
|
74
131
|
* ```
|
|
75
132
|
*/
|
|
133
|
+
// eslint-disable-next-line @atlaskit/volt-strict-mode/no-multiple-exports
|
|
76
134
|
function slideAndFade(options) {
|
|
77
135
|
var _options$distance;
|
|
78
136
|
var distance = (_options$distance = options === null || options === void 0 ? void 0 : options.distance) !== null && _options$distance !== void 0 ? _options$distance : 4;
|
|
79
137
|
return {
|
|
80
138
|
name: 'slide-and-fade',
|
|
81
139
|
css: SLIDE_AND_FADE_CSS,
|
|
82
|
-
getProperties: function getProperties(
|
|
83
|
-
var placement =
|
|
140
|
+
getProperties: function getProperties(_ref4) {
|
|
141
|
+
var placement = _ref4.placement;
|
|
84
142
|
return getSlideAndFadeProperties({
|
|
85
143
|
placement: placement,
|
|
86
144
|
distance: distance
|
|
@@ -147,7 +205,36 @@ function scaleAndFade() {
|
|
|
147
205
|
// Dialog presets
|
|
148
206
|
// ══════════════════════════════════════════════════════════════════════════════
|
|
149
207
|
|
|
150
|
-
//
|
|
208
|
+
// ── dialogMotion ──
|
|
209
|
+
|
|
210
|
+
var DIALOG_MOTION_CSS = "\n [data-ds-dialog-motion] {\n animation: ".concat("var(--ds-modal-exit, 200ms cubic-bezier(0.6, 0, 0.8, 0.6) ScaleOut100to95)", ";\n animation-fill-mode: forwards;\n transition:\n overlay 200ms allow-discrete,\n display 200ms allow-discrete;\n }\n\n [data-ds-dialog-motion][open] {\n animation: ", "var(--ds-modal-enter, 250ms cubic-bezier(0.4, 0, 0, 1) ScaleIn95to100)", ";\n animation-fill-mode: backwards;\n }\n\n [data-ds-dialog-motion]::backdrop {\n animation: ", "var(--ds-blanket-exit, 200ms cubic-bezier(0.6, 0, 0.8, 0.6) FadeOut100to0)", ";\n animation-fill-mode: forwards;\n background-color: var(--ds-blanket, #050C1F75);\n transition:\n overlay 200ms allow-discrete,\n display 200ms allow-discrete;\n }\n\n [data-ds-dialog-motion][open]::backdrop {\n animation: ", "var(--ds-blanket-enter, 250ms cubic-bezier(0.4, 0, 0, 1) FadeIn0to100)", ";\n animation-fill-mode: backwards;\n }\n\n @media (prefers-reduced-motion: reduce) {\n [data-ds-dialog-motion],\n [data-ds-dialog-motion][open],\n [data-ds-dialog-motion]::backdrop,\n [data-ds-dialog-motion][open]::backdrop {\n animation: none;\n transition-duration: 0s;\n }\n }\n");
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Dialog motion animation.
|
|
214
|
+
*
|
|
215
|
+
* Includes backdrop fade animation.
|
|
216
|
+
*
|
|
217
|
+
* @example
|
|
218
|
+
* ```tsx
|
|
219
|
+
* import { dialogMotion } from '@atlaskit/top-layer/animations';
|
|
220
|
+
*
|
|
221
|
+
* <Dialog animate={dialogMotion()} isOpen={isOpen} onClose={handleClose} label="...">
|
|
222
|
+
* ...
|
|
223
|
+
* </Dialog>
|
|
224
|
+
* ```
|
|
225
|
+
*/
|
|
226
|
+
// eslint-disable-next-line @atlaskit/volt-strict-mode/no-multiple-exports
|
|
227
|
+
function dialogMotion() {
|
|
228
|
+
// Bare preset name; `Dialog` prefixes it via `data-ds-dialog-{name}`.
|
|
229
|
+
return {
|
|
230
|
+
name: 'motion',
|
|
231
|
+
css: DIALOG_MOTION_CSS,
|
|
232
|
+
enterDurationMs: 250,
|
|
233
|
+
exitDurationMs: 200
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// ── dialogSlideUpAndFade ──
|
|
151
238
|
|
|
152
239
|
var DIALOG_SLIDE_UP_AND_FADE_CSS = "\n[data-ds-dialog-slide-up-and-fade] {\n opacity: 0;\n transform: translateY(calc(-1 * var(--ds-dialog-ty, 12px)));\n transition:\n opacity 175ms cubic-bezier(0.15, 1, 0.3, 1),\n transform 175ms cubic-bezier(0.15, 1, 0.3, 1),\n overlay 175ms allow-discrete,\n display 175ms allow-discrete;\n}\n\n[data-ds-dialog-slide-up-and-fade][open] {\n opacity: 1;\n transform: none;\n transition-duration: 350ms;\n}\n\n@starting-style {\n [data-ds-dialog-slide-up-and-fade][open] {\n opacity: 0;\n transform: translateY(var(--ds-dialog-ty, 12px));\n }\n}\n\n[data-ds-dialog-slide-up-and-fade]::backdrop {\n background-color: transparent;\n transition:\n background-color 175ms cubic-bezier(0.15, 1, 0.3, 1),\n overlay 175ms allow-discrete,\n display 175ms allow-discrete;\n}\n\n[data-ds-dialog-slide-up-and-fade][open]::backdrop {\n background-color: var(--ds-blanket, #050C1F75);\n transition-duration: 350ms;\n}\n\n@starting-style {\n [data-ds-dialog-slide-up-and-fade][open]::backdrop {\n background-color: transparent;\n }\n}\n\n@media (prefers-reduced-motion: reduce) {\n [data-ds-dialog-slide-up-and-fade],\n [data-ds-dialog-slide-up-and-fade][open],\n [data-ds-dialog-slide-up-and-fade]::backdrop,\n [data-ds-dialog-slide-up-and-fade][open]::backdrop {\n transition-duration: 0s;\n }\n}\n";
|
|
153
240
|
|
|
@@ -65,16 +65,30 @@ var Dialog = exports.Dialog = /*#__PURE__*/(0, _react.forwardRef)(function Dialo
|
|
|
65
65
|
elementRef: ownRef,
|
|
66
66
|
onExitFinish: onExitFinish
|
|
67
67
|
}),
|
|
68
|
-
|
|
68
|
+
phase = _useAnimatedVisibilit.phase,
|
|
69
69
|
preset = _useAnimatedVisibilit.preset;
|
|
70
70
|
|
|
71
|
+
// Pre-computed phase predicate. `isVisible` means "host element is
|
|
72
|
+
// mounted and on screen" (any phase except `closed`), used as a dep
|
|
73
|
+
// signal for the backdrop-click rebind effect so it re-attaches to
|
|
74
|
+
// the fresh <dialog> element after a close → reopen unmount/remount
|
|
75
|
+
// cycle.
|
|
76
|
+
var isVisible = phase !== 'closed';
|
|
77
|
+
|
|
71
78
|
// Focus wrap
|
|
72
79
|
// Native <dialog>.showModal() traps focus but wraps through <body> at
|
|
73
80
|
// the boundary (A → B → C → body → A). This hook intercepts Tab to
|
|
74
81
|
// wrap directly (A → B → C → A), matching the WAI-ARIA APG pattern.
|
|
82
|
+
//
|
|
83
|
+
// Takes `phase` directly: the listener stays attached while
|
|
84
|
+
// `phase !== 'closed'`, i.e. through the animated-exit window.
|
|
85
|
+
// Gating on `isOpen` would unbind the listener at the start of an
|
|
86
|
+
// exit animation while the dialog is still visible, letting focus
|
|
87
|
+
// escape (WCAG 2.4.3 Focus Order regression).
|
|
75
88
|
(0, _useFocusWrap.useFocusWrap)({
|
|
76
89
|
elementRef: ownRef,
|
|
77
|
-
role: 'dialog'
|
|
90
|
+
role: 'dialog',
|
|
91
|
+
phase: phase
|
|
78
92
|
});
|
|
79
93
|
|
|
80
94
|
// Open layer observer registration
|
|
@@ -120,6 +134,8 @@ var Dialog = exports.Dialog = /*#__PURE__*/(0, _react.forwardRef)(function Dialo
|
|
|
120
134
|
// Attached via bind-event-listener rather than a React prop so we avoid
|
|
121
135
|
// a11y lint suppressions on the <dialog> element.
|
|
122
136
|
// Keyboard dismiss is already handled natively (Escape → onCancel above).
|
|
137
|
+
// `isVisible` is in deps so the listener re-binds to the new <dialog>
|
|
138
|
+
// element after a host unmount / remount cycle.
|
|
123
139
|
(0, _react.useEffect)(function () {
|
|
124
140
|
var dialog = ownRef.current;
|
|
125
141
|
if (!dialog) {
|
|
@@ -135,13 +151,26 @@ var Dialog = exports.Dialog = /*#__PURE__*/(0, _react.forwardRef)(function Dialo
|
|
|
135
151
|
}
|
|
136
152
|
}
|
|
137
153
|
});
|
|
138
|
-
}, [onClose]);
|
|
154
|
+
}, [onClose, isVisible]);
|
|
139
155
|
|
|
140
156
|
// Atomic CSS (Compiled) deduplicates ::backdrop { background-color }
|
|
141
157
|
// into a single class, making it impossible to toggle between two values
|
|
142
158
|
// via cssMap entries. An ID-scoped <style> has higher specificity than
|
|
143
159
|
// any atomic class, so the override always wins.
|
|
144
160
|
var escapedId = CSS.escape(dialogId);
|
|
161
|
+
|
|
162
|
+
// The `<dialog>` host element is only rendered while open or its exit
|
|
163
|
+
// animation is playing. After exit completes the phase returns to
|
|
164
|
+
// `closed` and we unmount the dialog entirely so it does not leave an
|
|
165
|
+
// empty `role="dialog"` element in the accessibility tree. The element
|
|
166
|
+
// re-mounts on the next `isOpen=true` commit; `showModal()` is called
|
|
167
|
+
// from the existing `useLayoutEffect([isOpen])` on that same commit,
|
|
168
|
+
// and listeners (cancel, click) re-bind via the `[onClose, isVisible]`
|
|
169
|
+
// effect re-running after remount (React clears the ref on unmount, so
|
|
170
|
+
// we need the listener effect to re-run with the fresh element).
|
|
171
|
+
if (!isVisible) {
|
|
172
|
+
return null;
|
|
173
|
+
}
|
|
145
174
|
return /*#__PURE__*/_react.default.createElement("dialog", (0, _extends2.default)({
|
|
146
175
|
ref: combinedRef,
|
|
147
176
|
id: dialogId
|
|
@@ -162,5 +191,5 @@ var Dialog = exports.Dialog = /*#__PURE__*/(0, _react.forwardRef)(function Dialo
|
|
|
162
191
|
}), shouldHideBackdrop &&
|
|
163
192
|
/*#__PURE__*/
|
|
164
193
|
// eslint-disable-next-line @atlaskit/ui-styling-standard/no-global-styles
|
|
165
|
-
_react.default.createElement("style", null, "#".concat(escapedId, "::backdrop{background-color:transparent}")),
|
|
194
|
+
_react.default.createElement("style", null, "#".concat(escapedId, "::backdrop{background-color:transparent}")), children);
|
|
166
195
|
});
|
|
@@ -9,14 +9,20 @@ var _browserApis = require("@atlaskit/browser-apis");
|
|
|
9
9
|
/**
|
|
10
10
|
* __DialogScrollLock__
|
|
11
11
|
*
|
|
12
|
-
* Prevents background page scrolling while
|
|
13
|
-
* inside a `Dialog`
|
|
14
|
-
* dialog closes (unmounts its children).
|
|
12
|
+
* Prevents background page scrolling while a dialog is open. Designed to be
|
|
13
|
+
* rendered inside a `Dialog` and given the same `isOpen` value.
|
|
15
14
|
*
|
|
16
15
|
* Native `<dialog>.showModal()` makes background content _inert_ (not
|
|
17
16
|
* focusable/clickable), but does **not** prevent scroll events from reaching
|
|
18
17
|
* the document body. This component fills that gap by setting
|
|
19
|
-
* `overflow: hidden` on `document.body`.
|
|
18
|
+
* `overflow: hidden` on `document.body` while `isOpen` is `true`.
|
|
19
|
+
*
|
|
20
|
+
* The lock is driven by `isOpen` rather than by mount/unmount so that release
|
|
21
|
+
* happens synchronously in the same commit that flips `isOpen` to `false`.
|
|
22
|
+
* Relying on unmount alone was racy: `Dialog` defers the unmount of its
|
|
23
|
+
* children until the host element's close/toggle event fires, which can be
|
|
24
|
+
* after a consumer or test observes the dialog as hidden, leading to a
|
|
25
|
+
* stale `overflow: hidden` window.
|
|
20
26
|
*
|
|
21
27
|
* @example
|
|
22
28
|
* ```tsx
|
|
@@ -24,14 +30,18 @@ var _browserApis = require("@atlaskit/browser-apis");
|
|
|
24
30
|
* import { DialogScrollLock } from '@atlaskit/top-layer/dialog-scroll-lock';
|
|
25
31
|
*
|
|
26
32
|
* <Dialog isOpen={isOpen} onClose={onClose}>
|
|
27
|
-
* <DialogScrollLock />
|
|
33
|
+
* <DialogScrollLock isOpen={isOpen} />
|
|
28
34
|
* {children}
|
|
29
35
|
* </Dialog>
|
|
30
36
|
* ```
|
|
31
37
|
*/
|
|
32
|
-
function DialogScrollLock() {
|
|
38
|
+
function DialogScrollLock(_ref) {
|
|
39
|
+
var isOpen = _ref.isOpen;
|
|
33
40
|
(0, _react.useLayoutEffect)(function () {
|
|
34
41
|
var _getDocument;
|
|
42
|
+
if (!isOpen) {
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
35
45
|
var body = (_getDocument = (0, _browserApis.getDocument)()) === null || _getDocument === void 0 ? void 0 : _getDocument.body;
|
|
36
46
|
if (!body) {
|
|
37
47
|
return;
|
|
@@ -41,6 +51,6 @@ function DialogScrollLock() {
|
|
|
41
51
|
return function () {
|
|
42
52
|
body.style.overflow = previousOverflow;
|
|
43
53
|
};
|
|
44
|
-
}, []);
|
|
54
|
+
}, [isOpen]);
|
|
45
55
|
return null;
|
|
46
56
|
}
|
|
@@ -9,6 +9,12 @@ Object.defineProperty(exports, "dialogFade", {
|
|
|
9
9
|
return _presets.dialogFade;
|
|
10
10
|
}
|
|
11
11
|
});
|
|
12
|
+
Object.defineProperty(exports, "dialogMotion", {
|
|
13
|
+
enumerable: true,
|
|
14
|
+
get: function get() {
|
|
15
|
+
return _presets.dialogMotion;
|
|
16
|
+
}
|
|
17
|
+
});
|
|
12
18
|
Object.defineProperty(exports, "dialogSlideUpAndFade", {
|
|
13
19
|
enumerable: true,
|
|
14
20
|
get: function get() {
|
|
@@ -21,6 +27,12 @@ Object.defineProperty(exports, "fade", {
|
|
|
21
27
|
return _presets.fade;
|
|
22
28
|
}
|
|
23
29
|
});
|
|
30
|
+
Object.defineProperty(exports, "popupMotion", {
|
|
31
|
+
enumerable: true,
|
|
32
|
+
get: function get() {
|
|
33
|
+
return _presets.popupMotion;
|
|
34
|
+
}
|
|
35
|
+
});
|
|
24
36
|
Object.defineProperty(exports, "scaleAndFade", {
|
|
25
37
|
enumerable: true,
|
|
26
38
|
get: function get() {
|
|
@@ -16,6 +16,8 @@ var _useId = require("@atlaskit/ds-lib/use-id");
|
|
|
16
16
|
* own `anchor-name` on the trigger element.
|
|
17
17
|
*/
|
|
18
18
|
function usePopoverId() {
|
|
19
|
-
//
|
|
19
|
+
// `@atlaskit/ds-lib/use-id` already strips characters that React's
|
|
20
|
+
// `useId` produces (`:`, `«`, `»`) which are invalid in HTML ID
|
|
21
|
+
// tokens. The `popover-` prefix is purely for DOM debug clarity.
|
|
20
22
|
return "popover-".concat((0, _useId.useId)());
|
|
21
23
|
}
|
|
@@ -78,11 +78,16 @@ function getAriaForTrigger(_ref) {
|
|
|
78
78
|
var ariaHasPopup = (0, _roleToAriaHasPopup.roleToAriaHasPopup)({
|
|
79
79
|
role: role
|
|
80
80
|
});
|
|
81
|
+
// `aria-controls` is set to `undefined` while closed. The `Popover` /
|
|
82
|
+
// `Dialog` primitives unmount their host element after the exit
|
|
83
|
+
// animation finishes, so a closed-state `aria-controls` would point
|
|
84
|
+
// at a node that is not in the DOM. Returning `undefined` here means
|
|
85
|
+
// JSX spread renders no `aria-controls` attribute on the trigger
|
|
86
|
+
// until the target exists, avoiding the dangling reference while
|
|
87
|
+
// keeping the relationship live whenever it is meaningful.
|
|
81
88
|
return {
|
|
82
89
|
'aria-haspopup': ariaHasPopup,
|
|
83
90
|
'aria-expanded': isOpen,
|
|
84
|
-
|
|
85
|
-
// tells assistive technology whether the controlled popover is visible.
|
|
86
|
-
'aria-controls': popoverId
|
|
91
|
+
'aria-controls': isOpen ? popoverId : undefined
|
|
87
92
|
};
|
|
88
93
|
}
|
|
@@ -75,7 +75,8 @@ function useAnchorPositionAtPoint(_ref) {
|
|
|
75
75
|
placement = _ref.placement,
|
|
76
76
|
getPoint = _ref.getPoint,
|
|
77
77
|
_ref$isEnabled = _ref.isEnabled,
|
|
78
|
-
isEnabled = _ref$isEnabled === void 0 ? true : _ref$isEnabled
|
|
78
|
+
isEnabled = _ref$isEnabled === void 0 ? true : _ref$isEnabled,
|
|
79
|
+
isOpen = _ref.isOpen;
|
|
79
80
|
var syntheticAnchorRef = (0, _react.useRef)(null);
|
|
80
81
|
|
|
81
82
|
// `getPoint` is kept in a ref so the layout effect can call the
|
|
@@ -122,6 +123,7 @@ function useAnchorPositionAtPoint(_ref) {
|
|
|
122
123
|
anchorRef: syntheticAnchorRef,
|
|
123
124
|
popoverRef: popoverRef,
|
|
124
125
|
placement: placement,
|
|
125
|
-
isEnabled: isEnabled
|
|
126
|
+
isEnabled: isEnabled,
|
|
127
|
+
isOpen: isOpen
|
|
126
128
|
});
|
|
127
129
|
}
|
|
@@ -221,7 +221,8 @@ function useAnchorPosition(_ref3) {
|
|
|
221
221
|
_ref3$forceFallbackPo = _ref3.forceFallbackPositioning,
|
|
222
222
|
forceFallbackPositioning = _ref3$forceFallbackPo === void 0 ? false : _ref3$forceFallbackPo,
|
|
223
223
|
_ref3$isEnabled = _ref3.isEnabled,
|
|
224
|
-
isEnabled = _ref3$isEnabled === void 0 ? true : _ref3$isEnabled
|
|
224
|
+
isEnabled = _ref3$isEnabled === void 0 ? true : _ref3$isEnabled,
|
|
225
|
+
isOpen = _ref3.isOpen;
|
|
225
226
|
var id = (0, _react.useId)();
|
|
226
227
|
// Stabilize `placement` object so the same object literal
|
|
227
228
|
// value returns an object with the same reference
|
|
@@ -481,5 +482,5 @@ function useAnchorPosition(_ref3) {
|
|
|
481
482
|
popover.style.removeProperty('opacity');
|
|
482
483
|
});
|
|
483
484
|
return undoPositioning;
|
|
484
|
-
}, [anchorRef, popoverRef, stablePlacement, forceFallbackPositioning, isEnabled, id]);
|
|
485
|
+
}, [anchorRef, popoverRef, stablePlacement, forceFallbackPositioning, isEnabled, id, isOpen]);
|
|
485
486
|
}
|