@hyperframes/studio 0.6.0 → 0.6.2
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-CzwFysqv.js +418 -0
- package/dist/assets/index-hYc4aP7M.js +117 -0
- package/dist/index.html +1 -1
- package/package.json +4 -4
- package/src/App.tsx +2 -13
- package/src/captions/components/CaptionOverlay.tsx +13 -246
- package/src/captions/components/CaptionOverlayUtils.ts +221 -0
- package/src/components/StudioPreviewArea.tsx +6 -2
- package/src/components/editor/DomEditOverlay.tsx +88 -1007
- package/src/components/editor/EaseCurveEditor.tsx +221 -0
- package/src/components/editor/FileTree.tsx +13 -621
- package/src/components/editor/FileTreeIcons.tsx +128 -0
- package/src/components/editor/FileTreeNodes.tsx +496 -0
- package/src/components/editor/MotionPanel.tsx +16 -390
- package/src/components/editor/MotionPanelFields.tsx +185 -0
- package/src/components/editor/domEditOverlayGeometry.ts +211 -0
- package/src/components/editor/domEditOverlayGestures.ts +138 -0
- package/src/components/editor/domEditOverlayStartGesture.ts +155 -0
- package/src/components/editor/domEditing.ts +44 -1150
- package/src/components/editor/domEditingAgentPrompt.ts +97 -0
- package/src/components/editor/domEditingDom.ts +266 -0
- package/src/components/editor/domEditingElement.ts +329 -0
- package/src/components/editor/domEditingLayers.ts +460 -0
- package/src/components/editor/domEditingTypes.ts +125 -0
- package/src/components/editor/manualEdits.ts +84 -1081
- package/src/components/editor/manualEditsDom.ts +436 -0
- package/src/components/editor/manualEditsParsing.ts +280 -0
- package/src/components/editor/manualEditsSnapshot.ts +333 -0
- package/src/components/editor/manualEditsTypes.ts +141 -0
- package/src/components/editor/studioMotion.ts +47 -434
- package/src/components/editor/studioMotionOps.ts +299 -0
- package/src/components/editor/studioMotionTypes.ts +168 -0
- package/src/components/editor/useDomEditOverlayGestures.ts +393 -0
- package/src/components/editor/useDomEditOverlayRects.ts +207 -0
- package/src/components/nle/NLELayout.tsx +60 -144
- package/src/components/nle/useCompositionStack.ts +126 -0
- package/src/hooks/useToast.ts +20 -0
- package/src/player/components/Timeline.tsx +189 -1418
- package/src/player/components/TimelineCanvas.tsx +434 -0
- package/src/player/components/TimelineEmptyState.tsx +102 -0
- package/src/player/components/TimelineRuler.tsx +90 -0
- package/src/player/components/timelineIcons.tsx +49 -0
- package/src/player/components/timelineLayout.ts +215 -0
- package/src/player/components/timelineUtils.ts +211 -0
- package/src/player/components/useTimelineClipDrag.ts +388 -0
- package/src/player/components/useTimelinePlayhead.ts +200 -0
- package/src/player/components/useTimelineRangeSelection.ts +135 -0
- package/src/player/hooks/usePlaybackKeyboard.ts +171 -0
- package/src/player/hooks/useTimelinePlayer.ts +69 -1372
- package/src/player/hooks/useTimelineSyncCallbacks.ts +288 -0
- package/src/player/lib/playbackAdapter.ts +145 -0
- package/src/player/lib/playbackShortcuts.ts +68 -0
- package/src/player/lib/playbackTypes.ts +60 -0
- package/src/player/lib/timelineDOM.ts +373 -0
- package/src/player/lib/timelineElementHelpers.ts +303 -0
- package/src/player/lib/timelineIframeHelpers.ts +269 -0
- package/dist/assets/hyperframes-player-DOFETgjy.js +0 -418
- package/dist/assets/index-DUqUmaoH.js +0 -117
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
/* ── Public constants ──────────────────────────────────────────────── */
|
|
2
|
+
export const STUDIO_MANUAL_EDITS_PATH = ".hyperframes/studio-manual-edits.json";
|
|
3
|
+
export const STUDIO_OFFSET_X_PROP = "--hf-studio-offset-x";
|
|
4
|
+
export const STUDIO_OFFSET_Y_PROP = "--hf-studio-offset-y";
|
|
5
|
+
export const STUDIO_WIDTH_PROP = "--hf-studio-width";
|
|
6
|
+
export const STUDIO_HEIGHT_PROP = "--hf-studio-height";
|
|
7
|
+
export const STUDIO_ROTATION_PROP = "--hf-studio-rotation";
|
|
8
|
+
|
|
9
|
+
/* ── Internal DOM attribute names ─────────────────────────────────── */
|
|
10
|
+
export const STUDIO_PATH_OFFSET_ATTR = "data-hf-studio-path-offset";
|
|
11
|
+
export const STUDIO_MANUAL_EDIT_GESTURE_ATTR = "data-hf-studio-manual-edit-gesture";
|
|
12
|
+
export const STUDIO_BOX_SIZE_ATTR = "data-hf-studio-box-size";
|
|
13
|
+
export const STUDIO_ROTATION_ATTR = "data-hf-studio-rotation";
|
|
14
|
+
export const STUDIO_ORIGINAL_TRANSLATE_ATTR = "data-hf-studio-original-translate";
|
|
15
|
+
export const STUDIO_ORIGINAL_INLINE_TRANSLATE_ATTR = "data-hf-studio-original-inline-translate";
|
|
16
|
+
export const STUDIO_ORIGINAL_WIDTH_ATTR = "data-hf-studio-original-width";
|
|
17
|
+
export const STUDIO_ORIGINAL_HEIGHT_ATTR = "data-hf-studio-original-height";
|
|
18
|
+
export const STUDIO_ORIGINAL_MIN_WIDTH_ATTR = "data-hf-studio-original-min-width";
|
|
19
|
+
export const STUDIO_ORIGINAL_MIN_HEIGHT_ATTR = "data-hf-studio-original-min-height";
|
|
20
|
+
export const STUDIO_ORIGINAL_MAX_WIDTH_ATTR = "data-hf-studio-original-max-width";
|
|
21
|
+
export const STUDIO_ORIGINAL_MAX_HEIGHT_ATTR = "data-hf-studio-original-max-height";
|
|
22
|
+
export const STUDIO_ORIGINAL_FLEX_BASIS_ATTR = "data-hf-studio-original-flex-basis";
|
|
23
|
+
export const STUDIO_ORIGINAL_FLEX_GROW_ATTR = "data-hf-studio-original-flex-grow";
|
|
24
|
+
export const STUDIO_ORIGINAL_FLEX_SHRINK_ATTR = "data-hf-studio-original-flex-shrink";
|
|
25
|
+
export const STUDIO_ORIGINAL_BOX_SIZING_ATTR = "data-hf-studio-original-box-sizing";
|
|
26
|
+
export const STUDIO_ORIGINAL_SCALE_ATTR = "data-hf-studio-original-scale";
|
|
27
|
+
export const STUDIO_ORIGINAL_TRANSFORM_ORIGIN_ATTR = "data-hf-studio-original-transform-origin";
|
|
28
|
+
export const STUDIO_ORIGINAL_DISPLAY_ATTR = "data-hf-studio-original-display";
|
|
29
|
+
export const STUDIO_ORIGINAL_ROTATE_ATTR = "data-hf-studio-original-rotate";
|
|
30
|
+
export const STUDIO_ORIGINAL_INLINE_ROTATE_ATTR = "data-hf-studio-original-inline-rotate";
|
|
31
|
+
export const STUDIO_ORIGINAL_ROTATION_TRANSFORM_ORIGIN_ATTR =
|
|
32
|
+
"data-hf-studio-original-rotation-transform-origin";
|
|
33
|
+
export const STUDIO_ROTATION_DRAFT_ATTR = "data-hf-studio-rotation-draft";
|
|
34
|
+
export const STUDIO_ORIGINAL_TRANSFORM_DISPLAY_ATTR = "data-hf-studio-original-transform-display";
|
|
35
|
+
|
|
36
|
+
/* ── Internal window property names ──────────────────────────────── */
|
|
37
|
+
export const STUDIO_MANUAL_EDITS_APPLY_PROP = "__hfStudioManualEditsApply";
|
|
38
|
+
export const STUDIO_MANUAL_EDITS_WRAPPED_PROP = "__hfStudioManualEditsWrapped";
|
|
39
|
+
export const STUDIO_MANUAL_EDITS_PLAYBACK_FRAME_PROP = "__hfStudioManualEditsPlaybackFrame";
|
|
40
|
+
|
|
41
|
+
export const STUDIO_ROTATION_TRANSFORM_ORIGIN = "center center";
|
|
42
|
+
|
|
43
|
+
/* ── Edit types ───────────────────────────────────────────────────── */
|
|
44
|
+
export interface StudioManualEditTarget {
|
|
45
|
+
sourceFile: string;
|
|
46
|
+
selector?: string;
|
|
47
|
+
selectorIndex?: number;
|
|
48
|
+
id?: string;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export interface StudioPathOffsetEdit {
|
|
52
|
+
kind: "path-offset";
|
|
53
|
+
target: StudioManualEditTarget;
|
|
54
|
+
x: number;
|
|
55
|
+
y: number;
|
|
56
|
+
updatedAt?: string;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export interface StudioBoxSizeEdit {
|
|
60
|
+
kind: "box-size";
|
|
61
|
+
target: StudioManualEditTarget;
|
|
62
|
+
width: number;
|
|
63
|
+
height: number;
|
|
64
|
+
updatedAt?: string;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export interface StudioRotationEdit {
|
|
68
|
+
kind: "rotation";
|
|
69
|
+
target: StudioManualEditTarget;
|
|
70
|
+
angle: number;
|
|
71
|
+
updatedAt?: string;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export type StudioManualEdit = StudioPathOffsetEdit | StudioBoxSizeEdit | StudioRotationEdit;
|
|
75
|
+
|
|
76
|
+
export interface StudioManualEditManifest {
|
|
77
|
+
version: 1;
|
|
78
|
+
edits: StudioManualEdit[];
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export type StudioManualEditSeekWindow = Window & {
|
|
82
|
+
__hf?: Record<string, unknown>;
|
|
83
|
+
__player?: Record<string, unknown>;
|
|
84
|
+
__timeline?: Record<string, unknown>;
|
|
85
|
+
__timelines?: Record<string, Record<string, unknown>>;
|
|
86
|
+
__hfStudioManualEditsApply?: () => void;
|
|
87
|
+
__hfStudioManualEditsPlaybackFrame?: number | null;
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
/* ── Snapshot types ───────────────────────────────────────────────── */
|
|
91
|
+
export interface StudioBoxSizeSnapshot {
|
|
92
|
+
width: string;
|
|
93
|
+
height: string;
|
|
94
|
+
minWidth: string;
|
|
95
|
+
minHeight: string;
|
|
96
|
+
maxWidth: string;
|
|
97
|
+
maxHeight: string;
|
|
98
|
+
flexBasis: string;
|
|
99
|
+
flexGrow: string;
|
|
100
|
+
flexShrink: string;
|
|
101
|
+
boxSizing: string;
|
|
102
|
+
scale: string;
|
|
103
|
+
transformOrigin: string;
|
|
104
|
+
display: string;
|
|
105
|
+
studioWidth: string;
|
|
106
|
+
studioHeight: string;
|
|
107
|
+
marker: string | null;
|
|
108
|
+
originalWidth: string | null;
|
|
109
|
+
originalHeight: string | null;
|
|
110
|
+
originalMinWidth: string | null;
|
|
111
|
+
originalMinHeight: string | null;
|
|
112
|
+
originalMaxWidth: string | null;
|
|
113
|
+
originalMaxHeight: string | null;
|
|
114
|
+
originalFlexBasis: string | null;
|
|
115
|
+
originalFlexGrow: string | null;
|
|
116
|
+
originalFlexShrink: string | null;
|
|
117
|
+
originalBoxSizing: string | null;
|
|
118
|
+
originalScale: string | null;
|
|
119
|
+
originalTransformOrigin: string | null;
|
|
120
|
+
originalDisplay: string | null;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export interface StudioRotationSnapshot {
|
|
124
|
+
rotate: string;
|
|
125
|
+
transformOrigin: string;
|
|
126
|
+
studioRotation: string;
|
|
127
|
+
marker: string | null;
|
|
128
|
+
draftMarker: string | null;
|
|
129
|
+
originalRotate: string | null;
|
|
130
|
+
originalInlineRotate: string | null;
|
|
131
|
+
originalTransformOrigin: string | null;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export interface StudioPathOffsetSnapshot {
|
|
135
|
+
translate: string;
|
|
136
|
+
x: string;
|
|
137
|
+
y: string;
|
|
138
|
+
marker: string | null;
|
|
139
|
+
originalTranslate: string | null;
|
|
140
|
+
originalInlineTranslate: string | null;
|
|
141
|
+
}
|
|
@@ -1,437 +1,50 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
export
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
updatedAt?: string;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
export type StudioGsapMotionPreset = "fade-up" | "slide" | "pop";
|
|
52
|
-
export type StudioGsapMotionDirection = "up" | "down" | "left" | "right";
|
|
53
|
-
|
|
54
|
-
export const STUDIO_GSAP_EASE_OPTIONS = [
|
|
55
|
-
"none",
|
|
56
|
-
"power1.in",
|
|
57
|
-
"power1.out",
|
|
58
|
-
"power1.inOut",
|
|
59
|
-
"power2.in",
|
|
60
|
-
"power2.out",
|
|
61
|
-
"power2.inOut",
|
|
62
|
-
"power3.in",
|
|
63
|
-
"power3.out",
|
|
64
|
-
"power3.inOut",
|
|
65
|
-
"power4.in",
|
|
66
|
-
"power4.out",
|
|
67
|
-
"power4.inOut",
|
|
68
|
-
"sine.in",
|
|
69
|
-
"sine.out",
|
|
70
|
-
"sine.inOut",
|
|
71
|
-
"expo.in",
|
|
72
|
-
"expo.out",
|
|
73
|
-
"expo.inOut",
|
|
74
|
-
"circ.in",
|
|
75
|
-
"circ.out",
|
|
76
|
-
"circ.inOut",
|
|
77
|
-
"back.in(1.7)",
|
|
78
|
-
"back.out(1.7)",
|
|
79
|
-
"back.inOut(1.7)",
|
|
80
|
-
"elastic.out(1, 0.45)",
|
|
81
|
-
"bounce.out",
|
|
82
|
-
] as const;
|
|
83
|
-
|
|
84
|
-
const DEFAULT_CUSTOM_EASE_POINTS: StudioCustomEaseControlPoints = {
|
|
85
|
-
x1: 0.215,
|
|
86
|
-
y1: 0.61,
|
|
87
|
-
x2: 0.355,
|
|
88
|
-
y2: 1,
|
|
89
|
-
};
|
|
90
|
-
|
|
91
|
-
const GSAP_EASE_CONTROL_POINTS: Record<string, StudioCustomEaseControlPoints> = {
|
|
92
|
-
none: { x1: 0, y1: 0, x2: 1, y2: 1 },
|
|
93
|
-
"power1.in": { x1: 0.55, y1: 0.085, x2: 0.68, y2: 0.53 },
|
|
94
|
-
"power1.out": { x1: 0.25, y1: 0.46, x2: 0.45, y2: 0.94 },
|
|
95
|
-
"power1.inOut": { x1: 0.455, y1: 0.03, x2: 0.515, y2: 0.955 },
|
|
96
|
-
"power2.in": { x1: 0.55, y1: 0.055, x2: 0.675, y2: 0.19 },
|
|
97
|
-
"power2.out": { x1: 0.215, y1: 0.61, x2: 0.355, y2: 1 },
|
|
98
|
-
"power2.inOut": { x1: 0.645, y1: 0.045, x2: 0.355, y2: 1 },
|
|
99
|
-
"power3.in": { x1: 0.895, y1: 0.03, x2: 0.685, y2: 0.22 },
|
|
100
|
-
"power3.out": { x1: 0.165, y1: 0.84, x2: 0.44, y2: 1 },
|
|
101
|
-
"power3.inOut": { x1: 0.77, y1: 0, x2: 0.175, y2: 1 },
|
|
102
|
-
"power4.in": { x1: 0.755, y1: 0.05, x2: 0.855, y2: 0.06 },
|
|
103
|
-
"power4.out": { x1: 0.23, y1: 1, x2: 0.32, y2: 1 },
|
|
104
|
-
"power4.inOut": { x1: 0.86, y1: 0, x2: 0.07, y2: 1 },
|
|
105
|
-
"sine.in": { x1: 0.47, y1: 0, x2: 0.745, y2: 0.715 },
|
|
106
|
-
"sine.out": { x1: 0.39, y1: 0.575, x2: 0.565, y2: 1 },
|
|
107
|
-
"sine.inOut": { x1: 0.445, y1: 0.05, x2: 0.55, y2: 0.95 },
|
|
108
|
-
"expo.in": { x1: 0.95, y1: 0.05, x2: 0.795, y2: 0.035 },
|
|
109
|
-
"expo.out": { x1: 0.19, y1: 1, x2: 0.22, y2: 1 },
|
|
110
|
-
"expo.inOut": { x1: 1, y1: 0, x2: 0, y2: 1 },
|
|
111
|
-
"circ.in": { x1: 0.6, y1: 0.04, x2: 0.98, y2: 0.335 },
|
|
112
|
-
"circ.out": { x1: 0.075, y1: 0.82, x2: 0.165, y2: 1 },
|
|
113
|
-
"circ.inOut": { x1: 0.785, y1: 0.135, x2: 0.15, y2: 0.86 },
|
|
114
|
-
"back.in(1.7)": { x1: 0.6, y1: -0.28, x2: 0.735, y2: 0.045 },
|
|
115
|
-
"back.out(1.7)": { x1: 0.175, y1: 0.885, x2: 0.32, y2: 1.275 },
|
|
116
|
-
"back.inOut(1.7)": { x1: 0.68, y1: -0.55, x2: 0.265, y2: 1.55 },
|
|
117
|
-
"elastic.out(1, 0.45)": { x1: 0.16, y1: 1.32, x2: 0.28, y2: 0.86 },
|
|
118
|
-
"bounce.out": { x1: 0.34, y1: 1.56, x2: 0.64, y2: 0.74 },
|
|
119
|
-
};
|
|
120
|
-
|
|
121
|
-
const CUSTOM_EASE_DATA_PATTERN =
|
|
122
|
-
/^M\s*0\s*,\s*0\s*C\s*(-?\d+(?:\.\d+)?)\s*,\s*(-?\d+(?:\.\d+)?)\s+(-?\d+(?:\.\d+)?)\s*,\s*(-?\d+(?:\.\d+)?)\s+1\s*,\s*1\s*$/i;
|
|
123
|
-
|
|
124
|
-
export interface StudioGsapPresetMotionOptions {
|
|
125
|
-
start: number;
|
|
126
|
-
duration: number;
|
|
127
|
-
distance: number;
|
|
128
|
-
ease: string;
|
|
129
|
-
direction?: StudioGsapMotionDirection;
|
|
130
|
-
customEase?: StudioGsapCustomEase;
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
export interface StudioMotionManifest {
|
|
134
|
-
version: 1;
|
|
135
|
-
motions: StudioGsapMotion[];
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
interface StudioGsapTimeline {
|
|
139
|
-
fromTo?: (
|
|
140
|
-
target: HTMLElement,
|
|
141
|
-
from: Record<string, unknown>,
|
|
142
|
-
to: Record<string, unknown>,
|
|
143
|
-
at: number,
|
|
144
|
-
) => StudioGsapTimeline;
|
|
145
|
-
time?: (time: number) => StudioGsapTimeline;
|
|
146
|
-
totalTime?: (time: number, suppressEvents?: boolean) => StudioGsapTimeline;
|
|
147
|
-
pause?: () => StudioGsapTimeline;
|
|
148
|
-
kill?: () => void;
|
|
149
|
-
duration?: () => number;
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
type StudioMotionWindow = Window & {
|
|
153
|
-
gsap?: {
|
|
154
|
-
timeline?: (vars?: Record<string, unknown>) => StudioGsapTimeline;
|
|
155
|
-
set?: (target: HTMLElement, vars: Record<string, unknown>) => void;
|
|
156
|
-
registerPlugin?: (...plugins: unknown[]) => void;
|
|
157
|
-
};
|
|
158
|
-
CustomEase?: { create?: (id: string, data: string) => void };
|
|
159
|
-
__player?: {
|
|
160
|
-
getTime?: () => number;
|
|
161
|
-
renderSeek?: (time: number) => void;
|
|
162
|
-
seek?: (time: number) => void;
|
|
163
|
-
};
|
|
164
|
-
__timeline?: { time?: () => number };
|
|
165
|
-
__timelines?: Record<string, StudioGsapTimeline | undefined>;
|
|
166
|
-
__hfStudioMotionApply?: () => number;
|
|
167
|
-
__hfStudioMotionWrapped?: boolean;
|
|
168
|
-
};
|
|
169
|
-
|
|
170
|
-
export function emptyStudioMotionManifest(): StudioMotionManifest {
|
|
171
|
-
return { version: 1, motions: [] };
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
function clampPositiveNumber(value: number, fallback: number): number {
|
|
175
|
-
return Number.isFinite(value) && value > 0 ? value : fallback;
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
function clampNonNegativeNumber(value: number, fallback: number): number {
|
|
179
|
-
return Number.isFinite(value) && value >= 0 ? value : fallback;
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
function sanitizeEase(value: string): string {
|
|
183
|
-
return value.trim() || "none";
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
function roundEaseNumber(value: number): number {
|
|
187
|
-
return Math.round(value * 1000) / 1000;
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
function clampRange(value: number, min: number, max: number, fallback: number): number {
|
|
191
|
-
return Number.isFinite(value) ? Math.min(max, Math.max(min, value)) : fallback;
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
export function clampStudioCustomEasePoints(
|
|
195
|
-
points: Partial<StudioCustomEaseControlPoints>,
|
|
196
|
-
): StudioCustomEaseControlPoints {
|
|
197
|
-
return {
|
|
198
|
-
x1: roundEaseNumber(clampRange(points.x1 ?? DEFAULT_CUSTOM_EASE_POINTS.x1, 0, 1, 0.215)),
|
|
199
|
-
y1: roundEaseNumber(clampRange(points.y1 ?? DEFAULT_CUSTOM_EASE_POINTS.y1, -0.6, 1.6, 0.61)),
|
|
200
|
-
x2: roundEaseNumber(clampRange(points.x2 ?? DEFAULT_CUSTOM_EASE_POINTS.x2, 0, 1, 0.355)),
|
|
201
|
-
y2: roundEaseNumber(clampRange(points.y2 ?? DEFAULT_CUSTOM_EASE_POINTS.y2, -0.6, 1.6, 1)),
|
|
202
|
-
};
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
export function parseStudioCustomEaseData(
|
|
206
|
-
data: string | undefined,
|
|
207
|
-
): StudioCustomEaseControlPoints | null {
|
|
208
|
-
if (!data) return null;
|
|
209
|
-
const match = data.trim().match(CUSTOM_EASE_DATA_PATTERN);
|
|
210
|
-
if (!match) return null;
|
|
211
|
-
const points = {
|
|
212
|
-
x1: Number.parseFloat(match[1] ?? ""),
|
|
213
|
-
y1: Number.parseFloat(match[2] ?? ""),
|
|
214
|
-
x2: Number.parseFloat(match[3] ?? ""),
|
|
215
|
-
y2: Number.parseFloat(match[4] ?? ""),
|
|
216
|
-
};
|
|
217
|
-
if (!Object.values(points).every(Number.isFinite)) return null;
|
|
218
|
-
return clampStudioCustomEasePoints(points);
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
function formatEaseNumber(value: number): string {
|
|
222
|
-
const rounded = roundEaseNumber(value);
|
|
223
|
-
if (Object.is(rounded, -0)) return "0";
|
|
224
|
-
return `${rounded}`;
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
export function serializeStudioCustomEaseData(points: StudioCustomEaseControlPoints): string {
|
|
228
|
-
const clamped = clampStudioCustomEasePoints(points);
|
|
229
|
-
return `M0,0 C${formatEaseNumber(clamped.x1)},${formatEaseNumber(clamped.y1)} ${formatEaseNumber(clamped.x2)},${formatEaseNumber(clamped.y2)} 1,1`;
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
export function controlPointsForGsapEase(ease: string): StudioCustomEaseControlPoints {
|
|
233
|
-
return GSAP_EASE_CONTROL_POINTS[ease] ?? DEFAULT_CUSTOM_EASE_POINTS;
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
export function buildStudioGsapPresetMotion(
|
|
237
|
-
preset: StudioGsapMotionPreset,
|
|
238
|
-
options: StudioGsapPresetMotionOptions,
|
|
239
|
-
): Omit<StudioGsapMotion, "kind" | "target" | "updatedAt"> {
|
|
240
|
-
const start = clampNonNegativeNumber(options.start, 0);
|
|
241
|
-
const duration = clampPositiveNumber(options.duration, 0.6);
|
|
242
|
-
const distance = clampPositiveNumber(options.distance, 32);
|
|
243
|
-
const ease = sanitizeEase(options.ease);
|
|
244
|
-
const direction = options.direction ?? "up";
|
|
245
|
-
const base = { start, duration, ease, customEase: options.customEase };
|
|
246
|
-
|
|
247
|
-
if (preset === "pop") {
|
|
248
|
-
return {
|
|
249
|
-
...base,
|
|
250
|
-
from: { scale: 0.88, autoAlpha: 0 },
|
|
251
|
-
to: { scale: 1, autoAlpha: 1 },
|
|
252
|
-
};
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
if (preset === "slide") {
|
|
256
|
-
const x = direction === "right" ? -distance : direction === "left" ? distance : 0;
|
|
257
|
-
const y = direction === "down" ? -distance : direction === "up" ? distance : 0;
|
|
258
|
-
return {
|
|
259
|
-
...base,
|
|
260
|
-
from: { x, y, autoAlpha: 0 },
|
|
261
|
-
to: { x: 0, y: 0, autoAlpha: 1 },
|
|
262
|
-
};
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
return {
|
|
266
|
-
...base,
|
|
267
|
-
from: { y: direction === "down" ? -distance : distance, autoAlpha: 0 },
|
|
268
|
-
to: { y: 0, autoAlpha: 1 },
|
|
269
|
-
};
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
function finiteNumber(value: unknown): number | null {
|
|
273
|
-
return typeof value === "number" && Number.isFinite(value) ? value : null;
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
function parseMotionValues(value: unknown): StudioGsapMotionValues | null {
|
|
277
|
-
if (!value || typeof value !== "object") return null;
|
|
278
|
-
const record = value as Record<string, unknown>;
|
|
279
|
-
const parsed: StudioGsapMotionValues = {};
|
|
280
|
-
for (const key of ["x", "y", "scale", "rotation", "opacity", "autoAlpha"] as const) {
|
|
281
|
-
const next = finiteNumber(record[key]);
|
|
282
|
-
if (next != null) parsed[key] = next;
|
|
283
|
-
}
|
|
284
|
-
return Object.keys(parsed).length > 0 ? parsed : null;
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
function parseTarget(value: unknown): StudioMotionTarget | null {
|
|
288
|
-
if (!value || typeof value !== "object") return null;
|
|
289
|
-
const record = value as Record<string, unknown>;
|
|
290
|
-
const sourceFile = typeof record.sourceFile === "string" ? record.sourceFile : "";
|
|
291
|
-
if (!sourceFile) return null;
|
|
292
|
-
const selector = typeof record.selector === "string" ? record.selector : undefined;
|
|
293
|
-
const id = typeof record.id === "string" ? record.id : undefined;
|
|
294
|
-
if (!selector && !id) return null;
|
|
295
|
-
return {
|
|
296
|
-
sourceFile,
|
|
297
|
-
selector,
|
|
298
|
-
selectorIndex: finiteNumber(record.selectorIndex) ?? undefined,
|
|
299
|
-
id,
|
|
300
|
-
};
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
function parseCustomEase(value: unknown): StudioGsapCustomEase | undefined {
|
|
304
|
-
if (!value || typeof value !== "object") return undefined;
|
|
305
|
-
const record = value as Record<string, unknown>;
|
|
306
|
-
const id = typeof record.id === "string" ? record.id.trim() : "";
|
|
307
|
-
const data = typeof record.data === "string" ? record.data.trim() : "";
|
|
308
|
-
if (!id || !data) return undefined;
|
|
309
|
-
return { id, data };
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
function parseGsapMotion(value: unknown): StudioGsapMotion | null {
|
|
313
|
-
if (!value || typeof value !== "object") return null;
|
|
314
|
-
const record = value as Record<string, unknown>;
|
|
315
|
-
if (record.kind !== "gsap-motion") return null;
|
|
316
|
-
const target = parseTarget(record.target);
|
|
317
|
-
if (!target) return null;
|
|
318
|
-
const start = finiteNumber(record.start);
|
|
319
|
-
const duration = finiteNumber(record.duration);
|
|
320
|
-
if (start == null || duration == null || start < 0 || duration <= 0) return null;
|
|
321
|
-
const ease = typeof record.ease === "string" && record.ease.trim() ? record.ease.trim() : "none";
|
|
322
|
-
const from = parseMotionValues(record.from);
|
|
323
|
-
const to = parseMotionValues(record.to);
|
|
324
|
-
if (!from || !to) return null;
|
|
325
|
-
return {
|
|
326
|
-
kind: "gsap-motion",
|
|
327
|
-
target,
|
|
328
|
-
start,
|
|
329
|
-
duration,
|
|
330
|
-
ease,
|
|
331
|
-
customEase: parseCustomEase(record.customEase),
|
|
332
|
-
from,
|
|
333
|
-
to,
|
|
334
|
-
updatedAt: typeof record.updatedAt === "string" ? record.updatedAt : undefined,
|
|
335
|
-
};
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
export function parseStudioMotionManifest(content: string): StudioMotionManifest {
|
|
339
|
-
if (!content.trim()) return emptyStudioMotionManifest();
|
|
340
|
-
try {
|
|
341
|
-
const parsed = JSON.parse(content) as unknown;
|
|
342
|
-
if (!parsed || typeof parsed !== "object") return emptyStudioMotionManifest();
|
|
343
|
-
const motions = (parsed as { motions?: unknown }).motions;
|
|
344
|
-
if (!Array.isArray(motions)) return emptyStudioMotionManifest();
|
|
345
|
-
return {
|
|
346
|
-
version: 1,
|
|
347
|
-
motions: motions
|
|
348
|
-
.map(parseGsapMotion)
|
|
349
|
-
.filter((motion): motion is StudioGsapMotion => motion !== null),
|
|
350
|
-
};
|
|
351
|
-
} catch {
|
|
352
|
-
return emptyStudioMotionManifest();
|
|
353
|
-
}
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
export function serializeStudioMotionManifest(manifest: StudioMotionManifest): string {
|
|
357
|
-
return `${JSON.stringify(manifest, null, 2)}\n`;
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
function normalizeStudioFileChangePath(path: string): string {
|
|
361
|
-
return path
|
|
362
|
-
.trim()
|
|
363
|
-
.replace(/\\/g, "/")
|
|
364
|
-
.replace(/^\.?\//, "");
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
export function isStudioMotionManifestPath(path: string | null): boolean {
|
|
368
|
-
if (!path) return false;
|
|
369
|
-
const normalized = normalizeStudioFileChangePath(path);
|
|
370
|
-
return normalized === STUDIO_MOTION_PATH || normalized.endsWith(`/${STUDIO_MOTION_PATH}`);
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
function selectionTarget(selection: DomEditSelection): StudioMotionTarget {
|
|
374
|
-
return {
|
|
375
|
-
sourceFile: selection.sourceFile || "index.html",
|
|
376
|
-
selector: selection.selector,
|
|
377
|
-
selectorIndex: selection.selectorIndex,
|
|
378
|
-
id: selection.id ?? undefined,
|
|
379
|
-
};
|
|
380
|
-
}
|
|
381
|
-
|
|
382
|
-
function targetKey(target: StudioMotionTarget): string {
|
|
383
|
-
return [
|
|
384
|
-
target.sourceFile,
|
|
385
|
-
target.id ? `id:${target.id}` : "",
|
|
386
|
-
target.selector ? `selector:${target.selector}` : "",
|
|
387
|
-
target.selectorIndex != null ? `index:${target.selectorIndex}` : "",
|
|
388
|
-
].join("|");
|
|
389
|
-
}
|
|
390
|
-
|
|
391
|
-
function sameSelectionTarget(motion: StudioGsapMotion, selection: DomEditSelection): boolean {
|
|
392
|
-
const target = selectionTarget(selection);
|
|
393
|
-
if (motion.target.sourceFile !== target.sourceFile) return false;
|
|
394
|
-
if (motion.target.id && target.id && motion.target.id === target.id) return true;
|
|
395
|
-
return targetKey(motion.target) === targetKey(target);
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
export function upsertStudioGsapMotion(
|
|
399
|
-
manifest: StudioMotionManifest,
|
|
400
|
-
selection: DomEditSelection,
|
|
401
|
-
motion: Omit<StudioGsapMotion, "kind" | "target" | "updatedAt">,
|
|
402
|
-
): StudioMotionManifest {
|
|
403
|
-
const target = selectionTarget(selection);
|
|
404
|
-
const nextMotion: StudioGsapMotion = {
|
|
405
|
-
kind: "gsap-motion",
|
|
406
|
-
target,
|
|
407
|
-
...motion,
|
|
408
|
-
updatedAt: new Date().toISOString(),
|
|
409
|
-
};
|
|
410
|
-
return {
|
|
411
|
-
version: 1,
|
|
412
|
-
motions: [
|
|
413
|
-
...manifest.motions.filter((existing) => targetKey(existing.target) !== targetKey(target)),
|
|
414
|
-
nextMotion,
|
|
415
|
-
],
|
|
416
|
-
};
|
|
417
|
-
}
|
|
418
|
-
|
|
419
|
-
export function removeStudioMotionForSelection(
|
|
420
|
-
manifest: StudioMotionManifest,
|
|
421
|
-
selection: DomEditSelection,
|
|
422
|
-
): StudioMotionManifest {
|
|
423
|
-
return {
|
|
424
|
-
version: 1,
|
|
425
|
-
motions: manifest.motions.filter((motion) => !sameSelectionTarget(motion, selection)),
|
|
426
|
-
};
|
|
427
|
-
}
|
|
428
|
-
|
|
429
|
-
export function getStudioMotionForSelection(
|
|
430
|
-
manifest: StudioMotionManifest,
|
|
431
|
-
selection: DomEditSelection,
|
|
432
|
-
): StudioGsapMotion | null {
|
|
433
|
-
return manifest.motions.find((motion) => sameSelectionTarget(motion, selection)) ?? null;
|
|
434
|
-
}
|
|
1
|
+
// Public surface — re-exports types, constants, and ops; owns DOM application logic.
|
|
2
|
+
|
|
3
|
+
export {
|
|
4
|
+
STUDIO_MOTION_PATH,
|
|
5
|
+
STUDIO_MOTION_TIMELINE_ID,
|
|
6
|
+
STUDIO_GSAP_EASE_OPTIONS,
|
|
7
|
+
type StudioMotionTarget,
|
|
8
|
+
type StudioGsapMotionValues,
|
|
9
|
+
type StudioGsapCustomEase,
|
|
10
|
+
type StudioCustomEaseControlPoints,
|
|
11
|
+
type StudioGsapMotion,
|
|
12
|
+
type StudioGsapMotionPreset,
|
|
13
|
+
type StudioGsapMotionDirection,
|
|
14
|
+
type StudioGsapPresetMotionOptions,
|
|
15
|
+
type StudioMotionManifest,
|
|
16
|
+
type StudioGsapTimeline,
|
|
17
|
+
type StudioMotionWindow,
|
|
18
|
+
} from "./studioMotionTypes";
|
|
19
|
+
|
|
20
|
+
export {
|
|
21
|
+
clampStudioCustomEasePoints,
|
|
22
|
+
parseStudioCustomEaseData,
|
|
23
|
+
serializeStudioCustomEaseData,
|
|
24
|
+
controlPointsForGsapEase,
|
|
25
|
+
buildStudioGsapPresetMotion,
|
|
26
|
+
emptyStudioMotionManifest,
|
|
27
|
+
parseStudioMotionManifest,
|
|
28
|
+
serializeStudioMotionManifest,
|
|
29
|
+
isStudioMotionManifestPath,
|
|
30
|
+
upsertStudioGsapMotion,
|
|
31
|
+
removeStudioMotionForSelection,
|
|
32
|
+
getStudioMotionForSelection,
|
|
33
|
+
} from "./studioMotionOps";
|
|
34
|
+
|
|
35
|
+
import {
|
|
36
|
+
STUDIO_MOTION_ATTR,
|
|
37
|
+
STUDIO_MOTION_ORIGINAL_TRANSFORM_ATTR,
|
|
38
|
+
STUDIO_MOTION_ORIGINAL_OPACITY_ATTR,
|
|
39
|
+
STUDIO_MOTION_ORIGINAL_VISIBILITY_ATTR,
|
|
40
|
+
STUDIO_MOTION_TIMELINE_ID,
|
|
41
|
+
type StudioGsapMotion,
|
|
42
|
+
type StudioMotionManifest,
|
|
43
|
+
type StudioMotionTarget,
|
|
44
|
+
type StudioMotionWindow,
|
|
45
|
+
} from "./studioMotionTypes";
|
|
46
|
+
|
|
47
|
+
// ── DOM Helpers ──
|
|
435
48
|
|
|
436
49
|
function sourceFileForElement(element: HTMLElement, activeCompositionPath: string | null): string {
|
|
437
50
|
let current: HTMLElement | null = element;
|