@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,246 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* createTree - Provides accessibility for a tree component.
|
|
3
|
+
* Based on @react-aria/tree/useTree.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { createMemo, type Accessor } from 'solid-js';
|
|
7
|
+
import type { JSX } from 'solid-js';
|
|
8
|
+
import { createId } from '@proyecto-viviana/solid-stately';
|
|
9
|
+
import type { TreeState, TreeCollection, Key } from '@proyecto-viviana/solid-stately';
|
|
10
|
+
import type { AriaTreeProps, TreeAria } from './types';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Metadata stored for a tree instance.
|
|
14
|
+
*/
|
|
15
|
+
interface TreeData {
|
|
16
|
+
/** The generated ID for the tree. */
|
|
17
|
+
treeId: string;
|
|
18
|
+
/** Actions registered for the tree. */
|
|
19
|
+
actions: {
|
|
20
|
+
onAction?: (key: Key) => void;
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* WeakMap to store tree data for child components to access.
|
|
26
|
+
*/
|
|
27
|
+
const treeDataMap = new WeakMap<object, TreeData>();
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Gets the tree data for a given state.
|
|
31
|
+
*/
|
|
32
|
+
export function getTreeData<T extends object, C extends TreeCollection<T>>(
|
|
33
|
+
state: TreeState<T, C>
|
|
34
|
+
): TreeData | undefined {
|
|
35
|
+
return treeDataMap.get(state);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Creates accessibility props for a tree.
|
|
40
|
+
*/
|
|
41
|
+
export function createTree<T extends object, C extends TreeCollection<T> = TreeCollection<T>>(
|
|
42
|
+
props: Accessor<AriaTreeProps>,
|
|
43
|
+
state: Accessor<TreeState<T, C>>,
|
|
44
|
+
_ref: Accessor<HTMLDivElement | null>
|
|
45
|
+
): TreeAria {
|
|
46
|
+
// Generate a unique ID for the tree
|
|
47
|
+
const treeId = props().id ?? createId();
|
|
48
|
+
|
|
49
|
+
// Store tree data for child components
|
|
50
|
+
const treeData: TreeData = {
|
|
51
|
+
treeId,
|
|
52
|
+
actions: {
|
|
53
|
+
get onAction() {
|
|
54
|
+
return props().onAction;
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
// Store in WeakMap using the state as key
|
|
60
|
+
treeDataMap.set(state(), treeData);
|
|
61
|
+
|
|
62
|
+
// Handle keyboard navigation
|
|
63
|
+
const onKeyDown = (e: KeyboardEvent) => {
|
|
64
|
+
const s = state();
|
|
65
|
+
const p = props();
|
|
66
|
+
const collection = s.collection;
|
|
67
|
+
const focusedKey = s.focusedKey;
|
|
68
|
+
|
|
69
|
+
if (p.isDisabled) return;
|
|
70
|
+
|
|
71
|
+
switch (e.key) {
|
|
72
|
+
case 'ArrowDown': {
|
|
73
|
+
e.preventDefault();
|
|
74
|
+
if (focusedKey != null) {
|
|
75
|
+
const nextKey = collection.getKeyAfter(focusedKey);
|
|
76
|
+
if (nextKey != null) {
|
|
77
|
+
s.setFocusedKey(nextKey);
|
|
78
|
+
}
|
|
79
|
+
} else {
|
|
80
|
+
const firstKey = collection.getFirstKey();
|
|
81
|
+
if (firstKey != null) {
|
|
82
|
+
s.setFocusedKey(firstKey);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
break;
|
|
86
|
+
}
|
|
87
|
+
case 'ArrowUp': {
|
|
88
|
+
e.preventDefault();
|
|
89
|
+
if (focusedKey != null) {
|
|
90
|
+
const prevKey = collection.getKeyBefore(focusedKey);
|
|
91
|
+
if (prevKey != null) {
|
|
92
|
+
s.setFocusedKey(prevKey);
|
|
93
|
+
}
|
|
94
|
+
} else {
|
|
95
|
+
const lastKey = collection.getLastKey();
|
|
96
|
+
if (lastKey != null) {
|
|
97
|
+
s.setFocusedKey(lastKey);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
break;
|
|
101
|
+
}
|
|
102
|
+
case 'ArrowRight': {
|
|
103
|
+
e.preventDefault();
|
|
104
|
+
if (focusedKey != null) {
|
|
105
|
+
const node = collection.getItem(focusedKey);
|
|
106
|
+
if (node?.isExpandable) {
|
|
107
|
+
if (!s.isExpanded(focusedKey)) {
|
|
108
|
+
// Expand the node
|
|
109
|
+
s.expandKey(focusedKey);
|
|
110
|
+
} else {
|
|
111
|
+
// Move to first child
|
|
112
|
+
const children = [...collection.getChildren(focusedKey)];
|
|
113
|
+
if (children.length > 0) {
|
|
114
|
+
s.setFocusedKey(children[0].key);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
break;
|
|
120
|
+
}
|
|
121
|
+
case 'ArrowLeft': {
|
|
122
|
+
e.preventDefault();
|
|
123
|
+
if (focusedKey != null) {
|
|
124
|
+
const node = collection.getItem(focusedKey);
|
|
125
|
+
if (node?.isExpandable && s.isExpanded(focusedKey)) {
|
|
126
|
+
// Collapse the node
|
|
127
|
+
s.collapseKey(focusedKey);
|
|
128
|
+
} else if (node?.parentKey != null) {
|
|
129
|
+
// Move to parent
|
|
130
|
+
s.setFocusedKey(node.parentKey);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
break;
|
|
134
|
+
}
|
|
135
|
+
case 'Home': {
|
|
136
|
+
e.preventDefault();
|
|
137
|
+
const firstKey = collection.getFirstKey();
|
|
138
|
+
if (firstKey != null) {
|
|
139
|
+
s.setFocusedKey(firstKey);
|
|
140
|
+
}
|
|
141
|
+
break;
|
|
142
|
+
}
|
|
143
|
+
case 'End': {
|
|
144
|
+
e.preventDefault();
|
|
145
|
+
const lastKey = collection.getLastKey();
|
|
146
|
+
if (lastKey != null) {
|
|
147
|
+
s.setFocusedKey(lastKey);
|
|
148
|
+
}
|
|
149
|
+
break;
|
|
150
|
+
}
|
|
151
|
+
case 'a':
|
|
152
|
+
case 'A': {
|
|
153
|
+
if ((e.ctrlKey || e.metaKey) && s.selectionMode === 'multiple') {
|
|
154
|
+
e.preventDefault();
|
|
155
|
+
s.selectAll();
|
|
156
|
+
}
|
|
157
|
+
break;
|
|
158
|
+
}
|
|
159
|
+
case 'Escape': {
|
|
160
|
+
if (s.selectionMode !== 'none') {
|
|
161
|
+
e.preventDefault();
|
|
162
|
+
s.clearSelection();
|
|
163
|
+
}
|
|
164
|
+
break;
|
|
165
|
+
}
|
|
166
|
+
case '*': {
|
|
167
|
+
// Expand all siblings at current level
|
|
168
|
+
e.preventDefault();
|
|
169
|
+
if (focusedKey != null) {
|
|
170
|
+
const node = collection.getItem(focusedKey);
|
|
171
|
+
if (node) {
|
|
172
|
+
// Find all siblings at the same level
|
|
173
|
+
const parentKey = node.parentKey;
|
|
174
|
+
let siblings: Key[];
|
|
175
|
+
if (parentKey != null) {
|
|
176
|
+
siblings = [...collection.getChildren(parentKey)].map((n) => n.key);
|
|
177
|
+
} else {
|
|
178
|
+
// Root level siblings
|
|
179
|
+
siblings = collection.rows
|
|
180
|
+
.filter((n) => n.level === 0)
|
|
181
|
+
.map((n) => n.key);
|
|
182
|
+
}
|
|
183
|
+
// Expand all expandable siblings
|
|
184
|
+
for (const siblingKey of siblings) {
|
|
185
|
+
const sibling = collection.getItem(siblingKey);
|
|
186
|
+
if (sibling?.isExpandable && !s.isExpanded(siblingKey)) {
|
|
187
|
+
s.expandKey(siblingKey);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
break;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
const onFocus = () => {
|
|
198
|
+
const s = state();
|
|
199
|
+
s.setFocused(true);
|
|
200
|
+
|
|
201
|
+
// If nothing is focused, focus the first item
|
|
202
|
+
if (s.focusedKey == null) {
|
|
203
|
+
const firstKey = s.collection.getFirstKey();
|
|
204
|
+
if (firstKey != null) {
|
|
205
|
+
s.setFocusedKey(firstKey);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
const onBlur = () => {
|
|
211
|
+
const s = state();
|
|
212
|
+
s.setFocused(false);
|
|
213
|
+
};
|
|
214
|
+
|
|
215
|
+
const treeProps = createMemo(() => {
|
|
216
|
+
const p = props();
|
|
217
|
+
const s = state();
|
|
218
|
+
|
|
219
|
+
const baseProps: Record<string, unknown> = {
|
|
220
|
+
role: 'treegrid',
|
|
221
|
+
id: treeId,
|
|
222
|
+
'aria-label': p['aria-label'],
|
|
223
|
+
'aria-labelledby': p['aria-labelledby'],
|
|
224
|
+
'aria-describedby': p['aria-describedby'],
|
|
225
|
+
'aria-multiselectable': s.selectionMode === 'multiple' ? true : undefined,
|
|
226
|
+
'aria-disabled': p.isDisabled || undefined,
|
|
227
|
+
tabIndex: p.isDisabled ? undefined : 0,
|
|
228
|
+
onKeyDown,
|
|
229
|
+
onFocus,
|
|
230
|
+
onBlur,
|
|
231
|
+
};
|
|
232
|
+
|
|
233
|
+
// Add row count for virtualized trees
|
|
234
|
+
if (p.isVirtualized) {
|
|
235
|
+
baseProps['aria-rowcount'] = s.collection.rowCount;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
return baseProps as JSX.HTMLAttributes<HTMLDivElement>;
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
return {
|
|
242
|
+
get treeProps() {
|
|
243
|
+
return treeProps();
|
|
244
|
+
},
|
|
245
|
+
};
|
|
246
|
+
}
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* createTreeItem - Provides accessibility for a tree item.
|
|
3
|
+
* Based on @react-aria/tree/useTreeItem.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { createMemo, createSignal, type Accessor } from 'solid-js';
|
|
7
|
+
import type { JSX } from 'solid-js';
|
|
8
|
+
import type { TreeState, TreeCollection } from '@proyecto-viviana/solid-stately';
|
|
9
|
+
import type { AriaTreeItemProps, TreeItemAria } from './types';
|
|
10
|
+
import { getTreeData } from './createTree';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Creates accessibility props for a tree item.
|
|
14
|
+
*/
|
|
15
|
+
export function createTreeItem<T extends object, C extends TreeCollection<T> = TreeCollection<T>>(
|
|
16
|
+
props: Accessor<AriaTreeItemProps<T>>,
|
|
17
|
+
state: Accessor<TreeState<T, C>>,
|
|
18
|
+
_ref: Accessor<HTMLDivElement | null>
|
|
19
|
+
): TreeItemAria {
|
|
20
|
+
const [isPressed, setIsPressed] = createSignal(false);
|
|
21
|
+
|
|
22
|
+
const isSelected = createMemo(() => {
|
|
23
|
+
const s = state();
|
|
24
|
+
const p = props();
|
|
25
|
+
return s.isSelected(p.node.key);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
const isDisabled = createMemo(() => {
|
|
29
|
+
const s = state();
|
|
30
|
+
const p = props();
|
|
31
|
+
return s.isDisabled(p.node.key);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
const isFocused = createMemo(() => {
|
|
35
|
+
const s = state();
|
|
36
|
+
const p = props();
|
|
37
|
+
return s.focusedKey === p.node.key;
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
const isExpanded = createMemo(() => {
|
|
41
|
+
const s = state();
|
|
42
|
+
const p = props();
|
|
43
|
+
return s.isExpanded(p.node.key);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
const isExpandable = createMemo(() => {
|
|
47
|
+
const p = props();
|
|
48
|
+
return p.node.isExpandable ?? false;
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
const level = createMemo(() => {
|
|
52
|
+
const p = props();
|
|
53
|
+
return p.node.level;
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
// Handle click/press for selection and actions
|
|
57
|
+
const onClick = (e: MouseEvent) => {
|
|
58
|
+
const s = state();
|
|
59
|
+
const p = props();
|
|
60
|
+
|
|
61
|
+
if (isDisabled()) return;
|
|
62
|
+
|
|
63
|
+
// Get tree metadata for actions
|
|
64
|
+
const treeData = getTreeData(s);
|
|
65
|
+
const onAction = treeData?.actions.onAction;
|
|
66
|
+
|
|
67
|
+
// Handle selection
|
|
68
|
+
if (s.selectionMode !== 'none') {
|
|
69
|
+
if (e.shiftKey && s.selectionMode === 'multiple') {
|
|
70
|
+
s.extendSelection(p.node.key);
|
|
71
|
+
} else if (e.ctrlKey || e.metaKey) {
|
|
72
|
+
s.toggleSelection(p.node.key);
|
|
73
|
+
} else {
|
|
74
|
+
// Replace selection or toggle if already selected
|
|
75
|
+
if (isSelected() && s.selectedKeys !== 'all') {
|
|
76
|
+
const selectedKeys = s.selectedKeys as Set<unknown>;
|
|
77
|
+
if (selectedKeys.size === 1) {
|
|
78
|
+
// Single selection, trigger action
|
|
79
|
+
if (onAction) {
|
|
80
|
+
onAction(p.node.key);
|
|
81
|
+
}
|
|
82
|
+
if (p.onAction) {
|
|
83
|
+
p.onAction();
|
|
84
|
+
}
|
|
85
|
+
} else {
|
|
86
|
+
s.replaceSelection(p.node.key);
|
|
87
|
+
}
|
|
88
|
+
} else {
|
|
89
|
+
s.replaceSelection(p.node.key);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
} else {
|
|
93
|
+
// No selection mode, just trigger action
|
|
94
|
+
if (onAction) {
|
|
95
|
+
onAction(p.node.key);
|
|
96
|
+
}
|
|
97
|
+
if (p.onAction) {
|
|
98
|
+
p.onAction();
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
const onKeyDown = (e: KeyboardEvent) => {
|
|
104
|
+
const s = state();
|
|
105
|
+
const p = props();
|
|
106
|
+
|
|
107
|
+
if (isDisabled()) return;
|
|
108
|
+
|
|
109
|
+
if (e.key === 'Enter') {
|
|
110
|
+
// Get tree metadata for actions
|
|
111
|
+
const treeData = getTreeData(s);
|
|
112
|
+
const onAction = treeData?.actions.onAction;
|
|
113
|
+
|
|
114
|
+
if (onAction || p.onAction) {
|
|
115
|
+
e.preventDefault();
|
|
116
|
+
|
|
117
|
+
if (onAction) {
|
|
118
|
+
onAction(p.node.key);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (p.onAction) {
|
|
122
|
+
p.onAction();
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
} else if (e.key === ' ') {
|
|
126
|
+
// Space toggles selection
|
|
127
|
+
if (s.selectionMode !== 'none') {
|
|
128
|
+
e.preventDefault();
|
|
129
|
+
s.toggleSelection(p.node.key);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
const onFocus = () => {
|
|
135
|
+
const s = state();
|
|
136
|
+
const p = props();
|
|
137
|
+
s.setFocusedKey(p.node.key);
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
const onPointerDown = () => {
|
|
141
|
+
setIsPressed(true);
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
const onPointerUp = () => {
|
|
145
|
+
setIsPressed(false);
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
const rowProps = createMemo(() => {
|
|
149
|
+
const s = state();
|
|
150
|
+
const p = props();
|
|
151
|
+
const node = p.node;
|
|
152
|
+
|
|
153
|
+
const baseProps: Record<string, unknown> = {
|
|
154
|
+
role: 'row',
|
|
155
|
+
'aria-selected': s.selectionMode !== 'none' ? isSelected() : undefined,
|
|
156
|
+
'aria-disabled': isDisabled() || undefined,
|
|
157
|
+
'aria-expanded': isExpandable() ? isExpanded() : undefined,
|
|
158
|
+
'aria-level': node.level + 1, // 1-based for ARIA
|
|
159
|
+
tabIndex: isFocused() ? 0 : -1,
|
|
160
|
+
onClick,
|
|
161
|
+
onKeyDown,
|
|
162
|
+
onFocus,
|
|
163
|
+
onPointerDown,
|
|
164
|
+
onPointerUp,
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
// Add aria-rowindex for virtualized trees
|
|
168
|
+
if (p.isVirtualized && node.rowIndex != null) {
|
|
169
|
+
baseProps['aria-rowindex'] = node.rowIndex + 1; // 1-based
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
return baseProps as JSX.HTMLAttributes<HTMLDivElement>;
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
const gridCellProps = createMemo(() => {
|
|
176
|
+
return {
|
|
177
|
+
role: 'gridcell',
|
|
178
|
+
} as JSX.HTMLAttributes<HTMLDivElement>;
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
// Expand button handler
|
|
182
|
+
const onExpandClick = (e: MouseEvent) => {
|
|
183
|
+
e.stopPropagation(); // Don't trigger row click
|
|
184
|
+
const s = state();
|
|
185
|
+
const p = props();
|
|
186
|
+
|
|
187
|
+
if (isDisabled()) return;
|
|
188
|
+
|
|
189
|
+
s.toggleKey(p.node.key);
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
const expandButtonProps = createMemo(() => {
|
|
193
|
+
const baseProps: Record<string, unknown> = {
|
|
194
|
+
type: 'button',
|
|
195
|
+
'aria-label': isExpanded() ? 'Collapse' : 'Expand',
|
|
196
|
+
onClick: onExpandClick,
|
|
197
|
+
tabIndex: -1, // Not in tab order, use arrow keys
|
|
198
|
+
'aria-hidden': !isExpandable() ? true : undefined,
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
return baseProps as JSX.ButtonHTMLAttributes<HTMLButtonElement>;
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
return {
|
|
205
|
+
get rowProps() {
|
|
206
|
+
return rowProps();
|
|
207
|
+
},
|
|
208
|
+
get gridCellProps() {
|
|
209
|
+
return gridCellProps();
|
|
210
|
+
},
|
|
211
|
+
get expandButtonProps() {
|
|
212
|
+
return expandButtonProps();
|
|
213
|
+
},
|
|
214
|
+
get isSelected() {
|
|
215
|
+
return isSelected();
|
|
216
|
+
},
|
|
217
|
+
get isDisabled() {
|
|
218
|
+
return isDisabled();
|
|
219
|
+
},
|
|
220
|
+
get isPressed() {
|
|
221
|
+
return isPressed();
|
|
222
|
+
},
|
|
223
|
+
get isExpanded() {
|
|
224
|
+
return isExpanded();
|
|
225
|
+
},
|
|
226
|
+
get isExpandable() {
|
|
227
|
+
return isExpandable();
|
|
228
|
+
},
|
|
229
|
+
get level() {
|
|
230
|
+
return level();
|
|
231
|
+
},
|
|
232
|
+
};
|
|
233
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* createTreeSelectionCheckbox - Provides accessibility for a tree item's selection checkbox.
|
|
3
|
+
* Based on @react-aria/gridlist/useGridListSelectionCheckbox.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { createMemo, type Accessor } from 'solid-js';
|
|
7
|
+
import type { JSX } from 'solid-js';
|
|
8
|
+
import type { TreeState, TreeCollection } from '@proyecto-viviana/solid-stately';
|
|
9
|
+
import type { AriaTreeSelectionCheckboxProps, TreeSelectionCheckboxAria } from './types';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Creates accessibility props for a tree selection checkbox.
|
|
13
|
+
*/
|
|
14
|
+
export function createTreeSelectionCheckbox<T extends object, C extends TreeCollection<T> = TreeCollection<T>>(
|
|
15
|
+
props: Accessor<AriaTreeSelectionCheckboxProps>,
|
|
16
|
+
state: Accessor<TreeState<T, C>>
|
|
17
|
+
): TreeSelectionCheckboxAria {
|
|
18
|
+
const isSelected = createMemo(() => {
|
|
19
|
+
const s = state();
|
|
20
|
+
const p = props();
|
|
21
|
+
return s.isSelected(p.key);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
const isDisabled = createMemo(() => {
|
|
25
|
+
const s = state();
|
|
26
|
+
const p = props();
|
|
27
|
+
return s.isDisabled(p.key);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
const onChange = (e: Event) => {
|
|
31
|
+
const s = state();
|
|
32
|
+
const p = props();
|
|
33
|
+
const target = e.target as HTMLInputElement;
|
|
34
|
+
|
|
35
|
+
if (isDisabled()) return;
|
|
36
|
+
|
|
37
|
+
if (target.checked) {
|
|
38
|
+
s.toggleSelection(p.key);
|
|
39
|
+
} else {
|
|
40
|
+
s.toggleSelection(p.key);
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const onClick = (e: MouseEvent) => {
|
|
45
|
+
// Stop propagation to prevent row click from also firing
|
|
46
|
+
e.stopPropagation();
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const checkboxProps = createMemo(() => {
|
|
50
|
+
const baseProps: Record<string, unknown> = {
|
|
51
|
+
type: 'checkbox',
|
|
52
|
+
'aria-label': 'Select',
|
|
53
|
+
checked: isSelected(),
|
|
54
|
+
disabled: isDisabled(),
|
|
55
|
+
onChange,
|
|
56
|
+
onClick,
|
|
57
|
+
tabIndex: -1, // Use arrow keys to navigate, not tab
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
return baseProps as JSX.InputHTMLAttributes<HTMLInputElement>;
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
return {
|
|
64
|
+
get checkboxProps() {
|
|
65
|
+
return checkboxProps();
|
|
66
|
+
},
|
|
67
|
+
};
|
|
68
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tree ARIA layer exports.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export { createTree, getTreeData } from './createTree';
|
|
6
|
+
export { createTreeItem } from './createTreeItem';
|
|
7
|
+
export { createTreeSelectionCheckbox } from './createTreeSelectionCheckbox';
|
|
8
|
+
|
|
9
|
+
export type {
|
|
10
|
+
AriaTreeProps,
|
|
11
|
+
TreeAria,
|
|
12
|
+
AriaTreeItemProps,
|
|
13
|
+
TreeItemAria,
|
|
14
|
+
AriaTreeSelectionCheckboxProps,
|
|
15
|
+
TreeSelectionCheckboxAria,
|
|
16
|
+
} from './types';
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tree ARIA types.
|
|
3
|
+
* Based on @react-aria/tree.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { JSX } from 'solid-js';
|
|
7
|
+
import type { Key, TreeNode } from '@proyecto-viviana/solid-stately';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Props for createTree.
|
|
11
|
+
*/
|
|
12
|
+
export interface AriaTreeProps {
|
|
13
|
+
/** The unique id for the tree. */
|
|
14
|
+
id?: string;
|
|
15
|
+
/** Label for accessibility. */
|
|
16
|
+
'aria-label'?: string;
|
|
17
|
+
/** ID of an element that labels the tree. */
|
|
18
|
+
'aria-labelledby'?: string;
|
|
19
|
+
/** ID of an element that describes the tree. */
|
|
20
|
+
'aria-describedby'?: string;
|
|
21
|
+
/** Whether the tree is virtualized. */
|
|
22
|
+
isVirtualized?: boolean;
|
|
23
|
+
/** Handler called when an item action is triggered. */
|
|
24
|
+
onAction?: (key: Key) => void;
|
|
25
|
+
/** Whether the tree is disabled. */
|
|
26
|
+
isDisabled?: boolean;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Return value for createTree.
|
|
31
|
+
*/
|
|
32
|
+
export interface TreeAria {
|
|
33
|
+
/** Props for the tree container (role="treegrid"). */
|
|
34
|
+
treeProps: JSX.HTMLAttributes<HTMLDivElement>;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Props for createTreeItem.
|
|
39
|
+
*/
|
|
40
|
+
export interface AriaTreeItemProps<T = unknown> {
|
|
41
|
+
/** The tree node this item represents. */
|
|
42
|
+
node: TreeNode<T>;
|
|
43
|
+
/** Whether the item is rendered in a virtualized list. */
|
|
44
|
+
isVirtualized?: boolean;
|
|
45
|
+
/** Handler called when this item's action is triggered. */
|
|
46
|
+
onAction?: () => void;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Return value for createTreeItem.
|
|
51
|
+
*/
|
|
52
|
+
export interface TreeItemAria {
|
|
53
|
+
/** Props for the row element. */
|
|
54
|
+
rowProps: JSX.HTMLAttributes<HTMLDivElement>;
|
|
55
|
+
/** Props for the grid cell content wrapper. */
|
|
56
|
+
gridCellProps: JSX.HTMLAttributes<HTMLDivElement>;
|
|
57
|
+
/** Props for the expand button (if the item is expandable). */
|
|
58
|
+
expandButtonProps: JSX.ButtonHTMLAttributes<HTMLButtonElement>;
|
|
59
|
+
/** Whether the item is selected. */
|
|
60
|
+
isSelected: boolean;
|
|
61
|
+
/** Whether the item is disabled. */
|
|
62
|
+
isDisabled: boolean;
|
|
63
|
+
/** Whether the item is being pressed. */
|
|
64
|
+
isPressed: boolean;
|
|
65
|
+
/** Whether the item is expanded. */
|
|
66
|
+
isExpanded: boolean;
|
|
67
|
+
/** Whether the item is expandable (has children). */
|
|
68
|
+
isExpandable: boolean;
|
|
69
|
+
/** The nesting level of the item (0 for root). */
|
|
70
|
+
level: number;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Props for createTreeSelectionCheckbox.
|
|
75
|
+
*/
|
|
76
|
+
export interface AriaTreeSelectionCheckboxProps {
|
|
77
|
+
/** The key of the tree item this checkbox belongs to. */
|
|
78
|
+
key: Key;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Return value for createTreeSelectionCheckbox.
|
|
83
|
+
*/
|
|
84
|
+
export interface TreeSelectionCheckboxAria {
|
|
85
|
+
/** Props for the checkbox input element. */
|
|
86
|
+
checkboxProps: JSX.InputHTMLAttributes<HTMLInputElement>;
|
|
87
|
+
}
|