@zuzjs/ui 0.9.4 → 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.
Files changed (71) hide show
  1. package/dist/cjs/comps/Chart/index.d.ts +8 -0
  2. package/dist/cjs/comps/Chart/index.js +66 -0
  3. package/dist/cjs/comps/Chart/types.d.ts +10 -0
  4. package/dist/cjs/comps/Chart/types.js +4 -0
  5. package/dist/cjs/comps/Form/index.js +8 -4
  6. package/dist/cjs/comps/Group/index.d.ts +6 -0
  7. package/dist/cjs/comps/Group/index.js +21 -0
  8. package/dist/cjs/comps/Image/index.js +5 -3
  9. package/dist/cjs/comps/Password/index.d.ts +1 -1
  10. package/dist/cjs/comps/Select/index.d.ts +1 -0
  11. package/dist/cjs/comps/Select/index.js +10 -5
  12. package/dist/cjs/comps/Select/types.d.ts +4 -0
  13. package/dist/cjs/comps/Text/index.js +0 -2
  14. package/dist/cjs/comps/TextWheel/index.d.ts +2 -0
  15. package/dist/cjs/comps/TextWheel/index.js +24 -12
  16. package/dist/cjs/comps/TextWheel/types.d.ts +2 -0
  17. package/dist/cjs/comps/index.d.ts +3 -0
  18. package/dist/cjs/comps/index.js +4 -0
  19. package/dist/cjs/funs/index.d.ts +1 -1
  20. package/dist/cjs/funs/index.js +4 -3
  21. package/dist/cjs/hooks/index.d.ts +2 -0
  22. package/dist/cjs/hooks/index.js +2 -0
  23. package/dist/cjs/hooks/useBase.js +52 -11
  24. package/dist/cjs/hooks/useLineChart.d.ts +25 -0
  25. package/dist/cjs/hooks/useLineChart.js +86 -0
  26. package/dist/cjs/hooks/usePosition.d.ts +12 -0
  27. package/dist/cjs/hooks/usePosition.js +191 -0
  28. package/dist/cjs/hooks/useResizeObserver.d.ts +1 -1
  29. package/dist/cjs/hooks/useResizeObserver.js +5 -4
  30. package/dist/cjs/hooks/useScrollbar.js +115 -70
  31. package/dist/cjs/types/enums.d.ts +2 -0
  32. package/dist/cjs/types/enums.js +2 -0
  33. package/dist/cjs/types/index.d.ts +2 -1
  34. package/dist/cjs/types/interfaces.d.ts +3 -2
  35. package/dist/esm/comps/Chart/index.d.ts +8 -0
  36. package/dist/esm/comps/Chart/index.js +66 -0
  37. package/dist/esm/comps/Chart/types.d.ts +10 -0
  38. package/dist/esm/comps/Chart/types.js +4 -0
  39. package/dist/esm/comps/Form/index.js +8 -4
  40. package/dist/esm/comps/Group/index.d.ts +6 -0
  41. package/dist/esm/comps/Group/index.js +21 -0
  42. package/dist/esm/comps/Image/index.js +5 -3
  43. package/dist/esm/comps/Password/index.d.ts +1 -1
  44. package/dist/esm/comps/Select/index.d.ts +1 -0
  45. package/dist/esm/comps/Select/index.js +10 -5
  46. package/dist/esm/comps/Select/types.d.ts +4 -0
  47. package/dist/esm/comps/Text/index.js +0 -2
  48. package/dist/esm/comps/TextWheel/index.d.ts +2 -0
  49. package/dist/esm/comps/TextWheel/index.js +24 -12
  50. package/dist/esm/comps/TextWheel/types.d.ts +2 -0
  51. package/dist/esm/comps/index.d.ts +3 -0
  52. package/dist/esm/comps/index.js +4 -0
  53. package/dist/esm/funs/index.d.ts +1 -1
  54. package/dist/esm/funs/index.js +4 -3
  55. package/dist/esm/hooks/index.d.ts +2 -0
  56. package/dist/esm/hooks/index.js +2 -0
  57. package/dist/esm/hooks/useBase.js +52 -11
  58. package/dist/esm/hooks/useLineChart.d.ts +25 -0
  59. package/dist/esm/hooks/useLineChart.js +86 -0
  60. package/dist/esm/hooks/usePosition.d.ts +12 -0
  61. package/dist/esm/hooks/usePosition.js +191 -0
  62. package/dist/esm/hooks/useResizeObserver.d.ts +1 -1
  63. package/dist/esm/hooks/useResizeObserver.js +5 -4
  64. package/dist/esm/hooks/useScrollbar.js +115 -70
  65. package/dist/esm/types/enums.d.ts +2 -0
  66. package/dist/esm/types/enums.js +2 -0
  67. package/dist/esm/types/index.d.ts +2 -1
  68. package/dist/esm/types/interfaces.d.ts +3 -2
  69. package/dist/tsconfig.esm.tsbuildinfo +1 -1
  70. package/dist/tsconfig.tsbuildinfo +1 -1
  71. package/package.json +2 -2
@@ -0,0 +1,86 @@
1
+ import { useEffect, useState } from 'react';
2
+ // Helper function to generate a smooth SVG path from data points
3
+ // This uses cubic Bezier curves for smoothing.
4
+ const getSmoothLinePath = (data, width, height, padding = 20) => {
5
+ if (!data || data.length === 0 || width <= 0 || height <= 0)
6
+ return "";
7
+ // Calculate min/max for x and y values to scale the data
8
+ const minX = Math.min(...data.map(d => d.x));
9
+ const maxX = Math.max(...data.map(d => d.x));
10
+ const minY = Math.min(...data.map(d => d.y));
11
+ const maxY = Math.max(...data.map(d => d.y));
12
+ // Determine scaling factors, ensuring padding
13
+ const scaleX = (width - 2 * padding) / (maxX - minX);
14
+ const scaleY = (height - 2 * padding) / (maxY - minY);
15
+ // Function to convert data point to SVG coordinate
16
+ const toSvgCoords = (point) => {
17
+ const svgX = (point.x - minX) * scaleX + padding;
18
+ // SVG Y-axis is inverted, so we subtract from height
19
+ const svgY = height - ((point.y - minY) * scaleY + padding);
20
+ return { x: svgX, y: svgY };
21
+ };
22
+ // Convert all data points to SVG coordinates
23
+ const svgPoints = data.map(toSvgCoords);
24
+ let path = "";
25
+ if (svgPoints.length > 0) {
26
+ // Start the path at the first point
27
+ path = `M ${svgPoints[0].x},${svgPoints[0].y}`;
28
+ for (let i = 0; i < svgPoints.length - 1; i++) {
29
+ const p0 = svgPoints[i];
30
+ const p1 = svgPoints[i + 1];
31
+ // Calculate control points for cubic Bezier curve
32
+ // This is a simplified approach for smooth curves.
33
+ // For more advanced smoothing, consider D3's curve generators.
34
+ const cp1 = {
35
+ x: (p0.x + p1.x) / 2,
36
+ y: p0.y
37
+ };
38
+ const cp2 = {
39
+ x: (p0.x + p1.x) / 2,
40
+ y: p1.y
41
+ };
42
+ // Add cubic Bezier curve segment
43
+ path += ` C ${cp1.x},${cp1.y} ${cp2.x},${cp2.y} ${p1.x},${p1.y}`;
44
+ }
45
+ }
46
+ return path;
47
+ };
48
+ // Helper function to generate the SVG path for the filled area below the line
49
+ const getAreaPath = (data, width, height, padding = 20) => {
50
+ if (!data || data.length === 0)
51
+ return "";
52
+ // Reuse the line path generation logic for the top boundary of the area
53
+ const linePath = getSmoothLinePath(data, width, height, padding);
54
+ // Calculate min/max for x values to scale the data
55
+ const minX = Math.min(...data.map(d => d.x));
56
+ const maxX = Math.max(...data.map(d => d.x));
57
+ // Determine scaling factor for x
58
+ const scaleX = (width - 2 * padding) / (maxX - minX);
59
+ // Get SVG coordinates for the first and last points of the line
60
+ const firstPointSvgX = (data[0].x - minX) * scaleX + padding;
61
+ const lastPointSvgX = (data[data.length - 1].x - minX) * scaleX + padding;
62
+ // The bottom Y-coordinate of the chart, accounting for padding
63
+ const chartBottomY = height - padding;
64
+ // Construct the area path:
65
+ // 1. Start at the first point of the line path
66
+ // 2. Follow the smooth line path
67
+ // 3. Draw a vertical line down from the last point to the bottom of the chart
68
+ // 4. Draw a horizontal line across the bottom to the x-coordinate of the first point
69
+ // 5. Draw a vertical line up from the bottom to the first point's y-coordinate (closing the path)
70
+ return `${linePath} L ${lastPointSvgX},${chartBottomY} L ${firstPointSvgX},${chartBottomY} Z`;
71
+ };
72
+ // Custom React hook for the line chart logic
73
+ const useLineChart = (data, dimensions = { width: 600, height: 300 }, padding = 20) => {
74
+ const [pathD, setPathD] = useState("");
75
+ const [areaPathD, setAreaPathD] = useState("");
76
+ useEffect(() => {
77
+ // Generate the SVG path whenever data or dimensions change
78
+ const newPathD = getSmoothLinePath(data, dimensions.width, dimensions.height, padding);
79
+ setPathD(newPathD);
80
+ // Generate the area path for the gradient fill
81
+ const newAreaPathD = getAreaPath(data, dimensions.width, dimensions.height, padding);
82
+ setAreaPathD(newAreaPathD);
83
+ }, [data, dimensions.width, dimensions.height, padding]);
84
+ return { pathD, areaPathD };
85
+ };
86
+ export default useLineChart;
@@ -0,0 +1,12 @@
1
+ import { Direction } from "../types";
2
+ interface PositionOptions {
3
+ offset?: number;
4
+ direction?: Direction;
5
+ container?: HTMLElement | null;
6
+ triggerRef?: React.RefObject<HTMLElement>;
7
+ }
8
+ export declare const usePosition: (ref: React.RefObject<HTMLElement>, options?: PositionOptions) => {
9
+ postion: "fixed" | "absolute" | null;
10
+ reposition: () => void;
11
+ };
12
+ export default usePosition;
@@ -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;
@@ -5,5 +5,5 @@ interface Size {
5
5
  top: number;
6
6
  left: number;
7
7
  }
8
- declare const useResizeObserver: (ref: RefObject<HTMLElement | null>) => Size;
8
+ declare const useResizeObserver: (ref: RefObject<HTMLElement | null> | HTMLElement) => Size;
9
9
  export default useResizeObserver;
@@ -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 (ref.current) {
14
- resizeObserver.observe(ref.current);
14
+ if (_ref) {
15
+ resizeObserver.observe(_ref);
15
16
  }
16
17
  return () => {
17
- if (ref.current) {
18
- resizeObserver.unobserve(ref.current);
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
- const thumbHeight = useRef(30); // Default min height
17
- const thumbWidth = useRef(30); // Default min height
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 thumbSizeY = Math.max((clientHeight / scrollHeight) * clientHeight, 30); // Min thumb size: 30px
24
- thumbHeight.current = thumbSizeY;
25
- const thumbPosY = (scrollTop / (scrollHeight - clientHeight)) * (clientHeight - thumbSizeY);
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
- thumbY.current.style.top = `${thumbPosY}px`;
28
- //X thumb
29
- const thumbSizeX = Math.max((clientWidth / scrollWidth) * clientWidth, 30); // Min thumb size: 30px
30
- thumbWidth.current = thumbSizeX;
31
- const thumbPosX = (scrollLeft / (scrollWidth - clientWidth)) * (clientWidth - thumbSizeX);
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
- thumbX.current.style.left = `${thumbPosX}px`;
34
- if (thumbY.current.clientHeight == clientHeight && rootRef) {
35
- rootRef.current?.classList.add(`--no-y`);
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?.classList.remove(`--no-y`);
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
- else
43
- rootRef.current?.classList.remove(`--no-x`);
44
- }, []);
45
- const postScroll = (scrollPercentY) => {
46
- updateThumb();
47
- // Trigger breakpoints
48
- Object.keys(breakpoints).forEach((key) => {
49
- const breakpoint = parseFloat(key);
50
- if (Math.abs(scrollPercentY - breakpoint) < 1) {
51
- breakpoints[breakpoint]?.();
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
- const { scrollTop, scrollHeight, clientHeight, scrollLeft, scrollWidth, clientWidth } = containerRef.current;
65
- const scrollPercentY = (scrollTop / (scrollHeight - clientHeight)) * 100;
66
- const scrollPercentX = (scrollLeft / (scrollWidth - clientWidth)) * 100;
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 || !thumbY.current || !thumbX.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 maxThumbMove = clientHeight - thumbHeight.current;
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 maxThumbMoveX = clientWidth - thumbWidth.current;
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
- e.preventDefault();
123
- e.stopPropagation();
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
- let newScrollTop = scrollTop + e.deltaY;
158
+ newScrollTop = scrollTop + e.deltaY * SCROLL_SPEED;
131
159
  newScrollTop = Math.max(0, Math.min(newScrollTop, maxScrollY));
132
- containerRef.current.scrollTop = newScrollTop * SCROLL_SPEED;
160
+ if (newScrollTop !== scrollTop) {
161
+ containerRef.current.scrollTop = newScrollTop;
162
+ changed = true;
163
+ }
133
164
  }
134
- if (Math.abs(e.deltaX) > Math.abs(e.deltaY)) {
165
+ else { // Prefer deltaX if it's larger or if deltaY is 0
135
166
  const maxScrollX = scrollWidth - clientWidth;
136
- let newScrollLeft = scrollLeft + e.deltaX;
167
+ newScrollLeft = scrollLeft + e.deltaX * SCROLL_SPEED;
137
168
  newScrollLeft = Math.max(0, Math.min(newScrollLeft, maxScrollX));
138
- containerRef.current.scrollLeft = newScrollLeft * SCROLL_SPEED;
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", updateThumb);
146
- container.addEventListener("scroll", handleScroll);
147
- // Prevent blocking default scrolling (fixes touchpad scrolling)
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
- updateThumb();
184
+ // Initial update
185
+ requestVisualUpdate(); // Use requestVisualUpdate for initial render
152
186
  return () => {
153
- window.removeEventListener("resize", updateThumb);
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, updateThumb]);
160
- useMutationObserver(containerRef.current, updateThumb);
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, containerRef, thumbY, thumbX,
163
- scrollToTop, scrollToBottom, scrollToLeft, scrollToRight,
164
- onScrollY, onScrollX
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;
@@ -14,6 +14,8 @@ export declare enum FORMVALIDATION_STYLE {
14
14
  Dots = "DOTS"
15
15
  }
16
16
  export declare enum FORMVALIDATION {
17
+ IPV4 = "IPV4",
18
+ IPV6 = "IPV6",
17
19
  Email = "EMAIL",
18
20
  Uri = "URI",
19
21
  Password = "PASSWORD",
@@ -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;
@@ -1,6 +1,6 @@
1
1
  import { dynamicObject } from ".";
2
2
  import { SKELETON, TRANSITION_CURVES, TRANSITIONS } from "./enums";
3
- export interface scrollEffectProps {
3
+ export interface parallaxEffectProps {
4
4
  lerpFactor?: number;
5
5
  x?: number;
6
6
  y?: number;
@@ -34,7 +34,8 @@ export interface animationProps {
34
34
  delay?: number;
35
35
  /** Easing curve applied to the animation, as a string or {@link TRANSITION_CURVES} */
36
36
  curve?: string | TRANSITION_CURVES;
37
- scroll?: scrollEffectProps;
37
+ scroll?: parallaxEffectProps;
38
+ mouse?: parallaxEffectProps;
38
39
  }
39
40
  /**
40
41
  * `Skeleton` defines properties for a skeleton loader, used to indicate