You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
svelte-kit-vice/src/uix/soma/morfo/resolver.ts

143 lines
4.4 KiB

/**
* Pure morfo → attrs resolver.
*
* Walks a `MorfoPart`'s `data` and `aria` declarations, evaluates conditions,
* resolves value sources against the supplied bindings, and returns a flat
* attribute map ready to render or to pass to `dom.apply`.
*
* No reactivity, no side-effects, no DOM. The runtime layer wraps these in
* effects; the Provider base class wraps them in a `$derived.by`.
*/
import type {
MorfoAriaEntry,
MorfoCondition,
MorfoData,
MorfoPart,
MorfoPrimitiveValueSource,
MorfoValueSource
} from '../../morfo/types';
export interface MorfoBindings {
props?: Record<string, unknown>;
states?: Record<string, unknown>;
parts?: Record<string, unknown>;
translations?: (key: string) => string | undefined;
}
export function findMorfoPart(parts: readonly MorfoPart[], target: string): MorfoPart | undefined {
for (const part of parts) {
if (part.kebab === target) return part;
if (part.parts) {
const nested = findMorfoPart(part.parts, target);
if (nested) return nested;
}
}
return undefined;
}
export function shouldEmitMorfoEntry(
condition: MorfoCondition | undefined,
bindings: MorfoBindings
): boolean {
if (!condition || condition === 'always') return true;
if (condition.when === 'part-present') return Boolean(bindings.parts?.[condition.part]);
if (condition.when === 'state-equals') return bindings.states?.[condition.state] === condition.value;
if (condition.when === 'prop-truthy') return Boolean(bindings.props?.[condition.prop]);
if (condition.when === 'prop-falsy') return !bindings.props?.[condition.prop];
return true;
}
function resolveMorfoPrimitiveSource(
source: MorfoPrimitiveValueSource,
bindings: MorfoBindings
): unknown {
if (source.kind === 'literal') return source.value;
if (source.kind === 'stateRef') return bindings.states?.[source.state];
if (source.kind === 'partRef') return bindings.parts?.[source.target];
if (source.kind === 'propRef') return bindings.props?.[source.prop];
if (source.kind === 'translationRef') return bindings.translations?.(source.key);
return undefined;
}
export function resolveMorfoSource(source: MorfoValueSource, bindings: MorfoBindings): unknown {
if (source.kind !== 'mapRef') {
return resolveMorfoPrimitiveSource(source, bindings);
}
const raw = resolveMorfoPrimitiveSource(source.source, bindings);
if (raw === undefined || raw === null) {
return source.fallback;
}
const mapped = source.map[String(raw)];
return mapped ?? source.fallback;
}
export function resolveMorfoDataValue(data: MorfoData, bindings: MorfoBindings): unknown {
const source = data.value;
if (!source) return undefined;
const raw = resolveMorfoSource(source, bindings);
if (!data.values || data.values.length === 0) {
return raw ? '' : undefined;
}
if (source.kind === 'stateRef' && typeof raw === 'boolean') {
if (raw) return source.state;
return data.values.find((value) => value !== source.state);
}
return raw;
}
export function resolveMorfoAriaValue(entry: MorfoAriaEntry, bindings: MorfoBindings): unknown {
const raw = resolveMorfoSource(entry.value, bindings);
if (entry.value.kind === 'stateRef') {
// ARIA boolean attrs require literal 'true'/'false' strings, not the
// HTML boolean attribute semantics. `aria-pressed="false"` is a valid
// state distinct from the attribute being absent — never remove it
// based on falsy values.
return Boolean(raw) ? 'true' : 'false';
}
return raw;
}
/**
* Resolves a part's full attribute map (role + data-* + aria-*) against the
* given bindings. Returns `undefined`-valued entries for declared attrs that
* shouldn't be emitted in the current state — the caller decides whether to
* skip them (Svelte does it automatically; `dom.apply` removes them).
*/
export function resolvePartAttrs(
partMeta: MorfoPart,
bindings: MorfoBindings
): Record<string, unknown> {
const result: Record<string, unknown> = {};
if (partMeta.role) {
result.role = partMeta.role;
}
for (const data of partMeta.data) {
if (!data.value) continue;
if (!shouldEmitMorfoEntry(data.condition, bindings)) continue;
const value = resolveMorfoDataValue(data, bindings);
if (value !== undefined) {
result[data.attr] = value;
}
}
for (const aria of partMeta.aria) {
if (!shouldEmitMorfoEntry(aria.condition, bindings)) continue;
const value = resolveMorfoAriaValue(aria, bindings);
if (value !== undefined) {
result[aria.attr] = value;
}
}
return result;
}

Powered by TurnKey Linux.