ripple
Version:
Ripple is an elegant TypeScript UI framework
186 lines (167 loc) • 5.38 kB
TypeScript
import type { Tracked, Derived } from './runtime.js';
import type { Context } from './context.js';
export { Tracked, Derived };
/** One `Context.set` in a component: the context, its value, the parent's chain. */
export type ContextEntry = {
k: Context<any>;
v: any;
n: null | ContextEntry;
};
export type Component = {
b: null | Block;
// The chain of context values set by this component and its ancestors.
c: null | ContextEntry;
// Effects deferred until the component has rendered, as flat triples:
// fn, the block to create it under, the reaction active at the call.
e: null | Array<Function | Block | Derived | null>;
p: null | Component;
m: boolean;
};
export type Dependency = {
// clock of the tracked value when it was read
c: number;
t: Tracked | Derived;
// next dependency of the same reaction
n: null | Dependency;
// the reaction that read the tracked value
r: Block | Derived;
// previous / next subscriber of the same tracked value
sp: null | Dependency;
sn: null | Dependency;
};
export type DeferredTrackedEntry = {
b: Block; // boundary block
r: number; // request version id
};
/**
* Anchor sentinel for one-shot appends at the end of `parent` (all-component
* children, portal content). Only valid at first render: a block that keeps
* inserting relative to its anchor must go through `resolve_anchor` first.
*/
export type AppendIntoAnchor = {
parent: Node;
/** Marks the sentinel; a missing-property read on a DOM node is cheaper than `in`. */
into: true;
/**
* The sentinel is the tail of `parent`: nothing the framework renders
* follows what it anchors, so an `@if` there never needs an anchor node.
*/
tail: boolean;
};
export type Block = {
co: null | Component;
d: null | Dependency;
first: null | Block;
f: number;
fn: any;
// creation id; flushes run scheduled blocks in this order
i: number;
last: null | Block;
next: null | Block;
p: null | Block;
prev: null | Block;
s: any;
// teardown function
/** teardown; runtime-internal teardowns receive the block state */
t: ((state?: any) => void) | null;
};
export type TryCatchFunction = (anchor: Node, error: any, reset?: () => void) => void;
export type TryPendingFunction = (anchor: Node) => void;
/**
* The state of a `@try` block or root boundary. Everything the boundary's
* pending, catch, request and streaming helpers need lives here, so those
* helpers are module functions that compile only when a boundary uses them.
*/
export type TryState = {
/** anchor */
a: Node;
/** try function */
fn: (anchor: Node, block?: Block) => void;
/** catch function */
c: TryCatchFunction | null;
/** pending function */
p: TryPendingFunction | null;
/** the catch branch's `reset` callback, created on first use */
r: (() => void) | null;
/** pending request count */
n: number;
/** request version */
v: number;
/** active request ids */
q: Set<number>;
/** the try block */
b: Block | null;
/** resolved branch */
rb: Block | null;
/** pending branch */
pb: Block | null;
/** catch branch */
cb: Block | null;
/** fragment holding the resolved branch while it is offscreen */
o: DocumentFragment | null;
/** has resolved */
h: boolean;
/** mode: 0 resolved, 1 pending, 2 catch */
m: 0 | 1 | 2;
/** deferred rejections by request id */
d: Map<number, (reason: any) => void>;
/** blocks paused on a pending read */
z: Set<Block>;
/** a streamed slot this boundary hydrated, until its chunk activates it */
si: string | null;
/** the streamed slot errored */
se: boolean;
/** the streamed slot carries a hydratable fallback */
sf: boolean;
/** streamed slot open marker */
so: Comment | null;
/** streamed slot close marker */
sc: Comment | null;
};
export type BlockWithTryBoundary = Omit<Block, 's'> & {
s: TryState;
};
export type BlockWithTryBoundaryAndCatch = Omit<BlockWithTryBoundary, 's'> & {
s: TryState & { c: TryCatchFunction };
};
export type RootBoundaryOptions = {
pending?: (anchor: Node, props: Record<string, never>, block: Block | null) => void;
catch?: (anchor: Node, props: { error: unknown; reset: () => void }, block: Block | null) => void;
};
/**
* Called by the inline stream runtime when a boundary's chunk arrives after
* hydration: receives the chunk template (null for error-only chunks) and a
* truthy flag when the unit errored server-side.
*/
export type StreamBoundaryActivator = (
template: HTMLTemplateElement | null,
errored?: number,
) => void;
/**
* `window.__RIPPLE_B__` — shared between the inline stream runtime and
* hydrated try boundaries. A boundary that hydrated a still-pending slot
* registers `{ a: activator }` under its unit id; the runtime stores `1`
* once the slot has been swapped or handed over.
*/
export type StreamBoundaryRegistry = Record<string | number, 1 | { a: StreamBoundaryActivator }>;
declare global {
interface Window {
/** streamed-boundary registry, see {@link StreamBoundaryRegistry} */
__RIPPLE_B__?: StreamBoundaryRegistry;
/** inline stream runtime entry point: swaps unit `id`'s chunk into its slot */
__RIPPLE_S__?: (id: number, errored?: number) => void;
}
interface Element {
__attributes?: {
value?: string;
};
__click?: () => void;
__ripple_block?: Block;
}
interface Event {
__root?: EventTarget;
}
interface HTMLSelectElement {
__value?: unknown;
}
}