@hyperframes/studio 0.8.94 → 0.8.96

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 (33) hide show
  1. package/dist/assets/{hyperframes-player-Bkm-3G56.js → hyperframes-player-ClfxdXeC.js} +1 -1
  2. package/dist/assets/{index-n2ZRiO44.js → index-BNQLP5_v.js} +1 -1
  3. package/dist/assets/{index-BupvpoiM.js → index-CSHEHeOo.js} +176 -176
  4. package/dist/assets/{index-BUn5H_np.js → index-D8ztHPd3.js} +1 -1
  5. package/dist/index.d.ts +2 -0
  6. package/dist/index.html +1 -1
  7. package/dist/index.js +109 -43
  8. package/dist/index.js.map +1 -1
  9. package/package.json +8 -8
  10. package/src/components/editor/DomEditCropHandles.test.tsx +63 -0
  11. package/src/components/editor/DomEditCropHandles.tsx +7 -10
  12. package/src/components/editor/DomEditSelectionChrome.tsx +3 -3
  13. package/src/components/editor/domEditOverlayGestures.ts +16 -0
  14. package/src/components/editor/groupDragMove.ts +13 -6
  15. package/src/components/editor/manualEditsSeekReapply.ts +5 -1
  16. package/src/components/editor/manualOffsetDrag.test.ts +56 -0
  17. package/src/components/editor/manualOffsetDrag.ts +16 -9
  18. package/src/components/editor/shiftDragAxisLock.test.tsx +358 -0
  19. package/src/components/editor/snapEngine.ts +3 -2
  20. package/src/components/editor/useDomEditOverlayGestures.ts +24 -9
  21. package/src/player/components/Timeline.ghostLane.test.tsx +126 -0
  22. package/src/player/components/Timeline.rulerSeek.test.tsx +7 -0
  23. package/src/player/components/Timeline.test.ts +3 -3
  24. package/src/player/components/TimelineCanvas.tsx +30 -2
  25. package/src/player/components/TimelineProvider.tsx +1 -0
  26. package/src/player/components/timelineDragDrop.test.tsx +14 -12
  27. package/src/player/components/timelineLayout.test.ts +40 -11
  28. package/src/player/components/timelineLayout.ts +6 -6
  29. package/src/player/components/useTimelineProviderState.tsx +3 -1
  30. package/src/utils/gsapSoftReload.test.ts +51 -2
  31. package/src/utils/gsapSoftReload.ts +24 -10
  32. package/src/utils/gsapUndoRestore.test.ts +49 -0
  33. package/src/utils/gsapUndoRestore.ts +15 -0
@@ -1 +1 @@
1
- import{g as P}from"./index-BupvpoiM.js";function j(c,d){for(var s=0;s<d.length;s++){const a=d[s];if(typeof a!="string"&&!Array.isArray(a)){for(const i in a)if(i!=="default"&&!(i in c)){const l=Object.getOwnPropertyDescriptor(a,i);l&&Object.defineProperty(c,i,l.get?l:{enumerable:!0,get:()=>a[i]})}}}return Object.freeze(Object.defineProperty(c,Symbol.toStringTag,{value:"Module"}))}var v={},w;function k(){if(w)return v;w=1,Object.defineProperty(v,"__esModule",{value:!0}),v.default=d;var c=window.OfflineAudioContext||window.webkitOfflineAudioContext;function d(e){var r=a(e);return r.start(0),[i,y,O(e.sampleRate),s].reduce(function(t,o){return o(t)},r.buffer.getChannelData(0))}function s(e){return e.sort(function(r,t){return t.count-r.count}).splice(0,5)[0].tempo}function a(e){var r=e.length,t=e.numberOfChannels,o=e.sampleRate,n=new c(t,r,o),u=n.createBufferSource();u.buffer=e;var f=n.createBiquadFilter();return f.type="lowpass",u.connect(f),f.connect(n.destination),u}function i(e){for(var r=[],t=.9,o=.3,n=15;r.length<n&&t>=o;)r=l(e,t),t-=.05;if(r.length<n)throw new Error("Could not find enough samples for a reliable detection.");return r}function l(e,r){for(var t=[],o=0,n=e.length;o<n;o+=1)e[o]>r&&(t.push(o),o+=1e4);return t}function y(e){var r=[];return e.forEach(function(t,o){for(var n=function(x){var g=e[o+x]-t,_=r.some(function(h){if(h.interval===g)return h.count+=1});_||r.push({interval:g,count:1})},u=0;u<10;u+=1)n(u)}),r}function O(e){return function(r){var t=[];return r.forEach(function(o){if(o.interval!==0){for(var n=60/(o.interval/e);n<90;)n*=2;for(;n>180;)n/=2;n=Math.round(n);var u=t.some(function(f){if(f.tempo===n)return f.count+=o.count});u||t.push({tempo:n,count:o.count})}}),t}}return v}var p,b;function q(){return b||(b=1,p=k().default),p}var m=q();const A=P(m),D=j({__proto__:null,default:A},[m]);export{D as i};
1
+ import{g as P}from"./index-CSHEHeOo.js";function j(c,d){for(var s=0;s<d.length;s++){const a=d[s];if(typeof a!="string"&&!Array.isArray(a)){for(const i in a)if(i!=="default"&&!(i in c)){const l=Object.getOwnPropertyDescriptor(a,i);l&&Object.defineProperty(c,i,l.get?l:{enumerable:!0,get:()=>a[i]})}}}return Object.freeze(Object.defineProperty(c,Symbol.toStringTag,{value:"Module"}))}var v={},w;function k(){if(w)return v;w=1,Object.defineProperty(v,"__esModule",{value:!0}),v.default=d;var c=window.OfflineAudioContext||window.webkitOfflineAudioContext;function d(e){var r=a(e);return r.start(0),[i,y,O(e.sampleRate),s].reduce(function(t,o){return o(t)},r.buffer.getChannelData(0))}function s(e){return e.sort(function(r,t){return t.count-r.count}).splice(0,5)[0].tempo}function a(e){var r=e.length,t=e.numberOfChannels,o=e.sampleRate,n=new c(t,r,o),u=n.createBufferSource();u.buffer=e;var f=n.createBiquadFilter();return f.type="lowpass",u.connect(f),f.connect(n.destination),u}function i(e){for(var r=[],t=.9,o=.3,n=15;r.length<n&&t>=o;)r=l(e,t),t-=.05;if(r.length<n)throw new Error("Could not find enough samples for a reliable detection.");return r}function l(e,r){for(var t=[],o=0,n=e.length;o<n;o+=1)e[o]>r&&(t.push(o),o+=1e4);return t}function y(e){var r=[];return e.forEach(function(t,o){for(var n=function(x){var g=e[o+x]-t,_=r.some(function(h){if(h.interval===g)return h.count+=1});_||r.push({interval:g,count:1})},u=0;u<10;u+=1)n(u)}),r}function O(e){return function(r){var t=[];return r.forEach(function(o){if(o.interval!==0){for(var n=60/(o.interval/e);n<90;)n*=2;for(;n>180;)n/=2;n=Math.round(n);var u=t.some(function(f){if(f.tempo===n)return f.count+=o.count});u||t.push({tempo:n,count:o.count})}}),t}}return v}var p,b;function q(){return b||(b=1,p=k().default),p}var m=q();const A=P(m),D=j({__proto__:null,default:A},[m]);export{D as i};
package/dist/index.d.ts CHANGED
@@ -1531,6 +1531,7 @@ type TimelineCanvasState = Omit<TimelineLaneBaseProps, "setRangeSelection" | "se
1531
1531
  playheadRef: React.RefObject<HTMLDivElement | null>;
1532
1532
  laneGapStrips: TimelineLaneGapStrips[];
1533
1533
  dropPreview: TimelineDropPlacement | null;
1534
+ acceptsMediaDrop: boolean;
1534
1535
  setRangeSelection: (value: TimelineRangeSelection | null) => void;
1535
1536
  setResizingClip: (value: ResizingClipState | null) => void;
1536
1537
  setDraggedClip: (value: TimelineLaneBaseProps["draggedClip"]) => void;
@@ -3237,6 +3238,7 @@ declare function resolveSnapAdjustment(input: {
3237
3238
  disabled: boolean;
3238
3239
  /** Set when the gesture has not travelled far enough for snapping yet. */
3239
3240
  disabledForTravel?: boolean;
3241
+ lockedAxis?: "x" | "y";
3240
3242
  }): SnapResult;
3241
3243
  /**
3242
3244
  * Full-length guide line spanning the composition: a vertical line (axis "x")
package/dist/index.html CHANGED
@@ -5,7 +5,7 @@
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover" />
6
6
  <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
7
7
  <title>HyperFrames Studio</title>
8
- <script type="module" crossorigin src="/assets/index-BupvpoiM.js"></script>
8
+ <script type="module" crossorigin src="/assets/index-CSHEHeOo.js"></script>
9
9
  <link rel="stylesheet" crossorigin href="/assets/index-Dx79V1Se.css">
10
10
  </head>
11
11
  <body>
package/dist/index.js CHANGED
@@ -1944,8 +1944,8 @@ function getTimelineBeatEntries(beatTimes, beatStrengths, range, pinnedIndexes =
1944
1944
  function getTimelineLaneTop(laneIndex) {
1945
1945
  return TRACK_H + Math.max(0, Math.trunc(laneIndex)) * LANE_H;
1946
1946
  }
1947
- var TRACKS_TOP_PAD = 50;
1948
- var TRACKS_BOTTOM_PAD = Math.round(TRACK_H * 1.5);
1947
+ var TRACKS_TOP_PAD = 8;
1948
+ var TRACKS_BOTTOM_PAD = TRACK_H;
1949
1949
  var TRACKS_LEFT_PAD = 48;
1950
1950
  function trackHeights(tracks, expandedClipIds) {
1951
1951
  return tracks.map((clips) => {
@@ -12807,7 +12807,7 @@ function useTimelineProviderState({
12807
12807
  showAudioEffects = true,
12808
12808
  showKeyframes = true,
12809
12809
  trackPadding,
12810
- sessionEpoch = 0,
12810
+ sessionEpoch: sessionEpochProp,
12811
12811
  previewIframeRef,
12812
12812
  onZIndexReorder
12813
12813
  } = {}) {
@@ -12849,6 +12849,7 @@ function useTimelineProviderState({
12849
12849
  const currentTime = usePlayerStore((s) => s.currentTime);
12850
12850
  const beatDragging = usePlayerStore((s) => s.beatDragging);
12851
12851
  const timelineSessionEpoch = usePlayerStore((s) => s.timelineSessionEpoch);
12852
+ const sessionEpoch = sessionEpochProp ?? timelineSessionEpoch;
12852
12853
  const setFocusedEaseSegment = usePlayerStore((s) => s.setFocusedEaseSegment);
12853
12854
  const { zoomMode, manualZoomPercent, setZoomMode, setManualZoomPercent } = useTimelineZoom();
12854
12855
  const playheadRef = useRef25(null);
@@ -13164,6 +13165,7 @@ function useTimelineProviderState({
13164
13165
  marqueeRect,
13165
13166
  laneGapStrips,
13166
13167
  dropPreview: assetDrop.dropPreview,
13168
+ acceptsMediaDrop: !!(onFileDrop || onAssetDrop),
13167
13169
  theme,
13168
13170
  showAudioEffects,
13169
13171
  displayTrackOrder: displayLayout.displayTrackOrder,
@@ -19388,6 +19390,13 @@ function verifyTimelinesPopulated(win, targetKeys) {
19388
19390
  }
19389
19391
  return Object.keys(timelines).filter((k) => k !== "__proxied").length > 0;
19390
19392
  }
19393
+ function restoreAuthoredTransforms(style, authored) {
19394
+ style.transform = authored?.transform ?? "";
19395
+ if (!authored) return;
19396
+ for (const prop of ["translate", "rotate", "scale"]) {
19397
+ style.setProperty(prop, authored.getPropertyValue(prop));
19398
+ }
19399
+ }
19391
19400
  function finalizeSoftReload(win, currentTime) {
19392
19401
  win.__player?.seek?.(currentTime);
19393
19402
  win.__hfForceTimelineRebind?.();
@@ -19428,7 +19437,7 @@ function applySoftReload(iframe, scriptText, options = {}) {
19428
19437
  const currentTime = currentTimeOverride ?? win.__player?.getTime?.() ?? 0;
19429
19438
  let deferredToAsync = false;
19430
19439
  let authoredDoc;
19431
- const findAuthoredSource = (el) => {
19440
+ const findAuthoredStyle = (el) => {
19432
19441
  if (authoredDoc === void 0) {
19433
19442
  try {
19434
19443
  authoredDoc = authoredHtml ? parseSavedSource(authoredHtml) : null;
@@ -19436,13 +19445,10 @@ function applySoftReload(iframe, scriptText, options = {}) {
19436
19445
  authoredDoc = null;
19437
19446
  }
19438
19447
  }
19439
- return authoredDoc ? findAuthoredElement(authoredDoc, el) : null;
19440
- };
19441
- const readAuthoredOpacity = (el) => {
19442
- const source = findAuthoredSource(el);
19443
- if (source instanceof HTMLElement) return source.style.opacity;
19444
- return readStampedAuthoredOpacity(el);
19448
+ const source = authoredDoc ? findAuthoredElement(authoredDoc, el) : null;
19449
+ return source instanceof HTMLElement || source instanceof SVGElement ? source.style : null;
19445
19450
  };
19451
+ const readAuthoredOpacity = (el) => findAuthoredStyle(el)?.opacity ?? readStampedAuthoredOpacity(el);
19446
19452
  const doReload = () => {
19447
19453
  const timelines = win.__timelines;
19448
19454
  const allTargets = [];
@@ -19461,6 +19467,7 @@ function applySoftReload(iframe, scriptText, options = {}) {
19461
19467
  }
19462
19468
  }
19463
19469
  try {
19470
+ tl.clear?.();
19464
19471
  tl.kill?.();
19465
19472
  } catch {
19466
19473
  }
@@ -19487,7 +19494,7 @@ function applySoftReload(iframe, scriptText, options = {}) {
19487
19494
  for (const [el, css2] of saved2) {
19488
19495
  const s = el.style;
19489
19496
  s.cssText = css2;
19490
- s.removeProperty("transform");
19497
+ restoreAuthoredTransforms(s, findAuthoredStyle(el));
19491
19498
  const authored = readAuthoredOpacity(el);
19492
19499
  if (authored !== null) {
19493
19500
  applyAuthoredInlineOpacity(s, authored);
@@ -21239,6 +21246,9 @@ var TimelineCanvas = memo13(function TimelineCanvas2() {
21239
21246
  const dropTrackIndex = props.dropPreview ? displayTrackOrder2.indexOf(props.dropPreview.track) : -1;
21240
21247
  const dropRowIndex = dropTrackIndex < 0 ? displayTrackOrder2.length : dropTrackIndex;
21241
21248
  const insertLineRow = (draggedClip?.started ? draggedClip.insertRow : null) ?? props.dropPreview?.insertRow ?? null;
21249
+ const ghostLaneActive = (insertLineRow ?? -1) >= props.trackOrder.length || props.dropPreview?.insertRow == null && props.dropPreview != null && dropTrackIndex < 0;
21250
+ const showGhostLane = props.rowGeometry.padding.bottom >= TRACK_H;
21251
+ const ghostLaneRow = props.rowGeometry.getRowIndex(props.trackOrder.at(-1) ?? Number.NaN) + 1;
21242
21252
  const draggedRowHeight = getTimelineRowHeight(draggedRowIndex, props.rowHeights);
21243
21253
  const draggedClipHeight = Math.min(draggedRowHeight, TRACK_H) - CLIP_Y * 2;
21244
21254
  const beatDragging = props.beatDragging;
@@ -21281,6 +21291,22 @@ var TimelineCanvas = memo13(function TimelineCanvas2() {
21281
21291
  style: { height: props.rowsVirtualized ? 0 : props.rowGeometry.padding.bottom }
21282
21292
  }
21283
21293
  ),
21294
+ showGhostLane && /* @__PURE__ */ jsx55(
21295
+ "div",
21296
+ {
21297
+ "aria-hidden": "true",
21298
+ "data-testid": "timeline-ghost-lane",
21299
+ "data-active": ghostLaneActive || void 0,
21300
+ className: `pointer-events-none absolute flex items-center justify-center rounded-lg border border-dashed text-[11px] transition-colors duration-150 ${ghostLaneActive ? "border-studio-accent/60 bg-studio-accent/6 text-studio-accent" : "border-neutral-700/50 text-neutral-500"}`,
21301
+ style: {
21302
+ top: props.rowGeometry.getRowTop(ghostLaneRow) + CLIP_Y,
21303
+ left: props.contentOrigin,
21304
+ width: props.trackContentWidth,
21305
+ height: TRACK_H - CLIP_Y * 2
21306
+ },
21307
+ children: props.acceptsMediaDrop && "Drop media here"
21308
+ }
21309
+ ),
21284
21310
  props.laneGapStrips.map((strip) => {
21285
21311
  const rowIndex = displayTrackOrder2.indexOf(strip.track);
21286
21312
  if (rowIndex < 0) return null;
@@ -30725,6 +30751,10 @@ var logSelect = makeStudioDebugLogger("select");
30725
30751
 
30726
30752
  // src/components/editor/domEditOverlayGestures.ts
30727
30753
  var BLOCKED_MOVE_THRESHOLD_PX = 4;
30754
+ function lockDragToDominantAxis(dx, dy, shiftKey) {
30755
+ if (!shiftKey) return { dx, dy };
30756
+ return Math.abs(dx) >= Math.abs(dy) ? { dx, dy: 0, lockedAxis: "y" } : { dx: 0, dy, lockedAxis: "x" };
30757
+ }
30728
30758
  var ROTATION_COMMIT_EPSILON_DEGREES = 0.05;
30729
30759
  var ROTATION_SNAP_DEGREES = 15;
30730
30760
  var ROTATED_SNAP_BYPASS_DEGREES = 0.5;
@@ -31613,7 +31643,11 @@ function reapplyRotations(doc) {
31613
31643
  }
31614
31644
  }
31615
31645
  }
31616
- var STUDIO_EDIT_ATTRS = [STUDIO_PATH_OFFSET_ATTR, STUDIO_BOX_SIZE_ATTR, STUDIO_ROTATION_ATTR].flatMap((attr) => [attr, `data-${attr}`]).concat(STUDIO_MOTION_ATTR);
31646
+ var STUDIO_EDIT_ATTRS = [
31647
+ STUDIO_PATH_OFFSET_ATTR,
31648
+ STUDIO_BOX_SIZE_ATTR,
31649
+ STUDIO_ROTATION_ATTR
31650
+ ].flatMap((attr) => [attr, `data-${attr}`]).concat(STUDIO_MOTION_ATTR);
31617
31651
  var STUDIO_EDIT_SELECTORS = STUDIO_EDIT_ATTRS.map(
31618
31652
  (attr) => attr === STUDIO_MOTION_ATTR ? `[${attr}]` : `[${attr}="true"]`
31619
31653
  );
@@ -32402,6 +32436,7 @@ function applyManualOffsetNudgeCommit(member, delta) {
32402
32436
  }
32403
32437
  function restoreManualOffsetDragMember(member) {
32404
32438
  restoreStudioPathOffset(member.element, member.initialPathOffset);
32439
+ getOffsetDragGsap(member.element)?.set(member.element, { ...member.baseGsap });
32405
32440
  endStudioManualEditGesture(member.element, member.gestureToken);
32406
32441
  }
32407
32442
  function restoreManualOffsetDragMembers(members) {
@@ -32410,6 +32445,15 @@ function restoreManualOffsetDragMembers(members) {
32410
32445
  resumeGsapTimelines(member.element);
32411
32446
  }
32412
32447
  }
32448
+ function settleDraftTranslate(element, gsapOwnsPosition) {
32449
+ if (element.style.getPropertyValue("translate") === "none") {
32450
+ element.style.removeProperty("translate");
32451
+ }
32452
+ if (!gsapOwnsPosition) return;
32453
+ const view = element.ownerDocument.defaultView;
32454
+ const underneath = view?.getComputedStyle(element).getPropertyValue("translate");
32455
+ if (underneath && underneath !== "none") element.style.setProperty("translate", "none");
32456
+ }
32413
32457
  function endManualOffsetDragMembers(members) {
32414
32458
  for (const member of members) {
32415
32459
  endStudioManualEditGesture(member.element, member.gestureToken);
@@ -32417,12 +32461,9 @@ function endManualOffsetDragMembers(members) {
32417
32461
  member.element.removeAttribute("data-hf-drag-initial-offset-y");
32418
32462
  member.element.removeAttribute("data-hf-drag-gsap-base-x");
32419
32463
  member.element.removeAttribute("data-hf-drag-gsap-base-y");
32420
- if (member.element.style.getPropertyValue("translate") === "none") {
32421
- member.element.style.removeProperty("translate");
32422
- }
32423
- if (getOffsetDragGsap(member.element)) {
32424
- clearStudioPathOffset(member.element);
32425
- }
32464
+ const gsapOwnsPosition = Boolean(getOffsetDragGsap(member.element));
32465
+ if (gsapOwnsPosition) clearStudioPathOffset(member.element);
32466
+ settleDraftTranslate(member.element, gsapOwnsPosition);
32426
32467
  resumeGsapTimelines(member.element);
32427
32468
  }
32428
32469
  }
@@ -32768,8 +32809,8 @@ function resolveSnapAdjustment(input) {
32768
32809
  input.gridEdges?.y,
32769
32810
  input.threshold
32770
32811
  );
32771
- const bestX = pickBest(xCandidates);
32772
- const bestY = pickBest(yCandidates);
32812
+ const bestX = input.lockedAxis === "x" ? null : pickBest(xCandidates);
32813
+ const bestY = input.lockedAxis === "y" ? null : pickBest(yCandidates);
32773
32814
  const adjustedDx = input.proposedDx + (bestX?.adjustment ?? 0);
32774
32815
  const adjustedDy = input.proposedDy + (bestY?.adjustment ?? 0);
32775
32816
  const adjustedRect = {
@@ -33285,7 +33326,8 @@ function createGroupDragMover(opts, setDraftGroupOverlayItems) {
33285
33326
  targets: allTargets,
33286
33327
  gridEdges: sc.gridEdges ?? void 0,
33287
33328
  threshold: SNAP_THRESHOLD_PX,
33288
- disabled: e.altKey
33329
+ disabled: e.altKey,
33330
+ lockedAxis: proposed.lockedAxis
33289
33331
  });
33290
33332
  const movingRect = {
33291
33333
  ...groupBounds,
@@ -33305,10 +33347,11 @@ function createGroupDragMover(opts, setDraftGroupOverlayItems) {
33305
33347
  lastGesture = groupG;
33306
33348
  lastGroupPositions = {};
33307
33349
  }
33308
- const { dx, dy } = snapGroupDelta(groupG, e, {
33309
- dx: e.clientX - groupG.startX,
33310
- dy: e.clientY - groupG.startY
33311
- });
33350
+ const { dx, dy } = snapGroupDelta(
33351
+ groupG,
33352
+ e,
33353
+ lockDragToDominantAxis(e.clientX - groupG.startX, e.clientY - groupG.startY, e.shiftKey)
33354
+ );
33312
33355
  groupG.lastSnappedDx = dx;
33313
33356
  groupG.lastSnappedDy = dy;
33314
33357
  setDraftGroupOverlayItems(
@@ -33409,6 +33452,9 @@ function createDomEditSaveQueue(options = {}) {
33409
33452
  }
33410
33453
 
33411
33454
  // src/components/editor/useDomEditOverlayGestures.ts
33455
+ function isTap(g, e) {
33456
+ return !g.travelled && Math.hypot(e.clientX - g.startX, e.clientY - g.startY) < BLOCKED_MOVE_THRESHOLD_PX;
33457
+ }
33412
33458
  function logGestureCommitFailure(message, error) {
33413
33459
  if (error instanceof DomEditSaveQueueOpenError) return;
33414
33460
  console.error(message, error);
@@ -33466,10 +33512,12 @@ function createDomEditOverlayGestureHandlers(opts) {
33466
33512
  return;
33467
33513
  }
33468
33514
  if (groupG) {
33515
+ if (!isTap(groupG, e)) groupG.travelled = true;
33469
33516
  moveGroupDrag(groupG, e);
33470
33517
  return;
33471
33518
  }
33472
33519
  if (!g || !sel) return;
33520
+ if (!isTap(g, e)) g.travelled = true;
33473
33521
  let dx = e.clientX - g.startX;
33474
33522
  let dy = e.clientY - g.startY;
33475
33523
  if (g.kind === "rotate") {
@@ -33489,6 +33537,9 @@ function createDomEditOverlayGestureHandlers(opts) {
33489
33537
  return;
33490
33538
  }
33491
33539
  if (g.kind === "drag") {
33540
+ const lock = lockDragToDominantAxis(dx, dy, e.shiftKey);
33541
+ dx = lock.dx;
33542
+ dy = lock.dy;
33492
33543
  const sc = g.snapContext;
33493
33544
  const dragRotated = Math.abs(g.actualRotation) >= ROTATED_SNAP_BYPASS_DEGREES;
33494
33545
  if (!dragRotated && sc?.snapEnabled && sc.targets.length > 0) {
@@ -33514,7 +33565,8 @@ function createDomEditOverlayGestureHandlers(opts) {
33514
33565
  targets: allTargets,
33515
33566
  gridEdges: sc.gridEdges ?? void 0,
33516
33567
  threshold: SNAP_THRESHOLD_PX,
33517
- disabled: e.altKey
33568
+ disabled: e.altKey,
33569
+ lockedAxis: lock.lockedAxis
33518
33570
  });
33519
33571
  dx = snap.dx;
33520
33572
  dy = snap.dy;
@@ -33596,8 +33648,14 @@ function createDomEditOverlayGestureHandlers(opts) {
33596
33648
  const rawDx = e.clientX - groupG.startX;
33597
33649
  const rawDy = e.clientY - groupG.startY;
33598
33650
  opts.suppressNextBoxClickRef.current = true;
33599
- if (Math.hypot(rawDx, rawDy) < BLOCKED_MOVE_THRESHOLD_PX) {
33651
+ if (isTap(groupG, e)) {
33600
33652
  restoreGroupPathOffsets(groupG);
33653
+ if (e.shiftKey) {
33654
+ opts.onCanvasMouseDown(e, {
33655
+ preferClipAncestor: false,
33656
+ hoverSelection: opts.hoverSelectionRef.current
33657
+ });
33658
+ }
33601
33659
  return;
33602
33660
  }
33603
33661
  const dx = groupG.lastSnappedDx ?? rawDx;
@@ -33643,10 +33701,8 @@ function createDomEditOverlayGestureHandlers(opts) {
33643
33701
  opts.gestureRef.current = null;
33644
33702
  opts.rafPausedRef.current = false;
33645
33703
  const movedDistance = Math.hypot(e.clientX - g.startX, e.clientY - g.startY);
33646
- if (g.kind === "drag" && movedDistance < BLOCKED_MOVE_THRESHOLD_PX) {
33647
- restoreStudioPathOffset(sel.element, g.initialPathOffset);
33648
- endStudioManualEditGesture(sel.element, g.manualEditDragToken);
33649
- resumeGsapTimelines(sel.element);
33704
+ if (g.kind === "drag" && isTap(g, e)) {
33705
+ if (g.pathOffsetMember) restoreManualOffsetDragMembers([g.pathOffsetMember]);
33650
33706
  if (box) {
33651
33707
  box.style.left = `${g.originLeft}px`;
33652
33708
  box.style.top = `${g.originTop}px`;
@@ -33772,9 +33828,7 @@ function createDomEditOverlayGestureHandlers(opts) {
33772
33828
  const g = opts.gestureRef.current;
33773
33829
  const sel = g?.selection ?? selectionRef.current;
33774
33830
  if (g?.mode === "path-offset" && sel) {
33775
- restoreStudioPathOffset(sel.element, g.initialPathOffset);
33776
- endStudioManualEditGesture(sel.element, g.manualEditDragToken);
33777
- resumeGsapTimelines(sel.element);
33831
+ if (g.pathOffsetMember) restoreManualOffsetDragMembers([g.pathOffsetMember]);
33778
33832
  restoreGestureOverlayRect(g);
33779
33833
  }
33780
33834
  if (g?.mode === "box-size" && sel) {
@@ -34186,7 +34240,7 @@ function DomEditCropHandles({
34186
34240
  startX: event.clientX,
34187
34241
  startY: event.clientY,
34188
34242
  startInsets: state.insets,
34189
- didMove: false,
34243
+ insets: state.insets,
34190
34244
  angleDeg: frame.angleDeg,
34191
34245
  scaleX: frame.scaleX,
34192
34246
  scaleY: frame.scaleY
@@ -34211,7 +34265,7 @@ function DomEditCropHandles({
34211
34265
  scaleY: gesture.scaleY
34212
34266
  };
34213
34267
  const nextInsets = gesture.edge === "move" ? resolveCropInsetFromMoveDrag(drag) : resolveCropInsetFromEdgeDrag({ ...drag, edge: gesture.edge, width, height });
34214
- gesture.didMove = true;
34268
+ gesture.insets = nextInsets;
34215
34269
  setState((prev) => ({ ...prev, insets: nextInsets }));
34216
34270
  };
34217
34271
  const endCropGesture = (event) => {
@@ -34226,13 +34280,14 @@ function DomEditCropHandles({
34226
34280
  const finishCropGesture = (event) => {
34227
34281
  const gesture = endCropGesture(event);
34228
34282
  if (!gesture) return;
34229
- if (!gesture.didMove) return;
34230
34283
  const el = selection.element;
34231
34284
  const reLift = () => {
34232
34285
  if (liftedRef.current) el.style.setProperty("clip-path", "none");
34233
34286
  };
34234
- const committedValue = buildInsetClipPathSides(state.insets, state.radius);
34235
- const cropped = state.insets.top > 0 || state.insets.right > 0 || state.insets.bottom > 0 || state.insets.left > 0;
34287
+ const { insets } = gesture;
34288
+ const committedValue = buildInsetClipPathSides(insets, state.radius);
34289
+ if (committedValue === buildInsetClipPathSides(gesture.startInsets, state.radius)) return;
34290
+ const cropped = insets.top > 0 || insets.right > 0 || insets.bottom > 0 || insets.left > 0;
34236
34291
  const commit = onStyleCommit?.("clip-path", committedValue);
34237
34292
  reLift();
34238
34293
  void Promise.resolve(commit).then(() => {
@@ -34463,7 +34518,7 @@ function DomEditGroupChrome({
34463
34518
  cursor: !allowBodyDrag ? void 0 : canManipulate && groupCanMove ? "move" : "default"
34464
34519
  },
34465
34520
  onPointerDown: (e) => {
34466
- if (!canManipulate || e.shiftKey || !allowBodyDrag) return;
34521
+ if (!canManipulate || !allowBodyDrag || e.shiftKey && !groupCanMove) return;
34467
34522
  gestures.startGroupDrag(e);
34468
34523
  },
34469
34524
  onMouseDown: onBoxMouseDown,
@@ -34533,12 +34588,12 @@ function DomEditSelectionChrome({
34533
34588
  e.stopPropagation();
34534
34589
  return;
34535
34590
  }
34536
- if (!allowBodyDrag || !canManipulate || e.shiftKey) return;
34591
+ if (!allowBodyDrag || !canManipulate) return;
34537
34592
  if (selection.capabilities.canApplyManualOffset) {
34538
34593
  gestures.startGesture("drag", e);
34539
34594
  return;
34540
34595
  }
34541
- gestures.startBlockedMove(e, selection);
34596
+ if (!e.shiftKey) gestures.startBlockedMove(e, selection);
34542
34597
  },
34543
34598
  onMouseDown: onBoxMouseDown,
34544
34599
  onClick: onBoxClick,
@@ -68676,6 +68731,15 @@ function syncElementAttributes(target, source) {
68676
68731
  target.setAttribute(name, source.getAttribute(name) ?? "");
68677
68732
  }
68678
68733
  }
68734
+ function syncStaleEditMarks(doc, restored) {
68735
+ const restoredDoc = parseSavedSource(restored);
68736
+ for (const live of doc.querySelectorAll(STUDIO_EDIT_ATTRS.map((attr) => `[${attr}]`).join())) {
68737
+ const source = findAuthoredElement(restoredDoc, live);
68738
+ if (source && STUDIO_EDIT_ATTRS.some((a) => live.getAttribute(a) !== source.getAttribute(a))) {
68739
+ syncElementAttributes(live, source);
68740
+ }
68741
+ }
68742
+ }
68679
68743
  function readGsapScriptTexts(html2) {
68680
68744
  const doc = new DOMParser().parseFromString(html2, "text/html");
68681
68745
  return findGsapScriptElements(doc).map((script) => script.textContent ?? "");
@@ -68733,9 +68797,11 @@ function applyUndoRestoreToPreview(iframe, activeCompPath, files, currentTime, r
68733
68797
  const restoredScript = extractGsapScriptText(restored);
68734
68798
  const previousScript = extractGsapScriptText(previous);
68735
68799
  if (restoredScript && restoredScript !== previousScript) {
68800
+ syncStaleEditMarks(doc, restored);
68736
68801
  const result = applySoftReload(iframe, restoredScript, {
68737
68802
  onAsyncFailure: reloadPreview,
68738
- currentTimeOverride: currentTime
68803
+ currentTimeOverride: currentTime,
68804
+ authoredHtml: restored
68739
68805
  });
68740
68806
  if (result === "cannot-soft-reload") {
68741
68807
  reloadPreview();