From a60d0402827c0cc23634c0f687916c283fa595ca Mon Sep 17 00:00:00 2001 From: dev Date: Fri, 15 May 2026 02:12:26 +0200 Subject: [PATCH] Consolidate Soma provider runtimes --- .../accordion/accordion-provider.svelte.ts | 129 +++++++++--------- .../breadcrumb/breadcrumb-provider.svelte.ts | 114 ++++++++-------- .../checkbox/checkbox-provider.svelte.ts | 74 +++++----- .../clipboard/clipboard-provider.svelte.ts | 70 +++++----- .../components/meter/meter-provider.svelte.ts | 66 ++++----- .../pagination/pagination-provider.svelte.ts | 106 +++++++------- .../progress/progress-provider.svelte.ts | 66 ++++----- 7 files changed, 330 insertions(+), 295 deletions(-) diff --git a/src/uix/soma/components/accordion/accordion-provider.svelte.ts b/src/uix/soma/components/accordion/accordion-provider.svelte.ts index b7946f937..e99404099 100644 --- a/src/uix/soma/components/accordion/accordion-provider.svelte.ts +++ b/src/uix/soma/components/accordion/accordion-provider.svelte.ts @@ -35,6 +35,7 @@ export class AccordionProvider { readonly opts: AccordionOpts; readonly runtimePart: SomaRuntimePart; readonly soma = Soma.require(); + readonly runtime: SomaRuntime; static readonly ctx = context('Accordion'); static get(): AccordionProvider | undefined { return this.ctx.getOr(undefined) as AccordionProvider | undefined; @@ -51,20 +52,19 @@ export class AccordionProvider { private constructor(opts: AccordionOpts) { this.opts = opts; - this.runtimePart = this.soma - .runtime(accordionMorfo, { - props: { - orientation: () => this.opts.orientation.current, - disabled: () => this.opts.disabled.current - } - }) - .part('provider', { - id: opts.id, - ref: opts.ref, - owner: this, - context: AccordionProvider.ctx, - syncAttrs: true - }); + this.runtime = this.soma.runtime(accordionMorfo, { + props: { + orientation: () => this.opts.orientation.current, + disabled: () => this.opts.disabled.current + } + }); + this.runtimePart = this.runtime.part('provider', { + id: opts.id, + ref: opts.ref, + owner: this, + context: AccordionProvider.ctx, + syncAttrs: true + }); $effect(() => { if (this.opts.type.current !== 'single') return; @@ -177,7 +177,7 @@ export class AccordionItemProvider { private constructor(opts: AccordionItemOpts) { this.opts = opts; this.provider = AccordionProvider.require(); - this.runtime = Soma.require().runtime(accordionMorfo, { + this.runtime = this.provider.soma.runtime(accordionMorfo, { states: { open: () => this.isOpen }, @@ -255,18 +255,20 @@ export class AccordionHeaderProvider { private constructor(opts: AccordionHeaderOpts) { this.opts = opts; this.item = AccordionItemProvider.require(); - this.runtimePart = Soma.require() - .runtime(accordionMorfo, { - states: { - open: () => this.item.isOpen - }, - props: { - level: () => Math.max(1, Math.min(6, this.opts.level.current)), - disabled: () => this.item.isDisabled, - orientation: () => this.item.provider.opts.orientation.current - } - }) - .part('header', { id: opts.id, ref: opts.ref, owner: this, syncAttrs: true }); + this.runtimePart = this.item.runtime.part('header', { + id: opts.id, + ref: opts.ref, + states: { + open: () => this.item.isOpen + }, + props: { + level: () => Math.max(1, Math.min(6, this.opts.level.current)), + disabled: () => this.item.isDisabled, + orientation: () => this.item.provider.opts.orientation.current + }, + owner: this, + syncAttrs: true + }); } readonly props = $derived.by(() => @@ -292,20 +294,22 @@ export class AccordionTriggerProvider { private constructor(opts: AccordionTriggerOpts) { this.opts = opts; this.item = AccordionItemProvider.require(); - this.runtimePart = Soma.require() - .runtime(accordionMorfo, { - states: { - open: () => this.item.isOpen - }, - props: { - disabled: () => this.item.isDisabled, - orientation: () => this.item.provider.opts.orientation.current - }, - parts: { - content: () => this.item.contentId.current || undefined - } - }) - .part('trigger', { id: opts.id, ref: opts.ref, owner: this, syncAttrs: true }); + this.runtimePart = this.item.runtime.part('trigger', { + id: opts.id, + ref: opts.ref, + states: { + open: () => this.item.isOpen + }, + props: { + disabled: () => this.item.isDisabled, + orientation: () => this.item.provider.opts.orientation.current + }, + parts: { + content: () => this.item.contentId.current || undefined + }, + owner: this, + syncAttrs: true + }); this.item.triggerId.current = opts.id.current; } @@ -377,29 +381,26 @@ export class AccordionContentProvider { private constructor(opts: AccordionContentOpts) { this.opts = opts; this.item = AccordionItemProvider.require(); - this.runtimePart = Soma.require() - .runtime(accordionMorfo, { - states: { - open: () => this.item.isOpen - }, - props: { - disabled: () => this.item.isDisabled, - orientation: () => this.item.provider.opts.orientation.current, - region: () => this.item.provider.opts.region.current - }, - parts: { - trigger: () => this.item.triggerId.current || undefined - } - }) - .part('content', { - id: opts.id, - ref: opts.ref, - owner: this, - onRefChange: (el) => { - this.item.setContentRef(el); - }, - syncAttrs: true - }); + this.runtimePart = this.item.runtime.part('content', { + id: opts.id, + ref: opts.ref, + states: { + open: () => this.item.isOpen + }, + props: { + disabled: () => this.item.isDisabled, + orientation: () => this.item.provider.opts.orientation.current, + region: () => this.item.provider.opts.region.current + }, + parts: { + trigger: () => this.item.triggerId.current || undefined + }, + owner: this, + onRefChange: (el) => { + this.item.setContentRef(el); + }, + syncAttrs: true + }); this.item.contentId.current = opts.id.current; } diff --git a/src/uix/soma/components/breadcrumb/breadcrumb-provider.svelte.ts b/src/uix/soma/components/breadcrumb/breadcrumb-provider.svelte.ts index 8a020682c..5cefed0b5 100644 --- a/src/uix/soma/components/breadcrumb/breadcrumb-provider.svelte.ts +++ b/src/uix/soma/components/breadcrumb/breadcrumb-provider.svelte.ts @@ -5,7 +5,7 @@ import { BREADCRUMB_LANGS } from './langs'; import type { BreadcrumbCurrent } from './types'; import { breadcrumbMorfo } from '../../../morfo/components/breadcrumb'; -import type { SomaRuntimePart } from '../../runtime.svelte'; +import type { SomaRuntime, SomaRuntimePart } from '../../runtime.svelte'; // ── Root ──────────────────────────────────────────────────────────────────── @@ -21,6 +21,8 @@ interface BreadcrumbOpts export class BreadcrumbProvider { readonly opts: BreadcrumbOpts; readonly runtimePart: SomaRuntimePart; + readonly soma: Soma; + readonly runtime: SomaRuntime; static readonly ctx = context('Breadcrumb'); static get(): BreadcrumbProvider | undefined { return this.ctx.getOr(undefined) as BreadcrumbProvider | undefined; @@ -33,30 +35,28 @@ export class BreadcrumbProvider { return new BreadcrumbProvider(opts); } - readonly soma = Soma.get(); - private constructor(opts: BreadcrumbOpts) { this.opts = opts; - this.runtimePart = Soma.require() - .runtime(breadcrumbMorfo, { - props: { - ariaLabel: () => this.resolvedAriaLabel.current, - ariaLabelledby: () => this.opts.ariaLabelledby.current - } - }) - .part('provider', { - id: opts.id, - ref: opts.ref, - owner: this, - context: BreadcrumbProvider.ctx, - syncAttrs: true - }); + this.soma = Soma.require(); + this.runtime = this.soma.runtime(breadcrumbMorfo, { + props: { + ariaLabel: () => this.resolvedAriaLabel.current, + ariaLabelledby: () => this.opts.ariaLabelledby.current + } + }); + this.runtimePart = this.runtime.part('provider', { + id: opts.id, + ref: opts.ref, + owner: this, + context: BreadcrumbProvider.ctx, + syncAttrs: true + }); } readonly resolvedAriaLabel: Active = readableActive(() => this.opts.ariaLabelledby.current ? 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(() => @@ -81,10 +81,12 @@ export class BreadcrumbListProvider { private constructor(opts: BreadcrumbListOpts) { this.opts = opts; - this.runtimePart = Soma.require() - .runtime(breadcrumbMorfo, {}) - .part('list', { id: opts.id, ref: opts.ref, owner: this }); this.provider = BreadcrumbProvider.require(); + this.runtimePart = this.provider.runtime.part('list', { + id: opts.id, + ref: opts.ref, + owner: this + }); } readonly props = $derived.by(() => @@ -117,20 +119,17 @@ export class BreadcrumbItemProvider { private constructor(opts: BreadcrumbItemOpts) { 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.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(() => @@ -157,14 +156,16 @@ export class BreadcrumbLinkProvider { private constructor(opts: BreadcrumbLinkOpts) { 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.runtimePart = this.provider.runtime.part('link', { + id: opts.id, + ref: opts.ref, + props: { + current: () => this.ariaCurrent + }, + owner: this, + syncAttrs: true + }); this.item = BreadcrumbItemProvider.get(); // Mirror current state onto the parent Item (so `[data-breadcrumb-item]` @@ -216,10 +217,13 @@ export class BreadcrumbSeparatorProvider { private constructor(opts: BreadcrumbSeparatorOpts) { 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.runtimePart = this.provider.runtime.part('separator', { + id: opts.id, + ref: opts.ref, + owner: this, + syncAttrs: true + }); } readonly props = $derived.by(() => @@ -254,15 +258,17 @@ export class BreadcrumbEllipsisProvider { private constructor(opts: BreadcrumbEllipsisOpts) { this.opts = opts; this.provider = BreadcrumbProvider.require(); - this.runtimePart = Soma.require() - .runtime(breadcrumbMorfo, { - props: { - ariaLabel: () => this.resolvedAriaLabel.current, - interactive: () => this.isInteractive, - popupType: () => this.opts.popupType.current - } - }) - .part('ellipsis', { id: opts.id, ref: opts.ref, owner: this, syncAttrs: true }); + this.runtimePart = this.provider.runtime.part('ellipsis', { + id: opts.id, + ref: opts.ref, + props: { + ariaLabel: () => this.resolvedAriaLabel.current, + interactive: () => this.isInteractive, + popupType: () => this.opts.popupType.current + }, + owner: this, + syncAttrs: true + }); } readonly isInteractive = $derived.by(() => this.opts.interactive.current); @@ -270,7 +276,7 @@ export class BreadcrumbEllipsisProvider { readonly resolvedAriaLabel: Active = readableActive( () => this.opts.ariaLabel.current || - this.provider.soma?.langs.ts(BREADCRUMB_LANGS.ELLIPSIS) || + this.provider.soma.langs.ts(BREADCRUMB_LANGS.ELLIPSIS) || undefined ); diff --git a/src/uix/soma/components/checkbox/checkbox-provider.svelte.ts b/src/uix/soma/components/checkbox/checkbox-provider.svelte.ts index a69dd56c3..70073b630 100644 --- a/src/uix/soma/components/checkbox/checkbox-provider.svelte.ts +++ b/src/uix/soma/components/checkbox/checkbox-provider.svelte.ts @@ -190,13 +190,15 @@ export class CheckboxIndicatorProvider { private constructor(opts: CheckboxIndicatorOpts) { this.opts = opts; this.provider = CheckboxProvider.require(); - this.runtimePart = Soma.require() - .runtime(checkboxMorfo, { - states: { - checked: () => this.provider.dataState - } - }) - .part('indicator', { id: opts.id, ref: opts.ref, owner: this, syncAttrs: true }); + this.runtimePart = this.provider.runtime.part('indicator', { + id: opts.id, + ref: opts.ref, + states: { + checked: () => this.provider.dataState + }, + owner: this, + syncAttrs: true + }); } get isVisible(): boolean { @@ -232,10 +234,13 @@ export class CheckboxHiddenInputProvider { private constructor(opts: CheckboxHiddenInputOpts) { 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.runtimePart = this.provider.runtime.part('hidden-input', { + id: opts.id, + ref: opts.ref, + owner: this, + syncAttrs: true + }); } readonly props = $derived.by(() => @@ -268,6 +273,8 @@ interface CheckboxGroupOpts export class CheckboxGroupProvider { readonly opts: CheckboxGroupOpts; readonly runtimePart: SomaRuntimePart; + readonly soma: Soma; + readonly runtime: SomaRuntime; static readonly ctx = context('CheckboxGroup'); static get(): CheckboxGroupProvider | undefined { return this.ctx.getOr(undefined) as CheckboxGroupProvider | undefined; @@ -284,23 +291,23 @@ export class CheckboxGroupProvider { private constructor(opts: CheckboxGroupOpts) { this.opts = opts; - this.runtimePart = Soma.require() - .runtime(checkboxMorfo, { - props: { - disabled: () => this.opts.disabled.current, - orientation: () => this.opts.orientation.current - }, - parts: { - 'group-label': () => this.labelId.current || undefined - } - }) - .part('group', { - id: opts.id, - ref: opts.ref, - owner: this, - context: CheckboxGroupProvider.ctx, - syncAttrs: true - }); + this.soma = Soma.require(); + this.runtime = this.soma.runtime(checkboxMorfo, { + props: { + disabled: () => this.opts.disabled.current, + orientation: () => this.opts.orientation.current + }, + parts: { + 'group-label': () => this.labelId.current || undefined + } + }); + this.runtimePart = this.runtime.part('group', { + id: opts.id, + ref: opts.ref, + owner: this, + context: CheckboxGroupProvider.ctx, + syncAttrs: true + }); } isItemChecked(value: string): boolean { @@ -335,21 +342,24 @@ interface CheckboxGroupLabelOpts extends WithRefOpts {} export class CheckboxGroupLabelProvider { readonly opts: CheckboxGroupLabelOpts; readonly runtimePart: SomaRuntimePart; + readonly group: CheckboxGroupProvider; static create(opts: CheckboxGroupLabelOpts) { return new CheckboxGroupLabelProvider(opts); } private constructor(opts: CheckboxGroupLabelOpts) { this.opts = opts; - this.runtimePart = Soma.require() - .runtime(checkboxMorfo, {}) - .part('group-label', { id: opts.id, ref: opts.ref, owner: this }); - const group = CheckboxGroupProvider.get() as CheckboxGroupProvider | undefined; + this.group = CheckboxGroupProvider.require(); + this.runtimePart = this.group.runtime.part('group-label', { + id: opts.id, + ref: opts.ref, + owner: this + }); // A30: register label id with direct assignment in the constructor. // Ids are stable (generated once by `createId`) — wrapping this write // 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(() => diff --git a/src/uix/soma/components/clipboard/clipboard-provider.svelte.ts b/src/uix/soma/components/clipboard/clipboard-provider.svelte.ts index 8f3f1b742..a5fa2faaa 100644 --- a/src/uix/soma/components/clipboard/clipboard-provider.svelte.ts +++ b/src/uix/soma/components/clipboard/clipboard-provider.svelte.ts @@ -4,7 +4,7 @@ import type { OnChangeFn, SomaMouseEvent } from '../../types'; import { Soma } from '../../core/soma.svelte'; import { CLIPBOARD_LANGS } from './langs'; import { clipboardMorfo } from '../../../morfo/components/clipboard'; -import type { SomaRuntimePart } from '../../runtime.svelte'; +import type { SomaRuntime, SomaRuntimePart } from '../../runtime.svelte'; // ── Root provider ────────────────────────────────────────────────────────── @@ -22,6 +22,8 @@ interface ClipboardOpts export class ClipboardProvider { readonly opts: ClipboardOpts; readonly runtimePart: SomaRuntimePart; + readonly soma: Soma; + readonly runtime: SomaRuntime; static readonly ctx = context('Clipboard'); static get(): ClipboardProvider | undefined { return this.ctx.getOr(undefined) as ClipboardProvider | undefined; @@ -34,26 +36,24 @@ export class ClipboardProvider { return new ClipboardProvider(opts); } - readonly soma = Soma.get(); - copied = $state(false); private resetTimer: ReturnType | null = null; private constructor(opts: ClipboardOpts) { this.opts = opts; - this.runtimePart = Soma.require() - .runtime(clipboardMorfo, { - props: { - copied: () => this.copied - } - }) - .part('provider', { - id: opts.id, - ref: opts.ref, - owner: this, - context: ClipboardProvider.ctx, - syncAttrs: true - }); + this.soma = Soma.require(); + this.runtime = this.soma.runtime(clipboardMorfo, { + props: { + copied: () => this.copied + } + }); + this.runtimePart = this.runtime.part('provider', { + id: opts.id, + ref: opts.ref, + owner: this, + context: ClipboardProvider.ctx, + syncAttrs: true + }); $effect(() => { return () => { @@ -120,14 +120,16 @@ export class ClipboardTriggerProvider { private constructor(opts: ClipboardTriggerOpts) { this.opts = opts; this.provider = ClipboardProvider.require(); - this.runtimePart = Soma.require() - .runtime(clipboardMorfo, { - props: { - copied: () => this.provider.copied, - ariaLabel: () => this.resolvedAriaLabel.current - } - }) - .part('trigger', { id: opts.id, ref: opts.ref, owner: this, syncAttrs: true }); + this.runtimePart = this.provider.runtime.part('trigger', { + id: opts.id, + ref: opts.ref, + props: { + copied: () => this.provider.copied, + ariaLabel: () => this.resolvedAriaLabel.current + }, + owner: this, + syncAttrs: true + }); } readonly resolvedAriaLabel: Active = readableActive(() => { @@ -135,8 +137,8 @@ export class ClipboardTriggerProvider { if (override) return override; // When copied, announce the copied state; otherwise the copy action. return this.provider.copied - ? this.provider.soma?.langs.ts(CLIPBOARD_LANGS.COPIED) - : this.provider.soma?.langs.ts(CLIPBOARD_LANGS.COPY); + ? this.provider.soma.langs.ts(CLIPBOARD_LANGS.COPIED) + : this.provider.soma.langs.ts(CLIPBOARD_LANGS.COPY); }); readonly onclick = (_e: SomaMouseEvent) => { @@ -170,13 +172,15 @@ export class ClipboardIndicatorProvider { private constructor(opts: ClipboardIndicatorOpts) { this.opts = opts; this.provider = ClipboardProvider.require(); - this.runtimePart = Soma.require() - .runtime(clipboardMorfo, { - props: { - copied: () => this.provider.copied - } - }) - .part('indicator', { id: opts.id, ref: opts.ref, owner: this, syncAttrs: true }); + this.runtimePart = this.provider.runtime.part('indicator', { + id: opts.id, + ref: opts.ref, + props: { + copied: () => this.provider.copied + }, + owner: this, + syncAttrs: true + }); } readonly isPresent = $derived.by(() => this.provider.copied); diff --git a/src/uix/soma/components/meter/meter-provider.svelte.ts b/src/uix/soma/components/meter/meter-provider.svelte.ts index f62467085..abe3a3624 100644 --- a/src/uix/soma/components/meter/meter-provider.svelte.ts +++ b/src/uix/soma/components/meter/meter-provider.svelte.ts @@ -5,7 +5,7 @@ import { METER_LANGS } from './langs'; import type { MeterZone } from './types'; 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 { @@ -34,6 +34,8 @@ interface MeterOpts export class MeterProvider { readonly opts: MeterOpts; readonly runtimePart: SomaRuntimePart; + readonly soma: Soma; + readonly runtime: SomaRuntime; static readonly ctx = context('Meter'); static get(): MeterProvider | undefined { return this.ctx.getOr(undefined) as MeterProvider | undefined; @@ -46,31 +48,29 @@ export class MeterProvider { return new MeterProvider(opts); } - readonly soma = Soma.get(); - private constructor(opts: MeterOpts) { this.opts = opts; - this.runtimePart = Soma.require() - .runtime(meterMorfo, { - states: { - optimum: () => this.state - }, - props: { - min: () => this.opts.min.current, - max: () => this.opts.max.current, - value: () => this.opts.value.current, - valueText: () => this.resolvedValueText, - ariaLabel: () => this.resolvedAriaLabel.current, - ariaLabelledby: () => this.opts.ariaLabelledby.current - } - }) - .part('provider', { - id: opts.id, - ref: opts.ref, - owner: this, - context: MeterProvider.ctx, - syncAttrs: true - }); + this.soma = Soma.require(); + this.runtime = this.soma.runtime(meterMorfo, { + states: { + optimum: () => this.state + }, + props: { + min: () => this.opts.min.current, + max: () => this.opts.max.current, + value: () => this.opts.value.current, + valueText: () => this.resolvedValueText, + ariaLabel: () => this.resolvedAriaLabel.current, + ariaLabelledby: () => this.opts.ariaLabelledby.current + } + }); + this.runtimePart = this.runtime.part('provider', { + id: opts.id, + ref: opts.ref, + owner: this, + context: MeterProvider.ctx, + syncAttrs: true + }); } readonly resolvedLow = $derived.by(() => this.opts.low.current ?? this.opts.min.current); @@ -94,7 +94,7 @@ export class MeterProvider { readonly resolvedAriaLabel: Active = readableActive(() => { 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(() => { @@ -129,13 +129,15 @@ export class MeterIndicatorProvider { private constructor(opts: MeterIndicatorOpts) { this.opts = opts; this.provider = MeterProvider.require(); - this.runtimePart = Soma.require() - .runtime(meterMorfo, { - states: { - optimum: () => this.provider.state - } - }) - .part('indicator', { id: opts.id, ref: opts.ref, owner: this, syncAttrs: true }); + this.runtimePart = this.provider.runtime.part('indicator', { + id: opts.id, + ref: opts.ref, + states: { + optimum: () => this.provider.state + }, + owner: this, + syncAttrs: true + }); } readonly props = $derived.by(() => diff --git a/src/uix/soma/components/pagination/pagination-provider.svelte.ts b/src/uix/soma/components/pagination/pagination-provider.svelte.ts index 924bb3152..2021ccf5d 100644 --- a/src/uix/soma/components/pagination/pagination-provider.svelte.ts +++ b/src/uix/soma/components/pagination/pagination-provider.svelte.ts @@ -5,7 +5,7 @@ import { Soma } from '../../core/soma.svelte'; import { PAGINATION_LANGS } from './langs'; import { paginationMorfo } from '../../../morfo/components/pagination'; -import type { SomaRuntimePart } from '../../runtime.svelte'; +import type { SomaRuntime, SomaRuntimePart } from '../../runtime.svelte'; // ── Page range algorithm ───────────────────────────────────────────────────── @@ -91,6 +91,8 @@ interface PaginationOpts export class PaginationProvider { readonly opts: PaginationOpts; readonly runtimePart: SomaRuntimePart; + readonly soma: Soma; + readonly runtime: SomaRuntime; static readonly ctx = context('Pagination'); static get(): PaginationProvider | undefined { return this.ctx.getOr(undefined) as PaginationProvider | undefined; @@ -103,23 +105,21 @@ export class PaginationProvider { return new PaginationProvider(opts); } - readonly soma = Soma.get(); - private constructor(opts: PaginationOpts) { this.opts = opts; - this.runtimePart = Soma.require() - .runtime(paginationMorfo, { - props: { - disabled: () => this.opts.disabled.current - } - }) - .part('provider', { - id: opts.id, - ref: opts.ref, - owner: this, - context: PaginationProvider.ctx, - syncAttrs: true - }); + this.soma = Soma.require(); + this.runtime = this.soma.runtime(paginationMorfo, { + props: { + disabled: () => this.opts.disabled.current + } + }); + this.runtimePart = this.runtime.part('provider', { + id: opts.id, + ref: opts.ref, + owner: this, + context: PaginationProvider.ctx, + syncAttrs: true + }); // Clamp page to valid range reactively (like Ark-UI/Zag.js) $effect(() => { @@ -217,13 +217,15 @@ export class PaginationPrevTriggerProvider { private constructor(opts: PaginationPrevTriggerOpts) { this.opts = opts; this.provider = PaginationProvider.require(); - this.runtimePart = Soma.require() - .runtime(paginationMorfo, { - props: { - disabled: () => this.isDisabled - } - }) - .part('prev-trigger', { id: opts.id, ref: opts.ref, owner: this, syncAttrs: true }); + this.runtimePart = this.provider.runtime.part('prev-trigger', { + id: opts.id, + ref: opts.ref, + props: { + disabled: () => this.isDisabled + }, + owner: this, + syncAttrs: true + }); } readonly isDisabled = $derived.by( @@ -268,13 +270,15 @@ export class PaginationNextTriggerProvider { private constructor(opts: PaginationNextTriggerOpts) { this.opts = opts; this.provider = PaginationProvider.require(); - this.runtimePart = Soma.require() - .runtime(paginationMorfo, { - props: { - disabled: () => this.isDisabled - } - }) - .part('next-trigger', { id: opts.id, ref: opts.ref, owner: this, syncAttrs: true }); + this.runtimePart = this.provider.runtime.part('next-trigger', { + id: opts.id, + ref: opts.ref, + props: { + disabled: () => this.isDisabled + }, + owner: this, + syncAttrs: true + }); } readonly isDisabled = $derived.by( @@ -319,16 +323,18 @@ export class PaginationItemProvider { private constructor(opts: PaginationItemOpts) { this.opts = opts; this.provider = PaginationProvider.require(); - this.runtimePart = Soma.require() - .runtime(paginationMorfo, { - props: { - selected: () => this.isSelected, - disabled: () => this.isDisabled, - value: () => this.opts.value.current, - ariaLabel: () => this.ariaLabel - } - }) - .part('item', { id: opts.id, ref: opts.ref, owner: this, syncAttrs: true }); + this.runtimePart = this.provider.runtime.part('item', { + id: opts.id, + ref: opts.ref, + props: { + selected: () => this.isSelected, + disabled: () => this.isDisabled, + value: () => this.opts.value.current, + ariaLabel: () => this.ariaLabel + }, + owner: this, + syncAttrs: true + }); } readonly isSelected = $derived.by( @@ -339,7 +345,7 @@ export class PaginationItemProvider { 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) }) || `Page ${this.opts.value.current}` ); @@ -370,19 +376,23 @@ interface PaginationEllipsisOpts export class PaginationEllipsisProvider { readonly opts: PaginationEllipsisOpts; readonly runtimePart: SomaRuntimePart; + readonly provider: PaginationProvider; static create(opts: PaginationEllipsisOpts) { return new PaginationEllipsisProvider(opts); } private constructor(opts: PaginationEllipsisOpts) { this.opts = opts; - this.runtimePart = Soma.require() - .runtime(paginationMorfo, { - props: { - position: () => this.opts.position.current - } - }) - .part('ellipsis', { id: opts.id, ref: opts.ref, owner: this, syncAttrs: true }); + this.provider = PaginationProvider.require(); + this.runtimePart = this.provider.runtime.part('ellipsis', { + id: opts.id, + ref: opts.ref, + props: { + position: () => this.opts.position.current + }, + owner: this, + syncAttrs: true + }); } readonly props = $derived.by(() => diff --git a/src/uix/soma/components/progress/progress-provider.svelte.ts b/src/uix/soma/components/progress/progress-provider.svelte.ts index 789007085..2e5f8f561 100644 --- a/src/uix/soma/components/progress/progress-provider.svelte.ts +++ b/src/uix/soma/components/progress/progress-provider.svelte.ts @@ -5,7 +5,7 @@ import { PROGRESS_LANGS } from './langs'; import type { ProgressState } from './types'; 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 { @@ -32,6 +32,8 @@ interface ProgressOpts export class ProgressProvider { readonly opts: ProgressOpts; readonly runtimePart: SomaRuntimePart; + readonly soma: Soma; + readonly runtime: SomaRuntime; static readonly ctx = context('Progress'); static get(): ProgressProvider | undefined { return this.ctx.getOr(undefined) as ProgressProvider | undefined; @@ -44,31 +46,29 @@ export class ProgressProvider { return new ProgressProvider(opts); } - readonly soma = Soma.get(); - private constructor(opts: ProgressOpts) { this.opts = opts; - this.runtimePart = Soma.require() - .runtime(progressMorfo, { - states: { - loading: () => this.state - }, - props: { - min: () => this.opts.min.current, - max: () => this.opts.max.current, - value: () => this.opts.value.current ?? undefined, - valueText: () => this.resolvedValueText, - ariaLabel: () => this.resolvedAriaLabel.current, - ariaLabelledby: () => this.opts.ariaLabelledby.current - } - }) - .part('provider', { - id: opts.id, - ref: opts.ref, - owner: this, - context: ProgressProvider.ctx, - syncAttrs: true - }); + this.soma = Soma.require(); + this.runtime = this.soma.runtime(progressMorfo, { + states: { + loading: () => this.state + }, + props: { + min: () => this.opts.min.current, + max: () => this.opts.max.current, + value: () => this.opts.value.current ?? undefined, + valueText: () => this.resolvedValueText, + ariaLabel: () => this.resolvedAriaLabel.current, + ariaLabelledby: () => this.opts.ariaLabelledby.current + } + }); + this.runtimePart = this.runtime.part('provider', { + id: opts.id, + ref: opts.ref, + owner: this, + context: ProgressProvider.ctx, + syncAttrs: true + }); } readonly state: ReadonlyState = $derived.by(() => { @@ -88,7 +88,7 @@ export class ProgressProvider { readonly resolvedAriaLabel: Active = readableActive(() => { 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(() => { @@ -127,13 +127,15 @@ export class ProgressIndicatorProvider { private constructor(opts: ProgressIndicatorOpts) { this.opts = opts; this.provider = ProgressProvider.require(); - this.runtimePart = Soma.require() - .runtime(progressMorfo, { - states: { - loading: () => this.provider.state - } - }) - .part('indicator', { id: opts.id, ref: opts.ref, owner: this, syncAttrs: true }); + this.runtimePart = this.provider.runtime.part('indicator', { + id: opts.id, + ref: opts.ref, + states: { + loading: () => this.provider.state + }, + owner: this, + syncAttrs: true + }); } readonly props = $derived.by(() =>