@mezzanine-ui/react 1.0.0-beta.2 → 1.0.0-beta.4

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 (193) 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 +217 -0
  12. package/AutoComplete/AutoComplete.js +433 -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/Breadcrumb/Breadcrumb.js +16 -21
  24. package/Breadcrumb/BreadcrumbDropdown.d.ts +11 -0
  25. package/Breadcrumb/BreadcrumbDropdown.js +22 -0
  26. package/Breadcrumb/BreadcrumbItem.d.ts +2 -3
  27. package/Breadcrumb/BreadcrumbItem.js +13 -31
  28. package/Breadcrumb/BreadcrumbOverflowMenu.d.ts +7 -0
  29. package/Breadcrumb/BreadcrumbOverflowMenu.js +77 -0
  30. package/Breadcrumb/BreadcrumbOverflowMenuDropdown.d.ts +11 -0
  31. package/Breadcrumb/BreadcrumbOverflowMenuDropdown.js +21 -0
  32. package/Breadcrumb/BreadcrumbOverflowMenuItem.d.ts +3 -0
  33. package/Breadcrumb/BreadcrumbOverflowMenuItem.js +27 -0
  34. package/Breadcrumb/typings.d.ts +21 -39
  35. package/Button/Button.js +13 -11
  36. package/Button/index.d.ts +1 -1
  37. package/Button/typings.d.ts +27 -4
  38. package/Checkbox/index.d.ts +4 -5
  39. package/Checkbox/index.js +1 -5
  40. package/ContentHeader/ContentHeader.d.ts +160 -0
  41. package/ContentHeader/ContentHeader.js +54 -0
  42. package/ContentHeader/index.d.ts +2 -0
  43. package/ContentHeader/index.js +1 -0
  44. package/ContentHeader/utils.d.ts +23 -0
  45. package/ContentHeader/utils.js +215 -0
  46. package/Description/Description.d.ts +12 -22
  47. package/Description/Description.js +4 -24
  48. package/Dropdown/Dropdown.d.ts +46 -1
  49. package/Dropdown/Dropdown.js +99 -14
  50. package/Dropdown/DropdownAction.d.ts +1 -1
  51. package/Dropdown/DropdownAction.js +1 -4
  52. package/Dropdown/DropdownItem.d.ts +28 -1
  53. package/Dropdown/DropdownItem.js +56 -14
  54. package/Dropdown/DropdownItemCard.d.ts +2 -2
  55. package/Dropdown/DropdownItemCard.js +20 -16
  56. package/Dropdown/DropdownStatus.js +29 -0
  57. package/Dropdown/dropdownKeydownHandler.d.ts +2 -1
  58. package/Dropdown/dropdownKeydownHandler.js +73 -0
  59. package/Dropdown/highlightText.js +5 -1
  60. package/Dropdown/shortcutTextHandler.d.ts +24 -0
  61. package/Dropdown/shortcutTextHandler.js +171 -0
  62. package/Empty/Empty.js +2 -1
  63. package/Empty/icons/EmptyMainNotificationIcon.d.ts +4 -0
  64. package/Empty/icons/EmptyMainNotificationIcon.js +9 -0
  65. package/Empty/typings.d.ts +2 -2
  66. package/FilterArea/Filter.d.ts +32 -0
  67. package/FilterArea/Filter.js +23 -0
  68. package/FilterArea/FilterArea.d.ts +58 -0
  69. package/FilterArea/FilterArea.js +31 -0
  70. package/FilterArea/FilterLine.d.ts +11 -0
  71. package/FilterArea/FilterLine.js +13 -0
  72. package/FilterArea/index.d.ts +6 -0
  73. package/FilterArea/index.js +3 -0
  74. package/Form/FormField.js +3 -1
  75. package/Input/Input.d.ts +35 -7
  76. package/Input/Input.js +48 -14
  77. package/Input/index.d.ts +1 -1
  78. package/Modal/MediaPreviewModal.d.ts +54 -0
  79. package/Modal/MediaPreviewModal.js +158 -0
  80. package/Modal/Modal.d.ts +103 -11
  81. package/Modal/Modal.js +14 -9
  82. package/Modal/ModalBodyForVerification.d.ts +59 -0
  83. package/Modal/ModalBodyForVerification.js +99 -0
  84. package/Modal/ModalControl.d.ts +2 -2
  85. package/Modal/ModalControl.js +1 -1
  86. package/Modal/ModalFooter.d.ts +119 -1
  87. package/Modal/ModalFooter.js +15 -3
  88. package/Modal/ModalHeader.d.ts +26 -7
  89. package/Modal/ModalHeader.js +33 -7
  90. package/Modal/index.d.ts +6 -5
  91. package/Modal/index.js +2 -2
  92. package/Modal/useModalContainer.d.ts +12 -3
  93. package/Modal/useModalContainer.js +28 -6
  94. package/Navigation/Navigation.d.ts +7 -2
  95. package/Navigation/Navigation.js +36 -35
  96. package/Navigation/NavigationHeader.d.ts +4 -0
  97. package/Navigation/NavigationHeader.js +3 -2
  98. package/Navigation/NavigationOption.d.ts +8 -3
  99. package/Navigation/NavigationOption.js +46 -11
  100. package/Navigation/NavigationOptionCategory.js +1 -0
  101. package/Navigation/NavigationOverflowMenu.d.ts +6 -0
  102. package/Navigation/NavigationOverflowMenu.js +90 -0
  103. package/Navigation/NavigationOverflowMenuOption.d.ts +7 -0
  104. package/Navigation/NavigationOverflowMenuOption.js +68 -0
  105. package/Navigation/NavigationUserMenu.d.ts +4 -2
  106. package/Navigation/NavigationUserMenu.js +13 -5
  107. package/Navigation/context.d.ts +3 -2
  108. package/Navigation/useVisibleItems.d.ts +5 -0
  109. package/Navigation/useVisibleItems.js +54 -0
  110. package/NotificationCenter/NotificationCenter.d.ts +124 -0
  111. package/NotificationCenter/NotificationCenter.js +279 -0
  112. package/NotificationCenter/NotificationCenterDrawer.d.ts +109 -0
  113. package/NotificationCenter/index.d.ts +3 -0
  114. package/NotificationCenter/index.js +1 -0
  115. package/PageFooter/PageFooter.d.ts +19 -9
  116. package/PageFooter/PageFooter.js +10 -10
  117. package/PageHeader/PageHeader.d.ts +32 -25
  118. package/PageHeader/PageHeader.js +49 -43
  119. package/ResultState/ResultState.d.ts +9 -0
  120. package/ResultState/ResultState.js +36 -4
  121. package/Scrollbar/Scrollbar.d.ts +9 -0
  122. package/Scrollbar/Scrollbar.js +78 -0
  123. package/Scrollbar/index.d.ts +2 -0
  124. package/Scrollbar/index.js +1 -0
  125. package/Scrollbar/typings.d.ts +47 -0
  126. package/Select/SelectTrigger.js +5 -4
  127. package/Select/index.d.ts +0 -2
  128. package/Select/index.js +0 -1
  129. package/Select/typings.d.ts +6 -1
  130. package/Selection/Selection.js +1 -1
  131. package/Selection/SelectionGroup.d.ts +28 -0
  132. package/Slider/useSlider.js +1 -1
  133. package/Table/Table.d.ts +2 -120
  134. package/Table/Table.js +148 -53
  135. package/Table/TableContext.d.ts +11 -12
  136. package/Table/components/TableActionsCell.js +12 -4
  137. package/Table/components/TableBody.js +2 -1
  138. package/Table/components/TableBulkActions.js +1 -19
  139. package/Table/components/TableColGroup.d.ts +1 -4
  140. package/Table/components/TableColGroup.js +15 -16
  141. package/Table/components/TableCollectableCell.d.ts +17 -0
  142. package/Table/components/TableCollectableCell.js +54 -0
  143. package/Table/components/TableDragOrPinHandleCell.d.ts +20 -0
  144. package/Table/components/TableDragOrPinHandleCell.js +58 -0
  145. package/Table/components/TableExpandedRow.js +11 -2
  146. package/Table/components/TableHeader.js +12 -10
  147. package/Table/components/TableRow.js +38 -13
  148. package/Table/components/TableSelectionCell.js +1 -1
  149. package/Table/components/TableToggleableCell.d.ts +16 -0
  150. package/Table/components/TableToggleableCell.js +51 -0
  151. package/Table/components/index.d.ts +4 -1
  152. package/Table/components/index.js +3 -0
  153. package/Table/hooks/typings.d.ts +18 -4
  154. package/Table/hooks/useTableExpansion.d.ts +2 -2
  155. package/Table/hooks/useTableExpansion.js +5 -5
  156. package/Table/hooks/useTableFixedOffsets.d.ts +6 -2
  157. package/Table/hooks/useTableFixedOffsets.js +60 -26
  158. package/Table/hooks/useTableScroll.d.ts +9 -3
  159. package/Table/hooks/useTableScroll.js +34 -7
  160. package/Table/hooks/useTableVirtualization.d.ts +2 -1
  161. package/Table/hooks/useTableVirtualization.js +2 -8
  162. package/Table/index.d.ts +4 -3
  163. package/Table/index.js +3 -0
  164. package/Table/typings.d.ts +172 -0
  165. package/Table/utils/useTableRowSelection.js +13 -5
  166. package/Tag/TagGroup.d.ts +3 -0
  167. package/Tag/index.d.ts +2 -0
  168. package/Tag/index.js +1 -0
  169. package/Transition/Slide.d.ts +9 -2
  170. package/Transition/Slide.js +7 -4
  171. package/Tree/TreeNode.js +1 -1
  172. package/Upload/UploadPictureCard.js +1 -1
  173. package/index.d.ts +37 -21
  174. package/index.js +25 -11
  175. package/package.json +6 -4
  176. package/Modal/ModalActions.d.ts +0 -9
  177. package/Modal/ModalActions.js +0 -20
  178. package/Modal/ModalBody.d.ts +0 -7
  179. package/Modal/ModalBody.js +0 -14
  180. package/Notification/Notification.d.ts +0 -54
  181. package/Notification/Notification.js +0 -76
  182. package/Notification/index.d.ts +0 -3
  183. package/Notification/index.js +0 -1
  184. package/PageToolbar/PageToolbar.d.ts +0 -114
  185. package/PageToolbar/PageToolbar.js +0 -23
  186. package/PageToolbar/index.d.ts +0 -2
  187. package/PageToolbar/index.js +0 -1
  188. package/PageToolbar/utils.d.ts +0 -23
  189. package/PageToolbar/utils.js +0 -165
  190. package/Select/AutoComplete.d.ts +0 -107
  191. package/Select/AutoComplete.js +0 -114
  192. package/Table/components/TableDragHandleCell.d.ts +0 -11
  193. package/Table/components/TableDragHandleCell.js +0 -44
@@ -0,0 +1,433 @@
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, keepSearchTextOnBlur = false, inputPosition = 'outside', inputProps, inputRef, loading = false, loadingText, menuMaxHeight, mode = 'single', name, onClear: onClearProp, onChange: onChangeProp, onInsert, onSearch, onSearchTextChange, onVisibilityChange, open: openProp, options: optionsProp, placeholder = '', prefix, required = requiredFromFormControl || false, searchDebounceTime = 300, searchTextControlRef, size, trimOnCreate = true, value: valueProp, createActionText, createActionTextTemplate = '建立 "{text}"', dropdownZIndex, onReachBottom, onLeaveBottom, } = 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
151
+ && (focused || (keepSearchTextOnBlur && !value && searchText))) {
152
+ return () => searchText;
153
+ }
154
+ if (isMultiple) {
155
+ return () => '';
156
+ }
157
+ return undefined;
158
+ }, [focused, isMultiple, isSingle, keepSearchTextOnBlur, searchText, value]);
159
+ function getPlaceholder() {
160
+ if (isSingle && focused && isSingleValue(value)) {
161
+ return value.name;
162
+ }
163
+ return placeholder;
164
+ }
165
+ /** Trigger input props */
166
+ const onSearchInputChange = (e) => {
167
+ const nextSearch = e.target.value;
168
+ /** should sync both search input and value */
169
+ setSearchText(nextSearch);
170
+ setInsertText(nextSearch);
171
+ onSearchTextChange === null || onSearchTextChange === void 0 ? void 0 : onSearchTextChange(nextSearch);
172
+ if (autoSelectMatchingOption(nextSearch))
173
+ return;
174
+ if (!nextSearch) {
175
+ cancelSearch();
176
+ runSearch(nextSearch, { immediate: true });
177
+ return;
178
+ }
179
+ runSearch(nextSearch);
180
+ };
181
+ const onSearchInputFocus = (e) => {
182
+ var _a;
183
+ // When inputPosition is inside, let Dropdown handle the focus event
184
+ // Otherwise, stop propagation to prevent conflicts
185
+ if (inputPosition !== 'inside') {
186
+ e.stopPropagation();
187
+ }
188
+ // Only open if not already open to avoid flickering
189
+ // When inputPosition is inside, Dropdown will handle opening via inlineTriggerElement
190
+ if (inputPosition !== 'inside' && !open) {
191
+ toggleOpen(true);
192
+ }
193
+ onFocus(true);
194
+ (_a = inputProps === null || inputProps === void 0 ? void 0 : inputProps.onFocus) === null || _a === void 0 ? void 0 : _a.call(inputProps, e);
195
+ };
196
+ const onSearchInputBlur = (e) => {
197
+ var _a, _b, _c;
198
+ // When inputPosition is inside, we need special handling
199
+ if (inputPosition === 'inside') {
200
+ // When open is controlled, prevent default blur behavior to avoid conflicts
201
+ // The controlled open state should be the source of truth
202
+ if (isOpenControlled) {
203
+ // Don't let Dropdown's onBlur close the dropdown when controlled
204
+ // Only call onFocus(false) to update internal state
205
+ onFocus(false);
206
+ (_a = inputProps === null || inputProps === void 0 ? void 0 : inputProps.onBlur) === null || _a === void 0 ? void 0 : _a.call(inputProps, e);
207
+ return;
208
+ }
209
+ // For uncontrolled mode, let Dropdown handle it normally
210
+ // Dropdown's inlineTriggerElement will handle the blur and close logic
211
+ (_b = inputProps === null || inputProps === void 0 ? void 0 : inputProps.onBlur) === null || _b === void 0 ? void 0 : _b.call(inputProps, e);
212
+ return;
213
+ }
214
+ onFocus(false);
215
+ (_c = inputProps === null || inputProps === void 0 ? void 0 : inputProps.onBlur) === null || _c === void 0 ? void 0 : _c.call(inputProps, e);
216
+ };
217
+ const onClickSuffixActionIcon = () => {
218
+ toggleOpen((prev) => !prev);
219
+ };
220
+ const searchTextExistWithoutOption = !!searchText &&
221
+ options.find((option) => option.name === searchText) === undefined;
222
+ const shouldShowCreateAction = !!(searchTextExistWithoutOption && creationEnabled && insertText);
223
+ const context = useMemo(() => ({ onChange: wrappedOnChange, value }), [wrappedOnChange, value]);
224
+ // Convert SelectValue[] to DropdownOption[]
225
+ const dropdownOptions = useMemo(() => {
226
+ return options.map((option) => {
227
+ const created = isCreated(option.id);
228
+ const result = {
229
+ id: option.id,
230
+ name: option.name,
231
+ };
232
+ // Set checkSite based on mode
233
+ // Multiple mode: show checkbox at prepend
234
+ // Single mode: show checked icon at append when selected
235
+ if (mode === 'multiple') {
236
+ result.checkSite = 'prefix';
237
+ }
238
+ else {
239
+ result.checkSite = 'suffix';
240
+ }
241
+ // Set shortcutText to "New" for created items (persists even after selection)
242
+ if (created) {
243
+ result.shortcutText = 'New';
244
+ }
245
+ return result;
246
+ });
247
+ }, [isCreated, mode, options]);
248
+ // Get selected value for dropdown
249
+ const dropdownValue = useMemo(() => {
250
+ if (mode === 'multiple') {
251
+ return isMultipleValue(value) ? value.map((v) => v.id) : [];
252
+ }
253
+ return isSingleValue(value) ? value.id : undefined;
254
+ }, [mode, value]);
255
+ // Disable input when loading
256
+ const isInputDisabled = disabled || isLoading;
257
+ // For rendering: when loading, force options to empty to show loading status in Dropdown
258
+ const dropdownOptionsForRender = useMemo(() => {
259
+ if (isLoading)
260
+ return [];
261
+ return dropdownOptions;
262
+ }, [isLoading, dropdownOptions]);
263
+ const dropdownStatus = isLoading
264
+ ? 'loading'
265
+ : dropdownOptionsForRender.length === 0
266
+ ? 'empty'
267
+ : undefined;
268
+ // Handle dropdown option selection
269
+ const handleDropdownSelect = useCallback((option) => {
270
+ const selectedValue = options.find((opt) => opt.id === option.id);
271
+ if (selectedValue) {
272
+ // Close dropdown after selection in single mode
273
+ if (mode === 'single') {
274
+ // Update searchText first to prevent showing old value
275
+ setSearchText(selectedValue.name);
276
+ setInsertText(selectedValue.name);
277
+ // Then update value and focus state
278
+ wrappedOnChange(selectedValue);
279
+ toggleOpen(false);
280
+ onFocus(false);
281
+ }
282
+ else {
283
+ wrappedOnChange(selectedValue);
284
+ }
285
+ }
286
+ }, [mode, onFocus, options, setSearchText, setInsertText, toggleOpen, wrappedOnChange]);
287
+ // Active index for dropdown keyboard navigation
288
+ const [activeIndex, setActiveIndex] = useState(null);
289
+ const setListboxHasVisualFocus = useCallback(() => { }, []);
290
+ // Reset activeIndex when options change
291
+ useEffect(() => {
292
+ if (!dropdownOptions.length) {
293
+ setActiveIndex(null);
294
+ return;
295
+ }
296
+ setActiveIndex((prev) => {
297
+ if (prev === null)
298
+ return null;
299
+ return Math.min(prev, dropdownOptions.length - 1);
300
+ });
301
+ }, [dropdownOptions.length]);
302
+ // Scroll to active option when activeIndex changes
303
+ useEffect(() => {
304
+ if (!open || activeIndex === null)
305
+ return;
306
+ requestAnimationFrame(() => {
307
+ const activeOption = document.getElementById(`${menuId}-option-${activeIndex}`);
308
+ activeOption === null || activeOption === void 0 ? void 0 : activeOption.scrollIntoView({ block: 'nearest' });
309
+ });
310
+ }, [activeIndex, menuId, open]);
311
+ // Compute aria-activedescendant
312
+ const ariaActivedescendant = useMemo(() => {
313
+ if (activeIndex !== null && dropdownOptions[activeIndex]) {
314
+ return `${menuId}-option-${activeIndex}`;
315
+ }
316
+ return undefined;
317
+ }, [activeIndex, dropdownOptions, menuId]);
318
+ const { handleInputKeyDown } = useAutoCompleteKeyboard({
319
+ activeIndex,
320
+ addable: creationEnabled,
321
+ createSeparators,
322
+ dropdownOptions,
323
+ handleBulkCreate,
324
+ handleDropdownSelect,
325
+ inputPropsOnKeyDown: inputProps === null || inputProps === void 0 ? void 0 : inputProps.onKeyDown,
326
+ inputRef,
327
+ mode,
328
+ onFocus,
329
+ open,
330
+ processBulkCreate,
331
+ searchText,
332
+ searchTextExistWithoutOption,
333
+ setActiveIndex,
334
+ setInsertText,
335
+ setListboxHasVisualFocus,
336
+ setSearchText,
337
+ toggleOpen,
338
+ value,
339
+ wrappedOnChange,
340
+ });
341
+ // Handle visibility change from Dropdown to prevent flickering
342
+ const handleVisibilityChange = useCallback((newOpen) => {
343
+ // Only update if state actually changed to prevent flickering
344
+ if (newOpen !== open) {
345
+ toggleOpen(newOpen);
346
+ }
347
+ }, [open, toggleOpen]);
348
+ const handlePasteWithFallback = useCallback((e) => {
349
+ var _a;
350
+ handlePaste(e);
351
+ (_a = inputProps === null || inputProps === void 0 ? void 0 : inputProps.onPaste) === null || _a === void 0 ? void 0 : _a.call(inputProps, e);
352
+ }, [handlePaste, inputProps]);
353
+ const autoSelectMatchingOption = useCallback((keyword) => {
354
+ if (!creationEnabled || !keyword.length)
355
+ return false;
356
+ const matchingOption = options.find((option) => option.name === keyword);
357
+ if (!matchingOption)
358
+ return false;
359
+ if (isSingle) {
360
+ if (!value) {
361
+ // Update searchText first to prevent showing old value
362
+ setSearchText(matchingOption.name);
363
+ setInsertText(matchingOption.name);
364
+ // Then update value and focus state
365
+ wrappedOnChange(matchingOption);
366
+ toggleOpen(false);
367
+ onFocus(false);
368
+ return true;
369
+ }
370
+ return false;
371
+ }
372
+ const alreadySelected = isOptionSelected(matchingOption, value, isMultiple);
373
+ if (!alreadySelected) {
374
+ wrappedOnChange(matchingOption);
375
+ resetCreationInputs();
376
+ return true;
377
+ }
378
+ return false;
379
+ }, [
380
+ creationEnabled,
381
+ isMultiple,
382
+ isSingle,
383
+ onFocus,
384
+ options,
385
+ resetCreationInputs,
386
+ setSearchText,
387
+ setInsertText,
388
+ toggleOpen,
389
+ value,
390
+ wrappedOnChange,
391
+ ]);
392
+ const resolvedInputProps = {
393
+ ...inputProps,
394
+ 'aria-activedescendant': ariaActivedescendant,
395
+ 'aria-controls': menuId,
396
+ 'aria-expanded': open,
397
+ 'aria-owns': menuId,
398
+ id: id !== null && id !== void 0 ? id : inputProps === null || inputProps === void 0 ? void 0 : inputProps.id,
399
+ name: name !== null && name !== void 0 ? name : inputProps === null || inputProps === void 0 ? void 0 : inputProps.name,
400
+ onBlur: onSearchInputBlur,
401
+ onChange: onSearchInputChange,
402
+ onFocus: onSearchInputFocus,
403
+ onKeyDown: handleInputKeyDown,
404
+ onPaste: handlePasteWithFallback,
405
+ readOnly: false,
406
+ role: 'combobox',
407
+ };
408
+ return (jsx(SelectControlContext.Provider, { value: context, children: jsx("div", { ref: nodeRef, className: cx(autocompleteClasses.host, {
409
+ [autocompleteClasses.hostFullWidth]: fullWidth,
410
+ [autocompleteClasses.hostInsideClosed]: inputPosition === 'inside' && !open,
411
+ }), children: jsx(Dropdown, { actionText: shouldShowCreateAction
412
+ ? (createActionText
413
+ ? createActionText(insertText)
414
+ : createActionTextTemplate.replace('{text}', insertText))
415
+ : undefined, activeIndex: activeIndex, disabled: isInputDisabled, emptyText: emptyText, followText: searchText, inputPosition: inputPosition, isMatchInputValue: true, listboxId: menuId, loadingText: loadingText, maxHeight: menuMaxHeight, mode: mode, onActionCustom: shouldShowCreateAction
416
+ ? handleActionCustom
417
+ : 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, zIndex: dropdownZIndex, onReachBottom: onReachBottom, onLeaveBottom: onLeaveBottom, children: jsx(SelectTrigger, { ref: composedRef, active: open, className: className, clearable: true, isForceClearable: 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: {
418
+ ...resolvedInputProps,
419
+ onClick: (e) => {
420
+ var _a;
421
+ // When inputPosition is inside, let Dropdown handle the click event
422
+ // Otherwise, stop propagation to prevent conflicts
423
+ if (inputPosition !== 'inside') {
424
+ e.stopPropagation();
425
+ }
426
+ (_a = resolvedInputProps.onClick) === null || _a === void 0 ? void 0 : _a.call(resolvedInputProps, e);
427
+ },
428
+ }, searchText: searchText, size: size, showTextInputAfterTags: true, suffixAction: onClickSuffixActionIcon, value: mode === 'multiple' && isMultipleValue(value) && value.length === 0
429
+ ? undefined
430
+ : value !== null && value !== void 0 ? value : undefined, ...(mode === 'single' && renderValue ? { renderValue } : {}) }) }) }) }));
431
+ });
432
+
433
+ 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 };