@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
@@ -13,6 +13,7 @@ import {
13
13
  buildScope,
14
14
  defineElement,
15
15
  setSkipServerFunctions,
16
+ setSkipContentResolution,
16
17
  } from "@jxsuite/runtime";
17
18
  import {
18
19
  getEffectiveElements,
@@ -26,8 +27,21 @@ import {
26
27
  import { componentRegistry, computeRelativePath } from "../files/components.js";
27
28
  import { prepareForEditMode } from "../utils/edit-display.js";
28
29
  import { getActiveElement } from "../editor/inline-edit.js";
30
+ import { buildNestedSiteCSS } from "./nested-site-style.js";
29
31
 
30
- /** @type {any} */
32
+ export { buildNestedSiteCSS } from "./nested-site-style.js";
33
+
34
+ /** @param {Event} e */
35
+ function _preventNav(e) {
36
+ if (/** @type {HTMLElement} */ (e.target).closest("a[href]")) {
37
+ e.preventDefault();
38
+ }
39
+ }
40
+
41
+ /** Canvas elements that already have the delegated nav guard listener. */
42
+ const _navGuarded = new WeakSet();
43
+
44
+ /** @type {{ getCanvasMode: () => string } | null} */
31
45
  let _ctx = null;
32
46
 
33
47
  /** Set of DOM elements that originated from the layout (not page content). */
@@ -44,9 +58,9 @@ let _failedElementsDocPath = null;
44
58
  * the layout slot. Returns the path to the container whose children are the page content (first
45
59
  * non-$__layout children array).
46
60
  *
47
- * @param {any} node
48
- * @param {any[]} [path]
49
- * @returns {any[] | null}
61
+ * @param {JxMutableNode} node
62
+ * @param {(string | number)[]} [path]
63
+ * @returns {(string | number)[] | null}
50
64
  */
51
65
  function findPageContentPrefix(node, path = []) {
52
66
  if (!node || typeof node !== "object") return null;
@@ -75,14 +89,19 @@ export let activeLayoutPath = /** @type {string | null} */ (null);
75
89
  * Recursively mark all nodes in a layout doc tree with $__layout: true so we can identify which
76
90
  * rendered DOM elements came from the layout vs page content.
77
91
  */
78
- function markLayoutNodes(/** @type {any} */ node) {
92
+ function markLayoutNodes(/** @type {JxMutableNode} */ node) {
79
93
  if (!node || typeof node !== "object") return;
80
94
  node.$__layout = true;
81
95
  if (Array.isArray(node.children)) {
82
- for (const child of node.children) markLayoutNodes(child);
96
+ for (const child of node.children) {
97
+ if (typeof child === "string") continue;
98
+ markLayoutNodes(child);
99
+ }
83
100
  }
84
101
  if (node.$elements) {
85
- for (const el of node.$elements) markLayoutNodes(el);
102
+ for (const el of node.$elements) {
103
+ if (typeof el !== "string") markLayoutNodes(el);
104
+ }
86
105
  }
87
106
  }
88
107
 
@@ -103,13 +122,13 @@ export function initCanvasLiveRender(ctx) {
103
122
  * failure.
104
123
  *
105
124
  * @param {number} gen - Render generation for staleness detection
106
- * @param {any} doc
107
- * @param {any} canvasEl
125
+ * @param {JxMutableNode} doc
126
+ * @param {HTMLElement} canvasEl
108
127
  */
109
128
  export async function renderCanvasLive(gen, doc, canvasEl) {
110
129
  const tab = activeTab.value;
111
130
  const S = { documentPath: tab?.documentPath, mode: tab?.doc.mode, document: tab?.doc.document };
112
- const canvasMode = _ctx.getCanvasMode();
131
+ const canvasMode = /** @type {{ getCanvasMode: () => string }} */ (_ctx).getCanvasMode();
113
132
 
114
133
  if (S.documentPath !== _failedElementsDocPath) {
115
134
  _failedElements.clear();
@@ -120,6 +139,7 @@ export async function renderCanvasLive(gen, doc, canvasEl) {
120
139
  // failed proxy calls and infinite reactive retries (also covers
121
140
  // async custom element connectedCallbacks that run after this function returns)
122
141
  setSkipServerFunctions(canvasMode !== "preview");
142
+ setSkipContentResolution(canvasMode !== "preview");
123
143
 
124
144
  let renderDoc =
125
145
  canvasMode === "preview"
@@ -137,7 +157,7 @@ export async function renderCanvasLive(gen, doc, canvasEl) {
137
157
  projectState?.isSiteProject &&
138
158
  (S.documentPath.startsWith("pages/") || S.documentPath.startsWith("./pages/"));
139
159
 
140
- /** @type {any[] | null} Path prefix in merged doc where page children live */
160
+ /** @type {(string | number)[] | null} Path prefix in merged doc where page children live */
141
161
  let pageContentPrefix = null;
142
162
 
143
163
  if (isPage) {
@@ -162,18 +182,23 @@ export async function renderCanvasLive(gen, doc, canvasEl) {
162
182
  // so we can remap runtime paths (children,0,...) → (children,map,...)
163
183
  const mapParentPaths = new Set();
164
184
  if (canvasMode === "design" || canvasMode === "edit") {
165
- (function findMapParents(/** @type {any} */ node, /** @type {any[]} */ path) {
185
+ (function findMapParents(
186
+ /** @type {JxMutableNode} */ node,
187
+ /** @type {(string | number)[]} */ path,
188
+ ) {
166
189
  if (!node || typeof node !== "object") return;
167
190
  if (
168
191
  node.children &&
169
192
  typeof node.children === "object" &&
170
- node.children.$prototype === "Array"
193
+ /** @type {{ $prototype?: string }} */ (node.children).$prototype === "Array"
171
194
  ) {
172
195
  mapParentPaths.add(path.join("/"));
173
196
  }
174
197
  if (Array.isArray(node.children)) {
175
198
  for (let i = 0; i < node.children.length; i++) {
176
- findMapParents(node.children[i], [...path, "children", i]);
199
+ const child = node.children[i];
200
+ if (typeof child === "string") continue;
201
+ findMapParents(child, [...path, "children", i]);
177
202
  }
178
203
  }
179
204
  if (node.$switch && node.cases) {
@@ -190,15 +215,19 @@ export async function renderCanvasLive(gen, doc, canvasEl) {
190
215
  const docBase = S.documentPath ? `${location.origin}/${docPrefix}${S.documentPath}` : undefined;
191
216
 
192
217
  // Register custom elements so the runtime can render them
193
- let effectiveElements = getEffectiveElements(renderDoc.$elements);
218
+ let effectiveElements = getEffectiveElements(
219
+ /** @type {(JxElement | string)[]} */ (renderDoc.$elements),
220
+ );
194
221
 
195
222
  // In content mode (markdown), auto-discover components for directive-based
196
223
  // custom elements that have no explicit $elements registration.
197
224
  if (S.mode === "content" && componentRegistry.length > 0) {
198
225
  const existingRefs = new Set(
199
- effectiveElements.map((/** @type {any} */ e) => (typeof e === "string" ? e : e?.$ref)),
226
+ effectiveElements.map((/** @type {JxElement | string} */ e) =>
227
+ typeof e === "string" ? e : e?.$ref,
228
+ ),
200
229
  );
201
- /** @param {any} node */
230
+ /** @param {JxMutableNode} node */
202
231
  const collectTags = (node) => {
203
232
  /** @type {Set<string>} */
204
233
  const tags = new Set();
@@ -206,13 +235,16 @@ export async function renderCanvasLive(gen, doc, canvasEl) {
206
235
  if (node.tagName) tags.add(node.tagName);
207
236
  if (Array.isArray(node.children)) {
208
237
  for (const child of node.children) {
238
+ if (typeof child === "string") continue;
209
239
  for (const t of collectTags(child)) tags.add(t);
210
240
  }
211
241
  }
212
242
  return tags;
213
243
  };
214
244
  for (const tag of collectTags(renderDoc)) {
215
- const comp = componentRegistry.find((/** @type {any} */ c) => c.tagName === tag);
245
+ const comp = componentRegistry.find(
246
+ (/** @type {import("../files/components.js").ComponentEntry} */ c) => c.tagName === tag,
247
+ );
216
248
  if (comp && comp.source !== "npm") {
217
249
  const relPath = computeRelativePath(S.documentPath, comp.path);
218
250
  if (!existingRefs.has(relPath)) {
@@ -224,28 +256,30 @@ export async function renderCanvasLive(gen, doc, canvasEl) {
224
256
  }
225
257
 
226
258
  if (effectiveElements.length) {
227
- renderDoc.$elements = effectiveElements;
259
+ renderDoc.$elements = /** @type {(JxMutableNode | string | { $ref: string })[]} */ (
260
+ /** @type {unknown} */ (effectiveElements)
261
+ );
228
262
  for (const entry of effectiveElements) {
229
263
  if (typeof entry === "string") {
230
264
  try {
231
265
  const specifier =
232
266
  entry.startsWith("/") || entry.startsWith(".") ? entry : `/node_modules/${entry}`;
233
267
  await import(specifier);
234
- } catch (/** @type {any} */ e) {
268
+ } catch (/** @type {unknown} */ e) {
235
269
  console.warn("Studio: failed to import package", entry, e);
236
270
  }
237
271
  } else if (entry?.$ref) {
238
272
  let href;
239
273
  try {
240
274
  href = new URL(entry.$ref, docBase).href;
241
- } catch (/** @type {any} */ urlErr) {
275
+ } catch (/** @type {unknown} */ urlErr) {
242
276
  console.warn("Studio: invalid element URL", { ref: entry.$ref, docBase }, urlErr);
243
277
  continue;
244
278
  }
245
279
  if (_failedElements.has(href)) continue;
246
280
  try {
247
281
  await defineElement(href);
248
- } catch (/** @type {any} */ e) {
282
+ } catch (/** @type {unknown} */ e) {
249
283
  _failedElements.add(href);
250
284
  console.warn("Studio: failed to register element", entry.$ref, e);
251
285
  }
@@ -265,17 +299,22 @@ export async function renderCanvasLive(gen, doc, canvasEl) {
265
299
  // This ensures project font-family, color, etc. override the
266
300
  // content-mode fallback typography rules in the stylesheet.
267
301
  // In edit mode, propagate to the .content-edit-canvas wrapper for seamless appearance.
268
- const viewport = canvasEl.closest(".canvas-panel-viewport");
269
- const editSurface = canvasMode === "edit" ? canvasEl.closest(".content-edit-canvas") : null;
302
+ const viewport = /** @type {HTMLElement | null} */ (canvasEl.closest(".canvas-panel-viewport"));
303
+ const editSurface =
304
+ canvasMode === "edit"
305
+ ? /** @type {HTMLElement | null} */ (canvasEl.closest(".content-edit-canvas"))
306
+ : null;
270
307
  const siteStyle = projectState?.projectConfig?.style;
271
308
  if (viewport) {
272
309
  viewport.style.cssText = "";
273
310
  if (siteStyle && typeof siteStyle === "object") {
274
311
  for (const [k, v] of Object.entries(siteStyle)) {
312
+ if (v !== null && typeof v === "object" && !Array.isArray(v)) continue;
275
313
  if (k.startsWith("--")) {
276
314
  viewport.style.setProperty(k, String(v));
277
315
  } else {
278
- /** @type {any} */ (viewport.style)[k] = v;
316
+ /** @type {Record<string, string>} */ (/** @type {unknown} */ (viewport.style))[k] =
317
+ String(v);
279
318
  }
280
319
  }
281
320
  }
@@ -283,22 +322,42 @@ export async function renderCanvasLive(gen, doc, canvasEl) {
283
322
  if (editSurface) {
284
323
  if (siteStyle && typeof siteStyle === "object") {
285
324
  for (const [k, v] of Object.entries(siteStyle)) {
325
+ if (v !== null && typeof v === "object" && !Array.isArray(v)) continue;
286
326
  if (k.startsWith("--")) {
287
- /** @type {any} */ (editSurface).style.setProperty(k, String(v));
327
+ editSurface.style.setProperty(k, String(v));
288
328
  } else {
289
- /** @type {any} */ (editSurface.style)[k] = v;
329
+ /** @type {Record<string, string>} */ (/** @type {unknown} */ (editSurface.style))[k] =
330
+ String(v);
290
331
  }
291
332
  }
292
333
  }
293
334
  }
294
335
  if (siteStyle && typeof siteStyle === "object") {
295
336
  for (const [k, v] of Object.entries(siteStyle)) {
337
+ if (v !== null && typeof v === "object" && !Array.isArray(v)) continue;
296
338
  if (!k.startsWith("--")) {
297
- /** @type {any} */ (canvasEl.style)[k] = v;
339
+ /** @type {Record<string, string>} */ (/** @type {unknown} */ (canvasEl.style))[k] =
340
+ String(v);
298
341
  }
299
342
  }
300
343
  }
301
344
 
345
+ // Generate a <style> tag for nested selector rules (e.g. table, thead, etc.)
346
+ if (siteStyle && typeof siteStyle === "object") {
347
+ const scopeAttr = `data-jx-site`;
348
+ canvasEl.setAttribute(scopeAttr, "");
349
+ const css = buildNestedSiteCSS(siteStyle, `[${scopeAttr}]`);
350
+
351
+ if (css) {
352
+ const existingStyleEl = document.getElementById("jx-site-style");
353
+ if (existingStyleEl) existingStyleEl.remove();
354
+ const styleEl = document.createElement("style");
355
+ styleEl.id = "jx-site-style";
356
+ styleEl.textContent = css;
357
+ document.head.appendChild(styleEl);
358
+ }
359
+ }
360
+
302
361
  // Inject site-level $media so runtime can resolve media queries in styles
303
362
  renderDoc.$media = getEffectiveMedia(renderDoc.$media);
304
363
 
@@ -310,13 +369,14 @@ export async function renderCanvasLive(gen, doc, canvasEl) {
310
369
  const tag = entry.tagName.toLowerCase();
311
370
  const attrs = { ...entry.attributes };
312
371
  for (const key of ["href", "src"]) {
372
+ const val = attrs[key];
313
373
  if (
314
- attrs[key] &&
315
- !attrs[key].startsWith("/") &&
316
- !attrs[key].startsWith(".") &&
317
- !attrs[key].startsWith("http")
374
+ typeof val === "string" &&
375
+ !val.startsWith("/") &&
376
+ !val.startsWith(".") &&
377
+ !val.startsWith("http")
318
378
  ) {
319
- attrs[key] = `/node_modules/${attrs[key]}`;
379
+ attrs[key] = `/node_modules/${val}`;
320
380
  }
321
381
  }
322
382
  const selector = `${tag}${attrs.href ? `[href="${attrs.href}"]` : ""}${attrs.src ? `[src="${attrs.src}"]` : ""}`;
@@ -333,7 +393,12 @@ export async function renderCanvasLive(gen, doc, canvasEl) {
333
393
  if (gen !== view.renderGeneration) return null;
334
394
  const el = /** @type {HTMLElement} */ (
335
395
  runtimeRenderNode(renderDoc, $defs, {
336
- onNodeCreated(/** @type {any} */ el, /** @type {any} */ path, /** @type {any} */ def) {
396
+ onNodeCreated(
397
+ /** @type {HTMLElement | Text} */ el,
398
+ /** @type {(string | number)[]} */ path,
399
+ /** @type {Record<string, unknown>} */ def,
400
+ ) {
401
+ if (!(el instanceof HTMLElement)) return;
337
402
  // Track layout-originated elements — don't store in elToPath to avoid
338
403
  // path collisions with remapped page content paths
339
404
  if (layoutWrapped && def?.$__layout) {
@@ -349,7 +414,9 @@ export async function renderCanvasLive(gen, doc, canvasEl) {
349
414
  const pfx = pageContentPrefix;
350
415
  if (
351
416
  path.length >= pfx.length &&
352
- pfx.every((/** @type {any} */ seg, /** @type {number} */ i) => path[i] === seg)
417
+ pfx.every(
418
+ (/** @type {string | number} */ seg, /** @type {number} */ i) => path[i] === seg,
419
+ )
353
420
  ) {
354
421
  mappedPath = ["children", ...path.slice(pfx.length)];
355
422
  }
@@ -391,7 +458,7 @@ export async function renderCanvasLive(gen, doc, canvasEl) {
391
458
  // Disable pointer events on all rendered elements for edit mode
392
459
  el.style.pointerEvents = "none";
393
460
  for (const child of el.querySelectorAll("*")) {
394
- /** @type {any} */ (child).style.pointerEvents = "none";
461
+ /** @type {HTMLElement} */ (child).style.pointerEvents = "none";
395
462
  }
396
463
  }
397
464
  // Clear and append atomically — ensures the canvas is never left empty if a
@@ -405,19 +472,26 @@ export async function renderCanvasLive(gen, doc, canvasEl) {
405
472
 
406
473
  canvasEl.appendChild(el);
407
474
 
475
+ // Delegated click handler prevents link navigation in all canvas modes.
476
+ // Attached once per canvasEl (survives reactive re-renders that replace children).
477
+ if (!_navGuarded.has(canvasEl)) {
478
+ canvasEl.addEventListener("click", _preventNav);
479
+ _navGuarded.add(canvasEl);
480
+ }
481
+
408
482
  if (canvasMode === "design" || canvasMode === "edit") {
409
483
  requestAnimationFrame(() => {
410
484
  const editingEl = getActiveElement();
411
485
  for (const child of canvasEl.querySelectorAll("*")) {
412
486
  if (view.componentInlineEdit && child === view.componentInlineEdit.el) continue;
413
487
  if (editingEl && child === editingEl) continue;
414
- /** @type {any} */ (child).style.pointerEvents = "none";
488
+ /** @type {HTMLElement} */ (child).style.pointerEvents = "none";
415
489
  }
416
490
  });
417
491
  }
418
492
  return $defs;
419
- } catch (/** @type {any} */ err) {
420
- console.warn("renderCanvasLive failed:", err.message, err);
493
+ } catch (/** @type {unknown} */ err) {
494
+ console.warn("renderCanvasLive failed:", /** @type {Error} */ (err).message, err);
421
495
  return null;
422
496
  }
423
497
  }