@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.
@@ -1,4 +1,4 @@
1
- /* popover.tsx generated by @compiled/babel-plugin v2.0.0 */
1
+ /* popover.tsx generated by @compiled/babel-plugin v3.0.1 */
2
2
  "use strict";
3
3
 
4
4
  var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
@@ -98,7 +98,9 @@ var POPUP_ROLES = new Set(['menu', 'listbox', 'dialog', 'alertdialog', 'tree', '
98
98
  * ### 📜 Browser dismiss is non-cancellable
99
99
  *
100
100
  * Per the popover spec, `beforetoggle` is cancellable on open transitions
101
- * only, not on close.
101
+ * only, not on close. Consumers should update controlled intent by setting
102
+ * `isOpen` to `false` from `onClose`. If controlled intent remains open after
103
+ * native close, the Popover remains closed until the prop changes.
102
104
  *
103
105
  * - Spec: <https://html.spec.whatwg.org/multipage/popover.html#the-popover-attribute>
104
106
  * - <https://github.com/whatwg/html/issues/8973>
@@ -137,44 +139,25 @@ var Popover = exports.Popover = /*#__PURE__*/(0, _react.forwardRef)(function Pop
137
139
  var combinedRef = (0, _mergeRefs.default)([ownRef, ref].filter(Boolean));
138
140
  // `useId()` colons are invalid in CSS selectors and popover target attributes.
139
141
  var popoverId = idProp !== null && idProp !== void 0 ? idProp : "popover-".concat(autoId.replace(/:/g, ''));
140
- var _useAnimatedVisibilit = (0, _useAnimatedVisibility.useAnimatedVisibility)({
141
- isOpen: isOpen,
142
- shouldAnimate: shouldAnimate,
143
- elementRef: ownRef,
144
- onEnterFinish: onEnterFinish,
145
- onExitFinish: onExitFinish
146
- }),
147
- phase = _useAnimatedVisibilit.phase,
148
- onToggle = _useAnimatedVisibilit.onToggle;
149
142
 
150
- // True while the host element is mounted (any phase except `closed`).
151
- // Used as a dep so listener-rebind effects re-attach after a remount.
152
- var isVisible = phase !== 'closed';
153
-
154
- // Focus management: initial focus on entry (role-dependent), Tab cycling
155
- // for dialog roles. Restoration is native for outermost popovers; nested
156
- // focus-capturing roles are handled below via the `beforetoggle` snapshot.
157
- // Passing `phase` keeps `useFocusWrap` attached through the animated-exit
158
- // window (WCAG 2.4.3 guard). See notes/architecture/focus-restoration.md.
159
- (0, _useFocusWrap.useFocusWrap)({
160
- elementRef: ownRef,
161
- role: role,
162
- phase: phase
163
- });
164
- (0, _useInitialFocus.useInitialFocus)({
165
- elementRef: ownRef,
166
- phase: phase,
167
- role: role
168
- });
143
+ // Keep semantic callbacks current without rebinding native lifecycle
144
+ // listeners during the same commit that calls showPopover()/hidePopover().
145
+ var onCloseRef = (0, _react.useRef)(onClose);
146
+ var roleRef = (0, _react.useRef)(role);
147
+ (0, _react.useLayoutEffect)(function () {
148
+ onCloseRef.current = onClose;
149
+ roleRef.current = role;
150
+ }, [onClose, role]);
169
151
 
170
152
  // Register with open layer observer so `closeLayers()` and open-count
171
153
  // subscriptions work. Only popup-like roles register as `popup`; passive
172
154
  // roles (tooltip, status, etc.) leave `type` undefined.
173
155
  var handleObserverClose = (0, _react.useCallback)(function () {
174
- onClose === null || onClose === void 0 || onClose({
156
+ var _onCloseRef$current;
157
+ (_onCloseRef$current = onCloseRef.current) === null || _onCloseRef$current === void 0 || _onCloseRef$current.call(onCloseRef, {
175
158
  reason: 'programmatic'
176
159
  });
177
- }, [onClose]);
160
+ }, []);
178
161
  (0, _useNotifyOpenLayerObserver.useNotifyOpenLayerObserver)({
179
162
  type: role && POPUP_ROLES.has(role) ? 'popup' : undefined,
180
163
  isOpen: isOpen,
@@ -215,51 +198,75 @@ var Popover = exports.Popover = /*#__PURE__*/(0, _react.forwardRef)(function Pop
215
198
  var active = document.activeElement;
216
199
  previouslyFocusedElementRef.current = active instanceof HTMLElement ? active : null;
217
200
  }, []);
218
- var handleToggle = (0, _react.useCallback)(function (event) {
219
- var element = ownRef.current;
220
- if (event.newState === 'open') {
221
- onToggle(event);
222
- return;
201
+ var restorePreviouslyFocusedElement = (0, _react.useCallback)(function () {
202
+ var previouslyFocused = previouslyFocusedElementRef.current;
203
+ previouslyFocusedElementRef.current = null;
204
+ if (previouslyFocused !== null && previouslyFocused.isConnected && (0, _shouldFocusIntoPopover.shouldFocusIntoPopover)({
205
+ role: roleRef.current
206
+ })) {
207
+ previouslyFocused.focus({
208
+ preventScroll: true
209
+ });
210
+ }
211
+ }, []);
212
+ var handleToggleClosed = (0, _react.useCallback)(function (_event) {
213
+ var _onCloseRef$current2;
214
+ // Nested-popover focus restoration fallback (browser only
215
+ // restores outermost; Firefox skips nested entirely).
216
+ // Restore only when the role moved focus in on open AND the
217
+ // close was Escape/programmatic. For light dismiss, the
218
+ // click target keeps focus per HTML spec
219
+ // (`focusPreviousElement=false`).
220
+ var reason = closeReasonRef.current;
221
+ var isProgrammatic = programmaticCloseRef.current;
222
+ if (reason === 'escape' || isProgrammatic) {
223
+ restorePreviouslyFocusedElement();
224
+ } else {
225
+ previouslyFocusedElementRef.current = null;
223
226
  }
224
- if (event.newState === 'closed') {
225
- if (element) {
226
- // Nested-popover focus restoration fallback (browser only
227
- // restores outermost; Firefox skips nested entirely).
228
- // Restore only when the role moved focus in on open AND the
229
- // close was Escape/programmatic. For light dismiss, the
230
- // click target keeps focus per HTML spec
231
- // (`focusPreviousElement=false`).
232
- var previouslyFocused = previouslyFocusedElementRef.current;
233
- var reason = closeReasonRef.current;
234
- var isProgrammatic = programmaticCloseRef.current;
235
- var shouldRestore = previouslyFocused !== null && previouslyFocused.isConnected && (0, _shouldFocusIntoPopover.shouldFocusIntoPopover)({
236
- role: role
237
- }) && (reason === 'escape' || isProgrammatic);
238
- if (shouldRestore && previouslyFocused) {
239
- previouslyFocused.focus({
240
- preventScroll: true
241
- });
242
- }
243
- previouslyFocusedElementRef.current = null;
244
- }
245
227
 
246
- // Programmatic close: consumer already knows.
247
- if (programmaticCloseRef.current) {
248
- // Reset reason so a stale 'escape' from a race with Escape keydown
249
- // does not corrupt the next browser-dismiss cycle.
250
- closeReasonRef.current = 'light-dismiss';
251
- } else {
252
- // Browser dismiss (Escape/click-outside).
253
- var _reason = closeReasonRef.current;
254
- closeReasonRef.current = 'light-dismiss';
255
- // Optional: `manual` mode has no `onClose` in the forwarded type.
256
- onClose === null || onClose === void 0 || onClose({
257
- reason: _reason
258
- });
259
- }
228
+ // Reset reason so a stale 'escape' from a race with Escape keydown
229
+ // does not corrupt the next browser-dismiss cycle.
230
+ closeReasonRef.current = 'light-dismiss';
231
+
232
+ // Programmatic close: consumer already knows.
233
+ if (isProgrammatic) {
234
+ return;
260
235
  }
261
- onToggle(event);
262
- }, [onClose, onToggle, role]);
236
+
237
+ // Browser dismiss (Escape/click-outside).
238
+ // Optional: `manual` mode has no `onClose` in the forwarded type.
239
+ (_onCloseRef$current2 = onCloseRef.current) === null || _onCloseRef$current2 === void 0 || _onCloseRef$current2.call(onCloseRef, {
240
+ reason: reason
241
+ });
242
+ }, [restorePreviouslyFocusedElement]);
243
+ var _useAnimatedVisibilit = (0, _useAnimatedVisibility.useAnimatedVisibility)({
244
+ isOpen: isOpen,
245
+ shouldAnimate: shouldAnimate,
246
+ elementRef: ownRef,
247
+ onEnterFinish: onEnterFinish,
248
+ onExitFinish: onExitFinish
249
+ }),
250
+ phase = _useAnimatedVisibilit.phase,
251
+ isMounted = _useAnimatedVisibilit.isMounted,
252
+ onBeforeToggle = _useAnimatedVisibilit.onBeforeToggle,
253
+ onToggle = _useAnimatedVisibilit.onToggle;
254
+
255
+ // Focus management: initial focus on entry (role-dependent), Tab cycling
256
+ // for dialog roles. Restoration is native for outermost popovers; nested
257
+ // focus-capturing roles are handled above via the `beforetoggle` snapshot.
258
+ // Passing `phase` keeps `useFocusWrap` attached through the animated-exit
259
+ // window (WCAG 2.4.3 guard). See notes/architecture/focus-restoration.md.
260
+ (0, _useFocusWrap.useFocusWrap)({
261
+ elementRef: ownRef,
262
+ role: role,
263
+ phase: phase
264
+ });
265
+ (0, _useInitialFocus.useInitialFocus)({
266
+ elementRef: ownRef,
267
+ phase: phase,
268
+ role: role
269
+ });
263
270
 
264
271
  // Bind via `useLayoutEffect`, and BEFORE the show/hide layout effect
265
272
  // below, so listeners are attached when `showPopover()` synchronously
@@ -287,19 +294,28 @@ var Popover = exports.Popover = /*#__PURE__*/(0, _react.forwardRef)(function Pop
287
294
  });
288
295
  var unbindToggle = (0, _bindEventListener.bind)(element, {
289
296
  type: 'toggle',
290
- listener: handleToggle
297
+ listener: function listener(event) {
298
+ if (event.newState === 'closed') {
299
+ handleToggleClosed(event);
300
+ }
301
+ onToggle(event);
302
+ }
291
303
  });
292
- // Snapshot `document.activeElement` before `useInitialFocus` moves it.
304
+ // Snapshot `document.activeElement` before `useInitialFocus` moves it,
305
+ // then advance the shared visibility lifecycle.
293
306
  var unbindBeforeToggle = (0, _bindEventListener.bind)(element, {
294
307
  type: 'beforetoggle',
295
- listener: handleBeforeToggle
308
+ listener: function listener(event) {
309
+ handleBeforeToggle(event);
310
+ onBeforeToggle(event);
311
+ }
296
312
  });
297
313
  return function () {
298
314
  unbindEscape();
299
315
  unbindToggle();
300
316
  unbindBeforeToggle();
301
317
  };
302
- }, [handleToggle, handleBeforeToggle, isVisible]);
318
+ }, [handleBeforeToggle, handleToggleClosed, isMounted, onBeforeToggle, onToggle]);
303
319
 
304
320
  // Show/hide based on isOpen. `showPopover`/`hidePopover` are no-ops when
305
321
  // already in the target state. Try/catch guards `InvalidStateError` on
@@ -312,8 +328,8 @@ var Popover = exports.Popover = /*#__PURE__*/(0, _react.forwardRef)(function Pop
312
328
  if (isOpen) {
313
329
  programmaticCloseRef.current = false;
314
330
  // Clear stale 'escape' from a prior cycle: the ref outlives
315
- // the host element, and the no-animation path unmounts before
316
- // `handleToggle` can reset it.
331
+ // the host element, and interrupted close lifecycles may not reach
332
+ // `handleToggleClosed` to reset it.
317
333
  closeReasonRef.current = 'light-dismiss';
318
334
  try {
319
335
  element.showPopover();
@@ -334,7 +350,7 @@ var Popover = exports.Popover = /*#__PURE__*/(0, _react.forwardRef)(function Pop
334
350
  // Unmount the host once exit completes so it does not leave an empty
335
351
  // `role`/`popover` element in the accessibility tree. The element
336
352
  // remounts on the next open; the `popoverId` is stable across opens.
337
- if (!isVisible) {
353
+ if (!isMounted) {
338
354
  return null;
339
355
  }
340
356
  return /*#__PURE__*/_react.default.createElement("div", {
@@ -1,4 +1,4 @@
1
- /* popover-surface.tsx generated by @compiled/babel-plugin v2.0.0 */
1
+ /* popover-surface.tsx generated by @compiled/babel-plugin v3.0.1 */
2
2
  "use strict";
3
3
 
4
4
  var _typeof = require("@babel/runtime/helpers/typeof");
@@ -1,4 +1,4 @@
1
- /* dialog-content.tsx generated by @compiled/babel-plugin v2.0.0 */
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';
@@ -75,8 +75,17 @@ export const Dialog = /*#__PURE__*/forwardRef(function Dialog({
75
75
  const ownRef = useRef(null);
76
76
  const combinedRef = mergeRefs([ownRef, ref]);
77
77
  const closeReasonRef = useRef(null);
78
+ const dismissedByRef = useRef(dismissedBy);
79
+ const onCloseRef = useRef(onClose);
80
+ // Keep native event handlers stable while ensuring they read the latest committed props.
81
+ useLayoutEffect(() => {
82
+ dismissedByRef.current = dismissedBy;
83
+ onCloseRef.current = onClose;
84
+ }, [dismissedBy, onClose]);
78
85
  const {
79
86
  phase,
87
+ isMounted,
88
+ onBeforeToggle,
80
89
  onToggle
81
90
  } = useAnimatedVisibility({
82
91
  isOpen,
@@ -86,9 +95,48 @@ export const Dialog = /*#__PURE__*/forwardRef(function Dialog({
86
95
  onExitFinish
87
96
  });
88
97
 
89
- // True while the host element is mounted (any phase except `closed`).
90
- // Used as a dep so listener-rebind effects re-attach after a remount.
91
- const isVisible = phase !== 'closed';
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(() => {
102
+ if (!isMounted) {
103
+ return;
104
+ }
105
+ const 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(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(event) {
125
+ if (event.newState === 'closed') {
126
+ const 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
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
@@ -136,7 +184,7 @@ export const Dialog = /*#__PURE__*/forwardRef(function Dialog({
136
184
  shouldIgnoreEscape
137
185
  } = useSafariEscapeFix({
138
186
  dialogRef: ownRef,
139
- isVisible
187
+ isVisible: isMounted
140
188
  });
141
189
 
142
190
  // Handle native Escape (cancel event)
@@ -168,49 +216,11 @@ export const Dialog = /*#__PURE__*/forwardRef(function Dialog({
168
216
  closeReasonRef.current = 'escape';
169
217
  }, [dismissedBy, shouldIgnoreEscape]);
170
218
 
171
- // Handle backdrop interaction
172
- // Attached via bind-event-listener rather than a React prop so we avoid
173
- // a11y lint suppressions on the <dialog> element.
174
- // The click listener provides light dismiss consistently across browsers.
175
- // `isVisible` is in deps so the listener re-binds to the new <dialog>
176
- // element after a host unmount / remount cycle.
177
- useLayoutEffect(() => {
178
- const dialog = ownRef.current;
179
- if (!dialog) {
180
- return;
181
- }
182
- return bindAll(dialog, [{
183
- type: 'click',
184
- listener(event) {
185
- if (event.target === event.currentTarget && dismissedBy === 'escape-and-outside-click') {
186
- closeReasonRef.current = 'overlay-click';
187
- // Calls native close because this is emulating the native light-dismiss behavior
188
- dialog.close();
189
- }
190
- }
191
- }, {
192
- type: 'toggle',
193
- listener(event) {
194
- if (event.newState === 'closed') {
195
- const reason = closeReasonRef.current;
196
- closeReasonRef.current = null;
197
- if (reason) {
198
- onClose({
199
- reason
200
- });
201
- }
202
- }
203
-
204
- // Visibility cleanup runs after close-reason handling.
205
- onToggle(event);
206
- }
207
- }]);
208
- }, [dismissedBy, onClose, onToggle, isVisible]);
209
-
210
219
  // Unmount the `<dialog>` once exit completes so it does not leave an
211
- // empty `role="dialog"` element in the accessibility tree. On the next
212
- // open the element remounts and its effects run against the fresh element.
213
- if (!isVisible) {
220
+ // empty `role="dialog"` element in the accessibility tree. A later open
221
+ // remounts a fresh element, so the listener-binding and native-visibility
222
+ // effects run against that element.
223
+ if (!isMounted) {
214
224
  return null;
215
225
  }
216
226
  const shouldAnimateBackdrop = !shouldHideBackdrop && shouldAnimate;