@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,194 @@
1
+ /**
2
+ * Tag hook for Solidaria
3
+ *
4
+ * Provides the behavior and accessibility implementation for a tag component.
5
+ * Tags are individual items within a TagGroup.
6
+ *
7
+ * Based on @react-aria/tag useTag
8
+ */
9
+
10
+ import { createMemo } from 'solid-js';
11
+ import { createFocusable } from '../interactions/createFocusable';
12
+ import { createPress } from '../interactions/createPress';
13
+ import { filterDOMProps } from '../utils/filterDOMProps';
14
+ import { mergeProps } from '../utils/mergeProps';
15
+ import { createId } from '../ssr';
16
+ import { access, type MaybeAccessor } from '../utils/reactivity';
17
+ import { getTagGroupData } from './createTagGroup';
18
+ import type { ListState, Key } from '@proyecto-viviana/solid-stately';
19
+
20
+ // ============================================
21
+ // TYPES
22
+ // ============================================
23
+
24
+ export interface AriaTagProps {
25
+ /** The unique key for this tag. */
26
+ key: Key;
27
+ /** Whether the tag is disabled. */
28
+ isDisabled?: boolean;
29
+ /** A text value for the tag used for accessibility. */
30
+ textValue?: string;
31
+ }
32
+
33
+ export interface TagAria {
34
+ /** Props for the tag row element. */
35
+ rowProps: Record<string, unknown>;
36
+ /** Props for the tag cell element. */
37
+ gridCellProps: Record<string, unknown>;
38
+ /** Props for the tag remove button. */
39
+ removeButtonProps: Record<string, unknown>;
40
+ /** Whether the tag can be removed. */
41
+ allowsRemoving: boolean;
42
+ /** Whether the tag is selected. */
43
+ isSelected: boolean;
44
+ /** Whether the tag is disabled. */
45
+ isDisabled: boolean;
46
+ /** Whether the tag is focused. */
47
+ isFocused: boolean;
48
+ /** Whether the tag is pressed. */
49
+ isPressed: boolean;
50
+ }
51
+
52
+ // ============================================
53
+ // IMPLEMENTATION
54
+ // ============================================
55
+
56
+ /**
57
+ * Provides the behavior and accessibility implementation for a tag component.
58
+ * Tags are individual items within a TagGroup.
59
+ */
60
+ export function createTag<T>(
61
+ props: MaybeAccessor<AriaTagProps>,
62
+ state: ListState<T>,
63
+ ref: () => HTMLElement | null
64
+ ): TagAria {
65
+ const getProps = () => access(props);
66
+ const rowId = createId();
67
+ const cellId = createId();
68
+ const removeButtonId = createId();
69
+
70
+ // Get shared data from tag group
71
+ const getData = () => getTagGroupData(state);
72
+
73
+ // Get key
74
+ const key = () => getProps().key;
75
+
76
+ // Compute states
77
+ const isDisabled = createMemo(() => {
78
+ const p = getProps();
79
+ return p.isDisabled || state.isDisabled(key());
80
+ });
81
+
82
+ const isSelected = createMemo(() => {
83
+ return state.isSelected(key());
84
+ });
85
+
86
+ const isFocused = createMemo(() => {
87
+ return state.focusedKey() === key();
88
+ });
89
+
90
+ // Handle press for selection
91
+ const { pressProps, isPressed } = createPress({
92
+ isDisabled,
93
+ onPress: () => {
94
+ if (!isDisabled()) {
95
+ state.toggleSelection(key());
96
+ }
97
+ },
98
+ });
99
+
100
+ // Handle focusable
101
+ const { focusableProps } = createFocusable({
102
+ isDisabled,
103
+ }, ref);
104
+
105
+ // Handle keyboard for removal
106
+ const handleKeyDown = (e: KeyboardEvent) => {
107
+ if (isDisabled()) return;
108
+
109
+ if (e.key === 'Delete' || e.key === 'Backspace') {
110
+ e.preventDefault();
111
+ const data = getData();
112
+ if (data?.onRemove) {
113
+ // Remove selected keys if this tag is selected, otherwise just this tag
114
+ if (isSelected()) {
115
+ const selection = state.selectedKeys();
116
+ const keysToRemove = selection === 'all'
117
+ ? new Set(Array.from(state.collection()).map(item => (item as { key: Key }).key))
118
+ : new Set(selection);
119
+ data.onRemove(keysToRemove);
120
+ } else {
121
+ data.onRemove(new Set([key()]));
122
+ }
123
+ }
124
+ }
125
+ };
126
+
127
+ // Compute tabIndex
128
+ const tabIndex = createMemo(() => {
129
+ if (isDisabled()) return -1;
130
+ // If this is the focused item, or if nothing is focused yet
131
+ if (isFocused() || state.focusedKey() === null) {
132
+ return 0;
133
+ }
134
+ return -1;
135
+ });
136
+
137
+ // Filter DOM props
138
+ const domProps = () => filterDOMProps(getProps() as unknown as Record<string, unknown>);
139
+
140
+ // Check if removal is allowed
141
+ const allowsRemoving = createMemo(() => {
142
+ const data = getData();
143
+ return !!data?.onRemove;
144
+ });
145
+
146
+ return {
147
+ get rowProps() {
148
+ return mergeProps(domProps(), focusableProps as Record<string, unknown>, pressProps as Record<string, unknown>, {
149
+ id: rowId,
150
+ role: 'row',
151
+ tabIndex: tabIndex(),
152
+ 'aria-selected': isSelected(),
153
+ 'aria-disabled': isDisabled() || undefined,
154
+ onKeyDown: handleKeyDown,
155
+ });
156
+ },
157
+ get gridCellProps() {
158
+ return {
159
+ id: cellId,
160
+ role: 'gridcell',
161
+ 'aria-describedby': allowsRemoving() ? removeButtonId : undefined,
162
+ };
163
+ },
164
+ get removeButtonProps() {
165
+ const data = getData();
166
+ return {
167
+ id: removeButtonId,
168
+ 'aria-label': 'Remove',
169
+ 'aria-labelledby': `${removeButtonId} ${rowId}`,
170
+ isDisabled: isDisabled(),
171
+ onPress: () => {
172
+ if (data?.onRemove && !isDisabled()) {
173
+ data.onRemove(new Set([key()]));
174
+ }
175
+ },
176
+ };
177
+ },
178
+ get allowsRemoving() {
179
+ return allowsRemoving();
180
+ },
181
+ get isSelected() {
182
+ return isSelected();
183
+ },
184
+ get isDisabled() {
185
+ return isDisabled();
186
+ },
187
+ get isFocused() {
188
+ return isFocused();
189
+ },
190
+ get isPressed() {
191
+ return isPressed();
192
+ },
193
+ };
194
+ }
@@ -0,0 +1,154 @@
1
+ /**
2
+ * TagGroup hook for Solidaria
3
+ *
4
+ * Provides the behavior and accessibility implementation for a tag group component.
5
+ * A tag group is a focusable list of labels, categories, keywords, filters, or other items,
6
+ * with support for keyboard navigation, selection, and removal.
7
+ *
8
+ * Based on @react-aria/tag useTagGroup
9
+ */
10
+
11
+ import { createEffect, onCleanup } from 'solid-js';
12
+ import { createLabel } from '../label/createLabel';
13
+ import { filterDOMProps } from '../utils/filterDOMProps';
14
+ import { mergeProps } from '../utils/mergeProps';
15
+ import { createId } from '../ssr';
16
+ import { access, type MaybeAccessor } from '../utils/reactivity';
17
+ import type { ListState, Key } from '@proyecto-viviana/solid-stately';
18
+
19
+ // ============================================
20
+ // TYPES
21
+ // ============================================
22
+
23
+ export interface AriaTagGroupProps {
24
+ /** An ID for the tag group. */
25
+ id?: string;
26
+ /** Whether the tag group is disabled. */
27
+ isDisabled?: boolean;
28
+ /** The label for the tag group. */
29
+ label?: string;
30
+ /** An accessible label for the tag group when no visible label is provided. */
31
+ 'aria-label'?: string;
32
+ /** The ID of an element that labels the tag group. */
33
+ 'aria-labelledby'?: string;
34
+ /** The ID of an element that describes the tag group. */
35
+ 'aria-describedby'?: string;
36
+ /** A description of the tag group. */
37
+ description?: string;
38
+ /** An error message for the tag group. */
39
+ errorMessage?: string;
40
+ /** Handler that is called when a user removes a tag. */
41
+ onRemove?: (keys: Set<Key>) => void;
42
+ }
43
+
44
+ export interface TagGroupAria {
45
+ /** Props for the tag group container element. */
46
+ gridProps: Record<string, unknown>;
47
+ /** Props for the tag group's visible label (if any). */
48
+ labelProps: Record<string, unknown>;
49
+ /** Props for the tag group description element, if any. */
50
+ descriptionProps: Record<string, unknown>;
51
+ /** Props for the tag group error message element, if any. */
52
+ errorMessageProps: Record<string, unknown>;
53
+ }
54
+
55
+ // Shared data between tag group and tags
56
+ const tagGroupData = new WeakMap<object, TagGroupData>();
57
+
58
+ interface TagGroupData {
59
+ id: string;
60
+ onRemove?: (keys: Set<Key>) => void;
61
+ }
62
+
63
+ export function getTagGroupData(state: ListState): TagGroupData | undefined {
64
+ return tagGroupData.get(state);
65
+ }
66
+
67
+ // ============================================
68
+ // IMPLEMENTATION
69
+ // ============================================
70
+
71
+ /**
72
+ * Provides the behavior and accessibility implementation for a tag group component.
73
+ * A tag group is a focusable list of labels, categories, keywords, filters, or other items,
74
+ * with support for keyboard navigation, selection, and removal.
75
+ */
76
+ export function createTagGroup<T>(
77
+ props: MaybeAccessor<AriaTagGroupProps>,
78
+ state: ListState<T>,
79
+ _ref?: () => HTMLElement | null
80
+ ): TagGroupAria {
81
+ const getProps = () => access(props);
82
+ const id = createId(getProps().id);
83
+ const descriptionId = createId();
84
+ const errorMessageId = createId();
85
+
86
+ // Filter DOM props
87
+ const domProps = () => filterDOMProps(getProps() as unknown as Record<string, unknown>, { labelable: true });
88
+
89
+ // Create label handling
90
+ const { labelProps, fieldProps } = createLabel({
91
+ get label() { return getProps().label; },
92
+ get 'aria-label'() { return getProps()['aria-label']; },
93
+ get 'aria-labelledby'() { return getProps()['aria-labelledby']; },
94
+ labelElementType: 'span',
95
+ });
96
+
97
+ // Share data with child tags
98
+ createEffect(() => {
99
+ const p = getProps();
100
+ tagGroupData.set(state, {
101
+ id,
102
+ onRemove: p.onRemove,
103
+ });
104
+
105
+ onCleanup(() => {
106
+ tagGroupData.delete(state);
107
+ });
108
+ });
109
+
110
+ // Build aria-describedby
111
+ const getAriaDescribedBy = () => {
112
+ const p = getProps();
113
+ const ids: string[] = [];
114
+ if (p['aria-describedby']) {
115
+ ids.push(p['aria-describedby']);
116
+ }
117
+ if (p.description) {
118
+ ids.push(descriptionId);
119
+ }
120
+ if (p.errorMessage) {
121
+ ids.push(errorMessageId);
122
+ }
123
+ return ids.length > 0 ? ids.join(' ') : undefined;
124
+ };
125
+
126
+ return {
127
+ get gridProps() {
128
+ const p = getProps();
129
+ const hasItems = state.collection().size > 0;
130
+
131
+ return mergeProps(domProps(), fieldProps as Record<string, unknown>, {
132
+ id,
133
+ role: hasItems ? 'grid' : 'group',
134
+ 'aria-atomic': false,
135
+ 'aria-relevant': 'additions',
136
+ 'aria-describedby': getAriaDescribedBy(),
137
+ 'aria-disabled': p.isDisabled || undefined,
138
+ });
139
+ },
140
+ get labelProps() {
141
+ return labelProps as Record<string, unknown>;
142
+ },
143
+ get descriptionProps() {
144
+ return {
145
+ id: descriptionId,
146
+ };
147
+ },
148
+ get errorMessageProps() {
149
+ return {
150
+ id: errorMessageId,
151
+ };
152
+ },
153
+ };
154
+ }
@@ -0,0 +1,12 @@
1
+ export {
2
+ createTagGroup,
3
+ getTagGroupData,
4
+ type AriaTagGroupProps,
5
+ type TagGroupAria,
6
+ } from './createTagGroup';
7
+
8
+ export {
9
+ createTag,
10
+ type AriaTagProps,
11
+ type TagAria,
12
+ } from './createTag';
@@ -0,0 +1,198 @@
1
+ /**
2
+ * TextField hook for Solidaria
3
+ *
4
+ * Provides the behavior and accessibility implementation for a text field.
5
+ *
6
+ * This is a 1:1 port of @react-aria/textfield's useTextField hook.
7
+ */
8
+
9
+ import { JSX } from 'solid-js';
10
+ import { createField, type AriaFieldProps, type FieldAria } from '../label';
11
+ import { createFocusable, type FocusableProps } from '../interactions';
12
+ import { mergeProps, filterDOMProps } from '../utils';
13
+ import { type MaybeAccessor, access } from '../utils/reactivity';
14
+
15
+ // ============================================
16
+ // TYPES
17
+ // ============================================
18
+
19
+ export interface AriaTextFieldProps extends AriaFieldProps, FocusableProps {
20
+ /** The current value (controlled). */
21
+ value?: string;
22
+ /** The default value (uncontrolled). */
23
+ defaultValue?: string;
24
+ /** Handler that is called when the value changes. */
25
+ onChange?: (value: string) => void;
26
+ /** Whether the input is disabled. */
27
+ isDisabled?: boolean;
28
+ /** Whether the input is read only. */
29
+ isReadOnly?: boolean;
30
+ /** Whether the input is required. */
31
+ isRequired?: boolean;
32
+ /** The type of input to render. */
33
+ type?: 'text' | 'search' | 'url' | 'tel' | 'email' | 'password' | string;
34
+ /** The input mode hint for virtual keyboards. */
35
+ inputMode?: 'none' | 'text' | 'tel' | 'url' | 'email' | 'numeric' | 'decimal' | 'search';
36
+ /** The name of the input element, used when submitting an HTML form. */
37
+ name?: string;
38
+ /** Regex pattern to validate the input value. */
39
+ pattern?: string;
40
+ /** The maximum number of characters supported by the input. */
41
+ maxLength?: number;
42
+ /** The minimum number of characters required by the input. */
43
+ minLength?: number;
44
+ /** Placeholder text for the input. */
45
+ placeholder?: string;
46
+ /** Whether to enable auto complete. */
47
+ autoComplete?: string;
48
+ /** Whether to enable auto correct. */
49
+ autoCorrect?: string;
50
+ /** Whether to enable spell check. */
51
+ spellCheck?: 'true' | 'false';
52
+ /** Controls whether and how text input is automatically capitalized. */
53
+ autoCapitalize?: 'off' | 'none' | 'on' | 'sentences' | 'words' | 'characters';
54
+ /** The element type to use for the input. Defaults to 'input'. */
55
+ inputElementType?: 'input' | 'textarea';
56
+
57
+ // Clipboard events
58
+ onCopy?: JSX.EventHandler<HTMLInputElement | HTMLTextAreaElement, ClipboardEvent>;
59
+ onCut?: JSX.EventHandler<HTMLInputElement | HTMLTextAreaElement, ClipboardEvent>;
60
+ onPaste?: JSX.EventHandler<HTMLInputElement | HTMLTextAreaElement, ClipboardEvent>;
61
+
62
+ // Composition events
63
+ onCompositionStart?: JSX.EventHandler<HTMLInputElement | HTMLTextAreaElement, CompositionEvent>;
64
+ onCompositionEnd?: JSX.EventHandler<HTMLInputElement | HTMLTextAreaElement, CompositionEvent>;
65
+ onCompositionUpdate?: JSX.EventHandler<HTMLInputElement | HTMLTextAreaElement, CompositionEvent>;
66
+
67
+ // Selection events
68
+ onSelect?: JSX.EventHandler<HTMLInputElement | HTMLTextAreaElement, Event>;
69
+
70
+ // Input events
71
+ onBeforeInput?: JSX.EventHandler<HTMLInputElement | HTMLTextAreaElement, InputEvent>;
72
+ onInput?: JSX.EventHandler<HTMLInputElement | HTMLTextAreaElement, InputEvent>;
73
+ }
74
+
75
+ export interface TextFieldAria<T extends HTMLInputElement | HTMLTextAreaElement = HTMLInputElement> extends Omit<FieldAria, 'fieldProps'> {
76
+ /** Props for the input element. */
77
+ inputProps: JSX.InputHTMLAttributes<T>;
78
+ /** Whether the text field is invalid. */
79
+ isInvalid: boolean;
80
+ }
81
+
82
+ // ============================================
83
+ // IMPLEMENTATION
84
+ // ============================================
85
+
86
+ /**
87
+ * Provides the behavior and accessibility implementation for a text field.
88
+ * Text fields allow users to input text with a keyboard.
89
+ *
90
+ * @param props - Props for the text field.
91
+ * @param ref - Optional ref callback for the input element.
92
+ */
93
+ export function createTextField<T extends HTMLInputElement | HTMLTextAreaElement = HTMLInputElement>(
94
+ props: MaybeAccessor<AriaTextFieldProps>,
95
+ ref?: (el: T) => void
96
+ ): TextFieldAria<T> {
97
+ const getProps = () => access(props);
98
+
99
+ // Get field accessibility props (label, description, error message)
100
+ const { labelProps, fieldProps, descriptionProps, errorMessageProps } = createField(props);
101
+
102
+ // Get focusable props
103
+ const { focusableProps } = createFocusable(
104
+ {
105
+ get isDisabled() {
106
+ return getProps().isDisabled;
107
+ },
108
+ get autoFocus() {
109
+ return getProps().autoFocus;
110
+ },
111
+ onFocus: getProps().onFocus,
112
+ onBlur: getProps().onBlur,
113
+ onFocusChange: getProps().onFocusChange,
114
+ onKeyDown: getProps().onKeyDown,
115
+ onKeyUp: getProps().onKeyUp,
116
+ },
117
+ ref as ((el: HTMLElement) => void) | undefined
118
+ );
119
+
120
+ // Filter DOM props
121
+ const getDomProps = () => filterDOMProps(getProps() as Record<string, unknown>, { labelable: true });
122
+
123
+ // Build input props
124
+ const getInputProps = (): JSX.InputHTMLAttributes<T> => {
125
+ const p = getProps();
126
+ const isInvalid = p.isInvalid ?? false;
127
+ const isTextarea = p.inputElementType === 'textarea';
128
+
129
+ return mergeProps(
130
+ getDomProps(),
131
+ {
132
+ disabled: p.isDisabled,
133
+ readOnly: p.isReadOnly,
134
+ required: p.isRequired,
135
+ 'aria-required': p.isRequired || undefined,
136
+ 'aria-invalid': isInvalid || undefined,
137
+ value: p.value ?? p.defaultValue ?? '',
138
+ onChange: (e: Event) => {
139
+ const target = e.target as HTMLInputElement | HTMLTextAreaElement;
140
+ p.onChange?.(target.value);
141
+ },
142
+ // Don't include type and pattern for textarea elements
143
+ type: isTextarea ? undefined : (p.type ?? 'text'),
144
+ inputMode: p.inputMode,
145
+ name: p.name,
146
+ pattern: isTextarea ? undefined : p.pattern,
147
+ maxLength: p.maxLength,
148
+ minLength: p.minLength,
149
+ placeholder: p.placeholder,
150
+ autoComplete: p.autoComplete,
151
+ autoCorrect: p.autoCorrect,
152
+ autoCapitalize: p.autoCapitalize,
153
+ spellCheck: p.spellCheck,
154
+
155
+ // Clipboard events
156
+ onCopy: p.onCopy,
157
+ onCut: p.onCut,
158
+ onPaste: p.onPaste,
159
+
160
+ // Composition events
161
+ onCompositionStart: p.onCompositionStart,
162
+ onCompositionEnd: p.onCompositionEnd,
163
+ onCompositionUpdate: p.onCompositionUpdate,
164
+
165
+ // Selection events
166
+ onSelect: p.onSelect,
167
+
168
+ // Input events
169
+ onBeforeInput: p.onBeforeInput,
170
+ onInput: p.onInput,
171
+ },
172
+ focusableProps as Record<string, unknown>,
173
+ fieldProps as Record<string, unknown>
174
+ ) as JSX.InputHTMLAttributes<T>;
175
+ };
176
+
177
+ const getIsInvalid = () => {
178
+ return getProps().isInvalid ?? false;
179
+ };
180
+
181
+ return {
182
+ get labelProps() {
183
+ return labelProps;
184
+ },
185
+ get inputProps() {
186
+ return getInputProps();
187
+ },
188
+ get descriptionProps() {
189
+ return descriptionProps;
190
+ },
191
+ get errorMessageProps() {
192
+ return errorMessageProps;
193
+ },
194
+ get isInvalid() {
195
+ return getIsInvalid();
196
+ },
197
+ };
198
+ }
@@ -0,0 +1,5 @@
1
+ export {
2
+ createTextField,
3
+ type AriaTextFieldProps,
4
+ type TextFieldAria,
5
+ } from './createTextField';
@@ -0,0 +1,118 @@
1
+ /**
2
+ * createToast hook for Solidaria
3
+ *
4
+ * Provides the accessibility implementation for a Toast component.
5
+ *
6
+ * Port of @react-aria/toast useToast.
7
+ */
8
+
9
+ import { type JSX, createMemo } from 'solid-js';
10
+ import { type QueuedToast, type ToastState } from '@proyecto-viviana/solid-stately';
11
+ import { createId } from '../ssr';
12
+
13
+ // ============================================
14
+ // TYPES
15
+ // ============================================
16
+
17
+ export interface AriaToastProps<T> {
18
+ /** The toast to display. */
19
+ toast: QueuedToast<T>;
20
+ /** The toast state from createToastState. */
21
+ state: ToastState<T>;
22
+ }
23
+
24
+ export interface ToastAria {
25
+ /** Props for the toast container element. */
26
+ toastProps: JSX.HTMLAttributes<HTMLElement>;
27
+ /** Props for the toast content element (contains the message). */
28
+ contentProps: JSX.HTMLAttributes<HTMLElement>;
29
+ /** Props for the toast title element. */
30
+ titleProps: JSX.HTMLAttributes<HTMLElement>;
31
+ /** Props for the toast description element. */
32
+ descriptionProps: JSX.HTMLAttributes<HTMLElement>;
33
+ /** Props for the close button. */
34
+ closeButtonProps: JSX.ButtonHTMLAttributes<HTMLButtonElement>;
35
+ }
36
+
37
+ // ============================================
38
+ // IMPLEMENTATION
39
+ // ============================================
40
+
41
+ /**
42
+ * Provides the accessibility implementation for a Toast component.
43
+ *
44
+ * The toast uses role="alertdialog" to announce content to screen readers.
45
+ * The content area uses role="alert" for the actual message.
46
+ *
47
+ * @example
48
+ * ```tsx
49
+ * import { createToast } from 'solidaria';
50
+ *
51
+ * function Toast(props) {
52
+ * const {
53
+ * toastProps,
54
+ * contentProps,
55
+ * titleProps,
56
+ * closeButtonProps
57
+ * } = createToast({ toast: props.toast, state: props.state });
58
+ *
59
+ * return (
60
+ * <div {...toastProps}>
61
+ * <div {...contentProps}>
62
+ * <div {...titleProps}>{props.toast.content.title}</div>
63
+ * </div>
64
+ * <button {...closeButtonProps}>×</button>
65
+ * </div>
66
+ * );
67
+ * }
68
+ * ```
69
+ */
70
+ export function createToast<T>(props: AriaToastProps<T>): ToastAria {
71
+ const titleId = createId();
72
+ const descriptionId = createId();
73
+
74
+ const close = () => {
75
+ props.state.close(props.toast.key);
76
+ };
77
+
78
+ // Toast container - role="alertdialog" for screen readers
79
+ const toastProps = createMemo<JSX.HTMLAttributes<HTMLElement>>(() => ({
80
+ role: 'alertdialog',
81
+ 'aria-modal': 'false',
82
+ 'aria-labelledby': titleId,
83
+ 'aria-describedby': descriptionId,
84
+ 'data-animation': props.toast.animation,
85
+ 'data-key': props.toast.key,
86
+ }));
87
+
88
+ // Content area with role="alert" for immediate announcement
89
+ const contentProps = createMemo<JSX.HTMLAttributes<HTMLElement>>(() => ({
90
+ role: 'alert',
91
+ 'aria-atomic': 'true',
92
+ 'aria-live': 'assertive',
93
+ }));
94
+
95
+ // Title props
96
+ const titleProps = createMemo<JSX.HTMLAttributes<HTMLElement>>(() => ({
97
+ id: titleId,
98
+ }));
99
+
100
+ // Description props
101
+ const descriptionProps = createMemo<JSX.HTMLAttributes<HTMLElement>>(() => ({
102
+ id: descriptionId,
103
+ }));
104
+
105
+ // Close button
106
+ const closeButtonProps = createMemo<JSX.ButtonHTMLAttributes<HTMLButtonElement>>(() => ({
107
+ 'aria-label': 'Close',
108
+ onClick: close,
109
+ }));
110
+
111
+ return {
112
+ get toastProps() { return toastProps(); },
113
+ get contentProps() { return contentProps(); },
114
+ get titleProps() { return titleProps(); },
115
+ get descriptionProps() { return descriptionProps(); },
116
+ get closeButtonProps() { return closeButtonProps(); },
117
+ };
118
+ }