@hyperframes/studio 0.8.102 → 0.8.104

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 (73) hide show
  1. package/dist/assets/{hyperframes-player-BuRiFtrF.js → hyperframes-player-DAoaajuM.js} +1 -1
  2. package/dist/assets/{index-BT3sauee.js → index-BDK4rTMb.js} +1 -1
  3. package/dist/assets/{index-xB5O0hPw.css → index-CQN3M56N.css} +1 -1
  4. package/dist/assets/{index-C2utDZt-.js → index-De2aO4XM.js} +179 -179
  5. package/dist/assets/{index-BM35fjFf.js → index-WV9LroAD.js} +1 -1
  6. package/dist/{chunk-MTAVNAGW.js → chunk-46XP7J3B.js} +2 -2
  7. package/dist/{chunk-S65AQH6M.js → chunk-DI4LCJHL.js} +3 -2
  8. package/dist/chunk-DI4LCJHL.js.map +1 -0
  9. package/dist/{chunk-7X4XRP3L.js → chunk-HIGHVWYD.js} +2 -2
  10. package/dist/{domEditingLayers-NU6Z5QWA.js → domEditingLayers-AQBDFNUC.js} +3 -3
  11. package/dist/index.d.ts +183 -170
  12. package/dist/index.html +2 -2
  13. package/dist/index.js +550 -389
  14. package/dist/index.js.map +1 -1
  15. package/dist/player.js +2 -2
  16. package/dist/styles.css +1 -1
  17. package/package.json +8 -8
  18. package/src/components/editor/DomEditOverlay.tsx +7 -7
  19. package/src/components/editor/PropertyPanel.tsx +5 -5
  20. package/src/components/editor/anchoredResizeCommitFeedsOffset.test.ts +46 -6
  21. package/src/components/editor/domEditOverlayGestures.ts +14 -20
  22. package/src/components/editor/domEditOverlayStartGesture.test.ts +170 -13
  23. package/src/components/editor/domEditOverlayStartGesture.ts +17 -27
  24. package/src/components/editor/manualEdits.test.ts +3 -9
  25. package/src/components/editor/manualEdits.ts +0 -1
  26. package/src/components/editor/manualEditsDom.ts +0 -10
  27. package/src/components/editor/manualEditsDomPatches.test.ts +1 -45
  28. package/src/components/editor/manualEditsDomPatches.ts +1 -23
  29. package/src/components/editor/manualEditsSnapshot.ts +2 -0
  30. package/src/components/editor/manualEditsTypes.ts +2 -0
  31. package/src/components/editor/manualOffsetDrag.test.ts +57 -0
  32. package/src/components/editor/manualOffsetDrag.ts +11 -32
  33. package/src/components/editor/plainTranslate.test.ts +38 -22
  34. package/src/components/editor/plainTranslate.ts +75 -40
  35. package/src/components/editor/previewReadOnly.overlay.test.tsx +30 -0
  36. package/src/components/editor/previewReadOnlyContext.tsx +3 -1
  37. package/src/components/editor/propertyPanelTransformCommit.test.ts +80 -0
  38. package/src/components/editor/propertyPanelTransformCommit.ts +11 -6
  39. package/src/components/editor/propertyPanelTypes.ts +1 -0
  40. package/src/components/editor/rotateInlineSave.test.ts +111 -0
  41. package/src/components/editor/rotationDraft.test.ts +111 -0
  42. package/src/components/editor/rotationDraft.ts +145 -0
  43. package/src/components/editor/useColorGradingController.test.ts +36 -1
  44. package/src/components/editor/useColorGradingController.ts +2 -2
  45. package/src/components/editor/useDomEditNudge.test.tsx +34 -0
  46. package/src/components/editor/useDomEditNudge.ts +12 -5
  47. package/src/components/editor/useDomEditOverlayGestures.ts +22 -24
  48. package/src/hooks/draggedGsapPosition.ts +28 -0
  49. package/src/hooks/elementOffsetStager.ts +26 -6
  50. package/src/hooks/gsapRuntimeBridge.ts +5 -3
  51. package/src/hooks/gsapRuntimeKeyframes.ts +36 -2
  52. package/src/hooks/plainRotation.test.ts +122 -0
  53. package/src/hooks/plainRotation.ts +42 -0
  54. package/src/hooks/useDomEditSession.ts +5 -5
  55. package/src/hooks/useDomGeometryCommit.test.tsx +75 -0
  56. package/src/hooks/useDomGeometryCommit.ts +17 -17
  57. package/src/hooks/useDomGeometryCommits.test.tsx +56 -1
  58. package/src/hooks/useDomGeometryCommits.ts +28 -36
  59. package/src/hooks/useGsapAwareEditing.test.tsx +194 -11
  60. package/src/hooks/useGsapAwareEditing.ts +43 -42
  61. package/src/hooks/useInlineTextEdit.test.tsx +33 -3
  62. package/src/hooks/useInlineTextEdit.ts +2 -2
  63. package/src/hooks/usePreviewInteraction.test.ts +32 -0
  64. package/src/hooks/usePreviewInteraction.ts +3 -2
  65. package/src/player/components/TimelineClipFades.test.tsx +103 -3
  66. package/src/player/components/TimelineClipFades.tsx +53 -50
  67. package/src/styles/theme.css +1 -1
  68. package/src/utils/projectRouting.test.ts +9 -0
  69. package/src/utils/projectRouting.ts +3 -1
  70. package/dist/chunk-S65AQH6M.js.map +0 -1
  71. /package/dist/{chunk-MTAVNAGW.js.map → chunk-46XP7J3B.js.map} +0 -0
  72. /package/dist/{chunk-7X4XRP3L.js.map → chunk-HIGHVWYD.js.map} +0 -0
  73. /package/dist/{domEditingLayers-NU6Z5QWA.js.map → domEditingLayers-AQBDFNUC.js.map} +0 -0
package/dist/index.js CHANGED
@@ -67,7 +67,7 @@ import {
67
67
  whenPreviewBooted,
68
68
  wrapTimeline,
69
69
  writeStudioUiPreferences
70
- } from "./chunk-7X4XRP3L.js";
70
+ } from "./chunk-HIGHVWYD.js";
71
71
  import {
72
72
  Button,
73
73
  ContextMenu,
@@ -133,13 +133,13 @@ import {
133
133
  resolveDomEditSelection,
134
134
  serializeDomEditTextFields,
135
135
  setCompositionSourceMap
136
- } from "./chunk-MTAVNAGW.js";
136
+ } from "./chunk-46XP7J3B.js";
137
137
  import {
138
138
  buildProjectApiPath,
139
139
  buildProjectHash,
140
140
  parseProjectHashRoute,
141
141
  parseProjectIdFromHash
142
- } from "./chunk-S65AQH6M.js";
142
+ } from "./chunk-DI4LCJHL.js";
143
143
  import {
144
144
  __export
145
145
  } from "./chunk-PZ5AY32C.js";
@@ -13768,11 +13768,7 @@ import { Fragment as Fragment17, useId as useId4, useMemo as useMemo30 } from "r
13768
13768
  import { memo as memo10 } from "react";
13769
13769
 
13770
13770
  // src/player/components/TimelineClipFades.tsx
13771
- import {
13772
- useEffect as useEffect21,
13773
- useRef as useRef28,
13774
- useState as useState16
13775
- } from "react";
13771
+ import { useEffect as useEffect21, useRef as useRef28, useState as useState16 } from "react";
13776
13772
  import {
13777
13773
  HF_AUDIO_FADE_IN_ATTR,
13778
13774
  HF_AUDIO_FADE_OUT_ATTR,
@@ -13781,7 +13777,9 @@ import {
13781
13777
  } from "@hyperframes/core/audio-fade";
13782
13778
  import { Fragment as Fragment10, jsx as jsx29, jsxs as jsxs16 } from "react/jsx-runtime";
13783
13779
  var HANDLE_SIZE = 10;
13784
- var HANDLE_HIT = 16;
13780
+ var HANDLE_HIT = 24;
13781
+ var HANDLE_Z_ABOVE_CLIP_CONTENT = 30;
13782
+ var SUPPRESS_CLIP_NATIVE_TITLE = "";
13785
13783
  var DRAG_THRESHOLD_PX = 2;
13786
13784
  function TimelineClipFades({ el, pps, widthPx, showHandles }) {
13787
13785
  const { onSetElementAttributeLive, onSetElementAttributeQuiet, onRevertElementAttributeLive } = useTimelineEditContextOptional();
@@ -13868,29 +13866,36 @@ function TimelineClipFades({ el, pps, widthPx, showHandles }) {
13868
13866
  g.edge === "in" ? "Fade in" : "Fade out"
13869
13867
  );
13870
13868
  };
13871
- const onHandleKeyDown = (e) => {
13872
- const g = gesture.current;
13873
- if (e.key !== "Escape" || !g) return;
13874
- e.preventDefault();
13875
- gesture.current = null;
13876
- setDragging(null);
13877
- revertGesture(g);
13878
- };
13869
+ useEffect21(() => {
13870
+ if (dragging === null) return;
13871
+ const cancelOnWindowEscape = (e) => {
13872
+ const g = gesture.current;
13873
+ if (e.key !== "Escape" || !g) return;
13874
+ e.preventDefault();
13875
+ e.stopPropagation();
13876
+ gesture.current = null;
13877
+ setDragging(null);
13878
+ revertGesture(g);
13879
+ };
13880
+ window.addEventListener("keydown", cancelOnWindowEscape, { capture: true });
13881
+ return () => window.removeEventListener("keydown", cancelOnWindowEscape, { capture: true });
13882
+ });
13879
13883
  const showIn = fades.fadeIn > 0;
13880
13884
  const showOut = fades.fadeOut > 0;
13881
13885
  const handlesVisible = showHandles || dragging !== null;
13882
13886
  if (!showIn && !showOut && !handlesVisible) return null;
13883
- const handleStyle = (leftPx) => ({
13887
+ const hitWidth = Math.min(HANDLE_HIT, widthPx / 2);
13888
+ const handleStyle = (edge) => ({
13884
13889
  position: "absolute",
13885
13890
  top: -(HANDLE_HIT - HANDLE_SIZE) / 2 + 1,
13886
- left: leftPx - HANDLE_HIT / 2,
13887
- width: HANDLE_HIT,
13891
+ left: edge === "in" ? 0 : widthPx - hitWidth,
13892
+ width: hitWidth,
13888
13893
  height: HANDLE_HIT,
13889
13894
  display: "flex",
13890
13895
  alignItems: "center",
13891
13896
  justifyContent: "center",
13892
13897
  cursor: "ew-resize",
13893
- zIndex: 6,
13898
+ zIndex: HANDLE_Z_ABOVE_CLIP_CONTENT,
13894
13899
  opacity: handlesVisible ? 1 : 0,
13895
13900
  pointerEvents: handlesVisible && canEdit ? "auto" : "none",
13896
13901
  touchAction: "none",
@@ -13909,7 +13914,8 @@ function TimelineClipFades({ el, pps, widthPx, showHandles }) {
13909
13914
  height: "100%",
13910
13915
  pointerEvents: "none",
13911
13916
  zIndex: 5,
13912
- overflow: "visible"
13917
+ overflow: "hidden",
13918
+ borderRadius: "inherit"
13913
13919
  },
13914
13920
  viewBox: `0 0 ${Math.max(widthPx, 1)} 100`,
13915
13921
  preserveAspectRatio: "none",
@@ -13972,13 +13978,12 @@ function TimelineClipFades({ el, pps, widthPx, showHandles }) {
13972
13978
  direction: "in",
13973
13979
  value: fades.fadeIn,
13974
13980
  max: Math.max(0, el.duration - fades.fadeOut),
13975
- style: handleStyle(inPx),
13981
+ style: handleStyle("in"),
13976
13982
  dragging: dragging === "in",
13977
13983
  onPointerDown: onHandlePointerDown("in"),
13978
13984
  onPointerMove: onHandlePointerMove,
13979
13985
  onPointerUp: (e) => finish(e, false),
13980
- onPointerCancel: (e) => finish(e, true),
13981
- onKeyDown: onHandleKeyDown
13986
+ onPointerCancel: (e) => finish(e, true)
13982
13987
  }
13983
13988
  ),
13984
13989
  /* @__PURE__ */ jsx29(
@@ -13987,13 +13992,12 @@ function TimelineClipFades({ el, pps, widthPx, showHandles }) {
13987
13992
  direction: "out",
13988
13993
  value: fades.fadeOut,
13989
13994
  max: Math.max(0, el.duration - fades.fadeIn),
13990
- style: handleStyle(widthPx - outPx),
13995
+ style: handleStyle("out"),
13991
13996
  dragging: dragging === "out",
13992
13997
  onPointerDown: onHandlePointerDown("out"),
13993
13998
  onPointerMove: onHandlePointerMove,
13994
13999
  onPointerUp: (e) => finish(e, false),
13995
- onPointerCancel: (e) => finish(e, true),
13996
- onKeyDown: onHandleKeyDown
14000
+ onPointerCancel: (e) => finish(e, true)
13997
14001
  }
13998
14002
  )
13999
14003
  ] })
@@ -14008,11 +14012,10 @@ function FadeHandle({
14008
14012
  onPointerDown,
14009
14013
  onPointerMove,
14010
14014
  onPointerUp,
14011
- onPointerCancel,
14012
- onKeyDown
14015
+ onPointerCancel
14013
14016
  }) {
14014
14017
  const label = direction === "in" ? "Fade in" : "Fade out";
14015
- return /* @__PURE__ */ jsx29(
14018
+ return /* @__PURE__ */ jsx29(Tooltip, { label: `${label} ${formatFadeSeconds(value)} s`, children: /* @__PURE__ */ jsx29(
14016
14019
  "div",
14017
14020
  {
14018
14021
  role: "slider",
@@ -14023,18 +14026,17 @@ function FadeHandle({
14023
14026
  "aria-valuenow": value,
14024
14027
  "aria-valuetext": `${formatFadeSeconds(value)}s`,
14025
14028
  "data-testid": `clip-fade-handle-${direction}`,
14026
- title: `${label}: ${formatFadeSeconds(value)}s \u2014 drag to change`,
14029
+ title: SUPPRESS_CLIP_NATIVE_TITLE,
14027
14030
  style,
14028
14031
  onPointerDown,
14029
14032
  onPointerMove,
14030
14033
  onPointerUp,
14031
14034
  onPointerCancel,
14032
- onKeyDown,
14033
14035
  onClick: (e) => e.stopPropagation(),
14034
14036
  onDoubleClick: (e) => e.stopPropagation(),
14035
14037
  children: /* @__PURE__ */ jsx29(FadeDot, { active: dragging })
14036
14038
  }
14037
- );
14039
+ ) });
14038
14040
  }
14039
14041
  function FadeDot({ active }) {
14040
14042
  return /* @__PURE__ */ jsx29(
@@ -14043,10 +14045,12 @@ function FadeDot({ active }) {
14043
14045
  "aria-hidden": "true",
14044
14046
  style: {
14045
14047
  display: "block",
14048
+ flexShrink: 0,
14049
+ pointerEvents: "none",
14046
14050
  width: HANDLE_SIZE,
14047
14051
  height: HANDLE_SIZE,
14048
14052
  borderRadius: "50%",
14049
- background: active ? "var(--timeline-fade-dot-active)" : "var(--clip-handle)",
14053
+ background: "var(--timeline-fade-dot)",
14050
14054
  boxShadow: "0 0 0 1.5px var(--timeline-fade-dot-ring)",
14051
14055
  transform: active ? "scale(1.15)" : void 0,
14052
14056
  transition: "transform 80ms ease-out"
@@ -27804,10 +27808,11 @@ function usePreviewReadOnly() {
27804
27808
  function usePreviewReadOnlyReason() {
27805
27809
  return useContext8(PreviewReadOnlyContext).reason;
27806
27810
  }
27807
- function useManualEditDisabledFlags(capabilities) {
27811
+ function useManualEditDisabledFlags(capabilities, offset = { x: 0, y: 0 }) {
27808
27812
  const { readOnly } = useContext8(PreviewReadOnlyContext);
27813
+ const unreadable = !Number.isFinite(offset.x) || !Number.isFinite(offset.y);
27809
27814
  return {
27810
- manualOffsetEditingDisabled: !capabilities?.canApplyManualOffset || readOnly,
27815
+ manualOffsetEditingDisabled: !capabilities?.canApplyManualOffset || readOnly || unreadable,
27811
27816
  manualSizeEditingDisabled: !capabilities?.canApplyManualSize || readOnly,
27812
27817
  manualRotationEditingDisabled: !capabilities?.canApplyManualRotation || readOnly
27813
27818
  };
@@ -31301,7 +31306,7 @@ function OffCanvasIndicators({
31301
31306
  const selectOffCanvas = async () => {
31302
31307
  const el = elements.current.get(r.key);
31303
31308
  if (!el) return;
31304
- const { resolveDomEditSelection: resolveDomEditSelection2 } = await import("./domEditingLayers-NU6Z5QWA.js");
31309
+ const { resolveDomEditSelection: resolveDomEditSelection2 } = await import("./domEditingLayers-AQBDFNUC.js");
31305
31310
  const acp = activeCompositionPathRef.current ?? "index.html";
31306
31311
  const sel = await resolveDomEditSelection2(el, {
31307
31312
  activeCompositionPath: acp,
@@ -31835,15 +31840,6 @@ function applyStudioRotation(element, rotation) {
31835
31840
  composeStudioRotationValue(element, `var(${STUDIO_ROTATION_PROP}, 0deg)`)
31836
31841
  );
31837
31842
  }
31838
- function applyStudioRotationDraft(element, rotation) {
31839
- promoteInlineForTransform(element);
31840
- writeStudioRotationVars(element, rotation, { updateBase: false });
31841
- element.setAttribute(STUDIO_ROTATION_DRAFT_ATTR, "true");
31842
- element.style.setProperty(
31843
- "rotate",
31844
- composeStudioRotationValue(element, `${roundTo3(rotation.angle)}deg`)
31845
- );
31846
- }
31847
31843
 
31848
31844
  // src/components/editor/manualEditsSeekReapply.ts
31849
31845
  function queryStudioElements(doc, attr) {
@@ -31981,6 +31977,8 @@ function captureStudioBoxSize(element) {
31981
31977
  function captureStudioRotation(element) {
31982
31978
  return {
31983
31979
  rotate: element.style.getPropertyValue("rotate"),
31980
+ transform: element.style.getPropertyValue("transform"),
31981
+ display: element.style.getPropertyValue("display"),
31984
31982
  transformOrigin: element.style.getPropertyValue("transform-origin"),
31985
31983
  studioRotation: element.style.getPropertyValue(STUDIO_ROTATION_PROP),
31986
31984
  marker: element.getAttribute(STUDIO_ROTATION_ATTR),
@@ -32376,6 +32374,29 @@ function installStudioManualEditSeekReapply(win, apply) {
32376
32374
  }
32377
32375
 
32378
32376
  // src/hooks/draggedGsapPosition.ts
32377
+ var cssValue = (style, prop) => {
32378
+ const value = style.getPropertyValue(prop).trim();
32379
+ return value === "none" ? "" : value;
32380
+ };
32381
+ function readCssRotation(element, withRotate = true) {
32382
+ const view = element.ownerDocument.defaultView;
32383
+ if (!view) return 0;
32384
+ const style = view.getComputedStyle(element);
32385
+ const rotate2 = withRotate ? cssValue(style, "rotate") : "";
32386
+ const scale = cssValue(style, "scale");
32387
+ const transform = cssValue(style, "transform");
32388
+ const angle = (list) => {
32389
+ if (!list) return 0;
32390
+ const m = new view.DOMMatrix(list);
32391
+ return Math.atan2(m.b, m.a) * 180 / Math.PI;
32392
+ };
32393
+ const folded = [rotate2 && `rotate(${rotate2})`, scale && `scale(${scale.split(/\s+/).join(",")})`];
32394
+ try {
32395
+ return angle([...folded, transform].join(" ").trim());
32396
+ } catch {
32397
+ return angle(transform);
32398
+ }
32399
+ }
32379
32400
  function computeDraggedGsapPosition(element, studioOffset, fallbackBase) {
32380
32401
  const rotStyle = element.style.getPropertyValue("--hf-studio-rotation");
32381
32402
  const rotDeg = Number.parseFloat(rotStyle) || 0;
@@ -32402,6 +32423,144 @@ function computeDraggedGsapPosition(element, studioOffset, fallbackBase) {
32402
32423
 
32403
32424
  // src/hooks/gsapRuntimeKeyframes.ts
32404
32425
  import { buildArcPath } from "@hyperframes/core/gsap-parser-acorn";
32426
+
32427
+ // src/components/editor/manualEditsDomPatches.ts
32428
+ function collectInlineStyleOps(element, properties, ops) {
32429
+ for (const prop of properties) {
32430
+ const val = element.style.getPropertyValue(prop);
32431
+ if (val) ops.push({ type: "inline-style", property: prop, value: val });
32432
+ }
32433
+ }
32434
+ function collectAttributeOps(element, attrNames, ops) {
32435
+ for (const attr of attrNames) {
32436
+ const val = element.getAttribute(attr);
32437
+ if (val !== null) ops.push({ type: "attribute", property: attr, value: val });
32438
+ }
32439
+ }
32440
+ function appendTransformDisplayOps(element, ops) {
32441
+ const val = element.getAttribute(STUDIO_ORIGINAL_TRANSFORM_DISPLAY_ATTR);
32442
+ if (val !== null) {
32443
+ ops.push({ type: "inline-style", property: "display", value: val || null });
32444
+ ops.push({ type: "attribute", property: STUDIO_ORIGINAL_TRANSFORM_DISPLAY_ATTR, value: null });
32445
+ }
32446
+ }
32447
+ function buildPathOffsetPatches(element) {
32448
+ const ops = [];
32449
+ collectInlineStyleOps(element, [STUDIO_OFFSET_X_PROP, STUDIO_OFFSET_Y_PROP], ops);
32450
+ const inlineTranslate = element.style.getPropertyValue("translate");
32451
+ const hasOffsetVars = element.style.getPropertyValue(STUDIO_OFFSET_X_PROP) || element.style.getPropertyValue(STUDIO_OFFSET_Y_PROP);
32452
+ const translateValue = inlineTranslate && inlineTranslate !== "none" ? inlineTranslate : hasOffsetVars ? `var(${STUDIO_OFFSET_X_PROP}, 0px) var(${STUDIO_OFFSET_Y_PROP}, 0px)` : null;
32453
+ if (translateValue) {
32454
+ ops.push({ type: "inline-style", property: "translate", value: translateValue });
32455
+ }
32456
+ ops.push({ type: "attribute", property: STUDIO_PATH_OFFSET_ATTR, value: "true" });
32457
+ collectAttributeOps(
32458
+ element,
32459
+ [STUDIO_ORIGINAL_TRANSLATE_ATTR, STUDIO_ORIGINAL_INLINE_TRANSLATE_ATTR],
32460
+ ops
32461
+ );
32462
+ collectInlineStyleOps(element, ["display"], ops);
32463
+ collectAttributeOps(element, [STUDIO_ORIGINAL_TRANSFORM_DISPLAY_ATTR], ops);
32464
+ return ops;
32465
+ }
32466
+ function buildClearPathOffsetPatches(element) {
32467
+ const originalInlineTranslate = element.getAttribute(STUDIO_ORIGINAL_INLINE_TRANSLATE_ATTR);
32468
+ const ops = [
32469
+ { type: "inline-style", property: STUDIO_OFFSET_X_PROP, value: null },
32470
+ { type: "inline-style", property: STUDIO_OFFSET_Y_PROP, value: null },
32471
+ { type: "inline-style", property: "translate", value: originalInlineTranslate || null },
32472
+ { type: "attribute", property: STUDIO_PATH_OFFSET_ATTR, value: null },
32473
+ { type: "attribute", property: STUDIO_ORIGINAL_TRANSLATE_ATTR, value: null },
32474
+ { type: "attribute", property: STUDIO_ORIGINAL_INLINE_TRANSLATE_ATTR, value: null }
32475
+ ];
32476
+ appendTransformDisplayOps(element, ops);
32477
+ return ops;
32478
+ }
32479
+ var BOX_SIZE_STYLE_PROPS = [
32480
+ "width",
32481
+ "height",
32482
+ "min-width",
32483
+ "min-height",
32484
+ "max-width",
32485
+ "max-height",
32486
+ "flex-basis",
32487
+ "flex-grow",
32488
+ "flex-shrink",
32489
+ "box-sizing",
32490
+ "scale",
32491
+ "transform-origin",
32492
+ "display"
32493
+ ];
32494
+ var BOX_SIZE_ORIG_ATTRS = [
32495
+ [STUDIO_ORIGINAL_WIDTH_ATTR, "width"],
32496
+ [STUDIO_ORIGINAL_HEIGHT_ATTR, "height"],
32497
+ // Records a measurement rather than a style, so it restores nothing.
32498
+ [STUDIO_ORIGINAL_BOX_WIDTH_ATTR, ""],
32499
+ [STUDIO_ORIGINAL_BOX_HEIGHT_ATTR, ""],
32500
+ [STUDIO_ORIGINAL_MIN_WIDTH_ATTR, "min-width"],
32501
+ [STUDIO_ORIGINAL_MIN_HEIGHT_ATTR, "min-height"],
32502
+ [STUDIO_ORIGINAL_MAX_WIDTH_ATTR, "max-width"],
32503
+ [STUDIO_ORIGINAL_MAX_HEIGHT_ATTR, "max-height"],
32504
+ [STUDIO_ORIGINAL_FLEX_BASIS_ATTR, "flex-basis"],
32505
+ [STUDIO_ORIGINAL_FLEX_GROW_ATTR, "flex-grow"],
32506
+ [STUDIO_ORIGINAL_FLEX_SHRINK_ATTR, "flex-shrink"],
32507
+ [STUDIO_ORIGINAL_BOX_SIZING_ATTR, "box-sizing"],
32508
+ [STUDIO_ORIGINAL_SCALE_ATTR, "scale"],
32509
+ [STUDIO_ORIGINAL_TRANSFORM_ORIGIN_ATTR, "transform-origin"],
32510
+ [STUDIO_ORIGINAL_DISPLAY_ATTR, "display"],
32511
+ [STUDIO_ORIGINAL_TRANSFORM_DISPLAY_ATTR, ""]
32512
+ ];
32513
+ function buildBoxSizePatches(element) {
32514
+ const ops = [];
32515
+ collectInlineStyleOps(element, [STUDIO_WIDTH_PROP, STUDIO_HEIGHT_PROP], ops);
32516
+ collectInlineStyleOps(element, BOX_SIZE_STYLE_PROPS, ops);
32517
+ ops.push({ type: "attribute", property: STUDIO_BOX_SIZE_ATTR, value: "true" });
32518
+ collectAttributeOps(
32519
+ element,
32520
+ BOX_SIZE_ORIG_ATTRS.map(([attr]) => attr),
32521
+ ops
32522
+ );
32523
+ return ops;
32524
+ }
32525
+ function buildClearBoxSizePatches(element) {
32526
+ const ops = [
32527
+ { type: "inline-style", property: STUDIO_WIDTH_PROP, value: null },
32528
+ { type: "inline-style", property: STUDIO_HEIGHT_PROP, value: null },
32529
+ { type: "attribute", property: STUDIO_BOX_SIZE_ATTR, value: null }
32530
+ ];
32531
+ for (const [attrName, styleProp] of BOX_SIZE_ORIG_ATTRS) {
32532
+ const origVal = element.getAttribute(attrName);
32533
+ if (origVal !== null && styleProp) {
32534
+ ops.push({ type: "inline-style", property: styleProp, value: origVal || null });
32535
+ }
32536
+ ops.push({ type: "attribute", property: attrName, value: null });
32537
+ }
32538
+ return ops;
32539
+ }
32540
+ function buildClearRotationPatches(element) {
32541
+ const origInlineRotate = element.getAttribute(STUDIO_ORIGINAL_INLINE_ROTATE_ATTR);
32542
+ const origRotationTransformOrigin = element.getAttribute(
32543
+ STUDIO_ORIGINAL_ROTATION_TRANSFORM_ORIGIN_ATTR
32544
+ );
32545
+ const ops = [
32546
+ { type: "inline-style", property: STUDIO_ROTATION_PROP, value: null },
32547
+ { type: "inline-style", property: "rotate", value: origInlineRotate || null },
32548
+ {
32549
+ type: "inline-style",
32550
+ property: "transform-origin",
32551
+ value: origRotationTransformOrigin !== null ? origRotationTransformOrigin || null : null
32552
+ },
32553
+ { type: "attribute", property: STUDIO_ROTATION_ATTR, value: null },
32554
+ { type: "attribute", property: STUDIO_ROTATION_DRAFT_ATTR, value: null },
32555
+ { type: "attribute", property: STUDIO_ORIGINAL_ROTATE_ATTR, value: null },
32556
+ { type: "attribute", property: STUDIO_ORIGINAL_INLINE_ROTATE_ATTR, value: null },
32557
+ { type: "attribute", property: STUDIO_ORIGINAL_ROTATION_TRANSFORM_ORIGIN_ATTR, value: null }
32558
+ ];
32559
+ appendTransformDisplayOps(element, ops);
32560
+ return ops;
32561
+ }
32562
+
32563
+ // src/hooks/gsapRuntimeKeyframes.ts
32405
32564
  var FLAT_SKIP_KEYS = /* @__PURE__ */ new Set([
32406
32565
  "ease",
32407
32566
  "duration",
@@ -32616,9 +32775,32 @@ function gsapWritesChannels(el, channels) {
32616
32775
  )
32617
32776
  );
32618
32777
  }
32778
+ var ROTATION_CHANNELS = [
32779
+ ...["rotation", "rotationX", "rotationY", "rotationZ"],
32780
+ ...["rotate", "rotateX", "rotateY", "rotateZ"]
32781
+ ];
32782
+ var gsapRendersTransform = (el) => !!el._gsap?.renderTransform;
32619
32783
  function gsapWritesPosition(el) {
32784
+ return gsapRendersTransform(el) || gsapWritesChannels(el, MOVE_CHANNELS);
32785
+ }
32786
+ function gsapWritesRotation(el) {
32787
+ return gsapRendersTransform(el) || gsapWritesChannels(el, ROTATION_CHANNELS);
32788
+ }
32789
+ function gsapHoldsTranslate(el) {
32620
32790
  const cache2 = el._gsap;
32621
- return !!cache2?.renderTransform || gsapWritesChannels(el, MOVE_CHANNELS);
32791
+ return ["x", "y", "xPercent", "yPercent"].some(
32792
+ (key2) => !!Number.parseFloat(String(cache2?.[key2]))
32793
+ );
32794
+ }
32795
+ var BOX_CHANNELS = [
32796
+ ...BOX_SIZE_STYLE_PROPS.map(
32797
+ (prop) => prop.replace(/-([a-z])/g, (_, c) => c.toUpperCase())
32798
+ ),
32799
+ "scaleX",
32800
+ "scaleY"
32801
+ ];
32802
+ function gsapWritesBox(el) {
32803
+ return gsapWritesPosition(el) || gsapWritesChannels(el, BOX_CHANNELS);
32622
32804
  }
32623
32805
  function elementHasNonHoldTween(el, channels) {
32624
32806
  const win = el.ownerDocument.defaultView;
@@ -32677,49 +32859,75 @@ function scanAllRuntimeKeyframes(iframe, clipById) {
32677
32859
  }
32678
32860
 
32679
32861
  // src/components/editor/plainTranslate.ts
32680
- var TERM = /^(-?\d*\.?\d+(?:e[-+]?\d+)?)(px|%)$/i;
32681
- var SIGN = { "+": 1, "-": -1 };
32682
- function termPx(term, side) {
32683
- const match = TERM.exec(term ?? "");
32684
- return match ? Number(match[1]) * (match[2] === "%" ? side / 100 : 1) : null;
32685
- }
32686
- function resolveLength(value, side) {
32687
- const parts = (/^calc\((.*)\)$/.exec(value)?.[1] ?? value).split(" ");
32688
- if (parts.length % 2 === 0) return null;
32689
- let total = termPx(parts[0], side);
32690
- for (let i = 1; i < parts.length && total !== null; i += 2) {
32691
- const term = termPx(parts[i + 1], side);
32692
- const sign = SIGN[parts[i] ?? ""];
32693
- total = term === null || !sign ? null : total + sign * term;
32694
- }
32695
- return total;
32696
- }
32697
- function borderBox(cs, size, a, b) {
32862
+ var TOKEN = /\s*(?:(\d*\.?\d+(?:e[-+]?\d+)?)(px|%)?|([a-z]+)\(|([-+*/(),]))/giy;
32863
+ var FUNCTIONS = {
32864
+ "(": ([a = Number.NaN]) => a,
32865
+ "calc(": ([a = Number.NaN]) => a,
32866
+ "min(": (args) => Math.min(...args),
32867
+ "max(": (args) => Math.max(...args),
32868
+ "clamp(": ([lo = Number.NaN, v = Number.NaN, hi = Number.NaN]) => Math.max(lo, Math.min(v, hi))
32869
+ };
32870
+ function tokenize(value, side) {
32871
+ const tokens = [];
32872
+ const end = value.trimEnd().length;
32873
+ TOKEN.lastIndex = 0;
32874
+ for (let m = TOKEN.exec(value); m; m = TOKEN.lastIndex < end ? TOKEN.exec(value) : null) {
32875
+ if (m[1]) tokens.push(Number(m[1]) * (m[2] === "%" ? side / 100 : 1));
32876
+ else tokens.push(m[3] ? `${m[3].toLowerCase()}(` : m[4] ?? "");
32877
+ }
32878
+ return TOKEN.lastIndex < end ? [] : tokens;
32879
+ }
32880
+ function evaluateLength(value, side) {
32881
+ const tokens = tokenize(value, side);
32882
+ let i = 0;
32883
+ const next = () => tokens[i++];
32884
+ const take = (...ops) => ops.includes(tokens[i]) && next();
32885
+ function sum() {
32886
+ let total2 = product();
32887
+ for (let op = take("+", "-"); op; op = take("+", "-"))
32888
+ total2 += (op === "+" ? 1 : -1) * product();
32889
+ return total2;
32890
+ }
32891
+ function product() {
32892
+ let total2 = unary();
32893
+ for (let op = take("*", "/"); op; op = take("*", "/"))
32894
+ total2 = op === "*" ? total2 * unary() : total2 / unary();
32895
+ return total2;
32896
+ }
32897
+ function unary() {
32898
+ return take("-") ? -unary() : atom();
32899
+ }
32900
+ function atom() {
32901
+ const token = next();
32902
+ if (typeof token === "number") return token;
32903
+ const fn = FUNCTIONS[token ?? ""];
32904
+ if (!fn) return Number.NaN;
32905
+ const args = [sum()];
32906
+ while (take(",")) args.push(sum());
32907
+ return take(")") ? fn(args) : Number.NaN;
32908
+ }
32909
+ const total = tokens.length ? sum() : Number.NaN;
32910
+ return i === tokens.length ? total : Number.NaN;
32911
+ }
32912
+ var CONTENT_BOXES = /* @__PURE__ */ new Set(["content-box", "fill-box"]);
32913
+ function referenceBox(cs, size, a, b) {
32698
32914
  const n = (prop) => Number.parseFloat(cs.getPropertyValue(prop)) || 0;
32699
32915
  const pad = n(`padding-${a}`) + n(`padding-${b}`) + n(`border-${a}-width`) + n(`border-${b}-width`);
32700
- return n(size) + (cs.boxSizing === "border-box" ? 0 : pad);
32701
- }
32702
- function resolveInTransform(el, x, y) {
32703
- const style = el.style;
32704
- const saved2 = [style.getPropertyValue("transform"), style.getPropertyPriority("transform")];
32705
- style.setProperty("transform", `translate(${x}, ${y})`, "important");
32706
- try {
32707
- const resolved2 = el.ownerDocument.defaultView?.getComputedStyle(el).transform ?? "";
32708
- const matrix = new DOMMatrixReadOnly(resolved2.startsWith("matrix") ? resolved2 : void 0);
32709
- return { x: matrix.m41, y: matrix.m42 };
32710
- } finally {
32711
- style.setProperty("transform", saved2[0] ?? "", saved2[1] ?? "");
32712
- }
32916
+ const content = n(size) - (cs.boxSizing === "border-box" ? pad : 0);
32917
+ return CONTENT_BOXES.has(cs.getPropertyValue("transform-box")) ? content : content + pad;
32713
32918
  }
32714
32919
  function readTranslatePx(el) {
32715
32920
  const cs = el.ownerDocument.defaultView?.getComputedStyle(el);
32716
- const value = el.style.getPropertyValue("translate") || cs?.translate || "none";
32921
+ const value = cs?.translate || el.style.getPropertyValue("translate") || "none";
32717
32922
  if (value === "none") return { x: 0, y: 0 };
32718
32923
  const [x = "0px", y = "0px"] = splitTopLevelWhitespace(value);
32719
- const px = cs && resolveLength(x, borderBox(cs, "width", "left", "right"));
32720
- const py = cs && resolveLength(y, borderBox(cs, "height", "top", "bottom"));
32721
- return px != null && py != null ? { x: px, y: py } : resolveInTransform(el, x, y);
32924
+ const side = (size, a, b) => cs ? referenceBox(cs, size, a, b) : 0;
32925
+ return {
32926
+ x: evaluateLength(x, side("width", "left", "right")),
32927
+ y: evaluateLength(y, side("height", "top", "bottom"))
32928
+ };
32722
32929
  }
32930
+ var UNREADABLE_TRANSLATE = "Studio can't read this layer's translate. Move it in the Code tab.";
32723
32931
  function formatTranslatePx(p) {
32724
32932
  return `${roundTo3(p.x)}px ${roundTo3(p.y)}px`;
32725
32933
  }
@@ -32729,8 +32937,11 @@ function translatePatch(p) {
32729
32937
  function writeTranslatePx(el, p) {
32730
32938
  el.style.setProperty("translate", formatTranslatePx(p));
32731
32939
  }
32732
- function readMoveOffset(el) {
32733
- return gsapWritesPosition(el) ? readStudioPathOffset(el) : readTranslatePx(el);
32940
+ function readMoveOffset(el, plainTranslate = !gsapWritesPosition(el)) {
32941
+ return plainTranslate ? readTranslatePx(el) : readStudioPathOffset(el);
32942
+ }
32943
+ function readShownRotation(el) {
32944
+ return gsapWritesRotation(el) ? readStudioRotation(el) : { angle: roundTo3(readCssRotation(el)) };
32734
32945
  }
32735
32946
 
32736
32947
  // src/components/editor/manualOffsetDrag.ts
@@ -32747,17 +32958,6 @@ function applyOffsetDragDraftViaGsap(element, offset, baseGsap) {
32747
32958
  gsap3.set(element, { x: newX, y: newY });
32748
32959
  return true;
32749
32960
  }
32750
- function applyRotationDraftViaGsap(element, angle) {
32751
- const gsap3 = getOffsetDragGsap(element);
32752
- if (!gsap3) return false;
32753
- element.style.setProperty("rotate", "none");
32754
- gsap3.set(element, { rotation: angle });
32755
- return true;
32756
- }
32757
- function readGsapRotation(element) {
32758
- const gsap3 = getOffsetDragGsap(element);
32759
- return gsap3 ? Number(gsap3.getProperty(element, "rotation")) || 0 : 0;
32760
- }
32761
32961
  var DEFAULT_OFFSET_PROBE_PX = 100;
32762
32962
  var MIN_PROBE_VECTOR_LENGTH_PX = 0.01;
32763
32963
  var MIN_MATRIX_DETERMINANT = 1e-6;
@@ -32905,8 +33105,11 @@ function resolveManualOffsetForPointerDelta(input) {
32905
33105
  };
32906
33106
  }
32907
33107
  function createManualOffsetDragMember(input) {
32908
- const plainTranslate = !!input.gesture && !gsapWritesPosition(input.element);
33108
+ const gsapOwns = input.gesture === "resize" ? gsapWritesBox : gsapWritesPosition;
33109
+ const plainTranslate = !!input.gesture && !gsapOwns(input.element);
32909
33110
  const initialOffset = plainTranslate ? readTranslatePx(input.element) : readAppliedStudioPathOffset(input.element);
33111
+ if (!finitePoint(initialOffset))
33112
+ return { ok: false, reason: UNREADABLE_TRANSLATE, selection: input.selection };
32910
33113
  const win = input.element.ownerDocument.defaultView;
32911
33114
  const gsapX = !plainTranslate && win?.gsap?.getProperty?.(input.element, "x") || 0;
32912
33115
  const gsapY = !plainTranslate && win?.gsap?.getProperty?.(input.element, "y") || 0;
@@ -33069,6 +33272,94 @@ function resumeGsapTimelines(element) {
33069
33272
  win.__player?.seek?.(t);
33070
33273
  }
33071
33274
 
33275
+ // src/components/editor/rotationDraft.ts
33276
+ function lastRuleTransform(element) {
33277
+ let value = "";
33278
+ for (const sheet of Array.from(element.ownerDocument.styleSheets)) {
33279
+ let rules = [];
33280
+ try {
33281
+ rules = Array.from(sheet.cssRules);
33282
+ } catch {
33283
+ }
33284
+ for (const rule of rules) {
33285
+ const declared = rule.style?.getPropertyValue("transform");
33286
+ if (declared && element.matches(rule.selectorText)) value = declared;
33287
+ }
33288
+ }
33289
+ return value;
33290
+ }
33291
+ function translatingTransform(element) {
33292
+ const view = element.ownerDocument.defaultView;
33293
+ const computed = view?.getComputedStyle(element).transform ?? "none";
33294
+ const m = computed === "none" ? null : new view.DOMMatrix(computed);
33295
+ if (!m || m.m41 === 0 && m.m42 === 0) return "";
33296
+ const value = element.style.getPropertyValue("transform") || lastRuleTransform(element);
33297
+ return value === "none" ? "" : value;
33298
+ }
33299
+ var TRANSLATE = /^translate(?:3d|X|Y|Z)?\(/i;
33300
+ var OWN_TURN = /^rotate\(\s*-?[\d.]+(?:e[+-]?\d+)?deg\s*\)$/i;
33301
+ function readCssRotationTarget(element) {
33302
+ const style = element.ownerDocument.defaultView?.getComputedStyle(element);
33303
+ const inline = style?.display === "inline";
33304
+ const transform = translatingTransform(element);
33305
+ if (!transform) {
33306
+ const share2 = readCssRotation(element, false);
33307
+ return { property: "rotate", before: "", after: "", share: share2, sign: 1, inline };
33308
+ }
33309
+ const parts = splitTopLevelWhitespace(transform);
33310
+ const lead = parts.findIndex((part) => !TRANSLATE.test(part));
33311
+ const split = lead < 0 ? parts.length : lead;
33312
+ const rest = parts.slice(split);
33313
+ if (OWN_TURN.test(rest[0] ?? "")) rest.shift();
33314
+ const [before, after] = [parts.slice(0, split).join(" "), rest.join(" ")];
33315
+ const [sx = 1, sy = sx] = splitTopLevelWhitespace(style?.getPropertyValue("scale") ?? "").map(
33316
+ Number.parseFloat
33317
+ );
33318
+ const inlineStyle = element.style;
33319
+ const saved2 = [
33320
+ inlineStyle.getPropertyValue("transform"),
33321
+ inlineStyle.getPropertyPriority("transform")
33322
+ ];
33323
+ inlineStyle.setProperty("transform", `${before} ${after}`.trim() || "none");
33324
+ const share = readCssRotation(element);
33325
+ inlineStyle.setProperty("transform", saved2[0] ?? "", saved2[1] ?? "");
33326
+ return { property: "transform", before, after, share, sign: sx * sy < 0 ? -1 : 1, inline };
33327
+ }
33328
+ function applyCssRotation(element, angle, target = readCssRotationTarget(element)) {
33329
+ const turn = `${roundTo3(target.sign * (angle - target.share))}deg`;
33330
+ const value = target.property === "rotate" ? turn : [target.before, `rotate(${turn})`, target.after].filter(Boolean).join(" ");
33331
+ const patches = [
33332
+ { type: "inline-style", property: target.property, value }
33333
+ ];
33334
+ if (target.inline) {
33335
+ patches.unshift({ type: "inline-style", property: "display", value: "inline-block" });
33336
+ }
33337
+ for (const patch of patches) element.style.setProperty(patch.property, patch.value);
33338
+ return patches;
33339
+ }
33340
+ function restorePlainRotation(element, snapshot3) {
33341
+ restoreStudioRotation(element, snapshot3);
33342
+ element.style.setProperty("transform", snapshot3.transform);
33343
+ element.style.setProperty("display", snapshot3.display);
33344
+ }
33345
+ function applyRotationDraft(element, angle, plain) {
33346
+ if (plain) return void applyCssRotation(element, angle, plain);
33347
+ element.style.setProperty("rotate", "none");
33348
+ getOffsetDragGsap(element)?.set(element, { rotation: angle });
33349
+ }
33350
+ function restoreRotationDraft(element, angle, snapshot3, plain) {
33351
+ if (plain) return restorePlainRotation(element, snapshot3);
33352
+ getOffsetDragGsap(element)?.set(element, {
33353
+ rotation: angle - (Number.parseFloat(snapshot3.studioRotation) || 0)
33354
+ });
33355
+ restoreStudioRotation(element, snapshot3);
33356
+ }
33357
+ function readRotationBase(element, plain) {
33358
+ if (plain) return readCssRotation(element);
33359
+ const gsapRotation = Number(getOffsetDragGsap(element)?.getProperty(element, "rotation") ?? 0);
33360
+ return gsapRotation + readStudioRotation(element).angle;
33361
+ }
33362
+
33072
33363
  // src/components/editor/domEditResizeLocal.ts
33073
33364
  var MIN_RESIZE_LOCAL_PX = 1;
33074
33365
  var DEGENERATE_START_DIST_PX = 3;
@@ -33182,27 +33473,6 @@ function resolveResizeDraftRect(g, element, overlayEl, iframe, measureOrientedRe
33182
33473
  };
33183
33474
  }
33184
33475
 
33185
- // src/utils/elementGsap.ts
33186
- function gsapOf(element) {
33187
- return element.ownerDocument.defaultView?.gsap;
33188
- }
33189
- function setElementGsapPosition(element, x, y) {
33190
- const gsap3 = gsapOf(element);
33191
- if (!gsap3?.set) return false;
33192
- gsap3.set(element, { x, y });
33193
- return true;
33194
- }
33195
- function setElementGsapScale(element, x, y) {
33196
- const gsap3 = gsapOf(element);
33197
- if (!gsap3?.set) return false;
33198
- gsap3.set(element, { scaleX: x, scaleY: y });
33199
- return true;
33200
- }
33201
- function readElementGsapNumber(element, prop) {
33202
- const value = Number(gsapOf(element)?.getProperty?.(element, prop));
33203
- return Number.isFinite(value) ? value : null;
33204
- }
33205
-
33206
33476
  // src/components/editor/snapEngine.ts
33207
33477
  var SNAP_THRESHOLD_PX = 6;
33208
33478
  var SNAP_ENGAGE_TRAVEL_PX = 4;
@@ -33707,7 +33977,7 @@ function startGroupDrag(e, opts) {
33707
33977
  restoreManualOffsetDragMembers(members);
33708
33978
  e.preventDefault();
33709
33979
  e.stopPropagation();
33710
- opts.onBlockedMoveRef.current(result.selection);
33980
+ opts.onBlockedMoveRef.current(result.selection, result.reason);
33711
33981
  return false;
33712
33982
  }
33713
33983
  members.push(result.member);
@@ -33773,18 +34043,6 @@ function startGesture(kind, e, opts, options) {
33773
34043
  const rawContentScaleY = rect.height / (rect.editScaleY * actualHeight);
33774
34044
  const contentScaleX = Number.isFinite(rawContentScaleX) && rawContentScaleX > 0 ? rawContentScaleX : 1;
33775
34045
  const contentScaleY = Number.isFinite(rawContentScaleY) && rawContentScaleY > 0 ? rawContentScaleY : 1;
33776
- let resizeAnchor;
33777
- if (kind === "resize") {
33778
- const startBcr = sel.element.getBoundingClientRect();
33779
- resizeAnchor = {
33780
- anchorX: startBcr.x,
33781
- anchorY: startBcr.y,
33782
- baseGsapX: readElementGsapNumber(sel.element, "x") ?? 0,
33783
- baseGsapY: readElementGsapNumber(sel.element, "y") ?? 0,
33784
- pinX: 0,
33785
- pinY: 0
33786
- };
33787
- }
33788
34046
  let initialPathOffset = captureStudioPathOffset(sel.element);
33789
34047
  let manualEditDragToken;
33790
34048
  let pathOffsetMember;
@@ -33799,7 +34057,7 @@ function startGesture(kind, e, opts, options) {
33799
34057
  gesture: "drag"
33800
34058
  });
33801
34059
  if (!result.ok) {
33802
- opts.onBlockedMoveRef.current(result.selection);
34060
+ opts.onBlockedMoveRef.current(result.selection, result.reason);
33803
34061
  return false;
33804
34062
  }
33805
34063
  pathOffsetMember = result.member;
@@ -33812,12 +34070,14 @@ function startGesture(kind, e, opts, options) {
33812
34070
  key: selectionCacheKey(sel),
33813
34071
  selection: sel,
33814
34072
  element: sel.element,
33815
- rect
34073
+ rect,
34074
+ gesture: "resize"
33816
34075
  });
33817
34076
  if (result.ok) {
33818
34077
  pathOffsetMember = result.member;
33819
34078
  initialPathOffset = result.member.initialPathOffset;
33820
34079
  manualEditDragToken = result.member.gestureToken;
34080
+ if (result.member.plainTranslate) applyManualOffsetDragDraft(result.member, 0, 0);
33821
34081
  } else {
33822
34082
  manualEditDragToken = beginStudioManualEditGesture(sel.element);
33823
34083
  }
@@ -33825,8 +34085,9 @@ function startGesture(kind, e, opts, options) {
33825
34085
  manualEditDragToken = beginStudioManualEditGesture(sel.element);
33826
34086
  }
33827
34087
  }
33828
- const gsapRotation = pathOffsetMember?.plainTranslate ? 0 : readGsapRotation(sel.element);
33829
- const rotation = { angle: gsapRotation + readStudioRotation(sel.element).angle };
34088
+ const plain = !!pathOffsetMember?.plainTranslate || !gsapWritesRotation(sel.element);
34089
+ const plainRotation = plain && kind === "rotate" ? readCssRotationTarget(sel.element) : null;
34090
+ const rotation = { angle: readRotationBase(sel.element, plain) };
33830
34091
  const overlayBounds = overlayEl?.getBoundingClientRect();
33831
34092
  const centerX = (overlayBounds?.left ?? 0) + rect.left + rect.width / 2;
33832
34093
  const centerY = (overlayBounds?.top ?? 0) + rect.top + rect.height / 2;
@@ -33864,11 +34125,11 @@ function startGesture(kind, e, opts, options) {
33864
34125
  actualWidth,
33865
34126
  actualHeight,
33866
34127
  actualRotation: rotation.angle,
34128
+ plainRotation,
33867
34129
  editScaleX: rect.editScaleX,
33868
34130
  editScaleY: rect.editScaleY,
33869
34131
  contentScaleX,
33870
34132
  contentScaleY,
33871
- resizeAnchor,
33872
34133
  manualEditDragToken,
33873
34134
  snapContext,
33874
34135
  resizeHandle: kind === "resize" ? options?.resizeHandle ?? "se" : void 0,
@@ -34123,9 +34384,7 @@ function createDomEditOverlayGestureHandlers(opts) {
34123
34384
  actualAngle: g.actualRotation,
34124
34385
  snap: e.shiftKey
34125
34386
  });
34126
- if (!applyRotationDraftViaGsap(sel.element, rotated.angle)) {
34127
- applyStudioRotationDraft(sel.element, rotated);
34128
- }
34387
+ applyRotationDraft(sel.element, rotated.angle, g.plainRotation);
34129
34388
  return;
34130
34389
  }
34131
34390
  if (g.kind === "drag") {
@@ -34260,7 +34519,8 @@ function createDomEditOverlayGestureHandlers(opts) {
34260
34519
  );
34261
34520
  const updates = groupG.members.map((member) => ({
34262
34521
  selection: member.selection,
34263
- next: applyManualOffsetDragCommit(member, dx, dy)
34522
+ next: applyManualOffsetDragCommit(member, dx, dy),
34523
+ plainTranslate: member.plainTranslate
34264
34524
  }));
34265
34525
  logDrag("drop", {
34266
34526
  pointer: `${Math.round(rawDx)},${Math.round(rawDy)}`,
@@ -34333,20 +34593,20 @@ function createDomEditOverlayGestureHandlers(opts) {
34333
34593
  actualAngle: g.actualRotation,
34334
34594
  snap: e.shiftKey
34335
34595
  });
34336
- const restoreRotation = () => {
34337
- if (!applyRotationDraftViaGsap(sel.element, g.actualRotation)) {
34338
- restoreStudioRotation(sel.element, g.initialRotation);
34339
- }
34340
- };
34596
+ const restoreRotation = () => restoreRotationDraft(
34597
+ sel.element,
34598
+ g.actualRotation,
34599
+ g.initialRotation,
34600
+ g.plainRotation !== null
34601
+ );
34341
34602
  if (!hasDomEditRotationChanged(g.actualRotation, finalRotation.angle)) {
34342
34603
  restoreRotation();
34343
34604
  endStudioManualEditGesture(sel.element, g.manualEditDragToken);
34344
34605
  return;
34345
34606
  }
34346
- if (!applyRotationDraftViaGsap(sel.element, finalRotation.angle)) {
34347
- applyStudioRotation(sel.element, finalRotation);
34348
- }
34349
- void Promise.resolve(opts.onRotationCommitRef.current(sel, finalRotation)).catch((error) => {
34607
+ applyRotationDraft(sel.element, finalRotation.angle, g.plainRotation);
34608
+ const commit = g.plainRotation ? { ...finalRotation, plain: g.plainRotation } : finalRotation;
34609
+ void Promise.resolve(opts.onRotationCommitRef.current(sel, commit)).catch((error) => {
34350
34610
  logGestureCommitFailure("rotate commit failed", error);
34351
34611
  if (g.manualEditDragToken && isStudioManualEditGestureCurrent(sel.element, g.manualEditDragToken))
34352
34612
  restoreRotation();
@@ -34375,7 +34635,10 @@ function createDomEditOverlayGestureHandlers(opts) {
34375
34635
  box.style.top = `${nextBoxTop}px`;
34376
34636
  }
34377
34637
  void Promise.resolve(
34378
- opts.onPathOffsetCommitRef.current(sel, finalOffset, { altKey: e.altKey })
34638
+ opts.onPathOffsetCommitRef.current(sel, finalOffset, {
34639
+ altKey: e.altKey,
34640
+ plainTranslate: g.pathOffsetMember.plainTranslate
34641
+ })
34379
34642
  ).catch(() => {
34380
34643
  if (g.pathOffsetMember?.gestureToken && isStudioManualEditGestureCurrent(sel.element, g.pathOffsetMember.gestureToken))
34381
34644
  restoreStudioPathOffset(sel.element, g.initialPathOffset);
@@ -34433,11 +34696,12 @@ function createDomEditOverlayGestureHandlers(opts) {
34433
34696
  restoreGestureOverlayRect(g);
34434
34697
  }
34435
34698
  if (g?.mode === "rotation" && sel) {
34436
- applyRotationDraftViaGsap(
34699
+ restoreRotationDraft(
34437
34700
  sel.element,
34438
- g.actualRotation - (Number.parseFloat(g.initialRotation.studioRotation) || 0)
34701
+ g.actualRotation,
34702
+ g.initialRotation,
34703
+ g.plainRotation !== null
34439
34704
  );
34440
- restoreStudioRotation(sel.element, g.initialRotation);
34441
34705
  endStudioManualEditGesture(sel.element, g.manualEditDragToken);
34442
34706
  }
34443
34707
  opts.blockedMoveRef.current = null;
@@ -34516,10 +34780,13 @@ function useDomEditNudge(params) {
34516
34780
  if (session.timer) clearTimeout(session.timer);
34517
34781
  const updates = session.members.map((member) => ({
34518
34782
  selection: member.selection,
34519
- next: applyManualOffsetNudgeCommit(member, session.accum)
34783
+ next: applyManualOffsetNudgeCommit(member, session.accum),
34784
+ plainTranslate: member.plainTranslate
34520
34785
  }));
34521
34786
  const p = paramsRef.current;
34522
- const commit = session.isGroup ? p.onGroupPathOffsetCommitRef.current(updates) : p.onPathOffsetCommitRef.current(updates[0].selection, updates[0].next);
34787
+ const commit = session.isGroup ? p.onGroupPathOffsetCommitRef.current(updates) : p.onPathOffsetCommitRef.current(updates[0].selection, updates[0].next, {
34788
+ plainTranslate: updates[0].plainTranslate
34789
+ });
34523
34790
  void Promise.resolve(commit).catch(() => {
34524
34791
  for (const member of session.members) {
34525
34792
  if (isStudioManualEditGestureCurrent(member.element, member.gestureToken)) {
@@ -34538,7 +34805,7 @@ function useDomEditNudge(params) {
34538
34805
  restoreManualOffsetDragMembers(session.members);
34539
34806
  endManualOffsetDragMembers(session.members);
34540
34807
  }, [params.allowCanvasMovement]);
34541
- const beginSession = () => {
34808
+ const beginSession = (event) => {
34542
34809
  const p = paramsRef.current;
34543
34810
  const groupItems = p.groupOverlayItemsRef.current;
34544
34811
  const isGroup = groupItems.length > 1;
@@ -34549,7 +34816,8 @@ function useDomEditNudge(params) {
34549
34816
  const result = createManualOffsetDragMember({ ...target, gesture: "nudge" });
34550
34817
  if (!result.ok) {
34551
34818
  restoreManualOffsetDragMembers(members);
34552
- return null;
34819
+ if (!event.repeat) p.onBlockedMoveRef.current(result.selection, result.reason);
34820
+ return "refused";
34553
34821
  }
34554
34822
  members.push(result.member);
34555
34823
  }
@@ -34562,7 +34830,8 @@ function useDomEditNudge(params) {
34562
34830
  if (shouldIgnoreNudgeKey(p, event)) return;
34563
34831
  const delta = resolveCanvasNudgeDelta(event);
34564
34832
  if (!delta) return;
34565
- const session = sessionRef.current ?? beginSession();
34833
+ const session = sessionRef.current ?? beginSession(event);
34834
+ if (session === "refused") return void event.preventDefault();
34566
34835
  if (!session) return;
34567
34836
  sessionRef.current = session;
34568
34837
  event.preventDefault();
@@ -36305,7 +36574,7 @@ function useInlineTextEdit({
36305
36574
  }
36306
36575
  if (event.key === "Escape") {
36307
36576
  event.preventDefault();
36308
- cancel();
36577
+ commit();
36309
36578
  }
36310
36579
  };
36311
36580
  const onBlur = () => commit();
@@ -36333,7 +36602,7 @@ function useInlineTextEdit({
36333
36602
  element.removeEventListener("dragover", onDragOver);
36334
36603
  element.removeEventListener("drop", onDrop);
36335
36604
  };
36336
- }, [session, commit, cancel]);
36605
+ }, [session, commit]);
36337
36606
  const commitRef = useRef66(commit);
36338
36607
  commitRef.current = commit;
36339
36608
  useEffect46(() => () => commitRef.current(), []);
@@ -36827,6 +37096,7 @@ var DomEditOverlay = memo25(function DomEditOverlay2({
36827
37096
  gestureRef,
36828
37097
  groupGestureRef,
36829
37098
  blockedMoveRef,
37099
+ onBlockedMoveRef,
36830
37100
  onManualDragStartRef,
36831
37101
  onPathOffsetCommitRef,
36832
37102
  onGroupPathOffsetCommitRef
@@ -42530,7 +42800,6 @@ function getAnimationsForElement(animations, target, element) {
42530
42800
 
42531
42801
  // src/hooks/gsapRuntimeBridge.ts
42532
42802
  var POSITION_CHANNEL_SET = new Set(POSITION_CHANNELS);
42533
- var ROTATION_CHANNELS = ["rotation", "rotationX", "rotationY", "rotationZ"];
42534
42803
  var ROTATION_CHANNEL_SET = new Set(ROTATION_CHANNELS);
42535
42804
  async function resolveGroupTween(group, animations, selection, commitMutation, fetchFallbackAnimations) {
42536
42805
  const groupAnims = animations.filter((a) => a.propertyGroup === group);
@@ -47987,12 +48256,16 @@ function createTransformCommitHandlers({
47987
48256
  "This element's position can't be edited here yet \u2014 it is driven by its animation"
47988
48257
  ))
47989
48258
  return;
47990
- const current = readMoveOffset(element.element);
48259
+ const plainTranslate = !gsapWritesPosition(element.element);
48260
+ const current = readMoveOffset(element.element, plainTranslate);
48261
+ if (!Number.isFinite(current.x) || !Number.isFinite(current.y))
48262
+ return void showToast?.(UNREADABLE_TRANSLATE);
47991
48263
  await Promise.resolve(
47992
- onSetManualOffset(element, {
47993
- x: axis === "x" ? parsed : current.x,
47994
- y: axis === "y" ? parsed : current.y
47995
- })
48264
+ onSetManualOffset(
48265
+ element,
48266
+ { x: axis === "x" ? parsed : current.x, y: axis === "y" ? parsed : current.y },
48267
+ { plainTranslate }
48268
+ )
47996
48269
  );
47997
48270
  };
47998
48271
  const commitManualSize = async (axis, nextValue) => {
@@ -49603,13 +49876,13 @@ function useColorGradingController({
49603
49876
  useEffect69(() => {
49604
49877
  setMediaMetadata(null);
49605
49878
  if (!selectedAssetPath) return;
49606
- const cacheKey = `${projectId}:${selectedAssetPath}`;
49879
+ const cacheKey = mediaMetadataUrl(projectId, selectedAssetPath);
49607
49880
  if (MEDIA_METADATA_CACHE.has(cacheKey)) {
49608
49881
  setMediaMetadata(MEDIA_METADATA_CACHE.get(cacheKey) ?? null);
49609
49882
  return;
49610
49883
  }
49611
49884
  const controller = new AbortController();
49612
- fetch(mediaMetadataUrl(projectId, selectedAssetPath), { signal: controller.signal }).then(async (response) => {
49885
+ fetch(cacheKey, { signal: controller.signal }).then(async (response) => {
49613
49886
  if (!response.ok) return { ok: false };
49614
49887
  const data = await response.json();
49615
49888
  return { ok: true, metadata: data?.metadata ?? null };
@@ -65060,7 +65333,8 @@ var PropertyPanel = memo36(function PropertyPanel2(props) {
65060
65333
  scale: 1,
65061
65334
  transformPerspective: 0
65062
65335
  };
65063
- const { manualOffsetEditingDisabled, manualSizeEditingDisabled, manualRotationEditingDisabled } = useManualEditDisabledFlags(element?.capabilities);
65336
+ const manualOffset = element ? readMoveOffset(element.element) : { x: 0, y: 0 };
65337
+ const { manualOffsetEditingDisabled, manualSizeEditingDisabled, manualRotationEditingDisabled } = useManualEditDisabledFlags(element?.capabilities, manualOffset);
65064
65338
  if (!element) {
65065
65339
  return /* @__PURE__ */ jsx175(
65066
65340
  PropertyPanelEmptyState,
@@ -65077,11 +65351,10 @@ var PropertyPanel = memo36(function PropertyPanel2(props) {
65077
65351
  const sourceLabel = element.id ? `#${element.id}` : element.selector ?? "";
65078
65352
  const sections = resolveEditingSections(domEditSelectionToFacts(element, gsapAnimations.length));
65079
65353
  const showEditableSections = element.capabilities.canEditStyles && sections.style;
65080
- const manualOffset = readMoveOffset(element.element);
65081
65354
  const manualSize = readStudioBoxSize(element.element);
65082
65355
  const resolvedWidth = manualSize.width > 0 ? manualSize.width : parsePxMetricValue(styles.width ?? "") ?? element.boundingBox.width;
65083
65356
  const resolvedHeight = manualSize.height > 0 ? manualSize.height : parsePxMetricValue(styles.height ?? "") ?? element.boundingBox.height;
65084
- const manualRotation = readStudioRotation(element.element);
65357
+ const manualRotation = readShownRotation(element.element);
65085
65358
  const elStart = Number.parseFloat(element?.dataAttributes?.start ?? "0") || 0;
65086
65359
  const elDuration = Number.parseFloat(element?.dataAttributes?.duration ?? "1") || 0;
65087
65360
  const currentPct = elDuration > 0 ? (currentTime - elStart) / elDuration * 100 : 0;
@@ -73027,9 +73300,9 @@ function usePreviewInteraction({
73027
73300
  updateDomEditHoverSelection(null);
73028
73301
  }, [updateDomEditHoverSelection]);
73029
73302
  const handleBlockedDomMove = useCallback120(
73030
- (selection) => {
73303
+ (selection, reason) => {
73031
73304
  showToast(
73032
- selection.capabilities.reasonIfDisabled ?? "This element can't be adjusted directly from the preview.",
73305
+ reason ?? selection.capabilities.reasonIfDisabled ?? "This element can't be adjusted directly from the preview.",
73033
73306
  "info"
73034
73307
  );
73035
73308
  },
@@ -74410,16 +74683,26 @@ function applyElementPositionOffset(el, gestureOffset) {
74410
74683
  }
74411
74684
 
74412
74685
  // src/hooks/elementOffsetStager.ts
74686
+ var GSAP_TOOK_OVER = "The animation took over this layer's position during the move, so it was not saved.";
74413
74687
  var ELEMENT_OFFSET_REFUSED = {
74414
74688
  anchored: "This layer is anchored from its right or bottom edge. Move it in the Code tab.",
74415
74689
  percent: "This layer's position is set in percent. Move it in the Code tab."
74416
74690
  };
74691
+ function gsapOf(el) {
74692
+ return el.ownerDocument.defaultView?.gsap;
74693
+ }
74417
74694
  function settleGsapDraftAtGestureStart(el) {
74418
- const gsap3 = el.ownerDocument.defaultView?.gsap;
74695
+ const gsap3 = gsapOf(el);
74419
74696
  const x = Number.parseFloat(el.getAttribute("data-hf-drag-gsap-base-x") ?? "");
74420
74697
  const y = Number.parseFloat(el.getAttribute("data-hf-drag-gsap-base-y") ?? "");
74421
74698
  if (gsap3 && Number.isFinite(x) && Number.isFinite(y)) gsap3.set(el, { x, y });
74422
74699
  }
74700
+ function refuseGsapTakeover(el, showToast) {
74701
+ if (!gsapHoldsTranslate(el)) return;
74702
+ gsapOf(el)?.set(el, { x: 0, y: 0, xPercent: 0, yPercent: 0 });
74703
+ showToast(GSAP_TOOK_OVER, "error");
74704
+ throw markStudioSaveErrorAlreadyToasted(new Error(GSAP_TOOK_OVER));
74705
+ }
74423
74706
  var plainMoveCounter = 0;
74424
74707
  function stagePlainTranslate(commitPositionPatchToHtml, selection, next, coalesceKey) {
74425
74708
  const style = selection.element.style;
@@ -74437,10 +74720,11 @@ function stagePlainTranslate(commitPositionPatchToHtml, selection, next, coalesc
74437
74720
  });
74438
74721
  return { save, rollback };
74439
74722
  }
74440
- function stageElementOffset({ commitPositionPatchToHtml, showToast, readOnlyPreview }, selection, next, coalesceKey) {
74723
+ function stageElementOffset({ commitPositionPatchToHtml, showToast, readOnlyPreview }, selection, next, plainTranslate, coalesceKey) {
74441
74724
  const el = selection.element;
74442
74725
  if (readOnlyPreview) return { save: () => Promise.resolve(), rollback: () => void 0 };
74443
- if (!gsapWritesPosition(el)) {
74726
+ if (plainTranslate) {
74727
+ refuseGsapTakeover(el, showToast);
74444
74728
  return stagePlainTranslate(commitPositionPatchToHtml, selection, next, coalesceKey);
74445
74729
  }
74446
74730
  const previous = { position: el.style.position, left: el.style.left, top: el.style.top };
@@ -74466,159 +74750,27 @@ function stageElementOffset({ commitPositionPatchToHtml, showToast, readOnlyPrev
74466
74750
  return { save, rollback };
74467
74751
  }
74468
74752
 
74469
- // src/components/editor/manualEditsDomPatches.ts
74470
- function collectInlineStyleOps(element, properties, ops) {
74471
- for (const prop of properties) {
74472
- const val = element.style.getPropertyValue(prop);
74473
- if (val) ops.push({ type: "inline-style", property: prop, value: val });
74474
- }
74475
- }
74476
- function collectAttributeOps(element, attrNames, ops) {
74477
- for (const attr of attrNames) {
74478
- const val = element.getAttribute(attr);
74479
- if (val !== null) ops.push({ type: "attribute", property: attr, value: val });
74480
- }
74481
- }
74482
- function appendTransformDisplayOps(element, ops) {
74483
- const val = element.getAttribute(STUDIO_ORIGINAL_TRANSFORM_DISPLAY_ATTR);
74484
- if (val !== null) {
74485
- ops.push({ type: "inline-style", property: "display", value: val || null });
74486
- ops.push({ type: "attribute", property: STUDIO_ORIGINAL_TRANSFORM_DISPLAY_ATTR, value: null });
74487
- }
74488
- }
74489
- function buildPathOffsetPatches(element) {
74490
- const ops = [];
74491
- collectInlineStyleOps(element, [STUDIO_OFFSET_X_PROP, STUDIO_OFFSET_Y_PROP], ops);
74492
- const inlineTranslate = element.style.getPropertyValue("translate");
74493
- const hasOffsetVars = element.style.getPropertyValue(STUDIO_OFFSET_X_PROP) || element.style.getPropertyValue(STUDIO_OFFSET_Y_PROP);
74494
- const translateValue = inlineTranslate && inlineTranslate !== "none" ? inlineTranslate : hasOffsetVars ? `var(${STUDIO_OFFSET_X_PROP}, 0px) var(${STUDIO_OFFSET_Y_PROP}, 0px)` : null;
74495
- if (translateValue) {
74496
- ops.push({ type: "inline-style", property: "translate", value: translateValue });
74497
- }
74498
- ops.push({ type: "attribute", property: STUDIO_PATH_OFFSET_ATTR, value: "true" });
74499
- collectAttributeOps(
74500
- element,
74501
- [STUDIO_ORIGINAL_TRANSLATE_ATTR, STUDIO_ORIGINAL_INLINE_TRANSLATE_ATTR],
74502
- ops
74503
- );
74504
- collectInlineStyleOps(element, ["display"], ops);
74505
- collectAttributeOps(element, [STUDIO_ORIGINAL_TRANSFORM_DISPLAY_ATTR], ops);
74506
- return ops;
74507
- }
74508
- function buildClearPathOffsetPatches(element) {
74509
- const originalInlineTranslate = element.getAttribute(STUDIO_ORIGINAL_INLINE_TRANSLATE_ATTR);
74510
- const ops = [
74511
- { type: "inline-style", property: STUDIO_OFFSET_X_PROP, value: null },
74512
- { type: "inline-style", property: STUDIO_OFFSET_Y_PROP, value: null },
74513
- { type: "inline-style", property: "translate", value: originalInlineTranslate || null },
74514
- { type: "attribute", property: STUDIO_PATH_OFFSET_ATTR, value: null },
74515
- { type: "attribute", property: STUDIO_ORIGINAL_TRANSLATE_ATTR, value: null },
74516
- { type: "attribute", property: STUDIO_ORIGINAL_INLINE_TRANSLATE_ATTR, value: null }
74517
- ];
74518
- appendTransformDisplayOps(element, ops);
74519
- return ops;
74520
- }
74521
- var BOX_SIZE_STYLE_PROPS = [
74522
- "width",
74523
- "height",
74524
- "min-width",
74525
- "min-height",
74526
- "max-width",
74527
- "max-height",
74528
- "flex-basis",
74529
- "flex-grow",
74530
- "flex-shrink",
74531
- "box-sizing",
74532
- "scale",
74533
- "transform-origin",
74534
- "display"
74535
- ];
74536
- var BOX_SIZE_ORIG_ATTRS = [
74537
- [STUDIO_ORIGINAL_WIDTH_ATTR, "width"],
74538
- [STUDIO_ORIGINAL_HEIGHT_ATTR, "height"],
74539
- // Records a measurement rather than a style, so it restores nothing.
74540
- [STUDIO_ORIGINAL_BOX_WIDTH_ATTR, ""],
74541
- [STUDIO_ORIGINAL_BOX_HEIGHT_ATTR, ""],
74542
- [STUDIO_ORIGINAL_MIN_WIDTH_ATTR, "min-width"],
74543
- [STUDIO_ORIGINAL_MIN_HEIGHT_ATTR, "min-height"],
74544
- [STUDIO_ORIGINAL_MAX_WIDTH_ATTR, "max-width"],
74545
- [STUDIO_ORIGINAL_MAX_HEIGHT_ATTR, "max-height"],
74546
- [STUDIO_ORIGINAL_FLEX_BASIS_ATTR, "flex-basis"],
74547
- [STUDIO_ORIGINAL_FLEX_GROW_ATTR, "flex-grow"],
74548
- [STUDIO_ORIGINAL_FLEX_SHRINK_ATTR, "flex-shrink"],
74549
- [STUDIO_ORIGINAL_BOX_SIZING_ATTR, "box-sizing"],
74550
- [STUDIO_ORIGINAL_SCALE_ATTR, "scale"],
74551
- [STUDIO_ORIGINAL_TRANSFORM_ORIGIN_ATTR, "transform-origin"],
74552
- [STUDIO_ORIGINAL_DISPLAY_ATTR, "display"],
74553
- [STUDIO_ORIGINAL_TRANSFORM_DISPLAY_ATTR, ""]
74554
- ];
74555
- function buildBoxSizePatches(element) {
74556
- const ops = [];
74557
- collectInlineStyleOps(element, [STUDIO_WIDTH_PROP, STUDIO_HEIGHT_PROP], ops);
74558
- collectInlineStyleOps(element, BOX_SIZE_STYLE_PROPS, ops);
74559
- ops.push({ type: "attribute", property: STUDIO_BOX_SIZE_ATTR, value: "true" });
74560
- collectAttributeOps(
74561
- element,
74562
- BOX_SIZE_ORIG_ATTRS.map(([attr]) => attr),
74563
- ops
74564
- );
74565
- return ops;
74566
- }
74567
- function buildClearBoxSizePatches(element) {
74568
- const ops = [
74569
- { type: "inline-style", property: STUDIO_WIDTH_PROP, value: null },
74570
- { type: "inline-style", property: STUDIO_HEIGHT_PROP, value: null },
74571
- { type: "attribute", property: STUDIO_BOX_SIZE_ATTR, value: null }
74572
- ];
74573
- for (const [attrName, styleProp] of BOX_SIZE_ORIG_ATTRS) {
74574
- const origVal = element.getAttribute(attrName);
74575
- if (origVal !== null && styleProp) {
74576
- ops.push({ type: "inline-style", property: styleProp, value: origVal || null });
74753
+ // src/hooks/plainRotation.ts
74754
+ var plainRotateCounter = 0;
74755
+ function savePlainRotation({ commitPositionPatchToHtml, readOnlyPreview }, selection, next) {
74756
+ if (readOnlyPreview) return Promise.resolve();
74757
+ const { element } = selection;
74758
+ const before = captureStudioRotation(element);
74759
+ const patches = element.hasAttribute(STUDIO_ROTATION_ATTR) ? buildClearRotationPatches(element) : [];
74760
+ if (patches.length) clearStudioRotation(element);
74761
+ const drawn = applyCssRotation(element, next.angle, patches.length ? void 0 : next.plain);
74762
+ patches.push(...drawn);
74763
+ const turn = drawn.at(-1);
74764
+ return commitPositionPatchToHtml(selection, patches, {
74765
+ label: "Rotate layer",
74766
+ coalesceKey: `rotate:${++plainRotateCounter}`,
74767
+ coalesceMs: Number.POSITIVE_INFINITY
74768
+ }).catch((error) => {
74769
+ if (element.style.getPropertyValue(turn.property) === turn.value) {
74770
+ restorePlainRotation(element, before);
74577
74771
  }
74578
- ops.push({ type: "attribute", property: attrName, value: null });
74579
- }
74580
- return ops;
74581
- }
74582
- var ROTATION_STYLE_PROPS = [
74583
- STUDIO_ROTATION_PROP,
74584
- "rotate",
74585
- "transform-origin",
74586
- "display"
74587
- ];
74588
- var ROTATION_ORIG_ATTRS = [
74589
- STUDIO_ORIGINAL_ROTATE_ATTR,
74590
- STUDIO_ORIGINAL_INLINE_ROTATE_ATTR,
74591
- STUDIO_ORIGINAL_ROTATION_TRANSFORM_ORIGIN_ATTR,
74592
- STUDIO_ORIGINAL_TRANSFORM_DISPLAY_ATTR
74593
- ];
74594
- function buildRotationPatches(element) {
74595
- const ops = [];
74596
- collectInlineStyleOps(element, ROTATION_STYLE_PROPS, ops);
74597
- ops.push({ type: "attribute", property: STUDIO_ROTATION_ATTR, value: "true" });
74598
- collectAttributeOps(element, ROTATION_ORIG_ATTRS, ops);
74599
- return ops;
74600
- }
74601
- function buildClearRotationPatches(element) {
74602
- const origInlineRotate = element.getAttribute(STUDIO_ORIGINAL_INLINE_ROTATE_ATTR);
74603
- const origRotationTransformOrigin = element.getAttribute(
74604
- STUDIO_ORIGINAL_ROTATION_TRANSFORM_ORIGIN_ATTR
74605
- );
74606
- const ops = [
74607
- { type: "inline-style", property: STUDIO_ROTATION_PROP, value: null },
74608
- { type: "inline-style", property: "rotate", value: origInlineRotate || null },
74609
- {
74610
- type: "inline-style",
74611
- property: "transform-origin",
74612
- value: origRotationTransformOrigin !== null ? origRotationTransformOrigin || null : null
74613
- },
74614
- { type: "attribute", property: STUDIO_ROTATION_ATTR, value: null },
74615
- { type: "attribute", property: STUDIO_ROTATION_DRAFT_ATTR, value: null },
74616
- { type: "attribute", property: STUDIO_ORIGINAL_ROTATE_ATTR, value: null },
74617
- { type: "attribute", property: STUDIO_ORIGINAL_INLINE_ROTATE_ATTR, value: null },
74618
- { type: "attribute", property: STUDIO_ORIGINAL_ROTATION_TRANSFORM_ORIGIN_ATTR, value: null }
74619
- ];
74620
- appendTransformDisplayOps(element, ops);
74621
- return ops;
74772
+ throw error;
74773
+ });
74622
74774
  }
74623
74775
 
74624
74776
  // src/hooks/gsapTargetCache.ts
@@ -74684,10 +74836,11 @@ function useDomGeometryCommits({
74684
74836
  readOnlyPreview
74685
74837
  }) {
74686
74838
  const stageElementPositionOffset = useCallback125(
74687
- (selection, next, coalesceKey) => stageElementOffset(
74839
+ (selection, next, plainTranslate, coalesceKey) => stageElementOffset(
74688
74840
  { commitPositionPatchToHtml, showToast, readOnlyPreview },
74689
74841
  selection,
74690
74842
  next,
74843
+ plainTranslate,
74691
74844
  coalesceKey
74692
74845
  ),
74693
74846
  [commitPositionPatchToHtml, readOnlyPreview, showToast]
@@ -74710,49 +74863,36 @@ function useDomGeometryCommits({
74710
74863
  [commitPositionPatchToHtml, previewIframeRef, showToast, readOnlyPreview]
74711
74864
  );
74712
74865
  const handleDomBoxSizeCommit = useCallback125(
74713
- (selection, next, offset) => {
74866
+ (selection, next, offset, restore) => {
74714
74867
  if (readOnlyPreview) return Promise.resolve();
74715
- const gsapFallback = rejectGsapCssFallback(selection, previewIframeRef, showToast);
74716
- if (gsapFallback) return gsapFallback;
74717
- const beforeSize = captureStudioBoxSize(selection.element);
74718
- const beforeOffset = offset ? captureStudioPathOffset(selection.element) : null;
74719
- const stageCrop = prepareCropResize(selection.element);
74720
- applyStudioBoxSize(selection.element, next);
74868
+ const element = selection.element;
74869
+ const beforeSize = captureStudioBoxSize(element);
74870
+ const beforeTranslate = element.style.getPropertyValue("translate");
74871
+ const stageCrop = prepareCropResize(element);
74872
+ applyStudioBoxSize(element, next);
74721
74873
  const crop = stageCrop();
74722
- const patches = buildBoxSizePatches(selection.element);
74874
+ const patches = buildBoxSizePatches(element);
74723
74875
  if (crop) patches.push(crop.patch);
74724
74876
  if (offset) {
74725
- applyStudioPathOffset(selection.element, offset);
74726
- patches.push(...buildPathOffsetPatches(selection.element));
74877
+ writeTranslatePx(element, offset);
74878
+ patches.push(translatePatch(offset));
74727
74879
  }
74728
74880
  return commitPositionPatchToHtml(selection, patches, {
74729
74881
  label: "Resize layer box",
74730
74882
  coalesceKey: `box-size:${getDomEditTargetKey(selection)}`
74731
74883
  }).catch((error) => {
74732
- restoreStudioBoxSize(selection.element, beforeSize);
74733
- if (beforeOffset) restoreStudioPathOffset(selection.element, beforeOffset);
74884
+ restoreStudioBoxSize(element, beforeSize);
74885
+ if (offset) element.style.setProperty("translate", beforeTranslate);
74734
74886
  crop?.revert();
74887
+ restore?.();
74735
74888
  throw error;
74736
74889
  });
74737
74890
  },
74738
- [commitPositionPatchToHtml, previewIframeRef, showToast, readOnlyPreview]
74891
+ [commitPositionPatchToHtml, readOnlyPreview]
74739
74892
  );
74740
74893
  const handleDomRotationCommit = useCallback125(
74741
- (selection, next) => {
74742
- if (readOnlyPreview) return Promise.resolve();
74743
- const gsapFallback = rejectGsapCssFallback(selection, previewIframeRef, showToast);
74744
- if (gsapFallback) return gsapFallback;
74745
- const before = captureStudioRotation(selection.element);
74746
- applyStudioRotation(selection.element, next);
74747
- return commitPositionPatchToHtml(selection, buildRotationPatches(selection.element), {
74748
- label: "Rotate layer",
74749
- coalesceKey: `rotation:${getDomEditTargetKey(selection)}`
74750
- }).catch((error) => {
74751
- restoreStudioRotation(selection.element, before);
74752
- throw error;
74753
- });
74754
- },
74755
- [commitPositionPatchToHtml, previewIframeRef, showToast, readOnlyPreview]
74894
+ (selection, next) => savePlainRotation({ commitPositionPatchToHtml, readOnlyPreview }, selection, next),
74895
+ [commitPositionPatchToHtml, readOnlyPreview]
74756
74896
  );
74757
74897
  const handleDomManualEditsReset = useCallback125(
74758
74898
  (selection) => {
@@ -77201,6 +77341,23 @@ function useDomEditWiring(params) {
77201
77341
  // src/hooks/useGsapAwareEditing.ts
77202
77342
  import { useCallback as useCallback139 } from "react";
77203
77343
 
77344
+ // src/utils/elementGsap.ts
77345
+ function gsapOf2(element) {
77346
+ return element.ownerDocument.defaultView?.gsap;
77347
+ }
77348
+ function setElementGsapPosition(element, x, y) {
77349
+ const gsap3 = gsapOf2(element);
77350
+ if (!gsap3?.set) return false;
77351
+ gsap3.set(element, { x, y });
77352
+ return true;
77353
+ }
77354
+ function setElementGsapScale(element, x, y) {
77355
+ const gsap3 = gsapOf2(element);
77356
+ if (!gsap3?.set) return false;
77357
+ gsap3.set(element, { scaleX: x, scaleY: y });
77358
+ return true;
77359
+ }
77360
+
77204
77361
  // src/hooks/gsapResizePreflight.ts
77205
77362
  function resizeRoute(animations, fetchedAnimations) {
77206
77363
  const allKnownAnimations = [...animations, ...fetchedAnimations];
@@ -77905,6 +78062,7 @@ function useGsapAwareEditing({
77905
78062
  trackGsapInteractionFailure,
77906
78063
  stageElementPositionOffset,
77907
78064
  handleDomBoxSizeCommit,
78065
+ handleDomRotationCommit,
77908
78066
  commitPositionPatchToHtml,
77909
78067
  addGsapAnimation,
77910
78068
  convertToKeyframes,
@@ -77920,8 +78078,8 @@ function useGsapAwareEditing({
77920
78078
  );
77921
78079
  const handleGsapAwarePathOffsetCommit = useCallback139(
77922
78080
  async (selection, next, modifiers) => {
77923
- if (!gsapWritesPosition(selection.element))
77924
- return stageElementPositionOffset(selection, next).save();
78081
+ if (modifiers?.plainTranslate ?? !gsapWritesPosition(selection.element))
78082
+ return stageElementPositionOffset(selection, next, true).save();
77925
78083
  if (gsapCommitMutation) {
77926
78084
  try {
77927
78085
  const ownedAnimations = getGsapAnimationsForSelection(selection);
@@ -77935,7 +78093,7 @@ function useGsapAwareEditing({
77935
78093
  makeFetchFallback(selection),
77936
78094
  modifiers
77937
78095
  );
77938
- await saveMove(outcome, () => stageElementPositionOffset(selection, next).save());
78096
+ await saveMove(outcome, () => stageElementPositionOffset(selection, next, false).save());
77939
78097
  } catch (error) {
77940
78098
  trackGsapInteractionFailure(error, selection, "drag", "Move animated layer");
77941
78099
  throw error;
@@ -77984,10 +78142,13 @@ function useGsapAwareEditing({
77984
78142
  return Promise.resolve();
77985
78143
  };
77986
78144
  const preflightAnimations = /* @__PURE__ */ new Map();
77987
- const offsetMembers = /* @__PURE__ */ new Set();
78145
+ const offsetMembers = /* @__PURE__ */ new Map();
77988
78146
  const preflightResults = await Promise.allSettled(
77989
- updates.map(async ({ selection }) => {
77990
- if (!gsapWritesPosition(selection.element)) return void offsetMembers.add(selection);
78147
+ updates.map(async ({ selection, plainTranslate }) => {
78148
+ if (plainTranslate ?? !gsapWritesPosition(selection.element)) {
78149
+ refuseGsapTakeover(selection.element, showToast);
78150
+ return void offsetMembers.set(selection, true);
78151
+ }
77991
78152
  const animations = await makeFetchFallback(selection, { failOnFetchError: true })();
77992
78153
  preflightAnimations.set(selection, animations);
77993
78154
  const outcome = await tryGsapDragIntercept(
@@ -77999,7 +78160,7 @@ function useGsapAwareEditing({
77999
78160
  void 0,
78000
78161
  { preflightOnly: true, group: true }
78001
78162
  );
78002
- if (outcome.status === "element-offset") offsetMembers.add(selection);
78163
+ if (outcome.status === "element-offset") offsetMembers.set(selection, false);
78003
78164
  assertGsapEditPersisted(outcome);
78004
78165
  })
78005
78166
  );
@@ -78018,8 +78179,9 @@ function useGsapAwareEditing({
78018
78179
  );
78019
78180
  for (const [index, { selection, next }] of updates.entries()) {
78020
78181
  renderOnCommit = index === lastScriptWrite;
78021
- if (offsetMembers.has(selection)) {
78022
- await stageElementPositionOffset(selection, next, coalesceKey).save();
78182
+ const plain = offsetMembers.get(selection);
78183
+ if (plain !== void 0) {
78184
+ await stageElementPositionOffset(selection, next, plain, coalesceKey).save();
78023
78185
  continue;
78024
78186
  }
78025
78187
  try {
@@ -78056,11 +78218,14 @@ function useGsapAwareEditing({
78056
78218
  previewIframeRef,
78057
78219
  makeFetchFallback,
78058
78220
  trackGsapInteractionFailure,
78059
- stageElementPositionOffset
78221
+ stageElementPositionOffset,
78222
+ showToast
78060
78223
  ]
78061
78224
  );
78062
78225
  const handleGsapAwareBoxSizeCommit = useCallback139(
78063
78226
  async (selection, next, offset, restore = () => void 0) => {
78227
+ if (!gsapWritesBox(selection.element))
78228
+ return handleDomBoxSizeCommit(selection, next, offset, restore);
78064
78229
  let targetAnimations;
78065
78230
  try {
78066
78231
  const ownedAnimations = getGsapAnimationsForSelection(selection);
@@ -78131,7 +78296,8 @@ function useGsapAwareEditing({
78131
78296
  makeFetchFallback(selection)
78132
78297
  );
78133
78298
  await saveMove(dragOutcome, async () => {
78134
- anchorMove = stageElementPositionOffset(selection, offset, coalesceKey);
78299
+ const plain = !gsapWritesPosition(selection.element);
78300
+ anchorMove = stageElementPositionOffset(selection, offset, plain, coalesceKey);
78135
78301
  });
78136
78302
  }
78137
78303
  logResizeSettle(selection.element, ownsDragOffset ? "gsap-scale" : "gsap-size");
@@ -78141,13 +78307,7 @@ function useGsapAwareEditing({
78141
78307
  throw error;
78142
78308
  }
78143
78309
  }
78144
- logResize("dom-route", {
78145
- next,
78146
- offset: offset ?? null,
78147
- hadGsapMutation: !!gsapCommitMutation
78148
- });
78149
- logResizeSettle(selection.element, "dom-route");
78150
- await handleDomBoxSizeCommit(selection, next, offset);
78310
+ throw new Error("Resize of a GSAP-owned box has no GSAP writer");
78151
78311
  },
78152
78312
  afterBufferedCommitsSaved: async () => {
78153
78313
  await anchorMove?.save();
@@ -78175,10 +78335,11 @@ function useGsapAwareEditing({
78175
78335
  );
78176
78336
  const handleGsapAwareRotationCommit = useCallback139(
78177
78337
  async (selection, next) => {
78338
+ if (next.plain || !gsapWritesRotation(selection.element))
78339
+ return handleDomRotationCommit(selection, next);
78178
78340
  if (gsapCommitMutation) {
78179
78341
  try {
78180
- const ownedAnimations = getGsapAnimationsForSelection(selection);
78181
- const targetAnimations = Array.isArray(ownedAnimations) ? ownedAnimations : await ownedAnimations;
78342
+ const targetAnimations = await getGsapAnimationsForSelection(selection);
78182
78343
  const outcome = await tryGsapRotationIntercept(
78183
78344
  selection,
78184
78345
  next.angle,
@@ -78199,7 +78360,8 @@ function useGsapAwareEditing({
78199
78360
  previewIframeRef,
78200
78361
  makeFetchFallback,
78201
78362
  trackGsapInteractionFailure,
78202
- getGsapAnimationsForSelection
78363
+ getGsapAnimationsForSelection,
78364
+ handleDomRotationCommit
78203
78365
  ]
78204
78366
  );
78205
78367
  const { commitAnimatedProperties: commitAnimatedPropertiesRaw } = useAnimatedPropertyCommit({
@@ -78741,6 +78903,7 @@ function useDomEditSession({
78741
78903
  stageElementPositionOffset,
78742
78904
  commitPositionPatchToHtml,
78743
78905
  handleDomBoxSizeCommit,
78906
+ handleDomRotationCommit: handleDomCssRotationCommit,
78744
78907
  handleDomManualEditsReset,
78745
78908
  handleDomEditElementsDelete,
78746
78909
  handleDomZIndexReorderCommit
@@ -78970,6 +79133,7 @@ function useDomEditSession({
78970
79133
  trackGsapInteractionFailure,
78971
79134
  stageElementPositionOffset,
78972
79135
  handleDomBoxSizeCommit,
79136
+ handleDomRotationCommit: handleDomCssRotationCommit,
78973
79137
  commitPositionPatchToHtml,
78974
79138
  addGsapAnimation,
78975
79139
  convertToKeyframes,
@@ -92871,7 +93035,6 @@ import { useCallback as useCallback189, useMemo as useMemo84, useRef as useRef18
92871
93035
  var noop2 = () => {
92872
93036
  };
92873
93037
  var NO_SELECTED_ANIMATIONS = [];
92874
- var noDomBoxSizeRoute = () => Promise.reject(new Error("Resize has no DOM route here"));
92875
93038
  function useDomGeometryCommit({
92876
93039
  projectId,
92877
93040
  iframeRef,
@@ -92924,15 +93087,12 @@ function useDomGeometryCommit({
92924
93087
  },
92925
93088
  [commitPositionPatchToHtml, queue2]
92926
93089
  );
92927
- const stageElementPositionOffset = useCallback189(
92928
- (selection, next, coalesceKey) => stageElementOffset(
92929
- { commitPositionPatchToHtml: commitWithFreshQueue, showToast },
92930
- selection,
92931
- next,
92932
- coalesceKey
92933
- ),
92934
- [commitWithFreshQueue, showToast]
92935
- );
93090
+ const { stageElementPositionOffset, handleDomBoxSizeCommit, handleDomRotationCommit } = useDomGeometryCommits({
93091
+ previewIframeRef: iframeRef,
93092
+ showToast,
93093
+ commitPositionPatchToHtml: commitWithFreshQueue,
93094
+ readOnlyPreview: false
93095
+ });
92936
93096
  const makeFetchFallback = useGsapAnimationFetchFallback(projectId);
92937
93097
  const trackGsapInteractionFailure = useGsapInteractionFailureTelemetry(activeCompPath, showToast);
92938
93098
  const {
@@ -92951,7 +93111,8 @@ function useDomGeometryCommit({
92951
93111
  makeFetchFallback,
92952
93112
  trackGsapInteractionFailure,
92953
93113
  stageElementPositionOffset,
92954
- handleDomBoxSizeCommit: noDomBoxSizeRoute,
93114
+ handleDomBoxSizeCommit,
93115
+ handleDomRotationCommit,
92955
93116
  commitPositionPatchToHtml: commitWithFreshQueue,
92956
93117
  addGsapAnimation: gsap3.addGsapAnimation,
92957
93118
  convertToKeyframes: gsap3.convertToKeyframes,