@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.
Files changed (29) hide show
  1. package/dist/components/button/src/tv.js +7 -0
  2. package/dist/components/command/dist/index.d.ts +13 -0
  3. package/dist/components/command/src/command.js +2 -0
  4. package/dist/components/command/src/components/command-item.d.ts +4 -0
  5. package/dist/components/command/src/components/command-item.js +6 -0
  6. package/dist/components/command/src/components/command-list.d.ts +3 -0
  7. package/dist/components/command/src/components/command-list.js +5 -1
  8. package/dist/components/command/src/context/create-command-context.d.ts +1 -0
  9. package/dist/components/command/src/context/create-command-context.js +2 -0
  10. package/dist/components/command/src/tv.js +1 -1
  11. package/dist/components/command/src/types.d.ts +6 -0
  12. package/dist/components/scroll-area/dist/index.d.ts +4 -27
  13. package/dist/components/scroll-area/dist/index.js +96 -123
  14. package/dist/components/scroll-area/src/components/scroll-area-content.js +2 -2
  15. package/dist/components/scroll-area/src/components/scroll-area-root.js +9 -12
  16. package/dist/components/scroll-area/src/components/scroll-area-scrollbar.js +14 -4
  17. package/dist/components/scroll-area/src/components/scroll-area-viewport.js +2 -2
  18. package/dist/components/scroll-area/src/context/scroll-area-context.d.ts +17 -2
  19. package/dist/components/scroll-area/src/context/scroll-area-context.js +23 -6
  20. package/dist/components/scroll-area/src/hooks/index.d.ts +0 -1
  21. package/dist/components/scroll-area/src/hooks/use-scroll-state-and-visibility.d.ts +2 -2
  22. package/dist/components/scroll-area/src/hooks/use-scroll-state-and-visibility.js +30 -75
  23. package/dist/components/scroll-area/src/hooks/use-thumb.d.ts +1 -1
  24. package/dist/components/scroll-area/src/hooks/use-thumb.js +25 -28
  25. package/dist/components/scroll-area/src/types.d.ts +16 -4
  26. package/dist/index.js +0 -2
  27. package/package.json +1 -1
  28. package/dist/components/scroll-area/src/hooks/use-scroll-performance-monitor.d.ts +0 -23
  29. 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
@@ -7,7 +7,7 @@ const commandTv = tcv({
7
7
  variants: {
8
8
  variant: {
9
9
  default: {
10
- root: "bg-default-background text-default-foreground",
10
+ root: "text-default-foreground",
11
11
  divider: "bg-default-boundary"
12
12
  },
13
13
  dark: {
@@ -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 - avoid duplicate scroll event listening
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
- * 🚀 High-performance thumb drag hook - optimize drag responsiveness and performance
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
- interface PerformanceMetrics {
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 ScrollAreaContext = createContext(null);
5
- function useScrollAreaContext() {
6
- const context = useContext(ScrollAreaContext);
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("ScrollArea compound components must be used within ScrollArea");
17
+ throw new Error(ERROR_MESSAGE);
9
18
  }
10
19
  return context;
11
20
  }
12
- function useScrollStateAndVisibility(viewport) {
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
- rafRef.current = requestAnimationFrame(() => {
47
- const newState = {
48
- scrollLeft: viewport.scrollLeft,
49
- scrollTop: viewport.scrollTop,
50
- scrollWidth: viewport.scrollWidth,
51
- scrollHeight: viewport.scrollHeight,
52
- clientWidth: viewport.clientWidth,
53
- clientHeight: viewport.clientHeight
54
- };
55
- setScrollState((prevState) => {
56
- const scrollLeftChanged = Math.abs(prevState.scrollLeft - newState.scrollLeft) > 0.5;
57
- const scrollTopChanged = Math.abs(prevState.scrollTop - newState.scrollTop) > 0.5;
58
- const scrollWidthChanged = prevState.scrollWidth !== newState.scrollWidth;
59
- const scrollHeightChanged = prevState.scrollHeight !== newState.scrollHeight;
60
- const clientWidthChanged = prevState.clientWidth !== newState.clientWidth;
61
- const clientHeightChanged = prevState.clientHeight !== newState.clientHeight;
62
- const hasChanges = scrollLeftChanged || scrollTopChanged || scrollWidthChanged || scrollHeightChanged || clientWidthChanged || clientHeightChanged;
63
- if (hasChanges) {
64
- lastUpdateTimeRef.current = now;
65
- return newState;
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((entries) => {
113
- for (const entry of entries) {
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
- if (window.MutationObserver) {
123
- mutationObserverRef.current = new MutationObserver((mutations) => {
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, scrollState.scrollHeight - scrollState.clientHeight) : Math.max(0, scrollState.scrollWidth - scrollState.clientWidth);
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" ? scrollState.scrollTop : scrollState.scrollLeft;
266
- const scrollRatio = scrollableRange / scrollbarRange;
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
- if (rafId.current) {
270
- cancelAnimationFrame(rafId.current);
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, scrollState]
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
- }, [scrollState, orientation]);
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, scrollState, orientation);
603
+ handleScrollbarTrackClick(e, viewport, scrollStateRef.current, orientation);
628
604
  }
629
605
  },
630
- [viewport, scrollState, orientation]
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 contextValue = useMemo(
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(ScrollAreaContext.Provider, { value: contextValue, children: /* @__PURE__ */ jsxs(
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 } = useScrollAreaContext();
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 } = useScrollAreaContext();
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 { useScrollAreaContext } from "../context/scroll-area-context.js";
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 } = useScrollAreaContext();
7
+ const { setContent, orientation } = useScrollAreaConfigContext();
8
8
  const setRef = useCallback(
9
9
  (node) => {
10
10
  setContent(node);