@choice-ui/react 1.9.4 → 1.9.7

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/command/dist/index.d.ts +3 -0
  2. package/dist/components/command/src/components/command-list.d.ts +3 -0
  3. package/dist/components/command/src/components/command-list.js +4 -1
  4. package/dist/components/numeric-input/dist/index.d.ts +2 -0
  5. package/dist/components/numeric-input/dist/index.js +40 -13
  6. package/dist/components/numeric-input/src/context/numeric-input-context.d.ts +1 -0
  7. package/dist/components/numeric-input/src/hooks/use-input-interactions.d.ts +3 -1
  8. package/dist/components/numeric-input/src/hooks/use-numeric-input.d.ts +1 -0
  9. package/dist/components/numeric-input/src/hooks/use-numeric-input.js +36 -13
  10. package/dist/components/numeric-input/src/numeric-input.d.ts +1 -0
  11. package/dist/components/numeric-input/src/numeric-input.js +4 -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
@@ -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);
@@ -1,6 +1,6 @@
1
1
  import { jsx, jsxs } from "react/jsx-runtime";
2
2
  import { forwardRef, useState, useId, useMemo } from "react";
3
- import { ScrollAreaContext } from "../context/scroll-area-context.js";
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 contextValue = useMemo(
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(ScrollAreaContext.Provider, { value: contextValue, children: /* @__PURE__ */ jsxs(
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
- }, [scrollState, orientation]);
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, scrollState, orientation);
50
+ handleScrollbarTrackClick(e, viewport, scrollStateRef.current, orientation);
41
51
  }
42
52
  },
43
- [viewport, scrollState, orientation]
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 { 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 ScrollAreaViewport = forwardRef(
6
6
  ({ className, children, ...props }, ref) => {
7
- const { setViewport, orientation, viewportId } = useScrollAreaContext();
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 ScrollAreaContext: import('react').Context<ScrollAreaContextType | null>;
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 ScrollAreaContext = createContext(null);
3
- function useScrollAreaContext() {
4
- const context = useContext(ScrollAreaContext);
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("ScrollArea compound components must be used within ScrollArea");
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
- ScrollAreaContext,
12
- useScrollAreaContext
25
+ ScrollAreaConfigContext,
26
+ ScrollAreaStateContext,
27
+ useScrollAreaConfigContext,
28
+ useScrollAreaContext,
29
+ useScrollAreaStateContext
13
30
  };
@@ -1,4 +1,3 @@
1
1
  export * from './use-scroll-state-and-visibility';
2
2
  export * from './use-thumb';
3
3
  export * from './use-scrollbar';
4
- export * from './use-scroll-performance-monitor';
@@ -1,8 +1,8 @@
1
1
  import { ScrollState } from '../types';
2
2
  /**
3
- * Merged scroll state and visibility management hook - avoid duplicate scroll event listening
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;