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