@itwin/itwinui-react 2.0.4 → 2.1.1

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 (58) hide show
  1. package/CHANGELOG.md +15 -2
  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/ComboBoxMenuItem.js +1 -1
  10. package/cjs/core/ComboBox/ComboBoxMultipleContainer.d.ts +4 -0
  11. package/cjs/core/ComboBox/ComboBoxMultipleContainer.js +17 -0
  12. package/cjs/core/ComboBox/helpers.d.ts +20 -5
  13. package/cjs/core/ComboBox/helpers.js +24 -6
  14. package/cjs/core/Select/Select.js +2 -7
  15. package/cjs/core/Select/SelectTagContainer.d.ts +16 -0
  16. package/cjs/core/Select/SelectTagContainer.js +27 -0
  17. package/cjs/core/Table/Table.js +4 -2
  18. package/cjs/core/Table/actionHandlers/index.d.ts +1 -1
  19. package/cjs/core/Table/actionHandlers/index.js +2 -2
  20. package/cjs/core/Table/actionHandlers/selectHandler.d.ts +2 -2
  21. package/cjs/core/Table/actionHandlers/selectHandler.js +20 -6
  22. package/cjs/core/ThemeProvider/ThemeProvider.d.ts +25 -6
  23. package/cjs/core/ThemeProvider/ThemeProvider.js +29 -12
  24. package/cjs/core/utils/components/Popover.d.ts +1 -1
  25. package/cjs/core/utils/hooks/index.d.ts +1 -0
  26. package/cjs/core/utils/hooks/index.js +1 -0
  27. package/cjs/core/utils/hooks/useIsThemeAlreadySet.d.ts +7 -0
  28. package/cjs/core/utils/hooks/useIsThemeAlreadySet.js +34 -0
  29. package/cjs/core/utils/hooks/useTheme.js +4 -9
  30. package/esm/core/ComboBox/ComboBox.d.ts +25 -8
  31. package/esm/core/ComboBox/ComboBox.js +141 -44
  32. package/esm/core/ComboBox/ComboBoxDropdown.d.ts +1 -1
  33. package/esm/core/ComboBox/ComboBoxDropdown.js +2 -2
  34. package/esm/core/ComboBox/ComboBoxEndIcon.js +1 -1
  35. package/esm/core/ComboBox/ComboBoxInput.d.ts +2 -0
  36. package/esm/core/ComboBox/ComboBoxInput.js +41 -24
  37. package/esm/core/ComboBox/ComboBoxMenuItem.js +1 -1
  38. package/esm/core/ComboBox/ComboBoxMultipleContainer.d.ts +4 -0
  39. package/esm/core/ComboBox/ComboBoxMultipleContainer.js +11 -0
  40. package/esm/core/ComboBox/helpers.d.ts +20 -5
  41. package/esm/core/ComboBox/helpers.js +24 -6
  42. package/esm/core/Select/Select.js +3 -8
  43. package/esm/core/Select/SelectTagContainer.d.ts +16 -0
  44. package/esm/core/Select/SelectTagContainer.js +21 -0
  45. package/esm/core/Table/Table.js +5 -3
  46. package/esm/core/Table/actionHandlers/index.d.ts +1 -1
  47. package/esm/core/Table/actionHandlers/index.js +1 -1
  48. package/esm/core/Table/actionHandlers/selectHandler.d.ts +2 -2
  49. package/esm/core/Table/actionHandlers/selectHandler.js +17 -3
  50. package/esm/core/ThemeProvider/ThemeProvider.d.ts +25 -6
  51. package/esm/core/ThemeProvider/ThemeProvider.js +30 -13
  52. package/esm/core/utils/components/Popover.d.ts +1 -1
  53. package/esm/core/utils/hooks/index.d.ts +1 -0
  54. package/esm/core/utils/hooks/index.js +1 -0
  55. package/esm/core/utils/hooks/useIsThemeAlreadySet.d.ts +7 -0
  56. package/esm/core/utils/hooks/useIsThemeAlreadySet.js +27 -0
  57. package/esm/core/utils/hooks/useTheme.js +4 -6
  58. package/package.json +10 -5
@@ -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,21 @@
1
+ /*---------------------------------------------------------------------------------------------
2
+ * Copyright (c) Bentley Systems, Incorporated. All rights reserved.
3
+ * See LICENSE.md in the project root for license terms and full copyright notice.
4
+ *--------------------------------------------------------------------------------------------*/
5
+ import React from 'react';
6
+ import cx from 'classnames';
7
+ import { useTheme, useOverflow, useMergedRefs } from '../utils';
8
+ import SelectTag from './SelectTag';
9
+ /**
10
+ */
11
+ export const SelectTagContainer = React.forwardRef((props, ref) => {
12
+ const { tags, className, ...rest } = props;
13
+ useTheme();
14
+ const [containerRef, visibleCount] = useOverflow(tags);
15
+ const refs = useMergedRefs(ref, containerRef);
16
+ return (React.createElement("div", { className: cx('iui-select-tag-container', className), ref: refs, ...rest },
17
+ React.createElement(React.Fragment, null,
18
+ visibleCount < tags.length ? tags.slice(0, visibleCount - 1) : tags,
19
+ visibleCount < tags.length && (React.createElement(SelectTag, { label: `+${tags.length - visibleCount + 1} item(s)` })))));
20
+ });
21
+ export default SelectTagContainer;
@@ -13,7 +13,7 @@ import { TableRowMemoized } from './TableRowMemoized';
13
13
  import { FilterToggle } from './filters';
14
14
  import { customFilterFunctions } from './filters/customFilterFunctions';
15
15
  import { useExpanderCell, useSelectionCell, useSubRowFiltering, useSubRowSelection, useResizeColumns, useColumnDragAndDrop, useScrollToRow, useStickyColumns, } from './hooks';
16
- import { onExpandHandler, onFilterHandler, onSelectHandler, onShiftSelectHandler, onSingleSelectHandler, onTableResizeEnd, onTableResizeStart, } from './actionHandlers';
16
+ import { onExpandHandler, onFilterHandler, onToggleHandler, onShiftSelectHandler, onSingleSelectHandler, onTableResizeEnd, onTableResizeStart, } from './actionHandlers';
17
17
  import VirtualScroll from '../utils/components/VirtualScroll';
18
18
  import { SELECTION_CELL_ID } from './columns';
19
19
  const singleRowSelectedAction = 'singleRowSelected';
@@ -144,7 +144,7 @@ export const Table = (props) => {
144
144
  case TableActions.toggleRowSelected:
145
145
  case TableActions.toggleAllRowsSelected:
146
146
  case TableActions.toggleAllPageRowsSelected: {
147
- onSelectHandler(newState, instance, onSelect,
147
+ onToggleHandler(newState, action, instance, onSelect,
148
148
  // If it has manual selection column, then we can't check whether row is disabled
149
149
  hasManualSelectionColumn ? undefined : isRowDisabled);
150
150
  break;
@@ -203,6 +203,7 @@ export const Table = (props) => {
203
203
  const showSortButton = (column) => data.length !== 0 && column.canSort;
204
204
  const onRowClickHandler = React.useCallback((event, row) => {
205
205
  const isDisabled = isRowDisabled === null || isRowDisabled === void 0 ? void 0 : isRowDisabled(row.original);
206
+ const ctrlPressed = event.ctrlKey || event.metaKey;
206
207
  if (!isDisabled) {
207
208
  onRowClick === null || onRowClick === void 0 ? void 0 : onRowClick(event, row);
208
209
  }
@@ -214,10 +215,11 @@ export const Table = (props) => {
214
215
  dispatch({
215
216
  type: shiftRowSelectedAction,
216
217
  id: row.id,
218
+ ctrlPressed: ctrlPressed,
217
219
  });
218
220
  }
219
221
  else if (!row.isSelected &&
220
- (selectionMode === 'single' || !event.ctrlKey)) {
222
+ (selectionMode === 'single' || !ctrlPressed)) {
221
223
  dispatch({
222
224
  type: singleRowSelectedAction,
223
225
  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';
@@ -4,5 +4,5 @@
4
4
  *--------------------------------------------------------------------------------------------*/
5
5
  export { onExpandHandler } from './expandHandler';
6
6
  export { onFilterHandler } from './filterHandler';
7
- export { onSelectHandler, onSingleSelectHandler, onShiftSelectHandler, } from './selectHandler';
7
+ export { onToggleHandler, onSingleSelectHandler, onShiftSelectHandler, } from './selectHandler';
8
8
  export { onTableResizeStart, onTableResizeEnd } from './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,7 +1,9 @@
1
1
  /**
2
- * Handles selection when clicked on a checkbox.
2
+ * Handles subrow selection and validation.
3
+ * - Subrow selection: Selecting a row and calling this method automatically selects all the subrows that can be selected
4
+ * - Validation: Ensures that any disabled/unselectable row/subrow is not selected
3
5
  */
4
- export const onSelectHandler = (newState, instance, onSelect, isRowDisabled) => {
6
+ const onSelectHandler = (newState, instance, onSelect, isRowDisabled) => {
5
7
  if (!(instance === null || instance === void 0 ? void 0 : instance.rows.length)) {
6
8
  onSelect === null || onSelect === void 0 ? void 0 : onSelect([], newState);
7
9
  return;
@@ -33,6 +35,14 @@ export const onSelectHandler = (newState, instance, onSelect, isRowDisabled) =>
33
35
  newState.selectedRowIds = newSelectedRowIds;
34
36
  onSelect === null || onSelect === void 0 ? void 0 : onSelect(selectedData, newState);
35
37
  };
38
+ /**
39
+ * Handles selection when toggling a row (Ctrl click or checkbox click)
40
+ */
41
+ export const onToggleHandler = (newState, action, instance, onSelect, isRowDisabled) => {
42
+ onSelectHandler(newState, instance, onSelect, isRowDisabled);
43
+ // Toggling a row (ctrl click or checkbox click) updates the lastSelectedRowId
44
+ newState.lastSelectedRowId = action.id;
45
+ };
36
46
  /**
37
47
  * Handles selection when clicked on a row.
38
48
  */
@@ -68,7 +78,11 @@ export const onShiftSelectHandler = (state, action, instance, onSelect, isRowDis
68
78
  startIndex = endIndex;
69
79
  endIndex = temp;
70
80
  }
71
- const selectedRowIds = {};
81
+ // If ctrl + shift click, do not lose previous selection
82
+ // If shift click, start new selection
83
+ const selectedRowIds = !!action.ctrlPressed
84
+ ? state.selectedRowIds
85
+ : {};
72
86
  // 1. Select all rows between start and end
73
87
  instance.flatRows
74
88
  .slice(startIndex, endIndex + 1)
@@ -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;
@@ -4,7 +4,7 @@
4
4
  *--------------------------------------------------------------------------------------------*/
5
5
  import React from 'react';
6
6
  import cx from 'classnames';
7
- import { useTheme, useMediaQuery, useMergedRefs } from '../utils';
7
+ import { useTheme, useMediaQuery, useMergedRefs, useIsThemeAlreadySet, } from '../utils';
8
8
  import '@itwin/itwinui-css/css/global.css';
9
9
  import '@itwin/itwinui-variables/index.css';
10
10
  /**
@@ -12,10 +12,11 @@ import '@itwin/itwinui-variables/index.css';
12
12
  * that it is wrapping around. The `theme` prop is optional and defaults to the
13
13
  * light theme.
14
14
  *
15
- * If you want to theme the entire app, you should use this component at the root.
16
- * The `as` prop can be used to render a `<body>` element instead of a `<div>`.
15
+ * If you want to theme the entire app, you should use this component at the root. You can also
16
+ * use this component to apply a different theme to only a part of the tree.
17
17
  *
18
- * You can also use this component to apply a different theme to only a part of the tree.
18
+ * By default, the topmost `ThemeProvider` in the tree will apply the recommended
19
+ * `background-color`. You can override this behavior using `themeOptions.applyBackground`.
19
20
  *
20
21
  * @example
21
22
  * <ThemeProvider theme='os'>
@@ -26,26 +27,42 @@ import '@itwin/itwinui-variables/index.css';
26
27
  * <ThemeProvider as='body'>
27
28
  * <App />
28
29
  * </ThemeProvider>
30
+ *
31
+ * @example
32
+ * <ThemeProvider theme='dark' themeOptions={{ applyBackground: false }}>
33
+ * <App />
34
+ * </ThemeProvider>
29
35
  */
30
36
  export const ThemeProvider = React.forwardRef((props, ref) => {
31
- var _a;
32
- const { theme, children, themeOptions, as: Element = 'div', className, ...rest } = props;
37
+ const { theme, children, themeOptions, ...rest } = props;
33
38
  const rootRef = React.useRef(null);
34
39
  const mergedRefs = useMergedRefs(rootRef, ref);
35
40
  const hasChildren = React.Children.count(children) > 0;
36
41
  const parentContext = React.useContext(ThemeContext);
42
+ const contextValue = React.useMemo(() => ({ theme, themeOptions, rootRef }), [theme, themeOptions]);
43
+ // if no children, then fallback to this wrapper component which calls useTheme
44
+ if (!hasChildren) {
45
+ return (React.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 }));
46
+ }
47
+ // now that we know there are children, we can render the root and provide the context value
48
+ return (React.createElement(Root, { theme: theme, themeOptions: themeOptions, ref: mergedRefs, ...rest },
49
+ React.createElement(ThemeContext.Provider, { value: contextValue }, children)));
50
+ });
51
+ export default ThemeProvider;
52
+ export const ThemeContext = React.createContext(undefined);
53
+ const Root = React.forwardRef((props, forwardedRef) => {
54
+ var _a, _b, _c;
55
+ const { theme, children, themeOptions, as: Element = 'div', className, ...rest } = props;
56
+ const ref = React.useRef(null);
57
+ const mergedRefs = useMergedRefs(ref, forwardedRef);
37
58
  const prefersDark = useMediaQuery('(prefers-color-scheme: dark)');
38
59
  const prefersHighContrast = useMediaQuery('(prefers-contrast: more)');
39
60
  const shouldApplyDark = theme === 'dark' || (theme === 'os' && prefersDark);
40
61
  const shouldApplyHC = (_a = themeOptions === null || themeOptions === void 0 ? void 0 : themeOptions.highContrast) !== null && _a !== void 0 ? _a : prefersHighContrast;
41
- // only provide context if wrapped around children
42
- return hasChildren ? (React.createElement(ThemeContext.Provider, { value: { theme, themeOptions, rootRef } },
43
- React.createElement(Element, { className: cx('iui-root', className), "data-iui-theme": shouldApplyDark ? 'dark' : 'light', "data-iui-contrast": shouldApplyHC ? 'high' : 'default', ref: mergedRefs, ...rest }, children))) : (
44
- // otherwise just apply theme on the root using this wrapper component
45
- React.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 }));
62
+ const isThemeAlreadySet = useIsThemeAlreadySet((_b = ref.current) === null || _b === void 0 ? void 0 : _b.ownerDocument);
63
+ const shouldApplyBackground = (_c = themeOptions === null || themeOptions === void 0 ? void 0 : themeOptions.applyBackground) !== null && _c !== void 0 ? _c : !isThemeAlreadySet.current;
64
+ return (React.createElement(Element, { className: cx('iui-root', { 'iui-root-background': shouldApplyBackground }, className), "data-iui-theme": shouldApplyDark ? 'dark' : 'light', "data-iui-contrast": shouldApplyHC ? 'high' : 'default', ref: mergedRefs, ...rest }, children));
46
65
  });
47
- export default ThemeProvider;
48
- export const ThemeContext = React.createContext(undefined);
49
66
  const ThemeLogicWrapper = ({ theme, themeOptions }) => {
50
67
  useTheme(theme, themeOptions);
51
68
  return React.createElement(React.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';
@@ -13,3 +13,4 @@ export * from './useMediaQuery';
13
13
  export * from './useSafeContext';
14
14
  export * from './useLatestRef';
15
15
  export * from './useIsomorphicLayoutEffect';
16
+ export * from './useIsThemeAlreadySet';
@@ -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,27 @@
1
+ /*---------------------------------------------------------------------------------------------
2
+ * Copyright (c) Bentley Systems, Incorporated. All rights reserved.
3
+ * See LICENSE.md in the project root for license terms and full copyright notice.
4
+ *--------------------------------------------------------------------------------------------*/
5
+ import React from 'react';
6
+ import { ThemeContext } from '../../ThemeProvider/ThemeProvider';
7
+ import { useIsomorphicLayoutEffect } from './useIsomorphicLayoutEffect';
8
+ import { getDocument } from '../functions';
9
+ /**
10
+ * Hook that returns a boolean ref which is true if either:
11
+ * - There is a parent `ThemeProvider` in the tree, or
12
+ * - The <body> element has data-iui-theme attribute
13
+ */
14
+ export const useIsThemeAlreadySet = (ownerDocument = getDocument()) => {
15
+ const parentContext = React.useContext(ThemeContext);
16
+ const isThemeAlreadySet = React.useRef(!!parentContext || !!(ownerDocument === null || ownerDocument === void 0 ? void 0 : ownerDocument.body.dataset.iuiTheme));
17
+ useIsomorphicLayoutEffect(() => {
18
+ if (parentContext ||
19
+ (ownerDocument && !!ownerDocument.body.dataset.iuiTheme)) {
20
+ isThemeAlreadySet.current = true;
21
+ }
22
+ return () => {
23
+ isThemeAlreadySet.current = false;
24
+ };
25
+ }, [parentContext, ownerDocument]);
26
+ return isThemeAlreadySet;
27
+ };
@@ -2,10 +2,9 @@
2
2
  * Copyright (c) Bentley Systems, Incorporated. All rights reserved.
3
3
  * See LICENSE.md in the project root for license terms and full copyright notice.
4
4
  *--------------------------------------------------------------------------------------------*/
5
- import React from 'react';
6
- import { ThemeContext } from '../../ThemeProvider/ThemeProvider';
7
5
  import { getDocument, getWindow } from '../functions';
8
6
  import { useIsomorphicLayoutEffect } from './useIsomorphicLayoutEffect';
7
+ import { useIsThemeAlreadySet } from './useIsThemeAlreadySet';
9
8
  import '@itwin/itwinui-css/css/global.css';
10
9
  import '@itwin/itwinui-variables/index.css';
11
10
  /**
@@ -19,11 +18,10 @@ import '@itwin/itwinui-variables/index.css';
19
18
  */
20
19
  export const useTheme = (theme, themeOptions) => {
21
20
  var _a;
22
- const themeContext = React.useContext(ThemeContext);
23
21
  const ownerDocument = (_a = themeOptions === null || themeOptions === void 0 ? void 0 : themeOptions.ownerDocument) !== null && _a !== void 0 ? _a : getDocument();
22
+ const isThemeAlreadySet = useIsThemeAlreadySet(ownerDocument);
24
23
  useIsomorphicLayoutEffect(() => {
25
- // exit early if theme was already set by provider or is present on <body>
26
- if (themeContext || !ownerDocument || ownerDocument.body.dataset.iuiTheme) {
24
+ if (!ownerDocument || isThemeAlreadySet.current) {
27
25
  return;
28
26
  }
29
27
  ownerDocument.body.classList.toggle('iui-root', true);
@@ -41,7 +39,7 @@ export const useTheme = (theme, themeOptions) => {
41
39
  return;
42
40
  }
43
41
  }
44
- }, [theme, themeContext, themeOptions === null || themeOptions === void 0 ? void 0 : themeOptions.highContrast, ownerDocument]);
42
+ }, [theme, themeOptions === null || themeOptions === void 0 ? void 0 : themeOptions.highContrast, ownerDocument]);
45
43
  };
46
44
  /**
47
45
  * Helper function to apply the specified theme, or detect the OS theme.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@itwin/itwinui-react",
3
- "version": "2.0.4",
3
+ "version": "2.1.1",
4
4
  "author": "Bentley Systems",
5
5
  "license": "MIT",
6
6
  "main": "cjs/index.js",
@@ -47,8 +47,8 @@
47
47
  ],
48
48
  "scripts": {
49
49
  "build": "yarn clean:build && tsc -p tsconfig.cjs.json && tsc -p tsconfig.esm.json",
50
- "build:watch": "yarn clean:build && concurrently \"tsc -p tsconfig.cjs.json --watch\" \"tsc -p tsconfig.esm.json --watch\"",
51
- "clean:build": "yarn clean:coverage && rimraf esm && rimraf cjs",
50
+ "build:watch": "concurrently \"tsc -p tsconfig.cjs.json --watch\" \"tsc -p tsconfig.esm.json --watch\"",
51
+ "clean:build": "rimraf esm && rimraf cjs",
52
52
  "clean:coverage": "rimraf coverage",
53
53
  "clean": "rimraf .turbo && yarn clean:coverage && yarn clean:build && rimraf node_modules",
54
54
  "test": "jest",
@@ -56,11 +56,14 @@
56
56
  "format": "prettier --config .prettierrc **/*.{tsx,ts,js} --ignore-path .gitignore --write",
57
57
  "lint": "eslint \"**/*.{js,ts,tsx}\" --max-warnings=0",
58
58
  "lint:fix": "yarn lint --fix && node ../configs/copyrightLinter.js --fix \"*/**/*.{js,ts,tsx}\"",
59
- "dev": "yarn build:watch",
59
+ "dev": "yarn clean:build && concurrently \"yarn dev:esm\" \"yarn dev:cjs\" \"yarn dev:types\"",
60
+ "dev:esm": "swc src -d esm --watch",
61
+ "dev:cjs": "swc src -d cjs --watch -C module.type=commonjs",
62
+ "dev:types": "concurrently \"tsc -p tsconfig.cjs.json --emitDeclarationOnly --watch --preserveWatchOutput\" \"tsc -p tsconfig.esm.json --emitDeclarationOnly --watch --preserveWatchOutput\"",
60
63
  "createComponent": "node ../../scripts/createComponent.js"
61
64
  },
62
65
  "dependencies": {
63
- "@itwin/itwinui-css": "^1.1.0",
66
+ "@itwin/itwinui-css": "^1.2.0",
64
67
  "@itwin/itwinui-illustrations-react": "^2.0.0",
65
68
  "@itwin/itwinui-variables": "^1.0.0",
66
69
  "@tippyjs/react": "^4.2.6",
@@ -72,6 +75,8 @@
72
75
  },
73
76
  "devDependencies": {
74
77
  "@babel/core": "^7.12.10",
78
+ "@swc/cli": "^0.1.57",
79
+ "@swc/core": "^1.3.21",
75
80
  "@testing-library/jest-dom": "^5.16.4",
76
81
  "@testing-library/react": "^13.2.0",
77
82
  "@testing-library/user-event": "^14.1.1",