@jxsuite/studio 0.28.2 → 0.28.4
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.js +86144 -83971
- package/dist/studio.js.map +229 -219
- package/package.json +5 -5
- package/src/browse/browse-modal.ts +9 -3
- package/src/browse/browse.ts +139 -82
- package/src/canvas/canvas-diff.ts +35 -18
- package/src/canvas/canvas-helpers.ts +55 -21
- package/src/canvas/canvas-live-render.ts +140 -89
- package/src/canvas/canvas-render.ts +127 -82
- package/src/canvas/canvas-utils.ts +68 -42
- package/src/canvas/nested-site-style.ts +15 -7
- package/src/editor/component-inline-edit.ts +90 -51
- package/src/editor/content-inline-edit.ts +75 -72
- package/src/editor/context-menu.ts +125 -71
- package/src/editor/convert-targets.ts +17 -13
- package/src/editor/convert-to-component.ts +51 -28
- package/src/editor/convert-to-repeater.ts +44 -19
- package/src/editor/inline-edit.ts +107 -52
- package/src/editor/inline-format.ts +128 -60
- package/src/editor/insertion-helper.ts +26 -14
- package/src/editor/shortcuts.ts +89 -43
- package/src/editor/slash-menu.ts +41 -26
- package/src/files/components.ts +9 -7
- package/src/files/file-ops.ts +53 -33
- package/src/files/files.ts +246 -143
- package/src/format/constraints.ts +25 -15
- package/src/format/format-host.ts +33 -10
- package/src/github/github-auth.ts +24 -14
- package/src/github/github-publish.ts +20 -14
- package/src/new-project/new-project-modal.ts +27 -18
- package/src/panels/activity-bar.ts +27 -26
- package/src/panels/ai-panel.ts +100 -44
- package/src/panels/block-action-bar.ts +84 -52
- package/src/panels/canvas-dnd.ts +47 -28
- package/src/panels/data-explorer.ts +24 -11
- package/src/panels/dnd.ts +148 -100
- package/src/panels/editors.ts +54 -37
- package/src/panels/elements-panel.ts +23 -13
- package/src/panels/events-panel.ts +48 -38
- package/src/panels/git-panel.ts +95 -60
- package/src/panels/head-panel.ts +138 -84
- package/src/panels/imports-panel.ts +66 -29
- package/src/panels/layers-panel.ts +80 -36
- package/src/panels/left-panel.ts +86 -53
- package/src/panels/overlays.ts +36 -19
- package/src/panels/panel-events.ts +48 -27
- package/src/panels/panel-scheduler.ts +41 -23
- package/src/panels/preview-render.ts +26 -26
- package/src/panels/properties-panel.ts +155 -118
- package/src/panels/pseudo-preview.ts +31 -15
- package/src/panels/quick-search.ts +28 -13
- package/src/panels/right-panel.ts +35 -24
- package/src/panels/shared.ts +46 -29
- package/src/panels/signals-panel.ts +303 -186
- package/src/panels/statusbar.ts +27 -12
- package/src/panels/style-inputs.ts +30 -24
- package/src/panels/style-panel.ts +130 -90
- package/src/panels/style-utils.ts +63 -23
- package/src/panels/stylebook-layers-panel.ts +27 -30
- package/src/panels/stylebook-panel.ts +140 -72
- package/src/panels/tab-strip.ts +21 -10
- package/src/panels/toolbar.ts +52 -40
- package/src/panels/welcome-screen.ts +1 -1
- package/src/platform.ts +2 -1
- package/src/platforms/devserver.ts +221 -105
- package/src/recent-projects.ts +15 -7
- package/src/resize-edges.ts +7 -5
- package/src/services/cem-export.ts +73 -57
- package/src/services/code-services.ts +52 -25
- package/src/services/monaco-setup.ts +5 -5
- package/src/settings/content-types-editor.ts +133 -62
- package/src/settings/css-vars-editor.ts +31 -14
- package/src/settings/defs-editor.ts +125 -63
- package/src/settings/general-settings.ts +11 -6
- package/src/settings/head-editor.ts +30 -15
- package/src/settings/schema-field-ui.ts +75 -35
- package/src/settings/settings-modal.ts +35 -17
- package/src/site-context.ts +80 -47
- package/src/state.ts +98 -65
- package/src/store.ts +42 -23
- package/src/studio.ts +165 -144
- package/src/tabs/tab.ts +51 -45
- package/src/tabs/transact.ts +230 -109
- package/src/types.ts +60 -51
- package/src/ui/button-group.ts +3 -2
- package/src/ui/color-selector.ts +33 -15
- package/src/ui/expression-editor.ts +139 -86
- package/src/ui/field-input.ts +35 -18
- package/src/ui/field-row.ts +1 -1
- package/src/ui/icons.ts +5 -6
- package/src/ui/layers.ts +33 -26
- package/src/ui/media-picker.ts +13 -7
- package/src/ui/panel-resize.ts +39 -20
- package/src/ui/spectrum.ts +4 -2
- package/src/ui/unit-selector.ts +20 -11
- package/src/ui/value-selector.ts +13 -7
- package/src/ui/widgets.ts +34 -18
- package/src/utils/canvas-media.ts +66 -33
- package/src/utils/edit-display.ts +43 -44
- package/src/utils/google-fonts.ts +10 -6
- package/src/utils/inherited-style.ts +22 -8
- package/src/utils/studio-utils.ts +88 -45
- package/src/view.ts +9 -5
- package/src/workspace/workspace.ts +49 -29
|
@@ -5,11 +5,12 @@
|
|
|
5
5
|
* multiple canvas-related modules: element lookup, zoom, panel resolution, inline bubbling.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import { canvasPanels, elToPath,
|
|
8
|
+
import { canvasPanels, elToPath, getNodeAtPath, parentElementPath, pathsEqual } from "../store";
|
|
9
9
|
import { activeTab } from "../workspace/workspace";
|
|
10
10
|
import { isInlineInContext } from "../editor/inline-edit";
|
|
11
11
|
import type { JxPath } from "../state";
|
|
12
12
|
import type { JxMutableNode } from "@jxsuite/schema/types";
|
|
13
|
+
import type { CanvasPanel } from "../panels/canvas-dnd.js";
|
|
13
14
|
|
|
14
15
|
let _ctx: { getCanvasMode: () => string; getZoom: () => number } | null = null;
|
|
15
16
|
|
|
@@ -30,14 +31,33 @@ export function effectiveZoom() {
|
|
|
30
31
|
: (ctx.getZoom?.() ?? activeTab.value?.session.ui.zoom ?? 1);
|
|
31
32
|
}
|
|
32
33
|
|
|
34
|
+
/**
|
|
35
|
+
* Convert a canvas panel's mediaName to an activeMedia value. Panels without a breakpoint (content
|
|
36
|
+
* mode, single-panel docs) carry "" or "base" — both mean the base style context (null).
|
|
37
|
+
*
|
|
38
|
+
* @param {string | null | undefined} mediaName
|
|
39
|
+
* @returns {string | null}
|
|
40
|
+
*/
|
|
41
|
+
export function panelMediaToActiveMedia(mediaName: string | null | undefined) {
|
|
42
|
+
return !mediaName || mediaName === "base" ? null : mediaName;
|
|
43
|
+
}
|
|
44
|
+
|
|
33
45
|
/** Return the active canvas panel based on the current activeMedia setting. */
|
|
34
46
|
export function getActivePanel() {
|
|
35
|
-
if (canvasPanels.length === 0)
|
|
36
|
-
|
|
47
|
+
if (canvasPanels.length === 0) {
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
if (canvasPanels.length === 1) {
|
|
51
|
+
return canvasPanels[0];
|
|
52
|
+
}
|
|
37
53
|
const activeMedia = activeTab.value?.session.ui.activeMedia ?? null;
|
|
38
54
|
for (const p of canvasPanels) {
|
|
39
|
-
if (activeMedia === null && (p.mediaName === "base" || p.mediaName === null))
|
|
40
|
-
|
|
55
|
+
if (activeMedia === null && (p.mediaName === "base" || p.mediaName === null)) {
|
|
56
|
+
return p;
|
|
57
|
+
}
|
|
58
|
+
if (p.mediaName === activeMedia) {
|
|
59
|
+
return p;
|
|
60
|
+
}
|
|
41
61
|
}
|
|
42
62
|
return canvasPanels[0];
|
|
43
63
|
}
|
|
@@ -50,16 +70,22 @@ export function getActivePanel() {
|
|
|
50
70
|
* @param {JxPath} path
|
|
51
71
|
*/
|
|
52
72
|
export function bubbleInlinePath(doc: JxMutableNode | undefined, path: JxPath) {
|
|
53
|
-
if (!doc)
|
|
73
|
+
if (!doc) {
|
|
74
|
+
return path;
|
|
75
|
+
}
|
|
54
76
|
let currentPath = path;
|
|
55
77
|
while (currentPath.length >= 2) {
|
|
56
78
|
const node = getNodeAtPath(doc, currentPath);
|
|
57
79
|
const pPath = parentElementPath(currentPath);
|
|
58
80
|
const parentNode = pPath ? getNodeAtPath(doc, pPath) : null;
|
|
59
|
-
if (!node || !parentNode)
|
|
81
|
+
if (!node || !parentNode) {
|
|
82
|
+
break;
|
|
83
|
+
}
|
|
60
84
|
const childTag = (node.tagName ?? "div").toLowerCase();
|
|
61
85
|
const parentTag = (parentNode.tagName ?? "div").toLowerCase();
|
|
62
|
-
if (!isInlineInContext(childTag, parentTag))
|
|
86
|
+
if (!isInlineInContext(childTag, parentTag)) {
|
|
87
|
+
break;
|
|
88
|
+
}
|
|
63
89
|
currentPath = pPath as JxPath;
|
|
64
90
|
}
|
|
65
91
|
return currentPath;
|
|
@@ -74,11 +100,17 @@ export function bubbleInlinePath(doc: JxMutableNode | undefined, path: JxPath) {
|
|
|
74
100
|
*/
|
|
75
101
|
export function findCanvasElement(path: JxPath, canvasEl: HTMLElement) {
|
|
76
102
|
let el: HTMLElement | null | undefined = canvasEl.firstElementChild as HTMLElement | null;
|
|
77
|
-
if (!el)
|
|
78
|
-
|
|
103
|
+
if (!el) {
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
106
|
+
if (path.length === 0) {
|
|
107
|
+
return el;
|
|
108
|
+
}
|
|
79
109
|
|
|
80
110
|
for (let i = 0; i < path.length; i += 2) {
|
|
81
|
-
if (path[i] !== "children" && path[i] !== "cases")
|
|
111
|
+
if (path[i] !== "children" && path[i] !== "cases") {
|
|
112
|
+
return null;
|
|
113
|
+
}
|
|
82
114
|
const idx = path[i + 1];
|
|
83
115
|
if (idx === undefined) {
|
|
84
116
|
el = el.children[0] as HTMLElement | undefined;
|
|
@@ -87,17 +119,23 @@ export function findCanvasElement(path: JxPath, canvasEl: HTMLElement) {
|
|
|
87
119
|
} else {
|
|
88
120
|
el = el.children[idx as number] as HTMLElement | undefined;
|
|
89
121
|
}
|
|
90
|
-
if (!el)
|
|
122
|
+
if (!el) {
|
|
123
|
+
break;
|
|
124
|
+
}
|
|
91
125
|
}
|
|
92
126
|
|
|
93
127
|
if (el) {
|
|
94
128
|
const elPath = elToPath.get(el);
|
|
95
|
-
if (elPath && pathsEqual(elPath, path))
|
|
129
|
+
if (elPath && pathsEqual(elPath, path)) {
|
|
130
|
+
return el;
|
|
131
|
+
}
|
|
96
132
|
}
|
|
97
133
|
|
|
98
134
|
for (const candidate of canvasEl.querySelectorAll("*")) {
|
|
99
135
|
const p = elToPath.get(candidate);
|
|
100
|
-
if (p && pathsEqual(p, path))
|
|
136
|
+
if (p && pathsEqual(p, path)) {
|
|
137
|
+
return candidate as HTMLElement;
|
|
138
|
+
}
|
|
101
139
|
}
|
|
102
140
|
return null;
|
|
103
141
|
}
|
|
@@ -109,20 +147,16 @@ export function findCanvasElement(path: JxPath, canvasEl: HTMLElement) {
|
|
|
109
147
|
* @param {string} type
|
|
110
148
|
* @param {import("../panels/canvas-dnd.js").CanvasPanel} panel
|
|
111
149
|
*/
|
|
112
|
-
export function overlayBoxDescriptor(
|
|
113
|
-
el: Element,
|
|
114
|
-
type: string,
|
|
115
|
-
panel: import("../panels/canvas-dnd.js").CanvasPanel,
|
|
116
|
-
) {
|
|
150
|
+
export function overlayBoxDescriptor(el: Element, type: string, panel: CanvasPanel) {
|
|
117
151
|
const viewport = panel.viewport as HTMLElement;
|
|
118
152
|
const vpRect = viewport.getBoundingClientRect();
|
|
119
153
|
const elRect = el.getBoundingClientRect();
|
|
120
154
|
const scale = effectiveZoom();
|
|
121
155
|
return {
|
|
122
156
|
cls: `overlay-box overlay-${type}`,
|
|
123
|
-
|
|
157
|
+
height: `${elRect.height / scale}px`,
|
|
124
158
|
left: `${(elRect.left - vpRect.left + viewport.scrollLeft) / scale}px`,
|
|
159
|
+
top: `${(elRect.top - vpRect.top + viewport.scrollTop) / scale}px`,
|
|
125
160
|
width: `${elRect.width / scale}px`,
|
|
126
|
-
height: `${elRect.height / scale}px`,
|
|
127
161
|
};
|
|
128
162
|
}
|
|
@@ -6,33 +6,35 @@
|
|
|
6
6
|
* mapping ($map remapping), site-level style injection, and $head element injection.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
import { elToPath,
|
|
9
|
+
import { elToPath, projectState, stripEventHandlers } from "../store";
|
|
10
|
+
import { errorMessage } from "@jxsuite/schema/parse";
|
|
10
11
|
import { activeTab } from "../workspace/workspace";
|
|
11
12
|
import { view } from "../view";
|
|
12
13
|
import { toRaw } from "../reactivity";
|
|
13
14
|
import {
|
|
14
|
-
renderNode as runtimeRenderNode,
|
|
15
15
|
buildScope,
|
|
16
16
|
defineElement,
|
|
17
|
+
renderNode as runtimeRenderNode,
|
|
17
18
|
setSkipServerFunctions,
|
|
18
19
|
} from "@jxsuite/runtime";
|
|
19
20
|
import {
|
|
21
|
+
distributePageIntoLayout,
|
|
20
22
|
getEffectiveElements,
|
|
21
|
-
getEffectiveImports,
|
|
22
|
-
getEffectiveMedia,
|
|
23
23
|
getEffectiveHead,
|
|
24
|
+
getEffectiveImports,
|
|
24
25
|
getEffectiveLayoutPath,
|
|
26
|
+
getEffectiveMedia,
|
|
25
27
|
resolveLayoutDoc,
|
|
26
|
-
distributePageIntoLayout,
|
|
27
28
|
} from "../site-context";
|
|
28
29
|
import { componentRegistry, computeRelativePath } from "../files/components";
|
|
29
30
|
import { prepareForEditMode } from "../utils/edit-display";
|
|
30
31
|
import { getActiveElement } from "../editor/inline-edit";
|
|
31
32
|
import { buildNestedSiteCSS } from "./nested-site-style";
|
|
32
33
|
|
|
33
|
-
|
|
34
|
+
import type { JxDocument, JxElement, JxMutableNode } from "@jxsuite/schema/types";
|
|
35
|
+
import type { ComponentEntry } from "../files/components.js";
|
|
34
36
|
|
|
35
|
-
|
|
37
|
+
export { buildNestedSiteCSS } from "./nested-site-style";
|
|
36
38
|
|
|
37
39
|
/** @param {Event} e */
|
|
38
40
|
function _preventNav(e: Event) {
|
|
@@ -67,10 +69,11 @@ function findPageContentPrefix(
|
|
|
67
69
|
node: JxMutableNode,
|
|
68
70
|
path: (string | number)[] = [],
|
|
69
71
|
): (string | number)[] | null {
|
|
70
|
-
if (!node || typeof node !== "object")
|
|
72
|
+
if (!node || typeof node !== "object") {
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
71
75
|
if (Array.isArray(node.children)) {
|
|
72
|
-
for (
|
|
73
|
-
const child = node.children[i];
|
|
76
|
+
for (const child of node.children) {
|
|
74
77
|
if (child && typeof child === "object" && !child.$__layout) {
|
|
75
78
|
return [...path, "children"];
|
|
76
79
|
}
|
|
@@ -79,7 +82,9 @@ function findPageContentPrefix(
|
|
|
79
82
|
const child = node.children[i];
|
|
80
83
|
if (child && typeof child === "object" && child.$__layout) {
|
|
81
84
|
const found = findPageContentPrefix(child, [...path, "children", i]);
|
|
82
|
-
if (found)
|
|
85
|
+
if (found) {
|
|
86
|
+
return found;
|
|
87
|
+
}
|
|
83
88
|
}
|
|
84
89
|
}
|
|
85
90
|
}
|
|
@@ -94,17 +99,23 @@ export let activeLayoutPath = null as string | null;
|
|
|
94
99
|
* rendered DOM elements came from the layout vs page content.
|
|
95
100
|
*/
|
|
96
101
|
function markLayoutNodes(node: JxMutableNode) {
|
|
97
|
-
if (!node || typeof node !== "object")
|
|
102
|
+
if (!node || typeof node !== "object") {
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
98
105
|
node.$__layout = true;
|
|
99
106
|
if (Array.isArray(node.children)) {
|
|
100
107
|
for (const child of node.children) {
|
|
101
|
-
if (typeof child === "string")
|
|
108
|
+
if (typeof child === "string") {
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
102
111
|
markLayoutNodes(child);
|
|
103
112
|
}
|
|
104
113
|
}
|
|
105
114
|
if (node.$elements) {
|
|
106
115
|
for (const el of node.$elements) {
|
|
107
|
-
if (typeof el !== "string")
|
|
116
|
+
if (typeof el !== "string") {
|
|
117
|
+
markLayoutNodes(el as JxMutableNode);
|
|
118
|
+
}
|
|
108
119
|
}
|
|
109
120
|
}
|
|
110
121
|
}
|
|
@@ -132,9 +143,9 @@ export function initCanvasLiveRender(ctx: { getCanvasMode: () => string }) {
|
|
|
132
143
|
export async function renderCanvasLive(gen: number, doc: JxMutableNode, canvasEl: HTMLElement) {
|
|
133
144
|
const tab = activeTab.value;
|
|
134
145
|
const S = {
|
|
146
|
+
document: tab?.doc.document,
|
|
135
147
|
documentPath: tab?.documentPath,
|
|
136
148
|
mode: tab?.doc.mode,
|
|
137
|
-
document: tab?.doc.document,
|
|
138
149
|
};
|
|
139
150
|
const canvasMode = _ctx!.getCanvasMode();
|
|
140
151
|
|
|
@@ -144,8 +155,8 @@ export async function renderCanvasLive(gen: number, doc: JxMutableNode, canvasEl
|
|
|
144
155
|
}
|
|
145
156
|
|
|
146
157
|
// Suppress server function resolution in non-preview modes to avoid
|
|
147
|
-
//
|
|
148
|
-
//
|
|
158
|
+
// Failed proxy calls and infinite reactive retries (also covers
|
|
159
|
+
// Async custom element connectedCallbacks that run after this function returns)
|
|
149
160
|
setSkipServerFunctions(canvasMode !== "preview");
|
|
150
161
|
|
|
151
162
|
let renderDoc =
|
|
@@ -172,7 +183,9 @@ export async function renderCanvasLive(gen: number, doc: JxMutableNode, canvasEl
|
|
|
172
183
|
if (layoutPath) {
|
|
173
184
|
const layoutDoc = await resolveLayoutDoc(layoutPath);
|
|
174
185
|
if (layoutDoc) {
|
|
175
|
-
if (gen !== view.renderGeneration)
|
|
186
|
+
if (gen !== view.renderGeneration) {
|
|
187
|
+
return null;
|
|
188
|
+
}
|
|
176
189
|
activeLayoutPath = layoutPath.replace(/^\.\//, "");
|
|
177
190
|
markLayoutNodes(layoutDoc);
|
|
178
191
|
const pageForSlots = canvasMode === "preview" ? structuredClone(toRaw(doc)) : renderDoc;
|
|
@@ -186,11 +199,13 @@ export async function renderCanvasLive(gen: number, doc: JxMutableNode, canvasEl
|
|
|
186
199
|
}
|
|
187
200
|
|
|
188
201
|
// In edit mode, collect paths where $map templates were inlined as children[0]
|
|
189
|
-
//
|
|
202
|
+
// So we can remap runtime paths (children,0,...) → (children,map,...)
|
|
190
203
|
const mapParentPaths = new Set();
|
|
191
204
|
if (canvasMode === "design" || canvasMode === "edit") {
|
|
192
205
|
(function findMapParents(node: JxMutableNode, path: (string | number)[]) {
|
|
193
|
-
if (!node || typeof node !== "object")
|
|
206
|
+
if (!node || typeof node !== "object") {
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
194
209
|
if (
|
|
195
210
|
node.children &&
|
|
196
211
|
typeof node.children === "object" &&
|
|
@@ -201,7 +216,9 @@ export async function renderCanvasLive(gen: number, doc: JxMutableNode, canvasEl
|
|
|
201
216
|
if (Array.isArray(node.children)) {
|
|
202
217
|
for (let i = 0; i < node.children.length; i++) {
|
|
203
218
|
const child = node.children[i];
|
|
204
|
-
if (typeof child === "string")
|
|
219
|
+
if (typeof child === "string") {
|
|
220
|
+
continue;
|
|
221
|
+
}
|
|
205
222
|
findMapParents(child, [...path, "children", i]);
|
|
206
223
|
}
|
|
207
224
|
}
|
|
@@ -219,32 +236,38 @@ export async function renderCanvasLive(gen: number, doc: JxMutableNode, canvasEl
|
|
|
219
236
|
const docBase = S.documentPath ? `${location.origin}/${docPrefix}${S.documentPath}` : undefined;
|
|
220
237
|
|
|
221
238
|
// Register custom elements so the runtime can render them
|
|
222
|
-
|
|
239
|
+
const effectiveElements = getEffectiveElements(renderDoc.$elements as (JxElement | string)[]);
|
|
223
240
|
|
|
224
241
|
// In content mode (markdown) or when a layout is applied, auto-discover components
|
|
225
|
-
//
|
|
242
|
+
// For custom elements that have no explicit $elements registration.
|
|
226
243
|
if ((S.mode === "content" || layoutWrapped) && componentRegistry.length > 0) {
|
|
227
244
|
const existingRefs = new Set(
|
|
228
245
|
effectiveElements.map((e: JxElement | string) => (typeof e === "string" ? e : e?.$ref)),
|
|
229
246
|
);
|
|
230
247
|
/** @param {JxMutableNode} node */
|
|
231
248
|
const collectTags = (node: JxMutableNode) => {
|
|
232
|
-
const tags
|
|
233
|
-
if (!node || typeof node !== "object")
|
|
234
|
-
|
|
249
|
+
const tags = new Set<string>();
|
|
250
|
+
if (!node || typeof node !== "object") {
|
|
251
|
+
return tags;
|
|
252
|
+
}
|
|
253
|
+
if (node.tagName) {
|
|
254
|
+
tags.add(node.tagName);
|
|
255
|
+
}
|
|
235
256
|
if (Array.isArray(node.children)) {
|
|
236
257
|
for (const child of node.children) {
|
|
237
|
-
if (typeof child === "string")
|
|
238
|
-
|
|
258
|
+
if (typeof child === "string") {
|
|
259
|
+
continue;
|
|
260
|
+
}
|
|
261
|
+
for (const t of collectTags(child)) {
|
|
262
|
+
tags.add(t);
|
|
263
|
+
}
|
|
239
264
|
}
|
|
240
265
|
}
|
|
241
266
|
return tags;
|
|
242
267
|
};
|
|
243
268
|
for (const tag of collectTags(renderDoc)) {
|
|
244
|
-
const comp = componentRegistry.find(
|
|
245
|
-
|
|
246
|
-
);
|
|
247
|
-
if (comp && comp.source !== "npm") {
|
|
269
|
+
const comp = componentRegistry.find((c: ComponentEntry) => c.tagName === tag);
|
|
270
|
+
if (comp && comp.source !== "npm" && comp.path) {
|
|
248
271
|
const relPath = computeRelativePath(S.documentPath ?? null, comp.path);
|
|
249
272
|
if (!existingRefs.has(relPath)) {
|
|
250
273
|
effectiveElements.push({ $ref: relPath });
|
|
@@ -254,7 +277,7 @@ export async function renderCanvasLive(gen: number, doc: JxMutableNode, canvasEl
|
|
|
254
277
|
}
|
|
255
278
|
}
|
|
256
279
|
|
|
257
|
-
if (effectiveElements.length) {
|
|
280
|
+
if (effectiveElements.length > 0) {
|
|
258
281
|
renderDoc.$elements = effectiveElements as (string | JxMutableNode | { $ref: string })[];
|
|
259
282
|
for (const entry of effectiveElements) {
|
|
260
283
|
if (typeof entry === "string") {
|
|
@@ -262,39 +285,43 @@ export async function renderCanvasLive(gen: number, doc: JxMutableNode, canvasEl
|
|
|
262
285
|
const specifier =
|
|
263
286
|
entry.startsWith("/") || entry.startsWith(".") ? entry : `/node_modules/${entry}`;
|
|
264
287
|
await import(specifier);
|
|
265
|
-
} catch (
|
|
266
|
-
console.warn("Studio: failed to import package", entry,
|
|
288
|
+
} catch (error) {
|
|
289
|
+
console.warn("Studio: failed to import package", entry, error);
|
|
267
290
|
}
|
|
268
291
|
} else if (entry?.$ref) {
|
|
269
292
|
let href;
|
|
270
293
|
try {
|
|
271
|
-
href = new URL(entry.$ref, docBase)
|
|
272
|
-
} catch (
|
|
273
|
-
console.warn("Studio: invalid element URL", { ref: entry.$ref
|
|
294
|
+
({ href } = new URL(entry.$ref, docBase));
|
|
295
|
+
} catch (error) {
|
|
296
|
+
console.warn("Studio: invalid element URL", { docBase, ref: entry.$ref }, error);
|
|
297
|
+
continue;
|
|
298
|
+
}
|
|
299
|
+
if (_failedElements.has(href)) {
|
|
274
300
|
continue;
|
|
275
301
|
}
|
|
276
|
-
if (_failedElements.has(href)) continue;
|
|
277
302
|
try {
|
|
278
303
|
await defineElement(href);
|
|
279
|
-
} catch (
|
|
304
|
+
} catch (error) {
|
|
280
305
|
_failedElements.add(href);
|
|
281
|
-
console.warn("Studio: failed to register element", entry.$ref,
|
|
306
|
+
console.warn("Studio: failed to register element", entry.$ref, error);
|
|
282
307
|
}
|
|
283
308
|
}
|
|
284
309
|
}
|
|
285
310
|
}
|
|
286
311
|
|
|
287
312
|
// Bail out if a newer render started while we were importing elements
|
|
288
|
-
if (gen !== view.renderGeneration)
|
|
313
|
+
if (gen !== view.renderGeneration) {
|
|
314
|
+
return null;
|
|
315
|
+
}
|
|
289
316
|
|
|
290
317
|
// Inject site-level imports so buildScope can resolve $prototype names
|
|
291
318
|
renderDoc.imports = getEffectiveImports(renderDoc.imports);
|
|
292
319
|
|
|
293
320
|
// Apply project-level styles mirroring the compiler convention:
|
|
294
|
-
//
|
|
295
|
-
//
|
|
321
|
+
// Viewport ≈ :root → CSS custom properties (they inherit down)
|
|
322
|
+
// CanvasEl ≈ body → regular CSS properties (inline beats CSS defaults)
|
|
296
323
|
// This ensures project font-family, color, etc. override the
|
|
297
|
-
//
|
|
324
|
+
// Content-mode fallback typography rules in the stylesheet.
|
|
298
325
|
// In edit mode, propagate to the .content-edit-canvas wrapper for seamless appearance.
|
|
299
326
|
const viewport = canvasEl.closest(".canvas-panel-viewport") as HTMLElement | null;
|
|
300
327
|
const editSurface =
|
|
@@ -306,7 +333,9 @@ export async function renderCanvasLive(gen: number, doc: JxMutableNode, canvasEl
|
|
|
306
333
|
viewport.style.cssText = "";
|
|
307
334
|
if (siteStyle && typeof siteStyle === "object") {
|
|
308
335
|
for (const [k, v] of Object.entries(siteStyle)) {
|
|
309
|
-
if (v !== null && typeof v === "object" && !Array.isArray(v))
|
|
336
|
+
if (v !== null && typeof v === "object" && !Array.isArray(v)) {
|
|
337
|
+
continue;
|
|
338
|
+
}
|
|
310
339
|
if (k.startsWith("--")) {
|
|
311
340
|
viewport.style.setProperty(k, String(v));
|
|
312
341
|
} else {
|
|
@@ -315,21 +344,23 @@ export async function renderCanvasLive(gen: number, doc: JxMutableNode, canvasEl
|
|
|
315
344
|
}
|
|
316
345
|
}
|
|
317
346
|
}
|
|
318
|
-
if (editSurface) {
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
347
|
+
if (editSurface && siteStyle && typeof siteStyle === "object") {
|
|
348
|
+
for (const [k, v] of Object.entries(siteStyle)) {
|
|
349
|
+
if (v !== null && typeof v === "object" && !Array.isArray(v)) {
|
|
350
|
+
continue;
|
|
351
|
+
}
|
|
352
|
+
if (k.startsWith("--")) {
|
|
353
|
+
editSurface.style.setProperty(k, String(v));
|
|
354
|
+
} else {
|
|
355
|
+
(editSurface.style as unknown as Record<string, string>)[k] = String(v);
|
|
327
356
|
}
|
|
328
357
|
}
|
|
329
358
|
}
|
|
330
359
|
if (siteStyle && typeof siteStyle === "object") {
|
|
331
360
|
for (const [k, v] of Object.entries(siteStyle)) {
|
|
332
|
-
if (v !== null && typeof v === "object" && !Array.isArray(v))
|
|
361
|
+
if (v !== null && typeof v === "object" && !Array.isArray(v)) {
|
|
362
|
+
continue;
|
|
363
|
+
}
|
|
333
364
|
if (!k.startsWith("--")) {
|
|
334
365
|
(canvasEl.style as unknown as Record<string, string>)[k] = String(v);
|
|
335
366
|
}
|
|
@@ -343,12 +374,14 @@ export async function renderCanvasLive(gen: number, doc: JxMutableNode, canvasEl
|
|
|
343
374
|
const css = buildNestedSiteCSS(siteStyle, `[${scopeAttr}]`);
|
|
344
375
|
|
|
345
376
|
if (css) {
|
|
346
|
-
const existingStyleEl = document.
|
|
347
|
-
if (existingStyleEl)
|
|
377
|
+
const existingStyleEl = document.querySelector("#jx-site-style");
|
|
378
|
+
if (existingStyleEl) {
|
|
379
|
+
existingStyleEl.remove();
|
|
380
|
+
}
|
|
348
381
|
const styleEl = document.createElement("style");
|
|
349
382
|
styleEl.id = "jx-site-style";
|
|
350
383
|
styleEl.textContent = css;
|
|
351
|
-
document.head.
|
|
384
|
+
document.head.append(styleEl);
|
|
352
385
|
}
|
|
353
386
|
}
|
|
354
387
|
|
|
@@ -357,13 +390,17 @@ export async function renderCanvasLive(gen: number, doc: JxMutableNode, canvasEl
|
|
|
357
390
|
|
|
358
391
|
// Inject $head elements (link/meta/script) into document.head
|
|
359
392
|
const effectiveHead = getEffectiveHead(renderDoc.$head);
|
|
360
|
-
if (effectiveHead.length) {
|
|
393
|
+
if (effectiveHead.length > 0) {
|
|
361
394
|
for (const entry of effectiveHead) {
|
|
362
|
-
if (!entry?.tagName)
|
|
395
|
+
if (!entry?.tagName) {
|
|
396
|
+
continue;
|
|
397
|
+
}
|
|
363
398
|
const tag = entry.tagName.toLowerCase();
|
|
364
399
|
// Skip inline scripts — they can contain arbitrary JS/HTML that throws
|
|
365
|
-
//
|
|
366
|
-
if (tag === "script" && !entry.attributes?.src)
|
|
400
|
+
// When the browser tries to execute it in the studio context
|
|
401
|
+
if (tag === "script" && !entry.attributes?.src) {
|
|
402
|
+
continue;
|
|
403
|
+
}
|
|
367
404
|
const attrs = { ...entry.attributes };
|
|
368
405
|
for (const key of ["href", "src"]) {
|
|
369
406
|
const val = attrs[key];
|
|
@@ -377,34 +414,45 @@ export async function renderCanvasLive(gen: number, doc: JxMutableNode, canvasEl
|
|
|
377
414
|
}
|
|
378
415
|
}
|
|
379
416
|
const selector = `${tag}${attrs.href ? `[href="${attrs.href}"]` : ""}${attrs.src ? `[src="${attrs.src}"]` : ""}`;
|
|
380
|
-
if (selector !== tag && document.head.querySelector(selector))
|
|
417
|
+
if (selector !== tag && document.head.querySelector(selector)) {
|
|
418
|
+
continue;
|
|
419
|
+
}
|
|
381
420
|
const el = document.createElement(tag);
|
|
382
|
-
for (const [k, v] of Object.entries(attrs))
|
|
383
|
-
|
|
384
|
-
|
|
421
|
+
for (const [k, v] of Object.entries(attrs)) {
|
|
422
|
+
el.setAttribute(k, String(v));
|
|
423
|
+
}
|
|
424
|
+
if (entry.textContent) {
|
|
425
|
+
el.textContent = entry.textContent;
|
|
426
|
+
}
|
|
427
|
+
document.head.append(el);
|
|
385
428
|
}
|
|
386
429
|
}
|
|
387
430
|
|
|
388
|
-
const $defs = await buildScope(renderDoc, {}, docBase);
|
|
431
|
+
const $defs = await buildScope(renderDoc as JxDocument, {}, docBase);
|
|
389
432
|
// Bail out if a newer render started while buildScope was running
|
|
390
|
-
if (gen !== view.renderGeneration)
|
|
433
|
+
if (gen !== view.renderGeneration) {
|
|
434
|
+
return null;
|
|
435
|
+
}
|
|
391
436
|
const el = /** @type {HTMLElement} */ runtimeRenderNode(renderDoc, $defs, {
|
|
437
|
+
_path: [],
|
|
392
438
|
onNodeCreated(
|
|
393
|
-
|
|
439
|
+
created: HTMLElement | Text,
|
|
394
440
|
path: (string | number)[],
|
|
395
|
-
def:
|
|
441
|
+
def: JxElement | string,
|
|
396
442
|
) {
|
|
397
|
-
if (!(
|
|
443
|
+
if (!(created instanceof HTMLElement)) {
|
|
444
|
+
return;
|
|
445
|
+
}
|
|
398
446
|
// Track layout-originated elements — don't store in elToPath to avoid
|
|
399
|
-
//
|
|
400
|
-
if (layoutWrapped && def?.$__layout) {
|
|
401
|
-
layoutElements.add(
|
|
402
|
-
|
|
447
|
+
// Path collisions with remapped page content paths
|
|
448
|
+
if (layoutWrapped && typeof def === "object" && def?.$__layout) {
|
|
449
|
+
layoutElements.add(created);
|
|
450
|
+
created.dataset.jxLayout = "";
|
|
403
451
|
return;
|
|
404
452
|
}
|
|
405
453
|
|
|
406
454
|
// Remap layout-wrapped paths: strip the layout prefix so paths are
|
|
407
|
-
//
|
|
455
|
+
// Relative to the original page document (which is what S.document holds)
|
|
408
456
|
let mappedPath = path;
|
|
409
457
|
if (layoutWrapped && pageContentPrefix) {
|
|
410
458
|
const pfx = pageContentPrefix;
|
|
@@ -417,7 +465,7 @@ export async function renderCanvasLive(gen: number, doc: JxMutableNode, canvasEl
|
|
|
417
465
|
}
|
|
418
466
|
|
|
419
467
|
// Remap $map paths: wrapper and template children → real document paths
|
|
420
|
-
//
|
|
468
|
+
// PrepareForEditMode wraps $map template in: children[0] (wrapper) > children[0] (template)
|
|
421
469
|
// Real paths: wrapper → ['children'] ($map container), template → ['children', 'map']
|
|
422
470
|
if ((canvasMode === "design" || canvasMode === "edit") && mapParentPaths.size > 0) {
|
|
423
471
|
for (let i = 0; i < mappedPath.length - 1; i++) {
|
|
@@ -443,11 +491,10 @@ export async function renderCanvasLive(gen: number, doc: JxMutableNode, canvasEl
|
|
|
443
491
|
}
|
|
444
492
|
}
|
|
445
493
|
}
|
|
446
|
-
elToPath.set(
|
|
494
|
+
elToPath.set(created, mappedPath);
|
|
447
495
|
},
|
|
448
|
-
_path: [],
|
|
449
496
|
});
|
|
450
|
-
if (canvasMode === "design" || canvasMode === "edit") {
|
|
497
|
+
if ((canvasMode === "design" || canvasMode === "edit") && el instanceof HTMLElement) {
|
|
451
498
|
// Disable pointer events on all rendered elements for edit mode
|
|
452
499
|
el.style.pointerEvents = "none";
|
|
453
500
|
for (const child of el.querySelectorAll("*")) {
|
|
@@ -455,15 +502,15 @@ export async function renderCanvasLive(gen: number, doc: JxMutableNode, canvasEl
|
|
|
455
502
|
}
|
|
456
503
|
}
|
|
457
504
|
// Clear and append atomically — ensures the canvas is never left empty if a
|
|
458
|
-
//
|
|
505
|
+
// Newer render starts and this one would have bailed after clearing.
|
|
459
506
|
canvasEl.innerHTML = "";
|
|
460
507
|
if (S.mode === "content") {
|
|
461
|
-
canvasEl.
|
|
508
|
+
canvasEl.dataset.contentMode = "";
|
|
462
509
|
} else {
|
|
463
|
-
canvasEl.
|
|
510
|
+
delete canvasEl.dataset.contentMode;
|
|
464
511
|
}
|
|
465
512
|
|
|
466
|
-
canvasEl.
|
|
513
|
+
canvasEl.append(el);
|
|
467
514
|
|
|
468
515
|
// Delegated click handler prevents link navigation in all canvas modes.
|
|
469
516
|
// Attached once per canvasEl (survives reactive re-renders that replace children).
|
|
@@ -476,15 +523,19 @@ export async function renderCanvasLive(gen: number, doc: JxMutableNode, canvasEl
|
|
|
476
523
|
requestAnimationFrame(() => {
|
|
477
524
|
const editingEl = getActiveElement();
|
|
478
525
|
for (const child of canvasEl.querySelectorAll("*")) {
|
|
479
|
-
if (view.componentInlineEdit && child === view.componentInlineEdit.el)
|
|
480
|
-
|
|
526
|
+
if (view.componentInlineEdit && child === view.componentInlineEdit.el) {
|
|
527
|
+
continue;
|
|
528
|
+
}
|
|
529
|
+
if (editingEl && child === editingEl) {
|
|
530
|
+
continue;
|
|
531
|
+
}
|
|
481
532
|
(child as HTMLElement).style.pointerEvents = "none";
|
|
482
533
|
}
|
|
483
534
|
});
|
|
484
535
|
}
|
|
485
536
|
return $defs;
|
|
486
|
-
} catch (
|
|
487
|
-
console.warn("renderCanvasLive failed:", (
|
|
537
|
+
} catch (error) {
|
|
538
|
+
console.warn("renderCanvasLive failed:", errorMessage(error), error);
|
|
488
539
|
return null;
|
|
489
540
|
}
|
|
490
541
|
}
|