Consolidate Soma provider runtimes

active-uix
dev 5 months ago
parent 73862094f6
commit a60d040282

@ -35,6 +35,7 @@ export class AccordionProvider {
readonly opts: AccordionOpts; readonly opts: AccordionOpts;
readonly runtimePart: SomaRuntimePart; readonly runtimePart: SomaRuntimePart;
readonly soma = Soma.require(); readonly soma = Soma.require();
readonly runtime: SomaRuntime;
static readonly ctx = context<AccordionProvider>('Accordion'); static readonly ctx = context<AccordionProvider>('Accordion');
static get(): AccordionProvider | undefined { static get(): AccordionProvider | undefined {
return this.ctx.getOr(undefined) as AccordionProvider | undefined; return this.ctx.getOr(undefined) as AccordionProvider | undefined;
@ -51,20 +52,19 @@ export class AccordionProvider {
private constructor(opts: AccordionOpts) { private constructor(opts: AccordionOpts) {
this.opts = opts; this.opts = opts;
this.runtimePart = this.soma this.runtime = this.soma.runtime(accordionMorfo, {
.runtime(accordionMorfo, { props: {
props: { orientation: () => this.opts.orientation.current,
orientation: () => this.opts.orientation.current, disabled: () => this.opts.disabled.current
disabled: () => this.opts.disabled.current }
} });
}) this.runtimePart = this.runtime.part('provider', {
.part('provider', { id: opts.id,
id: opts.id, ref: opts.ref,
ref: opts.ref, owner: this,
owner: this, context: AccordionProvider.ctx,
context: AccordionProvider.ctx, syncAttrs: true
syncAttrs: true });
});
$effect(() => { $effect(() => {
if (this.opts.type.current !== 'single') return; if (this.opts.type.current !== 'single') return;
@ -177,7 +177,7 @@ export class AccordionItemProvider {
private constructor(opts: AccordionItemOpts) { private constructor(opts: AccordionItemOpts) {
this.opts = opts; this.opts = opts;
this.provider = AccordionProvider.require(); this.provider = AccordionProvider.require();
this.runtime = Soma.require().runtime(accordionMorfo, { this.runtime = this.provider.soma.runtime(accordionMorfo, {
states: { states: {
open: () => this.isOpen open: () => this.isOpen
}, },
@ -255,18 +255,20 @@ export class AccordionHeaderProvider {
private constructor(opts: AccordionHeaderOpts) { private constructor(opts: AccordionHeaderOpts) {
this.opts = opts; this.opts = opts;
this.item = AccordionItemProvider.require(); this.item = AccordionItemProvider.require();
this.runtimePart = Soma.require() this.runtimePart = this.item.runtime.part('header', {
.runtime(accordionMorfo, { id: opts.id,
states: { ref: opts.ref,
open: () => this.item.isOpen states: {
}, open: () => this.item.isOpen
props: { },
level: () => Math.max(1, Math.min(6, this.opts.level.current)), props: {
disabled: () => this.item.isDisabled, level: () => Math.max(1, Math.min(6, this.opts.level.current)),
orientation: () => this.item.provider.opts.orientation.current disabled: () => this.item.isDisabled,
} orientation: () => this.item.provider.opts.orientation.current
}) },
.part('header', { id: opts.id, ref: opts.ref, owner: this, syncAttrs: true }); owner: this,
syncAttrs: true
});
} }
readonly props = $derived.by(() => readonly props = $derived.by(() =>
@ -292,20 +294,22 @@ export class AccordionTriggerProvider {
private constructor(opts: AccordionTriggerOpts) { private constructor(opts: AccordionTriggerOpts) {
this.opts = opts; this.opts = opts;
this.item = AccordionItemProvider.require(); this.item = AccordionItemProvider.require();
this.runtimePart = Soma.require() this.runtimePart = this.item.runtime.part('trigger', {
.runtime(accordionMorfo, { id: opts.id,
states: { ref: opts.ref,
open: () => this.item.isOpen states: {
}, open: () => this.item.isOpen
props: { },
disabled: () => this.item.isDisabled, props: {
orientation: () => this.item.provider.opts.orientation.current disabled: () => this.item.isDisabled,
}, orientation: () => this.item.provider.opts.orientation.current
parts: { },
content: () => this.item.contentId.current || undefined parts: {
} content: () => this.item.contentId.current || undefined
}) },
.part('trigger', { id: opts.id, ref: opts.ref, owner: this, syncAttrs: true }); owner: this,
syncAttrs: true
});
this.item.triggerId.current = opts.id.current; this.item.triggerId.current = opts.id.current;
} }
@ -377,29 +381,26 @@ export class AccordionContentProvider {
private constructor(opts: AccordionContentOpts) { private constructor(opts: AccordionContentOpts) {
this.opts = opts; this.opts = opts;
this.item = AccordionItemProvider.require(); this.item = AccordionItemProvider.require();
this.runtimePart = Soma.require() this.runtimePart = this.item.runtime.part('content', {
.runtime(accordionMorfo, { id: opts.id,
states: { ref: opts.ref,
open: () => this.item.isOpen states: {
}, open: () => this.item.isOpen
props: { },
disabled: () => this.item.isDisabled, props: {
orientation: () => this.item.provider.opts.orientation.current, disabled: () => this.item.isDisabled,
region: () => this.item.provider.opts.region.current orientation: () => this.item.provider.opts.orientation.current,
}, region: () => this.item.provider.opts.region.current
parts: { },
trigger: () => this.item.triggerId.current || undefined parts: {
} trigger: () => this.item.triggerId.current || undefined
}) },
.part('content', { owner: this,
id: opts.id, onRefChange: (el) => {
ref: opts.ref, this.item.setContentRef(el);
owner: this, },
onRefChange: (el) => { syncAttrs: true
this.item.setContentRef(el); });
},
syncAttrs: true
});
this.item.contentId.current = opts.id.current; this.item.contentId.current = opts.id.current;
} }

@ -5,7 +5,7 @@ import { BREADCRUMB_LANGS } from './langs';
import type { BreadcrumbCurrent } from './types'; import type { BreadcrumbCurrent } from './types';
import { breadcrumbMorfo } from '../../../morfo/components/breadcrumb'; import { breadcrumbMorfo } from '../../../morfo/components/breadcrumb';
import type { SomaRuntimePart } from '../../runtime.svelte'; import type { SomaRuntime, SomaRuntimePart } from '../../runtime.svelte';
// ── Root ──────────────────────────────────────────────────────────────────── // ── Root ────────────────────────────────────────────────────────────────────
@ -21,6 +21,8 @@ interface BreadcrumbOpts
export class BreadcrumbProvider { export class BreadcrumbProvider {
readonly opts: BreadcrumbOpts; readonly opts: BreadcrumbOpts;
readonly runtimePart: SomaRuntimePart; readonly runtimePart: SomaRuntimePart;
readonly soma: Soma;
readonly runtime: SomaRuntime;
static readonly ctx = context<BreadcrumbProvider>('Breadcrumb'); static readonly ctx = context<BreadcrumbProvider>('Breadcrumb');
static get(): BreadcrumbProvider | undefined { static get(): BreadcrumbProvider | undefined {
return this.ctx.getOr(undefined) as BreadcrumbProvider | undefined; return this.ctx.getOr(undefined) as BreadcrumbProvider | undefined;
@ -33,30 +35,28 @@ export class BreadcrumbProvider {
return new BreadcrumbProvider(opts); return new BreadcrumbProvider(opts);
} }
readonly soma = Soma.get();
private constructor(opts: BreadcrumbOpts) { private constructor(opts: BreadcrumbOpts) {
this.opts = opts; this.opts = opts;
this.runtimePart = Soma.require() this.soma = Soma.require();
.runtime(breadcrumbMorfo, { this.runtime = this.soma.runtime(breadcrumbMorfo, {
props: { props: {
ariaLabel: () => this.resolvedAriaLabel.current, ariaLabel: () => this.resolvedAriaLabel.current,
ariaLabelledby: () => this.opts.ariaLabelledby.current ariaLabelledby: () => this.opts.ariaLabelledby.current
} }
}) });
.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: BreadcrumbProvider.ctx, context: BreadcrumbProvider.ctx,
syncAttrs: true syncAttrs: true
}); });
} }
readonly resolvedAriaLabel: Active<string | undefined> = readableActive(() => readonly resolvedAriaLabel: Active<string | undefined> = readableActive(() =>
this.opts.ariaLabelledby.current this.opts.ariaLabelledby.current
? undefined ? undefined
: this.opts.ariaLabel.current || this.soma?.langs.ts(BREADCRUMB_LANGS.LABEL) || undefined : this.opts.ariaLabel.current || this.soma.langs.ts(BREADCRUMB_LANGS.LABEL) || undefined
); );
readonly props = $derived.by(() => readonly props = $derived.by(() =>
@ -81,10 +81,12 @@ export class BreadcrumbListProvider {
private constructor(opts: BreadcrumbListOpts) { private constructor(opts: BreadcrumbListOpts) {
this.opts = opts; this.opts = opts;
this.runtimePart = Soma.require()
.runtime(breadcrumbMorfo, {})
.part('list', { id: opts.id, ref: opts.ref, owner: this });
this.provider = BreadcrumbProvider.require(); this.provider = BreadcrumbProvider.require();
this.runtimePart = this.provider.runtime.part('list', {
id: opts.id,
ref: opts.ref,
owner: this
});
} }
readonly props = $derived.by(() => readonly props = $derived.by(() =>
@ -117,20 +119,17 @@ export class BreadcrumbItemProvider {
private constructor(opts: BreadcrumbItemOpts) { private constructor(opts: BreadcrumbItemOpts) {
this.opts = opts; this.opts = opts;
this.runtimePart = Soma.require()
.runtime(breadcrumbMorfo, {
props: {
current: () => this.isCurrent
}
})
.part('item', {
id: opts.id,
ref: opts.ref,
owner: this,
context: BreadcrumbItemProvider.ctx,
syncAttrs: true
});
this.provider = BreadcrumbProvider.require(); this.provider = BreadcrumbProvider.require();
this.runtimePart = this.provider.runtime.part('item', {
id: opts.id,
ref: opts.ref,
props: {
current: () => this.isCurrent
},
owner: this,
context: BreadcrumbItemProvider.ctx,
syncAttrs: true
});
} }
readonly props = $derived.by(() => readonly props = $derived.by(() =>
@ -157,14 +156,16 @@ export class BreadcrumbLinkProvider {
private constructor(opts: BreadcrumbLinkOpts) { private constructor(opts: BreadcrumbLinkOpts) {
this.opts = opts; this.opts = opts;
this.runtimePart = Soma.require()
.runtime(breadcrumbMorfo, {
props: {
current: () => this.ariaCurrent
}
})
.part('link', { id: opts.id, ref: opts.ref, owner: this, syncAttrs: true });
this.provider = BreadcrumbProvider.require(); this.provider = BreadcrumbProvider.require();
this.runtimePart = this.provider.runtime.part('link', {
id: opts.id,
ref: opts.ref,
props: {
current: () => this.ariaCurrent
},
owner: this,
syncAttrs: true
});
this.item = BreadcrumbItemProvider.get(); this.item = BreadcrumbItemProvider.get();
// Mirror current state onto the parent Item (so `[data-breadcrumb-item]` // Mirror current state onto the parent Item (so `[data-breadcrumb-item]`
@ -216,10 +217,13 @@ export class BreadcrumbSeparatorProvider {
private constructor(opts: BreadcrumbSeparatorOpts) { private constructor(opts: BreadcrumbSeparatorOpts) {
this.opts = opts; this.opts = opts;
this.runtimePart = Soma.require()
.runtime(breadcrumbMorfo, {})
.part('separator', { id: opts.id, ref: opts.ref, owner: this, syncAttrs: true });
this.provider = BreadcrumbProvider.require(); this.provider = BreadcrumbProvider.require();
this.runtimePart = this.provider.runtime.part('separator', {
id: opts.id,
ref: opts.ref,
owner: this,
syncAttrs: true
});
} }
readonly props = $derived.by(() => readonly props = $derived.by(() =>
@ -254,15 +258,17 @@ export class BreadcrumbEllipsisProvider {
private constructor(opts: BreadcrumbEllipsisOpts) { private constructor(opts: BreadcrumbEllipsisOpts) {
this.opts = opts; this.opts = opts;
this.provider = BreadcrumbProvider.require(); this.provider = BreadcrumbProvider.require();
this.runtimePart = Soma.require() this.runtimePart = this.provider.runtime.part('ellipsis', {
.runtime(breadcrumbMorfo, { id: opts.id,
props: { ref: opts.ref,
ariaLabel: () => this.resolvedAriaLabel.current, props: {
interactive: () => this.isInteractive, ariaLabel: () => this.resolvedAriaLabel.current,
popupType: () => this.opts.popupType.current interactive: () => this.isInteractive,
} popupType: () => this.opts.popupType.current
}) },
.part('ellipsis', { id: opts.id, ref: opts.ref, owner: this, syncAttrs: true }); owner: this,
syncAttrs: true
});
} }
readonly isInteractive = $derived.by(() => this.opts.interactive.current); readonly isInteractive = $derived.by(() => this.opts.interactive.current);
@ -270,7 +276,7 @@ export class BreadcrumbEllipsisProvider {
readonly resolvedAriaLabel: Active<string | undefined> = readableActive( readonly resolvedAriaLabel: Active<string | undefined> = readableActive(
() => () =>
this.opts.ariaLabel.current || this.opts.ariaLabel.current ||
this.provider.soma?.langs.ts(BREADCRUMB_LANGS.ELLIPSIS) || this.provider.soma.langs.ts(BREADCRUMB_LANGS.ELLIPSIS) ||
undefined undefined
); );

@ -190,13 +190,15 @@ export class CheckboxIndicatorProvider {
private constructor(opts: CheckboxIndicatorOpts) { private constructor(opts: CheckboxIndicatorOpts) {
this.opts = opts; this.opts = opts;
this.provider = CheckboxProvider.require(); this.provider = CheckboxProvider.require();
this.runtimePart = Soma.require() this.runtimePart = this.provider.runtime.part('indicator', {
.runtime(checkboxMorfo, { id: opts.id,
states: { ref: opts.ref,
checked: () => this.provider.dataState states: {
} checked: () => this.provider.dataState
}) },
.part('indicator', { id: opts.id, ref: opts.ref, owner: this, syncAttrs: true }); owner: this,
syncAttrs: true
});
} }
get isVisible(): boolean { get isVisible(): boolean {
@ -232,10 +234,13 @@ export class CheckboxHiddenInputProvider {
private constructor(opts: CheckboxHiddenInputOpts) { private constructor(opts: CheckboxHiddenInputOpts) {
this.opts = opts; this.opts = opts;
this.runtimePart = Soma.require()
.runtime(checkboxMorfo, {})
.part('hidden-input', { id: opts.id, ref: opts.ref, owner: this, syncAttrs: true });
this.provider = CheckboxProvider.require(); this.provider = CheckboxProvider.require();
this.runtimePart = this.provider.runtime.part('hidden-input', {
id: opts.id,
ref: opts.ref,
owner: this,
syncAttrs: true
});
} }
readonly props = $derived.by(() => readonly props = $derived.by(() =>
@ -268,6 +273,8 @@ interface CheckboxGroupOpts
export class CheckboxGroupProvider { export class CheckboxGroupProvider {
readonly opts: CheckboxGroupOpts; readonly opts: CheckboxGroupOpts;
readonly runtimePart: SomaRuntimePart; readonly runtimePart: SomaRuntimePart;
readonly soma: Soma;
readonly runtime: SomaRuntime;
static readonly ctx = context<CheckboxGroupProvider>('CheckboxGroup'); static readonly ctx = context<CheckboxGroupProvider>('CheckboxGroup');
static get(): CheckboxGroupProvider | undefined { static get(): CheckboxGroupProvider | undefined {
return this.ctx.getOr(undefined) as CheckboxGroupProvider | undefined; return this.ctx.getOr(undefined) as CheckboxGroupProvider | undefined;
@ -284,23 +291,23 @@ export class CheckboxGroupProvider {
private constructor(opts: CheckboxGroupOpts) { private constructor(opts: CheckboxGroupOpts) {
this.opts = opts; this.opts = opts;
this.runtimePart = Soma.require() this.soma = Soma.require();
.runtime(checkboxMorfo, { this.runtime = this.soma.runtime(checkboxMorfo, {
props: { props: {
disabled: () => this.opts.disabled.current, disabled: () => this.opts.disabled.current,
orientation: () => this.opts.orientation.current orientation: () => this.opts.orientation.current
}, },
parts: { parts: {
'group-label': () => this.labelId.current || undefined 'group-label': () => this.labelId.current || undefined
} }
}) });
.part('group', { this.runtimePart = this.runtime.part('group', {
id: opts.id, id: opts.id,
ref: opts.ref, ref: opts.ref,
owner: this, owner: this,
context: CheckboxGroupProvider.ctx, context: CheckboxGroupProvider.ctx,
syncAttrs: true syncAttrs: true
}); });
} }
isItemChecked(value: string): boolean { isItemChecked(value: string): boolean {
@ -335,21 +342,24 @@ interface CheckboxGroupLabelOpts extends WithRefOpts {}
export class CheckboxGroupLabelProvider { export class CheckboxGroupLabelProvider {
readonly opts: CheckboxGroupLabelOpts; readonly opts: CheckboxGroupLabelOpts;
readonly runtimePart: SomaRuntimePart; readonly runtimePart: SomaRuntimePart;
readonly group: CheckboxGroupProvider;
static create(opts: CheckboxGroupLabelOpts) { static create(opts: CheckboxGroupLabelOpts) {
return new CheckboxGroupLabelProvider(opts); return new CheckboxGroupLabelProvider(opts);
} }
private constructor(opts: CheckboxGroupLabelOpts) { private constructor(opts: CheckboxGroupLabelOpts) {
this.opts = opts; this.opts = opts;
this.runtimePart = Soma.require() this.group = CheckboxGroupProvider.require();
.runtime(checkboxMorfo, {}) this.runtimePart = this.group.runtime.part('group-label', {
.part('group-label', { id: opts.id, ref: opts.ref, owner: this }); id: opts.id,
const group = CheckboxGroupProvider.get() as CheckboxGroupProvider | undefined; ref: opts.ref,
owner: this
});
// A30: register label id with direct assignment in the constructor. // A30: register label id with direct assignment in the constructor.
// Ids are stable (generated once by `createId`) — wrapping this write // Ids are stable (generated once by `createId`) — wrapping this write
// in `$effect` would create an unneeded child→parent reactive edge. // in `$effect` would create an unneeded child→parent reactive edge.
if (group) group.setLabelId(opts.id.current); this.group.setLabelId(opts.id.current);
} }
readonly props = $derived.by(() => readonly props = $derived.by(() =>

@ -4,7 +4,7 @@ import type { OnChangeFn, SomaMouseEvent } from '../../types';
import { Soma } from '../../core/soma.svelte'; import { Soma } from '../../core/soma.svelte';
import { CLIPBOARD_LANGS } from './langs'; import { CLIPBOARD_LANGS } from './langs';
import { clipboardMorfo } from '../../../morfo/components/clipboard'; import { clipboardMorfo } from '../../../morfo/components/clipboard';
import type { SomaRuntimePart } from '../../runtime.svelte'; import type { SomaRuntime, SomaRuntimePart } from '../../runtime.svelte';
// ── Root provider ────────────────────────────────────────────────────────── // ── Root provider ──────────────────────────────────────────────────────────
@ -22,6 +22,8 @@ interface ClipboardOpts
export class ClipboardProvider { export class ClipboardProvider {
readonly opts: ClipboardOpts; readonly opts: ClipboardOpts;
readonly runtimePart: SomaRuntimePart; readonly runtimePart: SomaRuntimePart;
readonly soma: Soma;
readonly runtime: SomaRuntime;
static readonly ctx = context<ClipboardProvider>('Clipboard'); static readonly ctx = context<ClipboardProvider>('Clipboard');
static get(): ClipboardProvider | undefined { static get(): ClipboardProvider | undefined {
return this.ctx.getOr(undefined) as ClipboardProvider | undefined; return this.ctx.getOr(undefined) as ClipboardProvider | undefined;
@ -34,26 +36,24 @@ export class ClipboardProvider {
return new ClipboardProvider(opts); return new ClipboardProvider(opts);
} }
readonly soma = Soma.get();
copied = $state(false); copied = $state(false);
private resetTimer: ReturnType<typeof setTimeout> | null = null; private resetTimer: ReturnType<typeof setTimeout> | null = null;
private constructor(opts: ClipboardOpts) { private constructor(opts: ClipboardOpts) {
this.opts = opts; this.opts = opts;
this.runtimePart = Soma.require() this.soma = Soma.require();
.runtime(clipboardMorfo, { this.runtime = this.soma.runtime(clipboardMorfo, {
props: { props: {
copied: () => this.copied copied: () => this.copied
} }
}) });
.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: ClipboardProvider.ctx, context: ClipboardProvider.ctx,
syncAttrs: true syncAttrs: true
}); });
$effect(() => { $effect(() => {
return () => { return () => {
@ -120,14 +120,16 @@ export class ClipboardTriggerProvider {
private constructor(opts: ClipboardTriggerOpts) { private constructor(opts: ClipboardTriggerOpts) {
this.opts = opts; this.opts = opts;
this.provider = ClipboardProvider.require(); this.provider = ClipboardProvider.require();
this.runtimePart = Soma.require() this.runtimePart = this.provider.runtime.part('trigger', {
.runtime(clipboardMorfo, { id: opts.id,
props: { ref: opts.ref,
copied: () => this.provider.copied, props: {
ariaLabel: () => this.resolvedAriaLabel.current copied: () => this.provider.copied,
} ariaLabel: () => this.resolvedAriaLabel.current
}) },
.part('trigger', { id: opts.id, ref: opts.ref, owner: this, syncAttrs: true }); owner: this,
syncAttrs: true
});
} }
readonly resolvedAriaLabel: Active<string | undefined> = readableActive(() => { readonly resolvedAriaLabel: Active<string | undefined> = readableActive(() => {
@ -135,8 +137,8 @@ export class ClipboardTriggerProvider {
if (override) return override; if (override) return override;
// When copied, announce the copied state; otherwise the copy action. // When copied, announce the copied state; otherwise the copy action.
return this.provider.copied return this.provider.copied
? this.provider.soma?.langs.ts(CLIPBOARD_LANGS.COPIED) ? this.provider.soma.langs.ts(CLIPBOARD_LANGS.COPIED)
: this.provider.soma?.langs.ts(CLIPBOARD_LANGS.COPY); : this.provider.soma.langs.ts(CLIPBOARD_LANGS.COPY);
}); });
readonly onclick = (_e: SomaMouseEvent<HTMLButtonElement>) => { readonly onclick = (_e: SomaMouseEvent<HTMLButtonElement>) => {
@ -170,13 +172,15 @@ export class ClipboardIndicatorProvider {
private constructor(opts: ClipboardIndicatorOpts) { private constructor(opts: ClipboardIndicatorOpts) {
this.opts = opts; this.opts = opts;
this.provider = ClipboardProvider.require(); this.provider = ClipboardProvider.require();
this.runtimePart = Soma.require() this.runtimePart = this.provider.runtime.part('indicator', {
.runtime(clipboardMorfo, { id: opts.id,
props: { ref: opts.ref,
copied: () => this.provider.copied props: {
} copied: () => this.provider.copied
}) },
.part('indicator', { id: opts.id, ref: opts.ref, owner: this, syncAttrs: true }); owner: this,
syncAttrs: true
});
} }
readonly isPresent = $derived.by(() => this.provider.copied); readonly isPresent = $derived.by(() => this.provider.copied);

@ -5,7 +5,7 @@ import { METER_LANGS } from './langs';
import type { MeterZone } from './types'; import type { MeterZone } from './types';
import { meterMorfo } from '../../../morfo/components/meter'; import { meterMorfo } from '../../../morfo/components/meter';
import type { SomaRuntimePart } from '../../runtime.svelte'; import type { SomaRuntime, SomaRuntimePart } from '../../runtime.svelte';
function computeZone(value: number, low: number, high: number): MeterZone { function computeZone(value: number, low: number, high: number): MeterZone {
@ -34,6 +34,8 @@ interface MeterOpts
export class MeterProvider { export class MeterProvider {
readonly opts: MeterOpts; readonly opts: MeterOpts;
readonly runtimePart: SomaRuntimePart; readonly runtimePart: SomaRuntimePart;
readonly soma: Soma;
readonly runtime: SomaRuntime;
static readonly ctx = context<MeterProvider>('Meter'); static readonly ctx = context<MeterProvider>('Meter');
static get(): MeterProvider | undefined { static get(): MeterProvider | undefined {
return this.ctx.getOr(undefined) as MeterProvider | undefined; return this.ctx.getOr(undefined) as MeterProvider | undefined;
@ -46,31 +48,29 @@ export class MeterProvider {
return new MeterProvider(opts); return new MeterProvider(opts);
} }
readonly soma = Soma.get();
private constructor(opts: MeterOpts) { private constructor(opts: MeterOpts) {
this.opts = opts; this.opts = opts;
this.runtimePart = Soma.require() this.soma = Soma.require();
.runtime(meterMorfo, { this.runtime = this.soma.runtime(meterMorfo, {
states: { states: {
optimum: () => this.state optimum: () => this.state
}, },
props: { props: {
min: () => this.opts.min.current, min: () => this.opts.min.current,
max: () => this.opts.max.current, max: () => this.opts.max.current,
value: () => this.opts.value.current, value: () => this.opts.value.current,
valueText: () => this.resolvedValueText, valueText: () => this.resolvedValueText,
ariaLabel: () => this.resolvedAriaLabel.current, ariaLabel: () => this.resolvedAriaLabel.current,
ariaLabelledby: () => this.opts.ariaLabelledby.current ariaLabelledby: () => this.opts.ariaLabelledby.current
} }
}) });
.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: MeterProvider.ctx, context: MeterProvider.ctx,
syncAttrs: true syncAttrs: true
}); });
} }
readonly resolvedLow = $derived.by(() => this.opts.low.current ?? this.opts.min.current); readonly resolvedLow = $derived.by(() => this.opts.low.current ?? this.opts.min.current);
@ -94,7 +94,7 @@ export class MeterProvider {
readonly resolvedAriaLabel: Active<string | undefined> = readableActive(() => { readonly resolvedAriaLabel: Active<string | undefined> = readableActive(() => {
if (this.opts.ariaLabelledby.current) return undefined; if (this.opts.ariaLabelledby.current) return undefined;
return this.opts.ariaLabel.current || this.soma?.langs.ts(METER_LANGS.LABEL) || undefined; return this.opts.ariaLabel.current || this.soma.langs.ts(METER_LANGS.LABEL) || undefined;
}); });
readonly resolvedValueText = $derived.by(() => { readonly resolvedValueText = $derived.by(() => {
@ -129,13 +129,15 @@ export class MeterIndicatorProvider {
private constructor(opts: MeterIndicatorOpts) { private constructor(opts: MeterIndicatorOpts) {
this.opts = opts; this.opts = opts;
this.provider = MeterProvider.require(); this.provider = MeterProvider.require();
this.runtimePart = Soma.require() this.runtimePart = this.provider.runtime.part('indicator', {
.runtime(meterMorfo, { id: opts.id,
states: { ref: opts.ref,
optimum: () => this.provider.state states: {
} optimum: () => this.provider.state
}) },
.part('indicator', { id: opts.id, ref: opts.ref, owner: this, syncAttrs: true }); owner: this,
syncAttrs: true
});
} }
readonly props = $derived.by(() => readonly props = $derived.by(() =>

@ -5,7 +5,7 @@ import { Soma } from '../../core/soma.svelte';
import { PAGINATION_LANGS } from './langs'; import { PAGINATION_LANGS } from './langs';
import { paginationMorfo } from '../../../morfo/components/pagination'; import { paginationMorfo } from '../../../morfo/components/pagination';
import type { SomaRuntimePart } from '../../runtime.svelte'; import type { SomaRuntime, SomaRuntimePart } from '../../runtime.svelte';
// ── Page range algorithm ───────────────────────────────────────────────────── // ── Page range algorithm ─────────────────────────────────────────────────────
@ -91,6 +91,8 @@ interface PaginationOpts
export class PaginationProvider { export class PaginationProvider {
readonly opts: PaginationOpts; readonly opts: PaginationOpts;
readonly runtimePart: SomaRuntimePart; readonly runtimePart: SomaRuntimePart;
readonly soma: Soma;
readonly runtime: SomaRuntime;
static readonly ctx = context<PaginationProvider>('Pagination'); static readonly ctx = context<PaginationProvider>('Pagination');
static get(): PaginationProvider | undefined { static get(): PaginationProvider | undefined {
return this.ctx.getOr(undefined) as PaginationProvider | undefined; return this.ctx.getOr(undefined) as PaginationProvider | undefined;
@ -103,23 +105,21 @@ export class PaginationProvider {
return new PaginationProvider(opts); return new PaginationProvider(opts);
} }
readonly soma = Soma.get();
private constructor(opts: PaginationOpts) { private constructor(opts: PaginationOpts) {
this.opts = opts; this.opts = opts;
this.runtimePart = Soma.require() this.soma = Soma.require();
.runtime(paginationMorfo, { this.runtime = this.soma.runtime(paginationMorfo, {
props: { props: {
disabled: () => this.opts.disabled.current disabled: () => this.opts.disabled.current
} }
}) });
.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: PaginationProvider.ctx, context: PaginationProvider.ctx,
syncAttrs: true syncAttrs: true
}); });
// Clamp page to valid range reactively (like Ark-UI/Zag.js) // Clamp page to valid range reactively (like Ark-UI/Zag.js)
$effect(() => { $effect(() => {
@ -217,13 +217,15 @@ export class PaginationPrevTriggerProvider {
private constructor(opts: PaginationPrevTriggerOpts) { private constructor(opts: PaginationPrevTriggerOpts) {
this.opts = opts; this.opts = opts;
this.provider = PaginationProvider.require(); this.provider = PaginationProvider.require();
this.runtimePart = Soma.require() this.runtimePart = this.provider.runtime.part('prev-trigger', {
.runtime(paginationMorfo, { id: opts.id,
props: { ref: opts.ref,
disabled: () => this.isDisabled props: {
} disabled: () => this.isDisabled
}) },
.part('prev-trigger', { id: opts.id, ref: opts.ref, owner: this, syncAttrs: true }); owner: this,
syncAttrs: true
});
} }
readonly isDisabled = $derived.by( readonly isDisabled = $derived.by(
@ -268,13 +270,15 @@ export class PaginationNextTriggerProvider {
private constructor(opts: PaginationNextTriggerOpts) { private constructor(opts: PaginationNextTriggerOpts) {
this.opts = opts; this.opts = opts;
this.provider = PaginationProvider.require(); this.provider = PaginationProvider.require();
this.runtimePart = Soma.require() this.runtimePart = this.provider.runtime.part('next-trigger', {
.runtime(paginationMorfo, { id: opts.id,
props: { ref: opts.ref,
disabled: () => this.isDisabled props: {
} disabled: () => this.isDisabled
}) },
.part('next-trigger', { id: opts.id, ref: opts.ref, owner: this, syncAttrs: true }); owner: this,
syncAttrs: true
});
} }
readonly isDisabled = $derived.by( readonly isDisabled = $derived.by(
@ -319,16 +323,18 @@ export class PaginationItemProvider {
private constructor(opts: PaginationItemOpts) { private constructor(opts: PaginationItemOpts) {
this.opts = opts; this.opts = opts;
this.provider = PaginationProvider.require(); this.provider = PaginationProvider.require();
this.runtimePart = Soma.require() this.runtimePart = this.provider.runtime.part('item', {
.runtime(paginationMorfo, { id: opts.id,
props: { ref: opts.ref,
selected: () => this.isSelected, props: {
disabled: () => this.isDisabled, selected: () => this.isSelected,
value: () => this.opts.value.current, disabled: () => this.isDisabled,
ariaLabel: () => this.ariaLabel value: () => this.opts.value.current,
} ariaLabel: () => this.ariaLabel
}) },
.part('item', { id: opts.id, ref: opts.ref, owner: this, syncAttrs: true }); owner: this,
syncAttrs: true
});
} }
readonly isSelected = $derived.by( readonly isSelected = $derived.by(
@ -339,7 +345,7 @@ export class PaginationItemProvider {
readonly ariaLabel = $derived.by( readonly ariaLabel = $derived.by(
() => () =>
this.provider.soma?.langs.t(PAGINATION_LANGS.PAGE, { this.provider.soma.langs.t(PAGINATION_LANGS.PAGE, {
value: String(this.opts.value.current) value: String(this.opts.value.current)
}) || `Page ${this.opts.value.current}` }) || `Page ${this.opts.value.current}`
); );
@ -370,19 +376,23 @@ interface PaginationEllipsisOpts
export class PaginationEllipsisProvider { export class PaginationEllipsisProvider {
readonly opts: PaginationEllipsisOpts; readonly opts: PaginationEllipsisOpts;
readonly runtimePart: SomaRuntimePart; readonly runtimePart: SomaRuntimePart;
readonly provider: PaginationProvider;
static create(opts: PaginationEllipsisOpts) { static create(opts: PaginationEllipsisOpts) {
return new PaginationEllipsisProvider(opts); return new PaginationEllipsisProvider(opts);
} }
private constructor(opts: PaginationEllipsisOpts) { private constructor(opts: PaginationEllipsisOpts) {
this.opts = opts; this.opts = opts;
this.runtimePart = Soma.require() this.provider = PaginationProvider.require();
.runtime(paginationMorfo, { this.runtimePart = this.provider.runtime.part('ellipsis', {
props: { id: opts.id,
position: () => this.opts.position.current ref: opts.ref,
} props: {
}) position: () => this.opts.position.current
.part('ellipsis', { id: opts.id, ref: opts.ref, owner: this, syncAttrs: true }); },
owner: this,
syncAttrs: true
});
} }
readonly props = $derived.by(() => readonly props = $derived.by(() =>

@ -5,7 +5,7 @@ import { PROGRESS_LANGS } from './langs';
import type { ProgressState } from './types'; import type { ProgressState } from './types';
import { progressMorfo } from '../../../morfo/components/progress'; import { progressMorfo } from '../../../morfo/components/progress';
import type { SomaRuntimePart } from '../../runtime.svelte'; import type { SomaRuntime, SomaRuntimePart } from '../../runtime.svelte';
function computeState(value: number | null, min: number, max: number): ProgressState { function computeState(value: number | null, min: number, max: number): ProgressState {
@ -32,6 +32,8 @@ interface ProgressOpts
export class ProgressProvider { export class ProgressProvider {
readonly opts: ProgressOpts; readonly opts: ProgressOpts;
readonly runtimePart: SomaRuntimePart; readonly runtimePart: SomaRuntimePart;
readonly soma: Soma;
readonly runtime: SomaRuntime;
static readonly ctx = context<ProgressProvider>('Progress'); static readonly ctx = context<ProgressProvider>('Progress');
static get(): ProgressProvider | undefined { static get(): ProgressProvider | undefined {
return this.ctx.getOr(undefined) as ProgressProvider | undefined; return this.ctx.getOr(undefined) as ProgressProvider | undefined;
@ -44,31 +46,29 @@ export class ProgressProvider {
return new ProgressProvider(opts); return new ProgressProvider(opts);
} }
readonly soma = Soma.get();
private constructor(opts: ProgressOpts) { private constructor(opts: ProgressOpts) {
this.opts = opts; this.opts = opts;
this.runtimePart = Soma.require() this.soma = Soma.require();
.runtime(progressMorfo, { this.runtime = this.soma.runtime(progressMorfo, {
states: { states: {
loading: () => this.state loading: () => this.state
}, },
props: { props: {
min: () => this.opts.min.current, min: () => this.opts.min.current,
max: () => this.opts.max.current, max: () => this.opts.max.current,
value: () => this.opts.value.current ?? undefined, value: () => this.opts.value.current ?? undefined,
valueText: () => this.resolvedValueText, valueText: () => this.resolvedValueText,
ariaLabel: () => this.resolvedAriaLabel.current, ariaLabel: () => this.resolvedAriaLabel.current,
ariaLabelledby: () => this.opts.ariaLabelledby.current ariaLabelledby: () => this.opts.ariaLabelledby.current
} }
}) });
.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: ProgressProvider.ctx, context: ProgressProvider.ctx,
syncAttrs: true syncAttrs: true
}); });
} }
readonly state: ReadonlyState = $derived.by(() => { readonly state: ReadonlyState = $derived.by(() => {
@ -88,7 +88,7 @@ export class ProgressProvider {
readonly resolvedAriaLabel: Active<string | undefined> = readableActive(() => { readonly resolvedAriaLabel: Active<string | undefined> = readableActive(() => {
if (this.opts.ariaLabelledby.current) return undefined; if (this.opts.ariaLabelledby.current) return undefined;
return this.opts.ariaLabel.current || this.soma?.langs.ts(PROGRESS_LANGS.LABEL) || undefined; return this.opts.ariaLabel.current || this.soma.langs.ts(PROGRESS_LANGS.LABEL) || undefined;
}); });
readonly resolvedValueText = $derived.by(() => { readonly resolvedValueText = $derived.by(() => {
@ -127,13 +127,15 @@ export class ProgressIndicatorProvider {
private constructor(opts: ProgressIndicatorOpts) { private constructor(opts: ProgressIndicatorOpts) {
this.opts = opts; this.opts = opts;
this.provider = ProgressProvider.require(); this.provider = ProgressProvider.require();
this.runtimePart = Soma.require() this.runtimePart = this.provider.runtime.part('indicator', {
.runtime(progressMorfo, { id: opts.id,
states: { ref: opts.ref,
loading: () => this.provider.state states: {
} loading: () => this.provider.state
}) },
.part('indicator', { id: opts.id, ref: opts.ref, owner: this, syncAttrs: true }); owner: this,
syncAttrs: true
});
} }
readonly props = $derived.by(() => readonly props = $derived.by(() =>

Loading…
Cancel
Save

Powered by TurnKey Linux.