@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
@@ -1,1022 +1,85 @@
1
- import type { DomEditSelection } from "./domEditing";
2
-
3
- export const STUDIO_MANUAL_EDITS_PATH = ".hyperframes/studio-manual-edits.json";
4
- export const STUDIO_OFFSET_X_PROP = "--hf-studio-offset-x";
5
- export const STUDIO_OFFSET_Y_PROP = "--hf-studio-offset-y";
6
- export const STUDIO_WIDTH_PROP = "--hf-studio-width";
7
- export const STUDIO_HEIGHT_PROP = "--hf-studio-height";
8
- export const STUDIO_ROTATION_PROP = "--hf-studio-rotation";
9
- const STUDIO_PATH_OFFSET_ATTR = "data-hf-studio-path-offset";
10
- const STUDIO_MANUAL_EDIT_GESTURE_ATTR = "data-hf-studio-manual-edit-gesture";
11
- const STUDIO_BOX_SIZE_ATTR = "data-hf-studio-box-size";
12
- const STUDIO_ROTATION_ATTR = "data-hf-studio-rotation";
13
- const STUDIO_ORIGINAL_TRANSLATE_ATTR = "data-hf-studio-original-translate";
14
- const STUDIO_ORIGINAL_INLINE_TRANSLATE_ATTR = "data-hf-studio-original-inline-translate";
15
- const STUDIO_ORIGINAL_WIDTH_ATTR = "data-hf-studio-original-width";
16
- const STUDIO_ORIGINAL_HEIGHT_ATTR = "data-hf-studio-original-height";
17
- const STUDIO_ORIGINAL_MIN_WIDTH_ATTR = "data-hf-studio-original-min-width";
18
- const STUDIO_ORIGINAL_MIN_HEIGHT_ATTR = "data-hf-studio-original-min-height";
19
- const STUDIO_ORIGINAL_MAX_WIDTH_ATTR = "data-hf-studio-original-max-width";
20
- const STUDIO_ORIGINAL_MAX_HEIGHT_ATTR = "data-hf-studio-original-max-height";
21
- const STUDIO_ORIGINAL_FLEX_BASIS_ATTR = "data-hf-studio-original-flex-basis";
22
- const STUDIO_ORIGINAL_FLEX_GROW_ATTR = "data-hf-studio-original-flex-grow";
23
- const STUDIO_ORIGINAL_FLEX_SHRINK_ATTR = "data-hf-studio-original-flex-shrink";
24
- const STUDIO_ORIGINAL_BOX_SIZING_ATTR = "data-hf-studio-original-box-sizing";
25
- const STUDIO_ORIGINAL_SCALE_ATTR = "data-hf-studio-original-scale";
26
- const STUDIO_ORIGINAL_TRANSFORM_ORIGIN_ATTR = "data-hf-studio-original-transform-origin";
27
- const STUDIO_ORIGINAL_DISPLAY_ATTR = "data-hf-studio-original-display";
28
- const STUDIO_ORIGINAL_ROTATE_ATTR = "data-hf-studio-original-rotate";
29
- const STUDIO_ORIGINAL_INLINE_ROTATE_ATTR = "data-hf-studio-original-inline-rotate";
30
- const STUDIO_ORIGINAL_ROTATION_TRANSFORM_ORIGIN_ATTR =
31
- "data-hf-studio-original-rotation-transform-origin";
32
- const STUDIO_ROTATION_DRAFT_ATTR = "data-hf-studio-rotation-draft";
33
- const STUDIO_MANUAL_EDITS_APPLY_PROP = "__hfStudioManualEditsApply";
34
- const STUDIO_MANUAL_EDITS_WRAPPED_PROP = "__hfStudioManualEditsWrapped";
35
- const STUDIO_MANUAL_EDITS_PLAYBACK_FRAME_PROP = "__hfStudioManualEditsPlaybackFrame";
36
- const STUDIO_ROTATION_TRANSFORM_ORIGIN = "center center";
37
- let studioManualEditGestureId = 0;
38
-
39
- export interface StudioManualEditTarget {
40
- sourceFile: string;
41
- selector?: string;
42
- selectorIndex?: number;
43
- id?: string;
44
- }
45
-
46
- export interface StudioPathOffsetEdit {
47
- kind: "path-offset";
48
- target: StudioManualEditTarget;
49
- x: number;
50
- y: number;
51
- updatedAt?: string;
52
- }
53
-
54
- export interface StudioBoxSizeEdit {
55
- kind: "box-size";
56
- target: StudioManualEditTarget;
57
- width: number;
58
- height: number;
59
- updatedAt?: string;
60
- }
61
-
62
- export interface StudioRotationEdit {
63
- kind: "rotation";
64
- target: StudioManualEditTarget;
65
- angle: number;
66
- updatedAt?: string;
67
- }
68
-
69
- export type StudioManualEdit = StudioPathOffsetEdit | StudioBoxSizeEdit | StudioRotationEdit;
70
-
71
- export interface StudioManualEditManifest {
72
- version: 1;
73
- edits: StudioManualEdit[];
74
- }
75
-
76
- type StudioManualEditSeekWindow = Window & {
77
- __hf?: Record<string, unknown>;
78
- __player?: Record<string, unknown>;
79
- __timeline?: Record<string, unknown>;
80
- __timelines?: Record<string, Record<string, unknown>>;
81
- __hfStudioManualEditsApply?: () => void;
82
- __hfStudioManualEditsPlaybackFrame?: number | null;
83
- };
84
-
85
- export function emptyStudioManualEditManifest(): StudioManualEditManifest {
86
- return { version: 1, edits: [] };
87
- }
88
-
89
- function finiteNumber(value: unknown): number | null {
90
- return typeof value === "number" && Number.isFinite(value) ? value : null;
91
- }
92
-
93
- function parsePathOffsetEdit(value: unknown): StudioPathOffsetEdit | null {
94
- if (!value || typeof value !== "object") return null;
95
- const record = value as Record<string, unknown>;
96
- if (record.kind !== "path-offset") return null;
97
- const target = record.target;
98
- if (!target || typeof target !== "object") return null;
99
- const targetRecord = target as Record<string, unknown>;
100
- const sourceFile = typeof targetRecord.sourceFile === "string" ? targetRecord.sourceFile : "";
101
- if (!sourceFile) return null;
102
-
103
- const selector = typeof targetRecord.selector === "string" ? targetRecord.selector : undefined;
104
- const id = typeof targetRecord.id === "string" ? targetRecord.id : undefined;
105
- if (!selector && !id) return null;
106
-
107
- const x = finiteNumber(record.x);
108
- const y = finiteNumber(record.y);
109
- if (x == null || y == null) return null;
110
-
111
- return {
112
- kind: "path-offset",
113
- target: {
114
- sourceFile,
115
- selector,
116
- selectorIndex: finiteNumber(targetRecord.selectorIndex) ?? undefined,
117
- id,
118
- },
119
- x,
120
- y,
121
- updatedAt: typeof record.updatedAt === "string" ? record.updatedAt : undefined,
122
- };
123
- }
124
-
125
- function parseBoxSizeEdit(value: unknown): StudioBoxSizeEdit | null {
126
- if (!value || typeof value !== "object") return null;
127
- const record = value as Record<string, unknown>;
128
- if (record.kind !== "box-size") return null;
129
- const target = record.target;
130
- if (!target || typeof target !== "object") return null;
131
- const targetRecord = target as Record<string, unknown>;
132
- const sourceFile = typeof targetRecord.sourceFile === "string" ? targetRecord.sourceFile : "";
133
- if (!sourceFile) return null;
134
-
135
- const selector = typeof targetRecord.selector === "string" ? targetRecord.selector : undefined;
136
- const id = typeof targetRecord.id === "string" ? targetRecord.id : undefined;
137
- if (!selector && !id) return null;
138
-
139
- const width = finiteNumber(record.width);
140
- const height = finiteNumber(record.height);
141
- if (width == null || height == null || width <= 0 || height <= 0) return null;
142
-
143
- return {
144
- kind: "box-size",
145
- target: {
146
- sourceFile,
147
- selector,
148
- selectorIndex: finiteNumber(targetRecord.selectorIndex) ?? undefined,
149
- id,
150
- },
151
- width,
152
- height,
153
- updatedAt: typeof record.updatedAt === "string" ? record.updatedAt : undefined,
154
- };
155
- }
156
-
157
- function parseRotationEdit(value: unknown): StudioRotationEdit | null {
158
- if (!value || typeof value !== "object") return null;
159
- const record = value as Record<string, unknown>;
160
- if (record.kind !== "rotation") return null;
161
- const target = record.target;
162
- if (!target || typeof target !== "object") return null;
163
- const targetRecord = target as Record<string, unknown>;
164
- const sourceFile = typeof targetRecord.sourceFile === "string" ? targetRecord.sourceFile : "";
165
- if (!sourceFile) return null;
166
-
167
- const selector = typeof targetRecord.selector === "string" ? targetRecord.selector : undefined;
168
- const id = typeof targetRecord.id === "string" ? targetRecord.id : undefined;
169
- if (!selector && !id) return null;
170
-
171
- const angle = finiteNumber(record.angle);
172
- if (angle == null) return null;
173
-
174
- return {
175
- kind: "rotation",
176
- target: {
177
- sourceFile,
178
- selector,
179
- selectorIndex: finiteNumber(targetRecord.selectorIndex) ?? undefined,
180
- id,
181
- },
182
- angle,
183
- updatedAt: typeof record.updatedAt === "string" ? record.updatedAt : undefined,
184
- };
185
- }
186
-
187
- function parseManualEdit(value: unknown): StudioManualEdit | null {
188
- return parsePathOffsetEdit(value) ?? parseBoxSizeEdit(value) ?? parseRotationEdit(value);
189
- }
190
-
191
- export function parseStudioManualEditManifest(content: string): StudioManualEditManifest {
192
- if (!content.trim()) return emptyStudioManualEditManifest();
193
-
194
- try {
195
- const parsed = JSON.parse(content) as unknown;
196
- if (!parsed || typeof parsed !== "object") return emptyStudioManualEditManifest();
197
- const edits = (parsed as { edits?: unknown }).edits;
198
- if (!Array.isArray(edits)) return emptyStudioManualEditManifest();
199
- return {
200
- version: 1,
201
- edits: edits.map(parseManualEdit).filter((edit): edit is StudioManualEdit => edit !== null),
202
- };
203
- } catch {
204
- return emptyStudioManualEditManifest();
205
- }
206
- }
207
-
208
- export function serializeStudioManualEditManifest(manifest: StudioManualEditManifest): string {
209
- return `${JSON.stringify(manifest, null, 2)}\n`;
210
- }
211
-
212
- function normalizeStudioFileChangePath(path: string): string {
213
- return path
214
- .trim()
215
- .replace(/\\/g, "/")
216
- .replace(/^\.?\//, "");
217
- }
218
-
219
- function readStudioFileChangePathFromValue(value: unknown): string | null {
220
- if (typeof value === "string") {
221
- const trimmed = value.trim();
222
- if (!trimmed) return null;
223
- if (trimmed.startsWith("{")) {
224
- try {
225
- return readStudioFileChangePathFromValue(JSON.parse(trimmed) as unknown);
226
- } catch {
227
- return normalizeStudioFileChangePath(trimmed);
228
- }
229
- }
230
- return normalizeStudioFileChangePath(trimmed);
231
- }
232
-
233
- if (!value || typeof value !== "object") return null;
234
- const record = value as Record<string, unknown>;
235
- if (typeof record.path === "string") return normalizeStudioFileChangePath(record.path);
236
- if (typeof record.filePath === "string") return normalizeStudioFileChangePath(record.filePath);
237
- if ("data" in record) return readStudioFileChangePathFromValue(record.data);
238
- return null;
239
- }
240
-
241
- export function readStudioFileChangePath(payload: unknown): string | null {
242
- return readStudioFileChangePathFromValue(payload);
243
- }
244
-
245
- export function isStudioManualEditManifestPath(path: string | null): boolean {
246
- if (!path) return false;
247
- const normalized = normalizeStudioFileChangePath(path);
248
- return (
249
- normalized === STUDIO_MANUAL_EDITS_PATH || normalized.endsWith(`/${STUDIO_MANUAL_EDITS_PATH}`)
250
- );
251
- }
252
-
253
- function selectionTarget(selection: DomEditSelection): StudioManualEditTarget {
254
- return {
255
- sourceFile: selection.sourceFile || "index.html",
256
- selector: selection.selector,
257
- selectorIndex: selection.selectorIndex,
258
- id: selection.id ?? undefined,
259
- };
260
- }
261
-
262
- function targetKey(target: StudioManualEditTarget): string {
263
- return [
264
- target.sourceFile,
265
- target.id ?? "",
266
- target.selector ?? "",
267
- target.selectorIndex ?? "",
268
- ].join("|");
269
- }
270
-
271
- function roundRotationAngle(angle: number): number {
272
- return Math.round(angle * 10) / 10;
273
- }
274
-
275
- export function upsertStudioPathOffsetEdit(
276
- manifest: StudioManualEditManifest,
277
- selection: DomEditSelection,
278
- offset: { x: number; y: number },
279
- ): StudioManualEditManifest {
280
- const target = selectionTarget(selection);
281
- const key = targetKey(target);
282
- const nextEdit: StudioPathOffsetEdit = {
283
- kind: "path-offset",
284
- target,
285
- x: Math.round(offset.x),
286
- y: Math.round(offset.y),
287
- updatedAt: new Date().toISOString(),
288
- };
289
-
290
- const edits = manifest.edits.filter(
291
- (edit) => edit.kind !== "path-offset" || targetKey(edit.target) !== key,
292
- );
293
- edits.push(nextEdit);
294
- return { version: 1, edits };
295
- }
296
-
297
- export function upsertStudioBoxSizeEdit(
298
- manifest: StudioManualEditManifest,
299
- selection: DomEditSelection,
300
- size: { width: number; height: number },
301
- ): StudioManualEditManifest {
302
- const target = selectionTarget(selection);
303
- const key = targetKey(target);
304
- const nextEdit: StudioBoxSizeEdit = {
305
- kind: "box-size",
306
- target,
307
- width: Math.round(Math.max(1, size.width)),
308
- height: Math.round(Math.max(1, size.height)),
309
- updatedAt: new Date().toISOString(),
310
- };
311
-
312
- const edits = manifest.edits.filter(
313
- (edit) => edit.kind !== "box-size" || targetKey(edit.target) !== key,
314
- );
315
- edits.push(nextEdit);
316
- return { version: 1, edits };
317
- }
318
-
319
- export function upsertStudioRotationEdit(
320
- manifest: StudioManualEditManifest,
321
- selection: DomEditSelection,
322
- rotation: { angle: number },
323
- ): StudioManualEditManifest {
324
- const target = selectionTarget(selection);
325
- const key = targetKey(target);
326
- const nextEdit: StudioRotationEdit = {
327
- kind: "rotation",
328
- target,
329
- angle: roundRotationAngle(rotation.angle),
330
- updatedAt: new Date().toISOString(),
331
- };
332
-
333
- const edits = manifest.edits.filter(
334
- (edit) => edit.kind !== "rotation" || targetKey(edit.target) !== key,
335
- );
336
- edits.push(nextEdit);
337
- return { version: 1, edits };
338
- }
339
-
340
- export function removeStudioManualEditsForSelection(
341
- manifest: StudioManualEditManifest,
342
- selection: DomEditSelection,
343
- ): StudioManualEditManifest {
344
- const key = targetKey(selectionTarget(selection));
345
- const edits = manifest.edits.filter((edit) => targetKey(edit.target) !== key);
346
- if (edits.length === manifest.edits.length) return manifest;
347
- return { version: 1, edits };
348
- }
349
-
350
- function readPxCustomProperty(element: HTMLElement, property: string): number {
351
- const value = Number.parseFloat(element.style.getPropertyValue(property));
352
- return Number.isFinite(value) ? value : 0;
353
- }
354
-
355
- function readDegreeCustomProperty(element: HTMLElement, property: string): number {
356
- const value = Number.parseFloat(element.style.getPropertyValue(property));
357
- return Number.isFinite(value) ? value : 0;
358
- }
359
-
360
- export function readStudioPathOffset(element: HTMLElement): { x: number; y: number } {
361
- return {
362
- x: readPxCustomProperty(element, STUDIO_OFFSET_X_PROP),
363
- y: readPxCustomProperty(element, STUDIO_OFFSET_Y_PROP),
364
- };
365
- }
366
-
367
- export function readStudioBoxSize(element: HTMLElement): { width: number; height: number } {
368
- return {
369
- width: readPxCustomProperty(element, STUDIO_WIDTH_PROP),
370
- height: readPxCustomProperty(element, STUDIO_HEIGHT_PROP),
371
- };
372
- }
373
-
374
- export function readStudioRotation(element: HTMLElement): { angle: number } {
375
- return {
376
- angle: readDegreeCustomProperty(element, STUDIO_ROTATION_PROP),
377
- };
378
- }
379
-
380
- export function beginStudioManualEditGesture(element: HTMLElement): string {
381
- studioManualEditGestureId += 1;
382
- const token = `gesture-${studioManualEditGestureId}`;
383
- element.setAttribute(STUDIO_MANUAL_EDIT_GESTURE_ATTR, token);
384
- return token;
385
- }
386
-
387
- export function endStudioManualEditGesture(element: HTMLElement, token?: string): void {
388
- if (token && element.getAttribute(STUDIO_MANUAL_EDIT_GESTURE_ATTR) !== token) return;
389
- element.removeAttribute(STUDIO_MANUAL_EDIT_GESTURE_ATTR);
390
- }
391
-
392
- function isStudioManualEditGestureActive(element: HTMLElement): boolean {
393
- return element.hasAttribute(STUDIO_MANUAL_EDIT_GESTURE_ATTR);
394
- }
395
-
396
- export function isStudioManualEditGestureCurrent(element: HTMLElement, token: string): boolean {
397
- return element.getAttribute(STUDIO_MANUAL_EDIT_GESTURE_ATTR) === token;
398
- }
399
-
400
- function splitTopLevelWhitespace(value: string): string[] {
401
- const parts: string[] = [];
402
- let depth = 0;
403
- let current = "";
404
- for (const char of value.trim()) {
405
- if (char === "(") depth += 1;
406
- if (char === ")") depth = Math.max(0, depth - 1);
407
- if (/\s/.test(char) && depth === 0) {
408
- if (current) parts.push(current);
409
- current = "";
410
- } else {
411
- current += char;
412
- }
413
- }
414
- if (current) parts.push(current);
415
- return parts;
416
- }
417
-
418
- function composeTranslateValue(element: HTMLElement, x: string, y: string): string {
419
- const original = element.getAttribute(STUDIO_ORIGINAL_TRANSLATE_ATTR)?.trim();
420
- if (!original || original === "none") return `${x} ${y}`;
421
-
422
- const parts = splitTopLevelWhitespace(original);
423
- if (parts.length === 1) return `calc(${parts[0]} + ${x}) ${y}`;
424
- if (parts.length === 2) return `calc(${parts[0]} + ${x}) calc(${parts[1]} + ${y})`;
425
- if (parts.length === 3) {
426
- return `calc(${parts[0]} + ${x}) calc(${parts[1]} + ${y}) ${parts[2]}`;
427
- }
428
- return `${x} ${y}`;
429
- }
430
-
431
- function readTransformLonghandBase(element: HTMLElement, property: "translate" | "rotate"): string {
432
- const value = readStyleOrComputed(element, property).trim();
433
- return value === "none" ? "" : value;
434
- }
435
-
436
- function prepareStudioPathOffsetBase(element: HTMLElement, updateBase: boolean): void {
437
- const inlineTranslate = element.style.getPropertyValue("translate");
438
- const currentTranslate = readTransformLonghandBase(element, "translate");
439
- const hasMarker = element.hasAttribute(STUDIO_PATH_OFFSET_ATTR);
440
- const wasResetByAnimation = !styleUsesStudioOffset(currentTranslate);
441
- if (!hasMarker) {
442
- element.setAttribute(
443
- STUDIO_ORIGINAL_INLINE_TRANSLATE_ATTR,
444
- styleUsesStudioOffset(inlineTranslate) ? "" : inlineTranslate,
445
- );
446
- element.setAttribute(
447
- STUDIO_ORIGINAL_TRANSLATE_ATTR,
448
- wasResetByAnimation ? currentTranslate : "",
449
- );
450
- } else if (updateBase && wasResetByAnimation && !isStudioManualEditGestureActive(element)) {
451
- element.setAttribute(STUDIO_ORIGINAL_TRANSLATE_ATTR, currentTranslate);
452
- }
453
- }
454
-
455
- function writeStudioPathOffsetVars(
456
- element: HTMLElement,
457
- offset: { x: number; y: number },
458
- options: { updateBase?: boolean } = {},
459
- ): void {
460
- prepareStudioPathOffsetBase(element, options.updateBase ?? true);
461
- element.setAttribute(STUDIO_PATH_OFFSET_ATTR, "true");
462
- element.style.setProperty(STUDIO_OFFSET_X_PROP, `${Math.round(offset.x)}px`);
463
- element.style.setProperty(STUDIO_OFFSET_Y_PROP, `${Math.round(offset.y)}px`);
464
- }
465
-
466
- function writeStudioBoxSizeVars(
467
- element: HTMLElement,
468
- size: { width: number; height: number },
469
- ): void {
470
- if (!element.hasAttribute(STUDIO_BOX_SIZE_ATTR)) {
471
- element.setAttribute(STUDIO_ORIGINAL_WIDTH_ATTR, element.style.getPropertyValue("width"));
472
- element.setAttribute(STUDIO_ORIGINAL_HEIGHT_ATTR, element.style.getPropertyValue("height"));
473
- element.setAttribute(
474
- STUDIO_ORIGINAL_MIN_WIDTH_ATTR,
475
- element.style.getPropertyValue("min-width"),
476
- );
477
- element.setAttribute(
478
- STUDIO_ORIGINAL_MIN_HEIGHT_ATTR,
479
- element.style.getPropertyValue("min-height"),
480
- );
481
- element.setAttribute(
482
- STUDIO_ORIGINAL_MAX_WIDTH_ATTR,
483
- element.style.getPropertyValue("max-width"),
484
- );
485
- element.setAttribute(
486
- STUDIO_ORIGINAL_MAX_HEIGHT_ATTR,
487
- element.style.getPropertyValue("max-height"),
488
- );
489
- element.setAttribute(
490
- STUDIO_ORIGINAL_FLEX_BASIS_ATTR,
491
- element.style.getPropertyValue("flex-basis"),
492
- );
493
- element.setAttribute(
494
- STUDIO_ORIGINAL_FLEX_GROW_ATTR,
495
- element.style.getPropertyValue("flex-grow"),
496
- );
497
- element.setAttribute(
498
- STUDIO_ORIGINAL_FLEX_SHRINK_ATTR,
499
- element.style.getPropertyValue("flex-shrink"),
500
- );
501
- element.setAttribute(
502
- STUDIO_ORIGINAL_BOX_SIZING_ATTR,
503
- element.style.getPropertyValue("box-sizing"),
504
- );
505
- element.setAttribute(STUDIO_ORIGINAL_SCALE_ATTR, element.style.getPropertyValue("scale"));
506
- element.setAttribute(
507
- STUDIO_ORIGINAL_TRANSFORM_ORIGIN_ATTR,
508
- element.style.getPropertyValue("transform-origin"),
509
- );
510
- element.setAttribute(STUDIO_ORIGINAL_DISPLAY_ATTR, element.style.getPropertyValue("display"));
511
- }
512
-
513
- element.setAttribute(STUDIO_BOX_SIZE_ATTR, "true");
514
- element.style.setProperty(STUDIO_WIDTH_PROP, `${Math.round(Math.max(1, size.width))}px`);
515
- element.style.setProperty(STUDIO_HEIGHT_PROP, `${Math.round(Math.max(1, size.height))}px`);
516
- }
517
-
518
- function prepareStudioRotationBase(element: HTMLElement, updateBase: boolean): void {
519
- const inlineRotate = element.style.getPropertyValue("rotate");
520
- const currentRotate = readTransformLonghandBase(element, "rotate");
521
- const hasMarker = element.hasAttribute(STUDIO_ROTATION_ATTR);
522
- const wasResetByAnimation =
523
- !styleUsesStudioRotation(currentRotate) &&
524
- !styleMatchesStudioRotationDraft(element, currentRotate);
525
- if (!hasMarker) {
526
- element.setAttribute(
527
- STUDIO_ORIGINAL_INLINE_ROTATE_ATTR,
528
- styleUsesStudioRotation(inlineRotate) ? "" : inlineRotate,
529
- );
530
- element.setAttribute(STUDIO_ORIGINAL_ROTATE_ATTR, wasResetByAnimation ? currentRotate : "");
531
- } else if (updateBase && wasResetByAnimation && !isStudioManualEditGestureActive(element)) {
532
- element.setAttribute(STUDIO_ORIGINAL_ROTATE_ATTR, currentRotate);
533
- }
534
- if (!element.hasAttribute(STUDIO_ORIGINAL_ROTATION_TRANSFORM_ORIGIN_ATTR)) {
535
- element.setAttribute(
536
- STUDIO_ORIGINAL_ROTATION_TRANSFORM_ORIGIN_ATTR,
537
- element.style.getPropertyValue("transform-origin"),
538
- );
539
- }
540
- }
541
-
542
- function writeStudioRotationVars(
543
- element: HTMLElement,
544
- rotation: { angle: number },
545
- options: { updateBase?: boolean } = {},
546
- ): void {
547
- prepareStudioRotationBase(element, options.updateBase ?? true);
548
- element.setAttribute(STUDIO_ROTATION_ATTR, "true");
549
- element.style.setProperty(STUDIO_ROTATION_PROP, `${roundRotationAngle(rotation.angle)}deg`);
550
- element.style.setProperty("transform-origin", STUDIO_ROTATION_TRANSFORM_ORIGIN);
551
- }
552
-
553
- function isSimpleRotateAngle(value: string): boolean {
554
- return /^-?(?:\d+(?:\.\d+)?|\.\d+)(?:deg|rad|turn|grad)$/.test(value.trim());
555
- }
556
-
557
- function composeStudioRotationValue(element: HTMLElement, rotationValue: string): string {
558
- const original = element.getAttribute(STUDIO_ORIGINAL_ROTATE_ATTR)?.trim();
559
- if (!original || original === "none" || !isSimpleRotateAngle(original)) {
560
- return rotationValue;
561
- }
562
- return `calc(${original} + ${rotationValue})`;
563
- }
564
-
565
- function safeComputedStyleProperty(element: HTMLElement, property: string): string {
566
- try {
567
- return (
568
- element.ownerDocument.defaultView?.getComputedStyle(element).getPropertyValue(property) ?? ""
569
- );
570
- } catch {
571
- return "";
572
- }
573
- }
574
-
575
- function readStyleOrComputed(element: HTMLElement, property: string): string {
576
- return element.style.getPropertyValue(property) || safeComputedStyleProperty(element, property);
577
- }
578
-
579
- function readParentFlexBasisPixels(
580
- element: HTMLElement,
581
- size: { width: number; height: number },
582
- ): number | null {
583
- const parent = element.parentElement;
584
- if (!parent) return null;
585
-
586
- const display = readStyleOrComputed(parent, "display").trim();
587
- if (display !== "flex" && display !== "inline-flex") return null;
588
-
589
- const direction = readStyleOrComputed(parent, "flex-direction").trim();
590
- return Math.round(Math.max(1, direction.startsWith("column") ? size.height : size.width));
591
- }
592
-
593
- function restoreStaleStudioScaleResize(element: HTMLElement): void {
594
- if (!element.hasAttribute(STUDIO_ORIGINAL_SCALE_ATTR)) return;
595
- restoreOriginalBoxSizeProperty(element, "scale", STUDIO_ORIGINAL_SCALE_ATTR);
596
- restoreOriginalBoxSizeProperty(
597
- element,
598
- "transform-origin",
599
- STUDIO_ORIGINAL_TRANSFORM_ORIGIN_ATTR,
600
- );
601
- }
602
-
603
- function applyStudioBoxSizeDimensions(
604
- element: HTMLElement,
605
- size: { width: number; height: number },
606
- ): void {
607
- writeStudioBoxSizeVars(element, size);
608
- restoreStaleStudioScaleResize(element);
609
-
610
- const width = Math.round(Math.max(1, size.width));
611
- const height = Math.round(Math.max(1, size.height));
612
- element.style.setProperty("box-sizing", "border-box");
613
- element.style.setProperty("width", `${width}px`);
614
- element.style.setProperty("height", `${height}px`);
615
- element.style.setProperty("min-width", "0px");
616
- element.style.setProperty("min-height", "0px");
617
- element.style.setProperty("max-width", "none");
618
- element.style.setProperty("max-height", "none");
619
- const flexBasis = readParentFlexBasisPixels(element, size);
620
- if (flexBasis != null) {
621
- element.style.setProperty("flex-basis", `${flexBasis}px`);
622
- element.style.setProperty("flex-grow", "0");
623
- element.style.setProperty("flex-shrink", "0");
624
- }
625
- const computedDisplay = safeComputedStyleProperty(element, "display");
626
- if (computedDisplay === "inline") {
627
- element.style.setProperty("display", "inline-block");
628
- }
629
- }
630
-
631
- function styleUsesStudioOffset(value: string): boolean {
632
- return value.includes(STUDIO_OFFSET_X_PROP) || value.includes(STUDIO_OFFSET_Y_PROP);
633
- }
634
-
635
- function styleUsesStudioSize(value: string): boolean {
636
- return value.includes(STUDIO_WIDTH_PROP) || value.includes(STUDIO_HEIGHT_PROP);
637
- }
638
-
639
- function styleUsesStudioRotation(value: string): boolean {
640
- return value.includes(STUDIO_ROTATION_PROP);
641
- }
642
-
643
- function compactStyleValue(value: string): string {
644
- return value.replace(/\s+/g, "").toLowerCase();
645
- }
646
-
647
- function styleMatchesStudioRotationDraft(element: HTMLElement, value: string): boolean {
648
- if (!element.hasAttribute(STUDIO_ROTATION_DRAFT_ATTR)) return false;
649
- const rotation = element.style.getPropertyValue(STUDIO_ROTATION_PROP).trim();
650
- if (!rotation || !value.trim()) return false;
651
- return (
652
- compactStyleValue(value) === compactStyleValue(composeStudioRotationValue(element, rotation))
653
- );
654
- }
655
-
656
- export function applyStudioPathOffset(
657
- element: HTMLElement,
658
- offset: { x: number; y: number },
659
- ): void {
660
- writeStudioPathOffsetVars(element, offset);
661
- element.style.setProperty(
662
- "translate",
663
- composeTranslateValue(
664
- element,
665
- `var(${STUDIO_OFFSET_X_PROP}, 0px)`,
666
- `var(${STUDIO_OFFSET_Y_PROP}, 0px)`,
667
- ),
668
- );
669
- }
670
-
671
- export function applyStudioPathOffsetDraft(
672
- element: HTMLElement,
673
- offset: { x: number; y: number },
674
- ): void {
675
- writeStudioPathOffsetVars(element, offset, { updateBase: false });
676
- element.style.setProperty(
677
- "translate",
678
- composeTranslateValue(element, `${Math.round(offset.x)}px`, `${Math.round(offset.y)}px`),
679
- );
680
- }
681
-
682
- export function applyStudioBoxSize(
683
- element: HTMLElement,
684
- size: { width: number; height: number },
685
- ): void {
686
- applyStudioBoxSizeDimensions(element, size);
687
- }
688
-
689
- export function applyStudioBoxSizeDraft(
690
- element: HTMLElement,
691
- size: { width: number; height: number },
692
- ): void {
693
- applyStudioBoxSizeDimensions(element, size);
694
- }
695
-
696
- export function applyStudioRotation(element: HTMLElement, rotation: { angle: number }): void {
697
- writeStudioRotationVars(element, rotation);
698
- element.removeAttribute(STUDIO_ROTATION_DRAFT_ATTR);
699
- element.style.setProperty(
700
- "rotate",
701
- composeStudioRotationValue(element, `var(${STUDIO_ROTATION_PROP}, 0deg)`),
702
- );
703
- }
704
-
705
- export function applyStudioRotationDraft(element: HTMLElement, rotation: { angle: number }): void {
706
- writeStudioRotationVars(element, rotation, { updateBase: false });
707
- element.setAttribute(STUDIO_ROTATION_DRAFT_ATTR, "true");
708
- element.style.setProperty(
709
- "rotate",
710
- composeStudioRotationValue(element, `${roundRotationAngle(rotation.angle)}deg`),
711
- );
712
- }
713
-
714
- function clearStudioPathOffset(element: HTMLElement): void {
715
- if (
716
- element.hasAttribute(STUDIO_PATH_OFFSET_ATTR) ||
717
- styleUsesStudioOffset(element.style.getPropertyValue("translate"))
718
- ) {
719
- restoreOriginalTranslateProperty(element);
720
- }
721
- element.style.removeProperty(STUDIO_OFFSET_X_PROP);
722
- element.style.removeProperty(STUDIO_OFFSET_Y_PROP);
723
- element.removeAttribute(STUDIO_PATH_OFFSET_ATTR);
724
- element.removeAttribute(STUDIO_ORIGINAL_TRANSLATE_ATTR);
725
- element.removeAttribute(STUDIO_ORIGINAL_INLINE_TRANSLATE_ATTR);
726
- }
727
-
728
- function clearStudioRotation(element: HTMLElement): void {
729
- if (
730
- element.hasAttribute(STUDIO_ROTATION_ATTR) ||
731
- styleUsesStudioRotation(element.style.getPropertyValue("rotate"))
732
- ) {
733
- restoreOriginalRotationProperty(element);
734
- }
735
-
736
- element.style.removeProperty(STUDIO_ROTATION_PROP);
737
- element.removeAttribute(STUDIO_ROTATION_ATTR);
738
- element.removeAttribute(STUDIO_ROTATION_DRAFT_ATTR);
739
- element.removeAttribute(STUDIO_ORIGINAL_ROTATE_ATTR);
740
- element.removeAttribute(STUDIO_ORIGINAL_INLINE_ROTATE_ATTR);
741
- element.removeAttribute(STUDIO_ORIGINAL_ROTATION_TRANSFORM_ORIGIN_ATTR);
742
- }
743
-
744
- function restoreOriginalBoxSizeProperty(
745
- element: HTMLElement,
746
- property:
747
- | "width"
748
- | "height"
749
- | "min-width"
750
- | "min-height"
751
- | "max-width"
752
- | "max-height"
753
- | "flex-basis"
754
- | "flex-grow"
755
- | "flex-shrink"
756
- | "box-sizing"
757
- | "scale"
758
- | "transform-origin"
759
- | "display",
760
- attribute: string,
761
- ): void {
762
- const original = element.getAttribute(attribute);
763
- if (original == null || original === "") element.style.removeProperty(property);
764
- else element.style.setProperty(property, original);
765
- element.removeAttribute(attribute);
766
- }
767
-
768
- function restoreOriginalRotationProperty(element: HTMLElement): void {
769
- const original = element.getAttribute(STUDIO_ORIGINAL_INLINE_ROTATE_ATTR);
770
- if (original == null || original === "") element.style.removeProperty("rotate");
771
- else element.style.setProperty("rotate", original);
772
- element.removeAttribute(STUDIO_ORIGINAL_INLINE_ROTATE_ATTR);
773
- element.removeAttribute(STUDIO_ORIGINAL_ROTATE_ATTR);
774
-
775
- const originalTransformOrigin = element.getAttribute(
776
- STUDIO_ORIGINAL_ROTATION_TRANSFORM_ORIGIN_ATTR,
777
- );
778
- if (originalTransformOrigin != null) {
779
- if (originalTransformOrigin === "") element.style.removeProperty("transform-origin");
780
- else element.style.setProperty("transform-origin", originalTransformOrigin);
781
- }
782
- element.removeAttribute(STUDIO_ORIGINAL_ROTATION_TRANSFORM_ORIGIN_ATTR);
783
- }
784
-
785
- function restoreOriginalTranslateProperty(element: HTMLElement): void {
786
- const original = element.getAttribute(STUDIO_ORIGINAL_INLINE_TRANSLATE_ATTR);
787
- if (original == null || original === "") element.style.removeProperty("translate");
788
- else element.style.setProperty("translate", original);
789
- element.removeAttribute(STUDIO_ORIGINAL_INLINE_TRANSLATE_ATTR);
790
- element.removeAttribute(STUDIO_ORIGINAL_TRANSLATE_ATTR);
791
- }
792
-
793
- function clearStudioBoxSize(element: HTMLElement): void {
794
- if (
795
- element.hasAttribute(STUDIO_BOX_SIZE_ATTR) ||
796
- styleUsesStudioSize(element.style.getPropertyValue("width")) ||
797
- styleUsesStudioSize(element.style.getPropertyValue("height")) ||
798
- element.hasAttribute(STUDIO_ORIGINAL_SCALE_ATTR)
799
- ) {
800
- restoreOriginalBoxSizeProperty(element, "width", STUDIO_ORIGINAL_WIDTH_ATTR);
801
- restoreOriginalBoxSizeProperty(element, "height", STUDIO_ORIGINAL_HEIGHT_ATTR);
802
- restoreOriginalBoxSizeProperty(element, "min-width", STUDIO_ORIGINAL_MIN_WIDTH_ATTR);
803
- restoreOriginalBoxSizeProperty(element, "min-height", STUDIO_ORIGINAL_MIN_HEIGHT_ATTR);
804
- restoreOriginalBoxSizeProperty(element, "max-width", STUDIO_ORIGINAL_MAX_WIDTH_ATTR);
805
- restoreOriginalBoxSizeProperty(element, "max-height", STUDIO_ORIGINAL_MAX_HEIGHT_ATTR);
806
- restoreOriginalBoxSizeProperty(element, "flex-basis", STUDIO_ORIGINAL_FLEX_BASIS_ATTR);
807
- restoreOriginalBoxSizeProperty(element, "flex-grow", STUDIO_ORIGINAL_FLEX_GROW_ATTR);
808
- restoreOriginalBoxSizeProperty(element, "flex-shrink", STUDIO_ORIGINAL_FLEX_SHRINK_ATTR);
809
- restoreOriginalBoxSizeProperty(element, "box-sizing", STUDIO_ORIGINAL_BOX_SIZING_ATTR);
810
- restoreOriginalBoxSizeProperty(element, "scale", STUDIO_ORIGINAL_SCALE_ATTR);
811
- restoreOriginalBoxSizeProperty(
812
- element,
813
- "transform-origin",
814
- STUDIO_ORIGINAL_TRANSFORM_ORIGIN_ATTR,
815
- );
816
- restoreOriginalBoxSizeProperty(element, "display", STUDIO_ORIGINAL_DISPLAY_ATTR);
817
- }
818
-
819
- element.style.removeProperty(STUDIO_WIDTH_PROP);
820
- element.style.removeProperty(STUDIO_HEIGHT_PROP);
821
- element.removeAttribute(STUDIO_BOX_SIZE_ATTR);
822
- }
823
-
824
- export interface StudioBoxSizeSnapshot {
825
- width: string;
826
- height: string;
827
- minWidth: string;
828
- minHeight: string;
829
- maxWidth: string;
830
- maxHeight: string;
831
- flexBasis: string;
832
- flexGrow: string;
833
- flexShrink: string;
834
- boxSizing: string;
835
- scale: string;
836
- transformOrigin: string;
837
- display: string;
838
- studioWidth: string;
839
- studioHeight: string;
840
- marker: string | null;
841
- originalWidth: string | null;
842
- originalHeight: string | null;
843
- originalMinWidth: string | null;
844
- originalMinHeight: string | null;
845
- originalMaxWidth: string | null;
846
- originalMaxHeight: string | null;
847
- originalFlexBasis: string | null;
848
- originalFlexGrow: string | null;
849
- originalFlexShrink: string | null;
850
- originalBoxSizing: string | null;
851
- originalScale: string | null;
852
- originalTransformOrigin: string | null;
853
- originalDisplay: string | null;
854
- }
855
-
856
- export interface StudioRotationSnapshot {
857
- rotate: string;
858
- transformOrigin: string;
859
- studioRotation: string;
860
- marker: string | null;
861
- draftMarker: string | null;
862
- originalRotate: string | null;
863
- originalInlineRotate: string | null;
864
- originalTransformOrigin: string | null;
865
- }
866
-
867
- export interface StudioPathOffsetSnapshot {
868
- translate: string;
869
- x: string;
870
- y: string;
871
- marker: string | null;
872
- originalTranslate: string | null;
873
- originalInlineTranslate: string | null;
874
- }
875
-
876
- export function captureStudioBoxSize(element: HTMLElement): StudioBoxSizeSnapshot {
877
- return {
878
- width: element.style.getPropertyValue("width"),
879
- height: element.style.getPropertyValue("height"),
880
- minWidth: element.style.getPropertyValue("min-width"),
881
- minHeight: element.style.getPropertyValue("min-height"),
882
- maxWidth: element.style.getPropertyValue("max-width"),
883
- maxHeight: element.style.getPropertyValue("max-height"),
884
- flexBasis: element.style.getPropertyValue("flex-basis"),
885
- flexGrow: element.style.getPropertyValue("flex-grow"),
886
- flexShrink: element.style.getPropertyValue("flex-shrink"),
887
- boxSizing: element.style.getPropertyValue("box-sizing"),
888
- scale: element.style.getPropertyValue("scale"),
889
- transformOrigin: element.style.getPropertyValue("transform-origin"),
890
- display: element.style.getPropertyValue("display"),
891
- studioWidth: element.style.getPropertyValue(STUDIO_WIDTH_PROP),
892
- studioHeight: element.style.getPropertyValue(STUDIO_HEIGHT_PROP),
893
- marker: element.getAttribute(STUDIO_BOX_SIZE_ATTR),
894
- originalWidth: element.getAttribute(STUDIO_ORIGINAL_WIDTH_ATTR),
895
- originalHeight: element.getAttribute(STUDIO_ORIGINAL_HEIGHT_ATTR),
896
- originalMinWidth: element.getAttribute(STUDIO_ORIGINAL_MIN_WIDTH_ATTR),
897
- originalMinHeight: element.getAttribute(STUDIO_ORIGINAL_MIN_HEIGHT_ATTR),
898
- originalMaxWidth: element.getAttribute(STUDIO_ORIGINAL_MAX_WIDTH_ATTR),
899
- originalMaxHeight: element.getAttribute(STUDIO_ORIGINAL_MAX_HEIGHT_ATTR),
900
- originalFlexBasis: element.getAttribute(STUDIO_ORIGINAL_FLEX_BASIS_ATTR),
901
- originalFlexGrow: element.getAttribute(STUDIO_ORIGINAL_FLEX_GROW_ATTR),
902
- originalFlexShrink: element.getAttribute(STUDIO_ORIGINAL_FLEX_SHRINK_ATTR),
903
- originalBoxSizing: element.getAttribute(STUDIO_ORIGINAL_BOX_SIZING_ATTR),
904
- originalScale: element.getAttribute(STUDIO_ORIGINAL_SCALE_ATTR),
905
- originalTransformOrigin: element.getAttribute(STUDIO_ORIGINAL_TRANSFORM_ORIGIN_ATTR),
906
- originalDisplay: element.getAttribute(STUDIO_ORIGINAL_DISPLAY_ATTR),
907
- };
908
- }
909
-
910
- export function captureStudioRotation(element: HTMLElement): StudioRotationSnapshot {
911
- return {
912
- rotate: element.style.getPropertyValue("rotate"),
913
- transformOrigin: element.style.getPropertyValue("transform-origin"),
914
- studioRotation: element.style.getPropertyValue(STUDIO_ROTATION_PROP),
915
- marker: element.getAttribute(STUDIO_ROTATION_ATTR),
916
- draftMarker: element.getAttribute(STUDIO_ROTATION_DRAFT_ATTR),
917
- originalRotate: element.getAttribute(STUDIO_ORIGINAL_ROTATE_ATTR),
918
- originalInlineRotate: element.getAttribute(STUDIO_ORIGINAL_INLINE_ROTATE_ATTR),
919
- originalTransformOrigin: element.getAttribute(STUDIO_ORIGINAL_ROTATION_TRANSFORM_ORIGIN_ATTR),
920
- };
921
- }
922
-
923
- export function captureStudioPathOffset(element: HTMLElement): StudioPathOffsetSnapshot {
924
- return {
925
- translate: element.style.getPropertyValue("translate"),
926
- x: element.style.getPropertyValue(STUDIO_OFFSET_X_PROP),
927
- y: element.style.getPropertyValue(STUDIO_OFFSET_Y_PROP),
928
- marker: element.getAttribute(STUDIO_PATH_OFFSET_ATTR),
929
- originalTranslate: element.getAttribute(STUDIO_ORIGINAL_TRANSLATE_ATTR),
930
- originalInlineTranslate: element.getAttribute(STUDIO_ORIGINAL_INLINE_TRANSLATE_ATTR),
931
- };
932
- }
933
-
934
- function restoreAttribute(element: HTMLElement, attribute: string, value: string | null): void {
935
- if (value == null) element.removeAttribute(attribute);
936
- else element.setAttribute(attribute, value);
937
- }
938
-
939
- function restoreStyleProperty(element: HTMLElement, property: string, value: string): void {
940
- if (value) element.style.setProperty(property, value);
941
- else element.style.removeProperty(property);
942
- }
943
-
944
- export function restoreStudioBoxSize(element: HTMLElement, previous: StudioBoxSizeSnapshot): void {
945
- restoreStyleProperty(element, "width", previous.width);
946
- restoreStyleProperty(element, "height", previous.height);
947
- restoreStyleProperty(element, "min-width", previous.minWidth);
948
- restoreStyleProperty(element, "min-height", previous.minHeight);
949
- restoreStyleProperty(element, "max-width", previous.maxWidth);
950
- restoreStyleProperty(element, "max-height", previous.maxHeight);
951
- restoreStyleProperty(element, "flex-basis", previous.flexBasis);
952
- restoreStyleProperty(element, "flex-grow", previous.flexGrow);
953
- restoreStyleProperty(element, "flex-shrink", previous.flexShrink);
954
- restoreStyleProperty(element, "box-sizing", previous.boxSizing);
955
- restoreStyleProperty(element, "scale", previous.scale);
956
- restoreStyleProperty(element, "transform-origin", previous.transformOrigin);
957
- restoreStyleProperty(element, "display", previous.display);
958
- restoreStyleProperty(element, STUDIO_WIDTH_PROP, previous.studioWidth);
959
- restoreStyleProperty(element, STUDIO_HEIGHT_PROP, previous.studioHeight);
960
- restoreAttribute(element, STUDIO_BOX_SIZE_ATTR, previous.marker);
961
- restoreAttribute(element, STUDIO_ORIGINAL_WIDTH_ATTR, previous.originalWidth);
962
- restoreAttribute(element, STUDIO_ORIGINAL_HEIGHT_ATTR, previous.originalHeight);
963
- restoreAttribute(element, STUDIO_ORIGINAL_MIN_WIDTH_ATTR, previous.originalMinWidth);
964
- restoreAttribute(element, STUDIO_ORIGINAL_MIN_HEIGHT_ATTR, previous.originalMinHeight);
965
- restoreAttribute(element, STUDIO_ORIGINAL_MAX_WIDTH_ATTR, previous.originalMaxWidth);
966
- restoreAttribute(element, STUDIO_ORIGINAL_MAX_HEIGHT_ATTR, previous.originalMaxHeight);
967
- restoreAttribute(element, STUDIO_ORIGINAL_FLEX_BASIS_ATTR, previous.originalFlexBasis);
968
- restoreAttribute(element, STUDIO_ORIGINAL_FLEX_GROW_ATTR, previous.originalFlexGrow);
969
- restoreAttribute(element, STUDIO_ORIGINAL_FLEX_SHRINK_ATTR, previous.originalFlexShrink);
970
- restoreAttribute(element, STUDIO_ORIGINAL_BOX_SIZING_ATTR, previous.originalBoxSizing);
971
- restoreAttribute(element, STUDIO_ORIGINAL_SCALE_ATTR, previous.originalScale);
972
- restoreAttribute(
973
- element,
974
- STUDIO_ORIGINAL_TRANSFORM_ORIGIN_ATTR,
975
- previous.originalTransformOrigin,
976
- );
977
- restoreAttribute(element, STUDIO_ORIGINAL_DISPLAY_ATTR, previous.originalDisplay);
978
- }
979
-
980
- export function restoreStudioRotation(
981
- element: HTMLElement,
982
- previous: StudioRotationSnapshot,
983
- ): void {
984
- restoreStyleProperty(element, "rotate", previous.rotate);
985
- restoreStyleProperty(element, "transform-origin", previous.transformOrigin);
986
- restoreStyleProperty(element, STUDIO_ROTATION_PROP, previous.studioRotation);
987
- restoreAttribute(element, STUDIO_ROTATION_ATTR, previous.marker);
988
- restoreAttribute(element, STUDIO_ROTATION_DRAFT_ATTR, previous.draftMarker);
989
- restoreAttribute(element, STUDIO_ORIGINAL_ROTATE_ATTR, previous.originalRotate);
990
- restoreAttribute(element, STUDIO_ORIGINAL_INLINE_ROTATE_ATTR, previous.originalInlineRotate);
991
- restoreAttribute(
992
- element,
993
- STUDIO_ORIGINAL_ROTATION_TRANSFORM_ORIGIN_ATTR,
994
- previous.originalTransformOrigin,
995
- );
996
- }
997
-
998
- export function restoreStudioPathOffset(
999
- element: HTMLElement,
1000
- previous: StudioPathOffsetSnapshot,
1001
- ): void {
1002
- if (previous.translate) element.style.setProperty("translate", previous.translate);
1003
- else element.style.removeProperty("translate");
1004
-
1005
- if (previous.x) element.style.setProperty(STUDIO_OFFSET_X_PROP, previous.x);
1006
- else element.style.removeProperty(STUDIO_OFFSET_X_PROP);
1007
-
1008
- if (previous.y) element.style.setProperty(STUDIO_OFFSET_Y_PROP, previous.y);
1009
- else element.style.removeProperty(STUDIO_OFFSET_Y_PROP);
1010
-
1011
- restoreAttribute(element, STUDIO_PATH_OFFSET_ATTR, previous.marker);
1012
- restoreAttribute(element, STUDIO_ORIGINAL_TRANSLATE_ATTR, previous.originalTranslate);
1013
- restoreAttribute(
1014
- element,
1015
- STUDIO_ORIGINAL_INLINE_TRANSLATE_ATTR,
1016
- previous.originalInlineTranslate,
1017
- );
1018
- }
1019
-
1
+ // Public re-exports consumers import from this file as before.
2
+ export {
3
+ STUDIO_MANUAL_EDITS_PATH,
4
+ STUDIO_OFFSET_X_PROP,
5
+ STUDIO_OFFSET_Y_PROP,
6
+ STUDIO_WIDTH_PROP,
7
+ STUDIO_HEIGHT_PROP,
8
+ STUDIO_ROTATION_PROP,
9
+ type StudioManualEditTarget,
10
+ type StudioPathOffsetEdit,
11
+ type StudioBoxSizeEdit,
12
+ type StudioRotationEdit,
13
+ type StudioManualEdit,
14
+ type StudioManualEditManifest,
15
+ type StudioManualEditSeekWindow,
16
+ type StudioBoxSizeSnapshot,
17
+ type StudioRotationSnapshot,
18
+ type StudioPathOffsetSnapshot,
19
+ } from "./manualEditsTypes";
20
+
21
+ export {
22
+ emptyStudioManualEditManifest,
23
+ parseStudioManualEditManifest,
24
+ serializeStudioManualEditManifest,
25
+ readStudioFileChangePath,
26
+ isStudioManualEditManifestPath,
27
+ upsertStudioPathOffsetEdit,
28
+ upsertStudioBoxSizeEdit,
29
+ upsertStudioRotationEdit,
30
+ removeStudioManualEditsForSelection,
31
+ } from "./manualEditsParsing";
32
+
33
+ export {
34
+ beginStudioManualEditGesture,
35
+ endStudioManualEditGesture,
36
+ isStudioManualEditGestureCurrent,
37
+ readStudioPathOffset,
38
+ readStudioBoxSize,
39
+ readStudioRotation,
40
+ applyStudioPathOffset,
41
+ applyStudioPathOffsetDraft,
42
+ applyStudioBoxSize,
43
+ applyStudioBoxSizeDraft,
44
+ applyStudioRotation,
45
+ applyStudioRotationDraft,
46
+ clearStudioPathOffset,
47
+ clearStudioRotation,
48
+ clearStudioBoxSize,
49
+ } from "./manualEditsDom";
50
+
51
+ export {
52
+ captureStudioBoxSize,
53
+ captureStudioRotation,
54
+ captureStudioPathOffset,
55
+ restoreStudioBoxSize,
56
+ restoreStudioRotation,
57
+ restoreStudioPathOffset,
58
+ } from "./manualEditsSnapshot";
59
+
60
+ import type {
61
+ StudioManualEdit,
62
+ StudioManualEditManifest,
63
+ StudioManualEditSeekWindow,
64
+ } from "./manualEditsTypes";
65
+ import {
66
+ STUDIO_MANUAL_EDITS_APPLY_PROP,
67
+ STUDIO_MANUAL_EDITS_WRAPPED_PROP,
68
+ STUDIO_MANUAL_EDITS_PLAYBACK_FRAME_PROP,
69
+ } from "./manualEditsTypes";
70
+ import { finiteNumber } from "./manualEditsParsing";
71
+ import {
72
+ applyStudioPathOffset,
73
+ applyStudioBoxSize,
74
+ applyStudioRotation,
75
+ isStudioManualEditGestureActive,
76
+ clearStudioPathOffset,
77
+ clearStudioBoxSize,
78
+ clearStudioRotation,
79
+ } from "./manualEditsDom";
80
+ import { collectStudioManualEditElements } from "./manualEditsSnapshot";
81
+
82
+ /* ── Seek/play reapply wrappers ───────────────────────────────────── */
1020
83
  function markWrapped(fn: (...args: unknown[]) => unknown): void {
1021
84
  try {
1022
85
  Object.defineProperty(fn, STUDIO_MANUAL_EDITS_WRAPPED_PROP, {
@@ -1244,6 +307,7 @@ export function installStudioManualEditSeekReapply(win: Window, apply: () => voi
1244
307
  );
1245
308
  }
1246
309
 
310
+ /* ── DOM target resolution ────────────────────────────────────────── */
1247
311
  function getManualEditSourceFileForElement(
1248
312
  el: HTMLElement,
1249
313
  activeCompositionPath: string | null,
@@ -1329,36 +393,7 @@ function resolveManualEditTarget(
1329
393
  }
1330
394
  }
1331
395
 
1332
- function collectStudioManualEditElements(doc: Document): HTMLElement[] {
1333
- const htmlElement = doc.defaultView?.HTMLElement;
1334
- if (!htmlElement) return [];
1335
-
1336
- const elements = [doc.documentElement, ...Array.from(doc.getElementsByTagName("*"))].filter(
1337
- (element): element is HTMLElement => element instanceof htmlElement,
1338
- );
1339
-
1340
- return elements.filter(
1341
- (element) =>
1342
- element.hasAttribute(STUDIO_PATH_OFFSET_ATTR) ||
1343
- element.hasAttribute(STUDIO_MANUAL_EDIT_GESTURE_ATTR) ||
1344
- element.hasAttribute(STUDIO_BOX_SIZE_ATTR) ||
1345
- element.hasAttribute(STUDIO_ROTATION_ATTR) ||
1346
- element.hasAttribute(STUDIO_ROTATION_DRAFT_ATTR) ||
1347
- element.hasAttribute(STUDIO_ORIGINAL_TRANSLATE_ATTR) ||
1348
- element.hasAttribute(STUDIO_ORIGINAL_INLINE_TRANSLATE_ATTR) ||
1349
- element.hasAttribute(STUDIO_ORIGINAL_MIN_WIDTH_ATTR) ||
1350
- element.hasAttribute(STUDIO_ORIGINAL_FLEX_BASIS_ATTR) ||
1351
- element.hasAttribute(STUDIO_ORIGINAL_SCALE_ATTR) ||
1352
- element.hasAttribute(STUDIO_ORIGINAL_ROTATE_ATTR) ||
1353
- element.hasAttribute(STUDIO_ORIGINAL_INLINE_ROTATE_ATTR) ||
1354
- Boolean(element.style.getPropertyValue(STUDIO_OFFSET_X_PROP)) ||
1355
- Boolean(element.style.getPropertyValue(STUDIO_OFFSET_Y_PROP)) ||
1356
- Boolean(element.style.getPropertyValue(STUDIO_WIDTH_PROP)) ||
1357
- Boolean(element.style.getPropertyValue(STUDIO_HEIGHT_PROP)) ||
1358
- Boolean(element.style.getPropertyValue(STUDIO_ROTATION_PROP)),
1359
- );
1360
- }
1361
-
396
+ /* ── Manifest application ─────────────────────────────────────────── */
1362
397
  export function applyStudioManualEditManifest(
1363
398
  doc: Document,
1364
399
  manifest: StudioManualEditManifest,