@proyecto-viviana/solidaria 0.2.2 → 0.2.4
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.
- package/dist/autocomplete/createAutocomplete.d.ts +2 -2
- package/dist/autocomplete/createAutocomplete.d.ts.map +1 -1
- package/dist/index.js +233 -234
- package/dist/index.js.map +2 -2
- package/dist/index.ssr.js +233 -234
- package/dist/index.ssr.js.map +2 -2
- package/dist/interactions/PressEvent.d.ts +13 -10
- package/dist/interactions/PressEvent.d.ts.map +1 -1
- package/dist/interactions/createPress.d.ts.map +1 -1
- package/dist/interactions/index.d.ts +1 -1
- package/dist/interactions/index.d.ts.map +1 -1
- package/dist/select/createHiddenSelect.d.ts.map +1 -1
- package/dist/toolbar/createToolbar.d.ts.map +1 -1
- package/dist/tooltip/createTooltipTrigger.d.ts.map +1 -1
- package/package.json +9 -7
- package/src/autocomplete/createAutocomplete.ts +341 -0
- package/src/autocomplete/index.ts +9 -0
- package/src/breadcrumbs/createBreadcrumbs.ts +196 -0
- package/src/breadcrumbs/index.ts +8 -0
- package/src/button/createButton.ts +142 -0
- package/src/button/createToggleButton.ts +101 -0
- package/src/button/index.ts +4 -0
- package/src/button/types.ts +78 -0
- package/src/calendar/createCalendar.ts +138 -0
- package/src/calendar/createCalendarCell.ts +187 -0
- package/src/calendar/createCalendarGrid.ts +140 -0
- package/src/calendar/createRangeCalendar.ts +136 -0
- package/src/calendar/createRangeCalendarCell.ts +186 -0
- package/src/calendar/index.ts +34 -0
- package/src/checkbox/createCheckbox.ts +135 -0
- package/src/checkbox/createCheckboxGroup.ts +137 -0
- package/src/checkbox/createCheckboxGroupItem.ts +117 -0
- package/src/checkbox/createCheckboxGroupState.ts +193 -0
- package/src/checkbox/index.ts +13 -0
- package/src/color/createColorArea.ts +314 -0
- package/src/color/createColorField.ts +137 -0
- package/src/color/createColorSlider.ts +197 -0
- package/src/color/createColorSwatch.ts +40 -0
- package/src/color/createColorWheel.ts +208 -0
- package/src/color/index.ts +24 -0
- package/src/color/types.ts +116 -0
- package/src/combobox/createComboBox.ts +647 -0
- package/src/combobox/index.ts +6 -0
- package/src/combobox/intl/en-US.json +7 -0
- package/src/combobox/intl/es-ES.json +7 -0
- package/src/combobox/intl/index.ts +23 -0
- package/src/datepicker/createDateField.ts +154 -0
- package/src/datepicker/createDatePicker.ts +206 -0
- package/src/datepicker/createDateSegment.ts +229 -0
- package/src/datepicker/createTimeField.ts +154 -0
- package/src/datepicker/index.ts +28 -0
- package/src/dialog/createDialog.ts +120 -0
- package/src/dialog/index.ts +2 -0
- package/src/dialog/types.ts +19 -0
- package/src/disclosure/createDisclosure.ts +131 -0
- package/src/disclosure/createDisclosureGroup.ts +62 -0
- package/src/disclosure/index.ts +11 -0
- package/src/dnd/createDrag.ts +209 -0
- package/src/dnd/createDraggableCollection.ts +63 -0
- package/src/dnd/createDraggableItem.ts +243 -0
- package/src/dnd/createDrop.ts +321 -0
- package/src/dnd/createDroppableCollection.ts +293 -0
- package/src/dnd/createDroppableItem.ts +213 -0
- package/src/dnd/index.ts +47 -0
- package/src/dnd/types.ts +89 -0
- package/src/dnd/utils.ts +294 -0
- package/src/focus/FocusScope.tsx +408 -0
- package/src/focus/createAutoFocus.ts +321 -0
- package/src/focus/createFocusRestore.ts +313 -0
- package/src/focus/createVirtualFocus.ts +396 -0
- package/src/focus/index.ts +35 -0
- package/src/form/createFormReset.ts +51 -0
- package/src/form/createFormValidation.ts +224 -0
- package/src/form/index.ts +11 -0
- package/src/grid/GridKeyboardDelegate.ts +429 -0
- package/src/grid/createGrid.ts +261 -0
- package/src/grid/createGridCell.ts +182 -0
- package/src/grid/createGridRow.ts +153 -0
- package/src/grid/index.ts +18 -0
- package/src/grid/types.ts +133 -0
- package/src/gridlist/createGridList.ts +185 -0
- package/src/gridlist/createGridListItem.ts +180 -0
- package/src/gridlist/createGridListSelectionCheckbox.ts +59 -0
- package/src/gridlist/index.ts +16 -0
- package/src/gridlist/types.ts +81 -0
- package/src/i18n/NumberFormatter.ts +266 -0
- package/src/i18n/createCollator.ts +79 -0
- package/src/i18n/createDateFormatter.ts +83 -0
- package/src/i18n/createFilter.ts +131 -0
- package/src/i18n/createNumberFormatter.ts +52 -0
- package/src/i18n/createStringFormatter.ts +87 -0
- package/src/i18n/index.ts +40 -0
- package/src/i18n/locale.tsx +188 -0
- package/src/i18n/utils.ts +99 -0
- package/src/index.ts +670 -0
- package/src/interactions/FocusableProvider.tsx +44 -0
- package/src/interactions/PressEvent.ts +126 -0
- package/src/interactions/createFocus.ts +163 -0
- package/src/interactions/createFocusRing.ts +89 -0
- package/src/interactions/createFocusWithin.ts +206 -0
- package/src/interactions/createFocusable.ts +168 -0
- package/src/interactions/createHover.ts +254 -0
- package/src/interactions/createInteractionModality.ts +424 -0
- package/src/interactions/createKeyboard.ts +82 -0
- package/src/interactions/createLongPress.ts +174 -0
- package/src/interactions/createMove.ts +289 -0
- package/src/interactions/createPress.ts +834 -0
- package/src/interactions/index.ts +78 -0
- package/src/label/createField.ts +145 -0
- package/src/label/createLabel.ts +117 -0
- package/src/label/createLabels.ts +50 -0
- package/src/label/index.ts +19 -0
- package/src/landmark/createLandmark.ts +377 -0
- package/src/landmark/index.ts +8 -0
- package/src/link/createLink.ts +182 -0
- package/src/link/index.ts +1 -0
- package/src/listbox/createListBox.ts +269 -0
- package/src/listbox/createOption.ts +151 -0
- package/src/listbox/index.ts +12 -0
- package/src/live-announcer/announce.ts +322 -0
- package/src/live-announcer/index.ts +9 -0
- package/src/menu/createMenu.ts +396 -0
- package/src/menu/createMenuItem.ts +149 -0
- package/src/menu/createMenuTrigger.ts +88 -0
- package/src/menu/index.ts +18 -0
- package/src/meter/createMeter.ts +75 -0
- package/src/meter/index.ts +1 -0
- package/src/numberfield/createNumberField.ts +268 -0
- package/src/numberfield/index.ts +5 -0
- package/src/overlays/ariaHideOutside.ts +219 -0
- package/src/overlays/createInteractOutside.ts +149 -0
- package/src/overlays/createModal.tsx +202 -0
- package/src/overlays/createOverlay.ts +155 -0
- package/src/overlays/createOverlayTrigger.ts +85 -0
- package/src/overlays/createPreventScroll.ts +266 -0
- package/src/overlays/index.ts +44 -0
- package/src/popover/calculatePosition.ts +766 -0
- package/src/popover/createOverlayPosition.ts +356 -0
- package/src/popover/createPopover.ts +170 -0
- package/src/popover/index.ts +24 -0
- package/src/progress/createProgressBar.ts +128 -0
- package/src/progress/index.ts +5 -0
- package/src/radio/createRadio.ts +287 -0
- package/src/radio/createRadioGroup.ts +189 -0
- package/src/radio/createRadioGroupState.ts +201 -0
- package/src/radio/index.ts +23 -0
- package/src/searchfield/createSearchField.ts +186 -0
- package/src/searchfield/index.ts +2 -0
- package/src/select/createHiddenSelect.tsx +236 -0
- package/src/select/createSelect.ts +395 -0
- package/src/select/index.ts +14 -0
- package/src/selection/createTypeSelect.ts +201 -0
- package/src/selection/index.ts +6 -0
- package/src/separator/createSeparator.ts +82 -0
- package/src/separator/index.ts +6 -0
- package/src/slider/createSlider.ts +349 -0
- package/src/slider/index.ts +2 -0
- package/src/ssr/index.tsx +370 -0
- package/src/switch/createSwitch.ts +70 -0
- package/src/switch/index.ts +1 -0
- package/src/table/createTable.ts +526 -0
- package/src/table/createTableCell.ts +147 -0
- package/src/table/createTableColumnHeader.ts +115 -0
- package/src/table/createTableHeaderRow.ts +40 -0
- package/src/table/createTableRow.ts +155 -0
- package/src/table/createTableRowGroup.ts +32 -0
- package/src/table/createTableSelectAllCheckbox.ts +73 -0
- package/src/table/createTableSelectionCheckbox.ts +59 -0
- package/src/table/index.ts +30 -0
- package/src/table/types.ts +165 -0
- package/src/tabs/createTabs.ts +472 -0
- package/src/tabs/index.ts +14 -0
- package/src/tag/createTag.ts +194 -0
- package/src/tag/createTagGroup.ts +154 -0
- package/src/tag/index.ts +12 -0
- package/src/textfield/createTextField.ts +198 -0
- package/src/textfield/index.ts +5 -0
- package/src/toast/createToast.ts +118 -0
- package/src/toast/createToastRegion.ts +100 -0
- package/src/toast/index.ts +11 -0
- package/src/toggle/createToggle.ts +223 -0
- package/src/toggle/createToggleState.ts +94 -0
- package/src/toggle/index.ts +7 -0
- package/src/toolbar/createToolbar.ts +369 -0
- package/src/toolbar/index.ts +6 -0
- package/src/tooltip/createTooltip.ts +79 -0
- package/src/tooltip/createTooltipTrigger.ts +222 -0
- package/src/tooltip/index.ts +6 -0
- package/src/tree/createTree.ts +246 -0
- package/src/tree/createTreeItem.ts +233 -0
- package/src/tree/createTreeSelectionCheckbox.ts +68 -0
- package/src/tree/index.ts +16 -0
- package/src/tree/types.ts +87 -0
- package/src/utils/createDescription.ts +137 -0
- package/src/utils/dom.ts +327 -0
- package/src/utils/env.ts +54 -0
- package/src/utils/events.ts +106 -0
- package/src/utils/filterDOMProps.ts +116 -0
- package/src/utils/focus.ts +151 -0
- package/src/utils/geometry.ts +115 -0
- package/src/utils/globalListeners.ts +142 -0
- package/src/utils/index.ts +80 -0
- package/src/utils/mergeProps.ts +52 -0
- package/src/utils/platform.ts +52 -0
- package/src/utils/reactivity.ts +36 -0
- package/src/utils/textSelection.ts +114 -0
- package/src/visually-hidden/createVisuallyHidden.ts +124 -0
- package/src/visually-hidden/index.ts +6 -0
- package/dist/index.jsx +0 -15845
- package/dist/index.jsx.map +0 -7
|
@@ -0,0 +1,321 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* createDrop - ARIA hook for drop operations.
|
|
3
|
+
*
|
|
4
|
+
* Provides accessibility props for drop target elements with support for
|
|
5
|
+
* mouse, touch, and keyboard interactions.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { createMemo, type Accessor } from 'solid-js';
|
|
9
|
+
import { createDropState } from '@proyecto-viviana/solid-stately';
|
|
10
|
+
import type { AriaDropOptions, DropAria } from './types';
|
|
11
|
+
import {
|
|
12
|
+
readFromDataTransfer,
|
|
13
|
+
DragTypesImpl,
|
|
14
|
+
DROP_OPERATION,
|
|
15
|
+
DROP_OPERATION_ALLOWED,
|
|
16
|
+
DROP_OPERATION_TO_DROP_EFFECT,
|
|
17
|
+
DROP_EFFECT_TO_DROP_OPERATION,
|
|
18
|
+
setGlobalDropEffect,
|
|
19
|
+
getGlobalAllowedDropOperations,
|
|
20
|
+
} from './utils';
|
|
21
|
+
import type { DropOperation } from '@proyecto-viviana/solid-stately';
|
|
22
|
+
|
|
23
|
+
const DROP_ACTIVATE_TIMEOUT = 800;
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Creates ARIA props for a drop target element.
|
|
27
|
+
*
|
|
28
|
+
* @param props - Accessor returning drop options
|
|
29
|
+
* @returns Drop ARIA props and state
|
|
30
|
+
*/
|
|
31
|
+
export function createDrop(props: Accessor<AriaDropOptions>): DropAria {
|
|
32
|
+
const getProps = createMemo(() => props());
|
|
33
|
+
|
|
34
|
+
// Create drop state
|
|
35
|
+
const state = createDropState(() => ({
|
|
36
|
+
getDropOperation: getProps().getDropOperation,
|
|
37
|
+
onDropEnter: getProps().onDropEnter,
|
|
38
|
+
onDropMove: getProps().onDropMove,
|
|
39
|
+
onDropActivate: getProps().onDropActivate,
|
|
40
|
+
onDropExit: getProps().onDropExit,
|
|
41
|
+
onDrop: getProps().onDrop,
|
|
42
|
+
isDisabled: getProps().isDisabled,
|
|
43
|
+
}));
|
|
44
|
+
|
|
45
|
+
// Track internal state
|
|
46
|
+
let x = 0;
|
|
47
|
+
let y = 0;
|
|
48
|
+
let dragOverElements = new Set<Element>();
|
|
49
|
+
let dropEffect: DataTransfer['dropEffect'] = 'none';
|
|
50
|
+
let allowedOperations = DROP_OPERATION.all;
|
|
51
|
+
let dropActivateTimer: ReturnType<typeof setTimeout> | undefined;
|
|
52
|
+
|
|
53
|
+
const fireDropEnter = (e: DragEvent) => {
|
|
54
|
+
const rect = (e.currentTarget as HTMLElement).getBoundingClientRect();
|
|
55
|
+
state.enterTarget(e.clientX - rect.x, e.clientY - rect.y);
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
const fireDropExit = (e: DragEvent) => {
|
|
59
|
+
const rect = (e.currentTarget as HTMLElement).getBoundingClientRect();
|
|
60
|
+
state.exitTarget(e.clientX - rect.x, e.clientY - rect.y);
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
const getAllowedOperations = (e: DragEvent): number => {
|
|
64
|
+
let allowed = DROP_OPERATION_ALLOWED[e.dataTransfer?.effectAllowed ?? 'none'] ?? DROP_OPERATION.none;
|
|
65
|
+
|
|
66
|
+
// Use global allowed operations if set (for internal drags)
|
|
67
|
+
const globalAllowed = getGlobalAllowedDropOperations();
|
|
68
|
+
if (globalAllowed) {
|
|
69
|
+
allowed &= globalAllowed;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Handle modifier keys for operation switching
|
|
73
|
+
let modifierAllowed = DROP_OPERATION.none;
|
|
74
|
+
|
|
75
|
+
// macOS: Alt=copy, Ctrl=link, Cmd=move
|
|
76
|
+
// Windows/Linux: Alt=link, Shift=move, Ctrl=copy
|
|
77
|
+
const isMac = typeof navigator !== 'undefined' && /mac/i.test(navigator.platform);
|
|
78
|
+
|
|
79
|
+
if (isMac) {
|
|
80
|
+
if (e.altKey) modifierAllowed |= DROP_OPERATION.copy;
|
|
81
|
+
if (e.ctrlKey) modifierAllowed |= DROP_OPERATION.link;
|
|
82
|
+
if (e.metaKey) modifierAllowed |= DROP_OPERATION.move;
|
|
83
|
+
} else {
|
|
84
|
+
if (e.altKey) modifierAllowed |= DROP_OPERATION.link;
|
|
85
|
+
if (e.shiftKey) modifierAllowed |= DROP_OPERATION.move;
|
|
86
|
+
if (e.ctrlKey) modifierAllowed |= DROP_OPERATION.copy;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (modifierAllowed) {
|
|
90
|
+
return allowed & modifierAllowed;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return allowed;
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
const allowedOperationsToArray = (ops: number): DropOperation[] => {
|
|
97
|
+
const result: DropOperation[] = [];
|
|
98
|
+
if (ops & DROP_OPERATION.move) result.push('move');
|
|
99
|
+
if (ops & DROP_OPERATION.copy) result.push('copy');
|
|
100
|
+
if (ops & DROP_OPERATION.link) result.push('link');
|
|
101
|
+
return result;
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
const getDropOperationForAllowed = (allowed: number, operation: DropOperation): DropOperation => {
|
|
105
|
+
const op = DROP_OPERATION[operation];
|
|
106
|
+
return allowed & op ? operation : 'cancel';
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
const onDragEnter = (e: DragEvent) => {
|
|
110
|
+
e.preventDefault();
|
|
111
|
+
e.stopPropagation();
|
|
112
|
+
|
|
113
|
+
dragOverElements.add(e.target as Element);
|
|
114
|
+
if (dragOverElements.size > 1) {
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const p = getProps();
|
|
119
|
+
const allowedOpsBits = getAllowedOperations(e);
|
|
120
|
+
const allowedOps = allowedOperationsToArray(allowedOpsBits);
|
|
121
|
+
let dropOp: DropOperation = allowedOps[0] ?? 'cancel';
|
|
122
|
+
|
|
123
|
+
if (typeof p.getDropOperation === 'function' && e.dataTransfer) {
|
|
124
|
+
const types = new DragTypesImpl(e.dataTransfer);
|
|
125
|
+
dropOp = getDropOperationForAllowed(
|
|
126
|
+
allowedOpsBits,
|
|
127
|
+
p.getDropOperation(types, allowedOps)
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (typeof p.getDropOperationForPoint === 'function' && e.dataTransfer) {
|
|
132
|
+
const types = new DragTypesImpl(e.dataTransfer);
|
|
133
|
+
const rect = (e.currentTarget as HTMLElement).getBoundingClientRect();
|
|
134
|
+
dropOp = getDropOperationForAllowed(
|
|
135
|
+
allowedOpsBits,
|
|
136
|
+
p.getDropOperationForPoint(types, allowedOps, e.clientX - rect.x, e.clientY - rect.y)
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
x = e.clientX;
|
|
141
|
+
y = e.clientY;
|
|
142
|
+
allowedOperations = allowedOpsBits;
|
|
143
|
+
dropEffect = (DROP_OPERATION_TO_DROP_EFFECT[dropOp] || 'none') as DataTransfer['dropEffect'];
|
|
144
|
+
|
|
145
|
+
if (e.dataTransfer) {
|
|
146
|
+
e.dataTransfer.dropEffect = dropEffect;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
if (dropOp !== 'cancel') {
|
|
150
|
+
fireDropEnter(e);
|
|
151
|
+
}
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
const onDragOver = (e: DragEvent) => {
|
|
155
|
+
e.preventDefault();
|
|
156
|
+
e.stopPropagation();
|
|
157
|
+
|
|
158
|
+
const allowedOpsBits = getAllowedOperations(e);
|
|
159
|
+
|
|
160
|
+
// Skip if position and operations haven't changed
|
|
161
|
+
if (e.clientX === x && e.clientY === y && allowedOpsBits === allowedOperations) {
|
|
162
|
+
if (e.dataTransfer) {
|
|
163
|
+
e.dataTransfer.dropEffect = dropEffect;
|
|
164
|
+
}
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
x = e.clientX;
|
|
169
|
+
y = e.clientY;
|
|
170
|
+
|
|
171
|
+
const prevDropEffect = dropEffect;
|
|
172
|
+
const p = getProps();
|
|
173
|
+
|
|
174
|
+
// Update drop effect if allowed operations changed
|
|
175
|
+
if (allowedOpsBits !== allowedOperations) {
|
|
176
|
+
const allowedOps = allowedOperationsToArray(allowedOpsBits);
|
|
177
|
+
let dropOp: DropOperation = allowedOps[0] ?? 'cancel';
|
|
178
|
+
|
|
179
|
+
if (typeof p.getDropOperation === 'function' && e.dataTransfer) {
|
|
180
|
+
const types = new DragTypesImpl(e.dataTransfer);
|
|
181
|
+
dropOp = getDropOperationForAllowed(
|
|
182
|
+
allowedOpsBits,
|
|
183
|
+
p.getDropOperation(types, allowedOps)
|
|
184
|
+
);
|
|
185
|
+
}
|
|
186
|
+
dropEffect = (DROP_OPERATION_TO_DROP_EFFECT[dropOp] || 'none') as DataTransfer['dropEffect'];
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// Check point-specific operation
|
|
190
|
+
if (typeof p.getDropOperationForPoint === 'function' && e.dataTransfer) {
|
|
191
|
+
const types = new DragTypesImpl(e.dataTransfer);
|
|
192
|
+
const rect = (e.currentTarget as HTMLElement).getBoundingClientRect();
|
|
193
|
+
const dropOp = getDropOperationForAllowed(
|
|
194
|
+
allowedOpsBits,
|
|
195
|
+
p.getDropOperationForPoint(types, allowedOperationsToArray(allowedOpsBits), x - rect.x, y - rect.y)
|
|
196
|
+
);
|
|
197
|
+
dropEffect = (DROP_OPERATION_TO_DROP_EFFECT[dropOp] || 'none') as DataTransfer['dropEffect'];
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
allowedOperations = allowedOpsBits;
|
|
201
|
+
|
|
202
|
+
if (e.dataTransfer) {
|
|
203
|
+
e.dataTransfer.dropEffect = dropEffect;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// Fire enter/exit events on drop effect change
|
|
207
|
+
if (dropEffect === 'none' && prevDropEffect !== 'none') {
|
|
208
|
+
fireDropExit(e);
|
|
209
|
+
} else if (dropEffect !== 'none' && prevDropEffect === 'none') {
|
|
210
|
+
fireDropEnter(e);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// Fire move event
|
|
214
|
+
if (dropEffect !== 'none') {
|
|
215
|
+
const rect = (e.currentTarget as HTMLElement).getBoundingClientRect();
|
|
216
|
+
state.moveInTarget(x - rect.x, y - rect.y);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// Handle drop activate timer
|
|
220
|
+
clearTimeout(dropActivateTimer);
|
|
221
|
+
|
|
222
|
+
if (typeof p.onDropActivate === 'function' && dropEffect !== 'none') {
|
|
223
|
+
const rect = (e.currentTarget as HTMLElement).getBoundingClientRect();
|
|
224
|
+
const activateX = x - rect.x;
|
|
225
|
+
const activateY = y - rect.y;
|
|
226
|
+
dropActivateTimer = setTimeout(() => {
|
|
227
|
+
state.activateTarget(activateX, activateY);
|
|
228
|
+
}, DROP_ACTIVATE_TIMEOUT);
|
|
229
|
+
}
|
|
230
|
+
};
|
|
231
|
+
|
|
232
|
+
const onDragLeave = (e: DragEvent) => {
|
|
233
|
+
e.preventDefault();
|
|
234
|
+
e.stopPropagation();
|
|
235
|
+
|
|
236
|
+
// Track drag over elements (WebKit workaround for relatedTarget being null)
|
|
237
|
+
dragOverElements.delete(e.target as Element);
|
|
238
|
+
|
|
239
|
+
// Remove elements no longer in DOM
|
|
240
|
+
for (const element of dragOverElements) {
|
|
241
|
+
if (!e.currentTarget || !(e.currentTarget as Element).contains(element)) {
|
|
242
|
+
dragOverElements.delete(element);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
if (dragOverElements.size > 0) {
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
if (dropEffect !== 'none') {
|
|
251
|
+
fireDropExit(e);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
clearTimeout(dropActivateTimer);
|
|
255
|
+
};
|
|
256
|
+
|
|
257
|
+
const onDropHandler = (e: DragEvent) => {
|
|
258
|
+
e.preventDefault();
|
|
259
|
+
e.stopPropagation();
|
|
260
|
+
|
|
261
|
+
// Track drop effect globally for Chrome Android
|
|
262
|
+
setGlobalDropEffect(dropEffect);
|
|
263
|
+
|
|
264
|
+
const p = getProps();
|
|
265
|
+
if (typeof p.onDrop === 'function' && e.dataTransfer) {
|
|
266
|
+
const items = readFromDataTransfer(e.dataTransfer);
|
|
267
|
+
const dropOperation = DROP_EFFECT_TO_DROP_OPERATION[dropEffect];
|
|
268
|
+
const rect = (e.currentTarget as HTMLElement).getBoundingClientRect();
|
|
269
|
+
|
|
270
|
+
state.drop(e.clientX - rect.x, e.clientY - rect.y, items, dropOperation);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
dragOverElements.clear();
|
|
274
|
+
fireDropExit(e);
|
|
275
|
+
clearTimeout(dropActivateTimer);
|
|
276
|
+
};
|
|
277
|
+
|
|
278
|
+
const dropProps = createMemo(() => {
|
|
279
|
+
const p = getProps();
|
|
280
|
+
|
|
281
|
+
if (p.isDisabled) {
|
|
282
|
+
return {};
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
const baseProps: Record<string, unknown> = {
|
|
286
|
+
onDragEnter,
|
|
287
|
+
onDragOver,
|
|
288
|
+
onDragLeave,
|
|
289
|
+
onDrop: onDropHandler,
|
|
290
|
+
};
|
|
291
|
+
|
|
292
|
+
return baseProps;
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
const dropButtonProps = createMemo(() => {
|
|
296
|
+
const p = getProps();
|
|
297
|
+
|
|
298
|
+
if (p.isDisabled) {
|
|
299
|
+
return {
|
|
300
|
+
disabled: true,
|
|
301
|
+
};
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
return {
|
|
305
|
+
type: 'button' as const,
|
|
306
|
+
'aria-label': 'Drop',
|
|
307
|
+
};
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
return {
|
|
311
|
+
get dropProps() {
|
|
312
|
+
return dropProps() as DropAria['dropProps'];
|
|
313
|
+
},
|
|
314
|
+
get isDropTarget() {
|
|
315
|
+
return state.isDropTarget;
|
|
316
|
+
},
|
|
317
|
+
get dropButtonProps() {
|
|
318
|
+
return dropButtonProps() as DropAria['dropButtonProps'];
|
|
319
|
+
},
|
|
320
|
+
};
|
|
321
|
+
}
|
|
@@ -0,0 +1,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 { 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
|
+
}
|