85453da49f
CodeQL / Analyze (javascript-typescript) (push) Waiting to run
Docs / Validate docs (push) Waiting to run
CodeQL / Analyze (actions) (push) Waiting to run
CodeQL / Analyze (python) (push) Waiting to run
Sync skills to ClawHub / Publish changed skills (push) Waiting to run
regression / regression-shards (style-16-prod style-9-prod style-17-prod iframe-render-compat variables-prod mp4-h265-sdr, shard-4) (push) Has been cancelled
regression / regression-shards (style-4-prod style-11-prod style-2-prod animejs-adapter typegpu-adapter parallel-capture-regression, shard-5) (push) Has been cancelled
regression / regression-shards (style-7-prod style-8-prod style-10-prod css-spinner-render-compat webm-transparency mp4-h264-sdr webm-vp9, shard-3) (push) Has been cancelled
regression / regression-shards (sub-composition-video style-18-prod raf-ball-render-compat font-variant-numeric sub-comp-t0 sub-comp-id-selector, shard-7) (push) Has been cancelled
Windows render verification / Detect changes (push) Has been cancelled
Windows render verification / Preflight (lint + format) (push) Has been cancelled
Windows render verification / Render on windows-latest (push) Has been cancelled
Windows render verification / Tests on windows-latest (push) Has been cancelled
CI / Detect changes (push) Has been cancelled
CI / Build (push) Has been cancelled
CI / Lint (push) Has been cancelled
CI / Fallow audit (push) Has been cancelled
CI / Format (push) Has been cancelled
CI / Typecheck (push) Has been cancelled
CI / Test (push) Has been cancelled
CI / Producer: integration tests (push) Has been cancelled
CI / Producer: unit tests (push) Has been cancelled
CI / File size check (push) Has been cancelled
CI / Test: skills (push) Has been cancelled
CI / Skills: manifest in sync (push) Has been cancelled
CI / CLI: npx shim (macos-latest) (push) Has been cancelled
CI / CLI: npx shim (ubuntu-latest) (push) Has been cancelled
CI / CLI: npx shim (windows-latest) (push) Has been cancelled
CI / SDK: unit + contract + smoke (push) Has been cancelled
CI / Test: runtime contract (push) Has been cancelled
CI / Studio: load smoke (push) Has been cancelled
CI / Smoke: global install (push) Has been cancelled
CI / CLI smoke (required) (push) Has been cancelled
CI / Semantic PR title (push) Has been cancelled
Player perf / Detect changes (push) Has been cancelled
Player perf / Preflight (lint + format) (push) Has been cancelled
Player perf / player-perf (push) Has been cancelled
Player perf / Perf: drift (push) Has been cancelled
Player perf / Perf: fps (push) Has been cancelled
Player perf / Perf: parity (push) Has been cancelled
Player perf / Perf: scrub (push) Has been cancelled
Player perf / Perf: load (push) Has been cancelled
preview-regression / Detect changes (push) Has been cancelled
preview-regression / Preflight (lint + format) (push) Has been cancelled
preview-regression / Preview parity (push) Has been cancelled
preview-regression / preview-regression (push) Has been cancelled
regression / regression (push) Has been cancelled
regression / Detect changes (push) Has been cancelled
regression / Preflight (lint + format) (push) Has been cancelled
regression / regression-shards (hdr-regression style-5-prod style-3-prod mov-prores, shard-1) (push) Has been cancelled
regression / regression-shards (overlay-montage-prod style-12-prod chat missing-host-comp-id png-sequence portrait-edge-bleed, shard-6) (push) Has been cancelled
regression / regression-shards (style-13-prod style-6-prod vignelli-stacking gsap-letters-render-compat audio-mux-parity, shard-8) (push) Has been cancelled
regression / regression-shards (style-15-prod hdr-hlg-regression style-1-prod many-cuts vfr-screen-recording render-symlinked-assets, shard-2) (push) Has been cancelled
757 lines
23 KiB
Plaintext
757 lines
23 KiB
Plaintext
---
|
||
title: "Composition"
|
||
description: "The main editing surface returned by openComposition — query, mutate, animate, and serialize a composition."
|
||
---
|
||
|
||
`Composition` is the object returned by [`openComposition`](/sdk/reference/open-composition). Every SDK edit goes through this interface. Typed methods are convenience wrappers over `dispatch()`; all validation runs in `dispatch()` regardless of which entry point you use.
|
||
|
||
```typescript
|
||
import { openComposition } from "@hyperframes/sdk";
|
||
import type { Composition } from "@hyperframes/sdk";
|
||
|
||
const comp: Composition = await openComposition(html);
|
||
```
|
||
|
||
For a practical walkthrough see [Querying and editing](/sdk/guides/querying-and-editing).
|
||
|
||
---
|
||
|
||
## Typed edit methods
|
||
|
||
Typed methods are the recommended way to apply common edits. Each one calls `dispatch()` internally, so all patch events, history, and persistence behavior is identical.
|
||
|
||
### `setStyle`
|
||
|
||
```typescript
|
||
setStyle(id: HfId, styles: Record<string, string | null>): void
|
||
```
|
||
|
||
Apply inline CSS styles to one element. Use camelCase property names (matching `CSSStyleDeclaration`). Pass `null` as a value to remove that property.
|
||
|
||
```typescript
|
||
comp.setStyle("hf-title", { color: "#FFD60A", fontSize: "96px" });
|
||
comp.setStyle("hf-card", { borderRadius: null }); // removes border-radius
|
||
```
|
||
|
||
### `setText`
|
||
|
||
```typescript
|
||
setText(id: HfId, value: string): void
|
||
```
|
||
|
||
Replace the display text of an element. Targets the element's direct text content, not all descendant text.
|
||
|
||
```typescript
|
||
comp.setText("hf-headline", "Product launch — June 2026");
|
||
```
|
||
|
||
### `setAttribute`
|
||
|
||
```typescript
|
||
setAttribute(id: HfId, name: string, value: string | null): void
|
||
```
|
||
|
||
Set or remove an HTML attribute. Pass `null` to remove the attribute entirely.
|
||
|
||
```typescript
|
||
comp.setAttribute("hf-logo", "src", "/assets/logo-v2.png");
|
||
comp.setAttribute("hf-video", "autoplay", null); // removes autoplay
|
||
```
|
||
|
||
### `setTiming`
|
||
|
||
```typescript
|
||
setTiming(id: HfId, timing: { start?: number; duration?: number; trackIndex?: number }): void
|
||
```
|
||
|
||
Update the `data-start`, `data-duration`, and/or `data-track` attributes of one element. All fields are optional; omitted fields are left unchanged.
|
||
|
||
```typescript
|
||
comp.setTiming("hf-title", { start: 0.5, duration: 2.5 });
|
||
comp.setTiming("hf-cta", { trackIndex: 1 });
|
||
```
|
||
|
||
### `removeElement`
|
||
|
||
```typescript
|
||
removeElement(id: HfId): void
|
||
```
|
||
|
||
Remove an element and all its descendants from the composition. The inverse is an `addElement` of the removed subtree, so `undo()` restores it exactly.
|
||
|
||
```typescript
|
||
comp.removeElement("hf-old-badge");
|
||
```
|
||
|
||
### `addElement`
|
||
|
||
```typescript
|
||
addElement(parent: HfId | null, index: number, html: string): HfId
|
||
```
|
||
|
||
Insert an HTML fragment as a child of `parent` at zero-based sibling `index`. Pass `null` for `parent` to insert at the document body root. The fragment must be single-root and must not contain `<script>` tags. Returns the minted `hf-id` of the inserted root element.
|
||
|
||
```typescript
|
||
const newId = comp.addElement("hf-scene", 0, '<div class="clip">New clip</div>');
|
||
comp.setText(newId, "Inserted clip");
|
||
```
|
||
|
||
### `setVariableValue`
|
||
|
||
```typescript
|
||
setVariableValue(id: string, value: string | number | boolean | FontValue | ImageValue): void
|
||
```
|
||
|
||
Update a composition variable. Font variables require a `FontValue` object `{ name, source }`; image variables require an `ImageValue` object `{ url, alt?, fit? }`. Scalar variables accept `string | number | boolean`.
|
||
|
||
```typescript
|
||
comp.setVariableValue("brandColor", "#6C5CE7");
|
||
comp.setVariableValue("brandFont", { name: "Inter", source: "https://fonts.googleapis.com/css2?family=Inter" });
|
||
comp.setVariableValue("heroImage", { url: "/assets/hero.jpg", fit: "cover" });
|
||
```
|
||
|
||
<Note>
|
||
`setVariableValue` only updates a variable's current value — it refuses to create an undeclared variable. Use `declareVariable` to create one.
|
||
</Note>
|
||
|
||
### `getVariableValue`
|
||
|
||
```typescript
|
||
getVariableValue(id: string): string | number | boolean | FontValue | ImageValue | undefined
|
||
```
|
||
|
||
Return a declared variable's current `default` value, or `undefined` if the id is undeclared or has no value set.
|
||
|
||
```typescript
|
||
const color = comp.getVariableValue("brandColor"); // "#6C5CE7"
|
||
```
|
||
|
||
### `listVariables`
|
||
|
||
```typescript
|
||
listVariables(): CompositionVariable[]
|
||
```
|
||
|
||
Return every declared variable's full schema — `id`, `type`, `label`, `default`, and any type-specific fields (`min`/`max`/`step` for numbers, `options` for enums, `source` for fonts, etc). Returns `[]` when the composition declares no variables.
|
||
|
||
```typescript
|
||
for (const v of comp.listVariables()) {
|
||
console.log(v.id, v.type, v.default);
|
||
}
|
||
```
|
||
|
||
### `declareVariable` / `updateVariableDeclaration` / `removeVariableDeclaration`
|
||
|
||
```typescript
|
||
declareVariable(declaration: CompositionVariable): void
|
||
updateVariableDeclaration(id: string, declaration: CompositionVariable): void
|
||
removeVariableDeclaration(id: string): void
|
||
```
|
||
|
||
Manage the `data-composition-variables` schema itself. `declareVariable` adds a new
|
||
typed declaration (creating the attribute when the composition has none);
|
||
`updateVariableDeclaration` replaces an existing declaration wholesale — the id is
|
||
immutable, rename by remove + declare; `removeVariableDeclaration` deletes a
|
||
declaration (the last removal drops the whole attribute). All three validate via
|
||
`can()` (`E_DUPLICATE_VARIABLE`, `E_VARIABLE_NOT_FOUND`, `E_INVALID_ARGS`,
|
||
`E_INVALID_VARIABLE_ID` — the id must match `/^[A-Za-z_][A-Za-z0-9_-]*$/` since it
|
||
becomes a CSS custom-property name, `data-var-*` value, and CLI `--variables` key;
|
||
and `E_FRAGMENT_COMPOSITION`). Declarations live on the composition's declaration
|
||
root: the `<html>` element for a full document, or the composition root element
|
||
(`[data-composition-id]`) for a template / fragment sub-composition. Despite its
|
||
name, `E_FRAGMENT_COMPOSITION` now fires only when the source has **no** root
|
||
element at all to carry the schema — a fragment that has a composition root
|
||
accepts declarations. (The code is kept for compatibility; the fix is "add a
|
||
composition root element", not necessarily "add an `<html>`".)
|
||
|
||
```typescript
|
||
comp.declareVariable({ id: "title", type: "string", label: "Title", default: "Hello" });
|
||
comp.updateVariableDeclaration("title", { id: "title", type: "string", label: "Headline", default: "Hello" });
|
||
comp.removeVariableDeclaration("title");
|
||
```
|
||
|
||
<Note>`removeVariable(id)` is a shorthand alias of `removeVariableDeclaration(id)`.</Note>
|
||
|
||
### `getVariableDeclarations` / `getVariableValues` / `validateVariableValues` / `getVariableUsage`
|
||
|
||
```typescript
|
||
getVariableDeclarations(): CompositionVariable[]
|
||
getVariableValues(overrides?: Record<string, unknown>): Record<string, unknown>
|
||
validateVariableValues(values: Record<string, unknown>): VariableValidationIssue[]
|
||
getVariableUsage(): VariableUsageReport
|
||
```
|
||
|
||
Read-only variable APIs (no dispatch). `getVariableDeclarations` returns the typed
|
||
schema (same strict filter the render pipeline uses). `getVariableValues` resolves
|
||
values exactly like the runtime's `getVariables()` — declared defaults merged under
|
||
the given overrides — so callers can predict what a composition script will read for
|
||
a given `--variables` payload. `validateVariableValues` runs the same checks as
|
||
`--strict-variables` (`undeclared` / `type-mismatch` / `enum-out-of-range`).
|
||
`getVariableUsage` statically scans every inline script for `getVariables()` reads
|
||
and cross-references the schema: `{ usedIds, unusedDeclarations, undeclaredReads,
|
||
scanIncomplete }` — `scanIncomplete` flips when scripts access variables dynamically,
|
||
making `usedIds` a lower bound.
|
||
|
||
### `setPreviewVariables`
|
||
|
||
```typescript
|
||
setPreviewVariables(values: Record<string, unknown> | null): boolean
|
||
```
|
||
|
||
Apply ephemeral variable values to the preview surface (never persisted — use
|
||
`setVariableValue` to change a default). Pass `null` to restore declared defaults.
|
||
Delegates to the preview adapter's optional `setPreviewVariables`; returns `false`
|
||
when no adapter support is available.
|
||
|
||
### `getElementTimings`
|
||
|
||
```typescript
|
||
getElementTimings(): Record<HfId, ElementTimingSnapshot>
|
||
```
|
||
|
||
Return a map of enter/exit times and active GSAP labels for every timed element. The SDK derives `enterAt`/`exitAt` using `data-duration` when present, falling back to `data-end − data-start`. GSAP labels are parsed fresh from the script each call. Elements with no timing attributes are excluded.
|
||
|
||
```typescript
|
||
const timings = comp.getElementTimings();
|
||
for (const [id, t] of Object.entries(timings)) {
|
||
console.log(id, t.enterAt, t.exitAt, t.labels);
|
||
}
|
||
```
|
||
|
||
### `setElementTiming`
|
||
|
||
```typescript
|
||
setElementTiming(map: Record<HfId, { start?: number; duration?: number; trackIndex?: number }>): void
|
||
```
|
||
|
||
Apply a sparse timing map in one batch. All entries are dispatched inside a single `batch()` call so the history sees one undo step. Entries for unknown IDs are silently skipped.
|
||
|
||
```typescript
|
||
comp.setElementTiming({
|
||
"hf-title": { start: 0, duration: 2 },
|
||
"hf-caption": { start: 1.5, duration: 1.5 },
|
||
"hf-cta": { start: 3, duration: 2 },
|
||
});
|
||
```
|
||
|
||
### `setHold`
|
||
|
||
```typescript
|
||
setHold(id: HfId, hold: ElasticHold): void
|
||
```
|
||
|
||
Set an elastic hold window — a `{ start, end, fill }` range that either freezes or loops the element during a pause in the timeline.
|
||
|
||
```typescript
|
||
comp.setHold("hf-hero", { start: 2, end: 5, fill: "freeze" });
|
||
```
|
||
|
||
### `addGsapTween`
|
||
|
||
```typescript
|
||
addGsapTween(target: HfId, tween: GsapTweenSpec): string
|
||
```
|
||
|
||
Add a GSAP tween to the composition's timeline targeting the given element. Returns the newly assigned `animationId`.
|
||
|
||
```typescript
|
||
const animId = comp.addGsapTween("hf-title", {
|
||
method: "from",
|
||
position: 0,
|
||
duration: 0.6,
|
||
ease: "power3.out",
|
||
fromProperties: { opacity: 0, y: 40 },
|
||
});
|
||
```
|
||
|
||
### `setGsapTween`
|
||
|
||
```typescript
|
||
setGsapTween(animationId: string, properties: Partial<GsapTweenSpec>): void
|
||
```
|
||
|
||
Update properties on an existing tween. Only the fields you supply are changed; the rest remain.
|
||
|
||
```typescript
|
||
comp.setGsapTween(animId, { ease: "back.out(1.7)", duration: 0.8 });
|
||
```
|
||
|
||
### `removeGsapTween`
|
||
|
||
```typescript
|
||
removeGsapTween(animationId: string): void
|
||
```
|
||
|
||
Remove a tween by animation ID.
|
||
|
||
```typescript
|
||
comp.removeGsapTween(animId);
|
||
```
|
||
|
||
### `addWithKeyframes`
|
||
|
||
```typescript
|
||
addWithKeyframes(
|
||
targetSelector: string,
|
||
position: number,
|
||
duration: number,
|
||
keyframes: KeyframeSpec[],
|
||
ease?: string,
|
||
): string
|
||
```
|
||
|
||
Add a new keyframed tween for `targetSelector` at the given timeline position and duration. Returns the newly minted `animationId`. Position is a number in seconds (not a label-relative string).
|
||
|
||
```typescript
|
||
const animId = comp.addWithKeyframes(
|
||
"#hf-logo",
|
||
0.5,
|
||
1.2,
|
||
[
|
||
{ percentage: 0, properties: { opacity: 0, scale: 0.8 } },
|
||
{ percentage: 100, properties: { opacity: 1, scale: 1 } },
|
||
],
|
||
"power2.out",
|
||
);
|
||
```
|
||
|
||
### `replaceWithKeyframes`
|
||
|
||
```typescript
|
||
replaceWithKeyframes(
|
||
animationId: string,
|
||
targetSelector: string,
|
||
position: number,
|
||
duration: number,
|
||
keyframes: KeyframeSpec[],
|
||
ease?: string,
|
||
): string
|
||
```
|
||
|
||
Atomically remove an existing tween and add a replacement keyframed tween. Returns the replacement's `animationId`. Because position-derived IDs renumber after the removal, the returned ID may differ from the input `animationId` — treat the return value as the new canonical ID.
|
||
|
||
```typescript
|
||
const newId = comp.replaceWithKeyframes(
|
||
oldAnimId,
|
||
"#hf-card",
|
||
1.0,
|
||
0.8,
|
||
[
|
||
{ percentage: 0, properties: { x: -100 } },
|
||
{ percentage: 100, properties: { x: 0 } },
|
||
],
|
||
);
|
||
```
|
||
|
||
### `undo` / `redo`
|
||
|
||
```typescript
|
||
undo(): void
|
||
redo(): void
|
||
```
|
||
|
||
Step backward or forward through the undo history. No-ops when history is disabled (`history: false` in options) or in embedded mode.
|
||
|
||
```typescript
|
||
comp.setText("hf-title", "Draft");
|
||
comp.undo(); // reverts to original text
|
||
comp.redo(); // re-applies "Draft"
|
||
```
|
||
|
||
### `canUndo` / `canRedo`
|
||
|
||
```typescript
|
||
canUndo(): boolean
|
||
canRedo(): boolean
|
||
```
|
||
|
||
Return `true` when a step in that direction is available. Use to drive the enabled state of undo/redo UI controls.
|
||
|
||
```typescript
|
||
undoButton.disabled = !comp.canUndo();
|
||
redoButton.disabled = !comp.canRedo();
|
||
```
|
||
|
||
---
|
||
|
||
## Query
|
||
|
||
### `getElements`
|
||
|
||
```typescript
|
||
getElements(): ElementSnapshot[]
|
||
```
|
||
|
||
Return a flat array of all elements in the composition, including elements nested inside sub-compositions. Each `ElementSnapshot` carries the element's `id`, `scopedId`, `tag`, `inlineStyles`, `classNames`, `attributes`, `text`, timing fields, and `animationIds`. The array is a fresh copy; mutations to the returned objects have no effect.
|
||
|
||
```typescript
|
||
const elements = comp.getElements();
|
||
const images = elements.filter((el) => el.tag === "img");
|
||
```
|
||
|
||
<Note>
|
||
For elements inside inlined sub-compositions, use `scopedId` (e.g. `"hf-host/hf-leaf"`) as the dispatch target, not `id`. Top-level elements have `scopedId === id`.
|
||
</Note>
|
||
|
||
### `getRootElements`
|
||
|
||
```typescript
|
||
getRootElements(): ElementSnapshot[]
|
||
```
|
||
|
||
Return only top-level elements, each carrying its full subtree — no id appears twice (unlike `getElements`, which flattens every nested element into the same array). Use this when you need one entry per top-level clip rather than every element in the tree.
|
||
|
||
```typescript
|
||
const roots = comp.getRootElements();
|
||
```
|
||
|
||
### `getElement`
|
||
|
||
```typescript
|
||
getElement(id: HfId): ElementSnapshot | null
|
||
```
|
||
|
||
Return the snapshot for one element by ID. Accepts both bare IDs (top-level elements) and scoped IDs (sub-composition elements). Returns `null` if no element matches.
|
||
|
||
```typescript
|
||
const el = comp.getElement("hf-title");
|
||
if (el) {
|
||
console.log(el.tag, el.inlineStyles);
|
||
}
|
||
```
|
||
|
||
### `find`
|
||
|
||
```typescript
|
||
find(query: FindQuery): string[]
|
||
```
|
||
|
||
Search elements by structured query. Returns an array of `scopedId` strings for matching elements. All fields in `FindQuery` are optional and combined with AND logic.
|
||
|
||
| Field | Type | Description |
|
||
|-------|------|-------------|
|
||
| `tag` | `string` | Exact HTML tag name, lowercase (`"div"`, `"img"`). |
|
||
| `text` | `string` | Substring match against the element's `text` field. |
|
||
| `name` | `string` | Exact match against `data-name` attribute. |
|
||
| `track` | `number` | Exact track index (`data-track`). |
|
||
| `composition` | `string` | Filter to elements inside a specific sub-composition host by its `hf-id`. |
|
||
|
||
```typescript
|
||
const headlineIds = comp.find({ name: "headline" });
|
||
const allImages = comp.find({ tag: "img" });
|
||
const track1Ids = comp.find({ track: 1 });
|
||
```
|
||
|
||
### `getAllAnimationIds`
|
||
|
||
```typescript
|
||
getAllAnimationIds(): Set<string>
|
||
```
|
||
|
||
Return every GSAP tween id parsed from the composition's script, regardless of whether its target selector currently matches a live DOM element. This differs from a given `ElementSnapshot`'s own `animationIds` field, which only lists tweens whose selector resolves to that specific element.
|
||
|
||
```typescript
|
||
const allIds = comp.getAllAnimationIds();
|
||
```
|
||
|
||
---
|
||
|
||
## Selection
|
||
|
||
### `selection`
|
||
|
||
```typescript
|
||
selection(): SelectionProxy
|
||
```
|
||
|
||
Return a `SelectionProxy` that resolves `getSelection()` at call time and dispatches operations to all selected elements. The proxy captures the ID list when you call `selection()` — subsequent selection changes do not affect an already-captured proxy.
|
||
|
||
```typescript
|
||
const sel = comp.selection();
|
||
sel.setStyle({ opacity: "0.5" });
|
||
sel.removeElement(); // removes all currently selected elements
|
||
```
|
||
|
||
### `element`
|
||
|
||
```typescript
|
||
element(id: HfId): ElementHandle
|
||
```
|
||
|
||
Return a curried `ElementHandle` for a single element. The handle stores only the ID string, so there is no stale-reference hazard if the DOM changes between calls.
|
||
|
||
```typescript
|
||
const title = comp.element("hf-title");
|
||
title.setText("Hello");
|
||
title.setStyle({ fontWeight: "700" });
|
||
```
|
||
|
||
### `getSelection`
|
||
|
||
```typescript
|
||
getSelection(): string[]
|
||
```
|
||
|
||
Return a copy of the current selection as an array of `hf-id` strings. Returns `[]` when nothing is selected.
|
||
|
||
```typescript
|
||
const ids = comp.getSelection();
|
||
console.log(`${ids.length} elements selected`);
|
||
```
|
||
|
||
### `setSelection`
|
||
|
||
```typescript
|
||
setSelection(ids: string[]): void
|
||
```
|
||
|
||
Replace the current selection. Fires `selectionchange`. Pass `[]` to clear the selection. Duplicate IDs are deduplicated automatically.
|
||
|
||
```typescript
|
||
comp.setSelection(["hf-title", "hf-subtitle"]);
|
||
comp.setSelection([]); // clear
|
||
```
|
||
|
||
---
|
||
|
||
## Advanced / agent
|
||
|
||
### `dispatch`
|
||
|
||
```typescript
|
||
dispatch(op: EditOp, opts?: { origin?: unknown }): void
|
||
```
|
||
|
||
Apply a data-shaped edit operation. All typed methods call this internally. Use `dispatch` when you need to apply op types that do not have a typed wrapper, when you are building automation that constructs ops programmatically, or when you need to set a custom `origin`.
|
||
|
||
```typescript
|
||
comp.dispatch({ type: "setStyle", target: "hf-card", styles: { borderRadius: "24px" } });
|
||
comp.dispatch({ type: "reorderElements", entries: [{ target: "hf-bg", zIndex: 0 }] });
|
||
comp.dispatch({ type: "setCompositionMetadata", width: 1920, height: 1080, duration: 30 });
|
||
```
|
||
|
||
For the full op catalog see [Edit operations](/sdk/reference/edit-operations).
|
||
|
||
You can also target multiple elements with a single `dispatch` by passing an array to `target`:
|
||
|
||
```typescript
|
||
comp.dispatch({ type: "setText", target: ["hf-title", "hf-subtitle"], value: "Coming soon" });
|
||
```
|
||
|
||
### `batch`
|
||
|
||
```typescript
|
||
batch(fn: () => void, opts?: { origin?: unknown }): void
|
||
```
|
||
|
||
Coalesce multiple dispatches into one undo entry and one `patch` event. If the callback throws, all DOM mutations that ran inside the batch are rolled back atomically and the model is restored to its state before `batch()` was called.
|
||
|
||
```typescript
|
||
comp.batch(() => {
|
||
comp.setText("hf-title", "Version 2");
|
||
comp.setStyle("hf-title", { color: "#22C55E" });
|
||
comp.setTiming("hf-title", { start: 1, duration: 3 });
|
||
});
|
||
```
|
||
|
||
### `can`
|
||
|
||
```typescript
|
||
can(op: EditOp): CanResult
|
||
```
|
||
|
||
Dry-run validation. Returns `{ ok: true }` when `dispatch(op)` would succeed, or `{ ok: false, code, message, hint? }` when it would fail or be a no-op.
|
||
|
||
Use this as a feature-detection gate before rendering controls that depend on GSAP timeline availability, or before showing UI that applies optional edits.
|
||
|
||
```typescript
|
||
const result = comp.can({
|
||
type: "setGsapTween",
|
||
animationId: "anim-1",
|
||
properties: { ease: "power3.out" },
|
||
});
|
||
|
||
if (result.ok) {
|
||
comp.setGsapTween("anim-1", { ease: "power3.out" });
|
||
} else {
|
||
console.warn(result.code, result.message);
|
||
}
|
||
```
|
||
|
||
Stable error codes: `E_TARGET_NOT_FOUND`, `E_NO_ROOT`, `E_NO_GSAP_TIMELINE`, `E_NO_GSAP_SCRIPT`.
|
||
|
||
For the full op catalog see [Edit operations](/sdk/reference/edit-operations).
|
||
|
||
---
|
||
|
||
## Events
|
||
|
||
All `on()` overloads return an unsubscribe function. Call it to remove the listener.
|
||
|
||
```typescript
|
||
on(event: "change", handler: () => void): () => void
|
||
on(event: "selectionchange", handler: (ids: string[]) => void): () => void
|
||
on(event: "patch", handler: (event: PatchEvent) => void): () => void
|
||
on(event: "persist:error", handler: (event: PersistErrorEvent) => void): () => void
|
||
```
|
||
|
||
### `change`
|
||
|
||
Fires after every committed mutation (whether typed method, `dispatch`, or `batch`). Use this to refresh a canvas or other derived view.
|
||
|
||
```typescript
|
||
const off = comp.on("change", () => {
|
||
canvas.render(comp.getElements());
|
||
});
|
||
|
||
off(); // unsubscribe
|
||
```
|
||
|
||
### `selectionchange`
|
||
|
||
Fires when the selection changes, with the new ID array as the argument.
|
||
|
||
```typescript
|
||
comp.on("selectionchange", (ids) => {
|
||
propertiesPanel.show(ids);
|
||
});
|
||
```
|
||
|
||
### `patch`
|
||
|
||
Fires after every committed change with a `PatchEvent` containing RFC 6902 forward and inverse patches, the `origin`, and semantic `opTypes`. Use this to mirror edits into a host history stack, collaboration layer, or audit log.
|
||
|
||
```typescript
|
||
comp.on("patch", ({ patches, inversePatches, origin, opTypes }) => {
|
||
if (origin !== ORIGIN_APPLY_PATCHES) {
|
||
hostHistory.push({ patches, inversePatches });
|
||
}
|
||
});
|
||
```
|
||
|
||
<Warning>
|
||
Always guard against `ORIGIN_APPLY_PATCHES` in `patch` listeners. Failing to skip that origin causes an infinite loop when your host replays inverse patches back into the SDK via `applyPatches()`.
|
||
</Warning>
|
||
|
||
### `persist:error`
|
||
|
||
Fires when the persist adapter fails to write. The session continues; edits accumulate in memory and the queue retries on the next mutation.
|
||
|
||
```typescript
|
||
comp.on("persist:error", ({ error }) => {
|
||
toast.error(`Autosave failed: ${error.message}`);
|
||
});
|
||
```
|
||
|
||
For undo/redo and patch patterns see [Undo, redo, and patches](/sdk/guides/undo-redo-and-patches).
|
||
|
||
---
|
||
|
||
## Serialization
|
||
|
||
### `serialize`
|
||
|
||
```typescript
|
||
serialize(): string
|
||
```
|
||
|
||
Return the current composition as an HTML string reflecting all mutations applied since `openComposition`. The base HTML is never modified; this always returns a fresh serialization of the live document.
|
||
|
||
```typescript
|
||
const updatedHtml = comp.serialize();
|
||
await fs.writeFile("output.html", updatedHtml);
|
||
```
|
||
|
||
---
|
||
|
||
## Embedded extras
|
||
|
||
These methods are only meaningful in embedded / override mode. See the [embedded override mode guide](/sdk/guides/embedded-override-mode).
|
||
|
||
### `getOverrides`
|
||
|
||
```typescript
|
||
getOverrides(): OverrideSet
|
||
```
|
||
|
||
Return a copy of the current override set — the sparse delta accumulated on top of the base template. Store this instead of the full HTML when the base template is shared.
|
||
|
||
```typescript
|
||
const delta = comp.getOverrides();
|
||
await db.saveUserOverrides(userId, delta);
|
||
```
|
||
|
||
### `applyPatches`
|
||
|
||
```typescript
|
||
applyPatches(patches: readonly JsonPatchOp[], opts?: { origin?: unknown }): void
|
||
```
|
||
|
||
Apply RFC 6902 patches directly to the live document. The default origin is `ORIGIN_APPLY_PATCHES`, which prevents `patch` listeners from forwarding these back in an undo loop. Use this when the host owns the undo stack and needs to replay inverse patches from its own history.
|
||
|
||
```typescript
|
||
// Host undo: replay inverse patches from the host stack
|
||
const { inversePatches } = hostHistory.pop();
|
||
comp.applyPatches(inversePatches);
|
||
```
|
||
|
||
---
|
||
|
||
## Lifecycle
|
||
|
||
### `flush`
|
||
|
||
```typescript
|
||
flush(): Promise<void>
|
||
```
|
||
|
||
Drain the persist queue. Resolves when any pending write has been committed to the adapter. No-op when no persist adapter was provided. Call this before process exit or navigation away to avoid losing queued writes.
|
||
|
||
```typescript
|
||
comp.setText("hf-title", "Final title");
|
||
await comp.flush();
|
||
comp.dispose();
|
||
```
|
||
|
||
### `dispose`
|
||
|
||
```typescript
|
||
dispose(): void
|
||
```
|
||
|
||
Tear down the session: unsubscribe preview adapter listeners, stop the persist queue, stop the history module, and clear all event handlers. After `dispose()`, the `Composition` object is inert; continued calls produce no-ops or errors.
|
||
|
||
```typescript
|
||
comp.dispose();
|
||
```
|
||
|
||
<Note>
|
||
Always call `dispose()` when you are done with a session. Skipping it leaks listeners attached to the preview adapter.
|
||
</Note>
|
||
|
||
---
|
||
|
||
## Related
|
||
|
||
<CardGroup cols={2}>
|
||
<Card title="openComposition" icon="door-open" href="/sdk/reference/open-composition">
|
||
Session factory — all options and modes.
|
||
</Card>
|
||
<Card title="Edit operations" icon="bolt" href="/sdk/reference/edit-operations">
|
||
Full op catalog for dispatch() and can().
|
||
</Card>
|
||
<Card title="Types reference" icon="file-code" href="/sdk/reference/types">
|
||
All exported TypeScript types — ElementSnapshot, PatchEvent, GsapTweenSpec, and more.
|
||
</Card>
|
||
<Card title="Querying and editing" icon="magnifying-glass" href="/sdk/guides/querying-and-editing">
|
||
Practical guide to find, getElements, and typed edits.
|
||
</Card>
|
||
<Card title="Undo, redo, and patches" icon="arrow-uturn-left" href="/sdk/guides/undo-redo-and-patches">
|
||
History, patch events, ORIGIN_APPLY_PATCHES guard.
|
||
</Card>
|
||
<Card title="Embedded override mode" icon="layers" href="/sdk/guides/embedded-override-mode">
|
||
Template-driven products with host-owned history.
|
||
</Card>
|
||
</CardGroup>
|
||
|