From c4c37a37192943bcba7607e6efc444d5ea00519d Mon Sep 17 00:00:00 2001 From: dev Date: Fri, 15 May 2026 02:59:31 +0200 Subject: [PATCH] Consolidate Soma selection runtimes --- .../combobox/combobox-provider.svelte.ts | 112 ++++++++++------- .../select/select-provider.svelte.ts | 115 +++++++++++------- 2 files changed, 136 insertions(+), 91 deletions(-) diff --git a/src/uix/soma/components/combobox/combobox-provider.svelte.ts b/src/uix/soma/components/combobox/combobox-provider.svelte.ts index bcffe3985..0761327df 100644 --- a/src/uix/soma/components/combobox/combobox-provider.svelte.ts +++ b/src/uix/soma/components/combobox/combobox-provider.svelte.ts @@ -27,7 +27,7 @@ import { } from '../../layers/floating'; import { comboboxMorfo } from '../../../morfo/components/combobox'; -import type { SomaRuntimePart } from '../../runtime.svelte'; +import type { SomaRuntime, SomaRuntimePart } from '../../runtime.svelte'; const attrs = createAttrs(comboboxMorfo); @@ -53,6 +53,8 @@ interface ComboboxOpts export class ComboboxProvider { readonly opts: ComboboxOpts; readonly runtimePart: SomaRuntimePart; + readonly soma: Soma; + readonly runtime: SomaRuntime; static readonly ctx = context('Combobox'); static get(): ComboboxProvider | undefined { return this.ctx.getOr(undefined) as ComboboxProvider | undefined; @@ -65,7 +67,6 @@ export class ComboboxProvider { return new ComboboxProvider(opts); } - readonly soma = Soma.get(); readonly floatingProvider: FloatingProvider; readonly contentPresence: Presence; @@ -98,9 +99,14 @@ export class ComboboxProvider { private constructor(opts: ComboboxOpts) { this.opts = opts; - this.runtimePart = Soma.require() - .runtime(comboboxMorfo, {}) - .part('provider', { id: opts.id, ref: opts.ref, owner: this, context: ComboboxProvider.ctx }); + this.soma = Soma.require(); + this.runtime = this.soma.runtime(comboboxMorfo, {}); + this.runtimePart = this.runtime.part('provider', { + id: opts.id, + ref: opts.ref, + owner: this, + context: ComboboxProvider.ctx + }); this.floatingProvider = FloatingProvider.create(); @@ -191,10 +197,12 @@ export class ComboboxInputProvider { private constructor(opts: ComboboxInputOpts) { this.opts = opts; - this.runtimePart = Soma.require() - .runtime(comboboxMorfo, {}) - .part('input', { id: opts.id, ref: opts.ref, owner: this }); this.provider = ComboboxProvider.require(); + this.runtimePart = this.provider.runtime.part('input', { + id: opts.id, + ref: opts.ref, + owner: this + }); this.provider.inputId.current = opts.id.current; this.provider.inputRef = opts.ref; @@ -297,10 +305,12 @@ export class ComboboxTriggerProvider { private constructor(opts: ComboboxTriggerOpts) { this.opts = opts; - this.runtimePart = Soma.require() - .runtime(comboboxMorfo, {}) - .part('trigger', { id: opts.id, ref: opts.ref, owner: this }); this.provider = ComboboxProvider.require(); + this.runtimePart = this.provider.runtime.part('trigger', { + id: opts.id, + ref: opts.ref, + owner: this + }); this.provider.triggerId.current = opts.id.current; this.provider.triggerRef = opts.ref; } @@ -314,7 +324,7 @@ export class ComboboxTriggerProvider { ...this.runtimePart.props, type: 'button' as const, tabindex: -1, - 'aria-label': this.provider.soma?.langs.ts(COMBOBOX_LANGS.TOGGLE), + 'aria-label': this.provider.soma.langs.ts(COMBOBOX_LANGS.TOGGLE), 'aria-haspopup': 'listbox' as const, 'aria-expanded': this.provider.opts.open.current, 'aria-controls': this.provider.contentId.current || undefined, @@ -364,17 +374,15 @@ export class ComboboxContentProvider { private constructor(opts: ComboboxContentOpts) { this.opts = opts; - this.runtimePart = Soma.require() - .runtime(comboboxMorfo, {}) - .part('content', { - id: opts.id, - ref: opts.ref, - owner: this, - onRefChange: (el) => { - this.provider.setContentRef(el); - } - }); this.provider = ComboboxProvider.require(); + this.runtimePart = this.provider.runtime.part('content', { + id: opts.id, + ref: opts.ref, + owner: this, + onRefChange: (el) => { + this.provider.setContentRef(el); + } + }); this.provider.contentId.current = opts.id.current; // Floating @@ -528,9 +536,12 @@ export class ComboboxViewportProvider { private constructor(opts: ComboboxViewportOpts) { this.opts = opts; - this.runtimePart = Soma.require() - .runtime(comboboxMorfo, {}) - .part('viewport', { id: opts.id, ref: opts.ref, owner: this }); + const provider = ComboboxProvider.require(); + this.runtimePart = provider.runtime.part('viewport', { + id: opts.id, + ref: opts.ref, + owner: this + }); } readonly props = $derived.by(() => @@ -563,10 +574,12 @@ export class ComboboxItemProvider { private constructor(opts: ComboboxItemOpts) { this.opts = opts; - this.runtimePart = Soma.require() - .runtime(comboboxMorfo, {}) - .part('item', { id: opts.id, ref: opts.ref, owner: this }); this.provider = ComboboxProvider.require(); + this.runtimePart = this.provider.runtime.part('item', { + id: opts.id, + ref: opts.ref, + owner: this + }); // Maintain value → label registry for display-text resolution without // relying on DOM queries (content may be unmounted or lazy-rendered). @@ -617,6 +630,7 @@ interface ComboboxGroupOpts extends WithRefOpts {} export class ComboboxGroupProvider { readonly opts: ComboboxGroupOpts; readonly runtimePart: SomaRuntimePart; + readonly provider: ComboboxProvider; static readonly ctx = context('ComboboxGroup'); static get(): ComboboxGroupProvider | undefined { return this.ctx.getOr(undefined) as ComboboxGroupProvider | undefined; @@ -633,14 +647,13 @@ export class ComboboxGroupProvider { private constructor(opts: ComboboxGroupOpts) { this.opts = opts; - this.runtimePart = Soma.require() - .runtime(comboboxMorfo, {}) - .part('group', { - id: opts.id, - ref: opts.ref, - owner: this, - context: ComboboxGroupProvider.ctx - }); + this.provider = ComboboxProvider.require(); + this.runtimePart = this.provider.runtime.part('group', { + id: opts.id, + ref: opts.ref, + owner: this, + context: ComboboxGroupProvider.ctx + }); } readonly props = $derived.by(() => @@ -667,10 +680,13 @@ export class ComboboxGroupHeadingProvider { private constructor(opts: ComboboxGroupHeadingOpts) { this.opts = opts; - this.runtimePart = Soma.require() - .runtime(comboboxMorfo, {}) - .part('group-heading', { id: opts.id, ref: opts.ref, owner: this }); this.group = ComboboxGroupProvider.get() as ComboboxGroupProvider | undefined; + const provider = this.group?.provider ?? ComboboxProvider.require(); + this.runtimePart = provider.runtime.part('group-heading', { + id: opts.id, + ref: opts.ref, + owner: this + }); if (this.group) { const group = this.group; group.headingId.current = opts.id.current; @@ -695,9 +711,12 @@ export class ComboboxSeparatorProvider { private constructor(opts: ComboboxSeparatorOpts) { this.opts = opts; - this.runtimePart = Soma.require() - .runtime(comboboxMorfo, {}) - .part('separator', { id: opts.id, ref: opts.ref, owner: this }); + const provider = ComboboxProvider.require(); + this.runtimePart = provider.runtime.part('separator', { + id: opts.id, + ref: opts.ref, + owner: this + }); } readonly props = $derived.by(() => @@ -723,9 +742,12 @@ export class ComboboxArrowProvider { private constructor(opts: ComboboxArrowOpts) { this.opts = opts; - this.runtimePart = Soma.require() - .runtime(comboboxMorfo, {}) - .part('arrow', { id: opts.id, ref: opts.ref, owner: this }); + const provider = ComboboxProvider.require(); + this.runtimePart = provider.runtime.part('arrow', { + id: opts.id, + ref: opts.ref, + owner: this + }); this.floatingArrow = FloatingArrow.create(opts); } diff --git a/src/uix/soma/components/select/select-provider.svelte.ts b/src/uix/soma/components/select/select-provider.svelte.ts index 6a64983ff..d13d60ff0 100644 --- a/src/uix/soma/components/select/select-provider.svelte.ts +++ b/src/uix/soma/components/select/select-provider.svelte.ts @@ -33,7 +33,7 @@ import { } from '../../layers/floating'; import { selectMorfo } from '../../../morfo/components/select'; -import type { SomaRuntimePart } from '../../runtime.svelte'; +import type { SomaRuntime, SomaRuntimePart } from '../../runtime.svelte'; const attrs = createAttrs(selectMorfo); @@ -96,6 +96,8 @@ interface SelectOpts export class SelectProvider { readonly opts: SelectOpts; readonly runtimePart: SomaRuntimePart; + readonly soma: Soma; + readonly runtime: SomaRuntime; static readonly ctx = context('Select'); static get(): SelectProvider | undefined { return this.ctx.getOr(undefined) as SelectProvider | undefined; @@ -108,7 +110,6 @@ export class SelectProvider { return new SelectProvider(opts); } - readonly config = Soma.get(); readonly floatingProvider: FloatingProvider; readonly contentPresence: Presence; readonly typeahead = new Typeahead(); @@ -136,9 +137,14 @@ export class SelectProvider { private constructor(opts: SelectOpts) { this.opts = opts; - this.runtimePart = Soma.require() - .runtime(selectMorfo, {}) - .part('provider', { id: opts.id, ref: opts.ref, owner: this, context: SelectProvider.ctx }); + this.soma = Soma.require(); + this.runtime = this.soma.runtime(selectMorfo, {}); + this.runtimePart = this.runtime.part('provider', { + id: opts.id, + ref: opts.ref, + owner: this, + context: SelectProvider.ctx + }); this.floatingProvider = FloatingProvider.create(); @@ -258,17 +264,15 @@ export class SelectTriggerProvider { // (fired by attachRef at mount/unmount inside `untrack`). Avoids the // `$effect` child→parent reactive edge that used to exist here. this.opts = opts; - this.runtimePart = Soma.require() - .runtime(selectMorfo, {}) - .part('trigger', { - id: opts.id, - ref: opts.ref, - owner: this, - onRefChange: (el) => { - this.provider.triggerRef.current = el; - } - }); this.provider = SelectProvider.require(); + this.runtimePart = this.provider.runtime.part('trigger', { + id: opts.id, + ref: opts.ref, + owner: this, + onRefChange: (el) => { + this.provider.triggerRef.current = el; + } + }); this.provider.triggerId.current = opts.id.current; FloatingAnchor.create({ @@ -394,10 +398,12 @@ export class SelectValueProvider { private constructor(opts: SelectValueOpts) { this.opts = opts; - this.runtimePart = Soma.require() - .runtime(selectMorfo, {}) - .part('value', { id: opts.id, ref: opts.ref, owner: this }); this.provider = SelectProvider.require(); + this.runtimePart = this.provider.runtime.part('value', { + id: opts.id, + ref: opts.ref, + owner: this + }); } readonly displayText = $derived.by(() => { @@ -454,17 +460,15 @@ export class SelectContentProvider { private constructor(opts: SelectContentOpts) { this.opts = opts; - this.runtimePart = Soma.require() - .runtime(selectMorfo, {}) - .part('content', { - id: opts.id, - ref: opts.ref, - owner: this, - onRefChange: (el) => { - this.provider.setContentRef(el); - } - }); this.provider = SelectProvider.require(); + this.runtimePart = this.provider.runtime.part('content', { + id: opts.id, + ref: opts.ref, + owner: this, + onRefChange: (el) => { + this.provider.setContentRef(el); + } + }); this.provider.contentId.current = opts.id.current; // Floating @@ -614,9 +618,12 @@ export class SelectViewportProvider { private constructor(opts: SelectViewportOpts) { this.opts = opts; - this.runtimePart = Soma.require() - .runtime(selectMorfo, {}) - .part('viewport', { id: opts.id, ref: opts.ref, owner: this }); + const provider = SelectProvider.require(); + this.runtimePart = provider.runtime.part('viewport', { + id: opts.id, + ref: opts.ref, + owner: this + }); } readonly props = $derived.by(() => @@ -648,10 +655,12 @@ export class SelectItemProvider { private constructor(opts: SelectItemOpts) { this.opts = opts; - this.runtimePart = Soma.require() - .runtime(selectMorfo, {}) - .part('item', { id: opts.id, ref: opts.ref, owner: this }); this.provider = SelectProvider.require(); + this.runtimePart = this.provider.runtime.part('item', { + id: opts.id, + ref: opts.ref, + owner: this + }); // Register label for display text (avoids DOM queries) this.provider.registerLabel(opts.value.current, opts.label.current || opts.value.current); @@ -699,6 +708,7 @@ interface SelectGroupOpts extends WithRefOpts {} export class SelectGroupProvider { readonly opts: SelectGroupOpts; readonly runtimePart: SomaRuntimePart; + readonly provider: SelectProvider; static readonly ctx = context('SelectGroup'); static get(): SelectGroupProvider | undefined { return this.ctx.getOr(undefined) as SelectGroupProvider | undefined; @@ -715,9 +725,13 @@ export class SelectGroupProvider { private constructor(opts: SelectGroupOpts) { this.opts = opts; - this.runtimePart = Soma.require() - .runtime(selectMorfo, {}) - .part('group', { id: opts.id, ref: opts.ref, owner: this, context: SelectGroupProvider.ctx }); + this.provider = SelectProvider.require(); + this.runtimePart = this.provider.runtime.part('group', { + id: opts.id, + ref: opts.ref, + owner: this, + context: SelectGroupProvider.ctx + }); } readonly props = $derived.by(() => @@ -744,10 +758,13 @@ export class SelectGroupHeadingProvider { private constructor(opts: SelectGroupHeadingOpts) { this.opts = opts; - this.runtimePart = Soma.require() - .runtime(selectMorfo, {}) - .part('group-heading', { id: opts.id, ref: opts.ref, owner: this }); this.group = SelectGroupProvider.get() as SelectGroupProvider | undefined; + const provider = this.group?.provider ?? SelectProvider.require(); + this.runtimePart = provider.runtime.part('group-heading', { + id: opts.id, + ref: opts.ref, + owner: this + }); if (this.group) { const group = this.group; group.headingId.current = opts.id.current; @@ -772,9 +789,12 @@ export class SelectSeparatorProvider { private constructor(opts: SelectSeparatorOpts) { this.opts = opts; - this.runtimePart = Soma.require() - .runtime(selectMorfo, {}) - .part('separator', { id: opts.id, ref: opts.ref, owner: this }); + const provider = SelectProvider.require(); + this.runtimePart = provider.runtime.part('separator', { + id: opts.id, + ref: opts.ref, + owner: this + }); } readonly props = $derived.by(() => @@ -800,9 +820,12 @@ export class SelectArrowProvider { private constructor(opts: SelectArrowOpts) { this.opts = opts; - this.runtimePart = Soma.require() - .runtime(selectMorfo, {}) - .part('arrow', { id: opts.id, ref: opts.ref, owner: this }); + const provider = SelectProvider.require(); + this.runtimePart = provider.runtime.part('arrow', { + id: opts.id, + ref: opts.ref, + owner: this + }); this.floatingArrow = FloatingArrow.create(opts); }