@itwin/itwinui-react 2.0.2 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/cjs/core/ComboBox/ComboBox.d.ts +25 -8
  3. package/cjs/core/ComboBox/ComboBox.js +141 -44
  4. package/cjs/core/ComboBox/ComboBoxDropdown.d.ts +1 -1
  5. package/cjs/core/ComboBox/ComboBoxDropdown.js +2 -2
  6. package/cjs/core/ComboBox/ComboBoxEndIcon.js +1 -1
  7. package/cjs/core/ComboBox/ComboBoxInput.d.ts +2 -0
  8. package/cjs/core/ComboBox/ComboBoxInput.js +40 -23
  9. package/cjs/core/ComboBox/ComboBoxMenu.js +9 -8
  10. package/cjs/core/ComboBox/ComboBoxMenuItem.js +1 -1
  11. package/cjs/core/ComboBox/ComboBoxMultipleContainer.d.ts +4 -0
  12. package/cjs/core/ComboBox/ComboBoxMultipleContainer.js +17 -0
  13. package/cjs/core/ComboBox/helpers.d.ts +20 -5
  14. package/cjs/core/ComboBox/helpers.js +24 -6
  15. package/cjs/core/Select/Select.js +2 -7
  16. package/cjs/core/Select/SelectTagContainer.d.ts +16 -0
  17. package/cjs/core/Select/SelectTagContainer.js +27 -0
  18. package/cjs/core/Table/Table.js +4 -2
  19. package/cjs/core/Table/actionHandlers/index.d.ts +1 -1
  20. package/cjs/core/Table/actionHandlers/index.js +2 -2
  21. package/cjs/core/Table/actionHandlers/selectHandler.d.ts +2 -2
  22. package/cjs/core/Table/actionHandlers/selectHandler.js +20 -6
  23. package/cjs/core/ThemeProvider/ThemeProvider.d.ts +25 -6
  24. package/cjs/core/ThemeProvider/ThemeProvider.js +29 -12
  25. package/cjs/core/utils/components/Popover.d.ts +1 -1
  26. package/cjs/core/utils/hooks/index.d.ts +1 -0
  27. package/cjs/core/utils/hooks/index.js +1 -0
  28. package/cjs/core/utils/hooks/useIsThemeAlreadySet.d.ts +7 -0
  29. package/cjs/core/utils/hooks/useIsThemeAlreadySet.js +34 -0
  30. package/cjs/core/utils/hooks/useTheme.js +4 -9
  31. package/esm/core/ComboBox/ComboBox.d.ts +25 -8
  32. package/esm/core/ComboBox/ComboBox.js +141 -44
  33. package/esm/core/ComboBox/ComboBoxDropdown.d.ts +1 -1
  34. package/esm/core/ComboBox/ComboBoxDropdown.js +2 -2
  35. package/esm/core/ComboBox/ComboBoxEndIcon.js +1 -1
  36. package/esm/core/ComboBox/ComboBoxInput.d.ts +2 -0
  37. package/esm/core/ComboBox/ComboBoxInput.js +41 -24
  38. package/esm/core/ComboBox/ComboBoxMenu.js +9 -8
  39. package/esm/core/ComboBox/ComboBoxMenuItem.js +1 -1
  40. package/esm/core/ComboBox/ComboBoxMultipleContainer.d.ts +4 -0
  41. package/esm/core/ComboBox/ComboBoxMultipleContainer.js +11 -0
  42. package/esm/core/ComboBox/helpers.d.ts +20 -5
  43. package/esm/core/ComboBox/helpers.js +24 -6
  44. package/esm/core/Select/Select.js +3 -8
  45. package/esm/core/Select/SelectTagContainer.d.ts +16 -0
  46. package/esm/core/Select/SelectTagContainer.js +21 -0
  47. package/esm/core/Table/Table.js +5 -3
  48. package/esm/core/Table/actionHandlers/index.d.ts +1 -1
  49. package/esm/core/Table/actionHandlers/index.js +1 -1
  50. package/esm/core/Table/actionHandlers/selectHandler.d.ts +2 -2
  51. package/esm/core/Table/actionHandlers/selectHandler.js +17 -3
  52. package/esm/core/ThemeProvider/ThemeProvider.d.ts +25 -6
  53. package/esm/core/ThemeProvider/ThemeProvider.js +30 -13
  54. package/esm/core/utils/components/Popover.d.ts +1 -1
  55. package/esm/core/utils/hooks/index.d.ts +1 -0
  56. package/esm/core/utils/hooks/index.js +1 -0
  57. package/esm/core/utils/hooks/useIsThemeAlreadySet.d.ts +7 -0
  58. package/esm/core/utils/hooks/useIsThemeAlreadySet.js +27 -0
  59. package/esm/core/utils/hooks/useTheme.js +4 -6
  60. package/package.json +10 -5
@@ -1,13 +1,26 @@
1
1
  import React from 'react';
2
2
  import { SelectOption } from '../Select/Select';
3
- declare type ComboBoxAction = 'open' | 'close' | 'select' | 'focus';
3
+ declare type ComboBoxAction = {
4
+ type: 'multiselect';
5
+ value: number[];
6
+ } | {
7
+ type: 'open';
8
+ } | {
9
+ type: 'close';
10
+ } | {
11
+ type: 'select';
12
+ value: number;
13
+ } | {
14
+ type: 'focus';
15
+ value: number | undefined;
16
+ };
4
17
  export declare const comboBoxReducer: (state: {
5
18
  isOpen: boolean;
6
- selectedIndex: number;
19
+ selected: number | number[];
7
20
  focusedIndex: number;
8
- }, [type, value]: [ComboBoxAction] | [ComboBoxAction, number | undefined]) => {
21
+ }, action: ComboBoxAction) => {
9
22
  isOpen: boolean;
10
- selectedIndex: number;
23
+ selected: number | number[];
11
24
  focusedIndex: number;
12
25
  };
13
26
  export declare const ComboBoxRefsContext: React.Context<{
@@ -24,9 +37,11 @@ declare type ComboBoxStateContextProps<T = unknown> = {
24
37
  minWidth: number;
25
38
  enableVirtualization: boolean;
26
39
  filteredOptions: SelectOption<T>[];
40
+ onClickHandler?: (prop: number) => void;
27
41
  getMenuItem: (option: SelectOption<T>, filteredIndex?: number) => JSX.Element;
28
42
  focusedIndex?: number;
43
+ multiple?: boolean;
29
44
  };
30
45
  export declare const ComboBoxStateContext: React.Context<ComboBoxStateContextProps<unknown> | undefined>;
31
- export declare const ComboBoxActionContext: React.Context<((x: [ComboBoxAction] | [ComboBoxAction, number]) => void) | undefined>;
46
+ export declare const ComboBoxActionContext: React.Context<((x: ComboBoxAction) => void) | undefined>;
32
47
  export {};
@@ -9,9 +9,9 @@ exports.ComboBoxActionContext = exports.ComboBoxStateContext = exports.ComboBoxR
9
9
  * See LICENSE.md in the project root for license terms and full copyright notice.
10
10
  *--------------------------------------------------------------------------------------------*/
11
11
  const react_1 = __importDefault(require("react"));
12
- const comboBoxReducer = (state, [type, value]) => {
13
- var _a;
14
- switch (type) {
12
+ const comboBoxReducer = (state, action) => {
13
+ var _a, _b, _c, _d, _e;
14
+ switch (action.type) {
15
15
  case 'open': {
16
16
  return { ...state, isOpen: true };
17
17
  }
@@ -19,14 +19,32 @@ const comboBoxReducer = (state, [type, value]) => {
19
19
  return { ...state, isOpen: false };
20
20
  }
21
21
  case 'select': {
22
+ if (Array.isArray(state.selected)) {
23
+ return { ...state };
24
+ }
22
25
  return {
23
26
  ...state,
24
- selectedIndex: value !== null && value !== void 0 ? value : state.selectedIndex,
25
- focusedIndex: value !== null && value !== void 0 ? value : state.focusedIndex,
27
+ selected: (_a = action.value) !== null && _a !== void 0 ? _a : state.selected,
28
+ focusedIndex: (_b = action.value) !== null && _b !== void 0 ? _b : state.focusedIndex,
26
29
  };
27
30
  }
31
+ case 'multiselect': {
32
+ if (!Array.isArray(state.selected)) {
33
+ return { ...state };
34
+ }
35
+ return { ...state, selected: action.value };
36
+ }
28
37
  case 'focus': {
29
- return { ...state, focusedIndex: (_a = value !== null && value !== void 0 ? value : state.selectedIndex) !== null && _a !== void 0 ? _a : -1 };
38
+ if (Array.isArray(state.selected)) {
39
+ return {
40
+ ...state,
41
+ focusedIndex: (_c = action.value) !== null && _c !== void 0 ? _c : -1,
42
+ };
43
+ }
44
+ return {
45
+ ...state,
46
+ focusedIndex: (_e = (_d = action.value) !== null && _d !== void 0 ? _d : state.selected) !== null && _e !== void 0 ? _e : -1,
47
+ };
30
48
  }
31
49
  default: {
32
50
  return state;
@@ -15,6 +15,7 @@ const MenuItem_1 = require("../Menu/MenuItem");
15
15
  const utils_1 = require("../utils");
16
16
  require("@itwin/itwinui-css/css/select.css");
17
17
  const SelectTag_1 = __importDefault(require("./SelectTag"));
18
+ const SelectTagContainer_1 = __importDefault(require("./SelectTagContainer"));
18
19
  const isMultipleEnabled = (variable, multiple) => {
19
20
  return multiple;
20
21
  };
@@ -204,17 +205,11 @@ const MultipleSelectButton = ({ selectedItems, selectedItemsRenderer, tagRendere
204
205
  }
205
206
  return selectedItems.map((item) => tagRenderer(item));
206
207
  }, [selectedItems, tagRenderer]);
207
- const [containerRef, visibleCount] = (0, utils_1.useOverflow)(selectedItemsElements);
208
208
  return (react_1.default.createElement(react_1.default.Fragment, null,
209
209
  selectedItems &&
210
210
  selectedItemsRenderer &&
211
211
  selectedItemsRenderer(selectedItems),
212
212
  selectedItems && !selectedItemsRenderer && (react_1.default.createElement("span", { className: 'iui-content' },
213
- react_1.default.createElement("div", { className: 'iui-select-tag-container', ref: containerRef },
214
- react_1.default.createElement(react_1.default.Fragment, null,
215
- visibleCount < selectedItemsElements.length
216
- ? selectedItemsElements.slice(0, visibleCount - 1)
217
- : selectedItemsElements,
218
- visibleCount < selectedItemsElements.length && (react_1.default.createElement(SelectTag_1.default, { label: `+${selectedItemsElements.length - visibleCount + 1} item(s)` }))))))));
213
+ react_1.default.createElement(SelectTagContainer_1.default, { tags: selectedItemsElements })))));
219
214
  };
220
215
  exports.default = exports.Select;
@@ -0,0 +1,16 @@
1
+ import React from 'react';
2
+ export declare type SelectTagContainerProps = {
3
+ /**
4
+ * Select tags.
5
+ */
6
+ tags: React.ReactNode[];
7
+ } & Omit<React.ComponentPropsWithoutRef<'div'>, 'children'>;
8
+ /**
9
+ */
10
+ export declare const SelectTagContainer: React.ForwardRefExoticComponent<{
11
+ /**
12
+ * Select tags.
13
+ */
14
+ tags: React.ReactNode[];
15
+ } & Omit<Pick<React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>, "key" | keyof React.HTMLAttributes<HTMLDivElement>>, "children"> & React.RefAttributes<HTMLDivElement>>;
16
+ export default SelectTagContainer;
@@ -0,0 +1,27 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.SelectTagContainer = void 0;
7
+ /*---------------------------------------------------------------------------------------------
8
+ * Copyright (c) Bentley Systems, Incorporated. All rights reserved.
9
+ * See LICENSE.md in the project root for license terms and full copyright notice.
10
+ *--------------------------------------------------------------------------------------------*/
11
+ const react_1 = __importDefault(require("react"));
12
+ const classnames_1 = __importDefault(require("classnames"));
13
+ const utils_1 = require("../utils");
14
+ const SelectTag_1 = __importDefault(require("./SelectTag"));
15
+ /**
16
+ */
17
+ exports.SelectTagContainer = react_1.default.forwardRef((props, ref) => {
18
+ const { tags, className, ...rest } = props;
19
+ (0, utils_1.useTheme)();
20
+ const [containerRef, visibleCount] = (0, utils_1.useOverflow)(tags);
21
+ const refs = (0, utils_1.useMergedRefs)(ref, containerRef);
22
+ return (react_1.default.createElement("div", { className: (0, classnames_1.default)('iui-select-tag-container', className), ref: refs, ...rest },
23
+ react_1.default.createElement(react_1.default.Fragment, null,
24
+ visibleCount < tags.length ? tags.slice(0, visibleCount - 1) : tags,
25
+ visibleCount < tags.length && (react_1.default.createElement(SelectTag_1.default, { label: `+${tags.length - visibleCount + 1} item(s)` })))));
26
+ });
27
+ exports.default = exports.SelectTagContainer;
@@ -150,7 +150,7 @@ const Table = (props) => {
150
150
  case react_table_1.actions.toggleRowSelected:
151
151
  case react_table_1.actions.toggleAllRowsSelected:
152
152
  case react_table_1.actions.toggleAllPageRowsSelected: {
153
- (0, actionHandlers_1.onSelectHandler)(newState, instance, onSelect,
153
+ (0, actionHandlers_1.onToggleHandler)(newState, action, instance, onSelect,
154
154
  // If it has manual selection column, then we can't check whether row is disabled
155
155
  hasManualSelectionColumn ? undefined : isRowDisabled);
156
156
  break;
@@ -209,6 +209,7 @@ const Table = (props) => {
209
209
  const showSortButton = (column) => data.length !== 0 && column.canSort;
210
210
  const onRowClickHandler = react_1.default.useCallback((event, row) => {
211
211
  const isDisabled = isRowDisabled === null || isRowDisabled === void 0 ? void 0 : isRowDisabled(row.original);
212
+ const ctrlPressed = event.ctrlKey || event.metaKey;
212
213
  if (!isDisabled) {
213
214
  onRowClick === null || onRowClick === void 0 ? void 0 : onRowClick(event, row);
214
215
  }
@@ -220,10 +221,11 @@ const Table = (props) => {
220
221
  dispatch({
221
222
  type: shiftRowSelectedAction,
222
223
  id: row.id,
224
+ ctrlPressed: ctrlPressed,
223
225
  });
224
226
  }
225
227
  else if (!row.isSelected &&
226
- (selectionMode === 'single' || !event.ctrlKey)) {
228
+ (selectionMode === 'single' || !ctrlPressed)) {
227
229
  dispatch({
228
230
  type: singleRowSelectedAction,
229
231
  id: row.id,
@@ -1,4 +1,4 @@
1
1
  export { onExpandHandler } from './expandHandler';
2
2
  export { onFilterHandler } from './filterHandler';
3
- export { onSelectHandler, onSingleSelectHandler, onShiftSelectHandler, } from './selectHandler';
3
+ export { onToggleHandler, onSingleSelectHandler, onShiftSelectHandler, } from './selectHandler';
4
4
  export { onTableResizeStart, onTableResizeEnd } from './resizeHandler';
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.onTableResizeEnd = exports.onTableResizeStart = exports.onShiftSelectHandler = exports.onSingleSelectHandler = exports.onSelectHandler = exports.onFilterHandler = exports.onExpandHandler = void 0;
3
+ exports.onTableResizeEnd = exports.onTableResizeStart = exports.onShiftSelectHandler = exports.onSingleSelectHandler = exports.onToggleHandler = exports.onFilterHandler = exports.onExpandHandler = void 0;
4
4
  /*---------------------------------------------------------------------------------------------
5
5
  * Copyright (c) Bentley Systems, Incorporated. All rights reserved.
6
6
  * See LICENSE.md in the project root for license terms and full copyright notice.
@@ -10,7 +10,7 @@ Object.defineProperty(exports, "onExpandHandler", { enumerable: true, get: funct
10
10
  var filterHandler_1 = require("./filterHandler");
11
11
  Object.defineProperty(exports, "onFilterHandler", { enumerable: true, get: function () { return filterHandler_1.onFilterHandler; } });
12
12
  var selectHandler_1 = require("./selectHandler");
13
- Object.defineProperty(exports, "onSelectHandler", { enumerable: true, get: function () { return selectHandler_1.onSelectHandler; } });
13
+ Object.defineProperty(exports, "onToggleHandler", { enumerable: true, get: function () { return selectHandler_1.onToggleHandler; } });
14
14
  Object.defineProperty(exports, "onSingleSelectHandler", { enumerable: true, get: function () { return selectHandler_1.onSingleSelectHandler; } });
15
15
  Object.defineProperty(exports, "onShiftSelectHandler", { enumerable: true, get: function () { return selectHandler_1.onShiftSelectHandler; } });
16
16
  var resizeHandler_1 = require("./resizeHandler");
@@ -1,8 +1,8 @@
1
1
  import { ActionType, TableInstance, TableState } from 'react-table';
2
2
  /**
3
- * Handles selection when clicked on a checkbox.
3
+ * Handles selection when toggling a row (Ctrl click or checkbox click)
4
4
  */
5
- export declare const onSelectHandler: <T extends Record<string, unknown>>(newState: TableState<T>, instance?: TableInstance<T> | undefined, onSelect?: ((selectedData: T[] | undefined, tableState?: TableState<T> | undefined) => void) | undefined, isRowDisabled?: ((rowData: T) => boolean) | undefined) => void;
5
+ export declare const onToggleHandler: <T extends Record<string, unknown>>(newState: TableState<T>, action: ActionType, instance?: TableInstance<T> | undefined, onSelect?: ((selectedData: T[] | undefined, tableState?: TableState<T> | undefined) => void) | undefined, isRowDisabled?: ((rowData: T) => boolean) | undefined) => void;
6
6
  /**
7
7
  * Handles selection when clicked on a row.
8
8
  */
@@ -1,8 +1,10 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.onShiftSelectHandler = exports.onSingleSelectHandler = exports.onSelectHandler = void 0;
3
+ exports.onShiftSelectHandler = exports.onSingleSelectHandler = exports.onToggleHandler = void 0;
4
4
  /**
5
- * Handles selection when clicked on a checkbox.
5
+ * Handles subrow selection and validation.
6
+ * - Subrow selection: Selecting a row and calling this method automatically selects all the subrows that can be selected
7
+ * - Validation: Ensures that any disabled/unselectable row/subrow is not selected
6
8
  */
7
9
  const onSelectHandler = (newState, instance, onSelect, isRowDisabled) => {
8
10
  if (!(instance === null || instance === void 0 ? void 0 : instance.rows.length)) {
@@ -36,7 +38,15 @@ const onSelectHandler = (newState, instance, onSelect, isRowDisabled) => {
36
38
  newState.selectedRowIds = newSelectedRowIds;
37
39
  onSelect === null || onSelect === void 0 ? void 0 : onSelect(selectedData, newState);
38
40
  };
39
- exports.onSelectHandler = onSelectHandler;
41
+ /**
42
+ * Handles selection when toggling a row (Ctrl click or checkbox click)
43
+ */
44
+ const onToggleHandler = (newState, action, instance, onSelect, isRowDisabled) => {
45
+ onSelectHandler(newState, instance, onSelect, isRowDisabled);
46
+ // Toggling a row (ctrl click or checkbox click) updates the lastSelectedRowId
47
+ newState.lastSelectedRowId = action.id;
48
+ };
49
+ exports.onToggleHandler = onToggleHandler;
40
50
  /**
41
51
  * Handles selection when clicked on a row.
42
52
  */
@@ -55,7 +65,7 @@ const onSingleSelectHandler = (state, action, instance, onSelect, isRowDisabled)
55
65
  selectedRowIds,
56
66
  };
57
67
  // Passing to `onSelectHandler` to handle filtered sub-rows
58
- (0, exports.onSelectHandler)(newState, instance, onSelect, isRowDisabled);
68
+ onSelectHandler(newState, instance, onSelect, isRowDisabled);
59
69
  return newState;
60
70
  };
61
71
  exports.onSingleSelectHandler = onSingleSelectHandler;
@@ -73,7 +83,11 @@ const onShiftSelectHandler = (state, action, instance, onSelect, isRowDisabled)
73
83
  startIndex = endIndex;
74
84
  endIndex = temp;
75
85
  }
76
- const selectedRowIds = {};
86
+ // If ctrl + shift click, do not lose previous selection
87
+ // If shift click, start new selection
88
+ const selectedRowIds = !!action.ctrlPressed
89
+ ? state.selectedRowIds
90
+ : {};
77
91
  // 1. Select all rows between start and end
78
92
  instance.flatRows
79
93
  .slice(startIndex, endIndex + 1)
@@ -91,7 +105,7 @@ const onShiftSelectHandler = (state, action, instance, onSelect, isRowDisabled)
91
105
  };
92
106
  // 3.1 Deselect all selected disabled rows and their children
93
107
  // 3.2 Convert all partially selected rows marked with tick mark to horizontal line
94
- (0, exports.onSelectHandler)(newState, instance, onSelect, isRowDisabled);
108
+ onSelectHandler(newState, instance, onSelect, isRowDisabled);
95
109
  return newState;
96
110
  };
97
111
  exports.onShiftSelectHandler = onShiftSelectHandler;
@@ -3,7 +3,7 @@ import type { PolymorphicComponentProps, PolymorphicForwardRefComponent, ThemeOp
3
3
  import '@itwin/itwinui-css/css/global.css';
4
4
  import '@itwin/itwinui-variables/index.css';
5
5
  export declare type ThemeProviderProps<T extends React.ElementType = 'div'> = PolymorphicComponentProps<T, ThemeProviderOwnProps>;
6
- declare type ThemeProviderOwnProps = {
6
+ declare type RootProps = {
7
7
  /**
8
8
  * Theme to be applied. Can be 'light' or 'dark' or 'os'.
9
9
  *
@@ -14,8 +14,21 @@ declare type ThemeProviderOwnProps = {
14
14
  * @default 'light'
15
15
  */
16
16
  theme?: ThemeType;
17
- } & ({
18
- themeOptions?: Pick<ThemeOptions, 'highContrast'>;
17
+ themeOptions?: Pick<ThemeOptions, 'highContrast'> & {
18
+ /**
19
+ * Whether or not the element should apply the recommended `background-color` on itself.
20
+ *
21
+ * When not specified, the default behavior is to apply a background-color only
22
+ * if it is the topmost `ThemeProvider` in the tree. Nested `ThemeProvider`s will
23
+ * be detected using React Context and will not apply a background-color.
24
+ *
25
+ * When set to true or false, it will override the default behavior.
26
+ */
27
+ applyBackground?: boolean;
28
+ };
29
+ };
30
+ declare type ThemeProviderOwnProps = Pick<RootProps, 'theme'> & ({
31
+ themeOptions?: RootProps['themeOptions'];
19
32
  children: Required<React.ReactNode>;
20
33
  } | {
21
34
  themeOptions?: ThemeOptions;
@@ -26,10 +39,11 @@ declare type ThemeProviderOwnProps = {
26
39
  * that it is wrapping around. The `theme` prop is optional and defaults to the
27
40
  * light theme.
28
41
  *
29
- * If you want to theme the entire app, you should use this component at the root.
30
- * The `as` prop can be used to render a `<body>` element instead of a `<div>`.
42
+ * If you want to theme the entire app, you should use this component at the root. You can also
43
+ * use this component to apply a different theme to only a part of the tree.
31
44
  *
32
- * You can also use this component to apply a different theme to only a part of the tree.
45
+ * By default, the topmost `ThemeProvider` in the tree will apply the recommended
46
+ * `background-color`. You can override this behavior using `themeOptions.applyBackground`.
33
47
  *
34
48
  * @example
35
49
  * <ThemeProvider theme='os'>
@@ -40,6 +54,11 @@ declare type ThemeProviderOwnProps = {
40
54
  * <ThemeProvider as='body'>
41
55
  * <App />
42
56
  * </ThemeProvider>
57
+ *
58
+ * @example
59
+ * <ThemeProvider theme='dark' themeOptions={{ applyBackground: false }}>
60
+ * <App />
61
+ * </ThemeProvider>
43
62
  */
44
63
  export declare const ThemeProvider: PolymorphicForwardRefComponent<"div", ThemeProviderOwnProps>;
45
64
  export default ThemeProvider;
@@ -18,10 +18,11 @@ require("@itwin/itwinui-variables/index.css");
18
18
  * that it is wrapping around. The `theme` prop is optional and defaults to the
19
19
  * light theme.
20
20
  *
21
- * If you want to theme the entire app, you should use this component at the root.
22
- * The `as` prop can be used to render a `<body>` element instead of a `<div>`.
21
+ * If you want to theme the entire app, you should use this component at the root. You can also
22
+ * use this component to apply a different theme to only a part of the tree.
23
23
  *
24
- * You can also use this component to apply a different theme to only a part of the tree.
24
+ * By default, the topmost `ThemeProvider` in the tree will apply the recommended
25
+ * `background-color`. You can override this behavior using `themeOptions.applyBackground`.
25
26
  *
26
27
  * @example
27
28
  * <ThemeProvider theme='os'>
@@ -32,26 +33,42 @@ require("@itwin/itwinui-variables/index.css");
32
33
  * <ThemeProvider as='body'>
33
34
  * <App />
34
35
  * </ThemeProvider>
36
+ *
37
+ * @example
38
+ * <ThemeProvider theme='dark' themeOptions={{ applyBackground: false }}>
39
+ * <App />
40
+ * </ThemeProvider>
35
41
  */
36
42
  exports.ThemeProvider = react_1.default.forwardRef((props, ref) => {
37
- var _a;
38
- const { theme, children, themeOptions, as: Element = 'div', className, ...rest } = props;
43
+ const { theme, children, themeOptions, ...rest } = props;
39
44
  const rootRef = react_1.default.useRef(null);
40
45
  const mergedRefs = (0, utils_1.useMergedRefs)(rootRef, ref);
41
46
  const hasChildren = react_1.default.Children.count(children) > 0;
42
47
  const parentContext = react_1.default.useContext(exports.ThemeContext);
48
+ const contextValue = react_1.default.useMemo(() => ({ theme, themeOptions, rootRef }), [theme, themeOptions]);
49
+ // if no children, then fallback to this wrapper component which calls useTheme
50
+ if (!hasChildren) {
51
+ return (react_1.default.createElement(ThemeLogicWrapper, { theme: theme !== null && theme !== void 0 ? theme : parentContext === null || parentContext === void 0 ? void 0 : parentContext.theme, themeOptions: themeOptions !== null && themeOptions !== void 0 ? themeOptions : parentContext === null || parentContext === void 0 ? void 0 : parentContext.themeOptions }));
52
+ }
53
+ // now that we know there are children, we can render the root and provide the context value
54
+ return (react_1.default.createElement(Root, { theme: theme, themeOptions: themeOptions, ref: mergedRefs, ...rest },
55
+ react_1.default.createElement(exports.ThemeContext.Provider, { value: contextValue }, children)));
56
+ });
57
+ exports.default = exports.ThemeProvider;
58
+ exports.ThemeContext = react_1.default.createContext(undefined);
59
+ const Root = react_1.default.forwardRef((props, forwardedRef) => {
60
+ var _a, _b, _c;
61
+ const { theme, children, themeOptions, as: Element = 'div', className, ...rest } = props;
62
+ const ref = react_1.default.useRef(null);
63
+ const mergedRefs = (0, utils_1.useMergedRefs)(ref, forwardedRef);
43
64
  const prefersDark = (0, utils_1.useMediaQuery)('(prefers-color-scheme: dark)');
44
65
  const prefersHighContrast = (0, utils_1.useMediaQuery)('(prefers-contrast: more)');
45
66
  const shouldApplyDark = theme === 'dark' || (theme === 'os' && prefersDark);
46
67
  const shouldApplyHC = (_a = themeOptions === null || themeOptions === void 0 ? void 0 : themeOptions.highContrast) !== null && _a !== void 0 ? _a : prefersHighContrast;
47
- // only provide context if wrapped around children
48
- return hasChildren ? (react_1.default.createElement(exports.ThemeContext.Provider, { value: { theme, themeOptions, rootRef } },
49
- react_1.default.createElement(Element, { className: (0, classnames_1.default)('iui-root', className), "data-iui-theme": shouldApplyDark ? 'dark' : 'light', "data-iui-contrast": shouldApplyHC ? 'high' : 'default', ref: mergedRefs, ...rest }, children))) : (
50
- // otherwise just apply theme on the root using this wrapper component
51
- react_1.default.createElement(ThemeLogicWrapper, { theme: theme !== null && theme !== void 0 ? theme : parentContext === null || parentContext === void 0 ? void 0 : parentContext.theme, themeOptions: themeOptions !== null && themeOptions !== void 0 ? themeOptions : parentContext === null || parentContext === void 0 ? void 0 : parentContext.themeOptions }));
68
+ const isThemeAlreadySet = (0, utils_1.useIsThemeAlreadySet)((_b = ref.current) === null || _b === void 0 ? void 0 : _b.ownerDocument);
69
+ const shouldApplyBackground = (_c = themeOptions === null || themeOptions === void 0 ? void 0 : themeOptions.applyBackground) !== null && _c !== void 0 ? _c : !isThemeAlreadySet.current;
70
+ return (react_1.default.createElement(Element, { className: (0, classnames_1.default)('iui-root', { 'iui-root-background': shouldApplyBackground }, className), "data-iui-theme": shouldApplyDark ? 'dark' : 'light', "data-iui-contrast": shouldApplyHC ? 'high' : 'default', ref: mergedRefs, ...rest }, children));
52
71
  });
53
- exports.default = exports.ThemeProvider;
54
- exports.ThemeContext = react_1.default.createContext(undefined);
55
72
  const ThemeLogicWrapper = ({ theme, themeOptions }) => {
56
73
  (0, utils_1.useTheme)(theme, themeOptions);
57
74
  return react_1.default.createElement(react_1.default.Fragment, null);
@@ -26,7 +26,7 @@ export declare type PopoverProps = {
26
26
  * with pre-configured props and plugins (e.g. lazy mounting, focus, etc).
27
27
  * @private
28
28
  */
29
- export declare const Popover: React.ForwardRefExoticComponent<Pick<PopoverProps, "disabled" | "theme" | "children" | "className" | "role" | "placement" | "trigger" | "visible" | "content" | "render" | "animateFill" | "appendTo" | "aria" | "delay" | "duration" | "followCursor" | "getReferenceClientRect" | "hideOnClick" | "ignoreAttributes" | "inlinePositioning" | "interactive" | "interactiveBorder" | "interactiveDebounce" | "moveTransition" | "offset" | "plugins" | "popperOptions" | "showOnCreate" | "sticky" | "touch" | "triggerTarget" | "onAfterUpdate" | "onBeforeUpdate" | "onCreate" | "onDestroy" | "onHidden" | "onHide" | "onMount" | "onShow" | "onShown" | "onTrigger" | "onUntrigger" | "onClickOutside" | "allowHTML" | "animation" | "arrow" | "inertia" | "maxWidth" | "zIndex" | "singleton" | "reference"> & React.RefAttributes<unknown>>;
29
+ export declare const Popover: React.ForwardRefExoticComponent<Pick<PopoverProps, "disabled" | "theme" | "children" | "className" | "role" | "offset" | "content" | "plugins" | "placement" | "trigger" | "visible" | "render" | "animateFill" | "appendTo" | "aria" | "delay" | "duration" | "followCursor" | "getReferenceClientRect" | "hideOnClick" | "ignoreAttributes" | "inlinePositioning" | "interactive" | "interactiveBorder" | "interactiveDebounce" | "moveTransition" | "popperOptions" | "showOnCreate" | "sticky" | "touch" | "triggerTarget" | "onAfterUpdate" | "onBeforeUpdate" | "onCreate" | "onDestroy" | "onHidden" | "onHide" | "onMount" | "onShow" | "onShown" | "onTrigger" | "onUntrigger" | "onClickOutside" | "allowHTML" | "animation" | "arrow" | "inertia" | "maxWidth" | "zIndex" | "singleton" | "reference"> & React.RefAttributes<unknown>>;
30
30
  /**
31
31
  * Plugin to hide Popover when either Esc key is pressed,
32
32
  * or when the content inside is not tabbable and Tab key is pressed.
@@ -9,3 +9,4 @@ export * from './useMediaQuery';
9
9
  export * from './useSafeContext';
10
10
  export * from './useLatestRef';
11
11
  export * from './useIsomorphicLayoutEffect';
12
+ export * from './useIsThemeAlreadySet';
@@ -29,3 +29,4 @@ __exportStar(require("./useMediaQuery"), exports);
29
29
  __exportStar(require("./useSafeContext"), exports);
30
30
  __exportStar(require("./useLatestRef"), exports);
31
31
  __exportStar(require("./useIsomorphicLayoutEffect"), exports);
32
+ __exportStar(require("./useIsThemeAlreadySet"), exports);
@@ -0,0 +1,7 @@
1
+ import React from 'react';
2
+ /**
3
+ * Hook that returns a boolean ref which is true if either:
4
+ * - There is a parent `ThemeProvider` in the tree, or
5
+ * - The <body> element has data-iui-theme attribute
6
+ */
7
+ export declare const useIsThemeAlreadySet: (ownerDocument?: Document | undefined) => React.MutableRefObject<boolean>;
@@ -0,0 +1,34 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.useIsThemeAlreadySet = void 0;
7
+ /*---------------------------------------------------------------------------------------------
8
+ * Copyright (c) Bentley Systems, Incorporated. All rights reserved.
9
+ * See LICENSE.md in the project root for license terms and full copyright notice.
10
+ *--------------------------------------------------------------------------------------------*/
11
+ const react_1 = __importDefault(require("react"));
12
+ const ThemeProvider_1 = require("../../ThemeProvider/ThemeProvider");
13
+ const useIsomorphicLayoutEffect_1 = require("./useIsomorphicLayoutEffect");
14
+ const functions_1 = require("../functions");
15
+ /**
16
+ * Hook that returns a boolean ref which is true if either:
17
+ * - There is a parent `ThemeProvider` in the tree, or
18
+ * - The <body> element has data-iui-theme attribute
19
+ */
20
+ const useIsThemeAlreadySet = (ownerDocument = (0, functions_1.getDocument)()) => {
21
+ const parentContext = react_1.default.useContext(ThemeProvider_1.ThemeContext);
22
+ const isThemeAlreadySet = react_1.default.useRef(!!parentContext || !!(ownerDocument === null || ownerDocument === void 0 ? void 0 : ownerDocument.body.dataset.iuiTheme));
23
+ (0, useIsomorphicLayoutEffect_1.useIsomorphicLayoutEffect)(() => {
24
+ if (parentContext ||
25
+ (ownerDocument && !!ownerDocument.body.dataset.iuiTheme)) {
26
+ isThemeAlreadySet.current = true;
27
+ }
28
+ return () => {
29
+ isThemeAlreadySet.current = false;
30
+ };
31
+ }, [parentContext, ownerDocument]);
32
+ return isThemeAlreadySet;
33
+ };
34
+ exports.useIsThemeAlreadySet = useIsThemeAlreadySet;
@@ -1,17 +1,13 @@
1
1
  "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
2
  Object.defineProperty(exports, "__esModule", { value: true });
6
3
  exports.useTheme = void 0;
7
4
  /*---------------------------------------------------------------------------------------------
8
5
  * Copyright (c) Bentley Systems, Incorporated. All rights reserved.
9
6
  * See LICENSE.md in the project root for license terms and full copyright notice.
10
7
  *--------------------------------------------------------------------------------------------*/
11
- const react_1 = __importDefault(require("react"));
12
- const ThemeProvider_1 = require("../../ThemeProvider/ThemeProvider");
13
8
  const functions_1 = require("../functions");
14
9
  const useIsomorphicLayoutEffect_1 = require("./useIsomorphicLayoutEffect");
10
+ const useIsThemeAlreadySet_1 = require("./useIsThemeAlreadySet");
15
11
  require("@itwin/itwinui-css/css/global.css");
16
12
  require("@itwin/itwinui-variables/index.css");
17
13
  /**
@@ -25,11 +21,10 @@ require("@itwin/itwinui-variables/index.css");
25
21
  */
26
22
  const useTheme = (theme, themeOptions) => {
27
23
  var _a;
28
- const themeContext = react_1.default.useContext(ThemeProvider_1.ThemeContext);
29
24
  const ownerDocument = (_a = themeOptions === null || themeOptions === void 0 ? void 0 : themeOptions.ownerDocument) !== null && _a !== void 0 ? _a : (0, functions_1.getDocument)();
25
+ const isThemeAlreadySet = (0, useIsThemeAlreadySet_1.useIsThemeAlreadySet)(ownerDocument);
30
26
  (0, useIsomorphicLayoutEffect_1.useIsomorphicLayoutEffect)(() => {
31
- // exit early if theme was already set by provider or is present on <body>
32
- if (themeContext || !ownerDocument || ownerDocument.body.dataset.iuiTheme) {
27
+ if (!ownerDocument || isThemeAlreadySet.current) {
33
28
  return;
34
29
  }
35
30
  ownerDocument.body.classList.toggle('iui-root', true);
@@ -47,7 +42,7 @@ const useTheme = (theme, themeOptions) => {
47
42
  return;
48
43
  }
49
44
  }
50
- }, [theme, themeContext, themeOptions === null || themeOptions === void 0 ? void 0 : themeOptions.highContrast, ownerDocument]);
45
+ }, [theme, themeOptions === null || themeOptions === void 0 ? void 0 : themeOptions.highContrast, ownerDocument]);
51
46
  };
52
47
  exports.useTheme = useTheme;
53
48
  /**
@@ -3,24 +3,41 @@ import { InputProps } from '../Input';
3
3
  import { SelectOption } from '../Select';
4
4
  import { PopoverProps, CommonProps, InputContainerProps } from '../utils';
5
5
  import 'tippy.js/animations/shift-away.css';
6
- export declare type ComboBoxProps<T> = {
6
+ declare type ActionType = 'added' | 'removed';
7
+ declare type MultipleOnChangeProps<T> = {
8
+ value: T;
9
+ type: ActionType;
10
+ };
11
+ export declare type ComboboxMultipleTypeProps<T> = {
7
12
  /**
8
- * Array of options that populate the dropdown list.
13
+ * Enable multiple selection.
14
+ * @default false
9
15
  */
10
- options: SelectOption<T>[];
16
+ multiple?: false;
11
17
  /**
12
18
  * Controlled value of ComboBox.
19
+ * If `multiple` is enabled, it is an array of values.
13
20
  */
14
21
  value?: T;
22
+ /**
23
+ * Callback fired when selected value changes.
24
+ */
25
+ onChange?: (value: T) => void;
26
+ } | {
27
+ multiple: true;
28
+ value?: T[];
29
+ onChange?: (value: T[], event: MultipleOnChangeProps<T>) => void;
30
+ };
31
+ export declare type ComboBoxProps<T> = {
32
+ /**
33
+ * Array of options that populate the dropdown list.
34
+ */
35
+ options: SelectOption<T>[];
15
36
  /**
16
37
  * Message shown below the combobox.
17
38
  * Use `StatusMessage` component.
18
39
  */
19
40
  message?: React.ReactNode;
20
- /**
21
- * Callback fired when selected value changes.
22
- */
23
- onChange?: (value: T) => void;
24
41
  /**
25
42
  * Function to customize the default filtering logic.
26
43
  */
@@ -67,7 +84,7 @@ export declare type ComboBoxProps<T> = {
67
84
  * Callback fired when dropdown menu is closed.
68
85
  */
69
86
  onHide?: () => void;
70
- } & Pick<InputContainerProps, 'status'> & Omit<CommonProps, 'title'>;
87
+ } & ComboboxMultipleTypeProps<T> & Pick<InputContainerProps, 'status'> & Omit<CommonProps, 'title'>;
71
88
  /**
72
89
  * ComboBox component that allows typing a value to filter the options in dropdown list.
73
90
  * Values can be selected either using mouse clicks or using the Enter key.