motion 12.9.6 → 12.10.0

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/dist/cjs/index.js CHANGED
@@ -3366,420 +3366,6 @@ function resolveElements(elementOrSelector, scope, selectorCache) {
3366
3366
  return Array.from(elementOrSelector);
3367
3367
  }
3368
3368
 
3369
- function styleEffect(subject, values) {
3370
- const elements = resolveElements(subject);
3371
- const subscriptions = [];
3372
- for (let i = 0; i < elements.length; i++) {
3373
- const element = elements[i];
3374
- for (const key in values) {
3375
- const value = values[key];
3376
- /**
3377
- * TODO: Get specific setters for combined props (like x)
3378
- * or values with default types (like color)
3379
- *
3380
- * TODO: CSS variable support
3381
- */
3382
- const updateStyle = () => {
3383
- element.style[key] = value.get();
3384
- };
3385
- const scheduleUpdate = () => frame.render(updateStyle);
3386
- const cancel = value.on("change", scheduleUpdate);
3387
- scheduleUpdate();
3388
- subscriptions.push(() => {
3389
- cancel();
3390
- cancelFrame(updateStyle);
3391
- });
3392
- }
3393
- }
3394
- return () => {
3395
- for (const cancel of subscriptions) {
3396
- cancel();
3397
- }
3398
- };
3399
- }
3400
-
3401
- const { schedule: microtask, cancel: cancelMicrotask } =
3402
- /* @__PURE__ */ createRenderBatcher(queueMicrotask, false);
3403
-
3404
- const isDragging = {
3405
- x: false,
3406
- y: false,
3407
- };
3408
- function isDragActive() {
3409
- return isDragging.x || isDragging.y;
3410
- }
3411
-
3412
- function setDragLock(axis) {
3413
- if (axis === "x" || axis === "y") {
3414
- if (isDragging[axis]) {
3415
- return null;
3416
- }
3417
- else {
3418
- isDragging[axis] = true;
3419
- return () => {
3420
- isDragging[axis] = false;
3421
- };
3422
- }
3423
- }
3424
- else {
3425
- if (isDragging.x || isDragging.y) {
3426
- return null;
3427
- }
3428
- else {
3429
- isDragging.x = isDragging.y = true;
3430
- return () => {
3431
- isDragging.x = isDragging.y = false;
3432
- };
3433
- }
3434
- }
3435
- }
3436
-
3437
- function setupGesture(elementOrSelector, options) {
3438
- const elements = resolveElements(elementOrSelector);
3439
- const gestureAbortController = new AbortController();
3440
- const eventOptions = {
3441
- passive: true,
3442
- ...options,
3443
- signal: gestureAbortController.signal,
3444
- };
3445
- const cancel = () => gestureAbortController.abort();
3446
- return [elements, eventOptions, cancel];
3447
- }
3448
-
3449
- function isValidHover(event) {
3450
- return !(event.pointerType === "touch" || isDragActive());
3451
- }
3452
- /**
3453
- * Create a hover gesture. hover() is different to .addEventListener("pointerenter")
3454
- * in that it has an easier syntax, filters out polyfilled touch events, interoperates
3455
- * with drag gestures, and automatically removes the "pointerennd" event listener when the hover ends.
3456
- *
3457
- * @public
3458
- */
3459
- function hover(elementOrSelector, onHoverStart, options = {}) {
3460
- const [elements, eventOptions, cancel] = setupGesture(elementOrSelector, options);
3461
- const onPointerEnter = (enterEvent) => {
3462
- if (!isValidHover(enterEvent))
3463
- return;
3464
- const { target } = enterEvent;
3465
- const onHoverEnd = onHoverStart(target, enterEvent);
3466
- if (typeof onHoverEnd !== "function" || !target)
3467
- return;
3468
- const onPointerLeave = (leaveEvent) => {
3469
- if (!isValidHover(leaveEvent))
3470
- return;
3471
- onHoverEnd(leaveEvent);
3472
- target.removeEventListener("pointerleave", onPointerLeave);
3473
- };
3474
- target.addEventListener("pointerleave", onPointerLeave, eventOptions);
3475
- };
3476
- elements.forEach((element) => {
3477
- element.addEventListener("pointerenter", onPointerEnter, eventOptions);
3478
- });
3479
- return cancel;
3480
- }
3481
-
3482
- /**
3483
- * Recursively traverse up the tree to check whether the provided child node
3484
- * is the parent or a descendant of it.
3485
- *
3486
- * @param parent - Element to find
3487
- * @param child - Element to test against parent
3488
- */
3489
- const isNodeOrChild = (parent, child) => {
3490
- if (!child) {
3491
- return false;
3492
- }
3493
- else if (parent === child) {
3494
- return true;
3495
- }
3496
- else {
3497
- return isNodeOrChild(parent, child.parentElement);
3498
- }
3499
- };
3500
-
3501
- const isPrimaryPointer = (event) => {
3502
- if (event.pointerType === "mouse") {
3503
- return typeof event.button !== "number" || event.button <= 0;
3504
- }
3505
- else {
3506
- /**
3507
- * isPrimary is true for all mice buttons, whereas every touch point
3508
- * is regarded as its own input. So subsequent concurrent touch points
3509
- * will be false.
3510
- *
3511
- * Specifically match against false here as incomplete versions of
3512
- * PointerEvents in very old browser might have it set as undefined.
3513
- */
3514
- return event.isPrimary !== false;
3515
- }
3516
- };
3517
-
3518
- const focusableElements = new Set([
3519
- "BUTTON",
3520
- "INPUT",
3521
- "SELECT",
3522
- "TEXTAREA",
3523
- "A",
3524
- ]);
3525
- function isElementKeyboardAccessible(element) {
3526
- return (focusableElements.has(element.tagName) ||
3527
- element.tabIndex !== -1);
3528
- }
3529
-
3530
- const isPressing = new WeakSet();
3531
-
3532
- /**
3533
- * Filter out events that are not "Enter" keys.
3534
- */
3535
- function filterEvents(callback) {
3536
- return (event) => {
3537
- if (event.key !== "Enter")
3538
- return;
3539
- callback(event);
3540
- };
3541
- }
3542
- function firePointerEvent(target, type) {
3543
- target.dispatchEvent(new PointerEvent("pointer" + type, { isPrimary: true, bubbles: true }));
3544
- }
3545
- const enableKeyboardPress = (focusEvent, eventOptions) => {
3546
- const element = focusEvent.currentTarget;
3547
- if (!element)
3548
- return;
3549
- const handleKeydown = filterEvents(() => {
3550
- if (isPressing.has(element))
3551
- return;
3552
- firePointerEvent(element, "down");
3553
- const handleKeyup = filterEvents(() => {
3554
- firePointerEvent(element, "up");
3555
- });
3556
- const handleBlur = () => firePointerEvent(element, "cancel");
3557
- element.addEventListener("keyup", handleKeyup, eventOptions);
3558
- element.addEventListener("blur", handleBlur, eventOptions);
3559
- });
3560
- element.addEventListener("keydown", handleKeydown, eventOptions);
3561
- /**
3562
- * Add an event listener that fires on blur to remove the keydown events.
3563
- */
3564
- element.addEventListener("blur", () => element.removeEventListener("keydown", handleKeydown), eventOptions);
3565
- };
3566
-
3567
- /**
3568
- * Filter out events that are not primary pointer events, or are triggering
3569
- * while a Motion gesture is active.
3570
- */
3571
- function isValidPressEvent(event) {
3572
- return isPrimaryPointer(event) && !isDragActive();
3573
- }
3574
- /**
3575
- * Create a press gesture.
3576
- *
3577
- * Press is different to `"pointerdown"`, `"pointerup"` in that it
3578
- * automatically filters out secondary pointer events like right
3579
- * click and multitouch.
3580
- *
3581
- * It also adds accessibility support for keyboards, where
3582
- * an element with a press gesture will receive focus and
3583
- * trigger on Enter `"keydown"` and `"keyup"` events.
3584
- *
3585
- * This is different to a browser's `"click"` event, which does
3586
- * respond to keyboards but only for the `"click"` itself, rather
3587
- * than the press start and end/cancel. The element also needs
3588
- * to be focusable for this to work, whereas a press gesture will
3589
- * make an element focusable by default.
3590
- *
3591
- * @public
3592
- */
3593
- function press(targetOrSelector, onPressStart, options = {}) {
3594
- const [targets, eventOptions, cancelEvents] = setupGesture(targetOrSelector, options);
3595
- const startPress = (startEvent) => {
3596
- const target = startEvent.currentTarget;
3597
- if (!isValidPressEvent(startEvent) || isPressing.has(target))
3598
- return;
3599
- isPressing.add(target);
3600
- const onPressEnd = onPressStart(target, startEvent);
3601
- const onPointerEnd = (endEvent, success) => {
3602
- window.removeEventListener("pointerup", onPointerUp);
3603
- window.removeEventListener("pointercancel", onPointerCancel);
3604
- if (!isValidPressEvent(endEvent) || !isPressing.has(target)) {
3605
- return;
3606
- }
3607
- isPressing.delete(target);
3608
- if (typeof onPressEnd === "function") {
3609
- onPressEnd(endEvent, { success });
3610
- }
3611
- };
3612
- const onPointerUp = (upEvent) => {
3613
- onPointerEnd(upEvent, target === window ||
3614
- target === document ||
3615
- options.useGlobalTarget ||
3616
- isNodeOrChild(target, upEvent.target));
3617
- };
3618
- const onPointerCancel = (cancelEvent) => {
3619
- onPointerEnd(cancelEvent, false);
3620
- };
3621
- window.addEventListener("pointerup", onPointerUp, eventOptions);
3622
- window.addEventListener("pointercancel", onPointerCancel, eventOptions);
3623
- };
3624
- targets.forEach((target) => {
3625
- const pointerDownTarget = options.useGlobalTarget ? window : target;
3626
- pointerDownTarget.addEventListener("pointerdown", startPress, eventOptions);
3627
- if (target instanceof HTMLElement) {
3628
- target.addEventListener("focus", (event) => enableKeyboardPress(event, eventOptions));
3629
- if (!isElementKeyboardAccessible(target) &&
3630
- !target.hasAttribute("tabindex")) {
3631
- target.tabIndex = 0;
3632
- }
3633
- }
3634
- });
3635
- return cancelEvents;
3636
- }
3637
-
3638
- function getComputedStyle$2(element, name) {
3639
- const computedStyle = window.getComputedStyle(element);
3640
- return isCSSVar(name)
3641
- ? computedStyle.getPropertyValue(name)
3642
- : computedStyle[name];
3643
- }
3644
-
3645
- function observeTimeline(update, timeline) {
3646
- let prevProgress;
3647
- const onFrame = () => {
3648
- const { currentTime } = timeline;
3649
- const percentage = currentTime === null ? 0 : currentTime.value;
3650
- const progress = percentage / 100;
3651
- if (prevProgress !== progress) {
3652
- update(progress);
3653
- }
3654
- prevProgress = progress;
3655
- };
3656
- frame.preUpdate(onFrame, true);
3657
- return () => cancelFrame(onFrame);
3658
- }
3659
-
3660
- function record() {
3661
- const { value } = statsBuffer;
3662
- if (value === null) {
3663
- cancelFrame(record);
3664
- return;
3665
- }
3666
- value.frameloop.rate.push(frameData.delta);
3667
- value.animations.mainThread.push(activeAnimations.mainThread);
3668
- value.animations.waapi.push(activeAnimations.waapi);
3669
- value.animations.layout.push(activeAnimations.layout);
3670
- }
3671
- function mean(values) {
3672
- return values.reduce((acc, value) => acc + value, 0) / values.length;
3673
- }
3674
- function summarise(values, calcAverage = mean) {
3675
- if (values.length === 0) {
3676
- return {
3677
- min: 0,
3678
- max: 0,
3679
- avg: 0,
3680
- };
3681
- }
3682
- return {
3683
- min: Math.min(...values),
3684
- max: Math.max(...values),
3685
- avg: calcAverage(values),
3686
- };
3687
- }
3688
- const msToFps = (ms) => Math.round(1000 / ms);
3689
- function clearStatsBuffer() {
3690
- statsBuffer.value = null;
3691
- statsBuffer.addProjectionMetrics = null;
3692
- }
3693
- function reportStats() {
3694
- const { value } = statsBuffer;
3695
- if (!value) {
3696
- throw new Error("Stats are not being measured");
3697
- }
3698
- clearStatsBuffer();
3699
- cancelFrame(record);
3700
- const summary = {
3701
- frameloop: {
3702
- setup: summarise(value.frameloop.setup),
3703
- rate: summarise(value.frameloop.rate),
3704
- read: summarise(value.frameloop.read),
3705
- resolveKeyframes: summarise(value.frameloop.resolveKeyframes),
3706
- preUpdate: summarise(value.frameloop.preUpdate),
3707
- update: summarise(value.frameloop.update),
3708
- preRender: summarise(value.frameloop.preRender),
3709
- render: summarise(value.frameloop.render),
3710
- postRender: summarise(value.frameloop.postRender),
3711
- },
3712
- animations: {
3713
- mainThread: summarise(value.animations.mainThread),
3714
- waapi: summarise(value.animations.waapi),
3715
- layout: summarise(value.animations.layout),
3716
- },
3717
- layoutProjection: {
3718
- nodes: summarise(value.layoutProjection.nodes),
3719
- calculatedTargetDeltas: summarise(value.layoutProjection.calculatedTargetDeltas),
3720
- calculatedProjections: summarise(value.layoutProjection.calculatedProjections),
3721
- },
3722
- };
3723
- /**
3724
- * Convert the rate to FPS
3725
- */
3726
- const { rate } = summary.frameloop;
3727
- rate.min = msToFps(rate.min);
3728
- rate.max = msToFps(rate.max);
3729
- rate.avg = msToFps(rate.avg);
3730
- [rate.min, rate.max] = [rate.max, rate.min];
3731
- return summary;
3732
- }
3733
- function recordStats() {
3734
- if (statsBuffer.value) {
3735
- clearStatsBuffer();
3736
- throw new Error("Stats are already being measured");
3737
- }
3738
- const newStatsBuffer = statsBuffer;
3739
- newStatsBuffer.value = {
3740
- frameloop: {
3741
- setup: [],
3742
- rate: [],
3743
- read: [],
3744
- resolveKeyframes: [],
3745
- preUpdate: [],
3746
- update: [],
3747
- preRender: [],
3748
- render: [],
3749
- postRender: [],
3750
- },
3751
- animations: {
3752
- mainThread: [],
3753
- waapi: [],
3754
- layout: [],
3755
- },
3756
- layoutProjection: {
3757
- nodes: [],
3758
- calculatedTargetDeltas: [],
3759
- calculatedProjections: [],
3760
- },
3761
- };
3762
- newStatsBuffer.addProjectionMetrics = (metrics) => {
3763
- const { layoutProjection } = newStatsBuffer.value;
3764
- layoutProjection.nodes.push(metrics.nodes);
3765
- layoutProjection.calculatedTargetDeltas.push(metrics.calculatedTargetDeltas);
3766
- layoutProjection.calculatedProjections.push(metrics.calculatedProjections);
3767
- };
3768
- frame.postRender(record, true);
3769
- return reportStats;
3770
- }
3771
-
3772
- function transform(...args) {
3773
- const useImmediate = !Array.isArray(args[0]);
3774
- const argOffset = useImmediate ? 0 : -1;
3775
- const inputValue = args[0 + argOffset];
3776
- const inputRange = args[1 + argOffset];
3777
- const outputRange = args[2 + argOffset];
3778
- const options = args[3 + argOffset];
3779
- const interpolator = interpolate(inputRange, outputRange, options);
3780
- return useImmediate ? interpolator(inputValue) : interpolator;
3781
- }
3782
-
3783
3369
  /**
3784
3370
  * Maximum time between the value of two frames, beyond which we
3785
3371
  * assume the velocity has since been 0.
@@ -3798,293 +3384,820 @@ const collectMotionValues = {
3798
3384
  */
3799
3385
  class MotionValue {
3800
3386
  /**
3801
- * @param init - The initiating value
3802
- * @param config - Optional configuration options
3387
+ * @param init - The initiating value
3388
+ * @param config - Optional configuration options
3389
+ *
3390
+ * - `transformer`: A function to transform incoming values with.
3391
+ */
3392
+ constructor(init, options = {}) {
3393
+ /**
3394
+ * This will be replaced by the build step with the latest version number.
3395
+ * When MotionValues are provided to motion components, warn if versions are mixed.
3396
+ */
3397
+ this.version = "__VERSION__";
3398
+ /**
3399
+ * Tracks whether this value can output a velocity. Currently this is only true
3400
+ * if the value is numerical, but we might be able to widen the scope here and support
3401
+ * other value types.
3402
+ *
3403
+ * @internal
3404
+ */
3405
+ this.canTrackVelocity = null;
3406
+ /**
3407
+ * An object containing a SubscriptionManager for each active event.
3408
+ */
3409
+ this.events = {};
3410
+ this.updateAndNotify = (v, render = true) => {
3411
+ const currentTime = time.now();
3412
+ /**
3413
+ * If we're updating the value during another frame or eventloop
3414
+ * than the previous frame, then the we set the previous frame value
3415
+ * to current.
3416
+ */
3417
+ if (this.updatedAt !== currentTime) {
3418
+ this.setPrevFrameValue();
3419
+ }
3420
+ this.prev = this.current;
3421
+ this.setCurrent(v);
3422
+ // Update update subscribers
3423
+ if (this.current !== this.prev) {
3424
+ this.events.change?.notify(this.current);
3425
+ if (this.dependents) {
3426
+ for (const dependent of this.dependents) {
3427
+ dependent.dirty();
3428
+ }
3429
+ }
3430
+ }
3431
+ // Update render subscribers
3432
+ if (render) {
3433
+ this.events.renderRequest?.notify(this.current);
3434
+ }
3435
+ };
3436
+ this.hasAnimated = false;
3437
+ this.setCurrent(init);
3438
+ this.owner = options.owner;
3439
+ }
3440
+ setCurrent(current) {
3441
+ this.current = current;
3442
+ this.updatedAt = time.now();
3443
+ if (this.canTrackVelocity === null && current !== undefined) {
3444
+ this.canTrackVelocity = isFloat(this.current);
3445
+ }
3446
+ }
3447
+ setPrevFrameValue(prevFrameValue = this.current) {
3448
+ this.prevFrameValue = prevFrameValue;
3449
+ this.prevUpdatedAt = this.updatedAt;
3450
+ }
3451
+ /**
3452
+ * Adds a function that will be notified when the `MotionValue` is updated.
3453
+ *
3454
+ * It returns a function that, when called, will cancel the subscription.
3455
+ *
3456
+ * When calling `onChange` inside a React component, it should be wrapped with the
3457
+ * `useEffect` hook. As it returns an unsubscribe function, this should be returned
3458
+ * from the `useEffect` function to ensure you don't add duplicate subscribers..
3459
+ *
3460
+ * ```jsx
3461
+ * export const MyComponent = () => {
3462
+ * const x = useMotionValue(0)
3463
+ * const y = useMotionValue(0)
3464
+ * const opacity = useMotionValue(1)
3465
+ *
3466
+ * useEffect(() => {
3467
+ * function updateOpacity() {
3468
+ * const maxXY = Math.max(x.get(), y.get())
3469
+ * const newOpacity = transform(maxXY, [0, 100], [1, 0])
3470
+ * opacity.set(newOpacity)
3471
+ * }
3472
+ *
3473
+ * const unsubscribeX = x.on("change", updateOpacity)
3474
+ * const unsubscribeY = y.on("change", updateOpacity)
3475
+ *
3476
+ * return () => {
3477
+ * unsubscribeX()
3478
+ * unsubscribeY()
3479
+ * }
3480
+ * }, [])
3481
+ *
3482
+ * return <motion.div style={{ x }} />
3483
+ * }
3484
+ * ```
3485
+ *
3486
+ * @param subscriber - A function that receives the latest value.
3487
+ * @returns A function that, when called, will cancel this subscription.
3488
+ *
3489
+ * @deprecated
3490
+ */
3491
+ onChange(subscription) {
3492
+ if (process.env.NODE_ENV !== "production") {
3493
+ warnOnce(false, `value.onChange(callback) is deprecated. Switch to value.on("change", callback).`);
3494
+ }
3495
+ return this.on("change", subscription);
3496
+ }
3497
+ on(eventName, callback) {
3498
+ if (!this.events[eventName]) {
3499
+ this.events[eventName] = new SubscriptionManager();
3500
+ }
3501
+ const unsubscribe = this.events[eventName].add(callback);
3502
+ if (eventName === "change") {
3503
+ return () => {
3504
+ unsubscribe();
3505
+ /**
3506
+ * If we have no more change listeners by the start
3507
+ * of the next frame, stop active animations.
3508
+ */
3509
+ frame.read(() => {
3510
+ if (!this.events.change.getSize()) {
3511
+ this.stop();
3512
+ }
3513
+ });
3514
+ };
3515
+ }
3516
+ return unsubscribe;
3517
+ }
3518
+ clearListeners() {
3519
+ for (const eventManagers in this.events) {
3520
+ this.events[eventManagers].clear();
3521
+ }
3522
+ }
3523
+ /**
3524
+ * Attaches a passive effect to the `MotionValue`.
3525
+ */
3526
+ attach(passiveEffect, stopPassiveEffect) {
3527
+ this.passiveEffect = passiveEffect;
3528
+ this.stopPassiveEffect = stopPassiveEffect;
3529
+ }
3530
+ /**
3531
+ * Sets the state of the `MotionValue`.
3532
+ *
3533
+ * @remarks
3534
+ *
3535
+ * ```jsx
3536
+ * const x = useMotionValue(0)
3537
+ * x.set(10)
3538
+ * ```
3539
+ *
3540
+ * @param latest - Latest value to set.
3541
+ * @param render - Whether to notify render subscribers. Defaults to `true`
3542
+ *
3543
+ * @public
3544
+ */
3545
+ set(v, render = true) {
3546
+ if (!render || !this.passiveEffect) {
3547
+ this.updateAndNotify(v, render);
3548
+ }
3549
+ else {
3550
+ this.passiveEffect(v, this.updateAndNotify);
3551
+ }
3552
+ }
3553
+ setWithVelocity(prev, current, delta) {
3554
+ this.set(current);
3555
+ this.prev = undefined;
3556
+ this.prevFrameValue = prev;
3557
+ this.prevUpdatedAt = this.updatedAt - delta;
3558
+ }
3559
+ /**
3560
+ * Set the state of the `MotionValue`, stopping any active animations,
3561
+ * effects, and resets velocity to `0`.
3562
+ */
3563
+ jump(v, endAnimation = true) {
3564
+ this.updateAndNotify(v);
3565
+ this.prev = v;
3566
+ this.prevUpdatedAt = this.prevFrameValue = undefined;
3567
+ endAnimation && this.stop();
3568
+ if (this.stopPassiveEffect)
3569
+ this.stopPassiveEffect();
3570
+ }
3571
+ dirty() {
3572
+ this.events.change?.notify(this.current);
3573
+ }
3574
+ addDependent(dependent) {
3575
+ if (!this.dependents) {
3576
+ this.dependents = new Set();
3577
+ }
3578
+ this.dependents.add(dependent);
3579
+ }
3580
+ removeDependent(dependent) {
3581
+ if (this.dependents) {
3582
+ this.dependents.delete(dependent);
3583
+ }
3584
+ }
3585
+ /**
3586
+ * Returns the latest state of `MotionValue`
3803
3587
  *
3804
- * - `transformer`: A function to transform incoming values with.
3588
+ * @returns - The latest state of `MotionValue`
3589
+ *
3590
+ * @public
3805
3591
  */
3806
- constructor(init, options = {}) {
3807
- /**
3808
- * This will be replaced by the build step with the latest version number.
3809
- * When MotionValues are provided to motion components, warn if versions are mixed.
3810
- */
3811
- this.version = "__VERSION__";
3812
- /**
3813
- * Tracks whether this value can output a velocity. Currently this is only true
3814
- * if the value is numerical, but we might be able to widen the scope here and support
3815
- * other value types.
3816
- *
3817
- * @internal
3818
- */
3819
- this.canTrackVelocity = null;
3820
- /**
3821
- * An object containing a SubscriptionManager for each active event.
3822
- */
3823
- this.events = {};
3824
- this.updateAndNotify = (v, render = true) => {
3825
- const currentTime = time.now();
3826
- /**
3827
- * If we're updating the value during another frame or eventloop
3828
- * than the previous frame, then the we set the previous frame value
3829
- * to current.
3830
- */
3831
- if (this.updatedAt !== currentTime) {
3832
- this.setPrevFrameValue();
3833
- }
3834
- this.prev = this.current;
3835
- this.setCurrent(v);
3836
- // Update update subscribers
3837
- if (this.current !== this.prev) {
3838
- this.events.change?.notify(this.current);
3839
- }
3840
- // Update render subscribers
3841
- if (render) {
3842
- this.events.renderRequest?.notify(this.current);
3843
- }
3844
- };
3845
- this.hasAnimated = false;
3846
- this.setCurrent(init);
3847
- this.owner = options.owner;
3848
- }
3849
- setCurrent(current) {
3850
- this.current = current;
3851
- this.updatedAt = time.now();
3852
- if (this.canTrackVelocity === null && current !== undefined) {
3853
- this.canTrackVelocity = isFloat(this.current);
3592
+ get() {
3593
+ if (collectMotionValues.current) {
3594
+ collectMotionValues.current.push(this);
3854
3595
  }
3596
+ return this.current;
3855
3597
  }
3856
- setPrevFrameValue(prevFrameValue = this.current) {
3857
- this.prevFrameValue = prevFrameValue;
3858
- this.prevUpdatedAt = this.updatedAt;
3598
+ /**
3599
+ * @public
3600
+ */
3601
+ getPrevious() {
3602
+ return this.prev;
3859
3603
  }
3860
3604
  /**
3861
- * Adds a function that will be notified when the `MotionValue` is updated.
3605
+ * Returns the latest velocity of `MotionValue`
3862
3606
  *
3863
- * It returns a function that, when called, will cancel the subscription.
3607
+ * @returns - The latest velocity of `MotionValue`. Returns `0` if the state is non-numerical.
3864
3608
  *
3865
- * When calling `onChange` inside a React component, it should be wrapped with the
3866
- * `useEffect` hook. As it returns an unsubscribe function, this should be returned
3867
- * from the `useEffect` function to ensure you don't add duplicate subscribers..
3609
+ * @public
3610
+ */
3611
+ getVelocity() {
3612
+ const currentTime = time.now();
3613
+ if (!this.canTrackVelocity ||
3614
+ this.prevFrameValue === undefined ||
3615
+ currentTime - this.updatedAt > MAX_VELOCITY_DELTA) {
3616
+ return 0;
3617
+ }
3618
+ const delta = Math.min(this.updatedAt - this.prevUpdatedAt, MAX_VELOCITY_DELTA);
3619
+ // Casts because of parseFloat's poor typing
3620
+ return velocityPerSecond(parseFloat(this.current) -
3621
+ parseFloat(this.prevFrameValue), delta);
3622
+ }
3623
+ /**
3624
+ * Registers a new animation to control this `MotionValue`. Only one
3625
+ * animation can drive a `MotionValue` at one time.
3868
3626
  *
3869
3627
  * ```jsx
3870
- * export const MyComponent = () => {
3871
- * const x = useMotionValue(0)
3872
- * const y = useMotionValue(0)
3873
- * const opacity = useMotionValue(1)
3874
- *
3875
- * useEffect(() => {
3876
- * function updateOpacity() {
3877
- * const maxXY = Math.max(x.get(), y.get())
3878
- * const newOpacity = transform(maxXY, [0, 100], [1, 0])
3879
- * opacity.set(newOpacity)
3880
- * }
3628
+ * value.start()
3629
+ * ```
3881
3630
  *
3882
- * const unsubscribeX = x.on("change", updateOpacity)
3883
- * const unsubscribeY = y.on("change", updateOpacity)
3631
+ * @param animation - A function that starts the provided animation
3632
+ */
3633
+ start(startAnimation) {
3634
+ this.stop();
3635
+ return new Promise((resolve) => {
3636
+ this.hasAnimated = true;
3637
+ this.animation = startAnimation(resolve);
3638
+ if (this.events.animationStart) {
3639
+ this.events.animationStart.notify();
3640
+ }
3641
+ }).then(() => {
3642
+ if (this.events.animationComplete) {
3643
+ this.events.animationComplete.notify();
3644
+ }
3645
+ this.clearAnimation();
3646
+ });
3647
+ }
3648
+ /**
3649
+ * Stop the currently active animation.
3884
3650
  *
3885
- * return () => {
3886
- * unsubscribeX()
3887
- * unsubscribeY()
3888
- * }
3889
- * }, [])
3651
+ * @public
3652
+ */
3653
+ stop() {
3654
+ if (this.animation) {
3655
+ this.animation.stop();
3656
+ if (this.events.animationCancel) {
3657
+ this.events.animationCancel.notify();
3658
+ }
3659
+ }
3660
+ this.clearAnimation();
3661
+ }
3662
+ /**
3663
+ * Returns `true` if this value is currently animating.
3890
3664
  *
3891
- * return <motion.div style={{ x }} />
3892
- * }
3893
- * ```
3665
+ * @public
3666
+ */
3667
+ isAnimating() {
3668
+ return !!this.animation;
3669
+ }
3670
+ clearAnimation() {
3671
+ delete this.animation;
3672
+ }
3673
+ /**
3674
+ * Destroy and clean up subscribers to this `MotionValue`.
3894
3675
  *
3895
- * @param subscriber - A function that receives the latest value.
3896
- * @returns A function that, when called, will cancel this subscription.
3676
+ * The `MotionValue` hooks like `useMotionValue` and `useTransform` automatically
3677
+ * handle the lifecycle of the returned `MotionValue`, so this method is only necessary if you've manually
3678
+ * created a `MotionValue` via the `motionValue` function.
3897
3679
  *
3898
- * @deprecated
3680
+ * @public
3899
3681
  */
3900
- onChange(subscription) {
3901
- if (process.env.NODE_ENV !== "production") {
3902
- warnOnce(false, `value.onChange(callback) is deprecated. Switch to value.on("change", callback).`);
3682
+ destroy() {
3683
+ this.dependents?.clear();
3684
+ this.events.destroy?.notify();
3685
+ this.clearListeners();
3686
+ this.stop();
3687
+ if (this.stopPassiveEffect) {
3688
+ this.stopPassiveEffect();
3903
3689
  }
3904
- return this.on("change", subscription);
3905
3690
  }
3906
- on(eventName, callback) {
3907
- if (!this.events[eventName]) {
3908
- this.events[eventName] = new SubscriptionManager();
3691
+ }
3692
+ function motionValue(init, options) {
3693
+ return new MotionValue(init, options);
3694
+ }
3695
+
3696
+ /**
3697
+ * Provided a value and a ValueType, returns the value as that value type.
3698
+ */
3699
+ const getValueAsType = (value, type) => {
3700
+ return type && typeof value === "number"
3701
+ ? type.transform(value)
3702
+ : value;
3703
+ };
3704
+
3705
+ class MotionValueState {
3706
+ constructor() {
3707
+ this.latest = {};
3708
+ this.values = new Map();
3709
+ }
3710
+ set(name, value, render, computed) {
3711
+ const existingValue = this.values.get(name);
3712
+ if (existingValue) {
3713
+ existingValue.onRemove();
3714
+ }
3715
+ const onChange = () => {
3716
+ this.latest[name] = getValueAsType(value.get(), numberValueTypes[name]);
3717
+ render && frame.render(render);
3718
+ };
3719
+ onChange();
3720
+ const cancelOnChange = value.on("change", onChange);
3721
+ computed && value.addDependent(computed);
3722
+ const remove = () => {
3723
+ cancelOnChange();
3724
+ render && cancelFrame(render);
3725
+ this.values.delete(name);
3726
+ computed && value.removeDependent(computed);
3727
+ };
3728
+ this.values.set(name, { value, onRemove: remove });
3729
+ return remove;
3730
+ }
3731
+ get(name) {
3732
+ return this.values.get(name)?.value;
3733
+ }
3734
+ destroy() {
3735
+ for (const value of this.values.values()) {
3736
+ value.onRemove();
3737
+ }
3738
+ }
3739
+ }
3740
+
3741
+ const translateAlias$1 = {
3742
+ x: "translateX",
3743
+ y: "translateY",
3744
+ z: "translateZ",
3745
+ transformPerspective: "perspective",
3746
+ };
3747
+ function buildTransform$1(state) {
3748
+ let transform = "";
3749
+ let transformIsDefault = true;
3750
+ /**
3751
+ * Loop over all possible transforms in order, adding the ones that
3752
+ * are present to the transform string.
3753
+ */
3754
+ for (let i = 0; i < transformPropOrder.length; i++) {
3755
+ const key = transformPropOrder[i];
3756
+ const value = state.latest[key];
3757
+ if (value === undefined)
3758
+ continue;
3759
+ let valueIsDefault = true;
3760
+ if (typeof value === "number") {
3761
+ valueIsDefault = value === (key.startsWith("scale") ? 1 : 0);
3762
+ }
3763
+ else {
3764
+ valueIsDefault = parseFloat(value) === 0;
3765
+ }
3766
+ if (!valueIsDefault) {
3767
+ transformIsDefault = false;
3768
+ const transformName = translateAlias$1[key] || key;
3769
+ const valueToRender = state.latest[key];
3770
+ transform += `${transformName}(${valueToRender}) `;
3771
+ }
3772
+ }
3773
+ return transformIsDefault ? "none" : transform.trim();
3774
+ }
3775
+
3776
+ const stateMap = new WeakMap();
3777
+ function styleEffect(subject, values) {
3778
+ const elements = resolveElements(subject);
3779
+ const subscriptions = [];
3780
+ for (let i = 0; i < elements.length; i++) {
3781
+ const element = elements[i];
3782
+ const state = stateMap.get(element) ?? new MotionValueState();
3783
+ stateMap.set(element, state);
3784
+ for (const key in values) {
3785
+ const value = values[key];
3786
+ const remove = addValue(element, state, key, value);
3787
+ subscriptions.push(remove);
3788
+ }
3789
+ }
3790
+ return () => {
3791
+ for (const cancel of subscriptions)
3792
+ cancel();
3793
+ };
3794
+ }
3795
+ function addValue(element, state, key, value) {
3796
+ let render = undefined;
3797
+ let computed = undefined;
3798
+ if (transformProps.has(key)) {
3799
+ if (!state.get("transform")) {
3800
+ state.set("transform", new MotionValue("none"), () => {
3801
+ element.style.transform = buildTransform$1(state);
3802
+ });
3803
+ }
3804
+ computed = state.get("transform");
3805
+ }
3806
+ else if (isCSSVar(key)) {
3807
+ render = () => {
3808
+ element.style.setProperty(key, state.latest[key]);
3809
+ };
3810
+ }
3811
+ else {
3812
+ render = () => {
3813
+ element.style[key] = state.latest[key];
3814
+ };
3815
+ }
3816
+ return state.set(key, value, render, computed);
3817
+ }
3818
+
3819
+ const { schedule: microtask, cancel: cancelMicrotask } =
3820
+ /* @__PURE__ */ createRenderBatcher(queueMicrotask, false);
3821
+
3822
+ const isDragging = {
3823
+ x: false,
3824
+ y: false,
3825
+ };
3826
+ function isDragActive() {
3827
+ return isDragging.x || isDragging.y;
3828
+ }
3829
+
3830
+ function setDragLock(axis) {
3831
+ if (axis === "x" || axis === "y") {
3832
+ if (isDragging[axis]) {
3833
+ return null;
3909
3834
  }
3910
- const unsubscribe = this.events[eventName].add(callback);
3911
- if (eventName === "change") {
3835
+ else {
3836
+ isDragging[axis] = true;
3912
3837
  return () => {
3913
- unsubscribe();
3914
- /**
3915
- * If we have no more change listeners by the start
3916
- * of the next frame, stop active animations.
3917
- */
3918
- frame.read(() => {
3919
- if (!this.events.change.getSize()) {
3920
- this.stop();
3921
- }
3922
- });
3838
+ isDragging[axis] = false;
3923
3839
  };
3924
3840
  }
3925
- return unsubscribe;
3926
- }
3927
- clearListeners() {
3928
- for (const eventManagers in this.events) {
3929
- this.events[eventManagers].clear();
3930
- }
3931
- }
3932
- /**
3933
- * Attaches a passive effect to the `MotionValue`.
3934
- */
3935
- attach(passiveEffect, stopPassiveEffect) {
3936
- this.passiveEffect = passiveEffect;
3937
- this.stopPassiveEffect = stopPassiveEffect;
3938
3841
  }
3939
- /**
3940
- * Sets the state of the `MotionValue`.
3941
- *
3942
- * @remarks
3943
- *
3944
- * ```jsx
3945
- * const x = useMotionValue(0)
3946
- * x.set(10)
3947
- * ```
3948
- *
3949
- * @param latest - Latest value to set.
3950
- * @param render - Whether to notify render subscribers. Defaults to `true`
3951
- *
3952
- * @public
3953
- */
3954
- set(v, render = true) {
3955
- if (!render || !this.passiveEffect) {
3956
- this.updateAndNotify(v, render);
3842
+ else {
3843
+ if (isDragging.x || isDragging.y) {
3844
+ return null;
3957
3845
  }
3958
3846
  else {
3959
- this.passiveEffect(v, this.updateAndNotify);
3847
+ isDragging.x = isDragging.y = true;
3848
+ return () => {
3849
+ isDragging.x = isDragging.y = false;
3850
+ };
3960
3851
  }
3961
3852
  }
3962
- setWithVelocity(prev, current, delta) {
3963
- this.set(current);
3964
- this.prev = undefined;
3965
- this.prevFrameValue = prev;
3966
- this.prevUpdatedAt = this.updatedAt - delta;
3853
+ }
3854
+
3855
+ function setupGesture(elementOrSelector, options) {
3856
+ const elements = resolveElements(elementOrSelector);
3857
+ const gestureAbortController = new AbortController();
3858
+ const eventOptions = {
3859
+ passive: true,
3860
+ ...options,
3861
+ signal: gestureAbortController.signal,
3862
+ };
3863
+ const cancel = () => gestureAbortController.abort();
3864
+ return [elements, eventOptions, cancel];
3865
+ }
3866
+
3867
+ function isValidHover(event) {
3868
+ return !(event.pointerType === "touch" || isDragActive());
3869
+ }
3870
+ /**
3871
+ * Create a hover gesture. hover() is different to .addEventListener("pointerenter")
3872
+ * in that it has an easier syntax, filters out polyfilled touch events, interoperates
3873
+ * with drag gestures, and automatically removes the "pointerennd" event listener when the hover ends.
3874
+ *
3875
+ * @public
3876
+ */
3877
+ function hover(elementOrSelector, onHoverStart, options = {}) {
3878
+ const [elements, eventOptions, cancel] = setupGesture(elementOrSelector, options);
3879
+ const onPointerEnter = (enterEvent) => {
3880
+ if (!isValidHover(enterEvent))
3881
+ return;
3882
+ const { target } = enterEvent;
3883
+ const onHoverEnd = onHoverStart(target, enterEvent);
3884
+ if (typeof onHoverEnd !== "function" || !target)
3885
+ return;
3886
+ const onPointerLeave = (leaveEvent) => {
3887
+ if (!isValidHover(leaveEvent))
3888
+ return;
3889
+ onHoverEnd(leaveEvent);
3890
+ target.removeEventListener("pointerleave", onPointerLeave);
3891
+ };
3892
+ target.addEventListener("pointerleave", onPointerLeave, eventOptions);
3893
+ };
3894
+ elements.forEach((element) => {
3895
+ element.addEventListener("pointerenter", onPointerEnter, eventOptions);
3896
+ });
3897
+ return cancel;
3898
+ }
3899
+
3900
+ /**
3901
+ * Recursively traverse up the tree to check whether the provided child node
3902
+ * is the parent or a descendant of it.
3903
+ *
3904
+ * @param parent - Element to find
3905
+ * @param child - Element to test against parent
3906
+ */
3907
+ const isNodeOrChild = (parent, child) => {
3908
+ if (!child) {
3909
+ return false;
3967
3910
  }
3968
- /**
3969
- * Set the state of the `MotionValue`, stopping any active animations,
3970
- * effects, and resets velocity to `0`.
3971
- */
3972
- jump(v, endAnimation = true) {
3973
- this.updateAndNotify(v);
3974
- this.prev = v;
3975
- this.prevUpdatedAt = this.prevFrameValue = undefined;
3976
- endAnimation && this.stop();
3977
- if (this.stopPassiveEffect)
3978
- this.stopPassiveEffect();
3911
+ else if (parent === child) {
3912
+ return true;
3979
3913
  }
3980
- /**
3981
- * Returns the latest state of `MotionValue`
3982
- *
3983
- * @returns - The latest state of `MotionValue`
3984
- *
3985
- * @public
3986
- */
3987
- get() {
3988
- if (collectMotionValues.current) {
3989
- collectMotionValues.current.push(this);
3990
- }
3991
- return this.current;
3914
+ else {
3915
+ return isNodeOrChild(parent, child.parentElement);
3992
3916
  }
3993
- /**
3994
- * @public
3995
- */
3996
- getPrevious() {
3997
- return this.prev;
3917
+ };
3918
+
3919
+ const isPrimaryPointer = (event) => {
3920
+ if (event.pointerType === "mouse") {
3921
+ return typeof event.button !== "number" || event.button <= 0;
3998
3922
  }
3999
- /**
4000
- * Returns the latest velocity of `MotionValue`
4001
- *
4002
- * @returns - The latest velocity of `MotionValue`. Returns `0` if the state is non-numerical.
4003
- *
4004
- * @public
4005
- */
4006
- getVelocity() {
4007
- const currentTime = time.now();
4008
- if (!this.canTrackVelocity ||
4009
- this.prevFrameValue === undefined ||
4010
- currentTime - this.updatedAt > MAX_VELOCITY_DELTA) {
4011
- return 0;
4012
- }
4013
- const delta = Math.min(this.updatedAt - this.prevUpdatedAt, MAX_VELOCITY_DELTA);
4014
- // Casts because of parseFloat's poor typing
4015
- return velocityPerSecond(parseFloat(this.current) -
4016
- parseFloat(this.prevFrameValue), delta);
3923
+ else {
3924
+ /**
3925
+ * isPrimary is true for all mice buttons, whereas every touch point
3926
+ * is regarded as its own input. So subsequent concurrent touch points
3927
+ * will be false.
3928
+ *
3929
+ * Specifically match against false here as incomplete versions of
3930
+ * PointerEvents in very old browser might have it set as undefined.
3931
+ */
3932
+ return event.isPrimary !== false;
4017
3933
  }
4018
- /**
4019
- * Registers a new animation to control this `MotionValue`. Only one
4020
- * animation can drive a `MotionValue` at one time.
4021
- *
4022
- * ```jsx
4023
- * value.start()
4024
- * ```
4025
- *
4026
- * @param animation - A function that starts the provided animation
4027
- */
4028
- start(startAnimation) {
4029
- this.stop();
4030
- return new Promise((resolve) => {
4031
- this.hasAnimated = true;
4032
- this.animation = startAnimation(resolve);
4033
- if (this.events.animationStart) {
4034
- this.events.animationStart.notify();
4035
- }
4036
- }).then(() => {
4037
- if (this.events.animationComplete) {
4038
- this.events.animationComplete.notify();
4039
- }
4040
- this.clearAnimation();
3934
+ };
3935
+
3936
+ const focusableElements = new Set([
3937
+ "BUTTON",
3938
+ "INPUT",
3939
+ "SELECT",
3940
+ "TEXTAREA",
3941
+ "A",
3942
+ ]);
3943
+ function isElementKeyboardAccessible(element) {
3944
+ return (focusableElements.has(element.tagName) ||
3945
+ element.tabIndex !== -1);
3946
+ }
3947
+
3948
+ const isPressing = new WeakSet();
3949
+
3950
+ /**
3951
+ * Filter out events that are not "Enter" keys.
3952
+ */
3953
+ function filterEvents(callback) {
3954
+ return (event) => {
3955
+ if (event.key !== "Enter")
3956
+ return;
3957
+ callback(event);
3958
+ };
3959
+ }
3960
+ function firePointerEvent(target, type) {
3961
+ target.dispatchEvent(new PointerEvent("pointer" + type, { isPrimary: true, bubbles: true }));
3962
+ }
3963
+ const enableKeyboardPress = (focusEvent, eventOptions) => {
3964
+ const element = focusEvent.currentTarget;
3965
+ if (!element)
3966
+ return;
3967
+ const handleKeydown = filterEvents(() => {
3968
+ if (isPressing.has(element))
3969
+ return;
3970
+ firePointerEvent(element, "down");
3971
+ const handleKeyup = filterEvents(() => {
3972
+ firePointerEvent(element, "up");
4041
3973
  });
4042
- }
3974
+ const handleBlur = () => firePointerEvent(element, "cancel");
3975
+ element.addEventListener("keyup", handleKeyup, eventOptions);
3976
+ element.addEventListener("blur", handleBlur, eventOptions);
3977
+ });
3978
+ element.addEventListener("keydown", handleKeydown, eventOptions);
4043
3979
  /**
4044
- * Stop the currently active animation.
4045
- *
4046
- * @public
3980
+ * Add an event listener that fires on blur to remove the keydown events.
4047
3981
  */
4048
- stop() {
4049
- if (this.animation) {
4050
- this.animation.stop();
4051
- if (this.events.animationCancel) {
4052
- this.events.animationCancel.notify();
3982
+ element.addEventListener("blur", () => element.removeEventListener("keydown", handleKeydown), eventOptions);
3983
+ };
3984
+
3985
+ /**
3986
+ * Filter out events that are not primary pointer events, or are triggering
3987
+ * while a Motion gesture is active.
3988
+ */
3989
+ function isValidPressEvent(event) {
3990
+ return isPrimaryPointer(event) && !isDragActive();
3991
+ }
3992
+ /**
3993
+ * Create a press gesture.
3994
+ *
3995
+ * Press is different to `"pointerdown"`, `"pointerup"` in that it
3996
+ * automatically filters out secondary pointer events like right
3997
+ * click and multitouch.
3998
+ *
3999
+ * It also adds accessibility support for keyboards, where
4000
+ * an element with a press gesture will receive focus and
4001
+ * trigger on Enter `"keydown"` and `"keyup"` events.
4002
+ *
4003
+ * This is different to a browser's `"click"` event, which does
4004
+ * respond to keyboards but only for the `"click"` itself, rather
4005
+ * than the press start and end/cancel. The element also needs
4006
+ * to be focusable for this to work, whereas a press gesture will
4007
+ * make an element focusable by default.
4008
+ *
4009
+ * @public
4010
+ */
4011
+ function press(targetOrSelector, onPressStart, options = {}) {
4012
+ const [targets, eventOptions, cancelEvents] = setupGesture(targetOrSelector, options);
4013
+ const startPress = (startEvent) => {
4014
+ const target = startEvent.currentTarget;
4015
+ if (!isValidPressEvent(startEvent) || isPressing.has(target))
4016
+ return;
4017
+ isPressing.add(target);
4018
+ const onPressEnd = onPressStart(target, startEvent);
4019
+ const onPointerEnd = (endEvent, success) => {
4020
+ window.removeEventListener("pointerup", onPointerUp);
4021
+ window.removeEventListener("pointercancel", onPointerCancel);
4022
+ if (isPressing.has(target)) {
4023
+ isPressing.delete(target);
4024
+ }
4025
+ if (!isValidPressEvent(endEvent)) {
4026
+ return;
4027
+ }
4028
+ if (typeof onPressEnd === "function") {
4029
+ onPressEnd(endEvent, { success });
4030
+ }
4031
+ };
4032
+ const onPointerUp = (upEvent) => {
4033
+ onPointerEnd(upEvent, target === window ||
4034
+ target === document ||
4035
+ options.useGlobalTarget ||
4036
+ isNodeOrChild(target, upEvent.target));
4037
+ };
4038
+ const onPointerCancel = (cancelEvent) => {
4039
+ onPointerEnd(cancelEvent, false);
4040
+ };
4041
+ window.addEventListener("pointerup", onPointerUp, eventOptions);
4042
+ window.addEventListener("pointercancel", onPointerCancel, eventOptions);
4043
+ };
4044
+ targets.forEach((target) => {
4045
+ const pointerDownTarget = options.useGlobalTarget ? window : target;
4046
+ pointerDownTarget.addEventListener("pointerdown", startPress, eventOptions);
4047
+ if (target instanceof HTMLElement) {
4048
+ target.addEventListener("focus", (event) => enableKeyboardPress(event, eventOptions));
4049
+ if (!isElementKeyboardAccessible(target) &&
4050
+ !target.hasAttribute("tabindex")) {
4051
+ target.tabIndex = 0;
4053
4052
  }
4054
4053
  }
4055
- this.clearAnimation();
4054
+ });
4055
+ return cancelEvents;
4056
+ }
4057
+
4058
+ function getComputedStyle$2(element, name) {
4059
+ const computedStyle = window.getComputedStyle(element);
4060
+ return isCSSVar(name)
4061
+ ? computedStyle.getPropertyValue(name)
4062
+ : computedStyle[name];
4063
+ }
4064
+
4065
+ function observeTimeline(update, timeline) {
4066
+ let prevProgress;
4067
+ const onFrame = () => {
4068
+ const { currentTime } = timeline;
4069
+ const percentage = currentTime === null ? 0 : currentTime.value;
4070
+ const progress = percentage / 100;
4071
+ if (prevProgress !== progress) {
4072
+ update(progress);
4073
+ }
4074
+ prevProgress = progress;
4075
+ };
4076
+ frame.preUpdate(onFrame, true);
4077
+ return () => cancelFrame(onFrame);
4078
+ }
4079
+
4080
+ function record() {
4081
+ const { value } = statsBuffer;
4082
+ if (value === null) {
4083
+ cancelFrame(record);
4084
+ return;
4056
4085
  }
4057
- /**
4058
- * Returns `true` if this value is currently animating.
4059
- *
4060
- * @public
4061
- */
4062
- isAnimating() {
4063
- return !!this.animation;
4086
+ value.frameloop.rate.push(frameData.delta);
4087
+ value.animations.mainThread.push(activeAnimations.mainThread);
4088
+ value.animations.waapi.push(activeAnimations.waapi);
4089
+ value.animations.layout.push(activeAnimations.layout);
4090
+ }
4091
+ function mean(values) {
4092
+ return values.reduce((acc, value) => acc + value, 0) / values.length;
4093
+ }
4094
+ function summarise(values, calcAverage = mean) {
4095
+ if (values.length === 0) {
4096
+ return {
4097
+ min: 0,
4098
+ max: 0,
4099
+ avg: 0,
4100
+ };
4064
4101
  }
4065
- clearAnimation() {
4066
- delete this.animation;
4102
+ return {
4103
+ min: Math.min(...values),
4104
+ max: Math.max(...values),
4105
+ avg: calcAverage(values),
4106
+ };
4107
+ }
4108
+ const msToFps = (ms) => Math.round(1000 / ms);
4109
+ function clearStatsBuffer() {
4110
+ statsBuffer.value = null;
4111
+ statsBuffer.addProjectionMetrics = null;
4112
+ }
4113
+ function reportStats() {
4114
+ const { value } = statsBuffer;
4115
+ if (!value) {
4116
+ throw new Error("Stats are not being measured");
4067
4117
  }
4118
+ clearStatsBuffer();
4119
+ cancelFrame(record);
4120
+ const summary = {
4121
+ frameloop: {
4122
+ setup: summarise(value.frameloop.setup),
4123
+ rate: summarise(value.frameloop.rate),
4124
+ read: summarise(value.frameloop.read),
4125
+ resolveKeyframes: summarise(value.frameloop.resolveKeyframes),
4126
+ preUpdate: summarise(value.frameloop.preUpdate),
4127
+ update: summarise(value.frameloop.update),
4128
+ preRender: summarise(value.frameloop.preRender),
4129
+ render: summarise(value.frameloop.render),
4130
+ postRender: summarise(value.frameloop.postRender),
4131
+ },
4132
+ animations: {
4133
+ mainThread: summarise(value.animations.mainThread),
4134
+ waapi: summarise(value.animations.waapi),
4135
+ layout: summarise(value.animations.layout),
4136
+ },
4137
+ layoutProjection: {
4138
+ nodes: summarise(value.layoutProjection.nodes),
4139
+ calculatedTargetDeltas: summarise(value.layoutProjection.calculatedTargetDeltas),
4140
+ calculatedProjections: summarise(value.layoutProjection.calculatedProjections),
4141
+ },
4142
+ };
4068
4143
  /**
4069
- * Destroy and clean up subscribers to this `MotionValue`.
4070
- *
4071
- * The `MotionValue` hooks like `useMotionValue` and `useTransform` automatically
4072
- * handle the lifecycle of the returned `MotionValue`, so this method is only necessary if you've manually
4073
- * created a `MotionValue` via the `motionValue` function.
4074
- *
4075
- * @public
4144
+ * Convert the rate to FPS
4076
4145
  */
4077
- destroy() {
4078
- this.events.destroy?.notify();
4079
- this.clearListeners();
4080
- this.stop();
4081
- if (this.stopPassiveEffect) {
4082
- this.stopPassiveEffect();
4083
- }
4146
+ const { rate } = summary.frameloop;
4147
+ rate.min = msToFps(rate.min);
4148
+ rate.max = msToFps(rate.max);
4149
+ rate.avg = msToFps(rate.avg);
4150
+ [rate.min, rate.max] = [rate.max, rate.min];
4151
+ return summary;
4152
+ }
4153
+ function recordStats() {
4154
+ if (statsBuffer.value) {
4155
+ clearStatsBuffer();
4156
+ throw new Error("Stats are already being measured");
4084
4157
  }
4158
+ const newStatsBuffer = statsBuffer;
4159
+ newStatsBuffer.value = {
4160
+ frameloop: {
4161
+ setup: [],
4162
+ rate: [],
4163
+ read: [],
4164
+ resolveKeyframes: [],
4165
+ preUpdate: [],
4166
+ update: [],
4167
+ preRender: [],
4168
+ render: [],
4169
+ postRender: [],
4170
+ },
4171
+ animations: {
4172
+ mainThread: [],
4173
+ waapi: [],
4174
+ layout: [],
4175
+ },
4176
+ layoutProjection: {
4177
+ nodes: [],
4178
+ calculatedTargetDeltas: [],
4179
+ calculatedProjections: [],
4180
+ },
4181
+ };
4182
+ newStatsBuffer.addProjectionMetrics = (metrics) => {
4183
+ const { layoutProjection } = newStatsBuffer.value;
4184
+ layoutProjection.nodes.push(metrics.nodes);
4185
+ layoutProjection.calculatedTargetDeltas.push(metrics.calculatedTargetDeltas);
4186
+ layoutProjection.calculatedProjections.push(metrics.calculatedProjections);
4187
+ };
4188
+ frame.postRender(record, true);
4189
+ return reportStats;
4085
4190
  }
4086
- function motionValue(init, options) {
4087
- return new MotionValue(init, options);
4191
+
4192
+ function transform(...args) {
4193
+ const useImmediate = !Array.isArray(args[0]);
4194
+ const argOffset = useImmediate ? 0 : -1;
4195
+ const inputValue = args[0 + argOffset];
4196
+ const inputRange = args[1 + argOffset];
4197
+ const outputRange = args[2 + argOffset];
4198
+ const options = args[3 + argOffset];
4199
+ const interpolator = interpolate(inputRange, outputRange, options);
4200
+ return useImmediate ? interpolator(inputValue) : interpolator;
4088
4201
  }
4089
4202
 
4090
4203
  function subscribeValue(inputValues, outputValue, getLatest) {
@@ -4179,15 +4292,6 @@ const valueTypes = [...dimensionValueTypes, color, complex];
4179
4292
  */
4180
4293
  const findValueType = (v) => valueTypes.find(testValueType(v));
4181
4294
 
4182
- /**
4183
- * Provided a value and a ValueType, returns the value as that value type.
4184
- */
4185
- const getValueAsType = (value, type) => {
4186
- return type && typeof value === "number"
4187
- ? type.transform(value)
4188
- : value;
4189
- };
4190
-
4191
4295
  function chooseLayerType(valueName) {
4192
4296
  if (valueName === "layout")
4193
4297
  return "group";