@hyperframes/studio 0.6.107 → 0.6.108

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 (37) hide show
  1. package/dist/assets/{hyperframes-player-DgsMQSvV.js → hyperframes-player-67pq7USK.js} +2 -2
  2. package/dist/assets/index-BVqybwMG.css +1 -0
  3. package/dist/assets/index-BgbVhDYd.js +296 -0
  4. package/dist/assets/{index-zIK7PWvk.js → index-Btg-jqxS.js} +1 -1
  5. package/dist/assets/{index-P52mbTRb.js → index-tn9M43-U.js} +1 -1
  6. package/dist/index.html +2 -2
  7. package/package.json +5 -5
  8. package/src/App.tsx +1 -0
  9. package/src/components/StudioRightPanel.tsx +161 -75
  10. package/src/components/editor/PropertyPanel.tsx +26 -1
  11. package/src/components/editor/domEditOverlayGeometry.ts +2 -11
  12. package/src/components/editor/domEditingDom.ts +21 -0
  13. package/src/components/editor/domEditingElement.ts +2 -11
  14. package/src/components/editor/manualEditingAvailability.test.ts +12 -0
  15. package/src/components/editor/manualEditingAvailability.ts +6 -0
  16. package/src/components/editor/propertyPanelColorGradingControls.tsx +475 -0
  17. package/src/components/editor/propertyPanelColorGradingSection.tsx +355 -0
  18. package/src/components/editor/propertyPanelHelpers.ts +2 -1
  19. package/src/components/editor/propertyPanelPrimitives.tsx +32 -37
  20. package/src/contexts/DomEditContext.tsx +4 -0
  21. package/src/contexts/PanelLayoutContext.tsx +6 -0
  22. package/src/hooks/useDomEditCommits.ts +7 -0
  23. package/src/hooks/useDomEditSession.ts +2 -2
  24. package/src/hooks/useDomEditTextCommits.ts +82 -26
  25. package/src/hooks/useDomEditWiring.ts +1 -0
  26. package/src/hooks/useDomSelection.ts +2 -10
  27. package/src/hooks/usePanelLayout.ts +26 -1
  28. package/src/hooks/useStudioContextValue.ts +8 -3
  29. package/src/icons/SystemIcons.tsx +4 -0
  30. package/src/player/lib/timelineDOM.test.ts +36 -0
  31. package/src/player/lib/timelineDOM.ts +2 -0
  32. package/src/player/lib/timelineElementHelpers.ts +18 -1
  33. package/src/styles/studio.css +81 -0
  34. package/src/utils/mediaTypes.ts +1 -0
  35. package/src/utils/studioHelpers.ts +6 -0
  36. package/dist/assets/index-BrhJl2JY.css +0 -1
  37. package/dist/assets/index-CVHV-S-B.js +0 -296
@@ -0,0 +1,355 @@
1
+ import {
2
+ useCallback,
3
+ useEffect,
4
+ useMemo,
5
+ useRef,
6
+ useState,
7
+ type PointerEvent as ReactPointerEvent,
8
+ type RefObject,
9
+ } from "react";
10
+ import {
11
+ HF_COLOR_GRADING_ATTR,
12
+ HF_COLOR_GRADING_COLOR_SPACE,
13
+ isHfColorGradingActive,
14
+ normalizeHfColorGrading,
15
+ serializeHfColorGrading,
16
+ type HfColorGradingAdjustKey,
17
+ type HfColorGradingTarget,
18
+ type NormalizedHfColorGrading,
19
+ } from "@hyperframes/core/color-grading";
20
+ import { Compare, Palette, RotateCcw } from "../../icons/SystemIcons";
21
+ import type { DomEditSelection } from "./domEditing";
22
+ import { ColorGradingControls } from "./propertyPanelColorGradingControls";
23
+ import { Section } from "./propertyPanelPrimitives";
24
+
25
+ const DEFAULT_ADJUST: Record<HfColorGradingAdjustKey, number> = {
26
+ exposure: 0,
27
+ contrast: 0,
28
+ highlights: 0,
29
+ shadows: 0,
30
+ whites: 0,
31
+ blacks: 0,
32
+ temperature: 0,
33
+ tint: 0,
34
+ saturation: 0,
35
+ };
36
+
37
+ const DEFAULT_COLOR_GRADING: NormalizedHfColorGrading = {
38
+ enabled: true,
39
+ preset: "neutral",
40
+ intensity: 1,
41
+ adjust: DEFAULT_ADJUST,
42
+ lut: null,
43
+ colorSpace: HF_COLOR_GRADING_COLOR_SPACE,
44
+ };
45
+
46
+ const COLOR_GRADING_DATA_KEY = HF_COLOR_GRADING_ATTR.replace(/^data-/, "");
47
+
48
+ interface RuntimeColorGradingStatus {
49
+ state: "missing" | "inactive" | "pending" | "active" | "unavailable";
50
+ message: string;
51
+ }
52
+
53
+ export function isColorGradingCapableElement(element: DomEditSelection): boolean {
54
+ return element.tagName === "video" || element.tagName === "img";
55
+ }
56
+
57
+ function readColorGradingFromElement(element: DomEditSelection): NormalizedHfColorGrading {
58
+ const grading =
59
+ normalizeHfColorGrading(element.dataAttributes[COLOR_GRADING_DATA_KEY]) ??
60
+ DEFAULT_COLOR_GRADING;
61
+ return { ...grading, intensity: 1 };
62
+ }
63
+
64
+ function toBridgeColorGrading(grading: NormalizedHfColorGrading): unknown {
65
+ if (!isHfColorGradingActive(grading)) return null;
66
+ const { enabled: _enabled, ...bridgeGrading } = grading;
67
+ return bridgeGrading;
68
+ }
69
+
70
+ function readRuntimeColorGradingStatus(
71
+ iframe: HTMLIFrameElement | null | undefined,
72
+ target: HfColorGradingTarget,
73
+ ): RuntimeColorGradingStatus {
74
+ try {
75
+ const win = iframe?.contentWindow as
76
+ | (Window & {
77
+ __hf?: {
78
+ colorGrading?: {
79
+ getStatus?: (
80
+ target: HfColorGradingTarget | string | null | undefined,
81
+ ) => RuntimeColorGradingStatus;
82
+ };
83
+ };
84
+ })
85
+ | null
86
+ | undefined;
87
+ const status = win?.__hf?.colorGrading?.getStatus?.(target);
88
+ return status ?? { state: "pending", message: "Waiting for runtime" };
89
+ } catch {
90
+ return { state: "unavailable", message: "Preview unavailable" };
91
+ }
92
+ }
93
+
94
+ function StatusPill({ status }: { status: RuntimeColorGradingStatus }) {
95
+ const dotClass =
96
+ status.state === "active"
97
+ ? "bg-emerald-400"
98
+ : status.state === "pending"
99
+ ? "bg-amber-300"
100
+ : status.state === "unavailable"
101
+ ? "bg-red-400"
102
+ : "bg-panel-text-5";
103
+ return (
104
+ <div className="flex min-w-0 items-center gap-1.5 rounded bg-panel-input px-2 py-1 text-[10px] font-medium text-panel-text-3">
105
+ <span className={`h-1.5 w-1.5 flex-shrink-0 rounded-full ${dotClass}`} />
106
+ <span className="truncate">{status.message}</span>
107
+ </div>
108
+ );
109
+ }
110
+
111
+ function HoldBeforeButton({
112
+ active,
113
+ disabled,
114
+ onHoldChange,
115
+ }: {
116
+ active: boolean;
117
+ disabled: boolean;
118
+ onHoldChange: (holding: boolean) => void;
119
+ }) {
120
+ const startHold = (event: ReactPointerEvent<HTMLButtonElement>) => {
121
+ if (disabled) return;
122
+ event.preventDefault();
123
+ event.stopPropagation();
124
+ onHoldChange(true);
125
+ const release = () => {
126
+ onHoldChange(false);
127
+ window.removeEventListener("pointerup", release);
128
+ window.removeEventListener("pointercancel", release);
129
+ window.removeEventListener("blur", release);
130
+ };
131
+ window.addEventListener("pointerup", release);
132
+ window.addEventListener("pointercancel", release);
133
+ window.addEventListener("blur", release);
134
+ };
135
+ const stopHold = (event: ReactPointerEvent<HTMLButtonElement>) => {
136
+ if (disabled) return;
137
+ event.preventDefault();
138
+ event.stopPropagation();
139
+ onHoldChange(false);
140
+ };
141
+
142
+ return (
143
+ <button
144
+ type="button"
145
+ disabled={disabled}
146
+ aria-pressed={active}
147
+ aria-label="Hold to show original"
148
+ onPointerDown={startHold}
149
+ onPointerUp={stopHold}
150
+ onPointerCancel={stopHold}
151
+ onBlur={() => {
152
+ if (active) onHoldChange(false);
153
+ }}
154
+ onKeyDown={(event) => {
155
+ if (disabled || (event.key !== " " && event.key !== "Enter")) return;
156
+ event.preventDefault();
157
+ if (!active) onHoldChange(true);
158
+ }}
159
+ onKeyUp={(event) => {
160
+ if (disabled || (event.key !== " " && event.key !== "Enter")) return;
161
+ event.preventDefault();
162
+ onHoldChange(false);
163
+ }}
164
+ className={`flex h-6 w-6 flex-shrink-0 items-center justify-center rounded transition-colors ${
165
+ active
166
+ ? "bg-studio-accent text-black"
167
+ : "text-panel-text-4 hover:bg-panel-hover hover:text-panel-text-1"
168
+ } disabled:cursor-not-allowed disabled:opacity-40`}
169
+ title="Hold to show original"
170
+ >
171
+ <Compare size={13} />
172
+ </button>
173
+ );
174
+ }
175
+
176
+ export function ColorGradingSection({
177
+ element,
178
+ assets,
179
+ previewIframeRef,
180
+ onImportAssets,
181
+ onSetAttributeLive,
182
+ }: {
183
+ element: DomEditSelection;
184
+ assets: string[];
185
+ previewIframeRef?: RefObject<HTMLIFrameElement | null>;
186
+ onImportAssets?: (files: FileList, dir?: string) => Promise<string[]>;
187
+ onSetAttributeLive: (attr: string, value: string | null) => void | Promise<void>;
188
+ }) {
189
+ const [grading, setGrading] = useState(() => readColorGradingFromElement(element));
190
+ const [compareEnabled, setCompareEnabled] = useState(false);
191
+ const [runtimeStatus, setRuntimeStatus] = useState<RuntimeColorGradingStatus>(() => ({
192
+ state: "pending",
193
+ message: "Waiting for runtime",
194
+ }));
195
+ const persistTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
196
+ const pendingPersistValueRef = useRef<string | null | undefined>(undefined);
197
+ const onSetAttributeLiveRef = useRef(onSetAttributeLive);
198
+ const compareEnabledRef = useRef(compareEnabled);
199
+ onSetAttributeLiveRef.current = onSetAttributeLive;
200
+ compareEnabledRef.current = compareEnabled;
201
+ const target = useMemo(
202
+ (): HfColorGradingTarget => ({
203
+ id: element.id ?? null,
204
+ hfId: element.hfId ?? null,
205
+ selector: element.selector ?? null,
206
+ selectorIndex: element.selectorIndex ?? null,
207
+ }),
208
+ [element.hfId, element.id, element.selector, element.selectorIndex],
209
+ );
210
+
211
+ const refreshRuntimeStatus = useCallback(() => {
212
+ setRuntimeStatus(readRuntimeColorGradingStatus(previewIframeRef?.current, target));
213
+ }, [previewIframeRef, target]);
214
+
215
+ useEffect(() => {
216
+ refreshRuntimeStatus();
217
+ }, [refreshRuntimeStatus]);
218
+
219
+ useEffect(() => {
220
+ const iframe = previewIframeRef?.current;
221
+ if (!iframe) return;
222
+ const refresh = () => {
223
+ window.setTimeout(refreshRuntimeStatus, 50);
224
+ };
225
+ iframe.addEventListener("load", refresh);
226
+ const timer = window.setTimeout(refreshRuntimeStatus, 80);
227
+ return () => {
228
+ iframe.removeEventListener("load", refresh);
229
+ window.clearTimeout(timer);
230
+ };
231
+ }, [previewIframeRef, refreshRuntimeStatus]);
232
+
233
+ useEffect(() => {
234
+ return () => {
235
+ if (persistTimerRef.current) clearTimeout(persistTimerRef.current);
236
+ if (pendingPersistValueRef.current !== undefined) {
237
+ void onSetAttributeLiveRef.current(COLOR_GRADING_DATA_KEY, pendingPersistValueRef.current);
238
+ pendingPersistValueRef.current = undefined;
239
+ }
240
+ };
241
+ }, []);
242
+
243
+ const postColorGrading = useCallback(
244
+ (nextGrading: NormalizedHfColorGrading) => {
245
+ previewIframeRef?.current?.contentWindow?.postMessage(
246
+ {
247
+ source: "hf-parent",
248
+ type: "control",
249
+ action: "set-color-grading",
250
+ target,
251
+ grading: toBridgeColorGrading(nextGrading),
252
+ },
253
+ "*",
254
+ );
255
+ },
256
+ [previewIframeRef, target],
257
+ );
258
+
259
+ const postCompare = useCallback(
260
+ (enabled: boolean) => {
261
+ previewIframeRef?.current?.contentWindow?.postMessage(
262
+ {
263
+ source: "hf-parent",
264
+ type: "control",
265
+ action: "set-color-grading-compare",
266
+ target,
267
+ compare: {
268
+ enabled,
269
+ position: 1,
270
+ lineWidth: 0,
271
+ },
272
+ },
273
+ "*",
274
+ );
275
+ },
276
+ [previewIframeRef, target],
277
+ );
278
+
279
+ useEffect(
280
+ () => () => {
281
+ postCompare(false);
282
+ },
283
+ [postCompare],
284
+ );
285
+
286
+ const commitColorGrading = useCallback(
287
+ (nextGrading: NormalizedHfColorGrading) => {
288
+ setGrading(nextGrading);
289
+ setRuntimeStatus({ state: "pending", message: "Updating shader" });
290
+ postColorGrading(nextGrading);
291
+ const active = isHfColorGradingActive(nextGrading);
292
+ if (compareEnabledRef.current) {
293
+ postCompare(active);
294
+ if (!active) setCompareEnabled(false);
295
+ }
296
+ window.setTimeout(refreshRuntimeStatus, 50);
297
+ if (persistTimerRef.current) clearTimeout(persistTimerRef.current);
298
+ pendingPersistValueRef.current = isHfColorGradingActive(nextGrading)
299
+ ? serializeHfColorGrading(nextGrading)
300
+ : null;
301
+ persistTimerRef.current = setTimeout(() => {
302
+ const value = pendingPersistValueRef.current;
303
+ pendingPersistValueRef.current = undefined;
304
+ void onSetAttributeLive(COLOR_GRADING_DATA_KEY, value ?? null);
305
+ }, 350);
306
+ },
307
+ [onSetAttributeLive, postColorGrading, postCompare, refreshRuntimeStatus],
308
+ );
309
+
310
+ const commitCompare = useCallback(
311
+ (enabled: boolean) => {
312
+ const nextEnabled = enabled && isHfColorGradingActive(grading);
313
+ setCompareEnabled(nextEnabled);
314
+ if (nextEnabled) postColorGrading(grading);
315
+ postCompare(nextEnabled);
316
+ window.setTimeout(refreshRuntimeStatus, 50);
317
+ },
318
+ [grading, postColorGrading, postCompare, refreshRuntimeStatus],
319
+ );
320
+
321
+ return (
322
+ <Section
323
+ title="Color Grading"
324
+ icon={<Palette size={15} />}
325
+ accessory={
326
+ <div className="flex min-w-0 items-center gap-1.5">
327
+ <HoldBeforeButton
328
+ active={compareEnabled}
329
+ disabled={!isHfColorGradingActive(grading)}
330
+ onHoldChange={commitCompare}
331
+ />
332
+ <StatusPill status={runtimeStatus} />
333
+ <button
334
+ type="button"
335
+ onClick={(event) => {
336
+ event.stopPropagation();
337
+ commitColorGrading(DEFAULT_COLOR_GRADING);
338
+ }}
339
+ className="flex h-6 w-6 flex-shrink-0 items-center justify-center rounded text-panel-text-4 transition-colors hover:bg-panel-hover hover:text-panel-text-1"
340
+ title="Reset grading"
341
+ >
342
+ <RotateCcw size={12} />
343
+ </button>
344
+ </div>
345
+ }
346
+ >
347
+ <ColorGradingControls
348
+ grading={grading}
349
+ assets={assets}
350
+ onImportAssets={onImportAssets}
351
+ onCommitColorGrading={commitColorGrading}
352
+ />
353
+ </Section>
354
+ );
355
+ }
@@ -15,6 +15,7 @@ export interface PropertyPanelProps {
15
15
  onClearSelection: () => void;
16
16
  onSetStyle: (prop: string, value: string) => void | Promise<void>;
17
17
  onSetAttribute: (attr: string, value: string) => void | Promise<void>;
18
+ onSetAttributeLive: (attr: string, value: string | null) => void | Promise<void>;
18
19
  onSetHtmlAttribute: (attr: string, value: string | null) => void | Promise<void>;
19
20
  onSetManualOffset: (element: DomEditSelection, next: { x: number; y: number }) => void;
20
21
  onSetManualSize: (element: DomEditSelection, next: { width: number; height: number }) => void;
@@ -24,7 +25,7 @@ export interface PropertyPanelProps {
24
25
  onAddTextField: (afterFieldKey?: string) => string | Promise<string | null> | null;
25
26
  onRemoveTextField: (fieldKey: string) => void;
26
27
  onAskAgent: () => void;
27
- onImportAssets?: (files: FileList) => Promise<string[]>;
28
+ onImportAssets?: (files: FileList, dir?: string) => Promise<string[]>;
28
29
  fontAssets?: ImportedFontAsset[];
29
30
  onImportFonts?: (files: FileList | File[]) => Promise<ImportedFontAsset[]>;
30
31
  previewIframeRef?: React.RefObject<HTMLIFrameElement | null>;
@@ -348,46 +348,41 @@ export function Section({
348
348
  defaultCollapsed?: boolean;
349
349
  }) {
350
350
  const [collapsed, setCollapsed] = useState(defaultCollapsed);
351
+ const collapseIcon = collapsed ? (
352
+ <svg
353
+ width="12"
354
+ height="12"
355
+ viewBox="0 0 12 12"
356
+ fill="none"
357
+ className="flex-shrink-0 text-panel-text-5"
358
+ >
359
+ <path d="M6 2.5v7M2.5 6h7" stroke="currentColor" strokeWidth="1.2" strokeLinecap="round" />
360
+ </svg>
361
+ ) : (
362
+ <svg
363
+ width="10"
364
+ height="10"
365
+ viewBox="0 0 10 10"
366
+ fill="currentColor"
367
+ className="flex-shrink-0 text-panel-text-5"
368
+ >
369
+ <path d="M2 3l3 4 3-4z" />
370
+ </svg>
371
+ );
351
372
 
352
373
  return (
353
374
  <section className="min-w-0 border-t border-panel-border">
354
- <button
355
- type="button"
356
- onClick={() => setCollapsed((v) => !v)}
357
- className="flex w-full items-center justify-between gap-2 px-4 py-2.5"
358
- >
359
- <h3 className="text-[12px] font-semibold text-panel-text-1">{title}</h3>
360
- <div className="flex items-center gap-2">
361
- {accessory}
362
- {collapsed && (
363
- <svg
364
- width="12"
365
- height="12"
366
- viewBox="0 0 12 12"
367
- fill="none"
368
- className="flex-shrink-0 text-panel-text-5"
369
- >
370
- <path
371
- d="M6 2.5v7M2.5 6h7"
372
- stroke="currentColor"
373
- strokeWidth="1.2"
374
- strokeLinecap="round"
375
- />
376
- </svg>
377
- )}
378
- {!collapsed && (
379
- <svg
380
- width="10"
381
- height="10"
382
- viewBox="0 0 10 10"
383
- fill="currentColor"
384
- className="flex-shrink-0 text-panel-text-5"
385
- >
386
- <path d="M2 3l3 4 3-4z" />
387
- </svg>
388
- )}
389
- </div>
390
- </button>
375
+ <div className="flex w-full items-center gap-2 px-4 py-2.5">
376
+ <button
377
+ type="button"
378
+ onClick={() => setCollapsed((v) => !v)}
379
+ className="flex min-w-0 flex-1 items-center justify-between gap-2 text-left"
380
+ >
381
+ <h3 className="text-[12px] font-semibold text-panel-text-1">{title}</h3>
382
+ {collapseIcon}
383
+ </button>
384
+ {accessory && <div className="flex flex-shrink-0 items-center">{accessory}</div>}
385
+ </div>
391
386
  {!collapsed && <div className="px-4 pb-3">{children}</div>}
392
387
  </section>
393
388
  );
@@ -14,6 +14,7 @@ export interface DomEditActionsValue extends Pick<
14
14
  | "clearDomSelection"
15
15
  | "handleDomStyleCommit"
16
16
  | "handleDomAttributeCommit"
17
+ | "handleDomAttributeLiveCommit"
17
18
  | "handleDomHtmlAttributeCommit"
18
19
  | "handleDomPathOffsetCommit"
19
20
  | "handleDomGroupPathOffsetCommit"
@@ -115,6 +116,7 @@ export function DomEditProvider({
115
116
  clearDomSelection,
116
117
  handleDomStyleCommit,
117
118
  handleDomAttributeCommit,
119
+ handleDomAttributeLiveCommit,
118
120
  handleDomHtmlAttributeCommit,
119
121
  handleDomPathOffsetCommit,
120
122
  handleDomGroupPathOffsetCommit,
@@ -189,6 +191,7 @@ export function DomEditProvider({
189
191
  clearDomSelection,
190
192
  handleDomStyleCommit,
191
193
  handleDomAttributeCommit,
194
+ handleDomAttributeLiveCommit,
192
195
  handleDomHtmlAttributeCommit,
193
196
  handleDomPathOffsetCommit,
194
197
  handleDomGroupPathOffsetCommit,
@@ -245,6 +248,7 @@ export function DomEditProvider({
245
248
  clearDomSelection,
246
249
  handleDomStyleCommit,
247
250
  handleDomAttributeCommit,
251
+ handleDomAttributeLiveCommit,
248
252
  handleDomHtmlAttributeCommit,
249
253
  handleDomPathOffsetCommit,
250
254
  handleDomGroupPathOffsetCommit,
@@ -22,6 +22,8 @@ export function PanelLayoutProvider({
22
22
  setRightCollapsed,
23
23
  rightPanelTab,
24
24
  setRightPanelTab,
25
+ rightInspectorPanes,
26
+ toggleRightInspectorPane,
25
27
  toggleLeftSidebar,
26
28
  handlePanelResizeStart,
27
29
  handlePanelResizeMove,
@@ -43,6 +45,8 @@ export function PanelLayoutProvider({
43
45
  setRightCollapsed,
44
46
  rightPanelTab,
45
47
  setRightPanelTab,
48
+ rightInspectorPanes,
49
+ toggleRightInspectorPane,
46
50
  toggleLeftSidebar,
47
51
  handlePanelResizeStart,
48
52
  handlePanelResizeMove,
@@ -58,6 +62,8 @@ export function PanelLayoutProvider({
58
62
  setRightCollapsed,
59
63
  rightPanelTab,
60
64
  setRightPanelTab,
65
+ rightInspectorPanes,
66
+ toggleRightInspectorPane,
61
67
  toggleLeftSidebar,
62
68
  handlePanelResizeStart,
63
69
  handlePanelResizeMove,
@@ -16,6 +16,9 @@ import { useDomGeometryCommits } from "./useDomGeometryCommits";
16
16
  import { useElementLifecycleOps } from "./useElementLifecycleOps";
17
17
  import { formatFieldsSuffix } from "./gsapScriptCommitHelpers";
18
18
 
19
+ // Re-export so existing consumers keep their import path
20
+ export { GSAP_CSS_FALLBACK_BLOCKED_MESSAGE } from "./useDomGeometryCommits";
21
+
19
22
  // ── Helpers ──
20
23
 
21
24
  function formatUnsafeFieldList(fields: Array<{ path: string }>): string {
@@ -42,6 +45,8 @@ interface RecordEditInput {
42
45
  files: Record<string, { before: string; after: string }>;
43
46
  }
44
47
 
48
+ export type { PersistDomEditOperations } from "./domEditCommitTypes";
49
+
45
50
  export interface UseDomEditCommitsParams {
46
51
  activeCompPath: string | null;
47
52
  previewIframeRef: React.MutableRefObject<HTMLIFrameElement | null>;
@@ -238,6 +243,7 @@ export function useDomEditCommits({
238
243
  const {
239
244
  handleDomStyleCommit,
240
245
  handleDomAttributeCommit,
246
+ handleDomAttributeLiveCommit,
241
247
  handleDomHtmlAttributeCommit,
242
248
  handleDomTextCommit,
243
249
  commitDomTextFields,
@@ -297,6 +303,7 @@ export function useDomEditCommits({
297
303
  resolveImportedFontAsset,
298
304
  handleDomStyleCommit,
299
305
  handleDomAttributeCommit,
306
+ handleDomAttributeLiveCommit,
300
307
  handleDomHtmlAttributeCommit,
301
308
  handleDomTextCommit,
302
309
  commitDomTextFields,
@@ -203,6 +203,7 @@ export function useDomEditSession({
203
203
  resolveImportedFontAsset,
204
204
  handleDomStyleCommit,
205
205
  handleDomAttributeCommit,
206
+ handleDomAttributeLiveCommit,
206
207
  handleDomHtmlAttributeCommit,
207
208
  handleDomTextCommit,
208
209
  handleDomTextFieldStyleCommit,
@@ -265,8 +266,6 @@ export function useDomEditSession({
265
266
  handleGsapRemoveAllKeyframes,
266
267
  handleResetSelectedElementKeyframes,
267
268
  } = useDomEditWiring({
268
- // Pre-existing prop-drilling clone (same param set forwarded to
269
- // useDomEditWiring); surfaced by this PR's adjacent edits, not introduced.
270
269
  // fallow-ignore-next-line code-duplication
271
270
  projectId,
272
271
  activeCompPath,
@@ -374,6 +373,7 @@ export function useDomEditSession({
374
373
  clearDomSelection,
375
374
  handleDomStyleCommit,
376
375
  handleDomAttributeCommit,
376
+ handleDomAttributeLiveCommit,
377
377
  handleDomHtmlAttributeCommit,
378
378
  handleDomPathOffsetCommit: handleGsapAwarePathOffsetCommit,
379
379
  handleDomGroupPathOffsetCommit,