feat: 氧化铝数字孪生系统监控大屏完成

This commit is contained in:
2026-04-08 21:44:08 +08:00
commit a48babc68d
67606 changed files with 3337335 additions and 0 deletions

View File

@@ -0,0 +1,57 @@
import { GroupAnimationWithThen } from 'motion-dom';
import { removeItem } from 'motion-utils';
import { animateSequence } from './sequence.mjs';
import { animateSubject } from './subject.mjs';
function isSequence(value) {
return Array.isArray(value) && value.some(Array.isArray);
}
/**
* Creates an animation function that is optionally scoped
* to a specific element.
*/
function createScopedAnimate(options = {}) {
const { scope, reduceMotion } = options;
/**
* Implementation
*/
function scopedAnimate(subjectOrSequence, optionsOrKeyframes, options) {
let animations = [];
let animationOnComplete;
if (isSequence(subjectOrSequence)) {
const { onComplete, ...sequenceOptions } = optionsOrKeyframes || {};
if (typeof onComplete === "function") {
animationOnComplete = onComplete;
}
animations = animateSequence(subjectOrSequence, reduceMotion !== undefined
? { reduceMotion, ...sequenceOptions }
: sequenceOptions, scope);
}
else {
// Extract top-level onComplete so it doesn't get applied per-value
const { onComplete, ...rest } = options || {};
if (typeof onComplete === "function") {
animationOnComplete = onComplete;
}
animations = animateSubject(subjectOrSequence, optionsOrKeyframes, (reduceMotion !== undefined
? { reduceMotion, ...rest }
: rest), scope);
}
const animation = new GroupAnimationWithThen(animations);
if (animationOnComplete) {
animation.finished.then(animationOnComplete);
}
if (scope) {
scope.animations.push(animation);
animation.finished.then(() => {
removeItem(scope.animations, animation);
});
}
return animation;
}
return scopedAnimate;
}
const animate = createScopedAnimate();
export { animate, createScopedAnimate };
//# sourceMappingURL=index.mjs.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,23 @@
import { resolveElements } from 'motion-dom';
import { isDOMKeyframes } from '../utils/is-dom-keyframes.mjs';
function resolveSubjects(subject, keyframes, scope, selectorCache) {
if (subject == null) {
return [];
}
if (typeof subject === "string" && isDOMKeyframes(keyframes)) {
return resolveElements(subject, scope, selectorCache);
}
else if (subject instanceof NodeList) {
return Array.from(subject);
}
else if (Array.isArray(subject)) {
return subject.filter((s) => s != null);
}
else {
return [subject];
}
}
export { resolveSubjects };
//# sourceMappingURL=resolve-subjects.mjs.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"resolve-subjects.mjs","sources":["../../../../src/animation/animate/resolve-subjects.ts"],"sourcesContent":["import {\n AnimationScope,\n DOMKeyframesDefinition,\n SelectorCache,\n resolveElements,\n} from \"motion-dom\"\nimport { ObjectTarget } from \"../sequence/types\"\nimport { isDOMKeyframes } from \"../utils/is-dom-keyframes\"\n\nexport function resolveSubjects<O extends {}>(\n subject:\n | string\n | Element\n | Element[]\n | NodeListOf<Element>\n | O\n | O[]\n | null\n | undefined,\n keyframes: DOMKeyframesDefinition | ObjectTarget<O>,\n scope?: AnimationScope,\n selectorCache?: SelectorCache\n) {\n if (subject == null) {\n return []\n }\n\n if (typeof subject === \"string\" && isDOMKeyframes(keyframes)) {\n return resolveElements(subject, scope, selectorCache)\n } else if (subject instanceof NodeList) {\n return Array.from(subject)\n } else if (Array.isArray(subject)) {\n return subject.filter((s) => s != null)\n } else {\n return [subject]\n }\n}\n"],"names":[],"mappings":";;;AASM,SAAU,eAAe,CAC3B,OAQe,EACf,SAAmD,EACnD,KAAsB,EACtB,aAA6B,EAAA;AAE7B,IAAA,IAAI,OAAO,IAAI,IAAI,EAAE;AACjB,QAAA,OAAO,EAAE;IACb;IAEA,IAAI,OAAO,OAAO,KAAK,QAAQ,IAAI,cAAc,CAAC,SAAS,CAAC,EAAE;QAC1D,OAAO,eAAe,CAAC,OAAO,EAAE,KAAK,EAAE,aAAa,CAAC;IACzD;AAAO,SAAA,IAAI,OAAO,YAAY,QAAQ,EAAE;AACpC,QAAA,OAAO,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC;IAC9B;AAAO,SAAA,IAAI,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE;AAC/B,QAAA,OAAO,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,IAAI,CAAC;IAC3C;SAAO;QACH,OAAO,CAAC,OAAO,CAAC;IACpB;AACJ;;;;"}

View File

@@ -0,0 +1,36 @@
import { motionValue, spring } from 'motion-dom';
import { createAnimationsFromSequence } from '../sequence/create.mjs';
import { animateSubject } from './subject.mjs';
function animateSequence(sequence, options, scope) {
const animations = [];
/**
* Pre-process: replace function segments with MotionValue segments,
* subscribe callbacks immediately
*/
const processedSequence = sequence.map((segment) => {
if (Array.isArray(segment) && typeof segment[0] === "function") {
const callback = segment[0];
const mv = motionValue(0);
mv.on("change", callback);
if (segment.length === 1) {
return [mv, [0, 1]];
}
else if (segment.length === 2) {
return [mv, [0, 1], segment[1]];
}
else {
return [mv, segment[1], segment[2]];
}
}
return segment;
});
const animationDefinitions = createAnimationsFromSequence(processedSequence, options, scope, { spring });
animationDefinitions.forEach(({ keyframes, transition }, subject) => {
animations.push(...animateSubject(subject, keyframes, transition));
});
return animations;
}
export { animateSequence };
//# sourceMappingURL=sequence.mjs.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"sequence.mjs","sources":["../../../../src/animation/animate/sequence.ts"],"sourcesContent":["import {\n AnimationPlaybackControlsWithThen,\n AnimationScope,\n motionValue,\n spring,\n} from \"motion-dom\"\nimport { createAnimationsFromSequence } from \"../sequence/create\"\nimport { AnimationSequence, SequenceOptions } from \"../sequence/types\"\nimport { animateSubject } from \"./subject\"\n\nexport function animateSequence(\n sequence: AnimationSequence,\n options?: SequenceOptions,\n scope?: AnimationScope\n) {\n const animations: AnimationPlaybackControlsWithThen[] = []\n\n /**\n * Pre-process: replace function segments with MotionValue segments,\n * subscribe callbacks immediately\n */\n const processedSequence = sequence.map((segment) => {\n if (Array.isArray(segment) && typeof segment[0] === \"function\") {\n const callback = segment[0] as (value: any) => void\n const mv = motionValue(0)\n mv.on(\"change\", callback)\n\n if (segment.length === 1) {\n return [mv, [0, 1]] as any\n } else if (segment.length === 2) {\n return [mv, [0, 1], segment[1]] as any\n } else {\n return [mv, segment[1], segment[2]] as any\n }\n }\n return segment\n }) as AnimationSequence\n\n const animationDefinitions = createAnimationsFromSequence(\n processedSequence,\n options,\n scope,\n { spring }\n )\n\n animationDefinitions.forEach(({ keyframes, transition }, subject) => {\n animations.push(...animateSubject(subject, keyframes, transition))\n })\n\n return animations\n}\n"],"names":[],"mappings":";;;;SAUgB,eAAe,CAC3B,QAA2B,EAC3B,OAAyB,EACzB,KAAsB,EAAA;IAEtB,MAAM,UAAU,GAAwC,EAAE;AAE1D;;;AAGG;IACH,MAAM,iBAAiB,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC,OAAO,KAAI;AAC/C,QAAA,IAAI,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,IAAI,OAAO,OAAO,CAAC,CAAC,CAAC,KAAK,UAAU,EAAE;AAC5D,YAAA,MAAM,QAAQ,GAAG,OAAO,CAAC,CAAC,CAAyB;AACnD,YAAA,MAAM,EAAE,GAAG,WAAW,CAAC,CAAC,CAAC;AACzB,YAAA,EAAE,CAAC,EAAE,CAAC,QAAQ,EAAE,QAAQ,CAAC;AAEzB,YAAA,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE;gBACtB,OAAO,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,CAAQ;YAC9B;AAAO,iBAAA,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE;AAC7B,gBAAA,OAAO,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC,CAAC,CAAQ;YAC1C;iBAAO;AACH,gBAAA,OAAO,CAAC,EAAE,EAAE,OAAO,CAAC,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC,CAAC,CAAQ;YAC9C;QACJ;AACA,QAAA,OAAO,OAAO;AAClB,IAAA,CAAC,CAAsB;AAEvB,IAAA,MAAM,oBAAoB,GAAG,4BAA4B,CACrD,iBAAiB,EACjB,OAAO,EACP,KAAK,EACL,EAAE,MAAM,EAAE,CACb;AAED,IAAA,oBAAoB,CAAC,OAAO,CAAC,CAAC,EAAE,SAAS,EAAE,UAAU,EAAE,EAAE,OAAO,KAAI;AAChE,QAAA,UAAU,CAAC,IAAI,CAAC,GAAG,cAAc,CAAC,OAAO,EAAE,SAAS,EAAE,UAAU,CAAC,CAAC;AACtE,IAAA,CAAC,CAAC;AAEF,IAAA,OAAO,UAAU;AACrB;;;;"}

View File

@@ -0,0 +1,54 @@
import { animateSingleValue, visualElementStore, animateTarget, isMotionValue } from 'motion-dom';
import { invariant } from 'motion-utils';
import { createDOMVisualElement, createObjectVisualElement } from '../utils/create-visual-element.mjs';
import { isDOMKeyframes } from '../utils/is-dom-keyframes.mjs';
import { resolveSubjects } from './resolve-subjects.mjs';
function isSingleValue(subject, keyframes) {
return (isMotionValue(subject) ||
typeof subject === "number" ||
(typeof subject === "string" && !isDOMKeyframes(keyframes)));
}
/**
* Implementation
*/
function animateSubject(subject, keyframes, options, scope) {
const animations = [];
if (isSingleValue(subject, keyframes)) {
animations.push(animateSingleValue(subject, isDOMKeyframes(keyframes)
? keyframes.default || keyframes
: keyframes, options ? options.default || options : options));
}
else {
// Gracefully handle null/undefined subjects (e.g., from querySelector returning null)
if (subject == null) {
return animations;
}
const subjects = resolveSubjects(subject, keyframes, scope);
const numSubjects = subjects.length;
invariant(Boolean(numSubjects), "No valid elements provided.", "no-valid-elements");
for (let i = 0; i < numSubjects; i++) {
const thisSubject = subjects[i];
const createVisualElement = thisSubject instanceof Element
? createDOMVisualElement
: createObjectVisualElement;
if (!visualElementStore.has(thisSubject)) {
createVisualElement(thisSubject);
}
const visualElement = visualElementStore.get(thisSubject);
const transition = { ...options };
/**
* Resolve stagger function if provided.
*/
if ("delay" in transition &&
typeof transition.delay === "function") {
transition.delay = transition.delay(i, numSubjects);
}
animations.push(...animateTarget(visualElement, { ...keyframes, transition }, {}));
}
}
return animations;
}
export { animateSubject };
//# sourceMappingURL=subject.mjs.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,110 @@
import { resolveElements, getValueTransition, getAnimationMap, animationMapKey, getComputedStyle, fillWildcards, applyPxDefaults, NativeAnimation } from 'motion-dom';
import { invariant, secondsToMilliseconds } from 'motion-utils';
function animateElements(elementOrSelector, keyframes, options, scope) {
// Gracefully handle null/undefined elements (e.g., from querySelector returning null)
if (elementOrSelector == null) {
return [];
}
const elements = resolveElements(elementOrSelector, scope);
const numElements = elements.length;
invariant(Boolean(numElements), "No valid elements provided.", "no-valid-elements");
/**
* WAAPI doesn't support interrupting animations.
*
* Therefore, starting animations requires a three-step process:
* 1. Stop existing animations (write styles to DOM)
* 2. Resolve keyframes (read styles from DOM)
* 3. Create new animations (write styles to DOM)
*
* The hybrid `animate()` function uses AsyncAnimation to resolve
* keyframes before creating new animations, which removes style
* thrashing. Here, we have much stricter filesize constraints.
* Therefore we do this in a synchronous way that ensures that
* at least within `animate()` calls there is no style thrashing.
*
* In the motion-native-animate-mini-interrupt benchmark this
* was 80% faster than a single loop.
*/
const animationDefinitions = [];
/**
* Step 1: Build options and stop existing animations (write)
*/
for (let i = 0; i < numElements; i++) {
const element = elements[i];
const elementTransition = { ...options };
/**
* Resolve stagger function if provided.
*/
if (typeof elementTransition.delay === "function") {
elementTransition.delay = elementTransition.delay(i, numElements);
}
for (const valueName in keyframes) {
let valueKeyframes = keyframes[valueName];
if (!Array.isArray(valueKeyframes)) {
valueKeyframes = [valueKeyframes];
}
const valueOptions = {
...getValueTransition(elementTransition, valueName),
};
valueOptions.duration && (valueOptions.duration = secondsToMilliseconds(valueOptions.duration));
valueOptions.delay && (valueOptions.delay = secondsToMilliseconds(valueOptions.delay));
/**
* If there's an existing animation playing on this element then stop it
* before creating a new one.
*/
const map = getAnimationMap(element);
const key = animationMapKey(valueName, valueOptions.pseudoElement || "");
const currentAnimation = map.get(key);
currentAnimation && currentAnimation.stop();
animationDefinitions.push({
map,
key,
unresolvedKeyframes: valueKeyframes,
options: {
...valueOptions,
element,
name: valueName,
allowFlatten: !elementTransition.type && !elementTransition.ease,
},
});
}
}
/**
* Step 2: Resolve keyframes (read)
*/
for (let i = 0; i < animationDefinitions.length; i++) {
const { unresolvedKeyframes, options: animationOptions } = animationDefinitions[i];
const { element, name, pseudoElement } = animationOptions;
if (!pseudoElement && unresolvedKeyframes[0] === null) {
unresolvedKeyframes[0] = getComputedStyle(element, name);
}
fillWildcards(unresolvedKeyframes);
applyPxDefaults(unresolvedKeyframes, name);
/**
* If we only have one keyframe, explicitly read the initial keyframe
* from the computed style. This is to ensure consistency with WAAPI behaviour
* for restarting animations, for instance .play() after finish, when it
* has one vs two keyframes.
*/
if (!pseudoElement && unresolvedKeyframes.length < 2) {
unresolvedKeyframes.unshift(getComputedStyle(element, name));
}
animationOptions.keyframes = unresolvedKeyframes;
}
/**
* Step 3: Create new animations (write)
*/
const animations = [];
for (let i = 0; i < animationDefinitions.length; i++) {
const { map, key, options: animationOptions } = animationDefinitions[i];
const animation = new NativeAnimation(animationOptions);
map.set(key, animation);
animation.finished.finally(() => map.delete(key));
animations.push(animation);
}
return animations;
}
export { animateElements };
//# sourceMappingURL=animate-elements.mjs.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,14 @@
import { GroupAnimationWithThen } from 'motion-dom';
import { createAnimationsFromSequence } from '../../sequence/create.mjs';
import { animateElements } from './animate-elements.mjs';
function animateSequence(definition, options) {
const animations = [];
createAnimationsFromSequence(definition, options).forEach(({ keyframes, transition }, element) => {
animations.push(...animateElements(element, keyframes, transition));
});
return new GroupAnimationWithThen(animations);
}
export { animateSequence };
//# sourceMappingURL=animate-sequence.mjs.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"animate-sequence.mjs","sources":["../../../../../src/animation/animators/waapi/animate-sequence.ts"],"sourcesContent":["import { AnimationPlaybackControls, GroupAnimationWithThen } from \"motion-dom\"\nimport { createAnimationsFromSequence } from \"../../sequence/create\"\nimport { AnimationSequence, SequenceOptions } from \"../../sequence/types\"\nimport { animateElements } from \"./animate-elements\"\n\nexport function animateSequence(\n definition: AnimationSequence,\n options?: SequenceOptions\n) {\n const animations: AnimationPlaybackControls[] = []\n\n createAnimationsFromSequence(definition, options).forEach(\n ({ keyframes, transition }, element: Element) => {\n animations.push(...animateElements(element, keyframes, transition))\n }\n )\n\n return new GroupAnimationWithThen(animations)\n}\n"],"names":[],"mappings":";;;;AAKM,SAAU,eAAe,CAC3B,UAA6B,EAC7B,OAAyB,EAAA;IAEzB,MAAM,UAAU,GAAgC,EAAE;AAElD,IAAA,4BAA4B,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC,OAAO,CACrD,CAAC,EAAE,SAAS,EAAE,UAAU,EAAE,EAAE,OAAgB,KAAI;AAC5C,QAAA,UAAU,CAAC,IAAI,CAAC,GAAG,eAAe,CAAC,OAAO,EAAE,SAAS,EAAE,UAAU,CAAC,CAAC;AACvE,IAAA,CAAC,CACJ;AAED,IAAA,OAAO,IAAI,sBAAsB,CAAC,UAAU,CAAC;AACjD;;;;"}

View File

@@ -0,0 +1,13 @@
import { GroupAnimationWithThen } from 'motion-dom';
import { animateElements } from './animate-elements.mjs';
const createScopedWaapiAnimate = (scope) => {
function scopedAnimate(elementOrSelector, keyframes, options) {
return new GroupAnimationWithThen(animateElements(elementOrSelector, keyframes, options, scope));
}
return scopedAnimate;
};
const animateMini = /*@__PURE__*/ createScopedWaapiAnimate();
export { animateMini, createScopedWaapiAnimate };
//# sourceMappingURL=animate-style.mjs.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"animate-style.mjs","sources":["../../../../../src/animation/animators/waapi/animate-style.ts"],"sourcesContent":["import {\n AnimationPlaybackControlsWithThen,\n AnimationScope,\n DOMKeyframesDefinition,\n AnimationOptions as DynamicAnimationOptions,\n ElementOrSelector,\n GroupAnimationWithThen,\n} from \"motion-dom\"\nimport { animateElements } from \"./animate-elements\"\n\nexport const createScopedWaapiAnimate = (scope?: AnimationScope) => {\n function scopedAnimate(\n elementOrSelector: ElementOrSelector,\n keyframes: DOMKeyframesDefinition,\n options?: DynamicAnimationOptions\n ): AnimationPlaybackControlsWithThen {\n return new GroupAnimationWithThen(\n animateElements(\n elementOrSelector,\n keyframes as DOMKeyframesDefinition,\n options,\n scope\n )\n )\n }\n\n return scopedAnimate\n}\n\nexport const animateMini = /*@__PURE__*/ createScopedWaapiAnimate()\n"],"names":[],"mappings":";;;AAUO,MAAM,wBAAwB,GAAG,CAAC,KAAsB,KAAI;AAC/D,IAAA,SAAS,aAAa,CAClB,iBAAoC,EACpC,SAAiC,EACjC,OAAiC,EAAA;AAEjC,QAAA,OAAO,IAAI,sBAAsB,CAC7B,eAAe,CACX,iBAAiB,EACjB,SAAmC,EACnC,OAAO,EACP,KAAK,CACR,CACJ;IACL;AAEA,IAAA,OAAO,aAAa;AACxB;MAEa,WAAW,iBAAiB,wBAAwB;;;;"}

View File

@@ -0,0 +1,80 @@
import { animateVisualElement, setTarget } from 'motion-dom';
import { invariant } from 'motion-utils';
function stopAnimation(visualElement) {
visualElement.values.forEach((value) => value.stop());
}
function setVariants(visualElement, variantLabels) {
const reversedLabels = [...variantLabels].reverse();
reversedLabels.forEach((key) => {
const variant = visualElement.getVariant(key);
variant && setTarget(visualElement, variant);
if (visualElement.variantChildren) {
visualElement.variantChildren.forEach((child) => {
setVariants(child, variantLabels);
});
}
});
}
function setValues(visualElement, definition) {
if (Array.isArray(definition)) {
return setVariants(visualElement, definition);
}
else if (typeof definition === "string") {
return setVariants(visualElement, [definition]);
}
else {
setTarget(visualElement, definition);
}
}
/**
* @public
*/
function animationControls() {
/**
* Track whether the host component has mounted.
*/
let hasMounted = false;
/**
* A collection of linked component animation controls.
*/
const subscribers = new Set();
const controls = {
subscribe(visualElement) {
subscribers.add(visualElement);
return () => void subscribers.delete(visualElement);
},
start(definition, transitionOverride) {
invariant(hasMounted, "controls.start() should only be called after a component has mounted. Consider calling within a useEffect hook.");
const animations = [];
subscribers.forEach((visualElement) => {
animations.push(animateVisualElement(visualElement, definition, {
transitionOverride,
}));
});
return Promise.all(animations);
},
set(definition) {
invariant(hasMounted, "controls.set() should only be called after a component has mounted. Consider calling within a useEffect hook.");
return subscribers.forEach((visualElement) => {
setValues(visualElement, definition);
});
},
stop() {
subscribers.forEach((visualElement) => {
stopAnimation(visualElement);
});
},
mount() {
hasMounted = true;
return () => {
hasMounted = false;
controls.stop();
};
},
};
return controls;
}
export { animationControls, setValues };
//# sourceMappingURL=animation-controls.mjs.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,19 @@
"use client";
import { useConstant } from '../../utils/use-constant.mjs';
import { useUnmountEffect } from '../../utils/use-unmount-effect.mjs';
import { createScopedWaapiAnimate } from '../animators/waapi/animate-style.mjs';
function useAnimateMini() {
const scope = useConstant(() => ({
current: null, // Will be hydrated by React
animations: [],
}));
const animate = useConstant(() => createScopedWaapiAnimate(scope));
useUnmountEffect(() => {
scope.animations.forEach((animation) => animation.stop());
});
return [scope, animate];
}
export { useAnimateMini };
//# sourceMappingURL=use-animate-style.mjs.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"use-animate-style.mjs","sources":["../../../../src/animation/hooks/use-animate-style.ts"],"sourcesContent":["\"use client\"\n\nimport { useConstant } from \"../../utils/use-constant\"\nimport { useUnmountEffect } from \"../../utils/use-unmount-effect\"\nimport { createScopedWaapiAnimate } from \"../animators/waapi/animate-style\"\nimport { AnimationScope } from \"motion-dom\"\n\nexport function useAnimateMini<T extends Element = any>() {\n const scope: AnimationScope<T> = useConstant(() => ({\n current: null!, // Will be hydrated by React\n animations: [],\n }))\n\n const animate = useConstant(() => createScopedWaapiAnimate(scope))\n\n useUnmountEffect(() => {\n scope.animations.forEach((animation) => animation.stop())\n })\n\n return [scope, animate] as [AnimationScope<T>, typeof animate]\n}\n"],"names":[],"mappings":";;;;;;AAQI;;AAEI;AACH;AAED;;AAGI;AACJ;AAEA;AACJ;;"}

View File

@@ -0,0 +1,23 @@
"use client";
import { useMemo } from 'react';
import { useConstant } from '../../utils/use-constant.mjs';
import { useUnmountEffect } from '../../utils/use-unmount-effect.mjs';
import { useReducedMotionConfig } from '../../utils/reduced-motion/use-reduced-motion-config.mjs';
import { createScopedAnimate } from '../animate/index.mjs';
function useAnimate() {
const scope = useConstant(() => ({
current: null, // Will be hydrated by React
animations: [],
}));
const reduceMotion = useReducedMotionConfig() ?? undefined;
const animate = useMemo(() => createScopedAnimate({ scope, reduceMotion }), [scope, reduceMotion]);
useUnmountEffect(() => {
scope.animations.forEach((animation) => animation.stop());
scope.animations.length = 0;
});
return [scope, animate];
}
export { useAnimate };
//# sourceMappingURL=use-animate.mjs.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"use-animate.mjs","sources":["../../../../src/animation/hooks/use-animate.ts"],"sourcesContent":["\"use client\"\n\nimport { useMemo } from \"react\"\nimport { AnimationScope } from \"motion-dom\"\nimport { useConstant } from \"../../utils/use-constant\"\nimport { useUnmountEffect } from \"../../utils/use-unmount-effect\"\nimport { useReducedMotionConfig } from \"../../utils/reduced-motion/use-reduced-motion-config\"\nimport { createScopedAnimate } from \"../animate\"\n\nexport function useAnimate<T extends Element = any>() {\n const scope: AnimationScope<T> = useConstant(() => ({\n current: null!, // Will be hydrated by React\n animations: [],\n }))\n\n const reduceMotion = useReducedMotionConfig() ?? undefined\n\n const animate = useMemo(\n () => createScopedAnimate({ scope, reduceMotion }),\n [scope, reduceMotion]\n )\n\n useUnmountEffect(() => {\n scope.animations.forEach((animation) => animation.stop())\n scope.animations.length = 0\n })\n\n return [scope, animate] as [AnimationScope<T>, typeof animate]\n}\n"],"names":[],"mappings":";;;;;;;;AAUI;;AAEI;AACH;AAED;;;AAQI;AACA;AACJ;AAEA;AACJ;;"}

View File

@@ -0,0 +1,64 @@
"use client";
import { animateVisualElement, VisualElement, createBox } from 'motion-dom';
import { useState, useLayoutEffect } from 'react';
import { makeUseVisualState } from '../../motion/utils/use-visual-state.mjs';
import { useConstant } from '../../utils/use-constant.mjs';
const createObject = () => ({});
class StateVisualElement extends VisualElement {
constructor() {
super(...arguments);
this.measureInstanceViewportBox = createBox;
}
build() { }
resetTransform() { }
restoreTransform() { }
removeValueFromRenderState() { }
renderInstance() { }
scrapeMotionValuesFromProps() {
return createObject();
}
getBaseTargetFromProps() {
return undefined;
}
readValueFromInstance(_state, key, options) {
return options.initialState[key] || 0;
}
sortInstanceNodePosition() {
return 0;
}
}
const useVisualState = makeUseVisualState({
scrapeMotionValuesFromProps: createObject,
createRenderState: createObject,
});
/**
* This is not an officially supported API and may be removed
* on any version.
*/
function useAnimatedState(initialState) {
const [animationState, setAnimationState] = useState(initialState);
const visualState = useVisualState({}, false);
const element = useConstant(() => {
return new StateVisualElement({
props: {
onUpdate: (v) => {
setAnimationState({ ...v });
},
},
visualState,
presenceContext: null,
}, { initialState });
});
useLayoutEffect(() => {
element.mount({});
return () => element.unmount();
}, [element]);
const startAnimation = useConstant(() => (animationDefinition) => {
return animateVisualElement(element, animationDefinition);
});
return [animationState, startAnimation];
}
export { useAnimatedState };
//# sourceMappingURL=use-animated-state.mjs.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"use-animated-state.mjs","sources":["../../../../src/animation/hooks/use-animated-state.ts"],"sourcesContent":["\"use client\"\n\nimport {\n animateVisualElement,\n createBox,\n ResolvedValues,\n TargetAndTransition,\n VisualElement,\n} from \"motion-dom\"\nimport { useLayoutEffect, useState } from \"react\"\nimport { makeUseVisualState } from \"../../motion/utils/use-visual-state\"\nimport { useConstant } from \"../../utils/use-constant\"\n\ninterface AnimatedStateOptions {\n initialState: ResolvedValues\n}\n\nconst createObject = () => ({})\n\nclass StateVisualElement extends VisualElement<\n ResolvedValues,\n {},\n AnimatedStateOptions\n> {\n type: \"state\"\n build() {}\n measureInstanceViewportBox = createBox\n resetTransform() {}\n restoreTransform() {}\n removeValueFromRenderState() {}\n renderInstance() {}\n scrapeMotionValuesFromProps() {\n return createObject()\n }\n getBaseTargetFromProps() {\n return undefined\n }\n\n readValueFromInstance(\n _state: ResolvedValues,\n key: string,\n options: AnimatedStateOptions\n ) {\n return options.initialState[key] || 0\n }\n\n sortInstanceNodePosition() {\n return 0\n }\n}\n\nconst useVisualState = makeUseVisualState({\n scrapeMotionValuesFromProps: createObject,\n createRenderState: createObject,\n})\n\n/**\n * This is not an officially supported API and may be removed\n * on any version.\n */\nexport function useAnimatedState(initialState: any) {\n const [animationState, setAnimationState] = useState(initialState)\n const visualState = useVisualState({}, false)\n\n const element = useConstant(() => {\n return new StateVisualElement(\n {\n props: {\n onUpdate: (v) => {\n setAnimationState({ ...v })\n },\n },\n visualState,\n presenceContext: null,\n },\n { initialState }\n )\n })\n\n useLayoutEffect(() => {\n element.mount({})\n return () => element.unmount()\n }, [element])\n\n const startAnimation = useConstant(\n () => (animationDefinition: TargetAndTransition) => {\n return animateVisualElement(element, animationDefinition)\n }\n )\n\n return [animationState, startAnimation]\n}\n"],"names":[],"mappings":";;;;;;AAiBA;AAEA;AAAA;;;;AAMI;AAEA;AACA;AACA;AACA;;;;;AAKI;;AAGJ;;;;AASI;;AAEP;AAED;AACI;AACA;AACH;AAED;;;AAGG;AACG;;;AAIF;;AAGY;AACI;AACI;;AAEP;;AAED;AACH;AAGT;;AAGI;AACA;AACJ;;AAIQ;AACJ;AAGJ;AACJ;;"}

View File

@@ -0,0 +1,43 @@
"use client";
import { useConstant } from '../../utils/use-constant.mjs';
import { useIsomorphicLayoutEffect } from '../../utils/use-isomorphic-effect.mjs';
import { animationControls } from './animation-controls.mjs';
/**
* Creates `LegacyAnimationControls`, which can be used to manually start, stop
* and sequence animations on one or more components.
*
* The returned `LegacyAnimationControls` should be passed to the `animate` property
* of the components you want to animate.
*
* These components can then be animated with the `start` method.
*
* ```jsx
* import * as React from 'react'
* import { motion, useAnimation } from 'framer-motion'
*
* export function MyComponent(props) {
* const controls = useAnimation()
*
* controls.start({
* x: 100,
* transition: { duration: 0.5 },
* })
*
* return <motion.div animate={controls} />
* }
* ```
*
* @returns Animation controller with `start` and `stop` methods
*
* @public
*/
function useAnimationControls() {
const controls = useConstant(animationControls);
useIsomorphicLayoutEffect(controls.mount, []);
return controls;
}
const useAnimation = useAnimationControls;
export { useAnimation, useAnimationControls };
//# sourceMappingURL=use-animation.mjs.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"use-animation.mjs","sources":["../../../../src/animation/hooks/use-animation.ts"],"sourcesContent":["\"use client\"\n\nimport { LegacyAnimationControls } from \"motion-dom\"\nimport { useConstant } from \"../../utils/use-constant\"\nimport { useIsomorphicLayoutEffect } from \"../../utils/use-isomorphic-effect\"\nimport { animationControls } from \"./animation-controls\"\n\n/**\n * Creates `LegacyAnimationControls`, which can be used to manually start, stop\n * and sequence animations on one or more components.\n *\n * The returned `LegacyAnimationControls` should be passed to the `animate` property\n * of the components you want to animate.\n *\n * These components can then be animated with the `start` method.\n *\n * ```jsx\n * import * as React from 'react'\n * import { motion, useAnimation } from 'framer-motion'\n *\n * export function MyComponent(props) {\n * const controls = useAnimation()\n *\n * controls.start({\n * x: 100,\n * transition: { duration: 0.5 },\n * })\n *\n * return <motion.div animate={controls} />\n * }\n * ```\n *\n * @returns Animation controller with `start` and `stop` methods\n *\n * @public\n */\nexport function useAnimationControls(): LegacyAnimationControls {\n const controls = useConstant(animationControls)\n\n useIsomorphicLayoutEffect(controls.mount, [])\n\n return controls\n}\n\nexport const useAnimation = useAnimationControls\n"],"names":[],"mappings":";;;;;AAOA;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA4BG;;AAEC;AAEA;AAEA;AACJ;AAEO;;"}

View File

@@ -0,0 +1,39 @@
import { appearAnimationStore } from './store.mjs';
import { appearStoreId } from './store-id.mjs';
function handoffOptimizedAppearAnimation(elementId, valueName, frame) {
const storeId = appearStoreId(elementId, valueName);
const optimisedAnimation = appearAnimationStore.get(storeId);
if (!optimisedAnimation) {
return null;
}
const { animation, startTime } = optimisedAnimation;
function cancelAnimation() {
window.MotionCancelOptimisedAnimation?.(elementId, valueName, frame);
}
/**
* We can cancel the animation once it's finished now that we've synced
* with Motion.
*
* Prefer onfinish over finished as onfinish is backwards compatible with
* older browsers.
*/
animation.onfinish = cancelAnimation;
if (startTime === null || window.MotionHandoffIsComplete?.(elementId)) {
/**
* If the startTime is null, this animation is the Paint Ready detection animation
* and we can cancel it immediately without handoff.
*
* Or if we've already handed off the animation then we're now interrupting it.
* In which case we need to cancel it.
*/
cancelAnimation();
return null;
}
else {
return startTime;
}
}
export { handoffOptimizedAppearAnimation };
//# sourceMappingURL=handoff.mjs.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"handoff.mjs","sources":["../../../../src/animation/optimized-appear/handoff.ts"],"sourcesContent":["import type { Batcher } from \"motion-dom\"\nimport { appearAnimationStore } from \"./store\"\nimport { appearStoreId } from \"./store-id\"\n\nexport function handoffOptimizedAppearAnimation(\n elementId: string,\n valueName: string,\n frame: Batcher\n): number | null {\n const storeId = appearStoreId(elementId, valueName)\n const optimisedAnimation = appearAnimationStore.get(storeId)\n\n if (!optimisedAnimation) {\n return null\n }\n\n const { animation, startTime } = optimisedAnimation\n\n function cancelAnimation() {\n window.MotionCancelOptimisedAnimation?.(elementId, valueName, frame)\n }\n\n /**\n * We can cancel the animation once it's finished now that we've synced\n * with Motion.\n *\n * Prefer onfinish over finished as onfinish is backwards compatible with\n * older browsers.\n */\n animation.onfinish = cancelAnimation\n\n if (startTime === null || window.MotionHandoffIsComplete?.(elementId)) {\n /**\n * If the startTime is null, this animation is the Paint Ready detection animation\n * and we can cancel it immediately without handoff.\n *\n * Or if we've already handed off the animation then we're now interrupting it.\n * In which case we need to cancel it.\n */\n cancelAnimation()\n return null\n } else {\n return startTime\n }\n}\n"],"names":[],"mappings":";;;SAIgB,+BAA+B,CAC3C,SAAiB,EACjB,SAAiB,EACjB,KAAc,EAAA;IAEd,MAAM,OAAO,GAAG,aAAa,CAAC,SAAS,EAAE,SAAS,CAAC;IACnD,MAAM,kBAAkB,GAAG,oBAAoB,CAAC,GAAG,CAAC,OAAO,CAAC;IAE5D,IAAI,CAAC,kBAAkB,EAAE;AACrB,QAAA,OAAO,IAAI;IACf;AAEA,IAAA,MAAM,EAAE,SAAS,EAAE,SAAS,EAAE,GAAG,kBAAkB;AAEnD,IAAA,SAAS,eAAe,GAAA;QACpB,MAAM,CAAC,8BAA8B,GAAG,SAAS,EAAE,SAAS,EAAE,KAAK,CAAC;IACxE;AAEA;;;;;;AAMG;AACH,IAAA,SAAS,CAAC,QAAQ,GAAG,eAAe;AAEpC,IAAA,IAAI,SAAS,KAAK,IAAI,IAAI,MAAM,CAAC,uBAAuB,GAAG,SAAS,CAAC,EAAE;AACnE;;;;;;AAMG;AACH,QAAA,eAAe,EAAE;AACjB,QAAA,OAAO,IAAI;IACf;SAAO;AACH,QAAA,OAAO,SAAS;IACpB;AACJ;;;;"}

View File

@@ -0,0 +1,170 @@
import { optimizedAppearDataId, startWaapiAnimation, getOptimisedAppearId } from 'motion-dom';
import { noop } from 'motion-utils';
import { handoffOptimizedAppearAnimation } from './handoff.mjs';
import { appearAnimationStore, appearComplete } from './store.mjs';
import { appearStoreId } from './store-id.mjs';
/**
* A single time to use across all animations to manually set startTime
* and ensure they're all in sync.
*/
let startFrameTime;
/**
* A dummy animation to detect when Chrome is ready to start
* painting the page and hold off from triggering the real animation
* until then. We only need one animation to detect paint ready.
*
* https://bugs.chromium.org/p/chromium/issues/detail?id=1406850
*/
let readyAnimation;
/**
* Keep track of animations that were suspended vs cancelled so we
* can easily resume them when we're done measuring layout.
*/
const suspendedAnimations = new Set();
function resumeSuspendedAnimations() {
suspendedAnimations.forEach((data) => {
data.animation.play();
data.animation.startTime = data.startTime;
});
suspendedAnimations.clear();
}
function startOptimizedAppearAnimation(element, name, keyframes, options, onReady) {
// Prevent optimised appear animations if Motion has already started animating.
if (window.MotionIsMounted) {
return;
}
const id = element.dataset[optimizedAppearDataId];
if (!id)
return;
window.MotionHandoffAnimation = handoffOptimizedAppearAnimation;
const storeId = appearStoreId(id, name);
if (!readyAnimation) {
readyAnimation = startWaapiAnimation(element, name, [keyframes[0], keyframes[0]],
/**
* 10 secs is basically just a super-safe duration to give Chrome
* long enough to get the animation ready.
*/
{ duration: 10000, ease: "linear" });
appearAnimationStore.set(storeId, {
animation: readyAnimation,
startTime: null,
});
/**
* If there's no readyAnimation then there's been no instantiation
* of handoff animations.
*/
window.MotionHandoffAnimation = handoffOptimizedAppearAnimation;
window.MotionHasOptimisedAnimation = (elementId, valueName) => {
if (!elementId)
return false;
/**
* Keep a map of elementIds that have started animating. We check
* via ID instead of Element because of hydration errors and
* pre-hydration checks. We also actively record IDs as they start
* animating rather than simply checking for data-appear-id as
* this attrbute might be present but not lead to an animation, for
* instance if the element's appear animation is on a different
* breakpoint.
*/
if (!valueName) {
return appearComplete.has(elementId);
}
const animationId = appearStoreId(elementId, valueName);
return Boolean(appearAnimationStore.get(animationId));
};
window.MotionHandoffMarkAsComplete = (elementId) => {
if (appearComplete.has(elementId)) {
appearComplete.set(elementId, true);
}
};
window.MotionHandoffIsComplete = (elementId) => {
return appearComplete.get(elementId) === true;
};
/**
* We only need to cancel transform animations as
* they're the ones that will interfere with the
* layout animation measurements.
*/
window.MotionCancelOptimisedAnimation = (elementId, valueName, frame, canResume) => {
const animationId = appearStoreId(elementId, valueName);
const data = appearAnimationStore.get(animationId);
if (!data)
return;
if (frame && canResume === undefined) {
/**
* Wait until the end of the subsequent frame to cancel the animation
* to ensure we don't remove the animation before the main thread has
* had a chance to resolve keyframes and render.
*/
frame.postRender(() => {
frame.postRender(() => {
data.animation.cancel();
});
});
}
else {
data.animation.cancel();
}
if (frame && canResume) {
suspendedAnimations.add(data);
frame.render(resumeSuspendedAnimations);
}
else {
appearAnimationStore.delete(animationId);
/**
* If there are no more animations left, we can remove the cancel function.
* This will let us know when we can stop checking for conflicting layout animations.
*/
if (!appearAnimationStore.size) {
window.MotionCancelOptimisedAnimation = undefined;
}
}
};
window.MotionCheckAppearSync = (visualElement, valueName, value) => {
const appearId = getOptimisedAppearId(visualElement);
if (!appearId)
return;
const valueIsOptimised = window.MotionHasOptimisedAnimation?.(appearId, valueName);
const externalAnimationValue = visualElement.props.values?.[valueName];
if (!valueIsOptimised || !externalAnimationValue)
return;
const removeSyncCheck = value.on("change", (latestValue) => {
if (externalAnimationValue.get() !== latestValue) {
window.MotionCancelOptimisedAnimation?.(appearId, valueName);
removeSyncCheck();
}
});
return removeSyncCheck;
};
}
const startAnimation = () => {
readyAnimation.cancel();
const appearAnimation = startWaapiAnimation(element, name, keyframes, options);
/**
* Record the time of the first started animation. We call performance.now() once
* here and once in handoff to ensure we're getting
* close to a frame-locked time. This keeps all animations in sync.
*/
if (startFrameTime === undefined) {
startFrameTime = performance.now();
}
appearAnimation.startTime = startFrameTime;
appearAnimationStore.set(storeId, {
animation: appearAnimation,
startTime: startFrameTime,
});
if (onReady)
onReady(appearAnimation);
};
appearComplete.set(id, false);
if (readyAnimation.ready) {
readyAnimation.ready.then(startAnimation).catch(noop);
}
else {
startAnimation();
}
}
export { startOptimizedAppearAnimation };
//# sourceMappingURL=start.mjs.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,9 @@
import { transformProps } from 'motion-dom';
const appearStoreId = (elementId, valueName) => {
const key = transformProps.has(valueName) ? "transform" : valueName;
return `${elementId}: ${key}`;
};
export { appearStoreId };
//# sourceMappingURL=store-id.mjs.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"store-id.mjs","sources":["../../../../src/animation/optimized-appear/store-id.ts"],"sourcesContent":["import { transformProps } from \"motion-dom\"\n\nexport const appearStoreId = (elementId: string, valueName: string) => {\n const key = transformProps.has(valueName) ? \"transform\" : valueName\n\n return `${elementId}: ${key}`\n}\n"],"names":[],"mappings":";;MAEa,aAAa,GAAG,CAAC,SAAiB,EAAE,SAAiB,KAAI;AAClE,IAAA,MAAM,GAAG,GAAG,cAAc,CAAC,GAAG,CAAC,SAAS,CAAC,GAAG,WAAW,GAAG,SAAS;AAEnE,IAAA,OAAO,CAAA,EAAG,SAAS,CAAA,EAAA,EAAK,GAAG,EAAE;AACjC;;;;"}

View File

@@ -0,0 +1,5 @@
const appearAnimationStore = new Map();
const appearComplete = new Map();
export { appearAnimationStore, appearComplete };
//# sourceMappingURL=store.mjs.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"store.mjs","sources":["../../../../src/animation/optimized-appear/store.ts"],"sourcesContent":["export interface AppearStoreEntry {\n animation: Animation\n startTime: number | null\n}\n\nexport type AppearElementId = string\n\nexport type IsComplete = boolean\n\nexport const appearAnimationStore = new Map<AppearElementId, AppearStoreEntry>()\n\nexport const appearComplete = new Map<AppearElementId, IsComplete>()\n"],"names":[],"mappings":"AASO,MAAM,oBAAoB,GAAG,IAAI,GAAG;AAEpC,MAAM,cAAc,GAAG,IAAI,GAAG;;;;"}

View File

@@ -0,0 +1,260 @@
import { isMotionValue, defaultOffset, isGenerator, createGeneratorEasing, fillOffset } from 'motion-dom';
import { progress, secondsToMilliseconds, invariant, getEasingForSegment } from 'motion-utils';
import { resolveSubjects } from '../animate/resolve-subjects.mjs';
import { calculateRepeatDuration } from './utils/calc-repeat-duration.mjs';
import { calcNextTime } from './utils/calc-time.mjs';
import { addKeyframes } from './utils/edit.mjs';
import { normalizeTimes } from './utils/normalize-times.mjs';
import { compareByTime } from './utils/sort.mjs';
const defaultSegmentEasing = "easeInOut";
const MAX_REPEAT = 20;
function createAnimationsFromSequence(sequence, { defaultTransition = {}, ...sequenceTransition } = {}, scope, generators) {
const defaultDuration = defaultTransition.duration || 0.3;
const animationDefinitions = new Map();
const sequences = new Map();
const elementCache = {};
const timeLabels = new Map();
let prevTime = 0;
let currentTime = 0;
let totalDuration = 0;
/**
* Build the timeline by mapping over the sequence array and converting
* the definitions into keyframes and offsets with absolute time values.
* These will later get converted into relative offsets in a second pass.
*/
for (let i = 0; i < sequence.length; i++) {
const segment = sequence[i];
/**
* If this is a timeline label, mark it and skip the rest of this iteration.
*/
if (typeof segment === "string") {
timeLabels.set(segment, currentTime);
continue;
}
else if (!Array.isArray(segment)) {
timeLabels.set(segment.name, calcNextTime(currentTime, segment.at, prevTime, timeLabels));
continue;
}
let [subject, keyframes, transition = {}] = segment;
/**
* If a relative or absolute time value has been specified we need to resolve
* it in relation to the currentTime.
*/
if (transition.at !== undefined) {
currentTime = calcNextTime(currentTime, transition.at, prevTime, timeLabels);
}
/**
* Keep track of the maximum duration in this definition. This will be
* applied to currentTime once the definition has been parsed.
*/
let maxDuration = 0;
const resolveValueSequence = (valueKeyframes, valueTransition, valueSequence, elementIndex = 0, numSubjects = 0) => {
const valueKeyframesAsList = keyframesAsList(valueKeyframes);
const { delay = 0, times = defaultOffset(valueKeyframesAsList), type = defaultTransition.type || "keyframes", repeat, repeatType, repeatDelay = 0, ...remainingTransition } = valueTransition;
let { ease = defaultTransition.ease || "easeOut", duration } = valueTransition;
/**
* Resolve stagger() if defined.
*/
const calculatedDelay = typeof delay === "function"
? delay(elementIndex, numSubjects)
: delay;
/**
* If this animation should and can use a spring, generate a spring easing function.
*/
const numKeyframes = valueKeyframesAsList.length;
const createGenerator = isGenerator(type)
? type
: generators?.[type || "keyframes"];
if (numKeyframes <= 2 && createGenerator) {
/**
* As we're creating an easing function from a spring,
* ideally we want to generate it using the real distance
* between the two keyframes. However this isn't always
* possible - in these situations we use 0-100.
*/
let absoluteDelta = 100;
if (numKeyframes === 2 &&
isNumberKeyframesArray(valueKeyframesAsList)) {
const delta = valueKeyframesAsList[1] - valueKeyframesAsList[0];
absoluteDelta = Math.abs(delta);
}
const springTransition = {
...defaultTransition,
...remainingTransition,
};
if (duration !== undefined) {
springTransition.duration = secondsToMilliseconds(duration);
}
const springEasing = createGeneratorEasing(springTransition, absoluteDelta, createGenerator);
ease = springEasing.ease;
duration = springEasing.duration;
}
duration ?? (duration = defaultDuration);
const startTime = currentTime + calculatedDelay;
/**
* If there's only one time offset of 0, fill in a second with length 1
*/
if (times.length === 1 && times[0] === 0) {
times[1] = 1;
}
/**
* Fill out if offset if fewer offsets than keyframes
*/
const remainder = times.length - valueKeyframesAsList.length;
remainder > 0 && fillOffset(times, remainder);
/**
* If only one value has been set, ie [1], push a null to the start of
* the keyframe array. This will let us mark a keyframe at this point
* that will later be hydrated with the previous value.
*/
valueKeyframesAsList.length === 1 &&
valueKeyframesAsList.unshift(null);
/**
* Handle repeat options
*/
if (repeat) {
invariant(repeat < MAX_REPEAT, "Repeat count too high, must be less than 20", "repeat-count-high");
duration = calculateRepeatDuration(duration, repeat);
const originalKeyframes = [...valueKeyframesAsList];
const originalTimes = [...times];
ease = Array.isArray(ease) ? [...ease] : [ease];
const originalEase = [...ease];
for (let repeatIndex = 0; repeatIndex < repeat; repeatIndex++) {
valueKeyframesAsList.push(...originalKeyframes);
for (let keyframeIndex = 0; keyframeIndex < originalKeyframes.length; keyframeIndex++) {
times.push(originalTimes[keyframeIndex] + (repeatIndex + 1));
ease.push(keyframeIndex === 0
? "linear"
: getEasingForSegment(originalEase, keyframeIndex - 1));
}
}
normalizeTimes(times, repeat);
}
const targetTime = startTime + duration;
/**
* Add keyframes, mapping offsets to absolute time.
*/
addKeyframes(valueSequence, valueKeyframesAsList, ease, times, startTime, targetTime);
maxDuration = Math.max(calculatedDelay + duration, maxDuration);
totalDuration = Math.max(targetTime, totalDuration);
};
if (isMotionValue(subject)) {
const subjectSequence = getSubjectSequence(subject, sequences);
resolveValueSequence(keyframes, transition, getValueSequence("default", subjectSequence));
}
else {
const subjects = resolveSubjects(subject, keyframes, scope, elementCache);
const numSubjects = subjects.length;
/**
* For every element in this segment, process the defined values.
*/
for (let subjectIndex = 0; subjectIndex < numSubjects; subjectIndex++) {
/**
* Cast necessary, but we know these are of this type
*/
keyframes = keyframes;
transition = transition;
const thisSubject = subjects[subjectIndex];
const subjectSequence = getSubjectSequence(thisSubject, sequences);
for (const key in keyframes) {
resolveValueSequence(keyframes[key], getValueTransition(transition, key), getValueSequence(key, subjectSequence), subjectIndex, numSubjects);
}
}
}
prevTime = currentTime;
currentTime += maxDuration;
}
/**
* For every element and value combination create a new animation.
*/
sequences.forEach((valueSequences, element) => {
for (const key in valueSequences) {
const valueSequence = valueSequences[key];
/**
* Arrange all the keyframes in ascending time order.
*/
valueSequence.sort(compareByTime);
const keyframes = [];
const valueOffset = [];
const valueEasing = [];
/**
* For each keyframe, translate absolute times into
* relative offsets based on the total duration of the timeline.
*/
for (let i = 0; i < valueSequence.length; i++) {
const { at, value, easing } = valueSequence[i];
keyframes.push(value);
valueOffset.push(progress(0, totalDuration, at));
valueEasing.push(easing || "easeOut");
}
/**
* If the first keyframe doesn't land on offset: 0
* provide one by duplicating the initial keyframe. This ensures
* it snaps to the first keyframe when the animation starts.
*/
if (valueOffset[0] !== 0) {
valueOffset.unshift(0);
keyframes.unshift(keyframes[0]);
valueEasing.unshift(defaultSegmentEasing);
}
/**
* If the last keyframe doesn't land on offset: 1
* provide one with a null wildcard value. This will ensure it
* stays static until the end of the animation.
*/
if (valueOffset[valueOffset.length - 1] !== 1) {
valueOffset.push(1);
keyframes.push(null);
}
if (!animationDefinitions.has(element)) {
animationDefinitions.set(element, {
keyframes: {},
transition: {},
});
}
const definition = animationDefinitions.get(element);
definition.keyframes[key] = keyframes;
/**
* Exclude `type` from defaultTransition since springs have been
* converted to duration-based easing functions in resolveValueSequence.
* Including `type: "spring"` would cause JSAnimation to error when
* the merged keyframes array has more than 2 keyframes.
*/
const { type: _type, ...remainingDefaultTransition } = defaultTransition;
definition.transition[key] = {
...remainingDefaultTransition,
duration: totalDuration,
ease: valueEasing,
times: valueOffset,
...sequenceTransition,
};
}
});
return animationDefinitions;
}
function getSubjectSequence(subject, sequences) {
!sequences.has(subject) && sequences.set(subject, {});
return sequences.get(subject);
}
function getValueSequence(name, sequences) {
if (!sequences[name])
sequences[name] = [];
return sequences[name];
}
function keyframesAsList(keyframes) {
return Array.isArray(keyframes) ? keyframes : [keyframes];
}
function getValueTransition(transition, key) {
return transition && transition[key]
? {
...transition,
...transition[key],
}
: { ...transition };
}
const isNumber = (keyframe) => typeof keyframe === "number";
const isNumberKeyframesArray = (keyframes) => keyframes.every(isNumber);
export { createAnimationsFromSequence, getValueTransition };
//# sourceMappingURL=create.mjs.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,6 @@
function calculateRepeatDuration(duration, repeat, _repeatDelay) {
return duration * (repeat + 1);
}
export { calculateRepeatDuration };
//# sourceMappingURL=calc-repeat-duration.mjs.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"calc-repeat-duration.mjs","sources":["../../../../../src/animation/sequence/utils/calc-repeat-duration.ts"],"sourcesContent":["export function calculateRepeatDuration(\n duration: number,\n repeat: number,\n _repeatDelay: number\n): number {\n return duration * (repeat + 1)\n}\n"],"names":[],"mappings":"SAAgB,uBAAuB,CACnC,QAAgB,EAChB,MAAc,EACd,YAAoB,EAAA;AAEpB,IAAA,OAAO,QAAQ,IAAI,MAAM,GAAG,CAAC,CAAC;AAClC;;;;"}

View File

@@ -0,0 +1,24 @@
/**
* Given a absolute or relative time definition and current/prev time state of the sequence,
* calculate an absolute time for the next keyframes.
*/
function calcNextTime(current, next, prev, labels) {
if (typeof next === "number") {
return next;
}
else if (next.startsWith("-") || next.startsWith("+")) {
return Math.max(0, current + parseFloat(next));
}
else if (next === "<") {
return prev;
}
else if (next.startsWith("<")) {
return Math.max(0, prev + parseFloat(next.slice(1)));
}
else {
return labels.get(next) ?? current;
}
}
export { calcNextTime };
//# sourceMappingURL=calc-time.mjs.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"calc-time.mjs","sources":["../../../../../src/animation/sequence/utils/calc-time.ts"],"sourcesContent":["import { SequenceTime } from \"../types\"\n\n/**\n * Given a absolute or relative time definition and current/prev time state of the sequence,\n * calculate an absolute time for the next keyframes.\n */\nexport function calcNextTime(\n current: number,\n next: SequenceTime,\n prev: number,\n labels: Map<string, number>\n): number {\n if (typeof next === \"number\") {\n return next\n } else if (next.startsWith(\"-\") || next.startsWith(\"+\")) {\n return Math.max(0, current + parseFloat(next))\n } else if (next === \"<\") {\n return prev\n } else if (next.startsWith(\"<\")) {\n return Math.max(0, prev + parseFloat(next.slice(1)))\n } else {\n return labels.get(next) ?? current\n }\n}\n"],"names":[],"mappings":"AAEA;;;AAGG;AACG,SAAU,YAAY,CACxB,OAAe,EACf,IAAkB,EAClB,IAAY,EACZ,MAA2B,EAAA;AAE3B,IAAA,IAAI,OAAO,IAAI,KAAK,QAAQ,EAAE;AAC1B,QAAA,OAAO,IAAI;IACf;AAAO,SAAA,IAAI,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE;AACrD,QAAA,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,OAAO,GAAG,UAAU,CAAC,IAAI,CAAC,CAAC;IAClD;AAAO,SAAA,IAAI,IAAI,KAAK,GAAG,EAAE;AACrB,QAAA,OAAO,IAAI;IACf;AAAO,SAAA,IAAI,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE;AAC7B,QAAA,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,GAAG,UAAU,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;IACxD;SAAO;QACH,OAAO,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,OAAO;IACtC;AACJ;;;;"}

View File

@@ -0,0 +1,31 @@
import { mixNumber } from 'motion-dom';
import { getEasingForSegment, removeItem } from 'motion-utils';
function eraseKeyframes(sequence, startTime, endTime) {
for (let i = 0; i < sequence.length; i++) {
const keyframe = sequence[i];
if (keyframe.at > startTime && keyframe.at < endTime) {
removeItem(sequence, keyframe);
// If we remove this item we have to push the pointer back one
i--;
}
}
}
function addKeyframes(sequence, keyframes, easing, offset, startTime, endTime) {
/**
* Erase every existing value between currentTime and targetTime,
* this will essentially splice this timeline into any currently
* defined ones.
*/
eraseKeyframes(sequence, startTime, endTime);
for (let i = 0; i < keyframes.length; i++) {
sequence.push({
value: keyframes[i],
at: mixNumber(startTime, endTime, offset[i]),
easing: getEasingForSegment(easing, i),
});
}
}
export { addKeyframes, eraseKeyframes };
//# sourceMappingURL=edit.mjs.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"edit.mjs","sources":["../../../../../src/animation/sequence/utils/edit.ts"],"sourcesContent":["import { mixNumber, UnresolvedValueKeyframe } from \"motion-dom\"\nimport { Easing, getEasingForSegment, removeItem } from \"motion-utils\"\nimport type { ValueSequence } from \"../types\"\n\nexport function eraseKeyframes(\n sequence: ValueSequence,\n startTime: number,\n endTime: number\n): void {\n for (let i = 0; i < sequence.length; i++) {\n const keyframe = sequence[i]\n\n if (keyframe.at > startTime && keyframe.at < endTime) {\n removeItem(sequence, keyframe)\n\n // If we remove this item we have to push the pointer back one\n i--\n }\n }\n}\n\nexport function addKeyframes(\n sequence: ValueSequence,\n keyframes: UnresolvedValueKeyframe[],\n easing: Easing | Easing[],\n offset: number[],\n startTime: number,\n endTime: number\n): void {\n /**\n * Erase every existing value between currentTime and targetTime,\n * this will essentially splice this timeline into any currently\n * defined ones.\n */\n eraseKeyframes(sequence, startTime, endTime)\n\n for (let i = 0; i < keyframes.length; i++) {\n sequence.push({\n value: keyframes[i],\n at: mixNumber(startTime, endTime, offset[i]),\n easing: getEasingForSegment(easing, i),\n })\n }\n}\n"],"names":[],"mappings":";;;SAIgB,cAAc,CAC1B,QAAuB,EACvB,SAAiB,EACjB,OAAe,EAAA;AAEf,IAAA,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,QAAQ,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE;AACtC,QAAA,MAAM,QAAQ,GAAG,QAAQ,CAAC,CAAC,CAAC;AAE5B,QAAA,IAAI,QAAQ,CAAC,EAAE,GAAG,SAAS,IAAI,QAAQ,CAAC,EAAE,GAAG,OAAO,EAAE;AAClD,YAAA,UAAU,CAAC,QAAQ,EAAE,QAAQ,CAAC;;AAG9B,YAAA,CAAC,EAAE;QACP;IACJ;AACJ;AAEM,SAAU,YAAY,CACxB,QAAuB,EACvB,SAAoC,EACpC,MAAyB,EACzB,MAAgB,EAChB,SAAiB,EACjB,OAAe,EAAA;AAEf;;;;AAIG;AACH,IAAA,cAAc,CAAC,QAAQ,EAAE,SAAS,EAAE,OAAO,CAAC;AAE5C,IAAA,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,SAAS,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE;QACvC,QAAQ,CAAC,IAAI,CAAC;AACV,YAAA,KAAK,EAAE,SAAS,CAAC,CAAC,CAAC;YACnB,EAAE,EAAE,SAAS,CAAC,SAAS,EAAE,OAAO,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC;AAC5C,YAAA,MAAM,EAAE,mBAAmB,CAAC,MAAM,EAAE,CAAC,CAAC;AACzC,SAAA,CAAC;IACN;AACJ;;;;"}

View File

@@ -0,0 +1,14 @@
/**
* Take an array of times that represent repeated keyframes. For instance
* if we have original times of [0, 0.5, 1] then our repeated times will
* be [0, 0.5, 1, 1, 1.5, 2]. Loop over the times and scale them back
* down to a 0-1 scale.
*/
function normalizeTimes(times, repeat) {
for (let i = 0; i < times.length; i++) {
times[i] = times[i] / (repeat + 1);
}
}
export { normalizeTimes };
//# sourceMappingURL=normalize-times.mjs.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"normalize-times.mjs","sources":["../../../../../src/animation/sequence/utils/normalize-times.ts"],"sourcesContent":["/**\n * Take an array of times that represent repeated keyframes. For instance\n * if we have original times of [0, 0.5, 1] then our repeated times will\n * be [0, 0.5, 1, 1, 1.5, 2]. Loop over the times and scale them back\n * down to a 0-1 scale.\n */\nexport function normalizeTimes(times: number[], repeat: number): void {\n for (let i = 0; i < times.length; i++) {\n times[i] = times[i] / (repeat + 1)\n }\n}\n"],"names":[],"mappings":"AAAA;;;;;AAKG;AACG,SAAU,cAAc,CAAC,KAAe,EAAE,MAAc,EAAA;AAC1D,IAAA,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE;AACnC,QAAA,KAAK,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC,IAAI,MAAM,GAAG,CAAC,CAAC;IACtC;AACJ;;;;"}

View File

@@ -0,0 +1,15 @@
function compareByTime(a, b) {
if (a.at === b.at) {
if (a.value === null)
return 1;
if (b.value === null)
return -1;
return 0;
}
else {
return a.at - b.at;
}
}
export { compareByTime };
//# sourceMappingURL=sort.mjs.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"sort.mjs","sources":["../../../../../src/animation/sequence/utils/sort.ts"],"sourcesContent":["import { AbsoluteKeyframe } from \"../types\"\n\nexport function compareByTime(\n a: AbsoluteKeyframe,\n b: AbsoluteKeyframe\n): number {\n if (a.at === b.at) {\n if (a.value === null) return 1\n if (b.value === null) return -1\n return 0\n } else {\n return a.at - b.at\n }\n}\n"],"names":[],"mappings":"AAEM,SAAU,aAAa,CACzB,CAAmB,EACnB,CAAmB,EAAA;IAEnB,IAAI,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,EAAE,EAAE;AACf,QAAA,IAAI,CAAC,CAAC,KAAK,KAAK,IAAI;AAAE,YAAA,OAAO,CAAC;AAC9B,QAAA,IAAI,CAAC,CAAC,KAAK,KAAK,IAAI;YAAE,OAAO,EAAE;AAC/B,QAAA,OAAO,CAAC;IACZ;SAAO;AACH,QAAA,OAAO,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,EAAE;IACtB;AACJ;;;;"}

View File

@@ -0,0 +1,41 @@
import { isSVGElement, isSVGSVGElement, SVGVisualElement, HTMLVisualElement, visualElementStore, ObjectVisualElement } from 'motion-dom';
function createDOMVisualElement(element) {
const options = {
presenceContext: null,
props: {},
visualState: {
renderState: {
transform: {},
transformOrigin: {},
style: {},
vars: {},
attrs: {},
},
latestValues: {},
},
};
const node = isSVGElement(element) && !isSVGSVGElement(element)
? new SVGVisualElement(options)
: new HTMLVisualElement(options);
node.mount(element);
visualElementStore.set(element, node);
}
function createObjectVisualElement(subject) {
const options = {
presenceContext: null,
props: {},
visualState: {
renderState: {
output: {},
},
latestValues: {},
},
};
const node = new ObjectVisualElement(options);
node.mount(subject);
visualElementStore.set(subject, node);
}
export { createDOMVisualElement, createObjectVisualElement };
//# sourceMappingURL=create-visual-element.mjs.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"create-visual-element.mjs","sources":["../../../../src/animation/utils/create-visual-element.ts"],"sourcesContent":["import {\n HTMLVisualElement,\n isSVGElement,\n isSVGSVGElement,\n ObjectVisualElement,\n SVGVisualElement,\n visualElementStore,\n} from \"motion-dom\"\n\nexport function createDOMVisualElement(element: HTMLElement | SVGElement) {\n const options = {\n presenceContext: null,\n props: {},\n visualState: {\n renderState: {\n transform: {},\n transformOrigin: {},\n style: {},\n vars: {},\n attrs: {},\n },\n latestValues: {},\n },\n }\n const node =\n isSVGElement(element) && !isSVGSVGElement(element)\n ? new SVGVisualElement(options)\n : new HTMLVisualElement(options)\n\n node.mount(element as any)\n\n visualElementStore.set(element, node)\n}\n\nexport function createObjectVisualElement(subject: Object) {\n const options = {\n presenceContext: null,\n props: {},\n visualState: {\n renderState: {\n output: {},\n },\n latestValues: {},\n },\n }\n const node = new ObjectVisualElement(options)\n\n node.mount(subject)\n\n visualElementStore.set(subject, node)\n}\n"],"names":[],"mappings":";;AASM,SAAU,sBAAsB,CAAC,OAAiC,EAAA;AACpE,IAAA,MAAM,OAAO,GAAG;AACZ,QAAA,eAAe,EAAE,IAAI;AACrB,QAAA,KAAK,EAAE,EAAE;AACT,QAAA,WAAW,EAAE;AACT,YAAA,WAAW,EAAE;AACT,gBAAA,SAAS,EAAE,EAAE;AACb,gBAAA,eAAe,EAAE,EAAE;AACnB,gBAAA,KAAK,EAAE,EAAE;AACT,gBAAA,IAAI,EAAE,EAAE;AACR,gBAAA,KAAK,EAAE,EAAE;AACZ,aAAA;AACD,YAAA,YAAY,EAAE,EAAE;AACnB,SAAA;KACJ;IACD,MAAM,IAAI,GACN,YAAY,CAAC,OAAO,CAAC,IAAI,CAAC,eAAe,CAAC,OAAO;AAC7C,UAAE,IAAI,gBAAgB,CAAC,OAAO;AAC9B,UAAE,IAAI,iBAAiB,CAAC,OAAO,CAAC;AAExC,IAAA,IAAI,CAAC,KAAK,CAAC,OAAc,CAAC;AAE1B,IAAA,kBAAkB,CAAC,GAAG,CAAC,OAAO,EAAE,IAAI,CAAC;AACzC;AAEM,SAAU,yBAAyB,CAAC,OAAe,EAAA;AACrD,IAAA,MAAM,OAAO,GAAG;AACZ,QAAA,eAAe,EAAE,IAAI;AACrB,QAAA,KAAK,EAAE,EAAE;AACT,QAAA,WAAW,EAAE;AACT,YAAA,WAAW,EAAE;AACT,gBAAA,MAAM,EAAE,EAAE;AACb,aAAA;AACD,YAAA,YAAY,EAAE,EAAE;AACnB,SAAA;KACJ;AACD,IAAA,MAAM,IAAI,GAAG,IAAI,mBAAmB,CAAC,OAAO,CAAC;AAE7C,IAAA,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC;AAEnB,IAAA,kBAAkB,CAAC,GAAG,CAAC,OAAO,EAAE,IAAI,CAAC;AACzC;;;;"}

View File

@@ -0,0 +1,6 @@
function isDOMKeyframes(keyframes) {
return typeof keyframes === "object" && !Array.isArray(keyframes);
}
export { isDOMKeyframes };
//# sourceMappingURL=is-dom-keyframes.mjs.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"is-dom-keyframes.mjs","sources":["../../../../src/animation/utils/is-dom-keyframes.ts"],"sourcesContent":["import { DOMKeyframesDefinition } from \"motion-dom\"\n\nexport function isDOMKeyframes(\n keyframes: unknown\n): keyframes is DOMKeyframesDefinition {\n return typeof keyframes === \"object\" && !Array.isArray(keyframes)\n}\n"],"names":[],"mappings":"AAEM,SAAU,cAAc,CAC1B,SAAkB,EAAA;AAElB,IAAA,OAAO,OAAO,SAAS,KAAK,QAAQ,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,SAAS,CAAC;AACrE;;;;"}