Align Soma public wrappers with providers

active-uix
dev 5 months ago
parent b74e363e01
commit 7a85ea59c2

@ -25,11 +25,14 @@ Arranque rapido para mañana:
- Sema queda protegido contra listeners globales nativos y `console.*`. - Sema queda protegido contra listeners globales nativos y `console.*`.
- `ScrollLock` de Soma inyecta `ActiveDom` hacia `BodyScrollLock`. - `ScrollLock` de Soma inyecta `ActiveDom` hacia `BodyScrollLock`.
- Lo primero para continuar: - Lo primero para continuar:
1. Hacer un barrido de wrappers publicos `.svelte` frente a providers para 1. Cerrado el primer barrido de wrappers publicos `.svelte` frente a
detectar props no reenviadas, bindables incompletos, snippets mal providers. Si se retoma esta linea, continuar con validacion navegador
tipados o defaults divergentes. de rutas `/uix/components/*`, no repetir el barrido estatico salvo que
cambien wrappers.
2. Decidir si el directorio vacio `src/uix/soma/components/avatar/` se borra 2. Decidir si el directorio vacio `src/uix/soma/components/avatar/` se borra
o se deja como placeholder documentado. Hoy Avatar es `eidos` scope. o se deja como placeholder documentado. Hoy Avatar es `eidos` scope.
Decidido: no hay accion versionable; solo existe un directorio local
vacio (`components/`) y Git no lo registra. No crear placeholder Soma.
3. Si se sigue con Sium: abordar P0 de integridad (`isPlainObjectRecord`, 3. Si se sigue con Sium: abordar P0 de integridad (`isPlainObjectRecord`,
`email`, `regex` con flags mutables, doc de `ctx.rootValue`). `email`, `regex` con flags mutables, doc de `ctx.rootValue`).
4. Mantener fuera de commits los logs sueltos (`.codex-vite-*`, 4. Mantener fuera de commits los logs sueltos (`.codex-vite-*`,
@ -37,6 +40,27 @@ Arranque rapido para mañana:
Actualizacion 2026-05-16: Actualizacion 2026-05-16:
- Barrido de wrappers publicos Soma frente a providers cerrado:
- `DateField.Provider` y `TimeField.Provider` vuelven a materializar su
root publico por defecto (`<div {...mergedProps}>`) y aceptan `ref` /
`restProps`, coherente con su morfo (`kind: public`,
`defaultElement: 'div'`).
- `DateField` ya respeta la prop `dir`; la direccion efectiva se pasa al
provider y a la navegacion de segmentos en vez de ignorarse y leer solo
`soma.prefs`.
- `DatePicker`, `DateRangePicker`, `TimePicker` y `TimeRangePicker`
mantienen root virtual: sus props root ya no anuncian `PrimitiveDivAttrs`
ni `child` con `props: {}`.
- `ColorPicker.Area` ya aplica de verdad los overrides locales
`xChannel` / `yChannel`; antes los tipos los exponian pero el wrapper los
descartaba.
- Guardias nuevas en `src/uix/contracts.test.ts` para roots publicos
Date/Time Field y roots virtuales picker.
- Validado: `npx vitest run src/uix/contracts.test.ts` + tests focales de
date/time pickers/fields -> 52 tests OK; `color-picker-provider` -> 5
tests OK; `npm run check` -> 0 errores / 0 warnings; `npm run test` ->
233 archivos, 2251 tests OK.
- Soma queda en estado funcionalmente cubierto a nivel provider/runtime: - Soma queda en estado funcionalmente cubierto a nivel provider/runtime:
`npm run check` -> 0 errores / 0 warnings, `npm run test` -> 233 archivos, `npm run check` -> 0 errores / 0 warnings, `npm run test` -> 233 archivos,
2242 tests OK, y la guardia de `*-provider.svelte.ts` sin test directo 2242 tests OK, y la guardia de `*-provider.svelte.ts` sin test directo

@ -347,6 +347,46 @@ describe('UIX layer contracts', () => {
expect(violations).toEqual([]); expect(violations).toEqual([]);
}); });
it('guards Date/Time field roots from swallowing provider DOM attrs', () => {
for (const dir of ['date-field', 'time-field']) {
const source = readFileSync(
join(HERE, 'soma', 'components', dir, 'components', `${dir}.svelte`),
'utf8'
);
expect(source, dir).toContain('ref = $bindable(null)');
expect(source, dir).toContain('...restProps');
expect(source, dir).toContain('const mergedProps');
expect(source, dir).toContain('<div {...mergedProps}>');
expect(source, dir).not.toContain('child({ props: {} })');
}
});
it('guards virtual picker roots from advertising DOM attrs', () => {
const roots = [
['date-picker', 'DatePickerProviderProps'],
['date-range-picker', 'DateRangePickerProviderProps'],
['time-picker', 'TimePickerProviderProps'],
['time-range-picker', 'TimeRangePickerProviderProps']
] as const;
for (const [dir, typeName] of roots) {
const types = readFileSync(join(HERE, 'soma', 'components', dir, 'types.ts'), 'utf8');
const wrapper = readFileSync(
join(HERE, 'soma', 'components', dir, 'components', `${dir}.svelte`),
'utf8'
);
const rootType =
new RegExp(`export type ${typeName}\\s*=\\s*\\{[\\s\\S]*?\\};`).exec(types)?.[0] ?? '';
expect(rootType, dir).toContain('children?: Snippet;');
expect(rootType, dir).not.toContain('WithChild');
expect(rootType, dir).not.toContain('PrimitiveDivAttributes');
expect(wrapper, dir).toContain('{@render children?.()}');
expect(wrapper, dir).not.toContain('child({ props: {} })');
}
});
it('guards Soma public barrels from provider implementation filenames', () => { it('guards Soma public barrels from provider implementation filenames', () => {
const violations = collectPublicSomaComponentDirs() const violations = collectPublicSomaComponentDirs()
.map((dir) => join(HERE, 'soma', 'components', dir, 'exports.ts')) .map((dir) => join(HERE, 'soma', 'components', dir, 'exports.ts'))

@ -196,7 +196,9 @@ describe('ColorPickerProvider', () => {
vi.spyOn(ColorPickerProvider.ctx, 'get').mockReturnValue(provider); vi.spyOn(ColorPickerProvider.ctx, 'get').mockReturnValue(provider);
const area = ColorPickerAreaProvider.create({ const area = ColorPickerAreaProvider.create({
id: state('color-picker-area'), id: state('color-picker-area'),
ref: state<HTMLElement | null>(document.createElement('div')) ref: state<HTMLElement | null>(document.createElement('div')),
xChannel: state<ColorChannel | undefined>(undefined),
yChannel: state<ColorChannel | undefined>(undefined)
}); });
vi.spyOn(ColorPickerAreaProvider.ctx, 'get').mockReturnValue(area); vi.spyOn(ColorPickerAreaProvider.ctx, 'get').mockReturnValue(area);
const thumb = ColorPickerAreaThumbProvider.create({ const thumb = ColorPickerAreaThumbProvider.create({
@ -227,6 +229,37 @@ describe('ColorPickerProvider', () => {
dom.dispose(); dom.dispose();
}); });
it('lets each area override the root channel mapping', () => {
const { dom } = installSomaHarness();
const opts = createColorPickerOpts();
opts.value.current = colorValueFromRgb({ r: 10, g: 20, b: 30, a: 1 });
const { result, cleanup } = withEffectRoot(() => {
const provider = ColorPickerProvider.create(opts);
vi.spyOn(ColorPickerProvider.ctx, 'get').mockReturnValue(provider);
const area = ColorPickerAreaProvider.create({
id: state('color-picker-area'),
ref: state<HTMLElement | null>(document.createElement('div')),
xChannel: state<ColorChannel | undefined>('red'),
yChannel: state<ColorChannel | undefined>('green')
});
return { area };
});
expect(result.area.props).toMatchObject({
'data-x-channel': 'red',
'data-y-channel': 'green'
});
result.area.handlePointerMove(128, 128, rect(0, 0, 255, 255));
expect(Math.round(opts.value.current?.rgb.r ?? 0)).toBe(128);
expect(Math.round(opts.value.current?.rgb.g ?? 0)).toBe(127);
cleanup();
dom.dispose();
});
it('drives a channel slider by pointer and keyboard and commits on pointerup', () => { it('drives a channel slider by pointer and keyboard and commits on pointerup', () => {
const { dom } = installSomaHarness(); const { dom } = installSomaHarness();
const opts = createColorPickerOpts(); const opts = createColorPickerOpts();

@ -528,7 +528,13 @@ import { KEYS } from '../../keyboard';
// ── Area ──────────────────────────────────────────────────────────────────── // ── Area ────────────────────────────────────────────────────────────────────
export interface ColorPickerAreaOpts extends WithRefOpts {} export interface ColorPickerAreaOpts
extends
WithRefOpts,
ActiveProps<{
xChannel: ColorChannel | undefined;
yChannel: ColorChannel | undefined;
}> {}
export class ColorPickerAreaProvider { export class ColorPickerAreaProvider {
readonly opts: ColorPickerAreaOpts; readonly opts: ColorPickerAreaOpts;
@ -562,8 +568,12 @@ export class ColorPickerAreaProvider {
}); });
} }
readonly xChannel: ColorChannel = $derived.by(() => this.provider.opts.areaChannels.current.x); readonly xChannel: ColorChannel = $derived.by(
readonly yChannel: ColorChannel = $derived.by(() => this.provider.opts.areaChannels.current.y); () => this.opts.xChannel.current ?? this.provider.opts.areaChannels.current.x
);
readonly yChannel: ColorChannel = $derived.by(
() => this.opts.yChannel.current ?? this.provider.opts.areaChannels.current.y
);
/** /**
* The `(xChannel, yChannel)` normalized progress (0..1) for the current * The `(xChannel, yChannel)` normalized progress (0..1) for the current

@ -13,22 +13,21 @@
let { let {
ref = $bindable(null), ref = $bindable(null),
id = createId(uid, 'color-picker-area'), id = createId(uid, 'color-picker-area'),
xChannel: _x, xChannel,
yChannel: _y, yChannel,
children, children,
child, child,
...restProps ...restProps
}: ColorPickerAreaProps = $props(); }: ColorPickerAreaProps = $props();
// Per-instance axis override is not implemented yet — the provider reads
// from root `areaChannels`. Destructured (as `_x` / `_y`) to keep them out
// of `restProps` so they don't leak onto the DOM; kept for forward compat.
const area = ColorPickerAreaProvider.create({ const area = ColorPickerAreaProvider.create({
id: readableActive(() => id), id: readableActive(() => id),
ref: writableActive( ref: writableActive(
() => ref, () => ref,
(v) => (ref = v) (v) => (ref = v)
) ),
xChannel: readableActive(() => xChannel),
yChannel: readableActive(() => yChannel)
}); });
const mergedProps = $derived(mergeProps(restProps, area.props)); const mergedProps = $derived(mergeProps(restProps, area.props));

@ -20,6 +20,7 @@
const soma = Soma.get(); const soma = Soma.get();
let { let {
ref = $bindable(null),
id = createId(uid, 'date-field'), id = createId(uid, 'date-field'),
value = $bindable(), value = $bindable(),
onValueChange = () => {}, onValueChange = () => {},
@ -37,9 +38,6 @@
required = false, required = false,
errorMessageId, errorMessageId,
locale, locale,
// `dir` is consumed by sub-parts through soma.prefs.getDir();
// destructured (as `_dir`) only to keep it out of `restProps`.
// eslint-disable-next-line @typescript-eslint/no-unused-vars
dir: _dir, dir: _dir,
hourCycle, hourCycle,
children, children,
@ -72,6 +70,10 @@
const state = DateFieldProvider.create({ const state = DateFieldProvider.create({
id: readableActive(() => id), id: readableActive(() => id),
ref: writableActive(
() => ref,
(v) => (ref = v)
),
value: writableActive( value: writableActive(
() => value, () => value,
(v) => { (v) => {
@ -97,6 +99,9 @@
hideTimeZone: readableActive(() => hideTimeZone), hideTimeZone: readableActive(() => hideTimeZone),
hourCycle: readableActive(() => hourCycle), hourCycle: readableActive(() => hourCycle),
locale: readableActive(() => locale ?? soma?.langs.getLocale() ?? ''), locale: readableActive(() => locale ?? soma?.langs.getLocale() ?? ''),
dir: readableActive<'ltr' | 'rtl'>(
() => (_dir ?? soma?.prefs.getDir() ?? 'ltr') as 'ltr' | 'rtl'
),
minValue: readableActive(() => minValue), minValue: readableActive(() => minValue),
maxValue: readableActive(() => maxValue), maxValue: readableActive(() => maxValue),
validate: readableActive(() => validate), validate: readableActive(() => validate),
@ -113,5 +118,7 @@
{#if child} {#if child}
{@render child({ props: mergedProps })} {@render child({ props: mergedProps })}
{:else} {:else}
<div {...mergedProps}>
{@render children?.()} {@render children?.()}
</div>
{/if} {/if}

@ -103,6 +103,7 @@ function createDateFieldOpts() {
granularity: state<Granularity | undefined>(undefined), granularity: state<Granularity | undefined>(undefined),
hourCycle: state<HourCycle | undefined>(undefined), hourCycle: state<HourCycle | undefined>(undefined),
locale: state('en-US'), locale: state('en-US'),
dir: state<'ltr' | 'rtl'>('ltr'),
hideTimeZone: state(false), hideTimeZone: state(false),
required: state(false), required: state(false),
errorMessageId: state<string | undefined>(undefined) errorMessageId: state<string | undefined>(undefined)

@ -12,7 +12,13 @@ import {
type ActiveProps, type ActiveProps,
type StateProps type StateProps
} from '$libs/reactive'; } from '$libs/reactive';
import type { SomaFocusEvent, SomaKeyboardEvent, SomaMouseEvent, OnChangeFn } from '../../types'; import type {
SomaFocusEvent,
SomaKeyboardEvent,
SomaMouseEvent,
OnChangeFn,
Direction
} from '../../types';
import { KEYS, getDirectionalKeys } from '../../keyboard'; import { KEYS, getDirectionalKeys } from '../../keyboard';
import { isNumberString } from '../../types/guards'; import { isNumberString } from '../../types/guards';
import { useId } from '../../id'; import { useId } from '../../id';
@ -158,6 +164,7 @@ export interface DateFieldOpts
granularity: Granularity | undefined; granularity: Granularity | undefined;
hourCycle: HourCycle | undefined; hourCycle: HourCycle | undefined;
locale: string; locale: string;
dir: Direction;
hideTimeZone: boolean; hideTimeZone: boolean;
required: boolean; required: boolean;
errorMessageId: string | undefined; errorMessageId: string | undefined;
@ -711,7 +718,8 @@ export class DateFieldProvider {
readonly props = $derived.by(() => readonly props = $derived.by(() =>
this.runtimePart.assert({ this.runtimePart.assert({
...this.runtimePart.props ...this.runtimePart.props,
dir: this.opts.dir.current
} as const) } as const)
); );
} }
@ -918,7 +926,7 @@ abstract class BaseNumericSegmentProvider {
} }
get directionalKeys() { get directionalKeys() {
return getDirectionalKeys(this.provider.soma?.prefs.getDir() ?? 'ltr', 'horizontal'); return getDirectionalKeys(this.provider.opts.dir.current, 'horizontal');
} }
/** /**
@ -1378,7 +1386,7 @@ class DateFieldDayPeriodSegmentProvider {
} }
get directionalKeys() { get directionalKeys() {
return getDirectionalKeys(this.provider.soma?.prefs.getDir() ?? 'ltr', 'horizontal'); return getDirectionalKeys(this.provider.opts.dir.current, 'horizontal');
} }
onkeydown = (e: SomaKeyboardEvent) => { onkeydown = (e: SomaKeyboardEvent) => {
@ -1498,7 +1506,7 @@ class DateFieldTimeZoneSegmentProvider {
} }
get directionalKeys() { get directionalKeys() {
return getDirectionalKeys(this.provider.soma?.prefs.getDir() ?? 'ltr', 'horizontal'); return getDirectionalKeys(this.provider.opts.dir.current, 'horizontal');
} }
onkeydown = (e: SomaKeyboardEvent) => { onkeydown = (e: SomaKeyboardEvent) => {

@ -61,8 +61,7 @@
isDateHoliday, isDateHoliday,
announceNavigation = true, announceNavigation = true,
errorMessageId, errorMessageId,
children, children
child
}: DatePickerProviderProps = $props(); }: DatePickerProviderProps = $props();
/** Today-based default when the consumer doesn't supply a placeholder. */ /** Today-based default when the consumer doesn't supply a placeholder. */
@ -137,7 +136,7 @@
granularity: readableActive(() => granularity), granularity: readableActive(() => granularity),
hideTimeZone: readableActive(() => hideTimeZone), hideTimeZone: readableActive(() => hideTimeZone),
hourCycle: readableActive(() => hourCycle), hourCycle: readableActive(() => hourCycle),
locale: readableActive(() => locale), locale: resolvedLocale,
dir: resolvedDir, dir: resolvedDir,
pagedNavigation: readableActive(() => pagedNavigation), pagedNavigation: readableActive(() => pagedNavigation),
weekStartsOn: readableActive(() => weekStartsOn), weekStartsOn: readableActive(() => weekStartsOn),
@ -183,12 +182,9 @@
hideTimeZone: readableActive(() => hideTimeZone), hideTimeZone: readableActive(() => hideTimeZone),
hourCycle: readableActive(() => hourCycle), hourCycle: readableActive(() => hourCycle),
locale: resolvedLocale, locale: resolvedLocale,
dir: resolvedDir,
errorMessageId: readableActive(() => errorMessageId) errorMessageId: readableActive(() => errorMessageId)
}); });
</script> </script>
{#if child} {@render children?.()}
{@render child({ props: {} })}
{:else}
{@render children?.()}
{/if}

@ -24,7 +24,7 @@ export type DatePickerCalendarSnippetProps = {
// ── Root provider ────────────────────────────────────────────────────────── // ── Root provider ──────────────────────────────────────────────────────────
/** Props for the root `DatePicker.Provider`. */ /** Props for the root `DatePicker.Provider`. */
export type DatePickerProviderProps = WithChild<{ export type DatePickerProviderProps = {
/** DOM id. Auto-generated when omitted. */ /** DOM id. Auto-generated when omitted. */
id?: string; id?: string;
@ -129,8 +129,8 @@ export type DatePickerProviderProps = WithChild<{
// ── ARIA ── // ── ARIA ──
/** External element id holding the error message (for aria-describedby). */ /** External element id holding the error message (for aria-describedby). */
errorMessageId?: string; errorMessageId?: string;
}> & children?: Snippet;
Omit<PrimitiveDivAttributes, 'placeholder'>; };
// ── Trigger ──────────────────────────────────────────────────────────────── // ── Trigger ────────────────────────────────────────────────────────────────

@ -75,6 +75,7 @@
hideTimeZone: provider.opts.hideTimeZone, hideTimeZone: provider.opts.hideTimeZone,
hourCycle: provider.opts.hourCycle, hourCycle: provider.opts.hourCycle,
locale: readableActive(() => provider.opts.locale.current ?? ''), locale: readableActive(() => provider.opts.locale.current ?? ''),
dir: provider.opts.dir,
errorMessageId: provider.opts.errorMessageId errorMessageId: provider.opts.errorMessageId
}); });

@ -71,8 +71,7 @@
weekNumberFormat = 'iso', weekNumberFormat = 'iso',
announceNavigation = true, announceNavigation = true,
errorMessageId, errorMessageId,
children, children
child
}: DateRangePickerProviderProps = $props(); }: DateRangePickerProviderProps = $props();
/** Today-based default placeholder. */ /** Today-based default placeholder. */
@ -196,7 +195,7 @@
granularity: readableActive(() => granularity), granularity: readableActive(() => granularity),
hideTimeZone: readableActive(() => hideTimeZone), hideTimeZone: readableActive(() => hideTimeZone),
hourCycle: readableActive(() => hourCycle), hourCycle: readableActive(() => hourCycle),
locale: readableActive(() => locale), locale: readableActive(() => locale ?? soma?.langs.getLocale() ?? ''),
dir: resolvedDir, dir: resolvedDir,
pagedNavigation: readableActive(() => pagedNavigation), pagedNavigation: readableActive(() => pagedNavigation),
weekStartsOn: readableActive(() => weekStartsOn), weekStartsOn: readableActive(() => weekStartsOn),
@ -253,8 +252,4 @@
}); });
</script> </script>
{#if child} {@render children?.()}
{@render child({ props: {} })}
{:else}
{@render children?.()}
{/if}

@ -23,7 +23,7 @@ export type DateRangePickerCalendarSnippetProps = {
}; };
/** Props for the root `DateRangePicker.Provider`. */ /** Props for the root `DateRangePicker.Provider`. */
export type DateRangePickerProviderProps = WithChild<{ export type DateRangePickerProviderProps = {
id?: string; id?: string;
// ── Value ── // ── Value ──
@ -109,8 +109,8 @@ export type DateRangePickerProviderProps = WithChild<{
// ── ARIA ── // ── ARIA ──
errorMessageId?: string; errorMessageId?: string;
}> & children?: Snippet;
Omit<PrimitiveDivAttributes, 'placeholder'>; };
/** Props for `DateRangePicker.Trigger`. */ /** Props for `DateRangePicker.Trigger`. */
export type DateRangePickerTriggerProps = WithChild<{ export type DateRangePickerTriggerProps = WithChild<{

@ -4,6 +4,7 @@
readableActive, readableActive,
writableActive writableActive
} from '$libs/reactive'; } from '$libs/reactive';
import { mergeProps } from '../../../props';
import { createId } from '../../../id'; import { createId } from '../../../id';
import { Soma } from '../../../core/soma.svelte'; import { Soma } from '../../../core/soma.svelte';
import { import {
@ -19,6 +20,7 @@
const soma = Soma.get(); const soma = Soma.get();
let { let {
ref = $bindable(null),
id = createId(uid, 'time-field'), id = createId(uid, 'time-field'),
value = $bindable(), value = $bindable(),
onValueChange = () => {}, onValueChange = () => {},
@ -39,7 +41,8 @@
dir: _dir, dir: _dir,
hourCycle, hourCycle,
children, children,
child child,
...restProps
}: TimeFieldProviderProps = $props(); }: TimeFieldProviderProps = $props();
/** Default placeholder = current wall-clock time (minute precision). */ /** Default placeholder = current wall-clock time (minute precision). */
@ -59,8 +62,12 @@
} }
); );
TimeFieldProvider.create({ const state = TimeFieldProvider.create({
id: readableActive(() => id), id: readableActive(() => id),
ref: writableActive(
() => ref,
(v) => (ref = v)
),
value: writableActive( value: writableActive(
() => value, () => value,
(v) => { (v) => {
@ -96,10 +103,14 @@
), ),
hourCycle: readableActive(() => hourCycle) hourCycle: readableActive(() => hourCycle)
}); });
const mergedProps = $derived(mergeProps(restProps, state.props));
</script> </script>
{#if child} {#if child}
{@render child({ props: {} })} {@render child({ props: mergedProps })}
{:else} {:else}
<div {...mergedProps}>
{@render children?.()} {@render children?.()}
</div>
{/if} {/if}

@ -41,8 +41,7 @@
minuteStep = 1, minuteStep = 1,
secondStep = 1, secondStep = 1,
errorMessageId, errorMessageId,
children, children
child
}: TimePickerProviderProps = $props(); }: TimePickerProviderProps = $props();
function defaultPlaceholder(): TimeValue { function defaultPlaceholder(): TimeValue {
@ -106,7 +105,7 @@
granularity: readableActive(() => granularity), granularity: readableActive(() => granularity),
hideTimeZone: readableActive(() => hideTimeZone), hideTimeZone: readableActive(() => hideTimeZone),
hourCycle: readableActive(() => hourCycle), hourCycle: readableActive(() => hourCycle),
locale: readableActive(() => locale), locale: readableActive(() => locale ?? soma?.langs.getLocale() ?? ''),
dir: resolvedDir, dir: resolvedDir,
hourStep: readableActive(() => hourStep), hourStep: readableActive(() => hourStep),
minuteStep: readableActive(() => minuteStep), minuteStep: readableActive(() => minuteStep),
@ -143,8 +142,4 @@
}); });
</script> </script>
{#if child} {@render children?.()}
{@render child({ props: {} })}
{:else}
{@render children?.()}
{/if}

@ -1,5 +1,6 @@
import type { Snippet } from 'svelte';
import type { WithChild, Without, OnChangeFn, Direction } from '../../types'; import type { WithChild, Without, OnChangeFn, Direction } from '../../types';
import type { PrimitiveDivAttributes, PrimitiveButtonAttributes } from '../../types'; import type { PrimitiveButtonAttributes } from '../../types';
import type { import type {
TimeValue, TimeValue,
TimeValidator, TimeValidator,
@ -10,7 +11,7 @@ import type {
} from '$libs/days'; } from '$libs/days';
/** Props for the root `TimePicker.Provider`. */ /** Props for the root `TimePicker.Provider`. */
export type TimePickerProviderProps = WithChild<{ export type TimePickerProviderProps = {
id?: string; id?: string;
// ── Value ── // ── Value ──
@ -54,8 +55,8 @@ export type TimePickerProviderProps = WithChild<{
// ── ARIA ── // ── ARIA ──
errorMessageId?: string; errorMessageId?: string;
}> & children?: Snippet;
Omit<PrimitiveDivAttributes, 'placeholder'>; };
/** Props for `TimePicker.Trigger`. */ /** Props for `TimePicker.Trigger`. */
export type TimePickerTriggerProps = WithChild<{ export type TimePickerTriggerProps = WithChild<{

@ -46,8 +46,7 @@
minuteStep = 1, minuteStep = 1,
secondStep = 1, secondStep = 1,
errorMessageId, errorMessageId,
children, children
child
}: TimeRangePickerProviderProps = $props(); }: TimeRangePickerProviderProps = $props();
/** Default placeholder = current wall-clock time (minute precision). */ /** Default placeholder = current wall-clock time (minute precision). */
@ -147,7 +146,7 @@
granularity: readableActive(() => granularity), granularity: readableActive(() => granularity),
hideTimeZone: readableActive(() => hideTimeZone), hideTimeZone: readableActive(() => hideTimeZone),
hourCycle: readableActive(() => hourCycle), hourCycle: readableActive(() => hourCycle),
locale: readableActive(() => locale), locale: readableActive(() => locale ?? soma?.langs.getLocale() ?? ''),
dir: resolvedDir, dir: resolvedDir,
hourStep: readableActive(() => hourStep), hourStep: readableActive(() => hourStep),
minuteStep: readableActive(() => minuteStep), minuteStep: readableActive(() => minuteStep),
@ -193,8 +192,4 @@
}); });
</script> </script>
{#if child} {@render children?.()}
{@render child({ props: {} })}
{:else}
{@render children?.()}
{/if}

@ -1,3 +1,4 @@
import type { Snippet } from 'svelte';
import type { WithChild, Without, OnChangeFn, Direction } from '../../types'; import type { WithChild, Without, OnChangeFn, Direction } from '../../types';
import type { PrimitiveDivAttributes, PrimitiveButtonAttributes } from '../../types'; import type { PrimitiveDivAttributes, PrimitiveButtonAttributes } from '../../types';
import type { import type {
@ -14,7 +15,7 @@ import type {
export type TimeRangeEndpoint = 'start' | 'end'; export type TimeRangeEndpoint = 'start' | 'end';
/** Props for the root `TimeRangePicker.Provider`. */ /** Props for the root `TimeRangePicker.Provider`. */
export type TimeRangePickerProviderProps = WithChild<{ export type TimeRangePickerProviderProps = {
id?: string; id?: string;
// ── Value ── // ── Value ──
@ -81,8 +82,8 @@ export type TimeRangePickerProviderProps = WithChild<{
// ── ARIA ── // ── ARIA ──
errorMessageId?: string; errorMessageId?: string;
}> & children?: Snippet;
Omit<PrimitiveDivAttributes, 'placeholder'>; };
/** Props for `TimeRangePicker.Trigger`. */ /** Props for `TimeRangePicker.Trigger`. */
export type TimeRangePickerTriggerProps = WithChild<{ export type TimeRangePickerTriggerProps = WithChild<{

Loading…
Cancel
Save

Powered by TurnKey Linux.