Consolidate Soma range control runtimes

active-uix
dev 5 months ago
parent 959439f5fa
commit 48b0c4ae2f

@ -25,7 +25,7 @@ import { FieldProvider } from '../field/field-provider.svelte';
import { dateRangeFieldMorfo } from '../../../morfo/components/date-range-field'; import { dateRangeFieldMorfo } from '../../../morfo/components/date-range-field';
const attrs = createAttrs(dateRangeFieldMorfo); const attrs = createAttrs(dateRangeFieldMorfo);
import type { SomaRuntimePart } from '../../runtime.svelte'; import type { SomaRuntime, SomaRuntimePart } from '../../runtime.svelte';
// ── Root ──────────────────────────────────────────────────────────────────── // ── Root ────────────────────────────────────────────────────────────────────
@ -65,6 +65,8 @@ interface DateRangeFieldOpts
export class DateRangeFieldProvider { export class DateRangeFieldProvider {
readonly opts: DateRangeFieldOpts; readonly opts: DateRangeFieldOpts;
readonly runtimePart: SomaRuntimePart; readonly runtimePart: SomaRuntimePart;
readonly soma: Soma;
readonly runtime: SomaRuntime;
static readonly ctx = context<DateRangeFieldProvider>('DateRangeField'); static readonly ctx = context<DateRangeFieldProvider>('DateRangeField');
static get(): DateRangeFieldProvider | undefined { static get(): DateRangeFieldProvider | undefined {
return this.ctx.getOr(undefined) as DateRangeFieldProvider | undefined; return this.ctx.getOr(undefined) as DateRangeFieldProvider | undefined;
@ -76,7 +78,6 @@ export class DateRangeFieldProvider {
return new DateRangeFieldProvider(opts); return new DateRangeFieldProvider(opts);
} }
readonly soma = Soma.get();
readonly field = FieldProvider.get(); readonly field = FieldProvider.get();
labelNode = $state<HTMLElement | null>(null); labelNode = $state<HTMLElement | null>(null);
@ -85,14 +86,14 @@ export class DateRangeFieldProvider {
private constructor(opts: DateRangeFieldOpts) { private constructor(opts: DateRangeFieldOpts) {
this.opts = opts; this.opts = opts;
this.runtimePart = Soma.require() this.soma = Soma.require();
.runtime(dateRangeFieldMorfo, {}) this.runtime = this.soma.runtime(dateRangeFieldMorfo, {});
.part('provider', { this.runtimePart = this.runtime.part('provider', {
id: opts.id, id: opts.id,
ref: opts.ref, ref: opts.ref,
owner: this, owner: this,
context: DateRangeFieldProvider.ctx context: DateRangeFieldProvider.ctx
}); });
} }
// ── Merged flags (own + Field parent via OR) ───────────────────────────── // ── Merged flags (own + Field parent via OR) ─────────────────────────────
@ -171,17 +172,15 @@ export class DateRangeFieldLabelProvider {
private constructor(opts: DateRangeFieldLabelOpts) { private constructor(opts: DateRangeFieldLabelOpts) {
this.opts = opts; this.opts = opts;
this.runtimePart = Soma.require()
.runtime(dateRangeFieldMorfo, {})
.part('label', {
id: opts.id,
ref: opts.ref,
owner: this,
onRefChange: (v) => {
this.provider.labelNode = v;
}
});
this.provider = DateRangeFieldProvider.require(); this.provider = DateRangeFieldProvider.require();
this.runtimePart = this.provider.runtime.part('label', {
id: opts.id,
ref: opts.ref,
owner: this,
onRefChange: (v) => {
this.provider.labelNode = v;
}
});
} }
readonly onclick = () => { readonly onclick = () => {

@ -20,7 +20,7 @@ import { PIN_INPUT_LANGS } from './langs';
import type { PinInputCellData, PinInputType } from './types'; import type { PinInputCellData, PinInputType } from './types';
import { pinInputMorfo } from '../../../morfo/components/pin-input'; import { pinInputMorfo } from '../../../morfo/components/pin-input';
import type { SomaRuntimePart } from '../../runtime.svelte'; import type { SomaRuntime, SomaRuntimePart } from '../../runtime.svelte';
// ── Preset patterns ──────────────────────────────────────────────────────── // ── Preset patterns ────────────────────────────────────────────────────────
@ -82,6 +82,8 @@ interface PinInputOpts
export class PinInputProvider { export class PinInputProvider {
readonly opts: PinInputOpts; readonly opts: PinInputOpts;
readonly runtimePart: SomaRuntimePart; readonly runtimePart: SomaRuntimePart;
readonly soma: Soma;
readonly runtime: SomaRuntime;
static readonly ctx = context<PinInputProvider>('PinInput'); static readonly ctx = context<PinInputProvider>('PinInput');
static get(): PinInputProvider | undefined { static get(): PinInputProvider | undefined {
return this.ctx.getOr(undefined) as PinInputProvider | undefined; return this.ctx.getOr(undefined) as PinInputProvider | undefined;
@ -94,7 +96,6 @@ export class PinInputProvider {
return new PinInputProvider(opts); return new PinInputProvider(opts);
} }
readonly soma = Soma.get();
readonly field = FieldProvider.get(); readonly field = FieldProvider.get();
inputRef = state<HTMLInputElement | null>(null); inputRef = state<HTMLInputElement | null>(null);
@ -105,9 +106,14 @@ export class PinInputProvider {
private constructor(opts: PinInputOpts) { private constructor(opts: PinInputOpts) {
this.opts = opts; this.opts = opts;
this.runtimePart = Soma.require() this.soma = Soma.require();
.runtime(pinInputMorfo, {}) this.runtime = this.soma.runtime(pinInputMorfo, {});
.part('provider', { id: opts.id, ref: opts.ref, owner: this, context: PinInputProvider.ctx }); this.runtimePart = this.runtime.part('provider', {
id: opts.id,
ref: opts.ref,
owner: this,
context: PinInputProvider.ctx
});
// Register the hidden input id with a parent Field so `Field.Label`'s // Register the hidden input id with a parent Field so `Field.Label`'s
// `for` attribute lands on the interactive element. Direct assignment // `for` attribute lands on the interactive element. Direct assignment
@ -157,7 +163,7 @@ export class PinInputProvider {
() => this.opts.autocomplete.current ?? 'one-time-code' () => this.opts.autocomplete.current ?? 'one-time-code'
); );
readonly resolvedAriaLabel: Active<string | undefined> = readableActive( readonly resolvedAriaLabel: Active<string | undefined> = readableActive(
() => this.opts.ariaLabel.current || this.soma?.langs.ts(PIN_INPUT_LANGS.INPUT) || undefined () => this.opts.ariaLabel.current || this.soma.langs.ts(PIN_INPUT_LANGS.INPUT) || undefined
); );
readonly isComplete = $derived.by( readonly isComplete = $derived.by(
@ -326,17 +332,15 @@ export class PinInputInputProvider {
private constructor(opts: PinInputInputOpts) { private constructor(opts: PinInputInputOpts) {
this.opts = opts; this.opts = opts;
this.runtimePart = Soma.require()
.runtime(pinInputMorfo, {})
.part('input', {
id: opts.id,
ref: opts.ref,
owner: this,
onRefChange: (el) => {
this.provider.setInputRef(el as HTMLInputElement | null);
}
});
this.provider = PinInputProvider.require(); this.provider = PinInputProvider.require();
this.runtimePart = this.provider.runtime.part('input', {
id: opts.id,
ref: opts.ref,
owner: this,
onRefChange: (el) => {
this.provider.setInputRef(el as HTMLInputElement | null);
}
});
} }
readonly props = $derived.by(() => readonly props = $derived.by(() =>
@ -384,10 +388,12 @@ export class PinInputCellProvider {
private constructor(opts: PinInputCellOpts) { private constructor(opts: PinInputCellOpts) {
this.opts = opts; this.opts = opts;
this.runtimePart = Soma.require()
.runtime(pinInputMorfo, {})
.part('cell', { id: opts.id, ref: opts.ref, owner: this });
this.provider = PinInputProvider.require(); this.provider = PinInputProvider.require();
this.runtimePart = this.provider.runtime.part('cell', {
id: opts.id,
ref: opts.ref,
owner: this
});
} }
readonly props = $derived.by(() => { readonly props = $derived.by(() => {

@ -6,7 +6,7 @@ import { KEYS, getDirectionalKeys } from '../../keyboard';
import { sliderMorfo } from '../../../morfo/components/slider'; import { sliderMorfo } from '../../../morfo/components/slider';
import { Soma } from '../../core/soma.svelte'; import { Soma } from '../../core/soma.svelte';
import type { SomaRuntimePart } from '../../runtime.svelte'; import type { SomaRuntime, SomaRuntimePart } from '../../runtime.svelte';
export type SliderType = 'single' | 'multiple'; export type SliderType = 'single' | 'multiple';
@ -53,6 +53,8 @@ interface SliderOpts
export class SliderProvider { export class SliderProvider {
readonly opts: SliderOpts; readonly opts: SliderOpts;
readonly runtimePart: SomaRuntimePart; readonly runtimePart: SomaRuntimePart;
readonly soma: Soma;
readonly runtime: SomaRuntime;
static readonly ctx = context<SliderProvider>('Slider'); static readonly ctx = context<SliderProvider>('Slider');
static get(): SliderProvider | undefined { static get(): SliderProvider | undefined {
return this.ctx.getOr(undefined) as SliderProvider | undefined; return this.ctx.getOr(undefined) as SliderProvider | undefined;
@ -69,9 +71,14 @@ export class SliderProvider {
private constructor(opts: SliderOpts) { private constructor(opts: SliderOpts) {
this.opts = opts; this.opts = opts;
this.runtimePart = Soma.require() this.soma = Soma.require();
.runtime(sliderMorfo, {}) this.runtime = this.soma.runtime(sliderMorfo, {});
.part('provider', { id: opts.id, ref: opts.ref, owner: this, context: SliderProvider.ctx }); this.runtimePart = this.runtime.part('provider', {
id: opts.id,
ref: opts.ref,
owner: this,
context: SliderProvider.ctx
});
} }
getPercent(value: number): number { getPercent(value: number): number {
@ -237,10 +244,12 @@ export class SliderRangeProvider {
private constructor(opts: SliderRangeOpts) { private constructor(opts: SliderRangeOpts) {
this.opts = opts; this.opts = opts;
this.runtimePart = Soma.require()
.runtime(sliderMorfo, {})
.part('range', { id: opts.id, ref: opts.ref, owner: this });
this.provider = SliderProvider.require(); this.provider = SliderProvider.require();
this.runtimePart = this.provider.runtime.part('range', {
id: opts.id,
ref: opts.ref,
owner: this
});
} }
readonly rangeStyle = $derived.by(() => { readonly rangeStyle = $derived.by(() => {
@ -289,10 +298,12 @@ export class SliderThumbProvider {
private constructor(opts: SliderThumbOpts) { private constructor(opts: SliderThumbOpts) {
this.opts = opts; this.opts = opts;
this.runtimePart = Soma.require()
.runtime(sliderMorfo, {})
.part('thumb', { id: opts.id, ref: opts.ref, owner: this });
this.provider = SliderProvider.require(); this.provider = SliderProvider.require();
this.runtimePart = this.provider.runtime.part('thumb', {
id: opts.id,
ref: opts.ref,
owner: this
});
} }
readonly value = $derived.by( readonly value = $derived.by(
@ -401,10 +412,12 @@ export class SliderTickProvider {
private constructor(opts: SliderTickOpts) { private constructor(opts: SliderTickOpts) {
this.opts = opts; this.opts = opts;
this.runtimePart = Soma.require()
.runtime(sliderMorfo, {})
.part('tick', { id: opts.id, ref: opts.ref, owner: this });
this.provider = SliderProvider.require(); this.provider = SliderProvider.require();
this.runtimePart = this.provider.runtime.part('tick', {
id: opts.id,
ref: opts.ref,
owner: this
});
} }
readonly percent = $derived.by(() => this.provider.getPercent(this.opts.value.current)); readonly percent = $derived.by(() => this.provider.getPercent(this.opts.value.current));

@ -7,7 +7,7 @@ import { Soma } from '../../core/soma.svelte';
import { SPLITTER_LANGS } from './langs'; import { SPLITTER_LANGS } from './langs';
import { splitterMorfo } from '../../../morfo/components/splitter'; import { splitterMorfo } from '../../../morfo/components/splitter';
import type { SomaRuntimePart } from '../../runtime.svelte'; import type { SomaRuntime, SomaRuntimePart } from '../../runtime.svelte';
// ── Types ──────────────────────────────────────────────────────────────────── // ── Types ────────────────────────────────────────────────────────────────────
@ -57,6 +57,8 @@ interface SplitterOpts
export class SplitterProvider { export class SplitterProvider {
readonly opts: SplitterOpts; readonly opts: SplitterOpts;
readonly runtimePart: SomaRuntimePart; readonly runtimePart: SomaRuntimePart;
readonly soma: Soma;
readonly runtime: SomaRuntime;
static readonly ctx = context<SplitterProvider>('Splitter'); static readonly ctx = context<SplitterProvider>('Splitter');
static get(): SplitterProvider | undefined { static get(): SplitterProvider | undefined {
return this.ctx.getOr(undefined) as SplitterProvider | undefined; return this.ctx.getOr(undefined) as SplitterProvider | undefined;
@ -69,15 +71,19 @@ export class SplitterProvider {
return new SplitterProvider(opts); return new SplitterProvider(opts);
} }
readonly soma = Soma.get();
private panels: PanelState[] = $state([]); private panels: PanelState[] = $state([]);
resizing = $state(false); resizing = $state(false);
private constructor(opts: SplitterOpts) { private constructor(opts: SplitterOpts) {
this.opts = opts; this.opts = opts;
this.runtimePart = Soma.require() this.soma = Soma.require();
.runtime(splitterMorfo, {}) this.runtime = this.soma.runtime(splitterMorfo, {});
.part('provider', { id: opts.id, ref: opts.ref, owner: this, context: SplitterProvider.ctx }); this.runtimePart = this.runtime.part('provider', {
id: opts.id,
ref: opts.ref,
owner: this,
context: SplitterProvider.ctx
});
} }
// ── Panel registration ─────────────────────────────────────────────────── // ── Panel registration ───────────────────────────────────────────────────
@ -282,10 +288,12 @@ export class SplitterPanelProvider {
private constructor(opts: SplitterPanelOpts) { private constructor(opts: SplitterPanelOpts) {
this.opts = opts; this.opts = opts;
this.runtimePart = Soma.require()
.runtime(splitterMorfo, {})
.part('panel', { id: opts.id, ref: opts.ref, owner: this });
this.provider = SplitterProvider.require(); this.provider = SplitterProvider.require();
this.runtimePart = this.provider.runtime.part('panel', {
id: opts.id,
ref: opts.ref,
owner: this
});
this.cleanup = this.provider.registerPanel({ this.cleanup = this.provider.registerPanel({
id: opts.panelId.current, id: opts.panelId.current,
@ -356,10 +364,12 @@ export class SplitterResizeTriggerProvider {
private constructor(opts: SplitterResizeTriggerOpts) { private constructor(opts: SplitterResizeTriggerOpts) {
this.opts = opts; this.opts = opts;
this.runtimePart = Soma.require()
.runtime(splitterMorfo, {})
.part('resize-trigger', { id: opts.id, ref: opts.ref, owner: this });
this.provider = SplitterProvider.require(); this.provider = SplitterProvider.require();
this.runtimePart = this.provider.runtime.part('resize-trigger', {
id: opts.id,
ref: opts.ref,
owner: this
});
// Cleanup drag state on unmount // Cleanup drag state on unmount
$effect(() => { $effect(() => {
@ -545,7 +555,7 @@ export class SplitterResizeTriggerProvider {
this.runtimePart.assert({ this.runtimePart.assert({
...this.runtimePart.props, ...this.runtimePart.props,
role: 'separator' as const, role: 'separator' as const,
'aria-label': this.provider.soma?.langs.ts(SPLITTER_LANGS.RESIZE), 'aria-label': this.provider.soma.langs.ts(SPLITTER_LANGS.RESIZE),
'aria-orientation': this.provider.opts.orientation.current, 'aria-orientation': this.provider.opts.orientation.current,
'aria-valuenow': this.ariaValueNow, 'aria-valuenow': this.ariaValueNow,
'aria-valuemin': this.ariaValueMin, 'aria-valuemin': this.ariaValueMin,

@ -20,7 +20,7 @@ import { FieldProvider } from '../field/field-provider.svelte';
import { timeRangeFieldMorfo } from '../../../morfo/components/time-range-field'; import { timeRangeFieldMorfo } from '../../../morfo/components/time-range-field';
const attrs = createAttrs(timeRangeFieldMorfo); const attrs = createAttrs(timeRangeFieldMorfo);
import type { SomaRuntimePart } from '../../runtime.svelte'; import type { SomaRuntime, SomaRuntimePart } from '../../runtime.svelte';
// ── Root ──────────────────────────────────────────────────────────────────── // ── Root ────────────────────────────────────────────────────────────────────
@ -60,6 +60,8 @@ interface TimeRangeFieldOpts
export class TimeRangeFieldProvider { export class TimeRangeFieldProvider {
readonly opts: TimeRangeFieldOpts; readonly opts: TimeRangeFieldOpts;
readonly runtimePart: SomaRuntimePart; readonly runtimePart: SomaRuntimePart;
readonly soma: Soma;
readonly runtime: SomaRuntime;
static readonly ctx = context<TimeRangeFieldProvider>('TimeRangeField'); static readonly ctx = context<TimeRangeFieldProvider>('TimeRangeField');
static get(): TimeRangeFieldProvider | undefined { static get(): TimeRangeFieldProvider | undefined {
return this.ctx.getOr(undefined) as TimeRangeFieldProvider | undefined; return this.ctx.getOr(undefined) as TimeRangeFieldProvider | undefined;
@ -71,7 +73,6 @@ export class TimeRangeFieldProvider {
return new TimeRangeFieldProvider(opts); return new TimeRangeFieldProvider(opts);
} }
readonly soma = Soma.get();
readonly field = FieldProvider.get(); readonly field = FieldProvider.get();
labelNode = $state<HTMLElement | null>(null); labelNode = $state<HTMLElement | null>(null);
@ -80,14 +81,14 @@ export class TimeRangeFieldProvider {
private constructor(opts: TimeRangeFieldOpts) { private constructor(opts: TimeRangeFieldOpts) {
this.opts = opts; this.opts = opts;
this.runtimePart = Soma.require() this.soma = Soma.require();
.runtime(timeRangeFieldMorfo, {}) this.runtime = this.soma.runtime(timeRangeFieldMorfo, {});
.part('provider', { this.runtimePart = this.runtime.part('provider', {
id: opts.id, id: opts.id,
ref: opts.ref, ref: opts.ref,
owner: this, owner: this,
context: TimeRangeFieldProvider.ctx context: TimeRangeFieldProvider.ctx
}); });
} }
// ── Merged flags (own + Field parent via OR) ───────────────────────────── // ── Merged flags (own + Field parent via OR) ─────────────────────────────
@ -179,17 +180,15 @@ export class TimeRangeFieldLabelProvider {
private constructor(opts: TimeRangeFieldLabelOpts) { private constructor(opts: TimeRangeFieldLabelOpts) {
this.opts = opts; this.opts = opts;
this.runtimePart = Soma.require()
.runtime(timeRangeFieldMorfo, {})
.part('label', {
id: opts.id,
ref: opts.ref,
owner: this,
onRefChange: (v) => {
this.provider.labelNode = v;
}
});
this.provider = TimeRangeFieldProvider.require(); this.provider = TimeRangeFieldProvider.require();
this.runtimePart = this.provider.runtime.part('label', {
id: opts.id,
ref: opts.ref,
owner: this,
onRefChange: (v) => {
this.provider.labelNode = v;
}
});
} }
readonly onclick = () => { readonly onclick = () => {

Loading…
Cancel
Save

Powered by TurnKey Linux.