@mezzanine-ui/react 1.0.0-beta.1 → 1.0.0-beta.3

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 (172) hide show
  1. package/Anchor/Anchor.d.ts +51 -18
  2. package/Anchor/Anchor.js +15 -15
  3. package/Anchor/AnchorGroup.d.ts +34 -0
  4. package/Anchor/AnchorGroup.js +37 -0
  5. package/Anchor/AnchorItem.d.ts +30 -0
  6. package/Anchor/AnchorItem.js +65 -0
  7. package/Anchor/index.d.ts +2 -0
  8. package/Anchor/index.js +1 -0
  9. package/Anchor/utils.d.ts +13 -0
  10. package/Anchor/utils.js +95 -0
  11. package/AutoComplete/AutoComplete.d.ts +194 -0
  12. package/AutoComplete/AutoComplete.js +419 -0
  13. package/AutoComplete/index.d.ts +2 -0
  14. package/AutoComplete/index.js +1 -0
  15. package/AutoComplete/useAutoCompleteCreation.d.ts +33 -0
  16. package/AutoComplete/useAutoCompleteCreation.js +201 -0
  17. package/AutoComplete/useAutoCompleteKeyboard.d.ts +31 -0
  18. package/AutoComplete/useAutoCompleteKeyboard.js +149 -0
  19. package/AutoComplete/useAutoCompleteSearch.d.ts +16 -0
  20. package/AutoComplete/useAutoCompleteSearch.js +69 -0
  21. package/AutoComplete/useCreationTracker.d.ts +17 -0
  22. package/AutoComplete/useCreationTracker.js +47 -0
  23. package/Badge/Badge.js +2 -2
  24. package/Breadcrumb/BreadcrumbItem.d.ts +1 -1
  25. package/Button/Button.js +13 -11
  26. package/Button/index.d.ts +1 -1
  27. package/Button/typings.d.ts +27 -4
  28. package/Description/Description.d.ts +30 -0
  29. package/Description/Description.js +13 -0
  30. package/Description/DescriptionContent.d.ts +41 -0
  31. package/Description/DescriptionContent.js +14 -0
  32. package/Description/DescriptionGroup.d.ts +13 -0
  33. package/Description/DescriptionGroup.js +12 -0
  34. package/Description/DescriptionTitle.d.ts +45 -0
  35. package/Description/DescriptionTitle.js +17 -0
  36. package/Description/index.d.ts +8 -0
  37. package/Description/index.js +4 -0
  38. package/Dropdown/Dropdown.d.ts +43 -3
  39. package/Dropdown/Dropdown.js +154 -35
  40. package/Dropdown/DropdownAction.d.ts +1 -1
  41. package/Dropdown/DropdownAction.js +1 -4
  42. package/Dropdown/DropdownItem.d.ts +21 -4
  43. package/Dropdown/DropdownItem.js +23 -10
  44. package/Dropdown/DropdownItemCard.d.ts +5 -5
  45. package/Dropdown/DropdownItemCard.js +11 -10
  46. package/Dropdown/DropdownStatus.d.ts +2 -2
  47. package/Dropdown/DropdownStatus.js +29 -0
  48. package/Dropdown/dropdownKeydownHandler.d.ts +2 -1
  49. package/Dropdown/dropdownKeydownHandler.js +73 -0
  50. package/Dropdown/highlightText.js +5 -1
  51. package/Dropdown/shortcutTextHandler.d.ts +24 -0
  52. package/Dropdown/shortcutTextHandler.js +171 -0
  53. package/Form/FormControlContext.d.ts +2 -2
  54. package/Form/FormField.d.ts +56 -4
  55. package/Form/FormField.js +10 -6
  56. package/Form/FormHintText.d.ts +24 -1
  57. package/Form/FormHintText.js +4 -4
  58. package/Form/FormLabel.d.ts +6 -3
  59. package/Form/FormLabel.js +5 -3
  60. package/Input/Input.d.ts +29 -3
  61. package/Input/Input.js +22 -6
  62. package/Input/PasswordStrengthIndicator/PasswordStrengthIndicator.js +1 -1
  63. package/Modal/Modal.d.ts +103 -11
  64. package/Modal/Modal.js +14 -9
  65. package/Modal/ModalBodyForVerification.d.ts +59 -0
  66. package/Modal/ModalBodyForVerification.js +99 -0
  67. package/Modal/ModalControl.d.ts +2 -2
  68. package/Modal/ModalControl.js +1 -1
  69. package/Modal/ModalFooter.d.ts +119 -1
  70. package/Modal/ModalFooter.js +15 -3
  71. package/Modal/ModalHeader.d.ts +26 -7
  72. package/Modal/ModalHeader.js +33 -7
  73. package/Modal/index.d.ts +4 -5
  74. package/Modal/index.js +1 -2
  75. package/Modal/useModalContainer.d.ts +12 -3
  76. package/Modal/useModalContainer.js +28 -6
  77. package/Navigation/CollapsedMenu.d.ts +6 -0
  78. package/Navigation/CollapsedMenu.js +16 -0
  79. package/Navigation/Navigation.d.ts +17 -3
  80. package/Navigation/Navigation.js +48 -33
  81. package/Navigation/NavigationFooter.js +4 -2
  82. package/Navigation/NavigationHeader.d.ts +11 -1
  83. package/Navigation/NavigationHeader.js +6 -3
  84. package/Navigation/NavigationOption.d.ts +3 -2
  85. package/Navigation/NavigationOption.js +45 -26
  86. package/Navigation/NavigationOptionCategory.js +20 -2
  87. package/Navigation/context.d.ts +2 -0
  88. package/Navigation/useVisibleItems.d.ts +5 -0
  89. package/Navigation/useVisibleItems.js +54 -0
  90. package/NotificationCenter/NotificationCenter.d.ts +124 -0
  91. package/NotificationCenter/NotificationCenter.js +259 -0
  92. package/NotificationCenter/NotificationCenterDrawer.d.ts +89 -0
  93. package/NotificationCenter/index.d.ts +3 -0
  94. package/NotificationCenter/index.js +1 -0
  95. package/PageFooter/PageFooter.d.ts +19 -9
  96. package/PageFooter/PageFooter.js +10 -10
  97. package/PageHeader/PageHeader.js +4 -12
  98. package/PageToolbar/PageToolbar.d.ts +2 -6
  99. package/PageToolbar/utils.js +4 -12
  100. package/Select/index.d.ts +0 -2
  101. package/Select/index.js +0 -1
  102. package/Slider/useSlider.js +1 -1
  103. package/Table/Table.d.ts +53 -15
  104. package/Table/Table.js +178 -82
  105. package/Table/TableContext.d.ts +18 -42
  106. package/Table/components/TableActionsCell.d.ts +26 -0
  107. package/Table/components/TableActionsCell.js +78 -0
  108. package/Table/components/TableBody.d.ts +2 -5
  109. package/Table/components/TableBody.js +16 -19
  110. package/Table/components/TableBulkActions.d.ts +15 -0
  111. package/Table/components/TableBulkActions.js +26 -0
  112. package/Table/components/TableCell.d.ts +2 -0
  113. package/Table/components/TableCell.js +42 -10
  114. package/Table/components/TableColGroup.js +10 -112
  115. package/Table/components/TableColumnTitleMenu.d.ts +6 -0
  116. package/Table/components/TableColumnTitleMenu.js +20 -0
  117. package/Table/components/TableDragHandleCell.d.ts +2 -0
  118. package/Table/components/TableDragHandleCell.js +8 -1
  119. package/Table/components/TableExpandCell.d.ts +2 -0
  120. package/Table/components/TableExpandCell.js +8 -1
  121. package/Table/components/TableExpandedRow.js +3 -2
  122. package/Table/components/TableHeader.d.ts +2 -4
  123. package/Table/components/TableHeader.js +11 -14
  124. package/Table/components/TableResizeHandle.js +3 -7
  125. package/Table/components/TableRow.js +54 -20
  126. package/Table/components/TableSelectionCell.d.ts +5 -0
  127. package/Table/components/TableSelectionCell.js +12 -1
  128. package/Table/components/index.d.ts +1 -0
  129. package/Table/components/index.js +1 -0
  130. package/Table/hooks/index.d.ts +1 -1
  131. package/Table/hooks/index.js +1 -1
  132. package/Table/hooks/useTableDataSource.d.ts +2 -2
  133. package/Table/hooks/useTableExpansion.js +0 -6
  134. package/Table/hooks/useTableFixedOffsets.d.ts +1 -1
  135. package/Table/hooks/useTableFixedOffsets.js +24 -26
  136. package/Table/hooks/useTableResizedColumns.d.ts +2 -0
  137. package/Table/hooks/useTableResizedColumns.js +22 -0
  138. package/Table/hooks/useTableScroll.d.ts +3 -1
  139. package/Table/hooks/useTableScroll.js +25 -19
  140. package/Table/hooks/useTableSelection.js +32 -8
  141. package/Table/hooks/useTableVirtualization.d.ts +1 -1
  142. package/Table/index.d.ts +4 -4
  143. package/Table/index.js +5 -3
  144. package/Table/utils/calculateColumnWidths.d.ts +28 -0
  145. package/Table/utils/calculateColumnWidths.js +80 -0
  146. package/Table/utils/index.d.ts +2 -0
  147. package/Table/utils/index.js +1 -0
  148. package/Table/utils/useTableRowSelection.d.ts +5 -5
  149. package/Table/utils/useTableRowSelection.js +14 -6
  150. package/Tag/TagGroup.d.ts +3 -0
  151. package/Tag/index.d.ts +2 -0
  152. package/Tag/index.js +1 -0
  153. package/Upload/UploadPictureCard.js +1 -1
  154. package/index.d.ts +36 -20
  155. package/index.js +26 -7
  156. package/package.json +4 -4
  157. package/utils/format-number-with-commas.d.ts +4 -0
  158. package/utils/format-number-with-commas.js +27 -0
  159. package/utils/parse-number-with-commas.d.ts +4 -0
  160. package/utils/parse-number-with-commas.js +22 -0
  161. package/Modal/ModalActions.d.ts +0 -9
  162. package/Modal/ModalActions.js +0 -20
  163. package/Modal/ModalBody.d.ts +0 -7
  164. package/Modal/ModalBody.js +0 -14
  165. package/Notification/Notification.d.ts +0 -54
  166. package/Notification/Notification.js +0 -76
  167. package/Notification/index.d.ts +0 -3
  168. package/Notification/index.js +0 -1
  169. package/Select/AutoComplete.d.ts +0 -107
  170. package/Select/AutoComplete.js +0 -114
  171. package/Table/hooks/useTableColumns.d.ts +0 -8
  172. package/Table/hooks/useTableColumns.js +0 -91
@@ -0,0 +1,419 @@
1
+ 'use client';
2
+ import { jsx } from 'react/jsx-runtime';
3
+ import { forwardRef, useContext, useState, useCallback, useImperativeHandle, useEffect, useId, useMemo, useRef } from 'react';
4
+ import { autocompleteClasses } from '@mezzanine-ui/core/autocomplete';
5
+ import { useAutoCompleteValueControl } from '../Form/useAutoCompleteValueControl.js';
6
+ import { useComposeRefs } from '../hooks/useComposeRefs.js';
7
+ import { SelectControlContext } from '../Select/SelectControlContext.js';
8
+ import SelectTrigger from '../Select/SelectTrigger.js';
9
+ import { useAutoCompleteCreation } from './useAutoCompleteCreation.js';
10
+ import { useAutoCompleteKeyboard } from './useAutoCompleteKeyboard.js';
11
+ import { useAutoCompleteSearch } from './useAutoCompleteSearch.js';
12
+ import { useCreationTracker } from './useCreationTracker.js';
13
+ import { FormControlContext } from '../Form/FormControlContext.js';
14
+ import Dropdown from '../Dropdown/Dropdown.js';
15
+ import cx from 'clsx';
16
+
17
+ const MENU_ID_PREFIX = 'mzn-select-autocomplete-menu-id';
18
+ /**
19
+ * Type guard to check if value is array (multiple mode)
20
+ */
21
+ function isMultipleValue(value) {
22
+ return Array.isArray(value);
23
+ }
24
+ /**
25
+ * Type guard to check if value is single (single mode)
26
+ */
27
+ function isSingleValue(value) {
28
+ return value !== null && value !== undefined && !Array.isArray(value);
29
+ }
30
+ /**
31
+ * Check if an option is already selected
32
+ */
33
+ function isOptionSelected(option, value, isMultiple) {
34
+ if (isMultiple && isMultipleValue(value)) {
35
+ return value.some((v) => v.id === option.id);
36
+ }
37
+ if (!isMultiple && isSingleValue(value)) {
38
+ return value.id === option.id;
39
+ }
40
+ return false;
41
+ }
42
+ /**
43
+ * The AutoComplete component for react. <br />
44
+ * Note that if you need search for ONLY given options, not included your typings,
45
+ * should considering using the `Select` component with `onSearch` prop.
46
+ */
47
+ const AutoComplete = forwardRef(function AutoComplete(props, ref) {
48
+ const { disabled: disabledFromFormControl, fullWidth: fullWidthFromFormControl, required: requiredFromFormControl, severity, } = useContext(FormControlContext) || {};
49
+ const { addable = false, asyncData = false, className, createSeparators = [',', '+', '\n'], defaultValue, disabled = disabledFromFormControl || false, disabledOptionsFilter = false, emptyText, error = severity === 'error' || false, fullWidth = fullWidthFromFormControl || false, id, inputPosition = 'outside', inputProps, inputRef, loading = false, loadingText, menuMaxHeight, mode = 'single', name, onClear: onClearProp, onChange: onChangeProp, onInsert, onSearch, onVisibilityChange, open: openProp, options: optionsProp, placeholder = '', prefix, required = requiredFromFormControl || false, searchDebounceTime = 300, searchTextControlRef, size, trimOnCreate = true, value: valueProp, createActionText, createActionTextTemplate = '建立 "{text}"', } = props;
50
+ const [uncontrolledOpen, setUncontrolledOpen] = useState(false);
51
+ const isMultiple = mode === 'multiple';
52
+ const isSingle = !isMultiple;
53
+ const isOpenControlled = openProp !== undefined;
54
+ const open = isOpenControlled ? openProp : uncontrolledOpen;
55
+ const toggleOpen = useCallback((newOpen) => {
56
+ const nextValue = typeof newOpen === 'function' ? newOpen(open) : newOpen;
57
+ if (!isOpenControlled) {
58
+ setUncontrolledOpen(nextValue);
59
+ }
60
+ onVisibilityChange === null || onVisibilityChange === void 0 ? void 0 : onVisibilityChange(nextValue);
61
+ }, [isOpenControlled, open, onVisibilityChange]);
62
+ const { focused, onFocus, onChange, onClear, options, searchText, setSearchText, value, } = useAutoCompleteValueControl(isMultiple
63
+ ? {
64
+ defaultValue: isMultipleValue(defaultValue) ? defaultValue : undefined,
65
+ disabledOptionsFilter,
66
+ mode: 'multiple',
67
+ onChange: onChangeProp,
68
+ onClear: onClearProp,
69
+ onClose: () => toggleOpen(false),
70
+ onSearch,
71
+ options: optionsProp,
72
+ value: isMultipleValue(valueProp) ? valueProp : undefined,
73
+ }
74
+ : {
75
+ defaultValue: isSingleValue(defaultValue) ? defaultValue : undefined,
76
+ disabledOptionsFilter,
77
+ mode: 'single',
78
+ onChange: onChangeProp,
79
+ onClear: onClearProp,
80
+ onClose: () => toggleOpen(false),
81
+ onSearch,
82
+ options: optionsProp,
83
+ value: isSingleValue(valueProp) || valueProp === null ? valueProp : undefined,
84
+ });
85
+ /** export set search text action to props (allow user to customize search text) */
86
+ useImperativeHandle(searchTextControlRef, () => ({ setSearchText }));
87
+ /** Track created items (new, unselected, all) */
88
+ const { clearUnselected, filterUnselected, isCreated, markCreated, clearNewlyCreated, markUnselected, } = useCreationTracker();
89
+ const creationEnabled = addable && typeof onInsert === 'function';
90
+ useEffect(() => {
91
+ if (addable && !onInsert) {
92
+ console.warn('[AutoComplete] `addable` 已開啟但未提供 `onInsert`,已停用建立功能。');
93
+ }
94
+ }, [addable, onInsert]);
95
+ const idSeed = useId();
96
+ const menuId = useMemo(() => `${MENU_ID_PREFIX}-${idSeed}`, [idSeed]);
97
+ const { handleActionCustom, handleBulkCreate, handlePaste, insertText, processBulkCreate, resetCreationInputs, setInsertText, } = useAutoCompleteCreation({
98
+ addable: creationEnabled,
99
+ clearUnselected,
100
+ createSeparators,
101
+ filterUnselected,
102
+ isMultiple,
103
+ isSingle,
104
+ markCreated,
105
+ clearNewlyCreated,
106
+ markUnselected,
107
+ onChangeMultiple: isMultiple
108
+ ? onChangeProp
109
+ : undefined,
110
+ onFocus,
111
+ onInsert,
112
+ options,
113
+ setSearchText,
114
+ toggleOpen,
115
+ trimOnCreate,
116
+ value,
117
+ wrappedOnChange: (chooseOption) => wrappedOnChange(chooseOption),
118
+ });
119
+ const { cancelSearch, isLoading, runSearch, } = useAutoCompleteSearch({
120
+ asyncData,
121
+ loading,
122
+ onSearch,
123
+ searchDebounceTime,
124
+ });
125
+ // Wrap onChange to track unselected created items
126
+ const wrappedOnChange = useCallback((chooseOption) => {
127
+ const result = onChange(chooseOption);
128
+ if (chooseOption) {
129
+ clearNewlyCreated([chooseOption.id]);
130
+ }
131
+ // In multiple mode, check if any created items were unselected
132
+ if (isMultiple && isMultipleValue(value) && isMultipleValue(result)) {
133
+ // Find items that were in value but not in result (unselected)
134
+ const unselectedItems = value.filter((v) => !result.some((r) => r.id === v.id));
135
+ // If any unselected item was created via onInsert, track it
136
+ markUnselected(unselectedItems.map((item) => item.id));
137
+ }
138
+ else if (isSingle && isSingleValue(value) && !result) {
139
+ // In single mode, if value was cleared and it was a created item, track it
140
+ markUnselected([value.id]);
141
+ }
142
+ return result;
143
+ }, [clearNewlyCreated, isMultiple, isSingle, markUnselected, onChange, value]);
144
+ const nodeRef = useRef(null);
145
+ const controlRef = useRef(null);
146
+ const composedRef = useComposeRefs([ref, controlRef]);
147
+ // In single mode, show searchText when focused, otherwise show selected value
148
+ // In multiple mode, always return empty string to avoid displaying "0"
149
+ const renderValue = useMemo(() => {
150
+ if (isSingle && focused) {
151
+ return () => searchText;
152
+ }
153
+ if (isMultiple) {
154
+ return () => '';
155
+ }
156
+ return undefined;
157
+ }, [focused, isMultiple, isSingle, searchText]);
158
+ function getPlaceholder() {
159
+ if (isSingle && focused && isSingleValue(value)) {
160
+ return value.name;
161
+ }
162
+ return placeholder;
163
+ }
164
+ /** Trigger input props */
165
+ const onSearchInputChange = (e) => {
166
+ const nextSearch = e.target.value;
167
+ /** should sync both search input and value */
168
+ setSearchText(nextSearch);
169
+ setInsertText(nextSearch);
170
+ if (autoSelectMatchingOption(nextSearch))
171
+ return;
172
+ if (!nextSearch) {
173
+ cancelSearch();
174
+ runSearch(nextSearch, { immediate: true });
175
+ return;
176
+ }
177
+ runSearch(nextSearch);
178
+ };
179
+ const onSearchInputFocus = (e) => {
180
+ var _a;
181
+ // When inputPosition is inside, let Dropdown handle the focus event
182
+ // Otherwise, stop propagation to prevent conflicts
183
+ if (inputPosition !== 'inside') {
184
+ e.stopPropagation();
185
+ }
186
+ // Only open if not already open to avoid flickering
187
+ // When inputPosition is inside, Dropdown will handle opening via inlineTriggerElement
188
+ if (inputPosition !== 'inside' && !open) {
189
+ toggleOpen(true);
190
+ }
191
+ onFocus(true);
192
+ (_a = inputProps === null || inputProps === void 0 ? void 0 : inputProps.onFocus) === null || _a === void 0 ? void 0 : _a.call(inputProps, e);
193
+ };
194
+ const onSearchInputBlur = (e) => {
195
+ var _a, _b, _c;
196
+ // When inputPosition is inside, we need special handling
197
+ if (inputPosition === 'inside') {
198
+ // When open is controlled, prevent default blur behavior to avoid conflicts
199
+ // The controlled open state should be the source of truth
200
+ if (isOpenControlled) {
201
+ // Don't let Dropdown's onBlur close the dropdown when controlled
202
+ // Only call onFocus(false) to update internal state
203
+ onFocus(false);
204
+ (_a = inputProps === null || inputProps === void 0 ? void 0 : inputProps.onBlur) === null || _a === void 0 ? void 0 : _a.call(inputProps, e);
205
+ return;
206
+ }
207
+ // For uncontrolled mode, let Dropdown handle it normally
208
+ // Dropdown's inlineTriggerElement will handle the blur and close logic
209
+ (_b = inputProps === null || inputProps === void 0 ? void 0 : inputProps.onBlur) === null || _b === void 0 ? void 0 : _b.call(inputProps, e);
210
+ return;
211
+ }
212
+ onFocus(false);
213
+ (_c = inputProps === null || inputProps === void 0 ? void 0 : inputProps.onBlur) === null || _c === void 0 ? void 0 : _c.call(inputProps, e);
214
+ };
215
+ const onClickSuffixActionIcon = () => {
216
+ toggleOpen((prev) => !prev);
217
+ };
218
+ const searchTextExistWithoutOption = !!searchText &&
219
+ options.find((option) => option.name === searchText) === undefined;
220
+ const shouldShowCreateAction = !!(searchTextExistWithoutOption && creationEnabled && insertText);
221
+ const context = useMemo(() => ({ onChange: wrappedOnChange, value }), [wrappedOnChange, value]);
222
+ // Convert SelectValue[] to DropdownOption[]
223
+ const dropdownOptions = useMemo(() => {
224
+ return options.map((option) => {
225
+ const created = isCreated(option.id);
226
+ const result = {
227
+ id: option.id,
228
+ name: option.name,
229
+ };
230
+ // Set checkSite based on mode
231
+ // Multiple mode: show checkbox at prepend
232
+ // Single mode: show checked icon at append when selected
233
+ if (mode === 'multiple') {
234
+ result.checkSite = 'prepend';
235
+ }
236
+ else {
237
+ result.checkSite = 'append';
238
+ }
239
+ // Set shortcutText to "New" for created items (persists even after selection)
240
+ if (created) {
241
+ result.shortcutText = 'New';
242
+ }
243
+ return result;
244
+ });
245
+ }, [isCreated, mode, options]);
246
+ // Get selected value for dropdown
247
+ const dropdownValue = useMemo(() => {
248
+ if (mode === 'multiple') {
249
+ return isMultipleValue(value) ? value.map((v) => v.id) : [];
250
+ }
251
+ return isSingleValue(value) ? value.id : undefined;
252
+ }, [mode, value]);
253
+ // Disable input when loading
254
+ const isInputDisabled = disabled || isLoading;
255
+ // For rendering: when loading, force options to empty to show loading status in Dropdown
256
+ const dropdownOptionsForRender = useMemo(() => {
257
+ if (isLoading)
258
+ return [];
259
+ return dropdownOptions;
260
+ }, [isLoading, dropdownOptions]);
261
+ const dropdownStatus = isLoading
262
+ ? 'loading'
263
+ : dropdownOptionsForRender.length === 0
264
+ ? 'empty'
265
+ : undefined;
266
+ // Handle dropdown option selection
267
+ const handleDropdownSelect = useCallback((option) => {
268
+ const selectedValue = options.find((opt) => opt.id === option.id);
269
+ if (selectedValue) {
270
+ wrappedOnChange(selectedValue);
271
+ // Close dropdown after selection in single mode
272
+ if (mode === 'single') {
273
+ toggleOpen(false);
274
+ onFocus(false);
275
+ }
276
+ }
277
+ }, [mode, onFocus, options, toggleOpen, wrappedOnChange]);
278
+ // Active index for dropdown keyboard navigation
279
+ const [activeIndex, setActiveIndex] = useState(null);
280
+ const setListboxHasVisualFocus = useCallback(() => { }, []);
281
+ // Reset activeIndex when options change
282
+ useEffect(() => {
283
+ if (!dropdownOptions.length) {
284
+ setActiveIndex(null);
285
+ return;
286
+ }
287
+ setActiveIndex((prev) => {
288
+ if (prev === null)
289
+ return null;
290
+ return Math.min(prev, dropdownOptions.length - 1);
291
+ });
292
+ }, [dropdownOptions.length]);
293
+ // Scroll to active option when activeIndex changes
294
+ useEffect(() => {
295
+ if (!open || activeIndex === null)
296
+ return;
297
+ requestAnimationFrame(() => {
298
+ const activeOption = document.getElementById(`${menuId}-option-${activeIndex}`);
299
+ activeOption === null || activeOption === void 0 ? void 0 : activeOption.scrollIntoView({ block: 'nearest' });
300
+ });
301
+ }, [activeIndex, menuId, open]);
302
+ // Compute aria-activedescendant
303
+ const ariaActivedescendant = useMemo(() => {
304
+ if (activeIndex !== null && dropdownOptions[activeIndex]) {
305
+ return `${menuId}-option-${activeIndex}`;
306
+ }
307
+ return undefined;
308
+ }, [activeIndex, dropdownOptions, menuId]);
309
+ const { handleInputKeyDown } = useAutoCompleteKeyboard({
310
+ activeIndex,
311
+ addable: creationEnabled,
312
+ createSeparators,
313
+ dropdownOptions,
314
+ handleBulkCreate,
315
+ handleDropdownSelect,
316
+ inputPropsOnKeyDown: inputProps === null || inputProps === void 0 ? void 0 : inputProps.onKeyDown,
317
+ inputRef,
318
+ mode,
319
+ onFocus,
320
+ open,
321
+ processBulkCreate,
322
+ searchText,
323
+ searchTextExistWithoutOption,
324
+ setActiveIndex,
325
+ setInsertText,
326
+ setListboxHasVisualFocus,
327
+ setSearchText,
328
+ toggleOpen,
329
+ value,
330
+ wrappedOnChange,
331
+ });
332
+ // Handle visibility change from Dropdown to prevent flickering
333
+ const handleVisibilityChange = useCallback((newOpen) => {
334
+ // Only update if state actually changed to prevent flickering
335
+ if (newOpen !== open) {
336
+ toggleOpen(newOpen);
337
+ }
338
+ }, [open, toggleOpen]);
339
+ const handlePasteWithFallback = useCallback((e) => {
340
+ var _a;
341
+ handlePaste(e);
342
+ (_a = inputProps === null || inputProps === void 0 ? void 0 : inputProps.onPaste) === null || _a === void 0 ? void 0 : _a.call(inputProps, e);
343
+ }, [handlePaste, inputProps]);
344
+ const autoSelectMatchingOption = useCallback((keyword) => {
345
+ if (!creationEnabled || !keyword.length)
346
+ return false;
347
+ const matchingOption = options.find((option) => option.name === keyword);
348
+ if (!matchingOption)
349
+ return false;
350
+ if (isSingle) {
351
+ if (!value) {
352
+ wrappedOnChange(matchingOption);
353
+ toggleOpen(false);
354
+ onFocus(false);
355
+ resetCreationInputs();
356
+ return true;
357
+ }
358
+ return false;
359
+ }
360
+ const alreadySelected = isOptionSelected(matchingOption, value, isMultiple);
361
+ if (!alreadySelected) {
362
+ wrappedOnChange(matchingOption);
363
+ resetCreationInputs();
364
+ return true;
365
+ }
366
+ return false;
367
+ }, [
368
+ creationEnabled,
369
+ isMultiple,
370
+ isSingle,
371
+ onFocus,
372
+ options,
373
+ resetCreationInputs,
374
+ toggleOpen,
375
+ value,
376
+ wrappedOnChange,
377
+ ]);
378
+ const resolvedInputProps = {
379
+ ...inputProps,
380
+ 'aria-activedescendant': ariaActivedescendant,
381
+ 'aria-controls': menuId,
382
+ 'aria-expanded': open,
383
+ 'aria-owns': menuId,
384
+ id: id !== null && id !== void 0 ? id : inputProps === null || inputProps === void 0 ? void 0 : inputProps.id,
385
+ name: name !== null && name !== void 0 ? name : inputProps === null || inputProps === void 0 ? void 0 : inputProps.name,
386
+ onBlur: onSearchInputBlur,
387
+ onChange: onSearchInputChange,
388
+ onFocus: onSearchInputFocus,
389
+ onKeyDown: handleInputKeyDown,
390
+ onPaste: handlePasteWithFallback,
391
+ readOnly: false,
392
+ role: 'combobox',
393
+ };
394
+ return (jsx(SelectControlContext.Provider, { value: context, children: jsx("div", { ref: nodeRef, className: cx(autocompleteClasses.host, {
395
+ [autocompleteClasses.hostFullWidth]: fullWidth,
396
+ [autocompleteClasses.hostInsideClosed]: inputPosition === 'inside' && !open,
397
+ }), children: jsx(Dropdown, { actionText: shouldShowCreateAction
398
+ ? (createActionText
399
+ ? createActionText(insertText)
400
+ : createActionTextTemplate.replace('{text}', insertText))
401
+ : undefined, activeIndex: activeIndex, disabled: isInputDisabled, emptyText: emptyText, followText: searchText, inputPosition: inputPosition, isMatchInputValue: true, listboxId: menuId, loadingText: loadingText, maxHeight: menuMaxHeight, mode: mode, onActionCustom: shouldShowCreateAction
402
+ ? handleActionCustom
403
+ : undefined, onItemHover: setActiveIndex, onSelect: handleDropdownSelect, onVisibilityChange: handleVisibilityChange, open: open, options: dropdownOptionsForRender, placement: "bottom", sameWidth: true, showDropdownActions: shouldShowCreateAction, showActionShowTopBar: shouldShowCreateAction, status: dropdownStatus, type: "default", value: dropdownValue, children: jsx(SelectTrigger, { ref: composedRef, active: open, className: className, clearable: true, disabled: isInputDisabled, fullWidth: fullWidth, inputRef: inputRef, mode: mode, onTagClose: wrappedOnChange, onClear: onClear, placeholder: getPlaceholder(), prefix: prefix, readOnly: false, required: required, type: error ? 'error' : 'default', inputProps: {
404
+ ...resolvedInputProps,
405
+ onClick: (e) => {
406
+ var _a;
407
+ // When inputPosition is inside, let Dropdown handle the click event
408
+ // Otherwise, stop propagation to prevent conflicts
409
+ if (inputPosition !== 'inside') {
410
+ e.stopPropagation();
411
+ }
412
+ (_a = resolvedInputProps.onClick) === null || _a === void 0 ? void 0 : _a.call(resolvedInputProps, e);
413
+ },
414
+ }, searchText: searchText, size: size, showTextInputAfterTags: true, suffixAction: onClickSuffixActionIcon, value: mode === 'multiple' && isMultipleValue(value) && value.length === 0
415
+ ? undefined
416
+ : value !== null && value !== void 0 ? value : undefined, ...(mode === 'single' && renderValue ? { renderValue } : {}) }) }) }) }));
417
+ });
418
+
419
+ export { AutoComplete as default };
@@ -0,0 +1,2 @@
1
+ export { default as AutoComplete, default } from './AutoComplete';
2
+ export type { AutoCompleteBaseProps, AutoCompleteMultipleProps, AutoCompleteProps, AutoCompleteSingleProps, } from './AutoComplete';
@@ -0,0 +1 @@
1
+ export { default as AutoComplete, default } from './AutoComplete.js';
@@ -0,0 +1,33 @@
1
+ import { ClipboardEvent } from 'react';
2
+ import { SelectValue } from '../Select/typings';
3
+ type UseAutoCompleteCreationParams = {
4
+ addable: boolean;
5
+ createSeparators: string[];
6
+ filterUnselected: (options: SelectValue[]) => SelectValue[];
7
+ clearUnselected: () => void;
8
+ isMultiple: boolean;
9
+ isSingle: boolean;
10
+ markCreated: (id: string) => void;
11
+ clearNewlyCreated: (ids?: string[]) => void;
12
+ markUnselected: (ids: string[]) => void;
13
+ onChangeMultiple?: (newOptions: SelectValue[]) => void;
14
+ onFocus: (focus: boolean) => void;
15
+ onInsert?: (text: string, currentOptions: SelectValue[]) => SelectValue[];
16
+ options: SelectValue[];
17
+ toggleOpen: (newOpen: boolean | ((prev: boolean) => boolean)) => void;
18
+ trimOnCreate: boolean;
19
+ value: SelectValue[] | SelectValue | null | undefined;
20
+ wrappedOnChange: (chooseOption: SelectValue | null) => SelectValue[] | SelectValue | null;
21
+ setSearchText: (value: string) => void;
22
+ };
23
+ type ProcessBulkCreate = (text: string) => string[];
24
+ export declare function useAutoCompleteCreation({ addable, createSeparators, filterUnselected, clearUnselected, isMultiple, isSingle, markCreated, markUnselected, onChangeMultiple, onFocus, onInsert, options, toggleOpen, trimOnCreate, value, wrappedOnChange, setSearchText, clearNewlyCreated, }: UseAutoCompleteCreationParams): {
25
+ handleActionCustom: () => void;
26
+ handleBulkCreate: (texts: string[]) => void;
27
+ handlePaste: (e: ClipboardEvent<HTMLInputElement>) => void;
28
+ insertText: string;
29
+ processBulkCreate: ProcessBulkCreate;
30
+ resetCreationInputs: () => void;
31
+ setInsertText: import("react").Dispatch<import("react").SetStateAction<string>>;
32
+ };
33
+ export {};
@@ -0,0 +1,201 @@
1
+ import { useState, useRef, useEffect, useCallback } from 'react';
2
+
3
+ function isMultipleValue(value) {
4
+ return Array.isArray(value);
5
+ }
6
+ function isSingleValue(value) {
7
+ return value !== null && value !== undefined && !Array.isArray(value);
8
+ }
9
+ function isOptionSelected(option, value, isMultiple) {
10
+ if (isMultiple && isMultipleValue(value)) {
11
+ return value.some((v) => v.id === option.id);
12
+ }
13
+ if (!isMultiple && isSingleValue(value)) {
14
+ return value.id === option.id;
15
+ }
16
+ return false;
17
+ }
18
+ function useAutoCompleteCreation({ addable, createSeparators, filterUnselected, clearUnselected, isMultiple, isSingle, markCreated, markUnselected, onChangeMultiple, onFocus, onInsert, options, toggleOpen, trimOnCreate, value, wrappedOnChange, setSearchText, clearNewlyCreated, }) {
19
+ const [insertText, setInsertText] = useState('');
20
+ const valueRef = useRef(value);
21
+ useEffect(() => {
22
+ valueRef.current = value;
23
+ }, [value]);
24
+ const resetCreationInputs = useCallback(() => {
25
+ setSearchText('');
26
+ setInsertText('');
27
+ }, [setSearchText]);
28
+ const processBulkCreate = useCallback((text) => {
29
+ if (!text || !addable || !onInsert)
30
+ return [];
31
+ let parts = [text];
32
+ createSeparators.forEach((separator) => {
33
+ const newParts = [];
34
+ parts.forEach((part) => {
35
+ newParts.push(...part.split(separator));
36
+ });
37
+ parts = newParts;
38
+ });
39
+ const processed = parts
40
+ .map((part) => (trimOnCreate ? part.trim() : part))
41
+ .filter((part) => part.length > 0);
42
+ const selectedNames = new Set();
43
+ if (isMultiple && isMultipleValue(valueRef.current)) {
44
+ valueRef.current.forEach((v) => selectedNames.add(v.name.toLowerCase()));
45
+ }
46
+ else if (isSingle && isSingleValue(valueRef.current)) {
47
+ selectedNames.add(valueRef.current.name.toLowerCase());
48
+ }
49
+ return processed.filter((part) => !selectedNames.has(part.toLowerCase()));
50
+ }, [addable, createSeparators, isMultiple, isSingle, onInsert, trimOnCreate]);
51
+ const handleBulkCreate = useCallback((texts) => {
52
+ if (!addable || texts.length === 0 || !onInsert)
53
+ return;
54
+ let currentOptions = filterUnselected(options);
55
+ clearUnselected();
56
+ const itemsToAdd = [];
57
+ const newlyCreatedIds = new Set();
58
+ const newlySelectedIds = new Set();
59
+ texts.forEach((text) => {
60
+ const existingOption = currentOptions.find((option) => option.name === text);
61
+ if (existingOption) {
62
+ const alreadySelected = isOptionSelected(existingOption, valueRef.current, isMultiple);
63
+ if (!alreadySelected) {
64
+ itemsToAdd.push(existingOption);
65
+ }
66
+ }
67
+ else {
68
+ try {
69
+ const updatedOptions = onInsert(text, currentOptions);
70
+ if (!Array.isArray(updatedOptions)) {
71
+ return;
72
+ }
73
+ const newOption = updatedOptions.find((opt) => !currentOptions.some((existing) => existing.id === opt.id));
74
+ if (newOption) {
75
+ itemsToAdd.push(newOption);
76
+ newlyCreatedIds.add(newOption.id);
77
+ markCreated(newOption.id);
78
+ currentOptions = updatedOptions;
79
+ }
80
+ }
81
+ catch (_a) {
82
+ console.warn('Invalid insert result');
83
+ // Ignore invalid insert result; do not mutate currentOptions
84
+ }
85
+ }
86
+ });
87
+ if (itemsToAdd.length > 0) {
88
+ if (isSingle && itemsToAdd[0]) {
89
+ wrappedOnChange(itemsToAdd[0]);
90
+ toggleOpen(false);
91
+ onFocus(false);
92
+ newlySelectedIds.add(itemsToAdd[0].id);
93
+ }
94
+ else if (isMultiple) {
95
+ const currentValues = isMultipleValue(valueRef.current)
96
+ ? valueRef.current
97
+ : [];
98
+ const newItemsToAdd = itemsToAdd.filter((item) => !currentValues.some((existing) => existing.id === item.id));
99
+ const mergedValues = [...currentValues, ...newItemsToAdd];
100
+ if (onChangeMultiple) {
101
+ onChangeMultiple(mergedValues);
102
+ mergedValues.forEach((v) => newlySelectedIds.add(v.id));
103
+ }
104
+ else {
105
+ newItemsToAdd.forEach((item) => {
106
+ wrappedOnChange(item);
107
+ newlySelectedIds.add(item.id);
108
+ });
109
+ }
110
+ }
111
+ if (newlySelectedIds.size) {
112
+ clearNewlyCreated(Array.from(newlySelectedIds));
113
+ newlySelectedIds.forEach((id) => newlyCreatedIds.delete(id));
114
+ }
115
+ if (newlyCreatedIds.size) {
116
+ markUnselected(Array.from(newlyCreatedIds));
117
+ }
118
+ }
119
+ }, [
120
+ addable,
121
+ clearNewlyCreated,
122
+ clearUnselected,
123
+ filterUnselected,
124
+ isMultiple,
125
+ isSingle,
126
+ markCreated,
127
+ markUnselected,
128
+ onChangeMultiple,
129
+ onFocus,
130
+ onInsert,
131
+ options,
132
+ toggleOpen,
133
+ wrappedOnChange,
134
+ ]);
135
+ const handleActionCustom = useCallback(() => {
136
+ if (!addable || !insertText)
137
+ return;
138
+ const hasSeparator = createSeparators.some((sep) => insertText.includes(sep));
139
+ if (hasSeparator && isMultiple) {
140
+ const textsToCreate = processBulkCreate(insertText);
141
+ if (textsToCreate.length > 0) {
142
+ handleBulkCreate(textsToCreate);
143
+ resetCreationInputs();
144
+ return;
145
+ }
146
+ }
147
+ const textsToCreate = processBulkCreate(insertText);
148
+ if (textsToCreate.length > 0) {
149
+ handleBulkCreate(textsToCreate);
150
+ resetCreationInputs();
151
+ }
152
+ }, [
153
+ addable,
154
+ createSeparators,
155
+ handleBulkCreate,
156
+ insertText,
157
+ isMultiple,
158
+ processBulkCreate,
159
+ resetCreationInputs,
160
+ ]);
161
+ const handlePaste = useCallback((e) => {
162
+ if (!addable || !onInsert) {
163
+ return;
164
+ }
165
+ const pastedText = e.clipboardData.getData('text');
166
+ if (!pastedText) {
167
+ return;
168
+ }
169
+ if (isMultiple) {
170
+ const hasSeparator = createSeparators.some((sep) => pastedText.includes(sep));
171
+ if (hasSeparator) {
172
+ e.preventDefault();
173
+ const textsToCreate = processBulkCreate(pastedText);
174
+ if (textsToCreate.length > 0) {
175
+ handleBulkCreate(textsToCreate);
176
+ resetCreationInputs();
177
+ return;
178
+ }
179
+ }
180
+ }
181
+ }, [
182
+ addable,
183
+ createSeparators,
184
+ handleBulkCreate,
185
+ isMultiple,
186
+ onInsert,
187
+ processBulkCreate,
188
+ resetCreationInputs,
189
+ ]);
190
+ return {
191
+ handleActionCustom,
192
+ handleBulkCreate,
193
+ handlePaste,
194
+ insertText,
195
+ processBulkCreate,
196
+ resetCreationInputs,
197
+ setInsertText,
198
+ };
199
+ }
200
+
201
+ export { useAutoCompleteCreation };