@itwin/itwinui-react 3.0.0-dev.8 → 3.0.0-dev.9
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 +12 -0
- package/cjs/core/Buttons/DropdownButton/DropdownButton.js +7 -19
- package/cjs/core/Buttons/SplitButton/SplitButton.d.ts +4 -4
- package/cjs/core/Buttons/SplitButton/SplitButton.js +53 -31
- package/cjs/core/ComboBox/ComboBox.d.ts +2 -2
- package/cjs/core/ComboBox/ComboBox.js +32 -24
- package/cjs/core/ComboBox/ComboBoxInput.js +29 -21
- package/cjs/core/ComboBox/ComboBoxMenu.js +73 -93
- package/cjs/core/ComboBox/helpers.d.ts +4 -1
- package/cjs/core/DropdownMenu/DropdownMenu.d.ts +6 -5
- package/cjs/core/DropdownMenu/DropdownMenu.js +59 -55
- package/cjs/core/Header/HeaderDropdownButton.js +1 -2
- package/cjs/core/Header/HeaderSplitButton.js +1 -2
- package/cjs/core/Menu/Menu.js +1 -1
- package/cjs/core/Menu/MenuItem.js +77 -55
- package/cjs/core/Select/Select.d.ts +5 -5
- package/cjs/core/Select/Select.js +74 -93
- package/cjs/core/Table/columns/actionColumn.js +3 -7
- package/cjs/core/Table/filters/DateRangeFilter/DatePickerInput.js +36 -41
- package/cjs/core/Table/filters/FilterToggle.js +3 -2
- package/cjs/core/Tile/Tile.js +21 -22
- package/cjs/core/index.d.ts +1 -1
- package/cjs/core/index.js +8 -1
- package/cjs/core/utils/components/InputContainer.d.ts +4 -4
- package/cjs/core/utils/components/InputContainer.js +7 -3
- package/cjs/core/utils/components/Popover.d.ts +113 -27
- package/cjs/core/utils/components/Popover.js +156 -118
- package/cjs/styles.js +2 -5
- package/esm/core/Buttons/DropdownButton/DropdownButton.js +8 -24
- package/esm/core/Buttons/SplitButton/SplitButton.d.ts +4 -4
- package/esm/core/Buttons/SplitButton/SplitButton.js +53 -28
- package/esm/core/ComboBox/ComboBox.d.ts +2 -2
- package/esm/core/ComboBox/ComboBox.js +33 -24
- package/esm/core/ComboBox/ComboBoxInput.js +22 -21
- package/esm/core/ComboBox/ComboBoxMenu.js +67 -87
- package/esm/core/ComboBox/helpers.d.ts +4 -1
- package/esm/core/DropdownMenu/DropdownMenu.d.ts +6 -5
- package/esm/core/DropdownMenu/DropdownMenu.js +64 -56
- package/esm/core/Header/HeaderDropdownButton.js +1 -2
- package/esm/core/Header/HeaderSplitButton.js +1 -2
- package/esm/core/Menu/Menu.js +7 -2
- package/esm/core/Menu/MenuItem.js +84 -52
- package/esm/core/Select/Select.d.ts +5 -5
- package/esm/core/Select/Select.js +74 -90
- package/esm/core/Table/columns/actionColumn.js +3 -7
- package/esm/core/Table/filters/DateRangeFilter/DatePickerInput.js +36 -41
- package/esm/core/Table/filters/FilterToggle.js +3 -2
- package/esm/core/Tile/Tile.js +21 -22
- package/esm/core/index.d.ts +1 -1
- package/esm/core/index.js +1 -0
- package/esm/core/utils/components/InputContainer.d.ts +4 -4
- package/esm/core/utils/components/InputContainer.js +7 -2
- package/esm/core/utils/components/Popover.d.ts +113 -27
- package/esm/core/utils/components/Popover.js +175 -118
- package/esm/styles.js +2 -5
- package/package.json +2 -4
- package/styles.css +3 -3
- package/cjs/core/ComboBox/ComboBoxDropdown.d.ts +0 -7
- package/cjs/core/ComboBox/ComboBoxDropdown.js +0 -43
- package/esm/core/ComboBox/ComboBoxDropdown.d.ts +0 -7
- package/esm/core/ComboBox/ComboBoxDropdown.js +0 -37
|
@@ -9,113 +9,93 @@ const tslib_1 = require('tslib');
|
|
|
9
9
|
const classnames_1 = tslib_1.__importDefault(require('classnames'));
|
|
10
10
|
const React = tslib_1.__importStar(require('react'));
|
|
11
11
|
const index_js_1 = require('../Menu/index.js');
|
|
12
|
-
const index_js_2 = require('../
|
|
13
|
-
const index_js_3 = require('../utils/index.js');
|
|
12
|
+
const index_js_2 = require('../utils/index.js');
|
|
14
13
|
const helpers_js_1 = require('./helpers.js');
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
-
const
|
|
18
|
-
(
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
)
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
14
|
+
const VirtualizedComboBoxMenu = (props) => {
|
|
15
|
+
const { children, ...rest } = props;
|
|
16
|
+
const { filteredOptions, getMenuItem, focusedIndex } = (0,
|
|
17
|
+
index_js_2.useSafeContext)(helpers_js_1.ComboBoxStateContext);
|
|
18
|
+
const { menuRef } = (0, index_js_2.useSafeContext)(
|
|
19
|
+
helpers_js_1.ComboBoxRefsContext,
|
|
20
|
+
);
|
|
21
|
+
const virtualItemRenderer = React.useCallback(
|
|
22
|
+
(index) =>
|
|
23
|
+
filteredOptions.length > 0
|
|
24
|
+
? getMenuItem(filteredOptions[index], index)
|
|
25
|
+
: children, // Here is expected empty state content
|
|
26
|
+
[filteredOptions, getMenuItem, children],
|
|
27
|
+
);
|
|
28
|
+
const focusedVisibleIndex = React.useMemo(() => {
|
|
29
|
+
const currentElement = menuRef.current?.querySelector(
|
|
30
|
+
`[data-iui-index="${focusedIndex}"]`,
|
|
30
31
|
);
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
return focusedIndex;
|
|
37
|
-
}
|
|
38
|
-
return Number(
|
|
39
|
-
currentElement.getAttribute('data-iui-filtered-index') ?? focusedIndex,
|
|
40
|
-
);
|
|
41
|
-
}, [focusedIndex, menuRef]);
|
|
42
|
-
const { outerProps, innerProps, visibleChildren } = (0,
|
|
43
|
-
index_js_3.useVirtualization)({
|
|
44
|
-
// 'Fool' VirtualScroll by passing length 1
|
|
45
|
-
// whenever there is no elements, to show empty state message
|
|
46
|
-
itemsLength: filteredOptions.length || 1,
|
|
47
|
-
itemRenderer: virtualItemRenderer,
|
|
48
|
-
scrollToIndex: focusedVisibleIndex,
|
|
49
|
-
});
|
|
50
|
-
const surfaceStyles = {
|
|
51
|
-
minInlineSize: minWidth,
|
|
52
|
-
// set as constant because we don't want it shifting when items are unmounted
|
|
53
|
-
maxInlineSize: minWidth,
|
|
54
|
-
// max-height must be on the outermost element for virtual scroll
|
|
55
|
-
maxBlockSize: 'calc((var(--iui-component-height) - 1px) * 8.5)',
|
|
56
|
-
overflowY: isOverflowOverlaySupported() ? 'overlay' : 'auto',
|
|
57
|
-
...style,
|
|
58
|
-
};
|
|
59
|
-
return React.createElement(
|
|
60
|
-
index_js_2.Surface,
|
|
61
|
-
{ style: surfaceStyles },
|
|
62
|
-
React.createElement(
|
|
63
|
-
'div',
|
|
64
|
-
{ ...outerProps },
|
|
65
|
-
React.createElement(
|
|
66
|
-
index_js_1.Menu,
|
|
67
|
-
{
|
|
68
|
-
id: `${id}-list`,
|
|
69
|
-
setFocus: false,
|
|
70
|
-
role: 'listbox',
|
|
71
|
-
ref: (0, index_js_3.mergeRefs)(
|
|
72
|
-
menuRef,
|
|
73
|
-
innerProps.ref,
|
|
74
|
-
forwardedRef,
|
|
75
|
-
),
|
|
76
|
-
className: className,
|
|
77
|
-
style: innerProps.style,
|
|
78
|
-
...rest,
|
|
79
|
-
},
|
|
80
|
-
visibleChildren,
|
|
81
|
-
),
|
|
82
|
-
),
|
|
32
|
+
if (!currentElement) {
|
|
33
|
+
return focusedIndex;
|
|
34
|
+
}
|
|
35
|
+
return Number(
|
|
36
|
+
currentElement.getAttribute('data-iui-filtered-index') ?? focusedIndex,
|
|
83
37
|
);
|
|
84
|
-
},
|
|
85
|
-
|
|
38
|
+
}, [focusedIndex, menuRef]);
|
|
39
|
+
const { outerProps, innerProps, visibleChildren } = (0,
|
|
40
|
+
index_js_2.useVirtualization)({
|
|
41
|
+
// 'Fool' VirtualScroll by passing length 1
|
|
42
|
+
// whenever there is no elements, to show empty state message
|
|
43
|
+
itemsLength: filteredOptions.length || 1,
|
|
44
|
+
itemRenderer: virtualItemRenderer,
|
|
45
|
+
scrollToIndex: focusedVisibleIndex,
|
|
46
|
+
});
|
|
47
|
+
return React.createElement(
|
|
48
|
+
index_js_2.Box,
|
|
49
|
+
{ as: 'div', ...outerProps, ...rest },
|
|
50
|
+
React.createElement(
|
|
51
|
+
'div',
|
|
52
|
+
{ ...innerProps, ref: innerProps.ref },
|
|
53
|
+
visibleChildren,
|
|
54
|
+
),
|
|
55
|
+
);
|
|
56
|
+
};
|
|
86
57
|
exports.ComboBoxMenu = React.forwardRef((props, forwardedRef) => {
|
|
87
|
-
const { className, style, ...rest } = props;
|
|
88
|
-
const {
|
|
58
|
+
const { className, children, style, ...rest } = props;
|
|
59
|
+
const { id, enableVirtualization, popover } = (0, index_js_2.useSafeContext)(
|
|
89
60
|
helpers_js_1.ComboBoxStateContext,
|
|
90
61
|
);
|
|
91
|
-
const { menuRef } = (0,
|
|
62
|
+
const { menuRef } = (0, index_js_2.useSafeContext)(
|
|
92
63
|
helpers_js_1.ComboBoxRefsContext,
|
|
93
64
|
);
|
|
94
|
-
const refs = (0,
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
maxInlineSize: `min(${minWidth * 2}px, 90vw)`,
|
|
99
|
-
}),
|
|
100
|
-
[minWidth],
|
|
65
|
+
const refs = (0, index_js_2.useMergedRefs)(
|
|
66
|
+
popover.refs.setFloating,
|
|
67
|
+
forwardedRef,
|
|
68
|
+
menuRef,
|
|
101
69
|
);
|
|
102
|
-
return
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
70
|
+
return (
|
|
71
|
+
popover.open &&
|
|
72
|
+
React.createElement(
|
|
73
|
+
index_js_2.Portal,
|
|
74
|
+
{ portal: true },
|
|
75
|
+
React.createElement(
|
|
76
|
+
index_js_1.Menu,
|
|
77
|
+
{
|
|
107
78
|
id: `${id}-list`,
|
|
108
|
-
style: { ...styles, ...style },
|
|
109
79
|
setFocus: false,
|
|
110
80
|
role: 'listbox',
|
|
111
81
|
ref: refs,
|
|
112
82
|
className: (0, classnames_1.default)('iui-scroll', className),
|
|
113
|
-
...
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
83
|
+
...popover.getFloatingProps({
|
|
84
|
+
style: !enableVirtualization
|
|
85
|
+
? style
|
|
86
|
+
: {
|
|
87
|
+
// set as constant because we don't want it shifting when items are unmounted
|
|
88
|
+
maxInlineSize: 0,
|
|
89
|
+
...style,
|
|
90
|
+
},
|
|
91
|
+
...rest,
|
|
92
|
+
}),
|
|
93
|
+
},
|
|
94
|
+
!enableVirtualization
|
|
95
|
+
? children
|
|
96
|
+
: React.createElement(VirtualizedComboBoxMenu, null, children),
|
|
97
|
+
),
|
|
98
|
+
)
|
|
119
99
|
);
|
|
120
100
|
});
|
|
121
101
|
exports.ComboBoxMenu.displayName = 'ComboBoxMenu';
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
2
|
import type { SelectOption } from '../Select/Select.js';
|
|
3
|
+
import type { usePopover } from '../utils/index.js';
|
|
3
4
|
type ComboBoxAction = {
|
|
4
5
|
type: 'multiselect';
|
|
5
6
|
value: number[];
|
|
@@ -33,13 +34,15 @@ export declare const ComboBoxRefsContext: React.Context<{
|
|
|
33
34
|
type ComboBoxStateContextProps<T = unknown> = {
|
|
34
35
|
isOpen: boolean;
|
|
35
36
|
id: string;
|
|
36
|
-
minWidth: number;
|
|
37
37
|
enableVirtualization: boolean;
|
|
38
38
|
filteredOptions: SelectOption<T>[];
|
|
39
39
|
onClickHandler?: (prop: number) => void;
|
|
40
40
|
getMenuItem: (option: SelectOption<T>, filteredIndex?: number) => JSX.Element;
|
|
41
41
|
focusedIndex?: number;
|
|
42
42
|
multiple?: boolean;
|
|
43
|
+
popover: ReturnType<typeof usePopover>;
|
|
44
|
+
show: () => void;
|
|
45
|
+
hide: () => void;
|
|
43
46
|
};
|
|
44
47
|
export declare const ComboBoxStateContext: React.Context<ComboBoxStateContextProps<unknown> | undefined>;
|
|
45
48
|
export declare const ComboBoxActionContext: React.Context<((x: ComboBoxAction) => void) | undefined>;
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
|
-
import
|
|
2
|
+
import { Popover } from '../utils/index.js';
|
|
3
|
+
import type { PolymorphicForwardRefComponent, PortalProps } from '../utils/index.js';
|
|
3
4
|
export type DropdownMenuProps = {
|
|
4
5
|
/**
|
|
5
6
|
* List of menu items. Recommended to use MenuItem component.
|
|
6
7
|
* You can pass function that takes argument `close` that closes the dropdown menu, or a list of MenuItems.
|
|
7
8
|
*/
|
|
8
|
-
menuItems: (close: () => void) => JSX.Element[] | JSX.Element[] | JSX.Element;
|
|
9
|
+
menuItems: ((close: () => void) => JSX.Element[]) | JSX.Element[] | JSX.Element;
|
|
9
10
|
/**
|
|
10
11
|
* ARIA role. Role of menu. For menu use 'menu', for select use 'listbox'.
|
|
11
12
|
* @default 'menu'
|
|
@@ -15,10 +16,10 @@ export type DropdownMenuProps = {
|
|
|
15
16
|
* Child element to wrap dropdown with.
|
|
16
17
|
*/
|
|
17
18
|
children: React.ReactNode;
|
|
18
|
-
} &
|
|
19
|
+
} & Pick<React.ComponentProps<typeof Popover>, 'visible' | 'onVisibleChange' | 'placement' | 'matchWidth'> & React.ComponentPropsWithoutRef<'ul'> & Pick<PortalProps, 'portal'>;
|
|
19
20
|
/**
|
|
20
21
|
* Dropdown menu component.
|
|
21
|
-
*
|
|
22
|
+
* Built on top of the {@link Popover} component.
|
|
22
23
|
* @example
|
|
23
24
|
* const menuItems = (close: () => void) => [
|
|
24
25
|
* <MenuItem key={1} onClick={onClick(1, close)}>
|
|
@@ -35,5 +36,5 @@ export type DropdownMenuProps = {
|
|
|
35
36
|
* <Button>Menu</Button>
|
|
36
37
|
* </DropdownMenu>
|
|
37
38
|
*/
|
|
38
|
-
export declare const DropdownMenu:
|
|
39
|
+
export declare const DropdownMenu: PolymorphicForwardRefComponent<"div", DropdownMenuProps>;
|
|
39
40
|
export default DropdownMenu;
|
|
@@ -11,7 +11,7 @@ const index_js_1 = require('../utils/index.js');
|
|
|
11
11
|
const index_js_2 = require('../Menu/index.js');
|
|
12
12
|
/**
|
|
13
13
|
* Dropdown menu component.
|
|
14
|
-
*
|
|
14
|
+
* Built on top of the {@link Popover} component.
|
|
15
15
|
* @example
|
|
16
16
|
* const menuItems = (close: () => void) => [
|
|
17
17
|
* <MenuItem key={1} onClick={onClick(1, close)}>
|
|
@@ -28,75 +28,79 @@ const index_js_2 = require('../Menu/index.js');
|
|
|
28
28
|
* <Button>Menu</Button>
|
|
29
29
|
* </DropdownMenu>
|
|
30
30
|
*/
|
|
31
|
-
|
|
31
|
+
exports.DropdownMenu = React.forwardRef((props, forwardedRef) => {
|
|
32
32
|
const {
|
|
33
33
|
menuItems,
|
|
34
34
|
children,
|
|
35
|
-
className,
|
|
36
|
-
style,
|
|
37
35
|
role = 'menu',
|
|
38
|
-
visible,
|
|
36
|
+
visible: visibleProp,
|
|
39
37
|
placement = 'bottom-start',
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
id,
|
|
38
|
+
matchWidth = false,
|
|
39
|
+
onVisibleChange,
|
|
40
|
+
portal = true,
|
|
44
41
|
...rest
|
|
45
42
|
} = props;
|
|
46
|
-
const [
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
const
|
|
43
|
+
const [visible, setVisible] = (0, index_js_1.useControlledState)(
|
|
44
|
+
false,
|
|
45
|
+
visibleProp,
|
|
46
|
+
onVisibleChange,
|
|
47
|
+
);
|
|
48
|
+
const triggerRef = React.useRef(null);
|
|
49
|
+
const close = React.useCallback(() => {
|
|
50
|
+
setVisible(false);
|
|
51
|
+
triggerRef.current?.focus({ preventScroll: true });
|
|
52
|
+
}, [setVisible]);
|
|
52
53
|
const menuContent = React.useMemo(() => {
|
|
53
54
|
if (typeof menuItems === 'function') {
|
|
54
55
|
return menuItems(close);
|
|
55
56
|
}
|
|
56
57
|
return menuItems;
|
|
57
58
|
}, [menuItems, close]);
|
|
58
|
-
const
|
|
59
|
-
|
|
60
|
-
(
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
(instance) => {
|
|
68
|
-
setIsVisible(false);
|
|
69
|
-
targetRef.current?.focus();
|
|
70
|
-
onHide?.(instance);
|
|
71
|
-
},
|
|
72
|
-
[onHide],
|
|
59
|
+
const popover = (0, index_js_1.usePopover)({
|
|
60
|
+
visible,
|
|
61
|
+
onVisibleChange: (open) => (open ? setVisible(true) : close()),
|
|
62
|
+
placement,
|
|
63
|
+
matchWidth,
|
|
64
|
+
});
|
|
65
|
+
const popoverRef = (0, index_js_1.useMergedRefs)(
|
|
66
|
+
forwardedRef,
|
|
67
|
+
popover.refs.setFloating,
|
|
73
68
|
);
|
|
74
69
|
return React.createElement(
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
70
|
+
React.Fragment,
|
|
71
|
+
null,
|
|
72
|
+
(0, index_js_1.cloneElementWithRef)(children, (children) => ({
|
|
73
|
+
...popover.getReferenceProps(children.props),
|
|
74
|
+
'aria-expanded': popover.open,
|
|
75
|
+
ref: (0, index_js_1.mergeRefs)(triggerRef, popover.refs.setReference),
|
|
76
|
+
})),
|
|
77
|
+
popover.open &&
|
|
78
|
+
React.createElement(
|
|
79
|
+
index_js_1.Portal,
|
|
80
|
+
{ portal: portal },
|
|
81
|
+
React.createElement(
|
|
82
|
+
index_js_2.Menu,
|
|
83
|
+
{
|
|
84
|
+
...popover.getFloatingProps({
|
|
85
|
+
role,
|
|
86
|
+
...rest,
|
|
87
|
+
onKeyDown: (0, index_js_1.mergeEventHandlers)(
|
|
88
|
+
props.onKeyDown,
|
|
89
|
+
(e) => {
|
|
90
|
+
if (e.defaultPrevented) {
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
if (e.key === 'Tab') {
|
|
94
|
+
close();
|
|
95
|
+
}
|
|
96
|
+
},
|
|
97
|
+
),
|
|
98
|
+
}),
|
|
99
|
+
ref: popoverRef,
|
|
96
100
|
},
|
|
97
|
-
|
|
98
|
-
|
|
101
|
+
menuContent,
|
|
102
|
+
),
|
|
103
|
+
),
|
|
99
104
|
);
|
|
100
|
-
};
|
|
101
|
-
exports.DropdownMenu = DropdownMenu;
|
|
105
|
+
});
|
|
102
106
|
exports.default = exports.DropdownMenu;
|
|
@@ -27,8 +27,7 @@ exports.HeaderDropdownButton = React.forwardRef((props, ref) => {
|
|
|
27
27
|
{
|
|
28
28
|
menuItems: menuItems,
|
|
29
29
|
style: { minInlineSize: menuWidth },
|
|
30
|
-
|
|
31
|
-
onHide: () => setIsMenuOpen(false),
|
|
30
|
+
onVisibleChange: (open) => setIsMenuOpen(open),
|
|
32
31
|
},
|
|
33
32
|
React.createElement(
|
|
34
33
|
HeaderBasicButton_js_1.HeaderBasicButton,
|
|
@@ -48,8 +48,7 @@ exports.HeaderSplitButton = React.forwardRef((props, forwardedRef) => {
|
|
|
48
48
|
placement: menuPlacement,
|
|
49
49
|
menuItems: menuItems,
|
|
50
50
|
style: { minInlineSize: menuWidth },
|
|
51
|
-
|
|
52
|
-
onHide: React.useCallback(() => setIsMenuOpen(false), []),
|
|
51
|
+
onVisibleChange: (open) => setIsMenuOpen(open),
|
|
53
52
|
},
|
|
54
53
|
React.createElement(
|
|
55
54
|
index_js_2.ButtonBase,
|
package/cjs/core/Menu/Menu.js
CHANGED
|
@@ -69,9 +69,9 @@ exports.Menu = React.forwardRef((props, ref) => {
|
|
|
69
69
|
as: 'div',
|
|
70
70
|
className: (0, classnames_1.default)('iui-menu', className),
|
|
71
71
|
role: 'menu',
|
|
72
|
-
onKeyDown: onKeyDown,
|
|
73
72
|
ref: refs,
|
|
74
73
|
...rest,
|
|
74
|
+
onKeyDown: (0, index_js_1.mergeEventHandlers)(props.onKeyDown, onKeyDown),
|
|
75
75
|
});
|
|
76
76
|
});
|
|
77
77
|
exports.default = exports.Menu;
|
|
@@ -10,14 +10,18 @@ const React = tslib_1.__importStar(require('react'));
|
|
|
10
10
|
const index_js_1 = require('../utils/index.js');
|
|
11
11
|
const Menu_js_1 = require('./Menu.js');
|
|
12
12
|
const ListItem_js_1 = require('../List/ListItem.js');
|
|
13
|
+
const react_dom_1 = require('react-dom');
|
|
13
14
|
/**
|
|
14
15
|
* Context used to provide menu item ref to sub-menu items.
|
|
15
16
|
*/
|
|
16
|
-
const MenuItemContext = React.createContext({
|
|
17
|
+
const MenuItemContext = React.createContext({
|
|
18
|
+
ref: undefined,
|
|
19
|
+
setIsNestedSubmenuVisible: () => {},
|
|
20
|
+
});
|
|
17
21
|
/**
|
|
18
22
|
* Basic menu item component. Should be used inside `Menu` component for each item.
|
|
19
23
|
*/
|
|
20
|
-
exports.MenuItem = React.forwardRef((props,
|
|
24
|
+
exports.MenuItem = React.forwardRef((props, forwardedRef) => {
|
|
21
25
|
const {
|
|
22
26
|
children,
|
|
23
27
|
isSelected,
|
|
@@ -26,21 +30,33 @@ exports.MenuItem = React.forwardRef((props, ref) => {
|
|
|
26
30
|
onClick,
|
|
27
31
|
sublabel,
|
|
28
32
|
size = !!sublabel ? 'large' : 'default',
|
|
29
|
-
startIcon: startIconProp,
|
|
30
33
|
icon,
|
|
31
|
-
|
|
34
|
+
startIcon = icon,
|
|
32
35
|
badge,
|
|
36
|
+
endIcon = badge,
|
|
33
37
|
role = 'menuitem',
|
|
34
38
|
subMenuItems = [],
|
|
35
39
|
...rest
|
|
36
40
|
} = props;
|
|
37
41
|
const menuItemRef = React.useRef(null);
|
|
38
|
-
const
|
|
39
|
-
const
|
|
40
|
-
const subMenuRef = React.useRef(null);
|
|
42
|
+
const [focusOnSubmenu, setFocusOnSubmenu] = React.useState(false);
|
|
43
|
+
const submenuId = (0, index_js_1.useId)();
|
|
41
44
|
const [isSubmenuVisible, setIsSubmenuVisible] = React.useState(false);
|
|
42
|
-
const
|
|
43
|
-
|
|
45
|
+
const [isNestedSubmenuVisible, setIsNestedSubmenuVisible] =
|
|
46
|
+
React.useState(false);
|
|
47
|
+
const parent = React.useContext(MenuItemContext);
|
|
48
|
+
const onVisibleChange = (open) => {
|
|
49
|
+
setIsSubmenuVisible(open);
|
|
50
|
+
// we don't want parent to close when mouse goes into a nested submenu,
|
|
51
|
+
// so we need to let the parent know whether the submenu is still open.
|
|
52
|
+
parent.setIsNestedSubmenuVisible(open);
|
|
53
|
+
};
|
|
54
|
+
const popover = (0, index_js_1.usePopover)({
|
|
55
|
+
visible: isSubmenuVisible || isNestedSubmenuVisible,
|
|
56
|
+
onVisibleChange,
|
|
57
|
+
placement: 'right-start',
|
|
58
|
+
trigger: { hover: true, focus: true },
|
|
59
|
+
});
|
|
44
60
|
const onKeyDown = (event) => {
|
|
45
61
|
if (event.altKey) {
|
|
46
62
|
return;
|
|
@@ -56,22 +72,37 @@ exports.MenuItem = React.forwardRef((props, ref) => {
|
|
|
56
72
|
case 'ArrowRight': {
|
|
57
73
|
if (subMenuItems.length > 0) {
|
|
58
74
|
setIsSubmenuVisible(true);
|
|
75
|
+
// flush and reset state so we are ready to focus again next time
|
|
76
|
+
(0, react_dom_1.flushSync)(() => setFocusOnSubmenu(true));
|
|
77
|
+
setFocusOnSubmenu(false);
|
|
59
78
|
event.preventDefault();
|
|
60
79
|
event.stopPropagation();
|
|
61
80
|
}
|
|
62
81
|
break;
|
|
63
82
|
}
|
|
64
83
|
case 'ArrowLeft': {
|
|
65
|
-
|
|
84
|
+
if (parent.ref) {
|
|
85
|
+
parent.ref.current?.focus();
|
|
86
|
+
parent.setIsNestedSubmenuVisible(false);
|
|
87
|
+
}
|
|
66
88
|
event.stopPropagation();
|
|
67
89
|
event.preventDefault();
|
|
68
90
|
break;
|
|
69
91
|
}
|
|
92
|
+
case 'Escape': {
|
|
93
|
+
// focus might get lost if submenu closes so move it back to parent
|
|
94
|
+
parent.ref?.current?.focus();
|
|
95
|
+
break;
|
|
96
|
+
}
|
|
70
97
|
default:
|
|
71
98
|
break;
|
|
72
99
|
}
|
|
73
100
|
};
|
|
74
|
-
const
|
|
101
|
+
const handlers = {
|
|
102
|
+
onClick: () => !disabled && onClick?.(value),
|
|
103
|
+
onKeyDown,
|
|
104
|
+
};
|
|
105
|
+
return React.createElement(
|
|
75
106
|
ListItem_js_1.ListItem,
|
|
76
107
|
{
|
|
77
108
|
as: 'div',
|
|
@@ -79,24 +110,21 @@ exports.MenuItem = React.forwardRef((props, ref) => {
|
|
|
79
110
|
size: size,
|
|
80
111
|
active: isSelected,
|
|
81
112
|
disabled: disabled,
|
|
82
|
-
|
|
83
|
-
|
|
113
|
+
ref: (0, index_js_1.useMergedRefs)(
|
|
114
|
+
menuItemRef,
|
|
115
|
+
forwardedRef,
|
|
116
|
+
subMenuItems.length > 0 ? popover.refs.setReference : null,
|
|
117
|
+
),
|
|
84
118
|
role: role,
|
|
85
119
|
tabIndex: disabled || role === 'presentation' ? undefined : -1,
|
|
86
120
|
'aria-selected': isSelected,
|
|
87
|
-
'aria-haspopup': subMenuItems.length > 0,
|
|
121
|
+
'aria-haspopup': subMenuItems.length > 0 ? 'true' : undefined,
|
|
122
|
+
'aria-controls': subMenuItems.length > 0 ? submenuId : undefined,
|
|
123
|
+
'aria-expanded': subMenuItems.length > 0 ? popover.open : undefined,
|
|
88
124
|
'aria-disabled': disabled,
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
if (
|
|
93
|
-
!(e.relatedTarget instanceof Node) ||
|
|
94
|
-
!subMenuRef.current?.contains(e.relatedTarget)
|
|
95
|
-
) {
|
|
96
|
-
setIsSubmenuVisible(false);
|
|
97
|
-
}
|
|
98
|
-
},
|
|
99
|
-
...rest,
|
|
125
|
+
...(subMenuItems.length === 0
|
|
126
|
+
? { ...handlers, ...rest }
|
|
127
|
+
: popover.getReferenceProps({ ...handlers, ...rest })),
|
|
100
128
|
},
|
|
101
129
|
startIcon &&
|
|
102
130
|
React.createElement(
|
|
@@ -124,38 +152,32 @@ exports.MenuItem = React.forwardRef((props, ref) => {
|
|
|
124
152
|
{ as: 'span', 'aria-hidden': true },
|
|
125
153
|
endIcon,
|
|
126
154
|
),
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
{ value: { ref: menuItemRef } },
|
|
155
|
+
subMenuItems.length > 0 &&
|
|
156
|
+
popover.open &&
|
|
157
|
+
React.createElement(
|
|
158
|
+
index_js_1.Portal,
|
|
159
|
+
null,
|
|
133
160
|
React.createElement(
|
|
134
|
-
|
|
135
|
-
{
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
{
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
setIsSubmenuVisible(false);
|
|
161
|
+
MenuItemContext.Provider,
|
|
162
|
+
{ value: { ref: menuItemRef, setIsNestedSubmenuVisible } },
|
|
163
|
+
React.createElement(
|
|
164
|
+
Menu_js_1.Menu,
|
|
165
|
+
{
|
|
166
|
+
setFocus: focusOnSubmenu,
|
|
167
|
+
ref: popover.refs.setFloating,
|
|
168
|
+
...popover.getFloatingProps({
|
|
169
|
+
id: submenuId,
|
|
170
|
+
onPointerMove: () => {
|
|
171
|
+
// pointer might move into a nested submenu and set isSubmenuVisible to false,
|
|
172
|
+
// so we need to flip it back to true when pointer re-enters this submenu.
|
|
173
|
+
setIsSubmenuVisible(true);
|
|
148
174
|
},
|
|
149
|
-
},
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
subMenuItems,
|
|
154
|
-
),
|
|
155
|
-
),
|
|
156
|
-
},
|
|
157
|
-
listItem,
|
|
175
|
+
}),
|
|
176
|
+
},
|
|
177
|
+
subMenuItems,
|
|
178
|
+
),
|
|
158
179
|
),
|
|
159
|
-
)
|
|
180
|
+
),
|
|
181
|
+
);
|
|
160
182
|
});
|
|
161
183
|
exports.default = exports.MenuItem;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
|
-
import
|
|
2
|
+
import { usePopover } from '../utils/index.js';
|
|
3
|
+
import type { CommonProps } from '../utils/index.js';
|
|
3
4
|
export type ItemRendererProps = {
|
|
4
5
|
/**
|
|
5
6
|
* Close handler that closes the dropdown.
|
|
@@ -110,15 +111,14 @@ export type SelectProps<T> = {
|
|
|
110
111
|
*/
|
|
111
112
|
menuStyle?: React.CSSProperties;
|
|
112
113
|
/**
|
|
113
|
-
* Props to customize
|
|
114
|
-
* @see [tippy.js props](https://atomiks.github.io/tippyjs/v6/all-props/)
|
|
114
|
+
* Props to customize Popover behavior.
|
|
115
115
|
*/
|
|
116
|
-
popoverProps?:
|
|
116
|
+
popoverProps?: Pick<Parameters<typeof usePopover>[0], 'visible' | 'onVisibleChange' | 'placement' | 'matchWidth' | 'closeOnOutsideClick'>;
|
|
117
117
|
/**
|
|
118
118
|
* Props to pass to the select button (trigger) element.
|
|
119
119
|
*/
|
|
120
120
|
triggerProps?: React.ComponentPropsWithoutRef<'div'>;
|
|
121
|
-
} & SelectMultipleTypeProps<T> &
|
|
121
|
+
} & SelectMultipleTypeProps<T> & Omit<React.ComponentPropsWithoutRef<'div'>, 'size' | 'disabled' | 'placeholder' | 'onChange'>;
|
|
122
122
|
/**
|
|
123
123
|
* Select component to select value from options.
|
|
124
124
|
* Generic type is used for value. It prevents you from mistakenly using other types in `options`, `value` and `onChange`.
|