@proyecto-viviana/solidaria-components 0.4.1 → 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.
Files changed (82) hide show
  1. package/dist/Breadcrumbs.d.ts.map +1 -1
  2. package/dist/Breadcrumbs.js +3 -2
  3. package/dist/Breadcrumbs.js.map +1 -1
  4. package/dist/Breadcrumbs.jsx +3 -3
  5. package/dist/Breadcrumbs.jsx.map +1 -1
  6. package/dist/DateField.d.ts.map +1 -1
  7. package/dist/DateField2.js.map +1 -1
  8. package/dist/DateField2.jsx.map +1 -1
  9. package/dist/DatePicker2.js.map +1 -1
  10. package/dist/DatePicker2.jsx.map +1 -1
  11. package/dist/ElementTag.d.ts +29 -0
  12. package/dist/ElementTag.d.ts.map +1 -0
  13. package/dist/ElementTag.js +220 -0
  14. package/dist/ElementTag.js.map +1 -0
  15. package/dist/ElementTag.jsx +67 -0
  16. package/dist/ElementTag.jsx.map +1 -0
  17. package/dist/Form.d.ts.map +1 -1
  18. package/dist/Form.js +2 -6
  19. package/dist/Form.js.map +1 -1
  20. package/dist/Form.jsx +2 -4
  21. package/dist/Form.jsx.map +1 -1
  22. package/dist/Landmark.js +4 -3
  23. package/dist/Landmark.js.map +1 -1
  24. package/dist/Landmark.jsx +3 -3
  25. package/dist/Landmark.jsx.map +1 -1
  26. package/dist/Link.js +3 -2
  27. package/dist/Link.js.map +1 -1
  28. package/dist/Link.jsx +3 -3
  29. package/dist/Link.jsx.map +1 -1
  30. package/dist/ListBox.d.ts.map +1 -1
  31. package/dist/ListBox.js.map +1 -1
  32. package/dist/ListBox.jsx +51 -51
  33. package/dist/ListBox.jsx.map +1 -1
  34. package/dist/SearchField.d.ts.map +1 -1
  35. package/dist/SearchField.js.map +1 -1
  36. package/dist/SearchField.jsx.map +1 -1
  37. package/dist/Select.d.ts.map +1 -1
  38. package/dist/Select.js +2 -2
  39. package/dist/Select.js.map +1 -1
  40. package/dist/Select.jsx +2 -2
  41. package/dist/Select.jsx.map +1 -1
  42. package/dist/Separator.js +3 -2
  43. package/dist/Separator.js.map +1 -1
  44. package/dist/Separator.jsx +2 -2
  45. package/dist/Separator.jsx.map +1 -1
  46. package/dist/SharedElementTransition.d.ts.map +1 -1
  47. package/dist/SharedElementTransition.js.map +1 -1
  48. package/dist/SharedElementTransition.jsx.map +1 -1
  49. package/dist/Text.d.ts.map +1 -1
  50. package/dist/Text.js +9 -8
  51. package/dist/Text.js.map +1 -1
  52. package/dist/Text.jsx +3 -3
  53. package/dist/Text.jsx.map +1 -1
  54. package/dist/TimeField.d.ts.map +1 -1
  55. package/dist/TimeField.js.map +1 -1
  56. package/dist/TimeField.jsx.map +1 -1
  57. package/dist/VisuallyHidden.d.ts.map +1 -1
  58. package/dist/VisuallyHidden.js +3 -11
  59. package/dist/VisuallyHidden.js.map +1 -1
  60. package/dist/VisuallyHidden.jsx +3 -4
  61. package/dist/VisuallyHidden.jsx.map +1 -1
  62. package/dist/index.d.ts +1 -0
  63. package/dist/index.d.ts.map +1 -1
  64. package/dist/index.js +2 -1
  65. package/dist/index.jsx +2 -1
  66. package/package.json +5 -3
  67. package/src/Breadcrumbs.tsx +5 -7
  68. package/src/DateField.tsx +5 -7
  69. package/src/DatePicker.tsx +25 -25
  70. package/src/ElementTag.tsx +116 -0
  71. package/src/Form.tsx +18 -1
  72. package/src/Landmark.tsx +4 -4
  73. package/src/Link.tsx +4 -4
  74. package/src/ListBox.tsx +123 -124
  75. package/src/SearchField.tsx +1 -3
  76. package/src/Select.tsx +13 -2
  77. package/src/Separator.tsx +3 -3
  78. package/src/SharedElementTransition.tsx +64 -67
  79. package/src/Text.tsx +5 -4
  80. package/src/TimeField.tsx +3 -10
  81. package/src/VisuallyHidden.tsx +8 -12
  82. package/src/index.ts +2 -0
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
 
package/src/Select.tsx CHANGED
@@ -624,12 +624,23 @@ export function Select<T>(props: SelectProps<T>): JSX.Element {
624
624
  const fallbackKey =
625
625
  itemRecord != null ? (toKey(itemRecord.key) ?? toKey(itemRecord.id)) : undefined;
626
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]".
627
633
  const fallbackTextValue =
628
634
  itemRecord != null
629
- ? (toTextValue(itemRecord.textValue) ?? toTextValue(itemRecord.label))
635
+ ? (toTextValue(itemRecord.textValue) ??
636
+ toTextValue(itemRecord.label) ??
637
+ toTextValue(itemRecord.name) ??
638
+ toTextValue(itemRecord.title))
630
639
  : undefined;
631
640
  const textValue =
632
- stateProps.getTextValue?.(item) ?? fallbackTextValue ?? String(item);
641
+ stateProps.getTextValue?.(item) ??
642
+ fallbackTextValue ??
643
+ (itemRecord != null ? String(key) : String(item));
633
644
  const selectedKeys = state.selectedKeys();
634
645
  const isSelected =
635
646
  state.selectionMode() === "multiple"
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)}
@@ -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/Text.tsx CHANGED
@@ -1,5 +1,5 @@
1
1
  import { type JSX, createContext, splitProps } from "solid-js";
2
- import { Dynamic } from "solid-js/web";
2
+ import { ElementTag } from "./ElementTag";
3
3
  import { type ContextValue, type SlotProps, useContextProps, filterDOMProps } from "./utils";
4
4
 
5
5
  export interface TextProps extends JSX.HTMLAttributes<HTMLElement>, SlotProps {
@@ -28,12 +28,13 @@ export function Text(props: TextProps): JSX.Element {
28
28
  // are rendered explicitly; everything else (notably the slotted `id`) is spread.
29
29
  const [local, domProps] = splitProps(merged, ["elementType", "class", "children", "slot", "ref"]);
30
30
  return (
31
- <Dynamic
32
- component={local.elementType ?? "span"}
31
+ <ElementTag
33
32
  class={local.class ?? "solidaria-Text"}
34
33
  {...filterDOMProps(domProps, { global: true })}
34
+ // last, so a stray `tag` in the spread can never redirect the element
35
+ tag={local.elementType ?? "span"}
35
36
  >
36
37
  {local.children}
37
- </Dynamic>
38
+ </ElementTag>
38
39
  );
39
40
  }
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={{
@@ -6,7 +6,7 @@
6
6
  */
7
7
 
8
8
  import { type JSX, type ParentProps, splitProps } from "solid-js";
9
- import { Dynamic } from "solid-js/web";
9
+ import { ElementTag } from "./ElementTag";
10
10
  import { createVisuallyHidden, mergeProps } from "@proyecto-viviana/solidaria";
11
11
 
12
12
  export interface VisuallyHiddenProps extends ParentProps, JSX.HTMLAttributes<HTMLElement> {
@@ -36,19 +36,15 @@ export function VisuallyHidden(props: VisuallyHiddenProps): JSX.Element {
36
36
  visuallyHiddenProps() as unknown as Record<string, unknown>,
37
37
  );
38
38
 
39
- // elementType is read once (structural, not reactive). The default `span` is
40
- // rendered as a static element rather than via `<Dynamic>`: a reactive
41
- // `<Dynamic>` desyncs Solid's hydration markers, leaving the registry dirty so
42
- // a later sibling re-render throws "template is not a function" in prod (and a
43
- // hard hydration crash under solid-refresh in dev). `<Dynamic>` is reserved for
44
- // an explicit custom elementType.
39
+ // elementType is read once (structural, not reactive). `ElementTag` renders it
40
+ // as a statically compiled element; `<Dynamic component={string}>` desyncs
41
+ // Solid's hydration markers, leaving the registry dirty so a later sibling
42
+ // re-render throws "template is not a function" in prod (and a hard hydration
43
+ // crash under solid-refresh in dev). See ElementTag.tsx for the mechanism.
45
44
  const tag = local.elementType ?? "span";
46
- if (tag === "span") {
47
- return <span {...mergedProps()}>{local.children}</span>;
48
- }
49
45
  return (
50
- <Dynamic component={tag} {...mergedProps()}>
46
+ <ElementTag tag={tag} {...mergedProps()}>
51
47
  {local.children}
52
- </Dynamic>
48
+ </ElementTag>
53
49
  );
54
50
  }
package/src/index.ts CHANGED
@@ -35,6 +35,8 @@ export {
35
35
  dataAttr,
36
36
  } from "./utils";
37
37
 
38
+ export { ElementTag, type ElementTagProps } from "./ElementTag";
39
+
38
40
  export { VisuallyHidden, type VisuallyHiddenProps } from "./VisuallyHidden";
39
41
 
40
42
  export { Button, ButtonContext, type ButtonProps, type ButtonRenderProps } from "./Button";