@itwin/itwinui-react 3.9.0 → 3.10.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.
- package/CHANGELOG.md +30 -0
- package/cjs/core/Breadcrumbs/Breadcrumbs.js +2 -3
- package/cjs/core/Buttons/Button.js +1 -1
- package/cjs/core/Buttons/IconButton.js +1 -1
- package/cjs/core/Buttons/IdeasButton.js +6 -2
- package/cjs/core/ComboBox/ComboBox.js +1 -1
- package/cjs/core/DropdownMenu/DropdownMenu.js +36 -13
- package/cjs/core/Input/Input.js +1 -1
- package/cjs/core/LabeledSelect/LabeledSelect.d.ts +26 -4
- package/cjs/core/Menu/Menu.js +9 -0
- package/cjs/core/Menu/MenuItem.d.ts +12 -0
- package/cjs/core/Menu/MenuItem.js +105 -66
- package/cjs/core/NotificationMarker/NotificationMarker.d.ts +7 -6
- package/cjs/core/Popover/Popover.d.ts +32 -9
- package/cjs/core/Popover/Popover.js +65 -17
- package/cjs/core/Select/Select.js +2 -3
- package/cjs/core/SideNavigation/SideNavigation.js +1 -1
- package/cjs/core/Table/TablePaginator.js +1 -3
- package/cjs/core/Table/columns/selectionColumn.js +10 -1
- package/cjs/core/Table/hooks/useSubRowSelection.js +1 -1
- package/cjs/core/ThemeProvider/ThemeProvider.js +53 -17
- package/cjs/core/TimePicker/TimePicker.js +12 -12
- package/cjs/core/ToggleSwitch/ToggleSwitch.d.ts +4 -0
- package/cjs/core/ToggleSwitch/ToggleSwitch.js +2 -2
- package/cjs/core/Tooltip/Tooltip.js +19 -7
- package/cjs/utils/components/Portal.d.ts +6 -2
- package/cjs/utils/components/Portal.js +11 -14
- package/cjs/utils/providers/ScopeProvider.d.ts +26 -0
- package/cjs/utils/providers/ScopeProvider.js +77 -0
- package/cjs/utils/providers/index.d.ts +1 -0
- package/cjs/utils/providers/index.js +1 -0
- package/esm/core/Breadcrumbs/Breadcrumbs.js +2 -3
- package/esm/core/Buttons/Button.js +1 -1
- package/esm/core/Buttons/IconButton.js +1 -1
- package/esm/core/Buttons/IdeasButton.js +3 -2
- package/esm/core/ComboBox/ComboBox.js +1 -1
- package/esm/core/DropdownMenu/DropdownMenu.js +36 -13
- package/esm/core/Input/Input.js +1 -1
- package/esm/core/LabeledSelect/LabeledSelect.d.ts +26 -4
- package/esm/core/Menu/Menu.js +9 -0
- package/esm/core/Menu/MenuItem.d.ts +12 -0
- package/esm/core/Menu/MenuItem.js +105 -66
- package/esm/core/NotificationMarker/NotificationMarker.d.ts +7 -6
- package/esm/core/Popover/Popover.d.ts +32 -9
- package/esm/core/Popover/Popover.js +68 -20
- package/esm/core/Select/Select.js +2 -3
- package/esm/core/SideNavigation/SideNavigation.js +1 -1
- package/esm/core/Table/TablePaginator.js +2 -4
- package/esm/core/Table/columns/selectionColumn.js +10 -1
- package/esm/core/Table/hooks/useSubRowSelection.js +1 -1
- package/esm/core/ThemeProvider/ThemeProvider.js +54 -18
- package/esm/core/TimePicker/TimePicker.js +12 -12
- package/esm/core/ToggleSwitch/ToggleSwitch.d.ts +4 -0
- package/esm/core/ToggleSwitch/ToggleSwitch.js +2 -2
- package/esm/core/Tooltip/Tooltip.js +19 -7
- package/esm/utils/components/Portal.d.ts +6 -2
- package/esm/utils/components/Portal.js +9 -8
- package/esm/utils/providers/ScopeProvider.d.ts +26 -0
- package/esm/utils/providers/ScopeProvider.js +48 -0
- package/esm/utils/providers/index.d.ts +1 -0
- package/esm/utils/providers/index.js +1 -0
- package/package.json +2 -1
- package/styles.css +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,35 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 3.10.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- [#1942](https://github.com/iTwin/iTwinUI/pull/1942): Clicking a `MenuItem` with `submenuItems` now toggles the submenu visibility.
|
|
8
|
+
- If both `submenuItems` and `onClick` props are passed, then clicking the `MenuItem` will toggle the submenu visibility but also _still_ call the `onClick`. However, this behavior can lead to a confusing UX and is not recommended, so a warning will be shown.
|
|
9
|
+
- [#1919](https://github.com/iTwin/iTwinUI/pull/1919): Aggregated a subset of CSS styles across some field components: `Button`, `Input`, `Textarea`, and `Select`, in order to reduce the CSS size and improve visual consistency. Some resulting changes:
|
|
10
|
+
- `Input`, `Textarea`, and `Select` have a similar hover state as `Button`.
|
|
11
|
+
- `Input`, `Textarea`, and `Select` now show their value as greyed out when disabled.
|
|
12
|
+
- [#1942](https://github.com/iTwin/iTwinUI/pull/1942): `DropdownMenu`'s keyboard navigation, hover triggers, and overall behavior has been improved.
|
|
13
|
+
- [#2010](https://github.com/iTwin/iTwinUI/pull/2010): Added new `labelProps` to `ToggleSwitch` to allow for customization of the label element.
|
|
14
|
+
- [#2011](https://github.com/iTwin/iTwinUI/pull/2011): Added dependency on `jotai`.
|
|
15
|
+
|
|
16
|
+
### Patch Changes
|
|
17
|
+
|
|
18
|
+
- [#1942](https://github.com/iTwin/iTwinUI/pull/1942): Disabled `MenuItem`s no longer show their submenu.
|
|
19
|
+
- [#1942](https://github.com/iTwin/iTwinUI/pull/1942): Fixed an issue in `DropdownMenu` where the submenus would not close in some circumstances, despite calling `close()` in `onClick`.
|
|
20
|
+
- [#2013](https://github.com/iTwin/iTwinUI/pull/2013): Fixed an issue with `Table` row selection not correctly deselecting all sub rows when the row has disabled sub rows or when some rows are filtered out.
|
|
21
|
+
- [#2009](https://github.com/iTwin/iTwinUI/pull/2009): Fixed an issue where `Popover` wasn't respecting the `ThemeProvider`'s `portalContainer`.
|
|
22
|
+
- [#2011](https://github.com/iTwin/iTwinUI/pull/2011): When `ThemeProvider` is portaled into popup windows, it will now automatically create a portal container in the correct document, avoiding the need to manually specify `portalContainer`.
|
|
23
|
+
- [#1919](https://github.com/iTwin/iTwinUI/pull/1919): The small sized `TablePaginator`'s buttons are now squares instead of rectangles. This makes it consistent with the shape of the regular sized `TablePaginator` buttons.
|
|
24
|
+
- [#1919](https://github.com/iTwin/iTwinUI/pull/1919): The hover styling of `Breadcrumbs.Item` has been made more consistent across buttons and anchors.
|
|
25
|
+
|
|
26
|
+
## 3.9.1
|
|
27
|
+
|
|
28
|
+
### Patch Changes
|
|
29
|
+
|
|
30
|
+
- [#2005](https://github.com/iTwin/iTwinUI/pull/2005): Improved `IconButton` so that its tooltip is removed from the DOM when not visible.
|
|
31
|
+
- [#2004](https://github.com/iTwin/iTwinUI/pull/2004): Fixed a performance issue in `Tooltip` where expensive calculations were being run even when the tooltip was not visible.
|
|
32
|
+
|
|
3
33
|
## 3.9.0
|
|
4
34
|
|
|
5
35
|
### Minor Changes
|
|
@@ -102,10 +102,9 @@ const ListItem = ({ item, isActive, }) => {
|
|
|
102
102
|
const Separator = ({ separator }) => (React.createElement(index_js_1.Box, { as: 'li', className: 'iui-breadcrumbs-separator', "aria-hidden": true }, separator ?? React.createElement(index_js_1.SvgChevronRight, null)));
|
|
103
103
|
// ----------------------------------------------------------------------------
|
|
104
104
|
const BreadcrumbsItem = React.forwardRef((props, forwardedRef) => {
|
|
105
|
-
const { children
|
|
105
|
+
const { children, className, ...rest } = props;
|
|
106
106
|
const defaultAs = !!props.href ? Anchor_js_1.Anchor : !!props.onClick ? 'button' : 'span';
|
|
107
|
-
|
|
108
|
-
return (React.createElement(index_js_1.Box, { as: defaultAs, className: (0, classnames_1.default)('iui-breadcrumbs-content', className), ref: forwardedRef, ...rest }, children));
|
|
107
|
+
return (React.createElement(Button_js_1.Button, { as: defaultAs, className: (0, classnames_1.default)('iui-breadcrumbs-content', className), styleType: 'borderless', ref: forwardedRef, ...rest }, children));
|
|
109
108
|
});
|
|
110
109
|
BreadcrumbsItem.displayName = 'Breadcrumbs.Item';
|
|
111
110
|
// ----------------------------------------------------------------------------
|
|
@@ -45,7 +45,7 @@ const index_js_1 = require("../../utils/index.js");
|
|
|
45
45
|
*/
|
|
46
46
|
exports.Button = React.forwardRef((props, ref) => {
|
|
47
47
|
const { children, className, size, styleType = 'default', startIcon, endIcon, labelProps, startIconProps, endIconProps, stretched, ...rest } = props;
|
|
48
|
-
return (React.createElement(index_js_1.ButtonBase, { ref: ref, className: (0, classnames_1.default)('iui-button', className), "data-iui-variant": styleType !== 'default' ? styleType : undefined, "data-iui-size": size, ...rest, style: {
|
|
48
|
+
return (React.createElement(index_js_1.ButtonBase, { ref: ref, className: (0, classnames_1.default)('iui-button', 'iui-field', className), "data-iui-variant": styleType !== 'default' ? styleType : undefined, "data-iui-size": size, ...rest, style: {
|
|
49
49
|
'--_iui-width': stretched ? '100%' : undefined,
|
|
50
50
|
...props.style,
|
|
51
51
|
} },
|
|
@@ -47,7 +47,7 @@ const ButtonGroup_js_1 = require("../ButtonGroup/ButtonGroup.js");
|
|
|
47
47
|
exports.IconButton = React.forwardRef((props, ref) => {
|
|
48
48
|
const { isActive, children, styleType = 'default', size, className, label, iconProps, labelProps, ...rest } = props;
|
|
49
49
|
const buttonGroupOrientation = React.useContext(ButtonGroup_js_1.ButtonGroupContext);
|
|
50
|
-
const button = (React.createElement(index_js_1.ButtonBase, { ref: ref, className: (0, classnames_1.default)('iui-button', className), "data-iui-variant": styleType !== 'default' ? styleType : undefined, "data-iui-size": size, "data-iui-active": isActive, "aria-pressed": isActive, ...rest },
|
|
50
|
+
const button = (React.createElement(index_js_1.ButtonBase, { ref: ref, className: (0, classnames_1.default)('iui-button', 'iui-field', className), "data-iui-variant": styleType !== 'default' ? styleType : undefined, "data-iui-size": size, "data-iui-active": isActive, "aria-pressed": isActive, ...rest },
|
|
51
51
|
React.createElement(index_js_1.Box, { as: 'span', "aria-hidden": true, ...iconProps, className: (0, classnames_1.default)('iui-button-icon', iconProps?.className) }, children),
|
|
52
52
|
label ? React.createElement(VisuallyHidden_js_1.VisuallyHidden, null, label) : null));
|
|
53
53
|
return label ? (React.createElement(Tooltip_js_1.Tooltip, { placement: buttonGroupOrientation === 'vertical' ? 'right' : 'top', ...labelProps, content: label, ariaStrategy: 'none' }, button)) : (button);
|
|
@@ -22,12 +22,16 @@ var __importStar = (this && this.__importStar) || function (mod) {
|
|
|
22
22
|
__setModuleDefault(result, mod);
|
|
23
23
|
return result;
|
|
24
24
|
};
|
|
25
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
26
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
27
|
+
};
|
|
25
28
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
26
29
|
exports.IdeasButton = void 0;
|
|
27
30
|
/*---------------------------------------------------------------------------------------------
|
|
28
31
|
* Copyright (c) Bentley Systems, Incorporated. All rights reserved.
|
|
29
32
|
* See LICENSE.md in the project root for license terms and full copyright notice.
|
|
30
33
|
*--------------------------------------------------------------------------------------------*/
|
|
34
|
+
const classnames_1 = __importDefault(require("classnames"));
|
|
31
35
|
const React = __importStar(require("react"));
|
|
32
36
|
const index_js_1 = require("../../utils/index.js");
|
|
33
37
|
const Button_js_1 = require("./Button.js");
|
|
@@ -37,6 +41,6 @@ const Button_js_1 = require("./Button.js");
|
|
|
37
41
|
* <IdeasButton />
|
|
38
42
|
*/
|
|
39
43
|
exports.IdeasButton = React.forwardRef((props, ref) => {
|
|
40
|
-
const { feedbackLabel = 'Feedback', onClick, ...rest } = props;
|
|
41
|
-
return (React.createElement(Button_js_1.Button, { ref: ref, "data-iui-variant": '
|
|
44
|
+
const { feedbackLabel = 'Feedback', className, onClick, ...rest } = props;
|
|
45
|
+
return (React.createElement(Button_js_1.Button, { ref: ref, className: (0, classnames_1.default)('iui-button-idea', className), "data-iui-variant": 'high-visibility', onClick: onClick, startIcon: React.createElement(index_js_1.SvgSmileyHappy, { "aria-hidden": true }), ...rest }, feedbackLabel));
|
|
42
46
|
});
|
|
@@ -308,7 +308,7 @@ exports.ComboBox = React.forwardRef((props, forwardedRef) => {
|
|
|
308
308
|
onVisibleChange: (open) => (open ? show() : hide()),
|
|
309
309
|
matchWidth: true,
|
|
310
310
|
closeOnOutsideClick: true,
|
|
311
|
-
|
|
311
|
+
interactions: { click: false, focus: true },
|
|
312
312
|
});
|
|
313
313
|
return (React.createElement(helpers_js_1.ComboBoxRefsContext.Provider, { value: { inputRef, menuRef, optionsExtraInfoRef } },
|
|
314
314
|
React.createElement(helpers_js_1.ComboBoxActionContext.Provider, { value: dispatch },
|
|
@@ -32,6 +32,8 @@ const React = __importStar(require("react"));
|
|
|
32
32
|
const index_js_1 = require("../../utils/index.js");
|
|
33
33
|
const Menu_js_1 = require("../Menu/Menu.js");
|
|
34
34
|
const Popover_js_1 = require("../Popover/Popover.js");
|
|
35
|
+
const react_1 = require("@floating-ui/react");
|
|
36
|
+
const MenuItem_js_1 = require("../Menu/MenuItem.js");
|
|
35
37
|
/**
|
|
36
38
|
* Dropdown menu component.
|
|
37
39
|
* Built on top of the {@link Popover} component.
|
|
@@ -52,6 +54,11 @@ const Popover_js_1 = require("../Popover/Popover.js");
|
|
|
52
54
|
* </DropdownMenu>
|
|
53
55
|
*/
|
|
54
56
|
exports.DropdownMenu = React.forwardRef((props, forwardedRef) => {
|
|
57
|
+
return (React.createElement(react_1.FloatingTree, null,
|
|
58
|
+
React.createElement(DropdownMenuContent, { ref: forwardedRef, ...props })));
|
|
59
|
+
});
|
|
60
|
+
// ----------------------------------------------------------------------------
|
|
61
|
+
const DropdownMenuContent = React.forwardRef((props, forwardedRef) => {
|
|
55
62
|
const { menuItems, children, role = 'menu', visible: visibleProp, placement = 'bottom-start', matchWidth = false, onVisibleChange, portal = true, ...rest } = props;
|
|
56
63
|
const [visible, setVisible] = (0, index_js_1.useControlledState)(false, visibleProp, onVisibleChange);
|
|
57
64
|
const triggerRef = React.useRef(null);
|
|
@@ -65,11 +72,23 @@ exports.DropdownMenu = React.forwardRef((props, forwardedRef) => {
|
|
|
65
72
|
}
|
|
66
73
|
return menuItems;
|
|
67
74
|
}, [menuItems, close]);
|
|
75
|
+
const [currentFocusedNodeIndex, setCurrentFocusedNodeIndex] = React.useState(null);
|
|
76
|
+
const focusableNodes = React.useRef([]);
|
|
77
|
+
const nodeId = (0, react_1.useFloatingNodeId)();
|
|
68
78
|
const popover = (0, Popover_js_1.usePopover)({
|
|
79
|
+
nodeId,
|
|
69
80
|
visible,
|
|
70
81
|
onVisibleChange: (open) => (open ? setVisible(true) : close()),
|
|
71
82
|
placement,
|
|
72
83
|
matchWidth,
|
|
84
|
+
interactions: {
|
|
85
|
+
listNavigation: {
|
|
86
|
+
activeIndex: currentFocusedNodeIndex,
|
|
87
|
+
onNavigate: setCurrentFocusedNodeIndex,
|
|
88
|
+
listRef: focusableNodes,
|
|
89
|
+
focusItemOnOpen: true,
|
|
90
|
+
},
|
|
91
|
+
},
|
|
73
92
|
});
|
|
74
93
|
const popoverRef = (0, index_js_1.useMergedRefs)(forwardedRef, popover.refs.setFloating);
|
|
75
94
|
return (React.createElement(React.Fragment, null,
|
|
@@ -78,17 +97,21 @@ exports.DropdownMenu = React.forwardRef((props, forwardedRef) => {
|
|
|
78
97
|
'aria-expanded': popover.open,
|
|
79
98
|
ref: (0, index_js_1.mergeRefs)(triggerRef, popover.refs.setReference),
|
|
80
99
|
})),
|
|
81
|
-
popover.open && (React.createElement(index_js_1.Portal, { portal: portal },
|
|
82
|
-
React.createElement(
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
100
|
+
React.createElement(react_1.FloatingNode, { id: nodeId }, popover.open && (React.createElement(index_js_1.Portal, { portal: portal },
|
|
101
|
+
React.createElement(MenuItem_js_1.MenuItemContext.Provider, { value: {
|
|
102
|
+
setCurrentFocusedNodeIndex,
|
|
103
|
+
focusableNodes,
|
|
104
|
+
} },
|
|
105
|
+
React.createElement(Menu_js_1.Menu, { setFocus: false, ...popover.getFloatingProps({
|
|
106
|
+
role,
|
|
107
|
+
...rest,
|
|
108
|
+
onKeyDown: (0, index_js_1.mergeEventHandlers)(props.onKeyDown, (e) => {
|
|
109
|
+
if (e.defaultPrevented) {
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
if (e.key === 'Tab') {
|
|
113
|
+
close();
|
|
114
|
+
}
|
|
115
|
+
}),
|
|
116
|
+
}), ref: popoverRef }, menuContent)))))));
|
|
94
117
|
});
|
package/cjs/core/Input/Input.js
CHANGED
|
@@ -44,5 +44,5 @@ exports.Input = React.forwardRef((props, ref) => {
|
|
|
44
44
|
const { size, htmlSize, status, className, ...rest } = props;
|
|
45
45
|
const inputRef = React.useRef(null);
|
|
46
46
|
const refs = (0, index_js_1.useMergedRefs)(inputRef, ref);
|
|
47
|
-
return (React.createElement(index_js_1.Box, { as: 'input', className: (0, classnames_1.default)('iui-input', className), "data-iui-size": size, "data-iui-status": status, size: htmlSize, ref: refs, ...rest }));
|
|
47
|
+
return (React.createElement(index_js_1.Box, { as: 'input', className: (0, classnames_1.default)('iui-input', 'iui-field', className), "data-iui-size": size, "data-iui-status": status, size: htmlSize, ref: refs, ...rest }));
|
|
48
48
|
});
|
|
@@ -298,6 +298,11 @@ export declare const LabeledSelect: <T>(props: ({
|
|
|
298
298
|
menuStyle?: React.CSSProperties | undefined;
|
|
299
299
|
popoverProps?: Pick<{
|
|
300
300
|
placement?: import("@floating-ui/utils").Placement | undefined;
|
|
301
|
+
/**
|
|
302
|
+
* @deprecated Pass a `<StatusMessage startIcon={svgIcon} />` to the `message` prop instead.
|
|
303
|
+
*
|
|
304
|
+
* Custom svg icon. Will override status icon if specified.
|
|
305
|
+
*/
|
|
301
306
|
visible?: boolean | undefined;
|
|
302
307
|
onVisibleChange?: ((visible: boolean) => void) | undefined;
|
|
303
308
|
closeOnOutsideClick?: boolean | undefined;
|
|
@@ -317,10 +322,16 @@ export declare const LabeledSelect: <T>(props: ({
|
|
|
317
322
|
animationFrame?: boolean | undefined;
|
|
318
323
|
layoutShift?: boolean | undefined;
|
|
319
324
|
} | undefined;
|
|
320
|
-
|
|
325
|
+
interactions?: {
|
|
326
|
+
click?: boolean | import("@floating-ui/react").UseClickProps | undefined;
|
|
327
|
+
dismiss?: boolean | import("@floating-ui/react").UseDismissProps | undefined;
|
|
328
|
+
hover?: boolean | import("@floating-ui/react").UseHoverProps<import("@floating-ui/react").ReferenceType> | undefined;
|
|
329
|
+
focus?: boolean | import("@floating-ui/react").UseFocusProps | undefined;
|
|
330
|
+
listNavigation?: import("@floating-ui/react").UseListNavigationProps | undefined;
|
|
331
|
+
} | undefined;
|
|
321
332
|
role?: "dialog" | "menu" | "listbox" | undefined;
|
|
322
333
|
matchWidth?: boolean | undefined;
|
|
323
|
-
}
|
|
334
|
+
} & Omit<import("@floating-ui/react").UseFloatingOptions<import("@floating-ui/react").ReferenceType>, "placement" | "middleware">, "placement" | "visible" | "onVisibleChange" | "closeOnOutsideClick" | "matchWidth"> | undefined;
|
|
324
335
|
triggerProps?: (Omit<React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>, "ref"> & {
|
|
325
336
|
ref?: ((instance: HTMLDivElement | null) => void) | React.RefObject<HTMLDivElement> | null | undefined;
|
|
326
337
|
}) | undefined;
|
|
@@ -428,6 +439,11 @@ export declare const LabeledSelect: <T>(props: ({
|
|
|
428
439
|
menuStyle?: React.CSSProperties | undefined;
|
|
429
440
|
popoverProps?: Pick<{
|
|
430
441
|
placement?: import("@floating-ui/utils").Placement | undefined;
|
|
442
|
+
/**
|
|
443
|
+
* @deprecated Pass a `<StatusMessage startIcon={svgIcon} />` to the `message` prop instead.
|
|
444
|
+
*
|
|
445
|
+
* Custom svg icon. Will override status icon if specified.
|
|
446
|
+
*/
|
|
431
447
|
visible?: boolean | undefined;
|
|
432
448
|
onVisibleChange?: ((visible: boolean) => void) | undefined;
|
|
433
449
|
closeOnOutsideClick?: boolean | undefined;
|
|
@@ -447,10 +463,16 @@ export declare const LabeledSelect: <T>(props: ({
|
|
|
447
463
|
animationFrame?: boolean | undefined;
|
|
448
464
|
layoutShift?: boolean | undefined;
|
|
449
465
|
} | undefined;
|
|
450
|
-
|
|
466
|
+
interactions?: {
|
|
467
|
+
click?: boolean | import("@floating-ui/react").UseClickProps | undefined;
|
|
468
|
+
dismiss?: boolean | import("@floating-ui/react").UseDismissProps | undefined;
|
|
469
|
+
hover?: boolean | import("@floating-ui/react").UseHoverProps<import("@floating-ui/react").ReferenceType> | undefined;
|
|
470
|
+
focus?: boolean | import("@floating-ui/react").UseFocusProps | undefined;
|
|
471
|
+
listNavigation?: import("@floating-ui/react").UseListNavigationProps | undefined;
|
|
472
|
+
} | undefined;
|
|
451
473
|
role?: "dialog" | "menu" | "listbox" | undefined;
|
|
452
474
|
matchWidth?: boolean | undefined;
|
|
453
|
-
}
|
|
475
|
+
} & Omit<import("@floating-ui/react").UseFloatingOptions<import("@floating-ui/react").ReferenceType>, "placement" | "middleware">, "placement" | "visible" | "onVisibleChange" | "closeOnOutsideClick" | "matchWidth"> | undefined;
|
|
454
476
|
triggerProps?: (Omit<React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>, "ref"> & {
|
|
455
477
|
ref?: ((instance: HTMLDivElement | null) => void) | React.RefObject<HTMLDivElement> | null | undefined;
|
|
456
478
|
}) | undefined;
|
package/cjs/core/Menu/Menu.js
CHANGED
|
@@ -34,6 +34,7 @@ exports.Menu = void 0;
|
|
|
34
34
|
const React = __importStar(require("react"));
|
|
35
35
|
const classnames_1 = __importDefault(require("classnames"));
|
|
36
36
|
const index_js_1 = require("../../utils/index.js");
|
|
37
|
+
const MenuItem_js_1 = require("./MenuItem.js");
|
|
37
38
|
/**
|
|
38
39
|
* Basic menu component. Can be used for select or dropdown components.
|
|
39
40
|
*/
|
|
@@ -42,11 +43,19 @@ exports.Menu = React.forwardRef((props, ref) => {
|
|
|
42
43
|
const [focusedIndex, setFocusedIndex] = React.useState();
|
|
43
44
|
const menuRef = React.useRef(null);
|
|
44
45
|
const refs = (0, index_js_1.useMergedRefs)(menuRef, ref);
|
|
46
|
+
const menuItemContext = React.useContext(MenuItem_js_1.MenuItemContext);
|
|
45
47
|
const getFocusableNodes = React.useCallback(() => {
|
|
46
48
|
const focusableItems = (0, index_js_1.getFocusableElements)(menuRef.current);
|
|
47
49
|
// Filter out focusable elements that are inside each menu item, e.g. checkbox, anchor
|
|
48
50
|
return focusableItems.filter((i) => !focusableItems.some((p) => p.contains(i.parentElement)));
|
|
49
51
|
}, []);
|
|
52
|
+
React.useEffect(() => {
|
|
53
|
+
const focusableNodes = getFocusableNodes();
|
|
54
|
+
if (menuItemContext != null &&
|
|
55
|
+
menuItemContext.focusableNodes.current !== focusableNodes) {
|
|
56
|
+
menuItemContext.focusableNodes.current = focusableNodes;
|
|
57
|
+
}
|
|
58
|
+
}, [getFocusableNodes, menuItemContext]);
|
|
50
59
|
React.useEffect(() => {
|
|
51
60
|
const items = getFocusableNodes();
|
|
52
61
|
if (focusedIndex != null) {
|
|
@@ -1,6 +1,14 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
2
|
import type { PolymorphicForwardRefComponent } from '../../utils/index.js';
|
|
3
3
|
import type { ListItemOwnProps } from '../List/ListItem.js';
|
|
4
|
+
/**
|
|
5
|
+
* Should be wrapped around the `Menu` containing the `MenuItem`s.
|
|
6
|
+
*/
|
|
7
|
+
export declare const MenuItemContext: React.Context<{
|
|
8
|
+
setCurrentFocusedNodeIndex: React.Dispatch<React.SetStateAction<number | null>>;
|
|
9
|
+
focusableNodes: React.MutableRefObject<(HTMLElement | null)[]>;
|
|
10
|
+
setHasFocusedNodeInSubmenu?: React.Dispatch<React.SetStateAction<boolean>> | undefined;
|
|
11
|
+
}>;
|
|
4
12
|
export type MenuItemProps = {
|
|
5
13
|
/**
|
|
6
14
|
* Item is selected.
|
|
@@ -65,3 +73,7 @@ export type MenuItemProps = {
|
|
|
65
73
|
* Basic menu item component. Should be used inside `Menu` component for each item.
|
|
66
74
|
*/
|
|
67
75
|
export declare const MenuItem: PolymorphicForwardRefComponent<"div", MenuItemProps>;
|
|
76
|
+
export type TreeEvent = {
|
|
77
|
+
nodeId: string;
|
|
78
|
+
parentId: string | null;
|
|
79
|
+
};
|
|
@@ -23,7 +23,7 @@ var __importStar = (this && this.__importStar) || function (mod) {
|
|
|
23
23
|
return result;
|
|
24
24
|
};
|
|
25
25
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
26
|
-
exports.MenuItem = void 0;
|
|
26
|
+
exports.MenuItem = exports.MenuItemContext = void 0;
|
|
27
27
|
/*---------------------------------------------------------------------------------------------
|
|
28
28
|
* Copyright (c) Bentley Systems, Incorporated. All rights reserved.
|
|
29
29
|
* See LICENSE.md in the project root for license terms and full copyright notice.
|
|
@@ -32,34 +32,73 @@ const React = __importStar(require("react"));
|
|
|
32
32
|
const index_js_1 = require("../../utils/index.js");
|
|
33
33
|
const Menu_js_1 = require("./Menu.js");
|
|
34
34
|
const ListItem_js_1 = require("../List/ListItem.js");
|
|
35
|
-
const react_dom_1 = require("react-dom");
|
|
36
35
|
const Popover_js_1 = require("../Popover/Popover.js");
|
|
36
|
+
const react_1 = require("@floating-ui/react");
|
|
37
|
+
const logWarningInDev = (0, index_js_1.createWarningLogger)();
|
|
37
38
|
/**
|
|
38
|
-
*
|
|
39
|
+
* Should be wrapped around the `Menu` containing the `MenuItem`s.
|
|
39
40
|
*/
|
|
40
|
-
|
|
41
|
+
exports.MenuItemContext = React.createContext({
|
|
42
|
+
setCurrentFocusedNodeIndex: () => { },
|
|
43
|
+
focusableNodes: { current: [] },
|
|
44
|
+
setHasFocusedNodeInSubmenu: undefined,
|
|
45
|
+
});
|
|
41
46
|
/**
|
|
42
47
|
* Basic menu item component. Should be used inside `Menu` component for each item.
|
|
43
48
|
*/
|
|
44
49
|
exports.MenuItem = React.forwardRef((props, forwardedRef) => {
|
|
45
|
-
const { children, isSelected, disabled, value, onClick, sublabel, size = !!sublabel ? 'large' : 'default', icon, startIcon = icon, badge, endIcon = badge, role = 'menuitem', subMenuItems = [], ...rest } = props;
|
|
50
|
+
const { children, isSelected, disabled, value, onClick: onClickProp, sublabel, size = !!sublabel ? 'large' : 'default', icon, startIcon = icon, badge, endIcon = badge, role = 'menuitem', subMenuItems = [], ...rest } = props;
|
|
51
|
+
if (onClickProp != null && subMenuItems.length > 0) {
|
|
52
|
+
logWarningInDev('Passing a non-empty submenuItems array and onClick to MenuItem at the same time is not supported. This is because when a non empty submenuItems array is passed, clicking the MenuItem toggles the submenu visibility.');
|
|
53
|
+
}
|
|
54
|
+
const parent = React.useContext(exports.MenuItemContext);
|
|
46
55
|
const menuItemRef = React.useRef(null);
|
|
47
|
-
const [focusOnSubmenu, setFocusOnSubmenu] = React.useState(false);
|
|
48
56
|
const submenuId = (0, index_js_1.useId)();
|
|
49
57
|
const [isSubmenuVisible, setIsSubmenuVisible] = React.useState(false);
|
|
50
|
-
const [
|
|
51
|
-
const
|
|
52
|
-
const
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
+
const [currentFocusedNodeIndex, setCurrentFocusedNodeIndex] = React.useState(null);
|
|
59
|
+
const [hasFocusedNodeInSubmenu, setHasFocusedNodeInSubmenu] = React.useState(false);
|
|
60
|
+
const nodeId = (0, react_1.useFloatingNodeId)();
|
|
61
|
+
const tree = (0, react_1.useFloatingTree)();
|
|
62
|
+
const parentId = (0, react_1.useFloatingParentNodeId)();
|
|
63
|
+
const focusableNodeIndexInParentTree = parent.focusableNodes.current.findIndex((el) => el === menuItemRef.current);
|
|
64
|
+
(0, index_js_1.useSyncExternalStore)(React.useCallback(() => {
|
|
65
|
+
const closeUnrelatedMenus = (event) => {
|
|
66
|
+
if (
|
|
67
|
+
// When a node "X" is focused, close "X"'s siblings' submenus
|
|
68
|
+
// i.e. only one submenu in each menu can be open at a time
|
|
69
|
+
(parentId === event.parentId && nodeId !== event.nodeId) ||
|
|
70
|
+
// Consider a node "X" with its submenu "Y".
|
|
71
|
+
// Focusing "X" should close all submenus of "Y".
|
|
72
|
+
parentId === event.nodeId) {
|
|
73
|
+
setIsSubmenuVisible(false);
|
|
74
|
+
setHasFocusedNodeInSubmenu(false);
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
tree?.events.on('onNodeFocused', closeUnrelatedMenus);
|
|
78
|
+
return () => {
|
|
79
|
+
tree?.events.off('onNodeFocused', closeUnrelatedMenus);
|
|
80
|
+
};
|
|
81
|
+
}, [nodeId, parentId, tree?.events]), () => undefined, () => undefined);
|
|
82
|
+
const focusableNodes = React.useRef([]);
|
|
58
83
|
const popover = (0, Popover_js_1.usePopover)({
|
|
59
|
-
|
|
60
|
-
|
|
84
|
+
nodeId,
|
|
85
|
+
visible: isSubmenuVisible,
|
|
86
|
+
onVisibleChange: setIsSubmenuVisible,
|
|
61
87
|
placement: 'right-start',
|
|
62
|
-
|
|
88
|
+
interactions: !disabled
|
|
89
|
+
? {
|
|
90
|
+
click: false,
|
|
91
|
+
hover: {
|
|
92
|
+
enabled: !hasFocusedNodeInSubmenu, // If focus is still inside submenu, don't close the submenu upon hovering out.
|
|
93
|
+
},
|
|
94
|
+
listNavigation: {
|
|
95
|
+
listRef: focusableNodes,
|
|
96
|
+
activeIndex: currentFocusedNodeIndex,
|
|
97
|
+
nested: subMenuItems.length > 0,
|
|
98
|
+
onNavigate: setCurrentFocusedNodeIndex,
|
|
99
|
+
},
|
|
100
|
+
}
|
|
101
|
+
: {},
|
|
63
102
|
});
|
|
64
103
|
const onKeyDown = (event) => {
|
|
65
104
|
if (event.altKey) {
|
|
@@ -69,61 +108,61 @@ exports.MenuItem = React.forwardRef((props, forwardedRef) => {
|
|
|
69
108
|
case 'Enter':
|
|
70
109
|
case ' ':
|
|
71
110
|
case 'Spacebar': {
|
|
72
|
-
|
|
111
|
+
onClick();
|
|
73
112
|
event.preventDefault();
|
|
74
113
|
break;
|
|
75
114
|
}
|
|
76
|
-
case 'ArrowRight': {
|
|
77
|
-
if (subMenuItems.length > 0) {
|
|
78
|
-
setIsSubmenuVisible(true);
|
|
79
|
-
// flush and reset state so we are ready to focus again next time
|
|
80
|
-
(0, react_dom_1.flushSync)(() => setFocusOnSubmenu(true));
|
|
81
|
-
setFocusOnSubmenu(false);
|
|
82
|
-
event.preventDefault();
|
|
83
|
-
event.stopPropagation();
|
|
84
|
-
}
|
|
85
|
-
break;
|
|
86
|
-
}
|
|
87
|
-
case 'ArrowLeft': {
|
|
88
|
-
if (parent.ref) {
|
|
89
|
-
parent.ref.current?.focus();
|
|
90
|
-
parent.setIsNestedSubmenuVisible(false);
|
|
91
|
-
}
|
|
92
|
-
event.stopPropagation();
|
|
93
|
-
event.preventDefault();
|
|
94
|
-
break;
|
|
95
|
-
}
|
|
96
|
-
case 'Escape': {
|
|
97
|
-
// focus might get lost if submenu closes so move it back to parent
|
|
98
|
-
parent.ref?.current?.focus();
|
|
99
|
-
break;
|
|
100
|
-
}
|
|
101
115
|
default:
|
|
102
116
|
break;
|
|
103
117
|
}
|
|
104
118
|
};
|
|
105
|
-
const
|
|
106
|
-
|
|
107
|
-
|
|
119
|
+
const onMouseEnter = (e) => {
|
|
120
|
+
// Focus the item when hovered.
|
|
121
|
+
if (e.target === e.currentTarget) {
|
|
122
|
+
menuItemRef.current?.focus();
|
|
123
|
+
// Since we manually focus the MenuItem on hover, we need to manually update the active index for
|
|
124
|
+
// Floating UI's keyboard navigation to work correctly.
|
|
125
|
+
if (parent != null && focusableNodeIndexInParentTree != null) {
|
|
126
|
+
parent.setCurrentFocusedNodeIndex(focusableNodeIndexInParentTree);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
};
|
|
130
|
+
const onFocus = () => {
|
|
131
|
+
parent.setHasFocusedNodeInSubmenu?.(true);
|
|
132
|
+
tree?.events.emit('onNodeFocused', {
|
|
133
|
+
nodeId,
|
|
134
|
+
parentId,
|
|
135
|
+
});
|
|
108
136
|
};
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
137
|
+
const onClick = () => {
|
|
138
|
+
onClickProp?.(value);
|
|
139
|
+
setIsSubmenuVisible((prev) => !prev);
|
|
140
|
+
};
|
|
141
|
+
const handlers = !disabled
|
|
142
|
+
? {
|
|
143
|
+
onClick,
|
|
144
|
+
onKeyDown,
|
|
145
|
+
onMouseEnter,
|
|
146
|
+
onFocus,
|
|
147
|
+
}
|
|
148
|
+
: {};
|
|
149
|
+
return (React.createElement(React.Fragment, null,
|
|
150
|
+
React.createElement(ListItem_js_1.ListItem, { as: 'div', actionable: true, size: size, active: isSelected, disabled: disabled, ref: (0, index_js_1.useMergedRefs)(menuItemRef, forwardedRef, subMenuItems.length > 0 ? popover.refs.setReference : null), role: role, tabIndex: disabled || role === 'presentation' ? undefined : -1, "aria-selected": isSelected, "aria-haspopup": subMenuItems.length > 0 ? 'true' : undefined, "aria-controls": subMenuItems.length > 0 ? submenuId : undefined, "aria-expanded": subMenuItems.length > 0 ? popover.open : undefined, "aria-disabled": disabled, ...(subMenuItems.length === 0
|
|
151
|
+
? { ...handlers, ...rest }
|
|
152
|
+
: popover.getReferenceProps({ ...handlers, ...rest })) },
|
|
153
|
+
startIcon && (React.createElement(ListItem_js_1.ListItem.Icon, { as: 'span', "aria-hidden": true }, startIcon)),
|
|
154
|
+
React.createElement(ListItem_js_1.ListItem.Content, null,
|
|
155
|
+
React.createElement("div", null, children),
|
|
156
|
+
sublabel && React.createElement(ListItem_js_1.ListItem.Description, null, sublabel)),
|
|
157
|
+
!endIcon && subMenuItems.length > 0 && (React.createElement(ListItem_js_1.ListItem.Icon, { as: 'span', "aria-hidden": true },
|
|
158
|
+
React.createElement(index_js_1.SvgCaretRightSmall, null))),
|
|
159
|
+
endIcon && (React.createElement(ListItem_js_1.ListItem.Icon, { as: 'span', "aria-hidden": true }, endIcon))),
|
|
160
|
+
subMenuItems.length > 0 && !disabled && popover.open && (React.createElement(react_1.FloatingNode, { id: nodeId },
|
|
161
|
+
React.createElement(index_js_1.Portal, null,
|
|
162
|
+
React.createElement(exports.MenuItemContext.Provider, { value: {
|
|
163
|
+
setCurrentFocusedNodeIndex,
|
|
164
|
+
focusableNodes,
|
|
165
|
+
setHasFocusedNodeInSubmenu,
|
|
166
|
+
} },
|
|
167
|
+
React.createElement(Menu_js_1.Menu, { setFocus: false, ref: popover.refs.setFloating, ...popover.getFloatingProps({ id: submenuId }) }, subMenuItems)))))));
|
|
129
168
|
});
|
|
@@ -24,14 +24,15 @@ type NotificationMarkerProps = {
|
|
|
24
24
|
*/
|
|
25
25
|
pulsing?: boolean;
|
|
26
26
|
/**
|
|
27
|
-
*
|
|
28
|
-
*
|
|
27
|
+
* Instead of conditionally rendering the `NotificationMarker`, the `enabled` prop can be used.
|
|
28
|
+
*
|
|
29
|
+
* When `enabled` is set to `false`, the DOM element will still be present, but the notification marker will not be displayed visually.
|
|
30
|
+
*
|
|
29
31
|
* @example
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
* <NotificationMarker enabled={newMessagesCount > 0}>
|
|
33
|
-
* <SvgNotification />
|
|
32
|
+
* <NotificationMarker enabled={notifications.length > 0}>
|
|
33
|
+
* …
|
|
34
34
|
* </NotificationMarker>
|
|
35
|
+
* @default true
|
|
35
36
|
*/
|
|
36
37
|
enabled?: boolean;
|
|
37
38
|
};
|
|
@@ -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
|
|
63
|
-
*
|
|
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
|
-
|
|
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<
|
|
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<
|
|
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<
|
|
107
|
-
elements: import("@floating-ui/react").ExtendedElements<
|
|
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>;
|