@xanui/core 1.2.71 → 1.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Iframe/index.cjs +1 -2
- package/Iframe/index.cjs.map +1 -1
- package/Iframe/index.d.ts +2 -1
- package/Iframe/index.js +1 -2
- package/Iframe/index.js.map +1 -1
- package/Transition/index.cjs +40 -97
- package/Transition/index.cjs.map +1 -1
- package/Transition/index.d.ts +13 -18
- package/Transition/index.js +41 -98
- package/Transition/index.js.map +1 -1
- package/Transition/variants.cjs +92 -171
- package/Transition/variants.cjs.map +1 -1
- package/Transition/variants.d.ts +65 -83
- package/Transition/variants.js +93 -170
- package/Transition/variants.js.map +1 -1
- package/animate/easing.cjs +59 -0
- package/animate/easing.cjs.map +1 -0
- package/animate/easing.d.ts +13 -0
- package/animate/easing.js +57 -0
- package/animate/easing.js.map +1 -0
- package/animate/index.cjs +104 -0
- package/animate/index.cjs.map +1 -0
- package/animate/index.d.ts +19 -0
- package/animate/index.js +99 -0
- package/animate/index.js.map +1 -0
- package/hooks/useTransition.cjs +98 -0
- package/hooks/useTransition.cjs.map +1 -0
- package/hooks/useTransition.d.ts +23 -0
- package/hooks/useTransition.js +96 -0
- package/hooks/useTransition.js.map +1 -0
- package/hooks/useTransitionGroup.cjs +95 -0
- package/hooks/useTransitionGroup.cjs.map +1 -0
- package/hooks/useTransitionGroup.d.ts +32 -0
- package/hooks/useTransitionGroup.js +93 -0
- package/hooks/useTransitionGroup.js.map +1 -0
- package/index.cjs +16 -11
- package/index.cjs.map +1 -1
- package/index.d.ts +5 -2
- package/index.js +4 -1
- package/index.js.map +1 -1
- package/package.json +1 -1
- package/hooks/useAnimation.cjs +0 -44
- package/hooks/useAnimation.cjs.map +0 -1
- package/hooks/useAnimation.d.ts +0 -20
- package/hooks/useAnimation.js +0 -39
- package/hooks/useAnimation.js.map +0 -1
package/animate/index.js
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import Easing from './easing.js';
|
|
3
|
+
|
|
4
|
+
const animate = ({ from, to, duration = 400, delay = 0, easing = Easing.default, onUpdate, onDone, breakpoints, repeat = 0, repeatBack = false, }) => {
|
|
5
|
+
let rafId;
|
|
6
|
+
let cycle = 0;
|
|
7
|
+
let forward = true;
|
|
8
|
+
// Track triggered breakpoints
|
|
9
|
+
const triggered = {};
|
|
10
|
+
const resolve = (val) => typeof val === "function" ? val() : val;
|
|
11
|
+
const getEased = (key, t) => {
|
|
12
|
+
if (typeof easing === "function")
|
|
13
|
+
return easing(t);
|
|
14
|
+
if (easing[key])
|
|
15
|
+
return easing[key](t);
|
|
16
|
+
return t;
|
|
17
|
+
};
|
|
18
|
+
const startAnimation = () => {
|
|
19
|
+
const fromVal = resolve(from);
|
|
20
|
+
const toVal = resolve(to);
|
|
21
|
+
const keys = Object.keys(fromVal);
|
|
22
|
+
if (breakpoints) {
|
|
23
|
+
for (const key of keys)
|
|
24
|
+
triggered[key] = new Set();
|
|
25
|
+
}
|
|
26
|
+
const start = performance.now();
|
|
27
|
+
const frame = (now) => {
|
|
28
|
+
const progress = duration === 0 ? 1 : Math.min((now - start) / duration, 1);
|
|
29
|
+
const current = {};
|
|
30
|
+
for (const key of keys) {
|
|
31
|
+
const f = forward ? fromVal[key] : toVal[key];
|
|
32
|
+
const t = forward ? toVal[key] : fromVal[key];
|
|
33
|
+
const eased = getEased(key, progress);
|
|
34
|
+
const val = f + (t - f) * eased;
|
|
35
|
+
current[key] = val;
|
|
36
|
+
// breakpoints
|
|
37
|
+
const bps = breakpoints === null || breakpoints === void 0 ? void 0 : breakpoints[key];
|
|
38
|
+
if (bps) {
|
|
39
|
+
for (let i = 0; i < bps.length; i++) {
|
|
40
|
+
const triggeredSet = triggered[key];
|
|
41
|
+
if (!triggeredSet.has(i) &&
|
|
42
|
+
((f < t && val >= bps[i].value) ||
|
|
43
|
+
(f > t && val <= bps[i].value))) {
|
|
44
|
+
triggeredSet.add(i);
|
|
45
|
+
bps[i].callback();
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
onUpdate(current, progress);
|
|
51
|
+
if (progress < 1) {
|
|
52
|
+
rafId = requestAnimationFrame(frame);
|
|
53
|
+
}
|
|
54
|
+
else {
|
|
55
|
+
const finalState = forward ? toVal : fromVal;
|
|
56
|
+
onUpdate(finalState, 1);
|
|
57
|
+
// fire remaining breakpoints
|
|
58
|
+
if (breakpoints) {
|
|
59
|
+
for (const key of keys) {
|
|
60
|
+
const bps = breakpoints[key];
|
|
61
|
+
if (!bps)
|
|
62
|
+
continue;
|
|
63
|
+
const triggeredSet = triggered[key];
|
|
64
|
+
for (let i = 0; i < bps.length; i++) {
|
|
65
|
+
if (!triggeredSet.has(i)) {
|
|
66
|
+
triggeredSet.add(i);
|
|
67
|
+
bps[i].callback();
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
cycle++;
|
|
73
|
+
if (cycle <= repeat) {
|
|
74
|
+
if (repeatBack)
|
|
75
|
+
forward = !forward;
|
|
76
|
+
startAnimation(); // 🔁 re-run with fresh from/to if functions
|
|
77
|
+
}
|
|
78
|
+
else {
|
|
79
|
+
onDone === null || onDone === void 0 ? void 0 : onDone();
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
rafId = requestAnimationFrame(frame);
|
|
84
|
+
};
|
|
85
|
+
if (delay > 0) {
|
|
86
|
+
const timeout = setTimeout(startAnimation, delay);
|
|
87
|
+
return () => {
|
|
88
|
+
clearTimeout(timeout);
|
|
89
|
+
cancelAnimationFrame(rafId);
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
else {
|
|
93
|
+
startAnimation();
|
|
94
|
+
return () => cancelAnimationFrame(rafId);
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
export { Easing, animate as default };
|
|
99
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sources":["../../src/animate/index.ts"],"sourcesContent":["\"use client\"\nimport Easing from \"./easing\";\n\nexport { Easing };\n\nexport type AnimateOptions<T extends Record<string, number>> = {\n from: T | (() => T);\n to: T | (() => T);\n duration?: number;\n delay?: number;\n easing?: ((t: number) => number) | Partial<Record<keyof T, (t: number) => number>>;\n onUpdate: (value: T, progress: number) => void;\n onDone?: () => void;\n breakpoints?: Partial<Record<keyof T, Array<{ value: number; callback: () => void }>>>;\n repeat?: number;\n repeatBack?: boolean;\n};\n\nconst animate = <T extends Record<string, number>>({\n from,\n to,\n duration = 400,\n delay = 0,\n easing = Easing.default,\n onUpdate,\n onDone,\n breakpoints,\n repeat = 0,\n repeatBack = false,\n}: AnimateOptions<T>) => {\n let rafId: number;\n let cycle = 0;\n let forward = true;\n\n // Track triggered breakpoints\n const triggered: Partial<Record<keyof T, Set<number>>> = {};\n\n const resolve = (val: T | (() => T)): T =>\n typeof val === \"function\" ? (val as () => T)() : val;\n\n const getEased = (key: keyof T, t: number) => {\n if (typeof easing === \"function\") return easing(t);\n if (easing[key]) return easing[key]!(t);\n return t;\n };\n\n const startAnimation = () => {\n const fromVal = resolve(from);\n const toVal = resolve(to);\n\n const keys = Object.keys(fromVal) as (keyof T)[];\n\n if (breakpoints) {\n for (const key of keys) triggered[key] = new Set();\n }\n\n const start = performance.now();\n\n const frame = (now: number) => {\n const progress =\n duration === 0 ? 1 : Math.min((now - start) / duration, 1);\n\n const current: any = {} as T;\n\n for (const key of keys) {\n const f = forward ? fromVal[key] : toVal[key];\n const t = forward ? toVal[key] : fromVal[key];\n\n const eased = getEased(key, progress);\n const val = f + (t - f) * eased;\n current[key] = val;\n\n // breakpoints\n const bps = breakpoints?.[key];\n if (bps) {\n for (let i = 0; i < bps.length; i++) {\n const triggeredSet = triggered[key]!;\n if (\n !triggeredSet.has(i) &&\n ((f < t && val >= bps[i].value) ||\n (f > t && val <= bps[i].value))\n ) {\n triggeredSet.add(i);\n bps[i].callback();\n }\n }\n }\n }\n\n onUpdate(current, progress);\n\n if (progress < 1) {\n rafId = requestAnimationFrame(frame);\n } else {\n const finalState = forward ? toVal : fromVal;\n onUpdate(finalState, 1);\n\n // fire remaining breakpoints\n if (breakpoints) {\n for (const key of keys) {\n const bps = breakpoints[key];\n if (!bps) continue;\n\n const triggeredSet = triggered[key]!;\n for (let i = 0; i < bps.length; i++) {\n if (!triggeredSet.has(i)) {\n triggeredSet.add(i);\n bps[i].callback();\n }\n }\n }\n }\n\n cycle++;\n\n if (cycle <= repeat) {\n if (repeatBack) forward = !forward;\n startAnimation(); // 🔁 re-run with fresh from/to if functions\n } else {\n onDone?.();\n }\n }\n };\n\n rafId = requestAnimationFrame(frame);\n };\n\n if (delay > 0) {\n const timeout = setTimeout(startAnimation, delay);\n return () => {\n clearTimeout(timeout);\n cancelAnimationFrame(rafId);\n };\n } else {\n startAnimation();\n return () => cancelAnimationFrame(rafId);\n }\n};\n\nexport default animate;"],"names":[],"mappings":";;;AAkBA;AAYG;;;;;;AAUA;;AACqC;;AACjB;AACjB;AACH;;AAGG;AACA;;;;AAK2B;;AAG3B;AAEA;;;AAMG;AACG;AACA;;;AAIA;;;;AAKG;AACG;AACA;AAEG;AACG;AAEH;AACA;;;;;AAMZ;AAEA;AACG;;;;AAGA;;;AAIG;AACG;AACA;;AAEA;AACA;;AAEM;AACA;;;;;AAMZ;AAEA;AACG;;;;;AAGA;;;AAGT;AAEA;AACH;AAEA;;AAEG;;;AAGA;;;AAEA;AACA;;AAEN;;"}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
var tslib = require('tslib');
|
|
5
|
+
var React = require('react');
|
|
6
|
+
var index = require('../animate/index.cjs');
|
|
7
|
+
|
|
8
|
+
const useTransition = (props) => {
|
|
9
|
+
const { initialStatus = "exited", onEnter, onEntered, onExit, onExited } = props, options = tslib.__rest(props, ["initialStatus", "onEnter", "onEntered", "onExit", "onExited"]);
|
|
10
|
+
const resolve = (val) => typeof val === "function" ? val() : val;
|
|
11
|
+
const [open, setOpen] = React.useState(initialStatus === "entered");
|
|
12
|
+
const [status, setStatus] = React.useState(initialStatus);
|
|
13
|
+
const stateRef = React.useRef(null);
|
|
14
|
+
const readyRef = React.useRef(false);
|
|
15
|
+
const animating = React.useRef(null);
|
|
16
|
+
React.useLayoutEffect(() => {
|
|
17
|
+
const from = resolve(options.from);
|
|
18
|
+
const to = resolve(options.to);
|
|
19
|
+
stateRef.current = initialStatus === "entered" ? to : from;
|
|
20
|
+
readyRef.current = true;
|
|
21
|
+
}, []);
|
|
22
|
+
const run = React.useCallback((nextOpen, withAnimation = true) => {
|
|
23
|
+
var _a, _b, _c, _d;
|
|
24
|
+
if (!readyRef.current || !stateRef.current)
|
|
25
|
+
return;
|
|
26
|
+
(_a = animating.current) === null || _a === void 0 ? void 0 : _a.call(animating);
|
|
27
|
+
const resolvedFrom = resolve(options.from);
|
|
28
|
+
const resolvedTo = resolve(options.to);
|
|
29
|
+
const from = stateRef.current;
|
|
30
|
+
const to = nextOpen ? resolvedTo : resolvedFrom;
|
|
31
|
+
if (nextOpen) {
|
|
32
|
+
setStatus("entering");
|
|
33
|
+
onEnter === null || onEnter === void 0 ? void 0 : onEnter();
|
|
34
|
+
}
|
|
35
|
+
else {
|
|
36
|
+
setStatus("exiting");
|
|
37
|
+
onExit === null || onExit === void 0 ? void 0 : onExit();
|
|
38
|
+
}
|
|
39
|
+
if (!withAnimation) {
|
|
40
|
+
stateRef.current = to;
|
|
41
|
+
(_b = options.onUpdate) === null || _b === void 0 ? void 0 : _b.call(options, to, 1);
|
|
42
|
+
if (nextOpen) {
|
|
43
|
+
setStatus("entered");
|
|
44
|
+
onEntered === null || onEntered === void 0 ? void 0 : onEntered();
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
setStatus("exited");
|
|
48
|
+
onExited === null || onExited === void 0 ? void 0 : onExited();
|
|
49
|
+
}
|
|
50
|
+
(_c = options.onDone) === null || _c === void 0 ? void 0 : _c.call(options);
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
animating.current = index.default(Object.assign(Object.assign({}, options), { from,
|
|
54
|
+
to, duration: (_d = options.duration) !== null && _d !== void 0 ? _d : 400, onUpdate: (value, progress) => {
|
|
55
|
+
var _a;
|
|
56
|
+
stateRef.current = value;
|
|
57
|
+
(_a = options.onUpdate) === null || _a === void 0 ? void 0 : _a.call(options, value, progress);
|
|
58
|
+
}, onDone: () => {
|
|
59
|
+
var _a;
|
|
60
|
+
if (nextOpen) {
|
|
61
|
+
setStatus("entered");
|
|
62
|
+
onEntered === null || onEntered === void 0 ? void 0 : onEntered();
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
setStatus("exited");
|
|
66
|
+
onExited === null || onExited === void 0 ? void 0 : onExited();
|
|
67
|
+
}
|
|
68
|
+
(_a = options.onDone) === null || _a === void 0 ? void 0 : _a.call(options);
|
|
69
|
+
} }));
|
|
70
|
+
}, [options, onEnter, onEntered, onExit, onExited]);
|
|
71
|
+
const enter = React.useCallback((withAnimation = true) => {
|
|
72
|
+
setOpen(true);
|
|
73
|
+
run(true, withAnimation);
|
|
74
|
+
}, [run]);
|
|
75
|
+
const exit = React.useCallback((withAnimation = true) => {
|
|
76
|
+
setOpen(false);
|
|
77
|
+
run(false, withAnimation);
|
|
78
|
+
}, [run]);
|
|
79
|
+
const toggle = React.useCallback((withAnimation = true) => {
|
|
80
|
+
setOpen((prev) => {
|
|
81
|
+
const next = !prev;
|
|
82
|
+
run(next, withAnimation);
|
|
83
|
+
return next;
|
|
84
|
+
});
|
|
85
|
+
}, [run]);
|
|
86
|
+
return {
|
|
87
|
+
isEntered: open,
|
|
88
|
+
status,
|
|
89
|
+
state: stateRef,
|
|
90
|
+
enter,
|
|
91
|
+
exit,
|
|
92
|
+
toggle,
|
|
93
|
+
isReady: readyRef.current
|
|
94
|
+
};
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
module.exports = useTransition;
|
|
98
|
+
//# sourceMappingURL=useTransition.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useTransition.cjs","sources":["../../src/hooks/useTransition.ts"],"sourcesContent":["\"use client\"\nimport { useRef, useState, useCallback, useLayoutEffect } from \"react\"\nimport animate, { AnimateOptions } from \"../animate\"\n\nexport type UseTransitionStatus =\n | \"entering\"\n | \"entered\"\n | \"exiting\"\n | \"exited\"\n\nexport type UseTransitionProps<T extends Record<string, number>> = AnimateOptions<T> & {\n initialStatus?: \"entered\" | \"exited\"\n onEnter?: () => void\n onEntered?: () => void\n onExit?: () => void\n onExited?: () => void\n}\n\nconst useTransition = <T extends Record<string, number>>(props: UseTransitionProps<T>) => {\n const {\n initialStatus = \"exited\",\n onEnter,\n onEntered,\n onExit,\n onExited,\n ...options\n } = props\n\n const resolve = (val: T | (() => T)): T =>\n typeof val === \"function\" ? (val as () => T)() : val\n\n const [open, setOpen] = useState(initialStatus === \"entered\")\n const [status, setStatus] = useState<UseTransitionStatus>(initialStatus)\n\n const stateRef = useRef<T | null>(null)\n const readyRef = useRef(false)\n\n const animating = useRef<null | (() => void)>(null)\n\n useLayoutEffect(() => {\n const from = resolve(options.from)\n const to = resolve(options.to)\n stateRef.current = initialStatus === \"entered\" ? to : from\n readyRef.current = true\n }, [])\n\n const run = useCallback(\n (nextOpen: boolean, withAnimation = true) => {\n if (!readyRef.current || !stateRef.current) return\n\n animating.current?.()\n\n const resolvedFrom = resolve(options.from)\n const resolvedTo = resolve(options.to)\n const from = stateRef.current\n const to = nextOpen ? resolvedTo : resolvedFrom\n\n if (nextOpen) {\n setStatus(\"entering\")\n onEnter?.()\n } else {\n setStatus(\"exiting\")\n onExit?.()\n }\n\n if (!withAnimation) {\n stateRef.current = to\n options.onUpdate?.(to, 1)\n\n if (nextOpen) {\n setStatus(\"entered\")\n onEntered?.()\n } else {\n setStatus(\"exited\")\n onExited?.()\n }\n\n options.onDone?.()\n return\n }\n\n animating.current = animate({\n ...options,\n from,\n to,\n duration: options.duration ?? 400,\n onUpdate: (value: T, progress: number) => {\n stateRef.current = value\n options.onUpdate?.(value, progress)\n },\n onDone: () => {\n if (nextOpen) {\n setStatus(\"entered\")\n onEntered?.()\n } else {\n setStatus(\"exited\")\n onExited?.()\n }\n options.onDone?.()\n },\n })\n },\n [options, onEnter, onEntered, onExit, onExited]\n )\n\n const enter = useCallback((withAnimation = true) => {\n setOpen(true)\n run(true, withAnimation)\n }, [run])\n\n const exit = useCallback((withAnimation = true) => {\n setOpen(false)\n run(false, withAnimation)\n }, [run])\n\n const toggle = useCallback((withAnimation = true) => {\n setOpen((prev) => {\n const next = !prev\n run(next, withAnimation)\n return next\n })\n }, [run])\n\n return {\n isEntered: open,\n status,\n state: stateRef,\n enter,\n exit,\n toggle,\n isReady: readyRef.current\n }\n}\n\nexport default useTransition"],"names":[],"mappings":";;;;;;;AAkBA;;;AAaG;;AAGA;AACA;AAEA;;;;AAKG;AACA;;;;;;AAOG;;;AAIA;;;;AAKG;;;;AAGA;;;AAIA;;;;AAKG;;;;AAGA;;AAGH;;;AAIH;AAGG;;AAGG;;AAEH;;;;AAIM;;;;AAGA;;AAEH;AACH;AAEN;;;AAMA;AACH;;;AAIG;AACH;;AAGG;AACG;AACA;AACA;AACH;AACH;;AAGG;;AAEA;;;;;;AAMN;;"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { AnimateOptions } from '../animate/index.js';
|
|
3
|
+
|
|
4
|
+
type UseTransitionStatus = "entering" | "entered" | "exiting" | "exited";
|
|
5
|
+
type UseTransitionProps<T extends Record<string, number>> = AnimateOptions<T> & {
|
|
6
|
+
initialStatus?: "entered" | "exited";
|
|
7
|
+
onEnter?: () => void;
|
|
8
|
+
onEntered?: () => void;
|
|
9
|
+
onExit?: () => void;
|
|
10
|
+
onExited?: () => void;
|
|
11
|
+
};
|
|
12
|
+
declare const useTransition: <T extends Record<string, number>>(props: UseTransitionProps<T>) => {
|
|
13
|
+
isEntered: boolean;
|
|
14
|
+
status: UseTransitionStatus;
|
|
15
|
+
state: React.RefObject<T | null>;
|
|
16
|
+
enter: (withAnimation?: boolean) => void;
|
|
17
|
+
exit: (withAnimation?: boolean) => void;
|
|
18
|
+
toggle: (withAnimation?: boolean) => void;
|
|
19
|
+
isReady: boolean;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export { useTransition as default };
|
|
23
|
+
export type { UseTransitionProps, UseTransitionStatus };
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { __rest } from 'tslib';
|
|
3
|
+
import { useState, useRef, useLayoutEffect, useCallback } from 'react';
|
|
4
|
+
import animate from '../animate/index.js';
|
|
5
|
+
|
|
6
|
+
const useTransition = (props) => {
|
|
7
|
+
const { initialStatus = "exited", onEnter, onEntered, onExit, onExited } = props, options = __rest(props, ["initialStatus", "onEnter", "onEntered", "onExit", "onExited"]);
|
|
8
|
+
const resolve = (val) => typeof val === "function" ? val() : val;
|
|
9
|
+
const [open, setOpen] = useState(initialStatus === "entered");
|
|
10
|
+
const [status, setStatus] = useState(initialStatus);
|
|
11
|
+
const stateRef = useRef(null);
|
|
12
|
+
const readyRef = useRef(false);
|
|
13
|
+
const animating = useRef(null);
|
|
14
|
+
useLayoutEffect(() => {
|
|
15
|
+
const from = resolve(options.from);
|
|
16
|
+
const to = resolve(options.to);
|
|
17
|
+
stateRef.current = initialStatus === "entered" ? to : from;
|
|
18
|
+
readyRef.current = true;
|
|
19
|
+
}, []);
|
|
20
|
+
const run = useCallback((nextOpen, withAnimation = true) => {
|
|
21
|
+
var _a, _b, _c, _d;
|
|
22
|
+
if (!readyRef.current || !stateRef.current)
|
|
23
|
+
return;
|
|
24
|
+
(_a = animating.current) === null || _a === void 0 ? void 0 : _a.call(animating);
|
|
25
|
+
const resolvedFrom = resolve(options.from);
|
|
26
|
+
const resolvedTo = resolve(options.to);
|
|
27
|
+
const from = stateRef.current;
|
|
28
|
+
const to = nextOpen ? resolvedTo : resolvedFrom;
|
|
29
|
+
if (nextOpen) {
|
|
30
|
+
setStatus("entering");
|
|
31
|
+
onEnter === null || onEnter === void 0 ? void 0 : onEnter();
|
|
32
|
+
}
|
|
33
|
+
else {
|
|
34
|
+
setStatus("exiting");
|
|
35
|
+
onExit === null || onExit === void 0 ? void 0 : onExit();
|
|
36
|
+
}
|
|
37
|
+
if (!withAnimation) {
|
|
38
|
+
stateRef.current = to;
|
|
39
|
+
(_b = options.onUpdate) === null || _b === void 0 ? void 0 : _b.call(options, to, 1);
|
|
40
|
+
if (nextOpen) {
|
|
41
|
+
setStatus("entered");
|
|
42
|
+
onEntered === null || onEntered === void 0 ? void 0 : onEntered();
|
|
43
|
+
}
|
|
44
|
+
else {
|
|
45
|
+
setStatus("exited");
|
|
46
|
+
onExited === null || onExited === void 0 ? void 0 : onExited();
|
|
47
|
+
}
|
|
48
|
+
(_c = options.onDone) === null || _c === void 0 ? void 0 : _c.call(options);
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
animating.current = animate(Object.assign(Object.assign({}, options), { from,
|
|
52
|
+
to, duration: (_d = options.duration) !== null && _d !== void 0 ? _d : 400, onUpdate: (value, progress) => {
|
|
53
|
+
var _a;
|
|
54
|
+
stateRef.current = value;
|
|
55
|
+
(_a = options.onUpdate) === null || _a === void 0 ? void 0 : _a.call(options, value, progress);
|
|
56
|
+
}, onDone: () => {
|
|
57
|
+
var _a;
|
|
58
|
+
if (nextOpen) {
|
|
59
|
+
setStatus("entered");
|
|
60
|
+
onEntered === null || onEntered === void 0 ? void 0 : onEntered();
|
|
61
|
+
}
|
|
62
|
+
else {
|
|
63
|
+
setStatus("exited");
|
|
64
|
+
onExited === null || onExited === void 0 ? void 0 : onExited();
|
|
65
|
+
}
|
|
66
|
+
(_a = options.onDone) === null || _a === void 0 ? void 0 : _a.call(options);
|
|
67
|
+
} }));
|
|
68
|
+
}, [options, onEnter, onEntered, onExit, onExited]);
|
|
69
|
+
const enter = useCallback((withAnimation = true) => {
|
|
70
|
+
setOpen(true);
|
|
71
|
+
run(true, withAnimation);
|
|
72
|
+
}, [run]);
|
|
73
|
+
const exit = useCallback((withAnimation = true) => {
|
|
74
|
+
setOpen(false);
|
|
75
|
+
run(false, withAnimation);
|
|
76
|
+
}, [run]);
|
|
77
|
+
const toggle = useCallback((withAnimation = true) => {
|
|
78
|
+
setOpen((prev) => {
|
|
79
|
+
const next = !prev;
|
|
80
|
+
run(next, withAnimation);
|
|
81
|
+
return next;
|
|
82
|
+
});
|
|
83
|
+
}, [run]);
|
|
84
|
+
return {
|
|
85
|
+
isEntered: open,
|
|
86
|
+
status,
|
|
87
|
+
state: stateRef,
|
|
88
|
+
enter,
|
|
89
|
+
exit,
|
|
90
|
+
toggle,
|
|
91
|
+
isReady: readyRef.current
|
|
92
|
+
};
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
export { useTransition as default };
|
|
96
|
+
//# sourceMappingURL=useTransition.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useTransition.js","sources":["../../src/hooks/useTransition.ts"],"sourcesContent":["\"use client\"\nimport { useRef, useState, useCallback, useLayoutEffect } from \"react\"\nimport animate, { AnimateOptions } from \"../animate\"\n\nexport type UseTransitionStatus =\n | \"entering\"\n | \"entered\"\n | \"exiting\"\n | \"exited\"\n\nexport type UseTransitionProps<T extends Record<string, number>> = AnimateOptions<T> & {\n initialStatus?: \"entered\" | \"exited\"\n onEnter?: () => void\n onEntered?: () => void\n onExit?: () => void\n onExited?: () => void\n}\n\nconst useTransition = <T extends Record<string, number>>(props: UseTransitionProps<T>) => {\n const {\n initialStatus = \"exited\",\n onEnter,\n onEntered,\n onExit,\n onExited,\n ...options\n } = props\n\n const resolve = (val: T | (() => T)): T =>\n typeof val === \"function\" ? (val as () => T)() : val\n\n const [open, setOpen] = useState(initialStatus === \"entered\")\n const [status, setStatus] = useState<UseTransitionStatus>(initialStatus)\n\n const stateRef = useRef<T | null>(null)\n const readyRef = useRef(false)\n\n const animating = useRef<null | (() => void)>(null)\n\n useLayoutEffect(() => {\n const from = resolve(options.from)\n const to = resolve(options.to)\n stateRef.current = initialStatus === \"entered\" ? to : from\n readyRef.current = true\n }, [])\n\n const run = useCallback(\n (nextOpen: boolean, withAnimation = true) => {\n if (!readyRef.current || !stateRef.current) return\n\n animating.current?.()\n\n const resolvedFrom = resolve(options.from)\n const resolvedTo = resolve(options.to)\n const from = stateRef.current\n const to = nextOpen ? resolvedTo : resolvedFrom\n\n if (nextOpen) {\n setStatus(\"entering\")\n onEnter?.()\n } else {\n setStatus(\"exiting\")\n onExit?.()\n }\n\n if (!withAnimation) {\n stateRef.current = to\n options.onUpdate?.(to, 1)\n\n if (nextOpen) {\n setStatus(\"entered\")\n onEntered?.()\n } else {\n setStatus(\"exited\")\n onExited?.()\n }\n\n options.onDone?.()\n return\n }\n\n animating.current = animate({\n ...options,\n from,\n to,\n duration: options.duration ?? 400,\n onUpdate: (value: T, progress: number) => {\n stateRef.current = value\n options.onUpdate?.(value, progress)\n },\n onDone: () => {\n if (nextOpen) {\n setStatus(\"entered\")\n onEntered?.()\n } else {\n setStatus(\"exited\")\n onExited?.()\n }\n options.onDone?.()\n },\n })\n },\n [options, onEnter, onEntered, onExit, onExited]\n )\n\n const enter = useCallback((withAnimation = true) => {\n setOpen(true)\n run(true, withAnimation)\n }, [run])\n\n const exit = useCallback((withAnimation = true) => {\n setOpen(false)\n run(false, withAnimation)\n }, [run])\n\n const toggle = useCallback((withAnimation = true) => {\n setOpen((prev) => {\n const next = !prev\n run(next, withAnimation)\n return next\n })\n }, [run])\n\n return {\n isEntered: open,\n status,\n state: stateRef,\n enter,\n exit,\n toggle,\n isReady: readyRef.current\n }\n}\n\nexport default useTransition"],"names":[],"mappings":";;;;;AAkBA;;;AAaG;;AAGA;AACA;AAEA;;;;AAKG;AACA;;;;;;AAOG;;;AAIA;;;;AAKG;;;;AAGA;;;AAIA;;;;AAKG;;;;AAGA;;AAGH;;;AAIH;AAGG;;AAGG;;AAEH;;;;AAIM;;;;AAGA;;AAEH;AACH;AAEN;;;AAMA;AACH;;;AAIG;AACH;;AAGG;AACG;AACA;AACA;AACH;AACH;;AAGG;;AAEA;;;;;;AAMN;;"}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
var React = require('react');
|
|
5
|
+
var index = require('../animate/index.cjs');
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* useTransitionGroup - staggered animations for multiple items
|
|
9
|
+
*/
|
|
10
|
+
function useTransitionGroup(options) {
|
|
11
|
+
// status of each item
|
|
12
|
+
const [statuses, setStatuses] = React.useState(() => Object.fromEntries(options.items.map((item) => [
|
|
13
|
+
item.key,
|
|
14
|
+
options.mountOnEnter ? "exited" : "entered",
|
|
15
|
+
])));
|
|
16
|
+
// refs to cancel per-item animations
|
|
17
|
+
const animatingRefs = React.useRef({});
|
|
18
|
+
// track mounted items (for mount/unmount)
|
|
19
|
+
const [mounted, setMounted] = React.useState(() => Object.fromEntries(options.items.map((item) => [
|
|
20
|
+
item.key,
|
|
21
|
+
!options.mountOnEnter,
|
|
22
|
+
])));
|
|
23
|
+
// animate a single item
|
|
24
|
+
const animateItem = React.useCallback((item, entering, delay = 0) => {
|
|
25
|
+
var _a, _b;
|
|
26
|
+
// cancel previous
|
|
27
|
+
(_b = (_a = animatingRefs.current)[item.key]) === null || _b === void 0 ? void 0 : _b.call(_a);
|
|
28
|
+
if (entering)
|
|
29
|
+
setStatuses((s) => (Object.assign(Object.assign({}, s), { [item.key]: "entering" })));
|
|
30
|
+
else
|
|
31
|
+
setStatuses((s) => (Object.assign(Object.assign({}, s), { [item.key]: "exiting" })));
|
|
32
|
+
if (entering && options.mountOnEnter) {
|
|
33
|
+
setMounted((m) => (Object.assign(Object.assign({}, m), { [item.key]: true })));
|
|
34
|
+
}
|
|
35
|
+
// start animation after delay
|
|
36
|
+
const timeout = setTimeout(() => {
|
|
37
|
+
var _a, _b, _c;
|
|
38
|
+
if (entering)
|
|
39
|
+
(_a = options.onEnter) === null || _a === void 0 ? void 0 : _a.call(options, item.key);
|
|
40
|
+
else
|
|
41
|
+
(_b = options.onExit) === null || _b === void 0 ? void 0 : _b.call(options, item.key);
|
|
42
|
+
animatingRefs.current[item.key] = index.default({
|
|
43
|
+
from: entering ? item.from : item.to,
|
|
44
|
+
to: entering ? item.to : item.from,
|
|
45
|
+
duration: (_c = options.duration) !== null && _c !== void 0 ? _c : 400,
|
|
46
|
+
onUpdate: (value, progress) => { var _a; return (_a = options.onUpdate) === null || _a === void 0 ? void 0 : _a.call(options, value, item.key, progress); },
|
|
47
|
+
onDone: () => {
|
|
48
|
+
var _a, _b;
|
|
49
|
+
if (entering) {
|
|
50
|
+
setStatuses((s) => (Object.assign(Object.assign({}, s), { [item.key]: "entered" })));
|
|
51
|
+
(_a = options.onEntered) === null || _a === void 0 ? void 0 : _a.call(options, item.key);
|
|
52
|
+
}
|
|
53
|
+
else {
|
|
54
|
+
setStatuses((s) => (Object.assign(Object.assign({}, s), { [item.key]: "exited" })));
|
|
55
|
+
(_b = options.onExited) === null || _b === void 0 ? void 0 : _b.call(options, item.key);
|
|
56
|
+
if (options.unmountOnExit) {
|
|
57
|
+
setMounted((m) => (Object.assign(Object.assign({}, m), { [item.key]: false })));
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
});
|
|
62
|
+
}, delay);
|
|
63
|
+
return () => clearTimeout(timeout);
|
|
64
|
+
}, [options]);
|
|
65
|
+
// run staggered animation on array of items
|
|
66
|
+
const run = React.useCallback((entering) => {
|
|
67
|
+
options.items.forEach((item, index) => {
|
|
68
|
+
var _a;
|
|
69
|
+
const delay = ((_a = options.stagger) !== null && _a !== void 0 ? _a : 100) * index;
|
|
70
|
+
const status = statuses[item.key];
|
|
71
|
+
// skip if already animating in same direction
|
|
72
|
+
if (entering && (status === "entering" || status === "entered"))
|
|
73
|
+
return;
|
|
74
|
+
if (!entering && (status === "exiting" || status === "exited"))
|
|
75
|
+
return;
|
|
76
|
+
animateItem(item, entering, delay);
|
|
77
|
+
});
|
|
78
|
+
}, [options.items, options.stagger, animateItem, statuses]);
|
|
79
|
+
const enter = React.useCallback(() => run(true), [run]);
|
|
80
|
+
const exit = React.useCallback(() => run(false), [run]);
|
|
81
|
+
const toggle = React.useCallback(() => {
|
|
82
|
+
const anyEntered = Object.values(statuses).some((s) => s === "entering" || s === "entered");
|
|
83
|
+
run(!anyEntered);
|
|
84
|
+
}, [run, statuses]);
|
|
85
|
+
return {
|
|
86
|
+
statuses,
|
|
87
|
+
mounted,
|
|
88
|
+
enter,
|
|
89
|
+
exit,
|
|
90
|
+
toggle,
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
module.exports = useTransitionGroup;
|
|
95
|
+
//# sourceMappingURL=useTransitionGroup.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useTransitionGroup.cjs","sources":["../../src/hooks/useTransitionGroup.ts"],"sourcesContent":["\"use client\"\nimport { useRef, useState, useCallback } from \"react\"\nimport animate from \"../animate\"\nimport { UseTransitionStatus } from \"./useTransition\"\n\nexport type UseTransitionGroupItem<T extends Record<string, number>> = {\n key: string | number\n from: T\n to: T\n}\n\nexport type UseTransitionGroupProps<T extends Record<string, number>> = {\n items: UseTransitionGroupItem<T>[]\n duration?: number\n stagger?: number // delay between items in ms\n mountOnEnter?: boolean\n unmountOnExit?: boolean\n onUpdate?: (value: T, key: string | number, progress: number) => void\n onEnter?: (key: string | number) => void\n onEntered?: (key: string | number) => void\n onExit?: (key: string | number) => void\n onExited?: (key: string | number) => void\n}\n\n/**\n * useTransitionGroup - staggered animations for multiple items\n */\nfunction useTransitionGroup<T extends Record<string, number>>(\n options: UseTransitionGroupProps<T>\n) {\n // status of each item\n const [statuses, setStatuses] = useState<\n Record<string | number, UseTransitionStatus>\n >(\n () =>\n Object.fromEntries(\n options.items.map((item) => [\n item.key,\n options.mountOnEnter ? \"exited\" : \"entered\",\n ])\n )\n )\n\n // refs to cancel per-item animations\n const animatingRefs = useRef<Record<string | number, (() => void) | null>>({})\n\n // track mounted items (for mount/unmount)\n const [mounted, setMounted] = useState<Record<string | number, boolean>>(\n () =>\n Object.fromEntries(\n options.items.map((item) => [\n item.key,\n !options.mountOnEnter,\n ])\n )\n )\n\n // animate a single item\n const animateItem = useCallback(\n (item: UseTransitionGroupItem<T>, entering: boolean, delay = 0) => {\n // cancel previous\n animatingRefs.current[item.key]?.()\n\n if (entering) setStatuses((s: any) => ({ ...s, [item.key]: \"entering\" }))\n else setStatuses((s: any) => ({ ...s, [item.key]: \"exiting\" }))\n\n if (entering && options.mountOnEnter) {\n setMounted((m) => ({ ...m, [item.key]: true }))\n }\n\n // start animation after delay\n const timeout = setTimeout(() => {\n if (entering) options.onEnter?.(item.key)\n else options.onExit?.(item.key)\n\n animatingRefs.current[item.key] = animate({\n from: entering ? item.from : item.to,\n to: entering ? item.to : item.from,\n duration: options.duration ?? 400,\n onUpdate: (value, progress) =>\n options.onUpdate?.(value, item.key, progress),\n onDone: () => {\n if (entering) {\n setStatuses((s: any) => ({ ...s, [item.key]: \"entered\" }))\n options.onEntered?.(item.key)\n } else {\n setStatuses((s: any) => ({ ...s, [item.key]: \"exited\" }))\n options.onExited?.(item.key)\n if (options.unmountOnExit) {\n setMounted((m) => ({ ...m, [item.key]: false }))\n }\n }\n },\n })\n }, delay)\n\n return () => clearTimeout(timeout)\n },\n [options]\n )\n\n // run staggered animation on array of items\n const run = useCallback(\n (entering: boolean) => {\n options.items.forEach((item, index) => {\n const delay = (options.stagger ?? 100) * index\n const status = statuses[item.key]\n\n // skip if already animating in same direction\n if (entering && (status === \"entering\" || status === \"entered\")) return\n if (!entering && (status === \"exiting\" || status === \"exited\")) return\n\n animateItem(item, entering, delay)\n })\n },\n [options.items, options.stagger, animateItem, statuses]\n )\n\n const enter = useCallback(() => run(true), [run])\n const exit = useCallback(() => run(false), [run])\n const toggle = useCallback(() => {\n const anyEntered = Object.values(statuses).some(\n (s) => s === \"entering\" || s === \"entered\"\n )\n run(!anyEntered)\n }, [run, statuses])\n\n return {\n statuses,\n mounted,\n enter,\n exit,\n toggle,\n }\n}\n\nexport default useTransitionGroup"],"names":[],"mappings":";;;;;;AAwBA;;AAEG;AACH;;;AAUe;;;;AAOZ;;;AAOY;;;;AAOZ;;;;AAKM;AAAc;;AACT;AAEL;AACG;;;AAIH;;AACG;;;;;AAIG;AACA;AACA;;;;;AAKM;;;;AAGA;;AAEA;AACG;;;;AAIX;;AAGJ;AACH;;AAKH;;;AAGS;;;;;;;AAOA;AACH;AACH;AAIH;AACA;AACA;;AAIG;AACH;;;;;;;;AASH;;"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { UseTransitionStatus } from './useTransition.js';
|
|
2
|
+
|
|
3
|
+
type UseTransitionGroupItem<T extends Record<string, number>> = {
|
|
4
|
+
key: string | number;
|
|
5
|
+
from: T;
|
|
6
|
+
to: T;
|
|
7
|
+
};
|
|
8
|
+
type UseTransitionGroupProps<T extends Record<string, number>> = {
|
|
9
|
+
items: UseTransitionGroupItem<T>[];
|
|
10
|
+
duration?: number;
|
|
11
|
+
stagger?: number;
|
|
12
|
+
mountOnEnter?: boolean;
|
|
13
|
+
unmountOnExit?: boolean;
|
|
14
|
+
onUpdate?: (value: T, key: string | number, progress: number) => void;
|
|
15
|
+
onEnter?: (key: string | number) => void;
|
|
16
|
+
onEntered?: (key: string | number) => void;
|
|
17
|
+
onExit?: (key: string | number) => void;
|
|
18
|
+
onExited?: (key: string | number) => void;
|
|
19
|
+
};
|
|
20
|
+
/**
|
|
21
|
+
* useTransitionGroup - staggered animations for multiple items
|
|
22
|
+
*/
|
|
23
|
+
declare function useTransitionGroup<T extends Record<string, number>>(options: UseTransitionGroupProps<T>): {
|
|
24
|
+
statuses: Record<string | number, UseTransitionStatus>;
|
|
25
|
+
mounted: Record<string | number, boolean>;
|
|
26
|
+
enter: () => void;
|
|
27
|
+
exit: () => void;
|
|
28
|
+
toggle: () => void;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export { useTransitionGroup as default };
|
|
32
|
+
export type { UseTransitionGroupItem, UseTransitionGroupProps };
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { useState, useRef, useCallback } from 'react';
|
|
3
|
+
import animate from '../animate/index.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* useTransitionGroup - staggered animations for multiple items
|
|
7
|
+
*/
|
|
8
|
+
function useTransitionGroup(options) {
|
|
9
|
+
// status of each item
|
|
10
|
+
const [statuses, setStatuses] = useState(() => Object.fromEntries(options.items.map((item) => [
|
|
11
|
+
item.key,
|
|
12
|
+
options.mountOnEnter ? "exited" : "entered",
|
|
13
|
+
])));
|
|
14
|
+
// refs to cancel per-item animations
|
|
15
|
+
const animatingRefs = useRef({});
|
|
16
|
+
// track mounted items (for mount/unmount)
|
|
17
|
+
const [mounted, setMounted] = useState(() => Object.fromEntries(options.items.map((item) => [
|
|
18
|
+
item.key,
|
|
19
|
+
!options.mountOnEnter,
|
|
20
|
+
])));
|
|
21
|
+
// animate a single item
|
|
22
|
+
const animateItem = useCallback((item, entering, delay = 0) => {
|
|
23
|
+
var _a, _b;
|
|
24
|
+
// cancel previous
|
|
25
|
+
(_b = (_a = animatingRefs.current)[item.key]) === null || _b === void 0 ? void 0 : _b.call(_a);
|
|
26
|
+
if (entering)
|
|
27
|
+
setStatuses((s) => (Object.assign(Object.assign({}, s), { [item.key]: "entering" })));
|
|
28
|
+
else
|
|
29
|
+
setStatuses((s) => (Object.assign(Object.assign({}, s), { [item.key]: "exiting" })));
|
|
30
|
+
if (entering && options.mountOnEnter) {
|
|
31
|
+
setMounted((m) => (Object.assign(Object.assign({}, m), { [item.key]: true })));
|
|
32
|
+
}
|
|
33
|
+
// start animation after delay
|
|
34
|
+
const timeout = setTimeout(() => {
|
|
35
|
+
var _a, _b, _c;
|
|
36
|
+
if (entering)
|
|
37
|
+
(_a = options.onEnter) === null || _a === void 0 ? void 0 : _a.call(options, item.key);
|
|
38
|
+
else
|
|
39
|
+
(_b = options.onExit) === null || _b === void 0 ? void 0 : _b.call(options, item.key);
|
|
40
|
+
animatingRefs.current[item.key] = animate({
|
|
41
|
+
from: entering ? item.from : item.to,
|
|
42
|
+
to: entering ? item.to : item.from,
|
|
43
|
+
duration: (_c = options.duration) !== null && _c !== void 0 ? _c : 400,
|
|
44
|
+
onUpdate: (value, progress) => { var _a; return (_a = options.onUpdate) === null || _a === void 0 ? void 0 : _a.call(options, value, item.key, progress); },
|
|
45
|
+
onDone: () => {
|
|
46
|
+
var _a, _b;
|
|
47
|
+
if (entering) {
|
|
48
|
+
setStatuses((s) => (Object.assign(Object.assign({}, s), { [item.key]: "entered" })));
|
|
49
|
+
(_a = options.onEntered) === null || _a === void 0 ? void 0 : _a.call(options, item.key);
|
|
50
|
+
}
|
|
51
|
+
else {
|
|
52
|
+
setStatuses((s) => (Object.assign(Object.assign({}, s), { [item.key]: "exited" })));
|
|
53
|
+
(_b = options.onExited) === null || _b === void 0 ? void 0 : _b.call(options, item.key);
|
|
54
|
+
if (options.unmountOnExit) {
|
|
55
|
+
setMounted((m) => (Object.assign(Object.assign({}, m), { [item.key]: false })));
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
});
|
|
60
|
+
}, delay);
|
|
61
|
+
return () => clearTimeout(timeout);
|
|
62
|
+
}, [options]);
|
|
63
|
+
// run staggered animation on array of items
|
|
64
|
+
const run = useCallback((entering) => {
|
|
65
|
+
options.items.forEach((item, index) => {
|
|
66
|
+
var _a;
|
|
67
|
+
const delay = ((_a = options.stagger) !== null && _a !== void 0 ? _a : 100) * index;
|
|
68
|
+
const status = statuses[item.key];
|
|
69
|
+
// skip if already animating in same direction
|
|
70
|
+
if (entering && (status === "entering" || status === "entered"))
|
|
71
|
+
return;
|
|
72
|
+
if (!entering && (status === "exiting" || status === "exited"))
|
|
73
|
+
return;
|
|
74
|
+
animateItem(item, entering, delay);
|
|
75
|
+
});
|
|
76
|
+
}, [options.items, options.stagger, animateItem, statuses]);
|
|
77
|
+
const enter = useCallback(() => run(true), [run]);
|
|
78
|
+
const exit = useCallback(() => run(false), [run]);
|
|
79
|
+
const toggle = useCallback(() => {
|
|
80
|
+
const anyEntered = Object.values(statuses).some((s) => s === "entering" || s === "entered");
|
|
81
|
+
run(!anyEntered);
|
|
82
|
+
}, [run, statuses]);
|
|
83
|
+
return {
|
|
84
|
+
statuses,
|
|
85
|
+
mounted,
|
|
86
|
+
enter,
|
|
87
|
+
exit,
|
|
88
|
+
toggle,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export { useTransitionGroup as default };
|
|
93
|
+
//# sourceMappingURL=useTransitionGroup.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useTransitionGroup.js","sources":["../../src/hooks/useTransitionGroup.ts"],"sourcesContent":["\"use client\"\nimport { useRef, useState, useCallback } from \"react\"\nimport animate from \"../animate\"\nimport { UseTransitionStatus } from \"./useTransition\"\n\nexport type UseTransitionGroupItem<T extends Record<string, number>> = {\n key: string | number\n from: T\n to: T\n}\n\nexport type UseTransitionGroupProps<T extends Record<string, number>> = {\n items: UseTransitionGroupItem<T>[]\n duration?: number\n stagger?: number // delay between items in ms\n mountOnEnter?: boolean\n unmountOnExit?: boolean\n onUpdate?: (value: T, key: string | number, progress: number) => void\n onEnter?: (key: string | number) => void\n onEntered?: (key: string | number) => void\n onExit?: (key: string | number) => void\n onExited?: (key: string | number) => void\n}\n\n/**\n * useTransitionGroup - staggered animations for multiple items\n */\nfunction useTransitionGroup<T extends Record<string, number>>(\n options: UseTransitionGroupProps<T>\n) {\n // status of each item\n const [statuses, setStatuses] = useState<\n Record<string | number, UseTransitionStatus>\n >(\n () =>\n Object.fromEntries(\n options.items.map((item) => [\n item.key,\n options.mountOnEnter ? \"exited\" : \"entered\",\n ])\n )\n )\n\n // refs to cancel per-item animations\n const animatingRefs = useRef<Record<string | number, (() => void) | null>>({})\n\n // track mounted items (for mount/unmount)\n const [mounted, setMounted] = useState<Record<string | number, boolean>>(\n () =>\n Object.fromEntries(\n options.items.map((item) => [\n item.key,\n !options.mountOnEnter,\n ])\n )\n )\n\n // animate a single item\n const animateItem = useCallback(\n (item: UseTransitionGroupItem<T>, entering: boolean, delay = 0) => {\n // cancel previous\n animatingRefs.current[item.key]?.()\n\n if (entering) setStatuses((s: any) => ({ ...s, [item.key]: \"entering\" }))\n else setStatuses((s: any) => ({ ...s, [item.key]: \"exiting\" }))\n\n if (entering && options.mountOnEnter) {\n setMounted((m) => ({ ...m, [item.key]: true }))\n }\n\n // start animation after delay\n const timeout = setTimeout(() => {\n if (entering) options.onEnter?.(item.key)\n else options.onExit?.(item.key)\n\n animatingRefs.current[item.key] = animate({\n from: entering ? item.from : item.to,\n to: entering ? item.to : item.from,\n duration: options.duration ?? 400,\n onUpdate: (value, progress) =>\n options.onUpdate?.(value, item.key, progress),\n onDone: () => {\n if (entering) {\n setStatuses((s: any) => ({ ...s, [item.key]: \"entered\" }))\n options.onEntered?.(item.key)\n } else {\n setStatuses((s: any) => ({ ...s, [item.key]: \"exited\" }))\n options.onExited?.(item.key)\n if (options.unmountOnExit) {\n setMounted((m) => ({ ...m, [item.key]: false }))\n }\n }\n },\n })\n }, delay)\n\n return () => clearTimeout(timeout)\n },\n [options]\n )\n\n // run staggered animation on array of items\n const run = useCallback(\n (entering: boolean) => {\n options.items.forEach((item, index) => {\n const delay = (options.stagger ?? 100) * index\n const status = statuses[item.key]\n\n // skip if already animating in same direction\n if (entering && (status === \"entering\" || status === \"entered\")) return\n if (!entering && (status === \"exiting\" || status === \"exited\")) return\n\n animateItem(item, entering, delay)\n })\n },\n [options.items, options.stagger, animateItem, statuses]\n )\n\n const enter = useCallback(() => run(true), [run])\n const exit = useCallback(() => run(false), [run])\n const toggle = useCallback(() => {\n const anyEntered = Object.values(statuses).some(\n (s) => s === \"entering\" || s === \"entered\"\n )\n run(!anyEntered)\n }, [run, statuses])\n\n return {\n statuses,\n mounted,\n enter,\n exit,\n toggle,\n }\n}\n\nexport default useTransitionGroup"],"names":[],"mappings":";;;;AAwBA;;AAEG;AACH;;;AAUe;;;;AAOZ;;;AAOY;;;;AAOZ;;;;AAKM;AAAc;;AACT;AAEL;AACG;;;AAIH;;AACG;;;;;AAIG;AACA;AACA;;;;;AAKM;;;;AAGA;;AAEA;AACG;;;;AAIX;;AAGJ;AACH;;AAKH;;;AAGS;;;;;;;AAOA;AACH;AACH;AAIH;AACA;AACA;;AAIG;AACH;;;;;;;;AASH;;"}
|