@itwin/itwinui-react 2.0.4 → 2.1.1
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/CHANGELOG.md +15 -2
- package/cjs/core/ComboBox/ComboBox.d.ts +25 -8
- package/cjs/core/ComboBox/ComboBox.js +141 -44
- package/cjs/core/ComboBox/ComboBoxDropdown.d.ts +1 -1
- package/cjs/core/ComboBox/ComboBoxDropdown.js +2 -2
- package/cjs/core/ComboBox/ComboBoxEndIcon.js +1 -1
- package/cjs/core/ComboBox/ComboBoxInput.d.ts +2 -0
- package/cjs/core/ComboBox/ComboBoxInput.js +40 -23
- package/cjs/core/ComboBox/ComboBoxMenuItem.js +1 -1
- package/cjs/core/ComboBox/ComboBoxMultipleContainer.d.ts +4 -0
- package/cjs/core/ComboBox/ComboBoxMultipleContainer.js +17 -0
- package/cjs/core/ComboBox/helpers.d.ts +20 -5
- package/cjs/core/ComboBox/helpers.js +24 -6
- package/cjs/core/Select/Select.js +2 -7
- package/cjs/core/Select/SelectTagContainer.d.ts +16 -0
- package/cjs/core/Select/SelectTagContainer.js +27 -0
- package/cjs/core/Table/Table.js +4 -2
- package/cjs/core/Table/actionHandlers/index.d.ts +1 -1
- package/cjs/core/Table/actionHandlers/index.js +2 -2
- package/cjs/core/Table/actionHandlers/selectHandler.d.ts +2 -2
- package/cjs/core/Table/actionHandlers/selectHandler.js +20 -6
- package/cjs/core/ThemeProvider/ThemeProvider.d.ts +25 -6
- package/cjs/core/ThemeProvider/ThemeProvider.js +29 -12
- package/cjs/core/utils/components/Popover.d.ts +1 -1
- package/cjs/core/utils/hooks/index.d.ts +1 -0
- package/cjs/core/utils/hooks/index.js +1 -0
- package/cjs/core/utils/hooks/useIsThemeAlreadySet.d.ts +7 -0
- package/cjs/core/utils/hooks/useIsThemeAlreadySet.js +34 -0
- package/cjs/core/utils/hooks/useTheme.js +4 -9
- package/esm/core/ComboBox/ComboBox.d.ts +25 -8
- package/esm/core/ComboBox/ComboBox.js +141 -44
- package/esm/core/ComboBox/ComboBoxDropdown.d.ts +1 -1
- package/esm/core/ComboBox/ComboBoxDropdown.js +2 -2
- package/esm/core/ComboBox/ComboBoxEndIcon.js +1 -1
- package/esm/core/ComboBox/ComboBoxInput.d.ts +2 -0
- package/esm/core/ComboBox/ComboBoxInput.js +41 -24
- package/esm/core/ComboBox/ComboBoxMenuItem.js +1 -1
- package/esm/core/ComboBox/ComboBoxMultipleContainer.d.ts +4 -0
- package/esm/core/ComboBox/ComboBoxMultipleContainer.js +11 -0
- package/esm/core/ComboBox/helpers.d.ts +20 -5
- package/esm/core/ComboBox/helpers.js +24 -6
- package/esm/core/Select/Select.js +3 -8
- package/esm/core/Select/SelectTagContainer.d.ts +16 -0
- package/esm/core/Select/SelectTagContainer.js +21 -0
- package/esm/core/Table/Table.js +5 -3
- package/esm/core/Table/actionHandlers/index.d.ts +1 -1
- package/esm/core/Table/actionHandlers/index.js +1 -1
- package/esm/core/Table/actionHandlers/selectHandler.d.ts +2 -2
- package/esm/core/Table/actionHandlers/selectHandler.js +17 -3
- package/esm/core/ThemeProvider/ThemeProvider.d.ts +25 -6
- package/esm/core/ThemeProvider/ThemeProvider.js +30 -13
- package/esm/core/utils/components/Popover.d.ts +1 -1
- package/esm/core/utils/hooks/index.d.ts +1 -0
- package/esm/core/utils/hooks/index.js +1 -0
- package/esm/core/utils/hooks/useIsThemeAlreadySet.d.ts +7 -0
- package/esm/core/utils/hooks/useIsThemeAlreadySet.js +27 -0
- package/esm/core/utils/hooks/useTheme.js +4 -6
- package/package.json +10 -5
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
import React from 'react';
|
|
6
6
|
import cx from 'classnames';
|
|
7
7
|
import { MenuExtraContent } from '../Menu';
|
|
8
|
+
import SelectTag from '../Select/SelectTag';
|
|
8
9
|
import { Text } from '../Typography';
|
|
9
10
|
import { useTheme, getRandomValue, mergeRefs, useLatestRef, useIsomorphicLayoutEffect, } from '../utils';
|
|
10
11
|
import 'tippy.js/animations/shift-away.css';
|
|
@@ -15,6 +16,14 @@ import { ComboBoxInput } from './ComboBoxInput';
|
|
|
15
16
|
import { ComboBoxInputContainer } from './ComboBoxInputContainer';
|
|
16
17
|
import { ComboBoxMenu } from './ComboBoxMenu';
|
|
17
18
|
import { ComboBoxMenuItem } from './ComboBoxMenuItem';
|
|
19
|
+
// Type guard for enabling multiple
|
|
20
|
+
const isMultipleEnabled = (variable, multiple) => {
|
|
21
|
+
return multiple && (Array.isArray(variable) || variable === undefined);
|
|
22
|
+
};
|
|
23
|
+
// Type guard for user onChange
|
|
24
|
+
const isSingleOnChange = (onChange, multiple) => {
|
|
25
|
+
return !multiple;
|
|
26
|
+
};
|
|
18
27
|
/** Returns either `option.id` or derives a stable id using `idPrefix` and `option.label` (without whitespace) */
|
|
19
28
|
const getOptionId = (option, idPrefix) => {
|
|
20
29
|
var _a;
|
|
@@ -35,7 +44,7 @@ const getOptionId = (option, idPrefix) => {
|
|
|
35
44
|
*/
|
|
36
45
|
export const ComboBox = (props) => {
|
|
37
46
|
var _a, _b;
|
|
38
|
-
const { options, value: valueProp, onChange, filterFunction, inputProps, dropdownMenuProps, emptyStateMessage = 'No options found', itemRenderer, enableVirtualization = false, onShow, onHide, ...rest } = props;
|
|
47
|
+
const { options, value: valueProp, onChange, filterFunction, inputProps, dropdownMenuProps, emptyStateMessage = 'No options found', itemRenderer, enableVirtualization = false, multiple = false, onShow, onHide, ...rest } = props;
|
|
39
48
|
// Generate a stateful random id if not specified
|
|
40
49
|
const [id] = React.useState(() => {
|
|
41
50
|
var _a, _b;
|
|
@@ -46,8 +55,6 @@ export const ComboBox = (props) => {
|
|
|
46
55
|
const inputRef = React.useRef(null);
|
|
47
56
|
const menuRef = React.useRef(null);
|
|
48
57
|
const toggleButtonRef = React.useRef(null);
|
|
49
|
-
const mounted = React.useRef(false);
|
|
50
|
-
const valuePropRef = useLatestRef(valueProp);
|
|
51
58
|
const onChangeProp = useLatestRef(onChange);
|
|
52
59
|
const optionsRef = useLatestRef(options);
|
|
53
60
|
// Record to store all extra information (e.g. original indexes), where the key is the id of the option
|
|
@@ -65,12 +72,23 @@ export const ComboBox = (props) => {
|
|
|
65
72
|
};
|
|
66
73
|
});
|
|
67
74
|
}
|
|
75
|
+
// Get indices of selected elements in options array when we have selected values.
|
|
76
|
+
const getSelectedIndexes = React.useCallback(() => {
|
|
77
|
+
if (isMultipleEnabled(valueProp, multiple)) {
|
|
78
|
+
const indexArray = [];
|
|
79
|
+
valueProp === null || valueProp === void 0 ? void 0 : valueProp.forEach((value) => {
|
|
80
|
+
indexArray.push(options.findIndex((option) => option.value === value));
|
|
81
|
+
});
|
|
82
|
+
return indexArray;
|
|
83
|
+
}
|
|
84
|
+
else {
|
|
85
|
+
return options.findIndex((option) => option.value === valueProp);
|
|
86
|
+
}
|
|
87
|
+
}, [multiple, options, valueProp]);
|
|
68
88
|
// Reducer where all the component-wide state is stored
|
|
69
|
-
const [{ isOpen,
|
|
89
|
+
const [{ isOpen, selected, focusedIndex }, dispatch] = React.useReducer(comboBoxReducer, {
|
|
70
90
|
isOpen: false,
|
|
71
|
-
|
|
72
|
-
? optionsRef.current.findIndex((option) => option.value === valueProp)
|
|
73
|
-
: -1,
|
|
91
|
+
selected: getSelectedIndexes(),
|
|
74
92
|
focusedIndex: -1,
|
|
75
93
|
});
|
|
76
94
|
useIsomorphicLayoutEffect(() => {
|
|
@@ -78,19 +96,24 @@ export const ComboBox = (props) => {
|
|
|
78
96
|
// When the dropdown opens
|
|
79
97
|
if (isOpen) {
|
|
80
98
|
(_a = inputRef.current) === null || _a === void 0 ? void 0 : _a.focus(); // Focus the input
|
|
81
|
-
|
|
82
|
-
|
|
99
|
+
// Reset the filtered list (does not reset when multiple enabled)
|
|
100
|
+
if (!multiple) {
|
|
101
|
+
setFilteredOptions(optionsRef.current);
|
|
102
|
+
dispatch({ type: 'focus', value: undefined });
|
|
103
|
+
}
|
|
83
104
|
}
|
|
84
105
|
// When the dropdown closes
|
|
85
106
|
else {
|
|
86
107
|
// Reset the focused index
|
|
87
|
-
dispatch(
|
|
88
|
-
// Reset the input value
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
108
|
+
dispatch({ type: 'focus', value: undefined });
|
|
109
|
+
// Reset the input value if not multiple
|
|
110
|
+
if (!isMultipleEnabled(selected, multiple)) {
|
|
111
|
+
setInputValue(selected != undefined && selected >= 0
|
|
112
|
+
? (_b = optionsRef.current[selected]) === null || _b === void 0 ? void 0 : _b.label
|
|
113
|
+
: '');
|
|
114
|
+
}
|
|
92
115
|
}
|
|
93
|
-
}, [isOpen, optionsRef,
|
|
116
|
+
}, [isOpen, multiple, optionsRef, selected]);
|
|
94
117
|
// Set min-width of menu to be same as input
|
|
95
118
|
const [minWidth, setMinWidth] = React.useState(0);
|
|
96
119
|
React.useEffect(() => {
|
|
@@ -108,7 +131,7 @@ export const ComboBox = (props) => {
|
|
|
108
131
|
else {
|
|
109
132
|
setFilteredOptions(options);
|
|
110
133
|
}
|
|
111
|
-
dispatch(
|
|
134
|
+
dispatch({ type: 'focus', value: undefined });
|
|
112
135
|
// Only need to call on options update
|
|
113
136
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
114
137
|
}, [options]);
|
|
@@ -118,41 +141,101 @@ export const ComboBox = (props) => {
|
|
|
118
141
|
var _a, _b;
|
|
119
142
|
const { value } = event.currentTarget;
|
|
120
143
|
setInputValue(value);
|
|
121
|
-
dispatch(
|
|
144
|
+
dispatch({ type: 'open' }); // reopen when typing
|
|
122
145
|
setFilteredOptions((_a = filterFunction === null || filterFunction === void 0 ? void 0 : filterFunction(optionsRef.current, value)) !== null && _a !== void 0 ? _a : optionsRef.current.filter((option) => option.label.toLowerCase().includes(value.toLowerCase())));
|
|
123
146
|
if (focusedIndex != -1) {
|
|
124
|
-
dispatch(
|
|
147
|
+
dispatch({ type: 'focus', value: -1 });
|
|
125
148
|
}
|
|
126
149
|
(_b = inputProps === null || inputProps === void 0 ? void 0 : inputProps.onChange) === null || _b === void 0 ? void 0 : _b.call(inputProps, event);
|
|
127
150
|
}, [filterFunction, focusedIndex, inputProps, optionsRef]);
|
|
128
|
-
// When the value prop changes, update the
|
|
151
|
+
// When the value prop changes, update the selected index/indices
|
|
129
152
|
React.useEffect(() => {
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
153
|
+
if (isMultipleEnabled(valueProp, multiple)) {
|
|
154
|
+
if (valueProp) {
|
|
155
|
+
// If user provided array of selected values
|
|
156
|
+
const indexes = valueProp.map((value) => {
|
|
157
|
+
return options.findIndex((option) => option.value === value);
|
|
158
|
+
});
|
|
159
|
+
dispatch({
|
|
160
|
+
type: 'multiselect',
|
|
161
|
+
value: indexes.filter((index) => index !== -1), // Add available options
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
else {
|
|
165
|
+
// if user provided one value or undefined
|
|
166
|
+
dispatch({
|
|
167
|
+
type: 'multiselect',
|
|
168
|
+
value: [], // Add empty list
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
else {
|
|
173
|
+
dispatch({
|
|
174
|
+
type: 'select',
|
|
175
|
+
value: options.findIndex((option) => option.value === valueProp),
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
}, [valueProp, options, multiple]);
|
|
179
|
+
const isMenuItemSelected = React.useCallback((index) => {
|
|
180
|
+
if (isMultipleEnabled(selected, multiple)) {
|
|
181
|
+
return !!selected.includes(index);
|
|
142
182
|
}
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
183
|
+
else {
|
|
184
|
+
return selected === index;
|
|
185
|
+
}
|
|
186
|
+
}, [multiple, selected]);
|
|
187
|
+
// Generates new array when item is added or removed
|
|
188
|
+
const selectedChangeHandler = React.useCallback((__originalIndex, action) => {
|
|
189
|
+
if (action === 'added') {
|
|
190
|
+
return [...selected, __originalIndex];
|
|
191
|
+
}
|
|
192
|
+
else {
|
|
193
|
+
return selected.filter((index) => index !== __originalIndex);
|
|
194
|
+
}
|
|
195
|
+
}, [selected]);
|
|
196
|
+
// Calls user defined onChange
|
|
197
|
+
const onChangeHandler = React.useCallback((__originalIndex, actionType, newArray) => {
|
|
198
|
+
var _a, _b, _c, _d;
|
|
199
|
+
if (isSingleOnChange(onChangeProp.current, multiple)) {
|
|
200
|
+
(_a = onChangeProp.current) === null || _a === void 0 ? void 0 : _a.call(onChangeProp, (_b = optionsRef.current[__originalIndex]) === null || _b === void 0 ? void 0 : _b.value);
|
|
201
|
+
}
|
|
202
|
+
else {
|
|
203
|
+
actionType &&
|
|
204
|
+
newArray &&
|
|
205
|
+
((_c = onChangeProp.current) === null || _c === void 0 ? void 0 : _c.call(onChangeProp, newArray === null || newArray === void 0 ? void 0 : newArray.map((item) => { var _a; return (_a = optionsRef.current[item]) === null || _a === void 0 ? void 0 : _a.value; }), {
|
|
206
|
+
value: (_d = optionsRef.current[__originalIndex]) === null || _d === void 0 ? void 0 : _d.value,
|
|
207
|
+
type: actionType,
|
|
208
|
+
}));
|
|
209
|
+
}
|
|
210
|
+
}, [multiple, onChangeProp, optionsRef]);
|
|
211
|
+
const onClickHandler = React.useCallback((__originalIndex) => {
|
|
212
|
+
if (isMultipleEnabled(selected, multiple)) {
|
|
213
|
+
const actionType = isMenuItemSelected(__originalIndex)
|
|
214
|
+
? 'removed'
|
|
215
|
+
: 'added';
|
|
216
|
+
const newArray = selectedChangeHandler(__originalIndex, actionType);
|
|
217
|
+
dispatch({ type: 'multiselect', value: newArray });
|
|
218
|
+
onChangeHandler(__originalIndex, actionType, newArray);
|
|
219
|
+
}
|
|
220
|
+
else {
|
|
221
|
+
dispatch({ type: 'select', value: __originalIndex });
|
|
222
|
+
dispatch({ type: 'close' });
|
|
223
|
+
onChangeHandler(__originalIndex);
|
|
146
224
|
}
|
|
147
|
-
|
|
148
|
-
|
|
225
|
+
}, [
|
|
226
|
+
selectedChangeHandler,
|
|
227
|
+
isMenuItemSelected,
|
|
228
|
+
multiple,
|
|
229
|
+
onChangeHandler,
|
|
230
|
+
selected,
|
|
231
|
+
]);
|
|
149
232
|
const getMenuItem = React.useCallback((option, filteredIndex) => {
|
|
150
233
|
const optionId = getOptionId(option, id);
|
|
151
234
|
const { __originalIndex } = optionsExtraInfoRef.current[optionId];
|
|
152
235
|
const customItem = itemRenderer
|
|
153
236
|
? itemRenderer(option, {
|
|
154
237
|
isFocused: focusedIndex === __originalIndex,
|
|
155
|
-
isSelected:
|
|
238
|
+
isSelected: selected === __originalIndex,
|
|
156
239
|
index: __originalIndex,
|
|
157
240
|
id: optionId,
|
|
158
241
|
})
|
|
@@ -160,8 +243,7 @@ export const ComboBox = (props) => {
|
|
|
160
243
|
return customItem ? (React.cloneElement(customItem, {
|
|
161
244
|
onClick: (e) => {
|
|
162
245
|
var _a, _b;
|
|
163
|
-
|
|
164
|
-
dispatch(['close']);
|
|
246
|
+
onClickHandler(__originalIndex);
|
|
165
247
|
(_b = (_a = customItem.props).onClick) === null || _b === void 0 ? void 0 : _b.call(_a, e);
|
|
166
248
|
},
|
|
167
249
|
// ComboBox.MenuItem handles scrollIntoView, data-iui-index and iui-focused through context
|
|
@@ -176,11 +258,18 @@ export const ComboBox = (props) => {
|
|
|
176
258
|
el === null || el === void 0 ? void 0 : el.scrollIntoView({ block: 'nearest' });
|
|
177
259
|
}
|
|
178
260
|
}),
|
|
179
|
-
})) : (React.createElement(ComboBoxMenuItem, { key: optionId, id: optionId, ...option, isSelected:
|
|
180
|
-
|
|
181
|
-
dispatch(['close']);
|
|
261
|
+
})) : (React.createElement(ComboBoxMenuItem, { key: optionId, id: optionId, ...option, isSelected: isMenuItemSelected(__originalIndex), onClick: () => {
|
|
262
|
+
onClickHandler(__originalIndex);
|
|
182
263
|
}, index: __originalIndex, "data-iui-filtered-index": filteredIndex }, option.label));
|
|
183
|
-
}, [
|
|
264
|
+
}, [
|
|
265
|
+
enableVirtualization,
|
|
266
|
+
focusedIndex,
|
|
267
|
+
id,
|
|
268
|
+
isMenuItemSelected,
|
|
269
|
+
itemRenderer,
|
|
270
|
+
onClickHandler,
|
|
271
|
+
selected,
|
|
272
|
+
]);
|
|
184
273
|
const emptyContent = React.useMemo(() => (React.createElement(React.Fragment, null, React.isValidElement(emptyStateMessage) ? (emptyStateMessage) : (React.createElement(MenuExtraContent, null,
|
|
185
274
|
React.createElement(Text, { isMuted: true }, emptyStateMessage))))), [emptyStateMessage]);
|
|
186
275
|
return (React.createElement(ComboBoxRefsContext.Provider, { value: { inputRef, menuRef, toggleButtonRef, optionsExtraInfoRef } },
|
|
@@ -190,12 +279,20 @@ export const ComboBox = (props) => {
|
|
|
190
279
|
minWidth,
|
|
191
280
|
isOpen,
|
|
192
281
|
focusedIndex,
|
|
282
|
+
onClickHandler,
|
|
193
283
|
enableVirtualization,
|
|
194
284
|
filteredOptions,
|
|
195
285
|
getMenuItem,
|
|
286
|
+
multiple,
|
|
196
287
|
} },
|
|
197
288
|
React.createElement(ComboBoxInputContainer, { disabled: inputProps === null || inputProps === void 0 ? void 0 : inputProps.disabled, ...rest },
|
|
198
|
-
React.createElement(
|
|
289
|
+
React.createElement(React.Fragment, null,
|
|
290
|
+
React.createElement(ComboBoxInput, { value: inputValue, ...inputProps, onChange: handleOnInput, selectTags: isMultipleEnabled(selected, multiple)
|
|
291
|
+
? selected.map((index) => {
|
|
292
|
+
const item = optionsRef.current[index];
|
|
293
|
+
return (React.createElement(SelectTag, { key: item.label, label: item.label }));
|
|
294
|
+
})
|
|
295
|
+
: undefined })),
|
|
199
296
|
React.createElement(ComboBoxEndIcon, { disabled: inputProps === null || inputProps === void 0 ? void 0 : inputProps.disabled, isOpen: isOpen })),
|
|
200
297
|
React.createElement(ComboBoxDropdown, { ...dropdownMenuProps, onShow: onShow, onHide: onHide },
|
|
201
298
|
React.createElement(ComboBoxMenu, null, filteredOptions.length > 0 && !enableVirtualization
|
|
@@ -3,5 +3,5 @@ import { PopoverProps } from '../utils';
|
|
|
3
3
|
declare type ComboBoxDropdownProps = PopoverProps & {
|
|
4
4
|
children: JSX.Element;
|
|
5
5
|
};
|
|
6
|
-
export declare const ComboBoxDropdown: React.ForwardRefExoticComponent<Pick<ComboBoxDropdownProps, "disabled" | "theme" | "children" | "className" | "role" | "
|
|
6
|
+
export declare const ComboBoxDropdown: React.ForwardRefExoticComponent<Pick<ComboBoxDropdownProps, "disabled" | "theme" | "children" | "className" | "role" | "offset" | "content" | "plugins" | "placement" | "trigger" | "visible" | "render" | "animateFill" | "appendTo" | "aria" | "delay" | "duration" | "followCursor" | "getReferenceClientRect" | "hideOnClick" | "ignoreAttributes" | "inlinePositioning" | "interactive" | "interactiveBorder" | "interactiveDebounce" | "moveTransition" | "popperOptions" | "showOnCreate" | "sticky" | "touch" | "triggerTarget" | "onAfterUpdate" | "onBeforeUpdate" | "onCreate" | "onDestroy" | "onHidden" | "onHide" | "onMount" | "onShow" | "onShown" | "onTrigger" | "onUntrigger" | "onClickOutside" | "allowHTML" | "animation" | "arrow" | "inertia" | "maxWidth" | "zIndex" | "singleton" | "reference"> & React.RefAttributes<Element>>;
|
|
7
7
|
export {};
|
|
@@ -13,13 +13,13 @@ export const ComboBoxDropdown = React.forwardRef((props, forwardedRef) => {
|
|
|
13
13
|
// sync internal isOpen state with user's visible prop
|
|
14
14
|
React.useEffect(() => {
|
|
15
15
|
if (props.visible != undefined) {
|
|
16
|
-
dispatch(
|
|
16
|
+
dispatch({ type: props.visible ? 'open' : 'close' });
|
|
17
17
|
}
|
|
18
18
|
}, [dispatch, props.visible]);
|
|
19
19
|
return (React.createElement(Popover, { placement: 'bottom-start', visible: isOpen, onClickOutside: React.useCallback((_, { target }) => {
|
|
20
20
|
var _a;
|
|
21
21
|
if (!((_a = toggleButtonRef.current) === null || _a === void 0 ? void 0 : _a.contains(target))) {
|
|
22
|
-
dispatch(
|
|
22
|
+
dispatch({ type: 'close' });
|
|
23
23
|
}
|
|
24
24
|
}, [dispatch, toggleButtonRef]), animation: 'shift-away', duration: 200, reference: inputRef, ref: forwardedRef, content: children, ...rest }));
|
|
25
25
|
});
|
|
@@ -18,7 +18,7 @@ export const ComboBoxEndIcon = React.forwardRef((props, forwardedRef) => {
|
|
|
18
18
|
}, className), onClick: (e) => {
|
|
19
19
|
onClickProp === null || onClickProp === void 0 ? void 0 : onClickProp(e);
|
|
20
20
|
if (!e.isDefaultPrevented()) {
|
|
21
|
-
dispatch(
|
|
21
|
+
dispatch({ type: isOpen ? 'close' : 'open' });
|
|
22
22
|
}
|
|
23
23
|
}, ...rest }, children !== null && children !== void 0 ? children : React.createElement(SvgCaretDownSmall, { "aria-hidden": true })));
|
|
24
24
|
});
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
export declare const ComboBoxInput: React.ForwardRefExoticComponent<{
|
|
3
|
+
selectTags?: JSX.Element[] | undefined;
|
|
4
|
+
} & {
|
|
3
5
|
setFocus?: boolean | undefined;
|
|
4
6
|
size?: "small" | "large" | undefined;
|
|
5
7
|
} & Omit<React.InputHTMLAttributes<HTMLInputElement>, "size"> & React.RefAttributes<HTMLInputElement>>;
|
|
@@ -4,11 +4,12 @@
|
|
|
4
4
|
*--------------------------------------------------------------------------------------------*/
|
|
5
5
|
import React from 'react';
|
|
6
6
|
import { Input } from '../Input';
|
|
7
|
-
import { useSafeContext, useMergedRefs } from '../utils';
|
|
7
|
+
import { useSafeContext, useMergedRefs, useContainerWidth } from '../utils';
|
|
8
|
+
import { ComboBoxMultipleContainer } from './ComboBoxMultipleContainer';
|
|
8
9
|
import { ComboBoxStateContext, ComboBoxActionContext, ComboBoxRefsContext, } from './helpers';
|
|
9
10
|
export const ComboBoxInput = React.forwardRef((props, forwardedRef) => {
|
|
10
|
-
const { onKeyDown: onKeyDownProp, onFocus: onFocusProp, ...rest } = props;
|
|
11
|
-
const { isOpen, id, focusedIndex, enableVirtualization } = useSafeContext(ComboBoxStateContext);
|
|
11
|
+
const { onKeyDown: onKeyDownProp, onFocus: onFocusProp, selectTags, ...rest } = props;
|
|
12
|
+
const { isOpen, id, focusedIndex, enableVirtualization, multiple, onClickHandler, } = useSafeContext(ComboBoxStateContext);
|
|
12
13
|
const dispatch = useSafeContext(ComboBoxActionContext);
|
|
13
14
|
const { inputRef, menuRef, optionsExtraInfoRef } = useSafeContext(ComboBoxRefsContext);
|
|
14
15
|
const refs = useMergedRefs(inputRef, forwardedRef);
|
|
@@ -28,17 +29,17 @@ export const ComboBoxInput = React.forwardRef((props, forwardedRef) => {
|
|
|
28
29
|
case 'ArrowDown': {
|
|
29
30
|
event.preventDefault();
|
|
30
31
|
if (!isOpen) {
|
|
31
|
-
return dispatch(
|
|
32
|
+
return dispatch({ type: 'open' });
|
|
32
33
|
}
|
|
33
34
|
if (length === 0) {
|
|
34
35
|
return;
|
|
35
36
|
}
|
|
36
37
|
if (focusedIndexRef.current === -1) {
|
|
37
38
|
const currentElement = (_b = menuRef.current) === null || _b === void 0 ? void 0 : _b.querySelector('[data-iui-index]');
|
|
38
|
-
return dispatch(
|
|
39
|
-
'focus',
|
|
40
|
-
Number((_c = currentElement === null || currentElement === void 0 ? void 0 : currentElement.getAttribute('data-iui-index')) !== null && _c !== void 0 ? _c : 0),
|
|
41
|
-
|
|
39
|
+
return dispatch({
|
|
40
|
+
type: 'focus',
|
|
41
|
+
value: Number((_c = currentElement === null || currentElement === void 0 ? void 0 : currentElement.getAttribute('data-iui-index')) !== null && _c !== void 0 ? _c : 0),
|
|
42
|
+
});
|
|
42
43
|
}
|
|
43
44
|
// If virtualization is enabled, dont let round scrolling
|
|
44
45
|
if (enableVirtualization &&
|
|
@@ -51,7 +52,7 @@ export const ComboBoxInput = React.forwardRef((props, forwardedRef) => {
|
|
|
51
52
|
const nextElement = (_g = currentElement === null || currentElement === void 0 ? void 0 : currentElement.nextElementSibling) !== null && _g !== void 0 ? _g : (_h = menuRef.current) === null || _h === void 0 ? void 0 : _h.querySelector('[data-iui-index]');
|
|
52
53
|
nextIndex = Number(nextElement === null || nextElement === void 0 ? void 0 : nextElement.getAttribute('data-iui-index'));
|
|
53
54
|
if ((nextElement === null || nextElement === void 0 ? void 0 : nextElement.ariaDisabled) !== 'true') {
|
|
54
|
-
return dispatch(
|
|
55
|
+
return dispatch({ type: 'focus', value: nextIndex });
|
|
55
56
|
}
|
|
56
57
|
} while (nextIndex !== focusedIndexRef.current);
|
|
57
58
|
break;
|
|
@@ -59,7 +60,7 @@ export const ComboBoxInput = React.forwardRef((props, forwardedRef) => {
|
|
|
59
60
|
case 'ArrowUp': {
|
|
60
61
|
event.preventDefault();
|
|
61
62
|
if (!isOpen) {
|
|
62
|
-
return dispatch(
|
|
63
|
+
return dispatch({ type: 'open' });
|
|
63
64
|
}
|
|
64
65
|
if (length === 0) {
|
|
65
66
|
return;
|
|
@@ -70,10 +71,10 @@ export const ComboBoxInput = React.forwardRef((props, forwardedRef) => {
|
|
|
70
71
|
return;
|
|
71
72
|
}
|
|
72
73
|
if (focusedIndexRef.current === -1) {
|
|
73
|
-
return dispatch(
|
|
74
|
-
'focus',
|
|
75
|
-
(_m = (_l = Object.values(optionsExtraInfoRef.current)) === null || _l === void 0 ? void 0 : _l[length - 1].__originalIndex) !== null && _m !== void 0 ? _m : -1,
|
|
76
|
-
|
|
74
|
+
return dispatch({
|
|
75
|
+
type: 'focus',
|
|
76
|
+
value: (_m = (_l = Object.values(optionsExtraInfoRef.current)) === null || _l === void 0 ? void 0 : _l[length - 1].__originalIndex) !== null && _m !== void 0 ? _m : -1,
|
|
77
|
+
});
|
|
77
78
|
}
|
|
78
79
|
let prevIndex = focusedIndexRef.current;
|
|
79
80
|
do {
|
|
@@ -81,7 +82,7 @@ export const ComboBoxInput = React.forwardRef((props, forwardedRef) => {
|
|
|
81
82
|
const prevElement = (_p = currentElement === null || currentElement === void 0 ? void 0 : currentElement.previousElementSibling) !== null && _p !== void 0 ? _p : (_q = menuRef.current) === null || _q === void 0 ? void 0 : _q.querySelector('[data-iui-index]:last-of-type');
|
|
82
83
|
prevIndex = Number(prevElement === null || prevElement === void 0 ? void 0 : prevElement.getAttribute('data-iui-index'));
|
|
83
84
|
if ((prevElement === null || prevElement === void 0 ? void 0 : prevElement.ariaDisabled) !== 'true') {
|
|
84
|
-
return dispatch(
|
|
85
|
+
return dispatch({ type: 'focus', value: prevIndex });
|
|
85
86
|
}
|
|
86
87
|
} while (prevIndex !== focusedIndexRef.current);
|
|
87
88
|
break;
|
|
@@ -89,21 +90,32 @@ export const ComboBoxInput = React.forwardRef((props, forwardedRef) => {
|
|
|
89
90
|
case 'Enter': {
|
|
90
91
|
event.preventDefault();
|
|
91
92
|
if (isOpen) {
|
|
92
|
-
|
|
93
|
-
|
|
93
|
+
if (multiple) {
|
|
94
|
+
// Keep menu open when multiselect is enabled and user selects an item
|
|
95
|
+
if (focusedIndexRef.current > -1) {
|
|
96
|
+
onClickHandler === null || onClickHandler === void 0 ? void 0 : onClickHandler(focusedIndexRef.current);
|
|
97
|
+
}
|
|
98
|
+
else {
|
|
99
|
+
dispatch({ type: 'close' });
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
else {
|
|
103
|
+
onClickHandler === null || onClickHandler === void 0 ? void 0 : onClickHandler(focusedIndexRef.current);
|
|
104
|
+
dispatch({ type: 'close' });
|
|
105
|
+
}
|
|
94
106
|
}
|
|
95
107
|
else {
|
|
96
|
-
dispatch(
|
|
108
|
+
dispatch({ type: 'open' });
|
|
97
109
|
}
|
|
98
110
|
break;
|
|
99
111
|
}
|
|
100
112
|
case 'Escape': {
|
|
101
113
|
event.preventDefault();
|
|
102
|
-
dispatch(
|
|
114
|
+
dispatch({ type: 'close' });
|
|
103
115
|
break;
|
|
104
116
|
}
|
|
105
117
|
case 'Tab':
|
|
106
|
-
dispatch(
|
|
118
|
+
dispatch({ type: 'close' });
|
|
107
119
|
break;
|
|
108
120
|
}
|
|
109
121
|
}, [
|
|
@@ -111,15 +123,20 @@ export const ComboBoxInput = React.forwardRef((props, forwardedRef) => {
|
|
|
111
123
|
enableVirtualization,
|
|
112
124
|
isOpen,
|
|
113
125
|
menuRef,
|
|
126
|
+
multiple,
|
|
127
|
+
onClickHandler,
|
|
114
128
|
onKeyDownProp,
|
|
115
129
|
optionsExtraInfoRef,
|
|
116
130
|
]);
|
|
117
131
|
const handleFocus = React.useCallback((event) => {
|
|
118
|
-
dispatch(
|
|
132
|
+
dispatch({ type: 'open' });
|
|
119
133
|
onFocusProp === null || onFocusProp === void 0 ? void 0 : onFocusProp(event);
|
|
120
134
|
}, [dispatch, onFocusProp]);
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
135
|
+
const [tagContainerWidthRef, tagContainerWidth] = useContainerWidth();
|
|
136
|
+
return (React.createElement(React.Fragment, null,
|
|
137
|
+
React.createElement(Input, { ref: refs, onKeyDown: handleKeyDown, onFocus: handleFocus, "aria-activedescendant": isOpen && focusedIndex != undefined && focusedIndex > -1
|
|
138
|
+
? getIdFromIndex(focusedIndex)
|
|
139
|
+
: undefined, role: 'combobox', "aria-controls": isOpen ? `${id}-list` : undefined, "aria-autocomplete": 'list', spellCheck: false, autoCapitalize: 'none', autoCorrect: 'off', style: multiple ? { paddingLeft: tagContainerWidth + 18 } : {}, ...rest }),
|
|
140
|
+
multiple && selectTags && (React.createElement(ComboBoxMultipleContainer, { ref: tagContainerWidthRef, selectedItems: selectTags }))));
|
|
124
141
|
});
|
|
125
142
|
ComboBoxInput.displayName = 'ComboBoxInput';
|
|
@@ -7,7 +7,7 @@ import React from 'react';
|
|
|
7
7
|
import { useSafeContext, useMergedRefs } from '../utils';
|
|
8
8
|
import { ComboBoxStateContext } from './helpers';
|
|
9
9
|
export const ComboBoxMenuItem = React.memo(React.forwardRef((props, forwardedRef) => {
|
|
10
|
-
const { children, isSelected, disabled, value, onClick, sublabel, size = !!sublabel ? 'large' : 'default', icon, badge, className, role = '
|
|
10
|
+
const { children, isSelected, disabled, value, onClick, sublabel, size = !!sublabel ? 'large' : 'default', icon, badge, className, role = 'option', index, ...rest } = props;
|
|
11
11
|
const { focusedIndex, enableVirtualization } = useSafeContext(ComboBoxStateContext);
|
|
12
12
|
const focusRef = (el) => {
|
|
13
13
|
if (!enableVirtualization && focusedIndex === index) {
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
export declare const ComboBoxMultipleContainer: React.ForwardRefExoticComponent<{
|
|
3
|
+
selectedItems?: React.ReactNode[] | undefined;
|
|
4
|
+
} & Omit<Pick<React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>, "key" | keyof React.HTMLAttributes<HTMLDivElement>>, "children"> & React.RefAttributes<HTMLDivElement>>;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/*---------------------------------------------------------------------------------------------
|
|
2
|
+
* Copyright (c) Bentley Systems, Incorporated. All rights reserved.
|
|
3
|
+
* See LICENSE.md in the project root for license terms and full copyright notice.
|
|
4
|
+
*--------------------------------------------------------------------------------------------*/
|
|
5
|
+
import React from 'react';
|
|
6
|
+
import SelectTagContainer from '../Select/SelectTagContainer';
|
|
7
|
+
export const ComboBoxMultipleContainer = React.forwardRef((props, ref) => {
|
|
8
|
+
const { selectedItems = [], ...rest } = props;
|
|
9
|
+
return React.createElement(SelectTagContainer, { ref: ref, tags: selectedItems, ...rest });
|
|
10
|
+
});
|
|
11
|
+
ComboBoxMultipleContainer.displayName = 'ComboBoxMultipleContainer';
|
|
@@ -1,13 +1,26 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { SelectOption } from '../Select/Select';
|
|
3
|
-
declare type ComboBoxAction =
|
|
3
|
+
declare type ComboBoxAction = {
|
|
4
|
+
type: 'multiselect';
|
|
5
|
+
value: number[];
|
|
6
|
+
} | {
|
|
7
|
+
type: 'open';
|
|
8
|
+
} | {
|
|
9
|
+
type: 'close';
|
|
10
|
+
} | {
|
|
11
|
+
type: 'select';
|
|
12
|
+
value: number;
|
|
13
|
+
} | {
|
|
14
|
+
type: 'focus';
|
|
15
|
+
value: number | undefined;
|
|
16
|
+
};
|
|
4
17
|
export declare const comboBoxReducer: (state: {
|
|
5
18
|
isOpen: boolean;
|
|
6
|
-
|
|
19
|
+
selected: number | number[];
|
|
7
20
|
focusedIndex: number;
|
|
8
|
-
},
|
|
21
|
+
}, action: ComboBoxAction) => {
|
|
9
22
|
isOpen: boolean;
|
|
10
|
-
|
|
23
|
+
selected: number | number[];
|
|
11
24
|
focusedIndex: number;
|
|
12
25
|
};
|
|
13
26
|
export declare const ComboBoxRefsContext: React.Context<{
|
|
@@ -24,9 +37,11 @@ declare type ComboBoxStateContextProps<T = unknown> = {
|
|
|
24
37
|
minWidth: number;
|
|
25
38
|
enableVirtualization: boolean;
|
|
26
39
|
filteredOptions: SelectOption<T>[];
|
|
40
|
+
onClickHandler?: (prop: number) => void;
|
|
27
41
|
getMenuItem: (option: SelectOption<T>, filteredIndex?: number) => JSX.Element;
|
|
28
42
|
focusedIndex?: number;
|
|
43
|
+
multiple?: boolean;
|
|
29
44
|
};
|
|
30
45
|
export declare const ComboBoxStateContext: React.Context<ComboBoxStateContextProps<unknown> | undefined>;
|
|
31
|
-
export declare const ComboBoxActionContext: React.Context<((x:
|
|
46
|
+
export declare const ComboBoxActionContext: React.Context<((x: ComboBoxAction) => void) | undefined>;
|
|
32
47
|
export {};
|
|
@@ -3,9 +3,9 @@
|
|
|
3
3
|
* See LICENSE.md in the project root for license terms and full copyright notice.
|
|
4
4
|
*--------------------------------------------------------------------------------------------*/
|
|
5
5
|
import React from 'react';
|
|
6
|
-
export const comboBoxReducer = (state,
|
|
7
|
-
var _a;
|
|
8
|
-
switch (type) {
|
|
6
|
+
export const comboBoxReducer = (state, action) => {
|
|
7
|
+
var _a, _b, _c, _d, _e;
|
|
8
|
+
switch (action.type) {
|
|
9
9
|
case 'open': {
|
|
10
10
|
return { ...state, isOpen: true };
|
|
11
11
|
}
|
|
@@ -13,14 +13,32 @@ export const comboBoxReducer = (state, [type, value]) => {
|
|
|
13
13
|
return { ...state, isOpen: false };
|
|
14
14
|
}
|
|
15
15
|
case 'select': {
|
|
16
|
+
if (Array.isArray(state.selected)) {
|
|
17
|
+
return { ...state };
|
|
18
|
+
}
|
|
16
19
|
return {
|
|
17
20
|
...state,
|
|
18
|
-
|
|
19
|
-
focusedIndex: value !== null &&
|
|
21
|
+
selected: (_a = action.value) !== null && _a !== void 0 ? _a : state.selected,
|
|
22
|
+
focusedIndex: (_b = action.value) !== null && _b !== void 0 ? _b : state.focusedIndex,
|
|
20
23
|
};
|
|
21
24
|
}
|
|
25
|
+
case 'multiselect': {
|
|
26
|
+
if (!Array.isArray(state.selected)) {
|
|
27
|
+
return { ...state };
|
|
28
|
+
}
|
|
29
|
+
return { ...state, selected: action.value };
|
|
30
|
+
}
|
|
22
31
|
case 'focus': {
|
|
23
|
-
|
|
32
|
+
if (Array.isArray(state.selected)) {
|
|
33
|
+
return {
|
|
34
|
+
...state,
|
|
35
|
+
focusedIndex: (_c = action.value) !== null && _c !== void 0 ? _c : -1,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
return {
|
|
39
|
+
...state,
|
|
40
|
+
focusedIndex: (_e = (_d = action.value) !== null && _d !== void 0 ? _d : state.selected) !== null && _e !== void 0 ? _e : -1,
|
|
41
|
+
};
|
|
24
42
|
}
|
|
25
43
|
default: {
|
|
26
44
|
return state;
|
|
@@ -6,9 +6,10 @@ import React from 'react';
|
|
|
6
6
|
import cx from 'classnames';
|
|
7
7
|
import { DropdownMenu } from '../DropdownMenu';
|
|
8
8
|
import { MenuItem } from '../Menu/MenuItem';
|
|
9
|
-
import { useTheme,
|
|
9
|
+
import { useTheme, SvgCaretDownSmall, } from '../utils';
|
|
10
10
|
import '@itwin/itwinui-css/css/select.css';
|
|
11
11
|
import SelectTag from './SelectTag';
|
|
12
|
+
import SelectTagContainer from './SelectTagContainer';
|
|
12
13
|
const isMultipleEnabled = (variable, multiple) => {
|
|
13
14
|
return multiple;
|
|
14
15
|
};
|
|
@@ -197,17 +198,11 @@ const MultipleSelectButton = ({ selectedItems, selectedItemsRenderer, tagRendere
|
|
|
197
198
|
}
|
|
198
199
|
return selectedItems.map((item) => tagRenderer(item));
|
|
199
200
|
}, [selectedItems, tagRenderer]);
|
|
200
|
-
const [containerRef, visibleCount] = useOverflow(selectedItemsElements);
|
|
201
201
|
return (React.createElement(React.Fragment, null,
|
|
202
202
|
selectedItems &&
|
|
203
203
|
selectedItemsRenderer &&
|
|
204
204
|
selectedItemsRenderer(selectedItems),
|
|
205
205
|
selectedItems && !selectedItemsRenderer && (React.createElement("span", { className: 'iui-content' },
|
|
206
|
-
React.createElement(
|
|
207
|
-
React.createElement(React.Fragment, null,
|
|
208
|
-
visibleCount < selectedItemsElements.length
|
|
209
|
-
? selectedItemsElements.slice(0, visibleCount - 1)
|
|
210
|
-
: selectedItemsElements,
|
|
211
|
-
visibleCount < selectedItemsElements.length && (React.createElement(SelectTag, { label: `+${selectedItemsElements.length - visibleCount + 1} item(s)` }))))))));
|
|
206
|
+
React.createElement(SelectTagContainer, { tags: selectedItemsElements })))));
|
|
212
207
|
};
|
|
213
208
|
export default Select;
|