@itwin/itwinui-react 1.37.3 → 1.38.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 (76) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/cjs/core/Breadcrumbs/Breadcrumbs.js +3 -5
  3. package/cjs/core/ColorPicker/ColorSwatch.d.ts +1 -1
  4. package/cjs/core/ComboBox/ComboBox.d.ts +4 -2
  5. package/cjs/core/ComboBox/ComboBox.js +129 -246
  6. package/cjs/core/ComboBox/ComboBoxDropdown.d.ts +8 -0
  7. package/cjs/core/ComboBox/ComboBoxDropdown.js +55 -0
  8. package/cjs/core/ComboBox/ComboBoxEndIcon.d.ts +5 -0
  9. package/cjs/core/ComboBox/ComboBoxEndIcon.js +54 -0
  10. package/cjs/core/ComboBox/ComboBoxInput.d.ts +5 -0
  11. package/cjs/core/ComboBox/ComboBoxInput.js +134 -0
  12. package/cjs/core/ComboBox/ComboBoxInputContainer.d.ts +8 -0
  13. package/cjs/core/ComboBox/ComboBoxInputContainer.js +45 -0
  14. package/cjs/core/ComboBox/ComboBoxMenu.d.ts +3 -0
  15. package/cjs/core/ComboBox/ComboBoxMenu.js +45 -0
  16. package/cjs/core/ComboBox/ComboBoxMenuItem.d.ts +21 -0
  17. package/cjs/core/ComboBox/ComboBoxMenuItem.js +65 -0
  18. package/cjs/core/ComboBox/helpers.d.ts +27 -0
  19. package/cjs/core/ComboBox/helpers.js +50 -0
  20. package/cjs/core/Modal/Modal.d.ts +1 -1
  21. package/cjs/core/Modal/Modal.js +6 -6
  22. package/cjs/core/Modal/ModalButtonBar.d.ts +1 -1
  23. package/cjs/core/Modal/ModalButtonBar.js +2 -2
  24. package/cjs/core/Modal/ModalContent.d.ts +1 -1
  25. package/cjs/core/Modal/ModalContent.js +2 -2
  26. package/cjs/core/RadioTiles/RadioTile.d.ts +1 -1
  27. package/cjs/core/RadioTiles/RadioTile.js +7 -9
  28. package/cjs/core/Table/Table.js +2 -2
  29. package/cjs/core/Table/filters/FilterToggle.js +3 -2
  30. package/cjs/core/Toast/ToastWrapper.d.ts +7 -5
  31. package/cjs/core/Toast/ToastWrapper.js +8 -4
  32. package/cjs/core/Toast/Toaster.d.ts +3 -0
  33. package/cjs/core/Toast/Toaster.js +66 -5
  34. package/cjs/core/utils/components/Popover.d.ts +1 -1
  35. package/cjs/core/utils/hooks/index.d.ts +1 -0
  36. package/cjs/core/utils/hooks/index.js +1 -0
  37. package/cjs/core/utils/hooks/useSafeContext.d.ts +6 -0
  38. package/cjs/core/utils/hooks/useSafeContext.js +23 -0
  39. package/esm/core/Breadcrumbs/Breadcrumbs.js +3 -5
  40. package/esm/core/ColorPicker/ColorSwatch.d.ts +1 -1
  41. package/esm/core/ComboBox/ComboBox.d.ts +4 -2
  42. package/esm/core/ComboBox/ComboBox.js +131 -248
  43. package/esm/core/ComboBox/ComboBoxDropdown.d.ts +8 -0
  44. package/esm/core/ComboBox/ComboBoxDropdown.js +49 -0
  45. package/esm/core/ComboBox/ComboBoxEndIcon.d.ts +5 -0
  46. package/esm/core/ComboBox/ComboBoxEndIcon.js +48 -0
  47. package/esm/core/ComboBox/ComboBoxInput.d.ts +5 -0
  48. package/esm/core/ComboBox/ComboBoxInput.js +128 -0
  49. package/esm/core/ComboBox/ComboBoxInputContainer.d.ts +8 -0
  50. package/esm/core/ComboBox/ComboBoxInputContainer.js +38 -0
  51. package/esm/core/ComboBox/ComboBoxMenu.d.ts +3 -0
  52. package/esm/core/ComboBox/ComboBoxMenu.js +39 -0
  53. package/esm/core/ComboBox/ComboBoxMenuItem.d.ts +21 -0
  54. package/esm/core/ComboBox/ComboBoxMenuItem.js +59 -0
  55. package/esm/core/ComboBox/helpers.d.ts +27 -0
  56. package/esm/core/ComboBox/helpers.js +43 -0
  57. package/esm/core/Modal/Modal.d.ts +1 -1
  58. package/esm/core/Modal/Modal.js +6 -6
  59. package/esm/core/Modal/ModalButtonBar.d.ts +1 -1
  60. package/esm/core/Modal/ModalButtonBar.js +2 -2
  61. package/esm/core/Modal/ModalContent.d.ts +1 -1
  62. package/esm/core/Modal/ModalContent.js +2 -2
  63. package/esm/core/RadioTiles/RadioTile.d.ts +1 -1
  64. package/esm/core/RadioTiles/RadioTile.js +7 -9
  65. package/esm/core/Table/Table.js +2 -2
  66. package/esm/core/Table/filters/FilterToggle.js +3 -2
  67. package/esm/core/Toast/ToastWrapper.d.ts +7 -5
  68. package/esm/core/Toast/ToastWrapper.js +8 -3
  69. package/esm/core/Toast/Toaster.d.ts +3 -0
  70. package/esm/core/Toast/Toaster.js +66 -5
  71. package/esm/core/utils/components/Popover.d.ts +1 -1
  72. package/esm/core/utils/hooks/index.d.ts +1 -0
  73. package/esm/core/utils/hooks/index.js +1 -0
  74. package/esm/core/utils/hooks/useSafeContext.d.ts +6 -0
  75. package/esm/core/utils/hooks/useSafeContext.js +16 -0
  76. package/package.json +5 -33
@@ -26,13 +26,22 @@ var __rest = (this && this.__rest) || function (s, e) {
26
26
  *--------------------------------------------------------------------------------------------*/
27
27
  import React from 'react';
28
28
  import cx from 'classnames';
29
- import { Input } from '../Input';
30
- import { Menu, MenuExtraContent, MenuItem } from '../Menu';
29
+ import { MenuExtraContent } from '../Menu';
31
30
  import { Text } from '../Typography';
32
- import { InputContainer, useTheme, Popover, getFocusableElements, getRandomValue, mergeRefs, } from '../utils';
33
- import SvgCaretDownSmall from '@itwin/itwinui-icons-react/cjs/icons/CaretDownSmall';
31
+ import { useTheme, getRandomValue, mergeRefs, } from '../utils';
34
32
  import 'tippy.js/animations/shift-away.css';
35
- import { StatusMessage } from '../StatusMessage';
33
+ import { ComboBoxActionContext, comboBoxReducer, ComboBoxRefsContext, ComboBoxStateContext, } from './helpers';
34
+ import { ComboBoxDropdown } from './ComboBoxDropdown';
35
+ import { ComboBoxEndIcon } from './ComboBoxEndIcon';
36
+ import { ComboBoxInput } from './ComboBoxInput';
37
+ import { ComboBoxInputContainer } from './ComboBoxInputContainer';
38
+ import { ComboBoxMenu } from './ComboBoxMenu';
39
+ import { ComboBoxMenuItem } from './ComboBoxMenuItem';
40
+ /** Returns either `option.id` or derives a stable id using `idPrefix` and `option.label` (without whitespace) */
41
+ var getOptionId = function (option, idPrefix) {
42
+ var _a;
43
+ return (_a = option.id) !== null && _a !== void 0 ? _a : "".concat(idPrefix, "-option-").concat(option.label.replace(/\s/g, '-'));
44
+ };
36
45
  /**
37
46
  * ComboBox component that allows typing a value to filter the options in dropdown list.
38
47
  * Values can be selected either using mouse clicks or using the Enter key.
@@ -47,272 +56,146 @@ import { StatusMessage } from '../StatusMessage';
47
56
  * />
48
57
  */
49
58
  export var ComboBox = function (props) {
50
- var options = props.options, value = props.value, onChange = props.onChange, filterFunction = props.filterFunction, className = props.className, inputProps = props.inputProps, dropdownMenuProps = props.dropdownMenuProps, message = props.message, status = props.status, _a = props.emptyStateMessage, emptyStateMessage = _a === void 0 ? 'No options found' : _a, itemRenderer = props.itemRenderer, rest = __rest(props, ["options", "value", "onChange", "filterFunction", "className", "inputProps", "dropdownMenuProps", "message", "status", "emptyStateMessage", "itemRenderer"]);
59
+ var _a;
60
+ var options = props.options, valueProp = props.value, onChange = props.onChange, filterFunction = props.filterFunction, inputProps = props.inputProps, dropdownMenuProps = props.dropdownMenuProps, _b = props.emptyStateMessage, emptyStateMessage = _b === void 0 ? 'No options found' : _b, itemRenderer = props.itemRenderer, rest = __rest(props, ["options", "value", "onChange", "filterFunction", "inputProps", "dropdownMenuProps", "emptyStateMessage", "itemRenderer"]);
51
61
  // Generate a stateful random id if not specified
52
62
  var id = React.useState(function () {
53
63
  var _a, _b;
54
64
  return (_b = (_a = props.id) !== null && _a !== void 0 ? _a : ((inputProps === null || inputProps === void 0 ? void 0 : inputProps.id) && "".concat(inputProps.id, "-cb"))) !== null && _b !== void 0 ? _b : "iui-cb-".concat(getRandomValue(10));
55
65
  })[0];
56
66
  useTheme();
57
- /** Generates a memoized id for an option, given the index from original list */
58
- var getOptionId = React.useCallback(function (index) {
59
- var _a;
60
- return (_a = options[index].id) !== null && _a !== void 0 ? _a : "".concat(id, "-option").concat(options.findIndex(function (_a) {
61
- var value = _a.value;
62
- return value === options[index].value;
63
- }));
64
- }, [options, id]);
65
- var userOnChange = React.useRef(onChange);
67
+ // Refs get set in subcomponents
68
+ var inputRef = React.useRef(null);
69
+ var menuRef = React.useRef(null);
70
+ var toggleButtonRef = React.useRef(null);
71
+ // Latest value of the onChange prop
72
+ var onChangeProp = React.useRef(onChange);
66
73
  React.useEffect(function () {
67
- userOnChange.current = onChange;
74
+ onChangeProp.current = onChange;
68
75
  }, [onChange]);
69
- var memoizedItems = React.useMemo(function () {
70
- return options.map(function (option, index) {
71
- var label = option.label, value = option.value, rest = __rest(option, ["label", "value"]);
72
- var additionalProps = {
73
- value: value,
74
- role: 'option',
75
- onClick: function () {
76
- var _a;
77
- setSelectedValue(value);
78
- (_a = userOnChange.current) === null || _a === void 0 ? void 0 : _a.call(userOnChange, value);
79
- setIsOpen(false);
80
- },
76
+ // Record to store all extra information (e.g. original indexes), where the key is the id of the option
77
+ var optionsExtraInfoRef = React.useRef({});
78
+ // Clear the extra info when the options change so that it can be reinitialized below
79
+ React.useEffect(function () {
80
+ optionsExtraInfoRef.current = {};
81
+ }, [options]);
82
+ // Initialize the extra info only if it is not already initialized
83
+ if (options.length > 0 &&
84
+ Object.keys(optionsExtraInfoRef.current).length === 0) {
85
+ options.forEach(function (option, index) {
86
+ optionsExtraInfoRef.current[getOptionId(option, id)] = {
87
+ __originalIndex: index,
81
88
  };
82
- if (itemRenderer) {
83
- return React.cloneElement(itemRenderer(option, {
84
- id: getOptionId(index),
85
- index: index,
86
- isSelected: false,
87
- isFocused: false,
88
- }), additionalProps);
89
- }
90
- return (React.createElement(MenuItem, __assign({ id: getOptionId(index), key: getOptionId(index) }, additionalProps, rest), label));
91
89
  });
92
- }, [options, getOptionId, itemRenderer]);
93
- var inputRef = React.useRef(null);
94
- var menuRef = React.useRef(null);
95
- var toggleButtonRef = React.useRef(null);
96
- var _b = React.useState(false), isOpen = _b[0], setIsOpen = _b[1];
90
+ }
91
+ // Reducer where all the component-wide state is stored
92
+ var _c = React.useReducer(comboBoxReducer, {
93
+ isOpen: false,
94
+ selectedIndex: -1,
95
+ focusedIndex: -1,
96
+ }), _d = _c[0], isOpen = _d.isOpen, selectedIndex = _d.selectedIndex, focusedIndex = _d.focusedIndex, dispatch = _c[1];
97
+ React.useEffect(function () {
98
+ var _a, _b;
99
+ // When the dropdown opens
100
+ if (isOpen) {
101
+ (_a = inputRef.current) === null || _a === void 0 ? void 0 : _a.focus(); // Focus the input
102
+ setFilteredOptions(options); // Reset the filtered list
103
+ }
104
+ // When the dropdown closes
105
+ else {
106
+ // Reset the focused index
107
+ dispatch(['focus']);
108
+ // Reset the input value
109
+ setInputValue(selectedIndex != undefined && selectedIndex >= 0
110
+ ? (_b = options[selectedIndex]) === null || _b === void 0 ? void 0 : _b.label
111
+ : '');
112
+ }
113
+ }, [isOpen, options, selectedIndex]);
97
114
  // Set min-width of menu to be same as input
98
- var _c = React.useState(0), minWidth = _c[0], setMinWidth = _c[1];
115
+ var _e = React.useState(0), minWidth = _e[0], setMinWidth = _e[1];
99
116
  React.useEffect(function () {
100
117
  if (inputRef.current) {
101
118
  setMinWidth(inputRef.current.offsetWidth);
102
119
  }
103
120
  }, [isOpen]);
104
- var _d = React.useState(options), filteredOptions = _d[0], setFilteredOptions = _d[1];
121
+ // Initialize filtered options to the latest value options
122
+ var _f = React.useState(options), filteredOptions = _f[0], setFilteredOptions = _f[1];
105
123
  React.useEffect(function () {
106
124
  setFilteredOptions(options);
107
125
  }, [options]);
108
- var _e = React.useState(function () {
109
- return options.findIndex(function (option) { return value === option.value; });
110
- }), focusedIndex = _e[0], setFocusedIndex = _e[1];
111
- // Maintain internal selected value state synced with `value` prop
112
- var _f = React.useState(value), selectedValue = _f[0], setSelectedValue = _f[1];
126
+ // Filter options based on input value
127
+ var _g = React.useState((_a = inputProps === null || inputProps === void 0 ? void 0 : inputProps.value) !== null && _a !== void 0 ? _a : ''), inputValue = _g[0], setInputValue = _g[1];
128
+ var handleOnInput = React.useCallback(function (event) {
129
+ var _a, _b;
130
+ var value = event.currentTarget.value;
131
+ setInputValue(value);
132
+ dispatch(['open']); // reopen when typing
133
+ setFilteredOptions((_a = filterFunction === null || filterFunction === void 0 ? void 0 : filterFunction(options, value)) !== null && _a !== void 0 ? _a : options.filter(function (option) {
134
+ return option.label.toLowerCase().includes(value.toLowerCase());
135
+ }));
136
+ (_b = inputProps === null || inputProps === void 0 ? void 0 : inputProps.onChange) === null || _b === void 0 ? void 0 : _b.call(inputProps, event);
137
+ }, [filterFunction, inputProps, options]);
138
+ // Reset focused item when filteredOptions change
113
139
  React.useEffect(function () {
114
- setSelectedValue(value);
115
- }, [value]);
116
- // Controlled input value
117
- var _g = React.useState(''), inputValue = _g[0], setInputValue = _g[1];
118
- var onInput = React.useCallback(function (event) {
119
- var _a;
120
- setInputValue(event.target.value);
121
- (_a = inputProps === null || inputProps === void 0 ? void 0 : inputProps.onChange) === null || _a === void 0 ? void 0 : _a.call(inputProps, event);
122
- }, [inputProps]);
123
- // update inputValue and focusedIndex every time selected value changes
140
+ dispatch(['focus']);
141
+ }, [filteredOptions]);
142
+ // When the value prop changes, update the selectedIndex
124
143
  React.useEffect(function () {
125
- var _a;
126
- var selectedOption = options.find(function (_a) {
127
- var value = _a.value;
128
- return value === selectedValue;
129
- });
130
- setInputValue((_a = selectedOption === null || selectedOption === void 0 ? void 0 : selectedOption.label) !== null && _a !== void 0 ? _a : '');
131
- setFocusedIndex(selectedOption ? options.indexOf(selectedOption) : -1);
132
- }, [selectedValue, options]);
133
- // Filter options and update focus when input value changes
144
+ dispatch([
145
+ 'select',
146
+ options.findIndex(function (option) { return option.value === valueProp; }),
147
+ ]);
148
+ }, [options, valueProp]);
149
+ // Call user-defined onChange when the value actually changes
134
150
  React.useEffect(function () {
135
- var _a;
136
- if (!isOpen) {
137
- return;
138
- }
139
- // if input is empty or same as selected value, show the whole list
140
- var selectedOption = options.find(function (_a) {
141
- var value = _a.value;
142
- return value === selectedValue;
143
- });
144
- if (!inputValue || (selectedOption === null || selectedOption === void 0 ? void 0 : selectedOption.label) === inputValue) {
145
- setFilteredOptions(options);
146
- return;
147
- }
148
- var _filteredOptions = (_a = filterFunction === null || filterFunction === void 0 ? void 0 : filterFunction(options, inputValue)) !== null && _a !== void 0 ? _a : options.filter(function (option) {
149
- return option.label.toLowerCase().includes(inputValue === null || inputValue === void 0 ? void 0 : inputValue.trim().toLowerCase());
150
- });
151
- setFilteredOptions(_filteredOptions);
152
- setFocusedIndex(function (previouslyFocusedIndex) {
153
- if (_filteredOptions.includes(options[previouslyFocusedIndex])) {
154
- return previouslyFocusedIndex;
155
- }
156
- else if (_filteredOptions.find(function (_a) {
157
- var value = _a.value;
158
- return value === selectedValue;
159
- })) {
160
- return options.findIndex(function (_a) {
161
- var value = _a.value;
162
- return value === selectedValue;
163
- });
164
- }
165
- else {
166
- return -1; // reset focus if previously focused or selected value is not in filtered list
151
+ var _a, _b;
152
+ if (selectedIndex != undefined && selectedIndex >= 0) {
153
+ var value = (_a = options[selectedIndex]) === null || _a === void 0 ? void 0 : _a.value;
154
+ if (value === valueProp) {
155
+ return;
167
156
  }
168
- });
169
- }, [inputValue, options, selectedValue, isOpen, filterFunction]);
170
- var onKeyDown = React.useCallback(function (event) {
171
- var _a;
172
- var focusableOptions = getFocusableElements(menuRef.current);
173
- var focusedIndexInFilteredList = focusableOptions.findIndex(function (_a) {
174
- var _b;
175
- var id = _a.id;
176
- return id === ((_b = inputRef.current) === null || _b === void 0 ? void 0 : _b.getAttribute('aria-activedescendant'));
177
- });
178
- switch (event.key) {
179
- case 'ArrowDown':
180
- if (isOpen) {
181
- var nextIndex_1 = Math.min(focusedIndexInFilteredList + 1, focusableOptions.length - 1);
182
- setFocusedIndex(options.findIndex(function (_, index) {
183
- return getOptionId(index) === focusableOptions[nextIndex_1].id;
184
- }));
185
- }
186
- else {
187
- setIsOpen(true); // reopen menu if closed when typing
188
- }
189
- event.preventDefault();
190
- event.stopPropagation();
191
- break;
192
- case 'ArrowUp':
193
- if (isOpen) {
194
- var previousIndex_1 = Math.max(focusedIndexInFilteredList - 1, 0);
195
- setFocusedIndex(options.findIndex(function (_, index) {
196
- return getOptionId(index) === focusableOptions[previousIndex_1].id;
197
- }));
198
- }
199
- event.preventDefault();
200
- event.stopPropagation();
201
- break;
202
- case 'Enter':
203
- if (isOpen) {
204
- setSelectedValue(options[focusedIndex].value);
205
- (_a = userOnChange.current) === null || _a === void 0 ? void 0 : _a.call(userOnChange, options[focusedIndex].value);
206
- }
207
- setIsOpen(function (open) { return !open; });
208
- event.preventDefault();
209
- event.stopPropagation();
210
- break;
211
- case 'Escape':
212
- setIsOpen(false);
213
- event.preventDefault();
214
- event.stopPropagation();
215
- break;
216
- case 'Tab':
217
- setIsOpen(false);
218
- break;
219
- default:
220
- if (!isOpen) {
221
- setIsOpen(true); // reopen menu if closed when typing
222
- }
223
- break;
157
+ (_b = onChangeProp.current) === null || _b === void 0 ? void 0 : _b.call(onChangeProp, value);
224
158
  }
225
- }, [focusedIndex, isOpen, options, getOptionId]);
226
- var menuItems = React.useMemo(function () {
227
- if (filteredOptions.length === 0) {
228
- return [
229
- React.createElement(MenuExtraContent, { key: 0 },
230
- React.createElement(Text, { isMuted: true }, emptyStateMessage)),
231
- ];
232
- }
233
- return filteredOptions.map(function (option) {
234
- var _a;
235
- var index = options.findIndex(function (_a) {
236
- var value = _a.value;
237
- return option.value === value;
238
- });
239
- if (index < 0) {
240
- return React.createElement(React.Fragment, null);
241
- }
242
- var id = getOptionId(index);
243
- var isSelected = selectedValue === option.value;
244
- var isFocused = focusedIndex === index;
245
- var focusScrollRef = function (el) {
246
- return isFocused && (el === null || el === void 0 ? void 0 : el.scrollIntoView({ block: 'nearest' }));
247
- };
248
- if (isSelected || isFocused) {
249
- var item = (_a = itemRenderer === null || itemRenderer === void 0 ? void 0 : itemRenderer(option, { index: index, id: id, isSelected: isSelected, isFocused: isFocused })) !== null && _a !== void 0 ? _a : React.cloneElement(memoizedItems[index], { isSelected: isSelected });
250
- return React.cloneElement(item, {
251
- className: cx({ 'iui-focused': isFocused }, item.props.className),
252
- ref: mergeRefs(focusScrollRef, item.props.ref),
253
- value: option.value,
254
- role: 'option',
255
- onClick: function () {
256
- var _a;
257
- setSelectedValue(option.value);
258
- (_a = userOnChange.current) === null || _a === void 0 ? void 0 : _a.call(userOnChange, option.value);
259
- setIsOpen(false);
260
- },
261
- });
262
- }
263
- return memoizedItems[index];
264
- });
265
- }, [
266
- filteredOptions,
267
- emptyStateMessage,
268
- options,
269
- getOptionId,
270
- selectedValue,
271
- focusedIndex,
272
- itemRenderer,
273
- memoizedItems,
274
- ]);
275
- return (React.createElement(InputContainer, __assign({ className: className, status: status, statusMessage: typeof message === 'string' ? (React.createElement(StatusMessage, { status: status }, message)) : (React.isValidElement(message) &&
276
- React.cloneElement(message, { status: status })) }, rest, { id: id }),
277
- React.createElement("div", { className: 'iui-input-with-icon' },
278
- React.createElement(Popover, __assign({ placement: 'bottom-start', visible: isOpen, onClickOutside: function (_, _a) {
279
- var _b;
280
- var target = _a.target;
281
- if (!((_b = toggleButtonRef.current) === null || _b === void 0 ? void 0 : _b.contains(target))) {
282
- setIsOpen(false);
283
- }
284
- }, animation: 'shift-away', duration: 200 }, dropdownMenuProps, { content: React.createElement(Menu, { id: "".concat(id, "-list"), className: 'iui-scroll', style: {
285
- minWidth: minWidth,
286
- maxWidth: "min(".concat(minWidth * 2, "px, 90vw)"),
287
- maxHeight: 300,
288
- }, setFocus: false, role: 'listbox', ref: menuRef }, menuItems), onHide: function (instance) {
289
- var _a;
290
- var selectedIndex = options.findIndex(function (_a) {
291
- var value = _a.value;
292
- return value === selectedValue;
293
- });
294
- setFocusedIndex(selectedIndex);
295
- if (selectedIndex > -1) {
296
- setInputValue(options[selectedIndex].label); // update input value to be same as selected value
297
- }
298
- (_a = dropdownMenuProps === null || dropdownMenuProps === void 0 ? void 0 : dropdownMenuProps.onHide) === null || _a === void 0 ? void 0 : _a.call(dropdownMenuProps, instance);
299
- } }),
300
- React.createElement(Input, __assign({ ref: inputRef, onKeyDown: onKeyDown, onFocus: function () { return setIsOpen(true); }, value: inputValue, "aria-activedescendant": isOpen && focusedIndex > -1
301
- ? getOptionId(focusedIndex)
302
- : undefined, role: 'combobox', "aria-controls": isOpen ? "".concat(id, "-list") : undefined, "aria-autocomplete": 'list', spellCheck: false, autoCapitalize: 'none', autoCorrect: 'off' }, inputProps, { onChange: onInput }))),
303
- React.createElement("span", { ref: toggleButtonRef, className: cx('iui-end-icon', {
304
- 'iui-actionable': !(inputProps === null || inputProps === void 0 ? void 0 : inputProps.disabled),
305
- 'iui-disabled': inputProps === null || inputProps === void 0 ? void 0 : inputProps.disabled,
306
- 'iui-open': isOpen,
307
- }), onClick: function () {
308
- var _a;
309
- if (isOpen) {
310
- setIsOpen(false);
311
- }
312
- else {
313
- (_a = inputRef.current) === null || _a === void 0 ? void 0 : _a.focus();
314
- }
315
- } },
316
- React.createElement(SvgCaretDownSmall, { "aria-hidden": true })))));
159
+ }, [options, selectedIndex, valueProp]);
160
+ var getMenuItem = React.useCallback(function (option) {
161
+ var optionId = getOptionId(option, id);
162
+ var __originalIndex = optionsExtraInfoRef.current[optionId].__originalIndex;
163
+ var customItem = itemRenderer
164
+ ? itemRenderer(option, {
165
+ isFocused: focusedIndex === __originalIndex,
166
+ isSelected: selectedIndex === __originalIndex,
167
+ index: __originalIndex,
168
+ id: optionId,
169
+ })
170
+ : null;
171
+ return customItem ? (React.cloneElement(customItem, {
172
+ onClick: function (e) {
173
+ var _a, _b;
174
+ dispatch(['select', __originalIndex]);
175
+ (_b = (_a = customItem.props).onClick) === null || _b === void 0 ? void 0 : _b.call(_a, e);
176
+ },
177
+ // ComboBox.MenuItem handles scrollIntoView, data-iui-index and iui-focused through context
178
+ // but we still need to pass them here for backwards compatibility with MenuItem
179
+ className: cx(customItem.props.className, {
180
+ 'iui-focused': focusedIndex === __originalIndex,
181
+ }),
182
+ 'data-iui-index': __originalIndex,
183
+ ref: mergeRefs(customItem.props.ref, function (el) {
184
+ // will need to check for virtualization here too
185
+ if (focusedIndex === __originalIndex) {
186
+ el === null || el === void 0 ? void 0 : el.scrollIntoView({ block: 'nearest' });
187
+ }
188
+ }),
189
+ })) : (React.createElement(ComboBoxMenuItem, __assign({ key: optionId, id: optionId }, option, { isSelected: selectedIndex === __originalIndex, onClick: function () { return dispatch(['select', __originalIndex]); }, index: __originalIndex }), option.label));
190
+ }, [focusedIndex, id, itemRenderer, selectedIndex]);
191
+ return (React.createElement(ComboBoxRefsContext.Provider, { value: { inputRef: inputRef, menuRef: menuRef, toggleButtonRef: toggleButtonRef, optionsExtraInfoRef: optionsExtraInfoRef } },
192
+ React.createElement(ComboBoxActionContext.Provider, { value: dispatch },
193
+ React.createElement(ComboBoxStateContext.Provider, { value: { id: id, minWidth: minWidth, isOpen: isOpen, focusedIndex: focusedIndex } },
194
+ React.createElement(ComboBoxInputContainer, __assign({ disabled: inputProps === null || inputProps === void 0 ? void 0 : inputProps.disabled }, rest),
195
+ React.createElement(ComboBoxInput, __assign({ value: inputValue }, inputProps, { onChange: handleOnInput })),
196
+ React.createElement(ComboBoxEndIcon, { disabled: inputProps === null || inputProps === void 0 ? void 0 : inputProps.disabled, isOpen: isOpen })),
197
+ React.createElement(ComboBoxDropdown, __assign({}, dropdownMenuProps),
198
+ React.createElement(ComboBoxMenu, null, filteredOptions.length > 0 ? (filteredOptions.map(getMenuItem)) : (React.createElement(MenuExtraContent, null,
199
+ React.createElement(Text, { isMuted: true }, emptyStateMessage)))))))));
317
200
  };
318
201
  export default ComboBox;
@@ -0,0 +1,8 @@
1
+ import React from 'react';
2
+ export declare const ComboBoxDropdown: React.ForwardRefExoticComponent<Pick<{
3
+ visible?: boolean | undefined;
4
+ trigger?: string | undefined;
5
+ placement?: import("@popperjs/core").Placement | undefined;
6
+ } & Omit<import("@tippyjs/react").TippyProps, "placement" | "trigger" | "visible"> & {
7
+ children: JSX.Element;
8
+ }, "disabled" | "children" | "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" | "role" | "theme" | "zIndex" | "className" | "singleton" | "reference"> & React.RefAttributes<Element>>;
@@ -0,0 +1,49 @@
1
+ var __assign = (this && this.__assign) || function () {
2
+ __assign = Object.assign || function(t) {
3
+ for (var s, i = 1, n = arguments.length; i < n; i++) {
4
+ s = arguments[i];
5
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
6
+ t[p] = s[p];
7
+ }
8
+ return t;
9
+ };
10
+ return __assign.apply(this, arguments);
11
+ };
12
+ var __rest = (this && this.__rest) || function (s, e) {
13
+ var t = {};
14
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
15
+ t[p] = s[p];
16
+ if (s != null && typeof Object.getOwnPropertySymbols === "function")
17
+ for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
18
+ if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
19
+ t[p[i]] = s[p[i]];
20
+ }
21
+ return t;
22
+ };
23
+ /*---------------------------------------------------------------------------------------------
24
+ * Copyright (c) Bentley Systems, Incorporated. All rights reserved.
25
+ * See LICENSE.md in the project root for license terms and full copyright notice.
26
+ *--------------------------------------------------------------------------------------------*/
27
+ import React from 'react';
28
+ import { Popover, useSafeContext } from '../utils';
29
+ import { ComboBoxStateContext, ComboBoxActionContext, ComboBoxRefsContext, } from './helpers';
30
+ export var ComboBoxDropdown = React.forwardRef(function (props, forwardedRef) {
31
+ var children = props.children, rest = __rest(props, ["children"]);
32
+ var isOpen = useSafeContext(ComboBoxStateContext).isOpen;
33
+ var dispatch = useSafeContext(ComboBoxActionContext);
34
+ var _a = useSafeContext(ComboBoxRefsContext), inputRef = _a.inputRef, toggleButtonRef = _a.toggleButtonRef;
35
+ // sync internal isOpen state with user's visible prop
36
+ React.useEffect(function () {
37
+ if (props.visible != undefined) {
38
+ dispatch([props.visible ? 'open' : 'close']);
39
+ }
40
+ }, [dispatch, props.visible]);
41
+ return (React.createElement(Popover, __assign({ placement: 'bottom-start', visible: isOpen, onClickOutside: React.useCallback(function (_, _a) {
42
+ var _b;
43
+ var target = _a.target;
44
+ if (!((_b = toggleButtonRef.current) === null || _b === void 0 ? void 0 : _b.contains(target))) {
45
+ dispatch(['close']);
46
+ }
47
+ }, [dispatch, toggleButtonRef]), animation: 'shift-away', duration: 200, reference: inputRef, ref: forwardedRef, content: children }, rest)));
48
+ });
49
+ ComboBoxDropdown.displayName = 'ComboBoxDropdown';
@@ -0,0 +1,5 @@
1
+ import React from 'react';
2
+ export declare const ComboBoxEndIcon: React.ForwardRefExoticComponent<Pick<React.DetailedHTMLProps<React.HTMLAttributes<HTMLSpanElement>, HTMLSpanElement>, "key" | keyof React.HTMLAttributes<HTMLSpanElement>> & {
3
+ disabled?: boolean | undefined;
4
+ isOpen?: boolean | undefined;
5
+ } & React.RefAttributes<HTMLSpanElement>>;
@@ -0,0 +1,48 @@
1
+ var __assign = (this && this.__assign) || function () {
2
+ __assign = Object.assign || function(t) {
3
+ for (var s, i = 1, n = arguments.length; i < n; i++) {
4
+ s = arguments[i];
5
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
6
+ t[p] = s[p];
7
+ }
8
+ return t;
9
+ };
10
+ return __assign.apply(this, arguments);
11
+ };
12
+ var __rest = (this && this.__rest) || function (s, e) {
13
+ var t = {};
14
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
15
+ t[p] = s[p];
16
+ if (s != null && typeof Object.getOwnPropertySymbols === "function")
17
+ for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
18
+ if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
19
+ t[p[i]] = s[p[i]];
20
+ }
21
+ return t;
22
+ };
23
+ /*---------------------------------------------------------------------------------------------
24
+ * Copyright (c) Bentley Systems, Incorporated. All rights reserved.
25
+ * See LICENSE.md in the project root for license terms and full copyright notice.
26
+ *--------------------------------------------------------------------------------------------*/
27
+ import cx from 'classnames';
28
+ import React from 'react';
29
+ import { useSafeContext, useMergedRefs } from '../utils';
30
+ import { ComboBoxActionContext, ComboBoxRefsContext } from './helpers';
31
+ import SvgCaretDownSmall from '@itwin/itwinui-icons-react/cjs/icons/CaretDownSmall';
32
+ export var ComboBoxEndIcon = React.forwardRef(function (props, forwardedRef) {
33
+ var className = props.className, children = props.children, onClickProp = props.onClick, disabled = props.disabled, isOpen = props.isOpen, rest = __rest(props, ["className", "children", "onClick", "disabled", "isOpen"]);
34
+ var dispatch = useSafeContext(ComboBoxActionContext);
35
+ var toggleButtonRef = useSafeContext(ComboBoxRefsContext).toggleButtonRef;
36
+ var refs = useMergedRefs(toggleButtonRef, forwardedRef);
37
+ return (React.createElement("span", __assign({ ref: refs, className: cx('iui-end-icon', {
38
+ 'iui-actionable': !disabled,
39
+ 'iui-disabled': disabled,
40
+ 'iui-open': isOpen,
41
+ }, className), onClick: function (e) {
42
+ onClickProp === null || onClickProp === void 0 ? void 0 : onClickProp(e);
43
+ if (!e.isDefaultPrevented()) {
44
+ dispatch([isOpen ? 'close' : 'open']);
45
+ }
46
+ } }, rest), children !== null && children !== void 0 ? children : React.createElement(SvgCaretDownSmall, { "aria-hidden": true })));
47
+ });
48
+ ComboBoxEndIcon.displayName = 'ComboBoxEndIcon';
@@ -0,0 +1,5 @@
1
+ import React from 'react';
2
+ export declare const ComboBoxInput: React.ForwardRefExoticComponent<{
3
+ setFocus?: boolean | undefined;
4
+ size?: "small" | "large" | undefined;
5
+ } & Omit<React.InputHTMLAttributes<HTMLInputElement>, "size"> & React.RefAttributes<HTMLInputElement>>;