@hyperframes/sdk 0.6.113
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +190 -0
- package/dist/adapters/fs.d.ts +9 -0
- package/dist/adapters/fs.d.ts.map +1 -0
- package/dist/adapters/fs.js +122 -0
- package/dist/adapters/fs.js.map +1 -0
- package/dist/adapters/headless.d.ts +3 -0
- package/dist/adapters/headless.d.ts.map +1 -0
- package/dist/adapters/headless.js +17 -0
- package/dist/adapters/headless.js.map +1 -0
- package/dist/adapters/iframe.d.ts +102 -0
- package/dist/adapters/iframe.d.ts.map +1 -0
- package/dist/adapters/iframe.js +569 -0
- package/dist/adapters/iframe.js.map +1 -0
- package/dist/adapters/memory.d.ts +5 -0
- package/dist/adapters/memory.d.ts.map +1 -0
- package/dist/adapters/memory.js +54 -0
- package/dist/adapters/memory.js.map +1 -0
- package/dist/adapters/types.d.ts +65 -0
- package/dist/adapters/types.d.ts.map +1 -0
- package/dist/adapters/types.js +2 -0
- package/dist/adapters/types.js.map +1 -0
- package/dist/document.d.ts +25 -0
- package/dist/document.d.ts.map +1 -0
- package/dist/document.js +238 -0
- package/dist/document.js.map +1 -0
- package/dist/engine/apply-patches.d.ts +20 -0
- package/dist/engine/apply-patches.d.ts.map +1 -0
- package/dist/engine/apply-patches.js +284 -0
- package/dist/engine/apply-patches.js.map +1 -0
- package/dist/engine/cssWriter.d.ts +18 -0
- package/dist/engine/cssWriter.d.ts.map +1 -0
- package/dist/engine/cssWriter.js +139 -0
- package/dist/engine/cssWriter.js.map +1 -0
- package/dist/engine/keyframeBackfill.d.ts +17 -0
- package/dist/engine/keyframeBackfill.d.ts.map +1 -0
- package/dist/engine/keyframeBackfill.js +43 -0
- package/dist/engine/keyframeBackfill.js.map +1 -0
- package/dist/engine/model.d.ts +55 -0
- package/dist/engine/model.d.ts.map +1 -0
- package/dist/engine/model.js +256 -0
- package/dist/engine/model.js.map +1 -0
- package/dist/engine/mutate.d.ts +26 -0
- package/dist/engine/mutate.d.ts.map +1 -0
- package/dist/engine/mutate.js +1243 -0
- package/dist/engine/mutate.js.map +1 -0
- package/dist/engine/patches.d.ts +71 -0
- package/dist/engine/patches.d.ts.map +1 -0
- package/dist/engine/patches.js +197 -0
- package/dist/engine/patches.js.map +1 -0
- package/dist/engine/serialize.d.ts +15 -0
- package/dist/engine/serialize.d.ts.map +1 -0
- package/dist/engine/serialize.js +20 -0
- package/dist/engine/serialize.js.map +1 -0
- package/dist/engine/variableModel.d.ts +29 -0
- package/dist/engine/variableModel.d.ts.map +1 -0
- package/dist/engine/variableModel.js +81 -0
- package/dist/engine/variableModel.js.map +1 -0
- package/dist/history.d.ts +39 -0
- package/dist/history.d.ts.map +1 -0
- package/dist/history.js +116 -0
- package/dist/history.js.map +1 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +11 -0
- package/dist/index.js.map +1 -0
- package/dist/persist-queue.d.ts +24 -0
- package/dist/persist-queue.d.ts.map +1 -0
- package/dist/persist-queue.js +62 -0
- package/dist/persist-queue.js.map +1 -0
- package/dist/session.d.ts +38 -0
- package/dist/session.d.ts.map +1 -0
- package/dist/session.js +514 -0
- package/dist/session.js.map +1 -0
- package/dist/types.d.ts +521 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +16 -0
- package/dist/types.js.map +1 -0
- package/package.json +55 -0
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import type { PersistErrorEvent } from "../types.js";
|
|
2
|
+
export interface PersistVersionEntry {
|
|
3
|
+
/** Opaque key identifying this version (adapter-defined format) */
|
|
4
|
+
key: string;
|
|
5
|
+
/** Full HTML content — may be omitted by adapters that load content lazily via loadFrom() */
|
|
6
|
+
content?: string;
|
|
7
|
+
timestamp?: number;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Injectable storage adapter — decouples the SDK from the underlying persistence mechanism.
|
|
11
|
+
* Implementations: memory (tests/demos), fs (local dev), S3 (cloud), HTTP (Pacific).
|
|
12
|
+
*
|
|
13
|
+
* Contract:
|
|
14
|
+
* - read() returns undefined for a path never written
|
|
15
|
+
* - write() is idempotent (second write overwrites)
|
|
16
|
+
* - flush() resolves when any queued writes are committed
|
|
17
|
+
* - listVersions() returns entries newest-first
|
|
18
|
+
* - loadFrom() returns content for the given version key (undefined if not found)
|
|
19
|
+
* - on('persist:error') fires when a write fails; the error must not propagate as a thrown exception
|
|
20
|
+
*/
|
|
21
|
+
export interface PersistAdapter {
|
|
22
|
+
read(path: string): Promise<string | undefined>;
|
|
23
|
+
write(path: string, content: string): Promise<void>;
|
|
24
|
+
/** Force all pending writes to commit before returning */
|
|
25
|
+
flush(): Promise<void>;
|
|
26
|
+
listVersions(path: string): Promise<PersistVersionEntry[]>;
|
|
27
|
+
loadFrom(path: string, versionKey: string): Promise<string | undefined>;
|
|
28
|
+
on(event: "persist:error", handler: (event: PersistErrorEvent) => void): () => void;
|
|
29
|
+
}
|
|
30
|
+
export interface ElementAtPointResult {
|
|
31
|
+
id: string;
|
|
32
|
+
tag: string;
|
|
33
|
+
}
|
|
34
|
+
export interface DraftProps {
|
|
35
|
+
dx?: number;
|
|
36
|
+
dy?: number;
|
|
37
|
+
width?: number;
|
|
38
|
+
height?: number;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Injectable preview adapter — decouples the SDK from the host preview surface.
|
|
42
|
+
* The null/headless adapter stubs all methods (no browser needed).
|
|
43
|
+
*
|
|
44
|
+
* The SDK is NOT in the 60fps draft loop — consumers call applyDraft() directly on
|
|
45
|
+
* the preview at 60fps; commitPreview() fires once on pointer-up to derive and
|
|
46
|
+
* dispatch the resulting op.
|
|
47
|
+
*/
|
|
48
|
+
export interface PreviewAdapter {
|
|
49
|
+
/** Sync hit-test at composition coordinates. Requires same-origin iframe. */
|
|
50
|
+
elementAtPoint(x: number, y: number, opts?: {
|
|
51
|
+
atTime?: number;
|
|
52
|
+
}): ElementAtPointResult | null;
|
|
53
|
+
/** Apply draft CSS markers to the preview element (60fps, SDK not involved) */
|
|
54
|
+
applyDraft(id: string, props: DraftProps): void;
|
|
55
|
+
/** Derive op from draft markers, dispatch it, emit patch event, clear markers */
|
|
56
|
+
commitPreview(): void;
|
|
57
|
+
/** Revert draft markers without committing. Model never changed. */
|
|
58
|
+
cancelPreview(): void;
|
|
59
|
+
/** Set preview selection; fires selectionchange on the session */
|
|
60
|
+
select(ids: string[], opts?: {
|
|
61
|
+
additive?: boolean;
|
|
62
|
+
}): void;
|
|
63
|
+
on(event: "selection", handler: (ids: string[]) => void): () => void;
|
|
64
|
+
}
|
|
65
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/adapters/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAIrD,MAAM,WAAW,mBAAmB;IAClC,mEAAmE;IACnE,GAAG,EAAE,MAAM,CAAC;IACZ,6FAA6F;IAC7F,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;;;;;;;;;;GAWG;AACH,MAAM,WAAW,cAAc;IAC7B,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAAC;IAChD,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACpD,0DAA0D;IAC1D,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACvB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,mBAAmB,EAAE,CAAC,CAAC;IAC3D,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAAC;IACxE,EAAE,CAAC,KAAK,EAAE,eAAe,EAAE,OAAO,EAAE,CAAC,KAAK,EAAE,iBAAiB,KAAK,IAAI,GAAG,MAAM,IAAI,CAAC;CACrF;AAID,MAAM,WAAW,oBAAoB;IACnC,EAAE,EAAE,MAAM,CAAC;IACX,GAAG,EAAE,MAAM,CAAC;CACb;AAED,MAAM,WAAW,UAAU;IACzB,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,cAAc;IAC7B,6EAA6E;IAC7E,cAAc,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE;QAAE,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,oBAAoB,GAAG,IAAI,CAAC;IAE9F,+EAA+E;IAC/E,UAAU,CAAC,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,UAAU,GAAG,IAAI,CAAC;IAEhD,iFAAiF;IACjF,aAAa,IAAI,IAAI,CAAC;IAEtB,oEAAoE;IACpE,aAAa,IAAI,IAAI,CAAC;IAEtB,kEAAkE;IAClE,MAAM,CAAC,GAAG,EAAE,MAAM,EAAE,EAAE,IAAI,CAAC,EAAE;QAAE,QAAQ,CAAC,EAAE,OAAO,CAAA;KAAE,GAAG,IAAI,CAAC;IAI3D,EAAE,CAAC,KAAK,EAAE,WAAW,EAAE,OAAO,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,IAAI,GAAG,MAAM,IAAI,CAAC;CACtE"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/adapters/types.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SDK document model — adaptation layer on top of @hyperframes/core.
|
|
3
|
+
*
|
|
4
|
+
* F6 decision: SDK builds ON core, no parser duplication.
|
|
5
|
+
* - ensureHfIds (from core) is the parse entry point: all construction starts here.
|
|
6
|
+
* - DOMParser is NOT used (browser-only). linkedom is the node-safe primitive.
|
|
7
|
+
* - ParsedHtml (core) is the Studio timeline view (timed elements only).
|
|
8
|
+
* HyperFramesElement is the editing view (ALL editable elements, with raw attrs).
|
|
9
|
+
*/
|
|
10
|
+
import type { HyperFramesElement, SdkDocument } from "./types.js";
|
|
11
|
+
/**
|
|
12
|
+
* Build the element tree from an already-parsed (hf-id-stamped) linkedom Document.
|
|
13
|
+
* Walks the live DOM directly — no serialize/re-parse round trip. This is what
|
|
14
|
+
* the session's query API uses against its mutable document.
|
|
15
|
+
*/
|
|
16
|
+
export declare function buildRoots(document: Document): HyperFramesElement[];
|
|
17
|
+
/**
|
|
18
|
+
* Parse an HTML string into the SDK document model.
|
|
19
|
+
* Calls ensureHfIds first so every element has a stable data-hf-id.
|
|
20
|
+
* Uses linkedom — node-safe (works in agents, CI, server-side).
|
|
21
|
+
*/
|
|
22
|
+
export declare function buildDocument(html: string): SdkDocument;
|
|
23
|
+
/** Flat walk of the element tree — returns every element in document order */
|
|
24
|
+
export declare function flatElements(roots: readonly HyperFramesElement[]): HyperFramesElement[];
|
|
25
|
+
//# sourceMappingURL=document.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"document.d.ts","sourceRoot":"","sources":["../src/document.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAMH,OAAO,KAAK,EAAE,kBAAkB,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AA0LlE;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,QAAQ,EAAE,QAAQ,GAAG,kBAAkB,EAAE,CAWnE;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,WAAW,CAoBvD;AAED,8EAA8E;AAC9E,wBAAgB,YAAY,CAAC,KAAK,EAAE,SAAS,kBAAkB,EAAE,GAAG,kBAAkB,EAAE,CAQvF"}
|
package/dist/document.js
ADDED
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SDK document model — adaptation layer on top of @hyperframes/core.
|
|
3
|
+
*
|
|
4
|
+
* F6 decision: SDK builds ON core, no parser duplication.
|
|
5
|
+
* - ensureHfIds (from core) is the parse entry point: all construction starts here.
|
|
6
|
+
* - DOMParser is NOT used (browser-only). linkedom is the node-safe primitive.
|
|
7
|
+
* - ParsedHtml (core) is the Studio timeline view (timed elements only).
|
|
8
|
+
* HyperFramesElement is the editing view (ALL editable elements, with raw attrs).
|
|
9
|
+
*/
|
|
10
|
+
import { parseHTML } from "linkedom";
|
|
11
|
+
import { ensureHfIds } from "@hyperframes/core/hf-ids";
|
|
12
|
+
import { parseGsapScriptAcornForWrite } from "@hyperframes/core/gsap-parser-acorn";
|
|
13
|
+
import { findRoot, getElementStyles, isNewHostBoundary } from "./engine/model.js";
|
|
14
|
+
// Tags that carry no editable content and must not enter the element tree.
|
|
15
|
+
const EXCLUDED_TAGS = new Set([
|
|
16
|
+
"script",
|
|
17
|
+
"style",
|
|
18
|
+
"template",
|
|
19
|
+
"meta",
|
|
20
|
+
"link",
|
|
21
|
+
"noscript",
|
|
22
|
+
"base",
|
|
23
|
+
"head",
|
|
24
|
+
]);
|
|
25
|
+
// Snapshot text is TRIMMED for display (markup indentation produces noisy
|
|
26
|
+
// whitespace text nodes). setText writes verbatim — engine getOwnText/setOwnText
|
|
27
|
+
// operate on raw text. el.text is a display value, not a round-trip identity.
|
|
28
|
+
function ownText(el) {
|
|
29
|
+
let text = "";
|
|
30
|
+
el.childNodes.forEach((n) => {
|
|
31
|
+
if (n.nodeType === 3)
|
|
32
|
+
text += n.nodeValue ?? "";
|
|
33
|
+
});
|
|
34
|
+
const trimmed = text.trim();
|
|
35
|
+
return trimmed.length > 0 ? trimmed : null;
|
|
36
|
+
}
|
|
37
|
+
// Parsing the GSAP script (acorn AST walk) is the expensive part and depends
|
|
38
|
+
// only on the script text, so memoize the {tween id, selector} pairs by script.
|
|
39
|
+
// Selector→hf-id resolution still runs each call — it depends on the live DOM,
|
|
40
|
+
// which changes on dispatch. Single-entry cache covers the hot path (same comp,
|
|
41
|
+
// repeated getElements() rebuilds) and stays bounded.
|
|
42
|
+
let gsapLocatedCacheKey = null;
|
|
43
|
+
let gsapLocatedCacheVal = [];
|
|
44
|
+
function parseLocatedCached(script) {
|
|
45
|
+
if (gsapLocatedCacheKey === script)
|
|
46
|
+
return gsapLocatedCacheVal;
|
|
47
|
+
const parsed = parseGsapScriptAcornForWrite(script);
|
|
48
|
+
gsapLocatedCacheVal = parsed
|
|
49
|
+
? parsed.located.map(({ id, animation }) => ({ id, selector: animation.targetSelector }))
|
|
50
|
+
: [];
|
|
51
|
+
gsapLocatedCacheKey = script;
|
|
52
|
+
return gsapLocatedCacheVal;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Map each element's data-hf-id → the GSAP tween ids targeting it. Tween ids
|
|
56
|
+
* come from the acorn parser's stable `targetSelector-method-position` scheme —
|
|
57
|
+
* the SAME id-space the studio-api read path and the SDK GSAP ops use, so these
|
|
58
|
+
* ids are dispatchable as-is via setGsapTween/removeGsapTween. Best-effort: a
|
|
59
|
+
* malformed selector or unparseable script yields no entries (animationIds: []).
|
|
60
|
+
*/
|
|
61
|
+
function buildAnimationIdMap(document) {
|
|
62
|
+
const map = new Map();
|
|
63
|
+
const script = extractGsapScript(document);
|
|
64
|
+
if (!script)
|
|
65
|
+
return map;
|
|
66
|
+
for (const { id, selector } of parseLocatedCached(script)) {
|
|
67
|
+
if (!selector)
|
|
68
|
+
continue;
|
|
69
|
+
let matches = [];
|
|
70
|
+
try {
|
|
71
|
+
matches = Array.from(document.querySelectorAll(selector));
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
continue; // selector not valid for querySelectorAll — skip
|
|
75
|
+
}
|
|
76
|
+
for (const el of matches) {
|
|
77
|
+
const hfId = el.getAttribute("data-hf-id");
|
|
78
|
+
if (!hfId)
|
|
79
|
+
continue;
|
|
80
|
+
const list = map.get(hfId);
|
|
81
|
+
if (list)
|
|
82
|
+
list.push(id);
|
|
83
|
+
else
|
|
84
|
+
map.set(hfId, [id]);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return map;
|
|
88
|
+
}
|
|
89
|
+
// fallow-ignore-next-line complexity
|
|
90
|
+
function buildElement(el, scopePrefix, animationIdsByHfId) {
|
|
91
|
+
const tag = el.tagName.toLowerCase();
|
|
92
|
+
if (EXCLUDED_TAGS.has(tag))
|
|
93
|
+
return null;
|
|
94
|
+
const id = el.getAttribute("data-hf-id") ?? "";
|
|
95
|
+
if (!id)
|
|
96
|
+
return null; // should never happen after ensureHfIds, but guard defensively
|
|
97
|
+
// scopedId: if we're inside a sub-comp scope, prefix with "scopePrefix/".
|
|
98
|
+
// The host element itself is in the PARENT scope (no prefix change for its own id).
|
|
99
|
+
const scopedId = scopePrefix ? `${scopePrefix}/${id}` : id;
|
|
100
|
+
// Children inherit the scope prefix from their parent.
|
|
101
|
+
// If this element is a new host boundary (starts a new sub-comp scope), its
|
|
102
|
+
// children use THIS element's scopedId as their prefix.
|
|
103
|
+
// Otherwise, children inherit the same prefix that this element used.
|
|
104
|
+
const childPrefix = isNewHostBoundary(el) ? scopedId : scopePrefix;
|
|
105
|
+
const inlineStyles = getElementStyles(el);
|
|
106
|
+
const classAttr = el.getAttribute("class") ?? "";
|
|
107
|
+
const classNames = classAttr
|
|
108
|
+
.split(/\s+/)
|
|
109
|
+
.map((c) => c.trim())
|
|
110
|
+
.filter(Boolean);
|
|
111
|
+
const attributes = {};
|
|
112
|
+
for (const attr of Array.from(el.attributes)) {
|
|
113
|
+
if (attr.name === "style" || attr.name === "class" || attr.name.startsWith("data-hf-")) {
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
attributes[attr.name] = attr.value;
|
|
117
|
+
}
|
|
118
|
+
const startAttr = el.getAttribute("data-start");
|
|
119
|
+
const endAttr = el.getAttribute("data-end");
|
|
120
|
+
const trackAttr = el.getAttribute("data-track-index");
|
|
121
|
+
const start = startAttr !== null ? parseFloat(startAttr) : null;
|
|
122
|
+
const duration = start !== null && endAttr !== null ? Math.max(0, parseFloat(endAttr) - start) : null;
|
|
123
|
+
const trackIndex = trackAttr !== null ? parseInt(trackAttr, 10) : null;
|
|
124
|
+
const children = [];
|
|
125
|
+
for (const child of Array.from(el.children)) {
|
|
126
|
+
const built = buildElement(child, childPrefix, animationIdsByHfId);
|
|
127
|
+
if (built)
|
|
128
|
+
children.push(built);
|
|
129
|
+
}
|
|
130
|
+
return {
|
|
131
|
+
id,
|
|
132
|
+
scopedId,
|
|
133
|
+
tag,
|
|
134
|
+
children,
|
|
135
|
+
inlineStyles,
|
|
136
|
+
classNames,
|
|
137
|
+
attributes,
|
|
138
|
+
text: ownText(el),
|
|
139
|
+
start,
|
|
140
|
+
duration,
|
|
141
|
+
trackIndex,
|
|
142
|
+
animationIds: animationIdsByHfId.get(id) ?? [],
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
// fallow-ignore-next-line complexity
|
|
146
|
+
function extractGsapScript(doc) {
|
|
147
|
+
// GSAP script is the first <script> tag whose text references gsap. Marker
|
|
148
|
+
// set must match studio sdkShadow.ts isGsapScriptBody so both pick the same
|
|
149
|
+
// script from a given composition.
|
|
150
|
+
for (const script of Array.from(doc.querySelectorAll("script"))) {
|
|
151
|
+
const text = script.textContent ?? "";
|
|
152
|
+
if (text.includes("gsap") || text.includes("__timelines") || text.includes("ScrollTrigger")) {
|
|
153
|
+
return text;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
return null;
|
|
157
|
+
}
|
|
158
|
+
function extractStyles(doc) {
|
|
159
|
+
const styleEl = doc.querySelector("style");
|
|
160
|
+
return styleEl ? styleEl.textContent : null;
|
|
161
|
+
}
|
|
162
|
+
// Root resolution delegates to the engine's findRoot so dimension extraction
|
|
163
|
+
// and mutations agree on which element is the composition root.
|
|
164
|
+
// fallow-ignore-next-line complexity
|
|
165
|
+
function extractDimensions(doc) {
|
|
166
|
+
const stage = findRoot(doc);
|
|
167
|
+
if (!stage)
|
|
168
|
+
return { width: null, height: null };
|
|
169
|
+
// data-width/data-height are the runtime's forced override — prefer them.
|
|
170
|
+
const wAttr = stage.getAttribute("data-width");
|
|
171
|
+
const hAttr = stage.getAttribute("data-height");
|
|
172
|
+
const style = stage.getAttribute?.("style") ?? "";
|
|
173
|
+
const wm = /width:\s*(\d+)px/.exec(style);
|
|
174
|
+
const hm = /height:\s*(\d+)px/.exec(style);
|
|
175
|
+
return {
|
|
176
|
+
width: wAttr !== null ? parseInt(wAttr, 10) : wm ? parseInt(wm[1] ?? "", 10) : null,
|
|
177
|
+
height: hAttr !== null ? parseInt(hAttr, 10) : hm ? parseInt(hm[1] ?? "", 10) : null,
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
function extractDuration(doc) {
|
|
181
|
+
const root = findRoot(doc) ?? doc.body;
|
|
182
|
+
const dur = root?.getAttribute("data-duration");
|
|
183
|
+
return dur ? parseFloat(dur) : null;
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Build the element tree from an already-parsed (hf-id-stamped) linkedom Document.
|
|
187
|
+
* Walks the live DOM directly — no serialize/re-parse round trip. This is what
|
|
188
|
+
* the session's query API uses against its mutable document.
|
|
189
|
+
*/
|
|
190
|
+
export function buildRoots(document) {
|
|
191
|
+
const body = document.body;
|
|
192
|
+
const roots = [];
|
|
193
|
+
const animationIdsByHfId = buildAnimationIdMap(document);
|
|
194
|
+
if (body) {
|
|
195
|
+
for (const child of Array.from(body.children)) {
|
|
196
|
+
const built = buildElement(child, "", animationIdsByHfId);
|
|
197
|
+
if (built)
|
|
198
|
+
roots.push(built);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
return roots;
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* Parse an HTML string into the SDK document model.
|
|
205
|
+
* Calls ensureHfIds first so every element has a stable data-hf-id.
|
|
206
|
+
* Uses linkedom — node-safe (works in agents, CI, server-side).
|
|
207
|
+
*/
|
|
208
|
+
export function buildDocument(html) {
|
|
209
|
+
const stamped = ensureHfIds(html);
|
|
210
|
+
const hasShell = /<!doctype|<html[\s>]/i.test(stamped);
|
|
211
|
+
const wrapped = !hasShell;
|
|
212
|
+
const { document } = wrapped
|
|
213
|
+
? parseHTML(`<!DOCTYPE html><html><head></head><body>${stamped}</body></html>`)
|
|
214
|
+
: parseHTML(stamped);
|
|
215
|
+
const dims = extractDimensions(document);
|
|
216
|
+
return {
|
|
217
|
+
roots: buildRoots(document),
|
|
218
|
+
gsapScript: extractGsapScript(document),
|
|
219
|
+
styles: extractStyles(document),
|
|
220
|
+
width: dims.width,
|
|
221
|
+
height: dims.height,
|
|
222
|
+
compositionDuration: extractDuration(document),
|
|
223
|
+
html: stamped,
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
/** Flat walk of the element tree — returns every element in document order */
|
|
227
|
+
export function flatElements(roots) {
|
|
228
|
+
const result = [];
|
|
229
|
+
function walk(el) {
|
|
230
|
+
result.push(el);
|
|
231
|
+
for (const child of el.children)
|
|
232
|
+
walk(child);
|
|
233
|
+
}
|
|
234
|
+
for (const root of roots)
|
|
235
|
+
walk(root);
|
|
236
|
+
return result;
|
|
237
|
+
}
|
|
238
|
+
//# sourceMappingURL=document.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"document.js","sourceRoot":"","sources":["../src/document.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bounded RFC 6902 patch applier — handles only the path patterns emitted by mutate.ts.
|
|
3
|
+
*
|
|
4
|
+
* Not a general-purpose JSON Patch implementation. Translates the well-defined path
|
|
5
|
+
* grammar back into DOM mutations. Used by applyPatches() for host undo (T3 mode).
|
|
6
|
+
*
|
|
7
|
+
* Supports only the emit subset (add/remove/replace) — move/copy/test ops and
|
|
8
|
+
* unknown paths are silently ignored, matching the JsonPatchOp contract.
|
|
9
|
+
*/
|
|
10
|
+
import type { JsonPatchOp, OverrideSet } from "../types.js";
|
|
11
|
+
import type { ParsedDocument } from "./model.js";
|
|
12
|
+
/**
|
|
13
|
+
* Replay a stored override-set onto a freshly-parsed base document (T3 init).
|
|
14
|
+
* A null value means the property was explicitly deleted — emit a remove patch
|
|
15
|
+
* so the base document matches the session state. (Removing a non-existent
|
|
16
|
+
* property is a no-op in applyOne, so this is safe against fresh-base misses.)
|
|
17
|
+
*/
|
|
18
|
+
export declare function applyOverrideSet(parsed: ParsedDocument, overrides: OverrideSet): void;
|
|
19
|
+
export declare function applyPatchesToDocument(parsed: ParsedDocument, patches: readonly JsonPatchOp[]): void;
|
|
20
|
+
//# sourceMappingURL=apply-patches.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"apply-patches.d.ts","sourceRoot":"","sources":["../../src/engine/apply-patches.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC5D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAwFjD;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,cAAc,EAAE,SAAS,EAAE,WAAW,GAAG,IAAI,CAwBrF;AAED,wBAAgB,sBAAsB,CACpC,MAAM,EAAE,cAAc,EACtB,OAAO,EAAE,SAAS,WAAW,EAAE,GAC9B,IAAI,CAMN"}
|
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bounded RFC 6902 patch applier — handles only the path patterns emitted by mutate.ts.
|
|
3
|
+
*
|
|
4
|
+
* Not a general-purpose JSON Patch implementation. Translates the well-defined path
|
|
5
|
+
* grammar back into DOM mutations. Used by applyPatches() for host undo (T3 mode).
|
|
6
|
+
*
|
|
7
|
+
* Supports only the emit subset (add/remove/replace) — move/copy/test ops and
|
|
8
|
+
* unknown paths are silently ignored, matching the JsonPatchOp contract.
|
|
9
|
+
*/
|
|
10
|
+
import { findById, findRoot, setElementStyles, setOwnText, setGsapScript, setStyleSheet, } from "./model.js";
|
|
11
|
+
import { keyToPath, stylePath } from "./patches.js";
|
|
12
|
+
import { writeVariableDefault, clearVariableDefault } from "./variableModel.js";
|
|
13
|
+
// fallow-ignore-next-line complexity
|
|
14
|
+
function parsePath(path) {
|
|
15
|
+
const styleM = /^\/elements\/([^/]+)\/inlineStyles\/(.+)$/.exec(path);
|
|
16
|
+
if (styleM)
|
|
17
|
+
return { type: "style", id: styleM[1], prop: styleM[2] };
|
|
18
|
+
const textM = /^\/elements\/([^/]+)\/text$/.exec(path);
|
|
19
|
+
if (textM)
|
|
20
|
+
return { type: "text", id: textM[1] };
|
|
21
|
+
const attrM = /^\/elements\/([^/]+)\/attributes\/(.+)$/.exec(path);
|
|
22
|
+
if (attrM)
|
|
23
|
+
return {
|
|
24
|
+
type: "attribute",
|
|
25
|
+
id: attrM[1],
|
|
26
|
+
prop: attrM[2]?.replace(/~1/g, "/").replace(/~0/g, "~"),
|
|
27
|
+
};
|
|
28
|
+
const timingM = /^\/elements\/([^/]+)\/timing\/(.+)$/.exec(path);
|
|
29
|
+
if (timingM)
|
|
30
|
+
return { type: "timing", id: timingM[1], field: timingM[2] };
|
|
31
|
+
const holdM = /^\/elements\/([^/]+)\/hold\/(.+)$/.exec(path);
|
|
32
|
+
if (holdM)
|
|
33
|
+
return { type: "hold", id: holdM[1], field: holdM[2] };
|
|
34
|
+
const elemM = /^\/elements\/([^/]+)$/.exec(path);
|
|
35
|
+
if (elemM)
|
|
36
|
+
return { type: "element", id: elemM[1] };
|
|
37
|
+
const varM = /^\/variables\/(.+)$/.exec(path);
|
|
38
|
+
if (varM)
|
|
39
|
+
return { type: "variable", id: varM[1] };
|
|
40
|
+
const metaM = /^\/metadata\/(.+)$/.exec(path);
|
|
41
|
+
if (metaM)
|
|
42
|
+
return { type: "metadata", field: metaM[1] };
|
|
43
|
+
if (path === "/script/gsap")
|
|
44
|
+
return { type: "script" };
|
|
45
|
+
if (path === "/style/css")
|
|
46
|
+
return { type: "stylesheet" };
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
// ─── Variable JSON model helper ───────────────────────────────────────────────
|
|
50
|
+
/**
|
|
51
|
+
* Apply a variable patch to `data-composition-variables`. A remove op (null)
|
|
52
|
+
* deletes the declaration's `default` key, restoring its "no authored default"
|
|
53
|
+
* state — the exact inverse of a first-set that added a default to a
|
|
54
|
+
* default-less variable, so undo of such a set round-trips. A value op upserts
|
|
55
|
+
* the matching declaration's `default`. No-ops when the attr/decl is absent.
|
|
56
|
+
* Shares the model logic with mutate.ts via ./variableModel.ts.
|
|
57
|
+
*/
|
|
58
|
+
function applyVariableDefault(document, id, newDefault) {
|
|
59
|
+
if (newDefault === null) {
|
|
60
|
+
clearVariableDefault(document, id);
|
|
61
|
+
}
|
|
62
|
+
else {
|
|
63
|
+
writeVariableDefault(document, id, newDefault);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
// ─── Patch application ───────────────────────────────────────────────────────
|
|
67
|
+
/**
|
|
68
|
+
* Replay a stored override-set onto a freshly-parsed base document (T3 init).
|
|
69
|
+
* A null value means the property was explicitly deleted — emit a remove patch
|
|
70
|
+
* so the base document matches the session state. (Removing a non-existent
|
|
71
|
+
* property is a no-op in applyOne, so this is safe against fresh-base misses.)
|
|
72
|
+
*/
|
|
73
|
+
export function applyOverrideSet(parsed, overrides) {
|
|
74
|
+
const patches = [];
|
|
75
|
+
const rootId = findRoot(parsed.document)?.getAttribute("data-hf-id") ?? null;
|
|
76
|
+
for (const [key, value] of Object.entries(overrides)) {
|
|
77
|
+
const path = keyToPath(key);
|
|
78
|
+
if (!path)
|
|
79
|
+
continue;
|
|
80
|
+
if (value === null) {
|
|
81
|
+
patches.push({ op: "remove", path });
|
|
82
|
+
}
|
|
83
|
+
else {
|
|
84
|
+
patches.push({ op: "replace", path, value });
|
|
85
|
+
}
|
|
86
|
+
// A scalar `var.{id}` override must also restore the `--{id}` CSS custom
|
|
87
|
+
// prop on the root. Current sessions persist a paired style override, but
|
|
88
|
+
// sets written before the model/CSS split only carry `var.{id}`; derive the
|
|
89
|
+
// CSS here so `var(--{id})` bindings rehydrate. Object (font/image) values
|
|
90
|
+
// are never CSS, so they are skipped.
|
|
91
|
+
if (rootId && key.startsWith("var.") && value !== null && typeof value !== "object") {
|
|
92
|
+
const cssPath = stylePath(rootId, `--${key.slice("var.".length)}`);
|
|
93
|
+
patches.push({ op: "replace", path: cssPath, value: String(value) });
|
|
94
|
+
}
|
|
95
|
+
else if (rootId && key.startsWith("var.") && value === null) {
|
|
96
|
+
patches.push({ op: "remove", path: stylePath(rootId, `--${key.slice("var.".length)}`) });
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
applyPatchesToDocument(parsed, patches);
|
|
100
|
+
}
|
|
101
|
+
export function applyPatchesToDocument(parsed, patches) {
|
|
102
|
+
for (const patch of patches) {
|
|
103
|
+
const p = parsePath(patch.path);
|
|
104
|
+
if (!p)
|
|
105
|
+
continue;
|
|
106
|
+
applyOne(parsed, patch, p);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
// fallow-ignore-next-line complexity
|
|
110
|
+
function applyOne(parsed, patch, p) {
|
|
111
|
+
switch (p.type) {
|
|
112
|
+
case "style": {
|
|
113
|
+
const el = p.id ? findById(parsed.document, p.id) : null;
|
|
114
|
+
if (!el || !p.prop)
|
|
115
|
+
return;
|
|
116
|
+
if (patch.op === "remove") {
|
|
117
|
+
setElementStyles(el, { [p.prop]: null });
|
|
118
|
+
}
|
|
119
|
+
else {
|
|
120
|
+
setElementStyles(el, { [p.prop]: String(patch.value) });
|
|
121
|
+
}
|
|
122
|
+
break;
|
|
123
|
+
}
|
|
124
|
+
case "text": {
|
|
125
|
+
const el = p.id ? findById(parsed.document, p.id) : null;
|
|
126
|
+
if (!el)
|
|
127
|
+
return;
|
|
128
|
+
if (patch.op === "remove") {
|
|
129
|
+
setOwnText(el, "");
|
|
130
|
+
}
|
|
131
|
+
else {
|
|
132
|
+
setOwnText(el, String(patch.value ?? ""));
|
|
133
|
+
}
|
|
134
|
+
break;
|
|
135
|
+
}
|
|
136
|
+
case "attribute": {
|
|
137
|
+
const el = p.id ? findById(parsed.document, p.id) : null;
|
|
138
|
+
if (!el || !p.prop)
|
|
139
|
+
return;
|
|
140
|
+
if (patch.op === "remove") {
|
|
141
|
+
el.removeAttribute(p.prop);
|
|
142
|
+
}
|
|
143
|
+
else {
|
|
144
|
+
el.setAttribute(p.prop, String(patch.value ?? ""));
|
|
145
|
+
}
|
|
146
|
+
break;
|
|
147
|
+
}
|
|
148
|
+
case "timing": {
|
|
149
|
+
const el = p.id ? findById(parsed.document, p.id) : null;
|
|
150
|
+
if (!el || !p.field)
|
|
151
|
+
return;
|
|
152
|
+
if (p.field === "start") {
|
|
153
|
+
if (patch.op === "remove")
|
|
154
|
+
el.removeAttribute("data-start");
|
|
155
|
+
else
|
|
156
|
+
el.setAttribute("data-start", String(patch.value));
|
|
157
|
+
}
|
|
158
|
+
else if (p.field === "duration") {
|
|
159
|
+
// Patch value is the data-duration value — set directly.
|
|
160
|
+
if (patch.op === "remove")
|
|
161
|
+
el.removeAttribute("data-duration");
|
|
162
|
+
else
|
|
163
|
+
el.setAttribute("data-duration", String(patch.value));
|
|
164
|
+
}
|
|
165
|
+
else if (p.field === "end") {
|
|
166
|
+
// Patch value is the absolute data-end time — set directly, no re-derivation.
|
|
167
|
+
if (patch.op === "remove")
|
|
168
|
+
el.removeAttribute("data-end");
|
|
169
|
+
else
|
|
170
|
+
el.setAttribute("data-end", String(patch.value));
|
|
171
|
+
}
|
|
172
|
+
else if (p.field === "trackIndex") {
|
|
173
|
+
if (patch.op === "remove")
|
|
174
|
+
el.removeAttribute("data-track-index");
|
|
175
|
+
else
|
|
176
|
+
el.setAttribute("data-track-index", String(patch.value));
|
|
177
|
+
}
|
|
178
|
+
break;
|
|
179
|
+
}
|
|
180
|
+
case "hold": {
|
|
181
|
+
const el = p.id ? findById(parsed.document, p.id) : null;
|
|
182
|
+
if (!el || !p.field)
|
|
183
|
+
return;
|
|
184
|
+
const attrName = `data-hold-${p.field}`;
|
|
185
|
+
if (patch.op === "remove")
|
|
186
|
+
el.removeAttribute(attrName);
|
|
187
|
+
else
|
|
188
|
+
el.setAttribute(attrName, String(patch.value));
|
|
189
|
+
break;
|
|
190
|
+
}
|
|
191
|
+
case "element": {
|
|
192
|
+
if (!p.id)
|
|
193
|
+
return;
|
|
194
|
+
if (patch.op === "remove") {
|
|
195
|
+
const el = findById(parsed.document, p.id);
|
|
196
|
+
el?.remove();
|
|
197
|
+
}
|
|
198
|
+
else if (patch.op === "add" && patch.value) {
|
|
199
|
+
const v = patch.value;
|
|
200
|
+
const parent = v.parentId
|
|
201
|
+
? findById(parsed.document, v.parentId)
|
|
202
|
+
: parsed.document.body;
|
|
203
|
+
if (!parent)
|
|
204
|
+
return;
|
|
205
|
+
// Parse within the target document to avoid cross-document node issues.
|
|
206
|
+
const tmp = parsed.document.createElement("div");
|
|
207
|
+
tmp.innerHTML = v.html;
|
|
208
|
+
const node = tmp.firstElementChild;
|
|
209
|
+
if (!node)
|
|
210
|
+
return;
|
|
211
|
+
const children = Array.from(parent.children);
|
|
212
|
+
const ref = children[v.siblingIndex] ?? null;
|
|
213
|
+
parent.insertBefore(node, ref);
|
|
214
|
+
}
|
|
215
|
+
break;
|
|
216
|
+
}
|
|
217
|
+
case "variable": {
|
|
218
|
+
if (!p.id)
|
|
219
|
+
return;
|
|
220
|
+
// B1: update the JSON model (data-composition-variables) so
|
|
221
|
+
// getVariables() returns the correct value in both preview and render.
|
|
222
|
+
// CSS compat is handled by explicit style-path patches emitted by mutate.ts,
|
|
223
|
+
// so we do NOT write CSS here — the style case above handles those patches.
|
|
224
|
+
applyVariableDefault(parsed.document, p.id, patch.op === "remove" ? null : patch.value);
|
|
225
|
+
break;
|
|
226
|
+
}
|
|
227
|
+
case "script": {
|
|
228
|
+
if (patch.op === "remove") {
|
|
229
|
+
setGsapScript(parsed.document, "");
|
|
230
|
+
}
|
|
231
|
+
else {
|
|
232
|
+
setGsapScript(parsed.document, String(patch.value ?? ""));
|
|
233
|
+
}
|
|
234
|
+
break;
|
|
235
|
+
}
|
|
236
|
+
case "stylesheet": {
|
|
237
|
+
if (patch.op === "remove") {
|
|
238
|
+
setStyleSheet(parsed.document, "");
|
|
239
|
+
}
|
|
240
|
+
else {
|
|
241
|
+
setStyleSheet(parsed.document, String(patch.value ?? ""));
|
|
242
|
+
}
|
|
243
|
+
break;
|
|
244
|
+
}
|
|
245
|
+
case "metadata": {
|
|
246
|
+
const root = findRoot(parsed.document);
|
|
247
|
+
if (!root || !p.field)
|
|
248
|
+
return;
|
|
249
|
+
// Mirror mutate.ts: style always written; the data-* forced-override
|
|
250
|
+
// attribute is updated only when the composition already carries it.
|
|
251
|
+
if (p.field === "width") {
|
|
252
|
+
if (patch.op === "remove") {
|
|
253
|
+
setElementStyles(root, { width: null });
|
|
254
|
+
root.removeAttribute("data-width");
|
|
255
|
+
}
|
|
256
|
+
else {
|
|
257
|
+
setElementStyles(root, { width: `${patch.value}px` });
|
|
258
|
+
if (root.hasAttribute("data-width"))
|
|
259
|
+
root.setAttribute("data-width", String(patch.value));
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
else if (p.field === "height") {
|
|
263
|
+
if (patch.op === "remove") {
|
|
264
|
+
setElementStyles(root, { height: null });
|
|
265
|
+
root.removeAttribute("data-height");
|
|
266
|
+
}
|
|
267
|
+
else {
|
|
268
|
+
setElementStyles(root, { height: `${patch.value}px` });
|
|
269
|
+
if (root.hasAttribute("data-height")) {
|
|
270
|
+
root.setAttribute("data-height", String(patch.value));
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
else if (p.field === "duration") {
|
|
275
|
+
if (patch.op === "remove")
|
|
276
|
+
root.removeAttribute("data-duration");
|
|
277
|
+
else
|
|
278
|
+
root.setAttribute("data-duration", String(patch.value));
|
|
279
|
+
}
|
|
280
|
+
break;
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
//# sourceMappingURL=apply-patches.js.map
|