@hyperframes/core 0.8.199 → 0.8.200
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/compiler/htmlBundler.js +14 -3
- package/dist/compiler/inlineSubCompositions.d.ts +2 -0
- package/dist/compiler/inlineSubCompositions.js +64 -5
- package/dist/generated/position-edits-render-inline.js +1 -1
- package/dist/generated/runtime-inline.js +1 -1
- package/dist/hyperframe.runtime.iife.js +65 -62
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/runtime/clipReveal.d.ts +1 -1
- package/dist/runtime/diagnostics.d.ts +2 -0
- package/dist/runtime/diagnostics.js +2 -0
- package/dist/runtime/timedClipHide.d.ts +2 -1
- package/dist/runtime/timedClipHide.js +4 -2
- package/dist/runtime/timeline.d.ts +8 -1
- package/dist/runtime/timeline.js +30 -12
- package/dist/studioPreviewMark.d.ts +3 -1
- package/dist/studioPreviewMark.js +4 -0
- package/package.json +4 -4
|
@@ -23,7 +23,7 @@ import { buildVariablesByCompScript, dedupeFontFaceRules, hoistFontFaceRules, sc
|
|
|
23
23
|
import { validateHyperframeHtmlContract } from "./staticGuard.js";
|
|
24
24
|
import { getHyperframeRuntimeScript } from "../generated/runtime-inline.js";
|
|
25
25
|
import { readDeclaredDefaults } from "../runtime/getVariables.js";
|
|
26
|
-
import { ensureExternalLinkTag, inlineSubCompositions, refuseSwapsReachedByRootScripts, } from "./inlineSubCompositions.js";
|
|
26
|
+
import { ensureExternalLinkTag, inlineSubCompositions, isLibraryCopy, refuseSwapsReachedByRootScripts, } from "./inlineSubCompositions.js";
|
|
27
27
|
import { isSafePath, resolveWithinProject } from "../safePath.js";
|
|
28
28
|
import { ensureHfIds } from "@hyperframes/parsers/hf-ids";
|
|
29
29
|
import { HF_COLOR_GRADING_ATTR } from "../colorGrading.js";
|
|
@@ -966,7 +966,8 @@ async function bundleProject(projectDir, options) {
|
|
|
966
966
|
].map((el) => {
|
|
967
967
|
const src = el.getAttribute("src");
|
|
968
968
|
const path = src && isRelativeUrl(src) ? resolveEntryUrl(src) : null;
|
|
969
|
-
|
|
969
|
+
const text = src ? (path && safeReadFile(path)) || "" : el.textContent || "";
|
|
970
|
+
return isLibraryCopy(text) ? "" : text;
|
|
970
971
|
})
|
|
971
972
|
: [];
|
|
972
973
|
// Inline sub-compositions (via shared function)
|
|
@@ -1006,6 +1007,10 @@ async function bundleProject(projectDir, options) {
|
|
|
1006
1007
|
},
|
|
1007
1008
|
flattenInnerRoot: prepareFlattenedInnerRoot,
|
|
1008
1009
|
tagScenes: options?.sceneParts === true,
|
|
1010
|
+
readProjectScript: (path) => {
|
|
1011
|
+
const resolved = isRelativeUrl(path) ? resolveEntryUrl(path) : null;
|
|
1012
|
+
return resolved ? safeReadFile(resolved) : null;
|
|
1013
|
+
},
|
|
1009
1014
|
readVariableDefaults: readDeclaredDefaults,
|
|
1010
1015
|
parseHostVariables: parseHostVariableValues,
|
|
1011
1016
|
buildScopeSelector: (compId) => cssAttributeSelector("data-composition-id", compId),
|
|
@@ -1025,6 +1030,10 @@ async function bundleProject(projectDir, options) {
|
|
|
1025
1030
|
...subCompResult.variablesByComp,
|
|
1026
1031
|
};
|
|
1027
1032
|
const seenCompScriptSrcs = new Set();
|
|
1033
|
+
const scenesBySrc = new Map();
|
|
1034
|
+
for (const item of subCompResult.scriptItems)
|
|
1035
|
+
if (item.kind !== "inline")
|
|
1036
|
+
scenesBySrc.set(item.src, (scenesBySrc.get(item.src) ?? new Set()).add(item.scene));
|
|
1028
1037
|
for (const scriptItem of subCompResult.scriptItems) {
|
|
1029
1038
|
if (scriptItem.kind === "inline") {
|
|
1030
1039
|
pushRun(scriptRuns, scriptItem.scene, scriptItem.content);
|
|
@@ -1039,12 +1048,14 @@ async function bundleProject(projectDir, options) {
|
|
|
1039
1048
|
if (seenCompScriptSrcs.has(extSrc))
|
|
1040
1049
|
continue;
|
|
1041
1050
|
seenCompScriptSrcs.add(extSrc);
|
|
1051
|
+
// Run once for the film when scenes share it: a scene mounts without the one that loaded it first.
|
|
1052
|
+
const scene = (scenesBySrc.get(extSrc)?.size ?? 0) > 1 ? undefined : scriptItem.scene;
|
|
1042
1053
|
if (isRelativeUrl(extSrc) && scriptItem.type !== "module") {
|
|
1043
1054
|
const jsPath = resolveEntryUrl(extSrc);
|
|
1044
1055
|
const js = jsPath ? safeReadFile(jsPath) : null;
|
|
1045
1056
|
if (js != null) {
|
|
1046
1057
|
const chunk = () => preserveLocalScriptIntegrity(document, extSrc, resolveEntryUrl) ? "" : js;
|
|
1047
|
-
pushRun(scriptRuns,
|
|
1058
|
+
pushRun(scriptRuns, scene, chunk);
|
|
1048
1059
|
continue;
|
|
1049
1060
|
}
|
|
1050
1061
|
}
|
|
@@ -11,6 +11,7 @@ import { type CompositionStyle } from "./scriptRuns.js";
|
|
|
11
11
|
* `data-composition-file`).
|
|
12
12
|
*/
|
|
13
13
|
import { type AssetExists } from "./rewriteSubCompPaths.js";
|
|
14
|
+
export declare const isLibraryCopy: (script: string) => boolean;
|
|
14
15
|
/** Marks each scene whose nodes a script outside it names by selector, id or class: a swap would strand it. */
|
|
15
16
|
export declare function refuseSwapsReachedByRootScripts(document: Document, rootScripts: string[]): void;
|
|
16
17
|
export interface InlineSubCompositionsOptions {
|
|
@@ -98,6 +99,7 @@ export interface InlineSubCompositionsOptions {
|
|
|
98
99
|
onMissingComposition?: (srcPath: string, reason?: string) => void;
|
|
99
100
|
/** Tag each top-level host with `data-hf-scene` and report every part's scene, for preview swaps. */
|
|
100
101
|
tagScenes?: boolean;
|
|
102
|
+
readProjectScript?: (path: string) => string | null;
|
|
101
103
|
}
|
|
102
104
|
export interface ExternalLink {
|
|
103
105
|
href: string;
|
|
@@ -20,15 +20,74 @@ import { enumerateNestedCompositionHosts, planCompositionAssembly, extractedComp
|
|
|
20
20
|
import { SCENE_NO_SWAP_ATTR, SCENE_PART_ATTR } from "../sceneParts.js";
|
|
21
21
|
// Anything a scene script can leave running, pending or registered outside its timeline, or that
|
|
22
22
|
// throws when run again: only the timeline is torn down when a scene is swapped, so when unsure, refuse.
|
|
23
|
-
const SIDE_EFFECT_RE = /\b(addEventListener|requestAnimationFrame|requestIdleCallback|setTimeout|setInterval|queueMicrotask|getContext|WebGL\w*|WebGPU\w*|gpu|Worker|WebSocket|EventSource|Audio\w*|\w*Observer|fetch|import|eval|Function|Promise|async|await|delayedCall|ScrollTrigger|Draggable|anime|customElements|registerProperty|addListener|BroadcastChannel|pushState|replaceState|adoptedStyleSheets|documentElement|getElementsByTagName|lottie|THREE|__hf[A-Z]\w*)\b|\.then\s*\(|\.
|
|
23
|
+
const SIDE_EFFECT_RE = /\b(addEventListener|requestAnimationFrame|requestIdleCallback|setTimeout|setInterval|queueMicrotask|getContext|WebGL\w*|WebGPU\w*|gpu|Worker|WebSocket|EventSource|Audio\w*|\w*Observer|fetch|import|eval|Function|Promise|async|await|delayedCall|ScrollTrigger|Draggable|anime|customElements|registerProperty|addListener|BroadcastChannel|pushState|replaceState|adoptedStyleSheets|documentElement|getElementsByTagName|lottie|THREE|__hf[A-Z]\w*)\b|\.then\s*\(|\.ticker\b|repeat\s*:\s*-1|\.repeat\s*\(\s*-1|defineProperty\s*\(\s*(window|globalThis|self|document)\b|\bfonts\s*\.\s*add\b|\.on[a-z]+\s*=(?!=)|\bon(resize|scroll|message|key\w+|click|pointer\w+|mouse\w+|wheel|visibilitychange|hashchange|popstate|error|load)\s*=(?!=)|\[\s*["']on[a-z]+["']\s*\]|document\s*\.\s*(head|body)\b|querySelector(All)?\(\s*["'](head|body)["']|\bgsap\s*\.\s*(?:to|from|fromTo)\s*\(|\bgsap\s*\.\s*timeline\s*\((?:[^()]|\([^()]*\))*\)\s*\.\s*(?:to|from|fromTo|set|add|call)\s*\(/;
|
|
24
24
|
// npm packages that only define globals when loaded; lottie-web is absent because it scans the page on load.
|
|
25
25
|
const SWAP_SAFE_LIBRARY_URL = /^https:\/\/(cdn\.jsdelivr\.net\/npm|unpkg\.com)\/(gsap|three|d3|d3-[a-z-]+|topojson-client|clipper-lib)(@[^/]+)?\//;
|
|
26
26
|
const isSwapSafeLibrary = (src) => URL.canParse(src) && SWAP_SAFE_LIBRARY_URL.test(new URL(src).href);
|
|
27
|
+
// GSAP's published file copied into a project, known by its license banner: it only defines globals too.
|
|
28
|
+
const GSAP_BANNER_RE = /^\s*\/\*!\s*\*\s*GSAP \d+\.\d+\.\d+\s*\*\s*https:\/\/gsap\.com\b/;
|
|
29
|
+
export const isLibraryCopy = (script) => GSAP_BANNER_RE.test(script);
|
|
30
|
+
// Waits on the page's fonts, ready or loading named faces: each settles once, leaving nothing behind.
|
|
31
|
+
const STRING_ARG = String.raw `(?:"(?:\\.|[^"\\\n])*"|'(?:\\.|[^'\\\n])*')`;
|
|
32
|
+
const FONT_LOAD = String.raw `document\s*\.\s*fonts\s*\.\s*load\s*\(\s*${STRING_ARG}\s*(?:,\s*${STRING_ARG}\s*)?\)`;
|
|
33
|
+
const FONT_PROMISE = String.raw `(?:document\s*\.\s*fonts\s*\.\s*ready\b|${FONT_LOAD}|Promise\s*\.\s*all\s*\(\s*\[\s*${FONT_LOAD}(?:\s*,\s*${FONT_LOAD})*\s*,?\s*\]\s*\))`;
|
|
34
|
+
const FONT_WAIT_RE = new RegExp(String.raw `\b${FONT_PROMISE}\s*\.\s*then\s*\(|\bawait\s+${FONT_PROMISE}`, "g");
|
|
35
|
+
// A canvas made only to measure text: never named, so nothing attaches it to the page or draws on it there.
|
|
36
|
+
const TEXT_MEASURE_CANVAS_RE = /\bdocument\s*\.\s*createElement\s*\(\s*(["'])canvas\1\s*\)\s*\.\s*getContext\s*\(\s*(["'])2d\2\s*\)/g;
|
|
37
|
+
// A timeline chained as it is registered: the registry holds it, so unmounting the scene stops it.
|
|
38
|
+
const REGISTERED_CHAIN_RE = /(\b(?:window\s*\.\s*)?__timelines\s*\[[^[\]]*\]\s*=\s*)gsap\s*\.\s*timeline\s*\(/g;
|
|
39
|
+
/** Whether a call of the browser's Element.animate may leave an animation: it gets timing, its second argument. */
|
|
40
|
+
function lastingAnimateCall(script) {
|
|
41
|
+
for (const call of script.matchAll(/\.animate\s*\(/g)) {
|
|
42
|
+
if (topLevelArgs(script, call.index + call[0].length) !== 1)
|
|
43
|
+
return true;
|
|
44
|
+
}
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
// Arguments of the call whose "(" ends before `from`; none, a spread or an unclosed call never count as one.
|
|
48
|
+
function topLevelArgs(script, from) {
|
|
49
|
+
let depth = 0;
|
|
50
|
+
let args = 1;
|
|
51
|
+
if (/^\s*(\.\.\.|\))/.test(script.slice(from)))
|
|
52
|
+
return Number.POSITIVE_INFINITY;
|
|
53
|
+
for (let i = from; i < script.length; i++) {
|
|
54
|
+
const ch = script[i];
|
|
55
|
+
if (ch === '"' || ch === "'" || ch === "`") {
|
|
56
|
+
const end = script.indexOf(ch, i + 1);
|
|
57
|
+
if (end < 0)
|
|
58
|
+
return Number.POSITIVE_INFINITY;
|
|
59
|
+
i = end;
|
|
60
|
+
}
|
|
61
|
+
else if ("([{".includes(ch))
|
|
62
|
+
depth++;
|
|
63
|
+
else if (")]}".includes(ch)) {
|
|
64
|
+
if (depth === 0)
|
|
65
|
+
return args;
|
|
66
|
+
depth--;
|
|
67
|
+
}
|
|
68
|
+
else if (ch === "," && depth === 0)
|
|
69
|
+
args++;
|
|
70
|
+
}
|
|
71
|
+
return Number.POSITIVE_INFINITY;
|
|
72
|
+
}
|
|
27
73
|
/** Why an authored scene script cannot be swapped out cleanly, or null when it can. */
|
|
28
74
|
function sceneScriptSwapRefusal(script) {
|
|
29
|
-
const
|
|
30
|
-
|
|
75
|
+
const withoutFontWaits = script.replace(FONT_WAIT_RE, "");
|
|
76
|
+
const awaitedFontsOnly = /\bawait\b/.test(script) && !/\bawait\b/.test(withoutFontWaits);
|
|
77
|
+
let checked = (awaitedFontsOnly ? withoutFontWaits.replace(/\basync\b/g, "") : withoutFontWaits).replace(REGISTERED_CHAIN_RE, "$1(");
|
|
78
|
+
// The context hands its canvas back through .canvas, which could then reach the page.
|
|
79
|
+
if (!/\.\s*canvas\b/.test(checked))
|
|
80
|
+
checked = checked.replace(TEXT_MEASURE_CANVAS_RE, "");
|
|
81
|
+
const match = SIDE_EFFECT_RE.exec(checked);
|
|
82
|
+
if (match)
|
|
83
|
+
return `its script uses ${match[0].trim()}`;
|
|
84
|
+
return lastingAnimateCall(checked) ? "its script uses .animate(" : null;
|
|
31
85
|
}
|
|
86
|
+
const projectScriptRefusal = (text) => text === null
|
|
87
|
+
? "it runs a script that is not a known library"
|
|
88
|
+
: isLibraryCopy(text)
|
|
89
|
+
? null
|
|
90
|
+
: sceneScriptSwapRefusal(text);
|
|
32
91
|
// A quoted string in a script, in any of the three quote styles.
|
|
33
92
|
const STRING_LITERAL_RE = /(["'`])((?:\\.|(?!\1)[^\\\n])*?)\1/g;
|
|
34
93
|
// Text ending where createElement takes its tag: that string makes a node, it selects none.
|
|
@@ -135,7 +194,7 @@ function defaultBuildScopeSelector(compId) {
|
|
|
135
194
|
*/
|
|
136
195
|
// fallow-ignore-next-line complexity
|
|
137
196
|
export function inlineSubCompositions(document, hosts, options) {
|
|
138
|
-
const { resolveHtml, parseHtml, hostIdentityMap, rewriteInlineStyles = false, flattenInnerRoot, compoundAuthoredRoot, readVariableDefaults, parseHostVariables, buildScopeSelector = defaultBuildScopeSelector, scriptErrorLabel = "[HyperFrames] composition script error:", onMissingComposition, assetExists, tagScenes = false, } = options;
|
|
197
|
+
const { resolveHtml, parseHtml, hostIdentityMap, rewriteInlineStyles = false, flattenInnerRoot, compoundAuthoredRoot, readVariableDefaults, parseHostVariables, buildScopeSelector = defaultBuildScopeSelector, scriptErrorLabel = "[HyperFrames] composition script error:", onMissingComposition, assetExists, tagScenes = false, readProjectScript, } = options;
|
|
139
198
|
const rootStyles = [...document.querySelectorAll("style")];
|
|
140
199
|
const styles = [];
|
|
141
200
|
const styleScenes = [];
|
|
@@ -338,7 +397,7 @@ export function inlineSubCompositions(document, hosts, options) {
|
|
|
338
397
|
? sceneScriptSwapRefusal(scriptEl.textContent || "")
|
|
339
398
|
: isSwapSafeLibrary(externalSrc)
|
|
340
399
|
? null
|
|
341
|
-
:
|
|
400
|
+
: projectScriptRefusal(readProjectScript?.(externalSrc) ?? null));
|
|
342
401
|
if (type === "importmap") {
|
|
343
402
|
const map = parseImportMap(scriptEl.textContent || "", (url) => {
|
|
344
403
|
// The rebase drops a leading "./" and a trailing "/"; an import map address needs both.
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// Generated by scripts/build-position-edits-render.ts; do not edit.
|
|
2
|
-
const POSITION_EDITS_RENDER_IIFE = '"use strict";(()=>{function
|
|
2
|
+
const POSITION_EDITS_RENDER_IIFE = '"use strict";(()=>{function P(){return globalThis}function T(e,t){if(typeof window>"u")return;let o=P(),r=o.__hf?.onSwallowed;if(r)try{r({label:e,error:t})}catch(s){}(o.__hfDebug||o.__HYPERFRAMES_DEBUG)&&console.debug(`[hyperframes] ${e} swallowed:`,t)}var x=null;function R(e,t){if(x)try{x({source:"hf-preview",type:"analytics",event:e,properties:t??{}})}catch(o){T("runtime.analytics.site1",o)}}var W="http://www.w3.org/1999/xhtml";function N(e){return e!=null&&e.nodeType===1}function k(e){return N(e)&&(e.namespaceURI===W||e.namespaceURI==="http://www.w3.org/2000/svg")}var w="data-hf-edit-base-x",y="data-hf-edit-base-y",m="data-hf-edit-original-translate",_=e=>{let t=parseFloat(e??"");return Number.isFinite(t)?t:0},C=e=>{let t=[],o=0,r="";for(let s of e.trim())s==="("&&(o+=1),s===")"&&(o=Math.max(0,o-1)),/\\s/.test(s)&&o===0?(r&&t.push(r),r=""):r+=s;return r&&t.push(r),t},L=/^-?(?:\\d+(?:\\.\\d+)?|\\.\\d+)px$/,E=(e,t)=>L.test(e)&&L.test(t)?`${parseFloat(e)+parseFloat(t)}px`:`calc(${e} + ${t})`,O=(e,t,o)=>{if(!e||e==="none")return`${t} ${o}`;let[r,s,a]=C(e);if(r===void 0)return`${t} ${o}`;if(s===void 0)return`${E(r,t)} ${o}`;let i=a===void 0?"":` ${a}`;return`${E(r,t)} ${E(s,o)}${i}`},V=e=>{try{e.ownerDocument.defaultView?.gsap?.getProperty?.(e,"x")}catch{}},j=e=>{let t=e.style.getPropertyValue("translate").trim();if(t)return t==="none"?"":t;try{let o=e.ownerDocument.defaultView,r=o?o.getComputedStyle(e).getPropertyValue("translate").trim():"";return r==="none"?"":r}catch{return""}},b=new WeakMap;function B(e,t){let o=b.get(e);if(!t?.force&&o!==void 0&&e.style.getPropertyValue("translate")!==o){R("position_edit_fold_skipped",{hfId:e.getAttribute("data-hf-id")});return}let r=_(e.getAttribute("data-x"))-_(e.getAttribute(w)),s=_(e.getAttribute("data-y"))-_(e.getAttribute(y));e.hasAttribute(m)||e.setAttribute(m,j(e)),o===void 0&&V(e);let a=e.getAttribute(m)??"",i=O(a,`${r}px`,`${s}px`);e.style.setProperty("translate",i),b.set(e,e.style.getPropertyValue("translate"))}function S(e,t){let o=e.querySelectorAll(`[${m}]:not([${w}]):not([${y}])`);for(let a=0;a<o.length;a++){let i=o[a];if(i===void 0||!k(i))continue;let g=i.getAttribute(m)??"";g===""?i.style.removeProperty("translate"):i.style.setProperty("translate",g),i.removeAttribute(m),b.delete(i)}let r=e.querySelectorAll(`[${w}], [${y}]`),s=0;for(let a=0;a<r.length;a++){let i=r[a];i===void 0||!k(i)||(B(i,t),s+=1)}return s}var $="__hfPositionEditsSeekReapplyWrapped",D=new WeakSet,F=new WeakMap,I=new WeakMap;function M(e){let t=e,o=()=>{try{S(t.document)}catch{}},r=n=>typeof n=="function"&&(D.has(n)||!!n[$]),s=n=>{D.add(n);try{Object.defineProperty(n,$,{value:!0})}catch{}},a=n=>{if(typeof n!="function"||r(n))return n;let l=function(...u){let d=n.apply(this,u);return o(),d};return s(l),l},i=(n,l)=>{let u=F.get(n);if(u?.has(l))return!0;let d=Object.getOwnPropertyDescriptor(n,l);if(d?.configurable===!1){let c=n[l];return typeof c=="function"&&(n[l]=a(c),o()),!1}let p=n[l],f=d?.set;return Object.defineProperty(n,l,{configurable:!0,enumerable:d?.enumerable??!0,get:()=>p,set:c=>{p=a(c),f?.call(n,c)}}),p=a(p),u??(u=new Set),u.add(l),F.set(n,u),o(),!0},g=(n,l)=>{let u=I.get(t),d=Object.getOwnPropertyDescriptor(t,n);if(!u?.has(n)){if(d?.configurable===!1){let c=t[n];return c?i(c,l):!1}let f=t[n];Object.defineProperty(t,n,{configurable:!0,enumerable:d?.enumerable??!0,get:()=>f,set:c=>{f=c,f&&i(f,l)}}),u??(u=new Set),u.add(n),I.set(t,u)}let p=t[n];return p?i(p,l):!1},h=()=>{let n=g("__hf","seek"),l=g("__player","renderSeek");return n&&l};if(h())return;let v=120,A=t.setInterval(()=>{if(h()){t.clearInterval(A);return}v-=1,v<=0&&t.clearInterval(A)},50)}function H(){document.querySelector(`[${w}], [${y}]`)&&(S(document),M(window))}document.readyState==="loading"?document.addEventListener("DOMContentLoaded",H,{once:!0}):H();})();\n';
|
|
3
3
|
/** Returns the pre-built position-edits render IIFE as a string constant. */
|
|
4
4
|
export function getPositionEditsRenderScript() {
|
|
5
5
|
return POSITION_EDITS_RENDER_IIFE;
|