Consolidate Soma form feed runtimes

active-uix
dev 5 months ago
parent a60d040282
commit 6df2f062ad

@ -5,7 +5,7 @@ import { ANNOUNCE_LANGS } from './langs';
import type { AnnouncePriority, AnnounceRole } from './types';
import { announceMorfo } from '../../../morfo/components/announce';
import type { SomaRuntimePart } from '../../runtime.svelte';
import type { SomaRuntime, SomaRuntimePart } from '../../runtime.svelte';
/** Visually-hidden style kept in-sync with the WAI "sr-only" recipe. */
@ -36,6 +36,8 @@ interface AnnounceOpts extends ProviderOpts, ActiveProps<{ defaultTimeout: numbe
export class AnnounceProvider {
readonly opts: AnnounceOpts;
readonly runtimePart: SomaRuntimePart;
readonly soma: Soma;
readonly runtime: SomaRuntime;
static readonly ctx = context<AnnounceProvider>('Announce');
static get(): AnnounceProvider | undefined {
return this.ctx.getOr(undefined) as AnnounceProvider | undefined;
@ -48,8 +50,6 @@ export class AnnounceProvider {
return new AnnounceProvider(opts);
}
readonly soma = Soma.get();
/** Current text for each internal region. Swap alternates so repeated announcements re-fire. */
politeA = $state('');
politeB = $state('');
@ -63,9 +63,14 @@ export class AnnounceProvider {
private constructor(opts: AnnounceOpts) {
this.opts = opts;
this.runtimePart = Soma.require()
.runtime(announceMorfo, {})
.part('provider', { id: opts.id, ref: opts.ref, owner: this, context: AnnounceProvider.ctx });
this.soma = Soma.require();
this.runtime = this.soma.runtime(announceMorfo, {});
this.runtimePart = this.runtime.part('provider', {
id: opts.id,
ref: opts.ref,
owner: this,
context: AnnounceProvider.ctx
});
$effect(() => {
return () => {
@ -134,7 +139,7 @@ export class AnnounceProvider {
readonly internalRegionStyle = VISUALLY_HIDDEN_STYLE;
readonly politeLabel = $derived.by(
() => this.soma?.langs.ts(ANNOUNCE_LANGS.LABEL) ?? 'Announcements'
() => this.soma.langs.ts(ANNOUNCE_LANGS.LABEL) ?? 'Announcements'
);
readonly props = $derived.by(() =>

@ -8,7 +8,7 @@ import { Soma } from '../../core/soma.svelte';
import { FEED_LANGS } from './langs';
import { feedMorfo } from '../../../morfo/components/feed';
import type { SomaRuntimePart } from '../../runtime.svelte';
import type { SomaRuntime, SomaRuntimePart } from '../../runtime.svelte';
const attrs = createAttrs(feedMorfo);
@ -28,6 +28,8 @@ interface FeedOpts
export class FeedProvider {
readonly opts: FeedOpts;
readonly runtimePart: SomaRuntimePart;
readonly soma: Soma;
readonly runtime: SomaRuntime;
static readonly ctx = context<FeedProvider>('Feed');
static get(): FeedProvider | undefined {
return this.ctx.getOr(undefined) as FeedProvider | undefined;
@ -40,18 +42,21 @@ export class FeedProvider {
return new FeedProvider(opts);
}
readonly soma = Soma.get();
private constructor(opts: FeedOpts) {
this.opts = opts;
this.runtimePart = Soma.require()
.runtime(feedMorfo, {})
.part('provider', { id: opts.id, ref: opts.ref, owner: this, context: FeedProvider.ctx });
this.soma = Soma.require();
this.runtime = this.soma.runtime(feedMorfo, {});
this.runtimePart = this.runtime.part('provider', {
id: opts.id,
ref: opts.ref,
owner: this,
context: FeedProvider.ctx
});
}
readonly resolvedAriaLabel: Active<string | undefined> = readableActive(() => {
if (this.opts.ariaLabelledby.current) return undefined;
return this.opts.ariaLabel.current || this.soma?.langs.ts(FEED_LANGS.LABEL) || undefined;
return this.opts.ariaLabel.current || this.soma.langs.ts(FEED_LANGS.LABEL) || undefined;
});
/** All article elements, in DOM order. */
@ -160,14 +165,19 @@ export class FeedThreadProvider {
}
readonly parentArticle: FeedArticleProvider;
readonly provider: FeedProvider;
readonly level: number;
private constructor(opts: FeedThreadOpts) {
this.opts = opts;
this.runtimePart = Soma.require()
.runtime(feedMorfo, {})
.part('thread', { id: opts.id, ref: opts.ref, owner: this, context: FeedThreadProvider.ctx });
this.parentArticle = FeedArticleProvider.require();
this.provider = this.parentArticle.provider;
this.runtimePart = this.provider.runtime.part('thread', {
id: opts.id,
ref: opts.ref,
owner: this,
context: FeedThreadProvider.ctx
});
this.level = this.parentArticle.level + 1;
}
@ -211,16 +221,14 @@ export class FeedArticleProvider {
private constructor(opts: FeedArticleOpts) {
this.opts = opts;
this.runtimePart = Soma.require()
.runtime(feedMorfo, {})
.part('article', {
id: opts.id,
ref: opts.ref,
owner: this,
context: FeedArticleProvider.ctx
});
this.provider = FeedProvider.require();
this.thread = FeedThreadProvider.get();
this.runtimePart = this.provider.runtime.part('article', {
id: opts.id,
ref: opts.ref,
owner: this,
context: FeedArticleProvider.ctx
});
this.level = this.thread ? this.thread.level : 1;
}
@ -299,10 +307,12 @@ export class FeedArticleTitleProvider {
private constructor(opts: FeedArticleTitleOpts) {
this.opts = opts;
this.runtimePart = Soma.require()
.runtime(feedMorfo, {})
.part('article-title', { id: opts.id, ref: opts.ref, owner: this });
this.article = FeedArticleProvider.require();
this.runtimePart = this.article.provider.runtime.part('article-title', {
id: opts.id,
ref: opts.ref,
owner: this
});
// A30: direct assignment.
this.article.titleId.current = opts.id.current;
}
@ -342,10 +352,12 @@ export class FeedArticleDescriptionProvider {
private constructor(opts: FeedArticleDescriptionOpts) {
this.opts = opts;
this.runtimePart = Soma.require()
.runtime(feedMorfo, {})
.part('article-description', { id: opts.id, ref: opts.ref, owner: this });
this.article = FeedArticleProvider.require();
this.runtimePart = this.article.provider.runtime.part('article-description', {
id: opts.id,
ref: opts.ref,
owner: this
});
this.article.descriptionId.current = opts.id.current;
}
@ -387,10 +399,9 @@ export class FeedSentinelProvider {
private constructor(opts: FeedSentinelOpts) {
this.opts = opts;
this.runtimePart = Soma.require()
.runtime(feedMorfo, {})
.part('sentinel', { id: opts.id, ref: opts.ref, owner: this });
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 });
$effect(() => {
const el = opts.ref.current;

@ -7,7 +7,7 @@ import { FormProvider } from '../form/form-provider.svelte';
import { fieldMorfo } from '../../../morfo/components/field';
import { Soma } from '../../core/soma.svelte';
import type { SomaRuntimePart } from '../../runtime.svelte';
import type { SomaRuntime, SomaRuntimePart } from '../../runtime.svelte';
// ── Root ─────────────────────────────────────────────────────────────────────
@ -30,6 +30,8 @@ interface FieldOpts
export class FieldProvider {
readonly opts: FieldOpts;
readonly runtimePart: SomaRuntimePart;
readonly soma: Soma;
readonly runtime: SomaRuntime;
static readonly ctx = context<FieldProvider>('Field');
static get(): FieldProvider | undefined {
return this.ctx.getOr(undefined) as FieldProvider | undefined;
@ -53,9 +55,14 @@ export class FieldProvider {
private constructor(opts: FieldOpts) {
this.opts = opts;
this.runtimePart = Soma.require()
.runtime(fieldMorfo, {})
.part('provider', { id: opts.id, ref: opts.ref, owner: this, context: FieldProvider.ctx });
this.soma = Soma.require();
this.runtime = this.soma.runtime(fieldMorfo, {});
this.runtimePart = this.runtime.part('provider', {
id: opts.id,
ref: opts.ref,
owner: this,
context: FieldProvider.ctx
});
// Auto-register / unregister with the parent Form (if `name` is set).
$effect(() => {
@ -174,10 +181,12 @@ export class FieldLabelProvider {
private constructor(opts: FieldLabelOpts) {
this.opts = opts;
this.runtimePart = Soma.require()
.runtime(fieldMorfo, {})
.part('label', { id: opts.id, ref: opts.ref, owner: this });
this.provider = FieldProvider.require();
this.runtimePart = this.provider.runtime.part('label', {
id: opts.id,
ref: opts.ref,
owner: this
});
this.provider.labelId.current = opts.id.current;
}
@ -207,10 +216,12 @@ export class FieldControlProvider {
private constructor(opts: FieldControlOpts) {
this.opts = opts;
this.runtimePart = Soma.require()
.runtime(fieldMorfo, {})
.part('control', { id: opts.id, ref: opts.ref, owner: this });
this.provider = FieldProvider.require();
this.runtimePart = this.provider.runtime.part('control', {
id: opts.id,
ref: opts.ref,
owner: this
});
}
readonly props = $derived.by(() =>
@ -246,10 +257,12 @@ export class FieldInputProvider {
private constructor(opts: FieldInputOpts) {
this.opts = opts;
this.runtimePart = Soma.require()
.runtime(fieldMorfo, {})
.part('input', { id: opts.id, ref: opts.ref, owner: this });
this.provider = FieldProvider.require();
this.runtimePart = this.provider.runtime.part('input', {
id: opts.id,
ref: opts.ref,
owner: this
});
this.provider.inputId.current = opts.id.current;
}
@ -303,10 +316,12 @@ export class FieldHelperTextProvider {
private constructor(opts: FieldHelperTextOpts) {
this.opts = opts;
this.runtimePart = Soma.require()
.runtime(fieldMorfo, {})
.part('helper-text', { id: opts.id, ref: opts.ref, owner: this });
this.provider = FieldProvider.require();
this.runtimePart = this.provider.runtime.part('helper-text', {
id: opts.id,
ref: opts.ref,
owner: this
});
this.provider.helperId.current = opts.id.current;
$effect(() => {
@ -340,10 +355,12 @@ export class FieldErrorTextProvider {
private constructor(opts: FieldErrorTextOpts) {
this.opts = opts;
this.runtimePart = Soma.require()
.runtime(fieldMorfo, {})
.part('error-text', { id: opts.id, ref: opts.ref, owner: this });
this.provider = FieldProvider.require();
this.runtimePart = this.provider.runtime.part('error-text', {
id: opts.id,
ref: opts.ref,
owner: this
});
this.provider.errorId.current = opts.id.current;
$effect(() => {
@ -370,16 +387,19 @@ interface FieldPrefixOpts extends WithRefOpts {}
export class FieldPrefixProvider {
readonly opts: FieldPrefixOpts;
readonly runtimePart: SomaRuntimePart;
readonly provider: FieldProvider;
static create(opts: FieldPrefixOpts) {
return new FieldPrefixProvider(opts);
}
private constructor(opts: FieldPrefixOpts) {
this.opts = opts;
this.runtimePart = Soma.require()
.runtime(fieldMorfo, {})
.part('prefix', { id: opts.id, ref: opts.ref, owner: this });
FieldProvider.require();
this.provider = FieldProvider.require();
this.runtimePart = this.provider.runtime.part('prefix', {
id: opts.id,
ref: opts.ref,
owner: this
});
}
readonly props = $derived.by(() =>
@ -397,16 +417,19 @@ interface FieldSuffixOpts extends WithRefOpts {}
export class FieldSuffixProvider {
readonly opts: FieldSuffixOpts;
readonly runtimePart: SomaRuntimePart;
readonly provider: FieldProvider;
static create(opts: FieldSuffixOpts) {
return new FieldSuffixProvider(opts);
}
private constructor(opts: FieldSuffixOpts) {
this.opts = opts;
this.runtimePart = Soma.require()
.runtime(fieldMorfo, {})
.part('suffix', { id: opts.id, ref: opts.ref, owner: this });
FieldProvider.require();
this.provider = FieldProvider.require();
this.runtimePart = this.provider.runtime.part('suffix', {
id: opts.id,
ref: opts.ref,
owner: this
});
}
readonly props = $derived.by(() =>

@ -12,7 +12,7 @@ import type { Form } from './form-core.svelte';
import { formMorfo } from '../../../morfo/components/form';
const attrs = createAttrs(formMorfo);
import type { SomaRuntimePart } from '../../runtime.svelte';
import type { SomaRuntime, SomaRuntimePart } from '../../runtime.svelte';
// ── Root provider ────────────────────────────────────────────────────────────
@ -30,6 +30,8 @@ export interface FormProviderOpts<TForm extends Form = Form>
export class FormProvider<TForm extends Form = Form> {
readonly opts: FormProviderOpts<TForm>;
readonly runtimePart: SomaRuntimePart;
readonly soma: Soma;
readonly runtime: SomaRuntime;
static readonly ctx = context<FormProvider>('Form');
static get(): FormProvider | undefined {
return this.ctx.getOr(undefined) as FormProvider | undefined;
@ -41,13 +43,16 @@ export class FormProvider<TForm extends Form = Form> {
return new FormProvider<TForm>(opts);
}
readonly soma = Soma.get();
private constructor(opts: FormProviderOpts<TForm>) {
this.opts = opts;
this.runtimePart = Soma.require()
.runtime(formMorfo, {})
.part('provider', { id: opts.id, ref: opts.ref, owner: this, context: FormProvider.ctx });
this.soma = Soma.require();
this.runtime = this.soma.runtime(formMorfo, {});
this.runtimePart = this.runtime.part('provider', {
id: opts.id,
ref: opts.ref,
owner: this,
context: FormProvider.ctx
});
}
get form(): TForm {
@ -142,14 +147,16 @@ export class FormSubmitProvider {
private constructor(opts: FormSubmitOpts) {
this.opts = opts;
this.runtimePart = Soma.require()
.runtime(formMorfo, {})
.part('submit', { id: opts.id, ref: opts.ref, owner: this });
this.provider = FormProvider.require();
this.runtimePart = this.provider.runtime.part('submit', {
id: opts.id,
ref: opts.ref,
owner: this
});
}
readonly resolvedLabel = $derived.by(
() => this.opts.ariaLabel.current ?? this.provider.soma?.langs.ts(FORM_LANGS.SUBMIT) ?? 'Submit'
() => this.opts.ariaLabel.current ?? this.provider.soma.langs.ts(FORM_LANGS.SUBMIT) ?? 'Submit'
);
readonly props = $derived.by(() =>
@ -182,14 +189,16 @@ export class FormResetProvider {
private constructor(opts: FormResetOpts) {
this.opts = opts;
this.runtimePart = Soma.require()
.runtime(formMorfo, {})
.part('reset', { id: opts.id, ref: opts.ref, owner: this });
this.provider = FormProvider.require();
this.runtimePart = this.provider.runtime.part('reset', {
id: opts.id,
ref: opts.ref,
owner: this
});
}
readonly resolvedLabel = $derived.by(
() => this.opts.ariaLabel.current ?? this.provider.soma?.langs.ts(FORM_LANGS.RESET) ?? 'Reset'
() => this.opts.ariaLabel.current ?? this.provider.soma.langs.ts(FORM_LANGS.RESET) ?? 'Reset'
);
readonly onclick = () => {
@ -229,10 +238,12 @@ export class FormErrorSummaryProvider {
private constructor(opts: FormErrorSummaryOpts) {
this.opts = opts;
this.runtimePart = Soma.require()
.runtime(formMorfo, {})
.part('error-summary', { id: opts.id, ref: opts.ref, owner: this });
this.provider = FormProvider.require();
this.runtimePart = this.provider.runtime.part('error-summary', {
id: opts.id,
ref: opts.ref,
owner: this
});
}
readonly formErrors: string[] = $derived.by(() => this.provider.form.errors[''] ?? []);

Loading…
Cancel
Save

Powered by TurnKey Linux.