@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.
- package/dist/studio.css +98 -98
- package/dist/studio.js +12577 -12183
- package/dist/studio.js.map +146 -144
- package/dist/workers/editor.worker.js +79 -79
- package/dist/workers/json.worker.js +109 -109
- package/dist/workers/ts.worker.js +82 -82
- package/package.json +17 -6
- package/src/browse/browse.js +25 -19
- package/src/canvas/canvas-diff.js +24 -16
- package/src/canvas/canvas-helpers.js +24 -19
- package/src/canvas/canvas-live-render.js +94 -39
- package/src/canvas/canvas-render.js +119 -73
- package/src/canvas/canvas-utils.js +38 -15
- package/src/canvas/nested-site-style.js +50 -0
- package/src/editor/component-inline-edit.js +14 -13
- package/src/editor/content-inline-edit.js +22 -10
- package/src/editor/context-menu.js +1 -1
- package/src/editor/convert-to-component.js +12 -10
- package/src/editor/inline-edit.js +66 -28
- package/src/editor/insertion-helper.js +16 -12
- package/src/editor/shortcuts.js +4 -2
- package/src/editor/slash-menu.js +6 -4
- package/src/files/components.js +1 -1
- package/src/files/file-ops.js +37 -16
- package/src/files/files.js +78 -52
- package/src/github/github-auth.js +122 -0
- package/src/github/github-publish.js +115 -0
- package/src/markdown/md-convert.js +172 -107
- package/src/new-project/new-project-modal.js +204 -0
- package/src/panels/activity-bar.js +22 -20
- package/src/panels/ai-panel.js +399 -0
- package/src/panels/block-action-bar.js +5 -5
- package/src/panels/canvas-dnd.js +8 -2
- package/src/panels/data-explorer.js +19 -13
- package/src/panels/dnd.js +144 -92
- package/src/panels/editors.js +55 -35
- package/src/panels/elements-panel.js +33 -19
- package/src/panels/events-panel.js +15 -10
- package/src/panels/git-panel.js +469 -98
- package/src/panels/head-panel.js +121 -81
- package/src/panels/imports-panel.js +88 -64
- package/src/panels/layers-panel.js +64 -43
- package/src/panels/left-panel.js +85 -47
- package/src/panels/overlays.js +1 -0
- package/src/panels/panel-events.js +45 -30
- package/src/panels/preview-render.js +23 -18
- package/src/panels/properties-panel.js +365 -249
- package/src/panels/pseudo-preview.js +2 -2
- package/src/panels/quick-search.js +7 -5
- package/src/panels/right-panel.js +26 -2
- package/src/panels/shared.js +4 -4
- package/src/panels/signals-panel.js +295 -166
- package/src/panels/statusbar.js +4 -4
- package/src/panels/style-inputs.js +67 -35
- package/src/panels/style-panel.js +233 -132
- package/src/panels/style-utils.js +34 -16
- package/src/panels/stylebook-layers-panel.js +12 -10
- package/src/panels/stylebook-panel.js +134 -66
- package/src/panels/toolbar.js +333 -109
- package/src/panels/welcome-screen.js +121 -0
- package/src/platform.js +2 -4
- package/src/platforms/devserver.js +113 -7
- package/src/resize-edges.js +98 -0
- package/src/services/cem-export.js +12 -7
- package/src/services/code-services.js +30 -12
- package/src/settings/content-types-editor.js +7 -7
- package/src/settings/css-vars-editor.js +30 -24
- package/src/settings/defs-editor.js +12 -7
- package/src/settings/general-settings.js +85 -3
- package/src/settings/head-editor.js +30 -24
- package/src/settings/schema-field-ui.js +58 -39
- package/src/site-context.js +41 -31
- package/src/state.js +70 -29
- package/src/store.js +21 -26
- package/src/studio.js +103 -81
- package/src/tabs/tab.js +67 -43
- package/src/tabs/transact.js +23 -13
- package/src/ui/button-group.js +24 -24
- package/src/ui/color-selector.js +28 -23
- package/src/ui/field-row.js +3 -3
- package/src/ui/icons.js +2 -2
- package/src/ui/media-picker.js +7 -7
- package/src/ui/spectrum.js +5 -1
- package/src/ui/unit-selector.js +18 -16
- package/src/ui/value-selector.js +21 -15
- package/src/ui/widgets.js +22 -19
- package/src/utils/canvas-media.js +5 -4
- package/src/utils/edit-display.js +31 -20
- package/src/utils/google-fonts.js +11 -11
- package/src/utils/inherited-style.js +9 -8
- package/src/utils/studio-utils.js +7 -7
- package/src/view.js +58 -1
- 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
|
-
|
|
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 {
|
|
48
|
-
* @param {
|
|
49
|
-
* @returns {
|
|
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 {
|
|
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)
|
|
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)
|
|
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 {
|
|
107
|
-
* @param {
|
|
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 {
|
|
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(
|
|
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
|
-
|
|
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(
|
|
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 {
|
|
214
|
+
effectiveElements.map((/** @type {JxElement | string} */ e) =>
|
|
215
|
+
typeof e === "string" ? e : e?.$ref,
|
|
216
|
+
),
|
|
200
217
|
);
|
|
201
|
-
/** @param {
|
|
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(
|
|
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 =
|
|
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 {
|
|
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 {
|
|
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 {
|
|
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 =
|
|
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 {
|
|
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
|
-
|
|
315
|
+
editSurface.style.setProperty(k, String(v));
|
|
288
316
|
} else {
|
|
289
|
-
/** @type {
|
|
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 {
|
|
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
|
-
|
|
315
|
-
!
|
|
316
|
-
!
|
|
317
|
-
!
|
|
362
|
+
typeof val === "string" &&
|
|
363
|
+
!val.startsWith("/") &&
|
|
364
|
+
!val.startsWith(".") &&
|
|
365
|
+
!val.startsWith("http")
|
|
318
366
|
) {
|
|
319
|
-
attrs[key] = `/node_modules/${
|
|
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(
|
|
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(
|
|
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 {
|
|
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 {
|
|
469
|
+
/** @type {HTMLElement} */ (child).style.pointerEvents = "none";
|
|
415
470
|
}
|
|
416
471
|
});
|
|
417
472
|
}
|
|
418
473
|
return $defs;
|
|
419
|
-
} catch (/** @type {
|
|
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
|
}
|