@any-tdf/react-motion 0.0.0-alpha.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 any-tdf
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,83 @@
1
+ # @any-tdf/react-motion
2
+
3
+ `@any-tdf/react-motion` 是一个面向 React 的动画工具包,参考 Svelte 5.56.3 的 `svelte/easing`、`svelte/transition`、`svelte/animate` 和 `svelte/motion` API。它不是 RTDF 专用包,可以在普通 React 项目中独立使用。
4
+
5
+ English: `@any-tdf/react-motion` is an independent React animation package inspired by Svelte 5 animation APIs. It keeps familiar function names, parameter names, and runtime behavior while exposing React components and hooks.
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ bun add @any-tdf/react-motion
11
+ ```
12
+
13
+ ## Usage
14
+
15
+ ### Easing
16
+
17
+ ```tsx
18
+ import { cubicOut } from '@any-tdf/react-motion/easing';
19
+
20
+ const easing = cubicOut;
21
+ ```
22
+
23
+ ### Transition
24
+
25
+ ```tsx
26
+ import { Transition } from '@any-tdf/react-motion/react';
27
+
28
+ export const Panel = ({ open }: { open: boolean }) => (
29
+ <Transition visible={open} transition="fly" params={{ y: 24, duration: 300 }}>
30
+ <div>Content</div>
31
+ </Transition>
32
+ );
33
+ ```
34
+
35
+ ### Motion
36
+
37
+ ```tsx
38
+ import { useTween } from '@any-tdf/react-motion/motion';
39
+
40
+ export const Progress = ({ value }: { value: number }) => {
41
+ const { current } = useTween(value, { duration: 400 });
42
+
43
+ return <progress value={current} max={100} />;
44
+ };
45
+ ```
46
+
47
+ ## API
48
+
49
+ | Module | Import | Includes |
50
+ | --- | --- | --- |
51
+ | Easing | `@any-tdf/react-motion/easing` | 31 个 Svelte easing 函数 |
52
+ | Transition | `@any-tdf/react-motion/transition` | `blur`、`crossfade`、`draw`、`fade`、`fly`、`scale`、`slide` |
53
+ | Animate | `@any-tdf/react-motion/animate` | `flip`、`FlipGroup`、`useFlipList` |
54
+ | Motion | `@any-tdf/react-motion/motion` | `Spring`、`Tween`、`spring`、`tweened`、`useSpring`、`useTween`、`prefersReducedMotion` |
55
+ | React | `@any-tdf/react-motion/react` | `Transition`、`useTransition` |
56
+
57
+ 更完整的对齐说明见 [API_COMPATIBILITY.md](./API_COMPATIBILITY.md)。
58
+
59
+ ## Documentation Site
60
+
61
+ 文档站在 `site` 目录内,支持中文和英文,并包含 Svelte Playground 风格的 Easing Visualiser demo。
62
+
63
+ ```bash
64
+ bun install
65
+ bun run docs:dev
66
+ ```
67
+
68
+ 默认开发地址:
69
+
70
+ - 中文: `http://localhost:8898/zh`
71
+ - English: `http://localhost:8898/en`
72
+ - Easing demo: `http://localhost:8898/zh/demo/easing`
73
+
74
+ ## Development
75
+
76
+ ```bash
77
+ bun test
78
+ bun run build
79
+ bunx tsc -p site/tsconfig.json
80
+ bun run docs:build
81
+ ```
82
+
83
+ `package.json` 的 `files` 只发布 `dist`,文档站不会进入 npm 包。
@@ -0,0 +1,40 @@
1
+ import React from 'react';
2
+ import { type EasingFunction } from './easing';
3
+ import { type AnimationConfig } from './internal';
4
+ export type { AnimationConfig };
5
+ export interface FlipParams {
6
+ delay?: number;
7
+ duration?: number | ((len: number) => number);
8
+ easing?: EasingFunction;
9
+ }
10
+ export type AnimateFunction<P = unknown> = (node: Element, states: {
11
+ from: DOMRect;
12
+ to: DOMRect;
13
+ }, params?: P) => AnimationConfig;
14
+ export declare const flip: (node: Element, { from, to }: {
15
+ from: DOMRect;
16
+ to: DOMRect;
17
+ }, params?: FlipParams) => AnimationConfig;
18
+ export interface UseFlipListOptions<P = FlipParams> {
19
+ animate?: AnimateFunction<P>;
20
+ params?: P;
21
+ disabled?: boolean;
22
+ }
23
+ export declare const useFlipList: <K extends React.Key, P = FlipParams>(keys: readonly K[], options?: UseFlipListOptions<P>) => {
24
+ getRef: (key: K) => (node: HTMLElement | null) => void;
25
+ };
26
+ export interface FlipGroupProps<T, P = FlipParams> {
27
+ items: readonly T[];
28
+ getKey: (item: T, index: number) => React.Key;
29
+ children: (item: T, index: number) => React.ReactNode;
30
+ as?: keyof React.JSX.IntrinsicElements;
31
+ itemAs?: keyof React.JSX.IntrinsicElements;
32
+ className?: string;
33
+ itemClassName?: string | ((item: T, index: number) => string);
34
+ params?: P;
35
+ animate?: AnimateFunction<P>;
36
+ disabled?: boolean;
37
+ }
38
+ export declare const FlipGroup: <T, P = FlipParams>({ items, getKey, children, as, itemAs, className, itemClassName, params, animate, disabled }: FlipGroupProps<T, P>) => React.ReactElement<{
39
+ className: string | undefined;
40
+ }, string | React.JSXElementConstructor<any>>;
@@ -0,0 +1,105 @@
1
+ import React, { useCallback, useLayoutEffect, useRef } from 'react';
2
+ import { cubicOut } from './easing';
3
+ import { runAnimationConfig } from './internal';
4
+ const getZoom = (element) => {
5
+ const htmlElement = element;
6
+ if (typeof htmlElement.currentCSSZoom === 'number') {
7
+ return Number(htmlElement.currentCSSZoom) || 1;
8
+ }
9
+ let current = htmlElement;
10
+ let zoom = 1;
11
+ while (current !== null) {
12
+ zoom *= Number(getComputedStyle(current).zoom) || 1;
13
+ current = current.parentElement;
14
+ }
15
+ return zoom;
16
+ };
17
+ export const flip = (node, { from, to }, params = {}) => {
18
+ const { delay = 0, duration = (distance) => Math.sqrt(distance) * 120, easing = cubicOut } = params;
19
+ const htmlNode = node;
20
+ const style = getComputedStyle(node);
21
+ const transform = style.transform === 'none' ? '' : style.transform;
22
+ const [originX = 0, originY = 0] = style.transformOrigin.split(' ').map(Number.parseFloat);
23
+ const clientWidth = htmlNode.clientWidth || to.width || 1;
24
+ const clientHeight = htmlNode.clientHeight || to.height || 1;
25
+ const ox = originX / clientWidth;
26
+ const oy = originY / clientHeight;
27
+ const zoom = getZoom(node);
28
+ const sx = clientWidth / (to.width || clientWidth) / zoom;
29
+ const sy = clientHeight / (to.height || clientHeight) / zoom;
30
+ const fx = from.left + from.width * ox;
31
+ const fy = from.top + from.height * oy;
32
+ const tx = to.left + to.width * ox;
33
+ const ty = to.top + to.height * oy;
34
+ const dx = (fx - tx) * sx;
35
+ const dy = (fy - ty) * sy;
36
+ const dsx = from.width / (to.width || from.width || 1);
37
+ const dsy = from.height / (to.height || from.height || 1);
38
+ const distance = Math.sqrt(dx * dx + dy * dy);
39
+ return {
40
+ delay,
41
+ duration: typeof duration === 'function' ? duration(distance) : duration,
42
+ easing,
43
+ css: (t, u) => {
44
+ const x = u * dx;
45
+ const y = u * dy;
46
+ const scaleX = t + u * dsx;
47
+ const scaleY = t + u * dsy;
48
+ return `transform: ${transform} translate(${x}px, ${y}px) scale(${scaleX}, ${scaleY});`;
49
+ }
50
+ };
51
+ };
52
+ export const useFlipList = (keys, options = {}) => {
53
+ const nodeMapRef = useRef(new Map());
54
+ const rectMapRef = useRef(new Map());
55
+ const animationMapRef = useRef(new Map());
56
+ const animateFn = options.animate ?? flip;
57
+ const getRef = useCallback((key) => (node) => {
58
+ if (node) {
59
+ nodeMapRef.current.set(key, node);
60
+ }
61
+ else {
62
+ nodeMapRef.current.delete(key);
63
+ }
64
+ }, []);
65
+ useLayoutEffect(() => {
66
+ const nextRects = new Map();
67
+ for (const key of keys) {
68
+ const node = nodeMapRef.current.get(key);
69
+ if (!node)
70
+ continue;
71
+ const nextRect = node.getBoundingClientRect();
72
+ const previousRect = rectMapRef.current.get(key);
73
+ nextRects.set(key, nextRect);
74
+ if (options.disabled || !previousRect)
75
+ continue;
76
+ if (previousRect.left === nextRect.left && previousRect.top === nextRect.top && previousRect.right === nextRect.right && previousRect.bottom === nextRect.bottom)
77
+ continue;
78
+ animationMapRef.current.get(key)?.cancel();
79
+ const config = animateFn(node, { from: previousRect, to: nextRect }, options.params);
80
+ animationMapRef.current.set(key, runAnimationConfig(node, config, 1, { fromT: 0, direction: 'both' }));
81
+ }
82
+ rectMapRef.current = nextRects;
83
+ return () => {
84
+ for (const controller of animationMapRef.current.values()) {
85
+ controller.cancel();
86
+ }
87
+ animationMapRef.current.clear();
88
+ };
89
+ }, [animateFn, keys, options.disabled, options.params]);
90
+ return { getRef };
91
+ };
92
+ export const FlipGroup = ({ items, getKey, children, as = 'div', itemAs = 'div', className, itemClassName, params, animate, disabled }) => {
93
+ const keys = items.map(getKey);
94
+ const { getRef } = useFlipList(keys, { animate, params, disabled });
95
+ const renderedItems = items.map((item, index) => {
96
+ const key = getKey(item, index);
97
+ const resolvedClassName = typeof itemClassName === 'function' ? itemClassName(item, index) : itemClassName;
98
+ return React.createElement(itemAs, {
99
+ key,
100
+ ref: getRef(key),
101
+ className: resolvedClassName
102
+ }, children(item, index));
103
+ });
104
+ return React.createElement(as, { className }, renderedItems);
105
+ };
@@ -0,0 +1,66 @@
1
+ export type EasingFunction = (t: number) => number;
2
+ export declare const linear: EasingFunction;
3
+ export declare const backInOut: EasingFunction;
4
+ export declare const backIn: EasingFunction;
5
+ export declare const backOut: EasingFunction;
6
+ export declare const bounceOut: EasingFunction;
7
+ export declare const bounceInOut: EasingFunction;
8
+ export declare const bounceIn: EasingFunction;
9
+ export declare const circInOut: EasingFunction;
10
+ export declare const circIn: EasingFunction;
11
+ export declare const circOut: EasingFunction;
12
+ export declare const cubicInOut: EasingFunction;
13
+ export declare const cubicIn: EasingFunction;
14
+ export declare const cubicOut: EasingFunction;
15
+ export declare const elasticInOut: EasingFunction;
16
+ export declare const elasticIn: EasingFunction;
17
+ export declare const elasticOut: EasingFunction;
18
+ export declare const expoInOut: EasingFunction;
19
+ export declare const expoIn: EasingFunction;
20
+ export declare const expoOut: EasingFunction;
21
+ export declare const quadInOut: EasingFunction;
22
+ export declare const quadIn: EasingFunction;
23
+ export declare const quadOut: EasingFunction;
24
+ export declare const quartInOut: EasingFunction;
25
+ export declare const quartIn: EasingFunction;
26
+ export declare const quartOut: EasingFunction;
27
+ export declare const quintInOut: EasingFunction;
28
+ export declare const quintIn: EasingFunction;
29
+ export declare const quintOut: EasingFunction;
30
+ export declare const sineInOut: EasingFunction;
31
+ export declare const sineIn: EasingFunction;
32
+ export declare const sineOut: EasingFunction;
33
+ export declare const easingFunctions: {
34
+ readonly backIn: EasingFunction;
35
+ readonly backInOut: EasingFunction;
36
+ readonly backOut: EasingFunction;
37
+ readonly bounceIn: EasingFunction;
38
+ readonly bounceInOut: EasingFunction;
39
+ readonly bounceOut: EasingFunction;
40
+ readonly circIn: EasingFunction;
41
+ readonly circInOut: EasingFunction;
42
+ readonly circOut: EasingFunction;
43
+ readonly cubicIn: EasingFunction;
44
+ readonly cubicInOut: EasingFunction;
45
+ readonly cubicOut: EasingFunction;
46
+ readonly elasticIn: EasingFunction;
47
+ readonly elasticInOut: EasingFunction;
48
+ readonly elasticOut: EasingFunction;
49
+ readonly expoIn: EasingFunction;
50
+ readonly expoInOut: EasingFunction;
51
+ readonly expoOut: EasingFunction;
52
+ readonly linear: EasingFunction;
53
+ readonly quadIn: EasingFunction;
54
+ readonly quadInOut: EasingFunction;
55
+ readonly quadOut: EasingFunction;
56
+ readonly quartIn: EasingFunction;
57
+ readonly quartInOut: EasingFunction;
58
+ readonly quartOut: EasingFunction;
59
+ readonly quintIn: EasingFunction;
60
+ readonly quintInOut: EasingFunction;
61
+ readonly quintOut: EasingFunction;
62
+ readonly sineIn: EasingFunction;
63
+ readonly sineInOut: EasingFunction;
64
+ readonly sineOut: EasingFunction;
65
+ };
66
+ export type EasingProps = keyof typeof easingFunctions;
package/dist/easing.js ADDED
@@ -0,0 +1,106 @@
1
+ export const linear = (t) => t;
2
+ export const backInOut = (t) => {
3
+ const s = 1.70158 * 1.525;
4
+ if ((t *= 2) < 1)
5
+ return 0.5 * (t * t * ((s + 1) * t - s));
6
+ return 0.5 * ((t -= 2) * t * ((s + 1) * t + s) + 2);
7
+ };
8
+ export const backIn = (t) => {
9
+ const s = 1.70158;
10
+ return t * t * ((s + 1) * t - s);
11
+ };
12
+ export const backOut = (t) => {
13
+ const s = 1.70158;
14
+ return --t * t * ((s + 1) * t + s) + 1;
15
+ };
16
+ export const bounceOut = (t) => {
17
+ const a = 4.0 / 11.0;
18
+ const b = 8.0 / 11.0;
19
+ const c = 9.0 / 10.0;
20
+ const ca = 4356.0 / 361.0;
21
+ const cb = 35442.0 / 1805.0;
22
+ const cc = 16061.0 / 1805.0;
23
+ const t2 = t * t;
24
+ return t < a ? 7.5625 * t2 : t < b ? 9.075 * t2 - 9.9 * t + 3.4 : t < c ? ca * t2 - cb * t + cc : 10.8 * t * t - 20.52 * t + 10.72;
25
+ };
26
+ export const bounceInOut = (t) => (t < 0.5 ? 0.5 * (1.0 - bounceOut(1.0 - t * 2.0)) : 0.5 * bounceOut(t * 2.0 - 1.0) + 0.5);
27
+ export const bounceIn = (t) => 1.0 - bounceOut(1.0 - t);
28
+ export const circInOut = (t) => {
29
+ if ((t *= 2) < 1)
30
+ return -0.5 * (Math.sqrt(1 - t * t) - 1);
31
+ return 0.5 * (Math.sqrt(1 - (t -= 2) * t) + 1);
32
+ };
33
+ export const circIn = (t) => 1.0 - Math.sqrt(1.0 - t * t);
34
+ export const circOut = (t) => Math.sqrt(1 - --t * t);
35
+ export const cubicInOut = (t) => (t < 0.5 ? 4.0 * t * t * t : 0.5 * (2.0 * t - 2.0) ** 3 + 1.0);
36
+ export const cubicIn = (t) => t * t * t;
37
+ export const cubicOut = (t) => {
38
+ const f = t - 1.0;
39
+ return f * f * f + 1.0;
40
+ };
41
+ export const elasticInOut = (t) => t < 0.5
42
+ ? 0.5 * Math.sin(((+13.0 * Math.PI) / 2) * 2.0 * t) * 2 ** (10.0 * (2.0 * t - 1.0))
43
+ : 0.5 * Math.sin(((-13.0 * Math.PI) / 2) * (2.0 * t)) * 2 ** (-10.0 * (2.0 * t - 1.0)) + 1.0;
44
+ export const elasticIn = (t) => Math.sin((13.0 * t * Math.PI) / 2) * 2 ** (10.0 * (t - 1.0));
45
+ export const elasticOut = (t) => Math.sin((-13.0 * (t + 1.0) * Math.PI) / 2) * 2 ** (-10.0 * t) + 1.0;
46
+ export const expoInOut = (t) => (t === 0.0 || t === 1.0 ? t : t < 0.5 ? +0.5 * 2 ** (20.0 * t - 10.0) : -0.5 * 2 ** (10.0 - t * 20.0) + 1.0);
47
+ export const expoIn = (t) => (t === 0.0 ? t : 2 ** (10.0 * (t - 1.0)));
48
+ export const expoOut = (t) => (t === 1.0 ? t : 1.0 - 2 ** (-10.0 * t));
49
+ export const quadInOut = (t) => {
50
+ t /= 0.5;
51
+ if (t < 1)
52
+ return 0.5 * t * t;
53
+ t--;
54
+ return -0.5 * (t * (t - 2) - 1);
55
+ };
56
+ export const quadIn = (t) => t * t;
57
+ export const quadOut = (t) => -t * (t - 2.0);
58
+ export const quartInOut = (t) => (t < 0.5 ? +8.0 * t ** 4.0 : -8.0 * (t - 1.0) ** 4.0 + 1.0);
59
+ export const quartIn = (t) => t ** 4.0;
60
+ export const quartOut = (t) => (t - 1.0) ** 3.0 * (1.0 - t) + 1.0;
61
+ export const quintInOut = (t) => {
62
+ if ((t *= 2) < 1)
63
+ return 0.5 * t * t * t * t * t;
64
+ return 0.5 * ((t -= 2) * t * t * t * t + 2);
65
+ };
66
+ export const quintIn = (t) => t * t * t * t * t;
67
+ export const quintOut = (t) => --t * t * t * t * t + 1;
68
+ export const sineInOut = (t) => -0.5 * (Math.cos(Math.PI * t) - 1);
69
+ export const sineIn = (t) => {
70
+ const v = Math.cos(t * Math.PI * 0.5);
71
+ return Math.abs(v) < 1e-14 ? 1 : 1 - v;
72
+ };
73
+ export const sineOut = (t) => Math.sin((t * Math.PI) / 2);
74
+ export const easingFunctions = {
75
+ backIn,
76
+ backInOut,
77
+ backOut,
78
+ bounceIn,
79
+ bounceInOut,
80
+ bounceOut,
81
+ circIn,
82
+ circInOut,
83
+ circOut,
84
+ cubicIn,
85
+ cubicInOut,
86
+ cubicOut,
87
+ elasticIn,
88
+ elasticInOut,
89
+ elasticOut,
90
+ expoIn,
91
+ expoInOut,
92
+ expoOut,
93
+ linear,
94
+ quadIn,
95
+ quadInOut,
96
+ quadOut,
97
+ quartIn,
98
+ quartInOut,
99
+ quartOut,
100
+ quintIn,
101
+ quintInOut,
102
+ quintOut,
103
+ sineIn,
104
+ sineInOut,
105
+ sineOut
106
+ };
@@ -0,0 +1,5 @@
1
+ export * from './easing';
2
+ export * from './transition';
3
+ export * from './animate';
4
+ export * from './motion';
5
+ export * from './react';
package/dist/index.js ADDED
@@ -0,0 +1,5 @@
1
+ export * from './easing';
2
+ export * from './transition';
3
+ export * from './animate';
4
+ export * from './motion';
5
+ export * from './react';
@@ -0,0 +1,26 @@
1
+ import { type EasingFunction } from './easing';
2
+ export interface AnimationConfig {
3
+ delay?: number;
4
+ duration?: number;
5
+ easing?: EasingFunction;
6
+ css?: (t: number, u: number) => string;
7
+ tick?: (t: number, u: number) => void;
8
+ }
9
+ export type DeferredAnimationConfig = (options?: {
10
+ direction: 'in' | 'out' | 'both';
11
+ }) => AnimationConfig;
12
+ export type MaybeDeferredAnimationConfig = AnimationConfig | DeferredAnimationConfig;
13
+ export type AnimationController = {
14
+ cancel: () => void;
15
+ deactivate: () => void;
16
+ reset: () => void;
17
+ t: () => number;
18
+ finished: Promise<void>;
19
+ };
20
+ export declare const runAnimationConfig: (element: Element, config: MaybeDeferredAnimationConfig, targetT: 0 | 1, options?: {
21
+ fromT?: number;
22
+ onStart?: () => void;
23
+ onEnd?: () => void;
24
+ direction?: "in" | "out" | "both";
25
+ }) => AnimationController;
26
+ export declare const splitCssUnit: (value: number | string) => [number, string];
@@ -0,0 +1,226 @@
1
+ import { linear } from './easing';
2
+ const propertyToCamelCase = (property) => {
3
+ if (property === 'float')
4
+ return 'cssFloat';
5
+ if (property === 'offset')
6
+ return 'cssOffset';
7
+ if (property.startsWith('--'))
8
+ return property;
9
+ const parts = property.split('-');
10
+ if (parts.length === 1)
11
+ return parts[0];
12
+ return `${parts[0]}${parts
13
+ .slice(1)
14
+ .map((part) => `${part[0]?.toUpperCase() || ''}${part.slice(1)}`)
15
+ .join('')}`;
16
+ };
17
+ const parseCssDeclarations = (css) => {
18
+ const entries = [];
19
+ for (const part of css.split(';')) {
20
+ const index = part.indexOf(':');
21
+ if (index < 0)
22
+ continue;
23
+ const property = part.slice(0, index).trim();
24
+ const value = part.slice(index + 1).trim();
25
+ if (!property || !value)
26
+ continue;
27
+ entries.push([property, value]);
28
+ }
29
+ return entries;
30
+ };
31
+ const cssToKeyframe = (css) => {
32
+ const keyframe = {};
33
+ for (const [property, value] of parseCssDeclarations(css)) {
34
+ keyframe[propertyToCamelCase(property)] = value;
35
+ }
36
+ return keyframe;
37
+ };
38
+ const applyCssText = (element, css) => {
39
+ const style = element.style;
40
+ for (const [property, value] of parseCssDeclarations(css)) {
41
+ style.setProperty(property, value);
42
+ }
43
+ };
44
+ const resolveConfig = (config, direction) => {
45
+ if (typeof config === 'function') {
46
+ return config({ direction });
47
+ }
48
+ return config;
49
+ };
50
+ const now = () => (typeof performance === 'undefined' ? Date.now() : performance.now());
51
+ const scheduleFrame = (callback) => {
52
+ if (typeof requestAnimationFrame === 'undefined') {
53
+ const id = setTimeout(() => callback(now()), 16);
54
+ return id;
55
+ }
56
+ return requestAnimationFrame(callback);
57
+ };
58
+ const cancelScheduledFrame = (id) => {
59
+ if (typeof cancelAnimationFrame === 'undefined') {
60
+ clearTimeout(id);
61
+ return;
62
+ }
63
+ cancelAnimationFrame(id);
64
+ };
65
+ export const runAnimationConfig = (element, config, targetT, options = {}) => {
66
+ const isIntro = targetT === 1;
67
+ const direction = options.direction ?? (isIntro ? 'in' : 'out');
68
+ let resolved = false;
69
+ let deactivated = false;
70
+ let aborted = false;
71
+ let animation = null;
72
+ let rafId = null;
73
+ let currentT = options.fromT ?? 1 - targetT;
74
+ let finishPromiseResolve = () => { };
75
+ const finished = new Promise((resolve) => {
76
+ finishPromiseResolve = resolve;
77
+ });
78
+ const finish = () => {
79
+ if (deactivated) {
80
+ finishPromiseResolve();
81
+ return;
82
+ }
83
+ options.onEnd?.();
84
+ finishPromiseResolve();
85
+ };
86
+ queueMicrotask(() => {
87
+ if (aborted)
88
+ return;
89
+ const resolvedConfig = resolveConfig(config, direction);
90
+ const delay = resolvedConfig.delay ?? 0;
91
+ const duration = resolvedConfig.duration ?? 0;
92
+ const easing = resolvedConfig.easing ?? linear;
93
+ const fromT = currentT;
94
+ const delta = targetT - fromT;
95
+ const activeDuration = Math.abs(delta) * duration;
96
+ resolved = true;
97
+ if (!activeDuration && !delay) {
98
+ options.onStart?.();
99
+ currentT = targetT;
100
+ resolvedConfig.tick?.(targetT, 1 - targetT);
101
+ if (resolvedConfig.css)
102
+ applyCssText(element, resolvedConfig.css(targetT, 1 - targetT));
103
+ finish();
104
+ return;
105
+ }
106
+ const runMain = () => {
107
+ if (aborted)
108
+ return;
109
+ options.onStart?.();
110
+ if (!activeDuration) {
111
+ currentT = targetT;
112
+ resolvedConfig.tick?.(targetT, 1 - targetT);
113
+ if (resolvedConfig.css)
114
+ applyCssText(element, resolvedConfig.css(targetT, 1 - targetT));
115
+ finish();
116
+ return;
117
+ }
118
+ if (resolvedConfig.tick) {
119
+ const start = now();
120
+ const step = (time) => {
121
+ if (aborted)
122
+ return;
123
+ const ratio = Math.min((time - start) / activeDuration, 1);
124
+ currentT = fromT + delta * easing(ratio);
125
+ resolvedConfig.tick?.(currentT, 1 - currentT);
126
+ if (resolvedConfig.css)
127
+ applyCssText(element, resolvedConfig.css(currentT, 1 - currentT));
128
+ if (ratio < 1) {
129
+ rafId = scheduleFrame(step);
130
+ }
131
+ else {
132
+ currentT = targetT;
133
+ resolvedConfig.tick?.(targetT, 1 - targetT);
134
+ if (resolvedConfig.css)
135
+ applyCssText(element, resolvedConfig.css(targetT, 1 - targetT));
136
+ finish();
137
+ }
138
+ };
139
+ rafId = scheduleFrame(step);
140
+ return;
141
+ }
142
+ if (resolvedConfig.css && typeof element.animate === 'function') {
143
+ const frameCount = Math.max(1, Math.ceil(activeDuration / (1000 / 60)));
144
+ const keyframes = [];
145
+ for (let index = 0; index <= frameCount; index += 1) {
146
+ const ratio = index / frameCount;
147
+ const t = fromT + delta * easing(ratio);
148
+ keyframes.push(cssToKeyframe(resolvedConfig.css(t, 1 - t)));
149
+ }
150
+ animation = element.animate(keyframes, { duration: activeDuration, fill: 'forwards' });
151
+ animation.onfinish = () => {
152
+ currentT = targetT;
153
+ finish();
154
+ };
155
+ return;
156
+ }
157
+ const start = now();
158
+ const step = (time) => {
159
+ if (aborted)
160
+ return;
161
+ const ratio = Math.min((time - start) / activeDuration, 1);
162
+ currentT = fromT + delta * easing(ratio);
163
+ if (ratio < 1) {
164
+ rafId = scheduleFrame(step);
165
+ }
166
+ else {
167
+ currentT = targetT;
168
+ finish();
169
+ }
170
+ };
171
+ rafId = scheduleFrame(step);
172
+ };
173
+ if (delay > 0 && typeof element.animate === 'function') {
174
+ animation = element.animate([], { duration: delay, fill: 'forwards' });
175
+ animation.onfinish = () => {
176
+ animation?.cancel();
177
+ animation = null;
178
+ runMain();
179
+ };
180
+ }
181
+ else if (delay > 0) {
182
+ const start = now();
183
+ const wait = (time) => {
184
+ if (aborted)
185
+ return;
186
+ if (time - start >= delay) {
187
+ rafId = null;
188
+ runMain();
189
+ return;
190
+ }
191
+ rafId = scheduleFrame(wait);
192
+ };
193
+ rafId = scheduleFrame(wait);
194
+ }
195
+ else {
196
+ runMain();
197
+ }
198
+ });
199
+ return {
200
+ cancel: () => {
201
+ aborted = true;
202
+ if (animation) {
203
+ animation.onfinish = null;
204
+ animation.cancel();
205
+ animation = null;
206
+ }
207
+ if (rafId !== null) {
208
+ cancelScheduledFrame(rafId);
209
+ rafId = null;
210
+ }
211
+ finishPromiseResolve();
212
+ },
213
+ deactivate: () => {
214
+ deactivated = true;
215
+ },
216
+ reset: () => {
217
+ currentT = targetT === 0 ? 1 : 0;
218
+ },
219
+ t: () => (resolved ? currentT : options.fromT ?? 1 - targetT),
220
+ finished
221
+ };
222
+ };
223
+ export const splitCssUnit = (value) => {
224
+ const split = typeof value === 'string' && value.match(/^\s*(-?[\d.]+)([^\s]*)\s*$/);
225
+ return split ? [Number.parseFloat(split[1]), split[2] || 'px'] : [value, 'px'];
226
+ };