@jxsuite/studio 0.19.0 → 0.20.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 (93) hide show
  1. package/dist/studio.css +98 -98
  2. package/dist/studio.js +12577 -12183
  3. package/dist/studio.js.map +146 -144
  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 +17 -6
  8. package/src/browse/browse.js +25 -19
  9. package/src/canvas/canvas-diff.js +24 -16
  10. package/src/canvas/canvas-helpers.js +24 -19
  11. package/src/canvas/canvas-live-render.js +94 -39
  12. package/src/canvas/canvas-render.js +119 -73
  13. package/src/canvas/canvas-utils.js +38 -15
  14. package/src/canvas/nested-site-style.js +50 -0
  15. package/src/editor/component-inline-edit.js +14 -13
  16. package/src/editor/content-inline-edit.js +22 -10
  17. package/src/editor/context-menu.js +1 -1
  18. package/src/editor/convert-to-component.js +12 -10
  19. package/src/editor/inline-edit.js +66 -28
  20. package/src/editor/insertion-helper.js +16 -12
  21. package/src/editor/shortcuts.js +4 -2
  22. package/src/editor/slash-menu.js +6 -4
  23. package/src/files/components.js +1 -1
  24. package/src/files/file-ops.js +37 -16
  25. package/src/files/files.js +78 -52
  26. package/src/github/github-auth.js +122 -0
  27. package/src/github/github-publish.js +115 -0
  28. package/src/markdown/md-convert.js +172 -107
  29. package/src/new-project/new-project-modal.js +204 -0
  30. package/src/panels/activity-bar.js +22 -20
  31. package/src/panels/ai-panel.js +399 -0
  32. package/src/panels/block-action-bar.js +5 -5
  33. package/src/panels/canvas-dnd.js +8 -2
  34. package/src/panels/data-explorer.js +19 -13
  35. package/src/panels/dnd.js +144 -92
  36. package/src/panels/editors.js +55 -35
  37. package/src/panels/elements-panel.js +33 -19
  38. package/src/panels/events-panel.js +15 -10
  39. package/src/panels/git-panel.js +469 -98
  40. package/src/panels/head-panel.js +121 -81
  41. package/src/panels/imports-panel.js +88 -64
  42. package/src/panels/layers-panel.js +64 -43
  43. package/src/panels/left-panel.js +85 -47
  44. package/src/panels/overlays.js +1 -0
  45. package/src/panels/panel-events.js +45 -30
  46. package/src/panels/preview-render.js +23 -18
  47. package/src/panels/properties-panel.js +365 -249
  48. package/src/panels/pseudo-preview.js +2 -2
  49. package/src/panels/quick-search.js +7 -5
  50. package/src/panels/right-panel.js +26 -2
  51. package/src/panels/shared.js +4 -4
  52. package/src/panels/signals-panel.js +295 -166
  53. package/src/panels/statusbar.js +4 -4
  54. package/src/panels/style-inputs.js +67 -35
  55. package/src/panels/style-panel.js +233 -132
  56. package/src/panels/style-utils.js +34 -16
  57. package/src/panels/stylebook-layers-panel.js +12 -10
  58. package/src/panels/stylebook-panel.js +134 -66
  59. package/src/panels/toolbar.js +333 -109
  60. package/src/panels/welcome-screen.js +121 -0
  61. package/src/platform.js +2 -4
  62. package/src/platforms/devserver.js +113 -7
  63. package/src/resize-edges.js +98 -0
  64. package/src/services/cem-export.js +12 -7
  65. package/src/services/code-services.js +30 -12
  66. package/src/settings/content-types-editor.js +7 -7
  67. package/src/settings/css-vars-editor.js +30 -24
  68. package/src/settings/defs-editor.js +12 -7
  69. package/src/settings/general-settings.js +85 -3
  70. package/src/settings/head-editor.js +30 -24
  71. package/src/settings/schema-field-ui.js +58 -39
  72. package/src/site-context.js +41 -31
  73. package/src/state.js +70 -29
  74. package/src/store.js +21 -26
  75. package/src/studio.js +103 -81
  76. package/src/tabs/tab.js +67 -43
  77. package/src/tabs/transact.js +23 -13
  78. package/src/ui/button-group.js +24 -24
  79. package/src/ui/color-selector.js +28 -23
  80. package/src/ui/field-row.js +3 -3
  81. package/src/ui/icons.js +2 -2
  82. package/src/ui/media-picker.js +7 -7
  83. package/src/ui/spectrum.js +5 -1
  84. package/src/ui/unit-selector.js +18 -16
  85. package/src/ui/value-selector.js +21 -15
  86. package/src/ui/widgets.js +22 -19
  87. package/src/utils/canvas-media.js +5 -4
  88. package/src/utils/edit-display.js +31 -20
  89. package/src/utils/google-fonts.js +11 -11
  90. package/src/utils/inherited-style.js +9 -8
  91. package/src/utils/studio-utils.js +7 -7
  92. package/src/view.js +58 -1
  93. package/src/workspace/workspace.js +10 -7
@@ -23,7 +23,7 @@ import { getLayerSlot } from "../ui/layers.js";
23
23
  *
24
24
  * @typedef {import("../state.js").JxPath} JxPath
25
25
  *
26
- * @typedef {{ command: string; tag: string; label: string; icon: string; shortcut?: string }} InlineAction
26
+ * @typedef {import("../editor/inline-edit.js").InlineAction} InlineAction
27
27
  */
28
28
 
29
29
  /**
@@ -182,7 +182,7 @@ function applyInlineFormat(action) {
182
182
  code: "code",
183
183
  };
184
184
 
185
- const tag = cmdToTag[action.command];
185
+ const tag = action.command ? cmdToTag[action.command] : undefined;
186
186
  if (tag) {
187
187
  const editableRoot = getActiveElement();
188
188
  toggleInlineFormat(tag, editableRoot);
@@ -423,7 +423,7 @@ export function renderBlockActionBar() {
423
423
  @mousedown=${captureSelectionRange}
424
424
  @click=${(/** @type {MouseEvent} */ e) => onFormatClick(e, action)}
425
425
  >
426
- ${formatIconMap[action.icon] ?? nothing}
426
+ ${action.icon ? (formatIconMap[action.icon] ?? nothing) : nothing}
427
427
  </sp-action-button>
428
428
  `,
429
429
  )}
@@ -437,7 +437,7 @@ export function renderBlockActionBar() {
437
437
 
438
438
  // Post-render side effects
439
439
  requestAnimationFrame(() => {
440
- const bar = view.blockActionBarEl?.firstElementChild;
440
+ const bar = /** @type {HTMLElement | null} */ (view.blockActionBarEl?.firstElementChild);
441
441
  if (!bar) return;
442
442
  // Clamp to window
443
443
  const barRect = bar.getBoundingClientRect();
@@ -447,7 +447,7 @@ export function renderBlockActionBar() {
447
447
  // Attach drag handle
448
448
  const currentTab = activeTab.value;
449
449
  if (currentTab?.session.selection && currentTab.session.selection.length >= 2) {
450
- const handle = bar.querySelector(".bar-drag-handle");
450
+ const handle = /** @type {HTMLElement | null} */ (bar.querySelector(".bar-drag-handle"));
451
451
  if (handle) {
452
452
  if (view.selDragCleanup) {
453
453
  view.selDragCleanup();
@@ -16,11 +16,15 @@ import { effectiveZoom } from "../canvas/canvas-helpers.js";
16
16
 
17
17
  /**
18
18
  * @typedef {{
19
+ * mediaName: string | null;
20
+ * element: HTMLElement | null;
19
21
  * canvas: HTMLElement;
20
- * overlayClk: HTMLElement;
21
22
  * overlay: HTMLElement;
23
+ * overlayClk: HTMLElement;
22
24
  * viewport: HTMLElement;
25
+ * scrollContainer: HTMLElement | null;
23
26
  * dropLine: HTMLElement;
27
+ * _width: number | null;
24
28
  * }} CanvasPanel
25
29
  *
26
30
  * @typedef {(string | number)[]} JxPath
@@ -56,7 +60,9 @@ export function registerPanelDnD(panel) {
56
60
  onDrop() {
57
61
  _activeDropEl?.classList.remove("canvas-drop-target");
58
62
  _activeDropEl = null;
59
- for (const p of canvasPanels) p.dropLine.style.display = "none";
63
+ for (const p of canvasPanels) {
64
+ if (p.dropLine) p.dropLine.style.display = "none";
65
+ }
60
66
  view.lastDragInput = null;
61
67
  for (const el of canvas.querySelectorAll("*")) {
62
68
  /** @type {HTMLElement} */ (el).style.pointerEvents = "none";
@@ -7,13 +7,18 @@ import { classMap } from "lit-html/directives/class-map.js";
7
7
  const expandedDataKeys = new Set();
8
8
 
9
9
  /** Unwrap a Vue ref (has .value and .__v_isRef) to get the underlying value. */
10
- function unwrapSignal(/** @type {any} */ value) {
11
- if (value && typeof value === "object" && value.__v_isRef) return value.value;
10
+ function unwrapSignal(/** @type {unknown} */ value) {
11
+ if (
12
+ value &&
13
+ typeof value === "object" &&
14
+ /** @type {Record<string, unknown>} */ (value).__v_isRef
15
+ )
16
+ return /** @type {Record<string, unknown>} */ (value).value;
12
17
  return value;
13
18
  }
14
19
 
15
20
  /** Type label for a signal value in the data explorer. */
16
- function dataTypeLabel(/** @type {any} */ value) {
21
+ function dataTypeLabel(/** @type {unknown} */ value) {
17
22
  const v = unwrapSignal(value);
18
23
  if (v === null) return "null";
19
24
  if (v === undefined) return "pending";
@@ -25,15 +30,15 @@ function dataTypeLabel(/** @type {any} */ value) {
25
30
  /**
26
31
  * Render the data explorer tab showing live resolved values.
27
32
  *
28
- * @param {Record<string, any>} state - S.document.state (the $defs definitions)
29
- * @param {Record<string, any> | null} liveScope - Cached live scope from runtime rendering
33
+ * @param {Record<string, unknown>} state - S.document.state (the $defs definitions)
34
+ * @param {Record<string, unknown> | null} liveScope - Cached live scope from runtime rendering
30
35
  * @param {{
31
36
  * renderCanvas: () => void;
32
37
  * renderLeftPanel: () => void;
33
- * defCategory: (def: any) => string;
34
- * defBadgeLabel: (def: any) => string;
38
+ * defCategory: (def: unknown) => string;
39
+ * defBadgeLabel: (def: unknown) => string;
35
40
  * }} callbacks
36
- * @returns {any}
41
+ * @returns {import("lit-html").TemplateResult}
37
42
  */
38
43
  export function renderDataExplorerTemplate(state, liveScope, callbacks) {
39
44
  const { renderCanvas, renderLeftPanel, defCategory, defBadgeLabel } = callbacks;
@@ -91,11 +96,11 @@ export function renderDataExplorerTemplate(state, liveScope, callbacks) {
91
96
  /**
92
97
  * Recursively render a JSON value as a tree view (Lit template).
93
98
  *
94
- * @returns {any}
99
+ * @returns {import("lit-html").TemplateResult}
95
100
  */
96
101
  export function renderDataTreeTemplate(
97
- /** @type {any} */ value,
98
- /** @type {any} */ depth,
102
+ /** @type {unknown} */ value,
103
+ /** @type {number} */ depth,
99
104
  maxDepth = 5,
100
105
  ) {
101
106
  const indent = `${(depth + 1) * 12}px`;
@@ -150,10 +155,11 @@ export function renderDataTreeTemplate(
150
155
  }
151
156
 
152
157
  // Object
153
- const keys = Object.keys(value);
158
+ const obj = /** @type {Record<string, unknown>} */ (value);
159
+ const keys = Object.keys(obj);
154
160
  const cap = 30;
155
161
  const items = keys.slice(0, cap).map((key) => {
156
- const v = value[key];
162
+ const v = obj[key];
157
163
  if (v === null || v === undefined || typeof v !== "object") {
158
164
  const valText =
159
165
  typeof v === "string" && v.length > 80 ? `"${v.slice(0, 80)}\u2026"` : JSON.stringify(v);
package/src/panels/dnd.js CHANGED
@@ -3,6 +3,19 @@
3
3
  * on layer rows, component cards, and element cards.
4
4
  */
5
5
 
6
+ /**
7
+ * @typedef {{ element: HTMLElement; input: { clientX: number; clientY: number } }} DragCanDragArgs
8
+ *
9
+ * @typedef {{ source: { data: Record<string, unknown>; element: HTMLElement } }} DragDropSourceArgs
10
+ *
11
+ * @typedef {{ self: { data: Record<string, unknown> } }} DragSelfArgs
12
+ *
13
+ * @typedef {{
14
+ * source: { data: Record<string, unknown>; element: HTMLElement };
15
+ * location: { current: { dropTargets: { data: Record<string, unknown> }[] } };
16
+ * }} DragMonitorDropArgs
17
+ */
18
+
6
19
  import {
7
20
  draggable,
8
21
  dropTargetForElements,
@@ -32,14 +45,18 @@ import { defaultDef, unsafeTags } from "./shared.js";
32
45
  /** Register DnD on layer rows — called from left-panel.js after render */
33
46
  export function registerLayersDnD() {
34
47
  requestAnimationFrame(() => {
35
- const container = /** @type {any} */ (leftPanel)?.querySelector(".layers-container");
48
+ const container = /** @type {HTMLElement | null} */ (
49
+ leftPanel?.querySelector(".layers-container")
50
+ );
36
51
  if (!container) return;
37
52
 
38
- container.querySelectorAll("[data-dnd-row]").forEach(
39
- /** @param {any} row */ (row) => {
40
- const rowPath = /** @type {string} */ (row.dataset.path)
41
- .split("/")
42
- .map((/** @type {any} */ s) => (/^\d+$/.test(s) ? parseInt(s) : s));
53
+ /** @type {NodeListOf<HTMLElement>} */ (container.querySelectorAll("[data-dnd-row]")).forEach(
54
+ (row) => {
55
+ const rowPath = /** @type {JxPath} */ (
56
+ /** @type {string} */ (row.dataset.path)
57
+ .split("/")
58
+ .map((/** @type {string} */ s) => (/^\d+$/.test(s) ? parseInt(s) : s))
59
+ );
43
60
  const rowDepth = parseInt(/** @type {string} */ (row.dataset.dndDepth)) || 0;
44
61
  const isVoid = row.hasAttribute("data-dnd-void");
45
62
  const isExpanded = row.hasAttribute("data-dnd-expanded");
@@ -47,7 +64,7 @@ export function registerLayersDnD() {
47
64
  const cleanup = combine(
48
65
  draggable({
49
66
  element: row,
50
- canDrag(/** @type {any} */ { element: _el, input }) {
67
+ canDrag(/** @type {DragCanDragArgs} */ { element: _el, input }) {
51
68
  const target = /** @type {HTMLElement} */ (
52
69
  document.elementFromPoint(input.clientX, input.clientY)
53
70
  );
@@ -73,28 +90,30 @@ export function registerLayersDnD() {
73
90
  }),
74
91
  dropTargetForElements({
75
92
  element: row,
76
- canDrop(/** @type {any} */ { source }) {
77
- const srcPath = source.data.path;
93
+ canDrop(/** @type {DragDropSourceArgs} */ { source }) {
94
+ const srcPath = /** @type {JxPath | undefined} */ (source.data.path);
78
95
  if (srcPath && isAncestor(srcPath, rowPath)) return false;
79
96
  return true;
80
97
  },
81
98
  getData(/** @type {any} */ { input, element }) {
82
- return attachInstruction(
83
- { path: rowPath },
84
- /** @type {any} */ ({
85
- input,
86
- element,
87
- currentLevel: rowDepth,
88
- indentPerLevel: 16,
89
- mode: isExpanded ? "expanded" : "standard",
90
- block: isVoid ? ["make-child"] : [],
91
- }),
99
+ return /** @type {Record<string | symbol, unknown>} */ (
100
+ attachInstruction(
101
+ { path: rowPath },
102
+ {
103
+ input,
104
+ element,
105
+ currentLevel: rowDepth,
106
+ indentPerLevel: 16,
107
+ mode: isExpanded ? "expanded" : "standard",
108
+ block: isVoid ? ["make-child"] : [],
109
+ },
110
+ )
92
111
  );
93
112
  },
94
- onDragEnter(/** @type {any} */ { self }) {
113
+ onDragEnter(/** @type {DragSelfArgs} */ { self }) {
95
114
  showLayerDropGap(row, self.data, container);
96
115
  },
97
- onDrag(/** @type {any} */ { self }) {
116
+ onDrag(/** @type {DragSelfArgs} */ { self }) {
98
117
  showLayerDropGap(row, self.data, container);
99
118
  },
100
119
  onDragLeave() {
@@ -111,14 +130,14 @@ export function registerLayersDnD() {
111
130
 
112
131
  // Global monitor
113
132
  const monitorCleanup = monitorForElements({
114
- onDrop(/** @type {any} */ { source, location }) {
133
+ onDrop(/** @type {DragMonitorDropArgs} */ { source, location }) {
115
134
  clearLayerDropGap(container);
116
135
  const target = location.current.dropTargets[0];
117
136
  if (!target) return;
118
137
  const instruction = extractInstruction(target.data);
119
138
  if (!instruction || instruction.type === "instruction-blocked") return;
120
139
  const srcData = source.data;
121
- const targetPath = target.data.path;
140
+ const targetPath = /** @type {JxPath} */ (target.data.path);
122
141
 
123
142
  // If the source had children, persist collapse at the new location
124
143
  const srcRow = srcData.type === "tree-node" && source.element;
@@ -143,56 +162,62 @@ export function registerLayersDnD() {
143
162
  /** Register DnD on component rows — called from renderLeftPanel when tab=components */
144
163
  export function registerComponentsDnD() {
145
164
  requestAnimationFrame(() => {
146
- const container = /** @type {any} */ (leftPanel)?.querySelector(".components-section");
165
+ const container = /** @type {HTMLElement | null} */ (
166
+ leftPanel?.querySelector(".components-section")
167
+ );
147
168
  if (!container) return;
148
169
 
149
- container.querySelectorAll("[data-component-tag]").forEach(
150
- /** @param {any} row */ (row) => {
151
- const tagName = row.dataset.componentTag;
152
- if (!tagName) return;
153
- const comp = componentRegistry.find(/** @param {any} c */ (c) => c.tagName === tagName);
154
- if (!comp) return;
170
+ /** @type {NodeListOf<HTMLElement>} */ (
171
+ container.querySelectorAll("[data-component-tag]")
172
+ ).forEach((row) => {
173
+ const tagName = row.dataset.componentTag;
174
+ if (!tagName) return;
175
+ const comp = componentRegistry.find(
176
+ (/** @type {import("../files/components.js").ComponentEntry} */ c) => c.tagName === tagName,
177
+ );
178
+ if (!comp) return;
155
179
 
156
- // Fill preview with live rendered component
157
- const preview = row.querySelector(".element-card-preview");
158
- if (preview && !preview.querySelector(tagName)) {
159
- renderComponentPreview(comp).then((/** @type {any} */ el) => {
160
- preview.textContent = "";
161
- preview.appendChild(el);
162
- });
163
- }
180
+ // Fill preview with live rendered component
181
+ const preview = row.querySelector(".element-card-preview");
182
+ if (preview && !preview.querySelector(tagName)) {
183
+ renderComponentPreview(comp).then((/** @type {HTMLElement} */ el) => {
184
+ preview.textContent = "";
185
+ preview.appendChild(el);
186
+ });
187
+ }
164
188
 
165
- const instanceDef = {
166
- tagName: comp.tagName,
167
- $props: comp.props
168
- ? Object.fromEntries(
169
- comp.props.map((/** @type {any} */ p) => [
189
+ const instanceDef = {
190
+ tagName: comp.tagName,
191
+ $props: comp.props
192
+ ? Object.fromEntries(
193
+ comp.props.map(
194
+ (/** @type {{ name: string; default?: unknown; [k: string]: unknown }} */ p) => [
170
195
  p.name,
171
196
  p.default !== undefined ? p.default : "",
172
- ]),
173
- )
174
- : {},
175
- };
176
- const cleanup = draggable({
177
- element: row,
178
- getInitialData() {
179
- return { type: "block", fragment: structuredClone(instanceDef) };
180
- },
181
- });
182
- view.dndCleanups.push(cleanup);
183
- },
184
- );
197
+ ],
198
+ ),
199
+ )
200
+ : {},
201
+ };
202
+ const cleanup = draggable({
203
+ element: row,
204
+ getInitialData() {
205
+ return { type: "block", fragment: structuredClone(instanceDef) };
206
+ },
207
+ });
208
+ view.dndCleanups.push(cleanup);
209
+ });
185
210
  });
186
211
  }
187
212
 
188
213
  /** Register DnD on element (HTML block) rows */
189
214
  export function registerElementsDnD() {
190
215
  requestAnimationFrame(() => {
191
- const container = /** @type {any} */ (leftPanel)?.querySelector(".panel-body");
216
+ const container = /** @type {HTMLElement | null} */ (leftPanel?.querySelector(".panel-body"));
192
217
  if (!container) return;
193
- container.querySelectorAll("[data-block-tag]").forEach(
194
- /** @param {any} row */ (row) => {
195
- const tag = row.dataset.blockTag;
218
+ /** @type {NodeListOf<HTMLElement>} */ (container.querySelectorAll("[data-block-tag]")).forEach(
219
+ (row) => {
220
+ const tag = /** @type {string} */ (row.dataset.blockTag);
196
221
  const preview = row.querySelector(".element-card-preview");
197
222
  if (preview && !preview.firstChild) {
198
223
  const el = document.createElement(unsafeTags.has(tag) ? "span" : tag);
@@ -215,23 +240,23 @@ export function registerElementsDnD() {
215
240
  /**
216
241
  * Hide descendant rows of the dragged item so it appears collapsed during drag.
217
242
  *
218
- * @param {any} parentRow
219
- * @param {any} container
243
+ * @param {HTMLElement} parentRow
244
+ * @param {HTMLElement} container
220
245
  */
221
246
  function hideDescendantRows(parentRow, container) {
222
247
  const prefix = parentRow.dataset.path + "/";
223
248
  const rows = container.querySelectorAll(".layers-tree .layer-row");
224
249
  for (const r of rows) {
225
- if (/** @type {any} */ (r).dataset.path?.startsWith(prefix)) {
226
- /** @type {any} */ (r).style.display = "none";
250
+ if (/** @type {HTMLElement} */ (r).dataset.path?.startsWith(prefix)) {
251
+ /** @type {HTMLElement} */ (r).style.display = "none";
227
252
  }
228
253
  }
229
254
  }
230
255
 
231
256
  /**
232
- * @param {any} rowEl
233
- * @param {any} data
234
- * @param {any} container
257
+ * @param {HTMLElement} rowEl
258
+ * @param {Record<string, unknown>} data
259
+ * @param {HTMLElement} container
235
260
  */
236
261
  export function showLayerDropGap(rowEl, data, container) {
237
262
  const instruction = extractInstruction(data);
@@ -262,37 +287,39 @@ export function showLayerDropGap(rowEl, data, container) {
262
287
  const gap = view.layerDragSourceHeight;
263
288
 
264
289
  for (let i = 0; i < rows.length; i++) {
265
- if (/** @type {any} */ (rows[i]).classList.contains("dragging")) continue;
290
+ if (/** @type {HTMLElement} */ (rows[i]).classList.contains("dragging")) continue;
266
291
  if (instruction.type === "reorder-above") {
267
- /** @type {any} */ (rows[i]).style.transform = i >= targetIdx ? `translateY(${gap}px)` : "";
292
+ /** @type {HTMLElement} */ (rows[i]).style.transform =
293
+ i >= targetIdx ? `translateY(${gap}px)` : "";
268
294
  } else {
269
- /** @type {any} */ (rows[i]).style.transform = i > targetIdx ? `translateY(${gap}px)` : "";
295
+ /** @type {HTMLElement} */ (rows[i]).style.transform =
296
+ i > targetIdx ? `translateY(${gap}px)` : "";
270
297
  }
271
298
  }
272
299
  }
273
300
 
274
- /** @param {any} container */
301
+ /** @param {HTMLElement} container */
275
302
  export function clearLayerDropGap(container) {
276
303
  if (view._currentDropTargetRow) {
277
304
  view._currentDropTargetRow.classList.remove("drop-target");
278
305
  view._currentDropTargetRow = null;
279
306
  }
280
307
  const rows = container.querySelectorAll(".layers-tree .layer-row");
281
- for (const r of rows) /** @type {any} */ (r).style.transform = "";
308
+ for (const r of rows) /** @type {HTMLElement} */ (r).style.transform = "";
282
309
  }
283
310
 
284
311
  /**
285
312
  * Apply a DnD instruction to the state
286
313
  *
287
- * @param {any} instruction
288
- * @param {any} srcData
289
- * @param {any} targetPath
314
+ * @param {{ type: string }} instruction
315
+ * @param {Record<string, unknown>} srcData
316
+ * @param {JxPath} targetPath
290
317
  */
291
318
  export function applyDropInstruction(instruction, srcData, targetPath) {
292
- const document = activeTab.value?.doc.document;
319
+ const doc = /** @type {JxMutableNode} */ (activeTab.value?.doc.document);
293
320
  if (srcData.type === "tree-node") {
294
- const fromPath = srcData.path;
295
- const targetParent = /** @type {any} */ (parentElementPath(targetPath));
321
+ const fromPath = /** @type {JxPath} */ (srcData.path);
322
+ const targetParent = /** @type {JxPath} */ (parentElementPath(targetPath));
296
323
  const targetIdx = /** @type {number} */ (childIndex(targetPath));
297
324
 
298
325
  switch (instruction.type) {
@@ -305,64 +332,89 @@ export function applyDropInstruction(instruction, srcData, targetPath) {
305
332
  );
306
333
  break;
307
334
  case "make-child": {
308
- const target = getNodeAtPath(document, targetPath);
335
+ const target = getNodeAtPath(doc, targetPath);
309
336
  const len = target?.children?.length || 0;
310
337
  transactDoc(activeTab.value, (t) => mutateMoveNode(t, fromPath, targetPath, len));
311
338
  break;
312
339
  }
313
340
  }
314
341
  } else if (srcData.type === "block") {
315
- const targetParent = /** @type {any} */ (parentElementPath(targetPath));
342
+ const targetParent = /** @type {JxPath} */ (parentElementPath(targetPath));
316
343
  const targetIdx = /** @type {number} */ (childIndex(targetPath));
317
344
 
318
345
  switch (instruction.type) {
319
346
  case "reorder-above":
320
347
  transactDoc(activeTab.value, (t) =>
321
- mutateInsertNode(t, targetParent, targetIdx, structuredClone(srcData.fragment)),
348
+ mutateInsertNode(
349
+ t,
350
+ targetParent,
351
+ targetIdx,
352
+ structuredClone(/** @type {JxMutableNode} */ (srcData.fragment)),
353
+ ),
322
354
  );
323
355
  break;
324
356
  case "reorder-below":
325
357
  transactDoc(activeTab.value, (t) =>
326
- mutateInsertNode(t, targetParent, targetIdx + 1, structuredClone(srcData.fragment)),
358
+ mutateInsertNode(
359
+ t,
360
+ targetParent,
361
+ targetIdx + 1,
362
+ structuredClone(/** @type {JxMutableNode} */ (srcData.fragment)),
363
+ ),
327
364
  );
328
365
  break;
329
366
  case "make-child": {
330
- const target = getNodeAtPath(document, targetPath);
367
+ const target = getNodeAtPath(doc, targetPath);
331
368
  const len = target?.children?.length || 0;
332
369
  transactDoc(activeTab.value, (t) =>
333
- mutateInsertNode(t, targetPath, len, structuredClone(srcData.fragment)),
370
+ mutateInsertNode(
371
+ t,
372
+ targetPath,
373
+ len,
374
+ structuredClone(/** @type {JxMutableNode} */ (srcData.fragment)),
375
+ ),
334
376
  );
335
377
  break;
336
378
  }
337
379
  }
338
380
 
339
381
  // Auto-import to $elements if the dropped block is a custom component
340
- const tag = srcData.fragment?.tagName;
382
+ const fragment = /** @type {JxMutableNode | undefined} */ (srcData.fragment);
383
+ const tag = fragment?.tagName;
341
384
  if (tag && tag.includes("-")) {
342
- const comp = componentRegistry.find((/** @type {any} */ c) => c.tagName === tag);
385
+ const comp = componentRegistry.find(
386
+ (/** @type {import("../files/components.js").ComponentEntry} */ c) => c.tagName === tag,
387
+ );
343
388
  if (comp) {
344
389
  const tab = activeTab.value;
345
390
  const elements = tab?.doc.document?.$elements || [];
346
391
  if (comp.source === "npm") {
347
392
  const specifier = comp.modulePath ? `${comp.package}/${comp.modulePath}` : comp.package;
393
+ if (!specifier) return;
348
394
  const alreadyImported = elements.some(
349
- (/** @type {any} */ e) => e === specifier || e === comp.package,
395
+ (/** @type {JxMutableNode | string | { $ref: string }} */ e) =>
396
+ e === specifier || e === comp.package,
350
397
  );
351
398
  if (!alreadyImported) {
352
- transact(activeTab.value, (/** @type {any} */ doc) => {
399
+ transact(activeTab.value, (/** @type {JxMutableNode} */ doc) => {
353
400
  if (!doc.$elements) doc.$elements = [];
354
401
  doc.$elements.push(specifier);
355
402
  });
356
403
  }
357
404
  } else {
358
405
  const alreadyImported = elements.some(
359
- (/** @type {any} */ e) =>
360
- e.$ref &&
361
- (e.$ref === `./${comp.path}` || e.$ref.endsWith(comp.path.split("/").pop())),
406
+ (/** @type {JxMutableNode | string | { $ref: string }} */ e) => {
407
+ const ref = typeof e === "object" && e !== null ? e.$ref : undefined;
408
+ return (
409
+ ref &&
410
+ (ref === `./${comp.path}` ||
411
+ ref.endsWith(/** @type {string} */ (comp.path.split("/").pop())))
412
+ );
413
+ },
362
414
  );
363
415
  if (!alreadyImported) {
364
416
  const relPath = computeRelativePath(tab?.documentPath, comp.path);
365
- transact(activeTab.value, (/** @type {any} */ doc) => {
417
+ transact(activeTab.value, (/** @type {JxMutableNode} */ doc) => {
366
418
  if (!doc.$elements) doc.$elements = [];
367
419
  doc.$elements.push({ $ref: relPath });
368
420
  });