@seed-design/stackflow 1.1.19 → 1.1.21

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.
Files changed (98) hide show
  1. package/lib/_virtual/_rolldown/runtime.cjs +34 -0
  2. package/lib/_virtual/_rolldown/runtime.js +14 -0
  3. package/lib/components/AppBar/AppBar.cjs +88 -87
  4. package/lib/components/AppBar/AppBar.d.ts +3 -0
  5. package/lib/components/AppBar/AppBar.js +86 -84
  6. package/lib/components/AppBar/AppBar.namespace.cjs +20 -15
  7. package/lib/components/AppBar/AppBar.namespace.d.ts +2 -2
  8. package/lib/components/AppBar/AppBar.namespace.js +15 -1
  9. package/lib/components/AppBar/index.cjs +17 -17
  10. package/lib/components/AppBar/index.d.ts +2 -2
  11. package/lib/components/AppBar/index.js +3 -3
  12. package/lib/components/AppScreen/AppScreen.cjs +46 -61
  13. package/lib/components/AppScreen/AppScreen.js +45 -57
  14. package/lib/components/AppScreen/AppScreen.namespace.cjs +17 -13
  15. package/lib/components/AppScreen/AppScreen.namespace.js +12 -1
  16. package/lib/components/AppScreen/index.cjs +14 -15
  17. package/lib/components/AppScreen/index.js +3 -3
  18. package/lib/components/index.cjs +30 -25
  19. package/lib/components/index.js +5 -6
  20. package/lib/hooks/index.cjs +3 -9
  21. package/lib/hooks/index.js +2 -1
  22. package/lib/hooks/useActivityZIndexBase.cjs +12 -11
  23. package/lib/hooks/useActivityZIndexBase.js +11 -7
  24. package/lib/index.cjs +41 -31
  25. package/lib/index.js +8 -10
  26. package/lib/plugin.cjs +16 -16
  27. package/lib/plugin.js +14 -12
  28. package/lib/primitive/AppBar/AppBar.cjs +41 -33
  29. package/lib/primitive/AppBar/AppBar.js +40 -29
  30. package/lib/primitive/AppBar/AppBar.namespace.cjs +16 -12
  31. package/lib/primitive/AppBar/AppBar.namespace.js +11 -1
  32. package/lib/primitive/AppBar/anatomy.cjs +18 -0
  33. package/lib/primitive/AppBar/anatomy.d.ts +15 -0
  34. package/lib/primitive/AppBar/anatomy.js +18 -0
  35. package/lib/primitive/AppBar/index.cjs +13 -14
  36. package/lib/primitive/AppBar/index.js +3 -3
  37. package/lib/primitive/AppBar/useAppBar.cjs +29 -36
  38. package/lib/primitive/AppBar/useAppBar.js +28 -32
  39. package/lib/primitive/AppBar/useAppBarContext.cjs +11 -18
  40. package/lib/primitive/AppBar/useAppBarContext.js +10 -14
  41. package/lib/primitive/AppScreen/AppScreen.cjs +49 -52
  42. package/lib/primitive/AppScreen/AppScreen.js +48 -48
  43. package/lib/primitive/AppScreen/AppScreen.namespace.cjs +16 -12
  44. package/lib/primitive/AppScreen/AppScreen.namespace.js +11 -1
  45. package/lib/primitive/AppScreen/anatomy.cjs +17 -0
  46. package/lib/primitive/AppScreen/anatomy.d.ts +14 -0
  47. package/lib/primitive/AppScreen/anatomy.js +17 -0
  48. package/lib/primitive/AppScreen/index.cjs +15 -16
  49. package/lib/primitive/AppScreen/index.js +4 -4
  50. package/lib/primitive/AppScreen/useAppScreen.cjs +60 -63
  51. package/lib/primitive/AppScreen/useAppScreen.js +59 -59
  52. package/lib/primitive/AppScreen/useAppScreenContext.cjs +11 -18
  53. package/lib/primitive/AppScreen/useAppScreenContext.js +10 -14
  54. package/lib/primitive/GlobalInteraction/GlobalInteraction.cjs +21 -26
  55. package/lib/primitive/GlobalInteraction/GlobalInteraction.js +20 -22
  56. package/lib/primitive/GlobalInteraction/animation.cjs +303 -0
  57. package/lib/primitive/GlobalInteraction/animation.d.ts +16 -0
  58. package/lib/primitive/GlobalInteraction/animation.js +299 -0
  59. package/lib/primitive/GlobalInteraction/constants.cjs +9 -0
  60. package/lib/primitive/GlobalInteraction/constants.d.ts +19 -0
  61. package/lib/primitive/GlobalInteraction/constants.js +8 -0
  62. package/lib/primitive/GlobalInteraction/dom.cjs +147 -0
  63. package/lib/primitive/GlobalInteraction/dom.d.ts +44 -0
  64. package/lib/primitive/GlobalInteraction/dom.js +140 -0
  65. package/lib/primitive/GlobalInteraction/index.cjs +5 -11
  66. package/lib/primitive/GlobalInteraction/index.js +3 -2
  67. package/lib/primitive/GlobalInteraction/useGlobalInteraction.cjs +220 -133
  68. package/lib/primitive/GlobalInteraction/useGlobalInteraction.d.ts +10 -26
  69. package/lib/primitive/GlobalInteraction/useGlobalInteraction.js +219 -129
  70. package/lib/primitive/GlobalInteraction/useGlobalInteractionContext.cjs +11 -18
  71. package/lib/primitive/GlobalInteraction/useGlobalInteractionContext.js +10 -14
  72. package/lib/primitive/GlobalInteraction/useSwipeBack.cjs +71 -55
  73. package/lib/primitive/GlobalInteraction/useSwipeBack.d.ts +19 -2
  74. package/lib/primitive/GlobalInteraction/useSwipeBack.js +70 -51
  75. package/lib/primitive/index.cjs +55 -27
  76. package/lib/primitive/index.js +26 -9
  77. package/lib/primitive/private/useElementOffset.cjs +29 -51
  78. package/lib/primitive/private/useElementOffset.js +27 -28
  79. package/lib/primitive/private/useTopActivity.cjs +22 -31
  80. package/lib/primitive/private/useTopActivity.js +21 -27
  81. package/lib/utils/createStyleContext.cjs +91 -74
  82. package/lib/utils/createStyleContext.js +89 -70
  83. package/lib/utils/styled.cjs +13 -20
  84. package/lib/utils/styled.js +12 -16
  85. package/package.json +4 -4
  86. package/src/components/AppBar/AppBar.namespace.ts +2 -0
  87. package/src/components/AppBar/AppBar.tsx +29 -3
  88. package/src/components/AppBar/index.ts +2 -0
  89. package/src/primitive/AppBar/AppBar.tsx +4 -1
  90. package/src/primitive/AppBar/anatomy.ts +16 -0
  91. package/src/primitive/AppBar/useAppBar.tsx +2 -1
  92. package/src/primitive/AppScreen/anatomy.ts +15 -0
  93. package/src/primitive/AppScreen/useAppScreen.tsx +5 -4
  94. package/src/primitive/GlobalInteraction/animation.ts +447 -0
  95. package/src/primitive/GlobalInteraction/constants.ts +24 -0
  96. package/src/primitive/GlobalInteraction/dom.ts +211 -0
  97. package/src/primitive/GlobalInteraction/useGlobalInteraction.ts +302 -129
  98. package/src/primitive/GlobalInteraction/useSwipeBack.ts +64 -26
@@ -1,24 +1,22 @@
1
- 'use client';
2
- import { jsx } from 'react/jsx-runtime';
3
- import { composeRefs } from '@radix-ui/react-compose-refs';
4
- import { mergeProps } from '@seed-design/dom-utils';
5
- import { Primitive } from '@seed-design/react-primitive';
6
- import { forwardRef } from 'react';
7
- import { useGlobalInteraction } from './useGlobalInteraction.js';
8
- import { GlobalInteractionProvider } from './useGlobalInteractionContext.js';
9
-
10
- const GlobalInteraction = forwardRef(
11
- (props, ref) => {
12
- const api = useGlobalInteraction();
13
- return /* @__PURE__ */ jsx(GlobalInteractionProvider, { value: api, children: /* @__PURE__ */ jsx(
14
- Primitive.div,
15
- {
16
- ref: composeRefs(api.stackRef, ref),
17
- ...mergeProps(api.stackProps, props)
18
- }
19
- ) });
20
- }
21
- );
1
+ "use client";
2
+ import { GlobalInteractionProvider } from "./useGlobalInteractionContext.js";
3
+ import { useGlobalInteraction } from "./useGlobalInteraction.js";
4
+ import { mergeProps } from "@seed-design/dom-utils";
5
+ import { Primitive } from "@seed-design/react-primitive";
6
+ import { forwardRef } from "react";
7
+ import { composeRefs } from "@radix-ui/react-compose-refs";
8
+ import { jsx } from "react/jsx-runtime";
9
+ //#region src/primitive/GlobalInteraction/GlobalInteraction.tsx
10
+ var GlobalInteraction = forwardRef((props, ref) => {
11
+ const api = useGlobalInteraction();
12
+ return /* @__PURE__ */ jsx(GlobalInteractionProvider, {
13
+ value: api,
14
+ children: /* @__PURE__ */ jsx(Primitive.div, {
15
+ ref: composeRefs(api.stackRef, ref),
16
+ ...mergeProps(api.stackProps, props)
17
+ })
18
+ });
19
+ });
22
20
  GlobalInteraction.displayName = "GlobalInteraction";
23
-
21
+ //#endregion
24
22
  export { GlobalInteraction };
@@ -0,0 +1,303 @@
1
+ "use client";
2
+ const require_constants = require("./constants.cjs");
3
+ const require_dom = require("./dom.cjs");
4
+ //#region src/primitive/GlobalInteraction/animation.ts
5
+ var IOS_EASING = "cubic-bezier(0.2, 0.1, 0.21, 0.99)";
6
+ var IOS_DURATION = 350;
7
+ var ANDROID_ENTER_EASING = "cubic-bezier(0.23, 0.1, 0.32, 1)";
8
+ var ANDROID_ENTER_DURATION = 300;
9
+ var ANDROID_EXIT_EASING = "linear";
10
+ var ANDROID_EXIT_DURATION = 150;
11
+ var FADE_IN_ENTER_EASING = "ease-out";
12
+ var FADE_IN_ENTER_DURATION = 300;
13
+ var FADE_IN_EXIT_EASING = "ease-in";
14
+ var FADE_IN_EXIT_DURATION = 150;
15
+ var FINISHED_TIMEOUT_MARGIN = 100;
16
+ function safeAnimate(el, keyframes, options) {
17
+ if (!el) return null;
18
+ return el.animate(keyframes, options);
19
+ }
20
+ /**
21
+ * Snap the app-bar background element transform during an interactive gesture
22
+ * (e.g. swipe-back). Reuses a single Animation by swapping its keyframes each
23
+ * call — recreating on every touchmove would cancel-then-recreate and cause a
24
+ * visible flicker.
25
+ */
26
+ function scrubAppBarBackground(el, transform, prev) {
27
+ if (prev && prev.effect instanceof KeyframeEffect) try {
28
+ prev.effect.setKeyframes([{ transform }]);
29
+ return prev;
30
+ } catch {
31
+ prev.cancel();
32
+ }
33
+ return safeAnimate(el, [{ transform }], {
34
+ duration: 1,
35
+ fill: "forwards"
36
+ });
37
+ }
38
+ function cancelAll(animations) {
39
+ for (const a of animations) try {
40
+ a?.cancel();
41
+ } catch {}
42
+ }
43
+ /**
44
+ * Resolve when the animation finishes or `timeoutMs` elapses, whichever
45
+ * comes first. Uses `Animation.finished` when available (Chrome 84+,
46
+ * Safari 13.1+, Firefox 110+) and falls back to `onfinish` / `oncancel`
47
+ * listeners for older browsers (e.g. Chrome 77). Both the timer and the
48
+ * listeners are always cleaned up on the losing side of the race, so a
49
+ * late-firing animation cannot invoke a stale callback or leak via
50
+ * closure-retained Animation references.
51
+ */
52
+ function waitOne(a, timeoutMs) {
53
+ const maybeFinished = a.finished;
54
+ if (maybeFinished && typeof maybeFinished.then === "function") return new Promise((resolve) => {
55
+ let settled = false;
56
+ const finish = () => {
57
+ if (settled) return;
58
+ settled = true;
59
+ clearTimeout(timer);
60
+ resolve();
61
+ };
62
+ const timer = setTimeout(finish, timeoutMs);
63
+ maybeFinished.then(finish, finish);
64
+ });
65
+ return new Promise((resolve) => {
66
+ let settled = false;
67
+ const finish = () => {
68
+ if (settled) return;
69
+ settled = true;
70
+ try {
71
+ a.onfinish = null;
72
+ a.oncancel = null;
73
+ } catch {}
74
+ clearTimeout(timer);
75
+ resolve();
76
+ };
77
+ a.onfinish = finish;
78
+ a.oncancel = finish;
79
+ const timer = setTimeout(finish, timeoutMs);
80
+ });
81
+ }
82
+ function waitAll(animations, durationMs) {
83
+ const valid = animations.filter((a) => a !== null);
84
+ if (valid.length === 0) return Promise.resolve();
85
+ const timeout = durationMs + FINISHED_TIMEOUT_MARGIN;
86
+ return Promise.all(valid.map((a) => waitOne(a, timeout))).then(() => {});
87
+ }
88
+ function collectAnimations(anims, durationMs) {
89
+ const animations = anims.filter((a) => a !== null);
90
+ return {
91
+ animations,
92
+ finished: waitAll(animations, durationMs)
93
+ };
94
+ }
95
+ var IOS_ONSCREEN = {
96
+ topLayer: "translate3d(0, 0, 0)",
97
+ behindLayer: `translate3d(-30%, 0, 0)`,
98
+ dim: "1",
99
+ topTitle: {
100
+ opacity: "1",
101
+ transform: "translate3d(0, 0, 0)"
102
+ },
103
+ behindTitle: {
104
+ opacity: "0",
105
+ transform: `translate3d(-25%, 0, 0)`
106
+ },
107
+ topIconOpacity: "1",
108
+ behindIconOpacity: "0",
109
+ appBarBackground: "translate3d(0, 0, 0)"
110
+ };
111
+ var IOS_OFFSCREEN = {
112
+ topLayer: "translate3d(100%, 0, 0)",
113
+ behindLayer: "translate3d(0, 0, 0)",
114
+ dim: "0",
115
+ topTitle: {
116
+ opacity: "0",
117
+ transform: `translate3d(25%, 0, 0)`
118
+ },
119
+ behindTitle: {
120
+ opacity: "1",
121
+ transform: "translate3d(0, 0, 0)"
122
+ },
123
+ topIconOpacity: "0",
124
+ behindIconOpacity: "1",
125
+ appBarBackground: "translate3d(100%, 0, 0)"
126
+ };
127
+ function iosAnimate(t, from, to) {
128
+ const opts = {
129
+ duration: IOS_DURATION,
130
+ easing: IOS_EASING,
131
+ fill: "forwards"
132
+ };
133
+ const anims = [];
134
+ anims.push(safeAnimate(t.topLayer, [{ transform: from.topLayer }, { transform: to.topLayer }], opts));
135
+ anims.push(safeAnimate(t.behindLayer, [{ transform: from.behindLayer }, { transform: to.behindLayer }], opts));
136
+ anims.push(safeAnimate(t.topDim, [{ opacity: from.dim }, { opacity: to.dim }], opts));
137
+ anims.push(safeAnimate(t.topTitle, [from.topTitle, to.topTitle], opts));
138
+ anims.push(safeAnimate(t.behindTitle, [from.behindTitle, to.behindTitle], opts));
139
+ for (const icon of t.topIcons) anims.push(safeAnimate(icon, [{
140
+ opacity: from.topIconOpacity,
141
+ transform: from.topTitle.transform
142
+ }, {
143
+ opacity: to.topIconOpacity,
144
+ transform: to.topTitle.transform
145
+ }], opts));
146
+ for (const icon of t.behindIcons) anims.push(safeAnimate(icon, [{ opacity: from.behindIconOpacity }, { opacity: to.behindIconOpacity }], opts));
147
+ anims.push(safeAnimate(t.topAppBarBackground, [{ transform: from.appBarBackground }, { transform: to.appBarBackground }], opts));
148
+ return collectAnimations(anims, IOS_DURATION);
149
+ }
150
+ /**
151
+ * Pin every iOS transition target to the given position with inline styles
152
+ * before the animation starts. Prevents a one-frame flash at the final
153
+ * position when the browser paints between React commit and animation
154
+ * commit, and guarantees a known start state when the previous transition's
155
+ * finish handler did not run (e.g. in browsers without Animation.finished
156
+ * support, where the polyfill could time out).
157
+ */
158
+ function pinIosInlineStyles(t, pos) {
159
+ require_dom.setTransform(t.topLayer, pos.topLayer);
160
+ require_dom.setTransform(t.behindLayer, pos.behindLayer);
161
+ require_dom.setOpacity(t.topDim, pos.dim);
162
+ require_dom.setTransform(t.topAppBarBackground, pos.appBarBackground);
163
+ require_dom.setOpacity(t.topTitle, pos.topTitle.opacity);
164
+ require_dom.setTransform(t.topTitle, pos.topTitle.transform);
165
+ require_dom.setOpacity(t.behindTitle, pos.behindTitle.opacity);
166
+ require_dom.setTransform(t.behindTitle, pos.behindTitle.transform);
167
+ for (const icon of t.topIcons) {
168
+ require_dom.setOpacity(icon, pos.topIconOpacity);
169
+ require_dom.setTransform(icon, pos.topTitle.transform);
170
+ }
171
+ for (const icon of t.behindIcons) require_dom.setOpacity(icon, pos.behindIconOpacity);
172
+ }
173
+ function iosAnimatePush(t) {
174
+ pinIosInlineStyles(t, IOS_OFFSCREEN);
175
+ return iosAnimate(t, IOS_OFFSCREEN, IOS_ONSCREEN);
176
+ }
177
+ function iosAnimatePop(t) {
178
+ return iosAnimate(t, IOS_ONSCREEN, IOS_OFFSCREEN);
179
+ }
180
+ function androidAnimate(t, direction) {
181
+ const isPush = direction === "push";
182
+ const duration = isPush ? ANDROID_ENTER_DURATION : ANDROID_EXIT_DURATION;
183
+ const opts = {
184
+ duration,
185
+ easing: isPush ? ANDROID_ENTER_EASING : ANDROID_EXIT_EASING,
186
+ fill: "forwards"
187
+ };
188
+ if (isPush) {
189
+ require_dom.setOpacity(t.topLayer, "0");
190
+ require_dom.setTransform(t.topLayer, "translate3d(0, 8vh, 0)");
191
+ require_dom.setOpacity(t.topDim, "0");
192
+ if (t.topAppBarRoot) {
193
+ require_dom.setOpacity(t.topAppBarRoot, "0");
194
+ require_dom.setTransform(t.topAppBarRoot, "translate3d(0, 8vh, 0)");
195
+ }
196
+ } else {
197
+ require_dom.setOpacity(t.topLayer, "1");
198
+ require_dom.setTransform(t.topLayer, "translate3d(0, 0, 0)");
199
+ if (t.topAppBarRoot) {
200
+ require_dom.setOpacity(t.topAppBarRoot, "1");
201
+ require_dom.setTransform(t.topAppBarRoot, "translate3d(0, 0, 0)");
202
+ }
203
+ if (t.behindAppBarRoot) t.behindAppBarRoot.style.opacity = "";
204
+ }
205
+ const [fromOpacity, toOpacity] = isPush ? ["0", "1"] : ["1", "0"];
206
+ const [fromY, toY] = isPush ? ["8vh", "0"] : ["0", "8vh"];
207
+ const shared = [{
208
+ opacity: fromOpacity,
209
+ transform: `translate3d(0, ${fromY}, 0)`
210
+ }, {
211
+ opacity: toOpacity,
212
+ transform: `translate3d(0, ${toY}, 0)`
213
+ }];
214
+ const dimFrames = [{
215
+ opacity: fromOpacity,
216
+ transform: `translate3d(0, ${isPush ? "-8vh" : "0"}, 0)`
217
+ }, {
218
+ opacity: toOpacity,
219
+ transform: `translate3d(0, ${isPush ? "0" : "-8vh"}, 0)`
220
+ }];
221
+ return collectAnimations([
222
+ safeAnimate(t.topLayer, shared, opts),
223
+ safeAnimate(t.topDim, dimFrames, opts),
224
+ safeAnimate(t.topAppBarRoot, shared, opts)
225
+ ], duration);
226
+ }
227
+ function fadeInAnimate(t, direction) {
228
+ const isPush = direction === "push";
229
+ const duration = isPush ? FADE_IN_ENTER_DURATION : FADE_IN_EXIT_DURATION;
230
+ const opts = {
231
+ duration,
232
+ easing: isPush ? FADE_IN_ENTER_EASING : FADE_IN_EXIT_EASING,
233
+ fill: "forwards"
234
+ };
235
+ if (isPush) {
236
+ require_dom.setOpacity(t.topLayer, "0");
237
+ if (t.topAppBarRoot) require_dom.setOpacity(t.topAppBarRoot, "0");
238
+ } else {
239
+ require_dom.setOpacity(t.topLayer, "1");
240
+ if (t.topAppBarRoot) require_dom.setOpacity(t.topAppBarRoot, "1");
241
+ if (t.behindAppBarRoot) t.behindAppBarRoot.style.opacity = "";
242
+ }
243
+ const frames = [{ opacity: isPush ? "0" : "1" }, { opacity: isPush ? "1" : "0" }];
244
+ return collectAnimations([safeAnimate(t.topLayer, frames, opts), safeAnimate(t.topAppBarRoot, frames, opts)], duration);
245
+ }
246
+ function animateSwipe(t, displacement, duration, end) {
247
+ const ratio = displacement / window.innerWidth;
248
+ const topFade = Math.max(0, 1 - ratio * 3);
249
+ const opts = {
250
+ duration,
251
+ easing: IOS_EASING,
252
+ fill: "forwards"
253
+ };
254
+ const currentBehind = `translate3d(calc(-30% + ${displacement * require_constants.BEHIND_PARALLAX}px), 0, 0)`;
255
+ const currentTitleOffset = `translate3d(${displacement * require_constants.TITLE_TRANSLATE_RATIO}px, 0, 0)`;
256
+ const currentBehindTitle = `translate3d(calc(-25% + ${displacement * require_constants.TITLE_TRANSLATE_RATIO}px), 0, 0)`;
257
+ const anims = [
258
+ safeAnimate(t.topLayer, [{ transform: `translate3d(${displacement}px, 0, 0)` }, { transform: end.topLayer }], opts),
259
+ safeAnimate(t.behindLayer, [{ transform: currentBehind }, { transform: end.behindLayer }], opts),
260
+ safeAnimate(t.topDim, [{ opacity: `${1 - ratio}` }, { opacity: end.dim }], opts),
261
+ safeAnimate(t.topTitle, [{
262
+ opacity: `${topFade}`,
263
+ transform: currentTitleOffset
264
+ }, end.topTitle], opts),
265
+ safeAnimate(t.behindTitle, [{
266
+ opacity: `${ratio}`,
267
+ transform: currentBehindTitle
268
+ }, end.behindTitle], opts)
269
+ ];
270
+ for (const icon of t.topIcons) anims.push(safeAnimate(icon, [{
271
+ opacity: `${topFade}`,
272
+ transform: currentTitleOffset
273
+ }, {
274
+ opacity: end.topIconOpacity,
275
+ transform: end.topTitle.transform
276
+ }], opts));
277
+ for (const icon of t.behindIcons) anims.push(safeAnimate(icon, [{ opacity: `${ratio}` }, { opacity: end.behindIconOpacity }], opts));
278
+ anims.push(safeAnimate(t.topAppBarBackground, [{ transform: `translate3d(${displacement}px, 0, 0)` }, { transform: end.appBarBackground }], opts));
279
+ return collectAnimations(anims, duration);
280
+ }
281
+ function animateTransition(targets, direction, style) {
282
+ switch (style) {
283
+ case "slideFromRightIOS": return direction === "push" ? iosAnimatePush(targets) : iosAnimatePop(targets);
284
+ case "fadeFromBottomAndroid": return androidAnimate(targets, direction);
285
+ case "fadeIn": return fadeInAnimate(targets, direction);
286
+ default: return {
287
+ animations: [],
288
+ finished: Promise.resolve()
289
+ };
290
+ }
291
+ }
292
+ function animateSwipeComplete(t, displacement) {
293
+ return animateSwipe(t, displacement, IOS_DURATION, IOS_OFFSCREEN);
294
+ }
295
+ function animateSwipeCancel(t, displacement) {
296
+ return animateSwipe(t, displacement, IOS_DURATION, IOS_ONSCREEN);
297
+ }
298
+ //#endregion
299
+ exports.animateSwipeCancel = animateSwipeCancel;
300
+ exports.animateSwipeComplete = animateSwipeComplete;
301
+ exports.animateTransition = animateTransition;
302
+ exports.cancelAll = cancelAll;
303
+ exports.scrubAppBarBackground = scrubAppBarBackground;
@@ -0,0 +1,16 @@
1
+ import { TransitionTargets, TransitionStyle } from './dom';
2
+ /**
3
+ * Snap the app-bar background element transform during an interactive gesture
4
+ * (e.g. swipe-back). Reuses a single Animation by swapping its keyframes each
5
+ * call — recreating on every touchmove would cancel-then-recreate and cause a
6
+ * visible flicker.
7
+ */
8
+ export declare function scrubAppBarBackground(el: HTMLElement | null, transform: string, prev: Animation | null): Animation | null;
9
+ export declare function cancelAll(animations: (Animation | null)[]): void;
10
+ export type AnimationResult = {
11
+ animations: Animation[];
12
+ finished: Promise<void>;
13
+ };
14
+ export declare function animateTransition(targets: TransitionTargets, direction: "push" | "pop", style: TransitionStyle): AnimationResult;
15
+ export declare function animateSwipeComplete(t: TransitionTargets, displacement: number): AnimationResult;
16
+ export declare function animateSwipeCancel(t: TransitionTargets, displacement: number): AnimationResult;
@@ -0,0 +1,299 @@
1
+ "use client";
2
+ import { BEHIND_PARALLAX, TITLE_TRANSLATE_RATIO } from "./constants.js";
3
+ import { setOpacity, setTransform } from "./dom.js";
4
+ //#region src/primitive/GlobalInteraction/animation.ts
5
+ var IOS_EASING = "cubic-bezier(0.2, 0.1, 0.21, 0.99)";
6
+ var IOS_DURATION = 350;
7
+ var ANDROID_ENTER_EASING = "cubic-bezier(0.23, 0.1, 0.32, 1)";
8
+ var ANDROID_ENTER_DURATION = 300;
9
+ var ANDROID_EXIT_EASING = "linear";
10
+ var ANDROID_EXIT_DURATION = 150;
11
+ var FADE_IN_ENTER_EASING = "ease-out";
12
+ var FADE_IN_ENTER_DURATION = 300;
13
+ var FADE_IN_EXIT_EASING = "ease-in";
14
+ var FADE_IN_EXIT_DURATION = 150;
15
+ var FINISHED_TIMEOUT_MARGIN = 100;
16
+ function safeAnimate(el, keyframes, options) {
17
+ if (!el) return null;
18
+ return el.animate(keyframes, options);
19
+ }
20
+ /**
21
+ * Snap the app-bar background element transform during an interactive gesture
22
+ * (e.g. swipe-back). Reuses a single Animation by swapping its keyframes each
23
+ * call — recreating on every touchmove would cancel-then-recreate and cause a
24
+ * visible flicker.
25
+ */
26
+ function scrubAppBarBackground(el, transform, prev) {
27
+ if (prev && prev.effect instanceof KeyframeEffect) try {
28
+ prev.effect.setKeyframes([{ transform }]);
29
+ return prev;
30
+ } catch {
31
+ prev.cancel();
32
+ }
33
+ return safeAnimate(el, [{ transform }], {
34
+ duration: 1,
35
+ fill: "forwards"
36
+ });
37
+ }
38
+ function cancelAll(animations) {
39
+ for (const a of animations) try {
40
+ a?.cancel();
41
+ } catch {}
42
+ }
43
+ /**
44
+ * Resolve when the animation finishes or `timeoutMs` elapses, whichever
45
+ * comes first. Uses `Animation.finished` when available (Chrome 84+,
46
+ * Safari 13.1+, Firefox 110+) and falls back to `onfinish` / `oncancel`
47
+ * listeners for older browsers (e.g. Chrome 77). Both the timer and the
48
+ * listeners are always cleaned up on the losing side of the race, so a
49
+ * late-firing animation cannot invoke a stale callback or leak via
50
+ * closure-retained Animation references.
51
+ */
52
+ function waitOne(a, timeoutMs) {
53
+ const maybeFinished = a.finished;
54
+ if (maybeFinished && typeof maybeFinished.then === "function") return new Promise((resolve) => {
55
+ let settled = false;
56
+ const finish = () => {
57
+ if (settled) return;
58
+ settled = true;
59
+ clearTimeout(timer);
60
+ resolve();
61
+ };
62
+ const timer = setTimeout(finish, timeoutMs);
63
+ maybeFinished.then(finish, finish);
64
+ });
65
+ return new Promise((resolve) => {
66
+ let settled = false;
67
+ const finish = () => {
68
+ if (settled) return;
69
+ settled = true;
70
+ try {
71
+ a.onfinish = null;
72
+ a.oncancel = null;
73
+ } catch {}
74
+ clearTimeout(timer);
75
+ resolve();
76
+ };
77
+ a.onfinish = finish;
78
+ a.oncancel = finish;
79
+ const timer = setTimeout(finish, timeoutMs);
80
+ });
81
+ }
82
+ function waitAll(animations, durationMs) {
83
+ const valid = animations.filter((a) => a !== null);
84
+ if (valid.length === 0) return Promise.resolve();
85
+ const timeout = durationMs + FINISHED_TIMEOUT_MARGIN;
86
+ return Promise.all(valid.map((a) => waitOne(a, timeout))).then(() => {});
87
+ }
88
+ function collectAnimations(anims, durationMs) {
89
+ const animations = anims.filter((a) => a !== null);
90
+ return {
91
+ animations,
92
+ finished: waitAll(animations, durationMs)
93
+ };
94
+ }
95
+ var IOS_ONSCREEN = {
96
+ topLayer: "translate3d(0, 0, 0)",
97
+ behindLayer: `translate3d(-30%, 0, 0)`,
98
+ dim: "1",
99
+ topTitle: {
100
+ opacity: "1",
101
+ transform: "translate3d(0, 0, 0)"
102
+ },
103
+ behindTitle: {
104
+ opacity: "0",
105
+ transform: `translate3d(-25%, 0, 0)`
106
+ },
107
+ topIconOpacity: "1",
108
+ behindIconOpacity: "0",
109
+ appBarBackground: "translate3d(0, 0, 0)"
110
+ };
111
+ var IOS_OFFSCREEN = {
112
+ topLayer: "translate3d(100%, 0, 0)",
113
+ behindLayer: "translate3d(0, 0, 0)",
114
+ dim: "0",
115
+ topTitle: {
116
+ opacity: "0",
117
+ transform: `translate3d(25%, 0, 0)`
118
+ },
119
+ behindTitle: {
120
+ opacity: "1",
121
+ transform: "translate3d(0, 0, 0)"
122
+ },
123
+ topIconOpacity: "0",
124
+ behindIconOpacity: "1",
125
+ appBarBackground: "translate3d(100%, 0, 0)"
126
+ };
127
+ function iosAnimate(t, from, to) {
128
+ const opts = {
129
+ duration: IOS_DURATION,
130
+ easing: IOS_EASING,
131
+ fill: "forwards"
132
+ };
133
+ const anims = [];
134
+ anims.push(safeAnimate(t.topLayer, [{ transform: from.topLayer }, { transform: to.topLayer }], opts));
135
+ anims.push(safeAnimate(t.behindLayer, [{ transform: from.behindLayer }, { transform: to.behindLayer }], opts));
136
+ anims.push(safeAnimate(t.topDim, [{ opacity: from.dim }, { opacity: to.dim }], opts));
137
+ anims.push(safeAnimate(t.topTitle, [from.topTitle, to.topTitle], opts));
138
+ anims.push(safeAnimate(t.behindTitle, [from.behindTitle, to.behindTitle], opts));
139
+ for (const icon of t.topIcons) anims.push(safeAnimate(icon, [{
140
+ opacity: from.topIconOpacity,
141
+ transform: from.topTitle.transform
142
+ }, {
143
+ opacity: to.topIconOpacity,
144
+ transform: to.topTitle.transform
145
+ }], opts));
146
+ for (const icon of t.behindIcons) anims.push(safeAnimate(icon, [{ opacity: from.behindIconOpacity }, { opacity: to.behindIconOpacity }], opts));
147
+ anims.push(safeAnimate(t.topAppBarBackground, [{ transform: from.appBarBackground }, { transform: to.appBarBackground }], opts));
148
+ return collectAnimations(anims, IOS_DURATION);
149
+ }
150
+ /**
151
+ * Pin every iOS transition target to the given position with inline styles
152
+ * before the animation starts. Prevents a one-frame flash at the final
153
+ * position when the browser paints between React commit and animation
154
+ * commit, and guarantees a known start state when the previous transition's
155
+ * finish handler did not run (e.g. in browsers without Animation.finished
156
+ * support, where the polyfill could time out).
157
+ */
158
+ function pinIosInlineStyles(t, pos) {
159
+ setTransform(t.topLayer, pos.topLayer);
160
+ setTransform(t.behindLayer, pos.behindLayer);
161
+ setOpacity(t.topDim, pos.dim);
162
+ setTransform(t.topAppBarBackground, pos.appBarBackground);
163
+ setOpacity(t.topTitle, pos.topTitle.opacity);
164
+ setTransform(t.topTitle, pos.topTitle.transform);
165
+ setOpacity(t.behindTitle, pos.behindTitle.opacity);
166
+ setTransform(t.behindTitle, pos.behindTitle.transform);
167
+ for (const icon of t.topIcons) {
168
+ setOpacity(icon, pos.topIconOpacity);
169
+ setTransform(icon, pos.topTitle.transform);
170
+ }
171
+ for (const icon of t.behindIcons) setOpacity(icon, pos.behindIconOpacity);
172
+ }
173
+ function iosAnimatePush(t) {
174
+ pinIosInlineStyles(t, IOS_OFFSCREEN);
175
+ return iosAnimate(t, IOS_OFFSCREEN, IOS_ONSCREEN);
176
+ }
177
+ function iosAnimatePop(t) {
178
+ return iosAnimate(t, IOS_ONSCREEN, IOS_OFFSCREEN);
179
+ }
180
+ function androidAnimate(t, direction) {
181
+ const isPush = direction === "push";
182
+ const duration = isPush ? ANDROID_ENTER_DURATION : ANDROID_EXIT_DURATION;
183
+ const opts = {
184
+ duration,
185
+ easing: isPush ? ANDROID_ENTER_EASING : ANDROID_EXIT_EASING,
186
+ fill: "forwards"
187
+ };
188
+ if (isPush) {
189
+ setOpacity(t.topLayer, "0");
190
+ setTransform(t.topLayer, "translate3d(0, 8vh, 0)");
191
+ setOpacity(t.topDim, "0");
192
+ if (t.topAppBarRoot) {
193
+ setOpacity(t.topAppBarRoot, "0");
194
+ setTransform(t.topAppBarRoot, "translate3d(0, 8vh, 0)");
195
+ }
196
+ } else {
197
+ setOpacity(t.topLayer, "1");
198
+ setTransform(t.topLayer, "translate3d(0, 0, 0)");
199
+ if (t.topAppBarRoot) {
200
+ setOpacity(t.topAppBarRoot, "1");
201
+ setTransform(t.topAppBarRoot, "translate3d(0, 0, 0)");
202
+ }
203
+ if (t.behindAppBarRoot) t.behindAppBarRoot.style.opacity = "";
204
+ }
205
+ const [fromOpacity, toOpacity] = isPush ? ["0", "1"] : ["1", "0"];
206
+ const [fromY, toY] = isPush ? ["8vh", "0"] : ["0", "8vh"];
207
+ const shared = [{
208
+ opacity: fromOpacity,
209
+ transform: `translate3d(0, ${fromY}, 0)`
210
+ }, {
211
+ opacity: toOpacity,
212
+ transform: `translate3d(0, ${toY}, 0)`
213
+ }];
214
+ const dimFrames = [{
215
+ opacity: fromOpacity,
216
+ transform: `translate3d(0, ${isPush ? "-8vh" : "0"}, 0)`
217
+ }, {
218
+ opacity: toOpacity,
219
+ transform: `translate3d(0, ${isPush ? "0" : "-8vh"}, 0)`
220
+ }];
221
+ return collectAnimations([
222
+ safeAnimate(t.topLayer, shared, opts),
223
+ safeAnimate(t.topDim, dimFrames, opts),
224
+ safeAnimate(t.topAppBarRoot, shared, opts)
225
+ ], duration);
226
+ }
227
+ function fadeInAnimate(t, direction) {
228
+ const isPush = direction === "push";
229
+ const duration = isPush ? FADE_IN_ENTER_DURATION : FADE_IN_EXIT_DURATION;
230
+ const opts = {
231
+ duration,
232
+ easing: isPush ? FADE_IN_ENTER_EASING : FADE_IN_EXIT_EASING,
233
+ fill: "forwards"
234
+ };
235
+ if (isPush) {
236
+ setOpacity(t.topLayer, "0");
237
+ if (t.topAppBarRoot) setOpacity(t.topAppBarRoot, "0");
238
+ } else {
239
+ setOpacity(t.topLayer, "1");
240
+ if (t.topAppBarRoot) setOpacity(t.topAppBarRoot, "1");
241
+ if (t.behindAppBarRoot) t.behindAppBarRoot.style.opacity = "";
242
+ }
243
+ const frames = [{ opacity: isPush ? "0" : "1" }, { opacity: isPush ? "1" : "0" }];
244
+ return collectAnimations([safeAnimate(t.topLayer, frames, opts), safeAnimate(t.topAppBarRoot, frames, opts)], duration);
245
+ }
246
+ function animateSwipe(t, displacement, duration, end) {
247
+ const ratio = displacement / window.innerWidth;
248
+ const topFade = Math.max(0, 1 - ratio * 3);
249
+ const opts = {
250
+ duration,
251
+ easing: IOS_EASING,
252
+ fill: "forwards"
253
+ };
254
+ const currentBehind = `translate3d(calc(-30% + ${displacement * BEHIND_PARALLAX}px), 0, 0)`;
255
+ const currentTitleOffset = `translate3d(${displacement * TITLE_TRANSLATE_RATIO}px, 0, 0)`;
256
+ const currentBehindTitle = `translate3d(calc(-25% + ${displacement * TITLE_TRANSLATE_RATIO}px), 0, 0)`;
257
+ const anims = [
258
+ safeAnimate(t.topLayer, [{ transform: `translate3d(${displacement}px, 0, 0)` }, { transform: end.topLayer }], opts),
259
+ safeAnimate(t.behindLayer, [{ transform: currentBehind }, { transform: end.behindLayer }], opts),
260
+ safeAnimate(t.topDim, [{ opacity: `${1 - ratio}` }, { opacity: end.dim }], opts),
261
+ safeAnimate(t.topTitle, [{
262
+ opacity: `${topFade}`,
263
+ transform: currentTitleOffset
264
+ }, end.topTitle], opts),
265
+ safeAnimate(t.behindTitle, [{
266
+ opacity: `${ratio}`,
267
+ transform: currentBehindTitle
268
+ }, end.behindTitle], opts)
269
+ ];
270
+ for (const icon of t.topIcons) anims.push(safeAnimate(icon, [{
271
+ opacity: `${topFade}`,
272
+ transform: currentTitleOffset
273
+ }, {
274
+ opacity: end.topIconOpacity,
275
+ transform: end.topTitle.transform
276
+ }], opts));
277
+ for (const icon of t.behindIcons) anims.push(safeAnimate(icon, [{ opacity: `${ratio}` }, { opacity: end.behindIconOpacity }], opts));
278
+ anims.push(safeAnimate(t.topAppBarBackground, [{ transform: `translate3d(${displacement}px, 0, 0)` }, { transform: end.appBarBackground }], opts));
279
+ return collectAnimations(anims, duration);
280
+ }
281
+ function animateTransition(targets, direction, style) {
282
+ switch (style) {
283
+ case "slideFromRightIOS": return direction === "push" ? iosAnimatePush(targets) : iosAnimatePop(targets);
284
+ case "fadeFromBottomAndroid": return androidAnimate(targets, direction);
285
+ case "fadeIn": return fadeInAnimate(targets, direction);
286
+ default: return {
287
+ animations: [],
288
+ finished: Promise.resolve()
289
+ };
290
+ }
291
+ }
292
+ function animateSwipeComplete(t, displacement) {
293
+ return animateSwipe(t, displacement, IOS_DURATION, IOS_OFFSCREEN);
294
+ }
295
+ function animateSwipeCancel(t, displacement) {
296
+ return animateSwipe(t, displacement, IOS_DURATION, IOS_ONSCREEN);
297
+ }
298
+ //#endregion
299
+ export { animateSwipeCancel, animateSwipeComplete, animateTransition, cancelAll, scrubAppBarBackground };
@@ -0,0 +1,9 @@
1
+ "use client";
2
+ //#region src/primitive/GlobalInteraction/constants.ts
3
+ /** Behind activity's parallax multiplier applied to swipe displacement. */
4
+ var BEHIND_PARALLAX = .3;
5
+ /** Title translate-X multiplier applied to swipe displacement (px). */
6
+ var TITLE_TRANSLATE_RATIO = .15;
7
+ //#endregion
8
+ exports.BEHIND_PARALLAX = BEHIND_PARALLAX;
9
+ exports.TITLE_TRANSLATE_RATIO = TITLE_TRANSLATE_RATIO;
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Shared numeric constants for WAAPI animations and inline style math.
3
+ *
4
+ * Consumers:
5
+ * - animation.ts (WAAPI keyframes)
6
+ * - dom.ts (swipe-time inline styles)
7
+ *
8
+ * Keeping these in one place prevents drift between the two consumers.
9
+ */
10
+ /** Behind activity's baseline X offset during iOS slide (percent). */
11
+ export declare const BEHIND_OFFSET_PERCENT = -30;
12
+ /** Behind activity's parallax multiplier applied to swipe displacement. */
13
+ export declare const BEHIND_PARALLAX = 0.3;
14
+ /** Title translate-X multiplier applied to swipe displacement (px). */
15
+ export declare const TITLE_TRANSLATE_RATIO = 0.15;
16
+ /** Top title/icon opacity fade rate during swipe (ratio multiplier). */
17
+ export declare const OPACITY_FADE_MULTIPLIER = 3;
18
+ /** Title X offset at onscreen/offscreen endpoints (percent). */
19
+ export declare const TITLE_OFFSET_PERCENT = 25;