@jxsuite/studio 0.19.0 → 0.20.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.css +98 -98
- package/dist/studio.js +12577 -12183
- package/dist/studio.js.map +146 -144
- package/dist/workers/editor.worker.js +79 -79
- package/dist/workers/json.worker.js +109 -109
- package/dist/workers/ts.worker.js +82 -82
- package/package.json +17 -6
- package/src/browse/browse.js +25 -19
- package/src/canvas/canvas-diff.js +24 -16
- package/src/canvas/canvas-helpers.js +24 -19
- package/src/canvas/canvas-live-render.js +94 -39
- package/src/canvas/canvas-render.js +119 -73
- package/src/canvas/canvas-utils.js +38 -15
- package/src/canvas/nested-site-style.js +50 -0
- package/src/editor/component-inline-edit.js +14 -13
- package/src/editor/content-inline-edit.js +22 -10
- package/src/editor/context-menu.js +1 -1
- package/src/editor/convert-to-component.js +12 -10
- package/src/editor/inline-edit.js +66 -28
- package/src/editor/insertion-helper.js +16 -12
- package/src/editor/shortcuts.js +4 -2
- package/src/editor/slash-menu.js +6 -4
- package/src/files/components.js +1 -1
- package/src/files/file-ops.js +37 -16
- package/src/files/files.js +78 -52
- package/src/github/github-auth.js +122 -0
- package/src/github/github-publish.js +115 -0
- package/src/markdown/md-convert.js +172 -107
- package/src/new-project/new-project-modal.js +204 -0
- package/src/panels/activity-bar.js +22 -20
- package/src/panels/ai-panel.js +399 -0
- package/src/panels/block-action-bar.js +5 -5
- package/src/panels/canvas-dnd.js +8 -2
- package/src/panels/data-explorer.js +19 -13
- package/src/panels/dnd.js +144 -92
- package/src/panels/editors.js +55 -35
- package/src/panels/elements-panel.js +33 -19
- package/src/panels/events-panel.js +15 -10
- package/src/panels/git-panel.js +469 -98
- package/src/panels/head-panel.js +121 -81
- package/src/panels/imports-panel.js +88 -64
- package/src/panels/layers-panel.js +64 -43
- package/src/panels/left-panel.js +85 -47
- package/src/panels/overlays.js +1 -0
- package/src/panels/panel-events.js +45 -30
- package/src/panels/preview-render.js +23 -18
- package/src/panels/properties-panel.js +365 -249
- package/src/panels/pseudo-preview.js +2 -2
- package/src/panels/quick-search.js +7 -5
- package/src/panels/right-panel.js +26 -2
- package/src/panels/shared.js +4 -4
- package/src/panels/signals-panel.js +295 -166
- package/src/panels/statusbar.js +4 -4
- package/src/panels/style-inputs.js +67 -35
- package/src/panels/style-panel.js +233 -132
- package/src/panels/style-utils.js +34 -16
- package/src/panels/stylebook-layers-panel.js +12 -10
- package/src/panels/stylebook-panel.js +134 -66
- package/src/panels/toolbar.js +333 -109
- package/src/panels/welcome-screen.js +121 -0
- package/src/platform.js +2 -4
- package/src/platforms/devserver.js +113 -7
- package/src/resize-edges.js +98 -0
- package/src/services/cem-export.js +12 -7
- package/src/services/code-services.js +30 -12
- package/src/settings/content-types-editor.js +7 -7
- package/src/settings/css-vars-editor.js +30 -24
- package/src/settings/defs-editor.js +12 -7
- package/src/settings/general-settings.js +85 -3
- package/src/settings/head-editor.js +30 -24
- package/src/settings/schema-field-ui.js +58 -39
- package/src/site-context.js +41 -31
- package/src/state.js +70 -29
- package/src/store.js +21 -26
- package/src/studio.js +103 -81
- package/src/tabs/tab.js +67 -43
- package/src/tabs/transact.js +23 -13
- package/src/ui/button-group.js +24 -24
- package/src/ui/color-selector.js +28 -23
- package/src/ui/field-row.js +3 -3
- package/src/ui/icons.js +2 -2
- package/src/ui/media-picker.js +7 -7
- package/src/ui/spectrum.js +5 -1
- package/src/ui/unit-selector.js +18 -16
- package/src/ui/value-selector.js +21 -15
- package/src/ui/widgets.js +22 -19
- package/src/utils/canvas-media.js +5 -4
- package/src/utils/edit-display.js +31 -20
- package/src/utils/google-fonts.js +11 -11
- package/src/utils/inherited-style.js +9 -8
- package/src/utils/studio-utils.js +7 -7
- package/src/view.js +58 -1
- package/src/workspace/workspace.js +10 -7
|
@@ -13,10 +13,23 @@ import { projectState } from "../store.js";
|
|
|
13
13
|
import { updateSiteConfig } from "../site-context.js";
|
|
14
14
|
import { getPlatform } from "../platform.js";
|
|
15
15
|
|
|
16
|
+
/** @typedef {import("../files/components.js").ComponentEntry} ComponentEntry */
|
|
17
|
+
|
|
18
|
+
/** @typedef {string | JxMutableNode | { $ref: string }} ElementsEntry */
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* @typedef {{
|
|
22
|
+
* renderLeftPanel: () => void;
|
|
23
|
+
* documentPath: string | null;
|
|
24
|
+
* documentElements: ElementsEntry[];
|
|
25
|
+
* applyMutation: (fn: (doc: JxMutableNode) => void) => void;
|
|
26
|
+
* }} ImportsContext
|
|
27
|
+
*/
|
|
28
|
+
|
|
16
29
|
/**
|
|
17
30
|
* Build the subpath specifier for a component: `<package>/<modulePath>`
|
|
18
31
|
*
|
|
19
|
-
* @param {
|
|
32
|
+
* @param {ComponentEntry} comp
|
|
20
33
|
* @returns {string}
|
|
21
34
|
*/
|
|
22
35
|
function componentSpecifier(comp) {
|
|
@@ -27,8 +40,8 @@ function componentSpecifier(comp) {
|
|
|
27
40
|
* Check if a component is enabled (present in $elements array). Supports both cherry-picked subpath
|
|
28
41
|
* specifiers and legacy full-package imports.
|
|
29
42
|
*
|
|
30
|
-
* @param {
|
|
31
|
-
* @param {
|
|
43
|
+
* @param {ComponentEntry} comp
|
|
44
|
+
* @param {ElementsEntry[]} elements
|
|
32
45
|
* @returns {boolean}
|
|
33
46
|
*/
|
|
34
47
|
function isComponentEnabled(comp, elements) {
|
|
@@ -47,10 +60,10 @@ function isComponentEnabled(comp, elements) {
|
|
|
47
60
|
/**
|
|
48
61
|
* Group npm components by package name.
|
|
49
62
|
*
|
|
50
|
-
* @returns {Map<string,
|
|
63
|
+
* @returns {Map<string, ComponentEntry[]>}
|
|
51
64
|
*/
|
|
52
65
|
function groupByPackage() {
|
|
53
|
-
/** @type {Map<string,
|
|
66
|
+
/** @type {Map<string, ComponentEntry[]>} */
|
|
54
67
|
const groups = new Map();
|
|
55
68
|
for (const comp of componentRegistry) {
|
|
56
69
|
if (comp.source !== "npm" || !comp.package || !comp.modulePath) continue;
|
|
@@ -61,13 +74,8 @@ function groupByPackage() {
|
|
|
61
74
|
}
|
|
62
75
|
|
|
63
76
|
/**
|
|
64
|
-
* @param {
|
|
65
|
-
*
|
|
66
|
-
* documentPath: string | null;
|
|
67
|
-
* documentElements: any[];
|
|
68
|
-
* applyMutation: (fn: (doc: any) => void) => void;
|
|
69
|
-
* }} ctx
|
|
70
|
-
* @returns {any}
|
|
77
|
+
* @param {ImportsContext} ctx
|
|
78
|
+
* @returns {import("lit-html").TemplateResult}
|
|
71
79
|
*/
|
|
72
80
|
export function renderImportsTemplate({
|
|
73
81
|
renderLeftPanel,
|
|
@@ -95,7 +103,9 @@ export function renderImportsTemplate({
|
|
|
95
103
|
function renderSiteLevelImports(renderLeftPanel) {
|
|
96
104
|
const siteImports = projectState?.projectConfig?.imports || {};
|
|
97
105
|
const entries = Object.entries(siteImports);
|
|
98
|
-
const siteElements =
|
|
106
|
+
const siteElements = /** @type {ElementsEntry[]} */ (
|
|
107
|
+
projectState?.projectConfig?.$elements || []
|
|
108
|
+
);
|
|
99
109
|
|
|
100
110
|
const packageGroups = groupByPackage();
|
|
101
111
|
|
|
@@ -141,10 +151,14 @@ function renderSiteLevelImports(renderLeftPanel) {
|
|
|
141
151
|
quiet
|
|
142
152
|
size="xs"
|
|
143
153
|
title="Add import"
|
|
144
|
-
@click=${async (/** @type {
|
|
145
|
-
const form = e.target.closest(".import-add-form");
|
|
146
|
-
const nameField =
|
|
147
|
-
|
|
154
|
+
@click=${async (/** @type {Event} */ e) => {
|
|
155
|
+
const form = /** @type {HTMLElement} */ (e.target).closest(".import-add-form");
|
|
156
|
+
const nameField = /** @type {HTMLInputElement} */ (
|
|
157
|
+
form?.querySelector(".import-add-name")
|
|
158
|
+
);
|
|
159
|
+
const pathField = /** @type {HTMLInputElement} */ (
|
|
160
|
+
form?.querySelector(".import-add-path")
|
|
161
|
+
);
|
|
148
162
|
const name = nameField?.value?.trim();
|
|
149
163
|
const path = pathField?.value?.trim();
|
|
150
164
|
if (!name || !path) return;
|
|
@@ -177,13 +191,16 @@ function renderSiteLevelImports(renderLeftPanel) {
|
|
|
177
191
|
await platform.removePackage(pkg);
|
|
178
192
|
// Also remove all cherry-picked elements for this package
|
|
179
193
|
const updatedElements = siteElements.filter(
|
|
180
|
-
(/** @type {
|
|
194
|
+
(/** @type {ElementsEntry} */ e) =>
|
|
195
|
+
typeof e !== "string" || !e.startsWith(pkg + "/"),
|
|
181
196
|
);
|
|
182
197
|
const { loadComponentRegistry } = await import("../files/components.js");
|
|
183
198
|
await loadComponentRegistry();
|
|
184
|
-
await updateSiteConfig({
|
|
199
|
+
await updateSiteConfig({
|
|
200
|
+
$elements: /** @type {(string | JxElement)[]} */ (updatedElements),
|
|
201
|
+
});
|
|
185
202
|
renderLeftPanel();
|
|
186
|
-
} catch (/** @type {
|
|
203
|
+
} catch (/** @type {unknown} */ e) {
|
|
187
204
|
console.error("Failed to remove package:", e);
|
|
188
205
|
}
|
|
189
206
|
}}
|
|
@@ -192,7 +209,7 @@ function renderSiteLevelImports(renderLeftPanel) {
|
|
|
192
209
|
</sp-action-button>
|
|
193
210
|
</div>
|
|
194
211
|
<div class="imports-list imports-component-list">
|
|
195
|
-
${comps.map((/** @type {
|
|
212
|
+
${comps.map((/** @type {ComponentEntry} */ comp) => {
|
|
196
213
|
const enabled = isComponentEnabled(comp, siteElements);
|
|
197
214
|
const specifier = componentSpecifier(comp);
|
|
198
215
|
return html`
|
|
@@ -200,16 +217,20 @@ function renderSiteLevelImports(renderLeftPanel) {
|
|
|
200
217
|
<sp-checkbox
|
|
201
218
|
size="s"
|
|
202
219
|
.checked=${enabled}
|
|
203
|
-
@change=${async (/** @type {
|
|
220
|
+
@change=${async (/** @type {Event} */ e) => {
|
|
204
221
|
let updated = [...siteElements];
|
|
205
222
|
// Remove legacy full-package import if present
|
|
206
|
-
updated = updated.filter((/** @type {
|
|
207
|
-
if (e.target.checked) {
|
|
223
|
+
updated = updated.filter((/** @type {ElementsEntry} */ el) => el !== pkg);
|
|
224
|
+
if (/** @type {HTMLInputElement} */ (e.target).checked) {
|
|
208
225
|
if (!updated.includes(specifier)) updated.push(specifier);
|
|
209
226
|
} else {
|
|
210
|
-
updated = updated.filter(
|
|
227
|
+
updated = updated.filter(
|
|
228
|
+
(/** @type {ElementsEntry} */ el) => el !== specifier,
|
|
229
|
+
);
|
|
211
230
|
}
|
|
212
|
-
await updateSiteConfig({
|
|
231
|
+
await updateSiteConfig({
|
|
232
|
+
$elements: /** @type {(string | JxElement)[]} */ (updated),
|
|
233
|
+
});
|
|
213
234
|
renderLeftPanel();
|
|
214
235
|
}}
|
|
215
236
|
>
|
|
@@ -233,18 +254,18 @@ function renderSiteLevelImports(renderLeftPanel) {
|
|
|
233
254
|
placeholder="Package name…"
|
|
234
255
|
size="s"
|
|
235
256
|
style="flex:1"
|
|
236
|
-
@keydown=${async (/** @type {
|
|
257
|
+
@keydown=${async (/** @type {KeyboardEvent} */ e) => {
|
|
237
258
|
if (e.key !== "Enter") return;
|
|
238
|
-
const name = e.target.value?.trim();
|
|
259
|
+
const name = /** @type {HTMLInputElement} */ (e.target).value?.trim();
|
|
239
260
|
if (!name) return;
|
|
240
|
-
e.target.value = "";
|
|
261
|
+
/** @type {HTMLInputElement} */ (e.target).value = "";
|
|
241
262
|
try {
|
|
242
263
|
const platform = getPlatform();
|
|
243
264
|
await platform.addPackage(name);
|
|
244
265
|
const { loadComponentRegistry } = await import("../files/components.js");
|
|
245
266
|
await loadComponentRegistry();
|
|
246
267
|
renderLeftPanel();
|
|
247
|
-
} catch (/** @type {
|
|
268
|
+
} catch (/** @type {unknown} */ err) {
|
|
248
269
|
console.error("Failed to add package:", err);
|
|
249
270
|
}
|
|
250
271
|
}}
|
|
@@ -253,18 +274,20 @@ function renderSiteLevelImports(renderLeftPanel) {
|
|
|
253
274
|
quiet
|
|
254
275
|
size="xs"
|
|
255
276
|
title="Add package"
|
|
256
|
-
@click=${async (/** @type {
|
|
257
|
-
const input = e.target
|
|
258
|
-
|
|
277
|
+
@click=${async (/** @type {Event} */ e) => {
|
|
278
|
+
const input = /** @type {HTMLElement} */ (e.target)
|
|
279
|
+
.closest(".import-add-form")
|
|
280
|
+
?.querySelector("sp-textfield");
|
|
281
|
+
const name = /** @type {HTMLInputElement | null} */ (input)?.value?.trim();
|
|
259
282
|
if (!name) return;
|
|
260
|
-
input.value = "";
|
|
283
|
+
/** @type {HTMLInputElement} */ (input).value = "";
|
|
261
284
|
try {
|
|
262
285
|
const platform = getPlatform();
|
|
263
286
|
await platform.addPackage(name);
|
|
264
287
|
const { loadComponentRegistry } = await import("../files/components.js");
|
|
265
288
|
await loadComponentRegistry();
|
|
266
289
|
renderLeftPanel();
|
|
267
|
-
} catch (/** @type {
|
|
290
|
+
} catch (/** @type {unknown} */ err) {
|
|
268
291
|
console.error("Failed to add package:", err);
|
|
269
292
|
}
|
|
270
293
|
}}
|
|
@@ -279,14 +302,7 @@ function renderSiteLevelImports(renderLeftPanel) {
|
|
|
279
302
|
|
|
280
303
|
// ─── Document-level: Component Imports + npm Component Cherry-pick ───────────
|
|
281
304
|
|
|
282
|
-
/**
|
|
283
|
-
* @param {{
|
|
284
|
-
* renderLeftPanel: () => void;
|
|
285
|
-
* documentPath: string | null;
|
|
286
|
-
* documentElements: any[];
|
|
287
|
-
* applyMutation: (fn: (doc: any) => void) => void;
|
|
288
|
-
* }} ctx
|
|
289
|
-
*/
|
|
305
|
+
/** @param {ImportsContext} ctx */
|
|
290
306
|
function renderDocumentLevelImports({
|
|
291
307
|
renderLeftPanel,
|
|
292
308
|
documentPath,
|
|
@@ -294,14 +310,18 @@ function renderDocumentLevelImports({
|
|
|
294
310
|
applyMutation,
|
|
295
311
|
}) {
|
|
296
312
|
const refEntries = documentElements.filter(
|
|
297
|
-
(/** @type {
|
|
313
|
+
(/** @type {ElementsEntry} */ e) => e && typeof e === "object" && e.$ref,
|
|
314
|
+
);
|
|
315
|
+
const npmEntries = documentElements.filter(
|
|
316
|
+
(/** @type {ElementsEntry} */ e) => typeof e === "string",
|
|
298
317
|
);
|
|
299
|
-
const npmEntries = documentElements.filter((/** @type {any} */ e) => typeof e === "string");
|
|
300
318
|
|
|
301
319
|
// Available JX components not yet imported
|
|
302
|
-
const importedRefs = new Set(
|
|
320
|
+
const importedRefs = new Set(
|
|
321
|
+
refEntries.map((/** @type {ElementsEntry} */ e) => /** @type {{ $ref: string }} */ (e).$ref),
|
|
322
|
+
);
|
|
303
323
|
const availableComponents = componentRegistry.filter(
|
|
304
|
-
(/** @type {
|
|
324
|
+
(/** @type {ComponentEntry} */ c) =>
|
|
305
325
|
c.source !== "npm" && !importedRefs.has(`./${c.path}`) && !importedRefs.has(c.path),
|
|
306
326
|
);
|
|
307
327
|
|
|
@@ -309,9 +329,9 @@ function renderDocumentLevelImports({
|
|
|
309
329
|
|
|
310
330
|
/** @param {string} ref */
|
|
311
331
|
const removeRef = (ref) => {
|
|
312
|
-
applyMutation((/** @type {
|
|
332
|
+
applyMutation((/** @type {JxMutableNode} */ doc) => {
|
|
313
333
|
doc.$elements = (doc.$elements || []).filter(
|
|
314
|
-
(/** @type {
|
|
334
|
+
(/** @type {ElementsEntry} */ e) => !(e && typeof e === "object" && e.$ref === ref),
|
|
315
335
|
);
|
|
316
336
|
});
|
|
317
337
|
renderLeftPanel();
|
|
@@ -329,14 +349,18 @@ function renderDocumentLevelImports({
|
|
|
329
349
|
? html`
|
|
330
350
|
<div class="imports-list">
|
|
331
351
|
${refEntries.map(
|
|
332
|
-
(/** @type {
|
|
352
|
+
(/** @type {ElementsEntry} */ entry) => html`
|
|
333
353
|
<div class="import-row">
|
|
334
|
-
<span
|
|
354
|
+
<span
|
|
355
|
+
class="import-path"
|
|
356
|
+
title=${/** @type {{ $ref: string }} */ (entry).$ref}
|
|
357
|
+
>${/** @type {{ $ref: string }} */ (entry).$ref}</span
|
|
358
|
+
>
|
|
335
359
|
<sp-action-button
|
|
336
360
|
quiet
|
|
337
361
|
size="xs"
|
|
338
362
|
title="Remove"
|
|
339
|
-
@click=${() => removeRef(entry.$ref)}
|
|
363
|
+
@click=${() => removeRef(/** @type {{ $ref: string }} */ (entry).$ref)}
|
|
340
364
|
>
|
|
341
365
|
<sp-icon-close slot="icon" size="xs"></sp-icon-close>
|
|
342
366
|
</sp-action-button>
|
|
@@ -353,16 +377,16 @@ function renderDocumentLevelImports({
|
|
|
353
377
|
size="s"
|
|
354
378
|
label="Add component…"
|
|
355
379
|
class="import-picker"
|
|
356
|
-
@change=${(/** @type {
|
|
357
|
-
const tag = e.target.value;
|
|
380
|
+
@change=${(/** @type {Event} */ e) => {
|
|
381
|
+
const tag = /** @type {HTMLInputElement} */ (e.target).value;
|
|
358
382
|
if (!tag) return;
|
|
359
|
-
e.target.value = "";
|
|
383
|
+
/** @type {HTMLInputElement} */ (e.target).value = "";
|
|
360
384
|
const comp = componentRegistry.find(
|
|
361
|
-
(/** @type {
|
|
385
|
+
(/** @type {ComponentEntry} */ c) => c.tagName === tag,
|
|
362
386
|
);
|
|
363
387
|
if (!comp) return;
|
|
364
388
|
const relPath = computeRelativePath(documentPath, comp.path);
|
|
365
|
-
applyMutation((/** @type {
|
|
389
|
+
applyMutation((/** @type {JxMutableNode} */ doc) => {
|
|
366
390
|
if (!doc.$elements) doc.$elements = [];
|
|
367
391
|
doc.$elements.push({ $ref: relPath });
|
|
368
392
|
});
|
|
@@ -370,7 +394,7 @@ function renderDocumentLevelImports({
|
|
|
370
394
|
}}
|
|
371
395
|
>
|
|
372
396
|
${availableComponents.map(
|
|
373
|
-
(/** @type {
|
|
397
|
+
(/** @type {ComponentEntry} */ c) =>
|
|
374
398
|
html`<sp-menu-item value=${c.tagName}><${c.tagName}></sp-menu-item>`,
|
|
375
399
|
)}
|
|
376
400
|
</sp-picker>
|
|
@@ -387,7 +411,7 @@ function renderDocumentLevelImports({
|
|
|
387
411
|
<span class="imports-section-title import-mono">${pkg}</span>
|
|
388
412
|
</div>
|
|
389
413
|
<div class="imports-list imports-component-list">
|
|
390
|
-
${comps.map((/** @type {
|
|
414
|
+
${comps.map((/** @type {ComponentEntry} */ comp) => {
|
|
391
415
|
const enabled = isComponentEnabled(comp, npmEntries);
|
|
392
416
|
const specifier = componentSpecifier(comp);
|
|
393
417
|
return html`
|
|
@@ -395,18 +419,18 @@ function renderDocumentLevelImports({
|
|
|
395
419
|
<sp-checkbox
|
|
396
420
|
size="s"
|
|
397
421
|
.checked=${enabled}
|
|
398
|
-
@change=${(/** @type {
|
|
399
|
-
applyMutation((/** @type {
|
|
422
|
+
@change=${(/** @type {Event} */ e) => {
|
|
423
|
+
applyMutation((/** @type {JxMutableNode} */ doc) => {
|
|
400
424
|
if (!doc.$elements) doc.$elements = [];
|
|
401
425
|
// Remove legacy full-package import if present
|
|
402
426
|
doc.$elements = doc.$elements.filter(
|
|
403
|
-
(/** @type {
|
|
427
|
+
(/** @type {ElementsEntry} */ el) => el !== pkg,
|
|
404
428
|
);
|
|
405
|
-
if (e.target.checked) {
|
|
429
|
+
if (/** @type {HTMLInputElement} */ (e.target).checked) {
|
|
406
430
|
if (!doc.$elements.includes(specifier)) doc.$elements.push(specifier);
|
|
407
431
|
} else {
|
|
408
432
|
doc.$elements = doc.$elements.filter(
|
|
409
|
-
(/** @type {
|
|
433
|
+
(/** @type {ElementsEntry} */ el) => el !== specifier,
|
|
410
434
|
);
|
|
411
435
|
}
|
|
412
436
|
});
|
|
@@ -31,7 +31,7 @@ import { panToElement } from "../canvas/canvas-utils.js";
|
|
|
31
31
|
/**
|
|
32
32
|
* Start inline title editing on a layer row.
|
|
33
33
|
*
|
|
34
|
-
* @param {
|
|
34
|
+
* @param {JxPath} path
|
|
35
35
|
* @param {() => void} rerender
|
|
36
36
|
*/
|
|
37
37
|
export function startLayerTitleEdit(path, rerender) {
|
|
@@ -87,7 +87,7 @@ export function startLayerTitleEdit(path, rerender) {
|
|
|
87
87
|
}
|
|
88
88
|
|
|
89
89
|
/**
|
|
90
|
-
* @param {{ navigateToComponent:
|
|
90
|
+
* @param {{ navigateToComponent: (path: string) => void; rerender: () => void }} ctx
|
|
91
91
|
* @returns {import("lit-html").TemplateResult}
|
|
92
92
|
*/
|
|
93
93
|
export function renderLayersTemplate(ctx) {
|
|
@@ -99,7 +99,7 @@ export function renderLayersTemplate(ctx) {
|
|
|
99
99
|
const rows = flattenTree(tab?.doc.document);
|
|
100
100
|
const collapsed = view._layersCollapsed || (view._layersCollapsed = new Set());
|
|
101
101
|
|
|
102
|
-
/** @type {
|
|
102
|
+
/** @type {import("lit-html").TemplateResult[]} */
|
|
103
103
|
const layerRows = [];
|
|
104
104
|
for (const { node, path, depth, nodeType } of rows) {
|
|
105
105
|
let hidden = false;
|
|
@@ -128,28 +128,40 @@ export function renderLayersTemplate(ctx) {
|
|
|
128
128
|
continue;
|
|
129
129
|
}
|
|
130
130
|
|
|
131
|
+
// After text-node skip, node is guaranteed to be JxMutableNode (not a primitive)
|
|
132
|
+
if (typeof node !== "object" || node === null) continue;
|
|
133
|
+
/** @type {JxMutableNode} */
|
|
134
|
+
const jxNode = /** @type {JxMutableNode} */ (node);
|
|
135
|
+
|
|
131
136
|
if (path.length >= 2 && nodeType === "element") {
|
|
132
137
|
const pPath = parentElementPath(path);
|
|
133
138
|
const parentNode = pPath ? getNodeAtPath(tab?.doc.document, pPath) : null;
|
|
134
|
-
if (parentNode && isInlineElement(
|
|
139
|
+
if (parentNode && isInlineElement(jxNode, parentNode)) continue;
|
|
135
140
|
}
|
|
136
141
|
|
|
137
142
|
const key = pathKey(path);
|
|
138
143
|
const isSelected = pathsEqual(path, tab?.session.selection);
|
|
139
|
-
const hasChildren = Array.isArray(
|
|
144
|
+
const hasChildren = Array.isArray(jxNode.children) && jxNode.children.length > 0;
|
|
140
145
|
const hasMapChildren =
|
|
141
|
-
|
|
146
|
+
jxNode.children &&
|
|
147
|
+
typeof jxNode.children === "object" &&
|
|
148
|
+
/** @type {{ $prototype?: string }} */ (/** @type {unknown} */ (jxNode.children))
|
|
149
|
+
.$prototype === "Array";
|
|
142
150
|
const hasCases =
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
typeof
|
|
146
|
-
Object.keys(
|
|
151
|
+
jxNode.$switch &&
|
|
152
|
+
jxNode.cases &&
|
|
153
|
+
typeof jxNode.cases === "object" &&
|
|
154
|
+
Object.keys(jxNode.cases).length > 0;
|
|
147
155
|
const isExpandable =
|
|
148
|
-
hasChildren || hasMapChildren || hasCases || (nodeType === "map" &&
|
|
149
|
-
const isVoidEl = VOID_ELEMENTS.has((
|
|
156
|
+
hasChildren || hasMapChildren || hasCases || (nodeType === "map" && jxNode.map);
|
|
157
|
+
const isVoidEl = VOID_ELEMENTS.has((jxNode.tagName || "div").toLowerCase());
|
|
150
158
|
|
|
151
|
-
/** @type {
|
|
152
|
-
let badgeClass
|
|
159
|
+
/** @type {string} */
|
|
160
|
+
let badgeClass;
|
|
161
|
+
/** @type {string | number} */
|
|
162
|
+
let badgeText;
|
|
163
|
+
/** @type {string | undefined} */
|
|
164
|
+
let badgeTitle;
|
|
153
165
|
if (nodeType === "map") {
|
|
154
166
|
badgeClass = "layer-tag map-tag";
|
|
155
167
|
badgeText = "↻";
|
|
@@ -158,41 +170,48 @@ export function renderLayersTemplate(ctx) {
|
|
|
158
170
|
badgeClass = "layer-tag case-tag";
|
|
159
171
|
badgeText = path[path.length - 1];
|
|
160
172
|
badgeTitle = `$switch case: ${path[path.length - 1]}`;
|
|
161
|
-
} else if (
|
|
173
|
+
} else if (jxNode.$switch) {
|
|
162
174
|
badgeClass = "layer-tag switch-tag";
|
|
163
175
|
badgeText = "⇄";
|
|
164
176
|
badgeTitle = "$switch";
|
|
165
177
|
} else {
|
|
166
178
|
badgeClass = "layer-tag";
|
|
167
|
-
badgeText =
|
|
179
|
+
badgeText = jxNode.tagName || "div";
|
|
168
180
|
badgeTitle = undefined;
|
|
169
181
|
}
|
|
170
182
|
|
|
171
|
-
/** @type {
|
|
172
|
-
let labelText
|
|
183
|
+
/** @type {string} */
|
|
184
|
+
let labelText;
|
|
185
|
+
/** @type {boolean} */
|
|
186
|
+
let labelItalic;
|
|
173
187
|
if (nodeType === "case-ref") {
|
|
174
|
-
labelText =
|
|
188
|
+
labelText = jxNode.$ref || "external";
|
|
175
189
|
labelItalic = true;
|
|
176
190
|
} else {
|
|
177
|
-
labelText = nodeLabel(
|
|
191
|
+
labelText = nodeLabel(jxNode);
|
|
178
192
|
labelItalic = false;
|
|
179
193
|
}
|
|
180
194
|
|
|
181
195
|
const isElement = nodeType === "element";
|
|
182
196
|
const isRoot = tab?.doc.mode === "content" ? path.length === 0 : path.length < 2;
|
|
183
197
|
const idx = isElement ? /** @type {number} */ (childIndex(path)) : 0;
|
|
184
|
-
const parentPath =
|
|
198
|
+
const parentPath =
|
|
199
|
+
isElement && !isRoot ? /** @type {JxPath} */ (parentElementPath(path)) : null;
|
|
185
200
|
const parentNode = parentPath ? getNodeAtPath(tab?.doc.document, parentPath) : null;
|
|
186
201
|
const siblingCount = parentNode?.children?.length || 0;
|
|
187
202
|
const canMoveUp = isElement && !isRoot && idx > 0;
|
|
188
203
|
const canMoveDown = isElement && !isRoot && idx < siblingCount - 1;
|
|
189
|
-
const prevSibling = canMoveUp && parentNode ? parentNode.children[idx - 1] : null;
|
|
204
|
+
const prevSibling = canMoveUp && parentNode ? parentNode.children?.[idx - 1] : null;
|
|
190
205
|
const prevIsContainer = (() => {
|
|
191
206
|
if (!prevSibling || typeof prevSibling !== "object") return false;
|
|
192
207
|
if (VOID_ELEMENTS.has((prevSibling.tagName || "div").toLowerCase())) return false;
|
|
193
208
|
const ch = prevSibling.children;
|
|
194
209
|
if (!ch) return false;
|
|
195
|
-
if (
|
|
210
|
+
if (
|
|
211
|
+
typeof ch === "object" &&
|
|
212
|
+
/** @type {{ $prototype?: string }} */ (/** @type {unknown} */ (ch)).$prototype === "Array"
|
|
213
|
+
)
|
|
214
|
+
return true;
|
|
196
215
|
if (!Array.isArray(ch)) return false;
|
|
197
216
|
if (ch.length === 0) return true;
|
|
198
217
|
return ch.some(
|
|
@@ -202,7 +221,7 @@ export function renderLayersTemplate(ctx) {
|
|
|
202
221
|
const canMoveIn = isElement && !isRoot && prevIsContainer;
|
|
203
222
|
const grandparentPath =
|
|
204
223
|
isElement && parentPath && parentPath.length >= 2
|
|
205
|
-
? /** @type {
|
|
224
|
+
? /** @type {JxPath} */ (parentElementPath(parentPath))
|
|
206
225
|
: null;
|
|
207
226
|
const canMoveOut = isElement && !isRoot && !!grandparentPath;
|
|
208
227
|
|
|
@@ -219,13 +238,13 @@ export function renderLayersTemplate(ctx) {
|
|
|
219
238
|
panToElement(path);
|
|
220
239
|
}}
|
|
221
240
|
@dblclick=${isElement
|
|
222
|
-
? (/** @type {
|
|
241
|
+
? (/** @type {MouseEvent} */ e) => {
|
|
223
242
|
e.stopPropagation();
|
|
224
243
|
startLayerTitleEdit(path, ctx.rerender);
|
|
225
244
|
}
|
|
226
245
|
: nothing}
|
|
227
246
|
@contextmenu=${isElement
|
|
228
|
-
? (/** @type {
|
|
247
|
+
? (/** @type {MouseEvent} */ e) =>
|
|
229
248
|
showContextMenu(e, path, {
|
|
230
249
|
onEditComponent: ctx.navigateToComponent,
|
|
231
250
|
rerender: ctx.rerender,
|
|
@@ -255,12 +274,11 @@ export function renderLayersTemplate(ctx) {
|
|
|
255
274
|
quiet
|
|
256
275
|
size="xs"
|
|
257
276
|
title="Move up"
|
|
258
|
-
@click=${(/** @type {
|
|
277
|
+
@click=${(/** @type {MouseEvent} */ e) => {
|
|
259
278
|
e.stopPropagation();
|
|
260
279
|
/** @type {HTMLElement} */ (e.currentTarget).blur();
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
);
|
|
280
|
+
const pp = /** @type {JxPath} */ (parentPath);
|
|
281
|
+
transactDoc(activeTab.value, (t) => mutateMoveNode(t, path, pp, idx - 1));
|
|
264
282
|
}}
|
|
265
283
|
>
|
|
266
284
|
<sp-icon-arrow-up slot="icon"></sp-icon-arrow-up>
|
|
@@ -271,12 +289,11 @@ export function renderLayersTemplate(ctx) {
|
|
|
271
289
|
quiet
|
|
272
290
|
size="xs"
|
|
273
291
|
title="Move down"
|
|
274
|
-
@click=${(/** @type {
|
|
292
|
+
@click=${(/** @type {MouseEvent} */ e) => {
|
|
275
293
|
e.stopPropagation();
|
|
276
294
|
/** @type {HTMLElement} */ (e.currentTarget).blur();
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
);
|
|
295
|
+
const pp = /** @type {JxPath} */ (parentPath);
|
|
296
|
+
transactDoc(activeTab.value, (t) => mutateMoveNode(t, path, pp, idx + 2));
|
|
280
297
|
}}
|
|
281
298
|
>
|
|
282
299
|
<sp-icon-arrow-down slot="icon"></sp-icon-arrow-down>
|
|
@@ -287,10 +304,11 @@ export function renderLayersTemplate(ctx) {
|
|
|
287
304
|
quiet
|
|
288
305
|
size="xs"
|
|
289
306
|
title="Move into previous sibling"
|
|
290
|
-
@click=${(/** @type {
|
|
307
|
+
@click=${(/** @type {MouseEvent} */ e) => {
|
|
291
308
|
e.stopPropagation();
|
|
292
309
|
/** @type {HTMLElement} */ (e.currentTarget).blur();
|
|
293
|
-
const
|
|
310
|
+
const pp = /** @type {JxPath} */ (parentPath);
|
|
311
|
+
const prevPath = [...pp, idx - 1];
|
|
294
312
|
const prev = getNodeAtPath(activeTab.value?.doc.document, prevPath);
|
|
295
313
|
const len = prev?.children?.length || 0;
|
|
296
314
|
transactDoc(activeTab.value, (t) => mutateMoveNode(t, path, prevPath, len));
|
|
@@ -304,12 +322,15 @@ export function renderLayersTemplate(ctx) {
|
|
|
304
322
|
quiet
|
|
305
323
|
size="xs"
|
|
306
324
|
title="Move out of parent"
|
|
307
|
-
@click=${(/** @type {
|
|
325
|
+
@click=${(/** @type {MouseEvent} */ e) => {
|
|
308
326
|
e.stopPropagation();
|
|
309
327
|
/** @type {HTMLElement} */ (e.currentTarget).blur();
|
|
310
|
-
const
|
|
328
|
+
const gp = /** @type {JxPath} */ (grandparentPath);
|
|
329
|
+
const parentIdx = /** @type {number} */ (
|
|
330
|
+
childIndex(/** @type {JxPath} */ (parentPath))
|
|
331
|
+
);
|
|
311
332
|
transactDoc(activeTab.value, (t) =>
|
|
312
|
-
mutateMoveNode(t, path,
|
|
333
|
+
mutateMoveNode(t, path, gp, parentIdx + 1),
|
|
313
334
|
);
|
|
314
335
|
}}
|
|
315
336
|
>
|
|
@@ -321,7 +342,7 @@ export function renderLayersTemplate(ctx) {
|
|
|
321
342
|
size="xs"
|
|
322
343
|
class="layer-delete"
|
|
323
344
|
title="Delete"
|
|
324
|
-
@click=${(/** @type {
|
|
345
|
+
@click=${(/** @type {MouseEvent} */ e) => {
|
|
325
346
|
e.stopPropagation();
|
|
326
347
|
transactDoc(activeTab.value, (t) => mutateRemoveNode(t, path));
|
|
327
348
|
}}
|
|
@@ -339,13 +360,13 @@ export function renderLayersTemplate(ctx) {
|
|
|
339
360
|
<div class="layers-container" style="position:relative">
|
|
340
361
|
<div
|
|
341
362
|
class="layers-tree"
|
|
342
|
-
@click=${(/** @type {
|
|
343
|
-
const toggle = e.target.closest(".layer-toggle");
|
|
363
|
+
@click=${(/** @type {MouseEvent} */ e) => {
|
|
364
|
+
const toggle = /** @type {HTMLElement} */ (e.target).closest(".layer-toggle");
|
|
344
365
|
if (!toggle) return;
|
|
345
366
|
e.stopPropagation();
|
|
346
367
|
const row = toggle.closest(".layer-row");
|
|
347
368
|
if (!row) return;
|
|
348
|
-
const key = row.dataset.path;
|
|
369
|
+
const key = /** @type {HTMLElement} */ (row).dataset.path;
|
|
349
370
|
if (!key) return;
|
|
350
371
|
if (collapsed.has(key)) collapsed.delete(key);
|
|
351
372
|
else collapsed.add(key);
|