@hyperframes/studio 0.6.0-alpha.9 → 0.6.1

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 (111) hide show
  1. package/dist/assets/hyperframes-player-CzwFysqv.js +418 -0
  2. package/dist/assets/index-D1JDq7Gg.css +1 -0
  3. package/dist/assets/index-hYc4aP7M.js +117 -0
  4. package/dist/favicon.svg +14 -0
  5. package/dist/index.html +3 -2
  6. package/package.json +9 -9
  7. package/src/App.tsx +421 -4303
  8. package/src/captions/components/CaptionOverlay.tsx +13 -246
  9. package/src/captions/components/CaptionOverlayUtils.ts +221 -0
  10. package/src/components/AskAgentModal.tsx +120 -0
  11. package/src/components/StudioHeader.tsx +133 -0
  12. package/src/components/StudioLeftSidebar.tsx +125 -0
  13. package/src/components/StudioPreviewArea.tsx +167 -0
  14. package/src/components/StudioRightPanel.tsx +198 -0
  15. package/src/components/TimelineToolbar.tsx +89 -0
  16. package/src/components/editor/DomEditOverlay.tsx +88 -993
  17. package/src/components/editor/EaseCurveEditor.tsx +221 -0
  18. package/src/components/editor/FileTree.tsx +13 -621
  19. package/src/components/editor/FileTreeIcons.tsx +128 -0
  20. package/src/components/editor/FileTreeNodes.tsx +496 -0
  21. package/src/components/editor/MotionPanel.tsx +16 -390
  22. package/src/components/editor/MotionPanelFields.tsx +185 -0
  23. package/src/components/editor/PropertyPanel.test.ts +0 -49
  24. package/src/components/editor/PropertyPanel.tsx +132 -2763
  25. package/src/components/editor/domEditOverlayGeometry.ts +211 -0
  26. package/src/components/editor/domEditOverlayGestures.ts +138 -0
  27. package/src/components/editor/domEditOverlayStartGesture.ts +155 -0
  28. package/src/components/editor/domEditing.ts +44 -1117
  29. package/src/components/editor/domEditingAgentPrompt.ts +97 -0
  30. package/src/components/editor/domEditingDom.ts +266 -0
  31. package/src/components/editor/domEditingElement.ts +329 -0
  32. package/src/components/editor/domEditingLayers.ts +460 -0
  33. package/src/components/editor/domEditingTypes.ts +125 -0
  34. package/src/components/editor/manualEditingAvailability.test.ts +2 -2
  35. package/src/components/editor/manualEditingAvailability.ts +1 -1
  36. package/src/components/editor/manualEdits.ts +84 -1049
  37. package/src/components/editor/manualEditsDom.ts +436 -0
  38. package/src/components/editor/manualEditsParsing.ts +280 -0
  39. package/src/components/editor/manualEditsSnapshot.ts +333 -0
  40. package/src/components/editor/manualEditsTypes.ts +141 -0
  41. package/src/components/editor/propertyPanelColor.tsx +371 -0
  42. package/src/components/editor/propertyPanelFill.tsx +421 -0
  43. package/src/components/editor/propertyPanelFont.tsx +455 -0
  44. package/src/components/editor/propertyPanelHelpers.ts +401 -0
  45. package/src/components/editor/propertyPanelPrimitives.tsx +357 -0
  46. package/src/components/editor/propertyPanelSections.tsx +453 -0
  47. package/src/components/editor/propertyPanelStyleSections.tsx +411 -0
  48. package/src/components/editor/studioMotion.ts +47 -434
  49. package/src/components/editor/studioMotionOps.ts +299 -0
  50. package/src/components/editor/studioMotionTypes.ts +168 -0
  51. package/src/components/editor/useDomEditOverlayGestures.ts +393 -0
  52. package/src/components/editor/useDomEditOverlayRects.ts +207 -0
  53. package/src/components/nle/NLELayout.tsx +68 -155
  54. package/src/components/nle/NLEPreview.tsx +3 -0
  55. package/src/components/nle/useCompositionStack.ts +126 -0
  56. package/src/components/renders/RenderQueue.tsx +102 -31
  57. package/src/components/renders/useRenderQueue.ts +8 -2
  58. package/src/components/sidebar/LeftSidebar.tsx +186 -186
  59. package/src/contexts/DomEditContext.tsx +137 -0
  60. package/src/contexts/FileManagerContext.tsx +110 -0
  61. package/src/contexts/PanelLayoutContext.tsx +68 -0
  62. package/src/contexts/StudioContext.tsx +135 -0
  63. package/src/hooks/useAppHotkeys.ts +326 -0
  64. package/src/hooks/useAskAgentModal.ts +162 -0
  65. package/src/hooks/useCaptionDetection.ts +132 -0
  66. package/src/hooks/useCompositionDimensions.ts +25 -0
  67. package/src/hooks/useConsoleErrorCapture.ts +60 -0
  68. package/src/hooks/useDomEditCommits.ts +437 -0
  69. package/src/hooks/useDomEditSession.ts +342 -0
  70. package/src/hooks/useDomEditTextCommits.ts +330 -0
  71. package/src/hooks/useDomSelection.ts +398 -0
  72. package/src/hooks/useFileManager.ts +431 -0
  73. package/src/hooks/useFrameCapture.ts +77 -0
  74. package/src/hooks/useLintModal.ts +35 -0
  75. package/src/hooks/useManifestPersistence.ts +492 -0
  76. package/src/hooks/usePanelLayout.ts +68 -0
  77. package/src/hooks/usePreviewInteraction.ts +153 -0
  78. package/src/hooks/useRenderClipContent.ts +124 -0
  79. package/src/hooks/useTimelineEditing.ts +472 -0
  80. package/src/hooks/useToast.ts +20 -0
  81. package/src/player/components/Player.tsx +33 -2
  82. package/src/player/components/Timeline.test.ts +0 -8
  83. package/src/player/components/Timeline.tsx +196 -1518
  84. package/src/player/components/TimelineCanvas.tsx +434 -0
  85. package/src/player/components/TimelineClip.tsx +9 -244
  86. package/src/player/components/TimelineEmptyState.tsx +102 -0
  87. package/src/player/components/TimelineRuler.tsx +90 -0
  88. package/src/player/components/timelineIcons.tsx +49 -0
  89. package/src/player/components/timelineLayout.ts +215 -0
  90. package/src/player/components/timelineUtils.ts +211 -0
  91. package/src/player/components/useTimelineClipDrag.ts +388 -0
  92. package/src/player/components/useTimelinePlayhead.ts +200 -0
  93. package/src/player/components/useTimelineRangeSelection.ts +135 -0
  94. package/src/player/hooks/usePlaybackKeyboard.ts +171 -0
  95. package/src/player/hooks/useTimelinePlayer.ts +105 -1371
  96. package/src/player/hooks/useTimelineSyncCallbacks.ts +288 -0
  97. package/src/player/lib/playbackAdapter.ts +145 -0
  98. package/src/player/lib/playbackShortcuts.ts +68 -0
  99. package/src/player/lib/playbackTypes.ts +60 -0
  100. package/src/player/lib/timelineDOM.ts +373 -0
  101. package/src/player/lib/timelineElementHelpers.ts +303 -0
  102. package/src/player/lib/timelineIframeHelpers.ts +269 -0
  103. package/src/utils/domEditHelpers.ts +50 -0
  104. package/src/utils/studioFontHelpers.ts +83 -0
  105. package/src/utils/studioHelpers.ts +214 -0
  106. package/src/utils/studioPreviewHelpers.ts +185 -0
  107. package/src/utils/timelineDiscovery.ts +1 -1
  108. package/dist/assets/hyperframes-player-DjsVzYFP.js +0 -418
  109. package/dist/assets/index-14zH9lqh.css +0 -1
  110. package/dist/assets/index-DYCiFGWQ.js +0 -108
  111. package/src/player/components/TimelineClip.test.ts +0 -92
@@ -0,0 +1,437 @@
1
+ import { useCallback } from "react";
2
+ import { usePlayerStore } from "../player";
3
+ import { FONT_EXT } from "../utils/mediaTypes";
4
+ import { applyPatchByTarget } from "../utils/sourcePatcher";
5
+ import { saveProjectFilesWithHistory } from "../utils/studioFileHistory";
6
+ import { primaryFontFamilyValue } from "../utils/studioFontHelpers";
7
+ import { getDomEditTargetKey, type DomEditSelection } from "../components/editor/domEditing";
8
+ import {
9
+ removeStudioManualEditsForSelection,
10
+ type StudioManualEditManifest,
11
+ upsertStudioBoxSizeEdit,
12
+ upsertStudioPathOffsetEdit,
13
+ upsertStudioRotationEdit,
14
+ } from "../components/editor/manualEdits";
15
+ import {
16
+ removeStudioMotionForSelection,
17
+ type StudioGsapMotion,
18
+ type StudioMotionManifest,
19
+ upsertStudioGsapMotion,
20
+ } from "../components/editor/studioMotion";
21
+ import { fontFamilyFromAssetPath, type ImportedFontAsset } from "../components/editor/fontAssets";
22
+ import type { DomEditGroupPathOffsetCommit } from "../components/editor/DomEditOverlay";
23
+ import type { EditHistoryKind } from "../utils/editHistory";
24
+ import { useDomEditTextCommits } from "./useDomEditTextCommits";
25
+
26
+ // ── Types ──
27
+
28
+ interface RecordEditInput {
29
+ label: string;
30
+ kind: EditHistoryKind;
31
+ coalesceKey?: string;
32
+ files: Record<string, { before: string; after: string }>;
33
+ }
34
+
35
+ export type PersistDomEditOperations = (
36
+ selection: DomEditSelection,
37
+ operations: Parameters<typeof applyPatchByTarget>[2][],
38
+ options?: {
39
+ label?: string;
40
+ coalesceKey?: string;
41
+ skipRefresh?: boolean;
42
+ prepareContent?: (html: string, sourceFile: string) => string;
43
+ shouldSave?: () => boolean;
44
+ },
45
+ ) => Promise<void>;
46
+
47
+ export interface UseDomEditCommitsParams {
48
+ activeCompPath: string | null;
49
+ previewIframeRef: React.MutableRefObject<HTMLIFrameElement | null>;
50
+ showToast: (message: string, tone?: "error" | "info") => void;
51
+ commitStudioManualEditManifestOptimistically: (
52
+ updateManifest: (manifest: StudioManualEditManifest) => StudioManualEditManifest,
53
+ options: { label: string; coalesceKey: string },
54
+ ) => void;
55
+ commitStudioMotionManifestOptimistically: (
56
+ updateManifest: (manifest: StudioMotionManifest) => StudioMotionManifest,
57
+ options: { label: string; coalesceKey: string },
58
+ ) => void;
59
+ applyCurrentStudioManualEditsToPreview: (iframe: HTMLIFrameElement | null) => void;
60
+ applyCurrentStudioMotionToPreview: (iframe: HTMLIFrameElement | null) => void;
61
+ writeProjectFile: (path: string, content: string) => Promise<void>;
62
+ domEditSaveTimestampRef: React.MutableRefObject<number>;
63
+ editHistory: { recordEdit: (entry: RecordEditInput) => Promise<void> };
64
+ fileTree: string[];
65
+ importedFontAssetsRef: React.MutableRefObject<ImportedFontAsset[]>;
66
+ projectId: string | null;
67
+ projectIdRef: React.MutableRefObject<string | null>;
68
+ reloadPreview: () => void;
69
+
70
+ // From useDomSelection
71
+ domEditSelection: DomEditSelection | null;
72
+ domEditSelectionRef: React.MutableRefObject<DomEditSelection | null>;
73
+ domEditGroupSelectionsRef: React.MutableRefObject<DomEditSelection[]>;
74
+ applyDomSelection: (
75
+ selection: DomEditSelection | null,
76
+ options?: { revealPanel?: boolean; additive?: boolean; preserveGroup?: boolean },
77
+ ) => void;
78
+ clearDomSelection: () => void;
79
+ refreshDomEditSelectionFromPreview: (selection: DomEditSelection) => void;
80
+ refreshDomEditGroupSelectionsFromPreview: (selections: DomEditSelection[]) => void;
81
+ buildDomSelectionFromTarget: (
82
+ target: HTMLElement,
83
+ options?: { preferClipAncestor?: boolean },
84
+ ) => DomEditSelection | null;
85
+ }
86
+
87
+ // ── Hook ──
88
+
89
+ export function useDomEditCommits({
90
+ activeCompPath,
91
+ previewIframeRef,
92
+ showToast,
93
+ commitStudioManualEditManifestOptimistically,
94
+ commitStudioMotionManifestOptimistically,
95
+ applyCurrentStudioManualEditsToPreview,
96
+ applyCurrentStudioMotionToPreview,
97
+ writeProjectFile,
98
+ domEditSaveTimestampRef,
99
+ editHistory,
100
+ fileTree,
101
+ importedFontAssetsRef,
102
+ projectId,
103
+ projectIdRef,
104
+ reloadPreview,
105
+ domEditSelection,
106
+ domEditGroupSelectionsRef,
107
+ applyDomSelection,
108
+ clearDomSelection,
109
+ refreshDomEditSelectionFromPreview,
110
+ refreshDomEditGroupSelectionsFromPreview,
111
+ buildDomSelectionFromTarget,
112
+ }: UseDomEditCommitsParams) {
113
+ const resolveImportedFontAsset = useCallback(
114
+ (fontFamilyValue: string): ImportedFontAsset | null => {
115
+ const family = primaryFontFamilyValue(fontFamilyValue);
116
+ if (!family) return null;
117
+ const imported = importedFontAssetsRef.current.find(
118
+ (font) => font.family.toLowerCase() === family.toLowerCase(),
119
+ );
120
+ if (imported) return imported;
121
+ const asset = fileTree.find(
122
+ (path) =>
123
+ FONT_EXT.test(path) &&
124
+ fontFamilyFromAssetPath(path).toLowerCase() === family.toLowerCase(),
125
+ );
126
+ if (!asset) return null;
127
+ return {
128
+ family: fontFamilyFromAssetPath(asset),
129
+ path: asset,
130
+ url: `/api/projects/${projectId}/preview/${asset}`,
131
+ };
132
+ },
133
+ [fileTree, projectId, importedFontAssetsRef],
134
+ );
135
+
136
+ const persistDomEditOperations: PersistDomEditOperations = useCallback(
137
+ async (selection, operations, options) => {
138
+ const pid = projectIdRef.current;
139
+ if (!pid) throw new Error("No active project");
140
+ if (options?.shouldSave && !options.shouldSave()) return;
141
+
142
+ const targetPath = selection.sourceFile || activeCompPath || "index.html";
143
+ const response = await fetch(`/api/projects/${pid}/files/${encodeURIComponent(targetPath)}`);
144
+ if (!response.ok) {
145
+ throw new Error(`Failed to read ${targetPath}`);
146
+ }
147
+
148
+ const data = (await response.json()) as { content?: string };
149
+ const originalContent = data.content;
150
+ if (typeof originalContent !== "string") {
151
+ throw new Error(`Missing file contents for ${targetPath}`);
152
+ }
153
+
154
+ let patchedContent = originalContent;
155
+ for (const operation of operations) {
156
+ patchedContent = applyPatchByTarget(patchedContent, selection, operation);
157
+ }
158
+ if (options?.prepareContent) {
159
+ patchedContent = options.prepareContent(patchedContent, targetPath);
160
+ }
161
+ if (options?.shouldSave && !options.shouldSave()) return;
162
+
163
+ if (patchedContent === originalContent) {
164
+ throw new Error(`Unable to patch ${selection.selector ?? selection.id ?? "selection"}`);
165
+ }
166
+
167
+ await saveProjectFilesWithHistory({
168
+ projectId: pid,
169
+ label: options?.label ?? "Edit layer",
170
+ kind: "manual",
171
+ coalesceKey: options?.coalesceKey,
172
+ files: { [targetPath]: patchedContent },
173
+ readFile: async () => originalContent,
174
+ writeFile: writeProjectFile,
175
+ recordEdit: editHistory.recordEdit,
176
+ });
177
+
178
+ if (options?.skipRefresh) {
179
+ domEditSaveTimestampRef.current = Date.now();
180
+ } else {
181
+ reloadPreview();
182
+ }
183
+ },
184
+ [
185
+ activeCompPath,
186
+ editHistory.recordEdit,
187
+ writeProjectFile,
188
+ projectIdRef,
189
+ domEditSaveTimestampRef,
190
+ reloadPreview,
191
+ ],
192
+ );
193
+
194
+ // ── Text & style commits (delegated to useDomEditTextCommits) ──
195
+
196
+ const {
197
+ handleDomStyleCommit,
198
+ handleDomTextCommit,
199
+ commitDomTextFields,
200
+ handleDomTextFieldStyleCommit,
201
+ handleDomAddTextField,
202
+ handleDomRemoveTextField,
203
+ } = useDomEditTextCommits({
204
+ activeCompPath,
205
+ previewIframeRef,
206
+ domEditSelection,
207
+ applyDomSelection,
208
+ refreshDomEditSelectionFromPreview,
209
+ buildDomSelectionFromTarget,
210
+ persistDomEditOperations,
211
+ resolveImportedFontAsset,
212
+ });
213
+
214
+ // ── Manifest commits ──
215
+
216
+ const handleDomPathOffsetCommit = useCallback(
217
+ (selection: DomEditSelection, next: { x: number; y: number }) => {
218
+ commitStudioManualEditManifestOptimistically(
219
+ (manifest) => upsertStudioPathOffsetEdit(manifest, selection, next),
220
+ {
221
+ label: "Move layer",
222
+ coalesceKey: `path-offset:${getDomEditTargetKey(selection)}`,
223
+ },
224
+ );
225
+ refreshDomEditSelectionFromPreview(selection);
226
+ },
227
+ [commitStudioManualEditManifestOptimistically, refreshDomEditSelectionFromPreview],
228
+ );
229
+
230
+ const handleDomGroupPathOffsetCommit = useCallback(
231
+ (updates: DomEditGroupPathOffsetCommit[]) => {
232
+ if (updates.length === 0) return;
233
+ const coalesceKey = updates
234
+ .map((update) => getDomEditTargetKey(update.selection))
235
+ .sort()
236
+ .join(":");
237
+ commitStudioManualEditManifestOptimistically(
238
+ (manifest) =>
239
+ updates.reduce(
240
+ (nextManifest, update) =>
241
+ upsertStudioPathOffsetEdit(nextManifest, update.selection, update.next),
242
+ manifest,
243
+ ),
244
+ {
245
+ label: `Move ${updates.length} layers`,
246
+ coalesceKey: `group-path-offset:${coalesceKey}`,
247
+ },
248
+ );
249
+ refreshDomEditGroupSelectionsFromPreview(domEditGroupSelectionsRef.current);
250
+ },
251
+ [
252
+ commitStudioManualEditManifestOptimistically,
253
+ domEditGroupSelectionsRef,
254
+ refreshDomEditGroupSelectionsFromPreview,
255
+ ],
256
+ );
257
+
258
+ const handleDomBoxSizeCommit = useCallback(
259
+ (selection: DomEditSelection, next: { width: number; height: number }) => {
260
+ commitStudioManualEditManifestOptimistically(
261
+ (manifest) => upsertStudioBoxSizeEdit(manifest, selection, next),
262
+ {
263
+ label: "Resize layer box",
264
+ coalesceKey: `box-size:${getDomEditTargetKey(selection)}`,
265
+ },
266
+ );
267
+ refreshDomEditSelectionFromPreview(selection);
268
+ },
269
+ [commitStudioManualEditManifestOptimistically, refreshDomEditSelectionFromPreview],
270
+ );
271
+
272
+ const handleDomRotationCommit = useCallback(
273
+ (selection: DomEditSelection, next: { angle: number }) => {
274
+ commitStudioManualEditManifestOptimistically(
275
+ (manifest) => upsertStudioRotationEdit(manifest, selection, next),
276
+ {
277
+ label: "Rotate layer",
278
+ coalesceKey: `rotation:${getDomEditTargetKey(selection)}`,
279
+ },
280
+ );
281
+ refreshDomEditSelectionFromPreview(selection);
282
+ },
283
+ [commitStudioManualEditManifestOptimistically, refreshDomEditSelectionFromPreview],
284
+ );
285
+
286
+ const handleDomManualEditsReset = useCallback(
287
+ (selection: DomEditSelection) => {
288
+ commitStudioManualEditManifestOptimistically(
289
+ (manifest) => removeStudioManualEditsForSelection(manifest, selection),
290
+ {
291
+ label: "Reset layer edits",
292
+ coalesceKey: `manual-reset:${getDomEditTargetKey(selection)}`,
293
+ },
294
+ );
295
+ applyCurrentStudioManualEditsToPreview(previewIframeRef.current);
296
+ refreshDomEditSelectionFromPreview(selection);
297
+ },
298
+ [
299
+ applyCurrentStudioManualEditsToPreview,
300
+ commitStudioManualEditManifestOptimistically,
301
+ refreshDomEditSelectionFromPreview,
302
+ previewIframeRef,
303
+ ],
304
+ );
305
+
306
+ const handleDomMotionCommit = useCallback(
307
+ (
308
+ selection: DomEditSelection,
309
+ motion: Omit<StudioGsapMotion, "kind" | "target" | "updatedAt">,
310
+ ) => {
311
+ commitStudioMotionManifestOptimistically(
312
+ (manifest) => upsertStudioGsapMotion(manifest, selection, motion),
313
+ {
314
+ label: "Set GSAP motion",
315
+ coalesceKey: `motion:${getDomEditTargetKey(selection)}`,
316
+ },
317
+ );
318
+ refreshDomEditSelectionFromPreview(selection);
319
+ },
320
+ [commitStudioMotionManifestOptimistically, refreshDomEditSelectionFromPreview],
321
+ );
322
+
323
+ const handleDomMotionClear = useCallback(
324
+ (selection: DomEditSelection) => {
325
+ commitStudioMotionManifestOptimistically(
326
+ (manifest) => removeStudioMotionForSelection(manifest, selection),
327
+ {
328
+ label: "Clear GSAP motion",
329
+ coalesceKey: `motion:${getDomEditTargetKey(selection)}`,
330
+ },
331
+ );
332
+ applyCurrentStudioMotionToPreview(previewIframeRef.current);
333
+ refreshDomEditSelectionFromPreview(selection);
334
+ },
335
+ [
336
+ applyCurrentStudioMotionToPreview,
337
+ commitStudioMotionManifestOptimistically,
338
+ refreshDomEditSelectionFromPreview,
339
+ previewIframeRef,
340
+ ],
341
+ );
342
+
343
+ const handleDomEditElementDelete = useCallback(
344
+ async (selection: DomEditSelection) => {
345
+ const pid = projectIdRef.current;
346
+ if (!pid) return;
347
+ const label = selection.label || selection.id || selection.selector || selection.tagName;
348
+
349
+ const targetPath = selection.sourceFile || activeCompPath || "index.html";
350
+ try {
351
+ const response = await fetch(
352
+ `/api/projects/${pid}/files/${encodeURIComponent(targetPath)}`,
353
+ );
354
+ if (!response.ok) throw new Error(`Failed to read ${targetPath}`);
355
+
356
+ const data = (await response.json()) as { content?: string };
357
+ const originalContent = data.content;
358
+ if (typeof originalContent !== "string")
359
+ throw new Error(`Missing file contents for ${targetPath}`);
360
+
361
+ const patchTarget: { id?: string; selector?: string; selectorIndex?: number } = selection.id
362
+ ? {
363
+ id: selection.id,
364
+ selector: selection.selector,
365
+ selectorIndex: selection.selectorIndex,
366
+ }
367
+ : selection.selector
368
+ ? { selector: selection.selector, selectorIndex: selection.selectorIndex }
369
+ : ({} as never);
370
+ if (!patchTarget.id && !patchTarget.selector) {
371
+ throw new Error("Selected element has no patchable target");
372
+ }
373
+
374
+ const removeResponse = await fetch(
375
+ `/api/projects/${pid}/file-mutations/remove-element/${encodeURIComponent(targetPath)}`,
376
+ {
377
+ method: "POST",
378
+ headers: { "Content-Type": "application/json" },
379
+ body: JSON.stringify({ target: patchTarget }),
380
+ },
381
+ );
382
+ if (!removeResponse.ok) throw new Error(`Failed to delete element from ${targetPath}`);
383
+
384
+ const removeData = (await removeResponse.json()) as { changed?: boolean; content?: string };
385
+ const patchedContent =
386
+ typeof removeData.content === "string" ? removeData.content : originalContent;
387
+
388
+ domEditSaveTimestampRef.current = Date.now();
389
+ await saveProjectFilesWithHistory({
390
+ projectId: pid,
391
+ label: "Delete element",
392
+ kind: "timeline",
393
+ files: { [targetPath]: patchedContent },
394
+ readFile: async () => originalContent,
395
+ writeFile: writeProjectFile,
396
+ recordEdit: editHistory.recordEdit,
397
+ });
398
+
399
+ clearDomSelection();
400
+ usePlayerStore.getState().setSelectedElementId(null);
401
+ reloadPreview();
402
+ showToast(`Deleted ${label}. Use Undo to restore it.`, "info");
403
+ } catch (error) {
404
+ const message = error instanceof Error ? error.message : "Failed to delete element";
405
+ showToast(message);
406
+ }
407
+ },
408
+ [
409
+ activeCompPath,
410
+ clearDomSelection,
411
+ domEditSaveTimestampRef,
412
+ editHistory.recordEdit,
413
+ projectIdRef,
414
+ reloadPreview,
415
+ showToast,
416
+ writeProjectFile,
417
+ ],
418
+ );
419
+
420
+ return {
421
+ resolveImportedFontAsset,
422
+ handleDomStyleCommit,
423
+ handleDomTextCommit,
424
+ commitDomTextFields,
425
+ handleDomTextFieldStyleCommit,
426
+ handleDomAddTextField,
427
+ handleDomRemoveTextField,
428
+ handleDomPathOffsetCommit,
429
+ handleDomGroupPathOffsetCommit,
430
+ handleDomBoxSizeCommit,
431
+ handleDomRotationCommit,
432
+ handleDomManualEditsReset,
433
+ handleDomMotionCommit,
434
+ handleDomMotionClear,
435
+ handleDomEditElementDelete,
436
+ };
437
+ }