@jxsuite/studio 0.19.0 → 0.21.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 +13476 -12330
- package/dist/studio.js.map +152 -148
- 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 +19 -7
- package/src/browse/browse-modal.js +16 -13
- package/src/browse/browse.js +44 -35
- package/src/canvas/canvas-diff.js +24 -16
- package/src/canvas/canvas-helpers.js +24 -19
- package/src/canvas/canvas-live-render.js +113 -39
- package/src/canvas/canvas-render.js +119 -73
- package/src/canvas/canvas-utils.js +51 -21
- 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 +28 -14
- package/src/editor/convert-targets.js +60 -0
- package/src/editor/convert-to-component.js +20 -19
- package/src/editor/convert-to-repeater.js +226 -0
- package/src/editor/inline-edit.js +69 -28
- package/src/editor/insertion-helper.js +16 -12
- package/src/editor/shortcuts.js +14 -4
- package/src/editor/slash-menu.js +40 -19
- package/src/files/components.js +1 -1
- package/src/files/file-ops.js +37 -16
- package/src/files/files.js +322 -68
- package/src/github/github-auth.js +122 -0
- package/src/github/github-publish.js +126 -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 +27 -25
- package/src/panels/ai-panel.js +406 -0
- package/src/panels/block-action-bar.js +77 -28
- 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 +475 -99
- package/src/panels/head-panel.js +121 -81
- package/src/panels/imports-panel.js +94 -65
- package/src/panels/layers-panel.js +64 -43
- package/src/panels/left-panel.js +105 -48
- 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 +378 -257
- package/src/panels/pseudo-preview.js +2 -2
- package/src/panels/quick-search.js +11 -9
- package/src/panels/right-panel.js +42 -16
- package/src/panels/shared.js +4 -4
- package/src/panels/signals-panel.js +415 -166
- package/src/panels/statusbar.js +10 -6
- package/src/panels/style-inputs.js +67 -35
- package/src/panels/style-panel.js +239 -134
- 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/tab-strip.js +5 -2
- 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 +8 -8
- 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/settings/settings-modal.js +12 -9
- package/src/site-context.js +41 -31
- package/src/state.js +70 -29
- package/src/store.js +36 -35
- package/src/studio.js +120 -90
- 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/layers.js +31 -1
- 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 +32 -26
- package/src/utils/google-fonts.js +11 -11
- package/src/utils/inherited-style.js +9 -8
- package/src/utils/studio-utils.js +18 -18
- package/src/view.js +58 -1
- package/src/workspace/workspace.js +29 -7
|
@@ -12,11 +12,25 @@ import { componentRegistry, computeRelativePath } from "../files/components.js";
|
|
|
12
12
|
import { projectState } from "../store.js";
|
|
13
13
|
import { updateSiteConfig } from "../site-context.js";
|
|
14
14
|
import { getPlatform } from "../platform.js";
|
|
15
|
+
import { showConfirmDialog } from "../ui/layers.js";
|
|
16
|
+
|
|
17
|
+
/** @typedef {import("../files/components.js").ComponentEntry} ComponentEntry */
|
|
18
|
+
|
|
19
|
+
/** @typedef {string | JxMutableNode | { $ref: string }} ElementsEntry */
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* @typedef {{
|
|
23
|
+
* renderLeftPanel: () => void;
|
|
24
|
+
* documentPath: string | null;
|
|
25
|
+
* documentElements: ElementsEntry[];
|
|
26
|
+
* applyMutation: (fn: (doc: JxMutableNode) => void) => void;
|
|
27
|
+
* }} ImportsContext
|
|
28
|
+
*/
|
|
15
29
|
|
|
16
30
|
/**
|
|
17
31
|
* Build the subpath specifier for a component: `<package>/<modulePath>`
|
|
18
32
|
*
|
|
19
|
-
* @param {
|
|
33
|
+
* @param {ComponentEntry} comp
|
|
20
34
|
* @returns {string}
|
|
21
35
|
*/
|
|
22
36
|
function componentSpecifier(comp) {
|
|
@@ -27,8 +41,8 @@ function componentSpecifier(comp) {
|
|
|
27
41
|
* Check if a component is enabled (present in $elements array). Supports both cherry-picked subpath
|
|
28
42
|
* specifiers and legacy full-package imports.
|
|
29
43
|
*
|
|
30
|
-
* @param {
|
|
31
|
-
* @param {
|
|
44
|
+
* @param {ComponentEntry} comp
|
|
45
|
+
* @param {ElementsEntry[]} elements
|
|
32
46
|
* @returns {boolean}
|
|
33
47
|
*/
|
|
34
48
|
function isComponentEnabled(comp, elements) {
|
|
@@ -47,10 +61,10 @@ function isComponentEnabled(comp, elements) {
|
|
|
47
61
|
/**
|
|
48
62
|
* Group npm components by package name.
|
|
49
63
|
*
|
|
50
|
-
* @returns {Map<string,
|
|
64
|
+
* @returns {Map<string, ComponentEntry[]>}
|
|
51
65
|
*/
|
|
52
66
|
function groupByPackage() {
|
|
53
|
-
/** @type {Map<string,
|
|
67
|
+
/** @type {Map<string, ComponentEntry[]>} */
|
|
54
68
|
const groups = new Map();
|
|
55
69
|
for (const comp of componentRegistry) {
|
|
56
70
|
if (comp.source !== "npm" || !comp.package || !comp.modulePath) continue;
|
|
@@ -61,13 +75,8 @@ function groupByPackage() {
|
|
|
61
75
|
}
|
|
62
76
|
|
|
63
77
|
/**
|
|
64
|
-
* @param {
|
|
65
|
-
*
|
|
66
|
-
* documentPath: string | null;
|
|
67
|
-
* documentElements: any[];
|
|
68
|
-
* applyMutation: (fn: (doc: any) => void) => void;
|
|
69
|
-
* }} ctx
|
|
70
|
-
* @returns {any}
|
|
78
|
+
* @param {ImportsContext} ctx
|
|
79
|
+
* @returns {import("lit-html").TemplateResult}
|
|
71
80
|
*/
|
|
72
81
|
export function renderImportsTemplate({
|
|
73
82
|
renderLeftPanel,
|
|
@@ -95,7 +104,9 @@ export function renderImportsTemplate({
|
|
|
95
104
|
function renderSiteLevelImports(renderLeftPanel) {
|
|
96
105
|
const siteImports = projectState?.projectConfig?.imports || {};
|
|
97
106
|
const entries = Object.entries(siteImports);
|
|
98
|
-
const siteElements =
|
|
107
|
+
const siteElements = /** @type {ElementsEntry[]} */ (
|
|
108
|
+
projectState?.projectConfig?.$elements || []
|
|
109
|
+
);
|
|
99
110
|
|
|
100
111
|
const packageGroups = groupByPackage();
|
|
101
112
|
|
|
@@ -141,10 +152,14 @@ function renderSiteLevelImports(renderLeftPanel) {
|
|
|
141
152
|
quiet
|
|
142
153
|
size="xs"
|
|
143
154
|
title="Add import"
|
|
144
|
-
@click=${async (/** @type {
|
|
145
|
-
const form = e.target.closest(".import-add-form");
|
|
146
|
-
const nameField =
|
|
147
|
-
|
|
155
|
+
@click=${async (/** @type {Event} */ e) => {
|
|
156
|
+
const form = /** @type {HTMLElement} */ (e.target).closest(".import-add-form");
|
|
157
|
+
const nameField = /** @type {HTMLInputElement} */ (
|
|
158
|
+
form?.querySelector(".import-add-name")
|
|
159
|
+
);
|
|
160
|
+
const pathField = /** @type {HTMLInputElement} */ (
|
|
161
|
+
form?.querySelector(".import-add-path")
|
|
162
|
+
);
|
|
148
163
|
const name = nameField?.value?.trim();
|
|
149
164
|
const path = pathField?.value?.trim();
|
|
150
165
|
if (!name || !path) return;
|
|
@@ -171,19 +186,26 @@ function renderSiteLevelImports(renderLeftPanel) {
|
|
|
171
186
|
size="xs"
|
|
172
187
|
title="Remove package"
|
|
173
188
|
@click=${async () => {
|
|
174
|
-
|
|
189
|
+
const confirmed = await showConfirmDialog("Remove Package", `Remove ${pkg}?`, {
|
|
190
|
+
confirmLabel: "Remove",
|
|
191
|
+
destructive: true,
|
|
192
|
+
});
|
|
193
|
+
if (!confirmed) return;
|
|
175
194
|
try {
|
|
176
195
|
const platform = getPlatform();
|
|
177
196
|
await platform.removePackage(pkg);
|
|
178
197
|
// Also remove all cherry-picked elements for this package
|
|
179
198
|
const updatedElements = siteElements.filter(
|
|
180
|
-
(/** @type {
|
|
199
|
+
(/** @type {ElementsEntry} */ e) =>
|
|
200
|
+
typeof e !== "string" || !e.startsWith(pkg + "/"),
|
|
181
201
|
);
|
|
182
202
|
const { loadComponentRegistry } = await import("../files/components.js");
|
|
183
203
|
await loadComponentRegistry();
|
|
184
|
-
await updateSiteConfig({
|
|
204
|
+
await updateSiteConfig({
|
|
205
|
+
$elements: /** @type {(string | JxElement)[]} */ (updatedElements),
|
|
206
|
+
});
|
|
185
207
|
renderLeftPanel();
|
|
186
|
-
} catch (/** @type {
|
|
208
|
+
} catch (/** @type {unknown} */ e) {
|
|
187
209
|
console.error("Failed to remove package:", e);
|
|
188
210
|
}
|
|
189
211
|
}}
|
|
@@ -192,7 +214,7 @@ function renderSiteLevelImports(renderLeftPanel) {
|
|
|
192
214
|
</sp-action-button>
|
|
193
215
|
</div>
|
|
194
216
|
<div class="imports-list imports-component-list">
|
|
195
|
-
${comps.map((/** @type {
|
|
217
|
+
${comps.map((/** @type {ComponentEntry} */ comp) => {
|
|
196
218
|
const enabled = isComponentEnabled(comp, siteElements);
|
|
197
219
|
const specifier = componentSpecifier(comp);
|
|
198
220
|
return html`
|
|
@@ -200,16 +222,20 @@ function renderSiteLevelImports(renderLeftPanel) {
|
|
|
200
222
|
<sp-checkbox
|
|
201
223
|
size="s"
|
|
202
224
|
.checked=${enabled}
|
|
203
|
-
@change=${async (/** @type {
|
|
225
|
+
@change=${async (/** @type {Event} */ e) => {
|
|
204
226
|
let updated = [...siteElements];
|
|
205
227
|
// Remove legacy full-package import if present
|
|
206
|
-
updated = updated.filter((/** @type {
|
|
207
|
-
if (e.target.checked) {
|
|
228
|
+
updated = updated.filter((/** @type {ElementsEntry} */ el) => el !== pkg);
|
|
229
|
+
if (/** @type {HTMLInputElement} */ (e.target).checked) {
|
|
208
230
|
if (!updated.includes(specifier)) updated.push(specifier);
|
|
209
231
|
} else {
|
|
210
|
-
updated = updated.filter(
|
|
232
|
+
updated = updated.filter(
|
|
233
|
+
(/** @type {ElementsEntry} */ el) => el !== specifier,
|
|
234
|
+
);
|
|
211
235
|
}
|
|
212
|
-
await updateSiteConfig({
|
|
236
|
+
await updateSiteConfig({
|
|
237
|
+
$elements: /** @type {(string | JxElement)[]} */ (updated),
|
|
238
|
+
});
|
|
213
239
|
renderLeftPanel();
|
|
214
240
|
}}
|
|
215
241
|
>
|
|
@@ -233,18 +259,18 @@ function renderSiteLevelImports(renderLeftPanel) {
|
|
|
233
259
|
placeholder="Package name…"
|
|
234
260
|
size="s"
|
|
235
261
|
style="flex:1"
|
|
236
|
-
@keydown=${async (/** @type {
|
|
262
|
+
@keydown=${async (/** @type {KeyboardEvent} */ e) => {
|
|
237
263
|
if (e.key !== "Enter") return;
|
|
238
|
-
const name = e.target.value?.trim();
|
|
264
|
+
const name = /** @type {HTMLInputElement} */ (e.target).value?.trim();
|
|
239
265
|
if (!name) return;
|
|
240
|
-
e.target.value = "";
|
|
266
|
+
/** @type {HTMLInputElement} */ (e.target).value = "";
|
|
241
267
|
try {
|
|
242
268
|
const platform = getPlatform();
|
|
243
269
|
await platform.addPackage(name);
|
|
244
270
|
const { loadComponentRegistry } = await import("../files/components.js");
|
|
245
271
|
await loadComponentRegistry();
|
|
246
272
|
renderLeftPanel();
|
|
247
|
-
} catch (/** @type {
|
|
273
|
+
} catch (/** @type {unknown} */ err) {
|
|
248
274
|
console.error("Failed to add package:", err);
|
|
249
275
|
}
|
|
250
276
|
}}
|
|
@@ -253,18 +279,20 @@ function renderSiteLevelImports(renderLeftPanel) {
|
|
|
253
279
|
quiet
|
|
254
280
|
size="xs"
|
|
255
281
|
title="Add package"
|
|
256
|
-
@click=${async (/** @type {
|
|
257
|
-
const input = e.target
|
|
258
|
-
|
|
282
|
+
@click=${async (/** @type {Event} */ e) => {
|
|
283
|
+
const input = /** @type {HTMLElement} */ (e.target)
|
|
284
|
+
.closest(".import-add-form")
|
|
285
|
+
?.querySelector("sp-textfield");
|
|
286
|
+
const name = /** @type {HTMLInputElement | null} */ (input)?.value?.trim();
|
|
259
287
|
if (!name) return;
|
|
260
|
-
input.value = "";
|
|
288
|
+
/** @type {HTMLInputElement} */ (input).value = "";
|
|
261
289
|
try {
|
|
262
290
|
const platform = getPlatform();
|
|
263
291
|
await platform.addPackage(name);
|
|
264
292
|
const { loadComponentRegistry } = await import("../files/components.js");
|
|
265
293
|
await loadComponentRegistry();
|
|
266
294
|
renderLeftPanel();
|
|
267
|
-
} catch (/** @type {
|
|
295
|
+
} catch (/** @type {unknown} */ err) {
|
|
268
296
|
console.error("Failed to add package:", err);
|
|
269
297
|
}
|
|
270
298
|
}}
|
|
@@ -279,14 +307,7 @@ function renderSiteLevelImports(renderLeftPanel) {
|
|
|
279
307
|
|
|
280
308
|
// ─── Document-level: Component Imports + npm Component Cherry-pick ───────────
|
|
281
309
|
|
|
282
|
-
/**
|
|
283
|
-
* @param {{
|
|
284
|
-
* renderLeftPanel: () => void;
|
|
285
|
-
* documentPath: string | null;
|
|
286
|
-
* documentElements: any[];
|
|
287
|
-
* applyMutation: (fn: (doc: any) => void) => void;
|
|
288
|
-
* }} ctx
|
|
289
|
-
*/
|
|
310
|
+
/** @param {ImportsContext} ctx */
|
|
290
311
|
function renderDocumentLevelImports({
|
|
291
312
|
renderLeftPanel,
|
|
292
313
|
documentPath,
|
|
@@ -294,14 +315,18 @@ function renderDocumentLevelImports({
|
|
|
294
315
|
applyMutation,
|
|
295
316
|
}) {
|
|
296
317
|
const refEntries = documentElements.filter(
|
|
297
|
-
(/** @type {
|
|
318
|
+
(/** @type {ElementsEntry} */ e) => e && typeof e === "object" && e.$ref,
|
|
319
|
+
);
|
|
320
|
+
const npmEntries = documentElements.filter(
|
|
321
|
+
(/** @type {ElementsEntry} */ e) => typeof e === "string",
|
|
298
322
|
);
|
|
299
|
-
const npmEntries = documentElements.filter((/** @type {any} */ e) => typeof e === "string");
|
|
300
323
|
|
|
301
324
|
// Available JX components not yet imported
|
|
302
|
-
const importedRefs = new Set(
|
|
325
|
+
const importedRefs = new Set(
|
|
326
|
+
refEntries.map((/** @type {ElementsEntry} */ e) => /** @type {{ $ref: string }} */ (e).$ref),
|
|
327
|
+
);
|
|
303
328
|
const availableComponents = componentRegistry.filter(
|
|
304
|
-
(/** @type {
|
|
329
|
+
(/** @type {ComponentEntry} */ c) =>
|
|
305
330
|
c.source !== "npm" && !importedRefs.has(`./${c.path}`) && !importedRefs.has(c.path),
|
|
306
331
|
);
|
|
307
332
|
|
|
@@ -309,9 +334,9 @@ function renderDocumentLevelImports({
|
|
|
309
334
|
|
|
310
335
|
/** @param {string} ref */
|
|
311
336
|
const removeRef = (ref) => {
|
|
312
|
-
applyMutation((/** @type {
|
|
337
|
+
applyMutation((/** @type {JxMutableNode} */ doc) => {
|
|
313
338
|
doc.$elements = (doc.$elements || []).filter(
|
|
314
|
-
(/** @type {
|
|
339
|
+
(/** @type {ElementsEntry} */ e) => !(e && typeof e === "object" && e.$ref === ref),
|
|
315
340
|
);
|
|
316
341
|
});
|
|
317
342
|
renderLeftPanel();
|
|
@@ -329,14 +354,18 @@ function renderDocumentLevelImports({
|
|
|
329
354
|
? html`
|
|
330
355
|
<div class="imports-list">
|
|
331
356
|
${refEntries.map(
|
|
332
|
-
(/** @type {
|
|
357
|
+
(/** @type {ElementsEntry} */ entry) => html`
|
|
333
358
|
<div class="import-row">
|
|
334
|
-
<span
|
|
359
|
+
<span
|
|
360
|
+
class="import-path"
|
|
361
|
+
title=${/** @type {{ $ref: string }} */ (entry).$ref}
|
|
362
|
+
>${/** @type {{ $ref: string }} */ (entry).$ref}</span
|
|
363
|
+
>
|
|
335
364
|
<sp-action-button
|
|
336
365
|
quiet
|
|
337
366
|
size="xs"
|
|
338
367
|
title="Remove"
|
|
339
|
-
@click=${() => removeRef(entry.$ref)}
|
|
368
|
+
@click=${() => removeRef(/** @type {{ $ref: string }} */ (entry).$ref)}
|
|
340
369
|
>
|
|
341
370
|
<sp-icon-close slot="icon" size="xs"></sp-icon-close>
|
|
342
371
|
</sp-action-button>
|
|
@@ -353,16 +382,16 @@ function renderDocumentLevelImports({
|
|
|
353
382
|
size="s"
|
|
354
383
|
label="Add component…"
|
|
355
384
|
class="import-picker"
|
|
356
|
-
@change=${(/** @type {
|
|
357
|
-
const tag = e.target.value;
|
|
385
|
+
@change=${(/** @type {Event} */ e) => {
|
|
386
|
+
const tag = /** @type {HTMLInputElement} */ (e.target).value;
|
|
358
387
|
if (!tag) return;
|
|
359
|
-
e.target.value = "";
|
|
388
|
+
/** @type {HTMLInputElement} */ (e.target).value = "";
|
|
360
389
|
const comp = componentRegistry.find(
|
|
361
|
-
(/** @type {
|
|
390
|
+
(/** @type {ComponentEntry} */ c) => c.tagName === tag,
|
|
362
391
|
);
|
|
363
392
|
if (!comp) return;
|
|
364
393
|
const relPath = computeRelativePath(documentPath, comp.path);
|
|
365
|
-
applyMutation((/** @type {
|
|
394
|
+
applyMutation((/** @type {JxMutableNode} */ doc) => {
|
|
366
395
|
if (!doc.$elements) doc.$elements = [];
|
|
367
396
|
doc.$elements.push({ $ref: relPath });
|
|
368
397
|
});
|
|
@@ -370,7 +399,7 @@ function renderDocumentLevelImports({
|
|
|
370
399
|
}}
|
|
371
400
|
>
|
|
372
401
|
${availableComponents.map(
|
|
373
|
-
(/** @type {
|
|
402
|
+
(/** @type {ComponentEntry} */ c) =>
|
|
374
403
|
html`<sp-menu-item value=${c.tagName}><${c.tagName}></sp-menu-item>`,
|
|
375
404
|
)}
|
|
376
405
|
</sp-picker>
|
|
@@ -387,7 +416,7 @@ function renderDocumentLevelImports({
|
|
|
387
416
|
<span class="imports-section-title import-mono">${pkg}</span>
|
|
388
417
|
</div>
|
|
389
418
|
<div class="imports-list imports-component-list">
|
|
390
|
-
${comps.map((/** @type {
|
|
419
|
+
${comps.map((/** @type {ComponentEntry} */ comp) => {
|
|
391
420
|
const enabled = isComponentEnabled(comp, npmEntries);
|
|
392
421
|
const specifier = componentSpecifier(comp);
|
|
393
422
|
return html`
|
|
@@ -395,18 +424,18 @@ function renderDocumentLevelImports({
|
|
|
395
424
|
<sp-checkbox
|
|
396
425
|
size="s"
|
|
397
426
|
.checked=${enabled}
|
|
398
|
-
@change=${(/** @type {
|
|
399
|
-
applyMutation((/** @type {
|
|
427
|
+
@change=${(/** @type {Event} */ e) => {
|
|
428
|
+
applyMutation((/** @type {JxMutableNode} */ doc) => {
|
|
400
429
|
if (!doc.$elements) doc.$elements = [];
|
|
401
430
|
// Remove legacy full-package import if present
|
|
402
431
|
doc.$elements = doc.$elements.filter(
|
|
403
|
-
(/** @type {
|
|
432
|
+
(/** @type {ElementsEntry} */ el) => el !== pkg,
|
|
404
433
|
);
|
|
405
|
-
if (e.target.checked) {
|
|
434
|
+
if (/** @type {HTMLInputElement} */ (e.target).checked) {
|
|
406
435
|
if (!doc.$elements.includes(specifier)) doc.$elements.push(specifier);
|
|
407
436
|
} else {
|
|
408
437
|
doc.$elements = doc.$elements.filter(
|
|
409
|
-
(/** @type {
|
|
438
|
+
(/** @type {ElementsEntry} */ el) => el !== specifier,
|
|
410
439
|
);
|
|
411
440
|
}
|
|
412
441
|
});
|
|
@@ -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);
|