@atlaskit/top-layer 0.16.0 → 0.18.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 +113 -0
- package/CHANGELOG.md +14 -0
- package/README.md +4 -4
- package/animations/package.json +1 -8
- package/dialog/package.json +1 -8
- package/dialog-scroll-lock/package.json +1 -8
- package/dist/cjs/animations/presets.js +96 -9
- package/dist/cjs/dialog/dialog-content.js +12 -30
- package/dist/cjs/entry-points/animations.js +12 -0
- package/dist/cjs/internal/set-style.js +5 -5
- package/dist/cjs/internal/slot.js +1 -1
- package/dist/cjs/internal/use-anchor-position-at-point.js +8 -8
- package/dist/cjs/internal/use-anchor-position.js +19 -40
- package/dist/cjs/internal/use-animated-visibility.js +31 -61
- package/dist/cjs/internal/use-initial-focus.js +9 -13
- package/dist/cjs/internal/use-width-from-anchor.js +6 -6
- package/dist/cjs/popover/popover.js +30 -58
- package/dist/cjs/popover-surface/popover-surface.js +4 -3
- package/dist/cjs/testing/polyfill.js +9 -9
- package/dist/cjs/use-simple-light-dismiss/index.js +3 -3
- package/dist/es2019/animations/presets.js +145 -2
- package/dist/es2019/dialog/dialog-content.js +12 -30
- package/dist/es2019/entry-points/animations.js +1 -1
- package/dist/es2019/internal/set-style.js +5 -5
- package/dist/es2019/internal/slot.js +1 -1
- package/dist/es2019/internal/use-anchor-position-at-point.js +8 -8
- package/dist/es2019/internal/use-anchor-position.js +19 -40
- package/dist/es2019/internal/use-animated-visibility.js +31 -61
- package/dist/es2019/internal/use-initial-focus.js +9 -13
- package/dist/es2019/internal/use-width-from-anchor.js +6 -6
- package/dist/es2019/popover/popover.js +30 -58
- package/dist/es2019/popover-surface/popover-surface.js +1 -1
- package/dist/es2019/testing/polyfill.js +7 -11
- package/dist/es2019/use-simple-light-dismiss/index.js +3 -3
- package/dist/esm/animations/presets.js +93 -9
- package/dist/esm/dialog/dialog-content.js +12 -30
- package/dist/esm/entry-points/animations.js +1 -1
- package/dist/esm/internal/set-style.js +5 -5
- package/dist/esm/internal/slot.js +1 -1
- package/dist/esm/internal/use-anchor-position-at-point.js +8 -8
- package/dist/esm/internal/use-anchor-position.js +19 -40
- package/dist/esm/internal/use-animated-visibility.js +31 -61
- package/dist/esm/internal/use-initial-focus.js +9 -13
- package/dist/esm/internal/use-width-from-anchor.js +6 -6
- package/dist/esm/popover/popover.js +30 -58
- package/dist/esm/popover-surface/popover-surface.js +1 -1
- package/dist/esm/testing/polyfill.js +9 -9
- package/dist/esm/use-simple-light-dismiss/index.js +3 -3
- package/dist/types/animations/presets.d.ts +16 -0
- package/dist/types/entry-points/animations.d.ts +1 -1
- package/dist/types/internal/set-style.d.ts +2 -2
- package/dist/types/internal/slot.d.ts +2 -2
- package/dist/types/internal/use-anchor-position-at-point.d.ts +2 -2
- package/dist/types/internal/use-anchor-position.d.ts +0 -4
- package/dist/types/internal/use-initial-focus.d.ts +1 -1
- package/dist/types/popover/types.d.ts +5 -7
- package/dist/types/popover-surface/popover-surface.d.ts +2 -2
- package/focus/package.json +1 -8
- package/get-aria-for-trigger/package.json +1 -8
- package/open-layer-observer/package.json +1 -8
- package/package.json +3 -3
- package/placement-map/package.json +1 -8
- package/popover/package.json +1 -8
- package/popover-surface/package.json +1 -8
- package/testing/polyfill/package.json +1 -8
- package/testing/to-be-visible/package.json +1 -8
- package/use-anchor-position/package.json +1 -8
- package/use-anchor-position-at-point/package.json +1 -8
- package/use-arrow-navigation/package.json +1 -8
- package/use-popover-id/package.json +1 -8
- package/use-simple-light-dismiss/package.json +1 -8
- package/use-width-from-anchor/package.json +1 -8
- package/dist/types-ts4.5/animations/presets.d.ts +0 -94
- package/dist/types-ts4.5/animations/types.d.ts +0 -38
- package/dist/types-ts4.5/dialog/create-close-event.d.ts +0 -23
- package/dist/types-ts4.5/dialog/dialog-content.d.ts +0 -24
- package/dist/types-ts4.5/dialog/index.d.ts +0 -3
- package/dist/types-ts4.5/dialog/types.d.ts +0 -126
- package/dist/types-ts4.5/dialog-scroll-lock/index.d.ts +0 -40
- package/dist/types-ts4.5/entry-points/animations.d.ts +0 -2
- package/dist/types-ts4.5/entry-points/dialog-scroll-lock.d.ts +0 -1
- package/dist/types-ts4.5/entry-points/dialog.d.ts +0 -2
- package/dist/types-ts4.5/entry-points/focus.d.ts +0 -4
- package/dist/types-ts4.5/entry-points/get-aria-for-trigger.d.ts +0 -1
- package/dist/types-ts4.5/entry-points/open-layer-observer.d.ts +0 -5
- package/dist/types-ts4.5/entry-points/placement-map.d.ts +0 -3
- package/dist/types-ts4.5/entry-points/popover-surface.d.ts +0 -2
- package/dist/types-ts4.5/entry-points/popover.d.ts +0 -2
- package/dist/types-ts4.5/entry-points/use-anchor-position-at-point.d.ts +0 -1
- package/dist/types-ts4.5/entry-points/use-anchor-position.d.ts +0 -1
- package/dist/types-ts4.5/entry-points/use-arrow-navigation.d.ts +0 -3
- package/dist/types-ts4.5/entry-points/use-popover-id.d.ts +0 -11
- package/dist/types-ts4.5/entry-points/use-simple-light-dismiss.d.ts +0 -2
- package/dist/types-ts4.5/entry-points/use-width-from-anchor.d.ts +0 -1
- package/dist/types-ts4.5/focus/get-first-focusable.d.ts +0 -8
- package/dist/types-ts4.5/focus/get-focusables.d.ts +0 -13
- package/dist/types-ts4.5/focus/get-last-focusable.d.ts +0 -8
- package/dist/types-ts4.5/focus/get-next-focusable.d.ts +0 -13
- package/dist/types-ts4.5/focus/is-in-same-layer.d.ts +0 -8
- package/dist/types-ts4.5/focus/is-nested-layer-focused.d.ts +0 -8
- package/dist/types-ts4.5/focus/selectors.d.ts +0 -4
- package/dist/types-ts4.5/focus/types.d.ts +0 -5
- package/dist/types-ts4.5/index.d.ts +0 -0
- package/dist/types-ts4.5/internal/anchor-positioning-fallback.d.ts +0 -26
- package/dist/types-ts4.5/internal/combine.d.ts +0 -14
- package/dist/types-ts4.5/internal/ensure-preset-styles.d.ts +0 -6
- package/dist/types-ts4.5/internal/get-aria-for-trigger.d.ts +0 -93
- package/dist/types-ts4.5/internal/placement-to-position-area.d.ts +0 -21
- package/dist/types-ts4.5/internal/placement-to-try-fallbacks.d.ts +0 -36
- package/dist/types-ts4.5/internal/reduced-motion.d.ts +0 -14
- package/dist/types-ts4.5/internal/resolve-css-length-to-pixels.d.ts +0 -20
- package/dist/types-ts4.5/internal/resolve-css-length.d.ts +0 -8
- package/dist/types-ts4.5/internal/resolve-placement.d.ts +0 -58
- package/dist/types-ts4.5/internal/role-to-aria-has-popup.d.ts +0 -13
- package/dist/types-ts4.5/internal/role-types.d.ts +0 -71
- package/dist/types-ts4.5/internal/set-style.d.ts +0 -11
- package/dist/types-ts4.5/internal/should-focus-into-popover.d.ts +0 -8
- package/dist/types-ts4.5/internal/slot.d.ts +0 -29
- package/dist/types-ts4.5/internal/use-anchor-position-at-point.d.ts +0 -67
- package/dist/types-ts4.5/internal/use-anchor-position.d.ts +0 -91
- package/dist/types-ts4.5/internal/use-animated-visibility.d.ts +0 -109
- package/dist/types-ts4.5/internal/use-animation-preset.d.ts +0 -11
- package/dist/types-ts4.5/internal/use-focus-wrap.d.ts +0 -25
- package/dist/types-ts4.5/internal/use-initial-focus.d.ts +0 -73
- package/dist/types-ts4.5/internal/use-preset-styles.d.ts +0 -15
- package/dist/types-ts4.5/internal/use-width-from-anchor.d.ts +0 -21
- package/dist/types-ts4.5/placement-map/index.d.ts +0 -51
- package/dist/types-ts4.5/placement-map/legacy-placements.d.ts +0 -27
- package/dist/types-ts4.5/placement-map/placement-mapping.d.ts +0 -13
- package/dist/types-ts4.5/popover/create-close-event.d.ts +0 -25
- package/dist/types-ts4.5/popover/index.d.ts +0 -3
- package/dist/types-ts4.5/popover/popover.d.ts +0 -17
- package/dist/types-ts4.5/popover/types.d.ts +0 -193
- package/dist/types-ts4.5/popover-surface/index.d.ts +0 -2
- package/dist/types-ts4.5/popover-surface/popover-surface.d.ts +0 -21
- package/dist/types-ts4.5/testing/polyfill.d.ts +0 -25
- package/dist/types-ts4.5/testing/to-be-visible.d.ts +0 -23
- package/dist/types-ts4.5/use-arrow-navigation/index.d.ts +0 -3
- package/dist/types-ts4.5/use-arrow-navigation/is-at-current-menu-level.d.ts +0 -48
- package/dist/types-ts4.5/use-arrow-navigation/use-arrow-navigation.d.ts +0 -102
- package/dist/types-ts4.5/use-simple-light-dismiss/index.d.ts +0 -45
|
@@ -1,6 +1,82 @@
|
|
|
1
1
|
import { getPlacement } from '../internal/resolve-placement';
|
|
2
|
+
// ══════════════════════════════════════════════════════════════════════════════
|
|
3
|
+
// Popover presets
|
|
4
|
+
// ══════════════════════════════════════════════════════════════════════════════
|
|
5
|
+
|
|
6
|
+
// ── popupMotion ──
|
|
7
|
+
|
|
8
|
+
const POPUP_MOTION_CSS = `
|
|
9
|
+
[data-ds-popover-popup-motion] {
|
|
10
|
+
animation: var(--ds-popover-motion-exit);
|
|
11
|
+
animation-fill-mode: forwards;
|
|
12
|
+
transition:
|
|
13
|
+
overlay 150ms allow-discrete,
|
|
14
|
+
display 150ms allow-discrete;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
[data-ds-popover-popup-motion]:popover-open {
|
|
18
|
+
animation: var(--ds-popover-motion-enter);
|
|
19
|
+
animation-fill-mode: backwards;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
@media (prefers-reduced-motion: reduce) {
|
|
23
|
+
[data-ds-popover-popup-motion],
|
|
24
|
+
[data-ds-popover-popup-motion]:popover-open {
|
|
25
|
+
animation: none;
|
|
26
|
+
transition-duration: 0s;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
`;
|
|
30
|
+
function getPopupMotionProperties({
|
|
31
|
+
placement
|
|
32
|
+
}) {
|
|
33
|
+
const {
|
|
34
|
+
axis,
|
|
35
|
+
edge
|
|
36
|
+
} = getPlacement({
|
|
37
|
+
placement
|
|
38
|
+
});
|
|
39
|
+
if (axis === 'block' && edge === 'start') {
|
|
40
|
+
return {
|
|
41
|
+
'--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)",
|
|
42
|
+
'--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)"
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
if (axis === 'inline' && edge === 'start') {
|
|
46
|
+
return {
|
|
47
|
+
'--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)",
|
|
48
|
+
'--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)"
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
if (axis === 'inline' && edge === 'end') {
|
|
52
|
+
return {
|
|
53
|
+
'--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)",
|
|
54
|
+
'--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)"
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
// Default: block/end (popover below trigger)
|
|
58
|
+
return {
|
|
59
|
+
'--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)",
|
|
60
|
+
'--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)"
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// eslint-disable-next-line @atlaskit/volt-strict-mode/no-multiple-exports
|
|
65
|
+
export function popupMotion() {
|
|
66
|
+
return {
|
|
67
|
+
name: 'popup-motion',
|
|
68
|
+
css: POPUP_MOTION_CSS,
|
|
69
|
+
enterDurationMs: 150,
|
|
70
|
+
exitDurationMs: 100,
|
|
71
|
+
getProperties: ({
|
|
72
|
+
placement
|
|
73
|
+
}) => getPopupMotionProperties({
|
|
74
|
+
placement
|
|
75
|
+
})
|
|
76
|
+
};
|
|
77
|
+
}
|
|
2
78
|
|
|
3
|
-
// slideAndFade
|
|
79
|
+
// ── slideAndFade ──
|
|
4
80
|
|
|
5
81
|
function getSlideAndFadeProperties({
|
|
6
82
|
placement,
|
|
@@ -106,6 +182,7 @@ const SLIDE_AND_FADE_CSS = `
|
|
|
106
182
|
* <Popover animate={slideAndFade({ distance: 8 })} />
|
|
107
183
|
* ```
|
|
108
184
|
*/
|
|
185
|
+
// eslint-disable-next-line @atlaskit/volt-strict-mode/no-multiple-exports
|
|
109
186
|
export function slideAndFade(options) {
|
|
110
187
|
var _options$distance;
|
|
111
188
|
const distance = (_options$distance = options === null || options === void 0 ? void 0 : options.distance) !== null && _options$distance !== void 0 ? _options$distance : 4;
|
|
@@ -235,7 +312,73 @@ export function scaleAndFade() {
|
|
|
235
312
|
// Dialog presets
|
|
236
313
|
// ══════════════════════════════════════════════════════════════════════════════
|
|
237
314
|
|
|
238
|
-
//
|
|
315
|
+
// ── dialogMotion ──
|
|
316
|
+
|
|
317
|
+
const DIALOG_MOTION_CSS = `
|
|
318
|
+
[data-ds-dialog-motion] {
|
|
319
|
+
animation: ${"var(--ds-modal-exit, 200ms cubic-bezier(0.6, 0, 0.8, 0.6) ScaleOut100to95)"};
|
|
320
|
+
animation-fill-mode: forwards;
|
|
321
|
+
transition:
|
|
322
|
+
overlay 200ms allow-discrete,
|
|
323
|
+
display 200ms allow-discrete;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
[data-ds-dialog-motion][open] {
|
|
327
|
+
animation: ${"var(--ds-modal-enter, 250ms cubic-bezier(0.4, 0, 0, 1) ScaleIn95to100)"};
|
|
328
|
+
animation-fill-mode: backwards;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
[data-ds-dialog-motion]::backdrop {
|
|
332
|
+
animation: ${"var(--ds-blanket-exit, 200ms cubic-bezier(0.6, 0, 0.8, 0.6) FadeOut100to0)"};
|
|
333
|
+
animation-fill-mode: forwards;
|
|
334
|
+
background-color: var(--ds-blanket, #050C1F75);
|
|
335
|
+
transition:
|
|
336
|
+
overlay 200ms allow-discrete,
|
|
337
|
+
display 200ms allow-discrete;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
[data-ds-dialog-motion][open]::backdrop {
|
|
341
|
+
animation: ${"var(--ds-blanket-enter, 250ms cubic-bezier(0.4, 0, 0, 1) FadeIn0to100)"};
|
|
342
|
+
animation-fill-mode: backwards;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
@media (prefers-reduced-motion: reduce) {
|
|
346
|
+
[data-ds-dialog-motion],
|
|
347
|
+
[data-ds-dialog-motion][open],
|
|
348
|
+
[data-ds-dialog-motion]::backdrop,
|
|
349
|
+
[data-ds-dialog-motion][open]::backdrop {
|
|
350
|
+
animation: none;
|
|
351
|
+
transition-duration: 0s;
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
`;
|
|
355
|
+
|
|
356
|
+
/**
|
|
357
|
+
* Dialog motion animation.
|
|
358
|
+
*
|
|
359
|
+
* Includes backdrop fade animation.
|
|
360
|
+
*
|
|
361
|
+
* @example
|
|
362
|
+
* ```tsx
|
|
363
|
+
* import { dialogMotion } from '@atlaskit/top-layer/animations';
|
|
364
|
+
*
|
|
365
|
+
* <Dialog animate={dialogMotion()} isOpen={isOpen} onClose={handleClose} label="...">
|
|
366
|
+
* ...
|
|
367
|
+
* </Dialog>
|
|
368
|
+
* ```
|
|
369
|
+
*/
|
|
370
|
+
// eslint-disable-next-line @atlaskit/volt-strict-mode/no-multiple-exports
|
|
371
|
+
export function dialogMotion() {
|
|
372
|
+
// Bare preset name; `Dialog` prefixes it via `data-ds-dialog-{name}`.
|
|
373
|
+
return {
|
|
374
|
+
name: 'motion',
|
|
375
|
+
css: DIALOG_MOTION_CSS,
|
|
376
|
+
enterDurationMs: 250,
|
|
377
|
+
exitDurationMs: 200
|
|
378
|
+
};
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
// ── dialogSlideUpAndFade ──
|
|
239
382
|
|
|
240
383
|
const DIALOG_SLIDE_UP_AND_FADE_CSS = `
|
|
241
384
|
[data-ds-dialog-slide-up-and-fade] {
|
|
@@ -48,8 +48,6 @@ export const Dialog = /*#__PURE__*/forwardRef(function Dialog({
|
|
|
48
48
|
const dialogId = providedId !== null && providedId !== void 0 ? providedId : generatedId;
|
|
49
49
|
const ownRef = useRef(null);
|
|
50
50
|
const combinedRef = mergeRefs([ownRef, ref]);
|
|
51
|
-
|
|
52
|
-
// Animation lifecycle
|
|
53
51
|
const {
|
|
54
52
|
phase,
|
|
55
53
|
preset
|
|
@@ -60,31 +58,22 @@ export const Dialog = /*#__PURE__*/forwardRef(function Dialog({
|
|
|
60
58
|
onExitFinish
|
|
61
59
|
});
|
|
62
60
|
|
|
63
|
-
//
|
|
64
|
-
//
|
|
65
|
-
// signal for the backdrop-click rebind effect so it re-attaches to
|
|
66
|
-
// the fresh <dialog> element after a close → reopen unmount/remount
|
|
67
|
-
// cycle.
|
|
61
|
+
// True while the host element is mounted (any phase except `closed`).
|
|
62
|
+
// Used as a dep so listener-rebind effects re-attach after a remount.
|
|
68
63
|
const isVisible = phase !== 'closed';
|
|
69
64
|
|
|
70
|
-
//
|
|
71
|
-
//
|
|
72
|
-
//
|
|
73
|
-
//
|
|
74
|
-
//
|
|
75
|
-
// Takes `phase` directly: the listener stays attached while
|
|
76
|
-
// `phase !== 'closed'`, i.e. through the animated-exit window.
|
|
77
|
-
// Gating on `isOpen` would unbind the listener at the start of an
|
|
78
|
-
// exit animation while the dialog is still visible, letting focus
|
|
79
|
-
// escape (WCAG 2.4.3 Focus Order regression).
|
|
65
|
+
// Native `<dialog>.showModal()` traps focus but wraps through `<body>` at
|
|
66
|
+
// the boundary (A → B → C → body → A). This hook intercepts Tab to wrap
|
|
67
|
+
// directly (A → B → C → A), matching the WAI-ARIA APG pattern. Passing
|
|
68
|
+
// `phase` keeps the listener attached through the animated-exit window
|
|
69
|
+
// (WCAG 2.4.3 Focus Order regression guard).
|
|
80
70
|
useFocusWrap({
|
|
81
71
|
elementRef: ownRef,
|
|
82
72
|
role: 'dialog',
|
|
83
73
|
phase
|
|
84
74
|
});
|
|
85
75
|
|
|
86
|
-
//
|
|
87
|
-
// Notifies the open layer observer so app-coordination features
|
|
76
|
+
// Notify the open layer observer so app-coordination features
|
|
88
77
|
// (open-count subscriptions) work with top-layer dialogs.
|
|
89
78
|
useNotifyOpenLayerObserver({
|
|
90
79
|
type: 'modal',
|
|
@@ -92,8 +81,6 @@ export const Dialog = /*#__PURE__*/forwardRef(function Dialog({
|
|
|
92
81
|
// No-op: no current use case for programmatic close via OpenLayerObserver.
|
|
93
82
|
onClose: noop
|
|
94
83
|
});
|
|
95
|
-
|
|
96
|
-
// Show/hide the dialog in response to isOpen changes.
|
|
97
84
|
useLayoutEffect(() => {
|
|
98
85
|
const dialog = ownRef.current;
|
|
99
86
|
if (!dialog) {
|
|
@@ -151,15 +138,10 @@ export const Dialog = /*#__PURE__*/forwardRef(function Dialog({
|
|
|
151
138
|
// any atomic class, so the override always wins.
|
|
152
139
|
const escapedId = CSS.escape(dialogId);
|
|
153
140
|
|
|
154
|
-
//
|
|
155
|
-
//
|
|
156
|
-
//
|
|
157
|
-
//
|
|
158
|
-
// re-mounts on the next `isOpen=true` commit; `showModal()` is called
|
|
159
|
-
// from the existing `useLayoutEffect([isOpen])` on that same commit,
|
|
160
|
-
// and listeners (cancel, click) re-bind via the `[onClose, isVisible]`
|
|
161
|
-
// effect re-running after remount (React clears the ref on unmount, so
|
|
162
|
-
// we need the listener effect to re-run with the fresh element).
|
|
141
|
+
// Unmount the `<dialog>` once exit completes so it does not leave an
|
|
142
|
+
// empty `role="dialog"` element in the accessibility tree. On the next
|
|
143
|
+
// open the element remounts and the `[isOpen]` and `[onClose, isVisible]`
|
|
144
|
+
// effects re-run against the fresh element.
|
|
163
145
|
if (!isVisible) {
|
|
164
146
|
return null;
|
|
165
147
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export { slideAndFade, fade, scaleAndFade, dialogSlideUpAndFade, dialogFade } from '../animations/presets';
|
|
1
|
+
export { dialogMotion, popupMotion, slideAndFade, fade, scaleAndFade, dialogSlideUpAndFade, dialogFade } from '../animations/presets';
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* restores the prior inline values (so we do not stomp consumer styles).
|
|
4
4
|
*/
|
|
5
5
|
export function setStyle({
|
|
6
|
-
|
|
6
|
+
element,
|
|
7
7
|
styles
|
|
8
8
|
}) {
|
|
9
9
|
// Snapshot the prior inline value (NOT the computed style - we only want
|
|
@@ -13,13 +13,13 @@ export function setStyle({
|
|
|
13
13
|
property
|
|
14
14
|
}) => ({
|
|
15
15
|
property,
|
|
16
|
-
value:
|
|
16
|
+
value: element.style.getPropertyValue(property)
|
|
17
17
|
}));
|
|
18
18
|
styles.forEach(({
|
|
19
19
|
property,
|
|
20
20
|
value
|
|
21
21
|
}) => {
|
|
22
|
-
|
|
22
|
+
element.style.setProperty(property, value);
|
|
23
23
|
});
|
|
24
24
|
return function cleanup() {
|
|
25
25
|
priorValues.forEach(({
|
|
@@ -27,10 +27,10 @@ export function setStyle({
|
|
|
27
27
|
value
|
|
28
28
|
}) => {
|
|
29
29
|
if (value === '') {
|
|
30
|
-
|
|
30
|
+
element.style.removeProperty(property);
|
|
31
31
|
return;
|
|
32
32
|
}
|
|
33
|
-
|
|
33
|
+
element.style.setProperty(property, value);
|
|
34
34
|
});
|
|
35
35
|
};
|
|
36
36
|
}
|
|
@@ -43,7 +43,7 @@ export const Slot = /*#__PURE__*/forwardRef(function Slot(slotProps, ref) {
|
|
|
43
43
|
children: rawChildren,
|
|
44
44
|
...props
|
|
45
45
|
} = slotProps;
|
|
46
|
-
// `
|
|
46
|
+
// `TSlotProps` intersects `Record<string, unknown>`, so `children` is typed as `unknown`.
|
|
47
47
|
const children = rawChildren;
|
|
48
48
|
if (!isSlotChild(children)) {
|
|
49
49
|
return children;
|
|
@@ -6,7 +6,7 @@ import { useAnchorPosition } from './use-anchor-position';
|
|
|
6
6
|
* anchor should be placed.
|
|
7
7
|
*
|
|
8
8
|
* Axis-named (`x`/`y`) rather than edge-named (`left`/`top`) so the
|
|
9
|
-
* shape is unambiguous in RTL writing modes
|
|
9
|
+
* shape is unambiguous in RTL writing modes - there is no ambiguity
|
|
10
10
|
* about whether a coordinate is logical (`inline-start`/`block-start`)
|
|
11
11
|
* or physical (`left`/`top`). Viewport coordinates are always
|
|
12
12
|
* physical, so we name them with the physical axis labels.
|
|
@@ -17,19 +17,19 @@ import { useAnchorPosition } from './use-anchor-position';
|
|
|
17
17
|
* Dynamic `inset-*` overrides are written by the positioning effect.
|
|
18
18
|
*
|
|
19
19
|
* Uses `Object.assign` rather than Compiled CSS (element is outside the React tree)
|
|
20
|
-
* or the internal `setStyle` helper (we own this element entirely
|
|
21
|
-
* and removed by this hook
|
|
20
|
+
* or the internal `setStyle` helper (we own this element entirely - it is created
|
|
21
|
+
* and removed by this hook - so the snapshot/restore semantics `setStyle`
|
|
22
22
|
* provides for caller-owned elements would be dead work here).
|
|
23
23
|
*/
|
|
24
|
-
function applyStaticAnchorStyles(
|
|
25
|
-
Object.assign(
|
|
24
|
+
function applyStaticAnchorStyles(element) {
|
|
25
|
+
Object.assign(element.style, {
|
|
26
26
|
position: 'fixed',
|
|
27
27
|
// `top`/`left` rather than `inset-block-start`/`inset-inline-start`
|
|
28
28
|
// for consistency with the dynamic positioning effect below, which
|
|
29
29
|
// uses physical properties (see comment there for the rationale).
|
|
30
30
|
top: '0',
|
|
31
31
|
left: '0',
|
|
32
|
-
// Zero size
|
|
32
|
+
// Zero size - placement decides which side the popover sits on.
|
|
33
33
|
width: '0',
|
|
34
34
|
height: '0',
|
|
35
35
|
// Prevent the overlay from intercepting pointer events.
|
|
@@ -75,7 +75,7 @@ export function useAnchorPositionAtPoint({
|
|
|
75
75
|
const syntheticAnchorRef = useRef(null);
|
|
76
76
|
|
|
77
77
|
// `getPoint` is kept in a ref so the layout effect can call the
|
|
78
|
-
// latest version without listing it as a dep
|
|
78
|
+
// latest version without listing it as a dep - that would re-fire
|
|
79
79
|
// on every render and re-latch the point.
|
|
80
80
|
const getPointRef = useRef(getPoint);
|
|
81
81
|
getPointRef.current = getPoint;
|
|
@@ -111,7 +111,7 @@ export function useAnchorPositionAtPoint({
|
|
|
111
111
|
};
|
|
112
112
|
}, [isEnabled]);
|
|
113
113
|
|
|
114
|
-
// Mirror `isEnabled` so both hooks stay in sync
|
|
114
|
+
// Mirror `isEnabled` so both hooks stay in sync - when disabled,
|
|
115
115
|
// neither this hook nor the inner one touches the popover, leaving
|
|
116
116
|
// any other positioning the consumer has set up free to take effect.
|
|
117
117
|
useAnchorPosition({
|
|
@@ -139,17 +139,11 @@ function crossAxisShiftMargin({
|
|
|
139
139
|
}
|
|
140
140
|
|
|
141
141
|
/**
|
|
142
|
-
* Returns a referentially-stable, fully-resolved `TPlacement`.
|
|
143
|
-
* keyed on the primitive fields
|
|
144
|
-
*
|
|
145
|
-
* `getPlacement` and the equality check are skipped when the inputs are
|
|
146
|
-
* unchanged.
|
|
142
|
+
* Returns a referentially-stable, fully-resolved `TPlacement`. The memo is
|
|
143
|
+
* keyed on the resolved primitive fields so a fresh inline `placement` object
|
|
144
|
+
* (or undefined fields vs explicit defaults) does not produce a fresh result.
|
|
147
145
|
*/
|
|
148
146
|
function useStablePlacement(placement) {
|
|
149
|
-
// Resolve to the fully-defaulted primitives BEFORE the memo so the deps
|
|
150
|
-
// array sees the same key for `{}`, `undefined` fields, and explicit
|
|
151
|
-
// defaults. Otherwise `axis: undefined` and `axis: 'block'` would be
|
|
152
|
-
// treated as different inputs and re-run the downstream effect.
|
|
153
147
|
const resolved = getPlacement({
|
|
154
148
|
placement
|
|
155
149
|
});
|
|
@@ -159,10 +153,8 @@ function useStablePlacement(placement) {
|
|
|
159
153
|
const gap = resolved.offset.gap;
|
|
160
154
|
const shiftValue = resolved.offset.crossAxisShift.value;
|
|
161
155
|
const shiftDirection = resolved.offset.crossAxisShift.direction;
|
|
162
|
-
// Rebuild
|
|
163
|
-
//
|
|
164
|
-
// `resolved` reference, and `react-hooks/exhaustive-deps` can verify the
|
|
165
|
-
// deps array.
|
|
156
|
+
// Rebuild inside the memo from primitive deps so the factory does not
|
|
157
|
+
// close over the outer `resolved` reference and exhaustive-deps can verify.
|
|
166
158
|
return useMemo(() => ({
|
|
167
159
|
axis,
|
|
168
160
|
edge,
|
|
@@ -192,10 +184,6 @@ function useStablePlacement(placement) {
|
|
|
192
184
|
* When not supported, it falls back to JavaScript-based positioning using
|
|
193
185
|
* `position: fixed` with measured coordinates, re-running on scroll (capture)
|
|
194
186
|
* and resize events.
|
|
195
|
-
*
|
|
196
|
-
* `placement` may be a fresh inline object on every render: a structural
|
|
197
|
-
* comparison is used internally so re-runs are skipped when the resolved
|
|
198
|
-
* placement shape is unchanged.
|
|
199
187
|
*/
|
|
200
188
|
// eslint-disable-next-line @atlaskit/volt-strict-mode/no-multiple-exports
|
|
201
189
|
export function useAnchorPosition({
|
|
@@ -207,8 +195,6 @@ export function useAnchorPosition({
|
|
|
207
195
|
isOpen
|
|
208
196
|
}) {
|
|
209
197
|
const id = useId();
|
|
210
|
-
// Stabilize `placement` object so the same object literal
|
|
211
|
-
// value returns an object with the same reference
|
|
212
198
|
const stablePlacement = useStablePlacement(placement);
|
|
213
199
|
useLayoutEffect(() => {
|
|
214
200
|
if (!isEnabled) {
|
|
@@ -230,9 +216,9 @@ export function useAnchorPosition({
|
|
|
230
216
|
placement: stablePlacement,
|
|
231
217
|
offset: gapCssValue
|
|
232
218
|
});
|
|
233
|
-
//
|
|
234
|
-
//
|
|
235
|
-
//
|
|
219
|
+
// Computed once; reused below to populate the active
|
|
220
|
+
// `--ds-cross-axis-shift-margin-*` custom property for the
|
|
221
|
+
// named arrow @position-try rules.
|
|
236
222
|
const crossAxisShift = crossAxisShiftMargin({
|
|
237
223
|
placement: stablePlacement,
|
|
238
224
|
crossAxisShiftCssValue,
|
|
@@ -260,22 +246,17 @@ export function useAnchorPosition({
|
|
|
260
246
|
}, {
|
|
261
247
|
property: 'inset',
|
|
262
248
|
value: 'auto'
|
|
263
|
-
},
|
|
264
|
-
// Main-axis gap between the popover and its trigger
|
|
265
|
-
{
|
|
249
|
+
}, {
|
|
266
250
|
property: gap.property,
|
|
267
251
|
value: gap.value
|
|
268
|
-
},
|
|
269
|
-
// Cross-axis shift
|
|
270
|
-
{
|
|
252
|
+
}, {
|
|
271
253
|
property: crossAxisShift.property,
|
|
272
254
|
value: crossAxisShift.value
|
|
273
255
|
}];
|
|
274
256
|
|
|
275
|
-
//
|
|
276
|
-
//
|
|
277
|
-
//
|
|
278
|
-
// at a time.
|
|
257
|
+
// Expose the active cross-axis shift via custom properties so each
|
|
258
|
+
// named arrow @position-try rule can re-apply it after a flip.
|
|
259
|
+
// One of the four (cross-axis, side) properties is non-zero at a time.
|
|
279
260
|
popoverStyles.push({
|
|
280
261
|
property: '--ds-cross-axis-shift-margin-start',
|
|
281
262
|
value: '0px'
|
|
@@ -292,9 +273,8 @@ export function useAnchorPosition({
|
|
|
292
273
|
property: '--ds-cross-axis-shift-margin-block-end',
|
|
293
274
|
value: '0px'
|
|
294
275
|
});
|
|
295
|
-
// `crossAxisShift` IS the active margin
|
|
276
|
+
// `crossAxisShift` IS the active margin; reuse instead of recomputing.
|
|
296
277
|
const crossAxisShiftActive = crossAxisShift;
|
|
297
|
-
// Map margin property to its corresponding custom property name.
|
|
298
278
|
const customPropertyByMarginProperty = {
|
|
299
279
|
'margin-inline-start': '--ds-cross-axis-shift-margin-start',
|
|
300
280
|
'margin-inline-end': '--ds-cross-axis-shift-margin-end',
|
|
@@ -328,18 +308,17 @@ export function useAnchorPosition({
|
|
|
328
308
|
*/
|
|
329
309
|
trigger.style.setProperty('anchor-name', anchorName);
|
|
330
310
|
const undoPositioning = combine(setStyle({
|
|
331
|
-
|
|
311
|
+
element: popover,
|
|
332
312
|
styles: popoverStyles
|
|
333
313
|
}));
|
|
334
314
|
return undoPositioning;
|
|
335
315
|
}
|
|
336
316
|
|
|
337
|
-
// JS fallback
|
|
338
|
-
//
|
|
339
|
-
//
|
|
340
|
-
// (inset: 0; margin: auto) and set top/left based on measurements.
|
|
317
|
+
// JS fallback. The popover is already in the top layer via
|
|
318
|
+
// popover="auto", so we only reset UA defaults and set top/left
|
|
319
|
+
// based on measurements.
|
|
341
320
|
const cleanupBaseStyles = setStyle({
|
|
342
|
-
|
|
321
|
+
element: popover,
|
|
343
322
|
styles: [{
|
|
344
323
|
property: 'margin',
|
|
345
324
|
value: '0'
|
|
@@ -12,14 +12,16 @@ function runOnTransitionEndOrTimeout({
|
|
|
12
12
|
safetyNetMs,
|
|
13
13
|
onSettled
|
|
14
14
|
}) {
|
|
15
|
-
|
|
15
|
+
const state = {
|
|
16
|
+
isSettled: false
|
|
17
|
+
};
|
|
16
18
|
function teardown() {
|
|
17
|
-
isSettled = true;
|
|
19
|
+
state.isSettled = true;
|
|
18
20
|
clearTimeout(safetyNetId);
|
|
19
21
|
unbind();
|
|
20
22
|
}
|
|
21
23
|
function settle() {
|
|
22
|
-
if (isSettled) {
|
|
24
|
+
if (state.isSettled) {
|
|
23
25
|
return;
|
|
24
26
|
}
|
|
25
27
|
teardown();
|
|
@@ -104,22 +106,16 @@ export function useAnimatedVisibility({
|
|
|
104
106
|
onEnterFinish,
|
|
105
107
|
onExitFinish
|
|
106
108
|
}) {
|
|
107
|
-
// Resolve the animation preset and inject its CSS (idempotent).
|
|
108
|
-
// Returns `null` when `animate` is falsy.
|
|
109
109
|
const preset = usePresetStyles({
|
|
110
110
|
preset: animate
|
|
111
111
|
});
|
|
112
112
|
|
|
113
|
-
//
|
|
114
|
-
// or the user has enabled the "prefers reduced motion" OS setting.
|
|
113
|
+
// False when there is no preset or `prefers-reduced-motion` is set.
|
|
115
114
|
const willAnimate = Boolean(animate) && !prefersReducedMotion();
|
|
116
115
|
|
|
117
|
-
// `
|
|
118
|
-
//
|
|
119
|
-
//
|
|
120
|
-
// in the same commit as the open intent (React supports a single
|
|
121
|
-
// setState during render as a synchronous re-render before paint).
|
|
122
|
-
// All other transitions are driven by phase-specific effects below.
|
|
116
|
+
// Promote `closed → entering`/`closed → open` synchronously during
|
|
117
|
+
// render so the host element mounts in the same commit as the open
|
|
118
|
+
// intent. All other transitions are driven by phase-specific effects.
|
|
123
119
|
const [phase, setPhase] = useState(() => {
|
|
124
120
|
if (!isOpen) {
|
|
125
121
|
return 'closed';
|
|
@@ -151,10 +147,8 @@ export function useAnimatedVisibility({
|
|
|
151
147
|
const onExitFinishRef = useRef(onExitFinish);
|
|
152
148
|
onExitFinishRef.current = onExitFinish;
|
|
153
149
|
|
|
154
|
-
// `
|
|
155
|
-
//
|
|
156
|
-
// `onEnterFinish`. A fallback timeout (enterDurationMs + 50ms buffer)
|
|
157
|
-
// guarantees the settle path runs even if `transitionend` is swallowed
|
|
150
|
+
// Animated entry: wait for `transitionend` (or a safety-net timeout) and
|
|
151
|
+
// move to `open`. The fallback guarantees settle on swallowed events
|
|
158
152
|
// (zero-duration, browser quirk, mid-transition unmount).
|
|
159
153
|
useEffect(() => {
|
|
160
154
|
var _preset$enterDuration;
|
|
@@ -180,12 +174,9 @@ export function useAnimatedVisibility({
|
|
|
180
174
|
});
|
|
181
175
|
}, [phase, preset === null || preset === void 0 ? void 0 : preset.enterDurationMs, elementRef]);
|
|
182
176
|
|
|
183
|
-
// Non-animated entry callback
|
|
184
|
-
//
|
|
185
|
-
//
|
|
186
|
-
// transition into `open` from the non-animated path. We detect this
|
|
187
|
-
// via a previous-phase ref to avoid firing on the animated path's
|
|
188
|
-
// `entering → open` transition (which has its own callback above).
|
|
177
|
+
// Non-animated entry callback. The lifecycle goes `closed → open`
|
|
178
|
+
// directly with no `entering` phase; fire `onEnterFinish` on that jump.
|
|
179
|
+
// A previous-phase ref avoids double-firing on the animated path.
|
|
189
180
|
const prevPhaseForEntryRef = useRef('closed');
|
|
190
181
|
useEffect(() => {
|
|
191
182
|
const prevPhase = prevPhaseForEntryRef.current;
|
|
@@ -199,12 +190,8 @@ export function useAnimatedVisibility({
|
|
|
199
190
|
}
|
|
200
191
|
}, [phase]);
|
|
201
192
|
|
|
202
|
-
// `
|
|
203
|
-
//
|
|
204
|
-
// (set phase back to `closed`) and fire `onExitFinish`. The fallback
|
|
205
|
-
// timeout (exitDurationMs + 50ms buffer) guarantees we always unmount
|
|
206
|
-
// even if `transitionend` is never emitted (element removed from the
|
|
207
|
-
// top layer, zero-duration transition, interrupted animation).
|
|
193
|
+
// Animated exit: wait for `transitionend` (or a safety-net timeout),
|
|
194
|
+
// fire `onExitFinish`, then move to `closed` to unmount the host.
|
|
208
195
|
useEffect(() => {
|
|
209
196
|
var _preset$exitDurationM;
|
|
210
197
|
if (phase !== 'exiting' || !willAnimate) {
|
|
@@ -231,33 +218,20 @@ export function useAnimatedVisibility({
|
|
|
231
218
|
});
|
|
232
219
|
}, [phase, willAnimate, preset === null || preset === void 0 ? void 0 : preset.exitDurationMs, elementRef]);
|
|
233
220
|
|
|
234
|
-
// `
|
|
235
|
-
//
|
|
236
|
-
//
|
|
237
|
-
//
|
|
238
|
-
//
|
|
239
|
-
//
|
|
240
|
-
// the still-attached element,
|
|
241
|
-
// - the browser's native focus restoration triggered by
|
|
242
|
-
// `hidePopover()` / `<dialog>.close()` has fired,
|
|
243
|
-
// before React unmounts the node. A naive `setTimeout(0)` race
|
|
244
|
-
// occasionally beats the `toggle` task in Chromium, causing focus
|
|
245
|
-
// restoration to drop (the toggle event arrives at a detached element).
|
|
221
|
+
// Non-animated exit: wait for the browser's `toggle` (closed) or
|
|
222
|
+
// `close` event before unmounting, so the consumer's listener (close
|
|
223
|
+
// reason, nested focus restoration, `onClose`) and the browser's
|
|
224
|
+
// native focus restoration both run against the still-attached
|
|
225
|
+
// element. A naive `setTimeout(0)` occasionally beats the `toggle`
|
|
226
|
+
// task in Chromium and drops focus restoration.
|
|
246
227
|
//
|
|
247
|
-
// `useLayoutEffect`
|
|
248
|
-
//
|
|
249
|
-
//
|
|
250
|
-
//
|
|
251
|
-
// frame (including all effect flushes) completes, so the listener is
|
|
252
|
-
// always attached before the event fires. Promotion to layout timing
|
|
253
|
-
// removes any theoretical gap and future-proofs the ordering against
|
|
254
|
-
// effect reordering. Safe under SSR: `phase` starts at `'closed'`, so
|
|
255
|
-
// the body short-circuits and no DOM access happens on the server.
|
|
228
|
+
// `useLayoutEffect` so the listener binds in the same commit as the
|
|
229
|
+
// show/hide layout effect that calls `hidePopover()` / `dialog.close()`.
|
|
230
|
+
// The toggle/close event is queued as a task and cannot run until the
|
|
231
|
+
// frame completes. SSR-safe: `phase` starts at `'closed'`.
|
|
256
232
|
//
|
|
257
|
-
// `onExitFinish` is
|
|
258
|
-
//
|
|
259
|
-
// effect below, so consumers observe a synchronous "exit finished"
|
|
260
|
-
// signal that matches `isOpen` flipping to `false`.
|
|
233
|
+
// `onExitFinish` is fired separately (see effect below) so consumers
|
|
234
|
+
// observe it the moment `isOpen` flips to `false`.
|
|
261
235
|
useLayoutEffect(() => {
|
|
262
236
|
if (phase !== 'exiting' || willAnimate) {
|
|
263
237
|
return;
|
|
@@ -282,13 +256,9 @@ export function useAnimatedVisibility({
|
|
|
282
256
|
}]);
|
|
283
257
|
}, [phase, willAnimate, elementRef]);
|
|
284
258
|
|
|
285
|
-
// Non-animated exit callback
|
|
286
|
-
//
|
|
287
|
-
//
|
|
288
|
-
// host-element unmount is gated on the browser `toggle`/`close` event
|
|
289
|
-
// by the effect above; `onExitFinish` is observably decoupled from
|
|
290
|
-
// that handshake so consumers see the callback fire at the same time
|
|
291
|
-
// `isOpen` flipped to `false`.
|
|
259
|
+
// Non-animated exit callback. No `transitionend` to wait for, so fire
|
|
260
|
+
// `onExitFinish` on the transition into `exiting` (the unmount itself
|
|
261
|
+
// is gated by the toggle/close handshake above).
|
|
292
262
|
const prevPhaseForExitRef = useRef('closed');
|
|
293
263
|
useEffect(() => {
|
|
294
264
|
const prevPhase = prevPhaseForExitRef.current;
|