@hyperframes/parsers 0.8.33 → 0.8.34
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/dist/gsapParser.d.ts +2 -2
- package/dist/gsapParser.js +2 -2
- package/dist/gsapParser.js.map +1 -1
- package/dist/gsapParserAcorn.d.ts +2 -2
- package/dist/gsapParserAcorn.js.map +1 -1
- package/dist/gsapParserExports.d.ts +1 -1
- package/dist/gsapParserExports.js +2 -2
- package/dist/gsapParserExports.js.map +1 -1
- package/dist/{gsapSerialize-DQW1kZ2G.d.ts → gsapSerialize-CqTuNW95.d.ts} +6 -0
- package/dist/gsapWriterAcorn.d.ts +1 -1
- package/dist/gsapWriterAcorn.js.map +1 -1
- package/dist/hfIds.d.ts +2 -1
- package/dist/hfIds.js +21 -16
- package/dist/hfIds.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +26 -21
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/hfIds.d.ts
CHANGED
|
@@ -9,7 +9,7 @@ declare const EXCLUDED_TAGS: Set<string>;
|
|
|
9
9
|
* `data-hf-id` to source the attribute is physically bound to its element.
|
|
10
10
|
* Reordering identical siblings carries the attribute along → zero
|
|
11
11
|
* order-dependence post-persist. `ensureHfIds` skips pinned elements
|
|
12
|
-
* (`if (el
|
|
12
|
+
* (`if (getContractAttribute(el, "data-hf-id")) continue`), so normal operation
|
|
13
13
|
* never re-exposes the ordering after first persist.
|
|
14
14
|
*/
|
|
15
15
|
declare function mintHfId(el: Element, assigned: Set<string>): string;
|
|
@@ -21,6 +21,7 @@ declare function isCompositionTemplate(el: Element): boolean;
|
|
|
21
21
|
* this traversal instead.
|
|
22
22
|
*/
|
|
23
23
|
declare function walkCompositionDescendants(root: Document | Element, visit: (el: Element) => void): void;
|
|
24
|
+
|
|
24
25
|
declare function ensureHfIds(html: string): string;
|
|
25
26
|
|
|
26
27
|
export { EXCLUDED_TAGS, ensureHfIds, isCompositionTemplate, mintHfId, walkCompositionDescendants };
|
package/dist/hfIds.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
// src/hfIds.ts
|
|
2
2
|
import { parseHTML } from "linkedom";
|
|
3
|
+
|
|
4
|
+
// src/hfIdAssignment.ts
|
|
3
5
|
var EXCLUDED_TAGS = /* @__PURE__ */ new Set([
|
|
4
6
|
"script",
|
|
5
7
|
"style",
|
|
@@ -29,8 +31,11 @@ function ownText(el) {
|
|
|
29
31
|
});
|
|
30
32
|
return text.trim();
|
|
31
33
|
}
|
|
34
|
+
function getContractAttribute(el, name) {
|
|
35
|
+
return Array.from(el.attributes).find((attr) => attr.name.toLowerCase() === name)?.value ?? null;
|
|
36
|
+
}
|
|
32
37
|
function contentKey(el) {
|
|
33
|
-
const attrs = Array.from(el.attributes).filter((a) => !a.name.startsWith("data-hf-")).map((a) => `${a.name}\0${a.value}`).sort().join("");
|
|
38
|
+
const attrs = Array.from(el.attributes).filter((a) => !a.name.toLowerCase().startsWith("data-hf-")).map((a) => `${a.name.toLowerCase()}\0${a.value}`).sort().join("");
|
|
34
39
|
return `${el.tagName.toLowerCase()}|${attrs}|${ownText(el)}`;
|
|
35
40
|
}
|
|
36
41
|
function mintHfId(el, assigned) {
|
|
@@ -57,9 +62,9 @@ function getChildElements(parent) {
|
|
|
57
62
|
}
|
|
58
63
|
function isCompositionTemplate(el) {
|
|
59
64
|
if (el.tagName.toLowerCase() !== "template") return false;
|
|
60
|
-
if (el
|
|
65
|
+
if (getContractAttribute(el, "data-composition-id") !== null) return true;
|
|
61
66
|
for (const child of getChildElements(el)) {
|
|
62
|
-
if (child
|
|
67
|
+
if (getContractAttribute(child, "data-composition-id") !== null) return true;
|
|
63
68
|
}
|
|
64
69
|
return false;
|
|
65
70
|
}
|
|
@@ -76,25 +81,25 @@ function walkCompositionDescendants(root, visit) {
|
|
|
76
81
|
};
|
|
77
82
|
walk(rootElement);
|
|
78
83
|
}
|
|
79
|
-
function
|
|
80
|
-
walkCompositionDescendants(root, visit);
|
|
81
|
-
}
|
|
82
|
-
function ensureHfIds(html) {
|
|
83
|
-
const hasDocumentShell = /<!doctype|<html[\s>]/i.test(html);
|
|
84
|
-
const wrapped = !hasDocumentShell;
|
|
85
|
-
const { document } = wrapped ? parseHTML(`<!DOCTYPE html><html><head></head><body>${html}</body></html>`) : parseHTML(html);
|
|
86
|
-
const body = document.body;
|
|
87
|
-
if (!body) return html;
|
|
84
|
+
function assignHfIds(body) {
|
|
88
85
|
const assigned = /* @__PURE__ */ new Set();
|
|
89
|
-
|
|
90
|
-
const existing = el
|
|
86
|
+
walkCompositionDescendants(body, (el) => {
|
|
87
|
+
const existing = getContractAttribute(el, "data-hf-id");
|
|
91
88
|
if (existing) assigned.add(existing);
|
|
92
89
|
});
|
|
93
|
-
|
|
90
|
+
walkCompositionDescendants(body, (el) => {
|
|
94
91
|
if (EXCLUDED_TAGS.has(el.tagName.toLowerCase())) return;
|
|
95
|
-
if (el
|
|
92
|
+
if (getContractAttribute(el, "data-hf-id")) return;
|
|
96
93
|
el.setAttribute("data-hf-id", mintHfId(el, assigned));
|
|
97
94
|
});
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// src/hfIds.ts
|
|
98
|
+
function ensureHfIds(html) {
|
|
99
|
+
const wrapped = !/<!doctype|<html[\s>]/i.test(html);
|
|
100
|
+
const { document } = wrapped ? parseHTML(`<!DOCTYPE html><html><head></head><body>${html}</body></html>`) : parseHTML(html);
|
|
101
|
+
if (!document.body) return html;
|
|
102
|
+
assignHfIds(document.body);
|
|
98
103
|
return wrapped ? document.body.innerHTML || "" : document.toString();
|
|
99
104
|
}
|
|
100
105
|
export {
|
package/dist/hfIds.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/hfIds.ts"],"sourcesContent":["/**\n * Stable hf- element id minting (R1). Node-safe (linkedom only, not browser DOM).\n *\n * Two surfaces share these helpers:\n * - ensureHfIds(html): node-id surface — mints data-hf-id on every element.\n * - mintHfId(el, assigned): shared by htmlParser for clip ids.\n *\n * Hash is CONTENT ONLY (tag + sorted attrs + own text) — no sibling position,\n * so inserting a non-identical sibling never shifts another element's id.\n */\nimport { parseHTML } from \"linkedom\";\n\n// Non-editable / non-visual elements that should never receive a stable id.\nexport const EXCLUDED_TAGS = new Set([\n \"script\",\n \"style\",\n \"template\",\n \"meta\",\n \"link\",\n \"noscript\",\n \"base\",\n]);\n\n// 32-bit FNV-1a. Pure, deterministic, no crypto, no Math.random.\nfunction fnv1a(str: string): number {\n let h = 0x811c9dc5;\n for (let i = 0; i < str.length; i++) {\n h ^= str.charCodeAt(i);\n h = Math.imul(h, 0x01000193);\n }\n return h >>> 0;\n}\n\n// 4 base-36 chars · 36^4 ≈ 1.68M ids per document. Birthday-paradox collision\n// ≈ N²/(2·36^4): well under 1% per document after dup rehash at realistic\n// clip-model sizes (≤ a few hundred elements). The dup-rehash in mintHfId\n// resolves the rare collision; width is deliberately small for readable ids.\nfunction toHfId(hash: number): string {\n const s = (hash >>> 0).toString(36);\n // Use suffix (most-avalanched bits) for better distribution within the 4-char window.\n const four = s.length >= 4 ? s.slice(-4) : s.padStart(4, \"0\");\n return `hf-${four}`;\n}\n\n// Element's own direct text (TEXT_NODE children), not descendants'.\nfunction ownText(el: Element): string {\n let text = \"\";\n el.childNodes.forEach((n) => {\n if (n.nodeType === 3) text += (n as Text).nodeValue ?? \"\";\n });\n return text.trim();\n}\n\nfunction contentKey(el: Element): string {\n // Exclude all data-hf-* attrs (ids, studio state) — they must not influence the hash.\n // Use \\x00 / \\x01 separators (invalid in HTML attrs) to prevent ambiguous serialization.\n const attrs = Array.from(el.attributes)\n .filter((a) => !a.name.startsWith(\"data-hf-\"))\n .map((a) => `${a.name}\\x00${a.value}`)\n .sort()\n .join(\"\\x01\");\n return `${el.tagName.toLowerCase()}|${attrs}|${ownText(el)}`;\n}\n\n/**\n * Collision tiebreak for byte-identical siblings: document-order dup counter\n * (`hash(key#N)`). This IS order-dependent — two identical `<span></span>`\n * get different ids based on which comes first in the DOM. This is unavoidable:\n * unique ids for byte-identical elements require a positional signal.\n *\n * Why this is safe in practice: once `ensureHfIds` write-back persists\n * `data-hf-id` to source the attribute is physically bound to its element.\n * Reordering identical siblings carries the attribute along → zero\n * order-dependence post-persist. `ensureHfIds` skips pinned elements\n * (`if (el.getAttribute(\"data-hf-id\")) continue`), so normal operation\n * never re-exposes the ordering after first persist.\n */\n// WIRE CONTRACT: id minting is content-keyed (FNV1a of innerHTML + tag). R7's\n// preview route relies on mintHfId producing identical ids across mint contexts\n// (disk-persist pass vs. in-memory bundle pass) — see preview.test.ts\n// \"bundle returning untagged HTML gets same ids as disk\". Any change that adds\n// positional, session, or random input to the hash breaks that invariant and\n// makes hf- ids diverge between disk and served HTML, silently corrupting\n// drag-to-edit targeting.\nexport function mintHfId(el: Element, assigned: Set<string>): string {\n const key = contentKey(el);\n let id = toHfId(fnv1a(key));\n let dup = 0;\n while (assigned.has(id)) {\n dup += 1;\n // Graceful fallback instead of a hard throw: rehashing only fails to find a\n // free 4-char slot in a pathological document (~1.6M identical elements).\n // Rather than crash the whole parse, widen the id with the dup counter —\n // still deterministic and unique, just longer than the 4-char norm.\n if (dup > 10000) {\n id = `hf-${(fnv1a(key) >>> 0).toString(36)}-${dup}`;\n break;\n }\n id = toHfId(fnv1a(`${key}#${dup}`));\n }\n assigned.add(id);\n return id;\n}\n\n/**\n * True for a sub-composition authoring template whose content the studio preview\n * unwraps into the served body. Two accepted forms:\n * A) `<template data-composition-id=\"X\">…` — the id on the template itself.\n * B) `<template id=\"X-template\"><div data-composition-id=\"X\">…` — the id on the\n * wrapped root div (the form `hyperframes add` scaffolds and registry blocks use).\n * Only these are treated as transparent containers for hf-id purposes. A plain\n * `<template>` (runtime clone-source: list item, particle, etc.) must NOT get\n * inner ids — its content is cloned N times into the live DOM, so a persisted\n * inner id would be duplicated across every clone. Form B is distinguished from\n * a clone-source by the presence of a direct `[data-composition-id]` child.\n */\nfunction getChildElements(parent: Element): Element[] {\n const directChildren = Array.from(parent.children);\n if (directChildren.length || parent.tagName.toLowerCase() !== \"template\") return directChildren;\n const content = (parent as HTMLTemplateElement).content;\n if (content?.children.length) return Array.from(content.children);\n return directChildren;\n}\n\nexport function isCompositionTemplate(el: Element): boolean {\n if (el.tagName.toLowerCase() !== \"template\") return false;\n if (el.getAttribute(\"data-composition-id\") !== null) return true;\n for (const child of getChildElements(el)) {\n if (child.getAttribute(\"data-composition-id\") !== null) return true;\n }\n return false;\n}\n\n/**\n * Walk document-order descendants, descending through composition templates\n * while keeping plain templates inert. linkedom's querySelectorAll does not\n * expose template contents, so callers that model the served composition use\n * this traversal instead.\n */\nexport function walkCompositionDescendants(\n root: Document | Element,\n visit: (el: Element) => void,\n): void {\n const rootElement: Element | null =\n root.nodeType === 9 ? (root as Document).documentElement : (root as Element);\n if (!rootElement) return;\n\n const walk = (parent: Element): void => {\n for (const child of getChildElements(parent)) {\n const isTemplate = child.tagName.toLowerCase() === \"template\";\n if (isTemplate && !isCompositionTemplate(child)) continue;\n visit(child);\n walk(child);\n }\n };\n\n walk(rootElement);\n}\n\n/**\n * Document-order walk of every element under `root`, descending into\n * composition `<template>` subtrees — linkedom's querySelectorAll does not, so\n * template-based sub-comps would otherwise never get inner ids (the preview\n * unwraps the template and stamps the SAME content, so skipping here splits\n * the id space between the served DOM and the raw file). Plain templates are\n * skipped entirely (see isCompositionTemplate).\n */\nfunction walkElements(root: Element, visit: (el: Element) => void): void {\n walkCompositionDescendants(root, visit);\n}\n\nexport function ensureHfIds(html: string): string {\n // Mirror parseSourceDocument's fragment-wrapping so bare fragments don't land\n // outside <body> in linkedom, which would cause body.querySelectorAll to return [].\n const hasDocumentShell = /<!doctype|<html[\\s>]/i.test(html);\n const wrapped = !hasDocumentShell;\n const { document } = wrapped\n ? parseHTML(`<!DOCTYPE html><html><head></head><body>${html}</body></html>`)\n : parseHTML(html);\n const body = document.body;\n if (!body) return html;\n\n const assigned = new Set<string>();\n // Seed with already-present ids (pin) so fresh mints never collide with them.\n // Scope to <body> to match the mint walk below — a stray data-hf-id in <head>\n // must not pin an id into the set that a body element would then be bumped off.\n walkElements(body, (el) => {\n const existing = el.getAttribute(\"data-hf-id\");\n if (existing) assigned.add(existing);\n });\n\n walkElements(body, (el) => {\n if (EXCLUDED_TAGS.has(el.tagName.toLowerCase())) return;\n if (el.getAttribute(\"data-hf-id\")) return; // pinned\n el.setAttribute(\"data-hf-id\", mintHfId(el, assigned));\n });\n\n return wrapped ? document.body.innerHTML || \"\" : document.toString();\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/hfIds.ts","../src/hfIdAssignment.ts"],"sourcesContent":["/**\n * Stable hf- element id minting (R1). Node-safe (linkedom only, not browser DOM).\n *\n * Two surfaces share these helpers:\n * - ensureHfIds(html): node-id surface — mints data-hf-id on every element.\n * - mintHfId(el, assigned): shared by htmlParser for clip ids.\n *\n * Hash is CONTENT ONLY (tag + sorted attrs + own text) — no sibling position,\n * so inserting a non-identical sibling never shifts another element's id.\n */\nimport { parseHTML } from \"linkedom\";\nimport { assignHfIds } from \"./hfIdAssignment.js\";\nexport {\n EXCLUDED_TAGS,\n mintHfId,\n isCompositionTemplate,\n walkCompositionDescendants,\n} from \"./hfIdAssignment.js\";\n\nexport function ensureHfIds(html: string): string {\n // Wrap fragments so every body element participates in stable ID assignment.\n const wrapped = !/<!doctype|<html[\\s>]/i.test(html);\n const { document } = wrapped\n ? parseHTML(`<!DOCTYPE html><html><head></head><body>${html}</body></html>`)\n : parseHTML(html);\n if (!document.body) return html;\n assignHfIds(document.body);\n return wrapped ? document.body.innerHTML || \"\" : document.toString();\n}\n","// Non-editable / non-visual elements that should never receive a stable id.\nexport const EXCLUDED_TAGS = new Set([\n \"script\",\n \"style\",\n \"template\",\n \"meta\",\n \"link\",\n \"noscript\",\n \"base\",\n]);\n\n// 32-bit FNV-1a. Pure, deterministic, no crypto, no Math.random.\nfunction fnv1a(str: string): number {\n let h = 0x811c9dc5;\n for (let i = 0; i < str.length; i++) {\n h ^= str.charCodeAt(i);\n h = Math.imul(h, 0x01000193);\n }\n return h >>> 0;\n}\n\n// 4 base-36 chars · 36^4 ≈ 1.68M ids per document. Birthday-paradox collision\n// ≈ N²/(2·36^4): well under 1% per document after dup rehash at realistic\n// clip-model sizes (≤ a few hundred elements). The dup-rehash in mintHfId\n// resolves the rare collision; width is deliberately small for readable ids.\nfunction toHfId(hash: number): string {\n const s = (hash >>> 0).toString(36);\n // Use suffix (most-avalanched bits) for better distribution within the 4-char window.\n const four = s.length >= 4 ? s.slice(-4) : s.padStart(4, \"0\");\n return `hf-${four}`;\n}\n\n// Element's own direct text (TEXT_NODE children), not descendants'.\nfunction ownText(el: Element): string {\n let text = \"\";\n el.childNodes.forEach((n) => {\n if (n.nodeType === 3) text += (n as Text).nodeValue ?? \"\";\n });\n return text.trim();\n}\n\nfunction getContractAttribute(el: Element, name: string): string | null {\n return Array.from(el.attributes).find((attr) => attr.name.toLowerCase() === name)?.value ?? null;\n}\n\nfunction contentKey(el: Element): string {\n // HTML parsers normalize foreign-content attribute names differently too.\n // Canonicalize only the hash input; retain actual SVG attribute spelling.\n // Exclude all data-hf-* attrs (ids, studio state) — they must not influence the hash.\n // Use \\x00 / \\x01 separators (invalid in HTML attrs) to prevent ambiguous serialization.\n const attrs = Array.from(el.attributes)\n .filter((a) => !a.name.toLowerCase().startsWith(\"data-hf-\"))\n .map((a) => `${a.name.toLowerCase()}\\x00${a.value}`)\n .sort()\n .join(\"\\x01\");\n return `${el.tagName.toLowerCase()}|${attrs}|${ownText(el)}`;\n}\n\n/**\n * Collision tiebreak for byte-identical siblings: document-order dup counter\n * (`hash(key#N)`). This IS order-dependent — two identical `<span></span>`\n * get different ids based on which comes first in the DOM. This is unavoidable:\n * unique ids for byte-identical elements require a positional signal.\n *\n * Why this is safe in practice: once `ensureHfIds` write-back persists\n * `data-hf-id` to source the attribute is physically bound to its element.\n * Reordering identical siblings carries the attribute along → zero\n * order-dependence post-persist. `ensureHfIds` skips pinned elements\n * (`if (getContractAttribute(el, \"data-hf-id\")) continue`), so normal operation\n * never re-exposes the ordering after first persist.\n */\n// WIRE CONTRACT: id minting is content-keyed (FNV1a of innerHTML + tag). R7's\n// preview route relies on mintHfId producing identical ids across mint contexts\n// (disk-persist pass vs. in-memory bundle pass) — see preview.test.ts\n// \"bundle returning untagged HTML gets same ids as disk\". Any change that adds\n// positional, session, or random input to the hash breaks that invariant and\n// makes hf- ids diverge between disk and served HTML, silently corrupting\n// drag-to-edit targeting.\nexport function mintHfId(el: Element, assigned: Set<string>): string {\n const key = contentKey(el);\n let id = toHfId(fnv1a(key));\n let dup = 0;\n while (assigned.has(id)) {\n dup += 1;\n // Graceful fallback instead of a hard throw: rehashing only fails to find a\n // free 4-char slot in a pathological document (~1.6M identical elements).\n // Rather than crash the whole parse, widen the id with the dup counter —\n // still deterministic and unique, just longer than the 4-char norm.\n if (dup > 10000) {\n id = `hf-${(fnv1a(key) >>> 0).toString(36)}-${dup}`;\n break;\n }\n id = toHfId(fnv1a(`${key}#${dup}`));\n }\n assigned.add(id);\n return id;\n}\n\n/**\n * True for a sub-composition authoring template whose content the studio preview\n * unwraps into the served body. Two accepted forms:\n * A) `<template data-composition-id=\"X\">…` — the id on the template itself.\n * B) `<template id=\"X-template\"><div data-composition-id=\"X\">…` — the id on the\n * wrapped root div (the form `hyperframes add` scaffolds and registry blocks use).\n * Only these are treated as transparent containers for hf-id purposes. A plain\n * `<template>` (runtime clone-source: list item, particle, etc.) must NOT get\n * inner ids — its content is cloned N times into the live DOM, so a persisted\n * inner id would be duplicated across every clone. Form B is distinguished from\n * a clone-source by the presence of a direct `[data-composition-id]` child.\n */\nfunction getChildElements(parent: Element): Element[] {\n const directChildren = Array.from(parent.children);\n if (directChildren.length || parent.tagName.toLowerCase() !== \"template\") return directChildren;\n const content = (parent as HTMLTemplateElement).content;\n if (content?.children.length) return Array.from(content.children);\n return directChildren;\n}\n\nexport function isCompositionTemplate(el: Element): boolean {\n if (el.tagName.toLowerCase() !== \"template\") return false;\n if (getContractAttribute(el, \"data-composition-id\") !== null) return true;\n for (const child of getChildElements(el)) {\n if (getContractAttribute(child, \"data-composition-id\") !== null) return true;\n }\n return false;\n}\n\n/**\n * Walk document-order descendants, descending through composition templates\n * while keeping plain templates inert. linkedom's querySelectorAll does not\n * expose template contents, so callers that model the served composition use\n * this traversal instead.\n */\nexport function walkCompositionDescendants(\n root: Document | Element,\n visit: (el: Element) => void,\n): void {\n const rootElement: Element | null =\n root.nodeType === 9 ? (root as Document).documentElement : (root as Element);\n if (!rootElement) return;\n\n const walk = (parent: Element): void => {\n for (const child of getChildElements(parent)) {\n const isTemplate = child.tagName.toLowerCase() === \"template\";\n if (isTemplate && !isCompositionTemplate(child)) continue;\n visit(child);\n walk(child);\n }\n };\n\n walk(rootElement);\n}\n\n// Internal DOM-only assignment: callers retain their parser and document identity.\nexport function assignHfIds(body: Element): void {\n const assigned = new Set<string>();\n walkCompositionDescendants(body, (el) => {\n const existing = getContractAttribute(el, \"data-hf-id\");\n if (existing) assigned.add(existing);\n });\n walkCompositionDescendants(body, (el) => {\n if (EXCLUDED_TAGS.has(el.tagName.toLowerCase())) return;\n if (getContractAttribute(el, \"data-hf-id\")) return;\n el.setAttribute(\"data-hf-id\", mintHfId(el, assigned));\n });\n}\n"],"mappings":"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","names":[]}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { C as CompositionVariable, T as TimelineElement, a as CanvasResolution, K as Keyframe, S as StageZoomKeyframe, V as ValidationResult } from './types-ewozML_N.js';
|
|
2
2
|
export { A as AddElementData, b as Asset, B as BooleanVariable, c as CANVAS_DIMENSIONS, d as COMPOSITION_VARIABLE_TYPES, e as ColorVariable, f as CompositionAPI, g as CompositionAsset, h as CompositionSpec, i as CompositionVariableBase, j as CompositionVariableType, D as DEFAULT_DURATIONS, E as ElementKeyframes, k as EnumVariable, F as FontVariable, I as ImageVariable, l as KeyframeProperties, M as MediaElementType, m as MediaFile, N as NumberVariable, P as PlayerAPI, R as ResolvedResolutionFlag, n as StageZoom, o as StringVariable, p as TIMELINE_COLORS, q as TimelineCompositionElement, r as TimelineElementBase, s as TimelineElementType, t as TimelineMediaElement, u as TimelineTextElement, v as VALID_CANVAS_RESOLUTIONS, W as WaveformData, w as getDefaultStageZoom, x as isAspectAgnosticResolutionAlias, y as isCompositionElement, z as isMediaElement, G as isTextElement, H as normalizeResolutionFlag, J as resolveResolutionFlagPair } from './types-ewozML_N.js';
|
|
3
|
-
export { A as ArcPathConfig, a as ArcPathSegment, G as GsapAnimation, b as GsapKeyframesData, c as GsapMethod, d as GsapPercentageKeyframe, e as GsapProvenance, f as GsapProvenanceKind, K as KeyframeEditability, P as ParsedGsap, S as SourcedGsapPercentageKeyframe, g as SplitAnimationsOptions, h as SplitAnimationsResult, i as editabilityForProvenance, j as getAnimationsForElementId, k as gsapAnimationsToKeyframes, l as keyframesToGsapAnimations, s as serializeGsapAnimations, v as validateCompositionGsap } from './gsapSerialize-
|
|
3
|
+
export { A as ArcPathConfig, a as ArcPathSegment, G as GsapAnimation, b as GsapKeyframesData, c as GsapMethod, d as GsapPercentageKeyframe, e as GsapProvenance, f as GsapProvenanceKind, K as KeyframeEditability, P as ParsedGsap, S as SourcedGsapPercentageKeyframe, g as SplitAnimationsOptions, h as SplitAnimationsResult, i as editabilityForProvenance, j as getAnimationsForElementId, k as gsapAnimationsToKeyframes, l as keyframesToGsapAnimations, s as serializeGsapAnimations, v as validateCompositionGsap } from './gsapSerialize-CqTuNW95.js';
|
|
4
4
|
export { isStudioHoldSet } from './gsapParser.js';
|
|
5
5
|
export { PROPERTY_GROUPS, PropertyGroupName, SUPPORTED_EASES, SUPPORTED_PROPS, classifyPropertyGroup, classifyTweenPropertyGroup } from './gsapConstants.js';
|
|
6
6
|
export { SPRING_PRESETS, SpringPreset, generateSpringEaseData } from './springEase.js';
|
package/dist/index.js
CHANGED
|
@@ -225,7 +225,7 @@ function serializeGsapAnimations(animations, timelineVar = "tl", options) {
|
|
|
225
225
|
return aNum - bNum;
|
|
226
226
|
});
|
|
227
227
|
const lines = sorted.map((anim) => {
|
|
228
|
-
const selector =
|
|
228
|
+
const selector = JSON.stringify(anim.targetSelector);
|
|
229
229
|
const props = { ...anim.properties };
|
|
230
230
|
if (anim.duration !== void 0) props.duration = anim.duration;
|
|
231
231
|
if (anim.ease) props.ease = anim.ease;
|
|
@@ -238,7 +238,7 @@ function serializeGsapAnimations(animations, timelineVar = "tl", options) {
|
|
|
238
238
|
propsStr = propsStr.slice(0, -2) + `, ${extrasStr} }`;
|
|
239
239
|
}
|
|
240
240
|
}
|
|
241
|
-
const posStr =
|
|
241
|
+
const posStr = JSON.stringify(anim.position);
|
|
242
242
|
switch (anim.method) {
|
|
243
243
|
case "set":
|
|
244
244
|
return anim.global ? ` gsap.set(${selector}, ${propsStr});` : ` ${timelineVar}.set(${selector}, ${propsStr}, ${posStr});`;
|
|
@@ -2245,6 +2245,8 @@ function parseCompositionVariables(htmlEl) {
|
|
|
2245
2245
|
|
|
2246
2246
|
// src/hfIds.ts
|
|
2247
2247
|
import { parseHTML } from "linkedom";
|
|
2248
|
+
|
|
2249
|
+
// src/hfIdAssignment.ts
|
|
2248
2250
|
var EXCLUDED_TAGS = /* @__PURE__ */ new Set([
|
|
2249
2251
|
"script",
|
|
2250
2252
|
"style",
|
|
@@ -2274,8 +2276,11 @@ function ownText(el) {
|
|
|
2274
2276
|
});
|
|
2275
2277
|
return text.trim();
|
|
2276
2278
|
}
|
|
2279
|
+
function getContractAttribute(el, name) {
|
|
2280
|
+
return Array.from(el.attributes).find((attr) => attr.name.toLowerCase() === name)?.value ?? null;
|
|
2281
|
+
}
|
|
2277
2282
|
function contentKey(el) {
|
|
2278
|
-
const attrs = Array.from(el.attributes).filter((a) => !a.name.startsWith("data-hf-")).map((a) => `${a.name}\0${a.value}`).sort().join("");
|
|
2283
|
+
const attrs = Array.from(el.attributes).filter((a) => !a.name.toLowerCase().startsWith("data-hf-")).map((a) => `${a.name.toLowerCase()}\0${a.value}`).sort().join("");
|
|
2279
2284
|
return `${el.tagName.toLowerCase()}|${attrs}|${ownText(el)}`;
|
|
2280
2285
|
}
|
|
2281
2286
|
function mintHfId(el, assigned) {
|
|
@@ -2302,9 +2307,9 @@ function getChildElements(parent) {
|
|
|
2302
2307
|
}
|
|
2303
2308
|
function isCompositionTemplate(el) {
|
|
2304
2309
|
if (el.tagName.toLowerCase() !== "template") return false;
|
|
2305
|
-
if (el
|
|
2310
|
+
if (getContractAttribute(el, "data-composition-id") !== null) return true;
|
|
2306
2311
|
for (const child of getChildElements(el)) {
|
|
2307
|
-
if (child
|
|
2312
|
+
if (getContractAttribute(child, "data-composition-id") !== null) return true;
|
|
2308
2313
|
}
|
|
2309
2314
|
return false;
|
|
2310
2315
|
}
|
|
@@ -2321,25 +2326,25 @@ function walkCompositionDescendants(root, visit) {
|
|
|
2321
2326
|
};
|
|
2322
2327
|
walk(rootElement);
|
|
2323
2328
|
}
|
|
2324
|
-
function
|
|
2325
|
-
walkCompositionDescendants(root, visit);
|
|
2326
|
-
}
|
|
2327
|
-
function ensureHfIds(html) {
|
|
2328
|
-
const hasDocumentShell = /<!doctype|<html[\s>]/i.test(html);
|
|
2329
|
-
const wrapped = !hasDocumentShell;
|
|
2330
|
-
const { document } = wrapped ? parseHTML(`<!DOCTYPE html><html><head></head><body>${html}</body></html>`) : parseHTML(html);
|
|
2331
|
-
const body = document.body;
|
|
2332
|
-
if (!body) return html;
|
|
2329
|
+
function assignHfIds(body) {
|
|
2333
2330
|
const assigned = /* @__PURE__ */ new Set();
|
|
2334
|
-
|
|
2335
|
-
const existing = el
|
|
2331
|
+
walkCompositionDescendants(body, (el) => {
|
|
2332
|
+
const existing = getContractAttribute(el, "data-hf-id");
|
|
2336
2333
|
if (existing) assigned.add(existing);
|
|
2337
2334
|
});
|
|
2338
|
-
|
|
2335
|
+
walkCompositionDescendants(body, (el) => {
|
|
2339
2336
|
if (EXCLUDED_TAGS.has(el.tagName.toLowerCase())) return;
|
|
2340
|
-
if (el
|
|
2337
|
+
if (getContractAttribute(el, "data-hf-id")) return;
|
|
2341
2338
|
el.setAttribute("data-hf-id", mintHfId(el, assigned));
|
|
2342
2339
|
});
|
|
2340
|
+
}
|
|
2341
|
+
|
|
2342
|
+
// src/hfIds.ts
|
|
2343
|
+
function ensureHfIds(html) {
|
|
2344
|
+
const wrapped = !/<!doctype|<html[\s>]/i.test(html);
|
|
2345
|
+
const { document } = wrapped ? parseHTML(`<!DOCTYPE html><html><head></head><body>${html}</body></html>`) : parseHTML(html);
|
|
2346
|
+
if (!document.body) return html;
|
|
2347
|
+
assignHfIds(document.body);
|
|
2343
2348
|
return wrapped ? document.body.innerHTML || "" : document.toString();
|
|
2344
2349
|
}
|
|
2345
2350
|
|
|
@@ -2771,9 +2776,8 @@ function resolveResolutionFromDimensions(width, height) {
|
|
|
2771
2776
|
return isUhd ? "portrait-4k" : "portrait";
|
|
2772
2777
|
}
|
|
2773
2778
|
function parseHtml(html) {
|
|
2774
|
-
const withIds = ensureHfIds(html);
|
|
2775
2779
|
const parser = new DOMParser();
|
|
2776
|
-
const doc = parser.parseFromString(
|
|
2780
|
+
const doc = parser.parseFromString(html, "text/html");
|
|
2777
2781
|
const elements = [];
|
|
2778
2782
|
const keyframes = {};
|
|
2779
2783
|
let idCounter = 0;
|
|
@@ -2781,6 +2785,7 @@ function parseHtml(html) {
|
|
|
2781
2785
|
if (!htmlEl) {
|
|
2782
2786
|
throw new CompositionHtmlParseError("parseHtml: input HTML is empty or could not be parsed");
|
|
2783
2787
|
}
|
|
2788
|
+
if (doc.body) assignHfIds(doc.body);
|
|
2784
2789
|
const customStylesAttr = htmlEl.getAttribute("data-custom-styles");
|
|
2785
2790
|
let customStyles = null;
|
|
2786
2791
|
if (customStylesAttr) {
|
|
@@ -2839,7 +2844,7 @@ function parseHtml(html) {
|
|
|
2839
2844
|
const opacity = opacityAttr ? parseFloat(opacityAttr) : void 0;
|
|
2840
2845
|
if (type === "text") {
|
|
2841
2846
|
const textEl = el.firstElementChild;
|
|
2842
|
-
const content = textEl?.textContent
|
|
2847
|
+
const content = textEl?.textContent ?? name;
|
|
2843
2848
|
const color = el.getAttribute("data-color") || void 0;
|
|
2844
2849
|
const fontSizeAttr = el.getAttribute("data-font-size");
|
|
2845
2850
|
const fontSize = fontSizeAttr ? parseInt(fontSizeAttr, 10) : void 0;
|