@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
@@ -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;
@@ -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
- rafRef.current = requestAnimationFrame(() => {
37
- const newState = {
38
- scrollLeft: viewport.scrollLeft,
39
- scrollTop: viewport.scrollTop,
40
- scrollWidth: viewport.scrollWidth,
41
- scrollHeight: viewport.scrollHeight,
42
- clientWidth: viewport.clientWidth,
43
- clientHeight: viewport.clientHeight
44
- };
45
- setScrollState((prevState) => {
46
- const scrollLeftChanged = Math.abs(prevState.scrollLeft - newState.scrollLeft) > 0.5;
47
- const scrollTopChanged = Math.abs(prevState.scrollTop - newState.scrollTop) > 0.5;
48
- const scrollWidthChanged = prevState.scrollWidth !== newState.scrollWidth;
49
- const scrollHeightChanged = prevState.scrollHeight !== newState.scrollHeight;
50
- const clientWidthChanged = prevState.clientWidth !== newState.clientWidth;
51
- const clientHeightChanged = prevState.clientHeight !== newState.clientHeight;
52
- const hasChanges = scrollLeftChanged || scrollTopChanged || scrollWidthChanged || scrollHeightChanged || clientWidthChanged || clientHeightChanged;
53
- if (hasChanges) {
54
- lastUpdateTimeRef.current = now;
55
- return newState;
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((entries) => {
103
- for (const entry of entries) {
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
- if (window.MutationObserver) {
113
- mutationObserverRef.current = new MutationObserver((mutations) => {
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
- * 🚀 High-performance thumb drag hook - optimize drag responsiveness and performance
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, scrollState.scrollHeight - scrollState.clientHeight) : Math.max(0, scrollState.scrollWidth - scrollState.clientWidth);
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" ? scrollState.scrollTop : scrollState.scrollLeft;
79
- const scrollRatio = scrollableRange / scrollbarRange;
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
- if (rafId.current) {
83
- cancelAnimationFrame(rafId.current);
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, scrollState]
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
- export interface ScrollAreaContextType {
28
- content: HTMLDivElement | null;
29
- hoverBoundary: HoverBoundary;
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,6 +1,6 @@
1
1
  {
2
2
  "name": "@choice-ui/react",
3
- "version": "1.9.3",
3
+ "version": "1.9.6",
4
4
  "description": "A desktop-first React UI component library built for professional desktop applications with comprehensive documentation",
5
5
  "sideEffects": false,
6
6
  "type": "module",
@@ -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 {};