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

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

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

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

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

Loading…
Cancel
Save

Powered by TurnKey Linux.