@jxsuite/studio 0.28.2 → 0.28.4

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 (104) hide show
  1. package/dist/studio.js +86144 -83971
  2. package/dist/studio.js.map +229 -219
  3. package/package.json +5 -5
  4. package/src/browse/browse-modal.ts +9 -3
  5. package/src/browse/browse.ts +139 -82
  6. package/src/canvas/canvas-diff.ts +35 -18
  7. package/src/canvas/canvas-helpers.ts +55 -21
  8. package/src/canvas/canvas-live-render.ts +140 -89
  9. package/src/canvas/canvas-render.ts +127 -82
  10. package/src/canvas/canvas-utils.ts +68 -42
  11. package/src/canvas/nested-site-style.ts +15 -7
  12. package/src/editor/component-inline-edit.ts +90 -51
  13. package/src/editor/content-inline-edit.ts +75 -72
  14. package/src/editor/context-menu.ts +125 -71
  15. package/src/editor/convert-targets.ts +17 -13
  16. package/src/editor/convert-to-component.ts +51 -28
  17. package/src/editor/convert-to-repeater.ts +44 -19
  18. package/src/editor/inline-edit.ts +107 -52
  19. package/src/editor/inline-format.ts +128 -60
  20. package/src/editor/insertion-helper.ts +26 -14
  21. package/src/editor/shortcuts.ts +89 -43
  22. package/src/editor/slash-menu.ts +41 -26
  23. package/src/files/components.ts +9 -7
  24. package/src/files/file-ops.ts +53 -33
  25. package/src/files/files.ts +246 -143
  26. package/src/format/constraints.ts +25 -15
  27. package/src/format/format-host.ts +33 -10
  28. package/src/github/github-auth.ts +24 -14
  29. package/src/github/github-publish.ts +20 -14
  30. package/src/new-project/new-project-modal.ts +27 -18
  31. package/src/panels/activity-bar.ts +27 -26
  32. package/src/panels/ai-panel.ts +100 -44
  33. package/src/panels/block-action-bar.ts +84 -52
  34. package/src/panels/canvas-dnd.ts +47 -28
  35. package/src/panels/data-explorer.ts +24 -11
  36. package/src/panels/dnd.ts +148 -100
  37. package/src/panels/editors.ts +54 -37
  38. package/src/panels/elements-panel.ts +23 -13
  39. package/src/panels/events-panel.ts +48 -38
  40. package/src/panels/git-panel.ts +95 -60
  41. package/src/panels/head-panel.ts +138 -84
  42. package/src/panels/imports-panel.ts +66 -29
  43. package/src/panels/layers-panel.ts +80 -36
  44. package/src/panels/left-panel.ts +86 -53
  45. package/src/panels/overlays.ts +36 -19
  46. package/src/panels/panel-events.ts +48 -27
  47. package/src/panels/panel-scheduler.ts +41 -23
  48. package/src/panels/preview-render.ts +26 -26
  49. package/src/panels/properties-panel.ts +155 -118
  50. package/src/panels/pseudo-preview.ts +31 -15
  51. package/src/panels/quick-search.ts +28 -13
  52. package/src/panels/right-panel.ts +35 -24
  53. package/src/panels/shared.ts +46 -29
  54. package/src/panels/signals-panel.ts +303 -186
  55. package/src/panels/statusbar.ts +27 -12
  56. package/src/panels/style-inputs.ts +30 -24
  57. package/src/panels/style-panel.ts +130 -90
  58. package/src/panels/style-utils.ts +63 -23
  59. package/src/panels/stylebook-layers-panel.ts +27 -30
  60. package/src/panels/stylebook-panel.ts +140 -72
  61. package/src/panels/tab-strip.ts +21 -10
  62. package/src/panels/toolbar.ts +52 -40
  63. package/src/panels/welcome-screen.ts +1 -1
  64. package/src/platform.ts +2 -1
  65. package/src/platforms/devserver.ts +221 -105
  66. package/src/recent-projects.ts +15 -7
  67. package/src/resize-edges.ts +7 -5
  68. package/src/services/cem-export.ts +73 -57
  69. package/src/services/code-services.ts +52 -25
  70. package/src/services/monaco-setup.ts +5 -5
  71. package/src/settings/content-types-editor.ts +133 -62
  72. package/src/settings/css-vars-editor.ts +31 -14
  73. package/src/settings/defs-editor.ts +125 -63
  74. package/src/settings/general-settings.ts +11 -6
  75. package/src/settings/head-editor.ts +30 -15
  76. package/src/settings/schema-field-ui.ts +75 -35
  77. package/src/settings/settings-modal.ts +35 -17
  78. package/src/site-context.ts +80 -47
  79. package/src/state.ts +98 -65
  80. package/src/store.ts +42 -23
  81. package/src/studio.ts +165 -144
  82. package/src/tabs/tab.ts +51 -45
  83. package/src/tabs/transact.ts +230 -109
  84. package/src/types.ts +60 -51
  85. package/src/ui/button-group.ts +3 -2
  86. package/src/ui/color-selector.ts +33 -15
  87. package/src/ui/expression-editor.ts +139 -86
  88. package/src/ui/field-input.ts +35 -18
  89. package/src/ui/field-row.ts +1 -1
  90. package/src/ui/icons.ts +5 -6
  91. package/src/ui/layers.ts +33 -26
  92. package/src/ui/media-picker.ts +13 -7
  93. package/src/ui/panel-resize.ts +39 -20
  94. package/src/ui/spectrum.ts +4 -2
  95. package/src/ui/unit-selector.ts +20 -11
  96. package/src/ui/value-selector.ts +13 -7
  97. package/src/ui/widgets.ts +34 -18
  98. package/src/utils/canvas-media.ts +66 -33
  99. package/src/utils/edit-display.ts +43 -44
  100. package/src/utils/google-fonts.ts +10 -6
  101. package/src/utils/inherited-style.ts +22 -8
  102. package/src/utils/studio-utils.ts +88 -45
  103. package/src/view.ts +9 -5
  104. package/src/workspace/workspace.ts +49 -29
@@ -46,14 +46,22 @@ function componentSpecifier(comp: ComponentEntry) {
46
46
  * @returns {boolean}
47
47
  */
48
48
  function isComponentEnabled(comp: ComponentEntry, elements: ElementsEntry[]) {
49
- if (!elements?.length) return false;
49
+ if (!elements?.length) {
50
+ return false;
51
+ }
50
52
  const specifier = componentSpecifier(comp);
51
53
  for (const entry of elements) {
52
- if (typeof entry !== "string") continue;
54
+ if (typeof entry !== "string") {
55
+ continue;
56
+ }
53
57
  // Cherry-picked subpath match
54
- if (entry === specifier) return true;
58
+ if (entry === specifier) {
59
+ return true;
60
+ }
55
61
  // Legacy full-package match
56
- if (entry === comp.package) return true;
62
+ if (entry === comp.package) {
63
+ return true;
64
+ }
57
65
  }
58
66
  return false;
59
67
  }
@@ -64,10 +72,14 @@ function isComponentEnabled(comp: ComponentEntry, elements: ElementsEntry[]) {
64
72
  * @returns {Map<string, ComponentEntry[]>}
65
73
  */
66
74
  function groupByPackage() {
67
- const groups: Map<string, ComponentEntry[]> = new Map();
75
+ const groups = new Map<string, ComponentEntry[]>();
68
76
  for (const comp of componentRegistry) {
69
- if (comp.source !== "npm" || !comp.package || !comp.modulePath) continue;
70
- if (!groups.has(comp.package)) groups.set(comp.package, []);
77
+ if (comp.source !== "npm" || !comp.package || !comp.modulePath) {
78
+ continue;
79
+ }
80
+ if (!groups.has(comp.package)) {
81
+ groups.set(comp.package, []);
82
+ }
71
83
  groups.get(comp.package)?.push(comp);
72
84
  }
73
85
  return groups;
@@ -90,10 +102,10 @@ export function renderImportsTemplate({
90
102
  }
91
103
 
92
104
  return renderDocumentLevelImports({
93
- renderLeftPanel,
94
- documentPath,
95
- documentElements,
96
105
  applyMutation,
106
+ documentElements,
107
+ documentPath,
108
+ renderLeftPanel,
97
109
  });
98
110
  }
99
111
 
@@ -155,7 +167,9 @@ function renderSiteLevelImports(renderLeftPanel: () => void) {
155
167
  const pathField = form?.querySelector(".import-add-path") as HTMLInputElement;
156
168
  const name = nameField?.value?.trim();
157
169
  const path = pathField?.value?.trim();
158
- if (!name || !path) return;
170
+ if (!name || !path) {
171
+ return;
172
+ }
159
173
  nameField.value = "";
160
174
  pathField.value = "";
161
175
  const updated = { ...siteImports, [name]: path };
@@ -183,13 +197,15 @@ function renderSiteLevelImports(renderLeftPanel: () => void) {
183
197
  confirmLabel: "Remove",
184
198
  destructive: true,
185
199
  });
186
- if (!confirmed) return;
200
+ if (!confirmed) {
201
+ return;
202
+ }
187
203
  try {
188
204
  const platform = getPlatform();
189
205
  await platform.removePackage(pkg);
190
206
  // Also remove all cherry-picked elements for this package
191
207
  const updatedElements = siteElements.filter(
192
- (e: ElementsEntry) => typeof e !== "string" || !e.startsWith(pkg + "/"),
208
+ (e: ElementsEntry) => typeof e !== "string" || !e.startsWith(`${pkg}/`),
193
209
  );
194
210
  const { loadComponentRegistry } = await import("../files/components.js");
195
211
  await loadComponentRegistry();
@@ -197,8 +213,8 @@ function renderSiteLevelImports(renderLeftPanel: () => void) {
197
213
  $elements: updatedElements as (string | JxElement)[],
198
214
  });
199
215
  renderLeftPanel();
200
- } catch (e) {
201
- console.error("Failed to remove package:", e);
216
+ } catch (error) {
217
+ console.error("Failed to remove package:", error);
202
218
  }
203
219
  }}
204
220
  >
@@ -219,7 +235,9 @@ function renderSiteLevelImports(renderLeftPanel: () => void) {
219
235
  // Remove legacy full-package import if present
220
236
  updated = updated.filter((el: ElementsEntry) => el !== pkg);
221
237
  if ((e.target as HTMLInputElement).checked) {
222
- if (!updated.includes(specifier)) updated.push(specifier);
238
+ if (!updated.includes(specifier)) {
239
+ updated.push(specifier);
240
+ }
223
241
  } else {
224
242
  updated = updated.filter((el: ElementsEntry) => el !== specifier);
225
243
  }
@@ -250,9 +268,13 @@ function renderSiteLevelImports(renderLeftPanel: () => void) {
250
268
  size="s"
251
269
  style="flex:1"
252
270
  @keydown=${async (e: KeyboardEvent) => {
253
- if (e.key !== "Enter") return;
271
+ if (e.key !== "Enter") {
272
+ return;
273
+ }
254
274
  const name = (e.target as HTMLInputElement).value?.trim();
255
- if (!name) return;
275
+ if (!name) {
276
+ return;
277
+ }
256
278
  (e.target as HTMLInputElement).value = "";
257
279
  try {
258
280
  const platform = getPlatform();
@@ -260,8 +282,8 @@ function renderSiteLevelImports(renderLeftPanel: () => void) {
260
282
  const { loadComponentRegistry } = await import("../files/components.js");
261
283
  await loadComponentRegistry();
262
284
  renderLeftPanel();
263
- } catch (err) {
264
- console.error("Failed to add package:", err);
285
+ } catch (error) {
286
+ console.error("Failed to add package:", error);
265
287
  }
266
288
  }}
267
289
  ></sp-textfield>
@@ -274,7 +296,9 @@ function renderSiteLevelImports(renderLeftPanel: () => void) {
274
296
  .closest(".import-add-form")
275
297
  ?.querySelector("sp-textfield");
276
298
  const name = (input as HTMLInputElement | null)?.value?.trim();
277
- if (!name) return;
299
+ if (!name) {
300
+ return;
301
+ }
278
302
  (input as HTMLInputElement).value = "";
279
303
  try {
280
304
  const platform = getPlatform();
@@ -282,8 +306,8 @@ function renderSiteLevelImports(renderLeftPanel: () => void) {
282
306
  const { loadComponentRegistry } = await import("../files/components.js");
283
307
  await loadComponentRegistry();
284
308
  renderLeftPanel();
285
- } catch (err) {
286
- console.error("Failed to add package:", err);
309
+ } catch (error) {
310
+ console.error("Failed to add package:", error);
287
311
  }
288
312
  }}
289
313
  >
@@ -313,7 +337,10 @@ function renderDocumentLevelImports({
313
337
  const importedRefs = new Set(refEntries.map((e: ElementsEntry) => (e as { $ref: string }).$ref));
314
338
  const availableComponents = componentRegistry.filter(
315
339
  (c: ComponentEntry) =>
316
- c.source !== "npm" && !importedRefs.has(`./${c.path}`) && !importedRefs.has(c.path),
340
+ c.source !== "npm" &&
341
+ c.path != null &&
342
+ !importedRefs.has(`./${c.path}`) &&
343
+ !importedRefs.has(c.path),
317
344
  );
318
345
 
319
346
  const packageGroups = groupByPackage();
@@ -368,13 +395,19 @@ function renderDocumentLevelImports({
368
395
  class="import-picker"
369
396
  @change=${(e: Event) => {
370
397
  const tag = (e.target as HTMLInputElement).value;
371
- if (!tag) return;
398
+ if (!tag) {
399
+ return;
400
+ }
372
401
  (e.target as HTMLInputElement).value = "";
373
402
  const comp = componentRegistry.find((c: ComponentEntry) => c.tagName === tag);
374
- if (!comp) return;
403
+ if (!comp?.path) {
404
+ return;
405
+ }
375
406
  const relPath = computeRelativePath(documentPath, comp.path);
376
407
  applyMutation((doc: JxMutableNode) => {
377
- if (!doc.$elements) doc.$elements = [];
408
+ if (!doc.$elements) {
409
+ doc.$elements = [];
410
+ }
378
411
  doc.$elements.push({ $ref: relPath });
379
412
  });
380
413
  renderLeftPanel();
@@ -408,11 +441,15 @@ function renderDocumentLevelImports({
408
441
  .checked=${enabled}
409
442
  @change=${(e: Event) => {
410
443
  applyMutation((doc: JxMutableNode) => {
411
- if (!doc.$elements) doc.$elements = [];
444
+ if (!doc.$elements) {
445
+ doc.$elements = [];
446
+ }
412
447
  // Remove legacy full-package import if present
413
448
  doc.$elements = doc.$elements.filter((el: ElementsEntry) => el !== pkg);
414
449
  if ((e.target as HTMLInputElement).checked) {
415
- if (!doc.$elements.includes(specifier)) doc.$elements.push(specifier);
450
+ if (!doc.$elements.includes(specifier)) {
451
+ doc.$elements.push(specifier);
452
+ }
416
453
  } else {
417
454
  doc.$elements = doc.$elements.filter(
418
455
  (el: ElementsEntry) => el !== specifier,
@@ -8,28 +8,30 @@ import { html, nothing } from "lit-html";
8
8
  import { classMap } from "lit-html/directives/class-map.js";
9
9
  import { ifDefined } from "lit-html/directives/if-defined.js";
10
10
  import {
11
+ VOID_ELEMENTS,
12
+ childIndex,
13
+ childList,
11
14
  flattenTree,
12
15
  getNodeAtPath,
16
+ nodeLabel,
17
+ parentElementPath,
13
18
  pathKey,
14
19
  pathsEqual,
15
- parentElementPath,
16
- childIndex,
17
- nodeLabel,
18
- VOID_ELEMENTS,
19
20
  } from "../store";
20
21
  import { activeTab } from "../workspace/workspace";
21
22
  import type { JxPath } from "../state";
22
23
  import type { JxMutableNode } from "@jxsuite/schema/types";
23
24
  import {
24
- transactDoc,
25
25
  mutateMoveNode,
26
26
  mutateRemoveNode,
27
27
  mutateUpdateProperty,
28
+ transactDoc,
28
29
  } from "../tabs/transact";
29
30
  import { view } from "../view";
30
31
  import { isInlineElement } from "../editor/inline-edit";
31
32
  import { showContextMenu } from "../editor/context-menu";
32
33
  import { panToElement } from "../canvas/canvas-utils";
34
+ import type { TemplateResult } from "lit-html";
33
35
 
34
36
  /**
35
37
  * Start inline title editing on a layer row.
@@ -40,14 +42,22 @@ import { panToElement } from "../canvas/canvas-utils";
40
42
  export function startLayerTitleEdit(path: JxPath, rerender: () => void) {
41
43
  const key = pathKey(path);
42
44
  const row = document.querySelector(`.layer-row[data-path="${key}"]`);
43
- if (!row) return;
45
+ if (!row) {
46
+ return;
47
+ }
44
48
  const label = row.querySelector(".layer-label") as HTMLElement | null;
45
- if (!label) return;
49
+ if (!label) {
50
+ return;
51
+ }
46
52
 
47
53
  const tab = activeTab.value;
48
- if (!tab) return;
54
+ if (!tab) {
55
+ return;
56
+ }
49
57
  const node = getNodeAtPath(tab.doc.document, path);
50
- if (!node) return;
58
+ if (!node) {
59
+ return;
60
+ }
51
61
 
52
62
  label.style.display = "none";
53
63
  const input = document.createElement("input");
@@ -65,7 +75,9 @@ export function startLayerTitleEdit(path: JxPath, rerender: () => void) {
65
75
  label.style.display = "";
66
76
  };
67
77
  const commit = () => {
68
- if (committed) return;
78
+ if (committed) {
79
+ return;
80
+ }
69
81
  committed = true;
70
82
  cleanup();
71
83
  const val = input.value.trim();
@@ -73,7 +85,9 @@ export function startLayerTitleEdit(path: JxPath, rerender: () => void) {
73
85
  rerender();
74
86
  };
75
87
  const cancel = () => {
76
- if (committed) return;
88
+ if (committed) {
89
+ return;
90
+ }
77
91
  committed = true;
78
92
  cleanup();
79
93
  rerender();
@@ -101,13 +115,15 @@ export function renderLayersTemplate(ctx: {
101
115
  }) {
102
116
  const tab = activeTab.value;
103
117
 
104
- for (const fn of view.dndCleanups) fn();
118
+ for (const fn of view.dndCleanups) {
119
+ fn();
120
+ }
105
121
  view.dndCleanups = [];
106
122
 
107
123
  const rows = flattenTree(tab!.doc.document);
108
- const collapsed = view._layersCollapsed || (view._layersCollapsed = new Set());
124
+ const collapsed = (view._layersCollapsed ||= new Set());
109
125
 
110
- const layerRows: import("lit-html").TemplateResult[] = [];
126
+ const layerRows: TemplateResult[] = [];
111
127
  for (const { node, path, depth, nodeType } of rows) {
112
128
  let hidden = false;
113
129
  for (let d = 1; d <= path.length; d++) {
@@ -117,12 +133,16 @@ export function renderLayersTemplate(ctx: {
117
133
  break;
118
134
  }
119
135
  }
120
- if (hidden) continue;
136
+ if (hidden) {
137
+ continue;
138
+ }
121
139
 
122
- if (tab?.doc.mode === "content" && path.length === 0) continue;
140
+ if (tab?.doc.mode === "content" && path.length === 0) {
141
+ continue;
142
+ }
123
143
 
124
144
  if (nodeType === "text") {
125
- const textPreview = String(node).length > 40 ? String(node).slice(0, 40) + "…" : String(node);
145
+ const textPreview = String(node).length > 40 ? `${String(node).slice(0, 40)}…` : String(node);
126
146
  layerRows.push(html`
127
147
  <div
128
148
  class="layer-row"
@@ -136,13 +156,17 @@ export function renderLayersTemplate(ctx: {
136
156
  }
137
157
 
138
158
  // After text-node skip, node is guaranteed to be JxMutableNode (not a primitive)
139
- if (typeof node !== "object" || node === null) continue;
159
+ if (typeof node !== "object" || node === null) {
160
+ continue;
161
+ }
140
162
  const jxNode: JxMutableNode = node as JxMutableNode;
141
163
 
142
164
  if (path.length >= 2 && nodeType === "element") {
143
165
  const pPath = parentElementPath(path);
144
166
  const parentNode = pPath ? getNodeAtPath(tab!.doc.document, pPath) : null;
145
- if (parentNode && isInlineElement(jxNode, parentNode)) continue;
167
+ if (parentNode && isInlineElement(jxNode, parentNode)) {
168
+ continue;
169
+ }
146
170
  }
147
171
 
148
172
  const key = pathKey(path);
@@ -173,8 +197,8 @@ export function renderLayersTemplate(ctx: {
173
197
  badgeTitle = "Repeater (mapped array)";
174
198
  } else if (nodeType === "case" || nodeType === "case-ref") {
175
199
  badgeClass = "layer-tag case-tag";
176
- badgeText = path[path.length - 1];
177
- badgeTitle = `$switch case: ${path[path.length - 1]}`;
200
+ badgeText = path.at(-1);
201
+ badgeTitle = `$switch case: ${path.at(-1)}`;
178
202
  } else if (jxNode.$switch) {
179
203
  badgeClass = "layer-tag switch-tag";
180
204
  badgeText = "⇄";
@@ -202,22 +226,33 @@ export function renderLayersTemplate(ctx: {
202
226
  const idx = isElement ? (childIndex(path) as number) : 0;
203
227
  const parentPath = isElement && !isRoot ? (parentElementPath(path) as JxPath) : null;
204
228
  const parentNode = parentPath ? getNodeAtPath(tab!.doc.document, parentPath) : null;
205
- const siblingCount = parentNode?.children?.length || 0;
229
+ const siblingCount = childList(parentNode).length;
206
230
  const canMoveUp = isElement && !isRoot && idx > 0;
207
231
  const canMoveDown = isElement && !isRoot && idx < siblingCount - 1;
208
- const prevSibling = canMoveUp && parentNode ? parentNode.children?.[idx - 1] : null;
232
+ const prevSibling = canMoveUp && parentNode ? childList(parentNode)[idx - 1] : null;
209
233
  const prevIsContainer = (() => {
210
- if (!prevSibling || typeof prevSibling !== "object") return false;
211
- if (VOID_ELEMENTS.has((prevSibling.tagName || "div").toLowerCase())) return false;
234
+ if (!prevSibling || typeof prevSibling !== "object") {
235
+ return false;
236
+ }
237
+ if (VOID_ELEMENTS.has((prevSibling.tagName || "div").toLowerCase())) {
238
+ return false;
239
+ }
212
240
  const ch = prevSibling.children;
213
- if (!ch) return false;
241
+ if (!ch) {
242
+ return false;
243
+ }
214
244
  if (
215
245
  typeof ch === "object" &&
216
246
  (ch as unknown as Record<string, unknown>).$prototype === "Array"
217
- )
247
+ ) {
218
248
  return true;
219
- if (!Array.isArray(ch)) return false;
220
- if (ch.length === 0) return true;
249
+ }
250
+ if (!Array.isArray(ch)) {
251
+ return false;
252
+ }
253
+ if (ch.length === 0) {
254
+ return true;
255
+ }
221
256
  return ch.some(
222
257
  (c) => typeof c === "object" && c !== null && !isInlineElement(c, prevSibling),
223
258
  );
@@ -227,7 +262,7 @@ export function renderLayersTemplate(ctx: {
227
262
  isElement && parentPath && parentPath.length >= 2
228
263
  ? (parentElementPath(parentPath) as JxPath)
229
264
  : null;
230
- const canMoveOut = isElement && !isRoot && !!grandparentPath;
265
+ const canMoveOut = isElement && !isRoot && Boolean(grandparentPath);
231
266
 
232
267
  layerRows.push(html`
233
268
  <div
@@ -314,7 +349,7 @@ export function renderLayersTemplate(ctx: {
314
349
  const pp = parentPath as JxPath;
315
350
  const prevPath = [...pp, idx - 1];
316
351
  const prev = getNodeAtPath(activeTab.value!.doc.document, prevPath);
317
- const len = prev?.children?.length || 0;
352
+ const len = childList(prev).length;
318
353
  transactDoc(activeTab.value!, (t) =>
319
354
  mutateMoveNode(t, path, prevPath, len),
320
355
  );
@@ -366,14 +401,23 @@ export function renderLayersTemplate(ctx: {
366
401
  class="layers-tree"
367
402
  @click=${(e: MouseEvent) => {
368
403
  const toggle = (e.target as HTMLElement).closest(".layer-toggle");
369
- if (!toggle) return;
404
+ if (!toggle) {
405
+ return;
406
+ }
370
407
  e.stopPropagation();
371
408
  const row = toggle.closest(".layer-row");
372
- if (!row) return;
409
+ if (!row) {
410
+ return;
411
+ }
373
412
  const key = (row as HTMLElement).dataset.path;
374
- if (!key) return;
375
- if (collapsed.has(key)) collapsed.delete(key);
376
- else collapsed.add(key);
413
+ if (!key) {
414
+ return;
415
+ }
416
+ if (collapsed.has(key)) {
417
+ collapsed.delete(key);
418
+ } else {
419
+ collapsed.add(key);
420
+ }
377
421
  ctx.rerender();
378
422
  }}
379
423
  >