@proyecto-viviana/solidaria-components 0.3.0 → 0.3.2
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/dist/Button.d.ts.map +1 -1
- package/dist/ComboBox.d.ts +4 -1
- package/dist/ComboBox.d.ts.map +1 -1
- package/dist/DatePicker.d.ts.map +1 -1
- package/dist/Menu.d.ts.map +1 -1
- package/dist/Modal.d.ts.map +1 -1
- package/dist/NumberField.d.ts.map +1 -1
- package/dist/Popover.d.ts.map +1 -1
- package/dist/TagGroup.d.ts.map +1 -1
- package/dist/TextField.d.ts +1 -0
- package/dist/TextField.d.ts.map +1 -1
- package/dist/Toast.d.ts.map +1 -1
- package/dist/Tooltip.d.ts.map +1 -1
- package/dist/index.js +264 -37
- package/dist/index.js.map +1 -1
- package/dist/index.jsx +264 -37
- package/dist/index.jsx.map +1 -1
- package/dist/utils.d.ts.map +1 -1
- package/package.json +3 -3
- package/src/Button.tsx +69 -22
- package/src/ComboBox.tsx +77 -9
- package/src/DatePicker.tsx +60 -5
- package/src/Menu.tsx +49 -6
- package/src/Modal.tsx +8 -1
- package/src/NumberField.tsx +22 -2
- package/src/Popover.tsx +19 -2
- package/src/TagGroup.tsx +1 -0
- package/src/TextField.tsx +32 -7
- package/src/Toast.tsx +7 -1
- package/src/Tooltip.tsx +52 -7
- package/src/utils.tsx +9 -5
package/src/TextField.tsx
CHANGED
|
@@ -79,6 +79,7 @@ export interface TextFieldContextValue {
|
|
|
79
79
|
isInvalid?: boolean;
|
|
80
80
|
slots?: Record<string, TextFieldProps>;
|
|
81
81
|
inputId?: string;
|
|
82
|
+
setInputId?: (id: string | undefined) => void;
|
|
82
83
|
}
|
|
83
84
|
|
|
84
85
|
export const TextFieldContext = createContext<TextFieldContextValue | null>(null);
|
|
@@ -204,6 +205,14 @@ export function Input(props: InputProps): JSX.Element {
|
|
|
204
205
|
const context = useContext(TextFieldContext);
|
|
205
206
|
let inputElement: HTMLInputElement | undefined;
|
|
206
207
|
|
|
208
|
+
createEffect(() => {
|
|
209
|
+
context?.setInputId?.(props.id);
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
onCleanup(() => {
|
|
213
|
+
context?.setInputId?.(undefined);
|
|
214
|
+
});
|
|
215
|
+
|
|
207
216
|
// Merge context inputProps with local props (local props take precedence)
|
|
208
217
|
const mergedProps = () => {
|
|
209
218
|
if (context) {
|
|
@@ -291,6 +300,14 @@ export function TextArea(props: TextAreaProps): JSX.Element {
|
|
|
291
300
|
const context = useContext(TextFieldContext);
|
|
292
301
|
let textAreaElement: HTMLTextAreaElement | undefined;
|
|
293
302
|
|
|
303
|
+
createEffect(() => {
|
|
304
|
+
context?.setInputId?.(props.id);
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
onCleanup(() => {
|
|
308
|
+
context?.setInputId?.(undefined);
|
|
309
|
+
});
|
|
310
|
+
|
|
294
311
|
// Merge context inputProps with local props (local props take precedence)
|
|
295
312
|
// Note: TextArea uses inputProps from context since it's an input variant
|
|
296
313
|
const mergedProps = () => {
|
|
@@ -396,6 +413,7 @@ export function TextField(props: TextFieldProps): JSX.Element {
|
|
|
396
413
|
errorMessageProps: _errorMessageProps,
|
|
397
414
|
isInvalid: _isInvalid,
|
|
398
415
|
slots: _slots,
|
|
416
|
+
setInputId: _setInputId,
|
|
399
417
|
...rest
|
|
400
418
|
} = contextProps;
|
|
401
419
|
return rest as TextFieldProps;
|
|
@@ -449,6 +467,7 @@ export function TextField(props: TextFieldProps): JSX.Element {
|
|
|
449
467
|
return ariaProps.isDisabled;
|
|
450
468
|
},
|
|
451
469
|
});
|
|
470
|
+
const [inputIdOverride, setInputIdOverride] = createSignal<string | undefined>();
|
|
452
471
|
|
|
453
472
|
const renderValues = createMemo<TextFieldRenderProps>(() => ({
|
|
454
473
|
isDisabled: ariaProps.isDisabled || false,
|
|
@@ -551,7 +570,10 @@ export function TextField(props: TextFieldProps): JSX.Element {
|
|
|
551
570
|
// onMount effect would flip undefined->id post-mount and re-execute the Label
|
|
552
571
|
// template — crashing hydration if the re-run lands mid-hydration (dev).
|
|
553
572
|
get inputId() {
|
|
554
|
-
return (textFieldAria.inputProps as { id?: string }).id;
|
|
573
|
+
return inputIdOverride() ?? (textFieldAria.inputProps as { id?: string }).id;
|
|
574
|
+
},
|
|
575
|
+
setInputId(id: string | undefined) {
|
|
576
|
+
setInputIdOverride(id);
|
|
555
577
|
},
|
|
556
578
|
};
|
|
557
579
|
// Resolve the render-prop children ONCE (untracked). Re-invoking it on a
|
|
@@ -559,10 +581,11 @@ export function TextField(props: TextFieldProps): JSX.Element {
|
|
|
559
581
|
// throws a Hydration Mismatch (worst in dev, where slow unbundled modules widen
|
|
560
582
|
// the hydration window). The children carry their own fine-grained reactivity
|
|
561
583
|
// (render-value getters + <Show>s), so they update without being re-created.
|
|
562
|
-
const
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
584
|
+
const FieldChildren = () =>
|
|
585
|
+
untrack(() => {
|
|
586
|
+
const children = local.children;
|
|
587
|
+
return typeof children === "function" ? children(childRenderValues) : children;
|
|
588
|
+
});
|
|
566
589
|
const rootProps = () =>
|
|
567
590
|
({
|
|
568
591
|
...domProps(),
|
|
@@ -581,7 +604,7 @@ export function TextField(props: TextFieldProps): JSX.Element {
|
|
|
581
604
|
const customRootProps = () =>
|
|
582
605
|
({
|
|
583
606
|
...rootProps(),
|
|
584
|
-
children:
|
|
607
|
+
children: <FieldChildren />,
|
|
585
608
|
}) as JSX.HTMLAttributes<HTMLDivElement>;
|
|
586
609
|
|
|
587
610
|
return (
|
|
@@ -590,7 +613,9 @@ export function TextField(props: TextFieldProps): JSX.Element {
|
|
|
590
613
|
{local.render ? (
|
|
591
614
|
local.render(customRootProps(), renderValues())
|
|
592
615
|
) : (
|
|
593
|
-
<div {...rootProps()}>
|
|
616
|
+
<div {...rootProps()}>
|
|
617
|
+
<FieldChildren />
|
|
618
|
+
</div>
|
|
594
619
|
)}
|
|
595
620
|
</TextFieldContext.Provider>
|
|
596
621
|
</FieldErrorContext.Provider>
|
package/src/Toast.tsx
CHANGED
|
@@ -252,7 +252,13 @@ export function ToastRegion(props: ToastRegionProps): JSX.Element {
|
|
|
252
252
|
|
|
253
253
|
const renderProps = useRenderProps(
|
|
254
254
|
{
|
|
255
|
-
|
|
255
|
+
// Lazy: the region content is gated behind `<Show when={isHydrated() && …}>`
|
|
256
|
+
// (Portal) below, so reading children must not instantiate templates during
|
|
257
|
+
// the component body (would walk getNextElement before the gate the server
|
|
258
|
+
// kept closed → hydration mismatch). See Popover for the full rationale.
|
|
259
|
+
get children() {
|
|
260
|
+
return props.children;
|
|
261
|
+
},
|
|
256
262
|
class: local.class,
|
|
257
263
|
style: local.style,
|
|
258
264
|
defaultClassName: "solidaria-ToastRegion",
|
package/src/Tooltip.tsx
CHANGED
|
@@ -226,6 +226,40 @@ const TriggerWrapper: ParentComponent<{
|
|
|
226
226
|
}> = (props) => {
|
|
227
227
|
const child = () => props.children as JSX.Element;
|
|
228
228
|
const [triggerElement, setTriggerElement] = createSignal<HTMLElement | null>(null);
|
|
229
|
+
const [wrapperElement, setWrapperElement] = createSignal<HTMLSpanElement | null>(null);
|
|
230
|
+
const getWrapperEventProps = () => {
|
|
231
|
+
const triggerProps = props.triggerProps as Record<string, unknown>;
|
|
232
|
+
const wrapperProps: Record<string, unknown> = {};
|
|
233
|
+
const eventPropNames = [
|
|
234
|
+
"onFocus",
|
|
235
|
+
"onBlur",
|
|
236
|
+
"onPointerEnter",
|
|
237
|
+
"onPointerLeave",
|
|
238
|
+
"onPointerOver",
|
|
239
|
+
"onPointerOut",
|
|
240
|
+
"onMouseEnter",
|
|
241
|
+
"onMouseLeave",
|
|
242
|
+
"onTouchStart",
|
|
243
|
+
"onPointerDown",
|
|
244
|
+
"onKeyDown",
|
|
245
|
+
];
|
|
246
|
+
|
|
247
|
+
for (const propName of eventPropNames) {
|
|
248
|
+
const handler = triggerProps[propName];
|
|
249
|
+
if (typeof handler === "function") {
|
|
250
|
+
wrapperProps[propName] = handler;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
if (!wrapperProps.onPointerEnter && typeof triggerProps.onMouseEnter === "function") {
|
|
255
|
+
wrapperProps.onPointerEnter = triggerProps.onMouseEnter;
|
|
256
|
+
}
|
|
257
|
+
if (!wrapperProps.onPointerLeave && typeof triggerProps.onMouseLeave === "function") {
|
|
258
|
+
wrapperProps.onPointerLeave = triggerProps.onMouseLeave;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
return wrapperProps as JSX.HTMLAttributes<HTMLSpanElement>;
|
|
262
|
+
};
|
|
229
263
|
|
|
230
264
|
createEffect(() => {
|
|
231
265
|
const element = triggerElement();
|
|
@@ -241,7 +275,9 @@ const TriggerWrapper: ParentComponent<{
|
|
|
241
275
|
element.removeAttribute("aria-describedby");
|
|
242
276
|
}
|
|
243
277
|
|
|
244
|
-
const
|
|
278
|
+
const wrapper = wrapperElement();
|
|
279
|
+
const targets = Array.from(new Set([element, wrapper].filter(Boolean))) as HTMLElement[];
|
|
280
|
+
const listeners: Array<[HTMLElement, string, EventListener]> = [];
|
|
245
281
|
const eventProps = [
|
|
246
282
|
["onFocus", "focus"],
|
|
247
283
|
["onBlur", "blur"],
|
|
@@ -257,17 +293,24 @@ const TriggerWrapper: ParentComponent<{
|
|
|
257
293
|
] as const;
|
|
258
294
|
|
|
259
295
|
for (const [propName, eventName] of eventProps) {
|
|
260
|
-
|
|
296
|
+
let handler = triggerProps[propName];
|
|
297
|
+
if (!handler && propName === "onPointerEnter") {
|
|
298
|
+
handler = triggerProps.onMouseEnter;
|
|
299
|
+
} else if (!handler && propName === "onPointerLeave") {
|
|
300
|
+
handler = triggerProps.onMouseLeave;
|
|
301
|
+
}
|
|
261
302
|
if (typeof handler === "function") {
|
|
262
303
|
const listener = handler as EventListener;
|
|
263
|
-
|
|
264
|
-
|
|
304
|
+
for (const target of targets) {
|
|
305
|
+
target.addEventListener(eventName, listener);
|
|
306
|
+
listeners.push([target, eventName, listener]);
|
|
307
|
+
}
|
|
265
308
|
}
|
|
266
309
|
}
|
|
267
310
|
|
|
268
311
|
onCleanup(() => {
|
|
269
|
-
for (const [eventName, listener] of listeners) {
|
|
270
|
-
|
|
312
|
+
for (const [target, eventName, listener] of listeners) {
|
|
313
|
+
target.removeEventListener(eventName, listener);
|
|
271
314
|
}
|
|
272
315
|
if (describedBy && element.getAttribute("aria-describedby") === describedBy) {
|
|
273
316
|
element.removeAttribute("aria-describedby");
|
|
@@ -279,6 +322,8 @@ const TriggerWrapper: ParentComponent<{
|
|
|
279
322
|
// However, display:contents makes getBoundingClientRect return zeros,
|
|
280
323
|
// so we pass a ref callback that finds the first actual element child.
|
|
281
324
|
const handleRef = (span: HTMLSpanElement) => {
|
|
325
|
+
setWrapperElement(span);
|
|
326
|
+
|
|
282
327
|
const findElementChild = (el: Element): HTMLElement | null => {
|
|
283
328
|
for (const child of el.children) {
|
|
284
329
|
if (child instanceof HTMLElement) {
|
|
@@ -329,7 +374,7 @@ const TriggerWrapper: ParentComponent<{
|
|
|
329
374
|
};
|
|
330
375
|
|
|
331
376
|
return (
|
|
332
|
-
<span ref={handleRef} style={{ display: "contents" }}>
|
|
377
|
+
<span {...getWrapperEventProps()} ref={handleRef} style={{ display: "contents" }}>
|
|
333
378
|
{child()}
|
|
334
379
|
</span>
|
|
335
380
|
);
|
package/src/utils.tsx
CHANGED
|
@@ -334,13 +334,17 @@ export function useIsHydrated(): Accessor<boolean> {
|
|
|
334
334
|
return () => false;
|
|
335
335
|
}
|
|
336
336
|
|
|
337
|
-
// On client, start false
|
|
338
|
-
//
|
|
337
|
+
// On client, start false (so the first render matches the server, which
|
|
338
|
+
// emitted nothing for hydrated-gated content) and flip to true after mount.
|
|
339
339
|
const [isHydrated, setIsHydrated] = createSignal(false);
|
|
340
340
|
|
|
341
|
-
//
|
|
342
|
-
//
|
|
343
|
-
|
|
341
|
+
// onMount runs in the effect phase — *after* the synchronous hydration pass
|
|
342
|
+
// has finished walking the server DOM — so flipping here renders the gated
|
|
343
|
+
// content as a fresh client-side update (Portal: no getNextElement walk, no
|
|
344
|
+
// mismatch), yet fires synchronously under `render()` (unit tests / pure CSR)
|
|
345
|
+
// where requestAnimationFrame would never run. This mirrors the component
|
|
346
|
+
// gate above and is strictly earlier than a rAF tick.
|
|
347
|
+
onMount(() => {
|
|
344
348
|
setIsHydrated(true);
|
|
345
349
|
});
|
|
346
350
|
|