@onewelcome/react-lib-components 6.4.4 → 6.6.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/dist/cjs/Form/FileUpload/FileItem/FileItem.cjs.js +1 -1
- package/dist/cjs/Form/FileUpload/FileItem/FileItem.cjs.js.map +1 -1
- package/dist/cjs/Form/FileUpload/FileItem/FileItem.module.cjs.js +2 -0
- package/dist/cjs/Form/FileUpload/FileItem/FileItem.module.cjs.js.map +1 -0
- package/dist/cjs/Form/Select/MultiSelect/MultiOption.cjs.js +1 -1
- package/dist/cjs/Form/Select/MultiSelect/MultiOption.cjs.js.map +1 -1
- package/dist/cjs/Form/Select/MultiSelect/MultiSelect.cjs.js +1 -1
- package/dist/cjs/Form/Select/MultiSelect/MultiSelect.cjs.js.map +1 -1
- package/dist/cjs/Form/Select/MultiSelect/MultiSelect.module.cjs.js +1 -1
- package/dist/cjs/Form/Select/MultiSelect/SelectButton.cjs.js +1 -1
- package/dist/cjs/Form/Select/MultiSelect/SelectButton.cjs.js.map +1 -1
- package/dist/cjs/Form/Select/MultiSelect/SelectButton.module.cjs.js +1 -1
- package/dist/cjs/Form/Select/MultiSelect/SelectedOptions.cjs.js +1 -1
- package/dist/cjs/Form/Select/MultiSelect/SelectedOptions.cjs.js.map +1 -1
- package/dist/cjs/Form/Select/MultiSelect/useArrowNavigation.cjs.js +2 -0
- package/dist/cjs/Form/Select/MultiSelect/useArrowNavigation.cjs.js.map +1 -0
- package/dist/cjs/Form/Select/MultiSelect/useSearch.cjs.js +2 -0
- package/dist/cjs/Form/Select/MultiSelect/useSearch.cjs.js.map +1 -0
- package/dist/cjs/Form/Select/SingleSelect/Option.cjs.js +1 -1
- package/dist/cjs/Form/Select/SingleSelect/Option.cjs.js.map +1 -1
- package/dist/cjs/Form/Select/SingleSelect/Select.cjs.js +1 -1
- package/dist/cjs/Form/Select/SingleSelect/Select.cjs.js.map +1 -1
- package/dist/cjs/Form/Select/SingleSelect/Select.module.cjs.js +1 -1
- package/dist/cjs/Form/Select/SingleSelect/useArrowNavigation.cjs.js +2 -0
- package/dist/cjs/Form/Select/SingleSelect/useArrowNavigation.cjs.js.map +1 -0
- package/dist/cjs/Form/Select/SingleSelect/useSearch.cjs.js +2 -0
- package/dist/cjs/Form/Select/SingleSelect/useSearch.cjs.js.map +1 -0
- package/dist/cjs/Form/Select/useAddNewBtn.cjs.js +1 -1
- package/dist/cjs/Form/Select/useAddNewBtn.cjs.js.map +1 -1
- package/dist/cjs/Form/Select/useAddNewBtn.module.cjs.js +1 -1
- package/dist/cjs/Form/Select/useSelectPositionList.cjs.js +2 -0
- package/dist/cjs/Form/Select/useSelectPositionList.cjs.js.map +1 -0
- package/dist/cjs/Notifications/BaseModal/BaseModalActions/BaseModalActions.cjs.js +1 -1
- package/dist/cjs/Notifications/BaseModal/BaseModalActions/BaseModalActions.cjs.js.map +1 -1
- package/dist/cjs/Notifications/Dialog/Dialog.cjs.js.map +1 -1
- package/dist/cjs/Notifications/Dialog/DialogTitle/DialogTitle.cjs.js +1 -1
- package/dist/cjs/Notifications/Dialog/DialogTitle/DialogTitle.cjs.js.map +1 -1
- package/dist/cjs/Tooltip/Tooltip.module.cjs.js +1 -1
- package/dist/cjs/src/components/Form/Select/MultiSelect/MultiSelect.test.d.ts +63 -1
- package/dist/cjs/src/components/Form/Select/MultiSelect/MultiSelectKeyboardNavigation.test.d.ts +1 -0
- package/dist/cjs/src/components/Form/Select/MultiSelect/SelectedOptions.d.ts +2 -1
- package/dist/cjs/src/components/Form/Select/MultiSelect/useArrowNavigation.d.ts +16 -0
- package/dist/cjs/src/components/Form/Select/MultiSelect/useSearch.d.ts +34 -0
- package/dist/cjs/src/components/Form/Select/Select.interfaces.d.ts +1 -0
- package/dist/cjs/src/components/Form/Select/SingleSelect/Option.d.ts +1 -0
- package/dist/cjs/src/components/Form/Select/SingleSelect/useArrowNavigation.d.ts +5 -0
- package/dist/cjs/src/components/Form/Select/{useSearch.d.ts → SingleSelect/useSearch.d.ts} +2 -1
- package/dist/cjs/src/components/Form/Select/useAddNewBtn.d.ts +10 -3
- package/dist/cjs/src/components/Form/Select/useSelectPositionList.d.ts +12 -0
- package/dist/cjs/src/components/Notifications/Dialog/Dialog.d.ts +1 -1
- package/dist/cjs/src/util/helper.cjs.js +1 -1
- package/dist/cjs/src/util/helper.cjs.js.map +1 -1
- package/dist/cjs/src/util/helper.d.ts +1 -0
- package/dist/esm/Form/FileUpload/FileItem/FileItem.esm.js +1 -1
- package/dist/esm/Form/FileUpload/FileItem/FileItem.esm.js.map +1 -1
- package/dist/esm/Form/FileUpload/FileItem/FileItem.module.esm.js +2 -0
- package/dist/esm/Form/FileUpload/FileItem/FileItem.module.esm.js.map +1 -0
- package/dist/esm/Form/Select/MultiSelect/MultiOption.esm.js +1 -1
- package/dist/esm/Form/Select/MultiSelect/MultiOption.esm.js.map +1 -1
- package/dist/esm/Form/Select/MultiSelect/MultiSelect.esm.js +1 -1
- package/dist/esm/Form/Select/MultiSelect/MultiSelect.esm.js.map +1 -1
- package/dist/esm/Form/Select/MultiSelect/MultiSelect.module.esm.js +1 -1
- package/dist/esm/Form/Select/MultiSelect/SelectButton.esm.js +1 -1
- package/dist/esm/Form/Select/MultiSelect/SelectButton.esm.js.map +1 -1
- package/dist/esm/Form/Select/MultiSelect/SelectButton.module.esm.js +1 -1
- package/dist/esm/Form/Select/MultiSelect/SelectedOptions.esm.js +1 -1
- package/dist/esm/Form/Select/MultiSelect/SelectedOptions.esm.js.map +1 -1
- package/dist/esm/Form/Select/MultiSelect/useArrowNavigation.esm.js +2 -0
- package/dist/esm/Form/Select/MultiSelect/useArrowNavigation.esm.js.map +1 -0
- package/dist/esm/Form/Select/MultiSelect/useSearch.esm.js +2 -0
- package/dist/esm/Form/Select/MultiSelect/useSearch.esm.js.map +1 -0
- package/dist/esm/Form/Select/SingleSelect/Option.esm.js +1 -1
- package/dist/esm/Form/Select/SingleSelect/Option.esm.js.map +1 -1
- package/dist/esm/Form/Select/SingleSelect/Select.esm.js +1 -1
- package/dist/esm/Form/Select/SingleSelect/Select.esm.js.map +1 -1
- package/dist/esm/Form/Select/SingleSelect/Select.module.esm.js +1 -1
- package/dist/esm/Form/Select/SingleSelect/useArrowNavigation.esm.js +2 -0
- package/dist/esm/Form/Select/SingleSelect/useArrowNavigation.esm.js.map +1 -0
- package/dist/esm/Form/Select/SingleSelect/useSearch.esm.js +2 -0
- package/dist/esm/Form/Select/SingleSelect/useSearch.esm.js.map +1 -0
- package/dist/esm/Form/Select/useAddNewBtn.esm.js +1 -1
- package/dist/esm/Form/Select/useAddNewBtn.esm.js.map +1 -1
- package/dist/esm/Form/Select/useAddNewBtn.module.esm.js +1 -1
- package/dist/esm/Form/Select/useSelectPositionList.esm.js +2 -0
- package/dist/esm/Form/Select/useSelectPositionList.esm.js.map +1 -0
- package/dist/esm/Notifications/BaseModal/BaseModalActions/BaseModalActions.esm.js +1 -1
- package/dist/esm/Notifications/BaseModal/BaseModalActions/BaseModalActions.esm.js.map +1 -1
- package/dist/esm/Notifications/Dialog/Dialog.esm.js.map +1 -1
- package/dist/esm/Notifications/Dialog/DialogTitle/DialogTitle.esm.js +1 -1
- package/dist/esm/Notifications/Dialog/DialogTitle/DialogTitle.esm.js.map +1 -1
- package/dist/esm/Tooltip/Tooltip.module.esm.js +1 -1
- package/dist/esm/src/components/Form/Select/MultiSelect/MultiSelect.test.d.ts +63 -1
- package/dist/esm/src/components/Form/Select/MultiSelect/MultiSelectKeyboardNavigation.test.d.ts +1 -0
- package/dist/esm/src/components/Form/Select/MultiSelect/SelectedOptions.d.ts +2 -1
- package/dist/esm/src/components/Form/Select/MultiSelect/useArrowNavigation.d.ts +16 -0
- package/dist/esm/src/components/Form/Select/MultiSelect/useSearch.d.ts +34 -0
- package/dist/esm/src/components/Form/Select/Select.interfaces.d.ts +1 -0
- package/dist/esm/src/components/Form/Select/SingleSelect/Option.d.ts +1 -0
- package/dist/esm/src/components/Form/Select/SingleSelect/useArrowNavigation.d.ts +5 -0
- package/dist/esm/src/components/Form/Select/{useSearch.d.ts → SingleSelect/useSearch.d.ts} +2 -1
- package/dist/esm/src/components/Form/Select/useAddNewBtn.d.ts +10 -3
- package/dist/esm/src/components/Form/Select/useSelectPositionList.d.ts +12 -0
- package/dist/esm/src/components/Notifications/Dialog/Dialog.d.ts +1 -1
- package/dist/esm/src/util/helper.d.ts +1 -0
- package/dist/esm/src/util/helper.esm.js +1 -1
- package/dist/esm/src/util/helper.esm.js.map +1 -1
- package/package.json +6 -6
- package/src/components/Form/FileUpload/FileItem/FileItem.tsx +1 -1
- package/src/components/Form/Select/MultiSelect/MultiOption.tsx +36 -3
- package/src/components/Form/Select/MultiSelect/MultiSelect.module.scss +29 -19
- package/src/components/Form/Select/MultiSelect/MultiSelect.tsx +85 -62
- package/src/components/Form/Select/MultiSelect/SelectButton.module.scss +1 -1
- package/src/components/Form/Select/MultiSelect/SelectButton.tsx +1 -1
- package/src/components/Form/Select/MultiSelect/SelectedOptions.tsx +5 -4
- package/src/components/Form/Select/MultiSelect/useArrowNavigation.ts +128 -0
- package/src/components/Form/Select/MultiSelect/useSearch.tsx +126 -0
- package/src/components/Form/Select/Select.interfaces.ts +1 -0
- package/src/components/Form/Select/SingleSelect/Option.tsx +15 -4
- package/src/components/Form/Select/SingleSelect/Select.module.scss +13 -2
- package/src/components/Form/Select/SingleSelect/Select.tsx +7 -3
- package/src/components/Form/Select/{SelectService.ts → SingleSelect/useArrowNavigation.ts} +1 -101
- package/src/components/Form/Select/{useSearch.tsx → SingleSelect/useSearch.tsx} +3 -2
- package/src/components/Form/Select/useAddNewBtn.module.scss +18 -4
- package/src/components/Form/Select/useAddNewBtn.tsx +42 -8
- package/src/components/Form/Select/useSelectPositionList.ts +113 -0
- package/src/components/Notifications/BaseModal/BaseModalActions/BaseModalActions.tsx +1 -1
- package/src/components/Notifications/Dialog/Dialog.tsx +1 -1
- package/src/components/Notifications/Dialog/DialogTitle/DialogTitle.tsx +5 -3
- package/src/components/Tooltip/Tooltip.module.scss +1 -1
- package/src/util/helper.tsx +2 -0
- package/dist/cjs/Form/FileUpload/FileItem/FileItem.modules.cjs.js +0 -2
- package/dist/cjs/Form/FileUpload/FileItem/FileItem.modules.cjs.js.map +0 -1
- package/dist/cjs/Form/Select/SelectService.cjs.js +0 -2
- package/dist/cjs/Form/Select/SelectService.cjs.js.map +0 -1
- package/dist/cjs/Form/Select/useSearch.cjs.js +0 -2
- package/dist/cjs/Form/Select/useSearch.cjs.js.map +0 -1
- package/dist/cjs/src/components/Form/Select/SelectService.d.ts +0 -17
- package/dist/esm/Form/FileUpload/FileItem/FileItem.modules.esm.js +0 -2
- package/dist/esm/Form/FileUpload/FileItem/FileItem.modules.esm.js.map +0 -1
- package/dist/esm/Form/Select/SelectService.esm.js +0 -2
- package/dist/esm/Form/Select/SelectService.esm.js.map +0 -1
- package/dist/esm/Form/Select/useSearch.esm.js +0 -2
- package/dist/esm/Form/Select/useSearch.esm.js.map +0 -1
- package/dist/esm/src/components/Form/Select/SelectService.d.ts +0 -17
- /package/src/components/Form/FileUpload/FileItem/{FileItem.modules.scss → FileItem.module.scss} +0 -0
|
@@ -17,28 +17,35 @@
|
|
|
17
17
|
import classes from "./MultiSelect.module.scss";
|
|
18
18
|
|
|
19
19
|
import React, {
|
|
20
|
-
createRef,
|
|
21
20
|
ForwardRefRenderFunction,
|
|
22
21
|
Fragment,
|
|
23
22
|
ReactElement,
|
|
23
|
+
createRef,
|
|
24
24
|
useEffect,
|
|
25
25
|
useRef,
|
|
26
26
|
useState
|
|
27
27
|
} from "react";
|
|
28
|
-
import { Icon, Icons } from "../../../Icon/Icon";
|
|
29
28
|
import { useBodyClick } from "../../../../hooks/useBodyClick";
|
|
30
|
-
import readyclasses from "../../../../readyclasses.module.scss";
|
|
31
|
-
import { filterProps } from "../../../../util/helper";
|
|
32
|
-
import { useArrowNavigation, useSelectPositionList } from "../SelectService";
|
|
33
29
|
import { useDetermineStatusIcon } from "../../../../hooks/useDetermineStatusIcon";
|
|
34
|
-
import
|
|
35
|
-
import {
|
|
30
|
+
import readyclasses from "../../../../readyclasses.module.scss";
|
|
31
|
+
import { escapeRegExp, filterProps, generateID } from "../../../../util/helper";
|
|
32
|
+
import { Icon, Icons } from "../../../Icon/Icon";
|
|
36
33
|
import { MultiSelectProps } from "../Select.interfaces";
|
|
37
|
-
import { useSearch } from "../useSearch";
|
|
38
34
|
import { useAddNewBtn } from "../useAddNewBtn";
|
|
35
|
+
import { useSelectPositionList } from "../useSelectPositionList";
|
|
36
|
+
import { SelectButton } from "./SelectButton";
|
|
37
|
+
import { Display, SelectedOptions } from "./SelectedOptions";
|
|
38
|
+
import { useArrowNavigation } from "./useArrowNavigation";
|
|
39
|
+
import { useSearch } from "./useSearch";
|
|
40
|
+
|
|
41
|
+
const getOptionId = (multiSelectId: string, optionIndex: number) =>
|
|
42
|
+
`${multiSelectId}_option${optionIndex}`;
|
|
43
|
+
|
|
44
|
+
const getListboxId = (multiSelectId: string) => `${multiSelectId}_listbox`;
|
|
39
45
|
|
|
40
46
|
const MultiSelectComponent: ForwardRefRenderFunction<HTMLSelectElement, MultiSelectProps> = (
|
|
41
47
|
{
|
|
48
|
+
id,
|
|
42
49
|
children,
|
|
43
50
|
name,
|
|
44
51
|
disabled = false,
|
|
@@ -56,42 +63,51 @@ const MultiSelectComponent: ForwardRefRenderFunction<HTMLSelectElement, MultiSel
|
|
|
56
63
|
noResultsLabel = "No results found",
|
|
57
64
|
onChange,
|
|
58
65
|
addNew,
|
|
59
|
-
search,
|
|
66
|
+
search = { enabled: true, renderThreshold: 0 },
|
|
60
67
|
...rest
|
|
61
68
|
}: MultiSelectProps,
|
|
62
69
|
ref
|
|
63
70
|
) => {
|
|
71
|
+
const multiSelectId = useRef(id ?? generateID(20));
|
|
64
72
|
const [expanded, setExpanded] = useState(false);
|
|
65
|
-
const [display, setDisplay] = useState<
|
|
73
|
+
const [display, setDisplay] = useState<Display[]>([]);
|
|
66
74
|
const containerReference = useRef<HTMLDivElement>(null);
|
|
67
75
|
const optionListReference = useRef<HTMLDivElement>(null);
|
|
68
|
-
const [focusedSelectItem, setFocusedSelectItem] = useState(
|
|
76
|
+
const [focusedSelectItem, setFocusedSelectItem] = useState(0);
|
|
69
77
|
const [shouldClick, setShouldClick] =
|
|
70
78
|
useState(
|
|
71
79
|
false
|
|
72
80
|
); /** We need this, because whenever we use the arrow keys to select the select item, and we focus the currently selected item it fires the "click" listener in Option component. Instead, we only want this to fire if we press "enter" or "spacebar" so we set this to true whenever that is the case, and back to false when it has been executed. */
|
|
73
81
|
const [shouldFocusButtonAfterClose, setShouldFocusButtonAfterClose] = useState(false);
|
|
74
|
-
const optionsVisibleCount =
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
searchInputRef,
|
|
80
|
-
setIsSearching,
|
|
81
|
-
searchThreshold,
|
|
82
|
-
searchVisible
|
|
83
|
-
} = useSearch({
|
|
82
|
+
const [optionsVisibleCount, setOptionsVisibleCount] = useState(
|
|
83
|
+
React.Children.count(children) - display.length
|
|
84
|
+
);
|
|
85
|
+
const { filter, renderSearch, searchInputRef, resetSearchState, searchVisible } = useSearch({
|
|
86
|
+
selectId: multiSelectId.current,
|
|
84
87
|
expanded,
|
|
85
88
|
search,
|
|
86
89
|
searchInputClassName: classes["select-search"],
|
|
87
90
|
optionsCount: optionsVisibleCount,
|
|
91
|
+
focusedSelectItem,
|
|
88
92
|
setFocusedSelectItem,
|
|
89
93
|
searchInputProps,
|
|
90
|
-
searchPlaceholder
|
|
94
|
+
searchPlaceholder,
|
|
95
|
+
describedBy,
|
|
96
|
+
getOptionId,
|
|
97
|
+
getListboxId
|
|
91
98
|
});
|
|
92
99
|
const { addBtnRef, addNewBtnOptionsContainerClassName, renderAddNew } = useAddNewBtn({
|
|
100
|
+
id: getOptionId(multiSelectId.current, optionsVisibleCount),
|
|
93
101
|
addNew,
|
|
94
|
-
filter
|
|
102
|
+
filter,
|
|
103
|
+
focusedSelectItem,
|
|
104
|
+
optionsCount: optionsVisibleCount,
|
|
105
|
+
searchInputRef,
|
|
106
|
+
shouldClick,
|
|
107
|
+
onClickCallback: () => {
|
|
108
|
+
setShouldClick(false);
|
|
109
|
+
resetSearchState();
|
|
110
|
+
}
|
|
95
111
|
});
|
|
96
112
|
|
|
97
113
|
const nativeSelect = (ref as React.RefObject<HTMLSelectElement>) || createRef();
|
|
@@ -110,8 +126,8 @@ const MultiSelectComponent: ForwardRefRenderFunction<HTMLSelectElement, MultiSel
|
|
|
110
126
|
});
|
|
111
127
|
nativeSelect.current.dispatchEvent(new Event("change", { bubbles: true }));
|
|
112
128
|
}
|
|
113
|
-
|
|
114
129
|
setExpanded(false);
|
|
130
|
+
resetSearchState();
|
|
115
131
|
};
|
|
116
132
|
|
|
117
133
|
const onSelectedOptionRemoveHandler = (value: string) => {
|
|
@@ -126,33 +142,24 @@ const MultiSelectComponent: ForwardRefRenderFunction<HTMLSelectElement, MultiSel
|
|
|
126
142
|
const { onArrowNavigation } = useArrowNavigation({
|
|
127
143
|
expanded,
|
|
128
144
|
setExpanded,
|
|
129
|
-
isSearching,
|
|
130
|
-
setIsSearching,
|
|
131
145
|
setFocusedSelectItem,
|
|
132
|
-
onOptionChangeHandler,
|
|
133
146
|
childrenCount: optionsVisibleCount,
|
|
134
147
|
setShouldClick,
|
|
135
|
-
searchInputRef,
|
|
136
148
|
addBtnRef,
|
|
137
|
-
|
|
149
|
+
searchInputRef,
|
|
150
|
+
customSelectButtonRef,
|
|
151
|
+
onClose: resetSearchState
|
|
138
152
|
});
|
|
139
153
|
|
|
140
154
|
const { listPosition, opacity, optionsListMaxHeight, setListPosition, setOpacity } =
|
|
141
155
|
useSelectPositionList({ expanded, optionListReference, containerReference, addBtnRef });
|
|
142
156
|
|
|
143
|
-
const syncDisplayValue = (
|
|
144
|
-
const
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
fixed: curOption.props.fixed
|
|
150
|
-
};
|
|
151
|
-
}
|
|
152
|
-
return prevOption;
|
|
153
|
-
},
|
|
154
|
-
{} as Record<string, Display>
|
|
155
|
-
);
|
|
157
|
+
const syncDisplayValue = (values: string[]) => {
|
|
158
|
+
const options = React.Children.map(children, child => child);
|
|
159
|
+
const displayArray: Display[] = values.map(value => {
|
|
160
|
+
const option = options.find(option => option.props.value === value);
|
|
161
|
+
return { value, label: option?.props.children, fixed: option?.props.fixed };
|
|
162
|
+
});
|
|
156
163
|
setDisplay(displayArray);
|
|
157
164
|
};
|
|
158
165
|
|
|
@@ -177,31 +184,37 @@ const MultiSelectComponent: ForwardRefRenderFunction<HTMLSelectElement, MultiSel
|
|
|
177
184
|
type ReactChildrenType = ReturnType<typeof React.Children.toArray>;
|
|
178
185
|
|
|
179
186
|
const filterOutSelectedChildren = (internalChildren: ReactChildrenType) => {
|
|
180
|
-
const selectedValues = Object.keys(display);
|
|
181
187
|
return internalChildren.filter(
|
|
182
188
|
child =>
|
|
183
189
|
typeof child === "object" &&
|
|
184
190
|
"props" in child &&
|
|
185
|
-
!
|
|
191
|
+
!display.find(option => option.value === child.props.value)
|
|
186
192
|
);
|
|
187
193
|
};
|
|
188
194
|
|
|
189
|
-
|
|
195
|
+
let results;
|
|
196
|
+
if (filter !== "") {
|
|
190
197
|
const filteredChildren = React.Children.toArray(children).filter(
|
|
191
198
|
child =>
|
|
192
|
-
(child as ReactElement).props.children
|
|
199
|
+
(child as ReactElement).props.children
|
|
200
|
+
.toLowerCase()
|
|
201
|
+
.match(escapeRegExp(filter.toLowerCase())) !== null
|
|
193
202
|
);
|
|
194
203
|
|
|
195
|
-
|
|
204
|
+
results = _internalRenderChildren(filterOutSelectedChildren(filteredChildren));
|
|
205
|
+
} else {
|
|
206
|
+
results = _internalRenderChildren(
|
|
207
|
+
filterOutSelectedChildren(React.Children.toArray(children))
|
|
208
|
+
);
|
|
209
|
+
}
|
|
196
210
|
|
|
197
|
-
|
|
198
|
-
return <li className={classes["no-results"]}>{noResultsLabel}</li>;
|
|
199
|
-
}
|
|
211
|
+
optionsVisibleCount !== results.length && setOptionsVisibleCount(results.length);
|
|
200
212
|
|
|
201
|
-
|
|
213
|
+
if (results.length === 0) {
|
|
214
|
+
return <li className={classes["no-results"]}>{noResultsLabel}</li>;
|
|
202
215
|
}
|
|
203
216
|
|
|
204
|
-
return
|
|
217
|
+
return results;
|
|
205
218
|
|
|
206
219
|
function _internalRenderChildren(internalChildren: ReactChildrenType) {
|
|
207
220
|
return React.Children.map(internalChildren, (child, index) => {
|
|
@@ -211,13 +224,15 @@ const MultiSelectComponent: ForwardRefRenderFunction<HTMLSelectElement, MultiSel
|
|
|
211
224
|
},
|
|
212
225
|
onOptionSelect: (optionRef: React.RefObject<HTMLLIElement>) => {
|
|
213
226
|
onOptionChangeHandler(optionRef.current);
|
|
227
|
+
setExpanded(false);
|
|
214
228
|
setShouldClick(false);
|
|
215
229
|
},
|
|
216
|
-
isSearching:
|
|
230
|
+
isSearching: false,
|
|
217
231
|
selectOpened: expanded,
|
|
218
232
|
childIndex: index,
|
|
219
233
|
hasFocus: focusedSelectItem === index,
|
|
220
|
-
shouldClick: shouldClick
|
|
234
|
+
shouldClick: shouldClick,
|
|
235
|
+
id: getOptionId(multiSelectId.current, index)
|
|
221
236
|
});
|
|
222
237
|
});
|
|
223
238
|
}
|
|
@@ -238,16 +253,21 @@ const MultiSelectComponent: ForwardRefRenderFunction<HTMLSelectElement, MultiSel
|
|
|
238
253
|
};
|
|
239
254
|
|
|
240
255
|
useEffect(() => {
|
|
241
|
-
if (expanded) {
|
|
242
|
-
setFocusedSelectItem(0);
|
|
256
|
+
if (expanded && searchInputRef.current) {
|
|
243
257
|
setShouldFocusButtonAfterClose(true);
|
|
258
|
+
searchInputRef.current.focus();
|
|
244
259
|
}
|
|
245
260
|
|
|
246
261
|
if (!expanded && customSelectButtonRef.current && shouldFocusButtonAfterClose) {
|
|
247
|
-
customSelectButtonRef.current.focus();
|
|
248
262
|
setShouldFocusButtonAfterClose(false);
|
|
263
|
+
customSelectButtonRef.current.focus();
|
|
249
264
|
}
|
|
250
|
-
}, [
|
|
265
|
+
}, [
|
|
266
|
+
expanded,
|
|
267
|
+
customSelectButtonRef.current,
|
|
268
|
+
shouldFocusButtonAfterClose,
|
|
269
|
+
searchInputRef.current
|
|
270
|
+
]);
|
|
251
271
|
|
|
252
272
|
useEffect(() => {
|
|
253
273
|
syncDisplayValue(value);
|
|
@@ -272,7 +292,8 @@ const MultiSelectComponent: ForwardRefRenderFunction<HTMLSelectElement, MultiSel
|
|
|
272
292
|
success && additionalClasses.push(classes.success);
|
|
273
293
|
|
|
274
294
|
const onSelectButtonClick = () => {
|
|
275
|
-
setExpanded(!expanded);
|
|
295
|
+
setExpanded(expanded => !expanded);
|
|
296
|
+
setShouldClick(false);
|
|
276
297
|
};
|
|
277
298
|
|
|
278
299
|
/** The native select is purely for external form libraries. We use it to emit an onChange with native select event object so they know exactly what's happening. */
|
|
@@ -295,16 +316,16 @@ const MultiSelectComponent: ForwardRefRenderFunction<HTMLSelectElement, MultiSel
|
|
|
295
316
|
</select>
|
|
296
317
|
<div
|
|
297
318
|
{...filterProps(rest, /^data-/)}
|
|
319
|
+
id={multiSelectId.current}
|
|
298
320
|
ref={containerReference}
|
|
299
321
|
onKeyDown={onArrowNavigation}
|
|
300
322
|
className={`custom-select ${classes.select} ${additionalClasses.join(" ")} ${
|
|
301
323
|
className ?? ""
|
|
302
324
|
}`}
|
|
303
325
|
>
|
|
304
|
-
{searchVisible && renderSearch()}
|
|
305
326
|
<div
|
|
306
327
|
className={`${classes["custom-select"]} ${additionalClasses.join(" ")} `}
|
|
307
|
-
style={{ display:
|
|
328
|
+
style={{ display: "flex" }}
|
|
308
329
|
>
|
|
309
330
|
<div className={classes["display-container"]} data-display>
|
|
310
331
|
<SelectButton
|
|
@@ -331,10 +352,10 @@ const MultiSelectComponent: ForwardRefRenderFunction<HTMLSelectElement, MultiSel
|
|
|
331
352
|
onRemove={onSelectedOptionRemoveHandler}
|
|
332
353
|
/>
|
|
333
354
|
)}
|
|
355
|
+
{searchVisible && renderSearch()}
|
|
334
356
|
</div>
|
|
335
357
|
<div className={classes["status"]}>{icon || renderChevronIcon()}</div>
|
|
336
358
|
</div>
|
|
337
|
-
|
|
338
359
|
<div
|
|
339
360
|
ref={optionListReference}
|
|
340
361
|
className={`list-wrapper ${classes["list-wrapper"]}`}
|
|
@@ -347,8 +368,10 @@ const MultiSelectComponent: ForwardRefRenderFunction<HTMLSelectElement, MultiSel
|
|
|
347
368
|
}}
|
|
348
369
|
>
|
|
349
370
|
<ul
|
|
371
|
+
id={getListboxId(multiSelectId.current)}
|
|
350
372
|
className={addNewBtnOptionsContainerClassName}
|
|
351
373
|
role="listbox"
|
|
374
|
+
aria-multiselectable="true"
|
|
352
375
|
style={{ maxHeight: optionsListMaxHeight.list }}
|
|
353
376
|
>
|
|
354
377
|
{renderOptions()}
|
|
@@ -20,12 +20,13 @@ import { Tag } from "../../../Tag/Tag";
|
|
|
20
20
|
import classes from "./SelectedOptions.module.scss";
|
|
21
21
|
|
|
22
22
|
export type Display = {
|
|
23
|
+
value: string;
|
|
23
24
|
label: string;
|
|
24
25
|
fixed?: boolean;
|
|
25
26
|
};
|
|
26
27
|
|
|
27
28
|
export interface Props extends ComponentPropsWithRef<"div"> {
|
|
28
|
-
display:
|
|
29
|
+
display: Display[];
|
|
29
30
|
onRemove: (value: string) => void;
|
|
30
31
|
disabled: boolean;
|
|
31
32
|
}
|
|
@@ -36,9 +37,9 @@ const SelectedOptionsComponent: ForwardRefRenderFunction<HTMLDivElement, Props>
|
|
|
36
37
|
) => {
|
|
37
38
|
return (
|
|
38
39
|
<div {...rest} className={classes["options"]} ref={ref}>
|
|
39
|
-
{
|
|
40
|
+
{display.map(item => (
|
|
40
41
|
<Tag
|
|
41
|
-
key={value}
|
|
42
|
+
key={item.value}
|
|
42
43
|
onClick={(e: React.MouseEvent<HTMLDivElement>) => {
|
|
43
44
|
e.stopPropagation();
|
|
44
45
|
}}
|
|
@@ -48,7 +49,7 @@ const SelectedOptionsComponent: ForwardRefRenderFunction<HTMLDivElement, Props>
|
|
|
48
49
|
? {
|
|
49
50
|
onRemove: (e: React.MouseEvent<HTMLButtonElement>) => {
|
|
50
51
|
e.stopPropagation();
|
|
51
|
-
onRemove(value);
|
|
52
|
+
onRemove(item.value);
|
|
52
53
|
},
|
|
53
54
|
className: classes["remove-btn"]
|
|
54
55
|
}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2022 OneWelcome B.V.
|
|
3
|
+
*
|
|
4
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
* you may not use this file except in compliance with the License.
|
|
6
|
+
* You may obtain a copy of the License at
|
|
7
|
+
*
|
|
8
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
*
|
|
10
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
* See the License for the specific language governing permissions and
|
|
14
|
+
* limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
interface UseArrowNavigationParams {
|
|
18
|
+
expanded: boolean;
|
|
19
|
+
setExpanded: React.Dispatch<React.SetStateAction<boolean>>;
|
|
20
|
+
setFocusedSelectItem: React.Dispatch<React.SetStateAction<number>>;
|
|
21
|
+
childrenCount: number;
|
|
22
|
+
setShouldClick: React.Dispatch<React.SetStateAction<boolean>>;
|
|
23
|
+
addBtnRef: React.RefObject<HTMLButtonElement>;
|
|
24
|
+
searchInputRef: React.RefObject<HTMLInputElement>;
|
|
25
|
+
customSelectButtonRef: React.RefObject<HTMLButtonElement>;
|
|
26
|
+
onClose: () => void;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** @scope .*/
|
|
30
|
+
export const useArrowNavigation = ({
|
|
31
|
+
expanded,
|
|
32
|
+
setExpanded,
|
|
33
|
+
setFocusedSelectItem,
|
|
34
|
+
childrenCount,
|
|
35
|
+
setShouldClick,
|
|
36
|
+
addBtnRef,
|
|
37
|
+
searchInputRef,
|
|
38
|
+
customSelectButtonRef,
|
|
39
|
+
onClose
|
|
40
|
+
}: UseArrowNavigationParams) => {
|
|
41
|
+
const onArrowNavigation = (event: React.KeyboardEvent) => {
|
|
42
|
+
const codesToPreventDefault = ["ArrowDown", "ArrowUp", "Escape", "End", "Home"];
|
|
43
|
+
const hasAddBtn = !!addBtnRef?.current;
|
|
44
|
+
const childrenCountWithAddButton = childrenCount + (hasAddBtn ? 1 : 0);
|
|
45
|
+
const isSearchEvent = event.target === searchInputRef.current;
|
|
46
|
+
const isSelectButtonEvent = event.target === customSelectButtonRef.current;
|
|
47
|
+
const isSearchOrSelectButtonEvent = isSelectButtonEvent || isSearchEvent;
|
|
48
|
+
|
|
49
|
+
if (expanded) {
|
|
50
|
+
codesToPreventDefault.push("Tab");
|
|
51
|
+
codesToPreventDefault.push("Enter");
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (!expanded) {
|
|
55
|
+
codesToPreventDefault.push("Space");
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (codesToPreventDefault.includes(event.code) && !event.metaKey) {
|
|
59
|
+
event.preventDefault();
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
switch (event.code) {
|
|
63
|
+
case "ArrowDown":
|
|
64
|
+
if (!expanded) {
|
|
65
|
+
setExpanded(true);
|
|
66
|
+
setFocusedSelectItem(0);
|
|
67
|
+
break;
|
|
68
|
+
}
|
|
69
|
+
setFocusedSelectItem(prevState =>
|
|
70
|
+
prevState + 1 > childrenCountWithAddButton - 1 ? 0 : prevState + 1
|
|
71
|
+
);
|
|
72
|
+
break;
|
|
73
|
+
case "ArrowUp":
|
|
74
|
+
if (!expanded) {
|
|
75
|
+
setExpanded(true);
|
|
76
|
+
setFocusedSelectItem(childrenCountWithAddButton - 1);
|
|
77
|
+
break;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
setFocusedSelectItem(prevState =>
|
|
81
|
+
prevState - 1 < 0 ? childrenCountWithAddButton - 1 : prevState - 1
|
|
82
|
+
);
|
|
83
|
+
break;
|
|
84
|
+
case "Enter":
|
|
85
|
+
if (expanded) {
|
|
86
|
+
setShouldClick(true);
|
|
87
|
+
} else {
|
|
88
|
+
setFocusedSelectItem(0);
|
|
89
|
+
}
|
|
90
|
+
if (isSearchOrSelectButtonEvent) {
|
|
91
|
+
setExpanded(!expanded);
|
|
92
|
+
}
|
|
93
|
+
break;
|
|
94
|
+
case "Space":
|
|
95
|
+
if (!expanded) {
|
|
96
|
+
setExpanded(true);
|
|
97
|
+
setFocusedSelectItem(0);
|
|
98
|
+
}
|
|
99
|
+
break;
|
|
100
|
+
case "Home":
|
|
101
|
+
setFocusedSelectItem(0);
|
|
102
|
+
break;
|
|
103
|
+
case "Escape":
|
|
104
|
+
if (expanded) {
|
|
105
|
+
setExpanded(false);
|
|
106
|
+
onClose();
|
|
107
|
+
}
|
|
108
|
+
break;
|
|
109
|
+
case "End":
|
|
110
|
+
setFocusedSelectItem(childrenCount - 1);
|
|
111
|
+
break;
|
|
112
|
+
case "ArrowLeft":
|
|
113
|
+
if (event.metaKey && expanded) {
|
|
114
|
+
event.preventDefault();
|
|
115
|
+
setFocusedSelectItem(0);
|
|
116
|
+
}
|
|
117
|
+
break;
|
|
118
|
+
case "ArrowRight":
|
|
119
|
+
if (event.metaKey && expanded) {
|
|
120
|
+
event.preventDefault();
|
|
121
|
+
setFocusedSelectItem(childrenCount - 1);
|
|
122
|
+
}
|
|
123
|
+
break;
|
|
124
|
+
}
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
return { onArrowNavigation };
|
|
128
|
+
};
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2022 OneWelcome B.V.
|
|
3
|
+
*
|
|
4
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
* you may not use this file except in compliance with the License.
|
|
6
|
+
* You may obtain a copy of the License at
|
|
7
|
+
*
|
|
8
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
*
|
|
10
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
* See the License for the specific language governing permissions and
|
|
14
|
+
* limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import React, { useEffect, useRef, useState } from "react";
|
|
18
|
+
import { PartialInputProps, SearchProps } from "../Select.interfaces";
|
|
19
|
+
|
|
20
|
+
interface Props {
|
|
21
|
+
selectId: string;
|
|
22
|
+
search?: SearchProps;
|
|
23
|
+
optionsCount: number;
|
|
24
|
+
/**
|
|
25
|
+
* @deprecated
|
|
26
|
+
*/
|
|
27
|
+
searchPlaceholder?: string;
|
|
28
|
+
/**
|
|
29
|
+
* @deprecated
|
|
30
|
+
*/
|
|
31
|
+
searchInputProps?: PartialInputProps & { reset?: boolean };
|
|
32
|
+
searchInputClassName: string;
|
|
33
|
+
expanded: boolean;
|
|
34
|
+
setFocusedSelectItem: (idx: number) => void;
|
|
35
|
+
focusedSelectItem: number;
|
|
36
|
+
describedBy?: string;
|
|
37
|
+
getOptionId: (multiSelectId: string, optionIndex: number) => string;
|
|
38
|
+
getListboxId: (multiSelectId: string) => string;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** @scope .*/
|
|
42
|
+
export const useSearch = ({
|
|
43
|
+
selectId,
|
|
44
|
+
search,
|
|
45
|
+
optionsCount,
|
|
46
|
+
searchPlaceholder,
|
|
47
|
+
searchInputProps,
|
|
48
|
+
searchInputClassName,
|
|
49
|
+
expanded,
|
|
50
|
+
setFocusedSelectItem,
|
|
51
|
+
focusedSelectItem,
|
|
52
|
+
describedBy,
|
|
53
|
+
getOptionId,
|
|
54
|
+
getListboxId
|
|
55
|
+
}: Props) => {
|
|
56
|
+
const [filter, setFilter] = useState("");
|
|
57
|
+
|
|
58
|
+
const DEFAULT_RENDER_THRESHOLD = 0;
|
|
59
|
+
|
|
60
|
+
const searchInputRef = useRef<HTMLInputElement>(null);
|
|
61
|
+
|
|
62
|
+
const threshold = search?.renderThreshold ?? DEFAULT_RENDER_THRESHOLD;
|
|
63
|
+
const hasEnoughChildren = optionsCount >= threshold;
|
|
64
|
+
|
|
65
|
+
const shouldRenderSearch = () => {
|
|
66
|
+
if (search?.enabled) {
|
|
67
|
+
return hasEnoughChildren;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (search) {
|
|
71
|
+
return search.enabled as boolean;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return optionsCount > DEFAULT_RENDER_THRESHOLD;
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
const renderSearch = () => {
|
|
78
|
+
return (
|
|
79
|
+
<input
|
|
80
|
+
{...((search?.searchInputProps as any) ?? searchInputProps ?? {})}
|
|
81
|
+
ref={searchInputRef}
|
|
82
|
+
value={filter}
|
|
83
|
+
onChange={event => setFilter(event.currentTarget.value)}
|
|
84
|
+
className={[searchInputClassName, searchInputProps?.className].join(" ")}
|
|
85
|
+
style={{
|
|
86
|
+
display: expanded ? "block" : "none"
|
|
87
|
+
}}
|
|
88
|
+
type="text"
|
|
89
|
+
name="search-option"
|
|
90
|
+
placeholder={search?.searchPlaceholder ?? searchPlaceholder}
|
|
91
|
+
role="combobox"
|
|
92
|
+
autoComplete="off"
|
|
93
|
+
autoCorrect="off"
|
|
94
|
+
spellCheck={false}
|
|
95
|
+
aria-controls={getListboxId(selectId)}
|
|
96
|
+
aria-describedby={describedBy}
|
|
97
|
+
aria-autocomplete="none"
|
|
98
|
+
aria-expanded={expanded}
|
|
99
|
+
aria-activedescendant={getOptionId(selectId, focusedSelectItem)}
|
|
100
|
+
aria-haspopup={true}
|
|
101
|
+
/>
|
|
102
|
+
);
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
const resetSearchState = () => {
|
|
106
|
+
setFilter("");
|
|
107
|
+
setFocusedSelectItem(0);
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
const visible = shouldRenderSearch();
|
|
111
|
+
|
|
112
|
+
useEffect(() => {
|
|
113
|
+
if (search?.searchInputProps?.reset || searchInputProps?.reset) {
|
|
114
|
+
resetSearchState();
|
|
115
|
+
}
|
|
116
|
+
}, [searchInputProps?.reset, search?.searchInputProps?.reset]);
|
|
117
|
+
|
|
118
|
+
return {
|
|
119
|
+
renderSearch,
|
|
120
|
+
resetSearchState,
|
|
121
|
+
searchVisible: visible,
|
|
122
|
+
searchThreshold: threshold,
|
|
123
|
+
searchInputRef,
|
|
124
|
+
filter
|
|
125
|
+
};
|
|
126
|
+
};
|
|
@@ -31,6 +31,7 @@ export interface AddNewProps {
|
|
|
31
31
|
label: string;
|
|
32
32
|
onAddNew: (value: string) => void;
|
|
33
33
|
btnProps?: React.ButtonHTMLAttributes<HTMLButtonElement>;
|
|
34
|
+
alwaysEnabled?: boolean;
|
|
34
35
|
}
|
|
35
36
|
|
|
36
37
|
export interface SelectProps<V extends string | readonly string[] | undefined>
|
|
@@ -20,15 +20,18 @@ import React, {
|
|
|
20
20
|
createRef,
|
|
21
21
|
RefObject,
|
|
22
22
|
useEffect,
|
|
23
|
-
ReactNode
|
|
23
|
+
ReactNode,
|
|
24
|
+
useRef
|
|
24
25
|
} from "react";
|
|
25
26
|
import classes from "./Select.module.scss";
|
|
27
|
+
import { generateID } from "../../../../util/helper";
|
|
26
28
|
|
|
27
29
|
export interface Props extends ComponentPropsWithRef<"li"> {
|
|
28
30
|
children: ReactNode;
|
|
29
31
|
value: string;
|
|
30
32
|
disabled?: boolean;
|
|
31
33
|
isSelected?: boolean;
|
|
34
|
+
disableDefaultSelectedStyle?: boolean;
|
|
32
35
|
selectOpened?: boolean;
|
|
33
36
|
hasFocus?: boolean;
|
|
34
37
|
shouldClick?: boolean;
|
|
@@ -42,9 +45,11 @@ export interface Props extends ComponentPropsWithRef<"li"> {
|
|
|
42
45
|
|
|
43
46
|
const OptionComponent: ForwardRefRenderFunction<HTMLLIElement, Props> = (
|
|
44
47
|
{
|
|
48
|
+
id,
|
|
45
49
|
children,
|
|
46
50
|
className,
|
|
47
51
|
isSelected = false,
|
|
52
|
+
disableDefaultSelectedStyle,
|
|
48
53
|
shouldClick,
|
|
49
54
|
hasFocus,
|
|
50
55
|
selectOpened,
|
|
@@ -59,8 +64,15 @@ const OptionComponent: ForwardRefRenderFunction<HTMLLIElement, Props> = (
|
|
|
59
64
|
}: Props,
|
|
60
65
|
ref
|
|
61
66
|
) => {
|
|
67
|
+
const defaultOptionId = useRef(generateID(20));
|
|
68
|
+
const optionId = id ?? defaultOptionId.current;
|
|
62
69
|
let innerOptionRef = (ref as RefObject<HTMLLIElement>) || createRef<HTMLLIElement>();
|
|
63
70
|
|
|
71
|
+
const additionalClasses = [];
|
|
72
|
+
className && additionalClasses.push(className);
|
|
73
|
+
isSelected && !disableDefaultSelectedStyle && additionalClasses.push(classes["selected-option"]);
|
|
74
|
+
disabled && additionalClasses.push(classes["disabled"]);
|
|
75
|
+
|
|
64
76
|
useEffect(() => {
|
|
65
77
|
if (isSelected && innerOptionRef.current && shouldClick) {
|
|
66
78
|
innerOptionRef.current.click();
|
|
@@ -79,11 +91,10 @@ const OptionComponent: ForwardRefRenderFunction<HTMLLIElement, Props> = (
|
|
|
79
91
|
return (
|
|
80
92
|
<li
|
|
81
93
|
{...rest}
|
|
94
|
+
id={optionId}
|
|
82
95
|
ref={innerOptionRef}
|
|
83
96
|
data-value={value}
|
|
84
|
-
className={
|
|
85
|
-
disabled ? classes.disabled : ""
|
|
86
|
-
} ${className ?? ""}`}
|
|
97
|
+
className={additionalClasses.join(" ")}
|
|
87
98
|
onClick={onSelectHandler}
|
|
88
99
|
onKeyDown={event => {
|
|
89
100
|
if (event.code === "Enter") {
|