@jxsuite/studio 0.8.0 → 0.9.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.js +120246 -120127
- package/dist/studio.js.map +71 -63
- package/package.json +1 -1
- package/src/canvas/canvas-utils.js +313 -0
- package/src/editor/component-inline-edit.js +316 -0
- package/src/editor/content-inline-edit.js +220 -0
- package/src/panels/canvas-dnd.js +165 -0
- package/src/panels/panel-events.js +263 -0
- package/src/panels/preview-render.js +103 -0
- package/src/panels/pseudo-preview.js +64 -0
- package/src/studio.js +59 -1536
- package/src/utils/edit-display.js +197 -0
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Preview render — extracted from studio.js (Phase 4m). Structural preview renderer that creates
|
|
3
|
+
* DOM from Jx node trees as a fallback when runtime rendering fails.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { getState, elToPath } from "../store.js";
|
|
7
|
+
import { applyCanvasStyle } from "../utils/canvas-media.js";
|
|
8
|
+
import { resolveDefaultForCanvas } from "../panels/signals-panel.js";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Recursively render a Jx node to the canvas DOM. Media-aware: applies base styles + active
|
|
12
|
+
* breakpoint/feature overrides.
|
|
13
|
+
*
|
|
14
|
+
* @param {any} node
|
|
15
|
+
* @param {any} path
|
|
16
|
+
* @param {any} parent
|
|
17
|
+
* @param {any} activeBreakpoints
|
|
18
|
+
* @param {any} featureToggles
|
|
19
|
+
*/
|
|
20
|
+
export function renderCanvasNode(node, path, parent, activeBreakpoints, featureToggles) {
|
|
21
|
+
if (typeof node === "string" || typeof node === "number" || typeof node === "boolean") {
|
|
22
|
+
parent.appendChild(document.createTextNode(String(node)));
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
if (!node || typeof node !== "object") return;
|
|
26
|
+
|
|
27
|
+
const tag = node.tagName || "div";
|
|
28
|
+
const el = document.createElement(tag);
|
|
29
|
+
|
|
30
|
+
elToPath.set(el, path);
|
|
31
|
+
|
|
32
|
+
if (typeof node.textContent === "string") {
|
|
33
|
+
el.textContent = node.textContent;
|
|
34
|
+
} else if (typeof node.textContent === "object" && node.textContent?.$ref) {
|
|
35
|
+
const resolved = resolveDefaultForCanvas(node.textContent, getState().document.state);
|
|
36
|
+
el.textContent = resolved;
|
|
37
|
+
el.style.opacity = "0.7";
|
|
38
|
+
el.style.fontStyle = "italic";
|
|
39
|
+
el.title = `Bound: ${node.textContent.$ref}`;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (node.id) el.id = node.id;
|
|
43
|
+
if (node.className) el.className = node.className;
|
|
44
|
+
|
|
45
|
+
applyCanvasStyle(el, node.style, activeBreakpoints, featureToggles);
|
|
46
|
+
|
|
47
|
+
if (node.attributes && typeof node.attributes === "object") {
|
|
48
|
+
for (const [attr, val] of Object.entries(node.attributes)) {
|
|
49
|
+
try {
|
|
50
|
+
if (typeof val === "object" && val?.$ref) {
|
|
51
|
+
const resolved = resolveDefaultForCanvas(val, getState().document.state);
|
|
52
|
+
el.setAttribute(attr, resolved);
|
|
53
|
+
} else {
|
|
54
|
+
el.setAttribute(attr, val);
|
|
55
|
+
}
|
|
56
|
+
} catch {}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (Array.isArray(node.children)) {
|
|
61
|
+
for (let i = 0; i < node.children.length; i++) {
|
|
62
|
+
renderCanvasNode(
|
|
63
|
+
node.children[i],
|
|
64
|
+
[...path, "children", i],
|
|
65
|
+
el,
|
|
66
|
+
activeBreakpoints,
|
|
67
|
+
featureToggles,
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
} else if (
|
|
71
|
+
node.children &&
|
|
72
|
+
typeof node.children === "object" &&
|
|
73
|
+
node.children.$prototype === "Array"
|
|
74
|
+
) {
|
|
75
|
+
const template = node.children.map;
|
|
76
|
+
if (template && typeof template === "object") {
|
|
77
|
+
const wrapper = document.createElement("div");
|
|
78
|
+
wrapper.className = "repeater-perimeter";
|
|
79
|
+
elToPath.set(wrapper, [...path, "children"]);
|
|
80
|
+
renderCanvasNode(
|
|
81
|
+
template,
|
|
82
|
+
[...path, "children", "map"],
|
|
83
|
+
wrapper,
|
|
84
|
+
activeBreakpoints,
|
|
85
|
+
featureToggles,
|
|
86
|
+
);
|
|
87
|
+
el.appendChild(wrapper);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (node.$switch && node.cases && typeof node.cases === "object") {
|
|
92
|
+
const keys = Object.keys(node.cases);
|
|
93
|
+
const placeholder = document.createElement("div");
|
|
94
|
+
placeholder.textContent = `[$switch: ${keys.join(" | ")}]`;
|
|
95
|
+
placeholder.style.cssText =
|
|
96
|
+
"font-family:monospace;font-size:11px;padding:6px 10px;background:color-mix(in srgb, var(--danger) 8%, transparent);border:1px dashed color-mix(in srgb, var(--danger) 40%, transparent);border-radius:4px;color:var(--danger);font-style:italic";
|
|
97
|
+
el.appendChild(placeholder);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
el.style.pointerEvents = "none";
|
|
101
|
+
parent.appendChild(el);
|
|
102
|
+
return el;
|
|
103
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pseudo-state preview — extracted from studio.js (Phase 4m). When a pseudo-selector (:hover,
|
|
3
|
+
* :focus, etc.) is active in the style sidebar, force those styles onto the selected element.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { getState, getNodeAtPath } from "../store.js";
|
|
7
|
+
import { view } from "../view.js";
|
|
8
|
+
|
|
9
|
+
/** @type {any} */
|
|
10
|
+
let _ctx = null;
|
|
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
|
+
}
|
|
20
|
+
|
|
21
|
+
export function updateForcedPseudoPreview() {
|
|
22
|
+
if (view.forcedStyleTag) {
|
|
23
|
+
view.forcedStyleTag.remove();
|
|
24
|
+
view.forcedStyleTag = null;
|
|
25
|
+
}
|
|
26
|
+
if (view.forcedAttrEl) {
|
|
27
|
+
view.forcedAttrEl.removeAttribute("data-studio-forced");
|
|
28
|
+
view.forcedAttrEl = null;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const S = getState();
|
|
32
|
+
const sel = S.ui?.activeSelector;
|
|
33
|
+
if (!sel || !sel.startsWith(":") || !S.selection) return;
|
|
34
|
+
|
|
35
|
+
const panel = _ctx.getActivePanel();
|
|
36
|
+
if (!panel) return;
|
|
37
|
+
const el = _ctx.findCanvasElement(S.selection, panel.canvas);
|
|
38
|
+
if (!el) return;
|
|
39
|
+
|
|
40
|
+
const node = getNodeAtPath(S.document, S.selection);
|
|
41
|
+
if (!node?.style) return;
|
|
42
|
+
const activeTab = S.ui.activeMedia;
|
|
43
|
+
/** @type {any} */
|
|
44
|
+
const ctx = activeTab ? node.style[`@${activeTab}`] || {} : node.style;
|
|
45
|
+
const rules = ctx[sel];
|
|
46
|
+
if (!rules || typeof rules !== "object") return;
|
|
47
|
+
|
|
48
|
+
const cssProps = Object.entries(rules)
|
|
49
|
+
.filter(([k]) => typeof rules[k] === "string" || typeof rules[k] === "number")
|
|
50
|
+
.map(
|
|
51
|
+
([k, v]) =>
|
|
52
|
+
`${k.replace(/[A-Z]/g, (/** @type {any} */ c) => `-${c.toLowerCase()}`)}: ${v} !important`,
|
|
53
|
+
)
|
|
54
|
+
.join("; ");
|
|
55
|
+
if (!cssProps) return;
|
|
56
|
+
|
|
57
|
+
el.setAttribute("data-studio-forced", "1");
|
|
58
|
+
view.forcedAttrEl = el;
|
|
59
|
+
|
|
60
|
+
const tag = document.createElement("style");
|
|
61
|
+
tag.textContent = `[data-studio-forced] { ${cssProps} }`;
|
|
62
|
+
document.head.appendChild(tag);
|
|
63
|
+
view.forcedStyleTag = tag;
|
|
64
|
+
}
|