@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
@@ -0,0 +1,440 @@
1
+ /** @jsxImportSource octane */
2
+ // Ported from adobe/react-spectrum@5ecb3333001313e83898cd07644227897e3bae1f (packages/react-stately/src/tokenfield/TokenFieldValue.ts).
3
+ /*
4
+ * Copyright 2026 Adobe. All rights reserved.
5
+ * This file is licensed to you under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License. You may obtain a copy
7
+ * of the License at http://www.apache.org/licenses/LICENSE-2.0
8
+ *
9
+ * Unless required by applicable law or agreed to in writing, software distributed under
10
+ * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
11
+ * OF ANY KIND, either express or implied. See the License for the specific language
12
+ * governing permissions and limitations under the License.
13
+ */
14
+
15
+ export type TokenFieldSegment<T = any> = TextSegment | TokenSegment<T>;
16
+
17
+ export interface TextSegment {
18
+ type: 'text';
19
+ text: string;
20
+ }
21
+
22
+ export interface TokenSegment<T = any> {
23
+ type: 'token';
24
+ text: string;
25
+ /** An arbitrary value associated with the token. */
26
+ value?: T;
27
+ }
28
+
29
+ export interface Position {
30
+ /** Index of the segment in the list. */
31
+ index: number;
32
+ /** Text offset within the segment in UTF-16 code units. */
33
+ offset: number;
34
+ }
35
+
36
+ enum Direction {
37
+ Forward = 1,
38
+ Backward = -1,
39
+ }
40
+
41
+ export interface TokenFieldValueOptions {
42
+ caretPosition?: Position | null;
43
+ }
44
+
45
+ /**
46
+ * A list of segments containing editable text and non-editable tokens.
47
+ */
48
+ export class TokenFieldValue<T = any> {
49
+ static readonly Direction = Direction;
50
+
51
+ /** The text and token segments in the list. */
52
+ readonly segments: readonly TokenFieldSegment<T>[];
53
+ /** The caret position. */
54
+ caretPosition: Position = { index: 0, offset: 0 };
55
+ // Linked list representing the undo/redo history.
56
+ private previous: this | null = null;
57
+ private next: this | null = null;
58
+ private isCoalescing = true;
59
+
60
+ /** Create a new list with the given segments. */
61
+ constructor(tokens: readonly TokenFieldSegment<T>[], options?: TokenFieldValueOptions) {
62
+ this.segments = tokens;
63
+ this.caretPosition = options?.caretPosition ?? { index: 0, offset: 0 };
64
+ }
65
+
66
+ protected createFieldValue(segments: readonly TokenFieldSegment<T>[]): this {
67
+ const Constructor = this.constructor as new (
68
+ segments: readonly TokenFieldSegment<T>[],
69
+ options?: TokenFieldValueOptions,
70
+ ) => this;
71
+ return new Constructor(segments);
72
+ }
73
+
74
+ /** Create a new list with the caret position set to the given position. */
75
+ withCaretPosition(caretPosition: Position): this {
76
+ if (
77
+ this.caretPosition.index === caretPosition.index &&
78
+ this.caretPosition.offset === caretPosition.offset
79
+ ) {
80
+ return this;
81
+ }
82
+
83
+ let result = this.createFieldValue(this.segments);
84
+ result.caretPosition = caretPosition;
85
+ result.previous = this.previous;
86
+ result.next = this.next;
87
+ result.isCoalescing = this.isCoalescing;
88
+ return result;
89
+ }
90
+
91
+ private splitSegment(
92
+ segment: TokenFieldSegment | undefined,
93
+ offset: number,
94
+ ): [TokenFieldSegment | null, TokenFieldSegment | null] {
95
+ if (!segment) {
96
+ let empty = this.createTextSegment('');
97
+ return [offset > 0 ? empty : null, offset > 0 ? null : empty];
98
+ }
99
+
100
+ if (segment.type === 'token') {
101
+ return [offset > 0 ? { ...segment } : null, offset > 0 ? null : { ...segment }];
102
+ }
103
+
104
+ return [
105
+ offset > 0 ? { type: 'text', text: segment.text.slice(0, offset) } : null,
106
+ offset < segment.text.length ? this.createTextSegment(segment.text.slice(offset)) : null,
107
+ ];
108
+ }
109
+
110
+ private createTextSegment(text: string): TextSegment {
111
+ return { type: 'text', text };
112
+ }
113
+
114
+ protected tokenize(text: string): TokenFieldSegment<T>[] {
115
+ return [this.createTextSegment(text)];
116
+ }
117
+
118
+ private clampPosition(position: Position): Position {
119
+ if (this.segments.length > 0 && position.index >= this.segments.length) {
120
+ return {
121
+ index: this.segments.length - 1,
122
+ offset: this.segments[this.segments.length - 1].text.length,
123
+ };
124
+ }
125
+
126
+ if (position.index < 0) {
127
+ return { index: 0, offset: 0 };
128
+ }
129
+ return position;
130
+ }
131
+
132
+ /** Replace the text between two positions with new text. */
133
+ replaceRange(start: Position, end: Position, text: string, coalesce = true): this {
134
+ return this.replaceRangeWithSegments(
135
+ start,
136
+ end,
137
+ text.length > 0 ? [this.createTextSegment(text)] : [],
138
+ coalesce,
139
+ );
140
+ }
141
+
142
+ /** Replace the text between two positions with new segments. */
143
+ replaceRangeWithSegments(
144
+ start: Position,
145
+ end: Position,
146
+ insert: TokenFieldSegment<T>[],
147
+ coalesce = true,
148
+ ): this {
149
+ start = this.clampPosition(start);
150
+ end = this.clampPosition(end);
151
+ let startSegment = this.segments[start.index];
152
+ let endSegment = this.segments[end.index];
153
+ let [startSplit] = this.splitSegment(startSegment, start.offset);
154
+ let [, endSplit] = this.splitSegment(endSegment, end.offset);
155
+
156
+ let newSegments = this.segments.slice(0, start.index);
157
+ if (startSplit) {
158
+ appendSegments(newSegments, [startSplit]);
159
+ }
160
+
161
+ if (insert.length) {
162
+ appendSegments(newSegments, insert, (text) => this.tokenize(text));
163
+ }
164
+
165
+ let lastSegment = newSegments[newSegments.length - 1];
166
+ let lastIsText = lastSegment && lastSegment.type === 'text';
167
+ let caret = {
168
+ index: lastIsText ? newSegments.length - 1 : newSegments.length,
169
+ offset: lastIsText ? lastSegment.text.length : 0,
170
+ };
171
+
172
+ if (endSplit) {
173
+ appendSegments(newSegments, [endSplit]);
174
+ }
175
+
176
+ appendSegments(newSegments, this.segments.slice(end.index + 1));
177
+
178
+ let segments = this.createFieldValue(newSegments);
179
+ segments.caretPosition = caret;
180
+ segments.isCoalescing = coalesce;
181
+ if (this.isCoalescing && coalesce && this.previous) {
182
+ segments.previous = this.previous;
183
+ segments.previous.next = segments;
184
+ } else {
185
+ segments.previous = this;
186
+ this.caretPosition = end;
187
+ this.next = segments;
188
+ }
189
+ return segments;
190
+ }
191
+
192
+ /** Find the boundary before or after a position using an Intl.Segmenter. */
193
+ findBoundaryWithSegmenter(
194
+ position: Position,
195
+ segmenter: Intl.Segmenter,
196
+ direction: Direction,
197
+ ): Position | null {
198
+ position = this.clampPosition(position);
199
+ for (let i = position.index; i >= 0 && i < this.segments.length; i += direction) {
200
+ let segment = this.segments[i];
201
+ switch (segment.type) {
202
+ case 'token':
203
+ if (
204
+ i !== position.index ||
205
+ (direction === Direction.Backward ? position.offset > 0 : position.offset === 0)
206
+ ) {
207
+ let index = i + direction;
208
+ return {
209
+ index: index >= 0 ? index : 0,
210
+ offset:
211
+ direction === Direction.Backward && index >= 0
212
+ ? this.segments[index].text.length
213
+ : 0,
214
+ };
215
+ }
216
+ continue;
217
+ case 'text': {
218
+ let offset = direction === Direction.Backward ? segment.text.length : 0;
219
+ if (i === position.index) {
220
+ offset = position.offset;
221
+ }
222
+ if (direction === Direction.Backward) {
223
+ offset--;
224
+ }
225
+ if (offset < 0 || offset >= segment.text.length) {
226
+ continue;
227
+ }
228
+
229
+ let part = segmenter.segment(segment.text).containing(offset);
230
+ while (part && part.isWordLike === false) {
231
+ offset += direction;
232
+ part = segmenter.segment(segment.text).containing(offset);
233
+ }
234
+
235
+ if (part) {
236
+ return {
237
+ index: i,
238
+ offset:
239
+ direction === Direction.Backward ? part.index : part.index + part.segment.length,
240
+ };
241
+ }
242
+ continue;
243
+ }
244
+ }
245
+ }
246
+
247
+ return null;
248
+ }
249
+
250
+ /** Find a line boundary before or after a position. */
251
+ findLineBoundary(position: Position, direction: Direction): Position | null {
252
+ let res = this.findText(position, direction, '\n');
253
+ if (res) {
254
+ return res;
255
+ }
256
+ return direction === Direction.Backward
257
+ ? { index: 0, offset: 0 }
258
+ : {
259
+ index: this.segments.length - 1,
260
+ offset: this.segments[this.segments.length - 1].text.length,
261
+ };
262
+ }
263
+
264
+ /** Find a string or regular expression match before or after a position. */
265
+ findText(position: Position, direction: Direction, search: string | RegExp): Position | null {
266
+ if (this.segments.length === 0) {
267
+ return null;
268
+ }
269
+
270
+ for (let i = position.index; i >= 0 && i < this.segments.length; i += direction) {
271
+ let segment = this.segments[i];
272
+ if (segment.type !== 'text') {
273
+ continue;
274
+ }
275
+ let offset = findInText(
276
+ segment.text,
277
+ search,
278
+ direction,
279
+ i === position.index ? position.offset : undefined,
280
+ );
281
+ if (offset >= 0) {
282
+ return {
283
+ index: i,
284
+ offset: offset,
285
+ };
286
+ }
287
+ }
288
+
289
+ return null;
290
+ }
291
+
292
+ /** Delete text at a position using a segmenter. */
293
+ delete(
294
+ position: Position,
295
+ segmenter: Intl.Segmenter,
296
+ direction: Direction,
297
+ coalesce = true,
298
+ ): this {
299
+ let boundary = this.findBoundaryWithSegmenter(position, segmenter, direction);
300
+ if (boundary) {
301
+ return this.replaceRange(
302
+ direction === Direction.Backward ? boundary : position,
303
+ direction === Direction.Backward ? position : boundary,
304
+ '',
305
+ coalesce,
306
+ );
307
+ }
308
+
309
+ this.caretPosition = position;
310
+ return this;
311
+ }
312
+
313
+ /** Delete text to the next or previous line break. */
314
+ deleteLine(position: Position, direction: Direction, coalesce = true): this {
315
+ if (this.segments.length === 0) {
316
+ return this;
317
+ }
318
+
319
+ let boundary = this.findLineBoundary(position, direction);
320
+ if (boundary) {
321
+ return this.replaceRange(
322
+ direction === Direction.Backward ? boundary : position,
323
+ direction === Direction.Backward ? position : boundary,
324
+ '',
325
+ coalesce,
326
+ );
327
+ }
328
+
329
+ return this;
330
+ }
331
+
332
+ /** Create a new list containing a subset of the segments. */
333
+ slice(start: Position, end: Position): this {
334
+ start = this.clampPosition(start);
335
+ end = this.clampPosition(end);
336
+ if (start.index === end.index && start.offset === end.offset) {
337
+ return this.createFieldValue([]);
338
+ }
339
+ if (start.index === end.index) {
340
+ let segment = this.segments[start.index];
341
+ if (segment.type === 'text') {
342
+ return this.createFieldValue([
343
+ { type: 'text', text: segment.text.slice(start.offset, end.offset) },
344
+ ]);
345
+ }
346
+ return this.createFieldValue([segment]);
347
+ }
348
+ let startSegment = this.segments[start.index];
349
+ let endSegment = this.segments[end.index];
350
+ let [, startSplit] = this.splitSegment(startSegment, start.offset);
351
+ let [endSplit] = this.splitSegment(endSegment, end.offset);
352
+
353
+ let result: TokenFieldSegment<T>[] = [];
354
+ if (startSplit) {
355
+ result.push(startSplit);
356
+ }
357
+ result.push(...this.segments.slice(start.index + 1, end.index));
358
+ if (endSplit) {
359
+ result.push(endSplit);
360
+ }
361
+ return this.createFieldValue(result);
362
+ }
363
+
364
+ /** Convert the list to a string. */
365
+ toString(): string {
366
+ return this.segments.map((seg) => seg.text).join('');
367
+ }
368
+
369
+ /** Returns the previous list in the undo history. */
370
+ undo(): this {
371
+ return this.previous ?? this;
372
+ }
373
+
374
+ /** Returns the next list in the redo history. */
375
+ redo(): this {
376
+ return this.next ?? this;
377
+ }
378
+
379
+ /** End coalescing undo/redo history. */
380
+ endCoalescing(): void {
381
+ this.isCoalescing = false;
382
+ }
383
+ }
384
+
385
+ function findInText(
386
+ text: string,
387
+ search: string | RegExp,
388
+ direction: Direction,
389
+ fromOffset?: number,
390
+ ): number {
391
+ if (typeof search === 'string') {
392
+ if (direction === Direction.Backward) {
393
+ return text.lastIndexOf(search, fromOffset !== undefined ? fromOffset - 1 : text.length - 1);
394
+ }
395
+ return text.indexOf(search, fromOffset ?? 0);
396
+ }
397
+
398
+ if (direction === Direction.Forward) {
399
+ let start = fromOffset ?? 0;
400
+ let index = text.slice(start).search(search);
401
+ return index >= 0 ? start + index : -1;
402
+ }
403
+
404
+ let limit = fromOffset !== undefined ? fromOffset : text.length;
405
+ if (limit < 0) {
406
+ return -1;
407
+ }
408
+
409
+ let re = search.flags.includes('g') ? search : new RegExp(search.source, search.flags + 'g');
410
+ let matches = Array.from(text.slice(0, limit).matchAll(re));
411
+ return matches.at(-1)?.index ?? -1;
412
+ }
413
+
414
+ function appendSegments(
415
+ segments: TokenFieldSegment[],
416
+ insert: TokenFieldSegment[],
417
+ tokenize?: (text: string) => TokenFieldSegment[],
418
+ ): TokenFieldSegment[] {
419
+ for (let segment of insert) {
420
+ if (segment.type === 'text' && segment.text.length === 0) {
421
+ continue;
422
+ }
423
+
424
+ let last = segments[segments.length - 1];
425
+ if (last && last.type === 'text' && segment.type === 'text') {
426
+ if (tokenize) {
427
+ let tokenized = tokenize(last.text + segment.text);
428
+ segments.splice(segments.length - 1, 1, ...tokenized);
429
+ } else {
430
+ segments[segments.length - 1] = { type: 'text', text: last.text + segment.text };
431
+ }
432
+ } else if (tokenize && segment.type === 'text') {
433
+ let tokenized = tokenize(segment.text);
434
+ segments.push(...tokenized);
435
+ } else {
436
+ segments.push(segment);
437
+ }
438
+ }
439
+ return segments;
440
+ }
@@ -0,0 +1,57 @@
1
+ /** @jsxImportSource octane */
2
+ // Ported from adobe/react-spectrum@5ecb3333001313e83898cd07644227897e3bae1f (packages/react-stately/src/tokenfield/useTokenFieldState.ts).
3
+ /*
4
+ * Copyright 2026 Adobe. All rights reserved.
5
+ * This file is licensed to you under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License. You may obtain a copy
7
+ * of the License at http://www.apache.org/licenses/LICENSE-2.0
8
+ *
9
+ * Unless required by applicable law or agreed to in writing, software distributed under
10
+ * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
11
+ * OF ANY KIND, either express or implied. See the License for the specific language
12
+ * governing permissions and limitations under the License.
13
+ */
14
+
15
+ import { TokenFieldValue } from './TokenFieldValue';
16
+ import { useControlledState } from '../utils/useControlledState';
17
+ import { useState } from '../../compat/react';
18
+ import { ValueBase } from '@react-types/shared';
19
+
20
+ export interface TokenFieldProps<
21
+ T extends TokenFieldValue = TokenFieldValue,
22
+ > extends ValueBase<T> {}
23
+
24
+ export interface TokenFieldState<T extends TokenFieldValue = TokenFieldValue> {
25
+ /** The current value of the token field. */
26
+ value: T;
27
+ /** Sets the value of the token field. */
28
+ setValue: (fn: T | ((value: T) => T)) => void;
29
+ /** Whether the token field is composing. */
30
+ isComposing: boolean;
31
+ /** Sets the composing state of the token field. */
32
+ setComposing: (isComposing: boolean) => void;
33
+ }
34
+
35
+ /**
36
+ * Provides state management for a token field. Tracks the field value and the
37
+ * composition state.
38
+ */
39
+ export function useTokenFieldState<T extends TokenFieldValue = TokenFieldValue>(
40
+ props: TokenFieldProps<T>,
41
+ ): TokenFieldState<T> {
42
+ let {
43
+ value: valueProp,
44
+ defaultValue: defaultValueProp = new TokenFieldValue([]),
45
+ onChange,
46
+ } = props;
47
+
48
+ let [value, setValue] = useControlledState(valueProp as any, defaultValueProp, onChange);
49
+ let [isComposing, setComposing] = useState(false);
50
+
51
+ return {
52
+ value,
53
+ setValue,
54
+ isComposing,
55
+ setComposing,
56
+ };
57
+ }
@@ -3,7 +3,7 @@
3
3
  // convention; the module-level warmup/cooldown timers and global tooltip registry are
4
4
  // ported verbatim; upstream's explicit useEffect dep arrays are kept verbatim with a
5
5
  // trailing subSlot.
6
- import { useEffect, useMemo, useRef } from 'octane';
6
+ import { useEffect, useMemo, useRef, useState } from 'octane';
7
7
 
8
8
  import { S, splitSlot, subSlot } from '../../internal';
9
9
  import {
@@ -54,6 +54,11 @@ const TOOLTIP_COOLDOWN = 500;
54
54
  export interface TooltipTriggerState {
55
55
  /** Whether the tooltip is currently showing. */
56
56
  isOpen: boolean;
57
+ /**
58
+ * Whether the tooltip is being shown or hidden without an animation. This is true while the
59
+ * global warmup timer is active, i.e. when quickly moving between tooltips.
60
+ */
61
+ shouldSkipAnimation: boolean;
57
62
  /**
58
63
  * Shows the tooltip. By default, the tooltip becomes visible after a delay
59
64
  * depending on a global warmup timer. The `immediate` option shows the
@@ -90,6 +95,7 @@ export function useTooltipTriggerState(...args: any[]): TooltipTriggerState {
90
95
 
91
96
  let { delay = TOOLTIP_DELAY, closeDelay = TOOLTIP_COOLDOWN } = props;
92
97
  let { isOpen, open, close } = useOverlayTriggerState(props, subSlot(slot, 'overlay'));
98
+ let [shouldSkipAnimation, setIsInstant] = useState(false, subSlot(slot, 'skipAnimation'));
93
99
  let id = useMemo(() => `${++tooltipId}`, [], subSlot(slot, 'id'));
94
100
  let closeTimeout = useRef<ReturnType<typeof setTimeout> | null>(
95
101
  null,
@@ -104,19 +110,20 @@ export function useTooltipTriggerState(...args: any[]): TooltipTriggerState {
104
110
  let closeOpenTooltips = () => {
105
111
  for (let hideTooltipId in tooltips) {
106
112
  if (hideTooltipId !== id) {
107
- tooltips[hideTooltipId](true);
113
+ tooltips[hideTooltipId](true, true);
108
114
  delete tooltips[hideTooltipId];
109
115
  }
110
116
  }
111
117
  };
112
118
 
113
- let showTooltip = () => {
119
+ let showTooltip = (instant?: boolean) => {
114
120
  if (closeTimeout.current) {
115
121
  clearTimeout(closeTimeout.current);
116
122
  }
117
123
  closeTimeout.current = null;
118
124
  closeOpenTooltips();
119
125
  ensureTooltipEntry();
126
+ setIsInstant(!!instant);
120
127
  globalWarmedUp = true;
121
128
  open();
122
129
  if (globalWarmUpTimeout) {
@@ -129,7 +136,8 @@ export function useTooltipTriggerState(...args: any[]): TooltipTriggerState {
129
136
  }
130
137
  };
131
138
 
132
- let hideTooltip = (immediate?: boolean) => {
139
+ let hideTooltip = (immediate?: boolean, instant?: boolean) => {
140
+ setIsInstant(!!instant);
133
141
  if (immediate || closeDelay <= 0) {
134
142
  if (closeTimeout.current) {
135
143
  clearTimeout(closeTimeout.current);
@@ -173,10 +181,10 @@ export function useTooltipTriggerState(...args: any[]): TooltipTriggerState {
173
181
  globalWarmUpTimeout = setTimeout(() => {
174
182
  globalWarmUpTimeout = null;
175
183
  globalWarmedUp = true;
176
- showTooltip();
184
+ showTooltip(false);
177
185
  }, delay);
178
186
  } else if (!isOpen) {
179
- showTooltip();
187
+ showTooltip(true);
180
188
  }
181
189
  };
182
190
 
@@ -206,11 +214,12 @@ export function useTooltipTriggerState(...args: any[]): TooltipTriggerState {
206
214
 
207
215
  return {
208
216
  isOpen,
217
+ shouldSkipAnimation,
209
218
  open: (immediate) => {
210
219
  if (!immediate && delay > 0 && !closeTimeout.current) {
211
220
  warmupTooltip();
212
221
  } else {
213
- showTooltip();
222
+ showTooltip(globalWarmedUp);
214
223
  }
215
224
  },
216
225
  close: hideTooltip,
@@ -1,4 +1,5 @@
1
1
  /** @jsxImportSource octane */
2
+ import type { ScrollAnchorInfo } from './ScrollAnchor';
2
3
  // Ported from adobe/react-spectrum@1c84a49a1faf50b571c84e00bcf9c60b22ddd03e (packages/react-stately/src/virtualizer/Layout.ts).
3
4
  /*
4
5
  * Copyright 2020 Adobe. All rights reserved.
@@ -33,6 +34,7 @@ import { Virtualizer } from './Virtualizer';
33
34
  export abstract class Layout<T extends object = Node<any>, O = any> implements LayoutDelegate {
34
35
  /** The Virtualizer the layout is currently attached to. */
35
36
  virtualizer: Virtualizer<T, any> | null = null;
37
+ UNSTABLE_getScrollAnchorInfo?(options?: O): ScrollAnchorInfo | null;
36
38
 
37
39
  /**
38
40
  * Returns an array of `LayoutInfo` objects which are inside the given rectangle.