@xwadex/fesd-next 0.3.4-7.4 → 0.3.4-7.6
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 -18
- package/dist/hooks/useAnchors.js +136 -117
- 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 AnimateOptions {
|
|
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 AnchorOptions extends AnimateOptions {
|
|
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 ScrollOptions extends AnimateOptions {
|
|
21
|
+
containerDom: HTMLElement | Window;
|
|
22
|
+
scrollValue: {
|
|
23
|
+
x: number;
|
|
24
|
+
y: number;
|
|
25
|
+
};
|
|
26
|
+
containerScrollValue: {
|
|
27
|
+
left: number;
|
|
28
|
+
top: number;
|
|
29
|
+
};
|
|
30
|
+
direction?: AnchorOptions["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: (states: AnchorStoreStates) => void;
|
|
21
37
|
getStores: () => AnchorsStores;
|
|
22
|
-
|
|
23
|
-
removeOffseter: (name: string) => void;
|
|
24
|
-
removeContainer: (name: string) => void;
|
|
25
|
-
scrollToAnchor: (anchorOptions: AnchorActionsOptions) => void;
|
|
38
|
+
scrollToAnchor: (anchorOptions: AnchorOptions) => void;
|
|
26
39
|
};
|
|
40
|
+
export interface RegistrationDatas {
|
|
41
|
+
key: keyof AnchorsStores;
|
|
42
|
+
name: string;
|
|
43
|
+
node: HTMLDivElement | null;
|
|
44
|
+
}
|
|
45
|
+
export declare function registration({ name, key, node }: RegistrationDatas): void;
|
|
27
46
|
export declare function registerAnchors(name: string): {
|
|
28
47
|
ref: (node: HTMLDivElement | null) => void;
|
|
29
48
|
"data-anchor": string;
|
|
@@ -36,16 +55,14 @@ export declare function registerOffseters(name: string): {
|
|
|
36
55
|
ref: (node: HTMLDivElement | null) => void;
|
|
37
56
|
"data-anchor-offseter": string;
|
|
38
57
|
};
|
|
39
|
-
export
|
|
58
|
+
export interface AnchorsStores {
|
|
40
59
|
anchors: Map<string, HTMLElement | null>;
|
|
41
60
|
containers: Map<string, HTMLElement | null>;
|
|
42
61
|
offseters: Map<string, HTMLElement | null>;
|
|
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
|
-
export declare const
|
|
50
|
-
export declare const addOffseter: (name: string, containerNode: HTMLElement | null) => void;
|
|
51
|
-
export declare const removeOffseter: (name: string) => void;
|
|
65
|
+
export interface AnchorStoreStates extends RegistrationDatas {
|
|
66
|
+
action: "add" | "remove";
|
|
67
|
+
}
|
|
68
|
+
export declare const setAnchorStore: (states: AnchorStoreStates) => void;
|
package/dist/hooks/useAnchors.js
CHANGED
|
@@ -1,148 +1,167 @@
|
|
|
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
|
-
animate_y_Ref.current = animate(motionValueY, verticalOffset, motionOptions);
|
|
25
|
+
if ((isBoth || isScrollX) && (scrollX !== scrollLeft)) {
|
|
26
|
+
motionValue.set(scrollLeft);
|
|
27
|
+
animateRef.current = animate(motionValue, scrollX, animationsOptions);
|
|
28
|
+
return;
|
|
39
29
|
}
|
|
40
|
-
if (
|
|
41
|
-
|
|
42
|
-
|
|
30
|
+
if ((isBoth || isScrollY) && (scrollY !== scrollTop)) {
|
|
31
|
+
motionValue.set(scrollTop);
|
|
32
|
+
animateRef.current = animate(motionValue, scrollY, animationsOptions);
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
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
|
-
|
|
148
|
-
|
|
141
|
+
export const setAnchorStore = (states) => {
|
|
142
|
+
const { key, action, name, node } = states;
|
|
143
|
+
useAnchorsStores.setState(stores => {
|
|
144
|
+
switch (key) {
|
|
145
|
+
case "anchors":
|
|
146
|
+
if (action == "add" && node instanceof HTMLElement)
|
|
147
|
+
stores.anchors?.set(name, node);
|
|
148
|
+
if (action == "remove")
|
|
149
|
+
stores.anchors?.delete(name);
|
|
150
|
+
return { anchors: stores.anchors };
|
|
151
|
+
case "containers":
|
|
152
|
+
if (action == "add" && node instanceof HTMLElement)
|
|
153
|
+
stores.containers?.set(name, node);
|
|
154
|
+
if (action == "remove")
|
|
155
|
+
stores.containers?.delete(name);
|
|
156
|
+
return { containers: stores.containers };
|
|
157
|
+
case "offseters":
|
|
158
|
+
if (action == "add" && node instanceof HTMLElement)
|
|
159
|
+
stores.offseters?.set(name, node);
|
|
160
|
+
if (action == "remove")
|
|
161
|
+
stores.offseters?.delete(name);
|
|
162
|
+
return { offseters: stores.offseters };
|
|
163
|
+
default:
|
|
164
|
+
return stores;
|
|
165
|
+
}
|
|
166
|
+
});
|
|
167
|
+
};
|
|
@@ -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
|
+
}
|