@hyperframes/lint 0.8.37 → 0.8.39
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 +16 -93
- package/dist/browser.js.map +1 -1
- package/dist/index.js +16 -93
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/browser.js
CHANGED
|
@@ -14,12 +14,24 @@ function parseHtmlStructure(source) {
|
|
|
14
14
|
const blocks = { script: [], style: [] };
|
|
15
15
|
const openTagsByName = /* @__PURE__ */ new Map();
|
|
16
16
|
const openBlocks = [];
|
|
17
|
+
let explicitOpenTag = null;
|
|
17
18
|
const parser = new Parser(
|
|
18
19
|
{
|
|
19
|
-
|
|
20
|
-
|
|
20
|
+
onopentagname(name) {
|
|
21
|
+
let tokenStart = parser.endIndex - 1;
|
|
22
|
+
while (tokenStart >= parser.startIndex && !/[\t\n\f\r />]/.test(source.charAt(tokenStart))) {
|
|
23
|
+
tokenStart -= 1;
|
|
24
|
+
}
|
|
25
|
+
const index = source.indexOf("<", tokenStart + 1);
|
|
26
|
+
explicitOpenTag = index >= 0 && index < parser.endIndex && source.slice(index + 1, parser.endIndex).toLowerCase() === name ? { index, nameEnd: parser.endIndex } : null;
|
|
27
|
+
},
|
|
28
|
+
onopentag(name, _attrs, isImplied) {
|
|
29
|
+
const origin = !isImplied ? explicitOpenTag : null;
|
|
30
|
+
const index = origin?.index ?? parser.startIndex;
|
|
31
|
+
explicitOpenTag = null;
|
|
21
32
|
const raw = source.slice(index, parser.endIndex + 1);
|
|
22
|
-
const
|
|
33
|
+
const rawAttrs = origin ? source.slice(origin.nameEnd, parser.endIndex) : raw.slice(name.length + 1, -1);
|
|
34
|
+
const attrs = rawAttrs.replace(/\s*\/$/, "");
|
|
23
35
|
const tag = { raw, name, attrs, index };
|
|
24
36
|
tags.push(tag);
|
|
25
37
|
const sameNameStack = openTagsByName.get(name) ?? [];
|
|
@@ -2840,10 +2852,6 @@ function targetHasNoStableIdentity(selector, identity) {
|
|
|
2840
2852
|
if (identity) return false;
|
|
2841
2853
|
return selector === UNRESOLVED_TARGET || selector === "dwell/hold" || selector.startsWith("proxy \u2192 ");
|
|
2842
2854
|
}
|
|
2843
|
-
function readRegisteredTimelineCompositionId(script) {
|
|
2844
|
-
const match = script.match(WINDOW_TIMELINE_ASSIGN_PATTERN);
|
|
2845
|
-
return match?.[1] || match?.[2] || null;
|
|
2846
|
-
}
|
|
2847
2855
|
function unwrapRaw(value) {
|
|
2848
2856
|
if (typeof value === "number") return value;
|
|
2849
2857
|
if (typeof value !== "string") return void 0;
|
|
@@ -3011,21 +3019,6 @@ function makesOverlayVisible(win) {
|
|
|
3011
3019
|
if (win.method === "from" && isHiddenGsapState(win.propertyValues)) return true;
|
|
3012
3020
|
return isVisibleGsapState(win.propertyValues);
|
|
3013
3021
|
}
|
|
3014
|
-
function isSceneBoundaryExit(win) {
|
|
3015
|
-
if (win.end <= win.position) return false;
|
|
3016
|
-
if (win.method !== "to" && win.method !== "fromTo") return false;
|
|
3017
|
-
return isHiddenGsapState(win.propertyValues);
|
|
3018
|
-
}
|
|
3019
|
-
function isHardKillSet(win, selector, boundary) {
|
|
3020
|
-
return win.method === "set" && win.targetSelector === selector && Math.abs(win.position - boundary) <= SCENE_BOUNDARY_EPSILON_SECONDS && isHiddenGsapState(win.propertyValues);
|
|
3021
|
-
}
|
|
3022
|
-
function hiddenStateLiteral(values) {
|
|
3023
|
-
if (zeroValue(values.autoAlpha)) return "{ autoAlpha: 0 }";
|
|
3024
|
-
if (zeroValue(values.opacity)) return "{ opacity: 0 }";
|
|
3025
|
-
if (stringValue(values.visibility)?.toLowerCase() === "hidden") return '{ visibility: "hidden" }';
|
|
3026
|
-
if (stringValue(values.display)?.toLowerCase() === "none") return '{ display: "none" }';
|
|
3027
|
-
return "{ opacity: 0 }";
|
|
3028
|
-
}
|
|
3029
3022
|
function findTagEnd(source, tag) {
|
|
3030
3023
|
const escapedTagName = tag.name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
3031
3024
|
const pattern = new RegExp(`<\\/?${escapedTagName}\\b[^>]*>`, "gi");
|
|
@@ -3042,56 +3035,11 @@ function findTagEnd(source, tag) {
|
|
|
3042
3035
|
}
|
|
3043
3036
|
return source.length;
|
|
3044
3037
|
}
|
|
3045
|
-
function collectCompositionRanges(source, tags) {
|
|
3046
|
-
return tags.map((tag) => {
|
|
3047
|
-
const id = readDecodedAttr(tag.raw, "data-composition-id");
|
|
3048
|
-
if (!id) return null;
|
|
3049
|
-
return {
|
|
3050
|
-
id,
|
|
3051
|
-
start: tag.index,
|
|
3052
|
-
end: findTagEnd(source, tag)
|
|
3053
|
-
};
|
|
3054
|
-
}).filter((range) => range !== null);
|
|
3055
|
-
}
|
|
3056
|
-
function findContainingCompositionId(tag, ranges) {
|
|
3057
|
-
let match = null;
|
|
3058
|
-
for (const range of ranges) {
|
|
3059
|
-
if (tag.index < range.start || tag.index >= range.end) continue;
|
|
3060
|
-
if (!match || range.start >= match.start) match = range;
|
|
3061
|
-
}
|
|
3062
|
-
return match?.id || null;
|
|
3063
|
-
}
|
|
3064
3038
|
function getClipTagClasses(tag) {
|
|
3065
3039
|
const classAttr = readAttr(tag.raw, "class") || "";
|
|
3066
3040
|
const classes = classAttr.split(/\s+/).filter(Boolean);
|
|
3067
3041
|
return classes.includes("clip") ? { classAttr, classes } : null;
|
|
3068
3042
|
}
|
|
3069
|
-
function collectClipStartBoundariesByComposition(source, tags) {
|
|
3070
|
-
const ranges = collectCompositionRanges(source, tags);
|
|
3071
|
-
const boundaries = /* @__PURE__ */ new Map();
|
|
3072
|
-
for (const tag of tags) {
|
|
3073
|
-
if (!getClipTagClasses(tag)) continue;
|
|
3074
|
-
const compositionId = findContainingCompositionId(tag, ranges);
|
|
3075
|
-
if (!compositionId) continue;
|
|
3076
|
-
const start = numberValue(readAttr(tag.raw, "data-start") ?? void 0);
|
|
3077
|
-
if (start == null || start <= 0) continue;
|
|
3078
|
-
const compositionBoundaries = boundaries.get(compositionId) ?? /* @__PURE__ */ new Set();
|
|
3079
|
-
compositionBoundaries.add(start);
|
|
3080
|
-
boundaries.set(compositionId, compositionBoundaries);
|
|
3081
|
-
}
|
|
3082
|
-
return new Map(
|
|
3083
|
-
[...boundaries.entries()].map(([compositionId, values]) => [
|
|
3084
|
-
compositionId,
|
|
3085
|
-
[...values].sort((a, b) => a - b)
|
|
3086
|
-
])
|
|
3087
|
-
);
|
|
3088
|
-
}
|
|
3089
|
-
function findMatchingSceneBoundary(time, boundaries) {
|
|
3090
|
-
for (const boundary of boundaries) {
|
|
3091
|
-
if (Math.abs(time - boundary) <= SCENE_BOUNDARY_EPSILON_SECONDS) return boundary;
|
|
3092
|
-
}
|
|
3093
|
-
return null;
|
|
3094
|
-
}
|
|
3095
3043
|
function readStyleProperty(style, property) {
|
|
3096
3044
|
const escapedProperty = property.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
3097
3045
|
const match = style.match(new RegExp(`(?:^|;)\\s*${escapedProperty}\\s*:\\s*([^;]+)`, "i"));
|
|
@@ -3590,7 +3538,7 @@ var gsapRules = [
|
|
|
3590
3538
|
},
|
|
3591
3539
|
// overlapping_gsap_tweens + gsap_animates_clip_element
|
|
3592
3540
|
// fallow-ignore-next-line complexity
|
|
3593
|
-
async ({
|
|
3541
|
+
async ({ tags, scripts, styles }) => {
|
|
3594
3542
|
const findings = [];
|
|
3595
3543
|
const authoredHiddenSelectors = new Set(
|
|
3596
3544
|
scripts.flatMap((script) => [...extractStandaloneHiddenSelectors(script.content)])
|
|
@@ -3611,13 +3559,10 @@ var gsapRules = [
|
|
|
3611
3559
|
if (cls !== "clip") clipClasses.set(`.${cls}`, info);
|
|
3612
3560
|
}
|
|
3613
3561
|
}
|
|
3614
|
-
const clipStartBoundariesByComposition = collectClipStartBoundariesByComposition(source, tags);
|
|
3615
3562
|
const styleRules = collectSimpleStyleRules(styles);
|
|
3616
3563
|
const reportedVisibleOverlayKeys = /* @__PURE__ */ new Set();
|
|
3617
3564
|
for (const script of scripts) {
|
|
3618
|
-
const localTimelineCompId = readRegisteredTimelineCompositionId(script.content);
|
|
3619
3565
|
const gsapWindows = await cachedExtractGsapWindows(script.content);
|
|
3620
|
-
const clipStartBoundaries = clipStartBoundariesByComposition.get(localTimelineCompId || rootCompositionId || "") ?? [];
|
|
3621
3566
|
for (let i = 0; i < gsapWindows.length; i++) {
|
|
3622
3567
|
const left = gsapWindows[i];
|
|
3623
3568
|
if (!left) continue;
|
|
@@ -3679,28 +3624,6 @@ ${right.raw}`)
|
|
|
3679
3624
|
snippet: truncateSnippet(fromToWindows.map((win) => win.raw).join("\n"))
|
|
3680
3625
|
});
|
|
3681
3626
|
}
|
|
3682
|
-
if (clipStartBoundaries.length > 0) {
|
|
3683
|
-
for (const win of gsapWindows) {
|
|
3684
|
-
if (win.targetSelector === UNRESOLVED_TARGET) continue;
|
|
3685
|
-
if (!isSceneBoundaryExit(win)) continue;
|
|
3686
|
-
const boundary = findMatchingSceneBoundary(win.end, clipStartBoundaries);
|
|
3687
|
-
if (boundary == null) continue;
|
|
3688
|
-
const hasHardKill = gsapWindows.some(
|
|
3689
|
-
(candidate) => isHardKillSet(candidate, win.targetSelector, boundary)
|
|
3690
|
-
);
|
|
3691
|
-
if (hasHardKill) continue;
|
|
3692
|
-
const exitClipInfo = clipIds.get(win.targetSelector) || clipClasses.get(win.targetSelector);
|
|
3693
|
-
const fixHint = exitClipInfo ? `"${win.targetSelector}" is a clip element \u2014 the framework already manages its visibility. Wrap the scene's content in an inner non-clip <div>, move the exit tween and the hard kill (\`tl.set("<inner-selector>", ${hiddenStateLiteral(win.propertyValues)}, ${boundary.toFixed(2)})\`) onto that wrapper instead.` : `Add \`tl.set("${win.targetSelector}", ${hiddenStateLiteral(win.propertyValues)}, ${boundary.toFixed(2)})\` after the exit tween.`;
|
|
3694
|
-
findings.push({
|
|
3695
|
-
code: "gsap_exit_missing_hard_kill",
|
|
3696
|
-
severity: "error",
|
|
3697
|
-
message: `GSAP exit on "${win.targetSelector}" ends at the ${boundary.toFixed(2)}s clip start boundary without a matching tl.set hard kill. Non-linear seeking can land after the fade and leave stale visibility state.`,
|
|
3698
|
-
selector: win.targetSelector,
|
|
3699
|
-
fixHint,
|
|
3700
|
-
snippet: truncateSnippet(win.raw)
|
|
3701
|
-
});
|
|
3702
|
-
}
|
|
3703
|
-
}
|
|
3704
3627
|
for (const tag of tags) {
|
|
3705
3628
|
const selectors = tagSimpleSelectors(tag);
|
|
3706
3629
|
if (selectors.length === 0) continue;
|