/** * Prototype model and interpreter. * * Interactions live on layers (`node.properties.interactions`); flows, variables and presentation * settings live in `document.metadata.prototype`. Both are plain DocumentValue shapes, so the * document engine persists, validates and undoes them like any other design property. The * interpreter is pure: `stepPrototype(document, state, event, now)` returns the next state and the * side effects a player must perform; time enters only through `now`. */ import type { DesignDocument, DocumentDiagnostic, DocumentDuplicateMetadataRemapper, DocumentNode, DocumentResolveOptions, DocumentTransaction, ResolvedDocumentNode } from './contracts.js'; import type { PrototypeOccurrence, PrototypeRowPath } from './prototypeOccurrence.js'; import type { PrototypeDragAddress, PrototypeDragConfiguration } from './prototypeMotionContracts.js'; import { type PrototypeSpringPlan } from './prototypeSpring.js'; export type { PrototypeOccurrence, PrototypeRowPath } from './prototypeOccurrence.js'; export declare const PROTOTYPE_METADATA_KEY = "prototype"; export declare const PROTOTYPE_INTERACTIONS_PROPERTY = "interactions"; /** Upper bound for an authored transition in this player. */ export declare const PROTOTYPE_MAX_DURATION_MS = 10000; export declare const prototypeTriggerTypes: readonly ["click", "drag", "hover", "press", "key", "mouse-enter", "mouse-leave", "mouse-down", "mouse-up", "delay"]; export type PrototypeTriggerType = typeof prototypeTriggerTypes[number]; export declare const prototypeTriggerLabels: Record; export interface PrototypeTrigger { type: PrototypeTriggerType; key?: string; delayMs?: number; drag?: PrototypeDragConfiguration; } export declare const prototypeTransitionTypes: readonly ["instant", "dissolve", "smart-animate", "move-in", "move-out", "push", "slide-in", "slide-out"]; export type PrototypeTransitionType = typeof prototypeTransitionTypes[number]; export declare const prototypeTransitionLabels: Record; export declare const prototypeDirectionalTransitions: ReadonlySet; export declare const prototypeDirections: readonly ["left", "right", "up", "down"]; export type PrototypeDirection = typeof prototypeDirections[number]; export declare const prototypeEasingTypes: readonly ["linear", "ease-in", "ease-out", "ease-in-out", "ease-in-back", "ease-out-back", "ease-in-out-back", "custom-bezier", "spring"]; export type PrototypeEasingType = typeof prototypeEasingTypes[number]; export declare const prototypeSpringPresets: readonly ["gentle", "quick", "bouncy", "slow", "custom"]; export type PrototypeSpringPreset = typeof prototypeSpringPresets[number]; export type PrototypeEasing = { type: Exclude; } | { type: 'custom-bezier'; bezier: [number, number, number, number]; } | { type: 'spring'; preset: PrototypeSpringPreset; mass?: number; stiffness?: number; damping?: number; }; export interface PrototypeTransition { type: PrototypeTransitionType; direction?: PrototypeDirection; durationMs: number; easing: PrototypeEasing; /** Move, push and slide transitions may also animate layers that share a name. */ matchLayers?: boolean; } export declare const prototypeOverlayPositions: readonly ["center", "top-left", "top-center", "top-right", "bottom-left", "bottom-center", "bottom-right", "manual"]; export type PrototypeOverlayPosition = typeof prototypeOverlayPositions[number]; export interface PrototypeOverlaySettings { position: PrototypeOverlayPosition; x?: number; y?: number; closeOnOutsideClick: boolean; background?: { color: string; opacity: number; } | null; } export declare const prototypeVariableTypes: readonly ["boolean", "number", "string", "color"]; export type PrototypeVariableType = typeof prototypeVariableTypes[number]; export type PrototypeValue = boolean | number | string; export interface PrototypeVariable { id: string; name: string; type: PrototypeVariableType; value: PrototypeValue; description?: string; } export declare const prototypeBinaryOperators: readonly ["+", "-", "*", "/", "%", "==", "!=", "<", "<=", ">", ">=", "and", "or"]; export type PrototypeBinaryOperator = typeof prototypeBinaryOperators[number]; export type PrototypeExpression = { kind: 'literal'; value: PrototypeValue; } | { kind: 'variable'; variableId: string; } | { kind: 'unary'; operator: 'not' | 'negate'; operand: PrototypeExpression; } | { kind: 'binary'; operator: PrototypeBinaryOperator; left: PrototypeExpression; right: PrototypeExpression; }; export declare const prototypeActionTypes: readonly ["navigate", "change-to", "open-overlay", "swap-overlay", "close-overlay", "back", "scroll-to", "open-url", "set-variable", "conditional"]; export type PrototypeActionType = typeof prototypeActionTypes[number]; export declare const prototypeActionLabels: Record; export type PrototypeAction = { type: 'navigate'; destinationId: string; transition: PrototypeTransition; preserveScroll?: boolean; } /** Switches the enclosing instance to another component definition (a variant). */ | { type: 'change-to'; destinationId: string; transition: PrototypeTransition; } | { type: 'open-overlay'; destinationId: string; transition: PrototypeTransition; overlay: PrototypeOverlaySettings; } | { type: 'swap-overlay'; destinationId: string; transition: PrototypeTransition; } | { type: 'close-overlay'; transition: PrototypeTransition; } | { type: 'back'; transition: PrototypeTransition; } | { type: 'scroll-to'; targetId: string; transition: PrototypeTransition; } | { type: 'open-url'; url: string; newTab?: boolean; } | { type: 'set-variable'; variableId: string; value: PrototypeExpression; } | { type: 'conditional'; branches: { condition: PrototypeExpression; actions: PrototypeAction[]; }[]; otherwise?: PrototypeAction[]; }; export interface PrototypeInteraction { id: string; trigger: PrototypeTrigger; actions: PrototypeAction[]; } export interface PrototypeFlow { id: string; name: string; startingNodeId: string; description?: string; } export interface PrototypeSettings { background?: string; } export interface PrototypeDocument { version: 1; flows: PrototypeFlow[]; variables: PrototypeVariable[]; settings?: PrototypeSettings; } export declare const PROTOTYPE_VARIABLE_NAME: RegExp; export declare function defaultPrototypeTransition(type?: PrototypeTransitionType, direction?: PrototypeDirection): PrototypeTransition; export declare function defaultPrototypeOverlaySettings(): PrototypeOverlaySettings; export declare function defaultPrototypeAction(type: PrototypeActionType, destinationId?: string): PrototypeAction; export declare function defaultPrototypeTrigger(type: PrototypeTriggerType): PrototypeTrigger; /** CSS timing function for an easing; springs are approximated by the closest cubic curve. */ export declare function prototypeEasingCss(easing: PrototypeEasing): string; /** Where an overlay sits inside its screen; manual positions are authored in screen coordinates. */ export declare function prototypeOverlayPlacement(settings: PrototypeOverlaySettings, screen: { width: number; height: number; }, overlay: { width: number; height: number; }): { x: number; y: number; }; export type PrototypeExpressionResult = { expression: PrototypeExpression; error?: undefined; } | { expression?: undefined; error: string; }; /** Parses `count + 1`, `isOpen and total >= 3`, `"Hello, " + name`; variables are referenced by name. */ export declare function parsePrototypeExpression(source: string, variables: readonly PrototypeVariable[]): PrototypeExpressionResult; /** Inverse of the parser; unknown variables print as their id so nothing is silently lost. */ export declare function formatPrototypeExpression(expression: PrototypeExpression, variables: readonly PrototypeVariable[]): string; export type PrototypeValueType = 'boolean' | 'number' | 'string'; export type PrototypeTypeResult = { type: PrototypeValueType; error?: undefined; } | { type?: undefined; error: string; }; /** Static type of an expression against the declared variables. */ export declare function prototypeExpressionType(expression: PrototypeExpression, variables: readonly PrototypeVariable[]): PrototypeTypeResult; export type PrototypeEvaluation = { value: PrototypeValue; error?: undefined; } | { value?: undefined; error: string; }; export declare function evaluatePrototypeExpression(expression: PrototypeExpression, values: Readonly>): PrototypeEvaluation; export declare function prototypeValueAssignable(variable: PrototypeVariable, value: PrototypeValue): boolean; export declare function isPrototypeTransition(value: unknown): value is PrototypeTransition; export declare function isPrototypeOverlaySettings(value: unknown): value is PrototypeOverlaySettings; export declare function isPrototypeExpression(value: unknown): value is PrototypeExpression; export declare function isPrototypeTrigger(value: unknown): value is PrototypeTrigger; export declare function isPrototypeAction(value: unknown, depth?: number): value is PrototypeAction; export declare function isPrototypeInteraction(value: unknown): value is PrototypeInteraction; export declare function isPrototypeVariable(value: unknown): value is PrototypeVariable; export declare function isPrototypeFlow(value: unknown): value is PrototypeFlow; export declare function isPrototypeDocument(value: unknown): value is PrototypeDocument; export declare function emptyPrototypeDocument(): PrototypeDocument; /** The document's prototype sidecar, or an empty one; malformed sidecars are reported by `validatePrototype`. */ export declare function readPrototypeDocument(document: DesignDocument): PrototypeDocument; export declare function writePrototypeDocument(transaction: DocumentTransaction, value: PrototypeDocument | null): void; /** Well-formed interactions of a (resolved or canonical) node; malformed entries are skipped here and reported by validation. */ export declare function nodeInteractions(node: Pick): PrototypeInteraction[]; export declare function writeNodeInteractions(transaction: DocumentTransaction, node: DocumentNode, interactions: readonly PrototypeInteraction[]): void; export declare function hasPrototypeInteractions(node: Pick): boolean; /** Remaps only explicit node destinations; variable identities remain scoped to the same prototype. */ export declare function remapPrototypeInteractions(interactions: readonly PrototypeInteraction[], nodeIds: Readonly>): PrototypeInteraction[]; /** Existing flows keep their starting nodes; duplicating a layer does not create another flow. */ export declare function prototypeDuplicateRemapper(inner?: DocumentDuplicateMetadataRemapper): DocumentDuplicateMetadataRemapper; /** Prototype destinations are the root's container children, the equivalent of Figma's top-level frames. */ export declare function prototypeScreens(document: DesignDocument): DocumentNode[]; export declare function isPrototypeScreen(document: DesignDocument, nodeId: string): boolean; /** The screen that contains a node (itself when it is a screen); null for the root. */ export declare function prototypeScreenOf(document: DesignDocument, nodeId: string): string | null; export declare function prototypeUrlValid(url: string): boolean; /** Cross-references and Figma rules for every interaction, flow and variable; codes are `prototype-*`. */ export declare function validatePrototype(document: DesignDocument): DocumentDiagnostic[]; export interface PrototypeSceneLayer { /** Source (definition-relative) node id, the id a DOM surface reports back in event paths. */ nodeId: string; path: string[]; /** Names from the scene root down to this layer, the smart-animate hierarchy key. */ namePath: string; /** Opaque name segments; slashes in a name never form a hierarchy. */ nameSegments: string[]; instancePath: readonly string[]; name: string; kind: DocumentNode['kind']; x: number; y: number; width: number; height: number; opacity: number; fill: string | null; node: DocumentNode; depth: number; occurrence?: PrototypeOccurrence; } export interface PrototypeResolvedSceneNode extends Omit { rowPath: PrototypeRowPath; instancePath?: readonly string[]; children: PrototypeResolvedSceneNode[]; } /** Supplies the same expanded scene used by the rendering surface, without owning authoring data. */ export interface PrototypeRuntimeContext { resolveScene(document: DesignDocument, state: PrototypeState, rootId: string, sceneId: string): PrototypeResolvedSceneNode; } /** Stable occurrence key. Every ID is opaque; nested instances never share canonical state. */ export declare function prototypeVariantKey(instancePath: readonly string[], occurrence?: PrototypeOccurrence): string; /** Presentation-only variant resolution; neither the document nor shared definitions are changed. */ export declare function prototypeResolveOptions(document: DesignDocument, variants: Readonly>, occurrence?: PrototypeOccurrence): DocumentResolveOptions; /** Resolves runtime variants explicitly; invalid choices never silently render canonical state. */ export declare function resolvePrototypeScene(document: DesignDocument, rootId: string, variants?: Readonly>): ResolvedDocumentNode; export declare function prototypeSceneLayers(document: DesignDocument, rootId: string, variants?: Readonly>, options?: { state: PrototypeState; sceneId?: string; context?: PrototypeRuntimeContext; }): PrototypeSceneLayer[]; export interface PrototypeSmartAnimatePair { name: string; from: PrototypeSceneLayer; to: PrototypeSceneLayer; } export interface PrototypeSmartAnimatePlan { pairs: PrototypeSmartAnimatePair[]; leaving: PrototypeSceneLayer[]; entering: PrototypeSceneLayer[]; diagnostics: { code: string; message: string; }[]; } /** Model and DOM playback share occurrence-aware matching; ambiguous layers remain unmatched. */ export declare function matchSmartAnimateLayers(document: DesignDocument, fromScreenId: string, toScreenId: string, variants?: Readonly>, options?: { from: PrototypeState; to: PrototypeState; context?: PrototypeRuntimeContext; }): PrototypeSmartAnimatePlan; export interface PrototypeTimer { id: string; at: number; sceneId: string; nodeId: string; interactionId: string; } export interface PrototypeOverlayState { id: string; nodeId: string; settings: PrototypeOverlaySettings; transition: PrototypeTransition; openedAt: number; } export type PrototypeRevert = { type: 'variant'; instanceId: string; definitionId: string | null; } | { type: 'screen'; screenId: string; transition: PrototypeTransition; } | { type: 'overlay'; overlayId: string; }; export interface PrototypeHold { nodeId: string; trigger: 'hover' | 'press'; occurrence?: PrototypeOccurrence; /** Screen-space bounds of the hotspot when the hold began; a surface keeps it hit-testable across navigation. */ bounds: { sceneId: string; x: number; y: number; width: number; height: number; } | null; reverts: PrototypeRevert[]; } export type PrototypeTransitionKind = 'start' | 'navigate' | 'back' | 'overlay-open' | 'overlay-close' | 'overlay-swap'; export interface PrototypeTransitionState { kind: PrototypeTransitionKind; fromScreenId: string | null; toScreenId: string | null; overlayId: string | null; transition: PrototypeTransition; startedAt: number; endsAt: number; /** Physical spring plan shared by the interpreter deadline and the renderer. */ spring?: PrototypeSpringPlan; } export interface PrototypeState { flowId: string | null; screenId: string; history: string[]; overlays: PrototypeOverlayState[]; variables: Record; /** Runtime variant choices keyed by the authored instance path and optional scene/row occurrence. */ variants: Record; scroll: Record; scrollTarget: { sceneId: string; targetId: string; transition: PrototypeTransition; at: number; occurrence?: PrototypeOccurrence; } | null; timers: PrototypeTimer[]; holds: PrototypeHold[]; transition: PrototypeTransitionState | null; sequence: number; startedAt: number; now: number; } export type PrototypePointerEventType = 'click' | 'drag' | 'mouse-enter' | 'mouse-leave' | 'mouse-down' | 'mouse-up' | 'hover-start' | 'hover-end' | 'press-start' | 'press-end'; export type PrototypeEvent = /** `path` lists source node ids from the hit layer up to its scene root. */ { type: PrototypePointerEventType; path: readonly string[]; occurrence?: PrototypeOccurrence; } | { type: 'key'; key: string; } | { type: 'outside-click'; } | { type: 'tick'; } | { type: 'scroll'; sceneId: string; x: number; y: number; } | { type: 'restart'; flowId?: string | null; startingNodeId?: string; }; export type PrototypeEffect = { type: 'fired'; nodeId: string; interactionId: string; trigger: PrototypeTriggerType; } | { type: 'navigated'; kind: PrototypeTransitionKind; fromScreenId: string | null; toScreenId: string; transition: PrototypeTransition; } | { type: 'overlay'; kind: 'opened' | 'closed' | 'swapped'; overlayId: string; nodeId: string; } | { type: 'variable'; variableId: string; value: PrototypeValue; } | { type: 'variant'; instanceId: string; definitionId: string | null; } | { type: 'scroll-to'; sceneId: string; targetId: string; transition: PrototypeTransition; occurrence?: PrototypeOccurrence; } | { type: 'open-url'; url: string; newTab: boolean; } | { type: 'diagnostic'; code: string; message: string; nodeId?: string; }; export interface PrototypeStepResult { state: PrototypeState; effects: PrototypeEffect[]; } export interface PrototypeStartOptions { flowId?: string | null; startingNodeId?: string; now?: number; context?: PrototypeRuntimeContext; } /** The initial state of a flow (or of an explicit starting screen); delay triggers on the screen are armed. */ export declare function createPrototypeState(document: DesignDocument, options?: PrototypeStartOptions): PrototypeState; /** The earliest moment the state changes without input, or null when it is at rest. */ export declare function prototypeNextDeadline(state: PrototypeState): number | null; export type PrototypeDragPreparation = { status: 'none'; } | { status: 'fallback'; message: string; } | { status: 'ready'; configuration: PrototypeDragConfiguration; state: PrototypeState; effects: PrototypeEffect[]; transition: PrototypeTransition; }; /** Prepare a captured target without advancing timers or publishing effects. Authoring stays untouched. */ export declare function preparePrototypeDrag(document: DesignDocument, previous: PrototypeState, address: PrototypeDragAddress, now: number, runtime?: PrototypeRuntimeContext): PrototypeDragPreparation; /** One pure step: the event (or the passage of time) against a frozen document; time is `now`. */ export declare function stepPrototype(document: DesignDocument, previous: PrototypeState, event: PrototypeEvent, now: number, runtime?: PrototypeRuntimeContext): PrototypeStepResult;