hyperframes 0.8.75 → 0.8.77

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.
@@ -52717,10 +52717,13 @@ function parseHTMLContent(html) {
52717
52717
  }
52718
52718
  return parseHTML(`<!DOCTYPE html><html><head></head><body>${html}</body></html>`).document;
52719
52719
  }
52720
+ function lowerAscii(text) {
52721
+ return text.replace(/[A-Z]+/g, (letters) => letters.toLowerCase());
52722
+ }
52720
52723
  function stripEmbeddedRuntimeScripts(html) {
52721
52724
  if (!html)
52722
52725
  return html;
52723
- const loweredHtml = html.toLowerCase();
52726
+ const loweredHtml = lowerAscii(html);
52724
52727
  let output = "";
52725
52728
  let cursor = 0;
52726
52729
  while (cursor < html.length) {
@@ -52757,22 +52760,41 @@ function findScriptStart(loweredHtml, from) {
52757
52760
  }
52758
52761
  function findTagEnd(html, from) {
52759
52762
  let quote;
52763
+ let state = "tagName";
52760
52764
  for (let index = from; index < html.length; index += 1) {
52761
- const char = html[index];
52765
+ const char = html.charAt(index);
52762
52766
  if (quote) {
52763
- if (char === quote)
52764
- quote = void 0;
52767
+ quote = char === quote ? void 0 : quote;
52765
52768
  continue;
52766
52769
  }
52767
- if (char === '"' || char === "'") {
52770
+ if (char === ">")
52771
+ return index;
52772
+ if (state === "equals" && (char === '"' || char === "'")) {
52768
52773
  quote = char;
52774
+ state = "between";
52769
52775
  continue;
52770
52776
  }
52771
- if (char === ">")
52772
- return index;
52777
+ state = nextTagState(state, char, index === from);
52773
52778
  }
52774
52779
  return -1;
52775
52780
  }
52781
+ function nextTagState(state, char, first) {
52782
+ if (isHtmlWhitespace(char))
52783
+ return stateAfterWhitespace(state);
52784
+ if (char === "/" && !first)
52785
+ return stateAfterSlash(state);
52786
+ if (char === "=" && state === "name")
52787
+ return "equals";
52788
+ if (state === "equals")
52789
+ return "value";
52790
+ return state === "between" ? "name" : state;
52791
+ }
52792
+ function stateAfterWhitespace(state) {
52793
+ return state === "tagName" || state === "value" ? "between" : state;
52794
+ }
52795
+ function stateAfterSlash(state) {
52796
+ return state === "equals" || state === "value" ? "value" : "between";
52797
+ }
52776
52798
  function findScriptCloseTagEnd(loweredHtml, from) {
52777
52799
  let index = loweredHtml.indexOf("</script", from);
52778
52800
  while (index !== -1) {
@@ -52811,7 +52833,7 @@ function getScriptSource(block) {
52811
52833
  const startTagEnd = findTagEnd(block, 1);
52812
52834
  if (startTagEnd === -1)
52813
52835
  return "";
52814
- const loweredBlock = block.toLowerCase();
52836
+ const loweredBlock = lowerAscii(block);
52815
52837
  const closeTagStart = loweredBlock.lastIndexOf("</script");
52816
52838
  const end = closeTagStart === -1 ? block.length : closeTagStart;
52817
52839
  return block.slice(startTagEnd + 1, end);
@@ -52826,8 +52848,8 @@ function escapeInlineScriptSource(source) {
52826
52848
  return escapeCaseInsensitiveToken(escapeCaseInsensitiveToken(source, "</script", "<\\/script"), "<!--", "<\\!--");
52827
52849
  }
52828
52850
  function escapeCaseInsensitiveToken(source, token, replacement) {
52829
- const loweredSource = source.toLowerCase();
52830
- const loweredToken = token.toLowerCase();
52851
+ const loweredSource = lowerAscii(source);
52852
+ const loweredToken = lowerAscii(token);
52831
52853
  let output = "";
52832
52854
  let cursor = 0;
52833
52855
  while (cursor < source.length) {
@@ -52844,16 +52866,78 @@ function escapeCaseInsensitiveToken(source, token, replacement) {
52844
52866
  function inlineScriptTags(scripts) {
52845
52867
  return scripts.map((source) => `<script>${escapeInlineScriptSource(source)}</script>`).join("\n");
52846
52868
  }
52847
- function injectTagsAtHeadStart(html, tags) {
52848
- if (html.includes("<head")) {
52849
- return html.replace(/<head\b[^>]*>/i, (match) => `${match}
52850
- ${tags}`);
52869
+ function findDocumentTag(html, tag) {
52870
+ const lowered = lowerAscii(html);
52871
+ const unclosedRawText = /* @__PURE__ */ new Set();
52872
+ let cursor = 0;
52873
+ while (cursor !== -1) {
52874
+ const open = lowered.indexOf("<", cursor);
52875
+ if (open === -1)
52876
+ return -1;
52877
+ if (isTagAt(lowered, open, tag))
52878
+ return open;
52879
+ cursor = skipMarkup(lowered, open, unclosedRawText);
52851
52880
  }
52852
- if (html.includes("<body")) {
52853
- return html.replace("<body", () => `${tags}
52854
- <body`);
52881
+ return -1;
52882
+ }
52883
+ function isTagAt(lowered, at2, token) {
52884
+ return lowered.startsWith(token, at2) && isTagBoundary(lowered.charAt(at2 + token.length));
52885
+ }
52886
+ function skipMarkup(lowered, open, unclosedRawText) {
52887
+ if (lowered.startsWith("<!--", open))
52888
+ return skipComment(lowered, open);
52889
+ const next = lowered.charAt(open + 1);
52890
+ if (next === "/" && !/[a-z]/.test(lowered.charAt(open + 2))) {
52891
+ const bogusEnd = lowered.indexOf(">", open);
52892
+ return bogusEnd === -1 ? -1 : bogusEnd + 1;
52893
+ }
52894
+ if (!/[a-z/]/.test(next))
52895
+ return open + 1;
52896
+ const tagEnd = findTagEnd(lowered, open + 1);
52897
+ return tagEnd === -1 ? -1 : skipRawText(lowered, open, tagEnd, unclosedRawText);
52898
+ }
52899
+ function skipComment(lowered, open) {
52900
+ if (lowered.startsWith("<!-->", open) || lowered.startsWith("<!--->", open)) {
52901
+ return lowered.indexOf(">", open) + 1;
52902
+ }
52903
+ COMMENT_END.lastIndex = open + 4;
52904
+ const end = COMMENT_END.exec(lowered);
52905
+ return end ? end.index + end[0].length : -1;
52906
+ }
52907
+ function skipRawText(lowered, open, tagEnd, unclosedRawText) {
52908
+ const rawText = RAW_TEXT_TAGS.find((name) => isTagAt(lowered, open, `<${name}`));
52909
+ if (!rawText)
52910
+ return tagEnd + 1;
52911
+ const close = unclosedRawText.has(rawText) ? -1 : findRawTextClose(lowered, rawText, tagEnd + 1);
52912
+ if (close !== -1)
52913
+ return close;
52914
+ unclosedRawText.add(rawText);
52915
+ return lowered.charAt(tagEnd - 1) === "/" ? tagEnd + 1 : -1;
52916
+ }
52917
+ function findRawTextClose(lowered, name, from) {
52918
+ const close = `</${name}`;
52919
+ for (let at2 = lowered.indexOf(close, from); at2 !== -1; at2 = lowered.indexOf(close, at2 + 1)) {
52920
+ if (isTagAt(lowered, at2, close))
52921
+ return at2;
52855
52922
  }
52856
- return `${tags}
52923
+ return -1;
52924
+ }
52925
+ function insertBeforeDocumentTag(html, tag, markup) {
52926
+ const at2 = findDocumentTag(html, tag);
52927
+ return at2 === -1 ? null : html.slice(0, at2) + markup + html.slice(at2);
52928
+ }
52929
+ function insertBeforeCloseTag(html, name, markup) {
52930
+ return insertBeforeDocumentTag(html, `</${name}`, markup);
52931
+ }
52932
+ function injectTagsAtHeadStart(html, tags) {
52933
+ const headOpen = findDocumentTag(html, "<head");
52934
+ const headOpenEnd = headOpen === -1 ? -1 : findTagEnd(html, headOpen + 1);
52935
+ if (headOpenEnd !== -1) {
52936
+ return `${html.slice(0, headOpenEnd + 1)}
52937
+ ${tags}${html.slice(headOpenEnd + 1)}`;
52938
+ }
52939
+ return insertBeforeDocumentTag(html, "<body", `${tags}
52940
+ `) ?? `${tags}
52857
52941
  ${html}`;
52858
52942
  }
52859
52943
  function injectScriptsAtHeadStart(html, scripts) {
@@ -52867,30 +52951,21 @@ function injectScriptsIntoHtml(html, headScripts, bodyScripts, stripEmbeddedRunt
52867
52951
  }
52868
52952
  if (headScripts.length > 0) {
52869
52953
  const headTags = inlineScriptTags(headScripts);
52870
- if (html.includes("</head>")) {
52871
- html = html.replace("</head>", () => `${headTags}
52872
- </head>`);
52873
- } else if (html.includes("<body")) {
52874
- html = html.replace("<body", () => `${headTags}
52875
- <body`);
52876
- } else {
52877
- html = `${headTags}
52954
+ const withHead = insertBeforeCloseTag(html, "head", `${headTags}
52955
+ `);
52956
+ html = withHead ?? insertBeforeDocumentTag(html, "<body", `${headTags}
52957
+ `) ?? `${headTags}
52878
52958
  ${html}`;
52879
- }
52880
52959
  }
52881
52960
  if (bodyScripts.length > 0) {
52882
52961
  const bodyTags = inlineScriptTags(bodyScripts);
52883
- if (html.includes("</body>")) {
52884
- html = html.replace("</body>", () => `${bodyTags}
52885
- </body>`);
52886
- } else {
52887
- html = `${html}
52962
+ html = insertBeforeCloseTag(html, "body", `${bodyTags}
52963
+ `) ?? `${html}
52888
52964
  ${bodyTags}`;
52889
- }
52890
52965
  }
52891
52966
  return html;
52892
52967
  }
52893
- var RUNTIME_BOOTSTRAP_ATTR, RUNTIME_SRC_MARKERS, RUNTIME_INLINE_MARKERS, SIMPLE_RUNTIME_FLAG_ASSIGNMENTS;
52968
+ var RUNTIME_BOOTSTRAP_ATTR, RUNTIME_SRC_MARKERS, RUNTIME_INLINE_MARKERS, SIMPLE_RUNTIME_FLAG_ASSIGNMENTS, RAW_TEXT_TAGS, COMMENT_END;
52894
52969
  var init_htmlDocument = __esm({
52895
52970
  "../core/dist/compiler/htmlDocument.js"() {
52896
52971
  "use strict";
@@ -52913,6 +52988,8 @@ var init_htmlDocument = __esm({
52913
52988
  /^window\.__playerReady\s*=\s*(?:true|false)\s*;?$/,
52914
52989
  /^window\.__renderReady\s*=\s*(?:true|false)\s*;?$/
52915
52990
  ];
52991
+ RAW_TEXT_TAGS = ["script", "style", "title", "textarea"];
52992
+ COMMENT_END = /--!?>/g;
52916
52993
  }
52917
52994
  });
52918
52995
 
@@ -66166,6 +66243,8 @@ function readExternalScriptAttributes(el) {
66166
66243
  attributes2.integrity = el.getAttribute("integrity") || "";
66167
66244
  if (el.hasAttribute("crossorigin"))
66168
66245
  attributes2.crossorigin = el.getAttribute("crossorigin") || "";
66246
+ if ((el.getAttribute("type") || "").trim().toLowerCase() === "module")
66247
+ attributes2.type = "module";
66169
66248
  return attributes2;
66170
66249
  }
66171
66250
  function ensureExternalScriptTag(doc, src, attributes2 = {}) {
@@ -66181,6 +66260,8 @@ function ensureExternalScriptTag(doc, src, attributes2 = {}) {
66181
66260
  el.setAttribute("integrity", integrity);
66182
66261
  if (attributes2.crossorigin !== void 0)
66183
66262
  el.setAttribute("crossorigin", attributes2.crossorigin);
66263
+ if (attributes2.type)
66264
+ el.setAttribute("type", attributes2.type);
66184
66265
  }
66185
66266
  if (!existing.length) {
66186
66267
  const el = elements[0];
@@ -66194,6 +66275,80 @@ var init_externalScripts = __esm({
66194
66275
  }
66195
66276
  });
66196
66277
 
66278
+ // ../core/dist/compiler/importMaps.js
66279
+ function parseImportMap(json, rebase) {
66280
+ let parsed;
66281
+ try {
66282
+ parsed = JSON.parse(json);
66283
+ } catch {
66284
+ return null;
66285
+ }
66286
+ if (!parsed || typeof parsed !== "object")
66287
+ return null;
66288
+ const { imports, scopes } = parsed;
66289
+ return {
66290
+ imports: rebaseEntries(imports, rebase),
66291
+ scopes: scopes && Object.fromEntries(Object.entries(scopes).map(([prefix, entries2]) => [
66292
+ rebase(prefix),
66293
+ rebaseEntries(entries2, rebase) ?? {}
66294
+ ]))
66295
+ };
66296
+ }
66297
+ function mergeEntries(into, from, where) {
66298
+ for (const [specifier, url] of Object.entries(from ?? {})) {
66299
+ if (!(specifier in into))
66300
+ into[specifier] = url;
66301
+ else if (into[specifier] !== url) {
66302
+ console.warn(`[HyperFrames] import map conflict for "${specifier}"${where}: keeping ${into[specifier]}, ignoring ${url}.`);
66303
+ }
66304
+ }
66305
+ }
66306
+ function mergeImportMapsIntoDocument(doc, maps) {
66307
+ if (maps.length === 0)
66308
+ return;
66309
+ let el = doc.querySelector('script[type="importmap"]');
66310
+ let page = {};
66311
+ try {
66312
+ page = el ? JSON.parse(el.textContent || "{}") : {};
66313
+ } catch {
66314
+ console.warn("[HyperFrames] the page's import map is not valid JSON; replacing it.");
66315
+ }
66316
+ const imports = { ...page.imports };
66317
+ const scopes = { ...page.scopes };
66318
+ for (const map of maps) {
66319
+ mergeEntries(imports, map.imports, "");
66320
+ for (const [prefix, entries2] of Object.entries(map.scopes ?? {})) {
66321
+ mergeEntries(scopes[prefix] ??= {}, entries2, ` in scope ${prefix}`);
66322
+ }
66323
+ }
66324
+ if (!el) {
66325
+ el = doc.createElement("script");
66326
+ el.setAttribute("type", "importmap");
66327
+ doc.head.prepend(el);
66328
+ }
66329
+ el.textContent = JSON.stringify({
66330
+ ...page,
66331
+ imports,
66332
+ ...Object.keys(scopes).length ? { scopes } : {}
66333
+ }).replace(/</g, "\\u003c");
66334
+ }
66335
+ function emitMountedModuleScripts(doc, importMaps, moduleScripts) {
66336
+ mergeImportMapsIntoDocument(doc, importMaps);
66337
+ for (const content of moduleScripts) {
66338
+ const el = doc.createElement("script");
66339
+ el.setAttribute("type", "module");
66340
+ el.textContent = content;
66341
+ doc.body.appendChild(el);
66342
+ }
66343
+ }
66344
+ var rebaseEntries;
66345
+ var init_importMaps = __esm({
66346
+ "../core/dist/compiler/importMaps.js"() {
66347
+ "use strict";
66348
+ rebaseEntries = (entries2, rebase) => entries2 && Object.fromEntries(Object.entries(entries2).map(([specifier, url]) => [specifier, rebase(url)]));
66349
+ }
66350
+ });
66351
+
66197
66352
  // ../core/dist/runtime/flattenedRoot.js
66198
66353
  function markFlattenedInnerRoot(prepared) {
66199
66354
  const authoredRootId = prepared.getAttribute("id")?.trim();
@@ -66642,6 +66797,12 @@ function scopeCssToComposition(css, compositionId, scopeSelectorOverride, author
66642
66797
  function jsonScriptLiteral(value) {
66643
66798
  return JSON.stringify(value).replace(/</g, "\\u003c");
66644
66799
  }
66800
+ function scopedModulePrelude(timelineCompositionId, compositionSrc) {
66801
+ return `const __hyperframes = (function(__hfBaseHyperframes, __hfTimelineCompId, __hfCompositionSrc) {
66802
+ return ${SCOPED_HYPERFRAMES_EXPRESSION};
66803
+ })(window.__hyperframes, ${jsonScriptLiteral(timelineCompositionId)}, ${jsonScriptLiteral(compositionSrc?.trim() || null)});
66804
+ `;
66805
+ }
66645
66806
  function wrapScopedCompositionScript(source, compositionId, errorLabel = "[HyperFrames] composition script error:", scopeSelectorOverride, timelineCompositionId = compositionId, authoredRootId, compositionSrc) {
66646
66807
  const compositionIdLiteral = jsonScriptLiteral(compositionId);
66647
66808
  const timelineCompositionIdLiteral = jsonScriptLiteral(timelineCompositionId);
@@ -66963,19 +67124,7 @@ function wrapScopedCompositionScript(source, compositionId, errorLabel = "[Hyper
66963
67124
  },
66964
67125
  });
66965
67126
  var __hfBaseHyperframes = window.__hyperframes;
66966
- var __hfScopedHyperframes = !__hfBaseHyperframes
66967
- ? __hfBaseHyperframes
66968
- : Object.assign({}, __hfBaseHyperframes, {
66969
- assetUrl: function(path) {
66970
- var page = window.document.baseURI;
66971
- return new URL(path, __hfCompositionSrc ? new URL(__hfCompositionSrc, page) : page).href;
66972
- },
66973
- getVariables: function() {
66974
- var byComp = window.__hfVariablesByComp;
66975
- var scoped = byComp && __hfTimelineCompId ? byComp[__hfTimelineCompId] : null;
66976
- return scoped ? Object.assign({}, scoped) : {};
66977
- },
66978
- });
67127
+ var __hfScopedHyperframes = ${SCOPED_HYPERFRAMES_EXPRESSION};
66979
67128
  var __hfRun = function() {
66980
67129
  try {
66981
67130
  (function(document, gsap, window, __hyperframes) {
@@ -66998,13 +67147,26 @@ function buildVariablesByCompScript(variablesByComp) {
66998
67147
  const json = jsonScriptLiteral(variablesByComp);
66999
67148
  return `window.__hfVariablesByComp = Object.assign({}, window.__hfVariablesByComp || {}, ${json});`;
67000
67149
  }
67001
- var AUTHORED_ROOT_ID_ATTR, INNER_ROOT_ATTR, GLOBAL_AT_RULES;
67150
+ var AUTHORED_ROOT_ID_ATTR, INNER_ROOT_ATTR, GLOBAL_AT_RULES, SCOPED_HYPERFRAMES_EXPRESSION;
67002
67151
  var init_compositionScoping = __esm({
67003
67152
  "../core/dist/compiler/compositionScoping.js"() {
67004
67153
  "use strict";
67005
67154
  AUTHORED_ROOT_ID_ATTR = "data-hf-authored-id";
67006
67155
  INNER_ROOT_ATTR = "data-hf-inner-root";
67007
67156
  GLOBAL_AT_RULES = /* @__PURE__ */ new Set(["keyframes", "-webkit-keyframes", "font-face"]);
67157
+ SCOPED_HYPERFRAMES_EXPRESSION = `!__hfBaseHyperframes
67158
+ ? __hfBaseHyperframes
67159
+ : Object.assign({}, __hfBaseHyperframes, {
67160
+ assetUrl: function(path) {
67161
+ var page = window.document.baseURI;
67162
+ return new URL(path, __hfCompositionSrc ? new URL(__hfCompositionSrc, page) : page).href;
67163
+ },
67164
+ getVariables: function() {
67165
+ var byComp = window.__hfVariablesByComp;
67166
+ var scoped = byComp && __hfTimelineCompId ? byComp[__hfTimelineCompId] : null;
67167
+ return scoped ? Object.assign({}, scoped) : {};
67168
+ },
67169
+ })`;
67008
67170
  }
67009
67171
  });
67010
67172
 
@@ -72433,6 +72595,13 @@ function parseGsapScriptAcornForWrite(script) {
72433
72595
  }
72434
72596
  }
72435
72597
  function parseGsapScriptAcorn(script) {
72598
+ const parsed = parseMemo.get(script) ?? parseGsapScriptAcornUncached(script);
72599
+ parseMemo.delete(script);
72600
+ parseMemo.set(script, parsed);
72601
+ if (parseMemo.size > PARSE_MEMO_ENTRIES) parseMemo.delete(parseMemo.keys().next().value);
72602
+ return structuredClone(parsed);
72603
+ }
72604
+ function parseGsapScriptAcornUncached(script) {
72436
72605
  try {
72437
72606
  const ast = parseProgram3(script);
72438
72607
  const scope = collectScopeBindings(ast);
@@ -72580,7 +72749,7 @@ function extractGsapLabels(script) {
72580
72749
  return [];
72581
72750
  }
72582
72751
  }
72583
- var PROPERTY_GROUPS2, PROP_TO_GROUP2, SKIP_KEYS, FUNCTION_TYPES, GSAP_METHODS, MAX_DEPTH, MAX_ITERS, SAFE_DEFAULT_NODES, SUPPORTED_PARAMS_CACHE, roundPercentage, GSAP_METHODS2, QUERY_METHODS, ITERATION_METHODS, SCOPE_NODE_TYPES, CONST_NODES, MATH_FNS, MATH_CONSTS, BUILTIN_VAR_KEYS, DROPPED_VAR_KEYS, EXTRAS_KEYS, PERCENTAGE_KEY_RE, GSAP_DEFAULT_DURATION;
72752
+ var PROPERTY_GROUPS2, PROP_TO_GROUP2, SKIP_KEYS, FUNCTION_TYPES, GSAP_METHODS, MAX_DEPTH, MAX_ITERS, SAFE_DEFAULT_NODES, SUPPORTED_PARAMS_CACHE, roundPercentage, GSAP_METHODS2, QUERY_METHODS, ITERATION_METHODS, SCOPE_NODE_TYPES, CONST_NODES, MATH_FNS, MATH_CONSTS, BUILTIN_VAR_KEYS, DROPPED_VAR_KEYS, EXTRAS_KEYS, PERCENTAGE_KEY_RE, GSAP_DEFAULT_DURATION, PARSE_MEMO_ENTRIES, parseMemo;
72584
72753
  var init_gsapParserAcorn = __esm({
72585
72754
  "../parsers/dist/gsapParserAcorn.js"() {
72586
72755
  "use strict";
@@ -72660,6 +72829,8 @@ var init_gsapParserAcorn = __esm({
72660
72829
  ]);
72661
72830
  PERCENTAGE_KEY_RE = /^(\d+(?:\.\d+)?)%$/;
72662
72831
  GSAP_DEFAULT_DURATION = 0.5;
72832
+ PARSE_MEMO_ENTRIES = 4;
72833
+ parseMemo = /* @__PURE__ */ new Map();
72663
72834
  }
72664
72835
  });
72665
72836
 
@@ -74211,6 +74382,81 @@ function collectDeclaredVariableIds(htmlTagRaw) {
74211
74382
  }
74212
74383
  return declared;
74213
74384
  }
74385
+ function variablesDeclarationFindings(tag, tags) {
74386
+ const raw = readJsonAttr(tag.raw, "data-composition-variables");
74387
+ if (!raw) return [];
74388
+ let parsed;
74389
+ try {
74390
+ parsed = JSON.parse(raw);
74391
+ } catch (err) {
74392
+ const reason = err instanceof Error ? err.message : "unknown";
74393
+ return [
74394
+ {
74395
+ code: "invalid_composition_variables_declaration",
74396
+ severity: "error",
74397
+ message: `data-composition-variables is not valid JSON (${reason}).`,
74398
+ fixHint: `Provide a JSON array of variable declarations: data-composition-variables='[{"id":"title","type":"string","label":"Title","default":"Hello"}]'.`,
74399
+ snippet: truncateSnippet(tag.raw)
74400
+ }
74401
+ ];
74402
+ }
74403
+ if (!Array.isArray(parsed)) {
74404
+ return [
74405
+ {
74406
+ code: "invalid_composition_variables_declaration",
74407
+ severity: "error",
74408
+ message: "data-composition-variables must be a JSON array of variable declarations.",
74409
+ fixHint: `Wrap declarations in [] and give each an id, type, label, and default: '[{"id":"title","type":"string","label":"Title","default":"Hello"}]'.`,
74410
+ snippet: truncateSnippet(tag.raw)
74411
+ }
74412
+ ];
74413
+ }
74414
+ const findings = [];
74415
+ const knownTypes = new Set(COMPOSITION_VARIABLE_TYPES2);
74416
+ const varSrcIds = /* @__PURE__ */ new Set();
74417
+ for (const other of tags) {
74418
+ const bound = readAttr(other.raw, "data-var-src");
74419
+ if (bound) varSrcIds.add(bound);
74420
+ }
74421
+ for (let i = 0; i < parsed.length; i += 1) {
74422
+ const entry = parsed[i];
74423
+ if (!entry || typeof entry !== "object" || Array.isArray(entry)) {
74424
+ findings.push({
74425
+ code: "invalid_composition_variables_declaration",
74426
+ severity: "error",
74427
+ message: `data-composition-variables entry [${i}] must be an object with id, type, label, and default.`,
74428
+ snippet: truncateSnippet(tag.raw)
74429
+ });
74430
+ continue;
74431
+ }
74432
+ const e = entry;
74433
+ const missing = [];
74434
+ if (typeof e.id !== "string") missing.push("id");
74435
+ if (typeof e.type !== "string" || !knownTypes.has(e.type)) missing.push("type");
74436
+ if (typeof e.label !== "string") missing.push("label");
74437
+ if (!("default" in e)) missing.push("default");
74438
+ if (missing.length > 0) {
74439
+ findings.push({
74440
+ code: "invalid_composition_variables_declaration",
74441
+ severity: "error",
74442
+ message: `data-composition-variables entry [${i}] is missing or has invalid: ${missing.join(", ")}. Type must be one of string, number, color, boolean, enum, font, image.`,
74443
+ snippet: truncateSnippet(tag.raw)
74444
+ });
74445
+ continue;
74446
+ }
74447
+ const id = String(e.id);
74448
+ if ((e.type === "image" || varSrcIds.has(id)) && typeof e.default === "string" && e.default.length > 0 && !isSafeMediaUrl(e.default)) {
74449
+ findings.push({
74450
+ code: "unloadable_media_variable_default",
74451
+ severity: "error",
74452
+ message: `Variable "${id}" defaults to a URL the runtime will refuse to load, so any element bound to it renders its authored fallback src instead and the render still exits 0.`,
74453
+ fixHint: `Media URLs must be relative, http(s), blob:, or a data:image/* URI. Copy the file into the project and reference it relatively (e.g. "assets/bg.png") rather than by absolute path.`,
74454
+ snippet: truncateSnippet(tag.raw)
74455
+ });
74456
+ }
74457
+ }
74458
+ return findings;
74459
+ }
74214
74460
  function collectAllDeclaredVariableIds(tags) {
74215
74461
  const all = /* @__PURE__ */ new Set();
74216
74462
  for (const tag of tags) {
@@ -76669,84 +76915,8 @@ var init_dist3 = __esm({
76669
76915
  // on any structural problem. Surface JSON / shape failures so authors
76670
76916
  // catch them at lint time rather than wondering why their `getVariables()`
76671
76917
  // defaults aren't applied.
76672
- // fallow-ignore-next-line complexity
76673
- ({ tags }) => {
76674
- const htmlTag = findHtmlTag(tags);
76675
- if (!htmlTag) return [];
76676
- const raw = readJsonAttr(htmlTag.raw, "data-composition-variables");
76677
- if (!raw) return [];
76678
- let parsed;
76679
- try {
76680
- parsed = JSON.parse(raw);
76681
- } catch (err) {
76682
- const reason = err instanceof Error ? err.message : "unknown";
76683
- return [
76684
- {
76685
- code: "invalid_composition_variables_declaration",
76686
- severity: "error",
76687
- message: `data-composition-variables is not valid JSON (${reason}).`,
76688
- fixHint: `Provide a JSON array of variable declarations: data-composition-variables='[{"id":"title","type":"string","label":"Title","default":"Hello"}]'.`,
76689
- snippet: truncateSnippet(htmlTag.raw)
76690
- }
76691
- ];
76692
- }
76693
- if (!Array.isArray(parsed)) {
76694
- return [
76695
- {
76696
- code: "invalid_composition_variables_declaration",
76697
- severity: "error",
76698
- message: "data-composition-variables must be a JSON array of variable declarations.",
76699
- fixHint: `Wrap declarations in [] and give each an id, type, label, and default: '[{"id":"title","type":"string","label":"Title","default":"Hello"}]'.`,
76700
- snippet: truncateSnippet(htmlTag.raw)
76701
- }
76702
- ];
76703
- }
76704
- const findings = [];
76705
- const knownTypes = new Set(COMPOSITION_VARIABLE_TYPES2);
76706
- const varSrcIds = /* @__PURE__ */ new Set();
76707
- for (const tag of tags) {
76708
- const bound = readAttr(tag.raw, "data-var-src");
76709
- if (bound) varSrcIds.add(bound);
76710
- }
76711
- for (let i = 0; i < parsed.length; i += 1) {
76712
- const entry = parsed[i];
76713
- if (!entry || typeof entry !== "object" || Array.isArray(entry)) {
76714
- findings.push({
76715
- code: "invalid_composition_variables_declaration",
76716
- severity: "error",
76717
- message: `data-composition-variables entry [${i}] must be an object with id, type, label, and default.`,
76718
- snippet: truncateSnippet(htmlTag.raw)
76719
- });
76720
- continue;
76721
- }
76722
- const e = entry;
76723
- const missing = [];
76724
- if (typeof e.id !== "string") missing.push("id");
76725
- if (typeof e.type !== "string" || !knownTypes.has(e.type)) missing.push("type");
76726
- if (typeof e.label !== "string") missing.push("label");
76727
- if (!("default" in e)) missing.push("default");
76728
- if (missing.length > 0) {
76729
- findings.push({
76730
- code: "invalid_composition_variables_declaration",
76731
- severity: "error",
76732
- message: `data-composition-variables entry [${i}] is missing or has invalid: ${missing.join(", ")}. Type must be one of string, number, color, boolean, enum, font, image.`,
76733
- snippet: truncateSnippet(htmlTag.raw)
76734
- });
76735
- continue;
76736
- }
76737
- const id = String(e.id);
76738
- if ((e.type === "image" || varSrcIds.has(id)) && typeof e.default === "string" && e.default.length > 0 && !isSafeMediaUrl(e.default)) {
76739
- findings.push({
76740
- code: "unloadable_media_variable_default",
76741
- severity: "error",
76742
- message: `Variable "${id}" defaults to a URL the runtime will refuse to load, so any element bound to it renders its authored fallback src instead and the render still exits 0.`,
76743
- fixHint: `Media URLs must be relative, http(s), blob:, or a data:image/* URI. Copy the file into the project and reference it relatively (e.g. "assets/bg.png") rather than by absolute path.`,
76744
- snippet: truncateSnippet(htmlTag.raw)
76745
- });
76746
- }
76747
- }
76748
- return findings;
76749
- },
76918
+ // Checked on every element that declares: <html>, or the composition root.
76919
+ ({ tags }) => tags.filter((tag) => readJsonAttr(tag.raw, "data-composition-variables")).flatMap((tag) => variablesDeclarationFindings(tag, tags)),
76750
76920
  // html_dir_attribute_breaks_render — valid non-LTR dir values on
76751
76921
  // <html> renders correctly in preview/snapshot but produces a fully
76752
76922
  // blank/black video from render, with no other lint/validate/inspect
@@ -78758,6 +78928,8 @@ function inlineSubCompositions(document2, hosts, options) {
78758
78928
  const scripts = [];
78759
78929
  const externalScriptSrcs = [];
78760
78930
  const scriptItems = [];
78931
+ const importMaps = [];
78932
+ const moduleScripts = [];
78761
78933
  const externalLinks = [];
78762
78934
  const seenLinkHrefs = /* @__PURE__ */ new Set();
78763
78935
  const variablesByComp = {};
@@ -78845,7 +79017,24 @@ function inlineSubCompositions(document2, hosts, options) {
78845
79017
  }
78846
79018
  for (const scriptEl of plan2.scriptSources) {
78847
79019
  const externalSrc = resolveSubAssetPath(scriptEl.getAttribute("src"));
78848
- if (externalSrc) {
79020
+ const type = (scriptEl.getAttribute("type") || "").trim().toLowerCase();
79021
+ if (type === "importmap") {
79022
+ const map = parseImportMap(scriptEl.textContent || "", (url) => {
79023
+ const authored = url.trim();
79024
+ const rebased = resolveSubAssetPath(authored);
79025
+ if (rebased === authored)
79026
+ return rebased;
79027
+ const urlLike = /^(\/|\.\.?\/|[a-z][a-z\d+.-]*:)/i.test(rebased) ? rebased : `./${rebased}`;
79028
+ return authored.endsWith("/") && !urlLike.endsWith("/") ? `${urlLike}/` : urlLike;
79029
+ });
79030
+ if (map)
79031
+ importMaps.push(map);
79032
+ else
79033
+ console.warn(`[HyperFrames] ${src}: import map is not valid JSON, so it is skipped.`);
79034
+ } else if (type === "module" && !externalSrc) {
79035
+ const prelude = scriptCompositionId ? scopedModulePrelude(runtimeCompId || scopeCompId || scriptCompositionId, src) : "";
79036
+ moduleScripts.push(prelude + (scriptEl.textContent || ""));
79037
+ } else if (externalSrc) {
78849
79038
  if (!externalScriptSrcs.includes(externalSrc)) {
78850
79039
  externalScriptSrcs.push(externalSrc);
78851
79040
  }
@@ -78916,12 +79105,22 @@ function inlineSubCompositions(document2, hosts, options) {
78916
79105
  queue.push({ element: nestedHost.host, ancestry: nestedAncestry });
78917
79106
  }
78918
79107
  }
78919
- return { styles, scripts, externalScriptSrcs, scriptItems, externalLinks, variablesByComp };
79108
+ return {
79109
+ styles,
79110
+ scripts,
79111
+ externalScriptSrcs,
79112
+ scriptItems,
79113
+ externalLinks,
79114
+ variablesByComp,
79115
+ importMaps,
79116
+ moduleScripts
79117
+ };
78920
79118
  }
78921
79119
  var init_inlineSubCompositions = __esm({
78922
79120
  "../core/dist/compiler/inlineSubCompositions.js"() {
78923
79121
  "use strict";
78924
79122
  init_externalScripts();
79123
+ init_importMaps();
78925
79124
  init_rewriteSubCompPaths();
78926
79125
  init_getVariables();
78927
79126
  init_compositionScoping();
@@ -79113,6 +79312,7 @@ var init_htmlBundler = __esm({
79113
79312
  "use strict";
79114
79313
  init_scriptRuns();
79115
79314
  init_externalScripts();
79315
+ init_importMaps();
79116
79316
  init_flattenedRoot();
79117
79317
  init_flattenedRoot();
79118
79318
  init_getVariables();
@@ -79160,6 +79360,7 @@ var init_compiler = __esm({
79160
79360
  init_assetPaths();
79161
79361
  init_mediaRenderIds();
79162
79362
  init_externalScripts();
79363
+ init_importMaps();
79163
79364
  }
79164
79365
  });
79165
79366
 
@@ -85263,6 +85464,7 @@ __export(src_exports, {
85263
85464
  scaleProtocolTimeoutForComposition: () => scaleProtocolTimeoutForComposition,
85264
85465
  selectVerifySampleIndicesForTask: () => selectVerifySampleIndicesForTask,
85265
85466
  shouldClampToScreenshotForConcreteGpu: () => shouldClampToScreenshotForConcreteGpu,
85467
+ shouldDisableBrowserPoolForParallelWorker: () => shouldDisableBrowserPoolForParallelWorker,
85266
85468
  showVideoElements: () => showVideoElements,
85267
85469
  spawnStreamingEncoder: () => spawnStreamingEncoder,
85268
85470
  syncVideoFrameVisibility: () => syncVideoFrameVisibility,
@@ -92371,13 +92573,19 @@ function defaultSessionFactory(input) {
92371
92573
  let probeSession = input.probeSession;
92372
92574
  return {
92373
92575
  create: async () => {
92374
- const session = probeSession ? probeSession : await openSegmentedSession({ ...input, probeSession: null });
92576
+ const session = probeSession ?? await openSegmentedSession(input);
92375
92577
  if (probeSession) {
92376
92578
  openSegmentedSessionReuse(input, probeSession);
92377
92579
  probeSession = null;
92378
92580
  }
92379
- if (!session.isInitialized) await initializeSession(session);
92380
- await completeDeferredDrawElementInit(session);
92581
+ try {
92582
+ if (!session.isInitialized) await initializeSession(session);
92583
+ await completeDeferredDrawElementInit(session);
92584
+ } catch (error) {
92585
+ await closeCaptureSession(session).catch(() => {
92586
+ });
92587
+ throw error;
92588
+ }
92381
92589
  return session;
92382
92590
  }
92383
92591
  };
@@ -92388,16 +92596,19 @@ function openSegmentedSessionReuse(input, session) {
92388
92596
  async function openSegmentedSession(input) {
92389
92597
  const captureCfg = input.cfg.forceScreenshot === input.plan.forceScreenshot ? input.cfg : { ...input.cfg, forceScreenshot: input.plan.forceScreenshot };
92390
92598
  const videoInjector = input.createRenderVideoFrameInjector();
92391
- if (input.probeSession) {
92392
- prepareCaptureSessionForReuse(input.probeSession, input.framesDir, videoInjector);
92393
- return input.probeSession;
92394
- }
92599
+ const captureOptions = input.buildCaptureOptions();
92600
+ const ownBrowser = shouldDisableBrowserPoolForParallelWorker({
92601
+ parallel: (input.workerCount ?? 1) > 1,
92602
+ platform: process.platform,
92603
+ deviceScaleFactor: captureOptions.deviceScaleFactor,
92604
+ headlessShellPath: resolveHeadlessShellPath(captureCfg)
92605
+ });
92395
92606
  return createCaptureSession(
92396
92607
  input.fileServer.url,
92397
92608
  input.framesDir,
92398
- input.buildCaptureOptions(),
92609
+ captureOptions,
92399
92610
  videoInjector,
92400
- captureCfg
92611
+ ownBrowser ? { ...captureCfg, enableBrowserPool: false } : captureCfg
92401
92612
  );
92402
92613
  }
92403
92614
  async function concatSegments(concat, segmentPaths, videoOnlyPath, abortSignal, cfg) {
@@ -92410,7 +92621,7 @@ async function concatSegments(concat, segmentPaths, videoOnlyPath, abortSignal,
92410
92621
  }
92411
92622
  async function createSegmentWorker(run, id) {
92412
92623
  const { input, deps } = run;
92413
- const factory = input.sessionFactoryForWorker?.(id) ?? input.sessionFactory ?? defaultSessionFactory(input);
92624
+ const factory = input.sessionFactoryForWorker?.(id) ?? run.sessionFactory;
92414
92625
  return {
92415
92626
  id,
92416
92627
  factory,
@@ -92532,16 +92743,16 @@ async function runCaptureSegmentedStage(input) {
92532
92743
  segmentDir,
92533
92744
  skipSize: skip.size,
92534
92745
  workerCount: Math.max(1, input.workerCount ?? 1),
92535
- recycleEvery: input.browserRecycleEverySegments ?? 0
92746
+ recycleEvery: input.browserRecycleEverySegments ?? 0,
92747
+ sessionFactory: input.sessionFactory ?? defaultSessionFactory(input)
92536
92748
  };
92537
92749
  const queue = createSegmentQueue(pending);
92538
92750
  const segmentPaths = segments.map((s) => segmentOutputPath(segmentDir, s.index));
92539
92751
  if (pending.length === 0) return concatResumedRun(run, segmentPaths);
92540
92752
  let fellBackToStreaming = false;
92541
92753
  const workers = [];
92542
- for (let id = 0; id < run.workerCount; id++) workers.push(await createSegmentWorker(run, id));
92543
92754
  const consoleOf = () => workers[0]?.ctx.session.browserConsoleBuffer ?? [];
92544
- let lastBrowserConsole = consoleOf();
92755
+ let lastBrowserConsole = [];
92545
92756
  const runWorkerLoop = async (worker) => {
92546
92757
  for (; ; ) {
92547
92758
  if (fellBackToStreaming) return;
@@ -92556,6 +92767,7 @@ async function runCaptureSegmentedStage(input) {
92556
92767
  };
92557
92768
  try {
92558
92769
  assertNotAborted();
92770
+ for (let id = 0; id < run.workerCount; id++) workers.push(await createSegmentWorker(run, id));
92559
92771
  const outcomes = await Promise.allSettled(workers.map((w) => runWorkerLoop(w)));
92560
92772
  for (const worker of workers) dedupPerfs.push(getCapturePerfSummary(worker.ctx.session));
92561
92773
  const failure = outcomes.find((o) => o.status === "rejected");
@@ -95600,9 +95812,7 @@ function injectSdkPositionEditsRenderScript(html) {
95600
95812
  }
95601
95813
  const scriptBody = getPositionEditsRenderScript().replace(/<\/script/gi, "<\\/script");
95602
95814
  const script = `<script>${scriptBody}</script>`;
95603
- const bodyClose = html.search(/<\/body\s*>/i);
95604
- if (bodyClose < 0) return `${html}${script}`;
95605
- return `${html.slice(0, bodyClose)}${script}${html.slice(bodyClose)}`;
95815
+ return insertBeforeCloseTag(html, "body", script) ?? `${html}${script}`;
95606
95816
  }
95607
95817
  var EmptyCompositionError = class extends Error {
95608
95818
  code = "EMPTY_COMPOSITION";
@@ -96179,6 +96389,13 @@ function inlineSubCompositions2(html, subCompositions, projectDir, variableOverr
96179
96389
  }
96180
96390
  flushInline();
96181
96391
  }
96392
+ if (body) {
96393
+ emitMountedModuleScripts(
96394
+ document2,
96395
+ result.importMaps,
96396
+ result.moduleScripts
96397
+ );
96398
+ }
96182
96399
  emitRootCompositionVariableStyles(
96183
96400
  document2,
96184
96401
  result.variablesByComp,
@@ -96727,10 +96944,11 @@ async function compileForRender(projectDir, htmlPath, downloadDir, options = {})
96727
96944
  'data-hf-studio-motion="'
96728
96945
  ];
96729
96946
  const hasPositionEdits = HF_POSITION_ATTRS.some((attr) => assembledHtml.includes(attr));
96730
- const htmlWithPositionScript = hasPositionEdits ? assembledHtml.replace(
96731
- /<\/body>/i,
96732
- `<script>${createStudioPositionSeekReapplyScript()}</script></body>`
96733
- ) : assembledHtml;
96947
+ const htmlWithPositionScript = hasPositionEdits ? insertBeforeCloseTag(
96948
+ assembledHtml,
96949
+ "body",
96950
+ `<script>${createStudioPositionSeekReapplyScript()}</script>`
96951
+ ) ?? assembledHtml : assembledHtml;
96734
96952
  const htmlWithSdkPositionScript = injectSdkPositionEditsRenderScript(htmlWithPositionScript);
96735
96953
  const { html: htmlWithLocalMedia, remoteMediaAssets } = await localizeRemoteMediaSources(
96736
96954
  htmlWithSdkPositionScript,
@@ -104295,8 +104513,8 @@ async function runFontLocalize(io, localize) {
104295
104513
  }
104296
104514
 
104297
104515
  // src/version.ts
104298
- var VERSION = true ? "0.8.75" : "0.0.0-dev";
104299
- var PRODUCER_VERSION = true ? "0.8.75" : "0.0.0-dev";
104516
+ var VERSION = true ? "0.8.77" : "0.0.0-dev";
104517
+ var PRODUCER_VERSION = true ? "0.8.77" : "0.0.0-dev";
104300
104518
 
104301
104519
  // src/fontLocalizeCli.ts
104302
104520
  async function readStdin() {