@jxsuite/studio 0.11.0 → 0.14.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 (55) hide show
  1. package/dist/studio.js +6517 -5870
  2. package/dist/studio.js.map +57 -51
  3. package/package.json +5 -3
  4. package/src/canvas/canvas-diff.js +184 -0
  5. package/src/canvas/canvas-helpers.js +10 -14
  6. package/src/canvas/canvas-live-render.js +28 -2
  7. package/src/canvas/canvas-render.js +170 -20
  8. package/src/canvas/canvas-utils.js +22 -25
  9. package/src/editor/component-inline-edit.js +55 -44
  10. package/src/editor/content-inline-edit.js +47 -50
  11. package/src/editor/context-menu.js +78 -53
  12. package/src/editor/convert-to-component.js +11 -14
  13. package/src/editor/insertion-helper.js +8 -11
  14. package/src/editor/shortcuts.js +88 -64
  15. package/src/files/components.js +15 -4
  16. package/src/files/file-ops.js +57 -108
  17. package/src/files/files.js +81 -28
  18. package/src/panels/activity-bar.js +31 -7
  19. package/src/panels/block-action-bar.js +104 -80
  20. package/src/panels/canvas-dnd.js +4 -10
  21. package/src/panels/dnd.js +77 -35
  22. package/src/panels/editors.js +17 -26
  23. package/src/panels/elements-panel.js +17 -11
  24. package/src/panels/events-panel.js +44 -39
  25. package/src/panels/git-panel.js +47 -4
  26. package/src/panels/layers-panel.js +25 -21
  27. package/src/panels/left-panel.js +109 -44
  28. package/src/panels/overlays.js +91 -41
  29. package/src/panels/panel-events.js +28 -37
  30. package/src/panels/preview-render.js +7 -3
  31. package/src/panels/properties-panel.js +179 -104
  32. package/src/panels/pseudo-preview.js +9 -8
  33. package/src/panels/right-panel.js +85 -37
  34. package/src/panels/shared.js +0 -22
  35. package/src/panels/signals-panel.js +125 -54
  36. package/src/panels/statusbar.js +26 -19
  37. package/src/panels/style-inputs.js +5 -4
  38. package/src/panels/style-panel.js +128 -105
  39. package/src/panels/style-utils.js +8 -6
  40. package/src/panels/stylebook-layers-panel.js +5 -5
  41. package/src/panels/stylebook-panel.js +27 -31
  42. package/src/panels/tab-strip.js +124 -0
  43. package/src/panels/toolbar.js +40 -10
  44. package/src/reactivity.js +28 -0
  45. package/src/settings/content-types-editor.js +56 -7
  46. package/src/settings/defs-editor.js +56 -7
  47. package/src/settings/schema-field-ui.js +60 -25
  48. package/src/state.js +2 -459
  49. package/src/store.js +61 -219
  50. package/src/studio.js +163 -300
  51. package/src/tabs/tab.js +168 -0
  52. package/src/tabs/transact.js +406 -0
  53. package/src/ui/color-selector.js +4 -5
  54. package/src/view.js +3 -0
  55. package/src/workspace/workspace.js +90 -0
@@ -7,33 +7,25 @@ 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 { 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
 
23
16
  function getFunctionBody(/** @type {any} */ editing) {
24
- const S = getState();
17
+ const document = activeTab.value?.doc.document;
25
18
  if (editing.type === "def") {
26
- return S.document.state?.[editing.defName]?.body || "";
19
+ return document?.state?.[editing.defName]?.body || "";
27
20
  } else if (editing.type === "event") {
28
- const node = getNodeAtPath(S.document, editing.path);
21
+ const node = getNodeAtPath(document, editing.path);
29
22
  return node?.[editing.eventKey]?.body || "";
30
23
  }
31
24
  return "";
32
25
  }
33
26
 
34
27
  export function renderFunctionEditor() {
35
- const S = getState();
36
- const editing = S.ui.editingFunction;
28
+ const editing = activeTab.value?.session.ui.editingFunction;
37
29
 
38
30
  // If editor already exists and matches current target, just sync value
39
31
  if (view.functionEditor && view.functionEditor._editingTarget === JSON.stringify(editing)) {
@@ -83,7 +75,7 @@ export function renderFunctionEditor() {
83
75
  );
84
76
 
85
77
  const body = getFunctionBody(editing);
86
- const args = getFunctionArgs(editing, S);
78
+ const args = getFunctionArgs(editing, activeTab.value?.doc.document);
87
79
 
88
80
  view.functionEditor = monaco.editor.create(/** @type {any} */ (editorContainer), {
89
81
  value: body,
@@ -126,15 +118,15 @@ export function renderFunctionEditor() {
126
118
 
127
119
  clearTimeout(syncDebounce);
128
120
  syncDebounce = setTimeout(() => {
129
- const S = getState();
130
121
  const newBody = view.functionEditor.getValue();
131
- if (editing.type === "def") {
132
- update(updateDef(S, editing.defName, { body: newBody }));
133
- } else if (editing.type === "event") {
134
- const node = getNodeAtPath(S.document, editing.path);
135
- const current = node?.[editing.eventKey] || {};
136
- update(
137
- updateProperty(S, editing.path, editing.eventKey, {
122
+ const ed = /** @type {any} */ (editing);
123
+ if (ed.type === "def") {
124
+ transactDoc(activeTab.value, (t) => mutateUpdateDef(t, ed.defName, { body: newBody }));
125
+ } else if (ed.type === "event") {
126
+ const node = getNodeAtPath(activeTab.value?.doc.document, ed.path);
127
+ const current = node?.[ed.eventKey] || {};
128
+ transactDoc(activeTab.value, (t) =>
129
+ mutateUpdateProperty(t, ed.path, ed.eventKey, {
138
130
  ...current,
139
131
  $prototype: "Function",
140
132
  body: newBody,
@@ -164,8 +156,7 @@ export function registerFunctionCompletions() {
164
156
  monaco.languages.registerCompletionItemProvider("javascript", {
165
157
  triggerCharacters: ["."],
166
158
  provideCompletionItems(model, position) {
167
- const S = getState();
168
- const defs = S?.document?.state || {};
159
+ const defs = activeTab.value?.doc.document?.state || {};
169
160
  const word = model.getWordUntilPosition(position);
170
161
  const range = {
171
162
  startLineNumber: position.lineNumber,
@@ -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 { 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";
@@ -11,7 +13,7 @@ import { componentRegistry } from "../files/components.js";
11
13
  * @returns {import("lit-html").TemplateResult}
12
14
  */
13
15
  export function renderElementsTemplate(ctx) {
14
- const S = getState();
16
+ const tab = activeTab.value;
15
17
 
16
18
  const categories = Object.entries(ctx.webdata.elements).map(
17
19
  (/** @type {any} */ [category, elements]) => {
@@ -36,11 +38,13 @@ export function renderElementsTemplate(ctx) {
36
38
  class="element-card"
37
39
  data-block-tag=${tag}
38
40
  @click=${() => {
39
- const s = getState();
40
- const parentPath = s.selection || [];
41
- const parent = getNodeAtPath(s.document, parentPath);
41
+ const t = activeTab.value;
42
+ const parentPath = t?.session.selection || [];
43
+ const parent = getNodeAtPath(t?.doc.document, parentPath);
42
44
  const idx = parent?.children ? parent.children.length : 0;
43
- update(insertNode(s, parentPath, idx, structuredClone(def)));
45
+ transactDoc(t, (tr) =>
46
+ mutateInsertNode(tr, parentPath, idx, structuredClone(def)),
47
+ );
44
48
  }}
45
49
  >
46
50
  <div class="element-card-preview"></div>
@@ -53,7 +57,7 @@ export function renderElementsTemplate(ctx) {
53
57
  },
54
58
  );
55
59
 
56
- const effectiveEls = getEffectiveElements(S.document?.$elements);
60
+ const effectiveEls = getEffectiveElements(tab?.doc.document?.$elements);
57
61
  /** @type {Set<string>} */
58
62
  const enabledTags = new Set();
59
63
  for (const entry of effectiveEls) {
@@ -101,9 +105,9 @@ export function renderElementsTemplate(ctx) {
101
105
  ? `${comp.package}: <${comp.tagName}>`
102
106
  : comp.path}
103
107
  @click=${() => {
104
- const s = getState();
105
- const parentPath = s.selection || [];
106
- const parent = getNodeAtPath(s.document, parentPath);
108
+ const t = activeTab.value;
109
+ const parentPath = t?.session.selection || [];
110
+ const parent = getNodeAtPath(t?.doc.document, parentPath);
107
111
  const idx = parent?.children ? parent.children.length : 0;
108
112
  const instanceDef = {
109
113
  tagName: comp.tagName,
@@ -114,7 +118,9 @@ export function renderElementsTemplate(ctx) {
114
118
  ]),
115
119
  ),
116
120
  };
117
- update(insertNode(s, parentPath, idx, structuredClone(instanceDef)));
121
+ transactDoc(t, (tr) =>
122
+ mutateInsertNode(tr, 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: [],
@@ -7,6 +7,7 @@ import { html, nothing } from "lit-html";
7
7
  import { live } from "lit-html/directives/live.js";
8
8
  import { getPlatform } from "../platform.js";
9
9
  import { updateUi, renderOnly } from "../store.js";
10
+ import { view } from "../view.js";
10
11
 
11
12
  async function refreshGitStatus() {
12
13
  const plat = getPlatform();
@@ -44,8 +45,11 @@ async function gitAction(action, body) {
44
45
 
45
46
  let _pollTimer = /** @type {any} */ (null);
46
47
 
47
- /** @param {any} S */
48
- export function renderGitPanel(S) {
48
+ /**
49
+ * @param {any} S
50
+ * @param {any} ctx
51
+ */
52
+ export function renderGitPanel(S, ctx) {
49
53
  const status = S.ui.gitStatus;
50
54
  const branches = S.ui.gitBranches;
51
55
  const loading = S.ui.gitLoading;
@@ -57,7 +61,7 @@ export function renderGitPanel(S) {
57
61
 
58
62
  if (!_pollTimer) {
59
63
  _pollTimer = setInterval(() => {
60
- if (S.ui.leftTab === "git" && !S.ui.gitLoading) refreshGitStatus();
64
+ if (view.leftTab === "git" && !S.ui.gitLoading) refreshGitStatus();
61
65
  }, 30000);
62
66
  }
63
67
 
@@ -156,9 +160,48 @@ export function renderGitPanel(S) {
156
160
  const parts = file.path.split("/");
157
161
  const name = parts.pop();
158
162
  const dir = parts.join("/");
163
+
164
+ const onFileClick = async () => {
165
+ // Only support diff for modified/added files
166
+ if (file.status !== "M" && file.status !== "A") return;
167
+ if (!file.path.endsWith(".md") && !file.path.endsWith(".json")) return;
168
+
169
+ try {
170
+ const plat = getPlatform();
171
+ updateUi("gitLoading", true);
172
+ const originalContent = await plat.gitShow({ path: file.path, ref: "HEAD" });
173
+
174
+ // Determine if it's markdown or JSON
175
+ const isMarkdown = file.path.endsWith(".md");
176
+
177
+ // For now, we'll need to pass the original content to the canvas
178
+ // This requires state management which we'll add to studio.js
179
+ updateUi("gitDiffState", {
180
+ filePath: file.path,
181
+ originalContent,
182
+ isMarkdown,
183
+ fileStatus: file.status,
184
+ });
185
+
186
+ // Trigger diff mode via context
187
+ if (ctx?.setCanvasMode) {
188
+ ctx.setCanvasMode("git-diff");
189
+ }
190
+ } catch (/** @type {any} */ e) {
191
+ updateUi("gitError", `Failed to load diff: ${e.message}`);
192
+ } finally {
193
+ updateUi("gitLoading", false);
194
+ }
195
+ };
196
+
159
197
  return html`
160
198
  <div class="git-file-row">
161
- <span class="git-file-info">
199
+ <span
200
+ class="git-file-info"
201
+ style="cursor: pointer; flex: 1;"
202
+ title="Click to view diff"
203
+ @click=${onFileClick}
204
+ >
162
205
  <span class="git-file-name" title=${file.path}>${name}</span>
163
206
  ${dir ? html`<span class="git-file-dir">${dir}</span>` : nothing}
164
207
  </span>
@@ -5,8 +5,6 @@
5
5
 
6
6
  import { html, nothing } from "lit-html";
7
7
  import {
8
- getState,
9
- update,
10
8
  flattenTree,
11
9
  getNodeAtPath,
12
10
  pathKey,
@@ -14,11 +12,10 @@ import {
14
12
  parentElementPath,
15
13
  childIndex,
16
14
  nodeLabel,
17
- selectNode,
18
- moveNode,
19
- removeNode,
20
15
  VOID_ELEMENTS,
21
16
  } from "../store.js";
17
+ import { activeTab } from "../workspace/workspace.js";
18
+ import { transactDoc, mutateMoveNode, mutateRemoveNode } from "../tabs/transact.js";
22
19
  import { view } from "../view.js";
23
20
  import { isInlineElement } from "../editor/inline-edit.js";
24
21
  import { showContextMenu } from "../editor/context-menu.js";
@@ -28,13 +25,13 @@ import { showContextMenu } from "../editor/context-menu.js";
28
25
  * @returns {import("lit-html").TemplateResult}
29
26
  */
30
27
  export function renderLayersTemplate(ctx) {
31
- const S = getState();
28
+ const tab = activeTab.value;
32
29
 
33
30
  for (const fn of view.dndCleanups) fn();
34
31
  view.dndCleanups = [];
35
32
 
36
- const rows = flattenTree(S.document);
37
- const collapsed = S._collapsed || (S._collapsed = new Set());
33
+ const rows = flattenTree(tab?.doc.document);
34
+ const collapsed = view._layersCollapsed || (view._layersCollapsed = new Set());
38
35
 
39
36
  /** @type {any[]} */
40
37
  const layerRows = [];
@@ -49,7 +46,7 @@ export function renderLayersTemplate(ctx) {
49
46
  }
50
47
  if (hidden) continue;
51
48
 
52
- if (S.mode === "content" && path.length === 0) continue;
49
+ if (tab?.doc.mode === "content" && path.length === 0) continue;
53
50
 
54
51
  if (nodeType === "text") {
55
52
  const textPreview = String(node).length > 40 ? String(node).slice(0, 40) + "…" : String(node);
@@ -67,12 +64,12 @@ export function renderLayersTemplate(ctx) {
67
64
 
68
65
  if (path.length >= 2 && nodeType === "element") {
69
66
  const pPath = parentElementPath(path);
70
- const parentNode = pPath ? getNodeAtPath(S.document, pPath) : null;
67
+ const parentNode = pPath ? getNodeAtPath(tab?.doc.document, pPath) : null;
71
68
  if (parentNode && isInlineElement(node, parentNode)) continue;
72
69
  }
73
70
 
74
71
  const key = pathKey(path);
75
- const isSelected = pathsEqual(path, S.selection);
72
+ const isSelected = pathsEqual(path, tab?.session.selection);
76
73
  const hasChildren = Array.isArray(node.children) && node.children.length > 0;
77
74
  const hasMapChildren =
78
75
  node.children && typeof node.children === "object" && node.children.$prototype === "Array";
@@ -116,10 +113,10 @@ export function renderLayersTemplate(ctx) {
116
113
  }
117
114
 
118
115
  const isElement = nodeType === "element";
119
- const isRoot = S.mode === "content" ? path.length === 0 : path.length < 2;
116
+ const isRoot = tab?.doc.mode === "content" ? path.length === 0 : path.length < 2;
120
117
  const idx = isElement ? /** @type {number} */ (childIndex(path)) : 0;
121
118
  const parentPath = isElement && !isRoot ? /** @type {any} */ (parentElementPath(path)) : null;
122
- const parentNode = parentPath ? getNodeAtPath(S.document, parentPath) : null;
119
+ const parentNode = parentPath ? getNodeAtPath(tab?.doc.document, parentPath) : null;
123
120
  const siblingCount = parentNode?.children?.length || 0;
124
121
  const canMoveUp = isElement && !isRoot && idx > 0;
125
122
  const canMoveDown = isElement && !isRoot && idx < siblingCount - 1;
@@ -142,10 +139,11 @@ export function renderLayersTemplate(ctx) {
142
139
  data-dnd-row=${isElement ? key : nothing}
143
140
  data-dnd-depth=${isElement ? depth : nothing}
144
141
  data-dnd-void=${isElement && isVoidEl ? "" : nothing}
145
- @click=${() => update(selectNode(getState(), path))}
142
+ data-dnd-expanded=${isElement && isExpandable && !collapsed.has(key) ? "" : nothing}
143
+ @click=${() => (activeTab.value.session.selection = path)}
146
144
  @contextmenu=${isElement
147
145
  ? (/** @type {any} */ e) =>
148
- showContextMenu(e, path, getState(), {
146
+ showContextMenu(e, path, {
149
147
  onEditComponent: ctx.navigateToComponent,
150
148
  })
151
149
  : nothing}
@@ -175,7 +173,9 @@ export function renderLayersTemplate(ctx) {
175
173
  @click=${(/** @type {any} */ e) => {
176
174
  e.stopPropagation();
177
175
  /** @type {HTMLElement} */ (e.currentTarget).blur();
178
- update(moveNode(getState(), path, parentPath, idx - 1));
176
+ transactDoc(activeTab.value, (t) =>
177
+ mutateMoveNode(t, path, parentPath, idx - 1),
178
+ );
179
179
  }}
180
180
  >
181
181
  <sp-icon-arrow-up slot="icon"></sp-icon-arrow-up>
@@ -189,7 +189,9 @@ export function renderLayersTemplate(ctx) {
189
189
  @click=${(/** @type {any} */ e) => {
190
190
  e.stopPropagation();
191
191
  /** @type {HTMLElement} */ (e.currentTarget).blur();
192
- update(moveNode(getState(), path, parentPath, idx + 2));
192
+ transactDoc(activeTab.value, (t) =>
193
+ mutateMoveNode(t, path, parentPath, idx + 2),
194
+ );
193
195
  }}
194
196
  >
195
197
  <sp-icon-arrow-down slot="icon"></sp-icon-arrow-down>
@@ -204,9 +206,9 @@ export function renderLayersTemplate(ctx) {
204
206
  e.stopPropagation();
205
207
  /** @type {HTMLElement} */ (e.currentTarget).blur();
206
208
  const prevPath = [...parentPath, idx - 1];
207
- const prev = getNodeAtPath(getState().document, prevPath);
209
+ const prev = getNodeAtPath(activeTab.value?.doc.document, prevPath);
208
210
  const len = prev?.children?.length || 0;
209
- update(moveNode(getState(), path, prevPath, len));
211
+ transactDoc(activeTab.value, (t) => mutateMoveNode(t, path, prevPath, len));
210
212
  }}
211
213
  >
212
214
  <sp-icon-arrow-right slot="icon"></sp-icon-arrow-right>
@@ -221,7 +223,9 @@ export function renderLayersTemplate(ctx) {
221
223
  e.stopPropagation();
222
224
  /** @type {HTMLElement} */ (e.currentTarget).blur();
223
225
  const parentIdx = /** @type {number} */ (childIndex(parentPath));
224
- update(moveNode(getState(), path, grandparentPath, parentIdx + 1));
226
+ transactDoc(activeTab.value, (t) =>
227
+ mutateMoveNode(t, path, grandparentPath, parentIdx + 1),
228
+ );
225
229
  }}
226
230
  >
227
231
  <sp-icon-arrow-left slot="icon"></sp-icon-arrow-left>
@@ -234,7 +238,7 @@ export function renderLayersTemplate(ctx) {
234
238
  title="Delete"
235
239
  @click=${(/** @type {any} */ e) => {
236
240
  e.stopPropagation();
237
- update(removeNode(getState(), path));
241
+ transactDoc(activeTab.value, (t) => mutateRemoveNode(t, path));
238
242
  }}
239
243
  >
240
244
  <sp-icon-close slot="icon"></sp-icon-close>