mirror of
https://github.com/hanzoai/frames.git
synced 2026-09-24 00:08:03 +00:00
Merge pull request #2108 from heygen-com/docs/sdk-attachsync-and-variable-crud
docs(sdk): document attachSync, variable CRUD, and getRootElements/getAllAnimationIds
This commit is contained in:
@@ -32,6 +32,23 @@ The callback references `comp` before it is declared — that is intentional and
|
||||
|
||||
The `dispatch` callback is optional. Omitting it means `commitPreview()` is a no-op, which is useful if you want to handle op derivation yourself.
|
||||
|
||||
## Keeping the preview in sync
|
||||
|
||||
Everything above wires up hit-testing and drag — but the iframe still won't reflect edits made any other way (an inspector panel calling `comp.setStyle()` directly, an undo, a collaborator's change replayed via `applyPatches()`). `attachSync` closes that gap: call it once you have both `preview` and `comp`, and every future edit — including undo/redo — mirrors onto the live iframe automatically.
|
||||
|
||||
```typescript
|
||||
const detach = preview.attachSync(comp);
|
||||
|
||||
// later, when the editor unmounts or swaps compositions:
|
||||
detach();
|
||||
```
|
||||
|
||||
`attachSync` does an immediate full sync of `comp`'s current state first (so re-opening a composition with existing overrides isn't a blank iframe), then subscribes to the same `patch` event your other listeners use. You don't need to write your own mirroring code, and you don't need a separate mechanism for undo/redo — both flow through the same subscription. Script-tag edits (GSAP script rewrites) are the one thing it never mirrors, since replaying a live `<script>` tag doesn't re-execute it.
|
||||
|
||||
<Note>
|
||||
Calling `attachSync` again with a different `comp` detaches the previous subscription first — useful if your editor swaps which composition an iframe is bound to without remounting it.
|
||||
</Note>
|
||||
|
||||
## Hit-testing: finding what the user clicked
|
||||
|
||||
`preview.elementAtPoint(x, y)` performs a synchronous hit-test at coordinates in the iframe's own coordinate space and returns the nearest `[data-hf-id]` element, or `null` for a transparent hit.
|
||||
|
||||
@@ -92,6 +92,7 @@ interface PreviewAdapter {
|
||||
cancelPreview(): void;
|
||||
select(ids: string[], opts?: { additive?: boolean }): void;
|
||||
on(event: "selection", handler: (ids: string[]) => void): () => void;
|
||||
attachSync(comp: Composition): () => void;
|
||||
}
|
||||
```
|
||||
|
||||
@@ -119,6 +120,21 @@ interface PreviewAdapter {
|
||||
Fired when the preview host changes the selection (for example, the user clicks an element). Returns an unsubscribe function. In the current release, callers listen to the session's own `selectionchange` event instead — this hook is wired in a future stage.
|
||||
</ParamField>
|
||||
|
||||
<ParamField path="attachSync" type="(comp: Composition) => () => void">
|
||||
Mirrors a composition's edits onto the adapter's own live document: an immediate full sync of the composition's current overrides, then a subscription that replays every future `patch` event — including undo/redo, since both fire through the same event with forward or inverse patches. Calling `attachSync` again while already attached detaches the previous subscription first. Returns an unsubscribe function.
|
||||
|
||||
Script-tag patches (`/script/gsap` and any future `/script/*` path) are never mirrored — rewriting a live `<script>` tag's content doesn't re-execute it, and re-running GSAP setup from scratch would conflict with running timeline state. Every other patch kind (style, text, attribute, timing, hold, element add/remove, stylesheet, variable value, variable declaration) mirrors as-is.
|
||||
|
||||
```typescript
|
||||
const adapter = createIframePreviewAdapter(iframe, dispatch);
|
||||
const comp = await openComposition(html, { preview: adapter });
|
||||
const detach = adapter.attachSync(comp);
|
||||
|
||||
// later, if the host tears down the preview:
|
||||
detach();
|
||||
```
|
||||
</ParamField>
|
||||
|
||||
### ElementAtPointResult
|
||||
|
||||
```typescript
|
||||
|
||||
@@ -110,6 +110,65 @@ comp.setVariableValue("brandFont", { name: "Inter", source: "https://fonts.googl
|
||||
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`
|
||||
|
||||
```typescript
|
||||
declareVariable(decl: CompositionVariable): void
|
||||
```
|
||||
|
||||
Create a new variable declaration, or fully replace an existing one (type, label, default, and all other schema fields — not just the value). This is the only path that creates the schema entry from scratch; `setVariableValue` intentionally refuses to.
|
||||
|
||||
```typescript
|
||||
comp.declareVariable({
|
||||
id: "brandColor",
|
||||
type: "color",
|
||||
label: "Brand color",
|
||||
default: "#6C5CE7",
|
||||
});
|
||||
```
|
||||
|
||||
### `removeVariable`
|
||||
|
||||
```typescript
|
||||
removeVariable(id: string): void
|
||||
```
|
||||
|
||||
Remove a variable's declaration entirely. Any live `var.{id}` overrides and `data-var-*` DOM references are left untouched — this only removes the schema entry.
|
||||
|
||||
```typescript
|
||||
comp.removeVariable("brandColor");
|
||||
```
|
||||
|
||||
### `getElementTimings`
|
||||
|
||||
```typescript
|
||||
@@ -300,6 +359,18 @@ const images = elements.filter((el) => el.tag === "img");
|
||||
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
|
||||
@@ -337,6 +408,18 @@ 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
|
||||
|
||||
@@ -263,7 +263,9 @@ comp.dispatch({
|
||||
|
||||
| `type` | Key fields | What it does |
|
||||
|--------|-----------|--------------|
|
||||
| `setVariableValue` | `id`, `value` | Sets a composition variable by id. Value may be a string, number, boolean, `FontValue`, or `ImageValue`. |
|
||||
| `setVariableValue` | `id`, `value` | Sets a composition variable's current value by id. Value may be a string, number, boolean, `FontValue`, or `ImageValue`. Refuses to create an undeclared variable. |
|
||||
| `declareVariable` | `decl` | Creates a new variable declaration, or fully replaces an existing one (type, label, default, and all other schema fields — not just the value). The only op that creates the schema entry from scratch. |
|
||||
| `removeVariable` | `id` | Removes a variable's declaration entirely. Live `var.{id}` overrides and `data-var-*` DOM references are left untouched. |
|
||||
|
||||
```typescript
|
||||
// Scalar variable
|
||||
@@ -293,6 +295,20 @@ comp.dispatch({
|
||||
fit: "cover",
|
||||
},
|
||||
});
|
||||
|
||||
// Create a new variable declaration
|
||||
comp.dispatch({
|
||||
type: "declareVariable",
|
||||
decl: {
|
||||
id: "brandColor",
|
||||
type: "color",
|
||||
label: "Brand color",
|
||||
default: "#6C5CE7",
|
||||
},
|
||||
});
|
||||
|
||||
// Remove a variable's declaration
|
||||
comp.dispatch({ type: "removeVariable", id: "brandColor" });
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
@@ -350,6 +350,105 @@ interface ElasticHold {
|
||||
|
||||
---
|
||||
|
||||
## CompositionVariable
|
||||
|
||||
The full schema for one declared variable — used by `comp.declareVariable()`, returned from `comp.listVariables()`. A closed union discriminated on `type`; every variant shares the same base fields plus its own type-specific ones.
|
||||
|
||||
```typescript
|
||||
interface CompositionVariableBase {
|
||||
id: string;
|
||||
type: "string" | "number" | "color" | "boolean" | "enum" | "font" | "image";
|
||||
label: string;
|
||||
description?: string;
|
||||
}
|
||||
|
||||
interface StringVariable extends CompositionVariableBase {
|
||||
type: "string";
|
||||
default: string;
|
||||
placeholder?: string;
|
||||
maxLength?: number;
|
||||
}
|
||||
|
||||
interface NumberVariable extends CompositionVariableBase {
|
||||
type: "number";
|
||||
default: number;
|
||||
min?: number;
|
||||
max?: number;
|
||||
step?: number;
|
||||
unit?: string;
|
||||
}
|
||||
|
||||
interface ColorVariable extends CompositionVariableBase {
|
||||
type: "color";
|
||||
default: string;
|
||||
brandRole?: string;
|
||||
}
|
||||
|
||||
interface BooleanVariable extends CompositionVariableBase {
|
||||
type: "boolean";
|
||||
default: boolean;
|
||||
}
|
||||
|
||||
interface EnumVariable extends CompositionVariableBase {
|
||||
type: "enum";
|
||||
default: string;
|
||||
options: { value: string; label: string }[];
|
||||
}
|
||||
|
||||
interface FontVariable extends CompositionVariableBase {
|
||||
type: "font";
|
||||
default: string;
|
||||
source?: string;
|
||||
default_name?: string;
|
||||
default_source?: string;
|
||||
}
|
||||
|
||||
interface ImageVariable extends CompositionVariableBase {
|
||||
type: "image";
|
||||
default: string;
|
||||
brandRole?: string;
|
||||
}
|
||||
|
||||
type CompositionVariable =
|
||||
| StringVariable
|
||||
| NumberVariable
|
||||
| ColorVariable
|
||||
| BooleanVariable
|
||||
| EnumVariable
|
||||
| FontVariable
|
||||
| ImageVariable;
|
||||
```
|
||||
|
||||
<ResponseField name="id" type="string">
|
||||
Stable identifier, referenced by `setVariableValue`, `getVariableValue`, `removeVariable`, and `var.{id}` override-set keys.
|
||||
</ResponseField>
|
||||
|
||||
<ResponseField name="type" type="string">
|
||||
Discriminant. Determines which variant's extra fields apply and what shape `default` takes.
|
||||
</ResponseField>
|
||||
|
||||
<ResponseField name="label" type="string">
|
||||
Human-readable name for a variables panel UI.
|
||||
</ResponseField>
|
||||
|
||||
<ResponseField name="default" type="string | number | boolean">
|
||||
The variable's fallback value. `font` and `image` variants store their fallback as a plain string here (a font-family name / image URL) — the richer `FontValue`/`ImageValue` object shapes are only used as `setVariableValue`'s runtime argument, not as `default`'s stored type.
|
||||
</ResponseField>
|
||||
|
||||
<ResponseField name="brandRole" type="string (color, image)">
|
||||
Optional semantic label for brand-system tooling, e.g. `"color:primary"` or `"logo:primary"`.
|
||||
</ResponseField>
|
||||
|
||||
<ResponseField name="options" type="{ value: string; label: string }[] (enum only)">
|
||||
The selectable choices for an `enum` variable.
|
||||
</ResponseField>
|
||||
|
||||
<ResponseField name="source" type="string (font only)">
|
||||
Font stylesheet URL (e.g. a Google Fonts CSS link) paired with the `default` font-family name.
|
||||
</ResponseField>
|
||||
|
||||
---
|
||||
|
||||
## FontValue
|
||||
|
||||
Object value for a `font` composition variable. Always an object — never a raw CSS string.
|
||||
@@ -619,6 +718,8 @@ type EditOp =
|
||||
| { type: "setClassStyle"; selector: string; styles: Record<string, string | null> }
|
||||
| { type: "setCompositionMetadata"; width?: number; height?: number; duration?: number }
|
||||
| { type: "setVariableValue"; id: string; value: string | number | boolean | FontValue | ImageValue }
|
||||
| { type: "declareVariable"; decl: CompositionVariable }
|
||||
| { type: "removeVariable"; id: string }
|
||||
| { type: "addGsapTween"; target: HfId; tween: GsapTweenSpec }
|
||||
| { type: "setGsapTween"; animationId: string; properties: Partial<GsapTweenSpec> }
|
||||
| { type: "removeGsapTween"; animationId: string }
|
||||
|
||||
@@ -230,6 +230,72 @@ const images = all.filter((el) => el.tag === "img");
|
||||
|
||||
---
|
||||
|
||||
## Id & Scope Utilities
|
||||
|
||||
```typescript
|
||||
import { resolveScoped, findById, bareId, escapeHfId, isNewHostBoundary } from "@hyperframes/sdk";
|
||||
```
|
||||
|
||||
Low-level id-resolution helpers used internally by dispatch, patch replay, and query — exposed for hosts building their own DOM-facing tooling against the same document model.
|
||||
|
||||
### resolveScoped
|
||||
|
||||
```typescript
|
||||
function resolveScoped(document: Document, id: string): Element | null;
|
||||
```
|
||||
|
||||
Resolve an `HfId` — bare (`"hf-title"`) or scoped (`"hf-host/hf-leaf"`) — to its `Element`. For an ambiguous bare id (the same id appearing both top-level and inside a sub-composition), prefers the canonical top-level match, matching `getElement()`'s own preference. This is the single resolution rule every mutation and patch-replay path shares — using your own `querySelector` instead can silently target the wrong duplicate.
|
||||
|
||||
### findById
|
||||
|
||||
```typescript
|
||||
function findById(document: Document, id: string): Element | null;
|
||||
```
|
||||
|
||||
Thin alias for `resolveScoped` kept for call-site clarity where "find" reads better than "resolve." Identical behavior.
|
||||
|
||||
### bareId
|
||||
|
||||
```typescript
|
||||
function bareId(scopedId: string): string;
|
||||
```
|
||||
|
||||
Strip a scoped id down to its leaf segment: `bareId("hf-host/hf-leaf")` returns `"hf-leaf"`. A no-op on an already-bare id.
|
||||
|
||||
### escapeHfId
|
||||
|
||||
```typescript
|
||||
function escapeHfId(id: string): string;
|
||||
```
|
||||
|
||||
Escape an id for safe interpolation into a `querySelectorAll` attribute-value selector (backslashes and double quotes). Use this if you're writing a raw `[data-hf-id="..."]` selector yourself instead of going through `resolveScoped`/`findById`.
|
||||
|
||||
### isNewHostBoundary
|
||||
|
||||
```typescript
|
||||
function isNewHostBoundary(el: Element): boolean;
|
||||
```
|
||||
|
||||
Returns `true` when `el` is the root of an inlined sub-composition — that is, it carries a `data-composition-file` attribute whose value differs from its parent's (or the parent has none). Use this to detect "entering a new sub-composition" while walking the tree, without hardcoding the attribute name.
|
||||
|
||||
---
|
||||
|
||||
## Variable Utilities
|
||||
|
||||
```typescript
|
||||
import { readVariableDefault } from "@hyperframes/sdk";
|
||||
```
|
||||
|
||||
### readVariableDefault
|
||||
|
||||
```typescript
|
||||
function readVariableDefault(document: Document, id: string): unknown;
|
||||
```
|
||||
|
||||
Read a declared variable's current `default` value directly from the document's `data-composition-variables` schema attribute, bypassing the session layer. This is the same function `comp.getVariableValue()` calls internally; prefer the typed `Composition` method in session code — use this only when you're working against a raw `Document` outside of an open session (matching `buildDocument`/`buildRoots`'s "same functions the SDK uses internally" pattern above).
|
||||
|
||||
---
|
||||
|
||||
## Constants
|
||||
|
||||
### ORIGIN\_APPLY\_PATCHES
|
||||
|
||||
Reference in New Issue
Block a user