@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,71 +1,69 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
//
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
1
|
+
/** @jsxImportSource octane */
|
|
2
|
+
interface TextInputDOMEvents {
|
|
3
|
+
onCopy?: (e: ClipboardEvent) => void;
|
|
4
|
+
onCut?: (e: ClipboardEvent) => void;
|
|
5
|
+
onPaste?: (e: ClipboardEvent) => void;
|
|
6
|
+
onCompositionStart?: (e: CompositionEvent) => void;
|
|
7
|
+
onCompositionEnd?: (e: CompositionEvent) => void;
|
|
8
|
+
onCompositionUpdate?: (e: CompositionEvent) => void;
|
|
9
|
+
onSelect?: (e: Event) => void;
|
|
10
|
+
onBeforeInput?: (e: InputEvent) => void;
|
|
11
|
+
onInput?: (e: Event) => void;
|
|
12
|
+
}
|
|
13
|
+
type DOMAttributes = Record<string, any>;
|
|
14
|
+
// Ported from adobe/react-spectrum@5ecb3333001313e83898cd07644227897e3bae1f (packages/react-aria/src/numberfield/useNumberField.ts).
|
|
15
|
+
/*
|
|
16
|
+
* Copyright 2020 Adobe. All rights reserved.
|
|
17
|
+
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
|
|
18
|
+
* you may not use this file except in compliance with the License. You may obtain a copy
|
|
19
|
+
* of the License at http://www.apache.org/licenses/LICENSE-2.0
|
|
20
|
+
*
|
|
21
|
+
* Unless required by applicable law or agreed to in writing, software distributed under
|
|
22
|
+
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
|
|
23
|
+
* OF ANY KIND, either express or implied. See the License for the specific language
|
|
24
|
+
* governing permissions and limitations under the License.
|
|
25
|
+
*/
|
|
26
|
+
|
|
20
27
|
import { announce } from '../live-announcer/LiveAnnouncer';
|
|
21
|
-
import
|
|
22
|
-
import
|
|
23
|
-
|
|
24
|
-
|
|
28
|
+
import { AriaButtonProps } from '../button/useButton';
|
|
29
|
+
import {
|
|
30
|
+
AriaLabelingProps,
|
|
31
|
+
DOMProps,
|
|
32
|
+
GroupDOMAttributes,
|
|
33
|
+
TextInputDOMProps,
|
|
34
|
+
ValidationResult,
|
|
35
|
+
} from '@react-types/shared';
|
|
36
|
+
import {
|
|
37
|
+
InputHTMLAttributes,
|
|
38
|
+
LabelHTMLAttributes,
|
|
39
|
+
RefObject,
|
|
40
|
+
useCallback,
|
|
41
|
+
useMemo,
|
|
42
|
+
useState,
|
|
43
|
+
} from '../compat/react';
|
|
25
44
|
import { filterDOMProps } from '../utils/filterDOMProps';
|
|
45
|
+
import { flushSync, withSlot } from 'octane';
|
|
26
46
|
import { getActiveElement, getEventTarget } from '../utils/shadowdom/DOMFunctions';
|
|
27
|
-
import intlMessages from '../intl/numberfield';
|
|
47
|
+
import intlMessages from '../intl/numberfield/index';
|
|
28
48
|
import { isAndroid, isIOS, isIPhone } from '../utils/platform';
|
|
29
49
|
import { mergeProps } from '../utils/mergeProps';
|
|
30
|
-
import
|
|
50
|
+
import {
|
|
31
51
|
NumberFieldProps,
|
|
32
52
|
NumberFieldState,
|
|
33
|
-
} from '../stately/
|
|
34
|
-
import { privateValidationStateProp } from '../stately/form/useFormValidationState';
|
|
53
|
+
} from '../upstream-exports/react-stately/useNumberFieldState';
|
|
54
|
+
import { privateValidationStateProp } from '../upstream-exports/react-stately/private/form/useFormValidationState';
|
|
35
55
|
import { useFocus } from '../interactions/useFocus';
|
|
36
56
|
import { useFocusWithin } from '../interactions/useFocusWithin';
|
|
37
57
|
import { useFormattedTextField } from '../textfield/useFormattedTextField';
|
|
38
58
|
import { useFormReset } from '../utils/useFormReset';
|
|
39
59
|
import { useId } from '../utils/useId';
|
|
60
|
+
import { useKeyboard } from '../interactions/useKeyboard';
|
|
40
61
|
import { useLayoutEffect } from '../utils/useLayoutEffect';
|
|
41
62
|
import { useLocalizedStringFormatter } from '../i18n/useLocalizedStringFormatter';
|
|
42
63
|
import { useNumberFormatter } from '../i18n/useNumberFormatter';
|
|
43
64
|
import { useScrollWheel } from '../interactions/useScrollWheel';
|
|
44
65
|
import { useSpinButton } from '../spinbutton/useSpinButton';
|
|
45
66
|
|
|
46
|
-
import { S, splitSlot, subSlot } from '../internal';
|
|
47
|
-
|
|
48
|
-
// octane adaptation: minimal structural DOMAttributes (upstream's drags React's synthetic
|
|
49
|
-
// handler types along).
|
|
50
|
-
type DOMAttributes = Record<string, any>;
|
|
51
|
-
|
|
52
|
-
// octane adaptation: structural native-event versions of upstream's TextInputDOMEvents.
|
|
53
|
-
interface TextInputDOMEvents {
|
|
54
|
-
// Clipboard events
|
|
55
|
-
onCopy?: (e: ClipboardEvent) => void;
|
|
56
|
-
onCut?: (e: ClipboardEvent) => void;
|
|
57
|
-
onPaste?: (e: ClipboardEvent) => void;
|
|
58
|
-
// Composition events
|
|
59
|
-
onCompositionEnd?: (e: CompositionEvent) => void;
|
|
60
|
-
onCompositionStart?: (e: CompositionEvent) => void;
|
|
61
|
-
onCompositionUpdate?: (e: CompositionEvent) => void;
|
|
62
|
-
// Selection events
|
|
63
|
-
onSelect?: (e: Event) => void;
|
|
64
|
-
// Input events
|
|
65
|
-
onBeforeInput?: (e: InputEvent) => void;
|
|
66
|
-
onInput?: (e: Event) => void;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
67
|
export interface AriaNumberFieldProps
|
|
70
68
|
extends NumberFieldProps, DOMProps, AriaLabelingProps, TextInputDOMEvents {
|
|
71
69
|
/**
|
|
@@ -86,11 +84,11 @@ export interface AriaNumberFieldProps
|
|
|
86
84
|
|
|
87
85
|
export interface NumberFieldAria extends ValidationResult {
|
|
88
86
|
/** Props for the label element. */
|
|
89
|
-
labelProps:
|
|
87
|
+
labelProps: LabelHTMLAttributes<HTMLLabelElement>;
|
|
90
88
|
/** Props for the group wrapper around the input and stepper buttons. */
|
|
91
|
-
groupProps:
|
|
89
|
+
groupProps: GroupDOMAttributes;
|
|
92
90
|
/** Props for the input element. */
|
|
93
|
-
inputProps:
|
|
91
|
+
inputProps: InputHTMLAttributes<HTMLInputElement>;
|
|
94
92
|
/** Props for the increment button, to be passed to `useButton`. */
|
|
95
93
|
incrementButtonProps: AriaButtonProps;
|
|
96
94
|
/** Props for the decrement button, to be passed to `useButton`. */
|
|
@@ -110,20 +108,11 @@ export function useNumberField(
|
|
|
110
108
|
props: AriaNumberFieldProps,
|
|
111
109
|
state: NumberFieldState,
|
|
112
110
|
inputRef: RefObject<HTMLInputElement | null>,
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
inputRef: RefObject<HTMLInputElement | null>,
|
|
119
|
-
slot: symbol | undefined,
|
|
120
|
-
): NumberFieldAria;
|
|
121
|
-
export function useNumberField(...args: any[]): NumberFieldAria {
|
|
122
|
-
const [user, slotArg] = splitSlot(args);
|
|
123
|
-
const slot = slotArg ?? S('useNumberField');
|
|
124
|
-
const props = user[0] as AriaNumberFieldProps;
|
|
125
|
-
const state = user[1] as NumberFieldState;
|
|
126
|
-
const inputRef = user[2] as RefObject<HTMLInputElement | null>;
|
|
111
|
+
_slot?: symbol,
|
|
112
|
+
): NumberFieldAria {
|
|
113
|
+
if (_slot !== undefined) {
|
|
114
|
+
return withSlot(_slot, useNumberField, props, state, inputRef);
|
|
115
|
+
}
|
|
127
116
|
|
|
128
117
|
let {
|
|
129
118
|
id,
|
|
@@ -159,85 +148,60 @@ export function useNumberField(...args: any[]): NumberFieldAria {
|
|
|
159
148
|
commitValidation,
|
|
160
149
|
} = state;
|
|
161
150
|
|
|
162
|
-
const stringFormatter = useLocalizedStringFormatter(
|
|
163
|
-
|
|
164
|
-
'
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
let oldValue = inputRef.current?.value ?? '';
|
|
170
|
-
// Set input value to normalized valid value
|
|
171
|
-
flushSync(() => {
|
|
172
|
-
commit();
|
|
173
|
-
});
|
|
174
|
-
|
|
175
|
-
if (inputRef.current?.value !== oldValue) {
|
|
176
|
-
announce(inputRef.current?.value ?? '', 'assertive');
|
|
177
|
-
}
|
|
178
|
-
},
|
|
179
|
-
[commit, inputRef],
|
|
180
|
-
subSlot(slot, 'commitAndAnnounce'),
|
|
181
|
-
);
|
|
151
|
+
const stringFormatter = useLocalizedStringFormatter(intlMessages, '@react-aria/numberfield');
|
|
152
|
+
let commitAndAnnounce = useCallback(() => {
|
|
153
|
+
let oldValue = inputRef.current?.value ?? '';
|
|
154
|
+
// Set input value to normalized valid value
|
|
155
|
+
flushSync(() => {
|
|
156
|
+
commit();
|
|
157
|
+
});
|
|
182
158
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
159
|
+
if (inputRef.current?.value !== oldValue) {
|
|
160
|
+
announce(inputRef.current?.value ?? '', 'assertive');
|
|
161
|
+
}
|
|
162
|
+
}, [commit, inputRef]);
|
|
163
|
+
|
|
164
|
+
let inputId = useId(id);
|
|
165
|
+
let { focusProps } = useFocus({
|
|
166
|
+
onBlur() {
|
|
167
|
+
commitAndAnnounce();
|
|
189
168
|
},
|
|
190
|
-
|
|
191
|
-
);
|
|
169
|
+
});
|
|
192
170
|
|
|
193
|
-
let numberFormatter = useNumberFormatter(formatOptions
|
|
194
|
-
let intlOptions = useMemo(
|
|
195
|
-
() => numberFormatter.resolvedOptions(),
|
|
196
|
-
[numberFormatter],
|
|
197
|
-
subSlot(slot, 'intlOptions'),
|
|
198
|
-
);
|
|
171
|
+
let numberFormatter = useNumberFormatter(formatOptions);
|
|
172
|
+
let intlOptions = useMemo(() => numberFormatter.resolvedOptions(), [numberFormatter]);
|
|
199
173
|
|
|
200
174
|
// Replace negative textValue formatted using currencySign: 'accounting'
|
|
201
175
|
// with a textValue that can be announced using a minus sign.
|
|
202
|
-
let textValueFormatter = useNumberFormatter(
|
|
203
|
-
{ ...formatOptions, currencySign: undefined },
|
|
204
|
-
subSlot(slot, 'textValueFormatter'),
|
|
205
|
-
);
|
|
176
|
+
let textValueFormatter = useNumberFormatter({ ...formatOptions, currencySign: undefined });
|
|
206
177
|
let textValue = useMemo(
|
|
207
178
|
() => (isNaN(numberValue) ? '' : textValueFormatter.format(numberValue)),
|
|
208
179
|
[textValueFormatter, numberValue],
|
|
209
|
-
subSlot(slot, 'textValue'),
|
|
210
180
|
);
|
|
211
181
|
|
|
212
182
|
let {
|
|
213
183
|
spinButtonProps,
|
|
214
184
|
incrementButtonProps: incButtonProps,
|
|
215
185
|
decrementButtonProps: decButtonProps,
|
|
216
|
-
} = useSpinButton(
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
},
|
|
230
|
-
subSlot(slot, 'spinButton'),
|
|
231
|
-
);
|
|
186
|
+
} = useSpinButton({
|
|
187
|
+
isDisabled,
|
|
188
|
+
isReadOnly,
|
|
189
|
+
isRequired,
|
|
190
|
+
maxValue,
|
|
191
|
+
minValue,
|
|
192
|
+
onIncrement: increment,
|
|
193
|
+
onIncrementToMax: incrementToMax,
|
|
194
|
+
onDecrement: decrement,
|
|
195
|
+
onDecrementToMin: decrementToMin,
|
|
196
|
+
value: numberValue,
|
|
197
|
+
textValue,
|
|
198
|
+
});
|
|
232
199
|
|
|
233
|
-
let [focusWithin, setFocusWithin] = useState(false
|
|
234
|
-
let { focusWithinProps } = useFocusWithin(
|
|
235
|
-
{ isDisabled, onFocusWithinChange: setFocusWithin },
|
|
236
|
-
subSlot(slot, 'focusWithinHook'),
|
|
237
|
-
);
|
|
200
|
+
let [focusWithin, setFocusWithin] = useState(false);
|
|
201
|
+
let { focusWithinProps } = useFocusWithin({ isDisabled, onFocusWithinChange: setFocusWithin });
|
|
238
202
|
|
|
239
203
|
let onWheel = useCallback(
|
|
240
|
-
(e
|
|
204
|
+
(e) => {
|
|
241
205
|
// if on a trackpad, users can scroll in both X and Y at once, check the magnitude of the change
|
|
242
206
|
// if it's mostly in the X direction, then just return, the user probably doesn't mean to inc/dec
|
|
243
207
|
// this isn't perfect, events come in fast with small deltas and a part of the scroll may give a false indication
|
|
@@ -252,15 +216,10 @@ export function useNumberField(...args: any[]): NumberFieldAria {
|
|
|
252
216
|
}
|
|
253
217
|
},
|
|
254
218
|
[decrement, increment],
|
|
255
|
-
subSlot(slot, 'wheel'),
|
|
256
219
|
);
|
|
257
220
|
// If the input isn't supposed to receive input, disable scrolling.
|
|
258
221
|
let scrollingDisabled = isWheelDisabled || isDisabled || isReadOnly || !focusWithin;
|
|
259
|
-
useScrollWheel(
|
|
260
|
-
{ onScroll: onWheel, isDisabled: scrollingDisabled },
|
|
261
|
-
inputRef,
|
|
262
|
-
subSlot(slot, 'scrollWheel'),
|
|
263
|
-
);
|
|
222
|
+
useScrollWheel({ onScroll: onWheel, isDisabled: scrollingDisabled }, inputRef);
|
|
264
223
|
|
|
265
224
|
// The inputMode attribute influences the software keyboard that is shown on touch devices.
|
|
266
225
|
// Browsers and operating systems are quite inconsistent about what keys are available, however.
|
|
@@ -268,7 +227,7 @@ export function useNumberField(...args: any[]): NumberFieldAria {
|
|
|
268
227
|
// and based on testing on various devices to determine what keys are available in each inputMode.
|
|
269
228
|
let hasDecimals = (intlOptions.maximumFractionDigits ?? 0) > 0;
|
|
270
229
|
let hasNegative = state.minValue === undefined || isNaN(state.minValue) || state.minValue < 0;
|
|
271
|
-
let inputMode:
|
|
230
|
+
let inputMode: TextInputDOMProps['inputMode'] = 'numeric';
|
|
272
231
|
if (isIPhone()) {
|
|
273
232
|
// iPhone doesn't have a minus sign in either numeric or decimal.
|
|
274
233
|
// Note this is only for iPhone, not iPad, which always has both
|
|
@@ -314,31 +273,25 @@ export function useNumberField(...args: any[]): NumberFieldAria {
|
|
|
314
273
|
};
|
|
315
274
|
|
|
316
275
|
let domProps = filterDOMProps(props);
|
|
317
|
-
let
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
if (e.isComposing) {
|
|
322
|
-
return;
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
if (e.key === 'Enter') {
|
|
276
|
+
let { keyboardProps } = useKeyboard({
|
|
277
|
+
isDisabled: isDisabled || isReadOnly,
|
|
278
|
+
shortcuts: {
|
|
279
|
+
Enter: () => {
|
|
326
280
|
flushSync(() => {
|
|
327
281
|
commit();
|
|
328
282
|
});
|
|
329
283
|
commitValidation();
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
}
|
|
284
|
+
return { shouldPreventDefault: false };
|
|
285
|
+
},
|
|
333
286
|
},
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
);
|
|
287
|
+
onKeyDown,
|
|
288
|
+
onKeyUp,
|
|
289
|
+
});
|
|
337
290
|
|
|
338
291
|
let { isInvalid, validationErrors, validationDetails } = state.displayValidation;
|
|
339
292
|
let {
|
|
340
293
|
labelProps,
|
|
341
|
-
inputProps:
|
|
294
|
+
inputProps: textFieldPropsFromHook,
|
|
342
295
|
descriptionProps,
|
|
343
296
|
errorMessageProps,
|
|
344
297
|
} = useFormattedTextField(
|
|
@@ -367,22 +320,20 @@ export function useNumberField(...args: any[]): NumberFieldAria {
|
|
|
367
320
|
onBlur,
|
|
368
321
|
onFocus,
|
|
369
322
|
onFocusChange,
|
|
370
|
-
onKeyDown: useMemo(
|
|
371
|
-
() => chain(onKeyDownEnter, onKeyDown),
|
|
372
|
-
[onKeyDownEnter, onKeyDown],
|
|
373
|
-
subSlot(slot, 'keyDownChain'),
|
|
374
|
-
),
|
|
375
|
-
onKeyUp,
|
|
376
323
|
onPaste,
|
|
377
324
|
description,
|
|
378
325
|
errorMessage,
|
|
379
|
-
}
|
|
326
|
+
},
|
|
380
327
|
state,
|
|
381
328
|
inputRef,
|
|
382
|
-
subSlot(slot, 'textField'),
|
|
383
329
|
);
|
|
384
330
|
|
|
385
|
-
|
|
331
|
+
// Merge outside useFormattedTextField so useKeyboard's createEventHandler is not nested inside
|
|
332
|
+
// useTextField/useFocusable's createEventHandler (avoids redundant stopPropagation on RS events).
|
|
333
|
+
// Shortcuts run first (mergeProps chains the second argument after the first).
|
|
334
|
+
let textFieldProps = mergeProps(keyboardProps, textFieldPropsFromHook);
|
|
335
|
+
|
|
336
|
+
useFormReset(inputRef, state.defaultNumberValue, state.setNumberValue);
|
|
386
337
|
useNativeValidation(
|
|
387
338
|
state,
|
|
388
339
|
props.validationBehavior,
|
|
@@ -392,27 +343,31 @@ export function useNumberField(...args: any[]): NumberFieldAria {
|
|
|
392
343
|
state.maxValue,
|
|
393
344
|
props.step,
|
|
394
345
|
state.numberValue,
|
|
395
|
-
subSlot(slot, 'nativeValidation'),
|
|
396
346
|
);
|
|
397
347
|
|
|
398
|
-
let inputProps:
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
348
|
+
let inputProps: InputHTMLAttributes<HTMLInputElement> = mergeProps(
|
|
349
|
+
spinButtonProps,
|
|
350
|
+
focusProps,
|
|
351
|
+
textFieldProps,
|
|
352
|
+
{
|
|
353
|
+
// override the spinbutton role, we can't focus a spin button with VO
|
|
354
|
+
role: null,
|
|
355
|
+
// ignore aria-roledescription on iOS so that required state will announce when it is present
|
|
356
|
+
'aria-roledescription': !isIOS() ? stringFormatter.format('numberField') : null,
|
|
357
|
+
'aria-valuemax': null,
|
|
358
|
+
'aria-valuemin': null,
|
|
359
|
+
'aria-valuenow': null,
|
|
360
|
+
'aria-valuetext': null,
|
|
361
|
+
autoCorrect: 'off',
|
|
362
|
+
spellCheck: 'false',
|
|
363
|
+
},
|
|
364
|
+
);
|
|
410
365
|
|
|
411
366
|
if (props.validationBehavior === 'native') {
|
|
412
367
|
inputProps['aria-required'] = undefined;
|
|
413
368
|
}
|
|
414
369
|
|
|
415
|
-
let onButtonPressStart = (e:
|
|
370
|
+
let onButtonPressStart = (e: import('@react-types/shared').PressEvent) => {
|
|
416
371
|
// If focus is already on the input, keep it there so we don't hide the
|
|
417
372
|
// software keyboard when tapping the increment/decrement buttons.
|
|
418
373
|
if (getActiveElement() === inputRef.current) {
|
|
@@ -425,7 +380,7 @@ export function useNumberField(...args: any[]): NumberFieldAria {
|
|
|
425
380
|
if (e.pointerType === 'mouse') {
|
|
426
381
|
inputRef.current?.focus();
|
|
427
382
|
} else {
|
|
428
|
-
e.target.focus();
|
|
383
|
+
(e.target as HTMLElement).focus();
|
|
429
384
|
}
|
|
430
385
|
};
|
|
431
386
|
|
|
@@ -445,9 +400,10 @@ export function useNumberField(...args: any[]): NumberFieldAria {
|
|
|
445
400
|
ariaLabelledby = props.label != null ? labelProps.id : props['aria-labelledby'];
|
|
446
401
|
}
|
|
447
402
|
|
|
448
|
-
let incrementId = useId(
|
|
449
|
-
let decrementId = useId(
|
|
403
|
+
let incrementId = useId();
|
|
404
|
+
let decrementId = useId();
|
|
450
405
|
|
|
406
|
+
// oxlint-disable-next-line react/react-compiler
|
|
451
407
|
let incrementButtonProps: AriaButtonProps = mergeProps(incButtonProps, {
|
|
452
408
|
'aria-label': incrementAriaLabel || stringFormatter.format('increase', { fieldLabel }).trim(),
|
|
453
409
|
id: ariaLabelledby && !incrementAriaLabel ? incrementId : null,
|
|
@@ -461,6 +417,7 @@ export function useNumberField(...args: any[]): NumberFieldAria {
|
|
|
461
417
|
onPressStart: onButtonPressStart,
|
|
462
418
|
});
|
|
463
419
|
|
|
420
|
+
// oxlint-disable-next-line react/react-compiler
|
|
464
421
|
let decrementButtonProps: AriaButtonProps = mergeProps(decButtonProps, {
|
|
465
422
|
'aria-label': decrementAriaLabel || stringFormatter.format('decrease', { fieldLabel }).trim(),
|
|
466
423
|
id: ariaLabelledby && !decrementAriaLabel ? decrementId : null,
|
|
@@ -504,69 +461,62 @@ function useNativeValidation(
|
|
|
504
461
|
max: number | undefined,
|
|
505
462
|
step: number | undefined,
|
|
506
463
|
value: number | undefined,
|
|
507
|
-
slot: symbol | undefined,
|
|
508
464
|
) {
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
) {
|
|
520
|
-
return;
|
|
521
|
-
}
|
|
465
|
+
useLayoutEffect(() => {
|
|
466
|
+
let input = inputRef.current;
|
|
467
|
+
if (
|
|
468
|
+
commitBehavior !== 'validate' ||
|
|
469
|
+
state.realtimeValidation.isInvalid ||
|
|
470
|
+
!input ||
|
|
471
|
+
input.disabled
|
|
472
|
+
) {
|
|
473
|
+
return;
|
|
474
|
+
}
|
|
522
475
|
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
476
|
+
// Create a native number input and use it to implement validation of min/max/step.
|
|
477
|
+
// This lets us get the native validation message provided by the browser instead of needing our own translations.
|
|
478
|
+
if (!numberInput && typeof document !== 'undefined') {
|
|
479
|
+
numberInput = document.createElement('input');
|
|
480
|
+
numberInput.type = 'number';
|
|
481
|
+
}
|
|
529
482
|
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
483
|
+
if (!numberInput) {
|
|
484
|
+
// For TypeScript.
|
|
485
|
+
return;
|
|
486
|
+
}
|
|
534
487
|
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
null,
|
|
570
|
-
subSlot(slot, 'effect'),
|
|
571
|
-
);
|
|
488
|
+
numberInput.min = min != null && !isNaN(min) ? String(min) : '';
|
|
489
|
+
numberInput.max = max != null && !isNaN(max) ? String(max) : '';
|
|
490
|
+
numberInput.step = step != null && !isNaN(step) ? String(step) : '';
|
|
491
|
+
numberInput.value = value != null && !isNaN(value) ? String(value) : '';
|
|
492
|
+
|
|
493
|
+
// Merge validity with the visible text input (for other validations like required).
|
|
494
|
+
let valid = input.validity.valid && numberInput.validity.valid;
|
|
495
|
+
let validationMessage = input.validationMessage || numberInput.validationMessage;
|
|
496
|
+
let validity = {
|
|
497
|
+
isInvalid: !valid,
|
|
498
|
+
validationErrors: validationMessage ? [validationMessage] : [],
|
|
499
|
+
validationDetails: {
|
|
500
|
+
badInput: input.validity.badInput,
|
|
501
|
+
customError: input.validity.customError,
|
|
502
|
+
patternMismatch: input.validity.patternMismatch,
|
|
503
|
+
rangeOverflow: numberInput.validity.rangeOverflow,
|
|
504
|
+
rangeUnderflow: numberInput.validity.rangeUnderflow,
|
|
505
|
+
stepMismatch: numberInput.validity.stepMismatch,
|
|
506
|
+
tooLong: input.validity.tooLong,
|
|
507
|
+
tooShort: input.validity.tooShort,
|
|
508
|
+
typeMismatch: input.validity.typeMismatch,
|
|
509
|
+
valueMissing: input.validity.valueMissing,
|
|
510
|
+
valid,
|
|
511
|
+
},
|
|
512
|
+
};
|
|
513
|
+
|
|
514
|
+
state.updateValidation(validity);
|
|
515
|
+
|
|
516
|
+
// Block form submission if validation behavior is native.
|
|
517
|
+
// This won't overwrite any user-defined validation message because we checked realtimeValidation above.
|
|
518
|
+
if (validationBehavior === 'native' && !numberInput.validity.valid) {
|
|
519
|
+
input.setCustomValidity(numberInput.validationMessage);
|
|
520
|
+
}
|
|
521
|
+
});
|
|
572
522
|
}
|
package/src/overlays/Overlay.ts
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
// Provider descriptor keeps a stable `{ value, children }` shape; React's ReactNode/JSX/Context
|
|
6
6
|
// types → `any`; `useOverlayFocusContain` gets public-hook slot threading.
|
|
7
7
|
import { ClearPressResponder } from '../interactions/PressResponder';
|
|
8
|
+
import { FocusableContext } from '../interactions/useFocusable';
|
|
8
9
|
import { FocusScope } from '../focus/FocusScope';
|
|
9
10
|
import { createContext, createElement, createPortal, useContext, useMemo, useState } from 'octane';
|
|
10
11
|
import { useIsSSR } from '../ssr/SSRProvider';
|
|
@@ -81,7 +82,10 @@ export function Overlay(props: OverlayProps): any {
|
|
|
81
82
|
|
|
82
83
|
contents = createElement(OverlayContext.Provider, {
|
|
83
84
|
value: contextValue as OverlayContextValue,
|
|
84
|
-
children: createElement(
|
|
85
|
+
children: createElement(FocusableContext.Provider, {
|
|
86
|
+
value: null,
|
|
87
|
+
children: createElement(ClearPressResponder, { children: contents }),
|
|
88
|
+
}),
|
|
85
89
|
});
|
|
86
90
|
|
|
87
91
|
return createPortal(contents, portalContainer);
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
// no slot threading. Module-level ref-count map and observer stack are ported verbatim.
|
|
5
5
|
import { createShadowTreeWalker } from '../utils/shadowdom/ShadowTreeWalker';
|
|
6
6
|
|
|
7
|
-
import { getOwnerDocument, getOwnerWindow } from '../utils/domHelpers';
|
|
7
|
+
import { getOwnerDocument, getOwnerWindow, isShadowRoot } from '../utils/domHelpers';
|
|
8
8
|
import { nodeContains } from '../utils/shadowdom/DOMFunctions';
|
|
9
9
|
import { shadowDOM } from '../stately/flags';
|
|
10
10
|
|
|
@@ -72,14 +72,12 @@ export function ariaHideOutside(targets: Element[], options?: AriaHideOutsideOpt
|
|
|
72
72
|
if (shadowDOM()) {
|
|
73
73
|
// find all shadow roots that are ancestors of the targets
|
|
74
74
|
// traverse upwards until the root is reached
|
|
75
|
+
let boundary = root.getRootNode();
|
|
75
76
|
for (let target of targets) {
|
|
76
|
-
let
|
|
77
|
-
while (
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
shadowRootsToWatch.add(root.shadowRoot as ShadowRoot);
|
|
81
|
-
}
|
|
82
|
-
node = root.parentNode as Element;
|
|
77
|
+
let current = target.getRootNode();
|
|
78
|
+
while (isShadowRoot(current) && current !== boundary) {
|
|
79
|
+
shadowRootsToWatch.add(current);
|
|
80
|
+
current = current.host.getRootNode();
|
|
83
81
|
}
|
|
84
82
|
}
|
|
85
83
|
}
|
|
@@ -1,10 +1,15 @@
|
|
|
1
|
+
import { addEvent } from '../utils/domHelpers';
|
|
1
2
|
// Ported from react-aria (source: .react-spectrum/packages/react-aria/src/overlays/useCloseOnScroll.ts).
|
|
2
3
|
// octane adaptations: public-hook slot threading (splitSlot/subSlot) per the binding
|
|
3
4
|
// convention; the explicit dependency array is kept verbatim.
|
|
4
5
|
//
|
|
5
6
|
// NOTE: ported ahead of the rest of the overlays area because useOverlayTrigger (needed by
|
|
6
7
|
// useMenuTrigger) shares its `onCloseMap` backward-compatibility channel.
|
|
7
|
-
import {
|
|
8
|
+
import {
|
|
9
|
+
getEventTarget,
|
|
10
|
+
getPropagationTargets,
|
|
11
|
+
nodeContains,
|
|
12
|
+
} from '../utils/shadowdom/DOMFunctions';
|
|
8
13
|
import type { RefObject } from '@react-types/shared';
|
|
9
14
|
import { useEffect } from 'octane';
|
|
10
15
|
|
|
@@ -64,10 +69,7 @@ export function useCloseOnScroll(...args: any[]): void {
|
|
|
64
69
|
}
|
|
65
70
|
};
|
|
66
71
|
|
|
67
|
-
|
|
68
|
-
return () => {
|
|
69
|
-
window.removeEventListener('scroll', onScroll, true);
|
|
70
|
-
};
|
|
72
|
+
return addEvent(getPropagationTargets(triggerRef.current), 'scroll', onScroll, true);
|
|
71
73
|
},
|
|
72
74
|
[isOpen, onClose, triggerRef],
|
|
73
75
|
subSlot(slot, 'scroll'),
|