@expcat/tigercat-react 2.0.19 → 2.1.0

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 (82) hide show
  1. package/README.md +25 -16
  2. package/dist/chunk-2YOP55OF.mjs +639 -0
  3. package/dist/{chunk-V6P7ZK6O.mjs → chunk-7EMCZVJ2.mjs} +1 -1
  4. package/dist/chunk-EW43YBPV.mjs +457 -0
  5. package/dist/{chunk-QJE3MZBO.mjs → chunk-FSMJLJFL.mjs} +1 -1
  6. package/dist/{chunk-BOGOQ44C.mjs → chunk-GCY55TBC.mjs} +1 -1
  7. package/dist/chunk-GR3Y3XWP.mjs +47 -0
  8. package/dist/chunk-IGBQGR6G.mjs +234 -0
  9. package/dist/{chunk-D33BIMGK.mjs → chunk-IR7VA6AO.mjs} +11 -1
  10. package/dist/chunk-J76R2S62.mjs +161 -0
  11. package/dist/{chunk-2WGEHH5A.mjs → chunk-JQJM65RJ.mjs} +1 -1
  12. package/dist/{chunk-7AFRHC7P.mjs → chunk-KC55B62J.mjs} +127 -42
  13. package/dist/chunk-KRDHM5KB.mjs +259 -0
  14. package/dist/chunk-M5MZWOHF.mjs +180 -0
  15. package/dist/chunk-MFPVVGNA.mjs +248 -0
  16. package/dist/chunk-NLGLBTSM.mjs +138 -0
  17. package/dist/chunk-NT7GW2FD.mjs +40 -0
  18. package/dist/chunk-OKYMTHFQ.mjs +279 -0
  19. package/dist/{chunk-HHIW7V7B.mjs → chunk-PFL4HNWM.mjs} +1 -8
  20. package/dist/chunk-RUAPTIQ7.mjs +181 -0
  21. package/dist/chunk-SEOG5AGS.mjs +72 -0
  22. package/dist/chunk-TQ6RIFWR.mjs +135 -0
  23. package/dist/chunk-U4M3DC2P.mjs +48 -0
  24. package/dist/{chunk-YIB6ZXZF.mjs → chunk-UETARXIK.mjs} +239 -52
  25. package/dist/chunk-XT43B7ZI.mjs +58 -0
  26. package/dist/components/ActivityFeed.mjs +1 -1
  27. package/dist/components/AreaChart.mjs +2 -2
  28. package/dist/components/AspectRatio.d.mts +10 -0
  29. package/dist/components/AspectRatio.mjs +8 -0
  30. package/dist/components/BarChart.mjs +2 -2
  31. package/dist/components/Cascader.mjs +1 -1
  32. package/dist/components/ChatWindow.mjs +1 -1
  33. package/dist/components/ContextMenu.d.mts +36 -0
  34. package/dist/components/ContextMenu.mjs +17 -0
  35. package/dist/components/DataExport.mjs +1 -1
  36. package/dist/components/DataTableWithToolbar.mjs +5 -5
  37. package/dist/components/Highlight.d.mts +11 -0
  38. package/dist/components/Highlight.mjs +8 -0
  39. package/dist/components/ImageCompare.d.mts +15 -0
  40. package/dist/components/ImageCompare.mjs +8 -0
  41. package/dist/components/InputOTP.d.mts +33 -0
  42. package/dist/components/InputOTP.mjs +12 -0
  43. package/dist/components/Kbd.d.mts +10 -0
  44. package/dist/components/Kbd.mjs +8 -0
  45. package/dist/components/LineChart.mjs +2 -2
  46. package/dist/components/List.mjs +2 -2
  47. package/dist/components/LoadingBar.d.mts +8 -0
  48. package/dist/components/LoadingBar.mjs +138 -0
  49. package/dist/components/LoadingBarContainer.d.mts +8 -0
  50. package/dist/components/LoadingBarContainer.mjs +8 -0
  51. package/dist/components/LoadingBarRoot.d.mts +6 -0
  52. package/dist/components/LoadingBarRoot.mjs +6 -0
  53. package/dist/components/Marquee.d.mts +10 -0
  54. package/dist/components/Marquee.mjs +8 -0
  55. package/dist/components/MaskInput.d.mts +37 -0
  56. package/dist/components/MaskInput.mjs +10 -0
  57. package/dist/components/Masonry.d.mts +10 -0
  58. package/dist/components/Masonry.mjs +8 -0
  59. package/dist/components/NavigationMenu.d.mts +84 -0
  60. package/dist/components/NavigationMenu.mjs +23 -0
  61. package/dist/components/NotificationCenter.mjs +3 -3
  62. package/dist/components/PageHeader.d.mts +34 -0
  63. package/dist/components/PageHeader.mjs +11 -0
  64. package/dist/components/Pagination.mjs +1 -1
  65. package/dist/components/RadarChart.mjs +2 -2
  66. package/dist/components/ScatterChart.mjs +2 -2
  67. package/dist/components/ScrollArea.d.mts +10 -0
  68. package/dist/components/ScrollArea.mjs +8 -0
  69. package/dist/components/SplitButton.d.mts +38 -0
  70. package/dist/components/SplitButton.mjs +12 -0
  71. package/dist/components/Table.mjs +2 -2
  72. package/dist/components/TagsInput.d.mts +41 -0
  73. package/dist/components/TagsInput.mjs +13 -0
  74. package/dist/components/TreeSelect.mjs +2 -1
  75. package/dist/index.d.mts +17 -1
  76. package/dist/index.mjs +355 -0
  77. package/package.json +117 -2
  78. package/dist/{chunk-NCHUEGUW.mjs → chunk-H6MVW5YD.mjs} +3 -3
  79. package/dist/{chunk-UT5BV2VO.mjs → chunk-I4MWOYJS.mjs} +3 -3
  80. package/dist/{chunk-E7OTDBI6.mjs → chunk-NWVNTZOP.mjs} +3 -3
  81. package/dist/{chunk-FYINJRYN.mjs → chunk-RQ4PUK6G.mjs} +3 -3
  82. package/dist/{chunk-KMP5CVNK.mjs → chunk-UKY2EPS3.mjs} +3 -3
@@ -0,0 +1,259 @@
1
+ import {
2
+ useFormItemControlContext
3
+ } from "./chunk-F6T3OLTQ.mjs";
4
+ import {
5
+ useControlledState
6
+ } from "./chunk-MEFARVQI.mjs";
7
+ import {
8
+ Tag
9
+ } from "./chunk-7EXOY7GD.mjs";
10
+ import {
11
+ useTigerConfig
12
+ } from "./chunk-YSIXURBW.mjs";
13
+
14
+ // src/components/TagsInput.tsx
15
+ import { useEffect, useId, useRef, useState } from "react";
16
+ import {
17
+ addTags,
18
+ classNames,
19
+ extractTagCandidates,
20
+ formatRemoveTagLabel,
21
+ getTagsInputClearButtonClasses,
22
+ getTagsInputContainerClasses,
23
+ getTagsInputErrorClasses,
24
+ getTagsInputHighlightClasses,
25
+ getTagsInputInnerInputClasses,
26
+ getTagsInputLabels,
27
+ injectShakeStyle,
28
+ removeTagAt,
29
+ SHAKE_CLASS,
30
+ splitTagInput
31
+ } from "@expcat/tigercat-core";
32
+ import { jsx, jsxs } from "react/jsx-runtime";
33
+ var TagsInput = ({
34
+ size = "md",
35
+ status: statusProp,
36
+ errorMessage: errorMessageProp,
37
+ _shakeTrigger: shakeTriggerProp,
38
+ value,
39
+ defaultValue,
40
+ placeholder = "",
41
+ allowDuplicates = false,
42
+ max,
43
+ delimiters = [","],
44
+ addOnBlur = false,
45
+ beforeAdd,
46
+ clearable = false,
47
+ disabled = false,
48
+ readonly = false,
49
+ name,
50
+ id,
51
+ removeTagAriaLabel,
52
+ onChange,
53
+ onAdd,
54
+ onRemove,
55
+ onClear,
56
+ onFocus,
57
+ onBlur,
58
+ className,
59
+ style,
60
+ ...rest
61
+ }) => {
62
+ injectShakeStyle();
63
+ const config = useTigerConfig();
64
+ const formItemControl = useFormItemControlContext();
65
+ const status = statusProp ?? formItemControl?.status ?? "default";
66
+ const errorMessage = errorMessageProp ?? formItemControl?.errorMessage;
67
+ const shakeTrigger = shakeTriggerProp ?? formItemControl?.shakeTrigger;
68
+ const labels = getTagsInputLabels(config.locale);
69
+ const reactId = useId();
70
+ const errorMsgId = `tiger-tags-input-error-${reactId}`;
71
+ const containerRef = useRef(null);
72
+ const inputRef = useRef(null);
73
+ const [tags, setTags] = useControlledState(value, defaultValue ?? [], onChange);
74
+ const [inputText, setInputText] = useState("");
75
+ const [highlightedIndex, setHighlightedIndex] = useState(null);
76
+ useEffect(() => {
77
+ if (status === "error" && containerRef.current) {
78
+ const el = containerRef.current;
79
+ el.classList.remove(SHAKE_CLASS);
80
+ void el.offsetWidth;
81
+ el.classList.add(SHAKE_CLASS);
82
+ }
83
+ }, [status, shakeTrigger]);
84
+ const isInteractive = !disabled && !readonly;
85
+ const commitCandidates = (candidates) => {
86
+ const prepared = [];
87
+ for (const raw of candidates) {
88
+ if (!beforeAdd) {
89
+ prepared.push(raw);
90
+ continue;
91
+ }
92
+ const result = beforeAdd(raw.trim());
93
+ if (result === false) continue;
94
+ prepared.push(typeof result === "string" ? result : raw);
95
+ }
96
+ if (prepared.length === 0) return { added: [] };
97
+ const { tags: nextTags, added } = addTags(tags, prepared, { allowDuplicates, max });
98
+ if (added.length > 0) {
99
+ setTags(nextTags);
100
+ added.forEach((tag) => onAdd?.(tag));
101
+ }
102
+ return { added };
103
+ };
104
+ const handleInputChange = (event) => {
105
+ if (!isInteractive) return;
106
+ setHighlightedIndex(null);
107
+ const { candidates, pending } = extractTagCandidates(event.currentTarget.value, delimiters);
108
+ if (candidates.length > 0) commitCandidates(candidates);
109
+ setInputText(pending);
110
+ };
111
+ const removeAt = (index) => {
112
+ const tag = tags[index];
113
+ if (tag === void 0) return;
114
+ setTags(removeTagAt(tags, index));
115
+ onRemove?.(tag, index);
116
+ };
117
+ const handleKeyDown = (event) => {
118
+ if (!isInteractive) return;
119
+ if (event.key === "Enter") {
120
+ event.preventDefault();
121
+ if (inputText.trim()) {
122
+ const { added } = commitCandidates([inputText]);
123
+ if (added.length > 0) setInputText("");
124
+ }
125
+ return;
126
+ }
127
+ if (event.key === "Backspace" && inputText === "") {
128
+ if (highlightedIndex !== null) {
129
+ removeAt(highlightedIndex);
130
+ setHighlightedIndex(null);
131
+ } else if (tags.length > 0) {
132
+ setHighlightedIndex(tags.length - 1);
133
+ }
134
+ return;
135
+ }
136
+ if (event.key === "ArrowLeft" && inputText === "" && tags.length > 0) {
137
+ setHighlightedIndex((prev) => {
138
+ if (prev === null) return tags.length - 1;
139
+ return Math.max(0, prev - 1);
140
+ });
141
+ return;
142
+ }
143
+ if (event.key === "ArrowRight" && highlightedIndex !== null) {
144
+ setHighlightedIndex((prev) => {
145
+ if (prev === null || prev >= tags.length - 1) return null;
146
+ return prev + 1;
147
+ });
148
+ return;
149
+ }
150
+ if (event.key === "Escape" && highlightedIndex !== null) {
151
+ setHighlightedIndex(null);
152
+ return;
153
+ }
154
+ if (highlightedIndex !== null) setHighlightedIndex(null);
155
+ };
156
+ const handlePaste = (event) => {
157
+ if (!isInteractive) return;
158
+ const text = event.clipboardData.getData("text");
159
+ const candidates = splitTagInput(text, delimiters);
160
+ if (candidates.length <= 1) return;
161
+ event.preventDefault();
162
+ commitCandidates(candidates);
163
+ setInputText("");
164
+ };
165
+ const handleBlur = (event) => {
166
+ setHighlightedIndex(null);
167
+ if (addOnBlur && inputText.trim() && isInteractive) {
168
+ const { added } = commitCandidates([inputText]);
169
+ if (added.length > 0) setInputText("");
170
+ }
171
+ onBlur?.(event);
172
+ };
173
+ const handleClear = () => {
174
+ if (!isInteractive || tags.length === 0) return;
175
+ setTags([]);
176
+ setInputText("");
177
+ setHighlightedIndex(null);
178
+ onClear?.();
179
+ inputRef.current?.focus();
180
+ };
181
+ const focusInput = (event) => {
182
+ if (event.target === containerRef.current) inputRef.current?.focus();
183
+ };
184
+ const activeError = status === "error" && !!errorMessage;
185
+ const isFull = max !== void 0 && tags.length >= max;
186
+ const removeLabelTemplate = removeTagAriaLabel ?? labels.removeTagLabel;
187
+ const containerNode = /* @__PURE__ */ jsxs(
188
+ "div",
189
+ {
190
+ ...rest,
191
+ ref: containerRef,
192
+ id,
193
+ className: classNames(getTagsInputContainerClasses(size, status, { disabled }), className),
194
+ style,
195
+ "data-state": isFull ? "full" : void 0,
196
+ onClick: focusInput,
197
+ children: [
198
+ tags.map((tag, index) => /* @__PURE__ */ jsx(
199
+ Tag,
200
+ {
201
+ size: size === "lg" ? "md" : "sm",
202
+ closable: isInteractive,
203
+ closeAriaLabel: formatRemoveTagLabel(removeLabelTemplate, tag),
204
+ className: index === highlightedIndex ? getTagsInputHighlightClasses() : void 0,
205
+ onClose: (event) => {
206
+ event.preventDefault();
207
+ removeAt(index);
208
+ },
209
+ children: tag
210
+ },
211
+ `${tag}-${index}`
212
+ )),
213
+ /* @__PURE__ */ jsx(
214
+ "input",
215
+ {
216
+ ref: inputRef,
217
+ className: getTagsInputInnerInputClasses(),
218
+ type: "text",
219
+ value: inputText,
220
+ placeholder: tags.length === 0 ? placeholder : "",
221
+ disabled,
222
+ readOnly: readonly,
223
+ id: id ? `${id}-input` : void 0,
224
+ ...status === "error" ? { "aria-invalid": true } : {},
225
+ ...activeError ? { "aria-describedby": errorMsgId } : {},
226
+ onChange: handleInputChange,
227
+ onKeyDown: handleKeyDown,
228
+ onPaste: handlePaste,
229
+ onFocus,
230
+ onBlur: handleBlur
231
+ }
232
+ ),
233
+ clearable && tags.length > 0 && isInteractive && /* @__PURE__ */ jsx(
234
+ "button",
235
+ {
236
+ type: "button",
237
+ className: getTagsInputClearButtonClasses(),
238
+ onClick: handleClear,
239
+ "aria-label": labels.clearAllLabel,
240
+ tabIndex: -1,
241
+ children: "\u2715"
242
+ }
243
+ ),
244
+ name && /* @__PURE__ */ jsx("input", { type: "hidden", name, value: tags.join(",") })
245
+ ]
246
+ }
247
+ );
248
+ if (!activeError) return containerNode;
249
+ return /* @__PURE__ */ jsxs("div", { children: [
250
+ containerNode,
251
+ /* @__PURE__ */ jsx("div", { id: errorMsgId, className: getTagsInputErrorClasses(), children: errorMessage })
252
+ ] });
253
+ };
254
+ var TagsInput_default = TagsInput;
255
+
256
+ export {
257
+ TagsInput,
258
+ TagsInput_default
259
+ };
@@ -0,0 +1,180 @@
1
+ import {
2
+ Dropdown,
3
+ DropdownItem,
4
+ DropdownMenu
5
+ } from "./chunk-YI6YWH66.mjs";
6
+ import {
7
+ Button
8
+ } from "./chunk-5TE7KLE5.mjs";
9
+ import {
10
+ ButtonGroup
11
+ } from "./chunk-ZJWUBR2X.mjs";
12
+
13
+ // src/components/SplitButton.tsx
14
+ import React, { useMemo } from "react";
15
+ import {
16
+ DROPDOWN_CHEVRON_PATH,
17
+ getDropdownChevronClasses,
18
+ getSplitButtonPrimaryClasses,
19
+ getSplitButtonRootClasses,
20
+ getSplitButtonTriggerClasses,
21
+ resolveSplitButtonSize,
22
+ resolveSplitButtonTriggerAriaLabel,
23
+ resolveSplitButtonVariant
24
+ } from "@expcat/tigercat-core";
25
+ import { jsx, jsxs } from "react/jsx-runtime";
26
+ function wrapMenu(nodes) {
27
+ if (nodes == null || typeof nodes === "boolean") return null;
28
+ if (React.isValidElement(nodes) && nodes.type === DropdownMenu) return nodes;
29
+ const array = React.Children.toArray(nodes);
30
+ if (array.length === 0) return null;
31
+ const existing = array.find((child) => React.isValidElement(child) && child.type === DropdownMenu);
32
+ if (existing) return existing;
33
+ return /* @__PURE__ */ jsx(DropdownMenu, { children: nodes });
34
+ }
35
+ function partitionChildren(children) {
36
+ const primary = [];
37
+ const menuItems = [];
38
+ let menu = null;
39
+ React.Children.forEach(children, (child) => {
40
+ if (!React.isValidElement(child)) {
41
+ if (child != null && typeof child !== "boolean") primary.push(child);
42
+ return;
43
+ }
44
+ if (child.type === DropdownMenu) {
45
+ menu = child;
46
+ return;
47
+ }
48
+ if (child.type === DropdownItem) {
49
+ menuItems.push(child);
50
+ return;
51
+ }
52
+ primary.push(child);
53
+ });
54
+ if (!menu && menuItems.length > 0) {
55
+ menu = /* @__PURE__ */ jsx(DropdownMenu, { children: menuItems });
56
+ }
57
+ return { primary, menu };
58
+ }
59
+ function Chevron({ open }) {
60
+ return /* @__PURE__ */ jsx(
61
+ "svg",
62
+ {
63
+ className: getDropdownChevronClasses(open),
64
+ viewBox: "0 0 24 24",
65
+ fill: "none",
66
+ stroke: "currentColor",
67
+ strokeWidth: "2",
68
+ strokeLinecap: "round",
69
+ strokeLinejoin: "round",
70
+ "aria-hidden": "true",
71
+ focusable: "false",
72
+ children: /* @__PURE__ */ jsx("path", { d: DROPDOWN_CHEVRON_PATH })
73
+ }
74
+ );
75
+ }
76
+ var SplitButton = ({
77
+ variant = "primary",
78
+ size = "md",
79
+ disabled = false,
80
+ loading = false,
81
+ danger = false,
82
+ block = false,
83
+ htmlType = "button",
84
+ iconPosition = "left",
85
+ icon,
86
+ loadingIcon,
87
+ trigger,
88
+ triggerAriaLabel,
89
+ open,
90
+ defaultOpen = false,
91
+ closeOnClick = true,
92
+ portal = true,
93
+ placement = "bottom-end",
94
+ offset = 4,
95
+ menu,
96
+ onClick,
97
+ onOpenChange,
98
+ className,
99
+ style,
100
+ children,
101
+ ...rest
102
+ }) => {
103
+ const resolvedVariant = resolveSplitButtonVariant(variant);
104
+ const resolvedSize = resolveSplitButtonSize(size);
105
+ const isDisabled = disabled || loading;
106
+ const triggerLabel = resolveSplitButtonTriggerAriaLabel(triggerAriaLabel);
107
+ const partitioned = useMemo(() => partitionChildren(children), [children]);
108
+ const menuNode = menu != null ? wrapMenu(menu) : partitioned.menu;
109
+ const rootClasses = getSplitButtonRootClasses({ block, className });
110
+ const primaryClasses = getSplitButtonPrimaryClasses({ block });
111
+ const triggerClasses = getSplitButtonTriggerClasses({ size: resolvedSize });
112
+ return /* @__PURE__ */ jsxs(
113
+ ButtonGroup,
114
+ {
115
+ size: resolvedSize,
116
+ className: rootClasses,
117
+ style,
118
+ "data-split-button": "",
119
+ ...rest,
120
+ children: [
121
+ /* @__PURE__ */ jsx(
122
+ Button,
123
+ {
124
+ variant: resolvedVariant,
125
+ size: resolvedSize,
126
+ disabled,
127
+ loading,
128
+ danger,
129
+ htmlType,
130
+ iconPosition,
131
+ icon,
132
+ loadingIcon,
133
+ className: primaryClasses,
134
+ "data-split-button-primary": "",
135
+ onClick,
136
+ children: partitioned.primary
137
+ }
138
+ ),
139
+ /* @__PURE__ */ jsx(
140
+ Dropdown,
141
+ {
142
+ trigger: "click",
143
+ showArrow: false,
144
+ disabled: isDisabled,
145
+ open,
146
+ defaultOpen,
147
+ closeOnClick,
148
+ portal,
149
+ placement,
150
+ offset,
151
+ onOpenChange,
152
+ renderTrigger: ({ open: menuOpen }) => /* @__PURE__ */ jsx(
153
+ Button,
154
+ {
155
+ variant: resolvedVariant,
156
+ size: resolvedSize,
157
+ disabled: isDisabled,
158
+ danger,
159
+ htmlType: "button",
160
+ className: triggerClasses,
161
+ "aria-label": triggerLabel,
162
+ "aria-haspopup": "menu",
163
+ "aria-expanded": menuOpen,
164
+ "data-split-button-trigger": "",
165
+ children: trigger ?? /* @__PURE__ */ jsx(Chevron, { open: menuOpen })
166
+ }
167
+ ),
168
+ children: menuNode
169
+ }
170
+ )
171
+ ]
172
+ }
173
+ );
174
+ };
175
+ var SplitButton_default = SplitButton;
176
+
177
+ export {
178
+ SplitButton,
179
+ SplitButton_default
180
+ };
@@ -0,0 +1,248 @@
1
+ // src/components/ScrollArea.tsx
2
+ import {
3
+ forwardRef,
4
+ useCallback,
5
+ useEffect,
6
+ useImperativeHandle,
7
+ useMemo,
8
+ useRef,
9
+ useState
10
+ } from "react";
11
+ import {
12
+ classNames,
13
+ computeScrollAreaState,
14
+ computeScrollFromThumbOffset,
15
+ computeScrollFromTrackPoint,
16
+ createDocumentDragSession,
17
+ createEmptyScrollAreaState,
18
+ getScrollAreaBoxStyle,
19
+ getScrollAreaContentClasses,
20
+ getScrollAreaScrollbarClasses,
21
+ getScrollAreaShadowClasses,
22
+ getScrollAreaShadowSides,
23
+ getScrollAreaThumbClasses,
24
+ getScrollAreaThumbStyle,
25
+ getScrollAreaViewportClasses,
26
+ observeScrollAreaSize,
27
+ readScrollAreaMetrics,
28
+ scrollAreaRootClasses,
29
+ shouldRenderScrollAreaScrollbar,
30
+ SCROLL_AREA_MIN_THUMB_SIZE
31
+ } from "@expcat/tigercat-core";
32
+ import { jsx, jsxs } from "react/jsx-runtime";
33
+ var ScrollArea = forwardRef(
34
+ ({
35
+ direction = "vertical",
36
+ scrollbar = "auto",
37
+ scrollbarSize = "md",
38
+ shadow = false,
39
+ minThumbSize = SCROLL_AREA_MIN_THUMB_SIZE,
40
+ height,
41
+ maxHeight,
42
+ width,
43
+ maxWidth,
44
+ ariaLabel,
45
+ className,
46
+ viewportClassName,
47
+ children,
48
+ onScroll,
49
+ ...rest
50
+ }, ref) => {
51
+ const viewportRef = useRef(null);
52
+ const contentRef = useRef(null);
53
+ const dragSessionRef = useRef(null);
54
+ const stateRef = useRef(createEmptyScrollAreaState());
55
+ const [state, setState] = useState(stateRef.current);
56
+ const [draggingAxis, setDraggingAxis] = useState(null);
57
+ const onScrollRef = useRef(onScroll);
58
+ onScrollRef.current = onScroll;
59
+ const syncState = useCallback(() => {
60
+ const viewport = viewportRef.current;
61
+ if (!viewport) return;
62
+ const next = computeScrollAreaState(readScrollAreaMetrics(viewport), minThumbSize);
63
+ stateRef.current = next;
64
+ setState(next);
65
+ }, [minThumbSize]);
66
+ const handleScroll = useCallback(() => {
67
+ const viewport = viewportRef.current;
68
+ if (!viewport) return;
69
+ syncState();
70
+ onScrollRef.current?.({
71
+ scrollTop: viewport.scrollTop,
72
+ scrollLeft: viewport.scrollLeft,
73
+ state: stateRef.current
74
+ });
75
+ }, [syncState]);
76
+ const applyScroll = useCallback(
77
+ (axis, position) => {
78
+ const viewport = viewportRef.current;
79
+ if (!viewport) return;
80
+ if (axis === "y") viewport.scrollTop = position;
81
+ else viewport.scrollLeft = position;
82
+ handleScroll();
83
+ },
84
+ [handleScroll]
85
+ );
86
+ const startThumbDrag = useCallback(
87
+ (axis, event) => {
88
+ const viewport = viewportRef.current;
89
+ if (!viewport) return;
90
+ event.preventDefault();
91
+ event.stopPropagation();
92
+ const axisState = axis === "y" ? stateRef.current.y : stateRef.current.x;
93
+ const startOffset = axisState.thumbOffset;
94
+ const startPoint = axis === "y" ? event.clientY : event.clientX;
95
+ dragSessionRef.current?.dispose();
96
+ setDraggingAxis(axis);
97
+ dragSessionRef.current = createDocumentDragSession({
98
+ startX: event.clientX,
99
+ startY: event.clientY,
100
+ ownerDocument: viewport.ownerDocument,
101
+ onMove: ({ currentX, currentY }) => {
102
+ const current = axis === "y" ? currentY : currentX;
103
+ const trackSize = axis === "y" ? viewport.clientHeight : viewport.clientWidth;
104
+ applyScroll(
105
+ axis,
106
+ computeScrollFromThumbOffset(
107
+ startOffset + (current - startPoint),
108
+ trackSize,
109
+ axisState.thumbSize,
110
+ axis === "y" ? viewport.scrollHeight : viewport.scrollWidth,
111
+ trackSize
112
+ )
113
+ );
114
+ },
115
+ onEnd: () => {
116
+ setDraggingAxis(null);
117
+ dragSessionRef.current = null;
118
+ }
119
+ });
120
+ },
121
+ [applyScroll]
122
+ );
123
+ const handleTrackMouseDown = useCallback(
124
+ (axis, event) => {
125
+ const target = event.target;
126
+ if (target?.dataset.scrollAreaThumb) {
127
+ startThumbDrag(axis, event);
128
+ return;
129
+ }
130
+ const viewport = viewportRef.current;
131
+ if (!viewport) return;
132
+ const rect = event.currentTarget.getBoundingClientRect();
133
+ const point = axis === "y" ? event.clientY - rect.top : event.clientX - rect.left;
134
+ const axisState = axis === "y" ? stateRef.current.y : stateRef.current.x;
135
+ const trackSize = axis === "y" ? viewport.clientHeight : viewport.clientWidth;
136
+ applyScroll(
137
+ axis,
138
+ computeScrollFromTrackPoint(
139
+ point,
140
+ trackSize,
141
+ axisState.thumbSize,
142
+ axis === "y" ? viewport.scrollHeight : viewport.scrollWidth,
143
+ trackSize
144
+ )
145
+ );
146
+ },
147
+ [applyScroll, startThumbDrag]
148
+ );
149
+ const scrollTo = useCallback(
150
+ (options) => {
151
+ const viewport = viewportRef.current;
152
+ if (!viewport) return;
153
+ const behavior = options.behavior ?? "auto";
154
+ if (typeof viewport.scrollTo === "function") {
155
+ viewport.scrollTo({ top: options.top, left: options.left, behavior });
156
+ } else {
157
+ if (options.top !== void 0) viewport.scrollTop = options.top;
158
+ if (options.left !== void 0) viewport.scrollLeft = options.left;
159
+ }
160
+ handleScroll();
161
+ },
162
+ [handleScroll]
163
+ );
164
+ useImperativeHandle(
165
+ ref,
166
+ () => ({
167
+ scrollTo,
168
+ scrollToTop: (behavior) => scrollTo({ top: 0, behavior }),
169
+ scrollToBottom: (behavior) => scrollTo({ top: viewportRef.current?.scrollHeight ?? 0, behavior }),
170
+ getViewport: () => viewportRef.current,
171
+ getState: () => stateRef.current
172
+ }),
173
+ [scrollTo]
174
+ );
175
+ useEffect(() => {
176
+ syncState();
177
+ return observeScrollAreaSize([viewportRef.current, contentRef.current], syncState);
178
+ }, [syncState, direction]);
179
+ useEffect(() => () => dragSessionRef.current?.dispose(), []);
180
+ const shadowSides = useMemo(
181
+ () => shadow ? getScrollAreaShadowSides(state, direction) : [],
182
+ [shadow, state, direction]
183
+ );
184
+ const renderScrollbar = (axis) => {
185
+ const axisState = axis === "y" ? state.y : state.x;
186
+ if (!shouldRenderScrollAreaScrollbar(scrollbar, direction, axis, axisState)) return null;
187
+ return /* @__PURE__ */ jsx(
188
+ "div",
189
+ {
190
+ className: getScrollAreaScrollbarClasses(axis, scrollbarSize, scrollbar),
191
+ "data-scroll-area-scrollbar": axis,
192
+ "aria-hidden": "true",
193
+ onMouseDown: (event) => handleTrackMouseDown(axis, event),
194
+ children: /* @__PURE__ */ jsx(
195
+ "div",
196
+ {
197
+ className: getScrollAreaThumbClasses(axis, draggingAxis === axis),
198
+ style: getScrollAreaThumbStyle(axis, axisState),
199
+ "data-scroll-area-thumb": axis
200
+ }
201
+ )
202
+ }
203
+ );
204
+ };
205
+ return /* @__PURE__ */ jsxs("div", { ...rest, className: classNames(scrollAreaRootClasses, className), "data-scroll-area": "", children: [
206
+ /* @__PURE__ */ jsx(
207
+ "div",
208
+ {
209
+ ref: viewportRef,
210
+ className: getScrollAreaViewportClasses(direction, viewportClassName),
211
+ style: getScrollAreaBoxStyle({ height, maxHeight, width, maxWidth }),
212
+ tabIndex: 0,
213
+ role: ariaLabel ? "region" : void 0,
214
+ "aria-label": ariaLabel,
215
+ "data-scroll-area-viewport": "",
216
+ onScroll: handleScroll,
217
+ children: /* @__PURE__ */ jsx(
218
+ "div",
219
+ {
220
+ ref: contentRef,
221
+ className: getScrollAreaContentClasses(direction),
222
+ "data-scroll-area-content": "",
223
+ children
224
+ }
225
+ )
226
+ }
227
+ ),
228
+ shadowSides.map((side) => /* @__PURE__ */ jsx(
229
+ "div",
230
+ {
231
+ className: getScrollAreaShadowClasses(side),
232
+ "data-scroll-area-shadow": side,
233
+ "aria-hidden": "true"
234
+ },
235
+ side
236
+ )),
237
+ renderScrollbar("y"),
238
+ renderScrollbar("x")
239
+ ] });
240
+ }
241
+ );
242
+ ScrollArea.displayName = "ScrollArea";
243
+ var ScrollArea_default = ScrollArea;
244
+
245
+ export {
246
+ ScrollArea,
247
+ ScrollArea_default
248
+ };