@hyperframes/parsers 0.7.51 → 0.7.53
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.js +7 -1
- package/dist/gsapParser.js.map +1 -1
- package/dist/gsapParserExports.js.map +1 -1
- package/dist/gsapWriterAcorn.js +8 -2
- package/dist/gsapWriterAcorn.js.map +1 -1
- package/dist/hfIds.d.ts +7 -12
- package/dist/hfIds.js +24 -8
- package/dist/hfIds.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +40 -17
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/hfIds.d.ts
CHANGED
|
@@ -13,19 +13,14 @@ declare const EXCLUDED_TAGS: Set<string>;
|
|
|
13
13
|
* never re-exposes the ordering after first persist.
|
|
14
14
|
*/
|
|
15
15
|
declare function mintHfId(el: Element, assigned: Set<string>): string;
|
|
16
|
+
declare function isCompositionTemplate(el: Element): boolean;
|
|
16
17
|
/**
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
* wrapped root div (the form `hyperframes add` scaffolds and registry blocks use).
|
|
22
|
-
* Only these are treated as transparent containers for hf-id purposes. A plain
|
|
23
|
-
* `<template>` (runtime clone-source: list item, particle, etc.) must NOT get
|
|
24
|
-
* inner ids — its content is cloned N times into the live DOM, so a persisted
|
|
25
|
-
* inner id would be duplicated across every clone. Form B is distinguished from
|
|
26
|
-
* a clone-source by the presence of a direct `[data-composition-id]` child.
|
|
18
|
+
* Walk document-order descendants, descending through composition templates
|
|
19
|
+
* while keeping plain templates inert. linkedom's querySelectorAll does not
|
|
20
|
+
* expose template contents, so callers that model the served composition use
|
|
21
|
+
* this traversal instead.
|
|
27
22
|
*/
|
|
28
|
-
declare function
|
|
23
|
+
declare function walkCompositionDescendants(root: Document | Element, visit: (el: Element) => void): void;
|
|
29
24
|
declare function ensureHfIds(html: string): string;
|
|
30
25
|
|
|
31
|
-
export { EXCLUDED_TAGS, ensureHfIds, isCompositionTemplate, mintHfId };
|
|
26
|
+
export { EXCLUDED_TAGS, ensureHfIds, isCompositionTemplate, mintHfId, walkCompositionDescendants };
|
package/dist/hfIds.js
CHANGED
|
@@ -48,21 +48,36 @@ function mintHfId(el, assigned) {
|
|
|
48
48
|
assigned.add(id);
|
|
49
49
|
return id;
|
|
50
50
|
}
|
|
51
|
+
function getChildElements(parent) {
|
|
52
|
+
const directChildren = Array.from(parent.children);
|
|
53
|
+
if (directChildren.length || parent.tagName.toLowerCase() !== "template") return directChildren;
|
|
54
|
+
const content = parent.content;
|
|
55
|
+
if (content?.children.length) return Array.from(content.children);
|
|
56
|
+
return directChildren;
|
|
57
|
+
}
|
|
51
58
|
function isCompositionTemplate(el) {
|
|
52
59
|
if (el.tagName.toLowerCase() !== "template") return false;
|
|
53
60
|
if (el.getAttribute("data-composition-id") !== null) return true;
|
|
54
|
-
for (const child of
|
|
61
|
+
for (const child of getChildElements(el)) {
|
|
55
62
|
if (child.getAttribute("data-composition-id") !== null) return true;
|
|
56
63
|
}
|
|
57
64
|
return false;
|
|
58
65
|
}
|
|
66
|
+
function walkCompositionDescendants(root, visit) {
|
|
67
|
+
const rootElement = root.nodeType === 9 ? root.documentElement : root;
|
|
68
|
+
if (!rootElement) return;
|
|
69
|
+
const walk = (parent) => {
|
|
70
|
+
for (const child of getChildElements(parent)) {
|
|
71
|
+
const isTemplate = child.tagName.toLowerCase() === "template";
|
|
72
|
+
if (isTemplate && !isCompositionTemplate(child)) continue;
|
|
73
|
+
visit(child);
|
|
74
|
+
walk(child);
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
walk(rootElement);
|
|
78
|
+
}
|
|
59
79
|
function walkElements(root, visit) {
|
|
60
|
-
|
|
61
|
-
const isTemplate = child.tagName.toLowerCase() === "template";
|
|
62
|
-
if (isTemplate && !isCompositionTemplate(child)) continue;
|
|
63
|
-
visit(child);
|
|
64
|
-
walkElements(child, visit);
|
|
65
|
-
}
|
|
80
|
+
walkCompositionDescendants(root, visit);
|
|
66
81
|
}
|
|
67
82
|
function ensureHfIds(html) {
|
|
68
83
|
const hasDocumentShell = /<!doctype|<html[\s>]/i.test(html);
|
|
@@ -86,6 +101,7 @@ export {
|
|
|
86
101
|
EXCLUDED_TAGS,
|
|
87
102
|
ensureHfIds,
|
|
88
103
|
isCompositionTemplate,
|
|
89
|
-
mintHfId
|
|
104
|
+
mintHfId,
|
|
105
|
+
walkCompositionDescendants
|
|
90
106
|
};
|
|
91
107
|
//# sourceMappingURL=hfIds.js.map
|
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 */\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 Array.from(el.children)) {\n if (child.getAttribute(\"data-composition-id\") !== null) return true;\n }\n return false;\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 for (const child of Array.from(root.children)) {\n const isTemplate = child.tagName.toLowerCase() === \"template\";\n if (isTemplate && !isCompositionTemplate(child)) continue;\n visit(child);\n walkElements(child, visit);\n }\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"],"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":[]}
|
package/dist/index.d.ts
CHANGED
|
@@ -5,7 +5,7 @@ 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';
|
|
7
7
|
export { parseGsapScriptAcorn as parseGsapScript } from './gsapParserAcorn.js';
|
|
8
|
-
export { EXCLUDED_TAGS, ensureHfIds, isCompositionTemplate, mintHfId } from './hfIds.js';
|
|
8
|
+
export { EXCLUDED_TAGS, ensureHfIds, isCompositionTemplate, mintHfId, walkCompositionDescendants } from './hfIds.js';
|
|
9
9
|
export { ParsableDocumentLike, SubCompositionValidity, SubCompositionValidityReason, checkSubCompositionUsability } from './subCompositionValidity.js';
|
|
10
10
|
export { CANONICAL_FONT_DISPLAY_NAMES, FONT_ALIAS_KEYS, FONT_ALIAS_MAP, VariableUsageScan, decodeUrlPathVariants, parseCompositionVariables, resolveAliasDisplayName, scanVariableUsage } from './composition.js';
|
|
11
11
|
|
package/dist/index.js
CHANGED
|
@@ -2026,21 +2026,36 @@ function mintHfId(el, assigned) {
|
|
|
2026
2026
|
assigned.add(id);
|
|
2027
2027
|
return id;
|
|
2028
2028
|
}
|
|
2029
|
+
function getChildElements(parent) {
|
|
2030
|
+
const directChildren = Array.from(parent.children);
|
|
2031
|
+
if (directChildren.length || parent.tagName.toLowerCase() !== "template") return directChildren;
|
|
2032
|
+
const content = parent.content;
|
|
2033
|
+
if (content?.children.length) return Array.from(content.children);
|
|
2034
|
+
return directChildren;
|
|
2035
|
+
}
|
|
2029
2036
|
function isCompositionTemplate(el) {
|
|
2030
2037
|
if (el.tagName.toLowerCase() !== "template") return false;
|
|
2031
2038
|
if (el.getAttribute("data-composition-id") !== null) return true;
|
|
2032
|
-
for (const child of
|
|
2039
|
+
for (const child of getChildElements(el)) {
|
|
2033
2040
|
if (child.getAttribute("data-composition-id") !== null) return true;
|
|
2034
2041
|
}
|
|
2035
2042
|
return false;
|
|
2036
2043
|
}
|
|
2044
|
+
function walkCompositionDescendants(root, visit) {
|
|
2045
|
+
const rootElement = root.nodeType === 9 ? root.documentElement : root;
|
|
2046
|
+
if (!rootElement) return;
|
|
2047
|
+
const walk = (parent) => {
|
|
2048
|
+
for (const child of getChildElements(parent)) {
|
|
2049
|
+
const isTemplate = child.tagName.toLowerCase() === "template";
|
|
2050
|
+
if (isTemplate && !isCompositionTemplate(child)) continue;
|
|
2051
|
+
visit(child);
|
|
2052
|
+
walk(child);
|
|
2053
|
+
}
|
|
2054
|
+
};
|
|
2055
|
+
walk(rootElement);
|
|
2056
|
+
}
|
|
2037
2057
|
function walkElements(root, visit) {
|
|
2038
|
-
|
|
2039
|
-
const isTemplate = child.tagName.toLowerCase() === "template";
|
|
2040
|
-
if (isTemplate && !isCompositionTemplate(child)) continue;
|
|
2041
|
-
visit(child);
|
|
2042
|
-
walkElements(child, visit);
|
|
2043
|
-
}
|
|
2058
|
+
walkCompositionDescendants(root, visit);
|
|
2044
2059
|
}
|
|
2045
2060
|
function ensureHfIds(html) {
|
|
2046
2061
|
const hasDocumentShell = /<!doctype|<html[\s>]/i.test(html);
|
|
@@ -2377,7 +2392,7 @@ function parseHtml(html) {
|
|
|
2377
2392
|
elements.push(mediaElement);
|
|
2378
2393
|
}
|
|
2379
2394
|
});
|
|
2380
|
-
const scriptTags = doc
|
|
2395
|
+
const scriptTags = findScriptElementsDeep(doc);
|
|
2381
2396
|
let gsapScript = null;
|
|
2382
2397
|
for (const script of scriptTags) {
|
|
2383
2398
|
const src = script.getAttribute("src");
|
|
@@ -2622,7 +2637,7 @@ function stripGsapForId(script, elementId) {
|
|
|
2622
2637
|
}
|
|
2623
2638
|
}
|
|
2624
2639
|
function cascadeRemoveGsapById(doc, elementId) {
|
|
2625
|
-
for (const script of
|
|
2640
|
+
for (const script of findScriptElementsDeep(doc)) {
|
|
2626
2641
|
const text = script.textContent ?? "";
|
|
2627
2642
|
if (!text.includes("gsap") && !text.includes("ScrollTrigger")) continue;
|
|
2628
2643
|
const updated = stripGsapForId(text, elementId);
|
|
@@ -2696,12 +2711,11 @@ function validateCompositionHtml(html) {
|
|
|
2696
2711
|
if (/javascript\s*:/i.test(html)) {
|
|
2697
2712
|
errors.push("javascript: URLs not allowed");
|
|
2698
2713
|
}
|
|
2699
|
-
const scripts = doc
|
|
2714
|
+
const scripts = findScriptElementsDeep(doc);
|
|
2700
2715
|
if (scripts.length > 2) {
|
|
2701
2716
|
warnings.push("Multiple script tags detected - only GSAP CDN and main script expected");
|
|
2702
2717
|
}
|
|
2703
|
-
const gsapScript
|
|
2704
|
-
if (gsapScript) {
|
|
2718
|
+
for (const gsapScript of extractGsapScripts(doc)) {
|
|
2705
2719
|
const gsapValidation = validateCompositionGsap(gsapScript);
|
|
2706
2720
|
errors.push(...gsapValidation.errors);
|
|
2707
2721
|
warnings.push(...gsapValidation.warnings);
|
|
@@ -2712,15 +2726,23 @@ function validateCompositionHtml(html) {
|
|
|
2712
2726
|
warnings
|
|
2713
2727
|
};
|
|
2714
2728
|
}
|
|
2715
|
-
function
|
|
2716
|
-
const scripts =
|
|
2729
|
+
function findScriptElementsDeep(doc) {
|
|
2730
|
+
const scripts = [];
|
|
2731
|
+
walkCompositionDescendants(doc, (el) => {
|
|
2732
|
+
if (el.tagName.toLowerCase() === "script") scripts.push(el);
|
|
2733
|
+
});
|
|
2734
|
+
return scripts;
|
|
2735
|
+
}
|
|
2736
|
+
function extractGsapScripts(doc) {
|
|
2737
|
+
const scripts = findScriptElementsDeep(doc);
|
|
2738
|
+
const gsapScripts = [];
|
|
2717
2739
|
for (const script of scripts) {
|
|
2718
2740
|
const content = script.textContent || "";
|
|
2719
2741
|
if (content.includes("gsap.timeline") || content.includes(".set(") || content.includes(".to(")) {
|
|
2720
|
-
|
|
2742
|
+
gsapScripts.push(content);
|
|
2721
2743
|
}
|
|
2722
2744
|
}
|
|
2723
|
-
return
|
|
2745
|
+
return gsapScripts;
|
|
2724
2746
|
}
|
|
2725
2747
|
|
|
2726
2748
|
// src/subCompositionValidity.ts
|
|
@@ -3199,6 +3221,7 @@ export {
|
|
|
3199
3221
|
unrollComputedTimeline,
|
|
3200
3222
|
updateElementInHtml,
|
|
3201
3223
|
validateCompositionGsap,
|
|
3202
|
-
validateCompositionHtml
|
|
3224
|
+
validateCompositionHtml,
|
|
3225
|
+
walkCompositionDescendants
|
|
3203
3226
|
};
|
|
3204
3227
|
//# sourceMappingURL=index.js.map
|