@itwin/itwinui-react 3.9.1 → 3.10.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 (65) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/cjs/core/Breadcrumbs/Breadcrumbs.js +2 -3
  3. package/cjs/core/Buttons/Button.js +1 -1
  4. package/cjs/core/Buttons/IconButton.js +1 -1
  5. package/cjs/core/Buttons/IdeasButton.js +6 -2
  6. package/cjs/core/ComboBox/ComboBox.js +1 -1
  7. package/cjs/core/DropdownMenu/DropdownMenu.js +36 -13
  8. package/cjs/core/Input/Input.js +1 -1
  9. package/cjs/core/LabeledSelect/LabeledSelect.d.ts +26 -4
  10. package/cjs/core/Menu/Menu.js +9 -0
  11. package/cjs/core/Menu/MenuItem.d.ts +12 -0
  12. package/cjs/core/Menu/MenuItem.js +114 -66
  13. package/cjs/core/NotificationMarker/NotificationMarker.d.ts +7 -6
  14. package/cjs/core/Popover/Popover.d.ts +32 -9
  15. package/cjs/core/Popover/Popover.js +68 -17
  16. package/cjs/core/Select/Select.js +2 -3
  17. package/cjs/core/SideNavigation/SideNavigation.d.ts +1 -0
  18. package/cjs/core/SideNavigation/SideNavigation.js +20 -21
  19. package/cjs/core/SideNavigation/SidenavButton.js +5 -1
  20. package/cjs/core/Table/TablePaginator.js +1 -3
  21. package/cjs/core/Table/columns/selectionColumn.js +10 -1
  22. package/cjs/core/Table/hooks/useSubRowSelection.js +1 -1
  23. package/cjs/core/ThemeProvider/ThemeProvider.js +53 -17
  24. package/cjs/core/TimePicker/TimePicker.js +12 -12
  25. package/cjs/core/ToggleSwitch/ToggleSwitch.d.ts +4 -0
  26. package/cjs/core/ToggleSwitch/ToggleSwitch.js +2 -2
  27. package/cjs/utils/components/Portal.d.ts +6 -2
  28. package/cjs/utils/components/Portal.js +11 -14
  29. package/cjs/utils/providers/ScopeProvider.d.ts +26 -0
  30. package/cjs/utils/providers/ScopeProvider.js +77 -0
  31. package/cjs/utils/providers/index.d.ts +1 -0
  32. package/cjs/utils/providers/index.js +1 -0
  33. package/esm/core/Breadcrumbs/Breadcrumbs.js +2 -3
  34. package/esm/core/Buttons/Button.js +1 -1
  35. package/esm/core/Buttons/IconButton.js +1 -1
  36. package/esm/core/Buttons/IdeasButton.js +3 -2
  37. package/esm/core/ComboBox/ComboBox.js +1 -1
  38. package/esm/core/DropdownMenu/DropdownMenu.js +36 -13
  39. package/esm/core/Input/Input.js +1 -1
  40. package/esm/core/LabeledSelect/LabeledSelect.d.ts +26 -4
  41. package/esm/core/Menu/Menu.js +9 -0
  42. package/esm/core/Menu/MenuItem.d.ts +12 -0
  43. package/esm/core/Menu/MenuItem.js +114 -66
  44. package/esm/core/NotificationMarker/NotificationMarker.d.ts +7 -6
  45. package/esm/core/Popover/Popover.d.ts +32 -9
  46. package/esm/core/Popover/Popover.js +71 -20
  47. package/esm/core/Select/Select.js +2 -3
  48. package/esm/core/SideNavigation/SideNavigation.d.ts +1 -0
  49. package/esm/core/SideNavigation/SideNavigation.js +20 -21
  50. package/esm/core/SideNavigation/SidenavButton.js +5 -1
  51. package/esm/core/Table/TablePaginator.js +2 -4
  52. package/esm/core/Table/columns/selectionColumn.js +10 -1
  53. package/esm/core/Table/hooks/useSubRowSelection.js +1 -1
  54. package/esm/core/ThemeProvider/ThemeProvider.js +54 -18
  55. package/esm/core/TimePicker/TimePicker.js +12 -12
  56. package/esm/core/ToggleSwitch/ToggleSwitch.d.ts +4 -0
  57. package/esm/core/ToggleSwitch/ToggleSwitch.js +2 -2
  58. package/esm/utils/components/Portal.d.ts +6 -2
  59. package/esm/utils/components/Portal.js +9 -8
  60. package/esm/utils/providers/ScopeProvider.d.ts +26 -0
  61. package/esm/utils/providers/ScopeProvider.js +48 -0
  62. package/esm/utils/providers/index.d.ts +1 -0
  63. package/esm/utils/providers/index.js +1 -0
  64. package/package.json +2 -1
  65. package/styles.css +1 -1
@@ -1,5 +1,5 @@
1
1
  import * as React from 'react';
2
- import type { Placement } from '@floating-ui/react';
2
+ import type { Placement, UseListNavigationProps, ReferenceType, UseFloatingOptions, UseHoverProps, UseClickProps, UseFocusProps, UseDismissProps } from '@floating-ui/react';
3
3
  import type { PolymorphicForwardRefComponent } from '../../utils/index.js';
4
4
  import type { PortalProps } from '../../utils/components/Portal.js';
5
5
  type PopoverOptions = {
@@ -59,16 +59,39 @@ type PopoverInternalProps = {
59
59
  layoutShift?: boolean;
60
60
  };
61
61
  /**
62
- * By default, the popover will only open on click.
63
- * `hover` and `focus` can be manually specified as triggers.
62
+ * By default, only the click and dismiss interactions/triggers are enabled.
63
+ * Explicitly pass `false` to disable the defaults.
64
+ *
65
+ * Pass a boolean to enable/disable any of the supported interactions.
66
+ * Alternatively, pass an object to override the default props that the Popover sets for an interaction/trigger.
67
+ *
68
+ * When additional parameters are _required_ for an interaction/trigger, an object must be passed to enable it.
69
+ * Booleans will not be allowed in this case.
70
+ *
71
+ * @example
72
+ * <Popover
73
+ * interactions={{
74
+ * click: false,
75
+ * focus: true,
76
+ * hover: { move: false },
77
+ * listNavigation: { … },
78
+ * }}
79
+ * // …
80
+ * >…</Popover>
64
81
  */
65
- trigger?: Partial<Record<'hover' | 'click' | 'focus', boolean>>;
82
+ interactions?: {
83
+ click?: boolean | UseClickProps;
84
+ dismiss?: boolean | UseDismissProps;
85
+ hover?: boolean | UseHoverProps<ReferenceType>;
86
+ focus?: boolean | UseFocusProps;
87
+ listNavigation?: UseListNavigationProps;
88
+ };
66
89
  role?: 'dialog' | 'menu' | 'listbox';
67
90
  /**
68
91
  * Whether the popover should match the width of the trigger.
69
92
  */
70
93
  matchWidth?: boolean;
71
- };
94
+ } & Omit<UseFloatingOptions, 'middleware' | 'placement'>;
72
95
  export declare const usePopover: (options: PopoverOptions & PopoverInternalProps) => {
73
96
  placement: Placement;
74
97
  strategy: import("@floating-ui/utils").Strategy;
@@ -83,11 +106,11 @@ export declare const usePopover: (options: PopoverOptions & PopoverInternalProps
83
106
  floating: React.MutableRefObject<HTMLElement | null>;
84
107
  setReference: (node: import("@floating-ui/react-dom").ReferenceType | null) => void;
85
108
  setFloating: (node: HTMLElement | null) => void;
86
- } & import("@floating-ui/react").ExtendedRefs<import("@floating-ui/react").ReferenceType>;
109
+ } & import("@floating-ui/react").ExtendedRefs<ReferenceType>;
87
110
  elements: {
88
111
  reference: import("@floating-ui/react-dom").ReferenceType | null;
89
112
  floating: HTMLElement | null;
90
- } & import("@floating-ui/react").ExtendedElements<import("@floating-ui/react").ReferenceType>;
113
+ } & import("@floating-ui/react").ExtendedElements<ReferenceType>;
91
114
  context: {
92
115
  x: number;
93
116
  y: number;
@@ -103,8 +126,8 @@ export declare const usePopover: (options: PopoverOptions & PopoverInternalProps
103
126
  dataRef: React.MutableRefObject<import("@floating-ui/react").ContextData>;
104
127
  nodeId: string | undefined;
105
128
  floatingId: string;
106
- refs: import("@floating-ui/react").ExtendedRefs<import("@floating-ui/react").ReferenceType>;
107
- elements: import("@floating-ui/react").ExtendedElements<import("@floating-ui/react").ReferenceType>;
129
+ refs: import("@floating-ui/react").ExtendedRefs<ReferenceType>;
130
+ elements: import("@floating-ui/react").ExtendedElements<ReferenceType>;
108
131
  };
109
132
  getFloatingProps: (userProps?: React.HTMLProps<HTMLElement>) => Record<string, unknown>;
110
133
  getReferenceProps: (userProps?: React.HTMLProps<Element> | undefined) => Record<string, unknown>;
@@ -39,15 +39,29 @@ const Portal_js_1 = require("../../utils/components/Portal.js");
39
39
  const ThemeProvider_js_1 = require("../ThemeProvider/ThemeProvider.js");
40
40
  // ----------------------------------------------------------------------------
41
41
  const usePopover = (options) => {
42
- const { placement = 'bottom-start', visible, onVisibleChange, closeOnOutsideClick, autoUpdateOptions, matchWidth, trigger = { click: true, hover: false, focus: false }, role, } = options;
43
- const middleware = { flip: true, shift: true, ...options.middleware };
42
+ const { placement = 'bottom-start', visible, onVisibleChange, closeOnOutsideClick, autoUpdateOptions, matchWidth, interactions: interactionsProp, role, ...rest } = options;
43
+ const mergedInteractions = {
44
+ ...{
45
+ click: true,
46
+ dismiss: true,
47
+ hover: false,
48
+ focus: false,
49
+ listNavigation: undefined,
50
+ },
51
+ ...interactionsProp,
52
+ };
53
+ const tree = (0, react_1.useFloatingTree)();
54
+ const middleware = React.useMemo(() => ({ flip: true, shift: true, ...options.middleware }), [options.middleware]);
44
55
  const [open, onOpenChange] = (0, index_js_1.useControlledState)(false, visible, onVisibleChange);
45
56
  const floating = (0, react_1.useFloating)({
46
57
  placement,
47
58
  open,
48
59
  onOpenChange,
49
- whileElementsMounted: (...args) => (0, react_1.autoUpdate)(...args, autoUpdateOptions),
50
- middleware: [
60
+ whileElementsMounted: React.useMemo(() =>
61
+ // autoUpdate is expensive and should only be called when the popover is open
62
+ open ? (...args) => (0, react_1.autoUpdate)(...args, autoUpdateOptions) : undefined, [autoUpdateOptions, open]),
63
+ ...rest,
64
+ middleware: React.useMemo(() => [
51
65
  middleware.offset !== undefined && (0, react_1.offset)(middleware.offset),
52
66
  middleware.flip && (0, react_1.flip)(),
53
67
  middleware.shift && (0, react_1.shift)(),
@@ -60,18 +74,38 @@ const usePopover = (options) => {
60
74
  middleware.autoPlacement && (0, react_1.autoPlacement)(),
61
75
  middleware.inline && (0, react_1.inline)(),
62
76
  middleware.hide && (0, react_1.hide)(),
63
- ].filter(Boolean),
77
+ ].filter(Boolean), [matchWidth, middleware]),
64
78
  });
65
79
  const interactions = (0, react_1.useInteractions)([
66
- (0, react_1.useClick)(floating.context, { enabled: !!trigger.click }),
67
- (0, react_1.useDismiss)(floating.context, { outsidePress: closeOnOutsideClick }),
80
+ (0, react_1.useClick)(floating.context, {
81
+ enabled: !!mergedInteractions.click,
82
+ ...mergedInteractions.click,
83
+ }),
84
+ (0, react_1.useDismiss)(floating.context, {
85
+ enabled: !!mergedInteractions.dismiss,
86
+ outsidePress: closeOnOutsideClick,
87
+ bubbles: tree != null,
88
+ ...mergedInteractions.dismiss,
89
+ }),
68
90
  (0, react_1.useHover)(floating.context, {
69
- enabled: !!trigger.hover,
91
+ enabled: !!mergedInteractions.hover,
70
92
  delay: 100,
71
- handleClose: (0, react_1.safePolygon)({ buffer: 1 }),
93
+ handleClose: (0, react_1.safePolygon)({
94
+ buffer: 1,
95
+ blockPointerEvents: true,
96
+ }),
97
+ move: false,
98
+ ...mergedInteractions.hover,
99
+ }),
100
+ (0, react_1.useFocus)(floating.context, {
101
+ enabled: !!mergedInteractions.focus,
102
+ ...mergedInteractions.focus,
72
103
  }),
73
- (0, react_1.useFocus)(floating.context, { enabled: !!trigger.focus }),
74
104
  (0, react_1.useRole)(floating.context, { role: 'dialog', enabled: !!role }),
105
+ (0, react_1.useListNavigation)(floating.context, {
106
+ enabled: !!mergedInteractions.listNavigation,
107
+ ...mergedInteractions.listNavigation,
108
+ }),
75
109
  ]);
76
110
  const [referenceWidth, setReferenceWidth] = React.useState();
77
111
  const getFloatingProps = React.useCallback((userProps) => interactions.getFloatingProps({
@@ -144,11 +178,28 @@ exports.Popover = React.forwardRef((props, forwardedRef) => {
144
178
  ...popover.getReferenceProps(children.props),
145
179
  ref: popover.refs.setReference,
146
180
  })),
147
- popover.open ? (React.createElement(Portal_js_1.Portal, { portal: portal },
148
- React.createElement(react_1.FloatingPortal, null,
149
- React.createElement(ThemeProvider_js_1.ThemeProvider, { portalContainer: popoverElement },
150
- React.createElement(react_1.FloatingFocusManager, { context: popover.context, modal: false, initialFocus: popover.refs.floating },
151
- React.createElement(index_js_1.Box, { className: (0, classnames_1.default)({ 'iui-popover-surface': applyBackground }, className), "aria-labelledby": !hasAriaLabel
152
- ? popover.refs.domReference.current?.id
153
- : undefined, ...popover.getFloatingProps(rest), ref: popoverRef }, content)))))) : null));
181
+ popover.open ? (React.createElement(PopoverPortal, { portal: portal },
182
+ React.createElement(ThemeProvider_js_1.ThemeProvider, { portalContainer: popoverElement },
183
+ React.createElement(DisplayContents, null),
184
+ React.createElement(react_1.FloatingFocusManager, { context: popover.context, modal: false, initialFocus: popover.refs.floating },
185
+ React.createElement(index_js_1.Box, { className: (0, classnames_1.default)({ 'iui-popover-surface': applyBackground }, className), "aria-labelledby": !hasAriaLabel
186
+ ? popover.refs.domReference.current?.id
187
+ : undefined, ...popover.getFloatingProps(rest), ref: popoverRef }, content))))) : null));
188
+ });
189
+ // ----------------------------------------------------------------------------
190
+ const PopoverPortal = ({ children, portal = true, }) => {
191
+ const portalTo = (0, Portal_js_1.usePortalTo)(portal);
192
+ return (React.createElement(react_1.FloatingPortal, { root: portalTo },
193
+ React.createElement(DisplayContents, null),
194
+ children));
195
+ };
196
+ // ----------------------------------------------------------------------------
197
+ /** Applies `display: contents` to the parent div. */
198
+ const DisplayContents = React.memo(() => {
199
+ return (React.createElement(index_js_1.ShadowRoot, { css: `
200
+ :host {
201
+ display: contents;
202
+ }
203
+ ` },
204
+ React.createElement("slot", null)));
154
205
  });
@@ -228,9 +228,8 @@ const CustomSelect = React.forwardRef((props, forwardedRef) => {
228
228
  });
229
229
  return (React.createElement(React.Fragment, null,
230
230
  React.createElement(index_js_1.InputWithIcon, { ...rest, ref: (0, index_js_1.useMergedRefs)(popover.refs.setPositionReference, forwardedRef) },
231
- 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`, styleType: styleType, ...triggerProps, ref: (0, index_js_1.useMergedRefs)(selectRef, triggerProps?.ref, popover.refs.setReference), className: (0, classnames_1.default)({
231
+ React.createElement(SelectButton, { ...popover.getReferenceProps(), tabIndex: 0, role: 'combobox', size: size, status: status, "aria-disabled": disabled ? 'true' : undefined, "data-iui-disabled": disabled ? 'true' : undefined, "aria-autocomplete": 'none', "aria-expanded": isOpen, "aria-haspopup": 'listbox', "aria-controls": `${uid}-menu`, styleType: styleType, ...triggerProps, ref: (0, index_js_1.useMergedRefs)(selectRef, triggerProps?.ref, popover.refs.setReference), className: (0, classnames_1.default)({
232
232
  'iui-placeholder': (!selectedItems || selectedItems.length === 0) && !!placeholder,
233
- 'iui-disabled': disabled,
234
233
  }, triggerProps?.className) },
235
234
  (!selectedItems || selectedItems.length === 0) && (React.createElement(index_js_1.Box, { as: 'span', className: 'iui-content' }, placeholder)),
236
235
  isMultipleEnabled(selectedItems, multiple) ? (React.createElement(MultipleSelectButton, { selectedItems: selectedItems, selectedItemsRenderer: selectedItemRenderer, tagRenderer: tagRenderer })) : (React.createElement(SingleSelectButton, { selectedItem: selectedItems, selectedItemRenderer: selectedItemRenderer }))),
@@ -258,7 +257,7 @@ const isSingleOnChange = (onChange, multiple) => {
258
257
  // ----------------------------------------------------------------------------
259
258
  const SelectButton = React.forwardRef((props, forwardedRef) => {
260
259
  const { size, status, styleType = 'default', ...rest } = props;
261
- return (React.createElement(index_js_1.Box, { "data-iui-size": size, "data-iui-status": status, "data-iui-variant": styleType !== 'default' ? styleType : undefined, ...rest, ref: forwardedRef, className: (0, classnames_1.default)('iui-select-button', props.className) }));
260
+ return (React.createElement(index_js_1.Box, { "data-iui-size": size, "data-iui-status": status, "data-iui-variant": styleType !== 'default' ? styleType : undefined, ...rest, ref: forwardedRef, className: (0, classnames_1.default)('iui-select-button', 'iui-field', props.className) }));
262
261
  });
263
262
  // ----------------------------------------------------------------------------
264
263
  const SelectEndIcon = React.forwardRef((props, forwardedRef) => {
@@ -1,5 +1,6 @@
1
1
  import * as React from 'react';
2
2
  import type { PolymorphicForwardRefComponent } from '../../utils/index.js';
3
+ export declare const SidenavExpandedContext: React.Context<boolean | undefined>;
3
4
  type SideNavigationProps = {
4
5
  /**
5
6
  * Buttons shown in the top portion of sidenav.
@@ -26,7 +26,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
26
26
  return (mod && mod.__esModule) ? mod : { "default": mod };
27
27
  };
28
28
  Object.defineProperty(exports, "__esModule", { value: true });
29
- exports.SideNavigation = void 0;
29
+ exports.SideNavigation = exports.SidenavExpandedContext = void 0;
30
30
  /*---------------------------------------------------------------------------------------------
31
31
  * Copyright (c) Bentley Systems, Incorporated. All rights reserved.
32
32
  * See LICENSE.md in the project root for license terms and full copyright notice.
@@ -35,7 +35,8 @@ const React = __importStar(require("react"));
35
35
  const classnames_1 = __importDefault(require("classnames"));
36
36
  const index_js_1 = require("../../utils/index.js");
37
37
  const IconButton_js_1 = require("../Buttons/IconButton.js");
38
- const Tooltip_js_1 = require("../Tooltip/Tooltip.js");
38
+ // ----------------------------------------------------------------------------
39
+ exports.SidenavExpandedContext = React.createContext(undefined);
39
40
  /**
40
41
  * Left side navigation menu component.
41
42
  * @example
@@ -51,25 +52,23 @@ const Tooltip_js_1 = require("../Tooltip/Tooltip.js");
51
52
  * />
52
53
  */
53
54
  exports.SideNavigation = React.forwardRef((props, forwardedRef) => {
54
- const { items, secondaryItems, expanderPlacement = 'top', className, isExpanded = false, onExpanderClick, submenu, isSubmenuOpen = false, wrapperProps, contentProps, topProps, bottomProps, ...rest } = props;
55
- const [_isExpanded, _setIsExpanded] = React.useState(isExpanded);
56
- React.useEffect(() => {
57
- _setIsExpanded(isExpanded);
58
- }, [isExpanded]);
59
- const ExpandButton = (React.createElement(IconButton_js_1.IconButton, { label: 'Toggle icon labels', "aria-expanded": _isExpanded, className: 'iui-sidenav-button iui-expand', onClick: React.useCallback(() => {
60
- _setIsExpanded((expanded) => !expanded);
55
+ const { items, secondaryItems, expanderPlacement = 'top', className, isExpanded: isExpandedProp, onExpanderClick, submenu, isSubmenuOpen = false, wrapperProps, contentProps, topProps, bottomProps, ...rest } = props;
56
+ const [isExpanded, setIsExpanded] = (0, index_js_1.useControlledState)(false, isExpandedProp);
57
+ const ExpandButton = (React.createElement(IconButton_js_1.IconButton, { label: 'Toggle icon labels', "aria-expanded": isExpanded, className: 'iui-sidenav-button iui-expand', size: 'small', onClick: React.useCallback(() => {
58
+ setIsExpanded((expanded) => !expanded);
61
59
  onExpanderClick?.();
62
- }, [onExpanderClick]) },
60
+ }, [onExpanderClick, setIsExpanded]) },
63
61
  React.createElement(index_js_1.SvgChevronRight, null)));
64
- return (React.createElement(index_js_1.Box, { ...wrapperProps, className: (0, classnames_1.default)('iui-side-navigation-wrapper', wrapperProps?.className), ref: forwardedRef },
65
- React.createElement(index_js_1.Box, { as: 'div', className: (0, classnames_1.default)('iui-side-navigation', {
66
- 'iui-expanded': _isExpanded,
67
- 'iui-collapsed': !_isExpanded,
68
- }, className), ...rest },
69
- expanderPlacement === 'top' && ExpandButton,
70
- React.createElement(index_js_1.Box, { as: 'div', ...contentProps, className: (0, classnames_1.default)('iui-sidenav-content', contentProps?.className) },
71
- React.createElement(index_js_1.Box, { as: 'div', ...topProps, className: (0, classnames_1.default)('iui-top', topProps?.className) }, items.map((sidenavButton, index) => !_isExpanded ? (React.createElement(Tooltip_js_1.Tooltip, { content: sidenavButton.props.children, placement: 'right', key: index }, sidenavButton)) : (sidenavButton))),
72
- React.createElement(index_js_1.Box, { as: 'div', ...bottomProps, className: (0, classnames_1.default)('iui-bottom', bottomProps?.className) }, secondaryItems?.map((sidenavButton, index) => !_isExpanded ? (React.createElement(Tooltip_js_1.Tooltip, { content: sidenavButton.props.children, placement: 'right', key: index }, sidenavButton)) : (sidenavButton)))),
73
- expanderPlacement === 'bottom' && ExpandButton),
74
- submenu && (React.createElement(index_js_1.WithCSSTransition, { in: isSubmenuOpen, dimension: 'width', timeout: 200, classNames: 'iui' }, submenu))));
62
+ return (React.createElement(exports.SidenavExpandedContext.Provider, { value: isExpanded },
63
+ React.createElement(index_js_1.Box, { ...wrapperProps, className: (0, classnames_1.default)('iui-side-navigation-wrapper', wrapperProps?.className), ref: forwardedRef },
64
+ React.createElement(index_js_1.Box, { as: 'div', className: (0, classnames_1.default)('iui-side-navigation', {
65
+ 'iui-expanded': isExpanded,
66
+ 'iui-collapsed': !isExpanded,
67
+ }, className), ...rest },
68
+ expanderPlacement === 'top' && ExpandButton,
69
+ React.createElement(index_js_1.Box, { as: 'div', ...contentProps, className: (0, classnames_1.default)('iui-sidenav-content', contentProps?.className) },
70
+ React.createElement(index_js_1.Box, { as: 'div', ...topProps, className: (0, classnames_1.default)('iui-top', topProps?.className) }, items),
71
+ React.createElement(index_js_1.Box, { as: 'div', ...bottomProps, className: (0, classnames_1.default)('iui-bottom', bottomProps?.className) }, secondaryItems)),
72
+ expanderPlacement === 'bottom' && ExpandButton),
73
+ submenu && (React.createElement(index_js_1.WithCSSTransition, { in: isSubmenuOpen, dimension: 'width', timeout: 200, classNames: 'iui' }, submenu)))));
75
74
  });
@@ -34,11 +34,15 @@ exports.SidenavButton = void 0;
34
34
  const classnames_1 = __importDefault(require("classnames"));
35
35
  const React = __importStar(require("react"));
36
36
  const Button_js_1 = require("../Buttons/Button.js");
37
+ const Tooltip_js_1 = require("../Tooltip/Tooltip.js");
38
+ const SideNavigation_js_1 = require("./SideNavigation.js");
37
39
  /**
38
40
  * Wrapper around Button to be used as SideNavigation items.
39
41
  * Label is hidden when sidenav is collapsed.
40
42
  */
41
43
  exports.SidenavButton = React.forwardRef((props, ref) => {
42
44
  const { className, children, isActive = false, disabled = false, isSubmenuOpen = false, ...rest } = props;
43
- return (React.createElement(Button_js_1.Button, { className: (0, classnames_1.default)('iui-sidenav-button', { 'iui-submenu-open': isSubmenuOpen }, className), "data-iui-active": isActive, size: 'large', disabled: disabled, ref: ref, ...rest }, children));
45
+ const isExpanded = React.useContext(SideNavigation_js_1.SidenavExpandedContext) === true;
46
+ const sidenavButton = (React.createElement(Button_js_1.Button, { className: (0, classnames_1.default)('iui-sidenav-button', { 'iui-submenu-open': isSubmenuOpen }, className), "data-iui-active": isActive, size: 'large', disabled: disabled, ref: ref, ...rest }, children));
47
+ return !isExpanded ? (React.createElement(Tooltip_js_1.Tooltip, { content: children, placement: 'right', ariaStrategy: 'none' }, sidenavButton)) : (sidenavButton);
44
48
  });
@@ -81,9 +81,7 @@ const TablePaginator = (props) => {
81
81
  isMounted.current = true;
82
82
  }, [focusedIndex]);
83
83
  const buttonSize = size != 'default' ? 'small' : undefined;
84
- const pageButton = React.useCallback((index, tabIndex = index === focusedIndex ? 0 : -1) => (React.createElement(index_js_1.ButtonBase, { key: index, className: (0, classnames_1.default)('iui-table-paginator-page-button', {
85
- 'iui-table-paginator-page-button-small': buttonSize === 'small',
86
- }), "data-iui-active": index === currentPage, onClick: () => onPageChange(index), "aria-current": index === currentPage, "aria-label": localization.goToPageLabel(index + 1), tabIndex: tabIndex }, index + 1)), [focusedIndex, currentPage, localization, buttonSize, onPageChange]);
84
+ const pageButton = React.useCallback((index, tabIndex = index === focusedIndex ? 0 : -1) => (React.createElement(Button_js_1.Button, { key: index, className: 'iui-table-paginator-page-button', styleType: 'borderless', size: buttonSize, "data-iui-active": index === currentPage, onClick: () => onPageChange(index), "aria-current": index === currentPage, "aria-label": localization.goToPageLabel(index + 1), tabIndex: tabIndex }, index + 1)), [focusedIndex, currentPage, localization, buttonSize, onPageChange]);
87
85
  const totalPagesCount = Math.ceil(totalRowsCount / pageSize);
88
86
  const pageList = React.useMemo(() => new Array(totalPagesCount)
89
87
  .fill(null)
@@ -65,7 +65,16 @@ const SelectionColumn = (props = {}) => {
65
65
  , checked: checked && !disabled, indeterminate: indeterminate, disabled: disabled, onChange: () => toggleAllRowsSelected(!rows.some((row) => row.isSelected)) }));
66
66
  },
67
67
  Cell: ({ row }) => (React.createElement(Checkbox_js_1.Checkbox, { ...row.getToggleRowSelectedProps(), style: {}, title: '' // Removes default title that comes from react-table
68
- , disabled: isDisabled?.(row.original), onClick: (e) => e.stopPropagation() })),
68
+ , disabled: isDisabled?.(row.original), onClick: (e) => e.stopPropagation(), onChange: () => {
69
+ if (row.subRows.length > 0) {
70
+ //This code ignores any sub-rows that are not currently available(i.e disabled or filtered out).
71
+ //If all available sub-rows are selected, then it deselects them all, otherwise it selects them all.
72
+ row.toggleRowSelected(!row.subRows.every((subRow) => subRow.isSelected || isDisabled?.(subRow.original)));
73
+ }
74
+ else {
75
+ row.toggleRowSelected();
76
+ }
77
+ } })),
69
78
  cellRenderer: (props) => (React.createElement(index_js_1.DefaultCell, { ...props, isDisabled: (rowData) => !!isDisabled?.(rowData) })),
70
79
  };
71
80
  };
@@ -38,7 +38,7 @@ const useInstance = (instance) => {
38
38
  const selectedFlatRows = [];
39
39
  const setSelectionState = (row, selectedRowIds) => {
40
40
  let isSomeSubRowsSelected = false;
41
- row.subRows.forEach((subRow) => {
41
+ row.initialSubRows.forEach((subRow) => {
42
42
  setSelectionState(subRow, selectedRowIds);
43
43
  if (subRow.isSelected || subRow.isSomeSelected) {
44
44
  isSomeSubRowsSelected = true;
@@ -37,6 +37,10 @@ const classnames_1 = __importDefault(require("classnames"));
37
37
  const index_js_1 = require("../../utils/index.js");
38
38
  const ThemeContext_js_1 = require("./ThemeContext.js");
39
39
  const Toaster_js_1 = require("../Toast/Toaster.js");
40
+ const jotai_1 = require("jotai");
41
+ // ----------------------------------------------------------------------------
42
+ const ownerDocumentAtom = (0, jotai_1.atom)(undefined);
43
+ // ----------------------------------------------------------------------------
40
44
  /**
41
45
  * This component provides global state and applies theme to the entire tree
42
46
  * that it is wrapping around.
@@ -74,26 +78,21 @@ exports.ThemeProvider = React.forwardRef((props, forwardedRef) => {
74
78
  themeOptions.applyBackground ?? (themeOptions.applyBackground = !parent.theme);
75
79
  // default inherit highContrast option from parent if also inheriting base theme
76
80
  themeOptions.highContrast ?? (themeOptions.highContrast = themeProp === 'inherit' ? parent.highContrast : undefined);
77
- /**
78
- * We will portal our portal container into `portalContainer` prop (if specified),
79
- * or inherit `portalContainer` from context (if also inheriting theme).
80
- */
81
- const portaledPortalContainer = portalContainerProp ||
82
- (themeProp === 'inherit' ? parent.context?.portalContainer : undefined);
83
- const [portalContainer, setPortalContainer] = (0, index_js_1.useControlledState)(null, portaledPortalContainer);
84
- const contextValue = React.useMemo(() => ({ theme, themeOptions, portalContainer }),
81
+ const [portalContainerFromParent] = (0, index_js_1.useScopedAtom)(index_js_1.portalContainerAtom);
82
+ const contextValue = React.useMemo(() => ({ theme, themeOptions }),
85
83
  // we do include all dependencies below, but we want to stringify the objects as they could be different on each render
86
84
  // eslint-disable-next-line react-hooks/exhaustive-deps
87
- [theme, JSON.stringify(themeOptions), portalContainer]);
88
- return (React.createElement(index_js_1.HydrationProvider, null,
89
- React.createElement(ThemeContext_js_1.ThemeContext.Provider, { value: contextValue },
90
- includeCss && rootElement ? (React.createElement(FallbackStyles, { root: rootElement })) : null,
91
- React.createElement(Root, { theme: theme, themeOptions: themeOptions, ref: (0, index_js_1.useMergedRefs)(forwardedRef, setRootElement), ...rest },
85
+ [theme, JSON.stringify(themeOptions)]);
86
+ return (React.createElement(index_js_1.ScopeProvider, null,
87
+ React.createElement(index_js_1.HydrationProvider, null,
88
+ React.createElement(ThemeContext_js_1.ThemeContext.Provider, { value: contextValue },
92
89
  React.createElement(Toaster_js_1.ToastProvider, null,
93
- children,
94
- portaledPortalContainer ? (ReactDOM.createPortal(React.createElement(Toaster_js_1.Toaster, null), portaledPortalContainer)) : (React.createElement("div", { ref: setPortalContainer, style: { display: 'contents' } },
95
- React.createElement(Toaster_js_1.Toaster, null))))))));
90
+ includeCss && rootElement ? (React.createElement(FallbackStyles, { root: rootElement })) : null,
91
+ React.createElement(Root, { theme: theme, themeOptions: themeOptions, ref: (0, index_js_1.useMergedRefs)(forwardedRef, setRootElement), ...rest },
92
+ children,
93
+ React.createElement(PortalContainer, { portalContainerProp: portalContainerProp, portalContainerFromParent: portalContainerFromParent, isInheritingTheme: themeProp === 'inherit' })))))));
96
94
  });
95
+ exports.ThemeProvider.displayName = 'ThemeProvider';
97
96
  // ----------------------------------------------------------------------------
98
97
  const Root = React.forwardRef((props, forwardedRef) => {
99
98
  const { theme, children, themeOptions, className, ...rest } = props;
@@ -102,7 +101,10 @@ const Root = React.forwardRef((props, forwardedRef) => {
102
101
  const shouldApplyDark = theme === 'dark' || (theme === 'os' && prefersDark);
103
102
  const shouldApplyHC = themeOptions?.highContrast ?? prefersHighContrast;
104
103
  const shouldApplyBackground = themeOptions?.applyBackground;
105
- return (React.createElement(index_js_1.Box, { 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: forwardedRef, ...rest }, children));
104
+ const setOwnerDocument = (0, index_js_1.useScopedSetAtom)(ownerDocumentAtom);
105
+ return (React.createElement(index_js_1.Box, { 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: (0, index_js_1.useMergedRefs)(forwardedRef, (el) => {
106
+ setOwnerDocument(el?.ownerDocument);
107
+ }), ...rest }, children));
106
108
  });
107
109
  // ----------------------------------------------------------------------------
108
110
  /**
@@ -150,6 +152,40 @@ const useParentThemeAndContext = (rootElement) => {
150
152
  };
151
153
  };
152
154
  // ----------------------------------------------------------------------------
155
+ /**
156
+ * Creates a new portal container if necessary, or reuses the parent portal container.
157
+ *
158
+ * Updates `portalContainerAtom` with the correct portal container.
159
+ */
160
+ const PortalContainer = React.memo(({ portalContainerProp, portalContainerFromParent, isInheritingTheme, }) => {
161
+ const [ownerDocument] = (0, index_js_1.useScopedAtom)(ownerDocumentAtom);
162
+ const [portalContainer, setPortalContainer] = (0, index_js_1.useScopedAtom)(index_js_1.portalContainerAtom);
163
+ // bail if not hydrated, because portals don't work on server
164
+ const isHydrated = (0, index_js_1.useHydration)() === 'hydrated';
165
+ if (!isHydrated) {
166
+ return null;
167
+ }
168
+ // Create a new portal container only if necessary:
169
+ // - not inheriting theme
170
+ // - no parent portal container to portal into
171
+ // - parent portal container is in a different window (#2006)
172
+ if (!portalContainerProp && // bail if portalContainerProp is set, because it takes precedence
173
+ (!isInheritingTheme ||
174
+ !portalContainerFromParent ||
175
+ portalContainerFromParent.ownerDocument !== ownerDocument)) {
176
+ return (React.createElement("div", { style: { display: 'contents' }, ref: setPortalContainer },
177
+ React.createElement(Toaster_js_1.Toaster, null)));
178
+ }
179
+ const portalTarget = portalContainerProp || portalContainerFromParent;
180
+ // Synchronize atom with the correct portal container if necessary.
181
+ if (portalTarget && portalTarget !== portalContainer) {
182
+ setPortalContainer(portalTarget);
183
+ }
184
+ return portalTarget
185
+ ? ReactDOM.createPortal(React.createElement(Toaster_js_1.Toaster, null), portalTarget)
186
+ : null;
187
+ });
188
+ // ----------------------------------------------------------------------------
153
189
  /**
154
190
  * When `@itwin/itwinui-react/styles.css` is not imported, we will attempt to
155
191
  * dynamically import it (if possible) and fallback to loading it from a CDN.
@@ -236,16 +236,6 @@ exports.TimePicker = React.forwardRef((props, forwardedRef) => {
236
236
  const TimePickerColumn = (props) => {
237
237
  const { data, onFocusChange, onSelectChange, isSameFocused, isSameSelected, setFocus = false, valueRenderer, precision = 'minutes', className = 'iui-time', } = props;
238
238
  const needFocus = React.useRef(setFocus);
239
- // Used to focus row only when changed (keyboard navigation)
240
- // e.g. without this on every rerender it would be focused
241
- React.useEffect(() => {
242
- if (needFocus.current) {
243
- needFocus.current = false;
244
- }
245
- });
246
- const scrollIntoView = (ref, isSame) => {
247
- isSame && ref?.scrollIntoView({ block: 'nearest', inline: 'nearest' });
248
- };
249
239
  const handleTimeKeyDown = (event, maxValue, onFocus, onSelect, currentValue) => {
250
240
  if (event.altKey) {
251
241
  return;
@@ -283,8 +273,18 @@ const TimePickerColumn = (props) => {
283
273
  }, className: (0, classnames_1.default)({
284
274
  'iui-selected': isSameSelected(value),
285
275
  }), key: index, tabIndex: isSameFocus ? 0 : undefined, ref: (ref) => {
286
- scrollIntoView(ref, isSameFocus);
287
- needFocus.current && isSameFocus && ref?.focus();
276
+ if (!ref || !isSameFocus) {
277
+ return;
278
+ }
279
+ // Move focus/scroll in the next task, after the DOM has stabilized.
280
+ // This gives it priority over other conflicting logic (e.g. from floating-ui/Popover).
281
+ setTimeout(() => {
282
+ ref.scrollIntoView({ block: 'nearest', inline: 'nearest' });
283
+ if (needFocus.current) {
284
+ ref.focus();
285
+ needFocus.current = false;
286
+ }
287
+ });
288
288
  }, onClick: () => {
289
289
  onSelectChange(value);
290
290
  } }, valueRenderer(value, precision)));
@@ -5,6 +5,10 @@ type ToggleSwitchProps = {
5
5
  * Label for the toggle switch.
6
6
  */
7
7
  label?: React.ReactNode;
8
+ /**
9
+ * Passes properties for ToggleSwitch label.
10
+ */
11
+ labelProps?: React.ComponentProps<'span'>;
8
12
  /**
9
13
  * Position of the label.
10
14
  * @default 'right'
@@ -53,7 +53,7 @@ const index_js_1 = require("../../utils/index.js");
53
53
  * <ToggleSwitch label='With icon toggle' icon={<svg viewBox='0 0 16 16'><path d='M1 1v14h14V1H1zm13 1.7v10.6L8.7 8 14 2.7zM8 7.3L2.7 2h10.6L8 7.3zm-.7.7L2 13.3V2.7L7.3 8zm.7.7l5.3 5.3H2.7L8 8.7z' /></svg>} />
54
54
  */
55
55
  exports.ToggleSwitch = React.forwardRef((props, ref) => {
56
- const { disabled = false, labelPosition = 'right', label, className, style, size = 'default', icon: iconProp, ...rest } = props;
56
+ const { disabled = false, labelPosition = 'right', label, className, style, size = 'default', labelProps = {}, icon: iconProp, ...rest } = props;
57
57
  // Disallow custom icon for small size, but keep the default checkmark when prop is not passed.
58
58
  const shouldShowIcon = iconProp === undefined || (iconProp !== null && size !== 'small');
59
59
  return (React.createElement(index_js_1.Box, { as: label ? 'label' : 'div', className: (0, classnames_1.default)('iui-toggle-switch-wrapper', {
@@ -63,5 +63,5 @@ exports.ToggleSwitch = React.forwardRef((props, ref) => {
63
63
  }, className), "data-iui-size": size, style: style },
64
64
  React.createElement(index_js_1.Box, { as: 'input', className: 'iui-toggle-switch', type: 'checkbox', role: 'switch', disabled: disabled, ref: ref, ...rest }),
65
65
  shouldShowIcon && (React.createElement(index_js_1.Box, { as: 'span', className: 'iui-toggle-switch-icon', "aria-hidden": true }, iconProp || React.createElement(index_js_1.SvgCheckmark, null))),
66
- label && (React.createElement(index_js_1.Box, { as: 'span', className: 'iui-toggle-switch-label' }, label))));
66
+ label && (React.createElement(index_js_1.Box, { as: 'span', ...labelProps, className: (0, classnames_1.default)('iui-toggle-switch-label', labelProps?.className) }, label))));
67
67
  });
@@ -1,9 +1,12 @@
1
1
  import * as React from 'react';
2
+ export declare const portalContainerAtom: import("jotai").PrimitiveAtom<HTMLElement | undefined> & {
3
+ init: HTMLElement | undefined;
4
+ };
2
5
  export type PortalProps = {
3
6
  /**
4
7
  * Where should the element be portaled to?
5
8
  *
6
- * If true, it will portal into nearest ThemeContext.portalContainer.
9
+ * If true, it will portal into nearest ThemeProvider's portalContainer.
7
10
  *
8
11
  * If false, it will not be portaled.
9
12
  *
@@ -20,7 +23,7 @@ export type PortalProps = {
20
23
  /**
21
24
  * Helper component that portals children according to the following conditions:
22
25
  * - renders null on server
23
- * - if `portal` is set to true, renders into nearest ThemeContext.portalContainer
26
+ * - if `portal` is set to true, renders into nearest ThemeProvider's portalContainer
24
27
  * - if `portal` is set to false, renders as-is without portal
25
28
  * - otherwise renders into `portal.to` (can be an element or a function)
26
29
  * - If `to`/`to()` === `null`/`undefined`, the default behavior will be used (i.e. as if `portal` is not passed).
@@ -29,3 +32,4 @@ export type PortalProps = {
29
32
  * @private
30
33
  */
31
34
  export declare const Portal: (props: React.PropsWithChildren<PortalProps>) => React.ReactNode;
35
+ export declare const usePortalTo: (portal: NonNullable<PortalProps['portal']>) => HTMLElement | null | undefined;
@@ -23,21 +23,18 @@ var __importStar = (this && this.__importStar) || function (mod) {
23
23
  return result;
24
24
  };
25
25
  Object.defineProperty(exports, "__esModule", { value: true });
26
- exports.Portal = void 0;
27
- /*---------------------------------------------------------------------------------------------
28
- * Copyright (c) Bentley Systems, Incorporated. All rights reserved.
29
- * See LICENSE.md in the project root for license terms and full copyright notice.
30
- *--------------------------------------------------------------------------------------------*/
31
- const React = __importStar(require("react"));
26
+ exports.usePortalTo = exports.Portal = exports.portalContainerAtom = void 0;
32
27
  const ReactDOM = __importStar(require("react-dom"));
33
- const ThemeContext_js_1 = require("../../core/ThemeProvider/ThemeContext.js");
34
- const dom_js_1 = require("../functions/dom.js");
35
28
  const useIsClient_js_1 = require("../hooks/useIsClient.js");
29
+ const jotai_1 = require("jotai");
30
+ const ScopeProvider_js_1 = require("../providers/ScopeProvider.js");
31
+ // ----------------------------------------------------------------------------
32
+ exports.portalContainerAtom = (0, jotai_1.atom)(undefined);
36
33
  // ----------------------------------------------------------------------------
37
34
  /**
38
35
  * Helper component that portals children according to the following conditions:
39
36
  * - renders null on server
40
- * - if `portal` is set to true, renders into nearest ThemeContext.portalContainer
37
+ * - if `portal` is set to true, renders into nearest ThemeProvider's portalContainer
41
38
  * - if `portal` is set to false, renders as-is without portal
42
39
  * - otherwise renders into `portal.to` (can be an element or a function)
43
40
  * - If `to`/`to()` === `null`/`undefined`, the default behavior will be used (i.e. as if `portal` is not passed).
@@ -48,7 +45,7 @@ const useIsClient_js_1 = require("../hooks/useIsClient.js");
48
45
  const Portal = (props) => {
49
46
  const { portal = true, children } = props;
50
47
  const isClient = (0, useIsClient_js_1.useIsClient)();
51
- const portalTo = usePortalTo(portal);
48
+ const portalTo = (0, exports.usePortalTo)(portal);
52
49
  if (!isClient) {
53
50
  return null;
54
51
  }
@@ -57,11 +54,11 @@ const Portal = (props) => {
57
54
  exports.Portal = Portal;
58
55
  // ----------------------------------------------------------------------------
59
56
  const usePortalTo = (portal) => {
60
- const themeInfo = React.useContext(ThemeContext_js_1.ThemeContext);
61
- const defaultPortalTo = themeInfo?.portalContainer ?? (0, dom_js_1.getDocument)()?.body;
57
+ const [portalContainer] = (0, ScopeProvider_js_1.useScopedAtom)(exports.portalContainerAtom);
62
58
  if (typeof portal === 'boolean') {
63
- return portal ? defaultPortalTo : null;
59
+ return portal ? portalContainer : null;
64
60
  }
65
61
  const portalTo = typeof portal.to === 'function' ? portal.to() : portal.to;
66
- return portalTo ?? defaultPortalTo;
62
+ return portalTo ?? portalContainer;
67
63
  };
64
+ exports.usePortalTo = usePortalTo;