motion 12.9.7 → 12.10.1

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