@jxsuite/studio 0.9.0 → 0.10.2
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 +117584 -117551
- package/dist/studio.js.map +103 -100
- package/package.json +29 -29
- package/src/browse/browse.js +21 -19
- package/src/canvas/canvas-helpers.js +121 -0
- package/src/canvas/canvas-live-render.js +292 -0
- package/src/canvas/canvas-render.js +431 -0
- package/src/canvas/canvas-utils.js +33 -69
- package/src/editor/component-inline-edit.js +5 -4
- package/src/editor/content-inline-edit.js +11 -18
- package/src/files/files.js +2 -0
- package/src/panels/block-action-bar.js +8 -4
- package/src/panels/canvas-dnd.js +2 -13
- package/src/panels/imports-panel.js +2 -2
- package/src/panels/left-panel.js +1 -2
- package/src/panels/overlays.js +9 -23
- package/src/panels/panel-events.js +10 -13
- package/src/panels/properties-panel.js +3 -3
- package/src/panels/pseudo-preview.js +7 -14
- package/src/panels/shared.js +1 -1
- package/src/panels/stylebook-panel.js +27 -20
- package/src/platform.js +9 -6
- package/src/settings/{collections-editor.js → content-types-editor.js} +48 -48
- package/src/settings/schema-field-ui.js +1 -1
- package/src/state.js +10 -2
- package/src/store.js +9 -0
- package/src/studio.js +67 -811
- package/src/utils/studio-utils.js +5 -5
- package/src/view.js +0 -2
|
@@ -23,6 +23,7 @@ import { isEditing, getActiveElement, getInlineActions } from "../editor/inline-
|
|
|
23
23
|
import { toggleInlineFormat, isTagActiveInSelection } from "../editor/inline-format.js";
|
|
24
24
|
import { componentRegistry } from "../files/components.js";
|
|
25
25
|
import { convertToComponent } from "../editor/convert-to-component.js";
|
|
26
|
+
import { findCanvasElement, getActivePanel } from "../canvas/canvas-helpers.js";
|
|
26
27
|
|
|
27
28
|
/** @type {any} */
|
|
28
29
|
let _ctx = null;
|
|
@@ -32,8 +33,6 @@ let _ctx = null;
|
|
|
32
33
|
*
|
|
33
34
|
* @param {{
|
|
34
35
|
* getCanvasMode: () => string;
|
|
35
|
-
* findCanvasElement: Function;
|
|
36
|
-
* getActivePanel: Function;
|
|
37
36
|
* navigateToComponent: Function;
|
|
38
37
|
* createFloatingContainer: Function;
|
|
39
38
|
* }} ctx
|
|
@@ -186,6 +185,11 @@ export function dismissLinkPopover() {
|
|
|
186
185
|
if (view.linkPopoverHost) litRender(nothing, view.linkPopoverHost);
|
|
187
186
|
}
|
|
188
187
|
|
|
188
|
+
/** Dismiss the block action bar. */
|
|
189
|
+
export function dismissBlockActionBar() {
|
|
190
|
+
if (view.blockActionBarEl) litRender(nothing, view.blockActionBarEl);
|
|
191
|
+
}
|
|
192
|
+
|
|
189
193
|
/** @param {any} anchorBtn */
|
|
190
194
|
function showLinkPopover(anchorBtn) {
|
|
191
195
|
litRender(nothing, view.linkPopoverHost);
|
|
@@ -312,12 +316,12 @@ export function renderBlockActionBar() {
|
|
|
312
316
|
return;
|
|
313
317
|
}
|
|
314
318
|
|
|
315
|
-
const activePanel =
|
|
319
|
+
const activePanel = getActivePanel();
|
|
316
320
|
if (!activePanel) {
|
|
317
321
|
litRender(nothing, view.blockActionBarEl);
|
|
318
322
|
return;
|
|
319
323
|
}
|
|
320
|
-
const el =
|
|
324
|
+
const el = findCanvasElement(S.selection, activePanel.canvas);
|
|
321
325
|
const node = el && getNodeAtPath(S.document, S.selection);
|
|
322
326
|
if (!el || !node) {
|
|
323
327
|
litRender(nothing, view.blockActionBarEl);
|
package/src/panels/canvas-dnd.js
CHANGED
|
@@ -18,18 +18,7 @@ import {
|
|
|
18
18
|
} from "../store.js";
|
|
19
19
|
import { view } from "../view.js";
|
|
20
20
|
import { applyDropInstruction } from "../panels/dnd.js";
|
|
21
|
-
|
|
22
|
-
/** @type {any} */
|
|
23
|
-
let _ctx = null;
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* Initialize the canvas DnD module.
|
|
27
|
-
*
|
|
28
|
-
* @param {{ effectiveZoom: () => number }} ctx
|
|
29
|
-
*/
|
|
30
|
-
export function initCanvasDnD(ctx) {
|
|
31
|
-
_ctx = ctx;
|
|
32
|
-
}
|
|
21
|
+
import { effectiveZoom } from "../canvas/canvas-helpers.js";
|
|
33
22
|
|
|
34
23
|
/**
|
|
35
24
|
* Register all canvas elements in a panel as DnD drop targets.
|
|
@@ -133,7 +122,7 @@ function showCanvasDropIndicator(el, elPath, isVoid, panel) {
|
|
|
133
122
|
return;
|
|
134
123
|
}
|
|
135
124
|
|
|
136
|
-
const scale =
|
|
125
|
+
const scale = effectiveZoom();
|
|
137
126
|
const wrapRect = viewport.getBoundingClientRect();
|
|
138
127
|
const elRect = el.getBoundingClientRect();
|
|
139
128
|
const left = (elRect.left - wrapRect.left + viewport.scrollLeft) / scale;
|
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
*
|
|
4
4
|
* When editing project.json: shows Class Imports, Dependencies (add/remove packages), and
|
|
5
5
|
* per-package component toggles for cherry-picking individual elements. When editing a
|
|
6
|
-
* page/layout/component/
|
|
7
|
-
* toggles.
|
|
6
|
+
* page/layout/component/content type: shows Component Imports ($ref picker) and per-package
|
|
7
|
+
* component toggles.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import { html, nothing } from "lit-html";
|
package/src/panels/left-panel.js
CHANGED
|
@@ -15,7 +15,6 @@ import {
|
|
|
15
15
|
applyMutation,
|
|
16
16
|
updateFrontmatter,
|
|
17
17
|
} from "../store.js";
|
|
18
|
-
import { view } from "../view.js";
|
|
19
18
|
import { ensureLitState } from "./shared.js";
|
|
20
19
|
import { renderLayersTemplate } from "./layers-panel.js";
|
|
21
20
|
import { renderStylebookLayersTemplate } from "./stylebook-layers-panel.js";
|
|
@@ -96,7 +95,7 @@ function _render() {
|
|
|
96
95
|
updateSession,
|
|
97
96
|
});
|
|
98
97
|
else if (tab === "data")
|
|
99
|
-
content = _ctx.renderDataExplorerTemplate(S.document.state,
|
|
98
|
+
content = _ctx.renderDataExplorerTemplate(S.document.state, S.canvas?.scope ?? null, {
|
|
100
99
|
renderCanvas: _ctx.renderCanvas,
|
|
101
100
|
renderLeftPanel: render,
|
|
102
101
|
defCategory: _ctx.defCategory,
|
package/src/panels/overlays.js
CHANGED
|
@@ -6,6 +6,11 @@
|
|
|
6
6
|
import { html, render as litRender, nothing } from "lit-html";
|
|
7
7
|
import { getState, canvasPanels, pathsEqual, subscribe } from "../store.js";
|
|
8
8
|
import { view } from "../view.js";
|
|
9
|
+
import {
|
|
10
|
+
findCanvasElement,
|
|
11
|
+
getActivePanel,
|
|
12
|
+
overlayBoxDescriptor,
|
|
13
|
+
} from "../canvas/canvas-helpers.js";
|
|
9
14
|
|
|
10
15
|
/** @type {any} */
|
|
11
16
|
let _ctx = null;
|
|
@@ -16,8 +21,7 @@ let _unsub = null;
|
|
|
16
21
|
/**
|
|
17
22
|
* Mount the overlays panel.
|
|
18
23
|
*
|
|
19
|
-
* @param {any} ctx — {
|
|
20
|
-
* findCanvasElement, getActivePanel }
|
|
24
|
+
* @param {any} ctx — { getCanvasMode, isEditing, renderBlockActionBar }
|
|
21
25
|
*/
|
|
22
26
|
export function mount(ctx) {
|
|
23
27
|
_ctx = ctx;
|
|
@@ -32,24 +36,6 @@ export function unmount() {
|
|
|
32
36
|
_ctx = null;
|
|
33
37
|
}
|
|
34
38
|
|
|
35
|
-
/**
|
|
36
|
-
* @param {any} el
|
|
37
|
-
* @param {any} type
|
|
38
|
-
* @param {any} panel
|
|
39
|
-
*/
|
|
40
|
-
function overlayBoxDescriptor(el, type, panel) {
|
|
41
|
-
const vpRect = panel.viewport.getBoundingClientRect();
|
|
42
|
-
const elRect = el.getBoundingClientRect();
|
|
43
|
-
const scale = _ctx.effectiveZoom();
|
|
44
|
-
return {
|
|
45
|
-
cls: `overlay-box overlay-${type}`,
|
|
46
|
-
top: `${(elRect.top - vpRect.top + panel.viewport.scrollTop) / scale}px`,
|
|
47
|
-
left: `${(elRect.left - vpRect.left + panel.viewport.scrollLeft) / scale}px`,
|
|
48
|
-
width: `${elRect.width / scale}px`,
|
|
49
|
-
height: `${elRect.height / scale}px`,
|
|
50
|
-
};
|
|
51
|
-
}
|
|
52
|
-
|
|
53
39
|
export function render() {
|
|
54
40
|
if (!_ctx) return;
|
|
55
41
|
const S = getState();
|
|
@@ -98,12 +84,12 @@ export function render() {
|
|
|
98
84
|
const boxes = [];
|
|
99
85
|
|
|
100
86
|
if (S.hover && !pathsEqual(S.hover, S.selection)) {
|
|
101
|
-
const el =
|
|
87
|
+
const el = findCanvasElement(S.hover, p.canvas);
|
|
102
88
|
if (el) boxes.push(overlayBoxDescriptor(el, "hover", p));
|
|
103
89
|
}
|
|
104
90
|
|
|
105
|
-
if (S.selection && p ===
|
|
106
|
-
const el =
|
|
91
|
+
if (S.selection && p === getActivePanel()) {
|
|
92
|
+
const el = findCanvasElement(S.selection, p.canvas);
|
|
107
93
|
if (el) {
|
|
108
94
|
const desc = overlayBoxDescriptor(el, "selection", p);
|
|
109
95
|
if (view.componentInlineEdit || _ctx.isEditing()) /** @type {any} */ (desc).border = "none";
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
import {
|
|
8
8
|
update,
|
|
9
|
+
updateUi,
|
|
9
10
|
selectNode,
|
|
10
11
|
hoverNode,
|
|
11
12
|
elToPath,
|
|
@@ -21,6 +22,7 @@ import { stopEditing, isEditing, isEditableBlock } from "../editor/inline-edit.j
|
|
|
21
22
|
import { showContextMenu } from "../editor/context-menu.js";
|
|
22
23
|
import * as insertionHelper from "../editor/insertion-helper.js";
|
|
23
24
|
import { defaultDef } from "../panels/shared.js";
|
|
25
|
+
import { bubbleInlinePath, findCanvasElement, effectiveZoom } from "../canvas/canvas-helpers.js";
|
|
24
26
|
|
|
25
27
|
/** @type {any} */
|
|
26
28
|
let _ctx = null;
|
|
@@ -32,11 +34,8 @@ let _ctx = null;
|
|
|
32
34
|
* getState: () => any;
|
|
33
35
|
* setState: (s: any) => void;
|
|
34
36
|
* getCanvasMode: () => string;
|
|
35
|
-
* bubbleInlinePath: (doc: any, path: any) => any;
|
|
36
|
-
* findCanvasElement: (path: any, canvasEl: any) => any;
|
|
37
37
|
* enterInlineEdit: (el: any, path: any) => void;
|
|
38
38
|
* navigateToComponent: (path: any) => void;
|
|
39
|
-
* effectiveZoom: () => number;
|
|
40
39
|
* }} ctx
|
|
41
40
|
*/
|
|
42
41
|
export function initPanelEvents(ctx) {
|
|
@@ -90,12 +89,11 @@ export function registerPanelEvents(panel) {
|
|
|
90
89
|
if (canvas.contains(el) && el !== canvas) {
|
|
91
90
|
const originalPath = elToPath.get(el);
|
|
92
91
|
if (originalPath) {
|
|
93
|
-
let path =
|
|
92
|
+
let path = bubbleInlinePath(S.document, originalPath);
|
|
94
93
|
const newMedia = mediaName === "base" ? null : (mediaName ?? null);
|
|
95
94
|
const withMedia = { ...S, ui: { ...S.ui, activeMedia: newMedia } };
|
|
96
95
|
|
|
97
|
-
const resolvedEl =
|
|
98
|
-
path === originalPath ? el : _ctx.findCanvasElement(path, canvas) || el;
|
|
96
|
+
const resolvedEl = path === originalPath ? el : findCanvasElement(path, canvas) || el;
|
|
99
97
|
|
|
100
98
|
if (
|
|
101
99
|
pathsEqual(path, S.selection) &&
|
|
@@ -108,7 +106,7 @@ export function registerPanelEvents(panel) {
|
|
|
108
106
|
}
|
|
109
107
|
|
|
110
108
|
if (canvasMode === "design" && S.mode !== "content") {
|
|
111
|
-
|
|
109
|
+
updateUi("pendingInlineEdit", { path, mediaName });
|
|
112
110
|
update(selectNode(withMedia, path));
|
|
113
111
|
return;
|
|
114
112
|
}
|
|
@@ -149,9 +147,8 @@ export function registerPanelEvents(panel) {
|
|
|
149
147
|
if (canvas.contains(el) && el !== canvas) {
|
|
150
148
|
const originalPath = elToPath.get(el);
|
|
151
149
|
if (originalPath) {
|
|
152
|
-
const path =
|
|
153
|
-
const resolvedEl =
|
|
154
|
-
path === originalPath ? el : _ctx.findCanvasElement(path, canvas) || el;
|
|
150
|
+
const path = bubbleInlinePath(S.document, originalPath);
|
|
151
|
+
const resolvedEl = path === originalPath ? el : findCanvasElement(path, canvas) || el;
|
|
155
152
|
if (isEditableBlock(resolvedEl)) {
|
|
156
153
|
const newMedia = mediaName === "base" ? null : (mediaName ?? null);
|
|
157
154
|
const withMedia = { ...S, ui: { ...S.ui, activeMedia: newMedia } };
|
|
@@ -188,7 +185,7 @@ export function registerPanelEvents(panel) {
|
|
|
188
185
|
if (canvas.contains(el) && el !== canvas) {
|
|
189
186
|
let path = elToPath.get(el);
|
|
190
187
|
if (path) {
|
|
191
|
-
path =
|
|
188
|
+
path = bubbleInlinePath(S.document, path);
|
|
192
189
|
showContextMenu(e, path, S, { onEditComponent: _ctx.navigateToComponent });
|
|
193
190
|
return;
|
|
194
191
|
}
|
|
@@ -218,7 +215,7 @@ export function registerPanelEvents(panel) {
|
|
|
218
215
|
if (el && canvas.contains(el) && el !== canvas) {
|
|
219
216
|
let path = elToPath.get(el);
|
|
220
217
|
if (path) {
|
|
221
|
-
path =
|
|
218
|
+
path = bubbleInlinePath(S.document, path);
|
|
222
219
|
if (!pathsEqual(path, S.hover)) {
|
|
223
220
|
_ctx.setState(hoverNode(S, path));
|
|
224
221
|
renderOnly("overlays");
|
|
@@ -249,7 +246,7 @@ export function registerPanelEvents(panel) {
|
|
|
249
246
|
update,
|
|
250
247
|
getCanvasMode: _ctx.getCanvasMode,
|
|
251
248
|
withPanelPointerEvents,
|
|
252
|
-
effectiveZoom:
|
|
249
|
+
effectiveZoom: effectiveZoom,
|
|
253
250
|
defaultDef,
|
|
254
251
|
insertNode,
|
|
255
252
|
selectNode,
|
|
@@ -27,7 +27,7 @@ import { renderFieldRow } from "../ui/field-row.js";
|
|
|
27
27
|
import {
|
|
28
28
|
attrLabel,
|
|
29
29
|
inferInputType,
|
|
30
|
-
|
|
30
|
+
findContentTypeSchema,
|
|
31
31
|
friendlyNameToVar,
|
|
32
32
|
camelToLabel,
|
|
33
33
|
parseCemType,
|
|
@@ -210,7 +210,7 @@ function kvRow(
|
|
|
210
210
|
function renderFrontmatterOnlyPanel() {
|
|
211
211
|
const S = getState();
|
|
212
212
|
const fm = S.content?.frontmatter || {};
|
|
213
|
-
const col =
|
|
213
|
+
const col = findContentTypeSchema(S.documentPath, projectState?.projectConfig);
|
|
214
214
|
const schemaProps = col?.schema?.properties;
|
|
215
215
|
const requiredFields = new Set(col?.schema?.required || []);
|
|
216
216
|
|
|
@@ -1276,7 +1276,7 @@ export function renderPropertiesPanelTemplate(ctx) {
|
|
|
1276
1276
|
S.mode === "content"
|
|
1277
1277
|
? (() => {
|
|
1278
1278
|
const fm = S.content?.frontmatter || {};
|
|
1279
|
-
const col =
|
|
1279
|
+
const col = findContentTypeSchema(S.documentPath, projectState?.projectConfig);
|
|
1280
1280
|
const schemaProps = col?.schema?.properties;
|
|
1281
1281
|
const requiredFields = new Set(col?.schema?.required || []);
|
|
1282
1282
|
|
|
@@ -5,18 +5,11 @@
|
|
|
5
5
|
|
|
6
6
|
import { getState, getNodeAtPath } from "../store.js";
|
|
7
7
|
import { view } from "../view.js";
|
|
8
|
+
import { getActivePanel, findCanvasElement } from "../canvas/canvas-helpers.js";
|
|
8
9
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* Initialize the pseudo-preview module.
|
|
14
|
-
*
|
|
15
|
-
* @param {{ getActivePanel: Function; findCanvasElement: Function }} ctx
|
|
16
|
-
*/
|
|
17
|
-
export function initPseudoPreview(ctx) {
|
|
18
|
-
_ctx = ctx;
|
|
19
|
-
}
|
|
10
|
+
const pseudoStyleHost = document.createElement("div");
|
|
11
|
+
pseudoStyleHost.style.display = "contents";
|
|
12
|
+
(document.querySelector("sp-theme") || document.body).appendChild(pseudoStyleHost);
|
|
20
13
|
|
|
21
14
|
export function updateForcedPseudoPreview() {
|
|
22
15
|
if (view.forcedStyleTag) {
|
|
@@ -32,9 +25,9 @@ export function updateForcedPseudoPreview() {
|
|
|
32
25
|
const sel = S.ui?.activeSelector;
|
|
33
26
|
if (!sel || !sel.startsWith(":") || !S.selection) return;
|
|
34
27
|
|
|
35
|
-
const panel =
|
|
28
|
+
const panel = getActivePanel();
|
|
36
29
|
if (!panel) return;
|
|
37
|
-
const el =
|
|
30
|
+
const el = findCanvasElement(S.selection, panel.canvas);
|
|
38
31
|
if (!el) return;
|
|
39
32
|
|
|
40
33
|
const node = getNodeAtPath(S.document, S.selection);
|
|
@@ -59,6 +52,6 @@ export function updateForcedPseudoPreview() {
|
|
|
59
52
|
|
|
60
53
|
const tag = document.createElement("style");
|
|
61
54
|
tag.textContent = `[data-studio-forced] { ${cssProps} }`;
|
|
62
|
-
|
|
55
|
+
pseudoStyleHost.appendChild(tag);
|
|
63
56
|
view.forcedStyleTag = tag;
|
|
64
57
|
}
|
package/src/panels/shared.js
CHANGED
|
@@ -31,7 +31,7 @@ export function ensureLitState(container) {
|
|
|
31
31
|
const start = part._$startNode;
|
|
32
32
|
const end = part._$endNode;
|
|
33
33
|
const startBad = start && start.parentNode !== container;
|
|
34
|
-
const endBad = end && end !== container && end.parentNode !== container;
|
|
34
|
+
const endBad = (end && end !== container && end.parentNode !== container) || (!end && start);
|
|
35
35
|
if (startBad || endBad) {
|
|
36
36
|
console.warn("ensureLitState: clearing corrupted Lit state on", container.id || container);
|
|
37
37
|
container.textContent = "";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Stylebook panel — renders the Settings mode canvas (element catalog, design variables,
|
|
3
|
-
* definitions,
|
|
3
|
+
* definitions, content types). Extracted from studio.js Phase 4e.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import { html, render as litRender, nothing } from "lit-html";
|
|
@@ -26,7 +26,7 @@ import { parseMediaEntries, activeBreakpointsForWidth } from "../utils/canvas-me
|
|
|
26
26
|
import { mediaDisplayName } from "./shared.js";
|
|
27
27
|
import { friendlyNameToVar, varDisplayName } from "../utils/studio-utils.js";
|
|
28
28
|
import { renderDefsEditor } from "../settings/defs-editor.js";
|
|
29
|
-
import {
|
|
29
|
+
import { renderContentTypesEditor } from "../settings/content-types-editor.js";
|
|
30
30
|
import stylebookMeta from "../../data/stylebook-meta.json";
|
|
31
31
|
|
|
32
32
|
export { stylebookMeta };
|
|
@@ -67,12 +67,12 @@ export function renderStylebookMode(ctx) {
|
|
|
67
67
|
>
|
|
68
68
|
<sp-tab label="Stylebook" value="stylebook"></sp-tab>
|
|
69
69
|
<sp-tab label="Definitions" value="definitions"></sp-tab>
|
|
70
|
-
<sp-tab label="
|
|
70
|
+
<sp-tab label="Content Types" value="contentTypes"></sp-tab>
|
|
71
71
|
</sp-tabs>
|
|
72
72
|
</div>
|
|
73
73
|
`;
|
|
74
74
|
|
|
75
|
-
if (settingsTab === "definitions" || settingsTab === "
|
|
75
|
+
if (settingsTab === "definitions" || settingsTab === "contentTypes") {
|
|
76
76
|
/** @type {any} */ (canvasWrap).style.overflow = "hidden";
|
|
77
77
|
|
|
78
78
|
litRender(
|
|
@@ -88,7 +88,7 @@ export function renderStylebookMode(ctx) {
|
|
|
88
88
|
canvasWrap.querySelector(".settings-editor-container")
|
|
89
89
|
);
|
|
90
90
|
if (settingsTab === "definitions") renderDefsEditor(container);
|
|
91
|
-
else
|
|
91
|
+
else renderContentTypesEditor(container);
|
|
92
92
|
return;
|
|
93
93
|
}
|
|
94
94
|
|
|
@@ -261,12 +261,6 @@ export function selectStylebookTag(tag, media) {
|
|
|
261
261
|
},
|
|
262
262
|
});
|
|
263
263
|
renderStylebookOverlays();
|
|
264
|
-
requestAnimationFrame(() => {
|
|
265
|
-
if (canvasPanels.length > 0) {
|
|
266
|
-
const el = findStylebookEl(canvasPanels[0].canvas, tag);
|
|
267
|
-
if (el) el.scrollIntoView({ behavior: "smooth", block: "center" });
|
|
268
|
-
}
|
|
269
|
-
});
|
|
270
264
|
}
|
|
271
265
|
|
|
272
266
|
/** Draw selection + hover overlays for stylebook elements */
|
|
@@ -324,7 +318,7 @@ export function renderStylebookOverlays() {
|
|
|
324
318
|
}
|
|
325
319
|
}
|
|
326
320
|
|
|
327
|
-
// ─── Internal helpers
|
|
321
|
+
// ─── Internal helpers (exported for testing) ─────────────────────────────────
|
|
328
322
|
|
|
329
323
|
/**
|
|
330
324
|
* Build a DOM element tree from a stylebook-meta.json entry.
|
|
@@ -332,8 +326,9 @@ export function renderStylebookOverlays() {
|
|
|
332
326
|
* @param {any} entry
|
|
333
327
|
* @param {any} rootStyle
|
|
334
328
|
* @param {any} activeBreakpoints
|
|
329
|
+
* @param {string | null} [parentTag]
|
|
335
330
|
*/
|
|
336
|
-
function buildStylebookElement(entry, rootStyle, activeBreakpoints) {
|
|
331
|
+
export function buildStylebookElement(entry, rootStyle, activeBreakpoints, parentTag = null) {
|
|
337
332
|
const el = document.createElement(entry.tag);
|
|
338
333
|
if (entry.text) el.textContent = entry.text;
|
|
339
334
|
if (entry.attributes) {
|
|
@@ -344,7 +339,9 @@ function buildStylebookElement(entry, rootStyle, activeBreakpoints) {
|
|
|
344
339
|
}
|
|
345
340
|
}
|
|
346
341
|
if (entry.style) el.style.cssText = entry.style;
|
|
347
|
-
const
|
|
342
|
+
const compoundSelector =
|
|
343
|
+
parentTag && parentTag !== entry.tag ? `& ${parentTag} ${entry.tag}` : null;
|
|
344
|
+
const tagStyle = (compoundSelector && rootStyle[compoundSelector]) || rootStyle[`& ${entry.tag}`];
|
|
348
345
|
if (tagStyle) {
|
|
349
346
|
for (const [prop, val] of Object.entries(tagStyle)) {
|
|
350
347
|
if (typeof val === "string" || typeof val === "number") {
|
|
@@ -372,7 +369,7 @@ function buildStylebookElement(entry, rootStyle, activeBreakpoints) {
|
|
|
372
369
|
}
|
|
373
370
|
if (entry.children) {
|
|
374
371
|
for (const child of entry.children) {
|
|
375
|
-
el.appendChild(buildStylebookElement(child, rootStyle, activeBreakpoints));
|
|
372
|
+
el.appendChild(buildStylebookElement(child, rootStyle, activeBreakpoints, entry.tag));
|
|
376
373
|
}
|
|
377
374
|
}
|
|
378
375
|
return el;
|
|
@@ -429,13 +426,19 @@ function hasTagStyle(rootStyle, tag) {
|
|
|
429
426
|
* @param {any} customizedOnly
|
|
430
427
|
* @param {any} activeBreakpoints
|
|
431
428
|
*/
|
|
432
|
-
function renderStylebookElementsIntoCanvas(
|
|
429
|
+
export function renderStylebookElementsIntoCanvas(
|
|
433
430
|
canvasEl,
|
|
434
431
|
rootStyle,
|
|
435
432
|
filter,
|
|
436
433
|
customizedOnly,
|
|
437
434
|
activeBreakpoints,
|
|
438
435
|
) {
|
|
436
|
+
for (const [k, v] of Object.entries(rootStyle)) {
|
|
437
|
+
if (k.startsWith("--") && (typeof v === "string" || typeof v === "number")) {
|
|
438
|
+
canvasEl.style.setProperty(k, String(v));
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
|
|
439
442
|
/** @type {import("lit-html").TemplateResult[]} */
|
|
440
443
|
const sectionTemplates = [];
|
|
441
444
|
|
|
@@ -462,10 +465,11 @@ function renderStylebookElementsIntoCanvas(
|
|
|
462
465
|
view.stylebookElToTag.set(card, entry.tag);
|
|
463
466
|
elToPath.set(card, ["__sb", entry.tag]);
|
|
464
467
|
for (const child of el.querySelectorAll("*")) {
|
|
465
|
-
const
|
|
468
|
+
const childTag = child.tagName.toLowerCase();
|
|
466
469
|
if (!view.stylebookElToTag.has(child)) {
|
|
467
|
-
|
|
468
|
-
|
|
470
|
+
const compound = childTag === entry.tag ? entry.tag : `${entry.tag} ${childTag}`;
|
|
471
|
+
view.stylebookElToTag.set(child, compound);
|
|
472
|
+
elToPath.set(child, ["__sb", compound]);
|
|
469
473
|
}
|
|
470
474
|
}
|
|
471
475
|
})}
|
|
@@ -473,7 +477,10 @@ function renderStylebookElementsIntoCanvas(
|
|
|
473
477
|
<div
|
|
474
478
|
class="element-card-preview"
|
|
475
479
|
${ref((c) => {
|
|
476
|
-
if (c
|
|
480
|
+
if (c) {
|
|
481
|
+
c.textContent = "";
|
|
482
|
+
c.appendChild(el);
|
|
483
|
+
}
|
|
477
484
|
})}
|
|
478
485
|
></div>
|
|
479
486
|
<div class="element-card-label"><${entry.tag}></div>
|
package/src/platform.js
CHANGED
|
@@ -5,27 +5,30 @@
|
|
|
5
5
|
* platform adapter at startup. All file I/O, project loading, and component discovery goes through
|
|
6
6
|
* this interface.
|
|
7
7
|
*
|
|
8
|
+
* Uses window.__jxPlatform so the platform can be registered from a separate script bundle (e.g.
|
|
9
|
+
* init.js) before studio.js loads.
|
|
10
|
+
*
|
|
8
11
|
* See spec/desktop.md §3 for the full StudioPlatform interface.
|
|
9
12
|
*/
|
|
10
13
|
|
|
11
14
|
/** @typedef {Record<string, any>} StudioPlatform */
|
|
12
15
|
|
|
13
|
-
/** @type {
|
|
14
|
-
|
|
16
|
+
/** @type {any} */
|
|
17
|
+
const g = globalThis;
|
|
15
18
|
|
|
16
19
|
/** @param {StudioPlatform} platform */
|
|
17
20
|
export function registerPlatform(platform) {
|
|
18
|
-
|
|
21
|
+
g.__jxPlatform = platform;
|
|
19
22
|
}
|
|
20
23
|
|
|
21
24
|
/** @returns {StudioPlatform} */
|
|
22
25
|
export function getPlatform() {
|
|
23
|
-
if (!
|
|
26
|
+
if (!g.__jxPlatform)
|
|
24
27
|
throw new Error("No platform registered. Call registerPlatform() before starting Studio.");
|
|
25
|
-
return
|
|
28
|
+
return g.__jxPlatform;
|
|
26
29
|
}
|
|
27
30
|
|
|
28
31
|
/** @returns {boolean} */
|
|
29
32
|
export function hasPlatform() {
|
|
30
|
-
return
|
|
33
|
+
return g.__jxPlatform != null;
|
|
31
34
|
}
|