@jbpark/use-hooks 2.0.2 → 2.2.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/README.ko.md +18 -10
- package/README.md +16 -10
- package/dist/hooks/index.d.mts +13 -0
- package/dist/hooks/index.mjs +15 -0
- package/dist/hooks/useBodyScrollLock/index.d.mts +5 -0
- package/dist/hooks/useBodyScrollLock/index.mjs +115 -0
- package/dist/hooks/useBodyScrollLock/index.mjs.map +1 -0
- package/dist/hooks/useDebounce/index.d.mts +12 -0
- package/dist/hooks/useDebounce/index.mjs +41 -0
- package/dist/hooks/useDebounce/index.mjs.map +1 -0
- package/dist/hooks/useElementPosition/index.d.mts +8 -0
- package/dist/hooks/useElementPosition/index.mjs +32 -0
- package/dist/hooks/useElementPosition/index.mjs.map +1 -0
- package/dist/hooks/useElementScroll/index.d.mts +15 -0
- package/dist/hooks/useElementScroll/index.mjs +68 -0
- package/dist/hooks/useElementScroll/index.mjs.map +1 -0
- package/dist/hooks/useImage/index.d.mts +14 -0
- package/dist/hooks/useImage/index.mjs +56 -0
- package/dist/hooks/useImage/index.mjs.map +1 -0
- package/dist/hooks/useLocalStorage/index.d.mts +5 -0
- package/dist/hooks/useLocalStorage/index.mjs +40 -0
- package/dist/hooks/useLocalStorage/index.mjs.map +1 -0
- package/dist/hooks/useRecursiveTimeout/index.d.mts +5 -0
- package/dist/hooks/useRecursiveTimeout/index.mjs +27 -0
- package/dist/hooks/useRecursiveTimeout/index.mjs.map +1 -0
- package/dist/hooks/useResponsiveSize/index.d.mts +26 -0
- package/dist/hooks/useResponsiveSize/index.mjs +108 -0
- package/dist/hooks/useResponsiveSize/index.mjs.map +1 -0
- package/dist/hooks/useScrollToElements/index.d.mts +14 -0
- package/dist/hooks/useScrollToElements/index.mjs +34 -0
- package/dist/hooks/useScrollToElements/index.mjs.map +1 -0
- package/dist/hooks/useThrottle/index.d.mts +5 -0
- package/dist/hooks/useThrottle/index.mjs +42 -0
- package/dist/hooks/useThrottle/index.mjs.map +1 -0
- package/dist/hooks/useTimeline/index.d.mts +50 -0
- package/dist/hooks/useTimeline/index.mjs +175 -0
- package/dist/hooks/useTimeline/index.mjs.map +1 -0
- package/dist/hooks/useViewport/index.d.mts +18 -0
- package/dist/hooks/useViewport/index.mjs +87 -0
- package/dist/hooks/useViewport/index.mjs.map +1 -0
- package/dist/hooks/useWindowScroll/index.d.mts +12 -0
- package/dist/hooks/useWindowScroll/index.mjs +60 -0
- package/dist/hooks/useWindowScroll/index.mjs.map +1 -0
- package/dist/index.d.mts +15 -0
- package/dist/index.mjs +16 -0
- package/package.json +7 -8
- package/dist/index.cjs +0 -1
- package/dist/index.d.ts +0 -106
- package/dist/index.js +0 -424
- package/dist/vite.svg +0 -1
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { useCallback, useEffect, useRef, useState } from "react";
|
|
2
|
+
|
|
3
|
+
//#region src/hooks/useLocalStorage/index.ts
|
|
4
|
+
const useLocalStorage = (key, initialValue) => {
|
|
5
|
+
const initialRef = useRef(initialValue);
|
|
6
|
+
const [storedValue, setStoredValue] = useState(() => {
|
|
7
|
+
if (typeof window === "undefined") return initialValue;
|
|
8
|
+
try {
|
|
9
|
+
const item = window.localStorage.getItem(key);
|
|
10
|
+
return item ? JSON.parse(item) : initialValue;
|
|
11
|
+
} catch {
|
|
12
|
+
return initialValue;
|
|
13
|
+
}
|
|
14
|
+
});
|
|
15
|
+
useEffect(() => {
|
|
16
|
+
if (typeof window === "undefined") return;
|
|
17
|
+
try {
|
|
18
|
+
const item = window.localStorage.getItem(key);
|
|
19
|
+
if (item) setStoredValue(JSON.parse(item));
|
|
20
|
+
else window.localStorage.setItem(key, JSON.stringify(initialRef.current));
|
|
21
|
+
} catch (e) {
|
|
22
|
+
console.error(`Error reading localStorage key "${key}":`, e);
|
|
23
|
+
}
|
|
24
|
+
}, [key]);
|
|
25
|
+
return [storedValue, useCallback((value) => {
|
|
26
|
+
try {
|
|
27
|
+
setStoredValue((prev) => {
|
|
28
|
+
const valueToStore = value instanceof Function ? value(prev) : value;
|
|
29
|
+
localStorage.setItem(key, JSON.stringify(valueToStore));
|
|
30
|
+
return valueToStore;
|
|
31
|
+
});
|
|
32
|
+
} catch (e) {
|
|
33
|
+
console.error(`Error setting localStorage key "${key}":`, e);
|
|
34
|
+
}
|
|
35
|
+
}, [key])];
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
//#endregion
|
|
39
|
+
export { useLocalStorage as default };
|
|
40
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../../../src/hooks/useLocalStorage/index.ts"],"sourcesContent":["import { useCallback, useEffect, useRef, useState } from 'react';\n\nconst useLocalStorage = <T>(key: string, initialValue: T) => {\n const initialRef = useRef(initialValue);\n const [storedValue, setStoredValue] = useState<T>(() => {\n if (typeof window === 'undefined') {\n return initialValue;\n }\n try {\n const item = window.localStorage.getItem(key);\n return item ? (JSON.parse(item) as T) : initialValue;\n } catch {\n return initialValue;\n }\n });\n\n useEffect(() => {\n if (typeof window === 'undefined') {\n return;\n }\n\n try {\n const item = window.localStorage.getItem(key);\n if (item) {\n setStoredValue(JSON.parse(item) as T);\n } else {\n window.localStorage.setItem(key, JSON.stringify(initialRef.current));\n }\n } catch (e) {\n console.error(`Error reading localStorage key \"${key}\":`, e);\n }\n }, [key]);\n\n const setValue = useCallback(\n (value: T | ((val: T) => T)) => {\n try {\n setStoredValue(prev => {\n const valueToStore = value instanceof Function ? value(prev) : value;\n localStorage.setItem(key, JSON.stringify(valueToStore));\n return valueToStore;\n });\n } catch (e) {\n console.error(`Error setting localStorage key \"${key}\":`, e);\n }\n },\n [key],\n );\n\n return [storedValue, setValue] as const;\n};\n\nexport default useLocalStorage;\n"],"mappings":";;;AAEA,MAAM,mBAAsB,KAAa,iBAAoB;CAC3D,MAAM,aAAa,OAAO,aAAa;CACvC,MAAM,CAAC,aAAa,kBAAkB,eAAkB;AACtD,MAAI,OAAO,WAAW,YACpB,QAAO;AAET,MAAI;GACF,MAAM,OAAO,OAAO,aAAa,QAAQ,IAAI;AAC7C,UAAO,OAAQ,KAAK,MAAM,KAAK,GAAS;UAClC;AACN,UAAO;;GAET;AAEF,iBAAgB;AACd,MAAI,OAAO,WAAW,YACpB;AAGF,MAAI;GACF,MAAM,OAAO,OAAO,aAAa,QAAQ,IAAI;AAC7C,OAAI,KACF,gBAAe,KAAK,MAAM,KAAK,CAAM;OAErC,QAAO,aAAa,QAAQ,KAAK,KAAK,UAAU,WAAW,QAAQ,CAAC;WAE/D,GAAG;AACV,WAAQ,MAAM,mCAAmC,IAAI,KAAK,EAAE;;IAE7D,CAAC,IAAI,CAAC;AAiBT,QAAO,CAAC,aAfS,aACd,UAA+B;AAC9B,MAAI;AACF,mBAAe,SAAQ;IACrB,MAAM,eAAe,iBAAiB,WAAW,MAAM,KAAK,GAAG;AAC/D,iBAAa,QAAQ,KAAK,KAAK,UAAU,aAAa,CAAC;AACvD,WAAO;KACP;WACK,GAAG;AACV,WAAQ,MAAM,mCAAmC,IAAI,KAAK,EAAE;;IAGhE,CAAC,IAAI,CACN,CAE6B"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { useEffect, useRef } from "react";
|
|
2
|
+
|
|
3
|
+
//#region src/hooks/useRecursiveTimeout/index.ts
|
|
4
|
+
const useRecursiveTimeout = (callback, delay) => {
|
|
5
|
+
const savedCallback = useRef(callback);
|
|
6
|
+
useEffect(() => {
|
|
7
|
+
savedCallback.current = callback;
|
|
8
|
+
}, [callback]);
|
|
9
|
+
useEffect(() => {
|
|
10
|
+
let id;
|
|
11
|
+
function tick() {
|
|
12
|
+
const ret = savedCallback.current();
|
|
13
|
+
if (ret instanceof Promise) ret.then(() => {
|
|
14
|
+
if (delay) id = setTimeout(tick, delay);
|
|
15
|
+
});
|
|
16
|
+
else if (delay) id = setTimeout(tick, delay);
|
|
17
|
+
}
|
|
18
|
+
if (delay) {
|
|
19
|
+
id = setTimeout(tick, delay);
|
|
20
|
+
return () => id && clearTimeout(id);
|
|
21
|
+
}
|
|
22
|
+
}, [delay]);
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
//#endregion
|
|
26
|
+
export { useRecursiveTimeout as default };
|
|
27
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../../../src/hooks/useRecursiveTimeout/index.ts"],"sourcesContent":["import { useEffect, useRef } from 'react';\n\nconst useRecursiveTimeout = <T>(\n callback: () => Promise<T> | (() => void),\n delay: number | null,\n) => {\n const savedCallback = useRef(callback);\n\n useEffect(() => {\n savedCallback.current = callback;\n }, [callback]);\n\n useEffect(() => {\n let id: NodeJS.Timeout;\n\n function tick() {\n const ret = savedCallback.current();\n\n if (ret instanceof Promise) {\n ret.then(() => {\n if (delay) {\n id = setTimeout(tick, delay);\n }\n });\n } else {\n if (delay) {\n id = setTimeout(tick, delay);\n }\n }\n }\n\n if (delay) {\n id = setTimeout(tick, delay);\n return () => id && clearTimeout(id);\n }\n }, [delay]);\n};\n\nexport default useRecursiveTimeout;\n"],"mappings":";;;AAEA,MAAM,uBACJ,UACA,UACG;CACH,MAAM,gBAAgB,OAAO,SAAS;AAEtC,iBAAgB;AACd,gBAAc,UAAU;IACvB,CAAC,SAAS,CAAC;AAEd,iBAAgB;EACd,IAAI;EAEJ,SAAS,OAAO;GACd,MAAM,MAAM,cAAc,SAAS;AAEnC,OAAI,eAAe,QACjB,KAAI,WAAW;AACb,QAAI,MACF,MAAK,WAAW,MAAM,MAAM;KAE9B;YAEE,MACF,MAAK,WAAW,MAAM,MAAM;;AAKlC,MAAI,OAAO;AACT,QAAK,WAAW,MAAM,MAAM;AAC5B,gBAAa,MAAM,aAAa,GAAG;;IAEpC,CAAC,MAAM,CAAC"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
//#region src/hooks/useResponsiveSize/index.d.ts
|
|
2
|
+
type Breakpoint = 'xs' | 'sm' | 'md' | 'lg' | 'xl' | '2xl';
|
|
3
|
+
interface BreakpointInfo {
|
|
4
|
+
current: Breakpoint;
|
|
5
|
+
xs: boolean;
|
|
6
|
+
sm: boolean;
|
|
7
|
+
md: boolean;
|
|
8
|
+
lg: boolean;
|
|
9
|
+
xl: boolean;
|
|
10
|
+
'2xl': boolean;
|
|
11
|
+
}
|
|
12
|
+
interface Options {
|
|
13
|
+
delay?: number;
|
|
14
|
+
container?: HTMLElement | null;
|
|
15
|
+
}
|
|
16
|
+
declare const useResponsiveSize: <T extends HTMLElement>(options?: Options) => {
|
|
17
|
+
size: {
|
|
18
|
+
width: number;
|
|
19
|
+
height: number;
|
|
20
|
+
};
|
|
21
|
+
breakpoint: BreakpointInfo;
|
|
22
|
+
ref: (node: T | null) => void;
|
|
23
|
+
};
|
|
24
|
+
//#endregion
|
|
25
|
+
export { useResponsiveSize };
|
|
26
|
+
//# sourceMappingURL=index.d.mts.map
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import useDebounce from "../useDebounce/index.mjs";
|
|
2
|
+
import { useCallback, useEffect, useRef, useState } from "react";
|
|
3
|
+
|
|
4
|
+
//#region src/hooks/useResponsiveSize/index.ts
|
|
5
|
+
const BREAKPOINTS = {
|
|
6
|
+
xs: 0,
|
|
7
|
+
sm: 640,
|
|
8
|
+
md: 768,
|
|
9
|
+
lg: 1024,
|
|
10
|
+
xl: 1280,
|
|
11
|
+
"2xl": 1536
|
|
12
|
+
};
|
|
13
|
+
const getBreakpointInfo = (width) => {
|
|
14
|
+
let current = "xs";
|
|
15
|
+
if (width >= BREAKPOINTS["2xl"]) current = "2xl";
|
|
16
|
+
else if (width >= BREAKPOINTS.xl) current = "xl";
|
|
17
|
+
else if (width >= BREAKPOINTS.lg) current = "lg";
|
|
18
|
+
else if (width >= BREAKPOINTS.md) current = "md";
|
|
19
|
+
else if (width >= BREAKPOINTS.sm) current = "sm";
|
|
20
|
+
else current = "xs";
|
|
21
|
+
return {
|
|
22
|
+
current,
|
|
23
|
+
xs: width < BREAKPOINTS.sm,
|
|
24
|
+
sm: width >= BREAKPOINTS.sm && width < BREAKPOINTS.md,
|
|
25
|
+
md: width >= BREAKPOINTS.md && width < BREAKPOINTS.lg,
|
|
26
|
+
lg: width >= BREAKPOINTS.lg && width < BREAKPOINTS.xl,
|
|
27
|
+
xl: width >= BREAKPOINTS.xl && width < BREAKPOINTS["2xl"],
|
|
28
|
+
"2xl": width >= BREAKPOINTS["2xl"]
|
|
29
|
+
};
|
|
30
|
+
};
|
|
31
|
+
const useResponsiveSize = (options) => {
|
|
32
|
+
const { delay = 100, container } = options || {};
|
|
33
|
+
const [size, setSize] = useState({
|
|
34
|
+
width: 0,
|
|
35
|
+
height: 0
|
|
36
|
+
});
|
|
37
|
+
const [breakpoint, setBreakpoint] = useState({
|
|
38
|
+
current: "xs",
|
|
39
|
+
xs: true,
|
|
40
|
+
sm: false,
|
|
41
|
+
md: false,
|
|
42
|
+
lg: false,
|
|
43
|
+
xl: false,
|
|
44
|
+
"2xl": false
|
|
45
|
+
});
|
|
46
|
+
const [debouncedSize, setDebouncedSize] = useState({
|
|
47
|
+
width: 0,
|
|
48
|
+
height: 0
|
|
49
|
+
});
|
|
50
|
+
const [element, setElement] = useState(null);
|
|
51
|
+
const observerRef = useRef(null);
|
|
52
|
+
const ref = useCallback((node) => {
|
|
53
|
+
setElement(node);
|
|
54
|
+
}, []);
|
|
55
|
+
useDebounce(() => {
|
|
56
|
+
setDebouncedSize(size);
|
|
57
|
+
}, { delay }, [size]);
|
|
58
|
+
useEffect(() => {
|
|
59
|
+
const updateSize = () => {
|
|
60
|
+
const target = container ?? element ?? document.body;
|
|
61
|
+
if (!target) return;
|
|
62
|
+
const { offsetWidth, offsetHeight } = target;
|
|
63
|
+
setSize((prev) => {
|
|
64
|
+
if (prev.width !== offsetWidth || prev.height !== offsetHeight) return {
|
|
65
|
+
width: offsetWidth,
|
|
66
|
+
height: offsetHeight
|
|
67
|
+
};
|
|
68
|
+
return prev;
|
|
69
|
+
});
|
|
70
|
+
setBreakpoint((prev) => {
|
|
71
|
+
const next = getBreakpointInfo(offsetWidth);
|
|
72
|
+
if (prev.current !== next.current) return next;
|
|
73
|
+
return prev;
|
|
74
|
+
});
|
|
75
|
+
};
|
|
76
|
+
const connect = () => {
|
|
77
|
+
const target = container ?? element ?? document.body;
|
|
78
|
+
if (!target) return;
|
|
79
|
+
updateSize();
|
|
80
|
+
if (observerRef.current) observerRef.current.disconnect();
|
|
81
|
+
observerRef.current = new ResizeObserver(() => {
|
|
82
|
+
requestAnimationFrame(() => {
|
|
83
|
+
updateSize();
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
observerRef.current.observe(target);
|
|
87
|
+
};
|
|
88
|
+
const disconnect = () => {
|
|
89
|
+
if (observerRef.current) {
|
|
90
|
+
observerRef.current.disconnect();
|
|
91
|
+
observerRef.current = null;
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
connect();
|
|
95
|
+
return () => {
|
|
96
|
+
disconnect();
|
|
97
|
+
};
|
|
98
|
+
}, [container, element]);
|
|
99
|
+
return {
|
|
100
|
+
size: debouncedSize,
|
|
101
|
+
breakpoint,
|
|
102
|
+
ref
|
|
103
|
+
};
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
//#endregion
|
|
107
|
+
export { useResponsiveSize as default };
|
|
108
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../../../src/hooks/useResponsiveSize/index.ts"],"sourcesContent":["import { useCallback, useEffect, useRef, useState } from 'react';\n\nimport useDebounce from '../useDebounce';\n\ntype Breakpoint = 'xs' | 'sm' | 'md' | 'lg' | 'xl' | '2xl';\n\ninterface BreakpointInfo {\n current: Breakpoint;\n xs: boolean;\n sm: boolean;\n md: boolean;\n lg: boolean;\n xl: boolean;\n '2xl': boolean;\n}\n\ninterface Options {\n delay?: number;\n container?: HTMLElement | null;\n}\n\nconst BREAKPOINTS = {\n xs: 0, // < 640px\n sm: 640, // >= 640px\n md: 768, // >= 768px\n lg: 1024, // >= 1024px\n xl: 1280, // >= 1280px\n '2xl': 1536, // >= 1536px\n} as const;\n\nconst getBreakpointInfo = (width: number): BreakpointInfo => {\n let current: Breakpoint = 'xs';\n\n if (width >= BREAKPOINTS['2xl']) {\n current = '2xl';\n } else if (width >= BREAKPOINTS.xl) {\n current = 'xl';\n } else if (width >= BREAKPOINTS.lg) {\n current = 'lg';\n } else if (width >= BREAKPOINTS.md) {\n current = 'md';\n } else if (width >= BREAKPOINTS.sm) {\n current = 'sm';\n } else {\n current = 'xs';\n }\n\n return {\n current,\n xs: width < BREAKPOINTS.sm,\n sm: width >= BREAKPOINTS.sm && width < BREAKPOINTS.md,\n md: width >= BREAKPOINTS.md && width < BREAKPOINTS.lg,\n lg: width >= BREAKPOINTS.lg && width < BREAKPOINTS.xl,\n xl: width >= BREAKPOINTS.xl && width < BREAKPOINTS['2xl'],\n '2xl': width >= BREAKPOINTS['2xl'],\n };\n};\n\nconst useResponsiveSize = <T extends HTMLElement>(options?: Options) => {\n const { delay = 100, container } = options || {};\n const [size, setSize] = useState({ width: 0, height: 0 });\n const [breakpoint, setBreakpoint] = useState<BreakpointInfo>({\n current: 'xs',\n xs: true,\n sm: false,\n md: false,\n lg: false,\n xl: false,\n '2xl': false,\n });\n\n const [debouncedSize, setDebouncedSize] = useState({ width: 0, height: 0 });\n\n const [element, setElement] = useState<T | null>(null);\n const observerRef = useRef<ResizeObserver | null>(null);\n\n const ref = useCallback((node: T | null) => {\n setElement(node);\n }, []);\n\n useDebounce(\n () => {\n setDebouncedSize(size);\n },\n { delay },\n [size],\n );\n\n useEffect(() => {\n const updateSize = () => {\n const target = container ?? element ?? document.body;\n\n if (!target) {\n return;\n }\n\n const { offsetWidth, offsetHeight } = target;\n\n setSize(prev => {\n if (prev.width !== offsetWidth || prev.height !== offsetHeight) {\n return { width: offsetWidth, height: offsetHeight };\n }\n return prev;\n });\n\n setBreakpoint(prev => {\n const next = getBreakpointInfo(offsetWidth);\n if (prev.current !== next.current) {\n return next;\n }\n return prev;\n });\n };\n\n const connect = () => {\n const target = container ?? element ?? document.body;\n\n if (!target) {\n return;\n }\n\n updateSize();\n\n if (observerRef.current) {\n observerRef.current.disconnect();\n }\n\n observerRef.current = new ResizeObserver(() => {\n requestAnimationFrame(() => {\n updateSize();\n });\n });\n\n observerRef.current.observe(target);\n };\n\n const disconnect = () => {\n if (observerRef.current) {\n observerRef.current.disconnect();\n observerRef.current = null;\n }\n };\n\n connect();\n\n return () => {\n disconnect();\n };\n }, [container, element]);\n\n return {\n size: debouncedSize,\n breakpoint,\n ref,\n };\n};\n\nexport default useResponsiveSize;\n"],"mappings":";;;;AAqBA,MAAM,cAAc;CAClB,IAAI;CACJ,IAAI;CACJ,IAAI;CACJ,IAAI;CACJ,IAAI;CACJ,OAAO;CACR;AAED,MAAM,qBAAqB,UAAkC;CAC3D,IAAI,UAAsB;AAE1B,KAAI,SAAS,YAAY,OACvB,WAAU;UACD,SAAS,YAAY,GAC9B,WAAU;UACD,SAAS,YAAY,GAC9B,WAAU;UACD,SAAS,YAAY,GAC9B,WAAU;UACD,SAAS,YAAY,GAC9B,WAAU;KAEV,WAAU;AAGZ,QAAO;EACL;EACA,IAAI,QAAQ,YAAY;EACxB,IAAI,SAAS,YAAY,MAAM,QAAQ,YAAY;EACnD,IAAI,SAAS,YAAY,MAAM,QAAQ,YAAY;EACnD,IAAI,SAAS,YAAY,MAAM,QAAQ,YAAY;EACnD,IAAI,SAAS,YAAY,MAAM,QAAQ,YAAY;EACnD,OAAO,SAAS,YAAY;EAC7B;;AAGH,MAAM,qBAA4C,YAAsB;CACtE,MAAM,EAAE,QAAQ,KAAK,cAAc,WAAW,EAAE;CAChD,MAAM,CAAC,MAAM,WAAW,SAAS;EAAE,OAAO;EAAG,QAAQ;EAAG,CAAC;CACzD,MAAM,CAAC,YAAY,iBAAiB,SAAyB;EAC3D,SAAS;EACT,IAAI;EACJ,IAAI;EACJ,IAAI;EACJ,IAAI;EACJ,IAAI;EACJ,OAAO;EACR,CAAC;CAEF,MAAM,CAAC,eAAe,oBAAoB,SAAS;EAAE,OAAO;EAAG,QAAQ;EAAG,CAAC;CAE3E,MAAM,CAAC,SAAS,cAAc,SAAmB,KAAK;CACtD,MAAM,cAAc,OAA8B,KAAK;CAEvD,MAAM,MAAM,aAAa,SAAmB;AAC1C,aAAW,KAAK;IACf,EAAE,CAAC;AAEN,mBACQ;AACJ,mBAAiB,KAAK;IAExB,EAAE,OAAO,EACT,CAAC,KAAK,CACP;AAED,iBAAgB;EACd,MAAM,mBAAmB;GACvB,MAAM,SAAS,aAAa,WAAW,SAAS;AAEhD,OAAI,CAAC,OACH;GAGF,MAAM,EAAE,aAAa,iBAAiB;AAEtC,YAAQ,SAAQ;AACd,QAAI,KAAK,UAAU,eAAe,KAAK,WAAW,aAChD,QAAO;KAAE,OAAO;KAAa,QAAQ;KAAc;AAErD,WAAO;KACP;AAEF,kBAAc,SAAQ;IACpB,MAAM,OAAO,kBAAkB,YAAY;AAC3C,QAAI,KAAK,YAAY,KAAK,QACxB,QAAO;AAET,WAAO;KACP;;EAGJ,MAAM,gBAAgB;GACpB,MAAM,SAAS,aAAa,WAAW,SAAS;AAEhD,OAAI,CAAC,OACH;AAGF,eAAY;AAEZ,OAAI,YAAY,QACd,aAAY,QAAQ,YAAY;AAGlC,eAAY,UAAU,IAAI,qBAAqB;AAC7C,gCAA4B;AAC1B,iBAAY;MACZ;KACF;AAEF,eAAY,QAAQ,QAAQ,OAAO;;EAGrC,MAAM,mBAAmB;AACvB,OAAI,YAAY,SAAS;AACvB,gBAAY,QAAQ,YAAY;AAChC,gBAAY,UAAU;;;AAI1B,WAAS;AAET,eAAa;AACX,eAAY;;IAEb,CAAC,WAAW,QAAQ,CAAC;AAExB,QAAO;EACL,MAAM;EACN;EACA;EACD"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import * as react from "react";
|
|
2
|
+
|
|
3
|
+
//#region src/hooks/useScrollToElements/index.d.ts
|
|
4
|
+
interface Options extends ScrollIntoViewOptions {
|
|
5
|
+
offset?: number;
|
|
6
|
+
}
|
|
7
|
+
declare const useScrollToElements: (options?: Options) => {
|
|
8
|
+
elementRefs: react.RefObject<HTMLElement[]>;
|
|
9
|
+
setElementRef: (element: HTMLElement, index: number) => void;
|
|
10
|
+
scrollToElement: (index: number) => void;
|
|
11
|
+
};
|
|
12
|
+
//#endregion
|
|
13
|
+
export { useScrollToElements };
|
|
14
|
+
//# sourceMappingURL=index.d.mts.map
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { useCallback, useRef } from "react";
|
|
2
|
+
|
|
3
|
+
//#region src/hooks/useScrollToElements/index.ts
|
|
4
|
+
const useScrollToElements = (options) => {
|
|
5
|
+
const elementRefs = useRef([]);
|
|
6
|
+
const scrollToElement = useCallback((index) => {
|
|
7
|
+
if (elementRefs.current[index]) {
|
|
8
|
+
elementRefs.current[index].scrollIntoView({
|
|
9
|
+
behavior: "smooth",
|
|
10
|
+
block: "start",
|
|
11
|
+
inline: "start",
|
|
12
|
+
...options
|
|
13
|
+
});
|
|
14
|
+
if (options?.offset) {
|
|
15
|
+
const top = elementRefs.current[index].getBoundingClientRect().top + window.scrollY - options.offset;
|
|
16
|
+
window.scrollTo({
|
|
17
|
+
top,
|
|
18
|
+
behavior: options.behavior || "smooth"
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}, [options]);
|
|
23
|
+
return {
|
|
24
|
+
elementRefs,
|
|
25
|
+
setElementRef: useCallback((element, index) => {
|
|
26
|
+
elementRefs.current[index] = element;
|
|
27
|
+
}, []),
|
|
28
|
+
scrollToElement
|
|
29
|
+
};
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
//#endregion
|
|
33
|
+
export { useScrollToElements as default };
|
|
34
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../../../src/hooks/useScrollToElements/index.ts"],"sourcesContent":["import { useCallback, useRef } from 'react';\n\ninterface Options extends ScrollIntoViewOptions {\n offset?: number;\n}\nconst useScrollToElements = (options?: Options) => {\n const elementRefs = useRef<HTMLElement[]>([]);\n\n const scrollToElement = useCallback(\n (index: number) => {\n if (elementRefs.current[index]) {\n elementRefs.current[index].scrollIntoView({\n behavior: 'smooth',\n block: 'start',\n inline: 'start',\n ...options,\n });\n\n if (options?.offset) {\n const top =\n elementRefs.current[index].getBoundingClientRect().top +\n window.scrollY -\n options.offset;\n\n window.scrollTo({\n top,\n behavior: options.behavior || 'smooth',\n });\n }\n }\n },\n [options],\n );\n\n const setElementRef = useCallback((element: HTMLElement, index: number) => {\n elementRefs.current[index] = element;\n }, []);\n\n return { elementRefs, setElementRef, scrollToElement };\n};\n\nexport default useScrollToElements;\n"],"mappings":";;;AAKA,MAAM,uBAAuB,YAAsB;CACjD,MAAM,cAAc,OAAsB,EAAE,CAAC;CAE7C,MAAM,kBAAkB,aACrB,UAAkB;AACjB,MAAI,YAAY,QAAQ,QAAQ;AAC9B,eAAY,QAAQ,OAAO,eAAe;IACxC,UAAU;IACV,OAAO;IACP,QAAQ;IACR,GAAG;IACJ,CAAC;AAEF,OAAI,SAAS,QAAQ;IACnB,MAAM,MACJ,YAAY,QAAQ,OAAO,uBAAuB,CAAC,MACnD,OAAO,UACP,QAAQ;AAEV,WAAO,SAAS;KACd;KACA,UAAU,QAAQ,YAAY;KAC/B,CAAC;;;IAIR,CAAC,QAAQ,CACV;AAMD,QAAO;EAAE;EAAa,eAJA,aAAa,SAAsB,UAAkB;AACzE,eAAY,QAAQ,SAAS;KAC5B,EAAE,CAAC;EAE+B;EAAiB"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { useEffect, useRef, useState } from "react";
|
|
2
|
+
|
|
3
|
+
//#region src/hooks/useThrottle/index.ts
|
|
4
|
+
const useThrottle = (value, delay = 100) => {
|
|
5
|
+
const [throttledValue, setThrottledValue] = useState(value);
|
|
6
|
+
const lastExecutedRef = useRef(0);
|
|
7
|
+
const timeoutRef = useRef(null);
|
|
8
|
+
const latestValueRef = useRef(value);
|
|
9
|
+
useEffect(() => {
|
|
10
|
+
latestValueRef.current = value;
|
|
11
|
+
}, [value]);
|
|
12
|
+
useEffect(() => {
|
|
13
|
+
const now = Date.now();
|
|
14
|
+
const elapsed = now - lastExecutedRef.current;
|
|
15
|
+
if (elapsed >= delay) {
|
|
16
|
+
if (timeoutRef.current) {
|
|
17
|
+
clearTimeout(timeoutRef.current);
|
|
18
|
+
timeoutRef.current = null;
|
|
19
|
+
}
|
|
20
|
+
lastExecutedRef.current = now;
|
|
21
|
+
setThrottledValue(latestValueRef.current);
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
const remaining = delay - elapsed;
|
|
25
|
+
if (timeoutRef.current) clearTimeout(timeoutRef.current);
|
|
26
|
+
timeoutRef.current = setTimeout(() => {
|
|
27
|
+
lastExecutedRef.current = Date.now();
|
|
28
|
+
setThrottledValue(latestValueRef.current);
|
|
29
|
+
}, remaining);
|
|
30
|
+
return () => {
|
|
31
|
+
if (timeoutRef.current) {
|
|
32
|
+
clearTimeout(timeoutRef.current);
|
|
33
|
+
timeoutRef.current = null;
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
}, [value, delay]);
|
|
37
|
+
return throttledValue;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
//#endregion
|
|
41
|
+
export { useThrottle as default };
|
|
42
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../../../src/hooks/useThrottle/index.ts"],"sourcesContent":["import { useEffect, useRef, useState } from 'react';\n\nconst useThrottle = <T>(value: T, delay = 100): T => {\n const [throttledValue, setThrottledValue] = useState(value);\n const lastExecutedRef = useRef(0);\n const timeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n const latestValueRef = useRef(value);\n\n useEffect(() => {\n latestValueRef.current = value;\n }, [value]);\n\n useEffect(() => {\n const now = Date.now();\n const elapsed = now - lastExecutedRef.current;\n\n if (elapsed >= delay) {\n if (timeoutRef.current) {\n clearTimeout(timeoutRef.current);\n timeoutRef.current = null;\n }\n\n lastExecutedRef.current = now;\n setThrottledValue(latestValueRef.current);\n return;\n }\n\n const remaining = delay - elapsed;\n\n if (timeoutRef.current) {\n clearTimeout(timeoutRef.current);\n }\n\n timeoutRef.current = setTimeout(() => {\n lastExecutedRef.current = Date.now();\n setThrottledValue(latestValueRef.current);\n }, remaining);\n\n return () => {\n if (timeoutRef.current) {\n clearTimeout(timeoutRef.current);\n timeoutRef.current = null;\n }\n };\n }, [value, delay]);\n\n return throttledValue;\n};\n\nexport default useThrottle;\n"],"mappings":";;;AAEA,MAAM,eAAkB,OAAU,QAAQ,QAAW;CACnD,MAAM,CAAC,gBAAgB,qBAAqB,SAAS,MAAM;CAC3D,MAAM,kBAAkB,OAAO,EAAE;CACjC,MAAM,aAAa,OAA6C,KAAK;CACrE,MAAM,iBAAiB,OAAO,MAAM;AAEpC,iBAAgB;AACd,iBAAe,UAAU;IACxB,CAAC,MAAM,CAAC;AAEX,iBAAgB;EACd,MAAM,MAAM,KAAK,KAAK;EACtB,MAAM,UAAU,MAAM,gBAAgB;AAEtC,MAAI,WAAW,OAAO;AACpB,OAAI,WAAW,SAAS;AACtB,iBAAa,WAAW,QAAQ;AAChC,eAAW,UAAU;;AAGvB,mBAAgB,UAAU;AAC1B,qBAAkB,eAAe,QAAQ;AACzC;;EAGF,MAAM,YAAY,QAAQ;AAE1B,MAAI,WAAW,QACb,cAAa,WAAW,QAAQ;AAGlC,aAAW,UAAU,iBAAiB;AACpC,mBAAgB,UAAU,KAAK,KAAK;AACpC,qBAAkB,eAAe,QAAQ;KACxC,UAAU;AAEb,eAAa;AACX,OAAI,WAAW,SAAS;AACtB,iBAAa,WAAW,QAAQ;AAChC,eAAW,UAAU;;;IAGxB,CAAC,OAAO,MAAM,CAAC;AAElB,QAAO"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import * as react from "react";
|
|
2
|
+
|
|
3
|
+
//#region src/hooks/useTimeline/index.d.ts
|
|
4
|
+
interface TimelineStep {
|
|
5
|
+
scale?: number;
|
|
6
|
+
rotate?: {
|
|
7
|
+
x?: number;
|
|
8
|
+
y?: number;
|
|
9
|
+
z?: number;
|
|
10
|
+
};
|
|
11
|
+
position?: {
|
|
12
|
+
x?: number;
|
|
13
|
+
y?: number;
|
|
14
|
+
};
|
|
15
|
+
top?: number;
|
|
16
|
+
left?: number;
|
|
17
|
+
width?: number;
|
|
18
|
+
height?: number;
|
|
19
|
+
zIndex?: number;
|
|
20
|
+
filter?: string;
|
|
21
|
+
backgroundColor?: string;
|
|
22
|
+
opacity?: number;
|
|
23
|
+
selector: string;
|
|
24
|
+
transition: Transition;
|
|
25
|
+
}
|
|
26
|
+
interface Transition {
|
|
27
|
+
duration: number;
|
|
28
|
+
ease?: string;
|
|
29
|
+
delay?: number;
|
|
30
|
+
easeX?: string;
|
|
31
|
+
easeY?: string;
|
|
32
|
+
}
|
|
33
|
+
interface Options {
|
|
34
|
+
steps?: TimelineStep[];
|
|
35
|
+
loading?: boolean;
|
|
36
|
+
immediate?: boolean;
|
|
37
|
+
loop?: boolean;
|
|
38
|
+
}
|
|
39
|
+
declare const useTimeline: ({
|
|
40
|
+
steps,
|
|
41
|
+
loading,
|
|
42
|
+
immediate,
|
|
43
|
+
loop
|
|
44
|
+
}: Options) => {
|
|
45
|
+
ref: react.RefObject<HTMLDivElement | null>;
|
|
46
|
+
completed: boolean;
|
|
47
|
+
};
|
|
48
|
+
//#endregion
|
|
49
|
+
export { useTimeline };
|
|
50
|
+
//# sourceMappingURL=index.d.mts.map
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import { useEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
|
|
2
|
+
|
|
3
|
+
//#region src/hooks/useTimeline/index.ts
|
|
4
|
+
const registeredProperties = /* @__PURE__ */ new Set();
|
|
5
|
+
const registerCSSProperty = (name, syntax = "<length>", initialValue = "0px") => {
|
|
6
|
+
if (registeredProperties.has(name) || typeof CSS === "undefined" || !CSS.registerProperty) return;
|
|
7
|
+
try {
|
|
8
|
+
CSS.registerProperty({
|
|
9
|
+
name,
|
|
10
|
+
syntax,
|
|
11
|
+
inherits: false,
|
|
12
|
+
initialValue
|
|
13
|
+
});
|
|
14
|
+
registeredProperties.add(name);
|
|
15
|
+
} catch {}
|
|
16
|
+
};
|
|
17
|
+
const buildTimeline = (steps) => {
|
|
18
|
+
const groups = [];
|
|
19
|
+
for (const step of steps) if (step.transition.delay === 0 && groups.length > 0) groups[groups.length - 1]?.push(step);
|
|
20
|
+
else groups.push([step]);
|
|
21
|
+
return groups;
|
|
22
|
+
};
|
|
23
|
+
const buildTransform = (step) => {
|
|
24
|
+
const parts = [];
|
|
25
|
+
if (step.position?.x != null || step.position?.y != null) parts.push(`translate(${step.position.x ?? 0}px, ${step.position.y ?? 0}px)`);
|
|
26
|
+
if (step.scale != null) parts.push(`scale(${step.scale})`);
|
|
27
|
+
if (step.rotate?.x != null) parts.push(`rotateX(${step.rotate.x}deg)`);
|
|
28
|
+
if (step.rotate?.y != null) parts.push(`rotateY(${step.rotate.y}deg)`);
|
|
29
|
+
if (step.rotate?.z != null) parts.push(`rotateZ(${step.rotate.z}deg)`);
|
|
30
|
+
return parts.join(" ") || void 0;
|
|
31
|
+
};
|
|
32
|
+
const applyStep = (container, step, skipTransition) => {
|
|
33
|
+
const elements = container.matches(step.selector) ? [container] : Array.from(container.querySelectorAll(step.selector));
|
|
34
|
+
if (!elements.length) return;
|
|
35
|
+
const dur = `${step.transition.duration}ms`;
|
|
36
|
+
const eas = step.transition.ease ?? "ease-out";
|
|
37
|
+
const easX = step.transition.easeX;
|
|
38
|
+
const easY = step.transition.easeY;
|
|
39
|
+
const hasCurve = !!(easX || easY);
|
|
40
|
+
const timers = [];
|
|
41
|
+
elements.forEach((el) => {
|
|
42
|
+
if (skipTransition) el.style.transition = "none";
|
|
43
|
+
else if (hasCurve) {
|
|
44
|
+
const xEase = easX ?? eas;
|
|
45
|
+
const yEase = easY ?? eas;
|
|
46
|
+
el.style.transition = [
|
|
47
|
+
`--tx ${dur} ${xEase}`,
|
|
48
|
+
`--ty ${dur} ${yEase}`,
|
|
49
|
+
`--s ${dur} ${eas}`,
|
|
50
|
+
`rotate ${dur} ${eas}`,
|
|
51
|
+
`width ${dur} ${eas}`,
|
|
52
|
+
`height ${dur} ${eas}`,
|
|
53
|
+
`filter ${dur} ${eas}`,
|
|
54
|
+
`opacity ${dur} ${eas}`
|
|
55
|
+
].join(", ");
|
|
56
|
+
} else el.style.transition = `all ${dur} ${eas}`;
|
|
57
|
+
if (step.backgroundColor) el.style.backgroundColor = step.backgroundColor;
|
|
58
|
+
if (step.opacity !== void 0) el.style.opacity = String(step.opacity);
|
|
59
|
+
if (step.top !== void 0) el.style.top = `${step.top}px`;
|
|
60
|
+
if (step.left !== void 0) el.style.left = `${step.left}px`;
|
|
61
|
+
if (step.width !== void 0) el.style.width = `${step.width}px`;
|
|
62
|
+
if (step.height !== void 0) el.style.height = `${step.height}px`;
|
|
63
|
+
if (step.zIndex !== void 0) if (skipTransition) el.style.zIndex = String(step.zIndex);
|
|
64
|
+
else {
|
|
65
|
+
const timerId = setTimeout(() => {
|
|
66
|
+
el.style.zIndex = String(step.zIndex);
|
|
67
|
+
}, step.transition.duration / 2);
|
|
68
|
+
timers.push(timerId);
|
|
69
|
+
}
|
|
70
|
+
if (step.filter !== void 0) el.style.filter = step.filter;
|
|
71
|
+
if (step.rotate?.z !== void 0) el.style.rotate = `${step.rotate.z}deg`;
|
|
72
|
+
if (hasCurve) {
|
|
73
|
+
if (step.position?.x != null || step.position?.y != null) {
|
|
74
|
+
el.style.setProperty("--tx", `${step.position.x ?? 0}px`);
|
|
75
|
+
el.style.setProperty("--ty", `${step.position.y ?? 0}px`);
|
|
76
|
+
}
|
|
77
|
+
if (step.scale != null) el.style.setProperty("--s", String(step.scale));
|
|
78
|
+
el.style.transform = `translate(var(--tx), var(--ty)) scale(var(--s, 1))`;
|
|
79
|
+
} else {
|
|
80
|
+
const transform = buildTransform(step);
|
|
81
|
+
if (transform) el.style.transform = transform;
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
return timers;
|
|
85
|
+
};
|
|
86
|
+
const useTimeline = ({ steps = [], loading, immediate, loop }) => {
|
|
87
|
+
const [slotIndex, setSlotIndex] = useState(-1);
|
|
88
|
+
const [completed, setCompleted] = useState(() => !!immediate);
|
|
89
|
+
const ref = useRef(null);
|
|
90
|
+
const timeline = useMemo(() => buildTimeline(steps), [steps]);
|
|
91
|
+
const hasCurveSteps = useMemo(() => steps.some((s) => s.transition.easeX || s.transition.easeY), [steps]);
|
|
92
|
+
useLayoutEffect(() => {
|
|
93
|
+
if (hasCurveSteps) {
|
|
94
|
+
registerCSSProperty("--tx");
|
|
95
|
+
registerCSSProperty("--ty");
|
|
96
|
+
registerCSSProperty("--s", "<number>", "1");
|
|
97
|
+
}
|
|
98
|
+
}, [hasCurveSteps]);
|
|
99
|
+
useLayoutEffect(() => {
|
|
100
|
+
if (!immediate || !ref.current || !steps.length) return;
|
|
101
|
+
const container = ref.current;
|
|
102
|
+
steps.forEach((step) => applyStep(container, step, true));
|
|
103
|
+
}, [immediate, steps]);
|
|
104
|
+
useEffect(() => {
|
|
105
|
+
if (immediate || loading || !timeline.length) return;
|
|
106
|
+
const firstDelay = timeline[0]?.[0]?.transition.delay ?? 0;
|
|
107
|
+
const timerId = window.setTimeout(() => {
|
|
108
|
+
requestAnimationFrame(() => setSlotIndex(0));
|
|
109
|
+
}, firstDelay);
|
|
110
|
+
return () => clearTimeout(timerId);
|
|
111
|
+
}, [
|
|
112
|
+
loading,
|
|
113
|
+
timeline,
|
|
114
|
+
immediate
|
|
115
|
+
]);
|
|
116
|
+
useEffect(() => {
|
|
117
|
+
if (immediate || loading || slotIndex < 0) return;
|
|
118
|
+
const isLast = slotIndex >= timeline.length - 1;
|
|
119
|
+
if (isLast && !loop) return;
|
|
120
|
+
const currentSlot = timeline[slotIndex];
|
|
121
|
+
const maxDuration = Math.max(...currentSlot?.map((s) => s.transition.duration) ?? [0]);
|
|
122
|
+
const nextIndex = isLast ? 0 : slotIndex + 1;
|
|
123
|
+
const nextDelay = timeline[nextIndex]?.[0]?.transition.delay ?? 0;
|
|
124
|
+
const timerId = window.setTimeout(() => {
|
|
125
|
+
setSlotIndex(nextIndex);
|
|
126
|
+
}, maxDuration + nextDelay);
|
|
127
|
+
return () => clearTimeout(timerId);
|
|
128
|
+
}, [
|
|
129
|
+
loading,
|
|
130
|
+
slotIndex,
|
|
131
|
+
timeline,
|
|
132
|
+
immediate,
|
|
133
|
+
loop
|
|
134
|
+
]);
|
|
135
|
+
useEffect(() => {
|
|
136
|
+
if (immediate || slotIndex < 0 || !ref.current) return;
|
|
137
|
+
const container = ref.current;
|
|
138
|
+
const timers = [];
|
|
139
|
+
requestAnimationFrame(() => {
|
|
140
|
+
timeline[slotIndex]?.forEach((step) => {
|
|
141
|
+
const stepTimers = applyStep(container, step);
|
|
142
|
+
if (stepTimers) timers.push(...stepTimers);
|
|
143
|
+
});
|
|
144
|
+
});
|
|
145
|
+
return () => {
|
|
146
|
+
timers.forEach(clearTimeout);
|
|
147
|
+
};
|
|
148
|
+
}, [
|
|
149
|
+
immediate,
|
|
150
|
+
slotIndex,
|
|
151
|
+
timeline
|
|
152
|
+
]);
|
|
153
|
+
useEffect(() => {
|
|
154
|
+
if (immediate || loop || slotIndex < 0 || slotIndex < timeline.length - 1 || !timeline.length) return;
|
|
155
|
+
const lastSlot = timeline[slotIndex];
|
|
156
|
+
const maxDuration = Math.max(...lastSlot?.map((s) => s.transition.duration) ?? [0]);
|
|
157
|
+
const timerId = window.setTimeout(() => {
|
|
158
|
+
setCompleted(true);
|
|
159
|
+
}, maxDuration);
|
|
160
|
+
return () => clearTimeout(timerId);
|
|
161
|
+
}, [
|
|
162
|
+
slotIndex,
|
|
163
|
+
timeline,
|
|
164
|
+
immediate,
|
|
165
|
+
loop
|
|
166
|
+
]);
|
|
167
|
+
return {
|
|
168
|
+
ref,
|
|
169
|
+
completed
|
|
170
|
+
};
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
//#endregion
|
|
174
|
+
export { useTimeline as default };
|
|
175
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../../../src/hooks/useTimeline/index.ts"],"sourcesContent":["import { useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react';\n\nconst registeredProperties = new Set<string>();\n\nconst registerCSSProperty = (\n name: string,\n syntax = '<length>',\n initialValue = '0px',\n) => {\n if (\n registeredProperties.has(name) ||\n typeof CSS === 'undefined' ||\n !CSS.registerProperty\n ) {\n return;\n }\n\n try {\n CSS.registerProperty({\n name,\n syntax,\n inherits: false,\n initialValue,\n });\n registeredProperties.add(name);\n } catch {\n // 이미 등록된 경우 무시\n }\n};\n\nexport interface TimelineStep {\n scale?: number;\n rotate?: { x?: number; y?: number; z?: number };\n position?: { x?: number; y?: number };\n top?: number;\n left?: number;\n width?: number;\n height?: number;\n zIndex?: number;\n filter?: string;\n backgroundColor?: string;\n opacity?: number;\n selector: string;\n transition: Transition;\n}\n\ninterface Transition {\n duration: number;\n ease?: string;\n delay?: number;\n easeX?: string;\n easeY?: string;\n}\n\ninterface Options {\n steps?: TimelineStep[];\n loading?: boolean;\n immediate?: boolean;\n loop?: boolean;\n}\n\nconst buildTimeline = (steps: TimelineStep[]): TimelineStep[][] => {\n const groups: TimelineStep[][] = [];\n\n for (const step of steps) {\n if (step.transition.delay === 0 && groups.length > 0) {\n groups[groups.length - 1]?.push(step);\n } else {\n groups.push([step]);\n }\n }\n\n return groups;\n};\n\nconst buildTransform = (step: TimelineStep) => {\n const parts: string[] = [];\n\n if (step.position?.x != null || step.position?.y != null) {\n parts.push(\n `translate(${step.position.x ?? 0}px, ${step.position.y ?? 0}px)`,\n );\n }\n\n if (step.scale != null) {\n parts.push(`scale(${step.scale})`);\n }\n\n if (step.rotate?.x != null) {\n parts.push(`rotateX(${step.rotate.x}deg)`);\n }\n\n if (step.rotate?.y != null) {\n parts.push(`rotateY(${step.rotate.y}deg)`);\n }\n\n if (step.rotate?.z != null) {\n parts.push(`rotateZ(${step.rotate.z}deg)`);\n }\n\n return parts.join(' ') || undefined;\n};\n\nconst applyStep = (\n container: HTMLElement,\n step: TimelineStep,\n skipTransition?: boolean,\n) => {\n const elements = container.matches(step.selector)\n ? [container]\n : Array.from(container.querySelectorAll<HTMLElement>(step.selector));\n\n if (!elements.length) {\n return;\n }\n\n const dur = `${step.transition.duration}ms`;\n const eas = step.transition.ease ?? 'ease-out';\n const easX = step.transition.easeX;\n const easY = step.transition.easeY;\n const hasCurve = !!(easX || easY);\n const timers: ReturnType<typeof setTimeout>[] = [];\n\n elements.forEach(el => {\n if (skipTransition) {\n el.style.transition = 'none';\n } else if (hasCurve) {\n const xEase = easX ?? eas;\n const yEase = easY ?? eas;\n el.style.transition = [\n `--tx ${dur} ${xEase}`,\n `--ty ${dur} ${yEase}`,\n `--s ${dur} ${eas}`,\n `rotate ${dur} ${eas}`,\n `width ${dur} ${eas}`,\n `height ${dur} ${eas}`,\n `filter ${dur} ${eas}`,\n `opacity ${dur} ${eas}`,\n ].join(', ');\n } else {\n el.style.transition = `all ${dur} ${eas}`;\n }\n\n if (step.backgroundColor) {\n el.style.backgroundColor = step.backgroundColor;\n }\n\n if (step.opacity !== undefined) {\n el.style.opacity = String(step.opacity);\n }\n\n if (step.top !== undefined) {\n el.style.top = `${step.top}px`;\n }\n\n if (step.left !== undefined) {\n el.style.left = `${step.left}px`;\n }\n\n if (step.width !== undefined) {\n el.style.width = `${step.width}px`;\n }\n\n if (step.height !== undefined) {\n el.style.height = `${step.height}px`;\n }\n\n if (step.zIndex !== undefined) {\n if (skipTransition) {\n el.style.zIndex = String(step.zIndex);\n } else {\n const timerId = setTimeout(() => {\n el.style.zIndex = String(step.zIndex);\n }, step.transition.duration / 2);\n timers.push(timerId);\n }\n }\n\n if (step.filter !== undefined) {\n el.style.filter = step.filter;\n }\n\n if (step.rotate?.z !== undefined) {\n el.style.rotate = `${step.rotate.z}deg`;\n }\n\n if (hasCurve) {\n if (step.position?.x != null || step.position?.y != null) {\n el.style.setProperty('--tx', `${step.position.x ?? 0}px`);\n el.style.setProperty('--ty', `${step.position.y ?? 0}px`);\n }\n if (step.scale != null) {\n el.style.setProperty('--s', String(step.scale));\n }\n el.style.transform = `translate(var(--tx), var(--ty)) scale(var(--s, 1))`;\n } else {\n const transform = buildTransform(step);\n\n if (transform) {\n el.style.transform = transform;\n }\n }\n });\n\n return timers;\n};\n\nconst useTimeline = ({ steps = [], loading, immediate, loop }: Options) => {\n const [slotIndex, setSlotIndex] = useState(-1);\n const [completed, setCompleted] = useState(() => !!immediate);\n\n const ref = useRef<HTMLDivElement>(null);\n const timeline = useMemo(() => buildTimeline(steps), [steps]);\n const hasCurveSteps = useMemo(\n () => steps.some(s => s.transition.easeX || s.transition.easeY),\n [steps],\n );\n\n useLayoutEffect(() => {\n if (hasCurveSteps) {\n registerCSSProperty('--tx');\n registerCSSProperty('--ty');\n registerCSSProperty('--s', '<number>', '1');\n }\n }, [hasCurveSteps]);\n\n useLayoutEffect(() => {\n if (!immediate || !ref.current || !steps.length) {\n return;\n }\n\n const container = ref.current;\n steps.forEach(step => applyStep(container, step, true));\n }, [immediate, steps]);\n\n useEffect(() => {\n if (immediate || loading || !timeline.length) {\n return;\n }\n\n const firstDelay = timeline[0]?.[0]?.transition.delay ?? 0;\n const timerId = window.setTimeout(() => {\n requestAnimationFrame(() => setSlotIndex(0));\n }, firstDelay);\n\n return () => clearTimeout(timerId);\n }, [loading, timeline, immediate]);\n\n useEffect(() => {\n if (immediate || loading || slotIndex < 0) {\n return;\n }\n\n const isLast = slotIndex >= timeline.length - 1;\n\n if (isLast && !loop) {\n return;\n }\n\n const currentSlot = timeline[slotIndex];\n const maxDuration = Math.max(\n ...(currentSlot?.map(s => s.transition.duration) ?? [0]),\n );\n\n const nextIndex = isLast ? 0 : slotIndex + 1;\n const nextDelay = timeline[nextIndex]?.[0]?.transition.delay ?? 0;\n\n const timerId = window.setTimeout(() => {\n setSlotIndex(nextIndex);\n }, maxDuration + nextDelay);\n\n return () => clearTimeout(timerId);\n }, [loading, slotIndex, timeline, immediate, loop]);\n\n useEffect(() => {\n if (immediate || slotIndex < 0 || !ref.current) {\n return;\n }\n\n const container = ref.current;\n const timers: ReturnType<typeof setTimeout>[] = [];\n\n requestAnimationFrame(() => {\n timeline[slotIndex]?.forEach(step => {\n const stepTimers = applyStep(container, step);\n\n if (stepTimers) {\n timers.push(...stepTimers);\n }\n });\n });\n\n return () => {\n timers.forEach(clearTimeout);\n };\n }, [immediate, slotIndex, timeline]);\n\n useEffect(() => {\n if (\n immediate ||\n loop ||\n slotIndex < 0 ||\n slotIndex < timeline.length - 1 ||\n !timeline.length\n ) {\n return;\n }\n\n const lastSlot = timeline[slotIndex];\n const maxDuration = Math.max(\n ...(lastSlot?.map(s => s.transition.duration) ?? [0]),\n );\n\n const timerId = window.setTimeout(() => {\n setCompleted(true);\n }, maxDuration);\n\n return () => clearTimeout(timerId);\n }, [slotIndex, timeline, immediate, loop]);\n\n return { ref, completed };\n};\n\nexport default useTimeline;\n"],"mappings":"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"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
//#region src/hooks/useViewport/index.d.ts
|
|
2
|
+
type ViewportInfo = VisualViewport | {
|
|
3
|
+
width: number;
|
|
4
|
+
height: number;
|
|
5
|
+
offsetLeft: number;
|
|
6
|
+
offsetTop: number;
|
|
7
|
+
pageLeft: number;
|
|
8
|
+
pageTop: number;
|
|
9
|
+
scale: number;
|
|
10
|
+
};
|
|
11
|
+
interface Options {
|
|
12
|
+
isInApp?: boolean;
|
|
13
|
+
debounce?: number;
|
|
14
|
+
}
|
|
15
|
+
declare const useViewport: (options?: Options) => ViewportInfo;
|
|
16
|
+
//#endregion
|
|
17
|
+
export { useViewport };
|
|
18
|
+
//# sourceMappingURL=index.d.mts.map
|