Consolidate Soma tag navigation runtimes

active-uix
dev 5 months ago
parent c499dd239a
commit 43552dde82

@ -22,7 +22,7 @@ import { Soma } from '../../core/soma.svelte';
import { NAVIGATION_MENU_LANGS } from './langs'; import { NAVIGATION_MENU_LANGS } from './langs';
import { navigationMenuMorfo } from '../../../morfo/components/navigation-menu'; import { navigationMenuMorfo } from '../../../morfo/components/navigation-menu';
import type { SomaRuntimePart } from '../../runtime.svelte'; import type { SomaRuntime, SomaRuntimePart } from '../../runtime.svelte';
const attrs = createAttrs(navigationMenuMorfo); const attrs = createAttrs(navigationMenuMorfo);
@ -48,6 +48,8 @@ interface NavigationMenuOpts
export class NavigationMenuProvider { export class NavigationMenuProvider {
readonly opts: NavigationMenuOpts; readonly opts: NavigationMenuOpts;
readonly runtimePart: SomaRuntimePart; readonly runtimePart: SomaRuntimePart;
readonly soma: Soma;
readonly runtime: SomaRuntime;
static readonly ctx = context<NavigationMenuProvider>('NavigationMenu'); static readonly ctx = context<NavigationMenuProvider>('NavigationMenu');
static get(): NavigationMenuProvider | undefined { static get(): NavigationMenuProvider | undefined {
return this.ctx.getOr(undefined) as NavigationMenuProvider | undefined; return this.ctx.getOr(undefined) as NavigationMenuProvider | undefined;
@ -60,8 +62,6 @@ export class NavigationMenuProvider {
return new NavigationMenuProvider(opts); return new NavigationMenuProvider(opts);
} }
readonly soma = Soma.get();
private openTimer: ReturnType<typeof setTimeout> | null = null; private openTimer: ReturnType<typeof setTimeout> | null = null;
private closeTimer: ReturnType<typeof setTimeout> | null = null; private closeTimer: ReturnType<typeof setTimeout> | null = null;
private skipDelayTimer: ReturnType<typeof setTimeout> | null = null; private skipDelayTimer: ReturnType<typeof setTimeout> | null = null;
@ -82,14 +82,14 @@ export class NavigationMenuProvider {
private constructor(opts: NavigationMenuOpts) { private constructor(opts: NavigationMenuOpts) {
this.opts = opts; this.opts = opts;
this.runtimePart = Soma.require() this.soma = Soma.require();
.runtime(navigationMenuMorfo, {}) this.runtime = this.soma.runtime(navigationMenuMorfo, {});
.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: NavigationMenuProvider.ctx context: NavigationMenuProvider.ctx
}); });
// Track previous value for motion direction. Uses a manual "previous" // Track previous value for motion direction. Uses a manual "previous"
// pattern rather than runed's `Previous` so that rapid swaps don't // pattern rather than runed's `Previous` so that rapid swaps don't
@ -115,12 +115,12 @@ export class NavigationMenuProvider {
} }
readonly resolvedDir: Active<Direction> = readableActive( readonly resolvedDir: Active<Direction> = readableActive(
() => this.opts.dir.current ?? this.soma?.prefs.getDir() ?? 'ltr' () => this.opts.dir.current ?? this.soma.prefs.getDir() ?? 'ltr'
); );
readonly resolvedAriaLabel: Active<string | undefined> = readableActive( readonly resolvedAriaLabel: Active<string | undefined> = 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 { isOpen(value: string): boolean {
@ -334,10 +334,12 @@ export class NavigationMenuListProvider {
private constructor(opts: NavigationMenuListOpts) { private constructor(opts: NavigationMenuListOpts) {
this.opts = opts; this.opts = opts;
this.runtimePart = Soma.require()
.runtime(navigationMenuMorfo, {})
.part('list', { id: opts.id, ref: opts.ref, owner: this });
this.provider = NavigationMenuProvider.require(); this.provider = NavigationMenuProvider.require();
this.runtimePart = this.provider.runtime.part('list', {
id: opts.id,
ref: opts.ref,
owner: this
});
} }
readonly props = $derived.by(() => readonly props = $derived.by(() =>
@ -380,15 +382,13 @@ export class NavigationMenuItemProvider {
private constructor(opts: NavigationMenuItemOpts) { private constructor(opts: NavigationMenuItemOpts) {
this.opts = opts; 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.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); readonly isOpen = $derived.by(() => this.provider.opts.value.current === this.opts.value.current);
@ -424,20 +424,18 @@ export class NavigationMenuTriggerProvider {
private constructor(opts: NavigationMenuTriggerOpts) { private constructor(opts: NavigationMenuTriggerOpts) {
this.opts = opts; 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.provider = NavigationMenuProvider.require();
this.item = NavigationMenuItemProvider.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; this.item.triggerId.current = opts.id.current;
} }
@ -523,11 +521,13 @@ export class NavigationMenuContentProvider {
private constructor(opts: NavigationMenuContentOpts) { private constructor(opts: NavigationMenuContentOpts) {
this.opts = opts; this.opts = opts;
this.runtimePart = Soma.require()
.runtime(navigationMenuMorfo, {})
.part('content', { id: opts.id, ref: opts.ref, owner: this });
this.provider = NavigationMenuProvider.require(); this.provider = NavigationMenuProvider.require();
this.item = NavigationMenuItemProvider.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; this.item.contentId.current = opts.id.current;
} }
@ -584,10 +584,12 @@ export class NavigationMenuLinkProvider {
private constructor(opts: NavigationMenuLinkOpts) { private constructor(opts: NavigationMenuLinkOpts) {
this.opts = opts; this.opts = opts;
this.runtimePart = Soma.require()
.runtime(navigationMenuMorfo, {})
.part('link', { id: opts.id, ref: opts.ref, owner: this });
this.provider = NavigationMenuProvider.require(); this.provider = NavigationMenuProvider.require();
this.runtimePart = this.provider.runtime.part('link', {
id: opts.id,
ref: opts.ref,
owner: this
});
} }
readonly onkeydown = (e: SomaKeyboardEvent<HTMLAnchorElement>) => { readonly onkeydown = (e: SomaKeyboardEvent<HTMLAnchorElement>) => {
@ -630,10 +632,12 @@ export class NavigationMenuIndicatorProvider {
private constructor(opts: NavigationMenuIndicatorOpts) { private constructor(opts: NavigationMenuIndicatorOpts) {
this.opts = opts; this.opts = opts;
this.runtimePart = Soma.require()
.runtime(navigationMenuMorfo, {})
.part('indicator', { id: opts.id, ref: opts.ref, owner: this });
this.provider = NavigationMenuProvider.require(); this.provider = NavigationMenuProvider.require();
this.runtimePart = this.provider.runtime.part('indicator', {
id: opts.id,
ref: opts.ref,
owner: this
});
$effect(() => { $effect(() => {
const openValue = this.provider.opts.value.current; const openValue = this.provider.opts.value.current;

@ -8,7 +8,7 @@ import { Soma } from '../../core/soma.svelte';
import { TAGS_INPUT_LANGS } from './langs'; import { TAGS_INPUT_LANGS } from './langs';
import { tagsInputMorfo } from '../../../morfo/components/tags-input'; import { tagsInputMorfo } from '../../../morfo/components/tags-input';
import type { SomaRuntimePart } from '../../runtime.svelte'; import type { SomaRuntime, SomaRuntimePart } from '../../runtime.svelte';
const attrs = createAttrs(tagsInputMorfo); const attrs = createAttrs(tagsInputMorfo);
@ -40,6 +40,8 @@ interface TagsInputOpts
export class TagsInputProvider { export class TagsInputProvider {
readonly opts: TagsInputOpts; readonly opts: TagsInputOpts;
readonly runtimePart: SomaRuntimePart; readonly runtimePart: SomaRuntimePart;
readonly soma: Soma;
readonly runtime: SomaRuntime;
static readonly ctx = context<TagsInputProvider>('TagsInput'); static readonly ctx = context<TagsInputProvider>('TagsInput');
static get(): TagsInputProvider | undefined { static get(): TagsInputProvider | undefined {
return this.ctx.getOr(undefined) as TagsInputProvider | undefined; return this.ctx.getOr(undefined) as TagsInputProvider | undefined;
@ -52,22 +54,20 @@ export class TagsInputProvider {
return new TagsInputProvider(opts); return new TagsInputProvider(opts);
} }
readonly soma = Soma.get();
// Focus tracking // Focus tracking
highlightedIndex = $state(-1); highlightedIndex = $state(-1);
inputRef = state<HTMLElement | null>(null); inputRef = state<HTMLElement | null>(null);
private constructor(opts: TagsInputOpts) { private constructor(opts: TagsInputOpts) {
this.opts = opts; this.opts = opts;
this.runtimePart = Soma.require() this.soma = Soma.require();
.runtime(tagsInputMorfo, {}) this.runtime = this.soma.runtime(tagsInputMorfo, {});
.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: TagsInputProvider.ctx context: TagsInputProvider.ctx
}); });
} }
readonly isEmpty = $derived.by(() => this.opts.value.current.length === 0); readonly isEmpty = $derived.by(() => this.opts.value.current.length === 0);
@ -224,10 +224,12 @@ export class TagsInputControlProvider {
private constructor(opts: TagsInputControlOpts) { private constructor(opts: TagsInputControlOpts) {
this.opts = opts; this.opts = opts;
this.runtimePart = Soma.require()
.runtime(tagsInputMorfo, {})
.part('control', { id: opts.id, ref: opts.ref, owner: this });
this.provider = TagsInputProvider.require(); this.provider = TagsInputProvider.require();
this.runtimePart = this.provider.runtime.part('control', {
id: opts.id,
ref: opts.ref,
owner: this
});
} }
readonly onclick = () => { readonly onclick = () => {
@ -264,17 +266,15 @@ export class TagsInputInputProvider {
private constructor(opts: TagsInputInputOpts) { private constructor(opts: TagsInputInputOpts) {
this.opts = opts; 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.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) => { readonly oninput = (e: Event) => {
@ -406,15 +406,13 @@ export class TagsInputItemProvider {
private constructor(opts: TagsInputItemOpts) { private constructor(opts: TagsInputItemOpts) {
this.opts = opts; 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.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( readonly isHighlighted = $derived.by(
@ -488,10 +486,12 @@ export class TagsInputItemTextProvider {
private constructor(opts: TagsInputItemTextOpts) { private constructor(opts: TagsInputItemTextOpts) {
this.opts = opts; 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.item = TagsInputItemProvider.require();
this.runtimePart = this.item.provider.runtime.part('item-text', {
id: opts.id,
ref: opts.ref,
owner: this
});
} }
readonly props = $derived.by(() => readonly props = $derived.by(() =>
@ -519,11 +519,13 @@ export class TagsInputItemDeleteTriggerProvider {
private constructor(opts: TagsInputItemDeleteTriggerOpts) { private constructor(opts: TagsInputItemDeleteTriggerOpts) {
this.opts = opts; 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.item = TagsInputItemProvider.require();
this.provider = this.item.provider; 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<HTMLButtonElement>) => { readonly onclick = (e: SomaMouseEvent<HTMLButtonElement>) => {
@ -538,7 +540,7 @@ export class TagsInputItemDeleteTriggerProvider {
...this.runtimePart.props, ...this.runtimePart.props,
type: 'button' as const, type: 'button' as const,
tabindex: -1, 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, disabled: this.item.isDisabled || this.provider.opts.readonly.current || undefined,
'data-disabled': boolToEmptyStrOrUndef(this.item.isDisabled), 'data-disabled': boolToEmptyStrOrUndef(this.item.isDisabled),
onclick: this.onclick onclick: this.onclick
@ -561,10 +563,12 @@ export class TagsInputClearTriggerProvider {
private constructor(opts: TagsInputClearTriggerOpts) { private constructor(opts: TagsInputClearTriggerOpts) {
this.opts = opts; 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.provider = TagsInputProvider.require();
this.runtimePart = this.provider.runtime.part('clear-trigger', {
id: opts.id,
ref: opts.ref,
owner: this
});
} }
readonly onclick = (_e: SomaMouseEvent<HTMLButtonElement>) => { readonly onclick = (_e: SomaMouseEvent<HTMLButtonElement>) => {
@ -576,7 +580,7 @@ export class TagsInputClearTriggerProvider {
this.runtimePart.assert({ this.runtimePart.assert({
...this.runtimePart.props, ...this.runtimePart.props,
type: 'button' as const, 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: hidden:
this.provider.isEmpty || this.provider.isEmpty ||
this.provider.opts.disabled.current || this.provider.opts.disabled.current ||

Loading…
Cancel
Save

Powered by TurnKey Linux.