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