@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.
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} +239 -259
  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} +121 -64
  37. package/src/grid/utils.ts +1 -0
  38. package/src/gridlist/{useGridListItem.ts → useGridListItem.tsx} +152 -111
  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 +217 -0
  123. package/src/menu/{useSubmenuTrigger.ts → useSubmenuTrigger.tsx} +112 -152
  124. package/src/numberfield/{useNumberField.ts → useNumberField.tsx} +185 -239
  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} +72 -84
  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} +83 -80
  133. package/src/searchfield/useSearchField.tsx +147 -0
  134. package/src/select/useSelect.ts +20 -20
  135. package/src/selection/useSelectableCollection.tsx +751 -0
  136. package/src/selection/useSelectableItem.ts +48 -11
  137. package/src/slider/{useSliderThumb.ts → useSliderThumb.tsx} +116 -159
  138. package/src/spinbutton/useSpinButton.tsx +330 -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 +354 -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 +3 -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 } 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,21 +108,8 @@ 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>;
127
-
111
+ _slot?: symbol,
112
+ ): NumberFieldAria {
128
113
  let {
129
114
  id,
130
115
  decrementAriaLabel,
@@ -159,85 +144,60 @@ export function useNumberField(...args: any[]): NumberFieldAria {
159
144
  commitValidation,
160
145
  } = state;
161
146
 
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
- );
147
+ const stringFormatter = useLocalizedStringFormatter(intlMessages, '@react-aria/numberfield');
148
+ let commitAndAnnounce = useCallback(() => {
149
+ let oldValue = inputRef.current?.value ?? '';
150
+ // Set input value to normalized valid value
151
+ flushSync(() => {
152
+ commit();
153
+ });
182
154
 
183
- let inputId = useId(id, subSlot(slot, 'inputId'));
184
- let { focusProps } = useFocus(
185
- {
186
- onBlur() {
187
- commitAndAnnounce();
188
- },
155
+ if (inputRef.current?.value !== oldValue) {
156
+ announce(inputRef.current?.value ?? '', 'assertive');
157
+ }
158
+ }, [commit, inputRef]);
159
+
160
+ let inputId = useId(id);
161
+ let { focusProps } = useFocus({
162
+ onBlur() {
163
+ commitAndAnnounce();
189
164
  },
190
- subSlot(slot, 'focus'),
191
- );
165
+ });
192
166
 
193
- let numberFormatter = useNumberFormatter(formatOptions, subSlot(slot, 'formatter'));
194
- let intlOptions = useMemo(
195
- () => numberFormatter.resolvedOptions(),
196
- [numberFormatter],
197
- subSlot(slot, 'intlOptions'),
198
- );
167
+ let numberFormatter = useNumberFormatter(formatOptions);
168
+ let intlOptions = useMemo(() => numberFormatter.resolvedOptions(), [numberFormatter]);
199
169
 
200
170
  // Replace negative textValue formatted using currencySign: 'accounting'
201
171
  // with a textValue that can be announced using a minus sign.
202
- let textValueFormatter = useNumberFormatter(
203
- { ...formatOptions, currencySign: undefined },
204
- subSlot(slot, 'textValueFormatter'),
205
- );
172
+ let textValueFormatter = useNumberFormatter({ ...formatOptions, currencySign: undefined });
206
173
  let textValue = useMemo(
207
174
  () => (isNaN(numberValue) ? '' : textValueFormatter.format(numberValue)),
208
175
  [textValueFormatter, numberValue],
209
- subSlot(slot, 'textValue'),
210
176
  );
211
177
 
212
178
  let {
213
179
  spinButtonProps,
214
180
  incrementButtonProps: incButtonProps,
215
181
  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
- );
182
+ } = useSpinButton({
183
+ isDisabled,
184
+ isReadOnly,
185
+ isRequired,
186
+ maxValue,
187
+ minValue,
188
+ onIncrement: increment,
189
+ onIncrementToMax: incrementToMax,
190
+ onDecrement: decrement,
191
+ onDecrementToMin: decrementToMin,
192
+ value: numberValue,
193
+ textValue,
194
+ });
232
195
 
233
- let [focusWithin, setFocusWithin] = useState(false, subSlot(slot, 'focusWithin'));
234
- let { focusWithinProps } = useFocusWithin(
235
- { isDisabled, onFocusWithinChange: setFocusWithin },
236
- subSlot(slot, 'focusWithinHook'),
237
- );
196
+ let [focusWithin, setFocusWithin] = useState(false);
197
+ let { focusWithinProps } = useFocusWithin({ isDisabled, onFocusWithinChange: setFocusWithin });
238
198
 
239
199
  let onWheel = useCallback(
240
- (e: { deltaX: number; deltaY: number }) => {
200
+ (e) => {
241
201
  // if on a trackpad, users can scroll in both X and Y at once, check the magnitude of the change
242
202
  // if it's mostly in the X direction, then just return, the user probably doesn't mean to inc/dec
243
203
  // this isn't perfect, events come in fast with small deltas and a part of the scroll may give a false indication
@@ -252,15 +212,10 @@ export function useNumberField(...args: any[]): NumberFieldAria {
252
212
  }
253
213
  },
254
214
  [decrement, increment],
255
- subSlot(slot, 'wheel'),
256
215
  );
257
216
  // If the input isn't supposed to receive input, disable scrolling.
258
217
  let scrollingDisabled = isWheelDisabled || isDisabled || isReadOnly || !focusWithin;
259
- useScrollWheel(
260
- { onScroll: onWheel, isDisabled: scrollingDisabled },
261
- inputRef,
262
- subSlot(slot, 'scrollWheel'),
263
- );
218
+ useScrollWheel({ onScroll: onWheel, isDisabled: scrollingDisabled }, inputRef);
264
219
 
265
220
  // The inputMode attribute influences the software keyboard that is shown on touch devices.
266
221
  // Browsers and operating systems are quite inconsistent about what keys are available, however.
@@ -268,7 +223,7 @@ export function useNumberField(...args: any[]): NumberFieldAria {
268
223
  // and based on testing on various devices to determine what keys are available in each inputMode.
269
224
  let hasDecimals = (intlOptions.maximumFractionDigits ?? 0) > 0;
270
225
  let hasNegative = state.minValue === undefined || isNaN(state.minValue) || state.minValue < 0;
271
- let inputMode: string = 'numeric';
226
+ let inputMode: TextInputDOMProps['inputMode'] = 'numeric';
272
227
  if (isIPhone()) {
273
228
  // iPhone doesn't have a minus sign in either numeric or decimal.
274
229
  // Note this is only for iPhone, not iPad, which always has both
@@ -314,31 +269,25 @@ export function useNumberField(...args: any[]): NumberFieldAria {
314
269
  };
315
270
 
316
271
  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') {
272
+ let { keyboardProps } = useKeyboard({
273
+ isDisabled: isDisabled || isReadOnly,
274
+ shortcuts: {
275
+ Enter: () => {
326
276
  flushSync(() => {
327
277
  commit();
328
278
  });
329
279
  commitValidation();
330
- } else {
331
- e.continuePropagation();
332
- }
280
+ return { shouldPreventDefault: false };
281
+ },
333
282
  },
334
- [commit, commitValidation],
335
- subSlot(slot, 'keyDownEnter'),
336
- );
283
+ onKeyDown,
284
+ onKeyUp,
285
+ });
337
286
 
338
287
  let { isInvalid, validationErrors, validationDetails } = state.displayValidation;
339
288
  let {
340
289
  labelProps,
341
- inputProps: textFieldProps,
290
+ inputProps: textFieldPropsFromHook,
342
291
  descriptionProps,
343
292
  errorMessageProps,
344
293
  } = useFormattedTextField(
@@ -367,22 +316,20 @@ export function useNumberField(...args: any[]): NumberFieldAria {
367
316
  onBlur,
368
317
  onFocus,
369
318
  onFocusChange,
370
- onKeyDown: useMemo(
371
- () => chain(onKeyDownEnter, onKeyDown),
372
- [onKeyDownEnter, onKeyDown],
373
- subSlot(slot, 'keyDownChain'),
374
- ),
375
- onKeyUp,
376
319
  onPaste,
377
320
  description,
378
321
  errorMessage,
379
- } as any,
322
+ },
380
323
  state,
381
324
  inputRef,
382
- subSlot(slot, 'textField'),
383
325
  );
384
326
 
385
- useFormReset(inputRef, state.defaultNumberValue, state.setNumberValue, subSlot(slot, 'reset'));
327
+ // Merge outside useFormattedTextField so useKeyboard's createEventHandler is not nested inside
328
+ // useTextField/useFocusable's createEventHandler (avoids redundant stopPropagation on RS events).
329
+ // Shortcuts run first (mergeProps chains the second argument after the first).
330
+ let textFieldProps = mergeProps(keyboardProps, textFieldPropsFromHook);
331
+
332
+ useFormReset(inputRef, state.defaultNumberValue, state.setNumberValue);
386
333
  useNativeValidation(
387
334
  state,
388
335
  props.validationBehavior,
@@ -392,27 +339,31 @@ export function useNumberField(...args: any[]): NumberFieldAria {
392
339
  state.maxValue,
393
340
  props.step,
394
341
  state.numberValue,
395
- subSlot(slot, 'nativeValidation'),
396
342
  );
397
343
 
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
- });
344
+ let inputProps: InputHTMLAttributes<HTMLInputElement> = mergeProps(
345
+ spinButtonProps,
346
+ focusProps,
347
+ textFieldProps,
348
+ {
349
+ // override the spinbutton role, we can't focus a spin button with VO
350
+ role: null,
351
+ // ignore aria-roledescription on iOS so that required state will announce when it is present
352
+ 'aria-roledescription': !isIOS() ? stringFormatter.format('numberField') : null,
353
+ 'aria-valuemax': null,
354
+ 'aria-valuemin': null,
355
+ 'aria-valuenow': null,
356
+ 'aria-valuetext': null,
357
+ autoCorrect: 'off',
358
+ spellCheck: 'false',
359
+ },
360
+ );
410
361
 
411
362
  if (props.validationBehavior === 'native') {
412
363
  inputProps['aria-required'] = undefined;
413
364
  }
414
365
 
415
- let onButtonPressStart = (e: any) => {
366
+ let onButtonPressStart = (e: import('@react-types/shared').PressEvent) => {
416
367
  // If focus is already on the input, keep it there so we don't hide the
417
368
  // software keyboard when tapping the increment/decrement buttons.
418
369
  if (getActiveElement() === inputRef.current) {
@@ -425,7 +376,7 @@ export function useNumberField(...args: any[]): NumberFieldAria {
425
376
  if (e.pointerType === 'mouse') {
426
377
  inputRef.current?.focus();
427
378
  } else {
428
- e.target.focus();
379
+ (e.target as HTMLElement).focus();
429
380
  }
430
381
  };
431
382
 
@@ -445,9 +396,10 @@ export function useNumberField(...args: any[]): NumberFieldAria {
445
396
  ariaLabelledby = props.label != null ? labelProps.id : props['aria-labelledby'];
446
397
  }
447
398
 
448
- let incrementId = useId(subSlot(slot, 'incrementId'));
449
- let decrementId = useId(subSlot(slot, 'decrementId'));
399
+ let incrementId = useId();
400
+ let decrementId = useId();
450
401
 
402
+ // oxlint-disable-next-line react/react-compiler
451
403
  let incrementButtonProps: AriaButtonProps = mergeProps(incButtonProps, {
452
404
  'aria-label': incrementAriaLabel || stringFormatter.format('increase', { fieldLabel }).trim(),
453
405
  id: ariaLabelledby && !incrementAriaLabel ? incrementId : null,
@@ -461,6 +413,7 @@ export function useNumberField(...args: any[]): NumberFieldAria {
461
413
  onPressStart: onButtonPressStart,
462
414
  });
463
415
 
416
+ // oxlint-disable-next-line react/react-compiler
464
417
  let decrementButtonProps: AriaButtonProps = mergeProps(decButtonProps, {
465
418
  'aria-label': decrementAriaLabel || stringFormatter.format('decrease', { fieldLabel }).trim(),
466
419
  id: ariaLabelledby && !decrementAriaLabel ? decrementId : null,
@@ -504,69 +457,62 @@ function useNativeValidation(
504
457
  max: number | undefined,
505
458
  step: number | undefined,
506
459
  value: number | undefined,
507
- slot: symbol | undefined,
508
460
  ) {
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
- }
461
+ useLayoutEffect(() => {
462
+ let input = inputRef.current;
463
+ if (
464
+ commitBehavior !== 'validate' ||
465
+ state.realtimeValidation.isInvalid ||
466
+ !input ||
467
+ input.disabled
468
+ ) {
469
+ return;
470
+ }
522
471
 
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
- }
472
+ // Create a native number input and use it to implement validation of min/max/step.
473
+ // This lets us get the native validation message provided by the browser instead of needing our own translations.
474
+ if (!numberInput && typeof document !== 'undefined') {
475
+ numberInput = document.createElement('input');
476
+ numberInput.type = 'number';
477
+ }
529
478
 
530
- if (!numberInput) {
531
- // For TypeScript.
532
- return;
533
- }
479
+ if (!numberInput) {
480
+ // For TypeScript.
481
+ return;
482
+ }
534
483
 
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
- );
484
+ numberInput.min = min != null && !isNaN(min) ? String(min) : '';
485
+ numberInput.max = max != null && !isNaN(max) ? String(max) : '';
486
+ numberInput.step = step != null && !isNaN(step) ? String(step) : '';
487
+ numberInput.value = value != null && !isNaN(value) ? String(value) : '';
488
+
489
+ // Merge validity with the visible text input (for other validations like required).
490
+ let valid = input.validity.valid && numberInput.validity.valid;
491
+ let validationMessage = input.validationMessage || numberInput.validationMessage;
492
+ let validity = {
493
+ isInvalid: !valid,
494
+ validationErrors: validationMessage ? [validationMessage] : [],
495
+ validationDetails: {
496
+ badInput: input.validity.badInput,
497
+ customError: input.validity.customError,
498
+ patternMismatch: input.validity.patternMismatch,
499
+ rangeOverflow: numberInput.validity.rangeOverflow,
500
+ rangeUnderflow: numberInput.validity.rangeUnderflow,
501
+ stepMismatch: numberInput.validity.stepMismatch,
502
+ tooLong: input.validity.tooLong,
503
+ tooShort: input.validity.tooShort,
504
+ typeMismatch: input.validity.typeMismatch,
505
+ valueMissing: input.validity.valueMissing,
506
+ valid,
507
+ },
508
+ };
509
+
510
+ state.updateValidation(validity);
511
+
512
+ // Block form submission if validation behavior is native.
513
+ // This won't overwrite any user-defined validation message because we checked realtimeValidation above.
514
+ if (validationBehavior === 'native' && !numberInput.validity.valid) {
515
+ input.setCustomValidity(numberInput.validationMessage);
516
+ }
517
+ });
572
518
  }
@@ -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'),