@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,180 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* createGridListItem - Provides accessibility for a grid list item.
|
|
3
|
+
* Based on @react-aria/gridlist/useGridListItem.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { createMemo, createSignal, type Accessor } from 'solid-js';
|
|
7
|
+
import type { JSX } from 'solid-js';
|
|
8
|
+
import type { GridState, GridCollection } from '@proyecto-viviana/solid-stately';
|
|
9
|
+
import type { AriaGridListItemProps, GridListItemAria } from './types';
|
|
10
|
+
import { getGridListData } from './createGridList';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Creates accessibility props for a grid list item.
|
|
14
|
+
*/
|
|
15
|
+
export function createGridListItem<T extends object, C extends GridCollection<T> = GridCollection<T>>(
|
|
16
|
+
props: Accessor<AriaGridListItemProps>,
|
|
17
|
+
state: Accessor<GridState<T, C>>,
|
|
18
|
+
_ref: Accessor<HTMLLIElement | null>
|
|
19
|
+
): GridListItemAria {
|
|
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
|
+
// Handle click/press for selection and actions
|
|
41
|
+
const onClick = (e: MouseEvent) => {
|
|
42
|
+
const s = state();
|
|
43
|
+
const p = props();
|
|
44
|
+
|
|
45
|
+
if (isDisabled()) return;
|
|
46
|
+
|
|
47
|
+
// Get grid list metadata for actions
|
|
48
|
+
const gridListData = getGridListData(s);
|
|
49
|
+
const onAction = gridListData?.actions.onAction;
|
|
50
|
+
|
|
51
|
+
// Handle selection
|
|
52
|
+
if (s.selectionMode !== 'none') {
|
|
53
|
+
if (e.shiftKey && s.selectionMode === 'multiple') {
|
|
54
|
+
s.extendSelection(p.node.key);
|
|
55
|
+
} else if (e.ctrlKey || e.metaKey) {
|
|
56
|
+
s.toggleSelection(p.node.key);
|
|
57
|
+
} else {
|
|
58
|
+
// Replace selection or toggle if already selected
|
|
59
|
+
if (isSelected() && s.selectedKeys !== 'all') {
|
|
60
|
+
const selectedKeys = s.selectedKeys as Set<unknown>;
|
|
61
|
+
if (selectedKeys.size === 1) {
|
|
62
|
+
// Single selection, trigger action
|
|
63
|
+
if (onAction) {
|
|
64
|
+
onAction(p.node.key);
|
|
65
|
+
}
|
|
66
|
+
if (p.onAction) {
|
|
67
|
+
p.onAction();
|
|
68
|
+
}
|
|
69
|
+
} else {
|
|
70
|
+
s.replaceSelection(p.node.key);
|
|
71
|
+
}
|
|
72
|
+
} else {
|
|
73
|
+
s.replaceSelection(p.node.key);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
} else {
|
|
77
|
+
// No selection mode, just trigger action
|
|
78
|
+
if (onAction) {
|
|
79
|
+
onAction(p.node.key);
|
|
80
|
+
}
|
|
81
|
+
if (p.onAction) {
|
|
82
|
+
p.onAction();
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
const onKeyDown = (e: KeyboardEvent) => {
|
|
88
|
+
const s = state();
|
|
89
|
+
const p = props();
|
|
90
|
+
|
|
91
|
+
if (isDisabled()) return;
|
|
92
|
+
|
|
93
|
+
if (e.key === 'Enter') {
|
|
94
|
+
// Get grid list metadata for actions
|
|
95
|
+
const gridListData = getGridListData(s);
|
|
96
|
+
const onAction = gridListData?.actions.onAction;
|
|
97
|
+
|
|
98
|
+
if (onAction || p.onAction) {
|
|
99
|
+
e.preventDefault();
|
|
100
|
+
|
|
101
|
+
if (onAction) {
|
|
102
|
+
onAction(p.node.key);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (p.onAction) {
|
|
106
|
+
p.onAction();
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
} else if (e.key === ' ') {
|
|
110
|
+
// Space toggles selection
|
|
111
|
+
if (s.selectionMode !== 'none') {
|
|
112
|
+
e.preventDefault();
|
|
113
|
+
s.toggleSelection(p.node.key);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
const onFocus = () => {
|
|
119
|
+
const s = state();
|
|
120
|
+
const p = props();
|
|
121
|
+
s.setFocusedKey(p.node.key);
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
const onPointerDown = () => {
|
|
125
|
+
setIsPressed(true);
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
const onPointerUp = () => {
|
|
129
|
+
setIsPressed(false);
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
const rowProps = createMemo(() => {
|
|
133
|
+
const s = state();
|
|
134
|
+
const p = props();
|
|
135
|
+
const node = p.node;
|
|
136
|
+
|
|
137
|
+
const baseProps: Record<string, unknown> = {
|
|
138
|
+
role: 'row',
|
|
139
|
+
'aria-selected': s.selectionMode !== 'none' ? isSelected() : undefined,
|
|
140
|
+
'aria-disabled': isDisabled() || undefined,
|
|
141
|
+
tabIndex: isFocused() ? 0 : -1,
|
|
142
|
+
onClick,
|
|
143
|
+
onKeyDown,
|
|
144
|
+
onFocus,
|
|
145
|
+
onPointerDown,
|
|
146
|
+
onPointerUp,
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
// Add aria-rowindex for virtualized lists
|
|
150
|
+
if (p.isVirtualized && node.rowIndex != null) {
|
|
151
|
+
baseProps['aria-rowindex'] = node.rowIndex + 1; // 1-based
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
return baseProps as JSX.HTMLAttributes<HTMLLIElement>;
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
const gridCellProps = createMemo(() => {
|
|
158
|
+
return {
|
|
159
|
+
role: 'gridcell',
|
|
160
|
+
} as JSX.HTMLAttributes<HTMLDivElement>;
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
return {
|
|
164
|
+
get rowProps() {
|
|
165
|
+
return rowProps();
|
|
166
|
+
},
|
|
167
|
+
get gridCellProps() {
|
|
168
|
+
return gridCellProps();
|
|
169
|
+
},
|
|
170
|
+
get isSelected() {
|
|
171
|
+
return isSelected();
|
|
172
|
+
},
|
|
173
|
+
get isDisabled() {
|
|
174
|
+
return isDisabled();
|
|
175
|
+
},
|
|
176
|
+
get isPressed() {
|
|
177
|
+
return isPressed();
|
|
178
|
+
},
|
|
179
|
+
};
|
|
180
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* createGridListSelectionCheckbox - Provides accessibility for a grid list item selection checkbox.
|
|
3
|
+
* Based on @react-aria/gridlist.
|
|
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 { GridState, GridCollection } from '@proyecto-viviana/solid-stately';
|
|
10
|
+
import type { AriaGridListSelectionCheckboxProps, GridListSelectionCheckboxAria } from './types';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Creates accessibility props for a grid list item selection checkbox.
|
|
14
|
+
*/
|
|
15
|
+
export function createGridListSelectionCheckbox<T extends object, C extends GridCollection<T> = GridCollection<T>>(
|
|
16
|
+
props: Accessor<AriaGridListSelectionCheckboxProps>,
|
|
17
|
+
state: Accessor<GridState<T, C>>
|
|
18
|
+
): GridListSelectionCheckboxAria {
|
|
19
|
+
const checkboxId = createId();
|
|
20
|
+
|
|
21
|
+
const isSelected = createMemo(() => {
|
|
22
|
+
const s = state();
|
|
23
|
+
const p = props();
|
|
24
|
+
return s.isSelected(p.key);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
const isDisabled = createMemo(() => {
|
|
28
|
+
const s = state();
|
|
29
|
+
const p = props();
|
|
30
|
+
return s.isDisabled(p.key);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
const onChange = () => {
|
|
34
|
+
const s = state();
|
|
35
|
+
const p = props();
|
|
36
|
+
if (!isDisabled()) {
|
|
37
|
+
s.toggleSelection(p.key);
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const checkboxProps = createMemo(() => {
|
|
42
|
+
const baseProps: Record<string, unknown> = {
|
|
43
|
+
id: checkboxId,
|
|
44
|
+
type: 'checkbox',
|
|
45
|
+
checked: isSelected(),
|
|
46
|
+
disabled: isDisabled(),
|
|
47
|
+
onChange,
|
|
48
|
+
'aria-label': 'Select',
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
return baseProps as JSX.InputHTMLAttributes<HTMLInputElement>;
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
return {
|
|
55
|
+
get checkboxProps() {
|
|
56
|
+
return checkboxProps();
|
|
57
|
+
},
|
|
58
|
+
};
|
|
59
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GridList ARIA hooks for solidaria.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export { createGridList, getGridListData } from './createGridList';
|
|
6
|
+
export { createGridListItem } from './createGridListItem';
|
|
7
|
+
export { createGridListSelectionCheckbox } from './createGridListSelectionCheckbox';
|
|
8
|
+
|
|
9
|
+
export type {
|
|
10
|
+
AriaGridListProps,
|
|
11
|
+
GridListAria,
|
|
12
|
+
AriaGridListItemProps,
|
|
13
|
+
GridListItemAria,
|
|
14
|
+
AriaGridListSelectionCheckboxProps,
|
|
15
|
+
GridListSelectionCheckboxAria,
|
|
16
|
+
} from './types';
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GridList ARIA types for GridList components.
|
|
3
|
+
* Based on @react-aria/gridlist types.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { JSX } from 'solid-js';
|
|
7
|
+
import type { Key, GridNode } from '@proyecto-viviana/solid-stately';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Props for the createGridList hook.
|
|
11
|
+
*/
|
|
12
|
+
export interface AriaGridListProps {
|
|
13
|
+
/** ID for the grid list element. */
|
|
14
|
+
id?: string;
|
|
15
|
+
/** ARIA label for the grid list. */
|
|
16
|
+
'aria-label'?: string;
|
|
17
|
+
/** ARIA labelledby for the grid list. */
|
|
18
|
+
'aria-labelledby'?: string;
|
|
19
|
+
/** ARIA describedby for the grid list. */
|
|
20
|
+
'aria-describedby'?: string;
|
|
21
|
+
/** Whether the grid list uses virtual scrolling. */
|
|
22
|
+
isVirtualized?: boolean;
|
|
23
|
+
/** Handler for item actions. */
|
|
24
|
+
onAction?: (key: Key) => void;
|
|
25
|
+
/** Whether selection should occur on press up. */
|
|
26
|
+
shouldSelectOnPressUp?: boolean;
|
|
27
|
+
/** Whether the grid list is disabled. */
|
|
28
|
+
isDisabled?: boolean;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Return value from createGridList.
|
|
33
|
+
*/
|
|
34
|
+
export interface GridListAria {
|
|
35
|
+
/** Props to spread on the grid list element. */
|
|
36
|
+
gridProps: JSX.HTMLAttributes<HTMLUListElement>;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Props for the createGridListItem hook.
|
|
41
|
+
*/
|
|
42
|
+
export interface AriaGridListItemProps {
|
|
43
|
+
/** The item node. */
|
|
44
|
+
node: GridNode<unknown>;
|
|
45
|
+
/** Whether the grid list is virtualized. */
|
|
46
|
+
isVirtualized?: boolean;
|
|
47
|
+
/** Handler for item action. */
|
|
48
|
+
onAction?: () => void;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Return value from createGridListItem.
|
|
53
|
+
*/
|
|
54
|
+
export interface GridListItemAria {
|
|
55
|
+
/** Props to spread on the item element. */
|
|
56
|
+
rowProps: JSX.HTMLAttributes<HTMLLIElement>;
|
|
57
|
+
/** Props to spread on the grid cell wrapper. */
|
|
58
|
+
gridCellProps: JSX.HTMLAttributes<HTMLDivElement>;
|
|
59
|
+
/** Whether the item is selected. */
|
|
60
|
+
isSelected: boolean;
|
|
61
|
+
/** Whether the item is disabled. */
|
|
62
|
+
isDisabled: boolean;
|
|
63
|
+
/** Whether the item is pressed. */
|
|
64
|
+
isPressed: boolean;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Props for the createGridListSelectionCheckbox hook.
|
|
69
|
+
*/
|
|
70
|
+
export interface AriaGridListSelectionCheckboxProps {
|
|
71
|
+
/** The key of the item. */
|
|
72
|
+
key: Key;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Return value from createGridListSelectionCheckbox.
|
|
77
|
+
*/
|
|
78
|
+
export interface GridListSelectionCheckboxAria {
|
|
79
|
+
/** Props to spread on the checkbox input element. */
|
|
80
|
+
checkboxProps: JSX.InputHTMLAttributes<HTMLInputElement>;
|
|
81
|
+
}
|
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* NumberFormatter for solidaria
|
|
3
|
+
*
|
|
4
|
+
* A wrapper around Intl.NumberFormat with caching and polyfills.
|
|
5
|
+
*
|
|
6
|
+
* Port of @internationalized/number NumberFormatter.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
// ============================================
|
|
10
|
+
// FEATURE DETECTION
|
|
11
|
+
// ============================================
|
|
12
|
+
|
|
13
|
+
let supportsSignDisplay = false;
|
|
14
|
+
try {
|
|
15
|
+
supportsSignDisplay =
|
|
16
|
+
new Intl.NumberFormat('de-DE', { signDisplay: 'exceptZero' }).resolvedOptions()
|
|
17
|
+
.signDisplay === 'exceptZero';
|
|
18
|
+
} catch {
|
|
19
|
+
// Not supported
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
let supportsUnit = false;
|
|
23
|
+
try {
|
|
24
|
+
supportsUnit =
|
|
25
|
+
new Intl.NumberFormat('de-DE', { style: 'unit', unit: 'degree' }).resolvedOptions()
|
|
26
|
+
.style === 'unit';
|
|
27
|
+
} catch {
|
|
28
|
+
// Not supported
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// ============================================
|
|
32
|
+
// POLYFILLS
|
|
33
|
+
// ============================================
|
|
34
|
+
|
|
35
|
+
// Polyfill for units since Safari doesn't support them yet.
|
|
36
|
+
// Currently only polyfilling the unit degree in narrow format.
|
|
37
|
+
const UNITS: Record<string, Record<string, Record<string, string>>> = {
|
|
38
|
+
degree: {
|
|
39
|
+
narrow: {
|
|
40
|
+
default: '°',
|
|
41
|
+
'ja-JP': ' 度',
|
|
42
|
+
'zh-TW': '度',
|
|
43
|
+
'sl-SI': ' °',
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
// ============================================
|
|
49
|
+
// TYPES
|
|
50
|
+
// ============================================
|
|
51
|
+
|
|
52
|
+
export interface NumberFormatOptions extends Intl.NumberFormatOptions {
|
|
53
|
+
/** Overrides default numbering system for the current locale. */
|
|
54
|
+
numberingSystem?: string;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
interface NumberRangeFormatPart extends Intl.NumberFormatPart {
|
|
58
|
+
source: 'startRange' | 'endRange' | 'shared';
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// ============================================
|
|
62
|
+
// CACHE
|
|
63
|
+
// ============================================
|
|
64
|
+
|
|
65
|
+
const formatterCache = new Map<string, Intl.NumberFormat>();
|
|
66
|
+
|
|
67
|
+
function getCachedNumberFormatter(
|
|
68
|
+
locale: string,
|
|
69
|
+
options: NumberFormatOptions = {}
|
|
70
|
+
): Intl.NumberFormat {
|
|
71
|
+
let processedLocale = locale;
|
|
72
|
+
const { numberingSystem } = options;
|
|
73
|
+
|
|
74
|
+
if (numberingSystem && !processedLocale.includes('-nu-')) {
|
|
75
|
+
if (!processedLocale.includes('-u-')) {
|
|
76
|
+
processedLocale += '-u-';
|
|
77
|
+
}
|
|
78
|
+
processedLocale += `-nu-${numberingSystem}`;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
let processedOptions = options;
|
|
82
|
+
if (options.style === 'unit' && !supportsUnit) {
|
|
83
|
+
const { unit, unitDisplay = 'short' } = options;
|
|
84
|
+
if (!unit) {
|
|
85
|
+
throw new Error('unit option must be provided with style: "unit"');
|
|
86
|
+
}
|
|
87
|
+
if (!UNITS[unit]?.[unitDisplay]) {
|
|
88
|
+
throw new Error(`Unsupported unit ${unit} with unitDisplay = ${unitDisplay}`);
|
|
89
|
+
}
|
|
90
|
+
processedOptions = { ...options, style: 'decimal' };
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const cacheKey =
|
|
94
|
+
processedLocale +
|
|
95
|
+
(processedOptions
|
|
96
|
+
? Object.entries(processedOptions)
|
|
97
|
+
.sort((a, b) => (a[0] < b[0] ? -1 : 1))
|
|
98
|
+
.join()
|
|
99
|
+
: '');
|
|
100
|
+
|
|
101
|
+
if (formatterCache.has(cacheKey)) {
|
|
102
|
+
return formatterCache.get(cacheKey)!;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const numberFormatter = new Intl.NumberFormat(processedLocale, processedOptions);
|
|
106
|
+
formatterCache.set(cacheKey, numberFormatter);
|
|
107
|
+
return numberFormatter;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// ============================================
|
|
111
|
+
// SIGN DISPLAY POLYFILL
|
|
112
|
+
// ============================================
|
|
113
|
+
|
|
114
|
+
function numberFormatSignDisplayPolyfill(
|
|
115
|
+
numberFormat: Intl.NumberFormat,
|
|
116
|
+
signDisplay: string,
|
|
117
|
+
num: number
|
|
118
|
+
): string {
|
|
119
|
+
if (signDisplay === 'auto') {
|
|
120
|
+
return numberFormat.format(num);
|
|
121
|
+
} else if (signDisplay === 'never') {
|
|
122
|
+
return numberFormat.format(Math.abs(num));
|
|
123
|
+
} else {
|
|
124
|
+
let needsPositiveSign = false;
|
|
125
|
+
let processedNum = num;
|
|
126
|
+
|
|
127
|
+
if (signDisplay === 'always') {
|
|
128
|
+
needsPositiveSign = num > 0 || Object.is(num, 0);
|
|
129
|
+
} else if (signDisplay === 'exceptZero') {
|
|
130
|
+
if (Object.is(num, -0) || Object.is(num, 0)) {
|
|
131
|
+
processedNum = Math.abs(num);
|
|
132
|
+
} else {
|
|
133
|
+
needsPositiveSign = num > 0;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (needsPositiveSign) {
|
|
138
|
+
const negative = numberFormat.format(-processedNum);
|
|
139
|
+
const noSign = numberFormat.format(processedNum);
|
|
140
|
+
// Ignore RTL/LTR marker characters
|
|
141
|
+
const minus = negative.replace(noSign, '').replace(/\u200e|\u061C/, '');
|
|
142
|
+
if ([...minus].length !== 1) {
|
|
143
|
+
console.warn(
|
|
144
|
+
'solidaria i18n polyfill for NumberFormat signDisplay: Unsupported case'
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
const positive = negative
|
|
148
|
+
.replace(noSign, '!!!')
|
|
149
|
+
.replace(minus, '+')
|
|
150
|
+
.replace('!!!', noSign);
|
|
151
|
+
return positive;
|
|
152
|
+
} else {
|
|
153
|
+
return numberFormat.format(processedNum);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// ============================================
|
|
159
|
+
// NUMBER FORMATTER CLASS
|
|
160
|
+
// ============================================
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* A wrapper around Intl.NumberFormat providing additional options, polyfills, and caching.
|
|
164
|
+
*
|
|
165
|
+
* @example
|
|
166
|
+
* ```ts
|
|
167
|
+
* const formatter = new NumberFormatter('en-US', {
|
|
168
|
+
* style: 'currency',
|
|
169
|
+
* currency: 'USD',
|
|
170
|
+
* });
|
|
171
|
+
* formatter.format(1234.56); // '$1,234.56'
|
|
172
|
+
* ```
|
|
173
|
+
*/
|
|
174
|
+
export class NumberFormatter implements Intl.NumberFormat {
|
|
175
|
+
private numberFormatter: Intl.NumberFormat;
|
|
176
|
+
private options: NumberFormatOptions;
|
|
177
|
+
|
|
178
|
+
constructor(locale: string, options: NumberFormatOptions = {}) {
|
|
179
|
+
this.numberFormatter = getCachedNumberFormatter(locale, options);
|
|
180
|
+
this.options = options;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/** Formats a number value as a string, according to the locale and options. */
|
|
184
|
+
format(value: number): string {
|
|
185
|
+
let res = '';
|
|
186
|
+
|
|
187
|
+
if (!supportsSignDisplay && this.options.signDisplay != null) {
|
|
188
|
+
res = numberFormatSignDisplayPolyfill(
|
|
189
|
+
this.numberFormatter,
|
|
190
|
+
this.options.signDisplay,
|
|
191
|
+
value
|
|
192
|
+
);
|
|
193
|
+
} else {
|
|
194
|
+
res = this.numberFormatter.format(value);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
if (this.options.style === 'unit' && !supportsUnit) {
|
|
198
|
+
const { unit, unitDisplay = 'short', locale } = this.resolvedOptions();
|
|
199
|
+
if (!unit) {
|
|
200
|
+
return res;
|
|
201
|
+
}
|
|
202
|
+
const values = UNITS[unit]?.[unitDisplay];
|
|
203
|
+
res += values[locale] || values.default;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
return res;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/** Formats a number to an array of parts. */
|
|
210
|
+
formatToParts(value: number): Intl.NumberFormatPart[] {
|
|
211
|
+
return this.numberFormatter.formatToParts(value);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/** Formats a number range as a string. */
|
|
215
|
+
formatRange(start: number, end: number): string {
|
|
216
|
+
if (typeof this.numberFormatter.formatRange === 'function') {
|
|
217
|
+
return this.numberFormatter.formatRange(start, end);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
if (end < start) {
|
|
221
|
+
throw new RangeError('End date must be >= start date');
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// Fallback for old browsers
|
|
225
|
+
return `${this.format(start)} – ${this.format(end)}`;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/** Formats a number range as an array of parts. */
|
|
229
|
+
formatRangeToParts(start: number, end: number): NumberRangeFormatPart[] {
|
|
230
|
+
if (typeof this.numberFormatter.formatRangeToParts === 'function') {
|
|
231
|
+
return this.numberFormatter.formatRangeToParts(start, end);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
if (end < start) {
|
|
235
|
+
throw new RangeError('End date must be >= start date');
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
const startParts = this.numberFormatter.formatToParts(start);
|
|
239
|
+
const endParts = this.numberFormatter.formatToParts(end);
|
|
240
|
+
return [
|
|
241
|
+
...startParts.map((p) => ({ ...p, source: 'startRange' as const })),
|
|
242
|
+
{ type: 'literal' as const, value: ' – ', source: 'shared' as const },
|
|
243
|
+
...endParts.map((p) => ({ ...p, source: 'endRange' as const })),
|
|
244
|
+
];
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/** Returns the resolved formatting options. */
|
|
248
|
+
resolvedOptions(): Intl.ResolvedNumberFormatOptions {
|
|
249
|
+
let options = this.numberFormatter.resolvedOptions();
|
|
250
|
+
|
|
251
|
+
if (!supportsSignDisplay && this.options.signDisplay != null) {
|
|
252
|
+
options = { ...options, signDisplay: this.options.signDisplay };
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
if (!supportsUnit && this.options.style === 'unit') {
|
|
256
|
+
options = {
|
|
257
|
+
...options,
|
|
258
|
+
style: 'unit',
|
|
259
|
+
unit: this.options.unit,
|
|
260
|
+
unitDisplay: this.options.unitDisplay,
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
return options;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* createCollator hook for solidaria
|
|
3
|
+
*
|
|
4
|
+
* Provides localized string comparison/collation with caching.
|
|
5
|
+
*
|
|
6
|
+
* Port of @react-aria/i18n useCollator.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { createMemo } from 'solid-js';
|
|
10
|
+
import { useLocale } from './locale';
|
|
11
|
+
import { createCacheKey } from './utils';
|
|
12
|
+
|
|
13
|
+
// ============================================
|
|
14
|
+
// CACHE
|
|
15
|
+
// ============================================
|
|
16
|
+
|
|
17
|
+
const collatorCache = new Map<string, Intl.Collator>();
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Gets or creates a cached collator.
|
|
21
|
+
*/
|
|
22
|
+
function getCachedCollator(
|
|
23
|
+
locale: string,
|
|
24
|
+
options?: Intl.CollatorOptions
|
|
25
|
+
): Intl.Collator {
|
|
26
|
+
const cacheKey = createCacheKey(locale, options as Record<string, unknown>);
|
|
27
|
+
|
|
28
|
+
if (collatorCache.has(cacheKey)) {
|
|
29
|
+
return collatorCache.get(cacheKey)!;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const collator = new Intl.Collator(locale, options);
|
|
33
|
+
collatorCache.set(cacheKey, collator);
|
|
34
|
+
return collator;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// ============================================
|
|
38
|
+
// HOOK
|
|
39
|
+
// ============================================
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Provides localized string collation for the current locale.
|
|
43
|
+
* Useful for sorting strings according to locale-specific rules.
|
|
44
|
+
*
|
|
45
|
+
* @example
|
|
46
|
+
* ```tsx
|
|
47
|
+
* function SortedList(props: { items: string[] }) {
|
|
48
|
+
* const collator = createCollator();
|
|
49
|
+
*
|
|
50
|
+
* const sortedItems = () =>
|
|
51
|
+
* [...props.items].sort((a, b) => collator().compare(a, b));
|
|
52
|
+
*
|
|
53
|
+
* return (
|
|
54
|
+
* <ul>
|
|
55
|
+
* <For each={sortedItems()}>
|
|
56
|
+
* {(item) => <li>{item}</li>}
|
|
57
|
+
* </For>
|
|
58
|
+
* </ul>
|
|
59
|
+
* );
|
|
60
|
+
* }
|
|
61
|
+
* ```
|
|
62
|
+
*
|
|
63
|
+
* @example
|
|
64
|
+
* ```tsx
|
|
65
|
+
* // Case-insensitive sorting
|
|
66
|
+
* const collator = createCollator({ sensitivity: 'base' });
|
|
67
|
+
*
|
|
68
|
+
* // Numeric sorting
|
|
69
|
+
* const numericCollator = createCollator({ numeric: true });
|
|
70
|
+
* // ['a1', 'a10', 'a2'].sort(numericCollator().compare) -> ['a1', 'a2', 'a10']
|
|
71
|
+
* ```
|
|
72
|
+
*/
|
|
73
|
+
export function createCollator(
|
|
74
|
+
options?: Intl.CollatorOptions
|
|
75
|
+
): () => Intl.Collator {
|
|
76
|
+
const locale = useLocale();
|
|
77
|
+
|
|
78
|
+
return createMemo(() => getCachedCollator(locale().locale, options));
|
|
79
|
+
}
|