@hyperframes/lint 0.8.111 → 0.8.112
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/browser.js +209 -33
- package/dist/browser.js.map +1 -1
- package/dist/index.js +209 -33
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -10,7 +10,7 @@ var TIMELINE_REGISTRY_OBJECT_LITERAL_PATTERN = /window\.__timelines\s*=\s*\{\s*(
|
|
|
10
10
|
var TIMELINE_REGISTRY_ASSIGN_PATTERN = /window\.__timelines(?:\[[^\]]+\]|\.[A-Za-z_$][\w$]*)\s*=/i;
|
|
11
11
|
var WINDOW_TIMELINE_ASSIGN_PATTERN = /window\.__timelines(?:\[\s*(?:["']([^"']+)["']|[A-Za-z_$][\w$.]*)\s*\]|\.\s*([A-Za-z_$][\w$]*))\s*=\s*([A-Za-z_$][\w$]*)/i;
|
|
12
12
|
var INVALID_SCRIPT_CLOSE_PATTERN = /<script[^>]*>[\s\S]*?<\s*\/\s*script(?!>)/i;
|
|
13
|
-
var TIMELINE_REGISTRY_KEY_PATTERN = /window\.__timelines(?:\[\s*["']([^"']+)["']\s*\]|\.\s*([A-Za-z_$][\w$]*))\s
|
|
13
|
+
var TIMELINE_REGISTRY_KEY_PATTERN = /window\.__timelines(?:\[\s*["']([^"']+)["']\s*\]|\.\s*([A-Za-z_$][\w$]*))\s*(?:\?\?|\|\||&&)?=/g;
|
|
14
14
|
var TIMELINE_REGISTRY_OBJECT_OPEN_PATTERN = /window\.__timelines\s*=\s*\{/i;
|
|
15
15
|
var TIMELINE_REGISTRY_OBJECT_ENTRY_PATTERN = /(?:["']([^"']+)["']|([A-Za-z_$][\w$]*))\s*:\s*[A-Za-z_$][\w$]*/g;
|
|
16
16
|
function parseHtmlStructure(source) {
|
|
@@ -114,9 +114,7 @@ function readAttr(tagSource, attr) {
|
|
|
114
114
|
return match?.[1] || null;
|
|
115
115
|
}
|
|
116
116
|
function hasAttrName(tagSource, attr) {
|
|
117
|
-
|
|
118
|
-
const attrs = tagSource.replace(/^<\s*[a-z][\w:-]*/i, "");
|
|
119
|
-
return new RegExp(`(?:^|\\s)${escaped}(?:\\s*=|\\s|/?>)`, "i").test(attrs);
|
|
117
|
+
return readDecodedAttr(tagSource, attr) !== null;
|
|
120
118
|
}
|
|
121
119
|
function isAudibleVideoTag(tagSource) {
|
|
122
120
|
if (hasAttrName(tagSource, "muted")) return false;
|
|
@@ -799,6 +797,34 @@ function repeatedDescendantId(selector) {
|
|
|
799
797
|
}
|
|
800
798
|
return repeated;
|
|
801
799
|
}
|
|
800
|
+
function addUnescapedDigitIds(selector, ids) {
|
|
801
|
+
try {
|
|
802
|
+
selectorParser((root) => {
|
|
803
|
+
root.walkIds((node) => {
|
|
804
|
+
if (/^#\d/.test(node.toString().trim())) ids.add(node.value);
|
|
805
|
+
});
|
|
806
|
+
}).processSync(selector);
|
|
807
|
+
} catch {
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
var SELECTOR_CALL_PATTERN = /\.(?:querySelector(?:All)?|closest|matches|to|from|fromTo|set)\s*\(\s*(["'`])((?:\\.|(?!\1)[^\\])*)\1/g;
|
|
811
|
+
function digitIdsTargetedBySelectors(styles, scripts) {
|
|
812
|
+
const ids = /* @__PURE__ */ new Set();
|
|
813
|
+
for (const style of styles) {
|
|
814
|
+
try {
|
|
815
|
+
postcss.parse(style.content).walkRules((rule) => {
|
|
816
|
+
for (const selector of rule.selectors) addUnescapedDigitIds(selector, ids);
|
|
817
|
+
});
|
|
818
|
+
} catch {
|
|
819
|
+
}
|
|
820
|
+
}
|
|
821
|
+
for (const script of scripts) {
|
|
822
|
+
for (const match of stripJsComments(script.content).matchAll(SELECTOR_CALL_PATTERN)) {
|
|
823
|
+
addUnescapedDigitIds(match[2] ?? "", ids);
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
return ids;
|
|
827
|
+
}
|
|
802
828
|
function resolvedRuleSelectors(rule) {
|
|
803
829
|
let ancestor = rule.parent;
|
|
804
830
|
while (ancestor && ancestor.type !== "rule") ancestor = ancestor.parent;
|
|
@@ -817,16 +843,20 @@ function resolvedRuleSelectors(rule) {
|
|
|
817
843
|
})
|
|
818
844
|
);
|
|
819
845
|
}
|
|
846
|
+
function rightmostCompoundNodes(selectorNode) {
|
|
847
|
+
const subject = [];
|
|
848
|
+
selectorNode.each((node) => {
|
|
849
|
+
if (node.type === "combinator") subject.length = 0;
|
|
850
|
+
else subject.push(node);
|
|
851
|
+
});
|
|
852
|
+
return subject;
|
|
853
|
+
}
|
|
820
854
|
function selectorAliasesRuntimeHiddenStyle(selector) {
|
|
821
855
|
let unsafe = false;
|
|
822
856
|
try {
|
|
823
857
|
selectorParser((root) => {
|
|
824
858
|
root.each((selectorNode) => {
|
|
825
|
-
const subject =
|
|
826
|
-
selectorNode.each((node) => {
|
|
827
|
-
if (node.type === "combinator") subject.length = 0;
|
|
828
|
-
else subject.push(node);
|
|
829
|
-
});
|
|
859
|
+
const subject = rightmostCompoundNodes(selectorNode);
|
|
830
860
|
const hostScoped = subject.some(
|
|
831
861
|
(node) => node.type === "attribute" && ["data-composition-src", "data-composition-file"].includes(
|
|
832
862
|
node.attribute.toLowerCase()
|
|
@@ -849,6 +879,57 @@ function selectorAliasesRuntimeHiddenStyle(selector) {
|
|
|
849
879
|
}
|
|
850
880
|
return unsafe;
|
|
851
881
|
}
|
|
882
|
+
var POSITION_PROPERTIES = /* @__PURE__ */ new Set(["left", "top", "right", "bottom", "inset"]);
|
|
883
|
+
function weakIdOf(node) {
|
|
884
|
+
if (node.type === "attribute" && node.attribute.toLowerCase() === "id") {
|
|
885
|
+
return node.operator === "=" && node.value ? node.value : null;
|
|
886
|
+
}
|
|
887
|
+
if (node.type !== "pseudo" || node.value.toLowerCase() !== ":where") return null;
|
|
888
|
+
const inner = node.nodes.flatMap((option) => option.nodes).find((n) => n.type === "id");
|
|
889
|
+
return inner ? inner.value : null;
|
|
890
|
+
}
|
|
891
|
+
function reducedSpecificityId(selector) {
|
|
892
|
+
let matched = null;
|
|
893
|
+
try {
|
|
894
|
+
selectorParser((root) => {
|
|
895
|
+
root.each((selectorNode) => {
|
|
896
|
+
const subject = rightmostCompoundNodes(selectorNode);
|
|
897
|
+
if (subject.some((node) => node.type === "id")) return;
|
|
898
|
+
for (const node of subject) matched = weakIdOf(node) ?? matched;
|
|
899
|
+
});
|
|
900
|
+
}).processSync(selector);
|
|
901
|
+
} catch {
|
|
902
|
+
return null;
|
|
903
|
+
}
|
|
904
|
+
return matched;
|
|
905
|
+
}
|
|
906
|
+
function cssIdSelector(id) {
|
|
907
|
+
return `#${id.replace(
|
|
908
|
+
/[^a-zA-Z0-9_-]|^-?\d/g,
|
|
909
|
+
(match) => Array.from(match, (char) => `\\${char.codePointAt(0)?.toString(16)} `).join("")
|
|
910
|
+
)}`;
|
|
911
|
+
}
|
|
912
|
+
function reducedSpecificityIdFindings(rule, reported) {
|
|
913
|
+
const positionProps = rule.nodes.flatMap(
|
|
914
|
+
(node) => node.type === "decl" && !node.important && POSITION_PROPERTIES.has(node.prop.toLowerCase()) ? [node.prop] : []
|
|
915
|
+
);
|
|
916
|
+
if (positionProps.length === 0) return [];
|
|
917
|
+
const findings = [];
|
|
918
|
+
for (const selector of resolvedRuleSelectors(rule)) {
|
|
919
|
+
const id = reported.has(selector) ? null : reducedSpecificityId(selector);
|
|
920
|
+
if (id === null) continue;
|
|
921
|
+
reported.add(selector);
|
|
922
|
+
findings.push({
|
|
923
|
+
code: "id_override_reduced_specificity",
|
|
924
|
+
severity: "warning",
|
|
925
|
+
message: `Selector "${selector}" sets ${positionProps.join("/")} through [id=...] or :where(#id), which has class-level or zero specificity, so a compound class rule (e.g. ".parent .row") on the same element can silently win over this override.`,
|
|
926
|
+
selector,
|
|
927
|
+
fixHint: `Use \`${cssIdSelector(id)}\` instead; id specificity beats any selector built only from classes.`,
|
|
928
|
+
snippet: truncateSnippet(rule.toString())
|
|
929
|
+
});
|
|
930
|
+
}
|
|
931
|
+
return findings;
|
|
932
|
+
}
|
|
852
933
|
function ruleForcesOpacityZero(rule) {
|
|
853
934
|
let forcesOpacityZero = false;
|
|
854
935
|
rule.walkDecls(/^opacity$/i, (declaration) => {
|
|
@@ -856,6 +937,34 @@ function ruleForcesOpacityZero(rule) {
|
|
|
856
937
|
});
|
|
857
938
|
return forcesOpacityZero;
|
|
858
939
|
}
|
|
940
|
+
var CSS_TRANSITION_PROPERTY_PATTERN = /^(?:-webkit-)?transition(?:-[a-z][a-z-]*)?$/i;
|
|
941
|
+
var TRANSITION_NONE_PROPERTIES = /* @__PURE__ */ new Set(["transition", "transition-property"]);
|
|
942
|
+
var TRANSITION_TIME_PROPERTIES = /* @__PURE__ */ new Set([
|
|
943
|
+
"transition",
|
|
944
|
+
"transition-duration",
|
|
945
|
+
"transition-delay"
|
|
946
|
+
]);
|
|
947
|
+
function hasNonZeroTime(value) {
|
|
948
|
+
const times = value.match(/[+-]?(?:\d*\.)?\d+m?s\b/g) ?? [];
|
|
949
|
+
return value.includes("var(") || times.some((time) => Number.parseFloat(time) !== 0);
|
|
950
|
+
}
|
|
951
|
+
function isSeekUnsafeTransition(declaration) {
|
|
952
|
+
const property = declaration.prop.trim().toLowerCase();
|
|
953
|
+
if (!CSS_TRANSITION_PROPERTY_PATTERN.test(property)) return false;
|
|
954
|
+
const name = property.replace(/^-webkit-/, "");
|
|
955
|
+
const value = declaration.value.trim().toLowerCase();
|
|
956
|
+
if (value === "none" && TRANSITION_NONE_PROPERTIES.has(name)) return false;
|
|
957
|
+
return !TRANSITION_TIME_PROPERTIES.has(name) || hasNonZeroTime(value);
|
|
958
|
+
}
|
|
959
|
+
function cssTransitionFinding(declaration, details) {
|
|
960
|
+
return {
|
|
961
|
+
code: "css_transition_used",
|
|
962
|
+
severity: "warning",
|
|
963
|
+
message: `CSS declaration \`${declaration.prop}: ${declaration.value}\` runs on the browser clock and cannot be seeked deterministically across render workers.`,
|
|
964
|
+
fixHint: "Keep the class or attribute swap for state; put the visual change on the paused GSAP timeline.",
|
|
965
|
+
...details
|
|
966
|
+
};
|
|
967
|
+
}
|
|
859
968
|
function isStudioTimelineElement(tag) {
|
|
860
969
|
if (["script", "style", "link", "meta", "template", "noscript"].includes(tag.name)) {
|
|
861
970
|
return false;
|
|
@@ -974,15 +1083,18 @@ function describeRootDimensionsDrift(source, dataWidth, dataHeight) {
|
|
|
974
1083
|
}
|
|
975
1084
|
var coreRules = [
|
|
976
1085
|
// id_requires_css_escape
|
|
977
|
-
({ tags }) => {
|
|
1086
|
+
({ tags, styles, scripts }) => {
|
|
978
1087
|
const findings = [];
|
|
1088
|
+
let targeted;
|
|
979
1089
|
for (const tag of tags) {
|
|
980
1090
|
const id = readAttr(tag.raw, "id");
|
|
981
1091
|
if (!id || !/^\d/.test(id)) continue;
|
|
1092
|
+
targeted ??= digitIdsTargetedBySelectors(styles, scripts);
|
|
1093
|
+
const used = targeted.has(id);
|
|
982
1094
|
findings.push({
|
|
983
1095
|
code: "id_requires_css_escape",
|
|
984
|
-
severity: "warning",
|
|
985
|
-
message: `id="${id}" starts with a digit, so the common selector \`#${id}\` throws a SyntaxError in querySelector().`,
|
|
1096
|
+
severity: used ? "error" : "warning",
|
|
1097
|
+
message: used ? `id="${id}" starts with a digit, and the selector \`#${id}\` used in this composition is invalid: querySelector and GSAP throw a SyntaxError, and CSS drops the rule.` : `id="${id}" starts with a digit, so the common selector \`#${id}\` throws a SyntaxError in querySelector().`,
|
|
986
1098
|
elementId: id,
|
|
987
1099
|
fixHint: "Rename the id to start with a letter (recommended), or build selectors with `#${CSS.escape(id)}` at runtime.",
|
|
988
1100
|
snippet: truncateSnippet(tag.raw)
|
|
@@ -1126,11 +1238,55 @@ var coreRules = [
|
|
|
1126
1238
|
}
|
|
1127
1239
|
return findings;
|
|
1128
1240
|
},
|
|
1241
|
+
// css_transition_used
|
|
1242
|
+
({ styles, tags }) => {
|
|
1243
|
+
const findings = [];
|
|
1244
|
+
for (const style of styles) {
|
|
1245
|
+
if (!/transition/i.test(style.content)) continue;
|
|
1246
|
+
let root;
|
|
1247
|
+
try {
|
|
1248
|
+
root = postcss.parse(style.content);
|
|
1249
|
+
} catch {
|
|
1250
|
+
continue;
|
|
1251
|
+
}
|
|
1252
|
+
root.walkDecls((declaration) => {
|
|
1253
|
+
if (!isSeekUnsafeTransition(declaration)) return;
|
|
1254
|
+
const selector = declaration.parent?.type === "rule" ? declaration.parent.selector : void 0;
|
|
1255
|
+
findings.push(
|
|
1256
|
+
cssTransitionFinding(declaration, {
|
|
1257
|
+
selector,
|
|
1258
|
+
snippet: truncateSnippet(declaration.toString())
|
|
1259
|
+
})
|
|
1260
|
+
);
|
|
1261
|
+
});
|
|
1262
|
+
}
|
|
1263
|
+
for (const tag of tags) {
|
|
1264
|
+
const inlineStyle = readDecodedAttr(tag.raw, "style");
|
|
1265
|
+
if (!inlineStyle || !/transition/i.test(inlineStyle)) continue;
|
|
1266
|
+
let root;
|
|
1267
|
+
try {
|
|
1268
|
+
root = postcss.parse(inlineStyle);
|
|
1269
|
+
} catch {
|
|
1270
|
+
continue;
|
|
1271
|
+
}
|
|
1272
|
+
root.walkDecls((declaration) => {
|
|
1273
|
+
if (!isSeekUnsafeTransition(declaration)) return;
|
|
1274
|
+
findings.push(
|
|
1275
|
+
cssTransitionFinding(declaration, {
|
|
1276
|
+
elementId: readDecodedAttr(tag.raw, "id") || void 0,
|
|
1277
|
+
snippet: truncateSnippet(tag.raw)
|
|
1278
|
+
})
|
|
1279
|
+
);
|
|
1280
|
+
});
|
|
1281
|
+
}
|
|
1282
|
+
return findings;
|
|
1283
|
+
},
|
|
1129
1284
|
// CSS selector safety
|
|
1130
1285
|
({ styles, locate }) => {
|
|
1131
1286
|
const findings = [];
|
|
1132
1287
|
const reportedRepeatedIds = /* @__PURE__ */ new Set();
|
|
1133
1288
|
const reportedHiddenStyleSelectors = /* @__PURE__ */ new Set();
|
|
1289
|
+
const reportedReducedIdSelectors = /* @__PURE__ */ new Set();
|
|
1134
1290
|
for (const style of styles) {
|
|
1135
1291
|
let root;
|
|
1136
1292
|
try {
|
|
@@ -1149,6 +1305,7 @@ var coreRules = [
|
|
|
1149
1305
|
}
|
|
1150
1306
|
root.walkRules((rule) => {
|
|
1151
1307
|
const forcesOpacityZero = ruleForcesOpacityZero(rule);
|
|
1308
|
+
findings.push(...reducedSpecificityIdFindings(rule, reportedReducedIdSelectors));
|
|
1152
1309
|
for (const selector of resolvedRuleSelectors(rule)) {
|
|
1153
1310
|
const repeatedId = repeatedDescendantId(selector);
|
|
1154
1311
|
if (repeatedId && !reportedRepeatedIds.has(repeatedId)) {
|
|
@@ -2386,6 +2543,7 @@ var HEAVY_OVERLAY_EXEMPT_TAGS = /* @__PURE__ */ new Set([
|
|
|
2386
2543
|
]);
|
|
2387
2544
|
var HEAVY_OVERLAY_CSS_PATTERN = /(?:filter\s*:[^;}]*\bblur\s*\()|(?:clip-path\s*:(?!\s*(?:none|inherit|initial|unset)\b)\s*[^;}]+)|(?:radial-gradient\s*\()/i;
|
|
2388
2545
|
var INLINE_STYLE_DISPLAY_NONE_PATTERN = /(?:^|;)\s*display\s*:\s*none\b/i;
|
|
2546
|
+
var COMPUTED_TIMELINE_REGISTRATION_PATTERN = /window\.__timelines\s*\[(?!\s*["'])(?:[^\r\n[\]]|\[[^\r\n\]]*\])+\]\s*(?:\?\?|\|\||&&)?=/;
|
|
2389
2547
|
function readTagTiming(rawTag) {
|
|
2390
2548
|
return readClipTiming({ getAttribute: (name) => readAttr(rawTag, name) });
|
|
2391
2549
|
}
|
|
@@ -2694,6 +2852,26 @@ function negativeZIndexFinding(match, selector) {
|
|
|
2694
2852
|
snippet: truncateSnippet(match[0])
|
|
2695
2853
|
};
|
|
2696
2854
|
}
|
|
2855
|
+
function hasComputedTimelineRegistration(scripts) {
|
|
2856
|
+
return scripts.some(
|
|
2857
|
+
(script) => COMPUTED_TIMELINE_REGISTRATION_PATTERN.test(stripJsCode(script.content))
|
|
2858
|
+
);
|
|
2859
|
+
}
|
|
2860
|
+
function mountsSource(tag) {
|
|
2861
|
+
return Boolean(
|
|
2862
|
+
readAttr(tag.raw, "data-composition-src") || readAttr(tag.raw, "data-composition-file")
|
|
2863
|
+
);
|
|
2864
|
+
}
|
|
2865
|
+
function missingTimelineFinding(tag, compositionId, isRoot) {
|
|
2866
|
+
return {
|
|
2867
|
+
code: "missing_data_no_timeline",
|
|
2868
|
+
severity: "warning",
|
|
2869
|
+
message: isRoot ? "This composition has no `window.__timelines` registration but is missing `data-no-timeline`. The producer polls for timeline registration for up to 45 seconds before timing out, adding 45 s to every render." : `Composition host "${compositionId}" has neither a matching \`window.__timelines\` registration nor \`data-no-timeline\`. The producer waits up to 45 seconds for every \`data-composition-id\` before rendering.`,
|
|
2870
|
+
elementId: readAttr(tag.raw, "id") || void 0,
|
|
2871
|
+
fixHint: isRoot ? 'Add `data-no-timeline` to the root element to skip the poll: `<div data-composition-id="..." data-no-timeline ...>`.' : "If this is a static section, use a plain `id` instead of `data-composition-id`, or add `data-no-timeline`. Otherwise, register its timeline or mount it with `data-composition-src`.",
|
|
2872
|
+
snippet: truncateSnippet(tag.raw)
|
|
2873
|
+
};
|
|
2874
|
+
}
|
|
2697
2875
|
var compositionRules = [
|
|
2698
2876
|
// duplicate_composition_id catches meta-tag/root collisions that create duplicate composition entries.
|
|
2699
2877
|
({ tags }) => {
|
|
@@ -2962,27 +3140,25 @@ var compositionRules = [
|
|
|
2962
3140
|
return findings;
|
|
2963
3141
|
},
|
|
2964
3142
|
// missing_data_no_timeline
|
|
2965
|
-
// The producer polls window.__timelines[id]
|
|
2966
|
-
//
|
|
2967
|
-
//
|
|
2968
|
-
// data-no-timeline
|
|
2969
|
-
({ rootTag, rootCompositionId, scripts, rawSource, options }) => {
|
|
2970
|
-
if (options.isSubComposition) return [];
|
|
3143
|
+
// The producer polls window.__timelines[id] for every composition id in the
|
|
3144
|
+
// rendered document. A timeline-free root or bare nested composition host
|
|
3145
|
+
// therefore spends the full 45-second readiness budget unless it explicitly
|
|
3146
|
+
// opts out with data-no-timeline.
|
|
3147
|
+
({ rootTag, rootCompositionId, tags, scripts, rawSource, options }) => {
|
|
2971
3148
|
if (!rootCompositionId || !rootTag) return [];
|
|
2972
|
-
const tagNoValues = rootTag.raw.replace(/"[^"]*"|'[^']*'/g, '""');
|
|
2973
|
-
if (/(?:^|\s)data-no-timeline(?=[\s>=/]|$)/i.test(tagNoValues)) return [];
|
|
2974
3149
|
if (/<script\b[^>]*\bsrc\s*=/i.test(rawSource)) return [];
|
|
2975
|
-
|
|
2976
|
-
|
|
2977
|
-
|
|
2978
|
-
|
|
2979
|
-
|
|
2980
|
-
|
|
2981
|
-
|
|
2982
|
-
|
|
2983
|
-
|
|
2984
|
-
|
|
2985
|
-
|
|
3150
|
+
if (hasComputedTimelineRegistration(scripts)) return [];
|
|
3151
|
+
const registeredIds = new Set(
|
|
3152
|
+
scripts.flatMap((script) => extractTimelineRegistryKeys(stripJsComments(script.content)))
|
|
3153
|
+
);
|
|
3154
|
+
return tags.flatMap((tag) => {
|
|
3155
|
+
const compositionId = readDecodedAttr(tag.raw, "data-composition-id");
|
|
3156
|
+
if (!compositionId || registeredIds.has(compositionId)) return [];
|
|
3157
|
+
const isRoot = tag.index === rootTag.index;
|
|
3158
|
+
if (isRoot ? options.isSubComposition : isInsideInertTemplate(tag, tags)) return [];
|
|
3159
|
+
if (readDecodedAttr(tag.raw, "data-no-timeline") !== null || mountsSource(tag)) return [];
|
|
3160
|
+
return [missingTimelineFinding(tag, compositionId, isRoot)];
|
|
3161
|
+
});
|
|
2986
3162
|
},
|
|
2987
3163
|
// negative_z_index
|
|
2988
3164
|
// An element at a negative z-index is silently absent from both `snapshot`
|
|
@@ -4285,7 +4461,7 @@ ${right.raw}`)
|
|
|
4285
4461
|
severity: "warning",
|
|
4286
4462
|
message: `${fromToWindows.length} tl.fromTo() calls target "${selector}" with no stable baseline. The last-authored fromTo "from" values become the element's resting state for any seek before the first tween actually runs, because GSAP applies fromTo from-values at authoring time (immediateRender), not at tween position.`,
|
|
4287
4463
|
selector,
|
|
4288
|
-
fixHint: `Add \`immediateRender: false\` to
|
|
4464
|
+
fixHint: `Add \`immediateRender: false\` to every fromTo except the earliest-positioned one (at ${firstFromToPosition}s), so pre-first-tween seeks keep its "from" values. A hiding \`tl.set(..., 0)\` baseline trips gsap_timeline_set_initial_hide, and a \`gsap.set\` authored before the fromTo calls is overwritten by them.`,
|
|
4289
4465
|
snippet: truncateSnippet(fromToWindows.map((win) => win.raw).join("\n"))
|
|
4290
4466
|
});
|
|
4291
4467
|
}
|
|
@@ -4418,7 +4594,7 @@ ${right.raw}`)
|
|
|
4418
4594
|
const cssFromScale = scaleProps.length > 0 ? matchCssTransform(sel, cssScaleSelectors) : void 0;
|
|
4419
4595
|
if (!cssFromTranslate && !cssFromScale) continue;
|
|
4420
4596
|
const existing = conflicts.get(sel) ?? {
|
|
4421
|
-
cssTransform: [cssFromTranslate, cssFromScale].filter(Boolean).join(" "),
|
|
4597
|
+
cssTransform: [...new Set([cssFromTranslate, cssFromScale].filter(Boolean))].join(" "),
|
|
4422
4598
|
props: /* @__PURE__ */ new Set(),
|
|
4423
4599
|
raw: call.raw
|
|
4424
4600
|
};
|