From e2313c0f0f331c899d04af603ffc0e50566fcac0 Mon Sep 17 00:00:00 2001 From: dev Date: Fri, 15 May 2026 03:47:18 +0200 Subject: [PATCH] Normalize standalone Soma runtimes --- src/uix/soma/README.md | 19 +++++++++++--- src/uix/soma/SOMA_ARCHITECTURE.md | 19 +++++++++----- .../announce/announce-provider.svelte.ts | 26 ++++++++++++------- .../components/feed/feed-provider.svelte.ts | 7 +++-- 4 files changed, 48 insertions(+), 23 deletions(-) diff --git a/src/uix/soma/README.md b/src/uix/soma/README.md index fb8ba7931..091daed4b 100644 --- a/src/uix/soma/README.md +++ b/src/uix/soma/README.md @@ -267,7 +267,8 @@ conectados por `ActiveUix`: ```ts import { dialogMorfo } from '../../../morfo/components/dialog'; -this.runtime = Soma.require().runtime(dialogMorfo, sources); +this.soma = Soma.require(); +this.runtime = this.soma.runtime(dialogMorfo, sources); ``` Cuando un provider necesita un selector DOM, puede usar `createAttrs(morfo)` @@ -424,7 +425,7 @@ autorizados por el contrato. ```ts import { context, type WithRefOpts } from '../provider'; import { Soma } from '../core/soma.svelte'; -import type { SomaRuntimePart } from '../runtime.svelte'; +import type { SomaRuntime, SomaRuntimePart } from '../runtime.svelte'; import { accordionMorfo } from '$uix/morfo/components/accordion'; export class AccordionProvider { @@ -437,6 +438,8 @@ export class AccordionProvider { } readonly opts: AccordionOpts; + readonly soma: Soma; + readonly runtime: SomaRuntime; readonly runtimePart: SomaRuntimePart; static create(opts: AccordionOpts) { @@ -445,7 +448,9 @@ export class AccordionProvider { private constructor(opts: AccordionOpts) { this.opts = opts; - this.runtimePart = Soma.require().runtime(accordionMorfo, {}).part('provider', { + this.soma = Soma.require(); + this.runtime = this.soma.runtime(accordionMorfo, {}); + this.runtimePart = this.runtime.part('provider', { id: opts.id, ref: opts.ref, owner: this, @@ -495,7 +500,9 @@ renderizados, deja `syncAttrs` sin activar para evitar doble autoridad DOM. ## 8. Sistema de attrs ```ts +import { createAttrs } from '$uix/morfo'; import { accordionMorfo } from '$uix/morfo/components/accordion'; + const attrs = createAttrs(accordionMorfo); attrs.provider; // "data-accordion" @@ -866,6 +873,8 @@ export class AccordionProvider { } readonly opts: AccordionOpts; + readonly soma: Soma; + readonly runtime: SomaRuntime; readonly runtimePart: SomaRuntimePart; static create(opts: AccordionOpts) { @@ -874,7 +883,9 @@ export class AccordionProvider { private constructor(opts: AccordionOpts) { this.opts = opts; - this.runtimePart = Soma.require().runtime(accordionMorfo, {}).part('provider', { + this.soma = Soma.require(); + this.runtime = this.soma.runtime(accordionMorfo, {}); + this.runtimePart = this.runtime.part('provider', { id: opts.id, ref: opts.ref, owner: this, diff --git a/src/uix/soma/SOMA_ARCHITECTURE.md b/src/uix/soma/SOMA_ARCHITECTURE.md index f6c099c92..513519879 100644 --- a/src/uix/soma/SOMA_ARCHITECTURE.md +++ b/src/uix/soma/SOMA_ARCHITECTURE.md @@ -82,7 +82,11 @@ Todo componente sigue el mismo patron: 3. Props derivados via `$derived.by` + `runtimePart.assert` 4. Contexto para comunicacion padre-hijo -No hay excepciones: componentes sin DOM usan `ProviderOpts` (ref opcional), componentes con DOM usan `WithRefOpts`. +Los roots sin DOM usan `ProviderOpts` (ref opcional), las partes con DOM usan +`WithRefOpts`. Las unicas excepciones son partes declarativas que pueden vivir +fuera de su provider (`AnnounceRegion`, `FeedSentinel`): si encuentran un +provider reutilizan su `runtime`; si no, crean un runtime propio desde el +`Soma` del scope actual. ### 3.3 Layers como behaviors, no como wrappers @@ -116,8 +120,8 @@ readonly props = $derived.by(() => this.runtimePart.assert({ ```ts // En un Provider: -readonly soma = Soma.get(); -const dir = this.soma?.prefs.getDir(); +readonly soma = Soma.require(); +const dir = this.soma.prefs.getDir(); ``` ### 3.5 Los data-\* son contrato publico @@ -177,9 +181,8 @@ ADom aplica mutaciones DOM (commit estructural) Una instancia por componente: ```ts -const runtime = createSomaRuntime(morfo, { - dom: this.soma.dom, - eventEngine: this.soma.events, +readonly soma = Soma.require(); +readonly runtime = this.soma.runtime(morfo, { states: { open: () => this.opts.open.current }, props: { disabled: () => this.opts.disabled.current }, parts: { content: () => this.contentId.current }, @@ -361,7 +364,9 @@ Ver A27 en `COMPONENT_GUIDE.md` para el checklist completo. ## 5. Runtime parts ```ts -const runtimePart = Soma.require().runtime(accordionMorfo, {}).part('provider', { +readonly soma = Soma.require(); +readonly runtime = this.soma.runtime(accordionMorfo, {}); +const runtimePart = this.runtime.part('provider', { id: opts.id, ref: opts.ref, owner: this, diff --git a/src/uix/soma/components/announce/announce-provider.svelte.ts b/src/uix/soma/components/announce/announce-provider.svelte.ts index fe957c023..1f264a00d 100644 --- a/src/uix/soma/components/announce/announce-provider.svelte.ts +++ b/src/uix/soma/components/announce/announce-provider.svelte.ts @@ -165,6 +165,8 @@ interface AnnounceRegionOpts export class AnnounceRegionProvider { readonly opts: AnnounceRegionOpts; readonly runtimePart: SomaRuntimePart; + readonly soma: Soma; + readonly runtime: SomaRuntime; static create(opts: AnnounceRegionOpts) { return new AnnounceRegionProvider(opts); } @@ -174,17 +176,21 @@ export class AnnounceRegionProvider { private constructor(opts: AnnounceRegionOpts) { this.opts = opts; - this.runtimePart = Soma.require() - .runtime(announceMorfo, { - props: { - role: () => this.opts.role.current, - ariaLive: () => this.resolvedLive, - ariaAtomic: () => this.opts.ariaAtomic.current, - ariaRelevant: () => this.opts.ariaRelevant.current - } - }) - .part('region', { id: opts.id, ref: opts.ref, owner: this, syncAttrs: true }); + this.soma = Soma.require(); this.provider = AnnounceProvider.get(); + this.runtime = this.provider?.runtime ?? this.soma.runtime(announceMorfo, {}); + this.runtimePart = this.runtime.part('region', { + id: opts.id, + ref: opts.ref, + owner: this, + props: { + role: () => this.opts.role.current, + ariaLive: () => this.resolvedLive, + ariaAtomic: () => this.opts.ariaAtomic.current, + ariaRelevant: () => this.opts.ariaRelevant.current + }, + syncAttrs: true + }); } readonly resolvedLive = $derived.by( diff --git a/src/uix/soma/components/feed/feed-provider.svelte.ts b/src/uix/soma/components/feed/feed-provider.svelte.ts index 923a24ac3..de273f439 100644 --- a/src/uix/soma/components/feed/feed-provider.svelte.ts +++ b/src/uix/soma/components/feed/feed-provider.svelte.ts @@ -389,6 +389,8 @@ interface FeedSentinelOpts export class FeedSentinelProvider { readonly opts: FeedSentinelOpts; readonly runtimePart: SomaRuntimePart; + readonly soma: Soma; + readonly runtime: SomaRuntime; static create(opts: FeedSentinelOpts) { return new FeedSentinelProvider(opts); } @@ -399,9 +401,10 @@ export class FeedSentinelProvider { private constructor(opts: FeedSentinelOpts) { this.opts = opts; + this.soma = Soma.require(); this.feed = FeedProvider.get(); - const runtime = this.feed?.runtime ?? Soma.require().runtime(feedMorfo, {}); - this.runtimePart = runtime.part('sentinel', { id: opts.id, ref: opts.ref, owner: this }); + this.runtime = this.feed?.runtime ?? this.soma.runtime(feedMorfo, {}); + this.runtimePart = this.runtime.part('sentinel', { id: opts.id, ref: opts.ref, owner: this }); $effect(() => { const el = opts.ref.current;