@hanzogui/animations-motion 8.3.0 → 8.3.2

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 (51) hide show
  1. package/dist/cjs/createAnimations.js +804 -0
  2. package/dist/cjs/createAnimations.js.map +1 -0
  3. package/dist/cjs/index.js +6 -0
  4. package/dist/cjs/index.js.map +1 -0
  5. package/dist/cjs/package.json +1 -0
  6. package/dist/cjs/polyfill.js +6 -0
  7. package/dist/cjs/polyfill.js.map +1 -0
  8. package/dist/esm/createAnimations.js +799 -0
  9. package/dist/esm/createAnimations.js.map +1 -0
  10. package/dist/esm/index.js +3 -3
  11. package/dist/esm/index.js.map +1 -1
  12. package/dist/esm/polyfill.js +6 -0
  13. package/dist/esm/polyfill.js.map +1 -0
  14. package/dist/native/createAnimations.js +799 -0
  15. package/dist/native/createAnimations.js.map +1 -0
  16. package/dist/native/index.js +20 -0
  17. package/dist/native/index.js.map +1 -0
  18. package/dist/native/polyfill.js +6 -0
  19. package/dist/native/polyfill.js.map +1 -0
  20. package/package.json +17 -17
  21. package/src/createAnimations.tsx +1 -1
  22. package/src/index.ts +2 -2
  23. package/types/createAnimations.d.ts +2 -3
  24. package/types/createAnimations.d.ts.map +1 -11
  25. package/types/index.d.ts +2 -3
  26. package/types/index.d.ts.map +1 -11
  27. package/types/index.native.d.ts +1 -2
  28. package/types/index.native.d.ts.map +1 -11
  29. package/types/polyfill.d.ts +0 -1
  30. package/types/polyfill.d.ts.map +1 -11
  31. package/dist/cjs/createAnimations.cjs +0 -720
  32. package/dist/cjs/createAnimations.native.js +0 -844
  33. package/dist/cjs/createAnimations.native.js.map +0 -1
  34. package/dist/cjs/index.cjs +0 -21
  35. package/dist/cjs/index.native.js +0 -45
  36. package/dist/cjs/index.native.js.map +0 -1
  37. package/dist/cjs/polyfill.cjs +0 -3
  38. package/dist/cjs/polyfill.native.js +0 -6
  39. package/dist/cjs/polyfill.native.js.map +0 -1
  40. package/dist/esm/createAnimations.mjs +0 -683
  41. package/dist/esm/createAnimations.mjs.map +0 -1
  42. package/dist/esm/createAnimations.native.js +0 -804
  43. package/dist/esm/createAnimations.native.js.map +0 -1
  44. package/dist/esm/index.mjs +0 -3
  45. package/dist/esm/index.mjs.map +0 -1
  46. package/dist/esm/index.native.js +0 -17
  47. package/dist/esm/index.native.js.map +0 -1
  48. package/dist/esm/polyfill.mjs +0 -4
  49. package/dist/esm/polyfill.mjs.map +0 -1
  50. package/dist/esm/polyfill.native.js +0 -4
  51. package/dist/esm/polyfill.native.js.map +0 -1
@@ -0,0 +1,804 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.disableAnimationProps = void 0;
4
+ exports.createAnimations = createAnimations;
5
+ const tslib_1 = require("tslib");
6
+ const jsx_runtime_1 = require("react/jsx-runtime");
7
+ const animation_helpers_1 = require("@hanzogui/animation-helpers");
8
+ const use_presence_1 = require("@hanzogui/use-presence");
9
+ const web_1 = require("@hanzogui/web");
10
+ const react_1 = require("motion/react");
11
+ const react_2 = tslib_1.__importStar(require("react"));
12
+ const isServer = typeof window === 'undefined';
13
+ // SSR-safe wrapper: framer-motion's useAnimate imports its own React copy in
14
+ // Vite SSR bundles which causes "Invalid hook call" errors. during SSR we
15
+ // don't need animations so we return a no-op scope/animate pair.
16
+ function useAnimateSSRSafe() {
17
+ if (isServer) {
18
+ return [(0, react_2.useRef)(null), () => { }];
19
+ }
20
+ return (0, react_1.useAnimate)();
21
+ }
22
+ const MotionValueStrategy = new WeakMap();
23
+ // pending setValue onFinish callbacks, keyed by motion value. setValue stores
24
+ // the callback here; the change handler in the animated component's useEffect
25
+ // consumes it by chaining to the DOM-level animate() controls so onFinish
26
+ // fires when the *visible* animation actually completes.
27
+ const PendingMotionOnFinish = new WeakMap();
28
+ function settlePendingMotionOnFinish(mv, controls) {
29
+ const onFinish = PendingMotionOnFinish.get(mv);
30
+ if (!onFinish)
31
+ return;
32
+ PendingMotionOnFinish.delete(mv);
33
+ // chain to the DOM animation's completion. settle on both resolve and
34
+ // reject — a rejection means the animation was cancelled by a later
35
+ // setValue, and the caller still needs a completion signal. use the
36
+ // real Promise interface (.then().catch()) because framer-motion types
37
+ // the .then() callbacks as VoidFunction with no error arg.
38
+ controls.then(() => onFinish()).catch(() => onFinish());
39
+ }
40
+ function createAnimations(animations) {
41
+ let isHydratingGlobal;
42
+ const hydratingComponents = new Set();
43
+ return {
44
+ View: MotionView,
45
+ Text: MotionText,
46
+ isReactNative: false,
47
+ inputStyle: 'css',
48
+ outputStyle: 'inline',
49
+ avoidReRenders: true,
50
+ animations,
51
+ usePresence: use_presence_1.usePresence,
52
+ ResetPresence: use_presence_1.ResetPresence,
53
+ onMount() {
54
+ isHydratingGlobal = false;
55
+ hydratingComponents.forEach((cb) => cb());
56
+ },
57
+ useAnimations: (animationProps) => {
58
+ if (isHydratingGlobal === undefined && !(0, web_1.getConfig)().settings.disableSSR) {
59
+ isHydratingGlobal = true;
60
+ }
61
+ const { props, style, componentState, stateRef, useStyleEmitter, presence, styleProps, } = animationProps;
62
+ const animationKey = Array.isArray(props.transition)
63
+ ? props.transition[0]
64
+ : props.transition;
65
+ const isComponentHydrating = componentState.unmounted === true;
66
+ const isMounting = componentState.unmounted === 'should-enter';
67
+ const isEntering = !!componentState.unmounted;
68
+ const isExiting = presence?.[0] === false;
69
+ const sendExitComplete = presence?.[1];
70
+ // single consolidated ref with lazy init
71
+ const refs = (0, react_2.useRef)(null);
72
+ if (!refs.current) {
73
+ refs.current = {
74
+ isFirstRender: true,
75
+ lastDoAnimate: null,
76
+ lastDontAnimate: null,
77
+ controls: null,
78
+ lastAnimateAt: 0,
79
+ disposed: false,
80
+ wasExiting: false,
81
+ isExiting: false,
82
+ sendExitComplete: undefined,
83
+ animationState: 'default',
84
+ frozenExitTarget: null,
85
+ exitCompleteScheduled: false,
86
+ wasEntering: false,
87
+ wasDisabled: false,
88
+ wasNoClass: !!styleProps.noClass,
89
+ };
90
+ }
91
+ // track entering state transitions
92
+ const justFinishedEntering = refs.current.wasEntering && !isEntering;
93
+ (0, react_2.useEffect)(() => {
94
+ refs.current.wasEntering = isEntering;
95
+ });
96
+ // determine animation state for enter/exit transitions
97
+ const animationState = isExiting
98
+ ? 'exit'
99
+ : isMounting || justFinishedEntering
100
+ ? 'enter'
101
+ : 'default';
102
+ // disable animation during hydration and mounting (prevents "flying across the page")
103
+ const disableAnimation = isComponentHydrating || isMounting || !animationKey;
104
+ const [scope, animate] = useAnimateSSRSafe();
105
+ // sync ref values for reliable access from callbacks
106
+ refs.current.isExiting = isExiting;
107
+ refs.current.sendExitComplete = sendExitComplete;
108
+ refs.current.animationState = animationState;
109
+ // detect transition into exiting state
110
+ const justStartedExiting = isExiting && !refs.current.wasExiting;
111
+ const justStoppedExiting = !isExiting && refs.current.wasExiting;
112
+ // freeze exit animation target so direction changes don't reverse mid-exit
113
+ if (justStartedExiting || justStoppedExiting) {
114
+ refs.current.frozenExitTarget = null;
115
+ refs.current.exitCompleteScheduled = false;
116
+ }
117
+ // track previous exiting state
118
+ (0, react_2.useEffect)(() => {
119
+ refs.current.wasExiting = isExiting;
120
+ });
121
+ const { dontAnimate = {}, doAnimate, animationOptions, } = getMotionAnimatedProps(props, style, disableAnimation, animationState);
122
+ const [firstRenderStyle] = (0, react_2.useState)(style);
123
+ // avoid first render returning wrong styles - always render all, after that we can just mutate
124
+ if (refs.current.isFirstRender) {
125
+ refs.current.lastDontAnimate = firstRenderStyle;
126
+ }
127
+ const [isHydrating, setIsHydrating] = (0, react_2.useState)(isHydratingGlobal);
128
+ (0, react_2.useLayoutEffect)(() => {
129
+ if (isHydratingGlobal) {
130
+ hydratingComponents.add(() => {
131
+ setIsHydrating(false);
132
+ });
133
+ }
134
+ return () => {
135
+ refs.current.disposed = true;
136
+ };
137
+ }, []);
138
+ const flushAnimation = ({ doAnimate: doAnimateRaw = {}, animationOptions: passedOptions = {}, dontAnimate, }) => {
139
+ // track whether THIS flush starts a new animation (vs using stale controls)
140
+ let startedControls = null;
141
+ // read current state from refs (closure variables can be stale)
142
+ const isCurrentlyExiting = refs.current.isExiting;
143
+ const currentSendExitComplete = refs.current.sendExitComplete;
144
+ // freeze exit target: once the first exit animation starts, subsequent
145
+ // renders (e.g. direction change) should not reverse the exit animation.
146
+ let doAnimate = doAnimateRaw;
147
+ if (isCurrentlyExiting && refs.current.frozenExitTarget) {
148
+ doAnimate = refs.current.frozenExitTarget;
149
+ }
150
+ // only recompute animation options for exit animations to avoid stale state.
151
+ const animationOptions = isCurrentlyExiting && currentSendExitComplete
152
+ ? getAnimationOptions(props.transition ?? null, 'exit')
153
+ : passedOptions;
154
+ try {
155
+ const node = stateRef.current.host;
156
+ // on first render, reset stale animation refs - they can persist if component
157
+ // instance is reused (e.g. AnimatePresence keepChildrenMounted)
158
+ if (refs.current.isFirstRender) {
159
+ refs.current.lastDontAnimate = null;
160
+ refs.current.lastDoAnimate = null;
161
+ }
162
+ if (process.env.NODE_ENV === 'development') {
163
+ if (props['debug'] && props['debug'] !== 'profile') {
164
+ console.groupCollapsed(`[motion] animate (${JSON.stringify(getDiff(refs.current.lastDoAnimate, doAnimate), null, 2)})`);
165
+ console.info({
166
+ props,
167
+ componentState,
168
+ doAnimate,
169
+ dontAnimate,
170
+ animationOptions,
171
+ animationProps,
172
+ lastDoAnimate: { ...refs.current.lastDoAnimate },
173
+ lastDontAnimate: { ...refs.current.lastDontAnimate },
174
+ isExiting,
175
+ style,
176
+ node,
177
+ });
178
+ console.groupCollapsed(`trace >`);
179
+ console.trace();
180
+ console.groupEnd();
181
+ console.groupEnd();
182
+ }
183
+ }
184
+ if (!(node instanceof HTMLElement)) {
185
+ return;
186
+ }
187
+ // handle case where dontAnimate changes
188
+ const prevDont = refs.current.lastDontAnimate;
189
+ if (dontAnimate) {
190
+ if (prevDont) {
191
+ removeRemovedStyles(prevDont, dontAnimate, node, doAnimate);
192
+ const changed = getDiff(prevDont, dontAnimate);
193
+ if (changed) {
194
+ Object.assign(node.style, changed);
195
+ }
196
+ }
197
+ else {
198
+ Object.assign(node.style, dontAnimate);
199
+ }
200
+ }
201
+ if (doAnimate) {
202
+ // when a property moves from dontAnimate to doAnimate, preserve
203
+ // the current inline style value so WAAPI starts from the right place
204
+ if (prevDont) {
205
+ for (const key in prevDont) {
206
+ if (key in doAnimate) {
207
+ node.style[key] = prevDont[key];
208
+ if (refs.current.lastDoAnimate) {
209
+ refs.current.lastDoAnimate[key] = prevDont[key];
210
+ }
211
+ }
212
+ }
213
+ }
214
+ const lastAnimated = refs.current.lastDoAnimate;
215
+ if (lastAnimated) {
216
+ removeRemovedStyles(lastAnimated, doAnimate, node, dontAnimate);
217
+ }
218
+ const diff = getDiff(refs.current.lastDoAnimate, doAnimate);
219
+ if (diff) {
220
+ // capture frozen exit target on first exit diff
221
+ if (isCurrentlyExiting && !refs.current.frozenExitTarget) {
222
+ refs.current.frozenExitTarget = { ...doAnimate };
223
+ }
224
+ // capture mid-flight values so we can provide explicit [from, to]
225
+ // keyframes to WAAPI, ensuring smooth interpolation from the
226
+ // current visual state.
227
+ //
228
+ // only stop() during exit — for non-exit cases, WAAPI
229
+ // naturally replaces only conflicting property animations,
230
+ // letting non-conflicting ones (like an in-flight enter
231
+ // opacity animation) continue to completion.
232
+ const isPopperPosition = node.hasAttribute('data-popper-animate-position');
233
+ let midFlightValues = null;
234
+ if (refs.current.controls) {
235
+ try {
236
+ const computed = getComputedStyle(node);
237
+ midFlightValues = {};
238
+ for (const key in diff) {
239
+ const val = computed[key];
240
+ if (val !== undefined && val !== '') {
241
+ midFlightValues[key] = val;
242
+ }
243
+ }
244
+ }
245
+ catch {
246
+ // getComputedStyle can fail on detached nodes
247
+ }
248
+ if (isCurrentlyExiting) {
249
+ refs.current.controls.stop();
250
+ }
251
+ // write mid-flight values to inline so the 1-frame gap
252
+ // (while motion resolves keyframes) shows the correct
253
+ // position instead of stale inline styles
254
+ if (midFlightValues) {
255
+ for (const key in midFlightValues) {
256
+ ;
257
+ node.style[key] = midFlightValues[key];
258
+ }
259
+ }
260
+ // for popper position elements, cancel WAAPI animations
261
+ // directly so motion.dev's internal stop() sees "idle" state
262
+ // and skips commitStyles. without this, commitStyles writes
263
+ // a mid-flight transform that's visible for 1 frame before
264
+ // the new animation starts, causing a flash toward (0,0).
265
+ if (isPopperPosition) {
266
+ const anims = node.getAnimations();
267
+ for (const anim of anims) {
268
+ anim.cancel();
269
+ }
270
+ }
271
+ }
272
+ const fixedDiff = fixTransparentColors(diff, refs.current.lastDoAnimate, doAnimate);
273
+ // provide explicit [from, to] keyframe for transforms during
274
+ // mid-flight interruption so motion starts from the right place —
275
+ // but ONLY when we've torn down the previous animation (popper
276
+ // cancel above, or exit stop()). otherwise the prior WAAPI
277
+ // transform animation is still running, and pinning a one-frame-
278
+ // stale `from` matrix makes each new flush re-start the animation
279
+ // from that stale base instead of continuing from the live value.
280
+ // for a plain transition element being interrupted repeatedly
281
+ // (the hanzogui.dev logo dot swept back and forth — worse the more
282
+ // concurrent React work the page is doing, since each render flushes
283
+ // again) that reads as a constant stutter/reset instead of a smooth
284
+ // glide. in the un-torn-down case motion's resolver already
285
+ // interpolates from the live value, so leave it alone. (regressed in
286
+ // 9485bcef0e when this keyframe was ungated; covered by
287
+ // LogoDotInterrupt.animated.test.tsx)
288
+ //
289
+ // note: an earlier version also keyframed entering-presence-child
290
+ // interrupts, but enter no longer tears down (see the stop() comment
291
+ // above — WAAPI replaces conflicting props per-property), so those
292
+ // now rely on the same live-value interpolation. covered by
293
+ // PopoverClickDuringEnter / AnimatePresenceEnterExit.
294
+ if ((isPopperPosition || isCurrentlyExiting) &&
295
+ midFlightValues?.transform &&
296
+ fixedDiff.transform) {
297
+ fixedDiff.transform = [midFlightValues.transform, fixedDiff.transform];
298
+ }
299
+ startedControls = animate(scope.current, fixedDiff, animationOptions);
300
+ refs.current.controls = startedControls;
301
+ refs.current.lastAnimateAt = Date.now();
302
+ }
303
+ }
304
+ refs.current.lastDontAnimate = dontAnimate ? { ...dontAnimate } : {};
305
+ refs.current.lastDoAnimate = doAnimate ? { ...doAnimate } : {};
306
+ }
307
+ finally {
308
+ // exit completion: notify AnimatePresence when exit animation finishes
309
+ if (isCurrentlyExiting && currentSendExitComplete) {
310
+ if (startedControls) {
311
+ // new animation started — attach completion handler
312
+ refs.current.exitCompleteScheduled = true;
313
+ startedControls.finished
314
+ .then(() => {
315
+ // guard: only complete if still exiting (prevents stale promise
316
+ // from calling sendExitComplete after a re-entry cancels the exit)
317
+ if (refs.current.isExiting) {
318
+ currentSendExitComplete();
319
+ }
320
+ })
321
+ .catch(() => {
322
+ if (refs.current.isExiting) {
323
+ currentSendExitComplete();
324
+ }
325
+ });
326
+ }
327
+ else if (!refs.current.exitCompleteScheduled) {
328
+ // no animation started AND none previously scheduled (e.g. diff=null
329
+ // on re-render mid-exit because frozenExitTarget matches lastDoAnimate)
330
+ // — complete immediately only if we've never started an exit animation
331
+ currentSendExitComplete();
332
+ }
333
+ // else: exit animation already scheduled via a previous flush,
334
+ // its .finished promise will call sendExitComplete when done
335
+ }
336
+ }
337
+ };
338
+ useStyleEmitter?.((nextStyle, effectiveTransition) => {
339
+ const animationProps = getMotionAnimatedProps(props, nextStyle, disableAnimation, refs.current.animationState, effectiveTransition);
340
+ flushAnimation(animationProps);
341
+ });
342
+ (0, web_1.useIsomorphicLayoutEffect)(() => {
343
+ if (refs.current.isFirstRender) {
344
+ refs.current.isFirstRender = false;
345
+ refs.current.wasDisabled = disableAnimation;
346
+ // during hydration, skip inline style writes entirely — SSR CSS
347
+ // already has the correct values. writing them again as inline
348
+ // styles triggers browser style recalc that causes visible font
349
+ // flashes (fontWeight, fontSize, letterSpacing, lineHeight).
350
+ // we only need to track refs for future animation diffing.
351
+ if (isHydrating) {
352
+ if (doAnimate && Object.keys(doAnimate).length > 0) {
353
+ refs.current.lastDoAnimate = { ...doAnimate };
354
+ }
355
+ else {
356
+ refs.current.lastDoAnimate = dontAnimate ? { ...dontAnimate } : {};
357
+ }
358
+ refs.current.lastDontAnimate = dontAnimate ? { ...dontAnimate } : {};
359
+ refs.current.lastAnimateAt = Date.now();
360
+ return;
361
+ }
362
+ // after hydration, use simpler logic
363
+ refs.current.lastDontAnimate = dontAnimate ? { ...dontAnimate } : {};
364
+ refs.current.lastDoAnimate = doAnimate ? { ...doAnimate } : {};
365
+ return;
366
+ }
367
+ // when animations first turn on after the mount/hydration handoff, the
368
+ // element is already at its resting position (SSR atomic class, or the
369
+ // dontAnimate inline styles). animating now would spring from the lost
370
+ // "from" value — which for a transform reads as 0 and flashes the
371
+ // element across the screen (e.g. progress bar flashing full, #4011).
372
+ // jump straight to the resolved styles instead, so it renders at the
373
+ // right place with no enter animation. only real changes after this
374
+ // animate. components with an explicit enter animation still animate.
375
+ const justEnabled = refs.current.wasDisabled && !disableAnimation;
376
+ refs.current.wasDisabled = disableAnimation;
377
+ // SSR hydration handoff: styleProps.noClass flips false -> true on the
378
+ // render where the core strips the SSR atomic classes and moves every
379
+ // style inline (outputStyle: 'inline'). the style delta is huge but the
380
+ // visual state is unchanged — animating it would spring every property
381
+ // from the stripped (zeroed) computed values, visibly collapsing and
382
+ // re-growing SSR-painted elements right after load. jump instead: the
383
+ // inline writes below run pre-paint, so the strip is never visible.
384
+ const justStrippedClasses = !refs.current.wasNoClass && !!styleProps.noClass;
385
+ refs.current.wasNoClass = !!styleProps.noClass;
386
+ if ((justEnabled || justStrippedClasses) && animationState !== 'enter') {
387
+ const node = stateRef.current.host;
388
+ if (node instanceof HTMLElement) {
389
+ if (dontAnimate)
390
+ Object.assign(node.style, dontAnimate);
391
+ if (doAnimate)
392
+ Object.assign(node.style, doAnimate);
393
+ }
394
+ refs.current.lastDontAnimate = dontAnimate ? { ...dontAnimate } : {};
395
+ refs.current.lastDoAnimate = doAnimate ? { ...doAnimate } : {};
396
+ return;
397
+ }
398
+ flushAnimation({
399
+ doAnimate,
400
+ dontAnimate,
401
+ animationOptions,
402
+ });
403
+ }, [style, isExiting, disableAnimation, styleProps.noClass]);
404
+ if (process.env.NODE_ENV === 'development') {
405
+ if (props['debug'] && props['debug'] !== 'profile') {
406
+ console.groupCollapsed(`[motion] render`);
407
+ console.info({
408
+ style,
409
+ doAnimate,
410
+ dontAnimate,
411
+ scope,
412
+ animationOptions,
413
+ isExiting,
414
+ isFirstRender: refs.current.isFirstRender,
415
+ animationProps,
416
+ });
417
+ console.groupEnd();
418
+ }
419
+ }
420
+ return {
421
+ style: firstRenderStyle,
422
+ ref: scope,
423
+ render: 'div',
424
+ };
425
+ },
426
+ useAnimatedNumber(initial) {
427
+ const motionValue = (0, react_1.useMotionValue)(initial);
428
+ return react_2.default.useMemo(() => ({
429
+ getInstance() {
430
+ return motionValue;
431
+ },
432
+ getValue() {
433
+ return motionValue.get();
434
+ },
435
+ setValue(next, config = { type: 'spring' }, onFinish) {
436
+ if (config.type === 'direct') {
437
+ MotionValueStrategy.set(motionValue, { type: 'direct' });
438
+ motionValue.set(next);
439
+ onFinish?.();
440
+ return;
441
+ }
442
+ MotionValueStrategy.set(motionValue, config);
443
+ // we intentionally DO NOT animate the motion value itself here
444
+ // (via framer-motion's imperative animate(motionValue, next)).
445
+ // doing so drives the JS value over time, which fires a 'change'
446
+ // event per frame, and each change event kicks off a new DOM
447
+ // animate(node, ...) that cancels the previous one — the DOM
448
+ // never reaches the target (double-animation stall).
449
+ //
450
+ // instead we jump the motion value to `next` synchronously. the
451
+ // animated component's change handler receives a single change
452
+ // event, computes the final webStyle, and drives the visible
453
+ // animation via DOM animate(node, webStyle, springConfig). that
454
+ // DOM animation is the real timing source.
455
+ //
456
+ // to make `onFinish` resolve when the VISIBLE animation finishes
457
+ // (not synchronously on the change event), we stash it in
458
+ // PendingMotionOnFinish here and the change handler chains it to
459
+ // the DOM animate() controls.
460
+ if (onFinish) {
461
+ // if a previous setValue is still pending on this motion value,
462
+ // fire it now — the new setValue will cancel the prior DOM
463
+ // animation, and the caller is still owed a completion signal.
464
+ const prior = PendingMotionOnFinish.get(motionValue);
465
+ if (prior) {
466
+ PendingMotionOnFinish.delete(motionValue);
467
+ prior();
468
+ }
469
+ PendingMotionOnFinish.set(motionValue, onFinish);
470
+ }
471
+ motionValue.set(next);
472
+ },
473
+ stop() {
474
+ motionValue.stop();
475
+ },
476
+ }), [motionValue]);
477
+ },
478
+ useAnimatedNumberReaction({ value }, onValue) {
479
+ const instance = value.getInstance();
480
+ (0, react_1.useMotionValueEvent)(instance, 'change', onValue);
481
+ },
482
+ useAnimatedNumberStyle(val, getStyleProp) {
483
+ const motionValue = val.getInstance();
484
+ const getStyleRef = (0, react_2.useRef)(getStyleProp);
485
+ // we need to change useAnimatedNumberStyle to have dep args to be concurrent safe
486
+ getStyleRef.current = getStyleProp;
487
+ return (0, react_2.useMemo)(() => {
488
+ return {
489
+ getStyle: (cur) => {
490
+ return getStyleRef.current(cur);
491
+ },
492
+ motionValue,
493
+ };
494
+ }, []);
495
+ },
496
+ useAnimatedNumbersStyle(vals, getStyleProp) {
497
+ const motionValues = vals.map((v) => v.getInstance());
498
+ const getStyleRef = (0, react_2.useRef)(getStyleProp);
499
+ getStyleRef.current = getStyleProp;
500
+ return (0, react_2.useMemo)(() => {
501
+ return {
502
+ getStyle: (...currentValues) => getStyleRef.current(...currentValues),
503
+ motionValues,
504
+ };
505
+ }, []);
506
+ },
507
+ };
508
+ function getMotionAnimatedProps(props, style, disable, animationState = 'default', transitionOverride) {
509
+ if (disable) {
510
+ return {
511
+ dontAnimate: style,
512
+ };
513
+ }
514
+ const animationOptions = getAnimationOptions(transitionOverride ?? props.transition ?? null, animationState);
515
+ let dontAnimate;
516
+ let doAnimate;
517
+ const animateOnly = props.animateOnly;
518
+ for (const key in style) {
519
+ const value = style[key];
520
+ if (exports.disableAnimationProps.has(key) || (animateOnly && !animateOnly.includes(key))) {
521
+ dontAnimate || (dontAnimate = {});
522
+ dontAnimate[key] = value;
523
+ }
524
+ else {
525
+ doAnimate || (doAnimate = {});
526
+ doAnimate[key] = value;
527
+ }
528
+ }
529
+ return {
530
+ dontAnimate,
531
+ doAnimate,
532
+ animationOptions,
533
+ };
534
+ }
535
+ function getAnimationOptions(transitionProp, animationState = 'default') {
536
+ const normalized = (0, animation_helpers_1.normalizeTransition)(transitionProp);
537
+ let effectiveKey = (0, animation_helpers_1.getEffectiveAnimation)(normalized, animationState);
538
+ // fallback: if we have enter/exit defined but state is 'default' and no default key,
539
+ // use enter timing as fallback to avoid empty animation options
540
+ if (!effectiveKey && animationState === 'default') {
541
+ effectiveKey = normalized.enter || normalized.exit || null;
542
+ }
543
+ const globalConfigOverride = normalized.config
544
+ ? { ...normalized.config }
545
+ : undefined;
546
+ if (!effectiveKey &&
547
+ Object.keys(normalized.properties).length === 0 &&
548
+ !globalConfigOverride) {
549
+ return {};
550
+ }
551
+ const defaultConfig = effectiveKey ? withInferredType(animations[effectiveKey]) : null;
552
+ const delay = normalized.delay;
553
+ // framer motion's animate() expects default config at the TOP LEVEL
554
+ const result = {};
555
+ if (defaultConfig) {
556
+ Object.assign(result, defaultConfig);
557
+ }
558
+ if (globalConfigOverride) {
559
+ Object.assign(result, globalConfigOverride);
560
+ if (result.type === undefined &&
561
+ result.duration !== undefined &&
562
+ result.damping === undefined &&
563
+ result.stiffness === undefined &&
564
+ result.mass === undefined) {
565
+ result.type = 'tween';
566
+ }
567
+ }
568
+ if (delay) {
569
+ result.delay = delay;
570
+ }
571
+ if (defaultConfig || globalConfigOverride || delay) {
572
+ result.default = {
573
+ ...defaultConfig,
574
+ ...globalConfigOverride,
575
+ ...(delay ? { delay } : null),
576
+ };
577
+ }
578
+ for (const [propName, animationNameOrConfig] of Object.entries(normalized.properties)) {
579
+ if (typeof animationNameOrConfig === 'string') {
580
+ result[propName] = withInferredType(animations[animationNameOrConfig]);
581
+ }
582
+ else if (animationNameOrConfig && typeof animationNameOrConfig === 'object') {
583
+ const baseConfig = animationNameOrConfig.type
584
+ ? withInferredType(animations[animationNameOrConfig.type])
585
+ : defaultConfig;
586
+ result[propName] = {
587
+ ...baseConfig,
588
+ ...animationNameOrConfig,
589
+ };
590
+ }
591
+ }
592
+ // we standardize to ms across drivers, motion expects s
593
+ convertMsToS(result);
594
+ convertMsToS(result.default);
595
+ for (const key in result) {
596
+ if (key !== 'default' && typeof result[key] === 'object') {
597
+ convertMsToS(result[key]);
598
+ }
599
+ }
600
+ return result;
601
+ }
602
+ }
603
+ function withInferredType(config) {
604
+ if (!config) {
605
+ return { type: 'spring' };
606
+ }
607
+ const isTimingBased = config.duration !== undefined &&
608
+ config.damping === undefined &&
609
+ config.stiffness === undefined &&
610
+ config.mass === undefined;
611
+ return { type: isTimingBased ? 'tween' : 'spring', ...config };
612
+ }
613
+ function convertMsToS(config) {
614
+ if (!config)
615
+ return;
616
+ if (typeof config.delay === 'number')
617
+ config.delay = config.delay / 1000;
618
+ if (typeof config.duration === 'number') {
619
+ const isTimingBased = config.type === 'tween' ||
620
+ (config.type === undefined &&
621
+ config.damping === undefined &&
622
+ config.stiffness === undefined &&
623
+ config.mass === undefined);
624
+ if (isTimingBased) {
625
+ config.duration = config.duration / 1000;
626
+ }
627
+ }
628
+ }
629
+ function removeRemovedStyles(prev, next, node, dontClearIfIn) {
630
+ for (const key in prev) {
631
+ if (!(key in next)) {
632
+ if (dontClearIfIn && key in dontClearIfIn) {
633
+ continue;
634
+ }
635
+ node.style[key] = '';
636
+ }
637
+ }
638
+ }
639
+ // truly non-animatable CSS properties (discrete, keyword-based, no interpolation)
640
+ // properties like margin, maxHeight, zIndex, etc are animatable and intentionally excluded
641
+ exports.disableAnimationProps = new Set([
642
+ 'alignContent',
643
+ 'alignItems',
644
+ 'boxSizing',
645
+ 'contain',
646
+ 'containerType',
647
+ 'display',
648
+ 'flexBasis',
649
+ 'flexDirection',
650
+ 'fontFamily',
651
+ 'justifyContent',
652
+ 'overflow',
653
+ 'overflowX',
654
+ 'overflowY',
655
+ 'pointerEvents',
656
+ 'position',
657
+ 'textWrap',
658
+ 'userSelect',
659
+ ]);
660
+ const MotionView = createMotionView('div');
661
+ const MotionText = createMotionView('span');
662
+ function createMotionView(defaultTag) {
663
+ const isText = defaultTag === 'span';
664
+ const Component = (0, react_2.forwardRef)((propsIn, ref) => {
665
+ const { forwardedRef, animation, render = defaultTag, style, ...propsRest } = propsIn;
666
+ const [scope, animate] = useAnimateSSRSafe();
667
+ const hostRef = (0, react_2.useRef)(null);
668
+ const composedRefs = (0, web_1.useComposedRefs)(forwardedRef, ref, hostRef, scope);
669
+ const stateRef = (0, react_2.useRef)(null);
670
+ if (!stateRef.current) {
671
+ stateRef.current = {
672
+ get host() {
673
+ return hostRef.current;
674
+ },
675
+ };
676
+ }
677
+ const [_, state] = (0, web_1.useThemeWithState)({});
678
+ const styles = Array.isArray(style) ? style : [style];
679
+ const [animatedStyle, nonAnimatedStyles] = (() => {
680
+ let animatedStyle;
681
+ const nonAnimatedStyles = [];
682
+ for (const style of styles) {
683
+ if (style.getStyle) {
684
+ animatedStyle = style;
685
+ }
686
+ else {
687
+ nonAnimatedStyles.push(style);
688
+ }
689
+ }
690
+ return [animatedStyle, nonAnimatedStyles];
691
+ })();
692
+ function getProps(props) {
693
+ const out = (0, web_1.getSplitStyles)(props, isText ? web_1.Text.staticConfig : web_1.View.staticConfig, state?.theme, state?.name, {
694
+ unmounted: false,
695
+ }, {
696
+ isAnimated: false,
697
+ noClass: true,
698
+ resolveValues: 'auto',
699
+ });
700
+ if (!out) {
701
+ return {};
702
+ }
703
+ if (out.viewProps.style) {
704
+ (0, web_1.fixStyles)(out.viewProps.style);
705
+ (0, web_1.styleToCSS)(out.viewProps.style);
706
+ }
707
+ return out.viewProps;
708
+ }
709
+ const props = getProps({ ...propsRest, style: nonAnimatedStyles });
710
+ const Element = render || 'div';
711
+ const transformedProps = web_1.hooks.usePropsTransform?.(render, props, stateRef, false);
712
+ (0, react_2.useEffect)(() => {
713
+ if (!animatedStyle)
714
+ return;
715
+ // multi-value path: subscribe to all motion values
716
+ if (animatedStyle.motionValues) {
717
+ const mvs = animatedStyle.motionValues;
718
+ const unsubs = mvs.map((mv) => mv.on('change', () => {
719
+ const currentValues = mvs.map((v) => v.get());
720
+ const nextStyle = animatedStyle.getStyle(...currentValues);
721
+ const animationConfig = MotionValueStrategy.get(mv);
722
+ const node = hostRef.current;
723
+ const webStyle = getProps({ style: nextStyle }).style;
724
+ if (webStyle && node instanceof HTMLElement) {
725
+ const motionAnimationConfig = animationConfig?.type === 'timing'
726
+ ? { type: 'tween', duration: (animationConfig?.duration || 0) / 1000 }
727
+ : animationConfig?.type === 'direct'
728
+ ? { type: 'tween', duration: 0 }
729
+ : { type: 'spring', ...animationConfig };
730
+ const controls = animate(node, webStyle, motionAnimationConfig);
731
+ settlePendingMotionOnFinish(mv, controls);
732
+ }
733
+ }));
734
+ return () => unsubs.forEach((fn) => fn());
735
+ }
736
+ // single-value path
737
+ if (!animatedStyle.motionValue)
738
+ return;
739
+ return animatedStyle.motionValue.on('change', (value) => {
740
+ const nextStyle = animatedStyle.getStyle(value);
741
+ const animationConfig = MotionValueStrategy.get(animatedStyle.motionValue);
742
+ const node = hostRef.current;
743
+ const webStyle = getProps({ style: nextStyle }).style;
744
+ if (webStyle && node instanceof HTMLElement) {
745
+ const motionAnimationConfig = animationConfig?.type === 'timing'
746
+ ? {
747
+ type: 'tween',
748
+ duration: (animationConfig?.duration || 0) / 1000,
749
+ }
750
+ : animationConfig?.type === 'direct'
751
+ ? { type: 'tween', duration: 0 }
752
+ : {
753
+ type: 'spring',
754
+ ...animationConfig,
755
+ };
756
+ const controls = animate(node, webStyle, motionAnimationConfig);
757
+ settlePendingMotionOnFinish(animatedStyle.motionValue, controls);
758
+ }
759
+ });
760
+ }, [animatedStyle]);
761
+ return (0, jsx_runtime_1.jsx)(Element, { ...transformedProps, ref: composedRefs });
762
+ });
763
+ Component['acceptRenderProp'] = true;
764
+ return Component;
765
+ }
766
+ function getDiff(previous, next) {
767
+ if (!previous) {
768
+ return next;
769
+ }
770
+ let diff = null;
771
+ for (const key in next) {
772
+ if (next[key] !== previous[key]) {
773
+ diff || (diff = {});
774
+ diff[key] = next[key];
775
+ }
776
+ }
777
+ return diff;
778
+ }
779
+ // motion.dev can't animate to "transparent" - convert it to rgba
780
+ // try to extract RGB from previous or next value for smooth color transitions
781
+ function fixTransparentColors(diff, previous, next) {
782
+ let result = diff;
783
+ for (const key in diff) {
784
+ if (diff[key] === 'transparent') {
785
+ let fixed = 'rgba(0, 0, 0, 0)';
786
+ const candidates = [previous?.[key], next?.[key]];
787
+ for (const candidate of candidates) {
788
+ if (typeof candidate === 'string' && candidate !== 'transparent') {
789
+ const rgbaMatch = candidate.match(/^rgba?\(([^,]+),\s*([^,]+),\s*([^,)]+)/);
790
+ if (rgbaMatch) {
791
+ fixed = `rgba(${rgbaMatch[1]}, ${rgbaMatch[2]}, ${rgbaMatch[3]}, 0)`;
792
+ break;
793
+ }
794
+ }
795
+ }
796
+ if (result === diff) {
797
+ result = { ...diff };
798
+ }
799
+ result[key] = fixed;
800
+ }
801
+ }
802
+ return result;
803
+ }
804
+ //# sourceMappingURL=createAnimations.js.map