@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.
package/README.md CHANGED
@@ -11,7 +11,10 @@ npm install @kokoa/clotho @kokoa/clotho-editor react react-dom
11
11
  ```
12
12
 
13
13
  ```tsx
14
- import { StudioMount, createLocalStorageRepository } from "@kokoa/clotho-editor";
14
+ import {
15
+ StudioMount,
16
+ createLocalStorageRepository,
17
+ } from "@kokoa/clotho-editor";
15
18
  import "@kokoa/clotho/styles.css";
16
19
  import "@kokoa/clotho-editor/styles.css";
17
20
 
@@ -59,6 +62,55 @@ const repository: AnimationRepository = {
59
62
 
60
63
  저장 버튼의 동작 자체를 바꾸려면 repository의 `create`와 `save`를 구현합니다. 예제 목록은 repository의 `list`와 `load`에서 제공하므로 editor package 안에 application별 API 경로를 넣을 필요가 없습니다.
61
64
 
65
+ ## Plugin host
66
+
67
+ application 전용 도구는 `plugins`로 추가할 수 있습니다. plugin은 toolbar, 왼쪽 panel, inspector와 command palette에 기능을 붙일 수 있지만, 문서를 읽거나 바꾸려면 host가 권한을 명시적으로 허용해야 합니다. 저장소, selection, undo/redo와 같은 편집기의 기본 기능은 plugin으로 분리하지 않습니다.
68
+
69
+ ```tsx
70
+ import { StudioMount, type EditorPluginDefinition } from "@kokoa/clotho-editor";
71
+
72
+ const reviewPlugin: EditorPluginDefinition = {
73
+ manifest: {
74
+ id: "com.example.review",
75
+ version: "1.0.0",
76
+ capabilities: ["editor"],
77
+ editor: { toolbarItems: ["review"] },
78
+ },
79
+ toolbarItems: {
80
+ review: ({ container, document }) => {
81
+ const button = document.createElement("button");
82
+ button.textContent = "검토 요청";
83
+ container.append(button);
84
+ },
85
+ },
86
+ };
87
+
88
+ <StudioMount
89
+ plugins={[reviewPlugin]}
90
+ resolvePluginPermissions={(manifest) =>
91
+ manifest.id === "com.example.review" ? { ui: true, documentRead: true } : {}
92
+ }
93
+ />;
94
+ ```
95
+
96
+ Clotho compiler plugin이 새로운 JSON 입력 형식을 처리해야 한다면 `importDocument`에서 compiler pipeline을 연결합니다. 이 경계 덕분에 Editor는 application의 plugin registry나 backend에 의존하지 않습니다.
97
+
98
+ ```tsx
99
+ import { createPluginRegistry, runPluginPipeline } from "@kokoa/clotho/plugins";
100
+
101
+ const registry = createPluginRegistry(compilerPlugins);
102
+
103
+ <StudioMount
104
+ importDocument={(input) => {
105
+ const result = runPluginPipeline(input, { registry });
106
+ if (!result.ok) throw result.error;
107
+ return result.document;
108
+ }}
109
+ />;
110
+ ```
111
+
112
+ 현재 plugin API는 신뢰할 수 있는 application code를 위한 실험적 API입니다. 외부에서 받은 plugin은 별도 Worker나 격리 환경에서 실행한 뒤 JSON 결과만 Editor로 전달해야 합니다.
113
+
62
114
  ## 주요 기능
63
115
 
64
116
  - Clotho v1 JSON 문서 작성과 검증
@@ -1,4 +1,4 @@
1
- import { animationDocumentSchema, computeSnapshot, encodeImageAsset, inlineAssetFromDataUri } from '@kokoa/clotho';
1
+ import { animationDocumentSchema, computeSnapshot, compileDataBindings, encodeImageAsset, inlineAssetFromDataUri, compileLayouts } from '@kokoa/clotho';
2
2
 
3
3
  // src/export-json.ts
4
4
  function animationDocumentToJson(def) {
@@ -292,7 +292,10 @@ function toggleSelectionFor(sel, id) {
292
292
  }
293
293
  function getCurrentSnapshot() {
294
294
  if (!state.def) return /* @__PURE__ */ new Map();
295
- return computeSnapshot(state.def, state.currentTime);
295
+ return computeSnapshot(
296
+ compileDataBindings(state.def).document,
297
+ state.currentTime
298
+ );
296
299
  }
297
300
 
298
301
  // src/legacy/state/history.ts
@@ -707,6 +710,180 @@ function updateDuration(ms) {
707
710
  );
708
711
  if (state.currentTime > ms) state.currentTime = ms;
709
712
  }
713
+
714
+ // src/legacy/state/camera.ts
715
+ var EMPTY = { tracks: [], focus: [], strokeScaling: "scale" };
716
+ function getCamera() {
717
+ return state.def?.camera ?? EMPTY;
718
+ }
719
+ function hasCamera() {
720
+ const camera = state.def?.camera;
721
+ return camera !== void 0 && (camera.tracks.length > 0 || camera.focus.length > 0 || camera.strokeScaling !== "scale");
722
+ }
723
+ function cameraControlAt(time) {
724
+ const camera = state.def?.camera;
725
+ if (!camera || camera.tracks.length === 0 && camera.focus.length === 0) {
726
+ return { kind: "none" };
727
+ }
728
+ let index = -1;
729
+ camera.focus.forEach((entry, i) => {
730
+ if (entry.time <= time) index = i;
731
+ });
732
+ return index >= 0 ? { kind: "focus", index } : { kind: "tracks" };
733
+ }
734
+ function ensure(def) {
735
+ def.camera ??= { tracks: [], focus: [], strokeScaling: "scale" };
736
+ return def.camera;
737
+ }
738
+ function setCameraStrokeScaling(value) {
739
+ mutateDef(
740
+ (def) => {
741
+ ensure(def).strokeScaling = value;
742
+ },
743
+ `\uCE74\uBA54\uB77C \uC120 \uB450\uAED8: ${value}`,
744
+ "camera"
745
+ );
746
+ }
747
+ function setCameraKeyframe(property, time, value, ease) {
748
+ mutateDef(
749
+ (def) => {
750
+ const camera = ensure(def);
751
+ let track = camera.tracks.find((t) => t.property === property);
752
+ if (!track) {
753
+ track = { property, keyframes: [] };
754
+ camera.tracks.push(track);
755
+ camera.tracks.sort(
756
+ (a, b) => ORDER.indexOf(a.property) - ORDER.indexOf(b.property)
757
+ );
758
+ }
759
+ const existing = track.keyframes.findIndex((kf) => kf.time === time);
760
+ const next = { time, value, ...ease ? { ease } : {} };
761
+ if (existing >= 0) track.keyframes[existing] = next;
762
+ else track.keyframes.push(next);
763
+ track.keyframes.sort((a, b) => a.time - b.time);
764
+ },
765
+ `\uCE74\uBA54\uB77C keyframe: ${property} @ ${time}ms`,
766
+ "camera"
767
+ );
768
+ }
769
+ var ORDER = ["zoom", "x", "y"];
770
+ function moveCameraKeyframe(property, from, to) {
771
+ if (from === to) return;
772
+ mutateDef(
773
+ (def) => {
774
+ const track = def.camera?.tracks.find((t) => t.property === property);
775
+ const kf = track?.keyframes.find((k) => k.time === from);
776
+ if (!track || !kf) return;
777
+ track.keyframes = track.keyframes.filter(
778
+ (k) => k.time !== from && k.time !== to
779
+ );
780
+ track.keyframes.push({ ...kf, time: to });
781
+ track.keyframes.sort((a, b) => a.time - b.time);
782
+ },
783
+ `\uCE74\uBA54\uB77C keyframe \uC774\uB3D9: ${property} ${from}\u2192${to}ms`,
784
+ "camera"
785
+ );
786
+ }
787
+ function removeCameraKeyframe(property, time) {
788
+ mutateDef(
789
+ (def) => {
790
+ const camera = def.camera;
791
+ const track = camera?.tracks.find((t) => t.property === property);
792
+ if (!camera || !track) return;
793
+ track.keyframes = track.keyframes.filter((kf) => kf.time !== time);
794
+ if (track.keyframes.length === 0) {
795
+ camera.tracks = camera.tracks.filter((t) => t.property !== property);
796
+ }
797
+ },
798
+ `\uCE74\uBA54\uB77C keyframe \uC0AD\uC81C: ${property} @ ${time}ms`,
799
+ "camera"
800
+ );
801
+ }
802
+ function removeCameraTrack(property) {
803
+ mutateDef(
804
+ (def) => {
805
+ if (def.camera) {
806
+ def.camera.tracks = def.camera.tracks.filter(
807
+ (t) => t.property !== property
808
+ );
809
+ }
810
+ },
811
+ `\uCE74\uBA54\uB77C track \uC0AD\uC81C: ${property}`,
812
+ "camera"
813
+ );
814
+ }
815
+ function addCameraFocus(focus) {
816
+ mutateDef(
817
+ (def) => {
818
+ const camera = ensure(def);
819
+ camera.focus.push(focus);
820
+ camera.focus.sort((a, b) => a.time - b.time);
821
+ },
822
+ `\uCE74\uBA54\uB77C focus \uCD94\uAC00 @ ${focus.time}ms`,
823
+ "camera"
824
+ );
825
+ const index = getCamera().focus.findIndex(
826
+ (f) => f.time === focus.time && f.elementIds.join() === focus.elementIds.join()
827
+ );
828
+ state.selection = {
829
+ kind: "camera",
830
+ focusIndex: index < 0 ? void 0 : index
831
+ };
832
+ emit();
833
+ }
834
+ function updateCameraFocus(index, patch) {
835
+ const before = getCamera().focus[index];
836
+ if (!before) return;
837
+ const after = { ...before, ...patch };
838
+ mutateDef(
839
+ (def) => {
840
+ const camera = def.camera;
841
+ if (!camera?.focus[index]) return;
842
+ camera.focus[index] = after;
843
+ camera.focus.sort((a, b) => a.time - b.time);
844
+ },
845
+ `\uCE74\uBA54\uB77C focus \uC218\uC815: ${Object.keys(patch).join(", ")}`,
846
+ "camera"
847
+ );
848
+ if (state.selection.kind === "camera") {
849
+ const moved = getCamera().focus.findIndex(
850
+ (f) => f.time === after.time && f.elementIds.join() === after.elementIds.join() && f.duration === after.duration
851
+ );
852
+ state.selection = {
853
+ kind: "camera",
854
+ focusIndex: moved < 0 ? void 0 : moved
855
+ };
856
+ emit();
857
+ }
858
+ }
859
+ function deleteCameraFocus(index) {
860
+ mutateDef(
861
+ (def) => {
862
+ if (def.camera) {
863
+ def.camera.focus = def.camera.focus.filter((_, i) => i !== index);
864
+ }
865
+ },
866
+ `\uCE74\uBA54\uB77C focus \uC0AD\uC81C #${index + 1}`,
867
+ "camera"
868
+ );
869
+ if (state.selection.kind === "camera") {
870
+ state.selection = { kind: "camera" };
871
+ emit();
872
+ }
873
+ }
874
+ function clearCamera() {
875
+ mutateDef(
876
+ (def) => {
877
+ delete def.camera;
878
+ },
879
+ "\uCE74\uBA54\uB77C \uC81C\uAC70",
880
+ "camera"
881
+ );
882
+ if (state.selection.kind === "camera") {
883
+ state.selection = { kind: "none" };
884
+ emit();
885
+ }
886
+ }
710
887
  function updateMeta(patch) {
711
888
  const keys = Object.keys(patch).join(", ");
712
889
  mutateDef(
@@ -726,6 +903,24 @@ function updateLocales(locales) {
726
903
  "meta"
727
904
  );
728
905
  }
906
+ function updateData(data) {
907
+ mutateDef(
908
+ (def) => {
909
+ def.data = data;
910
+ },
911
+ "\uC0D8\uD50C \uB370\uC774\uD130 \uBCC0\uACBD",
912
+ "meta"
913
+ );
914
+ }
915
+ function updateResponsive(responsive) {
916
+ mutateDef(
917
+ (def) => {
918
+ def.responsive = responsive;
919
+ },
920
+ "Responsive Stage \uBCC0\uACBD",
921
+ "canvas"
922
+ );
923
+ }
729
924
  function updateCanvas(patch) {
730
925
  const keys = Object.keys(patch).join(", ");
731
926
  mutateDef(
@@ -830,6 +1025,125 @@ function registerDataUriAsset(dataUri) {
830
1025
  );
831
1026
  return id;
832
1027
  }
1028
+ function uniqueLayoutId(def) {
1029
+ const used = new Set(def.layouts.map((layout) => layout.id));
1030
+ let index = 1;
1031
+ while (used.has(`layout-${index}`)) index += 1;
1032
+ return `layout-${index}`;
1033
+ }
1034
+ function createLayout(elementIds, mode) {
1035
+ if (elementIds.length === 0) return;
1036
+ mutateDef(
1037
+ (def) => {
1038
+ const selected = def.elements.filter(
1039
+ (element) => elementIds.includes(element.id)
1040
+ );
1041
+ if (selected.length === 0) return;
1042
+ const measured = compileLayouts({ ...def, layouts: [] }).boxes;
1043
+ const boxes = selected.flatMap((element) => {
1044
+ const box = measured[element.id];
1045
+ return box ? [box] : [];
1046
+ });
1047
+ const x = boxes.length > 0 ? Math.min(...boxes.map((box) => box.x)) : 0;
1048
+ const y = boxes.length > 0 ? Math.min(...boxes.map((box) => box.y)) : 0;
1049
+ def.layouts = def.layouts.filter(
1050
+ (layout) => !layout.elementIds.some((id) => elementIds.includes(id))
1051
+ );
1052
+ def.layouts.push({
1053
+ id: uniqueLayoutId(def),
1054
+ mode,
1055
+ elementIds: selected.map((element) => element.id),
1056
+ x,
1057
+ y,
1058
+ gap: 16,
1059
+ align: "start",
1060
+ constraints: []
1061
+ });
1062
+ def.elements = compileLayouts(def).document.elements;
1063
+ },
1064
+ `${mode} layout \uC0DD\uC131`,
1065
+ "layout"
1066
+ );
1067
+ }
1068
+ function detachFromLayout(elementIds) {
1069
+ const ids = new Set(elementIds);
1070
+ mutateDef(
1071
+ (def) => {
1072
+ def.layouts = def.layouts.flatMap((layout) => {
1073
+ const remaining = layout.elementIds.filter((id) => !ids.has(id));
1074
+ return remaining.length === 0 ? [] : [{ ...layout, elementIds: remaining }];
1075
+ });
1076
+ },
1077
+ "layout\uC5D0\uC11C \uBD84\uB9AC",
1078
+ "layout"
1079
+ );
1080
+ }
1081
+ function layoutIdsFor(elementIds) {
1082
+ if (!state.def) return [];
1083
+ const ids = new Set(elementIds);
1084
+ return state.def.layouts.filter((layout) => layout.elementIds.some((id) => ids.has(id))).map((layout) => layout.id);
1085
+ }
1086
+ function findLayoutCollisions(def) {
1087
+ const { boxes } = compileLayouts(def);
1088
+ const collisions = [];
1089
+ for (const layout of def.layouts) {
1090
+ for (let firstIndex = 0; firstIndex < layout.elementIds.length; firstIndex += 1) {
1091
+ for (let secondIndex = firstIndex + 1; secondIndex < layout.elementIds.length; secondIndex += 1) {
1092
+ const firstId = layout.elementIds[firstIndex];
1093
+ const secondId = layout.elementIds[secondIndex];
1094
+ const first = boxes[firstId];
1095
+ const second = boxes[secondId];
1096
+ if (!first || !second) continue;
1097
+ const overlaps = first.x < second.x + second.width && first.x + first.width > second.x && first.y < second.y + second.height && first.y + first.height > second.y;
1098
+ if (overlaps) collisions.push({ firstId, secondId });
1099
+ }
1100
+ }
1101
+ }
1102
+ return collisions;
1103
+ }
1104
+
1105
+ // src/legacy/state/checkpoints.ts
1106
+ function addCheckpoint(checkpoint) {
1107
+ mutateDef(
1108
+ (def) => {
1109
+ def.checkpoints.push(checkpoint);
1110
+ def.checkpoints.sort((a, b) => a.time - b.time);
1111
+ },
1112
+ `Checkpoint \uCD94\uAC00: ${checkpoint.id}`,
1113
+ "checkpoint"
1114
+ );
1115
+ }
1116
+ function updateCheckpoint(id, patch) {
1117
+ mutateDef(
1118
+ (def) => {
1119
+ const index = def.checkpoints.findIndex(
1120
+ (checkpoint) => checkpoint.id === id
1121
+ );
1122
+ if (index < 0) return;
1123
+ def.checkpoints[index] = {
1124
+ ...def.checkpoints[index],
1125
+ ...patch
1126
+ };
1127
+ def.checkpoints.sort((a, b) => a.time - b.time);
1128
+ },
1129
+ `Checkpoint \uC218\uC815: ${id}`,
1130
+ "checkpoint"
1131
+ );
1132
+ }
1133
+ function deleteCheckpoint(id) {
1134
+ mutateDef(
1135
+ (def) => {
1136
+ def.checkpoints = def.checkpoints.filter(
1137
+ (checkpoint) => checkpoint.id !== id
1138
+ );
1139
+ },
1140
+ `Checkpoint \uC0AD\uC81C: ${id}`,
1141
+ "checkpoint"
1142
+ );
1143
+ }
1144
+ function uniqueCheckpointId() {
1145
+ return `checkpoint-${Date.now().toString(36)}`;
1146
+ }
833
1147
 
834
1148
  // src/legacy/studio-groups.ts
835
1149
  function isGroup(el) {
@@ -974,21 +1288,6 @@ function groupElements(ids) {
974
1288
  const valid = ids.filter((id) => def.elements.some((e) => e.id === id));
975
1289
  if (valid.length < 2) return null;
976
1290
  const newId = uniqueElementId("group");
977
- (() => {
978
- let minX = Infinity, minY = Infinity;
979
- for (const id of valid) {
980
- const el = def.elements.find((e) => e.id === id);
981
- if (!el) continue;
982
- const b = elementBbox(el);
983
- if (!b) continue;
984
- if (b.x < minX) minX = b.x;
985
- if (b.y < minY) minY = b.y;
986
- }
987
- return {
988
- x: Number.isFinite(minX) ? minX : 0,
989
- y: Number.isFinite(minY) ? minY : 0
990
- };
991
- })();
992
1291
  const group = {
993
1292
  type: "group",
994
1293
  id: newId,
@@ -996,6 +1295,7 @@ function groupElements(ids) {
996
1295
  rotation: 0,
997
1296
  appearances: [],
998
1297
  tracks: [],
1298
+ bindings: [],
999
1299
  // Children keep their absolute coordinates, so the group's own transform starts at
1000
1300
  // the identity. Setting x/y here would shift every member on the next render.
1001
1301
  x: 0,
@@ -1033,6 +1333,122 @@ function placeholderImageUrl() {
1033
1333
  return placeholderUrl;
1034
1334
  }
1035
1335
 
1036
- export { addAppearance, addChapter, addEffect, addElement, animationDocumentFileName, animationDocumentToJson, apiBaseUrl, beginTransient, canRedo, canUndo, childIdsOf, configureAnimationRepository, configureApi, configureHost, createAnimation, deleteAnimation, deleteChapter, deleteEffect, deleteElement, downloadAnimationJson, duplicateAnimation, endTransient, findContainingGroup, garbageCollectAnimationAssets, getCurrentSnapshot, getCurrentTime, getDef, getHistory, getSelectedElementIds, getSelection, groupBbox, groupElements, importAnimation, isDirty, isDraft, isElementSelected, isGroup, jumpBack, jumpForward, listAnimations, loadAnimation, markClean, moveElementToEnd, moveElementToFront, moveGroupBy, placeholderImageUrl, promoteDraftToSaved, redo, registerDataUriAsset, registerExternalAsset, registerInlineAsset, removeAppearance, removeTrack, removeTrackKeyframe, reorderElement, resetHistory, saveAnimation, setCurrentTime, setDef, setDraft, setElementValueAtTime, setSelection, setTrackKeyframe, subscribe, toggleSelectionFor, undo, ungroupElement, uniqueChapterId, uniqueEffectId, uniqueElementId, updateAppearance, updateCanvas, updateChapter, updateDuration, updateEffect, updateElementBase, updateLocales, updateMeta, updateSettings };
1037
- //# sourceMappingURL=chunk-GMGB7NIL.js.map
1038
- //# sourceMappingURL=chunk-GMGB7NIL.js.map
1336
+ // src/plugin-host.ts
1337
+ var SLOT_KEYS = {
1338
+ toolbar: "toolbarItems",
1339
+ panel: "panels",
1340
+ inspector: "inspectors"
1341
+ };
1342
+ function viewsFor(plugin, slot) {
1343
+ if (slot === "toolbar") return plugin.toolbarItems ?? {};
1344
+ if (slot === "panel") return plugin.panels ?? {};
1345
+ return plugin.inspectors ?? {};
1346
+ }
1347
+ function validateEditorPlugin(plugin) {
1348
+ const issues = [];
1349
+ const { id, capabilities, editor } = plugin.manifest;
1350
+ if (!capabilities.includes("editor")) {
1351
+ issues.push({ pluginId: id, message: "editor capability\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4." });
1352
+ }
1353
+ for (const slot of ["toolbar", "panel", "inspector"]) {
1354
+ const declared = new Set(editor?.[SLOT_KEYS[slot]] ?? []);
1355
+ const implemented = new Set(Object.keys(viewsFor(plugin, slot)));
1356
+ for (const viewId of declared) {
1357
+ if (!implemented.has(viewId)) {
1358
+ issues.push({
1359
+ pluginId: id,
1360
+ message: `${slot} ${viewId} \uAD6C\uD604\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.`
1361
+ });
1362
+ }
1363
+ }
1364
+ for (const viewId of implemented) {
1365
+ if (!declared.has(viewId)) {
1366
+ issues.push({
1367
+ pluginId: id,
1368
+ message: `${slot} ${viewId}\uAC00 manifest\uC5D0 \uC5C6\uC2B5\uB2C8\uB2E4.`
1369
+ });
1370
+ }
1371
+ }
1372
+ }
1373
+ return issues;
1374
+ }
1375
+ function permissionError(pluginId, permission) {
1376
+ return new Error(
1377
+ `Editor plugin ${pluginId}\uC5D0\uB294 ${permission} \uAD8C\uD55C\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.`
1378
+ );
1379
+ }
1380
+ function createEditorPluginContext(pluginId, permissions, state2) {
1381
+ return Object.freeze({
1382
+ pluginId,
1383
+ getDocument() {
1384
+ if (!permissions.documentRead)
1385
+ throw permissionError(pluginId, "documentRead");
1386
+ const document2 = state2.getDocument();
1387
+ if (!document2) throw new Error("\uC5F4\uB9B0 \uC560\uB2C8\uBA54\uC774\uC158\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.");
1388
+ return structuredClone(document2);
1389
+ },
1390
+ replaceDocument(document2) {
1391
+ if (!permissions.documentWrite)
1392
+ throw permissionError(pluginId, "documentWrite");
1393
+ state2.replaceDocument(structuredClone(document2));
1394
+ },
1395
+ getSelection() {
1396
+ if (!permissions.documentRead)
1397
+ throw permissionError(pluginId, "documentRead");
1398
+ return structuredClone(state2.getSelection());
1399
+ },
1400
+ setSelection(selection) {
1401
+ if (!permissions.documentWrite)
1402
+ throw permissionError(pluginId, "documentWrite");
1403
+ state2.setSelection(structuredClone(selection));
1404
+ }
1405
+ });
1406
+ }
1407
+ function mountEditorPlugins(root, plugins, resolvePermissions, state2) {
1408
+ const cleanups = [];
1409
+ const commands = [];
1410
+ const seen = /* @__PURE__ */ new Set();
1411
+ for (const plugin of plugins) {
1412
+ const { id } = plugin.manifest;
1413
+ if (seen.has(id))
1414
+ throw new Error(`Editor plugin ${id}\uAC00 \uC911\uBCF5 \uB4F1\uB85D\uB418\uC5C8\uC2B5\uB2C8\uB2E4.`);
1415
+ seen.add(id);
1416
+ const issues = validateEditorPlugin(plugin);
1417
+ if (issues.length > 0)
1418
+ throw new Error(issues.map((issue) => issue.message).join(" "));
1419
+ const permissions = resolvePermissions(plugin.manifest);
1420
+ if (!permissions.ui) continue;
1421
+ const context = createEditorPluginContext(id, permissions, state2);
1422
+ commands.push(...plugin.commands ?? []);
1423
+ for (const slot of ["toolbar", "panel", "inspector"]) {
1424
+ const target = root.querySelector(
1425
+ `[data-editor-plugin-slot="${slot}"]`
1426
+ );
1427
+ if (!target) continue;
1428
+ for (const viewId of plugin.manifest.editor?.[SLOT_KEYS[slot]] ?? []) {
1429
+ const view = viewsFor(plugin, slot)[viewId];
1430
+ if (!view) continue;
1431
+ const container = document.createElement(
1432
+ slot === "toolbar" ? "span" : "section"
1433
+ );
1434
+ container.dataset.editorPlugin = id;
1435
+ container.dataset.editorPluginView = viewId;
1436
+ container.setAttribute("aria-label", view.label);
1437
+ target.append(container);
1438
+ const cleanup = view.mount(container, context);
1439
+ if (cleanup) cleanups.push(cleanup);
1440
+ }
1441
+ }
1442
+ }
1443
+ return {
1444
+ commands,
1445
+ dispose() {
1446
+ cleanups.reverse().forEach((cleanup) => cleanup());
1447
+ root.querySelectorAll("[data-editor-plugin]").forEach((element) => element.remove());
1448
+ }
1449
+ };
1450
+ }
1451
+
1452
+ export { addAppearance, addCameraFocus, addChapter, addCheckpoint, addEffect, addElement, animationDocumentFileName, animationDocumentToJson, apiBaseUrl, beginTransient, cameraControlAt, canRedo, canUndo, childIdsOf, clearCamera, configureAnimationRepository, configureApi, configureHost, createAnimation, createEditorPluginContext, createLayout, deleteAnimation, deleteCameraFocus, deleteChapter, deleteCheckpoint, deleteEffect, deleteElement, detachFromLayout, downloadAnimationJson, duplicateAnimation, endTransient, findContainingGroup, findLayoutCollisions, garbageCollectAnimationAssets, getCamera, getCurrentSnapshot, getCurrentTime, getDef, getHistory, getSelectedElementIds, getSelection, groupBbox, groupElements, hasCamera, importAnimation, isDirty, isDraft, isElementSelected, isGroup, jumpBack, jumpForward, layoutIdsFor, listAnimations, loadAnimation, markClean, mountEditorPlugins, moveCameraKeyframe, moveElementToEnd, moveElementToFront, moveGroupBy, placeholderImageUrl, promoteDraftToSaved, redo, registerDataUriAsset, registerExternalAsset, registerInlineAsset, removeAppearance, removeCameraKeyframe, removeCameraTrack, removeTrack, removeTrackKeyframe, reorderElement, resetHistory, saveAnimation, 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 };
1453
+ //# sourceMappingURL=chunk-N47HF4VB.js.map
1454
+ //# sourceMappingURL=chunk-N47HF4VB.js.map