UNPKG

lib0

Version:

isomorphic utility functions

223 lines • 10.7 kB
/** * One child slot of a {@link ProjectionTransformer}'s fixed output layout: a run of static text, a * single static node/embed, or a single hole (filled by a sub-transformer's output). * * A hole's `carrier` records how its sub-transformer's output is placed: `'value'` lifts a * `lib0:value` carrier to a bare scalar embed (1 position, replaced on update); `'node'` embeds a * structural node verbatim (a nested `project`'s render, or a non-value carrier) and forwards its * incremental change as a `modify`. The kind is fixed once: known at `init` for auto-wrapped nested * projects, otherwise sniffed from the first render and cached (`lastScalar` keeps the last lifted * scalar so a view deletion of a value slot can be self-healed). * * @typedef {{ kind: 'text', str: string, format?: any, attribution?: any } * | { kind: 'static', el: any, format?: any, attribution?: any } * | { kind: 'hole', t: Transformer<any,any>, el: any, carrier?: 'value'|'node', lastScalar?: any, format?: any, attribution?: any }} ChildItem */ /** * Stateful transformer produced by {@link ProjectionTemplate}. Side A is the data (the * *projectionDelta*); side B is the projected structure - the spec with each hole filled by its * sub-transformer's output. `project` resolves the `lib0:value` carrier itself: at an *attribute* * position (keyed) and at a *child* position (positional, 1->1) it lifts the carrier to its bare * scalar. A child hole that is a nested `project` (or any non-`lib0:value` carrier) is embedded as a * node. A `lib0:inline` fragment is left intact for a downstream `inline(['lib0:inline'])`. * * The output layout is *fixed*: every hole occupies exactly one attribute or one child node, and * static content is constant. So `applyA` routes data changes into holes, and `applyB` routes view * edits at *node*-hole positions back to holes while **self-healing** edits to static content and to * value slots (a bare scalar has no `modify` channel, so a value-slot view edit is not round-tripped * to data in v1 - use an attribute hole for an editable binding). * * @extends {Transformer<any,any>} */ export class ProjectionTransformer extends Transformer<any, any> { /** * @param {import('../../schema.js').Schema<delta.Delta<any>>} $in * @param {import('../../schema.js').Schema<delta.Delta<any>>} $out * @param {string} name * @param {Array<{ key: string|number, value: any, attribution?: any }>} staticAttrs * @param {Array<{ key: string|number, t: Transformer<any,any> }>} attrHoles * @param {Array<ChildItem>} items child slots in output order */ constructor($in: import("../../schema.js").Schema<delta.Delta<any>>, $out: import("../../schema.js").Schema<delta.Delta<any>>, name: string, staticAttrs: Array<{ key: string | number; value: any; attribution?: any; }>, attrHoles: Array<{ key: string | number; t: Transformer<any, any>; }>, items: Array<ChildItem>); name: string; staticAttrs: { key: string | number; value: any; attribution?: any; }[]; attrHoles: { key: string | number; t: Transformer<any, any>; }[]; items: ChildItem[]; /** * @type {Array<{ start: number, len: number, item: ChildItem }>} */ segs: Array<{ start: number; len: number; item: ChildItem; }>; initialized: boolean; /** * Lift a child hole's sub-transformer output and place it on `out`. Returns the output count (1). * For a value carrier the bare scalar is inserted (and cached for self-heal); otherwise the node is * embedded verbatim. The hole's `carrier` kind is sniffed once here and cached. * * @param {delta.DeltaBuilderAny} out * @param {ChildItem & { kind: 'hole' }} item * @param {delta.DeltaAny?} rb the sub-transformer's B output */ _placeHole(out: delta.DeltaBuilderAny, item: ChildItem & { kind: "hole"; }, rb: delta.DeltaAny | null): void; /** * Initial render: emit the full structure with every hole filled. * * @param {delta.DeltaBuilderAny} d */ _render(d: delta.DeltaBuilderAny): { a: delta.DeltaBuilder<any, false> | null; b: delta.DeltaBuilder<any, false> | null; isEmpty(): boolean; clear(): void; applyA(a: delta.DeltaBuilder<any, false> | null): /*elided*/ any; applyB(b: delta.DeltaBuilder<any, false> | null): /*elided*/ any; reverse(): /*elided*/ any; }; /** * Incremental data change: emit only the hole updates. A value hole is *replaced* * (`delete(1).insert([scalar])`, since a scalar embed has no inner structure to modify); a node * hole forwards its sub-transformer's incremental change as a `modify`. * * @param {delta.DeltaBuilderAny} d */ _update(d: delta.DeltaBuilderAny): { a: delta.DeltaBuilder<any, false> | null; b: delta.DeltaBuilder<any, false> | null; isEmpty(): boolean; clear(): void; applyA(a: delta.DeltaBuilder<any, false> | null): /*elided*/ any; applyB(b: delta.DeltaBuilder<any, false> | null): /*elided*/ any; reverse(): /*elided*/ any; }; /** * Reconstruct the content occupying output positions `[start, start+len)` as insert ops on `out` * (used to self-heal a view delete). Static text/nodes are restored from the spec, and a value * slot is restored from its last-rendered scalar. Node-hole positions are skipped (their dynamic * structure cannot be restored without the current data). * * @param {delta.DeltaBuilderAny} out * @param {number} start * @param {number} len */ _restore(out: delta.DeltaBuilderAny, start: number, len: number): void; } /** * Template that projects the *projectionDelta* onto a fixed structure described by `spec` - an * ordinary {@link import('../delta.js').create delta} whose attribute values and inserted children * may be transformer {@link Template templates} ("holes"). Each hole receives the whole * projectionDelta; its output is placed at that position. The spec is a *write-only template*: it is * walked once at {@link ProjectionTemplate#init} and never fingerprinted/diffed. * * Holes resolve themselves: a `lib0:value` carrier (produced by {@link import('./attr.js').attr}) is * lifted to a bare scalar; a nested subtree that contains a hole is **auto-wrapped** into a nested * `project` at `init`, so deep structure composes through nested templates with the recursion built * once (bounded by spec depth). Plain static subtrees are embedded verbatim. * * @template {delta.DeltaConf} [IN=any] * @template {delta.DeltaConf} [SpecConf=any] * @extends {Template<IN, ProjectOutput<SpecConf>>} */ export class ProjectionTemplate<IN extends delta.DeltaConf = any, SpecConf extends delta.DeltaConf = any> extends Template<IN, delta.DeltaConfOverwrite<delta.AsDeltaConf<Omit<SpecConf, "children" | "attrs"> & (SpecConf extends { attrs: infer A; } ? { attrs: ResolveAttrs<A>; } : {}) & (SpecConf extends { children: infer Ch; } ? { children: ResolveChild<Ch>; } : {})>, {}>> { /** * @param {import('../../schema.js').Schema<delta.Delta<IN>>} $d * @param {delta.Delta<SpecConf>} spec */ constructor($d: import("../../schema.js").Schema<delta.Delta<IN>>, spec: delta.Delta<SpecConf>); spec: delta.Delta<SpecConf>; } export function project<IN extends delta.DeltaConf, SpecConf extends delta.DeltaConf>($d: import("../../schema.js").Schema<delta.Delta<IN>>, spec: delta.Delta<SpecConf>): ProjectionTemplate<IN, SpecConf>; /** * Resolve a hole template's output conf `OUT` to its projected placement type: a `lib0:value` carrier * lifts to its bare scalar; any other node is embedded as a `Delta<OUT>`. */ export type ResolveHoleOut<OUT extends delta.DeltaConf> = OUT extends { name: "lib0:value"; attrs: { value: infer V; }; } ? V : delta.Delta<OUT>; /** * Resolve one spec child element to its projected output type, distributing over a children union: a * hole ({@link Template}, whose output conf the spec carries) resolves via its `OUT`, a nested spec * delta recurses, and anything else (text, a static scalar/node) is kept verbatim. */ export type ResolveChild<C> = C extends Template<any, infer OUT extends delta.DeltaConf> ? ResolveHoleOut<OUT> : C extends delta.Delta<infer NC extends delta.DeltaConf> ? delta.Delta<ProjectOutput<NC>> : C; /** * Resolve a spec's attrs map: a hole attribute value resolves via its output conf; a static value is kept. */ export type ResolveAttrs<A> = { [K in keyof A]: A[K] extends Template<any, infer OUT extends delta.DeltaConf> ? ResolveHoleOut<OUT> : A[K]; }; /** * The concrete output conf of a `project($d, spec)` whose spec has conf `SpecConf`: static structure * is kept, value holes lift to their scalar, nested specs / node holes recurse. Holes are schema-bound * {@link Template}s, so their output conf is already concrete in `SpecConf` (no input param needed). */ export type ProjectOutput<SpecConf extends delta.DeltaConf> = import("./core.js").ResolveOut<delta.AsDeltaConf<Omit<SpecConf, "attrs" | "children"> & (SpecConf extends { attrs: infer A; } ? { attrs: ResolveAttrs<A>; } : {}) & (SpecConf extends { children: infer Ch; } ? { children: ResolveChild<Ch>; } : {})>>; /** * One child slot of a {@link ProjectionTransformer}'s fixed output layout: a run of static text, a * single static node/embed, or a single hole (filled by a sub-transformer's output). * * A hole's `carrier` records how its sub-transformer's output is placed: `'value'` lifts a * `lib0:value` carrier to a bare scalar embed (1 position, replaced on update); `'node'` embeds a * structural node verbatim (a nested `project`'s render, or a non-value carrier) and forwards its * incremental change as a `modify`. The kind is fixed once: known at `init` for auto-wrapped nested * projects, otherwise sniffed from the first render and cached (`lastScalar` keeps the last lifted * scalar so a view deletion of a value slot can be self-healed). */ export type ChildItem = { kind: "text"; str: string; format?: any; attribution?: any; } | { kind: "static"; el: any; format?: any; attribution?: any; } | { kind: "hole"; t: Transformer<any, any>; el: any; carrier?: "value" | "node"; lastScalar?: any; format?: any; attribution?: any; }; import { Transformer } from './core.js'; import * as delta from '../delta.js'; import { Template } from './core.js'; //# sourceMappingURL=project.d.ts.map