@choice-ui/react 1.9.3 → 1.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/components/button/src/tv.js +7 -0
- package/dist/components/command/dist/index.d.ts +13 -0
- package/dist/components/command/src/command.js +2 -0
- package/dist/components/command/src/components/command-item.d.ts +4 -0
- package/dist/components/command/src/components/command-item.js +6 -0
- package/dist/components/command/src/components/command-list.d.ts +3 -0
- package/dist/components/command/src/components/command-list.js +5 -1
- package/dist/components/command/src/context/create-command-context.d.ts +1 -0
- package/dist/components/command/src/context/create-command-context.js +2 -0
- package/dist/components/command/src/tv.js +1 -1
- package/dist/components/command/src/types.d.ts +6 -0
- package/dist/components/scroll-area/dist/index.d.ts +4 -27
- package/dist/components/scroll-area/dist/index.js +96 -123
- package/dist/components/scroll-area/src/components/scroll-area-content.js +2 -2
- package/dist/components/scroll-area/src/components/scroll-area-root.js +9 -12
- package/dist/components/scroll-area/src/components/scroll-area-scrollbar.js +14 -4
- package/dist/components/scroll-area/src/components/scroll-area-viewport.js +2 -2
- package/dist/components/scroll-area/src/context/scroll-area-context.d.ts +17 -2
- package/dist/components/scroll-area/src/context/scroll-area-context.js +23 -6
- package/dist/components/scroll-area/src/hooks/index.d.ts +0 -1
- package/dist/components/scroll-area/src/hooks/use-scroll-state-and-visibility.d.ts +2 -2
- package/dist/components/scroll-area/src/hooks/use-scroll-state-and-visibility.js +30 -75
- package/dist/components/scroll-area/src/hooks/use-thumb.d.ts +1 -1
- package/dist/components/scroll-area/src/hooks/use-thumb.js +25 -28
- package/dist/components/scroll-area/src/types.d.ts +16 -4
- package/dist/index.js +0 -2
- package/package.json +1 -1
- package/dist/components/scroll-area/src/hooks/use-scroll-performance-monitor.d.ts +0 -23
- package/dist/components/scroll-area/src/hooks/use-scroll-performance-monitor.js +0 -123
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { forwardRef, useState, useId, useMemo } from "react";
|
|
3
|
-
import {
|
|
3
|
+
import { ScrollAreaConfigContext, ScrollAreaStateContext } from "../context/scroll-area-context.js";
|
|
4
4
|
import { ScrollTv } from "../tv.js";
|
|
5
5
|
import { ScrollAreaCorner } from "./scroll-area-corner.js";
|
|
6
6
|
import { ScrollAreaScrollbar } from "./scroll-area-scrollbar.js";
|
|
@@ -32,7 +32,7 @@ const ScrollAreaRoot = forwardRef(
|
|
|
32
32
|
const viewportId = `scroll-viewport${reactId}`;
|
|
33
33
|
const scrollbarXId = `scroll-x${reactId}`;
|
|
34
34
|
const scrollbarYId = `scroll-y${reactId}`;
|
|
35
|
-
const { scrollState, isHovering, isScrolling, handleMouseEnter, handleMouseLeave } = useScrollStateAndVisibility(viewport);
|
|
35
|
+
const { scrollState, isHovering, isScrolling, handleMouseEnter, handleMouseLeave } = useScrollStateAndVisibility(viewport, content);
|
|
36
36
|
const staticConfig = useMemo(
|
|
37
37
|
() => ({
|
|
38
38
|
orientation,
|
|
@@ -43,11 +43,14 @@ const ScrollAreaRoot = forwardRef(
|
|
|
43
43
|
}),
|
|
44
44
|
[orientation, scrollbarMode, hoverBoundary, variant, type]
|
|
45
45
|
);
|
|
46
|
-
const
|
|
46
|
+
const stateValue = useMemo(
|
|
47
|
+
() => ({ scrollState, isHovering, isScrolling }),
|
|
48
|
+
[scrollState, isHovering, isScrolling]
|
|
49
|
+
);
|
|
50
|
+
const configValue = useMemo(
|
|
47
51
|
() => ({
|
|
48
52
|
content,
|
|
49
53
|
orientation: staticConfig.orientation,
|
|
50
|
-
scrollState,
|
|
51
54
|
scrollbarMode: staticConfig.scrollbarMode,
|
|
52
55
|
hoverBoundary: staticConfig.hoverBoundary,
|
|
53
56
|
scrollbarX,
|
|
@@ -63,9 +66,6 @@ const ScrollAreaRoot = forwardRef(
|
|
|
63
66
|
variant: staticConfig.variant,
|
|
64
67
|
viewport,
|
|
65
68
|
type: staticConfig.type,
|
|
66
|
-
isHovering,
|
|
67
|
-
isScrolling,
|
|
68
|
-
// Add ID-related values
|
|
69
69
|
rootId,
|
|
70
70
|
viewportId,
|
|
71
71
|
scrollbarXId,
|
|
@@ -73,14 +73,11 @@ const ScrollAreaRoot = forwardRef(
|
|
|
73
73
|
}),
|
|
74
74
|
[
|
|
75
75
|
content,
|
|
76
|
-
scrollState,
|
|
77
76
|
scrollbarX,
|
|
78
77
|
scrollbarY,
|
|
79
78
|
thumbX,
|
|
80
79
|
thumbY,
|
|
81
80
|
viewport,
|
|
82
|
-
isHovering,
|
|
83
|
-
isScrolling,
|
|
84
81
|
staticConfig,
|
|
85
82
|
rootId,
|
|
86
83
|
viewportId,
|
|
@@ -145,7 +142,7 @@ const ScrollAreaRoot = forwardRef(
|
|
|
145
142
|
}
|
|
146
143
|
return scrollbars;
|
|
147
144
|
}, [orientation]);
|
|
148
|
-
return /* @__PURE__ */ jsx(
|
|
145
|
+
return /* @__PURE__ */ jsx(ScrollAreaConfigContext.Provider, { value: configValue, children: /* @__PURE__ */ jsx(ScrollAreaStateContext.Provider, { value: stateValue, children: /* @__PURE__ */ jsxs(
|
|
149
146
|
"div",
|
|
150
147
|
{
|
|
151
148
|
ref,
|
|
@@ -161,7 +158,7 @@ const ScrollAreaRoot = forwardRef(
|
|
|
161
158
|
autoScrollbars
|
|
162
159
|
]
|
|
163
160
|
}
|
|
164
|
-
) });
|
|
161
|
+
) }) });
|
|
165
162
|
}
|
|
166
163
|
);
|
|
167
164
|
ScrollAreaRoot.displayName = "ScrollArea.Root";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { jsx } from "react/jsx-runtime";
|
|
2
|
-
import { forwardRef, useMemo, useCallback } from "react";
|
|
2
|
+
import { forwardRef, useRef, useMemo, useCallback } from "react";
|
|
3
3
|
import { useScrollAreaContext } from "../context/scroll-area-context.js";
|
|
4
4
|
import { ScrollTv } from "../tv.js";
|
|
5
5
|
import { handleScrollbarTrackClick, getScrollbarPositionStyle } from "../utils/index.js";
|
|
@@ -22,6 +22,8 @@ const ScrollAreaScrollbar = forwardRef(
|
|
|
22
22
|
scrollbarXId,
|
|
23
23
|
scrollbarYId
|
|
24
24
|
} = useScrollAreaContext();
|
|
25
|
+
const scrollStateRef = useRef(scrollState);
|
|
26
|
+
scrollStateRef.current = scrollState;
|
|
25
27
|
const hasOverflow = useHasOverflow(scrollState, orientation);
|
|
26
28
|
const shouldShow = useScrollbarShouldShow(type, hasOverflow, isScrolling, isHovering);
|
|
27
29
|
const scrollPercentage = useMemo(() => {
|
|
@@ -32,15 +34,23 @@ const ScrollAreaScrollbar = forwardRef(
|
|
|
32
34
|
const maxScroll = scrollState.scrollWidth - scrollState.clientWidth;
|
|
33
35
|
return maxScroll > 0 ? Math.round(scrollState.scrollLeft / maxScroll * 100) : 0;
|
|
34
36
|
}
|
|
35
|
-
}, [
|
|
37
|
+
}, [
|
|
38
|
+
orientation,
|
|
39
|
+
scrollState.scrollTop,
|
|
40
|
+
scrollState.scrollLeft,
|
|
41
|
+
scrollState.scrollHeight,
|
|
42
|
+
scrollState.clientHeight,
|
|
43
|
+
scrollState.scrollWidth,
|
|
44
|
+
scrollState.clientWidth
|
|
45
|
+
]);
|
|
36
46
|
const handleTrackClick = useCallback(
|
|
37
47
|
(e) => {
|
|
38
48
|
if (!viewport) return;
|
|
39
49
|
if (e.target === e.currentTarget) {
|
|
40
|
-
handleScrollbarTrackClick(e, viewport,
|
|
50
|
+
handleScrollbarTrackClick(e, viewport, scrollStateRef.current, orientation);
|
|
41
51
|
}
|
|
42
52
|
},
|
|
43
|
-
[viewport,
|
|
53
|
+
[viewport, orientation]
|
|
44
54
|
);
|
|
45
55
|
const tv = useMemo(
|
|
46
56
|
() => ScrollTv({
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { jsx } from "react/jsx-runtime";
|
|
2
2
|
import { forwardRef, useMemo, useCallback } from "react";
|
|
3
|
-
import {
|
|
3
|
+
import { useScrollAreaConfigContext } from "../context/scroll-area-context.js";
|
|
4
4
|
import { tcx } from "../../../../shared/utils/tcx/tcx.js";
|
|
5
5
|
const ScrollAreaViewport = forwardRef(
|
|
6
6
|
({ className, children, ...props }, ref) => {
|
|
7
|
-
const { setViewport, orientation, viewportId } =
|
|
7
|
+
const { setViewport, orientation, viewportId } = useScrollAreaConfigContext();
|
|
8
8
|
const scrollClass = useMemo(() => {
|
|
9
9
|
switch (orientation) {
|
|
10
10
|
case "horizontal":
|
|
@@ -1,3 +1,18 @@
|
|
|
1
|
-
import { ScrollAreaContextType } from '../types';
|
|
2
|
-
export declare const
|
|
1
|
+
import { ScrollAreaConfigContextType, ScrollAreaContextType, ScrollAreaStateContextType } from '../types';
|
|
2
|
+
export declare const ScrollAreaStateContext: import('react').Context<ScrollAreaStateContextType | null>;
|
|
3
|
+
export declare const ScrollAreaConfigContext: import('react').Context<ScrollAreaConfigContextType | null>;
|
|
4
|
+
/**
|
|
5
|
+
* Access only the frequently-changing state (scrollState, isHovering, isScrolling).
|
|
6
|
+
* Use this in components that need to react to scroll position changes.
|
|
7
|
+
*/
|
|
8
|
+
export declare function useScrollAreaStateContext(): ScrollAreaStateContextType;
|
|
9
|
+
/**
|
|
10
|
+
* Access only the rarely-changing config (orientation, setters, refs, IDs).
|
|
11
|
+
* Use this in components that do NOT need scrollState — they won't re-render on scroll.
|
|
12
|
+
*/
|
|
13
|
+
export declare function useScrollAreaConfigContext(): ScrollAreaConfigContextType;
|
|
14
|
+
/**
|
|
15
|
+
* Combined hook for components that need both state and config (Scrollbar, Thumb, Corner).
|
|
16
|
+
* Maintains backward compatibility with existing useScrollAreaContext() usage.
|
|
17
|
+
*/
|
|
3
18
|
export declare function useScrollAreaContext(): ScrollAreaContextType;
|
|
@@ -1,13 +1,30 @@
|
|
|
1
1
|
import { createContext, useContext } from "react";
|
|
2
|
-
const
|
|
3
|
-
|
|
4
|
-
|
|
2
|
+
const ScrollAreaStateContext = createContext(null);
|
|
3
|
+
const ScrollAreaConfigContext = createContext(null);
|
|
4
|
+
const ERROR_MESSAGE = "ScrollArea compound components must be used within ScrollArea";
|
|
5
|
+
function useScrollAreaStateContext() {
|
|
6
|
+
const context = useContext(ScrollAreaStateContext);
|
|
5
7
|
if (!context) {
|
|
6
|
-
throw new Error(
|
|
8
|
+
throw new Error(ERROR_MESSAGE);
|
|
7
9
|
}
|
|
8
10
|
return context;
|
|
9
11
|
}
|
|
12
|
+
function useScrollAreaConfigContext() {
|
|
13
|
+
const context = useContext(ScrollAreaConfigContext);
|
|
14
|
+
if (!context) {
|
|
15
|
+
throw new Error(ERROR_MESSAGE);
|
|
16
|
+
}
|
|
17
|
+
return context;
|
|
18
|
+
}
|
|
19
|
+
function useScrollAreaContext() {
|
|
20
|
+
const state = useScrollAreaStateContext();
|
|
21
|
+
const config = useScrollAreaConfigContext();
|
|
22
|
+
return { ...state, ...config };
|
|
23
|
+
}
|
|
10
24
|
export {
|
|
11
|
-
|
|
12
|
-
|
|
25
|
+
ScrollAreaConfigContext,
|
|
26
|
+
ScrollAreaStateContext,
|
|
27
|
+
useScrollAreaConfigContext,
|
|
28
|
+
useScrollAreaContext,
|
|
29
|
+
useScrollAreaStateContext
|
|
13
30
|
};
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { ScrollState } from '../types';
|
|
2
2
|
/**
|
|
3
|
-
* Merged scroll state and visibility management hook
|
|
3
|
+
* Merged scroll state and visibility management hook
|
|
4
4
|
*/
|
|
5
|
-
export declare function useScrollStateAndVisibility(viewport: HTMLDivElement | null): {
|
|
5
|
+
export declare function useScrollStateAndVisibility(viewport: HTMLDivElement | null, content: HTMLDivElement | null): {
|
|
6
6
|
scrollState: ScrollState;
|
|
7
7
|
isHovering: boolean;
|
|
8
8
|
isScrolling: boolean;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { useState, useRef, useCallback, useEffect } from "react";
|
|
2
|
-
function useScrollStateAndVisibility(viewport) {
|
|
2
|
+
function useScrollStateAndVisibility(viewport, content) {
|
|
3
3
|
const [scrollState, setScrollState] = useState({
|
|
4
4
|
scrollLeft: 0,
|
|
5
5
|
scrollTop: 0,
|
|
@@ -13,8 +13,6 @@ function useScrollStateAndVisibility(viewport) {
|
|
|
13
13
|
const scrollTimeoutRef = useRef();
|
|
14
14
|
const rafRef = useRef();
|
|
15
15
|
const resizeObserverRef = useRef();
|
|
16
|
-
const mutationObserverRef = useRef();
|
|
17
|
-
const mutationTimeoutRef = useRef();
|
|
18
16
|
const lastUpdateTimeRef = useRef(0);
|
|
19
17
|
const minUpdateIntervalRef = useRef(16);
|
|
20
18
|
const updateScrollState = useCallback(() => {
|
|
@@ -26,36 +24,36 @@ function useScrollStateAndVisibility(viewport) {
|
|
|
26
24
|
cancelAnimationFrame(rafRef.current);
|
|
27
25
|
}
|
|
28
26
|
rafRef.current = requestAnimationFrame(() => {
|
|
27
|
+
rafRef.current = void 0;
|
|
29
28
|
updateScrollState();
|
|
30
29
|
});
|
|
31
30
|
return;
|
|
32
31
|
}
|
|
33
32
|
if (rafRef.current) {
|
|
34
33
|
cancelAnimationFrame(rafRef.current);
|
|
34
|
+
rafRef.current = void 0;
|
|
35
35
|
}
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
return prevState;
|
|
58
|
-
});
|
|
36
|
+
const newState = {
|
|
37
|
+
scrollLeft: viewport.scrollLeft,
|
|
38
|
+
scrollTop: viewport.scrollTop,
|
|
39
|
+
scrollWidth: viewport.scrollWidth,
|
|
40
|
+
scrollHeight: viewport.scrollHeight,
|
|
41
|
+
clientWidth: viewport.clientWidth,
|
|
42
|
+
clientHeight: viewport.clientHeight
|
|
43
|
+
};
|
|
44
|
+
setScrollState((prevState) => {
|
|
45
|
+
const scrollLeftChanged = Math.abs(prevState.scrollLeft - newState.scrollLeft) > 0.5;
|
|
46
|
+
const scrollTopChanged = Math.abs(prevState.scrollTop - newState.scrollTop) > 0.5;
|
|
47
|
+
const scrollWidthChanged = prevState.scrollWidth !== newState.scrollWidth;
|
|
48
|
+
const scrollHeightChanged = prevState.scrollHeight !== newState.scrollHeight;
|
|
49
|
+
const clientWidthChanged = prevState.clientWidth !== newState.clientWidth;
|
|
50
|
+
const clientHeightChanged = prevState.clientHeight !== newState.clientHeight;
|
|
51
|
+
const hasChanges = scrollLeftChanged || scrollTopChanged || scrollWidthChanged || scrollHeightChanged || clientWidthChanged || clientHeightChanged;
|
|
52
|
+
if (hasChanges) {
|
|
53
|
+
lastUpdateTimeRef.current = now;
|
|
54
|
+
return newState;
|
|
55
|
+
}
|
|
56
|
+
return prevState;
|
|
59
57
|
});
|
|
60
58
|
}, [viewport]);
|
|
61
59
|
const delayedUpdateScrollState = useCallback(() => {
|
|
@@ -92,54 +90,19 @@ function useScrollStateAndVisibility(viewport) {
|
|
|
92
90
|
passive: true,
|
|
93
91
|
signal,
|
|
94
92
|
capture: false
|
|
95
|
-
// Avoid unnecessary event capture
|
|
96
93
|
});
|
|
97
94
|
window.addEventListener("resize", handleResize, {
|
|
98
95
|
passive: true,
|
|
99
96
|
signal
|
|
100
97
|
});
|
|
101
98
|
if (window.ResizeObserver) {
|
|
102
|
-
resizeObserverRef.current = new ResizeObserver((
|
|
103
|
-
|
|
104
|
-
if (entry.target === viewport) {
|
|
105
|
-
updateScrollState();
|
|
106
|
-
break;
|
|
107
|
-
}
|
|
108
|
-
}
|
|
99
|
+
resizeObserverRef.current = new ResizeObserver(() => {
|
|
100
|
+
updateScrollState();
|
|
109
101
|
});
|
|
110
102
|
resizeObserverRef.current.observe(viewport);
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
const hasLayoutChanges = mutations.some((mutation) => {
|
|
115
|
-
if (mutation.type === "childList") {
|
|
116
|
-
return mutation.addedNodes.length > 0 || mutation.removedNodes.length > 0;
|
|
117
|
-
}
|
|
118
|
-
if (mutation.type === "attributes") {
|
|
119
|
-
const attr = mutation.attributeName;
|
|
120
|
-
return attr === "style" || attr === "class";
|
|
121
|
-
}
|
|
122
|
-
return mutation.type === "characterData";
|
|
123
|
-
});
|
|
124
|
-
if (!hasLayoutChanges) return;
|
|
125
|
-
if (mutationTimeoutRef.current) {
|
|
126
|
-
clearTimeout(mutationTimeoutRef.current);
|
|
127
|
-
}
|
|
128
|
-
mutationTimeoutRef.current = window.setTimeout(() => {
|
|
129
|
-
updateScrollState();
|
|
130
|
-
}, 16);
|
|
131
|
-
});
|
|
132
|
-
mutationObserverRef.current.observe(viewport, {
|
|
133
|
-
childList: true,
|
|
134
|
-
subtree: true,
|
|
135
|
-
attributes: true,
|
|
136
|
-
attributeFilter: ["style", "class"],
|
|
137
|
-
// Only listen to attributes that affect layout
|
|
138
|
-
characterData: true,
|
|
139
|
-
characterDataOldValue: false,
|
|
140
|
-
// No need for old value, improve performance
|
|
141
|
-
attributeOldValue: false
|
|
142
|
-
});
|
|
103
|
+
if (content) {
|
|
104
|
+
resizeObserverRef.current.observe(content);
|
|
105
|
+
}
|
|
143
106
|
}
|
|
144
107
|
delayedUpdateScrollState();
|
|
145
108
|
return () => {
|
|
@@ -148,10 +111,6 @@ function useScrollStateAndVisibility(viewport) {
|
|
|
148
111
|
clearTimeout(scrollTimeoutRef.current);
|
|
149
112
|
scrollTimeoutRef.current = void 0;
|
|
150
113
|
}
|
|
151
|
-
if (mutationTimeoutRef.current) {
|
|
152
|
-
clearTimeout(mutationTimeoutRef.current);
|
|
153
|
-
mutationTimeoutRef.current = void 0;
|
|
154
|
-
}
|
|
155
114
|
if (rafRef.current) {
|
|
156
115
|
cancelAnimationFrame(rafRef.current);
|
|
157
116
|
rafRef.current = void 0;
|
|
@@ -160,12 +119,8 @@ function useScrollStateAndVisibility(viewport) {
|
|
|
160
119
|
resizeObserverRef.current.disconnect();
|
|
161
120
|
resizeObserverRef.current = void 0;
|
|
162
121
|
}
|
|
163
|
-
if (mutationObserverRef.current) {
|
|
164
|
-
mutationObserverRef.current.disconnect();
|
|
165
|
-
mutationObserverRef.current = void 0;
|
|
166
|
-
}
|
|
167
122
|
};
|
|
168
|
-
}, [viewport, handleScroll, delayedUpdateScrollState]);
|
|
123
|
+
}, [viewport, content, handleScroll, delayedUpdateScrollState, updateScrollState]);
|
|
169
124
|
const handleMouseEnter = useCallback(() => setIsHovering(true), []);
|
|
170
125
|
const handleMouseLeave = useCallback(() => setIsHovering(false), []);
|
|
171
126
|
return {
|
|
@@ -14,7 +14,7 @@ export declare function useThumbStyle(scrollState: ScrollState, orientation: "ve
|
|
|
14
14
|
top?: undefined;
|
|
15
15
|
};
|
|
16
16
|
/**
|
|
17
|
-
*
|
|
17
|
+
* High-performance thumb drag hook
|
|
18
18
|
*/
|
|
19
19
|
export declare function useThumbDrag(viewport: HTMLDivElement | null, scrollState: ScrollState, orientation: "vertical" | "horizontal"): {
|
|
20
20
|
isDragging: boolean;
|
|
@@ -42,16 +42,13 @@ function useThumbDrag(viewport, scrollState, orientation) {
|
|
|
42
42
|
const isDragging = useRef(false);
|
|
43
43
|
const startPos = useRef(0);
|
|
44
44
|
const startScroll = useRef(0);
|
|
45
|
-
const rafId = useRef();
|
|
46
45
|
const cleanupRef = useRef(null);
|
|
46
|
+
const scrollStateRef = useRef(scrollState);
|
|
47
|
+
scrollStateRef.current = scrollState;
|
|
47
48
|
const dragContextRef = useRef(null);
|
|
48
49
|
useEffect(() => {
|
|
49
50
|
return () => {
|
|
50
51
|
isDragging.current = false;
|
|
51
|
-
if (rafId.current) {
|
|
52
|
-
cancelAnimationFrame(rafId.current);
|
|
53
|
-
rafId.current = void 0;
|
|
54
|
-
}
|
|
55
52
|
if (cleanupRef.current) {
|
|
56
53
|
cleanupRef.current();
|
|
57
54
|
cleanupRef.current = null;
|
|
@@ -61,13 +58,21 @@ function useThumbDrag(viewport, scrollState, orientation) {
|
|
|
61
58
|
const handleMouseDown = useCallback(
|
|
62
59
|
(e) => {
|
|
63
60
|
if (!viewport) return;
|
|
61
|
+
const currentScrollState = scrollStateRef.current;
|
|
64
62
|
const target = e.currentTarget;
|
|
65
63
|
const scrollbar = target.closest('[role="scrollbar"]');
|
|
66
64
|
if (!scrollbar) return;
|
|
67
65
|
const scrollbarRect = scrollbar.getBoundingClientRect();
|
|
68
|
-
const scrollableRange = orientation === "vertical" ? Math.max(0,
|
|
66
|
+
const scrollableRange = orientation === "vertical" ? Math.max(0, currentScrollState.scrollHeight - currentScrollState.clientHeight) : Math.max(0, currentScrollState.scrollWidth - currentScrollState.clientWidth);
|
|
69
67
|
const scrollbarRange = orientation === "vertical" ? scrollbarRect.height : scrollbarRect.width;
|
|
70
68
|
if (scrollableRange <= 0 || scrollbarRange <= 0) return;
|
|
69
|
+
const thumbFraction = Math.max(
|
|
70
|
+
0.1,
|
|
71
|
+
orientation === "vertical" ? currentScrollState.clientHeight / currentScrollState.scrollHeight : currentScrollState.clientWidth / currentScrollState.scrollWidth
|
|
72
|
+
);
|
|
73
|
+
const thumbSizePixels = scrollbarRange * thumbFraction;
|
|
74
|
+
const effectiveTrackRange = scrollbarRange - thumbSizePixels;
|
|
75
|
+
if (effectiveTrackRange <= 0) return;
|
|
71
76
|
dragContextRef.current = {
|
|
72
77
|
scrollbarRect,
|
|
73
78
|
scrollableRange,
|
|
@@ -75,34 +80,26 @@ function useThumbDrag(viewport, scrollState, orientation) {
|
|
|
75
80
|
};
|
|
76
81
|
isDragging.current = true;
|
|
77
82
|
startPos.current = orientation === "vertical" ? e.clientY : e.clientX;
|
|
78
|
-
startScroll.current = orientation === "vertical" ?
|
|
79
|
-
const scrollRatio = scrollableRange /
|
|
83
|
+
startScroll.current = orientation === "vertical" ? currentScrollState.scrollTop : currentScrollState.scrollLeft;
|
|
84
|
+
const scrollRatio = scrollableRange / effectiveTrackRange;
|
|
80
85
|
const handleMouseMove = (e2) => {
|
|
81
86
|
if (!isDragging.current || !viewport || !dragContextRef.current) return;
|
|
82
|
-
|
|
83
|
-
|
|
87
|
+
const currentPos = orientation === "vertical" ? e2.clientY : e2.clientX;
|
|
88
|
+
const delta = currentPos - startPos.current;
|
|
89
|
+
const scrollDelta = delta * scrollRatio;
|
|
90
|
+
const newScrollValue = Math.max(
|
|
91
|
+
0,
|
|
92
|
+
Math.min(startScroll.current + scrollDelta, dragContextRef.current.scrollableRange)
|
|
93
|
+
);
|
|
94
|
+
if (orientation === "vertical") {
|
|
95
|
+
viewport.scrollTop = newScrollValue;
|
|
96
|
+
} else {
|
|
97
|
+
viewport.scrollLeft = newScrollValue;
|
|
84
98
|
}
|
|
85
|
-
rafId.current = requestAnimationFrame(() => {
|
|
86
|
-
const currentPos = orientation === "vertical" ? e2.clientY : e2.clientX;
|
|
87
|
-
const delta = currentPos - startPos.current;
|
|
88
|
-
const scrollDelta = delta * scrollRatio;
|
|
89
|
-
const newScrollValue = Math.max(
|
|
90
|
-
0,
|
|
91
|
-
Math.min(startScroll.current + scrollDelta, dragContextRef.current.scrollableRange)
|
|
92
|
-
);
|
|
93
|
-
if (orientation === "vertical") {
|
|
94
|
-
viewport.scrollTop = newScrollValue;
|
|
95
|
-
} else {
|
|
96
|
-
viewport.scrollLeft = newScrollValue;
|
|
97
|
-
}
|
|
98
|
-
});
|
|
99
99
|
};
|
|
100
100
|
const handleMouseUp = () => {
|
|
101
101
|
isDragging.current = false;
|
|
102
102
|
dragContextRef.current = null;
|
|
103
|
-
if (rafId.current) {
|
|
104
|
-
cancelAnimationFrame(rafId.current);
|
|
105
|
-
}
|
|
106
103
|
document.removeEventListener("mousemove", handleMouseMove);
|
|
107
104
|
document.removeEventListener("mouseup", handleMouseUp);
|
|
108
105
|
cleanupRef.current = null;
|
|
@@ -116,7 +113,7 @@ function useThumbDrag(viewport, scrollState, orientation) {
|
|
|
116
113
|
document.addEventListener("mouseup", handleMouseUp, { passive: true });
|
|
117
114
|
e.preventDefault();
|
|
118
115
|
},
|
|
119
|
-
[viewport, orientation
|
|
116
|
+
[viewport, orientation]
|
|
120
117
|
);
|
|
121
118
|
return {
|
|
122
119
|
isDragging: isDragging.current,
|
|
@@ -24,14 +24,22 @@ export interface ScrollPosition {
|
|
|
24
24
|
* Render prop function type
|
|
25
25
|
*/
|
|
26
26
|
export type ScrollAreaRenderProp = (position: ScrollPosition) => React.ReactNode;
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
27
|
+
/**
|
|
28
|
+
* Frequently changing state — triggers re-renders on scroll
|
|
29
|
+
*/
|
|
30
|
+
export interface ScrollAreaStateContextType {
|
|
30
31
|
isHovering: boolean;
|
|
31
32
|
isScrolling: boolean;
|
|
33
|
+
scrollState: ScrollState;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Rarely changing config, refs, setters — does NOT change on scroll
|
|
37
|
+
*/
|
|
38
|
+
export interface ScrollAreaConfigContextType {
|
|
39
|
+
content: HTMLDivElement | null;
|
|
40
|
+
hoverBoundary: HoverBoundary;
|
|
32
41
|
orientation: ScrollOrientation;
|
|
33
42
|
rootId: string;
|
|
34
|
-
scrollState: ScrollState;
|
|
35
43
|
scrollbarMode: ScrollbarMode;
|
|
36
44
|
scrollbarX: HTMLDivElement | null;
|
|
37
45
|
scrollbarXId: string;
|
|
@@ -50,6 +58,10 @@ export interface ScrollAreaContextType {
|
|
|
50
58
|
viewport: HTMLDivElement | null;
|
|
51
59
|
viewportId: string;
|
|
52
60
|
}
|
|
61
|
+
/**
|
|
62
|
+
* Combined context type for components that need both state and config
|
|
63
|
+
*/
|
|
64
|
+
export type ScrollAreaContextType = ScrollAreaStateContextType & ScrollAreaConfigContextType;
|
|
53
65
|
export interface ScrollAreaProps extends Omit<React.ComponentPropsWithoutRef<"div">, "children"> {
|
|
54
66
|
/** Accessible name */
|
|
55
67
|
"aria-label"?: string;
|
package/dist/index.js
CHANGED
|
@@ -233,7 +233,6 @@ import { ScrollArea } from "./components/scroll-area/src/scroll-area.js";
|
|
|
233
233
|
import { useScrollStateAndVisibility } from "./components/scroll-area/src/hooks/use-scroll-state-and-visibility.js";
|
|
234
234
|
import { useThumbDrag, useThumbStyle } from "./components/scroll-area/src/hooks/use-thumb.js";
|
|
235
235
|
import { useHasOverflow, useScrollbarShouldShow } from "./components/scroll-area/src/hooks/use-scrollbar.js";
|
|
236
|
-
import { useScrollPerformanceMonitor } from "./components/scroll-area/src/hooks/use-scroll-performance-monitor.js";
|
|
237
236
|
import { SearchInput } from "./components/search-input/src/search-input.js";
|
|
238
237
|
import { Segmented } from "./components/segmented/src/segmented.js";
|
|
239
238
|
import { Select } from "./components/select/src/select.js";
|
|
@@ -770,7 +769,6 @@ export {
|
|
|
770
769
|
useRangeContext,
|
|
771
770
|
useRangeTupleContext,
|
|
772
771
|
useRenderData,
|
|
773
|
-
useScrollPerformanceMonitor,
|
|
774
772
|
useScrollStateAndVisibility,
|
|
775
773
|
useScrollbarShouldShow,
|
|
776
774
|
useSlot,
|
package/package.json
CHANGED
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
interface PerformanceMetrics {
|
|
2
|
-
averageFrameTime: number;
|
|
3
|
-
droppedFrames: number;
|
|
4
|
-
maxFrameTime: number;
|
|
5
|
-
scrollEventFrequency: number;
|
|
6
|
-
updateFrequency: number;
|
|
7
|
-
}
|
|
8
|
-
interface PerformanceMonitorOptions {
|
|
9
|
-
enabled?: boolean;
|
|
10
|
-
frameTimeThreshold?: number;
|
|
11
|
-
logInterval?: number;
|
|
12
|
-
}
|
|
13
|
-
/**
|
|
14
|
-
* 🔍 ScrollArea performance monitoring Hook
|
|
15
|
-
*
|
|
16
|
-
* Used to monitor and diagnose scroll performance issues, including:
|
|
17
|
-
* - Frame rate monitoring
|
|
18
|
-
* - Event frequency statistics
|
|
19
|
-
* - Performance bottleneck detection
|
|
20
|
-
* - Real-time performance reporting
|
|
21
|
-
*/
|
|
22
|
-
export declare function useScrollPerformanceMonitor(viewport: HTMLDivElement | null, options?: PerformanceMonitorOptions): PerformanceMetrics | null;
|
|
23
|
-
export {};
|