@jxsuite/studio 0.13.0 → 0.15.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 +467 -625
- package/dist/studio.js.map +37 -37
- package/package.json +1 -1
- package/src/canvas/canvas-live-render.js +25 -0
- package/src/canvas/canvas-render.js +20 -2
- package/src/canvas/canvas-utils.js +1 -2
- package/src/editor/component-inline-edit.js +1 -3
- package/src/editor/content-inline-edit.js +1 -3
- package/src/editor/context-menu.js +73 -35
- package/src/editor/insertion-helper.js +0 -1
- package/src/editor/shortcuts.js +39 -45
- package/src/files/file-ops.js +57 -108
- package/src/files/files.js +13 -8
- package/src/panels/activity-bar.js +6 -4
- package/src/panels/canvas-dnd.js +4 -10
- package/src/panels/dnd.js +45 -7
- package/src/panels/editors.js +14 -16
- package/src/panels/elements-panel.js +13 -13
- package/src/panels/git-panel.js +2 -1
- package/src/panels/layers-panel.js +11 -12
- package/src/panels/left-panel.js +2 -2
- package/src/panels/panel-events.js +19 -25
- package/src/panels/preview-render.js +7 -3
- package/src/panels/pseudo-preview.js +9 -8
- package/src/panels/statusbar.js +8 -16
- package/src/panels/style-inputs.js +2 -3
- package/src/panels/style-utils.js +8 -6
- package/src/panels/stylebook-layers-panel.js +5 -5
- package/src/panels/stylebook-panel.js +16 -16
- package/src/panels/toolbar.js +2 -2
- package/src/state.js +2 -3
- package/src/store.js +2 -133
- package/src/studio.js +111 -239
- package/src/tabs/tab.js +19 -4
- package/src/tabs/transact.js +19 -1
- package/src/ui/color-selector.js +4 -5
- package/src/view.js +3 -0
- package/src/workspace/workspace.js +5 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/** Elements panel — block/component palette with categorized accordion and search filter. */
|
|
2
2
|
|
|
3
3
|
import { html, nothing } from "lit-html";
|
|
4
|
-
import {
|
|
4
|
+
import { getNodeAtPath } from "../store.js";
|
|
5
5
|
import { activeTab } from "../workspace/workspace.js";
|
|
6
6
|
import { transactDoc, mutateInsertNode } from "../tabs/transact.js";
|
|
7
7
|
import { view } from "../view.js";
|
|
@@ -13,7 +13,7 @@ import { componentRegistry } from "../files/components.js";
|
|
|
13
13
|
* @returns {import("lit-html").TemplateResult}
|
|
14
14
|
*/
|
|
15
15
|
export function renderElementsTemplate(ctx) {
|
|
16
|
-
const
|
|
16
|
+
const tab = activeTab.value;
|
|
17
17
|
|
|
18
18
|
const categories = Object.entries(ctx.webdata.elements).map(
|
|
19
19
|
(/** @type {any} */ [category, elements]) => {
|
|
@@ -38,12 +38,12 @@ export function renderElementsTemplate(ctx) {
|
|
|
38
38
|
class="element-card"
|
|
39
39
|
data-block-tag=${tag}
|
|
40
40
|
@click=${() => {
|
|
41
|
-
const
|
|
42
|
-
const parentPath =
|
|
43
|
-
const parent = getNodeAtPath(
|
|
41
|
+
const t = activeTab.value;
|
|
42
|
+
const parentPath = t?.session.selection || [];
|
|
43
|
+
const parent = getNodeAtPath(t?.doc.document, parentPath);
|
|
44
44
|
const idx = parent?.children ? parent.children.length : 0;
|
|
45
|
-
transactDoc(
|
|
46
|
-
mutateInsertNode(
|
|
45
|
+
transactDoc(t, (tr) =>
|
|
46
|
+
mutateInsertNode(tr, parentPath, idx, structuredClone(def)),
|
|
47
47
|
);
|
|
48
48
|
}}
|
|
49
49
|
>
|
|
@@ -57,7 +57,7 @@ export function renderElementsTemplate(ctx) {
|
|
|
57
57
|
},
|
|
58
58
|
);
|
|
59
59
|
|
|
60
|
-
const effectiveEls = getEffectiveElements(
|
|
60
|
+
const effectiveEls = getEffectiveElements(tab?.doc.document?.$elements);
|
|
61
61
|
/** @type {Set<string>} */
|
|
62
62
|
const enabledTags = new Set();
|
|
63
63
|
for (const entry of effectiveEls) {
|
|
@@ -105,9 +105,9 @@ export function renderElementsTemplate(ctx) {
|
|
|
105
105
|
? `${comp.package}: <${comp.tagName}>`
|
|
106
106
|
: comp.path}
|
|
107
107
|
@click=${() => {
|
|
108
|
-
const
|
|
109
|
-
const parentPath =
|
|
110
|
-
const parent = getNodeAtPath(
|
|
108
|
+
const t = activeTab.value;
|
|
109
|
+
const parentPath = t?.session.selection || [];
|
|
110
|
+
const parent = getNodeAtPath(t?.doc.document, parentPath);
|
|
111
111
|
const idx = parent?.children ? parent.children.length : 0;
|
|
112
112
|
const instanceDef = {
|
|
113
113
|
tagName: comp.tagName,
|
|
@@ -118,8 +118,8 @@ export function renderElementsTemplate(ctx) {
|
|
|
118
118
|
]),
|
|
119
119
|
),
|
|
120
120
|
};
|
|
121
|
-
transactDoc(
|
|
122
|
-
mutateInsertNode(
|
|
121
|
+
transactDoc(t, (tr) =>
|
|
122
|
+
mutateInsertNode(tr, parentPath, idx, structuredClone(instanceDef)),
|
|
123
123
|
);
|
|
124
124
|
}}
|
|
125
125
|
>
|
package/src/panels/git-panel.js
CHANGED
|
@@ -7,6 +7,7 @@ import { html, nothing } from "lit-html";
|
|
|
7
7
|
import { live } from "lit-html/directives/live.js";
|
|
8
8
|
import { getPlatform } from "../platform.js";
|
|
9
9
|
import { updateUi, renderOnly } from "../store.js";
|
|
10
|
+
import { view } from "../view.js";
|
|
10
11
|
|
|
11
12
|
async function refreshGitStatus() {
|
|
12
13
|
const plat = getPlatform();
|
|
@@ -60,7 +61,7 @@ export function renderGitPanel(S, ctx) {
|
|
|
60
61
|
|
|
61
62
|
if (!_pollTimer) {
|
|
62
63
|
_pollTimer = setInterval(() => {
|
|
63
|
-
if (
|
|
64
|
+
if (view.leftTab === "git" && !S.ui.gitLoading) refreshGitStatus();
|
|
64
65
|
}, 30000);
|
|
65
66
|
}
|
|
66
67
|
|
|
@@ -5,7 +5,6 @@
|
|
|
5
5
|
|
|
6
6
|
import { html, nothing } from "lit-html";
|
|
7
7
|
import {
|
|
8
|
-
getState,
|
|
9
8
|
flattenTree,
|
|
10
9
|
getNodeAtPath,
|
|
11
10
|
pathKey,
|
|
@@ -26,14 +25,13 @@ import { showContextMenu } from "../editor/context-menu.js";
|
|
|
26
25
|
* @returns {import("lit-html").TemplateResult}
|
|
27
26
|
*/
|
|
28
27
|
export function renderLayersTemplate(ctx) {
|
|
29
|
-
const
|
|
28
|
+
const tab = activeTab.value;
|
|
30
29
|
|
|
31
30
|
for (const fn of view.dndCleanups) fn();
|
|
32
31
|
view.dndCleanups = [];
|
|
33
32
|
|
|
34
|
-
const rows = flattenTree(
|
|
35
|
-
const
|
|
36
|
-
const collapsed = _S._collapsed || (_S._collapsed = new Set());
|
|
33
|
+
const rows = flattenTree(tab?.doc.document);
|
|
34
|
+
const collapsed = view._layersCollapsed || (view._layersCollapsed = new Set());
|
|
37
35
|
|
|
38
36
|
/** @type {any[]} */
|
|
39
37
|
const layerRows = [];
|
|
@@ -48,7 +46,7 @@ export function renderLayersTemplate(ctx) {
|
|
|
48
46
|
}
|
|
49
47
|
if (hidden) continue;
|
|
50
48
|
|
|
51
|
-
if (
|
|
49
|
+
if (tab?.doc.mode === "content" && path.length === 0) continue;
|
|
52
50
|
|
|
53
51
|
if (nodeType === "text") {
|
|
54
52
|
const textPreview = String(node).length > 40 ? String(node).slice(0, 40) + "…" : String(node);
|
|
@@ -66,12 +64,12 @@ export function renderLayersTemplate(ctx) {
|
|
|
66
64
|
|
|
67
65
|
if (path.length >= 2 && nodeType === "element") {
|
|
68
66
|
const pPath = parentElementPath(path);
|
|
69
|
-
const parentNode = pPath ? getNodeAtPath(
|
|
67
|
+
const parentNode = pPath ? getNodeAtPath(tab?.doc.document, pPath) : null;
|
|
70
68
|
if (parentNode && isInlineElement(node, parentNode)) continue;
|
|
71
69
|
}
|
|
72
70
|
|
|
73
71
|
const key = pathKey(path);
|
|
74
|
-
const isSelected = pathsEqual(path,
|
|
72
|
+
const isSelected = pathsEqual(path, tab?.session.selection);
|
|
75
73
|
const hasChildren = Array.isArray(node.children) && node.children.length > 0;
|
|
76
74
|
const hasMapChildren =
|
|
77
75
|
node.children && typeof node.children === "object" && node.children.$prototype === "Array";
|
|
@@ -115,10 +113,10 @@ export function renderLayersTemplate(ctx) {
|
|
|
115
113
|
}
|
|
116
114
|
|
|
117
115
|
const isElement = nodeType === "element";
|
|
118
|
-
const isRoot =
|
|
116
|
+
const isRoot = tab?.doc.mode === "content" ? path.length === 0 : path.length < 2;
|
|
119
117
|
const idx = isElement ? /** @type {number} */ (childIndex(path)) : 0;
|
|
120
118
|
const parentPath = isElement && !isRoot ? /** @type {any} */ (parentElementPath(path)) : null;
|
|
121
|
-
const parentNode = parentPath ? getNodeAtPath(
|
|
119
|
+
const parentNode = parentPath ? getNodeAtPath(tab?.doc.document, parentPath) : null;
|
|
122
120
|
const siblingCount = parentNode?.children?.length || 0;
|
|
123
121
|
const canMoveUp = isElement && !isRoot && idx > 0;
|
|
124
122
|
const canMoveDown = isElement && !isRoot && idx < siblingCount - 1;
|
|
@@ -141,10 +139,11 @@ export function renderLayersTemplate(ctx) {
|
|
|
141
139
|
data-dnd-row=${isElement ? key : nothing}
|
|
142
140
|
data-dnd-depth=${isElement ? depth : nothing}
|
|
143
141
|
data-dnd-void=${isElement && isVoidEl ? "" : nothing}
|
|
142
|
+
data-dnd-expanded=${isElement && isExpandable && !collapsed.has(key) ? "" : nothing}
|
|
144
143
|
@click=${() => (activeTab.value.session.selection = path)}
|
|
145
144
|
@contextmenu=${isElement
|
|
146
145
|
? (/** @type {any} */ e) =>
|
|
147
|
-
showContextMenu(e, path,
|
|
146
|
+
showContextMenu(e, path, {
|
|
148
147
|
onEditComponent: ctx.navigateToComponent,
|
|
149
148
|
})
|
|
150
149
|
: nothing}
|
|
@@ -207,7 +206,7 @@ export function renderLayersTemplate(ctx) {
|
|
|
207
206
|
e.stopPropagation();
|
|
208
207
|
/** @type {HTMLElement} */ (e.currentTarget).blur();
|
|
209
208
|
const prevPath = [...parentPath, idx - 1];
|
|
210
|
-
const prev = getNodeAtPath(
|
|
209
|
+
const prev = getNodeAtPath(activeTab.value?.doc.document, prevPath);
|
|
211
210
|
const len = prev?.children?.length || 0;
|
|
212
211
|
transactDoc(activeTab.value, (t) => mutateMoveNode(t, path, prevPath, len));
|
|
213
212
|
}}
|
package/src/panels/left-panel.js
CHANGED
|
@@ -10,6 +10,7 @@ import { html, render as litRender, nothing } from "lit-html";
|
|
|
10
10
|
import { leftPanel, updateSession } from "../store.js";
|
|
11
11
|
import { effect, effectScope } from "../reactivity.js";
|
|
12
12
|
import { activeTab } from "../workspace/workspace.js";
|
|
13
|
+
import { view } from "../view.js";
|
|
13
14
|
import { transact, mutateUpdateFrontmatter } from "../tabs/transact.js";
|
|
14
15
|
|
|
15
16
|
import { renderLayersTemplate } from "./layers-panel.js";
|
|
@@ -67,7 +68,6 @@ export function mount(ctx) {
|
|
|
67
68
|
void tab.doc.document;
|
|
68
69
|
void tab.doc.mode;
|
|
69
70
|
void tab.session.selection;
|
|
70
|
-
void tab.session.ui.leftTab;
|
|
71
71
|
void tab.session.ui.settingsTab;
|
|
72
72
|
render();
|
|
73
73
|
});
|
|
@@ -121,7 +121,7 @@ function _render() {
|
|
|
121
121
|
mode: aTab.doc.mode,
|
|
122
122
|
selection: aTab.session.selection,
|
|
123
123
|
});
|
|
124
|
-
const tab =
|
|
124
|
+
const tab = view.leftTab;
|
|
125
125
|
|
|
126
126
|
/** @type {TemplateResult | typeof nothing} */
|
|
127
127
|
let content;
|
|
@@ -6,7 +6,6 @@
|
|
|
6
6
|
|
|
7
7
|
import {
|
|
8
8
|
updateUi,
|
|
9
|
-
hoverNode,
|
|
10
9
|
elToPath,
|
|
11
10
|
pathsEqual,
|
|
12
11
|
parentElementPath,
|
|
@@ -30,8 +29,6 @@ let _ctx = null;
|
|
|
30
29
|
* Initialize the panel events module.
|
|
31
30
|
*
|
|
32
31
|
* @param {{
|
|
33
|
-
* getState: () => any;
|
|
34
|
-
* setState: (s: any) => void;
|
|
35
32
|
* getCanvasMode: () => string;
|
|
36
33
|
* enterInlineEdit: (el: any, path: any) => void;
|
|
37
34
|
* navigateToComponent: (path: any) => void;
|
|
@@ -77,7 +74,7 @@ export function registerPanelEvents(panel) {
|
|
|
77
74
|
stopEditing();
|
|
78
75
|
}
|
|
79
76
|
|
|
80
|
-
const
|
|
77
|
+
const tab = activeTab.value;
|
|
81
78
|
const canvasMode = _ctx.getCanvasMode();
|
|
82
79
|
|
|
83
80
|
const elements = withPanelPointerEvents(() =>
|
|
@@ -97,23 +94,22 @@ export function registerPanelEvents(panel) {
|
|
|
97
94
|
|
|
98
95
|
const originalPath = elToPath.get(el);
|
|
99
96
|
if (originalPath) {
|
|
100
|
-
let path = bubbleInlinePath(
|
|
97
|
+
let path = bubbleInlinePath(tab?.doc.document, originalPath);
|
|
101
98
|
const newMedia = mediaName === "base" ? null : (mediaName ?? null);
|
|
102
|
-
const withMedia = { ...S, ui: { ...S.ui, activeMedia: newMedia } };
|
|
103
99
|
|
|
104
100
|
const resolvedEl = path === originalPath ? el : findCanvasElement(path, canvas) || el;
|
|
105
101
|
|
|
106
102
|
if (
|
|
107
|
-
pathsEqual(path,
|
|
103
|
+
pathsEqual(path, tab?.session.selection) &&
|
|
108
104
|
isEditableBlock(resolvedEl) &&
|
|
109
|
-
(canvasMode === "edit" ||
|
|
105
|
+
(canvasMode === "edit" || tab?.doc.mode === "content")
|
|
110
106
|
) {
|
|
111
|
-
|
|
107
|
+
activeTab.value.session.ui.activeMedia = newMedia;
|
|
112
108
|
_ctx.enterInlineEdit(resolvedEl, path);
|
|
113
109
|
return;
|
|
114
110
|
}
|
|
115
111
|
|
|
116
|
-
if (canvasMode === "design" &&
|
|
112
|
+
if (canvasMode === "design" && tab?.doc.mode !== "content") {
|
|
117
113
|
updateUi("pendingInlineEdit", { path, mediaName });
|
|
118
114
|
activeTab.value.session.ui.activeMedia = newMedia;
|
|
119
115
|
activeTab.value.session.selection = path;
|
|
@@ -148,7 +144,7 @@ export function registerPanelEvents(panel) {
|
|
|
148
144
|
const canvasMode = _ctx.getCanvasMode();
|
|
149
145
|
if (canvasMode !== "edit" && canvasMode !== "design") return;
|
|
150
146
|
|
|
151
|
-
const
|
|
147
|
+
const tab = activeTab.value;
|
|
152
148
|
const elements = withPanelPointerEvents(() =>
|
|
153
149
|
document.elementsFromPoint(e.clientX, e.clientY),
|
|
154
150
|
);
|
|
@@ -157,7 +153,7 @@ export function registerPanelEvents(panel) {
|
|
|
157
153
|
if (canvas.contains(el) && el !== canvas) {
|
|
158
154
|
const originalPath = elToPath.get(el);
|
|
159
155
|
if (originalPath) {
|
|
160
|
-
const path = bubbleInlinePath(
|
|
156
|
+
const path = bubbleInlinePath(tab?.doc.document, originalPath);
|
|
161
157
|
const resolvedEl = path === originalPath ? el : findCanvasElement(path, canvas) || el;
|
|
162
158
|
if (isEditableBlock(resolvedEl)) {
|
|
163
159
|
const newMedia = mediaName === "base" ? null : (mediaName ?? null);
|
|
@@ -187,7 +183,7 @@ export function registerPanelEvents(panel) {
|
|
|
187
183
|
)
|
|
188
184
|
return;
|
|
189
185
|
}
|
|
190
|
-
const
|
|
186
|
+
const tab = activeTab.value;
|
|
191
187
|
const elements = withPanelPointerEvents(() =>
|
|
192
188
|
document.elementsFromPoint(e.clientX, e.clientY),
|
|
193
189
|
);
|
|
@@ -195,8 +191,8 @@ export function registerPanelEvents(panel) {
|
|
|
195
191
|
if (canvas.contains(el) && el !== canvas) {
|
|
196
192
|
let path = elToPath.get(el);
|
|
197
193
|
if (path) {
|
|
198
|
-
path = bubbleInlinePath(
|
|
199
|
-
showContextMenu(e, path,
|
|
194
|
+
path = bubbleInlinePath(tab?.doc.document, path);
|
|
195
|
+
showContextMenu(e, path, { onEditComponent: _ctx.navigateToComponent });
|
|
200
196
|
return;
|
|
201
197
|
}
|
|
202
198
|
}
|
|
@@ -220,19 +216,19 @@ export function registerPanelEvents(panel) {
|
|
|
220
216
|
)
|
|
221
217
|
return;
|
|
222
218
|
}
|
|
223
|
-
|
|
219
|
+
const tab = activeTab.value;
|
|
224
220
|
const el = withPanelPointerEvents(() => document.elementFromPoint(e.clientX, e.clientY));
|
|
225
221
|
if (el && canvas.contains(el) && el !== canvas) {
|
|
226
222
|
let path = elToPath.get(el);
|
|
227
223
|
if (path) {
|
|
228
|
-
path = bubbleInlinePath(
|
|
229
|
-
if (!pathsEqual(path,
|
|
230
|
-
|
|
224
|
+
path = bubbleInlinePath(tab?.doc.document, path);
|
|
225
|
+
if (!pathsEqual(path, tab?.session.hover)) {
|
|
226
|
+
activeTab.value.session.hover = path;
|
|
231
227
|
renderOnly("overlays");
|
|
232
228
|
}
|
|
233
229
|
}
|
|
234
|
-
} else if (
|
|
235
|
-
|
|
230
|
+
} else if (tab?.session.hover) {
|
|
231
|
+
activeTab.value.session.hover = null;
|
|
236
232
|
renderOnly("overlays");
|
|
237
233
|
}
|
|
238
234
|
},
|
|
@@ -242,9 +238,8 @@ export function registerPanelEvents(panel) {
|
|
|
242
238
|
overlayClk.addEventListener(
|
|
243
239
|
"mouseleave",
|
|
244
240
|
() => {
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
_ctx.setState(hoverNode(S, null));
|
|
241
|
+
if (activeTab.value?.session.hover) {
|
|
242
|
+
activeTab.value.session.hover = null;
|
|
248
243
|
renderOnly("overlays");
|
|
249
244
|
}
|
|
250
245
|
},
|
|
@@ -252,7 +247,6 @@ export function registerPanelEvents(panel) {
|
|
|
252
247
|
);
|
|
253
248
|
|
|
254
249
|
insertionHelper.mount({
|
|
255
|
-
getState: _ctx.getState,
|
|
256
250
|
getCanvasMode: _ctx.getCanvasMode,
|
|
257
251
|
withPanelPointerEvents,
|
|
258
252
|
effectiveZoom: effectiveZoom,
|
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
* DOM from Jx node trees as a fallback when runtime rendering fails.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import {
|
|
6
|
+
import { elToPath } from "../store.js";
|
|
7
|
+
import { activeTab } from "../workspace/workspace.js";
|
|
7
8
|
import { applyCanvasStyle } from "../utils/canvas-media.js";
|
|
8
9
|
import { resolveDefaultForCanvas } from "../panels/signals-panel.js";
|
|
9
10
|
|
|
@@ -32,7 +33,10 @@ export function renderCanvasNode(node, path, parent, activeBreakpoints, featureT
|
|
|
32
33
|
if (typeof node.textContent === "string") {
|
|
33
34
|
el.textContent = node.textContent;
|
|
34
35
|
} else if (typeof node.textContent === "object" && node.textContent?.$ref) {
|
|
35
|
-
const resolved = resolveDefaultForCanvas(
|
|
36
|
+
const resolved = resolveDefaultForCanvas(
|
|
37
|
+
node.textContent,
|
|
38
|
+
activeTab.value?.doc.document?.state,
|
|
39
|
+
);
|
|
36
40
|
el.textContent = resolved;
|
|
37
41
|
el.style.opacity = "0.7";
|
|
38
42
|
el.style.fontStyle = "italic";
|
|
@@ -48,7 +52,7 @@ export function renderCanvasNode(node, path, parent, activeBreakpoints, featureT
|
|
|
48
52
|
for (const [attr, val] of Object.entries(node.attributes)) {
|
|
49
53
|
try {
|
|
50
54
|
if (typeof val === "object" && val?.$ref) {
|
|
51
|
-
const resolved = resolveDefaultForCanvas(val,
|
|
55
|
+
const resolved = resolveDefaultForCanvas(val, activeTab.value?.doc.document?.state);
|
|
52
56
|
el.setAttribute(attr, resolved);
|
|
53
57
|
} else {
|
|
54
58
|
el.setAttribute(attr, val);
|
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
* :focus, etc.) is active in the style sidebar, force those styles onto the selected element.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import {
|
|
6
|
+
import { getNodeAtPath } from "../store.js";
|
|
7
|
+
import { activeTab } from "../workspace/workspace.js";
|
|
7
8
|
import { view } from "../view.js";
|
|
8
9
|
import { getActivePanel, findCanvasElement } from "../canvas/canvas-helpers.js";
|
|
9
10
|
|
|
@@ -21,20 +22,20 @@ export function updateForcedPseudoPreview() {
|
|
|
21
22
|
view.forcedAttrEl = null;
|
|
22
23
|
}
|
|
23
24
|
|
|
24
|
-
const
|
|
25
|
-
const sel =
|
|
26
|
-
if (!sel || !sel.startsWith(":") || !
|
|
25
|
+
const tab = activeTab.value;
|
|
26
|
+
const sel = tab?.session.ui?.activeSelector;
|
|
27
|
+
if (!sel || !sel.startsWith(":") || !tab?.session.selection) return;
|
|
27
28
|
|
|
28
29
|
const panel = getActivePanel();
|
|
29
30
|
if (!panel) return;
|
|
30
|
-
const el = findCanvasElement(
|
|
31
|
+
const el = findCanvasElement(tab.session.selection, panel.canvas);
|
|
31
32
|
if (!el) return;
|
|
32
33
|
|
|
33
|
-
const node = getNodeAtPath(
|
|
34
|
+
const node = getNodeAtPath(tab.doc.document, tab.session.selection);
|
|
34
35
|
if (!node?.style) return;
|
|
35
|
-
const
|
|
36
|
+
const activeMedia = tab.session.ui.activeMedia;
|
|
36
37
|
/** @type {any} */
|
|
37
|
-
const ctx =
|
|
38
|
+
const ctx = activeMedia ? node.style[`@${activeMedia}`] || {} : node.style;
|
|
38
39
|
const rules = ctx[sel];
|
|
39
40
|
if (!rules || typeof rules !== "object") return;
|
|
40
41
|
|
package/src/panels/statusbar.js
CHANGED
|
@@ -34,11 +34,7 @@ export function mountStatusbar() {
|
|
|
34
34
|
void tab.doc.document;
|
|
35
35
|
void tab.doc.mode;
|
|
36
36
|
void tab.session.selection;
|
|
37
|
-
renderStatusbar(
|
|
38
|
-
mode: tab.doc.mode,
|
|
39
|
-
document: tab.doc.document,
|
|
40
|
-
selection: tab.session.selection,
|
|
41
|
-
});
|
|
37
|
+
renderStatusbar();
|
|
42
38
|
});
|
|
43
39
|
});
|
|
44
40
|
}
|
|
@@ -50,19 +46,15 @@ export function unmountStatusbar() {
|
|
|
50
46
|
|
|
51
47
|
// ─── Statusbar ───────────────────────────────────────────────────────────────
|
|
52
48
|
|
|
53
|
-
/**
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
*
|
|
57
|
-
* @param {any} S - Current studio state
|
|
58
|
-
*/
|
|
59
|
-
export function renderStatusbar(S) {
|
|
49
|
+
/** Render the statusbar text. */
|
|
50
|
+
export function renderStatusbar() {
|
|
51
|
+
const tab = activeTab.value;
|
|
60
52
|
const parts = [];
|
|
61
|
-
if (
|
|
62
|
-
if (
|
|
63
|
-
const node = getNodeAtPath(
|
|
53
|
+
if (tab?.doc.mode === "content") parts.push("Content Mode");
|
|
54
|
+
if (tab?.session.selection) {
|
|
55
|
+
const node = getNodeAtPath(tab.doc.document, tab.session.selection);
|
|
64
56
|
parts.push(`Selected: ${nodeLabel(node)}`);
|
|
65
|
-
parts.push(`Path: ${
|
|
57
|
+
parts.push(`Path: ${tab.session.selection.join(" > ") || "root"}`);
|
|
66
58
|
}
|
|
67
59
|
if (statusMsg) parts.push(statusMsg);
|
|
68
60
|
statusbarEl.textContent = parts.join(" | ") || "Jx Studio";
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import { html } from "lit-html";
|
|
4
4
|
import { live } from "lit-html/directives/live.js";
|
|
5
|
-
import {
|
|
5
|
+
import { debouncedStyleCommit } from "../store.js";
|
|
6
6
|
import { activeTab } from "../workspace/workspace.js";
|
|
7
7
|
import { transactDoc, mutateUpdateStyle } from "../tabs/transact.js";
|
|
8
8
|
import { widgetForType as _widgetForType } from "../ui/widgets.js";
|
|
@@ -53,9 +53,8 @@ export function renderSelectInput(entry, prop, value, onChange) {
|
|
|
53
53
|
|
|
54
54
|
/** @param {any} preset @param {any} onChange */
|
|
55
55
|
function handleFontPresetSelection(preset, onChange) {
|
|
56
|
-
const S = getState();
|
|
57
56
|
const varName = friendlyNameToVar(preset.title, "--font-");
|
|
58
|
-
if (!
|
|
57
|
+
if (!activeTab.value?.doc.document?.style?.[varName]) {
|
|
59
58
|
transactDoc(activeTab.value, (t) => mutateUpdateStyle(t, [], varName, preset.value));
|
|
60
59
|
}
|
|
61
60
|
onChange(`var(${varName})`);
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/** Style utilities — pure CSS helper functions used by the style panel. */
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { getNodeAtPath } from "../store.js";
|
|
4
|
+
import { activeTab } from "../workspace/workspace.js";
|
|
4
5
|
import { camelToKebab } from "../utils/studio-utils.js";
|
|
5
6
|
import cssMeta from "../../data/css-meta.json";
|
|
6
7
|
|
|
@@ -159,8 +160,7 @@ export function compressBorderSide(/** @type {string[]} */ vals) {
|
|
|
159
160
|
|
|
160
161
|
/** Extract --font-* CSS custom properties from the document root style. */
|
|
161
162
|
export function getFontVars() {
|
|
162
|
-
const
|
|
163
|
-
const style = S.document?.style;
|
|
163
|
+
const style = activeTab.value?.doc.document?.style;
|
|
164
164
|
if (!style) return [];
|
|
165
165
|
const vars = [];
|
|
166
166
|
for (const [k, v] of Object.entries(style)) {
|
|
@@ -181,12 +181,14 @@ export const TYPO_PREVIEW_PROPS = new Set([
|
|
|
181
181
|
|
|
182
182
|
/** Resolve the current font family for typography preview (handles var() references) */
|
|
183
183
|
export function currentFontFamily() {
|
|
184
|
-
const
|
|
185
|
-
const node =
|
|
184
|
+
const tab = activeTab.value;
|
|
185
|
+
const node = tab?.session.selection
|
|
186
|
+
? getNodeAtPath(tab.doc.document, tab.session.selection)
|
|
187
|
+
: null;
|
|
186
188
|
const raw = node?.style?.fontFamily;
|
|
187
189
|
if (!raw) return "";
|
|
188
190
|
const m = typeof raw === "string" && raw.match(/^var\((--[^)]+)\)$/);
|
|
189
|
-
if (m) return
|
|
191
|
+
if (m) return tab?.doc.document?.style?.[m[1]] || "";
|
|
190
192
|
return raw;
|
|
191
193
|
}
|
|
192
194
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/** Stylebook layers panel — shows element/variable tree when in stylebook (settings) mode. */
|
|
2
2
|
|
|
3
3
|
import { html, nothing } from "lit-html";
|
|
4
|
-
import {
|
|
4
|
+
import { activeTab } from "../workspace/workspace.js";
|
|
5
5
|
import { componentRegistry } from "../files/components.js";
|
|
6
6
|
|
|
7
7
|
/**
|
|
@@ -18,11 +18,11 @@ function hasTagStyle(rootStyle, tag) {
|
|
|
18
18
|
* @returns {import("lit-html").TemplateResult}
|
|
19
19
|
*/
|
|
20
20
|
export function renderStylebookLayersTemplate(ctx) {
|
|
21
|
-
const
|
|
22
|
-
const rootStyle =
|
|
23
|
-
const selectedTag =
|
|
21
|
+
const tab = activeTab.value;
|
|
22
|
+
const rootStyle = tab?.doc.document?.style || {};
|
|
23
|
+
const selectedTag = tab?.session.ui.stylebookSelection;
|
|
24
24
|
|
|
25
|
-
if (
|
|
25
|
+
if (tab?.session.ui.stylebookTab === "elements") {
|
|
26
26
|
/**
|
|
27
27
|
* @param {any} entry
|
|
28
28
|
* @param {number} depth
|
|
@@ -8,7 +8,6 @@ import { ref } from "lit-html/directives/ref.js";
|
|
|
8
8
|
import { styleMap } from "lit-html/directives/style-map.js";
|
|
9
9
|
|
|
10
10
|
import {
|
|
11
|
-
getState,
|
|
12
11
|
updateSession,
|
|
13
12
|
updateUi,
|
|
14
13
|
canvasWrap,
|
|
@@ -49,9 +48,8 @@ let _ctx = null;
|
|
|
49
48
|
*/
|
|
50
49
|
export function renderStylebookMode(ctx) {
|
|
51
50
|
_ctx = ctx;
|
|
52
|
-
const S = getState();
|
|
53
51
|
|
|
54
|
-
const settingsTab =
|
|
52
|
+
const settingsTab = activeTab.value?.session.ui.settingsTab || "stylebook";
|
|
55
53
|
|
|
56
54
|
const settingsChromeBarTpl = html`
|
|
57
55
|
<div
|
|
@@ -94,11 +92,14 @@ export function renderStylebookMode(ctx) {
|
|
|
94
92
|
|
|
95
93
|
// Stylebook tab — element catalog / variables
|
|
96
94
|
view.stylebookElToTag = new WeakMap();
|
|
97
|
-
const
|
|
98
|
-
const
|
|
99
|
-
const
|
|
95
|
+
const tab = activeTab.value;
|
|
96
|
+
const rootStyle = getEffectiveStyle(tab?.doc.document?.style);
|
|
97
|
+
const filter = (tab?.session.ui.stylebookFilter || "").toLowerCase();
|
|
98
|
+
const customizedOnly = tab?.session.ui.stylebookCustomizedOnly;
|
|
100
99
|
|
|
101
|
-
const { sizeBreakpoints, baseWidth } = parseMediaEntries(
|
|
100
|
+
const { sizeBreakpoints, baseWidth } = parseMediaEntries(
|
|
101
|
+
getEffectiveMedia(tab?.doc.document?.$media),
|
|
102
|
+
);
|
|
102
103
|
const hasMedia = sizeBreakpoints.length > 0;
|
|
103
104
|
|
|
104
105
|
const onTabClick = (/** @type {string} */ t) => {
|
|
@@ -110,7 +111,7 @@ export function renderStylebookMode(ctx) {
|
|
|
110
111
|
};
|
|
111
112
|
|
|
112
113
|
const onCustomizedToggle = () => {
|
|
113
|
-
updateUi("stylebookCustomizedOnly", !
|
|
114
|
+
updateUi("stylebookCustomizedOnly", !tab?.session.ui.stylebookCustomizedOnly);
|
|
114
115
|
};
|
|
115
116
|
|
|
116
117
|
const chromeBarTpl = html`
|
|
@@ -121,7 +122,7 @@ export function renderStylebookMode(ctx) {
|
|
|
121
122
|
>
|
|
122
123
|
<sp-tabs
|
|
123
124
|
size="s"
|
|
124
|
-
selected=${
|
|
125
|
+
selected=${tab?.session.ui.stylebookTab || "elements"}
|
|
125
126
|
@change=${(/** @type {any} */ e) => {
|
|
126
127
|
onTabClick(e.target.selected);
|
|
127
128
|
}}
|
|
@@ -132,17 +133,17 @@ export function renderStylebookMode(ctx) {
|
|
|
132
133
|
`,
|
|
133
134
|
)}
|
|
134
135
|
</sp-tabs>
|
|
135
|
-
${
|
|
136
|
+
${tab?.session.ui.stylebookTab === "elements"
|
|
136
137
|
? html`
|
|
137
138
|
<input
|
|
138
139
|
class="field-input"
|
|
139
140
|
style="flex:1;max-width:200px;margin-left:8px"
|
|
140
141
|
placeholder="Filter…"
|
|
141
|
-
.value=${
|
|
142
|
+
.value=${tab?.session.ui.stylebookFilter}
|
|
142
143
|
@input=${onFilterInput}
|
|
143
144
|
/>
|
|
144
145
|
<button
|
|
145
|
-
class="tb-toggle${
|
|
146
|
+
class="tb-toggle${tab?.session.ui.stylebookCustomizedOnly ? " active" : ""}"
|
|
146
147
|
style="margin-left:4px"
|
|
147
148
|
@click=${onCustomizedToggle}
|
|
148
149
|
>
|
|
@@ -176,7 +177,7 @@ export function renderStylebookMode(ctx) {
|
|
|
176
177
|
|
|
177
178
|
const renderIntoPanel = (/** @type {any} */ panel, /** @type {any} */ activeBreakpoints) => {
|
|
178
179
|
panel.canvas.classList.add("sb-canvas");
|
|
179
|
-
if (
|
|
180
|
+
if (tab?.session.ui.stylebookTab === "elements") {
|
|
180
181
|
renderStylebookElementsIntoCanvas(
|
|
181
182
|
panel.canvas,
|
|
182
183
|
rootStyle,
|
|
@@ -197,7 +198,7 @@ export function renderStylebookMode(ctx) {
|
|
|
197
198
|
/** @type {{ tpl: any; panel: any; activeSet: any }[]} */
|
|
198
199
|
let panelEntries;
|
|
199
200
|
if (!hasMedia) {
|
|
200
|
-
const effectiveMedia = getEffectiveMedia(
|
|
201
|
+
const effectiveMedia = getEffectiveMedia(tab?.doc.document?.$media);
|
|
201
202
|
const hasBaseWidth = effectiveMedia && effectiveMedia["--"];
|
|
202
203
|
const label = hasBaseWidth ? `${mediaDisplayName("--")} (${baseWidth}px)` : null;
|
|
203
204
|
const entry = ctx.canvasPanelTemplate(
|
|
@@ -268,8 +269,7 @@ export function renderStylebookOverlays() {
|
|
|
268
269
|
if (!_ctx) return;
|
|
269
270
|
if (canvasPanels.length === 0) return;
|
|
270
271
|
|
|
271
|
-
const
|
|
272
|
-
const selectedTag = S.ui.stylebookSelection;
|
|
272
|
+
const selectedTag = activeTab.value?.session.ui.stylebookSelection;
|
|
273
273
|
|
|
274
274
|
for (const panel of canvasPanels) {
|
|
275
275
|
const hoverTag = /** @type {any} */ (panel)._lastHoverTag;
|
package/src/panels/toolbar.js
CHANGED
|
@@ -72,9 +72,9 @@ export function mount(rootEl, ctx) {
|
|
|
72
72
|
void tab.doc.dirty;
|
|
73
73
|
void tab.doc.mode;
|
|
74
74
|
void tab.session.selection;
|
|
75
|
+
void tab.session.ui.canvasMode;
|
|
75
76
|
void tab.session.ui.editingFunction;
|
|
76
77
|
void tab.session.ui.featureToggles;
|
|
77
|
-
void tab.session.ui.leftTab;
|
|
78
78
|
void tab.session.ui.rightTab;
|
|
79
79
|
render();
|
|
80
80
|
});
|
|
@@ -213,7 +213,7 @@ function toolbarTemplate() {
|
|
|
213
213
|
/** @type {Record<string, any>} */
|
|
214
214
|
const uiPatch = { editingFunction: null };
|
|
215
215
|
if (m.key === "settings") uiPatch.rightTab = "style";
|
|
216
|
-
if (m.key === "manage")
|
|
216
|
+
if (m.key === "manage") view.leftTab = "files";
|
|
217
217
|
updateSession({ ui: uiPatch });
|
|
218
218
|
_ctx.renderCanvas();
|
|
219
219
|
_ctx.safeRenderRightPanel();
|