@proyecto-viviana/solidaria 0.2.4 → 0.2.8

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 (219) hide show
  1. package/LICENSE +21 -0
  2. package/dist/actiongroup/createActionGroup.d.ts +29 -0
  3. package/dist/actiongroup/createActionGroup.d.ts.map +1 -0
  4. package/dist/actiongroup/index.d.ts +2 -0
  5. package/dist/actiongroup/index.d.ts.map +1 -0
  6. package/dist/autocomplete/createAutocomplete.d.ts +6 -2
  7. package/dist/autocomplete/createAutocomplete.d.ts.map +1 -1
  8. package/dist/breadcrumbs/createBreadcrumbs.d.ts +2 -0
  9. package/dist/breadcrumbs/createBreadcrumbs.d.ts.map +1 -1
  10. package/dist/button/createToggleButtonGroup.d.ts +32 -0
  11. package/dist/button/createToggleButtonGroup.d.ts.map +1 -0
  12. package/dist/button/index.d.ts +2 -0
  13. package/dist/button/index.d.ts.map +1 -1
  14. package/dist/calendar/createCalendarCell.d.ts +2 -0
  15. package/dist/calendar/createCalendarCell.d.ts.map +1 -1
  16. package/dist/calendar/createCalendarGrid.d.ts.map +1 -1
  17. package/dist/calendar/createRangeCalendarCell.d.ts +3 -1
  18. package/dist/calendar/createRangeCalendarCell.d.ts.map +1 -1
  19. package/dist/checkbox/createCheckboxGroup.d.ts +5 -1
  20. package/dist/checkbox/createCheckboxGroup.d.ts.map +1 -1
  21. package/dist/collections/index.d.ts +56 -0
  22. package/dist/collections/index.d.ts.map +1 -0
  23. package/dist/color/createColorArea.d.ts.map +1 -1
  24. package/dist/color/createColorSlider.d.ts.map +1 -1
  25. package/dist/color/createColorWheel.d.ts.map +1 -1
  26. package/dist/combobox/createComboBox.d.ts +6 -0
  27. package/dist/combobox/createComboBox.d.ts.map +1 -1
  28. package/dist/datepicker/createDatePicker.d.ts +6 -0
  29. package/dist/datepicker/createDatePicker.d.ts.map +1 -1
  30. package/dist/datepicker/createDateRangePicker.d.ts +40 -0
  31. package/dist/datepicker/createDateRangePicker.d.ts.map +1 -0
  32. package/dist/datepicker/createDateSegment.d.ts +1 -1
  33. package/dist/datepicker/createDateSegment.d.ts.map +1 -1
  34. package/dist/datepicker/createTimeSegment.d.ts +29 -0
  35. package/dist/datepicker/createTimeSegment.d.ts.map +1 -0
  36. package/dist/datepicker/index.d.ts +2 -0
  37. package/dist/datepicker/index.d.ts.map +1 -1
  38. package/dist/disclosure/createDisclosureGroup.d.ts +2 -1
  39. package/dist/disclosure/createDisclosureGroup.d.ts.map +1 -1
  40. package/dist/dnd/createDrag.d.ts.map +1 -1
  41. package/dist/dnd/createDraggableCollection.d.ts +4 -0
  42. package/dist/dnd/createDraggableCollection.d.ts.map +1 -1
  43. package/dist/dnd/createDraggableItem.d.ts.map +1 -1
  44. package/dist/dnd/createDrop.d.ts.map +1 -1
  45. package/dist/dnd/createDroppableCollection.d.ts +32 -1
  46. package/dist/dnd/createDroppableCollection.d.ts.map +1 -1
  47. package/dist/dnd/createDroppableItem.d.ts.map +1 -1
  48. package/dist/dnd/index.d.ts +1 -1
  49. package/dist/dnd/index.d.ts.map +1 -1
  50. package/dist/grid/createGrid.d.ts.map +1 -1
  51. package/dist/gridlist/createGridList.d.ts.map +1 -1
  52. package/dist/index.d.ts +6 -4
  53. package/dist/index.d.ts.map +1 -1
  54. package/dist/index.js +4659 -3452
  55. package/dist/index.js.map +1 -7
  56. package/dist/index.ssr.js +4659 -3452
  57. package/dist/index.ssr.js.map +1 -7
  58. package/dist/interactions/createFocus.d.ts.map +1 -1
  59. package/dist/interactions/createFocusWithin.d.ts.map +1 -1
  60. package/dist/link/createLink.d.ts +10 -0
  61. package/dist/link/createLink.d.ts.map +1 -1
  62. package/dist/listbox/createListBox.d.ts +1 -0
  63. package/dist/listbox/createListBox.d.ts.map +1 -1
  64. package/dist/listbox/createOption.d.ts.map +1 -1
  65. package/dist/menu/createMenu.d.ts +1 -0
  66. package/dist/menu/createMenu.d.ts.map +1 -1
  67. package/dist/meter/createMeter.d.ts.map +1 -1
  68. package/dist/numberfield/createNumberField.d.ts +18 -0
  69. package/dist/numberfield/createNumberField.d.ts.map +1 -1
  70. package/dist/overlays/createModal.d.ts +16 -0
  71. package/dist/overlays/createModal.d.ts.map +1 -1
  72. package/dist/overlays/createOverlay.d.ts.map +1 -1
  73. package/dist/overlays/index.d.ts +1 -1
  74. package/dist/overlays/index.d.ts.map +1 -1
  75. package/dist/popover/createOverlayPosition.d.ts.map +1 -1
  76. package/dist/popover/createPopover.d.ts.map +1 -1
  77. package/dist/progress/createProgressBar.d.ts.map +1 -1
  78. package/dist/radio/createRadioGroup.d.ts +2 -2
  79. package/dist/radio/createRadioGroup.d.ts.map +1 -1
  80. package/dist/searchfield/createSearchField.d.ts.map +1 -1
  81. package/dist/select/createHiddenSelect.d.ts.map +1 -1
  82. package/dist/select/createSelect.d.ts.map +1 -1
  83. package/dist/slider/createSlider.d.ts.map +1 -1
  84. package/dist/table/createTable.d.ts.map +1 -1
  85. package/dist/tabs/createTabs.d.ts +1 -1
  86. package/dist/tabs/createTabs.d.ts.map +1 -1
  87. package/dist/tag/createTag.d.ts.map +1 -1
  88. package/dist/tag/createTagGroup.d.ts.map +1 -1
  89. package/dist/toast/createToast.d.ts +4 -0
  90. package/dist/toast/createToast.d.ts.map +1 -1
  91. package/dist/toast/createToastRegion.d.ts.map +1 -1
  92. package/dist/toolbar/createToolbar.d.ts.map +1 -1
  93. package/dist/tooltip/createTooltipTrigger.d.ts.map +1 -1
  94. package/dist/tree/createTree.d.ts.map +1 -1
  95. package/dist/tree/createTreeItem.d.ts.map +1 -1
  96. package/dist/tree/types.d.ts +4 -0
  97. package/dist/tree/types.d.ts.map +1 -1
  98. package/dist/utils/env.d.ts +1 -1
  99. package/dist/utils/env.d.ts.map +1 -1
  100. package/dist/utils/platform.d.ts.map +1 -1
  101. package/dist/visually-hidden/createVisuallyHidden.d.ts.map +1 -1
  102. package/package.json +8 -6
  103. package/src/actiongroup/createActionGroup.ts +324 -0
  104. package/src/actiongroup/index.ts +8 -0
  105. package/src/autocomplete/createAutocomplete.ts +32 -9
  106. package/src/breadcrumbs/createBreadcrumbs.ts +10 -15
  107. package/src/button/createButton.ts +1 -1
  108. package/src/button/createToggleButtonGroup.ts +128 -0
  109. package/src/button/index.ts +9 -0
  110. package/src/calendar/createCalendarCell.ts +6 -4
  111. package/src/calendar/createCalendarGrid.ts +27 -18
  112. package/src/calendar/createRangeCalendarCell.ts +26 -9
  113. package/src/checkbox/createCheckboxGroup.ts +21 -4
  114. package/src/collections/index.ts +242 -0
  115. package/src/color/createColorArea.ts +380 -314
  116. package/src/color/createColorField.ts +137 -137
  117. package/src/color/createColorSlider.ts +286 -197
  118. package/src/color/createColorSwatch.ts +40 -40
  119. package/src/color/createColorWheel.ts +218 -208
  120. package/src/color/index.ts +24 -24
  121. package/src/color/types.ts +116 -116
  122. package/src/combobox/createComboBox.ts +670 -647
  123. package/src/combobox/index.ts +6 -6
  124. package/src/datepicker/createDatePicker.ts +54 -16
  125. package/src/datepicker/createDateRangePicker.ts +246 -0
  126. package/src/datepicker/createDateSegment.ts +185 -31
  127. package/src/datepicker/createTimeSegment.ts +370 -0
  128. package/src/datepicker/index.ts +14 -0
  129. package/src/dialog/createDialog.ts +120 -120
  130. package/src/dialog/index.ts +2 -2
  131. package/src/dialog/types.ts +19 -19
  132. package/src/disclosure/createDisclosureGroup.ts +5 -2
  133. package/src/dnd/createDrag.ts +224 -209
  134. package/src/dnd/createDraggableCollection.ts +96 -63
  135. package/src/dnd/createDraggableItem.ts +259 -243
  136. package/src/dnd/createDrop.ts +322 -321
  137. package/src/dnd/createDroppableCollection.ts +682 -293
  138. package/src/dnd/createDroppableItem.ts +215 -213
  139. package/src/dnd/index.ts +55 -47
  140. package/src/dnd/types.ts +89 -89
  141. package/src/dnd/utils.ts +294 -294
  142. package/src/focus/createAutoFocus.ts +321 -321
  143. package/src/focus/createFocusRestore.ts +313 -313
  144. package/src/focus/createVirtualFocus.ts +396 -396
  145. package/src/form/createFormValidation.ts +224 -224
  146. package/src/form/index.ts +11 -11
  147. package/src/grid/createGrid.ts +3 -1
  148. package/src/gridlist/createGridList.ts +16 -0
  149. package/src/gridlist/createGridListItem.ts +1 -1
  150. package/src/i18n/NumberFormatter.ts +266 -266
  151. package/src/i18n/createCollator.ts +79 -79
  152. package/src/i18n/createDateFormatter.ts +83 -83
  153. package/src/i18n/createFilter.ts +131 -131
  154. package/src/i18n/createNumberFormatter.ts +52 -52
  155. package/src/i18n/index.ts +40 -40
  156. package/src/i18n/locale.tsx +188 -188
  157. package/src/i18n/utils.ts +99 -99
  158. package/src/index.ts +51 -0
  159. package/src/interactions/createFocus.ts +6 -5
  160. package/src/interactions/createFocusWithin.ts +6 -5
  161. package/src/interactions/createLongPress.ts +174 -174
  162. package/src/interactions/createMove.ts +289 -289
  163. package/src/interactions/createPress.ts +5 -5
  164. package/src/landmark/createLandmark.ts +377 -377
  165. package/src/landmark/index.ts +8 -8
  166. package/src/link/createLink.ts +23 -8
  167. package/src/listbox/createListBox.ts +308 -269
  168. package/src/listbox/createOption.ts +162 -151
  169. package/src/listbox/index.ts +12 -12
  170. package/src/live-announcer/announce.ts +322 -322
  171. package/src/live-announcer/index.ts +9 -9
  172. package/src/menu/createMenu.ts +405 -396
  173. package/src/menu/createMenuItem.ts +149 -149
  174. package/src/menu/createMenuTrigger.ts +88 -88
  175. package/src/menu/index.ts +18 -18
  176. package/src/meter/createMeter.ts +1 -6
  177. package/src/numberfield/createNumberField.ts +311 -268
  178. package/src/numberfield/index.ts +5 -5
  179. package/src/overlays/ariaHideOutside.ts +219 -219
  180. package/src/overlays/createInteractOutside.ts +149 -149
  181. package/src/overlays/createModal.tsx +238 -202
  182. package/src/overlays/createOverlay.ts +165 -155
  183. package/src/overlays/createOverlayTrigger.ts +85 -85
  184. package/src/overlays/createPreventScroll.ts +266 -266
  185. package/src/overlays/index.ts +48 -44
  186. package/src/popover/calculatePosition.ts +6 -6
  187. package/src/popover/createOverlayPosition.ts +7 -4
  188. package/src/popover/createPopover.ts +21 -7
  189. package/src/progress/createProgressBar.ts +6 -1
  190. package/src/radio/createRadioGroup.ts +88 -14
  191. package/src/searchfield/createSearchField.ts +241 -186
  192. package/src/searchfield/index.ts +2 -2
  193. package/src/select/createHiddenSelect.tsx +263 -236
  194. package/src/select/createSelect.ts +373 -395
  195. package/src/select/index.ts +14 -14
  196. package/src/slider/createSlider.ts +364 -349
  197. package/src/slider/index.ts +2 -2
  198. package/src/ssr/index.tsx +370 -370
  199. package/src/table/createTable.ts +3 -1
  200. package/src/table/createTableColumnHeader.ts +1 -1
  201. package/src/table/createTableRow.ts +1 -1
  202. package/src/tabs/createTabs.ts +80 -51
  203. package/src/tag/createTag.ts +135 -6
  204. package/src/tag/createTagGroup.ts +7 -2
  205. package/src/toast/createToast.ts +8 -2
  206. package/src/toast/createToastRegion.ts +0 -1
  207. package/src/toolbar/createToolbar.ts +75 -1
  208. package/src/tooltip/createTooltip.ts +79 -79
  209. package/src/tooltip/createTooltipTrigger.ts +226 -222
  210. package/src/tooltip/index.ts +6 -6
  211. package/src/tree/createTree.ts +261 -246
  212. package/src/tree/createTreeItem.ts +282 -233
  213. package/src/tree/createTreeSelectionCheckbox.ts +68 -68
  214. package/src/tree/index.ts +16 -16
  215. package/src/tree/types.ts +91 -87
  216. package/src/utils/env.ts +55 -54
  217. package/src/utils/platform.ts +16 -6
  218. package/src/visually-hidden/createVisuallyHidden.ts +139 -124
  219. package/src/visually-hidden/index.ts +6 -6
@@ -1,293 +1,682 @@
1
- /**
2
- * createDroppableCollection - ARIA hook for droppable collection targets.
3
- *
4
- * Provides accessibility support for dropping items into a collection
5
- * component like ListBox, GridList, or Table.
6
- */
7
-
8
- import { createMemo, onCleanup, type Accessor } from 'solid-js';
9
- import type { JSX } from 'solid-js';
10
- import type {
11
- DroppableCollectionState,
12
- DropTarget,
13
- DropOperation,
14
- DropItem,
15
- DragTypes,
16
- } from '@proyecto-viviana/solid-stately';
17
- import { createDrop } from './createDrop';
18
- import { getGlobalDraggingCollectionRef } from './createDraggableCollection';
19
-
20
- // Global state for tracking the drop collection
21
- let globalDropCollectionRef: HTMLElement | null = null;
22
-
23
- export function setGlobalDropCollectionRef(ref: HTMLElement | null): void {
24
- globalDropCollectionRef = ref;
25
- }
26
-
27
- export function getGlobalDropCollectionRef(): HTMLElement | null {
28
- return globalDropCollectionRef;
29
- }
30
-
31
- export interface DropTargetDelegate {
32
- /**
33
- * Returns a drop target from a point within the collection.
34
- */
35
- getDropTargetFromPoint(
36
- x: number,
37
- y: number,
38
- isValidDropTarget: (target: DropTarget) => boolean
39
- ): DropTarget | null;
40
- }
41
-
42
- export interface DroppableCollectionOptions {
43
- /** Reference to the collection element. */
44
- ref: Accessor<HTMLElement | null>;
45
- /** A delegate that provides drop targets for pointer coordinates. */
46
- dropTargetDelegate: DropTargetDelegate;
47
- /** Handler called when items are dropped to be inserted. */
48
- onInsert?: (e: {
49
- items: DropItem[];
50
- target: DropTarget;
51
- dropOperation: DropOperation;
52
- }) => void;
53
- /** Handler called when items are dropped on the root. */
54
- onRootDrop?: (e: { items: DropItem[]; dropOperation: DropOperation }) => void;
55
- /** Handler called when items are dropped on an item. */
56
- onItemDrop?: (e: {
57
- items: DropItem[];
58
- target: DropTarget;
59
- dropOperation: DropOperation;
60
- isInternal: boolean;
61
- }) => void;
62
- /** Handler called when items are reordered within the collection. */
63
- onReorder?: (e: {
64
- keys: Set<string | number>;
65
- target: DropTarget;
66
- dropOperation: DropOperation;
67
- }) => void;
68
- /** Handler called when items are moved within/between collections. */
69
- onMove?: (e: {
70
- keys: Set<string | number>;
71
- target: DropTarget;
72
- dropOperation: DropOperation;
73
- }) => void;
74
- /** Handler called when the drop target is activated (held over). */
75
- onDropActivate?: (e: { target: DropTarget; x: number; y: number }) => void;
76
- /** Whether the collection is disabled for dropping. */
77
- isDisabled?: boolean;
78
- /** Accepted drag types. 'all' accepts any type. */
79
- acceptedDragTypes?: 'all' | string[];
80
- }
81
-
82
- export interface DroppableCollectionAria {
83
- /** Props to spread on the collection element. */
84
- collectionProps: JSX.HTMLAttributes<HTMLElement>;
85
- }
86
-
87
- /**
88
- * Creates ARIA support for a droppable collection.
89
- *
90
- * @param options - Collection options accessor
91
- * @param state - Droppable collection state
92
- * @returns Droppable collection ARIA result
93
- */
94
- export function createDroppableCollection(
95
- options: Accessor<DroppableCollectionOptions>,
96
- state: DroppableCollectionState
97
- ): DroppableCollectionAria {
98
- const getOptions = createMemo(() => options());
99
-
100
- // Track the next target during drag operations
101
- let nextTarget: DropTarget | null = null;
102
- let currentDropOperation: DropOperation | null = null;
103
-
104
- const isInternalDropOperation = (): boolean => {
105
- const ref = getOptions().ref();
106
- const draggingRef = getGlobalDraggingCollectionRef();
107
- return ref !== null && draggingRef === ref;
108
- };
109
-
110
- const getDropOperationForTarget = (
111
- target: DropTarget,
112
- types: DragTypes,
113
- allowedOperations: DropOperation[]
114
- ): DropOperation => {
115
- return state.getDropOperation(target, types, allowedOperations);
116
- };
117
-
118
- // Create base drop behavior
119
- const drop = createDrop(() => ({
120
- isDisabled: getOptions().isDisabled,
121
- getDropOperationForPoint: (types, allowedOperations, x, y) => {
122
- const opts = getOptions();
123
- const isValidDropTarget = (target: DropTarget) =>
124
- getDropOperationForTarget(target, types, allowedOperations) !== 'cancel';
125
-
126
- const target = opts.dropTargetDelegate.getDropTargetFromPoint(
127
- x,
128
- y,
129
- isValidDropTarget
130
- );
131
-
132
- if (!target) {
133
- currentDropOperation = 'cancel';
134
- nextTarget = null;
135
- return 'cancel';
136
- }
137
-
138
- currentDropOperation = getDropOperationForTarget(
139
- target,
140
- types,
141
- allowedOperations
142
- );
143
-
144
- // If target doesn't accept, try root
145
- if (currentDropOperation === 'cancel') {
146
- const rootTarget: DropTarget = { type: 'root' };
147
- const rootOp = getDropOperationForTarget(
148
- rootTarget,
149
- types,
150
- allowedOperations
151
- );
152
- if (rootOp !== 'cancel') {
153
- nextTarget = rootTarget;
154
- currentDropOperation = rootOp;
155
- return currentDropOperation;
156
- }
157
- }
158
-
159
- // Update drop collection ref
160
- const ref = opts.ref();
161
- if (target && currentDropOperation !== 'cancel' && ref !== globalDropCollectionRef) {
162
- setGlobalDropCollectionRef(ref);
163
- }
164
-
165
- nextTarget = currentDropOperation === 'cancel' ? null : target;
166
- return currentDropOperation;
167
- },
168
- onDropEnter: () => {
169
- if (nextTarget) {
170
- state.setTarget(nextTarget);
171
- }
172
- },
173
- onDropMove: () => {
174
- if (nextTarget) {
175
- state.setTarget(nextTarget);
176
- }
177
- },
178
- onDropExit: () => {
179
- setGlobalDropCollectionRef(null);
180
- state.setTarget(null);
181
- },
182
- onDropActivate: (e) => {
183
- const opts = getOptions();
184
- if (state.target?.type === 'item' && typeof opts.onDropActivate === 'function') {
185
- opts.onDropActivate({
186
- target: state.target,
187
- x: e.x,
188
- y: e.y,
189
- });
190
- }
191
- },
192
- onDrop: (e) => {
193
- const opts = getOptions();
194
- setGlobalDropCollectionRef(opts.ref());
195
-
196
- if (state.target) {
197
- handleDrop(e.items, state.target, e.dropOperation);
198
- }
199
- },
200
- }));
201
-
202
- const handleDrop = async (
203
- items: DropItem[],
204
- target: DropTarget,
205
- dropOperation: DropOperation
206
- ) => {
207
- const opts = getOptions();
208
- const isInternal = isInternalDropOperation();
209
-
210
- // Filter items by accepted types
211
- let filteredItems = items;
212
- const acceptedTypes = opts.acceptedDragTypes;
213
- if (acceptedTypes && acceptedTypes !== 'all') {
214
- filteredItems = items.filter((item) => {
215
- const itemTypes =
216
- item.kind === 'file'
217
- ? new Set([item.type])
218
- : item.kind === 'text'
219
- ? item.types
220
- : new Set<string>();
221
- return acceptedTypes.some((type) => itemTypes.has(type));
222
- });
223
- }
224
-
225
- if (filteredItems.length === 0) return;
226
-
227
- // Call appropriate handlers based on target type
228
- if (target.type === 'root' && opts.onRootDrop) {
229
- await opts.onRootDrop({ items: filteredItems, dropOperation });
230
- }
231
-
232
- if (target.type === 'item') {
233
- if (target.dropPosition === 'on' && opts.onItemDrop) {
234
- await opts.onItemDrop({
235
- items: filteredItems,
236
- target,
237
- dropOperation,
238
- isInternal,
239
- });
240
- }
241
-
242
- // Handle move for internal operations
243
- if (opts.onMove && isInternal) {
244
- // Would get dragging keys from global state
245
- await opts.onMove({
246
- keys: new Set(),
247
- target,
248
- dropOperation,
249
- });
250
- }
251
-
252
- if (target.dropPosition !== 'on') {
253
- if (!isInternal && opts.onInsert) {
254
- await opts.onInsert({
255
- items: filteredItems,
256
- target,
257
- dropOperation,
258
- });
259
- }
260
-
261
- if (isInternal && opts.onReorder) {
262
- // Would get dragging keys from global state
263
- await opts.onReorder({
264
- keys: new Set(),
265
- target,
266
- dropOperation,
267
- });
268
- }
269
- }
270
- }
271
- };
272
-
273
- // Clean up on unmount
274
- onCleanup(() => {
275
- const ref = getOptions().ref();
276
- if (globalDropCollectionRef === ref) {
277
- setGlobalDropCollectionRef(null);
278
- }
279
- });
280
-
281
- const collectionProps = createMemo(() => {
282
- const baseDropProps = drop.dropProps;
283
- return {
284
- ...baseDropProps,
285
- };
286
- });
287
-
288
- return {
289
- get collectionProps() {
290
- return collectionProps() as DroppableCollectionAria['collectionProps'];
291
- },
292
- };
293
- }
1
+ /**
2
+ * createDroppableCollection - ARIA hook for droppable collection targets.
3
+ *
4
+ * Provides accessibility support for dropping items into a collection
5
+ * component like ListBox, GridList, or Table.
6
+ */
7
+
8
+ import { createMemo, onCleanup, type Accessor } from 'solid-js';
9
+ import type { JSX } from 'solid-js';
10
+ import type {
11
+ DroppableCollectionState,
12
+ DropTarget,
13
+ DropOperation,
14
+ DropItem,
15
+ DragTypes,
16
+ } from '@proyecto-viviana/solid-stately';
17
+ import { DIRECTORY_DRAG_TYPE } from '@proyecto-viviana/solid-stately';
18
+ import { createDrop } from './createDrop';
19
+ import {
20
+ getGlobalDraggingCollectionRef,
21
+ getGlobalDraggingKeys,
22
+ getGlobalDraggingTypes,
23
+ } from './createDraggableCollection';
24
+ import { getGlobalAllowedDropOperations, DROP_OPERATION } from './utils';
25
+
26
+ // Global state for tracking the drop collection
27
+ let globalDropCollectionRef: HTMLElement | null = null;
28
+
29
+ export function setGlobalDropCollectionRef(ref: HTMLElement | null): void {
30
+ globalDropCollectionRef = ref;
31
+ }
32
+
33
+ export function getGlobalDropCollectionRef(): HTMLElement | null {
34
+ return globalDropCollectionRef;
35
+ }
36
+
37
+ export interface DropTargetDelegate {
38
+ /**
39
+ * Returns a drop target from a point within the collection.
40
+ */
41
+ getDropTargetFromPoint(
42
+ x: number,
43
+ y: number,
44
+ isValidDropTarget: (target: DropTarget) => boolean
45
+ ): DropTarget | null;
46
+ /**
47
+ * Returns the next keyboard-navigable drop target.
48
+ */
49
+ getKeyboardNavigationTarget?(
50
+ target: DropTarget | null,
51
+ direction: 'next' | 'previous',
52
+ isValidDropTarget: (target: DropTarget) => boolean
53
+ ): DropTarget | null;
54
+ /**
55
+ * Returns the next page-navigable drop target.
56
+ */
57
+ getKeyboardPageNavigationTarget?(
58
+ target: DropTarget | null,
59
+ direction: 'next' | 'previous',
60
+ isValidDropTarget: (target: DropTarget) => boolean
61
+ ): DropTarget | null;
62
+ }
63
+
64
+ export interface KeyboardDelegateLike {
65
+ getFirstKey?: () => string | number | null;
66
+ getLastKey?: () => string | number | null;
67
+ getKeyBelow?: (key: string | number) => string | number | null;
68
+ getKeyAbove?: (key: string | number) => string | number | null;
69
+ getKeyRightOf?: (key: string | number) => string | number | null;
70
+ getKeyLeftOf?: (key: string | number) => string | number | null;
71
+ getKeyPageBelow?: (key: string | number) => string | number | null;
72
+ getKeyPageAbove?: (key: string | number) => string | number | null;
73
+ }
74
+
75
+ export interface DroppableCollectionOptions {
76
+ /** Reference to the collection element. */
77
+ ref: Accessor<HTMLElement | null>;
78
+ /** A delegate that provides drop targets for pointer coordinates. */
79
+ dropTargetDelegate: DropTargetDelegate;
80
+ /** Handler called when items are dropped to be inserted. */
81
+ onInsert?: (e: {
82
+ items: DropItem[];
83
+ target: DropTarget;
84
+ dropOperation: DropOperation;
85
+ }) => void;
86
+ /** Handler called when items are dropped on the root. */
87
+ onRootDrop?: (e: { items: DropItem[]; dropOperation: DropOperation }) => void;
88
+ /** Handler called when items are dropped on an item. */
89
+ onItemDrop?: (e: {
90
+ items: DropItem[];
91
+ target: DropTarget;
92
+ dropOperation: DropOperation;
93
+ isInternal: boolean;
94
+ }) => void;
95
+ /** Handler called when items are reordered within the collection. */
96
+ onReorder?: (e: {
97
+ keys: Set<string | number>;
98
+ target: DropTarget;
99
+ dropOperation: DropOperation;
100
+ }) => void;
101
+ /** Handler called when items are moved within/between collections. */
102
+ onMove?: (e: {
103
+ keys: Set<string | number>;
104
+ target: DropTarget;
105
+ dropOperation: DropOperation;
106
+ }) => void;
107
+ /** Handler called when a drop occurs on any collection target. */
108
+ onDrop?: (e: {
109
+ items: DropItem[];
110
+ target: DropTarget;
111
+ dropOperation: DropOperation;
112
+ x: number;
113
+ y: number;
114
+ }) => void;
115
+ /** Handler called when the drop target is activated (held over). */
116
+ onDropActivate?: (e: { target: DropTarget; x: number; y: number }) => void;
117
+ /** Optional keyboard delegate used as fallback when drop-target delegates do not provide keyboard navigation methods. */
118
+ keyboardDelegate?: KeyboardDelegateLike;
119
+ /** Optional keyboard handler composed with internal drop target navigation keys. */
120
+ onKeyDown?: (e: KeyboardEvent) => void;
121
+ /** Whether the collection is disabled for dropping. */
122
+ isDisabled?: boolean;
123
+ /** Accepted drag types. 'all' accepts any type. */
124
+ acceptedDragTypes?: 'all' | Array<string | symbol>;
125
+ }
126
+
127
+ export function getDropItemTypes(item: DropItem): Set<string | symbol> {
128
+ if (item.kind === 'file') {
129
+ return new Set([item.type]);
130
+ }
131
+ if (item.kind === 'text') {
132
+ return new Set(item.types);
133
+ }
134
+ return new Set([DIRECTORY_DRAG_TYPE]);
135
+ }
136
+
137
+ export interface DroppableCollectionAria {
138
+ /** Props to spread on the collection element. */
139
+ collectionProps: JSX.HTMLAttributes<HTMLElement>;
140
+ }
141
+
142
+ /**
143
+ * Creates ARIA support for a droppable collection.
144
+ *
145
+ * @param options - Collection options accessor
146
+ * @param state - Droppable collection state
147
+ * @returns Droppable collection ARIA result
148
+ */
149
+ export function createDroppableCollection(
150
+ options: Accessor<DroppableCollectionOptions>,
151
+ state: DroppableCollectionState
152
+ ): DroppableCollectionAria {
153
+ const getOptions = createMemo(() => options());
154
+
155
+ // Track the next target during drag operations
156
+ let nextTarget: DropTarget | null = null;
157
+ let currentDropOperation: DropOperation | null = null;
158
+
159
+ const isInternalDropOperation = (): boolean => {
160
+ const ref = getOptions().ref();
161
+ const draggingRef = getGlobalDraggingCollectionRef();
162
+ return ref !== null && draggingRef === ref;
163
+ };
164
+
165
+ const getDropOperationForTarget = (
166
+ target: DropTarget,
167
+ types: DragTypes,
168
+ allowedOperations: DropOperation[]
169
+ ): DropOperation => {
170
+ return state.getDropOperation(target, types, allowedOperations);
171
+ };
172
+
173
+ // Create base drop behavior
174
+ const drop = createDrop(() => ({
175
+ isDisabled: getOptions().isDisabled,
176
+ getDropOperationForPoint: (types, allowedOperations, x, y) => {
177
+ const opts = getOptions();
178
+ const isValidDropTarget = (target: DropTarget) =>
179
+ getDropOperationForTarget(target, types, allowedOperations) !== 'cancel';
180
+
181
+ const target = opts.dropTargetDelegate.getDropTargetFromPoint(
182
+ x,
183
+ y,
184
+ isValidDropTarget
185
+ );
186
+
187
+ if (!target) {
188
+ currentDropOperation = 'cancel';
189
+ nextTarget = null;
190
+ return 'cancel';
191
+ }
192
+
193
+ currentDropOperation = getDropOperationForTarget(
194
+ target,
195
+ types,
196
+ allowedOperations
197
+ );
198
+
199
+ // If target doesn't accept, try root
200
+ if (currentDropOperation === 'cancel') {
201
+ const rootTarget: DropTarget = { type: 'root' };
202
+ const rootOp = getDropOperationForTarget(
203
+ rootTarget,
204
+ types,
205
+ allowedOperations
206
+ );
207
+ if (rootOp !== 'cancel') {
208
+ nextTarget = rootTarget;
209
+ currentDropOperation = rootOp;
210
+ return currentDropOperation;
211
+ }
212
+ }
213
+
214
+ // Update drop collection ref
215
+ const ref = opts.ref();
216
+ if (target && currentDropOperation !== 'cancel' && ref !== globalDropCollectionRef) {
217
+ setGlobalDropCollectionRef(ref);
218
+ }
219
+
220
+ nextTarget = currentDropOperation === 'cancel' ? null : target;
221
+ return currentDropOperation;
222
+ },
223
+ onDropEnter: () => {
224
+ if (nextTarget) {
225
+ state.setTarget(nextTarget);
226
+ }
227
+ },
228
+ onDropMove: () => {
229
+ if (nextTarget) {
230
+ state.setTarget(nextTarget);
231
+ }
232
+ },
233
+ onDropExit: () => {
234
+ setGlobalDropCollectionRef(null);
235
+ state.setTarget(null);
236
+ },
237
+ onDropActivate: (e) => {
238
+ const opts = getOptions();
239
+ if (
240
+ state.target?.type === 'item' &&
241
+ state.target.dropPosition === 'on' &&
242
+ typeof opts.onDropActivate === 'function'
243
+ ) {
244
+ opts.onDropActivate({
245
+ target: state.target,
246
+ x: e.x,
247
+ y: e.y,
248
+ });
249
+ }
250
+ },
251
+ onDrop: (e) => {
252
+ const opts = getOptions();
253
+ setGlobalDropCollectionRef(opts.ref());
254
+
255
+ if (state.target) {
256
+ opts.onDrop?.({
257
+ items: e.items,
258
+ target: state.target,
259
+ dropOperation: e.dropOperation,
260
+ x: e.x,
261
+ y: e.y,
262
+ });
263
+ handleDrop(e.items, state.target, e.dropOperation);
264
+ }
265
+ },
266
+ }));
267
+
268
+ const handleDrop = async (
269
+ items: DropItem[],
270
+ target: DropTarget,
271
+ dropOperation: DropOperation
272
+ ) => {
273
+ const opts = getOptions();
274
+ const isInternal = isInternalDropOperation();
275
+
276
+ // Filter items by accepted types
277
+ let filteredItems = items;
278
+ const acceptedTypes = opts.acceptedDragTypes;
279
+ if (acceptedTypes && acceptedTypes !== 'all') {
280
+ filteredItems = items.filter((item) => {
281
+ const itemTypes = getDropItemTypes(item);
282
+ return acceptedTypes.some((type) => itemTypes.has(type));
283
+ });
284
+ }
285
+
286
+ if (filteredItems.length === 0) return;
287
+
288
+ // Call appropriate handlers based on target type
289
+ if (target.type === 'root' && opts.onRootDrop) {
290
+ await opts.onRootDrop({ items: filteredItems, dropOperation });
291
+ }
292
+
293
+ if (target.type === 'item') {
294
+ if (target.dropPosition === 'on' && opts.onItemDrop) {
295
+ await opts.onItemDrop({
296
+ items: filteredItems,
297
+ target,
298
+ dropOperation,
299
+ isInternal,
300
+ });
301
+ }
302
+
303
+ // Handle move for internal operations
304
+ if (opts.onMove && isInternal) {
305
+ const draggingKeys = getGlobalDraggingKeys();
306
+ await opts.onMove({
307
+ keys: draggingKeys,
308
+ target,
309
+ dropOperation,
310
+ });
311
+ }
312
+
313
+ if (target.dropPosition !== 'on') {
314
+ if (!isInternal && opts.onInsert) {
315
+ await opts.onInsert({
316
+ items: filteredItems,
317
+ target,
318
+ dropOperation,
319
+ });
320
+ }
321
+
322
+ if (isInternal && opts.onReorder) {
323
+ const draggingKeys = getGlobalDraggingKeys();
324
+ await opts.onReorder({
325
+ keys: draggingKeys,
326
+ target,
327
+ dropOperation,
328
+ });
329
+ }
330
+ }
331
+ }
332
+ };
333
+
334
+ // Clean up on unmount
335
+ onCleanup(() => {
336
+ const ref = getOptions().ref();
337
+ if (globalDropCollectionRef === ref) {
338
+ setGlobalDropCollectionRef(null);
339
+ }
340
+ });
341
+
342
+ const collectionProps = createMemo(() => {
343
+ const baseDropProps = drop.dropProps;
344
+ const onKeyDownBase = baseDropProps.onKeyDown as ((e: KeyboardEvent) => void) | undefined;
345
+ const onKeyDown = (e: KeyboardEvent): void => {
346
+ onKeyDownBase?.(e);
347
+ const opts = getOptions();
348
+ if (opts.isDisabled) return;
349
+ const resolveDirection = (): 'ltr' | 'rtl' => {
350
+ const refEl = opts.ref();
351
+ if (refEl && typeof window !== 'undefined' && typeof window.getComputedStyle === 'function') {
352
+ const computedDir = window.getComputedStyle(refEl).direction;
353
+ if (computedDir === 'rtl') return 'rtl';
354
+ }
355
+ return typeof document !== 'undefined' && document.dir === 'rtl' ? 'rtl' : 'ltr';
356
+ };
357
+ const isRtl = resolveDirection() === 'rtl';
358
+ const forwardHorizontalKey = isRtl ? 'ArrowLeft' : 'ArrowRight';
359
+ const backwardHorizontalKey = isRtl ? 'ArrowRight' : 'ArrowLeft';
360
+ const callUserOnKeyDown = () => opts.onKeyDown?.(e);
361
+ const getKeyboardAllowedOperations = (): DropOperation[] => {
362
+ const allowedBits = getGlobalAllowedDropOperations();
363
+ if (!allowedBits) return ['copy', 'move', 'link'];
364
+ const allowed: DropOperation[] = [];
365
+ if (allowedBits & DROP_OPERATION.copy) allowed.push('copy');
366
+ if (allowedBits & DROP_OPERATION.move) allowed.push('move');
367
+ if (allowedBits & DROP_OPERATION.link) allowed.push('link');
368
+ return allowed.length > 0 ? allowed : ['copy', 'move', 'link'];
369
+ };
370
+ const getKeyboardDragTypes = (): DragTypes => {
371
+ const draggingTypes = getGlobalDraggingTypes();
372
+ if (draggingTypes.size === 0) {
373
+ return { has: () => true };
374
+ }
375
+ return {
376
+ has: (type: string | symbol) => typeof type === 'string' && draggingTypes.has(type),
377
+ };
378
+ };
379
+ const isValidDropTarget = (target: DropTarget) =>
380
+ state.getDropOperation(target, getKeyboardDragTypes(), getKeyboardAllowedOperations()) !== 'cancel';
381
+ const targetsEqual = (a: DropTarget, b: DropTarget): boolean => {
382
+ if (a.type !== b.type) return false;
383
+ if (a.type === 'root' && b.type === 'root') return true;
384
+ if (a.type !== 'item' || b.type !== 'item') return false;
385
+ return a.key === b.key && a.dropPosition === b.dropPosition;
386
+ };
387
+ const findNextValidTarget = (
388
+ start: DropTarget | null,
389
+ getNext: (target: DropTarget | null) => DropTarget | null
390
+ ): DropTarget | null => {
391
+ let current = start;
392
+ let seenRoot = 0;
393
+ let safety = 0;
394
+ while (safety < 256) {
395
+ safety += 1;
396
+ const next = getNext(current);
397
+ if (!next) return null;
398
+ if (current && targetsEqual(current, next)) {
399
+ return isValidDropTarget(next) ? next : null;
400
+ }
401
+ current = next;
402
+ if (next.type === 'root') {
403
+ seenRoot += 1;
404
+ if (seenRoot >= 2) {
405
+ return isValidDropTarget(next) ? next : null;
406
+ }
407
+ }
408
+ if (isValidDropTarget(next)) return next;
409
+ }
410
+ return null;
411
+ };
412
+ const resolveTargetForKey = (
413
+ key: string | number | null,
414
+ direction: 'next' | 'previous'
415
+ ): DropTarget | null => {
416
+ if (key == null) return null;
417
+ const onTarget: DropTarget = { type: 'item', key, dropPosition: 'on' };
418
+ if (isValidDropTarget(onTarget)) return onTarget;
419
+ const insertionOrder: Array<'before' | 'after'> = direction === 'next'
420
+ ? ['before', 'after']
421
+ : ['after', 'before'];
422
+ for (const position of insertionOrder) {
423
+ const insertionTarget: DropTarget = { type: 'item', key, dropPosition: position };
424
+ if (isValidDropTarget(insertionTarget)) return insertionTarget;
425
+ }
426
+ return null;
427
+ };
428
+ const resolveBoundaryTargetForDirection = (
429
+ key: string | number | null,
430
+ direction: 'next' | 'previous'
431
+ ): DropTarget | null => {
432
+ if (key == null) return null;
433
+ const boundaryOrder: Array<'before' | 'on' | 'after'> = direction === 'next'
434
+ ? ['before', 'on', 'after']
435
+ : ['after', 'on', 'before'];
436
+ for (const position of boundaryOrder) {
437
+ const candidate: DropTarget = { type: 'item', key, dropPosition: position };
438
+ if (isValidDropTarget(candidate)) return candidate;
439
+ }
440
+ return null;
441
+ };
442
+ const resolveFallbackKeyboardTarget = (
443
+ keyName: string,
444
+ currentTarget: DropTarget | null = state.target
445
+ ): DropTarget | null => {
446
+ const keyboardDelegate = opts.keyboardDelegate;
447
+ if (!keyboardDelegate) return null;
448
+ const currentKey = currentTarget?.type === 'item' ? currentTarget.key : null;
449
+ const keyForDirection = (
450
+ direction: 'next' | 'previous',
451
+ getter: ((key: string | number) => string | number | null) | undefined
452
+ ): DropTarget | null => {
453
+ if (currentKey == null) {
454
+ const boundaryKey = direction === 'next'
455
+ ? keyboardDelegate.getFirstKey?.()
456
+ : keyboardDelegate.getLastKey?.();
457
+ return resolveBoundaryTargetForDirection(boundaryKey ?? null, direction);
458
+ }
459
+ if (!getter) return null;
460
+ let nextKey = getter(currentKey);
461
+ let safety = 0;
462
+ while (nextKey != null && safety < 256) {
463
+ const resolvedTarget = resolveTargetForKey(nextKey, direction);
464
+ if (resolvedTarget) return resolvedTarget;
465
+ const candidate = getter(nextKey);
466
+ if (candidate === nextKey) break;
467
+ nextKey = candidate;
468
+ safety += 1;
469
+ }
470
+ return null;
471
+ };
472
+
473
+ if (keyName === 'ArrowDown') return keyForDirection('next', keyboardDelegate.getKeyBelow);
474
+ if (keyName === 'ArrowUp') return keyForDirection('previous', keyboardDelegate.getKeyAbove);
475
+ if (keyName === forwardHorizontalKey) {
476
+ return keyForDirection('next', isRtl ? keyboardDelegate.getKeyLeftOf : keyboardDelegate.getKeyRightOf);
477
+ }
478
+ if (keyName === backwardHorizontalKey) {
479
+ return keyForDirection('previous', isRtl ? keyboardDelegate.getKeyRightOf : keyboardDelegate.getKeyLeftOf);
480
+ }
481
+ if (keyName === 'Home') return resolveBoundaryTargetForDirection(keyboardDelegate.getFirstKey?.() ?? null, 'next');
482
+ if (keyName === 'End') return resolveBoundaryTargetForDirection(keyboardDelegate.getLastKey?.() ?? null, 'previous');
483
+ if (keyName === 'PageDown') {
484
+ if (currentKey != null && keyboardDelegate.getKeyPageBelow) {
485
+ return resolveTargetForKey(keyboardDelegate.getKeyPageBelow(currentKey), 'next');
486
+ }
487
+ return keyForDirection('next', keyboardDelegate.getKeyBelow);
488
+ }
489
+ if (keyName === 'PageUp') {
490
+ if (currentKey != null && keyboardDelegate.getKeyPageAbove) {
491
+ return resolveTargetForKey(keyboardDelegate.getKeyPageAbove(currentKey), 'previous');
492
+ }
493
+ return keyForDirection('previous', keyboardDelegate.getKeyAbove);
494
+ }
495
+ return null;
496
+ };
497
+ const resolvePageTargetForState = (
498
+ direction: 'next' | 'previous',
499
+ currentTarget: DropTarget
500
+ ): DropTarget | null => {
501
+ const keyboardDelegate = opts.keyboardDelegate;
502
+ if (!keyboardDelegate) return null;
503
+ if (direction === 'next') {
504
+ let targetKey = keyboardDelegate.getFirstKey?.() ?? null;
505
+ let dropPosition: 'before' | 'on' | 'after' = 'after';
506
+ if (currentTarget.type === 'item') {
507
+ targetKey = currentTarget.key;
508
+ dropPosition = currentTarget.dropPosition;
509
+ }
510
+ let nextKey = targetKey != null ? keyboardDelegate.getKeyPageBelow?.(targetKey) ?? null : null;
511
+ if (
512
+ nextKey == null ||
513
+ (currentTarget.type === 'item' && currentTarget.key === keyboardDelegate.getLastKey?.())
514
+ ) {
515
+ nextKey = keyboardDelegate.getLastKey?.() ?? null;
516
+ dropPosition = 'after';
517
+ }
518
+ if (nextKey == null) return null;
519
+ return {
520
+ type: 'item',
521
+ key: nextKey,
522
+ dropPosition,
523
+ };
524
+ }
525
+
526
+ if (currentTarget.type === 'item') {
527
+ if (currentTarget.key === keyboardDelegate.getFirstKey?.()) {
528
+ return { type: 'root' };
529
+ }
530
+ let nextKey = keyboardDelegate.getKeyPageAbove?.(currentTarget.key) ?? null;
531
+ let dropPosition: 'before' | 'on' | 'after' = currentTarget.dropPosition;
532
+ if (nextKey == null) {
533
+ nextKey = keyboardDelegate.getFirstKey?.() ?? null;
534
+ dropPosition = 'before';
535
+ }
536
+ if (nextKey == null) return null;
537
+ return {
538
+ type: 'item',
539
+ key: nextKey,
540
+ dropPosition,
541
+ };
542
+ }
543
+
544
+ return currentTarget.type === 'root' ? currentTarget : null;
545
+ };
546
+ if (e.key === 'PageDown' || e.key === 'PageUp') {
547
+ if (
548
+ (e.key === 'PageDown' && !opts.keyboardDelegate?.getKeyPageBelow) ||
549
+ (e.key === 'PageUp' && !opts.keyboardDelegate?.getKeyPageAbove)
550
+ ) {
551
+ callUserOnKeyDown();
552
+ return;
553
+ }
554
+ const direction = e.key === 'PageDown' ? 'next' : 'previous';
555
+ const pageNavigation = opts.dropTargetDelegate.getKeyboardPageNavigationTarget;
556
+ const stepNavigation = opts.dropTargetDelegate.getKeyboardNavigationTarget;
557
+ const resolveStepTarget = (
558
+ target: DropTarget | null,
559
+ navDirection: 'next' | 'previous'
560
+ ): DropTarget | null =>
561
+ stepNavigation?.(target, navDirection, isValidDropTarget)
562
+ ?? resolveFallbackKeyboardTarget(navDirection === 'next' ? 'ArrowDown' : 'ArrowUp', target)
563
+ ?? resolveFallbackKeyboardTarget(navDirection === 'next' ? 'Home' : 'End', target)
564
+ ?? null;
565
+ let nextTarget: DropTarget | null = null;
566
+ if (!state.target) {
567
+ nextTarget = findNextValidTarget(null, (target) => resolveStepTarget(target, direction));
568
+ } else {
569
+ const pageTarget = pageNavigation?.(state.target, direction, isValidDropTarget)
570
+ ?? resolvePageTargetForState(direction, state.target)
571
+ ?? null;
572
+ if (pageTarget && isValidDropTarget(pageTarget)) {
573
+ nextTarget = pageTarget;
574
+ } else {
575
+ const startTarget = pageTarget ?? state.target;
576
+ nextTarget = findNextValidTarget(startTarget, (target) => resolveStepTarget(target, direction))
577
+ ?? findNextValidTarget(
578
+ startTarget,
579
+ (target) => resolveStepTarget(target, direction === 'next' ? 'previous' : 'next')
580
+ );
581
+ }
582
+ }
583
+ if (nextTarget) {
584
+ e.preventDefault();
585
+ state.setTarget(nextTarget);
586
+ }
587
+ callUserOnKeyDown();
588
+ return;
589
+ }
590
+ if (
591
+ (e.key === 'ArrowDown' ||
592
+ e.key === 'ArrowUp' ||
593
+ e.key === 'ArrowRight' ||
594
+ e.key === 'ArrowLeft' ||
595
+ e.key === 'Home' ||
596
+ e.key === 'End') &&
597
+ opts.dropTargetDelegate.getKeyboardNavigationTarget
598
+ ) {
599
+ if (
600
+ (e.key === 'ArrowDown' && !opts.keyboardDelegate?.getKeyBelow) ||
601
+ (e.key === 'ArrowUp' && !opts.keyboardDelegate?.getKeyAbove) ||
602
+ (e.key === 'ArrowLeft' && !opts.keyboardDelegate?.getKeyLeftOf) ||
603
+ (e.key === 'ArrowRight' && !opts.keyboardDelegate?.getKeyRightOf) ||
604
+ (e.key === 'Home' && !opts.keyboardDelegate?.getFirstKey) ||
605
+ (e.key === 'End' && !opts.keyboardDelegate?.getLastKey)
606
+ ) {
607
+ callUserOnKeyDown();
608
+ return;
609
+ }
610
+ const isForwardKey = e.key === 'ArrowDown' || e.key === forwardHorizontalKey || e.key === 'Home';
611
+ const direction = isForwardKey ? 'next' : 'previous';
612
+ const navigationStart = e.key === 'Home' || e.key === 'End' ? null : state.target;
613
+ const nextTarget = findNextValidTarget(navigationStart, (target) =>
614
+ opts.dropTargetDelegate.getKeyboardNavigationTarget?.(
615
+ target,
616
+ direction,
617
+ isValidDropTarget
618
+ ) ?? resolveFallbackKeyboardTarget(e.key, target)
619
+ );
620
+ if (nextTarget) {
621
+ e.preventDefault();
622
+ state.setTarget(nextTarget);
623
+ }
624
+ callUserOnKeyDown();
625
+ return;
626
+ }
627
+ if (
628
+ e.key === 'ArrowDown' ||
629
+ e.key === 'ArrowUp' ||
630
+ e.key === 'ArrowRight' ||
631
+ e.key === 'ArrowLeft' ||
632
+ e.key === 'Home' ||
633
+ e.key === 'End'
634
+ ) {
635
+ if (
636
+ (e.key === 'ArrowDown' && !opts.keyboardDelegate?.getKeyBelow) ||
637
+ (e.key === 'ArrowUp' && !opts.keyboardDelegate?.getKeyAbove) ||
638
+ (e.key === 'ArrowLeft' && !opts.keyboardDelegate?.getKeyLeftOf) ||
639
+ (e.key === 'ArrowRight' && !opts.keyboardDelegate?.getKeyRightOf) ||
640
+ (e.key === 'Home' && !opts.keyboardDelegate?.getFirstKey) ||
641
+ (e.key === 'End' && !opts.keyboardDelegate?.getLastKey)
642
+ ) {
643
+ callUserOnKeyDown();
644
+ return;
645
+ }
646
+ const navigationStart = e.key === 'Home' || e.key === 'End' ? null : state.target;
647
+ const nextTarget = findNextValidTarget(navigationStart, (target) =>
648
+ resolveFallbackKeyboardTarget(e.key, target)
649
+ );
650
+ if (nextTarget) {
651
+ e.preventDefault();
652
+ state.setTarget(nextTarget);
653
+ }
654
+ callUserOnKeyDown();
655
+ return;
656
+ }
657
+ if (e.key === 'Enter' && state.target) {
658
+ e.preventDefault();
659
+ state.activateTarget(0, 0);
660
+ callUserOnKeyDown();
661
+ return;
662
+ }
663
+ if (e.key === 'Escape' && state.target) {
664
+ e.preventDefault();
665
+ state.exitTarget(0, 0);
666
+ callUserOnKeyDown();
667
+ return;
668
+ }
669
+ callUserOnKeyDown();
670
+ };
671
+ return {
672
+ ...baseDropProps,
673
+ onKeyDown,
674
+ };
675
+ });
676
+
677
+ return {
678
+ get collectionProps() {
679
+ return collectionProps() as DroppableCollectionAria['collectionProps'];
680
+ },
681
+ };
682
+ }