@zuzjs/ui 0.9.5 → 0.9.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/cjs/comps/Chart/index.d.ts +8 -0
- package/dist/cjs/comps/Chart/index.js +66 -0
- package/dist/cjs/comps/Chart/types.d.ts +10 -0
- package/dist/cjs/comps/Chart/types.js +4 -0
- package/dist/cjs/comps/Form/index.js +7 -3
- package/dist/cjs/comps/Password/index.d.ts +1 -1
- package/dist/cjs/comps/Select/index.d.ts +1 -0
- package/dist/cjs/comps/Select/index.js +10 -5
- package/dist/cjs/comps/Select/types.d.ts +4 -0
- package/dist/cjs/comps/TextWheel/index.d.ts +2 -0
- package/dist/cjs/comps/TextWheel/index.js +24 -12
- package/dist/cjs/comps/TextWheel/types.d.ts +2 -0
- package/dist/cjs/comps/index.d.ts +2 -0
- package/dist/cjs/comps/index.js +2 -0
- package/dist/cjs/hooks/index.d.ts +2 -0
- package/dist/cjs/hooks/index.js +2 -0
- package/dist/cjs/hooks/useLineChart.d.ts +25 -0
- package/dist/cjs/hooks/useLineChart.js +86 -0
- package/dist/cjs/hooks/usePosition.d.ts +12 -0
- package/dist/cjs/hooks/usePosition.js +191 -0
- package/dist/cjs/hooks/useResizeObserver.d.ts +1 -1
- package/dist/cjs/hooks/useResizeObserver.js +5 -4
- package/dist/cjs/hooks/useScrollbar.js +115 -70
- package/dist/cjs/types/enums.d.ts +2 -0
- package/dist/cjs/types/enums.js +2 -0
- package/dist/cjs/types/index.d.ts +2 -1
- package/dist/esm/comps/Chart/index.d.ts +8 -0
- package/dist/esm/comps/Chart/index.js +66 -0
- package/dist/esm/comps/Chart/types.d.ts +10 -0
- package/dist/esm/comps/Chart/types.js +4 -0
- package/dist/esm/comps/Form/index.js +7 -3
- package/dist/esm/comps/Password/index.d.ts +1 -1
- package/dist/esm/comps/Select/index.d.ts +1 -0
- package/dist/esm/comps/Select/index.js +10 -5
- package/dist/esm/comps/Select/types.d.ts +4 -0
- package/dist/esm/comps/TextWheel/index.d.ts +2 -0
- package/dist/esm/comps/TextWheel/index.js +24 -12
- package/dist/esm/comps/TextWheel/types.d.ts +2 -0
- package/dist/esm/comps/index.d.ts +2 -0
- package/dist/esm/comps/index.js +2 -0
- package/dist/esm/hooks/index.d.ts +2 -0
- package/dist/esm/hooks/index.js +2 -0
- package/dist/esm/hooks/useLineChart.d.ts +25 -0
- package/dist/esm/hooks/useLineChart.js +86 -0
- package/dist/esm/hooks/usePosition.d.ts +12 -0
- package/dist/esm/hooks/usePosition.js +191 -0
- package/dist/esm/hooks/useResizeObserver.d.ts +1 -1
- package/dist/esm/hooks/useResizeObserver.js +5 -4
- package/dist/esm/hooks/useScrollbar.js +115 -70
- package/dist/esm/types/enums.d.ts +2 -0
- package/dist/esm/types/enums.js +2 -0
- package/dist/esm/types/index.d.ts +2 -1
- package/dist/tsconfig.esm.tsbuildinfo +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +2 -2
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
import { useCallback, useEffect, useRef } from "react"; // Added useRef for internal state if needed
|
|
2
|
+
export const usePosition = (ref, // The element to be positioned
|
|
3
|
+
options = {}) => {
|
|
4
|
+
const { offset = 8, direction = 'bottom', container = null, // Can be a specific parent container
|
|
5
|
+
triggerRef = null, // If the trigger is not the immediate parent
|
|
6
|
+
} = options;
|
|
7
|
+
// Internal state to store the applied position type
|
|
8
|
+
const appliedPositionRef = useRef(null);
|
|
9
|
+
const calculate = useCallback(() => {
|
|
10
|
+
const el = ref.current;
|
|
11
|
+
if (!el)
|
|
12
|
+
return;
|
|
13
|
+
// Determine the trigger element
|
|
14
|
+
const trigger = triggerRef?.current || el.parentElement;
|
|
15
|
+
if (!trigger) {
|
|
16
|
+
console.warn("usePosition: No trigger element found. Positioning may not work.");
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
const rect = el.getBoundingClientRect(); // Bounding rect of the element to position
|
|
20
|
+
const triggerRect = trigger.getBoundingClientRect(); // Bounding rect of the trigger
|
|
21
|
+
// --- Determine the effective positioning context ---
|
|
22
|
+
let parentRect;
|
|
23
|
+
let targetPosition;
|
|
24
|
+
if (container) {
|
|
25
|
+
// If a specific container is provided, we assume 'absolute' positioning relative to it
|
|
26
|
+
// The container MUST be positioned (relative, absolute, fixed, sticky) for this to work correctly.
|
|
27
|
+
parentRect = container.getBoundingClientRect();
|
|
28
|
+
targetPosition = 'absolute';
|
|
29
|
+
}
|
|
30
|
+
else {
|
|
31
|
+
// No specific container provided. We need to decide if fixed or absolute to the viewport/document.
|
|
32
|
+
// Heuristic: If the trigger itself (or its ancestors) has 'fixed' position,
|
|
33
|
+
// or if the trigger is very close to the viewport edge,
|
|
34
|
+
// and/or if we want the positioned element to stay visible during scroll,
|
|
35
|
+
// we might prefer 'fixed'. Otherwise, 'absolute' is the default.
|
|
36
|
+
// A simple check: if the element is meant to stay in view regardless of scroll, 'fixed' is better.
|
|
37
|
+
// Otherwise, if it should scroll with the document, 'absolute' relative to the document.
|
|
38
|
+
// Let's default to 'fixed' for viewport-relative behavior if no container is provided,
|
|
39
|
+
// as this is often desired for elements like tooltips/dropdowns that follow the viewport.
|
|
40
|
+
// If the user *wants* it to scroll with content when no explicit container, they should set
|
|
41
|
+
// `position: absolute` and its parent *will* determine its containing block.
|
|
42
|
+
// For automatic calculation *without* explicitly setting parent position:
|
|
43
|
+
// If the `el`'s *computed* position is already `fixed`, keep it `fixed`.
|
|
44
|
+
// Otherwise, we will explicitly set it to `absolute` and position relative to the document.
|
|
45
|
+
const computedElStyle = window.getComputedStyle(el);
|
|
46
|
+
if (computedElStyle.position === 'fixed') {
|
|
47
|
+
targetPosition = 'fixed';
|
|
48
|
+
}
|
|
49
|
+
else {
|
|
50
|
+
// If not fixed, we will try to make it absolute relative to the document body
|
|
51
|
+
// (which effectively means relative to the viewport if no parent is positioned).
|
|
52
|
+
// We'll set el.style.position = 'absolute'
|
|
53
|
+
targetPosition = 'absolute';
|
|
54
|
+
}
|
|
55
|
+
// For 'fixed' positioning, the parentRect is the viewport
|
|
56
|
+
if (targetPosition === 'fixed') {
|
|
57
|
+
parentRect = {
|
|
58
|
+
top: 0,
|
|
59
|
+
left: 0,
|
|
60
|
+
right: window.innerWidth,
|
|
61
|
+
bottom: window.innerHeight,
|
|
62
|
+
width: window.innerWidth,
|
|
63
|
+
height: window.innerHeight,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
else { // targetPosition === 'absolute'
|
|
67
|
+
// For 'absolute' positioning without a specified container,
|
|
68
|
+
// it implicitly positions relative to the initial containing block (viewport/document).
|
|
69
|
+
// So, the coordinates will still be viewport-relative.
|
|
70
|
+
// We need to ensure 'el' is actually positioned absolute.
|
|
71
|
+
parentRect = {
|
|
72
|
+
top: 0,
|
|
73
|
+
left: 0,
|
|
74
|
+
right: window.innerWidth,
|
|
75
|
+
bottom: window.innerHeight,
|
|
76
|
+
width: window.innerWidth,
|
|
77
|
+
height: window.innerHeight,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
// Apply the determined position style if it's different from current
|
|
82
|
+
if (el.style.position !== targetPosition) {
|
|
83
|
+
el.style.position = targetPosition;
|
|
84
|
+
appliedPositionRef.current = targetPosition; // Store what we applied
|
|
85
|
+
}
|
|
86
|
+
else {
|
|
87
|
+
appliedPositionRef.current = targetPosition; // Confirm current state
|
|
88
|
+
}
|
|
89
|
+
let top = 0;
|
|
90
|
+
let left = 0;
|
|
91
|
+
// Calculate position relative to the viewport for initial placement
|
|
92
|
+
// These are always viewport coordinates, even if `targetPosition` becomes `absolute`
|
|
93
|
+
// We'll adjust based on `targetPosition` when applying to `el.style`
|
|
94
|
+
let viewportTop = 0;
|
|
95
|
+
let viewportLeft = 0;
|
|
96
|
+
// Determine base top/left by direction
|
|
97
|
+
switch (direction) {
|
|
98
|
+
case 'top':
|
|
99
|
+
viewportTop = triggerRect.top - rect.height - offset;
|
|
100
|
+
viewportLeft = triggerRect.left;
|
|
101
|
+
// Flipping logic (if it goes off-screen in the current direction)
|
|
102
|
+
if (viewportTop < parentRect.top && triggerRect.bottom + rect.height + offset <= parentRect.bottom) {
|
|
103
|
+
viewportTop = triggerRect.bottom + offset;
|
|
104
|
+
}
|
|
105
|
+
break;
|
|
106
|
+
case 'bottom':
|
|
107
|
+
viewportTop = triggerRect.bottom + offset;
|
|
108
|
+
viewportLeft = triggerRect.left;
|
|
109
|
+
// Flipping logic
|
|
110
|
+
if (viewportTop + rect.height > parentRect.bottom && triggerRect.top - rect.height - offset >= parentRect.top) {
|
|
111
|
+
viewportTop = triggerRect.top - rect.height - offset;
|
|
112
|
+
}
|
|
113
|
+
break;
|
|
114
|
+
case 'left':
|
|
115
|
+
viewportTop = triggerRect.top;
|
|
116
|
+
viewportLeft = triggerRect.left - rect.width - offset;
|
|
117
|
+
// Flipping logic
|
|
118
|
+
if (viewportLeft < parentRect.left && triggerRect.right + rect.width + offset <= parentRect.right) {
|
|
119
|
+
viewportLeft = triggerRect.right + offset;
|
|
120
|
+
}
|
|
121
|
+
break;
|
|
122
|
+
case 'right':
|
|
123
|
+
viewportTop = triggerRect.top;
|
|
124
|
+
viewportLeft = triggerRect.right + offset;
|
|
125
|
+
// Flipping logic
|
|
126
|
+
if (viewportLeft + rect.width > parentRect.right && triggerRect.left - rect.width - offset >= parentRect.left) {
|
|
127
|
+
viewportLeft = triggerRect.left - rect.width - offset;
|
|
128
|
+
}
|
|
129
|
+
break;
|
|
130
|
+
}
|
|
131
|
+
// Clamp to parent/viewport boundaries *after* potential flipping
|
|
132
|
+
viewportTop = Math.max(parentRect.top, Math.min(viewportTop, parentRect.bottom - rect.height));
|
|
133
|
+
viewportLeft = Math.max(parentRect.left, Math.min(viewportLeft, parentRect.right - rect.width));
|
|
134
|
+
// Apply coordinates based on the determined `targetPosition`
|
|
135
|
+
if (appliedPositionRef.current === 'fixed') {
|
|
136
|
+
// If fixed, apply viewport coordinates directly
|
|
137
|
+
el.style.top = `${viewportTop}px`;
|
|
138
|
+
el.style.left = `${viewportLeft}px`;
|
|
139
|
+
}
|
|
140
|
+
else { // 'absolute'
|
|
141
|
+
// If absolute, coordinates need to be relative to the closest positioned ancestor (offsetParent)
|
|
142
|
+
// If there's no positioned ancestor, offsetParent is body/html, so it's effectively viewport relative
|
|
143
|
+
// If there *is* a positioned ancestor, we need to subtract its position.
|
|
144
|
+
// Get the actual offset parent's bounding rect
|
|
145
|
+
let offsetParentRect;
|
|
146
|
+
if (el.offsetParent) {
|
|
147
|
+
offsetParentRect = el.offsetParent.getBoundingClientRect();
|
|
148
|
+
}
|
|
149
|
+
else {
|
|
150
|
+
// If no offsetParent, it implies relative to initial containing block (viewport)
|
|
151
|
+
offsetParentRect = { top: 0, left: 0 };
|
|
152
|
+
}
|
|
153
|
+
// Calculate relative coordinates
|
|
154
|
+
const finalTop = viewportTop - offsetParentRect.top + window.scrollY; // Add scrollY for document-relative `top`
|
|
155
|
+
const finalLeft = viewportLeft - offsetParentRect.left + window.scrollX; // Add scrollX for document-relative `left`
|
|
156
|
+
el.style.top = `${finalTop}px`;
|
|
157
|
+
el.style.left = `${finalLeft}px`;
|
|
158
|
+
}
|
|
159
|
+
}, [ref, triggerRef, offset, direction, container]);
|
|
160
|
+
useEffect(() => {
|
|
161
|
+
const el = ref.current;
|
|
162
|
+
if (!el)
|
|
163
|
+
return;
|
|
164
|
+
// Determine the trigger element
|
|
165
|
+
const trigger = triggerRef?.current || el.parentElement;
|
|
166
|
+
if (!trigger) {
|
|
167
|
+
console.warn("usePosition: No trigger element found. Positioning may not work.");
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
// Observers and event listeners
|
|
171
|
+
const observer = new ResizeObserver(calculate);
|
|
172
|
+
observer.observe(el);
|
|
173
|
+
if (trigger)
|
|
174
|
+
observer.observe(trigger); // Observe trigger for size/position changes
|
|
175
|
+
calculate(); // Initial calculation
|
|
176
|
+
window.addEventListener('resize', calculate);
|
|
177
|
+
window.addEventListener('scroll', calculate, true); // Use capture phase for reliability
|
|
178
|
+
return () => {
|
|
179
|
+
observer.disconnect();
|
|
180
|
+
if (trigger)
|
|
181
|
+
observer.disconnect(); // Disconnect trigger observer as well
|
|
182
|
+
window.removeEventListener('resize', calculate);
|
|
183
|
+
window.removeEventListener('scroll', calculate, true);
|
|
184
|
+
};
|
|
185
|
+
}, [ref, direction, offset, container, triggerRef]); // Add triggerRef to dependency array
|
|
186
|
+
return {
|
|
187
|
+
postion: appliedPositionRef.current,
|
|
188
|
+
reposition: calculate
|
|
189
|
+
};
|
|
190
|
+
};
|
|
191
|
+
export default usePosition;
|
|
@@ -3,6 +3,7 @@ import { useEffect, useState } from 'react';
|
|
|
3
3
|
const useResizeObserver = (ref) => {
|
|
4
4
|
const [size, setSize] = useState({ width: 0, height: 0, top: 0, left: 0 });
|
|
5
5
|
useEffect(() => {
|
|
6
|
+
const _ref = ref instanceof HTMLElement ? ref : ref.current;
|
|
6
7
|
const handleResize = (entries) => {
|
|
7
8
|
for (let entry of entries) {
|
|
8
9
|
const { width, height, top, left } = entry.contentRect;
|
|
@@ -10,12 +11,12 @@ const useResizeObserver = (ref) => {
|
|
|
10
11
|
}
|
|
11
12
|
};
|
|
12
13
|
const resizeObserver = new ResizeObserver(handleResize);
|
|
13
|
-
if (
|
|
14
|
-
resizeObserver.observe(
|
|
14
|
+
if (_ref) {
|
|
15
|
+
resizeObserver.observe(_ref);
|
|
15
16
|
}
|
|
16
17
|
return () => {
|
|
17
|
-
if (
|
|
18
|
-
resizeObserver.unobserve(
|
|
18
|
+
if (_ref) {
|
|
19
|
+
resizeObserver.unobserve(_ref);
|
|
19
20
|
}
|
|
20
21
|
};
|
|
21
22
|
}, [ref]);
|
|
@@ -13,60 +13,78 @@ const useScrollbar = (speed, breakpoints = {}) => {
|
|
|
13
13
|
const dragStartY = useRef(0);
|
|
14
14
|
const scrollStartY = useRef(0);
|
|
15
15
|
const scrollStartX = useRef(0);
|
|
16
|
-
|
|
17
|
-
const
|
|
16
|
+
// No need for thumbHeight/Width refs if calculating them dynamically
|
|
17
|
+
// const thumbHeight = useRef(30);
|
|
18
|
+
// const thumbWidth = useRef(30);
|
|
19
|
+
// Animation frame ID for batching visual updates
|
|
20
|
+
const animationFrameId = useRef(null);
|
|
18
21
|
const updateThumb = useCallback(() => {
|
|
19
22
|
if (!containerRef.current || !thumbY.current || !thumbX.current)
|
|
20
23
|
return;
|
|
21
24
|
const { clientHeight, scrollHeight, scrollTop, clientWidth, scrollWidth, scrollLeft } = containerRef.current;
|
|
22
|
-
//Y thumb
|
|
23
|
-
const
|
|
24
|
-
|
|
25
|
-
const
|
|
25
|
+
// Y thumb calculations and update
|
|
26
|
+
const actualThumbMinHeight = 30; // Min thumb size: 30px
|
|
27
|
+
const thumbSizeY = Math.max((clientHeight / scrollHeight) * clientHeight, actualThumbMinHeight);
|
|
28
|
+
const maxThumbPosY = clientHeight - thumbSizeY;
|
|
29
|
+
const thumbPosY = (scrollTop / (scrollHeight - clientHeight)) * maxThumbPosY;
|
|
26
30
|
thumbY.current.style.height = `${thumbSizeY}px`;
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
const
|
|
31
|
+
// *** KEY CHANGE: Use transform for positioning ***
|
|
32
|
+
thumbY.current.style.transform = `translateY(${thumbPosY}px)`;
|
|
33
|
+
thumbY.current.style.willChange = 'transform, height'; // Hint for browser
|
|
34
|
+
// X thumb calculations and update
|
|
35
|
+
const actualThumbMinWidth = 30; // Min thumb size: 30px
|
|
36
|
+
const thumbSizeX = Math.max((clientWidth / scrollWidth) * clientWidth, actualThumbMinWidth);
|
|
37
|
+
const maxThumbPosX = clientWidth - thumbSizeX;
|
|
38
|
+
const thumbPosX = (scrollLeft / (scrollWidth - clientWidth)) * maxThumbPosX;
|
|
32
39
|
thumbX.current.style.width = `${thumbSizeX}px`;
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
40
|
+
// *** KEY CHANGE: Use transform for positioning ***
|
|
41
|
+
thumbX.current.style.transform = `translateX(${thumbPosX}px)`;
|
|
42
|
+
thumbX.current.style.willChange = 'transform, width'; // Hint for browser
|
|
43
|
+
// Handle --no-y and --no-x classes (consider if these are strictly needed on every frame)
|
|
44
|
+
// These might still cause reflows, but less frequently than 'top'/'left'
|
|
45
|
+
if (scrollHeight <= clientHeight && rootRef.current) { // Use scrollHeight <= clientHeight for accurate check
|
|
46
|
+
rootRef.current.classList.add(`--no-y`);
|
|
36
47
|
}
|
|
37
|
-
else
|
|
38
|
-
rootRef.current
|
|
39
|
-
if (thumbX.current.clientWidth == clientWidth && rootRef) {
|
|
40
|
-
rootRef.current?.classList.add(`--no-x`);
|
|
48
|
+
else if (rootRef.current) {
|
|
49
|
+
rootRef.current.classList.remove(`--no-y`);
|
|
41
50
|
}
|
|
42
|
-
|
|
43
|
-
rootRef.current
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
51
|
+
if (scrollWidth <= clientWidth && rootRef.current) { // Use scrollWidth <= clientWidth for accurate check
|
|
52
|
+
rootRef.current.classList.add(`--no-x`);
|
|
53
|
+
}
|
|
54
|
+
else if (rootRef.current) {
|
|
55
|
+
rootRef.current.classList.remove(`--no-x`);
|
|
56
|
+
}
|
|
57
|
+
}, []); // Dependencies can be added here if needed, but often not for core calculations
|
|
58
|
+
// *** NEW: Central function to request a visual update via requestAnimationFrame ***
|
|
59
|
+
const requestVisualUpdate = useCallback(() => {
|
|
60
|
+
if (animationFrameId.current) {
|
|
61
|
+
cancelAnimationFrame(animationFrameId.current);
|
|
62
|
+
}
|
|
63
|
+
animationFrameId.current = requestAnimationFrame(() => {
|
|
64
|
+
updateThumb();
|
|
65
|
+
// Trigger breakpoints after the visual update for this frame
|
|
66
|
+
if (containerRef.current) {
|
|
67
|
+
const { scrollTop, scrollHeight, clientHeight, scrollLeft, scrollWidth, clientWidth } = containerRef.current;
|
|
68
|
+
const scrollPercentY = (scrollTop / (scrollHeight - clientHeight)) * 100;
|
|
69
|
+
const scrollPercentX = (scrollLeft / (scrollWidth - clientWidth)) * 100;
|
|
70
|
+
Object.keys(breakpoints).forEach((key) => {
|
|
71
|
+
const breakpoint = parseFloat(key);
|
|
72
|
+
if (Math.abs(scrollPercentY - breakpoint) < 1) {
|
|
73
|
+
breakpoints[breakpoint]?.();
|
|
74
|
+
}
|
|
75
|
+
if (Math.abs(scrollPercentX - breakpoint) < 1) { // Assuming breakpoints can be for X too
|
|
76
|
+
breakpoints[breakpoint]?.();
|
|
77
|
+
}
|
|
78
|
+
});
|
|
52
79
|
}
|
|
53
80
|
});
|
|
54
|
-
};
|
|
81
|
+
}, [updateThumb, breakpoints]);
|
|
55
82
|
const handleScroll = useCallback(() => {
|
|
56
|
-
if (!containerRef.current
|
|
57
|
-
// ||
|
|
58
|
-
// (
|
|
59
|
-
// typeof window !== `undefined` &&
|
|
60
|
-
// window.document.body.classList.contains(`--no-scroll`)
|
|
61
|
-
// )
|
|
62
|
-
)
|
|
83
|
+
if (!containerRef.current)
|
|
63
84
|
return;
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
postScroll(scrollPercentY);
|
|
68
|
-
postScroll(scrollPercentX);
|
|
69
|
-
}, [breakpoints, updateThumb]);
|
|
85
|
+
// *** Call requestVisualUpdate instead of postScroll ***
|
|
86
|
+
requestVisualUpdate();
|
|
87
|
+
}, [requestVisualUpdate]);
|
|
70
88
|
// Dragging logic
|
|
71
89
|
const onScrollY = (e) => {
|
|
72
90
|
isDraggingY.current = true;
|
|
@@ -85,30 +103,37 @@ const useScrollbar = (speed, breakpoints = {}) => {
|
|
|
85
103
|
rootRef.current?.classList.add(`--scrolling`);
|
|
86
104
|
};
|
|
87
105
|
const handleDragMove = useCallback((e) => {
|
|
88
|
-
if (!containerRef.current || !
|
|
106
|
+
if (!containerRef.current || (!isDraggingY.current && !isDraggingX.current))
|
|
89
107
|
return;
|
|
90
108
|
const { clientHeight, scrollHeight, clientWidth, scrollWidth } = containerRef.current;
|
|
91
109
|
if (isDraggingY.current) {
|
|
92
110
|
const maxScroll = scrollHeight - clientHeight;
|
|
93
|
-
const
|
|
111
|
+
const thumbCurrentHeight = thumbY.current?.clientHeight || 30; // Use actual thumb height
|
|
112
|
+
const maxThumbMove = clientHeight - thumbCurrentHeight;
|
|
94
113
|
const deltaY = e.clientY - dragStartY.current;
|
|
95
114
|
const newScrollTop = Math.min(Math.max(scrollStartY.current + (deltaY / maxThumbMove) * maxScroll, 0), maxScroll);
|
|
96
115
|
containerRef.current.scrollTop = newScrollTop;
|
|
116
|
+
// *** No direct updateThumb here, the scroll event listener will trigger requestVisualUpdate ***
|
|
97
117
|
}
|
|
98
118
|
if (isDraggingX.current) {
|
|
99
119
|
const maxScrollX = scrollWidth - clientWidth;
|
|
100
|
-
const
|
|
120
|
+
const thumbCurrentWidth = thumbX.current?.clientWidth || 30; // Use actual thumb width
|
|
121
|
+
const maxThumbMoveX = clientWidth - thumbCurrentWidth;
|
|
101
122
|
const deltaX = e.clientX - dragStartX.current;
|
|
102
123
|
const newScrollLeft = Math.min(Math.max(scrollStartX.current + (deltaX / maxThumbMoveX) * maxScrollX, 0), maxScrollX);
|
|
103
124
|
containerRef.current.scrollLeft = newScrollLeft;
|
|
125
|
+
// *** No direct updateThumb here, the scroll event listener will trigger requestVisualUpdate ***
|
|
104
126
|
}
|
|
105
|
-
}, []);
|
|
127
|
+
}, []); // No dependencies needed if current values are always fresh
|
|
106
128
|
const handleDragEnd = () => {
|
|
107
129
|
isDraggingY.current = false;
|
|
108
130
|
isDraggingX.current = false;
|
|
109
131
|
document.body.style.userSelect = "";
|
|
110
132
|
if (rootRef.current)
|
|
111
133
|
rootRef.current?.classList.remove(`--scrolling`);
|
|
134
|
+
if (animationFrameId.current) {
|
|
135
|
+
cancelAnimationFrame(animationFrameId.current); // Clear any pending rAF
|
|
136
|
+
}
|
|
112
137
|
};
|
|
113
138
|
const scrollToTop = () => containerRef.current?.scrollTo({ top: 0, behavior: "smooth" });
|
|
114
139
|
const scrollToBottom = () => containerRef.current?.scrollTo({ top: containerRef.current.scrollHeight, behavior: "smooth" });
|
|
@@ -119,49 +144,69 @@ const useScrollbar = (speed, breakpoints = {}) => {
|
|
|
119
144
|
if (!container)
|
|
120
145
|
return;
|
|
121
146
|
const handleWheel = (e) => {
|
|
122
|
-
|
|
123
|
-
e.
|
|
147
|
+
// *** Passive listener means preventDefault is often not needed/effective here ***
|
|
148
|
+
// e.preventDefault();
|
|
149
|
+
// e.stopPropagation();
|
|
124
150
|
if (!containerRef.current)
|
|
125
151
|
return;
|
|
126
|
-
// Adjust scrollTop manually based on deltaY
|
|
127
152
|
const { scrollTop, scrollHeight, clientHeight, scrollLeft, scrollWidth, clientWidth } = containerRef.current;
|
|
153
|
+
let newScrollTop = scrollTop;
|
|
154
|
+
let newScrollLeft = scrollLeft;
|
|
155
|
+
let changed = false;
|
|
128
156
|
if (Math.abs(e.deltaY) > Math.abs(e.deltaX)) {
|
|
129
157
|
const maxScrollY = scrollHeight - clientHeight;
|
|
130
|
-
|
|
158
|
+
newScrollTop = scrollTop + e.deltaY * SCROLL_SPEED;
|
|
131
159
|
newScrollTop = Math.max(0, Math.min(newScrollTop, maxScrollY));
|
|
132
|
-
|
|
160
|
+
if (newScrollTop !== scrollTop) {
|
|
161
|
+
containerRef.current.scrollTop = newScrollTop;
|
|
162
|
+
changed = true;
|
|
163
|
+
}
|
|
133
164
|
}
|
|
134
|
-
|
|
165
|
+
else { // Prefer deltaX if it's larger or if deltaY is 0
|
|
135
166
|
const maxScrollX = scrollWidth - clientWidth;
|
|
136
|
-
|
|
167
|
+
newScrollLeft = scrollLeft + e.deltaX * SCROLL_SPEED;
|
|
137
168
|
newScrollLeft = Math.max(0, Math.min(newScrollLeft, maxScrollX));
|
|
138
|
-
|
|
169
|
+
if (newScrollLeft !== scrollLeft) {
|
|
170
|
+
containerRef.current.scrollLeft = newScrollLeft;
|
|
171
|
+
changed = true;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
// *** Only request update if scroll position actually changed ***
|
|
175
|
+
if (changed) {
|
|
176
|
+
requestVisualUpdate();
|
|
139
177
|
}
|
|
140
|
-
const scrollPercentY = (containerRef.current.scrollTop / (containerRef.current.scrollHeight - clientHeight)) * 100;
|
|
141
|
-
const scrollPercentX = (containerRef.current.scrollLeft / (containerRef.current.scrollWidth - clientWidth)) * 100;
|
|
142
|
-
postScroll(scrollPercentY);
|
|
143
|
-
postScroll(scrollPercentX);
|
|
144
178
|
};
|
|
145
|
-
window.addEventListener("resize",
|
|
146
|
-
container.addEventListener("scroll", handleScroll);
|
|
147
|
-
|
|
148
|
-
container.addEventListener("wheel", handleWheel, { passive: false });
|
|
179
|
+
window.addEventListener("resize", requestVisualUpdate); // Use requestVisualUpdate for resize
|
|
180
|
+
container.addEventListener("scroll", handleScroll, { passive: true }); // Make scroll passive if you don't preventDefault
|
|
181
|
+
container.addEventListener("wheel", handleWheel, { passive: true }); // Keep passive for wheel
|
|
149
182
|
document.addEventListener("mousemove", handleDragMove);
|
|
150
183
|
document.addEventListener("mouseup", handleDragEnd);
|
|
151
|
-
|
|
184
|
+
// Initial update
|
|
185
|
+
requestVisualUpdate(); // Use requestVisualUpdate for initial render
|
|
152
186
|
return () => {
|
|
153
|
-
window.removeEventListener("resize",
|
|
154
|
-
window.removeEventListener("wheel", handleWheel);
|
|
187
|
+
window.removeEventListener("resize", requestVisualUpdate);
|
|
155
188
|
container.removeEventListener("scroll", handleScroll);
|
|
189
|
+
container.removeEventListener("wheel", handleWheel); // Corrected: remove from container
|
|
156
190
|
document.removeEventListener("mousemove", handleDragMove);
|
|
157
191
|
document.removeEventListener("mouseup", handleDragEnd);
|
|
192
|
+
if (animationFrameId.current) {
|
|
193
|
+
cancelAnimationFrame(animationFrameId.current); // Clean up any pending rAF
|
|
194
|
+
}
|
|
158
195
|
};
|
|
159
|
-
}, [handleScroll, handleDragMove,
|
|
160
|
-
useMutationObserver
|
|
196
|
+
}, [handleScroll, handleDragMove, requestVisualUpdate, SCROLL_SPEED]); // Added SCROLL_SPEED to deps
|
|
197
|
+
// *** NEW: Hook useMutationObserver to call requestVisualUpdate ***
|
|
198
|
+
useMutationObserver(containerRef.current, requestVisualUpdate);
|
|
161
199
|
return {
|
|
162
|
-
rootRef,
|
|
163
|
-
|
|
164
|
-
|
|
200
|
+
rootRef,
|
|
201
|
+
containerRef,
|
|
202
|
+
thumbY,
|
|
203
|
+
thumbX,
|
|
204
|
+
scrollToTop,
|
|
205
|
+
scrollToBottom,
|
|
206
|
+
scrollToLeft,
|
|
207
|
+
scrollToRight,
|
|
208
|
+
onScrollY,
|
|
209
|
+
onScrollX,
|
|
165
210
|
};
|
|
166
211
|
};
|
|
167
212
|
export default useScrollbar;
|
package/dist/cjs/types/enums.js
CHANGED
|
@@ -19,6 +19,8 @@ export var FORMVALIDATION_STYLE;
|
|
|
19
19
|
})(FORMVALIDATION_STYLE || (FORMVALIDATION_STYLE = {}));
|
|
20
20
|
export var FORMVALIDATION;
|
|
21
21
|
(function (FORMVALIDATION) {
|
|
22
|
+
FORMVALIDATION["IPV4"] = "IPV4";
|
|
23
|
+
FORMVALIDATION["IPV6"] = "IPV6";
|
|
22
24
|
FORMVALIDATION["Email"] = "EMAIL";
|
|
23
25
|
FORMVALIDATION["Uri"] = "URI";
|
|
24
26
|
FORMVALIDATION["Password"] = "PASSWORD";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ComponentPropsWithoutRef, ElementType } from "react";
|
|
2
2
|
import { DragOptions } from "../hooks";
|
|
3
|
-
import { SHIMMER, SORT, TRANSITIONS } from "./enums";
|
|
3
|
+
import { Position, SHIMMER, SORT, TRANSITIONS } from "./enums";
|
|
4
4
|
import { animationProps, Skeleton } from "./interfaces";
|
|
5
5
|
export type Deprecated<T, M extends string> = T & {
|
|
6
6
|
__deprecatedMessage?: M;
|
|
@@ -85,3 +85,4 @@ export type cssShortKeys = {
|
|
|
85
85
|
sy: string | number;
|
|
86
86
|
sz: string | number;
|
|
87
87
|
};
|
|
88
|
+
export type Direction = Position;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { BoxProps } from "../Box";
|
|
2
|
+
import { CHART } from "./types";
|
|
3
|
+
declare const Chart: import("react").ForwardRefExoticComponent<BoxProps & import("../..").LineChartProps & {
|
|
4
|
+
type?: CHART;
|
|
5
|
+
animDuration?: number;
|
|
6
|
+
animDelay?: number;
|
|
7
|
+
} & import("react").RefAttributes<HTMLDivElement>>;
|
|
8
|
+
export default Chart;
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { forwardRef, useEffect, useId, useRef, useState } from "react";
|
|
4
|
+
import { useResizeObserver } from "../..";
|
|
5
|
+
import useBase from "../../hooks/useBase";
|
|
6
|
+
import useLineChart from "../../hooks/useLineChart";
|
|
7
|
+
import Box from "../Box";
|
|
8
|
+
import { CHART } from "./types";
|
|
9
|
+
const Chart = forwardRef((props, ref) => {
|
|
10
|
+
const { data, width, height, lineColor, strokeWidth = 2, gradientStartColor, gradientEndColor, animated, animDuration = 2, animDelay = 0, padding = 0, type, ...pops } = props;
|
|
11
|
+
const { className, style, rest } = useBase(pops);
|
|
12
|
+
const innerRef = useRef(null);
|
|
13
|
+
// State to store the actual dimensions of the container
|
|
14
|
+
const observedDimensions = useResizeObserver(innerRef.current?.parentElement || innerRef);
|
|
15
|
+
const chartWidth = typeof width === 'number' ? width : observedDimensions.width;
|
|
16
|
+
const chartHeight = typeof height === 'number' ? height : observedDimensions.height;
|
|
17
|
+
const dimensionsForChartHook = {
|
|
18
|
+
width: chartWidth,
|
|
19
|
+
height: chartHeight
|
|
20
|
+
};
|
|
21
|
+
// Generate a unique ID for the gradient to avoid conflicts if multiple charts are on the page
|
|
22
|
+
const gradientId = useId(); //`chartGradient-${Math.random().toString(36).substring(2, 9)}`;
|
|
23
|
+
// Use the custom hook to get the SVG path data and area path data
|
|
24
|
+
const { pathD, areaPathD } = useLineChart(data, dimensionsForChartHook, (padding + (strokeWidth || 2)));
|
|
25
|
+
// Refs for the path elements to get their total length for animation
|
|
26
|
+
const linePathRef = useRef(null);
|
|
27
|
+
const areaPathRef = useRef(null);
|
|
28
|
+
// State to store the calculated lengths of the paths
|
|
29
|
+
const [linePathLength, setLinePathLength] = useState(0);
|
|
30
|
+
const [areaPathLength, setAreaPathLength] = useState(0);
|
|
31
|
+
useEffect(() => {
|
|
32
|
+
if (animated) {
|
|
33
|
+
if (linePathRef.current) {
|
|
34
|
+
// Get the total length of the line path for stroke-dasharray animation
|
|
35
|
+
setLinePathLength(linePathRef.current.getTotalLength());
|
|
36
|
+
}
|
|
37
|
+
if (areaPathRef.current) {
|
|
38
|
+
// Get the total length of the area path for stroke-dasharray animation
|
|
39
|
+
setAreaPathLength(areaPathRef.current.getTotalLength());
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}, [animated, pathD, areaPathD, chartWidth, chartHeight]);
|
|
43
|
+
return _jsx(Box, { ref: innerRef, as: `--chart --${type || CHART.Line}-chart ${className}`.trim(), style: {
|
|
44
|
+
...style,
|
|
45
|
+
width,
|
|
46
|
+
height
|
|
47
|
+
}, ...rest, children: chartWidth > 0 && chartHeight > 0 && _jsxs("svg", { viewBox: `0 0 ${chartWidth} ${chartHeight}`, preserveAspectRatio: "xMidYMid meet" // Ensures the SVG scales nicely
|
|
48
|
+
, children: [_jsx("defs", { children: _jsxs("linearGradient", { id: gradientId, x1: "0%", y1: "0%", x2: "0%", y2: "100%", children: [_jsx("stop", { offset: "0%", stopColor: gradientStartColor }), _jsx("stop", { offset: "100%", stopColor: gradientEndColor })] }) }), _jsx("path", { ref: areaPathRef, d: areaPathD, fill: `url(#${gradientId})`, stroke: "none" // No stroke for the area
|
|
49
|
+
,
|
|
50
|
+
// Apply animation properties if 'animated' is true
|
|
51
|
+
style: animated ? {
|
|
52
|
+
strokeDasharray: areaPathLength,
|
|
53
|
+
strokeDashoffset: areaPathLength,
|
|
54
|
+
} : {}, children: animated && (_jsx("animate", { attributeName: "stroke-dashoffset", from: areaPathLength, to: "0", dur: `${animDuration || 2}s`, begin: `${animDelay || 0}s`, fill: "freeze" })) }), _jsx("path", { ref: linePathRef, d: pathD, fill: "none" // No fill for the line
|
|
55
|
+
, stroke: lineColor, strokeWidth: strokeWidth, strokeLinecap: "round" // Rounded line caps
|
|
56
|
+
, strokeLinejoin: "round" // Rounded line joins
|
|
57
|
+
,
|
|
58
|
+
// Apply animation properties if 'animated' is true
|
|
59
|
+
style: animated ? {
|
|
60
|
+
strokeDasharray: linePathLength,
|
|
61
|
+
strokeDashoffset: linePathLength,
|
|
62
|
+
} : {}, children: animated && (_jsx("animate", { attributeName: "stroke-dashoffset", from: linePathLength, to: "0", dur: `${animDuration || 2}s`, begin: `${animDelay || 0}s`, fill: "freeze" // Keep the final state
|
|
63
|
+
})) })] }) });
|
|
64
|
+
});
|
|
65
|
+
Chart.displayName = `Zuz.Chart`;
|
|
66
|
+
export default Chart;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { LineChartProps } from "../../hooks/useLineChart";
|
|
2
|
+
import { BoxProps } from "../Box";
|
|
3
|
+
export declare enum CHART {
|
|
4
|
+
Line = "line"
|
|
5
|
+
}
|
|
6
|
+
export type ChartProps = BoxProps & LineChartProps & {
|
|
7
|
+
type?: CHART;
|
|
8
|
+
animDuration?: number;
|
|
9
|
+
animDelay?: number;
|
|
10
|
+
};
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
-
import { withPost } from "@zuzjs/core";
|
|
3
|
+
import { _, withPost } from "@zuzjs/core";
|
|
4
4
|
import { forwardRef, startTransition, useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState } from "react";
|
|
5
|
-
import { addPropsToChildren,
|
|
5
|
+
import { addPropsToChildren, isEmpty } from "../../funs";
|
|
6
6
|
import { useBase, useToast } from "../../hooks";
|
|
7
7
|
import { FORMVALIDATION } from "../../types/enums";
|
|
8
8
|
import Box from "../Box";
|
|
@@ -89,8 +89,12 @@ const Form = forwardRef((props, ref) => {
|
|
|
89
89
|
}
|
|
90
90
|
}
|
|
91
91
|
switch (_with.toUpperCase()) {
|
|
92
|
+
case FORMVALIDATION.IPV4:
|
|
93
|
+
return _(el.value).isIPv4();
|
|
94
|
+
case FORMVALIDATION.IPV6:
|
|
95
|
+
return _(el.value).isIPv6();
|
|
92
96
|
case FORMVALIDATION.Email:
|
|
93
|
-
return
|
|
97
|
+
return _(el.value).isEmail();
|
|
94
98
|
case FORMVALIDATION.Uri:
|
|
95
99
|
try {
|
|
96
100
|
new URL(el.value);
|
|
@@ -2,7 +2,7 @@ import { InputProps } from '../Input';
|
|
|
2
2
|
export type PasswordProps = Omit<InputProps, `type` | `numeric`> & {
|
|
3
3
|
strenthMeter?: boolean;
|
|
4
4
|
};
|
|
5
|
-
declare const Password: import("react").ForwardRefExoticComponent<Omit<InputProps, "
|
|
5
|
+
declare const Password: import("react").ForwardRefExoticComponent<Omit<InputProps, "numeric" | "type"> & {
|
|
6
6
|
strenthMeter?: boolean;
|
|
7
7
|
} & import("react").RefAttributes<HTMLInputElement>>;
|
|
8
8
|
export default Password;
|