@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
@@ -26,8 +26,11 @@ import {
26
26
  import { componentRegistry, computeRelativePath } from "../files/components.js";
27
27
  import { prepareForEditMode } from "../utils/edit-display.js";
28
28
  import { getActiveElement } from "../editor/inline-edit.js";
29
+ import { buildNestedSiteCSS } from "./nested-site-style.js";
29
30
 
30
- /** @type {any} */
31
+ export { buildNestedSiteCSS } from "./nested-site-style.js";
32
+
33
+ /** @type {{ getCanvasMode: () => string } | null} */
31
34
  let _ctx = null;
32
35
 
33
36
  /** Set of DOM elements that originated from the layout (not page content). */
@@ -44,9 +47,9 @@ let _failedElementsDocPath = null;
44
47
  * the layout slot. Returns the path to the container whose children are the page content (first
45
48
  * non-$__layout children array).
46
49
  *
47
- * @param {any} node
48
- * @param {any[]} [path]
49
- * @returns {any[] | null}
50
+ * @param {JxMutableNode} node
51
+ * @param {(string | number)[]} [path]
52
+ * @returns {(string | number)[] | null}
50
53
  */
51
54
  function findPageContentPrefix(node, path = []) {
52
55
  if (!node || typeof node !== "object") return null;
@@ -75,14 +78,19 @@ export let activeLayoutPath = /** @type {string | null} */ (null);
75
78
  * Recursively mark all nodes in a layout doc tree with $__layout: true so we can identify which
76
79
  * rendered DOM elements came from the layout vs page content.
77
80
  */
78
- function markLayoutNodes(/** @type {any} */ node) {
81
+ function markLayoutNodes(/** @type {JxMutableNode} */ node) {
79
82
  if (!node || typeof node !== "object") return;
80
83
  node.$__layout = true;
81
84
  if (Array.isArray(node.children)) {
82
- for (const child of node.children) markLayoutNodes(child);
85
+ for (const child of node.children) {
86
+ if (typeof child === "string") continue;
87
+ markLayoutNodes(child);
88
+ }
83
89
  }
84
90
  if (node.$elements) {
85
- for (const el of node.$elements) markLayoutNodes(el);
91
+ for (const el of node.$elements) {
92
+ if (typeof el !== "string") markLayoutNodes(el);
93
+ }
86
94
  }
87
95
  }
88
96
 
@@ -103,13 +111,13 @@ export function initCanvasLiveRender(ctx) {
103
111
  * failure.
104
112
  *
105
113
  * @param {number} gen - Render generation for staleness detection
106
- * @param {any} doc
107
- * @param {any} canvasEl
114
+ * @param {JxMutableNode} doc
115
+ * @param {HTMLElement} canvasEl
108
116
  */
109
117
  export async function renderCanvasLive(gen, doc, canvasEl) {
110
118
  const tab = activeTab.value;
111
119
  const S = { documentPath: tab?.documentPath, mode: tab?.doc.mode, document: tab?.doc.document };
112
- const canvasMode = _ctx.getCanvasMode();
120
+ const canvasMode = /** @type {{ getCanvasMode: () => string }} */ (_ctx).getCanvasMode();
113
121
 
114
122
  if (S.documentPath !== _failedElementsDocPath) {
115
123
  _failedElements.clear();
@@ -137,7 +145,7 @@ export async function renderCanvasLive(gen, doc, canvasEl) {
137
145
  projectState?.isSiteProject &&
138
146
  (S.documentPath.startsWith("pages/") || S.documentPath.startsWith("./pages/"));
139
147
 
140
- /** @type {any[] | null} Path prefix in merged doc where page children live */
148
+ /** @type {(string | number)[] | null} Path prefix in merged doc where page children live */
141
149
  let pageContentPrefix = null;
142
150
 
143
151
  if (isPage) {
@@ -162,18 +170,23 @@ export async function renderCanvasLive(gen, doc, canvasEl) {
162
170
  // so we can remap runtime paths (children,0,...) → (children,map,...)
163
171
  const mapParentPaths = new Set();
164
172
  if (canvasMode === "design" || canvasMode === "edit") {
165
- (function findMapParents(/** @type {any} */ node, /** @type {any[]} */ path) {
173
+ (function findMapParents(
174
+ /** @type {JxMutableNode} */ node,
175
+ /** @type {(string | number)[]} */ path,
176
+ ) {
166
177
  if (!node || typeof node !== "object") return;
167
178
  if (
168
179
  node.children &&
169
180
  typeof node.children === "object" &&
170
- node.children.$prototype === "Array"
181
+ /** @type {{ $prototype?: string }} */ (node.children).$prototype === "Array"
171
182
  ) {
172
183
  mapParentPaths.add(path.join("/"));
173
184
  }
174
185
  if (Array.isArray(node.children)) {
175
186
  for (let i = 0; i < node.children.length; i++) {
176
- findMapParents(node.children[i], [...path, "children", i]);
187
+ const child = node.children[i];
188
+ if (typeof child === "string") continue;
189
+ findMapParents(child, [...path, "children", i]);
177
190
  }
178
191
  }
179
192
  if (node.$switch && node.cases) {
@@ -190,15 +203,19 @@ export async function renderCanvasLive(gen, doc, canvasEl) {
190
203
  const docBase = S.documentPath ? `${location.origin}/${docPrefix}${S.documentPath}` : undefined;
191
204
 
192
205
  // Register custom elements so the runtime can render them
193
- let effectiveElements = getEffectiveElements(renderDoc.$elements);
206
+ let effectiveElements = getEffectiveElements(
207
+ /** @type {(JxElement | string)[]} */ (renderDoc.$elements),
208
+ );
194
209
 
195
210
  // In content mode (markdown), auto-discover components for directive-based
196
211
  // custom elements that have no explicit $elements registration.
197
212
  if (S.mode === "content" && componentRegistry.length > 0) {
198
213
  const existingRefs = new Set(
199
- effectiveElements.map((/** @type {any} */ e) => (typeof e === "string" ? e : e?.$ref)),
214
+ effectiveElements.map((/** @type {JxElement | string} */ e) =>
215
+ typeof e === "string" ? e : e?.$ref,
216
+ ),
200
217
  );
201
- /** @param {any} node */
218
+ /** @param {JxMutableNode} node */
202
219
  const collectTags = (node) => {
203
220
  /** @type {Set<string>} */
204
221
  const tags = new Set();
@@ -206,13 +223,16 @@ export async function renderCanvasLive(gen, doc, canvasEl) {
206
223
  if (node.tagName) tags.add(node.tagName);
207
224
  if (Array.isArray(node.children)) {
208
225
  for (const child of node.children) {
226
+ if (typeof child === "string") continue;
209
227
  for (const t of collectTags(child)) tags.add(t);
210
228
  }
211
229
  }
212
230
  return tags;
213
231
  };
214
232
  for (const tag of collectTags(renderDoc)) {
215
- const comp = componentRegistry.find((/** @type {any} */ c) => c.tagName === tag);
233
+ const comp = componentRegistry.find(
234
+ (/** @type {import("../files/components.js").ComponentEntry} */ c) => c.tagName === tag,
235
+ );
216
236
  if (comp && comp.source !== "npm") {
217
237
  const relPath = computeRelativePath(S.documentPath, comp.path);
218
238
  if (!existingRefs.has(relPath)) {
@@ -224,28 +244,30 @@ export async function renderCanvasLive(gen, doc, canvasEl) {
224
244
  }
225
245
 
226
246
  if (effectiveElements.length) {
227
- renderDoc.$elements = effectiveElements;
247
+ renderDoc.$elements = /** @type {(JxMutableNode | string | { $ref: string })[]} */ (
248
+ /** @type {unknown} */ (effectiveElements)
249
+ );
228
250
  for (const entry of effectiveElements) {
229
251
  if (typeof entry === "string") {
230
252
  try {
231
253
  const specifier =
232
254
  entry.startsWith("/") || entry.startsWith(".") ? entry : `/node_modules/${entry}`;
233
255
  await import(specifier);
234
- } catch (/** @type {any} */ e) {
256
+ } catch (/** @type {unknown} */ e) {
235
257
  console.warn("Studio: failed to import package", entry, e);
236
258
  }
237
259
  } else if (entry?.$ref) {
238
260
  let href;
239
261
  try {
240
262
  href = new URL(entry.$ref, docBase).href;
241
- } catch (/** @type {any} */ urlErr) {
263
+ } catch (/** @type {unknown} */ urlErr) {
242
264
  console.warn("Studio: invalid element URL", { ref: entry.$ref, docBase }, urlErr);
243
265
  continue;
244
266
  }
245
267
  if (_failedElements.has(href)) continue;
246
268
  try {
247
269
  await defineElement(href);
248
- } catch (/** @type {any} */ e) {
270
+ } catch (/** @type {unknown} */ e) {
249
271
  _failedElements.add(href);
250
272
  console.warn("Studio: failed to register element", entry.$ref, e);
251
273
  }
@@ -265,17 +287,22 @@ export async function renderCanvasLive(gen, doc, canvasEl) {
265
287
  // This ensures project font-family, color, etc. override the
266
288
  // content-mode fallback typography rules in the stylesheet.
267
289
  // 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;
290
+ const viewport = /** @type {HTMLElement | null} */ (canvasEl.closest(".canvas-panel-viewport"));
291
+ const editSurface =
292
+ canvasMode === "edit"
293
+ ? /** @type {HTMLElement | null} */ (canvasEl.closest(".content-edit-canvas"))
294
+ : null;
270
295
  const siteStyle = projectState?.projectConfig?.style;
271
296
  if (viewport) {
272
297
  viewport.style.cssText = "";
273
298
  if (siteStyle && typeof siteStyle === "object") {
274
299
  for (const [k, v] of Object.entries(siteStyle)) {
300
+ if (v !== null && typeof v === "object" && !Array.isArray(v)) continue;
275
301
  if (k.startsWith("--")) {
276
302
  viewport.style.setProperty(k, String(v));
277
303
  } else {
278
- /** @type {any} */ (viewport.style)[k] = v;
304
+ /** @type {Record<string, string>} */ (/** @type {unknown} */ (viewport.style))[k] =
305
+ String(v);
279
306
  }
280
307
  }
281
308
  }
@@ -283,22 +310,42 @@ export async function renderCanvasLive(gen, doc, canvasEl) {
283
310
  if (editSurface) {
284
311
  if (siteStyle && typeof siteStyle === "object") {
285
312
  for (const [k, v] of Object.entries(siteStyle)) {
313
+ if (v !== null && typeof v === "object" && !Array.isArray(v)) continue;
286
314
  if (k.startsWith("--")) {
287
- /** @type {any} */ (editSurface).style.setProperty(k, String(v));
315
+ editSurface.style.setProperty(k, String(v));
288
316
  } else {
289
- /** @type {any} */ (editSurface.style)[k] = v;
317
+ /** @type {Record<string, string>} */ (/** @type {unknown} */ (editSurface.style))[k] =
318
+ String(v);
290
319
  }
291
320
  }
292
321
  }
293
322
  }
294
323
  if (siteStyle && typeof siteStyle === "object") {
295
324
  for (const [k, v] of Object.entries(siteStyle)) {
325
+ if (v !== null && typeof v === "object" && !Array.isArray(v)) continue;
296
326
  if (!k.startsWith("--")) {
297
- /** @type {any} */ (canvasEl.style)[k] = v;
327
+ /** @type {Record<string, string>} */ (/** @type {unknown} */ (canvasEl.style))[k] =
328
+ String(v);
298
329
  }
299
330
  }
300
331
  }
301
332
 
333
+ // Generate a <style> tag for nested selector rules (e.g. table, thead, etc.)
334
+ if (siteStyle && typeof siteStyle === "object") {
335
+ const scopeAttr = `data-jx-site`;
336
+ canvasEl.setAttribute(scopeAttr, "");
337
+ const css = buildNestedSiteCSS(siteStyle, `[${scopeAttr}]`);
338
+
339
+ if (css) {
340
+ const existingStyleEl = document.getElementById("jx-site-style");
341
+ if (existingStyleEl) existingStyleEl.remove();
342
+ const styleEl = document.createElement("style");
343
+ styleEl.id = "jx-site-style";
344
+ styleEl.textContent = css;
345
+ document.head.appendChild(styleEl);
346
+ }
347
+ }
348
+
302
349
  // Inject site-level $media so runtime can resolve media queries in styles
303
350
  renderDoc.$media = getEffectiveMedia(renderDoc.$media);
304
351
 
@@ -310,13 +357,14 @@ export async function renderCanvasLive(gen, doc, canvasEl) {
310
357
  const tag = entry.tagName.toLowerCase();
311
358
  const attrs = { ...entry.attributes };
312
359
  for (const key of ["href", "src"]) {
360
+ const val = attrs[key];
313
361
  if (
314
- attrs[key] &&
315
- !attrs[key].startsWith("/") &&
316
- !attrs[key].startsWith(".") &&
317
- !attrs[key].startsWith("http")
362
+ typeof val === "string" &&
363
+ !val.startsWith("/") &&
364
+ !val.startsWith(".") &&
365
+ !val.startsWith("http")
318
366
  ) {
319
- attrs[key] = `/node_modules/${attrs[key]}`;
367
+ attrs[key] = `/node_modules/${val}`;
320
368
  }
321
369
  }
322
370
  const selector = `${tag}${attrs.href ? `[href="${attrs.href}"]` : ""}${attrs.src ? `[src="${attrs.src}"]` : ""}`;
@@ -333,7 +381,12 @@ export async function renderCanvasLive(gen, doc, canvasEl) {
333
381
  if (gen !== view.renderGeneration) return null;
334
382
  const el = /** @type {HTMLElement} */ (
335
383
  runtimeRenderNode(renderDoc, $defs, {
336
- onNodeCreated(/** @type {any} */ el, /** @type {any} */ path, /** @type {any} */ def) {
384
+ onNodeCreated(
385
+ /** @type {HTMLElement | Text} */ el,
386
+ /** @type {(string | number)[]} */ path,
387
+ /** @type {Record<string, unknown>} */ def,
388
+ ) {
389
+ if (!(el instanceof HTMLElement)) return;
337
390
  // Track layout-originated elements — don't store in elToPath to avoid
338
391
  // path collisions with remapped page content paths
339
392
  if (layoutWrapped && def?.$__layout) {
@@ -349,7 +402,9 @@ export async function renderCanvasLive(gen, doc, canvasEl) {
349
402
  const pfx = pageContentPrefix;
350
403
  if (
351
404
  path.length >= pfx.length &&
352
- pfx.every((/** @type {any} */ seg, /** @type {number} */ i) => path[i] === seg)
405
+ pfx.every(
406
+ (/** @type {string | number} */ seg, /** @type {number} */ i) => path[i] === seg,
407
+ )
353
408
  ) {
354
409
  mappedPath = ["children", ...path.slice(pfx.length)];
355
410
  }
@@ -391,7 +446,7 @@ export async function renderCanvasLive(gen, doc, canvasEl) {
391
446
  // Disable pointer events on all rendered elements for edit mode
392
447
  el.style.pointerEvents = "none";
393
448
  for (const child of el.querySelectorAll("*")) {
394
- /** @type {any} */ (child).style.pointerEvents = "none";
449
+ /** @type {HTMLElement} */ (child).style.pointerEvents = "none";
395
450
  }
396
451
  }
397
452
  // Clear and append atomically — ensures the canvas is never left empty if a
@@ -411,13 +466,13 @@ export async function renderCanvasLive(gen, doc, canvasEl) {
411
466
  for (const child of canvasEl.querySelectorAll("*")) {
412
467
  if (view.componentInlineEdit && child === view.componentInlineEdit.el) continue;
413
468
  if (editingEl && child === editingEl) continue;
414
- /** @type {any} */ (child).style.pointerEvents = "none";
469
+ /** @type {HTMLElement} */ (child).style.pointerEvents = "none";
415
470
  }
416
471
  });
417
472
  }
418
473
  return $defs;
419
- } catch (/** @type {any} */ err) {
420
- console.warn("renderCanvasLive failed:", err.message, err);
474
+ } catch (/** @type {unknown} */ err) {
475
+ console.warn("renderCanvasLive failed:", /** @type {Error} */ (err).message, err);
421
476
  return null;
422
477
  }
423
478
  }