Drive Feed attrs from Soma runtime

active-uix
dev 5 months ago
parent 82aa9af76d
commit 06d8926356

@ -18,13 +18,19 @@ export const feedMorfo = {
defaultElement: 'section',
role: 'feed',
optional: false,
data: [{ attr: 'data-busy', severity: 'optional' }],
data: [{ attr: 'data-busy', value: v.propRef('busy'), severity: 'optional' }],
aria: [
{
attr: 'aria-label',
value: v.translationRef('#?components.feed.label|Feed'),
value: v.propRef('ariaLabel'),
severity: 'recommended'
},
{
attr: 'aria-labelledby',
value: v.propRef('ariaLabelledby'),
condition: { when: 'prop-truthy', prop: 'ariaLabelledby' },
severity: 'optional'
},
{
attr: 'aria-busy',
value: v.literal('true'),
@ -48,8 +54,8 @@ export const feedMorfo = {
role: 'article',
optional: false,
data: [
{ attr: 'data-posinset' },
{ attr: 'data-level' }
{ attr: 'data-posinset', value: v.propRef('posinset'), emit: 'value' },
{ attr: 'data-level', value: v.propRef('level'), emit: 'value' }
],
aria: [
{ attr: 'aria-posinset', value: v.propRef('posinset') },
@ -96,7 +102,7 @@ export const feedMorfo = {
defaultElement: 'div',
role: 'feed',
optional: true,
data: [{ attr: 'data-level' }],
data: [{ attr: 'data-level', value: v.propRef('level'), emit: 'value' }],
aria: [
{
attr: 'aria-labelledby',
@ -112,7 +118,9 @@ export const feedMorfo = {
kind: 'public',
defaultElement: 'div',
optional: true,
data: [{ attr: 'data-intersecting', severity: 'optional' }],
data: [
{ attr: 'data-intersecting', value: v.propRef('intersecting'), severity: 'optional' }
],
aria: [{ attr: 'aria-hidden', value: v.literal('true') }]
}
]

@ -1,6 +1,5 @@
import { context, type WithRefOpts } from '../../provider';
import { createAttrs } from '$uix/morfo';
import { boolToEmptyStrOrUndef } from '$adom';
import { readableActive, state, type Active, type ActiveProps } from '$libs/reactive';
import type { SomaKeyboardEvent } from '../../types';
import { KEYS } from '../../keyboard';
@ -45,12 +44,19 @@ export class FeedProvider {
private constructor(opts: FeedOpts) {
this.opts = opts;
this.soma = Soma.require();
this.runtime = this.soma.runtime(feedMorfo, {});
this.runtime = this.soma.runtime(feedMorfo, {
props: {
busy: () => this.opts.busy.current,
ariaLabel: () => this.resolvedAriaLabel.current,
ariaLabelledby: () => this.opts.ariaLabelledby.current
}
});
this.runtimePart = this.runtime.part('provider', {
id: opts.id,
ref: opts.ref,
owner: this,
context: FeedProvider.ctx
context: FeedProvider.ctx,
syncAttrs: true
});
}
@ -132,12 +138,7 @@ export class FeedProvider {
readonly props = $derived.by(() =>
this.runtimePart.assert({
...this.runtimePart.props,
role: 'feed' as const,
'aria-label': this.resolvedAriaLabel.current,
'aria-labelledby': this.opts.ariaLabelledby.current,
'aria-busy': this.opts.busy.current ? true : undefined,
'data-busy': boolToEmptyStrOrUndef(this.opts.busy.current)
...this.runtimePart.props
} as const)
);
}
@ -176,17 +177,21 @@ export class FeedThreadProvider {
id: opts.id,
ref: opts.ref,
owner: this,
context: FeedThreadProvider.ctx
context: FeedThreadProvider.ctx,
props: {
level: () => this.level
},
parts: {
'article-title': () => this.parentArticle.titleId.current || undefined
},
syncAttrs: true
});
this.level = this.parentArticle.level + 1;
}
readonly props = $derived.by(() =>
this.runtimePart.assert({
...this.runtimePart.props,
role: 'feed' as const,
'aria-labelledby': this.parentArticle.titleId.current || undefined,
'data-level': this.level
...this.runtimePart.props
} as const)
);
}
@ -227,7 +232,17 @@ export class FeedArticleProvider {
id: opts.id,
ref: opts.ref,
owner: this,
context: FeedArticleProvider.ctx
context: FeedArticleProvider.ctx,
props: {
posinset: () => this.resolvedPosinset,
setsize: () => this.resolvedSetsize,
level: () => this.level
},
parts: {
'article-title': () => this.titleId.current || undefined,
'article-description': () => this.descriptionId.current || undefined
},
syncAttrs: true
});
this.level = this.thread ? this.thread.level : 1;
}
@ -275,21 +290,13 @@ export class FeedArticleProvider {
this.provider.handleArticleKeydown(e as SomaKeyboardEvent<HTMLElement>);
};
readonly props = $derived.by(() => {
const posinset = this.resolvedPosinset;
return this.runtimePart.assert({
readonly props = $derived.by(() =>
this.runtimePart.assert({
...this.runtimePart.props,
role: 'article' as const,
tabindex: 0,
'aria-posinset': posinset,
'aria-setsize': this.resolvedSetsize,
'aria-labelledby': this.titleId.current || undefined,
'aria-describedby': this.descriptionId.current || undefined,
'data-posinset': posinset,
'data-level': this.level,
onkeydown: this.onkeydown
} as const);
});
} as const)
);
}
// ── ArticleTitle ───────────────────────────────────────────────────────────
@ -311,7 +318,11 @@ export class FeedArticleTitleProvider {
this.runtimePart = this.article.provider.runtime.part('article-title', {
id: opts.id,
ref: opts.ref,
owner: this
owner: this,
props: {
level: () => this.resolvedLevel
},
syncAttrs: true
});
// A30: direct assignment.
this.article.titleId.current = opts.id.current;
@ -330,9 +341,7 @@ export class FeedArticleTitleProvider {
readonly props = $derived.by(() =>
this.runtimePart.assert({
...this.runtimePart.props,
role: 'heading' as const,
'aria-level': this.resolvedLevel
...this.runtimePart.props
} as const)
);
}
@ -404,7 +413,15 @@ export class FeedSentinelProvider {
this.soma = Soma.require();
this.feed = FeedProvider.get();
this.runtime = this.feed?.runtime ?? this.soma.runtime(feedMorfo, {});
this.runtimePart = this.runtime.part('sentinel', { id: opts.id, ref: opts.ref, owner: this });
this.runtimePart = this.runtime.part('sentinel', {
id: opts.id,
ref: opts.ref,
owner: this,
props: {
intersecting: () => this.intersecting
},
syncAttrs: true
});
$effect(() => {
const el = opts.ref.current;
@ -438,9 +455,7 @@ export class FeedSentinelProvider {
readonly props = $derived.by(() =>
this.runtimePart.assert({
...this.runtimePart.props,
'aria-hidden': true as const,
'data-intersecting': boolToEmptyStrOrUndef(this.intersecting)
...this.runtimePart.props
} as const)
);
}

Loading…
Cancel
Save

Powered by TurnKey Linux.