@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,196 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Breadcrumbs hooks for Solidaria
|
|
3
|
+
*
|
|
4
|
+
* Provides accessibility implementation for breadcrumb navigation.
|
|
5
|
+
* Port of @react-aria/breadcrumbs.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { createLink, type AriaLinkProps, type LinkAria } from '../link';
|
|
9
|
+
import { filterDOMProps } from '../utils/filterDOMProps';
|
|
10
|
+
import { mergeProps } from '../utils/mergeProps';
|
|
11
|
+
import { type MaybeAccessor, access } from '../utils/reactivity';
|
|
12
|
+
|
|
13
|
+
// ============================================
|
|
14
|
+
// TYPES
|
|
15
|
+
// ============================================
|
|
16
|
+
|
|
17
|
+
export interface AriaBreadcrumbsProps {
|
|
18
|
+
/** Provides a label for the breadcrumbs navigation. */
|
|
19
|
+
'aria-label'?: string;
|
|
20
|
+
/** Identifies the element (or elements) that labels the breadcrumbs. */
|
|
21
|
+
'aria-labelledby'?: string;
|
|
22
|
+
/** Identifies the element (or elements) that describes the breadcrumbs. */
|
|
23
|
+
'aria-describedby'?: string;
|
|
24
|
+
/** Whether the breadcrumbs are disabled. */
|
|
25
|
+
isDisabled?: boolean;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface BreadcrumbsAria {
|
|
29
|
+
/** Props for the breadcrumbs nav element. */
|
|
30
|
+
navProps: Record<string, unknown>;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface AriaBreadcrumbItemProps extends Omit<AriaLinkProps, 'aria-current'> {
|
|
34
|
+
/** Whether this is the current/last item in the breadcrumb trail. */
|
|
35
|
+
isCurrent?: boolean;
|
|
36
|
+
/** The type of current location for aria-current. @default 'page' */
|
|
37
|
+
'aria-current'?: 'page' | 'step' | 'location' | 'date' | 'time' | boolean;
|
|
38
|
+
/** The HTML element type. @default 'a' */
|
|
39
|
+
elementType?: string;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface BreadcrumbItemAria extends LinkAria {
|
|
43
|
+
/** Props for the breadcrumb item element. */
|
|
44
|
+
itemProps: Record<string, unknown>;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// ============================================
|
|
48
|
+
// IMPLEMENTATION
|
|
49
|
+
// ============================================
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Provides accessibility implementation for the breadcrumbs navigation container.
|
|
53
|
+
*/
|
|
54
|
+
export function createBreadcrumbs(
|
|
55
|
+
props: MaybeAccessor<AriaBreadcrumbsProps> = {}
|
|
56
|
+
): BreadcrumbsAria {
|
|
57
|
+
const getProps = () => access(props);
|
|
58
|
+
|
|
59
|
+
const getNavProps = (): Record<string, unknown> => {
|
|
60
|
+
const p = getProps();
|
|
61
|
+
|
|
62
|
+
// Default aria-label to "Breadcrumbs" if not provided
|
|
63
|
+
const ariaLabel = p['aria-label'] ?? 'Breadcrumbs';
|
|
64
|
+
|
|
65
|
+
return mergeProps(
|
|
66
|
+
filterDOMProps(p as Record<string, unknown>, { labelable: true }),
|
|
67
|
+
{
|
|
68
|
+
'aria-label': ariaLabel,
|
|
69
|
+
}
|
|
70
|
+
);
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
return {
|
|
74
|
+
get navProps() {
|
|
75
|
+
return getNavProps();
|
|
76
|
+
},
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Provides accessibility implementation for an individual breadcrumb item.
|
|
82
|
+
*/
|
|
83
|
+
export function createBreadcrumbItem(
|
|
84
|
+
props: MaybeAccessor<AriaBreadcrumbItemProps> = {}
|
|
85
|
+
): BreadcrumbItemAria {
|
|
86
|
+
const getProps = () => access(props);
|
|
87
|
+
|
|
88
|
+
const isCurrent = () => getProps().isCurrent ?? false;
|
|
89
|
+
const isDisabled = () => getProps().isDisabled ?? false;
|
|
90
|
+
const elementType = () => getProps().elementType ?? 'a';
|
|
91
|
+
|
|
92
|
+
// Check if element is a heading
|
|
93
|
+
const isHeading = () => /^h[1-6]$/.test(elementType());
|
|
94
|
+
|
|
95
|
+
// Use createLink for base link behavior
|
|
96
|
+
// Current items are treated as disabled (can't navigate to current page)
|
|
97
|
+
const { linkProps, isPressed } = createLink({
|
|
98
|
+
get isDisabled() {
|
|
99
|
+
return isDisabled() || isCurrent();
|
|
100
|
+
},
|
|
101
|
+
get elementType() {
|
|
102
|
+
return elementType();
|
|
103
|
+
},
|
|
104
|
+
get href() {
|
|
105
|
+
return getProps().href;
|
|
106
|
+
},
|
|
107
|
+
get target() {
|
|
108
|
+
return getProps().target;
|
|
109
|
+
},
|
|
110
|
+
get rel() {
|
|
111
|
+
return getProps().rel;
|
|
112
|
+
},
|
|
113
|
+
get onPress() {
|
|
114
|
+
return getProps().onPress;
|
|
115
|
+
},
|
|
116
|
+
get onPressStart() {
|
|
117
|
+
return getProps().onPressStart;
|
|
118
|
+
},
|
|
119
|
+
get onPressEnd() {
|
|
120
|
+
return getProps().onPressEnd;
|
|
121
|
+
},
|
|
122
|
+
get onClick() {
|
|
123
|
+
return getProps().onClick;
|
|
124
|
+
},
|
|
125
|
+
get onFocus() {
|
|
126
|
+
return getProps().onFocus;
|
|
127
|
+
},
|
|
128
|
+
get onBlur() {
|
|
129
|
+
return getProps().onBlur;
|
|
130
|
+
},
|
|
131
|
+
get onFocusChange() {
|
|
132
|
+
return getProps().onFocusChange;
|
|
133
|
+
},
|
|
134
|
+
get onKeyDown() {
|
|
135
|
+
return getProps().onKeyDown;
|
|
136
|
+
},
|
|
137
|
+
get onKeyUp() {
|
|
138
|
+
return getProps().onKeyUp;
|
|
139
|
+
},
|
|
140
|
+
get autoFocus() {
|
|
141
|
+
return getProps().autoFocus;
|
|
142
|
+
},
|
|
143
|
+
get 'aria-label'() {
|
|
144
|
+
return getProps()['aria-label'];
|
|
145
|
+
},
|
|
146
|
+
get 'aria-labelledby'() {
|
|
147
|
+
return getProps()['aria-labelledby'];
|
|
148
|
+
},
|
|
149
|
+
get 'aria-describedby'() {
|
|
150
|
+
return getProps()['aria-describedby'];
|
|
151
|
+
},
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
const getItemProps = (): Record<string, unknown> => {
|
|
155
|
+
const p = getProps();
|
|
156
|
+
const current = isCurrent();
|
|
157
|
+
|
|
158
|
+
// Start with link props (unless it's a heading)
|
|
159
|
+
let baseProps: Record<string, unknown> = isHeading() ? {} : linkProps;
|
|
160
|
+
|
|
161
|
+
// Add aria-current for current page
|
|
162
|
+
if (current) {
|
|
163
|
+
const ariaCurrent = p['aria-current'] ?? 'page';
|
|
164
|
+
baseProps = mergeProps(baseProps, {
|
|
165
|
+
'aria-current': ariaCurrent,
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
// Adjust tabIndex for current item
|
|
169
|
+
// If autoFocus is true, we want the item focusable
|
|
170
|
+
if (p.autoFocus) {
|
|
171
|
+
baseProps = mergeProps(baseProps, {
|
|
172
|
+
tabIndex: -1,
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Add aria-disabled for disabled items
|
|
178
|
+
if (isDisabled()) {
|
|
179
|
+
baseProps = mergeProps(baseProps, {
|
|
180
|
+
'aria-disabled': true,
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
return baseProps;
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
return {
|
|
188
|
+
get itemProps() {
|
|
189
|
+
return getItemProps();
|
|
190
|
+
},
|
|
191
|
+
get linkProps() {
|
|
192
|
+
return linkProps;
|
|
193
|
+
},
|
|
194
|
+
isPressed,
|
|
195
|
+
};
|
|
196
|
+
}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { Accessor } from 'solid-js';
|
|
2
|
+
import { createPress } from '../interactions';
|
|
3
|
+
import { createFocusable } from '../interactions';
|
|
4
|
+
import { mergeProps, filterDOMProps } from '../utils';
|
|
5
|
+
import type { AriaButtonProps, ButtonAria } from './types';
|
|
6
|
+
|
|
7
|
+
function isDisabledValue(isDisabled: Accessor<boolean> | boolean | undefined): boolean {
|
|
8
|
+
if (typeof isDisabled === 'function') {
|
|
9
|
+
return isDisabled();
|
|
10
|
+
}
|
|
11
|
+
return isDisabled ?? false;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Provides the behavior and accessibility implementation for a button component.
|
|
16
|
+
* Handles press interactions across mouse, touch, keyboard and screen readers.
|
|
17
|
+
*
|
|
18
|
+
* Based on react-aria's useButton but adapted for SolidJS.
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
* ```tsx
|
|
22
|
+
* import { createButton } from 'solidaria';
|
|
23
|
+
*
|
|
24
|
+
* function Button(props) {
|
|
25
|
+
* let ref;
|
|
26
|
+
* const { buttonProps, isPressed } = createButton(props);
|
|
27
|
+
*
|
|
28
|
+
* return (
|
|
29
|
+
* <button
|
|
30
|
+
* {...buttonProps}
|
|
31
|
+
* ref={ref}
|
|
32
|
+
* class={isPressed() ? 'pressed' : ''}
|
|
33
|
+
* >
|
|
34
|
+
* {props.children}
|
|
35
|
+
* </button>
|
|
36
|
+
* );
|
|
37
|
+
* }
|
|
38
|
+
* ```
|
|
39
|
+
*/
|
|
40
|
+
export function createButton(props: AriaButtonProps = {}): ButtonAria {
|
|
41
|
+
const elementType = props.elementType ?? 'button';
|
|
42
|
+
|
|
43
|
+
const { pressProps, isPressed } = createPress({
|
|
44
|
+
isDisabled: props.isDisabled,
|
|
45
|
+
onPress: props.onPress,
|
|
46
|
+
onPressStart: props.onPressStart,
|
|
47
|
+
onPressEnd: props.onPressEnd,
|
|
48
|
+
onPressUp: props.onPressUp,
|
|
49
|
+
onPressChange: props.onPressChange,
|
|
50
|
+
onClick: props.onClick,
|
|
51
|
+
preventFocusOnPress: props.preventFocusOnPress,
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
const { focusableProps } = createFocusable({
|
|
55
|
+
isDisabled: props.isDisabled,
|
|
56
|
+
autoFocus: props.autoFocus,
|
|
57
|
+
excludeFromTabOrder: props.excludeFromTabOrder,
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
const isNativeButton = elementType === 'button' || elementType === 'input';
|
|
61
|
+
const isLink = elementType === 'a';
|
|
62
|
+
const disabled = isDisabledValue(props.isDisabled);
|
|
63
|
+
|
|
64
|
+
// Handle allowFocusWhenDisabled - set tabIndex to -1 when disabled but focusable
|
|
65
|
+
// This allows tooltips to be shown on disabled buttons
|
|
66
|
+
if (props.allowFocusWhenDisabled && disabled) {
|
|
67
|
+
(focusableProps as Record<string, unknown>).tabIndex = -1;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Build base props based on element type
|
|
71
|
+
let additionalProps: Record<string, unknown> = {};
|
|
72
|
+
|
|
73
|
+
if (isNativeButton) {
|
|
74
|
+
additionalProps = {
|
|
75
|
+
type: props.type ?? 'button',
|
|
76
|
+
disabled: disabled,
|
|
77
|
+
// Form-related attributes
|
|
78
|
+
...(props.form && { form: props.form }),
|
|
79
|
+
...(props.formAction && { formAction: props.formAction }),
|
|
80
|
+
...(props.formEncType && { formEncType: props.formEncType }),
|
|
81
|
+
...(props.formMethod && { formMethod: props.formMethod }),
|
|
82
|
+
...(props.formNoValidate && { formNoValidate: props.formNoValidate }),
|
|
83
|
+
...(props.formTarget && { formTarget: props.formTarget }),
|
|
84
|
+
...(props.name && { name: props.name }),
|
|
85
|
+
...(props.value && { value: props.value }),
|
|
86
|
+
};
|
|
87
|
+
} else {
|
|
88
|
+
// Non-native buttons need role and tabIndex
|
|
89
|
+
additionalProps = {
|
|
90
|
+
role: 'button',
|
|
91
|
+
tabIndex: disabled ? undefined : 0,
|
|
92
|
+
'aria-disabled': disabled ? true : undefined,
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
if (isLink) {
|
|
96
|
+
additionalProps.href = props.href;
|
|
97
|
+
additionalProps.target = props.target;
|
|
98
|
+
additionalProps.rel = props.rel;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// ARIA attributes
|
|
103
|
+
const ariaProps: Record<string, unknown> = {};
|
|
104
|
+
|
|
105
|
+
if (props['aria-pressed'] !== undefined) {
|
|
106
|
+
ariaProps['aria-pressed'] = props['aria-pressed'];
|
|
107
|
+
}
|
|
108
|
+
if (props['aria-haspopup'] !== undefined) {
|
|
109
|
+
ariaProps['aria-haspopup'] = props['aria-haspopup'];
|
|
110
|
+
}
|
|
111
|
+
if (props['aria-expanded'] !== undefined) {
|
|
112
|
+
ariaProps['aria-expanded'] = props['aria-expanded'];
|
|
113
|
+
}
|
|
114
|
+
if (props['aria-label']) {
|
|
115
|
+
ariaProps['aria-label'] = props['aria-label'];
|
|
116
|
+
}
|
|
117
|
+
if (props['aria-labelledby']) {
|
|
118
|
+
ariaProps['aria-labelledby'] = props['aria-labelledby'];
|
|
119
|
+
}
|
|
120
|
+
if (props['aria-describedby']) {
|
|
121
|
+
ariaProps['aria-describedby'] = props['aria-describedby'];
|
|
122
|
+
}
|
|
123
|
+
if (props['aria-controls']) {
|
|
124
|
+
ariaProps['aria-controls'] = props['aria-controls'];
|
|
125
|
+
}
|
|
126
|
+
if (props['aria-current'] !== undefined) {
|
|
127
|
+
ariaProps['aria-current'] = props['aria-current'];
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const buttonProps = mergeProps(
|
|
131
|
+
filterDOMProps(props as Record<string, unknown>, { labelable: true }),
|
|
132
|
+
additionalProps,
|
|
133
|
+
ariaProps,
|
|
134
|
+
focusableProps as Record<string, unknown>,
|
|
135
|
+
pressProps as Record<string, unknown>
|
|
136
|
+
);
|
|
137
|
+
|
|
138
|
+
return {
|
|
139
|
+
buttonProps,
|
|
140
|
+
isPressed,
|
|
141
|
+
};
|
|
142
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { Accessor, createSignal } from 'solid-js';
|
|
2
|
+
import { createButton } from './createButton';
|
|
3
|
+
import { mergeProps } from '../utils';
|
|
4
|
+
import type { AriaButtonProps, ButtonAria } from './types';
|
|
5
|
+
import type { PressEvent } from '../interactions';
|
|
6
|
+
|
|
7
|
+
export interface AriaToggleButtonProps extends Omit<AriaButtonProps, 'aria-pressed'> {
|
|
8
|
+
/** Whether the button is selected (controlled). */
|
|
9
|
+
isSelected?: Accessor<boolean> | boolean;
|
|
10
|
+
/** Handler called when the button's selection state changes. */
|
|
11
|
+
onChange?: (isSelected: boolean) => void;
|
|
12
|
+
/** The default selected state (uncontrolled). */
|
|
13
|
+
defaultSelected?: boolean;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface ToggleButtonAria extends ButtonAria {
|
|
17
|
+
/** Whether the button is currently selected. */
|
|
18
|
+
isSelected: Accessor<boolean>;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function getSelectedValue(isSelected: Accessor<boolean> | boolean | undefined): boolean {
|
|
22
|
+
if (typeof isSelected === 'function') {
|
|
23
|
+
return isSelected();
|
|
24
|
+
}
|
|
25
|
+
return isSelected ?? false;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Provides the behavior and accessibility implementation for a toggle button component.
|
|
30
|
+
* Toggle buttons allow users to toggle a selection on or off.
|
|
31
|
+
*
|
|
32
|
+
* Based on react-aria's useToggleButton but adapted for SolidJS.
|
|
33
|
+
*
|
|
34
|
+
* @example
|
|
35
|
+
* ```tsx
|
|
36
|
+
* import { createToggleButton } from 'solidaria';
|
|
37
|
+
*
|
|
38
|
+
* function ToggleButton(props) {
|
|
39
|
+
* const { buttonProps, isPressed, isSelected } = createToggleButton(props);
|
|
40
|
+
*
|
|
41
|
+
* return (
|
|
42
|
+
* <button
|
|
43
|
+
* {...buttonProps}
|
|
44
|
+
* class={isSelected() ? 'selected' : ''}
|
|
45
|
+
* style={{ opacity: isPressed() ? 0.8 : 1 }}
|
|
46
|
+
* >
|
|
47
|
+
* {props.children}
|
|
48
|
+
* </button>
|
|
49
|
+
* );
|
|
50
|
+
* }
|
|
51
|
+
* ```
|
|
52
|
+
*/
|
|
53
|
+
export function createToggleButton(props: AriaToggleButtonProps = {}): ToggleButtonAria {
|
|
54
|
+
// Handle controlled vs uncontrolled state
|
|
55
|
+
const isControlled = props.isSelected !== undefined;
|
|
56
|
+
const [uncontrolledSelected, setUncontrolledSelected] = createSignal(
|
|
57
|
+
props.defaultSelected ?? false
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
const isSelected = (): boolean => {
|
|
61
|
+
if (isControlled) {
|
|
62
|
+
return getSelectedValue(props.isSelected);
|
|
63
|
+
}
|
|
64
|
+
return uncontrolledSelected();
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
const toggleSelection = () => {
|
|
68
|
+
const newValue = !isSelected();
|
|
69
|
+
if (!isControlled) {
|
|
70
|
+
setUncontrolledSelected(newValue);
|
|
71
|
+
}
|
|
72
|
+
props.onChange?.(newValue);
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
// Create the press handler that toggles selection
|
|
76
|
+
const onPress = (e: PressEvent) => {
|
|
77
|
+
toggleSelection();
|
|
78
|
+
props.onPress?.(e);
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
// Get button props with our custom press handler
|
|
82
|
+
const { buttonProps: baseButtonProps, isPressed } = createButton(
|
|
83
|
+
mergeProps(props, {
|
|
84
|
+
onPress,
|
|
85
|
+
}) as AriaButtonProps
|
|
86
|
+
);
|
|
87
|
+
|
|
88
|
+
// Create buttonProps with a getter for aria-pressed so it stays reactive
|
|
89
|
+
const buttonProps = {
|
|
90
|
+
...baseButtonProps,
|
|
91
|
+
get 'aria-pressed'() {
|
|
92
|
+
return isSelected();
|
|
93
|
+
},
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
return {
|
|
97
|
+
buttonProps,
|
|
98
|
+
isPressed,
|
|
99
|
+
isSelected,
|
|
100
|
+
};
|
|
101
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { Accessor } from 'solid-js';
|
|
2
|
+
import { PressEvent } from '../interactions';
|
|
3
|
+
|
|
4
|
+
export interface AriaButtonProps {
|
|
5
|
+
/** Whether the button is disabled. */
|
|
6
|
+
isDisabled?: Accessor<boolean> | boolean;
|
|
7
|
+
/** Handler called when the press is released over the target. */
|
|
8
|
+
onPress?: (e: PressEvent) => void;
|
|
9
|
+
/** Handler called when a press interaction starts. */
|
|
10
|
+
onPressStart?: (e: PressEvent) => void;
|
|
11
|
+
/** Handler called when a press interaction ends. */
|
|
12
|
+
onPressEnd?: (e: PressEvent) => void;
|
|
13
|
+
/** Handler called when a press is released over the target. */
|
|
14
|
+
onPressUp?: (e: PressEvent) => void;
|
|
15
|
+
/** Handler called when the press state changes. */
|
|
16
|
+
onPressChange?: (isPressed: boolean) => void;
|
|
17
|
+
/**
|
|
18
|
+
* A native click handler, useful for form submission.
|
|
19
|
+
* Note: `onPress` is preferred for cross-device compatibility.
|
|
20
|
+
*/
|
|
21
|
+
onClick?: (e: MouseEvent) => void;
|
|
22
|
+
/** Whether the button should not receive focus on press. */
|
|
23
|
+
preventFocusOnPress?: boolean;
|
|
24
|
+
/**
|
|
25
|
+
* Whether to allow focus on the button when it is disabled.
|
|
26
|
+
* This enables showing tooltips on disabled buttons.
|
|
27
|
+
* When true, the button gets `tabIndex=-1` instead of being removed from tab order.
|
|
28
|
+
*/
|
|
29
|
+
allowFocusWhenDisabled?: boolean;
|
|
30
|
+
/** Whether the element should receive focus on render. */
|
|
31
|
+
autoFocus?: boolean;
|
|
32
|
+
/** The HTML element type to use for the button. */
|
|
33
|
+
elementType?: 'button' | 'a' | 'div' | 'input' | 'span';
|
|
34
|
+
/** The URL to link to (for anchor elements). */
|
|
35
|
+
href?: string;
|
|
36
|
+
/** The target for the link (for anchor elements). */
|
|
37
|
+
target?: string;
|
|
38
|
+
/** The rel attribute for the link (for anchor elements). */
|
|
39
|
+
rel?: string;
|
|
40
|
+
/** The type attribute for button elements. */
|
|
41
|
+
type?: 'button' | 'submit' | 'reset';
|
|
42
|
+
/** Whether the button is in a pressed state (controlled). */
|
|
43
|
+
'aria-pressed'?: boolean | 'true' | 'false' | 'mixed';
|
|
44
|
+
/** Whether the button has a popup. */
|
|
45
|
+
'aria-haspopup'?: boolean | 'menu' | 'listbox' | 'tree' | 'grid' | 'dialog' | 'true' | 'false';
|
|
46
|
+
/** Whether the popup is expanded. */
|
|
47
|
+
'aria-expanded'?: boolean | 'true' | 'false';
|
|
48
|
+
/** The accessible label for the button. */
|
|
49
|
+
'aria-label'?: string;
|
|
50
|
+
/** The id of the element that labels the button. */
|
|
51
|
+
'aria-labelledby'?: string;
|
|
52
|
+
/** The id of the element that describes the button. */
|
|
53
|
+
'aria-describedby'?: string;
|
|
54
|
+
/** Identifies the element (or elements) whose contents or presence are controlled by the button. */
|
|
55
|
+
'aria-controls'?: string;
|
|
56
|
+
/** Indicates the current "pressed" state of toggle buttons. */
|
|
57
|
+
'aria-current'?: boolean | 'page' | 'step' | 'location' | 'date' | 'time' | 'true' | 'false';
|
|
58
|
+
/** Additional attributes for form buttons. */
|
|
59
|
+
form?: string;
|
|
60
|
+
formAction?: string;
|
|
61
|
+
formEncType?: string;
|
|
62
|
+
formMethod?: string;
|
|
63
|
+
formNoValidate?: boolean;
|
|
64
|
+
formTarget?: string;
|
|
65
|
+
/** The name attribute for form buttons. */
|
|
66
|
+
name?: string;
|
|
67
|
+
/** The value attribute for form buttons. */
|
|
68
|
+
value?: string;
|
|
69
|
+
/** Whether to exclude the button from the tab order. */
|
|
70
|
+
excludeFromTabOrder?: boolean;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export interface ButtonAria {
|
|
74
|
+
/** Props to spread on the button element. */
|
|
75
|
+
buttonProps: Record<string, unknown>;
|
|
76
|
+
/** Whether the button is currently pressed. */
|
|
77
|
+
isPressed: Accessor<boolean>;
|
|
78
|
+
}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* createCalendar hook for Solidaria
|
|
3
|
+
*
|
|
4
|
+
* Provides the behavior and accessibility implementation for a calendar component.
|
|
5
|
+
* Based on @react-aria/calendar useCalendar
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { createMemo } from 'solid-js';
|
|
9
|
+
import { createId } from '../ssr';
|
|
10
|
+
import { access, type MaybeAccessor } from '../utils/reactivity';
|
|
11
|
+
import { mergeProps } from '../utils/mergeProps';
|
|
12
|
+
import type { CalendarState } from '@proyecto-viviana/solid-stately';
|
|
13
|
+
|
|
14
|
+
// ============================================
|
|
15
|
+
// TYPES
|
|
16
|
+
// ============================================
|
|
17
|
+
|
|
18
|
+
export interface AriaCalendarProps {
|
|
19
|
+
/** An ID for the calendar. */
|
|
20
|
+
id?: string;
|
|
21
|
+
/** Whether the calendar is disabled. */
|
|
22
|
+
isDisabled?: boolean;
|
|
23
|
+
/** Whether the calendar is read-only. */
|
|
24
|
+
isReadOnly?: boolean;
|
|
25
|
+
/** An accessible label for the calendar. */
|
|
26
|
+
'aria-label'?: string;
|
|
27
|
+
/** The ID of an element that labels the calendar. */
|
|
28
|
+
'aria-labelledby'?: string;
|
|
29
|
+
/** The ID of an element that describes the calendar. */
|
|
30
|
+
'aria-describedby'?: string;
|
|
31
|
+
/** Minimum number of visible months. */
|
|
32
|
+
visibleMonths?: number;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface CalendarAria {
|
|
36
|
+
/** Props for the calendar container element. */
|
|
37
|
+
calendarProps: Record<string, unknown>;
|
|
38
|
+
/** Props for the previous button. */
|
|
39
|
+
prevButtonProps: Record<string, unknown>;
|
|
40
|
+
/** Props for the next button. */
|
|
41
|
+
nextButtonProps: Record<string, unknown>;
|
|
42
|
+
/** Props for the title/heading element. */
|
|
43
|
+
titleProps: Record<string, unknown>;
|
|
44
|
+
/** An accessible label for the title. */
|
|
45
|
+
title: string;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// ============================================
|
|
49
|
+
// IMPLEMENTATION
|
|
50
|
+
// ============================================
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Provides the behavior and accessibility implementation for a calendar component.
|
|
54
|
+
*/
|
|
55
|
+
export function createCalendar<T extends CalendarState>(
|
|
56
|
+
props: MaybeAccessor<AriaCalendarProps>,
|
|
57
|
+
state: T
|
|
58
|
+
): CalendarAria {
|
|
59
|
+
const getProps = () => access(props);
|
|
60
|
+
const id = createId(getProps().id);
|
|
61
|
+
const titleId = createId();
|
|
62
|
+
|
|
63
|
+
// Title (e.g., "December 2024")
|
|
64
|
+
const title = createMemo(() => state.title());
|
|
65
|
+
|
|
66
|
+
// Previous button props
|
|
67
|
+
const prevButtonProps = createMemo(() => {
|
|
68
|
+
const p = getProps();
|
|
69
|
+
const isDisabled = p.isDisabled || state.isDisabled();
|
|
70
|
+
|
|
71
|
+
return {
|
|
72
|
+
'aria-label': 'Previous month',
|
|
73
|
+
onClick: () => {
|
|
74
|
+
if (!isDisabled) {
|
|
75
|
+
state.focusPreviousPage();
|
|
76
|
+
}
|
|
77
|
+
},
|
|
78
|
+
disabled: isDisabled,
|
|
79
|
+
tabIndex: -1,
|
|
80
|
+
};
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
// Next button props
|
|
84
|
+
const nextButtonProps = createMemo(() => {
|
|
85
|
+
const p = getProps();
|
|
86
|
+
const isDisabled = p.isDisabled || state.isDisabled();
|
|
87
|
+
|
|
88
|
+
return {
|
|
89
|
+
'aria-label': 'Next month',
|
|
90
|
+
onClick: () => {
|
|
91
|
+
if (!isDisabled) {
|
|
92
|
+
state.focusNextPage();
|
|
93
|
+
}
|
|
94
|
+
},
|
|
95
|
+
disabled: isDisabled,
|
|
96
|
+
tabIndex: -1,
|
|
97
|
+
};
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
// Title props
|
|
101
|
+
const titleProps = createMemo(() => ({
|
|
102
|
+
id: titleId,
|
|
103
|
+
'aria-live': 'polite' as const,
|
|
104
|
+
}));
|
|
105
|
+
|
|
106
|
+
// Calendar container props
|
|
107
|
+
const calendarProps = createMemo(() => {
|
|
108
|
+
const p = getProps();
|
|
109
|
+
|
|
110
|
+
return mergeProps(
|
|
111
|
+
{
|
|
112
|
+
id,
|
|
113
|
+
role: 'group',
|
|
114
|
+
'aria-labelledby': p['aria-labelledby'] ?? titleId,
|
|
115
|
+
'aria-label': p['aria-label'],
|
|
116
|
+
'aria-describedby': p['aria-describedby'],
|
|
117
|
+
}
|
|
118
|
+
);
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
return {
|
|
122
|
+
get calendarProps() {
|
|
123
|
+
return calendarProps();
|
|
124
|
+
},
|
|
125
|
+
get prevButtonProps() {
|
|
126
|
+
return prevButtonProps();
|
|
127
|
+
},
|
|
128
|
+
get nextButtonProps() {
|
|
129
|
+
return nextButtonProps();
|
|
130
|
+
},
|
|
131
|
+
get titleProps() {
|
|
132
|
+
return titleProps();
|
|
133
|
+
},
|
|
134
|
+
get title() {
|
|
135
|
+
return title();
|
|
136
|
+
},
|
|
137
|
+
};
|
|
138
|
+
}
|