@proyecto-viviana/solidaria 0.2.2 → 0.2.3
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,209 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* createDrag - ARIA hook for drag operations.
|
|
3
|
+
*
|
|
4
|
+
* Provides accessibility props for draggable elements with support for
|
|
5
|
+
* mouse, touch, and keyboard interactions.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { createMemo, type Accessor } from 'solid-js';
|
|
9
|
+
import { createDragState } from '@proyecto-viviana/solid-stately';
|
|
10
|
+
import type { AriaDragOptions, DragAria } from './types';
|
|
11
|
+
import {
|
|
12
|
+
writeToDataTransfer,
|
|
13
|
+
DROP_OPERATION,
|
|
14
|
+
EFFECT_ALLOWED,
|
|
15
|
+
DROP_EFFECT_TO_DROP_OPERATION,
|
|
16
|
+
setGlobalAllowedDropOperations,
|
|
17
|
+
setGlobalDropEffect,
|
|
18
|
+
getGlobalDropEffect,
|
|
19
|
+
} from './utils';
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Creates ARIA props for a draggable element.
|
|
23
|
+
*
|
|
24
|
+
* @param props - Accessor returning drag options
|
|
25
|
+
* @returns Drag ARIA props and state
|
|
26
|
+
*/
|
|
27
|
+
export function createDrag(props: Accessor<AriaDragOptions>): DragAria {
|
|
28
|
+
const getProps = createMemo(() => props());
|
|
29
|
+
|
|
30
|
+
// Create drag state
|
|
31
|
+
const state = createDragState(() => ({
|
|
32
|
+
getItems: getProps().getItems,
|
|
33
|
+
getAllowedDropOperations: getProps().getAllowedDropOperations,
|
|
34
|
+
onDragStart: getProps().onDragStart,
|
|
35
|
+
onDragMove: getProps().onDragMove,
|
|
36
|
+
onDragEnd: getProps().onDragEnd,
|
|
37
|
+
isDisabled: getProps().isDisabled,
|
|
38
|
+
hasDragButton: getProps().hasDragButton,
|
|
39
|
+
preview: getProps().preview,
|
|
40
|
+
}));
|
|
41
|
+
|
|
42
|
+
// Track position for drag move
|
|
43
|
+
let lastX = 0;
|
|
44
|
+
let lastY = 0;
|
|
45
|
+
|
|
46
|
+
const onDragStart = (e: DragEvent) => {
|
|
47
|
+
if (e.defaultPrevented) return;
|
|
48
|
+
e.stopPropagation();
|
|
49
|
+
|
|
50
|
+
const p = getProps();
|
|
51
|
+
|
|
52
|
+
// Call state handler
|
|
53
|
+
state.startDrag(e.clientX, e.clientY);
|
|
54
|
+
|
|
55
|
+
// Get items and write to data transfer
|
|
56
|
+
const items = state.getItems();
|
|
57
|
+
e.dataTransfer?.clearData?.();
|
|
58
|
+
if (e.dataTransfer) {
|
|
59
|
+
writeToDataTransfer(e.dataTransfer, items);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Set allowed drop operations
|
|
63
|
+
let allowed = DROP_OPERATION.all;
|
|
64
|
+
const allowedOps = state.getAllowedDropOperations();
|
|
65
|
+
if (allowedOps.length > 0) {
|
|
66
|
+
allowed = DROP_OPERATION.none;
|
|
67
|
+
for (const op of allowedOps) {
|
|
68
|
+
allowed |= DROP_OPERATION[op] || DROP_OPERATION.none;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
setGlobalAllowedDropOperations(allowed);
|
|
73
|
+
const effectAllowed = EFFECT_ALLOWED[allowed] || 'none';
|
|
74
|
+
if (e.dataTransfer) {
|
|
75
|
+
e.dataTransfer.effectAllowed =
|
|
76
|
+
(effectAllowed === 'cancel' ? 'none' : effectAllowed) as DataTransfer['effectAllowed'];
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Handle custom preview
|
|
80
|
+
if (typeof p.preview?.current === 'function' && e.dataTransfer) {
|
|
81
|
+
p.preview.current(items, (node, userX, userY) => {
|
|
82
|
+
if (!node || !e.dataTransfer) return;
|
|
83
|
+
|
|
84
|
+
const size = node.getBoundingClientRect();
|
|
85
|
+
const rect = (e.currentTarget as HTMLElement).getBoundingClientRect();
|
|
86
|
+
let defaultX = e.clientX - rect.x;
|
|
87
|
+
let defaultY = e.clientY - rect.y;
|
|
88
|
+
|
|
89
|
+
if (defaultX > size.width || defaultY > size.height) {
|
|
90
|
+
defaultX = size.width / 2;
|
|
91
|
+
defaultY = size.height / 2;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
let offsetX = typeof userX === 'number' ? userX : defaultX;
|
|
95
|
+
let offsetY = typeof userY === 'number' ? userY : defaultY;
|
|
96
|
+
|
|
97
|
+
offsetX = Math.max(0, Math.min(offsetX, size.width));
|
|
98
|
+
offsetY = Math.max(0, Math.min(offsetY, size.height));
|
|
99
|
+
|
|
100
|
+
e.dataTransfer.setDragImage(node, offsetX, offsetY);
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
lastX = e.clientX;
|
|
105
|
+
lastY = e.clientY;
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
const onDrag = (e: DragEvent) => {
|
|
109
|
+
e.stopPropagation();
|
|
110
|
+
|
|
111
|
+
if (e.clientX === lastX && e.clientY === lastY) {
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
state.moveDrag(e.clientX, e.clientY);
|
|
116
|
+
|
|
117
|
+
lastX = e.clientX;
|
|
118
|
+
lastY = e.clientY;
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
const onDragEnd = (e: DragEvent) => {
|
|
122
|
+
e.stopPropagation();
|
|
123
|
+
|
|
124
|
+
let dropEffect: string = e.dataTransfer?.dropEffect ?? 'none';
|
|
125
|
+
// Chrome Android fix - use global drop effect
|
|
126
|
+
if (getGlobalDropEffect()) {
|
|
127
|
+
dropEffect = getGlobalDropEffect()!;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const dropOperation = DROP_EFFECT_TO_DROP_OPERATION[dropEffect];
|
|
131
|
+
state.endDrag(e.clientX, e.clientY, dropOperation);
|
|
132
|
+
|
|
133
|
+
setGlobalAllowedDropOperations(DROP_OPERATION.none);
|
|
134
|
+
setGlobalDropEffect(undefined);
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
// Keyboard/screen reader drag initiation
|
|
138
|
+
const onKeyDown = (e: KeyboardEvent) => {
|
|
139
|
+
if (e.key === 'Enter' && e.target === e.currentTarget) {
|
|
140
|
+
e.preventDefault();
|
|
141
|
+
e.stopPropagation();
|
|
142
|
+
}
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
const onKeyUp = (e: KeyboardEvent) => {
|
|
146
|
+
if (e.key === 'Enter' && e.target === e.currentTarget) {
|
|
147
|
+
e.preventDefault();
|
|
148
|
+
e.stopPropagation();
|
|
149
|
+
// Would initiate keyboard-based drag mode
|
|
150
|
+
// This is a simplified version - full implementation needs DragManager
|
|
151
|
+
const rect = (e.target as HTMLElement).getBoundingClientRect();
|
|
152
|
+
state.startDrag(rect.x + rect.width / 2, rect.y + rect.height / 2);
|
|
153
|
+
}
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
const dragProps = createMemo(() => {
|
|
157
|
+
const p = getProps();
|
|
158
|
+
|
|
159
|
+
if (p.isDisabled) {
|
|
160
|
+
return {
|
|
161
|
+
draggable: false as const,
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const baseProps: Record<string, unknown> = {
|
|
166
|
+
draggable: true as const,
|
|
167
|
+
onDragStart,
|
|
168
|
+
onDrag,
|
|
169
|
+
onDragEnd,
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
// Add keyboard handlers if no separate drag button
|
|
173
|
+
if (!p.hasDragButton) {
|
|
174
|
+
baseProps.onKeyDown = onKeyDown;
|
|
175
|
+
baseProps.onKeyUp = onKeyUp;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
return baseProps;
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
const dragButtonProps = createMemo(() => {
|
|
182
|
+
const p = getProps();
|
|
183
|
+
|
|
184
|
+
if (p.isDisabled) {
|
|
185
|
+
return {
|
|
186
|
+
disabled: true,
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
return {
|
|
191
|
+
type: 'button' as const,
|
|
192
|
+
'aria-label': 'Drag',
|
|
193
|
+
onKeyDown,
|
|
194
|
+
onKeyUp,
|
|
195
|
+
};
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
return {
|
|
199
|
+
get dragProps() {
|
|
200
|
+
return dragProps() as DragAria['dragProps'];
|
|
201
|
+
},
|
|
202
|
+
get dragButtonProps() {
|
|
203
|
+
return dragButtonProps() as DragAria['dragButtonProps'];
|
|
204
|
+
},
|
|
205
|
+
get isDragging() {
|
|
206
|
+
return state.isDragging;
|
|
207
|
+
},
|
|
208
|
+
};
|
|
209
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* createDraggableCollection - ARIA hook for draggable collection items.
|
|
3
|
+
*
|
|
4
|
+
* Provides accessibility support for dragging items from a collection
|
|
5
|
+
* component like ListBox, GridList, or Table.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { createMemo, createEffect, onCleanup, type Accessor } from 'solid-js';
|
|
9
|
+
import type { DraggableCollectionState } from '@proyecto-viviana/solid-stately';
|
|
10
|
+
|
|
11
|
+
// Global state for tracking the dragging collection
|
|
12
|
+
let globalDraggingCollectionRef: HTMLElement | null = null;
|
|
13
|
+
|
|
14
|
+
export function setGlobalDraggingCollectionRef(ref: HTMLElement | null): void {
|
|
15
|
+
globalDraggingCollectionRef = ref;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function getGlobalDraggingCollectionRef(): HTMLElement | null {
|
|
19
|
+
return globalDraggingCollectionRef;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface DraggableCollectionOptions {
|
|
23
|
+
/** Reference to the collection element. */
|
|
24
|
+
ref: Accessor<HTMLElement | null>;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface DraggableCollectionAria {
|
|
28
|
+
/** The draggable collection state. */
|
|
29
|
+
state: DraggableCollectionState;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Creates ARIA support for a draggable collection.
|
|
34
|
+
*
|
|
35
|
+
* @param _options - Collection options
|
|
36
|
+
* @param state - Draggable collection state
|
|
37
|
+
* @returns Draggable collection ARIA result
|
|
38
|
+
*/
|
|
39
|
+
export function createDraggableCollection(
|
|
40
|
+
options: DraggableCollectionOptions,
|
|
41
|
+
state: DraggableCollectionState
|
|
42
|
+
): DraggableCollectionAria {
|
|
43
|
+
const ref = createMemo(() => options.ref());
|
|
44
|
+
|
|
45
|
+
// Track dragging state globally
|
|
46
|
+
createEffect(() => {
|
|
47
|
+
const currentRef = ref();
|
|
48
|
+
if (state.draggingKeys.size > 0 && globalDraggingCollectionRef !== currentRef) {
|
|
49
|
+
setGlobalDraggingCollectionRef(currentRef);
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
// Clean up on unmount
|
|
54
|
+
onCleanup(() => {
|
|
55
|
+
if (globalDraggingCollectionRef === ref()) {
|
|
56
|
+
setGlobalDraggingCollectionRef(null);
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
return {
|
|
61
|
+
state,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* createDraggableItem - ARIA hook for draggable items within a collection.
|
|
3
|
+
*
|
|
4
|
+
* Provides accessibility props for items that can be dragged from a collection.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { createMemo, type Accessor } from 'solid-js';
|
|
8
|
+
import type { JSX } from 'solid-js';
|
|
9
|
+
import type {
|
|
10
|
+
DraggableCollectionState,
|
|
11
|
+
DragPreviewRenderer,
|
|
12
|
+
} from '@proyecto-viviana/solid-stately';
|
|
13
|
+
import {
|
|
14
|
+
writeToDataTransfer,
|
|
15
|
+
DROP_OPERATION,
|
|
16
|
+
EFFECT_ALLOWED,
|
|
17
|
+
DROP_EFFECT_TO_DROP_OPERATION,
|
|
18
|
+
setGlobalAllowedDropOperations,
|
|
19
|
+
setGlobalDropEffect,
|
|
20
|
+
getGlobalDropEffect,
|
|
21
|
+
} from './utils';
|
|
22
|
+
|
|
23
|
+
export interface DraggableItemOptions {
|
|
24
|
+
/** The unique key of the item. */
|
|
25
|
+
key: string | number;
|
|
26
|
+
/** Whether the item has a separate drag button affordance. */
|
|
27
|
+
hasDragButton?: boolean;
|
|
28
|
+
/** Whether this item is disabled for dragging. */
|
|
29
|
+
isDisabled?: boolean;
|
|
30
|
+
/** Preview renderer function ref. */
|
|
31
|
+
preview?: { current: DragPreviewRenderer | null };
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface DraggableItemAria {
|
|
35
|
+
/** Props for the draggable item element. */
|
|
36
|
+
dragProps: JSX.HTMLAttributes<HTMLElement>;
|
|
37
|
+
/** Props for the explicit drag button affordance, if any. */
|
|
38
|
+
dragButtonProps: JSX.ButtonHTMLAttributes<HTMLButtonElement>;
|
|
39
|
+
/** Whether the item is currently being dragged. */
|
|
40
|
+
isDragging: boolean;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Creates ARIA props for a draggable item within a collection.
|
|
45
|
+
*
|
|
46
|
+
* @param options - Accessor returning item options
|
|
47
|
+
* @param state - Draggable collection state
|
|
48
|
+
* @returns Draggable item ARIA props
|
|
49
|
+
*/
|
|
50
|
+
export function createDraggableItem(
|
|
51
|
+
options: Accessor<DraggableItemOptions>,
|
|
52
|
+
state: DraggableCollectionState
|
|
53
|
+
): DraggableItemAria {
|
|
54
|
+
const getOptions = createMemo(() => options());
|
|
55
|
+
|
|
56
|
+
// Track position for drag move
|
|
57
|
+
let lastX = 0;
|
|
58
|
+
let lastY = 0;
|
|
59
|
+
|
|
60
|
+
const isDragging = createMemo(() => {
|
|
61
|
+
const key = getOptions().key;
|
|
62
|
+
return state.draggingKeys.has(key);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
const getKeysForDrag = (): Set<string | number> => {
|
|
66
|
+
const { key } = getOptions();
|
|
67
|
+
// If the key is not selected, only drag that item
|
|
68
|
+
// If it is selected, drag all selected items
|
|
69
|
+
// For now, just return the single key
|
|
70
|
+
return new Set([key]);
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
const onDragStart = (e: DragEvent) => {
|
|
74
|
+
if (e.defaultPrevented) return;
|
|
75
|
+
e.stopPropagation();
|
|
76
|
+
|
|
77
|
+
const opts = getOptions();
|
|
78
|
+
if (opts.isDisabled || state.isDisabled) return;
|
|
79
|
+
|
|
80
|
+
const keys = getKeysForDrag();
|
|
81
|
+
|
|
82
|
+
// Start drag state
|
|
83
|
+
state.startDrag(keys, e.clientX, e.clientY);
|
|
84
|
+
|
|
85
|
+
// Get items and write to data transfer
|
|
86
|
+
const items = state.getItems(keys);
|
|
87
|
+
e.dataTransfer?.clearData?.();
|
|
88
|
+
if (e.dataTransfer) {
|
|
89
|
+
writeToDataTransfer(e.dataTransfer, items);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Set allowed drop operations
|
|
93
|
+
let allowed = DROP_OPERATION.all;
|
|
94
|
+
const allowedOps = state.getAllowedDropOperations();
|
|
95
|
+
if (allowedOps.length > 0) {
|
|
96
|
+
allowed = DROP_OPERATION.none;
|
|
97
|
+
for (const op of allowedOps) {
|
|
98
|
+
allowed |= DROP_OPERATION[op] || DROP_OPERATION.none;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
setGlobalAllowedDropOperations(allowed);
|
|
103
|
+
const effectAllowed = EFFECT_ALLOWED[allowed] || 'none';
|
|
104
|
+
if (e.dataTransfer) {
|
|
105
|
+
e.dataTransfer.effectAllowed =
|
|
106
|
+
(effectAllowed === 'cancel' ? 'none' : effectAllowed) as DataTransfer['effectAllowed'];
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Handle custom preview
|
|
110
|
+
if (typeof opts.preview?.current === 'function' && e.dataTransfer) {
|
|
111
|
+
opts.preview.current(items, (node, userX, userY) => {
|
|
112
|
+
if (!node || !e.dataTransfer) return;
|
|
113
|
+
|
|
114
|
+
const size = node.getBoundingClientRect();
|
|
115
|
+
const rect = (e.currentTarget as HTMLElement).getBoundingClientRect();
|
|
116
|
+
let defaultX = e.clientX - rect.x;
|
|
117
|
+
let defaultY = e.clientY - rect.y;
|
|
118
|
+
|
|
119
|
+
if (defaultX > size.width || defaultY > size.height) {
|
|
120
|
+
defaultX = size.width / 2;
|
|
121
|
+
defaultY = size.height / 2;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
let offsetX = typeof userX === 'number' ? userX : defaultX;
|
|
125
|
+
let offsetY = typeof userY === 'number' ? userY : defaultY;
|
|
126
|
+
|
|
127
|
+
offsetX = Math.max(0, Math.min(offsetX, size.width));
|
|
128
|
+
offsetY = Math.max(0, Math.min(offsetY, size.height));
|
|
129
|
+
|
|
130
|
+
e.dataTransfer.setDragImage(node, offsetX, offsetY);
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
lastX = e.clientX;
|
|
135
|
+
lastY = e.clientY;
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
const onDrag = (e: DragEvent) => {
|
|
139
|
+
e.stopPropagation();
|
|
140
|
+
|
|
141
|
+
if (e.clientX === lastX && e.clientY === lastY) {
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
state.moveDrag(e.clientX, e.clientY);
|
|
146
|
+
|
|
147
|
+
lastX = e.clientX;
|
|
148
|
+
lastY = e.clientY;
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
const onDragEnd = (e: DragEvent) => {
|
|
152
|
+
e.stopPropagation();
|
|
153
|
+
|
|
154
|
+
let dropEffect: string = e.dataTransfer?.dropEffect ?? 'none';
|
|
155
|
+
// Chrome Android fix - use global drop effect
|
|
156
|
+
if (getGlobalDropEffect()) {
|
|
157
|
+
dropEffect = getGlobalDropEffect()!;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const dropOperation = DROP_EFFECT_TO_DROP_OPERATION[dropEffect];
|
|
161
|
+
const isInternal = false; // Would check global state
|
|
162
|
+
state.endDrag(e.clientX, e.clientY, dropOperation, isInternal);
|
|
163
|
+
|
|
164
|
+
setGlobalAllowedDropOperations(DROP_OPERATION.none);
|
|
165
|
+
setGlobalDropEffect(undefined);
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
// Keyboard/screen reader drag initiation
|
|
169
|
+
const onKeyDown = (e: KeyboardEvent) => {
|
|
170
|
+
if (e.key === 'Enter' && e.target === e.currentTarget) {
|
|
171
|
+
e.preventDefault();
|
|
172
|
+
e.stopPropagation();
|
|
173
|
+
}
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
const onKeyUp = (e: KeyboardEvent) => {
|
|
177
|
+
if (e.key === 'Enter' && e.target === e.currentTarget) {
|
|
178
|
+
e.preventDefault();
|
|
179
|
+
e.stopPropagation();
|
|
180
|
+
|
|
181
|
+
const opts = getOptions();
|
|
182
|
+
if (opts.isDisabled || state.isDisabled) return;
|
|
183
|
+
|
|
184
|
+
const keys = getKeysForDrag();
|
|
185
|
+
const rect = (e.target as HTMLElement).getBoundingClientRect();
|
|
186
|
+
state.startDrag(keys, rect.x + rect.width / 2, rect.y + rect.height / 2);
|
|
187
|
+
}
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
const dragProps = createMemo(() => {
|
|
191
|
+
const opts = getOptions();
|
|
192
|
+
|
|
193
|
+
if (opts.isDisabled || state.isDisabled) {
|
|
194
|
+
return {
|
|
195
|
+
draggable: false as const,
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const baseProps: Record<string, unknown> = {
|
|
200
|
+
draggable: true as const,
|
|
201
|
+
onDragStart,
|
|
202
|
+
onDrag,
|
|
203
|
+
onDragEnd,
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
// Add keyboard handlers if no separate drag button
|
|
207
|
+
if (!opts.hasDragButton) {
|
|
208
|
+
baseProps.onKeyDown = onKeyDown;
|
|
209
|
+
baseProps.onKeyUp = onKeyUp;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
return baseProps;
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
const dragButtonProps = createMemo(() => {
|
|
216
|
+
const opts = getOptions();
|
|
217
|
+
|
|
218
|
+
if (opts.isDisabled || state.isDisabled) {
|
|
219
|
+
return {
|
|
220
|
+
disabled: true,
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
return {
|
|
225
|
+
type: 'button' as const,
|
|
226
|
+
'aria-label': 'Drag',
|
|
227
|
+
onKeyDown,
|
|
228
|
+
onKeyUp,
|
|
229
|
+
};
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
return {
|
|
233
|
+
get dragProps() {
|
|
234
|
+
return dragProps() as DraggableItemAria['dragProps'];
|
|
235
|
+
},
|
|
236
|
+
get dragButtonProps() {
|
|
237
|
+
return dragButtonProps() as DraggableItemAria['dragButtonProps'];
|
|
238
|
+
},
|
|
239
|
+
get isDragging() {
|
|
240
|
+
return isDragging();
|
|
241
|
+
},
|
|
242
|
+
};
|
|
243
|
+
}
|