@cutcraft/kit 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -1 +1 @@
1
- export { Rn as ANIMATION_UNITS, zn as AUDIO_EFFECT_TYPES, Bn as AUTOMATION_LANES, N as AUTO_GROUP_TYPES, O as ActionHistory, It as Adjustment, Lt as Angle, Rt as Audio, zt as AudioEffect, Vn as BLEND_MODES, K as BUNDLED_FONTS, q as BUNDLED_FONT_WEIGHTS, Hn as CAPTION_ANIMATIONS, Un as CAPTION_POSITIONS, Bt as Caption, Vt as Captions, Ht as Clip, Ut as ColorGrade, Wt as Composition, Gt as Cut, tt as DEFAULT_CUE_OPTIONS, vn as DEFAULT_THEME, Wn as EASINGS, Ct as EASING_FN, Kt as ELEMENTS, Gn as EMPHASIS_ANIMATIONS, _t as EMPTY_MANIFEST, qt as Effect, D as Executor, Kn as FIT_MODES, yn as FONT_KEYS, Jt as Freeze, qn as GRAPHIC_ANIMATIONS, Yt as Gap, Xt as Graphic, dt as INTERIOR_PREFIX, Jn as INTERPOLATION_QUALITIES, Yn as KEYFRAME_PROPERTIES, Xn as KEYFRAME_PROPERTY_PATTERN, In as KIND_CAPABILITIES, Zt as Keyframe, Qt as Layer, Tn as MASKED_KINDS, Zn as MASK_EASINGS, En as MASK_NOT_RENDERED, Qn as MASK_TYPES, $n as MATTE_CHANNELS, bn as MOTIONS, xn as MOTION_NAMES, $t as Marker, en as Mask, tn as Multicam, Dn as NOT_RENDERED, nn as Nested, r as NotImplemented, Sn as PALETTE_KEYS, On as RENDERED_AUDIO_EFFECTS, kn as RENDERED_CAPTION_ANIMATIONS, An as RENDERED_COLOR_GRADE, jn as RENDERED_EFFECTS, Mn as RENDERED_MASK_TYPES, Nn as RENDERED_TITLE_ANIMATIONS, Pn as RENDERED_TRANSITIONS, Fn as RENDERED_VALUES, rn as SCHEMAS_BY_KIND, er as SHAPE_TYPES, tr as STABILIZE_CROP_MODES, pn as STYLED_KINDS, nr as SVG_COLOR_MODES, in as Shape, an as SpeedKey, on as Sticker, sn as Svg, P as TARGET_PARAM_KEYS, rr as TEXT_ALIGNS, ir as TEXT_ANIMATION_PRESETS, ar as TEXT_DECORATIONS, or as TEXT_VALIGNS, Cn as THEMES, wn as THEME_NAMES, mn as TOKEN_PATTERN, sr as TRACK_FLAGS, cr as TRACK_LAYOUTS, lr as TRACK_ROLES, dn as TRANSITION_PARAMS, ur as TRANSITION_TYPES, cn as Title, ln as Track, un as Transition, dr as VIDEO_EFFECT_TYPES, Q as applyTheme, V as asJson, vt as assetIdOf, Ln as capabilitiesOf, i as childAt, a as childIndex, lt as collect, o as collectErrors, ut as collectModule, H as createPatchLog, nt as cueNodes, fn as defaultTransitionParams, s as define, Or as defineElement, F as describeAction, ft as findParentOf, wt as freezeHoldsOf, Tt as freezeShift, Et as freezeUnshift, k as getHandler, t as graphicControls, Dt as holdTotal, c as insertNode, Nt as installLayerClock, bt as interpolate, L as invalid, A as inverseOf, J as isBundledFont, l as isError, fr as isKeyframeProperty, ct as isResolveError, mt as isTimeRefString, kr as isVocabulary, Ar as kindOf, u as lastChildId, j as listHandlers, yt as lookupAsset, d as makePropHandler, hn as motionStyleSchema, R as nodeOfSpec, z as nodeSpecOf, U as nodesEqual, B as ok, rt as onTimeline, ht as parseTimeRef, f as placeNode, p as placementOf, m as placementProps, it as planCues, at as playsKey, Ot as rampAdvance, kt as rampInverse, At as rampSpeed, xt as random, h as rawNode, g as rawProp, W as reduceInto, M as registerHandler, _ as requireElement, v as requireLayout, y as requireLiteralProp, b as requireNotOpaque, x as requireUnlocked, X as resolve, Y as resolveScene, $ as resolveTheme, gt as resolveTimeRef, ot as sameAsset, Z as samePlacement, S as setProps, C as siblingAfter, jt as speedKeysOf, St as spring, Mt as springAt, G as stableJson, w as stub, I as targetIdOf, gn as themeSchema, et as themeToken, st as timelineWords, _n as tokenSchema, T as uniqueId, Pt as useLayer, Ft as useT, E as validateTarget, pt as walk, pr as zAudioEffectType, mr as zBlendMode, hr as zCaptionAnimation, gr as zEasing, _r as zEmphasisAnimation, vr as zFitMode, yr as zGraphicAnimation, br as zKeyframeProperty, xr as zMaskEasing, Sr as zShapeType, Cr as zTextAnimationPreset, wr as zTrackLayout, Tr as zTrackRole, Er as zTransitionType, Dr as zVideoEffectType } from "./src-BsBU2JLf.mjs";
1
+ export { Jn as ANIMATION_UNITS, Yn as AUTOMATION_LANES, lt as AUTO_GROUP_TYPES, ct as ActionHistory, tn as Adjustment, nn as Angle, rn as Audio, Xn as BLEND_MODES, c as BUNDLED_FONTS, l as BUNDLED_FONT_WEIGHTS, Zn as CAPTION_ANIMATIONS, Qn as CAPTION_POSITIONS, in as Caption, an as Captions, on as Clip, sn as ColorGrade, cn as Composition, ln as Cut, St as DEFAULT_CUE_OPTIONS, Vn as DEFAULT_THEME, $n as EASINGS, kt as EASING_FN, un as ELEMENTS, Ht as EMPTY_MANIFEST, dn as ENGINE_LAYERS, fn as ENGINE_SCHEMAS, pn as Effect, rt as Executor, er as FIT_MODES, Hn as FONT_KEYS, mn as Freeze, tr as GRAPHIC_ANIMATIONS, hn as Gap, gn as Graphic, yt as INTERIOR_PREFIX, nr as KEYFRAME_PROPERTIES, rr as KEYFRAME_PROPERTY_PATTERN, Pr as KIND_CAPABILITIES, _n as Keyframe, vn as Layer, b as MASKED_KINDS, ir as MASK_EASINGS, x as MASK_NOT_RENDERED, ar as MASK_TYPES, u as MODULE_FONTS, Jt as MODULE_SHADERS, Un as MOTIONS, Wn as MOTION_NAMES, yn as Marker, bn as Mask, xn as Motion, Sn as Multicam, S as NOT_RENDERED, Cn as Nested, N as NotImplemented, Gn as PALETTE_KEYS, C as RENDERED_VALUES, wn as SCHEMAS_BY_KIND, or as SHAPE_TYPES, In as STYLED_KINDS, sr as SVG_COLOR_MODES, Tn as Shape, En as SpeedKey, Dn as Sticker, On as Svg, ut as TARGET_PARAM_KEYS, cr as TEXT_ALIGNS, lr as TEXT_ANIMATION_PRESETS, ur as TEXT_DECORATIONS, dr as TEXT_VALIGNS, Kn as THEMES, qn as THEME_NAMES, Ln as TOKEN_PATTERN, fr as TRACK_FLAGS, pr as TRACK_LAYOUTS, mr as TRACK_ROLES, Pn as TRANSITION_PARAMS, hr as TRANSITION_TYPES, kn as Three, An as Title, jn as Track, Mn as Transition, gr as VIDEO_EFFECT_TYPES, zt as applyTheme, Ir as asJson, Ut as assetIdOf, Fr as capabilitiesOf, P as childAt, F as childIndex, o as collect, I as collectErrors, d as collectFonts, s as collectModule, Yt as collectShaders, Lr as createPatchLog, Ct as cueNodes, Fn as defaultTransitionParams, L as define, Xt as defineEffect, jr as defineElement, Zt as defineTransition, dt as describeAction, Nn as engineOf, bt as findParentOf, f as font, p as fontsOf, At as freezeHoldsOf, jt as freezeShift, Mt as freezeUnshift, it as getHandler, m as googleFont, t as graphicControls, E as holdLayerFrame, Nt as holdTotal, R as insertNode, _ as installLayerClock, D as installLayerEngine, A as interpolate, pt as invalid, at as inverseOf, h as isBundledFont, z as isError, _r as isKeyframeProperty, a as isResolveError, Gt as isTimeRefString, Mr as isVocabulary, Nr as kindOf, B as lastChildId, O as layerEngine, k as layerFramesPending, ot as listHandlers, Wt as lookupAsset, V as makePropHandler, w as motionBlurOf, Rn as motionStyleSchema, mt as nodeOfSpec, ht as nodeSpecOf, Rr as nodesEqual, gt as ok, wt as onTimeline, Kt as parseTimeRef, g as parseTranscript, H as placeNode, U as placementOf, W as placementProps, Tt as planCues, Et as playsKey, Pt as rampAdvance, Ft as rampInverse, It as rampSpeed, j as random, G as rawNode, K as rawProp, zr as reduceInto, st as registerHandler, q as requireElement, J as requireLayout, Y as requireLiteralProp, X as requireNotOpaque, Z as requireUnlocked, _t as resolve, i as resolveScene, Bt as resolveTheme, qt as resolveTimeRef, Dt as sameAsset, vt as samePlacement, Q as setProps, Qt as shaderFor, $t as shadersOf, T as shutterTimes, $ as siblingAfter, Lt as speedKeysOf, M as spring, Rt as springAt, Br as stableJson, et as stub, r as subtitles, ft as targetIdOf, zn as themeSchema, Vt as themeToken, Ot as timelineWords, Bn as tokenSchema, tt as uniqueId, v as useLayer, y as useT, en as usesShaders, nt as validateTarget, xt as walk, vr as zBlendMode, yr as zCaptionAnimation, br as zEasing, xr as zFitMode, Sr as zGraphicAnimation, Cr as zKeyframeProperty, wr as zMaskEasing, Tr as zShapeType, Er as zTextAnimationPreset, Dr as zTrackLayout, Or as zTrackRole, kr as zTransitionType, Ar as zVideoEffectType } from "./src-CcFjz_DV.mjs";
@@ -1,8 +1,8 @@
1
- import { Dt as AssetManifest, I as ResolvedState, O as NodeSpec, lt as PatchLog, ot as Json } from "../index-CLhXqOpF.mjs";
1
+ import { O as NodeSpec, Rn as AssetManifest, V as ResolvedState, an as PatchLog, nn as Json } from "../index-BYYpzZuS.mjs";
2
2
  import { ZodObject } from "zod";
3
3
  import { ReactNode } from "react";
4
- import { ParseResult } from "@babel/parser";
5
4
  import * as t from "@babel/types";
5
+ import { ParseResult } from "@babel/parser";
6
6
  interface ParsedSource {
7
7
  readonly text: string;
8
8
  readonly ast: ParseResult<t.File>;
@@ -13,6 +13,48 @@ export declare function rangeOf(node: {
13
13
  start?: number | null;
14
14
  end?: number | null;
15
15
  }): Range;
16
+ type SourceEdit = {
17
+ readonly kind: "prop";
18
+ readonly id: string;
19
+ readonly prop: string;
20
+ readonly range: Range;
21
+ readonly text: string;
22
+ } | {
23
+ readonly kind: "text";
24
+ readonly id: string;
25
+ readonly range: Range;
26
+ readonly text: string;
27
+ } | {
28
+ readonly kind: "insert";
29
+ readonly id: string;
30
+ readonly range: Range;
31
+ readonly text: string;
32
+ } | {
33
+ readonly kind: "remove";
34
+ readonly id: string;
35
+ readonly range: Range;
36
+ readonly text: "";
37
+ } | {
38
+ readonly kind: "move";
39
+ readonly id: string;
40
+ readonly range: Range;
41
+ readonly text: string;
42
+ readonly from: Range;
43
+ } | {
44
+ readonly kind: "bake";
45
+ readonly id: string;
46
+ readonly range: Range;
47
+ readonly text: string;
48
+ } | {
49
+ readonly kind: "defaults";
50
+ readonly id: string;
51
+ readonly range: Range;
52
+ readonly text: string;
53
+ };
54
+ /** Splice edits into the text, last range first, refusing overlaps. */
55
+ export declare function applyEdits(text: string, edits: readonly SourceEdit[]): string;
56
+ /** Widen an element range to swallow its own line(s): leading indentation and the trailing newline. */
57
+ export declare function lineRange(text: string, range: Range): Range;
16
58
  interface PropSite {
17
59
  readonly name: string;
18
60
  /** The whole attribute, including the name. */
@@ -123,55 +165,12 @@ export declare function literalOf(node: t.Node | null | undefined): {
123
165
  value: Json | undefined;
124
166
  };
125
167
  export declare function indentOf(text: string, pos: number): string;
126
- type SourceEdit = {
127
- readonly kind: "prop";
128
- readonly id: string;
129
- readonly prop: string;
130
- readonly range: Range;
131
- readonly text: string;
132
- } | {
133
- readonly kind: "text";
134
- readonly id: string;
135
- readonly range: Range;
136
- readonly text: string;
137
- } | {
138
- readonly kind: "insert";
139
- readonly id: string;
140
- readonly range: Range;
141
- readonly text: string;
142
- } | {
143
- readonly kind: "remove";
144
- readonly id: string;
145
- readonly range: Range;
146
- readonly text: "";
147
- } | {
148
- readonly kind: "move";
149
- readonly id: string;
150
- readonly range: Range;
151
- readonly text: string;
152
- readonly from: Range;
153
- } | {
154
- readonly kind: "bake";
155
- readonly id: string;
156
- readonly range: Range;
157
- readonly text: string;
158
- } | {
159
- readonly kind: "defaults";
160
- readonly id: string;
161
- readonly range: Range;
162
- readonly text: string;
163
- };
164
- /** Splice edits into the text, last range first, refusing overlaps. */
165
- export declare function applyEdits(text: string, edits: readonly SourceEdit[]): string;
166
- /** Widen an element range to swallow its own line(s): leading indentation and the trailing newline. */
167
- export declare function lineRange(text: string, range: Range): Range;
168
- /** JavaScript source for a JSON value, compact, double-quoted. */
169
- export declare function printLiteral(value: Json | undefined): string;
170
- /** One JSX attribute. Strings as `name="…"`, everything else as `name={…}`, `true` as a bare attribute. */
171
- export declare function printProp(name: string, value: Json | undefined): string;
172
- export declare function elementName(kind: string): string;
173
- /** A whole element as JSX text, indented with `indent`, children on their own lines. */
174
- export declare function printElement(spec: NodeSpec, indent?: string, step?: string): string;
168
+ /**
169
+ * Replace the helper call that produced a generated element with the literal
170
+ * elements that call produced, so the editor can address them. Multicam cuts
171
+ * are baked by appending their generated clips after the generator.
172
+ */
173
+ export declare function bake(text: string, id: string, state: ResolvedState, index?: SourceIndex): SourceEdit[];
175
174
  interface FoldResult {
176
175
  readonly text: string;
177
176
  readonly edits: readonly SourceEdit[];
@@ -194,12 +193,6 @@ export declare function fold(log: PatchLog, index: SourceIndex, state: ResolvedS
194
193
  };
195
194
  /** Fold with baking: generated targets are baked into literals, the file is re-indexed, and the fold is retried. */
196
195
  export declare function foldToSource(text: string, log: PatchLog, state: ResolvedState, maxRounds?: number): FoldResult;
197
- /**
198
- * Replace the helper call that produced a generated element with the literal
199
- * elements that call produced, so the editor can address them. Multicam cuts
200
- * are baked by appending their generated clips after the generator.
201
- */
202
- export declare function bake(text: string, id: string, state: ResolvedState, index?: SourceIndex): SourceEdit[];
203
196
  interface Rule {
204
197
  readonly name: string;
205
198
  check(index: SourceIndex, options: LintOptions): Diagnostic[];
@@ -233,4 +226,11 @@ interface LoadOptions {
233
226
  readonly imports?: Readonly<Record<string, unknown>>;
234
227
  }
235
228
  export declare function loadModule(source: string, options?: LoadOptions): CutModule;
229
+ /** JavaScript source for a JSON value, compact, double-quoted. */
230
+ export declare function printLiteral(value: Json | undefined): string;
231
+ /** One JSX attribute. Strings as `name="…"`, everything else as `name={…}`, `true` as a bare attribute. */
232
+ export declare function printProp(name: string, value: Json | undefined): string;
233
+ export declare function elementName(kind: string): string;
234
+ /** A whole element as JSX text, indented with `indent`, children on their own lines. */
235
+ export declare function printElement(spec: NodeSpec, indent?: string, step?: string): string;
236
236
  export type { CutModule, Diagnostic, ElementSite, FoldResult, HelperCallSite, HelperSite, LintOptions, LintRule, LoadOptions, PropSite, Range, SourceEdit, SourceIndex };
@@ -1,12 +1,49 @@
1
- import { $ as resolveTheme, Dn as NOT_RENDERED, En as MASK_NOT_RENDERED, Fn as RENDERED_VALUES, J as isBundledFont, Kt as ELEMENTS, Ln as capabilitiesOf, Tn as MASKED_KINDS, dt as INTERIOR_PREFIX, et as themeToken, gt as resolveTimeRef, mn as TOKEN_PATTERN, n as src_exports, pn as STYLED_KINDS, yt as lookupAsset, z as nodeSpecOf } from "../src-BsBU2JLf.mjs";
2
- import * as zod from "zod";
1
+ import { Bt as resolveTheme, C as RENDERED_VALUES, Fr as capabilitiesOf, In as STYLED_KINDS, Jt as MODULE_SHADERS, Ln as TOKEN_PATTERN, S as NOT_RENDERED, Vt as themeToken, Wt as lookupAsset, Yt as collectShaders, b as MASKED_KINDS, d as collectFonts, dn as ENGINE_LAYERS, h as isBundledFont, ht as nodeSpecOf, n as src_exports, qt as resolveTimeRef, u as MODULE_FONTS, un as ELEMENTS, x as MASK_NOT_RENDERED, yt as INTERIOR_PREFIX } from "../src-CcFjz_DV.mjs";
3
2
  import { GRAPHIC_BASE_PROPS, GRAPHIC_KINDS, GRAPHIC_KIND_NAMES, isIcon } from "@cutcraft/graphics";
3
+ import * as zod from "zod";
4
4
  import * as React from "react";
5
- import { parse } from "@babel/parser";
6
5
  import traverseModule from "@babel/traverse";
7
6
  import * as t from "@babel/types";
7
+ import { parse } from "@babel/parser";
8
8
  import * as jsxRuntime from "react/jsx-runtime";
9
9
  import { transform } from "sucrase";
10
+ // src/source/edits.ts
11
+ /** Splice edits into the text, last range first, refusing overlaps. */
12
+ function applyEdits(text, edits) {
13
+ const splices = [];
14
+ for (const e of edits) if (e.kind === "move") {
15
+ splices.push({
16
+ range: e.from,
17
+ text: ""
18
+ });
19
+ splices.push({
20
+ range: e.range,
21
+ text: e.text
22
+ });
23
+ } else splices.push({
24
+ range: e.range,
25
+ text: e.text
26
+ });
27
+ splices.sort((a, b) => b.range[0] - a.range[0] || b.range[1] - a.range[1]);
28
+ let out = text;
29
+ let lastStart = Number.POSITIVE_INFINITY;
30
+ for (const s of splices) {
31
+ if (s.range[1] > lastStart) throw new Error(`overlapping edits at ${s.range[0]}..${s.range[1]}`);
32
+ out = out.slice(0, s.range[0]) + s.text + out.slice(s.range[1]);
33
+ lastStart = s.range[0];
34
+ }
35
+ return out;
36
+ }
37
+ /** Widen an element range to swallow its own line(s): leading indentation and the trailing newline. */
38
+ function lineRange(text, range) {
39
+ let start = range[0];
40
+ const lineStart = text.lastIndexOf("\n", start - 1) + 1;
41
+ if (/^\s*$/.test(text.slice(lineStart, start))) start = lineStart;
42
+ let end = range[1];
43
+ const rest = text.slice(end).match(/^[ \t]*\r?\n/);
44
+ if (rest) end += rest[0].length;
45
+ return [start, end];
46
+ }
10
47
  // src/source/parse.ts
11
48
  function parseSource(text) {
12
49
  return {
@@ -24,7 +61,7 @@ function rangeOf(node) {
24
61
  }
25
62
  // src/source/index.ts
26
63
  const traverse$1 = traverseModule.default ?? traverseModule;
27
- const KIND_BY_NAME = new Map(Object.entries(ELEMENTS).map(([name, c]) => [name, c.kind]));
64
+ const KIND_BY_NAME = new Map([...Object.entries(ELEMENTS), ...Object.entries(ENGINE_LAYERS)].map(([name, c]) => [name, c.kind]));
28
65
  /**
29
66
  * Index every vocabulary JSX element with a literal id so patches can be
30
67
  * written back as text edits. Elements inside helper functions are indexed
@@ -185,7 +222,7 @@ function buildSourceIndex(text) {
185
222
  });
186
223
  const closing = path.node.closingElement;
187
224
  const childrenRange = closing ? [opening.end ?? 0, closing.start ?? 0] : null;
188
- const interior = kind === "layer" ? interiorOf(path.node, inSchemaFn) : null;
225
+ const interior = name === "Layer" ? interiorOf(path.node, inSchemaFn) : null;
189
226
  if (interior) {
190
227
  for (const prop of interior.lockedProps) locked.push(`${INTERIOR_PREFIX}${prop}`);
191
228
  for (const [prop, key] of Object.entries(interior.boundProps)) bound[`${INTERIOR_PREFIX}${prop}`] = key;
@@ -447,43 +484,6 @@ function indentOf(text, pos) {
447
484
  const prefix = text.slice(lineStart, pos);
448
485
  return /^\s*$/.test(prefix) ? prefix : "";
449
486
  }
450
- // src/source/edits.ts
451
- /** Splice edits into the text, last range first, refusing overlaps. */
452
- function applyEdits(text, edits) {
453
- const splices = [];
454
- for (const e of edits) if (e.kind === "move") {
455
- splices.push({
456
- range: e.from,
457
- text: ""
458
- });
459
- splices.push({
460
- range: e.range,
461
- text: e.text
462
- });
463
- } else splices.push({
464
- range: e.range,
465
- text: e.text
466
- });
467
- splices.sort((a, b) => b.range[0] - a.range[0] || b.range[1] - a.range[1]);
468
- let out = text;
469
- let lastStart = Number.POSITIVE_INFINITY;
470
- for (const s of splices) {
471
- if (s.range[1] > lastStart) throw new Error(`overlapping edits at ${s.range[0]}..${s.range[1]}`);
472
- out = out.slice(0, s.range[0]) + s.text + out.slice(s.range[1]);
473
- lastStart = s.range[0];
474
- }
475
- return out;
476
- }
477
- /** Widen an element range to swallow its own line(s): leading indentation and the trailing newline. */
478
- function lineRange(text, range) {
479
- let start = range[0];
480
- const lineStart = text.lastIndexOf("\n", start - 1) + 1;
481
- if (/^\s*$/.test(text.slice(lineStart, start))) start = lineStart;
482
- let end = range[1];
483
- const rest = text.slice(end).match(/^[ \t]*\r?\n/);
484
- if (rest) end += rest[0].length;
485
- return [start, end];
486
- }
487
487
  // src/source/print.ts
488
488
  const NAME_BY_KIND = new Map(Object.entries(ELEMENTS).map(([name, c]) => [c.kind, name]));
489
489
  const IDENT = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
@@ -514,7 +514,7 @@ function elementName(kind) {
514
514
  function printElement(spec, indent = "", step = " ") {
515
515
  const name = elementName(spec.kind);
516
516
  const attrs = Object.entries(spec.props).filter(([k, v]) => v !== void 0 && true).map(([k, v]) => printProp(k, v)).filter(Boolean);
517
- const open = `${indent}<${name}${attrs.length ? " " + attrs.join(" ") : ""}`;
517
+ const open = `${indent}<${name}${attrs.length ? ` ${attrs.join(" ")}` : ""}`;
518
518
  const children = spec.children ?? [];
519
519
  if (children.length === 0 && spec.text === void 0) return `${open} />`;
520
520
  if (children.length === 0) return `${open}>${escapeText(spec.text ?? "")}</${name}>`;
@@ -933,7 +933,7 @@ function bindingEdit(index, id, prop, key, value, skipped) {
933
933
  });
934
934
  return null;
935
935
  }
936
- if (!target || !target.valueRange) {
936
+ if (!target?.valueRange) {
937
937
  skipped.push({
938
938
  id,
939
939
  reason: `"${prop}" is bound to defaults.${key}, which has no literal`
@@ -1035,6 +1035,97 @@ function reindent(source, from, to) {
1035
1035
  if (from === to) return source;
1036
1036
  return source.split("\n").map((line, i) => i === 0 ? line : line.startsWith(from) ? to + line.slice(from.length) : line).join("\n");
1037
1037
  }
1038
+ // src/source/lint/rules/fonts.ts
1039
+ const DECLARERS = /* @__PURE__ */ new Set([
1040
+ "font",
1041
+ "googleFont",
1042
+ "defineEffect",
1043
+ "defineTransition"
1044
+ ]);
1045
+ /** Declarations whose arguments hold code (a zod schema): only their name and shader need be literal. */
1046
+ const SHADERS = /* @__PURE__ */ new Set(["defineEffect", "defineTransition"]);
1047
+ /** Calls to the kit's declarations (`font()`, `googleFont()`, `defineEffect()`, `defineTransition()`), under whatever local names the file imports them as. */
1048
+ function declarations(index) {
1049
+ const local = /* @__PURE__ */ new Map();
1050
+ for (const node of index.ast.program.body) {
1051
+ if (!t.isImportDeclaration(node) || node.source.value !== "@cutcraft/kit") continue;
1052
+ for (const spec of node.specifiers) {
1053
+ if (!t.isImportSpecifier(spec)) continue;
1054
+ const imported = t.isIdentifier(spec.imported) ? spec.imported.name : spec.imported.value;
1055
+ if (DECLARERS.has(imported)) local.set(spec.local.name, imported);
1056
+ }
1057
+ }
1058
+ if (local.size === 0) return [];
1059
+ const top = /* @__PURE__ */ new Set();
1060
+ for (const node of index.ast.program.body) {
1061
+ const decl = t.isExportNamedDeclaration(node) ? node.declaration : node;
1062
+ if (t.isVariableDeclaration(decl)) {
1063
+ for (const d of decl.declarations) if (d.init) top.add(d.init);
1064
+ }
1065
+ }
1066
+ const out = [];
1067
+ t.traverseFast(index.ast.program, (node) => {
1068
+ if (t.isCallExpression(node) && t.isIdentifier(node.callee) && local.has(node.callee.name)) out.push({
1069
+ call: node,
1070
+ topLevel: top.has(node),
1071
+ declarer: local.get(node.callee.name)
1072
+ });
1073
+ });
1074
+ return out;
1075
+ }
1076
+ /** A value written out in the source: a string, number or boolean, or an array or object of them. */
1077
+ function literal(node) {
1078
+ if (t.isStringLiteral(node) || t.isNumericLiteral(node) || t.isBooleanLiteral(node)) return true;
1079
+ if (t.isTemplateLiteral(node)) return node.expressions.length === 0;
1080
+ if (t.isArrayExpression(node)) return node.elements.every((e) => e !== null && literal(e));
1081
+ if (t.isObjectExpression(node)) return node.properties.every((p) => t.isObjectProperty(p) && !p.computed && (t.isIdentifier(p.key) || t.isStringLiteral(p.key)) && literal(p.value));
1082
+ return false;
1083
+ }
1084
+ /** The families a file declares with literal arguments: `font({ family: "…" })` and `googleFont("…")`. */
1085
+ function declaredFamilies(index) {
1086
+ const out = /* @__PURE__ */ new Set();
1087
+ for (const { call, declarer } of declarations(index)) {
1088
+ if (SHADERS.has(declarer)) continue;
1089
+ const [first] = call.arguments;
1090
+ if (t.isStringLiteral(first)) out.add(first.value);
1091
+ else if (t.isObjectExpression(first)) {
1092
+ for (const p of first.properties) if (t.isObjectProperty(p) && t.isIdentifier(p.key, { name: "family" }) && t.isStringLiteral(p.value)) out.add(p.value.value);
1093
+ }
1094
+ }
1095
+ return out;
1096
+ }
1097
+ /** A `name` and `shader` written out in a `defineEffect()` / `defineTransition()` call's options. */
1098
+ function literalShader(call) {
1099
+ const [options] = call.arguments;
1100
+ if (!t.isObjectExpression(options)) return false;
1101
+ const field = (key) => options.properties.find((p) => t.isObjectProperty(p) && t.isIdentifier(p.key, { name: key }))?.value;
1102
+ const name = field("name");
1103
+ const shader = field("shader");
1104
+ return !!name && !!shader && literal(name) && literal(shader);
1105
+ }
1106
+ /**
1107
+ * The kit's declarations (`font()`, `googleFont()`, `defineEffect()`, `defineTransition()`) at the top of the file,
1108
+ * with what names them written out, where the editor and lint can read them.
1109
+ */
1110
+ const declaration = {
1111
+ name: "declaration",
1112
+ check: (index) => declarations(index).flatMap(({ call, topLevel, declarer }) => {
1113
+ const name = call.callee.name;
1114
+ if (!topLevel) return [{
1115
+ rule: "declaration",
1116
+ severity: "warning",
1117
+ range: rangeOf(call),
1118
+ message: `${name}() inside a function or expression is hidden from the editor; declare it at the top of the file: const x = ${name}(…)`
1119
+ }];
1120
+ if (SHADERS.has(declarer) ? !literalShader(call) : !call.arguments.every(literal)) return [{
1121
+ rule: "declaration",
1122
+ severity: "warning",
1123
+ range: rangeOf(call),
1124
+ message: SHADERS.has(declarer) ? `${name}() takes a literal name and shader, so the editor and lint can read them from the source` : `${name}() takes literal arguments, so the editor and lint can read the font from the source`
1125
+ }];
1126
+ return [];
1127
+ })
1128
+ };
1038
1129
  // src/source/lint/rules/free-code.ts
1039
1130
  /** HTML straight in a `<Track>` or `<Composition>`: the resolver walks past it and nothing draws it. */
1040
1131
  const strayHtml = {
@@ -1050,6 +1141,27 @@ const strayHtml = {
1050
1141
  * Free code renders frame by frame against the video's own clock. `useT()`, requestAnimationFrame, Date, performance
1051
1142
  * and CSS animations follow it; timers, media elements playing on their own and canvases kept on the CPU do not.
1052
1143
  */
1144
+ /** Libraries with a clock of their own, by module: the imports that run on it, and the adapter that puts them on the video's. */
1145
+ const OWN_CLOCK = {
1146
+ "@react-three/fiber": {
1147
+ names: ["Canvas"],
1148
+ use: "<ThreeScene> from @cutcraft/graphics/three"
1149
+ },
1150
+ motion: {
1151
+ names: ["animate"],
1152
+ use: "useTimeline from @cutcraft/graphics/motion"
1153
+ },
1154
+ "motion/react": {
1155
+ names: ["animate", "useAnimate"],
1156
+ use: "useTimeline or useLayerTime from @cutcraft/graphics/motion"
1157
+ },
1158
+ "framer-motion": {
1159
+ names: ["animate", "useAnimate"],
1160
+ use: "useTimeline or useLayerTime from @cutcraft/graphics/motion"
1161
+ }
1162
+ };
1163
+ /** Props that start a Motion component's own animation. */
1164
+ const MOTION_PLAYS = /* @__PURE__ */ new Set(["animate", "whileInView"]);
1053
1165
  const freeCodeClock = {
1054
1166
  name: "free-code-clock",
1055
1167
  check: (index) => {
@@ -1062,13 +1174,38 @@ const freeCodeClock = {
1062
1174
  message
1063
1175
  });
1064
1176
  };
1177
+ const ownClock = /* @__PURE__ */ new Map();
1178
+ const motionFactories = /* @__PURE__ */ new Set();
1179
+ for (const node of index.ast.program.body) {
1180
+ if (!t.isImportDeclaration(node)) continue;
1181
+ const lib = OWN_CLOCK[node.source.value];
1182
+ if (!lib) continue;
1183
+ for (const spec of node.specifiers) {
1184
+ if (!t.isImportSpecifier(spec)) continue;
1185
+ const imported = t.isIdentifier(spec.imported) ? spec.imported.name : spec.imported.value;
1186
+ if (lib.names.includes(imported)) ownClock.set(spec.local.name, {
1187
+ imported,
1188
+ use: lib.use
1189
+ });
1190
+ if (node.source.value !== "motion" && (imported === "motion" || imported === "m")) motionFactories.add(spec.local.name);
1191
+ }
1192
+ }
1065
1193
  t.traverseFast(index.ast.program, (node) => {
1066
1194
  if (t.isCallExpression(node)) {
1067
1195
  const callee = node.callee;
1068
1196
  const name = t.isIdentifier(callee) ? callee.name : t.isMemberExpression(callee) && t.isIdentifier(callee.property) ? callee.property.name : null;
1069
1197
  if (name === "setTimeout" || name === "setInterval") warn(node, `${name} runs on the wall clock, so an export would not repeat it frame for frame; derive the change from useT() instead`);
1070
- } else if (t.isJSXOpeningElement(node) && t.isJSXIdentifier(node.name) && (node.name.name === "video" || node.name.name === "audio")) warn(node, `<${node.name.name}> plays on its own clock and its sound is not in the export; use a <Clip> or <Audio>, and useClipFrame for a clip's picture in free code`);
1071
- else if (t.isObjectProperty(node) && t.isIdentifier(node.key) && node.key.name === "willReadFrequently" && t.isBooleanLiteral(node.value, { value: true })) warn(node, "a canvas with willReadFrequently is kept on the CPU and the export does not capture it; leave the option out");
1198
+ const own = t.isIdentifier(callee) ? ownClock.get(callee.name) : void 0;
1199
+ if (own) warn(node, `${own.imported}() plays on its library's own clock, so a scrub or an export does not follow it; use ${own.use}`);
1200
+ } else if (t.isJSXOpeningElement(node)) {
1201
+ const el = node.name;
1202
+ if (t.isJSXIdentifier(el) && (el.name === "video" || el.name === "audio")) warn(node, `<${el.name}> plays on its own clock and its sound is not in the export; use a <Clip> or <Audio>, and useClipFrame for a clip's picture in free code`);
1203
+ else if (t.isJSXIdentifier(el) && ownClock.has(el.name)) warn(node, `<${el.name}> draws on its own clock, so a scrub or an export does not follow it; use ${ownClock.get(el.name).use}`);
1204
+ else if (t.isJSXMemberExpression(el) && t.isJSXIdentifier(el.object) && motionFactories.has(el.object.name)) {
1205
+ const plays = node.attributes.find((a) => t.isJSXAttribute(a) && t.isJSXIdentifier(a.name) && MOTION_PLAYS.has(a.name.name));
1206
+ if (plays) warn(plays, `<${el.object.name}.${el.property.name}> animates on Motion's own clock, so a scrub or an export does not follow it; drive it with useTimeline or useLayerTime from @cutcraft/graphics/motion`);
1207
+ }
1208
+ } else if (t.isObjectProperty(node) && t.isIdentifier(node.key) && node.key.name === "willReadFrequently" && t.isBooleanLiteral(node.value, { value: true })) warn(node, "a canvas with willReadFrequently is kept on the CPU and the export does not capture it; leave the option out");
1072
1209
  });
1073
1210
  return out;
1074
1211
  }
@@ -1136,6 +1273,25 @@ const graphicProps = {
1136
1273
  return out;
1137
1274
  }
1138
1275
  };
1276
+ /** `<Graphic>` is deprecated: each one warns, pointing to the free code that replaces it. */
1277
+ const deprecatedGraphic = {
1278
+ name: "deprecated-graphic",
1279
+ check: (index) => {
1280
+ const out = [];
1281
+ for (const site of index.elements.values()) {
1282
+ if (site.kind !== "graphic") continue;
1283
+ const kind = site.props.get("kind")?.literal;
1284
+ out.push({
1285
+ rule: "deprecated-graphic",
1286
+ severity: "warning",
1287
+ message: `"${site.id}": <Graphic${typeof kind === "string" ? ` kind="${kind}"` : ""}> is deprecated and will be removed; draw it with <Motion>, <Three> or a <Layer> (see the examples)`,
1288
+ range: site.openingTag,
1289
+ id: site.id
1290
+ });
1291
+ }
1292
+ return out;
1293
+ }
1294
+ };
1139
1295
  // src/source/lint/rules/not-rendered.ts
1140
1296
  /** Values that ask for nothing, so writing them is never reported. */
1141
1297
  const OFF = [
@@ -1431,20 +1587,21 @@ const themeLiterals = {
1431
1587
  return out;
1432
1588
  }
1433
1589
  };
1434
- /** Fonts no renderer ships: the export sets them in Inter unless the fonts are passed to it. */
1590
+ /** Fonts no renderer ships and the file does not declare (`font()`, `googleFont()`): the export sets them in Inter. */
1435
1591
  const fontAvailable = {
1436
1592
  name: "font-available",
1437
1593
  check: (index) => {
1438
1594
  const theme = fileTheme(index) ?? resolveTheme(void 0);
1595
+ const listed = declaredFamilies(index);
1439
1596
  const out = [];
1440
1597
  const seen = /* @__PURE__ */ new Set();
1441
1598
  const check = (family, site, prop, name, path) => {
1442
- if (isBundledFont(family) || seen.has(`${site.id}.${path}`)) return;
1599
+ if (isBundledFont(family) || listed.has(family) || seen.has(`${site.id}.${path}`)) return;
1443
1600
  seen.add(`${site.id}.${path}`);
1444
1601
  out.push({
1445
1602
  rule: "font-available",
1446
1603
  severity: "warning",
1447
- message: `"${site.id}.${path}" uses font "${family}", which is not bundled; the export sets it in Inter unless the font is passed to it`,
1604
+ message: `"${site.id}.${path}" uses font "${family}", which is neither bundled nor declared with font() or googleFont(); the export sets it in Inter`,
1448
1605
  range: [prop.range[0], prop.range[1]],
1449
1606
  id: site.id,
1450
1607
  prop: name
@@ -1710,7 +1867,9 @@ const rules = [
1710
1867
  themeRefs,
1711
1868
  themeLiterals,
1712
1869
  fontAvailable,
1870
+ declaration,
1713
1871
  graphicProps,
1872
+ deprecatedGraphic,
1714
1873
  notRendered
1715
1874
  ];
1716
1875
  /** The composition's first track in document order that lays out as a sequence and is not hidden: the one under everything else. */
@@ -1761,8 +1920,13 @@ function loadModule(source, options = {}) {
1761
1920
  if (!(name in offered)) throw new Error(`The file imports “${name}”, which this host does not offer (it offers ${Object.keys(offered).join(", ")}).`);
1762
1921
  return offered[name];
1763
1922
  };
1764
- new Function("require", "exports", "module", code)(require, module.exports, module);
1923
+ let shaders = [];
1924
+ const fonts = collectFonts(() => {
1925
+ shaders = collectShaders(() => new Function("require", "exports", "module", code)(require, module.exports, module));
1926
+ });
1765
1927
  if (typeof module.exports.default !== "function") throw new Error("The file has no default export (the video).");
1928
+ Object.defineProperty(module.exports, MODULE_FONTS, { value: fonts });
1929
+ Object.defineProperty(module.exports, MODULE_SHADERS, { value: shaders });
1766
1930
  return module.exports;
1767
1931
  }
1768
1932
  export { ALLOWED_IMPORTS, applyEdits, bake, buildSourceIndex, elementName, fold, foldToSource, indentOf, lineRange, lint, literalOf, loadModule, parseSource, printElement, printLiteral, printProp, rangeOf, rules };