From 43552dde82fe766a870c3967e156cf6c304a4aae Mon Sep 17 00:00:00 2001 From: dev Date: Fri, 15 May 2026 02:41:18 +0200 Subject: [PATCH] Consolidate Soma tag navigation runtimes --- .../navigation-menu-provider.svelte.ts | 94 ++++++++++--------- .../tags-input/tags-input-provider.svelte.ts | 90 +++++++++--------- 2 files changed, 96 insertions(+), 88 deletions(-) diff --git a/src/uix/soma/components/navigation-menu/navigation-menu-provider.svelte.ts b/src/uix/soma/components/navigation-menu/navigation-menu-provider.svelte.ts index dbf5ec7f4..f989a872c 100644 --- a/src/uix/soma/components/navigation-menu/navigation-menu-provider.svelte.ts +++ b/src/uix/soma/components/navigation-menu/navigation-menu-provider.svelte.ts @@ -22,7 +22,7 @@ import { Soma } from '../../core/soma.svelte'; import { NAVIGATION_MENU_LANGS } from './langs'; import { navigationMenuMorfo } from '../../../morfo/components/navigation-menu'; -import type { SomaRuntimePart } from '../../runtime.svelte'; +import type { SomaRuntime, SomaRuntimePart } from '../../runtime.svelte'; const attrs = createAttrs(navigationMenuMorfo); @@ -48,6 +48,8 @@ interface NavigationMenuOpts export class NavigationMenuProvider { readonly opts: NavigationMenuOpts; readonly runtimePart: SomaRuntimePart; + readonly soma: Soma; + readonly runtime: SomaRuntime; static readonly ctx = context('NavigationMenu'); static get(): NavigationMenuProvider | undefined { return this.ctx.getOr(undefined) as NavigationMenuProvider | undefined; @@ -60,8 +62,6 @@ export class NavigationMenuProvider { return new NavigationMenuProvider(opts); } - readonly soma = Soma.get(); - private openTimer: ReturnType | null = null; private closeTimer: ReturnType | null = null; private skipDelayTimer: ReturnType | null = null; @@ -82,14 +82,14 @@ export class NavigationMenuProvider { private constructor(opts: NavigationMenuOpts) { this.opts = opts; - this.runtimePart = Soma.require() - .runtime(navigationMenuMorfo, {}) - .part('provider', { - id: opts.id, - ref: opts.ref, - owner: this, - context: NavigationMenuProvider.ctx - }); + this.soma = Soma.require(); + this.runtime = this.soma.runtime(navigationMenuMorfo, {}); + this.runtimePart = this.runtime.part('provider', { + id: opts.id, + ref: opts.ref, + owner: this, + context: NavigationMenuProvider.ctx + }); // Track previous value for motion direction. Uses a manual "previous" // pattern rather than runed's `Previous` so that rapid swaps don't @@ -115,12 +115,12 @@ export class NavigationMenuProvider { } readonly resolvedDir: Active = readableActive( - () => this.opts.dir.current ?? this.soma?.prefs.getDir() ?? 'ltr' + () => this.opts.dir.current ?? this.soma.prefs.getDir() ?? 'ltr' ); readonly resolvedAriaLabel: Active = readableActive( () => - this.opts.ariaLabel.current || this.soma?.langs.ts(NAVIGATION_MENU_LANGS.LABEL) || undefined + this.opts.ariaLabel.current || this.soma.langs.ts(NAVIGATION_MENU_LANGS.LABEL) || undefined ); isOpen(value: string): boolean { @@ -334,10 +334,12 @@ export class NavigationMenuListProvider { private constructor(opts: NavigationMenuListOpts) { this.opts = opts; - this.runtimePart = Soma.require() - .runtime(navigationMenuMorfo, {}) - .part('list', { id: opts.id, ref: opts.ref, owner: this }); this.provider = NavigationMenuProvider.require(); + this.runtimePart = this.provider.runtime.part('list', { + id: opts.id, + ref: opts.ref, + owner: this + }); } readonly props = $derived.by(() => @@ -380,15 +382,13 @@ export class NavigationMenuItemProvider { private constructor(opts: NavigationMenuItemOpts) { this.opts = opts; - this.runtimePart = Soma.require() - .runtime(navigationMenuMorfo, {}) - .part('item', { - id: opts.id, - ref: opts.ref, - owner: this, - context: NavigationMenuItemProvider.ctx - }); this.provider = NavigationMenuProvider.require(); + this.runtimePart = this.provider.runtime.part('item', { + id: opts.id, + ref: opts.ref, + owner: this, + context: NavigationMenuItemProvider.ctx + }); } readonly isOpen = $derived.by(() => this.provider.opts.value.current === this.opts.value.current); @@ -424,20 +424,18 @@ export class NavigationMenuTriggerProvider { private constructor(opts: NavigationMenuTriggerOpts) { this.opts = opts; - this.runtimePart = Soma.require() - .runtime(navigationMenuMorfo, {}) - .part('trigger', { - id: opts.id, - ref: opts.ref, - owner: this, - onRefChange: (el) => { - if (el) { - this.provider.registerTrigger(this.item.opts.value.current, el); - } - } - }); this.provider = NavigationMenuProvider.require(); this.item = NavigationMenuItemProvider.require(); + this.runtimePart = this.provider.runtime.part('trigger', { + id: opts.id, + ref: opts.ref, + owner: this, + onRefChange: (el) => { + if (el) { + this.provider.registerTrigger(this.item.opts.value.current, el); + } + } + }); this.item.triggerId.current = opts.id.current; } @@ -523,11 +521,13 @@ export class NavigationMenuContentProvider { private constructor(opts: NavigationMenuContentOpts) { this.opts = opts; - this.runtimePart = Soma.require() - .runtime(navigationMenuMorfo, {}) - .part('content', { id: opts.id, ref: opts.ref, owner: this }); this.provider = NavigationMenuProvider.require(); this.item = NavigationMenuItemProvider.require(); + this.runtimePart = this.provider.runtime.part('content', { + id: opts.id, + ref: opts.ref, + owner: this + }); this.item.contentId.current = opts.id.current; } @@ -584,10 +584,12 @@ export class NavigationMenuLinkProvider { private constructor(opts: NavigationMenuLinkOpts) { this.opts = opts; - this.runtimePart = Soma.require() - .runtime(navigationMenuMorfo, {}) - .part('link', { id: opts.id, ref: opts.ref, owner: this }); this.provider = NavigationMenuProvider.require(); + this.runtimePart = this.provider.runtime.part('link', { + id: opts.id, + ref: opts.ref, + owner: this + }); } readonly onkeydown = (e: SomaKeyboardEvent) => { @@ -630,10 +632,12 @@ export class NavigationMenuIndicatorProvider { private constructor(opts: NavigationMenuIndicatorOpts) { this.opts = opts; - this.runtimePart = Soma.require() - .runtime(navigationMenuMorfo, {}) - .part('indicator', { id: opts.id, ref: opts.ref, owner: this }); this.provider = NavigationMenuProvider.require(); + this.runtimePart = this.provider.runtime.part('indicator', { + id: opts.id, + ref: opts.ref, + owner: this + }); $effect(() => { const openValue = this.provider.opts.value.current; diff --git a/src/uix/soma/components/tags-input/tags-input-provider.svelte.ts b/src/uix/soma/components/tags-input/tags-input-provider.svelte.ts index b8333e565..15293d938 100644 --- a/src/uix/soma/components/tags-input/tags-input-provider.svelte.ts +++ b/src/uix/soma/components/tags-input/tags-input-provider.svelte.ts @@ -8,7 +8,7 @@ import { Soma } from '../../core/soma.svelte'; import { TAGS_INPUT_LANGS } from './langs'; import { tagsInputMorfo } from '../../../morfo/components/tags-input'; -import type { SomaRuntimePart } from '../../runtime.svelte'; +import type { SomaRuntime, SomaRuntimePart } from '../../runtime.svelte'; const attrs = createAttrs(tagsInputMorfo); @@ -40,6 +40,8 @@ interface TagsInputOpts export class TagsInputProvider { readonly opts: TagsInputOpts; readonly runtimePart: SomaRuntimePart; + readonly soma: Soma; + readonly runtime: SomaRuntime; static readonly ctx = context('TagsInput'); static get(): TagsInputProvider | undefined { return this.ctx.getOr(undefined) as TagsInputProvider | undefined; @@ -52,22 +54,20 @@ export class TagsInputProvider { return new TagsInputProvider(opts); } - readonly soma = Soma.get(); - // Focus tracking highlightedIndex = $state(-1); inputRef = state(null); private constructor(opts: TagsInputOpts) { this.opts = opts; - this.runtimePart = Soma.require() - .runtime(tagsInputMorfo, {}) - .part('provider', { - id: opts.id, - ref: opts.ref, - owner: this, - context: TagsInputProvider.ctx - }); + this.soma = Soma.require(); + this.runtime = this.soma.runtime(tagsInputMorfo, {}); + this.runtimePart = this.runtime.part('provider', { + id: opts.id, + ref: opts.ref, + owner: this, + context: TagsInputProvider.ctx + }); } readonly isEmpty = $derived.by(() => this.opts.value.current.length === 0); @@ -224,10 +224,12 @@ export class TagsInputControlProvider { private constructor(opts: TagsInputControlOpts) { this.opts = opts; - this.runtimePart = Soma.require() - .runtime(tagsInputMorfo, {}) - .part('control', { id: opts.id, ref: opts.ref, owner: this }); this.provider = TagsInputProvider.require(); + this.runtimePart = this.provider.runtime.part('control', { + id: opts.id, + ref: opts.ref, + owner: this + }); } readonly onclick = () => { @@ -264,17 +266,15 @@ export class TagsInputInputProvider { private constructor(opts: TagsInputInputOpts) { this.opts = opts; - this.runtimePart = Soma.require() - .runtime(tagsInputMorfo, {}) - .part('input', { - id: opts.id, - ref: opts.ref, - owner: this, - onRefChange: (el) => { - this.provider.inputRef.current = el; - } - }); this.provider = TagsInputProvider.require(); + this.runtimePart = this.provider.runtime.part('input', { + id: opts.id, + ref: opts.ref, + owner: this, + onRefChange: (el) => { + this.provider.inputRef.current = el; + } + }); } readonly oninput = (e: Event) => { @@ -406,15 +406,13 @@ export class TagsInputItemProvider { private constructor(opts: TagsInputItemOpts) { this.opts = opts; - this.runtimePart = Soma.require() - .runtime(tagsInputMorfo, {}) - .part('item', { - id: opts.id, - ref: opts.ref, - owner: this, - context: TagsInputItemProvider.ctx - }); this.provider = TagsInputProvider.require(); + this.runtimePart = this.provider.runtime.part('item', { + id: opts.id, + ref: opts.ref, + owner: this, + context: TagsInputItemProvider.ctx + }); } readonly isHighlighted = $derived.by( @@ -488,10 +486,12 @@ export class TagsInputItemTextProvider { private constructor(opts: TagsInputItemTextOpts) { this.opts = opts; - this.runtimePart = Soma.require() - .runtime(tagsInputMorfo, {}) - .part('item-text', { id: opts.id, ref: opts.ref, owner: this }); this.item = TagsInputItemProvider.require(); + this.runtimePart = this.item.provider.runtime.part('item-text', { + id: opts.id, + ref: opts.ref, + owner: this + }); } readonly props = $derived.by(() => @@ -519,11 +519,13 @@ export class TagsInputItemDeleteTriggerProvider { private constructor(opts: TagsInputItemDeleteTriggerOpts) { this.opts = opts; - this.runtimePart = Soma.require() - .runtime(tagsInputMorfo, {}) - .part('item-delete-trigger', { id: opts.id, ref: opts.ref, owner: this }); this.item = TagsInputItemProvider.require(); this.provider = this.item.provider; + this.runtimePart = this.provider.runtime.part('item-delete-trigger', { + id: opts.id, + ref: opts.ref, + owner: this + }); } readonly onclick = (e: SomaMouseEvent) => { @@ -538,7 +540,7 @@ export class TagsInputItemDeleteTriggerProvider { ...this.runtimePart.props, type: 'button' as const, tabindex: -1, - 'aria-label': this.provider.soma?.langs.ts(TAGS_INPUT_LANGS.REMOVE), + 'aria-label': this.provider.soma.langs.ts(TAGS_INPUT_LANGS.REMOVE), disabled: this.item.isDisabled || this.provider.opts.readonly.current || undefined, 'data-disabled': boolToEmptyStrOrUndef(this.item.isDisabled), onclick: this.onclick @@ -561,10 +563,12 @@ export class TagsInputClearTriggerProvider { private constructor(opts: TagsInputClearTriggerOpts) { this.opts = opts; - this.runtimePart = Soma.require() - .runtime(tagsInputMorfo, {}) - .part('clear-trigger', { id: opts.id, ref: opts.ref, owner: this }); this.provider = TagsInputProvider.require(); + this.runtimePart = this.provider.runtime.part('clear-trigger', { + id: opts.id, + ref: opts.ref, + owner: this + }); } readonly onclick = (_e: SomaMouseEvent) => { @@ -576,7 +580,7 @@ export class TagsInputClearTriggerProvider { this.runtimePart.assert({ ...this.runtimePart.props, type: 'button' as const, - 'aria-label': this.provider.soma?.langs.ts(TAGS_INPUT_LANGS.CLEAR), + 'aria-label': this.provider.soma.langs.ts(TAGS_INPUT_LANGS.CLEAR), hidden: this.provider.isEmpty || this.provider.opts.disabled.current ||