@hyperframes/parsers 0.7.45 → 0.7.47

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.
@@ -1,7 +1,64 @@
1
- export { A as AddElementData, b as Asset, B as BooleanVariable, c as CANVAS_DIMENSIONS, d as COMPOSITION_VARIABLE_TYPES, a as CanvasResolution, e as ColorVariable, f as CompositionAPI, g as CompositionAsset, h as CompositionSpec, C as CompositionVariable, i as CompositionVariableBase, j as CompositionVariableType, D as DEFAULT_DURATIONS, E as ElementKeyframes, k as EnumVariable, F as FontVariable, I as ImageVariable, K as Keyframe, l as KeyframeProperties, M as MediaElementType, m as MediaFile, N as NumberVariable, P as PlayerAPI, n as StageZoom, S as StageZoomKeyframe, o as StringVariable, p as TIMELINE_COLORS, q as TimelineCompositionElement, T as TimelineElement, r as TimelineElementBase, s as TimelineElementType, t as TimelineMediaElement, u as TimelineTextElement, v as VALID_CANVAS_RESOLUTIONS, V as ValidationResult, W as WaveformData, w as getDefaultStageZoom, x as isCompositionElement, y as isMediaElement, z as isTextElement, G as normalizeResolutionFlag } from './types-CaeOJXdW.js';
1
+ import { C as CompositionVariable } from './types-CaeOJXdW.js';
2
+ export { A as AddElementData, b as Asset, B as BooleanVariable, c as CANVAS_DIMENSIONS, d as COMPOSITION_VARIABLE_TYPES, a as CanvasResolution, e as ColorVariable, f as CompositionAPI, g as CompositionAsset, h as CompositionSpec, i as CompositionVariableBase, j as CompositionVariableType, D as DEFAULT_DURATIONS, E as ElementKeyframes, k as EnumVariable, F as FontVariable, I as ImageVariable, K as Keyframe, l as KeyframeProperties, M as MediaElementType, m as MediaFile, N as NumberVariable, P as PlayerAPI, n as StageZoom, S as StageZoomKeyframe, o as StringVariable, p as TIMELINE_COLORS, q as TimelineCompositionElement, T as TimelineElement, r as TimelineElementBase, s as TimelineElementType, t as TimelineMediaElement, u as TimelineTextElement, v as VALID_CANVAS_RESOLUTIONS, V as ValidationResult, W as WaveformData, w as getDefaultStageZoom, x as isCompositionElement, y as isMediaElement, z as isTextElement, G as normalizeResolutionFlag } from './types-CaeOJXdW.js';
3
+
4
+ /**
5
+ * Browser-safe parser for the `data-composition-variables` schema attribute.
6
+ * Lives outside htmlParser.ts so browser consumers (SDK, Studio, lint) can
7
+ * import it via `@hyperframes/parsers/composition` without pulling the
8
+ * linkedom/Node HTML-parser machinery from the main entry.
9
+ */
10
+
11
+ /**
12
+ * Scalar variable values (string/number/boolean) are the ones that flow into
13
+ * CSS custom props and text bindings; font/image values are object-shaped.
14
+ * Shared so the SDK's CSS-compat writes, the runtime bindings, and Studio's
15
+ * display logic can never disagree on what "scalar" means.
16
+ */
17
+ declare function isScalarVariableValue(value: unknown): value is string | number | boolean;
18
+ /**
19
+ * True when the value is a structurally valid variable declaration: id, label,
20
+ * a known type, a default matching that type, and options[] for enums. The
21
+ * same predicate parseCompositionVariables filters with — exported so writers
22
+ * (SDK declaration ops, Studio forms) can validate before persisting.
23
+ */
24
+ declare function isCompositionVariable(v: unknown): v is CompositionVariable;
25
+ /**
26
+ * Parse the typed variable declarations from an element's
27
+ * `data-composition-variables` attribute. Malformed entries (wrong shape,
28
+ * unknown type, default not matching the declared type) are dropped; an
29
+ * absent attribute, invalid JSON, or a non-array payload yields `[]`.
30
+ */
31
+ declare function parseCompositionVariables(htmlEl: Element): CompositionVariable[];
2
32
 
3
33
  declare function decodeUrlPathVariants(path: string): string[];
4
34
 
35
+ /**
36
+ * Browser-safe static scan for composition-variable reads in script text.
37
+ *
38
+ * Compositions read variables by calling the runtime API — `getVariables()`
39
+ * bare (sub-comp scoped shadow) or via `__hyperframes.getVariables()` /
40
+ * `window.__hyperframes.getVariables()` — and there is no DOM-attribute
41
+ * binding to scan, so "which variables does this composition use" can only be
42
+ * derived from the scripts. This is a best-effort static analysis: the
43
+ * patterns agents actually write (destructuring, member access, a single
44
+ * alias variable) resolve to ids; anything opaque flips `scanIncomplete`
45
+ * so consumers can present usage as a lower bound instead of a fact.
46
+ *
47
+ * AST nodes are handled untyped (same convention as gsapParserAcorn.ts) —
48
+ * acorn's structural types don't survive acorn-walk's visitor signatures.
49
+ */
50
+ interface VariableUsageScan {
51
+ /** Variable ids statically read by the script, in first-seen order. */
52
+ usedIds: string[];
53
+ /**
54
+ * True when the script accesses variables in a way the scan cannot resolve
55
+ * (computed keys, rest spreads, the values object escaping into a call…) or
56
+ * when the script fails to parse — usedIds is then a lower bound.
57
+ */
58
+ scanIncomplete: boolean;
59
+ }
60
+ declare function scanVariableUsage(scriptText: string): VariableUsageScan;
61
+
5
62
  /**
6
63
  * Single source of truth for the deterministic font alias map. Both the
7
64
  * producer's @font-face injector and the core lint rules import from here,
@@ -93,4 +150,4 @@ declare const CANONICAL_FONT_DISPLAY_NAMES: Readonly<Record<string, string>>;
93
150
  */
94
151
  declare function resolveAliasDisplayName(alias: string): string | undefined;
95
152
 
96
- export { CANONICAL_FONT_DISPLAY_NAMES, FONT_ALIAS_KEYS, FONT_ALIAS_MAP, decodeUrlPathVariants, resolveAliasDisplayName };
153
+ export { CANONICAL_FONT_DISPLAY_NAMES, CompositionVariable, FONT_ALIAS_KEYS, FONT_ALIAS_MAP, type VariableUsageScan, decodeUrlPathVariants, isCompositionVariable, isScalarVariableValue, parseCompositionVariables, resolveAliasDisplayName, scanVariableUsage };
@@ -187,6 +187,153 @@ function decodeUrlPathVariants(path) {
187
187
  }
188
188
  return variants;
189
189
  }
190
+
191
+ // src/compositionVariables.ts
192
+ var DEFAULT_TYPEOF = {
193
+ string: "string",
194
+ number: "number",
195
+ color: "string",
196
+ boolean: "boolean",
197
+ enum: "string",
198
+ font: "string",
199
+ image: "string"
200
+ };
201
+ function isScalarVariableValue(value) {
202
+ return typeof value === "string" || typeof value === "number" || typeof value === "boolean";
203
+ }
204
+ function isRecord(v) {
205
+ return typeof v === "object" && v !== null;
206
+ }
207
+ function isVariableType(t) {
208
+ return typeof t === "string" && t in DEFAULT_TYPEOF;
209
+ }
210
+ function isCompositionVariable(v) {
211
+ if (!isRecord(v)) return false;
212
+ if (typeof v.id !== "string" || typeof v.label !== "string") return false;
213
+ if (!isVariableType(v.type)) return false;
214
+ if (typeof v.default !== DEFAULT_TYPEOF[v.type]) return false;
215
+ if (v.type === "enum" && !Array.isArray(v.options)) return false;
216
+ return true;
217
+ }
218
+ function parseCompositionVariables(htmlEl) {
219
+ const variablesAttr = htmlEl.getAttribute("data-composition-variables");
220
+ if (!variablesAttr) {
221
+ return [];
222
+ }
223
+ try {
224
+ const parsed = JSON.parse(variablesAttr);
225
+ if (!Array.isArray(parsed)) {
226
+ return [];
227
+ }
228
+ return parsed.filter(isCompositionVariable);
229
+ } catch {
230
+ return [];
231
+ }
232
+ }
233
+
234
+ // src/variableUsage.ts
235
+ import * as acorn from "acorn";
236
+ import * as acornWalk from "acorn-walk";
237
+ function isGetVariablesCallee(callee) {
238
+ if (callee?.type === "Identifier") return callee.name === "getVariables";
239
+ if (callee?.type === "MemberExpression" && !callee.computed) {
240
+ return callee.property?.type === "Identifier" && callee.property.name === "getVariables";
241
+ }
242
+ return false;
243
+ }
244
+ function collectFromObjectPattern(pattern, out) {
245
+ for (const prop of pattern.properties ?? []) {
246
+ if (prop?.type === "RestElement") {
247
+ out.incomplete();
248
+ continue;
249
+ }
250
+ if (prop?.type !== "Property") continue;
251
+ if (prop.computed === true) {
252
+ out.incomplete();
253
+ } else if (prop.key?.type === "Identifier") {
254
+ out.use(String(prop.key.name));
255
+ } else if (prop.key?.type === "Literal" && typeof prop.key.value === "string") {
256
+ out.use(prop.key.value);
257
+ } else {
258
+ out.incomplete();
259
+ }
260
+ }
261
+ }
262
+ function collectFromMemberAccess(member, out) {
263
+ if (member.computed !== true && member.property?.type === "Identifier") {
264
+ out.use(String(member.property.name));
265
+ } else if (member.computed === true && member.property?.type === "Literal" && typeof member.property.value === "string") {
266
+ out.use(member.property.value);
267
+ } else {
268
+ out.incomplete();
269
+ }
270
+ }
271
+ function classifyValueRead(parent, valueNode, out) {
272
+ if (!parent || parent.type === "ExpressionStatement") {
273
+ return null;
274
+ }
275
+ if (parent.type === "MemberExpression" && parent.object === valueNode) {
276
+ collectFromMemberAccess(parent, out);
277
+ return null;
278
+ }
279
+ if (parent.type === "VariableDeclarator" && parent.init === valueNode) {
280
+ if (parent.id?.type === "ObjectPattern") {
281
+ collectFromObjectPattern(parent.id, out);
282
+ return null;
283
+ }
284
+ if (parent.id?.type === "Identifier") return String(parent.id.name);
285
+ out.incomplete();
286
+ return null;
287
+ }
288
+ out.incomplete();
289
+ return null;
290
+ }
291
+ function scanVariableUsage(scriptText) {
292
+ const usedIds = [];
293
+ const seen = /* @__PURE__ */ new Set();
294
+ let scanIncomplete = false;
295
+ const sink = {
296
+ use(id) {
297
+ if (!seen.has(id)) {
298
+ seen.add(id);
299
+ usedIds.push(id);
300
+ }
301
+ },
302
+ incomplete() {
303
+ scanIncomplete = true;
304
+ }
305
+ };
306
+ let ast;
307
+ try {
308
+ ast = acorn.parse(scriptText, { ecmaVersion: "latest", sourceType: "script" });
309
+ } catch {
310
+ return { usedIds: [], scanIncomplete: true };
311
+ }
312
+ const aliases = /* @__PURE__ */ new Set();
313
+ acornWalk.ancestor(ast, {
314
+ CallExpression(node, _, ancestors) {
315
+ if (!isGetVariablesCallee(node.callee)) return;
316
+ const parent = ancestors[ancestors.length - 2];
317
+ const alias = classifyValueRead(parent, node, sink);
318
+ if (alias) aliases.add(alias);
319
+ }
320
+ });
321
+ if (aliases.size > 0) {
322
+ acornWalk.ancestor(ast, {
323
+ // fallow-ignore-next-line complexity
324
+ Identifier(node, _, ancestors) {
325
+ if (!aliases.has(String(node.name))) return;
326
+ const parent = ancestors[ancestors.length - 2];
327
+ if (!parent) return;
328
+ if (parent.type === "VariableDeclarator" && parent.id === node) return;
329
+ if (parent.type === "MemberExpression" && parent.property === node) return;
330
+ if (parent.type === "Property" && parent.key === node && parent.computed !== true) return;
331
+ if (classifyValueRead(parent, node, sink)) sink.incomplete();
332
+ }
333
+ });
334
+ }
335
+ return { usedIds, scanIncomplete };
336
+ }
190
337
  export {
191
338
  CANONICAL_FONT_DISPLAY_NAMES,
192
339
  CANVAS_DIMENSIONS,
@@ -199,9 +346,13 @@ export {
199
346
  decodeUrlPathVariants,
200
347
  getDefaultStageZoom,
201
348
  isCompositionElement,
349
+ isCompositionVariable,
202
350
  isMediaElement,
351
+ isScalarVariableValue,
203
352
  isTextElement,
204
353
  normalizeResolutionFlag,
205
- resolveAliasDisplayName
354
+ parseCompositionVariables,
355
+ resolveAliasDisplayName,
356
+ scanVariableUsage
206
357
  };
207
358
  //# sourceMappingURL=composition.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/types.ts","../src/fontAliases.ts","../src/utils/urlPath.ts"],"sourcesContent":["// ── Composition data types ───────────────────────────────────────────────────\n// Moved from @hyperframes/core/core.types in the parsers extraction refactor.\n// These are the types produced and consumed by the parser pipeline.\n\nexport interface Asset {\n id: string;\n url: string;\n type: string;\n is_reference?: boolean;\n /** Duration in seconds for video/audio assets */\n duration?: number;\n}\n\n// ── Timeline types ──────────────────────────────────────────────────────────\n\nexport type TimelineElementType = \"video\" | \"image\" | \"text\" | \"audio\" | \"composition\";\nexport type MediaElementType = \"video\" | \"image\" | \"audio\";\n\nexport const CANVAS_DIMENSIONS = {\n landscape: { width: 1920, height: 1080 },\n portrait: { width: 1080, height: 1920 },\n \"landscape-4k\": { width: 3840, height: 2160 },\n \"portrait-4k\": { width: 2160, height: 3840 },\n square: { width: 1080, height: 1080 },\n \"square-4k\": { width: 2160, height: 2160 },\n} as const;\n\n// Single source of truth: derive the type from the table so adding a preset\n// extends the union automatically. Avoids the prior `as readonly CanvasResolution[]`\n// cast on `VALID_CANVAS_RESOLUTIONS` quietly drifting if the table grew but\n// the union didn't.\nexport type CanvasResolution = keyof typeof CANVAS_DIMENSIONS;\n\n// `Object.keys` ordering matches insertion order in `CANVAS_DIMENSIONS` on\n// every supported JS engine; tests pin the order in `index.test.ts`. Reorder\n// the table above with care.\nexport const VALID_CANVAS_RESOLUTIONS = Object.keys(\n CANVAS_DIMENSIONS,\n) as readonly CanvasResolution[];\n\nconst RESOLUTION_ALIASES: Record<string, CanvasResolution> = {\n \"1080p\": \"landscape\",\n hd: \"landscape\",\n \"1080p-portrait\": \"portrait\",\n \"portrait-1080p\": \"portrait\",\n \"4k\": \"landscape-4k\",\n uhd: \"landscape-4k\",\n \"4k-portrait\": \"portrait-4k\",\n \"1080p-square\": \"square\",\n \"square-1080p\": \"square\",\n \"4k-square\": \"square-4k\",\n};\n\n/**\n * Map a user-facing resolution string (canonical name or alias) to a\n * `CanvasResolution`. Returns undefined for unknown values so callers\n * can produce their own \"invalid\" UX (CLI exit, route validation, etc.).\n */\nexport function normalizeResolutionFlag(input: string | undefined): CanvasResolution | undefined {\n if (!input) return undefined;\n const lowered = input.toLowerCase();\n if ((VALID_CANVAS_RESOLUTIONS as readonly string[]).includes(lowered)) {\n return lowered as CanvasResolution;\n }\n return RESOLUTION_ALIASES[lowered];\n}\n\nexport interface TimelineElementBase {\n id: string;\n type: TimelineElementType;\n name: string;\n startTime: number;\n duration: number;\n zIndex: number;\n x?: number;\n y?: number;\n scale?: number;\n opacity?: number;\n}\n\nexport interface TimelineMediaElement extends TimelineElementBase {\n type: MediaElementType;\n src: string;\n mediaStartTime?: number;\n sourceDuration?: number;\n isAroll?: boolean;\n sourceWidth?: number;\n sourceHeight?: number;\n volume?: number; // 0-1 (0% to 100%), default 1.0\n hasAudio?: boolean; // For videos - indicates if video has audio track\n}\n\nexport interface WaveformData {\n peaks: number[];\n duration: number;\n sampleRate?: number;\n}\n\nexport interface TimelineTextElement extends TimelineElementBase {\n type: \"text\";\n content: string;\n color?: string;\n fontSize?: number;\n textShadow?: boolean;\n fontFamily?: string;\n fontWeight?: number;\n textOutline?: boolean;\n textOutlineColor?: string;\n textOutlineWidth?: number;\n textHighlight?: boolean;\n textHighlightColor?: string;\n textHighlightPadding?: number;\n textHighlightRadius?: number;\n}\n\nexport interface TimelineCompositionElement extends TimelineElementBase {\n type: \"composition\";\n src: string;\n compositionId: string;\n scale?: number;\n sourceDuration?: number;\n variableValues?: Record<string, string | number | boolean>;\n sourceWidth?: number;\n sourceHeight?: number;\n}\n\n// Composition Variable Types\nexport type CompositionVariableType =\n | \"string\"\n | \"number\"\n | \"color\"\n | \"boolean\"\n | \"enum\"\n | \"font\"\n | \"image\";\n\n/**\n * Runtime list of every valid `CompositionVariableType`. Use this anywhere\n * a Set/array of valid type strings is needed (lint rules, validators).\n * The `satisfies` guard turns adding a new variant to the union without\n * also adding it here into a compile error.\n */\nexport const COMPOSITION_VARIABLE_TYPES = [\n \"string\",\n \"number\",\n \"color\",\n \"boolean\",\n \"enum\",\n \"font\",\n \"image\",\n] as const satisfies readonly CompositionVariableType[];\n\nexport interface CompositionVariableBase {\n id: string;\n type: CompositionVariableType;\n label: string;\n description?: string;\n}\n\nexport interface StringVariable extends CompositionVariableBase {\n type: \"string\";\n default: string;\n placeholder?: string;\n maxLength?: number;\n}\n\nexport interface NumberVariable extends CompositionVariableBase {\n type: \"number\";\n default: number;\n min?: number;\n max?: number;\n step?: number;\n unit?: string;\n}\n\nexport interface ColorVariable extends CompositionVariableBase {\n type: \"color\";\n default: string;\n /** Brand role identifier, e.g. \"color:primary\". */\n brandRole?: string;\n}\n\nexport interface BooleanVariable extends CompositionVariableBase {\n type: \"boolean\";\n default: boolean;\n}\n\nexport interface EnumVariable extends CompositionVariableBase {\n type: \"enum\";\n default: string;\n options: { value: string; label: string }[];\n}\n\n/**\n * Font variable — value is a `{name, source}` object (object-valued; LOCKED §7).\n * `default` is the fallback font-family name string.\n * `source` is the font stylesheet URL (e.g. Google Fonts CSS).\n * `default_name` / `default_source` are the CSS-level fallbacks when the\n * brand font is absent.\n */\nexport interface FontVariable extends CompositionVariableBase {\n type: \"font\";\n /** Fallback font-family name, e.g. \"Inter\". */\n default: string;\n /** Font stylesheet URL (e.g. Google Fonts CSS link). */\n source?: string;\n /** CSS font-family name to use when source is unavailable, e.g. \"sans-serif\". */\n default_name?: string;\n /** Fallback font stylesheet URL (empty string = system font). */\n default_source?: string;\n}\n\n/**\n * Image variable — value is a `{url, …}` object (object-valued; LOCKED §7).\n * `default` is the fallback image URL string.\n * `brandRole` is an optional semantic label, e.g. \"logo:primary\".\n */\nexport interface ImageVariable extends CompositionVariableBase {\n type: \"image\";\n /** Fallback image URL. */\n default: string;\n /** Brand role identifier, e.g. \"logo:primary\". */\n brandRole?: string;\n}\n\nexport type CompositionVariable =\n | StringVariable\n | NumberVariable\n | ColorVariable\n | BooleanVariable\n | EnumVariable\n | FontVariable\n | ImageVariable;\n\nexport interface CompositionSpec {\n id: string;\n duration: number;\n variables: CompositionVariable[];\n}\n\nexport type TimelineElement =\n | TimelineMediaElement\n | TimelineTextElement\n | TimelineCompositionElement;\n\nexport function isTextElement(el: TimelineElement): el is TimelineTextElement {\n return el.type === \"text\";\n}\n\nexport function isMediaElement(el: TimelineElement): el is TimelineMediaElement {\n return el.type === \"video\" || el.type === \"image\" || el.type === \"audio\";\n}\n\nexport function isCompositionElement(el: TimelineElement): el is TimelineCompositionElement {\n return el.type === \"composition\";\n}\n\nexport interface MediaFile {\n id: string;\n name: string;\n type: TimelineElementType;\n src: string;\n file?: File;\n duration?: number;\n compositionId?: string;\n sourceWidth?: number; // Intrinsic width for compositions\n sourceHeight?: number; // Intrinsic height for compositions\n}\n\nexport const TIMELINE_COLORS: Record<TimelineElementType, string> = {\n video: \"#ec4899\",\n image: \"#3b82f6\",\n text: \"#06b6d4\",\n audio: \"#10b981\",\n composition: \"#f97316\",\n};\n\nexport const DEFAULT_DURATIONS: Record<TimelineElementType, number> = {\n video: 5,\n image: 5,\n text: 2,\n audio: 5,\n composition: 5,\n};\n\nexport interface CompositionAPI {\n id: string;\n duration: number;\n seek(time: number): void;\n getTime(): number;\n getDuration(): number;\n}\n\n// ── Player API types (used by runtime) ────────────────────────────────────\n\nexport interface PlayerAPI {\n play(): void;\n pause(): void;\n seek(time: number, options?: { keepPlaying?: boolean }): void;\n getTime(): number;\n getDuration(): number;\n isPlaying(): boolean;\n getMainTimeline(): unknown;\n getElementBounds(elementId: string): void;\n getElementsAtPoint(x: number, y: number): void;\n setElementPosition(elementId: string, x: number, y: number): void;\n previewElementPosition(elementId: string, x: number, y: number): void;\n setElementKeyframes(\n elementId: string,\n keyframes: Array<{\n id: string;\n time: number;\n properties: { x?: number; y?: number };\n }> | null,\n ): void;\n setElementScale(elementId: string, scale: number): void;\n setElementFontSize(elementId: string, fontSize: number): void;\n setElementTextContent(elementId: string, content: string): void;\n setElementTextColor(elementId: string, color: string): void;\n setElementTextShadow(elementId: string, enabled: boolean): void;\n setElementTextFontWeight(elementId: string, weight: number): void;\n setElementTextFontFamily(elementId: string, fontFamily: string): void;\n setElementTextOutline(elementId: string, enabled: boolean, color?: string, width?: number): void;\n setElementTextHighlight(\n elementId: string,\n enabled: boolean,\n color?: string,\n padding?: number,\n radius?: number,\n ): void;\n setElementVolume(elementId: string, volume: number): void;\n setStageZoom(scale: number, focusX: number, focusY: number): void;\n getStageZoom(): { scale: number; focusX: number; focusY: number };\n setStageZoomKeyframes(\n keyframes: Array<{\n id: string;\n time: number;\n zoom: { scale: number; focusX: number; focusY: number };\n ease?: string;\n }> | null,\n ): void;\n getStageZoomKeyframes(): Array<{\n id: string;\n time: number;\n zoom: { scale: number; focusX: number; focusY: number };\n ease?: string;\n }>;\n addElement(data: AddElementData): boolean;\n removeElement(elementId: string): boolean;\n updateElementTiming(elementId: string, start?: number, end?: number): boolean;\n setElementTiming(\n elementId: string,\n startTime: number,\n duration: number,\n mediaStartTime?: number,\n ): void;\n updateElementSrc(elementId: string, src: string): boolean;\n updateElementLayer(elementId: string, zIndex: number): boolean;\n updateElementBasePosition(elementId: string, x?: number, y?: number, scale?: number): boolean;\n markTimelineDirty(): void;\n isTimelineDirty(): boolean;\n rebuildTimeline(): void;\n ensureTimeline(): void;\n enableRenderMode(): void;\n disableRenderMode(): void;\n renderSeek(time: number, options?: { suppressEvents?: boolean }): void;\n getElementVisibility(elementId: string): { visible: boolean; opacity?: number };\n getVisibleElements(): Array<{ id: string; tagName: string; start: number; end: number }>;\n getRenderState(): {\n time: number;\n duration: number;\n isPlaying: boolean;\n renderMode: boolean;\n timelineDirty: boolean;\n };\n}\n\nexport interface AddElementData {\n id: string;\n type: \"video\" | \"image\" | \"text\" | \"audio\" | \"composition\";\n name?: string;\n src?: string;\n content?: string;\n start: number;\n end: number;\n zIndex?: number;\n x?: number;\n y?: number;\n scale?: number;\n fontSize?: number;\n color?: string;\n textShadow?: boolean;\n fontWeight?: number;\n textOutline?: boolean;\n textOutlineColor?: string;\n textOutlineWidth?: number;\n textHighlight?: boolean;\n textHighlightColor?: string;\n textHighlightPadding?: number;\n textHighlightRadius?: number;\n compositionId?: string;\n sourceWidth?: number;\n sourceHeight?: number;\n isAroll?: boolean;\n}\n\nexport interface ValidationResult {\n valid: boolean;\n errors: string[];\n warnings: string[];\n}\n\nexport interface CompositionAsset {\n id: string;\n name: string;\n type: \"composition\";\n src: string;\n duration: number;\n compositionId: string;\n thumbnail?: string;\n}\n\nexport interface Keyframe {\n id: string;\n time: number;\n properties: Partial<KeyframeProperties>;\n ease?: string;\n}\n\nexport interface KeyframeProperties {\n x: number;\n y: number;\n opacity: number;\n scale: number;\n scaleX: number;\n scaleY: number;\n rotation: number;\n width: number;\n height: number;\n}\n\nexport interface ElementKeyframes {\n elementId: string;\n keyframes: Keyframe[];\n}\n\nexport interface StageZoom {\n scale: number;\n focusX: number;\n focusY: number;\n}\n\nexport interface StageZoomKeyframe {\n id: string;\n time: number;\n zoom: StageZoom;\n ease?: string;\n}\n\nexport function getDefaultStageZoom(resolution: CanvasResolution): StageZoom {\n const { width, height } = CANVAS_DIMENSIONS[resolution];\n return {\n scale: 1,\n focusX: width / 2,\n focusY: height / 2,\n };\n}\n","/**\n * Single source of truth for the deterministic font alias map. Both the\n * producer's @font-face injector and the core lint rules import from here,\n * eliminating manual drift between the two.\n *\n * Keys are lowercase font family names. Values are canonical font slugs\n * matching CANONICAL_FONTS keys in the producer's deterministicFonts module.\n */\nexport const FONT_ALIAS_MAP = {\n // ── Canonical bundled fonts (self-referencing) ────────────────────────\n inter: \"inter\",\n montserrat: \"montserrat\",\n outfit: \"outfit\",\n nunito: \"nunito\",\n oswald: \"oswald\",\n \"league gothic\": \"league-gothic\",\n \"archivo black\": \"archivo-black\",\n \"space mono\": \"space-mono\",\n \"ibm plex mono\": \"ibm-plex-mono\",\n \"jetbrains mono\": \"jetbrains-mono\",\n \"eb garamond\": \"eb-garamond\",\n \"playfair display\": \"playfair-display\",\n \"source code pro\": \"source-code-pro\",\n \"noto sans jp\": \"noto-sans-jp\",\n roboto: \"roboto\",\n \"open sans\": \"open-sans\",\n lato: \"lato\",\n poppins: \"poppins\",\n\n // ── Common aliases → nearest canonical ────────────────────────────────\n \"helvetica neue\": \"inter\",\n helvetica: \"inter\",\n arial: \"inter\",\n \"helvetica bold\": \"inter\",\n futura: \"montserrat\",\n \"din alternate\": \"montserrat\",\n \"arial black\": \"montserrat\",\n \"bebas neue\": \"league-gothic\",\n \"courier new\": \"jetbrains-mono\",\n courier: \"jetbrains-mono\",\n garamond: \"eb-garamond\",\n \"noto sans japanese\": \"noto-sans-jp\",\n \"segoe ui\": \"roboto\",\n\n // ── macOS sans-serif system fonts → inter ─────────────────────────────\n \"sf pro\": \"inter\",\n \"sf pro display\": \"inter\",\n \"sf pro text\": \"inter\",\n \"sf pro rounded\": \"inter\",\n avenir: \"inter\",\n \"avenir next\": \"inter\",\n \"lucida grande\": \"inter\",\n geneva: \"inter\",\n optima: \"inter\",\n\n // ── Windows sans-serif system fonts → inter ───────────────────────────\n verdana: \"inter\",\n tahoma: \"inter\",\n \"trebuchet ms\": \"inter\",\n calibri: \"inter\",\n candara: \"inter\",\n corbel: \"inter\",\n \"lucida sans\": \"inter\",\n \"lucida sans unicode\": \"inter\",\n\n // ── Linux sans-serif system fonts → inter ─────────────────────────────\n \"noto sans\": \"inter\",\n \"dejavu sans\": \"inter\",\n \"liberation sans\": \"inter\",\n\n // ── Monospace system fonts → jetbrains-mono ───────────────────────────\n \"sf mono\": \"jetbrains-mono\",\n menlo: \"jetbrains-mono\",\n monaco: \"jetbrains-mono\",\n consolas: \"jetbrains-mono\",\n \"lucida console\": \"jetbrains-mono\",\n \"lucida sans typewriter\": \"jetbrains-mono\",\n \"andale mono\": \"jetbrains-mono\",\n \"dejavu sans mono\": \"jetbrains-mono\",\n \"liberation mono\": \"jetbrains-mono\",\n\n // ── Serif system fonts → eb-garamond ──────────────────────────────────\n georgia: \"eb-garamond\",\n palatino: \"eb-garamond\",\n \"palatino linotype\": \"eb-garamond\",\n \"book antiqua\": \"eb-garamond\",\n cambria: \"eb-garamond\",\n times: \"eb-garamond\",\n \"times new roman\": \"eb-garamond\",\n \"dejavu serif\": \"eb-garamond\",\n \"liberation serif\": \"eb-garamond\",\n} satisfies Readonly<Record<string, string>>;\n\nexport const FONT_ALIAS_KEYS: ReadonlySet<string> = new Set(Object.keys(FONT_ALIAS_MAP));\n\n/**\n * Human-readable display names for canonical font slugs. Used by the lint\n * rule to tell authors what their aliased font will render as.\n */\nexport const CANONICAL_FONT_DISPLAY_NAMES: Readonly<Record<string, string>> = {\n inter: \"Inter\",\n montserrat: \"Montserrat\",\n outfit: \"Outfit\",\n nunito: \"Nunito\",\n oswald: \"Oswald\",\n \"league-gothic\": \"League Gothic\",\n \"archivo-black\": \"Archivo Black\",\n \"space-mono\": \"Space Mono\",\n \"ibm-plex-mono\": \"IBM Plex Mono\",\n \"jetbrains-mono\": \"JetBrains Mono\",\n \"eb-garamond\": \"EB Garamond\",\n \"playfair-display\": \"Playfair Display\",\n \"source-code-pro\": \"Source Code Pro\",\n \"noto-sans-jp\": \"Noto Sans JP\",\n roboto: \"Roboto\",\n \"open-sans\": \"Open Sans\",\n lato: \"Lato\",\n poppins: \"Poppins\",\n};\n\n/**\n * Resolve a font alias to its canonical display name, or undefined if the\n * alias is not in the map.\n */\nexport function resolveAliasDisplayName(alias: string): string | undefined {\n const slug = (FONT_ALIAS_MAP as Record<string, string>)[alias.toLowerCase()];\n if (!slug) return undefined;\n return CANONICAL_FONT_DISPLAY_NAMES[slug];\n}\n","export function decodeUrlPathVariants(path: string): string[] {\n const variants = [path];\n try {\n const decoded = decodeURIComponent(path);\n if (decoded !== path) variants.unshift(decoded);\n } catch {\n // Malformed percent sequences may be literal filesystem names.\n }\n\n return variants;\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/types.ts","../src/fontAliases.ts","../src/utils/urlPath.ts","../src/compositionVariables.ts","../src/variableUsage.ts"],"sourcesContent":["// ── Composition data types ───────────────────────────────────────────────────\n// Moved from @hyperframes/core/core.types in the parsers extraction refactor.\n// These are the types produced and consumed by the parser pipeline.\n\nexport interface Asset {\n id: string;\n url: string;\n type: string;\n is_reference?: boolean;\n /** Duration in seconds for video/audio assets */\n duration?: number;\n}\n\n// ── Timeline types ──────────────────────────────────────────────────────────\n\nexport type TimelineElementType = \"video\" | \"image\" | \"text\" | \"audio\" | \"composition\";\nexport type MediaElementType = \"video\" | \"image\" | \"audio\";\n\nexport const CANVAS_DIMENSIONS = {\n landscape: { width: 1920, height: 1080 },\n portrait: { width: 1080, height: 1920 },\n \"landscape-4k\": { width: 3840, height: 2160 },\n \"portrait-4k\": { width: 2160, height: 3840 },\n square: { width: 1080, height: 1080 },\n \"square-4k\": { width: 2160, height: 2160 },\n} as const;\n\n// Single source of truth: derive the type from the table so adding a preset\n// extends the union automatically. Avoids the prior `as readonly CanvasResolution[]`\n// cast on `VALID_CANVAS_RESOLUTIONS` quietly drifting if the table grew but\n// the union didn't.\nexport type CanvasResolution = keyof typeof CANVAS_DIMENSIONS;\n\n// `Object.keys` ordering matches insertion order in `CANVAS_DIMENSIONS` on\n// every supported JS engine; tests pin the order in `index.test.ts`. Reorder\n// the table above with care.\nexport const VALID_CANVAS_RESOLUTIONS = Object.keys(\n CANVAS_DIMENSIONS,\n) as readonly CanvasResolution[];\n\nconst RESOLUTION_ALIASES: Record<string, CanvasResolution> = {\n \"1080p\": \"landscape\",\n hd: \"landscape\",\n \"1080p-portrait\": \"portrait\",\n \"portrait-1080p\": \"portrait\",\n \"4k\": \"landscape-4k\",\n uhd: \"landscape-4k\",\n \"4k-portrait\": \"portrait-4k\",\n \"1080p-square\": \"square\",\n \"square-1080p\": \"square\",\n \"4k-square\": \"square-4k\",\n};\n\n/**\n * Map a user-facing resolution string (canonical name or alias) to a\n * `CanvasResolution`. Returns undefined for unknown values so callers\n * can produce their own \"invalid\" UX (CLI exit, route validation, etc.).\n */\nexport function normalizeResolutionFlag(input: string | undefined): CanvasResolution | undefined {\n if (!input) return undefined;\n const lowered = input.toLowerCase();\n if ((VALID_CANVAS_RESOLUTIONS as readonly string[]).includes(lowered)) {\n return lowered as CanvasResolution;\n }\n return RESOLUTION_ALIASES[lowered];\n}\n\nexport interface TimelineElementBase {\n id: string;\n type: TimelineElementType;\n name: string;\n startTime: number;\n duration: number;\n zIndex: number;\n x?: number;\n y?: number;\n scale?: number;\n opacity?: number;\n}\n\nexport interface TimelineMediaElement extends TimelineElementBase {\n type: MediaElementType;\n src: string;\n mediaStartTime?: number;\n sourceDuration?: number;\n isAroll?: boolean;\n sourceWidth?: number;\n sourceHeight?: number;\n volume?: number; // 0-1 (0% to 100%), default 1.0\n hasAudio?: boolean; // For videos - indicates if video has audio track\n}\n\nexport interface WaveformData {\n peaks: number[];\n duration: number;\n sampleRate?: number;\n}\n\nexport interface TimelineTextElement extends TimelineElementBase {\n type: \"text\";\n content: string;\n color?: string;\n fontSize?: number;\n textShadow?: boolean;\n fontFamily?: string;\n fontWeight?: number;\n textOutline?: boolean;\n textOutlineColor?: string;\n textOutlineWidth?: number;\n textHighlight?: boolean;\n textHighlightColor?: string;\n textHighlightPadding?: number;\n textHighlightRadius?: number;\n}\n\nexport interface TimelineCompositionElement extends TimelineElementBase {\n type: \"composition\";\n src: string;\n compositionId: string;\n scale?: number;\n sourceDuration?: number;\n variableValues?: Record<string, string | number | boolean>;\n sourceWidth?: number;\n sourceHeight?: number;\n}\n\n// Composition Variable Types\nexport type CompositionVariableType =\n | \"string\"\n | \"number\"\n | \"color\"\n | \"boolean\"\n | \"enum\"\n | \"font\"\n | \"image\";\n\n/**\n * Runtime list of every valid `CompositionVariableType`. Use this anywhere\n * a Set/array of valid type strings is needed (lint rules, validators).\n * The `satisfies` guard turns adding a new variant to the union without\n * also adding it here into a compile error.\n */\nexport const COMPOSITION_VARIABLE_TYPES = [\n \"string\",\n \"number\",\n \"color\",\n \"boolean\",\n \"enum\",\n \"font\",\n \"image\",\n] as const satisfies readonly CompositionVariableType[];\n\nexport interface CompositionVariableBase {\n id: string;\n type: CompositionVariableType;\n label: string;\n description?: string;\n}\n\nexport interface StringVariable extends CompositionVariableBase {\n type: \"string\";\n default: string;\n placeholder?: string;\n maxLength?: number;\n}\n\nexport interface NumberVariable extends CompositionVariableBase {\n type: \"number\";\n default: number;\n min?: number;\n max?: number;\n step?: number;\n unit?: string;\n}\n\nexport interface ColorVariable extends CompositionVariableBase {\n type: \"color\";\n default: string;\n /** Brand role identifier, e.g. \"color:primary\". */\n brandRole?: string;\n}\n\nexport interface BooleanVariable extends CompositionVariableBase {\n type: \"boolean\";\n default: boolean;\n}\n\nexport interface EnumVariable extends CompositionVariableBase {\n type: \"enum\";\n default: string;\n options: { value: string; label: string }[];\n}\n\n/**\n * Font variable — value is a `{name, source}` object (object-valued; LOCKED §7).\n * `default` is the fallback font-family name string.\n * `source` is the font stylesheet URL (e.g. Google Fonts CSS).\n * `default_name` / `default_source` are the CSS-level fallbacks when the\n * brand font is absent.\n */\nexport interface FontVariable extends CompositionVariableBase {\n type: \"font\";\n /** Fallback font-family name, e.g. \"Inter\". */\n default: string;\n /** Font stylesheet URL (e.g. Google Fonts CSS link). */\n source?: string;\n /** CSS font-family name to use when source is unavailable, e.g. \"sans-serif\". */\n default_name?: string;\n /** Fallback font stylesheet URL (empty string = system font). */\n default_source?: string;\n}\n\n/**\n * Image variable — value is a `{url, …}` object (object-valued; LOCKED §7).\n * `default` is the fallback image URL string.\n * `brandRole` is an optional semantic label, e.g. \"logo:primary\".\n */\nexport interface ImageVariable extends CompositionVariableBase {\n type: \"image\";\n /** Fallback image URL. */\n default: string;\n /** Brand role identifier, e.g. \"logo:primary\". */\n brandRole?: string;\n}\n\nexport type CompositionVariable =\n | StringVariable\n | NumberVariable\n | ColorVariable\n | BooleanVariable\n | EnumVariable\n | FontVariable\n | ImageVariable;\n\nexport interface CompositionSpec {\n id: string;\n duration: number;\n variables: CompositionVariable[];\n}\n\nexport type TimelineElement =\n | TimelineMediaElement\n | TimelineTextElement\n | TimelineCompositionElement;\n\nexport function isTextElement(el: TimelineElement): el is TimelineTextElement {\n return el.type === \"text\";\n}\n\nexport function isMediaElement(el: TimelineElement): el is TimelineMediaElement {\n return el.type === \"video\" || el.type === \"image\" || el.type === \"audio\";\n}\n\nexport function isCompositionElement(el: TimelineElement): el is TimelineCompositionElement {\n return el.type === \"composition\";\n}\n\nexport interface MediaFile {\n id: string;\n name: string;\n type: TimelineElementType;\n src: string;\n file?: File;\n duration?: number;\n compositionId?: string;\n sourceWidth?: number; // Intrinsic width for compositions\n sourceHeight?: number; // Intrinsic height for compositions\n}\n\nexport const TIMELINE_COLORS: Record<TimelineElementType, string> = {\n video: \"#ec4899\",\n image: \"#3b82f6\",\n text: \"#06b6d4\",\n audio: \"#10b981\",\n composition: \"#f97316\",\n};\n\nexport const DEFAULT_DURATIONS: Record<TimelineElementType, number> = {\n video: 5,\n image: 5,\n text: 2,\n audio: 5,\n composition: 5,\n};\n\nexport interface CompositionAPI {\n id: string;\n duration: number;\n seek(time: number): void;\n getTime(): number;\n getDuration(): number;\n}\n\n// ── Player API types (used by runtime) ────────────────────────────────────\n\nexport interface PlayerAPI {\n play(): void;\n pause(): void;\n seek(time: number, options?: { keepPlaying?: boolean }): void;\n getTime(): number;\n getDuration(): number;\n isPlaying(): boolean;\n getMainTimeline(): unknown;\n getElementBounds(elementId: string): void;\n getElementsAtPoint(x: number, y: number): void;\n setElementPosition(elementId: string, x: number, y: number): void;\n previewElementPosition(elementId: string, x: number, y: number): void;\n setElementKeyframes(\n elementId: string,\n keyframes: Array<{\n id: string;\n time: number;\n properties: { x?: number; y?: number };\n }> | null,\n ): void;\n setElementScale(elementId: string, scale: number): void;\n setElementFontSize(elementId: string, fontSize: number): void;\n setElementTextContent(elementId: string, content: string): void;\n setElementTextColor(elementId: string, color: string): void;\n setElementTextShadow(elementId: string, enabled: boolean): void;\n setElementTextFontWeight(elementId: string, weight: number): void;\n setElementTextFontFamily(elementId: string, fontFamily: string): void;\n setElementTextOutline(elementId: string, enabled: boolean, color?: string, width?: number): void;\n setElementTextHighlight(\n elementId: string,\n enabled: boolean,\n color?: string,\n padding?: number,\n radius?: number,\n ): void;\n setElementVolume(elementId: string, volume: number): void;\n setStageZoom(scale: number, focusX: number, focusY: number): void;\n getStageZoom(): { scale: number; focusX: number; focusY: number };\n setStageZoomKeyframes(\n keyframes: Array<{\n id: string;\n time: number;\n zoom: { scale: number; focusX: number; focusY: number };\n ease?: string;\n }> | null,\n ): void;\n getStageZoomKeyframes(): Array<{\n id: string;\n time: number;\n zoom: { scale: number; focusX: number; focusY: number };\n ease?: string;\n }>;\n addElement(data: AddElementData): boolean;\n removeElement(elementId: string): boolean;\n updateElementTiming(elementId: string, start?: number, end?: number): boolean;\n setElementTiming(\n elementId: string,\n startTime: number,\n duration: number,\n mediaStartTime?: number,\n ): void;\n updateElementSrc(elementId: string, src: string): boolean;\n updateElementLayer(elementId: string, zIndex: number): boolean;\n updateElementBasePosition(elementId: string, x?: number, y?: number, scale?: number): boolean;\n markTimelineDirty(): void;\n isTimelineDirty(): boolean;\n rebuildTimeline(): void;\n ensureTimeline(): void;\n enableRenderMode(): void;\n disableRenderMode(): void;\n renderSeek(time: number, options?: { suppressEvents?: boolean }): void;\n getElementVisibility(elementId: string): { visible: boolean; opacity?: number };\n getVisibleElements(): Array<{ id: string; tagName: string; start: number; end: number }>;\n getRenderState(): {\n time: number;\n duration: number;\n isPlaying: boolean;\n renderMode: boolean;\n timelineDirty: boolean;\n };\n}\n\nexport interface AddElementData {\n id: string;\n type: \"video\" | \"image\" | \"text\" | \"audio\" | \"composition\";\n name?: string;\n src?: string;\n content?: string;\n start: number;\n end: number;\n zIndex?: number;\n x?: number;\n y?: number;\n scale?: number;\n fontSize?: number;\n color?: string;\n textShadow?: boolean;\n fontWeight?: number;\n textOutline?: boolean;\n textOutlineColor?: string;\n textOutlineWidth?: number;\n textHighlight?: boolean;\n textHighlightColor?: string;\n textHighlightPadding?: number;\n textHighlightRadius?: number;\n compositionId?: string;\n sourceWidth?: number;\n sourceHeight?: number;\n isAroll?: boolean;\n}\n\nexport interface ValidationResult {\n valid: boolean;\n errors: string[];\n warnings: string[];\n}\n\nexport interface CompositionAsset {\n id: string;\n name: string;\n type: \"composition\";\n src: string;\n duration: number;\n compositionId: string;\n thumbnail?: string;\n}\n\nexport interface Keyframe {\n id: string;\n time: number;\n properties: Partial<KeyframeProperties>;\n ease?: string;\n}\n\nexport interface KeyframeProperties {\n x: number;\n y: number;\n opacity: number;\n scale: number;\n scaleX: number;\n scaleY: number;\n rotation: number;\n width: number;\n height: number;\n}\n\nexport interface ElementKeyframes {\n elementId: string;\n keyframes: Keyframe[];\n}\n\nexport interface StageZoom {\n scale: number;\n focusX: number;\n focusY: number;\n}\n\nexport interface StageZoomKeyframe {\n id: string;\n time: number;\n zoom: StageZoom;\n ease?: string;\n}\n\nexport function getDefaultStageZoom(resolution: CanvasResolution): StageZoom {\n const { width, height } = CANVAS_DIMENSIONS[resolution];\n return {\n scale: 1,\n focusX: width / 2,\n focusY: height / 2,\n };\n}\n","/**\n * Single source of truth for the deterministic font alias map. Both the\n * producer's @font-face injector and the core lint rules import from here,\n * eliminating manual drift between the two.\n *\n * Keys are lowercase font family names. Values are canonical font slugs\n * matching CANONICAL_FONTS keys in the producer's deterministicFonts module.\n */\nexport const FONT_ALIAS_MAP = {\n // ── Canonical bundled fonts (self-referencing) ────────────────────────\n inter: \"inter\",\n montserrat: \"montserrat\",\n outfit: \"outfit\",\n nunito: \"nunito\",\n oswald: \"oswald\",\n \"league gothic\": \"league-gothic\",\n \"archivo black\": \"archivo-black\",\n \"space mono\": \"space-mono\",\n \"ibm plex mono\": \"ibm-plex-mono\",\n \"jetbrains mono\": \"jetbrains-mono\",\n \"eb garamond\": \"eb-garamond\",\n \"playfair display\": \"playfair-display\",\n \"source code pro\": \"source-code-pro\",\n \"noto sans jp\": \"noto-sans-jp\",\n roboto: \"roboto\",\n \"open sans\": \"open-sans\",\n lato: \"lato\",\n poppins: \"poppins\",\n\n // ── Common aliases → nearest canonical ────────────────────────────────\n \"helvetica neue\": \"inter\",\n helvetica: \"inter\",\n arial: \"inter\",\n \"helvetica bold\": \"inter\",\n futura: \"montserrat\",\n \"din alternate\": \"montserrat\",\n \"arial black\": \"montserrat\",\n \"bebas neue\": \"league-gothic\",\n \"courier new\": \"jetbrains-mono\",\n courier: \"jetbrains-mono\",\n garamond: \"eb-garamond\",\n \"noto sans japanese\": \"noto-sans-jp\",\n \"segoe ui\": \"roboto\",\n\n // ── macOS sans-serif system fonts → inter ─────────────────────────────\n \"sf pro\": \"inter\",\n \"sf pro display\": \"inter\",\n \"sf pro text\": \"inter\",\n \"sf pro rounded\": \"inter\",\n avenir: \"inter\",\n \"avenir next\": \"inter\",\n \"lucida grande\": \"inter\",\n geneva: \"inter\",\n optima: \"inter\",\n\n // ── Windows sans-serif system fonts → inter ───────────────────────────\n verdana: \"inter\",\n tahoma: \"inter\",\n \"trebuchet ms\": \"inter\",\n calibri: \"inter\",\n candara: \"inter\",\n corbel: \"inter\",\n \"lucida sans\": \"inter\",\n \"lucida sans unicode\": \"inter\",\n\n // ── Linux sans-serif system fonts → inter ─────────────────────────────\n \"noto sans\": \"inter\",\n \"dejavu sans\": \"inter\",\n \"liberation sans\": \"inter\",\n\n // ── Monospace system fonts → jetbrains-mono ───────────────────────────\n \"sf mono\": \"jetbrains-mono\",\n menlo: \"jetbrains-mono\",\n monaco: \"jetbrains-mono\",\n consolas: \"jetbrains-mono\",\n \"lucida console\": \"jetbrains-mono\",\n \"lucida sans typewriter\": \"jetbrains-mono\",\n \"andale mono\": \"jetbrains-mono\",\n \"dejavu sans mono\": \"jetbrains-mono\",\n \"liberation mono\": \"jetbrains-mono\",\n\n // ── Serif system fonts → eb-garamond ──────────────────────────────────\n georgia: \"eb-garamond\",\n palatino: \"eb-garamond\",\n \"palatino linotype\": \"eb-garamond\",\n \"book antiqua\": \"eb-garamond\",\n cambria: \"eb-garamond\",\n times: \"eb-garamond\",\n \"times new roman\": \"eb-garamond\",\n \"dejavu serif\": \"eb-garamond\",\n \"liberation serif\": \"eb-garamond\",\n} satisfies Readonly<Record<string, string>>;\n\nexport const FONT_ALIAS_KEYS: ReadonlySet<string> = new Set(Object.keys(FONT_ALIAS_MAP));\n\n/**\n * Human-readable display names for canonical font slugs. Used by the lint\n * rule to tell authors what their aliased font will render as.\n */\nexport const CANONICAL_FONT_DISPLAY_NAMES: Readonly<Record<string, string>> = {\n inter: \"Inter\",\n montserrat: \"Montserrat\",\n outfit: \"Outfit\",\n nunito: \"Nunito\",\n oswald: \"Oswald\",\n \"league-gothic\": \"League Gothic\",\n \"archivo-black\": \"Archivo Black\",\n \"space-mono\": \"Space Mono\",\n \"ibm-plex-mono\": \"IBM Plex Mono\",\n \"jetbrains-mono\": \"JetBrains Mono\",\n \"eb-garamond\": \"EB Garamond\",\n \"playfair-display\": \"Playfair Display\",\n \"source-code-pro\": \"Source Code Pro\",\n \"noto-sans-jp\": \"Noto Sans JP\",\n roboto: \"Roboto\",\n \"open-sans\": \"Open Sans\",\n lato: \"Lato\",\n poppins: \"Poppins\",\n};\n\n/**\n * Resolve a font alias to its canonical display name, or undefined if the\n * alias is not in the map.\n */\nexport function resolveAliasDisplayName(alias: string): string | undefined {\n const slug = (FONT_ALIAS_MAP as Record<string, string>)[alias.toLowerCase()];\n if (!slug) return undefined;\n return CANONICAL_FONT_DISPLAY_NAMES[slug];\n}\n","export function decodeUrlPathVariants(path: string): string[] {\n const variants = [path];\n try {\n const decoded = decodeURIComponent(path);\n if (decoded !== path) variants.unshift(decoded);\n } catch {\n // Malformed percent sequences may be literal filesystem names.\n }\n\n return variants;\n}\n","/**\n * Browser-safe parser for the `data-composition-variables` schema attribute.\n * Lives outside htmlParser.ts so browser consumers (SDK, Studio, lint) can\n * import it via `@hyperframes/parsers/composition` without pulling the\n * linkedom/Node HTML-parser machinery from the main entry.\n */\n\nimport type { CompositionVariable, CompositionVariableType } from \"./types.js\";\n\n/**\n * Required typeof for each variable type's `default`. For font the default is\n * the font-family name string; for image it is the fallback URL string —\n * extra metadata fields on both are optional and not validated here.\n */\nconst DEFAULT_TYPEOF: Record<CompositionVariableType, \"string\" | \"number\" | \"boolean\"> = {\n string: \"string\",\n number: \"number\",\n color: \"string\",\n boolean: \"boolean\",\n enum: \"string\",\n font: \"string\",\n image: \"string\",\n};\n\n/**\n * Scalar variable values (string/number/boolean) are the ones that flow into\n * CSS custom props and text bindings; font/image values are object-shaped.\n * Shared so the SDK's CSS-compat writes, the runtime bindings, and Studio's\n * display logic can never disagree on what \"scalar\" means.\n */\nexport function isScalarVariableValue(value: unknown): value is string | number | boolean {\n return typeof value === \"string\" || typeof value === \"number\" || typeof value === \"boolean\";\n}\n\nfunction isRecord(v: unknown): v is Record<string, unknown> {\n return typeof v === \"object\" && v !== null;\n}\n\nfunction isVariableType(t: unknown): t is CompositionVariableType {\n return typeof t === \"string\" && t in DEFAULT_TYPEOF;\n}\n\n/**\n * True when the value is a structurally valid variable declaration: id, label,\n * a known type, a default matching that type, and options[] for enums. The\n * same predicate parseCompositionVariables filters with — exported so writers\n * (SDK declaration ops, Studio forms) can validate before persisting.\n */\nexport function isCompositionVariable(v: unknown): v is CompositionVariable {\n if (!isRecord(v)) return false;\n if (typeof v.id !== \"string\" || typeof v.label !== \"string\") return false;\n if (!isVariableType(v.type)) return false;\n if (typeof v.default !== DEFAULT_TYPEOF[v.type]) return false;\n if (v.type === \"enum\" && !Array.isArray(v.options)) return false;\n return true;\n}\n\n/**\n * Parse the typed variable declarations from an element's\n * `data-composition-variables` attribute. Malformed entries (wrong shape,\n * unknown type, default not matching the declared type) are dropped; an\n * absent attribute, invalid JSON, or a non-array payload yields `[]`.\n */\nexport function parseCompositionVariables(htmlEl: Element): CompositionVariable[] {\n const variablesAttr = htmlEl.getAttribute(\"data-composition-variables\");\n if (!variablesAttr) {\n return [];\n }\n\n try {\n const parsed = JSON.parse(variablesAttr);\n if (!Array.isArray(parsed)) {\n return [];\n }\n return parsed.filter(isCompositionVariable);\n } catch {\n return [];\n }\n}\n","/**\n * Browser-safe static scan for composition-variable reads in script text.\n *\n * Compositions read variables by calling the runtime API — `getVariables()`\n * bare (sub-comp scoped shadow) or via `__hyperframes.getVariables()` /\n * `window.__hyperframes.getVariables()` — and there is no DOM-attribute\n * binding to scan, so \"which variables does this composition use\" can only be\n * derived from the scripts. This is a best-effort static analysis: the\n * patterns agents actually write (destructuring, member access, a single\n * alias variable) resolve to ids; anything opaque flips `scanIncomplete`\n * so consumers can present usage as a lower bound instead of a fact.\n *\n * AST nodes are handled untyped (same convention as gsapParserAcorn.ts) —\n * acorn's structural types don't survive acorn-walk's visitor signatures.\n */\n\nimport * as acorn from \"acorn\";\nimport * as acornWalk from \"acorn-walk\";\n\nexport interface VariableUsageScan {\n /** Variable ids statically read by the script, in first-seen order. */\n usedIds: string[];\n /**\n * True when the script accesses variables in a way the scan cannot resolve\n * (computed keys, rest spreads, the values object escaping into a call…) or\n * when the script fails to parse — usedIds is then a lower bound.\n */\n scanIncomplete: boolean;\n}\n\ninterface Sink {\n use(id: string): void;\n incomplete(): void;\n}\n\n// oxlint-disable no-explicit-any -- untyped acorn AST traversal, see header\n\nfunction isGetVariablesCallee(callee: any): boolean {\n if (callee?.type === \"Identifier\") return callee.name === \"getVariables\";\n if (callee?.type === \"MemberExpression\" && !callee.computed) {\n return callee.property?.type === \"Identifier\" && callee.property.name === \"getVariables\";\n }\n return false;\n}\n\n/** Collect ids from an ObjectPattern destructuring of the values object. */\n// Exhaustive AST-node classification — branchy by nature, same as gsapParserAcorn.\n// fallow-ignore-next-line complexity\nfunction collectFromObjectPattern(pattern: any, out: Sink): void {\n for (const prop of pattern.properties ?? []) {\n if (prop?.type === \"RestElement\") {\n out.incomplete();\n continue;\n }\n if (prop?.type !== \"Property\") continue;\n if (prop.computed === true) {\n out.incomplete();\n } else if (prop.key?.type === \"Identifier\") {\n out.use(String(prop.key.name));\n } else if (prop.key?.type === \"Literal\" && typeof prop.key.value === \"string\") {\n out.use(prop.key.value);\n } else {\n out.incomplete();\n }\n }\n}\n\n/** Collect an id from a MemberExpression reading the values object. */\nfunction collectFromMemberAccess(member: any, out: Sink): void {\n if (member.computed !== true && member.property?.type === \"Identifier\") {\n out.use(String(member.property.name));\n } else if (\n member.computed === true &&\n member.property?.type === \"Literal\" &&\n typeof member.property.value === \"string\"\n ) {\n out.use(member.property.value);\n } else {\n out.incomplete();\n }\n}\n\n/**\n * Classify one read of the values object (a getVariables() call result or an\n * alias holding it) by its immediate syntactic context. Returns the alias\n * name when the value is bound to a plain variable (`const vars = …`).\n */\n// fallow-ignore-next-line complexity\nfunction classifyValueRead(parent: any, valueNode: any, out: Sink): string | null {\n if (!parent || parent.type === \"ExpressionStatement\") {\n // Bare statement — value unused, nothing read.\n return null;\n }\n if (parent.type === \"MemberExpression\" && parent.object === valueNode) {\n collectFromMemberAccess(parent, out);\n return null;\n }\n if (parent.type === \"VariableDeclarator\" && parent.init === valueNode) {\n if (parent.id?.type === \"ObjectPattern\") {\n collectFromObjectPattern(parent.id, out);\n return null;\n }\n if (parent.id?.type === \"Identifier\") return String(parent.id.name);\n out.incomplete();\n return null;\n }\n // The values object escapes (argument, return, spread, assignment…) —\n // reads beyond this point are invisible to the scan.\n out.incomplete();\n return null;\n}\n\nexport function scanVariableUsage(scriptText: string): VariableUsageScan {\n const usedIds: string[] = [];\n const seen = new Set<string>();\n let scanIncomplete = false;\n\n const sink: Sink = {\n use(id: string) {\n if (!seen.has(id)) {\n seen.add(id);\n usedIds.push(id);\n }\n },\n incomplete() {\n scanIncomplete = true;\n },\n };\n\n let ast: any;\n try {\n ast = acorn.parse(scriptText, { ecmaVersion: \"latest\", sourceType: \"script\" });\n } catch {\n return { usedIds: [], scanIncomplete: true };\n }\n\n const aliases = new Set<string>();\n\n // Pass 1: classify every getVariables() call by its parent context.\n acornWalk.ancestor(ast, {\n CallExpression(node: any, _: unknown, ancestors: any[]) {\n if (!isGetVariablesCallee(node.callee)) return;\n const parent = ancestors[ancestors.length - 2];\n const alias = classifyValueRead(parent, node, sink);\n if (alias) aliases.add(alias);\n },\n } as any);\n\n // Pass 2: classify every reference to an alias of the values object.\n // Scope-naive by design: an unrelated same-named identifier can only make\n // the scan report extra ids or flip scanIncomplete, never miss a read.\n if (aliases.size > 0) {\n acornWalk.ancestor(ast, {\n // fallow-ignore-next-line complexity\n Identifier(node: any, _: unknown, ancestors: any[]) {\n if (!aliases.has(String(node.name))) return;\n const parent = ancestors[ancestors.length - 2];\n if (!parent) return;\n // Skip the declarator that introduced the alias and property-position\n // identifiers that merely share the name.\n if (parent.type === \"VariableDeclarator\" && parent.id === node) return;\n if (parent.type === \"MemberExpression\" && parent.property === node) return;\n if (parent.type === \"Property\" && parent.key === node && parent.computed !== true) return;\n // Chained aliases (const v2 = vars) are not followed — flag instead\n // of silently missing reads through the second name.\n if (classifyValueRead(parent, node, sink)) sink.incomplete();\n },\n } as any);\n }\n\n return { usedIds, scanIncomplete };\n}\n"],"mappings":"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","names":[]}
@@ -1253,6 +1253,11 @@ function findInsertionPoint(parsed) {
1253
1253
  const tlDecl = findTimelineDeclarationStatement(parsed.ast, parsed.timelineVar);
1254
1254
  return tlDecl?.end ?? parsed.ast.end;
1255
1255
  }
1256
+ function findGlobalSetInsertionPoint(parsed, script) {
1257
+ const tlDecl = findTimelineDeclarationStatement(parsed.ast, parsed.timelineVar);
1258
+ if (!tlDecl) return null;
1259
+ return script.lastIndexOf("\n", tlDecl.start) + 1;
1260
+ }
1256
1261
  function updateAnimationInScript(script, animationId, updates) {
1257
1262
  if (!Object.keys(updates).length) return script;
1258
1263
  const parsed = parseGsapScriptAcornForWrite(script);
@@ -1301,6 +1306,16 @@ function updateAnimationInScript(script, animationId, updates) {
1301
1306
  if (updates.position !== void 0) {
1302
1307
  overwritePosition(ms, call, updates.position);
1303
1308
  }
1309
+ if (target.animation.method === "set" && target.animation.global) {
1310
+ const globalSetPoint = findGlobalSetInsertionPoint(parsed, script);
1311
+ const exprStmt = findEnclosingExpressionStatement(call.ancestors);
1312
+ if (globalSetPoint !== null && exprStmt && exprStmt.start > globalSetPoint) {
1313
+ const lineStart = script.lastIndexOf("\n", exprStmt.start) + 1;
1314
+ const moveStart = /^\s*$/.test(script.slice(lineStart, exprStmt.start)) ? lineStart : exprStmt.start;
1315
+ const moveEnd = exprStmt.end < script.length && script[exprStmt.end] === "\n" ? exprStmt.end + 1 : exprStmt.end;
1316
+ ms.move(moveStart, moveEnd, globalSetPoint);
1317
+ }
1318
+ }
1304
1319
  return ms.toString();
1305
1320
  }
1306
1321
  function overwritePosition(ms, call, position) {
@@ -1350,14 +1365,31 @@ function scalePositionsInScript(script, targetSelector, oldStart, oldDuration, n
1350
1365
  function addAnimationToScript(script, animation) {
1351
1366
  const parsed = parseGsapScriptAcornForWrite(script);
1352
1367
  if (!parsed) return { script, id: "" };
1353
- const insertionPoint = findInsertionPoint(parsed);
1354
- if (insertionPoint === null) return { script, id: "" };
1355
1368
  const ms = new MagicString(script);
1356
1369
  const statementCode = buildTweenStatementCode(parsed.timelineVar, animation);
1357
- ms.appendLeft(insertionPoint, "\n" + statementCode);
1370
+ const globalSetPoint = animation.method === "set" && animation.global ? findGlobalSetInsertionPoint(parsed, script) : null;
1371
+ if (globalSetPoint !== null) {
1372
+ ms.appendLeft(globalSetPoint, statementCode + "\n");
1373
+ } else {
1374
+ const insertionPoint = findInsertionPoint(parsed);
1375
+ if (insertionPoint === null) return { script, id: "" };
1376
+ ms.appendLeft(insertionPoint, "\n" + statementCode);
1377
+ }
1358
1378
  const result = ms.toString();
1359
1379
  const reParsed = parseGsapScriptAcornForWrite(result);
1360
- const newId = reParsed?.located[reParsed.located.length - 1]?.id ?? "";
1380
+ const oldIdCounts = /* @__PURE__ */ new Map();
1381
+ for (const entry of parsed.located) {
1382
+ oldIdCounts.set(entry.id, (oldIdCounts.get(entry.id) ?? 0) + 1);
1383
+ }
1384
+ let newId = "";
1385
+ for (const entry of reParsed?.located ?? []) {
1386
+ const remaining = oldIdCounts.get(entry.id) ?? 0;
1387
+ if (remaining === 0) {
1388
+ newId = entry.id;
1389
+ break;
1390
+ }
1391
+ oldIdCounts.set(entry.id, remaining - 1);
1392
+ }
1361
1393
  return { script: result, id: newId };
1362
1394
  }
1363
1395
  function removeCallFromMagicString(ms, call, script) {