@xwadex/fesd-next 0.3.4-7.4 → 0.3.4-7.5
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/dist/components/TestComponent.js +5 -5
- package/dist/hooks/index.d.ts +1 -0
- package/dist/hooks/index.js +1 -0
- package/dist/hooks/useAnchors.d.ts +35 -15
- package/dist/hooks/useAnchors.js +133 -116
- package/dist/hooks/useHash.d.ts +1 -0
- package/dist/hooks/useHash.js +19 -0
- package/package.json +1 -1
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useAnchors } from "../hooks/index.js";
|
|
4
|
+
import { cn } from "../utils";
|
|
4
5
|
const TestComponent = () => {
|
|
5
|
-
const
|
|
6
|
-
|
|
7
|
-
return (_jsx(_Fragment, { children: "TestComponent content" }));
|
|
6
|
+
const { registerAnchors, registerContainers, scrollToAnchor } = useAnchors();
|
|
7
|
+
return (_jsxs("div", { children: [_jsxs("div", { className: "flex flex-col gap-3 fixed top-0 right-0", children: [_jsx("button", { onClick: () => scrollToAnchor({ anchor: "#", }), children: "Scoll to Top" }), _jsx("button", { onClick: () => scrollToAnchor({ anchor: "50", }), children: "Scoll to 50" }), _jsx("button", { onClick: () => scrollToAnchor({ anchor: "100", }), children: "Scoll to 100" })] }), _jsx("h2", { className: "text-xl", children: "TestComponent content" }), _jsx("div", { className: cn("flex flex-col gap-5"), children: Array.from({ length: 200 }).map((_, index) => _jsx("div", { className: cn("p-3", "bg-indigo-600"), ...registerAnchors(String(index)), children: index }, index)) })] }));
|
|
8
8
|
};
|
|
9
9
|
TestComponent.displayName = "TestComponent";
|
|
10
10
|
export default TestComponent;
|
package/dist/hooks/index.d.ts
CHANGED
package/dist/hooks/index.js
CHANGED
|
@@ -1,29 +1,48 @@
|
|
|
1
1
|
import { Easing } from "motion/react";
|
|
2
2
|
export interface PropsTypes {
|
|
3
3
|
}
|
|
4
|
-
export interface
|
|
5
|
-
anchor?: string;
|
|
6
|
-
container: string;
|
|
7
|
-
offseters?: string | string[];
|
|
8
|
-
direction?: "horizontal" | "vertical" | "both";
|
|
4
|
+
export interface AnimationOptions {
|
|
9
5
|
duration?: number;
|
|
10
|
-
offset?: number;
|
|
11
6
|
delay?: number;
|
|
12
7
|
ease?: Easing | Easing[] | undefined;
|
|
13
8
|
onScroll?: () => void;
|
|
14
9
|
onScrolling?: (value: number) => void;
|
|
15
10
|
onScrolled?: () => void;
|
|
16
11
|
}
|
|
12
|
+
export interface AnchorActionsOptions extends AnimationOptions {
|
|
13
|
+
anchor: string;
|
|
14
|
+
container?: string;
|
|
15
|
+
offseters?: string | string[];
|
|
16
|
+
direction?: "x" | "y" | "xy";
|
|
17
|
+
align?: "start" | "center" | "end";
|
|
18
|
+
offset?: number;
|
|
19
|
+
}
|
|
20
|
+
export interface ScrollAnimationsOptions extends AnimationOptions {
|
|
21
|
+
containerDom: HTMLElement | Window;
|
|
22
|
+
scrollValue: {
|
|
23
|
+
x: number;
|
|
24
|
+
y: number;
|
|
25
|
+
};
|
|
26
|
+
containerScrollValue: {
|
|
27
|
+
left: number;
|
|
28
|
+
top: number;
|
|
29
|
+
};
|
|
30
|
+
direction?: AnchorActionsOptions["direction"];
|
|
31
|
+
}
|
|
17
32
|
export declare function useAnchors(): {
|
|
18
33
|
registerAnchors: typeof registerAnchors;
|
|
19
34
|
registerContainers: typeof registerContainers;
|
|
20
35
|
registerOffseters: typeof registerOffseters;
|
|
36
|
+
setStores: (options: SetAnchorStoreOptions) => void;
|
|
21
37
|
getStores: () => AnchorsStores;
|
|
22
|
-
removeAnchor: (name: string) => void;
|
|
23
|
-
removeOffseter: (name: string) => void;
|
|
24
|
-
removeContainer: (name: string) => void;
|
|
25
38
|
scrollToAnchor: (anchorOptions: AnchorActionsOptions) => void;
|
|
26
39
|
};
|
|
40
|
+
export interface RegistrationProps {
|
|
41
|
+
name: string;
|
|
42
|
+
key: keyof AnchorsStores;
|
|
43
|
+
node: HTMLDivElement | null;
|
|
44
|
+
}
|
|
45
|
+
export declare function registration({ name, key, node }: RegistrationProps): void;
|
|
27
46
|
export declare function registerAnchors(name: string): {
|
|
28
47
|
ref: (node: HTMLDivElement | null) => void;
|
|
29
48
|
"data-anchor": string;
|
|
@@ -43,9 +62,10 @@ export type AnchorsStores = {
|
|
|
43
62
|
};
|
|
44
63
|
export declare const useAnchorsStores: import("zustand").UseBoundStore<import("zustand").StoreApi<AnchorsStores>>;
|
|
45
64
|
export declare const getAnchorsStores: () => AnchorsStores;
|
|
46
|
-
export
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
65
|
+
export interface SetAnchorStoreOptions {
|
|
66
|
+
key: keyof AnchorsStores;
|
|
67
|
+
action: "add" | "remove";
|
|
68
|
+
name: string;
|
|
69
|
+
node?: HTMLElement | null;
|
|
70
|
+
}
|
|
71
|
+
export declare const setAnchorStore: (options: SetAnchorStoreOptions) => void;
|
package/dist/hooks/useAnchors.js
CHANGED
|
@@ -1,148 +1,165 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
// update: 2025.10.
|
|
3
|
-
// version: 0.0.1
|
|
2
|
+
// update: 2025.10.30
|
|
3
|
+
// version: 0.0.1.bate
|
|
4
4
|
// dev: wade
|
|
5
5
|
import { useCallback, useMemo, useRef } from "react";
|
|
6
6
|
import { animate, useMotionValue } from "motion/react";
|
|
7
7
|
import { create } from "zustand";
|
|
8
8
|
export function useAnchors() {
|
|
9
|
-
const
|
|
10
|
-
const
|
|
11
|
-
const
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
const horizontalOffset = Math.round(targetLeft - containerLeft + containerScrollLeft);
|
|
25
|
-
const isDirectionV = direction === "vertical" || direction === "both";
|
|
26
|
-
const isDirectionH = direction === "horizontal" || direction === "both";
|
|
27
|
-
const onUpdate = (value) => {
|
|
28
|
-
if (isDirectionV)
|
|
29
|
-
containerDom.scrollTop = value;
|
|
30
|
-
if (isDirectionH)
|
|
31
|
-
containerDom.scrollLeft = value;
|
|
32
|
-
onScrolling?.(value);
|
|
9
|
+
const motionValue = useMotionValue(0);
|
|
10
|
+
const animateRef = useRef(null);
|
|
11
|
+
const scrollAnimations = useCallback(({ containerDom, scrollValue, containerScrollValue, direction = "y", duration = 0.5, delay = 0, ease = [0.215, 0.61, 0.355, 1.0], onScroll, onScrolled, onScrolling }) => {
|
|
12
|
+
const { left: scrollLeft, top: scrollTop } = containerScrollValue;
|
|
13
|
+
const { x: scrollX, y: scrollY } = scrollValue;
|
|
14
|
+
const isScrollX = direction === "x";
|
|
15
|
+
const isScrollY = direction === "y";
|
|
16
|
+
const isBoth = direction === "xy";
|
|
17
|
+
const animationsOptions = {
|
|
18
|
+
duration,
|
|
19
|
+
ease,
|
|
20
|
+
delay,
|
|
21
|
+
onPlay: onScroll,
|
|
22
|
+
onUpdate: onUpdate,
|
|
23
|
+
onComplete: onScrolled,
|
|
33
24
|
};
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
25
|
+
if ((isBoth || isScrollX) && (scrollX !== scrollLeft)) {
|
|
26
|
+
motionValue.set(scrollLeft);
|
|
27
|
+
animateRef.current = animate(motionValue, scrollX, animationsOptions);
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
if ((isBoth || isScrollY) && (scrollY !== scrollTop)) {
|
|
31
|
+
motionValue.set(scrollTop);
|
|
32
|
+
animateRef.current = animate(motionValue, scrollY, animationsOptions);
|
|
33
|
+
return;
|
|
39
34
|
}
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
35
|
+
function onUpdate(value) {
|
|
36
|
+
if (isScrollX)
|
|
37
|
+
containerDom.scrollTo({ left: value });
|
|
38
|
+
if (isScrollY)
|
|
39
|
+
containerDom.scrollTo({ top: value });
|
|
40
|
+
if (isBoth)
|
|
41
|
+
containerDom.scrollTo({ top: value, left: value });
|
|
42
|
+
onScrolling?.(value);
|
|
43
43
|
}
|
|
44
44
|
}, []);
|
|
45
|
-
const scrollToTop = useCallback((anchorOptions) => {
|
|
46
|
-
}, [motionValueX, motionValueY]);
|
|
47
45
|
const scrollToAnchor = useCallback((anchorOptions) => {
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
return;
|
|
53
|
-
if (!anchorName || !containerName)
|
|
54
|
-
return;
|
|
46
|
+
if (animateRef.current)
|
|
47
|
+
animateRef.current?.stop();
|
|
48
|
+
const { anchor: anchorName, container: containerName, offseters, align, offset = 0, ...options } = anchorOptions;
|
|
49
|
+
const isContainer = containerName && containerName !== "window";
|
|
55
50
|
const stores = getAnchorsStores();
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
const
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
51
|
+
const containerDom = isContainer
|
|
52
|
+
? stores.containers.get(containerName)
|
|
53
|
+
: window;
|
|
54
|
+
const containerScrollValue = {
|
|
55
|
+
top: Math.round(isContainer
|
|
56
|
+
? containerDom.scrollTop
|
|
57
|
+
: containerDom.pageYOffset),
|
|
58
|
+
left: Math.round(isContainer
|
|
59
|
+
? containerDom.scrollLeft
|
|
60
|
+
: pageXOffset),
|
|
61
|
+
};
|
|
62
|
+
const offsetValue = Array.isArray(offseters)
|
|
63
|
+
? offseters.reduce((init, offseter) => {
|
|
64
|
+
const dom = stores.offseters.get(offseter);
|
|
65
|
+
const width = (init.width ?? 0) + (dom ? dom.clientWidth : 0);
|
|
66
|
+
const height = (init.height ?? 0) + (dom ? dom.clientHeight : 0);
|
|
67
|
+
return { width, height };
|
|
68
|
+
}, {})
|
|
69
|
+
: { width: 0, height: 0 };
|
|
70
|
+
const scrollValue = {
|
|
71
|
+
x: 0 - offsetValue.width,
|
|
72
|
+
y: 0 - offsetValue.height
|
|
73
|
+
};
|
|
74
|
+
const AnimationsOptions = {
|
|
75
|
+
containerDom, containerScrollValue, scrollValue, ...options
|
|
76
|
+
};
|
|
77
|
+
if (anchorName !== "#") {
|
|
78
|
+
const anchorDom = stores.anchors.get(anchorName);
|
|
79
|
+
if (!anchorDom)
|
|
80
|
+
return;
|
|
81
|
+
const anchorRect = anchorDom.getBoundingClientRect();
|
|
82
|
+
const containerRect = {
|
|
83
|
+
left: (isContainer ? containerDom.getBoundingClientRect().left : 0),
|
|
84
|
+
top: (isContainer ? containerDom.getBoundingClientRect().top : 0)
|
|
85
|
+
};
|
|
86
|
+
const offsets = {
|
|
87
|
+
x: offsetValue.width - offset,
|
|
88
|
+
y: offsetValue.height - offset
|
|
89
|
+
};
|
|
90
|
+
AnimationsOptions.scrollValue = {
|
|
91
|
+
x: Math.round(anchorRect.left + containerScrollValue.left - offsets.x - containerRect.left),
|
|
92
|
+
y: Math.round(anchorRect.top + containerScrollValue.top - offsets.y - containerRect.top),
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
scrollAnimations(AnimationsOptions);
|
|
96
|
+
}, []);
|
|
64
97
|
const returnsMemo = useMemo(() => ({
|
|
65
98
|
registerAnchors,
|
|
66
99
|
registerContainers,
|
|
67
100
|
registerOffseters,
|
|
101
|
+
setStores: setAnchorStore,
|
|
68
102
|
getStores: getAnchorsStores,
|
|
69
|
-
removeAnchor: removeAnchor,
|
|
70
|
-
removeOffseter: removeOffseter,
|
|
71
|
-
removeContainer: removeContainer,
|
|
72
103
|
scrollToAnchor
|
|
73
104
|
}), [scrollToAnchor]);
|
|
74
105
|
return returnsMemo;
|
|
75
106
|
}
|
|
107
|
+
export function registration({ name, key, node }) {
|
|
108
|
+
if (!node)
|
|
109
|
+
return;
|
|
110
|
+
if (name == "#") {
|
|
111
|
+
console.error(`Cannot register ${key} name '#'`);
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
if (node instanceof Window || node.tagName === "BODY" || node.tagName === "HTML") {
|
|
115
|
+
console.error(`Cannot register ${key} 'body' or 'html' `);
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
setAnchorStore({ key, name, node, action: node ? "add" : "remove" });
|
|
119
|
+
}
|
|
120
|
+
const ANCHOR_ATTRIBUTE = "data-anchor";
|
|
121
|
+
const CONTAINER_ATTRIBUTE = "data-anchor-container";
|
|
122
|
+
const OFFSETER_ATTRIBUTE = "data-anchor-offseter";
|
|
76
123
|
export function registerAnchors(name) {
|
|
77
|
-
const register = (node) => {
|
|
78
|
-
|
|
79
|
-
return console.error("Cannot register anchor name '#'");
|
|
80
|
-
if (node)
|
|
81
|
-
addAnchor(name, node);
|
|
82
|
-
else
|
|
83
|
-
removeAnchor(name);
|
|
84
|
-
};
|
|
85
|
-
return {
|
|
86
|
-
ref: register,
|
|
87
|
-
"data-anchor": name
|
|
88
|
-
};
|
|
124
|
+
const register = (node) => registration({ key: "anchors", name, node });
|
|
125
|
+
return { ref: register, [ANCHOR_ATTRIBUTE]: name };
|
|
89
126
|
}
|
|
90
127
|
export function registerContainers(name) {
|
|
91
|
-
const register = (node) => {
|
|
92
|
-
|
|
93
|
-
return console.error("Cannot register anchor container name 'window'");
|
|
94
|
-
if (!node || node instanceof Window || node.tagName === "BODY" || node.tagName === "HTML")
|
|
95
|
-
return;
|
|
96
|
-
if (node)
|
|
97
|
-
addContainer(name, node);
|
|
98
|
-
else
|
|
99
|
-
removeContainer(name);
|
|
100
|
-
};
|
|
101
|
-
return {
|
|
102
|
-
ref: register,
|
|
103
|
-
"data-anchor-container": name
|
|
104
|
-
};
|
|
128
|
+
const register = (node) => registration({ key: "containers", name, node });
|
|
129
|
+
return { ref: register, [CONTAINER_ATTRIBUTE]: name };
|
|
105
130
|
}
|
|
106
131
|
export function registerOffseters(name) {
|
|
107
|
-
const register = (node) => {
|
|
108
|
-
|
|
109
|
-
addOffseter(name, node);
|
|
110
|
-
else
|
|
111
|
-
removeOffseter(name);
|
|
112
|
-
};
|
|
113
|
-
return {
|
|
114
|
-
ref: register,
|
|
115
|
-
"data-anchor-offseter": name
|
|
116
|
-
};
|
|
132
|
+
const register = (node) => registration({ key: "offseters", name, node });
|
|
133
|
+
return { ref: register, [OFFSETER_ATTRIBUTE]: name };
|
|
117
134
|
}
|
|
118
|
-
const
|
|
135
|
+
export const useAnchorsStores = create()(() => ({
|
|
119
136
|
anchors: new Map(),
|
|
120
137
|
containers: new Map(),
|
|
121
138
|
offseters: new Map(),
|
|
122
|
-
};
|
|
123
|
-
export const useAnchorsStores = create()(() => anchorsStores);
|
|
139
|
+
}));
|
|
124
140
|
export const getAnchorsStores = () => useAnchorsStores.getState();
|
|
125
|
-
export const
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
141
|
+
export const setAnchorStore = (options) => useAnchorsStores.setState(stores => {
|
|
142
|
+
const { key, action, name, node } = options;
|
|
143
|
+
switch (key) {
|
|
144
|
+
case "anchors":
|
|
145
|
+
if (action == "add" && node instanceof HTMLElement)
|
|
146
|
+
stores.anchors?.set(name, node);
|
|
147
|
+
if (action == "remove")
|
|
148
|
+
stores.anchors?.delete(name);
|
|
149
|
+
return { anchors: stores.anchors };
|
|
150
|
+
case "containers":
|
|
151
|
+
if (action == "add" && node instanceof HTMLElement)
|
|
152
|
+
stores.containers?.set(name, node);
|
|
153
|
+
if (action == "remove")
|
|
154
|
+
stores.containers?.delete(name);
|
|
155
|
+
return { containers: stores.containers };
|
|
156
|
+
case "offseters":
|
|
157
|
+
if (action == "add" && node instanceof HTMLElement)
|
|
158
|
+
stores.offseters?.set(name, node);
|
|
159
|
+
if (action == "remove")
|
|
160
|
+
stores.offseters?.delete(name);
|
|
161
|
+
return { offseters: stores.offseters };
|
|
162
|
+
default:
|
|
163
|
+
return stores;
|
|
164
|
+
}
|
|
148
165
|
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function useHash(): string;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { useEffect, useState } from "react";
|
|
3
|
+
export function useHash() {
|
|
4
|
+
const [hash, setHash] = useState("");
|
|
5
|
+
const updateHash = () => setHash(window.location.hash);
|
|
6
|
+
const hashchangeEvent = (e) => {
|
|
7
|
+
e.preventDefault?.();
|
|
8
|
+
updateHash();
|
|
9
|
+
};
|
|
10
|
+
// 初始化
|
|
11
|
+
useEffect(() => {
|
|
12
|
+
updateHash();
|
|
13
|
+
window.addEventListener("hashchange", hashchangeEvent);
|
|
14
|
+
return () => {
|
|
15
|
+
window.removeEventListener("hashchange", hashchangeEvent);
|
|
16
|
+
};
|
|
17
|
+
}, []);
|
|
18
|
+
return hash;
|
|
19
|
+
}
|