@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
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
useLatestRef,
|
|
13
13
|
useIsomorphicLayoutEffect,
|
|
14
14
|
AutoclearingHiddenLiveRegion,
|
|
15
|
+
usePopover,
|
|
15
16
|
} from '../utils/index.js';
|
|
16
17
|
import {
|
|
17
18
|
ComboBoxActionContext,
|
|
@@ -19,7 +20,6 @@ import {
|
|
|
19
20
|
ComboBoxRefsContext,
|
|
20
21
|
ComboBoxStateContext,
|
|
21
22
|
} from './helpers.js';
|
|
22
|
-
import { ComboBoxDropdown } from './ComboBoxDropdown.js';
|
|
23
23
|
import { ComboBoxEndIcon } from './ComboBoxEndIcon.js';
|
|
24
24
|
import { ComboBoxInput } from './ComboBoxInput.js';
|
|
25
25
|
import { ComboBoxInputContainer } from './ComboBoxInputContainer.js';
|
|
@@ -62,8 +62,8 @@ export const ComboBox = (props) => {
|
|
|
62
62
|
itemRenderer,
|
|
63
63
|
enableVirtualization = false,
|
|
64
64
|
multiple = false,
|
|
65
|
-
onShow,
|
|
66
|
-
onHide,
|
|
65
|
+
onShow: onShowProp,
|
|
66
|
+
onHide: onHideProp,
|
|
67
67
|
...rest
|
|
68
68
|
} = props;
|
|
69
69
|
// Generate a stateful random id if not specified
|
|
@@ -121,6 +121,16 @@ export const ComboBox = (props) => {
|
|
|
121
121
|
focusedIndex: -1,
|
|
122
122
|
},
|
|
123
123
|
);
|
|
124
|
+
const onShowRef = useLatestRef(onShowProp);
|
|
125
|
+
const onHideRef = useLatestRef(onHideProp);
|
|
126
|
+
const show = React.useCallback(() => {
|
|
127
|
+
dispatch({ type: 'open' });
|
|
128
|
+
onShowRef.current?.();
|
|
129
|
+
}, [onShowRef]);
|
|
130
|
+
const hide = React.useCallback(() => {
|
|
131
|
+
dispatch({ type: 'close' });
|
|
132
|
+
onHideRef.current?.();
|
|
133
|
+
}, [onHideRef]);
|
|
124
134
|
useIsomorphicLayoutEffect(() => {
|
|
125
135
|
// When the dropdown opens
|
|
126
136
|
if (isOpen) {
|
|
@@ -145,13 +155,6 @@ export const ComboBox = (props) => {
|
|
|
145
155
|
}
|
|
146
156
|
}
|
|
147
157
|
}, [isOpen, multiple, optionsRef, selected]);
|
|
148
|
-
// Set min-width of menu to be same as input
|
|
149
|
-
const [minWidth, setMinWidth] = React.useState(0);
|
|
150
|
-
React.useEffect(() => {
|
|
151
|
-
if (inputRef.current) {
|
|
152
|
-
setMinWidth(inputRef.current.offsetWidth);
|
|
153
|
-
}
|
|
154
|
-
}, [isOpen]);
|
|
155
158
|
// Update filtered options to the latest value options according to input value
|
|
156
159
|
const [filteredOptions, setFilteredOptions] = React.useState(options);
|
|
157
160
|
React.useEffect(() => {
|
|
@@ -178,7 +181,7 @@ export const ComboBox = (props) => {
|
|
|
178
181
|
(event) => {
|
|
179
182
|
const { value } = event.currentTarget;
|
|
180
183
|
setInputValue(value);
|
|
181
|
-
|
|
184
|
+
show(); // reopen when typing
|
|
182
185
|
setFilteredOptions(
|
|
183
186
|
filterFunction?.(optionsRef.current, value) ??
|
|
184
187
|
optionsRef.current.filter((option) =>
|
|
@@ -190,7 +193,7 @@ export const ComboBox = (props) => {
|
|
|
190
193
|
}
|
|
191
194
|
inputProps?.onChange?.(event);
|
|
192
195
|
},
|
|
193
|
-
[filterFunction, focusedIndex, inputProps, optionsRef],
|
|
196
|
+
[filterFunction, focusedIndex, inputProps, optionsRef, show],
|
|
194
197
|
);
|
|
195
198
|
// When the value prop changes, update the selected index/indices
|
|
196
199
|
React.useEffect(() => {
|
|
@@ -280,7 +283,7 @@ export const ComboBox = (props) => {
|
|
|
280
283
|
);
|
|
281
284
|
} else {
|
|
282
285
|
dispatch({ type: 'select', value: __originalIndex });
|
|
283
|
-
|
|
286
|
+
hide();
|
|
284
287
|
onChangeHandler(__originalIndex);
|
|
285
288
|
}
|
|
286
289
|
},
|
|
@@ -291,6 +294,7 @@ export const ComboBox = (props) => {
|
|
|
291
294
|
onChangeHandler,
|
|
292
295
|
selected,
|
|
293
296
|
optionsRef,
|
|
297
|
+
hide,
|
|
294
298
|
],
|
|
295
299
|
);
|
|
296
300
|
const getMenuItem = React.useCallback(
|
|
@@ -366,6 +370,13 @@ export const ComboBox = (props) => {
|
|
|
366
370
|
),
|
|
367
371
|
[emptyStateMessage],
|
|
368
372
|
);
|
|
373
|
+
const popover = usePopover({
|
|
374
|
+
visible: isOpen,
|
|
375
|
+
onVisibleChange: (open) => (open ? show() : hide()),
|
|
376
|
+
matchWidth: true,
|
|
377
|
+
closeOnOutsideClick: true,
|
|
378
|
+
trigger: { focus: true },
|
|
379
|
+
});
|
|
369
380
|
return React.createElement(
|
|
370
381
|
ComboBoxRefsContext.Provider,
|
|
371
382
|
{ value: { inputRef, menuRef, optionsExtraInfoRef } },
|
|
@@ -377,7 +388,6 @@ export const ComboBox = (props) => {
|
|
|
377
388
|
{
|
|
378
389
|
value: {
|
|
379
390
|
id,
|
|
380
|
-
minWidth,
|
|
381
391
|
isOpen,
|
|
382
392
|
focusedIndex,
|
|
383
393
|
onClickHandler,
|
|
@@ -385,11 +395,14 @@ export const ComboBox = (props) => {
|
|
|
385
395
|
filteredOptions,
|
|
386
396
|
getMenuItem,
|
|
387
397
|
multiple,
|
|
398
|
+
popover,
|
|
399
|
+
show,
|
|
400
|
+
hide,
|
|
388
401
|
},
|
|
389
402
|
},
|
|
390
403
|
React.createElement(
|
|
391
404
|
ComboBoxInputContainer,
|
|
392
|
-
{ ...rest },
|
|
405
|
+
{ disabled: inputProps?.disabled, ...rest },
|
|
393
406
|
React.createElement(
|
|
394
407
|
React.Fragment,
|
|
395
408
|
null,
|
|
@@ -420,15 +433,11 @@ export const ComboBox = (props) => {
|
|
|
420
433
|
: null,
|
|
421
434
|
),
|
|
422
435
|
React.createElement(
|
|
423
|
-
|
|
424
|
-
{
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
filteredOptions.length > 0 && !enableVirtualization
|
|
429
|
-
? filteredOptions.map(getMenuItem)
|
|
430
|
-
: emptyContent,
|
|
431
|
-
),
|
|
436
|
+
ComboBoxMenu,
|
|
437
|
+
{ as: 'div', ...dropdownMenuProps },
|
|
438
|
+
filteredOptions.length > 0 && !enableVirtualization
|
|
439
|
+
? filteredOptions.map(getMenuItem)
|
|
440
|
+
: emptyContent,
|
|
432
441
|
),
|
|
433
442
|
),
|
|
434
443
|
),
|
|
@@ -19,9 +19,9 @@ import {
|
|
|
19
19
|
export const ComboBoxInput = React.forwardRef((props, forwardedRef) => {
|
|
20
20
|
const {
|
|
21
21
|
onKeyDown: onKeyDownProp,
|
|
22
|
-
onFocus: onFocusProp,
|
|
23
22
|
onClick: onClickProp,
|
|
24
23
|
selectTags,
|
|
24
|
+
size,
|
|
25
25
|
...rest
|
|
26
26
|
} = props;
|
|
27
27
|
const {
|
|
@@ -31,11 +31,14 @@ export const ComboBoxInput = React.forwardRef((props, forwardedRef) => {
|
|
|
31
31
|
enableVirtualization,
|
|
32
32
|
multiple,
|
|
33
33
|
onClickHandler,
|
|
34
|
+
popover,
|
|
35
|
+
show,
|
|
36
|
+
hide,
|
|
34
37
|
} = useSafeContext(ComboBoxStateContext);
|
|
35
38
|
const dispatch = useSafeContext(ComboBoxActionContext);
|
|
36
39
|
const { inputRef, menuRef, optionsExtraInfoRef } =
|
|
37
40
|
useSafeContext(ComboBoxRefsContext);
|
|
38
|
-
const refs = useMergedRefs(inputRef, forwardedRef);
|
|
41
|
+
const refs = useMergedRefs(inputRef, popover.refs.setReference, forwardedRef);
|
|
39
42
|
const focusedIndexRef = React.useRef(focusedIndex ?? -1);
|
|
40
43
|
React.useEffect(() => {
|
|
41
44
|
focusedIndexRef.current = focusedIndex ?? -1;
|
|
@@ -47,7 +50,6 @@ export const ComboBoxInput = React.forwardRef((props, forwardedRef) => {
|
|
|
47
50
|
};
|
|
48
51
|
const handleKeyDown = React.useCallback(
|
|
49
52
|
(event) => {
|
|
50
|
-
onKeyDownProp?.(event);
|
|
51
53
|
const length = Object.keys(optionsExtraInfoRef.current).length ?? 0;
|
|
52
54
|
if (event.altKey) {
|
|
53
55
|
return;
|
|
@@ -56,7 +58,7 @@ export const ComboBoxInput = React.forwardRef((props, forwardedRef) => {
|
|
|
56
58
|
case 'ArrowDown': {
|
|
57
59
|
event.preventDefault();
|
|
58
60
|
if (!isOpen) {
|
|
59
|
-
return
|
|
61
|
+
return show();
|
|
60
62
|
}
|
|
61
63
|
if (length === 0) {
|
|
62
64
|
return;
|
|
@@ -98,7 +100,7 @@ export const ComboBoxInput = React.forwardRef((props, forwardedRef) => {
|
|
|
98
100
|
case 'ArrowUp': {
|
|
99
101
|
event.preventDefault();
|
|
100
102
|
if (!isOpen) {
|
|
101
|
-
return
|
|
103
|
+
return show();
|
|
102
104
|
}
|
|
103
105
|
if (length === 0) {
|
|
104
106
|
return;
|
|
@@ -142,17 +144,17 @@ export const ComboBoxInput = React.forwardRef((props, forwardedRef) => {
|
|
|
142
144
|
onClickHandler?.(focusedIndexRef.current);
|
|
143
145
|
}
|
|
144
146
|
} else {
|
|
145
|
-
|
|
147
|
+
show();
|
|
146
148
|
}
|
|
147
149
|
break;
|
|
148
150
|
}
|
|
149
151
|
case 'Escape': {
|
|
150
152
|
event.preventDefault();
|
|
151
|
-
|
|
153
|
+
hide();
|
|
152
154
|
break;
|
|
153
155
|
}
|
|
154
156
|
case 'Tab':
|
|
155
|
-
|
|
157
|
+
hide();
|
|
156
158
|
break;
|
|
157
159
|
}
|
|
158
160
|
},
|
|
@@ -162,31 +164,26 @@ export const ComboBoxInput = React.forwardRef((props, forwardedRef) => {
|
|
|
162
164
|
isOpen,
|
|
163
165
|
menuRef,
|
|
164
166
|
onClickHandler,
|
|
165
|
-
onKeyDownProp,
|
|
166
167
|
optionsExtraInfoRef,
|
|
168
|
+
show,
|
|
169
|
+
hide,
|
|
167
170
|
],
|
|
168
171
|
);
|
|
169
|
-
const handleFocus = React.useCallback(
|
|
170
|
-
(event) => {
|
|
171
|
-
dispatch({ type: 'open' });
|
|
172
|
-
onFocusProp?.(event);
|
|
173
|
-
},
|
|
174
|
-
[dispatch, onFocusProp],
|
|
175
|
-
);
|
|
176
172
|
const handleClick = React.useCallback(() => {
|
|
177
173
|
if (!isOpen) {
|
|
178
|
-
|
|
174
|
+
show();
|
|
175
|
+
} else {
|
|
176
|
+
hide();
|
|
179
177
|
}
|
|
180
|
-
}, [
|
|
178
|
+
}, [hide, isOpen, show]);
|
|
181
179
|
const [tagContainerWidthRef, tagContainerWidth] = useContainerWidth();
|
|
182
180
|
return React.createElement(
|
|
183
181
|
React.Fragment,
|
|
184
182
|
null,
|
|
185
183
|
React.createElement(Input, {
|
|
186
184
|
ref: refs,
|
|
187
|
-
onKeyDown: handleKeyDown,
|
|
188
185
|
onClick: mergeEventHandlers(onClickProp, handleClick),
|
|
189
|
-
|
|
186
|
+
'aria-expanded': isOpen,
|
|
190
187
|
'aria-activedescendant':
|
|
191
188
|
isOpen && focusedIndex != undefined && focusedIndex > -1
|
|
192
189
|
? getIdFromIndex(focusedIndex)
|
|
@@ -199,7 +196,11 @@ export const ComboBoxInput = React.forwardRef((props, forwardedRef) => {
|
|
|
199
196
|
autoCorrect: 'off',
|
|
200
197
|
style: multiple ? { paddingInlineStart: tagContainerWidth + 18 } : {},
|
|
201
198
|
'aria-describedby': multiple ? `${id}-selected-live` : undefined,
|
|
202
|
-
|
|
199
|
+
size: size,
|
|
200
|
+
...popover.getReferenceProps({
|
|
201
|
+
onKeyDown: mergeEventHandlers(onKeyDownProp, handleKeyDown),
|
|
202
|
+
...rest,
|
|
203
|
+
}),
|
|
203
204
|
}),
|
|
204
205
|
multiple && selectTags
|
|
205
206
|
? React.createElement(ComboBoxMultipleContainer, {
|
|
@@ -5,109 +5,89 @@
|
|
|
5
5
|
import cx from 'classnames';
|
|
6
6
|
import * as React from 'react';
|
|
7
7
|
import { Menu } from '../Menu/index.js';
|
|
8
|
-
import { Surface } from '../Surface/index.js';
|
|
9
8
|
import {
|
|
10
9
|
useSafeContext,
|
|
11
10
|
useMergedRefs,
|
|
12
11
|
useVirtualization,
|
|
13
|
-
|
|
14
|
-
|
|
12
|
+
Portal,
|
|
13
|
+
Box,
|
|
15
14
|
} from '../utils/index.js';
|
|
16
15
|
import { ComboBoxStateContext, ComboBoxRefsContext } from './helpers.js';
|
|
17
|
-
const
|
|
18
|
-
|
|
19
|
-
const
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
16
|
+
const VirtualizedComboBoxMenu = (props) => {
|
|
17
|
+
const { children, ...rest } = props;
|
|
18
|
+
const { filteredOptions, getMenuItem, focusedIndex } =
|
|
19
|
+
useSafeContext(ComboBoxStateContext);
|
|
20
|
+
const { menuRef } = useSafeContext(ComboBoxRefsContext);
|
|
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 } = useVirtualization({
|
|
43
|
-
// 'Fool' VirtualScroll by passing length 1
|
|
44
|
-
// whenever there is no elements, to show empty state message
|
|
45
|
-
itemsLength: filteredOptions.length || 1,
|
|
46
|
-
itemRenderer: virtualItemRenderer,
|
|
47
|
-
scrollToIndex: focusedVisibleIndex,
|
|
48
|
-
});
|
|
49
|
-
const surfaceStyles = {
|
|
50
|
-
minInlineSize: minWidth,
|
|
51
|
-
// set as constant because we don't want it shifting when items are unmounted
|
|
52
|
-
maxInlineSize: minWidth,
|
|
53
|
-
// max-height must be on the outermost element for virtual scroll
|
|
54
|
-
maxBlockSize: 'calc((var(--iui-component-height) - 1px) * 8.5)',
|
|
55
|
-
overflowY: isOverflowOverlaySupported() ? 'overlay' : 'auto',
|
|
56
|
-
...style,
|
|
57
|
-
};
|
|
58
|
-
return React.createElement(
|
|
59
|
-
Surface,
|
|
60
|
-
{ style: surfaceStyles },
|
|
61
|
-
React.createElement(
|
|
62
|
-
'div',
|
|
63
|
-
{ ...outerProps },
|
|
64
|
-
React.createElement(
|
|
65
|
-
Menu,
|
|
66
|
-
{
|
|
67
|
-
id: `${id}-list`,
|
|
68
|
-
setFocus: false,
|
|
69
|
-
role: 'listbox',
|
|
70
|
-
ref: mergeRefs(menuRef, innerProps.ref, forwardedRef),
|
|
71
|
-
className: className,
|
|
72
|
-
style: innerProps.style,
|
|
73
|
-
...rest,
|
|
74
|
-
},
|
|
75
|
-
visibleChildren,
|
|
76
|
-
),
|
|
77
|
-
),
|
|
32
|
+
if (!currentElement) {
|
|
33
|
+
return focusedIndex;
|
|
34
|
+
}
|
|
35
|
+
return Number(
|
|
36
|
+
currentElement.getAttribute('data-iui-filtered-index') ?? focusedIndex,
|
|
78
37
|
);
|
|
79
|
-
},
|
|
80
|
-
|
|
38
|
+
}, [focusedIndex, menuRef]);
|
|
39
|
+
const { outerProps, innerProps, visibleChildren } = useVirtualization({
|
|
40
|
+
// 'Fool' VirtualScroll by passing length 1
|
|
41
|
+
// whenever there is no elements, to show empty state message
|
|
42
|
+
itemsLength: filteredOptions.length || 1,
|
|
43
|
+
itemRenderer: virtualItemRenderer,
|
|
44
|
+
scrollToIndex: focusedVisibleIndex,
|
|
45
|
+
});
|
|
46
|
+
return React.createElement(
|
|
47
|
+
Box,
|
|
48
|
+
{ as: 'div', ...outerProps, ...rest },
|
|
49
|
+
React.createElement(
|
|
50
|
+
'div',
|
|
51
|
+
{ ...innerProps, ref: innerProps.ref },
|
|
52
|
+
visibleChildren,
|
|
53
|
+
),
|
|
54
|
+
);
|
|
55
|
+
};
|
|
81
56
|
export const ComboBoxMenu = React.forwardRef((props, forwardedRef) => {
|
|
82
|
-
const { className, style, ...rest } = props;
|
|
83
|
-
const {
|
|
57
|
+
const { className, children, style, ...rest } = props;
|
|
58
|
+
const { id, enableVirtualization, popover } =
|
|
84
59
|
useSafeContext(ComboBoxStateContext);
|
|
85
60
|
const { menuRef } = useSafeContext(ComboBoxRefsContext);
|
|
86
|
-
const refs = useMergedRefs(
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
React.Fragment,
|
|
96
|
-
null,
|
|
97
|
-
!enableVirtualization
|
|
98
|
-
? React.createElement(Menu, {
|
|
61
|
+
const refs = useMergedRefs(popover.refs.setFloating, forwardedRef, menuRef);
|
|
62
|
+
return (
|
|
63
|
+
popover.open &&
|
|
64
|
+
React.createElement(
|
|
65
|
+
Portal,
|
|
66
|
+
{ portal: true },
|
|
67
|
+
React.createElement(
|
|
68
|
+
Menu,
|
|
69
|
+
{
|
|
99
70
|
id: `${id}-list`,
|
|
100
|
-
style: { ...styles, ...style },
|
|
101
71
|
setFocus: false,
|
|
102
72
|
role: 'listbox',
|
|
103
73
|
ref: refs,
|
|
104
74
|
className: cx('iui-scroll', className),
|
|
105
|
-
...
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
75
|
+
...popover.getFloatingProps({
|
|
76
|
+
style: !enableVirtualization
|
|
77
|
+
? style
|
|
78
|
+
: {
|
|
79
|
+
// set as constant because we don't want it shifting when items are unmounted
|
|
80
|
+
maxInlineSize: 0,
|
|
81
|
+
...style,
|
|
82
|
+
},
|
|
83
|
+
...rest,
|
|
84
|
+
}),
|
|
85
|
+
},
|
|
86
|
+
!enableVirtualization
|
|
87
|
+
? children
|
|
88
|
+
: React.createElement(VirtualizedComboBoxMenu, null, children),
|
|
89
|
+
),
|
|
90
|
+
)
|
|
111
91
|
);
|
|
112
92
|
});
|
|
113
93
|
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;
|
|
@@ -3,11 +3,20 @@
|
|
|
3
3
|
* See LICENSE.md in the project root for license terms and full copyright notice.
|
|
4
4
|
*--------------------------------------------------------------------------------------------*/
|
|
5
5
|
import * as React from 'react';
|
|
6
|
-
import {
|
|
6
|
+
import {
|
|
7
|
+
Popover,
|
|
8
|
+
useMergedRefs,
|
|
9
|
+
usePopover,
|
|
10
|
+
Portal,
|
|
11
|
+
cloneElementWithRef,
|
|
12
|
+
useControlledState,
|
|
13
|
+
mergeRefs,
|
|
14
|
+
mergeEventHandlers,
|
|
15
|
+
} from '../utils/index.js';
|
|
7
16
|
import { Menu } from '../Menu/index.js';
|
|
8
17
|
/**
|
|
9
18
|
* Dropdown menu component.
|
|
10
|
-
*
|
|
19
|
+
* Built on top of the {@link Popover} component.
|
|
11
20
|
* @example
|
|
12
21
|
* const menuItems = (close: () => void) => [
|
|
13
22
|
* <MenuItem key={1} onClick={onClick(1, close)}>
|
|
@@ -24,74 +33,73 @@ import { Menu } from '../Menu/index.js';
|
|
|
24
33
|
* <Button>Menu</Button>
|
|
25
34
|
* </DropdownMenu>
|
|
26
35
|
*/
|
|
27
|
-
export const DropdownMenu = (props) => {
|
|
36
|
+
export const DropdownMenu = React.forwardRef((props, forwardedRef) => {
|
|
28
37
|
const {
|
|
29
38
|
menuItems,
|
|
30
39
|
children,
|
|
31
|
-
className,
|
|
32
|
-
style,
|
|
33
40
|
role = 'menu',
|
|
34
|
-
visible,
|
|
41
|
+
visible: visibleProp,
|
|
35
42
|
placement = 'bottom-start',
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
id,
|
|
43
|
+
matchWidth = false,
|
|
44
|
+
onVisibleChange,
|
|
45
|
+
portal = true,
|
|
40
46
|
...rest
|
|
41
47
|
} = props;
|
|
42
|
-
const [
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
const
|
|
48
|
+
const [visible, setVisible] = useControlledState(
|
|
49
|
+
false,
|
|
50
|
+
visibleProp,
|
|
51
|
+
onVisibleChange,
|
|
52
|
+
);
|
|
53
|
+
const triggerRef = React.useRef(null);
|
|
54
|
+
const close = React.useCallback(() => {
|
|
55
|
+
setVisible(false);
|
|
56
|
+
triggerRef.current?.focus({ preventScroll: true });
|
|
57
|
+
}, [setVisible]);
|
|
48
58
|
const menuContent = React.useMemo(() => {
|
|
49
59
|
if (typeof menuItems === 'function') {
|
|
50
60
|
return menuItems(close);
|
|
51
61
|
}
|
|
52
62
|
return menuItems;
|
|
53
63
|
}, [menuItems, close]);
|
|
54
|
-
const
|
|
55
|
-
|
|
56
|
-
(
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
);
|
|
62
|
-
const onHideHandler = React.useCallback(
|
|
63
|
-
(instance) => {
|
|
64
|
-
setIsVisible(false);
|
|
65
|
-
targetRef.current?.focus();
|
|
66
|
-
onHide?.(instance);
|
|
67
|
-
},
|
|
68
|
-
[onHide],
|
|
69
|
-
);
|
|
64
|
+
const popover = usePopover({
|
|
65
|
+
visible,
|
|
66
|
+
onVisibleChange: (open) => (open ? setVisible(true) : close()),
|
|
67
|
+
placement,
|
|
68
|
+
matchWidth,
|
|
69
|
+
});
|
|
70
|
+
const popoverRef = useMergedRefs(forwardedRef, popover.refs.setFloating);
|
|
70
71
|
return React.createElement(
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
72
|
+
React.Fragment,
|
|
73
|
+
null,
|
|
74
|
+
cloneElementWithRef(children, (children) => ({
|
|
75
|
+
...popover.getReferenceProps(children.props),
|
|
76
|
+
'aria-expanded': popover.open,
|
|
77
|
+
ref: mergeRefs(triggerRef, popover.refs.setReference),
|
|
78
|
+
})),
|
|
79
|
+
popover.open &&
|
|
80
|
+
React.createElement(
|
|
81
|
+
Portal,
|
|
82
|
+
{ portal: portal },
|
|
83
|
+
React.createElement(
|
|
84
|
+
Menu,
|
|
85
|
+
{
|
|
86
|
+
...popover.getFloatingProps({
|
|
87
|
+
role,
|
|
88
|
+
...rest,
|
|
89
|
+
onKeyDown: mergeEventHandlers(props.onKeyDown, (e) => {
|
|
90
|
+
if (e.defaultPrevented) {
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
if (e.key === 'Tab') {
|
|
94
|
+
close();
|
|
95
|
+
}
|
|
96
|
+
}),
|
|
97
|
+
}),
|
|
98
|
+
ref: popoverRef,
|
|
92
99
|
},
|
|
93
|
-
|
|
94
|
-
|
|
100
|
+
menuContent,
|
|
101
|
+
),
|
|
102
|
+
),
|
|
95
103
|
);
|
|
96
|
-
};
|
|
104
|
+
});
|
|
97
105
|
export default DropdownMenu;
|
|
@@ -27,8 +27,7 @@ export const 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,
|
|
@@ -46,8 +46,7 @@ export const HeaderSplitButton = React.forwardRef((props, forwardedRef) => {
|
|
|
46
46
|
placement: menuPlacement,
|
|
47
47
|
menuItems: menuItems,
|
|
48
48
|
style: { minInlineSize: menuWidth },
|
|
49
|
-
|
|
50
|
-
onHide: React.useCallback(() => setIsMenuOpen(false), []),
|
|
49
|
+
onVisibleChange: (open) => setIsMenuOpen(open),
|
|
51
50
|
},
|
|
52
51
|
React.createElement(
|
|
53
52
|
ButtonBase,
|