@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
|
@@ -246,6 +246,13 @@ const buttonTv = tcv({
|
|
|
246
246
|
variant: "secondary",
|
|
247
247
|
active: false,
|
|
248
248
|
class: { button: "hover:bg-secondary-background" }
|
|
249
|
+
},
|
|
250
|
+
{
|
|
251
|
+
disabled: false,
|
|
252
|
+
loading: false,
|
|
253
|
+
variant: "secondary-destruct",
|
|
254
|
+
active: false,
|
|
255
|
+
class: { button: "hover:bg-danger-background/10" }
|
|
249
256
|
}
|
|
250
257
|
],
|
|
251
258
|
defaultVariants: {
|
|
@@ -43,6 +43,10 @@ interface CommandItemProps extends Omit<HTMLProps<HTMLDivElement>, "onSelect"> {
|
|
|
43
43
|
keywords?: string[];
|
|
44
44
|
onSelect?: (value: string) => void;
|
|
45
45
|
prefixElement?: ReactNode;
|
|
46
|
+
/**
|
|
47
|
+
* When true, this item will be set as the selected item and scrolled into view.
|
|
48
|
+
*/
|
|
49
|
+
selected?: boolean;
|
|
46
50
|
shortcut?: {
|
|
47
51
|
keys?: ReactNode;
|
|
48
52
|
modifier?: KbdKey | KbdKey[] | undefined;
|
|
@@ -54,6 +58,9 @@ interface CommandItemProps extends Omit<HTMLProps<HTMLDivElement>, "onSelect"> {
|
|
|
54
58
|
interface CommandListProps extends ScrollAreaProps {
|
|
55
59
|
children: React.ReactNode;
|
|
56
60
|
className?: string;
|
|
61
|
+
classNames?: {
|
|
62
|
+
content?: string;
|
|
63
|
+
};
|
|
57
64
|
label?: string;
|
|
58
65
|
}
|
|
59
66
|
|
|
@@ -106,6 +113,11 @@ interface CommandProps extends Omit<react__default.HTMLAttributes<HTMLDivElement
|
|
|
106
113
|
* Event handler called when the selected item of the menu changes.
|
|
107
114
|
*/
|
|
108
115
|
onChange?: (value: string) => void;
|
|
116
|
+
/**
|
|
117
|
+
* Optionally set to `true` to enable selection mode.
|
|
118
|
+
* When enabled, items with `selected` prop will be scrolled into view on mount.
|
|
119
|
+
*/
|
|
120
|
+
selection?: boolean;
|
|
109
121
|
/**
|
|
110
122
|
* Optionally set to `false` to turn off the automatic filtering and sorting.
|
|
111
123
|
* If `false`, you must conditionally render valid items based on the search query yourself.
|
|
@@ -140,6 +152,7 @@ type Context = {
|
|
|
140
152
|
labelId: string;
|
|
141
153
|
listId: string;
|
|
142
154
|
listInnerRef: react__default.MutableRefObject<HTMLDivElement | null>;
|
|
155
|
+
selection?: boolean;
|
|
143
156
|
size?: "default" | "large";
|
|
144
157
|
store: Store;
|
|
145
158
|
value: (id: string, value?: string, keywords?: string[]) => void;
|
|
@@ -42,6 +42,7 @@ const Command = forwardRef((props, forwardedRef) => {
|
|
|
42
42
|
onChange: onValueChange,
|
|
43
43
|
filter,
|
|
44
44
|
shouldFilter,
|
|
45
|
+
selection,
|
|
45
46
|
loop,
|
|
46
47
|
size = "default",
|
|
47
48
|
variant = "default",
|
|
@@ -253,6 +254,7 @@ const Command = forwardRef((props, forwardedRef) => {
|
|
|
253
254
|
propsRef,
|
|
254
255
|
schedule,
|
|
255
256
|
selectFirstItem,
|
|
257
|
+
selection,
|
|
256
258
|
size,
|
|
257
259
|
sort,
|
|
258
260
|
state,
|
|
@@ -6,6 +6,10 @@ export interface CommandItemProps extends Omit<HTMLProps<HTMLDivElement>, "onSel
|
|
|
6
6
|
keywords?: string[];
|
|
7
7
|
onSelect?: (value: string) => void;
|
|
8
8
|
prefixElement?: ReactNode;
|
|
9
|
+
/**
|
|
10
|
+
* When true, this item will be set as the selected item and scrolled into view.
|
|
11
|
+
*/
|
|
12
|
+
selected?: boolean;
|
|
9
13
|
shortcut?: {
|
|
10
14
|
keys?: ReactNode;
|
|
11
15
|
modifier?: KbdKey | KbdKey[] | undefined;
|
|
@@ -16,6 +16,7 @@ const CommandItem = memo(
|
|
|
16
16
|
forceMount,
|
|
17
17
|
keywords,
|
|
18
18
|
onSelect,
|
|
19
|
+
selected: selectedProp,
|
|
19
20
|
value,
|
|
20
21
|
children,
|
|
21
22
|
prefixElement,
|
|
@@ -50,6 +51,11 @@ const CommandItem = memo(
|
|
|
50
51
|
const stableKeywords = useMemo(() => keywords || [], [keywords]);
|
|
51
52
|
const valueRef = useValue(id, ref, valueDeps, stableKeywords);
|
|
52
53
|
const store = context.store;
|
|
54
|
+
useEffect(() => {
|
|
55
|
+
if (context.selection && selectedProp && valueRef.current) {
|
|
56
|
+
store.setState("value", valueRef.current);
|
|
57
|
+
}
|
|
58
|
+
}, [selectedProp]);
|
|
53
59
|
const selected = useCommandState(
|
|
54
60
|
(state) => Boolean(state.value && state.value === (valueRef == null ? void 0 : valueRef.current))
|
|
55
61
|
);
|
|
@@ -2,6 +2,9 @@ import { ScrollAreaProps } from '../../../scroll-area/src';
|
|
|
2
2
|
export interface CommandListProps extends ScrollAreaProps {
|
|
3
3
|
children: React.ReactNode;
|
|
4
4
|
className?: string;
|
|
5
|
+
classNames?: {
|
|
6
|
+
content?: string;
|
|
7
|
+
};
|
|
5
8
|
label?: string;
|
|
6
9
|
}
|
|
7
10
|
export declare const CommandList: import('react').ForwardRefExoticComponent<CommandListProps & import('react').RefAttributes<HTMLDivElement>>;
|
|
@@ -12,6 +12,9 @@ const CommandList = forwardRef((props, forwardedRef) => {
|
|
|
12
12
|
label = "Suggestions",
|
|
13
13
|
hoverBoundary = "none",
|
|
14
14
|
scrollbarMode = "padding-b",
|
|
15
|
+
classNames = {
|
|
16
|
+
content: ""
|
|
17
|
+
},
|
|
15
18
|
orientation,
|
|
16
19
|
variant,
|
|
17
20
|
type,
|
|
@@ -58,6 +61,7 @@ const CommandList = forwardRef((props, forwardedRef) => {
|
|
|
58
61
|
},
|
|
59
62
|
...rest,
|
|
60
63
|
className: tcx(tv.root({ className })),
|
|
64
|
+
style: context.selection ? { scrollPaddingBlock: 8 } : void 0,
|
|
61
65
|
role: "listbox",
|
|
62
66
|
tabIndex: -1,
|
|
63
67
|
"aria-activedescendant": selectedItemId,
|
|
@@ -66,7 +70,7 @@ const CommandList = forwardRef((props, forwardedRef) => {
|
|
|
66
70
|
children: /* @__PURE__ */ jsx(
|
|
67
71
|
ScrollArea.Content,
|
|
68
72
|
{
|
|
69
|
-
className: tcx(tv.content()),
|
|
73
|
+
className: tcx(tv.content(), classNames == null ? void 0 : classNames.content),
|
|
70
74
|
ref: (el) => {
|
|
71
75
|
height.current = el;
|
|
72
76
|
if (context.listInnerRef) {
|
|
@@ -16,6 +16,7 @@ interface CreateCommandContextOptions {
|
|
|
16
16
|
propsRef: React.MutableRefObject<CommandProps>;
|
|
17
17
|
schedule: (id: string | number, cb: () => void) => void;
|
|
18
18
|
selectFirstItem: () => void;
|
|
19
|
+
selection?: boolean;
|
|
19
20
|
size?: "default" | "large";
|
|
20
21
|
sort: () => void;
|
|
21
22
|
state: React.MutableRefObject<State>;
|
|
@@ -13,6 +13,7 @@ function createCommandContext(options) {
|
|
|
13
13
|
propsRef,
|
|
14
14
|
schedule,
|
|
15
15
|
selectFirstItem,
|
|
16
|
+
selection,
|
|
16
17
|
size,
|
|
17
18
|
sort,
|
|
18
19
|
state,
|
|
@@ -93,6 +94,7 @@ function createCommandContext(options) {
|
|
|
93
94
|
inputId,
|
|
94
95
|
labelId,
|
|
95
96
|
listInnerRef,
|
|
97
|
+
selection,
|
|
96
98
|
store,
|
|
97
99
|
size,
|
|
98
100
|
variant
|
|
@@ -27,6 +27,11 @@ export interface CommandProps extends Omit<React.HTMLAttributes<HTMLDivElement>,
|
|
|
27
27
|
* Event handler called when the selected item of the menu changes.
|
|
28
28
|
*/
|
|
29
29
|
onChange?: (value: string) => void;
|
|
30
|
+
/**
|
|
31
|
+
* Optionally set to `true` to enable selection mode.
|
|
32
|
+
* When enabled, items with `selected` prop will be scrolled into view on mount.
|
|
33
|
+
*/
|
|
34
|
+
selection?: boolean;
|
|
30
35
|
/**
|
|
31
36
|
* Optionally set to `false` to turn off the automatic filtering and sorting.
|
|
32
37
|
* If `false`, you must conditionally render valid items based on the search query yourself.
|
|
@@ -61,6 +66,7 @@ export type Context = {
|
|
|
61
66
|
labelId: string;
|
|
62
67
|
listId: string;
|
|
63
68
|
listInnerRef: React.MutableRefObject<HTMLDivElement | null>;
|
|
69
|
+
selection?: boolean;
|
|
64
70
|
size?: "default" | "large";
|
|
65
71
|
store: Store;
|
|
66
72
|
value: (id: string, value?: string, keywords?: string[]) => void;
|
|
@@ -61,9 +61,9 @@ declare const ScrollArea: react.ForwardRefExoticComponent<ScrollAreaProps & reac
|
|
|
61
61
|
};
|
|
62
62
|
|
|
63
63
|
/**
|
|
64
|
-
* Merged scroll state and visibility management hook
|
|
64
|
+
* Merged scroll state and visibility management hook
|
|
65
65
|
*/
|
|
66
|
-
declare function useScrollStateAndVisibility(viewport: HTMLDivElement | null): {
|
|
66
|
+
declare function useScrollStateAndVisibility(viewport: HTMLDivElement | null, content: HTMLDivElement | null): {
|
|
67
67
|
scrollState: ScrollState;
|
|
68
68
|
isHovering: boolean;
|
|
69
69
|
isScrolling: boolean;
|
|
@@ -86,7 +86,7 @@ declare function useThumbStyle(scrollState: ScrollState, orientation: "vertical"
|
|
|
86
86
|
top?: undefined;
|
|
87
87
|
};
|
|
88
88
|
/**
|
|
89
|
-
*
|
|
89
|
+
* High-performance thumb drag hook
|
|
90
90
|
*/
|
|
91
91
|
declare function useThumbDrag(viewport: HTMLDivElement | null, scrollState: ScrollState, orientation: "vertical" | "horizontal"): {
|
|
92
92
|
isDragging: boolean;
|
|
@@ -102,27 +102,4 @@ declare function useHasOverflow(scrollState: ScrollState, orientation: "vertical
|
|
|
102
102
|
*/
|
|
103
103
|
declare function useScrollbarShouldShow(type: ScrollbarVisibilityType, hasOverflow: boolean, isScrolling: boolean, isHovering: boolean): boolean;
|
|
104
104
|
|
|
105
|
-
|
|
106
|
-
averageFrameTime: number;
|
|
107
|
-
droppedFrames: number;
|
|
108
|
-
maxFrameTime: number;
|
|
109
|
-
scrollEventFrequency: number;
|
|
110
|
-
updateFrequency: number;
|
|
111
|
-
}
|
|
112
|
-
interface PerformanceMonitorOptions {
|
|
113
|
-
enabled?: boolean;
|
|
114
|
-
frameTimeThreshold?: number;
|
|
115
|
-
logInterval?: number;
|
|
116
|
-
}
|
|
117
|
-
/**
|
|
118
|
-
* 🔍 ScrollArea performance monitoring Hook
|
|
119
|
-
*
|
|
120
|
-
* Used to monitor and diagnose scroll performance issues, including:
|
|
121
|
-
* - Frame rate monitoring
|
|
122
|
-
* - Event frequency statistics
|
|
123
|
-
* - Performance bottleneck detection
|
|
124
|
-
* - Real-time performance reporting
|
|
125
|
-
*/
|
|
126
|
-
declare function useScrollPerformanceMonitor(viewport: HTMLDivElement | null, options?: PerformanceMonitorOptions): PerformanceMetrics | null;
|
|
127
|
-
|
|
128
|
-
export { ScrollArea, type ScrollAreaProps, type ScrollbarProps, type ThumbProps, useHasOverflow, useScrollPerformanceMonitor, useScrollStateAndVisibility, useScrollbarShouldShow, useThumbDrag, useThumbStyle };
|
|
105
|
+
export { ScrollArea, type ScrollAreaProps, type ScrollbarProps, type ThumbProps, useHasOverflow, useScrollStateAndVisibility, useScrollbarShouldShow, useThumbDrag, useThumbStyle };
|
|
@@ -1,15 +1,29 @@
|
|
|
1
1
|
import { forwardRef, useState, useId, useMemo, createContext, useCallback, useRef, useEffect, useContext } from "react";
|
|
2
2
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { tcv, tcx } from "../../../shared/utils/tcx/tcx.js";
|
|
4
|
-
var
|
|
5
|
-
|
|
6
|
-
|
|
4
|
+
var ScrollAreaStateContext = createContext(null);
|
|
5
|
+
var ScrollAreaConfigContext = createContext(null);
|
|
6
|
+
var ERROR_MESSAGE = "ScrollArea compound components must be used within ScrollArea";
|
|
7
|
+
function useScrollAreaStateContext() {
|
|
8
|
+
const context = useContext(ScrollAreaStateContext);
|
|
9
|
+
if (!context) {
|
|
10
|
+
throw new Error(ERROR_MESSAGE);
|
|
11
|
+
}
|
|
12
|
+
return context;
|
|
13
|
+
}
|
|
14
|
+
function useScrollAreaConfigContext() {
|
|
15
|
+
const context = useContext(ScrollAreaConfigContext);
|
|
7
16
|
if (!context) {
|
|
8
|
-
throw new Error(
|
|
17
|
+
throw new Error(ERROR_MESSAGE);
|
|
9
18
|
}
|
|
10
19
|
return context;
|
|
11
20
|
}
|
|
12
|
-
function
|
|
21
|
+
function useScrollAreaContext() {
|
|
22
|
+
const state = useScrollAreaStateContext();
|
|
23
|
+
const config = useScrollAreaConfigContext();
|
|
24
|
+
return { ...state, ...config };
|
|
25
|
+
}
|
|
26
|
+
function useScrollStateAndVisibility(viewport, content) {
|
|
13
27
|
const [scrollState, setScrollState] = useState({
|
|
14
28
|
scrollLeft: 0,
|
|
15
29
|
scrollTop: 0,
|
|
@@ -23,8 +37,6 @@ function useScrollStateAndVisibility(viewport) {
|
|
|
23
37
|
const scrollTimeoutRef = useRef();
|
|
24
38
|
const rafRef = useRef();
|
|
25
39
|
const resizeObserverRef = useRef();
|
|
26
|
-
const mutationObserverRef = useRef();
|
|
27
|
-
const mutationTimeoutRef = useRef();
|
|
28
40
|
const lastUpdateTimeRef = useRef(0);
|
|
29
41
|
const minUpdateIntervalRef = useRef(16);
|
|
30
42
|
const updateScrollState = useCallback(() => {
|
|
@@ -36,36 +48,36 @@ function useScrollStateAndVisibility(viewport) {
|
|
|
36
48
|
cancelAnimationFrame(rafRef.current);
|
|
37
49
|
}
|
|
38
50
|
rafRef.current = requestAnimationFrame(() => {
|
|
51
|
+
rafRef.current = void 0;
|
|
39
52
|
updateScrollState();
|
|
40
53
|
});
|
|
41
54
|
return;
|
|
42
55
|
}
|
|
43
56
|
if (rafRef.current) {
|
|
44
57
|
cancelAnimationFrame(rafRef.current);
|
|
58
|
+
rafRef.current = void 0;
|
|
45
59
|
}
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
return prevState;
|
|
68
|
-
});
|
|
60
|
+
const newState = {
|
|
61
|
+
scrollLeft: viewport.scrollLeft,
|
|
62
|
+
scrollTop: viewport.scrollTop,
|
|
63
|
+
scrollWidth: viewport.scrollWidth,
|
|
64
|
+
scrollHeight: viewport.scrollHeight,
|
|
65
|
+
clientWidth: viewport.clientWidth,
|
|
66
|
+
clientHeight: viewport.clientHeight
|
|
67
|
+
};
|
|
68
|
+
setScrollState((prevState) => {
|
|
69
|
+
const scrollLeftChanged = Math.abs(prevState.scrollLeft - newState.scrollLeft) > 0.5;
|
|
70
|
+
const scrollTopChanged = Math.abs(prevState.scrollTop - newState.scrollTop) > 0.5;
|
|
71
|
+
const scrollWidthChanged = prevState.scrollWidth !== newState.scrollWidth;
|
|
72
|
+
const scrollHeightChanged = prevState.scrollHeight !== newState.scrollHeight;
|
|
73
|
+
const clientWidthChanged = prevState.clientWidth !== newState.clientWidth;
|
|
74
|
+
const clientHeightChanged = prevState.clientHeight !== newState.clientHeight;
|
|
75
|
+
const hasChanges = scrollLeftChanged || scrollTopChanged || scrollWidthChanged || scrollHeightChanged || clientWidthChanged || clientHeightChanged;
|
|
76
|
+
if (hasChanges) {
|
|
77
|
+
lastUpdateTimeRef.current = now;
|
|
78
|
+
return newState;
|
|
79
|
+
}
|
|
80
|
+
return prevState;
|
|
69
81
|
});
|
|
70
82
|
}, [viewport]);
|
|
71
83
|
const delayedUpdateScrollState = useCallback(() => {
|
|
@@ -102,54 +114,19 @@ function useScrollStateAndVisibility(viewport) {
|
|
|
102
114
|
passive: true,
|
|
103
115
|
signal,
|
|
104
116
|
capture: false
|
|
105
|
-
// Avoid unnecessary event capture
|
|
106
117
|
});
|
|
107
118
|
window.addEventListener("resize", handleResize, {
|
|
108
119
|
passive: true,
|
|
109
120
|
signal
|
|
110
121
|
});
|
|
111
122
|
if (window.ResizeObserver) {
|
|
112
|
-
resizeObserverRef.current = new ResizeObserver((
|
|
113
|
-
|
|
114
|
-
if (entry.target === viewport) {
|
|
115
|
-
updateScrollState();
|
|
116
|
-
break;
|
|
117
|
-
}
|
|
118
|
-
}
|
|
123
|
+
resizeObserverRef.current = new ResizeObserver(() => {
|
|
124
|
+
updateScrollState();
|
|
119
125
|
});
|
|
120
126
|
resizeObserverRef.current.observe(viewport);
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
const hasLayoutChanges = mutations.some((mutation) => {
|
|
125
|
-
if (mutation.type === "childList") {
|
|
126
|
-
return mutation.addedNodes.length > 0 || mutation.removedNodes.length > 0;
|
|
127
|
-
}
|
|
128
|
-
if (mutation.type === "attributes") {
|
|
129
|
-
const attr = mutation.attributeName;
|
|
130
|
-
return attr === "style" || attr === "class";
|
|
131
|
-
}
|
|
132
|
-
return mutation.type === "characterData";
|
|
133
|
-
});
|
|
134
|
-
if (!hasLayoutChanges) return;
|
|
135
|
-
if (mutationTimeoutRef.current) {
|
|
136
|
-
clearTimeout(mutationTimeoutRef.current);
|
|
137
|
-
}
|
|
138
|
-
mutationTimeoutRef.current = window.setTimeout(() => {
|
|
139
|
-
updateScrollState();
|
|
140
|
-
}, 16);
|
|
141
|
-
});
|
|
142
|
-
mutationObserverRef.current.observe(viewport, {
|
|
143
|
-
childList: true,
|
|
144
|
-
subtree: true,
|
|
145
|
-
attributes: true,
|
|
146
|
-
attributeFilter: ["style", "class"],
|
|
147
|
-
// Only listen to attributes that affect layout
|
|
148
|
-
characterData: true,
|
|
149
|
-
characterDataOldValue: false,
|
|
150
|
-
// No need for old value, improve performance
|
|
151
|
-
attributeOldValue: false
|
|
152
|
-
});
|
|
127
|
+
if (content) {
|
|
128
|
+
resizeObserverRef.current.observe(content);
|
|
129
|
+
}
|
|
153
130
|
}
|
|
154
131
|
delayedUpdateScrollState();
|
|
155
132
|
return () => {
|
|
@@ -158,10 +135,6 @@ function useScrollStateAndVisibility(viewport) {
|
|
|
158
135
|
clearTimeout(scrollTimeoutRef.current);
|
|
159
136
|
scrollTimeoutRef.current = void 0;
|
|
160
137
|
}
|
|
161
|
-
if (mutationTimeoutRef.current) {
|
|
162
|
-
clearTimeout(mutationTimeoutRef.current);
|
|
163
|
-
mutationTimeoutRef.current = void 0;
|
|
164
|
-
}
|
|
165
138
|
if (rafRef.current) {
|
|
166
139
|
cancelAnimationFrame(rafRef.current);
|
|
167
140
|
rafRef.current = void 0;
|
|
@@ -170,12 +143,8 @@ function useScrollStateAndVisibility(viewport) {
|
|
|
170
143
|
resizeObserverRef.current.disconnect();
|
|
171
144
|
resizeObserverRef.current = void 0;
|
|
172
145
|
}
|
|
173
|
-
if (mutationObserverRef.current) {
|
|
174
|
-
mutationObserverRef.current.disconnect();
|
|
175
|
-
mutationObserverRef.current = void 0;
|
|
176
|
-
}
|
|
177
146
|
};
|
|
178
|
-
}, [viewport, handleScroll, delayedUpdateScrollState]);
|
|
147
|
+
}, [viewport, content, handleScroll, delayedUpdateScrollState, updateScrollState]);
|
|
179
148
|
const handleMouseEnter = useCallback(() => setIsHovering(true), []);
|
|
180
149
|
const handleMouseLeave = useCallback(() => setIsHovering(false), []);
|
|
181
150
|
return {
|
|
@@ -229,16 +198,13 @@ function useThumbDrag(viewport, scrollState, orientation) {
|
|
|
229
198
|
const isDragging = useRef(false);
|
|
230
199
|
const startPos = useRef(0);
|
|
231
200
|
const startScroll = useRef(0);
|
|
232
|
-
const rafId = useRef();
|
|
233
201
|
const cleanupRef = useRef(null);
|
|
202
|
+
const scrollStateRef = useRef(scrollState);
|
|
203
|
+
scrollStateRef.current = scrollState;
|
|
234
204
|
const dragContextRef = useRef(null);
|
|
235
205
|
useEffect(() => {
|
|
236
206
|
return () => {
|
|
237
207
|
isDragging.current = false;
|
|
238
|
-
if (rafId.current) {
|
|
239
|
-
cancelAnimationFrame(rafId.current);
|
|
240
|
-
rafId.current = void 0;
|
|
241
|
-
}
|
|
242
208
|
if (cleanupRef.current) {
|
|
243
209
|
cleanupRef.current();
|
|
244
210
|
cleanupRef.current = null;
|
|
@@ -248,13 +214,21 @@ function useThumbDrag(viewport, scrollState, orientation) {
|
|
|
248
214
|
const handleMouseDown = useCallback(
|
|
249
215
|
(e) => {
|
|
250
216
|
if (!viewport) return;
|
|
217
|
+
const currentScrollState = scrollStateRef.current;
|
|
251
218
|
const target = e.currentTarget;
|
|
252
219
|
const scrollbar = target.closest('[role="scrollbar"]');
|
|
253
220
|
if (!scrollbar) return;
|
|
254
221
|
const scrollbarRect = scrollbar.getBoundingClientRect();
|
|
255
|
-
const scrollableRange = orientation === "vertical" ? Math.max(0,
|
|
222
|
+
const scrollableRange = orientation === "vertical" ? Math.max(0, currentScrollState.scrollHeight - currentScrollState.clientHeight) : Math.max(0, currentScrollState.scrollWidth - currentScrollState.clientWidth);
|
|
256
223
|
const scrollbarRange = orientation === "vertical" ? scrollbarRect.height : scrollbarRect.width;
|
|
257
224
|
if (scrollableRange <= 0 || scrollbarRange <= 0) return;
|
|
225
|
+
const thumbFraction = Math.max(
|
|
226
|
+
0.1,
|
|
227
|
+
orientation === "vertical" ? currentScrollState.clientHeight / currentScrollState.scrollHeight : currentScrollState.clientWidth / currentScrollState.scrollWidth
|
|
228
|
+
);
|
|
229
|
+
const thumbSizePixels = scrollbarRange * thumbFraction;
|
|
230
|
+
const effectiveTrackRange = scrollbarRange - thumbSizePixels;
|
|
231
|
+
if (effectiveTrackRange <= 0) return;
|
|
258
232
|
dragContextRef.current = {
|
|
259
233
|
scrollbarRect,
|
|
260
234
|
scrollableRange,
|
|
@@ -262,34 +236,26 @@ function useThumbDrag(viewport, scrollState, orientation) {
|
|
|
262
236
|
};
|
|
263
237
|
isDragging.current = true;
|
|
264
238
|
startPos.current = orientation === "vertical" ? e.clientY : e.clientX;
|
|
265
|
-
startScroll.current = orientation === "vertical" ?
|
|
266
|
-
const scrollRatio = scrollableRange /
|
|
239
|
+
startScroll.current = orientation === "vertical" ? currentScrollState.scrollTop : currentScrollState.scrollLeft;
|
|
240
|
+
const scrollRatio = scrollableRange / effectiveTrackRange;
|
|
267
241
|
const handleMouseMove = (e2) => {
|
|
268
242
|
if (!isDragging.current || !viewport || !dragContextRef.current) return;
|
|
269
|
-
|
|
270
|
-
|
|
243
|
+
const currentPos = orientation === "vertical" ? e2.clientY : e2.clientX;
|
|
244
|
+
const delta = currentPos - startPos.current;
|
|
245
|
+
const scrollDelta = delta * scrollRatio;
|
|
246
|
+
const newScrollValue = Math.max(
|
|
247
|
+
0,
|
|
248
|
+
Math.min(startScroll.current + scrollDelta, dragContextRef.current.scrollableRange)
|
|
249
|
+
);
|
|
250
|
+
if (orientation === "vertical") {
|
|
251
|
+
viewport.scrollTop = newScrollValue;
|
|
252
|
+
} else {
|
|
253
|
+
viewport.scrollLeft = newScrollValue;
|
|
271
254
|
}
|
|
272
|
-
rafId.current = requestAnimationFrame(() => {
|
|
273
|
-
const currentPos = orientation === "vertical" ? e2.clientY : e2.clientX;
|
|
274
|
-
const delta = currentPos - startPos.current;
|
|
275
|
-
const scrollDelta = delta * scrollRatio;
|
|
276
|
-
const newScrollValue = Math.max(
|
|
277
|
-
0,
|
|
278
|
-
Math.min(startScroll.current + scrollDelta, dragContextRef.current.scrollableRange)
|
|
279
|
-
);
|
|
280
|
-
if (orientation === "vertical") {
|
|
281
|
-
viewport.scrollTop = newScrollValue;
|
|
282
|
-
} else {
|
|
283
|
-
viewport.scrollLeft = newScrollValue;
|
|
284
|
-
}
|
|
285
|
-
});
|
|
286
255
|
};
|
|
287
256
|
const handleMouseUp = () => {
|
|
288
257
|
isDragging.current = false;
|
|
289
258
|
dragContextRef.current = null;
|
|
290
|
-
if (rafId.current) {
|
|
291
|
-
cancelAnimationFrame(rafId.current);
|
|
292
|
-
}
|
|
293
259
|
document.removeEventListener("mousemove", handleMouseMove);
|
|
294
260
|
document.removeEventListener("mouseup", handleMouseUp);
|
|
295
261
|
cleanupRef.current = null;
|
|
@@ -303,7 +269,7 @@ function useThumbDrag(viewport, scrollState, orientation) {
|
|
|
303
269
|
document.addEventListener("mouseup", handleMouseUp, { passive: true });
|
|
304
270
|
e.preventDefault();
|
|
305
271
|
},
|
|
306
|
-
[viewport, orientation
|
|
272
|
+
[viewport, orientation]
|
|
307
273
|
);
|
|
308
274
|
return {
|
|
309
275
|
isDragging: isDragging.current,
|
|
@@ -609,6 +575,8 @@ var ScrollAreaScrollbar = forwardRef(
|
|
|
609
575
|
scrollbarXId,
|
|
610
576
|
scrollbarYId
|
|
611
577
|
} = useScrollAreaContext();
|
|
578
|
+
const scrollStateRef = useRef(scrollState);
|
|
579
|
+
scrollStateRef.current = scrollState;
|
|
612
580
|
const hasOverflow = useHasOverflow(scrollState, orientation);
|
|
613
581
|
const shouldShow = useScrollbarShouldShow(type, hasOverflow, isScrolling, isHovering);
|
|
614
582
|
const scrollPercentage = useMemo(() => {
|
|
@@ -619,15 +587,23 @@ var ScrollAreaScrollbar = forwardRef(
|
|
|
619
587
|
const maxScroll = scrollState.scrollWidth - scrollState.clientWidth;
|
|
620
588
|
return maxScroll > 0 ? Math.round(scrollState.scrollLeft / maxScroll * 100) : 0;
|
|
621
589
|
}
|
|
622
|
-
}, [
|
|
590
|
+
}, [
|
|
591
|
+
orientation,
|
|
592
|
+
scrollState.scrollTop,
|
|
593
|
+
scrollState.scrollLeft,
|
|
594
|
+
scrollState.scrollHeight,
|
|
595
|
+
scrollState.clientHeight,
|
|
596
|
+
scrollState.scrollWidth,
|
|
597
|
+
scrollState.clientWidth
|
|
598
|
+
]);
|
|
623
599
|
const handleTrackClick = useCallback(
|
|
624
600
|
(e) => {
|
|
625
601
|
if (!viewport) return;
|
|
626
602
|
if (e.target === e.currentTarget) {
|
|
627
|
-
handleScrollbarTrackClick(e, viewport,
|
|
603
|
+
handleScrollbarTrackClick(e, viewport, scrollStateRef.current, orientation);
|
|
628
604
|
}
|
|
629
605
|
},
|
|
630
|
-
[viewport,
|
|
606
|
+
[viewport, orientation]
|
|
631
607
|
);
|
|
632
608
|
const tv = useMemo(
|
|
633
609
|
() => ScrollTv({
|
|
@@ -746,7 +722,7 @@ var ScrollAreaRoot = forwardRef(
|
|
|
746
722
|
const viewportId = `scroll-viewport${reactId}`;
|
|
747
723
|
const scrollbarXId = `scroll-x${reactId}`;
|
|
748
724
|
const scrollbarYId = `scroll-y${reactId}`;
|
|
749
|
-
const { scrollState, isHovering, isScrolling, handleMouseEnter, handleMouseLeave } = useScrollStateAndVisibility(viewport);
|
|
725
|
+
const { scrollState, isHovering, isScrolling, handleMouseEnter, handleMouseLeave } = useScrollStateAndVisibility(viewport, content);
|
|
750
726
|
const staticConfig = useMemo(
|
|
751
727
|
() => ({
|
|
752
728
|
orientation,
|
|
@@ -757,11 +733,14 @@ var ScrollAreaRoot = forwardRef(
|
|
|
757
733
|
}),
|
|
758
734
|
[orientation, scrollbarMode, hoverBoundary, variant, type]
|
|
759
735
|
);
|
|
760
|
-
const
|
|
736
|
+
const stateValue = useMemo(
|
|
737
|
+
() => ({ scrollState, isHovering, isScrolling }),
|
|
738
|
+
[scrollState, isHovering, isScrolling]
|
|
739
|
+
);
|
|
740
|
+
const configValue = useMemo(
|
|
761
741
|
() => ({
|
|
762
742
|
content,
|
|
763
743
|
orientation: staticConfig.orientation,
|
|
764
|
-
scrollState,
|
|
765
744
|
scrollbarMode: staticConfig.scrollbarMode,
|
|
766
745
|
hoverBoundary: staticConfig.hoverBoundary,
|
|
767
746
|
scrollbarX,
|
|
@@ -777,9 +756,6 @@ var ScrollAreaRoot = forwardRef(
|
|
|
777
756
|
variant: staticConfig.variant,
|
|
778
757
|
viewport,
|
|
779
758
|
type: staticConfig.type,
|
|
780
|
-
isHovering,
|
|
781
|
-
isScrolling,
|
|
782
|
-
// Add ID-related values
|
|
783
759
|
rootId,
|
|
784
760
|
viewportId,
|
|
785
761
|
scrollbarXId,
|
|
@@ -787,14 +763,11 @@ var ScrollAreaRoot = forwardRef(
|
|
|
787
763
|
}),
|
|
788
764
|
[
|
|
789
765
|
content,
|
|
790
|
-
scrollState,
|
|
791
766
|
scrollbarX,
|
|
792
767
|
scrollbarY,
|
|
793
768
|
thumbX,
|
|
794
769
|
thumbY,
|
|
795
770
|
viewport,
|
|
796
|
-
isHovering,
|
|
797
|
-
isScrolling,
|
|
798
771
|
staticConfig,
|
|
799
772
|
rootId,
|
|
800
773
|
viewportId,
|
|
@@ -859,7 +832,7 @@ var ScrollAreaRoot = forwardRef(
|
|
|
859
832
|
}
|
|
860
833
|
return scrollbars;
|
|
861
834
|
}, [orientation]);
|
|
862
|
-
return /* @__PURE__ */ jsx(
|
|
835
|
+
return /* @__PURE__ */ jsx(ScrollAreaConfigContext.Provider, { value: configValue, children: /* @__PURE__ */ jsx(ScrollAreaStateContext.Provider, { value: stateValue, children: /* @__PURE__ */ jsxs(
|
|
863
836
|
"div",
|
|
864
837
|
{
|
|
865
838
|
ref,
|
|
@@ -875,13 +848,13 @@ var ScrollAreaRoot = forwardRef(
|
|
|
875
848
|
autoScrollbars
|
|
876
849
|
]
|
|
877
850
|
}
|
|
878
|
-
) });
|
|
851
|
+
) }) });
|
|
879
852
|
}
|
|
880
853
|
);
|
|
881
854
|
ScrollAreaRoot.displayName = "ScrollArea.Root";
|
|
882
855
|
var ScrollAreaViewport = forwardRef(
|
|
883
856
|
({ className, children, ...props }, ref) => {
|
|
884
|
-
const { setViewport, orientation, viewportId } =
|
|
857
|
+
const { setViewport, orientation, viewportId } = useScrollAreaConfigContext();
|
|
885
858
|
const scrollClass = useMemo(() => {
|
|
886
859
|
switch (orientation) {
|
|
887
860
|
case "horizontal":
|
|
@@ -918,7 +891,7 @@ var ScrollAreaViewport = forwardRef(
|
|
|
918
891
|
ScrollAreaViewport.displayName = "ScrollArea.Viewport";
|
|
919
892
|
var ScrollAreaContent = forwardRef(
|
|
920
893
|
({ as: As = "div", className, children, ...props }, ref) => {
|
|
921
|
-
const { setContent, orientation } =
|
|
894
|
+
const { setContent, orientation } = useScrollAreaConfigContext();
|
|
922
895
|
const setRef = useCallback(
|
|
923
896
|
(node) => {
|
|
924
897
|
setContent(node);
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { jsx } from "react/jsx-runtime";
|
|
2
2
|
import { forwardRef, useCallback, useMemo } 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 ScrollAreaContent = forwardRef(
|
|
6
6
|
({ as: As = "div", className, children, ...props }, ref) => {
|
|
7
|
-
const { setContent, orientation } =
|
|
7
|
+
const { setContent, orientation } = useScrollAreaConfigContext();
|
|
8
8
|
const setRef = useCallback(
|
|
9
9
|
(node) => {
|
|
10
10
|
setContent(node);
|