@hyperframes/parsers 0.7.24 → 0.7.26
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/gsapParser.js +6 -5
- package/dist/gsapParser.js.map +1 -1
- package/dist/gsapParserAcorn.js +278 -20
- package/dist/gsapParserAcorn.js.map +1 -1
- package/dist/gsapParserExports.js +278 -20
- package/dist/gsapParserExports.js.map +1 -1
- package/dist/gsapWriterAcorn.js +166 -29
- package/dist/gsapWriterAcorn.js.map +1 -1
- package/dist/index.d.ts +61 -1
- package/dist/index.js +349 -20
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.d.ts
CHANGED
|
@@ -46,6 +46,66 @@ interface CompositionMetadata {
|
|
|
46
46
|
declare function extractCompositionMetadata(html: string): CompositionMetadata;
|
|
47
47
|
declare function validateCompositionHtml(html: string): ValidationResult;
|
|
48
48
|
|
|
49
|
+
/**
|
|
50
|
+
* Shared "is this `outputResolution` preset compatible with the composition?"
|
|
51
|
+
* check.
|
|
52
|
+
*
|
|
53
|
+
* The `--resolution` render flag (a `CanvasResolution` preset) is chosen
|
|
54
|
+
* independently of the composition it renders, so a portrait composition
|
|
55
|
+
* (1080×1920) rendered with `--resolution landscape` (1920×1080) is a common
|
|
56
|
+
* mistake — especially from AI agents that pick a preset by habit rather than
|
|
57
|
+
* by inspecting the composition. Historically this surfaced as a cryptic
|
|
58
|
+
* `Error` thrown deep inside the render compiler (`resolveDeviceScaleFactor`
|
|
59
|
+
* in `@hyperframes/producer`), after the browser and ffmpeg had already spun
|
|
60
|
+
* up, with a message that named the mismatch but not the fix.
|
|
61
|
+
*
|
|
62
|
+
* This module gives every consumer (the render pre-flight in the CLI, the
|
|
63
|
+
* producer's `resolveDeviceScaleFactor`, and any future lint rule) a single,
|
|
64
|
+
* dependency-free definition of "is this preset usable for this composition"
|
|
65
|
+
* — including a suggested preset when there's an unambiguously-correct swap —
|
|
66
|
+
* so the same check can run *before* a render is attempted (loud, actionable,
|
|
67
|
+
* cheap) and again as defense-in-depth inside the pipeline.
|
|
68
|
+
*
|
|
69
|
+
* It lives in `@hyperframes/parsers` (rather than `@hyperframes/core`) because
|
|
70
|
+
* both `core`/`producer` and `lint` may need it, and `lint` cannot depend on
|
|
71
|
+
* `core`. The geometry it needs (`CANVAS_DIMENSIONS`) already lives here.
|
|
72
|
+
*/
|
|
73
|
+
|
|
74
|
+
type OutputResolutionIssueKind = "hdr-incompatible" | "alpha-incompatible" | "aspect-mismatch" | "downsampling" | "non-integer-scale";
|
|
75
|
+
interface OutputResolutionCompatibility {
|
|
76
|
+
ok: boolean;
|
|
77
|
+
/** Present when `ok` is false. */
|
|
78
|
+
kind?: OutputResolutionIssueKind;
|
|
79
|
+
/** Human-readable, actionable message suitable for direct display. */
|
|
80
|
+
message?: string;
|
|
81
|
+
/**
|
|
82
|
+
* A preset whose orientation/aspect ratio matches the composition, when one
|
|
83
|
+
* exists and is an unambiguous swap for the user's intent. Present only for
|
|
84
|
+
* `aspect-mismatch`. Consumers may surface this as a suggestion ("did you
|
|
85
|
+
* mean `--resolution portrait`?"); it is intentionally *not* auto-applied —
|
|
86
|
+
* silently swapping a user-supplied flag changes their stated intent.
|
|
87
|
+
*/
|
|
88
|
+
suggestedResolution?: CanvasResolution;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Check whether rendering a composition of the given dimensions with the given
|
|
92
|
+
* `outputResolution` preset (and alpha/HDR modes) is supported.
|
|
93
|
+
*
|
|
94
|
+
* Pure and dependency-free — the single source of truth for the constraints
|
|
95
|
+
* `resolveDeviceScaleFactor` enforces, so the CLI can run the exact same check
|
|
96
|
+
* as a pre-flight before any browser/ffmpeg work.
|
|
97
|
+
*
|
|
98
|
+
* @param outputResolution The chosen preset, or `undefined` when the render
|
|
99
|
+
* uses the composition's native dimensions (always compatible).
|
|
100
|
+
*/
|
|
101
|
+
declare function checkOutputResolutionCompatibility(input: {
|
|
102
|
+
compositionWidth: number;
|
|
103
|
+
compositionHeight: number;
|
|
104
|
+
outputResolution: CanvasResolution | undefined;
|
|
105
|
+
alphaRequested?: boolean;
|
|
106
|
+
hdrRequested?: boolean;
|
|
107
|
+
}): OutputResolutionCompatibility;
|
|
108
|
+
|
|
49
109
|
/**
|
|
50
110
|
* Rewrite `script` so top-level helper calls / loops that build the timeline
|
|
51
111
|
* become explicit literal tweens. Returns the original script unchanged when
|
|
@@ -55,4 +115,4 @@ declare function unrollComputedTimeline(script: string): string;
|
|
|
55
115
|
|
|
56
116
|
declare function queryByAttr(root: ParentNode, attr: string, value: string, tag?: string): Element | null;
|
|
57
117
|
|
|
58
|
-
export { CanvasResolution, CompositionHtmlParseError, type CompositionMetadata, CompositionVariable, Keyframe, type ParsedHtml, StageZoomKeyframe, TimelineElement, ValidationResult, addElementToHtml, extractCompositionMetadata, parseHtml, queryByAttr, removeElementFromHtml, unrollComputedTimeline, updateElementInHtml, validateCompositionHtml };
|
|
118
|
+
export { CanvasResolution, CompositionHtmlParseError, type CompositionMetadata, CompositionVariable, Keyframe, type OutputResolutionCompatibility, type OutputResolutionIssueKind, type ParsedHtml, StageZoomKeyframe, TimelineElement, ValidationResult, addElementToHtml, checkOutputResolutionCompatibility, extractCompositionMetadata, parseHtml, queryByAttr, removeElementFromHtml, unrollComputedTimeline, updateElementInHtml, validateCompositionHtml };
|
package/dist/index.js
CHANGED
|
@@ -850,6 +850,49 @@ var SCOPE_NODE_TYPES = /* @__PURE__ */ new Set([
|
|
|
850
850
|
"FunctionExpression",
|
|
851
851
|
"ArrowFunctionExpression"
|
|
852
852
|
]);
|
|
853
|
+
var CONST_NODES = /* @__PURE__ */ Symbol("hf.constNodes");
|
|
854
|
+
function constNodesOf(scope) {
|
|
855
|
+
return scope[CONST_NODES];
|
|
856
|
+
}
|
|
857
|
+
var MATH_FNS = /* @__PURE__ */ new Set(["min", "max", "round", "floor", "ceil", "abs", "sqrt", "sign", "trunc"]);
|
|
858
|
+
var MATH_CONSTS = { PI: Math.PI, E: Math.E, SQRT2: Math.SQRT2 };
|
|
859
|
+
function resolveMemberNode(node, scope) {
|
|
860
|
+
if (node.object?.type === "Identifier" && node.object.name === "Math") {
|
|
861
|
+
const key = node.property?.name;
|
|
862
|
+
return typeof key === "string" ? MATH_CONSTS[key] : void 0;
|
|
863
|
+
}
|
|
864
|
+
const objNode = resolveConstNode(node.object, scope);
|
|
865
|
+
if (!objNode) return void 0;
|
|
866
|
+
let valueNode;
|
|
867
|
+
if (node.computed) {
|
|
868
|
+
const idx = resolveNode(node.property, scope);
|
|
869
|
+
if (objNode.type === "ArrayExpression" && typeof idx === "number") {
|
|
870
|
+
valueNode = objNode.elements?.[idx];
|
|
871
|
+
} else if (objNode.type === "ObjectExpression" && (typeof idx === "string" || typeof idx === "number")) {
|
|
872
|
+
valueNode = findPropertyNode(objNode, String(idx));
|
|
873
|
+
}
|
|
874
|
+
} else if (objNode.type === "ObjectExpression") {
|
|
875
|
+
valueNode = findPropertyNode(objNode, node.property?.name ?? node.property?.value);
|
|
876
|
+
}
|
|
877
|
+
return valueNode ? resolveNode(valueNode, scope) : void 0;
|
|
878
|
+
}
|
|
879
|
+
function resolveConstMember(objNode, node, scope) {
|
|
880
|
+
if (!node.computed) {
|
|
881
|
+
return objNode.type === "ObjectExpression" ? findPropertyNode(objNode, node.property?.name ?? node.property?.value) : void 0;
|
|
882
|
+
}
|
|
883
|
+
const idx = resolveNode(node.property, scope);
|
|
884
|
+
if (objNode.type === "ArrayExpression" && typeof idx === "number") return objNode.elements?.[idx];
|
|
885
|
+
if (objNode.type === "ObjectExpression") return findPropertyNode(objNode, String(idx));
|
|
886
|
+
return void 0;
|
|
887
|
+
}
|
|
888
|
+
function resolveConstNode(node, scope) {
|
|
889
|
+
if (!node) return void 0;
|
|
890
|
+
if (node.type === "ArrayExpression" || node.type === "ObjectExpression") return node;
|
|
891
|
+
if (node.type === "Identifier") return constNodesOf(scope)?.get(node.name);
|
|
892
|
+
if (node.type !== "MemberExpression") return void 0;
|
|
893
|
+
const objNode = resolveConstNode(node.object, scope);
|
|
894
|
+
return objNode ? resolveConstMember(objNode, node, scope) : void 0;
|
|
895
|
+
}
|
|
853
896
|
function resolveNode(node, scope) {
|
|
854
897
|
if (!node) return void 0;
|
|
855
898
|
if (node.type === "NumericLiteral" || node.type === "Literal" && typeof node.value === "number")
|
|
@@ -886,6 +929,15 @@ function resolveNode(node, scope) {
|
|
|
886
929
|
if (node.type === "TemplateLiteral" && node.expressions?.length === 0) {
|
|
887
930
|
return node.quasis?.[0]?.value?.cooked ?? void 0;
|
|
888
931
|
}
|
|
932
|
+
if (node.type === "MemberExpression") {
|
|
933
|
+
return resolveMemberNode(node, scope);
|
|
934
|
+
}
|
|
935
|
+
if (node.type === "CallExpression" && node.callee?.type === "MemberExpression" && node.callee.object?.type === "Identifier" && node.callee.object.name === "Math" && MATH_FNS.has(node.callee.property?.name)) {
|
|
936
|
+
const args = (node.arguments ?? []).map((a) => resolveNode(a, scope));
|
|
937
|
+
if (args.every((a) => typeof a === "number")) {
|
|
938
|
+
return Math[node.callee.property.name](...args);
|
|
939
|
+
}
|
|
940
|
+
}
|
|
889
941
|
return void 0;
|
|
890
942
|
}
|
|
891
943
|
function extractLiteralValue(node, scope) {
|
|
@@ -943,14 +995,19 @@ function resolveCollectionSelector(node, ancestors, scope, bindings) {
|
|
|
943
995
|
}
|
|
944
996
|
function collectScopeBindings(ast) {
|
|
945
997
|
const bindings = /* @__PURE__ */ new Map();
|
|
998
|
+
const constNodes = /* @__PURE__ */ new Map();
|
|
999
|
+
Object.defineProperty(bindings, CONST_NODES, { value: constNodes, enumerable: false });
|
|
946
1000
|
acornWalk.simple(ast, {
|
|
947
1001
|
VariableDeclarator(node) {
|
|
948
1002
|
const name = node.id?.name;
|
|
949
1003
|
const init = node.init;
|
|
950
|
-
if (name
|
|
951
|
-
|
|
952
|
-
|
|
1004
|
+
if (!name || !init) return;
|
|
1005
|
+
if (init.type === "ArrayExpression" || init.type === "ObjectExpression") {
|
|
1006
|
+
constNodes.set(name, init);
|
|
1007
|
+
return;
|
|
953
1008
|
}
|
|
1009
|
+
const val = resolveNode(init, bindings);
|
|
1010
|
+
if (val !== void 0) bindings.set(name, val);
|
|
954
1011
|
}
|
|
955
1012
|
});
|
|
956
1013
|
return bindings;
|
|
@@ -992,6 +1049,25 @@ function collectTargetBindings(ast, scope) {
|
|
|
992
1049
|
}
|
|
993
1050
|
}
|
|
994
1051
|
});
|
|
1052
|
+
const COLLECTION_ALIAS_METHODS = /* @__PURE__ */ new Set(["slice", "filter", "concat", "reverse"]);
|
|
1053
|
+
acornWalk.ancestor(ast, {
|
|
1054
|
+
// fallow-ignore-next-line complexity
|
|
1055
|
+
VariableDeclarator(node, _, ancestors) {
|
|
1056
|
+
const name = node.id?.name;
|
|
1057
|
+
const init = node.init;
|
|
1058
|
+
if (!name || !init) return;
|
|
1059
|
+
let sourceVar;
|
|
1060
|
+
if (init.type === "MemberExpression" && init.object?.type === "Identifier") {
|
|
1061
|
+
sourceVar = init.object.name;
|
|
1062
|
+
} else if (init.type === "CallExpression" && init.callee?.type === "MemberExpression" && init.callee.object?.type === "Identifier" && init.callee.property?.type === "Identifier" && COLLECTION_ALIAS_METHODS.has(init.callee.property.name)) {
|
|
1063
|
+
sourceVar = init.callee.object.name;
|
|
1064
|
+
}
|
|
1065
|
+
if (!sourceVar) return;
|
|
1066
|
+
const selector = lookupBindingFromAncestors(sourceVar, ancestors, bindings);
|
|
1067
|
+
if (selector)
|
|
1068
|
+
addBinding(bindings, enclosingScopeNodeFromAncestors(ancestors), name, selector);
|
|
1069
|
+
}
|
|
1070
|
+
});
|
|
995
1071
|
return bindings;
|
|
996
1072
|
}
|
|
997
1073
|
function resolveTargetSelector(node, ancestors, scope, bindings) {
|
|
@@ -1014,6 +1090,32 @@ function resolveTargetSelector(node, ancestors, scope, bindings) {
|
|
|
1014
1090
|
}
|
|
1015
1091
|
return null;
|
|
1016
1092
|
}
|
|
1093
|
+
function describeProxyTarget(targetNode, varsNode, scope) {
|
|
1094
|
+
const objNode = targetNode?.type === "ObjectExpression" ? targetNode : targetNode?.type === "Identifier" ? resolveConstNode(targetNode, scope) : void 0;
|
|
1095
|
+
if (objNode?.type !== "ObjectExpression") return null;
|
|
1096
|
+
const onUpdate = findPropertyNode(varsNode, "onUpdate");
|
|
1097
|
+
const driven = onUpdate ? drivenDomChannel(onUpdate) : void 0;
|
|
1098
|
+
if (driven) return `proxy \u2192 ${driven}`;
|
|
1099
|
+
return "dwell/hold";
|
|
1100
|
+
}
|
|
1101
|
+
function isStyleAssignmentTarget(left) {
|
|
1102
|
+
return left?.type === "MemberExpression" && left.object?.type === "MemberExpression" && left.object.property?.name === "style" && !!left.property?.name;
|
|
1103
|
+
}
|
|
1104
|
+
function drivenDomChannel(fnNode) {
|
|
1105
|
+
let found;
|
|
1106
|
+
acornWalk.simple(fnNode, {
|
|
1107
|
+
CallExpression(node) {
|
|
1108
|
+
if (node.callee?.type === "MemberExpression" && node.callee.property?.name === "setAttribute" && typeof node.arguments?.[0]?.value === "string") {
|
|
1109
|
+
found ??= node.arguments[0].value;
|
|
1110
|
+
}
|
|
1111
|
+
},
|
|
1112
|
+
AssignmentExpression(node) {
|
|
1113
|
+
const left = node.left;
|
|
1114
|
+
if (isStyleAssignmentTarget(left)) found ??= `style.${left.property.name}`;
|
|
1115
|
+
}
|
|
1116
|
+
});
|
|
1117
|
+
return found;
|
|
1118
|
+
}
|
|
1017
1119
|
function isObjectProperty(prop) {
|
|
1018
1120
|
return prop?.type === "ObjectProperty" || prop?.type === "Property";
|
|
1019
1121
|
}
|
|
@@ -1482,8 +1584,13 @@ function tweenCallToAnimation(call, scope, source) {
|
|
|
1482
1584
|
if (duration === void 0 && keyframesData) {
|
|
1483
1585
|
duration = computeKeyframesTotalDuration(call.varsArg, scope, source);
|
|
1484
1586
|
}
|
|
1587
|
+
let selector = call.selector;
|
|
1588
|
+
if (selector === "__unresolved__") {
|
|
1589
|
+
const proxyLabel = describeProxyTarget(call.node.arguments?.[0], call.varsArg, scope);
|
|
1590
|
+
if (proxyLabel) selector = proxyLabel;
|
|
1591
|
+
}
|
|
1485
1592
|
const anim = {
|
|
1486
|
-
targetSelector:
|
|
1593
|
+
targetSelector: selector,
|
|
1487
1594
|
method: call.method,
|
|
1488
1595
|
position,
|
|
1489
1596
|
properties,
|
|
@@ -1506,11 +1613,52 @@ function tweenCallToAnimation(call, scope, source) {
|
|
|
1506
1613
|
if (keyframesData) anim.keyframes = keyframesData;
|
|
1507
1614
|
if (motionPathResult) anim.arcPath = motionPathResult.arcPath;
|
|
1508
1615
|
if (hasUnresolvedKeyframes) anim.hasUnresolvedKeyframes = true;
|
|
1509
|
-
if (
|
|
1616
|
+
if (selector === "__unresolved__") anim.hasUnresolvedSelector = true;
|
|
1510
1617
|
const provenance = readProvenance(call.node);
|
|
1511
1618
|
if (provenance) anim.provenance = provenance;
|
|
1512
1619
|
return anim;
|
|
1513
1620
|
}
|
|
1621
|
+
function staggerAmount(raw) {
|
|
1622
|
+
if (typeof raw === "number") return raw;
|
|
1623
|
+
if (typeof raw !== "string") return void 0;
|
|
1624
|
+
const src = raw.startsWith("__raw:") ? raw.slice(6) : raw;
|
|
1625
|
+
const m = /(?:each\s*:\s*)?(-?\d+(?:\.\d+)?)/.exec(src);
|
|
1626
|
+
if (!m) return void 0;
|
|
1627
|
+
const n = Number.parseFloat(m[1]);
|
|
1628
|
+
return Number.isFinite(n) ? n : void 0;
|
|
1629
|
+
}
|
|
1630
|
+
function restValue(prop) {
|
|
1631
|
+
return prop === "opacity" || prop.startsWith("scale") ? 1 : 0;
|
|
1632
|
+
}
|
|
1633
|
+
function staggeredKeyframes(anim, each) {
|
|
1634
|
+
const vars = { ...anim.properties };
|
|
1635
|
+
let from;
|
|
1636
|
+
let to;
|
|
1637
|
+
if (anim.method === "fromTo") {
|
|
1638
|
+
from = { ...anim.fromProperties ?? {} };
|
|
1639
|
+
to = vars;
|
|
1640
|
+
} else if (anim.method === "from") {
|
|
1641
|
+
from = vars;
|
|
1642
|
+
to = {};
|
|
1643
|
+
for (const k of Object.keys(vars)) to[k] = restValue(k);
|
|
1644
|
+
} else {
|
|
1645
|
+
from = { ...anim.fromProperties ?? {} };
|
|
1646
|
+
for (const k of Object.keys(vars)) if (from[k] === void 0) from[k] = restValue(k);
|
|
1647
|
+
to = vars;
|
|
1648
|
+
}
|
|
1649
|
+
return [
|
|
1650
|
+
{ percentage: 0, properties: { ...from, stagger: each } },
|
|
1651
|
+
{ percentage: 100, properties: { ...to, stagger: each } }
|
|
1652
|
+
];
|
|
1653
|
+
}
|
|
1654
|
+
function annotateStaggeredCollections(anims) {
|
|
1655
|
+
for (const anim of anims) {
|
|
1656
|
+
if (anim.keyframes || anim.arcPath) continue;
|
|
1657
|
+
const each = staggerAmount(anim.extras?.stagger);
|
|
1658
|
+
if (each === void 0) continue;
|
|
1659
|
+
anim.keyframes = { format: "percentage", keyframes: staggeredKeyframes(anim, each) };
|
|
1660
|
+
}
|
|
1661
|
+
}
|
|
1514
1662
|
var GSAP_DEFAULT_DURATION = 0.5;
|
|
1515
1663
|
function resolvePositionString(pos, cursor, prevStart) {
|
|
1516
1664
|
const trimmed = pos.trim();
|
|
@@ -1536,6 +1684,55 @@ function resolvePositionString(pos, cursor, prevStart) {
|
|
|
1536
1684
|
const n = Number.parseFloat(trimmed);
|
|
1537
1685
|
return Number.isFinite(n) ? n : null;
|
|
1538
1686
|
}
|
|
1687
|
+
function collectGsapSetStates(ast, scope, bindings, source) {
|
|
1688
|
+
const states = /* @__PURE__ */ new Map();
|
|
1689
|
+
acornWalk.ancestor(ast, {
|
|
1690
|
+
// fallow-ignore-next-line complexity
|
|
1691
|
+
CallExpression(node, _, ancestors) {
|
|
1692
|
+
const callee = node.callee;
|
|
1693
|
+
if (callee?.type !== "MemberExpression" || callee.object?.name !== "gsap" || callee.property?.name !== "set")
|
|
1694
|
+
return;
|
|
1695
|
+
const selector = resolveTargetSelector(node.arguments?.[0], ancestors, scope, bindings);
|
|
1696
|
+
if (!selector) return;
|
|
1697
|
+
const rec = objectExpressionToRecord(node.arguments?.[1], scope, source);
|
|
1698
|
+
const props = states.get(selector) ?? {};
|
|
1699
|
+
for (const [k, v] of Object.entries(rec)) {
|
|
1700
|
+
if (typeof v === "number" || typeof v === "string") props[k] = v;
|
|
1701
|
+
}
|
|
1702
|
+
states.set(selector, props);
|
|
1703
|
+
}
|
|
1704
|
+
});
|
|
1705
|
+
return states;
|
|
1706
|
+
}
|
|
1707
|
+
function mergeProps(target, props) {
|
|
1708
|
+
for (const [k, v] of Object.entries(props)) target[k] = v;
|
|
1709
|
+
return target;
|
|
1710
|
+
}
|
|
1711
|
+
function seedFromPreState(anim, cur) {
|
|
1712
|
+
const from = { ...anim.fromProperties ?? {} };
|
|
1713
|
+
let seeded = false;
|
|
1714
|
+
for (const prop of Object.keys(anim.properties)) {
|
|
1715
|
+
if (from[prop] === void 0 && cur[prop] !== void 0) {
|
|
1716
|
+
from[prop] = cur[prop];
|
|
1717
|
+
seeded = true;
|
|
1718
|
+
}
|
|
1719
|
+
}
|
|
1720
|
+
if (seeded) anim.fromProperties = from;
|
|
1721
|
+
}
|
|
1722
|
+
function seedSetStates(anims, initial) {
|
|
1723
|
+
const state = /* @__PURE__ */ new Map();
|
|
1724
|
+
for (const [sel, props] of initial) state.set(sel, { ...props });
|
|
1725
|
+
for (const anim of anims) {
|
|
1726
|
+
const sel = anim.targetSelector;
|
|
1727
|
+
if (anim.method === "set") {
|
|
1728
|
+
state.set(sel, mergeProps(state.get(sel) ?? {}, anim.properties));
|
|
1729
|
+
continue;
|
|
1730
|
+
}
|
|
1731
|
+
const cur = state.get(sel);
|
|
1732
|
+
if (anim.method === "to" && cur) seedFromPreState(anim, cur);
|
|
1733
|
+
state.set(sel, mergeProps(state.get(sel) ?? {}, anim.properties));
|
|
1734
|
+
}
|
|
1735
|
+
}
|
|
1539
1736
|
function applyTimelineDefaults(anims, defaults) {
|
|
1540
1737
|
if (!defaults) return;
|
|
1541
1738
|
for (const anim of anims) {
|
|
@@ -1548,31 +1745,89 @@ function applyTimelineDefaults(anims, defaults) {
|
|
|
1548
1745
|
}
|
|
1549
1746
|
}
|
|
1550
1747
|
}
|
|
1551
|
-
function
|
|
1748
|
+
function resolveLabelPosition(pos, labels, cursor) {
|
|
1749
|
+
const m = /^([A-Za-z_$][\w$]*)\s*(?:([+-])=\s*([\d.]+))?$/.exec(pos.trim());
|
|
1750
|
+
if (!m) return null;
|
|
1751
|
+
const name = m[1];
|
|
1752
|
+
let base = labels.get(name);
|
|
1753
|
+
if (base === void 0) {
|
|
1754
|
+
base = cursor;
|
|
1755
|
+
labels.set(name, base);
|
|
1756
|
+
}
|
|
1757
|
+
if (m[2] && m[3]) {
|
|
1758
|
+
const n = Number.parseFloat(m[3]);
|
|
1759
|
+
if (Number.isFinite(n)) return m[2] === "+" ? base + n : base - n;
|
|
1760
|
+
}
|
|
1761
|
+
return base;
|
|
1762
|
+
}
|
|
1763
|
+
function resolveAnimStart(anim, cursor, prevStart, labels) {
|
|
1764
|
+
if (anim.implicitPosition) return cursor;
|
|
1765
|
+
if (typeof anim.position === "number") return anim.position;
|
|
1766
|
+
if (typeof anim.position === "string") {
|
|
1767
|
+
return resolveLabelPosition(anim.position, labels, cursor) ?? resolvePositionString(anim.position, cursor, prevStart);
|
|
1768
|
+
}
|
|
1769
|
+
return cursor;
|
|
1770
|
+
}
|
|
1771
|
+
function resolveTimelinePositions(anims, labelDefs = []) {
|
|
1552
1772
|
let cursor = 0;
|
|
1553
1773
|
let prevStart = 0;
|
|
1554
|
-
|
|
1774
|
+
const labels = /* @__PURE__ */ new Map();
|
|
1775
|
+
let labelIdx = 0;
|
|
1776
|
+
const sortedLabels = [...labelDefs].sort((a, b) => a.order - b.order);
|
|
1777
|
+
const defineLabel = (def) => {
|
|
1778
|
+
let value;
|
|
1779
|
+
if (typeof def.position === "number") value = def.position;
|
|
1780
|
+
else if (typeof def.position === "string") {
|
|
1781
|
+
value = resolveLabelPosition(def.position, labels, cursor) ?? cursor;
|
|
1782
|
+
} else value = cursor;
|
|
1783
|
+
labels.set(def.name, Math.max(0, value));
|
|
1784
|
+
};
|
|
1785
|
+
anims.forEach((anim, i) => {
|
|
1786
|
+
while (labelIdx < sortedLabels.length && sortedLabels[labelIdx].order <= i) {
|
|
1787
|
+
defineLabel(sortedLabels[labelIdx]);
|
|
1788
|
+
labelIdx++;
|
|
1789
|
+
}
|
|
1555
1790
|
if (anim.method === "set" && anim.global) {
|
|
1556
1791
|
anim.resolvedStart = 0;
|
|
1557
|
-
|
|
1792
|
+
return;
|
|
1558
1793
|
}
|
|
1559
1794
|
const duration = anim.method === "set" ? 0 : anim.duration ?? GSAP_DEFAULT_DURATION;
|
|
1560
|
-
|
|
1561
|
-
if (anim.implicitPosition) {
|
|
1562
|
-
start = cursor;
|
|
1563
|
-
} else if (typeof anim.position === "number") {
|
|
1564
|
-
start = anim.position;
|
|
1565
|
-
} else if (typeof anim.position === "string") {
|
|
1566
|
-
start = resolvePositionString(anim.position, cursor, prevStart);
|
|
1567
|
-
} else {
|
|
1568
|
-
start = cursor;
|
|
1569
|
-
}
|
|
1795
|
+
const start = resolveAnimStart(anim, cursor, prevStart, labels);
|
|
1570
1796
|
if (start != null) {
|
|
1571
1797
|
anim.resolvedStart = Math.max(0, start);
|
|
1572
1798
|
prevStart = anim.resolvedStart;
|
|
1573
1799
|
cursor = Math.max(cursor, anim.resolvedStart + duration);
|
|
1574
1800
|
}
|
|
1575
|
-
}
|
|
1801
|
+
});
|
|
1802
|
+
while (labelIdx < sortedLabels.length) defineLabel(sortedLabels[labelIdx++]);
|
|
1803
|
+
}
|
|
1804
|
+
function collectAddLabelDefs(ast, ref, scope, sortedCalls) {
|
|
1805
|
+
const callLocs = sortedCalls.map((c) => c.node.callee?.property?.loc?.start);
|
|
1806
|
+
const defs = [];
|
|
1807
|
+
acornWalk.simple(ast, {
|
|
1808
|
+
// fallow-ignore-next-line complexity
|
|
1809
|
+
CallExpression(node) {
|
|
1810
|
+
const callee = node.callee;
|
|
1811
|
+
const objMatches = ref.kind === "identifier" ? callee.object?.type === "Identifier" && callee.object.name === ref.name : sameMemberAccess(callee.object, ref.node);
|
|
1812
|
+
if (callee?.type !== "MemberExpression" || !objMatches || callee.property?.name !== "addLabel")
|
|
1813
|
+
return;
|
|
1814
|
+
const nameNode = node.arguments?.[0];
|
|
1815
|
+
const name = typeof nameNode?.value === "string" ? nameNode.value : void 0;
|
|
1816
|
+
if (!name) return;
|
|
1817
|
+
const posVal = resolveNode(node.arguments?.[1], scope);
|
|
1818
|
+
const position = typeof posVal === "number" || typeof posVal === "string" ? posVal : void 0;
|
|
1819
|
+
const labelLoc = callee.property?.loc?.start;
|
|
1820
|
+
let order = sortedCalls.length;
|
|
1821
|
+
if (labelLoc) {
|
|
1822
|
+
order = callLocs.findIndex(
|
|
1823
|
+
(l) => l && (l.line > labelLoc.line || l.line === labelLoc.line && l.column > labelLoc.column)
|
|
1824
|
+
);
|
|
1825
|
+
if (order === -1) order = sortedCalls.length;
|
|
1826
|
+
}
|
|
1827
|
+
defs.push({ name, position, order });
|
|
1828
|
+
}
|
|
1829
|
+
});
|
|
1830
|
+
return defs;
|
|
1576
1831
|
}
|
|
1577
1832
|
function compareByLoc(a, b) {
|
|
1578
1833
|
const aLoc = a.node.callee?.property?.loc?.start;
|
|
@@ -1653,7 +1908,10 @@ function parseGsapScriptAcorn(script) {
|
|
|
1653
1908
|
sortBySourcePosition(calls);
|
|
1654
1909
|
const rawAnims = calls.map((call) => tweenCallToAnimation(call, scope, script));
|
|
1655
1910
|
applyTimelineDefaults(rawAnims, detection.defaults);
|
|
1656
|
-
|
|
1911
|
+
seedSetStates(rawAnims, collectGsapSetStates(ast, scope, targetBindings, script));
|
|
1912
|
+
const labelDefs = collectAddLabelDefs(ast, ref, scope, calls);
|
|
1913
|
+
resolveTimelinePositions(rawAnims, labelDefs);
|
|
1914
|
+
annotateStaggeredCollections(rawAnims);
|
|
1657
1915
|
const animations = assignStableIds(rawAnims);
|
|
1658
1916
|
const declPattern = ref.kind === "identifier" ? `(?:const|let|var)\\s+${timelineVar}\\s*=\\s*gsap\\.timeline\\s*\\([^)]*\\)\\s*;?` : `${escapeRegExp(timelineVar)}\\s*=\\s*gsap\\.timeline\\s*\\([^)]*\\)\\s*;?`;
|
|
1659
1917
|
const timelineMatch = script.match(new RegExp(`^[\\s\\S]*?${declPattern}`));
|
|
@@ -2491,6 +2749,76 @@ function checkSubCompositionUsability(html, parseHtml2) {
|
|
|
2491
2749
|
return { ok: true };
|
|
2492
2750
|
}
|
|
2493
2751
|
|
|
2752
|
+
// src/outputResolutionCompatibility.ts
|
|
2753
|
+
var OK = { ok: true };
|
|
2754
|
+
function suggestMatchingPreset(compositionWidth, compositionHeight, chosen) {
|
|
2755
|
+
const aspectMatches = VALID_CANVAS_RESOLUTIONS.filter((preset) => {
|
|
2756
|
+
const { width, height } = CANVAS_DIMENSIONS[preset];
|
|
2757
|
+
return width * compositionHeight === height * compositionWidth;
|
|
2758
|
+
});
|
|
2759
|
+
if (aspectMatches.length === 0) return void 0;
|
|
2760
|
+
const chosenIs4k = chosen.endsWith("-4k");
|
|
2761
|
+
const sameTier = aspectMatches.find((preset) => preset.endsWith("-4k") === chosenIs4k);
|
|
2762
|
+
return sameTier ?? aspectMatches[0];
|
|
2763
|
+
}
|
|
2764
|
+
function describeOrientation(width, height) {
|
|
2765
|
+
if (width > height) return "landscape";
|
|
2766
|
+
if (width < height) return "portrait";
|
|
2767
|
+
return "square";
|
|
2768
|
+
}
|
|
2769
|
+
function buildAspectMismatch(compositionWidth, compositionHeight, outputResolution, target) {
|
|
2770
|
+
const suggestedResolution = suggestMatchingPreset(
|
|
2771
|
+
compositionWidth,
|
|
2772
|
+
compositionHeight,
|
|
2773
|
+
outputResolution
|
|
2774
|
+
);
|
|
2775
|
+
const suggestion = suggestedResolution ? ` The composition is ${describeOrientation(compositionWidth, compositionHeight)} \u2014 use --resolution ${suggestedResolution} instead.` : ` Pick a preset whose orientation matches, or omit --resolution to render at the composition's native dimensions.`;
|
|
2776
|
+
return {
|
|
2777
|
+
ok: false,
|
|
2778
|
+
kind: "aspect-mismatch",
|
|
2779
|
+
suggestedResolution,
|
|
2780
|
+
message: `outputResolution ${outputResolution} (${target.width}\xD7${target.height}) does not match the aspect ratio of the composition (${compositionWidth}\xD7${compositionHeight}).` + suggestion
|
|
2781
|
+
};
|
|
2782
|
+
}
|
|
2783
|
+
function checkOutputResolutionCompatibility(input) {
|
|
2784
|
+
const { compositionWidth, compositionHeight, outputResolution } = input;
|
|
2785
|
+
if (!outputResolution) return OK;
|
|
2786
|
+
if (input.hdrRequested) {
|
|
2787
|
+
return {
|
|
2788
|
+
ok: false,
|
|
2789
|
+
kind: "hdr-incompatible",
|
|
2790
|
+
message: `outputResolution cannot be combined with hdrMode='force-hdr'. HDR rendering composites at composition dimensions and does not yet support supersampling. Pick one or render in two passes.`
|
|
2791
|
+
};
|
|
2792
|
+
}
|
|
2793
|
+
if (input.alphaRequested) {
|
|
2794
|
+
return {
|
|
2795
|
+
ok: false,
|
|
2796
|
+
kind: "alpha-incompatible",
|
|
2797
|
+
message: `outputResolution cannot be combined with alpha output (--format webm|mov|png-sequence). The alpha screenshot path does not yet apply deviceScaleFactor and would silently produce composition-resolution frames. Render alpha at composition resolution and upscale separately, or use --format mp4.`
|
|
2798
|
+
};
|
|
2799
|
+
}
|
|
2800
|
+
const target = CANVAS_DIMENSIONS[outputResolution];
|
|
2801
|
+
if (target.width * compositionHeight !== target.height * compositionWidth) {
|
|
2802
|
+
return buildAspectMismatch(compositionWidth, compositionHeight, outputResolution, target);
|
|
2803
|
+
}
|
|
2804
|
+
const widthRatio = target.width / compositionWidth;
|
|
2805
|
+
if (widthRatio < 1) {
|
|
2806
|
+
return {
|
|
2807
|
+
ok: false,
|
|
2808
|
+
kind: "downsampling",
|
|
2809
|
+
message: `outputResolution ${outputResolution} (${target.width}\xD7${target.height}) is smaller than the composition (${compositionWidth}\xD7${compositionHeight}). Downsampling via --resolution is not supported.`
|
|
2810
|
+
};
|
|
2811
|
+
}
|
|
2812
|
+
if (!Number.isInteger(widthRatio)) {
|
|
2813
|
+
return {
|
|
2814
|
+
ok: false,
|
|
2815
|
+
kind: "non-integer-scale",
|
|
2816
|
+
message: `outputResolution ${outputResolution} requires a non-integer device scale factor (${widthRatio}\xD7) to upsample from ${compositionWidth}\xD7${compositionHeight}. Pick a preset that's an integer multiple, or rescale the composition.`
|
|
2817
|
+
};
|
|
2818
|
+
}
|
|
2819
|
+
return OK;
|
|
2820
|
+
}
|
|
2821
|
+
|
|
2494
2822
|
// src/gsapUnroll.ts
|
|
2495
2823
|
import * as acorn2 from "acorn";
|
|
2496
2824
|
import MagicString2 from "magic-string";
|
|
@@ -2718,6 +3046,7 @@ export {
|
|
|
2718
3046
|
TIMELINE_COLORS,
|
|
2719
3047
|
VALID_CANVAS_RESOLUTIONS,
|
|
2720
3048
|
addElementToHtml,
|
|
3049
|
+
checkOutputResolutionCompatibility,
|
|
2721
3050
|
checkSubCompositionUsability,
|
|
2722
3051
|
classifyPropertyGroup,
|
|
2723
3052
|
classifyTweenPropertyGroup,
|