@proyecto-viviana/solidaria 0.2.4 → 0.2.8

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 (219) hide show
  1. package/LICENSE +21 -0
  2. package/dist/actiongroup/createActionGroup.d.ts +29 -0
  3. package/dist/actiongroup/createActionGroup.d.ts.map +1 -0
  4. package/dist/actiongroup/index.d.ts +2 -0
  5. package/dist/actiongroup/index.d.ts.map +1 -0
  6. package/dist/autocomplete/createAutocomplete.d.ts +6 -2
  7. package/dist/autocomplete/createAutocomplete.d.ts.map +1 -1
  8. package/dist/breadcrumbs/createBreadcrumbs.d.ts +2 -0
  9. package/dist/breadcrumbs/createBreadcrumbs.d.ts.map +1 -1
  10. package/dist/button/createToggleButtonGroup.d.ts +32 -0
  11. package/dist/button/createToggleButtonGroup.d.ts.map +1 -0
  12. package/dist/button/index.d.ts +2 -0
  13. package/dist/button/index.d.ts.map +1 -1
  14. package/dist/calendar/createCalendarCell.d.ts +2 -0
  15. package/dist/calendar/createCalendarCell.d.ts.map +1 -1
  16. package/dist/calendar/createCalendarGrid.d.ts.map +1 -1
  17. package/dist/calendar/createRangeCalendarCell.d.ts +3 -1
  18. package/dist/calendar/createRangeCalendarCell.d.ts.map +1 -1
  19. package/dist/checkbox/createCheckboxGroup.d.ts +5 -1
  20. package/dist/checkbox/createCheckboxGroup.d.ts.map +1 -1
  21. package/dist/collections/index.d.ts +56 -0
  22. package/dist/collections/index.d.ts.map +1 -0
  23. package/dist/color/createColorArea.d.ts.map +1 -1
  24. package/dist/color/createColorSlider.d.ts.map +1 -1
  25. package/dist/color/createColorWheel.d.ts.map +1 -1
  26. package/dist/combobox/createComboBox.d.ts +6 -0
  27. package/dist/combobox/createComboBox.d.ts.map +1 -1
  28. package/dist/datepicker/createDatePicker.d.ts +6 -0
  29. package/dist/datepicker/createDatePicker.d.ts.map +1 -1
  30. package/dist/datepicker/createDateRangePicker.d.ts +40 -0
  31. package/dist/datepicker/createDateRangePicker.d.ts.map +1 -0
  32. package/dist/datepicker/createDateSegment.d.ts +1 -1
  33. package/dist/datepicker/createDateSegment.d.ts.map +1 -1
  34. package/dist/datepicker/createTimeSegment.d.ts +29 -0
  35. package/dist/datepicker/createTimeSegment.d.ts.map +1 -0
  36. package/dist/datepicker/index.d.ts +2 -0
  37. package/dist/datepicker/index.d.ts.map +1 -1
  38. package/dist/disclosure/createDisclosureGroup.d.ts +2 -1
  39. package/dist/disclosure/createDisclosureGroup.d.ts.map +1 -1
  40. package/dist/dnd/createDrag.d.ts.map +1 -1
  41. package/dist/dnd/createDraggableCollection.d.ts +4 -0
  42. package/dist/dnd/createDraggableCollection.d.ts.map +1 -1
  43. package/dist/dnd/createDraggableItem.d.ts.map +1 -1
  44. package/dist/dnd/createDrop.d.ts.map +1 -1
  45. package/dist/dnd/createDroppableCollection.d.ts +32 -1
  46. package/dist/dnd/createDroppableCollection.d.ts.map +1 -1
  47. package/dist/dnd/createDroppableItem.d.ts.map +1 -1
  48. package/dist/dnd/index.d.ts +1 -1
  49. package/dist/dnd/index.d.ts.map +1 -1
  50. package/dist/grid/createGrid.d.ts.map +1 -1
  51. package/dist/gridlist/createGridList.d.ts.map +1 -1
  52. package/dist/index.d.ts +6 -4
  53. package/dist/index.d.ts.map +1 -1
  54. package/dist/index.js +4659 -3452
  55. package/dist/index.js.map +1 -7
  56. package/dist/index.ssr.js +4659 -3452
  57. package/dist/index.ssr.js.map +1 -7
  58. package/dist/interactions/createFocus.d.ts.map +1 -1
  59. package/dist/interactions/createFocusWithin.d.ts.map +1 -1
  60. package/dist/link/createLink.d.ts +10 -0
  61. package/dist/link/createLink.d.ts.map +1 -1
  62. package/dist/listbox/createListBox.d.ts +1 -0
  63. package/dist/listbox/createListBox.d.ts.map +1 -1
  64. package/dist/listbox/createOption.d.ts.map +1 -1
  65. package/dist/menu/createMenu.d.ts +1 -0
  66. package/dist/menu/createMenu.d.ts.map +1 -1
  67. package/dist/meter/createMeter.d.ts.map +1 -1
  68. package/dist/numberfield/createNumberField.d.ts +18 -0
  69. package/dist/numberfield/createNumberField.d.ts.map +1 -1
  70. package/dist/overlays/createModal.d.ts +16 -0
  71. package/dist/overlays/createModal.d.ts.map +1 -1
  72. package/dist/overlays/createOverlay.d.ts.map +1 -1
  73. package/dist/overlays/index.d.ts +1 -1
  74. package/dist/overlays/index.d.ts.map +1 -1
  75. package/dist/popover/createOverlayPosition.d.ts.map +1 -1
  76. package/dist/popover/createPopover.d.ts.map +1 -1
  77. package/dist/progress/createProgressBar.d.ts.map +1 -1
  78. package/dist/radio/createRadioGroup.d.ts +2 -2
  79. package/dist/radio/createRadioGroup.d.ts.map +1 -1
  80. package/dist/searchfield/createSearchField.d.ts.map +1 -1
  81. package/dist/select/createHiddenSelect.d.ts.map +1 -1
  82. package/dist/select/createSelect.d.ts.map +1 -1
  83. package/dist/slider/createSlider.d.ts.map +1 -1
  84. package/dist/table/createTable.d.ts.map +1 -1
  85. package/dist/tabs/createTabs.d.ts +1 -1
  86. package/dist/tabs/createTabs.d.ts.map +1 -1
  87. package/dist/tag/createTag.d.ts.map +1 -1
  88. package/dist/tag/createTagGroup.d.ts.map +1 -1
  89. package/dist/toast/createToast.d.ts +4 -0
  90. package/dist/toast/createToast.d.ts.map +1 -1
  91. package/dist/toast/createToastRegion.d.ts.map +1 -1
  92. package/dist/toolbar/createToolbar.d.ts.map +1 -1
  93. package/dist/tooltip/createTooltipTrigger.d.ts.map +1 -1
  94. package/dist/tree/createTree.d.ts.map +1 -1
  95. package/dist/tree/createTreeItem.d.ts.map +1 -1
  96. package/dist/tree/types.d.ts +4 -0
  97. package/dist/tree/types.d.ts.map +1 -1
  98. package/dist/utils/env.d.ts +1 -1
  99. package/dist/utils/env.d.ts.map +1 -1
  100. package/dist/utils/platform.d.ts.map +1 -1
  101. package/dist/visually-hidden/createVisuallyHidden.d.ts.map +1 -1
  102. package/package.json +8 -6
  103. package/src/actiongroup/createActionGroup.ts +324 -0
  104. package/src/actiongroup/index.ts +8 -0
  105. package/src/autocomplete/createAutocomplete.ts +32 -9
  106. package/src/breadcrumbs/createBreadcrumbs.ts +10 -15
  107. package/src/button/createButton.ts +1 -1
  108. package/src/button/createToggleButtonGroup.ts +128 -0
  109. package/src/button/index.ts +9 -0
  110. package/src/calendar/createCalendarCell.ts +6 -4
  111. package/src/calendar/createCalendarGrid.ts +27 -18
  112. package/src/calendar/createRangeCalendarCell.ts +26 -9
  113. package/src/checkbox/createCheckboxGroup.ts +21 -4
  114. package/src/collections/index.ts +242 -0
  115. package/src/color/createColorArea.ts +380 -314
  116. package/src/color/createColorField.ts +137 -137
  117. package/src/color/createColorSlider.ts +286 -197
  118. package/src/color/createColorSwatch.ts +40 -40
  119. package/src/color/createColorWheel.ts +218 -208
  120. package/src/color/index.ts +24 -24
  121. package/src/color/types.ts +116 -116
  122. package/src/combobox/createComboBox.ts +670 -647
  123. package/src/combobox/index.ts +6 -6
  124. package/src/datepicker/createDatePicker.ts +54 -16
  125. package/src/datepicker/createDateRangePicker.ts +246 -0
  126. package/src/datepicker/createDateSegment.ts +185 -31
  127. package/src/datepicker/createTimeSegment.ts +370 -0
  128. package/src/datepicker/index.ts +14 -0
  129. package/src/dialog/createDialog.ts +120 -120
  130. package/src/dialog/index.ts +2 -2
  131. package/src/dialog/types.ts +19 -19
  132. package/src/disclosure/createDisclosureGroup.ts +5 -2
  133. package/src/dnd/createDrag.ts +224 -209
  134. package/src/dnd/createDraggableCollection.ts +96 -63
  135. package/src/dnd/createDraggableItem.ts +259 -243
  136. package/src/dnd/createDrop.ts +322 -321
  137. package/src/dnd/createDroppableCollection.ts +682 -293
  138. package/src/dnd/createDroppableItem.ts +215 -213
  139. package/src/dnd/index.ts +55 -47
  140. package/src/dnd/types.ts +89 -89
  141. package/src/dnd/utils.ts +294 -294
  142. package/src/focus/createAutoFocus.ts +321 -321
  143. package/src/focus/createFocusRestore.ts +313 -313
  144. package/src/focus/createVirtualFocus.ts +396 -396
  145. package/src/form/createFormValidation.ts +224 -224
  146. package/src/form/index.ts +11 -11
  147. package/src/grid/createGrid.ts +3 -1
  148. package/src/gridlist/createGridList.ts +16 -0
  149. package/src/gridlist/createGridListItem.ts +1 -1
  150. package/src/i18n/NumberFormatter.ts +266 -266
  151. package/src/i18n/createCollator.ts +79 -79
  152. package/src/i18n/createDateFormatter.ts +83 -83
  153. package/src/i18n/createFilter.ts +131 -131
  154. package/src/i18n/createNumberFormatter.ts +52 -52
  155. package/src/i18n/index.ts +40 -40
  156. package/src/i18n/locale.tsx +188 -188
  157. package/src/i18n/utils.ts +99 -99
  158. package/src/index.ts +51 -0
  159. package/src/interactions/createFocus.ts +6 -5
  160. package/src/interactions/createFocusWithin.ts +6 -5
  161. package/src/interactions/createLongPress.ts +174 -174
  162. package/src/interactions/createMove.ts +289 -289
  163. package/src/interactions/createPress.ts +5 -5
  164. package/src/landmark/createLandmark.ts +377 -377
  165. package/src/landmark/index.ts +8 -8
  166. package/src/link/createLink.ts +23 -8
  167. package/src/listbox/createListBox.ts +308 -269
  168. package/src/listbox/createOption.ts +162 -151
  169. package/src/listbox/index.ts +12 -12
  170. package/src/live-announcer/announce.ts +322 -322
  171. package/src/live-announcer/index.ts +9 -9
  172. package/src/menu/createMenu.ts +405 -396
  173. package/src/menu/createMenuItem.ts +149 -149
  174. package/src/menu/createMenuTrigger.ts +88 -88
  175. package/src/menu/index.ts +18 -18
  176. package/src/meter/createMeter.ts +1 -6
  177. package/src/numberfield/createNumberField.ts +311 -268
  178. package/src/numberfield/index.ts +5 -5
  179. package/src/overlays/ariaHideOutside.ts +219 -219
  180. package/src/overlays/createInteractOutside.ts +149 -149
  181. package/src/overlays/createModal.tsx +238 -202
  182. package/src/overlays/createOverlay.ts +165 -155
  183. package/src/overlays/createOverlayTrigger.ts +85 -85
  184. package/src/overlays/createPreventScroll.ts +266 -266
  185. package/src/overlays/index.ts +48 -44
  186. package/src/popover/calculatePosition.ts +6 -6
  187. package/src/popover/createOverlayPosition.ts +7 -4
  188. package/src/popover/createPopover.ts +21 -7
  189. package/src/progress/createProgressBar.ts +6 -1
  190. package/src/radio/createRadioGroup.ts +88 -14
  191. package/src/searchfield/createSearchField.ts +241 -186
  192. package/src/searchfield/index.ts +2 -2
  193. package/src/select/createHiddenSelect.tsx +263 -236
  194. package/src/select/createSelect.ts +373 -395
  195. package/src/select/index.ts +14 -14
  196. package/src/slider/createSlider.ts +364 -349
  197. package/src/slider/index.ts +2 -2
  198. package/src/ssr/index.tsx +370 -370
  199. package/src/table/createTable.ts +3 -1
  200. package/src/table/createTableColumnHeader.ts +1 -1
  201. package/src/table/createTableRow.ts +1 -1
  202. package/src/tabs/createTabs.ts +80 -51
  203. package/src/tag/createTag.ts +135 -6
  204. package/src/tag/createTagGroup.ts +7 -2
  205. package/src/toast/createToast.ts +8 -2
  206. package/src/toast/createToastRegion.ts +0 -1
  207. package/src/toolbar/createToolbar.ts +75 -1
  208. package/src/tooltip/createTooltip.ts +79 -79
  209. package/src/tooltip/createTooltipTrigger.ts +226 -222
  210. package/src/tooltip/index.ts +6 -6
  211. package/src/tree/createTree.ts +261 -246
  212. package/src/tree/createTreeItem.ts +282 -233
  213. package/src/tree/createTreeSelectionCheckbox.ts +68 -68
  214. package/src/tree/index.ts +16 -16
  215. package/src/tree/types.ts +91 -87
  216. package/src/utils/env.ts +55 -54
  217. package/src/utils/platform.ts +16 -6
  218. package/src/visually-hidden/createVisuallyHidden.ts +139 -124
  219. package/src/visually-hidden/index.ts +6 -6
@@ -116,7 +116,7 @@ const AXIS_SIZE: Record<string, SizeAxis> = {
116
116
  left: 'width',
117
117
  };
118
118
 
119
- const TOTAL_SIZE: Record<string, string> = {
119
+ const TOTAL_SIZE: Record<SizeAxis, 'totalWidth' | 'totalHeight'> = {
120
120
  width: 'totalWidth',
121
121
  height: 'totalHeight',
122
122
  };
@@ -307,7 +307,7 @@ function computePosition(
307
307
  if (placement === axis) {
308
308
  const containerHeight = isContainerPositioned
309
309
  ? containerDimensions[size]
310
- : (containerDimensions as any)[TOTAL_SIZE[size]];
310
+ : containerDimensions[TOTAL_SIZE[size]];
311
311
  position[FLIPPED_DIRECTION[axis] as keyof Position] = Math.floor(
312
312
  containerHeight - childOffset[axis] + offset
313
313
  );
@@ -333,7 +333,7 @@ function getMaxHeight(
333
333
  const overlayTop =
334
334
  (position.top != null
335
335
  ? position.top
336
- : (containerDimensions as any)[TOTAL_SIZE.height] -
336
+ : containerDimensions[TOTAL_SIZE.height] -
337
337
  (position.bottom ?? 0) -
338
338
  overlayHeight) - (containerDimensions.scroll.top ?? 0);
339
339
 
@@ -620,7 +620,7 @@ export function getRect(node: Element, ignoreScale: boolean) {
620
620
  let width = bWidth;
621
621
  let height = bHeight;
622
622
 
623
- if (ignoreScale && node instanceof (node.ownerDocument.defaultView as any).HTMLElement) {
623
+ if (ignoreScale && node instanceof (node.ownerDocument.defaultView?.HTMLElement ?? HTMLElement)) {
624
624
  width = (node as HTMLElement).offsetWidth;
625
625
  height = (node as HTMLElement).offsetHeight;
626
626
  }
@@ -690,8 +690,8 @@ function isContainingBlock(node: Element): boolean {
690
690
  /transform|perspective/.test(style.willChange) ||
691
691
  style.filter !== 'none' ||
692
692
  style.contain === 'paint' ||
693
- ('backdropFilter' in style && (style as any).backdropFilter !== 'none') ||
694
- ('WebkitBackdropFilter' in style && (style as any).WebkitBackdropFilter !== 'none')
693
+ ('backdropFilter' in style && style.getPropertyValue('backdrop-filter') !== 'none') ||
694
+ ('WebkitBackdropFilter' in style && style.getPropertyValue('-webkit-backdrop-filter') !== 'none')
695
695
  );
696
696
  }
697
697
 
@@ -6,6 +6,7 @@
6
6
  */
7
7
 
8
8
  import { createEffect, createSignal, onCleanup, type JSX } from 'solid-js';
9
+ import { useLocale } from '../i18n';
9
10
  import {
10
11
  calculatePosition,
11
12
  getRect,
@@ -122,7 +123,8 @@ function translateRTL(position: string, direction: string): string {
122
123
  * element, and updating the position when the window resizes.
123
124
  */
124
125
  export function createOverlayPosition(props: AriaPositionProps): PositionAria {
125
- const direction = 'ltr'; // TODO: get from locale context
126
+ const locale = useLocale();
127
+ const direction = () => locale().direction;
126
128
 
127
129
  const arrowSize = () => props.arrowSize ?? 0;
128
130
  const targetRef = () => props.targetRef();
@@ -178,7 +180,7 @@ export function createOverlayPosition(props: AriaPositionProps): PositionAria {
178
180
  }
179
181
 
180
182
  const result = calculatePosition({
181
- placement: translateRTL(placement(), direction) as Placement,
183
+ placement: translateRTL(placement(), direction()) as Placement,
182
184
  overlayNode,
183
185
  targetNode,
184
186
  scrollNode: scrollNode || overlayNode,
@@ -202,8 +204,9 @@ export function createOverlayPosition(props: AriaPositionProps): PositionAria {
202
204
  overlay.style.left = '';
203
205
  overlay.style.right = '';
204
206
 
205
- Object.keys(result.position).forEach((key) => {
206
- (overlay.style as any)[key] = (result.position as any)[key] + 'px';
207
+ const pos = result.position as Record<string, number | undefined>;
208
+ Object.keys(pos).forEach((key) => {
209
+ overlay.style.setProperty(key, pos[key] + 'px');
207
210
  });
208
211
  overlay.style.maxHeight = result.maxHeight != null ? result.maxHeight + 'px' : '';
209
212
 
@@ -109,12 +109,20 @@ export function createPopover(
109
109
  // Overlay behavior (dismiss handling)
110
110
  const { overlayProps, underlayProps } = createOverlay(
111
111
  {
112
- isOpen: state.isOpen(),
112
+ get isOpen() {
113
+ return state.isOpen();
114
+ },
113
115
  onClose: state.close,
114
116
  shouldCloseOnBlur: true,
115
- isDismissable: !isNonModal() || isSubmenu(),
116
- isKeyboardDismissDisabled: isKeyboardDismissDisabled(),
117
- shouldCloseOnInteractOutside,
117
+ get isDismissable() {
118
+ return !isNonModal() || isSubmenu();
119
+ },
120
+ get isKeyboardDismissDisabled() {
121
+ return isKeyboardDismissDisabled();
122
+ },
123
+ get shouldCloseOnInteractOutside() {
124
+ return shouldCloseOnInteractOutside;
125
+ },
118
126
  },
119
127
  () => groupRef() ?? popoverRef()
120
128
  );
@@ -129,13 +137,19 @@ export function createPopover(
129
137
  ...props,
130
138
  targetRef: triggerRef,
131
139
  overlayRef: popoverRef,
132
- isOpen: state.isOpen(),
133
- onClose: isNonModal() && !isSubmenu() ? state.close : null,
140
+ get isOpen() {
141
+ return state.isOpen();
142
+ },
143
+ get onClose() {
144
+ return isNonModal() && !isSubmenu() ? state.close : null;
145
+ },
134
146
  });
135
147
 
136
148
  // Prevent scroll when modal popover is open
137
149
  createPreventScroll({
138
- isDisabled: isNonModal() || !state.isOpen(),
150
+ get isDisabled() {
151
+ return isNonModal() || !state.isOpen();
152
+ },
139
153
  });
140
154
 
141
155
  // Aria-hide outside elements
@@ -57,6 +57,11 @@ function clamp(value: number, min: number, max: number): number {
57
57
  return Math.min(Math.max(value, min), max);
58
58
  }
59
59
 
60
+ function getSafeRange(min: number, max: number): number {
61
+ const range = max - min;
62
+ return Number.isFinite(range) && range > 0 ? range : 1;
63
+ }
64
+
60
65
  // ============================================
61
66
  // IMPLEMENTATION
62
67
  // ============================================
@@ -91,7 +96,7 @@ export function createProgressBar(
91
96
  const formatOptions = p.formatOptions ?? { style: 'percent' as const };
92
97
 
93
98
  const clampedValue = clamp(value, minValue, maxValue);
94
- const percentage = (clampedValue - minValue) / (maxValue - minValue);
99
+ const percentage = (clampedValue - minValue) / getSafeRange(minValue, maxValue);
95
100
 
96
101
  // Format value label
97
102
  let valueLabel = p.valueLabel;
@@ -12,9 +12,11 @@ import { createField } from '../label/createField';
12
12
  import { createFocusWithin } from '../interactions/createFocusWithin';
13
13
  import { mergeProps } from '../utils/mergeProps';
14
14
  import { filterDOMProps } from '../utils/filterDOMProps';
15
+ import { focusSafely, getEventTarget } from '../utils';
16
+ import { useLocale } from '../i18n';
15
17
  import { createId } from '../ssr';
16
18
  import { type MaybeAccessor, access } from '../utils/reactivity';
17
- import { type RadioGroupState } from '@proyecto-viviana/solid-stately';
19
+ import { type RadioGroupState, type ValidityState } from '@proyecto-viviana/solid-stately';
18
20
 
19
21
  // ============================================
20
22
  // TYPES
@@ -75,7 +77,7 @@ export interface RadioGroupAria {
75
77
  /** Validation errors, if any. */
76
78
  validationErrors: string[];
77
79
  /** Validation details, if any. */
78
- validationDetails: Record<string, boolean>;
80
+ validationDetails: ValidityState;
79
81
  }
80
82
 
81
83
  // WeakMap to share data between radio group and radio items
@@ -102,19 +104,31 @@ export function createRadioGroup(
102
104
  state: RadioGroupState
103
105
  ): RadioGroupAria {
104
106
  const getProps = () => access(props);
107
+ const locale = useLocale();
105
108
 
106
109
  const orientation = () => getProps().orientation ?? 'vertical';
107
110
  const isReadOnly = () => getProps().isReadOnly ?? false;
108
111
  const isRequired = () => getProps().isRequired ?? false;
109
112
  const isDisabled = () => getProps().isDisabled ?? false;
110
113
  const validationBehavior = () => getProps().validationBehavior ?? 'aria';
114
+ const displayValidation = () => state.displayValidation();
115
+ const validationErrors = () => displayValidation().validationErrors;
116
+ const validationDetails = () => displayValidation().validationDetails;
117
+ const isInvalid = () => displayValidation().isInvalid;
118
+ const fallbackErrorMessage = () => {
119
+ const errors = validationErrors();
120
+ return errors.length > 0 ? errors : undefined;
121
+ };
111
122
 
112
123
  // Use field for label, description, error message
113
124
  const { labelProps, fieldProps, descriptionProps, errorMessageProps } = createField({
125
+ get id() { return getProps().id; },
114
126
  get label() { return getProps().label; },
127
+ get 'aria-label'() { return getProps()['aria-label']; },
128
+ get 'aria-labelledby'() { return getProps()['aria-labelledby']; },
115
129
  get description() { return getProps().description; },
116
- get errorMessage() { return getProps().errorMessage; },
117
- get isInvalid() { return state.isInvalid; },
130
+ get errorMessage() { return getProps().errorMessage ?? fallbackErrorMessage(); },
131
+ get isInvalid() { return isInvalid(); },
118
132
  // Radio group is not an HTML input element so it
119
133
  // shouldn't be labeled by a <label> element.
120
134
  labelElementType: 'span',
@@ -147,12 +161,72 @@ export function createRadioGroup(
147
161
  validationBehavior: validationBehavior(),
148
162
  });
149
163
 
150
- // Keyboard navigation handler for arrow keys
151
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
152
- const onKeyDown = (_e: KeyboardEvent) => {
153
- // For now, we rely on native radio behavior
154
- // The complex tree walker implementation can be added later if needed
155
- // Most browsers handle arrow key navigation natively for radio groups
164
+ const getNavigableRadios = (root: HTMLElement): HTMLInputElement[] => {
165
+ return Array.from(root.querySelectorAll('input[type="radio"]'))
166
+ .filter((el): el is HTMLInputElement => {
167
+ return el instanceof HTMLInputElement && !el.matches(':disabled');
168
+ });
169
+ };
170
+
171
+ // Keyboard navigation parity with React Aria.
172
+ const onKeyDown: JSX.EventHandler<HTMLDivElement, KeyboardEvent> = (e) => {
173
+ let nextDir: 'next' | 'prev' | null = null;
174
+ const currentOrientation = orientation();
175
+ const isHorizontal = currentOrientation !== 'vertical';
176
+ const isRTL = locale().direction === 'rtl' && isHorizontal;
177
+
178
+ switch (e.key) {
179
+ case 'ArrowRight':
180
+ nextDir = isRTL ? 'prev' : 'next';
181
+ break;
182
+ case 'ArrowLeft':
183
+ nextDir = isRTL ? 'next' : 'prev';
184
+ break;
185
+ case 'ArrowDown':
186
+ nextDir = 'next';
187
+ break;
188
+ case 'ArrowUp':
189
+ nextDir = 'prev';
190
+ break;
191
+ default:
192
+ return;
193
+ }
194
+
195
+ e.preventDefault();
196
+
197
+ const root = e.currentTarget;
198
+ if (!(root instanceof HTMLElement)) {
199
+ return;
200
+ }
201
+
202
+ const radios = getNavigableRadios(root);
203
+ if (radios.length === 0) {
204
+ return;
205
+ }
206
+
207
+ const eventTarget = getEventTarget<Element>(e);
208
+ const activeElement = root.ownerDocument.activeElement;
209
+
210
+ const currentRadio = eventTarget instanceof HTMLInputElement && eventTarget.type === 'radio'
211
+ ? eventTarget
212
+ : (activeElement instanceof HTMLInputElement && activeElement.type === 'radio' ? activeElement : null);
213
+
214
+ const currentIndex = currentRadio ? radios.indexOf(currentRadio) : -1;
215
+
216
+ let nextIndex: number;
217
+ if (nextDir === 'next') {
218
+ nextIndex = currentIndex >= 0 ? (currentIndex + 1) % radios.length : 0;
219
+ } else {
220
+ nextIndex = currentIndex >= 0 ? (currentIndex - 1 + radios.length) % radios.length : radios.length - 1;
221
+ }
222
+
223
+ const nextRadio = radios[nextIndex];
224
+ if (!nextRadio) {
225
+ return;
226
+ }
227
+
228
+ focusSafely(nextRadio);
229
+ state.setSelectedValue(nextRadio.value);
156
230
  };
157
231
 
158
232
  return {
@@ -163,7 +237,7 @@ export function createRadioGroup(
163
237
  {
164
238
  role: 'radiogroup',
165
239
  onKeyDown,
166
- 'aria-invalid': state.isInvalid || undefined,
240
+ 'aria-invalid': isInvalid() || undefined,
167
241
  'aria-errormessage': getProps()['aria-errormessage'],
168
242
  'aria-readonly': isReadOnly() || undefined,
169
243
  'aria-required': isRequired() || undefined,
@@ -177,13 +251,13 @@ export function createRadioGroup(
177
251
  descriptionProps,
178
252
  errorMessageProps,
179
253
  get isInvalid() {
180
- return state.isInvalid;
254
+ return isInvalid();
181
255
  },
182
256
  get validationErrors() {
183
- return []; // Simplified - full validation can be added later
257
+ return validationErrors();
184
258
  },
185
259
  get validationDetails() {
186
- return {}; // Simplified - full validation can be added later
260
+ return validationDetails();
187
261
  },
188
262
  };
189
263
  }