@hyperframes/studio 0.7.37 → 0.7.38
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.
- package/dist/assets/hyperframes-player-BBrKzTGd.js +459 -0
- package/dist/assets/index-BLRTwY5l.js +396 -0
- package/dist/assets/{index-BXYJbIax.js → index-ClUipc8i.js} +1 -1
- package/dist/assets/index-DJaiR8T2.css +1 -0
- package/dist/assets/{index-Ct-X2wlf.js → index-Ykq7ihge.js} +1 -1
- package/dist/index.d.ts +35 -10
- package/dist/index.html +2 -2
- package/dist/index.js +5146 -3321
- package/dist/index.js.map +1 -1
- package/package.json +7 -7
- package/src/App.tsx +19 -18
- package/src/components/AskAgentModal.tsx +23 -6
- package/src/components/LintModal.tsx +40 -10
- package/src/components/MediaPreview.tsx +36 -1
- package/src/components/SaveQueuePausedBanner.tsx +7 -6
- package/src/components/StudioErrorBoundary.tsx +19 -31
- package/src/components/StudioFeedbackBar.tsx +11 -2
- package/src/components/StudioGlobalDragOverlay.tsx +1 -1
- package/src/components/StudioHeader.tsx +184 -106
- package/src/components/StudioLeftSidebar.tsx +24 -3
- package/src/components/StudioOverlays.tsx +34 -8
- package/src/components/StudioRightPanel.tsx +154 -9
- package/src/components/StudioSplash.tsx +6 -3
- package/src/components/StudioToast.tsx +12 -11
- package/src/components/TimelineToolbar.test.tsx +9 -3
- package/src/components/TimelineToolbar.tsx +63 -29
- package/src/components/editor/PropertyPanel.tsx +18 -17
- package/src/components/editor/colorGradingScopePatch.test.ts +74 -0
- package/src/components/editor/colorGradingScopePatch.ts +57 -0
- package/src/components/editor/manualEditingAvailability.test.ts +0 -12
- package/src/components/editor/manualEditingAvailability.ts +0 -6
- package/src/components/editor/propertyPanelColorGradingControls.tsx +371 -329
- package/src/components/editor/propertyPanelColorGradingSection.tsx +274 -55
- package/src/components/editor/propertyPanelColorGradingSlider.tsx +275 -0
- package/src/components/editor/propertyPanelHelpers.ts +22 -91
- package/src/components/editor/propertyPanelMediaSection.tsx +253 -99
- package/src/components/editor/propertyPanelTypes.ts +111 -0
- package/src/components/nle/CompositionBreadcrumb.tsx +3 -2
- package/src/components/nle/NLELayout.tsx +54 -51
- package/src/components/nle/NLEPreview.tsx +41 -5
- package/src/components/nle/TimelineResizeDivider.tsx +97 -0
- package/src/components/nle/usePreviewBlockDrop.ts +31 -7
- package/src/components/renders/RenderQueue.tsx +125 -24
- package/src/components/renders/RenderQueueItem.tsx +132 -64
- package/src/components/renders/useRenderQueue.ts +136 -24
- package/src/components/storyboard/StoryboardFrameFocus.tsx +39 -16
- package/src/components/storyboard/StoryboardLoaded.tsx +19 -2
- package/src/components/storyboard/StoryboardView.tsx +6 -0
- package/src/components/studioColorGradingScope.ts +124 -0
- package/src/components/studioMediaJobs.ts +123 -0
- package/src/components/ui/Button.tsx +20 -11
- package/src/components/ui/HyperframesLoader.tsx +9 -2
- package/src/components/ui/SearchInput.tsx +53 -0
- package/src/components/ui/Tooltip.tsx +52 -6
- package/src/components/ui/VideoFrameThumbnail.tsx +26 -3
- package/src/components/ui/useDialogBehavior.ts +83 -0
- package/src/contexts/PanelLayoutContext.tsx +3 -0
- package/src/contexts/StudioContext.tsx +7 -0
- package/src/hooks/useCompositionContentLoader.ts +40 -0
- package/src/hooks/useEditorSave.ts +14 -0
- package/src/hooks/useFileManager.ts +39 -25
- package/src/hooks/useFrameCapture.ts +12 -1
- package/src/hooks/usePanelLayout.ts +1 -0
- package/src/hooks/usePreviewPersistence.ts +24 -15
- package/src/hooks/useStudioContextValue.ts +5 -0
- package/src/hooks/useToast.ts +66 -13
- package/src/styles/studio.css +61 -0
- package/src/utils/sdkResolverShadow.test.ts +53 -3
- package/src/utils/sdkResolverShadow.ts +41 -1
- package/src/utils/studioPendingEdits.test.ts +43 -0
- package/src/utils/studioPendingEdits.ts +45 -0
- package/src/utils/studioUiPreferences.ts +4 -0
- package/dist/assets/hyperframes-player-BGuumSiM.js +0 -459
- package/dist/assets/index-DmkOvZns.css +0 -1
- package/dist/assets/index-SYSbQyK4.js +0 -396
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
import { useCallback, useEffect, useRef, useState } from "react";
|
|
2
|
+
import { Minus, Plus, RotateCcw, Settings } from "../../icons/SystemIcons";
|
|
3
|
+
import { LABEL } from "./propertyPanelHelpers";
|
|
4
|
+
|
|
5
|
+
const SLIDER_THUMB_SIZE = 10;
|
|
6
|
+
const SLIDER_THUMB_RADIUS = SLIDER_THUMB_SIZE / 2;
|
|
7
|
+
|
|
8
|
+
function clampNumber(value: number, min: number, max: number): number {
|
|
9
|
+
if (!Number.isFinite(value)) return min;
|
|
10
|
+
return Math.min(max, Math.max(min, value));
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function formatNumericInput(value: number, scale: number): string {
|
|
14
|
+
const scaled = value / scale;
|
|
15
|
+
return scale === 100 ? scaled.toFixed(2) : String(Math.round(scaled));
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function parseNumericInput(value: string, scale: number): number | null {
|
|
19
|
+
const parsed = Number(value);
|
|
20
|
+
if (!Number.isFinite(parsed)) return null;
|
|
21
|
+
return parsed * scale;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function tickPercent(value: number, min: number, max: number): number {
|
|
25
|
+
if (max <= min) return 0;
|
|
26
|
+
return ((value - min) / (max - min)) * 100;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function ColorGradingSliderControl({
|
|
30
|
+
label,
|
|
31
|
+
value,
|
|
32
|
+
min,
|
|
33
|
+
max,
|
|
34
|
+
step,
|
|
35
|
+
neutral = min,
|
|
36
|
+
scale = 1,
|
|
37
|
+
suffix = "",
|
|
38
|
+
displayValue,
|
|
39
|
+
disabled,
|
|
40
|
+
onCommit,
|
|
41
|
+
onReset,
|
|
42
|
+
settings,
|
|
43
|
+
}: {
|
|
44
|
+
label: string;
|
|
45
|
+
value: number;
|
|
46
|
+
min: number;
|
|
47
|
+
max: number;
|
|
48
|
+
step: number;
|
|
49
|
+
neutral?: number;
|
|
50
|
+
scale?: number;
|
|
51
|
+
suffix?: string;
|
|
52
|
+
displayValue: string;
|
|
53
|
+
disabled?: boolean;
|
|
54
|
+
onCommit: (nextValue: number) => void;
|
|
55
|
+
onReset?: () => void;
|
|
56
|
+
settings?: {
|
|
57
|
+
active?: boolean;
|
|
58
|
+
label: string;
|
|
59
|
+
onClick: () => void;
|
|
60
|
+
};
|
|
61
|
+
}) {
|
|
62
|
+
const [draftState, setDraftState] = useState<{ value: number; source: number } | null>(null);
|
|
63
|
+
const [inputDraft, setInputDraft] = useState<{ value: string; source: number } | null>(null);
|
|
64
|
+
const commitTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
65
|
+
const valueRef = useRef(value);
|
|
66
|
+
valueRef.current = value;
|
|
67
|
+
|
|
68
|
+
useEffect(
|
|
69
|
+
() => () => {
|
|
70
|
+
if (commitTimerRef.current) clearTimeout(commitTimerRef.current);
|
|
71
|
+
},
|
|
72
|
+
[],
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
const clampDraft = useCallback(
|
|
76
|
+
(nextValue: number) => clampNumber(nextValue, min, max),
|
|
77
|
+
[max, min],
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
const setLocalDraft = useCallback(
|
|
81
|
+
(nextValue: number) => {
|
|
82
|
+
const clamped = clampDraft(nextValue);
|
|
83
|
+
const source = valueRef.current;
|
|
84
|
+
setDraftState({ value: clamped, source });
|
|
85
|
+
setInputDraft({ value: formatNumericInput(clamped, scale), source });
|
|
86
|
+
return clamped;
|
|
87
|
+
},
|
|
88
|
+
[clampDraft, scale],
|
|
89
|
+
);
|
|
90
|
+
|
|
91
|
+
const commitDraft = useCallback(
|
|
92
|
+
(nextValue: number) => {
|
|
93
|
+
const clamped = setLocalDraft(nextValue);
|
|
94
|
+
if (commitTimerRef.current) clearTimeout(commitTimerRef.current);
|
|
95
|
+
if (clamped !== valueRef.current) onCommit(clamped);
|
|
96
|
+
},
|
|
97
|
+
[onCommit, setLocalDraft],
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
const scheduleCommit = useCallback(
|
|
101
|
+
(nextValue: number) => {
|
|
102
|
+
const clamped = setLocalDraft(nextValue);
|
|
103
|
+
if (commitTimerRef.current) clearTimeout(commitTimerRef.current);
|
|
104
|
+
commitTimerRef.current = setTimeout(() => {
|
|
105
|
+
if (clamped !== valueRef.current) onCommit(clamped);
|
|
106
|
+
}, 40);
|
|
107
|
+
},
|
|
108
|
+
[onCommit, setLocalDraft],
|
|
109
|
+
);
|
|
110
|
+
|
|
111
|
+
const draft = draftState?.source === value ? draftState.value : value;
|
|
112
|
+
const inputValue =
|
|
113
|
+
inputDraft?.source === value ? inputDraft.value : formatNumericInput(draft, scale);
|
|
114
|
+
|
|
115
|
+
const commitInputDraft = useCallback(() => {
|
|
116
|
+
const parsed = parseNumericInput(inputValue, scale);
|
|
117
|
+
if (parsed === null) {
|
|
118
|
+
setInputDraft(null);
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
commitDraft(parsed);
|
|
122
|
+
}, [commitDraft, inputValue, scale]);
|
|
123
|
+
|
|
124
|
+
const nudge = useCallback(
|
|
125
|
+
(direction: -1 | 1) => {
|
|
126
|
+
commitDraft(draft + step * direction);
|
|
127
|
+
},
|
|
128
|
+
[commitDraft, draft, step],
|
|
129
|
+
);
|
|
130
|
+
|
|
131
|
+
const range = max - min;
|
|
132
|
+
const valuePercent = range === 0 ? 0 : ((draft - min) / range) * 100;
|
|
133
|
+
const neutralPercent = range === 0 ? 0 : ((neutral - min) / range) * 100;
|
|
134
|
+
const fillLeft = Math.min(valuePercent, neutralPercent);
|
|
135
|
+
const fillWidth = Math.abs(valuePercent - neutralPercent);
|
|
136
|
+
const ticks = Array.from(new Set([min, neutral, max])).sort((a, b) => a - b);
|
|
137
|
+
|
|
138
|
+
return (
|
|
139
|
+
<div className="grid min-w-0 gap-0.5 rounded-md bg-panel-input/30 px-1.5 py-1">
|
|
140
|
+
<div className="flex min-w-0 items-center gap-1">
|
|
141
|
+
<span className={`${LABEL} min-w-0 flex-1 truncate`}>{label}</span>
|
|
142
|
+
{settings && (
|
|
143
|
+
<button
|
|
144
|
+
type="button"
|
|
145
|
+
disabled={disabled}
|
|
146
|
+
aria-label={settings.label}
|
|
147
|
+
onClick={(event) => {
|
|
148
|
+
event.stopPropagation();
|
|
149
|
+
settings.onClick();
|
|
150
|
+
}}
|
|
151
|
+
className={`relative flex h-5 w-5 flex-shrink-0 items-center justify-center rounded transition-colors hover:bg-panel-hover hover:text-panel-text-1 disabled:cursor-not-allowed disabled:opacity-40 ${
|
|
152
|
+
settings.active ? "text-studio-accent" : "text-panel-text-5"
|
|
153
|
+
}`}
|
|
154
|
+
title={settings.label}
|
|
155
|
+
>
|
|
156
|
+
<Settings size={11} />
|
|
157
|
+
{settings.active && (
|
|
158
|
+
<span className="absolute right-0.5 top-0.5 h-1 w-1 rounded-full bg-studio-accent" />
|
|
159
|
+
)}
|
|
160
|
+
</button>
|
|
161
|
+
)}
|
|
162
|
+
{onReset && (
|
|
163
|
+
<button
|
|
164
|
+
type="button"
|
|
165
|
+
disabled={disabled}
|
|
166
|
+
aria-label={`Reset ${label}`}
|
|
167
|
+
onClick={(event) => {
|
|
168
|
+
event.stopPropagation();
|
|
169
|
+
onReset();
|
|
170
|
+
}}
|
|
171
|
+
className="flex h-5 w-5 flex-shrink-0 items-center justify-center rounded text-panel-text-5 transition-colors hover:bg-panel-hover hover:text-panel-text-1 disabled:cursor-not-allowed disabled:opacity-40"
|
|
172
|
+
title={`Reset ${label}`}
|
|
173
|
+
>
|
|
174
|
+
<RotateCcw size={11} />
|
|
175
|
+
</button>
|
|
176
|
+
)}
|
|
177
|
+
</div>
|
|
178
|
+
|
|
179
|
+
<div className="relative h-5 min-w-0">
|
|
180
|
+
<div
|
|
181
|
+
data-color-grading-slider-track="true"
|
|
182
|
+
className="pointer-events-none absolute inset-y-0 z-0"
|
|
183
|
+
style={{ left: SLIDER_THUMB_RADIUS, right: SLIDER_THUMB_RADIUS }}
|
|
184
|
+
>
|
|
185
|
+
{ticks.map((tick) => (
|
|
186
|
+
<div
|
|
187
|
+
key={tick}
|
|
188
|
+
data-color-grading-slider-tick="true"
|
|
189
|
+
className="absolute top-1/2 h-3 w-px -translate-y-1/2 bg-panel-text-3"
|
|
190
|
+
style={{ left: `${tickPercent(tick, min, max)}%` }}
|
|
191
|
+
title={String(tick / scale)}
|
|
192
|
+
/>
|
|
193
|
+
))}
|
|
194
|
+
<div className="absolute left-0 right-0 top-1/2 z-10 h-0.5 -translate-y-1/2 rounded-full bg-panel-border" />
|
|
195
|
+
<div
|
|
196
|
+
className="absolute top-1/2 z-20 h-0.5 -translate-y-1/2 rounded-full bg-studio-accent"
|
|
197
|
+
style={{ left: `${fillLeft}%`, width: `${fillWidth}%` }}
|
|
198
|
+
/>
|
|
199
|
+
</div>
|
|
200
|
+
<input
|
|
201
|
+
type="range"
|
|
202
|
+
min={min}
|
|
203
|
+
max={max}
|
|
204
|
+
step={step}
|
|
205
|
+
value={draft}
|
|
206
|
+
disabled={disabled}
|
|
207
|
+
aria-label={label}
|
|
208
|
+
onChange={(event) => scheduleCommit(Number(event.currentTarget.value))}
|
|
209
|
+
onMouseUp={() => commitDraft(draft)}
|
|
210
|
+
onTouchEnd={() => commitDraft(draft)}
|
|
211
|
+
onBlur={() => commitDraft(draft)}
|
|
212
|
+
className="hf-color-grading-range absolute left-0 right-0 top-1/2 z-30 min-w-0 w-full -translate-y-1/2"
|
|
213
|
+
title={displayValue}
|
|
214
|
+
/>
|
|
215
|
+
</div>
|
|
216
|
+
|
|
217
|
+
<div className="flex min-w-0 items-center justify-end gap-1">
|
|
218
|
+
<div className="flex flex-shrink-0 items-center rounded-md bg-panel-input px-1.5 py-px">
|
|
219
|
+
<input
|
|
220
|
+
type="number"
|
|
221
|
+
value={inputValue}
|
|
222
|
+
min={min / scale}
|
|
223
|
+
max={max / scale}
|
|
224
|
+
step={step / scale}
|
|
225
|
+
disabled={disabled}
|
|
226
|
+
onChange={(event) =>
|
|
227
|
+
setInputDraft({ value: event.currentTarget.value, source: valueRef.current })
|
|
228
|
+
}
|
|
229
|
+
onBlur={commitInputDraft}
|
|
230
|
+
onKeyDown={(event) => {
|
|
231
|
+
if (event.key === "Enter") {
|
|
232
|
+
event.currentTarget.blur();
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
if (event.key === "ArrowUp") {
|
|
236
|
+
event.preventDefault();
|
|
237
|
+
nudge(1);
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
if (event.key === "ArrowDown") {
|
|
241
|
+
event.preventDefault();
|
|
242
|
+
nudge(-1);
|
|
243
|
+
}
|
|
244
|
+
}}
|
|
245
|
+
className="hf-color-grading-number h-4 w-[36px] bg-transparent text-right text-[10px] font-medium tabular-nums text-panel-text-1 outline-none disabled:cursor-not-allowed"
|
|
246
|
+
title={displayValue}
|
|
247
|
+
/>
|
|
248
|
+
{suffix && <span className="ml-0.5 text-[10px] text-panel-text-5">{suffix}</span>}
|
|
249
|
+
</div>
|
|
250
|
+
<div className="flex flex-shrink-0 overflow-hidden rounded-md bg-panel-input">
|
|
251
|
+
<button
|
|
252
|
+
type="button"
|
|
253
|
+
disabled={disabled}
|
|
254
|
+
aria-label={`Decrease ${label}`}
|
|
255
|
+
onClick={() => nudge(-1)}
|
|
256
|
+
className="flex h-5 w-5 items-center justify-center text-panel-text-4 transition-colors hover:bg-panel-hover hover:text-panel-text-1 disabled:cursor-not-allowed disabled:opacity-40"
|
|
257
|
+
title={`Decrease ${label}`}
|
|
258
|
+
>
|
|
259
|
+
<Minus size={11} />
|
|
260
|
+
</button>
|
|
261
|
+
<button
|
|
262
|
+
type="button"
|
|
263
|
+
disabled={disabled}
|
|
264
|
+
aria-label={`Increase ${label}`}
|
|
265
|
+
onClick={() => nudge(1)}
|
|
266
|
+
className="flex h-5 w-5 items-center justify-center border-l border-panel-border text-panel-text-4 transition-colors hover:bg-panel-hover hover:text-panel-text-1 disabled:cursor-not-allowed disabled:opacity-40"
|
|
267
|
+
title={`Increase ${label}`}
|
|
268
|
+
>
|
|
269
|
+
<Plus size={11} />
|
|
270
|
+
</button>
|
|
271
|
+
</div>
|
|
272
|
+
</div>
|
|
273
|
+
</div>
|
|
274
|
+
);
|
|
275
|
+
}
|
|
@@ -1,91 +1,21 @@
|
|
|
1
1
|
import { parseCssColor, type ParsedColor } from "./colorValue";
|
|
2
2
|
import { COMMON_LOCAL_FONT_FAMILIES } from "./fontCatalog";
|
|
3
3
|
import type { DomEditSelection } from "./domEditing";
|
|
4
|
-
import type { ImportedFontAsset } from "./fontAssets";
|
|
5
4
|
import type { GsapAnimation } from "@hyperframes/parsers/gsap-parser";
|
|
6
5
|
import { roundToCenti } from "../../utils/rounding";
|
|
7
6
|
|
|
8
|
-
export
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
onSetAttributeLive: (attr: string, value: string | null) => void | Promise<void>;
|
|
21
|
-
onSetHtmlAttribute: (attr: string, value: string | null) => void | Promise<void>;
|
|
22
|
-
onSetManualOffset: (element: DomEditSelection, next: { x: number; y: number }) => void;
|
|
23
|
-
onSetManualSize: (element: DomEditSelection, next: { width: number; height: number }) => void;
|
|
24
|
-
onSetManualRotation: (element: DomEditSelection, next: { angle: number }) => void;
|
|
25
|
-
onSetText: (value: string, fieldKey?: string) => void;
|
|
26
|
-
onSetTextFieldStyle: (fieldKey: string, property: string, value: string) => void;
|
|
27
|
-
onAddTextField: (afterFieldKey?: string) => string | Promise<string | null> | null;
|
|
28
|
-
onRemoveTextField: (fieldKey: string) => void;
|
|
29
|
-
onAskAgent: () => void;
|
|
30
|
-
onImportAssets?: (files: FileList, dir?: string) => Promise<string[]>;
|
|
31
|
-
fontAssets?: ImportedFontAsset[];
|
|
32
|
-
onImportFonts?: (files: FileList | File[]) => Promise<ImportedFontAsset[]>;
|
|
33
|
-
previewIframeRef?: React.RefObject<HTMLIFrameElement | null>;
|
|
34
|
-
gsapAnimations?: import("@hyperframes/parsers/gsap-parser").GsapAnimation[];
|
|
35
|
-
gsapMultipleTimelines?: boolean;
|
|
36
|
-
gsapUnsupportedTimelinePattern?: boolean;
|
|
37
|
-
onUpdateGsapProperty?: (animId: string, prop: string, value: number | string) => void;
|
|
38
|
-
onUpdateGsapMeta?: (
|
|
39
|
-
animId: string,
|
|
40
|
-
updates: { duration?: number; ease?: string; position?: number },
|
|
41
|
-
) => void;
|
|
42
|
-
onDeleteGsapAnimation?: (animId: string) => void;
|
|
43
|
-
onAddGsapProperty?: (animId: string, prop: string) => void;
|
|
44
|
-
onRemoveGsapProperty?: (animId: string, prop: string) => void;
|
|
45
|
-
onUpdateGsapFromProperty?: (animId: string, prop: string, value: number | string) => void;
|
|
46
|
-
onAddGsapFromProperty?: (animId: string, prop: string) => void;
|
|
47
|
-
onRemoveGsapFromProperty?: (animId: string, prop: string) => void;
|
|
48
|
-
onAddGsapAnimation?: (method: "to" | "from" | "set" | "fromTo") => void;
|
|
49
|
-
onSetArcPath?: (
|
|
50
|
-
animId: string,
|
|
51
|
-
config: {
|
|
52
|
-
enabled: boolean;
|
|
53
|
-
autoRotate?: boolean | number;
|
|
54
|
-
segments?: import("@hyperframes/parsers/gsap-parser").ArcPathSegment[];
|
|
55
|
-
},
|
|
56
|
-
) => void;
|
|
57
|
-
onUpdateArcSegment?: (
|
|
58
|
-
animId: string,
|
|
59
|
-
segmentIndex: number,
|
|
60
|
-
update: Partial<import("@hyperframes/parsers/gsap-parser").ArcPathSegment>,
|
|
61
|
-
) => void;
|
|
62
|
-
/** Unroll computed (helper/loop) tweens into literal tweens for direct editing. */
|
|
63
|
-
onUnroll?: (animationId: string) => void;
|
|
64
|
-
onAddKeyframe?: (
|
|
65
|
-
animationId: string,
|
|
66
|
-
percentage: number,
|
|
67
|
-
property: string,
|
|
68
|
-
value: number | string,
|
|
69
|
-
) => void;
|
|
70
|
-
onRemoveKeyframe?: (animationId: string, percentage: number) => void;
|
|
71
|
-
onUpdateKeyframeEase?: (animationId: string, percentage: number, ease: string) => void;
|
|
72
|
-
onSetAllKeyframeEases?: (animationId: string, ease: string) => void;
|
|
73
|
-
onConvertToKeyframes?: (animationId: string, duration?: number) => void;
|
|
74
|
-
onCommitAnimatedProperty?: (
|
|
75
|
-
selection: DomEditSelection,
|
|
76
|
-
property: string,
|
|
77
|
-
value: number | string,
|
|
78
|
-
) => Promise<void>;
|
|
79
|
-
/** Batched variant: commit several props into ONE keyframe (e.g. the 3D cube's
|
|
80
|
-
* rotationX/Y/Z) so multi-axis edits don't race into adjacent duplicates. */
|
|
81
|
-
onCommitAnimatedProperties?: (
|
|
82
|
-
selection: DomEditSelection,
|
|
83
|
-
props: Record<string, number | string>,
|
|
84
|
-
) => Promise<void>;
|
|
85
|
-
onSeekToTime?: (time: number) => void;
|
|
86
|
-
recordingState?: "idle" | "recording" | "preview";
|
|
87
|
-
recordingDuration?: number;
|
|
88
|
-
onToggleRecording?: () => void;
|
|
7
|
+
export type {
|
|
8
|
+
BackgroundRemovalProgress,
|
|
9
|
+
BackgroundRemovalResult,
|
|
10
|
+
PropertyPanelProps,
|
|
11
|
+
} from "./propertyPanelTypes";
|
|
12
|
+
|
|
13
|
+
export function stripQueryAndHash(value: string): string {
|
|
14
|
+
const queryIndex = value.indexOf("?");
|
|
15
|
+
const hashIndex = value.indexOf("#");
|
|
16
|
+
if (queryIndex < 0) return hashIndex < 0 ? value : value.slice(0, hashIndex);
|
|
17
|
+
if (hashIndex < 0) return value.slice(0, queryIndex);
|
|
18
|
+
return value.slice(0, Math.min(queryIndex, hashIndex));
|
|
89
19
|
}
|
|
90
20
|
|
|
91
21
|
/* ------------------------------------------------------------------ */
|
|
@@ -322,23 +252,24 @@ export function normalizeTextMetricValue(
|
|
|
322
252
|
}
|
|
323
253
|
|
|
324
254
|
function splitCssFunctions(value: string): string[] {
|
|
255
|
+
const source = value.trim();
|
|
325
256
|
const functions: string[] = [];
|
|
326
|
-
let current = "";
|
|
327
|
-
// fallow-ignore-next-line code-duplication -- pre-existing; surfaced in this file's diff by an unrelated line shift
|
|
328
257
|
let depth = 0;
|
|
258
|
+
let start = 0;
|
|
329
259
|
|
|
330
|
-
for (
|
|
260
|
+
for (let index = 0; index < source.length; index += 1) {
|
|
261
|
+
const char = source[index];
|
|
331
262
|
if (char === "(") depth += 1;
|
|
332
263
|
if (char === ")") depth = Math.max(0, depth - 1);
|
|
333
264
|
if (/\s/.test(char) && depth === 0) {
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
265
|
+
const part = source.slice(start, index).trim();
|
|
266
|
+
if (part) functions.push(part);
|
|
267
|
+
start = index + 1;
|
|
337
268
|
}
|
|
338
|
-
current += char;
|
|
339
269
|
}
|
|
340
270
|
|
|
341
|
-
|
|
271
|
+
const lastPart = source.slice(start).trim();
|
|
272
|
+
if (lastPart) functions.push(lastPart);
|
|
342
273
|
return functions;
|
|
343
274
|
}
|
|
344
275
|
|
|
@@ -490,11 +421,11 @@ export function extractBackgroundImageUrl(value: string | undefined): string {
|
|
|
490
421
|
|
|
491
422
|
// ── GSAP runtime value readers (used by PropertyPanel) ────────────────────
|
|
492
423
|
|
|
493
|
-
// fallow-ignore-next-line complexity -- pre-existing; surfaced in this file's diff by an unrelated line shift
|
|
494
424
|
// Core transform channels the panel ALWAYS reads live — even before a just-set
|
|
495
425
|
// value (e.g. rotationX) has re-parsed into `gsapAnimations`. Without this the
|
|
496
426
|
// cube + fields drop the prop and flicker to 0 on every commit; gsap.getProperty
|
|
497
427
|
// reflects the in-place instant patch, so it's the true current value.
|
|
428
|
+
// fallow-ignore-next-line complexity
|
|
498
429
|
const ALWAYS_READ_CHANNELS = [
|
|
499
430
|
"x",
|
|
500
431
|
"y",
|