@hyperframes/studio 0.8.94 → 0.8.95

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.
@@ -161,6 +161,55 @@ describe("applyUndoRestoreToPreview", () => {
161
161
  },
162
162
  );
163
163
 
164
+ describe("an undo that re-runs a changed script matches a fresh load of the restored file", () => {
165
+ const script = (extra: string) => `window.__timelines["root"]=gsap.timeline();${extra}`;
166
+ const undoScriptEdit = (live: string, authored: string, edit: string) => {
167
+ const { iframe, contentWindow, doc } = buildLiveIframe(
168
+ `${live}<script>${script(edit)}</script>`,
169
+ );
170
+ const clearProps = (targets: HTMLElement[]) =>
171
+ targets.forEach((t) => t.removeAttribute("style"));
172
+ Object.assign(contentWindow.gsap, { set: clearProps });
173
+ for (const el of doc.querySelectorAll("[style*=transform]")) Object.assign(el, { _gsap: {} });
174
+ const files = {
175
+ [ROOT]: {
176
+ previous: wrap(`${authored}<script>${script(edit)}</script>`),
177
+ restored: wrap(`${authored}<script>${script("")}</script>`),
178
+ },
179
+ };
180
+ expect(applyUndoRestoreToPreview(iframe, ROOT, files, 3, vi.fn())).toBe("soft");
181
+ return doc;
182
+ };
183
+
184
+ it("drops the size marks a resize left on the live element", () => {
185
+ const doc = undoScriptEdit(
186
+ `<div id="a" data-hf-studio-box-size="true" style="width: 502px; --hf-studio-width: 502px; --hf-studio-height: 334px">t</div>`,
187
+ `<div id="a" style="width: 300px">t</div>`,
188
+ `tl.set("#a",{width:502,height:334},0);`,
189
+ );
190
+ expect(doc.getElementById("a")!.hasAttribute("data-hf-studio-box-size")).toBe(false);
191
+ expect(doc.getElementById("a")!.getAttribute("style")).toBe("width: 300px");
192
+ });
193
+
194
+ it("keeps the authored inline rotation of an element GSAP moved", () => {
195
+ const doc = undoScriptEdit(
196
+ `<div id="r" style="left: 10px; transform: translate(116px, 72px) rotate(20deg)">t</div>`,
197
+ `<div id="r" style="left: 10px; transform: rotate(20deg)">t</div>`,
198
+ `gsap.set("#r",{x:116,y:72});`,
199
+ );
200
+ expect(doc.getElementById("r")!.style.transform).toBe("rotate(20deg)");
201
+ });
202
+
203
+ it("drops the translate mask GSAP wrote over a stylesheet translate", () => {
204
+ const doc = undoScriptEdit(
205
+ `<h1 id="t" style="opacity: 0.5; translate: none; transform: translate(116px, -113px)">t</h1>`,
206
+ `<h1 id="t">t</h1>`,
207
+ `gsap.set("#t",{x:116,y:-113});`,
208
+ );
209
+ expect(doc.getElementById("t")!.style.getPropertyValue("translate")).toBe("");
210
+ });
211
+ });
212
+
164
213
  it("full-reloads without partially restoring when any changed live target is missing", () => {
165
214
  const { iframe, doc } = buildLiveIframe(`<div id="a" style="z-index: 8">a</div>`);
166
215
  const reloadPreview = vi.fn();
@@ -8,6 +8,8 @@ import {
8
8
  extractGsapScriptText,
9
9
  findGsapScriptElements,
10
10
  } from "./gsapSoftReload";
11
+ import { findAuthoredElement, parseSavedSource } from "./authoredSource";
12
+ import { STUDIO_EDIT_ATTRS } from "../components/editor/manualEditsSeekReapply";
11
13
  import { markScenesStale } from "../player/sceneSwap";
12
14
 
13
15
  type PreviewWindow = Window & {
@@ -135,6 +137,17 @@ function syncElementAttributes(target: Element, source: Element): void {
135
137
  }
136
138
  }
137
139
 
140
+ // A gesture folded into the script leaves marks on the live element that every seek would re-impose.
141
+ function syncStaleEditMarks(doc: Document, restored: string): void {
142
+ const restoredDoc = parseSavedSource(restored);
143
+ for (const live of doc.querySelectorAll(STUDIO_EDIT_ATTRS.map((attr) => `[${attr}]`).join())) {
144
+ const source = findAuthoredElement(restoredDoc, live);
145
+ if (source && STUDIO_EDIT_ATTRS.some((a) => live.getAttribute(a) !== source.getAttribute(a))) {
146
+ syncElementAttributes(live, source);
147
+ }
148
+ }
149
+ }
150
+
138
151
  function readGsapScriptTexts(html: string): string[] {
139
152
  const doc = new DOMParser().parseFromString(html, "text/html");
140
153
  return findGsapScriptElements(doc).map((script) => script.textContent ?? "");
@@ -248,9 +261,11 @@ export function applyUndoRestoreToPreview(
248
261
  const restoredScript = extractGsapScriptText(restored);
249
262
  const previousScript = extractGsapScriptText(previous);
250
263
  if (restoredScript && restoredScript !== previousScript) {
264
+ syncStaleEditMarks(doc, restored);
251
265
  const result = applySoftReload(iframe, restoredScript, {
252
266
  onAsyncFailure: reloadPreview,
253
267
  currentTimeOverride: currentTime,
268
+ authoredHtml: restored,
254
269
  });
255
270
  if (result === "cannot-soft-reload") {
256
271
  reloadPreview();