@octanejs/aria 0.0.46 → 0.0.48
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/README.md +7 -6
- package/UPSTREAM.md +58 -69
- package/package.json +11 -11
- package/src/calendar/useCalendarGrid.tsx +64 -62
- package/src/collections/Document.ts +3 -0
- package/src/color/useColorArea.tsx +95 -69
- package/src/color/useColorWheel.tsx +38 -37
- package/src/combobox/{useComboBox.ts → useComboBox.tsx} +243 -258
- package/src/components/ComboBox.ts +35 -2
- package/src/components/Dialog.ts +12 -1
- package/src/components/DropZone.tsx +3 -2
- package/src/components/FileTrigger.tsx +1 -0
- package/src/components/GridList.ts +11 -2
- package/src/components/ListBox.ts +1 -1
- package/src/components/Menu.ts +1 -0
- package/src/components/Meter.ts +3 -1
- package/src/components/Popover.ts +20 -8
- package/src/components/PreviewTrigger.tsx +101 -0
- package/src/components/ProgressBar.ts +10 -2
- package/src/components/Select.ts +1 -1
- package/src/components/Table.ts +26 -15
- package/src/components/TokenField.tsx +395 -0
- package/src/components/Tooltip.ts +4 -6
- package/src/components/Tree.ts +14 -7
- package/src/components/Virtualizer.tsx +30 -14
- package/src/components/index.ts +22 -0
- package/src/components/utils.ts +1 -1
- package/src/datepicker/useDateField.tsx +2 -11
- package/src/datepicker/useDatePickerGroup.tsx +44 -33
- package/src/datepicker/useDateSegment.tsx +31 -30
- package/src/dialog/useDialog.ts +8 -1
- package/src/dnd/DragManager.tsx +5 -1
- package/src/dnd/useDrag.tsx +2 -1
- package/src/focus/FocusScope.ts +2 -2
- package/src/grid/useGrid.ts +3 -0
- package/src/grid/{useGridCell.ts → useGridCell.tsx} +128 -66
- package/src/grid/utils.ts +1 -0
- package/src/gridlist/{useGridListItem.ts → useGridListItem.tsx} +154 -112
- package/src/index.ts +22 -0
- package/src/interactions/createEventHandler.ts +11 -8
- package/src/interactions/createKeyboardShortcutHandler.tsx +218 -0
- package/src/interactions/textSelection.ts +3 -3
- package/src/interactions/useContextMenu.tsx +125 -0
- package/src/interactions/useFocusVisible.ts +4 -4
- package/src/interactions/useKeyboard.ts +50 -2
- package/src/interactions/useLongPress.ts +33 -22
- package/src/interactions/useMove.ts +6 -6
- package/src/interactions/usePress.ts +8 -0
- package/src/interactions/utils.ts +10 -10
- package/src/intl/combobox/fi-FI.json +2 -2
- package/src/intl/previewtrigger/ar-AE.json +3 -0
- package/src/intl/previewtrigger/bg-BG.json +3 -0
- package/src/intl/previewtrigger/cs-CZ.json +3 -0
- package/src/intl/previewtrigger/da-DK.json +3 -0
- package/src/intl/previewtrigger/de-DE.json +3 -0
- package/src/intl/previewtrigger/el-GR.json +3 -0
- package/src/intl/previewtrigger/en-US.json +3 -0
- package/src/intl/previewtrigger/es-ES.json +3 -0
- package/src/intl/previewtrigger/et-EE.json +3 -0
- package/src/intl/previewtrigger/fi-FI.json +3 -0
- package/src/intl/previewtrigger/fr-FR.json +3 -0
- package/src/intl/previewtrigger/he-IL.json +3 -0
- package/src/intl/previewtrigger/hr-HR.json +3 -0
- package/src/intl/previewtrigger/hu-HU.json +3 -0
- package/src/intl/previewtrigger/index.ts +73 -0
- package/src/intl/previewtrigger/it-IT.json +3 -0
- package/src/intl/previewtrigger/ja-JP.json +3 -0
- package/src/intl/previewtrigger/ko-KR.json +3 -0
- package/src/intl/previewtrigger/lt-LT.json +3 -0
- package/src/intl/previewtrigger/lv-LV.json +3 -0
- package/src/intl/previewtrigger/nb-NO.json +3 -0
- package/src/intl/previewtrigger/nl-NL.json +3 -0
- package/src/intl/previewtrigger/pl-PL.json +3 -0
- package/src/intl/previewtrigger/pt-BR.json +3 -0
- package/src/intl/previewtrigger/pt-PT.json +3 -0
- package/src/intl/previewtrigger/ro-RO.json +3 -0
- package/src/intl/previewtrigger/ru-RU.json +3 -0
- package/src/intl/previewtrigger/sk-SK.json +3 -0
- package/src/intl/previewtrigger/sl-SI.json +3 -0
- package/src/intl/previewtrigger/sr-SP.json +3 -0
- package/src/intl/previewtrigger/sv-SE.json +3 -0
- package/src/intl/previewtrigger/tr-TR.json +3 -0
- package/src/intl/previewtrigger/uk-UA.json +3 -0
- package/src/intl/previewtrigger/zh-CN.json +3 -0
- package/src/intl/previewtrigger/zh-TW.json +3 -0
- package/src/intl/table/ar-AE.json +3 -3
- package/src/intl/table/bg-BG.json +3 -3
- package/src/intl/table/cs-CZ.json +3 -3
- package/src/intl/table/da-DK.json +3 -3
- package/src/intl/table/de-DE.json +3 -3
- package/src/intl/table/el-GR.json +3 -3
- package/src/intl/table/es-ES.json +3 -3
- package/src/intl/table/et-EE.json +3 -3
- package/src/intl/table/fi-FI.json +3 -3
- package/src/intl/table/fr-FR.json +3 -3
- package/src/intl/table/he-IL.json +3 -3
- package/src/intl/table/hr-HR.json +3 -3
- package/src/intl/table/hu-HU.json +3 -3
- package/src/intl/table/it-IT.json +3 -3
- package/src/intl/table/ja-JP.json +3 -3
- package/src/intl/table/ko-KR.json +3 -3
- package/src/intl/table/lt-LT.json +3 -3
- package/src/intl/table/lv-LV.json +3 -3
- package/src/intl/table/nb-NO.json +3 -3
- package/src/intl/table/nl-NL.json +3 -3
- package/src/intl/table/pl-PL.json +3 -3
- package/src/intl/table/pt-BR.json +3 -3
- package/src/intl/table/pt-PT.json +3 -3
- package/src/intl/table/ro-RO.json +3 -3
- package/src/intl/table/ru-RU.json +3 -3
- package/src/intl/table/sk-SK.json +3 -3
- package/src/intl/table/sl-SI.json +3 -3
- package/src/intl/table/sr-SP.json +3 -3
- package/src/intl/table/sv-SE.json +3 -3
- package/src/intl/table/tr-TR.json +3 -3
- package/src/intl/table/uk-UA.json +3 -3
- package/src/intl/table/zh-CN.json +4 -4
- package/src/intl/table/zh-TW.json +3 -3
- package/src/intl/tree/es-ES.json +1 -1
- package/src/link/useLink.ts +2 -0
- package/src/menu/useMenuItem.ts +33 -36
- package/src/menu/useMenuTrigger.tsx +222 -0
- package/src/menu/{useSubmenuTrigger.ts → useSubmenuTrigger.tsx} +116 -151
- package/src/numberfield/{useNumberField.ts → useNumberField.tsx} +188 -238
- package/src/overlays/Overlay.ts +5 -1
- package/src/overlays/ariaHideOutside.ts +6 -8
- package/src/overlays/useCloseOnScroll.ts +7 -5
- package/src/overlays/{useOverlay.ts → useOverlay.tsx} +76 -83
- package/src/overlays/usePopover.ts +10 -5
- package/src/overlays/usePreventScroll.ts +4 -4
- package/src/progress/useProgressBar.ts +2 -1
- package/src/radio/{useRadioGroup.ts → useRadioGroup.tsx} +87 -79
- package/src/searchfield/useSearchField.tsx +152 -0
- package/src/select/useSelect.ts +20 -20
- package/src/selection/useSelectableCollection.tsx +755 -0
- package/src/selection/useSelectableItem.ts +48 -11
- package/src/slider/{useSliderThumb.ts → useSliderThumb.tsx} +120 -158
- package/src/spinbutton/useSpinButton.tsx +335 -0
- package/src/stately/combobox/useComboBoxState.ts +1 -1
- package/src/stately/grid/useGridState.ts +9 -10
- package/src/stately/index.ts +11 -0
- package/src/stately/menu/useMenuTriggerState.ts +1 -1
- package/src/stately/menu/useSubmenuTriggerState.ts +2 -0
- package/src/stately/overlays/useOverlayTriggerState.ts +12 -1
- package/src/stately/radio/useRadioGroupState.ts +5 -1
- package/src/stately/table/TableUtils.ts +15 -1
- package/src/stately/table/useTableState.ts +7 -0
- package/src/stately/tokenfield/TokenFieldValue.tsx +440 -0
- package/src/stately/tokenfield/useTokenFieldState.tsx +57 -0
- package/src/stately/tooltip/useTooltipTriggerState.ts +16 -7
- package/src/stately/virtualizer/Layout.tsx +2 -0
- package/src/stately/virtualizer/ScrollAnchor.tsx +338 -0
- package/src/stately/virtualizer/Virtualizer.tsx +52 -1
- package/src/stately/virtualizer/types.tsx +2 -0
- package/src/table/useTableCell.ts +12 -0
- package/src/table/useTableColumnHeader.ts +8 -1
- package/src/table/useTableColumnResize.tsx +359 -0
- package/src/tag/useTag.ts +22 -15
- package/src/textfield/useTextField.ts +2 -0
- package/src/tokenfield/useToken.tsx +64 -0
- package/src/tokenfield/useTokenField.tsx +849 -0
- package/src/tooltip/usePreviewTrigger.tsx +266 -0
- package/src/tooltip/useSafeArea.tsx +171 -0
- package/src/upstream-exports/react-aria/mergeRefs.tsx +3 -0
- package/src/upstream-exports/react-aria/usePreviewTrigger.tsx +18 -0
- package/src/upstream-exports/react-aria/useTokenField.tsx +22 -0
- package/src/upstream-exports/react-stately/private/collections/getChildNodes.tsx +8 -0
- package/src/upstream-exports/react-stately/private/collections/getItemCount.tsx +2 -0
- package/src/upstream-exports/react-stately/private/grid/GridCollection.tsx +7 -0
- package/src/upstream-exports/react-stately/private/grid/useGridState.tsx +6 -0
- package/src/upstream-exports/react-stately/private/utils/number.tsx +2 -0
- package/src/upstream-exports/react-stately/useComboBoxState.tsx +23 -0
- package/src/upstream-exports/react-stately/useListState.tsx +15 -0
- package/src/upstream-exports/react-stately/useMenuTriggerState.tsx +26 -0
- package/src/upstream-exports/react-stately/useMultipleSelectionState.tsx +21 -0
- package/src/upstream-exports/react-stately/useNumberFieldState.tsx +20 -0
- package/src/upstream-exports/react-stately/useOverlayTriggerState.tsx +20 -0
- package/src/upstream-exports/react-stately/useRadioGroupState.tsx +16 -0
- package/src/upstream-exports/react-stately/useSearchFieldState.tsx +19 -0
- package/src/upstream-exports/react-stately/useSliderState.tsx +20 -0
- package/src/upstream-exports/react-stately/useTableState.tsx +45 -0
- package/src/upstream-exports/react-stately/useTokenFieldState.tsx +25 -0
- package/src/upstream-exports/react-stately/useTooltipTriggerState.tsx +20 -0
- package/src/upstream-exports/react-stately/useTreeState.tsx +14 -0
- package/src/utils/domHelpers.ts +74 -17
- package/src/utils/isVirtualEvent.ts +2 -1
- package/src/utils/openLink.ts +8 -3
- package/src/utils/platform.ts +7 -3
- package/src/utils/scrollIntoView.ts +4 -4
- package/src/utils/shadowdom/DOMFunctions.ts +42 -2
- package/src/utils/useId.ts +11 -2
- package/src/utils/useLoadMoreSentinel.ts +8 -3
- package/src/utils/useViewportSize.ts +3 -3
- package/src/virtualizer/ScrollView.tsx +13 -4
- package/src/virtualizer/VirtualizerItem.tsx +4 -1
- package/src/virtualizer/useVirtualizerItem.tsx +43 -3
- package/src/menu/useMenuTrigger.ts +0 -189
- package/src/searchfield/useSearchField.ts +0 -156
- package/src/selection/useSelectableCollection.ts +0 -750
- package/src/spinbutton/useSpinButton.ts +0 -380
- package/src/table/useTableColumnResize.ts +0 -374
|
@@ -1,18 +1,22 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
// -
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
1
|
+
/** @jsxImportSource octane */
|
|
2
|
+
type DOMAttributes = Record<string, any>;
|
|
3
|
+
// Ported from adobe/react-spectrum@5ecb3333001313e83898cd07644227897e3bae1f (packages/react-aria/src/combobox/useComboBox.ts).
|
|
4
|
+
/*
|
|
5
|
+
* Copyright 2020 Adobe. All rights reserved.
|
|
6
|
+
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
|
|
7
|
+
* you may not use this file except in compliance with the License. You may obtain a copy
|
|
8
|
+
* of the License at http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
*
|
|
10
|
+
* Unless required by applicable law or agreed to in writing, software distributed under
|
|
11
|
+
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
|
|
12
|
+
* OF ANY KIND, either express or implied. See the License for the specific language
|
|
13
|
+
* governing permissions and limitations under the License.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { announce } from '../live-announcer/LiveAnnouncer';
|
|
17
|
+
import { AriaButtonProps } from '../button/useButton';
|
|
18
|
+
import { ariaHideOutside } from '../overlays/ariaHideOutside';
|
|
19
|
+
import {
|
|
16
20
|
AriaLabelingProps,
|
|
17
21
|
DOMProps,
|
|
18
22
|
InputDOMProps,
|
|
@@ -23,32 +27,39 @@ import type {
|
|
|
23
27
|
RouterOptions,
|
|
24
28
|
ValidationResult,
|
|
25
29
|
} from '@react-types/shared';
|
|
26
|
-
import {
|
|
27
|
-
|
|
28
|
-
import { announce } from '../live-announcer/LiveAnnouncer';
|
|
29
|
-
import type { AriaButtonProps } from '../button/useButton';
|
|
30
|
-
import { ariaHideOutside } from '../overlays/ariaHideOutside';
|
|
31
|
-
import type { AriaListBoxOptions } from '../listbox/useListBox';
|
|
30
|
+
import { AriaListBoxOptions } from '../listbox/useListBox';
|
|
32
31
|
import { chain } from '../utils/chain';
|
|
33
32
|
import {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
} from '../stately/
|
|
33
|
+
ComboBoxProps,
|
|
34
|
+
ComboBoxState,
|
|
35
|
+
SelectionMode,
|
|
36
|
+
} from '../upstream-exports/react-stately/useComboBoxState';
|
|
38
37
|
import { dispatchVirtualFocus } from '../focus/virtualFocus';
|
|
38
|
+
import {
|
|
39
|
+
ElementType,
|
|
40
|
+
InputHTMLAttributes,
|
|
41
|
+
TouchEvent,
|
|
42
|
+
useEffect,
|
|
43
|
+
useMemo,
|
|
44
|
+
useRef,
|
|
45
|
+
useState,
|
|
46
|
+
} from '../compat/react';
|
|
39
47
|
import { getActiveElement, getEventTarget, nodeContains } from '../utils/shadowdom/DOMFunctions';
|
|
40
|
-
import { getChildNodes } from '../stately/collections/getChildNodes';
|
|
41
|
-
import { getItemCount } from '../stately/collections/getItemCount';
|
|
48
|
+
import { getChildNodes } from '../upstream-exports/react-stately/private/collections/getChildNodes';
|
|
49
|
+
import { getItemCount } from '../upstream-exports/react-stately/private/collections/getItemCount';
|
|
50
|
+
import { withSlot } from 'octane';
|
|
42
51
|
import { getItemId, listData } from '../listbox/utils';
|
|
43
52
|
import { getOwnerDocument } from '../utils/domHelpers';
|
|
44
|
-
import intlMessages from '../intl/combobox';
|
|
53
|
+
import intlMessages from '../intl/combobox/index';
|
|
45
54
|
import { isAppleDevice } from '../utils/platform';
|
|
46
55
|
import { ListKeyboardDelegate } from '../selection/ListKeyboardDelegate';
|
|
47
56
|
import { mergeProps } from '../utils/mergeProps';
|
|
48
|
-
import { privateValidationStateProp } from '../stately/form/useFormValidationState';
|
|
57
|
+
import { privateValidationStateProp } from '../upstream-exports/react-stately/private/form/useFormValidationState';
|
|
58
|
+
import { setInteractionModality } from '../interactions/useFocusVisible';
|
|
49
59
|
import { useEvent } from '../utils/useEvent';
|
|
50
60
|
import { useFormReset } from '../utils/useFormReset';
|
|
51
61
|
import { useId } from '../utils/useId';
|
|
62
|
+
import { useKeyboard } from '../interactions/useKeyboard';
|
|
52
63
|
import { useLabels } from '../utils/useLabels';
|
|
53
64
|
import { useLocalizedStringFormatter } from '../i18n/useLocalizedStringFormatter';
|
|
54
65
|
import { useMenuTrigger } from '../menu/useMenuTrigger';
|
|
@@ -57,12 +68,6 @@ import { useSelectableCollection } from '../selection/useSelectableCollection';
|
|
|
57
68
|
import { useTextField } from '../textfield/useTextField';
|
|
58
69
|
import { useUpdateEffect } from '../utils/useUpdateEffect';
|
|
59
70
|
|
|
60
|
-
import { S, splitSlot, subSlot } from '../internal';
|
|
61
|
-
|
|
62
|
-
// octane adaptation: element/DOM attribute types collapse to structural bags (upstream's
|
|
63
|
-
// versions are typed over React's SyntheticEvent / JSX intrinsic-element machinery).
|
|
64
|
-
type DOMAttributes = Record<string, any>;
|
|
65
|
-
|
|
66
71
|
export interface AriaComboBoxProps<T, M extends SelectionMode = 'single'>
|
|
67
72
|
extends ComboBoxProps<T, M>, DOMProps, InputDOMProps, AriaLabelingProps {
|
|
68
73
|
/** Whether keyboard navigation is circular. */
|
|
@@ -81,6 +86,12 @@ export interface AriaComboBoxOptions<T, M extends SelectionMode = 'single'> exte
|
|
|
81
86
|
listBoxRef: RefObject<HTMLElement | null>;
|
|
82
87
|
/** The ref for the optional list box popup trigger button. */
|
|
83
88
|
buttonRef?: RefObject<Element | null>;
|
|
89
|
+
/**
|
|
90
|
+
* The HTML element used to render the label, e.g. 'label', or 'span'.
|
|
91
|
+
*
|
|
92
|
+
* @default 'label'
|
|
93
|
+
*/
|
|
94
|
+
labelElementType?: ElementType;
|
|
84
95
|
/** An optional keyboard delegate implementation, to override the default. */
|
|
85
96
|
keyboardDelegate?: KeyboardDelegate;
|
|
86
97
|
/**
|
|
@@ -95,7 +106,7 @@ export interface ComboBoxAria<T> extends ValidationResult {
|
|
|
95
106
|
/** Props for the label element. */
|
|
96
107
|
labelProps: DOMAttributes;
|
|
97
108
|
/** Props for the combo box input element. */
|
|
98
|
-
inputProps:
|
|
109
|
+
inputProps: InputHTMLAttributes<HTMLInputElement>;
|
|
99
110
|
/** Props for the list box, to be passed to `useListBox`. */
|
|
100
111
|
listBoxProps: AriaListBoxOptions<T>;
|
|
101
112
|
/** Props for the optional trigger button, to be passed to `useButton`. */
|
|
@@ -119,18 +130,11 @@ export interface ComboBoxAria<T> extends ValidationResult {
|
|
|
119
130
|
export function useComboBox<T, M extends SelectionMode = 'single'>(
|
|
120
131
|
props: AriaComboBoxOptions<T, M>,
|
|
121
132
|
state: ComboBoxState<T, M>,
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
slot: symbol | undefined,
|
|
128
|
-
): ComboBoxAria<T>;
|
|
129
|
-
export function useComboBox(...args: any[]): ComboBoxAria<any> {
|
|
130
|
-
const [user, slotArg] = splitSlot(args);
|
|
131
|
-
const slot = slotArg ?? S('useComboBox');
|
|
132
|
-
const props = user[0] as AriaComboBoxOptions<any, any>;
|
|
133
|
-
const state = user[1] as ComboBoxState<any, any>;
|
|
133
|
+
_slot?: symbol,
|
|
134
|
+
): ComboBoxAria<T> {
|
|
135
|
+
if (_slot !== undefined) {
|
|
136
|
+
return withSlot(_slot, useComboBox<T, M>, props, state);
|
|
137
|
+
}
|
|
134
138
|
|
|
135
139
|
let {
|
|
136
140
|
buttonRef,
|
|
@@ -144,22 +148,17 @@ export function useComboBox(...args: any[]): ComboBoxAria<any> {
|
|
|
144
148
|
isReadOnly,
|
|
145
149
|
isDisabled,
|
|
146
150
|
} = props;
|
|
147
|
-
let backupBtnRef = useRef(null
|
|
151
|
+
let backupBtnRef = useRef(null);
|
|
148
152
|
buttonRef = buttonRef ?? backupBtnRef;
|
|
149
153
|
|
|
150
|
-
let stringFormatter = useLocalizedStringFormatter(
|
|
151
|
-
|
|
152
|
-
'@react-aria/combobox',
|
|
153
|
-
subSlot(slot, 'formatter'),
|
|
154
|
-
);
|
|
155
|
-
let { menuTriggerProps, menuProps } = useMenuTrigger<any>(
|
|
154
|
+
let stringFormatter = useLocalizedStringFormatter(intlMessages, '@react-aria/combobox');
|
|
155
|
+
let { menuTriggerProps, menuProps } = useMenuTrigger<T>(
|
|
156
156
|
{
|
|
157
157
|
type: 'listbox',
|
|
158
158
|
isDisabled: isDisabled || isReadOnly,
|
|
159
159
|
},
|
|
160
160
|
state,
|
|
161
161
|
buttonRef,
|
|
162
|
-
subSlot(slot, 'menuTrigger'),
|
|
163
162
|
);
|
|
164
163
|
|
|
165
164
|
// Set listbox id so it can be used when calling getItemId later
|
|
@@ -179,41 +178,28 @@ export function useComboBox(...args: any[]): ComboBoxAria<any> {
|
|
|
179
178
|
layoutDelegate,
|
|
180
179
|
}),
|
|
181
180
|
[keyboardDelegate, layoutDelegate, collection, disabledKeys, listBoxRef],
|
|
182
|
-
subSlot(slot, 'delegate'),
|
|
183
181
|
);
|
|
184
182
|
|
|
185
183
|
// Use useSelectableCollection to get the keyboard handlers to apply to the textfield
|
|
186
|
-
let { collectionProps } = useSelectableCollection(
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
},
|
|
197
|
-
subSlot(slot, 'selectable'),
|
|
198
|
-
);
|
|
184
|
+
let { collectionProps } = useSelectableCollection({
|
|
185
|
+
selectionManager: state.selectionManager,
|
|
186
|
+
keyboardDelegate: delegate,
|
|
187
|
+
disallowTypeAhead: true,
|
|
188
|
+
disallowEmptySelection: true,
|
|
189
|
+
shouldFocusWrap,
|
|
190
|
+
ref: inputRef,
|
|
191
|
+
// Prevent item scroll behavior from being applied here, should be handled in the user's Popover + ListBox component
|
|
192
|
+
isVirtualized: true,
|
|
193
|
+
});
|
|
199
194
|
|
|
200
195
|
let router = useRouter();
|
|
201
196
|
|
|
202
|
-
//
|
|
203
|
-
let
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
}
|
|
209
|
-
switch (e.key) {
|
|
210
|
-
case 'Enter':
|
|
211
|
-
case 'Tab':
|
|
212
|
-
// Prevent form submission if menu is open since we may be selecting a option
|
|
213
|
-
if (state.isOpen && e.key === 'Enter') {
|
|
214
|
-
e.preventDefault();
|
|
215
|
-
}
|
|
216
|
-
|
|
197
|
+
// for textfield specific operations
|
|
198
|
+
let { keyboardProps } = useKeyboard({
|
|
199
|
+
shortcuts: {
|
|
200
|
+
Enter: (e) => {
|
|
201
|
+
// Prevent default form submission if menu is open since we may be selecting a option
|
|
202
|
+
let shouldPreventDefault = state.isOpen;
|
|
217
203
|
// If the focused item is a link, trigger opening it. Items that are links are not selectable.
|
|
218
204
|
if (state.isOpen && listBoxRef.current && state.selectionManager.focusedKey != null) {
|
|
219
205
|
let collectionItem = state.collection.getItem(state.selectionManager.focusedKey);
|
|
@@ -221,7 +207,7 @@ export function useComboBox(...args: any[]): ComboBoxAria<any> {
|
|
|
221
207
|
let item = listBoxRef.current.querySelector(
|
|
222
208
|
`[data-key="${CSS.escape(state.selectionManager.focusedKey.toString())}"]`,
|
|
223
209
|
);
|
|
224
|
-
if (
|
|
210
|
+
if (item instanceof HTMLAnchorElement) {
|
|
225
211
|
router.open(
|
|
226
212
|
item,
|
|
227
213
|
e,
|
|
@@ -230,43 +216,70 @@ export function useComboBox(...args: any[]): ComboBoxAria<any> {
|
|
|
230
216
|
);
|
|
231
217
|
}
|
|
232
218
|
state.close();
|
|
233
|
-
|
|
219
|
+
return { shouldPreventDefault };
|
|
234
220
|
} else if (collectionItem?.props.onAction) {
|
|
235
221
|
collectionItem.props.onAction();
|
|
236
222
|
state.close();
|
|
237
|
-
|
|
223
|
+
return { shouldPreventDefault };
|
|
238
224
|
}
|
|
239
225
|
}
|
|
240
|
-
|
|
241
|
-
|
|
226
|
+
state.commit();
|
|
227
|
+
return { shouldPreventDefault };
|
|
228
|
+
},
|
|
229
|
+
Tab: () => {
|
|
230
|
+
// If the focused item is a link, trigger opening it. Items that are links are not selectable.
|
|
231
|
+
if (state.isOpen && listBoxRef.current && state.selectionManager.focusedKey != null) {
|
|
232
|
+
let collectionItem = state.collection.getItem(state.selectionManager.focusedKey);
|
|
233
|
+
if (collectionItem?.props.href) {
|
|
234
|
+
state.close();
|
|
235
|
+
return { shouldPreventDefault: false };
|
|
236
|
+
} else if (collectionItem?.props.onAction) {
|
|
237
|
+
collectionItem.props.onAction();
|
|
238
|
+
state.close();
|
|
239
|
+
return { shouldPreventDefault: false, shouldContinuePropagation: true };
|
|
240
|
+
}
|
|
242
241
|
}
|
|
243
|
-
if (
|
|
244
|
-
|
|
242
|
+
if (state.isOpen) {
|
|
243
|
+
state.commit();
|
|
245
244
|
}
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
245
|
+
return { shouldPreventDefault: false, shouldContinuePropagation: true };
|
|
246
|
+
},
|
|
247
|
+
Escape: () => {
|
|
248
|
+
let shouldContinuePropagation = false;
|
|
249
249
|
if (!state.selectionManager.isEmpty || state.inputValue === '' || props.allowsCustomValue) {
|
|
250
|
-
|
|
250
|
+
shouldContinuePropagation = true;
|
|
251
251
|
}
|
|
252
252
|
state.revert();
|
|
253
|
-
|
|
254
|
-
|
|
253
|
+
return { shouldContinuePropagation };
|
|
254
|
+
},
|
|
255
|
+
},
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
let { keyboardProps: repeatKeyboardProps } = useKeyboard({
|
|
259
|
+
shortcuts: {
|
|
260
|
+
ArrowDown: () => {
|
|
255
261
|
state.open('first', 'manual');
|
|
256
|
-
|
|
257
|
-
|
|
262
|
+
return { shouldPreventDefault: false };
|
|
263
|
+
},
|
|
264
|
+
ArrowUp: () => {
|
|
258
265
|
state.open('last', 'manual');
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
266
|
+
return { shouldPreventDefault: false };
|
|
267
|
+
},
|
|
268
|
+
ArrowLeft: () => {
|
|
262
269
|
state.selectionManager.setFocusedKey(null);
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
270
|
+
return { shouldPreventDefault: false };
|
|
271
|
+
},
|
|
272
|
+
ArrowRight: () => {
|
|
273
|
+
state.selectionManager.setFocusedKey(null);
|
|
274
|
+
return { shouldPreventDefault: false };
|
|
275
|
+
},
|
|
276
|
+
},
|
|
277
|
+
allowRepeats: true,
|
|
278
|
+
});
|
|
266
279
|
|
|
267
|
-
let onBlur = (e:
|
|
280
|
+
let onBlur = (e: FocusEvent) => {
|
|
268
281
|
let blurFromButton = buttonRef?.current && buttonRef.current === e.relatedTarget;
|
|
269
|
-
let blurIntoPopover = nodeContains(popoverRef.current, e.relatedTarget);
|
|
282
|
+
let blurIntoPopover = nodeContains(popoverRef.current, e.relatedTarget as Node | null);
|
|
270
283
|
// Ignore blur if focused moved to the button(if exists) or into the popover.
|
|
271
284
|
if (blurFromButton || blurIntoPopover) {
|
|
272
285
|
return;
|
|
@@ -279,7 +292,7 @@ export function useComboBox(...args: any[]): ComboBoxAria<any> {
|
|
|
279
292
|
state.setFocused(false);
|
|
280
293
|
};
|
|
281
294
|
|
|
282
|
-
let onFocus = (e:
|
|
295
|
+
let onFocus = (e: FocusEvent) => {
|
|
283
296
|
if (state.isFocused) {
|
|
284
297
|
return;
|
|
285
298
|
}
|
|
@@ -291,10 +304,10 @@ export function useComboBox(...args: any[]): ComboBoxAria<any> {
|
|
|
291
304
|
state.setFocused(true);
|
|
292
305
|
};
|
|
293
306
|
|
|
294
|
-
let valueId = useValueId(
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
);
|
|
307
|
+
let valueId = useValueId([
|
|
308
|
+
state.selectionManager.selectedKeys,
|
|
309
|
+
state.selectionManager.selectionMode,
|
|
310
|
+
]);
|
|
298
311
|
let { isInvalid, validationErrors, validationDetails } = state.displayValidation;
|
|
299
312
|
let { labelProps, inputProps, descriptionProps, errorMessageProps } = useTextField(
|
|
300
313
|
{
|
|
@@ -306,7 +319,13 @@ export function useComboBox(...args: any[]): ComboBoxAria<any> {
|
|
|
306
319
|
: props.isRequired,
|
|
307
320
|
onChange: state.setInputValue,
|
|
308
321
|
onKeyDown: !isReadOnly
|
|
309
|
-
?
|
|
322
|
+
? // oxlint-disable-next-line react/react-compiler
|
|
323
|
+
chain(
|
|
324
|
+
state.isOpen && collectionProps.onKeyDown,
|
|
325
|
+
keyboardProps.onKeyDown,
|
|
326
|
+
repeatKeyboardProps.onKeyDown,
|
|
327
|
+
props.onKeyDown,
|
|
328
|
+
)
|
|
310
329
|
: props.onKeyDown,
|
|
311
330
|
onBlur,
|
|
312
331
|
value: state.inputValue,
|
|
@@ -319,10 +338,9 @@ export function useComboBox(...args: any[]): ComboBoxAria<any> {
|
|
|
319
338
|
[valueId, props['aria-describedby']].filter(Boolean).join(' ') || undefined,
|
|
320
339
|
},
|
|
321
340
|
inputRef,
|
|
322
|
-
subSlot(slot, 'textField'),
|
|
323
341
|
);
|
|
324
342
|
|
|
325
|
-
useFormReset(inputRef, state.defaultValue, state.setValue
|
|
343
|
+
useFormReset(inputRef, state.defaultValue, state.setValue);
|
|
326
344
|
|
|
327
345
|
// Press handlers for the ComboBox button
|
|
328
346
|
let onPress = (e: PressEvent) => {
|
|
@@ -343,29 +361,21 @@ export function useComboBox(...args: any[]): ComboBoxAria<any> {
|
|
|
343
361
|
}
|
|
344
362
|
};
|
|
345
363
|
|
|
346
|
-
let triggerLabelProps = useLabels(
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
},
|
|
352
|
-
undefined,
|
|
353
|
-
subSlot(slot, 'triggerLabels'),
|
|
354
|
-
);
|
|
364
|
+
let triggerLabelProps = useLabels({
|
|
365
|
+
id: menuTriggerProps.id,
|
|
366
|
+
'aria-label': stringFormatter.format('buttonLabel'),
|
|
367
|
+
'aria-labelledby': props['aria-labelledby'] || labelProps.id,
|
|
368
|
+
});
|
|
355
369
|
|
|
356
|
-
let listBoxProps = useLabels(
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
},
|
|
362
|
-
undefined,
|
|
363
|
-
subSlot(slot, 'listBoxLabels'),
|
|
364
|
-
);
|
|
370
|
+
let listBoxProps = useLabels({
|
|
371
|
+
id: menuProps.id,
|
|
372
|
+
'aria-label': stringFormatter.format('listboxLabel'),
|
|
373
|
+
'aria-labelledby': props['aria-labelledby'] || labelProps.id,
|
|
374
|
+
});
|
|
365
375
|
|
|
366
376
|
// If a touch happens on direct center of ComboBox input, might be virtual click from iPad so open ComboBox menu
|
|
367
|
-
let lastEventTime = useRef(0
|
|
368
|
-
let onTouchEnd = (e:
|
|
377
|
+
let lastEventTime = useRef(0);
|
|
378
|
+
let onTouchEnd = (e: TouchEvent) => {
|
|
369
379
|
if (isDisabled || isReadOnly) {
|
|
370
380
|
return;
|
|
371
381
|
}
|
|
@@ -401,117 +411,89 @@ export function useComboBox(...args: any[]): ComboBoxAria<any> {
|
|
|
401
411
|
: undefined;
|
|
402
412
|
let sectionKey = focusedItem?.parentKey ?? null;
|
|
403
413
|
let itemKey = state.selectionManager.focusedKey ?? null;
|
|
404
|
-
let lastSection = useRef(sectionKey
|
|
405
|
-
let lastItem = useRef(itemKey
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
announce(announcement);
|
|
431
|
-
}
|
|
432
|
-
|
|
433
|
-
lastSection.current = sectionKey;
|
|
434
|
-
lastItem.current = itemKey;
|
|
435
|
-
},
|
|
436
|
-
null,
|
|
437
|
-
subSlot(slot, 'announceFocus'),
|
|
438
|
-
);
|
|
414
|
+
let lastSection = useRef<typeof sectionKey>(sectionKey);
|
|
415
|
+
let lastItem = useRef<typeof itemKey>(itemKey);
|
|
416
|
+
useEffect(() => {
|
|
417
|
+
if (isAppleDevice() && focusedItem != null && itemKey != null && itemKey !== lastItem.current) {
|
|
418
|
+
let isSelected = state.selectionManager.isSelected(itemKey);
|
|
419
|
+
let section = sectionKey != null ? state.collection.getItem(sectionKey) : null;
|
|
420
|
+
let sectionTitle =
|
|
421
|
+
section?.['aria-label'] ||
|
|
422
|
+
(typeof section?.rendered === 'string' ? section.rendered : '') ||
|
|
423
|
+
'';
|
|
424
|
+
|
|
425
|
+
let announcement = stringFormatter.format('focusAnnouncement', {
|
|
426
|
+
isGroupChange: (section && sectionKey !== lastSection.current) ?? false,
|
|
427
|
+
groupTitle: sectionTitle,
|
|
428
|
+
groupCount: section ? [...getChildNodes(section, state.collection)].length : 0,
|
|
429
|
+
optionText: focusedItem['aria-label'] || focusedItem.textValue || '',
|
|
430
|
+
isSelected,
|
|
431
|
+
});
|
|
432
|
+
|
|
433
|
+
announce(announcement);
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
lastSection.current = sectionKey;
|
|
437
|
+
lastItem.current = itemKey;
|
|
438
|
+
});
|
|
439
439
|
|
|
440
440
|
// Announce the number of available suggestions when it changes
|
|
441
441
|
let optionCount = getItemCount(state.collection);
|
|
442
|
-
let lastSize = useRef(optionCount
|
|
443
|
-
let lastOpen = useRef(state.isOpen
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
lastOpen.current = state.isOpen;
|
|
461
|
-
},
|
|
462
|
-
null,
|
|
463
|
-
subSlot(slot, 'announceCount'),
|
|
464
|
-
);
|
|
442
|
+
let lastSize = useRef(optionCount);
|
|
443
|
+
let lastOpen = useRef(state.isOpen);
|
|
444
|
+
useEffect(() => {
|
|
445
|
+
// Only announce the number of options available when the menu opens if there is no
|
|
446
|
+
// focused item, otherwise screen readers will typically read e.g. "1 of 6".
|
|
447
|
+
// The exception is VoiceOver since this isn't included in the message above.
|
|
448
|
+
let didOpenWithoutFocusedItem =
|
|
449
|
+
state.isOpen !== lastOpen.current &&
|
|
450
|
+
(state.selectionManager.focusedKey == null || isAppleDevice());
|
|
451
|
+
|
|
452
|
+
if (state.isOpen && (didOpenWithoutFocusedItem || optionCount !== lastSize.current)) {
|
|
453
|
+
let announcement = stringFormatter.format('countAnnouncement', { optionCount });
|
|
454
|
+
announce(announcement);
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
lastSize.current = optionCount;
|
|
458
|
+
lastOpen.current = state.isOpen;
|
|
459
|
+
});
|
|
465
460
|
|
|
466
461
|
// Announce when a selection occurs for VoiceOver. Other screen readers typically do this automatically.
|
|
467
462
|
// TODO: do we need to do this for multi-select?
|
|
468
|
-
let lastSelectedKey = useRef(state.selectedKey
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
announce(announcement);
|
|
481
|
-
}
|
|
482
|
-
|
|
483
|
-
lastSelectedKey.current = state.selectedKey;
|
|
484
|
-
},
|
|
485
|
-
null,
|
|
486
|
-
subSlot(slot, 'announceSelected'),
|
|
487
|
-
);
|
|
463
|
+
let lastSelectedKey = useRef<typeof state.selectedKey>(state.selectedKey);
|
|
464
|
+
useEffect(() => {
|
|
465
|
+
if (
|
|
466
|
+
isAppleDevice() &&
|
|
467
|
+
state.isFocused &&
|
|
468
|
+
state.selectedItem &&
|
|
469
|
+
state.selectedKey !== lastSelectedKey.current
|
|
470
|
+
) {
|
|
471
|
+
let optionText = state.selectedItem['aria-label'] || state.selectedItem.textValue || '';
|
|
472
|
+
let announcement = stringFormatter.format('selectedAnnouncement', { optionText });
|
|
473
|
+
announce(announcement);
|
|
474
|
+
}
|
|
488
475
|
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
if (state.isOpen) {
|
|
492
|
-
return ariaHideOutside(
|
|
493
|
-
[inputRef.current, popoverRef.current].filter((element) => element != null),
|
|
494
|
-
);
|
|
495
|
-
}
|
|
496
|
-
},
|
|
497
|
-
[state.isOpen, inputRef, popoverRef],
|
|
498
|
-
subSlot(slot, 'hideOutside'),
|
|
499
|
-
);
|
|
476
|
+
lastSelectedKey.current = state.selectedKey;
|
|
477
|
+
});
|
|
500
478
|
|
|
501
|
-
|
|
502
|
-
()
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
479
|
+
useEffect(() => {
|
|
480
|
+
if (state.isOpen) {
|
|
481
|
+
return ariaHideOutside(
|
|
482
|
+
[inputRef.current, popoverRef.current].filter((element) => element != null),
|
|
483
|
+
);
|
|
484
|
+
}
|
|
485
|
+
}, [state.isOpen, inputRef, popoverRef]);
|
|
486
|
+
|
|
487
|
+
useUpdateEffect(() => {
|
|
488
|
+
// Re-show focus ring when there is no virtually focused item.
|
|
489
|
+
if (
|
|
490
|
+
!focusedItem &&
|
|
491
|
+
inputRef.current &&
|
|
492
|
+
getActiveElement(getOwnerDocument(inputRef.current)) === inputRef.current
|
|
493
|
+
) {
|
|
494
|
+
dispatchVirtualFocus(inputRef.current, null);
|
|
495
|
+
}
|
|
496
|
+
}, [focusedItem]);
|
|
515
497
|
|
|
516
498
|
useEvent(
|
|
517
499
|
listBoxRef,
|
|
@@ -521,11 +503,20 @@ export function useComboBox(...args: any[]): ComboBoxAria<any> {
|
|
|
521
503
|
state.close();
|
|
522
504
|
}
|
|
523
505
|
: undefined,
|
|
524
|
-
subSlot(slot, 'itemAction'),
|
|
525
506
|
);
|
|
526
507
|
|
|
527
508
|
return {
|
|
528
|
-
labelProps
|
|
509
|
+
labelProps: {
|
|
510
|
+
...labelProps,
|
|
511
|
+
// Focus the input in case the label is not a native <label> element.
|
|
512
|
+
onClick:
|
|
513
|
+
('htmlFor' in labelProps && labelProps.htmlFor) || props.isDisabled
|
|
514
|
+
? undefined
|
|
515
|
+
: () => {
|
|
516
|
+
inputRef.current?.focus();
|
|
517
|
+
setInteractionModality('keyboard');
|
|
518
|
+
},
|
|
519
|
+
},
|
|
529
520
|
buttonProps: {
|
|
530
521
|
...menuTriggerProps,
|
|
531
522
|
...triggerLabelProps,
|
|
@@ -535,6 +526,7 @@ export function useComboBox(...args: any[]): ComboBoxAria<any> {
|
|
|
535
526
|
onPressStart,
|
|
536
527
|
isDisabled: isDisabled || isReadOnly,
|
|
537
528
|
},
|
|
529
|
+
// oxlint-disable-next-line react/react-compiler
|
|
538
530
|
inputProps: mergeProps(inputProps, {
|
|
539
531
|
role: 'combobox',
|
|
540
532
|
'aria-expanded': menuTriggerProps['aria-expanded'],
|
|
@@ -573,14 +565,10 @@ export function useComboBox(...args: any[]): ComboBoxAria<any> {
|
|
|
573
565
|
// These re-renders preempt async state updates in the useEffect, which ends up running multiple times
|
|
574
566
|
// prior to the state being updated. This results in onSelectionChange being called multiple times.
|
|
575
567
|
// TODO: refactor useComboBoxState to avoid this.
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
): string | undefined {
|
|
581
|
-
let id = useId(subSlot(slot, 'id'));
|
|
582
|
-
let [exists, setExists] = useState(true, subSlot(slot, 'exists'));
|
|
583
|
-
let [lastDeps, setLastDeps] = useState(depArray, subSlot(slot, 'lastDeps'));
|
|
568
|
+
function useValueId(depArray: ReadonlyArray<any> = []): string | undefined {
|
|
569
|
+
let id = useId();
|
|
570
|
+
let [exists, setExists] = useState(true);
|
|
571
|
+
let [lastDeps, setLastDeps] = useState(depArray);
|
|
584
572
|
|
|
585
573
|
// If the deps changed, set exists to true so we can test whether the element exists.
|
|
586
574
|
if (lastDeps.some((v, i) => !Object.is(v, depArray[i]))) {
|
|
@@ -588,15 +576,12 @@ function useValueId(
|
|
|
588
576
|
setLastDeps(depArray);
|
|
589
577
|
}
|
|
590
578
|
|
|
591
|
-
useEffect(
|
|
592
|
-
()
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
[id, exists, lastDeps],
|
|
598
|
-
subSlot(slot, 'existsEffect'),
|
|
599
|
-
);
|
|
579
|
+
useEffect(() => {
|
|
580
|
+
if (exists && !document.getElementById(id)) {
|
|
581
|
+
// oxlint-disable-next-line react/react-compiler
|
|
582
|
+
setExists(false);
|
|
583
|
+
}
|
|
584
|
+
}, [id, exists, lastDeps]);
|
|
600
585
|
|
|
601
586
|
return exists ? id : undefined;
|
|
602
587
|
}
|