@hyperframes/studio 0.7.55 → 0.7.57

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 (274) hide show
  1. package/dist/assets/hyperframes-player--Z69cEkE.js +459 -0
  2. package/dist/assets/{index-CPetwHFV.js → index-Bf1x1y8H.js} +1 -1
  3. package/dist/assets/index-DfmYkU44.js +423 -0
  4. package/dist/assets/{index-BXaqaVKt.js → index-KsfE1bUu.js} +1 -1
  5. package/dist/assets/index-_pqzyxB1.css +1 -0
  6. package/dist/{chunk-SOTCF4DF.js → chunk-5QSIMBEJ.js} +2 -1
  7. package/dist/chunk-5QSIMBEJ.js.map +1 -0
  8. package/dist/{domEditingLayers-2ECJK24D.js → domEditingLayers-6LQGKPOI.js} +2 -2
  9. package/dist/index.d.ts +172 -144
  10. package/dist/index.html +2 -2
  11. package/dist/index.js +43613 -40346
  12. package/dist/index.js.map +1 -1
  13. package/package.json +7 -7
  14. package/src/App.tsx +106 -96
  15. package/src/components/EditorShell.tsx +253 -0
  16. package/src/components/StudioGlobalDragOverlay.tsx +1 -3
  17. package/src/components/StudioLeftSidebar.tsx +13 -3
  18. package/src/components/StudioRightPanel.tsx +8 -3
  19. package/src/components/TimelineToolbar.test.tsx +2 -2
  20. package/src/components/TimelineToolbar.tsx +179 -121
  21. package/src/components/editor/CanvasContextMenu.test.tsx +152 -1
  22. package/src/components/editor/CanvasContextMenu.tsx +33 -18
  23. package/src/components/editor/DomEditCropHandles.test.tsx +74 -3
  24. package/src/components/editor/DomEditCropHandles.tsx +64 -29
  25. package/src/components/editor/DomEditOverlay.test.ts +245 -279
  26. package/src/components/editor/DomEditOverlay.tsx +126 -125
  27. package/src/components/editor/DomEditOverlayHover.test.tsx +90 -0
  28. package/src/components/editor/DomEditRotateHandle.tsx +38 -18
  29. package/src/components/editor/DomEditSelectionChrome.test.tsx +80 -0
  30. package/src/components/editor/DomEditSelectionChrome.tsx +257 -0
  31. package/src/components/editor/LayersPanel.test.ts +66 -2
  32. package/src/components/editor/LayersPanel.tsx +53 -2
  33. package/src/components/editor/OffCanvasIndicators.test.tsx +34 -0
  34. package/src/components/editor/OffCanvasIndicators.tsx +49 -10
  35. package/src/components/editor/SnapGuideOverlay.tsx +28 -23
  36. package/src/components/editor/SnapToolbar.test.tsx +0 -1
  37. package/src/components/editor/anchoredResizeCommitFeedsOffset.test.ts +214 -0
  38. package/src/components/editor/anchoredResizeReleaseShift.test.ts +106 -10
  39. package/src/components/editor/canvasContextMenuZOrder.test.ts +186 -2
  40. package/src/components/editor/canvasContextMenuZOrder.ts +144 -22
  41. package/src/components/editor/domEditNudge.test.ts +80 -0
  42. package/src/components/editor/domEditNudge.ts +44 -0
  43. package/src/components/editor/domEditOverlayGeometry.test.ts +167 -1
  44. package/src/components/editor/domEditOverlayGeometry.ts +280 -20
  45. package/src/components/editor/domEditOverlayGestures.ts +44 -41
  46. package/src/components/editor/domEditOverlayStartGesture.ts +62 -2
  47. package/src/components/editor/domEditResizeLocal.test.ts +131 -0
  48. package/src/components/editor/domEditResizeLocal.ts +125 -0
  49. package/src/components/editor/domEditingDom.ts +1 -1
  50. package/src/components/editor/manualEditsDomPatches.test.ts +47 -0
  51. package/src/components/editor/manualOffsetDrag.ts +55 -12
  52. package/src/components/editor/offCanvasIndicatorGeometry.ts +31 -10
  53. package/src/components/editor/offCanvasIndicatorRefresh.test.tsx +25 -1
  54. package/src/components/editor/propertyPanelColorGradingSection.tsx +15 -21
  55. package/src/components/editor/resizeDraft.ts +108 -0
  56. package/src/components/editor/snapEngine.test.ts +12 -78
  57. package/src/components/editor/snapEngine.ts +13 -53
  58. package/src/components/editor/useCanvasContextMenuState.ts +92 -0
  59. package/src/components/editor/useDomEditNudge.test.tsx +228 -0
  60. package/src/components/editor/useDomEditNudge.ts +254 -0
  61. package/src/components/editor/useDomEditOverlayGestures.ts +115 -116
  62. package/src/components/editor/useDomEditOverlayRects.ts +9 -2
  63. package/src/components/editor/useZOrderCrossedFlash.tsx +68 -0
  64. package/src/components/nle/AssetPreviewOverlay.test.tsx +106 -0
  65. package/src/components/nle/AssetPreviewOverlay.tsx +171 -0
  66. package/src/components/nle/NLEContext.test.ts +144 -0
  67. package/src/components/nle/NLEContext.tsx +328 -0
  68. package/src/components/nle/NLEPreview.tsx +1 -1
  69. package/src/components/nle/PreviewOverlays.tsx +260 -0
  70. package/src/components/nle/PreviewPane.tsx +163 -0
  71. package/src/components/nle/TimelinePane.test.ts +60 -0
  72. package/src/components/nle/TimelinePane.tsx +293 -0
  73. package/src/components/nle/TimelineResizeDivider.tsx +8 -2
  74. package/src/components/nle/useCompositionStack.test.tsx +44 -0
  75. package/src/components/nle/useCompositionStack.ts +17 -9
  76. package/src/components/nle/useTimelineEditCallbacks.ts +217 -0
  77. package/src/components/sidebar/AssetCard.test.tsx +106 -0
  78. package/src/components/sidebar/AssetCard.tsx +343 -0
  79. package/src/components/sidebar/AssetContextMenu.tsx +15 -30
  80. package/src/components/sidebar/AssetsTab.test.ts +96 -0
  81. package/src/components/sidebar/AssetsTab.tsx +71 -206
  82. package/src/components/sidebar/AudioRow.tsx +56 -2
  83. package/src/components/sidebar/BlocksTab.tsx +8 -1
  84. package/src/components/sidebar/LeftSidebar.tsx +4 -1
  85. package/src/components/sidebar/assetHelpers.ts +29 -0
  86. package/src/contexts/DomEditContext.tsx +9 -0
  87. package/src/contexts/StudioContext.tsx +9 -8
  88. package/src/contexts/TimelineEditContext.tsx +3 -7
  89. package/src/hooks/deleteSelectedKeyframes.ts +35 -0
  90. package/src/hooks/domEditCommitTypes.ts +23 -1
  91. package/src/hooks/domSelectionTestHarness.ts +14 -0
  92. package/src/hooks/gestureTransaction.test.ts +311 -0
  93. package/src/hooks/gestureTransaction.ts +199 -0
  94. package/src/hooks/gsapDragCommit.test.ts +141 -41
  95. package/src/hooks/gsapDragCommit.ts +64 -74
  96. package/src/hooks/gsapDragStaticSetHelpers.ts +9 -21
  97. package/src/hooks/gsapResizeIntercept.test.ts +69 -0
  98. package/src/hooks/gsapResizeIntercept.ts +38 -10
  99. package/src/hooks/gsapRuntimeBridge.test.ts +49 -13
  100. package/src/hooks/gsapRuntimeBridge.ts +4 -4
  101. package/src/hooks/gsapScriptCommitTypes.ts +16 -5
  102. package/src/hooks/gsapShared.test.ts +19 -1
  103. package/src/hooks/gsapShared.ts +14 -0
  104. package/src/hooks/patchDocumentRootDuration.test.ts +71 -0
  105. package/src/hooks/timelineEditingGsap.ts +14 -0
  106. package/src/hooks/timelineEditingHelpers.test.ts +125 -1
  107. package/src/hooks/timelineEditingHelpers.ts +53 -292
  108. package/src/hooks/timelineMoveAdapter.test.ts +81 -0
  109. package/src/hooks/timelineMoveAdapter.ts +39 -0
  110. package/src/hooks/timelineTimingSync.test.ts +150 -0
  111. package/src/hooks/timelineTimingSync.ts +412 -0
  112. package/src/hooks/useAnimatedPropertyCommit.test.tsx +36 -8
  113. package/src/hooks/useAnimatedPropertyCommit.ts +31 -26
  114. package/src/hooks/useAppHotkeys.ts +16 -25
  115. package/src/hooks/useBlockHandlers.ts +55 -27
  116. package/src/hooks/useCaptionDetection.ts +2 -0
  117. package/src/hooks/useCompositionDimensions.ts +32 -5
  118. package/src/hooks/useContextMenuDismiss.ts +31 -7
  119. package/src/hooks/useDomEditCommits.test.tsx +245 -1
  120. package/src/hooks/useDomEditCommits.ts +162 -3
  121. package/src/hooks/useDomEditPositionPatchCommit.ts +2 -0
  122. package/src/hooks/useDomEditSession.ts +2 -2
  123. package/src/hooks/useDomEditTextCommits.test.tsx +135 -0
  124. package/src/hooks/useDomEditTextCommits.ts +8 -3
  125. package/src/hooks/useDomEditWiring.ts +7 -9
  126. package/src/hooks/useDomGeometryCommits.ts +17 -2
  127. package/src/hooks/useDomSelection.test.ts +2 -36
  128. package/src/hooks/useDomSelection.ts +81 -51
  129. package/src/hooks/useDomSelectionSelectionGuards.test.ts +276 -0
  130. package/src/hooks/useElementLifecycleOps.test.tsx +423 -0
  131. package/src/hooks/useElementLifecycleOps.ts +53 -71
  132. package/src/hooks/useElementPicker.ts +7 -8
  133. package/src/hooks/useEnableKeyframes.test.ts +142 -1
  134. package/src/hooks/useEnableKeyframes.ts +73 -29
  135. package/src/hooks/useGestureCommit.test.tsx +130 -0
  136. package/src/hooks/useGestureCommit.ts +32 -8
  137. package/src/hooks/useGroupCommits.ts +7 -28
  138. package/src/hooks/useGsapAwareEditing.test.tsx +239 -0
  139. package/src/hooks/useGsapAwareEditing.ts +115 -20
  140. package/src/hooks/useGsapKeyframeOps.test.tsx +59 -0
  141. package/src/hooks/useGsapKeyframeOps.ts +38 -10
  142. package/src/hooks/useGsapScriptCommits.test.tsx +40 -1
  143. package/src/hooks/useGsapScriptCommits.ts +134 -50
  144. package/src/hooks/useGsapSelectionHandlers.ts +49 -16
  145. package/src/hooks/useMusicBeatAnalysis.ts +72 -36
  146. package/src/hooks/usePersistentEditHistory.test.ts +33 -0
  147. package/src/hooks/usePersistentEditHistory.ts +92 -58
  148. package/src/hooks/usePreviewPersistence.ts +25 -7
  149. package/src/hooks/useRazorSplit.history.test.tsx +148 -18
  150. package/src/hooks/useRazorSplit.test.ts +269 -0
  151. package/src/hooks/useRazorSplit.testHelpers.ts +64 -0
  152. package/src/hooks/useRazorSplit.ts +107 -36
  153. package/src/hooks/useRenderClipContent.ts +24 -6
  154. package/src/hooks/useStudioContextValue.ts +15 -5
  155. package/src/hooks/useStudioUrlState.ts +2 -4
  156. package/src/hooks/useTimelineAssetDropOps.ts +175 -0
  157. package/src/hooks/useTimelineEditing.test.tsx +398 -241
  158. package/src/hooks/useTimelineEditing.ts +181 -265
  159. package/src/hooks/useTimelineEditingTypes.ts +6 -0
  160. package/src/hooks/useTimelineGroupEditing.ts +167 -132
  161. package/src/index.ts +1 -1
  162. package/src/player/components/ImageThumbnail.test.tsx +173 -0
  163. package/src/player/components/ImageThumbnail.tsx +160 -0
  164. package/src/player/components/PlayerControls.tsx +5 -5
  165. package/src/player/components/PlayheadIndicator.tsx +55 -5
  166. package/src/player/components/Timeline.test.ts +183 -23
  167. package/src/player/components/Timeline.tsx +194 -194
  168. package/src/player/components/TimelineCanvas.tsx +218 -523
  169. package/src/player/components/TimelineClip.tsx +4 -1
  170. package/src/player/components/TimelineClipDiamonds.tsx +9 -40
  171. package/src/player/components/TimelineEmptyState.tsx +3 -1
  172. package/src/player/components/TimelineLanes.tsx +487 -0
  173. package/src/player/components/TimelineOverlays.tsx +122 -0
  174. package/src/player/components/TimelineRuler.tsx +58 -48
  175. package/src/player/components/VideoThumbnail.test.tsx +152 -0
  176. package/src/player/components/VideoThumbnail.tsx +28 -7
  177. package/src/player/components/thumbnailUtils.test.ts +127 -0
  178. package/src/player/components/thumbnailUtils.ts +54 -0
  179. package/src/player/components/timelineCallbacks.ts +18 -18
  180. package/src/player/components/timelineClipChildren.tsx +39 -0
  181. package/src/player/components/timelineClipDragCommit.test.ts +1026 -0
  182. package/src/player/components/timelineClipDragCommit.ts +498 -0
  183. package/src/player/components/timelineClipDragPreview.test.ts +144 -0
  184. package/src/player/components/timelineClipDragPreview.ts +323 -38
  185. package/src/player/components/timelineClipDragTypes.ts +65 -0
  186. package/src/player/components/timelineCollision.test.ts +42 -2
  187. package/src/player/components/timelineDragDrop.ts +69 -60
  188. package/src/player/components/timelineEditCapabilities.ts +52 -0
  189. package/src/player/components/timelineEditing.test.ts +1 -1
  190. package/src/player/components/timelineEditing.ts +76 -59
  191. package/src/player/components/timelineGroupEditing.test.ts +126 -0
  192. package/src/player/components/timelineGroupEditing.ts +133 -0
  193. package/src/player/components/timelineGroupResizeCommit.ts +54 -0
  194. package/src/player/components/timelineLayout.test.ts +95 -23
  195. package/src/player/components/timelineLayout.ts +207 -75
  196. package/src/player/components/timelineMarquee.test.ts +197 -0
  197. package/src/player/components/timelineMarquee.ts +112 -0
  198. package/src/player/components/timelineOptimisticRevision.ts +42 -0
  199. package/src/player/components/timelineRevealScroll.test.ts +133 -0
  200. package/src/player/components/timelineRevealScroll.ts +91 -0
  201. package/src/player/components/timelineStackingSync.test.ts +60 -0
  202. package/src/player/components/timelineStackingSync.ts +27 -2
  203. package/src/player/components/timelineTheme.ts +6 -1
  204. package/src/player/components/timelineTrackPersistPipeline.test.ts +168 -0
  205. package/src/player/components/timelineZones.test.ts +122 -310
  206. package/src/player/components/timelineZones.ts +82 -135
  207. package/src/player/components/useResolvedTimelineEditCallbacks.ts +25 -3
  208. package/src/player/components/useTimelineClipDrag.resize.test.tsx +241 -0
  209. package/src/player/components/useTimelineClipDrag.ts +380 -386
  210. package/src/player/components/useTimelineEditPinning.ts +121 -0
  211. package/src/player/components/useTimelineGeometry.ts +129 -0
  212. package/src/player/components/useTimelinePlayhead.ts +4 -16
  213. package/src/player/components/useTimelineRangeSelection.ts +341 -50
  214. package/src/player/components/useTimelineRevealClip.ts +56 -0
  215. package/src/player/components/useTimelineStackingSync.test.tsx +77 -0
  216. package/src/player/components/useTimelineStackingSync.ts +94 -0
  217. package/src/player/hooks/useExpandedTimelineElements.ts +7 -3
  218. package/src/player/hooks/usePlaybackKeyboard.ts +5 -0
  219. package/src/player/hooks/useTimelinePlayer.seek.test.ts +55 -0
  220. package/src/player/hooks/useTimelinePlayer.test.ts +36 -3
  221. package/src/player/hooks/useTimelinePlayer.ts +52 -53
  222. package/src/player/hooks/useTimelinePlayerLoop.ts +85 -0
  223. package/src/player/hooks/useTimelineSyncCallbacks.ts +9 -1
  224. package/src/player/index.ts +5 -2
  225. package/src/player/lib/runtimeProtocol.test.ts +48 -0
  226. package/src/player/lib/runtimeProtocol.ts +65 -0
  227. package/src/player/lib/time.test.ts +19 -19
  228. package/src/player/lib/time.ts +6 -2
  229. package/src/player/lib/timelineDOM.test.ts +25 -98
  230. package/src/player/lib/timelineDOM.ts +21 -81
  231. package/src/player/lib/timelineElementHelpers.ts +73 -5
  232. package/src/player/lib/timelineIframeHelpers.ts +4 -4
  233. package/src/player/store/playerStore.test.ts +38 -13
  234. package/src/player/store/playerStore.ts +99 -3
  235. package/src/styles/studio.css +27 -0
  236. package/src/utils/assetPreviewDismiss.test.ts +27 -0
  237. package/src/utils/assetPreviewDismiss.ts +29 -0
  238. package/src/utils/assetPreviewStore.ts +33 -0
  239. package/src/utils/blockInstaller.ts +10 -2
  240. package/src/utils/editHistory.test.ts +35 -0
  241. package/src/utils/gsapSoftReload.test.ts +119 -1
  242. package/src/utils/gsapSoftReload.ts +153 -0
  243. package/src/utils/mediaTypes.ts +3 -2
  244. package/src/utils/resizeDebug.ts +55 -0
  245. package/src/utils/rootDuration.test.ts +90 -1
  246. package/src/utils/rootDuration.ts +76 -13
  247. package/src/utils/sdkCutover.ts +4 -0
  248. package/src/utils/studioFileHistory.ts +37 -7
  249. package/src/utils/studioHelpers.ts +62 -0
  250. package/src/utils/studioPreviewHelpers.test.ts +65 -8
  251. package/src/utils/studioPreviewHelpers.ts +10 -0
  252. package/src/utils/studioTelemetry.ts +4 -1
  253. package/src/utils/studioUrlState.test.ts +0 -1
  254. package/src/utils/timelineAssetDrop.test.ts +144 -52
  255. package/src/utils/timelineAssetDrop.ts +67 -29
  256. package/src/utils/timelineDiscovery.ts +0 -17
  257. package/dist/assets/hyperframes-player-BBrKzTGd.js +0 -459
  258. package/dist/assets/index-BRwkMj0w.js +0 -423
  259. package/dist/assets/index-Dq7FEg0K.css +0 -1
  260. package/dist/chunk-SOTCF4DF.js.map +0 -1
  261. package/src/components/StudioPreviewArea.tsx +0 -500
  262. package/src/components/nle/NLELayout.test.ts +0 -12
  263. package/src/components/nle/NLELayout.tsx +0 -591
  264. package/src/player/components/TimelineLayerGutter.tsx +0 -61
  265. package/src/player/components/TimelineSelectionOverlays.tsx +0 -55
  266. package/src/player/components/timelineMarqueeSelection.test.ts +0 -87
  267. package/src/player/components/timelineSnapTargets.test.ts +0 -144
  268. package/src/player/components/timelineSnapTargets.ts +0 -164
  269. package/src/player/components/useTimelineClipDrag.test.tsx +0 -506
  270. package/src/player/components/useTimelineClipGroupDrag.ts +0 -417
  271. package/src/player/components/useTimelineMarqueeSelection.test.tsx +0 -231
  272. package/src/player/components/useTimelineMarqueeSelection.ts +0 -276
  273. package/src/utils/timelineDiscovery.test.ts +0 -90
  274. /package/dist/{domEditingLayers-2ECJK24D.js.map → domEditingLayers-6LQGKPOI.js.map} +0 -0
@@ -9,6 +9,14 @@ export interface OverlayRect {
9
9
  height: number;
10
10
  editScaleX: number;
11
11
  editScaleY: number;
12
+ /**
13
+ * The element's live transform rotation in DEGREES (screen/CSS convention, CW
14
+ * positive), decomposed from its computed transform matrix. Present so the
15
+ * selection chrome can render as an oriented bounding box (OBB) that co-rotates
16
+ * with the element. Omitted (treated as 0) for group/union rects and when the
17
+ * transform is unmeasurable — those render axis-aligned exactly as before.
18
+ */
19
+ angle?: number;
12
20
  }
13
21
 
14
22
  export interface GroupOverlayItem {
@@ -98,31 +106,115 @@ export function toVisibleOverlayRect(
98
106
  return rect ? { ...rect, ...hugRectForElement(rect, element) } : null;
99
107
  }
100
108
 
101
- export function toOverlayRect(
109
+ /**
110
+ * getComputedStyle(element).transform decomposed into a DOMMatrix, read ONCE.
111
+ * Shared by orientedOverlayRect's rotation gate and elementCornerOverlayPoints
112
+ * so a single measurement pass serves both — constructing this twice per frame
113
+ * (one read per consumer) was redundant work; see orientedOverlayRect below.
114
+ */
115
+ interface ElementTransformSnapshot {
116
+ matrix: DOMMatrix;
117
+ cs: CSSStyleDeclaration;
118
+ }
119
+
120
+ function readElementTransformSnapshot(
121
+ win: Window,
122
+ element: HTMLElement,
123
+ ): ElementTransformSnapshot | null {
124
+ const DOMMatrixCtor = (win as Window & typeof globalThis).DOMMatrix;
125
+ if (!DOMMatrixCtor) return null;
126
+ const cs = win.getComputedStyle(element);
127
+ try {
128
+ const matrix = new DOMMatrixCtor(cs.transform === "none" ? "" : cs.transform);
129
+ return { matrix, cs };
130
+ } catch {
131
+ return null;
132
+ }
133
+ }
134
+
135
+ /**
136
+ * The element's live transform rotation, in DEGREES (screen/CSS convention, CW
137
+ * positive), decomposed from its transform matrix (rotation = atan2(b, a)).
138
+ * GSAP folds rotation and scale into the same matrix; this reads rotation only.
139
+ * Skew is ignored (does not affect atan2(b, a)).
140
+ */
141
+ function rotationDegreesFromMatrix(matrix: DOMMatrix): number {
142
+ const a = Number.isFinite(matrix.a) ? matrix.a : 1;
143
+ const b = Number.isFinite(matrix.b) ? matrix.b : 0;
144
+ const deg = (Math.atan2(b, a) * 180) / Math.PI;
145
+ return Number.isFinite(deg) ? deg : 0;
146
+ }
147
+
148
+ /** Below this, orientedOverlayRect treats the element as unrotated and returns
149
+ * the AABB directly (see its doc comment) — tight enough to only swallow
150
+ * matrix-decomposition floating-point noise, never an actual rotation. */
151
+ const ROTATION_GATE_EPSILON_DEG = 1e-4;
152
+
153
+ /** iframe→overlay mapping basis shared by every overlay-geometry function. */
154
+ interface OverlayRootScale {
155
+ iframeRect: DOMRect;
156
+ overlayRect: DOMRect;
157
+ rootScaleX: number;
158
+ rootScaleY: number;
159
+ }
160
+
161
+ /** The composition root element inside the preview doc (or null when absent). */
162
+ function findOverlayRootElement(doc: Document | null): HTMLElement | null {
163
+ return doc?.querySelector<HTMLElement>("[data-composition-id]") ?? doc?.documentElement ?? null;
164
+ }
165
+
166
+ /**
167
+ * The root's effective width/height for scaling: prefer the composition's
168
+ * declared dimensions (data-width/data-height), which stay fixed while GSAP
169
+ * transforms mutate the measured rect; fall back to the measured rect. Null when
170
+ * unmeasurable.
171
+ */
172
+ function resolveRootDimensions(root: HTMLElement | null): { width: number; height: number } | null {
173
+ if (!root) return null;
174
+ const rootRect = root.getBoundingClientRect();
175
+ const width = readPositiveDimension(root.getAttribute("data-width")) ?? rootRect.width;
176
+ const height = readPositiveDimension(root.getAttribute("data-height")) ?? rootRect.height;
177
+ if (!width || !height) return null;
178
+ return { width, height };
179
+ }
180
+
181
+ /**
182
+ * The iframe/overlay client rects and the iframe→root scale factors. Uses the
183
+ * composition's declared dimensions (data-width/data-height) for the scale
184
+ * instead of rootRect.width/height: when GSAP applies transforms (scale,
185
+ * translate) to the root, rootRect dimensions change but the composition's
186
+ * canonical size stays fixed, and using rootRect misaligns the overlay during
187
+ * animated playback. Returns null when the geometry is unmeasurable.
188
+ */
189
+ function computeOverlayRootScale(
102
190
  overlayEl: HTMLDivElement,
103
191
  iframe: HTMLIFrameElement,
104
- element: HTMLElement,
105
- ): OverlayRect | null {
192
+ doc: Document | null,
193
+ ): OverlayRootScale | null {
106
194
  const iframeRect = iframe.getBoundingClientRect();
107
195
  const overlayRect = overlayEl.getBoundingClientRect();
108
- const doc = iframe.contentDocument;
109
- const root =
110
- doc?.querySelector<HTMLElement>("[data-composition-id]") ?? doc?.documentElement ?? null;
111
- const rootRect = root?.getBoundingClientRect();
112
- // Use the composition's declared dimensions (data-width/data-height) for scale
113
- // calculation instead of rootRect.width/height. When GSAP applies transforms
114
- // (scale, translate) to the root element, rootRect dimensions change but the
115
- // composition's canonical size stays the same. Using rootRect causes overlay
116
- // misalignment during animated playback.
117
- const declaredWidth = readPositiveDimension(root?.getAttribute("data-width") ?? null);
118
- const declaredHeight = readPositiveDimension(root?.getAttribute("data-height") ?? null);
119
- const rootWidth = declaredWidth ?? rootRect?.width;
120
- const rootHeight = declaredHeight ?? rootRect?.height;
121
- if (!rootWidth || !rootHeight || !rootRect) return null;
196
+ const dims = resolveRootDimensions(findOverlayRootElement(doc));
197
+ if (!dims) return null;
198
+ return {
199
+ iframeRect,
200
+ overlayRect,
201
+ rootScaleX: iframeRect.width / dims.width,
202
+ rootScaleY: iframeRect.height / dims.height,
203
+ };
204
+ }
205
+
206
+ function toOverlayRect(
207
+ overlayEl: HTMLDivElement,
208
+ iframe: HTMLIFrameElement,
209
+ element: HTMLElement,
210
+ precomputedScale?: OverlayRootScale | null,
211
+ ): OverlayRect | null {
212
+ const scale =
213
+ precomputedScale ?? computeOverlayRootScale(overlayEl, iframe, iframe.contentDocument);
214
+ if (!scale) return null;
215
+ const { iframeRect, overlayRect, rootScaleX, rootScaleY } = scale;
122
216
 
123
217
  const elementRect = element.getBoundingClientRect();
124
- const rootScaleX = iframeRect.width / rootWidth;
125
- const rootScaleY = iframeRect.height / rootHeight;
126
218
  const sourceBoundary = findSourceBoundary(element);
127
219
  const sourceBoundaryRect = sourceBoundary?.getBoundingClientRect();
128
220
  const editScale = resolveDomEditCoordinateScale({
@@ -144,7 +236,163 @@ export function toOverlayRect(
144
236
  };
145
237
  }
146
238
 
239
+ /** Which physical corner of the (possibly rotated) element a resize handle keeps
240
+ * fixed: NW grabs the top-left, so the bottom-right (se) is the anchor, etc. */
241
+ export type FixedCorner = "nw" | "ne" | "sw" | "se";
242
+
243
+ /** Distance between two overlay-px corner points — the edge-length math
244
+ * orientedOverlayRect uses to turn corners into a width/height. Exported so a
245
+ * caller already holding raw corners (e.g. a resize gesture mid-measurement)
246
+ * can derive the same dimensions without a second orientedOverlayRect call. */
247
+ export function cornerEdgeLength(a: { x: number; y: number }, b: { x: number; y: number }): number {
248
+ return Math.hypot(b.x - a.x, b.y - a.y);
249
+ }
250
+
251
+ /**
252
+ * The centroid (rendered center) of the four transformed corners from
253
+ * `elementCornerOverlayPoints`, in overlay px. This is the element's true rotation
254
+ * center — the point a center-anchored resize keeps planted.
255
+ */
256
+ export function overlayCornersCentroid(corners: Record<FixedCorner, { x: number; y: number }>): {
257
+ x: number;
258
+ y: number;
259
+ } {
260
+ return {
261
+ x: (corners.nw.x + corners.ne.x + corners.se.x + corners.sw.x) / 4,
262
+ y: (corners.nw.y + corners.ne.y + corners.se.y + corners.sw.y) / 4,
263
+ };
264
+ }
265
+
266
+ /**
267
+ * The element's border-box corners in OVERLAY coordinates, honoring its live
268
+ * transform (rotation/skew/scale) — NOT the axis-aligned getBoundingClientRect.
269
+ * A rotated element's four visual corners are the transformed local box corners.
270
+ * Uses the same iframe→overlay root scale as toOverlayRect so the returned
271
+ * points share that function's coordinate space. Returns null when the
272
+ * geometry is unmeasurable.
273
+ */
274
+ export function elementCornerOverlayPoints(
275
+ overlayEl: HTMLDivElement,
276
+ iframe: HTMLIFrameElement,
277
+ element: HTMLElement,
278
+ precomputedScale?: OverlayRootScale | null,
279
+ precomputedTransform?: ElementTransformSnapshot | null,
280
+ ): Record<FixedCorner, { x: number; y: number }> | null {
281
+ const win = iframe.contentWindow;
282
+ const doc = iframe.contentDocument;
283
+ if (!win || !doc) return null;
284
+ const DOMPointCtor = (win as Window & typeof globalThis).DOMPoint;
285
+ if (!DOMPointCtor) return null;
286
+
287
+ const scale = precomputedScale ?? computeOverlayRootScale(overlayEl, iframe, doc);
288
+ if (!scale) return null;
289
+ const { iframeRect, overlayRect, rootScaleX, rootScaleY } = scale;
290
+
291
+ // The element's local border box maps to viewport coords by the SAME transform
292
+ // matrix the browser used for its BCR. We recover the transform's screen-space
293
+ // action from the BCR: transformPoint(localCorner - origin) gives a corner
294
+ // RELATIVE to the transformed origin. We anchor those relative corners to the
295
+ // BCR by matching the AABB of the transformed corners to the real BCR — the
296
+ // constant offset cancels in the before/after difference the caller takes, but
297
+ // we resolve it fully here so callers can also read absolute overlay positions.
298
+ const transform = precomputedTransform ?? readElementTransformSnapshot(win, element);
299
+ if (!transform) return null;
300
+ const { matrix, cs } = transform;
301
+ const w = element.offsetWidth;
302
+ const h = element.offsetHeight;
303
+ const originParts = cs.transformOrigin.split(" ").map((p) => Number.parseFloat(p));
304
+ const ox = Number.isFinite(originParts[0]!) ? originParts[0]! : w / 2;
305
+ const oy = Number.isFinite(originParts[1]!) ? originParts[1]! : h / 2;
306
+ const rel = (lx: number, ly: number): { x: number; y: number } => {
307
+ const p = matrix.transformPoint(new DOMPointCtor(lx - ox, ly - oy));
308
+ return { x: p.x, y: p.y };
309
+ };
310
+ const relCorners = {
311
+ nw: rel(0, 0),
312
+ ne: rel(w, 0),
313
+ se: rel(w, h),
314
+ sw: rel(0, h),
315
+ };
316
+ // Recover the absolute viewport position by matching to the element's BCR:
317
+ // the relative corners' AABB min corresponds to the BCR's top-left.
318
+ const xs = [relCorners.nw.x, relCorners.ne.x, relCorners.se.x, relCorners.sw.x];
319
+ const ys = [relCorners.nw.y, relCorners.ne.y, relCorners.se.y, relCorners.sw.y];
320
+ const bcr = element.getBoundingClientRect();
321
+ const dx = bcr.left - Math.min(...xs);
322
+ const dy = bcr.top - Math.min(...ys);
323
+ const toOverlay = (pt: { x: number; y: number }): { x: number; y: number } => ({
324
+ x: iframeRect.left - overlayRect.left + (pt.x + dx) * rootScaleX,
325
+ y: iframeRect.top - overlayRect.top + (pt.y + dy) * rootScaleY,
326
+ });
327
+ return {
328
+ nw: toOverlay(relCorners.nw),
329
+ ne: toOverlay(relCorners.ne),
330
+ se: toOverlay(relCorners.se),
331
+ sw: toOverlay(relCorners.sw),
332
+ };
333
+ }
334
+
335
+ /**
336
+ * The selection chrome's ORIENTED bounding box: the element's UNROTATED border box
337
+ * expressed in overlay coordinates (center-anchored left/top/width/height) plus the
338
+ * live rotation angle. Rendering that rect with `transform: rotate(angle)` about its
339
+ * center reproduces the element's real transformed corners exactly, so the border,
340
+ * corner dots, rotate handle, and crop pills all co-rotate with the object.
341
+ *
342
+ * Built from `elementCornerOverlayPoints` (the real transformed corners): the OBB
343
+ * center is the corner centroid, the unrotated width/height are the edge lengths, and
344
+ * left/top place the unrotated box so that rotating it about its center lands the
345
+ * corners back on the measured points. At angle 0 this equals `toOverlayRect` (the
346
+ * AABB and OBB coincide), so unrotated chrome is pixel-identical to today.
347
+ *
348
+ * Returns the plain AABB rect (angle 0) when the corner geometry can't be measured.
349
+ *
350
+ * Rotation gate: an unrotated element's OBB is numerically identical to its AABB
351
+ * (the comment above), so a cheap rotation read decides up front whether the
352
+ * (much pricier) corner-transform pass runs at all — for the overwhelming
353
+ * majority of selections, which aren't rotated, this call is just `toOverlayRect`
354
+ * plus one getComputedStyle/DOMMatrix read. The root scale and the transform
355
+ * snapshot are each computed once per call and threaded into both the rotation
356
+ * read and the corner math, instead of every helper re-measuring independently.
357
+ */
358
+ export function orientedOverlayRect(
359
+ overlayEl: HTMLDivElement,
360
+ iframe: HTMLIFrameElement,
361
+ element: HTMLElement,
362
+ ): OverlayRect | null {
363
+ const scale = computeOverlayRootScale(overlayEl, iframe, iframe.contentDocument);
364
+ if (!scale) return null;
365
+ const base = toOverlayRect(overlayEl, iframe, element, scale);
366
+ if (!base) return null;
367
+
368
+ const win = iframe.contentWindow;
369
+ const transform = win ? readElementTransformSnapshot(win, element) : null;
370
+ const angle = transform ? rotationDegreesFromMatrix(transform.matrix) : 0;
371
+ if (Math.abs(angle) < ROTATION_GATE_EPSILON_DEG) return base;
372
+
373
+ const corners = elementCornerOverlayPoints(overlayEl, iframe, element, scale, transform);
374
+ if (!corners) return base;
375
+ // Unrotated edge lengths (in overlay px): nw→ne is the width, nw→sw the height.
376
+ const width = cornerEdgeLength(corners.nw, corners.ne);
377
+ const height = cornerEdgeLength(corners.nw, corners.sw);
378
+ const centerX = (corners.nw.x + corners.se.x) / 2;
379
+ const centerY = (corners.nw.y + corners.se.y) / 2;
380
+ if (!Number.isFinite(width) || !Number.isFinite(height) || width <= 0 || height <= 0) {
381
+ return base;
382
+ }
383
+ return {
384
+ left: centerX - width / 2,
385
+ top: centerY - height / 2,
386
+ width,
387
+ height,
388
+ editScaleX: base.editScaleX,
389
+ editScaleY: base.editScaleY,
390
+ angle,
391
+ };
392
+ }
393
+
147
394
  const OVERLAY_RECT_EPSILON_PX = 0.5;
395
+ const OVERLAY_RECT_ANGLE_EPSILON_DEG = 0.1;
148
396
 
149
397
  export function rectsEqual(a: OverlayRect | null, b: OverlayRect | null): boolean {
150
398
  if (a === b) return true;
@@ -155,7 +403,8 @@ export function rectsEqual(a: OverlayRect | null, b: OverlayRect | null): boolea
155
403
  Math.abs(a.width - b.width) < OVERLAY_RECT_EPSILON_PX &&
156
404
  Math.abs(a.height - b.height) < OVERLAY_RECT_EPSILON_PX &&
157
405
  Math.abs(a.editScaleX - b.editScaleX) < 0.001 &&
158
- Math.abs(a.editScaleY - b.editScaleY) < 0.001
406
+ Math.abs(a.editScaleY - b.editScaleY) < 0.001 &&
407
+ Math.abs((a.angle ?? 0) - (b.angle ?? 0)) < OVERLAY_RECT_ANGLE_EPSILON_DEG
159
408
  );
160
409
  }
161
410
 
@@ -228,6 +477,17 @@ export function groupAwareOverlayRect(
228
477
  return { ...union, editScaleX: rect.editScaleX, editScaleY: rect.editScaleY };
229
478
  }
230
479
 
480
+ /** Groups stay axis-aligned unions; ordinary elements keep their oriented box. */
481
+ export function orientedGroupAwareOverlayRect(
482
+ overlayEl: HTMLDivElement,
483
+ iframe: HTMLIFrameElement,
484
+ el: HTMLElement,
485
+ ): OverlayRect | null {
486
+ return el.hasAttribute("data-hf-group")
487
+ ? groupAwareOverlayRect(overlayEl, iframe, el)
488
+ : orientedOverlayRect(overlayEl, iframe, el);
489
+ }
490
+
231
491
  export function filterNestedDomEditGroupItems<T extends { element: HTMLElement }>(items: T[]): T[] {
232
492
  return items.filter(
233
493
  (item) => !items.some((other) => other !== item && other.element.contains(item.element)),
@@ -13,10 +13,20 @@ import type { PreviewMouseDownOptions } from "../../hooks/usePreviewInteraction"
13
13
 
14
14
  export type GestureKind = "drag" | "resize" | "rotate";
15
15
 
16
+ /** Which corner handle initiated a resize gesture. */
17
+ export type ResizeHandle = "nw" | "ne" | "sw" | "se";
18
+
16
19
  export const BLOCKED_MOVE_THRESHOLD_PX = 4;
17
- const MIN_RESIZE_EDGE_PX = 20;
18
20
  const ROTATION_COMMIT_EPSILON_DEGREES = 0.05;
19
21
  const ROTATION_SNAP_DEGREES = 15;
22
+ /**
23
+ * Above this rotation, resize/move edge-snapping is bypassed. Industry editors
24
+ * (tldraw/Figma) don't edge-snap rotated boxes — the snap targets are axis-aligned
25
+ * AABBs, so snapping a rotated box's AABB to them shifts the box in a way the user
26
+ * can't predict; a wrong snap is worse than none. Rotation ~0 keeps snapping exactly
27
+ * as before.
28
+ */
29
+ export const ROTATED_SNAP_BYPASS_DEGREES = 0.5;
20
30
 
21
31
  export interface GestureState {
22
32
  kind: GestureKind;
@@ -62,6 +72,19 @@ export interface GestureState {
62
72
  snapContext?: SnapContext;
63
73
  lastSnappedDx?: number;
64
74
  lastSnappedDy?: number;
75
+ /** Corner the resize gesture grabbed (resize gestures only). */
76
+ resizeHandle?: ResizeHandle;
77
+ /** Last anchoring translation applied during a corner resize (overlay px). */
78
+ lastResizeAnchor?: { dx: number; dy: number };
79
+ /**
80
+ * The element's rendered CENTER in overlay px at gesture start (the centroid of
81
+ * its four real — possibly rotated — corners). A center-anchored resize keeps this
82
+ * point pinned; the per-frame anchor translation is computed as the shift of this
83
+ * exact center, not an AABB width/height delta (which only holds the center still
84
+ * when the element grows symmetrically from an unrotated layout box). Undefined
85
+ * when the corner geometry can't be measured (member creation still succeeded).
86
+ */
87
+ resizeFixedCenterStart?: { x: number; y: number };
65
88
  }
66
89
 
67
90
  export interface GroupGestureState {
@@ -89,48 +112,23 @@ export function focusDomEditOverlayElement(element: FocusableDomEditOverlay | nu
89
112
  element?.focus({ preventScroll: true });
90
113
  }
91
114
 
92
- export function resolveDomEditResizeGesture(input: {
115
+ /**
116
+ * Overlay-px translation that keeps the element's CENTER fixed while a corner
117
+ * resizes: a CSS width/height change grows the layout box from its top-left, so
118
+ * the center drifts by half the size change on each axis; translating back by that
119
+ * half-delta re-pins the center. This is the UNROTATED (AABB) fallback used only
120
+ * when the element's real transformed corners can't be measured — the primary path
121
+ * pins the measured center (rotation-safe) in useDomEditOverlayGestures.
122
+ */
123
+ export function resolveResizeCenterAnchorOffset(input: {
93
124
  originWidth: number;
94
125
  originHeight: number;
95
- actualWidth: number;
96
- actualHeight: number;
97
- scaleX: number;
98
- scaleY: number;
99
- // Rendered-per-CSS-pixel factor of the element itself (its live GSAP scale).
100
- // The CSS width/height the draft writes get multiplied by this on screen, so
101
- // the cursor delta must be divided by it — otherwise the box outruns the
102
- // pointer on a rescaled element and snaps back on release. Defaults to 1.
103
- contentScaleX?: number;
104
- contentScaleY?: number;
105
- dx: number;
106
- dy: number;
107
- uniform: boolean;
108
- }): { overlayWidth: number; overlayHeight: number; width: number; height: number } {
109
- const scaleX = input.scaleX > 0 ? input.scaleX : 1;
110
- const scaleY = input.scaleY > 0 ? input.scaleY : 1;
111
- const contentScaleX =
112
- input.contentScaleX !== undefined && input.contentScaleX > 0 ? input.contentScaleX : 1;
113
- const contentScaleY =
114
- input.contentScaleY !== undefined && input.contentScaleY > 0 ? input.contentScaleY : 1;
115
-
116
- if (input.uniform) {
117
- const deltaX = input.dx / (scaleX * contentScaleX);
118
- const deltaY = input.dy / (scaleY * contentScaleY);
119
- const delta = Math.abs(deltaX) >= Math.abs(deltaY) ? deltaX : deltaY;
120
- const side = Math.max(1, Math.max(input.actualWidth, input.actualHeight) + delta);
121
- return {
122
- overlayWidth: Math.max(MIN_RESIZE_EDGE_PX, side * scaleX * contentScaleX),
123
- overlayHeight: Math.max(MIN_RESIZE_EDGE_PX, side * scaleY * contentScaleY),
124
- width: side,
125
- height: side,
126
- };
127
- }
128
-
126
+ overlayWidth: number;
127
+ overlayHeight: number;
128
+ }): { dx: number; dy: number } {
129
129
  return {
130
- overlayWidth: Math.max(MIN_RESIZE_EDGE_PX, input.originWidth + input.dx),
131
- overlayHeight: Math.max(MIN_RESIZE_EDGE_PX, input.originHeight + input.dy),
132
- width: Math.max(1, input.actualWidth + input.dx / (scaleX * contentScaleX)),
133
- height: Math.max(1, input.actualHeight + input.dy / (scaleY * contentScaleY)),
130
+ dx: (input.originWidth - input.overlayWidth) / 2,
131
+ dy: (input.originHeight - input.overlayHeight) / 2,
134
132
  };
135
133
  }
136
134
 
@@ -214,7 +212,12 @@ export type UseDomEditOverlayGesturesOptions = {
214
212
  (updates: DomEditGroupPathOffsetCommit[]) => Promise<void> | void
215
213
  >;
216
214
  onBoxSizeCommitRef: RefObject<
217
- (s: DomEditSelection, n: { width: number; height: number }) => Promise<void> | void
215
+ (
216
+ s: DomEditSelection,
217
+ n: { width: number; height: number },
218
+ offset?: { x: number; y: number },
219
+ restore?: () => void,
220
+ ) => Promise<void> | void
218
221
  >;
219
222
  onRotationCommitRef: RefObject<
220
223
  (s: DomEditSelection, n: { angle: number }) => Promise<void> | void
@@ -20,15 +20,19 @@ import {
20
20
  } from "./manualEdits";
21
21
  import {
22
22
  type OverlayRect,
23
+ elementCornerOverlayPoints,
23
24
  filterNestedDomEditGroupItems,
25
+ overlayCornersCentroid,
24
26
  selectionCacheKey,
25
27
  } from "./domEditOverlayGeometry";
26
28
  import {
27
29
  type GestureKind,
28
30
  type GestureState,
31
+ type ResizeHandle,
29
32
  type UseDomEditOverlayGesturesOptions,
30
33
  } from "./domEditOverlayGestures";
31
34
  import { collectSnapContext, buildExcludeElements } from "./snapTargetCollection";
35
+ import { logResize, resetResizeMoveLog } from "../../utils/resizeDebug";
32
36
 
33
37
  export function startGroupDrag(
34
38
  e: React.PointerEvent<HTMLElement>,
@@ -100,7 +104,11 @@ export function startGesture(
100
104
  kind: GestureKind,
101
105
  e: React.PointerEvent<HTMLElement>,
102
106
  opts: UseDomEditOverlayGesturesOptions,
103
- options?: { selection?: DomEditSelection; rect?: OverlayRect | null },
107
+ options?: {
108
+ selection?: DomEditSelection;
109
+ rect?: OverlayRect | null;
110
+ resizeHandle?: ResizeHandle;
111
+ },
104
112
  ): boolean {
105
113
  const sel = options?.selection ?? opts.selectionRef.current;
106
114
  const rect = options?.rect ?? opts.overlayRectRef.current;
@@ -173,7 +181,29 @@ export function startGesture(
173
181
  initialPathOffset = result.member.initialPathOffset;
174
182
  manualEditDragToken = result.member.gestureToken;
175
183
  } else {
176
- manualEditDragToken = beginStudioManualEditGesture(sel.element);
184
+ // Center-anchored corner resize (CapCut model): the element scales about its
185
+ // CENTER, which stays planted. All four corners behave identically, so EVERY
186
+ // corner needs the manual-offset member that translates the element to re-pin
187
+ // its center per frame (the memberless else-branch is only a defensive fallback
188
+ // if member creation fails, e.g. the element can't take a manual offset).
189
+ const needsAnchorOffset = kind === "resize" && sel.capabilities.canApplyManualOffset;
190
+ if (needsAnchorOffset) {
191
+ const result = createManualOffsetDragMember({
192
+ key: selectionCacheKey(sel),
193
+ selection: sel,
194
+ element: sel.element,
195
+ rect,
196
+ });
197
+ if (result.ok) {
198
+ pathOffsetMember = result.member;
199
+ initialPathOffset = result.member.initialPathOffset;
200
+ manualEditDragToken = result.member.gestureToken;
201
+ } else {
202
+ manualEditDragToken = beginStudioManualEditGesture(sel.element);
203
+ }
204
+ } else {
205
+ manualEditDragToken = beginStudioManualEditGesture(sel.element);
206
+ }
177
207
  }
178
208
 
179
209
  const overlayBounds = overlayEl?.getBoundingClientRect();
@@ -181,6 +211,17 @@ export function startGesture(
181
211
  const centerY = (overlayBounds?.top ?? 0) + rect.top + rect.height / 2;
182
212
 
183
213
  const iframe = opts.iframeRef.current;
214
+
215
+ // For a center-anchored corner resize, capture the element's rendered CENTER (the
216
+ // centroid of its four real, rotation-aware corners) now, so per-frame anchoring
217
+ // can pin that exact point instead of an axis-aligned width/height delta (which
218
+ // only holds the center still when the element grows symmetrically from an
219
+ // unrotated layout box). Present whenever an anchor member exists (all corners).
220
+ let resizeFixedCenterStart: { x: number; y: number } | undefined;
221
+ if (kind === "resize" && pathOffsetMember && overlayEl && iframe) {
222
+ const corners = elementCornerOverlayPoints(overlayEl, iframe, sel.element);
223
+ if (corners) resizeFixedCenterStart = overlayCornersCentroid(corners);
224
+ }
184
225
  const snapContext =
185
226
  (kind === "drag" || kind === "resize") && overlayEl && iframe
186
227
  ? collectSnapContext({
@@ -219,6 +260,25 @@ export function startGesture(
219
260
  resizeAnchor,
220
261
  manualEditDragToken,
221
262
  snapContext,
263
+ resizeHandle: kind === "resize" ? (options?.resizeHandle ?? "se") : undefined,
264
+ resizeFixedCenterStart,
222
265
  };
266
+ if (kind === "resize") {
267
+ resetResizeMoveLog();
268
+ logResize("start", {
269
+ handle: options?.resizeHandle ?? "se",
270
+ pointer: { x: e.clientX, y: e.clientY },
271
+ center: { x: centerX, y: centerY },
272
+ origin: { left: rect.left, top: rect.top, w: rect.width, h: rect.height },
273
+ actual: { w: actualWidth, h: actualHeight },
274
+ editScale: { x: rect.editScaleX, y: rect.editScaleY },
275
+ contentScale: { x: contentScaleX, y: contentScaleY },
276
+ rotation: rotation.angle,
277
+ hasOffsetMember: !!pathOffsetMember,
278
+ fixedCenterStart: resizeFixedCenterStart ?? null,
279
+ initialBoxSize: opts.gestureRef.current?.initialBoxSize ?? null,
280
+ initialInlineStyle: sel.element.getAttribute("style"),
281
+ });
282
+ }
223
283
  return true;
224
284
  }