@proyecto-viviana/solidaria-components 0.4.0 → 0.4.1

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 (192) 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 +15 -16
  19. package/dist/Breadcrumbs.js.map +1 -1
  20. package/dist/Breadcrumbs.jsx +19 -24
  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/GridList.d.ts.map +1 -1
  60. package/dist/GridList.js +2 -2
  61. package/dist/GridList.js.map +1 -1
  62. package/dist/GridList.jsx +2 -2
  63. package/dist/GridList.jsx.map +1 -1
  64. package/dist/ListBox.d.ts +2 -2
  65. package/dist/ListBox.d.ts.map +1 -1
  66. package/dist/ListBox.js +327 -166
  67. package/dist/ListBox.js.map +1 -1
  68. package/dist/ListBox.jsx +161 -25
  69. package/dist/ListBox.jsx.map +1 -1
  70. package/dist/Modal.d.ts.map +1 -1
  71. package/dist/Modal.js +7 -5
  72. package/dist/Modal.js.map +1 -1
  73. package/dist/Modal.jsx +5 -5
  74. package/dist/Modal.jsx.map +1 -1
  75. package/dist/Popover.d.ts.map +1 -1
  76. package/dist/Popover.js +16 -9
  77. package/dist/Popover.js.map +1 -1
  78. package/dist/Popover.jsx +25 -4
  79. package/dist/Popover.jsx.map +1 -1
  80. package/dist/RangeCalendar.d.ts.map +1 -1
  81. package/dist/RangeCalendar.js +182 -46
  82. package/dist/RangeCalendar.js.map +1 -1
  83. package/dist/RangeCalendar.jsx +72 -2
  84. package/dist/RangeCalendar.jsx.map +1 -1
  85. package/dist/SearchField.d.ts.map +1 -1
  86. package/dist/SearchField.js +26 -2
  87. package/dist/SearchField.js.map +1 -1
  88. package/dist/SearchField.jsx +26 -2
  89. package/dist/SearchField.jsx.map +1 -1
  90. package/dist/Select.d.ts +7 -0
  91. package/dist/Select.d.ts.map +1 -1
  92. package/dist/Select.js +62 -49
  93. package/dist/Select.js.map +1 -1
  94. package/dist/Select.jsx +33 -18
  95. package/dist/Select.jsx.map +1 -1
  96. package/dist/SharedElementTransition.d.ts.map +1 -1
  97. package/dist/SharedElementTransition.js +35 -33
  98. package/dist/SharedElementTransition.js.map +1 -1
  99. package/dist/SharedElementTransition.jsx +35 -33
  100. package/dist/SharedElementTransition.jsx.map +1 -1
  101. package/dist/StepList.d.ts.map +1 -1
  102. package/dist/StepList.js +2 -9
  103. package/dist/StepList.js.map +1 -1
  104. package/dist/StepList.jsx +1 -6
  105. package/dist/StepList.jsx.map +1 -1
  106. package/dist/Table.d.ts +2 -0
  107. package/dist/Table.d.ts.map +1 -1
  108. package/dist/Table.js +10 -3
  109. package/dist/Table.js.map +1 -1
  110. package/dist/Table.jsx +10 -3
  111. package/dist/Table.jsx.map +1 -1
  112. package/dist/Tabs.d.ts.map +1 -1
  113. package/dist/Tabs.js +3 -0
  114. package/dist/Tabs.js.map +1 -1
  115. package/dist/Tabs.jsx +1 -1
  116. package/dist/Tabs.jsx.map +1 -1
  117. package/dist/TagGroup.d.ts +3 -1
  118. package/dist/TagGroup.d.ts.map +1 -1
  119. package/dist/TagGroup.js +12 -8
  120. package/dist/TagGroup.js.map +1 -1
  121. package/dist/TagGroup.jsx +11 -8
  122. package/dist/TagGroup.jsx.map +1 -1
  123. package/dist/TextField.js +4 -1
  124. package/dist/TextField.js.map +1 -1
  125. package/dist/TextField.jsx +4 -1
  126. package/dist/TextField.jsx.map +1 -1
  127. package/dist/TimeField.d.ts +14 -50
  128. package/dist/TimeField.d.ts.map +1 -1
  129. package/dist/TimeField.js +111 -205
  130. package/dist/TimeField.js.map +1 -1
  131. package/dist/TimeField.jsx +76 -82
  132. package/dist/TimeField.jsx.map +1 -1
  133. package/dist/Tree.d.ts.map +1 -1
  134. package/dist/Tree.js +3 -3
  135. package/dist/Tree.js.map +1 -1
  136. package/dist/Tree.jsx +3 -3
  137. package/dist/Tree.jsx.map +1 -1
  138. package/dist/index.d.ts +2 -3
  139. package/dist/index.d.ts.map +1 -1
  140. package/dist/index.js +5 -6
  141. package/dist/index.jsx +5 -6
  142. package/dist/useDragAndDrop.d.ts +0 -8
  143. package/dist/useDragAndDrop.d.ts.map +1 -1
  144. package/dist/useDragAndDrop2.js +6 -2
  145. package/dist/useDragAndDrop2.js.map +1 -1
  146. package/dist/useDragAndDrop2.jsx +6 -2
  147. package/dist/useDragAndDrop2.jsx.map +1 -1
  148. package/dist/utils.d.ts +1 -0
  149. package/dist/utils.d.ts.map +1 -1
  150. package/dist/utils.js +2 -2
  151. package/dist/utils.js.map +1 -1
  152. package/dist/utils.jsx +2 -2
  153. package/dist/utils.jsx.map +1 -1
  154. package/package.json +3 -9
  155. package/src/ActionBar.tsx +10 -12
  156. package/src/ActionGroup.tsx +5 -3
  157. package/src/Autocomplete.tsx +14 -9
  158. package/src/Breadcrumbs.tsx +55 -38
  159. package/src/Calendar.tsx +30 -1
  160. package/src/Color.tsx +42 -27
  161. package/src/ComboBox.tsx +49 -17
  162. package/src/DateField.tsx +285 -52
  163. package/src/DatePicker.tsx +345 -57
  164. package/src/DateRangePickerContext.tsx +2 -0
  165. package/src/Disclosure.tsx +4 -0
  166. package/src/GridList.tsx +8 -2
  167. package/src/ListBox.tsx +323 -21
  168. package/src/Modal.tsx +21 -5
  169. package/src/Popover.tsx +28 -7
  170. package/src/RangeCalendar.tsx +120 -1
  171. package/src/SearchField.tsx +53 -1
  172. package/src/Select.tsx +101 -20
  173. package/src/SharedElementTransition.tsx +92 -36
  174. package/src/StepList.tsx +0 -7
  175. package/src/Table.tsx +19 -4
  176. package/src/Tabs.tsx +1 -0
  177. package/src/TagGroup.tsx +26 -6
  178. package/src/TextField.tsx +1 -1
  179. package/src/TimeField.tsx +113 -196
  180. package/src/Tree.tsx +14 -4
  181. package/src/index.ts +1 -8
  182. package/src/useDragAndDrop.ts +8 -10
  183. package/src/utils.tsx +3 -3
  184. package/dist/DatePicker.js.map +0 -1
  185. package/dist/DatePicker.jsx.map +0 -1
  186. package/dist/HiddenTimeInput.d.ts +0 -25
  187. package/dist/HiddenTimeInput.d.ts.map +0 -1
  188. package/dist/HiddenTimeInput.js +0 -104
  189. package/dist/HiddenTimeInput.js.map +0 -1
  190. package/dist/HiddenTimeInput.jsx +0 -69
  191. package/dist/HiddenTimeInput.jsx.map +0 -1
  192. package/src/HiddenTimeInput.tsx +0 -133
package/src/DateField.tsx CHANGED
@@ -15,14 +15,25 @@ import {
15
15
  onCleanup,
16
16
  splitProps,
17
17
  useContext,
18
- For,
18
+ Index,
19
19
  Show,
20
20
  } from "solid-js";
21
21
  import {
22
22
  createDateField,
23
23
  createDateSegment,
24
+ createFocusRing,
25
+ createHover,
26
+ createVisuallyHidden,
27
+ mergeProps,
24
28
  type AriaDateFieldProps,
25
29
  } from "@proyecto-viviana/solidaria";
30
+ import {
31
+ parseDate,
32
+ parseDateTime,
33
+ toCalendarDate,
34
+ toCalendarDateTime,
35
+ toLocalTimeZone,
36
+ } from "@internationalized/date";
26
37
  import {
27
38
  createDateFieldState,
28
39
  access,
@@ -43,7 +54,6 @@ import {
43
54
  Provider,
44
55
  } from "./utils";
45
56
  import { TextContext } from "./Text";
46
- import { HiddenDateInput } from "./HiddenDateInput";
47
57
  import { FormContext, type FormProps } from "./Form";
48
58
  import {
49
59
  DateRangePickerContext,
@@ -95,12 +105,20 @@ export interface DateInputProps extends SlotProps {
95
105
  }
96
106
 
97
107
  export interface DateSegmentRenderProps {
108
+ /** Whether the segment is hovered. */
109
+ isHovered: boolean;
98
110
  /** Whether the segment is focused. */
99
111
  isFocused: boolean;
100
- /** Whether the segment is editable. */
101
- isEditable: boolean;
112
+ /** Whether the segment is keyboard focused. */
113
+ isFocusVisible: boolean;
102
114
  /** Whether the segment is a placeholder. */
103
115
  isPlaceholder: boolean;
116
+ /** Whether the segment is read only. */
117
+ isReadOnly: boolean;
118
+ /** Whether the segment is disabled. */
119
+ isDisabled: boolean;
120
+ /** Whether the segment is invalid. */
121
+ isInvalid: boolean;
104
122
  /** The segment type. */
105
123
  type: DateSegmentType["type"];
106
124
  /** The text to display. */
@@ -123,6 +141,10 @@ export interface DateFieldContextValue {
123
141
  aria: {
124
142
  labelProps: Record<string, unknown>;
125
143
  inputProps: Record<string, unknown>;
144
+ // The hidden validation <input> props (native type/required/onChange). Only
145
+ // the standalone DateField provides these; DatePicker/DateRangePicker manage
146
+ // their own hidden inputs, so this stays optional and its <input> is gated.
147
+ hiddenInputProps?: Record<string, unknown>;
126
148
  descriptionProps: Record<string, unknown>;
127
149
  errorMessageProps: Record<string, unknown>;
128
150
  };
@@ -278,14 +300,37 @@ function DateFieldInner<T extends DateValue = CalendarDate>(
278
300
  );
279
301
 
280
302
  const [fieldRef, setFieldRef] = createSignal<HTMLDivElement | null>(null);
303
+ // Ref to the hidden validation <input> (the DateInput's `<Input>` sibling).
304
+ // RAC's DateField creates this ref, passes it to useDateField, and threads it
305
+ // onto the <Input> via InputContext so useFormReset/useFormValidation target
306
+ // that element. We mirror that: create it here, hand it to createDateField
307
+ // below, and attach it to the same input through the context getter.
308
+ const [validationInputRef, setValidationInputRef] =
309
+ createSignal<HTMLInputElement | null>(null);
281
310
 
282
311
  const state = createDateFieldState(stateProps);
283
312
 
284
313
  const fieldAria = createDateField(
285
314
  () => ({
286
315
  ...(rest as Record<string, unknown>),
316
+ // RAC threads the same props into both useDateFieldState and useDateField;
317
+ // these live in `stateProps` here, so forward them so the field hook can
318
+ // build aria-disabled and the native validation <input> (type/required).
319
+ // access() unwraps the MaybeAccessor state-prop declarations.
320
+ isDisabled: access(stateProps.isDisabled),
321
+ isReadOnly: access(stateProps.isReadOnly),
322
+ isRequired: access(stateProps.isRequired),
323
+ // Mirror RAC DateField: validationBehavior ?? formValidationBehavior ??
324
+ // 'native'. withFormValidationBehavior already folds the form default into
325
+ // stateProps.validationBehavior; the last `?? 'native'` is the standalone
326
+ // default that flips the hidden input to type="text".
327
+ validationBehavior: stateProps.validationBehavior ?? "native",
287
328
  description: stateProps.description,
288
329
  errorMessage: stateProps.errorMessage,
330
+ // Form-reset + native constraint validation are wired onto this input by
331
+ // createDateField (see its createFormReset/createFormValidation calls),
332
+ // exactly as RAC's useDateField wires them onto props.inputRef.
333
+ inputRef: () => validationInputRef() ?? undefined,
289
334
  }),
290
335
  state as unknown as DateFieldState<DateValue>,
291
336
  fieldRef,
@@ -307,11 +352,6 @@ function DateFieldInner<T extends DateValue = CalendarDate>(
307
352
  renderValues,
308
353
  );
309
354
 
310
- const validationBehavior = () =>
311
- (stateProps as { validationBehavior?: "aria" | "native" }).validationBehavior ??
312
- formContext?.validationBehavior ??
313
- "native";
314
-
315
355
  const textSlots = {
316
356
  slots: {
317
357
  get description() {
@@ -328,17 +368,41 @@ function DateFieldInner<T extends DateValue = CalendarDate>(
328
368
  <DateFieldContext.Provider
329
369
  value={{
330
370
  state: state as unknown as DateFieldState<DateValue>,
371
+ // Read through getters so consumers see the LIVE memo values, not a
372
+ // snapshot frozen at first render. `createDescription` appends the
373
+ // value-description id via a deferred effect; the group's
374
+ // aria-describedby only picks it up if the props stay reactive here.
331
375
  aria: {
332
- labelProps: fieldAria.labelProps,
333
- inputProps: fieldAria.inputProps,
334
- descriptionProps: fieldAria.descriptionProps,
335
- errorMessageProps: fieldAria.errorMessageProps,
376
+ get labelProps() {
377
+ return fieldAria.labelProps;
378
+ },
379
+ // The segment group props (role="group", aria-labelledby, unicode-bidi
380
+ // isolate style, arrow-key nav, focus-within handlers) are routed to the
381
+ // DateInput element — mirroring RAC, where useDateField.fieldProps is
382
+ // published through GroupContext and consumed by the DateInput <Group>.
383
+ // The outer wrapper stays roleless.
384
+ get inputProps() {
385
+ return fieldAria.fieldProps;
386
+ },
387
+ // The REAL hidden validation input props (type/hidden/required/onChange
388
+ // under native validation). Rendered as the DateInput's <input> sibling.
389
+ // Thread the validation-input ref through here — mirroring RAC's
390
+ // `[InputContext, {...inputProps, ref: inputRef}]` — so createDateField's
391
+ // form-reset/validation wiring resolves to the mounted element.
392
+ get hiddenInputProps() {
393
+ return mergeProps(fieldAria.inputProps, { ref: setValidationInputRef });
394
+ },
395
+ get descriptionProps() {
396
+ return fieldAria.descriptionProps;
397
+ },
398
+ get errorMessageProps() {
399
+ return fieldAria.errorMessageProps;
400
+ },
336
401
  },
337
402
  }}
338
403
  >
339
404
  <div
340
405
  ref={setFieldRef}
341
- {...fieldAria.fieldProps}
342
406
  class={renderProps.class()}
343
407
  style={renderProps.style()}
344
408
  data-disabled={dataAttr(state.isDisabled())}
@@ -350,29 +414,121 @@ function DateFieldInner<T extends DateValue = CalendarDate>(
350
414
  {local.children as JSX.Element}
351
415
  </Provider>
352
416
  </div>
353
- <Show when={(rest as Record<string, unknown>).name}>
354
- <HiddenDateInput
355
- name={(rest as Record<string, unknown>).name as string | undefined}
356
- form={(rest as Record<string, unknown>).form as string | undefined}
357
- value={state.value()}
358
- autoComplete={(rest as Record<string, unknown>).autoComplete as string | undefined}
359
- isDisabled={state.isDisabled()}
360
- isRequired={state.isRequired()}
361
- validationBehavior={validationBehavior()}
362
- validationState={state}
363
- focus={() => {
364
- fieldRef()?.querySelector<HTMLElement>('[role="spinbutton"]')?.focus();
365
- }}
366
- minValue={access(stateProps.minValue) as DateValue | undefined}
367
- maxValue={access(stateProps.maxValue) as DateValue | undefined}
368
- granularity={state.granularity}
369
- />
370
- </Show>
417
+ {/*
418
+ RAC renders <HiddenDateInput> UNCONDITIONALLY at the DateField root
419
+ (react-aria-components DateField.mjs). It is NOT a validation input — it
420
+ is a clipped, aria-hidden, tabIndex={-1} native date input that mirrors
421
+ the value for browser autofill. Its `form=""` detaches it from form
422
+ submission (the DateInput's <Input> above is what submits), so it never
423
+ double-counts in FormData. It renders regardless of `name` — hence no
424
+ <Show> gate — which is why it appears in the focus trail as the trailing
425
+ tabindex="-1" element.
426
+ */}
427
+ <RootHiddenDateInput
428
+ autoComplete={(rest as Record<string, unknown>).autoComplete as string | undefined}
429
+ name={(rest as Record<string, unknown>).name as string | undefined}
430
+ isDisabled={state.isDisabled()}
431
+ state={state as unknown as DateFieldState<DateValue>}
432
+ />
371
433
  </DateFieldContext.Provider>
372
434
  </DateFieldStateContext.Provider>
373
435
  );
374
436
  }
375
437
 
438
+ const HIDDEN_DATE_SEGMENTS: readonly string[] = ["day", "month", "year"];
439
+ const HIDDEN_TIME_SEGMENTS: readonly string[] = ["hour", "minute", "second"];
440
+ const HIDDEN_GRANULARITY_MAP: Record<string, number> = { hour: 1, minute: 2, second: 3 };
441
+
442
+ interface RootHiddenDateInputProps {
443
+ autoComplete?: string;
444
+ name?: string;
445
+ isDisabled?: boolean;
446
+ state: DateFieldState<DateValue>;
447
+ }
448
+
449
+ /**
450
+ * The always-rendered hidden date input at the DateField root — a faithful port
451
+ * of react-aria-components' HiddenDateInput (private/HiddenDateInput.mjs). It is
452
+ * NOT a validation input: it is a clipped, aria-hidden, tabIndex={-1} native
453
+ * date input that mirrors the current value for browser autofill. `form=""`
454
+ * detaches it from form submission (the DateInput's <Input> submits), so it
455
+ * never double-counts in FormData. RAC renders it unconditionally, which is why
456
+ * it appears as the trailing tabindex="-1" element in the focus trail.
457
+ */
458
+ function RootHiddenDateInput(props: RootHiddenDateInputProps): JSX.Element {
459
+ // RAC uses useVisuallyHidden with position:fixed/top:0/left:0 to keep the
460
+ // clipped input from scrolling the page.
461
+ const { visuallyHiddenProps } = createVisuallyHidden(() => ({
462
+ style: { position: "fixed", top: "0", left: "0" },
463
+ }));
464
+
465
+ const granularity = () => props.state.granularity ?? "day";
466
+ const inputStep = () => {
467
+ const g = granularity();
468
+ if (g === "second") return 1;
469
+ if (g === "hour") return 3600;
470
+ return 60;
471
+ };
472
+ const inputType = () => (granularity() === "day" ? "date" : "datetime-local");
473
+ const dateValue = () => {
474
+ const value = props.state.value();
475
+ if (!value) return "";
476
+ if (granularity() === "day") return toCalendarDate(value).toString();
477
+ return toCalendarDateTime(
478
+ "timeZone" in value ? toLocalTimeZone(value) : value,
479
+ ).toString();
480
+ };
481
+
482
+ const onChange: JSX.EventHandler<HTMLInputElement, Event> = (event) => {
483
+ const targetString = (event.currentTarget.value ?? "").toString();
484
+ if (!targetString) return;
485
+ try {
486
+ const g = granularity();
487
+ const targetValue = (g === "day" ? parseDate(targetString) : parseDateTime(targetString)) as
488
+ unknown as Record<string, number>;
489
+ let timeSegments = HIDDEN_TIME_SEGMENTS;
490
+ if (HIDDEN_TIME_SEGMENTS.includes(g)) {
491
+ timeSegments = HIDDEN_TIME_SEGMENTS.slice(0, HIDDEN_GRANULARITY_MAP[g]);
492
+ }
493
+ const state = props.state as unknown as {
494
+ setSegment?: (type: string, value: number) => void;
495
+ setValue: (value: unknown) => void;
496
+ };
497
+ // Only DateFieldState (not DatePickerState) exposes setSegment; validate
498
+ // each parsed segment before committing the new value, mirroring RAC.
499
+ if (typeof state.setSegment === "function") {
500
+ for (const type in targetValue) {
501
+ if (HIDDEN_DATE_SEGMENTS.includes(type)) state.setSegment(type, targetValue[type]);
502
+ if (timeSegments.includes(type)) state.setSegment(type, targetValue[type]);
503
+ }
504
+ }
505
+ state.setValue(targetValue);
506
+ } catch {
507
+ // ignore unparseable autofill values
508
+ }
509
+ };
510
+
511
+ return (
512
+ <div
513
+ {...(visuallyHiddenProps() as unknown as Record<string, unknown>)}
514
+ aria-hidden="true"
515
+ data-testid="hidden-dateinput-container"
516
+ >
517
+ <input
518
+ tabIndex={-1}
519
+ autocomplete={props.autoComplete}
520
+ disabled={props.isDisabled}
521
+ type={inputType()}
522
+ form=""
523
+ name={props.name}
524
+ step={inputStep()}
525
+ value={dateValue()}
526
+ onChange={onChange}
527
+ />
528
+ </div>
529
+ );
530
+ }
531
+
376
532
  /**
377
533
  * The input area containing date segments.
378
534
  */
@@ -396,6 +552,20 @@ export function DateInput(props: DateInputProps): JSX.Element {
396
552
  renderValues,
397
553
  );
398
554
 
555
+ // The group props (role="group", aria-labelledby, unicode-bidi:isolate,
556
+ // arrow-key nav, focus-within handlers) carry the field's own `style` and
557
+ // focus handlers. Merge — not clobber — so the group keeps its unicode-bidi
558
+ // isolation and the local isFocused signal chains onto the field's
559
+ // focusWithin handlers. Solid onFocus/onBlur do not bubble, so track focus
560
+ // via onFocusIn/onFocusOut.
561
+ const inputDivProps = createMemo(() =>
562
+ mergeProps(aria.inputProps, {
563
+ onFocusIn: () => setIsFocused(true),
564
+ onFocusOut: () => setIsFocused(false),
565
+ style: renderProps.style(),
566
+ }),
567
+ );
568
+
399
569
  createEffect(() => {
400
570
  const element = inputRef();
401
571
  const handler = props.onPointerDownCapture;
@@ -411,16 +581,41 @@ export function DateInput(props: DateInputProps): JSX.Element {
411
581
  <DateFieldContext.Provider value={context as DateFieldContextValue}>
412
582
  <div
413
583
  ref={setInputRef}
414
- {...aria.inputProps}
584
+ {...inputDivProps()}
415
585
  class={renderProps.class()}
416
- style={renderProps.style()}
417
586
  data-disabled={dataAttr(state.isDisabled())}
418
587
  data-focused={dataAttr(isFocused())}
419
- onFocusIn={() => setIsFocused(true)}
420
- onFocusOut={() => setIsFocused(false)}
421
588
  >
422
- <For each={state.segments()}>{(segment) => props.children?.(segment)}</For>
589
+ {/*
590
+ <Index> keys by position, not identity, so each DateSegment instance
591
+ stays alive across keystrokes (state.segments() re-mints segment
592
+ objects every edit). A stable Proxy forwards property reads to the
593
+ live per-index accessor so the child stays reactive.
594
+ */}
595
+ <Index each={state.segments()}>
596
+ {(segment) => {
597
+ const liveSegment = new Proxy({} as DateSegmentType, {
598
+ get: (_t, key) => (segment() as unknown as Record<PropertyKey, unknown>)[key],
599
+ has: (_t, key) => key in (segment() as object),
600
+ });
601
+ return props.children?.(liveSegment);
602
+ }}
603
+ </Index>
423
604
  </div>
605
+ {/*
606
+ The hidden validation input, sibling of the group — mirrors RAC's
607
+ DateInputInner `<><Group/><Input/></>`. Under native validation
608
+ (default) `useDateField` publishes it as `type="text" hidden required`
609
+ so browser constraint validation fires; under aria validation it stays
610
+ `type="hidden"`. Both carry the ISO value string. Only the standalone
611
+ DateField supplies these props; inside a DatePicker/DateRangePicker the
612
+ field is presentation and its owner renders the hidden input instead.
613
+ */}
614
+ <Show when={aria.hiddenInputProps}>
615
+ {(hiddenInputProps) => (
616
+ <input {...(hiddenInputProps() as JSX.InputHTMLAttributes<HTMLInputElement>)} />
617
+ )}
618
+ </Show>
424
619
  </DateFieldContext.Provider>
425
620
  );
426
621
  }
@@ -432,14 +627,34 @@ export function DateSegment(props: DateSegmentProps): JSX.Element {
432
627
  const { state } = useDateFieldContext();
433
628
  const [segmentRef, setSegmentRef] = createSignal<HTMLElement | null>(null);
434
629
 
435
- const segmentAria = createDateSegment({ segment: props.segment }, state, segmentRef);
630
+ const segmentAria = createDateSegment(
631
+ {
632
+ get segment() {
633
+ return props.segment;
634
+ },
635
+ },
636
+ state,
637
+ segmentRef,
638
+ );
639
+
640
+ // Mirror upstream DateSegment: focus/focus-visible come from useFocusRing()
641
+ // and hover from useHover({isDisabled: field disabled || literal segment}).
642
+ // Literals never hover; the field being disabled suppresses hover entirely.
643
+ const { isFocused, isFocusVisible, focusProps } = createFocusRing();
644
+ const { isHovered, hoverProps } = createHover(() => ({
645
+ isDisabled: state.isDisabled() || props.segment.type === "literal",
646
+ }));
436
647
 
437
648
  const renderValues = createMemo<DateSegmentRenderProps>(() => ({
438
- isFocused: segmentAria.isFocused,
439
- isEditable: segmentAria.isEditable,
440
- isPlaceholder: segmentAria.isPlaceholder,
649
+ isHovered: isHovered(),
650
+ isFocused: isFocused(),
651
+ isFocusVisible: isFocusVisible(),
652
+ isPlaceholder: props.segment.isPlaceholder,
653
+ isReadOnly: state.isReadOnly(),
654
+ isDisabled: state.isDisabled(),
655
+ isInvalid: state.isInvalid(),
441
656
  type: props.segment.type,
442
- text: segmentAria.text,
657
+ text: props.segment.text,
443
658
  }));
444
659
 
445
660
  const renderProps = useRenderProps(
@@ -459,19 +674,37 @@ export function DateSegment(props: DateSegmentProps): JSX.Element {
459
674
  if (typeof props.children === "function") {
460
675
  return renderProps.renderChildren();
461
676
  }
462
- return segmentAria.text;
677
+ return props.segment.text;
463
678
  };
464
679
 
680
+ // Mirror upstream's `mergeProps(filterDOMProps(otherProps), segmentProps,
681
+ // focusProps, hoverProps)`: the segment's behavior/style props (caretColor +
682
+ // RTL direction) chain with the focus-ring and hover handlers so neither
683
+ // clobbers the other.
684
+ const segmentElementProps = createMemo(() =>
685
+ mergeProps(
686
+ segmentAria.segmentProps,
687
+ focusProps as Record<string, unknown>,
688
+ hoverProps as Record<string, unknown>,
689
+ {
690
+ style: renderProps.style(),
691
+ },
692
+ ),
693
+ );
694
+
465
695
  return (
466
696
  <span
467
697
  ref={setSegmentRef}
468
- {...segmentAria.segmentProps}
698
+ {...segmentElementProps()}
469
699
  class={renderProps.class()}
470
- style={renderProps.style()}
471
- data-focused={dataAttr(segmentAria.isFocused)}
472
- data-editable={dataAttr(segmentAria.isEditable)}
473
- data-placeholder={dataAttr(segmentAria.isPlaceholder)}
700
+ data-placeholder={props.segment.isPlaceholder || undefined}
701
+ data-invalid={state.isInvalid() || undefined}
702
+ data-readonly={state.isReadOnly() || undefined}
703
+ data-disabled={state.isDisabled() || undefined}
474
704
  data-type={props.segment.type}
705
+ data-hovered={isHovered() || undefined}
706
+ data-focused={isFocused() || undefined}
707
+ data-focus-visible={isFocusVisible() || undefined}
475
708
  >
476
709
  {getChildren()}
477
710
  </span>
@@ -500,9 +733,9 @@ export interface DateFieldDescriptionProps {
500
733
  export function DateFieldDescription(props: DateFieldDescriptionProps): JSX.Element {
501
734
  const { aria } = useDateFieldContext();
502
735
  return (
503
- <p {...aria.descriptionProps} class={props.class}>
736
+ <span {...aria.descriptionProps} class={props.class}>
504
737
  {props.children}
505
- </p>
738
+ </span>
506
739
  );
507
740
  }
508
741
 
@@ -514,9 +747,9 @@ export interface DateFieldErrorMessageProps {
514
747
  export function DateFieldErrorMessage(props: DateFieldErrorMessageProps): JSX.Element {
515
748
  const { aria } = useDateFieldContext();
516
749
  return (
517
- <p {...aria.errorMessageProps} class={props.class}>
750
+ <span {...aria.errorMessageProps} class={props.class}>
518
751
  {props.children}
519
- </p>
752
+ </span>
520
753
  );
521
754
  }
522
755