@proyecto-viviana/solidaria-components 0.4.0 → 0.5.0

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 (226) hide show
  1. package/dist/ActionBar.d.ts.map +1 -1
  2. package/dist/ActionBar.js +2 -11
  3. package/dist/ActionBar.js.map +1 -1
  4. package/dist/ActionBar.jsx +2 -11
  5. package/dist/ActionBar.jsx.map +1 -1
  6. package/dist/ActionGroup.d.ts.map +1 -1
  7. package/dist/ActionGroup.js +1 -1
  8. package/dist/ActionGroup.js.map +1 -1
  9. package/dist/ActionGroup.jsx +1 -1
  10. package/dist/ActionGroup.jsx.map +1 -1
  11. package/dist/Autocomplete.d.ts +2 -2
  12. package/dist/Autocomplete.d.ts.map +1 -1
  13. package/dist/Autocomplete.js +7 -7
  14. package/dist/Autocomplete.js.map +1 -1
  15. package/dist/Autocomplete.jsx +7 -7
  16. package/dist/Autocomplete.jsx.map +1 -1
  17. package/dist/Breadcrumbs.d.ts.map +1 -1
  18. package/dist/Breadcrumbs.js +18 -18
  19. package/dist/Breadcrumbs.js.map +1 -1
  20. package/dist/Breadcrumbs.jsx +21 -26
  21. package/dist/Breadcrumbs.jsx.map +1 -1
  22. package/dist/Calendar.d.ts.map +1 -1
  23. package/dist/Calendar.js +100 -65
  24. package/dist/Calendar.js.map +1 -1
  25. package/dist/Calendar.jsx +10 -1
  26. package/dist/Calendar.jsx.map +1 -1
  27. package/dist/Color.d.ts.map +1 -1
  28. package/dist/Color.js +15 -18
  29. package/dist/Color.js.map +1 -1
  30. package/dist/Color.jsx +15 -19
  31. package/dist/Color.jsx.map +1 -1
  32. package/dist/ComboBox.d.ts +4 -0
  33. package/dist/ComboBox.d.ts.map +1 -1
  34. package/dist/ComboBox.js +21 -13
  35. package/dist/ComboBox.js.map +1 -1
  36. package/dist/ComboBox.jsx +23 -13
  37. package/dist/ComboBox.jsx.map +1 -1
  38. package/dist/DateField.d.ts +11 -2
  39. package/dist/DateField.d.ts.map +1 -1
  40. package/dist/DateField2.js +237 -234
  41. package/dist/DateField2.js.map +1 -1
  42. package/dist/DateField2.jsx +134 -97
  43. package/dist/DateField2.jsx.map +1 -1
  44. package/dist/DatePicker.d.ts +42 -3
  45. package/dist/DatePicker.d.ts.map +1 -1
  46. package/dist/DatePicker.js +6 -990
  47. package/dist/DatePicker.jsx +6 -681
  48. package/dist/DatePicker2.js +1171 -0
  49. package/dist/DatePicker2.js.map +1 -0
  50. package/dist/DatePicker2.jsx +820 -0
  51. package/dist/DatePicker2.jsx.map +1 -0
  52. package/dist/DateRangePickerContext.d.ts +2 -0
  53. package/dist/DateRangePickerContext.d.ts.map +1 -1
  54. package/dist/Disclosure.d.ts.map +1 -1
  55. package/dist/Disclosure.js +3 -0
  56. package/dist/Disclosure.js.map +1 -1
  57. package/dist/Disclosure.jsx +1 -1
  58. package/dist/Disclosure.jsx.map +1 -1
  59. package/dist/ElementTag.d.ts +29 -0
  60. package/dist/ElementTag.d.ts.map +1 -0
  61. package/dist/ElementTag.js +220 -0
  62. package/dist/ElementTag.js.map +1 -0
  63. package/dist/ElementTag.jsx +67 -0
  64. package/dist/ElementTag.jsx.map +1 -0
  65. package/dist/GridList.d.ts.map +1 -1
  66. package/dist/GridList.js +2 -2
  67. package/dist/GridList.js.map +1 -1
  68. package/dist/GridList.jsx +2 -2
  69. package/dist/GridList.jsx.map +1 -1
  70. package/dist/Landmark.js +4 -3
  71. package/dist/Landmark.js.map +1 -1
  72. package/dist/Landmark.jsx +3 -3
  73. package/dist/Landmark.jsx.map +1 -1
  74. package/dist/Link.js +3 -2
  75. package/dist/Link.js.map +1 -1
  76. package/dist/Link.jsx +3 -3
  77. package/dist/Link.jsx.map +1 -1
  78. package/dist/ListBox.d.ts +2 -2
  79. package/dist/ListBox.d.ts.map +1 -1
  80. package/dist/ListBox.js +327 -166
  81. package/dist/ListBox.js.map +1 -1
  82. package/dist/ListBox.jsx +161 -25
  83. package/dist/ListBox.jsx.map +1 -1
  84. package/dist/Modal.d.ts.map +1 -1
  85. package/dist/Modal.js +7 -5
  86. package/dist/Modal.js.map +1 -1
  87. package/dist/Modal.jsx +5 -5
  88. package/dist/Modal.jsx.map +1 -1
  89. package/dist/Popover.d.ts.map +1 -1
  90. package/dist/Popover.js +16 -9
  91. package/dist/Popover.js.map +1 -1
  92. package/dist/Popover.jsx +25 -4
  93. package/dist/Popover.jsx.map +1 -1
  94. package/dist/RangeCalendar.d.ts.map +1 -1
  95. package/dist/RangeCalendar.js +182 -46
  96. package/dist/RangeCalendar.js.map +1 -1
  97. package/dist/RangeCalendar.jsx +72 -2
  98. package/dist/RangeCalendar.jsx.map +1 -1
  99. package/dist/SearchField.d.ts.map +1 -1
  100. package/dist/SearchField.js +26 -2
  101. package/dist/SearchField.js.map +1 -1
  102. package/dist/SearchField.jsx +26 -2
  103. package/dist/SearchField.jsx.map +1 -1
  104. package/dist/Select.d.ts +7 -0
  105. package/dist/Select.d.ts.map +1 -1
  106. package/dist/Select.js +64 -51
  107. package/dist/Select.js.map +1 -1
  108. package/dist/Select.jsx +35 -20
  109. package/dist/Select.jsx.map +1 -1
  110. package/dist/Separator.js +3 -2
  111. package/dist/Separator.js.map +1 -1
  112. package/dist/Separator.jsx +2 -2
  113. package/dist/Separator.jsx.map +1 -1
  114. package/dist/SharedElementTransition.d.ts.map +1 -1
  115. package/dist/SharedElementTransition.js +35 -33
  116. package/dist/SharedElementTransition.js.map +1 -1
  117. package/dist/SharedElementTransition.jsx +35 -33
  118. package/dist/SharedElementTransition.jsx.map +1 -1
  119. package/dist/StepList.d.ts.map +1 -1
  120. package/dist/StepList.js +2 -9
  121. package/dist/StepList.js.map +1 -1
  122. package/dist/StepList.jsx +1 -6
  123. package/dist/StepList.jsx.map +1 -1
  124. package/dist/Table.d.ts +2 -0
  125. package/dist/Table.d.ts.map +1 -1
  126. package/dist/Table.js +10 -3
  127. package/dist/Table.js.map +1 -1
  128. package/dist/Table.jsx +10 -3
  129. package/dist/Table.jsx.map +1 -1
  130. package/dist/Tabs.d.ts.map +1 -1
  131. package/dist/Tabs.js +3 -0
  132. package/dist/Tabs.js.map +1 -1
  133. package/dist/Tabs.jsx +1 -1
  134. package/dist/Tabs.jsx.map +1 -1
  135. package/dist/TagGroup.d.ts +3 -1
  136. package/dist/TagGroup.d.ts.map +1 -1
  137. package/dist/TagGroup.js +12 -8
  138. package/dist/TagGroup.js.map +1 -1
  139. package/dist/TagGroup.jsx +11 -8
  140. package/dist/TagGroup.jsx.map +1 -1
  141. package/dist/Text.d.ts.map +1 -1
  142. package/dist/Text.js +9 -8
  143. package/dist/Text.js.map +1 -1
  144. package/dist/Text.jsx +3 -3
  145. package/dist/Text.jsx.map +1 -1
  146. package/dist/TextField.js +4 -1
  147. package/dist/TextField.js.map +1 -1
  148. package/dist/TextField.jsx +4 -1
  149. package/dist/TextField.jsx.map +1 -1
  150. package/dist/TimeField.d.ts +14 -50
  151. package/dist/TimeField.d.ts.map +1 -1
  152. package/dist/TimeField.js +111 -205
  153. package/dist/TimeField.js.map +1 -1
  154. package/dist/TimeField.jsx +76 -82
  155. package/dist/TimeField.jsx.map +1 -1
  156. package/dist/Tree.d.ts.map +1 -1
  157. package/dist/Tree.js +3 -3
  158. package/dist/Tree.js.map +1 -1
  159. package/dist/Tree.jsx +3 -3
  160. package/dist/Tree.jsx.map +1 -1
  161. package/dist/VisuallyHidden.d.ts.map +1 -1
  162. package/dist/VisuallyHidden.js +3 -11
  163. package/dist/VisuallyHidden.js.map +1 -1
  164. package/dist/VisuallyHidden.jsx +3 -4
  165. package/dist/VisuallyHidden.jsx.map +1 -1
  166. package/dist/index.d.ts +3 -3
  167. package/dist/index.d.ts.map +1 -1
  168. package/dist/index.js +6 -6
  169. package/dist/index.jsx +6 -6
  170. package/dist/useDragAndDrop.d.ts +0 -8
  171. package/dist/useDragAndDrop.d.ts.map +1 -1
  172. package/dist/useDragAndDrop2.js +6 -2
  173. package/dist/useDragAndDrop2.js.map +1 -1
  174. package/dist/useDragAndDrop2.jsx +6 -2
  175. package/dist/useDragAndDrop2.jsx.map +1 -1
  176. package/dist/utils.d.ts +1 -0
  177. package/dist/utils.d.ts.map +1 -1
  178. package/dist/utils.js +2 -2
  179. package/dist/utils.js.map +1 -1
  180. package/dist/utils.jsx +2 -2
  181. package/dist/utils.jsx.map +1 -1
  182. package/package.json +3 -9
  183. package/src/ActionBar.tsx +10 -12
  184. package/src/ActionGroup.tsx +5 -3
  185. package/src/Autocomplete.tsx +14 -9
  186. package/src/Breadcrumbs.tsx +57 -42
  187. package/src/Calendar.tsx +30 -1
  188. package/src/Color.tsx +42 -27
  189. package/src/ComboBox.tsx +49 -17
  190. package/src/DateField.tsx +285 -52
  191. package/src/DatePicker.tsx +345 -57
  192. package/src/DateRangePickerContext.tsx +2 -0
  193. package/src/Disclosure.tsx +4 -0
  194. package/src/ElementTag.tsx +116 -0
  195. package/src/GridList.tsx +8 -2
  196. package/src/Landmark.tsx +4 -4
  197. package/src/Link.tsx +4 -4
  198. package/src/ListBox.tsx +323 -21
  199. package/src/Modal.tsx +21 -5
  200. package/src/Popover.tsx +28 -7
  201. package/src/RangeCalendar.tsx +120 -1
  202. package/src/SearchField.tsx +53 -1
  203. package/src/Select.tsx +114 -22
  204. package/src/Separator.tsx +3 -3
  205. package/src/SharedElementTransition.tsx +92 -36
  206. package/src/StepList.tsx +0 -7
  207. package/src/Table.tsx +19 -4
  208. package/src/Tabs.tsx +1 -0
  209. package/src/TagGroup.tsx +26 -6
  210. package/src/Text.tsx +5 -4
  211. package/src/TextField.tsx +1 -1
  212. package/src/TimeField.tsx +113 -196
  213. package/src/Tree.tsx +14 -4
  214. package/src/VisuallyHidden.tsx +8 -12
  215. package/src/index.ts +3 -8
  216. package/src/useDragAndDrop.ts +8 -10
  217. package/src/utils.tsx +3 -3
  218. package/dist/DatePicker.js.map +0 -1
  219. package/dist/DatePicker.jsx.map +0 -1
  220. package/dist/HiddenTimeInput.d.ts +0 -25
  221. package/dist/HiddenTimeInput.d.ts.map +0 -1
  222. package/dist/HiddenTimeInput.js +0 -104
  223. package/dist/HiddenTimeInput.js.map +0 -1
  224. package/dist/HiddenTimeInput.jsx +0 -69
  225. package/dist/HiddenTimeInput.jsx.map +0 -1
  226. package/src/HiddenTimeInput.tsx +0 -133
@@ -196,6 +196,13 @@ export function useRangeCalendarContext(): RangeCalendarState<DateValue> {
196
196
  export function RangeCalendar<T extends DateValue = CalendarDate>(
197
197
  props: RangeCalendarProps<T>,
198
198
  ): JSX.Element {
199
+ // When rendered inside a composite that owns the range state (e.g.
200
+ // DateRangePicker provides `RangeCalendarContext`), reuse that shared state
201
+ // instead of minting a fresh one — otherwise the composite's focus/anchor
202
+ // flags (e.g. the popover-open `setFocused(true)`) drive a different state
203
+ // instance than the one this grid renders. Mirrors the single `Calendar`.
204
+ const inheritedState = useContext(RangeCalendarContext);
205
+
199
206
  // Use hydration-safe pattern for client-only rendering
200
207
  const isHydrated = useIsHydrated();
201
208
 
@@ -209,11 +216,106 @@ export function RangeCalendar<T extends DateValue = CalendarDate>(
209
216
  />
210
217
  }
211
218
  >
212
- <RangeCalendarInner {...props} />
219
+ <Show when={inheritedState} fallback={<RangeCalendarInner {...props} />}>
220
+ <RangeCalendarWithState
221
+ state={inheritedState as RangeCalendarState<DateValue>}
222
+ {...props}
223
+ />
224
+ </Show>
213
225
  </Show>
214
226
  );
215
227
  }
216
228
 
229
+ /**
230
+ * RangeCalendar variant that renders against an externally-owned state (from
231
+ * `RangeCalendarContext`) rather than creating its own. Mirrors the single
232
+ * `CalendarWithState`.
233
+ */
234
+ function RangeCalendarWithState<T extends DateValue = CalendarDate>(
235
+ props: RangeCalendarProps<T> & { state: RangeCalendarState<DateValue> },
236
+ ): JSX.Element {
237
+ const [local, , rest] = splitProps(
238
+ props,
239
+ ["children", "class", "style", "slot", "state", "ref"],
240
+ [
241
+ "value",
242
+ "defaultValue",
243
+ "onChange",
244
+ "minValue",
245
+ "maxValue",
246
+ "isDisabled",
247
+ "isReadOnly",
248
+ "focusedValue",
249
+ "defaultFocusedValue",
250
+ "onFocusChange",
251
+ "locale",
252
+ "createCalendar",
253
+ "isDateUnavailable",
254
+ "visibleMonths",
255
+ "pageBehavior",
256
+ "selectionAlignment",
257
+ "isDateDisabled",
258
+ "validationState",
259
+ "allowsNonContiguousRanges",
260
+ "firstDayOfWeek",
261
+ ],
262
+ );
263
+
264
+ const state = () => props.state;
265
+
266
+ const calendarAria = createRangeCalendar(rest, state());
267
+
268
+ const renderValues = createMemo<RangeCalendarRenderProps>(() => ({
269
+ isDisabled: state().isDisabled(),
270
+ isReadOnly: state().isReadOnly(),
271
+ isInvalid: state().isValueInvalid(),
272
+ isDragging: state().isDragging(),
273
+ }));
274
+
275
+ const renderProps = useRenderProps(
276
+ {
277
+ class: local.class,
278
+ style: local.style,
279
+ defaultClassName: "solidaria-RangeCalendar",
280
+ },
281
+ renderValues,
282
+ );
283
+
284
+ return (
285
+ <RangeCalendarStateContext.Provider value={state()}>
286
+ <RangeCalendarContext.Provider value={state()}>
287
+ <div
288
+ {...calendarAria.calendarProps}
289
+ ref={(el) => assignRef(local.ref, el)}
290
+ class={renderProps.class()}
291
+ style={renderProps.style()}
292
+ data-disabled={dataAttr(state().isDisabled())}
293
+ data-readonly={dataAttr(state().isReadOnly())}
294
+ data-invalid={dataAttr(state().isValueInvalid())}
295
+ data-dragging={dataAttr(state().isDragging())}
296
+ >
297
+ <VisuallyHidden>
298
+ <h2>{String(calendarAria.calendarProps["aria-label"] ?? "")}</h2>
299
+ </VisuallyHidden>
300
+ {props.children}
301
+ {/* For touch screen readers, a visually hidden next button after the
302
+ * month grid so it's easy to navigate after reaching the end without
303
+ * going all the way back to the start. Mirrors react-aria-components
304
+ * Calendar (and the headless Calendar). */}
305
+ <VisuallyHidden>
306
+ <button
307
+ aria-label={String(calendarAria.nextButtonProps["aria-label"] ?? "")}
308
+ disabled={Boolean(calendarAria.nextButtonProps.disabled)}
309
+ onClick={() => state().focusNextPage()}
310
+ tabIndex={-1}
311
+ />
312
+ </VisuallyHidden>
313
+ </div>
314
+ </RangeCalendarContext.Provider>
315
+ </RangeCalendarStateContext.Provider>
316
+ );
317
+ }
318
+
217
319
  /**
218
320
  * Internal RangeCalendar component that renders after client mount.
219
321
  */
@@ -284,6 +386,18 @@ function RangeCalendarInner<T extends DateValue = CalendarDate>(
284
386
  <h2>{String(calendarAria.calendarProps["aria-label"] ?? "")}</h2>
285
387
  </VisuallyHidden>
286
388
  {props.children}
389
+ {/* For touch screen readers, a visually hidden next button after the
390
+ * month grid so it's easy to navigate after reaching the end without
391
+ * going all the way back to the start. Mirrors react-aria-components
392
+ * Calendar (and the headless Calendar). */}
393
+ <VisuallyHidden>
394
+ <button
395
+ aria-label={String(calendarAria.nextButtonProps["aria-label"] ?? "")}
396
+ disabled={Boolean(calendarAria.nextButtonProps.disabled)}
397
+ onClick={() => state.focusNextPage()}
398
+ tabIndex={-1}
399
+ />
400
+ </VisuallyHidden>
287
401
  </div>
288
402
  </RangeCalendarContext.Provider>
289
403
  </RangeCalendarStateContext.Provider>
@@ -350,6 +464,11 @@ export function RangeCalendarButton(props: RangeCalendarButtonProps): JSX.Elemen
350
464
  class={props.class ?? "solidaria-RangeCalendarButton"}
351
465
  style={props.style}
352
466
  disabled={isDisabled()}
467
+ // Mirror useFocusable (same as CalendarButton): always set an explicit
468
+ // tabIndex on an enabled focusable nav button (0), and drop it entirely
469
+ // when disabled — so the prev/next buttons appear in the roving tab order
470
+ // exactly as the S2 oracle does.
471
+ tabindex={isDisabled() ? undefined : 0}
353
472
  >
354
473
  {props.children}
355
474
  </button>
@@ -42,6 +42,7 @@ import {
42
42
  Provider,
43
43
  } from "./utils";
44
44
  import { TextContext } from "./Text";
45
+ import { useAutocompleteInput } from "./Autocomplete";
45
46
 
46
47
  export interface SearchFieldRenderProps {
47
48
  /** Whether the search field is empty. */
@@ -247,7 +248,35 @@ export function SearchField(props: SearchFieldProps): JSX.Element {
247
248
  const baseProps = (
248
249
  contextProps ? mergeProps(contextBaseProps(), contextSlotProps ?? {}, props) : props
249
250
  ) as SearchFieldProps;
250
- const mergedProps = withFormValidationBehavior(baseProps, formContext);
251
+
252
+ // When rendered inside an <Autocomplete>, the FieldInputContext (AutocompleteContext)
253
+ // controls the input's value and keyboard handling: value + onChange make the field's
254
+ // state controlled by the shared autocomplete state (which drives the collection
255
+ // filter), and onKeyDown/onFocus/onBlur chain the autocomplete's virtual-focus
256
+ // navigation into the input. This mirrors RAC's
257
+ // useContextProps(props, inputRef, FieldInputContext). The remaining passthroughs
258
+ // (aria-activedescendant/aria-controls/aria-autocomplete) and the input-ref
259
+ // registration are applied at the <input> in SearchFieldInput.
260
+ const autocompleteInput = useAutocompleteInput();
261
+ const autocompleteProps = autocompleteInput
262
+ ? ({
263
+ get value() {
264
+ return autocompleteInput.inputProps.value();
265
+ },
266
+ onChange: autocompleteInput.inputProps.onChange,
267
+ onKeyDown: autocompleteInput.inputProps
268
+ .onKeyDown as unknown as SearchFieldProps["onKeyDown"],
269
+ onFocus: autocompleteInput.inputProps.onFocus as unknown as SearchFieldProps["onFocus"],
270
+ onBlur: autocompleteInput.inputProps.onBlur as unknown as SearchFieldProps["onBlur"],
271
+ } as Partial<SearchFieldProps>)
272
+ : undefined;
273
+
274
+ const mergedProps = withFormValidationBehavior(
275
+ autocompleteProps
276
+ ? (mergeProps(baseProps, autocompleteProps) as SearchFieldProps)
277
+ : baseProps,
278
+ formContext,
279
+ );
251
280
 
252
281
  const [local, stateProps, ariaProps, rest] = splitProps(
253
282
  mergedProps,
@@ -629,6 +658,15 @@ export function SearchFieldInput(props: SearchFieldInputProps): JSX.Element {
629
658
  throw new Error("SearchFieldInput must be used within a SearchField");
630
659
  }
631
660
 
661
+ // Inside an <Autocomplete>, expose the ARIA attributes that identify the
662
+ // controlled collection and its virtually-focused option. value/onChange and the
663
+ // keyboard handlers are already merged upstream in SearchField(); here we only add
664
+ // the passthrough attributes that createTextField doesn't forward, and register the
665
+ // input element as the autocomplete inputRef so the beforeinput + reverse-path
666
+ // (aria-activedescendant mirror) listeners bind. Mirrors RAC's
667
+ // [InputContext, {...inputProps, ref: inputRef}].
668
+ const autocompleteInput = useAutocompleteInput();
669
+
632
670
  const { isFocused, isFocusVisible, focusProps } = createFocusRing();
633
671
 
634
672
  const { isHovered, hoverProps } = createHover({
@@ -667,12 +705,25 @@ export function SearchFieldInput(props: SearchFieldInputProps): JSX.Element {
667
705
  return rest;
668
706
  };
669
707
 
708
+ const autocompleteInputAttrs = (): Record<string, unknown> => {
709
+ if (!autocompleteInput) {
710
+ return {};
711
+ }
712
+ const inputProps = autocompleteInput.inputProps;
713
+ return {
714
+ "aria-controls": inputProps["aria-controls"],
715
+ "aria-autocomplete": inputProps["aria-autocomplete"],
716
+ "aria-activedescendant": inputProps["aria-activedescendant"](),
717
+ };
718
+ };
719
+
670
720
  const mergedInputProps = () =>
671
721
  ({
672
722
  ...domProps,
673
723
  ...cleanInputProps(),
674
724
  ...cleanFocusProps(),
675
725
  ...cleanHoverProps(),
726
+ ...autocompleteInputAttrs(),
676
727
  }) as Record<string, unknown>;
677
728
 
678
729
  onMount(() => {
@@ -723,6 +774,7 @@ export function SearchFieldInput(props: SearchFieldInputProps): JSX.Element {
723
774
  ref={(element) => {
724
775
  inputElement = element;
725
776
  context.setInputRef?.(element);
777
+ autocompleteInput?.inputRef(element);
726
778
  const ref = (domProps as { ref?: unknown }).ref;
727
779
  if (typeof ref === "function") {
728
780
  ref(element);
package/src/Select.tsx CHANGED
@@ -186,11 +186,15 @@ export interface SelectTriggerRenderProps {
186
186
  isFocusVisible: boolean;
187
187
  /** Whether the trigger is hovered. */
188
188
  isHovered: boolean;
189
+ /** Whether the trigger is pressed. */
190
+ isPressed: boolean;
189
191
  /** Whether the trigger is disabled. */
190
192
  isDisabled: boolean;
191
193
  }
192
194
 
193
195
  export interface SelectTriggerProps extends SlotProps {
196
+ /** A ref callback/object for the underlying trigger button element. */
197
+ ref?: RefLike<HTMLButtonElement>;
194
198
  /** The children of the trigger. A function may be provided to receive render props. */
195
199
  children?: RenderChildren<SelectTriggerRenderProps>;
196
200
  /** The CSS className for the element. */
@@ -241,6 +245,8 @@ export interface SelectOptionRenderProps {
241
245
  }
242
246
 
243
247
  export interface SelectOptionProps<T> extends Omit<AriaOptionProps, "children" | "key">, SlotProps {
248
+ /** A ref callback/object for the underlying option element. */
249
+ ref?: RefLike<HTMLDivElement>;
244
250
  /** The unique key for the option. */
245
251
  id: Key;
246
252
  /** The item value. */
@@ -269,6 +275,7 @@ interface SelectContextValue<T> {
269
275
  isOpen: Accessor<boolean>;
270
276
  isFocused: Accessor<boolean>;
271
277
  isFocusVisible: Accessor<boolean>;
278
+ isPressed: Accessor<boolean>;
272
279
  isDisabled: Accessor<boolean>;
273
280
  placeholder?: string;
274
281
  items: T[];
@@ -398,8 +405,16 @@ export function Select<T>(props: SelectProps<T>): JSX.Element {
398
405
  return clean as typeof ariaProps;
399
406
  });
400
407
 
401
- const { labelProps, triggerProps, valueProps, menuProps, isFocused, isFocusVisible, isOpen } =
402
- createSelect<T>(selectAriaProps, state);
408
+ const {
409
+ labelProps,
410
+ triggerProps,
411
+ valueProps,
412
+ menuProps,
413
+ isFocused,
414
+ isFocusVisible,
415
+ isOpen,
416
+ isPressed,
417
+ } = createSelect<T>(selectAriaProps, state);
403
418
 
404
419
  const { isHovered, hoverProps } = createHover({
405
420
  get isDisabled() {
@@ -579,6 +594,17 @@ export function Select<T>(props: SelectProps<T>): JSX.Element {
579
594
 
580
595
  return (
581
596
  <>
597
+ <Show when={ariaProps.label}>
598
+ <span {...cleanLabelProps()}>{ariaProps.label as JSX.Element}</span>
599
+ </Show>
600
+ {selectChildren}
601
+ {/*
602
+ The HiddenSelect (native `<select>` for form autofill/submission) renders
603
+ AFTER the trigger button, mirroring upstream RAC `Select` which emits
604
+ `{renderProps.children}` then `<HiddenSelect>` (Select.tsx:288-289). Order
605
+ matters for the DOM tab/focus trail: the visible button must precede the
606
+ form-only hidden control.
607
+ */}
582
608
  <div {...containerProps}>
583
609
  <select
584
610
  {...hiddenSelectProps}
@@ -598,12 +624,23 @@ export function Select<T>(props: SelectProps<T>): JSX.Element {
598
624
  const fallbackKey =
599
625
  itemRecord != null ? (toKey(itemRecord.key) ?? toKey(itemRecord.id)) : undefined;
600
626
  const key = stateProps.getKey?.(item) ?? fallbackKey ?? String(item);
627
+ // This hidden <select> re-derives each option's label straight
628
+ // from the raw items array, bypassing the collection's textValue.
629
+ // Mirror createListState's derivation: read the conventional
630
+ // display fields (textValue/label/name/title) and, for an object
631
+ // item with none of them, fall back to the key rather than
632
+ // String(item) — which would render the useless "[object Object]".
601
633
  const fallbackTextValue =
602
634
  itemRecord != null
603
- ? (toTextValue(itemRecord.textValue) ?? toTextValue(itemRecord.label))
635
+ ? (toTextValue(itemRecord.textValue) ??
636
+ toTextValue(itemRecord.label) ??
637
+ toTextValue(itemRecord.name) ??
638
+ toTextValue(itemRecord.title))
604
639
  : undefined;
605
640
  const textValue =
606
- stateProps.getTextValue?.(item) ?? fallbackTextValue ?? String(item);
641
+ stateProps.getTextValue?.(item) ??
642
+ fallbackTextValue ??
643
+ (itemRecord != null ? String(key) : String(item));
607
644
  const selectedKeys = state.selectedKeys();
608
645
  const isSelected =
609
646
  state.selectionMode() === "multiple"
@@ -652,10 +689,6 @@ export function Select<T>(props: SelectProps<T>): JSX.Element {
652
689
  />
653
690
  </Show>
654
691
  </div>
655
- <Show when={ariaProps.label}>
656
- <span {...cleanLabelProps()}>{ariaProps.label as JSX.Element}</span>
657
- </Show>
658
- {selectChildren}
659
692
  </>
660
693
  );
661
694
  };
@@ -711,6 +744,7 @@ export function Select<T>(props: SelectProps<T>): JSX.Element {
711
744
  isOpen,
712
745
  isFocused,
713
746
  isFocusVisible,
747
+ isPressed,
714
748
  isDisabled: resolveDisabled,
715
749
  placeholder: ariaProps.placeholder,
716
750
  items: stateProps.items,
@@ -731,17 +765,18 @@ export function Select<T>(props: SelectProps<T>): JSX.Element {
731
765
  * The trigger button for a select.
732
766
  */
733
767
  export function SelectTrigger(props: SelectTriggerProps): JSX.Element {
734
- const [local, domProps] = splitProps(props, ["class", "style", "slot", "children"]);
768
+ const [local, domProps] = splitProps(props, ["class", "style", "slot", "children", "ref"]);
735
769
 
736
770
  const context = useContext(SelectContext);
737
771
  if (!context) {
738
772
  throw new Error("SelectTrigger must be used within a Select");
739
773
  }
740
- const { isOpen, isFocused, isFocusVisible, state } = context;
774
+ const { isOpen, isFocused, isFocusVisible, isPressed, state } = context;
741
775
  let triggerRef: HTMLButtonElement | undefined;
742
776
  const setTriggerRef = (el: HTMLButtonElement) => {
743
777
  triggerRef = el;
744
778
  context.setTriggerRef(el);
779
+ assignRef(local.ref, el);
745
780
  };
746
781
 
747
782
  createEffect(() => {
@@ -761,6 +796,7 @@ export function SelectTrigger(props: SelectTriggerProps): JSX.Element {
761
796
  isFocused: isFocused(),
762
797
  isFocusVisible: isFocusVisible(),
763
798
  isHovered: isHovered(),
799
+ isPressed: isPressed(),
764
800
  isDisabled: state.isDisabled,
765
801
  }));
766
802
 
@@ -926,6 +962,14 @@ export function SelectListBox<T>(props: SelectListBoxProps<T>): JSX.Element {
926
962
  if (!isOpen()) {
927
963
  return;
928
964
  }
965
+ // On open the listbox takes collection focus. Upstream `useSelect` sets
966
+ // `menuProps.autoFocus = state.focusStrategy || true`, so the ListBox's
967
+ // `useSelectableCollection` auto-focuses on mount — setting the selection
968
+ // manager's `isFocused` true and focusing the selected option. Our
969
+ // `createListBox` reimplements keyboard nav inline (no `autoFocus` effect),
970
+ // so we set the manager focus here to match. Without it the selected option
971
+ // never paints its focus-visible background (`data-focused` stays unset).
972
+ state.selectionManager.setFocused(true);
929
973
  if (state.focusedKey() != null) {
930
974
  return;
931
975
  }
@@ -935,7 +979,7 @@ export function SelectListBox<T>(props: SelectListBoxProps<T>): JSX.Element {
935
979
  }
936
980
  });
937
981
 
938
- let listBoxRef: HTMLUListElement | undefined;
982
+ let listBoxRef: HTMLDivElement | undefined;
939
983
 
940
984
  createInteractOutside({
941
985
  ref: () => rootRef() ?? listBoxRef ?? null,
@@ -949,7 +993,14 @@ export function SelectListBox<T>(props: SelectListBoxProps<T>): JSX.Element {
949
993
  },
950
994
  });
951
995
 
952
- const { listBoxProps } = createListBox(
996
+ // Keep the hook result intact — destructuring `{ listBoxProps }` invokes the
997
+ // `get listBoxProps()` getter ONCE and freezes it (the Solid "destructuring a
998
+ // get-prop freezes reactivity" gotcha). The listbox `tabIndex` is computed from
999
+ // `focusedKey` (`focusedKey != null ? -1 : 0`); on open the effect above sets a
1000
+ // focused key, so a frozen snapshot would leave the listbox tabbable (`0`) when
1001
+ // upstream flips it to `-1`. Reading `listBoxHook.listBoxProps` per access keeps
1002
+ // it reactive.
1003
+ const listBoxHook = createListBox(
953
1004
  {
954
1005
  ...(menuProps as unknown as AriaListBoxProps),
955
1006
  shouldSelectOnPressUp: true,
@@ -982,8 +1033,37 @@ export function SelectListBox<T>(props: SelectListBoxProps<T>): JSX.Element {
982
1033
  return rest;
983
1034
  };
984
1035
  const cleanListBoxProps = () => {
985
- const { ref: _ref2, ...rest } = listBoxProps as Record<string, unknown>;
986
- return rest;
1036
+ const {
1037
+ ref: _ref2,
1038
+ "aria-activedescendant": _activeDescendant,
1039
+ onFocus: _onFocus,
1040
+ ...rest
1041
+ } = listBoxHook.listBoxProps as Record<string, unknown>;
1042
+ // Faithful Select listbox focus model: the focused option receives REAL DOM
1043
+ // focus (the open effect above + `createSelectableItem`'s self-focus move
1044
+ // `document.activeElement` onto it), mirroring upstream `useSelect`. So the
1045
+ // listbox must (a) leave the tab sequence once an option is focused —
1046
+ // `useSelectableCollection` uses `tabIndex = focusedKey == null ? 0 : -1`
1047
+ // (useSelectableCollection.ts:687-690) — and (b) NOT expose
1048
+ // `aria-activedescendant`, which is the virtual-focus channel upstream Select
1049
+ // never uses. These are overridden here, not in the shared `createListBox`,
1050
+ // because the standalone `ListBox` keeps its container-focus model (focus
1051
+ // stays on the listbox, `aria-activedescendant` is its AT channel).
1052
+ //
1053
+ // We also drop `createListBox`'s container-focus TRAMPOLINE (`onFocus`): it
1054
+ // marshals the first key when the container is focused with `focusedKey ==
1055
+ // null`, which is exactly Select's deliberate click-open state (the open
1056
+ // effect sets `isFocused` true but leaves `focusedKey` null when nothing is
1057
+ // selected, so the first arrow enters the first item). Upstream's own
1058
+ // trampoline no-ops in this case because it guards on `manager.isFocused`
1059
+ // (already true); our shared guard uses `focusedKey == null` for the
1060
+ // standalone ListBox, so we strip it here to preserve Select's faithful
1061
+ // model. (Auto-selecting on focus under replace behavior would also toggle
1062
+ // the first item selected on open — the bug this override prevents.)
1063
+ return {
1064
+ ...rest,
1065
+ tabIndex: state.isDisabled ? undefined : state.focusedKey() != null ? -1 : 0,
1066
+ };
987
1067
  };
988
1068
 
989
1069
  const items = () => Array.from(state.collection());
@@ -1003,7 +1083,12 @@ export function SelectListBox<T>(props: SelectListBoxProps<T>): JSX.Element {
1003
1083
  });
1004
1084
 
1005
1085
  const listBox = () => (
1006
- <ul
1086
+ // Upstream RAC ListBox (and our own Menu.tsx) render a `<div role="listbox">`
1087
+ // over `<div role="option">` rows, NOT `<ul>`/`<li>` — the collection is
1088
+ // div-based for virtualization parity. `<ul>`/`<li>` here was a self-inflicted
1089
+ // structural divergence surfaced by the Picker recertification (D5/D6/D8 saw
1090
+ // `li[option]`/`ul[listbox]` where the React oracle sees `div`).
1091
+ <div
1007
1092
  ref={(el) => (listBoxRef = el)}
1008
1093
  {...domProps}
1009
1094
  {...cleanMenuProps()}
@@ -1014,9 +1099,9 @@ export function SelectListBox<T>(props: SelectListBoxProps<T>): JSX.Element {
1014
1099
  data-empty={state.collection().size === 0 || undefined}
1015
1100
  >
1016
1101
  {state.collection().size === 0 && local.renderEmptyState ? (
1017
- <li role="option" style={{ display: "contents" }} data-empty-state>
1102
+ <div role="option" style={{ display: "contents" }} data-empty-state>
1018
1103
  {local.renderEmptyState()}
1019
- </li>
1104
+ </div>
1020
1105
  ) : (
1021
1106
  <Show
1022
1107
  when={local.children}
@@ -1040,7 +1125,7 @@ export function SelectListBox<T>(props: SelectListBoxProps<T>): JSX.Element {
1040
1125
  {local.renderLoadMore?.()}
1041
1126
  </ListBoxLoadMoreItem>
1042
1127
  </Show>
1043
- </ul>
1128
+ </div>
1044
1129
  );
1045
1130
 
1046
1131
  return (
@@ -1070,6 +1155,7 @@ export function SelectOption<T>(props: SelectOptionProps<T>): JSX.Element {
1070
1155
  "id",
1071
1156
  "item",
1072
1157
  "textValue",
1158
+ "ref",
1073
1159
  ]);
1074
1160
 
1075
1161
  const context = useContext(SelectStateContext);
@@ -1078,7 +1164,11 @@ export function SelectOption<T>(props: SelectOptionProps<T>): JSX.Element {
1078
1164
  }
1079
1165
  const state = context as SelectState<T>;
1080
1166
  const selectContext = useContext(SelectContext) as SelectContextValue<T> | null;
1081
- const [ref, setRef] = createSignal<HTMLLIElement | null>(null);
1167
+ const [ref, setRefSignal] = createSignal<HTMLDivElement | null>(null);
1168
+ const setRef = (el: HTMLDivElement | null) => {
1169
+ setRefSignal(el);
1170
+ assignRef(local.ref, el as HTMLDivElement);
1171
+ };
1082
1172
 
1083
1173
  const optionAria = createOption<T>(
1084
1174
  {
@@ -1187,7 +1277,7 @@ export function SelectOption<T>(props: SelectOptionProps<T>): JSX.Element {
1187
1277
  selectOption();
1188
1278
  }
1189
1279
  });
1190
- }) as JSX.EventHandler<HTMLLIElement, MouseEvent>;
1280
+ }) as JSX.EventHandler<HTMLDivElement, MouseEvent>;
1191
1281
  return rest;
1192
1282
  };
1193
1283
  const selectOption = () => {
@@ -1209,7 +1299,9 @@ export function SelectOption<T>(props: SelectOptionProps<T>): JSX.Element {
1209
1299
 
1210
1300
  return (
1211
1301
  <SelectionIndicatorContext.Provider value={selectionIndicatorContext()}>
1212
- <li
1302
+ {/* `<div role="option">`, not `<li>` — see the SelectListBox note; upstream
1303
+ RAC options are div-based. */}
1304
+ <div
1213
1305
  ref={setRef}
1214
1306
  {...cleanOptionProps()}
1215
1307
  class={renderProps.class()}
@@ -1226,7 +1318,7 @@ export function SelectOption<T>(props: SelectOptionProps<T>): JSX.Element {
1226
1318
  ) : (
1227
1319
  renderProps.renderChildren()
1228
1320
  )}
1229
- </li>
1321
+ </div>
1230
1322
  </SelectionIndicatorContext.Provider>
1231
1323
  );
1232
1324
  }
package/src/Separator.tsx CHANGED
@@ -6,7 +6,7 @@
6
6
  */
7
7
 
8
8
  import { type JSX, createContext, createMemo, splitProps } from "solid-js";
9
- import { Dynamic } from "solid-js/web";
9
+ import { ElementTag } from "./ElementTag";
10
10
  import {
11
11
  createSeparator,
12
12
  type AriaSeparatorProps,
@@ -115,8 +115,8 @@ export function Separator(props: SeparatorProps): JSX.Element {
115
115
  const domProps = createMemo(() => filterDOMProps(ariaProps, { global: true }));
116
116
 
117
117
  return (
118
- <Dynamic
119
- component={elementType()}
118
+ <ElementTag
119
+ tag={elementType()}
120
120
  {...domProps()}
121
121
  {...separatorAria.separatorProps}
122
122
  ref={(el: HTMLElement) => assignRef(local.ref, el)}