@jxsuite/studio 0.33.0 → 0.34.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/iframe-entry.js +6230 -0
- package/dist/iframe-entry.js.map +35 -0
- package/dist/studio.js +10754 -11060
- package/dist/studio.js.map +83 -72
- package/package.json +7 -7
- package/src/browse/browse.ts +11 -4
- package/src/canvas/canvas-helpers.ts +2 -56
- package/src/canvas/canvas-live-render.ts +102 -435
- package/src/canvas/canvas-origin.ts +66 -0
- package/src/canvas/canvas-patcher.ts +63 -403
- package/src/canvas/canvas-render.ts +70 -212
- package/src/canvas/canvas-utils.ts +37 -65
- package/src/canvas/iframe-channel.ts +154 -0
- package/src/canvas/iframe-drop.ts +484 -0
- package/src/canvas/iframe-entry.ts +600 -0
- package/src/canvas/iframe-host.ts +1373 -0
- package/src/canvas/iframe-inline-edit.ts +367 -0
- package/src/canvas/iframe-insert.ts +164 -0
- package/src/canvas/iframe-interaction.ts +176 -0
- package/src/canvas/iframe-keys.ts +85 -0
- package/src/canvas/iframe-overlay.ts +218 -0
- package/src/canvas/iframe-patch.ts +363 -0
- package/src/canvas/iframe-protocol.ts +361 -0
- package/src/canvas/iframe-render.ts +458 -0
- package/src/canvas/iframe-slash.ts +114 -0
- package/src/canvas/iframe-subtree.ts +113 -0
- package/src/canvas/path-mapping.ts +86 -0
- package/src/canvas/serialize-scope.ts +65 -0
- package/src/editor/canvas-context-menu.ts +40 -0
- package/src/editor/canvas-slash-bridge.ts +21 -0
- package/src/editor/context-menu.ts +2 -1
- package/src/editor/inline-edit-apply.ts +183 -0
- package/src/editor/inline-edit.ts +99 -21
- package/src/editor/inline-link.ts +89 -0
- package/src/editor/insert-zone-action.ts +35 -0
- package/src/editor/merge-tags.ts +26 -2
- package/src/editor/repeater-scope.ts +144 -0
- package/src/editor/shortcuts.ts +14 -28
- package/src/editor/slash-menu.ts +73 -42
- package/src/files/files.ts +2 -1
- package/src/page-params.ts +383 -0
- package/src/panels/ai-panel.ts +5 -7
- package/src/panels/block-action-bar.ts +296 -138
- package/src/panels/canvas-dnd-bridge.ts +397 -0
- package/src/panels/component-preview.ts +56 -0
- package/src/panels/dnd.ts +41 -17
- package/src/panels/drag-ghost.ts +62 -0
- package/src/panels/editors.ts +1 -1
- package/src/panels/overlays.ts +10 -125
- package/src/panels/properties-panel.ts +210 -0
- package/src/panels/right-panel.ts +0 -2
- package/src/panels/signals-panel.ts +136 -22
- package/src/panels/stylebook-doc.ts +373 -0
- package/src/panels/stylebook-panel.ts +46 -689
- package/src/panels/tab-bar.ts +159 -13
- package/src/panels/toolbar.ts +3 -2
- package/src/platforms/devserver.ts +15 -0
- package/src/services/monaco-setup.ts +12 -0
- package/src/services/render-critic.ts +9 -9
- package/src/settings/css-vars-editor.ts +2 -2
- package/src/store.ts +4 -62
- package/src/studio.ts +90 -40
- package/src/tabs/doc-op-apply.ts +89 -0
- package/src/tabs/patch-ops.ts +6 -2
- package/src/tabs/tab.ts +23 -4
- package/src/tabs/transact.ts +2 -74
- package/src/types.ts +14 -18
- package/src/ui/jx-theme.ts +63 -0
- package/src/ui/media-picker.ts +6 -4
- package/src/ui/spectrum.ts +5 -0
- package/src/utils/canvas-media.ts +0 -137
- package/src/utils/edit-display.ts +23 -3
- package/src/utils/geometry.ts +43 -0
- package/src/utils/link-target.ts +93 -0
- package/src/utils/strip-events.ts +54 -0
- package/src/view.ts +0 -23
- package/src/workspace/workspace.ts +14 -1
- package/src/canvas/canvas-subtree-render.ts +0 -113
- package/src/editor/component-inline-edit.ts +0 -349
- package/src/editor/content-inline-edit.ts +0 -207
- package/src/editor/insertion-helper.ts +0 -308
- package/src/panels/canvas-dnd.ts +0 -329
- package/src/panels/panel-events.ts +0 -306
- package/src/panels/preview-render.ts +0 -132
- package/src/panels/pseudo-preview.ts +0 -75
|
@@ -1,33 +1,27 @@
|
|
|
1
1
|
/// <reference lib="dom" />
|
|
2
|
-
/// <reference lib="dom.iterable" />
|
|
3
2
|
/**
|
|
4
3
|
* Stylebook panel — renders the Stylebook mode canvas (element catalog with per-file style
|
|
5
|
-
* defaults)
|
|
4
|
+
* defaults) through the IFRAME canvas pipeline: a specimen document is generated parent-side
|
|
5
|
+
* ({@link file://./stylebook-doc.ts}) and mounted per breakpoint panel via `mountStylebookCanvas`,
|
|
6
|
+
* so each panel is a real width-sized viewport and `@media` blocks evaluate for real (no JS
|
|
7
|
+
* flatten). Hits decode to tags in the host and route back here through the injected stylebook-hit
|
|
8
|
+
* handler (`setStylebookHitHandler` in studio.ts).
|
|
6
9
|
*/
|
|
7
10
|
|
|
8
|
-
import { html, render as litRender
|
|
11
|
+
import { html, render as litRender } from "lit-html";
|
|
9
12
|
import { ref } from "lit-html/directives/ref.js";
|
|
10
13
|
import { classMap } from "lit-html/directives/class-map.js";
|
|
11
|
-
import { styleMap } from "lit-html/directives/style-map.js";
|
|
12
14
|
import { live } from "lit-html/directives/live.js";
|
|
13
15
|
|
|
14
|
-
import {
|
|
15
|
-
canvasPanels,
|
|
16
|
-
canvasWrap,
|
|
17
|
-
elToPath,
|
|
18
|
-
projectState,
|
|
19
|
-
updateSession,
|
|
20
|
-
updateUi,
|
|
21
|
-
} from "../store";
|
|
16
|
+
import { canvasPanels, canvasWrap, projectState, updateSession, updateUi } from "../store";
|
|
22
17
|
import { activeTab } from "../workspace/workspace";
|
|
23
18
|
import { view } from "../view";
|
|
24
|
-
import { defineElement, setSkipServerFunctions } from "@jxsuite/runtime";
|
|
25
19
|
import { componentRegistry } from "../files/components";
|
|
26
|
-
import type { ComponentEntry } from "../files/components";
|
|
27
20
|
import { getEffectiveMedia, getEffectiveStyle } from "../site-context";
|
|
28
|
-
import {
|
|
21
|
+
import { parseMediaEntries } from "../utils/canvas-media";
|
|
29
22
|
import { mediaDisplayName } from "./shared";
|
|
30
|
-
import {
|
|
23
|
+
import { buildStylebookDoc } from "./stylebook-doc";
|
|
24
|
+
import { mountStylebookCanvas, panToStylebookTag } from "../canvas/iframe-host";
|
|
31
25
|
import stylebookMeta from "../../data/stylebook-meta.json";
|
|
32
26
|
import type { TemplateResult } from "lit-html";
|
|
33
27
|
import type { CanvasPanel } from "../types";
|
|
@@ -40,19 +34,6 @@ export interface StylebookEntry {
|
|
|
40
34
|
children?: StylebookEntry[];
|
|
41
35
|
}
|
|
42
36
|
|
|
43
|
-
interface StylebookPanel {
|
|
44
|
-
mediaName: string | null;
|
|
45
|
-
element: HTMLElement | null;
|
|
46
|
-
canvas: HTMLElement;
|
|
47
|
-
overlay: HTMLElement;
|
|
48
|
-
overlayClk: HTMLElement;
|
|
49
|
-
viewport: HTMLElement | null;
|
|
50
|
-
scrollContainer: HTMLElement | null;
|
|
51
|
-
dropLine: HTMLElement | null;
|
|
52
|
-
_width: number | null;
|
|
53
|
-
_lastHoverTag?: string | null;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
37
|
interface StylebookCtx {
|
|
57
38
|
canvasPanelTemplate: (
|
|
58
39
|
mediaName: string | null,
|
|
@@ -64,69 +45,23 @@ interface StylebookCtx {
|
|
|
64
45
|
observeCenterUntilStable: () => void;
|
|
65
46
|
renderZoomIndicator: () => void;
|
|
66
47
|
updateActivePanelHeaders: () => void;
|
|
67
|
-
overlayBoxDescriptor: (
|
|
68
|
-
el: Element,
|
|
69
|
-
type: string,
|
|
70
|
-
panel: CanvasPanel,
|
|
71
|
-
) => {
|
|
72
|
-
cls: string;
|
|
73
|
-
top: string;
|
|
74
|
-
left: string;
|
|
75
|
-
width: string;
|
|
76
|
-
height: string;
|
|
77
|
-
};
|
|
78
|
-
effectiveZoom: () => number;
|
|
79
48
|
}
|
|
80
49
|
|
|
81
50
|
export { default as stylebookMeta } from "../../data/stylebook-meta.json";
|
|
82
51
|
|
|
83
52
|
/**
|
|
84
|
-
*
|
|
85
|
-
*
|
|
86
|
-
* @param {Record<string, unknown>} style
|
|
87
|
-
* @param {string} tagPath
|
|
88
|
-
* @returns {Record<string, unknown> | null}
|
|
89
|
-
*/
|
|
90
|
-
function _resolveNestedStyle(style: Record<string, unknown>, tagPath: string) {
|
|
91
|
-
const parts = tagPath.split(" ");
|
|
92
|
-
let obj = style;
|
|
93
|
-
for (const part of parts) {
|
|
94
|
-
if (!obj || typeof obj !== "object") {
|
|
95
|
-
return null;
|
|
96
|
-
}
|
|
97
|
-
obj = obj[part] as Record<string, unknown>;
|
|
98
|
-
}
|
|
99
|
-
return obj && typeof obj === "object" ? (obj as Record<string, unknown>) : null;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
let _ctx: StylebookCtx | null = null;
|
|
103
|
-
|
|
104
|
-
/** Lookup: tag → entry from stylebookMeta (built once) */
|
|
105
|
-
const _entryByTag = new Map<string, StylebookEntry>();
|
|
106
|
-
for (const section of stylebookMeta.$sections) {
|
|
107
|
-
for (const entry of section.elements as StylebookEntry[]) {
|
|
108
|
-
_entryByTag.set(entry.tag, entry);
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
/**
|
|
113
|
-
* Render the stylebook/settings mode into the canvas.
|
|
53
|
+
* Render the stylebook mode into the canvas: chrome bar + one iframe panel per breakpoint, all
|
|
54
|
+
* mounting the SAME generated specimen document.
|
|
114
55
|
*
|
|
115
56
|
* @param {StylebookCtx} ctx
|
|
116
57
|
*/
|
|
117
58
|
export function renderStylebookMode(ctx: StylebookCtx) {
|
|
118
|
-
_ctx = ctx;
|
|
119
|
-
|
|
120
|
-
// Stylebook mode — element catalog only
|
|
121
|
-
view.stylebookElToTag = new WeakMap();
|
|
122
59
|
const tab = activeTab.value;
|
|
123
|
-
const rootStyle = getEffectiveStyle(tab?.doc.document?.style);
|
|
124
60
|
const filter = (tab?.session.ui.stylebookFilter || "").toLowerCase();
|
|
125
61
|
const customizedOnly = tab?.session.ui.stylebookCustomizedOnly;
|
|
126
62
|
|
|
127
|
-
const
|
|
128
|
-
|
|
129
|
-
);
|
|
63
|
+
const effectiveMedia = getEffectiveMedia(tab?.doc.document?.$media);
|
|
64
|
+
const { sizeBreakpoints, baseWidth } = parseMediaEntries(effectiveMedia);
|
|
130
65
|
const hasMedia = sizeBreakpoints.length > 0;
|
|
131
66
|
|
|
132
67
|
const onFilterInput = (e: Event) => {
|
|
@@ -165,18 +100,16 @@ export function renderStylebookMode(ctx: StylebookCtx) {
|
|
|
165
100
|
|
|
166
101
|
(canvasWrap as HTMLElement).style.overflow = "hidden";
|
|
167
102
|
|
|
168
|
-
/** @type {{ name: string; displayName: string; width: number
|
|
103
|
+
/** @type {{ name: string; displayName: string; width: number }[]} */
|
|
169
104
|
const allPanelDefs = [];
|
|
170
105
|
if (hasMedia) {
|
|
171
106
|
allPanelDefs.push({
|
|
172
|
-
activeSet: activeBreakpointsForWidth(sizeBreakpoints, baseWidth),
|
|
173
107
|
displayName: mediaDisplayName("--"),
|
|
174
108
|
name: "base",
|
|
175
109
|
width: baseWidth,
|
|
176
110
|
});
|
|
177
111
|
for (const bp of sizeBreakpoints) {
|
|
178
112
|
allPanelDefs.push({
|
|
179
|
-
activeSet: activeBreakpointsForWidth(sizeBreakpoints, bp.width),
|
|
180
113
|
displayName: mediaDisplayName(bp.name),
|
|
181
114
|
name: bp.name,
|
|
182
115
|
width: bp.width,
|
|
@@ -184,31 +117,9 @@ export function renderStylebookMode(ctx: StylebookCtx) {
|
|
|
184
117
|
}
|
|
185
118
|
}
|
|
186
119
|
|
|
187
|
-
|
|
188
|
-
panel.canvas.classList.add("sb-canvas");
|
|
189
|
-
renderStylebookElementsIntoCanvas(
|
|
190
|
-
panel.canvas,
|
|
191
|
-
rootStyle,
|
|
192
|
-
filter,
|
|
193
|
-
customizedOnly,
|
|
194
|
-
activeBreakpoints,
|
|
195
|
-
);
|
|
196
|
-
for (const child of panel.canvas.querySelectorAll("*")) {
|
|
197
|
-
(child as HTMLElement).style.pointerEvents = "none";
|
|
198
|
-
}
|
|
199
|
-
registerStylebookPanelEvents(panel);
|
|
200
|
-
};
|
|
201
|
-
|
|
202
|
-
/**
|
|
203
|
-
* @type {{
|
|
204
|
-
* tpl: import("lit-html").TemplateResult;
|
|
205
|
-
* panel: StylebookPanel;
|
|
206
|
-
* activeSet: Set<string>;
|
|
207
|
-
* }[]}
|
|
208
|
-
*/
|
|
120
|
+
/** @type {{ tpl: import("lit-html").TemplateResult; panel: CanvasPanel }[]} */
|
|
209
121
|
let panelEntries;
|
|
210
122
|
if (!hasMedia) {
|
|
211
|
-
const effectiveMedia = getEffectiveMedia(tab?.doc.document?.$media);
|
|
212
123
|
const hasBaseWidth = effectiveMedia && effectiveMedia["--"];
|
|
213
124
|
const label = hasBaseWidth ? `${mediaDisplayName("--")} (${baseWidth}px)` : null;
|
|
214
125
|
const entry = ctx.canvasPanelTemplate(
|
|
@@ -217,12 +128,12 @@ export function renderStylebookMode(ctx: StylebookCtx) {
|
|
|
217
128
|
!hasBaseWidth,
|
|
218
129
|
hasBaseWidth ? baseWidth : undefined,
|
|
219
130
|
);
|
|
220
|
-
panelEntries = [{
|
|
131
|
+
panelEntries = [{ panel: entry.panel, tpl: entry.tpl }];
|
|
221
132
|
} else {
|
|
222
133
|
panelEntries = allPanelDefs.map((def) => {
|
|
223
134
|
const label = `${def.displayName} (${def.width}px)`;
|
|
224
135
|
const { tpl, panel } = ctx.canvasPanelTemplate(def.name, label, false, def.width);
|
|
225
|
-
return {
|
|
136
|
+
return { panel, tpl };
|
|
226
137
|
});
|
|
227
138
|
}
|
|
228
139
|
|
|
@@ -244,9 +155,26 @@ export function renderStylebookMode(ctx: StylebookCtx) {
|
|
|
244
155
|
/** @type {HTMLElement} */ canvasWrap,
|
|
245
156
|
);
|
|
246
157
|
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
158
|
+
// ONE generated doc shared by every panel — per-panel @media differentiation comes from each
|
|
159
|
+
// Iframe's real viewport width, not from a per-panel style flatten.
|
|
160
|
+
const generated = buildStylebookDoc({
|
|
161
|
+
components: componentRegistry,
|
|
162
|
+
customizedOnly: Boolean(customizedOnly),
|
|
163
|
+
effectiveMedia: effectiveMedia ?? {},
|
|
164
|
+
effectiveStyle: getEffectiveStyle(tab?.doc.document?.style),
|
|
165
|
+
filter,
|
|
166
|
+
meta: stylebookMeta as { $sections: { label: string; elements: StylebookEntry[] }[] },
|
|
167
|
+
projectRoot: projectState?.projectRoot ?? null,
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
for (const { panel } of panelEntries) {
|
|
171
|
+
canvasPanels.push(panel);
|
|
172
|
+
mountStylebookCanvas(
|
|
173
|
+
view.renderGeneration,
|
|
174
|
+
generated,
|
|
175
|
+
panel.canvas as HTMLElement,
|
|
176
|
+
panel._width,
|
|
177
|
+
);
|
|
250
178
|
}
|
|
251
179
|
if (hasMedia) {
|
|
252
180
|
ctx.updateActivePanelHeaders();
|
|
@@ -257,111 +185,11 @@ export function renderStylebookMode(ctx: StylebookCtx) {
|
|
|
257
185
|
ctx.renderZoomIndicator();
|
|
258
186
|
}
|
|
259
187
|
|
|
260
|
-
/** Fast-path: re-apply styles to existing stylebook elements without rebuilding the DOM. */
|
|
261
|
-
export function refreshStylebookStyles() {
|
|
262
|
-
const tab = activeTab.value;
|
|
263
|
-
if (!tab) {
|
|
264
|
-
return;
|
|
265
|
-
}
|
|
266
|
-
const rootStyle = getEffectiveStyle(tab.doc.document?.style);
|
|
267
|
-
|
|
268
|
-
for (const panel of canvasPanels) {
|
|
269
|
-
const { canvas } = panel;
|
|
270
|
-
// Re-apply CSS custom properties
|
|
271
|
-
for (const [k, v] of Object.entries(rootStyle)) {
|
|
272
|
-
if (k.startsWith("--") && (typeof v === "string" || typeof v === "number")) {
|
|
273
|
-
canvas.style.setProperty(k, String(v));
|
|
274
|
-
}
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
const { sizeBreakpoints } = parseMediaEntries(getEffectiveMedia(tab.doc.document?.$media));
|
|
278
|
-
const activeBreakpoints = panel.mediaName
|
|
279
|
-
? activeBreakpointsForWidth(sizeBreakpoints, panel._width as number)
|
|
280
|
-
: new Set<string>();
|
|
281
|
-
|
|
282
|
-
// Re-apply styles to each element in the canvas
|
|
283
|
-
const allEls = canvas.querySelectorAll("*");
|
|
284
|
-
for (const el of allEls) {
|
|
285
|
-
const tag = view.stylebookElToTag.get(el);
|
|
286
|
-
if (!tag) {
|
|
287
|
-
continue;
|
|
288
|
-
}
|
|
289
|
-
const htmlEl = el as HTMLElement;
|
|
290
|
-
// Determine if it's a compound selector (e.g. "ul li") or simple tag
|
|
291
|
-
const parts = tag.split(" ");
|
|
292
|
-
const leafTag = parts.at(-1)!;
|
|
293
|
-
const entry = _entryByTag.get(leafTag);
|
|
294
|
-
|
|
295
|
-
// Reset to base style
|
|
296
|
-
htmlEl.style.cssText = entry?.style || "";
|
|
297
|
-
|
|
298
|
-
// Apply root style for this tag (nested path)
|
|
299
|
-
const tagStyle = _resolveNestedStyle(rootStyle, tag);
|
|
300
|
-
if (tagStyle) {
|
|
301
|
-
for (const [prop, val] of Object.entries(tagStyle)) {
|
|
302
|
-
if (typeof val === "string" || typeof val === "number") {
|
|
303
|
-
try {
|
|
304
|
-
(htmlEl.style as unknown as Record<string, string | number>)[prop] = val;
|
|
305
|
-
} catch {}
|
|
306
|
-
}
|
|
307
|
-
}
|
|
308
|
-
// Media overrides nested in tag style
|
|
309
|
-
if (activeBreakpoints.size > 0) {
|
|
310
|
-
for (const [key, val] of Object.entries(tagStyle)) {
|
|
311
|
-
if (!key.startsWith("@") || typeof val !== "object" || val === null) {
|
|
312
|
-
continue;
|
|
313
|
-
}
|
|
314
|
-
const mediaName = key.slice(1);
|
|
315
|
-
if (mediaName === "--") {
|
|
316
|
-
continue;
|
|
317
|
-
}
|
|
318
|
-
if (activeBreakpoints.has(mediaName)) {
|
|
319
|
-
for (const [prop, v] of Object.entries(val as Record<string, unknown>)) {
|
|
320
|
-
if (typeof v === "string" || typeof v === "number") {
|
|
321
|
-
try {
|
|
322
|
-
(htmlEl.style as unknown as Record<string, string | number>)[prop] = v;
|
|
323
|
-
} catch {}
|
|
324
|
-
}
|
|
325
|
-
}
|
|
326
|
-
}
|
|
327
|
-
}
|
|
328
|
-
}
|
|
329
|
-
}
|
|
330
|
-
// Top-level @media keys
|
|
331
|
-
if (activeBreakpoints.size > 0) {
|
|
332
|
-
for (const [key, val] of Object.entries(rootStyle)) {
|
|
333
|
-
if (!key.startsWith("@") || typeof val !== "object" || val === null) {
|
|
334
|
-
continue;
|
|
335
|
-
}
|
|
336
|
-
const mediaName = key.slice(1);
|
|
337
|
-
if (mediaName === "--") {
|
|
338
|
-
continue;
|
|
339
|
-
}
|
|
340
|
-
if (activeBreakpoints.has(mediaName)) {
|
|
341
|
-
const mediaTagStyle = _resolveNestedStyle(
|
|
342
|
-
/** @type {Record<string, unknown>} */ val,
|
|
343
|
-
tag,
|
|
344
|
-
);
|
|
345
|
-
if (mediaTagStyle && typeof mediaTagStyle === "object") {
|
|
346
|
-
for (const [prop, v] of Object.entries(mediaTagStyle)) {
|
|
347
|
-
if (typeof v === "string" || typeof v === "number") {
|
|
348
|
-
try {
|
|
349
|
-
(htmlEl.style as unknown as Record<string, string | number>)[prop] = v;
|
|
350
|
-
} catch {}
|
|
351
|
-
}
|
|
352
|
-
}
|
|
353
|
-
}
|
|
354
|
-
}
|
|
355
|
-
}
|
|
356
|
-
}
|
|
357
|
-
}
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
renderStylebookOverlays();
|
|
361
|
-
}
|
|
362
|
-
|
|
363
188
|
/**
|
|
364
|
-
* Select a tag in the stylebook — shared by
|
|
189
|
+
* Select a tag in the stylebook — shared by the canvas hit handler (via studio's
|
|
190
|
+
* setStylebookHitHandler wiring), the stylebook layers panel, and the style panel. The host's
|
|
191
|
+
* selection watcher tracks `ui.stylebookSelection` and measures the selected tag's card, so no
|
|
192
|
+
* direct overlay drawing happens here.
|
|
365
193
|
*
|
|
366
194
|
* @param {string} tag
|
|
367
195
|
* @param {string | null} [media]
|
|
@@ -376,482 +204,11 @@ export function selectStylebookTag(tag: string, media?: string | null, { panCanv
|
|
|
376
204
|
...(media !== undefined ? { activeMedia: media } : {}),
|
|
377
205
|
},
|
|
378
206
|
});
|
|
379
|
-
renderStylebookOverlays();
|
|
380
|
-
|
|
381
|
-
if (tag && panCanvas && canvasPanels.length > 0) {
|
|
382
|
-
const el = findStylebookEl(canvasPanels[0]!.canvas, tag);
|
|
383
|
-
if (el) {
|
|
384
|
-
panToCanvasEl(el);
|
|
385
|
-
}
|
|
386
|
-
}
|
|
387
|
-
}
|
|
388
|
-
|
|
389
|
-
/** Draw selection + hover overlays for stylebook elements */
|
|
390
|
-
export function renderStylebookOverlays() {
|
|
391
|
-
if (!_ctx) {
|
|
392
|
-
return;
|
|
393
|
-
}
|
|
394
|
-
if (canvasPanels.length === 0) {
|
|
395
|
-
return;
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
const selectedTag = activeTab.value?.session.ui.stylebookSelection;
|
|
399
207
|
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
/**
|
|
403
|
-
* @type {{
|
|
404
|
-
* cls: string;
|
|
405
|
-
* top: string;
|
|
406
|
-
* left: string;
|
|
407
|
-
* width: string;
|
|
408
|
-
* height: string;
|
|
409
|
-
* label?: string;
|
|
410
|
-
* }[]}
|
|
411
|
-
*/
|
|
412
|
-
const boxes = [];
|
|
413
|
-
|
|
414
|
-
if (hoverTag && hoverTag !== selectedTag) {
|
|
415
|
-
const el = findStylebookEl(panel.canvas, hoverTag);
|
|
416
|
-
if (el) {
|
|
417
|
-
boxes.push({
|
|
418
|
-
..._ctx.overlayBoxDescriptor(el, "hover", panel),
|
|
419
|
-
label: undefined,
|
|
420
|
-
});
|
|
421
|
-
}
|
|
422
|
-
}
|
|
423
|
-
|
|
424
|
-
if (selectedTag) {
|
|
425
|
-
const el = findStylebookEl(panel.canvas, selectedTag);
|
|
426
|
-
if (el) {
|
|
427
|
-
boxes.push({
|
|
428
|
-
..._ctx.overlayBoxDescriptor(el, "selection", panel),
|
|
429
|
-
label: `<${selectedTag}>`,
|
|
430
|
-
});
|
|
431
|
-
}
|
|
432
|
-
}
|
|
433
|
-
|
|
434
|
-
litRender(
|
|
435
|
-
html`
|
|
436
|
-
${panel.dropLine}
|
|
437
|
-
${boxes.map(
|
|
438
|
-
(b) => html`
|
|
439
|
-
<div
|
|
440
|
-
class=${b.cls}
|
|
441
|
-
style=${styleMap({
|
|
442
|
-
height: b.height,
|
|
443
|
-
left: b.left,
|
|
444
|
-
top: b.top,
|
|
445
|
-
width: b.width,
|
|
446
|
-
})}
|
|
447
|
-
>
|
|
448
|
-
${b.label ? html`<div class="overlay-label">${b.label}</div>` : nothing}
|
|
449
|
-
</div>
|
|
450
|
-
`,
|
|
451
|
-
)}
|
|
452
|
-
`,
|
|
453
|
-
panel.overlay,
|
|
454
|
-
);
|
|
208
|
+
if (tag && panCanvas) {
|
|
209
|
+
panToStylebookTag(tag);
|
|
455
210
|
}
|
|
456
211
|
}
|
|
457
212
|
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
/**
|
|
461
|
-
* Build a DOM element tree from a stylebook-meta.json entry.
|
|
462
|
-
*
|
|
463
|
-
* @param {StylebookEntry} entry
|
|
464
|
-
* @param {Record<string, unknown>} rootStyle
|
|
465
|
-
* @param {Set<string> | null} activeBreakpoints
|
|
466
|
-
* @param {string | null} [parentTag]
|
|
467
|
-
*/
|
|
468
|
-
export function buildStylebookElement(
|
|
469
|
-
entry: StylebookEntry,
|
|
470
|
-
rootStyle: Record<string, unknown>,
|
|
471
|
-
activeBreakpoints: Set<string> | null,
|
|
472
|
-
parentTag: string | null = null,
|
|
473
|
-
) {
|
|
474
|
-
const el = document.createElement(entry.tag);
|
|
475
|
-
if (entry.text) {
|
|
476
|
-
el.textContent = entry.text;
|
|
477
|
-
}
|
|
478
|
-
if (entry.attributes) {
|
|
479
|
-
for (const [k, v] of Object.entries(entry.attributes)) {
|
|
480
|
-
try {
|
|
481
|
-
el.setAttribute(k, /** @type {string} */ v);
|
|
482
|
-
} catch {}
|
|
483
|
-
}
|
|
484
|
-
}
|
|
485
|
-
if (entry.style) {
|
|
486
|
-
el.style.cssText = entry.style;
|
|
487
|
-
}
|
|
488
|
-
const compoundTag = parentTag && parentTag !== entry.tag ? `${parentTag} ${entry.tag}` : null;
|
|
489
|
-
const tagStyle =
|
|
490
|
-
(compoundTag && _resolveNestedStyle(rootStyle, compoundTag)) ||
|
|
491
|
-
_resolveNestedStyle(rootStyle, entry.tag);
|
|
492
|
-
if (tagStyle) {
|
|
493
|
-
for (const [prop, val] of Object.entries(tagStyle)) {
|
|
494
|
-
if (typeof val === "string" || typeof val === "number") {
|
|
495
|
-
try {
|
|
496
|
-
(el.style as unknown as Record<string, string | number>)[prop] = val;
|
|
497
|
-
} catch {}
|
|
498
|
-
}
|
|
499
|
-
}
|
|
500
|
-
if (activeBreakpoints) {
|
|
501
|
-
// Check media overrides nested inside the tag style (selector wraps media)
|
|
502
|
-
for (const [key, val] of Object.entries(tagStyle)) {
|
|
503
|
-
if (!key.startsWith("@") || typeof val !== "object" || val === null) {
|
|
504
|
-
continue;
|
|
505
|
-
}
|
|
506
|
-
const mediaName = key.slice(1);
|
|
507
|
-
if (mediaName === "--") {
|
|
508
|
-
continue;
|
|
509
|
-
}
|
|
510
|
-
if (activeBreakpoints.has(mediaName)) {
|
|
511
|
-
for (const [prop, v] of Object.entries(val as Record<string, unknown>)) {
|
|
512
|
-
if (typeof v === "string" || typeof v === "number") {
|
|
513
|
-
try {
|
|
514
|
-
(el.style as unknown as Record<string, string | number>)[prop] = v;
|
|
515
|
-
} catch {}
|
|
516
|
-
}
|
|
517
|
-
}
|
|
518
|
-
}
|
|
519
|
-
}
|
|
520
|
-
}
|
|
521
|
-
}
|
|
522
|
-
// Check top-level @media keys for tag-specific overrides (media wraps selector)
|
|
523
|
-
if (activeBreakpoints) {
|
|
524
|
-
const tagPath = compoundTag || entry.tag;
|
|
525
|
-
for (const [key, val] of Object.entries(rootStyle)) {
|
|
526
|
-
if (!key.startsWith("@") || typeof val !== "object" || val === null) {
|
|
527
|
-
continue;
|
|
528
|
-
}
|
|
529
|
-
const mediaName = key.slice(1);
|
|
530
|
-
if (mediaName === "--") {
|
|
531
|
-
continue;
|
|
532
|
-
}
|
|
533
|
-
if (activeBreakpoints.has(mediaName)) {
|
|
534
|
-
const mediaTagStyle = _resolveNestedStyle(val as Record<string, unknown>, tagPath);
|
|
535
|
-
if (mediaTagStyle && typeof mediaTagStyle === "object") {
|
|
536
|
-
for (const [prop, v] of Object.entries(mediaTagStyle)) {
|
|
537
|
-
if (typeof v === "string" || typeof v === "number") {
|
|
538
|
-
try {
|
|
539
|
-
(el.style as unknown as Record<string, string | number>)[prop] = v;
|
|
540
|
-
} catch {}
|
|
541
|
-
}
|
|
542
|
-
}
|
|
543
|
-
}
|
|
544
|
-
}
|
|
545
|
-
}
|
|
546
|
-
}
|
|
547
|
-
if (entry.children) {
|
|
548
|
-
for (const child of entry.children) {
|
|
549
|
-
el.append(buildStylebookElement(child, rootStyle, activeBreakpoints, entry.tag));
|
|
550
|
-
}
|
|
551
|
-
}
|
|
552
|
-
return el;
|
|
553
|
-
}
|
|
554
|
-
|
|
555
|
-
/**
|
|
556
|
-
* Render a live component preview by registering its custom element and instantiating it.
|
|
557
|
-
*
|
|
558
|
-
* @param {ComponentEntry} comp
|
|
559
|
-
* @returns {Promise<HTMLElement>}
|
|
560
|
-
*/
|
|
561
|
-
export async function renderComponentPreview(comp: ComponentEntry) {
|
|
562
|
-
setSkipServerFunctions(true);
|
|
563
|
-
try {
|
|
564
|
-
if (comp.source === "npm") {
|
|
565
|
-
if (!customElements.get(comp.tagName)) {
|
|
566
|
-
return _componentFallback(comp.tagName);
|
|
567
|
-
}
|
|
568
|
-
} else {
|
|
569
|
-
if (comp.path && !comp.path.endsWith(".json")) {
|
|
570
|
-
// Format-class component sources (e.g. markdown) can't be imported as modules
|
|
571
|
-
return _componentFallback(comp.tagName);
|
|
572
|
-
}
|
|
573
|
-
const root = projectState?.projectRoot;
|
|
574
|
-
const url = `${location.origin}/${root ? `${root}/` : ""}${comp.path}`;
|
|
575
|
-
await defineElement(url);
|
|
576
|
-
}
|
|
577
|
-
const el = document.createElement(comp.tagName);
|
|
578
|
-
for (const p of comp.props || []) {
|
|
579
|
-
if (p.default !== undefined && p.default !== "false" && p.default !== "''") {
|
|
580
|
-
const val = String(p.default).replaceAll(/^'|'$/g, "");
|
|
581
|
-
el.setAttribute(p.name, val);
|
|
582
|
-
}
|
|
583
|
-
}
|
|
584
|
-
return el;
|
|
585
|
-
} catch (error) {
|
|
586
|
-
console.warn("Component preview failed:", comp.tagName, error);
|
|
587
|
-
return _componentFallback(comp.tagName);
|
|
588
|
-
}
|
|
589
|
-
}
|
|
590
|
-
|
|
591
|
-
/** @param {string} tagName */
|
|
592
|
-
function _componentFallback(tagName: string) {
|
|
593
|
-
const fallback = document.createElement("div");
|
|
594
|
-
fallback.style.cssText =
|
|
595
|
-
"padding:12px;border:1px dashed var(--border);border-radius:var(--radius);color:var(--fg-dim)";
|
|
596
|
-
fallback.textContent = `<${tagName}>`;
|
|
597
|
-
return fallback;
|
|
598
|
-
}
|
|
599
|
-
|
|
600
|
-
/**
|
|
601
|
-
* @param {Record<string, unknown>} rootStyle
|
|
602
|
-
* @param {string} tag
|
|
603
|
-
*/
|
|
604
|
-
function hasTagStyle(rootStyle: Record<string, unknown>, tag: string) {
|
|
605
|
-
const s = _resolveNestedStyle(rootStyle, tag);
|
|
606
|
-
if (s && typeof s === "object" && Object.keys(s).length > 0) {
|
|
607
|
-
return true;
|
|
608
|
-
}
|
|
609
|
-
for (const [key, val] of Object.entries(rootStyle)) {
|
|
610
|
-
if (!key.startsWith("@") || typeof val !== "object" || val === null) {
|
|
611
|
-
continue;
|
|
612
|
-
}
|
|
613
|
-
const ms = _resolveNestedStyle(val as Record<string, unknown>, tag);
|
|
614
|
-
if (ms && typeof ms === "object" && Object.keys(ms).length > 0) {
|
|
615
|
-
return true;
|
|
616
|
-
}
|
|
617
|
-
}
|
|
618
|
-
return false;
|
|
619
|
-
}
|
|
620
|
-
|
|
621
|
-
/**
|
|
622
|
-
* @param {HTMLElement} canvasEl
|
|
623
|
-
* @param {Record<string, unknown>} rootStyle
|
|
624
|
-
* @param {string} filter
|
|
625
|
-
* @param {boolean | undefined} customizedOnly
|
|
626
|
-
* @param {Set<string> | null} activeBreakpoints
|
|
627
|
-
*/
|
|
628
|
-
export function renderStylebookElementsIntoCanvas(
|
|
629
|
-
canvasEl: HTMLElement,
|
|
630
|
-
rootStyle: Record<string, unknown>,
|
|
631
|
-
filter: string,
|
|
632
|
-
customizedOnly: boolean | undefined,
|
|
633
|
-
activeBreakpoints: Set<string> | null,
|
|
634
|
-
) {
|
|
635
|
-
for (const [k, v] of Object.entries(rootStyle)) {
|
|
636
|
-
if (k.startsWith("--") && (typeof v === "string" || typeof v === "number")) {
|
|
637
|
-
canvasEl.style.setProperty(k, String(v));
|
|
638
|
-
}
|
|
639
|
-
}
|
|
640
|
-
|
|
641
|
-
const sectionTemplates: TemplateResult[] = [];
|
|
642
|
-
|
|
643
|
-
for (const section of stylebookMeta.$sections) {
|
|
644
|
-
let entries = section.elements as StylebookEntry[];
|
|
645
|
-
if (filter) {
|
|
646
|
-
entries = entries.filter(
|
|
647
|
-
(e: StylebookEntry) =>
|
|
648
|
-
e.tag.includes(filter) || section.label.toLowerCase().includes(filter),
|
|
649
|
-
);
|
|
650
|
-
}
|
|
651
|
-
if (customizedOnly) {
|
|
652
|
-
entries = entries.filter((e: StylebookEntry) => hasTagStyle(rootStyle, e.tag));
|
|
653
|
-
}
|
|
654
|
-
if (entries.length === 0) {
|
|
655
|
-
continue;
|
|
656
|
-
}
|
|
657
|
-
|
|
658
|
-
const cardTemplates = entries.map((entry: StylebookEntry) => {
|
|
659
|
-
const el = buildStylebookElement(entry, rootStyle, activeBreakpoints);
|
|
660
|
-
return html`
|
|
661
|
-
<div
|
|
662
|
-
class="element-card"
|
|
663
|
-
${ref((card) => {
|
|
664
|
-
if (!card) {
|
|
665
|
-
return;
|
|
666
|
-
}
|
|
667
|
-
view.stylebookElToTag.set(card, entry.tag);
|
|
668
|
-
elToPath.set(card, ["__sb", entry.tag]);
|
|
669
|
-
for (const child of el.querySelectorAll("*")) {
|
|
670
|
-
const childTag = child.tagName.toLowerCase();
|
|
671
|
-
if (!view.stylebookElToTag.has(child)) {
|
|
672
|
-
const compound = childTag === entry.tag ? entry.tag : `${entry.tag} ${childTag}`;
|
|
673
|
-
view.stylebookElToTag.set(child, compound);
|
|
674
|
-
elToPath.set(child, ["__sb", compound]);
|
|
675
|
-
}
|
|
676
|
-
}
|
|
677
|
-
})}
|
|
678
|
-
>
|
|
679
|
-
<div
|
|
680
|
-
class="element-card-preview"
|
|
681
|
-
${ref((c) => {
|
|
682
|
-
if (c) {
|
|
683
|
-
c.textContent = "";
|
|
684
|
-
c.append(el);
|
|
685
|
-
}
|
|
686
|
-
})}
|
|
687
|
-
></div>
|
|
688
|
-
<div class="element-card-label"><${entry.tag}></div>
|
|
689
|
-
</div>
|
|
690
|
-
`;
|
|
691
|
-
});
|
|
692
|
-
|
|
693
|
-
sectionTemplates.push(html`
|
|
694
|
-
<div class="sb-section">
|
|
695
|
-
<div class="sb-label">${section.label}</div>
|
|
696
|
-
<div class="sb-body">${cardTemplates}</div>
|
|
697
|
-
</div>
|
|
698
|
-
`);
|
|
699
|
-
}
|
|
700
|
-
|
|
701
|
-
// Custom components from registry
|
|
702
|
-
if (componentRegistry.length > 0) {
|
|
703
|
-
let comps = componentRegistry;
|
|
704
|
-
if (filter) {
|
|
705
|
-
comps = comps.filter((c: ComponentEntry) => c.tagName.toLowerCase().includes(filter));
|
|
706
|
-
}
|
|
707
|
-
if (customizedOnly) {
|
|
708
|
-
comps = comps.filter((c: ComponentEntry) => hasTagStyle(rootStyle, c.tagName));
|
|
709
|
-
}
|
|
710
|
-
if (comps.length > 0) {
|
|
711
|
-
const compCards = comps.map((comp: ComponentEntry) => {
|
|
712
|
-
let previewEl: HTMLDivElement | null = null;
|
|
713
|
-
const cardTpl = html`
|
|
714
|
-
<div
|
|
715
|
-
class="element-card"
|
|
716
|
-
style="display:inline-flex;width:auto"
|
|
717
|
-
${ref((card) => {
|
|
718
|
-
if (!card) {
|
|
719
|
-
return;
|
|
720
|
-
}
|
|
721
|
-
view.stylebookElToTag.set(card, comp.tagName);
|
|
722
|
-
elToPath.set(card, ["__sb", comp.tagName]);
|
|
723
|
-
})}
|
|
724
|
-
>
|
|
725
|
-
<div
|
|
726
|
-
class="element-card-preview"
|
|
727
|
-
${ref((c) => {
|
|
728
|
-
if (c) {
|
|
729
|
-
previewEl = c as HTMLDivElement;
|
|
730
|
-
}
|
|
731
|
-
})}
|
|
732
|
-
></div>
|
|
733
|
-
<div class="element-card-label"><${comp.tagName}></div>
|
|
734
|
-
</div>
|
|
735
|
-
`;
|
|
736
|
-
renderComponentPreview(comp)
|
|
737
|
-
.then((el) => {
|
|
738
|
-
if (previewEl) {
|
|
739
|
-
previewEl.append(el);
|
|
740
|
-
}
|
|
741
|
-
})
|
|
742
|
-
.catch(() => {});
|
|
743
|
-
return cardTpl;
|
|
744
|
-
});
|
|
745
|
-
|
|
746
|
-
sectionTemplates.push(html`
|
|
747
|
-
<div class="sb-section">
|
|
748
|
-
<div class="sb-label">Components</div>
|
|
749
|
-
<div class="sb-body">${compCards}</div>
|
|
750
|
-
</div>
|
|
751
|
-
`);
|
|
752
|
-
}
|
|
753
|
-
}
|
|
754
|
-
|
|
755
|
-
if (sectionTemplates.length === 0) {
|
|
756
|
-
litRender(
|
|
757
|
-
html`
|
|
758
|
-
<div style="padding:48px;text-align:center;color:var(--fg-dim);font-size:13px">
|
|
759
|
-
${customizedOnly ? "No customized elements" : "No matching elements"}
|
|
760
|
-
</div>
|
|
761
|
-
`,
|
|
762
|
-
canvasEl,
|
|
763
|
-
);
|
|
764
|
-
} else {
|
|
765
|
-
litRender(html`${sectionTemplates}`, canvasEl);
|
|
766
|
-
}
|
|
767
|
-
}
|
|
768
|
-
|
|
769
|
-
/**
|
|
770
|
-
* Click handler for stylebook canvas — selects elements via elToPath/view.stylebookElToTag mapping
|
|
771
|
-
*
|
|
772
|
-
* @param {StylebookPanel} panel
|
|
773
|
-
*/
|
|
774
|
-
function registerStylebookPanelEvents(panel: StylebookPanel) {
|
|
775
|
-
const { canvas, overlayClk } = panel;
|
|
776
|
-
|
|
777
|
-
overlayClk.addEventListener("click", (e: MouseEvent) => {
|
|
778
|
-
const els = canvas.querySelectorAll("*");
|
|
779
|
-
for (const el of els) {
|
|
780
|
-
(el as HTMLElement).style.pointerEvents = "auto";
|
|
781
|
-
}
|
|
782
|
-
overlayClk.style.display = "none";
|
|
783
|
-
const elements = document.elementsFromPoint(e.clientX, e.clientY);
|
|
784
|
-
overlayClk.style.display = "";
|
|
785
|
-
for (const el of els) {
|
|
786
|
-
(el as HTMLElement).style.pointerEvents = "none";
|
|
787
|
-
}
|
|
788
|
-
|
|
789
|
-
for (const el of elements) {
|
|
790
|
-
if (!canvas.contains(el) || el === canvas) {
|
|
791
|
-
continue;
|
|
792
|
-
}
|
|
793
|
-
let cur = el as Element | null;
|
|
794
|
-
while (cur && cur !== canvas) {
|
|
795
|
-
const tag = view.stylebookElToTag.get(cur);
|
|
796
|
-
if (tag) {
|
|
797
|
-
const newMedia = panel.mediaName === "base" ? null : (panel.mediaName ?? null);
|
|
798
|
-
selectStylebookTag(tag, newMedia);
|
|
799
|
-
if (_ctx) {
|
|
800
|
-
_ctx.updateActivePanelHeaders();
|
|
801
|
-
}
|
|
802
|
-
return;
|
|
803
|
-
}
|
|
804
|
-
cur = cur.parentElement;
|
|
805
|
-
}
|
|
806
|
-
}
|
|
807
|
-
updateSession({ ui: { activeSelector: null, stylebookSelection: null } });
|
|
808
|
-
renderStylebookOverlays();
|
|
809
|
-
});
|
|
810
|
-
|
|
811
|
-
overlayClk.addEventListener("mousemove", (e: MouseEvent) => {
|
|
812
|
-
const els = canvas.querySelectorAll("*");
|
|
813
|
-
for (const el of els) {
|
|
814
|
-
(el as HTMLElement).style.pointerEvents = "auto";
|
|
815
|
-
}
|
|
816
|
-
overlayClk.style.display = "none";
|
|
817
|
-
const elements = document.elementsFromPoint(e.clientX, e.clientY);
|
|
818
|
-
overlayClk.style.display = "";
|
|
819
|
-
for (const el of els) {
|
|
820
|
-
(el as HTMLElement).style.pointerEvents = "none";
|
|
821
|
-
}
|
|
822
|
-
|
|
823
|
-
let hoverTag = null;
|
|
824
|
-
for (const el of elements) {
|
|
825
|
-
if (!canvas.contains(el) || el === canvas) {
|
|
826
|
-
continue;
|
|
827
|
-
}
|
|
828
|
-
let cur = el as Element | null;
|
|
829
|
-
while (cur && cur !== canvas) {
|
|
830
|
-
const tag = view.stylebookElToTag.get(cur);
|
|
831
|
-
if (tag) {
|
|
832
|
-
hoverTag = tag;
|
|
833
|
-
break;
|
|
834
|
-
}
|
|
835
|
-
cur = cur.parentElement;
|
|
836
|
-
}
|
|
837
|
-
if (hoverTag) {
|
|
838
|
-
break;
|
|
839
|
-
}
|
|
840
|
-
}
|
|
841
|
-
|
|
842
|
-
if (hoverTag !== panel._lastHoverTag) {
|
|
843
|
-
panel._lastHoverTag = hoverTag;
|
|
844
|
-
renderStylebookOverlays();
|
|
845
|
-
}
|
|
846
|
-
});
|
|
847
|
-
}
|
|
848
|
-
|
|
849
|
-
/** Find a stylebook element by tag in the canvas */
|
|
850
|
-
function findStylebookEl(canvasEl: HTMLElement, tag: string) {
|
|
851
|
-
for (const child of canvasEl.querySelectorAll("*")) {
|
|
852
|
-
if (view.stylebookElToTag.get(child) === tag) {
|
|
853
|
-
return child as HTMLElement;
|
|
854
|
-
}
|
|
855
|
-
}
|
|
856
|
-
return null;
|
|
857
|
-
}
|
|
213
|
+
/** Re-exported for legacy consumers (the preview now lives in component-preview.ts). */
|
|
214
|
+
export { renderComponentPreview } from "./component-preview";
|