@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
@@ -24,15 +24,15 @@ import { selectStylebookTag, stylebookMeta } from "./stylebook-panel.js";
24
24
  * @typedef {{
25
25
  * getCanvasMode: () => string;
26
26
  * setCanvasMode: (mode: string) => void;
27
- * renderImportsTemplate: (...args: any[]) => TemplateResult;
28
- * renderFilesTemplate: () => TemplateResult;
29
- * renderSignalsTemplate: (...args: any[]) => TemplateResult;
30
- * renderDataExplorerTemplate: (...args: any[]) => TemplateResult;
31
- * renderHeadTemplate: (...args: any[]) => TemplateResult;
32
- * renderGitPanel: (...args: any[]) => TemplateResult;
27
+ * renderImportsTemplate: Function;
28
+ * renderFilesTemplate: Function;
29
+ * renderSignalsTemplate: Function;
30
+ * renderDataExplorerTemplate: Function;
31
+ * renderHeadTemplate: Function;
32
+ * renderGitPanel: Function;
33
33
  * renderCanvas: () => void;
34
- * defCategory: (tag: string) => string;
35
- * defBadgeLabel: (tag: string) => string;
34
+ * defCategory: (def: unknown) => string;
35
+ * defBadgeLabel: (def: unknown) => string;
36
36
  * navigateToComponent: (path: string) => void;
37
37
  * webdata: object;
38
38
  * defaultDef: (tag: string) => object;
@@ -40,7 +40,9 @@ import { selectStylebookTag, stylebookMeta } from "./stylebook-panel.js";
40
40
  * registerElementsDnD: () => void;
41
41
  * registerComponentsDnD: () => void;
42
42
  * setupTreeKeyboard: (tree: HTMLElement) => void;
43
- * setGitDiffState: (state: any) => void;
43
+ * registerFileTreeDnD: (ctx: { renderLeftPanel: () => void }) => void;
44
+ * setGitDiffState: (state: import("../canvas/canvas-render.js").GitDiffState | null) => void;
45
+ * cloneRepository?: () => void;
44
46
  * }} LeftPanelCtx
45
47
  */
46
48
 
@@ -52,6 +54,16 @@ let _scope = null;
52
54
 
53
55
  let _rendering = false;
54
56
  let _scheduled = false;
57
+ let _hasFocus = false;
58
+
59
+ function _onFocusIn() {
60
+ _hasFocus = true;
61
+ }
62
+
63
+ function _onFocusOut() {
64
+ _hasFocus = false;
65
+ render();
66
+ }
55
67
 
56
68
  /**
57
69
  * Mount the left panel orchestrator.
@@ -60,20 +72,25 @@ let _scheduled = false;
60
72
  */
61
73
  export function mount(ctx) {
62
74
  _ctx = ctx;
75
+ leftPanel.addEventListener("focusin", _onFocusIn);
76
+ leftPanel.addEventListener("focusout", _onFocusOut);
63
77
  _scope = effectScope();
64
78
  _scope.run(() => {
65
79
  effect(() => {
66
80
  const tab = activeTab.value;
67
- if (!tab) return;
68
- // Track properties the left panel reads
69
- void tab.doc.document;
70
- void tab.doc.mode;
71
- void tab.session.selection;
72
- void tab.session.ui.settingsTab;
73
- void tab.session.ui.gitStatus;
74
- void tab.session.ui.gitLoading;
75
- void tab.session.ui.gitError;
76
- render();
81
+ if (tab) {
82
+ // Track properties the left panel reads
83
+ void tab.doc.document;
84
+ void tab.doc.mode;
85
+ void tab.session.selection;
86
+ void tab.session.ui.settingsTab;
87
+ void tab.session.ui.gitStatus;
88
+ void tab.session.ui.gitLoading;
89
+ void tab.session.ui.gitError;
90
+ }
91
+ if (!_hasFocus) {
92
+ render();
93
+ }
77
94
  });
78
95
  });
79
96
  }
@@ -82,6 +99,9 @@ export function unmount() {
82
99
  _scope?.stop();
83
100
  _scope = null;
84
101
  _ctx = null;
102
+ leftPanel.removeEventListener("focusin", _onFocusIn);
103
+ leftPanel.removeEventListener("focusout", _onFocusOut);
104
+ _hasFocus = false;
85
105
  }
86
106
 
87
107
  export function render() {
@@ -122,26 +142,78 @@ function _flush() {
122
142
 
123
143
  function _render() {
124
144
  const ctx = /** @type {LeftPanelCtx} */ (_ctx);
145
+ const tab = view.leftTab;
146
+
147
+ // ── Project-level panels: render based on projectState, independent of active tab ──
148
+
149
+ if (tab === "files") {
150
+ litRender(html`<div class="panel-body">${ctx.renderFilesTemplate()}</div>`, leftPanel);
151
+ const tree = /** @type {HTMLElement | null} */ (leftPanel.querySelector(".file-tree"));
152
+ if (tree) ctx.setupTreeKeyboard(tree);
153
+ ctx.registerFileTreeDnD({ renderLeftPanel: render });
154
+ return;
155
+ }
156
+
157
+ if (tab === "git") {
158
+ const aTab = activeTab.value;
159
+ const S = aTab ? { ui: aTab.session.ui } : { ui: {} };
160
+ litRender(html`<div class="panel-body">${ctx.renderGitPanel(S, ctx)}</div>`, leftPanel);
161
+ return;
162
+ }
163
+
164
+ if (tab === "blocks") {
165
+ const content = renderElementsTemplate(
166
+ /** @type {Parameters<typeof renderElementsTemplate>[0]} */ ({
167
+ webdata: ctx.webdata,
168
+ defaultDef: ctx.defaultDef,
169
+ rerender: render,
170
+ }),
171
+ );
172
+ litRender(html`<div class="panel-body">${content}</div>`, leftPanel);
173
+ ctx.registerElementsDnD();
174
+ ctx.registerComponentsDnD();
175
+ return;
176
+ }
177
+
178
+ // ── Document-level panels: require an active tab ──
179
+
125
180
  const aTab = activeTab.value;
126
- if (!aTab) return;
127
- const S = /** @type {any} */ ({
181
+ if (!aTab) {
182
+ litRender(html`<div class="panel-body"></div>`, leftPanel);
183
+ return;
184
+ }
185
+
186
+ const S = /**
187
+ * @type {{
188
+ * ui: unknown;
189
+ * document: JxMutableNode;
190
+ * mode: string;
191
+ * selection: JxPath | null;
192
+ * canvas: { scope?: object } | null;
193
+ * content?: { frontmatter?: Record<string, unknown> };
194
+ * documentPath?: string;
195
+ * }}
196
+ */ ({
128
197
  ui: aTab.session.ui,
129
198
  document: aTab.doc.document,
130
199
  mode: aTab.doc.mode,
131
200
  selection: aTab.session.selection,
132
201
  canvas: aTab.session.canvas,
202
+ content: aTab.doc.content,
203
+ documentPath: aTab.documentPath,
133
204
  });
134
- const tab = view.leftTab;
135
205
 
136
206
  /** @type {TemplateResult | typeof nothing} */
137
207
  let content;
138
208
  if (tab === "layers")
139
209
  content =
140
210
  ctx.getCanvasMode() === "stylebook"
141
- ? renderStylebookLayersTemplate({
142
- selectStylebookTag,
143
- stylebookMeta,
144
- })
211
+ ? renderStylebookLayersTemplate(
212
+ /** @type {Parameters<typeof renderStylebookLayersTemplate>[0]} */ ({
213
+ selectStylebookTag,
214
+ stylebookMeta,
215
+ }),
216
+ )
145
217
  : renderLayersTemplate({
146
218
  navigateToComponent: ctx.navigateToComponent,
147
219
  rerender: render,
@@ -155,13 +227,6 @@ function _render() {
155
227
  transact(activeTab.value, fn);
156
228
  },
157
229
  });
158
- else if (tab === "files") content = ctx.renderFilesTemplate();
159
- else if (tab === "blocks")
160
- content = renderElementsTemplate({
161
- webdata: ctx.webdata,
162
- defaultDef: ctx.defaultDef,
163
- rerender: render,
164
- });
165
230
  else if (tab === "state")
166
231
  content = ctx.renderSignalsTemplate(S, {
167
232
  renderLeftPanel: render,
@@ -184,33 +249,25 @@ function _render() {
184
249
  applyMutation: isContent
185
250
  ? (/** @type {(doc: object) => void} */ fn) => {
186
251
  const tab = activeTab.value;
187
- const fm = /** @type {Record<string, any>} */ (tab.doc.content?.frontmatter ?? {});
188
- const tmp = { title: fm.title, $head: fm.$head ? [...fm.$head] : undefined };
252
+ const fm = /** @type {Record<string, unknown>} */ (tab.doc.content?.frontmatter ?? {});
253
+ const fmHead = /** @type {unknown[] | undefined} */ (fm.$head);
254
+ const tmp = { title: fm.title, $head: fmHead ? [...fmHead] : undefined };
189
255
  fn(tmp);
190
256
  if (tmp.title !== fm.title)
191
- mutateUpdateFrontmatter(tab, "title", /** @type {any} */ (tmp.title));
257
+ mutateUpdateFrontmatter(tab, "title", /** @type {JsonValue} */ (tmp.title));
192
258
  const newHead = tmp.$head && tmp.$head.length > 0 ? tmp.$head : undefined;
193
- mutateUpdateFrontmatter(tab, "$head", newHead);
259
+ mutateUpdateFrontmatter(tab, "$head", /** @type {JsonValue} */ (newHead));
260
+ render();
194
261
  }
195
262
  : (/** @type {(doc: object) => void} */ fn) => {
196
263
  transact(activeTab.value, fn);
197
264
  },
198
265
  renderLeftPanel: render,
199
266
  });
200
- } else if (tab === "git") content = ctx.renderGitPanel(S, ctx);
201
- else content = nothing;
267
+ } else content = nothing;
202
268
 
203
269
  litRender(html`<div class="panel-body">${content}</div>`, leftPanel);
204
270
 
205
271
  // Post-render side effects
206
272
  if (tab === "layers" && ctx.getCanvasMode() !== "stylebook") ctx.registerLayersDnD();
207
- else if (tab === "imports") {
208
- /* no post-render DnD needed */
209
- } else if (tab === "blocks") {
210
- ctx.registerElementsDnD();
211
- ctx.registerComponentsDnD();
212
- } else if (tab === "files") {
213
- const tree = /** @type {HTMLElement | null} */ (leftPanel.querySelector(".file-tree"));
214
- if (tree) ctx.setupTreeKeyboard(tree);
215
- }
216
273
  }
@@ -118,6 +118,7 @@ function _flush() {
118
118
  const boxes = [];
119
119
 
120
120
  // Batch layout reads: read viewport geometry once per panel
121
+ if (!p.viewport) continue;
121
122
  const vpRect = p.viewport.getBoundingClientRect();
122
123
  const scrollTop = p.viewport.scrollTop;
123
124
  const scrollLeft = p.viewport.scrollLeft;
@@ -22,32 +22,39 @@ import { defaultDef } from "../panels/shared.js";
22
22
  import { bubbleInlinePath, findCanvasElement, effectiveZoom } from "../canvas/canvas-helpers.js";
23
23
  import { layoutElements, activeLayoutPath } from "../canvas/canvas-live-render.js";
24
24
 
25
- /** @type {any} */
25
+ /** @type {PanelEventsCtx | null} */
26
26
  let _ctx = null;
27
27
 
28
+ /**
29
+ * @typedef {{
30
+ * getCanvasMode: () => string;
31
+ * enterInlineEdit: (el: HTMLElement, path: JxPath) => void;
32
+ * navigateToComponent: (path: string) => void;
33
+ * }} PanelEventsCtx
34
+ */
35
+
28
36
  /**
29
37
  * Initialize the panel events module.
30
38
  *
31
- * @param {{
32
- * getCanvasMode: () => string;
33
- * enterInlineEdit: (el: any, path: any) => void;
34
- * navigateToComponent: (path: any) => void;
35
- * }} ctx
39
+ * @param {PanelEventsCtx} ctx
36
40
  */
37
41
  export function initPanelEvents(ctx) {
38
42
  _ctx = ctx;
39
43
  }
40
44
 
41
- /** @param {any} panel */
45
+ /** @param {import("../canvas/canvas-render.js").CanvasPanel} panel */
42
46
  export function registerPanelEvents(panel) {
43
- const { canvas, overlayClk, mediaName } = panel;
47
+ const ctx = /** @type {PanelEventsCtx} */ (_ctx);
48
+ const canvas = /** @type {HTMLElement} */ (panel.canvas);
49
+ const overlayClk = /** @type {HTMLElement} */ (panel.overlayClk);
50
+ const { mediaName } = panel;
44
51
  const ac = new AbortController();
45
52
  const opts = { signal: ac.signal };
46
53
  view.canvasEventCleanups.push(() => ac.abort());
47
54
 
48
- /** @param {any} fn */
55
+ /** @param {Function} fn */
49
56
  function withPanelPointerEvents(fn) {
50
- const els = canvas.querySelectorAll("*");
57
+ const els = /** @type {NodeListOf<HTMLElement>} */ (canvas.querySelectorAll("*"));
51
58
  for (const el of els) el.style.pointerEvents = "auto";
52
59
  overlayClk.style.display = "none";
53
60
  const result = fn();
@@ -58,7 +65,7 @@ export function registerPanelEvents(panel) {
58
65
 
59
66
  overlayClk.addEventListener(
60
67
  "click",
61
- (/** @type {any} */ e) => {
68
+ (/** @type {MouseEvent} */ e) => {
62
69
  const barInner = view.blockActionBarEl?.firstElementChild;
63
70
  if (barInner) {
64
71
  const r = barInner.getBoundingClientRect();
@@ -75,10 +82,10 @@ export function registerPanelEvents(panel) {
75
82
  }
76
83
 
77
84
  const tab = activeTab.value;
78
- const canvasMode = _ctx.getCanvasMode();
85
+ const canvasMode = ctx.getCanvasMode();
79
86
 
80
- const elements = withPanelPointerEvents(() =>
81
- document.elementsFromPoint(e.clientX, e.clientY),
87
+ const elements = /** @type {Element[]} */ (
88
+ withPanelPointerEvents(() => document.elementsFromPoint(e.clientX, e.clientY))
82
89
  );
83
90
 
84
91
  if (!tab) return;
@@ -99,7 +106,9 @@ export function registerPanelEvents(panel) {
99
106
  let path = bubbleInlinePath(tab.doc.document, originalPath);
100
107
  const newMedia = mediaName === "base" ? null : (mediaName ?? null);
101
108
 
102
- const resolvedEl = path === originalPath ? el : findCanvasElement(path, canvas) || el;
109
+ const resolvedEl = /** @type {HTMLElement} */ (
110
+ path === originalPath ? el : findCanvasElement(path, canvas) || el
111
+ );
103
112
 
104
113
  if (
105
114
  pathsEqual(path, tab.session.selection) &&
@@ -107,7 +116,7 @@ export function registerPanelEvents(panel) {
107
116
  (canvasMode === "edit" || tab.doc.mode === "content")
108
117
  ) {
109
118
  tab.session.ui.activeMedia = newMedia;
110
- _ctx.enterInlineEdit(resolvedEl, path);
119
+ ctx.enterInlineEdit(resolvedEl, path);
111
120
  return;
112
121
  }
113
122
 
@@ -131,7 +140,7 @@ export function registerPanelEvents(panel) {
131
140
 
132
141
  overlayClk.addEventListener(
133
142
  "dblclick",
134
- (/** @type {any} */ e) => {
143
+ (/** @type {MouseEvent} */ e) => {
135
144
  const barInner = view.blockActionBarEl?.firstElementChild;
136
145
  if (barInner) {
137
146
  const r = barInner.getBoundingClientRect();
@@ -143,12 +152,12 @@ export function registerPanelEvents(panel) {
143
152
  )
144
153
  return;
145
154
  }
146
- const canvasMode = _ctx.getCanvasMode();
155
+ const canvasMode = ctx.getCanvasMode();
147
156
  if (canvasMode !== "edit" && canvasMode !== "design") return;
148
157
 
149
158
  const tab = activeTab.value;
150
- const elements = withPanelPointerEvents(() =>
151
- document.elementsFromPoint(e.clientX, e.clientY),
159
+ const elements = /** @type {Element[]} */ (
160
+ withPanelPointerEvents(() => document.elementsFromPoint(e.clientX, e.clientY))
152
161
  );
153
162
 
154
163
  if (!tab) return;
@@ -158,12 +167,14 @@ export function registerPanelEvents(panel) {
158
167
  const originalPath = elToPath.get(el);
159
168
  if (originalPath) {
160
169
  const path = bubbleInlinePath(tab.doc.document, originalPath);
161
- const resolvedEl = path === originalPath ? el : findCanvasElement(path, canvas) || el;
170
+ const resolvedEl = /** @type {HTMLElement} */ (
171
+ path === originalPath ? el : findCanvasElement(path, canvas) || el
172
+ );
162
173
  if (isEditableBlock(resolvedEl)) {
163
174
  const newMedia = mediaName === "base" ? null : (mediaName ?? null);
164
175
  tab.session.ui.activeMedia = newMedia;
165
176
  tab.session.selection = path;
166
- _ctx.enterInlineEdit(resolvedEl, path);
177
+ ctx.enterInlineEdit(resolvedEl, path);
167
178
  return;
168
179
  }
169
180
  }
@@ -175,7 +186,7 @@ export function registerPanelEvents(panel) {
175
186
 
176
187
  overlayClk.addEventListener(
177
188
  "contextmenu",
178
- (/** @type {any} */ e) => {
189
+ (/** @type {MouseEvent} */ e) => {
179
190
  const barInner = view.blockActionBarEl?.firstElementChild;
180
191
  if (barInner) {
181
192
  const r = barInner.getBoundingClientRect();
@@ -188,15 +199,15 @@ export function registerPanelEvents(panel) {
188
199
  return;
189
200
  }
190
201
  const tab = activeTab.value;
191
- const elements = withPanelPointerEvents(() =>
192
- document.elementsFromPoint(e.clientX, e.clientY),
202
+ const elements = /** @type {Element[]} */ (
203
+ withPanelPointerEvents(() => document.elementsFromPoint(e.clientX, e.clientY))
193
204
  );
194
205
  for (const el of elements) {
195
206
  if (canvas.contains(el) && el !== canvas) {
196
207
  let path = elToPath.get(el);
197
208
  if (path) {
198
209
  path = bubbleInlinePath(tab?.doc.document, path);
199
- showContextMenu(e, path, { onEditComponent: _ctx.navigateToComponent });
210
+ showContextMenu(e, path, { onEditComponent: ctx.navigateToComponent });
200
211
  return;
201
212
  }
202
213
  }
@@ -208,7 +219,7 @@ export function registerPanelEvents(panel) {
208
219
 
209
220
  overlayClk.addEventListener(
210
221
  "mousemove",
211
- (/** @type {any} */ e) => {
222
+ (/** @type {MouseEvent} */ e) => {
212
223
  const barInner = view.blockActionBarEl?.firstElementChild;
213
224
  if (barInner) {
214
225
  const r = barInner.getBoundingClientRect();
@@ -222,7 +233,9 @@ export function registerPanelEvents(panel) {
222
233
  }
223
234
  const tab = activeTab.value;
224
235
  if (!tab) return;
225
- const el = withPanelPointerEvents(() => document.elementFromPoint(e.clientX, e.clientY));
236
+ const el = /** @type {Element | null} */ (
237
+ withPanelPointerEvents(() => document.elementFromPoint(e.clientX, e.clientY))
238
+ );
226
239
  if (el && canvas.contains(el) && el !== canvas) {
227
240
  let path = elToPath.get(el);
228
241
  if (path) {
@@ -252,7 +265,7 @@ export function registerPanelEvents(panel) {
252
265
  );
253
266
 
254
267
  insertionHelper.mount({
255
- getCanvasMode: _ctx.getCanvasMode,
268
+ getCanvasMode: ctx.getCanvasMode,
256
269
  withPanelPointerEvents,
257
270
  effectiveZoom: effectiveZoom,
258
271
  defaultDef,
@@ -260,7 +273,9 @@ export function registerPanelEvents(panel) {
260
273
  childIndex,
261
274
  getNodeAtPath,
262
275
  elToPath,
263
- panel,
276
+ panel: /** @type {import("../editor/insertion-helper.js").CanvasPanel} */ (
277
+ /** @type {unknown} */ (panel)
278
+ ),
264
279
  });
265
280
  view.canvasEventCleanups.push(() => insertionHelper.unmount());
266
281
  }
@@ -12,11 +12,11 @@ import { resolveDefaultForCanvas } from "../panels/signals-panel.js";
12
12
  * Recursively render a Jx node to the canvas DOM. Media-aware: applies base styles + active
13
13
  * breakpoint/feature overrides.
14
14
  *
15
- * @param {any} node
16
- * @param {any} path
17
- * @param {any} parent
18
- * @param {any} activeBreakpoints
19
- * @param {any} featureToggles
15
+ * @param {JxMutableNode | string | number | boolean | null | undefined} node
16
+ * @param {JxPath} path
17
+ * @param {HTMLElement} parent
18
+ * @param {Set<string>} activeBreakpoints
19
+ * @param {Record<string, boolean>} featureToggles
20
20
  */
21
21
  export function renderCanvasNode(node, path, parent, activeBreakpoints, featureToggles) {
22
22
  if (typeof node === "string" || typeof node === "number" || typeof node === "boolean") {
@@ -32,15 +32,15 @@ export function renderCanvasNode(node, path, parent, activeBreakpoints, featureT
32
32
 
33
33
  if (typeof node.textContent === "string") {
34
34
  el.textContent = node.textContent;
35
- } else if (typeof node.textContent === "object" && node.textContent?.$ref) {
36
- const resolved = resolveDefaultForCanvas(
37
- node.textContent,
38
- activeTab.value?.doc.document?.state,
39
- );
40
- el.textContent = resolved;
41
- el.style.opacity = "0.7";
42
- el.style.fontStyle = "italic";
43
- el.title = `Bound: ${node.textContent.$ref}`;
35
+ } else if (typeof node.textContent === "object" && node.textContent !== null) {
36
+ const tc = /** @type {Record<string, unknown>} */ (node.textContent);
37
+ if (tc.$ref) {
38
+ const resolved = resolveDefaultForCanvas(tc, activeTab.value?.doc.document?.state);
39
+ el.textContent = /** @type {string | null} */ (resolved);
40
+ el.style.opacity = "0.7";
41
+ el.style.fontStyle = "italic";
42
+ el.title = `Bound: ${tc.$ref}`;
43
+ }
44
44
  }
45
45
 
46
46
  if (node.id) el.id = node.id;
@@ -51,9 +51,13 @@ export function renderCanvasNode(node, path, parent, activeBreakpoints, featureT
51
51
  if (node.attributes && typeof node.attributes === "object") {
52
52
  for (const [attr, val] of Object.entries(node.attributes)) {
53
53
  try {
54
- if (typeof val === "object" && val?.$ref) {
54
+ if (
55
+ typeof val === "object" &&
56
+ val !== null &&
57
+ /** @type {Record<string, unknown>} */ (val).$ref
58
+ ) {
55
59
  const resolved = resolveDefaultForCanvas(val, activeTab.value?.doc.document?.state);
56
- el.setAttribute(attr, resolved);
60
+ el.setAttribute(attr, /** @type {string} */ (resolved));
57
61
  } else {
58
62
  el.setAttribute(attr, val);
59
63
  }
@@ -74,9 +78,10 @@ export function renderCanvasNode(node, path, parent, activeBreakpoints, featureT
74
78
  } else if (
75
79
  node.children &&
76
80
  typeof node.children === "object" &&
77
- node.children.$prototype === "Array"
81
+ /** @type {Record<string, unknown>} */ (node.children).$prototype === "Array"
78
82
  ) {
79
- const template = node.children.map;
83
+ const childrenObj = /** @type {Record<string, unknown>} */ (node.children);
84
+ const template = childrenObj.map;
80
85
  if (template && typeof template === "object") {
81
86
  const wrapper = document.createElement("div");
82
87
  wrapper.className = "repeater-perimeter";