@hyperframes/parsers 0.8.142 → 0.8.144

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/slideshow.js CHANGED
@@ -25,12 +25,24 @@ function isOptionalNumberArray(v) {
25
25
  function isOptionalBoolean(v) {
26
26
  return v === void 0 || typeof v === "boolean";
27
27
  }
28
+ function isRecord(v) {
29
+ return typeof v === "object" && v !== null && !Array.isArray(v);
30
+ }
31
+ function isHotspotRegion(v) {
32
+ return isRecord(v) && [v["x"], v["y"], v["w"], v["h"]].every(
33
+ (coordinate) => typeof coordinate === "number" && Number.isFinite(coordinate)
34
+ );
35
+ }
36
+ function isSlideHotspot(v) {
37
+ return isRecord(v) && typeof v["id"] === "string" && typeof v["label"] === "string" && typeof v["target"] === "string" && (v["region"] === void 0 || isHotspotRegion(v["region"]));
38
+ }
28
39
  function isSlideRef(v) {
29
- if (typeof v !== "object" || v === null) return false;
40
+ if (!isRecord(v)) return false;
30
41
  const r = v;
31
42
  if (typeof r["sceneId"] !== "string") return false;
32
43
  if (!isOptionalNumberArray(r["fragments"])) return false;
33
- if (r["hotspots"] !== void 0 && !Array.isArray(r["hotspots"])) return false;
44
+ if (r["hotspots"] !== void 0 && (!Array.isArray(r["hotspots"]) || !r["hotspots"].every(isSlideHotspot)))
45
+ return false;
34
46
  if (!isOptionalBoolean(r["autoplay"])) return false;
35
47
  return true;
36
48
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/slideshow/slideshow.types.ts","../src/slideshow/parseSlideshow.ts","../src/slideshow/sceneId.ts"],"sourcesContent":["// packages/core/src/slideshow/slideshow.types.ts\n\n/** Current manifest schema version. Stamped on persist so future schema\n * changes can detect and migrate older islands. */\nexport const SLIDESHOW_MANIFEST_VERSION = 1;\n\n/** Raw author-facing shapes parsed from the JSON island. */\nexport interface SlideshowManifest {\n /** Schema version (absent on pre-versioning islands → treat as 1). */\n version?: number;\n slides: SlideRef[];\n slideSequences?: SlideSequence[];\n}\n\nexport interface SlideRef {\n sceneId: string;\n startTime?: number;\n endTime?: number;\n notes?: string;\n fragments?: number[];\n hotspots?: SlideHotspot[];\n /**\n * When true, the slide's first `<video>` plays automatically on enter (the\n * presenter lands on the slide and the clip plays). The slideshow still holds\n * — it never auto-advances — so the presenter clicks Next when ready.\n * Defaults to false. Use it when the video is the slide's primary content and\n * its natural end is the cue to advance, not for background/ambient clips.\n */\n autoplay?: boolean;\n // Reserved — TTS deferred. Parsed and carried, never consumed.\n ttsScript?: string;\n ttsAudioUrl?: string;\n ttsDurationMs?: number;\n}\n\nexport interface SlideHotspot {\n id: string;\n label: string;\n target: string; // references a SlideSequence.id\n region?: { x: number; y: number; w: number; h: number }; // % of slide\n}\n\nexport interface SlideSequence {\n id: string;\n label: string;\n slides: SlideRef[];\n}\n\n/** A slide with its time range resolved from the matching scene. */\nexport interface ResolvedSlide extends SlideRef {\n start: number;\n end: number;\n fragments: number[]; // always present, sorted, defaulted to []\n hotspots: SlideHotspot[]; // always present, defaulted to []\n}\n\nexport interface ResolvedSlideSequence {\n id: string;\n label: string;\n slides: ResolvedSlide[];\n}\n\nexport interface ResolvedSlideshow {\n slides: ResolvedSlide[];\n sequences: Record<string, ResolvedSlideSequence>; // keyed by sequence id\n}\n","// packages/core/src/slideshow/parseSlideshow.ts\nimport type {\n SlideshowManifest,\n SlideRef,\n ResolvedSlide,\n ResolvedSlideshow,\n ResolvedSlideSequence,\n} from \"./slideshow.types\";\n\nexport const SLIDESHOW_ISLAND_TYPE = \"application/hyperframes-slideshow+json\";\n\n/**\n * Builds the island <script> matcher. Capture group 1 = inner JSON.\n *\n * Factory (fresh RegExp per call) on purpose: a RegExp with the `g` flag carries\n * a mutable `lastIndex`, so callers that need `g` must call this each time rather\n * than caching a shared instance.\n */\nexport function slideshowIslandRegex(flags = \"i\"): RegExp {\n // Escape ALL regex metacharacters in the constant (CodeQL flags the incomplete\n // `\\` escape). The constant has none today, but a complete escape is correct.\n const escaped = SLIDESHOW_ISLAND_TYPE.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n return new RegExp(`<script[^>]*type=[\"']${escaped}[\"'][^>]*>([\\\\s\\\\S]*?)<\\\\/script>`, flags);\n}\n\ninterface SceneRange {\n id: string;\n start: number;\n duration: number;\n}\n\n/** Extract the JSON island from composition HTML. Returns null if absent. */\nexport function parseSlideshowManifest(html: string): SlideshowManifest | null {\n // Match <script type=\"application/hyperframes-slideshow+json\"> ... </script>\n const re = slideshowIslandRegex(\"i\");\n const match = re.exec(html);\n if (!match || match[1] === undefined) return null;\n const raw = match[1].trim();\n if (raw.length === 0) return null;\n const parsed: unknown = JSON.parse(raw);\n if (!isManifest(parsed)) {\n throw new Error(\"slideshow island is not a valid SlideshowManifest\");\n }\n return parsed;\n}\n\nfunction isOptionalNumberArray(v: unknown): boolean {\n return v === undefined || (Array.isArray(v) && v.every((n) => typeof n === \"number\"));\n}\n\nfunction isOptionalBoolean(v: unknown): v is boolean | undefined {\n return v === undefined || typeof v === \"boolean\";\n}\n\nfunction isSlideRef(v: unknown): v is SlideRef {\n if (typeof v !== \"object\" || v === null) return false;\n const r = v as Record<string, unknown>;\n if (typeof r[\"sceneId\"] !== \"string\") return false;\n if (!isOptionalNumberArray(r[\"fragments\"])) return false;\n if (r[\"hotspots\"] !== undefined && !Array.isArray(r[\"hotspots\"])) return false;\n if (!isOptionalBoolean(r[\"autoplay\"])) return false;\n return true;\n}\n\nfunction isSlideSequence(v: unknown): boolean {\n if (typeof v !== \"object\" || v === null) return false;\n const s = v as Record<string, unknown>;\n return (\n typeof s[\"id\"] === \"string\" &&\n typeof s[\"label\"] === \"string\" &&\n Array.isArray(s[\"slides\"]) &&\n s[\"slides\"].every(isSlideRef)\n );\n}\n\nfunction isManifest(v: unknown): v is SlideshowManifest {\n if (typeof v !== \"object\" || v === null || Array.isArray(v)) return false;\n const o = v as Record<string, unknown>;\n if (!Array.isArray(o[\"slides\"]) || !o[\"slides\"].every(isSlideRef)) return false;\n if (o[\"slideSequences\"] !== undefined) {\n if (!Array.isArray(o[\"slideSequences\"]) || !o[\"slideSequences\"].every(isSlideSequence))\n return false;\n }\n return true;\n}\n\nfunction missingBoundError(sceneId: string, missing: \"startTime\" | \"endTime\"): string {\n const present = missing === \"startTime\" ? \"endTime\" : \"startTime\";\n return `slide \"${sceneId}\" sets ${present} but ${missing} cannot be resolved (no scene \"${sceneId}\")`;\n}\n\n// fallow-ignore-next-line complexity\nfunction resolveTimeRange(\n ref: SlideRef,\n scene: SceneRange | undefined,\n errors: string[],\n): { start: number; end: number } {\n const { startTime, endTime, sceneId } = ref;\n\n // Both explicit — use them directly, no scene needed.\n if (startTime !== undefined && endTime !== undefined) {\n return { start: startTime, end: endTime };\n }\n\n // Neither explicit — resolve both from scene.\n if (startTime === undefined && endTime === undefined) {\n if (!scene) {\n errors.push(`slide references unresolved sceneId \"${sceneId}\"`);\n return { start: 0, end: 0 };\n }\n return { start: scene.start, end: scene.start + scene.duration };\n }\n\n // Exactly one bound explicit — fill from scene, or report a clear error.\n if (!scene) {\n const missing = startTime === undefined ? \"startTime\" : \"endTime\";\n errors.push(missingBoundError(sceneId, missing));\n const bound = startTime ?? endTime ?? 0;\n return { start: bound, end: bound };\n }\n\n return {\n start: startTime ?? scene.start,\n end: endTime ?? scene.start + scene.duration,\n };\n}\n\nfunction validateFragments(\n sceneId: string,\n fragments: number[],\n start: number,\n end: number,\n errors: string[],\n): void {\n for (const f of fragments) {\n if (f < start || f > end) {\n errors.push(`slide \"${sceneId}\" fragment ${f} is outside range [${start}, ${end}]`);\n }\n }\n}\n\nfunction resolveSlide(\n ref: SlideRef,\n sceneById: Map<string, SceneRange>,\n errors: string[],\n): ResolvedSlide {\n const scene = sceneById.get(ref.sceneId);\n const { start, end } = resolveTimeRange(ref, scene, errors);\n if (ref.startTime !== undefined && ref.endTime !== undefined && end <= start) {\n errors.push(`slide \"${ref.sceneId}\" has endTime (${end}) <= startTime (${start})`);\n }\n const fragments = [...new Set(ref.fragments ?? [])].sort((a, b) => a - b);\n validateFragments(ref.sceneId, fragments, start, end, errors);\n return { ...ref, start, end, fragments, hotspots: ref.hotspots ?? [] };\n}\n\nexport function resolveSlideshow(\n manifest: SlideshowManifest,\n scenes: SceneRange[],\n): { resolved: ResolvedSlideshow; errors: string[] } {\n const errors: string[] = [];\n const sceneById = new Map(scenes.map((s) => [s.id, s]));\n\n const sequences: Record<string, ResolvedSlideSequence> = {};\n for (const seq of manifest.slideSequences ?? []) {\n // Flag duplicate sequence ids rather than silently overwriting the earlier one.\n if (Object.prototype.hasOwnProperty.call(sequences, seq.id)) {\n errors.push(`duplicate slideSequence id \"${seq.id}\" — only the last definition is kept`);\n }\n sequences[seq.id] = {\n id: seq.id,\n label: seq.label,\n slides: seq.slides.map((s) => resolveSlide(s, sceneById, errors)),\n };\n }\n\n const slides = manifest.slides.map((s) => resolveSlide(s, sceneById, errors));\n\n // Validate hotspot targets.\n const allSlides = [...slides, ...Object.values(sequences).flatMap((s) => s.slides)];\n for (const slide of allSlides) {\n for (const h of slide.hotspots) {\n const seq = sequences[h.target];\n if (!seq) {\n errors.push(`hotspot \"${h.id}\" targets unknown sequence \"${h.target}\"`);\n } else if (seq.slides.length === 0) {\n errors.push(`hotspot \"${h.id}\" targets empty sequence \"${h.target}\"`);\n }\n }\n }\n\n // Validate no main-line overlap (sorted by start; adjacent compare).\n const ordered = [...slides].sort((a, b) => a.start - b.start);\n for (let i = 1; i < ordered.length; i++) {\n const prev = ordered[i - 1];\n const curr = ordered[i];\n if (prev !== undefined && curr !== undefined && curr.start < prev.end) {\n errors.push(`main-line slides \"${prev.sceneId}\" and \"${curr.sceneId}\" overlap`);\n }\n }\n\n return { resolved: { slides, sequences }, errors };\n}\n","// packages/core/src/slideshow/sceneId.ts\n\n/**\n * Whether a composition id names a \"scene-like\" composition — i.e. a real slide\n * scene, not the root timeline (`main`) or a non-scene overlay (captions, ambient\n * layers). Shared by the runtime scene-window computation and the slideshow lint\n * rule so the two can never drift.\n */\nexport function isSceneLikeCompositionId(compositionId: string): boolean {\n const normalized = compositionId.trim().toLowerCase();\n if (!normalized || normalized === \"main\") return false;\n if (normalized.includes(\"caption\")) return false;\n if (normalized.includes(\"ambient\")) return false;\n return true;\n}\n"],"mappings":";AAIO,IAAM,6BAA6B;;;ACKnC,IAAM,wBAAwB;AAS9B,SAAS,qBAAqB,QAAQ,KAAa;AAGxD,QAAM,UAAU,sBAAsB,QAAQ,uBAAuB,MAAM;AAC3E,SAAO,IAAI,OAAO,wBAAwB,OAAO,qCAAqC,KAAK;AAC7F;AASO,SAAS,uBAAuB,MAAwC;AAE7E,QAAM,KAAK,qBAAqB,GAAG;AACnC,QAAM,QAAQ,GAAG,KAAK,IAAI;AAC1B,MAAI,CAAC,SAAS,MAAM,CAAC,MAAM,OAAW,QAAO;AAC7C,QAAM,MAAM,MAAM,CAAC,EAAE,KAAK;AAC1B,MAAI,IAAI,WAAW,EAAG,QAAO;AAC7B,QAAM,SAAkB,KAAK,MAAM,GAAG;AACtC,MAAI,CAAC,WAAW,MAAM,GAAG;AACvB,UAAM,IAAI,MAAM,mDAAmD;AAAA,EACrE;AACA,SAAO;AACT;AAEA,SAAS,sBAAsB,GAAqB;AAClD,SAAO,MAAM,UAAc,MAAM,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,OAAO,MAAM,QAAQ;AACrF;AAEA,SAAS,kBAAkB,GAAsC;AAC/D,SAAO,MAAM,UAAa,OAAO,MAAM;AACzC;AAEA,SAAS,WAAW,GAA2B;AAC7C,MAAI,OAAO,MAAM,YAAY,MAAM,KAAM,QAAO;AAChD,QAAM,IAAI;AACV,MAAI,OAAO,EAAE,SAAS,MAAM,SAAU,QAAO;AAC7C,MAAI,CAAC,sBAAsB,EAAE,WAAW,CAAC,EAAG,QAAO;AACnD,MAAI,EAAE,UAAU,MAAM,UAAa,CAAC,MAAM,QAAQ,EAAE,UAAU,CAAC,EAAG,QAAO;AACzE,MAAI,CAAC,kBAAkB,EAAE,UAAU,CAAC,EAAG,QAAO;AAC9C,SAAO;AACT;AAEA,SAAS,gBAAgB,GAAqB;AAC5C,MAAI,OAAO,MAAM,YAAY,MAAM,KAAM,QAAO;AAChD,QAAM,IAAI;AACV,SACE,OAAO,EAAE,IAAI,MAAM,YACnB,OAAO,EAAE,OAAO,MAAM,YACtB,MAAM,QAAQ,EAAE,QAAQ,CAAC,KACzB,EAAE,QAAQ,EAAE,MAAM,UAAU;AAEhC;AAEA,SAAS,WAAW,GAAoC;AACtD,MAAI,OAAO,MAAM,YAAY,MAAM,QAAQ,MAAM,QAAQ,CAAC,EAAG,QAAO;AACpE,QAAM,IAAI;AACV,MAAI,CAAC,MAAM,QAAQ,EAAE,QAAQ,CAAC,KAAK,CAAC,EAAE,QAAQ,EAAE,MAAM,UAAU,EAAG,QAAO;AAC1E,MAAI,EAAE,gBAAgB,MAAM,QAAW;AACrC,QAAI,CAAC,MAAM,QAAQ,EAAE,gBAAgB,CAAC,KAAK,CAAC,EAAE,gBAAgB,EAAE,MAAM,eAAe;AACnF,aAAO;AAAA,EACX;AACA,SAAO;AACT;AAEA,SAAS,kBAAkB,SAAiB,SAA0C;AACpF,QAAM,UAAU,YAAY,cAAc,YAAY;AACtD,SAAO,UAAU,OAAO,UAAU,OAAO,QAAQ,OAAO,kCAAkC,OAAO;AACnG;AAGA,SAAS,iBACP,KACA,OACA,QACgC;AAChC,QAAM,EAAE,WAAW,SAAS,QAAQ,IAAI;AAGxC,MAAI,cAAc,UAAa,YAAY,QAAW;AACpD,WAAO,EAAE,OAAO,WAAW,KAAK,QAAQ;AAAA,EAC1C;AAGA,MAAI,cAAc,UAAa,YAAY,QAAW;AACpD,QAAI,CAAC,OAAO;AACV,aAAO,KAAK,wCAAwC,OAAO,GAAG;AAC9D,aAAO,EAAE,OAAO,GAAG,KAAK,EAAE;AAAA,IAC5B;AACA,WAAO,EAAE,OAAO,MAAM,OAAO,KAAK,MAAM,QAAQ,MAAM,SAAS;AAAA,EACjE;AAGA,MAAI,CAAC,OAAO;AACV,UAAM,UAAU,cAAc,SAAY,cAAc;AACxD,WAAO,KAAK,kBAAkB,SAAS,OAAO,CAAC;AAC/C,UAAM,QAAQ,aAAa,WAAW;AACtC,WAAO,EAAE,OAAO,OAAO,KAAK,MAAM;AAAA,EACpC;AAEA,SAAO;AAAA,IACL,OAAO,aAAa,MAAM;AAAA,IAC1B,KAAK,WAAW,MAAM,QAAQ,MAAM;AAAA,EACtC;AACF;AAEA,SAAS,kBACP,SACA,WACA,OACA,KACA,QACM;AACN,aAAW,KAAK,WAAW;AACzB,QAAI,IAAI,SAAS,IAAI,KAAK;AACxB,aAAO,KAAK,UAAU,OAAO,cAAc,CAAC,sBAAsB,KAAK,KAAK,GAAG,GAAG;AAAA,IACpF;AAAA,EACF;AACF;AAEA,SAAS,aACP,KACA,WACA,QACe;AACf,QAAM,QAAQ,UAAU,IAAI,IAAI,OAAO;AACvC,QAAM,EAAE,OAAO,IAAI,IAAI,iBAAiB,KAAK,OAAO,MAAM;AAC1D,MAAI,IAAI,cAAc,UAAa,IAAI,YAAY,UAAa,OAAO,OAAO;AAC5E,WAAO,KAAK,UAAU,IAAI,OAAO,kBAAkB,GAAG,mBAAmB,KAAK,GAAG;AAAA,EACnF;AACA,QAAM,YAAY,CAAC,GAAG,IAAI,IAAI,IAAI,aAAa,CAAC,CAAC,CAAC,EAAE,KAAK,CAAC,GAAG,MAAM,IAAI,CAAC;AACxE,oBAAkB,IAAI,SAAS,WAAW,OAAO,KAAK,MAAM;AAC5D,SAAO,EAAE,GAAG,KAAK,OAAO,KAAK,WAAW,UAAU,IAAI,YAAY,CAAC,EAAE;AACvE;AAEO,SAAS,iBACd,UACA,QACmD;AACnD,QAAM,SAAmB,CAAC;AAC1B,QAAM,YAAY,IAAI,IAAI,OAAO,IAAI,CAAC,MAAM,CAAC,EAAE,IAAI,CAAC,CAAC,CAAC;AAEtD,QAAM,YAAmD,CAAC;AAC1D,aAAW,OAAO,SAAS,kBAAkB,CAAC,GAAG;AAE/C,QAAI,OAAO,UAAU,eAAe,KAAK,WAAW,IAAI,EAAE,GAAG;AAC3D,aAAO,KAAK,+BAA+B,IAAI,EAAE,2CAAsC;AAAA,IACzF;AACA,cAAU,IAAI,EAAE,IAAI;AAAA,MAClB,IAAI,IAAI;AAAA,MACR,OAAO,IAAI;AAAA,MACX,QAAQ,IAAI,OAAO,IAAI,CAAC,MAAM,aAAa,GAAG,WAAW,MAAM,CAAC;AAAA,IAClE;AAAA,EACF;AAEA,QAAM,SAAS,SAAS,OAAO,IAAI,CAAC,MAAM,aAAa,GAAG,WAAW,MAAM,CAAC;AAG5E,QAAM,YAAY,CAAC,GAAG,QAAQ,GAAG,OAAO,OAAO,SAAS,EAAE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;AAClF,aAAW,SAAS,WAAW;AAC7B,eAAW,KAAK,MAAM,UAAU;AAC9B,YAAM,MAAM,UAAU,EAAE,MAAM;AAC9B,UAAI,CAAC,KAAK;AACR,eAAO,KAAK,YAAY,EAAE,EAAE,+BAA+B,EAAE,MAAM,GAAG;AAAA,MACxE,WAAW,IAAI,OAAO,WAAW,GAAG;AAClC,eAAO,KAAK,YAAY,EAAE,EAAE,6BAA6B,EAAE,MAAM,GAAG;AAAA,MACtE;AAAA,IACF;AAAA,EACF;AAGA,QAAM,UAAU,CAAC,GAAG,MAAM,EAAE,KAAK,CAAC,GAAG,MAAM,EAAE,QAAQ,EAAE,KAAK;AAC5D,WAAS,IAAI,GAAG,IAAI,QAAQ,QAAQ,KAAK;AACvC,UAAM,OAAO,QAAQ,IAAI,CAAC;AAC1B,UAAM,OAAO,QAAQ,CAAC;AACtB,QAAI,SAAS,UAAa,SAAS,UAAa,KAAK,QAAQ,KAAK,KAAK;AACrE,aAAO,KAAK,qBAAqB,KAAK,OAAO,UAAU,KAAK,OAAO,WAAW;AAAA,IAChF;AAAA,EACF;AAEA,SAAO,EAAE,UAAU,EAAE,QAAQ,UAAU,GAAG,OAAO;AACnD;;;AClMO,SAAS,yBAAyB,eAAgC;AACvE,QAAM,aAAa,cAAc,KAAK,EAAE,YAAY;AACpD,MAAI,CAAC,cAAc,eAAe,OAAQ,QAAO;AACjD,MAAI,WAAW,SAAS,SAAS,EAAG,QAAO;AAC3C,MAAI,WAAW,SAAS,SAAS,EAAG,QAAO;AAC3C,SAAO;AACT;","names":[]}
1
+ {"version":3,"sources":["../src/slideshow/slideshow.types.ts","../src/slideshow/parseSlideshow.ts","../src/slideshow/sceneId.ts"],"sourcesContent":["// packages/core/src/slideshow/slideshow.types.ts\n\n/** Current manifest schema version. Stamped on persist so future schema\n * changes can detect and migrate older islands. */\nexport const SLIDESHOW_MANIFEST_VERSION = 1;\n\n/** Raw author-facing shapes parsed from the JSON island. */\nexport interface SlideshowManifest {\n /** Schema version (absent on pre-versioning islands → treat as 1). */\n version?: number;\n slides: SlideRef[];\n slideSequences?: SlideSequence[];\n}\n\nexport interface SlideRef {\n sceneId: string;\n startTime?: number;\n endTime?: number;\n notes?: string;\n fragments?: number[];\n hotspots?: SlideHotspot[];\n /**\n * When true, the slide's first `<video>` plays automatically on enter (the\n * presenter lands on the slide and the clip plays). The slideshow still holds\n * — it never auto-advances — so the presenter clicks Next when ready.\n * Defaults to false. Use it when the video is the slide's primary content and\n * its natural end is the cue to advance, not for background/ambient clips.\n */\n autoplay?: boolean;\n // Reserved — TTS deferred. Parsed and carried, never consumed.\n ttsScript?: string;\n ttsAudioUrl?: string;\n ttsDurationMs?: number;\n}\n\nexport interface SlideHotspot {\n id: string;\n label: string;\n target: string; // references a SlideSequence.id\n region?: { x: number; y: number; w: number; h: number }; // % of slide\n}\n\nexport interface SlideSequence {\n id: string;\n label: string;\n slides: SlideRef[];\n}\n\n/** A slide with its time range resolved from the matching scene. */\nexport interface ResolvedSlide extends SlideRef {\n start: number;\n end: number;\n fragments: number[]; // always present, sorted, defaulted to []\n hotspots: SlideHotspot[]; // always present, defaulted to []\n}\n\nexport interface ResolvedSlideSequence {\n id: string;\n label: string;\n slides: ResolvedSlide[];\n}\n\nexport interface ResolvedSlideshow {\n slides: ResolvedSlide[];\n sequences: Record<string, ResolvedSlideSequence>; // keyed by sequence id\n}\n","// packages/core/src/slideshow/parseSlideshow.ts\nimport type {\n SlideshowManifest,\n SlideRef,\n SlideHotspot,\n ResolvedSlide,\n ResolvedSlideshow,\n ResolvedSlideSequence,\n} from \"./slideshow.types\";\n\nexport const SLIDESHOW_ISLAND_TYPE = \"application/hyperframes-slideshow+json\";\n\n/**\n * Builds the island <script> matcher. Capture group 1 = inner JSON.\n *\n * Factory (fresh RegExp per call) on purpose: a RegExp with the `g` flag carries\n * a mutable `lastIndex`, so callers that need `g` must call this each time rather\n * than caching a shared instance.\n */\nexport function slideshowIslandRegex(flags = \"i\"): RegExp {\n // Escape ALL regex metacharacters in the constant (CodeQL flags the incomplete\n // `\\` escape). The constant has none today, but a complete escape is correct.\n const escaped = SLIDESHOW_ISLAND_TYPE.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n return new RegExp(`<script[^>]*type=[\"']${escaped}[\"'][^>]*>([\\\\s\\\\S]*?)<\\\\/script>`, flags);\n}\n\ninterface SceneRange {\n id: string;\n start: number;\n duration: number;\n}\n\n/** Extract the JSON island from composition HTML. Returns null if absent. */\nexport function parseSlideshowManifest(html: string): SlideshowManifest | null {\n // Match <script type=\"application/hyperframes-slideshow+json\"> ... </script>\n const re = slideshowIslandRegex(\"i\");\n const match = re.exec(html);\n if (!match || match[1] === undefined) return null;\n const raw = match[1].trim();\n if (raw.length === 0) return null;\n const parsed: unknown = JSON.parse(raw);\n if (!isManifest(parsed)) {\n throw new Error(\"slideshow island is not a valid SlideshowManifest\");\n }\n return parsed;\n}\n\nfunction isOptionalNumberArray(v: unknown): boolean {\n return v === undefined || (Array.isArray(v) && v.every((n) => typeof n === \"number\"));\n}\n\nfunction isOptionalBoolean(v: unknown): v is boolean | undefined {\n return v === undefined || typeof v === \"boolean\";\n}\n\nfunction isRecord(v: unknown): v is Record<string, unknown> {\n return typeof v === \"object\" && v !== null && !Array.isArray(v);\n}\n\nfunction isHotspotRegion(v: unknown): boolean {\n return (\n isRecord(v) &&\n [v[\"x\"], v[\"y\"], v[\"w\"], v[\"h\"]].every(\n (coordinate) => typeof coordinate === \"number\" && Number.isFinite(coordinate),\n )\n );\n}\n\nfunction isSlideHotspot(v: unknown): v is SlideHotspot {\n return (\n isRecord(v) &&\n typeof v[\"id\"] === \"string\" &&\n typeof v[\"label\"] === \"string\" &&\n typeof v[\"target\"] === \"string\" &&\n (v[\"region\"] === undefined || isHotspotRegion(v[\"region\"]))\n );\n}\n\nfunction isSlideRef(v: unknown): v is SlideRef {\n if (!isRecord(v)) return false;\n const r = v;\n if (typeof r[\"sceneId\"] !== \"string\") return false;\n if (!isOptionalNumberArray(r[\"fragments\"])) return false;\n if (\n r[\"hotspots\"] !== undefined &&\n (!Array.isArray(r[\"hotspots\"]) || !r[\"hotspots\"].every(isSlideHotspot))\n )\n return false;\n if (!isOptionalBoolean(r[\"autoplay\"])) return false;\n return true;\n}\n\nfunction isSlideSequence(v: unknown): boolean {\n if (typeof v !== \"object\" || v === null) return false;\n const s = v as Record<string, unknown>;\n return (\n typeof s[\"id\"] === \"string\" &&\n typeof s[\"label\"] === \"string\" &&\n Array.isArray(s[\"slides\"]) &&\n s[\"slides\"].every(isSlideRef)\n );\n}\n\nfunction isManifest(v: unknown): v is SlideshowManifest {\n if (typeof v !== \"object\" || v === null || Array.isArray(v)) return false;\n const o = v as Record<string, unknown>;\n if (!Array.isArray(o[\"slides\"]) || !o[\"slides\"].every(isSlideRef)) return false;\n if (o[\"slideSequences\"] !== undefined) {\n if (!Array.isArray(o[\"slideSequences\"]) || !o[\"slideSequences\"].every(isSlideSequence))\n return false;\n }\n return true;\n}\n\nfunction missingBoundError(sceneId: string, missing: \"startTime\" | \"endTime\"): string {\n const present = missing === \"startTime\" ? \"endTime\" : \"startTime\";\n return `slide \"${sceneId}\" sets ${present} but ${missing} cannot be resolved (no scene \"${sceneId}\")`;\n}\n\n// fallow-ignore-next-line complexity\nfunction resolveTimeRange(\n ref: SlideRef,\n scene: SceneRange | undefined,\n errors: string[],\n): { start: number; end: number } {\n const { startTime, endTime, sceneId } = ref;\n\n // Both explicit — use them directly, no scene needed.\n if (startTime !== undefined && endTime !== undefined) {\n return { start: startTime, end: endTime };\n }\n\n // Neither explicit — resolve both from scene.\n if (startTime === undefined && endTime === undefined) {\n if (!scene) {\n errors.push(`slide references unresolved sceneId \"${sceneId}\"`);\n return { start: 0, end: 0 };\n }\n return { start: scene.start, end: scene.start + scene.duration };\n }\n\n // Exactly one bound explicit — fill from scene, or report a clear error.\n if (!scene) {\n const missing = startTime === undefined ? \"startTime\" : \"endTime\";\n errors.push(missingBoundError(sceneId, missing));\n const bound = startTime ?? endTime ?? 0;\n return { start: bound, end: bound };\n }\n\n return {\n start: startTime ?? scene.start,\n end: endTime ?? scene.start + scene.duration,\n };\n}\n\nfunction validateFragments(\n sceneId: string,\n fragments: number[],\n start: number,\n end: number,\n errors: string[],\n): void {\n for (const f of fragments) {\n if (f < start || f > end) {\n errors.push(`slide \"${sceneId}\" fragment ${f} is outside range [${start}, ${end}]`);\n }\n }\n}\n\nfunction resolveSlide(\n ref: SlideRef,\n sceneById: Map<string, SceneRange>,\n errors: string[],\n): ResolvedSlide {\n const scene = sceneById.get(ref.sceneId);\n const { start, end } = resolveTimeRange(ref, scene, errors);\n if (ref.startTime !== undefined && ref.endTime !== undefined && end <= start) {\n errors.push(`slide \"${ref.sceneId}\" has endTime (${end}) <= startTime (${start})`);\n }\n const fragments = [...new Set(ref.fragments ?? [])].sort((a, b) => a - b);\n validateFragments(ref.sceneId, fragments, start, end, errors);\n return { ...ref, start, end, fragments, hotspots: ref.hotspots ?? [] };\n}\n\nexport function resolveSlideshow(\n manifest: SlideshowManifest,\n scenes: SceneRange[],\n): { resolved: ResolvedSlideshow; errors: string[] } {\n const errors: string[] = [];\n const sceneById = new Map(scenes.map((s) => [s.id, s]));\n\n const sequences: Record<string, ResolvedSlideSequence> = {};\n for (const seq of manifest.slideSequences ?? []) {\n // Flag duplicate sequence ids rather than silently overwriting the earlier one.\n if (Object.prototype.hasOwnProperty.call(sequences, seq.id)) {\n errors.push(`duplicate slideSequence id \"${seq.id}\" — only the last definition is kept`);\n }\n sequences[seq.id] = {\n id: seq.id,\n label: seq.label,\n slides: seq.slides.map((s) => resolveSlide(s, sceneById, errors)),\n };\n }\n\n const slides = manifest.slides.map((s) => resolveSlide(s, sceneById, errors));\n\n // Validate hotspot targets.\n const allSlides = [...slides, ...Object.values(sequences).flatMap((s) => s.slides)];\n for (const slide of allSlides) {\n for (const h of slide.hotspots) {\n const seq = sequences[h.target];\n if (!seq) {\n errors.push(`hotspot \"${h.id}\" targets unknown sequence \"${h.target}\"`);\n } else if (seq.slides.length === 0) {\n errors.push(`hotspot \"${h.id}\" targets empty sequence \"${h.target}\"`);\n }\n }\n }\n\n // Validate no main-line overlap (sorted by start; adjacent compare).\n const ordered = [...slides].sort((a, b) => a.start - b.start);\n for (let i = 1; i < ordered.length; i++) {\n const prev = ordered[i - 1];\n const curr = ordered[i];\n if (prev !== undefined && curr !== undefined && curr.start < prev.end) {\n errors.push(`main-line slides \"${prev.sceneId}\" and \"${curr.sceneId}\" overlap`);\n }\n }\n\n return { resolved: { slides, sequences }, errors };\n}\n","// packages/core/src/slideshow/sceneId.ts\n\n/**\n * Whether a composition id names a \"scene-like\" composition — i.e. a real slide\n * scene, not the root timeline (`main`) or a non-scene overlay (captions, ambient\n * layers). Shared by the runtime scene-window computation and the slideshow lint\n * rule so the two can never drift.\n */\nexport function isSceneLikeCompositionId(compositionId: string): boolean {\n const normalized = compositionId.trim().toLowerCase();\n if (!normalized || normalized === \"main\") return false;\n if (normalized.includes(\"caption\")) return false;\n if (normalized.includes(\"ambient\")) return false;\n return true;\n}\n"],"mappings":";AAIO,IAAM,6BAA6B;;;ACMnC,IAAM,wBAAwB;AAS9B,SAAS,qBAAqB,QAAQ,KAAa;AAGxD,QAAM,UAAU,sBAAsB,QAAQ,uBAAuB,MAAM;AAC3E,SAAO,IAAI,OAAO,wBAAwB,OAAO,qCAAqC,KAAK;AAC7F;AASO,SAAS,uBAAuB,MAAwC;AAE7E,QAAM,KAAK,qBAAqB,GAAG;AACnC,QAAM,QAAQ,GAAG,KAAK,IAAI;AAC1B,MAAI,CAAC,SAAS,MAAM,CAAC,MAAM,OAAW,QAAO;AAC7C,QAAM,MAAM,MAAM,CAAC,EAAE,KAAK;AAC1B,MAAI,IAAI,WAAW,EAAG,QAAO;AAC7B,QAAM,SAAkB,KAAK,MAAM,GAAG;AACtC,MAAI,CAAC,WAAW,MAAM,GAAG;AACvB,UAAM,IAAI,MAAM,mDAAmD;AAAA,EACrE;AACA,SAAO;AACT;AAEA,SAAS,sBAAsB,GAAqB;AAClD,SAAO,MAAM,UAAc,MAAM,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,OAAO,MAAM,QAAQ;AACrF;AAEA,SAAS,kBAAkB,GAAsC;AAC/D,SAAO,MAAM,UAAa,OAAO,MAAM;AACzC;AAEA,SAAS,SAAS,GAA0C;AAC1D,SAAO,OAAO,MAAM,YAAY,MAAM,QAAQ,CAAC,MAAM,QAAQ,CAAC;AAChE;AAEA,SAAS,gBAAgB,GAAqB;AAC5C,SACE,SAAS,CAAC,KACV,CAAC,EAAE,GAAG,GAAG,EAAE,GAAG,GAAG,EAAE,GAAG,GAAG,EAAE,GAAG,CAAC,EAAE;AAAA,IAC/B,CAAC,eAAe,OAAO,eAAe,YAAY,OAAO,SAAS,UAAU;AAAA,EAC9E;AAEJ;AAEA,SAAS,eAAe,GAA+B;AACrD,SACE,SAAS,CAAC,KACV,OAAO,EAAE,IAAI,MAAM,YACnB,OAAO,EAAE,OAAO,MAAM,YACtB,OAAO,EAAE,QAAQ,MAAM,aACtB,EAAE,QAAQ,MAAM,UAAa,gBAAgB,EAAE,QAAQ,CAAC;AAE7D;AAEA,SAAS,WAAW,GAA2B;AAC7C,MAAI,CAAC,SAAS,CAAC,EAAG,QAAO;AACzB,QAAM,IAAI;AACV,MAAI,OAAO,EAAE,SAAS,MAAM,SAAU,QAAO;AAC7C,MAAI,CAAC,sBAAsB,EAAE,WAAW,CAAC,EAAG,QAAO;AACnD,MACE,EAAE,UAAU,MAAM,WACjB,CAAC,MAAM,QAAQ,EAAE,UAAU,CAAC,KAAK,CAAC,EAAE,UAAU,EAAE,MAAM,cAAc;AAErE,WAAO;AACT,MAAI,CAAC,kBAAkB,EAAE,UAAU,CAAC,EAAG,QAAO;AAC9C,SAAO;AACT;AAEA,SAAS,gBAAgB,GAAqB;AAC5C,MAAI,OAAO,MAAM,YAAY,MAAM,KAAM,QAAO;AAChD,QAAM,IAAI;AACV,SACE,OAAO,EAAE,IAAI,MAAM,YACnB,OAAO,EAAE,OAAO,MAAM,YACtB,MAAM,QAAQ,EAAE,QAAQ,CAAC,KACzB,EAAE,QAAQ,EAAE,MAAM,UAAU;AAEhC;AAEA,SAAS,WAAW,GAAoC;AACtD,MAAI,OAAO,MAAM,YAAY,MAAM,QAAQ,MAAM,QAAQ,CAAC,EAAG,QAAO;AACpE,QAAM,IAAI;AACV,MAAI,CAAC,MAAM,QAAQ,EAAE,QAAQ,CAAC,KAAK,CAAC,EAAE,QAAQ,EAAE,MAAM,UAAU,EAAG,QAAO;AAC1E,MAAI,EAAE,gBAAgB,MAAM,QAAW;AACrC,QAAI,CAAC,MAAM,QAAQ,EAAE,gBAAgB,CAAC,KAAK,CAAC,EAAE,gBAAgB,EAAE,MAAM,eAAe;AACnF,aAAO;AAAA,EACX;AACA,SAAO;AACT;AAEA,SAAS,kBAAkB,SAAiB,SAA0C;AACpF,QAAM,UAAU,YAAY,cAAc,YAAY;AACtD,SAAO,UAAU,OAAO,UAAU,OAAO,QAAQ,OAAO,kCAAkC,OAAO;AACnG;AAGA,SAAS,iBACP,KACA,OACA,QACgC;AAChC,QAAM,EAAE,WAAW,SAAS,QAAQ,IAAI;AAGxC,MAAI,cAAc,UAAa,YAAY,QAAW;AACpD,WAAO,EAAE,OAAO,WAAW,KAAK,QAAQ;AAAA,EAC1C;AAGA,MAAI,cAAc,UAAa,YAAY,QAAW;AACpD,QAAI,CAAC,OAAO;AACV,aAAO,KAAK,wCAAwC,OAAO,GAAG;AAC9D,aAAO,EAAE,OAAO,GAAG,KAAK,EAAE;AAAA,IAC5B;AACA,WAAO,EAAE,OAAO,MAAM,OAAO,KAAK,MAAM,QAAQ,MAAM,SAAS;AAAA,EACjE;AAGA,MAAI,CAAC,OAAO;AACV,UAAM,UAAU,cAAc,SAAY,cAAc;AACxD,WAAO,KAAK,kBAAkB,SAAS,OAAO,CAAC;AAC/C,UAAM,QAAQ,aAAa,WAAW;AACtC,WAAO,EAAE,OAAO,OAAO,KAAK,MAAM;AAAA,EACpC;AAEA,SAAO;AAAA,IACL,OAAO,aAAa,MAAM;AAAA,IAC1B,KAAK,WAAW,MAAM,QAAQ,MAAM;AAAA,EACtC;AACF;AAEA,SAAS,kBACP,SACA,WACA,OACA,KACA,QACM;AACN,aAAW,KAAK,WAAW;AACzB,QAAI,IAAI,SAAS,IAAI,KAAK;AACxB,aAAO,KAAK,UAAU,OAAO,cAAc,CAAC,sBAAsB,KAAK,KAAK,GAAG,GAAG;AAAA,IACpF;AAAA,EACF;AACF;AAEA,SAAS,aACP,KACA,WACA,QACe;AACf,QAAM,QAAQ,UAAU,IAAI,IAAI,OAAO;AACvC,QAAM,EAAE,OAAO,IAAI,IAAI,iBAAiB,KAAK,OAAO,MAAM;AAC1D,MAAI,IAAI,cAAc,UAAa,IAAI,YAAY,UAAa,OAAO,OAAO;AAC5E,WAAO,KAAK,UAAU,IAAI,OAAO,kBAAkB,GAAG,mBAAmB,KAAK,GAAG;AAAA,EACnF;AACA,QAAM,YAAY,CAAC,GAAG,IAAI,IAAI,IAAI,aAAa,CAAC,CAAC,CAAC,EAAE,KAAK,CAAC,GAAG,MAAM,IAAI,CAAC;AACxE,oBAAkB,IAAI,SAAS,WAAW,OAAO,KAAK,MAAM;AAC5D,SAAO,EAAE,GAAG,KAAK,OAAO,KAAK,WAAW,UAAU,IAAI,YAAY,CAAC,EAAE;AACvE;AAEO,SAAS,iBACd,UACA,QACmD;AACnD,QAAM,SAAmB,CAAC;AAC1B,QAAM,YAAY,IAAI,IAAI,OAAO,IAAI,CAAC,MAAM,CAAC,EAAE,IAAI,CAAC,CAAC,CAAC;AAEtD,QAAM,YAAmD,CAAC;AAC1D,aAAW,OAAO,SAAS,kBAAkB,CAAC,GAAG;AAE/C,QAAI,OAAO,UAAU,eAAe,KAAK,WAAW,IAAI,EAAE,GAAG;AAC3D,aAAO,KAAK,+BAA+B,IAAI,EAAE,2CAAsC;AAAA,IACzF;AACA,cAAU,IAAI,EAAE,IAAI;AAAA,MAClB,IAAI,IAAI;AAAA,MACR,OAAO,IAAI;AAAA,MACX,QAAQ,IAAI,OAAO,IAAI,CAAC,MAAM,aAAa,GAAG,WAAW,MAAM,CAAC;AAAA,IAClE;AAAA,EACF;AAEA,QAAM,SAAS,SAAS,OAAO,IAAI,CAAC,MAAM,aAAa,GAAG,WAAW,MAAM,CAAC;AAG5E,QAAM,YAAY,CAAC,GAAG,QAAQ,GAAG,OAAO,OAAO,SAAS,EAAE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;AAClF,aAAW,SAAS,WAAW;AAC7B,eAAW,KAAK,MAAM,UAAU;AAC9B,YAAM,MAAM,UAAU,EAAE,MAAM;AAC9B,UAAI,CAAC,KAAK;AACR,eAAO,KAAK,YAAY,EAAE,EAAE,+BAA+B,EAAE,MAAM,GAAG;AAAA,MACxE,WAAW,IAAI,OAAO,WAAW,GAAG;AAClC,eAAO,KAAK,YAAY,EAAE,EAAE,6BAA6B,EAAE,MAAM,GAAG;AAAA,MACtE;AAAA,IACF;AAAA,EACF;AAGA,QAAM,UAAU,CAAC,GAAG,MAAM,EAAE,KAAK,CAAC,GAAG,MAAM,EAAE,QAAQ,EAAE,KAAK;AAC5D,WAAS,IAAI,GAAG,IAAI,QAAQ,QAAQ,KAAK;AACvC,UAAM,OAAO,QAAQ,IAAI,CAAC;AAC1B,UAAM,OAAO,QAAQ,CAAC;AACtB,QAAI,SAAS,UAAa,SAAS,UAAa,KAAK,QAAQ,KAAK,KAAK;AACrE,aAAO,KAAK,qBAAqB,KAAK,OAAO,UAAU,KAAK,OAAO,WAAW;AAAA,IAChF;AAAA,EACF;AAEA,SAAO,EAAE,UAAU,EAAE,QAAQ,UAAU,GAAG,OAAO;AACnD;;;AC9NO,SAAS,yBAAyB,eAAgC;AACvE,QAAM,aAAa,cAAc,KAAK,EAAE,YAAY;AACpD,MAAI,CAAC,cAAc,eAAe,OAAQ,QAAO;AACjD,MAAI,WAAW,SAAS,SAAS,EAAG,QAAO;AAC3C,MAAI,WAAW,SAAS,SAAS,EAAG,QAAO;AAC3C,SAAO;AACT;","names":[]}
@@ -0,0 +1,11 @@
1
+ declare function decodeUrlPathVariants(path: string): string[];
2
+ declare function decodeWellFormedEscapes(path: string): string;
3
+ declare function splitUrlSuffix(urlValue: string): {
4
+ basePath: string;
5
+ suffix: string;
6
+ };
7
+ declare function decodedUrlPath(url: string): string;
8
+ declare function encodeUrlPath(path: string): string;
9
+ declare function decodeCssEscapes(text: string): string;
10
+
11
+ export { decodeWellFormedEscapes as a, decodedUrlPath as b, decodeUrlPathVariants as c, decodeCssEscapes as d, encodeUrlPath as e, splitUrlSuffix as s };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hyperframes/parsers",
3
- "version": "0.8.142",
3
+ "version": "0.8.144",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "https://github.com/heygen-com/hyperframes",
@@ -92,6 +92,10 @@
92
92
  "import": "./dist/assetResolution.js",
93
93
  "types": "./dist/assetResolution.d.ts"
94
94
  },
95
+ "./html-attribute-spans": {
96
+ "import": "./dist/htmlAttributeSpans.js",
97
+ "types": "./dist/htmlAttributeSpans.d.ts"
98
+ },
95
99
  "./composition-duration": {
96
100
  "import": "./dist/compositionDuration.js",
97
101
  "types": "./dist/compositionDuration.d.ts"
@@ -104,6 +108,7 @@
104
108
  "@babel/parser": "^7.27.0",
105
109
  "acorn": "^8.17.0",
106
110
  "acorn-walk": "^8.3.5",
111
+ "entities": "^7.0.1",
107
112
  "linkedom": "^0.18.12",
108
113
  "magic-string": "^0.30.21",
109
114
  "recast": "^0.23.11"
@@ -114,7 +119,7 @@
114
119
  "tsx": "^4.21.0",
115
120
  "typescript": "^5.0.0",
116
121
  "vitest": "^4.1.11",
117
- "@hyperframes/core": "0.8.142"
122
+ "@hyperframes/core": "0.8.144"
118
123
  },
119
124
  "scripts": {
120
125
  "build": "tsup",