@hyperframes/parsers 0.7.22 → 0.7.23

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/index.d.ts CHANGED
@@ -6,8 +6,21 @@ export { PROPERTY_GROUPS, PropertyGroupName, SUPPORTED_EASES, SUPPORTED_PROPS, c
6
6
  export { SPRING_PRESETS, SpringPreset, generateSpringEaseData } from './springEase.js';
7
7
  export { parseGsapScriptAcorn as parseGsapScript } from './gsapParserAcorn.js';
8
8
  export { EXCLUDED_TAGS, ensureHfIds, mintHfId } from './hfIds.js';
9
+ export { ParsableDocumentLike, SubCompositionValidity, SubCompositionValidityReason, checkSubCompositionUsability } from './subCompositionValidity.js';
9
10
  export { CANONICAL_FONT_DISPLAY_NAMES, FONT_ALIAS_KEYS, FONT_ALIAS_MAP, decodeUrlPathVariants, resolveAliasDisplayName } from './composition.js';
10
11
 
12
+ /**
13
+ * Thrown by htmlParser functions when the input HTML is empty or does not
14
+ * parse to a document with a `documentElement` — the condition that,
15
+ * unguarded, previously surfaced as a raw
16
+ * `Cannot read properties of null (reading '...')` /
17
+ * `Cannot destructure property 'firstElementChild' of 'documentElement' as
18
+ * it is null` crash deep inside the DOM implementation instead of a clear,
19
+ * catchable error naming which function received bad input.
20
+ */
21
+ declare class CompositionHtmlParseError extends Error {
22
+ constructor(message: string);
23
+ }
11
24
  interface ParsedHtml {
12
25
  elements: TimelineElement[];
13
26
  gsapScript: string | null;
@@ -42,4 +55,4 @@ declare function unrollComputedTimeline(script: string): string;
42
55
 
43
56
  declare function queryByAttr(root: ParentNode, attr: string, value: string, tag?: string): Element | null;
44
57
 
45
- export { CanvasResolution, type CompositionMetadata, CompositionVariable, Keyframe, type ParsedHtml, StageZoomKeyframe, TimelineElement, ValidationResult, addElementToHtml, extractCompositionMetadata, parseHtml, queryByAttr, removeElementFromHtml, unrollComputedTimeline, updateElementInHtml, validateCompositionHtml };
58
+ export { CanvasResolution, CompositionHtmlParseError, type CompositionMetadata, CompositionVariable, Keyframe, type ParsedHtml, StageZoomKeyframe, TimelineElement, ValidationResult, addElementToHtml, extractCompositionMetadata, parseHtml, queryByAttr, removeElementFromHtml, unrollComputedTimeline, updateElementInHtml, validateCompositionHtml };
package/dist/index.js CHANGED
@@ -1787,6 +1787,12 @@ function removeAnimationFromScript(script, animationId) {
1787
1787
 
1788
1788
  // src/htmlParser.ts
1789
1789
  var MEDIA_TYPES = /* @__PURE__ */ new Set(["video", "image", "audio"]);
1790
+ var CompositionHtmlParseError = class extends Error {
1791
+ constructor(message) {
1792
+ super(message);
1793
+ this.name = "CompositionHtmlParseError";
1794
+ }
1795
+ };
1790
1796
  function getElementType(el) {
1791
1797
  const tag = el.tagName.toLowerCase();
1792
1798
  if (tag === "video") return "video";
@@ -1856,6 +1862,7 @@ function parseResolutionFromCss(doc, cssText) {
1856
1862
  }
1857
1863
  function parseResolutionFromHtml(doc) {
1858
1864
  const htmlEl = doc.documentElement;
1865
+ if (!htmlEl) return null;
1859
1866
  const resolutionAttr = htmlEl.getAttribute("data-resolution");
1860
1867
  if (resolutionAttr === "landscape" || resolutionAttr === "portrait" || resolutionAttr === "landscape-4k" || resolutionAttr === "portrait-4k" || resolutionAttr === "square" || resolutionAttr === "square-4k") {
1861
1868
  return resolutionAttr;
@@ -1891,6 +1898,9 @@ function parseHtml(html) {
1891
1898
  const keyframes = {};
1892
1899
  let idCounter = 0;
1893
1900
  const htmlEl = doc.documentElement;
1901
+ if (!htmlEl) {
1902
+ throw new CompositionHtmlParseError("parseHtml: input HTML is empty or could not be parsed");
1903
+ }
1894
1904
  const customStylesAttr = htmlEl.getAttribute("data-custom-styles");
1895
1905
  let customStyles = null;
1896
1906
  if (customStylesAttr) {
@@ -2201,12 +2211,25 @@ function updateElementInHtml(html, elementId, updates) {
2201
2211
  el.removeAttribute("data-has-audio");
2202
2212
  }
2203
2213
  }
2214
+ if (!doc.documentElement) {
2215
+ throw new CompositionHtmlParseError(
2216
+ "updateElementInHtml: input HTML is empty or could not be parsed"
2217
+ );
2218
+ }
2204
2219
  return "<!DOCTYPE html>\n" + doc.documentElement.outerHTML;
2205
2220
  }
2206
2221
  function addElementToHtml(html, element) {
2207
2222
  const parser = new DOMParser();
2208
2223
  const doc = parser.parseFromString(html, "text/html");
2224
+ if (!doc.documentElement) {
2225
+ throw new CompositionHtmlParseError(
2226
+ "addElementToHtml: input HTML is empty or could not be parsed"
2227
+ );
2228
+ }
2209
2229
  const container = doc.querySelector("#stage-zoom-container") || doc.querySelector(".container") || doc.querySelector("#stage") || doc.body;
2230
+ if (!container) {
2231
+ throw new CompositionHtmlParseError("addElementToHtml: input HTML has no <body>");
2232
+ }
2210
2233
  const id = element.id || `element-${Date.now()}`;
2211
2234
  let newEl;
2212
2235
  function applyMediaAttrs(el, mediaEl) {
@@ -2295,6 +2318,11 @@ function cascadeRemoveGsapById(doc, elementId) {
2295
2318
  function removeElementFromHtml(html, elementId) {
2296
2319
  const parser = new DOMParser();
2297
2320
  const doc = parser.parseFromString(html, "text/html");
2321
+ if (!doc.documentElement) {
2322
+ throw new CompositionHtmlParseError(
2323
+ "removeElementFromHtml: input HTML is empty or could not be parsed"
2324
+ );
2325
+ }
2298
2326
  doc.getElementById(elementId)?.remove();
2299
2327
  cascadeRemoveGsapById(doc, elementId);
2300
2328
  return "<!DOCTYPE html>\n" + doc.documentElement.outerHTML;
@@ -2303,6 +2331,11 @@ function extractCompositionMetadata(html) {
2303
2331
  const parser = new DOMParser();
2304
2332
  const doc = parser.parseFromString(html, "text/html");
2305
2333
  const htmlEl = doc.documentElement;
2334
+ if (!htmlEl) {
2335
+ throw new CompositionHtmlParseError(
2336
+ "extractCompositionMetadata: input HTML is empty or could not be parsed"
2337
+ );
2338
+ }
2306
2339
  const compositionId = htmlEl.getAttribute("data-composition-id");
2307
2340
  const durationStr = htmlEl.getAttribute("data-composition-duration");
2308
2341
  const compositionDuration = durationStr ? parseFloat(durationStr) : null;
@@ -2357,6 +2390,13 @@ function validateCompositionHtml(html) {
2357
2390
  const parser = new DOMParser();
2358
2391
  const doc = parser.parseFromString(html, "text/html");
2359
2392
  const htmlEl = doc.documentElement;
2393
+ if (!htmlEl) {
2394
+ return {
2395
+ valid: false,
2396
+ errors: ["Composition HTML is empty or could not be parsed"],
2397
+ warnings: []
2398
+ };
2399
+ }
2360
2400
  const compositionId = htmlEl.getAttribute("data-composition-id");
2361
2401
  if (!compositionId) {
2362
2402
  errors.push("Missing data-composition-id attribute on <html> element");
@@ -2407,6 +2447,50 @@ function extractGsapScript(doc) {
2407
2447
  return null;
2408
2448
  }
2409
2449
 
2450
+ // src/subCompositionValidity.ts
2451
+ function checkSubCompositionUsability(html, parseHtml2) {
2452
+ if (html == null || !html.trim()) {
2453
+ return {
2454
+ ok: false,
2455
+ reason: "empty",
2456
+ detail: "the file is empty (0 bytes or whitespace-only)"
2457
+ };
2458
+ }
2459
+ const compDoc = parseHtml2(html);
2460
+ if (!compDoc.documentElement) {
2461
+ return {
2462
+ ok: false,
2463
+ reason: "unparsable",
2464
+ detail: "the file's contents could not be parsed as HTML"
2465
+ };
2466
+ }
2467
+ const contentRoot = compDoc.querySelector("template");
2468
+ const contentHtml = contentRoot ? contentRoot.innerHTML || "" : compDoc.body?.innerHTML || "";
2469
+ if (!contentHtml.trim()) {
2470
+ return {
2471
+ ok: false,
2472
+ reason: "no-content",
2473
+ detail: "the file has no <template> or <body> content to render"
2474
+ };
2475
+ }
2476
+ const contentDoc = parseHtml2(contentHtml);
2477
+ if (!contentDoc.documentElement) {
2478
+ return {
2479
+ ok: false,
2480
+ reason: "unparsable",
2481
+ detail: "the file's <template>/<body> contents could not be parsed as HTML"
2482
+ };
2483
+ }
2484
+ if (!contentDoc.querySelector("[data-composition-id]")) {
2485
+ return {
2486
+ ok: false,
2487
+ reason: "no-composition-root",
2488
+ detail: "the file's <template>/<body> content has no element with a data-composition-id attribute"
2489
+ };
2490
+ }
2491
+ return { ok: true };
2492
+ }
2493
+
2410
2494
  // src/gsapUnroll.ts
2411
2495
  import * as acorn2 from "acorn";
2412
2496
  import MagicString2 from "magic-string";
@@ -2622,6 +2706,7 @@ export {
2622
2706
  CANONICAL_FONT_DISPLAY_NAMES,
2623
2707
  CANVAS_DIMENSIONS,
2624
2708
  COMPOSITION_VARIABLE_TYPES,
2709
+ CompositionHtmlParseError,
2625
2710
  DEFAULT_DURATIONS,
2626
2711
  EXCLUDED_TAGS,
2627
2712
  FONT_ALIAS_KEYS,
@@ -2633,6 +2718,7 @@ export {
2633
2718
  TIMELINE_COLORS,
2634
2719
  VALID_CANVAS_RESOLUTIONS,
2635
2720
  addElementToHtml,
2721
+ checkSubCompositionUsability,
2636
2722
  classifyPropertyGroup,
2637
2723
  classifyTweenPropertyGroup,
2638
2724
  decodeUrlPathVariants,