@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,234 @@
1
+ import {
2
+ useFormItemControlContext
3
+ } from "./chunk-F6T3OLTQ.mjs";
4
+ import {
5
+ useControlledState
6
+ } from "./chunk-MEFARVQI.mjs";
7
+ import {
8
+ useTigerConfig
9
+ } from "./chunk-YSIXURBW.mjs";
10
+
11
+ // src/components/InputOTP.tsx
12
+ import { useEffect, useId, useMemo, useRef } from "react";
13
+ import {
14
+ applyOtpBackspace,
15
+ applyOtpCharInput,
16
+ applyOtpDelete,
17
+ classNames,
18
+ devWarn,
19
+ distributeOtpPaste,
20
+ formatOtpSlotLabel,
21
+ getInputOTPLabels,
22
+ getOtpContainerClasses,
23
+ getOtpErrorClasses,
24
+ getOtpSeparatorClasses,
25
+ getOtpSeparatorIndices,
26
+ getOtpSlotClasses,
27
+ injectShakeStyle,
28
+ isOtpComplete,
29
+ normalizeOtpValue,
30
+ SHAKE_CLASS
31
+ } from "@expcat/tigercat-core";
32
+ import { jsx, jsxs } from "react/jsx-runtime";
33
+ var InputOTP = ({
34
+ size = "md",
35
+ status: statusProp,
36
+ errorMessage: errorMessageProp,
37
+ _shakeTrigger: shakeTriggerProp,
38
+ length = 6,
39
+ value,
40
+ defaultValue,
41
+ type = "numeric",
42
+ pattern,
43
+ masked = false,
44
+ maskChar = "\u2022",
45
+ groups,
46
+ separator = "-",
47
+ disabled = false,
48
+ readonly = false,
49
+ autoFocus = false,
50
+ name,
51
+ id,
52
+ ariaLabel,
53
+ onChange,
54
+ onComplete,
55
+ onFocus,
56
+ onBlur,
57
+ className,
58
+ style,
59
+ ...rest
60
+ }) => {
61
+ injectShakeStyle();
62
+ const config = useTigerConfig();
63
+ const formItemControl = useFormItemControlContext();
64
+ const status = statusProp ?? formItemControl?.status ?? "default";
65
+ const errorMessage = errorMessageProp ?? formItemControl?.errorMessage;
66
+ const shakeTrigger = shakeTriggerProp ?? formItemControl?.shakeTrigger;
67
+ const labels = getInputOTPLabels(config.locale);
68
+ const reactId = useId();
69
+ const errorMsgId = `tiger-input-otp-error-${reactId}`;
70
+ const containerRef = useRef(null);
71
+ const slotRefs = useRef([]);
72
+ const charOptions = useMemo(() => ({ type, pattern }), [type, pattern]);
73
+ const [rawValue, setRawValue] = useControlledState(value, defaultValue ?? "", onChange);
74
+ const currentValue = normalizeOtpValue(rawValue, length, charOptions);
75
+ const separatorIndices = useMemo(() => getOtpSeparatorIndices(length, groups), [length, groups]);
76
+ if (groups && groups.length > 0 && separatorIndices.length === 0) {
77
+ devWarn("InputOTP.groups", "[Tigercat] InputOTP: `groups` must sum to `length`; ignoring.");
78
+ }
79
+ useEffect(() => {
80
+ if (status === "error" && containerRef.current) {
81
+ const el = containerRef.current;
82
+ el.classList.remove(SHAKE_CLASS);
83
+ void el.offsetWidth;
84
+ el.classList.add(SHAKE_CLASS);
85
+ }
86
+ }, [status, shakeTrigger]);
87
+ useEffect(() => {
88
+ if (autoFocus) {
89
+ slotRefs.current[Math.min(currentValue.length, length - 1)]?.focus();
90
+ }
91
+ }, []);
92
+ const focusSlot = (index) => {
93
+ slotRefs.current[Math.max(0, Math.min(index, length - 1))]?.focus();
94
+ };
95
+ const displayChar = (val, index) => {
96
+ const char = val[index] ?? "";
97
+ return char && masked ? maskChar : char;
98
+ };
99
+ const emitValue = (next) => {
100
+ if (next === currentValue) return;
101
+ setRawValue(next);
102
+ if (isOtpComplete(next, length)) onComplete?.(next);
103
+ };
104
+ const isInteractive = !disabled && !readonly;
105
+ const handleSlotChange = (index, event) => {
106
+ if (!isInteractive) return;
107
+ const result = applyOtpCharInput(
108
+ currentValue,
109
+ index,
110
+ event.currentTarget.value,
111
+ length,
112
+ charOptions
113
+ );
114
+ event.currentTarget.value = displayChar(result.value, index);
115
+ emitValue(result.value);
116
+ focusSlot(result.nextIndex);
117
+ };
118
+ const handleSlotKeyDown = (index, event) => {
119
+ if (!isInteractive) return;
120
+ switch (event.key) {
121
+ case "Backspace": {
122
+ event.preventDefault();
123
+ const result = applyOtpBackspace(currentValue, index);
124
+ emitValue(result.value);
125
+ focusSlot(result.nextIndex);
126
+ break;
127
+ }
128
+ case "Delete": {
129
+ event.preventDefault();
130
+ const result = applyOtpDelete(currentValue, index);
131
+ emitValue(result.value);
132
+ break;
133
+ }
134
+ case "ArrowLeft":
135
+ event.preventDefault();
136
+ focusSlot(index - 1);
137
+ break;
138
+ case "ArrowRight":
139
+ event.preventDefault();
140
+ focusSlot(index + 1);
141
+ break;
142
+ case "Home":
143
+ event.preventDefault();
144
+ focusSlot(0);
145
+ break;
146
+ case "End":
147
+ event.preventDefault();
148
+ focusSlot(Math.min(currentValue.length, length - 1));
149
+ break;
150
+ }
151
+ };
152
+ const handlePaste = (event) => {
153
+ if (!isInteractive) return;
154
+ event.preventDefault();
155
+ const result = distributeOtpPaste(event.clipboardData.getData("text"), length, charOptions);
156
+ if (!result) return;
157
+ emitValue(result.value);
158
+ focusSlot(result.nextIndex);
159
+ };
160
+ const handleSlotFocus = (event) => {
161
+ event.currentTarget.select();
162
+ };
163
+ const handleGroupFocus = (event) => {
164
+ if (!containerRef.current?.contains(event.relatedTarget)) {
165
+ onFocus?.(event);
166
+ }
167
+ };
168
+ const handleGroupBlur = (event) => {
169
+ if (!containerRef.current?.contains(event.relatedTarget)) {
170
+ onBlur?.(event);
171
+ }
172
+ };
173
+ const activeError = status === "error" && !!errorMessage;
174
+ const slotClasses = getOtpSlotClasses(size, status, { disabled, readonly });
175
+ const slots = [];
176
+ for (let i = 0; i < length; i++) {
177
+ slots.push(
178
+ /* @__PURE__ */ jsx(
179
+ "input",
180
+ {
181
+ ref: (el) => {
182
+ slotRefs.current[i] = el;
183
+ },
184
+ className: slotClasses,
185
+ type: "text",
186
+ inputMode: type === "numeric" ? "numeric" : void 0,
187
+ autoComplete: i === 0 ? "one-time-code" : "off",
188
+ value: displayChar(currentValue, i),
189
+ disabled,
190
+ readOnly: readonly,
191
+ "aria-label": formatOtpSlotLabel(labels.slotLabel, i + 1, length),
192
+ ...status === "error" ? { "aria-invalid": true } : {},
193
+ onChange: (event) => handleSlotChange(i, event),
194
+ onKeyDown: (event) => handleSlotKeyDown(i, event),
195
+ onFocus: handleSlotFocus
196
+ },
197
+ `slot-${i}`
198
+ )
199
+ );
200
+ if (separatorIndices.includes(i) && separator) {
201
+ slots.push(
202
+ /* @__PURE__ */ jsx("span", { "aria-hidden": "true", className: getOtpSeparatorClasses(size), children: separator }, `separator-${i}`)
203
+ );
204
+ }
205
+ }
206
+ return /* @__PURE__ */ jsxs("div", { className: classNames("inline-block", className), style, children: [
207
+ /* @__PURE__ */ jsxs(
208
+ "div",
209
+ {
210
+ ...rest,
211
+ ref: containerRef,
212
+ id,
213
+ role: "group",
214
+ "aria-label": ariaLabel ?? labels.groupLabel,
215
+ ...activeError ? { "aria-describedby": errorMsgId } : {},
216
+ className: getOtpContainerClasses(size),
217
+ onPaste: handlePaste,
218
+ onFocus: handleGroupFocus,
219
+ onBlur: handleGroupBlur,
220
+ children: [
221
+ slots,
222
+ name && /* @__PURE__ */ jsx("input", { type: "hidden", name, value: currentValue })
223
+ ]
224
+ }
225
+ ),
226
+ activeError && /* @__PURE__ */ jsx("div", { id: errorMsgId, className: getOtpErrorClasses(), children: errorMessage })
227
+ ] });
228
+ };
229
+ var InputOTP_default = InputOTP;
230
+
231
+ export {
232
+ InputOTP,
233
+ InputOTP_default
234
+ };
@@ -61,7 +61,17 @@ var DataExport = ({
61
61
  setExporting(false);
62
62
  }
63
63
  },
64
- [columns, dataSource, sheetName, cellFormatter, fileName, disabled, exporting, onExport, onError]
64
+ [
65
+ columns,
66
+ dataSource,
67
+ sheetName,
68
+ cellFormatter,
69
+ fileName,
70
+ disabled,
71
+ exporting,
72
+ onExport,
73
+ onError
74
+ ]
65
75
  );
66
76
  const formatLabel = (format) => format === "xlsx" ? resolvedLabels.xlsxText : resolvedLabels.markdownText;
67
77
  if (formats.length === 0) return null;
@@ -0,0 +1,161 @@
1
+ // src/components/Masonry.tsx
2
+ import {
3
+ Children,
4
+ forwardRef,
5
+ useCallback,
6
+ useEffect,
7
+ useImperativeHandle,
8
+ useMemo,
9
+ useRef,
10
+ useState,
11
+ useSyncExternalStore
12
+ } from "react";
13
+ import {
14
+ classNames,
15
+ computeMasonryColumnHeights,
16
+ distributeMasonryItems,
17
+ getMasonryColumnClasses,
18
+ getMasonryColumnStyle,
19
+ getMasonryGapStyle,
20
+ getMasonryItemClasses,
21
+ getMasonryRootClasses,
22
+ moduloDistributeMasonryItems,
23
+ observeScrollAreaSize,
24
+ readMasonryItemHeight,
25
+ resolveMasonryColumnCount,
26
+ resolveMasonryGap,
27
+ MASONRY_DEFAULT_COLUMNS,
28
+ MASONRY_DEFAULT_GAP
29
+ } from "@expcat/tigercat-core";
30
+ import { jsx } from "react/jsx-runtime";
31
+ function childSignature(nodes) {
32
+ return nodes.map(
33
+ (node, index) => `${index}:${typeof node === "object" && node !== null && "key" in node ? String(node.key ?? "") : String(node)}`
34
+ ).join("\0");
35
+ }
36
+ function sameHeights(previous, next) {
37
+ return previous.length === next.length && previous.every((height, index) => height === next[index]);
38
+ }
39
+ function subscribeToResize(onChange) {
40
+ window.addEventListener("resize", onChange);
41
+ return () => window.removeEventListener("resize", onChange);
42
+ }
43
+ var noopSubscribe = () => () => void 0;
44
+ var Masonry = forwardRef(
45
+ ({
46
+ columns = MASONRY_DEFAULT_COLUMNS,
47
+ gap = MASONRY_DEFAULT_GAP,
48
+ className,
49
+ columnClassName,
50
+ itemClassName,
51
+ children,
52
+ onLayout,
53
+ style,
54
+ ...rest
55
+ }, ref) => {
56
+ const itemRefs = useRef(/* @__PURE__ */ new Map());
57
+ const heightsRef = useRef([]);
58
+ const lastSignatureRef = useRef("");
59
+ const onLayoutRef = useRef(onLayout);
60
+ onLayoutRef.current = onLayout;
61
+ const [distribution, setDistribution] = useState([]);
62
+ const childNodes = useMemo(() => Children.toArray(children), [children]);
63
+ const signature = childSignature(childNodes);
64
+ const isResponsive = typeof columns === "object" || typeof gap === "object";
65
+ const subscribe = useMemo(
66
+ () => isResponsive ? subscribeToResize : noopSubscribe,
67
+ [isResponsive]
68
+ );
69
+ const windowWidth = useSyncExternalStore(
70
+ subscribe,
71
+ () => window.innerWidth,
72
+ () => 1024
73
+ );
74
+ const columnCount = resolveMasonryColumnCount(columns, windowWidth);
75
+ const gapPx = resolveMasonryGap(gap, windowWidth);
76
+ const relayout = useCallback(() => {
77
+ const elements = itemRefs.current;
78
+ const indices = Array.from(elements.keys());
79
+ const maxIndex = indices.length > 0 ? Math.max(...indices) : -1;
80
+ const nextHeights = [];
81
+ for (let index = 0; index <= maxIndex; index++) {
82
+ const element = elements.get(index);
83
+ nextHeights.push(element ? readMasonryItemHeight(element) : 0);
84
+ }
85
+ const countChanged = nextHeights.length !== heightsRef.current.length;
86
+ if (!countChanged && sameHeights(heightsRef.current, nextHeights)) return;
87
+ heightsRef.current = nextHeights;
88
+ const nextDistribution = distributeMasonryItems(nextHeights, columnCount);
89
+ setDistribution(nextDistribution);
90
+ onLayoutRef.current?.({
91
+ columnCount,
92
+ columnHeights: computeMasonryColumnHeights(nextHeights, nextDistribution, gapPx)
93
+ });
94
+ }, [columnCount, gapPx]);
95
+ useEffect(() => {
96
+ if (lastSignatureRef.current !== signature) {
97
+ lastSignatureRef.current = signature;
98
+ relayout();
99
+ return;
100
+ }
101
+ if (heightsRef.current.length === 0) return;
102
+ const nextDistribution = distributeMasonryItems(heightsRef.current, columnCount);
103
+ setDistribution(nextDistribution);
104
+ onLayoutRef.current?.({
105
+ columnCount,
106
+ columnHeights: computeMasonryColumnHeights(heightsRef.current, nextDistribution, gapPx)
107
+ });
108
+ }, [signature, columnCount, gapPx, relayout]);
109
+ useEffect(() => {
110
+ if (childNodes.length === 0) return;
111
+ return observeScrollAreaSize(Array.from(itemRefs.current.values()), relayout);
112
+ }, [signature, distribution, relayout, childNodes.length]);
113
+ useImperativeHandle(
114
+ ref,
115
+ () => ({
116
+ relayout,
117
+ getColumnCount: () => columnCount
118
+ }),
119
+ [relayout, columnCount]
120
+ );
121
+ const activeDistribution = distribution.length > 0 && heightsRef.current.length === childNodes.length ? distribution : moduloDistributeMasonryItems(childNodes.length, columnCount);
122
+ return /* @__PURE__ */ jsx(
123
+ "div",
124
+ {
125
+ ...rest,
126
+ className: classNames(getMasonryRootClasses(className)),
127
+ style: { ...getMasonryGapStyle(gapPx), ...style },
128
+ "data-masonry": "",
129
+ children: activeDistribution.map((indices, columnIndex) => /* @__PURE__ */ jsx(
130
+ "div",
131
+ {
132
+ className: getMasonryColumnClasses(columnClassName),
133
+ style: getMasonryColumnStyle(gapPx),
134
+ "data-masonry-column": columnIndex,
135
+ children: indices.map((index) => /* @__PURE__ */ jsx(
136
+ "div",
137
+ {
138
+ className: getMasonryItemClasses(itemClassName),
139
+ "data-masonry-item": index,
140
+ ref: (el) => {
141
+ if (el) itemRefs.current.set(index, el);
142
+ else itemRefs.current.delete(index);
143
+ },
144
+ children: childNodes[index]
145
+ },
146
+ `item-${index}`
147
+ ))
148
+ },
149
+ `column-${columnIndex}`
150
+ ))
151
+ }
152
+ );
153
+ }
154
+ );
155
+ Masonry.displayName = "Masonry";
156
+ var Masonry_default = Masonry;
157
+
158
+ export {
159
+ Masonry,
160
+ Masonry_default
161
+ };
@@ -4,7 +4,7 @@ import {
4
4
  } from "./chunk-Z3CRHC5F.mjs";
5
5
  import {
6
6
  List
7
- } from "./chunk-QJE3MZBO.mjs";
7
+ } from "./chunk-FSMJLJFL.mjs";
8
8
  import {
9
9
  Button
10
10
  } from "./chunk-5TE7KLE5.mjs";
@@ -1,3 +1,6 @@
1
+ import {
2
+ VirtualList
3
+ } from "./chunk-Y5X3G2LD.mjs";
1
4
  import {
2
5
  renderOverlayPortal,
3
6
  useAnchoredOverlay
@@ -7,24 +10,30 @@ import {
7
10
  } from "./chunk-YSIXURBW.mjs";
8
11
 
9
12
  // src/components/TreeSelect.tsx
10
- import { useState, useMemo, useRef, useId } from "react";
13
+ import { useState, useMemo, useRef, useId, useLayoutEffect, useCallback } from "react";
11
14
  import {
12
15
  resolveLocaleText,
13
16
  mergeTigerLocale,
14
17
  treeSelectBaseClasses,
15
- treeSelectDropdownClasses,
16
18
  treeSelectSearchClasses,
17
19
  treeSelectEmptyClasses,
18
20
  getTreeSelectTriggerClasses,
19
21
  getTreeSelectNodeClasses,
22
+ getTreeSelectDropdownClasses,
20
23
  getTreeSelectDisplayLabel,
21
24
  getAllTreeSelectKeys,
22
25
  flattenTreeSelectNodes,
23
26
  filterTreeSelectNodes,
27
+ getTreeSelectVisibleIndex,
28
+ alignTreeSelectVirtualScroll,
29
+ TREE_SELECT_DEFAULT_HEIGHT,
30
+ TREE_SELECT_DEFAULT_ITEM_HEIGHT,
24
31
  getPickerComboboxAria,
25
32
  getPickerListboxAria,
26
33
  getPickerOptionAria,
27
34
  getPickerTriggerKeyAction,
35
+ getPickerNavigationIndex,
36
+ findFirstEnabledIndex,
28
37
  classNames,
29
38
  icon20ViewBox,
30
39
  chevronDownSolidIcon20PathD,
@@ -45,6 +54,9 @@ var TREESELECT_KEYS = /* @__PURE__ */ new Set([
45
54
  "defaultSearchValue",
46
55
  "emptyText",
47
56
  "defaultExpandAll",
57
+ "virtual",
58
+ "height",
59
+ "itemHeight",
48
60
  "onChange",
49
61
  "onSearchChange"
50
62
  ]);
@@ -62,6 +74,9 @@ var TreeSelect = (props) => {
62
74
  defaultSearchValue = "",
63
75
  emptyText,
64
76
  defaultExpandAll = false,
77
+ virtual = false,
78
+ height = TREE_SELECT_DEFAULT_HEIGHT,
79
+ itemHeight = TREE_SELECT_DEFAULT_ITEM_HEIGHT,
65
80
  className,
66
81
  onChange,
67
82
  onSearchChange,
@@ -90,6 +105,8 @@ var TreeSelect = (props) => {
90
105
  const containerRef = useRef(null);
91
106
  const triggerRef = useRef(null);
92
107
  const dropdownRef = useRef(null);
108
+ const virtualListWrapperRef = useRef(null);
109
+ const [activeIndex, setActiveIndex] = useState(-1);
93
110
  const overlay = useAnchoredOverlay({
94
111
  enabled: isOpen,
95
112
  referenceRef: triggerRef,
@@ -118,6 +135,60 @@ var TreeSelect = (props) => {
118
135
  if (!matchedKeys) return flatNodes;
119
136
  return flatNodes.filter((f) => matchedKeys.has(f.node.key));
120
137
  }, [flatNodes, matchedKeys]);
138
+ const isNodeDisabled = useCallback((item) => !!item.node.disabled, []);
139
+ const visibleNodesRef = useRef(visibleNodes);
140
+ visibleNodesRef.current = visibleNodes;
141
+ const valueRef = useRef(value);
142
+ valueRef.current = value;
143
+ const lastActiveKeyRef = useRef(void 0);
144
+ const resolveActiveIndex = useCallback(() => {
145
+ const nodes = visibleNodesRef.current;
146
+ const selected = getTreeSelectVisibleIndex(nodes, valueRef.current);
147
+ if (selected >= 0 && !nodes[selected]?.node.disabled) return selected;
148
+ return findFirstEnabledIndex(nodes, isNodeDisabled);
149
+ }, [isNodeDisabled]);
150
+ const commitActiveIndex = useCallback((index) => {
151
+ lastActiveKeyRef.current = index >= 0 ? visibleNodesRef.current[index]?.node.key : void 0;
152
+ setActiveIndex(index);
153
+ }, []);
154
+ const alignVirtualScroll = useCallback(
155
+ (index) => {
156
+ if (!virtual) return;
157
+ const wrapper = virtualListWrapperRef.current;
158
+ const el = wrapper?.firstElementChild ?? wrapper;
159
+ alignTreeSelectVirtualScroll(el, index, itemHeight, height);
160
+ },
161
+ [virtual, itemHeight, height]
162
+ );
163
+ useLayoutEffect(() => {
164
+ if (!isOpen) {
165
+ commitActiveIndex(-1);
166
+ return;
167
+ }
168
+ if (!virtual) return;
169
+ commitActiveIndex(resolveActiveIndex());
170
+ }, [isOpen, searchQuery, virtual, commitActiveIndex, resolveActiveIndex]);
171
+ useLayoutEffect(() => {
172
+ if (!isOpen || !virtual) return;
173
+ const remapped = getTreeSelectVisibleIndex(visibleNodesRef.current, lastActiveKeyRef.current);
174
+ if (remapped >= 0) setActiveIndex(remapped);
175
+ }, [expandedKeys, treeData, isOpen, virtual]);
176
+ useLayoutEffect(() => {
177
+ if (!isOpen || !virtual) return;
178
+ alignVirtualScroll(activeIndex);
179
+ }, [activeIndex, isOpen, virtual, alignVirtualScroll, visibleNodes.length]);
180
+ function handleVirtualListKeyDown(e) {
181
+ if (e.key !== "ArrowDown" && e.key !== "ArrowUp" && e.key !== "Home" && e.key !== "End") {
182
+ return;
183
+ }
184
+ e.preventDefault();
185
+ e.stopPropagation();
186
+ setActiveIndex((current) => {
187
+ const next = getPickerNavigationIndex(visibleNodes, current, e.key, isNodeDisabled);
188
+ lastActiveKeyRef.current = visibleNodes[next]?.node.key;
189
+ return next;
190
+ });
191
+ }
121
192
  function openDropdown() {
122
193
  if (disabled) return;
123
194
  setIsOpen(true);
@@ -181,6 +252,38 @@ var TreeSelect = (props) => {
181
252
  closeDropdown();
182
253
  }
183
254
  }
255
+ function renderFlatNode(flatNode) {
256
+ const { node, level, hasChildren, isExpanded } = flatNode;
257
+ const selected = isSelected(node.key);
258
+ const indent = level * 20;
259
+ return /* @__PURE__ */ jsxs(
260
+ "div",
261
+ {
262
+ ...getPickerOptionAria({ selected, disabled: !!node.disabled }),
263
+ className: getTreeSelectNodeClasses(selected, !!node.disabled, size),
264
+ style: { paddingLeft: `${indent + 8}px`, height: virtual ? "100%" : void 0 },
265
+ onClick: (e) => {
266
+ e.stopPropagation();
267
+ handleNodeSelect(node);
268
+ },
269
+ children: [
270
+ hasChildren ? /* @__PURE__ */ jsx(
271
+ "span",
272
+ {
273
+ className: classNames(
274
+ "inline-flex items-center justify-center w-4 h-4 mr-1 transition-transform",
275
+ isExpanded ? "rotate-90" : ""
276
+ ),
277
+ onClick: (e) => toggleExpand(node.key, e),
278
+ children: /* @__PURE__ */ jsx("svg", { className: "w-3 h-3", viewBox: icon20ViewBox, fill: "currentColor", children: /* @__PURE__ */ jsx("path", { d: chevronRightSolidIcon20PathD, fillRule: "evenodd", clipRule: "evenodd" }) })
279
+ }
280
+ ) : /* @__PURE__ */ jsx("span", { className: "w-4 mr-1" }),
281
+ /* @__PURE__ */ jsx("span", { className: "flex-1 truncate", children: node.label })
282
+ ]
283
+ },
284
+ String(node.key)
285
+ );
286
+ }
184
287
  return /* @__PURE__ */ jsxs("div", { ref: containerRef, className: classNames(treeSelectBaseClasses, className), ...divProps, children: [
185
288
  /* @__PURE__ */ jsxs(
186
289
  "button",
@@ -239,7 +342,7 @@ var TreeSelect = (props) => {
239
342
  {
240
343
  ref: dropdownRef,
241
344
  ...getPickerListboxAria({ id: listboxId }),
242
- className: classNames(treeSelectDropdownClasses, overlay.floatingClasses),
345
+ className: classNames(getTreeSelectDropdownClasses(virtual), overlay.floatingClasses),
243
346
  style: overlay.floatingStyles,
244
347
  "data-positioned": overlay.positioned,
245
348
  children: [
@@ -260,45 +363,27 @@ var TreeSelect = (props) => {
260
363
  onChange: (e) => updateSearchValue(e.target.value)
261
364
  }
262
365
  ),
263
- visibleNodes.length > 0 ? visibleNodes.map((flatNode) => {
264
- const { node, level, hasChildren, isExpanded } = flatNode;
265
- const selected = isSelected(node.key);
266
- const indent = level * 20;
267
- return /* @__PURE__ */ jsxs(
268
- "div",
269
- {
270
- ...getPickerOptionAria({ selected, disabled: !!node.disabled }),
271
- className: getTreeSelectNodeClasses(selected, !!node.disabled, size),
272
- style: { paddingLeft: `${indent + 8}px` },
273
- onClick: (e) => {
274
- e.stopPropagation();
275
- handleNodeSelect(node);
276
- },
277
- children: [
278
- hasChildren ? /* @__PURE__ */ jsx(
279
- "span",
280
- {
281
- className: classNames(
282
- "inline-flex items-center justify-center w-4 h-4 mr-1 transition-transform",
283
- isExpanded ? "rotate-90" : ""
284
- ),
285
- onClick: (e) => toggleExpand(node.key, e),
286
- children: /* @__PURE__ */ jsx("svg", { className: "w-3 h-3", viewBox: icon20ViewBox, fill: "currentColor", children: /* @__PURE__ */ jsx(
287
- "path",
288
- {
289
- d: chevronRightSolidIcon20PathD,
290
- fillRule: "evenodd",
291
- clipRule: "evenodd"
292
- }
293
- ) })
294
- }
295
- ) : /* @__PURE__ */ jsx("span", { className: "w-4 mr-1" }),
296
- /* @__PURE__ */ jsx("span", { className: "flex-1 truncate", children: node.label })
297
- ]
298
- },
299
- String(node.key)
300
- );
301
- }) : /* @__PURE__ */ jsx("div", { className: treeSelectEmptyClasses, children: resolveLocaleText("No data", emptyText, mergedLocale?.common?.emptyText) })
366
+ visibleNodes.length > 0 ? virtual ? /* @__PURE__ */ jsx(
367
+ "div",
368
+ {
369
+ ref: virtualListWrapperRef,
370
+ "data-tiger-treeselect-virtual": "",
371
+ tabIndex: 0,
372
+ onKeyDown: handleVirtualListKeyDown,
373
+ children: /* @__PURE__ */ jsx(
374
+ VirtualList,
375
+ {
376
+ itemCount: visibleNodes.length,
377
+ itemHeight,
378
+ height,
379
+ renderItem: ({ index }) => {
380
+ const item = visibleNodes[index];
381
+ return item ? renderFlatNode(item) : null;
382
+ }
383
+ }
384
+ )
385
+ }
386
+ ) : visibleNodes.map((flatNode) => renderFlatNode(flatNode)) : /* @__PURE__ */ jsx("div", { className: treeSelectEmptyClasses, children: resolveLocaleText("No data", emptyText, mergedLocale?.common?.emptyText) })
302
387
  ]
303
388
  }
304
389
  ) : null,