@atlaskit/top-layer 4.2.0 → 4.2.1

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 CHANGED
@@ -1,5 +1,13 @@
1
1
  # @atlaskit/top-layer
2
2
 
3
+ ## 4.2.1
4
+
5
+ ### Patch Changes
6
+
7
+ - [`b348503c8c9cb`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/b348503c8c9cb) -
8
+ Use the Web Animations API to wait for all active top-layer animations before completing
9
+ visibility callbacks.
10
+
3
11
  ## 4.2.0
4
12
 
5
13
  ### Minor Changes
@@ -87,7 +87,6 @@ var Dialog = exports.Dialog = /*#__PURE__*/(0, _react.forwardRef)(function Dialo
87
87
  var closeReasonRef = (0, _react.useRef)(null);
88
88
  var _useAnimatedVisibilit = (0, _useAnimatedVisibility.useAnimatedVisibility)({
89
89
  isOpen: isOpen,
90
- animationKind: 'dialog',
91
90
  shouldAnimate: shouldAnimate,
92
91
  elementRef: ownRef,
93
92
  onEnterFinish: onEnterFinish,
@@ -7,64 +7,39 @@ Object.defineProperty(exports, "__esModule", {
7
7
  exports.useAnimatedVisibility = useAnimatedVisibility;
8
8
  var _slicedToArray2 = _interopRequireDefault(require("@babel/runtime/helpers/slicedToArray"));
9
9
  var _react = require("react");
10
- var _bindEventListener = require("bind-event-listener");
11
10
  var _reducedMotion = require("./reduced-motion");
12
- /**
13
- * Using the recommended motion timings for safety net.
14
- *
15
- * Not setting these timings on the preset config because they're defined in the CSS,
16
- * so configuring it in the preset doesn't achieve anything except introduce a potential
17
- * source of drift.
18
- *
19
- * TODO: figure out a way to not need to redeclare these in the future,
20
- * and keep CSS and JS timings in sync always.
21
- */
22
- var safetyNetMsMap = {
23
- dialog: {
24
- enter: 250,
25
- // Corresponds to `motion.duration.long`
26
- exit: 200 // Corresponds to `motion.duration.medium`
27
- },
28
- popover: {
29
- enter: 150,
30
- // Corresponds to `motion.duration.short`
31
- exit: 100 // Corresponds to `motion.duration.xshort`
32
- }
33
- };
34
-
35
- /**
36
- * Fires `onSettled` once when `transitionend` fires on `element`, or after
37
- * `safetyNetMs` if the event is swallowed (mid-transition unmount, CSS
38
- * mismatch, browser quirks). Returns a cleanup that cancels both paths.
39
- */
40
- function runOnTransitionEndOrTimeout(_ref) {
41
- var element = _ref.element,
42
- safetyNetMs = _ref.safetyNetMs,
11
+ function runCancellableSettlement(_ref) {
12
+ var schedule = _ref.schedule,
43
13
  onSettled = _ref.onSettled;
44
- var state = {
45
- isSettled: false
46
- };
47
- function teardown() {
48
- state.isSettled = true;
49
- clearTimeout(safetyNetId);
50
- unbind();
51
- }
52
- function settle() {
53
- if (state.isSettled) {
14
+ var isCancelled = false;
15
+ schedule(function () {
16
+ if (isCancelled) {
54
17
  return;
55
18
  }
56
- teardown();
57
19
  onSettled();
58
- }
59
- var unbind = (0, _bindEventListener.bind)(element, {
60
- type: 'transitionend',
61
- listener: settle,
62
- options: {
63
- once: true
20
+ });
21
+ return function () {
22
+ isCancelled = true;
23
+ };
24
+ }
25
+
26
+ /**
27
+ * Fires `onSettled` once all animations currently applied to `element`
28
+ * finish or are cancelled. Returns a cleanup that prevents stale settlement.
29
+ */
30
+ function runOnAnimationsSettled(_ref2) {
31
+ var element = _ref2.element,
32
+ onSettled = _ref2.onSettled;
33
+ return runCancellableSettlement({
34
+ onSettled: onSettled,
35
+ schedule: function schedule(settle) {
36
+ var _element$getAnimation, _element$getAnimation2;
37
+ var animations = (_element$getAnimation = (_element$getAnimation2 = element.getAnimations) === null || _element$getAnimation2 === void 0 ? void 0 : _element$getAnimation2.call(element)) !== null && _element$getAnimation !== void 0 ? _element$getAnimation : [];
38
+ Promise.allSettled(animations.map(function (animation) {
39
+ return animation.finished;
40
+ })).then(settle);
64
41
  }
65
42
  });
66
- var safetyNetId = setTimeout(settle, safetyNetMs);
67
- return teardown;
68
43
  }
69
44
 
70
45
  /**
@@ -128,13 +103,12 @@ function runOnTransitionEndOrTimeout(_ref) {
128
103
  * Every effect dispatches on the single `phase` value instead of a
129
104
  * cross-product of booleans.
130
105
  */
131
- function useAnimatedVisibility(_ref2) {
132
- var isOpen = _ref2.isOpen,
133
- animationKind = _ref2.animationKind,
134
- shouldAnimate = _ref2.shouldAnimate,
135
- elementRef = _ref2.elementRef,
136
- onEnterFinish = _ref2.onEnterFinish,
137
- onExitFinish = _ref2.onExitFinish;
106
+ function useAnimatedVisibility(_ref3) {
107
+ var isOpen = _ref3.isOpen,
108
+ shouldAnimate = _ref3.shouldAnimate,
109
+ elementRef = _ref3.elementRef,
110
+ onEnterFinish = _ref3.onEnterFinish,
111
+ onExitFinish = _ref3.onExitFinish;
138
112
  var willAnimate = shouldAnimate && !(0, _reducedMotion.prefersReducedMotion)();
139
113
  var isNativeClosedRef = (0, _react.useRef)(false);
140
114
 
@@ -175,16 +149,14 @@ function useAnimatedVisibility(_ref2) {
175
149
  setPhase(willAnimate ? 'entering' : 'open');
176
150
  }
177
151
 
178
- // Keep stable refs to the latest callbacks so effects and timeout closures
152
+ // Keep stable refs to the latest callbacks so effects and promise callbacks
179
153
  // always call the current callback without needing them in dependency arrays.
180
154
  var onEnterFinishRef = (0, _react.useRef)(onEnterFinish);
181
155
  onEnterFinishRef.current = onEnterFinish;
182
156
  var onExitFinishRef = (0, _react.useRef)(onExitFinish);
183
157
  onExitFinishRef.current = onExitFinish;
184
158
 
185
- // Animated entry: wait for `transitionend` (or a safety-net timeout) and
186
- // move to `open`. The fallback guarantees settle on swallowed events
187
- // (zero-duration, browser quirk, mid-transition unmount).
159
+ // Animated entry: wait for all active animations and move to `open`.
188
160
  (0, _react.useEffect)(function () {
189
161
  if (phase !== 'entering') {
190
162
  return;
@@ -193,9 +165,8 @@ function useAnimatedVisibility(_ref2) {
193
165
  if (!element) {
194
166
  return;
195
167
  }
196
- return runOnTransitionEndOrTimeout({
168
+ return runOnAnimationsSettled({
197
169
  element: element,
198
- safetyNetMs: safetyNetMsMap[animationKind].enter + 50,
199
170
  onSettled: function onSettled() {
200
171
  var _onEnterFinishRef$cur;
201
172
  // Fire the consumer callback before transitioning phase so any
@@ -208,7 +179,7 @@ function useAnimatedVisibility(_ref2) {
208
179
  });
209
180
  }
210
181
  });
211
- }, [phase, animationKind, elementRef]);
182
+ }, [phase, elementRef]);
212
183
 
213
184
  // Non-animated entry callback. The lifecycle goes `closed → open`
214
185
  // directly with no `entering` phase; fire `onEnterFinish` on that jump.
@@ -226,8 +197,8 @@ function useAnimatedVisibility(_ref2) {
226
197
  }
227
198
  }, [phase]);
228
199
 
229
- // Animated exit: wait for `transitionend` (or a safety-net timeout),
230
- // fire `onExitFinish`, then move to `closed` to unmount the host.
200
+ // Animated exit: wait for all active animations, fire `onExitFinish`,
201
+ // then move to `closed` to unmount the host.
231
202
  (0, _react.useEffect)(function () {
232
203
  if (phase !== 'exiting' || !willAnimate) {
233
204
  return;
@@ -236,9 +207,8 @@ function useAnimatedVisibility(_ref2) {
236
207
  if (!element) {
237
208
  return;
238
209
  }
239
- return runOnTransitionEndOrTimeout({
210
+ return runOnAnimationsSettled({
240
211
  element: element,
241
- safetyNetMs: safetyNetMsMap[animationKind].exit + 50,
242
212
  onSettled: function onSettled() {
243
213
  var _onExitFinishRef$curr;
244
214
  // Fire the consumer callback before transitioning phase to
@@ -253,7 +223,7 @@ function useAnimatedVisibility(_ref2) {
253
223
  });
254
224
  }
255
225
  });
256
- }, [phase, willAnimate, animationKind, elementRef]);
226
+ }, [phase, willAnimate, elementRef]);
257
227
 
258
228
  // Native dismissal is authoritative. Its toggle can arrive while controlled
259
229
  // state still says open, so remember that native state and do not
@@ -283,7 +253,7 @@ function useAnimatedVisibility(_ref2) {
283
253
  });
284
254
  }, [willAnimate]);
285
255
 
286
- // Non-animated exit callback. No `transitionend` to wait for, so fire
256
+ // Non-animated exit callback. There are no animations to wait for, so fire
287
257
  // `onExitFinish` on the transition into `exiting` (the unmount itself
288
258
  // is gated by the closing toggle handshake above).
289
259
  var prevPhaseForExitRef = (0, _react.useRef)('closed');
@@ -293,7 +263,7 @@ function useAnimatedVisibility(_ref2) {
293
263
 
294
264
  // Only fire on the non-animated transition into `exiting`. The
295
265
  // animated path's `exiting → closed` settle already fires
296
- // `onExitFinish` from `runOnTransitionEndOrTimeout`.
266
+ // `onExitFinish` from `runOnAnimationsSettled`.
297
267
  if (prevPhase !== 'exiting' && phase === 'exiting' && !willAnimate) {
298
268
  var _onExitFinishRef$curr2;
299
269
  (_onExitFinishRef$curr2 = onExitFinishRef.current) === null || _onExitFinishRef$curr2 === void 0 || _onExitFinishRef$curr2.call(onExitFinishRef);
@@ -139,7 +139,6 @@ var Popover = exports.Popover = /*#__PURE__*/(0, _react.forwardRef)(function Pop
139
139
  var popoverId = idProp !== null && idProp !== void 0 ? idProp : "popover-".concat(autoId.replace(/:/g, ''));
140
140
  var _useAnimatedVisibilit = (0, _useAnimatedVisibility.useAnimatedVisibility)({
141
141
  isOpen: isOpen,
142
- animationKind: 'popover',
143
142
  shouldAnimate: shouldAnimate,
144
143
  elementRef: ownRef,
145
144
  onEnterFinish: onEnterFinish,
@@ -80,7 +80,6 @@ export const Dialog = /*#__PURE__*/forwardRef(function Dialog({
80
80
  onToggle
81
81
  } = useAnimatedVisibility({
82
82
  isOpen,
83
- animationKind: 'dialog',
84
83
  shouldAnimate,
85
84
  elementRef: ownRef,
86
85
  onEnterFinish,
@@ -1,63 +1,37 @@
1
1
  import { useCallback, useEffect, useLayoutEffect, useRef, useState } from 'react';
2
- import { bind } from 'bind-event-listener';
3
2
  import { prefersReducedMotion } from './reduced-motion';
4
- /**
5
- * Using the recommended motion timings for safety net.
6
- *
7
- * Not setting these timings on the preset config because they're defined in the CSS,
8
- * so configuring it in the preset doesn't achieve anything except introduce a potential
9
- * source of drift.
10
- *
11
- * TODO: figure out a way to not need to redeclare these in the future,
12
- * and keep CSS and JS timings in sync always.
13
- */
14
- const safetyNetMsMap = {
15
- dialog: {
16
- enter: 250,
17
- // Corresponds to `motion.duration.long`
18
- exit: 200 // Corresponds to `motion.duration.medium`
19
- },
20
- popover: {
21
- enter: 150,
22
- // Corresponds to `motion.duration.short`
23
- exit: 100 // Corresponds to `motion.duration.xshort`
24
- }
25
- };
3
+ function runCancellableSettlement({
4
+ schedule,
5
+ onSettled
6
+ }) {
7
+ let isCancelled = false;
8
+ schedule(() => {
9
+ if (isCancelled) {
10
+ return;
11
+ }
12
+ onSettled();
13
+ });
14
+ return () => {
15
+ isCancelled = true;
16
+ };
17
+ }
26
18
 
27
19
  /**
28
- * Fires `onSettled` once when `transitionend` fires on `element`, or after
29
- * `safetyNetMs` if the event is swallowed (mid-transition unmount, CSS
30
- * mismatch, browser quirks). Returns a cleanup that cancels both paths.
20
+ * Fires `onSettled` once all animations currently applied to `element`
21
+ * finish or are cancelled. Returns a cleanup that prevents stale settlement.
31
22
  */
32
- function runOnTransitionEndOrTimeout({
23
+ function runOnAnimationsSettled({
33
24
  element,
34
- safetyNetMs,
35
25
  onSettled
36
26
  }) {
37
- const state = {
38
- isSettled: false
39
- };
40
- function teardown() {
41
- state.isSettled = true;
42
- clearTimeout(safetyNetId);
43
- unbind();
44
- }
45
- function settle() {
46
- if (state.isSettled) {
47
- return;
48
- }
49
- teardown();
50
- onSettled();
51
- }
52
- const unbind = bind(element, {
53
- type: 'transitionend',
54
- listener: settle,
55
- options: {
56
- once: true
27
+ return runCancellableSettlement({
28
+ onSettled,
29
+ schedule: settle => {
30
+ var _element$getAnimation, _element$getAnimation2;
31
+ const animations = (_element$getAnimation = (_element$getAnimation2 = element.getAnimations) === null || _element$getAnimation2 === void 0 ? void 0 : _element$getAnimation2.call(element)) !== null && _element$getAnimation !== void 0 ? _element$getAnimation : [];
32
+ Promise.allSettled(animations.map(animation => animation.finished)).then(settle);
57
33
  }
58
34
  });
59
- const safetyNetId = setTimeout(settle, safetyNetMs);
60
- return teardown;
61
35
  }
62
36
 
63
37
  /**
@@ -123,7 +97,6 @@ function runOnTransitionEndOrTimeout({
123
97
  */
124
98
  export function useAnimatedVisibility({
125
99
  isOpen,
126
- animationKind,
127
100
  shouldAnimate,
128
101
  elementRef,
129
102
  onEnterFinish,
@@ -166,16 +139,14 @@ export function useAnimatedVisibility({
166
139
  setPhase(willAnimate ? 'entering' : 'open');
167
140
  }
168
141
 
169
- // Keep stable refs to the latest callbacks so effects and timeout closures
142
+ // Keep stable refs to the latest callbacks so effects and promise callbacks
170
143
  // always call the current callback without needing them in dependency arrays.
171
144
  const onEnterFinishRef = useRef(onEnterFinish);
172
145
  onEnterFinishRef.current = onEnterFinish;
173
146
  const onExitFinishRef = useRef(onExitFinish);
174
147
  onExitFinishRef.current = onExitFinish;
175
148
 
176
- // Animated entry: wait for `transitionend` (or a safety-net timeout) and
177
- // move to `open`. The fallback guarantees settle on swallowed events
178
- // (zero-duration, browser quirk, mid-transition unmount).
149
+ // Animated entry: wait for all active animations and move to `open`.
179
150
  useEffect(() => {
180
151
  if (phase !== 'entering') {
181
152
  return;
@@ -184,9 +155,8 @@ export function useAnimatedVisibility({
184
155
  if (!element) {
185
156
  return;
186
157
  }
187
- return runOnTransitionEndOrTimeout({
158
+ return runOnAnimationsSettled({
188
159
  element,
189
- safetyNetMs: safetyNetMsMap[animationKind].enter + 50,
190
160
  onSettled: () => {
191
161
  var _onEnterFinishRef$cur;
192
162
  // Fire the consumer callback before transitioning phase so any
@@ -197,7 +167,7 @@ export function useAnimatedVisibility({
197
167
  setPhase(current => current === 'entering' ? 'open' : current);
198
168
  }
199
169
  });
200
- }, [phase, animationKind, elementRef]);
170
+ }, [phase, elementRef]);
201
171
 
202
172
  // Non-animated entry callback. The lifecycle goes `closed → open`
203
173
  // directly with no `entering` phase; fire `onEnterFinish` on that jump.
@@ -215,8 +185,8 @@ export function useAnimatedVisibility({
215
185
  }
216
186
  }, [phase]);
217
187
 
218
- // Animated exit: wait for `transitionend` (or a safety-net timeout),
219
- // fire `onExitFinish`, then move to `closed` to unmount the host.
188
+ // Animated exit: wait for all active animations, fire `onExitFinish`,
189
+ // then move to `closed` to unmount the host.
220
190
  useEffect(() => {
221
191
  if (phase !== 'exiting' || !willAnimate) {
222
192
  return;
@@ -225,9 +195,8 @@ export function useAnimatedVisibility({
225
195
  if (!element) {
226
196
  return;
227
197
  }
228
- return runOnTransitionEndOrTimeout({
198
+ return runOnAnimationsSettled({
229
199
  element,
230
- safetyNetMs: safetyNetMsMap[animationKind].exit + 50,
231
200
  onSettled: () => {
232
201
  var _onExitFinishRef$curr;
233
202
  // Fire the consumer callback before transitioning phase to
@@ -240,7 +209,7 @@ export function useAnimatedVisibility({
240
209
  setPhase(current => current === 'exiting' ? 'closed' : current);
241
210
  }
242
211
  });
243
- }, [phase, willAnimate, animationKind, elementRef]);
212
+ }, [phase, willAnimate, elementRef]);
244
213
 
245
214
  // Native dismissal is authoritative. Its toggle can arrive while controlled
246
215
  // state still says open, so remember that native state and do not
@@ -270,7 +239,7 @@ export function useAnimatedVisibility({
270
239
  });
271
240
  }, [willAnimate]);
272
241
 
273
- // Non-animated exit callback. No `transitionend` to wait for, so fire
242
+ // Non-animated exit callback. There are no animations to wait for, so fire
274
243
  // `onExitFinish` on the transition into `exiting` (the unmount itself
275
244
  // is gated by the closing toggle handshake above).
276
245
  const prevPhaseForExitRef = useRef('closed');
@@ -280,7 +249,7 @@ export function useAnimatedVisibility({
280
249
 
281
250
  // Only fire on the non-animated transition into `exiting`. The
282
251
  // animated path's `exiting → closed` settle already fires
283
- // `onExitFinish` from `runOnTransitionEndOrTimeout`.
252
+ // `onExitFinish` from `runOnAnimationsSettled`.
284
253
  if (prevPhase !== 'exiting' && phase === 'exiting' && !willAnimate) {
285
254
  var _onExitFinishRef$curr2;
286
255
  (_onExitFinishRef$curr2 = onExitFinishRef.current) === null || _onExitFinishRef$curr2 === void 0 ? void 0 : _onExitFinishRef$curr2.call(onExitFinishRef);
@@ -133,7 +133,6 @@ export const Popover = /*#__PURE__*/forwardRef(function Popover({
133
133
  onToggle
134
134
  } = useAnimatedVisibility({
135
135
  isOpen,
136
- animationKind: 'popover',
137
136
  shouldAnimate,
138
137
  elementRef: ownRef,
139
138
  onEnterFinish,
@@ -78,7 +78,6 @@ export var Dialog = /*#__PURE__*/forwardRef(function Dialog(_ref, ref) {
78
78
  var closeReasonRef = useRef(null);
79
79
  var _useAnimatedVisibilit = useAnimatedVisibility({
80
80
  isOpen: isOpen,
81
- animationKind: 'dialog',
82
81
  shouldAnimate: shouldAnimate,
83
82
  elementRef: ownRef,
84
83
  onEnterFinish: onEnterFinish,
@@ -1,63 +1,38 @@
1
1
  import _slicedToArray from "@babel/runtime/helpers/slicedToArray";
2
2
  import { useCallback, useEffect, useLayoutEffect, useRef, useState } from 'react';
3
- import { bind } from 'bind-event-listener';
4
3
  import { prefersReducedMotion } from './reduced-motion';
5
- /**
6
- * Using the recommended motion timings for safety net.
7
- *
8
- * Not setting these timings on the preset config because they're defined in the CSS,
9
- * so configuring it in the preset doesn't achieve anything except introduce a potential
10
- * source of drift.
11
- *
12
- * TODO: figure out a way to not need to redeclare these in the future,
13
- * and keep CSS and JS timings in sync always.
14
- */
15
- var safetyNetMsMap = {
16
- dialog: {
17
- enter: 250,
18
- // Corresponds to `motion.duration.long`
19
- exit: 200 // Corresponds to `motion.duration.medium`
20
- },
21
- popover: {
22
- enter: 150,
23
- // Corresponds to `motion.duration.short`
24
- exit: 100 // Corresponds to `motion.duration.xshort`
25
- }
26
- };
27
-
28
- /**
29
- * Fires `onSettled` once when `transitionend` fires on `element`, or after
30
- * `safetyNetMs` if the event is swallowed (mid-transition unmount, CSS
31
- * mismatch, browser quirks). Returns a cleanup that cancels both paths.
32
- */
33
- function runOnTransitionEndOrTimeout(_ref) {
34
- var element = _ref.element,
35
- safetyNetMs = _ref.safetyNetMs,
4
+ function runCancellableSettlement(_ref) {
5
+ var schedule = _ref.schedule,
36
6
  onSettled = _ref.onSettled;
37
- var state = {
38
- isSettled: false
39
- };
40
- function teardown() {
41
- state.isSettled = true;
42
- clearTimeout(safetyNetId);
43
- unbind();
44
- }
45
- function settle() {
46
- if (state.isSettled) {
7
+ var isCancelled = false;
8
+ schedule(function () {
9
+ if (isCancelled) {
47
10
  return;
48
11
  }
49
- teardown();
50
12
  onSettled();
51
- }
52
- var unbind = bind(element, {
53
- type: 'transitionend',
54
- listener: settle,
55
- options: {
56
- once: true
13
+ });
14
+ return function () {
15
+ isCancelled = true;
16
+ };
17
+ }
18
+
19
+ /**
20
+ * Fires `onSettled` once all animations currently applied to `element`
21
+ * finish or are cancelled. Returns a cleanup that prevents stale settlement.
22
+ */
23
+ function runOnAnimationsSettled(_ref2) {
24
+ var element = _ref2.element,
25
+ onSettled = _ref2.onSettled;
26
+ return runCancellableSettlement({
27
+ onSettled: onSettled,
28
+ schedule: function schedule(settle) {
29
+ var _element$getAnimation, _element$getAnimation2;
30
+ var animations = (_element$getAnimation = (_element$getAnimation2 = element.getAnimations) === null || _element$getAnimation2 === void 0 ? void 0 : _element$getAnimation2.call(element)) !== null && _element$getAnimation !== void 0 ? _element$getAnimation : [];
31
+ Promise.allSettled(animations.map(function (animation) {
32
+ return animation.finished;
33
+ })).then(settle);
57
34
  }
58
35
  });
59
- var safetyNetId = setTimeout(settle, safetyNetMs);
60
- return teardown;
61
36
  }
62
37
 
63
38
  /**
@@ -121,13 +96,12 @@ function runOnTransitionEndOrTimeout(_ref) {
121
96
  * Every effect dispatches on the single `phase` value instead of a
122
97
  * cross-product of booleans.
123
98
  */
124
- export function useAnimatedVisibility(_ref2) {
125
- var isOpen = _ref2.isOpen,
126
- animationKind = _ref2.animationKind,
127
- shouldAnimate = _ref2.shouldAnimate,
128
- elementRef = _ref2.elementRef,
129
- onEnterFinish = _ref2.onEnterFinish,
130
- onExitFinish = _ref2.onExitFinish;
99
+ export function useAnimatedVisibility(_ref3) {
100
+ var isOpen = _ref3.isOpen,
101
+ shouldAnimate = _ref3.shouldAnimate,
102
+ elementRef = _ref3.elementRef,
103
+ onEnterFinish = _ref3.onEnterFinish,
104
+ onExitFinish = _ref3.onExitFinish;
131
105
  var willAnimate = shouldAnimate && !prefersReducedMotion();
132
106
  var isNativeClosedRef = useRef(false);
133
107
 
@@ -168,16 +142,14 @@ export function useAnimatedVisibility(_ref2) {
168
142
  setPhase(willAnimate ? 'entering' : 'open');
169
143
  }
170
144
 
171
- // Keep stable refs to the latest callbacks so effects and timeout closures
145
+ // Keep stable refs to the latest callbacks so effects and promise callbacks
172
146
  // always call the current callback without needing them in dependency arrays.
173
147
  var onEnterFinishRef = useRef(onEnterFinish);
174
148
  onEnterFinishRef.current = onEnterFinish;
175
149
  var onExitFinishRef = useRef(onExitFinish);
176
150
  onExitFinishRef.current = onExitFinish;
177
151
 
178
- // Animated entry: wait for `transitionend` (or a safety-net timeout) and
179
- // move to `open`. The fallback guarantees settle on swallowed events
180
- // (zero-duration, browser quirk, mid-transition unmount).
152
+ // Animated entry: wait for all active animations and move to `open`.
181
153
  useEffect(function () {
182
154
  if (phase !== 'entering') {
183
155
  return;
@@ -186,9 +158,8 @@ export function useAnimatedVisibility(_ref2) {
186
158
  if (!element) {
187
159
  return;
188
160
  }
189
- return runOnTransitionEndOrTimeout({
161
+ return runOnAnimationsSettled({
190
162
  element: element,
191
- safetyNetMs: safetyNetMsMap[animationKind].enter + 50,
192
163
  onSettled: function onSettled() {
193
164
  var _onEnterFinishRef$cur;
194
165
  // Fire the consumer callback before transitioning phase so any
@@ -201,7 +172,7 @@ export function useAnimatedVisibility(_ref2) {
201
172
  });
202
173
  }
203
174
  });
204
- }, [phase, animationKind, elementRef]);
175
+ }, [phase, elementRef]);
205
176
 
206
177
  // Non-animated entry callback. The lifecycle goes `closed → open`
207
178
  // directly with no `entering` phase; fire `onEnterFinish` on that jump.
@@ -219,8 +190,8 @@ export function useAnimatedVisibility(_ref2) {
219
190
  }
220
191
  }, [phase]);
221
192
 
222
- // Animated exit: wait for `transitionend` (or a safety-net timeout),
223
- // fire `onExitFinish`, then move to `closed` to unmount the host.
193
+ // Animated exit: wait for all active animations, fire `onExitFinish`,
194
+ // then move to `closed` to unmount the host.
224
195
  useEffect(function () {
225
196
  if (phase !== 'exiting' || !willAnimate) {
226
197
  return;
@@ -229,9 +200,8 @@ export function useAnimatedVisibility(_ref2) {
229
200
  if (!element) {
230
201
  return;
231
202
  }
232
- return runOnTransitionEndOrTimeout({
203
+ return runOnAnimationsSettled({
233
204
  element: element,
234
- safetyNetMs: safetyNetMsMap[animationKind].exit + 50,
235
205
  onSettled: function onSettled() {
236
206
  var _onExitFinishRef$curr;
237
207
  // Fire the consumer callback before transitioning phase to
@@ -246,7 +216,7 @@ export function useAnimatedVisibility(_ref2) {
246
216
  });
247
217
  }
248
218
  });
249
- }, [phase, willAnimate, animationKind, elementRef]);
219
+ }, [phase, willAnimate, elementRef]);
250
220
 
251
221
  // Native dismissal is authoritative. Its toggle can arrive while controlled
252
222
  // state still says open, so remember that native state and do not
@@ -276,7 +246,7 @@ export function useAnimatedVisibility(_ref2) {
276
246
  });
277
247
  }, [willAnimate]);
278
248
 
279
- // Non-animated exit callback. No `transitionend` to wait for, so fire
249
+ // Non-animated exit callback. There are no animations to wait for, so fire
280
250
  // `onExitFinish` on the transition into `exiting` (the unmount itself
281
251
  // is gated by the closing toggle handshake above).
282
252
  var prevPhaseForExitRef = useRef('closed');
@@ -286,7 +256,7 @@ export function useAnimatedVisibility(_ref2) {
286
256
 
287
257
  // Only fire on the non-animated transition into `exiting`. The
288
258
  // animated path's `exiting → closed` settle already fires
289
- // `onExitFinish` from `runOnTransitionEndOrTimeout`.
259
+ // `onExitFinish` from `runOnAnimationsSettled`.
290
260
  if (prevPhase !== 'exiting' && phase === 'exiting' && !willAnimate) {
291
261
  var _onExitFinishRef$curr2;
292
262
  (_onExitFinishRef$curr2 = onExitFinishRef.current) === null || _onExitFinishRef$curr2 === void 0 || _onExitFinishRef$curr2.call(onExitFinishRef);
@@ -130,7 +130,6 @@ export var Popover = /*#__PURE__*/forwardRef(function Popover(_ref, ref) {
130
130
  var popoverId = idProp !== null && idProp !== void 0 ? idProp : "popover-".concat(autoId.replace(/:/g, ''));
131
131
  var _useAnimatedVisibilit = useAnimatedVisibility({
132
132
  isOpen: isOpen,
133
- animationKind: 'popover',
134
133
  shouldAnimate: shouldAnimate,
135
134
  elementRef: ownRef,
136
135
  onEnterFinish: onEnterFinish,
@@ -1,14 +1,9 @@
1
1
  import { type RefObject } from 'react';
2
- type TAnimationKind = 'dialog' | 'popover';
3
2
  type TUseAnimatedVisibilityArgs = {
4
3
  /**
5
4
  * Whether the element is logically open.
6
5
  */
7
6
  isOpen: boolean;
8
- /**
9
- * Animation kind used to select default safety-net timings.
10
- */
11
- animationKind: TAnimationKind;
12
7
  /**
13
8
  * Animation config for entry/exit transitions.
14
9
  * Pass `false` or `undefined` to disable animation.
@@ -16,7 +11,7 @@ type TUseAnimatedVisibilityArgs = {
16
11
  shouldAnimate: boolean;
17
12
  /**
18
13
  * Ref to the DOM element that plays the entry/exit transitions.
19
- * Used to listen for `transitionend`.
14
+ * Used to inspect active animations.
20
15
  */
21
16
  elementRef: RefObject<HTMLElement | null>;
22
17
  /**
@@ -110,5 +105,5 @@ export type TPhase = 'closed' | 'entering' | 'open' | 'exiting';
110
105
  * Every effect dispatches on the single `phase` value instead of a
111
106
  * cross-product of booleans.
112
107
  */
113
- export declare function useAnimatedVisibility({ isOpen, animationKind, shouldAnimate, elementRef, onEnterFinish, onExitFinish, }: TUseAnimatedVisibilityArgs): TUseAnimatedVisibilityResult;
108
+ export declare function useAnimatedVisibility({ isOpen, shouldAnimate, elementRef, onEnterFinish, onExitFinish, }: TUseAnimatedVisibilityArgs): TUseAnimatedVisibilityResult;
114
109
  export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atlaskit/top-layer",
3
- "version": "4.2.0",
3
+ "version": "4.2.1",
4
4
  "description": "Low-level layering primitives based on popover and dialog",
5
5
  "homepage": "https://atlassian.design/components/top-layer",
6
6
  "repository": "https://bitbucket.org/atlassian/atlassian-frontend-mirror",
@@ -55,7 +55,7 @@
55
55
  "@af/visual-regression": "workspace:^",
56
56
  "@atlaskit/button": "^25.0.0",
57
57
  "@atlaskit/heading": "^7.0.0",
58
- "@atlaskit/lozenge": "^15.0.0",
58
+ "@atlaskit/lozenge": "^15.1.0",
59
59
  "@atlaskit/primitives": "^22.2.0",
60
60
  "@atlaskit/ssr": "workspace:^",
61
61
  "@atlaskit/textfield": "^10.0.0",