hyperframes 0.8.98 → 0.8.100

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.
Files changed (159) hide show
  1. package/dist/{add-QAX64UD5.js → add-YOGFHV5P.js} +18 -17
  2. package/dist/{animatedGifPrep-5KUI3AAL.js → animatedGifPrep-RZVYVAXD.js} +8 -7
  3. package/dist/{asset-6PAUWV7A.js → asset-RVTEKOMS.js} +2 -2
  4. package/dist/{assetCataloger-FUXNMJYE.js → assetCataloger-BQREHRS2.js} +4 -3
  5. package/dist/{auth-RURB6JL2.js → auth-3ZGHKXTY.js} +2 -2
  6. package/dist/{autoUpdate-ZX5GPKIG.js → autoUpdate-DHDVAKLG.js} +2 -2
  7. package/dist/{backgroundChecks-GZ3LYLFZ.js → backgroundChecks-TJZXWBCQ.js} +3 -3
  8. package/dist/backgroundChecksWorker.js +3 -3
  9. package/dist/{batchRender-2BIKSPOP.js → batchRender-AQN5PRXM.js} +5 -4
  10. package/dist/{beats-FMDUDET7.js → beats-PZEJBKPX.js} +12 -11
  11. package/dist/{benchmark-5NXPAFTK.js → benchmark-XTNKYDB3.js} +14 -13
  12. package/dist/{browser-W5IZK2LF.js → browser-33R5MU5T.js} +11 -10
  13. package/dist/{bundleWithLocalizedFonts-QYS67RJT.js → bundleWithLocalizedFonts-JKJKZR2S.js} +2 -2
  14. package/dist/{capture-Z47X2EG6.js → capture-5BCJWVC7.js} +2 -2
  15. package/dist/{capture-HOXFOHCG.js → capture-QYLHIYKJ.js} +9 -8
  16. package/dist/{catalog-TEUY2DP3.js → catalog-QCJADIDT.js} +19 -18
  17. package/dist/{check-6KE5ZV6K.js → check-V5AIEMEJ.js} +34 -32
  18. package/dist/{checkBrowser-F6MKBAL3.js → checkBrowser-LI4OCQCC.js} +33 -31
  19. package/dist/{chunk-45WXLLUL.js → chunk-32Y3HRS3.js} +2 -2
  20. package/dist/{chunk-DHLZLPIA.js → chunk-3ME4RRT4.js} +8 -8
  21. package/dist/{chunk-M7Y4BQAC.js → chunk-4WAMN4IF.js} +1 -1
  22. package/dist/chunk-4Z5O7BTD.js +114 -0
  23. package/dist/{chunk-H5ZRB3SN.js → chunk-6CN2QLB5.js} +3 -3
  24. package/dist/{chunk-6SXRFLBV.js → chunk-6F6EEQNB.js} +5 -12
  25. package/dist/{chunk-MRQEWIM3.js → chunk-7PJG4Z2H.js} +8 -8
  26. package/dist/{chunk-OIT6L7OY.js → chunk-AZQLJHAO.js} +1 -1
  27. package/dist/{chunk-A7GG3RLG.js → chunk-EBY3SP4T.js} +4 -4
  28. package/dist/chunk-FOKUJKBN.js +3420 -0
  29. package/dist/{chunk-EA5GAMEY.js → chunk-GMECXFAN.js} +1 -1
  30. package/dist/{chunk-QVGB3PQ2.js → chunk-GOI4GCUB.js} +1 -1
  31. package/dist/{chunk-ULER6XBR.js → chunk-HFFR7TN7.js} +1 -1
  32. package/dist/{chunk-6D7WQM6D.js → chunk-HGJZKJRO.js} +1 -1
  33. package/dist/{chunk-J7QMXLNY.js → chunk-HUHJYNST.js} +1 -1
  34. package/dist/{chunk-4CMGNIJD.js → chunk-I6M4GXMC.js} +1 -1
  35. package/dist/{chunk-QPN3GV5R.js → chunk-I77CAR4W.js} +2 -2
  36. package/dist/{chunk-UH4PYJXV.js → chunk-IGV5J5ZW.js} +1 -1
  37. package/dist/{chunk-WPP2NJVF.js → chunk-IH6NEFVL.js} +3 -3
  38. package/dist/{chunk-UPGW6TTK.js → chunk-IQ4UCWV7.js} +1 -1
  39. package/dist/{chunk-6RC6OPDF.js → chunk-JUJ2XONM.js} +9 -9
  40. package/dist/{chunk-VSVNEDUL.js → chunk-JXXE3SWC.js} +10 -10
  41. package/dist/{chunk-7JABNLBL.js → chunk-L5FZ5SFX.js} +5 -5
  42. package/dist/{chunk-B7E6IWMM.js → chunk-LBPAJ5EJ.js} +1 -1
  43. package/dist/{chunk-KF4BPO7K.js → chunk-M4KS6MJN.js} +4 -4
  44. package/dist/{chunk-IOM5EJZB.js → chunk-MCABWC2G.js} +2 -2
  45. package/dist/{chunk-E6ABDLBN.js → chunk-O5TBFZ7X.js} +3 -3
  46. package/dist/chunk-OFQWTKPW.js +117 -0
  47. package/dist/{chunk-P7CAZ6YA.js → chunk-OKQLXBQQ.js} +3 -3
  48. package/dist/{chunk-G7REKKB7.js → chunk-P5GP2XJX.js} +1 -1
  49. package/dist/{chunk-WY7DTUVY.js → chunk-PB2OX2JY.js} +1 -1
  50. package/dist/{chunk-A67QXF4W.js → chunk-PDRHNLKE.js} +3 -3
  51. package/dist/{chunk-NY4NHA3Y.js → chunk-PUB5XZYL.js} +1 -1
  52. package/dist/{chunk-6P7TUF6Z.js → chunk-R5RF3LGS.js} +1 -1
  53. package/dist/{chunk-XMNWWURD.js → chunk-RDMAU7AJ.js} +14 -0
  54. package/dist/{chunk-RRJU47XY.js → chunk-T2HQSEIH.js} +1 -1
  55. package/dist/{chunk-TXY724ZK.js → chunk-TEXPOC5V.js} +2 -2
  56. package/dist/{chunk-YVGQM466.js → chunk-TH4APNRP.js} +6 -4
  57. package/dist/{chunk-WFO4PSIZ.js → chunk-TLYU5625.js} +9 -0
  58. package/dist/{chunk-YM2WZIJ7.js → chunk-TMLEIURT.js} +1 -1
  59. package/dist/{chunk-EJQTOUPG.js → chunk-TYA65W4U.js} +4 -4
  60. package/dist/{chunk-XKHIUGY3.js → chunk-USWOTTSP.js} +1 -1
  61. package/dist/{chunk-KVA65K7M.js → chunk-UVUAU3CH.js} +2 -2
  62. package/dist/{chunk-NBQ7RWSG.js → chunk-VBTX4DPW.js} +3 -3
  63. package/dist/{chunk-H7EMX7GA.js → chunk-VW5OMJNG.js} +3 -3
  64. package/dist/{chunk-UBSAIOMW.js → chunk-XL2CUGEG.js} +105 -92
  65. package/dist/{chunk-5BJFUD4T.js → chunk-XZSH5GS7.js} +4 -4
  66. package/dist/{chunk-CLW66MZ7.js → chunk-Y2CZEJPK.js} +2 -2
  67. package/dist/{chunk-N4H5U5BT.js → chunk-YZMYYF2X.js} +2 -2
  68. package/dist/{chunk-U3ZB2Z53.js → chunk-ZN2VS7HX.js} +2 -2
  69. package/dist/{clean-GB5Z4WEM.js → clean-S5HLSHKF.js} +18 -16
  70. package/dist/cli.js +50 -50
  71. package/dist/{cloud-TEEFEBH2.js → cloud-YVFKYAPL.js} +4 -4
  72. package/dist/{cloudrun-6OB4LUGS.js → cloudrun-ISE6GSUO.js} +6 -5
  73. package/dist/{compare-DVPSR4DL.js → compare-ADNK57U4.js} +28 -26
  74. package/dist/{compiler-CYMKVOVA.js → compiler-QMEZK6CU.js} +4 -3
  75. package/dist/{component-U2INC5IT.js → component-PEY7SIFS.js} +3 -3
  76. package/dist/{compositions-74IQZR7R.js → compositions-45NJDJ4D.js} +13 -12
  77. package/dist/{delete-HEYUHORS.js → delete-5JV2P3JW.js} +5 -3
  78. package/dist/{deterministicFonts-RMJFQXDW.js → deterministicFonts-XS3PGWH5.js} +2 -2
  79. package/dist/{dist-HJFL4ZN5.js → dist-E44WU6LH.js} +1 -1
  80. package/dist/{dist-XAD3AJS3.js → dist-R24TLUYO.js} +13 -11
  81. package/dist/{doctor-ULRZSIUT.js → doctor-EPDFEGGY.js} +12 -11
  82. package/dist/{events-DMSH3WM4.js → events-G6FHOEJQ.js} +10 -9
  83. package/dist/{events-LESQE4GC.js → events-JIGBMB22.js} +11 -10
  84. package/dist/{feedback-2XHCU2ZK.js → feedback-Y7DL2O2L.js} +12 -11
  85. package/dist/{figma-BERLUHLI.js → figma-4RJX4QL5.js} +3 -3
  86. package/dist/fontLocalizeCli.js +11 -10
  87. package/dist/{get-4YBNEMTP.js → get-2VZICXM2.js} +5 -3
  88. package/dist/{gpuPolicy-HKUPBZJK.js → gpuPolicy-BQFPO7UA.js} +8 -7
  89. package/dist/{grade-compare-EPUP4JHB.js → grade-compare-UIR2Q6GF.js} +28 -26
  90. package/dist/{gsapParser-IQS5TBGJ.js → gsapParser-EZGJSSV5.js} +46 -11
  91. package/dist/{gsapParserAcorn-OYHIHFF5.js → gsapParserAcorn-SZANFUAQ.js} +3 -1
  92. package/dist/{help-7RREVPSW.js → help-LTA4R652.js} +1 -1
  93. package/dist/{history-DRM6E3K7.js → history-RJIZMB4C.js} +18 -16
  94. package/dist/hyperframe-runtime.js +1 -1
  95. package/dist/hyperframe.manifest.json +1 -1
  96. package/dist/hyperframe.runtime.iife.js +1 -1
  97. package/dist/hyperframes-player.global.js +1 -1
  98. package/dist/{info-T46MLAIJ.js → info-Q74CT24F.js} +13 -12
  99. package/dist/{init-XUENGFUL.js → init-ZPLWAASI.js} +23 -22
  100. package/dist/{inspect-UJ6VQ4AQ.js → inspect-ALYVRN2U.js} +29 -27
  101. package/dist/{installer-3EXYDXIP.js → installer-UZSVMUKT.js} +7 -6
  102. package/dist/{keyframes-W3CGZVFU.js → keyframes-7KIXRNZL.js} +23 -13
  103. package/dist/{lambda-AORIF7O3.js → lambda-U4WIJQCJ.js} +7 -6
  104. package/dist/{layout-ZWO2PFYM.js → layout-RGO5RRS3.js} +29 -27
  105. package/dist/{lint-363LQDQQ.js → lint-HF4ZT3Q2.js} +14 -13
  106. package/dist/{lintProject-ESEUOGXF.js → lintProject-4P4C6AN2.js} +1 -1
  107. package/dist/{list-4UY5CXXR.js → list-YFOSCQPU.js} +5 -3
  108. package/dist/{login-GQEPZV6R.js → login-6DWXT7KZ.js} +4 -4
  109. package/dist/{media-treatment-ALANZWB6.js → media-treatment-5G6F4AV6.js} +14 -13
  110. package/dist/{models-QKXFAS6W.js → models-LXUIU5DC.js} +9 -8
  111. package/dist/{motionShot-BMDELKKS.js → motionShot-XRSPP3G6.js} +12 -11
  112. package/dist/{normalize-audio-IUMGNRZP.js → normalize-audio-5SDICUXG.js} +12 -11
  113. package/dist/{pipeline-HB3B54ID.js → pipeline-OFFKTFXS.js} +8 -7
  114. package/dist/{play-BPMDBZ4B.js → play-64NBPQZX.js} +24 -22
  115. package/dist/{preflight-A5GAQUXY.js → preflight-TVCVP5P2.js} +9 -8
  116. package/dist/{present-TVA7OFEX.js → present-P65ZUVJ4.js} +19 -17
  117. package/dist/{preview-YPBQWOBV.js → preview-VHRLBZ6E.js} +20 -19
  118. package/dist/{producer-JX3R2SJD.js → producer-LGG3TOUI.js} +1 -1
  119. package/dist/{projectConfig-D72ZCV4U.js → projectConfig-V53SEFIS.js} +9 -8
  120. package/dist/{publication-NXK5YHOQ.js → publication-7RKKPKLM.js} +6 -5
  121. package/dist/{publish-5OD66ODR.js → publish-TRRFCEMQ.js} +11 -10
  122. package/dist/{remove-background-LBVJV7OI.js → remove-background-HJFT46Z5.js} +9 -8
  123. package/dist/{render-3P4RQGWW.js → render-3T3GAQRD.js} +16 -14
  124. package/dist/{render-WV3L66CZ.js → render-H6NZB4R5.js} +24 -23
  125. package/dist/{render-ZI2B2XFE.js → render-TPZ5FXHS.js} +5 -4
  126. package/dist/{render-batch-S3H4HVUJ.js → render-batch-4ZF5C2LN.js} +5 -4
  127. package/dist/{renderCancellation-O6XHCGN6.js → renderCancellation-4PYGAN5D.js} +8 -7
  128. package/dist/renderSetupWorker.js +1 -1
  129. package/dist/{resolver-Y3I7ADOB.js → resolver-4JRJPC6S.js} +7 -6
  130. package/dist/{sherpa-NIP27UXM.js → sherpa-MNCWRFD4.js} +11 -9
  131. package/dist/{skills-QIN53D3I.js → skills-REGO6WJ3.js} +13 -12
  132. package/dist/{skillsUpdateCheck-2QDUGU26.js → skillsUpdateCheck-7CKNNDEI.js} +3 -3
  133. package/dist/{snapshot-U4QNSCIR.js → snapshot-QGXDXDFW.js} +29 -27
  134. package/dist/{src-BH62F364.js → src-QF6RYX76.js} +10 -9
  135. package/dist/{src-3CKPQCXW.js → src-QX5PPANW.js} +7 -6
  136. package/dist/{staticProjectServer-2XMNYJTA.js → staticProjectServer-TEVGFDXU.js} +18 -16
  137. package/dist/{status-RP5AM4YY.js → status-C3IDZFDG.js} +8 -7
  138. package/dist/studio/assets/{hyperframes-player-C6muFtfk.js → hyperframes-player-fWt87gyh.js} +1 -1
  139. package/dist/studio/assets/{index-B6vU39NM.js → index-D34Oy5DN.js} +1 -1
  140. package/dist/studio/assets/{index-itc1XziP.js → index-D6TjRAvz.js} +171 -171
  141. package/dist/studio/assets/{index-BxnoLgxG.js → index-DofdzdxJ.js} +1 -1
  142. package/dist/studio/index.html +1 -1
  143. package/dist/studio/index.js +718 -438
  144. package/dist/studio/index.js.map +1 -1
  145. package/dist/{studio-api-OGPSLKV7.js → studio-api-VNIEVFVL.js} +12 -10
  146. package/dist/{studioServer-ZJ22K33M.js → studioServer-QRHT7WQE.js} +48 -44
  147. package/dist/{system-SHCBJHHU.js → system-OAB2LYBV.js} +8 -7
  148. package/dist/{telemetry-GK7XOOXI.js → telemetry-O6OTMDXT.js} +11 -10
  149. package/dist/{timeline-46FRT64J.js → timeline-GKK57X67.js} +18 -16
  150. package/dist/{tokens-HJ2LB2GR.js → tokens-2YYS3Y5M.js} +2 -2
  151. package/dist/{transcribe-S75EUMVN.js → transcribe-5V3UCT5O.js} +14 -13
  152. package/dist/{transcribe-IK5WYH4B.js → transcribe-AL3E5MBZ.js} +9 -8
  153. package/dist/{updateCheck-PLFHDWQS.js → updateCheck-Y6FACNPT.js} +2 -2
  154. package/dist/{upgrade-2VUYCOJA.js → upgrade-E2P6HGXJ.js} +2 -2
  155. package/dist/{urlDownloader-EMO4Y44J.js → urlDownloader-U3TNOPD5.js} +7 -6
  156. package/dist/{validate-G5S2R6QT.js → validate-PGI3PX6W.js} +17 -16
  157. package/dist/{video-5WCMLASZ.js → video-KAHL76HG.js} +8 -7
  158. package/package.json +1 -1
  159. package/dist/chunk-M5YNZQXZ.js +0 -3528
@@ -2173,6 +2173,9 @@ function getTimelinePlayheadTransform(time, pixelsPerSecond, contentOrigin, atRe
2173
2173
  function roundTo3(val) {
2174
2174
  return Math.round(val * 1e3) / 1e3;
2175
2175
  }
2176
+ function roundToLayoutPx(val) {
2177
+ return Math.round(val);
2178
+ }
2176
2179
  function roundToCenti(val) {
2177
2180
  return Math.round(val * 100) / 100;
2178
2181
  }
@@ -19808,6 +19811,8 @@ async function postGsapMutation(projectId, filePath, mutation, fallback) {
19808
19811
  }
19809
19812
 
19810
19813
  // src/hooks/timelineTimingSync.ts
19814
+ import { findTimelineScript } from "@hyperframes/core/gsap-parser-acorn";
19815
+ import { walkCompositionDescendants } from "@hyperframes/parsers/hf-ids";
19811
19816
  async function readFileContent(projectId, targetPath) {
19812
19817
  if (targetPath.includes("\0") || targetPath.includes("..")) {
19813
19818
  throw new Error(`Unsafe path: ${targetPath}`);
@@ -20007,8 +20012,19 @@ async function scaleGsapPositions(projectId, filePath, elementId2, oldStart, old
20007
20012
  "scale-positions failed"
20008
20013
  );
20009
20014
  }
20015
+ function sdkTimingGsapSync(result) {
20016
+ const timelineText = (html2) => {
20017
+ const scripts = [];
20018
+ walkCompositionDescendants(new DOMParser().parseFromString(html2, "text/html"), (el) => {
20019
+ if (el.tagName.toLowerCase() === "script") scripts.push(el);
20020
+ });
20021
+ return findTimelineScript(scripts)?.textContent ?? null;
20022
+ };
20023
+ if (timelineText(result.before) === timelineText(result.after)) return null;
20024
+ return { mutated: true, scriptText: extractGsapScriptText(result.after) };
20025
+ }
20010
20026
  function finishClipTimingFallback(input) {
20011
- const { projectId, targetPath, domId, edit } = input;
20027
+ const { projectId, targetPath, domId, edit, sdkGsap } = input;
20012
20028
  const timingChanged = edit.kind === "shift" ? edit.delta !== 0 : edit.from.start !== edit.to.start || edit.from.duration !== edit.to.duration;
20013
20029
  const runMutation = (pid, id) => edit.kind === "shift" ? shiftGsapPositions(pid, targetPath, id, edit.delta) : scaleGsapPositions(
20014
20030
  pid,
@@ -20023,7 +20039,7 @@ function finishClipTimingFallback(input) {
20023
20039
  return finishTimelineTimingFallback({
20024
20040
  iframe: input.iframe,
20025
20041
  reloadPreview: input.reloadPreview,
20026
- gsapMutation: timingChanged && domId && projectId ? () => foldGsapMutationIntoHistory({
20042
+ gsapMutation: sdkGsap ? () => Promise.resolve(sdkGsap) : timingChanged && domId && projectId ? () => foldGsapMutationIntoHistory({
20027
20043
  writeFile: input.writeProjectFile,
20028
20044
  paths: [targetPath],
20029
20045
  projectId,
@@ -20043,6 +20059,17 @@ async function finishGroupTimingGsapFallback(input) {
20043
20059
  (change) => input.resolveChangePath(change.element) !== activePath
20044
20060
  );
20045
20061
  const onGsapError = (err) => console.error(`[Timeline] ${input.errorLabel}`, err);
20062
+ const { sdkGsap } = input;
20063
+ if (sdkGsap) {
20064
+ await finishTimelineTimingFallback({
20065
+ iframe: input.iframe,
20066
+ reloadPreview: input.reloadPreview,
20067
+ gsapMutation: () => Promise.resolve(sdkGsap),
20068
+ onGsapError,
20069
+ rebindWhenUnmutated: !otherFileChanged
20070
+ });
20071
+ return;
20072
+ }
20046
20073
  await finishTimelineTimingFallback({
20047
20074
  iframe: input.iframe,
20048
20075
  reloadPreview: input.reloadPreview,
@@ -30159,12 +30186,52 @@ function cropRectFromInsets(rect, insets, scaleX, scaleY) {
30159
30186
  height: Math.max(0, rect.height - (insets.top + insets.bottom) * sy)
30160
30187
  };
30161
30188
  }
30162
- function readElementCropInsets(element) {
30189
+ var cropLifts = /* @__PURE__ */ new WeakMap();
30190
+ function liftSelector(element) {
30191
+ const quote = (value) => `"${value.replace(/["\\]/g, "\\$&")}"`;
30192
+ const hfId = element.getAttribute("data-hf-id");
30193
+ if (hfId) return `[data-hf-id=${quote(hfId)}]`;
30194
+ return element.id ? `[id=${quote(element.id)}]` : null;
30195
+ }
30196
+ function liftElementCrop(element) {
30197
+ const selector = liftSelector(element);
30198
+ if (!selector || cropLifts.has(element)) return;
30199
+ const doc = element.ownerDocument;
30200
+ const rule = doc.createElement("style");
30201
+ rule.textContent = `${selector}{clip-path:none!important}`;
30202
+ (doc.head ?? doc.documentElement).append(rule);
30203
+ cropLifts.set(element, { rule });
30204
+ }
30205
+ function dropElementCropLift(element) {
30206
+ cropLifts.get(element)?.rule.remove();
30207
+ cropLifts.delete(element);
30208
+ }
30209
+ function isElementCropLifted(element) {
30210
+ return cropLifts.has(element);
30211
+ }
30212
+ var computedClipPath = (element) => element.ownerDocument.defaultView?.getComputedStyle(element).clipPath.trim() || "";
30213
+ function readElementClipPath(element) {
30163
30214
  const inline = element.style.getPropertyValue("clip-path").trim();
30164
- const value = inline || element.ownerDocument.defaultView?.getComputedStyle(element).clipPath.trim() || "";
30215
+ const lift = cropLifts.get(element);
30216
+ if (inline || !lift) return inline || computedClipPath(element);
30217
+ if (lift.sheetClip === void 0) {
30218
+ const { parentNode, nextSibling } = lift.rule;
30219
+ lift.rule.remove();
30220
+ lift.sheetClip = computedClipPath(element);
30221
+ parentNode?.insertBefore(lift.rule, nextSibling);
30222
+ }
30223
+ return lift.sheetClip;
30224
+ }
30225
+ function parseCropClipPath(value) {
30165
30226
  if (!value || value === "none") return { top: 0, right: 0, bottom: 0, left: 0, radius: 0 };
30166
30227
  return parseInsetClipPathSides(value);
30167
30228
  }
30229
+ function readElementCropInsets(element) {
30230
+ return parseCropClipPath(readElementClipPath(element));
30231
+ }
30232
+ function hasCropInsets(insets) {
30233
+ return insets.top > 0 || insets.right > 0 || insets.bottom > 0 || insets.left > 0;
30234
+ }
30168
30235
  function clampInset(value, max) {
30169
30236
  if (!Number.isFinite(value)) return 0;
30170
30237
  return Math.min(Math.max(0, value), Math.max(0, max));
@@ -30206,7 +30273,7 @@ function resolveCropInsetFromMoveDrag(input) {
30206
30273
  return { left, right: totalX - left, top, bottom: totalY - top };
30207
30274
  }
30208
30275
  function hugRectForElement(rect, element) {
30209
- const insets = readElementCropInsets(element);
30276
+ const insets = isElementCropLifted(element) ? null : readElementCropInsets(element);
30210
30277
  if (!insets || insets.top <= 0 && insets.right <= 0 && insets.bottom <= 0 && insets.left <= 0)
30211
30278
  return rect;
30212
30279
  return cropRectFromInsets(rect, insets, rect.editScaleX, rect.editScaleY);
@@ -30944,9 +31011,6 @@ function pointerAngleDegrees(centerX, centerY, x, y) {
30944
31011
  function normalizeAngleDelta(delta) {
30945
31012
  return ((delta + 180) % 360 + 360) % 360 - 180;
30946
31013
  }
30947
- function roundAngle(angle) {
30948
- return Math.round(angle * 10) / 10;
30949
- }
30950
31014
  function resolveDomEditRotationGesture(input) {
30951
31015
  const startAngle = pointerAngleDegrees(input.centerX, input.centerY, input.startX, input.startY);
30952
31016
  const currentAngle = pointerAngleDegrees(
@@ -30958,7 +31022,7 @@ function resolveDomEditRotationGesture(input) {
30958
31022
  const delta = normalizeAngleDelta(currentAngle - startAngle);
30959
31023
  const angle = input.actualAngle + delta;
30960
31024
  return {
30961
- angle: input.snap ? Math.round(angle / ROTATION_SNAP_DEGREES) * ROTATION_SNAP_DEGREES : roundAngle(angle)
31025
+ angle: input.snap ? Math.round(angle / ROTATION_SNAP_DEGREES) * ROTATION_SNAP_DEGREES : roundTo3(angle)
30962
31026
  };
30963
31027
  }
30964
31028
  function hasDomEditRotationChanged(initialAngle, nextAngle) {
@@ -31317,9 +31381,6 @@ var STUDIO_ROTATION_TRANSFORM_ORIGIN = "center center";
31317
31381
  function finiteNumber2(value) {
31318
31382
  return typeof value === "number" && Number.isFinite(value) ? value : null;
31319
31383
  }
31320
- function roundRotationAngle(angle) {
31321
- return Math.round(angle * 10) / 10;
31322
- }
31323
31384
  function normalizeStudioFileChangePath(path) {
31324
31385
  return path.trim().replace(/\\/g, "/").replace(/^\.?\//, "");
31325
31386
  }
@@ -31523,8 +31584,8 @@ function prepareStudioPathOffsetBase(element, updateBase) {
31523
31584
  function writeStudioPathOffsetVars(element, offset, options = {}) {
31524
31585
  prepareStudioPathOffsetBase(element, options.updateBase ?? true);
31525
31586
  element.setAttribute(STUDIO_PATH_OFFSET_ATTR, "true");
31526
- element.style.setProperty(STUDIO_OFFSET_X_PROP, `${Math.round(offset.x)}px`);
31527
- element.style.setProperty(STUDIO_OFFSET_Y_PROP, `${Math.round(offset.y)}px`);
31587
+ element.style.setProperty(STUDIO_OFFSET_X_PROP, `${roundTo3(offset.x)}px`);
31588
+ element.style.setProperty(STUDIO_OFFSET_Y_PROP, `${roundTo3(offset.y)}px`);
31528
31589
  }
31529
31590
  function isIdentityAfterTranslateStrip(m) {
31530
31591
  return m.is2D && m.a === 1 && m.b === 0 && m.c === 0 && m.d === 1;
@@ -31594,7 +31655,7 @@ function applyStudioPathOffsetDraft(element, offset) {
31594
31655
  if (applyStudioPathOffsetViaGsap(element, offset)) return;
31595
31656
  element.style.setProperty(
31596
31657
  "translate",
31597
- composeTranslateValue(element, `${Math.round(offset.x)}px`, `${Math.round(offset.y)}px`)
31658
+ composeTranslateValue(element, `${roundTo3(offset.x)}px`, `${roundTo3(offset.y)}px`)
31598
31659
  );
31599
31660
  stripGsapTranslateFromTransform(element);
31600
31661
  }
@@ -31604,7 +31665,7 @@ function readParentFlexBasisPixels(element, size) {
31604
31665
  const display2 = readStyleOrComputed(parent, "display").trim();
31605
31666
  if (display2 !== "flex" && display2 !== "inline-flex") return null;
31606
31667
  const direction = readStyleOrComputed(parent, "flex-direction").trim();
31607
- return Math.round(Math.max(1, direction.startsWith("column") ? size.height : size.width));
31668
+ return roundToLayoutPx(Math.max(1, direction.startsWith("column") ? size.height : size.width));
31608
31669
  }
31609
31670
  function restoreStaleStudioScaleResize(element) {
31610
31671
  if (!element.hasAttribute(STUDIO_ORIGINAL_SCALE_ATTR)) return;
@@ -31667,14 +31728,14 @@ function writeStudioBoxSizeVars(element, size) {
31667
31728
  element.setAttribute(STUDIO_ORIGINAL_DISPLAY_ATTR, element.style.getPropertyValue("display"));
31668
31729
  }
31669
31730
  element.setAttribute(STUDIO_BOX_SIZE_ATTR, "true");
31670
- element.style.setProperty(STUDIO_WIDTH_PROP, `${Math.round(Math.max(1, size.width))}px`);
31671
- element.style.setProperty(STUDIO_HEIGHT_PROP, `${Math.round(Math.max(1, size.height))}px`);
31731
+ element.style.setProperty(STUDIO_WIDTH_PROP, `${roundToLayoutPx(Math.max(1, size.width))}px`);
31732
+ element.style.setProperty(STUDIO_HEIGHT_PROP, `${roundToLayoutPx(Math.max(1, size.height))}px`);
31672
31733
  }
31673
31734
  function applyStudioBoxSizeDimensions(element, size) {
31674
31735
  writeStudioBoxSizeVars(element, size);
31675
31736
  restoreStaleStudioScaleResize(element);
31676
- const width = Math.round(Math.max(1, size.width));
31677
- const height = Math.round(Math.max(1, size.height));
31737
+ const width = roundToLayoutPx(Math.max(1, size.width));
31738
+ const height = roundToLayoutPx(Math.max(1, size.height));
31678
31739
  element.style.setProperty("box-sizing", "border-box");
31679
31740
  element.style.setProperty("width", `${width}px`);
31680
31741
  element.style.setProperty("height", `${height}px`);
@@ -31697,7 +31758,17 @@ function applyStudioBoxSize(element, size) {
31697
31758
  promoteInlineForTransform(element);
31698
31759
  applyStudioBoxSizeDimensions(element, size);
31699
31760
  }
31761
+ var boxSizeDraftBases = /* @__PURE__ */ new WeakMap();
31762
+ function readStudioBoxSizeDraftBase(element) {
31763
+ return boxSizeDraftBases.get(element) ?? null;
31764
+ }
31765
+ function forgetStudioBoxSizeDraftBase(element) {
31766
+ boxSizeDraftBases.delete(element);
31767
+ }
31700
31768
  function applyStudioBoxSizeDraft(element, size) {
31769
+ if (!boxSizeDraftBases.has(element)) {
31770
+ boxSizeDraftBases.set(element, { width: element.offsetWidth, height: element.offsetHeight });
31771
+ }
31701
31772
  promoteInlineForTransform(element);
31702
31773
  applyStudioBoxSizeDimensions(element, size);
31703
31774
  }
@@ -31735,7 +31806,7 @@ function prepareStudioRotationBase(element, updateBase) {
31735
31806
  function writeStudioRotationVars(element, rotation, options = {}) {
31736
31807
  prepareStudioRotationBase(element, options.updateBase ?? true);
31737
31808
  element.setAttribute(STUDIO_ROTATION_ATTR, "true");
31738
- element.style.setProperty(STUDIO_ROTATION_PROP, `${roundRotationAngle(rotation.angle)}deg`);
31809
+ element.style.setProperty(STUDIO_ROTATION_PROP, `${roundTo3(rotation.angle)}deg`);
31739
31810
  element.style.setProperty("transform-origin", STUDIO_ROTATION_TRANSFORM_ORIGIN);
31740
31811
  }
31741
31812
  function applyStudioRotation(element, rotation) {
@@ -31753,7 +31824,7 @@ function applyStudioRotationDraft(element, rotation) {
31753
31824
  element.setAttribute(STUDIO_ROTATION_DRAFT_ATTR, "true");
31754
31825
  element.style.setProperty(
31755
31826
  "rotate",
31756
- composeStudioRotationValue(element, `${roundRotationAngle(rotation.angle)}deg`)
31827
+ composeStudioRotationValue(element, `${roundTo3(rotation.angle)}deg`)
31757
31828
  );
31758
31829
  }
31759
31830
 
@@ -31921,6 +31992,7 @@ function restoreStyleProperty(element, property, value) {
31921
31992
  else element.style.removeProperty(property);
31922
31993
  }
31923
31994
  function restoreStudioBoxSize(element, previous) {
31995
+ forgetStudioBoxSizeDraftBase(element);
31924
31996
  restoreStyleProperty(element, "width", previous.width);
31925
31997
  restoreStyleProperty(element, "height", previous.height);
31926
31998
  restoreStyleProperty(element, "min-width", previous.minWidth);
@@ -32050,6 +32122,7 @@ function clearStudioRotation(element) {
32050
32122
  element.removeAttribute(STUDIO_ORIGINAL_ROTATION_TRANSFORM_ORIGIN_ATTR);
32051
32123
  }
32052
32124
  function clearStudioBoxSize(element) {
32125
+ forgetStudioBoxSizeDraftBase(element);
32053
32126
  if (element.hasAttribute(STUDIO_BOX_SIZE_ATTR) || styleUsesStudioSize(element.style.getPropertyValue("width")) || styleUsesStudioSize(element.style.getPropertyValue("height")) || element.hasAttribute(STUDIO_ORIGINAL_SCALE_ATTR)) {
32054
32127
  restoreOriginalBoxSizeProperty(element, "width", STUDIO_ORIGINAL_WIDTH_ATTR);
32055
32128
  restoreOriginalBoxSizeProperty(element, "height", STUDIO_ORIGINAL_HEIGHT_ATTR);
@@ -32303,13 +32376,341 @@ function computeDraggedGsapPosition(element, studioOffset, fallbackBase) {
32303
32376
  const baseGsapX = Number.isFinite(parsedBaseX) ? parsedBaseX : fallbackBase.x;
32304
32377
  const baseGsapY = Number.isFinite(parsedBaseY) ? parsedBaseY : fallbackBase.y;
32305
32378
  return {
32306
- newX: Math.round(baseGsapX + adjX),
32307
- newY: Math.round(baseGsapY + adjY),
32379
+ newX: roundTo3(baseGsapX + adjX),
32380
+ newY: roundTo3(baseGsapY + adjY),
32308
32381
  baseGsapX,
32309
32382
  baseGsapY
32310
32383
  };
32311
32384
  }
32312
32385
 
32386
+ // src/hooks/gsapRuntimeKeyframes.ts
32387
+ import { buildArcPath } from "@hyperframes/core/gsap-parser-acorn";
32388
+ var FLAT_SKIP_KEYS = /* @__PURE__ */ new Set([
32389
+ "ease",
32390
+ "duration",
32391
+ "delay",
32392
+ "stagger",
32393
+ "motionPath",
32394
+ "overwrite",
32395
+ "immediateRender",
32396
+ "onComplete",
32397
+ "onUpdate",
32398
+ "onStart",
32399
+ "keyframes"
32400
+ ]);
32401
+ function timelinesOf(iframe) {
32402
+ if (!iframe?.contentWindow) return null;
32403
+ try {
32404
+ return iframe.contentWindow.__timelines ?? null;
32405
+ } catch {
32406
+ return null;
32407
+ }
32408
+ }
32409
+ function isXY(p) {
32410
+ return !!p && typeof p.x === "number" && typeof p.y === "number";
32411
+ }
32412
+ function isZeroDurationSet(duration) {
32413
+ return !(duration > 0);
32414
+ }
32415
+ function coordsFromMotionPath(mp) {
32416
+ if (!mp || typeof mp !== "object") return null;
32417
+ const obj = mp;
32418
+ const pathVal = Array.isArray(mp) ? mp : obj.path;
32419
+ if (!Array.isArray(pathVal)) return null;
32420
+ const coords = pathVal.filter(isXY).map((p) => ({ x: p.x, y: p.y }));
32421
+ if (coords.length < 2) return null;
32422
+ const curviness = typeof obj.curviness === "number" ? obj.curviness : 1;
32423
+ const autoRotate = typeof obj.autoRotate === "number" ? obj.autoRotate : obj.autoRotate === true;
32424
+ return { coords, curviness, autoRotate, isCubic: obj.type === "cubic" };
32425
+ }
32426
+ function flatTweenKeyframes(vars) {
32427
+ const properties = {};
32428
+ for (const [k, v] of Object.entries(vars)) {
32429
+ if (FLAT_SKIP_KEYS.has(k)) continue;
32430
+ if (typeof v === "number") properties[k] = roundTo3(v);
32431
+ else if (typeof v === "string") properties[k] = v;
32432
+ }
32433
+ if (Object.keys(properties).length === 0) return null;
32434
+ return [
32435
+ { percentage: 0, properties },
32436
+ { percentage: 100, properties }
32437
+ ];
32438
+ }
32439
+ function readTween(vars) {
32440
+ if (vars.keyframes && typeof vars.keyframes === "object") {
32441
+ const parsed = parsePercentageKeyframes(vars.keyframes);
32442
+ if (parsed) return parsed;
32443
+ }
32444
+ const mp = coordsFromMotionPath(vars.motionPath);
32445
+ if (mp) {
32446
+ const shape = buildArcPath(mp.coords, mp.curviness, mp.autoRotate, mp.isCubic);
32447
+ if (shape) {
32448
+ const n = shape.waypoints.length;
32449
+ const keyframes = shape.waypoints.map((wp, i) => ({
32450
+ percentage: n > 1 ? Math.round(i / (n - 1) * 100) : 0,
32451
+ properties: { x: wp.x, y: wp.y }
32452
+ }));
32453
+ return { keyframes, arcPath: shape.arcPath };
32454
+ }
32455
+ }
32456
+ const flat = flatTweenKeyframes(vars);
32457
+ return flat ? { keyframes: flat } : null;
32458
+ }
32459
+ function matchesElement(tween, el) {
32460
+ if (!tween.targets) return false;
32461
+ for (const t of tween.targets()) {
32462
+ if (t === el || el.id && t.id === el.id) return true;
32463
+ }
32464
+ return false;
32465
+ }
32466
+ function tweenTiming(tween) {
32467
+ const rawStart = typeof tween.startTime === "function" ? tween.startTime() : 0;
32468
+ const rawDur = typeof tween.duration === "function" ? tween.duration() : 0;
32469
+ return {
32470
+ start: Number.isFinite(rawStart) ? rawStart : 0,
32471
+ duration: Number.isFinite(rawDur) ? rawDur : 0
32472
+ };
32473
+ }
32474
+ function varsCarryChannel(vars, channels) {
32475
+ if (!vars) return false;
32476
+ for (const ch of channels) {
32477
+ if (Object.prototype.hasOwnProperty.call(vars, ch)) return true;
32478
+ }
32479
+ return false;
32480
+ }
32481
+ function keyframeVarsCarryChannel(vars, channels) {
32482
+ const kf = vars?.keyframes;
32483
+ if (!kf || typeof kf !== "object") return false;
32484
+ if (!Array.isArray(kf) && channels.some((ch) => Object.prototype.hasOwnProperty.call(kf, ch)))
32485
+ return true;
32486
+ const steps = Array.isArray(kf) ? kf : Object.values(kf);
32487
+ return steps.some(
32488
+ (step) => step != null && typeof step === "object" && channels.some((ch) => Object.prototype.hasOwnProperty.call(step, ch))
32489
+ );
32490
+ }
32491
+ function resolveRuntimeTween(iframe, selector, kind, compositionId, channels) {
32492
+ const timelines = timelinesOf(iframe);
32493
+ if (!timelines) return null;
32494
+ let targetEl = null;
32495
+ try {
32496
+ targetEl = iframe?.contentDocument?.querySelector(selector) ?? null;
32497
+ } catch {
32498
+ return null;
32499
+ }
32500
+ if (!targetEl) return null;
32501
+ const tlIds = compositionId ? [compositionId] : Object.keys(timelines).filter((k) => typeof timelines[k]?.getChildren === "function");
32502
+ const wantChannels = channels && channels.length > 0 ? channels : null;
32503
+ let first = null;
32504
+ let channelMatch = null;
32505
+ for (const tlId of tlIds) {
32506
+ const timeline = timelines[tlId];
32507
+ if (!timeline?.getChildren) continue;
32508
+ const now2 = typeof timeline.time === "function" ? timeline.time() : null;
32509
+ for (const tween of timeline.getChildren(true)) {
32510
+ if (!tween.vars || !matchesElement(tween, targetEl)) continue;
32511
+ const dur = typeof tween.duration === "function" ? tween.duration() : 0;
32512
+ const isSet = !(dur > 0);
32513
+ if (kind === "set" ? !isSet : isSet) continue;
32514
+ if (wantChannels) {
32515
+ const carries = kind === "set" ? varsCarryChannel(tween.vars, wantChannels) : keyframeVarsCarryChannel(tween.vars, wantChannels);
32516
+ if (carries) {
32517
+ if (channelMatch === null) channelMatch = { tween, timeline };
32518
+ } else if (first === null) {
32519
+ first = { tween, timeline };
32520
+ }
32521
+ continue;
32522
+ }
32523
+ if (first === null) first = { tween, timeline };
32524
+ if (kind === "keyframe" && now2 != null) {
32525
+ const start = typeof tween.startTime === "function" ? tween.startTime() : 0;
32526
+ if (now2 >= start - 1e-3 && now2 <= start + dur + 1e-3) return { tween, timeline };
32527
+ }
32528
+ }
32529
+ }
32530
+ return channelMatch ?? first;
32531
+ }
32532
+ function readCarriesChannel(read, channels) {
32533
+ return read.keyframes.some((kf) => channels.some((c) => kf.properties[c] != null));
32534
+ }
32535
+ function readRuntimeKeyframes(iframe, selector, compositionId, requireChannels) {
32536
+ const timelines = timelinesOf(iframe);
32537
+ if (!timelines) return null;
32538
+ let targetEl = null;
32539
+ try {
32540
+ targetEl = iframe?.contentDocument?.querySelector(selector) ?? null;
32541
+ } catch {
32542
+ return null;
32543
+ }
32544
+ if (!targetEl) return null;
32545
+ const tlIds = compositionId ? [compositionId] : Object.keys(timelines).filter((k) => typeof timelines[k]?.getChildren === "function");
32546
+ if (tlIds.length === 0) return null;
32547
+ let firstRead = null;
32548
+ for (const tlId of tlIds) {
32549
+ const timeline = timelines[tlId];
32550
+ if (!timeline?.getChildren) continue;
32551
+ const now2 = typeof timeline.time === "function" ? timeline.time() : null;
32552
+ for (const tween of timeline.getChildren(true)) {
32553
+ if (!tween.vars || !matchesElement(tween, targetEl)) continue;
32554
+ const dur = typeof tween.duration === "function" ? tween.duration() : 0;
32555
+ if (isZeroDurationSet(dur)) continue;
32556
+ const read = readTween(tween.vars);
32557
+ if (!read) continue;
32558
+ if (requireChannels && !readCarriesChannel(read, requireChannels)) continue;
32559
+ if (firstRead === null) firstRead = read;
32560
+ if (now2 != null) {
32561
+ const start = typeof tween.startTime === "function" ? tween.startTime() : 0;
32562
+ if (now2 >= start - 1e-3 && now2 <= start + dur + 1e-3) return read;
32563
+ }
32564
+ }
32565
+ }
32566
+ return firstRead;
32567
+ }
32568
+ function hasNonHoldTweenForElement(iframe, selector, compositionId, channels) {
32569
+ let targetEl = null;
32570
+ try {
32571
+ targetEl = iframe?.contentDocument?.querySelector(selector) ?? null;
32572
+ } catch {
32573
+ return false;
32574
+ }
32575
+ return !!targetEl && hasNonHoldTween(timelinesOf(iframe), targetEl, channels, compositionId);
32576
+ }
32577
+ var POSITION_CHANNELS = [
32578
+ "x",
32579
+ "y",
32580
+ "xPercent",
32581
+ "yPercent",
32582
+ "left",
32583
+ "top",
32584
+ // readTween reads the authored translateX/Y; GSAP normalizes them to x/y only at play time.
32585
+ "translateX",
32586
+ "translateY"
32587
+ ];
32588
+ var MOVE_CHANNELS = [...POSITION_CHANNELS, "motionPath"];
32589
+ function gsapWritesChannels(el, channels) {
32590
+ const win = el.ownerDocument.defaultView;
32591
+ return Object.values(win?.__timelines ?? {}).some(
32592
+ (tl) => (tl?.getChildren?.(true) ?? []).some(
32593
+ (tween) => !!tween.vars && matchesElement(tween, el) && (channels.some((ch) => ch in tween.vars) || keyframeVarsCarryChannel(tween.vars, channels))
32594
+ )
32595
+ );
32596
+ }
32597
+ function gsapWritesPosition(el) {
32598
+ const cache2 = el._gsap;
32599
+ return !!cache2?.renderTransform || gsapWritesChannels(el, MOVE_CHANNELS);
32600
+ }
32601
+ function elementHasNonHoldTween(el, channels) {
32602
+ const win = el.ownerDocument.defaultView;
32603
+ return hasNonHoldTween(win?.__timelines ?? null, el, channels);
32604
+ }
32605
+ function hasNonHoldTween(timelines, targetEl, channels, compositionId) {
32606
+ if (!timelines) return false;
32607
+ for (const tlId of compositionId ? [compositionId] : Object.keys(timelines)) {
32608
+ for (const tween of timelines[tlId]?.getChildren?.(true) ?? []) {
32609
+ if (!tween.vars || !matchesElement(tween, targetEl)) continue;
32610
+ const dur = typeof tween.duration === "function" ? tween.duration() : 0;
32611
+ if (isZeroDurationSet(dur)) continue;
32612
+ if (channels && keyframeVarsCarryChannel(tween.vars, channels)) return true;
32613
+ const read = readTween(tween.vars);
32614
+ if (read && (!channels || readCarriesChannel(read, channels))) return true;
32615
+ }
32616
+ }
32617
+ return false;
32618
+ }
32619
+ function toClipRelative(keyframes, tweenStart, tweenDuration, clip) {
32620
+ if (!clip || clip.duration <= 0) return keyframes;
32621
+ return keyframes.map((kf) => {
32622
+ const abs = toAbsoluteTime(tweenStart, tweenDuration, kf.percentage);
32623
+ return { ...kf, percentage: Math.round((abs - clip.start) / clip.duration * 1e5) / 1e3 };
32624
+ });
32625
+ }
32626
+ function buildEntry(read, start, duration, clip) {
32627
+ return {
32628
+ keyframes: toClipRelative(read.keyframes, start, duration, clip),
32629
+ tweenStart: start,
32630
+ tweenDuration: duration,
32631
+ ...read.easeEach ? { easeEach: read.easeEach } : {},
32632
+ ...read.arcPath ? { arcPath: read.arcPath } : {}
32633
+ };
32634
+ }
32635
+ function addScanEntry(result, tween, clipById) {
32636
+ if (!tween.targets || !tween.vars) return;
32637
+ const { start, duration } = tweenTiming(tween);
32638
+ if (isZeroDurationSet(duration)) return;
32639
+ const read = readTween(tween.vars);
32640
+ if (!read) return;
32641
+ for (const target of tween.targets()) {
32642
+ const id = target.id;
32643
+ if (id && !result.has(id)) result.set(id, buildEntry(read, start, duration, clipById?.get(id)));
32644
+ }
32645
+ }
32646
+ function scanAllRuntimeKeyframes(iframe, clipById) {
32647
+ const result = /* @__PURE__ */ new Map();
32648
+ const timelines = timelinesOf(iframe);
32649
+ if (!timelines) return result;
32650
+ for (const timeline of Object.values(timelines)) {
32651
+ if (!timeline?.getChildren) continue;
32652
+ for (const tween of timeline.getChildren(true)) addScanEntry(result, tween, clipById);
32653
+ }
32654
+ return result;
32655
+ }
32656
+
32657
+ // src/components/editor/plainTranslate.ts
32658
+ var TERM = /^(-?\d*\.?\d+(?:e[-+]?\d+)?)(px|%)$/i;
32659
+ var SIGN = { "+": 1, "-": -1 };
32660
+ function termPx(term, side) {
32661
+ const match = TERM.exec(term ?? "");
32662
+ return match ? Number(match[1]) * (match[2] === "%" ? side / 100 : 1) : null;
32663
+ }
32664
+ function resolveLength(value, side) {
32665
+ const parts = (/^calc\((.*)\)$/.exec(value)?.[1] ?? value).split(" ");
32666
+ if (parts.length % 2 === 0) return null;
32667
+ let total = termPx(parts[0], side);
32668
+ for (let i = 1; i < parts.length && total !== null; i += 2) {
32669
+ const term = termPx(parts[i + 1], side);
32670
+ const sign = SIGN[parts[i] ?? ""];
32671
+ total = term === null || !sign ? null : total + sign * term;
32672
+ }
32673
+ return total;
32674
+ }
32675
+ function borderBox(cs, size, a, b) {
32676
+ const n = (prop) => Number.parseFloat(cs.getPropertyValue(prop)) || 0;
32677
+ const pad = n(`padding-${a}`) + n(`padding-${b}`) + n(`border-${a}-width`) + n(`border-${b}-width`);
32678
+ return n(size) + (cs.boxSizing === "border-box" ? 0 : pad);
32679
+ }
32680
+ function resolveInTransform(el, x, y) {
32681
+ const style = el.style;
32682
+ const saved2 = [style.getPropertyValue("transform"), style.getPropertyPriority("transform")];
32683
+ style.setProperty("transform", `translate(${x}, ${y})`, "important");
32684
+ try {
32685
+ const resolved2 = el.ownerDocument.defaultView?.getComputedStyle(el).transform ?? "";
32686
+ const matrix = new DOMMatrixReadOnly(resolved2.startsWith("matrix") ? resolved2 : void 0);
32687
+ return { x: matrix.m41, y: matrix.m42 };
32688
+ } finally {
32689
+ style.setProperty("transform", saved2[0] ?? "", saved2[1] ?? "");
32690
+ }
32691
+ }
32692
+ function readTranslatePx(el) {
32693
+ const cs = el.ownerDocument.defaultView?.getComputedStyle(el);
32694
+ const value = el.style.getPropertyValue("translate") || cs?.translate || "none";
32695
+ if (value === "none") return { x: 0, y: 0 };
32696
+ const [x = "0px", y = "0px"] = splitTopLevelWhitespace(value);
32697
+ const px = cs && resolveLength(x, borderBox(cs, "width", "left", "right"));
32698
+ const py = cs && resolveLength(y, borderBox(cs, "height", "top", "bottom"));
32699
+ return px != null && py != null ? { x: px, y: py } : resolveInTransform(el, x, y);
32700
+ }
32701
+ function formatTranslatePx(p) {
32702
+ return `${roundTo3(p.x)}px ${roundTo3(p.y)}px`;
32703
+ }
32704
+ function translatePatch(p) {
32705
+ return { type: "inline-style", property: "translate", value: formatTranslatePx(p) };
32706
+ }
32707
+ function writeTranslatePx(el, p) {
32708
+ el.style.setProperty("translate", formatTranslatePx(p));
32709
+ }
32710
+ function readMoveOffset(el) {
32711
+ return gsapWritesPosition(el) ? readStudioPathOffset(el) : readTranslatePx(el);
32712
+ }
32713
+
32313
32714
  // src/components/editor/manualOffsetDrag.ts
32314
32715
  function getOffsetDragGsap(element) {
32315
32716
  const win = element.ownerDocument.defaultView;
@@ -32421,14 +32822,15 @@ function measureManualOffsetDragScreenToOffsetMatrix(element, initialOffset, opt
32421
32822
  if (!Number.isFinite(probeSize) || probeSize <= 0) {
32422
32823
  return { ok: false, reason: "Invalid movement probe size." };
32423
32824
  }
32825
+ const draw = options.plainTranslate ? writeTranslatePx : applyStudioPathOffsetDraft;
32424
32826
  const snapshot3 = captureStudioPathOffset(element);
32425
32827
  try {
32426
- applyStudioPathOffsetDraft(element, initialOffset);
32828
+ draw(element, initialOffset);
32427
32829
  const origin = getRectCenter(element);
32428
32830
  if (!origin) {
32429
32831
  return { ok: false, reason: "Element has no measurable box." };
32430
32832
  }
32431
- applyStudioPathOffsetDraft(element, {
32833
+ draw(element, {
32432
32834
  x: initialOffset.x + probeSize,
32433
32835
  y: initialOffset.y
32434
32836
  });
@@ -32436,7 +32838,7 @@ function measureManualOffsetDragScreenToOffsetMatrix(element, initialOffset, opt
32436
32838
  if (!probeX) {
32437
32839
  return { ok: false, reason: "Element X movement could not be measured." };
32438
32840
  }
32439
- applyStudioPathOffsetDraft(element, {
32841
+ draw(element, {
32440
32842
  x: initialOffset.x,
32441
32843
  y: initialOffset.y + probeSize
32442
32844
  });
@@ -32481,14 +32883,13 @@ function resolveManualOffsetForPointerDelta(input) {
32481
32883
  };
32482
32884
  }
32483
32885
  function createManualOffsetDragMember(input) {
32484
- const initialOffset = readAppliedStudioPathOffset(input.element);
32485
- input.element.setAttribute("data-hf-drag-initial-offset-x", String(initialOffset.x));
32486
- input.element.setAttribute("data-hf-drag-initial-offset-y", String(initialOffset.y));
32886
+ const plainTranslate = !!input.gesture && !gsapWritesPosition(input.element);
32887
+ const initialOffset = plainTranslate ? readTranslatePx(input.element) : readAppliedStudioPathOffset(input.element);
32487
32888
  const win = input.element.ownerDocument.defaultView;
32488
- const gsapX = win?.gsap?.getProperty?.(input.element, "x") || 0;
32489
- const gsapY = win?.gsap?.getProperty?.(input.element, "y") || 0;
32490
- input.element.setAttribute("data-hf-drag-gsap-base-x", String(gsapX));
32491
- input.element.setAttribute("data-hf-drag-gsap-base-y", String(gsapY));
32889
+ const gsapX = !plainTranslate && win?.gsap?.getProperty?.(input.element, "x") || 0;
32890
+ const gsapY = !plainTranslate && win?.gsap?.getProperty?.(input.element, "y") || 0;
32891
+ const baseGsap = { x: gsapX, y: gsapY };
32892
+ if (!plainTranslate) stampGestureBase(input.element, initialOffset, baseGsap);
32492
32893
  if (win?.__timelines) {
32493
32894
  const paused = [];
32494
32895
  for (const [id, tl] of Object.entries(win.__timelines)) {
@@ -32508,9 +32909,9 @@ function createManualOffsetDragMember(input) {
32508
32909
  const gestureToken = beginStudioManualEditGesture(input.element);
32509
32910
  const measured = measureManualOffsetDragScreenToOffsetMatrix(input.element, initialOffset, {
32510
32911
  scaleX: input.rect.editScaleX,
32511
- scaleY: input.rect.editScaleY
32912
+ scaleY: input.rect.editScaleY,
32913
+ plainTranslate
32512
32914
  });
32513
- const baseGsap = { x: gsapX, y: gsapY };
32514
32915
  if (!measured.ok) {
32515
32916
  const scaleX = input.rect.editScaleX || 1;
32516
32917
  const scaleY = input.rect.editScaleY || 1;
@@ -32522,6 +32923,7 @@ function createManualOffsetDragMember(input) {
32522
32923
  selection: input.selection,
32523
32924
  element: input.element,
32524
32925
  initialOffset,
32926
+ plainTranslate,
32525
32927
  baseGsap,
32526
32928
  initialPathOffset,
32527
32929
  gestureToken,
@@ -32537,6 +32939,7 @@ function createManualOffsetDragMember(input) {
32537
32939
  selection: input.selection,
32538
32940
  element: input.element,
32539
32941
  initialOffset,
32942
+ plainTranslate,
32540
32943
  baseGsap,
32541
32944
  initialPathOffset,
32542
32945
  gestureToken,
@@ -32553,24 +32956,27 @@ function resolveManualOffsetDragMemberOffset(member, dx, dy) {
32553
32956
  dy
32554
32957
  });
32555
32958
  }
32959
+ function drawMemberOffset(member, offset, commit) {
32960
+ if (member.plainTranslate) writeTranslatePx(member.element, offset);
32961
+ else if (applyOffsetDragDraftViaGsap(member.element, offset, member.baseGsap)) return;
32962
+ else if (commit) applyStudioPathOffset(member.element, offset);
32963
+ else applyStudioPathOffsetDraft(member.element, offset);
32964
+ }
32556
32965
  function applyManualOffsetDragDraft(member, dx, dy) {
32557
32966
  const offset = resolveManualOffsetDragMemberOffset(member, dx, dy);
32558
- if (!applyOffsetDragDraftViaGsap(member.element, offset, member.baseGsap)) {
32559
- applyStudioPathOffsetDraft(member.element, offset);
32560
- }
32967
+ drawMemberOffset(member, offset, false);
32561
32968
  return offset;
32562
32969
  }
32563
- function restampManualOffsetDragGestureBase(member) {
32564
- member.element.setAttribute("data-hf-drag-gsap-base-x", String(member.baseGsap.x));
32565
- member.element.setAttribute("data-hf-drag-gsap-base-y", String(member.baseGsap.y));
32566
- member.element.setAttribute("data-hf-drag-initial-offset-x", String(member.initialOffset.x));
32567
- member.element.setAttribute("data-hf-drag-initial-offset-y", String(member.initialOffset.y));
32970
+ function stampGestureBase(el, initialOffset, baseGsap) {
32971
+ el.setAttribute("data-hf-drag-gsap-base-x", String(baseGsap.x));
32972
+ el.setAttribute("data-hf-drag-gsap-base-y", String(baseGsap.y));
32973
+ el.setAttribute("data-hf-drag-initial-offset-x", String(initialOffset.x));
32974
+ el.setAttribute("data-hf-drag-initial-offset-y", String(initialOffset.y));
32568
32975
  }
32569
32976
  function applyManualOffsetCommitValue(member, offset) {
32570
- restampManualOffsetDragGestureBase(member);
32571
- if (!applyOffsetDragDraftViaGsap(member.element, offset, member.baseGsap)) {
32572
- applyStudioPathOffset(member.element, offset);
32573
- }
32977
+ if (!member.plainTranslate)
32978
+ stampGestureBase(member.element, member.initialOffset, member.baseGsap);
32979
+ drawMemberOffset(member, offset, true);
32574
32980
  return offset;
32575
32981
  }
32576
32982
  function applyManualOffsetDragCommit(member, dx, dy) {
@@ -32581,9 +32987,7 @@ function applyManualOffsetNudgeDraft(member, delta) {
32581
32987
  x: member.initialOffset.x + delta.x,
32582
32988
  y: member.initialOffset.y + delta.y
32583
32989
  };
32584
- if (!applyOffsetDragDraftViaGsap(member.element, offset, member.baseGsap)) {
32585
- applyStudioPathOffsetDraft(member.element, offset);
32586
- }
32990
+ drawMemberOffset(member, offset, false);
32587
32991
  return offset;
32588
32992
  }
32589
32993
  function applyManualOffsetNudgeCommit(member, delta) {
@@ -32594,7 +32998,9 @@ function applyManualOffsetNudgeCommit(member, delta) {
32594
32998
  }
32595
32999
  function restoreManualOffsetDragMember(member) {
32596
33000
  restoreStudioPathOffset(member.element, member.initialPathOffset);
32597
- getOffsetDragGsap(member.element)?.set(member.element, { ...member.baseGsap });
33001
+ if (!member.plainTranslate) {
33002
+ getOffsetDragGsap(member.element)?.set(member.element, { ...member.baseGsap });
33003
+ }
32598
33004
  endStudioManualEditGesture(member.element, member.gestureToken);
32599
33005
  }
32600
33006
  function restoreManualOffsetDragMembers(members) {
@@ -32619,7 +33025,7 @@ function endManualOffsetDragMembers(members) {
32619
33025
  member.element.removeAttribute("data-hf-drag-initial-offset-y");
32620
33026
  member.element.removeAttribute("data-hf-drag-gsap-base-x");
32621
33027
  member.element.removeAttribute("data-hf-drag-gsap-base-y");
32622
- const gsapOwnsPosition = Boolean(getOffsetDragGsap(member.element));
33028
+ const gsapOwnsPosition = !member.plainTranslate && Boolean(getOffsetDragGsap(member.element));
32623
33029
  if (gsapOwnsPosition) clearStudioPathOffset(member.element);
32624
33030
  settleDraftTranslate(member.element, gsapOwnsPosition);
32625
33031
  resumeGsapTimelines(member.element);
@@ -33272,7 +33678,8 @@ function startGroupDrag(e, opts) {
33272
33678
  key: item.key,
33273
33679
  selection: item.selection,
33274
33680
  element: item.element,
33275
- rect: item.rect
33681
+ rect: item.rect,
33682
+ gesture: "drag"
33276
33683
  });
33277
33684
  if (!result.ok) {
33278
33685
  restoreManualOffsetDragMembers(members);
@@ -33336,7 +33743,6 @@ function startGesture(kind, e, opts, options) {
33336
33743
  if (kind === "resize" && (!Number.isFinite(rect.width) || !Number.isFinite(rect.height)))
33337
33744
  return false;
33338
33745
  const size = readStudioBoxSize(sel.element);
33339
- const rotation = { angle: readGsapRotation(sel.element) + readStudioRotation(sel.element).angle };
33340
33746
  const layoutWidth = sel.element.offsetWidth;
33341
33747
  const layoutHeight = sel.element.offsetHeight;
33342
33748
  const actualWidth = size.width > 0 ? size.width : layoutWidth > 0 ? layoutWidth : rect.width / rect.editScaleX;
@@ -33367,7 +33773,8 @@ function startGesture(kind, e, opts, options) {
33367
33773
  key: selectionCacheKey(sel),
33368
33774
  selection: sel,
33369
33775
  element: sel.element,
33370
- rect
33776
+ rect,
33777
+ gesture: "drag"
33371
33778
  });
33372
33779
  if (!result.ok) {
33373
33780
  opts.onBlockedMoveRef.current(result.selection);
@@ -33396,6 +33803,8 @@ function startGesture(kind, e, opts, options) {
33396
33803
  manualEditDragToken = beginStudioManualEditGesture(sel.element);
33397
33804
  }
33398
33805
  }
33806
+ const gsapRotation = pathOffsetMember?.plainTranslate ? 0 : readGsapRotation(sel.element);
33807
+ const rotation = { angle: gsapRotation + readStudioRotation(sel.element).angle };
33399
33808
  const overlayBounds = overlayEl?.getBoundingClientRect();
33400
33809
  const centerX = (overlayBounds?.left ?? 0) + rect.left + rect.width / 2;
33401
33810
  const centerY = (overlayBounds?.top ?? 0) + rect.top + rect.height / 2;
@@ -34112,7 +34521,7 @@ function useDomEditNudge(params) {
34112
34521
  if (!targets) return null;
34113
34522
  const members = [];
34114
34523
  for (const target of targets) {
34115
- const result = createManualOffsetDragMember(target);
34524
+ const result = createManualOffsetDragMember({ ...target, gesture: "nudge" });
34116
34525
  if (!result.ok) {
34117
34526
  restoreManualOffsetDragMembers(members);
34118
34527
  return null;
@@ -34301,6 +34710,93 @@ var SnapGuideOverlay = memo23(function SnapGuideOverlay2({
34301
34710
 
34302
34711
  // src/components/editor/DomEditCropHandles.tsx
34303
34712
  import { useEffect as useEffect44, useRef as useRef65, useState as useState40 } from "react";
34713
+
34714
+ // src/components/editor/cropResize.ts
34715
+ var boxOf = (element) => ({
34716
+ width: element.offsetWidth,
34717
+ height: element.offsetHeight
34718
+ });
34719
+ function cropFollows(element) {
34720
+ const clip = gsapAnimatesProperty(element, "clipPath");
34721
+ return {
34722
+ width: !clip && !elementHasNonHoldTween(element, ["width"]),
34723
+ height: !clip && !elementHasNonHoldTween(element, ["height"])
34724
+ };
34725
+ }
34726
+ function scaleCrop(crop, from, to, follows) {
34727
+ const sx = follows.width && from.width > 0 ? to.width / from.width : 1;
34728
+ const sy = follows.height && from.height > 0 ? to.height / from.height : 1;
34729
+ return {
34730
+ top: crop.top * sy,
34731
+ right: crop.right * sx,
34732
+ bottom: crop.bottom * sy,
34733
+ left: crop.left * sx,
34734
+ radius: crop.radius
34735
+ };
34736
+ }
34737
+ function readCropFollowingResize(element) {
34738
+ const crop = readElementCropInsets(element);
34739
+ const base2 = readStudioBoxSizeDraftBase(element);
34740
+ return crop && base2 ? scaleCrop(crop, base2, boxOf(element), cropFollows(element)) : crop;
34741
+ }
34742
+ function prepareCropResize(element) {
34743
+ const from = readStudioBoxSizeDraftBase(element) ?? boxOf(element);
34744
+ const before = element.style.getPropertyValue("clip-path");
34745
+ const priority = element.style.getPropertyPriority("clip-path");
34746
+ const follows = cropFollows(element);
34747
+ const crop = readElementCropInsets(element);
34748
+ return () => {
34749
+ forgetStudioBoxSizeDraftBase(element);
34750
+ const to = boxOf(element);
34751
+ const untouched = element.style.getPropertyValue("clip-path") === before;
34752
+ if (!crop || !hasCropInsets(crop) || !untouched || !(to.width > 0 && to.height > 0))
34753
+ return null;
34754
+ const clip = buildInsetClipPathSides(scaleCrop(crop, from, to, follows), crop.radius);
34755
+ if (clip === buildInsetClipPathSides(crop, crop.radius)) return null;
34756
+ element.style.setProperty("clip-path", clip, priority);
34757
+ return {
34758
+ patch: {
34759
+ type: "inline-style",
34760
+ property: "clip-path",
34761
+ value: priority ? `${clip} !important` : clip
34762
+ },
34763
+ revert: () => {
34764
+ if (before) element.style.setProperty("clip-path", before, priority);
34765
+ else element.style.removeProperty("clip-path");
34766
+ }
34767
+ };
34768
+ };
34769
+ }
34770
+ async function saveCropResize(stage, selection, commit, coalesceKey) {
34771
+ const crop = stage();
34772
+ if (!crop) return;
34773
+ try {
34774
+ await commit(selection, [crop.patch], {
34775
+ label: "Resize layer",
34776
+ coalesceKey,
34777
+ coalesceMs: Number.POSITIVE_INFINITY
34778
+ });
34779
+ } catch (error) {
34780
+ crop.revert();
34781
+ throw error;
34782
+ }
34783
+ }
34784
+ var sizeWriteCounter = 0;
34785
+ async function writeSizeWithCrop(selection, properties, mutation, commit, write) {
34786
+ if (!mutation || !("width" in properties || "height" in properties)) return write();
34787
+ const stage = prepareCropResize(selection.element);
34788
+ const coalesceKey = `size-write:${++sizeWriteCounter}`;
34789
+ const undoStep = { coalesceKey, coalesceMs: Number.POSITIVE_INFINITY };
34790
+ const keyed = (s, m, options) => mutation(s, m, { ...options, ...undoStep });
34791
+ if (mutation.batch) {
34792
+ const batch = mutation.batch;
34793
+ keyed.batch = (calls, options) => batch(calls, { ...options, ...undoStep });
34794
+ }
34795
+ await write(keyed);
34796
+ await saveCropResize(stage, selection, commit, coalesceKey);
34797
+ }
34798
+
34799
+ // src/components/editor/DomEditCropHandles.tsx
34304
34800
  import { Fragment as Fragment26, jsx as jsx84, jsxs as jsxs57 } from "react/jsx-runtime";
34305
34801
  var EDGE_HIT_THICKNESS = 12;
34306
34802
  var EDGE_HIT_LENGTH = 32;
@@ -34341,6 +34837,7 @@ function repositionHandleSize(rect) {
34341
34837
  return Math.min(REPOSITION_HANDLE_PX, Math.max(rect.width, rect.height) - RESIZE_HANDLE_HIT_PX);
34342
34838
  }
34343
34839
  var EDGES = ["top", "right", "bottom", "left"];
34840
+ var NO_CROP = { top: 0, right: 0, bottom: 0, left: 0, radius: 0 };
34344
34841
  function DomEditCropHandles({
34345
34842
  selection,
34346
34843
  overlayRect,
@@ -34351,38 +34848,28 @@ function DomEditCropHandles({
34351
34848
  const [hotEdge, setHotEdge] = useState40(null);
34352
34849
  const cropStateFor = (element) => {
34353
34850
  const parsed = readElementCropInsets(element);
34354
- const { radius, ...insets } = parsed ?? { top: 0, right: 0, bottom: 0, left: 0, radius: 0 };
34355
- return { element, croppable: parsed !== null, insets, radius };
34851
+ const { top, right, bottom, left } = parsed ?? NO_CROP;
34852
+ return { element, croppable: parsed !== null, insets: { top, right, bottom, left } };
34356
34853
  };
34357
34854
  const [state, setState] = useState40(() => cropStateFor(selection.element));
34358
34855
  if (state.element !== selection.element) {
34359
34856
  setState(cropStateFor(selection.element));
34360
34857
  }
34361
- const hasCrop = state.insets.top > 0 || state.insets.right > 0 || state.insets.bottom > 0 || state.insets.left > 0;
34362
- const liftedRef = useRef65(false);
34363
- const preLiftInlineClipRef = useRef65("");
34364
- const committedClipRef = useRef65(null);
34858
+ const committed = readCropFollowingResize(selection.element) ?? NO_CROP;
34859
+ const insets = dragging ? state.insets : committed;
34860
+ const hasCrop = hasCropInsets(insets);
34365
34861
  useEffect44(() => {
34366
34862
  const el = selection.element;
34367
34863
  if (readElementCropInsets(el) === null) return;
34368
- preLiftInlineClipRef.current = el.style.getPropertyValue("clip-path");
34369
- committedClipRef.current = null;
34370
- el.style.setProperty("clip-path", "none");
34371
- liftedRef.current = true;
34372
- return () => {
34373
- liftedRef.current = false;
34374
- const committed = committedClipRef.current;
34375
- const restore = committed !== null ? committed || null : preLiftInlineClipRef.current || null;
34376
- if (restore) el.style.setProperty("clip-path", restore);
34377
- else el.style.removeProperty("clip-path");
34378
- };
34864
+ liftElementCrop(el);
34865
+ return () => dropElementCropLift(el);
34379
34866
  }, [selection.element]);
34380
34867
  const frame = readElementCropFrame(selection.element, overlayRect);
34381
34868
  const width = frame.width / frame.scaleX;
34382
34869
  const height = frame.height / frame.scaleY;
34383
34870
  const cropRect = cropRectFromInsets(
34384
34871
  { left: 0, top: 0, width: frame.width, height: frame.height },
34385
- state.insets,
34872
+ insets,
34386
34873
  frame.scaleX,
34387
34874
  frame.scaleY
34388
34875
  );
@@ -34392,17 +34879,20 @@ function DomEditCropHandles({
34392
34879
  event.preventDefault();
34393
34880
  event.stopPropagation();
34394
34881
  event.currentTarget.setPointerCapture(event.pointerId);
34882
+ const pressed = readCropFollowingResize(selection.element) ?? NO_CROP;
34395
34883
  gestureRef.current = {
34396
34884
  edge,
34397
34885
  pointerId: event.pointerId,
34398
34886
  startX: event.clientX,
34399
34887
  startY: event.clientY,
34400
- startInsets: state.insets,
34401
- insets: state.insets,
34888
+ startInsets: pressed,
34889
+ insets: pressed,
34890
+ radius: pressed.radius,
34402
34891
  angleDeg: frame.angleDeg,
34403
34892
  scaleX: frame.scaleX,
34404
34893
  scaleY: frame.scaleY
34405
34894
  };
34895
+ setState((prev) => ({ ...prev, insets: pressed }));
34406
34896
  setDragging(true);
34407
34897
  };
34408
34898
  const updateCropGesture = (event) => {
@@ -34438,19 +34928,10 @@ function DomEditCropHandles({
34438
34928
  const finishCropGesture = (event) => {
34439
34929
  const gesture = endCropGesture(event);
34440
34930
  if (!gesture) return;
34441
- const el = selection.element;
34442
- const reLift = () => {
34443
- if (liftedRef.current) el.style.setProperty("clip-path", "none");
34444
- };
34445
- const { insets } = gesture;
34446
- const committedValue = buildInsetClipPathSides(insets, state.radius);
34447
- if (committedValue === buildInsetClipPathSides(gesture.startInsets, state.radius)) return;
34448
- const cropped = insets.top > 0 || insets.right > 0 || insets.bottom > 0 || insets.left > 0;
34449
- const commit = onStyleCommit?.("clip-path", committedValue);
34450
- reLift();
34451
- void Promise.resolve(commit).then(() => {
34452
- committedClipRef.current = cropped ? committedValue : "";
34453
- }, reLift);
34931
+ const value = buildInsetClipPathSides(gesture.insets, gesture.radius);
34932
+ if (value === buildInsetClipPathSides(gesture.startInsets, gesture.radius)) return;
34933
+ const commit = onStyleCommit?.("clip-path", value);
34934
+ void Promise.resolve(commit).catch(() => void 0);
34454
34935
  };
34455
34936
  const cancelCropGesture = (event) => {
34456
34937
  const gesture = endCropGesture(event);
@@ -34803,19 +35284,19 @@ function DomEditSelectionChrome({
34803
35284
  // src/hooks/useCropOverlay.ts
34804
35285
  function useCropOverlay(params) {
34805
35286
  const { selection, overlayRect } = params;
34806
- const cropInsets = selection ? readElementCropInsets(selection.element) : null;
34807
- const hasCropInsets = Boolean(
35287
+ const cropInsets = selection && !isElementCropLifted(selection.element) ? readElementCropInsets(selection.element) : null;
35288
+ const hasCropInsets2 = Boolean(
34808
35289
  cropInsets && (cropInsets.top > 0 || cropInsets.right > 0 || cropInsets.bottom > 0 || cropInsets.left > 0)
34809
35290
  );
34810
35291
  const sx = overlayRect && overlayRect.editScaleX > 0 ? overlayRect.editScaleX : 1;
34811
35292
  const sy = overlayRect && overlayRect.editScaleY > 0 ? overlayRect.editScaleY : 1;
34812
- const cropOutlineInsetPx = cropInsets && hasCropInsets ? {
35293
+ const cropOutlineInsetPx = cropInsets && hasCropInsets2 ? {
34813
35294
  top: cropInsets.top * sy,
34814
35295
  right: cropInsets.right * sx,
34815
35296
  bottom: cropInsets.bottom * sy,
34816
35297
  left: cropInsets.left * sx
34817
35298
  } : void 0;
34818
- return { hasCropInsets, cropOutlineInsetPx };
35299
+ return { hasCropInsets: hasCropInsets2, cropOutlineInsetPx };
34819
35300
  }
34820
35301
 
34821
35302
  // src/components/editor/domEditOverlayShape.ts
@@ -36270,11 +36751,11 @@ var DomEditOverlay = memo25(function DomEditOverlay2({
36270
36751
  const compRect = useDomEditCompositionRect({ iframeRef, overlayRef });
36271
36752
  const compRectRef = useRef69(compRect);
36272
36753
  compRectRef.current = compRect;
36273
- const { hasCropInsets, cropOutlineInsetPx } = useCropOverlay({
36754
+ const { hasCropInsets: hasCropInsets2, cropOutlineInsetPx } = useCropOverlay({
36274
36755
  selection,
36275
36756
  overlayRect
36276
36757
  });
36277
- const boxClipPath = hasCropInsets ? void 0 : selectionShapeStyles.clipPath;
36758
+ const boxClipPath = hasCropInsets2 ? void 0 : selectionShapeStyles.clipPath;
36278
36759
  const boxChromeClass = resolveBoxChromeClass(Boolean(cropOutlineInsetPx), boxClipPath);
36279
36760
  const { offCanvasRects, offCanvasElementsRef } = useOffCanvasIndicators({
36280
36761
  iframeRef,
@@ -37576,7 +38057,12 @@ async function commitCandidateEdit(edit, targetPath, originalContent, deps, opti
37576
38057
  });
37577
38058
  }
37578
38059
  if (refreshTarget) refreshCommittedEdit(edit.after, deps, options);
37579
- return { status: "committed", version: hashContent(edit.after) };
38060
+ return {
38061
+ status: "committed",
38062
+ version: hashContent(edit.after),
38063
+ before: originalContent,
38064
+ after: edit.after
38065
+ };
37580
38066
  }
37581
38067
  async function persistSdkCandidateMutation(live, targetPath, originalContent, deps, mutate, options, sourceSnapshot) {
37582
38068
  return serializeStudioFileMutation(
@@ -39854,6 +40340,10 @@ var GsapEditBlockedError = class extends Error {
39854
40340
  function assertGsapEditPersisted(outcome) {
39855
40341
  if (outcome.status === "blocked") throw new GsapEditBlockedError(outcome.reason, outcome.detail);
39856
40342
  }
40343
+ async function saveMove(outcome, saveOnElement) {
40344
+ if (outcome.status === "element-offset") return saveOnElement();
40345
+ assertGsapEditPersisted(outcome);
40346
+ }
39857
40347
  function assertGsapAnimationDirectlyEditable(animation) {
39858
40348
  const editability = editabilityForProvenance(animation.provenance);
39859
40349
  if (editability === "unroll") throw new GsapEditBlockedError("unroll-required");
@@ -40034,8 +40524,8 @@ async function commitStaticGsapRotation(selection, newRotation, selector, existi
40034
40524
  );
40035
40525
  }
40036
40526
  async function commitStaticGsapSize(selection, size, selector, existingSet, callbacks) {
40037
- const width = Math.round(size.width);
40038
- const height = Math.round(size.height);
40527
+ const width = roundToLayoutPx(size.width);
40528
+ const height = roundToLayoutPx(size.height);
40039
40529
  if (existingSet) {
40040
40530
  await callbacks.commitMutation(
40041
40531
  selection,
@@ -40063,7 +40553,7 @@ async function commitStaticGsapSize(selection, size, selector, existingSet, call
40063
40553
  );
40064
40554
  }
40065
40555
  function positiveOr(n, fallback) {
40066
- return Number.isFinite(n) && n > 0 ? Math.round(n) : fallback;
40556
+ return Number.isFinite(n) && n > 0 ? roundToLayoutPx(n) : fallback;
40067
40557
  }
40068
40558
  function resolvePriorSize(sizeSet, el, fallbackW, fallbackH) {
40069
40559
  if (sizeSet) {
@@ -40080,8 +40570,8 @@ async function commitKeyframedSizeFromResize(selection, size, selector, sizeSet,
40080
40570
  const ts = resolveTweenStart(animatedTween) ?? 0;
40081
40571
  const td = resolveTweenDuration(animatedTween);
40082
40572
  if (!(td > 0)) return false;
40083
- const newW = Math.round(size.width);
40084
- const newH = Math.round(size.height);
40573
+ const newW = roundToLayoutPx(size.width);
40574
+ const newH = roundToLayoutPx(size.height);
40085
40575
  const prior = resolvePriorSize(sizeSet, selection.element, newW, newH);
40086
40576
  const ct = usePlayerStore.getState().currentTime;
40087
40577
  const pct = Math.max(0, Math.min(100, Math.round((ct - ts) / td * 1e3) / 10));
@@ -40411,249 +40901,6 @@ function commitCreatePath(targetSelector, position, x, y, commit) {
40411
40901
 
40412
40902
  // src/components/editor/useMotionPathData.ts
40413
40903
  import { useEffect as useEffect52, useRef as useRef73, useState as useState45 } from "react";
40414
-
40415
- // src/hooks/gsapRuntimeKeyframes.ts
40416
- import { buildArcPath } from "@hyperframes/core/gsap-parser-acorn";
40417
- var FLAT_SKIP_KEYS = /* @__PURE__ */ new Set([
40418
- "ease",
40419
- "duration",
40420
- "delay",
40421
- "stagger",
40422
- "motionPath",
40423
- "overwrite",
40424
- "immediateRender",
40425
- "onComplete",
40426
- "onUpdate",
40427
- "onStart",
40428
- "keyframes"
40429
- ]);
40430
- function timelinesOf(iframe) {
40431
- if (!iframe?.contentWindow) return null;
40432
- try {
40433
- return iframe.contentWindow.__timelines ?? null;
40434
- } catch {
40435
- return null;
40436
- }
40437
- }
40438
- function isXY(p) {
40439
- return !!p && typeof p.x === "number" && typeof p.y === "number";
40440
- }
40441
- function isZeroDurationSet(duration) {
40442
- return !(duration > 0);
40443
- }
40444
- function coordsFromMotionPath(mp) {
40445
- if (!mp || typeof mp !== "object") return null;
40446
- const obj = mp;
40447
- const pathVal = Array.isArray(mp) ? mp : obj.path;
40448
- if (!Array.isArray(pathVal)) return null;
40449
- const coords = pathVal.filter(isXY).map((p) => ({ x: p.x, y: p.y }));
40450
- if (coords.length < 2) return null;
40451
- const curviness = typeof obj.curviness === "number" ? obj.curviness : 1;
40452
- const autoRotate = typeof obj.autoRotate === "number" ? obj.autoRotate : obj.autoRotate === true;
40453
- return { coords, curviness, autoRotate, isCubic: obj.type === "cubic" };
40454
- }
40455
- function flatTweenKeyframes(vars) {
40456
- const properties = {};
40457
- for (const [k, v] of Object.entries(vars)) {
40458
- if (FLAT_SKIP_KEYS.has(k)) continue;
40459
- if (typeof v === "number") properties[k] = roundTo3(v);
40460
- else if (typeof v === "string") properties[k] = v;
40461
- }
40462
- if (Object.keys(properties).length === 0) return null;
40463
- return [
40464
- { percentage: 0, properties },
40465
- { percentage: 100, properties }
40466
- ];
40467
- }
40468
- function readTween(vars) {
40469
- if (vars.keyframes && typeof vars.keyframes === "object") {
40470
- const parsed = parsePercentageKeyframes(vars.keyframes);
40471
- if (parsed) return parsed;
40472
- }
40473
- const mp = coordsFromMotionPath(vars.motionPath);
40474
- if (mp) {
40475
- const shape = buildArcPath(mp.coords, mp.curviness, mp.autoRotate, mp.isCubic);
40476
- if (shape) {
40477
- const n = shape.waypoints.length;
40478
- const keyframes = shape.waypoints.map((wp, i) => ({
40479
- percentage: n > 1 ? Math.round(i / (n - 1) * 100) : 0,
40480
- properties: { x: wp.x, y: wp.y }
40481
- }));
40482
- return { keyframes, arcPath: shape.arcPath };
40483
- }
40484
- }
40485
- const flat = flatTweenKeyframes(vars);
40486
- return flat ? { keyframes: flat } : null;
40487
- }
40488
- function matchesElement(tween, el) {
40489
- if (!tween.targets) return false;
40490
- for (const t of tween.targets()) {
40491
- if (t === el || el.id && t.id === el.id) return true;
40492
- }
40493
- return false;
40494
- }
40495
- function tweenTiming(tween) {
40496
- const rawStart = typeof tween.startTime === "function" ? tween.startTime() : 0;
40497
- const rawDur = typeof tween.duration === "function" ? tween.duration() : 0;
40498
- return {
40499
- start: Number.isFinite(rawStart) ? rawStart : 0,
40500
- duration: Number.isFinite(rawDur) ? rawDur : 0
40501
- };
40502
- }
40503
- function varsCarryChannel(vars, channels) {
40504
- if (!vars) return false;
40505
- for (const ch of channels) {
40506
- if (Object.prototype.hasOwnProperty.call(vars, ch)) return true;
40507
- }
40508
- return false;
40509
- }
40510
- function keyframeVarsCarryChannel(vars, channels) {
40511
- const kf = vars?.keyframes;
40512
- if (!kf || typeof kf !== "object") return false;
40513
- const steps = Array.isArray(kf) ? kf : Object.values(kf);
40514
- return steps.some(
40515
- (step) => step != null && typeof step === "object" && channels.some((ch) => Object.prototype.hasOwnProperty.call(step, ch))
40516
- );
40517
- }
40518
- function resolveRuntimeTween(iframe, selector, kind, compositionId, channels) {
40519
- const timelines = timelinesOf(iframe);
40520
- if (!timelines) return null;
40521
- let targetEl = null;
40522
- try {
40523
- targetEl = iframe?.contentDocument?.querySelector(selector) ?? null;
40524
- } catch {
40525
- return null;
40526
- }
40527
- if (!targetEl) return null;
40528
- const tlIds = compositionId ? [compositionId] : Object.keys(timelines).filter((k) => typeof timelines[k]?.getChildren === "function");
40529
- const wantChannels = channels && channels.length > 0 ? channels : null;
40530
- let first = null;
40531
- let channelMatch = null;
40532
- for (const tlId of tlIds) {
40533
- const timeline = timelines[tlId];
40534
- if (!timeline?.getChildren) continue;
40535
- const now2 = typeof timeline.time === "function" ? timeline.time() : null;
40536
- for (const tween of timeline.getChildren(true)) {
40537
- if (!tween.vars || !matchesElement(tween, targetEl)) continue;
40538
- const dur = typeof tween.duration === "function" ? tween.duration() : 0;
40539
- const isSet = !(dur > 0);
40540
- if (kind === "set" ? !isSet : isSet) continue;
40541
- if (wantChannels) {
40542
- const carries = kind === "set" ? varsCarryChannel(tween.vars, wantChannels) : keyframeVarsCarryChannel(tween.vars, wantChannels);
40543
- if (carries) {
40544
- if (channelMatch === null) channelMatch = { tween, timeline };
40545
- } else if (first === null) {
40546
- first = { tween, timeline };
40547
- }
40548
- continue;
40549
- }
40550
- if (first === null) first = { tween, timeline };
40551
- if (kind === "keyframe" && now2 != null) {
40552
- const start = typeof tween.startTime === "function" ? tween.startTime() : 0;
40553
- if (now2 >= start - 1e-3 && now2 <= start + dur + 1e-3) return { tween, timeline };
40554
- }
40555
- }
40556
- }
40557
- return channelMatch ?? first;
40558
- }
40559
- function readCarriesChannel(read, channels) {
40560
- return read.keyframes.some((kf) => channels.some((c) => kf.properties[c] != null));
40561
- }
40562
- function readRuntimeKeyframes(iframe, selector, compositionId, requireChannels) {
40563
- const timelines = timelinesOf(iframe);
40564
- if (!timelines) return null;
40565
- let targetEl = null;
40566
- try {
40567
- targetEl = iframe?.contentDocument?.querySelector(selector) ?? null;
40568
- } catch {
40569
- return null;
40570
- }
40571
- if (!targetEl) return null;
40572
- const tlIds = compositionId ? [compositionId] : Object.keys(timelines).filter((k) => typeof timelines[k]?.getChildren === "function");
40573
- if (tlIds.length === 0) return null;
40574
- let firstRead = null;
40575
- for (const tlId of tlIds) {
40576
- const timeline = timelines[tlId];
40577
- if (!timeline?.getChildren) continue;
40578
- const now2 = typeof timeline.time === "function" ? timeline.time() : null;
40579
- for (const tween of timeline.getChildren(true)) {
40580
- if (!tween.vars || !matchesElement(tween, targetEl)) continue;
40581
- const dur = typeof tween.duration === "function" ? tween.duration() : 0;
40582
- if (isZeroDurationSet(dur)) continue;
40583
- const read = readTween(tween.vars);
40584
- if (!read) continue;
40585
- if (requireChannels && !readCarriesChannel(read, requireChannels)) continue;
40586
- if (firstRead === null) firstRead = read;
40587
- if (now2 != null) {
40588
- const start = typeof tween.startTime === "function" ? tween.startTime() : 0;
40589
- if (now2 >= start - 1e-3 && now2 <= start + dur + 1e-3) return read;
40590
- }
40591
- }
40592
- }
40593
- return firstRead;
40594
- }
40595
- function hasNonHoldTweenForElement(iframe, selector, compositionId, channels) {
40596
- const timelines = timelinesOf(iframe);
40597
- if (!timelines) return false;
40598
- const tlId = compositionId || Object.keys(timelines).find((k) => typeof timelines[k]?.getChildren === "function");
40599
- if (!tlId) return false;
40600
- const timeline = timelines[tlId];
40601
- if (!timeline?.getChildren) return false;
40602
- let targetEl = null;
40603
- try {
40604
- targetEl = iframe?.contentDocument?.querySelector(selector) ?? null;
40605
- } catch {
40606
- return false;
40607
- }
40608
- if (!targetEl) return false;
40609
- for (const tween of timeline.getChildren(true)) {
40610
- if (!tween.vars || !matchesElement(tween, targetEl)) continue;
40611
- const dur = typeof tween.duration === "function" ? tween.duration() : 0;
40612
- if (isZeroDurationSet(dur)) continue;
40613
- const read = readTween(tween.vars);
40614
- if (read && (!channels || readCarriesChannel(read, channels))) return true;
40615
- }
40616
- return false;
40617
- }
40618
- function toClipRelative(keyframes, tweenStart, tweenDuration, clip) {
40619
- if (!clip || clip.duration <= 0) return keyframes;
40620
- return keyframes.map((kf) => {
40621
- const abs = toAbsoluteTime(tweenStart, tweenDuration, kf.percentage);
40622
- return { ...kf, percentage: Math.round((abs - clip.start) / clip.duration * 1e5) / 1e3 };
40623
- });
40624
- }
40625
- function buildEntry(read, start, duration, clip) {
40626
- return {
40627
- keyframes: toClipRelative(read.keyframes, start, duration, clip),
40628
- tweenStart: start,
40629
- tweenDuration: duration,
40630
- ...read.easeEach ? { easeEach: read.easeEach } : {},
40631
- ...read.arcPath ? { arcPath: read.arcPath } : {}
40632
- };
40633
- }
40634
- function addScanEntry(result, tween, clipById) {
40635
- if (!tween.targets || !tween.vars) return;
40636
- const { start, duration } = tweenTiming(tween);
40637
- if (isZeroDurationSet(duration)) return;
40638
- const read = readTween(tween.vars);
40639
- if (!read) return;
40640
- for (const target of tween.targets()) {
40641
- const id = target.id;
40642
- if (id && !result.has(id)) result.set(id, buildEntry(read, start, duration, clipById?.get(id)));
40643
- }
40644
- }
40645
- function scanAllRuntimeKeyframes(iframe, clipById) {
40646
- const result = /* @__PURE__ */ new Map();
40647
- const timelines = timelinesOf(iframe);
40648
- if (!timelines) return result;
40649
- for (const timeline of Object.values(timelines)) {
40650
- if (!timeline?.getChildren) continue;
40651
- for (const tween of timeline.getChildren(true)) addScanEntry(result, tween, clipById);
40652
- }
40653
- return result;
40654
- }
40655
-
40656
- // src/components/editor/useMotionPathData.ts
40657
40904
  function transformTranslate(el) {
40658
40905
  const t = el.ownerDocument?.defaultView?.getComputedStyle(el).transform;
40659
40906
  if (!t || t === "none") return { x: 0, y: 0 };
@@ -42226,19 +42473,6 @@ function getAnimationsForElement(animations, target, element) {
42226
42473
  }
42227
42474
 
42228
42475
  // src/hooks/gsapRuntimeBridge.ts
42229
- var POSITION_CHANNELS = [
42230
- "x",
42231
- "y",
42232
- "xPercent",
42233
- "yPercent",
42234
- "left",
42235
- "top",
42236
- // GSAP normalizes translateX/Y to x/y at play time, but readTween reads the
42237
- // AUTHORED shape — include them so a hand-authored translateX/Y position tween
42238
- // still counts as a live position tween.
42239
- "translateX",
42240
- "translateY"
42241
- ];
42242
42476
  var POSITION_CHANNEL_SET = new Set(POSITION_CHANNELS);
42243
42477
  var ROTATION_CHANNELS = ["rotation", "rotationX", "rotationY", "rotationZ"];
42244
42478
  var ROTATION_CHANNEL_SET = new Set(ROTATION_CHANNELS);
@@ -42451,7 +42685,7 @@ async function tryGsapRotationIntercept(selection, angle, animations, iframe, co
42451
42685
  if (!anim) {
42452
42686
  anim = workingAnimations.find((a) => animationWritesAnyProperty(a, ROTATION_CHANNEL_SET)) ?? null;
42453
42687
  }
42454
- const newRotation = Math.round(angle);
42688
+ const newRotation = roundTo3(angle);
42455
42689
  if (!anim || isInstantHold(anim)) {
42456
42690
  const existingSet = anim ?? findRotationSetAnimation(resolvedAnimations, selector, selection.element);
42457
42691
  await commitStaticGsapRotation(selection, newRotation, selector, existingSet, {
@@ -47680,7 +47914,7 @@ function createTransformCommitHandlers({
47680
47914
  "This element's position can't be edited here yet \u2014 it is driven by its animation"
47681
47915
  ))
47682
47916
  return;
47683
- const current = readStudioPathOffset(element.element);
47917
+ const current = readMoveOffset(element.element);
47684
47918
  await Promise.resolve(
47685
47919
  onSetManualOffset(element, {
47686
47920
  x: axis === "x" ? parsed : current.x,
@@ -64763,7 +64997,7 @@ var PropertyPanel = memo36(function PropertyPanel2(props) {
64763
64997
  const sourceLabel = element.id ? `#${element.id}` : element.selector ?? "";
64764
64998
  const sections = resolveEditingSections(domEditSelectionToFacts(element, gsapAnimations.length));
64765
64999
  const showEditableSections = element.capabilities.canEditStyles && sections.style;
64766
- const manualOffset = readStudioPathOffset(element.element);
65000
+ const manualOffset = readMoveOffset(element.element);
64767
65001
  const manualSize = readStudioBoxSize(element.element);
64768
65002
  const resolvedWidth = manualSize.width > 0 ? manualSize.width : parsePxMetricValue(styles.width ?? "") ?? element.boundingBox.width;
64769
65003
  const resolvedHeight = manualSize.height > 0 ? manualSize.height : parsePxMetricValue(styles.height ?? "") ?? element.boundingBox.height;
@@ -70976,7 +71210,7 @@ function useTimelineGroupEditing({
70976
71210
  async (input) => {
70977
71211
  const sharedPath = allChangesSharePath(input.changes, activeCompPath);
70978
71212
  const canUseSdk = !input.needsExtension && sharedPath !== null && input.eligible && input.sdkChanges.every((change) => change !== null);
70979
- if (!canUseSdk) return false;
71213
+ if (!canUseSdk) return null;
70980
71214
  const result = await sdkTimingBatchPersist(
70981
71215
  input.sdkChanges.filter((change) => change !== null),
70982
71216
  sharedPath,
@@ -70996,7 +71230,7 @@ function useTimelineGroupEditing({
70996
71230
  skipRefresh: true
70997
71231
  }
70998
71232
  );
70999
- return cutoverCommittedOrThrow(result);
71233
+ return cutoverCommittedOrThrow(result) ? { sdkGsap: sdkTimingGsapSync(result) } : null;
71000
71234
  },
71001
71235
  [
71002
71236
  activeCompPath,
@@ -71033,7 +71267,7 @@ function useTimelineGroupEditing({
71033
71267
  const label = options?.label ?? "Move timeline clips";
71034
71268
  return enqueueGroupOperation(label, async (projectId) => {
71035
71269
  await options?.beforeTiming;
71036
- const handledBySdk = await trySdkBatchPersist({
71270
+ const sdk = await trySdkBatchPersist({
71037
71271
  changes,
71038
71272
  sdkChanges: toSdkTimingChanges(changes, (change) => ({
71039
71273
  start: toAuthoredStart(change.element, change.start)
@@ -71044,7 +71278,7 @@ function useTimelineGroupEditing({
71044
71278
  coalesceKey,
71045
71279
  coalesceMs
71046
71280
  });
71047
- if (!handledBySdk) {
71281
+ if (!sdk) {
71048
71282
  await persistServerBatch(
71049
71283
  projectId,
71050
71284
  label,
@@ -71075,6 +71309,7 @@ function useTimelineGroupEditing({
71075
71309
  writeProjectFile,
71076
71310
  activeCompPath,
71077
71311
  changes,
71312
+ sdkGsap: sdk?.sdkGsap,
71078
71313
  resolveChangePath: (element) => targetPathFor(element, activeCompPath),
71079
71314
  mutateChange: (change, changePath) => {
71080
71315
  const delta = change.start - change.element.start;
@@ -71134,7 +71369,7 @@ function useTimelineGroupEditing({
71134
71369
  const coalesceMs = options?.coalesceMs;
71135
71370
  return enqueueGroupOperation("Resize timeline clips", async (projectId) => {
71136
71371
  await options?.beforeTiming;
71137
- const handledBySdk = await trySdkBatchPersist({
71372
+ const sdk = await trySdkBatchPersist({
71138
71373
  changes,
71139
71374
  sdkChanges: toSdkTimingChanges(changes, (change) => ({
71140
71375
  start: toAuthoredStart(change.element, change.start),
@@ -71146,7 +71381,7 @@ function useTimelineGroupEditing({
71146
71381
  coalesceKey,
71147
71382
  coalesceMs
71148
71383
  });
71149
- if (!handledBySdk) {
71384
+ if (!sdk) {
71150
71385
  await persistServerBatch(
71151
71386
  projectId,
71152
71387
  "Resize timeline clips",
@@ -71174,6 +71409,7 @@ function useTimelineGroupEditing({
71174
71409
  writeProjectFile,
71175
71410
  activeCompPath,
71176
71411
  changes,
71412
+ sdkGsap: sdk?.sdkGsap,
71177
71413
  resolveChangePath: (element) => targetPathFor(element, activeCompPath),
71178
71414
  mutateChange: (change, changePath) => {
71179
71415
  const domId = change.element.domId;
@@ -71378,10 +71614,8 @@ function useTimelineEditing({
71378
71614
  );
71379
71615
  };
71380
71616
  const coalesceKey = `timeline-move:${element.hfId ?? element.id}`;
71381
- const finishMoveGsapSync = () => (
71382
- // Every timing writer converges the same GSAP positions after its
71383
- // durable clip-start commit. The SDK owns the attribute write; this
71384
- // sync owns only the dependent animation rewrite and preview refresh.
71617
+ const finishMoveGsapSync = (sdkGsap) => (
71618
+ // One GSAP sync per edit: the SDK commit's own (sdkGsap), else the server rewrite here.
71385
71619
  finishClipTimingFallback({
71386
71620
  iframe: previewIframeRef.current,
71387
71621
  reloadPreview,
@@ -71392,11 +71626,12 @@ function useTimelineEditing({
71392
71626
  coalesceKey,
71393
71627
  recordEdit,
71394
71628
  writeProjectFile,
71395
- edit: { kind: "shift", delta: updates.start - element.start }
71629
+ edit: { kind: "shift", delta: updates.start - element.start },
71630
+ sdkGsap
71396
71631
  }).finally(() => invalidateGsapCache?.())
71397
71632
  );
71398
71633
  const moveFallback = () => enqueueEdit(element, "Move timeline clip", buildMovePatches, coalesceKey).then(
71399
- finishMoveGsapSync
71634
+ () => finishMoveGsapSync()
71400
71635
  );
71401
71636
  return reorderDone.then(() => {
71402
71637
  if (sdkSession && element.hfId && !needsExtension && !trackChanged) {
@@ -71418,7 +71653,7 @@ function useTimelineEditing({
71418
71653
  { label: "Move timeline clip", coalesceKey, skipRefresh: true }
71419
71654
  ).then((result) => {
71420
71655
  if (!cutoverCommittedOrThrow(result)) return moveFallback();
71421
- return finishMoveGsapSync();
71656
+ return finishMoveGsapSync(sdkTimingGsapSync(result));
71422
71657
  });
71423
71658
  }
71424
71659
  return moveFallback();
@@ -71467,7 +71702,7 @@ function useTimelineEditing({
71467
71702
  };
71468
71703
  const hasPbsAdjustment = updates.playbackStart != null || updates.start !== element.start && element.playbackStart != null;
71469
71704
  const coalesceKey = `timeline-resize:${element.hfId ?? element.id}`;
71470
- const finishResizeGsapSync = () => finishClipTimingFallback({
71705
+ const finishResizeGsapSync = (sdkGsap) => finishClipTimingFallback({
71471
71706
  iframe: previewIframeRef.current,
71472
71707
  reloadPreview,
71473
71708
  projectId: projectIdRef.current,
@@ -71481,10 +71716,11 @@ function useTimelineEditing({
71481
71716
  kind: "scale",
71482
71717
  from: { start: toCompositionTime(element, element.start), duration: element.duration },
71483
71718
  to: { start: toCompositionTime(element, updates.start), duration: updates.duration }
71484
- }
71719
+ },
71720
+ sdkGsap
71485
71721
  }).finally(() => invalidateGsapCache?.());
71486
71722
  const resizeFallback = () => enqueueEdit(element, "Resize timeline clip", buildResizePatches, coalesceKey).then(
71487
- finishResizeGsapSync
71723
+ () => finishResizeGsapSync()
71488
71724
  );
71489
71725
  const persistDone = sdkSession && element.hfId && !hasPbsAdjustment && !needsExtension ? sdkTimingPersist(
71490
71726
  element.hfId,
@@ -71504,7 +71740,7 @@ function useTimelineEditing({
71504
71740
  { label: "Resize timeline clip", coalesceKey, skipRefresh: true }
71505
71741
  ).then((result) => {
71506
71742
  if (!cutoverCommittedOrThrow(result)) return resizeFallback();
71507
- return finishResizeGsapSync();
71743
+ return finishResizeGsapSync(sdkTimingGsapSync(result));
71508
71744
  }) : resizeFallback();
71509
71745
  return persistDone.catch((error) => {
71510
71746
  rollbackDuration();
@@ -73556,10 +73792,6 @@ function canCommitInlineTextSelection(selection, element) {
73556
73792
  if (selection.isCompositionHost || selection.isInsideLockedComposition) return false;
73557
73793
  return canEditElementTextInline(element);
73558
73794
  }
73559
- function ownsCurrentPreviewElement(selection, element, document2) {
73560
- if (!document2 || !element.isConnected) return false;
73561
- return element === selection.element && element.ownerDocument === document2;
73562
- }
73563
73795
  async function resyncDomTextSelectionFromPreview(doc, selection, activeCompPath, buildDomSelectionFromTarget, applyDomSelection) {
73564
73796
  if (!doc) return;
73565
73797
  const refreshed = findElementForSelection(doc, selection, activeCompPath);
@@ -73582,6 +73814,8 @@ function useDomEditTextCommits({
73582
73814
  }) {
73583
73815
  const latestReadOnlyPreviewRef = useRef142(readOnlyPreview);
73584
73816
  latestReadOnlyPreviewRef.current = readOnlyPreview;
73817
+ const latestSelectionRef = useRef142(domEditSelection);
73818
+ latestSelectionRef.current = domEditSelection;
73585
73819
  const domTextCommitVersionRef = useRef142(/* @__PURE__ */ new Map());
73586
73820
  const domStyleCommitVersionRef = useRef142(/* @__PURE__ */ new Map());
73587
73821
  const {
@@ -73693,18 +73927,32 @@ function useDomEditTextCommits({
73693
73927
  );
73694
73928
  const handleDomRichTextCommit = useCallback123(
73695
73929
  async ({ element, html: html2, previousHtml }) => {
73696
- if (!domEditSelection) return;
73697
- if (latestReadOnlyPreviewRef.current) {
73930
+ const putBack = () => {
73698
73931
  if (element.isConnected && element.innerHTML === html2) element.innerHTML = previousHtml;
73699
- return;
73932
+ };
73933
+ if (latestReadOnlyPreviewRef.current) return putBack();
73934
+ const refuse = (reason) => {
73935
+ console.error("[Studio] text edit not saved:", reason, element);
73936
+ showToast(`Couldn't save the text edit: ${reason}`, "error");
73937
+ putBack();
73938
+ };
73939
+ const doc = previewIframeRef.current?.contentDocument;
73940
+ if (!doc || !element.isConnected || element.ownerDocument !== doc) {
73941
+ return refuse("the text's element is gone from the preview");
73942
+ }
73943
+ const selection = await buildDomSelectionFromTarget(element, {
73944
+ exactTarget: true,
73945
+ skipSourceProbe: true
73946
+ });
73947
+ if (selection?.element !== element) {
73948
+ return refuse("this text was not found in the composition's source");
73949
+ }
73950
+ if (!canCommitInlineTextSelection(selection, element)) {
73951
+ return refuse("this text can't be edited in place");
73700
73952
  }
73701
- if (!canCommitInlineTextSelection(domEditSelection, element)) return;
73702
- const iframe = previewIframeRef.current;
73703
- const doc = iframe?.contentDocument;
73704
- if (!ownsCurrentPreviewElement(domEditSelection, element, doc)) return;
73705
73953
  const isLatestTextCommit = bumpDomEditCommitMapVersion(
73706
73954
  domTextCommitVersionRef.current,
73707
- getDomEditTargetKey(domEditSelection)
73955
+ getDomEditTargetKey(selection)
73708
73956
  );
73709
73957
  const operations = [buildDomEditRichTextPatchOperation(html2)];
73710
73958
  let appliedHtml = "";
@@ -73716,7 +73964,7 @@ function useDomEditTextCommits({
73716
73964
  appliedHtml = element.innerHTML;
73717
73965
  },
73718
73966
  persist: async () => {
73719
- await persistDomEditOperations(domEditSelection, operations, {
73967
+ await persistDomEditOperations(selection, operations, {
73720
73968
  label: "Edit text",
73721
73969
  skipRefresh: true,
73722
73970
  shouldSave: isLatestTextCommit
@@ -73728,11 +73976,12 @@ function useDomEditTextCommits({
73728
73976
  element.innerHTML = previousHtml;
73729
73977
  }
73730
73978
  },
73731
- onError: (error) => reportDomEditPersistFailure(domEditSelection, operations, error, showToast),
73732
- shouldResync: isLatestTextCommit,
73979
+ onError: (error) => reportDomEditPersistFailure(selection, operations, error, showToast),
73980
+ // Re-select only what is still selected: the selection may have moved on, or a host cleared it.
73981
+ shouldResync: () => isLatestTextCommit() && latestSelectionRef.current?.element === element,
73733
73982
  resync: () => resyncDomTextSelectionFromPreview(
73734
73983
  doc,
73735
- domEditSelection,
73984
+ selection,
73736
73985
  activeCompPath,
73737
73986
  buildDomSelectionFromTarget,
73738
73987
  applyDomSelection
@@ -73744,7 +73993,6 @@ function useDomEditTextCommits({
73744
73993
  activeCompPath,
73745
73994
  applyDomSelection,
73746
73995
  buildDomSelectionFromTarget,
73747
- domEditSelection,
73748
73996
  persistDomEditOperations,
73749
73997
  previewIframeRef,
73750
73998
  showToast
@@ -73925,8 +74173,8 @@ function readDragStart(el, axis) {
73925
74173
  function applyElementPositionOffset(el, gestureOffset) {
73926
74174
  const cs = el.ownerDocument.defaultView?.getComputedStyle(el);
73927
74175
  if (!cs) return "anchored";
73928
- const dx = Math.round(gestureOffset.x - readDragStart(el, "x"));
73929
- const dy = Math.round(gestureOffset.y - readDragStart(el, "y"));
74176
+ const dx = roundTo3(gestureOffset.x - readDragStart(el, "x"));
74177
+ const dy = roundTo3(gestureOffset.y - readDragStart(el, "y"));
73930
74178
  const lifted = el.getAttribute(LAYER_REVEAL_PRIOR_POSITION_ATTR) === "static";
73931
74179
  const isStatic = lifted || !/^(relative|absolute|fixed|sticky)$/.test(cs.position);
73932
74180
  if (!isStatic && (cs.left.endsWith("%") || cs.top.endsWith("%"))) return "percent";
@@ -73935,8 +74183,8 @@ function applyElementPositionOffset(el, gestureOffset) {
73935
74183
  const previous = { position: el.style.position, left: el.style.left, top: el.style.top };
73936
74184
  const before = layoutBox(el);
73937
74185
  if (isStatic) el.style.position = "relative";
73938
- el.style.left = `${Math.round(baseLeft) + dx}px`;
73939
- el.style.top = `${Math.round(baseTop) + dy}px`;
74186
+ el.style.left = `${roundTo3(baseLeft + dx)}px`;
74187
+ el.style.top = `${roundTo3(baseTop + dy)}px`;
73940
74188
  const after = layoutBox(el);
73941
74189
  const shiftedExactly = Math.abs(after.left - before.left - dx) <= 1 && Math.abs(after.top - before.top - dy) <= 1 && Math.abs(after.width - before.width) <= 1 && Math.abs(after.height - before.height) <= 1;
73942
74190
  if (!shiftedExactly) {
@@ -73962,9 +74210,29 @@ function settleGsapDraftAtGestureStart(el) {
73962
74210
  const y = Number.parseFloat(el.getAttribute("data-hf-drag-gsap-base-y") ?? "");
73963
74211
  if (gsap3 && Number.isFinite(x) && Number.isFinite(y)) gsap3.set(el, { x, y });
73964
74212
  }
74213
+ var plainMoveCounter = 0;
74214
+ function stagePlainTranslate(commitPositionPatchToHtml, selection, next, coalesceKey) {
74215
+ const style = selection.element.style;
74216
+ const before = style.getPropertyValue("translate");
74217
+ const patch = translatePatch(next);
74218
+ style.setProperty("translate", patch.value);
74219
+ const rollback = () => {
74220
+ if (style.getPropertyValue("translate") === patch.value) style.setProperty("translate", before);
74221
+ };
74222
+ const key2 = coalesceKey ?? `move:${++plainMoveCounter}`;
74223
+ const options = { label: "Move layer", coalesceKey: key2, coalesceMs: Number.POSITIVE_INFINITY };
74224
+ const save = () => commitPositionPatchToHtml(selection, [patch], options).catch((error) => {
74225
+ rollback();
74226
+ throw error;
74227
+ });
74228
+ return { save, rollback };
74229
+ }
73965
74230
  function stageElementOffset({ commitPositionPatchToHtml, showToast, readOnlyPreview }, selection, next, coalesceKey) {
73966
74231
  const el = selection.element;
73967
74232
  if (readOnlyPreview) return { save: () => Promise.resolve(), rollback: () => void 0 };
74233
+ if (!gsapWritesPosition(el)) {
74234
+ return stagePlainTranslate(commitPositionPatchToHtml, selection, next, coalesceKey);
74235
+ }
73968
74236
  const previous = { position: el.style.position, left: el.style.left, top: el.style.top };
73969
74237
  const liftMarker = el.getAttribute(LAYER_REVEAL_PRIOR_POSITION_ATTR);
73970
74238
  const result = applyElementPositionOffset(el, next);
@@ -74238,8 +74506,11 @@ function useDomGeometryCommits({
74238
74506
  if (gsapFallback) return gsapFallback;
74239
74507
  const beforeSize = captureStudioBoxSize(selection.element);
74240
74508
  const beforeOffset = offset ? captureStudioPathOffset(selection.element) : null;
74509
+ const stageCrop = prepareCropResize(selection.element);
74241
74510
  applyStudioBoxSize(selection.element, next);
74511
+ const crop = stageCrop();
74242
74512
  const patches = buildBoxSizePatches(selection.element);
74513
+ if (crop) patches.push(crop.patch);
74243
74514
  if (offset) {
74244
74515
  applyStudioPathOffset(selection.element, offset);
74245
74516
  patches.push(...buildPathOffsetPatches(selection.element));
@@ -74250,6 +74521,7 @@ function useDomGeometryCommits({
74250
74521
  }).catch((error) => {
74251
74522
  restoreStudioBoxSize(selection.element, beforeSize);
74252
74523
  if (beforeOffset) restoreStudioPathOffset(selection.element, beforeOffset);
74524
+ crop?.revert();
74253
74525
  throw error;
74254
74526
  });
74255
74527
  },
@@ -74520,6 +74792,7 @@ function useDomEditCommits({
74520
74792
  handleDomAddTextField,
74521
74793
  handleDomRemoveTextField,
74522
74794
  stageElementPositionOffset,
74795
+ commitPositionPatchToHtml,
74523
74796
  handleDomPathOffsetCommit,
74524
74797
  handleDomBoxSizeCommit,
74525
74798
  handleDomRotationCommit,
@@ -76874,8 +77147,8 @@ async function tryGsapResizeIntercept(selection, size, animations, iframe, commi
76874
77147
  }
76875
77148
  } else {
76876
77149
  resizeProps = {
76877
- width: Math.round(size.width),
76878
- height: Math.round(size.height)
77150
+ width: roundToLayoutPx(size.width),
77151
+ height: roundToLayoutPx(size.height)
76879
77152
  };
76880
77153
  }
76881
77154
  const finalizeScaleResizeCommit = async () => {
@@ -76901,8 +77174,8 @@ async function tryGsapResizeIntercept(selection, size, animations, iframe, commi
76901
77174
  return true;
76902
77175
  }
76903
77176
  const corrected = {
76904
- x: Math.round(base2.x + residual.x),
76905
- y: Math.round(base2.y + residual.y)
77177
+ x: roundTo3(base2.x + residual.x),
77178
+ y: roundTo3(base2.y + residual.y)
76906
77179
  };
76907
77180
  logResize("scale-finalize", {
76908
77181
  dropPoint: scaleDraftDropPoint,
@@ -77291,8 +77564,8 @@ function useAnimatedPropertyCommit(deps) {
77291
77564
  const commitAnimatedProperties = useCallback137(
77292
77565
  // This is the single routing boundary for set, keyframe, whole-tween, and first-group writes.
77293
77566
  // fallow-ignore-next-line complexity
77294
- async (selection, props) => {
77295
- if (!gsapCommitMutation) return;
77567
+ async (selection, props, commit = gsapCommitMutation) => {
77568
+ if (!commit) return;
77296
77569
  const propEntries = Object.entries(props);
77297
77570
  if (propEntries.length === 0) return;
77298
77571
  const primaryProp = propEntries[0][0];
@@ -77329,7 +77602,7 @@ function useAnimatedPropertyCommit(deps) {
77329
77602
  ),
77330
77603
  pct,
77331
77604
  iframe,
77332
- { commitMutation: gsapCommitMutation },
77605
+ { commitMutation: commit },
77333
77606
  `Edit ${primaryProp} (whole animation)`
77334
77607
  );
77335
77608
  return;
@@ -77342,18 +77615,12 @@ function useAnimatedPropertyCommit(deps) {
77342
77615
  primaryProp,
77343
77616
  selector,
77344
77617
  iframe,
77345
- gsapCommitMutation
77618
+ commit
77346
77619
  );
77347
77620
  return;
77348
77621
  }
77349
77622
  if (!elementHasKeyframes) {
77350
- await commitStaticSet(
77351
- selection,
77352
- propEntries,
77353
- selector,
77354
- selectedGsapAnimations,
77355
- gsapCommitMutation
77356
- );
77623
+ await commitStaticSet(selection, propEntries, selector, selectedGsapAnimations, commit);
77357
77624
  return;
77358
77625
  }
77359
77626
  if (anim && isInstantHold(anim)) {
@@ -77363,7 +77630,7 @@ function useAnimatedPropertyCommit(deps) {
77363
77630
  propEntries,
77364
77631
  selector,
77365
77632
  selectedGsapAnimations,
77366
- gsapCommitMutation
77633
+ commit
77367
77634
  );
77368
77635
  return;
77369
77636
  }
@@ -77379,7 +77646,7 @@ function useAnimatedPropertyCommit(deps) {
77379
77646
  { percentage: 0, properties: { ...newProps, _auto: 1 } },
77380
77647
  { percentage: pct, properties: newProps }
77381
77648
  ];
77382
- await gsapCommitMutation(
77649
+ await commit(
77383
77650
  selection,
77384
77651
  {
77385
77652
  type: "add-with-keyframes",
@@ -77408,10 +77675,6 @@ function useAnimatedPropertyCommit(deps) {
77408
77675
  }
77409
77676
 
77410
77677
  // src/hooks/useGsapAwareEditing.ts
77411
- async function saveMove(outcome, saveOnElement) {
77412
- if (outcome.status === "element-offset") return saveOnElement();
77413
- assertGsapEditPersisted(outcome);
77414
- }
77415
77678
  var groupDragCommitCounter = 0;
77416
77679
  function firstPreflightFailure(results, updates) {
77417
77680
  for (const [index, result] of results.entries()) {
@@ -77432,6 +77695,7 @@ function useGsapAwareEditing({
77432
77695
  trackGsapInteractionFailure,
77433
77696
  stageElementPositionOffset,
77434
77697
  handleDomBoxSizeCommit,
77698
+ commitPositionPatchToHtml,
77435
77699
  addGsapAnimation,
77436
77700
  convertToKeyframes,
77437
77701
  setArcPath,
@@ -77446,6 +77710,8 @@ function useGsapAwareEditing({
77446
77710
  );
77447
77711
  const handleGsapAwarePathOffsetCommit = useCallback138(
77448
77712
  async (selection, next, modifiers) => {
77713
+ if (!gsapWritesPosition(selection.element))
77714
+ return stageElementPositionOffset(selection, next).save();
77449
77715
  if (gsapCommitMutation) {
77450
77716
  try {
77451
77717
  const ownedAnimations = getGsapAnimationsForSelection(selection);
@@ -77511,6 +77777,7 @@ function useGsapAwareEditing({
77511
77777
  const offsetMembers = /* @__PURE__ */ new Set();
77512
77778
  const preflightResults = await Promise.allSettled(
77513
77779
  updates.map(async ({ selection }) => {
77780
+ if (!gsapWritesPosition(selection.element)) return void offsetMembers.add(selection);
77514
77781
  const animations = await makeFetchFallback(selection, { failOnFetchError: true })();
77515
77782
  preflightAnimations.set(selection, animations);
77516
77783
  const outcome = await tryGsapDragIntercept(
@@ -77609,6 +77876,8 @@ function useGsapAwareEditing({
77609
77876
  animGroups: targetAnimations.map((a) => `${a.propertyGroup}:${a.method}`)
77610
77877
  });
77611
77878
  let anchorMove = null;
77879
+ const stageCrop = prepareCropResize(selection.element);
77880
+ let cropUndoKey = null;
77612
77881
  return runGestureTransaction({
77613
77882
  element: selection.element,
77614
77883
  label: "Resize layer",
@@ -77635,6 +77904,7 @@ function useGsapAwareEditing({
77635
77904
  makeFetchFallback(selection)
77636
77905
  );
77637
77906
  assertGsapEditPersisted(outcome);
77907
+ cropUndoKey = coalesceKey;
77638
77908
  const ownsDragOffset = outcome.status === "persisted" && outcome.ownsDragOffset === true;
77639
77909
  logResize("intercept-handled", {
77640
77910
  scaleRoute,
@@ -77669,7 +77939,12 @@ function useGsapAwareEditing({
77669
77939
  logResizeSettle(selection.element, "dom-route");
77670
77940
  await handleDomBoxSizeCommit(selection, next, offset);
77671
77941
  },
77672
- afterBufferedCommitsSaved: () => anchorMove?.save() ?? Promise.resolve(),
77942
+ afterBufferedCommitsSaved: async () => {
77943
+ await anchorMove?.save();
77944
+ if (cropUndoKey) {
77945
+ await saveCropResize(stageCrop, selection, commitPositionPatchToHtml, cropUndoKey);
77946
+ }
77947
+ },
77673
77948
  restore: () => {
77674
77949
  anchorMove?.rollback();
77675
77950
  restore();
@@ -77679,6 +77954,7 @@ function useGsapAwareEditing({
77679
77954
  },
77680
77955
  [
77681
77956
  handleDomBoxSizeCommit,
77957
+ commitPositionPatchToHtml,
77682
77958
  stageElementPositionOffset,
77683
77959
  gsapCommitMutation,
77684
77960
  previewIframeRef,
@@ -77716,10 +77992,7 @@ function useGsapAwareEditing({
77716
77992
  getGsapAnimationsForSelection
77717
77993
  ]
77718
77994
  );
77719
- const {
77720
- commitAnimatedProperty: commitAnimatedPropertyRaw,
77721
- commitAnimatedProperties: commitAnimatedPropertiesRaw
77722
- } = useAnimatedPropertyCommit({
77995
+ const { commitAnimatedProperties: commitAnimatedPropertiesRaw } = useAnimatedPropertyCommit({
77723
77996
  selectedGsapAnimations,
77724
77997
  gsapCommitMutation,
77725
77998
  addGsapAnimation: (sel, method, time) => addGsapAnimation(sel, method, time),
@@ -77730,24 +78003,28 @@ function useGsapAwareEditing({
77730
78003
  const commitAnimatedProperties = useCallback138(
77731
78004
  async (selection, properties) => {
77732
78005
  try {
77733
- await commitAnimatedPropertiesRaw(selection, properties);
78006
+ await writeSizeWithCrop(
78007
+ selection,
78008
+ properties,
78009
+ gsapCommitMutation,
78010
+ commitPositionPatchToHtml,
78011
+ (keyed) => commitAnimatedPropertiesRaw(selection, properties, keyed)
78012
+ );
77734
78013
  } catch (error) {
77735
78014
  trackGsapInteractionFailure(error, selection, "property", "Edit animated property");
77736
78015
  throw error;
77737
78016
  }
77738
78017
  },
77739
- [commitAnimatedPropertiesRaw, trackGsapInteractionFailure]
78018
+ [
78019
+ commitAnimatedPropertiesRaw,
78020
+ commitPositionPatchToHtml,
78021
+ gsapCommitMutation,
78022
+ trackGsapInteractionFailure
78023
+ ]
77740
78024
  );
77741
78025
  const commitAnimatedProperty = useCallback138(
77742
- async (selection, property, value) => {
77743
- try {
77744
- await commitAnimatedPropertyRaw(selection, property, value);
77745
- } catch (error) {
77746
- trackGsapInteractionFailure(error, selection, "property", "Edit animated property");
77747
- throw error;
77748
- }
77749
- },
77750
- [commitAnimatedPropertyRaw, trackGsapInteractionFailure]
78026
+ (selection, property, value) => commitAnimatedProperties(selection, { [property]: value }),
78027
+ [commitAnimatedProperties]
77751
78028
  );
77752
78029
  const handleSetArcPath = useCallback138(
77753
78030
  (animId, config) => {
@@ -78252,6 +78529,7 @@ function useDomEditSession({
78252
78529
  handleDomAddTextField,
78253
78530
  handleDomRemoveTextField,
78254
78531
  stageElementPositionOffset,
78532
+ commitPositionPatchToHtml,
78255
78533
  handleDomBoxSizeCommit,
78256
78534
  handleDomManualEditsReset,
78257
78535
  handleDomEditElementsDelete,
@@ -78482,6 +78760,7 @@ function useDomEditSession({
78482
78760
  trackGsapInteractionFailure,
78483
78761
  stageElementPositionOffset,
78484
78762
  handleDomBoxSizeCommit,
78763
+ commitPositionPatchToHtml,
78485
78764
  addGsapAnimation,
78486
78765
  convertToKeyframes,
78487
78766
  setArcPath,
@@ -80155,7 +80434,7 @@ function useAppHotkeys({
80155
80434
 
80156
80435
  // src/hooks/useClipboard.ts
80157
80436
  import { useCallback as useCallback147, useRef as useRef154 } from "react";
80158
- import { EXCLUDED_TAGS, mintHfId, walkCompositionDescendants } from "@hyperframes/parsers/hf-ids";
80437
+ import { EXCLUDED_TAGS, mintHfId, walkCompositionDescendants as walkCompositionDescendants2 } from "@hyperframes/parsers/hf-ids";
80159
80438
 
80160
80439
  // src/utils/clipboardPayload.ts
80161
80440
  function insertAsSibling(source, newHtml, selector, selectorIndex) {
@@ -80354,7 +80633,7 @@ function remintHfIds(html2, parser, taken) {
80354
80633
  return html2.replace(/\sdata-hf-id=("[^"]*"|'[^']*')/g, "");
80355
80634
  }
80356
80635
  const elements = [root];
80357
- walkCompositionDescendants(root, (el) => elements.push(el));
80636
+ walkCompositionDescendants2(root, (el) => elements.push(el));
80358
80637
  for (const el of elements) {
80359
80638
  if (EXCLUDED_TAGS.has(el.tagName.toLowerCase())) el.removeAttribute("data-hf-id");
80360
80639
  else el.setAttribute("data-hf-id", mintHfId(el, taken));
@@ -92455,6 +92734,7 @@ function useDomGeometryCommit({
92455
92734
  trackGsapInteractionFailure,
92456
92735
  stageElementPositionOffset,
92457
92736
  handleDomBoxSizeCommit: noDomBoxSizeRoute,
92737
+ commitPositionPatchToHtml: commitWithFreshQueue,
92458
92738
  addGsapAnimation: gsap3.addGsapAnimation,
92459
92739
  convertToKeyframes: gsap3.convertToKeyframes,
92460
92740
  setArcPath: gsap3.setArcPath,