lib0
Version:
isomorphic utility functions
223 lines • 10.7 kB
TypeScript
/**
* 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