@octanejs/aria 0.0.46 → 0.0.47
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} +239 -259
- 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} +121 -64
- package/src/grid/utils.ts +1 -0
- package/src/gridlist/{useGridListItem.ts → useGridListItem.tsx} +152 -111
- 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 +217 -0
- package/src/menu/{useSubmenuTrigger.ts → useSubmenuTrigger.tsx} +112 -152
- package/src/numberfield/{useNumberField.ts → useNumberField.tsx} +185 -239
- 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} +72 -84
- 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} +83 -80
- package/src/searchfield/useSearchField.tsx +147 -0
- package/src/select/useSelect.ts +20 -20
- package/src/selection/useSelectableCollection.tsx +751 -0
- package/src/selection/useSelectableItem.ts +48 -11
- package/src/slider/{useSliderThumb.ts → useSliderThumb.tsx} +116 -159
- package/src/spinbutton/useSpinButton.tsx +330 -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 +354 -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 +3 -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,38 @@ 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';
|
|
42
50
|
import { getItemId, listData } from '../listbox/utils';
|
|
43
51
|
import { getOwnerDocument } from '../utils/domHelpers';
|
|
44
|
-
import intlMessages from '../intl/combobox';
|
|
52
|
+
import intlMessages from '../intl/combobox/index';
|
|
45
53
|
import { isAppleDevice } from '../utils/platform';
|
|
46
54
|
import { ListKeyboardDelegate } from '../selection/ListKeyboardDelegate';
|
|
47
55
|
import { mergeProps } from '../utils/mergeProps';
|
|
48
|
-
import { privateValidationStateProp } from '../stately/form/useFormValidationState';
|
|
56
|
+
import { privateValidationStateProp } from '../upstream-exports/react-stately/private/form/useFormValidationState';
|
|
57
|
+
import { setInteractionModality } from '../interactions/useFocusVisible';
|
|
49
58
|
import { useEvent } from '../utils/useEvent';
|
|
50
59
|
import { useFormReset } from '../utils/useFormReset';
|
|
51
60
|
import { useId } from '../utils/useId';
|
|
61
|
+
import { useKeyboard } from '../interactions/useKeyboard';
|
|
52
62
|
import { useLabels } from '../utils/useLabels';
|
|
53
63
|
import { useLocalizedStringFormatter } from '../i18n/useLocalizedStringFormatter';
|
|
54
64
|
import { useMenuTrigger } from '../menu/useMenuTrigger';
|
|
@@ -57,12 +67,6 @@ import { useSelectableCollection } from '../selection/useSelectableCollection';
|
|
|
57
67
|
import { useTextField } from '../textfield/useTextField';
|
|
58
68
|
import { useUpdateEffect } from '../utils/useUpdateEffect';
|
|
59
69
|
|
|
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
70
|
export interface AriaComboBoxProps<T, M extends SelectionMode = 'single'>
|
|
67
71
|
extends ComboBoxProps<T, M>, DOMProps, InputDOMProps, AriaLabelingProps {
|
|
68
72
|
/** Whether keyboard navigation is circular. */
|
|
@@ -81,6 +85,12 @@ export interface AriaComboBoxOptions<T, M extends SelectionMode = 'single'> exte
|
|
|
81
85
|
listBoxRef: RefObject<HTMLElement | null>;
|
|
82
86
|
/** The ref for the optional list box popup trigger button. */
|
|
83
87
|
buttonRef?: RefObject<Element | null>;
|
|
88
|
+
/**
|
|
89
|
+
* The HTML element used to render the label, e.g. 'label', or 'span'.
|
|
90
|
+
*
|
|
91
|
+
* @default 'label'
|
|
92
|
+
*/
|
|
93
|
+
labelElementType?: ElementType;
|
|
84
94
|
/** An optional keyboard delegate implementation, to override the default. */
|
|
85
95
|
keyboardDelegate?: KeyboardDelegate;
|
|
86
96
|
/**
|
|
@@ -95,7 +105,7 @@ export interface ComboBoxAria<T> extends ValidationResult {
|
|
|
95
105
|
/** Props for the label element. */
|
|
96
106
|
labelProps: DOMAttributes;
|
|
97
107
|
/** Props for the combo box input element. */
|
|
98
|
-
inputProps:
|
|
108
|
+
inputProps: InputHTMLAttributes<HTMLInputElement>;
|
|
99
109
|
/** Props for the list box, to be passed to `useListBox`. */
|
|
100
110
|
listBoxProps: AriaListBoxOptions<T>;
|
|
101
111
|
/** Props for the optional trigger button, to be passed to `useButton`. */
|
|
@@ -119,19 +129,8 @@ export interface ComboBoxAria<T> extends ValidationResult {
|
|
|
119
129
|
export function useComboBox<T, M extends SelectionMode = 'single'>(
|
|
120
130
|
props: AriaComboBoxOptions<T, M>,
|
|
121
131
|
state: ComboBoxState<T, M>,
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
export function useComboBox<T, M extends SelectionMode = 'single'>(
|
|
125
|
-
props: AriaComboBoxOptions<T, M>,
|
|
126
|
-
state: ComboBoxState<T, M>,
|
|
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>;
|
|
134
|
-
|
|
132
|
+
_slot?: symbol,
|
|
133
|
+
): ComboBoxAria<T> {
|
|
135
134
|
let {
|
|
136
135
|
buttonRef,
|
|
137
136
|
popoverRef,
|
|
@@ -144,22 +143,17 @@ export function useComboBox(...args: any[]): ComboBoxAria<any> {
|
|
|
144
143
|
isReadOnly,
|
|
145
144
|
isDisabled,
|
|
146
145
|
} = props;
|
|
147
|
-
let backupBtnRef = useRef(null
|
|
146
|
+
let backupBtnRef = useRef(null);
|
|
148
147
|
buttonRef = buttonRef ?? backupBtnRef;
|
|
149
148
|
|
|
150
|
-
let stringFormatter = useLocalizedStringFormatter(
|
|
151
|
-
|
|
152
|
-
'@react-aria/combobox',
|
|
153
|
-
subSlot(slot, 'formatter'),
|
|
154
|
-
);
|
|
155
|
-
let { menuTriggerProps, menuProps } = useMenuTrigger<any>(
|
|
149
|
+
let stringFormatter = useLocalizedStringFormatter(intlMessages, '@react-aria/combobox');
|
|
150
|
+
let { menuTriggerProps, menuProps } = useMenuTrigger<T>(
|
|
156
151
|
{
|
|
157
152
|
type: 'listbox',
|
|
158
153
|
isDisabled: isDisabled || isReadOnly,
|
|
159
154
|
},
|
|
160
155
|
state,
|
|
161
156
|
buttonRef,
|
|
162
|
-
subSlot(slot, 'menuTrigger'),
|
|
163
157
|
);
|
|
164
158
|
|
|
165
159
|
// Set listbox id so it can be used when calling getItemId later
|
|
@@ -179,41 +173,28 @@ export function useComboBox(...args: any[]): ComboBoxAria<any> {
|
|
|
179
173
|
layoutDelegate,
|
|
180
174
|
}),
|
|
181
175
|
[keyboardDelegate, layoutDelegate, collection, disabledKeys, listBoxRef],
|
|
182
|
-
subSlot(slot, 'delegate'),
|
|
183
176
|
);
|
|
184
177
|
|
|
185
178
|
// 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
|
-
);
|
|
179
|
+
let { collectionProps } = useSelectableCollection({
|
|
180
|
+
selectionManager: state.selectionManager,
|
|
181
|
+
keyboardDelegate: delegate,
|
|
182
|
+
disallowTypeAhead: true,
|
|
183
|
+
disallowEmptySelection: true,
|
|
184
|
+
shouldFocusWrap,
|
|
185
|
+
ref: inputRef,
|
|
186
|
+
// Prevent item scroll behavior from being applied here, should be handled in the user's Popover + ListBox component
|
|
187
|
+
isVirtualized: true,
|
|
188
|
+
});
|
|
199
189
|
|
|
200
190
|
let router = useRouter();
|
|
201
191
|
|
|
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
|
-
|
|
192
|
+
// for textfield specific operations
|
|
193
|
+
let { keyboardProps } = useKeyboard({
|
|
194
|
+
shortcuts: {
|
|
195
|
+
Enter: (e) => {
|
|
196
|
+
// Prevent default form submission if menu is open since we may be selecting a option
|
|
197
|
+
let shouldPreventDefault = state.isOpen;
|
|
217
198
|
// If the focused item is a link, trigger opening it. Items that are links are not selectable.
|
|
218
199
|
if (state.isOpen && listBoxRef.current && state.selectionManager.focusedKey != null) {
|
|
219
200
|
let collectionItem = state.collection.getItem(state.selectionManager.focusedKey);
|
|
@@ -221,7 +202,7 @@ export function useComboBox(...args: any[]): ComboBoxAria<any> {
|
|
|
221
202
|
let item = listBoxRef.current.querySelector(
|
|
222
203
|
`[data-key="${CSS.escape(state.selectionManager.focusedKey.toString())}"]`,
|
|
223
204
|
);
|
|
224
|
-
if (
|
|
205
|
+
if (item instanceof HTMLAnchorElement) {
|
|
225
206
|
router.open(
|
|
226
207
|
item,
|
|
227
208
|
e,
|
|
@@ -230,43 +211,70 @@ export function useComboBox(...args: any[]): ComboBoxAria<any> {
|
|
|
230
211
|
);
|
|
231
212
|
}
|
|
232
213
|
state.close();
|
|
233
|
-
|
|
214
|
+
return { shouldPreventDefault };
|
|
234
215
|
} else if (collectionItem?.props.onAction) {
|
|
235
216
|
collectionItem.props.onAction();
|
|
236
217
|
state.close();
|
|
237
|
-
|
|
218
|
+
return { shouldPreventDefault };
|
|
238
219
|
}
|
|
239
220
|
}
|
|
240
|
-
|
|
241
|
-
|
|
221
|
+
state.commit();
|
|
222
|
+
return { shouldPreventDefault };
|
|
223
|
+
},
|
|
224
|
+
Tab: () => {
|
|
225
|
+
// If the focused item is a link, trigger opening it. Items that are links are not selectable.
|
|
226
|
+
if (state.isOpen && listBoxRef.current && state.selectionManager.focusedKey != null) {
|
|
227
|
+
let collectionItem = state.collection.getItem(state.selectionManager.focusedKey);
|
|
228
|
+
if (collectionItem?.props.href) {
|
|
229
|
+
state.close();
|
|
230
|
+
return { shouldPreventDefault: false };
|
|
231
|
+
} else if (collectionItem?.props.onAction) {
|
|
232
|
+
collectionItem.props.onAction();
|
|
233
|
+
state.close();
|
|
234
|
+
return { shouldPreventDefault: false, shouldContinuePropagation: true };
|
|
235
|
+
}
|
|
242
236
|
}
|
|
243
|
-
if (
|
|
244
|
-
|
|
237
|
+
if (state.isOpen) {
|
|
238
|
+
state.commit();
|
|
245
239
|
}
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
240
|
+
return { shouldPreventDefault: false, shouldContinuePropagation: true };
|
|
241
|
+
},
|
|
242
|
+
Escape: () => {
|
|
243
|
+
let shouldContinuePropagation = false;
|
|
249
244
|
if (!state.selectionManager.isEmpty || state.inputValue === '' || props.allowsCustomValue) {
|
|
250
|
-
|
|
245
|
+
shouldContinuePropagation = true;
|
|
251
246
|
}
|
|
252
247
|
state.revert();
|
|
253
|
-
|
|
254
|
-
|
|
248
|
+
return { shouldContinuePropagation };
|
|
249
|
+
},
|
|
250
|
+
},
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
let { keyboardProps: repeatKeyboardProps } = useKeyboard({
|
|
254
|
+
shortcuts: {
|
|
255
|
+
ArrowDown: () => {
|
|
255
256
|
state.open('first', 'manual');
|
|
256
|
-
|
|
257
|
-
|
|
257
|
+
return { shouldPreventDefault: false };
|
|
258
|
+
},
|
|
259
|
+
ArrowUp: () => {
|
|
258
260
|
state.open('last', 'manual');
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
261
|
+
return { shouldPreventDefault: false };
|
|
262
|
+
},
|
|
263
|
+
ArrowLeft: () => {
|
|
262
264
|
state.selectionManager.setFocusedKey(null);
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
265
|
+
return { shouldPreventDefault: false };
|
|
266
|
+
},
|
|
267
|
+
ArrowRight: () => {
|
|
268
|
+
state.selectionManager.setFocusedKey(null);
|
|
269
|
+
return { shouldPreventDefault: false };
|
|
270
|
+
},
|
|
271
|
+
},
|
|
272
|
+
allowRepeats: true,
|
|
273
|
+
});
|
|
266
274
|
|
|
267
|
-
let onBlur = (e:
|
|
275
|
+
let onBlur = (e: FocusEvent) => {
|
|
268
276
|
let blurFromButton = buttonRef?.current && buttonRef.current === e.relatedTarget;
|
|
269
|
-
let blurIntoPopover = nodeContains(popoverRef.current, e.relatedTarget);
|
|
277
|
+
let blurIntoPopover = nodeContains(popoverRef.current, e.relatedTarget as Node | null);
|
|
270
278
|
// Ignore blur if focused moved to the button(if exists) or into the popover.
|
|
271
279
|
if (blurFromButton || blurIntoPopover) {
|
|
272
280
|
return;
|
|
@@ -279,7 +287,7 @@ export function useComboBox(...args: any[]): ComboBoxAria<any> {
|
|
|
279
287
|
state.setFocused(false);
|
|
280
288
|
};
|
|
281
289
|
|
|
282
|
-
let onFocus = (e:
|
|
290
|
+
let onFocus = (e: FocusEvent) => {
|
|
283
291
|
if (state.isFocused) {
|
|
284
292
|
return;
|
|
285
293
|
}
|
|
@@ -291,10 +299,10 @@ export function useComboBox(...args: any[]): ComboBoxAria<any> {
|
|
|
291
299
|
state.setFocused(true);
|
|
292
300
|
};
|
|
293
301
|
|
|
294
|
-
let valueId = useValueId(
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
);
|
|
302
|
+
let valueId = useValueId([
|
|
303
|
+
state.selectionManager.selectedKeys,
|
|
304
|
+
state.selectionManager.selectionMode,
|
|
305
|
+
]);
|
|
298
306
|
let { isInvalid, validationErrors, validationDetails } = state.displayValidation;
|
|
299
307
|
let { labelProps, inputProps, descriptionProps, errorMessageProps } = useTextField(
|
|
300
308
|
{
|
|
@@ -306,7 +314,13 @@ export function useComboBox(...args: any[]): ComboBoxAria<any> {
|
|
|
306
314
|
: props.isRequired,
|
|
307
315
|
onChange: state.setInputValue,
|
|
308
316
|
onKeyDown: !isReadOnly
|
|
309
|
-
?
|
|
317
|
+
? // oxlint-disable-next-line react/react-compiler
|
|
318
|
+
chain(
|
|
319
|
+
state.isOpen && collectionProps.onKeyDown,
|
|
320
|
+
keyboardProps.onKeyDown,
|
|
321
|
+
repeatKeyboardProps.onKeyDown,
|
|
322
|
+
props.onKeyDown,
|
|
323
|
+
)
|
|
310
324
|
: props.onKeyDown,
|
|
311
325
|
onBlur,
|
|
312
326
|
value: state.inputValue,
|
|
@@ -319,10 +333,9 @@ export function useComboBox(...args: any[]): ComboBoxAria<any> {
|
|
|
319
333
|
[valueId, props['aria-describedby']].filter(Boolean).join(' ') || undefined,
|
|
320
334
|
},
|
|
321
335
|
inputRef,
|
|
322
|
-
subSlot(slot, 'textField'),
|
|
323
336
|
);
|
|
324
337
|
|
|
325
|
-
useFormReset(inputRef, state.defaultValue, state.setValue
|
|
338
|
+
useFormReset(inputRef, state.defaultValue, state.setValue);
|
|
326
339
|
|
|
327
340
|
// Press handlers for the ComboBox button
|
|
328
341
|
let onPress = (e: PressEvent) => {
|
|
@@ -343,29 +356,21 @@ export function useComboBox(...args: any[]): ComboBoxAria<any> {
|
|
|
343
356
|
}
|
|
344
357
|
};
|
|
345
358
|
|
|
346
|
-
let triggerLabelProps = useLabels(
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
},
|
|
352
|
-
undefined,
|
|
353
|
-
subSlot(slot, 'triggerLabels'),
|
|
354
|
-
);
|
|
359
|
+
let triggerLabelProps = useLabels({
|
|
360
|
+
id: menuTriggerProps.id,
|
|
361
|
+
'aria-label': stringFormatter.format('buttonLabel'),
|
|
362
|
+
'aria-labelledby': props['aria-labelledby'] || labelProps.id,
|
|
363
|
+
});
|
|
355
364
|
|
|
356
|
-
let listBoxProps = useLabels(
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
},
|
|
362
|
-
undefined,
|
|
363
|
-
subSlot(slot, 'listBoxLabels'),
|
|
364
|
-
);
|
|
365
|
+
let listBoxProps = useLabels({
|
|
366
|
+
id: menuProps.id,
|
|
367
|
+
'aria-label': stringFormatter.format('listboxLabel'),
|
|
368
|
+
'aria-labelledby': props['aria-labelledby'] || labelProps.id,
|
|
369
|
+
});
|
|
365
370
|
|
|
366
371
|
// 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:
|
|
372
|
+
let lastEventTime = useRef(0);
|
|
373
|
+
let onTouchEnd = (e: TouchEvent) => {
|
|
369
374
|
if (isDisabled || isReadOnly) {
|
|
370
375
|
return;
|
|
371
376
|
}
|
|
@@ -401,117 +406,89 @@ export function useComboBox(...args: any[]): ComboBoxAria<any> {
|
|
|
401
406
|
: undefined;
|
|
402
407
|
let sectionKey = focusedItem?.parentKey ?? null;
|
|
403
408
|
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
|
-
);
|
|
409
|
+
let lastSection = useRef<typeof sectionKey>(sectionKey);
|
|
410
|
+
let lastItem = useRef<typeof itemKey>(itemKey);
|
|
411
|
+
useEffect(() => {
|
|
412
|
+
if (isAppleDevice() && focusedItem != null && itemKey != null && itemKey !== lastItem.current) {
|
|
413
|
+
let isSelected = state.selectionManager.isSelected(itemKey);
|
|
414
|
+
let section = sectionKey != null ? state.collection.getItem(sectionKey) : null;
|
|
415
|
+
let sectionTitle =
|
|
416
|
+
section?.['aria-label'] ||
|
|
417
|
+
(typeof section?.rendered === 'string' ? section.rendered : '') ||
|
|
418
|
+
'';
|
|
419
|
+
|
|
420
|
+
let announcement = stringFormatter.format('focusAnnouncement', {
|
|
421
|
+
isGroupChange: (section && sectionKey !== lastSection.current) ?? false,
|
|
422
|
+
groupTitle: sectionTitle,
|
|
423
|
+
groupCount: section ? [...getChildNodes(section, state.collection)].length : 0,
|
|
424
|
+
optionText: focusedItem['aria-label'] || focusedItem.textValue || '',
|
|
425
|
+
isSelected,
|
|
426
|
+
});
|
|
427
|
+
|
|
428
|
+
announce(announcement);
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
lastSection.current = sectionKey;
|
|
432
|
+
lastItem.current = itemKey;
|
|
433
|
+
});
|
|
439
434
|
|
|
440
435
|
// Announce the number of available suggestions when it changes
|
|
441
436
|
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
|
-
);
|
|
437
|
+
let lastSize = useRef(optionCount);
|
|
438
|
+
let lastOpen = useRef(state.isOpen);
|
|
439
|
+
useEffect(() => {
|
|
440
|
+
// Only announce the number of options available when the menu opens if there is no
|
|
441
|
+
// focused item, otherwise screen readers will typically read e.g. "1 of 6".
|
|
442
|
+
// The exception is VoiceOver since this isn't included in the message above.
|
|
443
|
+
let didOpenWithoutFocusedItem =
|
|
444
|
+
state.isOpen !== lastOpen.current &&
|
|
445
|
+
(state.selectionManager.focusedKey == null || isAppleDevice());
|
|
446
|
+
|
|
447
|
+
if (state.isOpen && (didOpenWithoutFocusedItem || optionCount !== lastSize.current)) {
|
|
448
|
+
let announcement = stringFormatter.format('countAnnouncement', { optionCount });
|
|
449
|
+
announce(announcement);
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
lastSize.current = optionCount;
|
|
453
|
+
lastOpen.current = state.isOpen;
|
|
454
|
+
});
|
|
465
455
|
|
|
466
456
|
// Announce when a selection occurs for VoiceOver. Other screen readers typically do this automatically.
|
|
467
457
|
// 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
|
-
);
|
|
458
|
+
let lastSelectedKey = useRef<typeof state.selectedKey>(state.selectedKey);
|
|
459
|
+
useEffect(() => {
|
|
460
|
+
if (
|
|
461
|
+
isAppleDevice() &&
|
|
462
|
+
state.isFocused &&
|
|
463
|
+
state.selectedItem &&
|
|
464
|
+
state.selectedKey !== lastSelectedKey.current
|
|
465
|
+
) {
|
|
466
|
+
let optionText = state.selectedItem['aria-label'] || state.selectedItem.textValue || '';
|
|
467
|
+
let announcement = stringFormatter.format('selectedAnnouncement', { optionText });
|
|
468
|
+
announce(announcement);
|
|
469
|
+
}
|
|
488
470
|
|
|
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
|
-
);
|
|
471
|
+
lastSelectedKey.current = state.selectedKey;
|
|
472
|
+
});
|
|
500
473
|
|
|
501
|
-
|
|
502
|
-
()
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
474
|
+
useEffect(() => {
|
|
475
|
+
if (state.isOpen) {
|
|
476
|
+
return ariaHideOutside(
|
|
477
|
+
[inputRef.current, popoverRef.current].filter((element) => element != null),
|
|
478
|
+
);
|
|
479
|
+
}
|
|
480
|
+
}, [state.isOpen, inputRef, popoverRef]);
|
|
481
|
+
|
|
482
|
+
useUpdateEffect(() => {
|
|
483
|
+
// Re-show focus ring when there is no virtually focused item.
|
|
484
|
+
if (
|
|
485
|
+
!focusedItem &&
|
|
486
|
+
inputRef.current &&
|
|
487
|
+
getActiveElement(getOwnerDocument(inputRef.current)) === inputRef.current
|
|
488
|
+
) {
|
|
489
|
+
dispatchVirtualFocus(inputRef.current, null);
|
|
490
|
+
}
|
|
491
|
+
}, [focusedItem]);
|
|
515
492
|
|
|
516
493
|
useEvent(
|
|
517
494
|
listBoxRef,
|
|
@@ -521,11 +498,20 @@ export function useComboBox(...args: any[]): ComboBoxAria<any> {
|
|
|
521
498
|
state.close();
|
|
522
499
|
}
|
|
523
500
|
: undefined,
|
|
524
|
-
subSlot(slot, 'itemAction'),
|
|
525
501
|
);
|
|
526
502
|
|
|
527
503
|
return {
|
|
528
|
-
labelProps
|
|
504
|
+
labelProps: {
|
|
505
|
+
...labelProps,
|
|
506
|
+
// Focus the input in case the label is not a native <label> element.
|
|
507
|
+
onClick:
|
|
508
|
+
('htmlFor' in labelProps && labelProps.htmlFor) || props.isDisabled
|
|
509
|
+
? undefined
|
|
510
|
+
: () => {
|
|
511
|
+
inputRef.current?.focus();
|
|
512
|
+
setInteractionModality('keyboard');
|
|
513
|
+
},
|
|
514
|
+
},
|
|
529
515
|
buttonProps: {
|
|
530
516
|
...menuTriggerProps,
|
|
531
517
|
...triggerLabelProps,
|
|
@@ -535,6 +521,7 @@ export function useComboBox(...args: any[]): ComboBoxAria<any> {
|
|
|
535
521
|
onPressStart,
|
|
536
522
|
isDisabled: isDisabled || isReadOnly,
|
|
537
523
|
},
|
|
524
|
+
// oxlint-disable-next-line react/react-compiler
|
|
538
525
|
inputProps: mergeProps(inputProps, {
|
|
539
526
|
role: 'combobox',
|
|
540
527
|
'aria-expanded': menuTriggerProps['aria-expanded'],
|
|
@@ -573,14 +560,10 @@ export function useComboBox(...args: any[]): ComboBoxAria<any> {
|
|
|
573
560
|
// These re-renders preempt async state updates in the useEffect, which ends up running multiple times
|
|
574
561
|
// prior to the state being updated. This results in onSelectionChange being called multiple times.
|
|
575
562
|
// 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'));
|
|
563
|
+
function useValueId(depArray: ReadonlyArray<any> = []): string | undefined {
|
|
564
|
+
let id = useId();
|
|
565
|
+
let [exists, setExists] = useState(true);
|
|
566
|
+
let [lastDeps, setLastDeps] = useState(depArray);
|
|
584
567
|
|
|
585
568
|
// If the deps changed, set exists to true so we can test whether the element exists.
|
|
586
569
|
if (lastDeps.some((v, i) => !Object.is(v, depArray[i]))) {
|
|
@@ -588,15 +571,12 @@ function useValueId(
|
|
|
588
571
|
setLastDeps(depArray);
|
|
589
572
|
}
|
|
590
573
|
|
|
591
|
-
useEffect(
|
|
592
|
-
()
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
[id, exists, lastDeps],
|
|
598
|
-
subSlot(slot, 'existsEffect'),
|
|
599
|
-
);
|
|
574
|
+
useEffect(() => {
|
|
575
|
+
if (exists && !document.getElementById(id)) {
|
|
576
|
+
// oxlint-disable-next-line react/react-compiler
|
|
577
|
+
setExists(false);
|
|
578
|
+
}
|
|
579
|
+
}, [id, exists, lastDeps]);
|
|
600
580
|
|
|
601
581
|
return exists ? id : undefined;
|
|
602
582
|
}
|