@jxsuite/studio 0.10.2 → 0.13.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.
Files changed (53) hide show
  1. package/README.md +82 -0
  2. package/dist/studio.js +5114 -3424
  3. package/dist/studio.js.map +65 -49
  4. package/package.json +6 -3
  5. package/src/browse/browse.js +27 -3
  6. package/src/canvas/canvas-diff.js +184 -0
  7. package/src/canvas/canvas-helpers.js +10 -14
  8. package/src/canvas/canvas-live-render.js +136 -17
  9. package/src/canvas/canvas-render.js +154 -21
  10. package/src/canvas/canvas-utils.js +21 -23
  11. package/src/editor/component-inline-edit.js +54 -41
  12. package/src/editor/content-inline-edit.js +46 -47
  13. package/src/editor/context-menu.js +63 -39
  14. package/src/editor/convert-to-component.js +11 -14
  15. package/src/editor/insertion-helper.js +8 -10
  16. package/src/editor/shortcuts.js +69 -39
  17. package/src/files/components.js +15 -4
  18. package/src/files/files.js +72 -24
  19. package/src/panels/activity-bar.js +29 -7
  20. package/src/panels/block-action-bar.js +104 -80
  21. package/src/panels/canvas-dnd.js +132 -50
  22. package/src/panels/dnd.js +32 -28
  23. package/src/panels/editors.js +7 -14
  24. package/src/panels/elements-panel.js +9 -3
  25. package/src/panels/events-panel.js +44 -39
  26. package/src/panels/git-panel.js +45 -3
  27. package/src/panels/layers-panel.js +16 -11
  28. package/src/panels/left-panel.js +108 -43
  29. package/src/panels/overlays.js +97 -35
  30. package/src/panels/panel-events.js +18 -11
  31. package/src/panels/properties-panel.js +467 -98
  32. package/src/panels/right-panel.js +85 -37
  33. package/src/panels/shared.js +0 -22
  34. package/src/panels/signals-panel.js +125 -54
  35. package/src/panels/statusbar.js +23 -8
  36. package/src/panels/style-inputs.js +4 -2
  37. package/src/panels/style-panel.js +128 -105
  38. package/src/panels/stylebook-panel.js +42 -17
  39. package/src/panels/tab-strip.js +124 -0
  40. package/src/panels/toolbar.js +39 -9
  41. package/src/reactivity.js +28 -0
  42. package/src/settings/content-types-editor.js +78 -8
  43. package/src/settings/defs-editor.js +56 -7
  44. package/src/settings/schema-field-ui.js +99 -11
  45. package/src/site-context.js +105 -0
  46. package/src/state.js +0 -456
  47. package/src/store.js +105 -124
  48. package/src/studio.js +112 -121
  49. package/src/tabs/tab.js +153 -0
  50. package/src/tabs/transact.js +406 -0
  51. package/src/ui/spectrum.js +2 -0
  52. package/src/view.js +3 -0
  53. package/src/workspace/workspace.js +89 -0
package/src/panels/dnd.js CHANGED
@@ -16,16 +16,14 @@ import {
16
16
 
17
17
  import {
18
18
  getState,
19
- update,
20
19
  leftPanel,
21
- moveNode,
22
- insertNode,
23
- applyMutation,
24
20
  getNodeAtPath,
25
21
  parentElementPath,
26
22
  childIndex,
27
23
  isAncestor,
28
24
  } from "../store.js";
25
+ import { transact, transactDoc, mutateMoveNode, mutateInsertNode } from "../tabs/transact.js";
26
+ import { activeTab } from "../workspace/workspace.js";
29
27
  import { view } from "../view.js";
30
28
  import { componentRegistry, computeRelativePath } from "../files/components.js";
31
29
  import { renderComponentPreview } from "./stylebook-panel.js";
@@ -144,12 +142,14 @@ export function registerComponentsDnD() {
144
142
 
145
143
  const instanceDef = {
146
144
  tagName: comp.tagName,
147
- $props: Object.fromEntries(
148
- comp.props.map((/** @type {any} */ p) => [
149
- p.name,
150
- p.default !== undefined ? p.default : "",
151
- ]),
152
- ),
145
+ $props: comp.props
146
+ ? Object.fromEntries(
147
+ comp.props.map((/** @type {any} */ p) => [
148
+ p.name,
149
+ p.default !== undefined ? p.default : "",
150
+ ]),
151
+ )
152
+ : {},
153
153
  };
154
154
  const cleanup = draggable({
155
155
  element: row,
@@ -259,15 +259,17 @@ export function applyDropInstruction(instruction, srcData, targetPath) {
259
259
 
260
260
  switch (instruction.type) {
261
261
  case "reorder-above":
262
- update(moveNode(S, fromPath, targetParent, targetIdx));
262
+ transactDoc(activeTab.value, (t) => mutateMoveNode(t, fromPath, targetParent, targetIdx));
263
263
  break;
264
264
  case "reorder-below":
265
- update(moveNode(S, fromPath, targetParent, targetIdx + 1));
265
+ transactDoc(activeTab.value, (t) =>
266
+ mutateMoveNode(t, fromPath, targetParent, targetIdx + 1),
267
+ );
266
268
  break;
267
269
  case "make-child": {
268
270
  const target = getNodeAtPath(S.document, targetPath);
269
271
  const len = target?.children?.length || 0;
270
- update(moveNode(S, fromPath, targetPath, len));
272
+ transactDoc(activeTab.value, (t) => mutateMoveNode(t, fromPath, targetPath, len));
271
273
  break;
272
274
  }
273
275
  }
@@ -277,15 +279,21 @@ export function applyDropInstruction(instruction, srcData, targetPath) {
277
279
 
278
280
  switch (instruction.type) {
279
281
  case "reorder-above":
280
- update(insertNode(S, targetParent, targetIdx, structuredClone(srcData.fragment)));
282
+ transactDoc(activeTab.value, (t) =>
283
+ mutateInsertNode(t, targetParent, targetIdx, structuredClone(srcData.fragment)),
284
+ );
281
285
  break;
282
286
  case "reorder-below":
283
- update(insertNode(S, targetParent, targetIdx + 1, structuredClone(srcData.fragment)));
287
+ transactDoc(activeTab.value, (t) =>
288
+ mutateInsertNode(t, targetParent, targetIdx + 1, structuredClone(srcData.fragment)),
289
+ );
284
290
  break;
285
291
  case "make-child": {
286
292
  const target = getNodeAtPath(S.document, targetPath);
287
293
  const len = target?.children?.length || 0;
288
- update(insertNode(S, targetPath, len, structuredClone(srcData.fragment)));
294
+ transactDoc(activeTab.value, (t) =>
295
+ mutateInsertNode(t, targetPath, len, structuredClone(srcData.fragment)),
296
+ );
289
297
  break;
290
298
  }
291
299
  }
@@ -303,12 +311,10 @@ export function applyDropInstruction(instruction, srcData, targetPath) {
303
311
  (/** @type {any} */ e) => e === specifier || e === comp.package,
304
312
  );
305
313
  if (!alreadyImported) {
306
- update(
307
- applyMutation(currentS, (/** @type {any} */ doc) => {
308
- if (!doc.$elements) doc.$elements = [];
309
- doc.$elements.push(specifier);
310
- }),
311
- );
314
+ transact(activeTab.value, (/** @type {any} */ doc) => {
315
+ if (!doc.$elements) doc.$elements = [];
316
+ doc.$elements.push(specifier);
317
+ });
312
318
  }
313
319
  } else {
314
320
  const alreadyImported = elements.some(
@@ -318,12 +324,10 @@ export function applyDropInstruction(instruction, srcData, targetPath) {
318
324
  );
319
325
  if (!alreadyImported) {
320
326
  const relPath = computeRelativePath(currentS.documentPath, comp.path);
321
- update(
322
- applyMutation(currentS, (/** @type {any} */ doc) => {
323
- if (!doc.$elements) doc.$elements = [];
324
- doc.$elements.push({ $ref: relPath });
325
- }),
326
- );
327
+ transact(activeTab.value, (/** @type {any} */ doc) => {
328
+ if (!doc.$elements) doc.$elements = [];
329
+ doc.$elements.push({ $ref: relPath });
330
+ });
327
331
  }
328
332
  }
329
333
  }
@@ -7,16 +7,9 @@ import * as monaco from "monaco-editor/esm/vs/editor/editor.api.js";
7
7
  import { html, render as litRender, nothing } from "lit-html";
8
8
  import { ref } from "lit-html/directives/ref.js";
9
9
 
10
- import {
11
- getState,
12
- update,
13
- renderOnly,
14
- canvasWrap,
15
- canvasPanels,
16
- updateDef,
17
- updateProperty,
18
- getNodeAtPath,
19
- } from "../store.js";
10
+ import { getState, renderOnly, canvasWrap, canvasPanels, getNodeAtPath } from "../store.js";
11
+ import { activeTab } from "../workspace/workspace.js";
12
+ import { transactDoc, mutateUpdateDef, mutateUpdateProperty } from "../tabs/transact.js";
20
13
  import { view } from "../view.js";
21
14
  import { codeService, setLintMarkers, getFunctionArgs } from "../services/code-services.js";
22
15
 
@@ -126,15 +119,15 @@ export function renderFunctionEditor() {
126
119
 
127
120
  clearTimeout(syncDebounce);
128
121
  syncDebounce = setTimeout(() => {
129
- const S = getState();
130
122
  const newBody = view.functionEditor.getValue();
131
123
  if (editing.type === "def") {
132
- update(updateDef(S, editing.defName, { body: newBody }));
124
+ transactDoc(activeTab.value, (t) => mutateUpdateDef(t, editing.defName, { body: newBody }));
133
125
  } else if (editing.type === "event") {
126
+ const S = getState();
134
127
  const node = getNodeAtPath(S.document, editing.path);
135
128
  const current = node?.[editing.eventKey] || {};
136
- update(
137
- updateProperty(S, editing.path, editing.eventKey, {
129
+ transactDoc(activeTab.value, (t) =>
130
+ mutateUpdateProperty(t, editing.path, editing.eventKey, {
138
131
  ...current,
139
132
  $prototype: "Function",
140
133
  body: newBody,
@@ -1,7 +1,9 @@
1
1
  /** Elements panel — block/component palette with categorized accordion and search filter. */
2
2
 
3
3
  import { html, nothing } from "lit-html";
4
- import { getState, update, getNodeAtPath, insertNode } from "../store.js";
4
+ import { getState, getNodeAtPath } from "../store.js";
5
+ import { activeTab } from "../workspace/workspace.js";
6
+ import { transactDoc, mutateInsertNode } from "../tabs/transact.js";
5
7
  import { view } from "../view.js";
6
8
  import { getEffectiveElements } from "../site-context.js";
7
9
  import { componentRegistry } from "../files/components.js";
@@ -40,7 +42,9 @@ export function renderElementsTemplate(ctx) {
40
42
  const parentPath = s.selection || [];
41
43
  const parent = getNodeAtPath(s.document, parentPath);
42
44
  const idx = parent?.children ? parent.children.length : 0;
43
- update(insertNode(s, parentPath, idx, structuredClone(def)));
45
+ transactDoc(activeTab.value, (t) =>
46
+ mutateInsertNode(t, parentPath, idx, structuredClone(def)),
47
+ );
44
48
  }}
45
49
  >
46
50
  <div class="element-card-preview"></div>
@@ -114,7 +118,9 @@ export function renderElementsTemplate(ctx) {
114
118
  ]),
115
119
  ),
116
120
  };
117
- update(insertNode(s, parentPath, idx, structuredClone(instanceDef)));
121
+ transactDoc(activeTab.value, (t) =>
122
+ mutateInsertNode(t, parentPath, idx, structuredClone(instanceDef)),
123
+ );
118
124
  }}
119
125
  >
120
126
  <div class="element-card-preview">
@@ -1,6 +1,8 @@
1
- import { getNodeAtPath, updateProperty, update } from "../store.js";
1
+ import { getNodeAtPath } from "../store.js";
2
2
  import { html, nothing } from "lit-html";
3
3
  import { live } from "lit-html/directives/live.js";
4
+ import { activeTab } from "../workspace/workspace.js";
5
+ import { transactDoc, mutateUpdateProperty } from "../tabs/transact.js";
4
6
 
5
7
  export const EVENT_NAMES = [
6
8
  "onclick",
@@ -15,17 +17,17 @@ export const EVENT_NAMES = [
15
17
  "onmouseleave",
16
18
  ];
17
19
 
18
- /**
19
- * @param {any} S - Studio state
20
- * @param {{ isCustomElementDoc: () => boolean; renderCanvas: () => void }} helpers
21
- */
22
- export function eventsSidebarTemplate(S, helpers) {
23
- const { isCustomElementDoc, renderCanvas } = helpers;
24
- if (!S.selection) return html`<div class="empty-state">Select an element to edit events</div>`;
25
- const node = getNodeAtPath(S.document, S.selection);
20
+ /** @param {{ isCustomElementDoc: () => boolean }} helpers */
21
+ export function eventsSidebarTemplate(helpers) {
22
+ const { isCustomElementDoc } = helpers;
23
+ const tab = activeTab.value;
24
+ const selection = tab?.session.selection;
25
+ const document = tab?.doc.document;
26
+ if (!selection) return html`<div class="empty-state">Select an element to edit events</div>`;
27
+ const node = getNodeAtPath(document, selection);
26
28
  if (!node) return html`<div class="empty-state">Node not found</div>`;
27
29
 
28
- const defs = S.document.state || {};
30
+ const defs = document.state || {};
29
31
  const functionDefs = Object.entries(defs).filter(
30
32
  ([, d]) => d.$prototype === "Function" || d.$handler,
31
33
  );
@@ -87,9 +89,10 @@ export function eventsSidebarTemplate(S, helpers) {
87
89
  @change=${(/** @type {any} */ e) => {
88
90
  const newKey = e.target.value;
89
91
  if (newKey && newKey !== evKey) {
90
- let s = updateProperty(S, S.selection, evKey, undefined);
91
- s = updateProperty(s, S.selection, newKey, node[evKey]);
92
- update(s);
92
+ transactDoc(activeTab.value, (t) => {
93
+ mutateUpdateProperty(t, selection, evKey, undefined);
94
+ mutateUpdateProperty(t, selection, newKey, node[evKey]);
95
+ });
93
96
  }
94
97
  }}
95
98
  >
@@ -103,8 +106,8 @@ export function eventsSidebarTemplate(S, helpers) {
103
106
  .value=${live(isInline ? "inline" : "ref")}
104
107
  @change=${(/** @type {any} */ e) => {
105
108
  if (e.target.value === "inline") {
106
- update(
107
- updateProperty(S, S.selection, evKey, {
109
+ transactDoc(activeTab.value, (t) =>
110
+ mutateUpdateProperty(t, selection, evKey, {
108
111
  $prototype: "Function",
109
112
  body: "",
110
113
  parameters: [],
@@ -112,10 +115,10 @@ export function eventsSidebarTemplate(S, helpers) {
112
115
  );
113
116
  } else {
114
117
  const firstFn = functionDefs[0];
115
- update(
116
- updateProperty(
117
- S,
118
- S.selection,
118
+ transactDoc(activeTab.value, (t) =>
119
+ mutateUpdateProperty(
120
+ t,
121
+ selection,
119
122
  evKey,
120
123
  firstFn ? { $ref: `#/state/${firstFn[0]}` } : { $ref: "" },
121
124
  ),
@@ -129,7 +132,10 @@ export function eventsSidebarTemplate(S, helpers) {
129
132
  <sp-action-button
130
133
  size="xs"
131
134
  quiet
132
- @click=${() => update(updateProperty(S, S.selection, evKey, undefined))}
135
+ @click=${() =>
136
+ transactDoc(activeTab.value, (t) =>
137
+ mutateUpdateProperty(t, selection, evKey, undefined),
138
+ )}
133
139
  >
134
140
  <sp-icon-delete slot="icon"></sp-icon-delete>
135
141
  </sp-action-button>
@@ -144,8 +150,8 @@ export function eventsSidebarTemplate(S, helpers) {
144
150
  placeholder="// handler body"
145
151
  .value=${live(evVal.body || "")}
146
152
  @input=${(/** @type {any} */ e) => {
147
- update(
148
- updateProperty(S, S.selection, evKey, {
153
+ transactDoc(activeTab.value, (t) =>
154
+ mutateUpdateProperty(t, selection, evKey, {
149
155
  $prototype: "Function",
150
156
  body: e.target.value,
151
157
  parameters: evVal.parameters || [],
@@ -159,18 +165,11 @@ export function eventsSidebarTemplate(S, helpers) {
159
165
  quiet
160
166
  title="Open in editor"
161
167
  @click=${() => {
162
- S = {
163
- ...S,
164
- ui: {
165
- ...S.ui,
166
- editingFunction: {
167
- type: "event",
168
- path: S.selection,
169
- eventKey: evKey,
170
- },
171
- },
168
+ tab.session.ui.editingFunction = {
169
+ type: "event",
170
+ path: selection,
171
+ eventKey: evKey,
172
172
  };
173
- renderCanvas();
174
173
  }}
175
174
  >
176
175
  <sp-icon-code slot="icon"></sp-icon-code>
@@ -184,9 +183,13 @@ export function eventsSidebarTemplate(S, helpers) {
184
183
  .value=${live(evVal.$ref || "__none__")}
185
184
  @change=${(/** @type {any} */ e) => {
186
185
  if (e.target.value && e.target.value !== "__none__") {
187
- update(updateProperty(S, S.selection, evKey, { $ref: e.target.value }));
186
+ transactDoc(activeTab.value, (t) =>
187
+ mutateUpdateProperty(t, selection, evKey, { $ref: e.target.value }),
188
+ );
188
189
  } else {
189
- update(updateProperty(S, S.selection, evKey, undefined));
190
+ transactDoc(activeTab.value, (t) =>
191
+ mutateUpdateProperty(t, selection, evKey, undefined),
192
+ );
190
193
  }
191
194
  }}
192
195
  >
@@ -212,12 +215,14 @@ export function eventsSidebarTemplate(S, helpers) {
212
215
  }
213
216
  }
214
217
  if (functionDefs.length > 0) {
215
- update(
216
- updateProperty(S, S.selection, evName, { $ref: `#/state/${functionDefs[0][0]}` }),
218
+ transactDoc(activeTab.value, (t) =>
219
+ mutateUpdateProperty(t, selection, evName, {
220
+ $ref: `#/state/${functionDefs[0][0]}`,
221
+ }),
217
222
  );
218
223
  } else {
219
- update(
220
- updateProperty(S, S.selection, evName, {
224
+ transactDoc(activeTab.value, (t) =>
225
+ mutateUpdateProperty(t, selection, evName, {
221
226
  $prototype: "Function",
222
227
  body: "",
223
228
  parameters: [],
@@ -44,8 +44,11 @@ async function gitAction(action, body) {
44
44
 
45
45
  let _pollTimer = /** @type {any} */ (null);
46
46
 
47
- /** @param {any} S */
48
- export function renderGitPanel(S) {
47
+ /**
48
+ * @param {any} S
49
+ * @param {any} ctx
50
+ */
51
+ export function renderGitPanel(S, ctx) {
49
52
  const status = S.ui.gitStatus;
50
53
  const branches = S.ui.gitBranches;
51
54
  const loading = S.ui.gitLoading;
@@ -156,9 +159,48 @@ export function renderGitPanel(S) {
156
159
  const parts = file.path.split("/");
157
160
  const name = parts.pop();
158
161
  const dir = parts.join("/");
162
+
163
+ const onFileClick = async () => {
164
+ // Only support diff for modified/added files
165
+ if (file.status !== "M" && file.status !== "A") return;
166
+ if (!file.path.endsWith(".md") && !file.path.endsWith(".json")) return;
167
+
168
+ try {
169
+ const plat = getPlatform();
170
+ updateUi("gitLoading", true);
171
+ const originalContent = await plat.gitShow({ path: file.path, ref: "HEAD" });
172
+
173
+ // Determine if it's markdown or JSON
174
+ const isMarkdown = file.path.endsWith(".md");
175
+
176
+ // For now, we'll need to pass the original content to the canvas
177
+ // This requires state management which we'll add to studio.js
178
+ updateUi("gitDiffState", {
179
+ filePath: file.path,
180
+ originalContent,
181
+ isMarkdown,
182
+ fileStatus: file.status,
183
+ });
184
+
185
+ // Trigger diff mode via context
186
+ if (ctx?.setCanvasMode) {
187
+ ctx.setCanvasMode("git-diff");
188
+ }
189
+ } catch (/** @type {any} */ e) {
190
+ updateUi("gitError", `Failed to load diff: ${e.message}`);
191
+ } finally {
192
+ updateUi("gitLoading", false);
193
+ }
194
+ };
195
+
159
196
  return html`
160
197
  <div class="git-file-row">
161
- <span class="git-file-info">
198
+ <span
199
+ class="git-file-info"
200
+ style="cursor: pointer; flex: 1;"
201
+ title="Click to view diff"
202
+ @click=${onFileClick}
203
+ >
162
204
  <span class="git-file-name" title=${file.path}>${name}</span>
163
205
  ${dir ? html`<span class="git-file-dir">${dir}</span>` : nothing}
164
206
  </span>
@@ -6,7 +6,6 @@
6
6
  import { html, nothing } from "lit-html";
7
7
  import {
8
8
  getState,
9
- update,
10
9
  flattenTree,
11
10
  getNodeAtPath,
12
11
  pathKey,
@@ -14,11 +13,10 @@ import {
14
13
  parentElementPath,
15
14
  childIndex,
16
15
  nodeLabel,
17
- selectNode,
18
- moveNode,
19
- removeNode,
20
16
  VOID_ELEMENTS,
21
17
  } from "../store.js";
18
+ import { activeTab } from "../workspace/workspace.js";
19
+ import { transactDoc, mutateMoveNode, mutateRemoveNode } from "../tabs/transact.js";
22
20
  import { view } from "../view.js";
23
21
  import { isInlineElement } from "../editor/inline-edit.js";
24
22
  import { showContextMenu } from "../editor/context-menu.js";
@@ -34,7 +32,8 @@ export function renderLayersTemplate(ctx) {
34
32
  view.dndCleanups = [];
35
33
 
36
34
  const rows = flattenTree(S.document);
37
- const collapsed = S._collapsed || (S._collapsed = new Set());
35
+ const _S = /** @type {any} */ (S);
36
+ const collapsed = _S._collapsed || (_S._collapsed = new Set());
38
37
 
39
38
  /** @type {any[]} */
40
39
  const layerRows = [];
@@ -142,7 +141,7 @@ export function renderLayersTemplate(ctx) {
142
141
  data-dnd-row=${isElement ? key : nothing}
143
142
  data-dnd-depth=${isElement ? depth : nothing}
144
143
  data-dnd-void=${isElement && isVoidEl ? "" : nothing}
145
- @click=${() => update(selectNode(getState(), path))}
144
+ @click=${() => (activeTab.value.session.selection = path)}
146
145
  @contextmenu=${isElement
147
146
  ? (/** @type {any} */ e) =>
148
147
  showContextMenu(e, path, getState(), {
@@ -175,7 +174,9 @@ export function renderLayersTemplate(ctx) {
175
174
  @click=${(/** @type {any} */ e) => {
176
175
  e.stopPropagation();
177
176
  /** @type {HTMLElement} */ (e.currentTarget).blur();
178
- update(moveNode(getState(), path, parentPath, idx - 1));
177
+ transactDoc(activeTab.value, (t) =>
178
+ mutateMoveNode(t, path, parentPath, idx - 1),
179
+ );
179
180
  }}
180
181
  >
181
182
  <sp-icon-arrow-up slot="icon"></sp-icon-arrow-up>
@@ -189,7 +190,9 @@ export function renderLayersTemplate(ctx) {
189
190
  @click=${(/** @type {any} */ e) => {
190
191
  e.stopPropagation();
191
192
  /** @type {HTMLElement} */ (e.currentTarget).blur();
192
- update(moveNode(getState(), path, parentPath, idx + 2));
193
+ transactDoc(activeTab.value, (t) =>
194
+ mutateMoveNode(t, path, parentPath, idx + 2),
195
+ );
193
196
  }}
194
197
  >
195
198
  <sp-icon-arrow-down slot="icon"></sp-icon-arrow-down>
@@ -206,7 +209,7 @@ export function renderLayersTemplate(ctx) {
206
209
  const prevPath = [...parentPath, idx - 1];
207
210
  const prev = getNodeAtPath(getState().document, prevPath);
208
211
  const len = prev?.children?.length || 0;
209
- update(moveNode(getState(), path, prevPath, len));
212
+ transactDoc(activeTab.value, (t) => mutateMoveNode(t, path, prevPath, len));
210
213
  }}
211
214
  >
212
215
  <sp-icon-arrow-right slot="icon"></sp-icon-arrow-right>
@@ -221,7 +224,9 @@ export function renderLayersTemplate(ctx) {
221
224
  e.stopPropagation();
222
225
  /** @type {HTMLElement} */ (e.currentTarget).blur();
223
226
  const parentIdx = /** @type {number} */ (childIndex(parentPath));
224
- update(moveNode(getState(), path, grandparentPath, parentIdx + 1));
227
+ transactDoc(activeTab.value, (t) =>
228
+ mutateMoveNode(t, path, grandparentPath, parentIdx + 1),
229
+ );
225
230
  }}
226
231
  >
227
232
  <sp-icon-arrow-left slot="icon"></sp-icon-arrow-left>
@@ -234,7 +239,7 @@ export function renderLayersTemplate(ctx) {
234
239
  title="Delete"
235
240
  @click=${(/** @type {any} */ e) => {
236
241
  e.stopPropagation();
237
- update(removeNode(getState(), path));
242
+ transactDoc(activeTab.value, (t) => mutateRemoveNode(t, path));
238
243
  }}
239
244
  >
240
245
  <sp-icon-close slot="icon"></sp-icon-close>