@jxsuite/studio 0.19.0 → 0.21.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 (99) hide show
  1. package/dist/studio.css +98 -98
  2. package/dist/studio.js +13476 -12330
  3. package/dist/studio.js.map +152 -148
  4. package/dist/workers/editor.worker.js +79 -79
  5. package/dist/workers/json.worker.js +109 -109
  6. package/dist/workers/ts.worker.js +82 -82
  7. package/package.json +19 -7
  8. package/src/browse/browse-modal.js +16 -13
  9. package/src/browse/browse.js +44 -35
  10. package/src/canvas/canvas-diff.js +24 -16
  11. package/src/canvas/canvas-helpers.js +24 -19
  12. package/src/canvas/canvas-live-render.js +113 -39
  13. package/src/canvas/canvas-render.js +119 -73
  14. package/src/canvas/canvas-utils.js +51 -21
  15. package/src/canvas/nested-site-style.js +50 -0
  16. package/src/editor/component-inline-edit.js +14 -13
  17. package/src/editor/content-inline-edit.js +22 -10
  18. package/src/editor/context-menu.js +28 -14
  19. package/src/editor/convert-targets.js +60 -0
  20. package/src/editor/convert-to-component.js +20 -19
  21. package/src/editor/convert-to-repeater.js +226 -0
  22. package/src/editor/inline-edit.js +69 -28
  23. package/src/editor/insertion-helper.js +16 -12
  24. package/src/editor/shortcuts.js +14 -4
  25. package/src/editor/slash-menu.js +40 -19
  26. package/src/files/components.js +1 -1
  27. package/src/files/file-ops.js +37 -16
  28. package/src/files/files.js +322 -68
  29. package/src/github/github-auth.js +122 -0
  30. package/src/github/github-publish.js +126 -0
  31. package/src/markdown/md-convert.js +172 -107
  32. package/src/new-project/new-project-modal.js +204 -0
  33. package/src/panels/activity-bar.js +27 -25
  34. package/src/panels/ai-panel.js +406 -0
  35. package/src/panels/block-action-bar.js +77 -28
  36. package/src/panels/canvas-dnd.js +8 -2
  37. package/src/panels/data-explorer.js +19 -13
  38. package/src/panels/dnd.js +144 -92
  39. package/src/panels/editors.js +55 -35
  40. package/src/panels/elements-panel.js +33 -19
  41. package/src/panels/events-panel.js +15 -10
  42. package/src/panels/git-panel.js +475 -99
  43. package/src/panels/head-panel.js +121 -81
  44. package/src/panels/imports-panel.js +94 -65
  45. package/src/panels/layers-panel.js +64 -43
  46. package/src/panels/left-panel.js +105 -48
  47. package/src/panels/overlays.js +1 -0
  48. package/src/panels/panel-events.js +45 -30
  49. package/src/panels/preview-render.js +23 -18
  50. package/src/panels/properties-panel.js +378 -257
  51. package/src/panels/pseudo-preview.js +2 -2
  52. package/src/panels/quick-search.js +11 -9
  53. package/src/panels/right-panel.js +42 -16
  54. package/src/panels/shared.js +4 -4
  55. package/src/panels/signals-panel.js +415 -166
  56. package/src/panels/statusbar.js +10 -6
  57. package/src/panels/style-inputs.js +67 -35
  58. package/src/panels/style-panel.js +239 -134
  59. package/src/panels/style-utils.js +34 -16
  60. package/src/panels/stylebook-layers-panel.js +12 -10
  61. package/src/panels/stylebook-panel.js +134 -66
  62. package/src/panels/tab-strip.js +5 -2
  63. package/src/panels/toolbar.js +333 -109
  64. package/src/panels/welcome-screen.js +121 -0
  65. package/src/platform.js +2 -4
  66. package/src/platforms/devserver.js +113 -7
  67. package/src/resize-edges.js +98 -0
  68. package/src/services/cem-export.js +12 -7
  69. package/src/services/code-services.js +30 -12
  70. package/src/settings/content-types-editor.js +8 -8
  71. package/src/settings/css-vars-editor.js +30 -24
  72. package/src/settings/defs-editor.js +12 -7
  73. package/src/settings/general-settings.js +85 -3
  74. package/src/settings/head-editor.js +30 -24
  75. package/src/settings/schema-field-ui.js +58 -39
  76. package/src/settings/settings-modal.js +12 -9
  77. package/src/site-context.js +41 -31
  78. package/src/state.js +70 -29
  79. package/src/store.js +36 -35
  80. package/src/studio.js +120 -90
  81. package/src/tabs/tab.js +67 -43
  82. package/src/tabs/transact.js +23 -13
  83. package/src/ui/button-group.js +24 -24
  84. package/src/ui/color-selector.js +28 -23
  85. package/src/ui/field-row.js +3 -3
  86. package/src/ui/icons.js +2 -2
  87. package/src/ui/layers.js +31 -1
  88. package/src/ui/media-picker.js +7 -7
  89. package/src/ui/spectrum.js +5 -1
  90. package/src/ui/unit-selector.js +18 -16
  91. package/src/ui/value-selector.js +21 -15
  92. package/src/ui/widgets.js +22 -19
  93. package/src/utils/canvas-media.js +5 -4
  94. package/src/utils/edit-display.js +32 -26
  95. package/src/utils/google-fonts.js +11 -11
  96. package/src/utils/inherited-style.js +9 -8
  97. package/src/utils/studio-utils.js +18 -18
  98. package/src/view.js +58 -1
  99. package/src/workspace/workspace.js +29 -7
@@ -12,11 +12,25 @@ import { componentRegistry, computeRelativePath } from "../files/components.js";
12
12
  import { projectState } from "../store.js";
13
13
  import { updateSiteConfig } from "../site-context.js";
14
14
  import { getPlatform } from "../platform.js";
15
+ import { showConfirmDialog } from "../ui/layers.js";
16
+
17
+ /** @typedef {import("../files/components.js").ComponentEntry} ComponentEntry */
18
+
19
+ /** @typedef {string | JxMutableNode | { $ref: string }} ElementsEntry */
20
+
21
+ /**
22
+ * @typedef {{
23
+ * renderLeftPanel: () => void;
24
+ * documentPath: string | null;
25
+ * documentElements: ElementsEntry[];
26
+ * applyMutation: (fn: (doc: JxMutableNode) => void) => void;
27
+ * }} ImportsContext
28
+ */
15
29
 
16
30
  /**
17
31
  * Build the subpath specifier for a component: `<package>/<modulePath>`
18
32
  *
19
- * @param {any} comp
33
+ * @param {ComponentEntry} comp
20
34
  * @returns {string}
21
35
  */
22
36
  function componentSpecifier(comp) {
@@ -27,8 +41,8 @@ function componentSpecifier(comp) {
27
41
  * Check if a component is enabled (present in $elements array). Supports both cherry-picked subpath
28
42
  * specifiers and legacy full-package imports.
29
43
  *
30
- * @param {any} comp
31
- * @param {any[]} elements
44
+ * @param {ComponentEntry} comp
45
+ * @param {ElementsEntry[]} elements
32
46
  * @returns {boolean}
33
47
  */
34
48
  function isComponentEnabled(comp, elements) {
@@ -47,10 +61,10 @@ function isComponentEnabled(comp, elements) {
47
61
  /**
48
62
  * Group npm components by package name.
49
63
  *
50
- * @returns {Map<string, any[]>}
64
+ * @returns {Map<string, ComponentEntry[]>}
51
65
  */
52
66
  function groupByPackage() {
53
- /** @type {Map<string, any[]>} */
67
+ /** @type {Map<string, ComponentEntry[]>} */
54
68
  const groups = new Map();
55
69
  for (const comp of componentRegistry) {
56
70
  if (comp.source !== "npm" || !comp.package || !comp.modulePath) continue;
@@ -61,13 +75,8 @@ function groupByPackage() {
61
75
  }
62
76
 
63
77
  /**
64
- * @param {{
65
- * renderLeftPanel: () => void;
66
- * documentPath: string | null;
67
- * documentElements: any[];
68
- * applyMutation: (fn: (doc: any) => void) => void;
69
- * }} ctx
70
- * @returns {any}
78
+ * @param {ImportsContext} ctx
79
+ * @returns {import("lit-html").TemplateResult}
71
80
  */
72
81
  export function renderImportsTemplate({
73
82
  renderLeftPanel,
@@ -95,7 +104,9 @@ export function renderImportsTemplate({
95
104
  function renderSiteLevelImports(renderLeftPanel) {
96
105
  const siteImports = projectState?.projectConfig?.imports || {};
97
106
  const entries = Object.entries(siteImports);
98
- const siteElements = projectState?.projectConfig?.$elements || [];
107
+ const siteElements = /** @type {ElementsEntry[]} */ (
108
+ projectState?.projectConfig?.$elements || []
109
+ );
99
110
 
100
111
  const packageGroups = groupByPackage();
101
112
 
@@ -141,10 +152,14 @@ function renderSiteLevelImports(renderLeftPanel) {
141
152
  quiet
142
153
  size="xs"
143
154
  title="Add import"
144
- @click=${async (/** @type {any} */ e) => {
145
- const form = e.target.closest(".import-add-form");
146
- const nameField = form?.querySelector(".import-add-name");
147
- const pathField = form?.querySelector(".import-add-path");
155
+ @click=${async (/** @type {Event} */ e) => {
156
+ const form = /** @type {HTMLElement} */ (e.target).closest(".import-add-form");
157
+ const nameField = /** @type {HTMLInputElement} */ (
158
+ form?.querySelector(".import-add-name")
159
+ );
160
+ const pathField = /** @type {HTMLInputElement} */ (
161
+ form?.querySelector(".import-add-path")
162
+ );
148
163
  const name = nameField?.value?.trim();
149
164
  const path = pathField?.value?.trim();
150
165
  if (!name || !path) return;
@@ -171,19 +186,26 @@ function renderSiteLevelImports(renderLeftPanel) {
171
186
  size="xs"
172
187
  title="Remove package"
173
188
  @click=${async () => {
174
- if (!confirm("Remove " + pkg + "?")) return;
189
+ const confirmed = await showConfirmDialog("Remove Package", `Remove ${pkg}?`, {
190
+ confirmLabel: "Remove",
191
+ destructive: true,
192
+ });
193
+ if (!confirmed) return;
175
194
  try {
176
195
  const platform = getPlatform();
177
196
  await platform.removePackage(pkg);
178
197
  // Also remove all cherry-picked elements for this package
179
198
  const updatedElements = siteElements.filter(
180
- (/** @type {any} */ e) => typeof e !== "string" || !e.startsWith(pkg + "/"),
199
+ (/** @type {ElementsEntry} */ e) =>
200
+ typeof e !== "string" || !e.startsWith(pkg + "/"),
181
201
  );
182
202
  const { loadComponentRegistry } = await import("../files/components.js");
183
203
  await loadComponentRegistry();
184
- await updateSiteConfig({ $elements: updatedElements });
204
+ await updateSiteConfig({
205
+ $elements: /** @type {(string | JxElement)[]} */ (updatedElements),
206
+ });
185
207
  renderLeftPanel();
186
- } catch (/** @type {any} */ e) {
208
+ } catch (/** @type {unknown} */ e) {
187
209
  console.error("Failed to remove package:", e);
188
210
  }
189
211
  }}
@@ -192,7 +214,7 @@ function renderSiteLevelImports(renderLeftPanel) {
192
214
  </sp-action-button>
193
215
  </div>
194
216
  <div class="imports-list imports-component-list">
195
- ${comps.map((/** @type {any} */ comp) => {
217
+ ${comps.map((/** @type {ComponentEntry} */ comp) => {
196
218
  const enabled = isComponentEnabled(comp, siteElements);
197
219
  const specifier = componentSpecifier(comp);
198
220
  return html`
@@ -200,16 +222,20 @@ function renderSiteLevelImports(renderLeftPanel) {
200
222
  <sp-checkbox
201
223
  size="s"
202
224
  .checked=${enabled}
203
- @change=${async (/** @type {any} */ e) => {
225
+ @change=${async (/** @type {Event} */ e) => {
204
226
  let updated = [...siteElements];
205
227
  // Remove legacy full-package import if present
206
- updated = updated.filter((/** @type {any} */ el) => el !== pkg);
207
- if (e.target.checked) {
228
+ updated = updated.filter((/** @type {ElementsEntry} */ el) => el !== pkg);
229
+ if (/** @type {HTMLInputElement} */ (e.target).checked) {
208
230
  if (!updated.includes(specifier)) updated.push(specifier);
209
231
  } else {
210
- updated = updated.filter((/** @type {any} */ el) => el !== specifier);
232
+ updated = updated.filter(
233
+ (/** @type {ElementsEntry} */ el) => el !== specifier,
234
+ );
211
235
  }
212
- await updateSiteConfig({ $elements: updated });
236
+ await updateSiteConfig({
237
+ $elements: /** @type {(string | JxElement)[]} */ (updated),
238
+ });
213
239
  renderLeftPanel();
214
240
  }}
215
241
  >
@@ -233,18 +259,18 @@ function renderSiteLevelImports(renderLeftPanel) {
233
259
  placeholder="Package name…"
234
260
  size="s"
235
261
  style="flex:1"
236
- @keydown=${async (/** @type {any} */ e) => {
262
+ @keydown=${async (/** @type {KeyboardEvent} */ e) => {
237
263
  if (e.key !== "Enter") return;
238
- const name = e.target.value?.trim();
264
+ const name = /** @type {HTMLInputElement} */ (e.target).value?.trim();
239
265
  if (!name) return;
240
- e.target.value = "";
266
+ /** @type {HTMLInputElement} */ (e.target).value = "";
241
267
  try {
242
268
  const platform = getPlatform();
243
269
  await platform.addPackage(name);
244
270
  const { loadComponentRegistry } = await import("../files/components.js");
245
271
  await loadComponentRegistry();
246
272
  renderLeftPanel();
247
- } catch (/** @type {any} */ err) {
273
+ } catch (/** @type {unknown} */ err) {
248
274
  console.error("Failed to add package:", err);
249
275
  }
250
276
  }}
@@ -253,18 +279,20 @@ function renderSiteLevelImports(renderLeftPanel) {
253
279
  quiet
254
280
  size="xs"
255
281
  title="Add package"
256
- @click=${async (/** @type {any} */ e) => {
257
- const input = e.target.closest(".import-add-form")?.querySelector("sp-textfield");
258
- const name = input?.value?.trim();
282
+ @click=${async (/** @type {Event} */ e) => {
283
+ const input = /** @type {HTMLElement} */ (e.target)
284
+ .closest(".import-add-form")
285
+ ?.querySelector("sp-textfield");
286
+ const name = /** @type {HTMLInputElement | null} */ (input)?.value?.trim();
259
287
  if (!name) return;
260
- input.value = "";
288
+ /** @type {HTMLInputElement} */ (input).value = "";
261
289
  try {
262
290
  const platform = getPlatform();
263
291
  await platform.addPackage(name);
264
292
  const { loadComponentRegistry } = await import("../files/components.js");
265
293
  await loadComponentRegistry();
266
294
  renderLeftPanel();
267
- } catch (/** @type {any} */ err) {
295
+ } catch (/** @type {unknown} */ err) {
268
296
  console.error("Failed to add package:", err);
269
297
  }
270
298
  }}
@@ -279,14 +307,7 @@ function renderSiteLevelImports(renderLeftPanel) {
279
307
 
280
308
  // ─── Document-level: Component Imports + npm Component Cherry-pick ───────────
281
309
 
282
- /**
283
- * @param {{
284
- * renderLeftPanel: () => void;
285
- * documentPath: string | null;
286
- * documentElements: any[];
287
- * applyMutation: (fn: (doc: any) => void) => void;
288
- * }} ctx
289
- */
310
+ /** @param {ImportsContext} ctx */
290
311
  function renderDocumentLevelImports({
291
312
  renderLeftPanel,
292
313
  documentPath,
@@ -294,14 +315,18 @@ function renderDocumentLevelImports({
294
315
  applyMutation,
295
316
  }) {
296
317
  const refEntries = documentElements.filter(
297
- (/** @type {any} */ e) => e && typeof e === "object" && e.$ref,
318
+ (/** @type {ElementsEntry} */ e) => e && typeof e === "object" && e.$ref,
319
+ );
320
+ const npmEntries = documentElements.filter(
321
+ (/** @type {ElementsEntry} */ e) => typeof e === "string",
298
322
  );
299
- const npmEntries = documentElements.filter((/** @type {any} */ e) => typeof e === "string");
300
323
 
301
324
  // Available JX components not yet imported
302
- const importedRefs = new Set(refEntries.map((/** @type {any} */ e) => e.$ref));
325
+ const importedRefs = new Set(
326
+ refEntries.map((/** @type {ElementsEntry} */ e) => /** @type {{ $ref: string }} */ (e).$ref),
327
+ );
303
328
  const availableComponents = componentRegistry.filter(
304
- (/** @type {any} */ c) =>
329
+ (/** @type {ComponentEntry} */ c) =>
305
330
  c.source !== "npm" && !importedRefs.has(`./${c.path}`) && !importedRefs.has(c.path),
306
331
  );
307
332
 
@@ -309,9 +334,9 @@ function renderDocumentLevelImports({
309
334
 
310
335
  /** @param {string} ref */
311
336
  const removeRef = (ref) => {
312
- applyMutation((/** @type {any} */ doc) => {
337
+ applyMutation((/** @type {JxMutableNode} */ doc) => {
313
338
  doc.$elements = (doc.$elements || []).filter(
314
- (/** @type {any} */ e) => !(e && typeof e === "object" && e.$ref === ref),
339
+ (/** @type {ElementsEntry} */ e) => !(e && typeof e === "object" && e.$ref === ref),
315
340
  );
316
341
  });
317
342
  renderLeftPanel();
@@ -329,14 +354,18 @@ function renderDocumentLevelImports({
329
354
  ? html`
330
355
  <div class="imports-list">
331
356
  ${refEntries.map(
332
- (/** @type {any} */ entry) => html`
357
+ (/** @type {ElementsEntry} */ entry) => html`
333
358
  <div class="import-row">
334
- <span class="import-path" title=${entry.$ref}>${entry.$ref}</span>
359
+ <span
360
+ class="import-path"
361
+ title=${/** @type {{ $ref: string }} */ (entry).$ref}
362
+ >${/** @type {{ $ref: string }} */ (entry).$ref}</span
363
+ >
335
364
  <sp-action-button
336
365
  quiet
337
366
  size="xs"
338
367
  title="Remove"
339
- @click=${() => removeRef(entry.$ref)}
368
+ @click=${() => removeRef(/** @type {{ $ref: string }} */ (entry).$ref)}
340
369
  >
341
370
  <sp-icon-close slot="icon" size="xs"></sp-icon-close>
342
371
  </sp-action-button>
@@ -353,16 +382,16 @@ function renderDocumentLevelImports({
353
382
  size="s"
354
383
  label="Add component…"
355
384
  class="import-picker"
356
- @change=${(/** @type {any} */ e) => {
357
- const tag = e.target.value;
385
+ @change=${(/** @type {Event} */ e) => {
386
+ const tag = /** @type {HTMLInputElement} */ (e.target).value;
358
387
  if (!tag) return;
359
- e.target.value = "";
388
+ /** @type {HTMLInputElement} */ (e.target).value = "";
360
389
  const comp = componentRegistry.find(
361
- (/** @type {any} */ c) => c.tagName === tag,
390
+ (/** @type {ComponentEntry} */ c) => c.tagName === tag,
362
391
  );
363
392
  if (!comp) return;
364
393
  const relPath = computeRelativePath(documentPath, comp.path);
365
- applyMutation((/** @type {any} */ doc) => {
394
+ applyMutation((/** @type {JxMutableNode} */ doc) => {
366
395
  if (!doc.$elements) doc.$elements = [];
367
396
  doc.$elements.push({ $ref: relPath });
368
397
  });
@@ -370,7 +399,7 @@ function renderDocumentLevelImports({
370
399
  }}
371
400
  >
372
401
  ${availableComponents.map(
373
- (/** @type {any} */ c) =>
402
+ (/** @type {ComponentEntry} */ c) =>
374
403
  html`<sp-menu-item value=${c.tagName}>&lt;${c.tagName}&gt;</sp-menu-item>`,
375
404
  )}
376
405
  </sp-picker>
@@ -387,7 +416,7 @@ function renderDocumentLevelImports({
387
416
  <span class="imports-section-title import-mono">${pkg}</span>
388
417
  </div>
389
418
  <div class="imports-list imports-component-list">
390
- ${comps.map((/** @type {any} */ comp) => {
419
+ ${comps.map((/** @type {ComponentEntry} */ comp) => {
391
420
  const enabled = isComponentEnabled(comp, npmEntries);
392
421
  const specifier = componentSpecifier(comp);
393
422
  return html`
@@ -395,18 +424,18 @@ function renderDocumentLevelImports({
395
424
  <sp-checkbox
396
425
  size="s"
397
426
  .checked=${enabled}
398
- @change=${(/** @type {any} */ e) => {
399
- applyMutation((/** @type {any} */ doc) => {
427
+ @change=${(/** @type {Event} */ e) => {
428
+ applyMutation((/** @type {JxMutableNode} */ doc) => {
400
429
  if (!doc.$elements) doc.$elements = [];
401
430
  // Remove legacy full-package import if present
402
431
  doc.$elements = doc.$elements.filter(
403
- (/** @type {any} */ el) => el !== pkg,
432
+ (/** @type {ElementsEntry} */ el) => el !== pkg,
404
433
  );
405
- if (e.target.checked) {
434
+ if (/** @type {HTMLInputElement} */ (e.target).checked) {
406
435
  if (!doc.$elements.includes(specifier)) doc.$elements.push(specifier);
407
436
  } else {
408
437
  doc.$elements = doc.$elements.filter(
409
- (/** @type {any} */ el) => el !== specifier,
438
+ (/** @type {ElementsEntry} */ el) => el !== specifier,
410
439
  );
411
440
  }
412
441
  });
@@ -31,7 +31,7 @@ import { panToElement } from "../canvas/canvas-utils.js";
31
31
  /**
32
32
  * Start inline title editing on a layer row.
33
33
  *
34
- * @param {any[]} path
34
+ * @param {JxPath} path
35
35
  * @param {() => void} rerender
36
36
  */
37
37
  export function startLayerTitleEdit(path, rerender) {
@@ -87,7 +87,7 @@ export function startLayerTitleEdit(path, rerender) {
87
87
  }
88
88
 
89
89
  /**
90
- * @param {{ navigateToComponent: any; rerender: () => void }} ctx
90
+ * @param {{ navigateToComponent: (path: string) => void; rerender: () => void }} ctx
91
91
  * @returns {import("lit-html").TemplateResult}
92
92
  */
93
93
  export function renderLayersTemplate(ctx) {
@@ -99,7 +99,7 @@ export function renderLayersTemplate(ctx) {
99
99
  const rows = flattenTree(tab?.doc.document);
100
100
  const collapsed = view._layersCollapsed || (view._layersCollapsed = new Set());
101
101
 
102
- /** @type {any[]} */
102
+ /** @type {import("lit-html").TemplateResult[]} */
103
103
  const layerRows = [];
104
104
  for (const { node, path, depth, nodeType } of rows) {
105
105
  let hidden = false;
@@ -128,28 +128,40 @@ export function renderLayersTemplate(ctx) {
128
128
  continue;
129
129
  }
130
130
 
131
+ // After text-node skip, node is guaranteed to be JxMutableNode (not a primitive)
132
+ if (typeof node !== "object" || node === null) continue;
133
+ /** @type {JxMutableNode} */
134
+ const jxNode = /** @type {JxMutableNode} */ (node);
135
+
131
136
  if (path.length >= 2 && nodeType === "element") {
132
137
  const pPath = parentElementPath(path);
133
138
  const parentNode = pPath ? getNodeAtPath(tab?.doc.document, pPath) : null;
134
- if (parentNode && isInlineElement(node, parentNode)) continue;
139
+ if (parentNode && isInlineElement(jxNode, parentNode)) continue;
135
140
  }
136
141
 
137
142
  const key = pathKey(path);
138
143
  const isSelected = pathsEqual(path, tab?.session.selection);
139
- const hasChildren = Array.isArray(node.children) && node.children.length > 0;
144
+ const hasChildren = Array.isArray(jxNode.children) && jxNode.children.length > 0;
140
145
  const hasMapChildren =
141
- node.children && typeof node.children === "object" && node.children.$prototype === "Array";
146
+ jxNode.children &&
147
+ typeof jxNode.children === "object" &&
148
+ /** @type {{ $prototype?: string }} */ (/** @type {unknown} */ (jxNode.children))
149
+ .$prototype === "Array";
142
150
  const hasCases =
143
- node.$switch &&
144
- node.cases &&
145
- typeof node.cases === "object" &&
146
- Object.keys(node.cases).length > 0;
151
+ jxNode.$switch &&
152
+ jxNode.cases &&
153
+ typeof jxNode.cases === "object" &&
154
+ Object.keys(jxNode.cases).length > 0;
147
155
  const isExpandable =
148
- hasChildren || hasMapChildren || hasCases || (nodeType === "map" && node.map);
149
- const isVoidEl = VOID_ELEMENTS.has((node.tagName || "div").toLowerCase());
156
+ hasChildren || hasMapChildren || hasCases || (nodeType === "map" && jxNode.map);
157
+ const isVoidEl = VOID_ELEMENTS.has((jxNode.tagName || "div").toLowerCase());
150
158
 
151
- /** @type {any} */
152
- let badgeClass, badgeText, badgeTitle;
159
+ /** @type {string} */
160
+ let badgeClass;
161
+ /** @type {string | number} */
162
+ let badgeText;
163
+ /** @type {string | undefined} */
164
+ let badgeTitle;
153
165
  if (nodeType === "map") {
154
166
  badgeClass = "layer-tag map-tag";
155
167
  badgeText = "↻";
@@ -158,41 +170,48 @@ export function renderLayersTemplate(ctx) {
158
170
  badgeClass = "layer-tag case-tag";
159
171
  badgeText = path[path.length - 1];
160
172
  badgeTitle = `$switch case: ${path[path.length - 1]}`;
161
- } else if (node.$switch) {
173
+ } else if (jxNode.$switch) {
162
174
  badgeClass = "layer-tag switch-tag";
163
175
  badgeText = "⇄";
164
176
  badgeTitle = "$switch";
165
177
  } else {
166
178
  badgeClass = "layer-tag";
167
- badgeText = node.tagName || "div";
179
+ badgeText = jxNode.tagName || "div";
168
180
  badgeTitle = undefined;
169
181
  }
170
182
 
171
- /** @type {any} */
172
- let labelText, labelItalic;
183
+ /** @type {string} */
184
+ let labelText;
185
+ /** @type {boolean} */
186
+ let labelItalic;
173
187
  if (nodeType === "case-ref") {
174
- labelText = node.$ref || "external";
188
+ labelText = jxNode.$ref || "external";
175
189
  labelItalic = true;
176
190
  } else {
177
- labelText = nodeLabel(node);
191
+ labelText = nodeLabel(jxNode);
178
192
  labelItalic = false;
179
193
  }
180
194
 
181
195
  const isElement = nodeType === "element";
182
196
  const isRoot = tab?.doc.mode === "content" ? path.length === 0 : path.length < 2;
183
197
  const idx = isElement ? /** @type {number} */ (childIndex(path)) : 0;
184
- const parentPath = isElement && !isRoot ? /** @type {any} */ (parentElementPath(path)) : null;
198
+ const parentPath =
199
+ isElement && !isRoot ? /** @type {JxPath} */ (parentElementPath(path)) : null;
185
200
  const parentNode = parentPath ? getNodeAtPath(tab?.doc.document, parentPath) : null;
186
201
  const siblingCount = parentNode?.children?.length || 0;
187
202
  const canMoveUp = isElement && !isRoot && idx > 0;
188
203
  const canMoveDown = isElement && !isRoot && idx < siblingCount - 1;
189
- const prevSibling = canMoveUp && parentNode ? parentNode.children[idx - 1] : null;
204
+ const prevSibling = canMoveUp && parentNode ? parentNode.children?.[idx - 1] : null;
190
205
  const prevIsContainer = (() => {
191
206
  if (!prevSibling || typeof prevSibling !== "object") return false;
192
207
  if (VOID_ELEMENTS.has((prevSibling.tagName || "div").toLowerCase())) return false;
193
208
  const ch = prevSibling.children;
194
209
  if (!ch) return false;
195
- if (typeof ch === "object" && ch.$prototype === "Array") return true;
210
+ if (
211
+ typeof ch === "object" &&
212
+ /** @type {{ $prototype?: string }} */ (/** @type {unknown} */ (ch)).$prototype === "Array"
213
+ )
214
+ return true;
196
215
  if (!Array.isArray(ch)) return false;
197
216
  if (ch.length === 0) return true;
198
217
  return ch.some(
@@ -202,7 +221,7 @@ export function renderLayersTemplate(ctx) {
202
221
  const canMoveIn = isElement && !isRoot && prevIsContainer;
203
222
  const grandparentPath =
204
223
  isElement && parentPath && parentPath.length >= 2
205
- ? /** @type {any} */ (parentElementPath(parentPath))
224
+ ? /** @type {JxPath} */ (parentElementPath(parentPath))
206
225
  : null;
207
226
  const canMoveOut = isElement && !isRoot && !!grandparentPath;
208
227
 
@@ -219,13 +238,13 @@ export function renderLayersTemplate(ctx) {
219
238
  panToElement(path);
220
239
  }}
221
240
  @dblclick=${isElement
222
- ? (/** @type {any} */ e) => {
241
+ ? (/** @type {MouseEvent} */ e) => {
223
242
  e.stopPropagation();
224
243
  startLayerTitleEdit(path, ctx.rerender);
225
244
  }
226
245
  : nothing}
227
246
  @contextmenu=${isElement
228
- ? (/** @type {any} */ e) =>
247
+ ? (/** @type {MouseEvent} */ e) =>
229
248
  showContextMenu(e, path, {
230
249
  onEditComponent: ctx.navigateToComponent,
231
250
  rerender: ctx.rerender,
@@ -255,12 +274,11 @@ export function renderLayersTemplate(ctx) {
255
274
  quiet
256
275
  size="xs"
257
276
  title="Move up"
258
- @click=${(/** @type {any} */ e) => {
277
+ @click=${(/** @type {MouseEvent} */ e) => {
259
278
  e.stopPropagation();
260
279
  /** @type {HTMLElement} */ (e.currentTarget).blur();
261
- transactDoc(activeTab.value, (t) =>
262
- mutateMoveNode(t, path, parentPath, idx - 1),
263
- );
280
+ const pp = /** @type {JxPath} */ (parentPath);
281
+ transactDoc(activeTab.value, (t) => mutateMoveNode(t, path, pp, idx - 1));
264
282
  }}
265
283
  >
266
284
  <sp-icon-arrow-up slot="icon"></sp-icon-arrow-up>
@@ -271,12 +289,11 @@ export function renderLayersTemplate(ctx) {
271
289
  quiet
272
290
  size="xs"
273
291
  title="Move down"
274
- @click=${(/** @type {any} */ e) => {
292
+ @click=${(/** @type {MouseEvent} */ e) => {
275
293
  e.stopPropagation();
276
294
  /** @type {HTMLElement} */ (e.currentTarget).blur();
277
- transactDoc(activeTab.value, (t) =>
278
- mutateMoveNode(t, path, parentPath, idx + 2),
279
- );
295
+ const pp = /** @type {JxPath} */ (parentPath);
296
+ transactDoc(activeTab.value, (t) => mutateMoveNode(t, path, pp, idx + 2));
280
297
  }}
281
298
  >
282
299
  <sp-icon-arrow-down slot="icon"></sp-icon-arrow-down>
@@ -287,10 +304,11 @@ export function renderLayersTemplate(ctx) {
287
304
  quiet
288
305
  size="xs"
289
306
  title="Move into previous sibling"
290
- @click=${(/** @type {any} */ e) => {
307
+ @click=${(/** @type {MouseEvent} */ e) => {
291
308
  e.stopPropagation();
292
309
  /** @type {HTMLElement} */ (e.currentTarget).blur();
293
- const prevPath = [...parentPath, idx - 1];
310
+ const pp = /** @type {JxPath} */ (parentPath);
311
+ const prevPath = [...pp, idx - 1];
294
312
  const prev = getNodeAtPath(activeTab.value?.doc.document, prevPath);
295
313
  const len = prev?.children?.length || 0;
296
314
  transactDoc(activeTab.value, (t) => mutateMoveNode(t, path, prevPath, len));
@@ -304,12 +322,15 @@ export function renderLayersTemplate(ctx) {
304
322
  quiet
305
323
  size="xs"
306
324
  title="Move out of parent"
307
- @click=${(/** @type {any} */ e) => {
325
+ @click=${(/** @type {MouseEvent} */ e) => {
308
326
  e.stopPropagation();
309
327
  /** @type {HTMLElement} */ (e.currentTarget).blur();
310
- const parentIdx = /** @type {number} */ (childIndex(parentPath));
328
+ const gp = /** @type {JxPath} */ (grandparentPath);
329
+ const parentIdx = /** @type {number} */ (
330
+ childIndex(/** @type {JxPath} */ (parentPath))
331
+ );
311
332
  transactDoc(activeTab.value, (t) =>
312
- mutateMoveNode(t, path, grandparentPath, parentIdx + 1),
333
+ mutateMoveNode(t, path, gp, parentIdx + 1),
313
334
  );
314
335
  }}
315
336
  >
@@ -321,7 +342,7 @@ export function renderLayersTemplate(ctx) {
321
342
  size="xs"
322
343
  class="layer-delete"
323
344
  title="Delete"
324
- @click=${(/** @type {any} */ e) => {
345
+ @click=${(/** @type {MouseEvent} */ e) => {
325
346
  e.stopPropagation();
326
347
  transactDoc(activeTab.value, (t) => mutateRemoveNode(t, path));
327
348
  }}
@@ -339,13 +360,13 @@ export function renderLayersTemplate(ctx) {
339
360
  <div class="layers-container" style="position:relative">
340
361
  <div
341
362
  class="layers-tree"
342
- @click=${(/** @type {any} */ e) => {
343
- const toggle = e.target.closest(".layer-toggle");
363
+ @click=${(/** @type {MouseEvent} */ e) => {
364
+ const toggle = /** @type {HTMLElement} */ (e.target).closest(".layer-toggle");
344
365
  if (!toggle) return;
345
366
  e.stopPropagation();
346
367
  const row = toggle.closest(".layer-row");
347
368
  if (!row) return;
348
- const key = row.dataset.path;
369
+ const key = /** @type {HTMLElement} */ (row).dataset.path;
349
370
  if (!key) return;
350
371
  if (collapsed.has(key)) collapsed.delete(key);
351
372
  else collapsed.add(key);