@hyperframes/studio 0.6.96 → 0.6.98

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 (120) hide show
  1. package/dist/assets/hyperframes-player-DgsMQSvV.js +418 -0
  2. package/dist/assets/index-B62bDCQv.css +1 -0
  3. package/dist/assets/index-Ce3pBm_I.js +252 -0
  4. package/dist/assets/{index-BWFaypdT.js → index-D-ET9M0b.js} +1 -1
  5. package/dist/assets/index-D-bS9Dxx.js +1 -0
  6. package/dist/index.html +2 -2
  7. package/package.json +7 -5
  8. package/src/App.tsx +182 -177
  9. package/src/captions/store.ts +11 -11
  10. package/src/components/StudioHeader.tsx +4 -4
  11. package/src/components/StudioLeftSidebar.tsx +2 -2
  12. package/src/components/StudioPreviewArea.tsx +225 -183
  13. package/src/components/StudioRightPanel.tsx +3 -3
  14. package/src/components/TimelineToolbar.tsx +25 -0
  15. package/src/components/editor/DomEditOverlay.tsx +2 -5
  16. package/src/components/editor/EaseCurveSection.tsx +2 -3
  17. package/src/components/editor/GestureTrailOverlay.tsx +4 -3
  18. package/src/components/editor/LayersPanel.tsx +3 -9
  19. package/src/components/editor/PropertyPanel.tsx +20 -61
  20. package/src/components/editor/colorValue.ts +3 -1
  21. package/src/components/editor/domEditOverlayGestures.ts +54 -1
  22. package/src/components/editor/domEditOverlayStartGesture.ts +5 -2
  23. package/src/components/editor/gradientValue.ts +3 -3
  24. package/src/components/editor/keyframeMove.test.ts +101 -0
  25. package/src/components/editor/keyframeMove.ts +151 -0
  26. package/src/components/editor/manualEditsDom.ts +0 -12
  27. package/src/components/editor/propertyPanelHelpers.ts +10 -38
  28. package/src/components/editor/propertyPanelMediaSection.tsx +1 -5
  29. package/src/components/editor/propertyPanelTimingSection.tsx +1 -6
  30. package/src/components/editor/propertyPanelTransformCommit.ts +129 -0
  31. package/src/components/editor/studioMotionOps.test.ts +1 -1
  32. package/src/components/editor/studioMotionOps.ts +2 -1
  33. package/src/components/editor/useDomEditOverlayGestures.ts +1 -46
  34. package/src/components/nle/NLELayout.tsx +1 -24
  35. package/src/components/sidebar/BlocksTab.tsx +2 -2
  36. package/src/contexts/DomEditContext.tsx +134 -31
  37. package/src/contexts/StudioContext.tsx +90 -40
  38. package/src/contexts/TimelineEditContext.tsx +47 -0
  39. package/src/hooks/domEditCommitTypes.ts +14 -0
  40. package/src/hooks/gsapDragCommit.ts +9 -24
  41. package/src/hooks/gsapKeyframeCacheHelpers.ts +2 -1
  42. package/src/hooks/gsapKeyframeCommit.ts +5 -15
  43. package/src/hooks/gsapRuntimeBridge.ts +18 -52
  44. package/src/hooks/gsapRuntimeKeyframes.ts +8 -57
  45. package/src/hooks/gsapRuntimeReaders.ts +19 -26
  46. package/src/hooks/gsapScriptCommitHelpers.ts +1 -11
  47. package/src/hooks/gsapScriptCommitTypes.ts +58 -0
  48. package/src/hooks/gsapShared.ts +157 -0
  49. package/src/hooks/timelineEditingHelpers.ts +63 -2
  50. package/src/hooks/useAnimatedPropertyCommit.ts +3 -25
  51. package/src/hooks/useAppHotkeys.ts +299 -377
  52. package/src/hooks/useConsoleErrorCapture.ts +33 -5
  53. package/src/hooks/useDomEditCommits.ts +35 -293
  54. package/src/hooks/useDomEditPositionPatchCommit.ts +1 -1
  55. package/src/hooks/useDomEditSession.ts +78 -249
  56. package/src/hooks/useDomEditTextCommits.ts +1 -1
  57. package/src/hooks/useDomEditWiring.ts +255 -0
  58. package/src/hooks/useDomGeometryCommits.ts +181 -0
  59. package/src/hooks/useDomSelection.ts +10 -27
  60. package/src/hooks/useEditorSave.ts +82 -0
  61. package/src/hooks/useElementLifecycleOps.ts +177 -0
  62. package/src/hooks/useEnableKeyframes.ts +10 -15
  63. package/src/hooks/useFileManager.ts +32 -114
  64. package/src/hooks/useFileTree.ts +80 -0
  65. package/src/hooks/useGestureCommit.ts +7 -5
  66. package/src/hooks/useGestureRecording.ts +1 -1
  67. package/src/hooks/useGsapAnimationOps.ts +122 -0
  68. package/src/hooks/useGsapArcPathOps.ts +61 -0
  69. package/src/hooks/useGsapAwareEditing.ts +242 -0
  70. package/src/hooks/useGsapKeyframeOps.ts +167 -0
  71. package/src/hooks/useGsapPropertyDebounce.ts +135 -0
  72. package/src/hooks/useGsapScriptCommits.ts +58 -570
  73. package/src/hooks/useGsapSelectionHandlers.ts +22 -9
  74. package/src/hooks/useGsapTweenCache.ts +35 -29
  75. package/src/hooks/useLintModal.ts +7 -0
  76. package/src/hooks/useMusicBeatAnalysis.ts +152 -0
  77. package/src/hooks/useRazorSplit.ts +1 -1
  78. package/src/hooks/useRenderClipContent.ts +46 -21
  79. package/src/hooks/useTimelineEditing.ts +48 -4
  80. package/src/player/components/AudioWaveform.tsx +29 -4
  81. package/src/player/components/BeatStrip.tsx +166 -0
  82. package/src/player/components/Timeline.tsx +39 -18
  83. package/src/player/components/TimelineCanvas.tsx +52 -12
  84. package/src/player/components/TimelineClipDiamonds.tsx +130 -20
  85. package/src/player/components/TimelinePropertyRows.tsx +8 -2
  86. package/src/player/components/TimelineRuler.tsx +36 -2
  87. package/src/player/components/timelineEditing.ts +30 -5
  88. package/src/player/components/useTimelineClipDrag.ts +155 -4
  89. package/src/player/components/useTimelinePlayhead.ts +30 -1
  90. package/src/player/hooks/useTimelinePlayer.ts +47 -45
  91. package/src/player/lib/mediaProbe.ts +46 -3
  92. package/src/player/lib/playbackScrub.ts +16 -0
  93. package/src/player/lib/timelineDOM.ts +10 -2
  94. package/src/player/lib/timelineIframeHelpers.ts +89 -0
  95. package/src/player/store/playerStore.ts +92 -33
  96. package/src/utils/beatEditActions.ts +109 -0
  97. package/src/utils/beatEditing.ts +136 -0
  98. package/src/utils/clipboardPayload.ts +3 -2
  99. package/src/utils/compositionPatterns.ts +2 -0
  100. package/src/utils/keyframeSelection.test.ts +45 -0
  101. package/src/utils/keyframeSelection.ts +29 -0
  102. package/src/utils/rounding.ts +9 -0
  103. package/src/utils/studioHelpers.ts +5 -2
  104. package/src/utils/studioUrlState.ts +2 -1
  105. package/src/utils/timelineAssetDrop.ts +6 -5
  106. package/src/utils/timelineInspector.ts +15 -100
  107. package/dist/assets/hyperframes-player-0esDKGRk.js +0 -418
  108. package/dist/assets/index-B0twsRu0.css +0 -1
  109. package/dist/assets/index-BA979yF1.js +0 -251
  110. package/src/components/editor/DopesheetStrip.tsx +0 -141
  111. package/src/components/editor/StaggerControls.tsx +0 -61
  112. package/src/components/editor/TimelineLayerPanel.test.ts +0 -42
  113. package/src/components/editor/TimelineLayerPanel.tsx +0 -15
  114. package/src/components/nle/TimelineEditorNotice.tsx +0 -133
  115. package/src/hooks/gsapRuntimePreview.ts +0 -19
  116. package/src/player/components/timelineUtils.ts +0 -211
  117. package/src/utils/audioBeatDetection.ts +0 -58
  118. package/src/utils/keyframeSnapping.test.ts +0 -74
  119. package/src/utils/keyframeSnapping.ts +0 -63
  120. package/src/utils/timelineInspector.test.ts +0 -79
@@ -1,12 +1,4 @@
1
- import { useCallback, useEffect, useRef } from "react";
2
1
  import type { TimelineElement } from "../player";
3
- import { usePlayerStore } from "../player";
4
- import {
5
- STUDIO_GSAP_DRAG_INTERCEPT_ENABLED,
6
- STUDIO_GSAP_PANEL_ENABLED,
7
- } from "../components/editor/manualEditingAvailability";
8
- import { type DomEditSelection } from "../components/editor/domEditing";
9
- import { useDomEditPreviewSync } from "./useDomEditPreviewSync";
10
2
  import type { ImportedFontAsset } from "../components/editor/fontAssets";
11
3
  import type { EditHistoryKind } from "../utils/editHistory";
12
4
  import type { RightPanelTab } from "../utils/studioHelpers";
@@ -15,22 +7,11 @@ import type { SidebarTab } from "../components/sidebar/LeftSidebar";
15
7
  import { useAskAgentModal } from "./useAskAgentModal";
16
8
  import { useDomSelection } from "./useDomSelection";
17
9
  import { usePreviewInteraction } from "./usePreviewInteraction";
18
- import { GSAP_CSS_FALLBACK_BLOCKED_MESSAGE, useDomEditCommits } from "./useDomEditCommits";
10
+ import { useDomEditCommits } from "./useDomEditCommits";
19
11
  import { useGsapScriptCommits } from "./useGsapScriptCommits";
20
- import {
21
- useGsapAnimationsForElement,
22
- useGsapCacheVersion,
23
- usePopulateKeyframeCacheForFile,
24
- } from "./useGsapTweenCache";
25
- import {
26
- tryGsapDragIntercept,
27
- tryGsapResizeIntercept,
28
- tryGsapRotationIntercept,
29
- } from "./gsapRuntimeBridge";
30
- import { useAnimatedPropertyCommit } from "./useAnimatedPropertyCommit";
31
- import { useGsapAnimationFetchFallback } from "./useGsapAnimationFetchFallback";
32
- import { useGsapInteractionFailureTelemetry } from "./useGsapInteractionFailureTelemetry";
33
- import { useGsapSelectionHandlers } from "./useGsapSelectionHandlers";
12
+ import { useGsapCacheVersion } from "./useGsapTweenCache";
13
+ import { useDomEditWiring } from "./useDomEditWiring";
14
+ import { useGsapAwareEditing } from "./useGsapAwareEditing";
34
15
 
35
16
  // ── Types ──
36
17
 
@@ -81,7 +62,6 @@ export interface UseDomEditSessionParams {
81
62
 
82
63
  // ── Hook ──
83
64
 
84
- // fallow-ignore-next-line complexity
85
65
  export function useDomEditSession({
86
66
  projectId,
87
67
  activeCompPath,
@@ -118,22 +98,9 @@ export function useDomEditSession({
118
98
  getSidebarTab,
119
99
  }: UseDomEditSessionParams) {
120
100
  void _setRefreshKey;
101
+ void _readProjectFile;
121
102
 
122
- const onClickToSource = useCallback(
123
- (selection: DomEditSelection) => {
124
- if (!openSourceForSelection || !selectSidebarTab) return;
125
- if (!selection.sourceFile) return;
126
- selectSidebarTab("code");
127
- openSourceForSelection(selection.sourceFile, {
128
- id: selection.id,
129
- selector: selection.selector,
130
- selectorIndex: selection.selectorIndex,
131
- });
132
- },
133
- [openSourceForSelection, selectSidebarTab],
134
- );
135
-
136
- // ── Selection (delegated to useDomSelection) ──
103
+ // ── Selection ──
137
104
 
138
105
  const {
139
106
  domEditSelection,
@@ -165,7 +132,7 @@ export function useDomEditSession({
165
132
  rightPanelTab,
166
133
  });
167
134
 
168
- // ── Agent modal (delegated to useAskAgentModal) ──
135
+ // ── Agent modal ──
169
136
 
170
137
  const {
171
138
  agentModalOpen,
@@ -187,75 +154,11 @@ export function useDomEditSession({
187
154
  domEditSelection,
188
155
  });
189
156
 
190
- // ── Preview interaction (delegated to usePreviewInteraction) ──
191
-
192
- const {
193
- handlePreviewCanvasMouseDown,
194
- handlePreviewCanvasPointerMove,
195
- handlePreviewCanvasPointerLeave,
196
- handleBlockedDomMove,
197
- handleDomManualDragStart,
198
- } = usePreviewInteraction({
199
- captionEditMode,
200
- compositionLoading,
201
- previewIframeRef,
202
- showToast,
203
- applyDomSelection,
204
- resolveDomSelectionFromPreviewPoint,
205
- resolveAllDomSelectionsFromPreviewPoint,
206
- updateDomEditHoverSelection,
207
- onClickToSource,
208
- });
209
-
210
- // Sync DOM selection → timeline selectedElementId so that clip selection
211
- // highlights and diamond playhead fills work on cold-load URL restore.
212
- useEffect(() => {
213
- if (!domEditSelection?.id) return;
214
- const { selectedElementId, elements, setSelectedElementId } = usePlayerStore.getState();
215
- const matchKey = elements.find(
216
- (el) => el.domId === domEditSelection.id || el.id === domEditSelection.id,
217
- );
218
- const key = matchKey ? (matchKey.key ?? matchKey.id) : null;
219
- if (key && key !== selectedElementId) setSelectedElementId(key);
220
- }, [domEditSelection?.id]);
221
-
222
- // ── GSAP script editing ──
157
+ // ── GSAP cache (hoisted so both useGsapScriptCommits and useDomEditWiring share the same instance) ──
223
158
 
224
159
  const { version: gsapCacheVersion, bump: bumpGsapCache } = useGsapCacheVersion();
225
160
 
226
- // Bump GSAP cache when refreshKey changes (code-tab edits trigger iframe
227
- // reload via refreshKey but don't go through commitMutation, so the cache
228
- // would otherwise retain stale keyframe entries).
229
- const prevRefreshKeyRef = useRef(refreshKey);
230
- // eslint-disable-next-line no-restricted-syntax
231
- useEffect(() => {
232
- if (refreshKey !== prevRefreshKeyRef.current) {
233
- prevRefreshKeyRef.current = refreshKey;
234
- bumpGsapCache();
235
- }
236
- }, [refreshKey, bumpGsapCache]);
237
-
238
- const gsapSourceFile = domEditSelection?.sourceFile || activeCompPath || "index.html";
239
-
240
- usePopulateKeyframeCacheForFile(
241
- STUDIO_GSAP_PANEL_ENABLED ? (projectId ?? null) : null,
242
- gsapSourceFile,
243
- gsapCacheVersion,
244
- previewIframeRef,
245
- );
246
-
247
- const {
248
- animations: selectedGsapAnimations,
249
- multipleTimelines: gsapMultipleTimelines,
250
- unsupportedTimelinePattern: gsapUnsupportedTimelinePattern,
251
- } = useGsapAnimationsForElement(
252
- STUDIO_GSAP_PANEL_ENABLED ? (projectId ?? null) : null,
253
- gsapSourceFile,
254
- domEditSelection
255
- ? { id: domEditSelection.id ?? null, selector: domEditSelection.selector ?? null }
256
- : null,
257
- gsapCacheVersion,
258
- );
161
+ // ── GSAP script commits ──
259
162
 
260
163
  const {
261
164
  commitMutation: gsapCommitMutation,
@@ -288,7 +191,7 @@ export function useDomEditSession({
288
191
  showToast,
289
192
  });
290
193
 
291
- // ── Commit handlers (delegated to useDomEditCommits) ──
194
+ // ── DOM commit handlers ──
292
195
 
293
196
  const {
294
197
  resolveImportedFontAsset,
@@ -326,108 +229,15 @@ export function useDomEditSession({
326
229
  buildDomSelectionFromTarget,
327
230
  });
328
231
 
329
- const trackGsapInteractionFailure = useGsapInteractionFailureTelemetry(activeCompPath, showToast);
330
-
331
- const makeFetchFallback = useGsapAnimationFetchFallback(projectId, gsapSourceFile);
332
-
333
- // GSAP-aware: intercept offset/resize/rotation to commit via script mutation when animated.
334
- const handleGsapAwarePathOffsetCommit = useCallback(
335
- async (selection: DomEditSelection, next: { x: number; y: number }) => {
336
- const hasGsapAnims = selectedGsapAnimations.length > 0;
337
- if (hasGsapAnims && !STUDIO_GSAP_DRAG_INTERCEPT_ENABLED) {
338
- showToast(GSAP_CSS_FALLBACK_BLOCKED_MESSAGE, "error");
339
- throw new Error(GSAP_CSS_FALLBACK_BLOCKED_MESSAGE);
340
- }
341
- if (STUDIO_GSAP_DRAG_INTERCEPT_ENABLED && gsapCommitMutation) {
342
- try {
343
- const handled = await tryGsapDragIntercept(
344
- selection,
345
- next,
346
- selectedGsapAnimations,
347
- previewIframeRef.current,
348
- gsapCommitMutation,
349
- makeFetchFallback(selection),
350
- );
351
- if (handled) return;
352
- } catch (error) {
353
- trackGsapInteractionFailure(error, selection, "drag", "Move animated layer");
354
- throw error;
355
- }
356
- }
357
- return handleDomPathOffsetCommit(selection, next);
358
- },
359
- [
360
- handleDomPathOffsetCommit,
361
- selectedGsapAnimations,
362
- gsapCommitMutation,
363
- previewIframeRef,
364
- makeFetchFallback,
365
- trackGsapInteractionFailure,
366
- showToast,
367
- ],
368
- );
369
-
370
- const handleGsapAwareBoxSizeCommit = useCallback(
371
- async (selection: DomEditSelection, next: { width: number; height: number }) => {
372
- if (STUDIO_GSAP_DRAG_INTERCEPT_ENABLED && gsapCommitMutation) {
373
- try {
374
- const handled = await tryGsapResizeIntercept(
375
- selection,
376
- next,
377
- selectedGsapAnimations,
378
- previewIframeRef.current,
379
- gsapCommitMutation,
380
- makeFetchFallback(selection),
381
- );
382
- if (handled) return;
383
- } catch (error) {
384
- trackGsapInteractionFailure(error, selection, "resize", "Resize animated layer");
385
- throw error;
386
- }
387
- }
388
- return handleDomBoxSizeCommit(selection, next);
389
- },
390
- [
391
- handleDomBoxSizeCommit,
392
- selectedGsapAnimations,
393
- gsapCommitMutation,
394
- previewIframeRef,
395
- makeFetchFallback,
396
- trackGsapInteractionFailure,
397
- ],
398
- );
399
-
400
- const handleGsapAwareRotationCommit = useCallback(
401
- async (selection: DomEditSelection, next: { angle: number }) => {
402
- if (STUDIO_GSAP_DRAG_INTERCEPT_ENABLED && gsapCommitMutation) {
403
- try {
404
- const handled = await tryGsapRotationIntercept(
405
- selection,
406
- next.angle,
407
- selectedGsapAnimations,
408
- previewIframeRef.current,
409
- gsapCommitMutation,
410
- makeFetchFallback(selection),
411
- );
412
- if (handled) return;
413
- } catch (error) {
414
- trackGsapInteractionFailure(error, selection, "rotation", "Rotate animated layer");
415
- throw error;
416
- }
417
- }
418
- return handleDomRotationCommit(selection, next);
419
- },
420
- [
421
- handleDomRotationCommit,
422
- selectedGsapAnimations,
423
- gsapCommitMutation,
424
- previewIframeRef,
425
- makeFetchFallback,
426
- trackGsapInteractionFailure,
427
- ],
428
- );
232
+ // ── Wiring: selection sync, GSAP cache, preview sync, selection handlers ──
429
233
 
430
234
  const {
235
+ onClickToSource,
236
+ selectedGsapAnimations,
237
+ gsapMultipleTimelines,
238
+ gsapUnsupportedTimelinePattern,
239
+ trackGsapInteractionFailure,
240
+ makeFetchFallback,
431
241
  handleGsapUpdateProperty,
432
242
  handleGsapUpdateMeta,
433
243
  handleGsapDeleteAnimation,
@@ -444,8 +254,26 @@ export function useDomEditSession({
444
254
  handleGsapConvertToKeyframes,
445
255
  handleGsapRemoveAllKeyframes,
446
256
  handleResetSelectedElementKeyframes,
447
- } = useGsapSelectionHandlers({
257
+ } = useDomEditWiring({
258
+ projectId,
259
+ activeCompPath,
448
260
  domEditSelection,
261
+ domEditSelectionRef,
262
+ previewIframeRef,
263
+ previewIframe,
264
+ captionEditMode,
265
+ refreshKey,
266
+ gsapCacheVersion,
267
+ bumpGsapCache,
268
+ showToast,
269
+ refreshPreviewDocumentVersion,
270
+ syncPreviewHistoryHotkey,
271
+ applyStudioManualEditsToPreviewRef,
272
+ applyDomSelection,
273
+ buildDomSelectionFromTarget,
274
+ openSourceForSelection,
275
+ selectSidebarTab,
276
+ getSidebarTab,
449
277
  updateGsapProperty,
450
278
  updateGsapMeta,
451
279
  deleteGsapAnimation,
@@ -462,47 +290,54 @@ export function useDomEditSession({
462
290
  convertToKeyframes,
463
291
  removeAllKeyframes,
464
292
  handleDomManualEditsReset,
465
- selectedGsapAnimations,
466
293
  });
467
294
 
468
- const commitAnimatedProperty = useAnimatedPropertyCommit({
469
- selectedGsapAnimations,
470
- gsapCommitMutation,
471
- addGsapAnimation: (sel, method, time) => addGsapAnimation(sel, method, time),
472
- convertToKeyframes: (sel, animId) => convertToKeyframes(sel, animId),
295
+ // ── Preview interaction ──
296
+
297
+ const {
298
+ handlePreviewCanvasMouseDown,
299
+ handlePreviewCanvasPointerMove,
300
+ handlePreviewCanvasPointerLeave,
301
+ handleBlockedDomMove,
302
+ handleDomManualDragStart,
303
+ } = usePreviewInteraction({
304
+ captionEditMode,
305
+ compositionLoading,
473
306
  previewIframeRef,
474
- bumpGsapCache,
307
+ showToast,
308
+ applyDomSelection,
309
+ resolveDomSelectionFromPreviewPoint,
310
+ resolveAllDomSelectionsFromPreviewPoint,
311
+ updateDomEditHoverSelection,
312
+ onClickToSource,
475
313
  });
476
314
 
477
- const handleSetArcPath = useCallback(
478
- (animId: string, config: Parameters<typeof setArcPath>[2]) => {
479
- if (!domEditSelection) return;
480
- setArcPath(domEditSelection, animId, config);
481
- },
482
- [domEditSelection, setArcPath],
483
- );
315
+ // ── GSAP-aware geometry intercepts + animated property commit ──
484
316
 
485
- const handleUpdateArcSegment = useCallback(
486
- (animId: string, segmentIndex: number, update: Parameters<typeof updateArcSegment>[3]) => {
487
- if (!domEditSelection) return;
488
- updateArcSegment(domEditSelection, animId, segmentIndex, update);
489
- },
490
- [domEditSelection, updateArcSegment],
491
- );
492
-
493
- useDomEditPreviewSync({
494
- previewIframe,
495
- activeCompPath,
496
- captionEditMode,
497
- domEditSelectionRef,
317
+ const {
318
+ handleGsapAwarePathOffsetCommit,
319
+ handleGsapAwareBoxSizeCommit,
320
+ handleGsapAwareRotationCommit,
321
+ commitAnimatedProperty,
322
+ handleSetArcPath,
323
+ handleUpdateArcSegment,
324
+ commitMutation,
325
+ } = useGsapAwareEditing({
498
326
  domEditSelection,
499
- applyDomSelection,
500
- buildDomSelectionFromTarget,
501
- refreshPreviewDocumentVersion,
502
- syncPreviewHistoryHotkey,
503
- applyStudioManualEditsToPreviewRef,
504
- openSourceForSelection,
505
- getSidebarTab,
327
+ selectedGsapAnimations,
328
+ gsapCommitMutation,
329
+ previewIframeRef,
330
+ showToast,
331
+ bumpGsapCache,
332
+ makeFetchFallback,
333
+ trackGsapInteractionFailure,
334
+ handleDomPathOffsetCommit,
335
+ handleDomBoxSizeCommit,
336
+ handleDomRotationCommit,
337
+ addGsapAnimation,
338
+ convertToKeyframes,
339
+ setArcPath,
340
+ updateArcSegment,
506
341
  });
507
342
 
508
343
  return {
@@ -574,12 +409,6 @@ export function useDomEditSession({
574
409
  handleUpdateArcSegment,
575
410
  invalidateGsapCache: bumpGsapCache,
576
411
  previewIframeRef,
577
- commitMutation: async (
578
- mutation: Record<string, unknown>,
579
- options: { label: string; softReload?: boolean },
580
- ) => {
581
- if (!domEditSelection) return;
582
- await gsapCommitMutation(domEditSelection, mutation, options);
583
- },
412
+ commitMutation,
584
413
  };
585
414
  }
@@ -22,7 +22,7 @@ import {
22
22
  type DomEditSelection,
23
23
  } from "../components/editor/domEditing";
24
24
  import type { ImportedFontAsset } from "../components/editor/fontAssets";
25
- import type { PersistDomEditOperations } from "./useDomEditCommits";
25
+ import type { PersistDomEditOperations } from "./domEditCommitTypes";
26
26
 
27
27
  // ── Types ──
28
28
 
@@ -0,0 +1,255 @@
1
+ /**
2
+ * Wiring layer for DOM edit sessions: click-to-source navigation,
3
+ * DOM selection to timeline sync, GSAP cache invalidation on refresh,
4
+ * GSAP cache population, animation resolution for the selected element,
5
+ * and preview sync side-effects.
6
+ *
7
+ * Extracted from useDomEditSession to isolate orchestration wiring from
8
+ * the GSAP-aware geometry intercept logic.
9
+ */
10
+ import { useCallback, useEffect, useRef } from "react";
11
+ import type { DomEditSelection } from "../components/editor/domEditingTypes";
12
+ import { STUDIO_GSAP_PANEL_ENABLED } from "../components/editor/manualEditingAvailability";
13
+ import { usePlayerStore } from "../player";
14
+ import { useDomEditPreviewSync } from "./useDomEditPreviewSync";
15
+ import { useGsapAnimationsForElement, usePopulateKeyframeCacheForFile } from "./useGsapTweenCache";
16
+ import { useGsapAnimationFetchFallback } from "./useGsapAnimationFetchFallback";
17
+ import { useGsapInteractionFailureTelemetry } from "./useGsapInteractionFailureTelemetry";
18
+ import { useGsapSelectionHandlers } from "./useGsapSelectionHandlers";
19
+ import type { PatchTarget } from "../utils/sourcePatcher";
20
+ import type { SidebarTab } from "../components/sidebar/LeftSidebar";
21
+
22
+ export interface UseDomEditWiringParams {
23
+ projectId: string | null;
24
+ activeCompPath: string | null;
25
+ domEditSelection: DomEditSelection | null;
26
+ domEditSelectionRef: React.MutableRefObject<DomEditSelection | null>;
27
+ previewIframeRef: React.RefObject<HTMLIFrameElement | null>;
28
+ previewIframe: HTMLIFrameElement | null;
29
+ captionEditMode: boolean;
30
+ refreshKey: number;
31
+ gsapCacheVersion: number;
32
+ bumpGsapCache: () => void;
33
+ showToast: (message: string, tone?: "error" | "info") => void;
34
+ refreshPreviewDocumentVersion: () => void;
35
+ syncPreviewHistoryHotkey: (iframe: HTMLIFrameElement | null) => void;
36
+ applyStudioManualEditsToPreviewRef: React.MutableRefObject<
37
+ (iframe: HTMLIFrameElement) => Promise<void>
38
+ >;
39
+ applyDomSelection: (
40
+ selection: DomEditSelection | null,
41
+ options?: { revealPanel?: boolean; preserveGroup?: boolean },
42
+ ) => void;
43
+ buildDomSelectionFromTarget: (element: HTMLElement) => Promise<DomEditSelection | null>;
44
+ openSourceForSelection?: (sourceFile: string, target: PatchTarget) => void;
45
+ selectSidebarTab?: (tab: SidebarTab) => void;
46
+ getSidebarTab?: () => SidebarTab;
47
+ // GSAP script commit ops (from useGsapScriptCommits)
48
+ updateGsapProperty: (
49
+ sel: DomEditSelection,
50
+ animId: string,
51
+ prop: string,
52
+ value: number | string,
53
+ ) => void;
54
+ updateGsapMeta: (
55
+ sel: DomEditSelection,
56
+ animId: string,
57
+ updates: { duration?: number; ease?: string; position?: number },
58
+ ) => void;
59
+ deleteGsapAnimation: (sel: DomEditSelection, animId: string) => void;
60
+ deleteAllForSelector: (sel: DomEditSelection, targetSelector: string) => void;
61
+ addGsapAnimation: (
62
+ sel: DomEditSelection,
63
+ method: "to" | "from" | "set" | "fromTo",
64
+ time: number,
65
+ ) => Promise<void>;
66
+ addGsapProperty: (sel: DomEditSelection, animId: string, prop: string) => void;
67
+ removeGsapProperty: (sel: DomEditSelection, animId: string, prop: string) => void;
68
+ updateGsapFromProperty: (
69
+ sel: DomEditSelection,
70
+ animId: string,
71
+ prop: string,
72
+ value: number | string,
73
+ ) => void;
74
+ addGsapFromProperty: (sel: DomEditSelection, animId: string, prop: string) => void;
75
+ removeGsapFromProperty: (sel: DomEditSelection, animId: string, prop: string) => void;
76
+ addKeyframe: (
77
+ sel: DomEditSelection,
78
+ animId: string,
79
+ percentage: number,
80
+ property: string,
81
+ value: number | string,
82
+ ) => void;
83
+ addKeyframeBatch: (
84
+ sel: DomEditSelection,
85
+ animId: string,
86
+ percentage: number,
87
+ properties: Record<string, number | string>,
88
+ ) => Promise<void>;
89
+ removeKeyframe: (sel: DomEditSelection, animId: string, percentage: number) => void;
90
+ convertToKeyframes: (
91
+ sel: DomEditSelection,
92
+ animId: string,
93
+ resolvedFromValues?: Record<string, number | string>,
94
+ ) => Promise<void>;
95
+ removeAllKeyframes: (sel: DomEditSelection, animId: string) => void;
96
+ handleDomManualEditsReset: (sel: DomEditSelection) => void;
97
+ }
98
+
99
+ // fallow-ignore-next-line complexity
100
+ export function useDomEditWiring({
101
+ projectId,
102
+ activeCompPath,
103
+ domEditSelection,
104
+ domEditSelectionRef,
105
+ previewIframeRef,
106
+ previewIframe,
107
+ captionEditMode,
108
+ refreshKey,
109
+ gsapCacheVersion,
110
+ bumpGsapCache,
111
+ showToast,
112
+ refreshPreviewDocumentVersion,
113
+ syncPreviewHistoryHotkey,
114
+ applyStudioManualEditsToPreviewRef,
115
+ applyDomSelection,
116
+ buildDomSelectionFromTarget,
117
+ openSourceForSelection,
118
+ selectSidebarTab,
119
+ getSidebarTab,
120
+ updateGsapProperty,
121
+ updateGsapMeta,
122
+ deleteGsapAnimation,
123
+ deleteAllForSelector,
124
+ addGsapAnimation,
125
+ addGsapProperty,
126
+ removeGsapProperty,
127
+ updateGsapFromProperty,
128
+ addGsapFromProperty,
129
+ removeGsapFromProperty,
130
+ addKeyframe,
131
+ addKeyframeBatch,
132
+ removeKeyframe,
133
+ convertToKeyframes,
134
+ removeAllKeyframes,
135
+ handleDomManualEditsReset,
136
+ }: UseDomEditWiringParams) {
137
+ // ── Click-to-source navigation ──
138
+
139
+ const onClickToSource = useCallback(
140
+ (selection: DomEditSelection) => {
141
+ if (!openSourceForSelection || !selectSidebarTab) return;
142
+ if (!selection.sourceFile) return;
143
+ selectSidebarTab("code");
144
+ openSourceForSelection(selection.sourceFile, {
145
+ id: selection.id,
146
+ selector: selection.selector,
147
+ selectorIndex: selection.selectorIndex,
148
+ });
149
+ },
150
+ [openSourceForSelection, selectSidebarTab],
151
+ );
152
+
153
+ // ── DOM selection -> timeline element sync ──
154
+
155
+ useEffect(() => {
156
+ if (!domEditSelection?.id) return;
157
+ const { selectedElementId, elements, setSelectedElementId } = usePlayerStore.getState();
158
+ const matchKey = elements.find(
159
+ (el) => el.domId === domEditSelection.id || el.id === domEditSelection.id,
160
+ );
161
+ const key = matchKey ? (matchKey.key ?? matchKey.id) : null;
162
+ if (key && key !== selectedElementId) setSelectedElementId(key);
163
+ }, [domEditSelection?.id]);
164
+
165
+ // ── GSAP cache sync ──
166
+
167
+ // Bump GSAP cache when refreshKey changes (code-tab edits trigger iframe
168
+ // reload via refreshKey but don't go through commitMutation, so the cache
169
+ // would otherwise retain stale keyframe entries).
170
+ const prevRefreshKeyRef = useRef(refreshKey);
171
+ // eslint-disable-next-line no-restricted-syntax
172
+ useEffect(() => {
173
+ if (refreshKey !== prevRefreshKeyRef.current) {
174
+ prevRefreshKeyRef.current = refreshKey;
175
+ bumpGsapCache();
176
+ }
177
+ }, [refreshKey, bumpGsapCache]);
178
+
179
+ const gsapSourceFile = domEditSelection?.sourceFile || activeCompPath || "index.html";
180
+
181
+ usePopulateKeyframeCacheForFile(
182
+ STUDIO_GSAP_PANEL_ENABLED ? (projectId ?? null) : null,
183
+ gsapSourceFile,
184
+ gsapCacheVersion,
185
+ previewIframeRef,
186
+ );
187
+
188
+ const {
189
+ animations: selectedGsapAnimations,
190
+ multipleTimelines: gsapMultipleTimelines,
191
+ unsupportedTimelinePattern: gsapUnsupportedTimelinePattern,
192
+ } = useGsapAnimationsForElement(
193
+ STUDIO_GSAP_PANEL_ENABLED ? (projectId ?? null) : null,
194
+ gsapSourceFile,
195
+ domEditSelection
196
+ ? { id: domEditSelection.id ?? null, selector: domEditSelection.selector ?? null }
197
+ : null,
198
+ gsapCacheVersion,
199
+ );
200
+
201
+ // ── Telemetry & fallback ──
202
+
203
+ const trackGsapInteractionFailure = useGsapInteractionFailureTelemetry(activeCompPath, showToast);
204
+ const makeFetchFallback = useGsapAnimationFetchFallback(projectId, gsapSourceFile);
205
+
206
+ // ── GSAP selection handlers ──
207
+
208
+ const gsapSelectionHandlers = useGsapSelectionHandlers({
209
+ domEditSelection,
210
+ updateGsapProperty,
211
+ updateGsapMeta,
212
+ deleteGsapAnimation,
213
+ deleteAllForSelector,
214
+ addGsapAnimation,
215
+ addGsapProperty,
216
+ removeGsapProperty,
217
+ updateGsapFromProperty,
218
+ addGsapFromProperty,
219
+ removeGsapFromProperty,
220
+ addKeyframe,
221
+ addKeyframeBatch,
222
+ removeKeyframe,
223
+ convertToKeyframes,
224
+ removeAllKeyframes,
225
+ handleDomManualEditsReset,
226
+ selectedGsapAnimations,
227
+ });
228
+
229
+ // ── Preview sync side-effects ──
230
+
231
+ useDomEditPreviewSync({
232
+ previewIframe,
233
+ activeCompPath,
234
+ captionEditMode,
235
+ domEditSelectionRef,
236
+ domEditSelection,
237
+ applyDomSelection,
238
+ buildDomSelectionFromTarget,
239
+ refreshPreviewDocumentVersion,
240
+ syncPreviewHistoryHotkey,
241
+ applyStudioManualEditsToPreviewRef,
242
+ openSourceForSelection,
243
+ getSidebarTab,
244
+ });
245
+
246
+ return {
247
+ onClickToSource,
248
+ selectedGsapAnimations,
249
+ gsapMultipleTimelines,
250
+ gsapUnsupportedTimelinePattern,
251
+ trackGsapInteractionFailure,
252
+ makeFetchFallback,
253
+ ...gsapSelectionHandlers,
254
+ };
255
+ }