@proyecto-viviana/solidaria-components 0.5.0 → 0.5.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.
package/src/DateField.tsx CHANGED
@@ -305,8 +305,7 @@ function DateFieldInner<T extends DateValue = CalendarDate>(
305
305
  // onto the <Input> via InputContext so useFormReset/useFormValidation target
306
306
  // that element. We mirror that: create it here, hand it to createDateField
307
307
  // below, and attach it to the same input through the context getter.
308
- const [validationInputRef, setValidationInputRef] =
309
- createSignal<HTMLInputElement | null>(null);
308
+ const [validationInputRef, setValidationInputRef] = createSignal<HTMLInputElement | null>(null);
310
309
 
311
310
  const state = createDateFieldState(stateProps);
312
311
 
@@ -474,9 +473,7 @@ function RootHiddenDateInput(props: RootHiddenDateInputProps): JSX.Element {
474
473
  const value = props.state.value();
475
474
  if (!value) return "";
476
475
  if (granularity() === "day") return toCalendarDate(value).toString();
477
- return toCalendarDateTime(
478
- "timeZone" in value ? toLocalTimeZone(value) : value,
479
- ).toString();
476
+ return toCalendarDateTime("timeZone" in value ? toLocalTimeZone(value) : value).toString();
480
477
  };
481
478
 
482
479
  const onChange: JSX.EventHandler<HTMLInputElement, Event> = (event) => {
@@ -484,8 +481,9 @@ function RootHiddenDateInput(props: RootHiddenDateInputProps): JSX.Element {
484
481
  if (!targetString) return;
485
482
  try {
486
483
  const g = granularity();
487
- const targetValue = (g === "day" ? parseDate(targetString) : parseDateTime(targetString)) as
488
- unknown as Record<string, number>;
484
+ const targetValue = (g === "day"
485
+ ? parseDate(targetString)
486
+ : parseDateTime(targetString)) as unknown as Record<string, number>;
489
487
  let timeSegments = HIDDEN_TIME_SEGMENTS;
490
488
  if (HIDDEN_TIME_SEGMENTS.includes(g)) {
491
489
  timeSegments = HIDDEN_TIME_SEGMENTS.slice(0, HIDDEN_GRANULARITY_MAP[g]);
@@ -601,10 +601,10 @@ function DatePickerInner<T extends DateValue = CalendarDate>(
601
601
  >
602
602
  <CalendarContext.Provider value={calendarState as unknown as CalendarState<DateValue>}>
603
603
  {/* BARE ROLELESS root — mirrors RAC `DatePicker`'s outer `<div>`. The
604
- * presentation FieldGroup shell (rendered as a child via
605
- * `DatePickerFieldGroup`) is what carries `pickerAria.groupProps`
606
- * (role="presentation" + label/describedby + arrow-nav/press). A
607
- * described node here would be a spurious AX entry the S2 oracle lacks. */}
604
+ * presentation FieldGroup shell (rendered as a child via
605
+ * `DatePickerFieldGroup`) is what carries `pickerAria.groupProps`
606
+ * (role="presentation" + label/describedby + arrow-nav/press). A
607
+ * described node here would be a spurious AX entry the S2 oracle lacks. */}
608
608
  <div
609
609
  ref={setFieldRef}
610
610
  class={renderProps.class()}
@@ -960,12 +960,12 @@ function DateRangePickerInner<T extends DateValue = CalendarDate>(
960
960
  value={calendarState as unknown as RangeCalendarState<DateValue>}
961
961
  >
962
962
  {/* BARE ROLELESS root — mirrors RAC `DateRangePicker`'s outer `<div>`.
963
- * The presentation FieldGroup shell (rendered as a child via the styled
964
- * DateRangeDisplay) is what carries `pickerAria.groupProps` (role=
965
- * "presentation" + label/describedby + outer arrow-nav/press). A described
966
- * or role="group" node here would be a spurious AX entry the S2 oracle
967
- * lacks. The ref scopes the shared segment focus manager across both
968
- * fields (see createDateRangePicker). */}
963
+ * The presentation FieldGroup shell (rendered as a child via the styled
964
+ * DateRangeDisplay) is what carries `pickerAria.groupProps` (role=
965
+ * "presentation" + label/describedby + outer arrow-nav/press). A described
966
+ * or role="group" node here would be a spurious AX entry the S2 oracle
967
+ * lacks. The ref scopes the shared segment focus manager across both
968
+ * fields (see createDateRangePicker). */}
969
969
  <div
970
970
  ref={setFieldRef}
971
971
  class={renderProps.class()}
@@ -1409,14 +1409,14 @@ export function DatePickerContent(props: DatePickerContentProps): JSX.Element {
1409
1409
  <Portal mount={portalContainer()}>
1410
1410
  <FocusScope contain restoreFocus>
1411
1411
  {/* Un-folded to mirror S2's `<Popover>` DOM. The OUTER role-null `<div>`
1412
- * is the positioned surface that carries the enter motion + chrome —
1413
- * this is the element RAC/S2 animate (`AriaPopover`'s div: role null, no
1414
- * tabindex, accessible name computed from the calendar's textContent).
1415
- * A nested `role="dialog"` element (RAC's `Dialog`, which S2's
1416
- * CalendarPopover wraps the Calendar in) carries the dialog semantics.
1417
- * Folding both onto one `<section role="dialog">` made D2 animate the
1418
- * dialog node instead of the role-null div; the role-null wrapper is
1419
- * pruned from the AX tree so D5/D6 are unaffected. */}
1412
+ * is the positioned surface that carries the enter motion + chrome —
1413
+ * this is the element RAC/S2 animate (`AriaPopover`'s div: role null, no
1414
+ * tabindex, accessible name computed from the calendar's textContent).
1415
+ * A nested `role="dialog"` element (RAC's `Dialog`, which S2's
1416
+ * CalendarPopover wraps the Calendar in) carries the dialog semantics.
1417
+ * Folding both onto one `<section role="dialog">` made D2 animate the
1418
+ * dialog node instead of the role-null div; the role-null wrapper is
1419
+ * pruned from the AX tree so D5/D6 are unaffected. */}
1420
1420
  <div
1421
1421
  ref={setContentRef}
1422
1422
  {...cleanPopoverProps()}
@@ -1426,8 +1426,8 @@ export function DatePickerContent(props: DatePickerContentProps): JSX.Element {
1426
1426
  data-entering={dataAttr(isEntering())}
1427
1427
  >
1428
1428
  {/* RAC's `Dialog` renders a `<section role="dialog">` by default — the
1429
- * D5 roving trail records this element's tag, so it must be `section`
1430
- * (a `<div>` here regressed the focus-trail tag match). */}
1429
+ * D5 roving trail records this element's tag, so it must be `section`
1430
+ * (a `<div>` here regressed the focus-trail tag match). */}
1431
1431
  <section {...context.pickerAria.dialogProps} tabIndex={-1}>
1432
1432
  {props.children}
1433
1433
  </section>
@@ -1514,11 +1514,11 @@ export function DateRangePickerContent(props: DateRangePickerContentProps): JSX.
1514
1514
  <Portal mount={portalContainer()}>
1515
1515
  <FocusScope contain restoreFocus>
1516
1516
  {/* Un-folded to mirror S2's `<Popover>` DOM (see DatePickerContent).
1517
- * OUTER role-null `<div>` = positioned surface carrying the enter
1518
- * motion + chrome (the element RAC/S2 animate; pruned from the AX
1519
- * tree). Nested `<section role="dialog">` carries the dialog
1520
- * semantics — the D5 roving trail records this tag, so it must be
1521
- * `section`, not a folded `div`. */}
1517
+ * OUTER role-null `<div>` = positioned surface carrying the enter
1518
+ * motion + chrome (the element RAC/S2 animate; pruned from the AX
1519
+ * tree). Nested `<section role="dialog">` carries the dialog
1520
+ * semantics — the D5 roving trail records this tag, so it must be
1521
+ * `section`, not a folded `div`. */}
1522
1522
  <div
1523
1523
  ref={setContentRef}
1524
1524
  {...cleanPopoverProps()}
package/src/Form.tsx CHANGED
@@ -121,6 +121,23 @@ export function Form(props: FormProps): JSX.Element {
121
121
 
122
122
  const filteredDomProps = filterFormDOMProps(domProps);
123
123
 
124
+ // FormContext is only consumed for `validationBehavior` (TextField, Checkbox,
125
+ // DateField, …). Never spread `props` into the context value: in Solid,
126
+ // `props.children` is a getter that *creates* the child tree when read. Eager
127
+ // `{...props}` therefore builds every child once for the discarded context
128
+ // object and again for `renderChildren()`, double-advancing createUniqueId
129
+ // and desyncing SSR hydration keys. RAC can spread props because React
130
+ // elements are already-built descriptors; Solid cannot.
131
+ //
132
+ // Surface symptom (effect-latam /perfil, /foros): Form+sole Spectrum Button
133
+ // → "Hydration Mismatch" / "template is not a function" and a blank route.
134
+ // Form+TextField (multiple children) often still hydrates because array/
135
+ // fragment child paths mask the slot drift — the double-create is still
136
+ // wrong either way.
137
+ const formContextValue: FormProps = {
138
+ validationBehavior,
139
+ };
140
+
124
141
  return (
125
142
  <form
126
143
  {...filteredDomProps}
@@ -130,7 +147,7 @@ export function Form(props: FormProps): JSX.Element {
130
147
  style={renderProps.style()}
131
148
  slot={local.slot}
132
149
  >
133
- <FormContext.Provider value={{ ...props, validationBehavior }}>
150
+ <FormContext.Provider value={formContextValue}>
134
151
  <FormValidationContext.Provider value={errors}>
135
152
  {renderProps.renderChildren()}
136
153
  </FormValidationContext.Provider>
package/src/ListBox.tsx CHANGED
@@ -596,8 +596,7 @@ export function ListBox<T>(props: ListBoxProps<T>): JSX.Element {
596
596
  const dropIndicatorTargetsEqual = (
597
597
  a: DropTarget | null | undefined,
598
598
  b: ItemDropTarget,
599
- ): boolean =>
600
- !!a && a.type === "item" && a.key === b.key && a.dropPosition === b.dropPosition;
599
+ ): boolean => !!a && a.type === "item" && a.key === b.key && a.dropPosition === b.dropPosition;
601
600
  // Faithful port of useDropIndicator's label logic: "Drop on X" for an on-item
602
601
  // target, else "Insert between X and Y" / "Insert before X" / "Insert after X"
603
602
  // resolved from the target's neighbours in the collection.
@@ -860,132 +859,132 @@ export function ListBox<T>(props: ListBoxProps<T>): JSX.Element {
860
859
  >
861
860
  <ListBoxStateContext.Provider value={state}>
862
861
  <CollectionRendererContext.Provider value={collectionRenderer()}>
863
- <DropIndicatorContext.Provider value={dropIndicatorContextValue}>
864
- <>
865
- <Show when={ariaProps.label}>
866
- <span {...cleanLabelProps()}>{ariaProps.label as JSX.Element}</span>
867
- </Show>
868
- <div
869
- {...mergeProps(
870
- domProps(),
871
- cleanListBoxProps(),
872
- cleanFocusProps(),
873
- (droppableCollection()?.collectionProps as Record<string, unknown> | undefined) ??
874
- {},
875
- )}
876
- ref={(el) => {
877
- setListRef(el);
878
- assignRef(local.ref, el);
879
- autocompleteCtx?.collectionRef(el);
880
- }}
881
- class={renderProps.class()}
882
- style={renderProps.style()}
883
- data-focused={state.isFocused() || undefined}
884
- data-focus-visible={isFocusVisible() || undefined}
885
- data-disabled={resolveDisabled() || undefined}
886
- data-empty={isEmpty() || undefined}
887
- data-layout={stateProps.layout}
888
- data-orientation={stateProps.orientation}
889
- data-drop-target={isRootDropTarget() || undefined}
890
- slot={local.slot}
891
- >
892
- <SharedElementTransition>
893
- {isEmpty() && local.renderEmptyState ? (
894
- <div role="option" style={{ display: "contents" }} data-empty-state>
895
- {local.renderEmptyState()}
896
- </div>
897
- ) : hasSections() ? (
898
- <For each={sectionedRenderEntries()}>
899
- {(entry) =>
900
- entry.type === "section" ? (
901
- <div role="presentation" data-section-wrapper>
902
- <Section class="solidaria-ListBox-section">
903
- {entry.section.title != null && (
904
- <Header class="solidaria-ListBox-sectionHeader">
905
- {entry.section.title}
906
- </Header>
907
- )}
908
- <Group class="solidaria-ListBox-sectionGroup">
909
- <div role="group" aria-label={entry.section["aria-label"]}>
910
- <For each={entry.items}>
911
- {(indexedItem) => (
912
- <>
913
- {collectionRenderer().renderDropIndicator?.(
914
- indexedItem.index,
915
- "before",
916
- )}
917
- {collectionRenderer().renderDropIndicator?.(
918
- indexedItem.index,
919
- "on",
920
- )}
921
- {local.children(indexedItem.item)}
922
- {collectionRenderer().renderDropIndicator?.(
923
- indexedItem.index,
924
- "after",
925
- )}
926
- </>
927
- )}
928
- </For>
929
- </div>
930
- </Group>
931
- </Section>
932
- </div>
933
- ) : (
934
- <>
935
- {collectionRenderer().renderDropIndicator?.(entry.item.index, "before")}
936
- {collectionRenderer().renderDropIndicator?.(entry.item.index, "on")}
937
- {local.children(entry.item.item)}
938
- {collectionRenderer().renderDropIndicator?.(entry.item.index, "after")}
939
- </>
940
- )
941
- }
942
- </For>
943
- ) : (
944
- <>
945
- {virtualRange()?.offsetTop ? (
946
- <div
947
- role="presentation"
948
- aria-hidden="true"
949
- style={virtualSpacerStyle(virtualRange()!.offsetTop)}
950
- data-virtualizer-spacer="top"
951
- />
952
- ) : null}
953
- <For each={visibleItems()}>
954
- {(item, index) => {
955
- const itemIndex = () => (virtualRange()?.start ?? 0) + index();
956
- const beforeIndicator = () =>
957
- collectionRenderer().renderDropIndicator?.(itemIndex(), "before");
958
- const onIndicator = () =>
959
- collectionRenderer().renderDropIndicator?.(itemIndex(), "on");
960
- const afterIndicator = () =>
961
- collectionRenderer().renderDropIndicator?.(itemIndex(), "after");
962
- return (
862
+ <DropIndicatorContext.Provider value={dropIndicatorContextValue}>
863
+ <>
864
+ <Show when={ariaProps.label}>
865
+ <span {...cleanLabelProps()}>{ariaProps.label as JSX.Element}</span>
866
+ </Show>
867
+ <div
868
+ {...mergeProps(
869
+ domProps(),
870
+ cleanListBoxProps(),
871
+ cleanFocusProps(),
872
+ (droppableCollection()?.collectionProps as Record<string, unknown> | undefined) ??
873
+ {},
874
+ )}
875
+ ref={(el) => {
876
+ setListRef(el);
877
+ assignRef(local.ref, el);
878
+ autocompleteCtx?.collectionRef(el);
879
+ }}
880
+ class={renderProps.class()}
881
+ style={renderProps.style()}
882
+ data-focused={state.isFocused() || undefined}
883
+ data-focus-visible={isFocusVisible() || undefined}
884
+ data-disabled={resolveDisabled() || undefined}
885
+ data-empty={isEmpty() || undefined}
886
+ data-layout={stateProps.layout}
887
+ data-orientation={stateProps.orientation}
888
+ data-drop-target={isRootDropTarget() || undefined}
889
+ slot={local.slot}
890
+ >
891
+ <SharedElementTransition>
892
+ {isEmpty() && local.renderEmptyState ? (
893
+ <div role="option" style={{ display: "contents" }} data-empty-state>
894
+ {local.renderEmptyState()}
895
+ </div>
896
+ ) : hasSections() ? (
897
+ <For each={sectionedRenderEntries()}>
898
+ {(entry) =>
899
+ entry.type === "section" ? (
900
+ <div role="presentation" data-section-wrapper>
901
+ <Section class="solidaria-ListBox-section">
902
+ {entry.section.title != null && (
903
+ <Header class="solidaria-ListBox-sectionHeader">
904
+ {entry.section.title}
905
+ </Header>
906
+ )}
907
+ <Group class="solidaria-ListBox-sectionGroup">
908
+ <div role="group" aria-label={entry.section["aria-label"]}>
909
+ <For each={entry.items}>
910
+ {(indexedItem) => (
911
+ <>
912
+ {collectionRenderer().renderDropIndicator?.(
913
+ indexedItem.index,
914
+ "before",
915
+ )}
916
+ {collectionRenderer().renderDropIndicator?.(
917
+ indexedItem.index,
918
+ "on",
919
+ )}
920
+ {local.children(indexedItem.item)}
921
+ {collectionRenderer().renderDropIndicator?.(
922
+ indexedItem.index,
923
+ "after",
924
+ )}
925
+ </>
926
+ )}
927
+ </For>
928
+ </div>
929
+ </Group>
930
+ </Section>
931
+ </div>
932
+ ) : (
963
933
  <>
964
- {beforeIndicator()}
965
- {onIndicator()}
966
- {local.children(item as T)}
967
- {afterIndicator()}
934
+ {collectionRenderer().renderDropIndicator?.(entry.item.index, "before")}
935
+ {collectionRenderer().renderDropIndicator?.(entry.item.index, "on")}
936
+ {local.children(entry.item.item)}
937
+ {collectionRenderer().renderDropIndicator?.(entry.item.index, "after")}
968
938
  </>
969
- );
970
- }}
939
+ )
940
+ }
971
941
  </For>
972
- {virtualRange()?.offsetBottom ? (
973
- <div
974
- role="presentation"
975
- aria-hidden="true"
976
- style={virtualSpacerStyle(virtualRange()!.offsetBottom)}
977
- data-virtualizer-spacer="bottom"
978
- />
979
- ) : null}
980
- </>
942
+ ) : (
943
+ <>
944
+ {virtualRange()?.offsetTop ? (
945
+ <div
946
+ role="presentation"
947
+ aria-hidden="true"
948
+ style={virtualSpacerStyle(virtualRange()!.offsetTop)}
949
+ data-virtualizer-spacer="top"
950
+ />
951
+ ) : null}
952
+ <For each={visibleItems()}>
953
+ {(item, index) => {
954
+ const itemIndex = () => (virtualRange()?.start ?? 0) + index();
955
+ const beforeIndicator = () =>
956
+ collectionRenderer().renderDropIndicator?.(itemIndex(), "before");
957
+ const onIndicator = () =>
958
+ collectionRenderer().renderDropIndicator?.(itemIndex(), "on");
959
+ const afterIndicator = () =>
960
+ collectionRenderer().renderDropIndicator?.(itemIndex(), "after");
961
+ return (
962
+ <>
963
+ {beforeIndicator()}
964
+ {onIndicator()}
965
+ {local.children(item as T)}
966
+ {afterIndicator()}
967
+ </>
968
+ );
969
+ }}
970
+ </For>
971
+ {virtualRange()?.offsetBottom ? (
972
+ <div
973
+ role="presentation"
974
+ aria-hidden="true"
975
+ style={virtualSpacerStyle(virtualRange()!.offsetBottom)}
976
+ data-virtualizer-spacer="bottom"
977
+ />
978
+ ) : null}
979
+ </>
980
+ )}
981
+ </SharedElementTransition>
982
+ {local.hasMore && local.onLoadMore && (
983
+ <ListBoxLoadMoreItem onLoadMore={local.onLoadMore} isLoading={local.isLoading} />
981
984
  )}
982
- </SharedElementTransition>
983
- {local.hasMore && local.onLoadMore && (
984
- <ListBoxLoadMoreItem onLoadMore={local.onLoadMore} isLoading={local.isLoading} />
985
- )}
986
- </div>
987
- </>
988
- </DropIndicatorContext.Provider>
985
+ </div>
986
+ </>
987
+ </DropIndicatorContext.Provider>
989
988
  </CollectionRendererContext.Provider>
990
989
  </ListBoxStateContext.Provider>
991
990
  </ListBoxContext.Provider>
@@ -272,9 +272,7 @@ export function SearchField(props: SearchFieldProps): JSX.Element {
272
272
  : undefined;
273
273
 
274
274
  const mergedProps = withFormValidationBehavior(
275
- autocompleteProps
276
- ? (mergeProps(baseProps, autocompleteProps) as SearchFieldProps)
277
- : baseProps,
275
+ autocompleteProps ? (mergeProps(baseProps, autocompleteProps) as SearchFieldProps) : baseProps,
278
276
  formContext,
279
277
  );
280
278
 
@@ -202,82 +202,79 @@ export function SharedElement(props: SharedElementProps): JSX.Element | null {
202
202
  // fresh-enter branch: with no committed div there is nothing to measure, so we
203
203
  // wait — exactly as React never runs the body before commit.
204
204
  createEffect(
205
- on(
206
- [() => local.isVisible !== false, element] as const,
207
- ([isVisible, el]) => {
208
- const name = local.name;
205
+ on([() => local.isVisible !== false, element] as const, ([isVisible, el]) => {
206
+ const name = local.name;
209
207
 
210
- if (frame != null) {
211
- cancelAnimationFrame(frame);
212
- frame = undefined;
213
- }
208
+ if (frame != null) {
209
+ cancelAnimationFrame(frame);
210
+ frame = undefined;
211
+ }
214
212
 
215
- if (isVisible && el) {
216
- const prevSnapshot = scope.snapshots[name];
213
+ if (isVisible && el) {
214
+ const prevSnapshot = scope.snapshots[name];
217
215
 
218
- if (prevSnapshot) {
219
- // FLIP: Element is transitioning from a previous instance.
220
- setLifecycle("visible");
221
- const animations = getAnimations(el);
222
-
223
- // Set properties to animate from.
224
- const values = prevSnapshot.style.map(([property, prevValue]) => {
225
- const value = el.style.getPropertyValue(property);
226
- if (property === "translate") {
227
- const prevRect = prevSnapshot.rect;
228
- const currentRect = el.getBoundingClientRect();
229
- const deltaX = prevRect.left - currentRect.left;
230
- const deltaY = prevRect.top - currentRect.top;
231
- el.style.setProperty("translate", `${deltaX}px ${deltaY}px`);
232
- } else {
233
- el.style.setProperty(property, prevValue);
234
- }
235
- return [property, value] as [string, string];
236
- });
237
-
238
- // Cancel any new animations triggered by these properties.
239
- for (const a of getAnimations(el)) {
240
- if (!animations.includes(a)) {
241
- a.cancel();
242
- }
216
+ if (prevSnapshot) {
217
+ // FLIP: Element is transitioning from a previous instance.
218
+ setLifecycle("visible");
219
+ const animations = getAnimations(el);
220
+
221
+ // Set properties to animate from.
222
+ const values = prevSnapshot.style.map(([property, prevValue]) => {
223
+ const value = el.style.getPropertyValue(property);
224
+ if (property === "translate") {
225
+ const prevRect = prevSnapshot.rect;
226
+ const currentRect = el.getBoundingClientRect();
227
+ const deltaX = prevRect.left - currentRect.left;
228
+ const deltaY = prevRect.top - currentRect.top;
229
+ el.style.setProperty("translate", `${deltaX}px ${deltaY}px`);
230
+ } else {
231
+ el.style.setProperty(property, prevValue);
243
232
  }
233
+ return [property, value] as [string, string];
234
+ });
244
235
 
245
- // Remove overrides after one frame to animate to the current values.
246
- frame = requestAnimationFrame(() => {
247
- frame = undefined;
248
- for (const [property, value] of values) {
249
- el.style.setProperty(property, value);
250
- }
251
- });
252
-
253
- delete scope.snapshots[name];
254
- } else {
255
- // No previous instance exists, apply the entering state.
256
- queueMicrotask(() => setLifecycle("entering"));
257
- frame = requestAnimationFrame(() => {
258
- frame = undefined;
259
- setLifecycle("visible");
260
- });
236
+ // Cancel any new animations triggered by these properties.
237
+ for (const a of getAnimations(el)) {
238
+ if (!animations.includes(a)) {
239
+ a.cancel();
240
+ }
261
241
  }
262
- } else if (!isVisible && el) {
263
- // Wait a microtask to check if a snapshot still exists (meaning no new
264
- // SharedElement consumed it), then enter exiting state.
265
- queueMicrotask(() => {
266
- if (scope.snapshots[name]) {
267
- delete scope.snapshots[name];
268
- setLifecycle("exiting");
269
- // Wait for animations to finish before hiding.
270
- Promise.all(getAnimations(el).map((a) => a.finished))
271
- .then(() => setLifecycle("hidden"))
272
- .catch(() => {});
273
- } else {
274
- // Snapshot was consumed by another instance, unmount immediately.
275
- setLifecycle("hidden");
242
+
243
+ // Remove overrides after one frame to animate to the current values.
244
+ frame = requestAnimationFrame(() => {
245
+ frame = undefined;
246
+ for (const [property, value] of values) {
247
+ el.style.setProperty(property, value);
276
248
  }
277
249
  });
250
+
251
+ delete scope.snapshots[name];
252
+ } else {
253
+ // No previous instance exists, apply the entering state.
254
+ queueMicrotask(() => setLifecycle("entering"));
255
+ frame = requestAnimationFrame(() => {
256
+ frame = undefined;
257
+ setLifecycle("visible");
258
+ });
278
259
  }
279
- },
280
- ),
260
+ } else if (!isVisible && el) {
261
+ // Wait a microtask to check if a snapshot still exists (meaning no new
262
+ // SharedElement consumed it), then enter exiting state.
263
+ queueMicrotask(() => {
264
+ if (scope.snapshots[name]) {
265
+ delete scope.snapshots[name];
266
+ setLifecycle("exiting");
267
+ // Wait for animations to finish before hiding.
268
+ Promise.all(getAnimations(el).map((a) => a.finished))
269
+ .then(() => setLifecycle("hidden"))
270
+ .catch(() => {});
271
+ } else {
272
+ // Snapshot was consumed by another instance, unmount immediately.
273
+ setLifecycle("hidden");
274
+ }
275
+ });
276
+ }
277
+ }),
281
278
  );
282
279
 
283
280
  // Cancel any pending FLIP frame on disposal. The snapshot store lives in the
package/src/TimeField.tsx CHANGED
@@ -32,11 +32,7 @@ import {
32
32
  type TimeSegment as TimeSegmentType,
33
33
  type TimeValue,
34
34
  } from "@proyecto-viviana/solid-stately";
35
- import {
36
- DateFieldContext,
37
- DateFieldStateContext,
38
- type DateFieldContextValue,
39
- } from "./DateField";
35
+ import { DateFieldContext, DateFieldStateContext, type DateFieldContextValue } from "./DateField";
40
36
  import {
41
37
  type ClassNameOrFunction,
42
38
  type StyleOrFunction,
@@ -220,8 +216,7 @@ function TimeFieldInner<T extends TimeValue = TimeValue>(
220
216
  // Ref to the hidden validation <input> (the DateInput's `<Input>` sibling).
221
217
  // Mirrors DateField: create it here, hand it to createTimeField below, and
222
218
  // attach it to the same input through the DateFieldContext getter.
223
- const [validationInputRef, setValidationInputRef] =
224
- createSignal<HTMLInputElement | null>(null);
219
+ const [validationInputRef, setValidationInputRef] = createSignal<HTMLInputElement | null>(null);
225
220
 
226
221
  const state = createTimeFieldState(stateProps);
227
222
 
@@ -307,9 +302,7 @@ function TimeFieldInner<T extends TimeValue = TimeValue>(
307
302
 
308
303
  return (
309
304
  <TimeFieldStateContext.Provider value={state as unknown as TimeFieldState<TimeValue>}>
310
- <DateFieldStateContext.Provider
311
- value={state as unknown as DateFieldContextValue["state"]}
312
- >
305
+ <DateFieldStateContext.Provider value={state as unknown as DateFieldContextValue["state"]}>
313
306
  <DateFieldContext.Provider value={dateFieldContextValue}>
314
307
  <TimeFieldContext.Provider
315
308
  value={{