@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.
@@ -1,10 +1,10 @@
1
1
  "use client";
2
- import { Fragment as _Fragment, jsx as _jsx } from "react/jsx-runtime";
3
- import { useCookies } from "../hooks/index.js";
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 cookie = useCookies();
6
- console.log(cookie);
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;
@@ -8,3 +8,4 @@ export * from "./useEffectOne";
8
8
  export * from "./useEffectLeave";
9
9
  export * from "./useAnchors";
10
10
  export * from "./useIsMobile";
11
+ export * from "./useHash";
@@ -8,3 +8,4 @@ export * from "./useEffectOne";
8
8
  export * from "./useEffectLeave";
9
9
  export * from "./useAnchors";
10
10
  export * from "./useIsMobile";
11
+ export * from "./useHash";
@@ -1,29 +1,48 @@
1
1
  import { Easing } from "motion/react";
2
2
  export interface PropsTypes {
3
3
  }
4
- export interface AnchorActionsOptions {
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
- removeAnchor: (name: string) => void;
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 type AnchorsStores = {
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 declare const addAnchor: (name: string, anchorNode: HTMLElement | null) => void;
47
- export declare const removeAnchor: (name: string) => void;
48
- export declare const addContainer: (name: string, containerNode: HTMLElement | null) => void;
49
- export declare const removeContainer: (name: string) => void;
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;
@@ -1,148 +1,167 @@
1
1
  "use client";
2
- // update: 2025.10.29
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 motionValueX = useMotionValue(0);
10
- const motionValueY = useMotionValue(0);
11
- const animate_x_Ref = useRef(null);
12
- const animate_y_Ref = useRef(null);
13
- const scrollAnimation = useCallback(({ anchorDom, containerDom, options }) => {
14
- if (animate_x_Ref.current)
15
- animate_x_Ref.current?.stop();
16
- if (animate_y_Ref.current)
17
- animate_y_Ref.current?.stop();
18
- const { direction = "vertical", duration = 0.5, ease = [0.215, 0.61, 0.355, 1.0], delay = 0, onScroll: onPlay, onScrolled: onScrolled, onScrolling, } = options;
19
- const { top: targetTop, left: targetLeft } = anchorDom.getBoundingClientRect();
20
- const { top: containerTop, left: containerLeft } = containerDom.getBoundingClientRect();
21
- const containerScrollTop = Math.round(containerDom.scrollTop);
22
- const containerScrollLeft = Math.round(containerDom.scrollLeft);
23
- const verticalOffset = Math.round(targetTop - containerTop + containerScrollTop);
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
- const motionOptions = { duration, ease, delay, onPlay, onScrolled, onUpdate };
35
- if (verticalOffset !== containerScrollTop && isDirectionV) {
36
- console.log("direction", direction);
37
- motionValueY.set(containerScrollTop);
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 (horizontalOffset !== containerScrollLeft && isDirectionH) {
41
- motionValueX.set(containerScrollLeft);
42
- animate_y_Ref.current = animate(motionValueY, horizontalOffset, motionOptions);
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
- const { anchor: anchorName, container: containerName, offseters, ...options } = anchorOptions;
49
- console.log(options);
50
- // if (anchorName == "#") return scrollToTop(options)
51
- if (containerName == "window")
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
- if (!stores?.anchors || !stores?.containers)
57
- return;
58
- const anchorDom = stores.anchors.get(anchorName);
59
- const containerDom = stores.containers.get(containerName);
60
- if (!anchorDom || !containerDom)
61
- return;
62
- scrollAnimation({ anchorDom, containerDom, options });
63
- }, [motionValueX, motionValueY]);
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
- if (name == "#")
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
- if (name == "window")
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
- if (node)
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 anchorsStores = {
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 addAnchor = (name, anchorNode) => useAnchorsStores.setState(stores => {
126
- stores.anchors?.set(name, anchorNode);
127
- return { anchors: stores.anchors };
128
- });
129
- export const removeAnchor = (name) => useAnchorsStores.setState(stores => {
130
- stores.anchors?.delete(name);
131
- return { anchors: stores.anchors };
132
- });
133
- export const addContainer = (name, containerNode) => useAnchorsStores.setState(stores => {
134
- stores.containers?.set(name, containerNode);
135
- return { containers: stores.containers };
136
- });
137
- export const removeContainer = (name) => useAnchorsStores.setState(stores => {
138
- stores.containers?.delete(name);
139
- return { containers: stores.containers };
140
- });
141
- export const addOffseter = (name, containerNode) => useAnchorsStores.setState(stores => {
142
- stores.offseters?.set(name, containerNode);
143
- return { offseters: stores.offseters };
144
- });
145
- export const removeOffseter = (name) => useAnchorsStores.setState(stores => {
146
- stores.offseters?.delete(name);
147
- return { offseters: stores.offseters };
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
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xwadex/fesd-next",
3
- "version": "0.3.4-7.4",
3
+ "version": "0.3.4-7.6",
4
4
  "private": false,
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",