@itwin/itwinui-react 3.4.2 → 3.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) hide show
  1. package/CHANGELOG.md +46 -0
  2. package/cjs/core/Carousel/Carousel.d.ts +4 -4
  3. package/cjs/core/Carousel/CarouselNavigation.d.ts +4 -4
  4. package/cjs/core/Dialog/Dialog.js +19 -17
  5. package/cjs/core/Dialog/DialogContext.d.ts +4 -0
  6. package/cjs/core/Dialog/DialogMain.js +1 -1
  7. package/cjs/core/Dialog/DialogTitleBar.js +1 -1
  8. package/cjs/core/InputGroup/InputGroup.d.ts +4 -0
  9. package/cjs/core/InputGroup/InputGroup.js +20 -4
  10. package/cjs/core/InputWithDecorations/InputWithDecorations.d.ts +18 -62
  11. package/cjs/core/InputWithDecorations/InputWithDecorations.js +21 -6
  12. package/cjs/core/LabeledSelect/LabeledSelect.d.ts +376 -4
  13. package/cjs/core/LabeledSelect/LabeledSelect.js +1 -1
  14. package/cjs/core/Popover/Popover.d.ts +6 -3
  15. package/cjs/core/ProgressIndicators/ProgressLinear.js +6 -1
  16. package/cjs/core/ProgressIndicators/ProgressRadial.js +8 -3
  17. package/cjs/core/SearchBox/SearchBox.js +4 -6
  18. package/cjs/core/Select/Select.d.ts +197 -102
  19. package/cjs/core/Select/Select.js +72 -19
  20. package/cjs/core/Table/Table.js +2 -1
  21. package/cjs/core/Tabs/Tabs.js +19 -3
  22. package/cjs/core/Tile/Tile.d.ts +2 -2
  23. package/cjs/core/ToggleSwitch/ToggleSwitch.d.ts +4 -2
  24. package/cjs/core/ToggleSwitch/ToggleSwitch.js +5 -11
  25. package/cjs/core/Typography/Anchor.d.ts +21 -1
  26. package/cjs/core/Typography/Anchor.js +38 -3
  27. package/cjs/core/VisuallyHidden/VisuallyHidden.js +19 -3
  28. package/cjs/core/utils/components/InputFlexContainer.d.ts +25 -0
  29. package/cjs/core/utils/components/InputFlexContainer.js +19 -1
  30. package/cjs/core/utils/components/LineClamp.d.ts +5 -0
  31. package/cjs/core/utils/components/LineClamp.js +49 -0
  32. package/cjs/core/utils/components/ShadowRoot.d.ts +2 -1
  33. package/cjs/core/utils/components/ShadowRoot.js +19 -4
  34. package/cjs/core/utils/components/index.d.ts +1 -0
  35. package/cjs/core/utils/components/index.js +1 -0
  36. package/cjs/core/utils/functions/polymorphic.d.ts +5 -3
  37. package/cjs/core/utils/functions/polymorphic.js +20 -5
  38. package/cjs/core/utils/icons/Svg.js +5 -1
  39. package/cjs/core/utils/icons/SvgStatusError.js +1 -1
  40. package/esm/core/Carousel/Carousel.d.ts +4 -4
  41. package/esm/core/Carousel/CarouselNavigation.d.ts +4 -4
  42. package/esm/core/Dialog/Dialog.js +19 -17
  43. package/esm/core/Dialog/DialogContext.d.ts +4 -0
  44. package/esm/core/Dialog/DialogMain.js +1 -1
  45. package/esm/core/Dialog/DialogTitleBar.js +1 -1
  46. package/esm/core/InputGroup/InputGroup.d.ts +4 -0
  47. package/esm/core/InputGroup/InputGroup.js +20 -4
  48. package/esm/core/InputWithDecorations/InputWithDecorations.d.ts +18 -62
  49. package/esm/core/InputWithDecorations/InputWithDecorations.js +22 -7
  50. package/esm/core/LabeledSelect/LabeledSelect.d.ts +376 -4
  51. package/esm/core/LabeledSelect/LabeledSelect.js +1 -1
  52. package/esm/core/Popover/Popover.d.ts +6 -3
  53. package/esm/core/ProgressIndicators/ProgressLinear.js +7 -2
  54. package/esm/core/ProgressIndicators/ProgressRadial.js +9 -4
  55. package/esm/core/SearchBox/SearchBox.js +5 -7
  56. package/esm/core/Select/Select.d.ts +197 -102
  57. package/esm/core/Select/Select.js +73 -20
  58. package/esm/core/Table/Table.js +2 -1
  59. package/esm/core/Tabs/Tabs.js +19 -3
  60. package/esm/core/Tile/Tile.d.ts +2 -2
  61. package/esm/core/ToggleSwitch/ToggleSwitch.d.ts +4 -2
  62. package/esm/core/ToggleSwitch/ToggleSwitch.js +6 -12
  63. package/esm/core/Typography/Anchor.d.ts +21 -1
  64. package/esm/core/Typography/Anchor.js +11 -2
  65. package/esm/core/VisuallyHidden/VisuallyHidden.js +19 -3
  66. package/esm/core/utils/components/InputFlexContainer.d.ts +25 -0
  67. package/esm/core/utils/components/InputFlexContainer.js +18 -0
  68. package/esm/core/utils/components/LineClamp.d.ts +5 -0
  69. package/esm/core/utils/components/LineClamp.js +23 -0
  70. package/esm/core/utils/components/ShadowRoot.d.ts +2 -1
  71. package/esm/core/utils/components/ShadowRoot.js +19 -4
  72. package/esm/core/utils/components/index.d.ts +1 -0
  73. package/esm/core/utils/components/index.js +1 -0
  74. package/esm/core/utils/functions/polymorphic.d.ts +5 -3
  75. package/esm/core/utils/functions/polymorphic.js +20 -5
  76. package/esm/core/utils/icons/Svg.js +5 -1
  77. package/esm/core/utils/icons/SvgStatusError.js +1 -1
  78. package/package.json +27 -27
  79. package/styles.css +39 -38
@@ -4,9 +4,7 @@
4
4
  *--------------------------------------------------------------------------------------------*/
5
5
  import * as React from 'react';
6
6
  import cx from 'classnames';
7
- import { InputFlexContainer, SvgSearch, SvgCloseSmall, useSafeContext, useId, useMergedRefs, mergeEventHandlers, Box, } from '../utils/index.js';
8
- import { IconButton } from '../Buttons/IconButton.js';
9
- import { Icon } from '../Icon/Icon.js';
7
+ import { InputFlexContainer, SvgSearch, SvgCloseSmall, useSafeContext, useId, useMergedRefs, mergeEventHandlers, Box, InputFlexContainerIcon, InputFlexContainerButton, } from '../utils/index.js';
10
8
  const SearchBoxContext = React.createContext(undefined);
11
9
  const SearchBoxComponent = React.forwardRef((props, ref) => {
12
10
  const { size, expandable = false, isDisabled = false, onCollapse: onCollapseProp, onExpand: onExpandProp, isExpanded: isExpandedProp, children, inputProps, className, ...rest } = props;
@@ -67,7 +65,7 @@ SearchBoxExpandedState.displayName = 'SearchBox.ExpandedState';
67
65
  // ----------------------------------------------------------------------------
68
66
  const SearchBoxIcon = React.forwardRef((props, ref) => {
69
67
  const { className, children, ...rest } = props;
70
- return (React.createElement(Icon, { "aria-hidden": true, className: cx('iui-search-icon', className), ref: ref, ...rest }, children ?? React.createElement(SvgSearch, null)));
68
+ return (React.createElement(InputFlexContainerIcon, { "aria-hidden": true, className: cx('iui-search-icon', className), ref: ref, ...rest }, children ?? React.createElement(SvgSearch, null)));
71
69
  });
72
70
  SearchBoxIcon.displayName = 'SearchBox.Icon';
73
71
  // ----------------------------------------------------------------------------
@@ -86,7 +84,7 @@ SearchBoxInput.displayName = 'SearchBox.Input';
86
84
  const SearchBoxButton = React.forwardRef((props, ref) => {
87
85
  const { children, ...rest } = props;
88
86
  const { size: sizeContext, isDisabled } = useSafeContext(SearchBoxContext);
89
- return (React.createElement(IconButton, { styleType: 'borderless', size: sizeContext, ref: ref, disabled: isDisabled, ...rest }, children ?? React.createElement(SvgSearch, null)));
87
+ return (React.createElement(InputFlexContainerButton, { size: sizeContext, ref: ref, disabled: isDisabled, ...rest }, children ?? React.createElement(SvgSearch, null)));
90
88
  });
91
89
  SearchBoxButton.displayName = 'SearchBox.Button';
92
90
  // ----------------------------------------------------------------------------
@@ -98,9 +96,9 @@ const SearchBoxCollapseButton = React.forwardRef((props, ref) => {
98
96
  SearchBoxCollapseButton.displayName = 'SearchBox.CollapseButton';
99
97
  // ----------------------------------------------------------------------------
100
98
  const SearchBoxExpandButton = React.forwardRef((props, ref) => {
101
- const { children, className, onClick: onClickProp, ...rest } = props;
99
+ const { children, onClick: onClickProp, ...rest } = props;
102
100
  const { onExpand, size: sizeContext, isDisabled, openButtonRef, } = useSafeContext(SearchBoxContext);
103
- return (React.createElement(SearchBoxButton, { ref: useMergedRefs(ref, openButtonRef), className: cx('iui-searchbox-open-button', className), "aria-label": 'Expand searchbox', size: sizeContext, disabled: isDisabled, onClick: mergeEventHandlers(onClickProp, onExpand), ...rest }, children ?? React.createElement(SvgSearch, null)));
101
+ return (React.createElement(SearchBoxButton, { ref: useMergedRefs(ref, openButtonRef), "aria-label": 'Expand searchbox', size: sizeContext, disabled: isDisabled, onClick: mergeEventHandlers(onClickProp, onExpand), styleType: 'default', ...rest }, children ?? React.createElement(SvgSearch, null)));
104
102
  });
105
103
  SearchBoxExpandButton.displayName = 'SearchBox.ExpandButton';
106
104
  // ----------------------------------------------------------------------------
@@ -1,54 +1,193 @@
1
1
  import * as React from 'react';
2
2
  import type { CommonProps } from '../utils/index.js';
3
3
  import { usePopover } from '../Popover/Popover.js';
4
- export type ItemRendererProps = {
4
+ /**
5
+ * Select component to select value from options.
6
+ * Generic type is used for value. It prevents you from mistakenly using other types in `options`, `value` and `onChange`.
7
+ * @example
8
+ * <caption>Basic select.</caption>
9
+ * <Select
10
+ * options={[
11
+ * { value: '1', label: 'Option 1' },
12
+ * { value: '2', label: 'Option 2' },
13
+ * { value: '3', label: 'Option 3' },
14
+ * ]}
15
+ * />
16
+ * @example
17
+ * <caption>Native select.</caption>
18
+ * <Select
19
+ * native
20
+ * options={[
21
+ * { value: '1', label: 'Option 1' },
22
+ * { value: '2', label: 'Option 2' },
23
+ * { value: '3', label: 'Option 3' },
24
+ * ]}
25
+ * />
26
+ * @example
27
+ * <caption>Disabled select with placeholder.</caption>
28
+ * <Select
29
+ * disabled={true}
30
+ * placeholder='Placeholder text'
31
+ * options={[{ value: 1, label: 'Option 1' }, { value: 2, label: 'Option 2' }, { value: 3, label: 'Option 3' }]}
32
+ * />
33
+ * @example
34
+ * <caption>Select with selected value and change handler.</caption>
35
+ * <Select
36
+ * value={selectedValue}
37
+ * onChange={(value) => setSelectedValue(value)}
38
+ * options={[{ value: 1, label: 'Option 1' }, { value: 2, label: 'Option 2' }, { value: 3, label: 'Option 3' }]}
39
+ * />
40
+ * @example
41
+ * <caption>Select using custom renderers for menu items and selected value.</caption>
42
+ * <Select
43
+ * options={[
44
+ * { value: 'yellow', label: 'Yellow' },
45
+ * { value: 'green', label: 'Green' },
46
+ * { value: 'red', label: 'Red' },
47
+ * ]}
48
+ * value={selectedValue}
49
+ * placeholder='Placeholder text'
50
+ * itemRenderer={(option, itemProps) => (
51
+ * <MenuItem
52
+ * style={{ color: option.value }}
53
+ * isSelected={itemProps.isSelected}
54
+ * onClick={() => {
55
+ * setSelectedValue(option.value);
56
+ * itemProps.close();
57
+ * }}
58
+ * role='option'
59
+ * ref={(el) => itemProps.isSelected && el?.scrollIntoView()}
60
+ * >
61
+ * {option.label}
62
+ * </MenuItem>
63
+ * )}
64
+ * selectedItemRenderer={(option) => (
65
+ * <span style={{ backgroundColor: option.value }}>{option.label}</span>
66
+ * )}
67
+ * />
68
+ */
69
+ export declare const Select: <T>(props: SelectProps<T> & {
70
+ ref?: React.ForwardedRef<HTMLElement> | undefined;
71
+ }) => JSX.Element;
72
+ export type SelectProps<T> = Omit<React.ComponentPropsWithoutRef<'div'>, 'onChange' | 'placeholder' | 'value' | 'defaultValue'> & (({
5
73
  /**
6
- * Close handler that closes the dropdown.
74
+ * If true, the native `<select>` element will be rendered.
75
+ *
76
+ * Extra props, such as `name` can be passed to the `<select>` using `triggerProps`.
77
+ *
78
+ * @default false
7
79
  */
8
- close: () => void;
80
+ native: true;
81
+ } & NativeSelectProps) | ({
82
+ native?: false;
83
+ } & CustomSelectProps<T> & {
9
84
  /**
10
- * Indicates whether an item is selected.
85
+ * styleType is only supported for `<Select native>`.
11
86
  */
12
- isSelected: boolean;
13
- };
14
- export type SelectOption<T> = {
87
+ styleType?: never;
88
+ }));
89
+ type NativeSelectProps = SelectCommonProps & {
15
90
  /**
16
- * Label of the item used in dropdown list and when selected.
91
+ * Selected option value.
92
+ *
93
+ * Must be a string, because it is passed as an attribute to the native <select>.
94
+ *
95
+ * Alternatively, pass `null` to reset the value.
17
96
  */
18
- label: string;
97
+ value?: string | null;
19
98
  /**
20
- * Sublabel of the item shown below the label.
99
+ * Callback invoked when the selected value changes.
21
100
  */
22
- sublabel?: React.ReactNode;
101
+ onChange?: (value: string, event: React.ChangeEvent<HTMLSelectElement>) => void;
23
102
  /**
24
- * Modify height of the item.
25
- * Use 'large' when any of the select options have `sublabel`.
103
+ * Array of options that populates the select menu.
26
104
  *
27
- * Defaults to 'large' if `sublabel` provided, otherwise 'default'.
105
+ * The `value` property of each option must be a string.
28
106
  */
29
- size?: 'default' | 'large';
107
+ options: Array<{
108
+ label: string;
109
+ value: string;
110
+ disabled?: boolean;
111
+ }>;
30
112
  /**
31
- * Value of the item.
113
+ * Default value that is selected on initial render. This is useful when you don't want to
114
+ * maintain your own state but still want to control the initial value.
115
+ *
116
+ * If not passed, the first option (or placeholder) will be automatically selected.
32
117
  */
33
- value: T;
118
+ defaultValue?: string;
34
119
  /**
35
- * @deprecated Use startIcon
36
- * SVG icon component shown on the left.
120
+ * Props to pass to the select element.
37
121
  */
38
- icon?: JSX.Element;
122
+ triggerProps?: Omit<React.ComponentPropsWithRef<'select'>, 'size'>;
123
+ required?: boolean;
124
+ multiple?: never;
125
+ } & NativeSelectStyleTypeProps;
126
+ type NativeSelectStyleTypeProps = {
39
127
  /**
40
- * SVG icon component shown on the left.
128
+ * Style of the select.
129
+ * Use 'borderless' to hide outline.
130
+ * @default 'default'
41
131
  */
42
- startIcon?: JSX.Element;
132
+ styleType?: 'default';
43
133
  /**
44
- * Item is disabled.
134
+ * Placeholder for when no item is selected.
135
+ *
136
+ * Will be rendered as a disabled option at the top of the list, and automatically
137
+ * selected when no `value` or `defaultValue` is provided.
138
+ *
139
+ * Not allowed when `styleType` is `borderless`.
140
+ */
141
+ placeholder?: string;
142
+ } | {
143
+ styleType: 'borderless';
144
+ placeholder?: never;
145
+ };
146
+ type SelectCommonProps = {
147
+ /**
148
+ * Disables select.
149
+ * @default false
45
150
  */
46
151
  disabled?: boolean;
47
152
  /**
48
- * Any other props.
153
+ * Modify size of select.
49
154
  */
50
- [key: string]: unknown;
51
- } & CommonProps;
155
+ size?: 'small' | 'large';
156
+ /**
157
+ * Status of select.
158
+ */
159
+ status?: 'positive' | 'warning' | 'negative';
160
+ };
161
+ export type CustomSelectProps<T> = SelectCommonProps & {
162
+ /**
163
+ * Placeholder when no item is selected.
164
+ */
165
+ placeholder?: React.ReactNode;
166
+ /**
167
+ * Array of options that populates the select menu.
168
+ */
169
+ options: SelectOption<T>[];
170
+ /**
171
+ * Custom renderer for an item in the dropdown list. `MenuItem` item props are going to be populated if not provided.
172
+ */
173
+ itemRenderer?: (option: SelectOption<T>, itemProps: ItemRendererProps) => JSX.Element;
174
+ /**
175
+ * Custom class for menu.
176
+ */
177
+ menuClassName?: string;
178
+ /**
179
+ * Custom style for menu.
180
+ */
181
+ menuStyle?: React.CSSProperties;
182
+ /**
183
+ * Props to customize Popover behavior.
184
+ */
185
+ popoverProps?: Pick<Parameters<typeof usePopover>[0], 'visible' | 'onVisibleChange' | 'placement' | 'matchWidth' | 'closeOnOutsideClick'>;
186
+ /**
187
+ * Props to pass to the select button (trigger) element.
188
+ */
189
+ triggerProps?: React.ComponentPropsWithRef<'div'>;
190
+ } & SelectMultipleTypeProps<T> & Omit<React.ComponentPropsWithoutRef<'div'>, 'size' | 'disabled' | 'placeholder' | 'onChange'>;
52
191
  export type SelectValueChangeEvent = 'added' | 'removed';
53
192
  export type SelectMultipleTypeProps<T> = {
54
193
  /**
@@ -64,8 +203,10 @@ export type SelectMultipleTypeProps<T> = {
64
203
  /**
65
204
  * Selected option value.
66
205
  * If `multiple` is enabled, it is an array of values.
206
+ *
207
+ * Pass `null` to reset the value.
67
208
  */
68
- value?: T;
209
+ value?: T | null;
69
210
  /**
70
211
  * Callback function handling change event on select.
71
212
  */
@@ -76,98 +217,52 @@ export type SelectMultipleTypeProps<T> = {
76
217
  value?: T[];
77
218
  onChange?: (value: T, event: SelectValueChangeEvent) => void;
78
219
  };
79
- export type SelectProps<T> = {
220
+ export type ItemRendererProps = {
80
221
  /**
81
- * Array of options that populates the select menu.
222
+ * Close handler that closes the dropdown.
82
223
  */
83
- options: SelectOption<T>[];
224
+ close: () => void;
84
225
  /**
85
- * Placeholder when no item is selected.
226
+ * Indicates whether an item is selected.
86
227
  */
87
- placeholder?: React.ReactNode;
228
+ isSelected: boolean;
229
+ };
230
+ export type SelectOption<T> = {
88
231
  /**
89
- * Disables select.
90
- * @default false
232
+ * Label of the item used in dropdown list and when selected.
91
233
  */
92
- disabled?: boolean;
234
+ label: string;
93
235
  /**
94
- * Modify size of select.
236
+ * Sublabel of the item shown below the label.
95
237
  */
96
- size?: 'small' | 'large';
238
+ sublabel?: React.ReactNode;
97
239
  /**
98
- * Status of select.
240
+ * Modify height of the item.
241
+ * Use 'large' when any of the select options have `sublabel`.
242
+ *
243
+ * Defaults to 'large' if `sublabel` provided, otherwise 'default'.
99
244
  */
100
- status?: 'positive' | 'warning' | 'negative';
245
+ size?: 'default' | 'large';
101
246
  /**
102
- * Custom renderer for an item in the dropdown list. `MenuItem` item props are going to be populated if not provided.
247
+ * Value of the item.
103
248
  */
104
- itemRenderer?: (option: SelectOption<T>, itemProps: ItemRendererProps) => JSX.Element;
249
+ value: T;
105
250
  /**
106
- * Custom class for menu.
251
+ * @deprecated Use startIcon
252
+ * SVG icon component shown on the left.
107
253
  */
108
- menuClassName?: string;
254
+ icon?: JSX.Element;
109
255
  /**
110
- * Custom style for menu.
256
+ * SVG icon component shown on the left.
111
257
  */
112
- menuStyle?: React.CSSProperties;
258
+ startIcon?: JSX.Element;
113
259
  /**
114
- * Props to customize Popover behavior.
260
+ * Item is disabled.
115
261
  */
116
- popoverProps?: Pick<Parameters<typeof usePopover>[0], 'visible' | 'onVisibleChange' | 'placement' | 'matchWidth' | 'closeOnOutsideClick'>;
262
+ disabled?: boolean;
117
263
  /**
118
- * Props to pass to the select button (trigger) element.
264
+ * Any other props.
119
265
  */
120
- triggerProps?: React.ComponentPropsWithRef<'div'>;
121
- } & SelectMultipleTypeProps<T> & Omit<React.ComponentPropsWithoutRef<'div'>, 'size' | 'disabled' | 'placeholder' | 'onChange'>;
122
- /**
123
- * Select component to select value from options.
124
- * Generic type is used for value. It prevents you from mistakenly using other types in `options`, `value` and `onChange`.
125
- * @example
126
- * <caption>Basic select.</caption>
127
- * <Select<number> options={[{ value: 1, label: 'Option 1' }, { value: 2, label: 'Option 2' }, { value: 3, label: 'Option 3' }]} />
128
- * @example
129
- * <caption>Disabled select with placeholder.</caption>
130
- * <Select
131
- * disabled={true}
132
- * placeholder='Placeholder text'
133
- * options={[{ value: 1, label: 'Option 1' }, { value: 2, label: 'Option 2' }, { value: 3, label: 'Option 3' }]}
134
- * />
135
- * @example
136
- * <caption>Select with selected value and change handler.</caption>
137
- * <Select<number>
138
- * value={selectedValue}
139
- * onChange={(value) => setSelectedValue(value)}
140
- * options={[{ value: 1, label: 'Option 1' }, { value: 2, label: 'Option 2' }, { value: 3, label: 'Option 3' }]}
141
- * />
142
- * @example
143
- * <caption>Select using custom renderers for menu items and selected value.</caption>
144
- * <Select<string>
145
- * options={[
146
- * { value: 'yellow', label: 'Yellow' },
147
- * { value: 'green', label: 'Green' },
148
- * { value: 'red', label: 'Red' },
149
- * ]}
150
- * value={selectedValue}
151
- * placeholder='Placeholder text'
152
- * itemRenderer={(option, itemProps) => (
153
- * <MenuItem
154
- * style={{ color: option.value }}
155
- * isSelected={itemProps.isSelected}
156
- * onClick={() => {
157
- * setSelectedValue(option.value);
158
- * itemProps.close();
159
- * }}
160
- * role='option'
161
- * ref={(el) => itemProps.isSelected && el?.scrollIntoView()}
162
- * >
163
- * {option.label}
164
- * </MenuItem>
165
- * )}
166
- * selectedItemRenderer={(option) => (
167
- * <span style={{ backgroundColor: option.value }}>{option.label}</span>
168
- * )}
169
- * />
170
- */
171
- export declare const Select: <T>(props: SelectProps<T> & {
172
- ref?: React.ForwardedRef<HTMLElement> | undefined;
173
- }) => JSX.Element;
266
+ [key: string]: unknown;
267
+ } & CommonProps;
268
+ export {};
@@ -6,24 +6,34 @@ import * as React from 'react';
6
6
  import cx from 'classnames';
7
7
  import { Menu } from '../Menu/Menu.js';
8
8
  import { MenuItem } from '../Menu/MenuItem.js';
9
- import { SvgCaretDownSmall, useId, AutoclearingHiddenLiveRegion, Box, Portal, useMergedRefs, SvgCheckmark, useLatestRef, InputWithIcon, } from '../utils/index.js';
9
+ import { SvgCaretDownSmall, useId, AutoclearingHiddenLiveRegion, Box, Portal, useMergedRefs, SvgCheckmark, useLatestRef, InputWithIcon, mergeEventHandlers, } from '../utils/index.js';
10
10
  import { SelectTag } from './SelectTag.js';
11
11
  import { SelectTagContainer } from './SelectTagContainer.js';
12
12
  import { Icon } from '../Icon/Icon.js';
13
13
  import { usePopover } from '../Popover/Popover.js';
14
- const isMultipleEnabled = (variable, multiple) => {
15
- return multiple;
16
- };
17
- // Type guard for multiple did not work
18
- const isSingleOnChange = (onChange, multiple) => {
19
- return !multiple;
20
- };
14
+ // ----------------------------------------------------------------------------
21
15
  /**
22
16
  * Select component to select value from options.
23
17
  * Generic type is used for value. It prevents you from mistakenly using other types in `options`, `value` and `onChange`.
24
18
  * @example
25
19
  * <caption>Basic select.</caption>
26
- * <Select<number> options={[{ value: 1, label: 'Option 1' }, { value: 2, label: 'Option 2' }, { value: 3, label: 'Option 3' }]} />
20
+ * <Select
21
+ * options={[
22
+ * { value: '1', label: 'Option 1' },
23
+ * { value: '2', label: 'Option 2' },
24
+ * { value: '3', label: 'Option 3' },
25
+ * ]}
26
+ * />
27
+ * @example
28
+ * <caption>Native select.</caption>
29
+ * <Select
30
+ * native
31
+ * options={[
32
+ * { value: '1', label: 'Option 1' },
33
+ * { value: '2', label: 'Option 2' },
34
+ * { value: '3', label: 'Option 3' },
35
+ * ]}
36
+ * />
27
37
  * @example
28
38
  * <caption>Disabled select with placeholder.</caption>
29
39
  * <Select
@@ -33,14 +43,14 @@ const isSingleOnChange = (onChange, multiple) => {
33
43
  * />
34
44
  * @example
35
45
  * <caption>Select with selected value and change handler.</caption>
36
- * <Select<number>
46
+ * <Select
37
47
  * value={selectedValue}
38
48
  * onChange={(value) => setSelectedValue(value)}
39
49
  * options={[{ value: 1, label: 'Option 1' }, { value: 2, label: 'Option 2' }, { value: 3, label: 'Option 3' }]}
40
50
  * />
41
51
  * @example
42
52
  * <caption>Select using custom renderers for menu items and selected value.</caption>
43
- * <Select<string>
53
+ * <Select
44
54
  * options={[
45
55
  * { value: 'yellow', label: 'Yellow' },
46
56
  * { value: 'green', label: 'Green' },
@@ -68,12 +78,38 @@ const isSingleOnChange = (onChange, multiple) => {
68
78
  * />
69
79
  */
70
80
  export const Select = React.forwardRef((props, forwardedRef) => {
81
+ const { native, ...rest } = props;
82
+ const Component = native ? NativeSelect : CustomSelect;
83
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
84
+ // @ts-ignore
85
+ return React.createElement(Component, { ...rest, ref: forwardedRef });
86
+ });
87
+ // ----------------------------------------------------------------------------
88
+ const NativeSelect = React.forwardRef((props, forwardedRef) => {
89
+ const { triggerProps, options, disabled, placeholder, defaultValue: defaultValueProp = placeholder !== undefined ? '' : undefined, value: valueProp, onChange: onChangeProp, size, status, styleType, required, ...rest } = props;
90
+ return (React.createElement(InputWithIcon, { ...rest, ref: forwardedRef },
91
+ React.createElement(SelectButton, { as: 'select', size: size, status: status, styleType: styleType, disabled: disabled, defaultValue: valueProp === undefined ? defaultValueProp : undefined, value: valueProp === null ? '' : valueProp, required: required, ...triggerProps, onKeyDown: mergeEventHandlers(triggerProps?.onKeyDown, (event) => {
92
+ // Firefox does not open the menu on Enter, so we need to do it manually.
93
+ if (event.key === 'Enter') {
94
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
95
+ // @ts-ignore
96
+ event.currentTarget.showPicker?.();
97
+ }
98
+ }), onChange: mergeEventHandlers(triggerProps?.onChange, (event) => {
99
+ onChangeProp?.(event.currentTarget.value, event);
100
+ }) },
101
+ styleType !== 'borderless' && placeholder !== undefined ? (React.createElement("option", { value: '', disabled: true }, placeholder)) : null,
102
+ options.map((option) => (React.createElement("option", { key: option.value, ...option }, option.label)))),
103
+ React.createElement(SelectEndIcon, { disabled: disabled })));
104
+ });
105
+ // ----------------------------------------------------------------------------
106
+ const CustomSelect = React.forwardRef((props, forwardedRef) => {
71
107
  const uid = useId();
72
108
  const { options, value: valueProp, onChange: onChangeProp, placeholder, disabled = false, size, itemRenderer, selectedItemRenderer, menuClassName, menuStyle, multiple = false, triggerProps, status, popoverProps, ...rest } = props;
73
109
  const [isOpen, setIsOpen] = React.useState(false);
74
110
  const [liveRegionSelection, setLiveRegionSelection] = React.useState('');
75
111
  const [uncontrolledValue, setUncontrolledValue] = React.useState();
76
- const value = valueProp ?? uncontrolledValue;
112
+ const value = valueProp !== undefined ? valueProp : uncontrolledValue;
77
113
  const onChangeRef = useLatestRef(onChangeProp);
78
114
  const selectRef = React.useRef(null);
79
115
  const show = React.useCallback(() => {
@@ -161,18 +197,13 @@ export const Select = React.forwardRef((props, forwardedRef) => {
161
197
  });
162
198
  return (React.createElement(React.Fragment, null,
163
199
  React.createElement(InputWithIcon, { ...rest, ref: useMergedRefs(popover.refs.setPositionReference, forwardedRef) },
164
- React.createElement(Box, { ...popover.getReferenceProps(), tabIndex: 0, role: 'combobox', "data-iui-size": size, "data-iui-status": status, "aria-disabled": disabled, "aria-autocomplete": 'none', "aria-expanded": isOpen, "aria-haspopup": 'listbox', "aria-controls": `${uid}-menu`, ...triggerProps, ref: useMergedRefs(selectRef, triggerProps?.ref, popover.refs.setReference), className: cx('iui-select-button', {
165
- 'iui-placeholder': (!selectedItems || selectedItems.length === 0) &&
166
- !!placeholder,
200
+ React.createElement(SelectButton, { ...popover.getReferenceProps(), tabIndex: 0, role: 'combobox', size: size, status: status, "aria-disabled": disabled ? 'true' : undefined, "aria-autocomplete": 'none', "aria-expanded": isOpen, "aria-haspopup": 'listbox', "aria-controls": `${uid}-menu`, ...triggerProps, ref: useMergedRefs(selectRef, triggerProps?.ref, popover.refs.setReference), className: cx({
201
+ 'iui-placeholder': (!selectedItems || selectedItems.length === 0) && !!placeholder,
167
202
  'iui-disabled': disabled,
168
203
  }, triggerProps?.className) },
169
204
  (!selectedItems || selectedItems.length === 0) && (React.createElement(Box, { as: 'span', className: 'iui-content' }, placeholder)),
170
205
  isMultipleEnabled(selectedItems, multiple) ? (React.createElement(MultipleSelectButton, { selectedItems: selectedItems, selectedItemsRenderer: selectedItemRenderer, tagRenderer: tagRenderer })) : (React.createElement(SingleSelectButton, { selectedItem: selectedItems, selectedItemRenderer: selectedItemRenderer }))),
171
- React.createElement(Icon, { as: 'span', "aria-hidden": true, className: cx('iui-end-icon', {
172
- 'iui-disabled': disabled,
173
- 'iui-open': isOpen,
174
- }) },
175
- React.createElement(SvgCaretDownSmall, null)),
206
+ React.createElement(SelectEndIcon, { disabled: disabled, isOpen: isOpen }),
176
207
  multiple ? (React.createElement(AutoclearingHiddenLiveRegion, { text: liveRegionSelection })) : null),
177
208
  popover.open && (React.createElement(Portal, null,
178
209
  React.createElement(Menu, { role: 'listbox', className: menuClassName, id: `${uid}-menu`, key: `${uid}-menu`, ...popover.getFloatingProps({
@@ -184,6 +215,27 @@ export const Select = React.forwardRef((props, forwardedRef) => {
184
215
  },
185
216
  }), ref: popover.refs.setFloating }, menuItems)))));
186
217
  });
218
+ // ----------------------------------------------------------------------------
219
+ // Type guards
220
+ const isMultipleEnabled = (variable, multiple) => {
221
+ return multiple;
222
+ };
223
+ // Type guard for multiple did not work
224
+ const isSingleOnChange = (onChange, multiple) => {
225
+ return !multiple;
226
+ };
227
+ // ----------------------------------------------------------------------------
228
+ const SelectButton = React.forwardRef((props, forwardedRef) => {
229
+ const { size, status, styleType = 'default', ...rest } = props;
230
+ return (React.createElement(Box, { "data-iui-size": size, "data-iui-status": status, "data-iui-variant": styleType !== 'default' ? styleType : undefined, ...rest, ref: forwardedRef, className: cx('iui-select-button', props.className) }));
231
+ });
232
+ // ----------------------------------------------------------------------------
233
+ const SelectEndIcon = React.forwardRef((props, forwardedRef) => {
234
+ const { disabled, isOpen, ...rest } = props;
235
+ return (React.createElement(Icon, { "aria-hidden": true, ...rest, ref: forwardedRef, className: cx('iui-end-icon', { 'iui-disabled': disabled, 'iui-open': isOpen }, props.className) },
236
+ React.createElement(SvgCaretDownSmall, null)));
237
+ });
238
+ // ----------------------------------------------------------------------------
187
239
  const SingleSelectButton = ({ selectedItem, selectedItemRenderer, }) => {
188
240
  const startIcon = selectedItem?.startIcon ?? selectedItem?.icon;
189
241
  return (React.createElement(React.Fragment, null,
@@ -194,6 +246,7 @@ const SingleSelectButton = ({ selectedItem, selectedItemRenderer, }) => {
194
246
  startIcon && (React.createElement(Box, { as: 'span', className: 'iui-icon', "aria-hidden": true }, startIcon)),
195
247
  React.createElement(Box, { as: 'span', className: 'iui-content' }, selectedItem.label)))));
196
248
  };
249
+ // ----------------------------------------------------------------------------
197
250
  const MultipleSelectButton = ({ selectedItems, selectedItemsRenderer, tagRenderer, }) => {
198
251
  const selectedItemsElements = React.useMemo(() => {
199
252
  if (!selectedItems) {
@@ -261,7 +261,8 @@ export const Table = (props) => {
261
261
  // This is to avoid the old columnOrder from affecting the new columns' columnOrder
262
262
  React.useEffect(() => {
263
263
  // Check if columns have changed (by value)
264
- if (JSON.stringify(lastPassedColumns.current) !== JSON.stringify(columns)) {
264
+ if (lastPassedColumns.current.length > 0 &&
265
+ JSON.stringify(lastPassedColumns.current) !== JSON.stringify(columns)) {
265
266
  instance.setColumnOrder([]);
266
267
  }
267
268
  lastPassedColumns.current = columns;
@@ -42,13 +42,14 @@ const TabList = React.forwardRef((props, ref) => {
42
42
  }, className), role: 'tablist', ref: refs, ...rest },
43
43
  React.createElement(TabListContext.Provider, { value: {
44
44
  tabsWidth,
45
+ tablistRef,
45
46
  } }, children)));
46
47
  });
47
48
  TabList.displayName = 'Tabs.TabList';
48
49
  const Tab = React.forwardRef((props, forwardedRef) => {
49
50
  const { className, children, value, label, ...rest } = props;
50
51
  const { orientation, activeValue, setActiveValue, type, setStripeProperties, idPrefix, focusActivationMode, } = useSafeContext(TabsContext);
51
- const { tabsWidth } = useSafeContext(TabListContext);
52
+ const { tabsWidth, tablistRef } = useSafeContext(TabListContext);
52
53
  const tabRef = React.useRef();
53
54
  const isActive = activeValue === value;
54
55
  const isActiveRef = useLatestRef(isActive);
@@ -65,13 +66,27 @@ const Tab = React.forwardRef((props, forwardedRef) => {
65
66
  useLayoutEffect(() => {
66
67
  const updateStripe = () => {
67
68
  const currentTabRect = tabRef.current?.getBoundingClientRect();
69
+ const tabslistRect = tablistRef.current?.getBoundingClientRect();
70
+ // Using getBoundingClientRect() to get decimal granularity.
71
+ // Not using offsetLeft/offsetTop because they round to the nearest integer.
72
+ // Even minor inaccuracies in the stripe position can cause unexpected scroll/scrollbar.
73
+ // See: https://github.com/iTwin/iTwinUI/issues/1870
74
+ const tabsStripePosition = currentTabRect != null && tabslistRect != null
75
+ ? {
76
+ horizontal: currentTabRect.x - tabslistRect.x,
77
+ vertical: currentTabRect.y - tabslistRect.y,
78
+ }
79
+ : {
80
+ horizontal: 0,
81
+ vertical: 0,
82
+ };
68
83
  setStripeProperties({
69
84
  '--iui-tabs-stripe-size': orientation === 'horizontal'
70
85
  ? `${currentTabRect?.width}px`
71
86
  : `${currentTabRect?.height}px`,
72
87
  '--iui-tabs-stripe-position': orientation === 'horizontal'
73
- ? `${tabRef.current?.offsetLeft}px`
74
- : `${tabRef.current?.offsetTop}px`,
88
+ ? `${tabsStripePosition.horizontal}px`
89
+ : `${tabsStripePosition.vertical}px`,
75
90
  });
76
91
  };
77
92
  if (type !== 'default' && isActive) {
@@ -83,6 +98,7 @@ const Tab = React.forwardRef((props, forwardedRef) => {
83
98
  isActive,
84
99
  tabsWidth,
85
100
  setStripeProperties,
101
+ tablistRef,
86
102
  ]);
87
103
  const onKeyDown = (event) => {
88
104
  if (event.altKey) {
@@ -255,7 +255,7 @@ export declare const Tile: PolymorphicForwardRefComponent<"div", TileLegacyProps
255
255
  labelProps?: Omit<Omit<Omit<Omit<React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>, "ref"> & {
256
256
  ref?: ((instance: HTMLDivElement | null) => void) | React.RefObject<HTMLDivElement> | null | undefined;
257
257
  }, "as" | "children" | "content" | "portal" | keyof {
258
- placement?: import("@floating-ui/utils").Placement | undefined;
258
+ placement?: import("@floating-ui/core").Placement | undefined;
259
259
  visible?: boolean | undefined;
260
260
  onVisibleChange?: ((visible: boolean) => void) | undefined;
261
261
  autoUpdateOptions?: {
@@ -284,7 +284,7 @@ export declare const Tile: PolymorphicForwardRefComponent<"div", TileLegacyProps
284
284
  content: React.ReactNode;
285
285
  children?: React.ReactNode;
286
286
  } & import("../utils/index.js").PortalProps & {
287
- placement?: import("@floating-ui/utils").Placement | undefined;
287
+ placement?: import("@floating-ui/core").Placement | undefined;
288
288
  visible?: boolean | undefined;
289
289
  onVisibleChange?: ((visible: boolean) => void) | undefined;
290
290
  autoUpdateOptions?: {
@@ -17,9 +17,11 @@ type ToggleSwitchProps = {
17
17
  */
18
18
  size?: 'default';
19
19
  /**
20
- * Icon inside the toggle switch. Shown only when toggle is checked and size is not small.
20
+ * Custom icon inside the toggle switch. Shown only when toggle is checked and size is not small.
21
+ *
22
+ * Will override the default checkmark icon.
21
23
  */
22
- icon?: JSX.Element;
24
+ icon?: JSX.Element | null;
23
25
  } | {
24
26
  size: 'small';
25
27
  icon?: never;