@hyperframes/studio 0.8.92 → 0.8.94

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 (43) hide show
  1. package/dist/assets/{hyperframes-player-DKJPDHUp.js → hyperframes-player-Bkm-3G56.js} +3 -3
  2. package/dist/assets/{index-DpzpqJ_j.js → index-BUn5H_np.js} +1 -1
  3. package/dist/assets/{index-C5_j5-3u.js → index-BupvpoiM.js} +188 -188
  4. package/dist/assets/{index-CHZ4kFWm.js → index-n2ZRiO44.js} +1 -1
  5. package/dist/index.d.ts +62 -46
  6. package/dist/index.html +1 -1
  7. package/dist/index.js +156 -102
  8. package/dist/index.js.map +1 -1
  9. package/package.json +8 -8
  10. package/src/components/TimelineToolbar.test.tsx +17 -0
  11. package/src/components/TimelineToolbar.tsx +3 -1
  12. package/src/components/editor/ConnectedDomEditOverlay.test.tsx +2 -0
  13. package/src/components/editor/ConnectedDomEditOverlay.tsx +3 -1
  14. package/src/components/editor/DomEditCropHandles.test.tsx +87 -1
  15. package/src/components/editor/DomEditCropHandles.tsx +64 -62
  16. package/src/components/editor/DomEditOverlay.test.ts +39 -0
  17. package/src/components/editor/DomEditOverlay.tsx +6 -1
  18. package/src/components/editor/DomEditSelectionChrome.test.tsx +114 -1
  19. package/src/components/editor/DomEditSelectionChrome.tsx +17 -10
  20. package/src/components/editor/PreviewOverlayProvider.test.tsx +19 -0
  21. package/src/components/editor/PreviewOverlayProvider.tsx +1 -1
  22. package/src/components/editor/SnapToolbar.tsx +1 -1
  23. package/src/components/editor/domEditOverlayGeometry.ts +2 -0
  24. package/src/components/nle/PreviewOverlays.tsx +3 -3
  25. package/src/hooks/gsapResizeIntercept.test.ts +40 -0
  26. package/src/hooks/gsapResizeIntercept.ts +8 -4
  27. package/src/index.ts +4 -0
  28. package/src/player/components/PlayerControls.tsx +2 -15
  29. package/src/player/components/ShortcutsPanel.test.tsx +33 -1
  30. package/src/player/components/ShortcutsPanel.tsx +26 -0
  31. package/src/player/components/Timeline.trackPadding.test.tsx +157 -0
  32. package/src/player/components/TimelineCanvas.tsx +16 -19
  33. package/src/player/components/TimelineTypes.ts +2 -0
  34. package/src/player/components/timelineLayout.test.ts +47 -0
  35. package/src/player/components/timelineLayout.ts +22 -15
  36. package/src/player/components/useTimelineProviderState.tsx +2 -0
  37. package/src/player/components/useTimelineTrackLayout.ts +11 -2
  38. package/src/player/components/useTimelineVirtualRows.test.tsx +32 -0
  39. package/src/player/components/useTimelineVirtualRows.ts +2 -2
  40. package/src/player/hooks/useTimelinePlayer.seek.test.ts +29 -1
  41. package/src/player/hooks/useTimelinePlayer.ts +6 -0
  42. package/src/player/hooks/useTimelinePlayerLoop.ts +6 -2
  43. package/src/player/lib/playbackTypes.ts +1 -0
package/dist/index.js CHANGED
@@ -876,6 +876,25 @@ var ShortcutsPanel = memo(function ShortcutsPanel2({
876
876
  )
877
877
  ] });
878
878
  });
879
+ function ShortcutsButton({ sections, disabled = false, onSeek }) {
880
+ const duration = usePlayerStore((s) => s.duration);
881
+ const inPoint = usePlayerStore((s) => s.inPoint);
882
+ const outPoint = usePlayerStore((s) => s.outPoint);
883
+ const { setInPoint, setOutPoint, requestSeek } = usePlayerStore.getState();
884
+ return /* @__PURE__ */ jsx(
885
+ ShortcutsPanel,
886
+ {
887
+ disabled,
888
+ duration,
889
+ inPoint,
890
+ outPoint,
891
+ setInPoint,
892
+ setOutPoint,
893
+ onSeek: onSeek ?? requestSeek,
894
+ sections
895
+ }
896
+ );
897
+ }
879
898
 
880
899
  // src/player/components/SpeedMenu.tsx
881
900
  import { useState as useState2, useCallback as useCallback3, memo as memo2 } from "react";
@@ -1152,10 +1171,6 @@ var PlayerControls = memo4(function PlayerControls2({
1152
1171
  const setAudioMuted = usePlayerStore.getState().setAudioMuted;
1153
1172
  const setAudioVolume = usePlayerStore.getState().setAudioVolume;
1154
1173
  const setLoopEnabled = usePlayerStore.getState().setLoopEnabled;
1155
- const inPoint = usePlayerStore((s) => s.inPoint);
1156
- const outPoint = usePlayerStore((s) => s.outPoint);
1157
- const setInPoint = usePlayerStore.getState().setInPoint;
1158
- const setOutPoint = usePlayerStore.getState().setOutPoint;
1159
1174
  const timeDisplayMode = usePlayerStore((s) => s.timeDisplayMode);
1160
1175
  const setTimeDisplayMode = usePlayerStore.getState().setTimeDisplayMode;
1161
1176
  const timeDisplayRef = useRef3(null);
@@ -1255,19 +1270,7 @@ var PlayerControls = memo4(function PlayerControls2({
1255
1270
  }
1256
1271
  ),
1257
1272
  onToggleFullscreen && /* @__PURE__ */ jsx4(FullscreenButton, { isFullscreen, onToggleFullscreen }),
1258
- /* @__PURE__ */ jsx4(
1259
- ShortcutsPanel,
1260
- {
1261
- disabled,
1262
- duration,
1263
- inPoint,
1264
- outPoint,
1265
- setInPoint,
1266
- setOutPoint,
1267
- onSeek,
1268
- sections: shortcutSections
1269
- }
1270
- )
1273
+ /* @__PURE__ */ jsx4(ShortcutsButton, { disabled, onSeek, sections: shortcutSections })
1271
1274
  ] })
1272
1275
  ]
1273
1276
  }
@@ -1960,9 +1963,14 @@ function validRowHeight(height) {
1960
1963
  if (height === void 0 || !Number.isFinite(height) || height <= 0) return TRACK_H;
1961
1964
  return height;
1962
1965
  }
1966
+ function validPad(pad, fallback) {
1967
+ return pad !== void 0 && Number.isFinite(pad) ? Math.max(0, pad) : fallback;
1968
+ }
1963
1969
  var rowGeometryCache = /* @__PURE__ */ new WeakMap();
1964
1970
  var EMPTY_ROW_HEIGHTS = Object.freeze([]);
1965
- function createTimelineRowGeometry(rowKeys, rowHeights) {
1971
+ function createTimelineRowGeometry(rowKeys, rowHeights, padding = {}) {
1972
+ const topPad = validPad(padding.top, TRACKS_TOP_PAD);
1973
+ const bottomPad = validPad(padding.bottom, TRACKS_BOTTOM_PAD);
1966
1974
  const heights = Object.freeze(rowHeights.map(validRowHeight));
1967
1975
  const keys = Object.freeze(
1968
1976
  heights.map((_, row) => {
@@ -1985,7 +1993,7 @@ function createTimelineRowGeometry(rowKeys, rowHeights) {
1985
1993
  return (offsets[wholeRow] ?? 0) + (row - wholeRow) * getRowHeight(wholeRow);
1986
1994
  };
1987
1995
  const getRowFromY = (contentY) => {
1988
- const y = contentY - RULER_H - TRACKS_TOP_PAD;
1996
+ const y = contentY - RULER_H - topPad;
1989
1997
  if (heights.length === 0) return y / TRACK_H;
1990
1998
  if (y < 0) return y / getRowHeight(0);
1991
1999
  const rowsHeight = offsets[heights.length] ?? 0;
@@ -2005,10 +2013,11 @@ function createTimelineRowGeometry(rowKeys, rowHeights) {
2005
2013
  rowHeights: heights,
2006
2014
  rowOffsets: offsets,
2007
2015
  rowsHeight: offsets.at(-1) ?? 0,
2008
- canvasHeight: RULER_H + TRACKS_TOP_PAD + (offsets.at(-1) ?? 0) + TRACKS_BOTTOM_PAD,
2016
+ padding: Object.freeze({ top: topPad, bottom: bottomPad }),
2017
+ canvasHeight: RULER_H + topPad + (offsets.at(-1) ?? 0) + bottomPad,
2009
2018
  getRowIndex: (rowKey) => rowIndexByKey.get(rowKey) ?? -1,
2010
2019
  getRowHeight,
2011
- getRowTop: (row) => RULER_H + TRACKS_TOP_PAD + getRowOffset(row),
2020
+ getRowTop: (row) => RULER_H + topPad + getRowOffset(row),
2012
2021
  getRowFromY,
2013
2022
  getRowPositionFromY: (contentY) => {
2014
2023
  const rowFloat = getRowFromY(contentY);
@@ -2033,12 +2042,6 @@ function getTimelineRowGeometry(rowHeights) {
2033
2042
  function getTimelineRowHeight(row, rowHeights = EMPTY_ROW_HEIGHTS) {
2034
2043
  return validRowHeight(rowHeights[row]);
2035
2044
  }
2036
- function getTimelineRowOffset(row, rowHeights) {
2037
- return getTimelineRowGeometry(rowHeights).getRowTop(row) - RULER_H - TRACKS_TOP_PAD;
2038
- }
2039
- function getTimelineRowTop(row, rowHeights = EMPTY_ROW_HEIGHTS) {
2040
- return RULER_H + TRACKS_TOP_PAD + getTimelineRowOffset(row, rowHeights);
2041
- }
2042
2045
  function getTimelineRowFromY(contentY, rowHeights = EMPTY_ROW_HEIGHTS) {
2043
2046
  return getTimelineRowGeometry(rowHeights).getRowFromY(contentY);
2044
2047
  }
@@ -11322,7 +11325,9 @@ function applyGroupStripHeights(tracks, rowHeights, groups, expandedLaneOwnerIds
11322
11325
  return TRACK_H + groupOwnLaneCount(group) * AUTOMATION_LANE_H;
11323
11326
  });
11324
11327
  }
11325
- function useTimelineRowHeights(tracks, gsapAnimations, selectedElementId, selectedElementIds, groups) {
11328
+ function useTimelineRowHeights(tracks, gsapAnimations, selectedElementId, selectedElementIds, groups, trackPadding) {
11329
+ const padTop = trackPadding?.top;
11330
+ const padBottom = trackPadding?.bottom;
11326
11331
  const expandedClipIds = usePlayerStore((s) => s.expandedClipIds);
11327
11332
  const expandedLaneOwnerIds = usePlayerStore((s) => s.expandedLaneOwnerIds);
11328
11333
  const { laneCounts, rowGeometry } = useMemo15(() => {
@@ -11357,10 +11362,13 @@ function useTimelineRowHeights(tracks, gsapAnimations, selectedElementId, select
11357
11362
  laneCounts: laneCounts2,
11358
11363
  rowGeometry: createTimelineRowGeometry(
11359
11364
  tracks.map(([track2]) => track2),
11360
- rowHeights
11365
+ rowHeights,
11366
+ { top: padTop, bottom: padBottom }
11361
11367
  )
11362
11368
  };
11363
11369
  }, [
11370
+ padTop,
11371
+ padBottom,
11364
11372
  expandedClipIds,
11365
11373
  expandedLaneOwnerIds,
11366
11374
  gsapAnimations,
@@ -11378,7 +11386,7 @@ function useTimelineRowHeights(tracks, gsapAnimations, selectedElementId, select
11378
11386
  rowHeights: rowGeometry.rowHeights
11379
11387
  };
11380
11388
  }
11381
- function useTimelineTrackLayout(expandedElements, gsapAnimations, selectedElementId, selectedElementIds) {
11389
+ function useTimelineTrackLayout(expandedElements, gsapAnimations, selectedElementId, selectedElementIds, trackPadding) {
11382
11390
  const { tracks, trackStyles, trackOrder, groups, trackGroupOf } = useTimelineTrackDerivations(expandedElements);
11383
11391
  const trackOrderRef = useRef20(trackOrder);
11384
11392
  trackOrderRef.current = trackOrder;
@@ -11387,7 +11395,8 @@ function useTimelineTrackLayout(expandedElements, gsapAnimations, selectedElemen
11387
11395
  gsapAnimations,
11388
11396
  selectedElementId,
11389
11397
  selectedElementIds,
11390
- groups
11398
+ groups,
11399
+ trackPadding
11391
11400
  );
11392
11401
  return {
11393
11402
  tracks,
@@ -11432,8 +11441,8 @@ function useTimelineDisplayLayout(draggedClip, trackOrder, rowGeometry) {
11432
11441
  const displayTrackOrder2 = useDisplayTrackOrder(draggedClip, trackOrder);
11433
11442
  const displayRowHeights = useDisplayRowHeights(displayTrackOrder2, rowGeometry);
11434
11443
  const displayRowGeometry = useMemo15(
11435
- () => createTimelineRowGeometry(displayTrackOrder2, displayRowHeights),
11436
- [displayTrackOrder2, displayRowHeights]
11444
+ () => createTimelineRowGeometry(displayTrackOrder2, displayRowHeights, rowGeometry.padding),
11445
+ [displayTrackOrder2, displayRowHeights, rowGeometry.padding]
11437
11446
  );
11438
11447
  return {
11439
11448
  displayTrackOrder: displayTrackOrder2,
@@ -12579,7 +12588,7 @@ function useTimelineVirtualRows({
12579
12588
  getItemKey,
12580
12589
  overscan: TIMELINE_VIEWPORT_BUDGETS.rowOverscanPerSide,
12581
12590
  rangeExtractor,
12582
- scrollMargin: RULER_H + TRACKS_TOP_PAD,
12591
+ scrollMargin: RULER_H + rowGeometry.padding.top,
12583
12592
  initialRect: { width: viewport.clientWidth, height: viewport.clientHeight },
12584
12593
  initialOffset
12585
12594
  });
@@ -12797,6 +12806,7 @@ function useTimelineProviderState({
12797
12806
  theme: themeOverrides,
12798
12807
  showAudioEffects = true,
12799
12808
  showKeyframes = true,
12809
+ trackPadding,
12800
12810
  sessionEpoch = 0,
12801
12811
  previewIframeRef,
12802
12812
  onZIndexReorder
@@ -12874,7 +12884,8 @@ function useTimelineProviderState({
12874
12884
  timelineElements,
12875
12885
  gsapAnimations,
12876
12886
  selectedElementId,
12877
- selectedElementIds
12887
+ selectedElementIds,
12888
+ trackPadding
12878
12889
  );
12879
12890
  const timelineElementsRef = useRef25(timelineElements);
12880
12891
  timelineElementsRef.current = timelineElements;
@@ -21239,7 +21250,13 @@ var TimelineCanvas = memo13(function TimelineCanvas2() {
21239
21250
  style: { height: props.totalH, width: props.contentOrigin + props.trackContentWidth },
21240
21251
  children: [
21241
21252
  /* @__PURE__ */ jsx55(TimelineRulerPart, {}),
21242
- /* @__PURE__ */ jsx55("div", { "aria-hidden": "true", style: { height: props.rowsVirtualized ? 0 : TRACKS_TOP_PAD } }),
21253
+ /* @__PURE__ */ jsx55(
21254
+ "div",
21255
+ {
21256
+ "aria-hidden": "true",
21257
+ style: { height: props.rowsVirtualized ? 0 : props.rowGeometry.padding.top }
21258
+ }
21259
+ ),
21243
21260
  /* @__PURE__ */ jsx55(
21244
21261
  TimelineLanes,
21245
21262
  {
@@ -21257,7 +21274,13 @@ var TimelineCanvas = memo13(function TimelineCanvas2() {
21257
21274
  onRazorSplitAll: props.onRazorSplitAll
21258
21275
  }
21259
21276
  ),
21260
- /* @__PURE__ */ jsx55("div", { "aria-hidden": "true", style: { height: props.rowsVirtualized ? 0 : TRACKS_BOTTOM_PAD } }),
21277
+ /* @__PURE__ */ jsx55(
21278
+ "div",
21279
+ {
21280
+ "aria-hidden": "true",
21281
+ style: { height: props.rowsVirtualized ? 0 : props.rowGeometry.padding.bottom }
21282
+ }
21283
+ ),
21261
21284
  props.laneGapStrips.map((strip) => {
21262
21285
  const rowIndex = displayTrackOrder2.indexOf(strip.track);
21263
21286
  if (rowIndex < 0) return null;
@@ -21270,7 +21293,7 @@ var TimelineCanvas = memo13(function TimelineCanvas2() {
21270
21293
  {
21271
21294
  className: "pointer-events-none absolute",
21272
21295
  style: {
21273
- top: getTimelineRowTop(rowIndex, props.rowHeights) + CLIP_Y,
21296
+ top: props.rowGeometry.getRowTop(rowIndex) + CLIP_Y,
21274
21297
  left: props.contentOrigin + gap.start * props.pps,
21275
21298
  width: Math.max((gap.end - gap.start) * props.pps, 2),
21276
21299
  height: TRACK_H - CLIP_Y * 2,
@@ -21287,7 +21310,7 @@ var TimelineCanvas = memo13(function TimelineCanvas2() {
21287
21310
  {
21288
21311
  className: "absolute pointer-events-none",
21289
21312
  style: {
21290
- top: getTimelineRowTop(draggedRowIndex, props.rowHeights) + CLIP_Y,
21313
+ top: props.rowGeometry.getRowTop(draggedRowIndex) + CLIP_Y,
21291
21314
  left: props.contentOrigin + draggedClip.previewStart * props.pps,
21292
21315
  width: Math.max(draggedClip.element.duration * props.pps, 4),
21293
21316
  height: draggedClipHeight,
@@ -21305,7 +21328,7 @@ var TimelineCanvas = memo13(function TimelineCanvas2() {
21305
21328
  "data-testid": "timeline-drop-preview",
21306
21329
  className: "absolute pointer-events-none",
21307
21330
  style: {
21308
- top: getTimelineRowTop(dropRowIndex, props.rowHeights) + CLIP_Y,
21331
+ top: props.rowGeometry.getRowTop(dropRowIndex) + CLIP_Y,
21309
21332
  left: props.contentOrigin + props.dropPreview.start * props.pps,
21310
21333
  width: DROP_PREVIEW_SECONDS * props.pps,
21311
21334
  height: TRACK_H - CLIP_Y * 2,
@@ -21322,7 +21345,7 @@ var TimelineCanvas = memo13(function TimelineCanvas2() {
21322
21345
  "data-testid": "timeline-insert-line",
21323
21346
  className: "absolute pointer-events-none",
21324
21347
  style: {
21325
- top: getTimelineRowTop(insertLineRow, props.rowHeights) - 0.5,
21348
+ top: props.rowGeometry.getRowTop(insertLineRow) - 0.5,
21326
21349
  left: props.contentOrigin,
21327
21350
  width: props.trackContentWidth,
21328
21351
  height: 1,
@@ -25328,7 +25351,8 @@ function useTimelinePlayerLoop({
25328
25351
  reverseRafRef,
25329
25352
  getAdapter,
25330
25353
  setCurrentTime,
25331
- setIsPlaying
25354
+ setIsPlaying,
25355
+ setLoopStart
25332
25356
  }) {
25333
25357
  const stopReverseLoop = useCallback38(() => {
25334
25358
  cancelAnimationFrame(reverseRafRef.current);
@@ -25346,8 +25370,10 @@ function useTimelinePlayerLoop({
25346
25370
  const rawLoopStart = inPoint !== null ? inPoint : 0;
25347
25371
  const loopEnd = rawLoopStart < rawLoopEnd ? rawLoopEnd : dur;
25348
25372
  const loopStart = rawLoopStart < rawLoopEnd ? rawLoopStart : 0;
25373
+ const looping = usePlayerStore.getState().loopEnabled && dur > 0;
25374
+ setLoopStart(looping ? loopStart : null);
25349
25375
  if (time >= loopEnd) {
25350
- if (usePlayerStore.getState().loopEnabled && dur > 0) {
25376
+ if (looping) {
25351
25377
  adapter.seek(loopStart, { keepPlaying: true });
25352
25378
  liveTime.notify(loopStart);
25353
25379
  adapter.play();
@@ -25365,7 +25391,7 @@ function useTimelinePlayerLoop({
25365
25391
  rafRef.current = requestAnimationFrame(tick);
25366
25392
  };
25367
25393
  rafRef.current = requestAnimationFrame(tick);
25368
- }, [rafRef, getAdapter, setCurrentTime, setIsPlaying]);
25394
+ }, [rafRef, getAdapter, setCurrentTime, setIsPlaying, setLoopStart]);
25369
25395
  const stopRAFLoop = useCallback38(() => {
25370
25396
  cancelAnimationFrame(rafRef.current);
25371
25397
  }, [rafRef]);
@@ -25863,12 +25889,19 @@ function useTimelinePlayer({
25863
25889
  },
25864
25890
  []
25865
25891
  );
25892
+ const setLoopStart = useCallback39((seconds) => {
25893
+ try {
25894
+ iframeRef.current?.contentWindow?.__hf?.setLoopStart?.(seconds);
25895
+ } catch {
25896
+ }
25897
+ }, []);
25866
25898
  const { startRAFLoop, stopRAFLoop, stopReverseLoop } = useTimelinePlayerLoop({
25867
25899
  rafRef,
25868
25900
  reverseRafRef,
25869
25901
  getAdapter,
25870
25902
  setCurrentTime,
25871
- setIsPlaying
25903
+ setIsPlaying,
25904
+ setLoopStart
25872
25905
  });
25873
25906
  const applyPlaybackRate = useCallback39((rate) => {
25874
25907
  const iframe = iframeRef.current;
@@ -28029,7 +28062,7 @@ function PreviewOverlayProvider({ iframe, children }) {
28029
28062
  toggleSafeMargins: () => usePreviewGuidesStore.getState().toggle("safeMarginsVisible")
28030
28063
  }
28031
28064
  };
28032
- return /* @__PURE__ */ jsx74(PreviewOverlayContext.Provider, { value: contextValue, children: /* @__PURE__ */ jsx74("div", { ref: overlayRef, className: "absolute inset-0", children }) });
28065
+ return /* @__PURE__ */ jsx74(PreviewOverlayContext.Provider, { value: contextValue, children: /* @__PURE__ */ jsx74("div", { ref: overlayRef, className: "pointer-events-none absolute inset-0", children }) });
28033
28066
  }
28034
28067
  function usePreviewOverlayContext() {
28035
28068
  const context = useContext8(PreviewOverlayContext);
@@ -30100,6 +30133,7 @@ function readThroughPass(memo53, key2, compute) {
30100
30133
  }
30101
30134
 
30102
30135
  // src/components/editor/domEditOverlayGeometry.ts
30136
+ var RESIZE_HANDLE_HIT_PX = 16;
30103
30137
  function isElementVisibleForOverlay(el) {
30104
30138
  return isElementVisibleThroughAncestors(el);
30105
30139
  }
@@ -34058,30 +34092,43 @@ import { useEffect as useEffect43, useRef as useRef64, useState as useState40 }
34058
34092
  import { Fragment as Fragment26, jsx as jsx84, jsxs as jsxs57 } from "react/jsx-runtime";
34059
34093
  var EDGE_HIT_THICKNESS = 12;
34060
34094
  var EDGE_HIT_LENGTH = 32;
34061
- function edgeHandlePlacement(edge, rect) {
34062
- const cx = rect.left + rect.width / 2;
34063
- const cy = rect.top + rect.height / 2;
34064
- if (edge === "top") {
34065
- return { left: cx, top: rect.top, transform: "translate(-50%, -100%)" };
34066
- }
34067
- if (edge === "bottom") {
34068
- return { left: cx, top: rect.top + rect.height, transform: "translate(-50%, 0)" };
34069
- }
34070
- if (edge === "left") {
34071
- return { left: rect.left, top: cy, transform: "translate(-100%, -50%)" };
34095
+ var EDGE_PILL_LENGTH = 24;
34096
+ var REPOSITION_HANDLE_PX = 22;
34097
+ function edgeHandleLayout(edge, rect) {
34098
+ const vertical = edge === "left" || edge === "right";
34099
+ const length = Math.min(
34100
+ EDGE_HIT_LENGTH,
34101
+ (vertical ? rect.height : rect.width) - RESIZE_HANDLE_HIT_PX
34102
+ );
34103
+ if (length <= 0) return null;
34104
+ const pill = Math.min(EDGE_PILL_LENGTH, length);
34105
+ if (vertical) {
34106
+ return {
34107
+ hit: {
34108
+ left: edge === "left" ? rect.left - EDGE_HIT_THICKNESS : rect.left + rect.width,
34109
+ top: rect.top + (rect.height - length) / 2,
34110
+ width: EDGE_HIT_THICKNESS,
34111
+ height: length
34112
+ },
34113
+ cursor: "ew-resize",
34114
+ pill: { width: 4, height: pill }
34115
+ };
34072
34116
  }
34073
- return { left: rect.left + rect.width, top: cy, transform: "translate(0, -50%)" };
34074
- }
34075
- var EDGES = ["top", "right", "bottom", "left"];
34076
- function edgeHandleMetrics(vertical) {
34077
34117
  return {
34078
- hitWidth: vertical ? EDGE_HIT_THICKNESS : EDGE_HIT_LENGTH,
34079
- hitHeight: vertical ? EDGE_HIT_LENGTH : EDGE_HIT_THICKNESS,
34080
- cursor: vertical ? "ew-resize" : "ns-resize",
34081
- pillWidth: vertical ? 4 : 24,
34082
- pillHeight: vertical ? 24 : 4
34118
+ hit: {
34119
+ left: rect.left + (rect.width - length) / 2,
34120
+ top: edge === "top" ? rect.top - EDGE_HIT_THICKNESS : rect.top + rect.height,
34121
+ width: length,
34122
+ height: EDGE_HIT_THICKNESS
34123
+ },
34124
+ cursor: "ns-resize",
34125
+ pill: { width: pill, height: 4 }
34083
34126
  };
34084
34127
  }
34128
+ function repositionHandleSize(rect) {
34129
+ return Math.min(REPOSITION_HANDLE_PX, Math.max(rect.width, rect.height) - RESIZE_HANDLE_HIT_PX);
34130
+ }
34131
+ var EDGES = ["top", "right", "bottom", "left"];
34085
34132
  function DomEditCropHandles({
34086
34133
  selection,
34087
34134
  overlayRect,
@@ -34127,6 +34174,7 @@ function DomEditCropHandles({
34127
34174
  frame.scaleX,
34128
34175
  frame.scaleY
34129
34176
  );
34177
+ const repositionSize = repositionHandleSize(cropRect);
34130
34178
  const startCropGesture = (edge, event) => {
34131
34179
  if (!onStyleCommit) return;
34132
34180
  event.preventDefault();
@@ -34166,13 +34214,18 @@ function DomEditCropHandles({
34166
34214
  gesture.didMove = true;
34167
34215
  setState((prev) => ({ ...prev, insets: nextInsets }));
34168
34216
  };
34169
- const finishCropGesture = (event) => {
34217
+ const endCropGesture = (event) => {
34170
34218
  const gesture = gestureRef.current;
34171
- if (!gesture || gesture.pointerId !== event.pointerId) return;
34219
+ if (!gesture || gesture.pointerId !== event.pointerId) return null;
34172
34220
  event.preventDefault();
34173
34221
  event.stopPropagation();
34174
34222
  gestureRef.current = null;
34175
34223
  setDragging(false);
34224
+ return gesture;
34225
+ };
34226
+ const finishCropGesture = (event) => {
34227
+ const gesture = endCropGesture(event);
34228
+ if (!gesture) return;
34176
34229
  if (!gesture.didMove) return;
34177
34230
  const el = selection.element;
34178
34231
  const reLift = () => {
@@ -34187,12 +34240,8 @@ function DomEditCropHandles({
34187
34240
  }, reLift);
34188
34241
  };
34189
34242
  const cancelCropGesture = (event) => {
34190
- const gesture = gestureRef.current;
34191
- if (!gesture || gesture.pointerId !== event.pointerId) return;
34192
- event.preventDefault();
34193
- event.stopPropagation();
34194
- gestureRef.current = null;
34195
- setDragging(false);
34243
+ const gesture = endCropGesture(event);
34244
+ if (!gesture) return;
34196
34245
  setState((prev) => ({ ...prev, insets: gesture.startInsets }));
34197
34246
  };
34198
34247
  if (!state.croppable) return null;
@@ -34241,7 +34290,7 @@ function DomEditCropHandles({
34241
34290
  ] })
34242
34291
  }
34243
34292
  ),
34244
- hasCrop && /* @__PURE__ */ jsx84(
34293
+ hasCrop && repositionSize > 0 && /* @__PURE__ */ jsx84(
34245
34294
  "button",
34246
34295
  {
34247
34296
  type: "button",
@@ -34250,11 +34299,10 @@ function DomEditCropHandles({
34250
34299
  "data-dom-edit-crop-handle": "true",
34251
34300
  className: "pointer-events-auto absolute rounded-full border-2 border-studio-accent bg-studio-accent/30 shadow-[0_0_0_1px_rgba(0,0,0,0.4)]",
34252
34301
  style: {
34253
- left: cropRect.left + cropRect.width / 2,
34254
- top: cropRect.top + cropRect.height / 2,
34255
- width: 22,
34256
- height: 22,
34257
- transform: "translate(-50%, -50%)",
34302
+ left: cropRect.left + (cropRect.width - repositionSize) / 2,
34303
+ top: cropRect.top + (cropRect.height - repositionSize) / 2,
34304
+ width: repositionSize,
34305
+ height: repositionSize,
34258
34306
  cursor: "move",
34259
34307
  touchAction: "none"
34260
34308
  },
@@ -34265,10 +34313,9 @@ function DomEditCropHandles({
34265
34313
  }
34266
34314
  ),
34267
34315
  EDGES.map((edge) => {
34268
- const vertical = edge === "left" || edge === "right";
34269
- const place = edgeHandlePlacement(edge, cropRect);
34316
+ const layout = edgeHandleLayout(edge, cropRect);
34317
+ if (!layout) return null;
34270
34318
  const revealed = dragging || hasCrop || hotEdge === edge;
34271
- const m = edgeHandleMetrics(vertical);
34272
34319
  return /* @__PURE__ */ jsx84(
34273
34320
  "button",
34274
34321
  {
@@ -34278,12 +34325,8 @@ function DomEditCropHandles({
34278
34325
  "data-dom-edit-crop-handle": "true",
34279
34326
  className: "pointer-events-auto absolute flex items-center justify-center border-0 bg-transparent p-0",
34280
34327
  style: {
34281
- left: place.left,
34282
- top: place.top,
34283
- width: m.hitWidth,
34284
- height: m.hitHeight,
34285
- transform: place.transform,
34286
- cursor: m.cursor,
34328
+ ...layout.hit,
34329
+ cursor: layout.cursor,
34287
34330
  touchAction: "none"
34288
34331
  },
34289
34332
  onPointerEnter: () => setHotEdge(edge),
@@ -34297,8 +34340,7 @@ function DomEditCropHandles({
34297
34340
  {
34298
34341
  className: "pointer-events-none rounded-full bg-studio-accent/90 shadow-[0_0_0_1px_rgba(0,0,0,0.4)] transition-opacity duration-100",
34299
34342
  style: {
34300
- width: m.pillWidth,
34301
- height: m.pillHeight,
34343
+ ...layout.pill,
34302
34344
  opacity: revealed ? 1 : 0
34303
34345
  }
34304
34346
  }
@@ -34373,7 +34415,6 @@ var RESIZE_HANDLE_DEFS = [
34373
34415
  { handle: "sw", cursor: "nesw-resize", x: "left", y: "bottom" },
34374
34416
  { handle: "se", cursor: "nwse-resize", x: "right", y: "bottom" }
34375
34417
  ];
34376
- var RESIZE_HANDLE_HIT_PX = 16;
34377
34418
  var NO_CROP_INSET = { top: 0, right: 0, bottom: 0, left: 0 };
34378
34419
  function resizeHandleStyle(def, overlayRect, cropInset) {
34379
34420
  const half = RESIZE_HANDLE_HIT_PX / 2;
@@ -34387,6 +34428,7 @@ function DomEditGroupChrome({
34387
34428
  groupOverlayItems,
34388
34429
  groupBounds,
34389
34430
  allowCanvasMovement,
34431
+ allowBodyDrag,
34390
34432
  groupCanMove,
34391
34433
  gestures,
34392
34434
  onBoxMouseDown,
@@ -34418,10 +34460,10 @@ function DomEditGroupChrome({
34418
34460
  top: groupBounds.top,
34419
34461
  width: groupBounds.width,
34420
34462
  height: groupBounds.height,
34421
- cursor: canManipulate && groupCanMove ? "move" : "default"
34463
+ cursor: !allowBodyDrag ? void 0 : canManipulate && groupCanMove ? "move" : "default"
34422
34464
  },
34423
34465
  onPointerDown: (e) => {
34424
- if (!canManipulate || e.shiftKey) return;
34466
+ if (!canManipulate || e.shiftKey || !allowBodyDrag) return;
34425
34467
  gestures.startGroupDrag(e);
34426
34468
  },
34427
34469
  onMouseDown: onBoxMouseDown,
@@ -34434,6 +34476,7 @@ function DomEditSelectionChrome({
34434
34476
  selection,
34435
34477
  overlayRect,
34436
34478
  allowCanvasMovement,
34479
+ allowBodyDrag,
34437
34480
  cropOutlineInsetPx,
34438
34481
  boxRef,
34439
34482
  boxChromeClass,
@@ -34482,7 +34525,7 @@ function DomEditSelectionChrome({
34482
34525
  width: overlayRect.width,
34483
34526
  height: overlayRect.height,
34484
34527
  clipPath: boxClipPath,
34485
- cursor: canManipulate && selection.capabilities.canApplyManualOffset ? "move" : "default"
34528
+ cursor: !allowBodyDrag ? void 0 : canManipulate && selection.capabilities.canApplyManualOffset ? "move" : "default"
34486
34529
  },
34487
34530
  onPointerDown: (e) => {
34488
34531
  if (inlineText?.startFromPress(e)) {
@@ -34490,7 +34533,7 @@ function DomEditSelectionChrome({
34490
34533
  e.stopPropagation();
34491
34534
  return;
34492
34535
  }
34493
- if (!canManipulate || e.shiftKey) return;
34536
+ if (!allowBodyDrag || !canManipulate || e.shiftKey) return;
34494
34537
  if (selection.capabilities.canApplyManualOffset) {
34495
34538
  gestures.startGesture("drag", e);
34496
34539
  return;
@@ -35920,6 +35963,7 @@ var DomEditOverlay = memo25(function DomEditOverlay2({
35920
35963
  groupSelections = [],
35921
35964
  hoverSelection,
35922
35965
  allowCanvasMovement = true,
35966
+ allowBodyDrag = true,
35923
35967
  canvasInput = "overlay",
35924
35968
  onTextEditingChange,
35925
35969
  onSelectionBoxClick,
@@ -35940,6 +35984,7 @@ var DomEditOverlay = memo25(function DomEditOverlay2({
35940
35984
  }) {
35941
35985
  const readOnly = usePreviewReadOnly();
35942
35986
  const hostInput = canvasInput === "host";
35987
+ const bodyDrag = allowBodyDrag || !hostInput;
35943
35988
  const overlayRef = useRef68(null);
35944
35989
  const boxRef = useRef68(null);
35945
35990
  const onMarqueeSelectRef = useRef68(onMarqueeSelect);
@@ -36229,6 +36274,7 @@ var DomEditOverlay = memo25(function DomEditOverlay2({
36229
36274
  groupOverlayItems,
36230
36275
  groupBounds,
36231
36276
  allowCanvasMovement,
36277
+ allowBodyDrag: bodyDrag,
36232
36278
  groupCanMove,
36233
36279
  gestures,
36234
36280
  onBoxMouseDown: suppressBoxMouseDown,
@@ -36242,6 +36288,7 @@ var DomEditOverlay = memo25(function DomEditOverlay2({
36242
36288
  selection,
36243
36289
  overlayRect,
36244
36290
  allowCanvasMovement,
36291
+ allowBodyDrag: bodyDrag,
36245
36292
  cropOutlineInsetPx: cropOutlineInsetPx ?? void 0,
36246
36293
  boxRef,
36247
36294
  boxChromeClass,
@@ -38255,6 +38302,7 @@ function ConnectedDomEditOverlay({
38255
38302
  shouldShowSelectedDomBounds,
38256
38303
  isGestureRecording,
38257
38304
  canvasInput,
38305
+ allowBodyDrag,
38258
38306
  onTextEditingChange,
38259
38307
  onSelectionBoxClick
38260
38308
  }) {
@@ -38287,6 +38335,7 @@ function ConnectedDomEditOverlay({
38287
38335
  groupSelections: shouldShowSelectedDomBounds ? domEditGroupSelections : [],
38288
38336
  allowCanvasMovement: !isGestureRecording,
38289
38337
  canvasInput,
38338
+ allowBodyDrag,
38290
38339
  onTextEditingChange,
38291
38340
  onSelectionBoxClick,
38292
38341
  onCanvasMouseDown: handlePreviewCanvasMouseDown,
@@ -40994,7 +41043,7 @@ var SnapToolbar = memo27(function SnapToolbar2() {
40994
41043
  return /* @__PURE__ */ jsxs66(
40995
41044
  "div",
40996
41045
  {
40997
- className: "absolute top-2 right-2 z-50 flex items-center gap-1",
41046
+ className: "pointer-events-auto absolute top-2 right-2 z-50 flex items-center gap-1",
40998
41047
  onPointerDown: (e) => e.stopPropagation(),
40999
41048
  children: [
41000
41049
  motionPathCreateAvailable && /* @__PURE__ */ jsx95(
@@ -41260,7 +41309,7 @@ function PreviewOverlays({
41260
41309
  return /* @__PURE__ */ jsxs67(Fragment30, { children: [
41261
41310
  /* @__PURE__ */ jsx97(TopologyLens, { iframeRef: previewIframeRef, activeCompositionPath: activeCompPath }),
41262
41311
  /* @__PURE__ */ jsx97(CaptionOverlay, { iframeRef: previewIframeRef }),
41263
- /* @__PURE__ */ jsxs67("div", { className: "absolute top-2 left-1/2 -translate-x-1/2 z-60 flex items-center gap-2 rounded-full border border-studio-accent/40 bg-black/70 px-2.5 py-1", children: [
41312
+ /* @__PURE__ */ jsxs67("div", { className: "pointer-events-auto absolute top-2 left-1/2 -translate-x-1/2 z-60 flex items-center gap-2 rounded-full border border-studio-accent/40 bg-black/70 px-2.5 py-1", children: [
41264
41313
  /* @__PURE__ */ jsx97("span", { className: "h-1.5 w-1.5 rounded-full bg-studio-accent", "aria-hidden": "true" }),
41265
41314
  /* @__PURE__ */ jsx97("span", { className: "text-2xs text-neutral-200", children: "Editing captions" }),
41266
41315
  /* @__PURE__ */ jsx97(
@@ -41277,7 +41326,7 @@ function PreviewOverlays({
41277
41326
  "div",
41278
41327
  {
41279
41328
  role: "alert",
41280
- className: "absolute top-10 left-1/2 -translate-x-1/2 z-60 flex items-center gap-2 rounded-full border border-red-500/50 bg-red-950/90 px-2.5 py-1",
41329
+ className: "pointer-events-auto absolute top-10 left-1/2 -translate-x-1/2 z-60 flex items-center gap-2 rounded-full border border-red-500/50 bg-red-950/90 px-2.5 py-1",
41281
41330
  children: [
41282
41331
  /* @__PURE__ */ jsx97("span", { className: "text-2xs text-red-200", children: captionSyncError }),
41283
41332
  /* @__PURE__ */ jsx97(
@@ -41332,7 +41381,7 @@ function PreviewOverlays({
41332
41381
  {
41333
41382
  type: "button",
41334
41383
  onClick: enterCaptionMode,
41335
- className: "absolute top-2 left-1/2 -translate-x-1/2 z-60 rounded-full border border-neutral-700 bg-black/60 px-2.5 py-1 text-2xs text-neutral-300 transition-colors hover:border-studio-accent/50 hover:text-studio-accent focus-visible:outline-solid focus-visible:outline-1 focus-visible:outline-studio-accent",
41384
+ className: "pointer-events-auto absolute top-2 left-1/2 -translate-x-1/2 z-60 rounded-full border border-neutral-700 bg-black/60 px-2.5 py-1 text-2xs text-neutral-300 transition-colors hover:border-studio-accent/50 hover:text-studio-accent focus-visible:outline-solid focus-visible:outline-1 focus-visible:outline-studio-accent",
41336
41385
  children: "Edit captions"
41337
41386
  }
41338
41387
  )
@@ -76581,7 +76630,9 @@ async function tryGsapResizeIntercept(selection, size, animations, iframe, commi
76581
76630
  const sizeSet = resizeGroup === "size" ? anim ?? findSizeSetAnimation(workingAnimations, sel, selection.element) : findSizeSetAnimation(workingAnimations, sel, selection.element);
76582
76631
  if (resizeGroup === "size") {
76583
76632
  const animatedTween = pickClosestToPlayhead(
76584
- workingAnimations.filter((a) => !isInstantHold(a) && resolveTweenDuration(a) > 0)
76633
+ workingAnimations.filter(
76634
+ (a) => !isInstantHold(a) && resolveTweenDuration(a) > 0 && animationWritesAnyProperty(a, resizeProperties)
76635
+ )
76585
76636
  );
76586
76637
  if (animatedTween) {
76587
76638
  logResize("intercept-route", { route: "keyframed-size", tweenId: animatedTween.id });
@@ -90539,6 +90590,7 @@ function TimelineToolbar({
90539
90590
  domEditSession,
90540
90591
  onSplitElement,
90541
90592
  history: history2,
90593
+ showHistory = true,
90542
90594
  showAddBeat = true,
90543
90595
  showKeyframes = true
90544
90596
  }) {
@@ -90598,7 +90650,7 @@ function TimelineToolbar({
90598
90650
  // position (tracks right below) makes it self-evident.
90599
90651
  /* @__PURE__ */ jsx227("div", { className: "border-b border-neutral-800/60", children: /* @__PURE__ */ jsxs180("div", { className: "flex items-center justify-between px-2 py-0.5", children: [
90600
90652
  /* @__PURE__ */ jsxs180("div", { className: "flex items-center gap-0.5", children: [
90601
- /* @__PURE__ */ jsx227(TimelineHistoryButtons, { ...history2 }),
90653
+ showHistory && /* @__PURE__ */ jsx227(TimelineHistoryButtons, { ...history2 }),
90602
90654
  /* @__PURE__ */ jsx227(TimelineToolPicker, {}),
90603
90655
  /* @__PURE__ */ jsx227("div", { "aria-hidden": "true", className: "mx-1 h-4 w-px bg-neutral-800" }),
90604
90656
  /* @__PURE__ */ jsx227(Tooltip, { label: timelineSnapEnabled ? "Snapping on (N)" : "Snapping off (N)", children: /* @__PURE__ */ jsx227(
@@ -92240,12 +92292,14 @@ export {
92240
92292
  RenderQueue,
92241
92293
  SNAP_THRESHOLD_PX,
92242
92294
  Select,
92295
+ ShortcutsButton,
92243
92296
  Slider,
92244
92297
  SnapGuideOverlay,
92245
92298
  SnapToolbar,
92246
92299
  SourceEditor,
92247
92300
  StudioApp,
92248
92301
  StudioFileConflictError,
92302
+ TRACK_H,
92249
92303
  Tab,
92250
92304
  TabPanel,
92251
92305
  Tabs,