@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.
- package/Anchor/Anchor.d.ts +51 -18
- package/Anchor/Anchor.js +15 -15
- package/Anchor/AnchorGroup.d.ts +34 -0
- package/Anchor/AnchorGroup.js +37 -0
- package/Anchor/AnchorItem.d.ts +30 -0
- package/Anchor/AnchorItem.js +65 -0
- package/Anchor/index.d.ts +2 -0
- package/Anchor/index.js +1 -0
- package/Anchor/utils.d.ts +13 -0
- package/Anchor/utils.js +95 -0
- package/AutoComplete/AutoComplete.d.ts +217 -0
- package/AutoComplete/AutoComplete.js +433 -0
- package/AutoComplete/index.d.ts +2 -0
- package/AutoComplete/index.js +1 -0
- package/AutoComplete/useAutoCompleteCreation.d.ts +33 -0
- package/AutoComplete/useAutoCompleteCreation.js +201 -0
- package/AutoComplete/useAutoCompleteKeyboard.d.ts +31 -0
- package/AutoComplete/useAutoCompleteKeyboard.js +149 -0
- package/AutoComplete/useAutoCompleteSearch.d.ts +16 -0
- package/AutoComplete/useAutoCompleteSearch.js +69 -0
- package/AutoComplete/useCreationTracker.d.ts +17 -0
- package/AutoComplete/useCreationTracker.js +47 -0
- package/Breadcrumb/Breadcrumb.js +16 -21
- package/Breadcrumb/BreadcrumbDropdown.d.ts +11 -0
- package/Breadcrumb/BreadcrumbDropdown.js +22 -0
- package/Breadcrumb/BreadcrumbItem.d.ts +2 -3
- package/Breadcrumb/BreadcrumbItem.js +13 -31
- package/Breadcrumb/BreadcrumbOverflowMenu.d.ts +7 -0
- package/Breadcrumb/BreadcrumbOverflowMenu.js +77 -0
- package/Breadcrumb/BreadcrumbOverflowMenuDropdown.d.ts +11 -0
- package/Breadcrumb/BreadcrumbOverflowMenuDropdown.js +21 -0
- package/Breadcrumb/BreadcrumbOverflowMenuItem.d.ts +3 -0
- package/Breadcrumb/BreadcrumbOverflowMenuItem.js +27 -0
- package/Breadcrumb/typings.d.ts +21 -39
- package/Button/Button.js +13 -11
- package/Button/index.d.ts +1 -1
- package/Button/typings.d.ts +27 -4
- package/Checkbox/index.d.ts +4 -5
- package/Checkbox/index.js +1 -5
- package/ContentHeader/ContentHeader.d.ts +160 -0
- package/ContentHeader/ContentHeader.js +54 -0
- package/ContentHeader/index.d.ts +2 -0
- package/ContentHeader/index.js +1 -0
- package/ContentHeader/utils.d.ts +23 -0
- package/ContentHeader/utils.js +215 -0
- package/Description/Description.d.ts +12 -22
- package/Description/Description.js +4 -24
- package/Dropdown/Dropdown.d.ts +46 -1
- package/Dropdown/Dropdown.js +99 -14
- package/Dropdown/DropdownAction.d.ts +1 -1
- package/Dropdown/DropdownAction.js +1 -4
- package/Dropdown/DropdownItem.d.ts +28 -1
- package/Dropdown/DropdownItem.js +56 -14
- package/Dropdown/DropdownItemCard.d.ts +2 -2
- package/Dropdown/DropdownItemCard.js +20 -16
- package/Dropdown/DropdownStatus.js +29 -0
- package/Dropdown/dropdownKeydownHandler.d.ts +2 -1
- package/Dropdown/dropdownKeydownHandler.js +73 -0
- package/Dropdown/highlightText.js +5 -1
- package/Dropdown/shortcutTextHandler.d.ts +24 -0
- package/Dropdown/shortcutTextHandler.js +171 -0
- package/Empty/Empty.js +2 -1
- package/Empty/icons/EmptyMainNotificationIcon.d.ts +4 -0
- package/Empty/icons/EmptyMainNotificationIcon.js +9 -0
- package/Empty/typings.d.ts +2 -2
- package/FilterArea/Filter.d.ts +32 -0
- package/FilterArea/Filter.js +23 -0
- package/FilterArea/FilterArea.d.ts +58 -0
- package/FilterArea/FilterArea.js +31 -0
- package/FilterArea/FilterLine.d.ts +11 -0
- package/FilterArea/FilterLine.js +13 -0
- package/FilterArea/index.d.ts +6 -0
- package/FilterArea/index.js +3 -0
- package/Form/FormField.js +3 -1
- package/Input/Input.d.ts +35 -7
- package/Input/Input.js +48 -14
- package/Input/index.d.ts +1 -1
- package/Modal/MediaPreviewModal.d.ts +54 -0
- package/Modal/MediaPreviewModal.js +158 -0
- package/Modal/Modal.d.ts +103 -11
- package/Modal/Modal.js +14 -9
- package/Modal/ModalBodyForVerification.d.ts +59 -0
- package/Modal/ModalBodyForVerification.js +99 -0
- package/Modal/ModalControl.d.ts +2 -2
- package/Modal/ModalControl.js +1 -1
- package/Modal/ModalFooter.d.ts +119 -1
- package/Modal/ModalFooter.js +15 -3
- package/Modal/ModalHeader.d.ts +26 -7
- package/Modal/ModalHeader.js +33 -7
- package/Modal/index.d.ts +6 -5
- package/Modal/index.js +2 -2
- package/Modal/useModalContainer.d.ts +12 -3
- package/Modal/useModalContainer.js +28 -6
- package/Navigation/Navigation.d.ts +7 -2
- package/Navigation/Navigation.js +36 -35
- package/Navigation/NavigationHeader.d.ts +4 -0
- package/Navigation/NavigationHeader.js +3 -2
- package/Navigation/NavigationOption.d.ts +8 -3
- package/Navigation/NavigationOption.js +46 -11
- package/Navigation/NavigationOptionCategory.js +1 -0
- package/Navigation/NavigationOverflowMenu.d.ts +6 -0
- package/Navigation/NavigationOverflowMenu.js +90 -0
- package/Navigation/NavigationOverflowMenuOption.d.ts +7 -0
- package/Navigation/NavigationOverflowMenuOption.js +68 -0
- package/Navigation/NavigationUserMenu.d.ts +4 -2
- package/Navigation/NavigationUserMenu.js +13 -5
- package/Navigation/context.d.ts +3 -2
- package/Navigation/useVisibleItems.d.ts +5 -0
- package/Navigation/useVisibleItems.js +54 -0
- package/NotificationCenter/NotificationCenter.d.ts +124 -0
- package/NotificationCenter/NotificationCenter.js +279 -0
- package/NotificationCenter/NotificationCenterDrawer.d.ts +109 -0
- package/NotificationCenter/index.d.ts +3 -0
- package/NotificationCenter/index.js +1 -0
- package/PageFooter/PageFooter.d.ts +19 -9
- package/PageFooter/PageFooter.js +10 -10
- package/PageHeader/PageHeader.d.ts +32 -25
- package/PageHeader/PageHeader.js +49 -43
- package/ResultState/ResultState.d.ts +9 -0
- package/ResultState/ResultState.js +36 -4
- package/Scrollbar/Scrollbar.d.ts +9 -0
- package/Scrollbar/Scrollbar.js +78 -0
- package/Scrollbar/index.d.ts +2 -0
- package/Scrollbar/index.js +1 -0
- package/Scrollbar/typings.d.ts +47 -0
- package/Select/SelectTrigger.js +5 -4
- package/Select/index.d.ts +0 -2
- package/Select/index.js +0 -1
- package/Select/typings.d.ts +6 -1
- package/Selection/Selection.js +1 -1
- package/Selection/SelectionGroup.d.ts +28 -0
- package/Slider/useSlider.js +1 -1
- package/Table/Table.d.ts +2 -120
- package/Table/Table.js +148 -53
- package/Table/TableContext.d.ts +11 -12
- package/Table/components/TableActionsCell.js +12 -4
- package/Table/components/TableBody.js +2 -1
- package/Table/components/TableBulkActions.js +1 -19
- package/Table/components/TableColGroup.d.ts +1 -4
- package/Table/components/TableColGroup.js +15 -16
- package/Table/components/TableCollectableCell.d.ts +17 -0
- package/Table/components/TableCollectableCell.js +54 -0
- package/Table/components/TableDragOrPinHandleCell.d.ts +20 -0
- package/Table/components/TableDragOrPinHandleCell.js +58 -0
- package/Table/components/TableExpandedRow.js +11 -2
- package/Table/components/TableHeader.js +12 -10
- package/Table/components/TableRow.js +38 -13
- package/Table/components/TableSelectionCell.js +1 -1
- package/Table/components/TableToggleableCell.d.ts +16 -0
- package/Table/components/TableToggleableCell.js +51 -0
- package/Table/components/index.d.ts +4 -1
- package/Table/components/index.js +3 -0
- package/Table/hooks/typings.d.ts +18 -4
- package/Table/hooks/useTableExpansion.d.ts +2 -2
- package/Table/hooks/useTableExpansion.js +5 -5
- package/Table/hooks/useTableFixedOffsets.d.ts +6 -2
- package/Table/hooks/useTableFixedOffsets.js +60 -26
- package/Table/hooks/useTableScroll.d.ts +9 -3
- package/Table/hooks/useTableScroll.js +34 -7
- package/Table/hooks/useTableVirtualization.d.ts +2 -1
- package/Table/hooks/useTableVirtualization.js +2 -8
- package/Table/index.d.ts +4 -3
- package/Table/index.js +3 -0
- package/Table/typings.d.ts +172 -0
- package/Table/utils/useTableRowSelection.js +13 -5
- package/Tag/TagGroup.d.ts +3 -0
- package/Tag/index.d.ts +2 -0
- package/Tag/index.js +1 -0
- package/Transition/Slide.d.ts +9 -2
- package/Transition/Slide.js +7 -4
- package/Tree/TreeNode.js +1 -1
- package/Upload/UploadPictureCard.js +1 -1
- package/index.d.ts +37 -21
- package/index.js +25 -11
- package/package.json +6 -4
- package/Modal/ModalActions.d.ts +0 -9
- package/Modal/ModalActions.js +0 -20
- package/Modal/ModalBody.d.ts +0 -7
- package/Modal/ModalBody.js +0 -14
- package/Notification/Notification.d.ts +0 -54
- package/Notification/Notification.js +0 -76
- package/Notification/index.d.ts +0 -3
- package/Notification/index.js +0 -1
- package/PageToolbar/PageToolbar.d.ts +0 -114
- package/PageToolbar/PageToolbar.js +0 -23
- package/PageToolbar/index.d.ts +0 -2
- package/PageToolbar/index.js +0 -1
- package/PageToolbar/utils.d.ts +0 -23
- package/PageToolbar/utils.js +0 -165
- package/Select/AutoComplete.d.ts +0 -107
- package/Select/AutoComplete.js +0 -114
- package/Table/components/TableDragHandleCell.d.ts +0 -11
- 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 @@
|
|
|
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 };
|