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