@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
@@ -0,0 +1,330 @@
1
+ /** @jsxImportSource octane */
2
+ type DOMAttributes = Record<string, any>;
3
+ // Ported from adobe/react-spectrum@5ecb3333001313e83898cd07644227897e3bae1f (packages/react-aria/src/spinbutton/useSpinButton.ts).
4
+ /*
5
+ * Copyright 2020 Adobe. All rights reserved.
6
+ * This file is licensed to you under the Apache License, Version 2.0 (the "License");
7
+ * you may not use this file except in compliance with the License. You may obtain a copy
8
+ * of the License at http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software distributed under
11
+ * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
12
+ * OF ANY KIND, either express or implied. See the License for the specific language
13
+ * governing permissions and limitations under the License.
14
+ */
15
+
16
+ import { announce, clearAnnouncer } from '../live-announcer/LiveAnnouncer';
17
+
18
+ import { AriaButtonProps } from '../button/useButton';
19
+ import { InputBase, RangeInputBase, Validation, ValueBase } from '@react-types/shared';
20
+ import intlMessages from '../intl/spinbutton/index';
21
+ import { useCallback, useEffect, useRef, useState } from '../compat/react';
22
+ import { useEffectEvent } from '../utils/useEffectEvent';
23
+ import { useGlobalListeners } from '../utils/useGlobalListeners';
24
+ import { useKeyboard } from '../interactions/useKeyboard';
25
+ import { useLocalizedStringFormatter } from '../i18n/useLocalizedStringFormatter';
26
+
27
+ const noop = () => {};
28
+
29
+ export interface SpinButtonProps
30
+ extends InputBase, Validation<number>, ValueBase<number>, RangeInputBase<number> {
31
+ textValue?: string;
32
+ onIncrement?: () => void;
33
+ onIncrementPage?: () => void;
34
+ onDecrement?: () => void;
35
+ onDecrementPage?: () => void;
36
+ onDecrementToMin?: () => void;
37
+ onIncrementToMax?: () => void;
38
+ }
39
+
40
+ export interface SpinbuttonAria {
41
+ spinButtonProps: DOMAttributes;
42
+ incrementButtonProps: AriaButtonProps;
43
+ decrementButtonProps: AriaButtonProps;
44
+ }
45
+
46
+ export function useSpinButton(props: SpinButtonProps, _slot?: symbol): SpinbuttonAria {
47
+ const _async = useRef<number | undefined>(undefined);
48
+ let {
49
+ value,
50
+ textValue,
51
+ minValue,
52
+ maxValue,
53
+ isDisabled,
54
+ isReadOnly,
55
+ isRequired,
56
+ onIncrement,
57
+ onIncrementPage,
58
+ onDecrement,
59
+ onDecrementPage,
60
+ onDecrementToMin,
61
+ onIncrementToMax,
62
+ } = props;
63
+ const stringFormatter = useLocalizedStringFormatter(intlMessages, '@react-aria/spinbutton');
64
+
65
+ let isSpinning = useRef(false);
66
+ const clearAsync = useCallback(() => {
67
+ clearTimeout(_async.current);
68
+ isSpinning.current = false;
69
+ }, []);
70
+ const clearAsyncEvent = useEffectEvent(() => {
71
+ clearAsync();
72
+ });
73
+
74
+ useEffect(() => {
75
+ return () => clearAsyncEvent();
76
+ }, []);
77
+
78
+ let { keyboardProps } = useKeyboard({
79
+ isDisabled: isDisabled || isReadOnly,
80
+ shortcuts: {
81
+ PageUp: () => {
82
+ if (onIncrementPage) {
83
+ onIncrementPage();
84
+ return;
85
+ }
86
+ if (onIncrement) {
87
+ onIncrement();
88
+ return;
89
+ }
90
+ return false;
91
+ },
92
+ ArrowUp: () => {
93
+ if (onIncrement) {
94
+ onIncrement();
95
+ return;
96
+ }
97
+ return false;
98
+ },
99
+ PageDown: () => {
100
+ if (onDecrementPage) {
101
+ onDecrementPage();
102
+ return;
103
+ }
104
+ if (onDecrement) {
105
+ onDecrement();
106
+ return;
107
+ }
108
+ return false;
109
+ },
110
+ ArrowDown: () => {
111
+ if (onDecrement) {
112
+ onDecrement();
113
+ return;
114
+ }
115
+ return false;
116
+ },
117
+ Home: () => {
118
+ if (onDecrementToMin) {
119
+ onDecrementToMin();
120
+ return;
121
+ }
122
+ return false;
123
+ },
124
+ End: () => {
125
+ if (onIncrementToMax) {
126
+ onIncrementToMax();
127
+ return;
128
+ }
129
+ return false;
130
+ },
131
+ },
132
+ allowRepeats: true,
133
+ });
134
+
135
+ let isFocused = useRef(false);
136
+ let onFocus = () => {
137
+ isFocused.current = true;
138
+ };
139
+
140
+ let onBlur = () => {
141
+ isFocused.current = false;
142
+ };
143
+
144
+ // Replace Unicode hyphen-minus (U+002D) with minus sign (U+2212).
145
+ // This ensures that macOS VoiceOver announces it as "minus" even with other characters between the minus sign
146
+ // and the number (e.g. currency symbol). Otherwise it announces nothing because it assumes the character is a hyphen.
147
+ // In addition, replace the empty string with the word "Empty" so that iOS VoiceOver does not read "50%" for an empty field.
148
+ let ariaTextValue =
149
+ textValue === ''
150
+ ? stringFormatter.format('Empty')
151
+ : (textValue || `${value}`).replace('-', '\u2212');
152
+
153
+ useEffect(() => {
154
+ if (isFocused.current) {
155
+ clearAnnouncer('assertive');
156
+ announce(ariaTextValue, 'assertive');
157
+ }
158
+ }, [ariaTextValue]);
159
+
160
+ // For touch users, if they move their finger like they're scrolling, we don't want to trigger a spin.
161
+ let onPointerCancel = useCallback(() => {
162
+ clearAsync();
163
+ }, [clearAsync]);
164
+
165
+ const onIncrementEvent = useEffectEvent(onIncrement ?? noop);
166
+ const onDecrementEvent = useEffectEvent(onDecrement ?? noop);
167
+
168
+ const stepUpEvent = useEffectEvent(() => {
169
+ if (
170
+ maxValue === undefined ||
171
+ isNaN(maxValue) ||
172
+ value === undefined ||
173
+ isNaN(value) ||
174
+ value < maxValue
175
+ ) {
176
+ onIncrementEvent();
177
+ // oxlint-disable-next-line react/react-compiler
178
+ onIncrementPressStartEvent(60);
179
+ }
180
+ });
181
+
182
+ const onIncrementPressStartEvent = useEffectEvent((initialStepDelay: number) => {
183
+ clearAsyncEvent();
184
+ isSpinning.current = true;
185
+ // Start spinning after initial delay
186
+ _async.current = window.setTimeout(stepUpEvent, initialStepDelay);
187
+ });
188
+
189
+ const stepDownEvent = useEffectEvent(() => {
190
+ if (
191
+ minValue === undefined ||
192
+ isNaN(minValue) ||
193
+ value === undefined ||
194
+ isNaN(value) ||
195
+ value > minValue
196
+ ) {
197
+ onDecrementEvent();
198
+ // oxlint-disable-next-line react/react-compiler
199
+ onDecrementPressStartEvent(60);
200
+ }
201
+ });
202
+
203
+ const onDecrementPressStartEvent = useEffectEvent((initialStepDelay: number) => {
204
+ clearAsyncEvent();
205
+ isSpinning.current = true;
206
+ // Start spinning after initial delay
207
+ _async.current = window.setTimeout(stepDownEvent, initialStepDelay);
208
+ });
209
+
210
+ let cancelContextMenu = (e: MouseEvent) => {
211
+ e.preventDefault();
212
+ };
213
+
214
+ let { addGlobalListener, removeAllGlobalListeners } = useGlobalListeners();
215
+
216
+ // Tracks in touch if the press end event was preceded by a press up.
217
+ // If it wasn't, then we know the finger left the button while still in contact with the screen.
218
+ // This means that the user is trying to scroll or interact in some way that shouldn't trigger
219
+ // an increment or decrement.
220
+ let isUp = useRef(false);
221
+
222
+ let [isIncrementPressed, setIsIncrementPressed] = useState<'touch' | 'mouse' | null>(null);
223
+ useEffect(() => {
224
+ if (isIncrementPressed === 'touch') {
225
+ onIncrementPressStartEvent(600);
226
+ } else if (isIncrementPressed) {
227
+ onIncrementPressStartEvent(400);
228
+ } else if (!isIncrementPressed) {
229
+ clearAsyncEvent();
230
+ }
231
+ }, [isIncrementPressed]);
232
+
233
+ let [isDecrementPressed, setIsDecrementPressed] = useState<'touch' | 'mouse' | null>(null);
234
+ useEffect(() => {
235
+ if (isDecrementPressed === 'touch') {
236
+ onDecrementPressStartEvent(600);
237
+ } else if (isDecrementPressed) {
238
+ onDecrementPressStartEvent(400);
239
+ } else if (!isDecrementPressed) {
240
+ clearAsyncEvent();
241
+ }
242
+ }, [isDecrementPressed]);
243
+
244
+ return {
245
+ spinButtonProps: {
246
+ role: 'spinbutton',
247
+ 'aria-valuenow': value !== undefined && !isNaN(value) ? value : undefined,
248
+ 'aria-valuetext': ariaTextValue,
249
+ 'aria-valuemin': minValue,
250
+ 'aria-valuemax': maxValue,
251
+ 'aria-disabled': isDisabled || undefined,
252
+ 'aria-readonly': isReadOnly || undefined,
253
+ 'aria-required': isRequired || undefined,
254
+ ...keyboardProps,
255
+ onFocus,
256
+ onBlur,
257
+ },
258
+ incrementButtonProps: {
259
+ onPressStart: (e) => {
260
+ clearAsync();
261
+ if (e.pointerType !== 'touch') {
262
+ onIncrement?.();
263
+ setIsIncrementPressed('mouse');
264
+ } else {
265
+ addGlobalListener(window, 'pointercancel', onPointerCancel, { capture: true });
266
+ isUp.current = false;
267
+ // For touch users, don't trigger a decrement on press start, we'll wait for the press end to trigger it if
268
+ // the control isn't spinning.
269
+ setIsIncrementPressed('touch');
270
+ }
271
+ addGlobalListener(window, 'contextmenu', cancelContextMenu);
272
+ },
273
+ onPressUp: (e) => {
274
+ clearAsync();
275
+ if (e.pointerType === 'touch') {
276
+ isUp.current = true;
277
+ }
278
+ removeAllGlobalListeners();
279
+ setIsIncrementPressed(null);
280
+ },
281
+ onPressEnd: (e) => {
282
+ clearAsync();
283
+ if (e.pointerType === 'touch') {
284
+ if (!isSpinning.current && isUp.current) {
285
+ onIncrement?.();
286
+ }
287
+ }
288
+ isUp.current = false;
289
+ setIsIncrementPressed(null);
290
+ },
291
+ onFocus,
292
+ onBlur,
293
+ },
294
+ decrementButtonProps: {
295
+ onPressStart: (e) => {
296
+ clearAsync();
297
+ if (e.pointerType !== 'touch') {
298
+ onDecrement?.();
299
+ setIsDecrementPressed('mouse');
300
+ } else {
301
+ addGlobalListener(window, 'pointercancel', onPointerCancel, { capture: true });
302
+ isUp.current = false;
303
+ // For touch users, don't trigger a decrement on press start, we'll wait for the press end to trigger it if
304
+ // the control isn't spinning.
305
+ setIsDecrementPressed('touch');
306
+ }
307
+ },
308
+ onPressUp: (e) => {
309
+ clearAsync();
310
+ if (e.pointerType === 'touch') {
311
+ isUp.current = true;
312
+ }
313
+ removeAllGlobalListeners();
314
+ setIsDecrementPressed(null);
315
+ },
316
+ onPressEnd: (e) => {
317
+ clearAsync();
318
+ if (e.pointerType === 'touch') {
319
+ if (!isSpinning.current && isUp.current) {
320
+ onDecrement?.();
321
+ }
322
+ }
323
+ isUp.current = false;
324
+ setIsDecrementPressed(null);
325
+ },
326
+ onFocus,
327
+ onBlur,
328
+ },
329
+ };
330
+ }
@@ -38,7 +38,7 @@ import { useControlledState } from '../utils/useControlledState';
38
38
 
39
39
  export type MenuTriggerAction = 'focus' | 'input' | 'manual';
40
40
  export type SelectionMode = 'single' | 'multiple';
41
- export type ValueType<M extends SelectionMode> = M extends 'single' ? Key | null : Key[];
41
+ export type ValueType<M extends SelectionMode> = M extends 'single' ? Key | null : readonly Key[];
42
42
  export type ChangeValueType<M extends SelectionMode> = M extends 'single' ? Key | null : Key[];
43
43
  type ValidationType<M extends SelectionMode> = M extends 'single' ? Key | null : Key[];
44
44
 
@@ -118,20 +118,19 @@ export function useGridState(...args: any[]): GridState<any, IGridCollection<any
118
118
  rows.length - 1,
119
119
  );
120
120
  let newRow: GridNode<any> | null = null;
121
- while (index >= 0) {
122
- if (!selectionManager.isDisabled(rows[index].key) && rows[index].type !== 'headerrow') {
123
- newRow = rows[index];
121
+ for (let i = Math.max(0, index); i < rows.length; i++) {
122
+ if (!selectionManager.isDisabled(rows[i].key) && rows[i].type !== 'headerrow') {
123
+ newRow = rows[i];
124
124
  break;
125
125
  }
126
126
  // Find next, not disabled row.
127
- if (index < rows.length - 1) {
128
- index++;
129
- // Otherwise, find previous, not disabled row.
130
- } else {
131
- if (index > parentNode.index) {
132
- index = parentNode.index;
127
+ }
128
+ if (newRow === null) {
129
+ for (let i = index - 1; i >= 0; i--) {
130
+ if (!selectionManager.isDisabled(rows[i].key) && rows[i].type !== 'headerrow') {
131
+ newRow = rows[i];
132
+ break;
133
133
  }
134
- index--;
135
134
  }
136
135
  }
137
136
  if (newRow) {
@@ -159,3 +159,14 @@ export type { TableBodyProps } from './table/TableBody';
159
159
  export type { RowProps, RowElement } from './table/Row';
160
160
  export type { CellProps, CellElement, CellRenderer } from './table/Cell';
161
161
  export type { ITableCollection } from './table/TableCollection';
162
+
163
+ export { useTokenFieldState } from './tokenfield/useTokenFieldState';
164
+ export type { TokenFieldProps, TokenFieldState } from './tokenfield/useTokenFieldState';
165
+ export { TokenFieldValue } from './tokenfield/TokenFieldValue';
166
+ export type {
167
+ TokenFieldSegment,
168
+ TextSegment,
169
+ TokenSegment,
170
+ Position,
171
+ TokenFieldValueOptions,
172
+ } from './tokenfield/TokenFieldValue';
@@ -11,7 +11,7 @@ import {
11
11
  useOverlayTriggerState,
12
12
  } from '../overlays/useOverlayTriggerState';
13
13
 
14
- export type MenuTriggerType = 'press' | 'longPress';
14
+ export type MenuTriggerType = 'press' | 'longPress' | 'contextMenu';
15
15
 
16
16
  export interface MenuTriggerProps extends OverlayTriggerProps {
17
17
  /**
@@ -109,6 +109,8 @@ export function useSubmenuTriggerState(...args: any[]): SubmenuTriggerState {
109
109
  // https://github.com/adobe/react-spectrum/pull/4976#discussion_r1336472863
110
110
  setOpen: () => {},
111
111
  toggle,
112
+ point: null,
113
+ setPoint: () => {},
112
114
  }),
113
115
  [isOpen, open, close, closeAll, focusStrategy, toggle, submenuLevel],
114
116
  subSlot(slot, 'state'),
@@ -2,7 +2,7 @@
2
2
  // octane adaptations: public-hook slot threading (splitSlot/subSlot) per the binding
3
3
  // convention; the public value-level `onOpenChange` callback is unchanged (the onInput
4
4
  // rule applies only to DOM wiring); explicit dependency arrays are kept verbatim.
5
- import { useCallback } from 'octane';
5
+ import { useCallback, useState } from 'octane';
6
6
 
7
7
  import { S, splitSlot, subSlot } from '../../internal';
8
8
  import { useControlledState } from '../utils/useControlledState';
@@ -16,6 +16,11 @@ export interface OverlayTriggerProps {
16
16
  onOpenChange?: (isOpen: boolean) => void;
17
17
  }
18
18
 
19
+ interface Point {
20
+ x: number;
21
+ y: number;
22
+ }
23
+
19
24
  export interface OverlayTriggerState {
20
25
  /** Whether the overlay is currently open. */
21
26
  readonly isOpen: boolean;
@@ -27,6 +32,9 @@ export interface OverlayTriggerState {
27
32
  close(): void;
28
33
  /** Toggles the overlay's visibility. */
29
34
  toggle(): void;
35
+ readonly point: Point | null;
36
+ /** Sets the cursor position relative to the window viewport. */
37
+ setPoint(point: Point): void;
30
38
  }
31
39
 
32
40
  /**
@@ -51,6 +59,7 @@ export function useOverlayTriggerState(...args: any[]): OverlayTriggerState {
51
59
  subSlot(slot, 'open'),
52
60
  );
53
61
 
62
+ let [point, setPoint] = useState<Point | null>(null, subSlot(slot, 'point'));
54
63
  const open = useCallback(
55
64
  () => {
56
65
  setOpen(true);
@@ -81,5 +90,7 @@ export function useOverlayTriggerState(...args: any[]): OverlayTriggerState {
81
90
  open,
82
91
  close,
83
92
  toggle,
93
+ point,
94
+ setPoint,
84
95
  };
85
96
  }
@@ -1,9 +1,13 @@
1
+ interface FocusEvents {
2
+ onFocus?: (event: FocusEvent) => void;
3
+ onBlur?: (event: FocusEvent) => void;
4
+ onFocusChange?: (isFocused: boolean) => void;
5
+ }
1
6
  // Ported from react-stately (source: .react-spectrum/packages/react-stately/src/radio/useRadioGroupState.ts).
2
7
  // octane adaptations: public-hook slot threading (splitSlot/subSlot) per the binding
3
8
  // convention; the module-level instance/name counters port verbatim. The public
4
9
  // value-level `onChange` callback is unchanged (the onInput rule applies only to DOM wiring).
5
10
  import type {
6
- FocusEvents,
7
11
  HelpTextProps,
8
12
  InputBase,
9
13
  InputDOMProps,
@@ -104,6 +104,10 @@ export function calculateColumnSizes(
104
104
  getDefaultWidth?: (index: number) => ColumnSize | null | undefined,
105
105
  getDefaultMinWidth?: (index: number) => ColumnSize | null | undefined,
106
106
  ): number[] {
107
+ let originalWidth = availableWidth;
108
+ let flooredWidth = Math.floor(availableWidth);
109
+ let hasFractionalWidth = availableWidth - flooredWidth > 0;
110
+ availableWidth = flooredWidth;
107
111
  let hasNonFrozenItems = false;
108
112
  let flexItems: FlexItem[] = columns.map((column, index) => {
109
113
  let width: ColumnSize = (
@@ -245,7 +249,17 @@ export function calculateColumnSizes(
245
249
  });
246
250
  }
247
251
 
248
- return cascadeRounding(flexItems);
252
+ let columnSizes = cascadeRounding(flexItems);
253
+
254
+ // Give the leftover sub-pixel width to the last column so the columns sum
255
+ // exactly to the (possibly fractional) available width.
256
+ if (hasFractionalWidth && columnSizes.length > 0) {
257
+ let tableFractionalWidth = originalWidth.toString().split('.')[1];
258
+ let columnWidth = columnSizes[columnSizes.length - 1].toString();
259
+ columnSizes[columnSizes.length - 1] = Number(columnWidth + '.' + tableFractionalWidth);
260
+ }
261
+
262
+ return columnSizes;
249
263
  }
250
264
 
251
265
  function cascadeRounding(flexItems: FlexItem[]): number[] {
@@ -42,6 +42,13 @@ export interface TableProps<T> extends MultipleSelection, Sortable, Expandable {
42
42
  shouldSelectOnPressUp?: boolean;
43
43
  /** The id of the column that displays hierarchical data. */
44
44
  treeColumn?: Key;
45
+ /**
46
+ * Whether keyboard navigation to focusable elements within the cells is
47
+ * via the left/right arrow keys or the tab key.
48
+ *
49
+ * @default 'arrow'
50
+ */
51
+ keyboardNavigationBehavior?: 'arrow' | 'tab';
45
52
  }
46
53
 
47
54
  export interface TableState<T> extends GridState<T, ITableCollection<T>> {