@ringcentral/spring-ui 1.12.0 → 1.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Accordion.js +90 -34
- package/AccordionHeader.js +71 -21
- package/Alert.js +120 -28
- package/Animation.js +2 -46
- package/Announcement.js +46 -35
- package/Autocomplete.js +1011 -583
- package/Avatar.js +134 -73
- package/AvatarGroup.js +50 -7
- package/Backdrop.js +45 -7
- package/Badge.js +77 -15
- package/Block.js +34 -12
- package/BlockHeader.js +57 -15
- package/BottomNavigation.js +181 -65
- package/BottomNavigationItem.js +93 -30
- package/Button.js +160 -24
- package/CallButton.js +53 -31
- package/Checkbox.js +88 -26
- package/Chip.js +126 -26
- package/CircularProgressIndicator.js +80 -8
- package/ClickAwayListener.js +4 -1
- package/ComponentsProps.js +1 -0
- package/DatePicker.js +611 -192
- package/DateUtilsContext.js +43 -25
- package/Dialer.js +564 -253
- package/Dialog.js +99 -45
- package/Divider.js +43 -7
- package/DragHandle.js +32 -6
- package/Drawer.js +117 -19
- package/Dropdown.js +154 -77
- package/EmptyState.js +65 -15
- package/FabButton.js +51 -17
- package/FocusTrap.js +109 -12
- package/FormField.js +108 -12
- package/FormLabel.js +81 -7
- package/Icon.js +1 -8
- package/IconButton.js +142 -26
- package/IconButtonGroup.js +35 -12
- package/InlineEditable.js +98 -44
- package/LinearProgressIndicator.js +73 -24
- package/Link.js +40 -11
- package/List.js +54 -9
- package/ListItem.js +152 -23
- package/ListItemText.js +44 -17
- package/ListNavigation.js +272 -20
- package/Menu.js +139 -23
- package/MenuActions.js +37 -12
- package/MenuDivider.js +34 -8
- package/MenuFooter.js +36 -12
- package/MenuHeader.js +54 -16
- package/MenuItem.js +402 -35
- package/MenuItemText.js +51 -9
- package/MenuList.js +87 -36
- package/Modal.js +345 -16
- package/NestedMenuContext.js +183 -11
- package/Option.js +32 -36
- package/Overlap.js +54 -7
- package/PageHeader.js +62 -24
- package/PickerTextField.js +156 -35
- package/Popover.js +262 -21
- package/Popper.js +112 -62
- package/PopperPaper.js +54 -8
- package/Portal.js +6 -1
- package/Radio.js +65 -21
- package/RadioGroup.js +56 -17
- package/Rating.js +223 -87
- package/Select.js +387 -44
- package/SingleFilter.js +148 -79
- package/Skeleton.js +68 -17
- package/Slider.js +167 -69
- package/Snackbar.js +122 -59
- package/SortableList.js +32 -12
- package/SortableListDndContext.js +69 -22
- package/SortableListItem.js +92 -47
- package/SortableVirtualizedList.js +44 -20
- package/SplitButton.js +124 -89
- package/Squircle.js +4 -7
- package/StatusIndicator.js +51 -9
- package/Step.js +63 -38
- package/StepButton.js +51 -23
- package/StepConnector.js +48 -8
- package/StepContent.js +67 -24
- package/StepIcon.js +90 -9
- package/StepLabel.js +90 -14
- package/Stepper.js +65 -23
- package/SubMenu.js +203 -101
- package/SwipeableDrawer.js +439 -194
- package/Switch.js +70 -23
- package/Tab.js +120 -57
- package/TabContext.js +59 -15
- package/TabPanel.js +40 -13
- package/Table.js +60 -17
- package/TableBody.js +42 -12
- package/TableCell.js +84 -50
- package/TableContainer.js +32 -10
- package/TableFooter.js +38 -10
- package/TableHead.js +49 -15
- package/TablePagination.js +153 -79
- package/TableRow.js +60 -21
- package/Tabs.js +592 -271
- package/Tag.js +51 -16
- package/Text.js +68 -37
- package/TextField.js +209 -31
- package/Textarea.js +51 -42
- package/TimePicker.js +490 -232
- package/Tooltip.js +48 -23
- package/Tray.js +237 -13
- package/TrayMenu.js +62 -43
- package/VirtualizedList.js +205 -107
- package/VirtualizedMenu.js +359 -29
- package/VisuallyHidden.js +29 -6
- package/chunks/1250.js +98 -0
- package/chunks/1294.js +126 -0
- package/chunks/2956.js +18 -0
- package/chunks/4476.js +460 -0
- package/chunks/4491.js +10 -0
- package/chunks/4535.js +364 -0
- package/chunks/5047.js +3 -0
- package/chunks/6335.js +24 -0
- package/chunks/7279.js +10 -0
- package/chunks/8011.js +8 -0
- package/chunks/8307.js +56 -0
- package/chunks/8467.js +8 -0
- package/chunks/8809.js +8 -0
- package/chunks/{RadioGroup.context.js → 9551.js} +12 -6
- package/chunks/987.js +8 -0
- package/index.css +2436 -2261
- package/index.js +245 -195
- package/internal/utils/setRef.d.ts +2 -1
- package/package.json +4 -4
- package/src/Animation/hooks/useMotionSafeTransition/useMotionSafeTransition.d.ts +2 -0
- package/src/Autocomplete/SuggestionList/SuggestionList.d.ts +1 -1
- package/src/Button/Button.classes.d.ts +1 -1
- package/src/ComponentsProps.d.ts +111 -110
- package/src/DatePicker/DatePickerDataAttributes.d.ts +11 -0
- package/src/DatePicker/utils/DatePickerUtils.d.ts +1 -1
- package/src/MenuItem/MenuItem.type.d.ts +1 -0
- package/src/MenuList/MenuListContext.d.ts +2 -3
- package/src/Modal/Modal.type.d.ts +1 -0
- package/src/Popover/Popover.classes.d.ts +1 -1
- package/src/Popover/Popover.internal.d.ts +8 -0
- package/src/Popover/Popover.type.d.ts +1 -0
- package/src/Select/Select.type.d.ts +6 -4
- package/src/Table/Table.type.d.ts +3 -0
- package/src/Table/TableContext.d.ts +3 -0
- package/src/Table/TableRowContext.d.ts +7 -0
- package/src/Table/index.d.ts +1 -0
- package/src/TableCell/TableCell.classes.d.ts +1 -1
- package/src/TableFooter/TableFooter.classes.d.ts +1 -1
- package/src/TableFooter/TableFooter.type.d.ts +1 -0
- package/src/TableHead/TableHead.classes.d.ts +1 -1
- package/src/TableHead/TableHead.type.d.ts +1 -0
- package/src/TableRow/TableRow.type.d.ts +2 -0
- package/chunks/AccordionContext.js +0 -6
- package/chunks/Alert.js +0 -48
- package/chunks/AvatarGroup.js +0 -22
- package/chunks/Backdrop.js +0 -18
- package/chunks/Badge.js +0 -32
- package/chunks/BottomNavigationItem.js +0 -36
- package/chunks/Button.js +0 -88
- package/chunks/Chip.js +0 -73
- package/chunks/CircularProgressIndicator.js +0 -36
- package/chunks/Divider.js +0 -19
- package/chunks/DragHandle.js +0 -11
- package/chunks/Drawer.js +0 -33
- package/chunks/ExpandCollapseCaret.js +0 -24
- package/chunks/FormLabel.js +0 -27
- package/chunks/Icon.js +0 -25
- package/chunks/IconButtonLabel.js +0 -68
- package/chunks/List.js +0 -22
- package/chunks/ListItem.js +0 -83
- package/chunks/Menu.js +0 -61
- package/chunks/MenuDivider.js +0 -14
- package/chunks/MenuItemText.js +0 -18
- package/chunks/MenuList.js +0 -47
- package/chunks/Modal.js +0 -211
- package/chunks/Overlap.js +0 -25
- package/chunks/PickerTextField.js +0 -85
- package/chunks/Popover.js +0 -61
- package/chunks/PopperPaper.js +0 -15
- package/chunks/Select.js +0 -225
- package/chunks/ShowHideEye.js +0 -56
- package/chunks/SlideIn.js +0 -20
- package/chunks/SortableArrow.js +0 -73
- package/chunks/Squircle.js +0 -31
- package/chunks/StatusIndicator.js +0 -20
- package/chunks/StepConnector.js +0 -21
- package/chunks/StepIcon.js +0 -43
- package/chunks/StepLabel.js +0 -29
- package/chunks/StepperContext.js +0 -9
- package/chunks/TableContext.js +0 -6
- package/chunks/Tablelvl2Context.js +0 -6
- package/chunks/TabsContext.js +0 -6
- package/chunks/TextField.js +0 -110
- package/chunks/Tooltip.js +0 -217
- package/chunks/VirtualizedMenu.js +0 -162
- package/chunks/VisuallyHidden.js +0 -12
- package/chunks/composeEventHandlers.js +0 -10
- package/chunks/getFade.js +0 -27
- package/chunks/getGrow.js +0 -70
- package/chunks/getSlideIn.js +0 -30
- package/chunks/hasContent.js +0 -3
- package/chunks/useAnimationFrame.js +0 -15
- package/chunks/useFocusTrapStack.js +0 -38
- package/chunks/useHelperTextId.js +0 -24
- package/chunks/useListNavigation.js +0 -199
- package/chunks/useMenuItemSelect.js +0 -282
- package/chunks/useMotionSafeTransition.js +0 -9
- package/chunks/useNestedMenuItem.js +0 -27
- package/chunks/useNestedParentMenu.js +0 -113
- package/chunks/usePrefersReducedMotion.js +0 -36
- package/chunks/useTooltipForceHide.js +0 -27
- package/chunks/useTrayNavigate.js +0 -150
- package/chunks/withTooltip.js +0 -15
package/Autocomplete.js
CHANGED
|
@@ -1,80 +1,119 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { forwardRef,
|
|
3
|
-
import clsx from
|
|
4
|
-
import { Virtuoso } from
|
|
5
|
-
import {
|
|
6
|
-
import { useClassesWithMode, useThemeProps } from
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
const SuggestionListItem = forwardRef((props, ref) => {
|
|
1
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { forwardRef, useCallback, useEffect, useImperativeHandle, useLayoutEffect, useMemo, useRef, useState } from "react";
|
|
3
|
+
import clsx from "clsx";
|
|
4
|
+
import { Virtuoso } from "react-virtuoso";
|
|
5
|
+
import { combineProps, getSelectionPosition, logInDev, useChange, useClasses, useControlled, useDepsChange, useEventCallback, useForceUpdate, useForkRef, useId, useKeyboardMoveFocus, usePrevious, useRefState, useRetry, useSleep, useVirtuosoExternalWindow } from "@ringcentral/spring-base";
|
|
6
|
+
import { useClassesWithMode, useThemeProps } from "@ringcentral/spring-theme";
|
|
7
|
+
import { AnimatePresence, motion } from "framer-motion";
|
|
8
|
+
import { Xsm } from "@ringcentral/spring-icon";
|
|
9
|
+
import uniqueId from "lodash/uniqueId";
|
|
10
|
+
import { getFade } from "./chunks/4476.js";
|
|
11
|
+
import { IconButton as IconButton_IconButton } from "./IconButton.js";
|
|
12
|
+
import { useAnimationFrame } from "./chunks/6335.js";
|
|
13
|
+
import { Chip as Chip_Chip } from "./Chip.js";
|
|
14
|
+
import { Popper } from "./Popper.js";
|
|
15
|
+
import { TextField as TextField_TextField } from "./TextField.js";
|
|
16
|
+
import { usePrefersReducedMotion, ExpandCollapseCaret as ExpandCollapseCaret_ExpandCollapseCaret } from "./chunks/1294.js";
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
// extracted by css-extract-rspack-plugin
|
|
25
|
+
/* export default */ var SuggestionList = ({"sui-suggestion-list-root":"sui-suggestion-list-root","sui-suggestion-list-item":"sui-suggestion-list-item","sui-suggestion-list-readonly":"sui-suggestion-list-readonly","sui-disabled":"sui-disabled","sui-focus-visible":"sui-focus-visible","sui-suggestion-list-selected":"sui-suggestion-list-selected","sui-suggestion-list-highlighted":"sui-suggestion-list-highlighted","sui-suggestion-list-group-title-readonly":"sui-suggestion-list-group-title-readonly","sui-suggestion-list-expand-icon":"sui-suggestion-list-expand-icon","sui-compact":"sui-compact"});
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
const SuggestionListItem = /*#__PURE__*/ forwardRef((props, ref)=>{
|
|
37
35
|
const { id, label, children, disabled, isGroupTitle = false, isTitleMode = false, highlighted = false, selected = false, expanded = false, expandIconProps, classes, className, role, ...rest } = props;
|
|
38
|
-
const cls = useClassesWithMode('suggestion-list', classes, className, [
|
|
36
|
+
const cls = useClassesWithMode('suggestion-list', classes, className, [
|
|
37
|
+
'item'
|
|
38
|
+
]);
|
|
39
39
|
const readonly = isGroupTitle && isTitleMode;
|
|
40
40
|
const ariaDisabled = role !== undefined && disabled ? true : undefined;
|
|
41
|
-
return
|
|
41
|
+
return /*#__PURE__*/ jsxs("div", {
|
|
42
|
+
"aria-disabled": ariaDisabled,
|
|
43
|
+
role: role,
|
|
44
|
+
...rest,
|
|
45
|
+
id: id,
|
|
46
|
+
className: cls('item', {
|
|
42
47
|
'group-title': isGroupTitle,
|
|
43
48
|
readonly,
|
|
44
49
|
'group-title-readonly': readonly,
|
|
45
50
|
highlighted: highlighted,
|
|
46
51
|
selected: selected,
|
|
47
|
-
disabled
|
|
48
|
-
}),
|
|
52
|
+
disabled
|
|
53
|
+
}),
|
|
54
|
+
children: [
|
|
55
|
+
label || children,
|
|
56
|
+
expandIconProps && /*#__PURE__*/ jsx(IconButton_IconButton, {
|
|
57
|
+
"aria-hidden": "true",
|
|
58
|
+
role: "presentation",
|
|
59
|
+
tabIndex: -1,
|
|
60
|
+
component: "div",
|
|
61
|
+
shape: 'squircle',
|
|
62
|
+
background: false,
|
|
63
|
+
color: 'secondary',
|
|
64
|
+
variant: "contained",
|
|
65
|
+
size: "small",
|
|
66
|
+
...expandIconProps,
|
|
67
|
+
children: /*#__PURE__*/ jsx(ExpandCollapseCaret_ExpandCollapseCaret, {
|
|
68
|
+
expanded: expanded
|
|
69
|
+
})
|
|
70
|
+
})
|
|
71
|
+
]
|
|
72
|
+
});
|
|
49
73
|
});
|
|
50
74
|
|
|
51
|
-
|
|
75
|
+
/**
|
|
76
|
+
* Check if an option is the first option in its group.
|
|
77
|
+
*
|
|
78
|
+
* Note: Whether this option is actually a "group title" depends on the `groupVariant`:
|
|
79
|
+
* - In `'normal'` mode: the first option is a dedicated title item (disabled, non-selectable)
|
|
80
|
+
* - In `'expanded'` mode: the first option is a regular data item (clickable, selectable)
|
|
81
|
+
*
|
|
82
|
+
* @param option - The option to check
|
|
83
|
+
* @returns true if the option is the first in its group, false otherwise
|
|
84
|
+
*/ const isFirstInGroup = (option)=>{
|
|
52
85
|
return option === option?.group?.options[0];
|
|
53
86
|
};
|
|
54
87
|
|
|
55
|
-
|
|
56
|
-
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* auto calculate container height
|
|
94
|
+
*/ const useDynamicHeight = ({ itemCount, maxContainerHeight, onContainerHeightChange })=>{
|
|
95
|
+
const prevItemCount = usePrevious(()=>itemCount);
|
|
57
96
|
const fullHeight = maxContainerHeight === '100%';
|
|
58
97
|
const maxContainerSize = fullHeight ? -1 : maxContainerHeight;
|
|
59
98
|
const containerHeighRef = useRef(maxContainerSize);
|
|
60
99
|
if (!fullHeight) {
|
|
100
|
+
// * when prev item count is zero, and current is not zero, set hight for vl to trigger totalListHeightChanged to re-change height
|
|
61
101
|
if (prevItemCount === 0 && itemCount !== 0) {
|
|
62
102
|
containerHeighRef.current = maxContainerSize;
|
|
63
|
-
}
|
|
64
|
-
|
|
103
|
+
} else if (itemCount === 0) {
|
|
104
|
+
// * when item count is zero always height to zero, when zero init
|
|
65
105
|
containerHeighRef.current = 0;
|
|
66
106
|
}
|
|
67
107
|
}
|
|
68
108
|
return {
|
|
69
109
|
containerHeighRef,
|
|
70
|
-
totalListHeightChanged: (height)
|
|
110
|
+
totalListHeightChanged: (height)=>{
|
|
71
111
|
if (!fullHeight) {
|
|
72
112
|
const containerHeigh = containerHeighRef.current;
|
|
73
113
|
let changeHeight = -1;
|
|
74
114
|
if (height < containerHeigh) {
|
|
75
115
|
changeHeight = height;
|
|
76
|
-
}
|
|
77
|
-
else if (containerHeigh !== maxContainerSize) {
|
|
116
|
+
} else if (containerHeigh !== maxContainerSize) {
|
|
78
117
|
changeHeight = Math.min(height, maxContainerSize);
|
|
79
118
|
}
|
|
80
119
|
if (changeHeight !== -1) {
|
|
@@ -84,59 +123,76 @@ const useDynamicHeight = ({ itemCount, maxContainerHeight, onContainerHeightChan
|
|
|
84
123
|
}
|
|
85
124
|
},
|
|
86
125
|
style: {
|
|
87
|
-
height: fullHeight ? '100%' : containerHeighRef.current
|
|
88
|
-
}
|
|
126
|
+
height: fullHeight ? '100%' : containerHeighRef.current
|
|
127
|
+
}
|
|
89
128
|
};
|
|
90
129
|
};
|
|
91
130
|
|
|
92
|
-
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* scroll into current highlight index
|
|
135
|
+
*/ function useHighlightScroll({ virtualize = true, containerHeighRef, scrollToIndex }) {
|
|
93
136
|
const renderedItemsRef = useRef([]);
|
|
94
137
|
const scrollerRef = useRef();
|
|
95
|
-
const scrollToHighlightedIndex = (prevHighlightedIndex, currHighlightedIndex, topHighlightIndex = 0)
|
|
138
|
+
const scrollToHighlightedIndex = (prevHighlightedIndex, currHighlightedIndex, topHighlightIndex = 0)=>{
|
|
139
|
+
// Reset scroll position when list needs re-initialization (prevHighlightedIndex === -1)
|
|
140
|
+
// This occurs when: menu reopens, input changes, options update, etc.
|
|
141
|
+
// Required because AnimatePresence preserves DOM elements, keeping old scrollTop
|
|
96
142
|
const needsScrollReset = prevHighlightedIndex === -1;
|
|
143
|
+
// Handle case when scroller element is not yet mounted
|
|
97
144
|
if (!scrollerRef.current) {
|
|
145
|
+
// Delegate to scrollToIndex if not at top position
|
|
146
|
+
// The retry mechanism will handle timing until scroller is ready
|
|
98
147
|
if (currHighlightedIndex !== topHighlightIndex) {
|
|
99
148
|
scrollToIndex({
|
|
100
149
|
index: currHighlightedIndex,
|
|
101
|
-
align: 'start'
|
|
150
|
+
align: 'start'
|
|
102
151
|
});
|
|
103
152
|
}
|
|
104
153
|
return;
|
|
105
154
|
}
|
|
106
155
|
const scrollElm = scrollerRef.current;
|
|
156
|
+
// Non-virtualized mode: use simplified scroll logic
|
|
107
157
|
if (!virtualize) {
|
|
158
|
+
// Reset scroll position when opening at the first item
|
|
108
159
|
if (needsScrollReset && currHighlightedIndex === topHighlightIndex) {
|
|
109
160
|
scrollElm.scrollTop = 0;
|
|
110
161
|
return;
|
|
111
162
|
}
|
|
163
|
+
// Delegate to scrollToIndex for all other cases
|
|
164
|
+
// scrollToIndex will check if item is visible and only scroll if necessary
|
|
112
165
|
scrollToIndex({
|
|
113
166
|
index: currHighlightedIndex,
|
|
114
|
-
align: 'start'
|
|
167
|
+
align: 'start'
|
|
115
168
|
});
|
|
116
169
|
return;
|
|
117
170
|
}
|
|
171
|
+
// Virtualized mode: calculate visibility using rendered item positions
|
|
118
172
|
const renderedItems = renderedItemsRef.current;
|
|
119
|
-
const lastHighlightedIndex = needsScrollReset
|
|
120
|
-
? currHighlightedIndex
|
|
121
|
-
: prevHighlightedIndex;
|
|
173
|
+
const lastHighlightedIndex = needsScrollReset ? currHighlightedIndex : prevHighlightedIndex;
|
|
122
174
|
const isBackward = lastHighlightedIndex > currHighlightedIndex;
|
|
123
175
|
const isForward = lastHighlightedIndex < currHighlightedIndex;
|
|
176
|
+
// Calculate visible viewport boundaries
|
|
124
177
|
const currentVisibleOffset = {
|
|
125
178
|
start: scrollElm.scrollTop,
|
|
126
|
-
stop: scrollElm.scrollTop + containerHeighRef.current
|
|
179
|
+
stop: scrollElm.scrollTop + containerHeighRef.current
|
|
127
180
|
};
|
|
128
|
-
|
|
181
|
+
// Check if target item is within the currently rendered range
|
|
182
|
+
const isInRenderedRange = renderedItems.some((x)=>x.index === currHighlightedIndex);
|
|
129
183
|
if (!isInRenderedRange) {
|
|
130
184
|
scrollToIndex({
|
|
131
185
|
index: currHighlightedIndex,
|
|
132
|
-
align: isBackward ? 'start' : 'end'
|
|
186
|
+
align: isBackward ? 'start' : 'end'
|
|
133
187
|
});
|
|
134
188
|
return;
|
|
135
189
|
}
|
|
190
|
+
// Handle backward navigation (scrolling up)
|
|
136
191
|
if (isBackward) {
|
|
137
192
|
let firstShouldScrollIndex = currHighlightedIndex;
|
|
138
|
-
for
|
|
193
|
+
for(let i = renderedItems.length - 1; i >= 0; i--){
|
|
139
194
|
const item = renderedItems[i];
|
|
195
|
+
// Stop when item top is above visible area
|
|
140
196
|
if (item.offset < currentVisibleOffset.start) {
|
|
141
197
|
break;
|
|
142
198
|
}
|
|
@@ -145,15 +201,16 @@ function useHighlightScroll({ virtualize = true, containerHeighRef, scrollToInde
|
|
|
145
201
|
if (currHighlightedIndex < firstShouldScrollIndex) {
|
|
146
202
|
scrollToIndex({
|
|
147
203
|
index: currHighlightedIndex,
|
|
148
|
-
align: 'start'
|
|
204
|
+
align: 'start'
|
|
149
205
|
});
|
|
150
206
|
}
|
|
151
|
-
|
|
152
|
-
else if (isForward) {
|
|
207
|
+
// Handle forward navigation (scrolling down)
|
|
208
|
+
} else if (isForward) {
|
|
153
209
|
let lastShouldScrollIndex = currHighlightedIndex;
|
|
154
|
-
for
|
|
210
|
+
for(let i = 0; i < renderedItems.length; i++){
|
|
155
211
|
const item = renderedItems[i];
|
|
156
212
|
const itemBottom = item.offset + item.size;
|
|
213
|
+
// Stop when item bottom extends beyond visible area
|
|
157
214
|
if (itemBottom >= currentVisibleOffset.stop) {
|
|
158
215
|
break;
|
|
159
216
|
}
|
|
@@ -162,23 +219,27 @@ function useHighlightScroll({ virtualize = true, containerHeighRef, scrollToInde
|
|
|
162
219
|
if (currHighlightedIndex > lastShouldScrollIndex) {
|
|
163
220
|
scrollToIndex({
|
|
164
221
|
index: currHighlightedIndex,
|
|
165
|
-
align: 'end'
|
|
222
|
+
align: 'end'
|
|
166
223
|
});
|
|
167
224
|
}
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
const currentItem = renderedItems.find((item)
|
|
225
|
+
} else {
|
|
226
|
+
// Handle no direction change
|
|
227
|
+
const currentItem = renderedItems.find((item)=>item.index === currHighlightedIndex);
|
|
171
228
|
if (needsScrollReset) {
|
|
229
|
+
// When list needs re-initialization (menu reopens, input changes, etc.)
|
|
172
230
|
if (currHighlightedIndex === topHighlightIndex) {
|
|
231
|
+
// At top position: reset scrollTop and skip scroll
|
|
232
|
+
// This prevents unnecessary scroll when menu opens at the default position
|
|
173
233
|
if (scrollerRef.current) {
|
|
174
234
|
scrollerRef.current.scrollTop = 0;
|
|
175
235
|
}
|
|
176
236
|
return;
|
|
177
|
-
}
|
|
178
|
-
|
|
237
|
+
} else {
|
|
238
|
+
// Not at top position: let scrollToIndex handle the scroll position
|
|
239
|
+
// No need to reset scrollTop as scrollToIndex will set the correct position
|
|
179
240
|
scrollToIndex({
|
|
180
241
|
index: currHighlightedIndex,
|
|
181
|
-
align: 'start'
|
|
242
|
+
align: 'start'
|
|
182
243
|
});
|
|
183
244
|
return;
|
|
184
245
|
}
|
|
@@ -186,10 +247,11 @@ function useHighlightScroll({ virtualize = true, containerHeighRef, scrollToInde
|
|
|
186
247
|
if (!currentItem) {
|
|
187
248
|
scrollToIndex({
|
|
188
249
|
index: currHighlightedIndex,
|
|
189
|
-
align: 'start'
|
|
250
|
+
align: 'start'
|
|
190
251
|
});
|
|
191
252
|
return;
|
|
192
253
|
}
|
|
254
|
+
// Verify item is fully visible within viewport
|
|
193
255
|
const itemBottom = currentItem.offset + currentItem.size;
|
|
194
256
|
const isTopVisible = currentItem.offset >= currentVisibleOffset.start;
|
|
195
257
|
const isBottomVisible = itemBottom < currentVisibleOffset.stop;
|
|
@@ -197,23 +259,40 @@ function useHighlightScroll({ virtualize = true, containerHeighRef, scrollToInde
|
|
|
197
259
|
if (!isFullyVisible) {
|
|
198
260
|
scrollToIndex({
|
|
199
261
|
index: currHighlightedIndex,
|
|
200
|
-
align: 'start'
|
|
262
|
+
align: 'start'
|
|
201
263
|
});
|
|
202
264
|
}
|
|
203
265
|
}
|
|
204
266
|
};
|
|
205
267
|
return {
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
268
|
+
/**
|
|
269
|
+
* scroller reference
|
|
270
|
+
*/ scrollerRef,
|
|
271
|
+
/**
|
|
272
|
+
* Scroll to target highlighted index
|
|
273
|
+
* @param prevHighlightedIndex previous highlightedIndex
|
|
274
|
+
* @param currHighlightedIndex current highlightedIndex
|
|
275
|
+
* @param topHighlightIndex top highlighIndex for check should scroll when init
|
|
276
|
+
*/ scrollToHighlightedIndex,
|
|
277
|
+
/**
|
|
278
|
+
* set to virtuoso `scrollerRef` prop, auto bind inner `scrollerRef`
|
|
279
|
+
*/ scrollerRefFn: (elm)=>scrollerRef.current = elm,
|
|
280
|
+
/**
|
|
281
|
+
* set to virtuoso `itemsRendered` prop, auto bind inner `renderedItemsRef`
|
|
282
|
+
*/ itemsRendered: (e)=>renderedItemsRef.current = e
|
|
210
283
|
};
|
|
211
284
|
}
|
|
212
285
|
|
|
213
|
-
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
const useAutocompleteGroup = ({ groupBy, options, filteredResult, getExpandIconProps, groupExpanded, groupDefaultExpanded, onGroupExpanded, groupVariant, getOptionDisabled, id })=>{
|
|
214
293
|
const [groupExpandedMapRef, setGroupExpandedMap] = useRefState({});
|
|
215
294
|
const isTitleGroup = groupVariant === 'normal';
|
|
216
|
-
const handleGroupExpandedChange = (groupName)
|
|
295
|
+
const handleGroupExpandedChange = (groupName)=>{
|
|
217
296
|
const groupExpandedMap = groupExpandedMapRef.current;
|
|
218
297
|
if (typeof groupExpanded === 'boolean') {
|
|
219
298
|
return;
|
|
@@ -221,40 +300,39 @@ const useAutocompleteGroup = ({ groupBy, options, filteredResult, getExpandIconP
|
|
|
221
300
|
const toExpandedState = !groupExpandedMap[groupName];
|
|
222
301
|
const newExpandedMap = {
|
|
223
302
|
...groupExpandedMap,
|
|
224
|
-
[groupName]: toExpandedState
|
|
303
|
+
[groupName]: toExpandedState
|
|
225
304
|
};
|
|
226
305
|
setGroupExpandedMap(newExpandedMap);
|
|
227
|
-
if (!onGroupExpanded)
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
if (!group)
|
|
231
|
-
return;
|
|
306
|
+
if (!onGroupExpanded) return;
|
|
307
|
+
const group = optionsGroupList.find((x)=>x.group === groupName);
|
|
308
|
+
if (!group) return;
|
|
232
309
|
const { getExpandIconProps, ...restGroupProps } = group;
|
|
233
310
|
onGroupExpanded({
|
|
234
311
|
...restGroupProps,
|
|
235
|
-
expanded: toExpandedState
|
|
312
|
+
expanded: toExpandedState
|
|
236
313
|
}, newExpandedMap);
|
|
237
314
|
};
|
|
238
|
-
const handleExpandIconClick = useEventCallback((e, groupName)
|
|
315
|
+
const handleExpandIconClick = useEventCallback((e, groupName)=>{
|
|
239
316
|
handleGroupExpandedChange(groupName);
|
|
240
317
|
e.preventDefault();
|
|
241
318
|
e.stopPropagation();
|
|
242
319
|
});
|
|
243
|
-
const optionsGroupList = useMemo(()
|
|
244
|
-
if (!groupBy || !options) {
|
|
320
|
+
const optionsGroupList = useMemo(()=>{
|
|
321
|
+
/** key with this group count */ if (!groupBy || !options) {
|
|
245
322
|
return [];
|
|
246
323
|
}
|
|
324
|
+
// used to keep track of key and indexes in the result array
|
|
247
325
|
const indexBy = new Map();
|
|
248
326
|
let warn = false;
|
|
249
327
|
const afterGroupFn = [];
|
|
250
|
-
const result = filteredResult.reduce((acc, option, index)
|
|
328
|
+
const result = filteredResult.reduce((acc, option, index)=>{
|
|
251
329
|
const group = groupBy(option);
|
|
252
330
|
if (acc.length > 0 && acc[acc.length - 1].group === group) {
|
|
253
331
|
const currGroup = acc[acc.length - 1];
|
|
254
332
|
acc[acc.length - 1].options.push({
|
|
255
333
|
...option,
|
|
256
334
|
group: currGroup,
|
|
257
|
-
indexInOwnGroup: currGroup.options.length - 1
|
|
335
|
+
indexInOwnGroup: currGroup.options.length - 1
|
|
258
336
|
});
|
|
259
337
|
return acc;
|
|
260
338
|
}
|
|
@@ -263,7 +341,7 @@ const useAutocompleteGroup = ({ groupBy, options, filteredResult, getExpandIconP
|
|
|
263
341
|
logInDev({
|
|
264
342
|
scope: 'useAutocomplete',
|
|
265
343
|
message: `Material-UI: The options provided combined with the \`groupBy\` method returns duplicated headers.
|
|
266
|
-
You can solve the issue by sorting the options with the output of \`groupBy
|
|
344
|
+
You can solve the issue by sorting the options with the output of \`groupBy\``
|
|
267
345
|
});
|
|
268
346
|
warn = true;
|
|
269
347
|
}
|
|
@@ -274,22 +352,25 @@ const useAutocompleteGroup = ({ groupBy, options, filteredResult, getExpandIconP
|
|
|
274
352
|
index,
|
|
275
353
|
group,
|
|
276
354
|
order: acc.length,
|
|
277
|
-
options: [
|
|
355
|
+
options: [
|
|
356
|
+
{
|
|
357
|
+
...option,
|
|
358
|
+
indexInOwnGroup: 0
|
|
359
|
+
}
|
|
360
|
+
],
|
|
278
361
|
expanded: false,
|
|
279
|
-
getExpandIconProps: (additionExpandIconProps)
|
|
280
|
-
return newGroup.options.length > 1
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
: undefined;
|
|
292
|
-
},
|
|
362
|
+
getExpandIconProps: (additionExpandIconProps)=>{
|
|
363
|
+
return newGroup.options.length > 1 ? combineProps({
|
|
364
|
+
onClick: (e)=>{
|
|
365
|
+
handleExpandIconClick(e, group);
|
|
366
|
+
},
|
|
367
|
+
onMouseDown: (e)=>{
|
|
368
|
+
e.preventDefault();
|
|
369
|
+
e.stopPropagation();
|
|
370
|
+
},
|
|
371
|
+
tabIndex: -1
|
|
372
|
+
}, combineProps(additionExpandIconProps, getExpandIconProps?.(newGroup))) : undefined;
|
|
373
|
+
}
|
|
293
374
|
};
|
|
294
375
|
newGroup.options[0].group = newGroup;
|
|
295
376
|
if (isTitleGroup) {
|
|
@@ -297,10 +378,13 @@ const useAutocompleteGroup = ({ groupBy, options, filteredResult, getExpandIconP
|
|
|
297
378
|
id: `${id}-${group}`,
|
|
298
379
|
label: group,
|
|
299
380
|
disabled: true,
|
|
300
|
-
|
|
381
|
+
// * here cause by self Type reference
|
|
382
|
+
// oxlint-disable-next-line @typescript-eslint/no-explicit-any
|
|
383
|
+
group: newGroup
|
|
301
384
|
};
|
|
385
|
+
// push fn to array, do that after all options be add into group
|
|
302
386
|
if (getOptionDisabled) {
|
|
303
|
-
afterGroupFn.push(()
|
|
387
|
+
afterGroupFn.push(()=>{
|
|
304
388
|
groupTitleOption.disabled = getOptionDisabled(groupTitleOption);
|
|
305
389
|
});
|
|
306
390
|
}
|
|
@@ -309,7 +393,7 @@ const useAutocompleteGroup = ({ groupBy, options, filteredResult, getExpandIconP
|
|
|
309
393
|
acc.push(newGroup);
|
|
310
394
|
return acc;
|
|
311
395
|
}, []);
|
|
312
|
-
afterGroupFn.forEach((fn)
|
|
396
|
+
afterGroupFn.forEach((fn)=>fn());
|
|
313
397
|
return result;
|
|
314
398
|
}, [
|
|
315
399
|
filteredResult,
|
|
@@ -319,92 +403,112 @@ const useAutocompleteGroup = ({ groupBy, options, filteredResult, getExpandIconP
|
|
|
319
403
|
handleExpandIconClick,
|
|
320
404
|
id,
|
|
321
405
|
isTitleGroup,
|
|
322
|
-
options
|
|
406
|
+
options
|
|
323
407
|
]);
|
|
324
|
-
const initGroupExpandedState = (expandedState)
|
|
408
|
+
const initGroupExpandedState = (expandedState)=>{
|
|
325
409
|
if (typeof expandedState === 'object') {
|
|
326
410
|
setGroupExpandedMap(expandedState, false);
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
setGroupExpandedMap(optionsGroupList.reduce((prev, curr) => {
|
|
411
|
+
} else if (typeof expandedState === 'boolean') {
|
|
412
|
+
setGroupExpandedMap(optionsGroupList.reduce((prev, curr)=>{
|
|
330
413
|
prev[curr.group] = expandedState;
|
|
331
414
|
return prev;
|
|
332
415
|
}, {}), false);
|
|
333
416
|
}
|
|
334
417
|
};
|
|
335
|
-
useDepsChange(()
|
|
418
|
+
useDepsChange(()=>{
|
|
336
419
|
initGroupExpandedState(groupDefaultExpanded);
|
|
337
420
|
}, []);
|
|
338
|
-
useDepsChange(()
|
|
421
|
+
useDepsChange(()=>{
|
|
339
422
|
initGroupExpandedState(groupExpanded);
|
|
340
|
-
}, [
|
|
423
|
+
}, [
|
|
424
|
+
groupExpanded
|
|
425
|
+
]);
|
|
341
426
|
const groupExpandedMap = groupExpandedMapRef.current;
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
427
|
+
// * speared logic with group, prevent calculate every time.
|
|
428
|
+
const groupedResult = useMemo(()=>{
|
|
429
|
+
const addExpandedResult = Object.entries(groupExpandedMap).reduce((prev, [key, expended])=>{
|
|
430
|
+
const group = prev.find((x)=>x.group === key);
|
|
431
|
+
if (group) group.expanded = expended;
|
|
347
432
|
return prev;
|
|
348
|
-
}, [
|
|
349
|
-
|
|
433
|
+
}, [
|
|
434
|
+
...optionsGroupList
|
|
435
|
+
]);
|
|
436
|
+
return addExpandedResult.reduce((prev, curr)=>{
|
|
350
437
|
if (curr.expanded) {
|
|
351
438
|
prev.push(...curr.options);
|
|
352
|
-
}
|
|
353
|
-
else {
|
|
439
|
+
} else {
|
|
354
440
|
prev.push(curr.options[0]);
|
|
355
441
|
}
|
|
356
442
|
return prev;
|
|
357
443
|
}, []);
|
|
358
|
-
}, [
|
|
444
|
+
}, [
|
|
445
|
+
groupExpandedMap,
|
|
446
|
+
optionsGroupList
|
|
447
|
+
]);
|
|
359
448
|
return {
|
|
360
449
|
optionsGroupList,
|
|
361
450
|
groupedResult,
|
|
362
451
|
groupExpandedMap,
|
|
363
|
-
handleGroupExpandedChange
|
|
452
|
+
handleGroupExpandedChange
|
|
364
453
|
};
|
|
365
454
|
};
|
|
366
455
|
|
|
367
|
-
|
|
368
|
-
|
|
456
|
+
|
|
457
|
+
|
|
458
|
+
|
|
459
|
+
|
|
460
|
+
const DEFAULT_HIGHLIGHTED_INDEX = -1;
|
|
461
|
+
const isItemCanSelected = (item)=>{
|
|
369
462
|
return Boolean(item && !item.disabled);
|
|
370
463
|
};
|
|
371
|
-
const toOptionIdString = (id)
|
|
372
|
-
const DEFAULT_GET_OPTION_LABEL = (item)
|
|
373
|
-
|
|
374
|
-
|
|
464
|
+
const toOptionIdString = (id)=>typeof id === 'number' ? String(id) : id;
|
|
465
|
+
const DEFAULT_GET_OPTION_LABEL = (item)=>item?.label || '';
|
|
466
|
+
/**
|
|
467
|
+
* provide suggestion list with search function and expandable group with virtualized list
|
|
468
|
+
*/ /** Default equality check using strict equality */ const defaultIsOptionEqualToValue = (option, value)=>option === value;
|
|
469
|
+
const useSuggestionList = ({ id, inputValue: inputValueProp, getOptionLabel = DEFAULT_GET_OPTION_LABEL, filterOptions, disabledItemsHighlightable, options, onInputChange: onInputChangeProp, inputRef, onKeyDown: onKeyDownProp, onSelect, onClear, getOptionDisabled, groupBy, onGroupExpanded, groupVariant, groupExpanded, groupDefaultExpanded, getExpandIconProps, processFilteredResult, isOptionEqualToValue = defaultIsOptionEqualToValue })=>{
|
|
375
470
|
const isTitleMode = groupVariant === 'normal';
|
|
376
471
|
const [inputValue, setInputValue] = useControlled({
|
|
377
472
|
controlled: inputValueProp,
|
|
378
473
|
default: '',
|
|
379
|
-
name: 'useSuggestionList'
|
|
474
|
+
name: 'useSuggestionList'
|
|
380
475
|
});
|
|
381
|
-
const updateInputValue = (newValue)
|
|
476
|
+
const updateInputValue = (newValue)=>{
|
|
382
477
|
onInputChangeProp?.(newValue);
|
|
383
478
|
setInputValue(newValue);
|
|
384
479
|
};
|
|
385
|
-
const focusInput = ()
|
|
480
|
+
const focusInput = ()=>inputRef.current?.focus();
|
|
386
481
|
const currUniqueId = useId(id || 'suggestion-list', !id);
|
|
387
|
-
const highlightedIndexRef = useRef(DEFAULT_HIGHLIGHTED_INDEX
|
|
482
|
+
const highlightedIndexRef = useRef(DEFAULT_HIGHLIGHTED_INDEX);
|
|
388
483
|
const changeHighlightedIndexReasonRef = useRef();
|
|
389
484
|
const forceUpdate = useForceUpdate();
|
|
390
|
-
const getFilteredItems = useCallback((items)
|
|
485
|
+
const getFilteredItems = useCallback((items)=>{
|
|
391
486
|
if (filterOptions) {
|
|
392
487
|
return filterOptions(items, {
|
|
393
488
|
inputValue,
|
|
394
489
|
getOptionLabel,
|
|
395
490
|
selectedItems: [],
|
|
396
|
-
isOptionEqualToValue
|
|
491
|
+
isOptionEqualToValue
|
|
397
492
|
});
|
|
398
493
|
}
|
|
399
494
|
return items;
|
|
400
|
-
}, [
|
|
401
|
-
|
|
495
|
+
}, [
|
|
496
|
+
filterOptions,
|
|
497
|
+
getOptionLabel,
|
|
498
|
+
inputValue,
|
|
499
|
+
isOptionEqualToValue
|
|
500
|
+
]);
|
|
501
|
+
const filteredResult = useMemo(()=>{
|
|
502
|
+
// * only when isOpen calculate the filtered result
|
|
402
503
|
let results = getFilteredItems(options);
|
|
403
|
-
results = processFilteredResult
|
|
404
|
-
? processFilteredResult(results, inputValue)
|
|
405
|
-
: results;
|
|
504
|
+
results = processFilteredResult ? processFilteredResult(results, inputValue) : results;
|
|
406
505
|
return results;
|
|
407
|
-
}, [
|
|
506
|
+
}, [
|
|
507
|
+
getFilteredItems,
|
|
508
|
+
inputValue,
|
|
509
|
+
options,
|
|
510
|
+
processFilteredResult
|
|
511
|
+
]);
|
|
408
512
|
const { groupedResult, handleGroupExpandedChange, optionsGroupList } = useAutocompleteGroup({
|
|
409
513
|
id: currUniqueId,
|
|
410
514
|
options,
|
|
@@ -415,61 +519,61 @@ const useSuggestionList = ({ id, inputValue: inputValueProp, getOptionLabel = DE
|
|
|
415
519
|
groupBy,
|
|
416
520
|
getExpandIconProps,
|
|
417
521
|
onGroupExpanded,
|
|
418
|
-
getOptionDisabled
|
|
522
|
+
getOptionDisabled
|
|
419
523
|
});
|
|
420
524
|
const optionItems = groupBy ? groupedResult : filteredResult;
|
|
421
|
-
const setHighlightedIndex = (index, { reRender = false, reason
|
|
525
|
+
const setHighlightedIndex = (index, { reRender = false, reason })=>{
|
|
422
526
|
changeHighlightedIndexReasonRef.current = reason;
|
|
423
527
|
if (highlightedIndexRef.current !== index) {
|
|
424
528
|
highlightedIndexRef.current = index;
|
|
425
|
-
if (reRender)
|
|
426
|
-
forceUpdate();
|
|
529
|
+
if (reRender) forceUpdate();
|
|
427
530
|
}
|
|
428
531
|
};
|
|
429
|
-
const handleInputChange = (newValue)
|
|
532
|
+
const handleInputChange = (newValue)=>{
|
|
430
533
|
if (inputValue !== newValue) {
|
|
431
|
-
setHighlightedIndex(DEFAULT_HIGHLIGHTED_INDEX
|
|
534
|
+
setHighlightedIndex(DEFAULT_HIGHLIGHTED_INDEX, {
|
|
535
|
+
reason: 'auto'
|
|
536
|
+
});
|
|
432
537
|
updateInputValue(newValue);
|
|
433
538
|
}
|
|
434
539
|
return false;
|
|
435
540
|
};
|
|
436
|
-
const getIsItemCanSelected = (item)
|
|
437
|
-
return
|
|
438
|
-
(isItemCanSelected(item) && !getOptionDisabled?.(item)));
|
|
541
|
+
const getIsItemCanSelected = (item)=>{
|
|
542
|
+
return !!item && item.freeSolo || isItemCanSelected(item) && !getOptionDisabled?.(item);
|
|
439
543
|
};
|
|
440
|
-
const selectItem = (e, selectedItem)
|
|
544
|
+
const selectItem = (e, selectedItem)=>{
|
|
441
545
|
if (getIsItemCanSelected(selectedItem)) {
|
|
442
546
|
onSelect?.(e, selectedItem);
|
|
443
547
|
return true;
|
|
444
548
|
}
|
|
445
549
|
return false;
|
|
446
550
|
};
|
|
447
|
-
const clearInput = ()
|
|
551
|
+
const clearInput = ()=>{
|
|
448
552
|
if (inputRef.current && inputRef.current.value.length > 0) {
|
|
449
553
|
updateInputValue('');
|
|
450
554
|
}
|
|
451
555
|
};
|
|
452
|
-
const reset = (isFocus)
|
|
556
|
+
const reset = (isFocus)=>{
|
|
453
557
|
clearInput();
|
|
454
558
|
onInputChangeProp?.('');
|
|
455
|
-
if (isFocus)
|
|
456
|
-
focusInput();
|
|
559
|
+
if (isFocus) focusInput();
|
|
457
560
|
};
|
|
458
561
|
const { onKeyFocusedIndexHandle, getNextFocusableOption } = useKeyboardMoveFocus({
|
|
459
562
|
options: optionItems,
|
|
460
563
|
focusedIndexRef: highlightedIndexRef,
|
|
461
564
|
infinite: true,
|
|
462
|
-
onFocusedIndexChange: (event, toIndex)
|
|
463
|
-
setHighlightedIndex(toIndex, {
|
|
565
|
+
onFocusedIndexChange: (event, toIndex)=>{
|
|
566
|
+
setHighlightedIndex(toIndex, {
|
|
567
|
+
reason: 'keyboard',
|
|
568
|
+
reRender: true
|
|
569
|
+
});
|
|
464
570
|
event?.preventDefault();
|
|
465
571
|
},
|
|
466
|
-
getOptionDisabled: disabledItemsHighlightable
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
return !getIsItemCanSelected(child);
|
|
470
|
-
},
|
|
572
|
+
getOptionDisabled: disabledItemsHighlightable ? undefined : (child)=>{
|
|
573
|
+
return !getIsItemCanSelected(child);
|
|
574
|
+
}
|
|
471
575
|
});
|
|
472
|
-
const getItemProps = ({ item, index = 0
|
|
576
|
+
const getItemProps = ({ item, index = 0 })=>{
|
|
473
577
|
const currGroup = item.group;
|
|
474
578
|
const isItemFirstInGroup = isFirstInGroup(item);
|
|
475
579
|
const itemCount = optionItems.length;
|
|
@@ -477,75 +581,76 @@ const useSuggestionList = ({ id, inputValue: inputValueProp, getOptionLabel = DE
|
|
|
477
581
|
const stringId = toOptionIdString(item.id);
|
|
478
582
|
return {
|
|
479
583
|
id: stringId ?? `${currUniqueId}-option-${index}`,
|
|
480
|
-
onClick: (e)
|
|
584
|
+
onClick: (e)=>{
|
|
481
585
|
selectItem(e, item);
|
|
482
586
|
},
|
|
483
|
-
onMouseOver: ()
|
|
484
|
-
if (highlightedIndexRef.current !== index &&
|
|
485
|
-
|
|
486
|
-
|
|
587
|
+
onMouseOver: ()=>{
|
|
588
|
+
if (highlightedIndexRef.current !== index && getIsItemCanSelected(item)) {
|
|
589
|
+
setHighlightedIndex(index, {
|
|
590
|
+
reason: 'mouse',
|
|
591
|
+
reRender: true
|
|
592
|
+
});
|
|
487
593
|
}
|
|
488
594
|
},
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
:
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
}),
|
|
595
|
+
// When title mode and is title, skip that posinset
|
|
596
|
+
...isTitleMode && isItemFirstInGroup ? {} : {
|
|
597
|
+
role: 'option',
|
|
598
|
+
'aria-setsize': itemCount - (isTitleMode ? optionsGroupList?.length || 0 : 0),
|
|
599
|
+
'aria-posinset': index + 1 - (isTitleMode ? groupOrder + 1 : 0)
|
|
600
|
+
}
|
|
496
601
|
};
|
|
497
602
|
};
|
|
498
|
-
const handleF10KeyDown = (e)
|
|
603
|
+
const handleF10KeyDown = (e)=>{
|
|
499
604
|
const highlightedIndex = highlightedIndexRef.current;
|
|
500
|
-
if (!e.shiftKey)
|
|
501
|
-
return;
|
|
605
|
+
if (!e.shiftKey) return;
|
|
502
606
|
const currOption = optionItems[highlightedIndex];
|
|
503
607
|
const currentGroup = currOption.group;
|
|
504
608
|
if (currentGroup && currentGroup.options.length > 1) {
|
|
505
609
|
handleGroupExpandedChange(currentGroup.group);
|
|
506
610
|
}
|
|
507
611
|
};
|
|
508
|
-
const getInputProps = (props)
|
|
612
|
+
const getInputProps = (props)=>{
|
|
509
613
|
return combineProps({
|
|
510
614
|
id: `${currUniqueId}-input`,
|
|
511
615
|
autoComplete: 'off',
|
|
512
|
-
onChange: (e)
|
|
616
|
+
onChange: (e)=>{
|
|
513
617
|
const changeValue = e.target.value;
|
|
514
618
|
handleInputChange(changeValue);
|
|
515
619
|
},
|
|
516
|
-
onKeyDown: (e)
|
|
620
|
+
onKeyDown: (e)=>{
|
|
517
621
|
onKeyDownProp?.(e, highlightedIndexRef.current);
|
|
518
|
-
switch
|
|
622
|
+
switch(e.key){
|
|
519
623
|
case 'F10':
|
|
520
624
|
handleF10KeyDown(e);
|
|
521
625
|
break;
|
|
522
|
-
case 'Enter':
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
return;
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
626
|
+
case 'Enter':
|
|
627
|
+
{
|
|
628
|
+
const highlightedIndex = highlightedIndexRef.current;
|
|
629
|
+
if (e.which === 229) return;
|
|
630
|
+
if (highlightedIndex !== DEFAULT_HIGHLIGHTED_INDEX) {
|
|
631
|
+
const currOption = optionItems[highlightedIndex];
|
|
632
|
+
const isCurrOptionFirstInGroup = isFirstInGroup(currOption);
|
|
633
|
+
if (isCurrOptionFirstInGroup) {
|
|
634
|
+
// oxlint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
635
|
+
const { onClick } = currOption.group.getExpandIconProps();
|
|
636
|
+
onClick?.(e);
|
|
637
|
+
} else {
|
|
638
|
+
selectItem(e, currOption);
|
|
639
|
+
}
|
|
640
|
+
e.stopPropagation();
|
|
535
641
|
}
|
|
536
|
-
|
|
642
|
+
// always preventDefault for not inset all enter into textarea
|
|
643
|
+
e.preventDefault();
|
|
644
|
+
break;
|
|
537
645
|
}
|
|
538
|
-
e.preventDefault();
|
|
539
|
-
break;
|
|
540
|
-
}
|
|
541
646
|
default:
|
|
542
647
|
onKeyFocusedIndexHandle(e);
|
|
543
648
|
break;
|
|
544
649
|
}
|
|
545
|
-
}
|
|
650
|
+
}
|
|
546
651
|
}, props);
|
|
547
652
|
};
|
|
548
|
-
const getInputAriaProps = (props)
|
|
653
|
+
const getInputAriaProps = (props)=>{
|
|
549
654
|
return combineProps({
|
|
550
655
|
role: 'combobox',
|
|
551
656
|
'aria-autocomplete': 'list',
|
|
@@ -553,39 +658,38 @@ const useSuggestionList = ({ id, inputValue: inputValueProp, getOptionLabel = DE
|
|
|
553
658
|
'aria-haspopup': true,
|
|
554
659
|
'aria-controls': `${currUniqueId}-menu`,
|
|
555
660
|
'aria-owns': `${currUniqueId}-menu`,
|
|
556
|
-
'aria-activedescendant': (()
|
|
661
|
+
'aria-activedescendant': (()=>{
|
|
557
662
|
const index = highlightedIndexRef.current;
|
|
558
|
-
if (index < 0)
|
|
559
|
-
return undefined;
|
|
663
|
+
if (index < 0) return undefined;
|
|
560
664
|
const option = optionItems[index];
|
|
561
665
|
const optionId = toOptionIdString(option?.id);
|
|
562
666
|
return optionId ?? `${currUniqueId}-option-${index}`;
|
|
563
|
-
})()
|
|
667
|
+
})()
|
|
564
668
|
}, props);
|
|
565
669
|
};
|
|
566
|
-
const getLabelProps = (props)
|
|
670
|
+
const getLabelProps = (props)=>{
|
|
567
671
|
return combineProps({
|
|
568
672
|
htmlFor: `${currUniqueId}-input`,
|
|
569
|
-
id: `${currUniqueId}-label
|
|
673
|
+
id: `${currUniqueId}-label`
|
|
570
674
|
}, props);
|
|
571
675
|
};
|
|
572
|
-
const getSuggestionListProps = (restMenuProps)
|
|
676
|
+
const getSuggestionListProps = (restMenuProps)=>{
|
|
573
677
|
return combineProps({
|
|
574
678
|
'aria-labelledby': `${currUniqueId}-label`,
|
|
575
679
|
id: `${currUniqueId}-menu`,
|
|
576
|
-
role: 'listbox'
|
|
680
|
+
role: 'listbox'
|
|
577
681
|
}, restMenuProps);
|
|
578
682
|
};
|
|
579
|
-
const getClearButtonProps = (props)
|
|
683
|
+
const getClearButtonProps = (props)=>{
|
|
580
684
|
return combineProps({
|
|
581
685
|
id: `${currUniqueId}-clear-button`,
|
|
582
|
-
onClick: (e)
|
|
686
|
+
onClick: (e)=>{
|
|
583
687
|
onClear?.(e);
|
|
584
688
|
reset(true);
|
|
585
|
-
}
|
|
689
|
+
}
|
|
586
690
|
}, props);
|
|
587
691
|
};
|
|
588
|
-
useEffect(()
|
|
692
|
+
useEffect(()=>{
|
|
589
693
|
changeHighlightedIndexReasonRef.current = undefined;
|
|
590
694
|
});
|
|
591
695
|
return {
|
|
@@ -611,41 +715,75 @@ const useSuggestionList = ({ id, inputValue: inputValueProp, getOptionLabel = DE
|
|
|
611
715
|
changeHighlightedIndexReasonRef,
|
|
612
716
|
changeHighlightedIndexReason: changeHighlightedIndexReasonRef.current,
|
|
613
717
|
optionsGroupList,
|
|
614
|
-
id: currUniqueId,
|
|
718
|
+
/** current unique id */ id: currUniqueId,
|
|
615
719
|
getNextFocusableOption,
|
|
616
|
-
handleF10KeyDown
|
|
720
|
+
handleF10KeyDown
|
|
617
721
|
};
|
|
618
722
|
};
|
|
619
723
|
|
|
724
|
+
|
|
725
|
+
|
|
726
|
+
|
|
727
|
+
|
|
728
|
+
|
|
729
|
+
|
|
730
|
+
|
|
731
|
+
|
|
732
|
+
|
|
733
|
+
|
|
734
|
+
|
|
735
|
+
|
|
736
|
+
|
|
737
|
+
|
|
738
|
+
|
|
739
|
+
|
|
620
740
|
const DEFAULT_PADDING = 8;
|
|
621
|
-
|
|
741
|
+
/**
|
|
742
|
+
* Calculates the padding value from the padding prop.
|
|
743
|
+
* Note: padding will never be undefined due to default parameter (padding = DEFAULT_PADDING).
|
|
744
|
+
* @param padding - padding prop value (boolean | number)
|
|
745
|
+
* @param itemCount - number of items in the list
|
|
746
|
+
* @returns padding value as a number representing pixels (>= 0)
|
|
747
|
+
*/ const getPaddingValue = (padding, itemCount)=>{
|
|
622
748
|
if (itemCount === 0) {
|
|
623
749
|
return 0;
|
|
624
750
|
}
|
|
625
751
|
if (padding === false) {
|
|
626
|
-
return 0;
|
|
752
|
+
return 0; // explicitly disabled
|
|
627
753
|
}
|
|
628
754
|
if (padding === true) {
|
|
629
|
-
return DEFAULT_PADDING;
|
|
755
|
+
return DEFAULT_PADDING; // default enabled
|
|
630
756
|
}
|
|
757
|
+
// padding is a number (including default value when not passed)
|
|
758
|
+
// Clamp to 0 to handle negative values
|
|
631
759
|
if (typeof padding === 'number') {
|
|
632
760
|
return Math.max(0, padding);
|
|
633
761
|
}
|
|
762
|
+
// Fallback (should never reach here due to default parameter)
|
|
634
763
|
return DEFAULT_PADDING;
|
|
635
764
|
};
|
|
636
|
-
const List = forwardRef((props, ref)
|
|
765
|
+
const List = /*#__PURE__*/ forwardRef((props, ref)=>{
|
|
637
766
|
const { className, context, style, ...restProps } = props;
|
|
638
767
|
const listRef = context?.listRef;
|
|
639
768
|
const rootClass = context?.rootClass;
|
|
640
769
|
const paddingValue = context?.paddingValue ?? 0;
|
|
641
770
|
const forkRef = useForkRef(ref, listRef);
|
|
642
|
-
return
|
|
771
|
+
return /*#__PURE__*/ jsx("div", {
|
|
772
|
+
ref: forkRef,
|
|
773
|
+
...restProps,
|
|
774
|
+
className: clsx(className, rootClass),
|
|
775
|
+
"data-suggestion-list": true,
|
|
776
|
+
style: {
|
|
643
777
|
...style,
|
|
644
|
-
['--sui-suggestion-list-vertical-padding']: `${paddingValue}px
|
|
645
|
-
}
|
|
778
|
+
['--sui-suggestion-list-vertical-padding']: `${paddingValue}px`
|
|
779
|
+
}
|
|
780
|
+
});
|
|
646
781
|
});
|
|
647
|
-
const
|
|
648
|
-
const props = useThemeProps({
|
|
782
|
+
const SuggestionList_SuggestionList = /*#__PURE__*/ forwardRef((inProps, ref)=>{
|
|
783
|
+
const props = useThemeProps({
|
|
784
|
+
props: inProps,
|
|
785
|
+
name: 'SuggestionList'
|
|
786
|
+
});
|
|
649
787
|
const { highlightedIndex, prevHighlightedIndex, options, getItemProps, virtualize = true, renderOption, value: selectedOptions, inputValue, groupVariant, groupExpanded, renderGroup, optionsGroupList, getOptionDisabled, getOptionSelected, changeHighlightedIndexReason, isKeepHighlightedIndex, components: componentsProp, onContainerHeightChange, getOptionLabel, padding = DEFAULT_PADDING, maxContainerHeight = '100%', className: classNameProp, classes: classesProp, getSuggestionListProps, ...rest } = props;
|
|
650
788
|
const vlRef = useRef(null);
|
|
651
789
|
const forkVlRef = useForkRef(ref, vlRef);
|
|
@@ -654,146 +792,158 @@ const SuggestionList = forwardRef((inProps, ref) => {
|
|
|
654
792
|
const itemData = options;
|
|
655
793
|
const itemCount = options.length;
|
|
656
794
|
const externalWindowProps = useVirtuosoExternalWindow();
|
|
657
|
-
|
|
795
|
+
// Track previous highlightedIndex internally for backward compatibility
|
|
796
|
+
// Using isDefault: true ensures consistent behavior across render cycles
|
|
797
|
+
const internalPrevHighlightedIndex = usePrevious(()=>highlightedIndex, true);
|
|
798
|
+
// Use parent-provided value if available, otherwise fall back to internal tracking
|
|
658
799
|
const actualPrevHighlightedIndex = prevHighlightedIndex ?? internalPrevHighlightedIndex;
|
|
659
800
|
const cls = useClassesWithMode('suggestion-list', classesProp, classNameProp);
|
|
660
801
|
const paddingValue = getPaddingValue(padding, itemCount);
|
|
802
|
+
// Track whether items have been rendered to ensure accurate height calculations
|
|
661
803
|
const hasItemsRenderedRef = useRef(false);
|
|
662
804
|
const { totalListHeightChanged, style, containerHeighRef } = useDynamicHeight({
|
|
663
805
|
itemCount,
|
|
664
806
|
maxContainerHeight,
|
|
665
|
-
onContainerHeightChange: (changeHeight)
|
|
807
|
+
onContainerHeightChange: (changeHeight)=>{
|
|
666
808
|
const scroller = scrollerRef.current;
|
|
667
809
|
if (scroller?.style) {
|
|
668
810
|
scroller.style.height = `${changeHeight}px`;
|
|
669
811
|
onContainerHeightChange?.();
|
|
670
812
|
}
|
|
671
|
-
}
|
|
813
|
+
}
|
|
672
814
|
});
|
|
673
|
-
|
|
815
|
+
/**
|
|
816
|
+
* Adjusts Virtuoso's reported height to include padding from :before/:after pseudo-elements.
|
|
817
|
+
* Prevents reporting height changes before items are rendered to ensure accurate height calculation.
|
|
818
|
+
*/ const handleTotalListHeightChanged = (height)=>{
|
|
674
819
|
const adjustedHeight = paddingValue > 0 ? height + paddingValue * 2 : height;
|
|
675
|
-
if (!hasItemsRenderedRef.current &&
|
|
676
|
-
adjustedHeight < containerHeighRef.current) {
|
|
820
|
+
if (!hasItemsRenderedRef.current && adjustedHeight < containerHeighRef.current) {
|
|
677
821
|
return;
|
|
678
822
|
}
|
|
679
823
|
totalListHeightChanged(adjustedHeight);
|
|
680
824
|
};
|
|
681
825
|
const { sleep } = useSleep();
|
|
682
|
-
const { retry: scrollToIndexWithRetry } = useRetry(async (location)
|
|
826
|
+
const { retry: scrollToIndexWithRetry } = useRetry(async (location)=>{
|
|
683
827
|
const toIndex = location.index;
|
|
684
828
|
if (!virtualize) {
|
|
685
829
|
const scroller = listRef.current;
|
|
686
830
|
const needsScrollReset = prevHighlightedIndex === -1;
|
|
687
831
|
const isLastItem = itemCount > 1 && toIndex === itemCount - 1;
|
|
832
|
+
// Reveal bottom padding when scrolling to last item after scroll reset
|
|
688
833
|
if (paddingValue > 0 && needsScrollReset && isLastItem && scroller) {
|
|
689
834
|
scroller.scrollTop = scroller.scrollHeight;
|
|
690
835
|
return true;
|
|
691
836
|
}
|
|
837
|
+
// Use custom scrollTo logic to avoid affecting page scroll
|
|
692
838
|
const toElm = scroller?.querySelector(`[data-item-index="${toIndex}"]`);
|
|
693
839
|
if (!toElm || !scroller) {
|
|
694
840
|
return false;
|
|
695
841
|
}
|
|
842
|
+
// Calculate element position relative to scroller
|
|
696
843
|
const scrollerRect = scroller.getBoundingClientRect();
|
|
697
844
|
const elementRect = toElm.getBoundingClientRect();
|
|
698
845
|
const scrollerTop = scrollerRect.top;
|
|
699
846
|
const scrollerBottom = scrollerRect.bottom;
|
|
700
847
|
const elementTop = elementRect.top;
|
|
701
848
|
const elementBottom = elementRect.bottom;
|
|
849
|
+
// Check if element is fully visible
|
|
702
850
|
const isFullyVisible = elementTop >= scrollerTop && elementBottom <= scrollerBottom;
|
|
703
851
|
if (!isFullyVisible) {
|
|
852
|
+
// Scroll element into view using scrollTo (affects only the scroller)
|
|
704
853
|
let scrollOffset = 0;
|
|
705
854
|
if (elementTop < scrollerTop) {
|
|
855
|
+
// Element is above viewport - align to start
|
|
706
856
|
scrollOffset = toElm.offsetTop - paddingValue;
|
|
707
|
-
}
|
|
708
|
-
|
|
709
|
-
scrollOffset =
|
|
710
|
-
toElm.offsetTop -
|
|
711
|
-
scroller.clientHeight +
|
|
712
|
-
toElm.offsetHeight +
|
|
713
|
-
paddingValue;
|
|
857
|
+
} else if (elementBottom > scrollerBottom) {
|
|
858
|
+
// Element is below viewport - align to end
|
|
859
|
+
scrollOffset = toElm.offsetTop - scroller.clientHeight + toElm.offsetHeight + paddingValue;
|
|
714
860
|
}
|
|
715
861
|
scroller.scrollTo({
|
|
716
862
|
top: scrollOffset,
|
|
717
|
-
behavior: 'auto'
|
|
863
|
+
behavior: 'auto'
|
|
718
864
|
});
|
|
719
865
|
}
|
|
720
866
|
return true;
|
|
721
867
|
}
|
|
722
|
-
|
|
723
|
-
|
|
868
|
+
// Apply padding offset to ensure full item visibility in virtualized mode
|
|
869
|
+
if (paddingValue > 0 && (location.align === 'start' || location.align === 'end')) {
|
|
724
870
|
location = {
|
|
725
871
|
...location,
|
|
726
|
-
offset: paddingValue
|
|
872
|
+
offset: paddingValue
|
|
727
873
|
};
|
|
728
874
|
}
|
|
729
875
|
vlRef.current?.scrollToIndex(location);
|
|
730
876
|
await sleep(0);
|
|
877
|
+
// Verify the item has been rendered
|
|
731
878
|
const toElm = listRef.current?.querySelector(`[data-item-index="${toIndex}"]`);
|
|
732
879
|
return !!toElm;
|
|
733
880
|
}, {
|
|
734
881
|
retryTimes: 10,
|
|
735
|
-
intervalTime: 20
|
|
882
|
+
intervalTime: 20
|
|
736
883
|
});
|
|
737
|
-
const { scrollerRef, scrollerRefFn, itemsRendered: itemsRenderedFromHook, scrollToHighlightedIndex
|
|
884
|
+
const { scrollerRef, scrollerRefFn, itemsRendered: itemsRenderedFromHook, scrollToHighlightedIndex } = useHighlightScroll({
|
|
738
885
|
virtualize,
|
|
739
886
|
containerHeighRef,
|
|
740
|
-
scrollToIndex: scrollToIndexWithRetry
|
|
887
|
+
scrollToIndex: scrollToIndexWithRetry
|
|
741
888
|
});
|
|
742
|
-
|
|
889
|
+
/**
|
|
890
|
+
* Wraps itemsRendered callback to track when items have been rendered.
|
|
891
|
+
*/ const handleItemsRendered = (items)=>{
|
|
743
892
|
if (items.length > 0 || itemCount === 0) {
|
|
744
893
|
hasItemsRenderedRef.current = true;
|
|
745
894
|
}
|
|
746
895
|
itemsRenderedFromHook(items);
|
|
747
896
|
};
|
|
748
|
-
|
|
897
|
+
// Reset flag when itemCount changes to non-zero
|
|
898
|
+
useEffect(()=>{
|
|
749
899
|
if (itemCount > 0) {
|
|
750
900
|
hasItemsRenderedRef.current = false;
|
|
751
901
|
}
|
|
752
|
-
}, [
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
902
|
+
}, [
|
|
903
|
+
itemCount
|
|
904
|
+
]);
|
|
905
|
+
useLayoutEffect(()=>{
|
|
906
|
+
const shouldScroll = (!virtualize || vlRef.current) && !isKeepHighlightedIndex && // Only scroll when reason is 'keyboard'
|
|
907
|
+
changeHighlightedIndexReason && changeHighlightedIndexReason !== 'mouse';
|
|
908
|
+
// Check if the first item is actually a group title
|
|
758
909
|
const firstOption = options[0];
|
|
759
910
|
const isFirstItemGroupTitle = isTitleMode && isFirstInGroup(firstOption);
|
|
911
|
+
// In title group mode, first item is group title, so start from index 1
|
|
760
912
|
const topHighlightIndex = isFirstItemGroupTitle ? 1 : 0;
|
|
761
913
|
if (shouldScroll) {
|
|
762
914
|
scrollToHighlightedIndex(actualPrevHighlightedIndex, highlightedIndex, topHighlightIndex);
|
|
763
915
|
}
|
|
764
916
|
});
|
|
765
|
-
const handleScrolling = (scrolling)
|
|
917
|
+
const handleScrolling = (scrolling)=>{
|
|
766
918
|
const list = listRef.current;
|
|
767
|
-
if (!list)
|
|
768
|
-
return;
|
|
919
|
+
if (!list) return;
|
|
769
920
|
list.style.pointerEvents = scrolling ? 'none' : '';
|
|
770
921
|
};
|
|
771
|
-
const itemContent = (index, option)
|
|
922
|
+
const itemContent = (index, option)=>{
|
|
772
923
|
const isOptionFirstInGroup = isFirstInGroup(option);
|
|
773
924
|
const isFixedGroupExpanded = typeof groupExpanded === 'boolean';
|
|
774
925
|
const expandable = !isTitleMode && !isFixedGroupExpanded && isOptionFirstInGroup;
|
|
775
926
|
const expanded = option.group?.expanded;
|
|
776
|
-
const expandIconProps = expandable
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
})
|
|
781
|
-
: undefined;
|
|
927
|
+
const expandIconProps = expandable ? option.group?.getExpandIconProps?.({
|
|
928
|
+
className: cls('expand-icon'),
|
|
929
|
+
'aria-expanded': expanded
|
|
930
|
+
}) : undefined;
|
|
782
931
|
const itemProps = getItemProps({
|
|
783
932
|
item: option,
|
|
784
|
-
index
|
|
933
|
+
index
|
|
785
934
|
});
|
|
786
935
|
const resultProps = {
|
|
787
936
|
'data-item-index': !virtualize ? index : undefined,
|
|
788
937
|
...option,
|
|
789
938
|
...itemProps,
|
|
790
939
|
label: option.label,
|
|
791
|
-
key: itemProps.id
|
|
940
|
+
key: itemProps.id
|
|
792
941
|
};
|
|
793
942
|
const highlighted = highlightedIndex === index;
|
|
794
943
|
const selected = getOptionSelected?.(option);
|
|
795
944
|
const indexInOwnGroup = resultProps.indexInOwnGroup;
|
|
796
945
|
delete resultProps.indexInOwnGroup;
|
|
946
|
+
// Check external disabled state if item is not already disabled
|
|
797
947
|
if (!resultProps.freeSolo && !resultProps.disabled && getOptionDisabled) {
|
|
798
948
|
resultProps.disabled = getOptionDisabled(option);
|
|
799
949
|
}
|
|
@@ -801,69 +951,128 @@ const SuggestionList = forwardRef((inProps, ref) => {
|
|
|
801
951
|
inputValue,
|
|
802
952
|
highlighted,
|
|
803
953
|
index,
|
|
804
|
-
indexInOwnGroup
|
|
954
|
+
indexInOwnGroup
|
|
805
955
|
};
|
|
806
956
|
if (renderGroup && isOptionFirstInGroup) {
|
|
807
957
|
return renderGroup(resultProps, {
|
|
808
958
|
...state,
|
|
809
959
|
expanded,
|
|
810
|
-
expandIconProps
|
|
960
|
+
expandIconProps
|
|
811
961
|
});
|
|
812
962
|
}
|
|
813
963
|
if (renderOption) {
|
|
814
964
|
return renderOption(resultProps, state);
|
|
815
965
|
}
|
|
816
966
|
const { error, group, freeSolo, key, ...restProps } = resultProps;
|
|
817
|
-
return
|
|
818
|
-
|
|
819
|
-
|
|
967
|
+
return /*#__PURE__*/ jsx(SuggestionListItem, {
|
|
968
|
+
...restProps,
|
|
969
|
+
id: typeof resultProps.id === 'number' ? `${resultProps.id}` : resultProps.id,
|
|
970
|
+
expanded: expanded,
|
|
971
|
+
isGroupTitle: isOptionFirstInGroup,
|
|
972
|
+
isTitleMode: isTitleMode,
|
|
973
|
+
highlighted: highlighted,
|
|
974
|
+
selected: selected,
|
|
975
|
+
disabled: resultProps.disabled,
|
|
976
|
+
label: getOptionLabel ? getOptionLabel(option) : option.label,
|
|
977
|
+
expandIconProps: expandIconProps
|
|
978
|
+
}, key);
|
|
820
979
|
};
|
|
821
980
|
const components = {
|
|
822
981
|
List: List,
|
|
823
|
-
...componentsProp
|
|
982
|
+
...componentsProp
|
|
824
983
|
};
|
|
825
984
|
const rootClass = cls('root');
|
|
826
|
-
const context = {
|
|
827
|
-
|
|
828
|
-
|
|
985
|
+
const context = {
|
|
986
|
+
listRef,
|
|
987
|
+
rootClass,
|
|
988
|
+
paddingValue
|
|
989
|
+
};
|
|
990
|
+
return virtualize ? /*#__PURE__*/ jsx(Virtuoso, {
|
|
991
|
+
ref: forkVlRef,
|
|
992
|
+
totalCount: itemCount,
|
|
993
|
+
data: itemData,
|
|
994
|
+
context: context,
|
|
995
|
+
components: components,
|
|
996
|
+
itemContent: itemContent,
|
|
997
|
+
...externalWindowProps,
|
|
998
|
+
...getSuggestionListProps(),
|
|
999
|
+
...combineProps({
|
|
1000
|
+
scrollerRef: (scrollElm)=>{
|
|
829
1001
|
scrollerRefFn(scrollElm);
|
|
830
1002
|
},
|
|
831
1003
|
itemsRendered: handleItemsRendered,
|
|
832
1004
|
totalListHeightChanged: handleTotalListHeightChanged,
|
|
833
1005
|
style,
|
|
834
|
-
isScrolling: handleScrolling
|
|
835
|
-
}, rest)
|
|
1006
|
+
isScrolling: handleScrolling
|
|
1007
|
+
}, rest)
|
|
1008
|
+
}) : /*#__PURE__*/ jsx(List, {
|
|
1009
|
+
context: context,
|
|
1010
|
+
style: {
|
|
836
1011
|
maxHeight: style.height,
|
|
837
|
-
overflow: 'auto'
|
|
838
|
-
},
|
|
1012
|
+
overflow: 'auto'
|
|
1013
|
+
},
|
|
1014
|
+
ref: (scrollElm)=>{
|
|
839
1015
|
listRef.current = scrollElm;
|
|
840
1016
|
scrollerRefFn(scrollElm);
|
|
841
|
-
},
|
|
1017
|
+
},
|
|
1018
|
+
className: cls('root'),
|
|
1019
|
+
...getSuggestionListProps(),
|
|
1020
|
+
...rest,
|
|
1021
|
+
children: options.map((x, i)=>{
|
|
842
1022
|
return itemContent(i, x);
|
|
843
|
-
})
|
|
1023
|
+
})
|
|
1024
|
+
});
|
|
844
1025
|
});
|
|
845
|
-
|
|
1026
|
+
SuggestionList_SuggestionList.displayName = 'SuggestionList';
|
|
1027
|
+
|
|
1028
|
+
|
|
1029
|
+
|
|
1030
|
+
|
|
1031
|
+
|
|
1032
|
+
|
|
1033
|
+
|
|
1034
|
+
|
|
1035
|
+
|
|
1036
|
+
|
|
1037
|
+
|
|
1038
|
+
|
|
1039
|
+
|
|
1040
|
+
|
|
1041
|
+
// extracted by css-extract-rspack-plugin
|
|
1042
|
+
/* export default */ var Autocomplete = ({"sui-autocomplete-chevron":"sui-autocomplete-chevron","sui-autocomplete-clear":"sui-autocomplete-clear","sui-autocomplete-root":"sui-autocomplete-root","sui-focused":"sui-focused","sui-disabled":"sui-disabled","sui-autocomplete-input-container":"sui-autocomplete-input-container","sui-autocomplete-form-field-content":"sui-autocomplete-form-field-content","sui-autocomplete-form-field-content-xlarge":"sui-autocomplete-form-field-content-xlarge","sui-autocomplete-form-field-content-large":"sui-autocomplete-form-field-content-large","sui-autocomplete-form-field-content-medium":"sui-autocomplete-form-field-content-medium","sui-autocomplete-input-container-xlarge":"sui-autocomplete-input-container-xlarge","sui-autocomplete-input-container-large":"sui-autocomplete-input-container-large","sui-autocomplete-input-container-medium":"sui-autocomplete-input-container-medium","sui-autocomplete-input":"sui-autocomplete-input","sui-autocomplete-input-xlarge":"sui-autocomplete-input-xlarge","sui-autocomplete-input-large":"sui-autocomplete-input-large","sui-autocomplete-input-medium":"sui-autocomplete-input-medium","sui-autocomplete-outlined-expanded":"sui-autocomplete-outlined-expanded","sui-autocomplete-outlined-expanded-chevron":"sui-autocomplete-outlined-expanded-chevron","sui-autocomplete-standard-expanded-chevron":"sui-autocomplete-standard-expanded-chevron","sui-autocomplete-error-chevron":"sui-autocomplete-error-chevron","sui-autocomplete-end-adornment":"sui-autocomplete-end-adornment","sui-autocomplete-popper-root":"sui-autocomplete-popper-root","sui-popper-widget":"sui-popper-widget","sui-autocomplete-popper-paper":"sui-autocomplete-popper-paper","sui-autocomplete-popper-empty":"sui-autocomplete-popper-empty","sui-autocomplete-status-container":"sui-autocomplete-status-container","sui-autocomplete-status-label":"sui-autocomplete-status-label"});
|
|
1043
|
+
|
|
1044
|
+
|
|
1045
|
+
|
|
1046
|
+
|
|
1047
|
+
|
|
1048
|
+
|
|
1049
|
+
|
|
846
1050
|
|
|
847
|
-
var Autocomplete$1 = {"sui-autocomplete-chevron":"sui-autocomplete-chevron","sui-autocomplete-clear":"sui-autocomplete-clear","sui-autocomplete-root":"sui-autocomplete-root","sui-focused":"sui-focused","sui-disabled":"sui-disabled","sui-autocomplete-input-container":"sui-autocomplete-input-container","sui-autocomplete-form-field-content":"sui-autocomplete-form-field-content","sui-autocomplete-form-field-content-xlarge":"sui-autocomplete-form-field-content-xlarge","sui-autocomplete-form-field-content-large":"sui-autocomplete-form-field-content-large","sui-autocomplete-form-field-content-medium":"sui-autocomplete-form-field-content-medium","sui-autocomplete-input-container-xlarge":"sui-autocomplete-input-container-xlarge","sui-autocomplete-input-container-large":"sui-autocomplete-input-container-large","sui-autocomplete-input-container-medium":"sui-autocomplete-input-container-medium","sui-autocomplete-input":"sui-autocomplete-input","sui-autocomplete-input-xlarge":"sui-autocomplete-input-xlarge","sui-autocomplete-input-large":"sui-autocomplete-input-large","sui-autocomplete-input-medium":"sui-autocomplete-input-medium","sui-autocomplete-outlined-expanded":"sui-autocomplete-outlined-expanded","sui-autocomplete-outlined-expanded-chevron":"sui-autocomplete-outlined-expanded-chevron","sui-autocomplete-standard-expanded-chevron":"sui-autocomplete-standard-expanded-chevron","sui-autocomplete-error-chevron":"sui-autocomplete-error-chevron","sui-autocomplete-end-adornment":"sui-autocomplete-end-adornment","sui-autocomplete-popper-root":"sui-autocomplete-popper-root","sui-popper-widget":"sui-popper-widget","sui-autocomplete-popper-paper":"sui-autocomplete-popper-paper","sui-autocomplete-popper-empty":"sui-autocomplete-popper-empty","sui-autocomplete-status-container":"sui-autocomplete-status-container","sui-autocomplete-status-label":"sui-autocomplete-status-label"};
|
|
848
1051
|
|
|
849
1052
|
function stringArrToRegExp(keyToTags) {
|
|
850
1053
|
return new RegExp(keyToTags?.join('|') || '', 'g');
|
|
851
1054
|
}
|
|
852
1055
|
const TAG_TOKEN = 'rc-chip-';
|
|
853
|
-
const DEFAULT_LIMIT_CHIPS
|
|
854
|
-
const DEFAULT_KEY_TO_CHIPS = [
|
|
855
|
-
|
|
1056
|
+
const DEFAULT_LIMIT_CHIPS = 20;
|
|
1057
|
+
const DEFAULT_KEY_TO_CHIPS = [
|
|
1058
|
+
';',
|
|
1059
|
+
',',
|
|
1060
|
+
'\\n'
|
|
1061
|
+
];
|
|
1062
|
+
/** @inner inner hook work with useAutocomplete */ const useAutocompleteTag = ({ id: AutocompleteId, value: selectedItemsProp = [], onChange: onSelectChange, variant, getOptionLabel = DEFAULT_GET_OPTION_LABEL, onInputChange: onInputChangeProp, freeSolo, multiple, wrapperRef, inputRef, maxFreeSolo = DEFAULT_LIMIT_CHIPS, keyToTags = DEFAULT_KEY_TO_CHIPS, onMaxFreeSolo, required, disabled, getStopCreateFreeSolo, onReset, onAddTags })=>{
|
|
856
1063
|
const isAutocomplete = variant === 'autocomplete';
|
|
857
1064
|
const isSelectedFromAutocompleteRef = useRef(false);
|
|
858
1065
|
const [tags, _setTags] = useControlled({
|
|
859
1066
|
controlled: selectedItemsProp,
|
|
860
1067
|
default: [],
|
|
861
|
-
name: 'useAutocompleteTag'
|
|
1068
|
+
name: 'useAutocompleteTag'
|
|
862
1069
|
});
|
|
863
1070
|
const [focused, setFocused] = useState(false);
|
|
864
1071
|
const [focusedIndexRef, setFocusedIndex] = useRefState(0);
|
|
865
|
-
const freeSoloCount = useMemo(()
|
|
866
|
-
|
|
1072
|
+
const freeSoloCount = useMemo(()=>tags.filter((x)=>x.freeSolo).length, [
|
|
1073
|
+
tags
|
|
1074
|
+
]);
|
|
1075
|
+
const setTags = (_selectedItems)=>{
|
|
867
1076
|
_setTags(_selectedItems);
|
|
868
1077
|
onSelectChange?.(_selectedItems);
|
|
869
1078
|
if (isAutocomplete && _selectedItems.length === 1) {
|
|
@@ -872,38 +1081,26 @@ const useAutocompleteTag = ({ id: AutocompleteId, value: selectedItemsProp = [],
|
|
|
872
1081
|
isSelectedFromAutocompleteRef.current = true;
|
|
873
1082
|
}
|
|
874
1083
|
};
|
|
875
|
-
const _focusTag = (tagToFocus)
|
|
1084
|
+
const _focusTag = (tagToFocus)=>{
|
|
876
1085
|
const textFieldElm = wrapperRef?.current;
|
|
877
1086
|
const inputElm = inputRef.current;
|
|
878
1087
|
if (tagToFocus === -1) {
|
|
879
1088
|
inputElm?.focus();
|
|
880
|
-
}
|
|
881
|
-
|
|
882
|
-
textFieldElm
|
|
883
|
-
?.querySelector(`[data-tag-index="${tagToFocus}"]`)
|
|
884
|
-
?.focus();
|
|
1089
|
+
} else {
|
|
1090
|
+
textFieldElm?.querySelector(`[data-tag-index="${tagToFocus}"]`)?.focus();
|
|
885
1091
|
}
|
|
886
1092
|
};
|
|
887
|
-
const focusTag = useEventCallback((tagToFocus, focus = true)
|
|
888
|
-
if (focusedIndexRef.current === tagToFocus)
|
|
889
|
-
return;
|
|
1093
|
+
const focusTag = useEventCallback((tagToFocus, focus = true)=>{
|
|
1094
|
+
if (focusedIndexRef.current === tagToFocus) return;
|
|
890
1095
|
setFocusedIndex(tagToFocus);
|
|
891
|
-
if (focus)
|
|
892
|
-
_focusTag(tagToFocus);
|
|
1096
|
+
if (focus) _focusTag(tagToFocus);
|
|
893
1097
|
});
|
|
894
|
-
const checkAndAddFreeSolo = ({ selectedItem, value
|
|
1098
|
+
/** when return `true` mean add item success */ const checkAndAddFreeSolo = ({ selectedItem, value } = {}, e)=>{
|
|
895
1099
|
let isAddItem = false;
|
|
896
1100
|
const currentValue = value ?? inputRef.current?.value;
|
|
897
|
-
if (!getStopCreateFreeSolo?.() &&
|
|
898
|
-
!selectedItem &&
|
|
899
|
-
freeSolo &&
|
|
900
|
-
currentValue &&
|
|
901
|
-
currentValue.length > 0) {
|
|
1101
|
+
if (!getStopCreateFreeSolo?.() && !selectedItem && freeSolo && currentValue && currentValue.length > 0) {
|
|
902
1102
|
if (freeSoloCount < maxFreeSolo) {
|
|
903
|
-
const items = currentValue
|
|
904
|
-
.trim()
|
|
905
|
-
.split(stringArrToRegExp(keyToTags))
|
|
906
|
-
.filter((x) => x.trim() !== '');
|
|
1103
|
+
const items = currentValue.trim().split(stringArrToRegExp(keyToTags)).filter((x)=>x.trim() !== '');
|
|
907
1104
|
const toLength = freeSoloCount + items.length;
|
|
908
1105
|
if (toLength > maxFreeSolo) {
|
|
909
1106
|
items.splice(-(toLength - maxFreeSolo));
|
|
@@ -912,116 +1109,122 @@ const useAutocompleteTag = ({ id: AutocompleteId, value: selectedItemsProp = [],
|
|
|
912
1109
|
if (items.length > 0) {
|
|
913
1110
|
addTags([
|
|
914
1111
|
...tags,
|
|
915
|
-
...items.map((label)
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
1112
|
+
...items.map((label)=>({
|
|
1113
|
+
id: uniqueId(TAG_TOKEN),
|
|
1114
|
+
freeSolo: true,
|
|
1115
|
+
label
|
|
1116
|
+
}))
|
|
920
1117
|
]);
|
|
921
1118
|
isAddItem = true;
|
|
922
1119
|
}
|
|
923
|
-
}
|
|
924
|
-
else {
|
|
1120
|
+
} else {
|
|
925
1121
|
onMaxFreeSolo?.(maxFreeSolo);
|
|
926
1122
|
}
|
|
927
1123
|
onReset?.(e);
|
|
928
1124
|
}
|
|
929
1125
|
return isAddItem;
|
|
930
1126
|
};
|
|
931
|
-
const onInputChange = (e)
|
|
1127
|
+
const onInputChange = (e)=>{
|
|
932
1128
|
const newValue = e.target['value'];
|
|
933
1129
|
if (stringArrToRegExp(keyToTags).test(newValue || '')) {
|
|
934
|
-
return checkAndAddFreeSolo({
|
|
1130
|
+
return checkAndAddFreeSolo({
|
|
1131
|
+
value: newValue
|
|
1132
|
+
});
|
|
935
1133
|
}
|
|
936
1134
|
return false;
|
|
937
1135
|
};
|
|
938
|
-
const removeTag = (selectedItem)
|
|
1136
|
+
const removeTag = (selectedItem)=>{
|
|
939
1137
|
const selectedItemIndex = tags.indexOf(selectedItem);
|
|
940
1138
|
if (selectedItemIndex > -1) {
|
|
941
1139
|
setTags([
|
|
942
1140
|
...tags.slice(0, selectedItemIndex),
|
|
943
|
-
...tags.slice(selectedItemIndex + 1)
|
|
1141
|
+
...tags.slice(selectedItemIndex + 1)
|
|
944
1142
|
]);
|
|
945
1143
|
}
|
|
946
1144
|
};
|
|
947
|
-
const addTags = (tags)
|
|
1145
|
+
const addTags = (tags)=>{
|
|
948
1146
|
setTags(tags);
|
|
949
1147
|
onAddTags?.();
|
|
950
1148
|
};
|
|
951
|
-
const addTagOnSelect = (selectedItem)
|
|
1149
|
+
const addTagOnSelect = (selectedItem)=>{
|
|
1150
|
+
// * change to uniqueId
|
|
952
1151
|
if (selectedItem.freeSolo) {
|
|
953
1152
|
if (freeSoloCount < maxFreeSolo) {
|
|
954
1153
|
selectedItem.id = uniqueId(TAG_TOKEN);
|
|
955
|
-
}
|
|
956
|
-
else {
|
|
1154
|
+
} else {
|
|
957
1155
|
onMaxFreeSolo?.(maxFreeSolo);
|
|
958
1156
|
return;
|
|
959
1157
|
}
|
|
960
1158
|
}
|
|
961
1159
|
if (multiple) {
|
|
962
|
-
addTags([
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
1160
|
+
addTags([
|
|
1161
|
+
...tags,
|
|
1162
|
+
selectedItem
|
|
1163
|
+
]);
|
|
1164
|
+
} else {
|
|
1165
|
+
addTags([
|
|
1166
|
+
selectedItem
|
|
1167
|
+
]);
|
|
966
1168
|
}
|
|
967
1169
|
};
|
|
968
1170
|
const { onKeyFocusedIndexHandle: handleTagKey } = useKeyboardMoveFocus({
|
|
969
1171
|
options: tags,
|
|
970
1172
|
focusedIndexRef,
|
|
971
1173
|
infinite: 'order',
|
|
972
|
-
onFocusedIndexChange: (event, toIndex)
|
|
1174
|
+
onFocusedIndexChange: (event, toIndex)=>{
|
|
973
1175
|
focusTag(toIndex);
|
|
974
1176
|
event?.preventDefault();
|
|
975
|
-
}
|
|
1177
|
+
}
|
|
976
1178
|
});
|
|
977
|
-
|
|
1179
|
+
/**
|
|
1180
|
+
* All Get props methods
|
|
1181
|
+
*/ const getTagListBoxProps = (props)=>{
|
|
978
1182
|
return {
|
|
979
1183
|
'aria-required': required || false,
|
|
980
1184
|
'aria-disabled': disabled || false,
|
|
981
|
-
|
|
1185
|
+
// TODO: wait for input can complete that.
|
|
1186
|
+
// 'aria-multiselectable': false,
|
|
1187
|
+
// role: selectedItems.length > 0 ? 'listbox' : undefined,
|
|
1188
|
+
// 'aria-orientation': 'horizontal',
|
|
1189
|
+
...props
|
|
982
1190
|
};
|
|
983
1191
|
};
|
|
984
|
-
const getTagProps = ({ index, selectedItem, ...restTagProps })
|
|
985
|
-
const tabIndex =
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
: -1;
|
|
1192
|
+
const getTagProps = ({ index, selectedItem, ...restTagProps })=>{
|
|
1193
|
+
const tabIndex = // * when tag is not focus
|
|
1194
|
+
!focused && // * and is first item
|
|
1195
|
+
index === 0 ? 0 : -1;
|
|
989
1196
|
return combineProps({
|
|
990
1197
|
tabIndex,
|
|
1198
|
+
// TODO: wait for input can complete that.
|
|
1199
|
+
// role: 'option',
|
|
991
1200
|
role: 'button',
|
|
992
1201
|
key: `${AutocompleteId}-selected-item-${index}`,
|
|
993
1202
|
'data-tag-index': index,
|
|
994
1203
|
'data-item-last': index === tags.length - 1,
|
|
995
|
-
onClick: (e)
|
|
1204
|
+
onClick: (e)=>{
|
|
996
1205
|
e.stopPropagation();
|
|
997
1206
|
},
|
|
998
|
-
onBlur: ()
|
|
999
|
-
onFocus: ()
|
|
1207
|
+
onBlur: ()=>setFocused(false),
|
|
1208
|
+
onFocus: ()=>{
|
|
1000
1209
|
focusTag(index);
|
|
1001
1210
|
setFocused(true);
|
|
1002
1211
|
},
|
|
1003
|
-
onKeyDown: (e)
|
|
1212
|
+
onKeyDown: (e)=>{
|
|
1004
1213
|
const focusedIndex = focusedIndexRef.current;
|
|
1005
|
-
switch
|
|
1214
|
+
switch(e.key){
|
|
1006
1215
|
case 'Backspace':
|
|
1007
1216
|
{
|
|
1008
1217
|
const lengthBeforeDelete = tags.length;
|
|
1009
|
-
focusTag(
|
|
1010
|
-
|
|
1011
|
-
: focusedIndex === 0
|
|
1012
|
-
? 0
|
|
1013
|
-
: focusedIndex - 1);
|
|
1218
|
+
focusTag(// * when length is 1 mean that will be remove all
|
|
1219
|
+
lengthBeforeDelete === 1 ? -1 : focusedIndex === 0 ? 0 : focusedIndex - 1);
|
|
1014
1220
|
removeTag(selectedItem);
|
|
1015
1221
|
}
|
|
1016
1222
|
break;
|
|
1017
1223
|
case 'Delete':
|
|
1018
1224
|
{
|
|
1019
1225
|
const lengthBeforeDelete = tags.length;
|
|
1020
|
-
focusTag(
|
|
1021
|
-
|
|
1022
|
-
: focusedIndex === tags.length - 1
|
|
1023
|
-
? focusedIndex - 1
|
|
1024
|
-
: focusedIndex);
|
|
1226
|
+
focusTag(// * when length is 1 mean that will be remove all
|
|
1227
|
+
lengthBeforeDelete === 1 ? -1 : focusedIndex === tags.length - 1 ? focusedIndex - 1 : focusedIndex);
|
|
1025
1228
|
removeTag(selectedItem);
|
|
1026
1229
|
}
|
|
1027
1230
|
break;
|
|
@@ -1030,13 +1233,13 @@ const useAutocompleteTag = ({ id: AutocompleteId, value: selectedItemsProp = [],
|
|
|
1030
1233
|
break;
|
|
1031
1234
|
}
|
|
1032
1235
|
},
|
|
1033
|
-
onDelete: (e)
|
|
1034
|
-
|
|
1035
|
-
|
|
1236
|
+
onDelete: (e)=>{
|
|
1237
|
+
// * only allow left button click
|
|
1238
|
+
if (e.button !== 0) return;
|
|
1036
1239
|
removeTag(selectedItem);
|
|
1037
1240
|
setFocused(false);
|
|
1038
1241
|
inputRef.current?.focus();
|
|
1039
|
-
}
|
|
1242
|
+
}
|
|
1040
1243
|
}, restTagProps);
|
|
1041
1244
|
};
|
|
1042
1245
|
return {
|
|
@@ -1051,39 +1254,43 @@ const useAutocompleteTag = ({ id: AutocompleteId, value: selectedItemsProp = [],
|
|
|
1051
1254
|
getTagProps,
|
|
1052
1255
|
focusTag,
|
|
1053
1256
|
addTagOnSelect,
|
|
1054
|
-
clearTags: ()
|
|
1055
|
-
blur: ()
|
|
1257
|
+
clearTags: ()=>setTags([]),
|
|
1258
|
+
blur: ()=>{
|
|
1056
1259
|
if (focusedIndexRef.current !== -1) {
|
|
1057
1260
|
focusTag(-1, false);
|
|
1058
1261
|
}
|
|
1059
1262
|
setFocused(false);
|
|
1060
1263
|
},
|
|
1061
|
-
reset: ()
|
|
1062
|
-
focusLast: ()
|
|
1264
|
+
reset: ()=>focusTag(-1),
|
|
1265
|
+
focusLast: ()=>{
|
|
1063
1266
|
const currTagsLength = tags.length;
|
|
1064
|
-
if (currTagsLength <= 0)
|
|
1065
|
-
return;
|
|
1267
|
+
if (currTagsLength <= 0) return;
|
|
1066
1268
|
focusTag(currTagsLength - 1);
|
|
1067
|
-
}
|
|
1269
|
+
}
|
|
1068
1270
|
};
|
|
1069
1271
|
};
|
|
1070
1272
|
|
|
1273
|
+
|
|
1274
|
+
|
|
1275
|
+
|
|
1276
|
+
|
|
1277
|
+
|
|
1071
1278
|
const EMPTY_TAG_TOKEN = 'rc-chip-empty';
|
|
1072
|
-
const
|
|
1073
|
-
const
|
|
1074
|
-
const useAutocomplete = ({ multiple: multipleProp, variant, inputValue: inputValueProp, getOptionLabel = DEFAULT_GET_OPTION_LABEL, keyToTags = DEFAULT_KEY_TO_CHIPS, filterOptions: filterOptionsProp, disabledItemsHighlightable, options, freeSolo, onInputChange: onInputChangeProp, maxFreeSolo =
|
|
1279
|
+
const useAutocomplete_DEFAULT_HIGHLIGHTED_INDEX = -1;
|
|
1280
|
+
const useAutocomplete_DEFAULT_LIMIT_CHIPS = 20;
|
|
1281
|
+
const useAutocomplete = ({ multiple: multipleProp, variant, inputValue: inputValueProp, getOptionLabel = DEFAULT_GET_OPTION_LABEL, keyToTags = DEFAULT_KEY_TO_CHIPS, filterOptions: filterOptionsProp, disabledItemsHighlightable, options, freeSolo, onInputChange: onInputChangeProp, maxFreeSolo = useAutocomplete_DEFAULT_LIMIT_CHIPS, onMaxFreeSolo, openOnFocus, toggleWithInput, disableCloseOnSelect, initialIsOpen, autoSelect, inputContainerRef, inputRef, wrapperRef, onKeyDown: onKeyDownProp, onChange: onSelectChange, value: selectedItemsProp = [], disabled, renderNoOptions, onClear, autoHighlight, getOptionDisabled, addNoOptionItem, groupBy, onGroupExpanded, groupVariant, groupExpanded, groupDefaultExpanded, getExpandIconProps, required, open: openProp, onOpen, onClose, focused, isOptionEqualToValue })=>{
|
|
1075
1282
|
const isAutocomplete = variant === 'autocomplete';
|
|
1076
|
-
const autoCompleteSelectedIndexRef = useRef(
|
|
1283
|
+
const autoCompleteSelectedIndexRef = useRef(useAutocomplete_DEFAULT_HIGHLIGHTED_INDEX);
|
|
1077
1284
|
const AutocompleteId = useId('Autocomplete', true);
|
|
1078
1285
|
const [inputFocused, setInputFocused] = useControlled({
|
|
1079
1286
|
controlled: focused,
|
|
1080
1287
|
default: false,
|
|
1081
|
-
name: 'useAutocomplete'
|
|
1288
|
+
name: 'useAutocomplete'
|
|
1082
1289
|
});
|
|
1083
1290
|
const [isOpen, setIsOpen] = useControlled({
|
|
1084
1291
|
controlled: openProp,
|
|
1085
1292
|
default: initialIsOpen || false,
|
|
1086
|
-
name: 'useAutocomplete'
|
|
1293
|
+
name: 'useAutocomplete'
|
|
1087
1294
|
});
|
|
1088
1295
|
const isInputValueChangedRef = useRef(false);
|
|
1089
1296
|
const isCompositionRef = useRef(false);
|
|
@@ -1094,16 +1301,17 @@ const useAutocomplete = ({ multiple: multipleProp, variant, inputValue: inputVal
|
|
|
1094
1301
|
const popperElementRef = useRef(null);
|
|
1095
1302
|
const { sleep, getSleeping } = useSleep();
|
|
1096
1303
|
const { requestAnimationFrame } = useAnimationFrame();
|
|
1304
|
+
// * when that is autocomplete, that will never be multiple
|
|
1097
1305
|
const multiple = isAutocomplete ? false : multipleProp;
|
|
1098
|
-
const inputContainerScrollToBottom = ()
|
|
1099
|
-
requestAnimationFrame(()
|
|
1306
|
+
const inputContainerScrollToBottom = ()=>{
|
|
1307
|
+
requestAnimationFrame(()=>{
|
|
1100
1308
|
const inputContainer = inputContainerRef?.current;
|
|
1101
1309
|
if (inputContainer) {
|
|
1102
1310
|
inputContainer.scrollTop = inputContainer.scrollHeight;
|
|
1103
1311
|
}
|
|
1104
1312
|
});
|
|
1105
1313
|
};
|
|
1106
|
-
const { isSelectedFromAutocompleteRef, tags, focused: tagFocused, addTagOnSelect, checkAndAddFreeSolo, focusedIndex: focusedTagIndex, focusTag, focusLast: focusLastTag, clearTags, blur: blurTags, getTagProps, getTagListBoxProps
|
|
1314
|
+
const { isSelectedFromAutocompleteRef, tags, focused: tagFocused, addTagOnSelect, checkAndAddFreeSolo, focusedIndex: focusedTagIndex, focusTag, focusLast: focusLastTag, clearTags, blur: blurTags, getTagProps, getTagListBoxProps } = useAutocompleteTag({
|
|
1107
1315
|
id: AutocompleteId,
|
|
1108
1316
|
value: selectedItemsProp,
|
|
1109
1317
|
onChange: onSelectChange,
|
|
@@ -1119,50 +1327,58 @@ const useAutocomplete = ({ multiple: multipleProp, variant, inputValue: inputVal
|
|
|
1119
1327
|
multiple,
|
|
1120
1328
|
required,
|
|
1121
1329
|
disabled,
|
|
1122
|
-
|
|
1123
|
-
|
|
1330
|
+
// * when is composition, stop freeSolo create new item
|
|
1331
|
+
getStopCreateFreeSolo: ()=>isCompositionRef.current,
|
|
1332
|
+
onReset: ()=>{
|
|
1124
1333
|
resetState();
|
|
1125
1334
|
},
|
|
1126
|
-
onAddTags: inputContainerScrollToBottom
|
|
1335
|
+
onAddTags: inputContainerScrollToBottom
|
|
1127
1336
|
});
|
|
1128
|
-
const processFilteredResult = useCallback((results, inputValue = '')
|
|
1129
|
-
const getInputValueAsItem = ()
|
|
1337
|
+
const processFilteredResult = useCallback((results, inputValue = '')=>{
|
|
1338
|
+
const getInputValueAsItem = ()=>{
|
|
1130
1339
|
const label = inputValue.trim();
|
|
1131
1340
|
if (label.length > 0) {
|
|
1132
1341
|
return {
|
|
1133
1342
|
id: EMPTY_TAG_TOKEN,
|
|
1134
1343
|
freeSolo: true,
|
|
1135
|
-
label
|
|
1344
|
+
label
|
|
1136
1345
|
};
|
|
1137
1346
|
}
|
|
1138
1347
|
return null;
|
|
1139
1348
|
};
|
|
1349
|
+
// * only when isOpen calculate the filtered result
|
|
1140
1350
|
noOptionItemRef.current = null;
|
|
1141
1351
|
if (addNoOptionItem) {
|
|
1142
1352
|
const item = getInputValueAsItem();
|
|
1143
1353
|
noOptionItemRef.current = item;
|
|
1144
1354
|
if (item && freeSolo) {
|
|
1145
|
-
switch
|
|
1355
|
+
switch(addNoOptionItem){
|
|
1146
1356
|
case 'first':
|
|
1147
1357
|
noOptionItemRef.current = null;
|
|
1148
|
-
return [
|
|
1358
|
+
return [
|
|
1359
|
+
item,
|
|
1360
|
+
...results
|
|
1361
|
+
];
|
|
1149
1362
|
case 'last':
|
|
1150
1363
|
noOptionItemRef.current = null;
|
|
1151
|
-
return [
|
|
1364
|
+
return [
|
|
1365
|
+
...results,
|
|
1366
|
+
item
|
|
1367
|
+
];
|
|
1368
|
+
default:
|
|
1369
|
+
break;
|
|
1152
1370
|
}
|
|
1153
1371
|
}
|
|
1154
1372
|
if (results.length === 0 && renderNoOptions === true) {
|
|
1155
1373
|
if (item) {
|
|
1156
1374
|
noOptionItemRef.current = item;
|
|
1157
|
-
}
|
|
1158
|
-
else {
|
|
1375
|
+
} else {
|
|
1159
1376
|
noOptionItemRef.current = {
|
|
1160
1377
|
id: EMPTY_TAG_TOKEN,
|
|
1161
|
-
label: ''
|
|
1378
|
+
label: ''
|
|
1162
1379
|
};
|
|
1163
1380
|
}
|
|
1164
|
-
}
|
|
1165
|
-
else if (results.length > 0) {
|
|
1381
|
+
} else if (results.length > 0) {
|
|
1166
1382
|
noOptionItemRef.current = null;
|
|
1167
1383
|
}
|
|
1168
1384
|
return results;
|
|
@@ -1170,36 +1386,48 @@ const useAutocomplete = ({ multiple: multipleProp, variant, inputValue: inputVal
|
|
|
1170
1386
|
if (results.length === 0) {
|
|
1171
1387
|
const item = getInputValueAsItem();
|
|
1172
1388
|
if (renderNoOptions === true) {
|
|
1389
|
+
// * when using boolean renderNoOptions, always create a noOptionItem
|
|
1173
1390
|
if (item) {
|
|
1174
1391
|
noOptionItemRef.current = item;
|
|
1175
|
-
}
|
|
1176
|
-
else {
|
|
1392
|
+
} else {
|
|
1177
1393
|
noOptionItemRef.current = {
|
|
1178
1394
|
id: EMPTY_TAG_TOKEN,
|
|
1179
|
-
label: ''
|
|
1395
|
+
label: ''
|
|
1180
1396
|
};
|
|
1181
1397
|
}
|
|
1182
|
-
}
|
|
1183
|
-
|
|
1398
|
+
} else {
|
|
1399
|
+
// * keep existing behavior for function / undefined renderNoOptions
|
|
1184
1400
|
noOptionItemRef.current = item;
|
|
1185
1401
|
}
|
|
1402
|
+
// * only when not set render no options will auto add item
|
|
1186
1403
|
if (item && freeSolo && !renderNoOptions) {
|
|
1187
|
-
|
|
1404
|
+
// * when outside not use filter, that will be original array, must use spread
|
|
1405
|
+
return [
|
|
1406
|
+
...results,
|
|
1407
|
+
item
|
|
1408
|
+
];
|
|
1188
1409
|
}
|
|
1189
1410
|
}
|
|
1190
1411
|
return results;
|
|
1191
|
-
}, [
|
|
1192
|
-
|
|
1412
|
+
}, [
|
|
1413
|
+
addNoOptionItem,
|
|
1414
|
+
freeSolo,
|
|
1415
|
+
renderNoOptions
|
|
1416
|
+
]);
|
|
1417
|
+
const filterOptions = useCallback((items, state)=>{
|
|
1193
1418
|
if (filterOptionsProp) {
|
|
1194
1419
|
return filterOptionsProp(items, {
|
|
1195
1420
|
...state,
|
|
1196
1421
|
inputChanged: isInputValueChangedRef.current,
|
|
1197
|
-
selectedItems: tags
|
|
1422
|
+
selectedItems: tags
|
|
1198
1423
|
});
|
|
1199
1424
|
}
|
|
1200
1425
|
return items;
|
|
1201
|
-
}, [
|
|
1202
|
-
|
|
1426
|
+
}, [
|
|
1427
|
+
filterOptionsProp,
|
|
1428
|
+
tags
|
|
1429
|
+
]);
|
|
1430
|
+
const { inputValue, optionItems, optionsGroupList, highlightedIndexRef, changeHighlightedIndexReasonRef, forceUpdate, selectItem, handleF10KeyDown, onKeyFocusedIndexHandle, setHighlightedIndex, updateInputValue, clearInput, getNextFocusableOption, getItemProps: getSuggestionListItemProps, focusInput, reset: resetSuggestionList, getInputProps: getSuggestionListInputProps, getInputAriaProps: getSuggestionListInputAriaProps, getClearButtonProps: getSuggestionListClearButtonProps, getLabelProps, getSuggestionListProps } = useSuggestionList({
|
|
1203
1431
|
id: AutocompleteId,
|
|
1204
1432
|
inputRef,
|
|
1205
1433
|
inputValue: inputValueProp,
|
|
@@ -1219,35 +1447,36 @@ const useAutocomplete = ({ multiple: multipleProp, variant, inputValue: inputVal
|
|
|
1219
1447
|
onGroupExpanded,
|
|
1220
1448
|
getExpandIconProps,
|
|
1221
1449
|
isOptionEqualToValue,
|
|
1222
|
-
onSelect: (e, selectedItem)
|
|
1450
|
+
onSelect: (e, selectedItem)=>{
|
|
1223
1451
|
addTagOnSelect(selectedItem);
|
|
1224
1452
|
resetState(e);
|
|
1225
1453
|
if (disableCloseOnSelect) {
|
|
1226
1454
|
if (autoHighlight) {
|
|
1227
1455
|
const currIndex = highlightedIndexRef.current;
|
|
1228
1456
|
const nextValidIndex = getNextFocusableOption();
|
|
1229
|
-
const toIndex = nextValidIndex
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
: nextValidIndex;
|
|
1457
|
+
const toIndex = // * if that index is bigger than current index or nextValidIndex is last index,
|
|
1458
|
+
// * cut 1 (because current item will be remove)
|
|
1459
|
+
nextValidIndex > currIndex || nextValidIndex === optionItems.length - 1 ? nextValidIndex - 1 : nextValidIndex;
|
|
1233
1460
|
setHighlightedIndex(toIndex, {
|
|
1234
|
-
reRender: true
|
|
1461
|
+
reRender: true
|
|
1235
1462
|
});
|
|
1236
|
-
}
|
|
1237
|
-
|
|
1238
|
-
highlightedIndexRef.current = DEFAULT_HIGHLIGHTED_INDEX;
|
|
1463
|
+
} else {
|
|
1464
|
+
highlightedIndexRef.current = useAutocomplete_DEFAULT_HIGHLIGHTED_INDEX;
|
|
1239
1465
|
}
|
|
1240
1466
|
}
|
|
1241
|
-
}
|
|
1467
|
+
}
|
|
1242
1468
|
});
|
|
1243
|
-
const getItemProps = (props)
|
|
1469
|
+
const getItemProps = (props)=>{
|
|
1244
1470
|
return getSuggestionListItemProps(props);
|
|
1245
1471
|
};
|
|
1246
1472
|
const readOnly = !isAutocomplete && !multiple && tags.length >= 1 ? true : undefined;
|
|
1247
|
-
const closeMenu = (e, reason)
|
|
1473
|
+
const closeMenu = (e, reason)=>{
|
|
1248
1474
|
keepHighlightedIndexRef.current = false;
|
|
1249
1475
|
isInputValueChangedRef.current = false;
|
|
1250
|
-
|
|
1476
|
+
// * set to default highlighted
|
|
1477
|
+
setHighlightedIndex(useAutocomplete_DEFAULT_HIGHLIGHTED_INDEX, {
|
|
1478
|
+
reason: 'auto'
|
|
1479
|
+
});
|
|
1251
1480
|
if (isOpen) {
|
|
1252
1481
|
if (e && reason) {
|
|
1253
1482
|
onClose?.(e, reason);
|
|
@@ -1255,43 +1484,45 @@ const useAutocomplete = ({ multiple: multipleProp, variant, inputValue: inputVal
|
|
|
1255
1484
|
setIsOpen(false);
|
|
1256
1485
|
}
|
|
1257
1486
|
};
|
|
1258
|
-
const openMenu = (e)
|
|
1487
|
+
const openMenu = (e)=>{
|
|
1259
1488
|
if (!isOpen) {
|
|
1260
|
-
if (e)
|
|
1261
|
-
onOpen?.(e);
|
|
1489
|
+
if (e) onOpen?.(e);
|
|
1262
1490
|
setIsOpen(true);
|
|
1263
1491
|
}
|
|
1264
1492
|
};
|
|
1265
|
-
const toggleMenu = (e, focus = true)
|
|
1493
|
+
const toggleMenu = (e, focus = true)=>{
|
|
1266
1494
|
if (isOpen) {
|
|
1267
1495
|
closeMenu(e, 'toggleInput');
|
|
1268
|
-
}
|
|
1269
|
-
else {
|
|
1496
|
+
} else {
|
|
1270
1497
|
openMenu(e);
|
|
1271
|
-
if (focus)
|
|
1272
|
-
focusInput();
|
|
1498
|
+
if (focus) focusInput();
|
|
1273
1499
|
}
|
|
1274
1500
|
};
|
|
1275
|
-
const handleInputChange = (newValue, open = true, e)
|
|
1501
|
+
const handleInputChange = (newValue, open = true, e)=>{
|
|
1276
1502
|
if (inputValue !== newValue) {
|
|
1277
|
-
setHighlightedIndex(
|
|
1503
|
+
setHighlightedIndex(useAutocomplete_DEFAULT_HIGHLIGHTED_INDEX, {
|
|
1504
|
+
reason: 'auto'
|
|
1505
|
+
});
|
|
1506
|
+
// !!! Get regexp every time for prevent bug with browser
|
|
1507
|
+
// https://stackoverflow.com/questions/3827456/what-is-wrong-with-my-date-regex/3827500#3827500
|
|
1278
1508
|
if (stringArrToRegExp(keyToTags).test((newValue || '').trim())) {
|
|
1279
|
-
const resultHaveAdd = checkAndAddFreeSolo({
|
|
1509
|
+
const resultHaveAdd = checkAndAddFreeSolo({
|
|
1510
|
+
value: newValue
|
|
1511
|
+
}, e);
|
|
1280
1512
|
if (!disableCloseOnSelect) {
|
|
1281
1513
|
closeMenu(e, 'toggleInput');
|
|
1282
|
-
}
|
|
1283
|
-
else if (open) {
|
|
1514
|
+
} else if (open) {
|
|
1284
1515
|
openMenu(e);
|
|
1285
1516
|
}
|
|
1286
1517
|
return resultHaveAdd;
|
|
1287
1518
|
}
|
|
1288
1519
|
updateInputValue(newValue);
|
|
1289
1520
|
}
|
|
1290
|
-
if (open)
|
|
1291
|
-
openMenu(e);
|
|
1521
|
+
if (open) openMenu(e);
|
|
1292
1522
|
return false;
|
|
1293
1523
|
};
|
|
1294
|
-
const resetState = (e)
|
|
1524
|
+
const resetState = (e)=>{
|
|
1525
|
+
// * when autocomplete select mode not reset input value
|
|
1295
1526
|
if (!isSelectedFromAutocompleteRef.current) {
|
|
1296
1527
|
clearInput();
|
|
1297
1528
|
}
|
|
@@ -1303,148 +1534,164 @@ const useAutocomplete = ({ multiple: multipleProp, variant, inputValue: inputVal
|
|
|
1303
1534
|
closeMenu(e, 'select-option');
|
|
1304
1535
|
}
|
|
1305
1536
|
};
|
|
1306
|
-
const reset = (isFocus)
|
|
1537
|
+
const reset = (isFocus)=>{
|
|
1307
1538
|
resetState();
|
|
1308
1539
|
onSelectChange?.([]);
|
|
1309
1540
|
resetSuggestionList(isFocus);
|
|
1310
1541
|
};
|
|
1311
|
-
const keepHighlightedIndex = ()
|
|
1542
|
+
const keepHighlightedIndex = ()=>{
|
|
1312
1543
|
keepHighlightedIndexRef.current = true;
|
|
1313
1544
|
};
|
|
1314
|
-
|
|
1545
|
+
// * when outside options props props change also need set highlightedIndex to default
|
|
1546
|
+
useChange(()=>{
|
|
1315
1547
|
const shouldResetHighlightedIndex = !keepHighlightedIndexRef.current;
|
|
1316
1548
|
if (options !== undefined && shouldResetHighlightedIndex) {
|
|
1317
|
-
setHighlightedIndex(
|
|
1549
|
+
setHighlightedIndex(useAutocomplete_DEFAULT_HIGHLIGHTED_INDEX, {
|
|
1550
|
+
reason: 'auto'
|
|
1551
|
+
});
|
|
1318
1552
|
}
|
|
1319
|
-
}, ()
|
|
1320
|
-
|
|
1553
|
+
}, ()=>options);
|
|
1554
|
+
// * check that defaultHighlightedIndex
|
|
1555
|
+
const defaultHighlightedIndex = useMemo(()=>{
|
|
1321
1556
|
return autoHighlight ? getNextFocusableOption(0) : -1;
|
|
1322
|
-
}, [
|
|
1557
|
+
}, [
|
|
1558
|
+
autoHighlight,
|
|
1559
|
+
getNextFocusableOption
|
|
1560
|
+
]);
|
|
1561
|
+
// * only when menu is open set to defaultHighlightedIndex
|
|
1323
1562
|
if (isOpen) {
|
|
1324
|
-
if (highlightedIndexRef.current ===
|
|
1563
|
+
if (highlightedIndexRef.current === useAutocomplete_DEFAULT_HIGHLIGHTED_INDEX) {
|
|
1325
1564
|
let toIndex = defaultHighlightedIndex;
|
|
1326
1565
|
if (isAutocomplete && tags.length === 1) {
|
|
1327
1566
|
const itemText = getOptionLabel(tags[0]);
|
|
1328
|
-
const fIndex = optionItems.findIndex((x)
|
|
1567
|
+
const fIndex = optionItems.findIndex((x)=>getOptionLabel(x) === itemText);
|
|
1329
1568
|
if (fIndex > -1) {
|
|
1330
1569
|
toIndex = fIndex;
|
|
1331
1570
|
autoCompleteSelectedIndexRef.current = fIndex;
|
|
1332
1571
|
}
|
|
1333
1572
|
}
|
|
1334
|
-
setHighlightedIndex(toIndex, {
|
|
1335
|
-
|
|
1336
|
-
|
|
1573
|
+
setHighlightedIndex(toIndex, {
|
|
1574
|
+
reason: 'auto'
|
|
1575
|
+
});
|
|
1576
|
+
} else {
|
|
1337
1577
|
const currIndex = highlightedIndexRef.current;
|
|
1338
1578
|
const availableIndex = getNextFocusableOption(currIndex);
|
|
1339
1579
|
if (currIndex !== availableIndex) {
|
|
1340
|
-
requestAnimationFrame(()
|
|
1580
|
+
requestAnimationFrame(()=>{
|
|
1341
1581
|
setHighlightedIndex(availableIndex, {
|
|
1342
1582
|
reason: 'auto',
|
|
1343
|
-
reRender: true
|
|
1583
|
+
reRender: true
|
|
1344
1584
|
});
|
|
1345
1585
|
});
|
|
1346
1586
|
}
|
|
1347
1587
|
}
|
|
1348
1588
|
}
|
|
1349
|
-
const handleAutocompleteText = ()
|
|
1589
|
+
const handleAutocompleteText = ()=>{
|
|
1350
1590
|
if (isAutocomplete && tags.length > 0) {
|
|
1351
1591
|
const result = getOptionLabel(tags[0]);
|
|
1352
1592
|
onInputChangeProp?.(result);
|
|
1353
1593
|
}
|
|
1354
1594
|
};
|
|
1355
|
-
|
|
1595
|
+
// * use force update to sync open state when init is open
|
|
1596
|
+
useLayoutEffect(()=>{
|
|
1356
1597
|
if (isOpen) {
|
|
1357
1598
|
forceUpdate();
|
|
1358
1599
|
}
|
|
1359
1600
|
handleAutocompleteText();
|
|
1601
|
+
// oxlint-disable-next-line react-hooks/exhaustive-deps
|
|
1360
1602
|
}, []);
|
|
1361
|
-
const getInputProps = (props)
|
|
1603
|
+
const getInputProps = (props)=>{
|
|
1362
1604
|
const suggestionListInputProps = getSuggestionListInputProps(props);
|
|
1363
1605
|
return combineProps({
|
|
1364
1606
|
...suggestionListInputProps,
|
|
1365
|
-
onPaste: (e)
|
|
1607
|
+
onPaste: (e)=>{
|
|
1366
1608
|
if (freeSolo) {
|
|
1367
1609
|
const clipboardData = e.clipboardData;
|
|
1368
1610
|
const { position } = getSelectionPosition(inputRef);
|
|
1369
1611
|
const currText = inputValue ?? '';
|
|
1370
1612
|
const pasteText = clipboardData.getData('Text') ?? '';
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1613
|
+
// * because input can't get next line char(\n), we need get that value in onPaste
|
|
1614
|
+
// * get current input result use paste
|
|
1615
|
+
// * remember combine current input value, that will be use in onChange
|
|
1616
|
+
fromPasteString.current = currText.slice(0, position.start) + pasteText + currText.slice(position.end);
|
|
1375
1617
|
}
|
|
1376
1618
|
},
|
|
1377
|
-
onChange: (e)
|
|
1619
|
+
onChange: (e)=>{
|
|
1378
1620
|
const changeValue = fromPasteString.current || e.target.value;
|
|
1379
1621
|
handleInputChange(changeValue, undefined, e);
|
|
1622
|
+
// * when input value clear all, clear selected item
|
|
1380
1623
|
if (isAutocomplete && changeValue.length === 0) {
|
|
1381
1624
|
clearTags();
|
|
1382
1625
|
}
|
|
1383
1626
|
fromPasteString.current = '';
|
|
1384
1627
|
isInputValueChangedRef.current = true;
|
|
1385
1628
|
},
|
|
1386
|
-
onFocus: (e)
|
|
1629
|
+
onFocus: (e)=>{
|
|
1387
1630
|
setInputFocused(true);
|
|
1388
|
-
if (openOnFocus)
|
|
1389
|
-
|
|
1631
|
+
if (openOnFocus) openMenu(e);
|
|
1632
|
+
// * when autocomplete mode, always select all text when focus
|
|
1390
1633
|
if (isAutocomplete) {
|
|
1391
1634
|
inputRef.current?.select();
|
|
1392
1635
|
}
|
|
1636
|
+
// * reset stopAutoSelect in focus input
|
|
1393
1637
|
stopAutoSelectRef.current = false;
|
|
1394
1638
|
blurTags();
|
|
1395
1639
|
},
|
|
1396
|
-
onBlur: (e)
|
|
1640
|
+
onBlur: (e)=>{
|
|
1397
1641
|
setInputFocused(false);
|
|
1398
|
-
|
|
1399
|
-
|
|
1642
|
+
// mouse down suggestion item will change focus,
|
|
1643
|
+
// but we don't want to close popper in this case
|
|
1644
|
+
if (popperElementRef.current?.contains(e.relatedTarget)) return;
|
|
1400
1645
|
if (autoSelect && !stopAutoSelectRef.current) {
|
|
1401
|
-
if (!freeSolo)
|
|
1402
|
-
selectItem(e, optionItems[highlightedIndexRef.current]);
|
|
1646
|
+
if (!freeSolo) selectItem(e, optionItems[highlightedIndexRef.current]);
|
|
1403
1647
|
checkAndAddFreeSolo({}, e);
|
|
1404
1648
|
}
|
|
1649
|
+
// * when blur should restore text when have selected item
|
|
1405
1650
|
handleAutocompleteText();
|
|
1406
1651
|
closeMenu(e, 'blur');
|
|
1407
1652
|
},
|
|
1408
|
-
onCompositionStart: ()
|
|
1653
|
+
onCompositionStart: ()=>{
|
|
1409
1654
|
isCompositionRef.current = true;
|
|
1410
1655
|
},
|
|
1411
|
-
onCompositionEnd: async ()
|
|
1656
|
+
onCompositionEnd: async ()=>{
|
|
1657
|
+
// sleep for prevent tab in composition
|
|
1412
1658
|
await sleep(20);
|
|
1413
1659
|
isCompositionRef.current = false;
|
|
1414
1660
|
},
|
|
1415
|
-
onKeyDown: (e)
|
|
1661
|
+
onKeyDown: (e)=>{
|
|
1416
1662
|
keepHighlightedIndexRef.current = false;
|
|
1417
1663
|
onKeyDownProp?.(e, highlightedIndexRef.current);
|
|
1418
1664
|
const { isSelectRange, position } = getSelectionPosition(inputRef);
|
|
1419
1665
|
const currTagsLength = tags.length;
|
|
1420
|
-
if (!isSelectRange &&
|
|
1421
|
-
position.start === 0 &&
|
|
1422
|
-
currTagsLength > 0 &&
|
|
1423
|
-
e.key === 'Backspace') {
|
|
1666
|
+
if (!isSelectRange && position.start === 0 && currTagsLength > 0 && e.key === 'Backspace') {
|
|
1424
1667
|
stopAutoSelectRef.current = true;
|
|
1425
1668
|
focusLastTag();
|
|
1426
|
-
}
|
|
1427
|
-
else {
|
|
1669
|
+
} else {
|
|
1428
1670
|
const highlightedIndex = highlightedIndexRef.current;
|
|
1429
|
-
switch
|
|
1671
|
+
switch(e.key){
|
|
1430
1672
|
case 'Escape':
|
|
1431
1673
|
if (isOpen) {
|
|
1674
|
+
// Avoid Opera to exit fullscreen mode.
|
|
1432
1675
|
e.preventDefault();
|
|
1676
|
+
// Avoid the Modal to handle the event.
|
|
1433
1677
|
e.stopPropagation();
|
|
1434
1678
|
closeMenu(e, 'escape');
|
|
1435
1679
|
}
|
|
1436
1680
|
break;
|
|
1437
1681
|
case 'Tab':
|
|
1438
|
-
if (e.which === 229)
|
|
1439
|
-
return;
|
|
1682
|
+
if (e.which === 229) return;
|
|
1440
1683
|
if (getSleeping()) {
|
|
1441
1684
|
e.preventDefault();
|
|
1442
1685
|
e.stopPropagation();
|
|
1443
1686
|
return;
|
|
1444
1687
|
}
|
|
1688
|
+
// * when shift+ tab also not need autoSelect
|
|
1445
1689
|
if (e.shiftKey && currTagsLength > 0) {
|
|
1446
1690
|
stopAutoSelectRef.current = true;
|
|
1447
1691
|
}
|
|
1692
|
+
// ! [Browser] when in composition,
|
|
1693
|
+
// ! and keydown 'Tab' that will trigger twice keydown,
|
|
1694
|
+
// ! must check is that is not in composition mode
|
|
1448
1695
|
if (!e.shiftKey && !isCompositionRef.current) {
|
|
1449
1696
|
const currentHighlightItem = optionItems[highlightedIndex];
|
|
1450
1697
|
if (isOpen && currentHighlightItem) {
|
|
@@ -1452,32 +1699,24 @@ const useAutocomplete = ({ multiple: multipleProp, variant, inputValue: inputVal
|
|
|
1452
1699
|
e.preventDefault();
|
|
1453
1700
|
e.stopPropagation();
|
|
1454
1701
|
}
|
|
1455
|
-
}
|
|
1456
|
-
else if (checkAndAddFreeSolo({}, e)) {
|
|
1702
|
+
} else if (checkAndAddFreeSolo({}, e)) {
|
|
1457
1703
|
e.preventDefault();
|
|
1458
1704
|
e.stopPropagation();
|
|
1459
1705
|
}
|
|
1460
1706
|
}
|
|
1461
1707
|
break;
|
|
1462
1708
|
case 'Enter':
|
|
1463
|
-
if (e.which === 229)
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
checkAndAddFreeSolo({
|
|
1468
|
-
selectedItem: isOpen
|
|
1469
|
-
? optionItems[highlightedIndex]
|
|
1470
|
-
: undefined,
|
|
1471
|
-
}, e)) {
|
|
1709
|
+
if (e.which === 229) return;
|
|
1710
|
+
if (freeSolo && highlightedIndex === useAutocomplete_DEFAULT_HIGHLIGHTED_INDEX && checkAndAddFreeSolo({
|
|
1711
|
+
selectedItem: isOpen ? optionItems[highlightedIndex] : undefined
|
|
1712
|
+
}, e)) {
|
|
1472
1713
|
e.stopPropagation();
|
|
1473
1714
|
closeMenu(e, 'select-option');
|
|
1474
|
-
}
|
|
1475
|
-
else if (highlightedIndex !== DEFAULT_HIGHLIGHTED_INDEX &&
|
|
1476
|
-
isOpen &&
|
|
1477
|
-
!isCompositionRef.current) {
|
|
1715
|
+
} else if (highlightedIndex !== useAutocomplete_DEFAULT_HIGHLIGHTED_INDEX && isOpen && !isCompositionRef.current) {
|
|
1478
1716
|
selectItem(e, optionItems[highlightedIndex]);
|
|
1479
1717
|
e.stopPropagation();
|
|
1480
1718
|
}
|
|
1719
|
+
// always preventDefault for not inset all enter into textarea
|
|
1481
1720
|
e.preventDefault();
|
|
1482
1721
|
break;
|
|
1483
1722
|
case 'F10':
|
|
@@ -1489,6 +1728,7 @@ const useAutocomplete = ({ multiple: multipleProp, variant, inputValue: inputVal
|
|
|
1489
1728
|
e.preventDefault();
|
|
1490
1729
|
return;
|
|
1491
1730
|
}
|
|
1731
|
+
// * check state for is that should move key in option items
|
|
1492
1732
|
if (isAutocomplete && tags.length > 0 && !isOpen) {
|
|
1493
1733
|
openMenu(e);
|
|
1494
1734
|
e.preventDefault();
|
|
@@ -1505,66 +1745,70 @@ const useAutocomplete = ({ multiple: multipleProp, variant, inputValue: inputVal
|
|
|
1505
1745
|
}
|
|
1506
1746
|
}
|
|
1507
1747
|
},
|
|
1508
|
-
onMouseDown: toggleWithInput
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1748
|
+
onMouseDown: toggleWithInput ? (e)=>{
|
|
1749
|
+
/**
|
|
1750
|
+
* If the popup isn't open and `openOnFocus` is disabled we should
|
|
1751
|
+
* show the popup. If `openOnFocus` is enabled the `onFocus` handler
|
|
1752
|
+
* will take care of opening the popup when necessary.
|
|
1753
|
+
*
|
|
1754
|
+
* If the popup is open and the user hasn't typed a value we should
|
|
1755
|
+
* hide it, but if they have typed a value we shouldn't hide it to
|
|
1756
|
+
* ensure the the available results remain visible to the user.
|
|
1757
|
+
*/ if (!isOpen || isOpen && !inputValue) {
|
|
1758
|
+
toggleMenu(e, false);
|
|
1513
1759
|
}
|
|
1514
|
-
|
|
1760
|
+
} : undefined
|
|
1515
1761
|
}, props);
|
|
1516
1762
|
};
|
|
1517
|
-
const getInputAriaProps = (props)
|
|
1763
|
+
const getInputAriaProps = (props)=>{
|
|
1518
1764
|
const suggestionListInputAriaProps = getSuggestionListInputAriaProps(props);
|
|
1519
1765
|
return combineProps({
|
|
1520
1766
|
...suggestionListInputAriaProps,
|
|
1521
1767
|
readOnly,
|
|
1522
1768
|
unselectable: readOnly ? 'on' : undefined,
|
|
1523
|
-
'aria-owns': isOpen
|
|
1524
|
-
|
|
1525
|
-
: undefined,
|
|
1526
|
-
'aria-expanded': !!isOpen,
|
|
1769
|
+
'aria-owns': isOpen ? suggestionListInputAriaProps['aria-owns'] : undefined,
|
|
1770
|
+
'aria-expanded': !!isOpen
|
|
1527
1771
|
}, props);
|
|
1528
1772
|
};
|
|
1529
|
-
const getPopperProps = (restPopperProps)
|
|
1773
|
+
const getPopperProps = (restPopperProps)=>{
|
|
1530
1774
|
return combineProps({
|
|
1531
1775
|
id: `${AutocompleteId}-popper`,
|
|
1532
1776
|
role: 'presentation',
|
|
1533
|
-
onMouseDown: (e)
|
|
1777
|
+
onMouseDown: (e)=>{
|
|
1534
1778
|
e.preventDefault();
|
|
1535
1779
|
e.stopPropagation();
|
|
1536
|
-
}
|
|
1780
|
+
}
|
|
1537
1781
|
}, restPopperProps);
|
|
1538
1782
|
};
|
|
1539
|
-
const getToggleButtonProps = (props)
|
|
1783
|
+
const getToggleButtonProps = (props)=>{
|
|
1540
1784
|
return combineProps({
|
|
1541
|
-
disabled:
|
|
1785
|
+
disabled: !isOpen && optionItems.length === 0 || disabled || readOnly,
|
|
1542
1786
|
id: `${AutocompleteId}-toggle-button`,
|
|
1543
|
-
onMouseDown: (e)
|
|
1787
|
+
onMouseDown: (e)=>{
|
|
1544
1788
|
e.preventDefault();
|
|
1545
1789
|
e.stopPropagation();
|
|
1546
1790
|
},
|
|
1547
1791
|
onClick: toggleMenu,
|
|
1548
|
-
tabIndex: -1
|
|
1792
|
+
tabIndex: -1
|
|
1549
1793
|
}, props);
|
|
1550
1794
|
};
|
|
1551
|
-
const getClearButtonProps = (props)
|
|
1795
|
+
const getClearButtonProps = (props)=>{
|
|
1552
1796
|
const suggestionListClearButtonProps = getSuggestionListClearButtonProps(props);
|
|
1553
1797
|
return combineProps({
|
|
1554
1798
|
...suggestionListClearButtonProps,
|
|
1555
1799
|
disabled,
|
|
1556
|
-
onClick: (e)
|
|
1800
|
+
onClick: (e)=>{
|
|
1557
1801
|
onClear?.(e);
|
|
1558
1802
|
reset(true);
|
|
1559
|
-
}
|
|
1803
|
+
}
|
|
1560
1804
|
}, props);
|
|
1561
1805
|
};
|
|
1562
|
-
const getNoOptionsProps = (additionProps)
|
|
1806
|
+
const getNoOptionsProps = (additionProps)=>{
|
|
1563
1807
|
return combineProps({
|
|
1564
|
-
onClick: (e)
|
|
1808
|
+
onClick: (e)=>{
|
|
1565
1809
|
closeMenu(e, 'blur');
|
|
1566
1810
|
},
|
|
1567
|
-
role: 'presentation'
|
|
1811
|
+
role: 'presentation'
|
|
1568
1812
|
}, additionProps);
|
|
1569
1813
|
};
|
|
1570
1814
|
return {
|
|
@@ -1599,32 +1843,74 @@ const useAutocomplete = ({ multiple: multipleProp, variant, inputValue: inputVal
|
|
|
1599
1843
|
reset,
|
|
1600
1844
|
forceUpdate,
|
|
1601
1845
|
optionsGroupList,
|
|
1602
|
-
focused: focused ?? (tagFocused || inputFocused ? true : undefined),
|
|
1846
|
+
/** that state of current Autocomplete focused, both tag or input focused */ focused: focused ?? (tagFocused || inputFocused ? true : undefined),
|
|
1603
1847
|
id: AutocompleteId,
|
|
1604
1848
|
inputChanged: isInputValueChangedRef.current,
|
|
1605
1849
|
autoCompleteSelectedIndex: autoCompleteSelectedIndexRef.current,
|
|
1606
|
-
popperElementRef
|
|
1850
|
+
popperElementRef
|
|
1607
1851
|
};
|
|
1608
1852
|
};
|
|
1609
1853
|
|
|
1854
|
+
|
|
1855
|
+
|
|
1856
|
+
|
|
1857
|
+
|
|
1858
|
+
|
|
1859
|
+
|
|
1860
|
+
|
|
1861
|
+
|
|
1862
|
+
|
|
1863
|
+
|
|
1864
|
+
|
|
1865
|
+
|
|
1866
|
+
|
|
1867
|
+
|
|
1868
|
+
|
|
1869
|
+
|
|
1610
1870
|
const ChipSizeMap = {
|
|
1611
1871
|
xlarge: 'large',
|
|
1612
1872
|
large: 'medium',
|
|
1613
|
-
medium: 'small'
|
|
1873
|
+
medium: 'small'
|
|
1614
1874
|
};
|
|
1615
|
-
const InputComponent = forwardRef(({ startAdornment, containerRef, containerClassName, onContainerClick, ...rest }, ref)
|
|
1616
|
-
return
|
|
1875
|
+
const InputComponent = /*#__PURE__*/ forwardRef(({ startAdornment, containerRef, containerClassName, onContainerClick, ...rest }, ref)=>{
|
|
1876
|
+
return(// oxlint-disable-next-line jsx-a11y/click-events-have-key-events
|
|
1877
|
+
/*#__PURE__*/ jsxs("div", {
|
|
1878
|
+
ref: containerRef,
|
|
1879
|
+
className: containerClassName,
|
|
1880
|
+
onClick: onContainerClick,
|
|
1881
|
+
children: [
|
|
1882
|
+
startAdornment,
|
|
1883
|
+
/*#__PURE__*/ jsx("input", {
|
|
1884
|
+
...rest,
|
|
1885
|
+
ref: ref
|
|
1886
|
+
})
|
|
1887
|
+
]
|
|
1888
|
+
}));
|
|
1617
1889
|
});
|
|
1618
|
-
const TextInput = forwardRef(({ renderInput, ...rest }, ref)
|
|
1890
|
+
const TextInput = /*#__PURE__*/ forwardRef(({ renderInput, ...rest }, ref)=>{
|
|
1619
1891
|
if (renderInput) {
|
|
1620
|
-
return renderInput({
|
|
1892
|
+
return renderInput({
|
|
1893
|
+
...rest,
|
|
1894
|
+
ref
|
|
1895
|
+
});
|
|
1621
1896
|
}
|
|
1622
|
-
return jsx(
|
|
1897
|
+
return /*#__PURE__*/ jsx(TextField_TextField, {
|
|
1898
|
+
ref: ref,
|
|
1899
|
+
...rest
|
|
1900
|
+
});
|
|
1623
1901
|
});
|
|
1624
|
-
const
|
|
1625
|
-
const props = useThemeProps({
|
|
1902
|
+
const Autocomplete_Autocomplete = /*#__PURE__*/ forwardRef((inProps, ref)=>{
|
|
1903
|
+
const props = useThemeProps({
|
|
1904
|
+
props: inProps,
|
|
1905
|
+
name: 'Autocomplete'
|
|
1906
|
+
});
|
|
1626
1907
|
const isAutocomplete = props.variant === 'autocomplete';
|
|
1627
|
-
const { inputVariant = 'standard', variant, groupExpanded, onGroupExpanded, groupDefaultExpanded, groupVariant = 'normal', getExpandIconProps, groupBy, value: valueProp, autoHighlight = true, clearBtn = false, toggleButton = false, multiple = false, ClearButtonProps, onClear, ToggleButtonProps, getToggleButtonProps: getToggleButtonPropsProp, inputValue: inputValueProp, onKeyDown: onKeyDownProp, helperText: helperTextProp, options, openOnFocus = true, toggleWithInput = true, getOptionLabel = DEFAULT_GET_OPTION_LABEL, renderInput, label, inputRef: inputRefProp, error, placeholder, showPlaceholderWithTags, freeSolo, keyToTags = DEFAULT_KEY_TO_CHIPS, maxFreeSolo, SuggestionListProps: { virtualize = true, ...SuggestionListProps } = {}, autoSelect, PopperComponent = Popper, PopperProps: { anchorElType = 'root',
|
|
1908
|
+
const { inputVariant = 'standard', variant, groupExpanded, onGroupExpanded, groupDefaultExpanded, groupVariant = 'normal', getExpandIconProps, groupBy, value: valueProp, autoHighlight = true, clearBtn = false, toggleButton = false, multiple = false, ClearButtonProps, onClear, ToggleButtonProps, getToggleButtonProps: getToggleButtonPropsProp, inputValue: inputValueProp, onKeyDown: onKeyDownProp, helperText: helperTextProp, options, openOnFocus = true, toggleWithInput = true, getOptionLabel = DEFAULT_GET_OPTION_LABEL, renderInput, label, inputRef: inputRefProp, error, placeholder, showPlaceholderWithTags, freeSolo, keyToTags = DEFAULT_KEY_TO_CHIPS, maxFreeSolo, SuggestionListProps: { virtualize = true, ...SuggestionListProps } = {}, autoSelect, PopperComponent = Popper, PopperProps: { anchorElType = 'root', // transition: popperTransition,
|
|
1909
|
+
// TransitionComponent = virtualize ? RcFade : RcGrow,
|
|
1910
|
+
// transitionDuration: transitionDurationProp = 'auto',
|
|
1911
|
+
// TransitionProps: TransitionPropsProp = {},
|
|
1912
|
+
children: _popperChildrenIgnored, ...PopperProps } = {}, initialIsOpen, disabled, required: requiredProp, disableCloseOnSelect = multiple, onChange: onChangeProp, onInputChange: onInputChangeProp, onMaxFreeSolo, FormHelperTextProps, action, filterOptions, renderOption, renderTags, getOptionDisabled, renderGroup, renderNoOptions, noOptionsText = 'No matches found', inputProps, // InputProps: InputPropsProp,
|
|
1913
|
+
debug, disabledItemsHighlightable, open: openProp, addNoOptionItem, onOpen, onClose, focused, classes, className, fullWidth = true, size = 'xlarge', isOptionEqualToValue, ...rest } = props;
|
|
1628
1914
|
const cls = useClasses('autocomplete', classes, className);
|
|
1629
1915
|
const innerInputRef = useRef(null);
|
|
1630
1916
|
const textFieldRef = useRef(null);
|
|
@@ -1635,7 +1921,10 @@ const Autocomplete = forwardRef((inProps, ref) => {
|
|
|
1635
1921
|
const anchorElRef = anchorElType === 'input' ? formFieldContentRef : textFieldRef;
|
|
1636
1922
|
let oneOfTagError = false;
|
|
1637
1923
|
const required = requiredProp;
|
|
1638
|
-
const { focusInput, optionItems, optionsGroupList, tags: selectedItems, highlightedIndex, getToggleButtonProps, getTagProps, getTagListBoxProps, getSuggestionListProps, getPopperProps, getInputProps, getInputAriaProps,
|
|
1924
|
+
const { focusInput, optionItems, optionsGroupList, tags: selectedItems, highlightedIndex, getToggleButtonProps, getTagProps, getTagListBoxProps, getSuggestionListProps, getPopperProps, getInputProps, getInputAriaProps, // getLabelProps,
|
|
1925
|
+
getItemProps, isOpen, inputValue, activeIndex, setActiveIndex, setHighlightedIndex, onInputChange, keepHighlightedIndex, changeHighlightedIndexReason, closeMenu, openMenu, reset, getClearButtonProps, noOptionItem, getNoOptionsProps, isKeepHighlightedIndex, // focused: isAutocompleteFocused,
|
|
1926
|
+
// autoCompleteSelectedIndex,
|
|
1927
|
+
popperElementRef } = useAutocomplete({
|
|
1639
1928
|
focused,
|
|
1640
1929
|
open: openProp,
|
|
1641
1930
|
addNoOptionItem,
|
|
@@ -1676,103 +1965,242 @@ const Autocomplete = forwardRef((inProps, ref) => {
|
|
|
1676
1965
|
disabledItemsHighlightable,
|
|
1677
1966
|
onGroupExpanded,
|
|
1678
1967
|
isOptionEqualToValue,
|
|
1679
|
-
onClear
|
|
1968
|
+
onClear
|
|
1680
1969
|
});
|
|
1681
1970
|
const open = Boolean((isOpen || noOptionItem) && textFieldRef.current);
|
|
1682
1971
|
const { onBlur, ...InputProps } = getInputProps(inputProps);
|
|
1683
1972
|
const helperText = helperTextProp;
|
|
1684
1973
|
const toTextFieldRef = useForkRef(textFieldRef, ref);
|
|
1685
|
-
const startAdornment = (()
|
|
1974
|
+
const startAdornment = (()=>{
|
|
1686
1975
|
if (isAutocomplete || selectedItems.length === 0) {
|
|
1687
1976
|
return undefined;
|
|
1688
1977
|
}
|
|
1689
|
-
const getCustomizedTagProps = (selectedItem, index)
|
|
1978
|
+
const getCustomizedTagProps = (selectedItem, index)=>{
|
|
1690
1979
|
const tagProps = getTagProps({
|
|
1691
1980
|
selectedItem,
|
|
1692
1981
|
index,
|
|
1693
1982
|
label: getOptionLabel(selectedItem),
|
|
1694
|
-
disabled
|
|
1983
|
+
disabled
|
|
1695
1984
|
});
|
|
1696
1985
|
if (selectedItem.error) {
|
|
1697
1986
|
oneOfTagError = true;
|
|
1698
1987
|
}
|
|
1699
|
-
return {
|
|
1988
|
+
return {
|
|
1989
|
+
...tagProps,
|
|
1990
|
+
size: ChipSizeMap[size],
|
|
1991
|
+
className: cls('chip')
|
|
1992
|
+
};
|
|
1700
1993
|
};
|
|
1701
|
-
return renderTags
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1994
|
+
return renderTags ? renderTags(selectedItems, getCustomizedTagProps) : selectedItems.map((selectedItem, index)=>{
|
|
1995
|
+
const { children, key, ...itemChipProps } = getCustomizedTagProps(selectedItem, index);
|
|
1996
|
+
return /*#__PURE__*/ jsx(Chip_Chip, {
|
|
1997
|
+
"aria-label": `${itemChipProps.label}, press Backspace to remove`,
|
|
1998
|
+
color: selectedItem.error ? 'error' : undefined,
|
|
1999
|
+
...itemChipProps
|
|
2000
|
+
}, key);
|
|
2001
|
+
});
|
|
1707
2002
|
})();
|
|
1708
|
-
const endAdornment = (toggleButton || clearBtn) &&
|
|
2003
|
+
const endAdornment = (toggleButton || clearBtn) && /*#__PURE__*/ jsxs(Fragment, {
|
|
2004
|
+
children: [
|
|
2005
|
+
clearBtn && (selectedItems.length > 0 || inputValue) && /*#__PURE__*/ jsx(IconButton_IconButton, {
|
|
2006
|
+
shape: 'squircle',
|
|
2007
|
+
background: false,
|
|
2008
|
+
color: 'secondary',
|
|
2009
|
+
variant: "contained",
|
|
2010
|
+
size: "small",
|
|
2011
|
+
symbol: Xsm,
|
|
2012
|
+
...getClearButtonProps(combineProps({
|
|
2013
|
+
className: cls('clear')
|
|
2014
|
+
}, ClearButtonProps))
|
|
2015
|
+
}),
|
|
2016
|
+
toggleButton && /*#__PURE__*/ jsx(IconButton_IconButton, {
|
|
2017
|
+
"aria-hidden": true,
|
|
2018
|
+
tabIndex: -1,
|
|
2019
|
+
shape: 'squircle',
|
|
2020
|
+
color: 'secondary',
|
|
2021
|
+
background: false,
|
|
2022
|
+
variant: "contained",
|
|
2023
|
+
size: "small",
|
|
2024
|
+
className: cls('chevron', error && 'error-chevron', open && `${inputVariant}-expanded-chevron`),
|
|
2025
|
+
children: /*#__PURE__*/ jsx(ExpandCollapseCaret_ExpandCollapseCaret, {
|
|
2026
|
+
size: 'small',
|
|
2027
|
+
expanded: open
|
|
2028
|
+
}),
|
|
2029
|
+
...getToggleButtonProps({
|
|
1709
2030
|
...ToggleButtonProps,
|
|
1710
|
-
...getToggleButtonPropsProp?.(open)
|
|
1711
|
-
})
|
|
2031
|
+
...getToggleButtonPropsProp?.(open)
|
|
2032
|
+
})
|
|
2033
|
+
})
|
|
2034
|
+
]
|
|
2035
|
+
});
|
|
1712
2036
|
const TextFieldInputProps = {
|
|
1713
2037
|
'aria-label': typeof label === 'string' ? label : undefined,
|
|
1714
2038
|
...getTagListBoxProps(),
|
|
1715
2039
|
...InputProps,
|
|
1716
|
-
onBlur: debug ? undefined : onBlur
|
|
2040
|
+
onBlur: debug ? undefined : onBlur
|
|
1717
2041
|
};
|
|
1718
|
-
useImperativeHandle(action, ()
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
2042
|
+
useImperativeHandle(action, ()=>({
|
|
2043
|
+
getActiveIndex: ()=>activeIndex,
|
|
2044
|
+
setActiveIndex,
|
|
2045
|
+
getFilterResultItems: ()=>optionItems,
|
|
2046
|
+
getHighlightedIndex: ()=>highlightedIndex,
|
|
2047
|
+
setHighlightedIndex: (index)=>setHighlightedIndex(index, {
|
|
2048
|
+
reason: 'auto',
|
|
2049
|
+
reRender: true
|
|
2050
|
+
}),
|
|
2051
|
+
closeMenu,
|
|
2052
|
+
openMenu,
|
|
2053
|
+
reset,
|
|
2054
|
+
clearInput: ()=>onInputChange('', false),
|
|
2055
|
+
keepHighlightedIndex: ()=>keepHighlightedIndex(),
|
|
2056
|
+
focus: focusInput
|
|
2057
|
+
}));
|
|
1731
2058
|
const hasTags = selectedItems.length > 0;
|
|
1732
2059
|
const hasNoOptionItem = !!noOptionItem;
|
|
1733
2060
|
const shouldRenderNoOptions = renderNoOptions === true || typeof renderNoOptions === 'function';
|
|
1734
2061
|
const willRenderNoOptions = hasNoOptionItem && shouldRenderNoOptions;
|
|
1735
|
-
const
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
2062
|
+
// const popperRef: ComponentProps<typeof StyledPopper>['popperRef'] =
|
|
2063
|
+
// useRef(null);
|
|
2064
|
+
// const handleUpdatePopper = useEventCallback(() => {
|
|
2065
|
+
// popperRef.current?.update();
|
|
2066
|
+
// });
|
|
2067
|
+
// Track previous highlightedIndex to detect list re-initialization in SuggestionList
|
|
2068
|
+
// When prev is -1, it indicates the list needs scroll reset (menu opened, input changed, etc.)
|
|
2069
|
+
const prevHighlightedIndex = usePrevious(()=>highlightedIndex, true);
|
|
2070
|
+
const menuChildren = // * that root element for animation host
|
|
2071
|
+
isOpen && /*#__PURE__*/ jsxs(Fragment, {
|
|
2072
|
+
children: [
|
|
2073
|
+
/*#__PURE__*/ jsx(SuggestionList_SuggestionList, {
|
|
2074
|
+
// TODO: deprecated, need to check if it will cause error when variant is 'autocomplete'
|
|
2075
|
+
// selectedIndex={isAutocomplete ? autoCompleteSelectedIndex : undefined}
|
|
2076
|
+
highlightedIndex: highlightedIndex,
|
|
2077
|
+
prevHighlightedIndex: prevHighlightedIndex,
|
|
2078
|
+
optionsGroupList: optionsGroupList,
|
|
2079
|
+
options: optionItems,
|
|
2080
|
+
groupVariant: groupVariant,
|
|
2081
|
+
groupExpanded: groupExpanded,
|
|
2082
|
+
renderGroup: renderGroup,
|
|
2083
|
+
renderOption: renderOption,
|
|
2084
|
+
inputValue: inputValue,
|
|
2085
|
+
getItemProps: getItemProps,
|
|
2086
|
+
getSuggestionListProps: getSuggestionListProps,
|
|
2087
|
+
changeHighlightedIndexReason: changeHighlightedIndexReason,
|
|
2088
|
+
getOptionDisabled: getOptionDisabled,
|
|
2089
|
+
isKeepHighlightedIndex: isKeepHighlightedIndex,
|
|
2090
|
+
// onContainerHeightChange={handleUpdatePopper}
|
|
2091
|
+
maxContainerHeight: 180,
|
|
2092
|
+
getOptionLabel: getOptionLabel,
|
|
2093
|
+
position: "unset",
|
|
2094
|
+
virtualize: virtualize,
|
|
2095
|
+
...SuggestionListProps
|
|
2096
|
+
}),
|
|
2097
|
+
willRenderNoOptions && /*#__PURE__*/ jsx(Fragment, {
|
|
2098
|
+
children: renderNoOptions === true ? /*#__PURE__*/ jsx("div", {
|
|
2099
|
+
...getNoOptionsProps({
|
|
2100
|
+
className: cls('status-container')
|
|
2101
|
+
}),
|
|
2102
|
+
children: /*#__PURE__*/ jsx("span", {
|
|
2103
|
+
className: cls('status-label'),
|
|
2104
|
+
children: noOptionsText
|
|
2105
|
+
})
|
|
2106
|
+
}) : typeof renderNoOptions === 'function' ? renderNoOptions(getNoOptionsProps, noOptionItem) : null
|
|
2107
|
+
})
|
|
2108
|
+
]
|
|
2109
|
+
});
|
|
2110
|
+
const isPopperContentEmpty = !isOpen || !willRenderNoOptions && optionItems.length === 0;
|
|
1740
2111
|
const popperCls = useClasses('autocomplete-popper', PopperProps?.classes, PopperProps?.className);
|
|
1741
|
-
return
|
|
1742
|
-
|
|
1743
|
-
|
|
2112
|
+
return /*#__PURE__*/ jsxs(Fragment, {
|
|
2113
|
+
children: [
|
|
2114
|
+
/*#__PURE__*/ jsx(TextInput, {
|
|
2115
|
+
renderInput: renderInput,
|
|
2116
|
+
// hasTags={hasTags}
|
|
2117
|
+
variant: inputVariant,
|
|
2118
|
+
ref: toTextFieldRef,
|
|
2119
|
+
inputRef: inputRef,
|
|
2120
|
+
fullWidth: fullWidth,
|
|
2121
|
+
placeholder: !hasTags || hasTags && multiple && showPlaceholderWithTags ? placeholder : undefined,
|
|
2122
|
+
label: label,
|
|
2123
|
+
disabled: disabled,
|
|
2124
|
+
required: required,
|
|
2125
|
+
// * if below one of tag is error, that main Autocomplete default will be error
|
|
2126
|
+
error: error ?? oneOfTagError,
|
|
2127
|
+
FormHelperTextProps: FormHelperTextProps,
|
|
2128
|
+
inputComponent: InputComponent,
|
|
2129
|
+
endAdornment: endAdornment,
|
|
2130
|
+
helperText: helperText,
|
|
2131
|
+
size: size,
|
|
2132
|
+
...combineProps({
|
|
1744
2133
|
RootProps: {
|
|
1745
|
-
contentRef: formFieldContentRef
|
|
2134
|
+
contentRef: formFieldContentRef
|
|
1746
2135
|
},
|
|
1747
2136
|
inputProps: {
|
|
1748
2137
|
startAdornment,
|
|
1749
2138
|
containerRef: inputContainerRef,
|
|
1750
2139
|
...getInputAriaProps(),
|
|
1751
2140
|
...TextFieldInputProps,
|
|
1752
|
-
containerClassName: clsx(cls('input-container', `input-container-${size}`, `${inputVariant}-input-container`), TextFieldInputProps?.containerClassName)
|
|
1753
|
-
}
|
|
1754
|
-
}, rest),
|
|
1755
|
-
|
|
1756
|
-
|
|
2141
|
+
containerClassName: clsx(cls('input-container', `input-container-${size}`, `${inputVariant}-input-container`), TextFieldInputProps?.containerClassName)
|
|
2142
|
+
}
|
|
2143
|
+
}, rest),
|
|
2144
|
+
classes: {
|
|
2145
|
+
root: cls('root', {
|
|
2146
|
+
error
|
|
2147
|
+
}),
|
|
2148
|
+
formFieldContent: cls('form-field-content', `${inputVariant}-form-field-content`, `form-field-content-${size}`, open && `${inputVariant}-expanded`, {
|
|
2149
|
+
expanded: open
|
|
2150
|
+
}),
|
|
1757
2151
|
standard: cls('standard'),
|
|
1758
2152
|
outlined: cls('outlined'),
|
|
1759
2153
|
endAdornment: cls('end-adornment'),
|
|
1760
|
-
input: cls('input', `input-${size}`)
|
|
1761
|
-
},
|
|
1762
|
-
|
|
1763
|
-
|
|
2154
|
+
input: cls('input', `input-${size}`)
|
|
2155
|
+
},
|
|
2156
|
+
value: inputValue,
|
|
2157
|
+
clearBtn: false
|
|
2158
|
+
}),
|
|
2159
|
+
/*#__PURE__*/ jsx(AnimatePresence, {
|
|
2160
|
+
children: open && /*#__PURE__*/ jsx(PopperComponent, {
|
|
2161
|
+
matchAnchorWidth: true,
|
|
2162
|
+
offset: 2,
|
|
2163
|
+
...getPopperProps(PopperProps),
|
|
2164
|
+
className: popperCls('root'),
|
|
2165
|
+
ref: popperElementRef,
|
|
2166
|
+
anchorEl: anchorElRef.current,
|
|
2167
|
+
children: /*#__PURE__*/ jsx(motion.div, {
|
|
2168
|
+
...getFade({
|
|
2169
|
+
prefersReducedMotion
|
|
2170
|
+
}),
|
|
2171
|
+
className: popperCls('paper', {
|
|
2172
|
+
empty: isPopperContentEmpty
|
|
2173
|
+
}),
|
|
2174
|
+
children: menuChildren
|
|
2175
|
+
})
|
|
2176
|
+
})
|
|
2177
|
+
})
|
|
2178
|
+
]
|
|
2179
|
+
});
|
|
1764
2180
|
});
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
2181
|
+
Autocomplete_Autocomplete.displayName = 'SuiAutocomplete';
|
|
2182
|
+
|
|
2183
|
+
/**
|
|
2184
|
+
* default Autocomplete filter options method, use with `variant="tags"`
|
|
2185
|
+
*
|
|
2186
|
+
* - filter options by input value and selected items
|
|
2187
|
+
* - uses `isOptionEqualToValue` to determine if an option is already selected
|
|
2188
|
+
*/ const RcAutocompleteDefaultFilterOptions = (options, { inputValue, getOptionLabel, selectedItems, isOptionEqualToValue })=>{
|
|
2189
|
+
return options.filter((item)=>{
|
|
2190
|
+
// Check if this option is already selected using the equality function
|
|
2191
|
+
const isSelected = selectedItems.some((selected)=>isOptionEqualToValue(item, selected));
|
|
2192
|
+
if (isSelected) return false;
|
|
2193
|
+
// Filter by input text
|
|
1771
2194
|
const optionLabel = getOptionLabel?.(item) || '';
|
|
1772
|
-
return optionLabel
|
|
1773
|
-
.toLowerCase()
|
|
1774
|
-
.startsWith(inputValue?.toLowerCase() || '');
|
|
2195
|
+
return optionLabel.toLowerCase().startsWith(inputValue?.toLowerCase() || '');
|
|
1775
2196
|
});
|
|
1776
2197
|
};
|
|
1777
2198
|
|
|
1778
|
-
|
|
2199
|
+
|
|
2200
|
+
|
|
2201
|
+
|
|
2202
|
+
|
|
2203
|
+
|
|
2204
|
+
|
|
2205
|
+
|
|
2206
|
+
export { Autocomplete as autocompleteStyles, Autocomplete_Autocomplete as Autocomplete, DEFAULT_GET_OPTION_LABEL, DEFAULT_KEY_TO_CHIPS, DEFAULT_PADDING, RcAutocompleteDefaultFilterOptions, SuggestionList as suggestionListStyles, SuggestionListItem, SuggestionList_SuggestionList as SuggestionList, getPaddingValue, isFirstInGroup, stringArrToRegExp, useAutocomplete, useAutocompleteGroup, useAutocompleteTag, useDynamicHeight, useHighlightScroll, useSuggestionList };
|