@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.
Files changed (210) hide show
  1. package/dist/autocomplete/createAutocomplete.d.ts +2 -2
  2. package/dist/autocomplete/createAutocomplete.d.ts.map +1 -1
  3. package/dist/index.js +233 -234
  4. package/dist/index.js.map +2 -2
  5. package/dist/index.ssr.js +233 -234
  6. package/dist/index.ssr.js.map +2 -2
  7. package/dist/interactions/PressEvent.d.ts +13 -10
  8. package/dist/interactions/PressEvent.d.ts.map +1 -1
  9. package/dist/interactions/createPress.d.ts.map +1 -1
  10. package/dist/interactions/index.d.ts +1 -1
  11. package/dist/interactions/index.d.ts.map +1 -1
  12. package/dist/select/createHiddenSelect.d.ts.map +1 -1
  13. package/dist/toolbar/createToolbar.d.ts.map +1 -1
  14. package/dist/tooltip/createTooltipTrigger.d.ts.map +1 -1
  15. package/package.json +9 -7
  16. package/src/autocomplete/createAutocomplete.ts +341 -0
  17. package/src/autocomplete/index.ts +9 -0
  18. package/src/breadcrumbs/createBreadcrumbs.ts +196 -0
  19. package/src/breadcrumbs/index.ts +8 -0
  20. package/src/button/createButton.ts +142 -0
  21. package/src/button/createToggleButton.ts +101 -0
  22. package/src/button/index.ts +4 -0
  23. package/src/button/types.ts +78 -0
  24. package/src/calendar/createCalendar.ts +138 -0
  25. package/src/calendar/createCalendarCell.ts +187 -0
  26. package/src/calendar/createCalendarGrid.ts +140 -0
  27. package/src/calendar/createRangeCalendar.ts +136 -0
  28. package/src/calendar/createRangeCalendarCell.ts +186 -0
  29. package/src/calendar/index.ts +34 -0
  30. package/src/checkbox/createCheckbox.ts +135 -0
  31. package/src/checkbox/createCheckboxGroup.ts +137 -0
  32. package/src/checkbox/createCheckboxGroupItem.ts +117 -0
  33. package/src/checkbox/createCheckboxGroupState.ts +193 -0
  34. package/src/checkbox/index.ts +13 -0
  35. package/src/color/createColorArea.ts +314 -0
  36. package/src/color/createColorField.ts +137 -0
  37. package/src/color/createColorSlider.ts +197 -0
  38. package/src/color/createColorSwatch.ts +40 -0
  39. package/src/color/createColorWheel.ts +208 -0
  40. package/src/color/index.ts +24 -0
  41. package/src/color/types.ts +116 -0
  42. package/src/combobox/createComboBox.ts +647 -0
  43. package/src/combobox/index.ts +6 -0
  44. package/src/combobox/intl/en-US.json +7 -0
  45. package/src/combobox/intl/es-ES.json +7 -0
  46. package/src/combobox/intl/index.ts +23 -0
  47. package/src/datepicker/createDateField.ts +154 -0
  48. package/src/datepicker/createDatePicker.ts +206 -0
  49. package/src/datepicker/createDateSegment.ts +229 -0
  50. package/src/datepicker/createTimeField.ts +154 -0
  51. package/src/datepicker/index.ts +28 -0
  52. package/src/dialog/createDialog.ts +120 -0
  53. package/src/dialog/index.ts +2 -0
  54. package/src/dialog/types.ts +19 -0
  55. package/src/disclosure/createDisclosure.ts +131 -0
  56. package/src/disclosure/createDisclosureGroup.ts +62 -0
  57. package/src/disclosure/index.ts +11 -0
  58. package/src/dnd/createDrag.ts +209 -0
  59. package/src/dnd/createDraggableCollection.ts +63 -0
  60. package/src/dnd/createDraggableItem.ts +243 -0
  61. package/src/dnd/createDrop.ts +321 -0
  62. package/src/dnd/createDroppableCollection.ts +293 -0
  63. package/src/dnd/createDroppableItem.ts +213 -0
  64. package/src/dnd/index.ts +47 -0
  65. package/src/dnd/types.ts +89 -0
  66. package/src/dnd/utils.ts +294 -0
  67. package/src/focus/FocusScope.tsx +408 -0
  68. package/src/focus/createAutoFocus.ts +321 -0
  69. package/src/focus/createFocusRestore.ts +313 -0
  70. package/src/focus/createVirtualFocus.ts +396 -0
  71. package/src/focus/index.ts +35 -0
  72. package/src/form/createFormReset.ts +51 -0
  73. package/src/form/createFormValidation.ts +224 -0
  74. package/src/form/index.ts +11 -0
  75. package/src/grid/GridKeyboardDelegate.ts +429 -0
  76. package/src/grid/createGrid.ts +261 -0
  77. package/src/grid/createGridCell.ts +182 -0
  78. package/src/grid/createGridRow.ts +153 -0
  79. package/src/grid/index.ts +18 -0
  80. package/src/grid/types.ts +133 -0
  81. package/src/gridlist/createGridList.ts +185 -0
  82. package/src/gridlist/createGridListItem.ts +180 -0
  83. package/src/gridlist/createGridListSelectionCheckbox.ts +59 -0
  84. package/src/gridlist/index.ts +16 -0
  85. package/src/gridlist/types.ts +81 -0
  86. package/src/i18n/NumberFormatter.ts +266 -0
  87. package/src/i18n/createCollator.ts +79 -0
  88. package/src/i18n/createDateFormatter.ts +83 -0
  89. package/src/i18n/createFilter.ts +131 -0
  90. package/src/i18n/createNumberFormatter.ts +52 -0
  91. package/src/i18n/createStringFormatter.ts +87 -0
  92. package/src/i18n/index.ts +40 -0
  93. package/src/i18n/locale.tsx +188 -0
  94. package/src/i18n/utils.ts +99 -0
  95. package/src/index.ts +670 -0
  96. package/src/interactions/FocusableProvider.tsx +44 -0
  97. package/src/interactions/PressEvent.ts +126 -0
  98. package/src/interactions/createFocus.ts +163 -0
  99. package/src/interactions/createFocusRing.ts +89 -0
  100. package/src/interactions/createFocusWithin.ts +206 -0
  101. package/src/interactions/createFocusable.ts +168 -0
  102. package/src/interactions/createHover.ts +254 -0
  103. package/src/interactions/createInteractionModality.ts +424 -0
  104. package/src/interactions/createKeyboard.ts +82 -0
  105. package/src/interactions/createLongPress.ts +174 -0
  106. package/src/interactions/createMove.ts +289 -0
  107. package/src/interactions/createPress.ts +834 -0
  108. package/src/interactions/index.ts +78 -0
  109. package/src/label/createField.ts +145 -0
  110. package/src/label/createLabel.ts +117 -0
  111. package/src/label/createLabels.ts +50 -0
  112. package/src/label/index.ts +19 -0
  113. package/src/landmark/createLandmark.ts +377 -0
  114. package/src/landmark/index.ts +8 -0
  115. package/src/link/createLink.ts +182 -0
  116. package/src/link/index.ts +1 -0
  117. package/src/listbox/createListBox.ts +269 -0
  118. package/src/listbox/createOption.ts +151 -0
  119. package/src/listbox/index.ts +12 -0
  120. package/src/live-announcer/announce.ts +322 -0
  121. package/src/live-announcer/index.ts +9 -0
  122. package/src/menu/createMenu.ts +396 -0
  123. package/src/menu/createMenuItem.ts +149 -0
  124. package/src/menu/createMenuTrigger.ts +88 -0
  125. package/src/menu/index.ts +18 -0
  126. package/src/meter/createMeter.ts +75 -0
  127. package/src/meter/index.ts +1 -0
  128. package/src/numberfield/createNumberField.ts +268 -0
  129. package/src/numberfield/index.ts +5 -0
  130. package/src/overlays/ariaHideOutside.ts +219 -0
  131. package/src/overlays/createInteractOutside.ts +149 -0
  132. package/src/overlays/createModal.tsx +202 -0
  133. package/src/overlays/createOverlay.ts +155 -0
  134. package/src/overlays/createOverlayTrigger.ts +85 -0
  135. package/src/overlays/createPreventScroll.ts +266 -0
  136. package/src/overlays/index.ts +44 -0
  137. package/src/popover/calculatePosition.ts +766 -0
  138. package/src/popover/createOverlayPosition.ts +356 -0
  139. package/src/popover/createPopover.ts +170 -0
  140. package/src/popover/index.ts +24 -0
  141. package/src/progress/createProgressBar.ts +128 -0
  142. package/src/progress/index.ts +5 -0
  143. package/src/radio/createRadio.ts +287 -0
  144. package/src/radio/createRadioGroup.ts +189 -0
  145. package/src/radio/createRadioGroupState.ts +201 -0
  146. package/src/radio/index.ts +23 -0
  147. package/src/searchfield/createSearchField.ts +186 -0
  148. package/src/searchfield/index.ts +2 -0
  149. package/src/select/createHiddenSelect.tsx +236 -0
  150. package/src/select/createSelect.ts +395 -0
  151. package/src/select/index.ts +14 -0
  152. package/src/selection/createTypeSelect.ts +201 -0
  153. package/src/selection/index.ts +6 -0
  154. package/src/separator/createSeparator.ts +82 -0
  155. package/src/separator/index.ts +6 -0
  156. package/src/slider/createSlider.ts +349 -0
  157. package/src/slider/index.ts +2 -0
  158. package/src/ssr/index.tsx +370 -0
  159. package/src/switch/createSwitch.ts +70 -0
  160. package/src/switch/index.ts +1 -0
  161. package/src/table/createTable.ts +526 -0
  162. package/src/table/createTableCell.ts +147 -0
  163. package/src/table/createTableColumnHeader.ts +115 -0
  164. package/src/table/createTableHeaderRow.ts +40 -0
  165. package/src/table/createTableRow.ts +155 -0
  166. package/src/table/createTableRowGroup.ts +32 -0
  167. package/src/table/createTableSelectAllCheckbox.ts +73 -0
  168. package/src/table/createTableSelectionCheckbox.ts +59 -0
  169. package/src/table/index.ts +30 -0
  170. package/src/table/types.ts +165 -0
  171. package/src/tabs/createTabs.ts +472 -0
  172. package/src/tabs/index.ts +14 -0
  173. package/src/tag/createTag.ts +194 -0
  174. package/src/tag/createTagGroup.ts +154 -0
  175. package/src/tag/index.ts +12 -0
  176. package/src/textfield/createTextField.ts +198 -0
  177. package/src/textfield/index.ts +5 -0
  178. package/src/toast/createToast.ts +118 -0
  179. package/src/toast/createToastRegion.ts +100 -0
  180. package/src/toast/index.ts +11 -0
  181. package/src/toggle/createToggle.ts +223 -0
  182. package/src/toggle/createToggleState.ts +94 -0
  183. package/src/toggle/index.ts +7 -0
  184. package/src/toolbar/createToolbar.ts +369 -0
  185. package/src/toolbar/index.ts +6 -0
  186. package/src/tooltip/createTooltip.ts +79 -0
  187. package/src/tooltip/createTooltipTrigger.ts +222 -0
  188. package/src/tooltip/index.ts +6 -0
  189. package/src/tree/createTree.ts +246 -0
  190. package/src/tree/createTreeItem.ts +233 -0
  191. package/src/tree/createTreeSelectionCheckbox.ts +68 -0
  192. package/src/tree/index.ts +16 -0
  193. package/src/tree/types.ts +87 -0
  194. package/src/utils/createDescription.ts +137 -0
  195. package/src/utils/dom.ts +327 -0
  196. package/src/utils/env.ts +54 -0
  197. package/src/utils/events.ts +106 -0
  198. package/src/utils/filterDOMProps.ts +116 -0
  199. package/src/utils/focus.ts +151 -0
  200. package/src/utils/geometry.ts +115 -0
  201. package/src/utils/globalListeners.ts +142 -0
  202. package/src/utils/index.ts +80 -0
  203. package/src/utils/mergeProps.ts +52 -0
  204. package/src/utils/platform.ts +52 -0
  205. package/src/utils/reactivity.ts +36 -0
  206. package/src/utils/textSelection.ts +114 -0
  207. package/src/visually-hidden/createVisuallyHidden.ts +124 -0
  208. package/src/visually-hidden/index.ts +6 -0
  209. package/dist/index.jsx +0 -15845
  210. 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,8 @@
1
+ export {
2
+ createBreadcrumbs,
3
+ createBreadcrumbItem,
4
+ type AriaBreadcrumbsProps,
5
+ type BreadcrumbsAria,
6
+ type AriaBreadcrumbItemProps,
7
+ type BreadcrumbItemAria,
8
+ } from './createBreadcrumbs';
@@ -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,4 @@
1
+ export { createButton } from './createButton';
2
+ export { createToggleButton } from './createToggleButton';
3
+ export type { AriaButtonProps, ButtonAria } from './types';
4
+ export type { AriaToggleButtonProps, ToggleButtonAria } from './createToggleButton';
@@ -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
+ }