 85bf1341f3
			
		
	
	85bf1341f3
	
	
	
		
			
			Frontend Enhancements: - Complete React TypeScript frontend with modern UI components - Distributed workflows management interface with real-time updates - Socket.IO integration for live agent status monitoring - Agent management dashboard with cluster visualization - Project management interface with metrics and task tracking - Responsive design with proper error handling and loading states Backend Infrastructure: - Distributed coordinator for multi-agent workflow orchestration - Cluster management API with comprehensive agent operations - Enhanced database models for agents and projects - Project service for filesystem-based project discovery - Performance monitoring and metrics collection - Comprehensive API documentation and error handling Documentation: - Complete distributed development guide (README_DISTRIBUTED.md) - Comprehensive development report with architecture insights - System configuration templates and deployment guides The platform now provides a complete web interface for managing the distributed AI cluster with real-time monitoring, workflow orchestration, and agent coordination capabilities. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
		
			
				
	
	
		
			6166 lines
		
	
	
		
			245 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			6166 lines
		
	
	
		
			245 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| (function (global, factory) {
 | |
|     typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
 | |
|     typeof define === 'function' && define.amd ? define(['exports'], factory) :
 | |
|     (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.Projection = {}));
 | |
| })(this, (function (exports) { 'use strict';
 | |
| 
 | |
|     const noop = (any) => any;
 | |
| 
 | |
|     class Queue {
 | |
|         constructor() {
 | |
|             this.order = [];
 | |
|             this.scheduled = new Set();
 | |
|         }
 | |
|         add(process) {
 | |
|             if (!this.scheduled.has(process)) {
 | |
|                 this.scheduled.add(process);
 | |
|                 this.order.push(process);
 | |
|                 return true;
 | |
|             }
 | |
|         }
 | |
|         remove(process) {
 | |
|             const index = this.order.indexOf(process);
 | |
|             if (index !== -1) {
 | |
|                 this.order.splice(index, 1);
 | |
|                 this.scheduled.delete(process);
 | |
|             }
 | |
|         }
 | |
|         clear() {
 | |
|             this.order.length = 0;
 | |
|             this.scheduled.clear();
 | |
|         }
 | |
|     }
 | |
|     function createRenderStep(runNextFrame) {
 | |
|         /**
 | |
|          * We create and reuse two queues, one to queue jobs for the current frame
 | |
|          * and one for the next. We reuse to avoid triggering GC after x frames.
 | |
|          */
 | |
|         let thisFrame = new Queue();
 | |
|         let nextFrame = new Queue();
 | |
|         let numToRun = 0;
 | |
|         /**
 | |
|          * Track whether we're currently processing jobs in this step. This way
 | |
|          * we can decide whether to schedule new jobs for this frame or next.
 | |
|          */
 | |
|         let isProcessing = false;
 | |
|         let flushNextFrame = false;
 | |
|         /**
 | |
|          * A set of processes which were marked keepAlive when scheduled.
 | |
|          */
 | |
|         const toKeepAlive = new WeakSet();
 | |
|         const step = {
 | |
|             /**
 | |
|              * Schedule a process to run on the next frame.
 | |
|              */
 | |
|             schedule: (callback, keepAlive = false, immediate = false) => {
 | |
|                 const addToCurrentFrame = immediate && isProcessing;
 | |
|                 const queue = addToCurrentFrame ? thisFrame : nextFrame;
 | |
|                 if (keepAlive)
 | |
|                     toKeepAlive.add(callback);
 | |
|                 if (queue.add(callback) && addToCurrentFrame && isProcessing) {
 | |
|                     // If we're adding it to the currently running queue, update its measured size
 | |
|                     numToRun = thisFrame.order.length;
 | |
|                 }
 | |
|                 return callback;
 | |
|             },
 | |
|             /**
 | |
|              * Cancel the provided callback from running on the next frame.
 | |
|              */
 | |
|             cancel: (callback) => {
 | |
|                 nextFrame.remove(callback);
 | |
|                 toKeepAlive.delete(callback);
 | |
|             },
 | |
|             /**
 | |
|              * Execute all schedule callbacks.
 | |
|              */
 | |
|             process: (frameData) => {
 | |
|                 /**
 | |
|                  * If we're already processing we've probably been triggered by a flushSync
 | |
|                  * inside an existing process. Instead of executing, mark flushNextFrame
 | |
|                  * as true and ensure we flush the following frame at the end of this one.
 | |
|                  */
 | |
|                 if (isProcessing) {
 | |
|                     flushNextFrame = true;
 | |
|                     return;
 | |
|                 }
 | |
|                 isProcessing = true;
 | |
|                 [thisFrame, nextFrame] = [nextFrame, thisFrame];
 | |
|                 // Clear the next frame queue
 | |
|                 nextFrame.clear();
 | |
|                 // Execute this frame
 | |
|                 numToRun = thisFrame.order.length;
 | |
|                 if (numToRun) {
 | |
|                     for (let i = 0; i < numToRun; i++) {
 | |
|                         const callback = thisFrame.order[i];
 | |
|                         callback(frameData);
 | |
|                         if (toKeepAlive.has(callback)) {
 | |
|                             step.schedule(callback);
 | |
|                             runNextFrame();
 | |
|                         }
 | |
|                     }
 | |
|                 }
 | |
|                 isProcessing = false;
 | |
|                 if (flushNextFrame) {
 | |
|                     flushNextFrame = false;
 | |
|                     step.process(frameData);
 | |
|                 }
 | |
|             },
 | |
|         };
 | |
|         return step;
 | |
|     }
 | |
| 
 | |
|     const stepsOrder = [
 | |
|         "prepare",
 | |
|         "read",
 | |
|         "update",
 | |
|         "preRender",
 | |
|         "render",
 | |
|         "postRender",
 | |
|     ];
 | |
|     const maxElapsed = 40;
 | |
|     function createRenderBatcher(scheduleNextBatch, allowKeepAlive) {
 | |
|         let runNextFrame = false;
 | |
|         let useDefaultElapsed = true;
 | |
|         const state = {
 | |
|             delta: 0,
 | |
|             timestamp: 0,
 | |
|             isProcessing: false,
 | |
|         };
 | |
|         const steps = stepsOrder.reduce((acc, key) => {
 | |
|             acc[key] = createRenderStep(() => (runNextFrame = true));
 | |
|             return acc;
 | |
|         }, {});
 | |
|         const processStep = (stepId) => steps[stepId].process(state);
 | |
|         const processBatch = () => {
 | |
|             const timestamp = performance.now();
 | |
|             runNextFrame = false;
 | |
|             state.delta = useDefaultElapsed
 | |
|                 ? 1000 / 60
 | |
|                 : Math.max(Math.min(timestamp - state.timestamp, maxElapsed), 1);
 | |
|             state.timestamp = timestamp;
 | |
|             state.isProcessing = true;
 | |
|             stepsOrder.forEach(processStep);
 | |
|             state.isProcessing = false;
 | |
|             if (runNextFrame && allowKeepAlive) {
 | |
|                 useDefaultElapsed = false;
 | |
|                 scheduleNextBatch(processBatch);
 | |
|             }
 | |
|         };
 | |
|         const wake = () => {
 | |
|             runNextFrame = true;
 | |
|             useDefaultElapsed = true;
 | |
|             if (!state.isProcessing) {
 | |
|                 scheduleNextBatch(processBatch);
 | |
|             }
 | |
|         };
 | |
|         const schedule = stepsOrder.reduce((acc, key) => {
 | |
|             const step = steps[key];
 | |
|             acc[key] = (process, keepAlive = false, immediate = false) => {
 | |
|                 if (!runNextFrame)
 | |
|                     wake();
 | |
|                 return step.schedule(process, keepAlive, immediate);
 | |
|             };
 | |
|             return acc;
 | |
|         }, {});
 | |
|         const cancel = (process) => stepsOrder.forEach((key) => steps[key].cancel(process));
 | |
|         return { schedule, cancel, state, steps };
 | |
|     }
 | |
| 
 | |
|     const { schedule: frame, cancel: cancelFrame, state: frameData, steps, } = createRenderBatcher(typeof requestAnimationFrame !== "undefined" ? requestAnimationFrame : noop, true);
 | |
| 
 | |
|     function addUniqueItem(arr, item) {
 | |
|         if (arr.indexOf(item) === -1)
 | |
|             arr.push(item);
 | |
|     }
 | |
|     function removeItem(arr, item) {
 | |
|         const index = arr.indexOf(item);
 | |
|         if (index > -1)
 | |
|             arr.splice(index, 1);
 | |
|     }
 | |
| 
 | |
|     class SubscriptionManager {
 | |
|         constructor() {
 | |
|             this.subscriptions = [];
 | |
|         }
 | |
|         add(handler) {
 | |
|             addUniqueItem(this.subscriptions, handler);
 | |
|             return () => removeItem(this.subscriptions, handler);
 | |
|         }
 | |
|         notify(a, b, c) {
 | |
|             const numSubscriptions = this.subscriptions.length;
 | |
|             if (!numSubscriptions)
 | |
|                 return;
 | |
|             if (numSubscriptions === 1) {
 | |
|                 /**
 | |
|                  * If there's only a single handler we can just call it without invoking a loop.
 | |
|                  */
 | |
|                 this.subscriptions[0](a, b, c);
 | |
|             }
 | |
|             else {
 | |
|                 for (let i = 0; i < numSubscriptions; i++) {
 | |
|                     /**
 | |
|                      * Check whether the handler exists before firing as it's possible
 | |
|                      * the subscriptions were modified during this loop running.
 | |
|                      */
 | |
|                     const handler = this.subscriptions[i];
 | |
|                     handler && handler(a, b, c);
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
|         getSize() {
 | |
|             return this.subscriptions.length;
 | |
|         }
 | |
|         clear() {
 | |
|             this.subscriptions.length = 0;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     // Accepts an easing function and returns a new one that outputs mirrored values for
 | |
|     // the second half of the animation. Turns easeIn into easeInOut.
 | |
|     const mirrorEasing = (easing) => (p) => p <= 0.5 ? easing(2 * p) / 2 : (2 - easing(2 * (1 - p))) / 2;
 | |
| 
 | |
|     // Accepts an easing function and returns a new one that outputs reversed values.
 | |
|     // Turns easeIn into easeOut.
 | |
|     const reverseEasing = (easing) => (p) => 1 - easing(1 - p);
 | |
| 
 | |
|     const circIn = (p) => 1 - Math.sin(Math.acos(p));
 | |
|     const circOut = reverseEasing(circIn);
 | |
|     const circInOut = mirrorEasing(circIn);
 | |
| 
 | |
|     /*
 | |
|       Progress within given range
 | |
| 
 | |
|       Given a lower limit and an upper limit, we return the progress
 | |
|       (expressed as a number 0-1) represented by the given value, and
 | |
|       limit that progress to within 0-1.
 | |
| 
 | |
|       @param [number]: Lower limit
 | |
|       @param [number]: Upper limit
 | |
|       @param [number]: Value to find progress within given range
 | |
|       @return [number]: Progress of value within range as expressed 0-1
 | |
|     */
 | |
|     const progress = (from, to, value) => {
 | |
|         const toFromDifference = to - from;
 | |
|         return toFromDifference === 0 ? 1 : (value - from) / toFromDifference;
 | |
|     };
 | |
| 
 | |
|     /*
 | |
|       Value in range from progress
 | |
| 
 | |
|       Given a lower limit and an upper limit, we return the value within
 | |
|       that range as expressed by progress (usually a number from 0 to 1)
 | |
| 
 | |
|       So progress = 0.5 would change
 | |
| 
 | |
|       from -------- to
 | |
| 
 | |
|       to
 | |
| 
 | |
|       from ---- to
 | |
| 
 | |
|       E.g. from = 10, to = 20, progress = 0.5 => 15
 | |
| 
 | |
|       @param [number]: Lower limit of range
 | |
|       @param [number]: Upper limit of range
 | |
|       @param [number]: The progress between lower and upper limits expressed 0-1
 | |
|       @return [number]: Value as calculated from progress within range (not limited within range)
 | |
|     */
 | |
|     const mix = (from, to, progress) => -progress * from + progress * to + from;
 | |
| 
 | |
|     /**
 | |
|      * TODO: When we move from string as a source of truth to data models
 | |
|      * everything in this folder should probably be referred to as models vs types
 | |
|      */
 | |
|     // If this number is a decimal, make it just five decimal places
 | |
|     // to avoid exponents
 | |
|     const sanitize = (v) => Math.round(v * 100000) / 100000;
 | |
|     const floatRegex = /(-)?([\d]*\.?[\d])+/g;
 | |
|     const colorRegex = /(#[0-9a-f]{3,8}|(rgb|hsl)a?\((-?[\d\.]+%?[,\s]+){2}(-?[\d\.]+%?)\s*[\,\/]?\s*[\d\.]*%?\))/gi;
 | |
|     const singleColorRegex = /^(#[0-9a-f]{3,8}|(rgb|hsl)a?\((-?[\d\.]+%?[,\s]+){2}(-?[\d\.]+%?)\s*[\,\/]?\s*[\d\.]*%?\))$/i;
 | |
|     function isString(v) {
 | |
|         return typeof v === "string";
 | |
|     }
 | |
| 
 | |
|     const createUnitType = (unit) => ({
 | |
|         test: (v) => isString(v) && v.endsWith(unit) && v.split(" ").length === 1,
 | |
|         parse: parseFloat,
 | |
|         transform: (v) => `${v}${unit}`,
 | |
|     });
 | |
|     const degrees = createUnitType("deg");
 | |
|     const percent = createUnitType("%");
 | |
|     const px = createUnitType("px");
 | |
|     const vh = createUnitType("vh");
 | |
|     const vw = createUnitType("vw");
 | |
|     const progressPercentage = {
 | |
|         ...percent,
 | |
|         parse: (v) => percent.parse(v) / 100,
 | |
|         transform: (v) => percent.transform(v * 100),
 | |
|     };
 | |
| 
 | |
|     const borders = ["TopLeft", "TopRight", "BottomLeft", "BottomRight"];
 | |
|     const numBorders = borders.length;
 | |
|     const asNumber = (value) => typeof value === "string" ? parseFloat(value) : value;
 | |
|     const isPx = (value) => typeof value === "number" || px.test(value);
 | |
|     function mixValues(target, follow, lead, progress, shouldCrossfadeOpacity, isOnlyMember) {
 | |
|         if (shouldCrossfadeOpacity) {
 | |
|             target.opacity = mix(0, 
 | |
|             // TODO Reinstate this if only child
 | |
|             lead.opacity !== undefined ? lead.opacity : 1, easeCrossfadeIn(progress));
 | |
|             target.opacityExit = mix(follow.opacity !== undefined ? follow.opacity : 1, 0, easeCrossfadeOut(progress));
 | |
|         }
 | |
|         else if (isOnlyMember) {
 | |
|             target.opacity = mix(follow.opacity !== undefined ? follow.opacity : 1, lead.opacity !== undefined ? lead.opacity : 1, progress);
 | |
|         }
 | |
|         /**
 | |
|          * Mix border radius
 | |
|          */
 | |
|         for (let i = 0; i < numBorders; i++) {
 | |
|             const borderLabel = `border${borders[i]}Radius`;
 | |
|             let followRadius = getRadius(follow, borderLabel);
 | |
|             let leadRadius = getRadius(lead, borderLabel);
 | |
|             if (followRadius === undefined && leadRadius === undefined)
 | |
|                 continue;
 | |
|             followRadius || (followRadius = 0);
 | |
|             leadRadius || (leadRadius = 0);
 | |
|             const canMix = followRadius === 0 ||
 | |
|                 leadRadius === 0 ||
 | |
|                 isPx(followRadius) === isPx(leadRadius);
 | |
|             if (canMix) {
 | |
|                 target[borderLabel] = Math.max(mix(asNumber(followRadius), asNumber(leadRadius), progress), 0);
 | |
|                 if (percent.test(leadRadius) || percent.test(followRadius)) {
 | |
|                     target[borderLabel] += "%";
 | |
|                 }
 | |
|             }
 | |
|             else {
 | |
|                 target[borderLabel] = leadRadius;
 | |
|             }
 | |
|         }
 | |
|         /**
 | |
|          * Mix rotation
 | |
|          */
 | |
|         if (follow.rotate || lead.rotate) {
 | |
|             target.rotate = mix(follow.rotate || 0, lead.rotate || 0, progress);
 | |
|         }
 | |
|     }
 | |
|     function getRadius(values, radiusName) {
 | |
|         return values[radiusName] !== undefined
 | |
|             ? values[radiusName]
 | |
|             : values.borderRadius;
 | |
|     }
 | |
|     // /**
 | |
|     //  * We only want to mix the background color if there's a follow element
 | |
|     //  * that we're not crossfading opacity between. For instance with switch
 | |
|     //  * AnimateSharedLayout animations, this helps the illusion of a continuous
 | |
|     //  * element being animated but also cuts down on the number of paints triggered
 | |
|     //  * for elements where opacity is doing that work for us.
 | |
|     //  */
 | |
|     // if (
 | |
|     //     !hasFollowElement &&
 | |
|     //     latestLeadValues.backgroundColor &&
 | |
|     //     latestFollowValues.backgroundColor
 | |
|     // ) {
 | |
|     //     /**
 | |
|     //      * This isn't ideal performance-wise as mixColor is creating a new function every frame.
 | |
|     //      * We could probably create a mixer that runs at the start of the animation but
 | |
|     //      * the idea behind the crossfader is that it runs dynamically between two potentially
 | |
|     //      * changing targets (ie opacity or borderRadius may be animating independently via variants)
 | |
|     //      */
 | |
|     //     leadState.backgroundColor = followState.backgroundColor = mixColor(
 | |
|     //         latestFollowValues.backgroundColor as string,
 | |
|     //         latestLeadValues.backgroundColor as string
 | |
|     //     )(p)
 | |
|     // }
 | |
|     const easeCrossfadeIn = compress(0, 0.5, circOut);
 | |
|     const easeCrossfadeOut = compress(0.5, 0.95, noop);
 | |
|     function compress(min, max, easing) {
 | |
|         return (p) => {
 | |
|             // Could replace ifs with clamp
 | |
|             if (p < min)
 | |
|                 return 0;
 | |
|             if (p > max)
 | |
|                 return 1;
 | |
|             return easing(progress(min, max, p));
 | |
|         };
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Reset an axis to the provided origin box.
 | |
|      *
 | |
|      * This is a mutative operation.
 | |
|      */
 | |
|     function copyAxisInto(axis, originAxis) {
 | |
|         axis.min = originAxis.min;
 | |
|         axis.max = originAxis.max;
 | |
|     }
 | |
|     /**
 | |
|      * Reset a box to the provided origin box.
 | |
|      *
 | |
|      * This is a mutative operation.
 | |
|      */
 | |
|     function copyBoxInto(box, originBox) {
 | |
|         copyAxisInto(box.x, originBox.x);
 | |
|         copyAxisInto(box.y, originBox.y);
 | |
|     }
 | |
| 
 | |
|     function isIdentityScale(scale) {
 | |
|         return scale === undefined || scale === 1;
 | |
|     }
 | |
|     function hasScale({ scale, scaleX, scaleY }) {
 | |
|         return (!isIdentityScale(scale) ||
 | |
|             !isIdentityScale(scaleX) ||
 | |
|             !isIdentityScale(scaleY));
 | |
|     }
 | |
|     function hasTransform(values) {
 | |
|         return (hasScale(values) ||
 | |
|             has2DTranslate(values) ||
 | |
|             values.z ||
 | |
|             values.rotate ||
 | |
|             values.rotateX ||
 | |
|             values.rotateY);
 | |
|     }
 | |
|     function has2DTranslate(values) {
 | |
|         return is2DTranslate(values.x) || is2DTranslate(values.y);
 | |
|     }
 | |
|     function is2DTranslate(value) {
 | |
|         return value && value !== "0%";
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Scales a point based on a factor and an originPoint
 | |
|      */
 | |
|     function scalePoint(point, scale, originPoint) {
 | |
|         const distanceFromOrigin = point - originPoint;
 | |
|         const scaled = scale * distanceFromOrigin;
 | |
|         return originPoint + scaled;
 | |
|     }
 | |
|     /**
 | |
|      * Applies a translate/scale delta to a point
 | |
|      */
 | |
|     function applyPointDelta(point, translate, scale, originPoint, boxScale) {
 | |
|         if (boxScale !== undefined) {
 | |
|             point = scalePoint(point, boxScale, originPoint);
 | |
|         }
 | |
|         return scalePoint(point, scale, originPoint) + translate;
 | |
|     }
 | |
|     /**
 | |
|      * Applies a translate/scale delta to an axis
 | |
|      */
 | |
|     function applyAxisDelta(axis, translate = 0, scale = 1, originPoint, boxScale) {
 | |
|         axis.min = applyPointDelta(axis.min, translate, scale, originPoint, boxScale);
 | |
|         axis.max = applyPointDelta(axis.max, translate, scale, originPoint, boxScale);
 | |
|     }
 | |
|     /**
 | |
|      * Applies a translate/scale delta to a box
 | |
|      */
 | |
|     function applyBoxDelta(box, { x, y }) {
 | |
|         applyAxisDelta(box.x, x.translate, x.scale, x.originPoint);
 | |
|         applyAxisDelta(box.y, y.translate, y.scale, y.originPoint);
 | |
|     }
 | |
|     /**
 | |
|      * Apply a tree of deltas to a box. We do this to calculate the effect of all the transforms
 | |
|      * in a tree upon our box before then calculating how to project it into our desired viewport-relative box
 | |
|      *
 | |
|      * This is the final nested loop within updateLayoutDelta for future refactoring
 | |
|      */
 | |
|     function applyTreeDeltas(box, treeScale, treePath, isSharedTransition = false) {
 | |
|         const treeLength = treePath.length;
 | |
|         if (!treeLength)
 | |
|             return;
 | |
|         // Reset the treeScale
 | |
|         treeScale.x = treeScale.y = 1;
 | |
|         let node;
 | |
|         let delta;
 | |
|         for (let i = 0; i < treeLength; i++) {
 | |
|             node = treePath[i];
 | |
|             delta = node.projectionDelta;
 | |
|             /**
 | |
|              * TODO: Prefer to remove this, but currently we have motion components with
 | |
|              * display: contents in Framer.
 | |
|              */
 | |
|             const instance = node.instance;
 | |
|             if (instance &&
 | |
|                 instance.style &&
 | |
|                 instance.style.display === "contents") {
 | |
|                 continue;
 | |
|             }
 | |
|             if (isSharedTransition &&
 | |
|                 node.options.layoutScroll &&
 | |
|                 node.scroll &&
 | |
|                 node !== node.root) {
 | |
|                 transformBox(box, {
 | |
|                     x: -node.scroll.offset.x,
 | |
|                     y: -node.scroll.offset.y,
 | |
|                 });
 | |
|             }
 | |
|             if (delta) {
 | |
|                 // Incoporate each ancestor's scale into a culmulative treeScale for this component
 | |
|                 treeScale.x *= delta.x.scale;
 | |
|                 treeScale.y *= delta.y.scale;
 | |
|                 // Apply each ancestor's calculated delta into this component's recorded layout box
 | |
|                 applyBoxDelta(box, delta);
 | |
|             }
 | |
|             if (isSharedTransition && hasTransform(node.latestValues)) {
 | |
|                 transformBox(box, node.latestValues);
 | |
|             }
 | |
|         }
 | |
|         /**
 | |
|          * Snap tree scale back to 1 if it's within a non-perceivable threshold.
 | |
|          * This will help reduce useless scales getting rendered.
 | |
|          */
 | |
|         treeScale.x = snapToDefault(treeScale.x);
 | |
|         treeScale.y = snapToDefault(treeScale.y);
 | |
|     }
 | |
|     function snapToDefault(scale) {
 | |
|         if (Number.isInteger(scale))
 | |
|             return scale;
 | |
|         return scale > 1.0000000000001 || scale < 0.999999999999 ? scale : 1;
 | |
|     }
 | |
|     function translateAxis(axis, distance) {
 | |
|         axis.min = axis.min + distance;
 | |
|         axis.max = axis.max + distance;
 | |
|     }
 | |
|     /**
 | |
|      * Apply a transform to an axis from the latest resolved motion values.
 | |
|      * This function basically acts as a bridge between a flat motion value map
 | |
|      * and applyAxisDelta
 | |
|      */
 | |
|     function transformAxis(axis, transforms, [key, scaleKey, originKey]) {
 | |
|         const axisOrigin = transforms[originKey] !== undefined ? transforms[originKey] : 0.5;
 | |
|         const originPoint = mix(axis.min, axis.max, axisOrigin);
 | |
|         // Apply the axis delta to the final axis
 | |
|         applyAxisDelta(axis, transforms[key], transforms[scaleKey], originPoint, transforms.scale);
 | |
|     }
 | |
|     /**
 | |
|      * The names of the motion values we want to apply as translation, scale and origin.
 | |
|      */
 | |
|     const xKeys$1 = ["x", "scaleX", "originX"];
 | |
|     const yKeys$1 = ["y", "scaleY", "originY"];
 | |
|     /**
 | |
|      * Apply a transform to a box from the latest resolved motion values.
 | |
|      */
 | |
|     function transformBox(box, transform) {
 | |
|         transformAxis(box.x, transform, xKeys$1);
 | |
|         transformAxis(box.y, transform, yKeys$1);
 | |
|     }
 | |
| 
 | |
|     function calcLength(axis) {
 | |
|         return axis.max - axis.min;
 | |
|     }
 | |
|     function isNear(value, target = 0, maxDistance = 0.01) {
 | |
|         return Math.abs(value - target) <= maxDistance;
 | |
|     }
 | |
|     function calcAxisDelta(delta, source, target, origin = 0.5) {
 | |
|         delta.origin = origin;
 | |
|         delta.originPoint = mix(source.min, source.max, delta.origin);
 | |
|         delta.scale = calcLength(target) / calcLength(source);
 | |
|         if (isNear(delta.scale, 1, 0.0001) || isNaN(delta.scale))
 | |
|             delta.scale = 1;
 | |
|         delta.translate =
 | |
|             mix(target.min, target.max, delta.origin) - delta.originPoint;
 | |
|         if (isNear(delta.translate) || isNaN(delta.translate))
 | |
|             delta.translate = 0;
 | |
|     }
 | |
|     function calcBoxDelta(delta, source, target, origin) {
 | |
|         calcAxisDelta(delta.x, source.x, target.x, origin ? origin.originX : undefined);
 | |
|         calcAxisDelta(delta.y, source.y, target.y, origin ? origin.originY : undefined);
 | |
|     }
 | |
|     function calcRelativeAxis(target, relative, parent) {
 | |
|         target.min = parent.min + relative.min;
 | |
|         target.max = target.min + calcLength(relative);
 | |
|     }
 | |
|     function calcRelativeBox(target, relative, parent) {
 | |
|         calcRelativeAxis(target.x, relative.x, parent.x);
 | |
|         calcRelativeAxis(target.y, relative.y, parent.y);
 | |
|     }
 | |
|     function calcRelativeAxisPosition(target, layout, parent) {
 | |
|         target.min = layout.min - parent.min;
 | |
|         target.max = target.min + calcLength(layout);
 | |
|     }
 | |
|     function calcRelativePosition(target, layout, parent) {
 | |
|         calcRelativeAxisPosition(target.x, layout.x, parent.x);
 | |
|         calcRelativeAxisPosition(target.y, layout.y, parent.y);
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Remove a delta from a point. This is essentially the steps of applyPointDelta in reverse
 | |
|      */
 | |
|     function removePointDelta(point, translate, scale, originPoint, boxScale) {
 | |
|         point -= translate;
 | |
|         point = scalePoint(point, 1 / scale, originPoint);
 | |
|         if (boxScale !== undefined) {
 | |
|             point = scalePoint(point, 1 / boxScale, originPoint);
 | |
|         }
 | |
|         return point;
 | |
|     }
 | |
|     /**
 | |
|      * Remove a delta from an axis. This is essentially the steps of applyAxisDelta in reverse
 | |
|      */
 | |
|     function removeAxisDelta(axis, translate = 0, scale = 1, origin = 0.5, boxScale, originAxis = axis, sourceAxis = axis) {
 | |
|         if (percent.test(translate)) {
 | |
|             translate = parseFloat(translate);
 | |
|             const relativeProgress = mix(sourceAxis.min, sourceAxis.max, translate / 100);
 | |
|             translate = relativeProgress - sourceAxis.min;
 | |
|         }
 | |
|         if (typeof translate !== "number")
 | |
|             return;
 | |
|         let originPoint = mix(originAxis.min, originAxis.max, origin);
 | |
|         if (axis === originAxis)
 | |
|             originPoint -= translate;
 | |
|         axis.min = removePointDelta(axis.min, translate, scale, originPoint, boxScale);
 | |
|         axis.max = removePointDelta(axis.max, translate, scale, originPoint, boxScale);
 | |
|     }
 | |
|     /**
 | |
|      * Remove a transforms from an axis. This is essentially the steps of applyAxisTransforms in reverse
 | |
|      * and acts as a bridge between motion values and removeAxisDelta
 | |
|      */
 | |
|     function removeAxisTransforms(axis, transforms, [key, scaleKey, originKey], origin, sourceAxis) {
 | |
|         removeAxisDelta(axis, transforms[key], transforms[scaleKey], transforms[originKey], transforms.scale, origin, sourceAxis);
 | |
|     }
 | |
|     /**
 | |
|      * The names of the motion values we want to apply as translation, scale and origin.
 | |
|      */
 | |
|     const xKeys = ["x", "scaleX", "originX"];
 | |
|     const yKeys = ["y", "scaleY", "originY"];
 | |
|     /**
 | |
|      * Remove a transforms from an box. This is essentially the steps of applyAxisBox in reverse
 | |
|      * and acts as a bridge between motion values and removeAxisDelta
 | |
|      */
 | |
|     function removeBoxTransforms(box, transforms, originBox, sourceBox) {
 | |
|         removeAxisTransforms(box.x, transforms, xKeys, originBox ? originBox.x : undefined, sourceBox ? sourceBox.x : undefined);
 | |
|         removeAxisTransforms(box.y, transforms, yKeys, originBox ? originBox.y : undefined, sourceBox ? sourceBox.y : undefined);
 | |
|     }
 | |
| 
 | |
|     const createAxisDelta = () => ({
 | |
|         translate: 0,
 | |
|         scale: 1,
 | |
|         origin: 0,
 | |
|         originPoint: 0,
 | |
|     });
 | |
|     const createDelta = () => ({
 | |
|         x: createAxisDelta(),
 | |
|         y: createAxisDelta(),
 | |
|     });
 | |
|     const createAxis = () => ({ min: 0, max: 0 });
 | |
|     const createBox = () => ({
 | |
|         x: createAxis(),
 | |
|         y: createAxis(),
 | |
|     });
 | |
| 
 | |
|     /**
 | |
|      * Decide whether a transition is defined on a given Transition.
 | |
|      * This filters out orchestration options and returns true
 | |
|      * if any options are left.
 | |
|      */
 | |
|     function isTransitionDefined({ when, delay: _delay, delayChildren, staggerChildren, staggerDirection, repeat, repeatType, repeatDelay, from, elapsed, ...transition }) {
 | |
|         return !!Object.keys(transition).length;
 | |
|     }
 | |
|     function getValueTransition(transition, key) {
 | |
|         return transition[key] || transition["default"] || transition;
 | |
|     }
 | |
| 
 | |
|     function isAxisDeltaZero(delta) {
 | |
|         return delta.translate === 0 && delta.scale === 1;
 | |
|     }
 | |
|     function isDeltaZero(delta) {
 | |
|         return isAxisDeltaZero(delta.x) && isAxisDeltaZero(delta.y);
 | |
|     }
 | |
|     function boxEquals(a, b) {
 | |
|         return (a.x.min === b.x.min &&
 | |
|             a.x.max === b.x.max &&
 | |
|             a.y.min === b.y.min &&
 | |
|             a.y.max === b.y.max);
 | |
|     }
 | |
|     function boxEqualsRounded(a, b) {
 | |
|         return (Math.round(a.x.min) === Math.round(b.x.min) &&
 | |
|             Math.round(a.x.max) === Math.round(b.x.max) &&
 | |
|             Math.round(a.y.min) === Math.round(b.y.min) &&
 | |
|             Math.round(a.y.max) === Math.round(b.y.max));
 | |
|     }
 | |
|     function aspectRatio(box) {
 | |
|         return calcLength(box.x) / calcLength(box.y);
 | |
|     }
 | |
| 
 | |
|     class NodeStack {
 | |
|         constructor() {
 | |
|             this.members = [];
 | |
|         }
 | |
|         add(node) {
 | |
|             addUniqueItem(this.members, node);
 | |
|             node.scheduleRender();
 | |
|         }
 | |
|         remove(node) {
 | |
|             removeItem(this.members, node);
 | |
|             if (node === this.prevLead) {
 | |
|                 this.prevLead = undefined;
 | |
|             }
 | |
|             if (node === this.lead) {
 | |
|                 const prevLead = this.members[this.members.length - 1];
 | |
|                 if (prevLead) {
 | |
|                     this.promote(prevLead);
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
|         relegate(node) {
 | |
|             const indexOfNode = this.members.findIndex((member) => node === member);
 | |
|             if (indexOfNode === 0)
 | |
|                 return false;
 | |
|             /**
 | |
|              * Find the next projection node that is present
 | |
|              */
 | |
|             let prevLead;
 | |
|             for (let i = indexOfNode; i >= 0; i--) {
 | |
|                 const member = this.members[i];
 | |
|                 if (member.isPresent !== false) {
 | |
|                     prevLead = member;
 | |
|                     break;
 | |
|                 }
 | |
|             }
 | |
|             if (prevLead) {
 | |
|                 this.promote(prevLead);
 | |
|                 return true;
 | |
|             }
 | |
|             else {
 | |
|                 return false;
 | |
|             }
 | |
|         }
 | |
|         promote(node, preserveFollowOpacity) {
 | |
|             const prevLead = this.lead;
 | |
|             if (node === prevLead)
 | |
|                 return;
 | |
|             this.prevLead = prevLead;
 | |
|             this.lead = node;
 | |
|             node.show();
 | |
|             if (prevLead) {
 | |
|                 prevLead.instance && prevLead.scheduleRender();
 | |
|                 node.scheduleRender();
 | |
|                 node.resumeFrom = prevLead;
 | |
|                 if (preserveFollowOpacity) {
 | |
|                     node.resumeFrom.preserveOpacity = true;
 | |
|                 }
 | |
|                 if (prevLead.snapshot) {
 | |
|                     node.snapshot = prevLead.snapshot;
 | |
|                     node.snapshot.latestValues =
 | |
|                         prevLead.animationValues || prevLead.latestValues;
 | |
|                 }
 | |
|                 if (node.root && node.root.isUpdating) {
 | |
|                     node.isLayoutDirty = true;
 | |
|                 }
 | |
|                 const { crossfade } = node.options;
 | |
|                 if (crossfade === false) {
 | |
|                     prevLead.hide();
 | |
|                 }
 | |
|                 /**
 | |
|                  * TODO:
 | |
|                  *   - Test border radius when previous node was deleted
 | |
|                  *   - boxShadow mixing
 | |
|                  *   - Shared between element A in scrolled container and element B (scroll stays the same or changes)
 | |
|                  *   - Shared between element A in transformed container and element B (transform stays the same or changes)
 | |
|                  *   - Shared between element A in scrolled page and element B (scroll stays the same or changes)
 | |
|                  * ---
 | |
|                  *   - Crossfade opacity of root nodes
 | |
|                  *   - layoutId changes after animation
 | |
|                  *   - layoutId changes mid animation
 | |
|                  */
 | |
|             }
 | |
|         }
 | |
|         exitAnimationComplete() {
 | |
|             this.members.forEach((node) => {
 | |
|                 const { options, resumingFrom } = node;
 | |
|                 options.onExitComplete && options.onExitComplete();
 | |
|                 if (resumingFrom) {
 | |
|                     resumingFrom.options.onExitComplete &&
 | |
|                         resumingFrom.options.onExitComplete();
 | |
|                 }
 | |
|             });
 | |
|         }
 | |
|         scheduleRender() {
 | |
|             this.members.forEach((node) => {
 | |
|                 node.instance && node.scheduleRender(false);
 | |
|             });
 | |
|         }
 | |
|         /**
 | |
|          * Clear any leads that have been removed this render to prevent them from being
 | |
|          * used in future animations and to prevent memory leaks
 | |
|          */
 | |
|         removeLeadSnapshot() {
 | |
|             if (this.lead && this.lead.snapshot) {
 | |
|                 this.lead.snapshot = undefined;
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     const scaleCorrectors = {};
 | |
|     function addScaleCorrector(correctors) {
 | |
|         Object.assign(scaleCorrectors, correctors);
 | |
|     }
 | |
| 
 | |
|     function buildProjectionTransform(delta, treeScale, latestTransform) {
 | |
|         let transform = "";
 | |
|         /**
 | |
|          * The translations we use to calculate are always relative to the viewport coordinate space.
 | |
|          * But when we apply scales, we also scale the coordinate space of an element and its children.
 | |
|          * For instance if we have a treeScale (the culmination of all parent scales) of 0.5 and we need
 | |
|          * to move an element 100 pixels, we actually need to move it 200 in within that scaled space.
 | |
|          */
 | |
|         const xTranslate = delta.x.translate / treeScale.x;
 | |
|         const yTranslate = delta.y.translate / treeScale.y;
 | |
|         if (xTranslate || yTranslate) {
 | |
|             transform = `translate3d(${xTranslate}px, ${yTranslate}px, 0) `;
 | |
|         }
 | |
|         /**
 | |
|          * Apply scale correction for the tree transform.
 | |
|          * This will apply scale to the screen-orientated axes.
 | |
|          */
 | |
|         if (treeScale.x !== 1 || treeScale.y !== 1) {
 | |
|             transform += `scale(${1 / treeScale.x}, ${1 / treeScale.y}) `;
 | |
|         }
 | |
|         if (latestTransform) {
 | |
|             const { rotate, rotateX, rotateY } = latestTransform;
 | |
|             if (rotate)
 | |
|                 transform += `rotate(${rotate}deg) `;
 | |
|             if (rotateX)
 | |
|                 transform += `rotateX(${rotateX}deg) `;
 | |
|             if (rotateY)
 | |
|                 transform += `rotateY(${rotateY}deg) `;
 | |
|         }
 | |
|         /**
 | |
|          * Apply scale to match the size of the element to the size we want it.
 | |
|          * This will apply scale to the element-orientated axes.
 | |
|          */
 | |
|         const elementScaleX = delta.x.scale * treeScale.x;
 | |
|         const elementScaleY = delta.y.scale * treeScale.y;
 | |
|         if (elementScaleX !== 1 || elementScaleY !== 1) {
 | |
|             transform += `scale(${elementScaleX}, ${elementScaleY})`;
 | |
|         }
 | |
|         return transform || "none";
 | |
|     }
 | |
| 
 | |
|     function eachAxis(callback) {
 | |
|         return [callback("x"), callback("y")];
 | |
|     }
 | |
| 
 | |
|     const compareByDepth = (a, b) => a.depth - b.depth;
 | |
| 
 | |
|     class FlatTree {
 | |
|         constructor() {
 | |
|             this.children = [];
 | |
|             this.isDirty = false;
 | |
|         }
 | |
|         add(child) {
 | |
|             addUniqueItem(this.children, child);
 | |
|             this.isDirty = true;
 | |
|         }
 | |
|         remove(child) {
 | |
|             removeItem(this.children, child);
 | |
|             this.isDirty = true;
 | |
|         }
 | |
|         forEach(callback) {
 | |
|             this.isDirty && this.children.sort(compareByDepth);
 | |
|             this.isDirty = false;
 | |
|             this.children.forEach(callback);
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     const isKeyframesTarget = (v) => {
 | |
|         return Array.isArray(v);
 | |
|     };
 | |
| 
 | |
|     const isCustomValue = (v) => {
 | |
|         return Boolean(v && typeof v === "object" && v.mix && v.toValue);
 | |
|     };
 | |
| 
 | |
|     const isMotionValue = (value) => Boolean(value && value.getVelocity);
 | |
| 
 | |
|     /**
 | |
|      * If the provided value is a MotionValue, this returns the actual value, otherwise just the value itself
 | |
|      *
 | |
|      * TODO: Remove and move to library
 | |
|      */
 | |
|     function resolveMotionValue(value) {
 | |
|         const unwrappedValue = isMotionValue(value) ? value.get() : value;
 | |
|         return isCustomValue(unwrappedValue)
 | |
|             ? unwrappedValue.toValue()
 | |
|             : unwrappedValue;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * This should only ever be modified on the client otherwise it'll
 | |
|      * persist through server requests. If we need instanced states we
 | |
|      * could lazy-init via root.
 | |
|      */
 | |
|     const globalProjectionState = {
 | |
|         /**
 | |
|          * Global flag as to whether the tree has animated since the last time
 | |
|          * we resized the window
 | |
|          */
 | |
|         hasAnimatedSinceResize: true,
 | |
|         /**
 | |
|          * We set this to true once, on the first update. Any nodes added to the tree beyond that
 | |
|          * update will be given a `data-projection-id` attribute.
 | |
|          */
 | |
|         hasEverUpdated: false,
 | |
|     };
 | |
| 
 | |
|     /**
 | |
|      * Timeout defined in ms
 | |
|      */
 | |
|     function delay(callback, timeout) {
 | |
|         const start = performance.now();
 | |
|         const checkElapsed = ({ timestamp }) => {
 | |
|             const elapsed = timestamp - start;
 | |
|             if (elapsed >= timeout) {
 | |
|                 cancelFrame(checkElapsed);
 | |
|                 callback(elapsed - timeout);
 | |
|             }
 | |
|         };
 | |
|         frame.read(checkElapsed, true);
 | |
|         return () => cancelFrame(checkElapsed);
 | |
|     }
 | |
| 
 | |
|     function record(data) {
 | |
|         if (window.MotionDebug) {
 | |
|             window.MotionDebug.record(data);
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     /*
 | |
|       Convert velocity into velocity per second
 | |
| 
 | |
|       @param [number]: Unit per frame
 | |
|       @param [number]: Frame duration in ms
 | |
|     */
 | |
|     function velocityPerSecond(velocity, frameDuration) {
 | |
|         return frameDuration ? velocity * (1000 / frameDuration) : 0;
 | |
|     }
 | |
| 
 | |
|     const warned = new Set();
 | |
|     function warnOnce(condition, message, element) {
 | |
|         if (condition || warned.has(message))
 | |
|             return;
 | |
|         console.warn(message);
 | |
|         if (element)
 | |
|             console.warn(element);
 | |
|         warned.add(message);
 | |
|     }
 | |
| 
 | |
|     const isFloat = (value) => {
 | |
|         return !isNaN(parseFloat(value));
 | |
|     };
 | |
|     /**
 | |
|      * `MotionValue` is used to track the state and velocity of motion values.
 | |
|      *
 | |
|      * @public
 | |
|      */
 | |
|     class MotionValue {
 | |
|         /**
 | |
|          * @param init - The initiating value
 | |
|          * @param config - Optional configuration options
 | |
|          *
 | |
|          * -  `transformer`: A function to transform incoming values with.
 | |
|          *
 | |
|          * @internal
 | |
|          */
 | |
|         constructor(init, options = {}) {
 | |
|             /**
 | |
|              * This will be replaced by the build step with the latest version number.
 | |
|              * When MotionValues are provided to motion components, warn if versions are mixed.
 | |
|              */
 | |
|             this.version = "10.18.0";
 | |
|             /**
 | |
|              * Duration, in milliseconds, since last updating frame.
 | |
|              *
 | |
|              * @internal
 | |
|              */
 | |
|             this.timeDelta = 0;
 | |
|             /**
 | |
|              * Timestamp of the last time this `MotionValue` was updated.
 | |
|              *
 | |
|              * @internal
 | |
|              */
 | |
|             this.lastUpdated = 0;
 | |
|             /**
 | |
|              * Tracks whether this value can output a velocity. Currently this is only true
 | |
|              * if the value is numerical, but we might be able to widen the scope here and support
 | |
|              * other value types.
 | |
|              *
 | |
|              * @internal
 | |
|              */
 | |
|             this.canTrackVelocity = false;
 | |
|             /**
 | |
|              * An object containing a SubscriptionManager for each active event.
 | |
|              */
 | |
|             this.events = {};
 | |
|             this.updateAndNotify = (v, render = true) => {
 | |
|                 this.prev = this.current;
 | |
|                 this.current = v;
 | |
|                 // Update timestamp
 | |
|                 const { delta, timestamp } = frameData;
 | |
|                 if (this.lastUpdated !== timestamp) {
 | |
|                     this.timeDelta = delta;
 | |
|                     this.lastUpdated = timestamp;
 | |
|                     frame.postRender(this.scheduleVelocityCheck);
 | |
|                 }
 | |
|                 // Update update subscribers
 | |
|                 if (this.prev !== this.current && this.events.change) {
 | |
|                     this.events.change.notify(this.current);
 | |
|                 }
 | |
|                 // Update velocity subscribers
 | |
|                 if (this.events.velocityChange) {
 | |
|                     this.events.velocityChange.notify(this.getVelocity());
 | |
|                 }
 | |
|                 // Update render subscribers
 | |
|                 if (render && this.events.renderRequest) {
 | |
|                     this.events.renderRequest.notify(this.current);
 | |
|                 }
 | |
|             };
 | |
|             /**
 | |
|              * Schedule a velocity check for the next frame.
 | |
|              *
 | |
|              * This is an instanced and bound function to prevent generating a new
 | |
|              * function once per frame.
 | |
|              *
 | |
|              * @internal
 | |
|              */
 | |
|             this.scheduleVelocityCheck = () => frame.postRender(this.velocityCheck);
 | |
|             /**
 | |
|              * Updates `prev` with `current` if the value hasn't been updated this frame.
 | |
|              * This ensures velocity calculations return `0`.
 | |
|              *
 | |
|              * This is an instanced and bound function to prevent generating a new
 | |
|              * function once per frame.
 | |
|              *
 | |
|              * @internal
 | |
|              */
 | |
|             this.velocityCheck = ({ timestamp }) => {
 | |
|                 if (timestamp !== this.lastUpdated) {
 | |
|                     this.prev = this.current;
 | |
|                     if (this.events.velocityChange) {
 | |
|                         this.events.velocityChange.notify(this.getVelocity());
 | |
|                     }
 | |
|                 }
 | |
|             };
 | |
|             this.hasAnimated = false;
 | |
|             this.prev = this.current = init;
 | |
|             this.canTrackVelocity = isFloat(this.current);
 | |
|             this.owner = options.owner;
 | |
|         }
 | |
|         /**
 | |
|          * Adds a function that will be notified when the `MotionValue` is updated.
 | |
|          *
 | |
|          * It returns a function that, when called, will cancel the subscription.
 | |
|          *
 | |
|          * When calling `onChange` inside a React component, it should be wrapped with the
 | |
|          * `useEffect` hook. As it returns an unsubscribe function, this should be returned
 | |
|          * from the `useEffect` function to ensure you don't add duplicate subscribers..
 | |
|          *
 | |
|          * ```jsx
 | |
|          * export const MyComponent = () => {
 | |
|          *   const x = useMotionValue(0)
 | |
|          *   const y = useMotionValue(0)
 | |
|          *   const opacity = useMotionValue(1)
 | |
|          *
 | |
|          *   useEffect(() => {
 | |
|          *     function updateOpacity() {
 | |
|          *       const maxXY = Math.max(x.get(), y.get())
 | |
|          *       const newOpacity = transform(maxXY, [0, 100], [1, 0])
 | |
|          *       opacity.set(newOpacity)
 | |
|          *     }
 | |
|          *
 | |
|          *     const unsubscribeX = x.on("change", updateOpacity)
 | |
|          *     const unsubscribeY = y.on("change", updateOpacity)
 | |
|          *
 | |
|          *     return () => {
 | |
|          *       unsubscribeX()
 | |
|          *       unsubscribeY()
 | |
|          *     }
 | |
|          *   }, [])
 | |
|          *
 | |
|          *   return <motion.div style={{ x }} />
 | |
|          * }
 | |
|          * ```
 | |
|          *
 | |
|          * @param subscriber - A function that receives the latest value.
 | |
|          * @returns A function that, when called, will cancel this subscription.
 | |
|          *
 | |
|          * @deprecated
 | |
|          */
 | |
|         onChange(subscription) {
 | |
|             {
 | |
|                 warnOnce(false, `value.onChange(callback) is deprecated. Switch to value.on("change", callback).`);
 | |
|             }
 | |
|             return this.on("change", subscription);
 | |
|         }
 | |
|         on(eventName, callback) {
 | |
|             if (!this.events[eventName]) {
 | |
|                 this.events[eventName] = new SubscriptionManager();
 | |
|             }
 | |
|             const unsubscribe = this.events[eventName].add(callback);
 | |
|             if (eventName === "change") {
 | |
|                 return () => {
 | |
|                     unsubscribe();
 | |
|                     /**
 | |
|                      * If we have no more change listeners by the start
 | |
|                      * of the next frame, stop active animations.
 | |
|                      */
 | |
|                     frame.read(() => {
 | |
|                         if (!this.events.change.getSize()) {
 | |
|                             this.stop();
 | |
|                         }
 | |
|                     });
 | |
|                 };
 | |
|             }
 | |
|             return unsubscribe;
 | |
|         }
 | |
|         clearListeners() {
 | |
|             for (const eventManagers in this.events) {
 | |
|                 this.events[eventManagers].clear();
 | |
|             }
 | |
|         }
 | |
|         /**
 | |
|          * Attaches a passive effect to the `MotionValue`.
 | |
|          *
 | |
|          * @internal
 | |
|          */
 | |
|         attach(passiveEffect, stopPassiveEffect) {
 | |
|             this.passiveEffect = passiveEffect;
 | |
|             this.stopPassiveEffect = stopPassiveEffect;
 | |
|         }
 | |
|         /**
 | |
|          * Sets the state of the `MotionValue`.
 | |
|          *
 | |
|          * @remarks
 | |
|          *
 | |
|          * ```jsx
 | |
|          * const x = useMotionValue(0)
 | |
|          * x.set(10)
 | |
|          * ```
 | |
|          *
 | |
|          * @param latest - Latest value to set.
 | |
|          * @param render - Whether to notify render subscribers. Defaults to `true`
 | |
|          *
 | |
|          * @public
 | |
|          */
 | |
|         set(v, render = true) {
 | |
|             if (!render || !this.passiveEffect) {
 | |
|                 this.updateAndNotify(v, render);
 | |
|             }
 | |
|             else {
 | |
|                 this.passiveEffect(v, this.updateAndNotify);
 | |
|             }
 | |
|         }
 | |
|         setWithVelocity(prev, current, delta) {
 | |
|             this.set(current);
 | |
|             this.prev = prev;
 | |
|             this.timeDelta = delta;
 | |
|         }
 | |
|         /**
 | |
|          * Set the state of the `MotionValue`, stopping any active animations,
 | |
|          * effects, and resets velocity to `0`.
 | |
|          */
 | |
|         jump(v) {
 | |
|             this.updateAndNotify(v);
 | |
|             this.prev = v;
 | |
|             this.stop();
 | |
|             if (this.stopPassiveEffect)
 | |
|                 this.stopPassiveEffect();
 | |
|         }
 | |
|         /**
 | |
|          * Returns the latest state of `MotionValue`
 | |
|          *
 | |
|          * @returns - The latest state of `MotionValue`
 | |
|          *
 | |
|          * @public
 | |
|          */
 | |
|         get() {
 | |
|             return this.current;
 | |
|         }
 | |
|         /**
 | |
|          * @public
 | |
|          */
 | |
|         getPrevious() {
 | |
|             return this.prev;
 | |
|         }
 | |
|         /**
 | |
|          * Returns the latest velocity of `MotionValue`
 | |
|          *
 | |
|          * @returns - The latest velocity of `MotionValue`. Returns `0` if the state is non-numerical.
 | |
|          *
 | |
|          * @public
 | |
|          */
 | |
|         getVelocity() {
 | |
|             // This could be isFloat(this.prev) && isFloat(this.current), but that would be wasteful
 | |
|             return this.canTrackVelocity
 | |
|                 ? // These casts could be avoided if parseFloat would be typed better
 | |
|                     velocityPerSecond(parseFloat(this.current) -
 | |
|                         parseFloat(this.prev), this.timeDelta)
 | |
|                 : 0;
 | |
|         }
 | |
|         /**
 | |
|          * Registers a new animation to control this `MotionValue`. Only one
 | |
|          * animation can drive a `MotionValue` at one time.
 | |
|          *
 | |
|          * ```jsx
 | |
|          * value.start()
 | |
|          * ```
 | |
|          *
 | |
|          * @param animation - A function that starts the provided animation
 | |
|          *
 | |
|          * @internal
 | |
|          */
 | |
|         start(startAnimation) {
 | |
|             this.stop();
 | |
|             return new Promise((resolve) => {
 | |
|                 this.hasAnimated = true;
 | |
|                 this.animation = startAnimation(resolve);
 | |
|                 if (this.events.animationStart) {
 | |
|                     this.events.animationStart.notify();
 | |
|                 }
 | |
|             }).then(() => {
 | |
|                 if (this.events.animationComplete) {
 | |
|                     this.events.animationComplete.notify();
 | |
|                 }
 | |
|                 this.clearAnimation();
 | |
|             });
 | |
|         }
 | |
|         /**
 | |
|          * Stop the currently active animation.
 | |
|          *
 | |
|          * @public
 | |
|          */
 | |
|         stop() {
 | |
|             if (this.animation) {
 | |
|                 this.animation.stop();
 | |
|                 if (this.events.animationCancel) {
 | |
|                     this.events.animationCancel.notify();
 | |
|                 }
 | |
|             }
 | |
|             this.clearAnimation();
 | |
|         }
 | |
|         /**
 | |
|          * Returns `true` if this value is currently animating.
 | |
|          *
 | |
|          * @public
 | |
|          */
 | |
|         isAnimating() {
 | |
|             return !!this.animation;
 | |
|         }
 | |
|         clearAnimation() {
 | |
|             delete this.animation;
 | |
|         }
 | |
|         /**
 | |
|          * Destroy and clean up subscribers to this `MotionValue`.
 | |
|          *
 | |
|          * The `MotionValue` hooks like `useMotionValue` and `useTransform` automatically
 | |
|          * handle the lifecycle of the returned `MotionValue`, so this method is only necessary if you've manually
 | |
|          * created a `MotionValue` via the `motionValue` function.
 | |
|          *
 | |
|          * @public
 | |
|          */
 | |
|         destroy() {
 | |
|             this.clearListeners();
 | |
|             this.stop();
 | |
|             if (this.stopPassiveEffect) {
 | |
|                 this.stopPassiveEffect();
 | |
|             }
 | |
|         }
 | |
|     }
 | |
|     function motionValue(init, options) {
 | |
|         return new MotionValue(init, options);
 | |
|     }
 | |
| 
 | |
|     let warning = noop;
 | |
|     let invariant = noop;
 | |
|     {
 | |
|         warning = (check, message) => {
 | |
|             if (!check && typeof console !== "undefined") {
 | |
|                 console.warn(message);
 | |
|             }
 | |
|         };
 | |
|         invariant = (check, message) => {
 | |
|             if (!check) {
 | |
|                 throw new Error(message);
 | |
|             }
 | |
|         };
 | |
|     }
 | |
| 
 | |
|     const visualElementStore = new WeakMap();
 | |
| 
 | |
|     function memo(callback) {
 | |
|         let result;
 | |
|         return () => {
 | |
|             if (result === undefined)
 | |
|                 result = callback();
 | |
|             return result;
 | |
|         };
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Generate a list of every possible transform key.
 | |
|      */
 | |
|     const transformPropOrder = [
 | |
|         "transformPerspective",
 | |
|         "x",
 | |
|         "y",
 | |
|         "z",
 | |
|         "translateX",
 | |
|         "translateY",
 | |
|         "translateZ",
 | |
|         "scale",
 | |
|         "scaleX",
 | |
|         "scaleY",
 | |
|         "rotate",
 | |
|         "rotateX",
 | |
|         "rotateY",
 | |
|         "rotateZ",
 | |
|         "skew",
 | |
|         "skewX",
 | |
|         "skewY",
 | |
|     ];
 | |
|     /**
 | |
|      * A quick lookup for transform props.
 | |
|      */
 | |
|     const transformProps = new Set(transformPropOrder);
 | |
| 
 | |
|     /**
 | |
|      * Converts seconds to milliseconds
 | |
|      *
 | |
|      * @param seconds - Time in seconds.
 | |
|      * @return milliseconds - Converted time in milliseconds.
 | |
|      */
 | |
|     const secondsToMilliseconds = (seconds) => seconds * 1000;
 | |
|     const millisecondsToSeconds = (milliseconds) => milliseconds / 1000;
 | |
| 
 | |
|     const instantAnimationState = {
 | |
|         current: false,
 | |
|     };
 | |
| 
 | |
|     const isBezierDefinition = (easing) => Array.isArray(easing) && typeof easing[0] === "number";
 | |
| 
 | |
|     function isWaapiSupportedEasing(easing) {
 | |
|         return Boolean(!easing ||
 | |
|             (typeof easing === "string" && supportedWaapiEasing[easing]) ||
 | |
|             isBezierDefinition(easing) ||
 | |
|             (Array.isArray(easing) && easing.every(isWaapiSupportedEasing)));
 | |
|     }
 | |
|     const cubicBezierAsString = ([a, b, c, d]) => `cubic-bezier(${a}, ${b}, ${c}, ${d})`;
 | |
|     const supportedWaapiEasing = {
 | |
|         linear: "linear",
 | |
|         ease: "ease",
 | |
|         easeIn: "ease-in",
 | |
|         easeOut: "ease-out",
 | |
|         easeInOut: "ease-in-out",
 | |
|         circIn: cubicBezierAsString([0, 0.65, 0.55, 1]),
 | |
|         circOut: cubicBezierAsString([0.55, 0, 1, 0.45]),
 | |
|         backIn: cubicBezierAsString([0.31, 0.01, 0.66, -0.59]),
 | |
|         backOut: cubicBezierAsString([0.33, 1.53, 0.69, 0.99]),
 | |
|     };
 | |
|     function mapEasingToNativeEasing(easing) {
 | |
|         if (!easing)
 | |
|             return undefined;
 | |
|         return isBezierDefinition(easing)
 | |
|             ? cubicBezierAsString(easing)
 | |
|             : Array.isArray(easing)
 | |
|                 ? easing.map(mapEasingToNativeEasing)
 | |
|                 : supportedWaapiEasing[easing];
 | |
|     }
 | |
| 
 | |
|     function animateStyle(element, valueName, keyframes, { delay = 0, duration, repeat = 0, repeatType = "loop", ease, times, } = {}) {
 | |
|         const keyframeOptions = { [valueName]: keyframes };
 | |
|         if (times)
 | |
|             keyframeOptions.offset = times;
 | |
|         const easing = mapEasingToNativeEasing(ease);
 | |
|         /**
 | |
|          * If this is an easing array, apply to keyframes, not animation as a whole
 | |
|          */
 | |
|         if (Array.isArray(easing))
 | |
|             keyframeOptions.easing = easing;
 | |
|         return element.animate(keyframeOptions, {
 | |
|             delay,
 | |
|             duration,
 | |
|             easing: !Array.isArray(easing) ? easing : "linear",
 | |
|             fill: "both",
 | |
|             iterations: repeat + 1,
 | |
|             direction: repeatType === "reverse" ? "alternate" : "normal",
 | |
|         });
 | |
|     }
 | |
| 
 | |
|     function getFinalKeyframe(keyframes, { repeat, repeatType = "loop" }) {
 | |
|         const index = repeat && repeatType !== "loop" && repeat % 2 === 1
 | |
|             ? 0
 | |
|             : keyframes.length - 1;
 | |
|         return keyframes[index];
 | |
|     }
 | |
| 
 | |
|     /*
 | |
|       Bezier function generator
 | |
|       This has been modified from Gaëtan Renaudeau's BezierEasing
 | |
|       https://github.com/gre/bezier-easing/blob/master/src/index.js
 | |
|       https://github.com/gre/bezier-easing/blob/master/LICENSE
 | |
|       
 | |
|       I've removed the newtonRaphsonIterate algo because in benchmarking it
 | |
|       wasn't noticiably faster than binarySubdivision, indeed removing it
 | |
|       usually improved times, depending on the curve.
 | |
|       I also removed the lookup table, as for the added bundle size and loop we're
 | |
|       only cutting ~4 or so subdivision iterations. I bumped the max iterations up
 | |
|       to 12 to compensate and this still tended to be faster for no perceivable
 | |
|       loss in accuracy.
 | |
|       Usage
 | |
|         const easeOut = cubicBezier(.17,.67,.83,.67);
 | |
|         const x = easeOut(0.5); // returns 0.627...
 | |
|     */
 | |
|     // Returns x(t) given t, x1, and x2, or y(t) given t, y1, and y2.
 | |
|     const calcBezier = (t, a1, a2) => (((1.0 - 3.0 * a2 + 3.0 * a1) * t + (3.0 * a2 - 6.0 * a1)) * t + 3.0 * a1) *
 | |
|         t;
 | |
|     const subdivisionPrecision = 0.0000001;
 | |
|     const subdivisionMaxIterations = 12;
 | |
|     function binarySubdivide(x, lowerBound, upperBound, mX1, mX2) {
 | |
|         let currentX;
 | |
|         let currentT;
 | |
|         let i = 0;
 | |
|         do {
 | |
|             currentT = lowerBound + (upperBound - lowerBound) / 2.0;
 | |
|             currentX = calcBezier(currentT, mX1, mX2) - x;
 | |
|             if (currentX > 0.0) {
 | |
|                 upperBound = currentT;
 | |
|             }
 | |
|             else {
 | |
|                 lowerBound = currentT;
 | |
|             }
 | |
|         } while (Math.abs(currentX) > subdivisionPrecision &&
 | |
|             ++i < subdivisionMaxIterations);
 | |
|         return currentT;
 | |
|     }
 | |
|     function cubicBezier(mX1, mY1, mX2, mY2) {
 | |
|         // If this is a linear gradient, return linear easing
 | |
|         if (mX1 === mY1 && mX2 === mY2)
 | |
|             return noop;
 | |
|         const getTForX = (aX) => binarySubdivide(aX, 0, 1, mX1, mX2);
 | |
|         // If animation is at start/end, return t without easing
 | |
|         return (t) => t === 0 || t === 1 ? t : calcBezier(getTForX(t), mY1, mY2);
 | |
|     }
 | |
| 
 | |
|     const easeIn = cubicBezier(0.42, 0, 1, 1);
 | |
|     const easeOut = cubicBezier(0, 0, 0.58, 1);
 | |
|     const easeInOut = cubicBezier(0.42, 0, 0.58, 1);
 | |
| 
 | |
|     const isEasingArray = (ease) => {
 | |
|         return Array.isArray(ease) && typeof ease[0] !== "number";
 | |
|     };
 | |
| 
 | |
|     const backOut = cubicBezier(0.33, 1.53, 0.69, 0.99);
 | |
|     const backIn = reverseEasing(backOut);
 | |
|     const backInOut = mirrorEasing(backIn);
 | |
| 
 | |
|     const anticipate = (p) => (p *= 2) < 1 ? 0.5 * backIn(p) : 0.5 * (2 - Math.pow(2, -10 * (p - 1)));
 | |
| 
 | |
|     const easingLookup = {
 | |
|         linear: noop,
 | |
|         easeIn,
 | |
|         easeInOut,
 | |
|         easeOut,
 | |
|         circIn,
 | |
|         circInOut,
 | |
|         circOut,
 | |
|         backIn,
 | |
|         backInOut,
 | |
|         backOut,
 | |
|         anticipate,
 | |
|     };
 | |
|     const easingDefinitionToFunction = (definition) => {
 | |
|         if (Array.isArray(definition)) {
 | |
|             // If cubic bezier definition, create bezier curve
 | |
|             invariant(definition.length === 4, `Cubic bezier arrays must contain four numerical values.`);
 | |
|             const [x1, y1, x2, y2] = definition;
 | |
|             return cubicBezier(x1, y1, x2, y2);
 | |
|         }
 | |
|         else if (typeof definition === "string") {
 | |
|             // Else lookup from table
 | |
|             invariant(easingLookup[definition] !== undefined, `Invalid easing type '${definition}'`);
 | |
|             return easingLookup[definition];
 | |
|         }
 | |
|         return definition;
 | |
|     };
 | |
| 
 | |
|     const clamp = (min, max, v) => Math.min(Math.max(v, min), max);
 | |
| 
 | |
|     const number = {
 | |
|         test: (v) => typeof v === "number",
 | |
|         parse: parseFloat,
 | |
|         transform: (v) => v,
 | |
|     };
 | |
|     const alpha = {
 | |
|         ...number,
 | |
|         transform: (v) => clamp(0, 1, v),
 | |
|     };
 | |
|     const scale = {
 | |
|         ...number,
 | |
|         default: 1,
 | |
|     };
 | |
| 
 | |
|     /**
 | |
|      * Returns true if the provided string is a color, ie rgba(0,0,0,0) or #000,
 | |
|      * but false if a number or multiple colors
 | |
|      */
 | |
|     const isColorString = (type, testProp) => (v) => {
 | |
|         return Boolean((isString(v) && singleColorRegex.test(v) && v.startsWith(type)) ||
 | |
|             (testProp && Object.prototype.hasOwnProperty.call(v, testProp)));
 | |
|     };
 | |
|     const splitColor = (aName, bName, cName) => (v) => {
 | |
|         if (!isString(v))
 | |
|             return v;
 | |
|         const [a, b, c, alpha] = v.match(floatRegex);
 | |
|         return {
 | |
|             [aName]: parseFloat(a),
 | |
|             [bName]: parseFloat(b),
 | |
|             [cName]: parseFloat(c),
 | |
|             alpha: alpha !== undefined ? parseFloat(alpha) : 1,
 | |
|         };
 | |
|     };
 | |
| 
 | |
|     const clampRgbUnit = (v) => clamp(0, 255, v);
 | |
|     const rgbUnit = {
 | |
|         ...number,
 | |
|         transform: (v) => Math.round(clampRgbUnit(v)),
 | |
|     };
 | |
|     const rgba = {
 | |
|         test: isColorString("rgb", "red"),
 | |
|         parse: splitColor("red", "green", "blue"),
 | |
|         transform: ({ red, green, blue, alpha: alpha$1 = 1 }) => "rgba(" +
 | |
|             rgbUnit.transform(red) +
 | |
|             ", " +
 | |
|             rgbUnit.transform(green) +
 | |
|             ", " +
 | |
|             rgbUnit.transform(blue) +
 | |
|             ", " +
 | |
|             sanitize(alpha.transform(alpha$1)) +
 | |
|             ")",
 | |
|     };
 | |
| 
 | |
|     function parseHex(v) {
 | |
|         let r = "";
 | |
|         let g = "";
 | |
|         let b = "";
 | |
|         let a = "";
 | |
|         // If we have 6 characters, ie #FF0000
 | |
|         if (v.length > 5) {
 | |
|             r = v.substring(1, 3);
 | |
|             g = v.substring(3, 5);
 | |
|             b = v.substring(5, 7);
 | |
|             a = v.substring(7, 9);
 | |
|             // Or we have 3 characters, ie #F00
 | |
|         }
 | |
|         else {
 | |
|             r = v.substring(1, 2);
 | |
|             g = v.substring(2, 3);
 | |
|             b = v.substring(3, 4);
 | |
|             a = v.substring(4, 5);
 | |
|             r += r;
 | |
|             g += g;
 | |
|             b += b;
 | |
|             a += a;
 | |
|         }
 | |
|         return {
 | |
|             red: parseInt(r, 16),
 | |
|             green: parseInt(g, 16),
 | |
|             blue: parseInt(b, 16),
 | |
|             alpha: a ? parseInt(a, 16) / 255 : 1,
 | |
|         };
 | |
|     }
 | |
|     const hex = {
 | |
|         test: isColorString("#"),
 | |
|         parse: parseHex,
 | |
|         transform: rgba.transform,
 | |
|     };
 | |
| 
 | |
|     const hsla = {
 | |
|         test: isColorString("hsl", "hue"),
 | |
|         parse: splitColor("hue", "saturation", "lightness"),
 | |
|         transform: ({ hue, saturation, lightness, alpha: alpha$1 = 1 }) => {
 | |
|             return ("hsla(" +
 | |
|                 Math.round(hue) +
 | |
|                 ", " +
 | |
|                 percent.transform(sanitize(saturation)) +
 | |
|                 ", " +
 | |
|                 percent.transform(sanitize(lightness)) +
 | |
|                 ", " +
 | |
|                 sanitize(alpha.transform(alpha$1)) +
 | |
|                 ")");
 | |
|         },
 | |
|     };
 | |
| 
 | |
|     const color = {
 | |
|         test: (v) => rgba.test(v) || hex.test(v) || hsla.test(v),
 | |
|         parse: (v) => {
 | |
|             if (rgba.test(v)) {
 | |
|                 return rgba.parse(v);
 | |
|             }
 | |
|             else if (hsla.test(v)) {
 | |
|                 return hsla.parse(v);
 | |
|             }
 | |
|             else {
 | |
|                 return hex.parse(v);
 | |
|             }
 | |
|         },
 | |
|         transform: (v) => {
 | |
|             return isString(v)
 | |
|                 ? v
 | |
|                 : v.hasOwnProperty("red")
 | |
|                     ? rgba.transform(v)
 | |
|                     : hsla.transform(v);
 | |
|         },
 | |
|     };
 | |
| 
 | |
|     // Adapted from https://gist.github.com/mjackson/5311256
 | |
|     function hueToRgb(p, q, t) {
 | |
|         if (t < 0)
 | |
|             t += 1;
 | |
|         if (t > 1)
 | |
|             t -= 1;
 | |
|         if (t < 1 / 6)
 | |
|             return p + (q - p) * 6 * t;
 | |
|         if (t < 1 / 2)
 | |
|             return q;
 | |
|         if (t < 2 / 3)
 | |
|             return p + (q - p) * (2 / 3 - t) * 6;
 | |
|         return p;
 | |
|     }
 | |
|     function hslaToRgba({ hue, saturation, lightness, alpha }) {
 | |
|         hue /= 360;
 | |
|         saturation /= 100;
 | |
|         lightness /= 100;
 | |
|         let red = 0;
 | |
|         let green = 0;
 | |
|         let blue = 0;
 | |
|         if (!saturation) {
 | |
|             red = green = blue = lightness;
 | |
|         }
 | |
|         else {
 | |
|             const q = lightness < 0.5
 | |
|                 ? lightness * (1 + saturation)
 | |
|                 : lightness + saturation - lightness * saturation;
 | |
|             const p = 2 * lightness - q;
 | |
|             red = hueToRgb(p, q, hue + 1 / 3);
 | |
|             green = hueToRgb(p, q, hue);
 | |
|             blue = hueToRgb(p, q, hue - 1 / 3);
 | |
|         }
 | |
|         return {
 | |
|             red: Math.round(red * 255),
 | |
|             green: Math.round(green * 255),
 | |
|             blue: Math.round(blue * 255),
 | |
|             alpha,
 | |
|         };
 | |
|     }
 | |
| 
 | |
|     // Linear color space blending
 | |
|     // Explained https://www.youtube.com/watch?v=LKnqECcg6Gw
 | |
|     // Demonstrated http://codepen.io/osublake/pen/xGVVaN
 | |
|     const mixLinearColor = (from, to, v) => {
 | |
|         const fromExpo = from * from;
 | |
|         return Math.sqrt(Math.max(0, v * (to * to - fromExpo) + fromExpo));
 | |
|     };
 | |
|     const colorTypes = [hex, rgba, hsla];
 | |
|     const getColorType = (v) => colorTypes.find((type) => type.test(v));
 | |
|     function asRGBA(color) {
 | |
|         const type = getColorType(color);
 | |
|         invariant(Boolean(type), `'${color}' is not an animatable color. Use the equivalent color code instead.`);
 | |
|         let model = type.parse(color);
 | |
|         if (type === hsla) {
 | |
|             // TODO Remove this cast - needed since Framer Motion's stricter typing
 | |
|             model = hslaToRgba(model);
 | |
|         }
 | |
|         return model;
 | |
|     }
 | |
|     const mixColor = (from, to) => {
 | |
|         const fromRGBA = asRGBA(from);
 | |
|         const toRGBA = asRGBA(to);
 | |
|         const blended = { ...fromRGBA };
 | |
|         return (v) => {
 | |
|             blended.red = mixLinearColor(fromRGBA.red, toRGBA.red, v);
 | |
|             blended.green = mixLinearColor(fromRGBA.green, toRGBA.green, v);
 | |
|             blended.blue = mixLinearColor(fromRGBA.blue, toRGBA.blue, v);
 | |
|             blended.alpha = mix(fromRGBA.alpha, toRGBA.alpha, v);
 | |
|             return rgba.transform(blended);
 | |
|         };
 | |
|     };
 | |
| 
 | |
|     /**
 | |
|      * Pipe
 | |
|      * Compose other transformers to run linearily
 | |
|      * pipe(min(20), max(40))
 | |
|      * @param  {...functions} transformers
 | |
|      * @return {function}
 | |
|      */
 | |
|     const combineFunctions = (a, b) => (v) => b(a(v));
 | |
|     const pipe = (...transformers) => transformers.reduce(combineFunctions);
 | |
| 
 | |
|     const checkStringStartsWith = (token) => (key) => typeof key === "string" && key.startsWith(token);
 | |
|     const isCSSVariableName = checkStringStartsWith("--");
 | |
|     const isCSSVariableToken = checkStringStartsWith("var(--");
 | |
|     const cssVariableRegex = /var\s*\(\s*--[\w-]+(\s*,\s*(?:(?:[^)(]|\((?:[^)(]+|\([^)(]*\))*\))*)+)?\s*\)/g;
 | |
| 
 | |
|     function test(v) {
 | |
|         var _a, _b;
 | |
|         return (isNaN(v) &&
 | |
|             isString(v) &&
 | |
|             (((_a = v.match(floatRegex)) === null || _a === void 0 ? void 0 : _a.length) || 0) +
 | |
|                 (((_b = v.match(colorRegex)) === null || _b === void 0 ? void 0 : _b.length) || 0) >
 | |
|                 0);
 | |
|     }
 | |
|     const cssVarTokeniser = {
 | |
|         regex: cssVariableRegex,
 | |
|         countKey: "Vars",
 | |
|         token: "${v}",
 | |
|         parse: noop,
 | |
|     };
 | |
|     const colorTokeniser = {
 | |
|         regex: colorRegex,
 | |
|         countKey: "Colors",
 | |
|         token: "${c}",
 | |
|         parse: color.parse,
 | |
|     };
 | |
|     const numberTokeniser = {
 | |
|         regex: floatRegex,
 | |
|         countKey: "Numbers",
 | |
|         token: "${n}",
 | |
|         parse: number.parse,
 | |
|     };
 | |
|     function tokenise(info, { regex, countKey, token, parse }) {
 | |
|         const matches = info.tokenised.match(regex);
 | |
|         if (!matches)
 | |
|             return;
 | |
|         info["num" + countKey] = matches.length;
 | |
|         info.tokenised = info.tokenised.replace(regex, token);
 | |
|         info.values.push(...matches.map(parse));
 | |
|     }
 | |
|     function analyseComplexValue(value) {
 | |
|         const originalValue = value.toString();
 | |
|         const info = {
 | |
|             value: originalValue,
 | |
|             tokenised: originalValue,
 | |
|             values: [],
 | |
|             numVars: 0,
 | |
|             numColors: 0,
 | |
|             numNumbers: 0,
 | |
|         };
 | |
|         if (info.value.includes("var(--"))
 | |
|             tokenise(info, cssVarTokeniser);
 | |
|         tokenise(info, colorTokeniser);
 | |
|         tokenise(info, numberTokeniser);
 | |
|         return info;
 | |
|     }
 | |
|     function parseComplexValue(v) {
 | |
|         return analyseComplexValue(v).values;
 | |
|     }
 | |
|     function createTransformer(source) {
 | |
|         const { values, numColors, numVars, tokenised } = analyseComplexValue(source);
 | |
|         const numValues = values.length;
 | |
|         return (v) => {
 | |
|             let output = tokenised;
 | |
|             for (let i = 0; i < numValues; i++) {
 | |
|                 if (i < numVars) {
 | |
|                     output = output.replace(cssVarTokeniser.token, v[i]);
 | |
|                 }
 | |
|                 else if (i < numVars + numColors) {
 | |
|                     output = output.replace(colorTokeniser.token, color.transform(v[i]));
 | |
|                 }
 | |
|                 else {
 | |
|                     output = output.replace(numberTokeniser.token, sanitize(v[i]));
 | |
|                 }
 | |
|             }
 | |
|             return output;
 | |
|         };
 | |
|     }
 | |
|     const convertNumbersToZero = (v) => typeof v === "number" ? 0 : v;
 | |
|     function getAnimatableNone$1(v) {
 | |
|         const parsed = parseComplexValue(v);
 | |
|         const transformer = createTransformer(v);
 | |
|         return transformer(parsed.map(convertNumbersToZero));
 | |
|     }
 | |
|     const complex = {
 | |
|         test,
 | |
|         parse: parseComplexValue,
 | |
|         createTransformer,
 | |
|         getAnimatableNone: getAnimatableNone$1,
 | |
|     };
 | |
| 
 | |
|     const mixImmediate = (origin, target) => (p) => `${p > 0 ? target : origin}`;
 | |
|     function getMixer(origin, target) {
 | |
|         if (typeof origin === "number") {
 | |
|             return (v) => mix(origin, target, v);
 | |
|         }
 | |
|         else if (color.test(origin)) {
 | |
|             return mixColor(origin, target);
 | |
|         }
 | |
|         else {
 | |
|             return origin.startsWith("var(")
 | |
|                 ? mixImmediate(origin, target)
 | |
|                 : mixComplex(origin, target);
 | |
|         }
 | |
|     }
 | |
|     const mixArray = (from, to) => {
 | |
|         const output = [...from];
 | |
|         const numValues = output.length;
 | |
|         const blendValue = from.map((fromThis, i) => getMixer(fromThis, to[i]));
 | |
|         return (v) => {
 | |
|             for (let i = 0; i < numValues; i++) {
 | |
|                 output[i] = blendValue[i](v);
 | |
|             }
 | |
|             return output;
 | |
|         };
 | |
|     };
 | |
|     const mixObject = (origin, target) => {
 | |
|         const output = { ...origin, ...target };
 | |
|         const blendValue = {};
 | |
|         for (const key in output) {
 | |
|             if (origin[key] !== undefined && target[key] !== undefined) {
 | |
|                 blendValue[key] = getMixer(origin[key], target[key]);
 | |
|             }
 | |
|         }
 | |
|         return (v) => {
 | |
|             for (const key in blendValue) {
 | |
|                 output[key] = blendValue[key](v);
 | |
|             }
 | |
|             return output;
 | |
|         };
 | |
|     };
 | |
|     const mixComplex = (origin, target) => {
 | |
|         const template = complex.createTransformer(target);
 | |
|         const originStats = analyseComplexValue(origin);
 | |
|         const targetStats = analyseComplexValue(target);
 | |
|         const canInterpolate = originStats.numVars === targetStats.numVars &&
 | |
|             originStats.numColors === targetStats.numColors &&
 | |
|             originStats.numNumbers >= targetStats.numNumbers;
 | |
|         if (canInterpolate) {
 | |
|             return pipe(mixArray(originStats.values, targetStats.values), template);
 | |
|         }
 | |
|         else {
 | |
|             warning(true, `Complex values '${origin}' and '${target}' too different to mix. Ensure all colors are of the same type, and that each contains the same quantity of number and color values. Falling back to instant transition.`);
 | |
|             return mixImmediate(origin, target);
 | |
|         }
 | |
|     };
 | |
| 
 | |
|     const mixNumber = (from, to) => (p) => mix(from, to, p);
 | |
|     function detectMixerFactory(v) {
 | |
|         if (typeof v === "number") {
 | |
|             return mixNumber;
 | |
|         }
 | |
|         else if (typeof v === "string") {
 | |
|             return color.test(v) ? mixColor : mixComplex;
 | |
|         }
 | |
|         else if (Array.isArray(v)) {
 | |
|             return mixArray;
 | |
|         }
 | |
|         else if (typeof v === "object") {
 | |
|             return mixObject;
 | |
|         }
 | |
|         return mixNumber;
 | |
|     }
 | |
|     function createMixers(output, ease, customMixer) {
 | |
|         const mixers = [];
 | |
|         const mixerFactory = customMixer || detectMixerFactory(output[0]);
 | |
|         const numMixers = output.length - 1;
 | |
|         for (let i = 0; i < numMixers; i++) {
 | |
|             let mixer = mixerFactory(output[i], output[i + 1]);
 | |
|             if (ease) {
 | |
|                 const easingFunction = Array.isArray(ease) ? ease[i] || noop : ease;
 | |
|                 mixer = pipe(easingFunction, mixer);
 | |
|             }
 | |
|             mixers.push(mixer);
 | |
|         }
 | |
|         return mixers;
 | |
|     }
 | |
|     /**
 | |
|      * Create a function that maps from a numerical input array to a generic output array.
 | |
|      *
 | |
|      * Accepts:
 | |
|      *   - Numbers
 | |
|      *   - Colors (hex, hsl, hsla, rgb, rgba)
 | |
|      *   - Complex (combinations of one or more numbers or strings)
 | |
|      *
 | |
|      * ```jsx
 | |
|      * const mixColor = interpolate([0, 1], ['#fff', '#000'])
 | |
|      *
 | |
|      * mixColor(0.5) // 'rgba(128, 128, 128, 1)'
 | |
|      * ```
 | |
|      *
 | |
|      * TODO Revist this approach once we've moved to data models for values,
 | |
|      * probably not needed to pregenerate mixer functions.
 | |
|      *
 | |
|      * @public
 | |
|      */
 | |
|     function interpolate(input, output, { clamp: isClamp = true, ease, mixer } = {}) {
 | |
|         const inputLength = input.length;
 | |
|         invariant(inputLength === output.length, "Both input and output ranges must be the same length");
 | |
|         /**
 | |
|          * If we're only provided a single input, we can just make a function
 | |
|          * that returns the output.
 | |
|          */
 | |
|         if (inputLength === 1)
 | |
|             return () => output[0];
 | |
|         // If input runs highest -> lowest, reverse both arrays
 | |
|         if (input[0] > input[inputLength - 1]) {
 | |
|             input = [...input].reverse();
 | |
|             output = [...output].reverse();
 | |
|         }
 | |
|         const mixers = createMixers(output, ease, mixer);
 | |
|         const numMixers = mixers.length;
 | |
|         const interpolator = (v) => {
 | |
|             let i = 0;
 | |
|             if (numMixers > 1) {
 | |
|                 for (; i < input.length - 2; i++) {
 | |
|                     if (v < input[i + 1])
 | |
|                         break;
 | |
|                 }
 | |
|             }
 | |
|             const progressInRange = progress(input[i], input[i + 1], v);
 | |
|             return mixers[i](progressInRange);
 | |
|         };
 | |
|         return isClamp
 | |
|             ? (v) => interpolator(clamp(input[0], input[inputLength - 1], v))
 | |
|             : interpolator;
 | |
|     }
 | |
| 
 | |
|     function fillOffset(offset, remaining) {
 | |
|         const min = offset[offset.length - 1];
 | |
|         for (let i = 1; i <= remaining; i++) {
 | |
|             const offsetProgress = progress(0, remaining, i);
 | |
|             offset.push(mix(min, 1, offsetProgress));
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     function defaultOffset(arr) {
 | |
|         const offset = [0];
 | |
|         fillOffset(offset, arr.length - 1);
 | |
|         return offset;
 | |
|     }
 | |
| 
 | |
|     function convertOffsetToTimes(offset, duration) {
 | |
|         return offset.map((o) => o * duration);
 | |
|     }
 | |
| 
 | |
|     function defaultEasing(values, easing) {
 | |
|         return values.map(() => easing || easeInOut).splice(0, values.length - 1);
 | |
|     }
 | |
|     function keyframes({ duration = 300, keyframes: keyframeValues, times, ease = "easeInOut", }) {
 | |
|         /**
 | |
|          * Easing functions can be externally defined as strings. Here we convert them
 | |
|          * into actual functions.
 | |
|          */
 | |
|         const easingFunctions = isEasingArray(ease)
 | |
|             ? ease.map(easingDefinitionToFunction)
 | |
|             : easingDefinitionToFunction(ease);
 | |
|         /**
 | |
|          * This is the Iterator-spec return value. We ensure it's mutable rather than using a generator
 | |
|          * to reduce GC during animation.
 | |
|          */
 | |
|         const state = {
 | |
|             done: false,
 | |
|             value: keyframeValues[0],
 | |
|         };
 | |
|         /**
 | |
|          * Create a times array based on the provided 0-1 offsets
 | |
|          */
 | |
|         const absoluteTimes = convertOffsetToTimes(
 | |
|         // Only use the provided offsets if they're the correct length
 | |
|         // TODO Maybe we should warn here if there's a length mismatch
 | |
|         times && times.length === keyframeValues.length
 | |
|             ? times
 | |
|             : defaultOffset(keyframeValues), duration);
 | |
|         const mapTimeToKeyframe = interpolate(absoluteTimes, keyframeValues, {
 | |
|             ease: Array.isArray(easingFunctions)
 | |
|                 ? easingFunctions
 | |
|                 : defaultEasing(keyframeValues, easingFunctions),
 | |
|         });
 | |
|         return {
 | |
|             calculatedDuration: duration,
 | |
|             next: (t) => {
 | |
|                 state.value = mapTimeToKeyframe(t);
 | |
|                 state.done = t >= duration;
 | |
|                 return state;
 | |
|             },
 | |
|         };
 | |
|     }
 | |
| 
 | |
|     const velocitySampleDuration = 5; // ms
 | |
|     function calcGeneratorVelocity(resolveValue, t, current) {
 | |
|         const prevT = Math.max(t - velocitySampleDuration, 0);
 | |
|         return velocityPerSecond(current - resolveValue(prevT), t - prevT);
 | |
|     }
 | |
| 
 | |
|     const safeMin = 0.001;
 | |
|     const minDuration = 0.01;
 | |
|     const maxDuration$1 = 10.0;
 | |
|     const minDamping = 0.05;
 | |
|     const maxDamping = 1;
 | |
|     function findSpring({ duration = 800, bounce = 0.25, velocity = 0, mass = 1, }) {
 | |
|         let envelope;
 | |
|         let derivative;
 | |
|         warning(duration <= secondsToMilliseconds(maxDuration$1), "Spring duration must be 10 seconds or less");
 | |
|         let dampingRatio = 1 - bounce;
 | |
|         /**
 | |
|          * Restrict dampingRatio and duration to within acceptable ranges.
 | |
|          */
 | |
|         dampingRatio = clamp(minDamping, maxDamping, dampingRatio);
 | |
|         duration = clamp(minDuration, maxDuration$1, millisecondsToSeconds(duration));
 | |
|         if (dampingRatio < 1) {
 | |
|             /**
 | |
|              * Underdamped spring
 | |
|              */
 | |
|             envelope = (undampedFreq) => {
 | |
|                 const exponentialDecay = undampedFreq * dampingRatio;
 | |
|                 const delta = exponentialDecay * duration;
 | |
|                 const a = exponentialDecay - velocity;
 | |
|                 const b = calcAngularFreq(undampedFreq, dampingRatio);
 | |
|                 const c = Math.exp(-delta);
 | |
|                 return safeMin - (a / b) * c;
 | |
|             };
 | |
|             derivative = (undampedFreq) => {
 | |
|                 const exponentialDecay = undampedFreq * dampingRatio;
 | |
|                 const delta = exponentialDecay * duration;
 | |
|                 const d = delta * velocity + velocity;
 | |
|                 const e = Math.pow(dampingRatio, 2) * Math.pow(undampedFreq, 2) * duration;
 | |
|                 const f = Math.exp(-delta);
 | |
|                 const g = calcAngularFreq(Math.pow(undampedFreq, 2), dampingRatio);
 | |
|                 const factor = -envelope(undampedFreq) + safeMin > 0 ? -1 : 1;
 | |
|                 return (factor * ((d - e) * f)) / g;
 | |
|             };
 | |
|         }
 | |
|         else {
 | |
|             /**
 | |
|              * Critically-damped spring
 | |
|              */
 | |
|             envelope = (undampedFreq) => {
 | |
|                 const a = Math.exp(-undampedFreq * duration);
 | |
|                 const b = (undampedFreq - velocity) * duration + 1;
 | |
|                 return -safeMin + a * b;
 | |
|             };
 | |
|             derivative = (undampedFreq) => {
 | |
|                 const a = Math.exp(-undampedFreq * duration);
 | |
|                 const b = (velocity - undampedFreq) * (duration * duration);
 | |
|                 return a * b;
 | |
|             };
 | |
|         }
 | |
|         const initialGuess = 5 / duration;
 | |
|         const undampedFreq = approximateRoot(envelope, derivative, initialGuess);
 | |
|         duration = secondsToMilliseconds(duration);
 | |
|         if (isNaN(undampedFreq)) {
 | |
|             return {
 | |
|                 stiffness: 100,
 | |
|                 damping: 10,
 | |
|                 duration,
 | |
|             };
 | |
|         }
 | |
|         else {
 | |
|             const stiffness = Math.pow(undampedFreq, 2) * mass;
 | |
|             return {
 | |
|                 stiffness,
 | |
|                 damping: dampingRatio * 2 * Math.sqrt(mass * stiffness),
 | |
|                 duration,
 | |
|             };
 | |
|         }
 | |
|     }
 | |
|     const rootIterations = 12;
 | |
|     function approximateRoot(envelope, derivative, initialGuess) {
 | |
|         let result = initialGuess;
 | |
|         for (let i = 1; i < rootIterations; i++) {
 | |
|             result = result - envelope(result) / derivative(result);
 | |
|         }
 | |
|         return result;
 | |
|     }
 | |
|     function calcAngularFreq(undampedFreq, dampingRatio) {
 | |
|         return undampedFreq * Math.sqrt(1 - dampingRatio * dampingRatio);
 | |
|     }
 | |
| 
 | |
|     const durationKeys = ["duration", "bounce"];
 | |
|     const physicsKeys = ["stiffness", "damping", "mass"];
 | |
|     function isSpringType(options, keys) {
 | |
|         return keys.some((key) => options[key] !== undefined);
 | |
|     }
 | |
|     function getSpringOptions(options) {
 | |
|         let springOptions = {
 | |
|             velocity: 0.0,
 | |
|             stiffness: 100,
 | |
|             damping: 10,
 | |
|             mass: 1.0,
 | |
|             isResolvedFromDuration: false,
 | |
|             ...options,
 | |
|         };
 | |
|         // stiffness/damping/mass overrides duration/bounce
 | |
|         if (!isSpringType(options, physicsKeys) &&
 | |
|             isSpringType(options, durationKeys)) {
 | |
|             const derived = findSpring(options);
 | |
|             springOptions = {
 | |
|                 ...springOptions,
 | |
|                 ...derived,
 | |
|                 mass: 1.0,
 | |
|             };
 | |
|             springOptions.isResolvedFromDuration = true;
 | |
|         }
 | |
|         return springOptions;
 | |
|     }
 | |
|     function spring({ keyframes, restDelta, restSpeed, ...options }) {
 | |
|         const origin = keyframes[0];
 | |
|         const target = keyframes[keyframes.length - 1];
 | |
|         /**
 | |
|          * This is the Iterator-spec return value. We ensure it's mutable rather than using a generator
 | |
|          * to reduce GC during animation.
 | |
|          */
 | |
|         const state = { done: false, value: origin };
 | |
|         const { stiffness, damping, mass, duration, velocity, isResolvedFromDuration, } = getSpringOptions({
 | |
|             ...options,
 | |
|             velocity: -millisecondsToSeconds(options.velocity || 0),
 | |
|         });
 | |
|         const initialVelocity = velocity || 0.0;
 | |
|         const dampingRatio = damping / (2 * Math.sqrt(stiffness * mass));
 | |
|         const initialDelta = target - origin;
 | |
|         const undampedAngularFreq = millisecondsToSeconds(Math.sqrt(stiffness / mass));
 | |
|         /**
 | |
|          * If we're working on a granular scale, use smaller defaults for determining
 | |
|          * when the spring is finished.
 | |
|          *
 | |
|          * These defaults have been selected emprically based on what strikes a good
 | |
|          * ratio between feeling good and finishing as soon as changes are imperceptible.
 | |
|          */
 | |
|         const isGranularScale = Math.abs(initialDelta) < 5;
 | |
|         restSpeed || (restSpeed = isGranularScale ? 0.01 : 2);
 | |
|         restDelta || (restDelta = isGranularScale ? 0.005 : 0.5);
 | |
|         let resolveSpring;
 | |
|         if (dampingRatio < 1) {
 | |
|             const angularFreq = calcAngularFreq(undampedAngularFreq, dampingRatio);
 | |
|             // Underdamped spring
 | |
|             resolveSpring = (t) => {
 | |
|                 const envelope = Math.exp(-dampingRatio * undampedAngularFreq * t);
 | |
|                 return (target -
 | |
|                     envelope *
 | |
|                         (((initialVelocity +
 | |
|                             dampingRatio * undampedAngularFreq * initialDelta) /
 | |
|                             angularFreq) *
 | |
|                             Math.sin(angularFreq * t) +
 | |
|                             initialDelta * Math.cos(angularFreq * t)));
 | |
|             };
 | |
|         }
 | |
|         else if (dampingRatio === 1) {
 | |
|             // Critically damped spring
 | |
|             resolveSpring = (t) => target -
 | |
|                 Math.exp(-undampedAngularFreq * t) *
 | |
|                     (initialDelta +
 | |
|                         (initialVelocity + undampedAngularFreq * initialDelta) * t);
 | |
|         }
 | |
|         else {
 | |
|             // Overdamped spring
 | |
|             const dampedAngularFreq = undampedAngularFreq * Math.sqrt(dampingRatio * dampingRatio - 1);
 | |
|             resolveSpring = (t) => {
 | |
|                 const envelope = Math.exp(-dampingRatio * undampedAngularFreq * t);
 | |
|                 // When performing sinh or cosh values can hit Infinity so we cap them here
 | |
|                 const freqForT = Math.min(dampedAngularFreq * t, 300);
 | |
|                 return (target -
 | |
|                     (envelope *
 | |
|                         ((initialVelocity +
 | |
|                             dampingRatio * undampedAngularFreq * initialDelta) *
 | |
|                             Math.sinh(freqForT) +
 | |
|                             dampedAngularFreq *
 | |
|                                 initialDelta *
 | |
|                                 Math.cosh(freqForT))) /
 | |
|                         dampedAngularFreq);
 | |
|             };
 | |
|         }
 | |
|         return {
 | |
|             calculatedDuration: isResolvedFromDuration ? duration || null : null,
 | |
|             next: (t) => {
 | |
|                 const current = resolveSpring(t);
 | |
|                 if (!isResolvedFromDuration) {
 | |
|                     let currentVelocity = initialVelocity;
 | |
|                     if (t !== 0) {
 | |
|                         /**
 | |
|                          * We only need to calculate velocity for under-damped springs
 | |
|                          * as over- and critically-damped springs can't overshoot, so
 | |
|                          * checking only for displacement is enough.
 | |
|                          */
 | |
|                         if (dampingRatio < 1) {
 | |
|                             currentVelocity = calcGeneratorVelocity(resolveSpring, t, current);
 | |
|                         }
 | |
|                         else {
 | |
|                             currentVelocity = 0;
 | |
|                         }
 | |
|                     }
 | |
|                     const isBelowVelocityThreshold = Math.abs(currentVelocity) <= restSpeed;
 | |
|                     const isBelowDisplacementThreshold = Math.abs(target - current) <= restDelta;
 | |
|                     state.done =
 | |
|                         isBelowVelocityThreshold && isBelowDisplacementThreshold;
 | |
|                 }
 | |
|                 else {
 | |
|                     state.done = t >= duration;
 | |
|                 }
 | |
|                 state.value = state.done ? target : current;
 | |
|                 return state;
 | |
|             },
 | |
|         };
 | |
|     }
 | |
| 
 | |
|     function inertia({ keyframes, velocity = 0.0, power = 0.8, timeConstant = 325, bounceDamping = 10, bounceStiffness = 500, modifyTarget, min, max, restDelta = 0.5, restSpeed, }) {
 | |
|         const origin = keyframes[0];
 | |
|         const state = {
 | |
|             done: false,
 | |
|             value: origin,
 | |
|         };
 | |
|         const isOutOfBounds = (v) => (min !== undefined && v < min) || (max !== undefined && v > max);
 | |
|         const nearestBoundary = (v) => {
 | |
|             if (min === undefined)
 | |
|                 return max;
 | |
|             if (max === undefined)
 | |
|                 return min;
 | |
|             return Math.abs(min - v) < Math.abs(max - v) ? min : max;
 | |
|         };
 | |
|         let amplitude = power * velocity;
 | |
|         const ideal = origin + amplitude;
 | |
|         const target = modifyTarget === undefined ? ideal : modifyTarget(ideal);
 | |
|         /**
 | |
|          * If the target has changed we need to re-calculate the amplitude, otherwise
 | |
|          * the animation will start from the wrong position.
 | |
|          */
 | |
|         if (target !== ideal)
 | |
|             amplitude = target - origin;
 | |
|         const calcDelta = (t) => -amplitude * Math.exp(-t / timeConstant);
 | |
|         const calcLatest = (t) => target + calcDelta(t);
 | |
|         const applyFriction = (t) => {
 | |
|             const delta = calcDelta(t);
 | |
|             const latest = calcLatest(t);
 | |
|             state.done = Math.abs(delta) <= restDelta;
 | |
|             state.value = state.done ? target : latest;
 | |
|         };
 | |
|         /**
 | |
|          * Ideally this would resolve for t in a stateless way, we could
 | |
|          * do that by always precalculating the animation but as we know
 | |
|          * this will be done anyway we can assume that spring will
 | |
|          * be discovered during that.
 | |
|          */
 | |
|         let timeReachedBoundary;
 | |
|         let spring$1;
 | |
|         const checkCatchBoundary = (t) => {
 | |
|             if (!isOutOfBounds(state.value))
 | |
|                 return;
 | |
|             timeReachedBoundary = t;
 | |
|             spring$1 = spring({
 | |
|                 keyframes: [state.value, nearestBoundary(state.value)],
 | |
|                 velocity: calcGeneratorVelocity(calcLatest, t, state.value),
 | |
|                 damping: bounceDamping,
 | |
|                 stiffness: bounceStiffness,
 | |
|                 restDelta,
 | |
|                 restSpeed,
 | |
|             });
 | |
|         };
 | |
|         checkCatchBoundary(0);
 | |
|         return {
 | |
|             calculatedDuration: null,
 | |
|             next: (t) => {
 | |
|                 /**
 | |
|                  * We need to resolve the friction to figure out if we need a
 | |
|                  * spring but we don't want to do this twice per frame. So here
 | |
|                  * we flag if we updated for this frame and later if we did
 | |
|                  * we can skip doing it again.
 | |
|                  */
 | |
|                 let hasUpdatedFrame = false;
 | |
|                 if (!spring$1 && timeReachedBoundary === undefined) {
 | |
|                     hasUpdatedFrame = true;
 | |
|                     applyFriction(t);
 | |
|                     checkCatchBoundary(t);
 | |
|                 }
 | |
|                 /**
 | |
|                  * If we have a spring and the provided t is beyond the moment the friction
 | |
|                  * animation crossed the min/max boundary, use the spring.
 | |
|                  */
 | |
|                 if (timeReachedBoundary !== undefined && t > timeReachedBoundary) {
 | |
|                     return spring$1.next(t - timeReachedBoundary);
 | |
|                 }
 | |
|                 else {
 | |
|                     !hasUpdatedFrame && applyFriction(t);
 | |
|                     return state;
 | |
|                 }
 | |
|             },
 | |
|         };
 | |
|     }
 | |
| 
 | |
|     const frameloopDriver = (update) => {
 | |
|         const passTimestamp = ({ timestamp }) => update(timestamp);
 | |
|         return {
 | |
|             start: () => frame.update(passTimestamp, true),
 | |
|             stop: () => cancelFrame(passTimestamp),
 | |
|             /**
 | |
|              * If we're processing this frame we can use the
 | |
|              * framelocked timestamp to keep things in sync.
 | |
|              */
 | |
|             now: () => frameData.isProcessing ? frameData.timestamp : performance.now(),
 | |
|         };
 | |
|     };
 | |
| 
 | |
|     /**
 | |
|      * Implement a practical max duration for keyframe generation
 | |
|      * to prevent infinite loops
 | |
|      */
 | |
|     const maxGeneratorDuration = 20000;
 | |
|     function calcGeneratorDuration(generator) {
 | |
|         let duration = 0;
 | |
|         const timeStep = 50;
 | |
|         let state = generator.next(duration);
 | |
|         while (!state.done && duration < maxGeneratorDuration) {
 | |
|             duration += timeStep;
 | |
|             state = generator.next(duration);
 | |
|         }
 | |
|         return duration >= maxGeneratorDuration ? Infinity : duration;
 | |
|     }
 | |
| 
 | |
|     const types = {
 | |
|         decay: inertia,
 | |
|         inertia,
 | |
|         tween: keyframes,
 | |
|         keyframes: keyframes,
 | |
|         spring,
 | |
|     };
 | |
|     /**
 | |
|      * Animate a single value on the main thread.
 | |
|      *
 | |
|      * This function is written, where functionality overlaps,
 | |
|      * to be largely spec-compliant with WAAPI to allow fungibility
 | |
|      * between the two.
 | |
|      */
 | |
|     function animateValue({ autoplay = true, delay = 0, driver = frameloopDriver, keyframes: keyframes$1, type = "keyframes", repeat = 0, repeatDelay = 0, repeatType = "loop", onPlay, onStop, onComplete, onUpdate, ...options }) {
 | |
|         let speed = 1;
 | |
|         let hasStopped = false;
 | |
|         let resolveFinishedPromise;
 | |
|         let currentFinishedPromise;
 | |
|         /**
 | |
|          * Resolve the current Promise every time we enter the
 | |
|          * finished state. This is WAAPI-compatible behaviour.
 | |
|          */
 | |
|         const updateFinishedPromise = () => {
 | |
|             currentFinishedPromise = new Promise((resolve) => {
 | |
|                 resolveFinishedPromise = resolve;
 | |
|             });
 | |
|         };
 | |
|         // Create the first finished promise
 | |
|         updateFinishedPromise();
 | |
|         let animationDriver;
 | |
|         const generatorFactory = types[type] || keyframes;
 | |
|         /**
 | |
|          * If this isn't the keyframes generator and we've been provided
 | |
|          * strings as keyframes, we need to interpolate these.
 | |
|          */
 | |
|         let mapNumbersToKeyframes;
 | |
|         if (generatorFactory !== keyframes &&
 | |
|             typeof keyframes$1[0] !== "number") {
 | |
|             {
 | |
|                 invariant(keyframes$1.length === 2, `Only two keyframes currently supported with spring and inertia animations. Trying to animate ${keyframes$1}`);
 | |
|             }
 | |
|             mapNumbersToKeyframes = interpolate([0, 100], keyframes$1, {
 | |
|                 clamp: false,
 | |
|             });
 | |
|             keyframes$1 = [0, 100];
 | |
|         }
 | |
|         const generator = generatorFactory({ ...options, keyframes: keyframes$1 });
 | |
|         let mirroredGenerator;
 | |
|         if (repeatType === "mirror") {
 | |
|             mirroredGenerator = generatorFactory({
 | |
|                 ...options,
 | |
|                 keyframes: [...keyframes$1].reverse(),
 | |
|                 velocity: -(options.velocity || 0),
 | |
|             });
 | |
|         }
 | |
|         let playState = "idle";
 | |
|         let holdTime = null;
 | |
|         let startTime = null;
 | |
|         let cancelTime = null;
 | |
|         /**
 | |
|          * If duration is undefined and we have repeat options,
 | |
|          * we need to calculate a duration from the generator.
 | |
|          *
 | |
|          * We set it to the generator itself to cache the duration.
 | |
|          * Any timeline resolver will need to have already precalculated
 | |
|          * the duration by this step.
 | |
|          */
 | |
|         if (generator.calculatedDuration === null && repeat) {
 | |
|             generator.calculatedDuration = calcGeneratorDuration(generator);
 | |
|         }
 | |
|         const { calculatedDuration } = generator;
 | |
|         let resolvedDuration = Infinity;
 | |
|         let totalDuration = Infinity;
 | |
|         if (calculatedDuration !== null) {
 | |
|             resolvedDuration = calculatedDuration + repeatDelay;
 | |
|             totalDuration = resolvedDuration * (repeat + 1) - repeatDelay;
 | |
|         }
 | |
|         let currentTime = 0;
 | |
|         const tick = (timestamp) => {
 | |
|             if (startTime === null)
 | |
|                 return;
 | |
|             /**
 | |
|              * requestAnimationFrame timestamps can come through as lower than
 | |
|              * the startTime as set by performance.now(). Here we prevent this,
 | |
|              * though in the future it could be possible to make setting startTime
 | |
|              * a pending operation that gets resolved here.
 | |
|              */
 | |
|             if (speed > 0)
 | |
|                 startTime = Math.min(startTime, timestamp);
 | |
|             if (speed < 0)
 | |
|                 startTime = Math.min(timestamp - totalDuration / speed, startTime);
 | |
|             if (holdTime !== null) {
 | |
|                 currentTime = holdTime;
 | |
|             }
 | |
|             else {
 | |
|                 // Rounding the time because floating point arithmetic is not always accurate, e.g. 3000.367 - 1000.367 =
 | |
|                 // 2000.0000000000002. This is a problem when we are comparing the currentTime with the duration, for
 | |
|                 // example.
 | |
|                 currentTime = Math.round(timestamp - startTime) * speed;
 | |
|             }
 | |
|             // Rebase on delay
 | |
|             const timeWithoutDelay = currentTime - delay * (speed >= 0 ? 1 : -1);
 | |
|             const isInDelayPhase = speed >= 0 ? timeWithoutDelay < 0 : timeWithoutDelay > totalDuration;
 | |
|             currentTime = Math.max(timeWithoutDelay, 0);
 | |
|             /**
 | |
|              * If this animation has finished, set the current time
 | |
|              * to the total duration.
 | |
|              */
 | |
|             if (playState === "finished" && holdTime === null) {
 | |
|                 currentTime = totalDuration;
 | |
|             }
 | |
|             let elapsed = currentTime;
 | |
|             let frameGenerator = generator;
 | |
|             if (repeat) {
 | |
|                 /**
 | |
|                  * Get the current progress (0-1) of the animation. If t is >
 | |
|                  * than duration we'll get values like 2.5 (midway through the
 | |
|                  * third iteration)
 | |
|                  */
 | |
|                 const progress = Math.min(currentTime, totalDuration) / resolvedDuration;
 | |
|                 /**
 | |
|                  * Get the current iteration (0 indexed). For instance the floor of
 | |
|                  * 2.5 is 2.
 | |
|                  */
 | |
|                 let currentIteration = Math.floor(progress);
 | |
|                 /**
 | |
|                  * Get the current progress of the iteration by taking the remainder
 | |
|                  * so 2.5 is 0.5 through iteration 2
 | |
|                  */
 | |
|                 let iterationProgress = progress % 1.0;
 | |
|                 /**
 | |
|                  * If iteration progress is 1 we count that as the end
 | |
|                  * of the previous iteration.
 | |
|                  */
 | |
|                 if (!iterationProgress && progress >= 1) {
 | |
|                     iterationProgress = 1;
 | |
|                 }
 | |
|                 iterationProgress === 1 && currentIteration--;
 | |
|                 currentIteration = Math.min(currentIteration, repeat + 1);
 | |
|                 /**
 | |
|                  * Reverse progress if we're not running in "normal" direction
 | |
|                  */
 | |
|                 const isOddIteration = Boolean(currentIteration % 2);
 | |
|                 if (isOddIteration) {
 | |
|                     if (repeatType === "reverse") {
 | |
|                         iterationProgress = 1 - iterationProgress;
 | |
|                         if (repeatDelay) {
 | |
|                             iterationProgress -= repeatDelay / resolvedDuration;
 | |
|                         }
 | |
|                     }
 | |
|                     else if (repeatType === "mirror") {
 | |
|                         frameGenerator = mirroredGenerator;
 | |
|                     }
 | |
|                 }
 | |
|                 elapsed = clamp(0, 1, iterationProgress) * resolvedDuration;
 | |
|             }
 | |
|             /**
 | |
|              * If we're in negative time, set state as the initial keyframe.
 | |
|              * This prevents delay: x, duration: 0 animations from finishing
 | |
|              * instantly.
 | |
|              */
 | |
|             const state = isInDelayPhase
 | |
|                 ? { done: false, value: keyframes$1[0] }
 | |
|                 : frameGenerator.next(elapsed);
 | |
|             if (mapNumbersToKeyframes) {
 | |
|                 state.value = mapNumbersToKeyframes(state.value);
 | |
|             }
 | |
|             let { done } = state;
 | |
|             if (!isInDelayPhase && calculatedDuration !== null) {
 | |
|                 done = speed >= 0 ? currentTime >= totalDuration : currentTime <= 0;
 | |
|             }
 | |
|             const isAnimationFinished = holdTime === null &&
 | |
|                 (playState === "finished" || (playState === "running" && done));
 | |
|             if (onUpdate) {
 | |
|                 onUpdate(state.value);
 | |
|             }
 | |
|             if (isAnimationFinished) {
 | |
|                 finish();
 | |
|             }
 | |
|             return state;
 | |
|         };
 | |
|         const stopAnimationDriver = () => {
 | |
|             animationDriver && animationDriver.stop();
 | |
|             animationDriver = undefined;
 | |
|         };
 | |
|         const cancel = () => {
 | |
|             playState = "idle";
 | |
|             stopAnimationDriver();
 | |
|             resolveFinishedPromise();
 | |
|             updateFinishedPromise();
 | |
|             startTime = cancelTime = null;
 | |
|         };
 | |
|         const finish = () => {
 | |
|             playState = "finished";
 | |
|             onComplete && onComplete();
 | |
|             stopAnimationDriver();
 | |
|             resolveFinishedPromise();
 | |
|         };
 | |
|         const play = () => {
 | |
|             if (hasStopped)
 | |
|                 return;
 | |
|             if (!animationDriver)
 | |
|                 animationDriver = driver(tick);
 | |
|             const now = animationDriver.now();
 | |
|             onPlay && onPlay();
 | |
|             if (holdTime !== null) {
 | |
|                 startTime = now - holdTime;
 | |
|             }
 | |
|             else if (!startTime || playState === "finished") {
 | |
|                 startTime = now;
 | |
|             }
 | |
|             if (playState === "finished") {
 | |
|                 updateFinishedPromise();
 | |
|             }
 | |
|             cancelTime = startTime;
 | |
|             holdTime = null;
 | |
|             /**
 | |
|              * Set playState to running only after we've used it in
 | |
|              * the previous logic.
 | |
|              */
 | |
|             playState = "running";
 | |
|             animationDriver.start();
 | |
|         };
 | |
|         if (autoplay) {
 | |
|             play();
 | |
|         }
 | |
|         const controls = {
 | |
|             then(resolve, reject) {
 | |
|                 return currentFinishedPromise.then(resolve, reject);
 | |
|             },
 | |
|             get time() {
 | |
|                 return millisecondsToSeconds(currentTime);
 | |
|             },
 | |
|             set time(newTime) {
 | |
|                 newTime = secondsToMilliseconds(newTime);
 | |
|                 currentTime = newTime;
 | |
|                 if (holdTime !== null || !animationDriver || speed === 0) {
 | |
|                     holdTime = newTime;
 | |
|                 }
 | |
|                 else {
 | |
|                     startTime = animationDriver.now() - newTime / speed;
 | |
|                 }
 | |
|             },
 | |
|             get duration() {
 | |
|                 const duration = generator.calculatedDuration === null
 | |
|                     ? calcGeneratorDuration(generator)
 | |
|                     : generator.calculatedDuration;
 | |
|                 return millisecondsToSeconds(duration);
 | |
|             },
 | |
|             get speed() {
 | |
|                 return speed;
 | |
|             },
 | |
|             set speed(newSpeed) {
 | |
|                 if (newSpeed === speed || !animationDriver)
 | |
|                     return;
 | |
|                 speed = newSpeed;
 | |
|                 controls.time = millisecondsToSeconds(currentTime);
 | |
|             },
 | |
|             get state() {
 | |
|                 return playState;
 | |
|             },
 | |
|             play,
 | |
|             pause: () => {
 | |
|                 playState = "paused";
 | |
|                 holdTime = currentTime;
 | |
|             },
 | |
|             stop: () => {
 | |
|                 hasStopped = true;
 | |
|                 if (playState === "idle")
 | |
|                     return;
 | |
|                 playState = "idle";
 | |
|                 onStop && onStop();
 | |
|                 cancel();
 | |
|             },
 | |
|             cancel: () => {
 | |
|                 if (cancelTime !== null)
 | |
|                     tick(cancelTime);
 | |
|                 cancel();
 | |
|             },
 | |
|             complete: () => {
 | |
|                 playState = "finished";
 | |
|             },
 | |
|             sample: (elapsed) => {
 | |
|                 startTime = 0;
 | |
|                 return tick(elapsed);
 | |
|             },
 | |
|         };
 | |
|         return controls;
 | |
|     }
 | |
| 
 | |
|     const supportsWaapi = memo(() => Object.hasOwnProperty.call(Element.prototype, "animate"));
 | |
|     /**
 | |
|      * A list of values that can be hardware-accelerated.
 | |
|      */
 | |
|     const acceleratedValues = new Set([
 | |
|         "opacity",
 | |
|         "clipPath",
 | |
|         "filter",
 | |
|         "transform",
 | |
|         "backgroundColor",
 | |
|     ]);
 | |
|     /**
 | |
|      * 10ms is chosen here as it strikes a balance between smooth
 | |
|      * results (more than one keyframe per frame at 60fps) and
 | |
|      * keyframe quantity.
 | |
|      */
 | |
|     const sampleDelta = 10; //ms
 | |
|     /**
 | |
|      * Implement a practical max duration for keyframe generation
 | |
|      * to prevent infinite loops
 | |
|      */
 | |
|     const maxDuration = 20000;
 | |
|     const requiresPregeneratedKeyframes = (valueName, options) => options.type === "spring" ||
 | |
|         valueName === "backgroundColor" ||
 | |
|         !isWaapiSupportedEasing(options.ease);
 | |
|     function createAcceleratedAnimation(value, valueName, { onUpdate, onComplete, ...options }) {
 | |
|         const canAccelerateAnimation = supportsWaapi() &&
 | |
|             acceleratedValues.has(valueName) &&
 | |
|             !options.repeatDelay &&
 | |
|             options.repeatType !== "mirror" &&
 | |
|             options.damping !== 0 &&
 | |
|             options.type !== "inertia";
 | |
|         if (!canAccelerateAnimation)
 | |
|             return false;
 | |
|         /**
 | |
|          * TODO: Unify with js/index
 | |
|          */
 | |
|         let hasStopped = false;
 | |
|         let resolveFinishedPromise;
 | |
|         let currentFinishedPromise;
 | |
|         /**
 | |
|          * Cancelling an animation will write to the DOM. For safety we want to defer
 | |
|          * this until the next `update` frame lifecycle. This flag tracks whether we
 | |
|          * have a pending cancel, if so we shouldn't allow animations to finish.
 | |
|          */
 | |
|         let pendingCancel = false;
 | |
|         /**
 | |
|          * Resolve the current Promise every time we enter the
 | |
|          * finished state. This is WAAPI-compatible behaviour.
 | |
|          */
 | |
|         const updateFinishedPromise = () => {
 | |
|             currentFinishedPromise = new Promise((resolve) => {
 | |
|                 resolveFinishedPromise = resolve;
 | |
|             });
 | |
|         };
 | |
|         // Create the first finished promise
 | |
|         updateFinishedPromise();
 | |
|         let { keyframes, duration = 300, ease, times } = options;
 | |
|         /**
 | |
|          * If this animation needs pre-generated keyframes then generate.
 | |
|          */
 | |
|         if (requiresPregeneratedKeyframes(valueName, options)) {
 | |
|             const sampleAnimation = animateValue({
 | |
|                 ...options,
 | |
|                 repeat: 0,
 | |
|                 delay: 0,
 | |
|             });
 | |
|             let state = { done: false, value: keyframes[0] };
 | |
|             const pregeneratedKeyframes = [];
 | |
|             /**
 | |
|              * Bail after 20 seconds of pre-generated keyframes as it's likely
 | |
|              * we're heading for an infinite loop.
 | |
|              */
 | |
|             let t = 0;
 | |
|             while (!state.done && t < maxDuration) {
 | |
|                 state = sampleAnimation.sample(t);
 | |
|                 pregeneratedKeyframes.push(state.value);
 | |
|                 t += sampleDelta;
 | |
|             }
 | |
|             times = undefined;
 | |
|             keyframes = pregeneratedKeyframes;
 | |
|             duration = t - sampleDelta;
 | |
|             ease = "linear";
 | |
|         }
 | |
|         const animation = animateStyle(value.owner.current, valueName, keyframes, {
 | |
|             ...options,
 | |
|             duration,
 | |
|             /**
 | |
|              * This function is currently not called if ease is provided
 | |
|              * as a function so the cast is safe.
 | |
|              *
 | |
|              * However it would be possible for a future refinement to port
 | |
|              * in easing pregeneration from Motion One for browsers that
 | |
|              * support the upcoming `linear()` easing function.
 | |
|              */
 | |
|             ease: ease,
 | |
|             times,
 | |
|         });
 | |
|         const cancelAnimation = () => {
 | |
|             pendingCancel = false;
 | |
|             animation.cancel();
 | |
|         };
 | |
|         const safeCancel = () => {
 | |
|             pendingCancel = true;
 | |
|             frame.update(cancelAnimation);
 | |
|             resolveFinishedPromise();
 | |
|             updateFinishedPromise();
 | |
|         };
 | |
|         /**
 | |
|          * Prefer the `onfinish` prop as it's more widely supported than
 | |
|          * the `finished` promise.
 | |
|          *
 | |
|          * Here, we synchronously set the provided MotionValue to the end
 | |
|          * keyframe. If we didn't, when the WAAPI animation is finished it would
 | |
|          * be removed from the element which would then revert to its old styles.
 | |
|          */
 | |
|         animation.onfinish = () => {
 | |
|             if (pendingCancel)
 | |
|                 return;
 | |
|             value.set(getFinalKeyframe(keyframes, options));
 | |
|             onComplete && onComplete();
 | |
|             safeCancel();
 | |
|         };
 | |
|         /**
 | |
|          * Animation interrupt callback.
 | |
|          */
 | |
|         const controls = {
 | |
|             then(resolve, reject) {
 | |
|                 return currentFinishedPromise.then(resolve, reject);
 | |
|             },
 | |
|             attachTimeline(timeline) {
 | |
|                 animation.timeline = timeline;
 | |
|                 animation.onfinish = null;
 | |
|                 return noop;
 | |
|             },
 | |
|             get time() {
 | |
|                 return millisecondsToSeconds(animation.currentTime || 0);
 | |
|             },
 | |
|             set time(newTime) {
 | |
|                 animation.currentTime = secondsToMilliseconds(newTime);
 | |
|             },
 | |
|             get speed() {
 | |
|                 return animation.playbackRate;
 | |
|             },
 | |
|             set speed(newSpeed) {
 | |
|                 animation.playbackRate = newSpeed;
 | |
|             },
 | |
|             get duration() {
 | |
|                 return millisecondsToSeconds(duration);
 | |
|             },
 | |
|             play: () => {
 | |
|                 if (hasStopped)
 | |
|                     return;
 | |
|                 animation.play();
 | |
|                 /**
 | |
|                  * Cancel any pending cancel tasks
 | |
|                  */
 | |
|                 cancelFrame(cancelAnimation);
 | |
|             },
 | |
|             pause: () => animation.pause(),
 | |
|             stop: () => {
 | |
|                 hasStopped = true;
 | |
|                 if (animation.playState === "idle")
 | |
|                     return;
 | |
|                 /**
 | |
|                  * WAAPI doesn't natively have any interruption capabilities.
 | |
|                  *
 | |
|                  * Rather than read commited styles back out of the DOM, we can
 | |
|                  * create a renderless JS animation and sample it twice to calculate
 | |
|                  * its current value, "previous" value, and therefore allow
 | |
|                  * Motion to calculate velocity for any subsequent animation.
 | |
|                  */
 | |
|                 const { currentTime } = animation;
 | |
|                 if (currentTime) {
 | |
|                     const sampleAnimation = animateValue({
 | |
|                         ...options,
 | |
|                         autoplay: false,
 | |
|                     });
 | |
|                     value.setWithVelocity(sampleAnimation.sample(currentTime - sampleDelta).value, sampleAnimation.sample(currentTime).value, sampleDelta);
 | |
|                 }
 | |
|                 safeCancel();
 | |
|             },
 | |
|             complete: () => {
 | |
|                 if (pendingCancel)
 | |
|                     return;
 | |
|                 animation.finish();
 | |
|             },
 | |
|             cancel: safeCancel,
 | |
|         };
 | |
|         return controls;
 | |
|     }
 | |
| 
 | |
|     function createInstantAnimation({ keyframes, delay, onUpdate, onComplete, }) {
 | |
|         const setValue = () => {
 | |
|             onUpdate && onUpdate(keyframes[keyframes.length - 1]);
 | |
|             onComplete && onComplete();
 | |
|             /**
 | |
|              * TODO: As this API grows it could make sense to always return
 | |
|              * animateValue. This will be a bigger project as animateValue
 | |
|              * is frame-locked whereas this function resolves instantly.
 | |
|              * This is a behavioural change and also has ramifications regarding
 | |
|              * assumptions within tests.
 | |
|              */
 | |
|             return {
 | |
|                 time: 0,
 | |
|                 speed: 1,
 | |
|                 duration: 0,
 | |
|                 play: (noop),
 | |
|                 pause: (noop),
 | |
|                 stop: (noop),
 | |
|                 then: (resolve) => {
 | |
|                     resolve();
 | |
|                     return Promise.resolve();
 | |
|                 },
 | |
|                 cancel: (noop),
 | |
|                 complete: (noop),
 | |
|             };
 | |
|         };
 | |
|         return delay
 | |
|             ? animateValue({
 | |
|                 keyframes: [0, 1],
 | |
|                 duration: 0,
 | |
|                 delay,
 | |
|                 onComplete: setValue,
 | |
|             })
 | |
|             : setValue();
 | |
|     }
 | |
| 
 | |
|     const underDampedSpring = {
 | |
|         type: "spring",
 | |
|         stiffness: 500,
 | |
|         damping: 25,
 | |
|         restSpeed: 10,
 | |
|     };
 | |
|     const criticallyDampedSpring = (target) => ({
 | |
|         type: "spring",
 | |
|         stiffness: 550,
 | |
|         damping: target === 0 ? 2 * Math.sqrt(550) : 30,
 | |
|         restSpeed: 10,
 | |
|     });
 | |
|     const keyframesTransition = {
 | |
|         type: "keyframes",
 | |
|         duration: 0.8,
 | |
|     };
 | |
|     /**
 | |
|      * Default easing curve is a slightly shallower version of
 | |
|      * the default browser easing curve.
 | |
|      */
 | |
|     const ease = {
 | |
|         type: "keyframes",
 | |
|         ease: [0.25, 0.1, 0.35, 1],
 | |
|         duration: 0.3,
 | |
|     };
 | |
|     const getDefaultTransition = (valueKey, { keyframes }) => {
 | |
|         if (keyframes.length > 2) {
 | |
|             return keyframesTransition;
 | |
|         }
 | |
|         else if (transformProps.has(valueKey)) {
 | |
|             return valueKey.startsWith("scale")
 | |
|                 ? criticallyDampedSpring(keyframes[1])
 | |
|                 : underDampedSpring;
 | |
|         }
 | |
|         return ease;
 | |
|     };
 | |
| 
 | |
|     /**
 | |
|      * Check if a value is animatable. Examples:
 | |
|      *
 | |
|      * ✅: 100, "100px", "#fff"
 | |
|      * ❌: "block", "url(2.jpg)"
 | |
|      * @param value
 | |
|      *
 | |
|      * @internal
 | |
|      */
 | |
|     const isAnimatable = (key, value) => {
 | |
|         // If the list of keys tat might be non-animatable grows, replace with Set
 | |
|         if (key === "zIndex")
 | |
|             return false;
 | |
|         // If it's a number or a keyframes array, we can animate it. We might at some point
 | |
|         // need to do a deep isAnimatable check of keyframes, or let Popmotion handle this,
 | |
|         // but for now lets leave it like this for performance reasons
 | |
|         if (typeof value === "number" || Array.isArray(value))
 | |
|             return true;
 | |
|         if (typeof value === "string" && // It's animatable if we have a string
 | |
|             (complex.test(value) || value === "0") && // And it contains numbers and/or colors
 | |
|             !value.startsWith("url(") // Unless it starts with "url("
 | |
|         ) {
 | |
|             return true;
 | |
|         }
 | |
|         return false;
 | |
|     };
 | |
| 
 | |
|     /**
 | |
|      * Properties that should default to 1 or 100%
 | |
|      */
 | |
|     const maxDefaults = new Set(["brightness", "contrast", "saturate", "opacity"]);
 | |
|     function applyDefaultFilter(v) {
 | |
|         const [name, value] = v.slice(0, -1).split("(");
 | |
|         if (name === "drop-shadow")
 | |
|             return v;
 | |
|         const [number] = value.match(floatRegex) || [];
 | |
|         if (!number)
 | |
|             return v;
 | |
|         const unit = value.replace(number, "");
 | |
|         let defaultValue = maxDefaults.has(name) ? 1 : 0;
 | |
|         if (number !== value)
 | |
|             defaultValue *= 100;
 | |
|         return name + "(" + defaultValue + unit + ")";
 | |
|     }
 | |
|     const functionRegex = /([a-z-]*)\(.*?\)/g;
 | |
|     const filter = {
 | |
|         ...complex,
 | |
|         getAnimatableNone: (v) => {
 | |
|             const functions = v.match(functionRegex);
 | |
|             return functions ? functions.map(applyDefaultFilter).join(" ") : v;
 | |
|         },
 | |
|     };
 | |
| 
 | |
|     const int = {
 | |
|         ...number,
 | |
|         transform: Math.round,
 | |
|     };
 | |
| 
 | |
|     const numberValueTypes = {
 | |
|         // Border props
 | |
|         borderWidth: px,
 | |
|         borderTopWidth: px,
 | |
|         borderRightWidth: px,
 | |
|         borderBottomWidth: px,
 | |
|         borderLeftWidth: px,
 | |
|         borderRadius: px,
 | |
|         radius: px,
 | |
|         borderTopLeftRadius: px,
 | |
|         borderTopRightRadius: px,
 | |
|         borderBottomRightRadius: px,
 | |
|         borderBottomLeftRadius: px,
 | |
|         // Positioning props
 | |
|         width: px,
 | |
|         maxWidth: px,
 | |
|         height: px,
 | |
|         maxHeight: px,
 | |
|         size: px,
 | |
|         top: px,
 | |
|         right: px,
 | |
|         bottom: px,
 | |
|         left: px,
 | |
|         // Spacing props
 | |
|         padding: px,
 | |
|         paddingTop: px,
 | |
|         paddingRight: px,
 | |
|         paddingBottom: px,
 | |
|         paddingLeft: px,
 | |
|         margin: px,
 | |
|         marginTop: px,
 | |
|         marginRight: px,
 | |
|         marginBottom: px,
 | |
|         marginLeft: px,
 | |
|         // Transform props
 | |
|         rotate: degrees,
 | |
|         rotateX: degrees,
 | |
|         rotateY: degrees,
 | |
|         rotateZ: degrees,
 | |
|         scale,
 | |
|         scaleX: scale,
 | |
|         scaleY: scale,
 | |
|         scaleZ: scale,
 | |
|         skew: degrees,
 | |
|         skewX: degrees,
 | |
|         skewY: degrees,
 | |
|         distance: px,
 | |
|         translateX: px,
 | |
|         translateY: px,
 | |
|         translateZ: px,
 | |
|         x: px,
 | |
|         y: px,
 | |
|         z: px,
 | |
|         perspective: px,
 | |
|         transformPerspective: px,
 | |
|         opacity: alpha,
 | |
|         originX: progressPercentage,
 | |
|         originY: progressPercentage,
 | |
|         originZ: px,
 | |
|         // Misc
 | |
|         zIndex: int,
 | |
|         // SVG
 | |
|         fillOpacity: alpha,
 | |
|         strokeOpacity: alpha,
 | |
|         numOctaves: int,
 | |
|     };
 | |
| 
 | |
|     /**
 | |
|      * A map of default value types for common values
 | |
|      */
 | |
|     const defaultValueTypes = {
 | |
|         ...numberValueTypes,
 | |
|         // Color props
 | |
|         color,
 | |
|         backgroundColor: color,
 | |
|         outlineColor: color,
 | |
|         fill: color,
 | |
|         stroke: color,
 | |
|         // Border props
 | |
|         borderColor: color,
 | |
|         borderTopColor: color,
 | |
|         borderRightColor: color,
 | |
|         borderBottomColor: color,
 | |
|         borderLeftColor: color,
 | |
|         filter,
 | |
|         WebkitFilter: filter,
 | |
|     };
 | |
|     /**
 | |
|      * Gets the default ValueType for the provided value key
 | |
|      */
 | |
|     const getDefaultValueType = (key) => defaultValueTypes[key];
 | |
| 
 | |
|     function getAnimatableNone(key, value) {
 | |
|         let defaultValueType = getDefaultValueType(key);
 | |
|         if (defaultValueType !== filter)
 | |
|             defaultValueType = complex;
 | |
|         // If value is not recognised as animatable, ie "none", create an animatable version origin based on the target
 | |
|         return defaultValueType.getAnimatableNone
 | |
|             ? defaultValueType.getAnimatableNone(value)
 | |
|             : undefined;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Check if the value is a zero value string like "0px" or "0%"
 | |
|      */
 | |
|     const isZeroValueString = (v) => /^0[^.\s]+$/.test(v);
 | |
| 
 | |
|     function isNone(value) {
 | |
|         if (typeof value === "number") {
 | |
|             return value === 0;
 | |
|         }
 | |
|         else if (value !== null) {
 | |
|             return value === "none" || value === "0" || isZeroValueString(value);
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     function getKeyframes(value, valueName, target, transition) {
 | |
|         const isTargetAnimatable = isAnimatable(valueName, target);
 | |
|         let keyframes;
 | |
|         if (Array.isArray(target)) {
 | |
|             keyframes = [...target];
 | |
|         }
 | |
|         else {
 | |
|             keyframes = [null, target];
 | |
|         }
 | |
|         const defaultOrigin = transition.from !== undefined ? transition.from : value.get();
 | |
|         let animatableTemplateValue = undefined;
 | |
|         const noneKeyframeIndexes = [];
 | |
|         for (let i = 0; i < keyframes.length; i++) {
 | |
|             /**
 | |
|              * Fill null/wildcard keyframes
 | |
|              */
 | |
|             if (keyframes[i] === null) {
 | |
|                 keyframes[i] = i === 0 ? defaultOrigin : keyframes[i - 1];
 | |
|             }
 | |
|             if (isNone(keyframes[i])) {
 | |
|                 noneKeyframeIndexes.push(i);
 | |
|             }
 | |
|             // TODO: Clean this conditional, it works for now
 | |
|             if (typeof keyframes[i] === "string" &&
 | |
|                 keyframes[i] !== "none" &&
 | |
|                 keyframes[i] !== "0") {
 | |
|                 animatableTemplateValue = keyframes[i];
 | |
|             }
 | |
|         }
 | |
|         if (isTargetAnimatable &&
 | |
|             noneKeyframeIndexes.length &&
 | |
|             animatableTemplateValue) {
 | |
|             for (let i = 0; i < noneKeyframeIndexes.length; i++) {
 | |
|                 const index = noneKeyframeIndexes[i];
 | |
|                 keyframes[index] = getAnimatableNone(valueName, animatableTemplateValue);
 | |
|             }
 | |
|         }
 | |
|         return keyframes;
 | |
|     }
 | |
| 
 | |
|     const MotionGlobalConfig = {
 | |
|         skipAnimations: false,
 | |
|     };
 | |
| 
 | |
|     const animateMotionValue = (valueName, value, target, transition = {}) => {
 | |
|         return (onComplete) => {
 | |
|             const valueTransition = getValueTransition(transition, valueName) || {};
 | |
|             /**
 | |
|              * Most transition values are currently completely overwritten by value-specific
 | |
|              * transitions. In the future it'd be nicer to blend these transitions. But for now
 | |
|              * delay actually does inherit from the root transition if not value-specific.
 | |
|              */
 | |
|             const delay = valueTransition.delay || transition.delay || 0;
 | |
|             /**
 | |
|              * Elapsed isn't a public transition option but can be passed through from
 | |
|              * optimized appear effects in milliseconds.
 | |
|              */
 | |
|             let { elapsed = 0 } = transition;
 | |
|             elapsed = elapsed - secondsToMilliseconds(delay);
 | |
|             const keyframes = getKeyframes(value, valueName, target, valueTransition);
 | |
|             /**
 | |
|              * Check if we're able to animate between the start and end keyframes,
 | |
|              * and throw a warning if we're attempting to animate between one that's
 | |
|              * animatable and another that isn't.
 | |
|              */
 | |
|             const originKeyframe = keyframes[0];
 | |
|             const targetKeyframe = keyframes[keyframes.length - 1];
 | |
|             const isOriginAnimatable = isAnimatable(valueName, originKeyframe);
 | |
|             const isTargetAnimatable = isAnimatable(valueName, targetKeyframe);
 | |
|             warning(isOriginAnimatable === isTargetAnimatable, `You are trying to animate ${valueName} from "${originKeyframe}" to "${targetKeyframe}". ${originKeyframe} is not an animatable value - to enable this animation set ${originKeyframe} to a value animatable to ${targetKeyframe} via the \`style\` property.`);
 | |
|             let options = {
 | |
|                 keyframes,
 | |
|                 velocity: value.getVelocity(),
 | |
|                 ease: "easeOut",
 | |
|                 ...valueTransition,
 | |
|                 delay: -elapsed,
 | |
|                 onUpdate: (v) => {
 | |
|                     value.set(v);
 | |
|                     valueTransition.onUpdate && valueTransition.onUpdate(v);
 | |
|                 },
 | |
|                 onComplete: () => {
 | |
|                     onComplete();
 | |
|                     valueTransition.onComplete && valueTransition.onComplete();
 | |
|                 },
 | |
|             };
 | |
|             /**
 | |
|              * If there's no transition defined for this value, we can generate
 | |
|              * unqiue transition settings for this value.
 | |
|              */
 | |
|             if (!isTransitionDefined(valueTransition)) {
 | |
|                 options = {
 | |
|                     ...options,
 | |
|                     ...getDefaultTransition(valueName, options),
 | |
|                 };
 | |
|             }
 | |
|             /**
 | |
|              * Both WAAPI and our internal animation functions use durations
 | |
|              * as defined by milliseconds, while our external API defines them
 | |
|              * as seconds.
 | |
|              */
 | |
|             if (options.duration) {
 | |
|                 options.duration = secondsToMilliseconds(options.duration);
 | |
|             }
 | |
|             if (options.repeatDelay) {
 | |
|                 options.repeatDelay = secondsToMilliseconds(options.repeatDelay);
 | |
|             }
 | |
|             if (!isOriginAnimatable ||
 | |
|                 !isTargetAnimatable ||
 | |
|                 instantAnimationState.current ||
 | |
|                 valueTransition.type === false ||
 | |
|                 MotionGlobalConfig.skipAnimations) {
 | |
|                 /**
 | |
|                  * If we can't animate this value, or the global instant animation flag is set,
 | |
|                  * or this is simply defined as an instant transition, return an instant transition.
 | |
|                  */
 | |
|                 return createInstantAnimation(instantAnimationState.current
 | |
|                     ? { ...options, delay: 0 }
 | |
|                     : options);
 | |
|             }
 | |
|             /**
 | |
|              * Animate via WAAPI if possible.
 | |
|              */
 | |
|             if (
 | |
|             /**
 | |
|              * If this is a handoff animation, the optimised animation will be running via
 | |
|              * WAAPI. Therefore, this animation must be JS to ensure it runs "under" the
 | |
|              * optimised animation.
 | |
|              */
 | |
|             !transition.isHandoff &&
 | |
|                 value.owner &&
 | |
|                 value.owner.current instanceof HTMLElement &&
 | |
|                 /**
 | |
|                  * If we're outputting values to onUpdate then we can't use WAAPI as there's
 | |
|                  * no way to read the value from WAAPI every frame.
 | |
|                  */
 | |
|                 !value.owner.getProps().onUpdate) {
 | |
|                 const acceleratedAnimation = createAcceleratedAnimation(value, valueName, options);
 | |
|                 if (acceleratedAnimation)
 | |
|                     return acceleratedAnimation;
 | |
|             }
 | |
|             /**
 | |
|              * If we didn't create an accelerated animation, create a JS animation
 | |
|              */
 | |
|             return animateValue(options);
 | |
|         };
 | |
|     };
 | |
| 
 | |
|     function isWillChangeMotionValue(value) {
 | |
|         return Boolean(isMotionValue(value) && value.add);
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Check if value is a numerical string, ie a string that is purely a number eg "100" or "-100.1"
 | |
|      */
 | |
|     const isNumericalString = (v) => /^\-?\d*\.?\d+$/.test(v);
 | |
| 
 | |
|     /**
 | |
|      * Tests a provided value against a ValueType
 | |
|      */
 | |
|     const testValueType = (v) => (type) => type.test(v);
 | |
| 
 | |
|     /**
 | |
|      * ValueType for "auto"
 | |
|      */
 | |
|     const auto = {
 | |
|         test: (v) => v === "auto",
 | |
|         parse: (v) => v,
 | |
|     };
 | |
| 
 | |
|     /**
 | |
|      * A list of value types commonly used for dimensions
 | |
|      */
 | |
|     const dimensionValueTypes = [number, px, percent, degrees, vw, vh, auto];
 | |
|     /**
 | |
|      * Tests a dimensional value against the list of dimension ValueTypes
 | |
|      */
 | |
|     const findDimensionValueType = (v) => dimensionValueTypes.find(testValueType(v));
 | |
| 
 | |
|     /**
 | |
|      * A list of all ValueTypes
 | |
|      */
 | |
|     const valueTypes = [...dimensionValueTypes, color, complex];
 | |
|     /**
 | |
|      * Tests a value against the list of ValueTypes
 | |
|      */
 | |
|     const findValueType = (v) => valueTypes.find(testValueType(v));
 | |
| 
 | |
|     function resolveVariantFromProps(props, definition, custom, currentValues = {}, currentVelocity = {}) {
 | |
|         /**
 | |
|          * If the variant definition is a function, resolve.
 | |
|          */
 | |
|         if (typeof definition === "function") {
 | |
|             definition = definition(custom !== undefined ? custom : props.custom, currentValues, currentVelocity);
 | |
|         }
 | |
|         /**
 | |
|          * If the variant definition is a variant label, or
 | |
|          * the function returned a variant label, resolve.
 | |
|          */
 | |
|         if (typeof definition === "string") {
 | |
|             definition = props.variants && props.variants[definition];
 | |
|         }
 | |
|         /**
 | |
|          * At this point we've resolved both functions and variant labels,
 | |
|          * but the resolved variant label might itself have been a function.
 | |
|          * If so, resolve. This can only have returned a valid target object.
 | |
|          */
 | |
|         if (typeof definition === "function") {
 | |
|             definition = definition(custom !== undefined ? custom : props.custom, currentValues, currentVelocity);
 | |
|         }
 | |
|         return definition;
 | |
|     }
 | |
| 
 | |
|     function checkTargetForNewValues(visualElement, target, origin) {
 | |
|         var _a, _b;
 | |
|         const newValueKeys = Object.keys(target).filter((key) => !visualElement.hasValue(key));
 | |
|         const numNewValues = newValueKeys.length;
 | |
|         if (!numNewValues)
 | |
|             return;
 | |
|         for (let i = 0; i < numNewValues; i++) {
 | |
|             const key = newValueKeys[i];
 | |
|             const targetValue = target[key];
 | |
|             let value = null;
 | |
|             /**
 | |
|              * If the target is a series of keyframes, we can use the first value
 | |
|              * in the array. If this first value is null, we'll still need to read from the DOM.
 | |
|              */
 | |
|             if (Array.isArray(targetValue)) {
 | |
|                 value = targetValue[0];
 | |
|             }
 | |
|             /**
 | |
|              * If the target isn't keyframes, or the first keyframe was null, we need to
 | |
|              * first check if an origin value was explicitly defined in the transition as "from",
 | |
|              * if not read the value from the DOM. As an absolute fallback, take the defined target value.
 | |
|              */
 | |
|             if (value === null) {
 | |
|                 value = (_b = (_a = origin[key]) !== null && _a !== void 0 ? _a : visualElement.readValue(key)) !== null && _b !== void 0 ? _b : target[key];
 | |
|             }
 | |
|             /**
 | |
|              * If value is still undefined or null, ignore it. Preferably this would throw,
 | |
|              * but this was causing issues in Framer.
 | |
|              */
 | |
|             if (value === undefined || value === null)
 | |
|                 continue;
 | |
|             if (typeof value === "string" &&
 | |
|                 (isNumericalString(value) || isZeroValueString(value))) {
 | |
|                 // If this is a number read as a string, ie "0" or "200", convert it to a number
 | |
|                 value = parseFloat(value);
 | |
|             }
 | |
|             else if (!findValueType(value) && complex.test(targetValue)) {
 | |
|                 value = getAnimatableNone(key, targetValue);
 | |
|             }
 | |
|             visualElement.addValue(key, motionValue(value, { owner: visualElement }));
 | |
|             if (origin[key] === undefined) {
 | |
|                 origin[key] = value;
 | |
|             }
 | |
|             if (value !== null)
 | |
|                 visualElement.setBaseTarget(key, value);
 | |
|         }
 | |
|     }
 | |
|     function getOriginFromTransition(key, transition) {
 | |
|         if (!transition)
 | |
|             return;
 | |
|         const valueTransition = transition[key] || transition["default"] || transition;
 | |
|         return valueTransition.from;
 | |
|     }
 | |
|     function getOrigin(target, transition, visualElement) {
 | |
|         const origin = {};
 | |
|         for (const key in target) {
 | |
|             const transitionOrigin = getOriginFromTransition(key, transition);
 | |
|             if (transitionOrigin !== undefined) {
 | |
|                 origin[key] = transitionOrigin;
 | |
|             }
 | |
|             else {
 | |
|                 const value = visualElement.getValue(key);
 | |
|                 if (value) {
 | |
|                     origin[key] = value.get();
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
|         return origin;
 | |
|     }
 | |
| 
 | |
|     function isSVGElement(element) {
 | |
|         return element instanceof SVGElement && element.tagName !== "svg";
 | |
|     }
 | |
| 
 | |
|     function isForcedMotionValue(key, { layout, layoutId }) {
 | |
|         return (transformProps.has(key) ||
 | |
|             key.startsWith("origin") ||
 | |
|             ((layout || layoutId !== undefined) &&
 | |
|                 (!!scaleCorrectors[key] || key === "opacity")));
 | |
|     }
 | |
| 
 | |
|     function scrapeMotionValuesFromProps(props, prevProps) {
 | |
|         const { style } = props;
 | |
|         const newValues = {};
 | |
|         for (const key in style) {
 | |
|             if (isMotionValue(style[key]) ||
 | |
|                 (prevProps.style && isMotionValue(prevProps.style[key])) ||
 | |
|                 isForcedMotionValue(key, props)) {
 | |
|                 newValues[key] = style[key];
 | |
|             }
 | |
|         }
 | |
|         return newValues;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Parse Framer's special CSS variable format into a CSS token and a fallback.
 | |
|      *
 | |
|      * ```
 | |
|      * `var(--foo, #fff)` => [`--foo`, '#fff']
 | |
|      * ```
 | |
|      *
 | |
|      * @param current
 | |
|      */
 | |
|     const splitCSSVariableRegex = /var\((--[a-zA-Z0-9-_]+),? ?([a-zA-Z0-9 ()%#.,-]+)?\)/;
 | |
|     function parseCSSVariable(current) {
 | |
|         const match = splitCSSVariableRegex.exec(current);
 | |
|         if (!match)
 | |
|             return [,];
 | |
|         const [, token, fallback] = match;
 | |
|         return [token, fallback];
 | |
|     }
 | |
|     const maxDepth = 4;
 | |
|     function getVariableValue(current, element, depth = 1) {
 | |
|         invariant(depth <= maxDepth, `Max CSS variable fallback depth detected in property "${current}". This may indicate a circular fallback dependency.`);
 | |
|         const [token, fallback] = parseCSSVariable(current);
 | |
|         // No CSS variable detected
 | |
|         if (!token)
 | |
|             return;
 | |
|         // Attempt to read this CSS variable off the element
 | |
|         const resolved = window.getComputedStyle(element).getPropertyValue(token);
 | |
|         if (resolved) {
 | |
|             const trimmed = resolved.trim();
 | |
|             return isNumericalString(trimmed) ? parseFloat(trimmed) : trimmed;
 | |
|         }
 | |
|         else if (isCSSVariableToken(fallback)) {
 | |
|             // The fallback might itself be a CSS variable, in which case we attempt to resolve it too.
 | |
|             return getVariableValue(fallback, element, depth + 1);
 | |
|         }
 | |
|         else {
 | |
|             return fallback;
 | |
|         }
 | |
|     }
 | |
|     /**
 | |
|      * Resolve CSS variables from
 | |
|      *
 | |
|      * @internal
 | |
|      */
 | |
|     function resolveCSSVariables(visualElement, { ...target }, transitionEnd) {
 | |
|         const element = visualElement.current;
 | |
|         if (!(element instanceof Element))
 | |
|             return { target, transitionEnd };
 | |
|         // If `transitionEnd` isn't `undefined`, clone it. We could clone `target` and `transitionEnd`
 | |
|         // only if they change but I think this reads clearer and this isn't a performance-critical path.
 | |
|         if (transitionEnd) {
 | |
|             transitionEnd = { ...transitionEnd };
 | |
|         }
 | |
|         // Go through existing `MotionValue`s and ensure any existing CSS variables are resolved
 | |
|         visualElement.values.forEach((value) => {
 | |
|             const current = value.get();
 | |
|             if (!isCSSVariableToken(current))
 | |
|                 return;
 | |
|             const resolved = getVariableValue(current, element);
 | |
|             if (resolved)
 | |
|                 value.set(resolved);
 | |
|         });
 | |
|         // Cycle through every target property and resolve CSS variables. Currently
 | |
|         // we only read single-var properties like `var(--foo)`, not `calc(var(--foo) + 20px)`
 | |
|         for (const key in target) {
 | |
|             const current = target[key];
 | |
|             if (!isCSSVariableToken(current))
 | |
|                 continue;
 | |
|             const resolved = getVariableValue(current, element);
 | |
|             if (!resolved)
 | |
|                 continue;
 | |
|             // Clone target if it hasn't already been
 | |
|             target[key] = resolved;
 | |
|             if (!transitionEnd)
 | |
|                 transitionEnd = {};
 | |
|             // If the user hasn't already set this key on `transitionEnd`, set it to the unresolved
 | |
|             // CSS variable. This will ensure that after the animation the component will reflect
 | |
|             // changes in the value of the CSS variable.
 | |
|             if (transitionEnd[key] === undefined) {
 | |
|                 transitionEnd[key] = current;
 | |
|             }
 | |
|         }
 | |
|         return { target, transitionEnd };
 | |
|     }
 | |
| 
 | |
|     const isBrowser = typeof document !== "undefined";
 | |
| 
 | |
|     const positionalKeys = new Set([
 | |
|         "width",
 | |
|         "height",
 | |
|         "top",
 | |
|         "left",
 | |
|         "right",
 | |
|         "bottom",
 | |
|         "x",
 | |
|         "y",
 | |
|         "translateX",
 | |
|         "translateY",
 | |
|     ]);
 | |
|     const isPositionalKey = (key) => positionalKeys.has(key);
 | |
|     const hasPositionalKey = (target) => {
 | |
|         return Object.keys(target).some(isPositionalKey);
 | |
|     };
 | |
|     const isNumOrPxType = (v) => v === number || v === px;
 | |
|     const getPosFromMatrix = (matrix, pos) => parseFloat(matrix.split(", ")[pos]);
 | |
|     const getTranslateFromMatrix = (pos2, pos3) => (_bbox, { transform }) => {
 | |
|         if (transform === "none" || !transform)
 | |
|             return 0;
 | |
|         const matrix3d = transform.match(/^matrix3d\((.+)\)$/);
 | |
|         if (matrix3d) {
 | |
|             return getPosFromMatrix(matrix3d[1], pos3);
 | |
|         }
 | |
|         else {
 | |
|             const matrix = transform.match(/^matrix\((.+)\)$/);
 | |
|             if (matrix) {
 | |
|                 return getPosFromMatrix(matrix[1], pos2);
 | |
|             }
 | |
|             else {
 | |
|                 return 0;
 | |
|             }
 | |
|         }
 | |
|     };
 | |
|     const transformKeys = new Set(["x", "y", "z"]);
 | |
|     const nonTranslationalTransformKeys = transformPropOrder.filter((key) => !transformKeys.has(key));
 | |
|     function removeNonTranslationalTransform(visualElement) {
 | |
|         const removedTransforms = [];
 | |
|         nonTranslationalTransformKeys.forEach((key) => {
 | |
|             const value = visualElement.getValue(key);
 | |
|             if (value !== undefined) {
 | |
|                 removedTransforms.push([key, value.get()]);
 | |
|                 value.set(key.startsWith("scale") ? 1 : 0);
 | |
|             }
 | |
|         });
 | |
|         // Apply changes to element before measurement
 | |
|         if (removedTransforms.length)
 | |
|             visualElement.render();
 | |
|         return removedTransforms;
 | |
|     }
 | |
|     const positionalValues = {
 | |
|         // Dimensions
 | |
|         width: ({ x }, { paddingLeft = "0", paddingRight = "0" }) => x.max - x.min - parseFloat(paddingLeft) - parseFloat(paddingRight),
 | |
|         height: ({ y }, { paddingTop = "0", paddingBottom = "0" }) => y.max - y.min - parseFloat(paddingTop) - parseFloat(paddingBottom),
 | |
|         top: (_bbox, { top }) => parseFloat(top),
 | |
|         left: (_bbox, { left }) => parseFloat(left),
 | |
|         bottom: ({ y }, { top }) => parseFloat(top) + (y.max - y.min),
 | |
|         right: ({ x }, { left }) => parseFloat(left) + (x.max - x.min),
 | |
|         // Transform
 | |
|         x: getTranslateFromMatrix(4, 13),
 | |
|         y: getTranslateFromMatrix(5, 14),
 | |
|     };
 | |
|     // Alias translate longform names
 | |
|     positionalValues.translateX = positionalValues.x;
 | |
|     positionalValues.translateY = positionalValues.y;
 | |
|     const convertChangedValueTypes = (target, visualElement, changedKeys) => {
 | |
|         const originBbox = visualElement.measureViewportBox();
 | |
|         const element = visualElement.current;
 | |
|         const elementComputedStyle = getComputedStyle(element);
 | |
|         const { display } = elementComputedStyle;
 | |
|         const origin = {};
 | |
|         // If the element is currently set to display: "none", make it visible before
 | |
|         // measuring the target bounding box
 | |
|         if (display === "none") {
 | |
|             visualElement.setStaticValue("display", target.display || "block");
 | |
|         }
 | |
|         /**
 | |
|          * Record origins before we render and update styles
 | |
|          */
 | |
|         changedKeys.forEach((key) => {
 | |
|             origin[key] = positionalValues[key](originBbox, elementComputedStyle);
 | |
|         });
 | |
|         // Apply the latest values (as set in checkAndConvertChangedValueTypes)
 | |
|         visualElement.render();
 | |
|         const targetBbox = visualElement.measureViewportBox();
 | |
|         changedKeys.forEach((key) => {
 | |
|             // Restore styles to their **calculated computed style**, not their actual
 | |
|             // originally set style. This allows us to animate between equivalent pixel units.
 | |
|             const value = visualElement.getValue(key);
 | |
|             value && value.jump(origin[key]);
 | |
|             target[key] = positionalValues[key](targetBbox, elementComputedStyle);
 | |
|         });
 | |
|         return target;
 | |
|     };
 | |
|     const checkAndConvertChangedValueTypes = (visualElement, target, origin = {}, transitionEnd = {}) => {
 | |
|         target = { ...target };
 | |
|         transitionEnd = { ...transitionEnd };
 | |
|         const targetPositionalKeys = Object.keys(target).filter(isPositionalKey);
 | |
|         // We want to remove any transform values that could affect the element's bounding box before
 | |
|         // it's measured. We'll reapply these later.
 | |
|         let removedTransformValues = [];
 | |
|         let hasAttemptedToRemoveTransformValues = false;
 | |
|         const changedValueTypeKeys = [];
 | |
|         targetPositionalKeys.forEach((key) => {
 | |
|             const value = visualElement.getValue(key);
 | |
|             if (!visualElement.hasValue(key))
 | |
|                 return;
 | |
|             let from = origin[key];
 | |
|             let fromType = findDimensionValueType(from);
 | |
|             const to = target[key];
 | |
|             let toType;
 | |
|             // TODO: The current implementation of this basically throws an error
 | |
|             // if you try and do value conversion via keyframes. There's probably
 | |
|             // a way of doing this but the performance implications would need greater scrutiny,
 | |
|             // as it'd be doing multiple resize-remeasure operations.
 | |
|             if (isKeyframesTarget(to)) {
 | |
|                 const numKeyframes = to.length;
 | |
|                 const fromIndex = to[0] === null ? 1 : 0;
 | |
|                 from = to[fromIndex];
 | |
|                 fromType = findDimensionValueType(from);
 | |
|                 for (let i = fromIndex; i < numKeyframes; i++) {
 | |
|                     /**
 | |
|                      * Don't allow wildcard keyframes to be used to detect
 | |
|                      * a difference in value types.
 | |
|                      */
 | |
|                     if (to[i] === null)
 | |
|                         break;
 | |
|                     if (!toType) {
 | |
|                         toType = findDimensionValueType(to[i]);
 | |
|                         invariant(toType === fromType ||
 | |
|                             (isNumOrPxType(fromType) && isNumOrPxType(toType)), "Keyframes must be of the same dimension as the current value");
 | |
|                     }
 | |
|                     else {
 | |
|                         invariant(findDimensionValueType(to[i]) === toType, "All keyframes must be of the same type");
 | |
|                     }
 | |
|                 }
 | |
|             }
 | |
|             else {
 | |
|                 toType = findDimensionValueType(to);
 | |
|             }
 | |
|             if (fromType !== toType) {
 | |
|                 // If they're both just number or px, convert them both to numbers rather than
 | |
|                 // relying on resize/remeasure to convert (which is wasteful in this situation)
 | |
|                 if (isNumOrPxType(fromType) && isNumOrPxType(toType)) {
 | |
|                     const current = value.get();
 | |
|                     if (typeof current === "string") {
 | |
|                         value.set(parseFloat(current));
 | |
|                     }
 | |
|                     if (typeof to === "string") {
 | |
|                         target[key] = parseFloat(to);
 | |
|                     }
 | |
|                     else if (Array.isArray(to) && toType === px) {
 | |
|                         target[key] = to.map(parseFloat);
 | |
|                     }
 | |
|                 }
 | |
|                 else if ((fromType === null || fromType === void 0 ? void 0 : fromType.transform) &&
 | |
|                     (toType === null || toType === void 0 ? void 0 : toType.transform) &&
 | |
|                     (from === 0 || to === 0)) {
 | |
|                     // If one or the other value is 0, it's safe to coerce it to the
 | |
|                     // type of the other without measurement
 | |
|                     if (from === 0) {
 | |
|                         value.set(toType.transform(from));
 | |
|                     }
 | |
|                     else {
 | |
|                         target[key] = fromType.transform(to);
 | |
|                     }
 | |
|                 }
 | |
|                 else {
 | |
|                     // If we're going to do value conversion via DOM measurements, we first
 | |
|                     // need to remove non-positional transform values that could affect the bbox measurements.
 | |
|                     if (!hasAttemptedToRemoveTransformValues) {
 | |
|                         removedTransformValues =
 | |
|                             removeNonTranslationalTransform(visualElement);
 | |
|                         hasAttemptedToRemoveTransformValues = true;
 | |
|                     }
 | |
|                     changedValueTypeKeys.push(key);
 | |
|                     transitionEnd[key] =
 | |
|                         transitionEnd[key] !== undefined
 | |
|                             ? transitionEnd[key]
 | |
|                             : target[key];
 | |
|                     value.jump(to);
 | |
|                 }
 | |
|             }
 | |
|         });
 | |
|         if (changedValueTypeKeys.length) {
 | |
|             const scrollY = changedValueTypeKeys.indexOf("height") >= 0
 | |
|                 ? window.pageYOffset
 | |
|                 : null;
 | |
|             const convertedTarget = convertChangedValueTypes(target, visualElement, changedValueTypeKeys);
 | |
|             // If we removed transform values, reapply them before the next render
 | |
|             if (removedTransformValues.length) {
 | |
|                 removedTransformValues.forEach(([key, value]) => {
 | |
|                     visualElement.getValue(key).set(value);
 | |
|                 });
 | |
|             }
 | |
|             // Reapply original values
 | |
|             visualElement.render();
 | |
|             // Restore scroll position
 | |
|             if (isBrowser && scrollY !== null) {
 | |
|                 window.scrollTo({ top: scrollY });
 | |
|             }
 | |
|             return { target: convertedTarget, transitionEnd };
 | |
|         }
 | |
|         else {
 | |
|             return { target, transitionEnd };
 | |
|         }
 | |
|     };
 | |
|     /**
 | |
|      * Convert value types for x/y/width/height/top/left/bottom/right
 | |
|      *
 | |
|      * Allows animation between `'auto'` -> `'100%'` or `0` -> `'calc(50% - 10vw)'`
 | |
|      *
 | |
|      * @internal
 | |
|      */
 | |
|     function unitConversion(visualElement, target, origin, transitionEnd) {
 | |
|         return hasPositionalKey(target)
 | |
|             ? checkAndConvertChangedValueTypes(visualElement, target, origin, transitionEnd)
 | |
|             : { target, transitionEnd };
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Parse a DOM variant to make it animatable. This involves resolving CSS variables
 | |
|      * and ensuring animations like "20%" => "calc(50vw)" are performed in pixels.
 | |
|      */
 | |
|     const parseDomVariant = (visualElement, target, origin, transitionEnd) => {
 | |
|         const resolved = resolveCSSVariables(visualElement, target, transitionEnd);
 | |
|         target = resolved.target;
 | |
|         transitionEnd = resolved.transitionEnd;
 | |
|         return unitConversion(visualElement, target, origin, transitionEnd);
 | |
|     };
 | |
| 
 | |
|     function isRefObject(ref) {
 | |
|         return (ref &&
 | |
|             typeof ref === "object" &&
 | |
|             Object.prototype.hasOwnProperty.call(ref, "current"));
 | |
|     }
 | |
| 
 | |
|     // Does this device prefer reduced motion? Returns `null` server-side.
 | |
|     const prefersReducedMotion = { current: null };
 | |
|     const hasReducedMotionListener = { current: false };
 | |
| 
 | |
|     function initPrefersReducedMotion() {
 | |
|         hasReducedMotionListener.current = true;
 | |
|         if (!isBrowser)
 | |
|             return;
 | |
|         if (window.matchMedia) {
 | |
|             const motionMediaQuery = window.matchMedia("(prefers-reduced-motion)");
 | |
|             const setReducedMotionPreferences = () => (prefersReducedMotion.current = motionMediaQuery.matches);
 | |
|             motionMediaQuery.addListener(setReducedMotionPreferences);
 | |
|             setReducedMotionPreferences();
 | |
|         }
 | |
|         else {
 | |
|             prefersReducedMotion.current = false;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     function isAnimationControls(v) {
 | |
|         return (v !== null &&
 | |
|             typeof v === "object" &&
 | |
|             typeof v.start === "function");
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Decides if the supplied variable is variant label
 | |
|      */
 | |
|     function isVariantLabel(v) {
 | |
|         return typeof v === "string" || Array.isArray(v);
 | |
|     }
 | |
| 
 | |
|     const variantPriorityOrder = [
 | |
|         "animate",
 | |
|         "whileInView",
 | |
|         "whileFocus",
 | |
|         "whileHover",
 | |
|         "whileTap",
 | |
|         "whileDrag",
 | |
|         "exit",
 | |
|     ];
 | |
|     const variantProps = ["initial", ...variantPriorityOrder];
 | |
| 
 | |
|     function isControllingVariants(props) {
 | |
|         return (isAnimationControls(props.animate) ||
 | |
|             variantProps.some((name) => isVariantLabel(props[name])));
 | |
|     }
 | |
|     function isVariantNode(props) {
 | |
|         return Boolean(isControllingVariants(props) || props.variants);
 | |
|     }
 | |
| 
 | |
|     function updateMotionValuesFromProps(element, next, prev) {
 | |
|         const { willChange } = next;
 | |
|         for (const key in next) {
 | |
|             const nextValue = next[key];
 | |
|             const prevValue = prev[key];
 | |
|             if (isMotionValue(nextValue)) {
 | |
|                 /**
 | |
|                  * If this is a motion value found in props or style, we want to add it
 | |
|                  * to our visual element's motion value map.
 | |
|                  */
 | |
|                 element.addValue(key, nextValue);
 | |
|                 if (isWillChangeMotionValue(willChange)) {
 | |
|                     willChange.add(key);
 | |
|                 }
 | |
|                 /**
 | |
|                  * Check the version of the incoming motion value with this version
 | |
|                  * and warn against mismatches.
 | |
|                  */
 | |
|                 {
 | |
|                     warnOnce(nextValue.version === "10.18.0", `Attempting to mix Framer Motion versions ${nextValue.version} with 10.18.0 may not work as expected.`);
 | |
|                 }
 | |
|             }
 | |
|             else if (isMotionValue(prevValue)) {
 | |
|                 /**
 | |
|                  * If we're swapping from a motion value to a static value,
 | |
|                  * create a new motion value from that
 | |
|                  */
 | |
|                 element.addValue(key, motionValue(nextValue, { owner: element }));
 | |
|                 if (isWillChangeMotionValue(willChange)) {
 | |
|                     willChange.remove(key);
 | |
|                 }
 | |
|             }
 | |
|             else if (prevValue !== nextValue) {
 | |
|                 /**
 | |
|                  * If this is a flat value that has changed, update the motion value
 | |
|                  * or create one if it doesn't exist. We only want to do this if we're
 | |
|                  * not handling the value with our animation state.
 | |
|                  */
 | |
|                 if (element.hasValue(key)) {
 | |
|                     const existingValue = element.getValue(key);
 | |
|                     // TODO: Only update values that aren't being animated or even looked at
 | |
|                     !existingValue.hasAnimated && existingValue.set(nextValue);
 | |
|                 }
 | |
|                 else {
 | |
|                     const latestValue = element.getStaticValue(key);
 | |
|                     element.addValue(key, motionValue(latestValue !== undefined ? latestValue : nextValue, { owner: element }));
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
|         // Handle removed values
 | |
|         for (const key in prev) {
 | |
|             if (next[key] === undefined)
 | |
|                 element.removeValue(key);
 | |
|         }
 | |
|         return next;
 | |
|     }
 | |
| 
 | |
|     const featureProps = {
 | |
|         animation: [
 | |
|             "animate",
 | |
|             "variants",
 | |
|             "whileHover",
 | |
|             "whileTap",
 | |
|             "exit",
 | |
|             "whileInView",
 | |
|             "whileFocus",
 | |
|             "whileDrag",
 | |
|         ],
 | |
|         exit: ["exit"],
 | |
|         drag: ["drag", "dragControls"],
 | |
|         focus: ["whileFocus"],
 | |
|         hover: ["whileHover", "onHoverStart", "onHoverEnd"],
 | |
|         tap: ["whileTap", "onTap", "onTapStart", "onTapCancel"],
 | |
|         pan: ["onPan", "onPanStart", "onPanSessionStart", "onPanEnd"],
 | |
|         inView: ["whileInView", "onViewportEnter", "onViewportLeave"],
 | |
|         layout: ["layout", "layoutId"],
 | |
|     };
 | |
|     const featureDefinitions = {};
 | |
|     for (const key in featureProps) {
 | |
|         featureDefinitions[key] = {
 | |
|             isEnabled: (props) => featureProps[key].some((name) => !!props[name]),
 | |
|         };
 | |
|     }
 | |
| 
 | |
|     const featureNames = Object.keys(featureDefinitions);
 | |
|     const numFeatures = featureNames.length;
 | |
|     const propEventHandlers = [
 | |
|         "AnimationStart",
 | |
|         "AnimationComplete",
 | |
|         "Update",
 | |
|         "BeforeLayoutMeasure",
 | |
|         "LayoutMeasure",
 | |
|         "LayoutAnimationStart",
 | |
|         "LayoutAnimationComplete",
 | |
|     ];
 | |
|     const numVariantProps = variantProps.length;
 | |
|     /**
 | |
|      * A VisualElement is an imperative abstraction around UI elements such as
 | |
|      * HTMLElement, SVGElement, Three.Object3D etc.
 | |
|      */
 | |
|     class VisualElement {
 | |
|         constructor({ parent, props, presenceContext, reducedMotionConfig, visualState, }, options = {}) {
 | |
|             /**
 | |
|              * A reference to the current underlying Instance, e.g. a HTMLElement
 | |
|              * or Three.Mesh etc.
 | |
|              */
 | |
|             this.current = null;
 | |
|             /**
 | |
|              * A set containing references to this VisualElement's children.
 | |
|              */
 | |
|             this.children = new Set();
 | |
|             /**
 | |
|              * Determine what role this visual element should take in the variant tree.
 | |
|              */
 | |
|             this.isVariantNode = false;
 | |
|             this.isControllingVariants = false;
 | |
|             /**
 | |
|              * Decides whether this VisualElement should animate in reduced motion
 | |
|              * mode.
 | |
|              *
 | |
|              * TODO: This is currently set on every individual VisualElement but feels
 | |
|              * like it could be set globally.
 | |
|              */
 | |
|             this.shouldReduceMotion = null;
 | |
|             /**
 | |
|              * A map of all motion values attached to this visual element. Motion
 | |
|              * values are source of truth for any given animated value. A motion
 | |
|              * value might be provided externally by the component via props.
 | |
|              */
 | |
|             this.values = new Map();
 | |
|             /**
 | |
|              * Cleanup functions for active features (hover/tap/exit etc)
 | |
|              */
 | |
|             this.features = {};
 | |
|             /**
 | |
|              * A map of every subscription that binds the provided or generated
 | |
|              * motion values onChange listeners to this visual element.
 | |
|              */
 | |
|             this.valueSubscriptions = new Map();
 | |
|             /**
 | |
|              * A reference to the previously-provided motion values as returned
 | |
|              * from scrapeMotionValuesFromProps. We use the keys in here to determine
 | |
|              * if any motion values need to be removed after props are updated.
 | |
|              */
 | |
|             this.prevMotionValues = {};
 | |
|             /**
 | |
|              * An object containing a SubscriptionManager for each active event.
 | |
|              */
 | |
|             this.events = {};
 | |
|             /**
 | |
|              * An object containing an unsubscribe function for each prop event subscription.
 | |
|              * For example, every "Update" event can have multiple subscribers via
 | |
|              * VisualElement.on(), but only one of those can be defined via the onUpdate prop.
 | |
|              */
 | |
|             this.propEventSubscriptions = {};
 | |
|             this.notifyUpdate = () => this.notify("Update", this.latestValues);
 | |
|             this.render = () => {
 | |
|                 if (!this.current)
 | |
|                     return;
 | |
|                 this.triggerBuild();
 | |
|                 this.renderInstance(this.current, this.renderState, this.props.style, this.projection);
 | |
|             };
 | |
|             this.scheduleRender = () => frame.render(this.render, false, true);
 | |
|             const { latestValues, renderState } = visualState;
 | |
|             this.latestValues = latestValues;
 | |
|             this.baseTarget = { ...latestValues };
 | |
|             this.initialValues = props.initial ? { ...latestValues } : {};
 | |
|             this.renderState = renderState;
 | |
|             this.parent = parent;
 | |
|             this.props = props;
 | |
|             this.presenceContext = presenceContext;
 | |
|             this.depth = parent ? parent.depth + 1 : 0;
 | |
|             this.reducedMotionConfig = reducedMotionConfig;
 | |
|             this.options = options;
 | |
|             this.isControllingVariants = isControllingVariants(props);
 | |
|             this.isVariantNode = isVariantNode(props);
 | |
|             if (this.isVariantNode) {
 | |
|                 this.variantChildren = new Set();
 | |
|             }
 | |
|             this.manuallyAnimateOnMount = Boolean(parent && parent.current);
 | |
|             /**
 | |
|              * Any motion values that are provided to the element when created
 | |
|              * aren't yet bound to the element, as this would technically be impure.
 | |
|              * However, we iterate through the motion values and set them to the
 | |
|              * initial values for this component.
 | |
|              *
 | |
|              * TODO: This is impure and we should look at changing this to run on mount.
 | |
|              * Doing so will break some tests but this isn't neccessarily a breaking change,
 | |
|              * more a reflection of the test.
 | |
|              */
 | |
|             const { willChange, ...initialMotionValues } = this.scrapeMotionValuesFromProps(props, {});
 | |
|             for (const key in initialMotionValues) {
 | |
|                 const value = initialMotionValues[key];
 | |
|                 if (latestValues[key] !== undefined && isMotionValue(value)) {
 | |
|                     value.set(latestValues[key], false);
 | |
|                     if (isWillChangeMotionValue(willChange)) {
 | |
|                         willChange.add(key);
 | |
|                     }
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
|         /**
 | |
|          * This method takes React props and returns found MotionValues. For example, HTML
 | |
|          * MotionValues will be found within the style prop, whereas for Three.js within attribute arrays.
 | |
|          *
 | |
|          * This isn't an abstract method as it needs calling in the constructor, but it is
 | |
|          * intended to be one.
 | |
|          */
 | |
|         scrapeMotionValuesFromProps(_props, _prevProps) {
 | |
|             return {};
 | |
|         }
 | |
|         mount(instance) {
 | |
|             this.current = instance;
 | |
|             visualElementStore.set(instance, this);
 | |
|             if (this.projection && !this.projection.instance) {
 | |
|                 this.projection.mount(instance);
 | |
|             }
 | |
|             if (this.parent && this.isVariantNode && !this.isControllingVariants) {
 | |
|                 this.removeFromVariantTree = this.parent.addVariantChild(this);
 | |
|             }
 | |
|             this.values.forEach((value, key) => this.bindToMotionValue(key, value));
 | |
|             if (!hasReducedMotionListener.current) {
 | |
|                 initPrefersReducedMotion();
 | |
|             }
 | |
|             this.shouldReduceMotion =
 | |
|                 this.reducedMotionConfig === "never"
 | |
|                     ? false
 | |
|                     : this.reducedMotionConfig === "always"
 | |
|                         ? true
 | |
|                         : prefersReducedMotion.current;
 | |
|             {
 | |
|                 warnOnce(this.shouldReduceMotion !== true, "You have Reduced Motion enabled on your device. Animations may not appear as expected.");
 | |
|             }
 | |
|             if (this.parent)
 | |
|                 this.parent.children.add(this);
 | |
|             this.update(this.props, this.presenceContext);
 | |
|         }
 | |
|         unmount() {
 | |
|             visualElementStore.delete(this.current);
 | |
|             this.projection && this.projection.unmount();
 | |
|             cancelFrame(this.notifyUpdate);
 | |
|             cancelFrame(this.render);
 | |
|             this.valueSubscriptions.forEach((remove) => remove());
 | |
|             this.removeFromVariantTree && this.removeFromVariantTree();
 | |
|             this.parent && this.parent.children.delete(this);
 | |
|             for (const key in this.events) {
 | |
|                 this.events[key].clear();
 | |
|             }
 | |
|             for (const key in this.features) {
 | |
|                 this.features[key].unmount();
 | |
|             }
 | |
|             this.current = null;
 | |
|         }
 | |
|         bindToMotionValue(key, value) {
 | |
|             const valueIsTransform = transformProps.has(key);
 | |
|             const removeOnChange = value.on("change", (latestValue) => {
 | |
|                 this.latestValues[key] = latestValue;
 | |
|                 this.props.onUpdate &&
 | |
|                     frame.update(this.notifyUpdate, false, true);
 | |
|                 if (valueIsTransform && this.projection) {
 | |
|                     this.projection.isTransformDirty = true;
 | |
|                 }
 | |
|             });
 | |
|             const removeOnRenderRequest = value.on("renderRequest", this.scheduleRender);
 | |
|             this.valueSubscriptions.set(key, () => {
 | |
|                 removeOnChange();
 | |
|                 removeOnRenderRequest();
 | |
|             });
 | |
|         }
 | |
|         sortNodePosition(other) {
 | |
|             /**
 | |
|              * If these nodes aren't even of the same type we can't compare their depth.
 | |
|              */
 | |
|             if (!this.current ||
 | |
|                 !this.sortInstanceNodePosition ||
 | |
|                 this.type !== other.type) {
 | |
|                 return 0;
 | |
|             }
 | |
|             return this.sortInstanceNodePosition(this.current, other.current);
 | |
|         }
 | |
|         loadFeatures({ children, ...renderedProps }, isStrict, preloadedFeatures, initialLayoutGroupConfig) {
 | |
|             let ProjectionNodeConstructor;
 | |
|             let MeasureLayout;
 | |
|             /**
 | |
|              * If we're in development mode, check to make sure we're not rendering a motion component
 | |
|              * as a child of LazyMotion, as this will break the file-size benefits of using it.
 | |
|              */
 | |
|             if (preloadedFeatures &&
 | |
|                 isStrict) {
 | |
|                 const strictMessage = "You have rendered a `motion` component within a `LazyMotion` component. This will break tree shaking. Import and render a `m` component instead.";
 | |
|                 renderedProps.ignoreStrict
 | |
|                     ? warning(false, strictMessage)
 | |
|                     : invariant(false, strictMessage);
 | |
|             }
 | |
|             for (let i = 0; i < numFeatures; i++) {
 | |
|                 const name = featureNames[i];
 | |
|                 const { isEnabled, Feature: FeatureConstructor, ProjectionNode, MeasureLayout: MeasureLayoutComponent, } = featureDefinitions[name];
 | |
|                 if (ProjectionNode)
 | |
|                     ProjectionNodeConstructor = ProjectionNode;
 | |
|                 if (isEnabled(renderedProps)) {
 | |
|                     if (!this.features[name] && FeatureConstructor) {
 | |
|                         this.features[name] = new FeatureConstructor(this);
 | |
|                     }
 | |
|                     if (MeasureLayoutComponent) {
 | |
|                         MeasureLayout = MeasureLayoutComponent;
 | |
|                     }
 | |
|                 }
 | |
|             }
 | |
|             if ((this.type === "html" || this.type === "svg") &&
 | |
|                 !this.projection &&
 | |
|                 ProjectionNodeConstructor) {
 | |
|                 this.projection = new ProjectionNodeConstructor(this.latestValues, this.parent && this.parent.projection);
 | |
|                 const { layoutId, layout, drag, dragConstraints, layoutScroll, layoutRoot, } = renderedProps;
 | |
|                 this.projection.setOptions({
 | |
|                     layoutId,
 | |
|                     layout,
 | |
|                     alwaysMeasureLayout: Boolean(drag) ||
 | |
|                         (dragConstraints && isRefObject(dragConstraints)),
 | |
|                     visualElement: this,
 | |
|                     scheduleRender: () => this.scheduleRender(),
 | |
|                     /**
 | |
|                      * TODO: Update options in an effect. This could be tricky as it'll be too late
 | |
|                      * to update by the time layout animations run.
 | |
|                      * We also need to fix this safeToRemove by linking it up to the one returned by usePresence,
 | |
|                      * ensuring it gets called if there's no potential layout animations.
 | |
|                      *
 | |
|                      */
 | |
|                     animationType: typeof layout === "string" ? layout : "both",
 | |
|                     initialPromotionConfig: initialLayoutGroupConfig,
 | |
|                     layoutScroll,
 | |
|                     layoutRoot,
 | |
|                 });
 | |
|             }
 | |
|             return MeasureLayout;
 | |
|         }
 | |
|         updateFeatures() {
 | |
|             for (const key in this.features) {
 | |
|                 const feature = this.features[key];
 | |
|                 if (feature.isMounted) {
 | |
|                     feature.update();
 | |
|                 }
 | |
|                 else {
 | |
|                     feature.mount();
 | |
|                     feature.isMounted = true;
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
|         triggerBuild() {
 | |
|             this.build(this.renderState, this.latestValues, this.options, this.props);
 | |
|         }
 | |
|         /**
 | |
|          * Measure the current viewport box with or without transforms.
 | |
|          * Only measures axis-aligned boxes, rotate and skew must be manually
 | |
|          * removed with a re-render to work.
 | |
|          */
 | |
|         measureViewportBox() {
 | |
|             return this.current
 | |
|                 ? this.measureInstanceViewportBox(this.current, this.props)
 | |
|                 : createBox();
 | |
|         }
 | |
|         getStaticValue(key) {
 | |
|             return this.latestValues[key];
 | |
|         }
 | |
|         setStaticValue(key, value) {
 | |
|             this.latestValues[key] = value;
 | |
|         }
 | |
|         /**
 | |
|          * Make a target animatable by Popmotion. For instance, if we're
 | |
|          * trying to animate width from 100px to 100vw we need to measure 100vw
 | |
|          * in pixels to determine what we really need to animate to. This is also
 | |
|          * pluggable to support Framer's custom value types like Color,
 | |
|          * and CSS variables.
 | |
|          */
 | |
|         makeTargetAnimatable(target, canMutate = true) {
 | |
|             return this.makeTargetAnimatableFromInstance(target, this.props, canMutate);
 | |
|         }
 | |
|         /**
 | |
|          * Update the provided props. Ensure any newly-added motion values are
 | |
|          * added to our map, old ones removed, and listeners updated.
 | |
|          */
 | |
|         update(props, presenceContext) {
 | |
|             if (props.transformTemplate || this.props.transformTemplate) {
 | |
|                 this.scheduleRender();
 | |
|             }
 | |
|             this.prevProps = this.props;
 | |
|             this.props = props;
 | |
|             this.prevPresenceContext = this.presenceContext;
 | |
|             this.presenceContext = presenceContext;
 | |
|             /**
 | |
|              * Update prop event handlers ie onAnimationStart, onAnimationComplete
 | |
|              */
 | |
|             for (let i = 0; i < propEventHandlers.length; i++) {
 | |
|                 const key = propEventHandlers[i];
 | |
|                 if (this.propEventSubscriptions[key]) {
 | |
|                     this.propEventSubscriptions[key]();
 | |
|                     delete this.propEventSubscriptions[key];
 | |
|                 }
 | |
|                 const listener = props["on" + key];
 | |
|                 if (listener) {
 | |
|                     this.propEventSubscriptions[key] = this.on(key, listener);
 | |
|                 }
 | |
|             }
 | |
|             this.prevMotionValues = updateMotionValuesFromProps(this, this.scrapeMotionValuesFromProps(props, this.prevProps), this.prevMotionValues);
 | |
|             if (this.handleChildMotionValue) {
 | |
|                 this.handleChildMotionValue();
 | |
|             }
 | |
|         }
 | |
|         getProps() {
 | |
|             return this.props;
 | |
|         }
 | |
|         /**
 | |
|          * Returns the variant definition with a given name.
 | |
|          */
 | |
|         getVariant(name) {
 | |
|             return this.props.variants ? this.props.variants[name] : undefined;
 | |
|         }
 | |
|         /**
 | |
|          * Returns the defined default transition on this component.
 | |
|          */
 | |
|         getDefaultTransition() {
 | |
|             return this.props.transition;
 | |
|         }
 | |
|         getTransformPagePoint() {
 | |
|             return this.props.transformPagePoint;
 | |
|         }
 | |
|         getClosestVariantNode() {
 | |
|             return this.isVariantNode
 | |
|                 ? this
 | |
|                 : this.parent
 | |
|                     ? this.parent.getClosestVariantNode()
 | |
|                     : undefined;
 | |
|         }
 | |
|         getVariantContext(startAtParent = false) {
 | |
|             if (startAtParent) {
 | |
|                 return this.parent ? this.parent.getVariantContext() : undefined;
 | |
|             }
 | |
|             if (!this.isControllingVariants) {
 | |
|                 const context = this.parent
 | |
|                     ? this.parent.getVariantContext() || {}
 | |
|                     : {};
 | |
|                 if (this.props.initial !== undefined) {
 | |
|                     context.initial = this.props.initial;
 | |
|                 }
 | |
|                 return context;
 | |
|             }
 | |
|             const context = {};
 | |
|             for (let i = 0; i < numVariantProps; i++) {
 | |
|                 const name = variantProps[i];
 | |
|                 const prop = this.props[name];
 | |
|                 if (isVariantLabel(prop) || prop === false) {
 | |
|                     context[name] = prop;
 | |
|                 }
 | |
|             }
 | |
|             return context;
 | |
|         }
 | |
|         /**
 | |
|          * Add a child visual element to our set of children.
 | |
|          */
 | |
|         addVariantChild(child) {
 | |
|             const closestVariantNode = this.getClosestVariantNode();
 | |
|             if (closestVariantNode) {
 | |
|                 closestVariantNode.variantChildren &&
 | |
|                     closestVariantNode.variantChildren.add(child);
 | |
|                 return () => closestVariantNode.variantChildren.delete(child);
 | |
|             }
 | |
|         }
 | |
|         /**
 | |
|          * Add a motion value and bind it to this visual element.
 | |
|          */
 | |
|         addValue(key, value) {
 | |
|             // Remove existing value if it exists
 | |
|             if (value !== this.values.get(key)) {
 | |
|                 this.removeValue(key);
 | |
|                 this.bindToMotionValue(key, value);
 | |
|             }
 | |
|             this.values.set(key, value);
 | |
|             this.latestValues[key] = value.get();
 | |
|         }
 | |
|         /**
 | |
|          * Remove a motion value and unbind any active subscriptions.
 | |
|          */
 | |
|         removeValue(key) {
 | |
|             this.values.delete(key);
 | |
|             const unsubscribe = this.valueSubscriptions.get(key);
 | |
|             if (unsubscribe) {
 | |
|                 unsubscribe();
 | |
|                 this.valueSubscriptions.delete(key);
 | |
|             }
 | |
|             delete this.latestValues[key];
 | |
|             this.removeValueFromRenderState(key, this.renderState);
 | |
|         }
 | |
|         /**
 | |
|          * Check whether we have a motion value for this key
 | |
|          */
 | |
|         hasValue(key) {
 | |
|             return this.values.has(key);
 | |
|         }
 | |
|         getValue(key, defaultValue) {
 | |
|             if (this.props.values && this.props.values[key]) {
 | |
|                 return this.props.values[key];
 | |
|             }
 | |
|             let value = this.values.get(key);
 | |
|             if (value === undefined && defaultValue !== undefined) {
 | |
|                 value = motionValue(defaultValue, { owner: this });
 | |
|                 this.addValue(key, value);
 | |
|             }
 | |
|             return value;
 | |
|         }
 | |
|         /**
 | |
|          * If we're trying to animate to a previously unencountered value,
 | |
|          * we need to check for it in our state and as a last resort read it
 | |
|          * directly from the instance (which might have performance implications).
 | |
|          */
 | |
|         readValue(key) {
 | |
|             var _a;
 | |
|             return this.latestValues[key] !== undefined || !this.current
 | |
|                 ? this.latestValues[key]
 | |
|                 : (_a = this.getBaseTargetFromProps(this.props, key)) !== null && _a !== void 0 ? _a : this.readValueFromInstance(this.current, key, this.options);
 | |
|         }
 | |
|         /**
 | |
|          * Set the base target to later animate back to. This is currently
 | |
|          * only hydrated on creation and when we first read a value.
 | |
|          */
 | |
|         setBaseTarget(key, value) {
 | |
|             this.baseTarget[key] = value;
 | |
|         }
 | |
|         /**
 | |
|          * Find the base target for a value thats been removed from all animation
 | |
|          * props.
 | |
|          */
 | |
|         getBaseTarget(key) {
 | |
|             var _a;
 | |
|             const { initial } = this.props;
 | |
|             const valueFromInitial = typeof initial === "string" || typeof initial === "object"
 | |
|                 ? (_a = resolveVariantFromProps(this.props, initial)) === null || _a === void 0 ? void 0 : _a[key]
 | |
|                 : undefined;
 | |
|             /**
 | |
|              * If this value still exists in the current initial variant, read that.
 | |
|              */
 | |
|             if (initial && valueFromInitial !== undefined) {
 | |
|                 return valueFromInitial;
 | |
|             }
 | |
|             /**
 | |
|              * Alternatively, if this VisualElement config has defined a getBaseTarget
 | |
|              * so we can read the value from an alternative source, try that.
 | |
|              */
 | |
|             const target = this.getBaseTargetFromProps(this.props, key);
 | |
|             if (target !== undefined && !isMotionValue(target))
 | |
|                 return target;
 | |
|             /**
 | |
|              * If the value was initially defined on initial, but it doesn't any more,
 | |
|              * return undefined. Otherwise return the value as initially read from the DOM.
 | |
|              */
 | |
|             return this.initialValues[key] !== undefined &&
 | |
|                 valueFromInitial === undefined
 | |
|                 ? undefined
 | |
|                 : this.baseTarget[key];
 | |
|         }
 | |
|         on(eventName, callback) {
 | |
|             if (!this.events[eventName]) {
 | |
|                 this.events[eventName] = new SubscriptionManager();
 | |
|             }
 | |
|             return this.events[eventName].add(callback);
 | |
|         }
 | |
|         notify(eventName, ...args) {
 | |
|             if (this.events[eventName]) {
 | |
|                 this.events[eventName].notify(...args);
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     class DOMVisualElement extends VisualElement {
 | |
|         sortInstanceNodePosition(a, b) {
 | |
|             /**
 | |
|              * compareDocumentPosition returns a bitmask, by using the bitwise &
 | |
|              * we're returning true if 2 in that bitmask is set to true. 2 is set
 | |
|              * to true if b preceeds a.
 | |
|              */
 | |
|             return a.compareDocumentPosition(b) & 2 ? 1 : -1;
 | |
|         }
 | |
|         getBaseTargetFromProps(props, key) {
 | |
|             return props.style ? props.style[key] : undefined;
 | |
|         }
 | |
|         removeValueFromRenderState(key, { vars, style }) {
 | |
|             delete vars[key];
 | |
|             delete style[key];
 | |
|         }
 | |
|         makeTargetAnimatableFromInstance({ transition, transitionEnd, ...target }, { transformValues }, isMounted) {
 | |
|             let origin = getOrigin(target, transition || {}, this);
 | |
|             /**
 | |
|              * If Framer has provided a function to convert `Color` etc value types, convert them
 | |
|              */
 | |
|             if (transformValues) {
 | |
|                 if (transitionEnd)
 | |
|                     transitionEnd = transformValues(transitionEnd);
 | |
|                 if (target)
 | |
|                     target = transformValues(target);
 | |
|                 if (origin)
 | |
|                     origin = transformValues(origin);
 | |
|             }
 | |
|             if (isMounted) {
 | |
|                 checkTargetForNewValues(this, target, origin);
 | |
|                 const parsed = parseDomVariant(this, target, origin, transitionEnd);
 | |
|                 transitionEnd = parsed.transitionEnd;
 | |
|                 target = parsed.target;
 | |
|             }
 | |
|             return {
 | |
|                 transition,
 | |
|                 transitionEnd,
 | |
|                 ...target,
 | |
|             };
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     const translateAlias = {
 | |
|         x: "translateX",
 | |
|         y: "translateY",
 | |
|         z: "translateZ",
 | |
|         transformPerspective: "perspective",
 | |
|     };
 | |
|     const numTransforms = transformPropOrder.length;
 | |
|     /**
 | |
|      * Build a CSS transform style from individual x/y/scale etc properties.
 | |
|      *
 | |
|      * This outputs with a default order of transforms/scales/rotations, this can be customised by
 | |
|      * providing a transformTemplate function.
 | |
|      */
 | |
|     function buildTransform(transform, { enableHardwareAcceleration = true, allowTransformNone = true, }, transformIsDefault, transformTemplate) {
 | |
|         // The transform string we're going to build into.
 | |
|         let transformString = "";
 | |
|         /**
 | |
|          * Loop over all possible transforms in order, adding the ones that
 | |
|          * are present to the transform string.
 | |
|          */
 | |
|         for (let i = 0; i < numTransforms; i++) {
 | |
|             const key = transformPropOrder[i];
 | |
|             if (transform[key] !== undefined) {
 | |
|                 const transformName = translateAlias[key] || key;
 | |
|                 transformString += `${transformName}(${transform[key]}) `;
 | |
|             }
 | |
|         }
 | |
|         if (enableHardwareAcceleration && !transform.z) {
 | |
|             transformString += "translateZ(0)";
 | |
|         }
 | |
|         transformString = transformString.trim();
 | |
|         // If we have a custom `transform` template, pass our transform values and
 | |
|         // generated transformString to that before returning
 | |
|         if (transformTemplate) {
 | |
|             transformString = transformTemplate(transform, transformIsDefault ? "" : transformString);
 | |
|         }
 | |
|         else if (allowTransformNone && transformIsDefault) {
 | |
|             transformString = "none";
 | |
|         }
 | |
|         return transformString;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Provided a value and a ValueType, returns the value as that value type.
 | |
|      */
 | |
|     const getValueAsType = (value, type) => {
 | |
|         return type && typeof value === "number"
 | |
|             ? type.transform(value)
 | |
|             : value;
 | |
|     };
 | |
| 
 | |
|     function buildHTMLStyles(state, latestValues, options, transformTemplate) {
 | |
|         const { style, vars, transform, transformOrigin } = state;
 | |
|         // Track whether we encounter any transform or transformOrigin values.
 | |
|         let hasTransform = false;
 | |
|         let hasTransformOrigin = false;
 | |
|         // Does the calculated transform essentially equal "none"?
 | |
|         let transformIsNone = true;
 | |
|         /**
 | |
|          * Loop over all our latest animated values and decide whether to handle them
 | |
|          * as a style or CSS variable.
 | |
|          *
 | |
|          * Transforms and transform origins are kept seperately for further processing.
 | |
|          */
 | |
|         for (const key in latestValues) {
 | |
|             const value = latestValues[key];
 | |
|             /**
 | |
|              * If this is a CSS variable we don't do any further processing.
 | |
|              */
 | |
|             if (isCSSVariableName(key)) {
 | |
|                 vars[key] = value;
 | |
|                 continue;
 | |
|             }
 | |
|             // Convert the value to its default value type, ie 0 -> "0px"
 | |
|             const valueType = numberValueTypes[key];
 | |
|             const valueAsType = getValueAsType(value, valueType);
 | |
|             if (transformProps.has(key)) {
 | |
|                 // If this is a transform, flag to enable further transform processing
 | |
|                 hasTransform = true;
 | |
|                 transform[key] = valueAsType;
 | |
|                 // If we already know we have a non-default transform, early return
 | |
|                 if (!transformIsNone)
 | |
|                     continue;
 | |
|                 // Otherwise check to see if this is a default transform
 | |
|                 if (value !== (valueType.default || 0))
 | |
|                     transformIsNone = false;
 | |
|             }
 | |
|             else if (key.startsWith("origin")) {
 | |
|                 // If this is a transform origin, flag and enable further transform-origin processing
 | |
|                 hasTransformOrigin = true;
 | |
|                 transformOrigin[key] = valueAsType;
 | |
|             }
 | |
|             else {
 | |
|                 style[key] = valueAsType;
 | |
|             }
 | |
|         }
 | |
|         if (!latestValues.transform) {
 | |
|             if (hasTransform || transformTemplate) {
 | |
|                 style.transform = buildTransform(state.transform, options, transformIsNone, transformTemplate);
 | |
|             }
 | |
|             else if (style.transform) {
 | |
|                 /**
 | |
|                  * If we have previously created a transform but currently don't have any,
 | |
|                  * reset transform style to none.
 | |
|                  */
 | |
|                 style.transform = "none";
 | |
|             }
 | |
|         }
 | |
|         /**
 | |
|          * Build a transformOrigin style. Uses the same defaults as the browser for
 | |
|          * undefined origins.
 | |
|          */
 | |
|         if (hasTransformOrigin) {
 | |
|             const { originX = "50%", originY = "50%", originZ = 0, } = transformOrigin;
 | |
|             style.transformOrigin = `${originX} ${originY} ${originZ}`;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     function renderHTML(element, { style, vars }, styleProp, projection) {
 | |
|         Object.assign(element.style, style, projection && projection.getProjectionStyles(styleProp));
 | |
|         // Loop over any CSS variables and assign those.
 | |
|         for (const key in vars) {
 | |
|             element.style.setProperty(key, vars[key]);
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Bounding boxes tend to be defined as top, left, right, bottom. For various operations
 | |
|      * it's easier to consider each axis individually. This function returns a bounding box
 | |
|      * as a map of single-axis min/max values.
 | |
|      */
 | |
|     function convertBoundingBoxToBox({ top, left, right, bottom, }) {
 | |
|         return {
 | |
|             x: { min: left, max: right },
 | |
|             y: { min: top, max: bottom },
 | |
|         };
 | |
|     }
 | |
|     /**
 | |
|      * Applies a TransformPoint function to a bounding box. TransformPoint is usually a function
 | |
|      * provided by Framer to allow measured points to be corrected for device scaling. This is used
 | |
|      * when measuring DOM elements and DOM event points.
 | |
|      */
 | |
|     function transformBoxPoints(point, transformPoint) {
 | |
|         if (!transformPoint)
 | |
|             return point;
 | |
|         const topLeft = transformPoint({ x: point.left, y: point.top });
 | |
|         const bottomRight = transformPoint({ x: point.right, y: point.bottom });
 | |
|         return {
 | |
|             top: topLeft.y,
 | |
|             left: topLeft.x,
 | |
|             bottom: bottomRight.y,
 | |
|             right: bottomRight.x,
 | |
|         };
 | |
|     }
 | |
| 
 | |
|     function measureViewportBox(instance, transformPoint) {
 | |
|         return convertBoundingBoxToBox(transformBoxPoints(instance.getBoundingClientRect(), transformPoint));
 | |
|     }
 | |
| 
 | |
|     function getComputedStyle$1(element) {
 | |
|         return window.getComputedStyle(element);
 | |
|     }
 | |
|     class HTMLVisualElement extends DOMVisualElement {
 | |
|         constructor() {
 | |
|             super(...arguments);
 | |
|             this.type = "html";
 | |
|         }
 | |
|         readValueFromInstance(instance, key) {
 | |
|             if (transformProps.has(key)) {
 | |
|                 const defaultType = getDefaultValueType(key);
 | |
|                 return defaultType ? defaultType.default || 0 : 0;
 | |
|             }
 | |
|             else {
 | |
|                 const computedStyle = getComputedStyle$1(instance);
 | |
|                 const value = (isCSSVariableName(key)
 | |
|                     ? computedStyle.getPropertyValue(key)
 | |
|                     : computedStyle[key]) || 0;
 | |
|                 return typeof value === "string" ? value.trim() : value;
 | |
|             }
 | |
|         }
 | |
|         measureInstanceViewportBox(instance, { transformPagePoint }) {
 | |
|             return measureViewportBox(instance, transformPagePoint);
 | |
|         }
 | |
|         build(renderState, latestValues, options, props) {
 | |
|             buildHTMLStyles(renderState, latestValues, options, props.transformTemplate);
 | |
|         }
 | |
|         scrapeMotionValuesFromProps(props, prevProps) {
 | |
|             return scrapeMotionValuesFromProps(props, prevProps);
 | |
|         }
 | |
|         handleChildMotionValue() {
 | |
|             if (this.childSubscription) {
 | |
|                 this.childSubscription();
 | |
|                 delete this.childSubscription;
 | |
|             }
 | |
|             const { children } = this.props;
 | |
|             if (isMotionValue(children)) {
 | |
|                 this.childSubscription = children.on("change", (latest) => {
 | |
|                     if (this.current)
 | |
|                         this.current.textContent = `${latest}`;
 | |
|                 });
 | |
|             }
 | |
|         }
 | |
|         renderInstance(instance, renderState, styleProp, projection) {
 | |
|             renderHTML(instance, renderState, styleProp, projection);
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     function animateSingleValue(value, keyframes, options) {
 | |
|         const motionValue$1 = isMotionValue(value) ? value : motionValue(value);
 | |
|         motionValue$1.start(animateMotionValue("", motionValue$1, keyframes, options));
 | |
|         return motionValue$1.animation;
 | |
|     }
 | |
| 
 | |
|     const transformAxes = ["", "X", "Y", "Z"];
 | |
|     const hiddenVisibility = { visibility: "hidden" };
 | |
|     /**
 | |
|      * We use 1000 as the animation target as 0-1000 maps better to pixels than 0-1
 | |
|      * which has a noticeable difference in spring animations
 | |
|      */
 | |
|     const animationTarget = 1000;
 | |
|     let id = 0;
 | |
|     /**
 | |
|      * Use a mutable data object for debug data so as to not create a new
 | |
|      * object every frame.
 | |
|      */
 | |
|     const projectionFrameData = {
 | |
|         type: "projectionFrame",
 | |
|         totalNodes: 0,
 | |
|         resolvedTargetDeltas: 0,
 | |
|         recalculatedProjection: 0,
 | |
|     };
 | |
|     function createProjectionNode({ attachResizeListener, defaultParent, measureScroll, checkIsScrollRoot, resetTransform, }) {
 | |
|         return class ProjectionNode {
 | |
|             constructor(latestValues = {}, parent = defaultParent === null || defaultParent === void 0 ? void 0 : defaultParent()) {
 | |
|                 /**
 | |
|                  * A unique ID generated for every projection node.
 | |
|                  */
 | |
|                 this.id = id++;
 | |
|                 /**
 | |
|                  * An id that represents a unique session instigated by startUpdate.
 | |
|                  */
 | |
|                 this.animationId = 0;
 | |
|                 /**
 | |
|                  * A Set containing all this component's children. This is used to iterate
 | |
|                  * through the children.
 | |
|                  *
 | |
|                  * TODO: This could be faster to iterate as a flat array stored on the root node.
 | |
|                  */
 | |
|                 this.children = new Set();
 | |
|                 /**
 | |
|                  * Options for the node. We use this to configure what kind of layout animations
 | |
|                  * we should perform (if any).
 | |
|                  */
 | |
|                 this.options = {};
 | |
|                 /**
 | |
|                  * We use this to detect when its safe to shut down part of a projection tree.
 | |
|                  * We have to keep projecting children for scale correction and relative projection
 | |
|                  * until all their parents stop performing layout animations.
 | |
|                  */
 | |
|                 this.isTreeAnimating = false;
 | |
|                 this.isAnimationBlocked = false;
 | |
|                 /**
 | |
|                  * Flag to true if we think this layout has been changed. We can't always know this,
 | |
|                  * currently we set it to true every time a component renders, or if it has a layoutDependency
 | |
|                  * if that has changed between renders. Additionally, components can be grouped by LayoutGroup
 | |
|                  * and if one node is dirtied, they all are.
 | |
|                  */
 | |
|                 this.isLayoutDirty = false;
 | |
|                 /**
 | |
|                  * Flag to true if we think the projection calculations for this node needs
 | |
|                  * recalculating as a result of an updated transform or layout animation.
 | |
|                  */
 | |
|                 this.isProjectionDirty = false;
 | |
|                 /**
 | |
|                  * Flag to true if the layout *or* transform has changed. This then gets propagated
 | |
|                  * throughout the projection tree, forcing any element below to recalculate on the next frame.
 | |
|                  */
 | |
|                 this.isSharedProjectionDirty = false;
 | |
|                 /**
 | |
|                  * Flag transform dirty. This gets propagated throughout the whole tree but is only
 | |
|                  * respected by shared nodes.
 | |
|                  */
 | |
|                 this.isTransformDirty = false;
 | |
|                 /**
 | |
|                  * Block layout updates for instant layout transitions throughout the tree.
 | |
|                  */
 | |
|                 this.updateManuallyBlocked = false;
 | |
|                 this.updateBlockedByResize = false;
 | |
|                 /**
 | |
|                  * Set to true between the start of the first `willUpdate` call and the end of the `didUpdate`
 | |
|                  * call.
 | |
|                  */
 | |
|                 this.isUpdating = false;
 | |
|                 /**
 | |
|                  * If this is an SVG element we currently disable projection transforms
 | |
|                  */
 | |
|                 this.isSVG = false;
 | |
|                 /**
 | |
|                  * Flag to true (during promotion) if a node doing an instant layout transition needs to reset
 | |
|                  * its projection styles.
 | |
|                  */
 | |
|                 this.needsReset = false;
 | |
|                 /**
 | |
|                  * Flags whether this node should have its transform reset prior to measuring.
 | |
|                  */
 | |
|                 this.shouldResetTransform = false;
 | |
|                 /**
 | |
|                  * An object representing the calculated contextual/accumulated/tree scale.
 | |
|                  * This will be used to scale calculcated projection transforms, as these are
 | |
|                  * calculated in screen-space but need to be scaled for elements to layoutly
 | |
|                  * make it to their calculated destinations.
 | |
|                  *
 | |
|                  * TODO: Lazy-init
 | |
|                  */
 | |
|                 this.treeScale = { x: 1, y: 1 };
 | |
|                 /**
 | |
|                  *
 | |
|                  */
 | |
|                 this.eventHandlers = new Map();
 | |
|                 this.hasTreeAnimated = false;
 | |
|                 // Note: Currently only running on root node
 | |
|                 this.updateScheduled = false;
 | |
|                 this.projectionUpdateScheduled = false;
 | |
|                 this.checkUpdateFailed = () => {
 | |
|                     if (this.isUpdating) {
 | |
|                         this.isUpdating = false;
 | |
|                         this.clearAllSnapshots();
 | |
|                     }
 | |
|                 };
 | |
|                 /**
 | |
|                  * This is a multi-step process as shared nodes might be of different depths. Nodes
 | |
|                  * are sorted by depth order, so we need to resolve the entire tree before moving to
 | |
|                  * the next step.
 | |
|                  */
 | |
|                 this.updateProjection = () => {
 | |
|                     this.projectionUpdateScheduled = false;
 | |
|                     /**
 | |
|                      * Reset debug counts. Manually resetting rather than creating a new
 | |
|                      * object each frame.
 | |
|                      */
 | |
|                     projectionFrameData.totalNodes =
 | |
|                         projectionFrameData.resolvedTargetDeltas =
 | |
|                             projectionFrameData.recalculatedProjection =
 | |
|                                 0;
 | |
|                     this.nodes.forEach(propagateDirtyNodes);
 | |
|                     this.nodes.forEach(resolveTargetDelta);
 | |
|                     this.nodes.forEach(calcProjection);
 | |
|                     this.nodes.forEach(cleanDirtyNodes);
 | |
|                     record(projectionFrameData);
 | |
|                 };
 | |
|                 this.hasProjected = false;
 | |
|                 this.isVisible = true;
 | |
|                 this.animationProgress = 0;
 | |
|                 /**
 | |
|                  * Shared layout
 | |
|                  */
 | |
|                 // TODO Only running on root node
 | |
|                 this.sharedNodes = new Map();
 | |
|                 this.latestValues = latestValues;
 | |
|                 this.root = parent ? parent.root || parent : this;
 | |
|                 this.path = parent ? [...parent.path, parent] : [];
 | |
|                 this.parent = parent;
 | |
|                 this.depth = parent ? parent.depth + 1 : 0;
 | |
|                 for (let i = 0; i < this.path.length; i++) {
 | |
|                     this.path[i].shouldResetTransform = true;
 | |
|                 }
 | |
|                 if (this.root === this)
 | |
|                     this.nodes = new FlatTree();
 | |
|             }
 | |
|             addEventListener(name, handler) {
 | |
|                 if (!this.eventHandlers.has(name)) {
 | |
|                     this.eventHandlers.set(name, new SubscriptionManager());
 | |
|                 }
 | |
|                 return this.eventHandlers.get(name).add(handler);
 | |
|             }
 | |
|             notifyListeners(name, ...args) {
 | |
|                 const subscriptionManager = this.eventHandlers.get(name);
 | |
|                 subscriptionManager && subscriptionManager.notify(...args);
 | |
|             }
 | |
|             hasListeners(name) {
 | |
|                 return this.eventHandlers.has(name);
 | |
|             }
 | |
|             /**
 | |
|              * Lifecycles
 | |
|              */
 | |
|             mount(instance, isLayoutDirty = this.root.hasTreeAnimated) {
 | |
|                 if (this.instance)
 | |
|                     return;
 | |
|                 this.isSVG = isSVGElement(instance);
 | |
|                 this.instance = instance;
 | |
|                 const { layoutId, layout, visualElement } = this.options;
 | |
|                 if (visualElement && !visualElement.current) {
 | |
|                     visualElement.mount(instance);
 | |
|                 }
 | |
|                 this.root.nodes.add(this);
 | |
|                 this.parent && this.parent.children.add(this);
 | |
|                 if (isLayoutDirty && (layout || layoutId)) {
 | |
|                     this.isLayoutDirty = true;
 | |
|                 }
 | |
|                 if (attachResizeListener) {
 | |
|                     let cancelDelay;
 | |
|                     const resizeUnblockUpdate = () => (this.root.updateBlockedByResize = false);
 | |
|                     attachResizeListener(instance, () => {
 | |
|                         this.root.updateBlockedByResize = true;
 | |
|                         cancelDelay && cancelDelay();
 | |
|                         cancelDelay = delay(resizeUnblockUpdate, 250);
 | |
|                         if (globalProjectionState.hasAnimatedSinceResize) {
 | |
|                             globalProjectionState.hasAnimatedSinceResize = false;
 | |
|                             this.nodes.forEach(finishAnimation);
 | |
|                         }
 | |
|                     });
 | |
|                 }
 | |
|                 if (layoutId) {
 | |
|                     this.root.registerSharedNode(layoutId, this);
 | |
|                 }
 | |
|                 // Only register the handler if it requires layout animation
 | |
|                 if (this.options.animate !== false &&
 | |
|                     visualElement &&
 | |
|                     (layoutId || layout)) {
 | |
|                     this.addEventListener("didUpdate", ({ delta, hasLayoutChanged, hasRelativeTargetChanged, layout: newLayout, }) => {
 | |
|                         if (this.isTreeAnimationBlocked()) {
 | |
|                             this.target = undefined;
 | |
|                             this.relativeTarget = undefined;
 | |
|                             return;
 | |
|                         }
 | |
|                         // TODO: Check here if an animation exists
 | |
|                         const layoutTransition = this.options.transition ||
 | |
|                             visualElement.getDefaultTransition() ||
 | |
|                             defaultLayoutTransition;
 | |
|                         const { onLayoutAnimationStart, onLayoutAnimationComplete, } = visualElement.getProps();
 | |
|                         /**
 | |
|                          * The target layout of the element might stay the same,
 | |
|                          * but its position relative to its parent has changed.
 | |
|                          */
 | |
|                         const targetChanged = !this.targetLayout ||
 | |
|                             !boxEqualsRounded(this.targetLayout, newLayout) ||
 | |
|                             hasRelativeTargetChanged;
 | |
|                         /**
 | |
|                          * If the layout hasn't seemed to have changed, it might be that the
 | |
|                          * element is visually in the same place in the document but its position
 | |
|                          * relative to its parent has indeed changed. So here we check for that.
 | |
|                          */
 | |
|                         const hasOnlyRelativeTargetChanged = !hasLayoutChanged && hasRelativeTargetChanged;
 | |
|                         if (this.options.layoutRoot ||
 | |
|                             (this.resumeFrom && this.resumeFrom.instance) ||
 | |
|                             hasOnlyRelativeTargetChanged ||
 | |
|                             (hasLayoutChanged &&
 | |
|                                 (targetChanged || !this.currentAnimation))) {
 | |
|                             if (this.resumeFrom) {
 | |
|                                 this.resumingFrom = this.resumeFrom;
 | |
|                                 this.resumingFrom.resumingFrom = undefined;
 | |
|                             }
 | |
|                             this.setAnimationOrigin(delta, hasOnlyRelativeTargetChanged);
 | |
|                             const animationOptions = {
 | |
|                                 ...getValueTransition(layoutTransition, "layout"),
 | |
|                                 onPlay: onLayoutAnimationStart,
 | |
|                                 onComplete: onLayoutAnimationComplete,
 | |
|                             };
 | |
|                             if (visualElement.shouldReduceMotion ||
 | |
|                                 this.options.layoutRoot) {
 | |
|                                 animationOptions.delay = 0;
 | |
|                                 animationOptions.type = false;
 | |
|                             }
 | |
|                             this.startAnimation(animationOptions);
 | |
|                         }
 | |
|                         else {
 | |
|                             /**
 | |
|                              * If the layout hasn't changed and we have an animation that hasn't started yet,
 | |
|                              * finish it immediately. Otherwise it will be animating from a location
 | |
|                              * that was probably never commited to screen and look like a jumpy box.
 | |
|                              */
 | |
|                             if (!hasLayoutChanged) {
 | |
|                                 finishAnimation(this);
 | |
|                             }
 | |
|                             if (this.isLead() && this.options.onExitComplete) {
 | |
|                                 this.options.onExitComplete();
 | |
|                             }
 | |
|                         }
 | |
|                         this.targetLayout = newLayout;
 | |
|                     });
 | |
|                 }
 | |
|             }
 | |
|             unmount() {
 | |
|                 this.options.layoutId && this.willUpdate();
 | |
|                 this.root.nodes.remove(this);
 | |
|                 const stack = this.getStack();
 | |
|                 stack && stack.remove(this);
 | |
|                 this.parent && this.parent.children.delete(this);
 | |
|                 this.instance = undefined;
 | |
|                 cancelFrame(this.updateProjection);
 | |
|             }
 | |
|             // only on the root
 | |
|             blockUpdate() {
 | |
|                 this.updateManuallyBlocked = true;
 | |
|             }
 | |
|             unblockUpdate() {
 | |
|                 this.updateManuallyBlocked = false;
 | |
|             }
 | |
|             isUpdateBlocked() {
 | |
|                 return this.updateManuallyBlocked || this.updateBlockedByResize;
 | |
|             }
 | |
|             isTreeAnimationBlocked() {
 | |
|                 return (this.isAnimationBlocked ||
 | |
|                     (this.parent && this.parent.isTreeAnimationBlocked()) ||
 | |
|                     false);
 | |
|             }
 | |
|             // Note: currently only running on root node
 | |
|             startUpdate() {
 | |
|                 if (this.isUpdateBlocked())
 | |
|                     return;
 | |
|                 this.isUpdating = true;
 | |
|                 this.nodes && this.nodes.forEach(resetRotation);
 | |
|                 this.animationId++;
 | |
|             }
 | |
|             getTransformTemplate() {
 | |
|                 const { visualElement } = this.options;
 | |
|                 return visualElement && visualElement.getProps().transformTemplate;
 | |
|             }
 | |
|             willUpdate(shouldNotifyListeners = true) {
 | |
|                 this.root.hasTreeAnimated = true;
 | |
|                 if (this.root.isUpdateBlocked()) {
 | |
|                     this.options.onExitComplete && this.options.onExitComplete();
 | |
|                     return;
 | |
|                 }
 | |
|                 !this.root.isUpdating && this.root.startUpdate();
 | |
|                 if (this.isLayoutDirty)
 | |
|                     return;
 | |
|                 this.isLayoutDirty = true;
 | |
|                 for (let i = 0; i < this.path.length; i++) {
 | |
|                     const node = this.path[i];
 | |
|                     node.shouldResetTransform = true;
 | |
|                     node.updateScroll("snapshot");
 | |
|                     if (node.options.layoutRoot) {
 | |
|                         node.willUpdate(false);
 | |
|                     }
 | |
|                 }
 | |
|                 const { layoutId, layout } = this.options;
 | |
|                 if (layoutId === undefined && !layout)
 | |
|                     return;
 | |
|                 const transformTemplate = this.getTransformTemplate();
 | |
|                 this.prevTransformTemplateValue = transformTemplate
 | |
|                     ? transformTemplate(this.latestValues, "")
 | |
|                     : undefined;
 | |
|                 this.updateSnapshot();
 | |
|                 shouldNotifyListeners && this.notifyListeners("willUpdate");
 | |
|             }
 | |
|             update() {
 | |
|                 this.updateScheduled = false;
 | |
|                 const updateWasBlocked = this.isUpdateBlocked();
 | |
|                 // When doing an instant transition, we skip the layout update,
 | |
|                 // but should still clean up the measurements so that the next
 | |
|                 // snapshot could be taken correctly.
 | |
|                 if (updateWasBlocked) {
 | |
|                     this.unblockUpdate();
 | |
|                     this.clearAllSnapshots();
 | |
|                     this.nodes.forEach(clearMeasurements);
 | |
|                     return;
 | |
|                 }
 | |
|                 if (!this.isUpdating) {
 | |
|                     this.nodes.forEach(clearIsLayoutDirty);
 | |
|                 }
 | |
|                 this.isUpdating = false;
 | |
|                 /**
 | |
|                  * Write
 | |
|                  */
 | |
|                 this.nodes.forEach(resetTransformStyle);
 | |
|                 /**
 | |
|                  * Read ==================
 | |
|                  */
 | |
|                 // Update layout measurements of updated children
 | |
|                 this.nodes.forEach(updateLayout);
 | |
|                 /**
 | |
|                  * Write
 | |
|                  */
 | |
|                 // Notify listeners that the layout is updated
 | |
|                 this.nodes.forEach(notifyLayoutUpdate);
 | |
|                 this.clearAllSnapshots();
 | |
|                 /**
 | |
|                  * Manually flush any pending updates. Ideally
 | |
|                  * we could leave this to the following requestAnimationFrame but this seems
 | |
|                  * to leave a flash of incorrectly styled content.
 | |
|                  */
 | |
|                 const now = performance.now();
 | |
|                 frameData.delta = clamp(0, 1000 / 60, now - frameData.timestamp);
 | |
|                 frameData.timestamp = now;
 | |
|                 frameData.isProcessing = true;
 | |
|                 steps.update.process(frameData);
 | |
|                 steps.preRender.process(frameData);
 | |
|                 steps.render.process(frameData);
 | |
|                 frameData.isProcessing = false;
 | |
|             }
 | |
|             didUpdate() {
 | |
|                 if (!this.updateScheduled) {
 | |
|                     this.updateScheduled = true;
 | |
|                     queueMicrotask(() => this.update());
 | |
|                 }
 | |
|             }
 | |
|             clearAllSnapshots() {
 | |
|                 this.nodes.forEach(clearSnapshot);
 | |
|                 this.sharedNodes.forEach(removeLeadSnapshots);
 | |
|             }
 | |
|             scheduleUpdateProjection() {
 | |
|                 if (!this.projectionUpdateScheduled) {
 | |
|                     this.projectionUpdateScheduled = true;
 | |
|                     frame.preRender(this.updateProjection, false, true);
 | |
|                 }
 | |
|             }
 | |
|             scheduleCheckAfterUnmount() {
 | |
|                 /**
 | |
|                  * If the unmounting node is in a layoutGroup and did trigger a willUpdate,
 | |
|                  * we manually call didUpdate to give a chance to the siblings to animate.
 | |
|                  * Otherwise, cleanup all snapshots to prevents future nodes from reusing them.
 | |
|                  */
 | |
|                 frame.postRender(() => {
 | |
|                     if (this.isLayoutDirty) {
 | |
|                         this.root.didUpdate();
 | |
|                     }
 | |
|                     else {
 | |
|                         this.root.checkUpdateFailed();
 | |
|                     }
 | |
|                 });
 | |
|             }
 | |
|             /**
 | |
|              * Update measurements
 | |
|              */
 | |
|             updateSnapshot() {
 | |
|                 if (this.snapshot || !this.instance)
 | |
|                     return;
 | |
|                 this.snapshot = this.measure();
 | |
|             }
 | |
|             updateLayout() {
 | |
|                 if (!this.instance)
 | |
|                     return;
 | |
|                 // TODO: Incorporate into a forwarded scroll offset
 | |
|                 this.updateScroll();
 | |
|                 if (!(this.options.alwaysMeasureLayout && this.isLead()) &&
 | |
|                     !this.isLayoutDirty) {
 | |
|                     return;
 | |
|                 }
 | |
|                 /**
 | |
|                  * When a node is mounted, it simply resumes from the prevLead's
 | |
|                  * snapshot instead of taking a new one, but the ancestors scroll
 | |
|                  * might have updated while the prevLead is unmounted. We need to
 | |
|                  * update the scroll again to make sure the layout we measure is
 | |
|                  * up to date.
 | |
|                  */
 | |
|                 if (this.resumeFrom && !this.resumeFrom.instance) {
 | |
|                     for (let i = 0; i < this.path.length; i++) {
 | |
|                         const node = this.path[i];
 | |
|                         node.updateScroll();
 | |
|                     }
 | |
|                 }
 | |
|                 const prevLayout = this.layout;
 | |
|                 this.layout = this.measure(false);
 | |
|                 this.layoutCorrected = createBox();
 | |
|                 this.isLayoutDirty = false;
 | |
|                 this.projectionDelta = undefined;
 | |
|                 this.notifyListeners("measure", this.layout.layoutBox);
 | |
|                 const { visualElement } = this.options;
 | |
|                 visualElement &&
 | |
|                     visualElement.notify("LayoutMeasure", this.layout.layoutBox, prevLayout ? prevLayout.layoutBox : undefined);
 | |
|             }
 | |
|             updateScroll(phase = "measure") {
 | |
|                 let needsMeasurement = Boolean(this.options.layoutScroll && this.instance);
 | |
|                 if (this.scroll &&
 | |
|                     this.scroll.animationId === this.root.animationId &&
 | |
|                     this.scroll.phase === phase) {
 | |
|                     needsMeasurement = false;
 | |
|                 }
 | |
|                 if (needsMeasurement) {
 | |
|                     this.scroll = {
 | |
|                         animationId: this.root.animationId,
 | |
|                         phase,
 | |
|                         isRoot: checkIsScrollRoot(this.instance),
 | |
|                         offset: measureScroll(this.instance),
 | |
|                     };
 | |
|                 }
 | |
|             }
 | |
|             resetTransform() {
 | |
|                 if (!resetTransform)
 | |
|                     return;
 | |
|                 const isResetRequested = this.isLayoutDirty || this.shouldResetTransform;
 | |
|                 const hasProjection = this.projectionDelta && !isDeltaZero(this.projectionDelta);
 | |
|                 const transformTemplate = this.getTransformTemplate();
 | |
|                 const transformTemplateValue = transformTemplate
 | |
|                     ? transformTemplate(this.latestValues, "")
 | |
|                     : undefined;
 | |
|                 const transformTemplateHasChanged = transformTemplateValue !== this.prevTransformTemplateValue;
 | |
|                 if (isResetRequested &&
 | |
|                     (hasProjection ||
 | |
|                         hasTransform(this.latestValues) ||
 | |
|                         transformTemplateHasChanged)) {
 | |
|                     resetTransform(this.instance, transformTemplateValue);
 | |
|                     this.shouldResetTransform = false;
 | |
|                     this.scheduleRender();
 | |
|                 }
 | |
|             }
 | |
|             measure(removeTransform = true) {
 | |
|                 const pageBox = this.measurePageBox();
 | |
|                 let layoutBox = this.removeElementScroll(pageBox);
 | |
|                 /**
 | |
|                  * Measurements taken during the pre-render stage
 | |
|                  * still have transforms applied so we remove them
 | |
|                  * via calculation.
 | |
|                  */
 | |
|                 if (removeTransform) {
 | |
|                     layoutBox = this.removeTransform(layoutBox);
 | |
|                 }
 | |
|                 roundBox(layoutBox);
 | |
|                 return {
 | |
|                     animationId: this.root.animationId,
 | |
|                     measuredBox: pageBox,
 | |
|                     layoutBox,
 | |
|                     latestValues: {},
 | |
|                     source: this.id,
 | |
|                 };
 | |
|             }
 | |
|             measurePageBox() {
 | |
|                 const { visualElement } = this.options;
 | |
|                 if (!visualElement)
 | |
|                     return createBox();
 | |
|                 const box = visualElement.measureViewportBox();
 | |
|                 // Remove viewport scroll to give page-relative coordinates
 | |
|                 const { scroll } = this.root;
 | |
|                 if (scroll) {
 | |
|                     translateAxis(box.x, scroll.offset.x);
 | |
|                     translateAxis(box.y, scroll.offset.y);
 | |
|                 }
 | |
|                 return box;
 | |
|             }
 | |
|             removeElementScroll(box) {
 | |
|                 const boxWithoutScroll = createBox();
 | |
|                 copyBoxInto(boxWithoutScroll, box);
 | |
|                 /**
 | |
|                  * Performance TODO: Keep a cumulative scroll offset down the tree
 | |
|                  * rather than loop back up the path.
 | |
|                  */
 | |
|                 for (let i = 0; i < this.path.length; i++) {
 | |
|                     const node = this.path[i];
 | |
|                     const { scroll, options } = node;
 | |
|                     if (node !== this.root && scroll && options.layoutScroll) {
 | |
|                         /**
 | |
|                          * If this is a new scroll root, we want to remove all previous scrolls
 | |
|                          * from the viewport box.
 | |
|                          */
 | |
|                         if (scroll.isRoot) {
 | |
|                             copyBoxInto(boxWithoutScroll, box);
 | |
|                             const { scroll: rootScroll } = this.root;
 | |
|                             /**
 | |
|                              * Undo the application of page scroll that was originally added
 | |
|                              * to the measured bounding box.
 | |
|                              */
 | |
|                             if (rootScroll) {
 | |
|                                 translateAxis(boxWithoutScroll.x, -rootScroll.offset.x);
 | |
|                                 translateAxis(boxWithoutScroll.y, -rootScroll.offset.y);
 | |
|                             }
 | |
|                         }
 | |
|                         translateAxis(boxWithoutScroll.x, scroll.offset.x);
 | |
|                         translateAxis(boxWithoutScroll.y, scroll.offset.y);
 | |
|                     }
 | |
|                 }
 | |
|                 return boxWithoutScroll;
 | |
|             }
 | |
|             applyTransform(box, transformOnly = false) {
 | |
|                 const withTransforms = createBox();
 | |
|                 copyBoxInto(withTransforms, box);
 | |
|                 for (let i = 0; i < this.path.length; i++) {
 | |
|                     const node = this.path[i];
 | |
|                     if (!transformOnly &&
 | |
|                         node.options.layoutScroll &&
 | |
|                         node.scroll &&
 | |
|                         node !== node.root) {
 | |
|                         transformBox(withTransforms, {
 | |
|                             x: -node.scroll.offset.x,
 | |
|                             y: -node.scroll.offset.y,
 | |
|                         });
 | |
|                     }
 | |
|                     if (!hasTransform(node.latestValues))
 | |
|                         continue;
 | |
|                     transformBox(withTransforms, node.latestValues);
 | |
|                 }
 | |
|                 if (hasTransform(this.latestValues)) {
 | |
|                     transformBox(withTransforms, this.latestValues);
 | |
|                 }
 | |
|                 return withTransforms;
 | |
|             }
 | |
|             removeTransform(box) {
 | |
|                 const boxWithoutTransform = createBox();
 | |
|                 copyBoxInto(boxWithoutTransform, box);
 | |
|                 for (let i = 0; i < this.path.length; i++) {
 | |
|                     const node = this.path[i];
 | |
|                     if (!node.instance)
 | |
|                         continue;
 | |
|                     if (!hasTransform(node.latestValues))
 | |
|                         continue;
 | |
|                     hasScale(node.latestValues) && node.updateSnapshot();
 | |
|                     const sourceBox = createBox();
 | |
|                     const nodeBox = node.measurePageBox();
 | |
|                     copyBoxInto(sourceBox, nodeBox);
 | |
|                     removeBoxTransforms(boxWithoutTransform, node.latestValues, node.snapshot ? node.snapshot.layoutBox : undefined, sourceBox);
 | |
|                 }
 | |
|                 if (hasTransform(this.latestValues)) {
 | |
|                     removeBoxTransforms(boxWithoutTransform, this.latestValues);
 | |
|                 }
 | |
|                 return boxWithoutTransform;
 | |
|             }
 | |
|             setTargetDelta(delta) {
 | |
|                 this.targetDelta = delta;
 | |
|                 this.root.scheduleUpdateProjection();
 | |
|                 this.isProjectionDirty = true;
 | |
|             }
 | |
|             setOptions(options) {
 | |
|                 this.options = {
 | |
|                     ...this.options,
 | |
|                     ...options,
 | |
|                     crossfade: options.crossfade !== undefined ? options.crossfade : true,
 | |
|                 };
 | |
|             }
 | |
|             clearMeasurements() {
 | |
|                 this.scroll = undefined;
 | |
|                 this.layout = undefined;
 | |
|                 this.snapshot = undefined;
 | |
|                 this.prevTransformTemplateValue = undefined;
 | |
|                 this.targetDelta = undefined;
 | |
|                 this.target = undefined;
 | |
|                 this.isLayoutDirty = false;
 | |
|             }
 | |
|             forceRelativeParentToResolveTarget() {
 | |
|                 if (!this.relativeParent)
 | |
|                     return;
 | |
|                 /**
 | |
|                  * If the parent target isn't up-to-date, force it to update.
 | |
|                  * This is an unfortunate de-optimisation as it means any updating relative
 | |
|                  * projection will cause all the relative parents to recalculate back
 | |
|                  * up the tree.
 | |
|                  */
 | |
|                 if (this.relativeParent.resolvedRelativeTargetAt !==
 | |
|                     frameData.timestamp) {
 | |
|                     this.relativeParent.resolveTargetDelta(true);
 | |
|                 }
 | |
|             }
 | |
|             resolveTargetDelta(forceRecalculation = false) {
 | |
|                 var _a;
 | |
|                 /**
 | |
|                  * Once the dirty status of nodes has been spread through the tree, we also
 | |
|                  * need to check if we have a shared node of a different depth that has itself
 | |
|                  * been dirtied.
 | |
|                  */
 | |
|                 const lead = this.getLead();
 | |
|                 this.isProjectionDirty || (this.isProjectionDirty = lead.isProjectionDirty);
 | |
|                 this.isTransformDirty || (this.isTransformDirty = lead.isTransformDirty);
 | |
|                 this.isSharedProjectionDirty || (this.isSharedProjectionDirty = lead.isSharedProjectionDirty);
 | |
|                 const isShared = Boolean(this.resumingFrom) || this !== lead;
 | |
|                 /**
 | |
|                  * We don't use transform for this step of processing so we don't
 | |
|                  * need to check whether any nodes have changed transform.
 | |
|                  */
 | |
|                 const canSkip = !(forceRecalculation ||
 | |
|                     (isShared && this.isSharedProjectionDirty) ||
 | |
|                     this.isProjectionDirty ||
 | |
|                     ((_a = this.parent) === null || _a === void 0 ? void 0 : _a.isProjectionDirty) ||
 | |
|                     this.attemptToResolveRelativeTarget);
 | |
|                 if (canSkip)
 | |
|                     return;
 | |
|                 const { layout, layoutId } = this.options;
 | |
|                 /**
 | |
|                  * If we have no layout, we can't perform projection, so early return
 | |
|                  */
 | |
|                 if (!this.layout || !(layout || layoutId))
 | |
|                     return;
 | |
|                 this.resolvedRelativeTargetAt = frameData.timestamp;
 | |
|                 /**
 | |
|                  * If we don't have a targetDelta but do have a layout, we can attempt to resolve
 | |
|                  * a relativeParent. This will allow a component to perform scale correction
 | |
|                  * even if no animation has started.
 | |
|                  */
 | |
|                 // TODO If this is unsuccessful this currently happens every frame
 | |
|                 if (!this.targetDelta && !this.relativeTarget) {
 | |
|                     // TODO: This is a semi-repetition of further down this function, make DRY
 | |
|                     const relativeParent = this.getClosestProjectingParent();
 | |
|                     if (relativeParent &&
 | |
|                         relativeParent.layout &&
 | |
|                         this.animationProgress !== 1) {
 | |
|                         this.relativeParent = relativeParent;
 | |
|                         this.forceRelativeParentToResolveTarget();
 | |
|                         this.relativeTarget = createBox();
 | |
|                         this.relativeTargetOrigin = createBox();
 | |
|                         calcRelativePosition(this.relativeTargetOrigin, this.layout.layoutBox, relativeParent.layout.layoutBox);
 | |
|                         copyBoxInto(this.relativeTarget, this.relativeTargetOrigin);
 | |
|                     }
 | |
|                     else {
 | |
|                         this.relativeParent = this.relativeTarget = undefined;
 | |
|                     }
 | |
|                 }
 | |
|                 /**
 | |
|                  * If we have no relative target or no target delta our target isn't valid
 | |
|                  * for this frame.
 | |
|                  */
 | |
|                 if (!this.relativeTarget && !this.targetDelta)
 | |
|                     return;
 | |
|                 /**
 | |
|                  * Lazy-init target data structure
 | |
|                  */
 | |
|                 if (!this.target) {
 | |
|                     this.target = createBox();
 | |
|                     this.targetWithTransforms = createBox();
 | |
|                 }
 | |
|                 /**
 | |
|                  * If we've got a relative box for this component, resolve it into a target relative to the parent.
 | |
|                  */
 | |
|                 if (this.relativeTarget &&
 | |
|                     this.relativeTargetOrigin &&
 | |
|                     this.relativeParent &&
 | |
|                     this.relativeParent.target) {
 | |
|                     this.forceRelativeParentToResolveTarget();
 | |
|                     calcRelativeBox(this.target, this.relativeTarget, this.relativeParent.target);
 | |
|                     /**
 | |
|                      * If we've only got a targetDelta, resolve it into a target
 | |
|                      */
 | |
|                 }
 | |
|                 else if (this.targetDelta) {
 | |
|                     if (Boolean(this.resumingFrom)) {
 | |
|                         // TODO: This is creating a new object every frame
 | |
|                         this.target = this.applyTransform(this.layout.layoutBox);
 | |
|                     }
 | |
|                     else {
 | |
|                         copyBoxInto(this.target, this.layout.layoutBox);
 | |
|                     }
 | |
|                     applyBoxDelta(this.target, this.targetDelta);
 | |
|                 }
 | |
|                 else {
 | |
|                     /**
 | |
|                      * If no target, use own layout as target
 | |
|                      */
 | |
|                     copyBoxInto(this.target, this.layout.layoutBox);
 | |
|                 }
 | |
|                 /**
 | |
|                  * If we've been told to attempt to resolve a relative target, do so.
 | |
|                  */
 | |
|                 if (this.attemptToResolveRelativeTarget) {
 | |
|                     this.attemptToResolveRelativeTarget = false;
 | |
|                     const relativeParent = this.getClosestProjectingParent();
 | |
|                     if (relativeParent &&
 | |
|                         Boolean(relativeParent.resumingFrom) ===
 | |
|                             Boolean(this.resumingFrom) &&
 | |
|                         !relativeParent.options.layoutScroll &&
 | |
|                         relativeParent.target &&
 | |
|                         this.animationProgress !== 1) {
 | |
|                         this.relativeParent = relativeParent;
 | |
|                         this.forceRelativeParentToResolveTarget();
 | |
|                         this.relativeTarget = createBox();
 | |
|                         this.relativeTargetOrigin = createBox();
 | |
|                         calcRelativePosition(this.relativeTargetOrigin, this.target, relativeParent.target);
 | |
|                         copyBoxInto(this.relativeTarget, this.relativeTargetOrigin);
 | |
|                     }
 | |
|                     else {
 | |
|                         this.relativeParent = this.relativeTarget = undefined;
 | |
|                     }
 | |
|                 }
 | |
|                 /**
 | |
|                  * Increase debug counter for resolved target deltas
 | |
|                  */
 | |
|                 projectionFrameData.resolvedTargetDeltas++;
 | |
|             }
 | |
|             getClosestProjectingParent() {
 | |
|                 if (!this.parent ||
 | |
|                     hasScale(this.parent.latestValues) ||
 | |
|                     has2DTranslate(this.parent.latestValues)) {
 | |
|                     return undefined;
 | |
|                 }
 | |
|                 if (this.parent.isProjecting()) {
 | |
|                     return this.parent;
 | |
|                 }
 | |
|                 else {
 | |
|                     return this.parent.getClosestProjectingParent();
 | |
|                 }
 | |
|             }
 | |
|             isProjecting() {
 | |
|                 return Boolean((this.relativeTarget ||
 | |
|                     this.targetDelta ||
 | |
|                     this.options.layoutRoot) &&
 | |
|                     this.layout);
 | |
|             }
 | |
|             calcProjection() {
 | |
|                 var _a;
 | |
|                 const lead = this.getLead();
 | |
|                 const isShared = Boolean(this.resumingFrom) || this !== lead;
 | |
|                 let canSkip = true;
 | |
|                 /**
 | |
|                  * If this is a normal layout animation and neither this node nor its nearest projecting
 | |
|                  * is dirty then we can't skip.
 | |
|                  */
 | |
|                 if (this.isProjectionDirty || ((_a = this.parent) === null || _a === void 0 ? void 0 : _a.isProjectionDirty)) {
 | |
|                     canSkip = false;
 | |
|                 }
 | |
|                 /**
 | |
|                  * If this is a shared layout animation and this node's shared projection is dirty then
 | |
|                  * we can't skip.
 | |
|                  */
 | |
|                 if (isShared &&
 | |
|                     (this.isSharedProjectionDirty || this.isTransformDirty)) {
 | |
|                     canSkip = false;
 | |
|                 }
 | |
|                 /**
 | |
|                  * If we have resolved the target this frame we must recalculate the
 | |
|                  * projection to ensure it visually represents the internal calculations.
 | |
|                  */
 | |
|                 if (this.resolvedRelativeTargetAt === frameData.timestamp) {
 | |
|                     canSkip = false;
 | |
|                 }
 | |
|                 if (canSkip)
 | |
|                     return;
 | |
|                 const { layout, layoutId } = this.options;
 | |
|                 /**
 | |
|                  * If this section of the tree isn't animating we can
 | |
|                  * delete our target sources for the following frame.
 | |
|                  */
 | |
|                 this.isTreeAnimating = Boolean((this.parent && this.parent.isTreeAnimating) ||
 | |
|                     this.currentAnimation ||
 | |
|                     this.pendingAnimation);
 | |
|                 if (!this.isTreeAnimating) {
 | |
|                     this.targetDelta = this.relativeTarget = undefined;
 | |
|                 }
 | |
|                 if (!this.layout || !(layout || layoutId))
 | |
|                     return;
 | |
|                 /**
 | |
|                  * Reset the corrected box with the latest values from box, as we're then going
 | |
|                  * to perform mutative operations on it.
 | |
|                  */
 | |
|                 copyBoxInto(this.layoutCorrected, this.layout.layoutBox);
 | |
|                 /**
 | |
|                  * Record previous tree scales before updating.
 | |
|                  */
 | |
|                 const prevTreeScaleX = this.treeScale.x;
 | |
|                 const prevTreeScaleY = this.treeScale.y;
 | |
|                 /**
 | |
|                  * Apply all the parent deltas to this box to produce the corrected box. This
 | |
|                  * is the layout box, as it will appear on screen as a result of the transforms of its parents.
 | |
|                  */
 | |
|                 applyTreeDeltas(this.layoutCorrected, this.treeScale, this.path, isShared);
 | |
|                 /**
 | |
|                  * If this layer needs to perform scale correction but doesn't have a target,
 | |
|                  * use the layout as the target.
 | |
|                  */
 | |
|                 if (lead.layout &&
 | |
|                     !lead.target &&
 | |
|                     (this.treeScale.x !== 1 || this.treeScale.y !== 1)) {
 | |
|                     lead.target = lead.layout.layoutBox;
 | |
|                 }
 | |
|                 const { target } = lead;
 | |
|                 if (!target) {
 | |
|                     /**
 | |
|                      * If we don't have a target to project into, but we were previously
 | |
|                      * projecting, we want to remove the stored transform and schedule
 | |
|                      * a render to ensure the elements reflect the removed transform.
 | |
|                      */
 | |
|                     if (this.projectionTransform) {
 | |
|                         this.projectionDelta = createDelta();
 | |
|                         this.projectionTransform = "none";
 | |
|                         this.scheduleRender();
 | |
|                     }
 | |
|                     return;
 | |
|                 }
 | |
|                 if (!this.projectionDelta) {
 | |
|                     this.projectionDelta = createDelta();
 | |
|                     this.projectionDeltaWithTransform = createDelta();
 | |
|                 }
 | |
|                 const prevProjectionTransform = this.projectionTransform;
 | |
|                 /**
 | |
|                  * Update the delta between the corrected box and the target box before user-set transforms were applied.
 | |
|                  * This will allow us to calculate the corrected borderRadius and boxShadow to compensate
 | |
|                  * for our layout reprojection, but still allow them to be scaled correctly by the user.
 | |
|                  * It might be that to simplify this we may want to accept that user-set scale is also corrected
 | |
|                  * and we wouldn't have to keep and calc both deltas, OR we could support a user setting
 | |
|                  * to allow people to choose whether these styles are corrected based on just the
 | |
|                  * layout reprojection or the final bounding box.
 | |
|                  */
 | |
|                 calcBoxDelta(this.projectionDelta, this.layoutCorrected, target, this.latestValues);
 | |
|                 this.projectionTransform = buildProjectionTransform(this.projectionDelta, this.treeScale);
 | |
|                 if (this.projectionTransform !== prevProjectionTransform ||
 | |
|                     this.treeScale.x !== prevTreeScaleX ||
 | |
|                     this.treeScale.y !== prevTreeScaleY) {
 | |
|                     this.hasProjected = true;
 | |
|                     this.scheduleRender();
 | |
|                     this.notifyListeners("projectionUpdate", target);
 | |
|                 }
 | |
|                 /**
 | |
|                  * Increase debug counter for recalculated projections
 | |
|                  */
 | |
|                 projectionFrameData.recalculatedProjection++;
 | |
|             }
 | |
|             hide() {
 | |
|                 this.isVisible = false;
 | |
|                 // TODO: Schedule render
 | |
|             }
 | |
|             show() {
 | |
|                 this.isVisible = true;
 | |
|                 // TODO: Schedule render
 | |
|             }
 | |
|             scheduleRender(notifyAll = true) {
 | |
|                 this.options.scheduleRender && this.options.scheduleRender();
 | |
|                 if (notifyAll) {
 | |
|                     const stack = this.getStack();
 | |
|                     stack && stack.scheduleRender();
 | |
|                 }
 | |
|                 if (this.resumingFrom && !this.resumingFrom.instance) {
 | |
|                     this.resumingFrom = undefined;
 | |
|                 }
 | |
|             }
 | |
|             setAnimationOrigin(delta, hasOnlyRelativeTargetChanged = false) {
 | |
|                 const snapshot = this.snapshot;
 | |
|                 const snapshotLatestValues = snapshot
 | |
|                     ? snapshot.latestValues
 | |
|                     : {};
 | |
|                 const mixedValues = { ...this.latestValues };
 | |
|                 const targetDelta = createDelta();
 | |
|                 if (!this.relativeParent ||
 | |
|                     !this.relativeParent.options.layoutRoot) {
 | |
|                     this.relativeTarget = this.relativeTargetOrigin = undefined;
 | |
|                 }
 | |
|                 this.attemptToResolveRelativeTarget = !hasOnlyRelativeTargetChanged;
 | |
|                 const relativeLayout = createBox();
 | |
|                 const snapshotSource = snapshot ? snapshot.source : undefined;
 | |
|                 const layoutSource = this.layout ? this.layout.source : undefined;
 | |
|                 const isSharedLayoutAnimation = snapshotSource !== layoutSource;
 | |
|                 const stack = this.getStack();
 | |
|                 const isOnlyMember = !stack || stack.members.length <= 1;
 | |
|                 const shouldCrossfadeOpacity = Boolean(isSharedLayoutAnimation &&
 | |
|                     !isOnlyMember &&
 | |
|                     this.options.crossfade === true &&
 | |
|                     !this.path.some(hasOpacityCrossfade));
 | |
|                 this.animationProgress = 0;
 | |
|                 let prevRelativeTarget;
 | |
|                 this.mixTargetDelta = (latest) => {
 | |
|                     const progress = latest / 1000;
 | |
|                     mixAxisDelta(targetDelta.x, delta.x, progress);
 | |
|                     mixAxisDelta(targetDelta.y, delta.y, progress);
 | |
|                     this.setTargetDelta(targetDelta);
 | |
|                     if (this.relativeTarget &&
 | |
|                         this.relativeTargetOrigin &&
 | |
|                         this.layout &&
 | |
|                         this.relativeParent &&
 | |
|                         this.relativeParent.layout) {
 | |
|                         calcRelativePosition(relativeLayout, this.layout.layoutBox, this.relativeParent.layout.layoutBox);
 | |
|                         mixBox(this.relativeTarget, this.relativeTargetOrigin, relativeLayout, progress);
 | |
|                         /**
 | |
|                          * If this is an unchanged relative target we can consider the
 | |
|                          * projection not dirty.
 | |
|                          */
 | |
|                         if (prevRelativeTarget &&
 | |
|                             boxEquals(this.relativeTarget, prevRelativeTarget)) {
 | |
|                             this.isProjectionDirty = false;
 | |
|                         }
 | |
|                         if (!prevRelativeTarget)
 | |
|                             prevRelativeTarget = createBox();
 | |
|                         copyBoxInto(prevRelativeTarget, this.relativeTarget);
 | |
|                     }
 | |
|                     if (isSharedLayoutAnimation) {
 | |
|                         this.animationValues = mixedValues;
 | |
|                         mixValues(mixedValues, snapshotLatestValues, this.latestValues, progress, shouldCrossfadeOpacity, isOnlyMember);
 | |
|                     }
 | |
|                     this.root.scheduleUpdateProjection();
 | |
|                     this.scheduleRender();
 | |
|                     this.animationProgress = progress;
 | |
|                 };
 | |
|                 this.mixTargetDelta(this.options.layoutRoot ? 1000 : 0);
 | |
|             }
 | |
|             startAnimation(options) {
 | |
|                 this.notifyListeners("animationStart");
 | |
|                 this.currentAnimation && this.currentAnimation.stop();
 | |
|                 if (this.resumingFrom && this.resumingFrom.currentAnimation) {
 | |
|                     this.resumingFrom.currentAnimation.stop();
 | |
|                 }
 | |
|                 if (this.pendingAnimation) {
 | |
|                     cancelFrame(this.pendingAnimation);
 | |
|                     this.pendingAnimation = undefined;
 | |
|                 }
 | |
|                 /**
 | |
|                  * Start the animation in the next frame to have a frame with progress 0,
 | |
|                  * where the target is the same as when the animation started, so we can
 | |
|                  * calculate the relative positions correctly for instant transitions.
 | |
|                  */
 | |
|                 this.pendingAnimation = frame.update(() => {
 | |
|                     globalProjectionState.hasAnimatedSinceResize = true;
 | |
|                     this.currentAnimation = animateSingleValue(0, animationTarget, {
 | |
|                         ...options,
 | |
|                         onUpdate: (latest) => {
 | |
|                             this.mixTargetDelta(latest);
 | |
|                             options.onUpdate && options.onUpdate(latest);
 | |
|                         },
 | |
|                         onComplete: () => {
 | |
|                             options.onComplete && options.onComplete();
 | |
|                             this.completeAnimation();
 | |
|                         },
 | |
|                     });
 | |
|                     if (this.resumingFrom) {
 | |
|                         this.resumingFrom.currentAnimation = this.currentAnimation;
 | |
|                     }
 | |
|                     this.pendingAnimation = undefined;
 | |
|                 });
 | |
|             }
 | |
|             completeAnimation() {
 | |
|                 if (this.resumingFrom) {
 | |
|                     this.resumingFrom.currentAnimation = undefined;
 | |
|                     this.resumingFrom.preserveOpacity = undefined;
 | |
|                 }
 | |
|                 const stack = this.getStack();
 | |
|                 stack && stack.exitAnimationComplete();
 | |
|                 this.resumingFrom =
 | |
|                     this.currentAnimation =
 | |
|                         this.animationValues =
 | |
|                             undefined;
 | |
|                 this.notifyListeners("animationComplete");
 | |
|             }
 | |
|             finishAnimation() {
 | |
|                 if (this.currentAnimation) {
 | |
|                     this.mixTargetDelta && this.mixTargetDelta(animationTarget);
 | |
|                     this.currentAnimation.stop();
 | |
|                 }
 | |
|                 this.completeAnimation();
 | |
|             }
 | |
|             applyTransformsToTarget() {
 | |
|                 const lead = this.getLead();
 | |
|                 let { targetWithTransforms, target, layout, latestValues } = lead;
 | |
|                 if (!targetWithTransforms || !target || !layout)
 | |
|                     return;
 | |
|                 /**
 | |
|                  * If we're only animating position, and this element isn't the lead element,
 | |
|                  * then instead of projecting into the lead box we instead want to calculate
 | |
|                  * a new target that aligns the two boxes but maintains the layout shape.
 | |
|                  */
 | |
|                 if (this !== lead &&
 | |
|                     this.layout &&
 | |
|                     layout &&
 | |
|                     shouldAnimatePositionOnly(this.options.animationType, this.layout.layoutBox, layout.layoutBox)) {
 | |
|                     target = this.target || createBox();
 | |
|                     const xLength = calcLength(this.layout.layoutBox.x);
 | |
|                     target.x.min = lead.target.x.min;
 | |
|                     target.x.max = target.x.min + xLength;
 | |
|                     const yLength = calcLength(this.layout.layoutBox.y);
 | |
|                     target.y.min = lead.target.y.min;
 | |
|                     target.y.max = target.y.min + yLength;
 | |
|                 }
 | |
|                 copyBoxInto(targetWithTransforms, target);
 | |
|                 /**
 | |
|                  * Apply the latest user-set transforms to the targetBox to produce the targetBoxFinal.
 | |
|                  * This is the final box that we will then project into by calculating a transform delta and
 | |
|                  * applying it to the corrected box.
 | |
|                  */
 | |
|                 transformBox(targetWithTransforms, latestValues);
 | |
|                 /**
 | |
|                  * Update the delta between the corrected box and the final target box, after
 | |
|                  * user-set transforms are applied to it. This will be used by the renderer to
 | |
|                  * create a transform style that will reproject the element from its layout layout
 | |
|                  * into the desired bounding box.
 | |
|                  */
 | |
|                 calcBoxDelta(this.projectionDeltaWithTransform, this.layoutCorrected, targetWithTransforms, latestValues);
 | |
|             }
 | |
|             registerSharedNode(layoutId, node) {
 | |
|                 if (!this.sharedNodes.has(layoutId)) {
 | |
|                     this.sharedNodes.set(layoutId, new NodeStack());
 | |
|                 }
 | |
|                 const stack = this.sharedNodes.get(layoutId);
 | |
|                 stack.add(node);
 | |
|                 const config = node.options.initialPromotionConfig;
 | |
|                 node.promote({
 | |
|                     transition: config ? config.transition : undefined,
 | |
|                     preserveFollowOpacity: config && config.shouldPreserveFollowOpacity
 | |
|                         ? config.shouldPreserveFollowOpacity(node)
 | |
|                         : undefined,
 | |
|                 });
 | |
|             }
 | |
|             isLead() {
 | |
|                 const stack = this.getStack();
 | |
|                 return stack ? stack.lead === this : true;
 | |
|             }
 | |
|             getLead() {
 | |
|                 var _a;
 | |
|                 const { layoutId } = this.options;
 | |
|                 return layoutId ? ((_a = this.getStack()) === null || _a === void 0 ? void 0 : _a.lead) || this : this;
 | |
|             }
 | |
|             getPrevLead() {
 | |
|                 var _a;
 | |
|                 const { layoutId } = this.options;
 | |
|                 return layoutId ? (_a = this.getStack()) === null || _a === void 0 ? void 0 : _a.prevLead : undefined;
 | |
|             }
 | |
|             getStack() {
 | |
|                 const { layoutId } = this.options;
 | |
|                 if (layoutId)
 | |
|                     return this.root.sharedNodes.get(layoutId);
 | |
|             }
 | |
|             promote({ needsReset, transition, preserveFollowOpacity, } = {}) {
 | |
|                 const stack = this.getStack();
 | |
|                 if (stack)
 | |
|                     stack.promote(this, preserveFollowOpacity);
 | |
|                 if (needsReset) {
 | |
|                     this.projectionDelta = undefined;
 | |
|                     this.needsReset = true;
 | |
|                 }
 | |
|                 if (transition)
 | |
|                     this.setOptions({ transition });
 | |
|             }
 | |
|             relegate() {
 | |
|                 const stack = this.getStack();
 | |
|                 if (stack) {
 | |
|                     return stack.relegate(this);
 | |
|                 }
 | |
|                 else {
 | |
|                     return false;
 | |
|                 }
 | |
|             }
 | |
|             resetRotation() {
 | |
|                 const { visualElement } = this.options;
 | |
|                 if (!visualElement)
 | |
|                     return;
 | |
|                 // If there's no detected rotation values, we can early return without a forced render.
 | |
|                 let hasRotate = false;
 | |
|                 /**
 | |
|                  * An unrolled check for rotation values. Most elements don't have any rotation and
 | |
|                  * skipping the nested loop and new object creation is 50% faster.
 | |
|                  */
 | |
|                 const { latestValues } = visualElement;
 | |
|                 if (latestValues.rotate ||
 | |
|                     latestValues.rotateX ||
 | |
|                     latestValues.rotateY ||
 | |
|                     latestValues.rotateZ) {
 | |
|                     hasRotate = true;
 | |
|                 }
 | |
|                 // If there's no rotation values, we don't need to do any more.
 | |
|                 if (!hasRotate)
 | |
|                     return;
 | |
|                 const resetValues = {};
 | |
|                 // Check the rotate value of all axes and reset to 0
 | |
|                 for (let i = 0; i < transformAxes.length; i++) {
 | |
|                     const key = "rotate" + transformAxes[i];
 | |
|                     // Record the rotation and then temporarily set it to 0
 | |
|                     if (latestValues[key]) {
 | |
|                         resetValues[key] = latestValues[key];
 | |
|                         visualElement.setStaticValue(key, 0);
 | |
|                     }
 | |
|                 }
 | |
|                 // Force a render of this element to apply the transform with all rotations
 | |
|                 // set to 0.
 | |
|                 visualElement.render();
 | |
|                 // Put back all the values we reset
 | |
|                 for (const key in resetValues) {
 | |
|                     visualElement.setStaticValue(key, resetValues[key]);
 | |
|                 }
 | |
|                 // Schedule a render for the next frame. This ensures we won't visually
 | |
|                 // see the element with the reset rotate value applied.
 | |
|                 visualElement.scheduleRender();
 | |
|             }
 | |
|             getProjectionStyles(styleProp) {
 | |
|                 var _a, _b;
 | |
|                 if (!this.instance || this.isSVG)
 | |
|                     return undefined;
 | |
|                 if (!this.isVisible) {
 | |
|                     return hiddenVisibility;
 | |
|                 }
 | |
|                 const styles = {
 | |
|                     visibility: "",
 | |
|                 };
 | |
|                 const transformTemplate = this.getTransformTemplate();
 | |
|                 if (this.needsReset) {
 | |
|                     this.needsReset = false;
 | |
|                     styles.opacity = "";
 | |
|                     styles.pointerEvents =
 | |
|                         resolveMotionValue(styleProp === null || styleProp === void 0 ? void 0 : styleProp.pointerEvents) || "";
 | |
|                     styles.transform = transformTemplate
 | |
|                         ? transformTemplate(this.latestValues, "")
 | |
|                         : "none";
 | |
|                     return styles;
 | |
|                 }
 | |
|                 const lead = this.getLead();
 | |
|                 if (!this.projectionDelta || !this.layout || !lead.target) {
 | |
|                     const emptyStyles = {};
 | |
|                     if (this.options.layoutId) {
 | |
|                         emptyStyles.opacity =
 | |
|                             this.latestValues.opacity !== undefined
 | |
|                                 ? this.latestValues.opacity
 | |
|                                 : 1;
 | |
|                         emptyStyles.pointerEvents =
 | |
|                             resolveMotionValue(styleProp === null || styleProp === void 0 ? void 0 : styleProp.pointerEvents) || "";
 | |
|                     }
 | |
|                     if (this.hasProjected && !hasTransform(this.latestValues)) {
 | |
|                         emptyStyles.transform = transformTemplate
 | |
|                             ? transformTemplate({}, "")
 | |
|                             : "none";
 | |
|                         this.hasProjected = false;
 | |
|                     }
 | |
|                     return emptyStyles;
 | |
|                 }
 | |
|                 const valuesToRender = lead.animationValues || lead.latestValues;
 | |
|                 this.applyTransformsToTarget();
 | |
|                 styles.transform = buildProjectionTransform(this.projectionDeltaWithTransform, this.treeScale, valuesToRender);
 | |
|                 if (transformTemplate) {
 | |
|                     styles.transform = transformTemplate(valuesToRender, styles.transform);
 | |
|                 }
 | |
|                 const { x, y } = this.projectionDelta;
 | |
|                 styles.transformOrigin = `${x.origin * 100}% ${y.origin * 100}% 0`;
 | |
|                 if (lead.animationValues) {
 | |
|                     /**
 | |
|                      * If the lead component is animating, assign this either the entering/leaving
 | |
|                      * opacity
 | |
|                      */
 | |
|                     styles.opacity =
 | |
|                         lead === this
 | |
|                             ? (_b = (_a = valuesToRender.opacity) !== null && _a !== void 0 ? _a : this.latestValues.opacity) !== null && _b !== void 0 ? _b : 1
 | |
|                             : this.preserveOpacity
 | |
|                                 ? this.latestValues.opacity
 | |
|                                 : valuesToRender.opacityExit;
 | |
|                 }
 | |
|                 else {
 | |
|                     /**
 | |
|                      * Or we're not animating at all, set the lead component to its layout
 | |
|                      * opacity and other components to hidden.
 | |
|                      */
 | |
|                     styles.opacity =
 | |
|                         lead === this
 | |
|                             ? valuesToRender.opacity !== undefined
 | |
|                                 ? valuesToRender.opacity
 | |
|                                 : ""
 | |
|                             : valuesToRender.opacityExit !== undefined
 | |
|                                 ? valuesToRender.opacityExit
 | |
|                                 : 0;
 | |
|                 }
 | |
|                 /**
 | |
|                  * Apply scale correction
 | |
|                  */
 | |
|                 for (const key in scaleCorrectors) {
 | |
|                     if (valuesToRender[key] === undefined)
 | |
|                         continue;
 | |
|                     const { correct, applyTo } = scaleCorrectors[key];
 | |
|                     /**
 | |
|                      * Only apply scale correction to the value if we have an
 | |
|                      * active projection transform. Otherwise these values become
 | |
|                      * vulnerable to distortion if the element changes size without
 | |
|                      * a corresponding layout animation.
 | |
|                      */
 | |
|                     const corrected = styles.transform === "none"
 | |
|                         ? valuesToRender[key]
 | |
|                         : correct(valuesToRender[key], lead);
 | |
|                     if (applyTo) {
 | |
|                         const num = applyTo.length;
 | |
|                         for (let i = 0; i < num; i++) {
 | |
|                             styles[applyTo[i]] = corrected;
 | |
|                         }
 | |
|                     }
 | |
|                     else {
 | |
|                         styles[key] = corrected;
 | |
|                     }
 | |
|                 }
 | |
|                 /**
 | |
|                  * Disable pointer events on follow components. This is to ensure
 | |
|                  * that if a follow component covers a lead component it doesn't block
 | |
|                  * pointer events on the lead.
 | |
|                  */
 | |
|                 if (this.options.layoutId) {
 | |
|                     styles.pointerEvents =
 | |
|                         lead === this
 | |
|                             ? resolveMotionValue(styleProp === null || styleProp === void 0 ? void 0 : styleProp.pointerEvents) || ""
 | |
|                             : "none";
 | |
|                 }
 | |
|                 return styles;
 | |
|             }
 | |
|             clearSnapshot() {
 | |
|                 this.resumeFrom = this.snapshot = undefined;
 | |
|             }
 | |
|             // Only run on root
 | |
|             resetTree() {
 | |
|                 this.root.nodes.forEach((node) => { var _a; return (_a = node.currentAnimation) === null || _a === void 0 ? void 0 : _a.stop(); });
 | |
|                 this.root.nodes.forEach(clearMeasurements);
 | |
|                 this.root.sharedNodes.clear();
 | |
|             }
 | |
|         };
 | |
|     }
 | |
|     function updateLayout(node) {
 | |
|         node.updateLayout();
 | |
|     }
 | |
|     function notifyLayoutUpdate(node) {
 | |
|         var _a;
 | |
|         const snapshot = ((_a = node.resumeFrom) === null || _a === void 0 ? void 0 : _a.snapshot) || node.snapshot;
 | |
|         if (node.isLead() &&
 | |
|             node.layout &&
 | |
|             snapshot &&
 | |
|             node.hasListeners("didUpdate")) {
 | |
|             const { layoutBox: layout, measuredBox: measuredLayout } = node.layout;
 | |
|             const { animationType } = node.options;
 | |
|             const isShared = snapshot.source !== node.layout.source;
 | |
|             // TODO Maybe we want to also resize the layout snapshot so we don't trigger
 | |
|             // animations for instance if layout="size" and an element has only changed position
 | |
|             if (animationType === "size") {
 | |
|                 eachAxis((axis) => {
 | |
|                     const axisSnapshot = isShared
 | |
|                         ? snapshot.measuredBox[axis]
 | |
|                         : snapshot.layoutBox[axis];
 | |
|                     const length = calcLength(axisSnapshot);
 | |
|                     axisSnapshot.min = layout[axis].min;
 | |
|                     axisSnapshot.max = axisSnapshot.min + length;
 | |
|                 });
 | |
|             }
 | |
|             else if (shouldAnimatePositionOnly(animationType, snapshot.layoutBox, layout)) {
 | |
|                 eachAxis((axis) => {
 | |
|                     const axisSnapshot = isShared
 | |
|                         ? snapshot.measuredBox[axis]
 | |
|                         : snapshot.layoutBox[axis];
 | |
|                     const length = calcLength(layout[axis]);
 | |
|                     axisSnapshot.max = axisSnapshot.min + length;
 | |
|                     /**
 | |
|                      * Ensure relative target gets resized and rerendererd
 | |
|                      */
 | |
|                     if (node.relativeTarget && !node.currentAnimation) {
 | |
|                         node.isProjectionDirty = true;
 | |
|                         node.relativeTarget[axis].max =
 | |
|                             node.relativeTarget[axis].min + length;
 | |
|                     }
 | |
|                 });
 | |
|             }
 | |
|             const layoutDelta = createDelta();
 | |
|             calcBoxDelta(layoutDelta, layout, snapshot.layoutBox);
 | |
|             const visualDelta = createDelta();
 | |
|             if (isShared) {
 | |
|                 calcBoxDelta(visualDelta, node.applyTransform(measuredLayout, true), snapshot.measuredBox);
 | |
|             }
 | |
|             else {
 | |
|                 calcBoxDelta(visualDelta, layout, snapshot.layoutBox);
 | |
|             }
 | |
|             const hasLayoutChanged = !isDeltaZero(layoutDelta);
 | |
|             let hasRelativeTargetChanged = false;
 | |
|             if (!node.resumeFrom) {
 | |
|                 const relativeParent = node.getClosestProjectingParent();
 | |
|                 /**
 | |
|                  * If the relativeParent is itself resuming from a different element then
 | |
|                  * the relative snapshot is not relavent
 | |
|                  */
 | |
|                 if (relativeParent && !relativeParent.resumeFrom) {
 | |
|                     const { snapshot: parentSnapshot, layout: parentLayout } = relativeParent;
 | |
|                     if (parentSnapshot && parentLayout) {
 | |
|                         const relativeSnapshot = createBox();
 | |
|                         calcRelativePosition(relativeSnapshot, snapshot.layoutBox, parentSnapshot.layoutBox);
 | |
|                         const relativeLayout = createBox();
 | |
|                         calcRelativePosition(relativeLayout, layout, parentLayout.layoutBox);
 | |
|                         if (!boxEqualsRounded(relativeSnapshot, relativeLayout)) {
 | |
|                             hasRelativeTargetChanged = true;
 | |
|                         }
 | |
|                         if (relativeParent.options.layoutRoot) {
 | |
|                             node.relativeTarget = relativeLayout;
 | |
|                             node.relativeTargetOrigin = relativeSnapshot;
 | |
|                             node.relativeParent = relativeParent;
 | |
|                         }
 | |
|                     }
 | |
|                 }
 | |
|             }
 | |
|             node.notifyListeners("didUpdate", {
 | |
|                 layout,
 | |
|                 snapshot,
 | |
|                 delta: visualDelta,
 | |
|                 layoutDelta,
 | |
|                 hasLayoutChanged,
 | |
|                 hasRelativeTargetChanged,
 | |
|             });
 | |
|         }
 | |
|         else if (node.isLead()) {
 | |
|             const { onExitComplete } = node.options;
 | |
|             onExitComplete && onExitComplete();
 | |
|         }
 | |
|         /**
 | |
|          * Clearing transition
 | |
|          * TODO: Investigate why this transition is being passed in as {type: false } from Framer
 | |
|          * and why we need it at all
 | |
|          */
 | |
|         node.options.transition = undefined;
 | |
|     }
 | |
|     function propagateDirtyNodes(node) {
 | |
|         /**
 | |
|          * Increase debug counter for nodes encountered this frame
 | |
|          */
 | |
|         projectionFrameData.totalNodes++;
 | |
|         if (!node.parent)
 | |
|             return;
 | |
|         /**
 | |
|          * If this node isn't projecting, propagate isProjectionDirty. It will have
 | |
|          * no performance impact but it will allow the next child that *is* projecting
 | |
|          * but *isn't* dirty to just check its parent to see if *any* ancestor needs
 | |
|          * correcting.
 | |
|          */
 | |
|         if (!node.isProjecting()) {
 | |
|             node.isProjectionDirty = node.parent.isProjectionDirty;
 | |
|         }
 | |
|         /**
 | |
|          * Propagate isSharedProjectionDirty and isTransformDirty
 | |
|          * throughout the whole tree. A future revision can take another look at
 | |
|          * this but for safety we still recalcualte shared nodes.
 | |
|          */
 | |
|         node.isSharedProjectionDirty || (node.isSharedProjectionDirty = Boolean(node.isProjectionDirty ||
 | |
|             node.parent.isProjectionDirty ||
 | |
|             node.parent.isSharedProjectionDirty));
 | |
|         node.isTransformDirty || (node.isTransformDirty = node.parent.isTransformDirty);
 | |
|     }
 | |
|     function cleanDirtyNodes(node) {
 | |
|         node.isProjectionDirty =
 | |
|             node.isSharedProjectionDirty =
 | |
|                 node.isTransformDirty =
 | |
|                     false;
 | |
|     }
 | |
|     function clearSnapshot(node) {
 | |
|         node.clearSnapshot();
 | |
|     }
 | |
|     function clearMeasurements(node) {
 | |
|         node.clearMeasurements();
 | |
|     }
 | |
|     function clearIsLayoutDirty(node) {
 | |
|         node.isLayoutDirty = false;
 | |
|     }
 | |
|     function resetTransformStyle(node) {
 | |
|         const { visualElement } = node.options;
 | |
|         if (visualElement && visualElement.getProps().onBeforeLayoutMeasure) {
 | |
|             visualElement.notify("BeforeLayoutMeasure");
 | |
|         }
 | |
|         node.resetTransform();
 | |
|     }
 | |
|     function finishAnimation(node) {
 | |
|         node.finishAnimation();
 | |
|         node.targetDelta = node.relativeTarget = node.target = undefined;
 | |
|         node.isProjectionDirty = true;
 | |
|     }
 | |
|     function resolveTargetDelta(node) {
 | |
|         node.resolveTargetDelta();
 | |
|     }
 | |
|     function calcProjection(node) {
 | |
|         node.calcProjection();
 | |
|     }
 | |
|     function resetRotation(node) {
 | |
|         node.resetRotation();
 | |
|     }
 | |
|     function removeLeadSnapshots(stack) {
 | |
|         stack.removeLeadSnapshot();
 | |
|     }
 | |
|     function mixAxisDelta(output, delta, p) {
 | |
|         output.translate = mix(delta.translate, 0, p);
 | |
|         output.scale = mix(delta.scale, 1, p);
 | |
|         output.origin = delta.origin;
 | |
|         output.originPoint = delta.originPoint;
 | |
|     }
 | |
|     function mixAxis(output, from, to, p) {
 | |
|         output.min = mix(from.min, to.min, p);
 | |
|         output.max = mix(from.max, to.max, p);
 | |
|     }
 | |
|     function mixBox(output, from, to, p) {
 | |
|         mixAxis(output.x, from.x, to.x, p);
 | |
|         mixAxis(output.y, from.y, to.y, p);
 | |
|     }
 | |
|     function hasOpacityCrossfade(node) {
 | |
|         return (node.animationValues && node.animationValues.opacityExit !== undefined);
 | |
|     }
 | |
|     const defaultLayoutTransition = {
 | |
|         duration: 0.45,
 | |
|         ease: [0.4, 0, 0.1, 1],
 | |
|     };
 | |
|     const userAgentContains = (string) => typeof navigator !== "undefined" &&
 | |
|         navigator.userAgent.toLowerCase().includes(string);
 | |
|     /**
 | |
|      * Measured bounding boxes must be rounded in Safari and
 | |
|      * left untouched in Chrome, otherwise non-integer layouts within scaled-up elements
 | |
|      * can appear to jump.
 | |
|      */
 | |
|     const roundPoint = userAgentContains("applewebkit/") && !userAgentContains("chrome/")
 | |
|         ? Math.round
 | |
|         : noop;
 | |
|     function roundAxis(axis) {
 | |
|         // Round to the nearest .5 pixels to support subpixel layouts
 | |
|         axis.min = roundPoint(axis.min);
 | |
|         axis.max = roundPoint(axis.max);
 | |
|     }
 | |
|     function roundBox(box) {
 | |
|         roundAxis(box.x);
 | |
|         roundAxis(box.y);
 | |
|     }
 | |
|     function shouldAnimatePositionOnly(animationType, snapshot, layout) {
 | |
|         return (animationType === "position" ||
 | |
|             (animationType === "preserve-aspect" &&
 | |
|                 !isNear(aspectRatio(snapshot), aspectRatio(layout), 0.2)));
 | |
|     }
 | |
| 
 | |
|     function addDomEvent(target, eventName, handler, options = { passive: true }) {
 | |
|         target.addEventListener(eventName, handler, options);
 | |
|         return () => target.removeEventListener(eventName, handler);
 | |
|     }
 | |
| 
 | |
|     const DocumentProjectionNode = createProjectionNode({
 | |
|         attachResizeListener: (ref, notify) => addDomEvent(ref, "resize", notify),
 | |
|         measureScroll: () => ({
 | |
|             x: document.documentElement.scrollLeft || document.body.scrollLeft,
 | |
|             y: document.documentElement.scrollTop || document.body.scrollTop,
 | |
|         }),
 | |
|         checkIsScrollRoot: () => true,
 | |
|     });
 | |
| 
 | |
|     const rootProjectionNode = {
 | |
|         current: undefined,
 | |
|     };
 | |
|     const HTMLProjectionNode = createProjectionNode({
 | |
|         measureScroll: (instance) => ({
 | |
|             x: instance.scrollLeft,
 | |
|             y: instance.scrollTop,
 | |
|         }),
 | |
|         defaultParent: () => {
 | |
|             if (!rootProjectionNode.current) {
 | |
|                 const documentNode = new DocumentProjectionNode({});
 | |
|                 documentNode.mount(window);
 | |
|                 documentNode.setOptions({ layoutScroll: true });
 | |
|                 rootProjectionNode.current = documentNode;
 | |
|             }
 | |
|             return rootProjectionNode.current;
 | |
|         },
 | |
|         resetTransform: (instance, value) => {
 | |
|             instance.style.transform = value !== undefined ? value : "none";
 | |
|         },
 | |
|         checkIsScrollRoot: (instance) => Boolean(window.getComputedStyle(instance).position === "fixed"),
 | |
|     });
 | |
| 
 | |
|     const notify = (node) => !node.isLayoutDirty && node.willUpdate(false);
 | |
|     function nodeGroup() {
 | |
|         const nodes = new Set();
 | |
|         const subscriptions = new WeakMap();
 | |
|         const dirtyAll = () => nodes.forEach(notify);
 | |
|         return {
 | |
|             add: (node) => {
 | |
|                 nodes.add(node);
 | |
|                 subscriptions.set(node, node.addEventListener("willUpdate", dirtyAll));
 | |
|             },
 | |
|             remove: (node) => {
 | |
|                 nodes.delete(node);
 | |
|                 const unsubscribe = subscriptions.get(node);
 | |
|                 if (unsubscribe) {
 | |
|                     unsubscribe();
 | |
|                     subscriptions.delete(node);
 | |
|                 }
 | |
|                 dirtyAll();
 | |
|             },
 | |
|             dirty: dirtyAll,
 | |
|         };
 | |
|     }
 | |
| 
 | |
|     function pixelsToPercent(pixels, axis) {
 | |
|         if (axis.max === axis.min)
 | |
|             return 0;
 | |
|         return (pixels / (axis.max - axis.min)) * 100;
 | |
|     }
 | |
|     /**
 | |
|      * We always correct borderRadius as a percentage rather than pixels to reduce paints.
 | |
|      * For example, if you are projecting a box that is 100px wide with a 10px borderRadius
 | |
|      * into a box that is 200px wide with a 20px borderRadius, that is actually a 10%
 | |
|      * borderRadius in both states. If we animate between the two in pixels that will trigger
 | |
|      * a paint each time. If we animate between the two in percentage we'll avoid a paint.
 | |
|      */
 | |
|     const correctBorderRadius = {
 | |
|         correct: (latest, node) => {
 | |
|             if (!node.target)
 | |
|                 return latest;
 | |
|             /**
 | |
|              * If latest is a string, if it's a percentage we can return immediately as it's
 | |
|              * going to be stretched appropriately. Otherwise, if it's a pixel, convert it to a number.
 | |
|              */
 | |
|             if (typeof latest === "string") {
 | |
|                 if (px.test(latest)) {
 | |
|                     latest = parseFloat(latest);
 | |
|                 }
 | |
|                 else {
 | |
|                     return latest;
 | |
|                 }
 | |
|             }
 | |
|             /**
 | |
|              * If latest is a number, it's a pixel value. We use the current viewportBox to calculate that
 | |
|              * pixel value as a percentage of each axis
 | |
|              */
 | |
|             const x = pixelsToPercent(latest, node.target.x);
 | |
|             const y = pixelsToPercent(latest, node.target.y);
 | |
|             return `${x}% ${y}%`;
 | |
|         },
 | |
|     };
 | |
| 
 | |
|     const correctBoxShadow = {
 | |
|         correct: (latest, { treeScale, projectionDelta }) => {
 | |
|             const original = latest;
 | |
|             const shadow = complex.parse(latest);
 | |
|             // TODO: Doesn't support multiple shadows
 | |
|             if (shadow.length > 5)
 | |
|                 return original;
 | |
|             const template = complex.createTransformer(latest);
 | |
|             const offset = typeof shadow[0] !== "number" ? 1 : 0;
 | |
|             // Calculate the overall context scale
 | |
|             const xScale = projectionDelta.x.scale * treeScale.x;
 | |
|             const yScale = projectionDelta.y.scale * treeScale.y;
 | |
|             shadow[0 + offset] /= xScale;
 | |
|             shadow[1 + offset] /= yScale;
 | |
|             /**
 | |
|              * Ideally we'd correct x and y scales individually, but because blur and
 | |
|              * spread apply to both we have to take a scale average and apply that instead.
 | |
|              * We could potentially improve the outcome of this by incorporating the ratio between
 | |
|              * the two scales.
 | |
|              */
 | |
|             const averageScale = mix(xScale, yScale, 0.5);
 | |
|             // Blur
 | |
|             if (typeof shadow[2 + offset] === "number")
 | |
|                 shadow[2 + offset] /= averageScale;
 | |
|             // Spread
 | |
|             if (typeof shadow[3 + offset] === "number")
 | |
|                 shadow[3 + offset] /= averageScale;
 | |
|             return template(shadow);
 | |
|         },
 | |
|     };
 | |
| 
 | |
|     exports.HTMLProjectionNode = HTMLProjectionNode;
 | |
|     exports.HTMLVisualElement = HTMLVisualElement;
 | |
|     exports.addScaleCorrector = addScaleCorrector;
 | |
|     exports.animate = animateValue;
 | |
|     exports.buildTransform = buildTransform;
 | |
|     exports.calcBoxDelta = calcBoxDelta;
 | |
|     exports.correctBorderRadius = correctBorderRadius;
 | |
|     exports.correctBoxShadow = correctBoxShadow;
 | |
|     exports.frame = frame;
 | |
|     exports.frameData = frameData;
 | |
|     exports.mix = mix;
 | |
|     exports.nodeGroup = nodeGroup;
 | |
| 
 | |
|     Object.defineProperty(exports, '__esModule', { value: true });
 | |
| 
 | |
| }));
 |