@expcat/tigercat-react 2.0.19 → 2.1.1

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 (186) hide show
  1. package/README.md +25 -16
  2. package/dist/chunk-246DAUEF.mjs +181 -0
  3. package/dist/{chunk-PU3JY6RE.mjs → chunk-2JMI7TCM.mjs} +1 -1
  4. package/dist/chunk-2YBLMVDT.mjs +428 -0
  5. package/dist/chunk-2YOP55OF.mjs +639 -0
  6. package/dist/{chunk-25D5TXZH.mjs → chunk-32MCQMBJ.mjs} +8 -6
  7. package/dist/chunk-3HHEVMHE.mjs +180 -0
  8. package/dist/{chunk-HSBN5MLS.mjs → chunk-3LACOSJQ.mjs} +1 -1
  9. package/dist/{chunk-F6WV53BS.mjs → chunk-3LDDXHW2.mjs} +28 -11
  10. package/dist/{chunk-IXQAECFP.mjs → chunk-3QNIFKVF.mjs} +12 -14
  11. package/dist/{chunk-UD4DTVJZ.mjs → chunk-4C2X5PZO.mjs} +57 -25
  12. package/dist/chunk-5JETWHLJ.mjs +28 -0
  13. package/dist/{chunk-VA6PZWOH.mjs → chunk-5SEMRHBD.mjs} +14 -6
  14. package/dist/{chunk-EKILTLUO.mjs → chunk-72CJNKDY.mjs} +1 -1
  15. package/dist/{chunk-XE4YFBOZ.mjs → chunk-AUD3VOLO.mjs} +1 -1
  16. package/dist/{chunk-UT5BV2VO.mjs → chunk-AYFKHWJS.mjs} +16 -10
  17. package/dist/{chunk-RPSHILHD.mjs → chunk-BXAH2GH3.mjs} +8 -2
  18. package/dist/{chunk-EW5EO7VE.mjs → chunk-BYD7EAWK.mjs} +1 -1
  19. package/dist/{chunk-BOGOQ44C.mjs → chunk-CQOLQAB5.mjs} +105 -90
  20. package/dist/{chunk-L55UK45A.mjs → chunk-CRIEUKMD.mjs} +5 -2
  21. package/dist/{chunk-DKNVSQSV.mjs → chunk-CUAXRBKO.mjs} +12 -0
  22. package/dist/{chunk-E7OTDBI6.mjs → chunk-DECUASRW.mjs} +17 -19
  23. package/dist/{chunk-XMTEMBQV.mjs → chunk-EAAWB3CK.mjs} +16 -13
  24. package/dist/{chunk-D33BIMGK.mjs → chunk-EG7EMDKK.mjs} +12 -2
  25. package/dist/chunk-EW43YBPV.mjs +457 -0
  26. package/dist/{chunk-HOYBHB7B.mjs → chunk-FNK2VWPP.mjs} +1 -1
  27. package/dist/{chunk-RJPNBYK5.mjs → chunk-FQS24AUH.mjs} +12 -3
  28. package/dist/{chunk-LADKKOJT.mjs → chunk-GBEDWXLL.mjs} +16 -10
  29. package/dist/chunk-GR3Y3XWP.mjs +47 -0
  30. package/dist/{chunk-2ENQZECC.mjs → chunk-HAXRHSCN.mjs} +4 -3
  31. package/dist/{chunk-K4GIKSZE.mjs → chunk-I2BENK5X.mjs} +2 -1
  32. package/dist/chunk-IGBQGR6G.mjs +234 -0
  33. package/dist/chunk-J76R2S62.mjs +161 -0
  34. package/dist/{chunk-DNOPEJ5I.mjs → chunk-JZZPLKTW.mjs} +7 -4
  35. package/dist/{chunk-7KCQMXJA.mjs → chunk-KDURW5AS.mjs} +4 -3
  36. package/dist/{chunk-TFMEHUVZ.mjs → chunk-KKN7KLTB.mjs} +0 -1
  37. package/dist/{chunk-6MGQAJ62.mjs → chunk-KL6YJIZ3.mjs} +24 -7
  38. package/dist/chunk-KRDHM5KB.mjs +259 -0
  39. package/dist/{chunk-HHIW7V7B.mjs → chunk-M6ZPTGBV.mjs} +2 -10
  40. package/dist/chunk-MFPVVGNA.mjs +248 -0
  41. package/dist/{chunk-PXRZ6CKY.mjs → chunk-MJVSZ2UJ.mjs} +46 -16
  42. package/dist/{chunk-LZKBDQE4.mjs → chunk-MYI3FPG4.mjs} +23 -4
  43. package/dist/{chunk-OIGEBCAS.mjs → chunk-NADODEZX.mjs} +69 -1
  44. package/dist/{chunk-KMP5CVNK.mjs → chunk-NJWTZ5MO.mjs} +57 -45
  45. package/dist/chunk-NLGLBTSM.mjs +138 -0
  46. package/dist/chunk-NT7GW2FD.mjs +40 -0
  47. package/dist/{chunk-773LH7BK.mjs → chunk-O6VYD4E4.mjs} +24 -2
  48. package/dist/{chunk-G7D4QV7A.mjs → chunk-O7TV5OKS.mjs} +33 -6
  49. package/dist/{chunk-DPWRXXXW.mjs → chunk-OJ6SHHOZ.mjs} +3 -3
  50. package/dist/chunk-OKYMTHFQ.mjs +279 -0
  51. package/dist/{chunk-UYJJLHVA.mjs → chunk-PMQJ6AYT.mjs} +23 -21
  52. package/dist/chunk-PY2VUNEJ.mjs +494 -0
  53. package/dist/{chunk-BEJZPJIK.mjs → chunk-QOAOOD53.mjs} +10 -4
  54. package/dist/{chunk-2WGEHH5A.mjs → chunk-QU3QXUTC.mjs} +49 -23
  55. package/dist/{chunk-V6P7ZK6O.mjs → chunk-QXSUG7SN.mjs} +3 -3
  56. package/dist/{chunk-FYINJRYN.mjs → chunk-R7YZ6FKP.mjs} +27 -16
  57. package/dist/{chunk-YI6YWH66.mjs → chunk-RE5ZLA5D.mjs} +6 -6
  58. package/dist/{chunk-UCBW5RJN.mjs → chunk-RGVU4EMA.mjs} +11 -6
  59. package/dist/{chunk-IBSOUQGB.mjs → chunk-RRGPDFDI.mjs} +7 -4
  60. package/dist/{chunk-ZH5ZBLAN.mjs → chunk-RUQ3F3MV.mjs} +34 -7
  61. package/dist/chunk-SEOG5AGS.mjs +72 -0
  62. package/dist/{chunk-PGD5RPKF.mjs → chunk-SVIJ2UZA.mjs} +2 -1
  63. package/dist/chunk-SXWEFZOF.mjs +766 -0
  64. package/dist/{chunk-NCHUEGUW.mjs → chunk-TEVEXDY2.mjs} +27 -16
  65. package/dist/{chunk-DJOFM2VO.mjs → chunk-TO6MZFNQ.mjs} +52 -40
  66. package/dist/chunk-TQ6RIFWR.mjs +135 -0
  67. package/dist/chunk-U4M3DC2P.mjs +48 -0
  68. package/dist/{chunk-ZCGIZOMT.mjs → chunk-V7HJ5HLC.mjs} +6 -10
  69. package/dist/{chunk-SIDDYP3O.mjs → chunk-VJWSKJLJ.mjs} +1 -1
  70. package/dist/{chunk-ZG56T75O.mjs → chunk-WTHWLCRL.mjs} +51 -11
  71. package/dist/{chunk-KP6DEYWB.mjs → chunk-X4L3W7RQ.mjs} +1 -1
  72. package/dist/{chunk-ZMRTETN2.mjs → chunk-X6C6MNG5.mjs} +4 -3
  73. package/dist/chunk-XT43B7ZI.mjs +58 -0
  74. package/dist/{chunk-OGG2TQDG.mjs → chunk-XVHTJQ4T.mjs} +12 -1
  75. package/dist/{chunk-QJE3MZBO.mjs → chunk-Y6UH3EKE.mjs} +1 -1
  76. package/dist/chunk-YJ5T4XZN.mjs +81 -0
  77. package/dist/{chunk-VVNFXJMN.mjs → chunk-YNDPQVEK.mjs} +8 -1
  78. package/dist/{chunk-QGZ32NFQ.mjs → chunk-Z23KOLNU.mjs} +4 -3
  79. package/dist/{chunk-5CKMYMTS.mjs → chunk-ZATZRXMW.mjs} +27 -8
  80. package/dist/components/ActivityFeed.mjs +3 -3
  81. package/dist/components/Affix.mjs +1 -1
  82. package/dist/components/Anchor.mjs +1 -1
  83. package/dist/components/AreaChart.mjs +5 -4
  84. package/dist/components/AspectRatio.d.mts +10 -0
  85. package/dist/components/AspectRatio.mjs +8 -0
  86. package/dist/components/AutoComplete.mjs +1 -1
  87. package/dist/components/BarChart.mjs +5 -4
  88. package/dist/components/Calendar.mjs +1 -1
  89. package/dist/components/Cascader.mjs +1 -1
  90. package/dist/components/ChartCanvas.mjs +1 -1
  91. package/dist/components/ChatWindow.mjs +4 -4
  92. package/dist/components/Code.mjs +3 -1
  93. package/dist/components/CollapsePanel.mjs +1 -1
  94. package/dist/components/ColorPicker.mjs +1 -1
  95. package/dist/components/CommentThread.mjs +2 -2
  96. package/dist/components/ContextMenu.d.mts +36 -0
  97. package/dist/components/ContextMenu.mjs +17 -0
  98. package/dist/components/DataExport.mjs +2 -2
  99. package/dist/components/DataTableWithToolbar.mjs +7 -7
  100. package/dist/components/DonutChart.mjs +4 -4
  101. package/dist/components/Dropdown.mjs +1 -1
  102. package/dist/components/FileManager.mjs +2 -1
  103. package/dist/components/FloatButton.mjs +1 -1
  104. package/dist/components/FormItem.mjs +1 -1
  105. package/dist/components/FormWizard.mjs +1 -1
  106. package/dist/components/FunnelChart.mjs +3 -3
  107. package/dist/components/Gantt.d.mts +3 -1
  108. package/dist/components/Gantt.mjs +2 -2
  109. package/dist/components/GaugeChart.mjs +2 -2
  110. package/dist/components/HeatmapChart.mjs +3 -3
  111. package/dist/components/Highlight.d.mts +11 -0
  112. package/dist/components/Highlight.mjs +8 -0
  113. package/dist/components/Image.mjs +4 -4
  114. package/dist/components/ImageCompare.d.mts +15 -0
  115. package/dist/components/ImageCompare.mjs +8 -0
  116. package/dist/components/ImageGroup.mjs +2 -2
  117. package/dist/components/ImagePreview.mjs +1 -1
  118. package/dist/components/Input.mjs +1 -1
  119. package/dist/components/InputNumber.d.mts +1 -1
  120. package/dist/components/InputNumber.mjs +1 -1
  121. package/dist/components/InputOTP.d.mts +33 -0
  122. package/dist/components/InputOTP.mjs +12 -0
  123. package/dist/components/Kanban.mjs +2 -2
  124. package/dist/components/Kbd.d.mts +10 -0
  125. package/dist/components/Kbd.mjs +8 -0
  126. package/dist/components/LineChart.mjs +5 -4
  127. package/dist/components/List.mjs +2 -2
  128. package/dist/components/Loading.mjs +1 -1
  129. package/dist/components/LoadingBar.d.mts +8 -0
  130. package/dist/components/LoadingBar.mjs +138 -0
  131. package/dist/components/LoadingBarContainer.d.mts +8 -0
  132. package/dist/components/LoadingBarContainer.mjs +8 -0
  133. package/dist/components/LoadingBarRoot.d.mts +6 -0
  134. package/dist/components/LoadingBarRoot.mjs +6 -0
  135. package/dist/components/Marquee.d.mts +10 -0
  136. package/dist/components/Marquee.mjs +8 -0
  137. package/dist/components/MaskInput.d.mts +37 -0
  138. package/dist/components/MaskInput.mjs +10 -0
  139. package/dist/components/Masonry.d.mts +10 -0
  140. package/dist/components/Masonry.mjs +8 -0
  141. package/dist/components/NavigationMenu.d.mts +84 -0
  142. package/dist/components/NavigationMenu.mjs +23 -0
  143. package/dist/components/NotificationCenter.mjs +4 -4
  144. package/dist/components/OrgChart.mjs +2 -2
  145. package/dist/components/PageHeader.d.mts +34 -0
  146. package/dist/components/PageHeader.mjs +11 -0
  147. package/dist/components/Pagination.mjs +1 -1
  148. package/dist/components/PieChart.mjs +3 -3
  149. package/dist/components/Popconfirm.mjs +2 -2
  150. package/dist/components/Popover.mjs +2 -2
  151. package/dist/components/QRCode.d.mts +2 -2
  152. package/dist/components/QRCode.mjs +1 -1
  153. package/dist/components/RadarChart.mjs +4 -4
  154. package/dist/components/Rate.mjs +1 -1
  155. package/dist/components/ScatterChart.mjs +5 -4
  156. package/dist/components/ScrollArea.d.mts +10 -0
  157. package/dist/components/ScrollArea.mjs +8 -0
  158. package/dist/components/ScrollSpy.mjs +1 -1
  159. package/dist/components/Signature.mjs +1 -1
  160. package/dist/components/SplitButton.d.mts +38 -0
  161. package/dist/components/SplitButton.mjs +12 -0
  162. package/dist/components/Splitter.mjs +1 -1
  163. package/dist/components/Stepper.d.mts +2 -0
  164. package/dist/components/Stepper.mjs +2 -1
  165. package/dist/components/SunburstChart.mjs +3 -3
  166. package/dist/components/Switch.d.mts +5 -1
  167. package/dist/components/Switch.mjs +2 -1
  168. package/dist/components/Table.mjs +2 -2
  169. package/dist/components/TagsInput.d.mts +41 -0
  170. package/dist/components/TagsInput.mjs +13 -0
  171. package/dist/components/TaskBoard.mjs +1 -1
  172. package/dist/components/Textarea.mjs +1 -1
  173. package/dist/components/Tooltip.mjs +2 -2
  174. package/dist/components/Tour.mjs +1 -1
  175. package/dist/components/TreeMapChart.mjs +3 -3
  176. package/dist/components/TreeSelect.mjs +2 -1
  177. package/dist/components/Upload.mjs +1 -1
  178. package/dist/hooks/useChartInteraction.d.mts +5 -0
  179. package/dist/hooks/useChartInteraction.mjs +1 -1
  180. package/dist/index.d.mts +17 -1
  181. package/dist/index.mjs +406 -0
  182. package/package.json +117 -2
  183. package/dist/chunk-7AFRHC7P.mjs +0 -312
  184. package/dist/chunk-CB4SK5AU.mjs +0 -235
  185. package/dist/chunk-KPACLRQH.mjs +0 -55
  186. package/dist/chunk-YIB6ZXZF.mjs +0 -386
@@ -0,0 +1,766 @@
1
+ import {
2
+ renderOverlayPortal,
3
+ useAnchoredOverlay
4
+ } from "./chunk-HKAZXG74.mjs";
5
+ import {
6
+ useTigerConfig
7
+ } from "./chunk-YSIXURBW.mjs";
8
+
9
+ // src/components/Cascader.tsx
10
+ import { useCallback, useEffect, useId, useMemo, useRef, useState } from "react";
11
+ import {
12
+ classNames,
13
+ cascaderBaseClasses,
14
+ cascaderDropdownClasses,
15
+ cascaderColumnClasses,
16
+ cascaderSearchInputClasses,
17
+ cascaderEmptyStateClasses,
18
+ cascaderSearchResultClasses,
19
+ getCascaderTriggerClasses,
20
+ getCascaderOptionClasses,
21
+ getCascaderColumns,
22
+ getCascaderDisplayLabel,
23
+ flattenCascaderOptions,
24
+ filterCascaderOptions,
25
+ isCascaderOptionExpandable,
26
+ getCascaderVirtualItemHeight,
27
+ getCascaderVirtualRange,
28
+ getCascaderVirtualAlignScrollTop,
29
+ CASCADER_DEFAULT_LIST_HEIGHT,
30
+ getPickerComboboxAria,
31
+ getPickerListboxAria,
32
+ getPickerOptionAria,
33
+ getPickerTriggerKeyAction,
34
+ getPickerNavigationIndex,
35
+ getPickerOptionId,
36
+ findFirstEnabledIndex,
37
+ icon20ViewBox,
38
+ chevronDownSolidIcon20PathD,
39
+ closeSolidIcon20PathD,
40
+ resolveLocaleText,
41
+ mergeTigerLocale
42
+ } from "@expcat/tigercat-core";
43
+ import { jsx, jsxs } from "react/jsx-runtime";
44
+ var CASCADER_KEYS = /* @__PURE__ */ new Set([
45
+ "options",
46
+ "value",
47
+ "onChange",
48
+ "placeholder",
49
+ "size",
50
+ "disabled",
51
+ "clearable",
52
+ "searchable",
53
+ "searchValue",
54
+ "defaultSearchValue",
55
+ "onSearchChange",
56
+ "expandTrigger",
57
+ "changeOnSelect",
58
+ "separator",
59
+ "emptyText",
60
+ "virtual",
61
+ "listHeight",
62
+ "className",
63
+ "locale"
64
+ ]);
65
+ var EMPTY_CASCADER_VALUE = [];
66
+ function selectedIndexInColumn(options, selectedValue) {
67
+ if (selectedValue === void 0) return -1;
68
+ return options.findIndex((option) => option.value === selectedValue);
69
+ }
70
+ var VirtualCascaderList = ({
71
+ items,
72
+ size,
73
+ listHeight,
74
+ activeIndex,
75
+ onActiveIndexChange,
76
+ className,
77
+ listboxProps,
78
+ renderItem
79
+ }) => {
80
+ const itemHeight = getCascaderVirtualItemHeight(size);
81
+ const itemCount = items.length;
82
+ const clampedActiveIndex = activeIndex >= 0 && activeIndex < itemCount ? activeIndex : -1;
83
+ const containerRef = useRef(null);
84
+ const [scrollTop, setScrollTop] = useState(
85
+ () => getCascaderVirtualAlignScrollTop(0, clampedActiveIndex, itemHeight, listHeight)
86
+ );
87
+ const maxScrollTop = Math.max(0, itemCount * itemHeight - listHeight);
88
+ const effectiveScrollTop = Math.min(Math.max(0, scrollTop), maxScrollTop);
89
+ const { startIndex, endIndex, totalHeight } = getCascaderVirtualRange(
90
+ effectiveScrollTop,
91
+ listHeight,
92
+ itemCount,
93
+ itemHeight
94
+ );
95
+ const fromIndex = itemCount <= 0 ? 0 : Math.min(startIndex, endIndex, itemCount - 1);
96
+ const toIndex = itemCount <= 0 ? -1 : Math.min(endIndex, itemCount - 1);
97
+ useEffect(() => {
98
+ const el = containerRef.current;
99
+ if (!el) return;
100
+ if (el.scrollTop !== effectiveScrollTop) el.scrollTop = effectiveScrollTop;
101
+ }, [effectiveScrollTop]);
102
+ useEffect(() => {
103
+ const el = containerRef.current;
104
+ if (!el || clampedActiveIndex < 0) return;
105
+ const next = getCascaderVirtualAlignScrollTop(
106
+ el.scrollTop,
107
+ clampedActiveIndex,
108
+ itemHeight,
109
+ listHeight
110
+ );
111
+ if (next !== el.scrollTop) {
112
+ el.scrollTop = next;
113
+ setScrollTop(next);
114
+ }
115
+ }, [clampedActiveIndex, itemHeight, listHeight]);
116
+ const visible = [];
117
+ if (fromIndex <= toIndex) {
118
+ for (let i = fromIndex; i <= toIndex; i++) {
119
+ visible.push(renderItem(items[i], i));
120
+ }
121
+ }
122
+ return /* @__PURE__ */ jsx(
123
+ "div",
124
+ {
125
+ ref: containerRef,
126
+ "data-tiger-cascader-virtual": "",
127
+ tabIndex: 0,
128
+ className,
129
+ style: { maxHeight: `${listHeight}px`, overflowY: "auto" },
130
+ onScroll: (e) => setScrollTop(e.currentTarget.scrollTop),
131
+ onKeyDown: (e) => {
132
+ if (e.key !== "ArrowDown" && e.key !== "ArrowUp" && e.key !== "Home" && e.key !== "End") {
133
+ return;
134
+ }
135
+ e.preventDefault();
136
+ e.stopPropagation();
137
+ onActiveIndexChange(getPickerNavigationIndex(items, clampedActiveIndex, e.key));
138
+ },
139
+ ...listboxProps,
140
+ children: /* @__PURE__ */ jsx("div", { style: { height: `${totalHeight}px`, position: "relative" }, children: /* @__PURE__ */ jsx("div", { style: { transform: `translateY(${fromIndex * itemHeight}px)` }, children: visible }) })
141
+ }
142
+ );
143
+ };
144
+ var Cascader = (props) => {
145
+ const {
146
+ options = [],
147
+ value = EMPTY_CASCADER_VALUE,
148
+ onChange,
149
+ placeholder = "Please select",
150
+ size = "md",
151
+ disabled = false,
152
+ clearable = true,
153
+ searchable = false,
154
+ searchValue,
155
+ defaultSearchValue = "",
156
+ onSearchChange,
157
+ expandTrigger = "click",
158
+ changeOnSelect = false,
159
+ separator = " / ",
160
+ emptyText,
161
+ virtual = false,
162
+ listHeight = CASCADER_DEFAULT_LIST_HEIGHT,
163
+ className,
164
+ locale
165
+ } = props;
166
+ const config = useTigerConfig();
167
+ const mergedLocale = useMemo(
168
+ () => mergeTigerLocale(config.locale, locale),
169
+ [config.locale, locale]
170
+ );
171
+ const divProps = {};
172
+ for (const [k, v] of Object.entries(props)) {
173
+ if (!CASCADER_KEYS.has(k)) divProps[k] = v;
174
+ }
175
+ const instanceId = useId();
176
+ const listboxId = `tiger-cascader-listbox-${instanceId}`;
177
+ const [isOpen, setIsOpen] = useState(false);
178
+ const [uncontrolledSearchValue, setUncontrolledSearchValue] = useState(defaultSearchValue);
179
+ const searchQuery = searchValue ?? uncontrolledSearchValue;
180
+ const [activePath, setActivePath] = useState([]);
181
+ const [columnActiveIndices, setColumnActiveIndices] = useState([]);
182
+ const [searchActiveIndex, setSearchActiveIndex] = useState(-1);
183
+ const [focusedColumnIndex, setFocusedColumnIndex] = useState(0);
184
+ const [indexedSearchQuery, setIndexedSearchQuery] = useState(searchQuery);
185
+ if (indexedSearchQuery !== searchQuery) {
186
+ setIndexedSearchQuery(searchQuery);
187
+ setSearchActiveIndex(-1);
188
+ }
189
+ const wasOpenRef = useRef(false);
190
+ const triggerRef = useRef(null);
191
+ const dropdownRef = useRef(null);
192
+ const overlay = useAnchoredOverlay({
193
+ enabled: isOpen,
194
+ referenceRef: triggerRef,
195
+ floatingRef: dropdownRef,
196
+ placement: "bottom-start",
197
+ offset: 4,
198
+ layout: "fullscreen-sm",
199
+ matchReferenceWidth: true,
200
+ dismissOnOutside: true,
201
+ dismissOnEscape: true,
202
+ restoreFocusOnDismiss: true,
203
+ onDismiss: () => setIsOpen(false)
204
+ });
205
+ const displayLabel = useMemo(
206
+ () => getCascaderDisplayLabel(options, value, separator),
207
+ [options, value, separator]
208
+ );
209
+ const columns = useMemo(() => getCascaderColumns(options, activePath), [options, activePath]);
210
+ const isSearchMode = searchable && searchQuery.length > 0;
211
+ const flattenedOptions = useMemo(() => {
212
+ if (!searchable) return [];
213
+ return flattenCascaderOptions(options, [], [], changeOnSelect);
214
+ }, [searchable, options, changeOnSelect]);
215
+ const searchResults = useMemo(() => {
216
+ if (!isSearchMode) return [];
217
+ return filterCascaderOptions(flattenedOptions, searchQuery, searchable);
218
+ }, [isSearchMode, flattenedOptions, searchQuery, searchable]);
219
+ const triggerClasses = useMemo(
220
+ () => classNames(getCascaderTriggerClasses(size, disabled, isOpen), className),
221
+ [size, disabled, isOpen, className]
222
+ );
223
+ const updateSearchValue = useCallback(
224
+ (nextValue) => {
225
+ if (searchValue === void 0) {
226
+ setUncontrolledSearchValue(nextValue);
227
+ }
228
+ onSearchChange?.(nextValue);
229
+ },
230
+ [onSearchChange, searchValue]
231
+ );
232
+ useEffect(() => {
233
+ if (!isOpen) {
234
+ wasOpenRef.current = false;
235
+ setColumnActiveIndices([]);
236
+ setSearchActiveIndex(-1);
237
+ setFocusedColumnIndex(0);
238
+ return;
239
+ }
240
+ if (wasOpenRef.current) return;
241
+ wasOpenRef.current = true;
242
+ setActivePath(value ? [...value] : []);
243
+ updateSearchValue("");
244
+ }, [isOpen, updateSearchValue, value]);
245
+ useEffect(() => {
246
+ if (!isOpen) return;
247
+ setColumnActiveIndices(
248
+ (prev) => columns.map((col, i) => {
249
+ if (prev[i] !== void 0 && prev[i] >= 0 && prev[i] < col.options.length && !col.options[prev[i]]?.disabled) {
250
+ return prev[i];
251
+ }
252
+ return selectedIndexInColumn(col.options, col.selectedValue);
253
+ })
254
+ );
255
+ setFocusedColumnIndex((prev) => {
256
+ const last = columns.length - 1;
257
+ if (last < 0) return 0;
258
+ return prev > last ? last : prev;
259
+ });
260
+ }, [isOpen, columns]);
261
+ const toggleOpen = useCallback(() => {
262
+ if (disabled) return;
263
+ setIsOpen((prev) => !prev);
264
+ }, [disabled]);
265
+ const handleOptionClick = useCallback(
266
+ (option, level) => {
267
+ if (option.disabled) return;
268
+ const optionIndex = columns[level]?.options.findIndex((item) => item.value === option.value);
269
+ const newPath = activePath.slice(0, level);
270
+ newPath.push(option.value);
271
+ setActivePath(newPath);
272
+ if (optionIndex !== void 0 && optionIndex >= 0) {
273
+ setColumnActiveIndices((prev) => {
274
+ const next = prev.slice();
275
+ next[level] = optionIndex;
276
+ return next;
277
+ });
278
+ setFocusedColumnIndex(level);
279
+ }
280
+ const hasChildren = isCascaderOptionExpandable(option);
281
+ if (!hasChildren) {
282
+ onChange?.(newPath);
283
+ setIsOpen(false);
284
+ } else if (changeOnSelect) {
285
+ onChange?.(newPath);
286
+ }
287
+ },
288
+ [activePath, onChange, changeOnSelect, columns]
289
+ );
290
+ const handleOptionHover = useCallback(
291
+ (option, level) => {
292
+ const optionIndex = columns[level]?.options.findIndex((item) => item.value === option.value);
293
+ if (optionIndex !== void 0 && optionIndex >= 0 && !option.disabled) {
294
+ setColumnActiveIndices((prev) => {
295
+ const next = prev.slice();
296
+ next[level] = optionIndex;
297
+ return next;
298
+ });
299
+ setFocusedColumnIndex(level);
300
+ }
301
+ if (expandTrigger !== "hover" || option.disabled) return;
302
+ const newPath = activePath.slice(0, level);
303
+ newPath.push(option.value);
304
+ setActivePath(newPath);
305
+ },
306
+ [expandTrigger, activePath, columns]
307
+ );
308
+ const handleSearchResultClick = useCallback(
309
+ (valuePath, itemDisabled) => {
310
+ if (itemDisabled) return;
311
+ onChange?.(valuePath);
312
+ setIsOpen(false);
313
+ },
314
+ [onChange]
315
+ );
316
+ const handleClear = useCallback(
317
+ (e) => {
318
+ e.stopPropagation();
319
+ onChange?.([]);
320
+ },
321
+ [onChange]
322
+ );
323
+ const isOptionDisabled = useCallback((option) => !!option.disabled, []);
324
+ const getColumnOptionDomId = useCallback(
325
+ (colIndex, optionIndex) => `${listboxId}-col${colIndex}-opt${optionIndex}`,
326
+ [listboxId]
327
+ );
328
+ const getCurrentColumnIndex = useCallback(() => {
329
+ const last = columns.length - 1;
330
+ if (last < 0) return 0;
331
+ if (focusedColumnIndex >= 0 && focusedColumnIndex <= last) return focusedColumnIndex;
332
+ return last;
333
+ }, [columns.length, focusedColumnIndex]);
334
+ const commitActiveOption = useCallback(() => {
335
+ if (isSearchMode) {
336
+ const item = searchResults[searchActiveIndex];
337
+ if (!item || item.disabled) return;
338
+ handleSearchResultClick(item.valuePath, item.disabled);
339
+ return;
340
+ }
341
+ const colIndex = getCurrentColumnIndex();
342
+ const col = columns[colIndex];
343
+ const idx = columnActiveIndices[colIndex] ?? -1;
344
+ const option = col?.options[idx];
345
+ if (!option || option.disabled) return;
346
+ handleOptionClick(option, colIndex);
347
+ if (isCascaderOptionExpandable(option)) {
348
+ const nextCol = colIndex + 1;
349
+ const childOptions = option.children ?? [];
350
+ const nextIdx = findFirstEnabledIndex(childOptions, isOptionDisabled);
351
+ setColumnActiveIndices((prev) => {
352
+ const next = prev.slice();
353
+ next[colIndex] = idx;
354
+ next[nextCol] = nextIdx;
355
+ return next;
356
+ });
357
+ setFocusedColumnIndex(nextCol);
358
+ }
359
+ }, [
360
+ isSearchMode,
361
+ searchResults,
362
+ searchActiveIndex,
363
+ handleSearchResultClick,
364
+ getCurrentColumnIndex,
365
+ columns,
366
+ columnActiveIndices,
367
+ handleOptionClick,
368
+ isOptionDisabled
369
+ ]);
370
+ const handleOpenListKeyDown = useCallback(
371
+ (e, fromSearchInput = false) => {
372
+ const key = e.key;
373
+ if (fromSearchInput && key === " ") return false;
374
+ if (isSearchMode) {
375
+ if (key === "ArrowDown" || key === "ArrowUp" || key === "Home" || key === "End") {
376
+ e.preventDefault();
377
+ e.stopPropagation();
378
+ setSearchActiveIndex(
379
+ (current) => getPickerNavigationIndex(searchResults, current, key, (item) => !!item.disabled)
380
+ );
381
+ return true;
382
+ }
383
+ if (key === "Enter" || key === " ") {
384
+ e.preventDefault();
385
+ e.stopPropagation();
386
+ commitActiveOption();
387
+ return true;
388
+ }
389
+ return false;
390
+ }
391
+ const colIndex = getCurrentColumnIndex();
392
+ const col = columns[colIndex];
393
+ if (!col) return false;
394
+ if (key === "ArrowDown" || key === "ArrowUp" || key === "Home" || key === "End") {
395
+ e.preventDefault();
396
+ e.stopPropagation();
397
+ const current = columnActiveIndices[colIndex] ?? -1;
398
+ const next = getPickerNavigationIndex(col.options, current, key, isOptionDisabled);
399
+ setColumnActiveIndices((prev) => {
400
+ const nextIndices = prev.slice();
401
+ nextIndices[colIndex] = next;
402
+ return nextIndices;
403
+ });
404
+ setFocusedColumnIndex(colIndex);
405
+ return true;
406
+ }
407
+ if (key === "ArrowRight") {
408
+ e.preventDefault();
409
+ e.stopPropagation();
410
+ const idx = columnActiveIndices[colIndex] ?? -1;
411
+ const option = col.options[idx];
412
+ if (option && !option.disabled && isCascaderOptionExpandable(option)) {
413
+ handleOptionClick(option, colIndex);
414
+ const childOptions = option.children ?? [];
415
+ const nextIdx = findFirstEnabledIndex(childOptions, isOptionDisabled);
416
+ setColumnActiveIndices((prev) => {
417
+ const next = prev.slice();
418
+ next[colIndex] = idx;
419
+ next[colIndex + 1] = nextIdx;
420
+ return next;
421
+ });
422
+ setFocusedColumnIndex(colIndex + 1);
423
+ }
424
+ return true;
425
+ }
426
+ if (key === "ArrowLeft") {
427
+ e.preventDefault();
428
+ e.stopPropagation();
429
+ if (activePath.length > 0) {
430
+ setActivePath(activePath.slice(0, -1));
431
+ setFocusedColumnIndex(Math.max(0, colIndex - 1));
432
+ }
433
+ return true;
434
+ }
435
+ if (key === "Enter" || key === " ") {
436
+ e.preventDefault();
437
+ e.stopPropagation();
438
+ commitActiveOption();
439
+ return true;
440
+ }
441
+ return false;
442
+ },
443
+ [
444
+ isSearchMode,
445
+ searchResults,
446
+ commitActiveOption,
447
+ getCurrentColumnIndex,
448
+ columns,
449
+ columnActiveIndices,
450
+ isOptionDisabled,
451
+ handleOptionClick,
452
+ activePath
453
+ ]
454
+ );
455
+ const handleTriggerKeyDown = useCallback(
456
+ (e) => {
457
+ if (disabled) return;
458
+ if (isOpen && handleOpenListKeyDown(e)) return;
459
+ const action = getPickerTriggerKeyAction(e.key, isOpen);
460
+ if (action === "none") return;
461
+ e.preventDefault();
462
+ if (action === "toggle") {
463
+ toggleOpen();
464
+ } else if (action === "open") {
465
+ setIsOpen(true);
466
+ } else if (action === "close") {
467
+ setIsOpen(false);
468
+ }
469
+ },
470
+ [disabled, isOpen, handleOpenListKeyDown, toggleOpen]
471
+ );
472
+ const handleDropdownKeyDown = useCallback(
473
+ (e) => {
474
+ if (e.target instanceof HTMLInputElement) return;
475
+ if (!isOpen) return;
476
+ handleOpenListKeyDown(e);
477
+ },
478
+ [isOpen, handleOpenListKeyDown]
479
+ );
480
+ const handleSearchKeyDown = useCallback(
481
+ (e) => {
482
+ switch (e.key) {
483
+ case " ":
484
+ e.stopPropagation();
485
+ return;
486
+ case "ArrowDown":
487
+ case "ArrowUp":
488
+ case "Enter":
489
+ handleOpenListKeyDown(e, true);
490
+ return;
491
+ case "Escape":
492
+ e.preventDefault();
493
+ e.stopPropagation();
494
+ setIsOpen(false);
495
+ triggerRef.current?.focus();
496
+ return;
497
+ default:
498
+ return;
499
+ }
500
+ },
501
+ [handleOpenListKeyDown]
502
+ );
503
+ const hasValue = value && value.length > 0;
504
+ const showClearBtn = clearable && hasValue && !disabled;
505
+ const currentCol = getCurrentColumnIndex();
506
+ const currentOpt = columnActiveIndices[currentCol] ?? -1;
507
+ const activeOptionId = !isOpen ? void 0 : isSearchMode ? searchActiveIndex >= 0 ? getPickerOptionId(listboxId, searchActiveIndex) : void 0 : currentOpt >= 0 ? getColumnOptionDomId(currentCol, currentOpt) : void 0;
508
+ return /* @__PURE__ */ jsxs("div", { className: cascaderBaseClasses, "data-testid": "cascader", ...divProps, children: [
509
+ /* @__PURE__ */ jsx(
510
+ "button",
511
+ {
512
+ ref: triggerRef,
513
+ type: "button",
514
+ className: classNames(triggerClasses, showClearBtn ? "pr-14" : "pr-9"),
515
+ disabled,
516
+ ...getPickerComboboxAria({ expanded: isOpen, listboxId, activeOptionId }),
517
+ onClick: toggleOpen,
518
+ onKeyDown: handleTriggerKeyDown,
519
+ children: /* @__PURE__ */ jsx(
520
+ "span",
521
+ {
522
+ className: classNames(
523
+ "flex-1 text-left truncate",
524
+ !hasValue && "text-[var(--tiger-cascader-placeholder,var(--tiger-text-muted,#9ca3af))]"
525
+ ),
526
+ children: hasValue ? displayLabel : placeholder
527
+ }
528
+ )
529
+ }
530
+ ),
531
+ /* @__PURE__ */ jsxs("span", { className: "pointer-events-none absolute inset-y-0 right-3 flex items-center gap-1", children: [
532
+ showClearBtn && /* @__PURE__ */ jsx(
533
+ "button",
534
+ {
535
+ type: "button",
536
+ className: "pointer-events-auto inline-flex rounded-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--tiger-cascader-ring,var(--tiger-primary,#2563eb))]",
537
+ "data-tiger-cascader-clear": "",
538
+ "aria-label": resolveLocaleText("Clear selection", mergedLocale?.common?.clearText),
539
+ onClick: handleClear,
540
+ children: /* @__PURE__ */ jsx(
541
+ "svg",
542
+ {
543
+ className: "w-4 h-4 text-[var(--tiger-cascader-icon,var(--tiger-text-muted,#9ca3af))] hover:text-[var(--tiger-cascader-icon-hover,var(--tiger-text-muted,#6b7280))]",
544
+ xmlns: "http://www.w3.org/2000/svg",
545
+ viewBox: icon20ViewBox,
546
+ fill: "currentColor",
547
+ children: /* @__PURE__ */ jsx("path", { fillRule: "evenodd", d: closeSolidIcon20PathD, clipRule: "evenodd" })
548
+ }
549
+ )
550
+ }
551
+ ),
552
+ /* @__PURE__ */ jsx("span", { className: classNames("inline-flex", isOpen && "rotate-180"), "aria-hidden": "true", children: /* @__PURE__ */ jsx(
553
+ "svg",
554
+ {
555
+ className: "w-5 h-5 text-[var(--tiger-cascader-icon,var(--tiger-text-muted,#9ca3af))] transition-transform",
556
+ xmlns: "http://www.w3.org/2000/svg",
557
+ viewBox: icon20ViewBox,
558
+ fill: "currentColor",
559
+ children: /* @__PURE__ */ jsx("path", { fillRule: "evenodd", d: chevronDownSolidIcon20PathD, clipRule: "evenodd" })
560
+ }
561
+ ) })
562
+ ] }),
563
+ renderOverlayPortal(
564
+ isOpen ? /* @__PURE__ */ jsxs(
565
+ "div",
566
+ {
567
+ ref: dropdownRef,
568
+ className: classNames(
569
+ cascaderDropdownClasses,
570
+ isSearchMode && "flex-col",
571
+ overlay.floatingClasses
572
+ ),
573
+ style: overlay.floatingStyles,
574
+ "data-positioned": overlay.positioned,
575
+ onKeyDown: handleDropdownKeyDown,
576
+ children: [
577
+ searchable && /* @__PURE__ */ jsx(
578
+ "input",
579
+ {
580
+ type: "text",
581
+ className: cascaderSearchInputClasses,
582
+ placeholder: resolveLocaleText(
583
+ "Search...",
584
+ mergedLocale?.common?.searchPlaceholder
585
+ ),
586
+ value: searchQuery,
587
+ onChange: (e) => updateSearchValue(e.target.value),
588
+ onKeyDown: handleSearchKeyDown,
589
+ "aria-label": resolveLocaleText(
590
+ "Search options",
591
+ mergedLocale?.common?.searchPlaceholder
592
+ )
593
+ }
594
+ ),
595
+ isSearchMode ? (
596
+ // Search results
597
+ searchResults.length === 0 ? /* @__PURE__ */ jsx("div", { className: cascaderEmptyStateClasses, children: resolveLocaleText(
598
+ "No results found",
599
+ emptyText,
600
+ mergedLocale?.common?.emptyText
601
+ ) }) : virtual ? /* @__PURE__ */ jsx(
602
+ VirtualCascaderList,
603
+ {
604
+ items: searchResults,
605
+ size,
606
+ listHeight,
607
+ activeIndex: searchActiveIndex,
608
+ onActiveIndexChange: setSearchActiveIndex,
609
+ listboxProps: getPickerListboxAria({ id: listboxId }),
610
+ renderItem: (item, index) => /* @__PURE__ */ jsx(
611
+ "div",
612
+ {
613
+ id: getPickerOptionId(listboxId, index),
614
+ "data-option-index": index,
615
+ "data-active": searchActiveIndex === index || void 0,
616
+ className: classNames(
617
+ cascaderSearchResultClasses,
618
+ searchActiveIndex === index && "bg-[var(--tiger-cascader-option-bg-selected,var(--tiger-outline-bg-hover,#eff6ff))]",
619
+ item.disabled && "opacity-50 cursor-not-allowed"
620
+ ),
621
+ ...getPickerOptionAria({
622
+ selected: value?.join(",") === item.valuePath.join(","),
623
+ disabled: item.disabled
624
+ }),
625
+ onMouseEnter: () => {
626
+ if (!item.disabled) setSearchActiveIndex(index);
627
+ },
628
+ onClick: () => handleSearchResultClick(item.valuePath, item.disabled),
629
+ children: typeof searchable === "object" && searchable.render ? searchable.render(searchQuery, item.path) : item.label
630
+ },
631
+ item.valuePath.join(",")
632
+ )
633
+ },
634
+ searchQuery
635
+ ) : /* @__PURE__ */ jsx(
636
+ "div",
637
+ {
638
+ className: "max-h-64 overflow-auto",
639
+ ...getPickerListboxAria({ id: listboxId }),
640
+ children: searchResults.map((item, index) => /* @__PURE__ */ jsx(
641
+ "div",
642
+ {
643
+ id: getPickerOptionId(listboxId, index),
644
+ "data-option-index": index,
645
+ "data-active": searchActiveIndex === index || void 0,
646
+ className: classNames(
647
+ cascaderSearchResultClasses,
648
+ searchActiveIndex === index && "bg-[var(--tiger-cascader-option-bg-selected,var(--tiger-outline-bg-hover,#eff6ff))]",
649
+ item.disabled && "opacity-50 cursor-not-allowed"
650
+ ),
651
+ ...getPickerOptionAria({
652
+ selected: value?.join(",") === item.valuePath.join(","),
653
+ disabled: item.disabled
654
+ }),
655
+ onMouseEnter: () => {
656
+ if (!item.disabled) setSearchActiveIndex(index);
657
+ },
658
+ onClick: () => handleSearchResultClick(item.valuePath, item.disabled),
659
+ children: typeof searchable === "object" && searchable.render ? searchable.render(searchQuery, item.path) : item.label
660
+ },
661
+ item.valuePath.join(",")
662
+ ))
663
+ }
664
+ )
665
+ ) : (
666
+ // Cascading columns
667
+ /* @__PURE__ */ jsx("div", { className: "flex", children: columns.map((col, colIndex) => {
668
+ const listboxProps = getPickerListboxAria({
669
+ id: colIndex === 0 ? listboxId : void 0,
670
+ label: `Level ${colIndex + 1}`
671
+ });
672
+ const renderColumnOption = (option, optionIndex) => {
673
+ const isSelected = col.selectedValue === option.value;
674
+ const isActive = (columnActiveIndices[colIndex] ?? -1) === optionIndex;
675
+ const hasChildren = isCascaderOptionExpandable(option);
676
+ return /* @__PURE__ */ jsxs(
677
+ "div",
678
+ {
679
+ id: getColumnOptionDomId(colIndex, optionIndex),
680
+ "data-option-index": optionIndex,
681
+ "data-active": isActive || void 0,
682
+ className: getCascaderOptionClasses(
683
+ isSelected || isActive,
684
+ !!option.disabled,
685
+ size
686
+ ),
687
+ ...getPickerOptionAria({
688
+ selected: isSelected,
689
+ disabled: !!option.disabled
690
+ }),
691
+ onClick: () => handleOptionClick(option, colIndex),
692
+ onMouseEnter: () => handleOptionHover(option, colIndex),
693
+ children: [
694
+ /* @__PURE__ */ jsx("span", { className: "flex-1 truncate", children: option.label }),
695
+ hasChildren && /* @__PURE__ */ jsx(
696
+ "svg",
697
+ {
698
+ className: "w-4 h-4 text-[var(--tiger-text-muted,#9ca3af)]",
699
+ xmlns: "http://www.w3.org/2000/svg",
700
+ viewBox: "0 0 20 20",
701
+ fill: "currentColor",
702
+ children: /* @__PURE__ */ jsx(
703
+ "path",
704
+ {
705
+ fillRule: "evenodd",
706
+ d: "M7.21 14.77a.75.75 0 01.02-1.06L11.168 10 7.23 6.29a.75.75 0 111.04-1.08l4.5 4.25a.75.75 0 010 1.08l-4.5 4.25a.75.75 0 01-1.06-.02z",
707
+ clipRule: "evenodd"
708
+ }
709
+ )
710
+ }
711
+ )
712
+ ]
713
+ },
714
+ option.value
715
+ );
716
+ };
717
+ if (virtual) {
718
+ return /* @__PURE__ */ jsx(
719
+ VirtualCascaderList,
720
+ {
721
+ items: col.options,
722
+ size,
723
+ listHeight,
724
+ activeIndex: columnActiveIndices[colIndex] ?? -1,
725
+ onActiveIndexChange: (index) => {
726
+ setColumnActiveIndices((prev) => {
727
+ const next = prev.slice();
728
+ next[colIndex] = index;
729
+ return next;
730
+ });
731
+ },
732
+ className: cascaderColumnClasses,
733
+ listboxProps: {
734
+ ...listboxProps,
735
+ "aria-label": `Level ${colIndex + 1}`
736
+ },
737
+ renderItem: renderColumnOption
738
+ },
739
+ colIndex
740
+ );
741
+ }
742
+ return /* @__PURE__ */ jsx(
743
+ "div",
744
+ {
745
+ className: cascaderColumnClasses,
746
+ ...listboxProps,
747
+ "aria-label": `Level ${colIndex + 1}`,
748
+ children: col.options.map(
749
+ (option, optionIndex) => renderColumnOption(option, optionIndex)
750
+ )
751
+ },
752
+ colIndex
753
+ );
754
+ }) })
755
+ )
756
+ ]
757
+ }
758
+ ) : null,
759
+ overlay.target
760
+ )
761
+ ] });
762
+ };
763
+
764
+ export {
765
+ Cascader
766
+ };