@atlaskit/top-layer 4.4.3 → 4.4.4
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 +9 -0
- package/dist/cjs/dialog/dialog-content.js +57 -47
- package/dist/cjs/internal/use-animated-visibility.js +238 -165
- package/dist/cjs/popover/popover.js +97 -81
- package/dist/cjs/popover-surface/popover-surface.js +1 -1
- package/dist/es2019/dialog/dialog-content.js +57 -47
- package/dist/es2019/internal/use-animated-visibility.js +235 -154
- package/dist/es2019/popover/popover.js +98 -82
- package/dist/es2019/popover-surface/popover-surface.js +1 -1
- package/dist/esm/dialog/dialog-content.js +57 -47
- package/dist/esm/internal/use-animated-visibility.js +239 -166
- package/dist/esm/popover/popover.js +97 -81
- package/dist/esm/popover-surface/popover-surface.js +1 -1
- package/dist/types/dialog/types.d.ts +6 -4
- package/dist/types/internal/use-animated-visibility.d.ts +37 -50
- package/dist/types/popover/popover.d.ts +3 -1
- package/dist/types/popover/types.d.ts +12 -9
- package/package.json +3 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/* popover.tsx generated by @compiled/babel-plugin
|
|
1
|
+
/* popover.tsx generated by @compiled/babel-plugin v3.0.1 */
|
|
2
2
|
import "./popover.compiled.css";
|
|
3
3
|
import { ax, ix } from "@compiled/react/runtime";
|
|
4
4
|
import React, { forwardRef, useCallback, useId, useLayoutEffect, useMemo, useRef } from 'react';
|
|
@@ -89,7 +89,9 @@ const POPUP_ROLES = new Set(['menu', 'listbox', 'dialog', 'alertdialog', 'tree',
|
|
|
89
89
|
* ### 📜 Browser dismiss is non-cancellable
|
|
90
90
|
*
|
|
91
91
|
* Per the popover spec, `beforetoggle` is cancellable on open transitions
|
|
92
|
-
* only, not on close.
|
|
92
|
+
* only, not on close. Consumers should update controlled intent by setting
|
|
93
|
+
* `isOpen` to `false` from `onClose`. If controlled intent remains open after
|
|
94
|
+
* native close, the Popover remains closed until the prop changes.
|
|
93
95
|
*
|
|
94
96
|
* - Spec: <https://html.spec.whatwg.org/multipage/popover.html#the-popover-attribute>
|
|
95
97
|
* - <https://github.com/whatwg/html/issues/8973>
|
|
@@ -128,45 +130,25 @@ export const Popover = /*#__PURE__*/forwardRef(function Popover({
|
|
|
128
130
|
const combinedRef = mergeRefs([ownRef, ref].filter(Boolean));
|
|
129
131
|
// `useId()` colons are invalid in CSS selectors and popover target attributes.
|
|
130
132
|
const popoverId = idProp !== null && idProp !== void 0 ? idProp : `popover-${autoId.replace(/:/g, '')}`;
|
|
131
|
-
const {
|
|
132
|
-
phase,
|
|
133
|
-
onToggle
|
|
134
|
-
} = useAnimatedVisibility({
|
|
135
|
-
isOpen,
|
|
136
|
-
shouldAnimate,
|
|
137
|
-
elementRef: ownRef,
|
|
138
|
-
onEnterFinish,
|
|
139
|
-
onExitFinish
|
|
140
|
-
});
|
|
141
|
-
|
|
142
|
-
// True while the host element is mounted (any phase except `closed`).
|
|
143
|
-
// Used as a dep so listener-rebind effects re-attach after a remount.
|
|
144
|
-
const isVisible = phase !== 'closed';
|
|
145
133
|
|
|
146
|
-
//
|
|
147
|
-
//
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
phase
|
|
155
|
-
});
|
|
156
|
-
useInitialFocus({
|
|
157
|
-
elementRef: ownRef,
|
|
158
|
-
phase,
|
|
159
|
-
role
|
|
160
|
-
});
|
|
134
|
+
// Keep semantic callbacks current without rebinding native lifecycle
|
|
135
|
+
// listeners during the same commit that calls showPopover()/hidePopover().
|
|
136
|
+
const onCloseRef = useRef(onClose);
|
|
137
|
+
const roleRef = useRef(role);
|
|
138
|
+
useLayoutEffect(() => {
|
|
139
|
+
onCloseRef.current = onClose;
|
|
140
|
+
roleRef.current = role;
|
|
141
|
+
}, [onClose, role]);
|
|
161
142
|
|
|
162
143
|
// Register with open layer observer so `closeLayers()` and open-count
|
|
163
144
|
// subscriptions work. Only popup-like roles register as `popup`; passive
|
|
164
145
|
// roles (tooltip, status, etc.) leave `type` undefined.
|
|
165
146
|
const handleObserverClose = useCallback(() => {
|
|
166
|
-
|
|
147
|
+
var _onCloseRef$current;
|
|
148
|
+
(_onCloseRef$current = onCloseRef.current) === null || _onCloseRef$current === void 0 ? void 0 : _onCloseRef$current.call(onCloseRef, {
|
|
167
149
|
reason: 'programmatic'
|
|
168
150
|
});
|
|
169
|
-
}, [
|
|
151
|
+
}, []);
|
|
170
152
|
useNotifyOpenLayerObserver({
|
|
171
153
|
type: role && POPUP_ROLES.has(role) ? 'popup' : undefined,
|
|
172
154
|
isOpen,
|
|
@@ -208,51 +190,76 @@ export const Popover = /*#__PURE__*/forwardRef(function Popover({
|
|
|
208
190
|
const active = document.activeElement;
|
|
209
191
|
previouslyFocusedElementRef.current = active instanceof HTMLElement ? active : null;
|
|
210
192
|
}, []);
|
|
211
|
-
const
|
|
212
|
-
const
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
193
|
+
const restorePreviouslyFocusedElement = useCallback(() => {
|
|
194
|
+
const previouslyFocused = previouslyFocusedElementRef.current;
|
|
195
|
+
previouslyFocusedElementRef.current = null;
|
|
196
|
+
if (previouslyFocused !== null && previouslyFocused.isConnected && shouldFocusIntoPopover({
|
|
197
|
+
role: roleRef.current
|
|
198
|
+
})) {
|
|
199
|
+
previouslyFocused.focus({
|
|
200
|
+
preventScroll: true
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
}, []);
|
|
204
|
+
const handleToggleClosed = useCallback(_event => {
|
|
205
|
+
var _onCloseRef$current2;
|
|
206
|
+
// Nested-popover focus restoration fallback (browser only
|
|
207
|
+
// restores outermost; Firefox skips nested entirely).
|
|
208
|
+
// Restore only when the role moved focus in on open AND the
|
|
209
|
+
// close was Escape/programmatic. For light dismiss, the
|
|
210
|
+
// click target keeps focus per HTML spec
|
|
211
|
+
// (`focusPreviousElement=false`).
|
|
212
|
+
const reason = closeReasonRef.current;
|
|
213
|
+
const isProgrammatic = programmaticCloseRef.current;
|
|
214
|
+
if (reason === 'escape' || isProgrammatic) {
|
|
215
|
+
restorePreviouslyFocusedElement();
|
|
216
|
+
} else {
|
|
217
|
+
previouslyFocusedElementRef.current = null;
|
|
216
218
|
}
|
|
217
|
-
if (event.newState === 'closed') {
|
|
218
|
-
if (element) {
|
|
219
|
-
// Nested-popover focus restoration fallback (browser only
|
|
220
|
-
// restores outermost; Firefox skips nested entirely).
|
|
221
|
-
// Restore only when the role moved focus in on open AND the
|
|
222
|
-
// close was Escape/programmatic. For light dismiss, the
|
|
223
|
-
// click target keeps focus per HTML spec
|
|
224
|
-
// (`focusPreviousElement=false`).
|
|
225
|
-
const previouslyFocused = previouslyFocusedElementRef.current;
|
|
226
|
-
const reason = closeReasonRef.current;
|
|
227
|
-
const isProgrammatic = programmaticCloseRef.current;
|
|
228
|
-
const shouldRestore = previouslyFocused !== null && previouslyFocused.isConnected && shouldFocusIntoPopover({
|
|
229
|
-
role
|
|
230
|
-
}) && (reason === 'escape' || isProgrammatic);
|
|
231
|
-
if (shouldRestore && previouslyFocused) {
|
|
232
|
-
previouslyFocused.focus({
|
|
233
|
-
preventScroll: true
|
|
234
|
-
});
|
|
235
|
-
}
|
|
236
|
-
previouslyFocusedElementRef.current = null;
|
|
237
|
-
}
|
|
238
219
|
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
const reason = closeReasonRef.current;
|
|
247
|
-
closeReasonRef.current = 'light-dismiss';
|
|
248
|
-
// Optional: `manual` mode has no `onClose` in the forwarded type.
|
|
249
|
-
onClose === null || onClose === void 0 ? void 0 : onClose({
|
|
250
|
-
reason
|
|
251
|
-
});
|
|
252
|
-
}
|
|
220
|
+
// Reset reason so a stale 'escape' from a race with Escape keydown
|
|
221
|
+
// does not corrupt the next browser-dismiss cycle.
|
|
222
|
+
closeReasonRef.current = 'light-dismiss';
|
|
223
|
+
|
|
224
|
+
// Programmatic close: consumer already knows.
|
|
225
|
+
if (isProgrammatic) {
|
|
226
|
+
return;
|
|
253
227
|
}
|
|
254
|
-
|
|
255
|
-
|
|
228
|
+
|
|
229
|
+
// Browser dismiss (Escape/click-outside).
|
|
230
|
+
// Optional: `manual` mode has no `onClose` in the forwarded type.
|
|
231
|
+
(_onCloseRef$current2 = onCloseRef.current) === null || _onCloseRef$current2 === void 0 ? void 0 : _onCloseRef$current2.call(onCloseRef, {
|
|
232
|
+
reason
|
|
233
|
+
});
|
|
234
|
+
}, [restorePreviouslyFocusedElement]);
|
|
235
|
+
const {
|
|
236
|
+
phase,
|
|
237
|
+
isMounted,
|
|
238
|
+
onBeforeToggle,
|
|
239
|
+
onToggle
|
|
240
|
+
} = useAnimatedVisibility({
|
|
241
|
+
isOpen,
|
|
242
|
+
shouldAnimate,
|
|
243
|
+
elementRef: ownRef,
|
|
244
|
+
onEnterFinish,
|
|
245
|
+
onExitFinish
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
// Focus management: initial focus on entry (role-dependent), Tab cycling
|
|
249
|
+
// for dialog roles. Restoration is native for outermost popovers; nested
|
|
250
|
+
// focus-capturing roles are handled above via the `beforetoggle` snapshot.
|
|
251
|
+
// Passing `phase` keeps `useFocusWrap` attached through the animated-exit
|
|
252
|
+
// window (WCAG 2.4.3 guard). See notes/architecture/focus-restoration.md.
|
|
253
|
+
useFocusWrap({
|
|
254
|
+
elementRef: ownRef,
|
|
255
|
+
role,
|
|
256
|
+
phase
|
|
257
|
+
});
|
|
258
|
+
useInitialFocus({
|
|
259
|
+
elementRef: ownRef,
|
|
260
|
+
phase,
|
|
261
|
+
role
|
|
262
|
+
});
|
|
256
263
|
|
|
257
264
|
// Bind via `useLayoutEffect`, and BEFORE the show/hide layout effect
|
|
258
265
|
// below, so listeners are attached when `showPopover()` synchronously
|
|
@@ -280,19 +287,28 @@ export const Popover = /*#__PURE__*/forwardRef(function Popover({
|
|
|
280
287
|
});
|
|
281
288
|
const unbindToggle = bind(element, {
|
|
282
289
|
type: 'toggle',
|
|
283
|
-
listener:
|
|
290
|
+
listener: event => {
|
|
291
|
+
if (event.newState === 'closed') {
|
|
292
|
+
handleToggleClosed(event);
|
|
293
|
+
}
|
|
294
|
+
onToggle(event);
|
|
295
|
+
}
|
|
284
296
|
});
|
|
285
|
-
// Snapshot `document.activeElement` before `useInitialFocus` moves it
|
|
297
|
+
// Snapshot `document.activeElement` before `useInitialFocus` moves it,
|
|
298
|
+
// then advance the shared visibility lifecycle.
|
|
286
299
|
const unbindBeforeToggle = bind(element, {
|
|
287
300
|
type: 'beforetoggle',
|
|
288
|
-
listener:
|
|
301
|
+
listener: event => {
|
|
302
|
+
handleBeforeToggle(event);
|
|
303
|
+
onBeforeToggle(event);
|
|
304
|
+
}
|
|
289
305
|
});
|
|
290
306
|
return () => {
|
|
291
307
|
unbindEscape();
|
|
292
308
|
unbindToggle();
|
|
293
309
|
unbindBeforeToggle();
|
|
294
310
|
};
|
|
295
|
-
}, [
|
|
311
|
+
}, [handleBeforeToggle, handleToggleClosed, isMounted, onBeforeToggle, onToggle]);
|
|
296
312
|
|
|
297
313
|
// Show/hide based on isOpen. `showPopover`/`hidePopover` are no-ops when
|
|
298
314
|
// already in the target state. Try/catch guards `InvalidStateError` on
|
|
@@ -305,8 +321,8 @@ export const Popover = /*#__PURE__*/forwardRef(function Popover({
|
|
|
305
321
|
if (isOpen) {
|
|
306
322
|
programmaticCloseRef.current = false;
|
|
307
323
|
// Clear stale 'escape' from a prior cycle: the ref outlives
|
|
308
|
-
// the host element, and
|
|
309
|
-
// `
|
|
324
|
+
// the host element, and interrupted close lifecycles may not reach
|
|
325
|
+
// `handleToggleClosed` to reset it.
|
|
310
326
|
closeReasonRef.current = 'light-dismiss';
|
|
311
327
|
try {
|
|
312
328
|
element.showPopover();
|
|
@@ -327,7 +343,7 @@ export const Popover = /*#__PURE__*/forwardRef(function Popover({
|
|
|
327
343
|
// Unmount the host once exit completes so it does not leave an empty
|
|
328
344
|
// `role`/`popover` element in the accessibility tree. The element
|
|
329
345
|
// remounts on the next open; the `popoverId` is stable across opens.
|
|
330
|
-
if (!
|
|
346
|
+
if (!isMounted) {
|
|
331
347
|
return null;
|
|
332
348
|
}
|
|
333
349
|
return /*#__PURE__*/React.createElement("div", {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/* dialog-content.tsx generated by @compiled/babel-plugin
|
|
1
|
+
/* dialog-content.tsx generated by @compiled/babel-plugin v3.0.1 */
|
|
2
2
|
import "./dialog-content.compiled.css";
|
|
3
3
|
import { ax, ix } from "@compiled/react/runtime";
|
|
4
4
|
import React, { forwardRef, useCallback, useId, useLayoutEffect, useRef } from 'react';
|
|
@@ -76,6 +76,13 @@ export var Dialog = /*#__PURE__*/forwardRef(function Dialog(_ref, ref) {
|
|
|
76
76
|
var ownRef = useRef(null);
|
|
77
77
|
var combinedRef = mergeRefs([ownRef, ref]);
|
|
78
78
|
var closeReasonRef = useRef(null);
|
|
79
|
+
var dismissedByRef = useRef(dismissedBy);
|
|
80
|
+
var onCloseRef = useRef(onClose);
|
|
81
|
+
// Keep native event handlers stable while ensuring they read the latest committed props.
|
|
82
|
+
useLayoutEffect(function () {
|
|
83
|
+
dismissedByRef.current = dismissedBy;
|
|
84
|
+
onCloseRef.current = onClose;
|
|
85
|
+
}, [dismissedBy, onClose]);
|
|
79
86
|
var _useAnimatedVisibilit = useAnimatedVisibility({
|
|
80
87
|
isOpen: isOpen,
|
|
81
88
|
shouldAnimate: shouldAnimate,
|
|
@@ -84,11 +91,52 @@ export var Dialog = /*#__PURE__*/forwardRef(function Dialog(_ref, ref) {
|
|
|
84
91
|
onExitFinish: onExitFinish
|
|
85
92
|
}),
|
|
86
93
|
phase = _useAnimatedVisibilit.phase,
|
|
94
|
+
isMounted = _useAnimatedVisibilit.isMounted,
|
|
95
|
+
onBeforeToggle = _useAnimatedVisibilit.onBeforeToggle,
|
|
87
96
|
onToggle = _useAnimatedVisibilit.onToggle;
|
|
88
97
|
|
|
89
|
-
//
|
|
90
|
-
//
|
|
91
|
-
|
|
98
|
+
// Ordering is important: bind native lifecycle listeners before the later
|
|
99
|
+
// layout effect calls showModal() or close(), because `beforetoggle` fires
|
|
100
|
+
// synchronously during those calls.
|
|
101
|
+
useLayoutEffect(function () {
|
|
102
|
+
if (!isMounted) {
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
var dialog = ownRef.current;
|
|
106
|
+
if (!dialog) {
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
return bindAll(dialog, [{
|
|
110
|
+
type: 'beforetoggle',
|
|
111
|
+
listener: onBeforeToggle
|
|
112
|
+
}, {
|
|
113
|
+
// Browsers retarget clicks on ::backdrop to the <dialog> element.
|
|
114
|
+
// Close on backdrop click when outside-click dismissal is enabled.
|
|
115
|
+
type: 'click',
|
|
116
|
+
listener: function listener(event) {
|
|
117
|
+
if (event.target === event.currentTarget && dismissedByRef.current === 'escape-and-outside-click') {
|
|
118
|
+
closeReasonRef.current = 'overlay-click';
|
|
119
|
+
dialog.close();
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}, {
|
|
123
|
+
type: 'toggle',
|
|
124
|
+
listener: function listener(event) {
|
|
125
|
+
if (event.newState === 'closed') {
|
|
126
|
+
var reason = closeReasonRef.current;
|
|
127
|
+
closeReasonRef.current = null;
|
|
128
|
+
// A reason is only set for user-initiated closes. When React closes the
|
|
129
|
+
// dialog because isOpen changed, the consumer already knows.
|
|
130
|
+
if (reason) {
|
|
131
|
+
onCloseRef.current({
|
|
132
|
+
reason: reason
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
onToggle(event);
|
|
137
|
+
}
|
|
138
|
+
}]);
|
|
139
|
+
}, [isMounted, onBeforeToggle, onToggle]);
|
|
92
140
|
|
|
93
141
|
// Native `<dialog>.showModal()` traps focus but wraps through `<body>` at
|
|
94
142
|
// the boundary (A → B → C → body → A). This hook intercepts Tab to wrap
|
|
@@ -134,7 +182,7 @@ export var Dialog = /*#__PURE__*/forwardRef(function Dialog(_ref, ref) {
|
|
|
134
182
|
// See notes/decisions/safari-escape-nested-popover-in-dialog.md
|
|
135
183
|
var _useSafariEscapeFix = useSafariEscapeFix({
|
|
136
184
|
dialogRef: ownRef,
|
|
137
|
-
isVisible:
|
|
185
|
+
isVisible: isMounted
|
|
138
186
|
}),
|
|
139
187
|
shouldIgnoreEscape = _useSafariEscapeFix.shouldIgnoreEscape;
|
|
140
188
|
|
|
@@ -167,49 +215,11 @@ export var Dialog = /*#__PURE__*/forwardRef(function Dialog(_ref, ref) {
|
|
|
167
215
|
closeReasonRef.current = 'escape';
|
|
168
216
|
}, [dismissedBy, shouldIgnoreEscape]);
|
|
169
217
|
|
|
170
|
-
// Handle backdrop interaction
|
|
171
|
-
// Attached via bind-event-listener rather than a React prop so we avoid
|
|
172
|
-
// a11y lint suppressions on the <dialog> element.
|
|
173
|
-
// The click listener provides light dismiss consistently across browsers.
|
|
174
|
-
// `isVisible` is in deps so the listener re-binds to the new <dialog>
|
|
175
|
-
// element after a host unmount / remount cycle.
|
|
176
|
-
useLayoutEffect(function () {
|
|
177
|
-
var dialog = ownRef.current;
|
|
178
|
-
if (!dialog) {
|
|
179
|
-
return;
|
|
180
|
-
}
|
|
181
|
-
return bindAll(dialog, [{
|
|
182
|
-
type: 'click',
|
|
183
|
-
listener: function listener(event) {
|
|
184
|
-
if (event.target === event.currentTarget && dismissedBy === 'escape-and-outside-click') {
|
|
185
|
-
closeReasonRef.current = 'overlay-click';
|
|
186
|
-
// Calls native close because this is emulating the native light-dismiss behavior
|
|
187
|
-
dialog.close();
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
}, {
|
|
191
|
-
type: 'toggle',
|
|
192
|
-
listener: function listener(event) {
|
|
193
|
-
if (event.newState === 'closed') {
|
|
194
|
-
var reason = closeReasonRef.current;
|
|
195
|
-
closeReasonRef.current = null;
|
|
196
|
-
if (reason) {
|
|
197
|
-
onClose({
|
|
198
|
-
reason: reason
|
|
199
|
-
});
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
// Visibility cleanup runs after close-reason handling.
|
|
204
|
-
onToggle(event);
|
|
205
|
-
}
|
|
206
|
-
}]);
|
|
207
|
-
}, [dismissedBy, onClose, onToggle, isVisible]);
|
|
208
|
-
|
|
209
218
|
// Unmount the `<dialog>` once exit completes so it does not leave an
|
|
210
|
-
// empty `role="dialog"` element in the accessibility tree.
|
|
211
|
-
//
|
|
212
|
-
|
|
219
|
+
// empty `role="dialog"` element in the accessibility tree. A later open
|
|
220
|
+
// remounts a fresh element, so the listener-binding and native-visibility
|
|
221
|
+
// effects run against that element.
|
|
222
|
+
if (!isMounted) {
|
|
213
223
|
return null;
|
|
214
224
|
}
|
|
215
225
|
var shouldAnimateBackdrop = !shouldHideBackdrop && shouldAnimate;
|