@xwadex/fesd-next 0.3.4-7.3 → 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.
@@ -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;
@@ -7,3 +7,5 @@ export * from "./useMounted";
7
7
  export * from "./useEffectOne";
8
8
  export * from "./useEffectLeave";
9
9
  export * from "./useAnchors";
10
+ export * from "./useIsMobile";
11
+ export * from "./useHash";
@@ -7,3 +7,5 @@ export * from "./useMounted";
7
7
  export * from "./useEffectOne";
8
8
  export * from "./useEffectLeave";
9
9
  export * from "./useAnchors";
10
+ export * from "./useIsMobile";
11
+ export * from "./useHash";
@@ -1,41 +1,60 @@
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";
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
- scrollToAnchor: ({ anchor: anchorName, container: containerName, ...anchorOptions }: AnchorActionsOptions) => void;
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;
30
49
  };
31
- export declare function registerOffseters(name: string): {
32
- ref: (node: HTMLDivElement | null) => void;
33
- "data-anchor-offseter": string;
34
- };
35
50
  export declare function registerContainers(name: string): {
36
51
  ref: (node: HTMLDivElement | null) => void;
37
52
  "data-anchor-container": string;
38
53
  };
54
+ export declare function registerOffseters(name: string): {
55
+ ref: (node: HTMLDivElement | null) => void;
56
+ "data-anchor-offseter": string;
57
+ };
39
58
  export type AnchorsStores = {
40
59
  anchors: Map<string, HTMLElement | null>;
41
60
  containers: Map<string, HTMLElement | null>;
@@ -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 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 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;
@@ -1,122 +1,165 @@
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
9
  const motionValue = useMotionValue(0);
10
- const controlsRef = useRef(null);
11
- const scrollToAnchor = useCallback(({ anchor: anchorName, container: containerName, ...anchorOptions }) => {
12
- const { offseters, direction = "vertical", onScroll, onScrolling, onScrolled, ...options } = anchorOptions;
13
- if (!anchorName || !containerName)
14
- return;
15
- const stores = getAnchorsStores();
16
- if (!stores?.anchors || !stores?.containers)
17
- return;
18
- const anchorDom = stores.anchors.get(anchorName);
19
- const containerDom = stores.containers.get(containerName);
20
- if (!anchorDom || !containerDom)
21
- return;
22
- const { top: targetTop } = anchorDom.getBoundingClientRect();
23
- const { top: containerTop } = containerDom.getBoundingClientRect();
24
- const containerScrollTop = Math.round(containerDom.scrollTop);
25
- const scrollOffset = Math.round(targetTop - containerTop + containerScrollTop);
26
- if (scrollOffset == containerScrollTop)
27
- return;
28
- if (controlsRef.current)
29
- controlsRef.current?.stop();
30
- // console.log("scrollOffset", scrollOffset);
31
- // console.log("containerScrollTop", containerScrollTop);
32
- // console.log(scrollOffset == containerScrollTop);
33
- motionValue.set(containerScrollTop);
34
- controlsRef.current = animate(motionValue, scrollOffset, {
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,
35
21
  onPlay: onScroll,
22
+ onUpdate: onUpdate,
36
23
  onComplete: onScrolled,
37
- onUpdate: (value) => {
38
- containerDom.scrollTop = value;
39
- onScrolling?.(value);
40
- },
41
- ...options,
42
- });
43
- }, [motionValue]);
24
+ };
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;
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
+ }
44
+ }, []);
45
+ const scrollToAnchor = useCallback((anchorOptions) => {
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";
50
+ const stores = getAnchorsStores();
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
+ }, []);
44
97
  const returnsMemo = useMemo(() => ({
45
98
  registerAnchors,
46
99
  registerContainers,
47
100
  registerOffseters,
101
+ setStores: setAnchorStore,
48
102
  getStores: getAnchorsStores,
49
- removeAnchor: removeAnchor,
50
- removeOffseter: removeOffseter,
51
- removeContainer: removeContainer,
52
103
  scrollToAnchor
53
104
  }), [scrollToAnchor]);
54
105
  return returnsMemo;
55
106
  }
56
- export function registerAnchors(name) {
57
- const register = (node) => {
58
- if (node)
59
- addAnchor(name, node);
60
- else
61
- removeAnchor(name);
62
- };
63
- return {
64
- ref: register,
65
- "data-anchor": name
66
- };
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" });
67
119
  }
68
- export function registerOffseters(name) {
69
- const register = (node) => {
70
- if (node)
71
- addOffseter(name, node);
72
- else
73
- removeOffseter(name);
74
- };
75
- return {
76
- ref: register,
77
- "data-anchor-offseter": name
78
- };
120
+ const ANCHOR_ATTRIBUTE = "data-anchor";
121
+ const CONTAINER_ATTRIBUTE = "data-anchor-container";
122
+ const OFFSETER_ATTRIBUTE = "data-anchor-offseter";
123
+ export function registerAnchors(name) {
124
+ const register = (node) => registration({ key: "anchors", name, node });
125
+ return { ref: register, [ANCHOR_ATTRIBUTE]: name };
79
126
  }
80
127
  export function registerContainers(name) {
81
- const register = (node) => {
82
- if (node)
83
- addContainer(name, node);
84
- else
85
- removeContainer(name);
86
- };
87
- return {
88
- ref: register,
89
- "data-anchor-container": name
90
- };
128
+ const register = (node) => registration({ key: "containers", name, node });
129
+ return { ref: register, [CONTAINER_ATTRIBUTE]: name };
91
130
  }
92
- const anchorsStores = {
131
+ export function registerOffseters(name) {
132
+ const register = (node) => registration({ key: "offseters", name, node });
133
+ return { ref: register, [OFFSETER_ATTRIBUTE]: name };
134
+ }
135
+ export const useAnchorsStores = create()(() => ({
93
136
  anchors: new Map(),
94
137
  containers: new Map(),
95
138
  offseters: new Map(),
96
- };
97
- export const useAnchorsStores = create()(() => anchorsStores);
139
+ }));
98
140
  export const getAnchorsStores = () => useAnchorsStores.getState();
99
- export const addAnchor = (name, anchorNode) => useAnchorsStores.setState(stores => {
100
- stores.anchors?.set(name, anchorNode);
101
- return { anchors: stores.anchors };
102
- });
103
- export const removeAnchor = (name) => useAnchorsStores.setState(stores => {
104
- stores.anchors?.delete(name);
105
- return { anchors: stores.anchors };
106
- });
107
- export const addContainer = (name, containerNode) => useAnchorsStores.setState(stores => {
108
- stores.containers?.set(name, containerNode);
109
- return { containers: stores.containers };
110
- });
111
- export const removeContainer = (name) => useAnchorsStores.setState(stores => {
112
- stores.containers?.delete(name);
113
- return { containers: stores.containers };
114
- });
115
- export const addOffseter = (name, containerNode) => useAnchorsStores.setState(stores => {
116
- stores.offseters?.set(name, containerNode);
117
- return { offseters: stores.offseters };
118
- });
119
- export const removeOffseter = (name) => useAnchorsStores.setState(stores => {
120
- stores.offseters?.delete(name);
121
- return { offseters: stores.offseters };
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
+ }
122
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
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xwadex/fesd-next",
3
- "version": "0.3.4-7.3",
3
+ "version": "0.3.4-7.5",
4
4
  "private": false,
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",