@itwin/itwinui-react 2.0.2 → 2.1.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.
Files changed (60) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/cjs/core/ComboBox/ComboBox.d.ts +25 -8
  3. package/cjs/core/ComboBox/ComboBox.js +141 -44
  4. package/cjs/core/ComboBox/ComboBoxDropdown.d.ts +1 -1
  5. package/cjs/core/ComboBox/ComboBoxDropdown.js +2 -2
  6. package/cjs/core/ComboBox/ComboBoxEndIcon.js +1 -1
  7. package/cjs/core/ComboBox/ComboBoxInput.d.ts +2 -0
  8. package/cjs/core/ComboBox/ComboBoxInput.js +40 -23
  9. package/cjs/core/ComboBox/ComboBoxMenu.js +9 -8
  10. package/cjs/core/ComboBox/ComboBoxMenuItem.js +1 -1
  11. package/cjs/core/ComboBox/ComboBoxMultipleContainer.d.ts +4 -0
  12. package/cjs/core/ComboBox/ComboBoxMultipleContainer.js +17 -0
  13. package/cjs/core/ComboBox/helpers.d.ts +20 -5
  14. package/cjs/core/ComboBox/helpers.js +24 -6
  15. package/cjs/core/Select/Select.js +2 -7
  16. package/cjs/core/Select/SelectTagContainer.d.ts +16 -0
  17. package/cjs/core/Select/SelectTagContainer.js +27 -0
  18. package/cjs/core/Table/Table.js +4 -2
  19. package/cjs/core/Table/actionHandlers/index.d.ts +1 -1
  20. package/cjs/core/Table/actionHandlers/index.js +2 -2
  21. package/cjs/core/Table/actionHandlers/selectHandler.d.ts +2 -2
  22. package/cjs/core/Table/actionHandlers/selectHandler.js +20 -6
  23. package/cjs/core/ThemeProvider/ThemeProvider.d.ts +25 -6
  24. package/cjs/core/ThemeProvider/ThemeProvider.js +29 -12
  25. package/cjs/core/utils/components/Popover.d.ts +1 -1
  26. package/cjs/core/utils/hooks/index.d.ts +1 -0
  27. package/cjs/core/utils/hooks/index.js +1 -0
  28. package/cjs/core/utils/hooks/useIsThemeAlreadySet.d.ts +7 -0
  29. package/cjs/core/utils/hooks/useIsThemeAlreadySet.js +34 -0
  30. package/cjs/core/utils/hooks/useTheme.js +4 -9
  31. package/esm/core/ComboBox/ComboBox.d.ts +25 -8
  32. package/esm/core/ComboBox/ComboBox.js +141 -44
  33. package/esm/core/ComboBox/ComboBoxDropdown.d.ts +1 -1
  34. package/esm/core/ComboBox/ComboBoxDropdown.js +2 -2
  35. package/esm/core/ComboBox/ComboBoxEndIcon.js +1 -1
  36. package/esm/core/ComboBox/ComboBoxInput.d.ts +2 -0
  37. package/esm/core/ComboBox/ComboBoxInput.js +41 -24
  38. package/esm/core/ComboBox/ComboBoxMenu.js +9 -8
  39. package/esm/core/ComboBox/ComboBoxMenuItem.js +1 -1
  40. package/esm/core/ComboBox/ComboBoxMultipleContainer.d.ts +4 -0
  41. package/esm/core/ComboBox/ComboBoxMultipleContainer.js +11 -0
  42. package/esm/core/ComboBox/helpers.d.ts +20 -5
  43. package/esm/core/ComboBox/helpers.js +24 -6
  44. package/esm/core/Select/Select.js +3 -8
  45. package/esm/core/Select/SelectTagContainer.d.ts +16 -0
  46. package/esm/core/Select/SelectTagContainer.js +21 -0
  47. package/esm/core/Table/Table.js +5 -3
  48. package/esm/core/Table/actionHandlers/index.d.ts +1 -1
  49. package/esm/core/Table/actionHandlers/index.js +1 -1
  50. package/esm/core/Table/actionHandlers/selectHandler.d.ts +2 -2
  51. package/esm/core/Table/actionHandlers/selectHandler.js +17 -3
  52. package/esm/core/ThemeProvider/ThemeProvider.d.ts +25 -6
  53. package/esm/core/ThemeProvider/ThemeProvider.js +30 -13
  54. package/esm/core/utils/components/Popover.d.ts +1 -1
  55. package/esm/core/utils/hooks/index.d.ts +1 -0
  56. package/esm/core/utils/hooks/index.js +1 -0
  57. package/esm/core/utils/hooks/useIsThemeAlreadySet.d.ts +7 -0
  58. package/esm/core/utils/hooks/useIsThemeAlreadySet.js +27 -0
  59. package/esm/core/utils/hooks/useTheme.js +4 -6
  60. package/package.json +10 -5
package/CHANGELOG.md CHANGED
@@ -1,5 +1,24 @@
1
1
  # Changelog
2
2
 
3
+ ## [2.1.0](https://www.github.com/iTwin/iTwinUI-react/compare/v2.0.2...v2.1.0) (2022-12-12)
4
+
5
+ ### What's new
6
+
7
+ * **Combobox:** Add support for multi-selection through `multiple` prop ([#830](https://www.github.com/iTwin/iTwinUI-react/issues/830)) ([ced7588](https://www.github.com/iTwin/iTwinUI-react/commit/ced7588acee5273e83c0b0d05a732f260997b9bb))
8
+ * **ThemeProvider:** Add `applyBackground` to `themeOptions` prop. ([#974](https://www.github.com/iTwin/iTwinUI-react/issues/974)) ([13cff7f](https://www.github.com/iTwin/iTwinUI-react/commit/13cff7fab356e1015e2e7c361f15c6a7c8fe4d6b))
9
+ * Defaults to true for the topmost ThemeProvider in the tree.
10
+ * **Table:** Ctrl + Shift click now keeps previous selection & also shift clicks ([#888](https://www.github.com/iTwin/iTwinUI-react/issues/888)) ([13edfb5](https://www.github.com/iTwin/iTwinUI-react/commit/13edfb5ecc62de284ac03cc2fc27b8a41b4f7b61))
11
+
12
+ ### Fixes
13
+
14
+ * **Table:** Ctrl and checkbox clicks update start row of shift selection ([#889](https://www.github.com/iTwin/iTwinUI-react/issues/889)) ([21900d4](https://www.github.com/iTwin/iTwinUI-react/commit/21900d42900f7a03305bf2040cc64ddb29361b2d))
15
+
16
+ ### 2.0.3 (2022-12-12)
17
+
18
+ ### Fixes
19
+
20
+ * **ComboBox:** Move max-height to outer element to fix virtual scroll ([#986](https://www.github.com/iTwin/iTwinUI-react/issues/986)) ([596559e](https://www.github.com/iTwin/iTwinUI-react/commit/596559e7158877e09c98c5c672e8a58a9507a33d))
21
+
3
22
  ### [2.0.2](https://www.github.com/iTwin/iTwinUI-react/compare/v2.0.1...v2.0.2) (2022-12-07)
4
23
 
5
24
  ### Fixes
@@ -3,24 +3,41 @@ import { InputProps } from '../Input';
3
3
  import { SelectOption } from '../Select';
4
4
  import { PopoverProps, CommonProps, InputContainerProps } from '../utils';
5
5
  import 'tippy.js/animations/shift-away.css';
6
- export declare type ComboBoxProps<T> = {
6
+ declare type ActionType = 'added' | 'removed';
7
+ declare type MultipleOnChangeProps<T> = {
8
+ value: T;
9
+ type: ActionType;
10
+ };
11
+ export declare type ComboboxMultipleTypeProps<T> = {
7
12
  /**
8
- * Array of options that populate the dropdown list.
13
+ * Enable multiple selection.
14
+ * @default false
9
15
  */
10
- options: SelectOption<T>[];
16
+ multiple?: false;
11
17
  /**
12
18
  * Controlled value of ComboBox.
19
+ * If `multiple` is enabled, it is an array of values.
13
20
  */
14
21
  value?: T;
22
+ /**
23
+ * Callback fired when selected value changes.
24
+ */
25
+ onChange?: (value: T) => void;
26
+ } | {
27
+ multiple: true;
28
+ value?: T[];
29
+ onChange?: (value: T[], event: MultipleOnChangeProps<T>) => void;
30
+ };
31
+ export declare type ComboBoxProps<T> = {
32
+ /**
33
+ * Array of options that populate the dropdown list.
34
+ */
35
+ options: SelectOption<T>[];
15
36
  /**
16
37
  * Message shown below the combobox.
17
38
  * Use `StatusMessage` component.
18
39
  */
19
40
  message?: React.ReactNode;
20
- /**
21
- * Callback fired when selected value changes.
22
- */
23
- onChange?: (value: T) => void;
24
41
  /**
25
42
  * Function to customize the default filtering logic.
26
43
  */
@@ -67,7 +84,7 @@ export declare type ComboBoxProps<T> = {
67
84
  * Callback fired when dropdown menu is closed.
68
85
  */
69
86
  onHide?: () => void;
70
- } & Pick<InputContainerProps, 'status'> & Omit<CommonProps, 'title'>;
87
+ } & ComboboxMultipleTypeProps<T> & Pick<InputContainerProps, 'status'> & Omit<CommonProps, 'title'>;
71
88
  /**
72
89
  * ComboBox component that allows typing a value to filter the options in dropdown list.
73
90
  * Values can be selected either using mouse clicks or using the Enter key.
@@ -11,6 +11,7 @@ exports.ComboBox = void 0;
11
11
  const react_1 = __importDefault(require("react"));
12
12
  const classnames_1 = __importDefault(require("classnames"));
13
13
  const Menu_1 = require("../Menu");
14
+ const SelectTag_1 = __importDefault(require("../Select/SelectTag"));
14
15
  const Typography_1 = require("../Typography");
15
16
  const utils_1 = require("../utils");
16
17
  require("tippy.js/animations/shift-away.css");
@@ -21,6 +22,14 @@ const ComboBoxInput_1 = require("./ComboBoxInput");
21
22
  const ComboBoxInputContainer_1 = require("./ComboBoxInputContainer");
22
23
  const ComboBoxMenu_1 = require("./ComboBoxMenu");
23
24
  const ComboBoxMenuItem_1 = require("./ComboBoxMenuItem");
25
+ // Type guard for enabling multiple
26
+ const isMultipleEnabled = (variable, multiple) => {
27
+ return multiple && (Array.isArray(variable) || variable === undefined);
28
+ };
29
+ // Type guard for user onChange
30
+ const isSingleOnChange = (onChange, multiple) => {
31
+ return !multiple;
32
+ };
24
33
  /** Returns either `option.id` or derives a stable id using `idPrefix` and `option.label` (without whitespace) */
25
34
  const getOptionId = (option, idPrefix) => {
26
35
  var _a;
@@ -41,7 +50,7 @@ const getOptionId = (option, idPrefix) => {
41
50
  */
42
51
  const ComboBox = (props) => {
43
52
  var _a, _b;
44
- const { options, value: valueProp, onChange, filterFunction, inputProps, dropdownMenuProps, emptyStateMessage = 'No options found', itemRenderer, enableVirtualization = false, onShow, onHide, ...rest } = props;
53
+ const { options, value: valueProp, onChange, filterFunction, inputProps, dropdownMenuProps, emptyStateMessage = 'No options found', itemRenderer, enableVirtualization = false, multiple = false, onShow, onHide, ...rest } = props;
45
54
  // Generate a stateful random id if not specified
46
55
  const [id] = react_1.default.useState(() => {
47
56
  var _a, _b;
@@ -52,8 +61,6 @@ const ComboBox = (props) => {
52
61
  const inputRef = react_1.default.useRef(null);
53
62
  const menuRef = react_1.default.useRef(null);
54
63
  const toggleButtonRef = react_1.default.useRef(null);
55
- const mounted = react_1.default.useRef(false);
56
- const valuePropRef = (0, utils_1.useLatestRef)(valueProp);
57
64
  const onChangeProp = (0, utils_1.useLatestRef)(onChange);
58
65
  const optionsRef = (0, utils_1.useLatestRef)(options);
59
66
  // Record to store all extra information (e.g. original indexes), where the key is the id of the option
@@ -71,12 +78,23 @@ const ComboBox = (props) => {
71
78
  };
72
79
  });
73
80
  }
81
+ // Get indices of selected elements in options array when we have selected values.
82
+ const getSelectedIndexes = react_1.default.useCallback(() => {
83
+ if (isMultipleEnabled(valueProp, multiple)) {
84
+ const indexArray = [];
85
+ valueProp === null || valueProp === void 0 ? void 0 : valueProp.forEach((value) => {
86
+ indexArray.push(options.findIndex((option) => option.value === value));
87
+ });
88
+ return indexArray;
89
+ }
90
+ else {
91
+ return options.findIndex((option) => option.value === valueProp);
92
+ }
93
+ }, [multiple, options, valueProp]);
74
94
  // Reducer where all the component-wide state is stored
75
- const [{ isOpen, selectedIndex, focusedIndex }, dispatch] = react_1.default.useReducer(helpers_1.comboBoxReducer, {
95
+ const [{ isOpen, selected, focusedIndex }, dispatch] = react_1.default.useReducer(helpers_1.comboBoxReducer, {
76
96
  isOpen: false,
77
- selectedIndex: valueProp
78
- ? optionsRef.current.findIndex((option) => option.value === valueProp)
79
- : -1,
97
+ selected: getSelectedIndexes(),
80
98
  focusedIndex: -1,
81
99
  });
82
100
  (0, utils_1.useIsomorphicLayoutEffect)(() => {
@@ -84,19 +102,24 @@ const ComboBox = (props) => {
84
102
  // When the dropdown opens
85
103
  if (isOpen) {
86
104
  (_a = inputRef.current) === null || _a === void 0 ? void 0 : _a.focus(); // Focus the input
87
- setFilteredOptions(optionsRef.current); // Reset the filtered list
88
- dispatch(['focus']);
105
+ // Reset the filtered list (does not reset when multiple enabled)
106
+ if (!multiple) {
107
+ setFilteredOptions(optionsRef.current);
108
+ dispatch({ type: 'focus', value: undefined });
109
+ }
89
110
  }
90
111
  // When the dropdown closes
91
112
  else {
92
113
  // Reset the focused index
93
- dispatch(['focus']);
94
- // Reset the input value
95
- setInputValue(selectedIndex != undefined && selectedIndex >= 0
96
- ? (_b = optionsRef.current[selectedIndex]) === null || _b === void 0 ? void 0 : _b.label
97
- : '');
114
+ dispatch({ type: 'focus', value: undefined });
115
+ // Reset the input value if not multiple
116
+ if (!isMultipleEnabled(selected, multiple)) {
117
+ setInputValue(selected != undefined && selected >= 0
118
+ ? (_b = optionsRef.current[selected]) === null || _b === void 0 ? void 0 : _b.label
119
+ : '');
120
+ }
98
121
  }
99
- }, [isOpen, optionsRef, selectedIndex]);
122
+ }, [isOpen, multiple, optionsRef, selected]);
100
123
  // Set min-width of menu to be same as input
101
124
  const [minWidth, setMinWidth] = react_1.default.useState(0);
102
125
  react_1.default.useEffect(() => {
@@ -114,7 +137,7 @@ const ComboBox = (props) => {
114
137
  else {
115
138
  setFilteredOptions(options);
116
139
  }
117
- dispatch(['focus']);
140
+ dispatch({ type: 'focus', value: undefined });
118
141
  // Only need to call on options update
119
142
  // eslint-disable-next-line react-hooks/exhaustive-deps
120
143
  }, [options]);
@@ -124,41 +147,101 @@ const ComboBox = (props) => {
124
147
  var _a, _b;
125
148
  const { value } = event.currentTarget;
126
149
  setInputValue(value);
127
- dispatch(['open']); // reopen when typing
150
+ dispatch({ type: 'open' }); // reopen when typing
128
151
  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())));
129
152
  if (focusedIndex != -1) {
130
- dispatch(['focus', -1]);
153
+ dispatch({ type: 'focus', value: -1 });
131
154
  }
132
155
  (_b = inputProps === null || inputProps === void 0 ? void 0 : inputProps.onChange) === null || _b === void 0 ? void 0 : _b.call(inputProps, event);
133
156
  }, [filterFunction, focusedIndex, inputProps, optionsRef]);
134
- // When the value prop changes, update the selectedIndex
157
+ // When the value prop changes, update the selected index/indices
135
158
  react_1.default.useEffect(() => {
136
- dispatch([
137
- 'select',
138
- options.findIndex((option) => option.value === valueProp),
139
- ]);
140
- }, [options, valueProp]);
141
- // Call user-defined onChange when the value actually changes
142
- react_1.default.useEffect(() => {
143
- var _a, _b;
144
- // Prevent user-defined onChange to be called on mount
145
- if (!mounted.current) {
146
- mounted.current = true;
147
- return;
159
+ if (isMultipleEnabled(valueProp, multiple)) {
160
+ if (valueProp) {
161
+ // If user provided array of selected values
162
+ const indexes = valueProp.map((value) => {
163
+ return options.findIndex((option) => option.value === value);
164
+ });
165
+ dispatch({
166
+ type: 'multiselect',
167
+ value: indexes.filter((index) => index !== -1), // Add available options
168
+ });
169
+ }
170
+ else {
171
+ // if user provided one value or undefined
172
+ dispatch({
173
+ type: 'multiselect',
174
+ value: [], // Add empty list
175
+ });
176
+ }
177
+ }
178
+ else {
179
+ dispatch({
180
+ type: 'select',
181
+ value: options.findIndex((option) => option.value === valueProp),
182
+ });
183
+ }
184
+ }, [valueProp, options, multiple]);
185
+ const isMenuItemSelected = react_1.default.useCallback((index) => {
186
+ if (isMultipleEnabled(selected, multiple)) {
187
+ return !!selected.includes(index);
148
188
  }
149
- const currentValue = (_a = optionsRef.current[selectedIndex]) === null || _a === void 0 ? void 0 : _a.value;
150
- if (currentValue === valuePropRef.current || selectedIndex === -1) {
151
- return;
189
+ else {
190
+ return selected === index;
191
+ }
192
+ }, [multiple, selected]);
193
+ // Generates new array when item is added or removed
194
+ const selectedChangeHandler = react_1.default.useCallback((__originalIndex, action) => {
195
+ if (action === 'added') {
196
+ return [...selected, __originalIndex];
197
+ }
198
+ else {
199
+ return selected.filter((index) => index !== __originalIndex);
200
+ }
201
+ }, [selected]);
202
+ // Calls user defined onChange
203
+ const onChangeHandler = react_1.default.useCallback((__originalIndex, actionType, newArray) => {
204
+ var _a, _b, _c, _d;
205
+ if (isSingleOnChange(onChangeProp.current, multiple)) {
206
+ (_a = onChangeProp.current) === null || _a === void 0 ? void 0 : _a.call(onChangeProp, (_b = optionsRef.current[__originalIndex]) === null || _b === void 0 ? void 0 : _b.value);
207
+ }
208
+ else {
209
+ actionType &&
210
+ newArray &&
211
+ ((_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; }), {
212
+ value: (_d = optionsRef.current[__originalIndex]) === null || _d === void 0 ? void 0 : _d.value,
213
+ type: actionType,
214
+ }));
215
+ }
216
+ }, [multiple, onChangeProp, optionsRef]);
217
+ const onClickHandler = react_1.default.useCallback((__originalIndex) => {
218
+ if (isMultipleEnabled(selected, multiple)) {
219
+ const actionType = isMenuItemSelected(__originalIndex)
220
+ ? 'removed'
221
+ : 'added';
222
+ const newArray = selectedChangeHandler(__originalIndex, actionType);
223
+ dispatch({ type: 'multiselect', value: newArray });
224
+ onChangeHandler(__originalIndex, actionType, newArray);
225
+ }
226
+ else {
227
+ dispatch({ type: 'select', value: __originalIndex });
228
+ dispatch({ type: 'close' });
229
+ onChangeHandler(__originalIndex);
152
230
  }
153
- (_b = onChangeProp.current) === null || _b === void 0 ? void 0 : _b.call(onChangeProp, currentValue);
154
- }, [onChangeProp, optionsRef, selectedIndex, valuePropRef]);
231
+ }, [
232
+ selectedChangeHandler,
233
+ isMenuItemSelected,
234
+ multiple,
235
+ onChangeHandler,
236
+ selected,
237
+ ]);
155
238
  const getMenuItem = react_1.default.useCallback((option, filteredIndex) => {
156
239
  const optionId = getOptionId(option, id);
157
240
  const { __originalIndex } = optionsExtraInfoRef.current[optionId];
158
241
  const customItem = itemRenderer
159
242
  ? itemRenderer(option, {
160
243
  isFocused: focusedIndex === __originalIndex,
161
- isSelected: selectedIndex === __originalIndex,
244
+ isSelected: selected === __originalIndex,
162
245
  index: __originalIndex,
163
246
  id: optionId,
164
247
  })
@@ -166,8 +249,7 @@ const ComboBox = (props) => {
166
249
  return customItem ? (react_1.default.cloneElement(customItem, {
167
250
  onClick: (e) => {
168
251
  var _a, _b;
169
- dispatch(['select', __originalIndex]);
170
- dispatch(['close']);
252
+ onClickHandler(__originalIndex);
171
253
  (_b = (_a = customItem.props).onClick) === null || _b === void 0 ? void 0 : _b.call(_a, e);
172
254
  },
173
255
  // ComboBox.MenuItem handles scrollIntoView, data-iui-index and iui-focused through context
@@ -182,11 +264,18 @@ const ComboBox = (props) => {
182
264
  el === null || el === void 0 ? void 0 : el.scrollIntoView({ block: 'nearest' });
183
265
  }
184
266
  }),
185
- })) : (react_1.default.createElement(ComboBoxMenuItem_1.ComboBoxMenuItem, { key: optionId, id: optionId, ...option, isSelected: selectedIndex === __originalIndex, onClick: () => {
186
- dispatch(['select', __originalIndex]);
187
- dispatch(['close']);
267
+ })) : (react_1.default.createElement(ComboBoxMenuItem_1.ComboBoxMenuItem, { key: optionId, id: optionId, ...option, isSelected: isMenuItemSelected(__originalIndex), onClick: () => {
268
+ onClickHandler(__originalIndex);
188
269
  }, index: __originalIndex, "data-iui-filtered-index": filteredIndex }, option.label));
189
- }, [enableVirtualization, focusedIndex, id, itemRenderer, selectedIndex]);
270
+ }, [
271
+ enableVirtualization,
272
+ focusedIndex,
273
+ id,
274
+ isMenuItemSelected,
275
+ itemRenderer,
276
+ onClickHandler,
277
+ selected,
278
+ ]);
190
279
  const emptyContent = react_1.default.useMemo(() => (react_1.default.createElement(react_1.default.Fragment, null, react_1.default.isValidElement(emptyStateMessage) ? (emptyStateMessage) : (react_1.default.createElement(Menu_1.MenuExtraContent, null,
191
280
  react_1.default.createElement(Typography_1.Text, { isMuted: true }, emptyStateMessage))))), [emptyStateMessage]);
192
281
  return (react_1.default.createElement(helpers_1.ComboBoxRefsContext.Provider, { value: { inputRef, menuRef, toggleButtonRef, optionsExtraInfoRef } },
@@ -196,12 +285,20 @@ const ComboBox = (props) => {
196
285
  minWidth,
197
286
  isOpen,
198
287
  focusedIndex,
288
+ onClickHandler,
199
289
  enableVirtualization,
200
290
  filteredOptions,
201
291
  getMenuItem,
292
+ multiple,
202
293
  } },
203
294
  react_1.default.createElement(ComboBoxInputContainer_1.ComboBoxInputContainer, { disabled: inputProps === null || inputProps === void 0 ? void 0 : inputProps.disabled, ...rest },
204
- react_1.default.createElement(ComboBoxInput_1.ComboBoxInput, { value: inputValue, ...inputProps, onChange: handleOnInput }),
295
+ react_1.default.createElement(react_1.default.Fragment, null,
296
+ react_1.default.createElement(ComboBoxInput_1.ComboBoxInput, { value: inputValue, ...inputProps, onChange: handleOnInput, selectTags: isMultipleEnabled(selected, multiple)
297
+ ? selected.map((index) => {
298
+ const item = optionsRef.current[index];
299
+ return (react_1.default.createElement(SelectTag_1.default, { key: item.label, label: item.label }));
300
+ })
301
+ : undefined })),
205
302
  react_1.default.createElement(ComboBoxEndIcon_1.ComboBoxEndIcon, { disabled: inputProps === null || inputProps === void 0 ? void 0 : inputProps.disabled, isOpen: isOpen })),
206
303
  react_1.default.createElement(ComboBoxDropdown_1.ComboBoxDropdown, { ...dropdownMenuProps, onShow: onShow, onHide: onHide },
207
304
  react_1.default.createElement(ComboBoxMenu_1.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" | "placement" | "trigger" | "visible" | "content" | "render" | "animateFill" | "appendTo" | "aria" | "delay" | "duration" | "followCursor" | "getReferenceClientRect" | "hideOnClick" | "ignoreAttributes" | "inlinePositioning" | "interactive" | "interactiveBorder" | "interactiveDebounce" | "moveTransition" | "offset" | "plugins" | "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>>;
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 {};
@@ -19,13 +19,13 @@ exports.ComboBoxDropdown = react_1.default.forwardRef((props, forwardedRef) => {
19
19
  // sync internal isOpen state with user's visible prop
20
20
  react_1.default.useEffect(() => {
21
21
  if (props.visible != undefined) {
22
- dispatch([props.visible ? 'open' : 'close']);
22
+ dispatch({ type: props.visible ? 'open' : 'close' });
23
23
  }
24
24
  }, [dispatch, props.visible]);
25
25
  return (react_1.default.createElement(utils_1.Popover, { placement: 'bottom-start', visible: isOpen, onClickOutside: react_1.default.useCallback((_, { target }) => {
26
26
  var _a;
27
27
  if (!((_a = toggleButtonRef.current) === null || _a === void 0 ? void 0 : _a.contains(target))) {
28
- dispatch(['close']);
28
+ dispatch({ type: 'close' });
29
29
  }
30
30
  }, [dispatch, toggleButtonRef]), animation: 'shift-away', duration: 200, reference: inputRef, ref: forwardedRef, content: children, ...rest }));
31
31
  });
@@ -24,7 +24,7 @@ exports.ComboBoxEndIcon = react_1.default.forwardRef((props, forwardedRef) => {
24
24
  }, className), onClick: (e) => {
25
25
  onClickProp === null || onClickProp === void 0 ? void 0 : onClickProp(e);
26
26
  if (!e.isDefaultPrevented()) {
27
- dispatch([isOpen ? 'close' : 'open']);
27
+ dispatch({ type: isOpen ? 'close' : 'open' });
28
28
  }
29
29
  }, ...rest }, children !== null && children !== void 0 ? children : react_1.default.createElement(utils_1.SvgCaretDownSmall, { "aria-hidden": true })));
30
30
  });
@@ -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>>;
@@ -11,10 +11,11 @@ exports.ComboBoxInput = void 0;
11
11
  const react_1 = __importDefault(require("react"));
12
12
  const Input_1 = require("../Input");
13
13
  const utils_1 = require("../utils");
14
+ const ComboBoxMultipleContainer_1 = require("./ComboBoxMultipleContainer");
14
15
  const helpers_1 = require("./helpers");
15
16
  exports.ComboBoxInput = react_1.default.forwardRef((props, forwardedRef) => {
16
- const { onKeyDown: onKeyDownProp, onFocus: onFocusProp, ...rest } = props;
17
- const { isOpen, id, focusedIndex, enableVirtualization } = (0, utils_1.useSafeContext)(helpers_1.ComboBoxStateContext);
17
+ const { onKeyDown: onKeyDownProp, onFocus: onFocusProp, selectTags, ...rest } = props;
18
+ const { isOpen, id, focusedIndex, enableVirtualization, multiple, onClickHandler, } = (0, utils_1.useSafeContext)(helpers_1.ComboBoxStateContext);
18
19
  const dispatch = (0, utils_1.useSafeContext)(helpers_1.ComboBoxActionContext);
19
20
  const { inputRef, menuRef, optionsExtraInfoRef } = (0, utils_1.useSafeContext)(helpers_1.ComboBoxRefsContext);
20
21
  const refs = (0, utils_1.useMergedRefs)(inputRef, forwardedRef);
@@ -34,17 +35,17 @@ exports.ComboBoxInput = react_1.default.forwardRef((props, forwardedRef) => {
34
35
  case 'ArrowDown': {
35
36
  event.preventDefault();
36
37
  if (!isOpen) {
37
- return dispatch(['open']);
38
+ return dispatch({ type: 'open' });
38
39
  }
39
40
  if (length === 0) {
40
41
  return;
41
42
  }
42
43
  if (focusedIndexRef.current === -1) {
43
44
  const currentElement = (_b = menuRef.current) === null || _b === void 0 ? void 0 : _b.querySelector('[data-iui-index]');
44
- return dispatch([
45
- 'focus',
46
- Number((_c = currentElement === null || currentElement === void 0 ? void 0 : currentElement.getAttribute('data-iui-index')) !== null && _c !== void 0 ? _c : 0),
47
- ]);
45
+ return dispatch({
46
+ type: 'focus',
47
+ value: Number((_c = currentElement === null || currentElement === void 0 ? void 0 : currentElement.getAttribute('data-iui-index')) !== null && _c !== void 0 ? _c : 0),
48
+ });
48
49
  }
49
50
  // If virtualization is enabled, dont let round scrolling
50
51
  if (enableVirtualization &&
@@ -57,7 +58,7 @@ exports.ComboBoxInput = react_1.default.forwardRef((props, forwardedRef) => {
57
58
  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]');
58
59
  nextIndex = Number(nextElement === null || nextElement === void 0 ? void 0 : nextElement.getAttribute('data-iui-index'));
59
60
  if ((nextElement === null || nextElement === void 0 ? void 0 : nextElement.ariaDisabled) !== 'true') {
60
- return dispatch(['focus', nextIndex]);
61
+ return dispatch({ type: 'focus', value: nextIndex });
61
62
  }
62
63
  } while (nextIndex !== focusedIndexRef.current);
63
64
  break;
@@ -65,7 +66,7 @@ exports.ComboBoxInput = react_1.default.forwardRef((props, forwardedRef) => {
65
66
  case 'ArrowUp': {
66
67
  event.preventDefault();
67
68
  if (!isOpen) {
68
- return dispatch(['open']);
69
+ return dispatch({ type: 'open' });
69
70
  }
70
71
  if (length === 0) {
71
72
  return;
@@ -76,10 +77,10 @@ exports.ComboBoxInput = react_1.default.forwardRef((props, forwardedRef) => {
76
77
  return;
77
78
  }
78
79
  if (focusedIndexRef.current === -1) {
79
- return dispatch([
80
- 'focus',
81
- (_m = (_l = Object.values(optionsExtraInfoRef.current)) === null || _l === void 0 ? void 0 : _l[length - 1].__originalIndex) !== null && _m !== void 0 ? _m : -1,
82
- ]);
80
+ return dispatch({
81
+ type: 'focus',
82
+ value: (_m = (_l = Object.values(optionsExtraInfoRef.current)) === null || _l === void 0 ? void 0 : _l[length - 1].__originalIndex) !== null && _m !== void 0 ? _m : -1,
83
+ });
83
84
  }
84
85
  let prevIndex = focusedIndexRef.current;
85
86
  do {
@@ -87,7 +88,7 @@ exports.ComboBoxInput = react_1.default.forwardRef((props, forwardedRef) => {
87
88
  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');
88
89
  prevIndex = Number(prevElement === null || prevElement === void 0 ? void 0 : prevElement.getAttribute('data-iui-index'));
89
90
  if ((prevElement === null || prevElement === void 0 ? void 0 : prevElement.ariaDisabled) !== 'true') {
90
- return dispatch(['focus', prevIndex]);
91
+ return dispatch({ type: 'focus', value: prevIndex });
91
92
  }
92
93
  } while (prevIndex !== focusedIndexRef.current);
93
94
  break;
@@ -95,21 +96,32 @@ exports.ComboBoxInput = react_1.default.forwardRef((props, forwardedRef) => {
95
96
  case 'Enter': {
96
97
  event.preventDefault();
97
98
  if (isOpen) {
98
- dispatch(['select', focusedIndexRef.current]);
99
- dispatch(['close']);
99
+ if (multiple) {
100
+ // Keep menu open when multiselect is enabled and user selects an item
101
+ if (focusedIndexRef.current > -1) {
102
+ onClickHandler === null || onClickHandler === void 0 ? void 0 : onClickHandler(focusedIndexRef.current);
103
+ }
104
+ else {
105
+ dispatch({ type: 'close' });
106
+ }
107
+ }
108
+ else {
109
+ onClickHandler === null || onClickHandler === void 0 ? void 0 : onClickHandler(focusedIndexRef.current);
110
+ dispatch({ type: 'close' });
111
+ }
100
112
  }
101
113
  else {
102
- dispatch(['open']);
114
+ dispatch({ type: 'open' });
103
115
  }
104
116
  break;
105
117
  }
106
118
  case 'Escape': {
107
119
  event.preventDefault();
108
- dispatch(['close']);
120
+ dispatch({ type: 'close' });
109
121
  break;
110
122
  }
111
123
  case 'Tab':
112
- dispatch(['close']);
124
+ dispatch({ type: 'close' });
113
125
  break;
114
126
  }
115
127
  }, [
@@ -117,15 +129,20 @@ exports.ComboBoxInput = react_1.default.forwardRef((props, forwardedRef) => {
117
129
  enableVirtualization,
118
130
  isOpen,
119
131
  menuRef,
132
+ multiple,
133
+ onClickHandler,
120
134
  onKeyDownProp,
121
135
  optionsExtraInfoRef,
122
136
  ]);
123
137
  const handleFocus = react_1.default.useCallback((event) => {
124
- dispatch(['open']);
138
+ dispatch({ type: 'open' });
125
139
  onFocusProp === null || onFocusProp === void 0 ? void 0 : onFocusProp(event);
126
140
  }, [dispatch, onFocusProp]);
127
- return (react_1.default.createElement(Input_1.Input, { ref: refs, onKeyDown: handleKeyDown, onFocus: handleFocus, "aria-activedescendant": isOpen && focusedIndex != undefined && focusedIndex > -1
128
- ? getIdFromIndex(focusedIndex)
129
- : undefined, role: 'combobox', "aria-controls": isOpen ? `${id}-list` : undefined, "aria-autocomplete": 'list', spellCheck: false, autoCapitalize: 'none', autoCorrect: 'off', ...rest }));
141
+ const [tagContainerWidthRef, tagContainerWidth] = (0, utils_1.useContainerWidth)();
142
+ return (react_1.default.createElement(react_1.default.Fragment, null,
143
+ react_1.default.createElement(Input_1.Input, { ref: refs, onKeyDown: handleKeyDown, onFocus: handleFocus, "aria-activedescendant": isOpen && focusedIndex != undefined && focusedIndex > -1
144
+ ? getIdFromIndex(focusedIndex)
145
+ : undefined, role: 'combobox', "aria-controls": isOpen ? `${id}-list` : undefined, "aria-autocomplete": 'list', spellCheck: false, autoCapitalize: 'none', autoCorrect: 'off', style: multiple ? { paddingLeft: tagContainerWidth + 18 } : {}, ...rest }),
146
+ multiple && selectTags && (react_1.default.createElement(ComboBoxMultipleContainer_1.ComboBoxMultipleContainer, { ref: tagContainerWidthRef, selectedItems: selectTags }))));
130
147
  });
131
148
  exports.ComboBoxInput.displayName = 'ComboBoxInput';
@@ -14,8 +14,8 @@ const Menu_1 = require("../Menu");
14
14
  const Surface_1 = require("../Surface");
15
15
  const utils_1 = require("../utils");
16
16
  const helpers_1 = require("./helpers");
17
+ const isOverflowOverlaySupported = () => { var _a, _b, _c; return (_c = (_b = (_a = (0, utils_1.getWindow)()) === null || _a === void 0 ? void 0 : _a.CSS) === null || _b === void 0 ? void 0 : _b.supports) === null || _c === void 0 ? void 0 : _c.call(_b, 'overflow: overlay'); };
17
18
  const VirtualizedComboBoxMenu = react_1.default.forwardRef(({ children, className, style, ...rest }, forwardedRef) => {
18
- var _a, _b, _c;
19
19
  const { minWidth, id, filteredOptions, getMenuItem, focusedIndex } = (0, utils_1.useSafeContext)(helpers_1.ComboBoxStateContext);
20
20
  const { menuRef } = (0, utils_1.useSafeContext)(helpers_1.ComboBoxRefsContext);
21
21
  const virtualItemRenderer = react_1.default.useCallback((index) => filteredOptions.length > 0
@@ -37,16 +37,17 @@ const VirtualizedComboBoxMenu = react_1.default.forwardRef(({ children, classNam
37
37
  itemRenderer: virtualItemRenderer,
38
38
  scrollToIndex: focusedVisibleIndex,
39
39
  });
40
- const overflowY = ((_c = (_b = (_a = (0, utils_1.getWindow)()) === null || _a === void 0 ? void 0 : _a.CSS) === null || _b === void 0 ? void 0 : _b.supports) === null || _c === void 0 ? void 0 : _c.call(_b, 'overflow-x: overlay'))
41
- ? { overflowY: 'overlay' }
42
- : { overflowY: 'auto' };
43
- const styles = react_1.default.useMemo(() => ({
40
+ const surfaceStyles = {
44
41
  minWidth,
45
42
  maxWidth: `min(${minWidth * 2}px, 90vw)`,
46
- }), [minWidth]);
47
- return (react_1.default.createElement(Surface_1.Surface, { elevation: 1, style: { ...styles, ...overflowY, ...style }, ...rest },
43
+ // max-height must be on the outermost element for virtual scroll
44
+ maxHeight: 'calc((var(--iui-component-height) - 1px) * 8.5)',
45
+ overflowY: isOverflowOverlaySupported() ? 'overlay' : 'auto',
46
+ ...style,
47
+ };
48
+ return (react_1.default.createElement(Surface_1.Surface, { style: surfaceStyles, ...rest },
48
49
  react_1.default.createElement("div", { ...outerProps },
49
- react_1.default.createElement(Menu_1.Menu, { id: `${id}-list`, setFocus: false, role: 'listbox', ref: (0, utils_1.mergeRefs)(menuRef, innerProps.ref, forwardedRef), className: (0, classnames_1.default)('iui-scroll', className), style: innerProps.style }, visibleChildren))));
50
+ react_1.default.createElement(Menu_1.Menu, { id: `${id}-list`, setFocus: false, role: 'listbox', ref: (0, utils_1.mergeRefs)(menuRef, innerProps.ref, forwardedRef), className: className, style: innerProps.style }, visibleChildren))));
50
51
  });
51
52
  exports.ComboBoxMenu = react_1.default.forwardRef((props, forwardedRef) => {
52
53
  const { className, style, ...rest } = props;
@@ -13,7 +13,7 @@ const react_1 = __importDefault(require("react"));
13
13
  const utils_1 = require("../utils");
14
14
  const helpers_1 = require("./helpers");
15
15
  exports.ComboBoxMenuItem = react_1.default.memo(react_1.default.forwardRef((props, forwardedRef) => {
16
- const { children, isSelected, disabled, value, onClick, sublabel, size = !!sublabel ? 'large' : 'default', icon, badge, className, role = 'menuitem', index, ...rest } = props;
16
+ const { children, isSelected, disabled, value, onClick, sublabel, size = !!sublabel ? 'large' : 'default', icon, badge, className, role = 'option', index, ...rest } = props;
17
17
  const { focusedIndex, enableVirtualization } = (0, utils_1.useSafeContext)(helpers_1.ComboBoxStateContext);
18
18
  const focusRef = (el) => {
19
19
  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,17 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.ComboBoxMultipleContainer = void 0;
7
+ /*---------------------------------------------------------------------------------------------
8
+ * Copyright (c) Bentley Systems, Incorporated. All rights reserved.
9
+ * See LICENSE.md in the project root for license terms and full copyright notice.
10
+ *--------------------------------------------------------------------------------------------*/
11
+ const react_1 = __importDefault(require("react"));
12
+ const SelectTagContainer_1 = __importDefault(require("../Select/SelectTagContainer"));
13
+ exports.ComboBoxMultipleContainer = react_1.default.forwardRef((props, ref) => {
14
+ const { selectedItems = [], ...rest } = props;
15
+ return react_1.default.createElement(SelectTagContainer_1.default, { ref: ref, tags: selectedItems, ...rest });
16
+ });
17
+ exports.ComboBoxMultipleContainer.displayName = 'ComboBoxMultipleContainer';