@kokoa/clotho-editor 0.1.4 → 0.3.0

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.
@@ -1096,6 +1096,227 @@ body.studio-player-popout #clotho-player-preview {
1096
1096
  color: var(--color-accent);
1097
1097
  background: linear-gradient(90deg, transparent, rgba(99, 102, 241, 0.15));
1098
1098
  }
1099
+ /* Canvas overlay for the camera. Drawn into the editing SVG, which keeps the
1100
+ full-canvas viewBox, so the frame is the part a reader will actually see. */
1101
+ .studio-camera-frame-scrim {
1102
+ /* Fixed rather than themed: this dims the *document's* canvas, which keeps its
1103
+ own background whatever the editor chrome is doing. A themed value goes
1104
+ invisible exactly when the editor is dark and the canvas is white. */
1105
+ fill: rgba(15, 23, 42, 0.32);
1106
+ }
1107
+ .studio-camera-frame-rect {
1108
+ fill: none;
1109
+ stroke: #7c3aed;
1110
+ stroke-width: 2;
1111
+ stroke-dasharray: 6 4;
1112
+ }
1113
+ .studio-camera-frame.is-selected .studio-camera-frame-rect {
1114
+ stroke-dasharray: none;
1115
+ stroke-width: 2.5;
1116
+ }
1117
+ .studio-camera-frame-rect[data-camera-frame] {
1118
+ cursor: pointer;
1119
+ }
1120
+ /* Transparent, but it takes the pan drag — so it needs a fill to be hit. */
1121
+ .studio-camera-frame-body {
1122
+ fill: transparent;
1123
+ cursor: grab;
1124
+ }
1125
+ .studio-camera-frame-body:active {
1126
+ cursor: grabbing;
1127
+ }
1128
+ .studio-camera-frame-handle {
1129
+ fill: var(--color-surface-elevated, #fff);
1130
+ stroke: #7c3aed;
1131
+ stroke-width: 1.5;
1132
+ cursor: nwse-resize;
1133
+ }
1134
+ .studio-camera-frame-handle:hover {
1135
+ fill: #7c3aed;
1136
+ }
1137
+ .studio-camera-frame-label {
1138
+ fill: #7c3aed;
1139
+ font-family: var(--font-mono);
1140
+ font-size: 12px;
1141
+ }
1142
+ .studio-camera-frame-warning {
1143
+ fill: #ef4444;
1144
+ font-family: var(--font-mono);
1145
+ font-size: 11px;
1146
+ }
1147
+ .studio-camera-readout {
1148
+ padding: 0.4rem 0.55rem;
1149
+ margin-bottom: 0.4rem;
1150
+ border-radius: 6px;
1151
+ background-color: var(--color-surface);
1152
+ font-family: var(--font-mono);
1153
+ font-size: 0.7rem;
1154
+ color: var(--color-fg-muted);
1155
+ }
1156
+ .studio-camera-issue {
1157
+ color: #ef4444;
1158
+ }
1159
+ .studio-camera-focus > summary {
1160
+ cursor: pointer;
1161
+ padding: 0.3rem 0;
1162
+ font-family: var(--font-mono);
1163
+ font-size: 0.7rem;
1164
+ }
1165
+ .studio-camera-track {
1166
+ padding: 0.3rem 0;
1167
+ border-top: 1px solid var(--color-border);
1168
+ }
1169
+ .studio-camera-track-head {
1170
+ display: flex;
1171
+ align-items: center;
1172
+ gap: 0.4rem;
1173
+ font-family: var(--font-mono);
1174
+ font-size: 0.72rem;
1175
+ }
1176
+ .studio-camera-track-now {
1177
+ margin-left: auto;
1178
+ color: var(--color-fg-muted);
1179
+ font-size: 0.68rem;
1180
+ }
1181
+ .studio-camera-track .studio-props-empty {
1182
+ margin: 0.2rem 0 0;
1183
+ padding: 0;
1184
+ text-align: left;
1185
+ font-size: 0.68rem;
1186
+ }
1187
+ .studio-camera-control {
1188
+ color: #7c3aed;
1189
+ }
1190
+ .studio-camera-field-label {
1191
+ margin: 0.5rem 0 0.25rem;
1192
+ font-size: 0.72rem;
1193
+ color: var(--color-fg-muted);
1194
+ }
1195
+ .studio-camera-range {
1196
+ margin: 0.45rem 0;
1197
+ }
1198
+ .studio-camera-range-head {
1199
+ display: flex;
1200
+ justify-content: space-between;
1201
+ align-items: baseline;
1202
+ font-size: 0.72rem;
1203
+ color: var(--color-fg-muted);
1204
+ }
1205
+ .studio-camera-range-value {
1206
+ font-family: var(--font-mono);
1207
+ color: var(--color-fg);
1208
+ }
1209
+ .studio-camera-range-row {
1210
+ display: flex;
1211
+ align-items: center;
1212
+ gap: 0.4rem;
1213
+ }
1214
+ .studio-camera-range-row input[type="range"] {
1215
+ flex: 1;
1216
+ min-width: 0;
1217
+ }
1218
+ .studio-camera-range-row input[type="number"] {
1219
+ width: 5.2rem;
1220
+ flex: 0 0 auto;
1221
+ }
1222
+ .studio-camera-hint {
1223
+ margin: 0.2rem 0 0;
1224
+ font-size: 0.66rem;
1225
+ color: var(--color-fg-muted);
1226
+ }
1227
+ .studio-camera-chips {
1228
+ display: flex;
1229
+ flex-wrap: wrap;
1230
+ gap: 0.25rem;
1231
+ margin: 0.25rem 0;
1232
+ }
1233
+ .studio-chip {
1234
+ padding: 0.1rem 0.45rem;
1235
+ border: 1px solid var(--color-border);
1236
+ border-radius: 999px;
1237
+ background: none;
1238
+ color: var(--color-fg-muted);
1239
+ font: inherit;
1240
+ font-size: 0.68rem;
1241
+ cursor: pointer;
1242
+ }
1243
+ .studio-chip.is-on {
1244
+ border-color: #7c3aed;
1245
+ background-color: color-mix(in srgb, #7c3aed 18%, transparent);
1246
+ color: var(--color-fg);
1247
+ }
1248
+ .studio-camera-focus-actions {
1249
+ display: flex;
1250
+ flex-wrap: wrap;
1251
+ gap: 0.3rem;
1252
+ margin: 0.3rem 0;
1253
+ }
1254
+ /* The target list scrolls: a document with forty elements must not push the
1255
+ padding and maxZoom controls off the panel. */
1256
+ .studio-camera-targets {
1257
+ max-height: 11rem;
1258
+ overflow-y: auto;
1259
+ border: 1px solid var(--color-border);
1260
+ border-radius: 6px;
1261
+ padding: 0.2rem;
1262
+ }
1263
+ .studio-camera-target {
1264
+ display: flex;
1265
+ align-items: center;
1266
+ gap: 0.35rem;
1267
+ padding: 0.15rem 0.3rem;
1268
+ border-radius: 4px;
1269
+ font-size: 0.72rem;
1270
+ cursor: pointer;
1271
+ }
1272
+ .studio-camera-target:hover {
1273
+ background-color: color-mix(in srgb, var(--color-accent) 10%, transparent);
1274
+ }
1275
+ .studio-camera-target.is-on {
1276
+ background-color: color-mix(in srgb, #7c3aed 16%, transparent);
1277
+ }
1278
+ .studio-camera-target-name {
1279
+ flex: 1;
1280
+ overflow: hidden;
1281
+ text-overflow: ellipsis;
1282
+ white-space: nowrap;
1283
+ }
1284
+ .studio-camera-target-id,
1285
+ .studio-camera-target-type {
1286
+ font-family: var(--font-mono);
1287
+ font-size: 0.64rem;
1288
+ color: var(--color-fg-muted);
1289
+ }
1290
+ .studio-camera-kf-list {
1291
+ list-style: none;
1292
+ margin: 0.3rem 0 0;
1293
+ padding: 0;
1294
+ display: flex;
1295
+ flex-wrap: wrap;
1296
+ gap: 0.3rem;
1297
+ }
1298
+ .studio-camera-kf-list li {
1299
+ display: flex;
1300
+ align-items: center;
1301
+ gap: 0.25rem;
1302
+ padding: 0.1rem 0.3rem;
1303
+ border: 1px solid var(--color-border);
1304
+ border-radius: 999px;
1305
+ font-family: var(--font-mono);
1306
+ font-size: 0.68rem;
1307
+ }
1308
+ .studio-camera-kf,
1309
+ .studio-camera-kf-del {
1310
+ border: 0;
1311
+ background: none;
1312
+ color: inherit;
1313
+ font: inherit;
1314
+ cursor: pointer;
1315
+ padding: 0;
1316
+ }
1317
+ .studio-camera-kf-del {
1318
+ color: #ef4444;
1319
+ }
1099
1320
  .studio-tl-total {
1100
1321
  margin-top: 0.3rem;
1101
1322
  padding: 0.2rem 0.4rem;
@@ -1885,6 +2106,81 @@ body.studio-player-popout #clotho-player-preview {
1885
2106
  color: #fff;
1886
2107
  border-color: #b91c1c;
1887
2108
  }
2109
+ /* Camera rows: the same shape as an element row, tinted so a document-level
2110
+ track is not mistaken for one belonging to whatever element is above it. */
2111
+ .studio-tl-camera-row {
2112
+ border-bottom: 1px solid var(--color-border);
2113
+ min-height: 30px;
2114
+ }
2115
+ .studio-tl-camera-row.is-selected .studio-tl-gutter,
2116
+ .studio-tl-camera-row.is-selected .studio-tl-body {
2117
+ background-color: color-mix(
2118
+ in srgb,
2119
+ var(--color-accent) 10%,
2120
+ var(--color-surface-elevated)
2121
+ );
2122
+ }
2123
+ .studio-tl-camera-gutter {
2124
+ gap: 0.35rem;
2125
+ color: #7c3aed;
2126
+ font-weight: 700;
2127
+ cursor: pointer;
2128
+ }
2129
+ .studio-tl-camera-gutter.is-sub {
2130
+ color: var(--color-fg-muted);
2131
+ font-weight: 400;
2132
+ padding-left: 1.4rem;
2133
+ }
2134
+ .studio-tl-camera-add {
2135
+ margin-left: auto;
2136
+ border: 1px solid var(--color-border);
2137
+ border-radius: 4px;
2138
+ background: none;
2139
+ color: inherit;
2140
+ font: inherit;
2141
+ line-height: 1;
2142
+ padding: 0 0.3rem;
2143
+ cursor: pointer;
2144
+ }
2145
+ .studio-tl-camera-track {
2146
+ position: relative;
2147
+ height: 100%;
2148
+ min-height: 30px;
2149
+ }
2150
+ .studio-tl-camera-empty {
2151
+ position: absolute;
2152
+ left: 0.5rem;
2153
+ top: 50%;
2154
+ transform: translateY(-50%);
2155
+ font-size: 0.68rem;
2156
+ color: var(--color-fg-muted);
2157
+ }
2158
+ .studio-tl-camera-focus {
2159
+ position: absolute;
2160
+ top: 5px;
2161
+ bottom: 5px;
2162
+ border-radius: 4px;
2163
+ border: 1px solid #7c3aed;
2164
+ background-color: color-mix(in srgb, #7c3aed 22%, transparent);
2165
+ cursor: grab;
2166
+ overflow: hidden;
2167
+ display: flex;
2168
+ align-items: center;
2169
+ }
2170
+ .studio-tl-camera-focus.is-selected {
2171
+ border-color: #ef4444;
2172
+ background-color: color-mix(in srgb, #ef4444 26%, transparent);
2173
+ }
2174
+ .studio-tl-camera-focus-label {
2175
+ padding: 0 0.35rem;
2176
+ font-size: 0.66rem;
2177
+ font-family: var(--font-mono);
2178
+ white-space: nowrap;
2179
+ pointer-events: none;
2180
+ }
2181
+ .studio-tl-camera-keyframe {
2182
+ color: #7c3aed;
2183
+ }
1888
2184
  .studio-tl-playhead {
1889
2185
  position: absolute;
1890
2186
  top: 0;
@@ -2051,3 +2347,132 @@ body.studio-player-popout #clotho-player-preview {
2051
2347
  margin-top: 0.4rem;
2052
2348
  }
2053
2349
  }
2350
+
2351
+ .studio-plugin-toolbar {
2352
+ display: contents;
2353
+ }
2354
+
2355
+ .studio-plugin-panels,
2356
+ .studio-plugin-inspectors {
2357
+ display: grid;
2358
+ gap: 0.55rem;
2359
+ }
2360
+
2361
+ .studio-plugin-panels > section,
2362
+ .studio-plugin-inspectors > section {
2363
+ padding: 0.65rem;
2364
+ border: 1px solid var(--color-border);
2365
+ border-radius: 0.55rem;
2366
+ background: var(--color-surface);
2367
+ }
2368
+
2369
+ .studio-template-panel {
2370
+ display: grid;
2371
+ gap: 0.55rem;
2372
+ }
2373
+
2374
+ .studio-template-select {
2375
+ width: 100%;
2376
+ padding: 0.35rem 0.45rem;
2377
+ border: 1px solid var(--color-border);
2378
+ border-radius: 0.3rem;
2379
+ background: var(--color-surface);
2380
+ color: var(--color-fg);
2381
+ }
2382
+
2383
+ .studio-template-status[data-error="true"] {
2384
+ color: var(--color-danger);
2385
+ }
2386
+
2387
+ .studio-checkpoint-card {
2388
+ margin-bottom: 0.7rem;
2389
+ padding: 0.65rem;
2390
+ border: 1px solid var(--color-border);
2391
+ border-radius: 0.5rem;
2392
+ background: var(--color-surface-raised, var(--color-surface));
2393
+ }
2394
+
2395
+ .studio-responsive-grid {
2396
+ display: grid;
2397
+ gap: 0.6rem;
2398
+ margin-top: 0.65rem;
2399
+ }
2400
+
2401
+ .studio-responsive-grid figure {
2402
+ margin: 0;
2403
+ padding: 0.4rem;
2404
+ border: 1px solid var(--color-border);
2405
+ border-radius: 0.45rem;
2406
+ overflow: hidden;
2407
+ background: var(--color-canvas);
2408
+ }
2409
+
2410
+ .studio-responsive-grid figcaption {
2411
+ margin-bottom: 0.35rem;
2412
+ color: var(--color-fg-muted);
2413
+ font-size: 0.7rem;
2414
+ }
2415
+
2416
+ .studio-responsive-grid svg {
2417
+ display: block;
2418
+ width: 100%;
2419
+ height: auto;
2420
+ }
2421
+
2422
+ .studio-profile {
2423
+ display: grid;
2424
+ gap: 0.35rem;
2425
+ margin: 0.55rem 0;
2426
+ }
2427
+ .studio-profile > div {
2428
+ display: flex;
2429
+ justify-content: space-between;
2430
+ gap: 0.75rem;
2431
+ }
2432
+ .studio-profile dt {
2433
+ color: var(--color-fg-muted);
2434
+ }
2435
+ .studio-profile dd {
2436
+ margin: 0;
2437
+ font-family: var(--font-mono);
2438
+ }
2439
+
2440
+ .studio-lint-list {
2441
+ display: grid;
2442
+ gap: 0.35rem;
2443
+ padding: 0;
2444
+ list-style: none;
2445
+ }
2446
+ .studio-lint-list button {
2447
+ width: 100%;
2448
+ display: grid;
2449
+ gap: 0.2rem;
2450
+ padding: 0.45rem;
2451
+ border: 1px solid var(--color-border);
2452
+ border-radius: 0.4rem;
2453
+ background: var(--color-surface);
2454
+ color: var(--color-fg);
2455
+ text-align: left;
2456
+ }
2457
+ .studio-lint-list span {
2458
+ color: var(--color-fg-muted);
2459
+ font-size: 0.72rem;
2460
+ }
2461
+ .studio-lint-list em {
2462
+ color: var(--color-accent);
2463
+ font-size: 0.68rem;
2464
+ font-style: normal;
2465
+ }
2466
+ .studio-lint-compare {
2467
+ display: grid;
2468
+ grid-template-columns: 1fr 1fr;
2469
+ gap: 0.4rem;
2470
+ }
2471
+ .studio-lint-compare figure {
2472
+ min-width: 0;
2473
+ margin: 0;
2474
+ }
2475
+ .studio-lint-compare svg {
2476
+ width: 100%;
2477
+ height: auto;
2478
+ }
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { AnimationDocument, SnapshotMap, Appearance, AnimationElement, TrackKeyframe, Chapter, AnimationEffect, GroupElement } from '@kokoa/clotho';
1
+ import { AnimationDocument, SnapshotMap, Appearance, AnimationElement, TrackKeyframe, Chapter, AnimationEffect, CameraFocus, Camera, CameraProperty, DataValue, LayoutMode, Checkpoint, StoryManifest, StoryEdge, AnimationTemplate, GroupElement, ParameterSchema } from '@kokoa/clotho';
2
2
 
3
3
  interface StudioProps {
4
4
  initial: AnimationDocument;
@@ -29,22 +29,12 @@ declare function createLocalStorageRepository(options?: LocalStorageRepositoryOp
29
29
 
30
30
  type ImageUploadResolver = (file: File) => Promise<string> | string;
31
31
 
32
- interface StudioMountProps {
33
- initialId?: string;
34
- repository?: AnimationRepository;
35
- editorTitle?: string;
36
- resolveImage?: ImageUploadResolver;
32
+ interface PaletteCommand {
33
+ id: string;
34
+ label: string;
35
+ hint?: string;
36
+ run: () => void;
37
37
  }
38
- declare function StudioMount({ initialId, repository, editorTitle, resolveImage, }: StudioMountProps): React.JSX.Element;
39
-
40
- /** 검증된 애니메이션 문서를 읽기 쉬운 JSON으로 변환한다. */
41
- declare function animationDocumentToJson(def: AnimationDocument): string;
42
- /** 어떤 image 요소에서도 참조하지 않는 asset을 제거한 새 문서를 반환한다. */
43
- declare function garbageCollectAnimationAssets(def: AnimationDocument): AnimationDocument;
44
- /** 문서 ID를 운영체제에서 안전하게 사용할 수 있는 파일 이름으로 바꾼다. */
45
- declare function animationDocumentFileName(def: AnimationDocument): string;
46
- /** 현재 문서를 브라우저에서 JSON 파일로 내려받는다. */
47
- declare function downloadAnimationJson(def: AnimationDocument): void;
48
38
 
49
39
  type Selection = {
50
40
  kind: "none";
@@ -60,8 +50,11 @@ type Selection = {
60
50
  } | {
61
51
  kind: "effect";
62
52
  effectId: string;
53
+ } | {
54
+ kind: "camera";
55
+ focusIndex?: number;
63
56
  };
64
- type HistoryKind = "meta" | "canvas" | "settings" | "add" | "delete" | "move" | "style" | "rotate" | "resize" | "reorder" | "track" | "appearance" | "chapter" | "effect" | "group" | "asset" | "other";
57
+ type HistoryKind = "meta" | "canvas" | "settings" | "add" | "delete" | "move" | "style" | "rotate" | "resize" | "reorder" | "track" | "appearance" | "chapter" | "effect" | "camera" | "group" | "layout" | "checkpoint" | "asset" | "other";
65
58
  interface HistoryEntry {
66
59
  snap: string;
67
60
  label: string;
@@ -124,9 +117,58 @@ declare function updateEffect(id: string, patch: Partial<AnimationEffect>): void
124
117
  declare function deleteEffect(id: string): void;
125
118
  declare function updateDuration(ms: number): void;
126
119
 
120
+ declare function getCamera(): Camera;
121
+ /** Whether the document actually carries a camera, as opposed to defaults. */
122
+ declare function hasCamera(): boolean;
123
+ /**
124
+ * Which mechanism decides the view at `time`.
125
+ *
126
+ * The renderer's rule is that a focus entry that has already started wins, and the
127
+ * tracks supply the value otherwise. Direct manipulation on the canvas has to know
128
+ * which one it is editing — dragging the frame when a focus is in control would
129
+ * write track keyframes that the focus immediately overrides, and a control that
130
+ * silently does nothing is worse than one that is disabled.
131
+ */
132
+ type CameraControl = {
133
+ kind: "none";
134
+ } | {
135
+ kind: "tracks";
136
+ } | {
137
+ kind: "focus";
138
+ index: number;
139
+ };
140
+ declare function cameraControlAt(time: number): CameraControl;
141
+ declare function setCameraStrokeScaling(value: Camera["strokeScaling"]): void;
142
+ /** Add or replace the keyframe at `time` on one camera property. */
143
+ declare function setCameraKeyframe(property: CameraProperty, time: number, value: number, ease?: CameraKeyframeEase): void;
144
+ type CameraKeyframeEase = NonNullable<Camera["tracks"][number]["keyframes"][number]["ease"]>;
145
+ /**
146
+ * Move a keyframe in time, keeping its value.
147
+ *
148
+ * Separate from `setCameraKeyframe` because a timeline drag has to remove the old
149
+ * time as well; doing it as remove-then-set would push two history entries for one
150
+ * gesture.
151
+ */
152
+ declare function moveCameraKeyframe(property: CameraProperty, from: number, to: number): void;
153
+ declare function removeCameraKeyframe(property: CameraProperty, time: number): void;
154
+ declare function removeCameraTrack(property: CameraProperty): void;
155
+ declare function addCameraFocus(focus: CameraFocus): void;
156
+ /**
157
+ * Patch one focus entry.
158
+ *
159
+ * Sorting can move it, so the selection is re-pointed at the same entry rather than
160
+ * at whatever ends up at the old index.
161
+ */
162
+ declare function updateCameraFocus(index: number, patch: Partial<CameraFocus>): void;
163
+ declare function deleteCameraFocus(index: number): void;
164
+ /** Remove the camera entirely, so the document renders as it did without one. */
165
+ declare function clearCamera(): void;
166
+
127
167
  declare function updateMeta(patch: Partial<Pick<AnimationDocument, "title" | "description">>): void;
128
168
  /** Update the locales offered to localized text elements in this document. */
129
169
  declare function updateLocales(locales: string[]): void;
170
+ declare function updateData(data: Record<string, DataValue>): void;
171
+ declare function updateResponsive(responsive: NonNullable<AnimationDocument["responsive"]>): void;
130
172
  declare function updateCanvas(patch: Partial<AnimationDocument["canvas"]>): void;
131
173
  declare function updateSettings(patch: Partial<AnimationDocument["settings"]>): void;
132
174
  declare function uniqueElementId(type: string): string;
@@ -152,6 +194,107 @@ declare function registerInlineAsset(bytes: Uint8Array, mime: string): string;
152
194
  */
153
195
  declare function registerDataUriAsset(dataUri: string): string;
154
196
 
197
+ interface LayoutCollision {
198
+ readonly firstId: string;
199
+ readonly secondId: string;
200
+ }
201
+ declare function createLayout(elementIds: readonly string[], mode: LayoutMode): void;
202
+ declare function detachFromLayout(elementIds: readonly string[]): void;
203
+ declare function layoutIdsFor(elementIds: readonly string[]): string[];
204
+ declare function findLayoutCollisions(def: AnimationDocument): LayoutCollision[];
205
+
206
+ declare function addCheckpoint(checkpoint: Checkpoint): void;
207
+ declare function updateCheckpoint(id: string, patch: Partial<Checkpoint>): void;
208
+ declare function deleteCheckpoint(id: string): void;
209
+ declare function uniqueCheckpointId(): string;
210
+
211
+ type EditorPluginSlot = "toolbar" | "panel" | "inspector";
212
+ interface EditorPluginManifestLike {
213
+ id: string;
214
+ name: string;
215
+ capabilities: readonly string[];
216
+ editor?: {
217
+ toolbarItems?: readonly string[];
218
+ panels?: readonly string[];
219
+ inspectors?: readonly string[];
220
+ };
221
+ }
222
+ interface EditorPluginPermissions {
223
+ ui?: boolean;
224
+ documentRead?: boolean;
225
+ documentWrite?: boolean;
226
+ }
227
+ interface EditorPluginContext {
228
+ readonly pluginId: string;
229
+ getDocument(): AnimationDocument;
230
+ replaceDocument(document: AnimationDocument): void;
231
+ getSelection(): Selection;
232
+ setSelection(selection: Selection): void;
233
+ }
234
+ interface EditorPluginView {
235
+ id: string;
236
+ label: string;
237
+ mount(container: HTMLElement, context: EditorPluginContext): void | (() => void);
238
+ }
239
+ interface EditorPluginDefinition {
240
+ manifest: EditorPluginManifestLike;
241
+ toolbarItems?: Record<string, EditorPluginView>;
242
+ panels?: Record<string, EditorPluginView>;
243
+ inspectors?: Record<string, EditorPluginView>;
244
+ commands?: readonly PaletteCommand[];
245
+ }
246
+ type EditorPluginPermissionResolver = (plugin: EditorPluginManifestLike) => EditorPluginPermissions;
247
+ type DocumentImporter = (input: unknown) => AnimationDocument | Promise<AnimationDocument>;
248
+ interface EditorPluginHostState {
249
+ getDocument(): AnimationDocument | null;
250
+ replaceDocument(document: AnimationDocument): void;
251
+ getSelection(): Selection;
252
+ setSelection(selection: Selection): void;
253
+ }
254
+ interface EditorPluginMountResult {
255
+ commands: PaletteCommand[];
256
+ dispose(): void;
257
+ }
258
+ interface EditorPluginIssue {
259
+ pluginId: string;
260
+ message: string;
261
+ }
262
+ declare function validateEditorPlugin(plugin: EditorPluginDefinition): EditorPluginIssue[];
263
+ declare function createEditorPluginContext(pluginId: string, permissions: EditorPluginPermissions, state: EditorPluginHostState): EditorPluginContext;
264
+ declare function mountEditorPlugins(root: HTMLElement, plugins: readonly EditorPluginDefinition[], resolvePermissions: EditorPluginPermissionResolver, state: EditorPluginHostState): EditorPluginMountResult;
265
+
266
+ interface StoryEditorOptions {
267
+ manifest: StoryManifest;
268
+ onChange?(manifest: StoryManifest): void;
269
+ }
270
+ declare function replaceStoryNodeDocument(manifest: StoryManifest, nodeId: string, document: AnimationDocument): StoryManifest;
271
+ declare function appendStoryEdge(manifest: StoryManifest, edge: StoryEdge): StoryManifest;
272
+ declare function createStoryEditorPlugin(options: StoryEditorOptions): EditorPluginDefinition;
273
+
274
+ interface StudioMountProps {
275
+ initialId?: string;
276
+ repository?: AnimationRepository;
277
+ editorTitle?: string;
278
+ resolveImage?: ImageUploadResolver;
279
+ importDocument?: DocumentImporter;
280
+ plugins?: readonly EditorPluginDefinition[];
281
+ resolvePluginPermissions?: EditorPluginPermissionResolver;
282
+ /** Trusted build-time templates shown as a parameter panel. */
283
+ templates?: readonly AnimationTemplate[];
284
+ /** Optional multi-document Story Graph edited alongside the active node timeline. */
285
+ story?: StoryEditorOptions;
286
+ }
287
+ declare function StudioMount({ initialId, repository, editorTitle, resolveImage, importDocument, plugins, resolvePluginPermissions, templates, story, }: StudioMountProps): React.JSX.Element;
288
+
289
+ /** 검증된 애니메이션 문서를 읽기 쉬운 JSON으로 변환한다. */
290
+ declare function animationDocumentToJson(def: AnimationDocument): string;
291
+ /** 어떤 image 요소에서도 참조하지 않는 asset을 제거한 새 문서를 반환한다. */
292
+ declare function garbageCollectAnimationAssets(def: AnimationDocument): AnimationDocument;
293
+ /** 문서 ID를 운영체제에서 안전하게 사용할 수 있는 파일 이름으로 바꾼다. */
294
+ declare function animationDocumentFileName(def: AnimationDocument): string;
295
+ /** 현재 문서를 브라우저에서 JSON 파일로 내려받는다. */
296
+ declare function downloadAnimationJson(def: AnimationDocument): void;
297
+
155
298
  declare function isGroup(el: AnimationElement): el is GroupElement;
156
299
  /** Direct children of a group, in document order (which is also paint order). */
157
300
  declare function childIdsOf(groupId: string): string[];
@@ -173,4 +316,42 @@ interface HostOptions {
173
316
  declare function configureHost(options: HostOptions): void;
174
317
  declare function placeholderImageUrl(): string;
175
318
 
176
- export { type AnimationRepository, type AnimationSummary, type HistoryEntry, type HistoryKind, type HostOptions, type ImageUploadResolver, type LocalStorageRepositoryOptions, type Selection, Studio, StudioMount, type StudioMountProps, type StudioProps, addAppearance, addChapter, addEffect, addElement, animationDocumentFileName, animationDocumentToJson, apiBaseUrl, beginTransient, canRedo, canUndo, childIdsOf, configureAnimationRepository, configureApi, configureHost, createLocalStorageRepository, deleteChapter, deleteEffect, deleteElement, downloadAnimationJson, endTransient, garbageCollectAnimationAssets, getCurrentSnapshot, getCurrentTime, getDef, getHistory, getSelectedElementIds, getSelection, groupElements, isDirty, isDraft, isElementSelected, isGroup, jumpBack, jumpForward, markClean, moveElementToEnd, moveElementToFront, placeholderImageUrl, promoteDraftToSaved, redo, registerDataUriAsset, registerExternalAsset, registerInlineAsset, removeAppearance, removeTrack, removeTrackKeyframe, reorderElement, resetHistory, setCurrentTime, setDef, setDraft, setElementValueAtTime, setSelection, setTrackKeyframe, subscribe, toggleSelectionFor, undo, ungroupElement, uniqueChapterId, uniqueEffectId, uniqueElementId, updateAppearance, updateCanvas, updateChapter, updateDuration, updateEffect, updateElementBase, updateLocales, updateMeta, updateSettings };
319
+ declare function initialParameterValue(schema: ParameterSchema): unknown;
320
+ declare function parameterInputValue(schema: ParameterSchema, raw: string | boolean): unknown;
321
+ /** Built-in authoring panel for trusted templates registered by the host. */
322
+ declare function createTemplateEditorPlugin(templates: readonly AnimationTemplate[]): EditorPluginDefinition;
323
+
324
+ declare function createVisualRegressionPlugin(): EditorPluginDefinition;
325
+
326
+ declare const RESPONSIVE_VIEWPORTS: readonly [{
327
+ readonly id: "compact";
328
+ readonly label: "모바일";
329
+ readonly width: 375;
330
+ }, {
331
+ readonly id: "regular";
332
+ readonly label: "본문";
333
+ readonly width: 768;
334
+ }, {
335
+ readonly id: "wide";
336
+ readonly label: "넓은 화면";
337
+ readonly width: 1280;
338
+ }];
339
+ declare function addDefaultResponsiveVariants(document: AnimationDocument): AnimationDocument;
340
+ declare function createResponsiveInspectorPlugin(): EditorPluginDefinition;
341
+
342
+ interface SceneProfile {
343
+ elementCount: number;
344
+ trackCount: number;
345
+ keyframeCount: number;
346
+ sampleCount: number;
347
+ totalMs: number;
348
+ averageMs: number;
349
+ maxMs: number;
350
+ overBudget: boolean;
351
+ }
352
+ declare function profileAnimation(document: AnimationDocument, sampleCount?: number, frameBudgetMs?: number): SceneProfile;
353
+ declare function createPerformanceProfilerPlugin(): EditorPluginDefinition;
354
+
355
+ declare function createLinterPlugin(): EditorPluginDefinition;
356
+
357
+ export { type AnimationRepository, type AnimationSummary, type CameraControl, type DocumentImporter, type EditorPluginContext, type EditorPluginDefinition, type EditorPluginHostState, type EditorPluginManifestLike, type EditorPluginMountResult, type EditorPluginPermissionResolver, type EditorPluginPermissions, type EditorPluginSlot, type EditorPluginView, type HistoryEntry, type HistoryKind, type HostOptions, type ImageUploadResolver, type LocalStorageRepositoryOptions, RESPONSIVE_VIEWPORTS, type SceneProfile, type Selection, type StoryEditorOptions, Studio, StudioMount, type StudioMountProps, type StudioProps, addAppearance, addCameraFocus, addChapter, addCheckpoint, addDefaultResponsiveVariants, addEffect, addElement, animationDocumentFileName, animationDocumentToJson, apiBaseUrl, appendStoryEdge, beginTransient, cameraControlAt, canRedo, canUndo, childIdsOf, clearCamera, configureAnimationRepository, configureApi, configureHost, createEditorPluginContext, createLayout, createLinterPlugin, createLocalStorageRepository, createPerformanceProfilerPlugin, createResponsiveInspectorPlugin, createStoryEditorPlugin, createTemplateEditorPlugin, createVisualRegressionPlugin, deleteCameraFocus, deleteChapter, deleteCheckpoint, deleteEffect, deleteElement, detachFromLayout, downloadAnimationJson, endTransient, findLayoutCollisions, garbageCollectAnimationAssets, getCamera, getCurrentSnapshot, getCurrentTime, getDef, getHistory, getSelectedElementIds, getSelection, groupElements, hasCamera, initialParameterValue, isDirty, isDraft, isElementSelected, isGroup, jumpBack, jumpForward, layoutIdsFor, markClean, mountEditorPlugins, moveCameraKeyframe, moveElementToEnd, moveElementToFront, parameterInputValue, placeholderImageUrl, profileAnimation, promoteDraftToSaved, redo, registerDataUriAsset, registerExternalAsset, registerInlineAsset, removeAppearance, removeCameraKeyframe, removeCameraTrack, removeTrack, removeTrackKeyframe, reorderElement, replaceStoryNodeDocument, resetHistory, setCameraKeyframe, setCameraStrokeScaling, setCurrentTime, setDef, setDraft, setElementValueAtTime, setSelection, setTrackKeyframe, subscribe, toggleSelectionFor, undo, ungroupElement, uniqueChapterId, uniqueCheckpointId, uniqueEffectId, uniqueElementId, updateAppearance, updateCameraFocus, updateCanvas, updateChapter, updateCheckpoint, updateData, updateDuration, updateEffect, updateElementBase, updateLocales, updateMeta, updateResponsive, updateSettings, validateEditorPlugin };