@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.
Files changed (201) hide show
  1. package/README.md +7 -6
  2. package/UPSTREAM.md +58 -69
  3. package/package.json +11 -11
  4. package/src/calendar/useCalendarGrid.tsx +64 -62
  5. package/src/collections/Document.ts +3 -0
  6. package/src/color/useColorArea.tsx +95 -69
  7. package/src/color/useColorWheel.tsx +38 -37
  8. package/src/combobox/{useComboBox.ts → useComboBox.tsx} +243 -258
  9. package/src/components/ComboBox.ts +35 -2
  10. package/src/components/Dialog.ts +12 -1
  11. package/src/components/DropZone.tsx +3 -2
  12. package/src/components/FileTrigger.tsx +1 -0
  13. package/src/components/GridList.ts +11 -2
  14. package/src/components/ListBox.ts +1 -1
  15. package/src/components/Menu.ts +1 -0
  16. package/src/components/Meter.ts +3 -1
  17. package/src/components/Popover.ts +20 -8
  18. package/src/components/PreviewTrigger.tsx +101 -0
  19. package/src/components/ProgressBar.ts +10 -2
  20. package/src/components/Select.ts +1 -1
  21. package/src/components/Table.ts +26 -15
  22. package/src/components/TokenField.tsx +395 -0
  23. package/src/components/Tooltip.ts +4 -6
  24. package/src/components/Tree.ts +14 -7
  25. package/src/components/Virtualizer.tsx +30 -14
  26. package/src/components/index.ts +22 -0
  27. package/src/components/utils.ts +1 -1
  28. package/src/datepicker/useDateField.tsx +2 -11
  29. package/src/datepicker/useDatePickerGroup.tsx +44 -33
  30. package/src/datepicker/useDateSegment.tsx +31 -30
  31. package/src/dialog/useDialog.ts +8 -1
  32. package/src/dnd/DragManager.tsx +5 -1
  33. package/src/dnd/useDrag.tsx +2 -1
  34. package/src/focus/FocusScope.ts +2 -2
  35. package/src/grid/useGrid.ts +3 -0
  36. package/src/grid/{useGridCell.ts → useGridCell.tsx} +128 -66
  37. package/src/grid/utils.ts +1 -0
  38. package/src/gridlist/{useGridListItem.ts → useGridListItem.tsx} +154 -112
  39. package/src/index.ts +22 -0
  40. package/src/interactions/createEventHandler.ts +11 -8
  41. package/src/interactions/createKeyboardShortcutHandler.tsx +218 -0
  42. package/src/interactions/textSelection.ts +3 -3
  43. package/src/interactions/useContextMenu.tsx +125 -0
  44. package/src/interactions/useFocusVisible.ts +4 -4
  45. package/src/interactions/useKeyboard.ts +50 -2
  46. package/src/interactions/useLongPress.ts +33 -22
  47. package/src/interactions/useMove.ts +6 -6
  48. package/src/interactions/usePress.ts +8 -0
  49. package/src/interactions/utils.ts +10 -10
  50. package/src/intl/combobox/fi-FI.json +2 -2
  51. package/src/intl/previewtrigger/ar-AE.json +3 -0
  52. package/src/intl/previewtrigger/bg-BG.json +3 -0
  53. package/src/intl/previewtrigger/cs-CZ.json +3 -0
  54. package/src/intl/previewtrigger/da-DK.json +3 -0
  55. package/src/intl/previewtrigger/de-DE.json +3 -0
  56. package/src/intl/previewtrigger/el-GR.json +3 -0
  57. package/src/intl/previewtrigger/en-US.json +3 -0
  58. package/src/intl/previewtrigger/es-ES.json +3 -0
  59. package/src/intl/previewtrigger/et-EE.json +3 -0
  60. package/src/intl/previewtrigger/fi-FI.json +3 -0
  61. package/src/intl/previewtrigger/fr-FR.json +3 -0
  62. package/src/intl/previewtrigger/he-IL.json +3 -0
  63. package/src/intl/previewtrigger/hr-HR.json +3 -0
  64. package/src/intl/previewtrigger/hu-HU.json +3 -0
  65. package/src/intl/previewtrigger/index.ts +73 -0
  66. package/src/intl/previewtrigger/it-IT.json +3 -0
  67. package/src/intl/previewtrigger/ja-JP.json +3 -0
  68. package/src/intl/previewtrigger/ko-KR.json +3 -0
  69. package/src/intl/previewtrigger/lt-LT.json +3 -0
  70. package/src/intl/previewtrigger/lv-LV.json +3 -0
  71. package/src/intl/previewtrigger/nb-NO.json +3 -0
  72. package/src/intl/previewtrigger/nl-NL.json +3 -0
  73. package/src/intl/previewtrigger/pl-PL.json +3 -0
  74. package/src/intl/previewtrigger/pt-BR.json +3 -0
  75. package/src/intl/previewtrigger/pt-PT.json +3 -0
  76. package/src/intl/previewtrigger/ro-RO.json +3 -0
  77. package/src/intl/previewtrigger/ru-RU.json +3 -0
  78. package/src/intl/previewtrigger/sk-SK.json +3 -0
  79. package/src/intl/previewtrigger/sl-SI.json +3 -0
  80. package/src/intl/previewtrigger/sr-SP.json +3 -0
  81. package/src/intl/previewtrigger/sv-SE.json +3 -0
  82. package/src/intl/previewtrigger/tr-TR.json +3 -0
  83. package/src/intl/previewtrigger/uk-UA.json +3 -0
  84. package/src/intl/previewtrigger/zh-CN.json +3 -0
  85. package/src/intl/previewtrigger/zh-TW.json +3 -0
  86. package/src/intl/table/ar-AE.json +3 -3
  87. package/src/intl/table/bg-BG.json +3 -3
  88. package/src/intl/table/cs-CZ.json +3 -3
  89. package/src/intl/table/da-DK.json +3 -3
  90. package/src/intl/table/de-DE.json +3 -3
  91. package/src/intl/table/el-GR.json +3 -3
  92. package/src/intl/table/es-ES.json +3 -3
  93. package/src/intl/table/et-EE.json +3 -3
  94. package/src/intl/table/fi-FI.json +3 -3
  95. package/src/intl/table/fr-FR.json +3 -3
  96. package/src/intl/table/he-IL.json +3 -3
  97. package/src/intl/table/hr-HR.json +3 -3
  98. package/src/intl/table/hu-HU.json +3 -3
  99. package/src/intl/table/it-IT.json +3 -3
  100. package/src/intl/table/ja-JP.json +3 -3
  101. package/src/intl/table/ko-KR.json +3 -3
  102. package/src/intl/table/lt-LT.json +3 -3
  103. package/src/intl/table/lv-LV.json +3 -3
  104. package/src/intl/table/nb-NO.json +3 -3
  105. package/src/intl/table/nl-NL.json +3 -3
  106. package/src/intl/table/pl-PL.json +3 -3
  107. package/src/intl/table/pt-BR.json +3 -3
  108. package/src/intl/table/pt-PT.json +3 -3
  109. package/src/intl/table/ro-RO.json +3 -3
  110. package/src/intl/table/ru-RU.json +3 -3
  111. package/src/intl/table/sk-SK.json +3 -3
  112. package/src/intl/table/sl-SI.json +3 -3
  113. package/src/intl/table/sr-SP.json +3 -3
  114. package/src/intl/table/sv-SE.json +3 -3
  115. package/src/intl/table/tr-TR.json +3 -3
  116. package/src/intl/table/uk-UA.json +3 -3
  117. package/src/intl/table/zh-CN.json +4 -4
  118. package/src/intl/table/zh-TW.json +3 -3
  119. package/src/intl/tree/es-ES.json +1 -1
  120. package/src/link/useLink.ts +2 -0
  121. package/src/menu/useMenuItem.ts +33 -36
  122. package/src/menu/useMenuTrigger.tsx +222 -0
  123. package/src/menu/{useSubmenuTrigger.ts → useSubmenuTrigger.tsx} +116 -151
  124. package/src/numberfield/{useNumberField.ts → useNumberField.tsx} +188 -238
  125. package/src/overlays/Overlay.ts +5 -1
  126. package/src/overlays/ariaHideOutside.ts +6 -8
  127. package/src/overlays/useCloseOnScroll.ts +7 -5
  128. package/src/overlays/{useOverlay.ts → useOverlay.tsx} +76 -83
  129. package/src/overlays/usePopover.ts +10 -5
  130. package/src/overlays/usePreventScroll.ts +4 -4
  131. package/src/progress/useProgressBar.ts +2 -1
  132. package/src/radio/{useRadioGroup.ts → useRadioGroup.tsx} +87 -79
  133. package/src/searchfield/useSearchField.tsx +152 -0
  134. package/src/select/useSelect.ts +20 -20
  135. package/src/selection/useSelectableCollection.tsx +755 -0
  136. package/src/selection/useSelectableItem.ts +48 -11
  137. package/src/slider/{useSliderThumb.ts → useSliderThumb.tsx} +120 -158
  138. package/src/spinbutton/useSpinButton.tsx +335 -0
  139. package/src/stately/combobox/useComboBoxState.ts +1 -1
  140. package/src/stately/grid/useGridState.ts +9 -10
  141. package/src/stately/index.ts +11 -0
  142. package/src/stately/menu/useMenuTriggerState.ts +1 -1
  143. package/src/stately/menu/useSubmenuTriggerState.ts +2 -0
  144. package/src/stately/overlays/useOverlayTriggerState.ts +12 -1
  145. package/src/stately/radio/useRadioGroupState.ts +5 -1
  146. package/src/stately/table/TableUtils.ts +15 -1
  147. package/src/stately/table/useTableState.ts +7 -0
  148. package/src/stately/tokenfield/TokenFieldValue.tsx +440 -0
  149. package/src/stately/tokenfield/useTokenFieldState.tsx +57 -0
  150. package/src/stately/tooltip/useTooltipTriggerState.ts +16 -7
  151. package/src/stately/virtualizer/Layout.tsx +2 -0
  152. package/src/stately/virtualizer/ScrollAnchor.tsx +338 -0
  153. package/src/stately/virtualizer/Virtualizer.tsx +52 -1
  154. package/src/stately/virtualizer/types.tsx +2 -0
  155. package/src/table/useTableCell.ts +12 -0
  156. package/src/table/useTableColumnHeader.ts +8 -1
  157. package/src/table/useTableColumnResize.tsx +359 -0
  158. package/src/tag/useTag.ts +22 -15
  159. package/src/textfield/useTextField.ts +2 -0
  160. package/src/tokenfield/useToken.tsx +64 -0
  161. package/src/tokenfield/useTokenField.tsx +849 -0
  162. package/src/tooltip/usePreviewTrigger.tsx +266 -0
  163. package/src/tooltip/useSafeArea.tsx +171 -0
  164. package/src/upstream-exports/react-aria/mergeRefs.tsx +3 -0
  165. package/src/upstream-exports/react-aria/usePreviewTrigger.tsx +18 -0
  166. package/src/upstream-exports/react-aria/useTokenField.tsx +22 -0
  167. package/src/upstream-exports/react-stately/private/collections/getChildNodes.tsx +8 -0
  168. package/src/upstream-exports/react-stately/private/collections/getItemCount.tsx +2 -0
  169. package/src/upstream-exports/react-stately/private/grid/GridCollection.tsx +7 -0
  170. package/src/upstream-exports/react-stately/private/grid/useGridState.tsx +6 -0
  171. package/src/upstream-exports/react-stately/private/utils/number.tsx +2 -0
  172. package/src/upstream-exports/react-stately/useComboBoxState.tsx +23 -0
  173. package/src/upstream-exports/react-stately/useListState.tsx +15 -0
  174. package/src/upstream-exports/react-stately/useMenuTriggerState.tsx +26 -0
  175. package/src/upstream-exports/react-stately/useMultipleSelectionState.tsx +21 -0
  176. package/src/upstream-exports/react-stately/useNumberFieldState.tsx +20 -0
  177. package/src/upstream-exports/react-stately/useOverlayTriggerState.tsx +20 -0
  178. package/src/upstream-exports/react-stately/useRadioGroupState.tsx +16 -0
  179. package/src/upstream-exports/react-stately/useSearchFieldState.tsx +19 -0
  180. package/src/upstream-exports/react-stately/useSliderState.tsx +20 -0
  181. package/src/upstream-exports/react-stately/useTableState.tsx +45 -0
  182. package/src/upstream-exports/react-stately/useTokenFieldState.tsx +25 -0
  183. package/src/upstream-exports/react-stately/useTooltipTriggerState.tsx +20 -0
  184. package/src/upstream-exports/react-stately/useTreeState.tsx +14 -0
  185. package/src/utils/domHelpers.ts +74 -17
  186. package/src/utils/isVirtualEvent.ts +2 -1
  187. package/src/utils/openLink.ts +8 -3
  188. package/src/utils/platform.ts +7 -3
  189. package/src/utils/scrollIntoView.ts +4 -4
  190. package/src/utils/shadowdom/DOMFunctions.ts +42 -2
  191. package/src/utils/useId.ts +11 -2
  192. package/src/utils/useLoadMoreSentinel.ts +8 -3
  193. package/src/utils/useViewportSize.ts +3 -3
  194. package/src/virtualizer/ScrollView.tsx +13 -4
  195. package/src/virtualizer/VirtualizerItem.tsx +4 -1
  196. package/src/virtualizer/useVirtualizerItem.tsx +43 -3
  197. package/src/menu/useMenuTrigger.ts +0 -189
  198. package/src/searchfield/useSearchField.ts +0 -156
  199. package/src/selection/useSelectableCollection.ts +0 -750
  200. package/src/spinbutton/useSpinButton.ts +0 -380
  201. package/src/table/useTableColumnResize.ts +0 -374
@@ -1,71 +1,69 @@
1
- // Ported from react-aria (source: .react-spectrum/packages/react-aria/src/numberfield/useNumberField.ts).
2
- // octane adaptations:
3
- // - Handlers receive NATIVE events. The Enter-commit keydown handler flows through the
4
- // ported useKeyboard wrapper (so `continuePropagation()` works) and upstream's
5
- // `e.nativeEvent.isComposing` reads `e.isComposing` off the native event; onPaste takes
6
- // the native ClipboardEvent. The value-level `onChange(value)` callback is unchanged —
7
- // the per-keystroke DOM wiring rides the native `input` event inside the ported
8
- // useTextField, and the blur commit rides the native blur/focusout path in useFocus.
9
- // - `flushSync` comes from 'octane'.
10
- // - The Parcel glob intl import becomes the generated src/intl/numberfield index
11
- // (verbatim dictionaries).
12
- // - React's InputHTMLAttributes/LabelHTMLAttributes/GroupDOMAttributes element types
13
- // collapse to the local structural `DOMAttributes` prop bag; `TextInputDOMEvents` is
14
- // declared structurally over native events (upstream's drags React handler types).
15
- // - `useNativeValidation`'s dep-less useLayoutEffect keeps run-every-render semantics via
16
- // an explicit `null` dependency argument, and threads a sub-slot like every other hook
17
- // call (it is an internal helper invoked exactly once per useNumberField call).
18
- // - Public-hook slot threading (splitSlot/subSlot); explicit dependency arrays are kept
19
- // verbatim.
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 type { AriaButtonProps } from '../button/useButton';
22
- import type { AriaLabelingProps, DOMProps, RefObject, ValidationResult } from '@react-types/shared';
23
- import { chain } from '../utils/chain';
24
- import { flushSync, useCallback, useMemo, useState } from 'octane';
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 type {
50
+ import {
31
51
  NumberFieldProps,
32
52
  NumberFieldState,
33
- } from '../stately/numberfield/useNumberFieldState';
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: DOMAttributes;
87
+ labelProps: LabelHTMLAttributes<HTMLLabelElement>;
90
88
  /** Props for the group wrapper around the input and stepper buttons. */
91
- groupProps: DOMAttributes;
89
+ groupProps: GroupDOMAttributes;
92
90
  /** Props for the input element. */
93
- inputProps: DOMAttributes;
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
- ): NumberFieldAria;
114
- // Slot-threading form: sibling ported hooks pass their derived sub-slot as the trailing arg.
115
- export function useNumberField(
116
- props: AriaNumberFieldProps,
117
- state: NumberFieldState,
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
- intlMessages,
164
- '@react-aria/numberfield',
165
- subSlot(slot, 'strings'),
166
- );
167
- let commitAndAnnounce = useCallback(
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
- let inputId = useId(id, subSlot(slot, 'inputId'));
184
- let { focusProps } = useFocus(
185
- {
186
- onBlur() {
187
- commitAndAnnounce();
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
- subSlot(slot, 'focus'),
191
- );
169
+ });
192
170
 
193
- let numberFormatter = useNumberFormatter(formatOptions, subSlot(slot, 'formatter'));
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
- isDisabled,
219
- isReadOnly,
220
- isRequired,
221
- maxValue,
222
- minValue,
223
- onIncrement: increment,
224
- onIncrementToMax: incrementToMax,
225
- onDecrement: decrement,
226
- onDecrementToMin: decrementToMin,
227
- value: numberValue,
228
- textValue,
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, subSlot(slot, 'focusWithin'));
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: { deltaX: number; deltaY: number }) => {
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: string = 'numeric';
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 onKeyDownEnter = useCallback(
318
- (e: any) => {
319
- // octane adaptation: `e.nativeEvent.isComposing` → the native event's own flag
320
- // (the wrapped keyboard event proxies it through).
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
- } else {
331
- e.continuePropagation();
332
- }
284
+ return { shouldPreventDefault: false };
285
+ },
333
286
  },
334
- [commit, commitValidation],
335
- subSlot(slot, 'keyDownEnter'),
336
- );
287
+ onKeyDown,
288
+ onKeyUp,
289
+ });
337
290
 
338
291
  let { isInvalid, validationErrors, validationDetails } = state.displayValidation;
339
292
  let {
340
293
  labelProps,
341
- inputProps: textFieldProps,
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
- } as any,
326
+ },
380
327
  state,
381
328
  inputRef,
382
- subSlot(slot, 'textField'),
383
329
  );
384
330
 
385
- useFormReset(inputRef, state.defaultNumberValue, state.setNumberValue, subSlot(slot, 'reset'));
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: DOMAttributes = mergeProps(spinButtonProps, focusProps, textFieldProps, {
399
- // override the spinbutton role, we can't focus a spin button with VO
400
- role: null,
401
- // ignore aria-roledescription on iOS so that required state will announce when it is present
402
- 'aria-roledescription': !isIOS() ? stringFormatter.format('numberField') : null,
403
- 'aria-valuemax': null,
404
- 'aria-valuemin': null,
405
- 'aria-valuenow': null,
406
- 'aria-valuetext': null,
407
- autoCorrect: 'off',
408
- spellCheck: 'false',
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: any) => {
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(subSlot(slot, 'incrementId'));
449
- let decrementId = useId(subSlot(slot, 'decrementId'));
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
- // octane adaptation: upstream's dep-less useLayoutEffect (run after every render) is
510
- // an explicit `null` dependency argument here.
511
- useLayoutEffect(
512
- () => {
513
- let input = inputRef.current;
514
- if (
515
- commitBehavior !== 'validate' ||
516
- state.realtimeValidation.isInvalid ||
517
- !input ||
518
- input.disabled
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
- // Create a native number input and use it to implement validation of min/max/step.
524
- // This lets us get the native validation message provided by the browser instead of needing our own translations.
525
- if (!numberInput && typeof document !== 'undefined') {
526
- numberInput = document.createElement('input');
527
- numberInput.type = 'number';
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
- if (!numberInput) {
531
- // For TypeScript.
532
- return;
533
- }
483
+ if (!numberInput) {
484
+ // For TypeScript.
485
+ return;
486
+ }
534
487
 
535
- numberInput.min = min != null && !isNaN(min) ? String(min) : '';
536
- numberInput.max = max != null && !isNaN(max) ? String(max) : '';
537
- numberInput.step = step != null && !isNaN(step) ? String(step) : '';
538
- numberInput.value = value != null && !isNaN(value) ? String(value) : '';
539
-
540
- // Merge validity with the visible text input (for other validations like required).
541
- let valid = input.validity.valid && numberInput.validity.valid;
542
- let validationMessage = input.validationMessage || numberInput.validationMessage;
543
- let validity = {
544
- isInvalid: !valid,
545
- validationErrors: validationMessage ? [validationMessage] : [],
546
- validationDetails: {
547
- badInput: input.validity.badInput,
548
- customError: input.validity.customError,
549
- patternMismatch: input.validity.patternMismatch,
550
- rangeOverflow: numberInput.validity.rangeOverflow,
551
- rangeUnderflow: numberInput.validity.rangeUnderflow,
552
- stepMismatch: numberInput.validity.stepMismatch,
553
- tooLong: input.validity.tooLong,
554
- tooShort: input.validity.tooShort,
555
- typeMismatch: input.validity.typeMismatch,
556
- valueMissing: input.validity.valueMissing,
557
- valid,
558
- },
559
- };
560
-
561
- state.updateValidation(validity);
562
-
563
- // Block form submission if validation behavior is native.
564
- // This won't overwrite any user-defined validation message because we checked realtimeValidation above.
565
- if (validationBehavior === 'native' && !numberInput.validity.valid) {
566
- input.setCustomValidity(numberInput.validationMessage);
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
  }
@@ -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(ClearPressResponder, { children: contents }),
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 node = target;
77
- while (node && node !== root) {
78
- let root = node.getRootNode();
79
- if ('shadowRoot' in root) {
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 { getEventTarget, nodeContains } from '../utils/shadowdom/DOMFunctions';
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
- window.addEventListener('scroll', onScroll, true);
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'),