@jxsuite/studio 0.28.5 → 0.30.1
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 +9368 -6586
- package/dist/studio.js.map +109 -83
- 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 +11 -10
- package/src/browse/browse-modal.ts +2 -2
- package/src/browse/browse.ts +20 -19
- package/src/canvas/canvas-diff.ts +3 -3
- package/src/canvas/canvas-helpers.ts +15 -2
- package/src/canvas/canvas-live-render.ts +168 -86
- package/src/canvas/canvas-patcher.ts +656 -0
- package/src/canvas/canvas-perf.ts +68 -0
- package/src/canvas/canvas-render.ts +135 -23
- package/src/canvas/canvas-subtree-render.ts +113 -0
- package/src/canvas/canvas-utils.ts +4 -0
- package/src/editor/component-inline-edit.ts +4 -35
- package/src/editor/context-menu.ts +96 -76
- package/src/editor/convert-to-component.ts +11 -2
- package/src/editor/convert-to-repeater.ts +4 -5
- package/src/editor/inline-edit.ts +9 -9
- package/src/editor/inline-format.ts +11 -11
- package/src/editor/merge-tags.ts +120 -0
- package/src/editor/shortcuts.ts +4 -4
- package/src/files/components.ts +37 -0
- package/src/files/file-ops.ts +28 -7
- package/src/files/files.ts +68 -24
- package/src/files/fs-events.ts +162 -0
- package/src/github/github-auth.ts +14 -2
- package/src/github/github-publish.ts +12 -2
- package/src/panels/activity-bar.ts +1 -1
- package/src/panels/ai-panel.ts +67 -39
- package/src/panels/block-action-bar.ts +72 -1
- package/src/panels/canvas-dnd.ts +55 -26
- package/src/panels/data-explorer.ts +5 -3
- package/src/panels/dnd.ts +14 -17
- package/src/panels/editors.ts +5 -23
- package/src/panels/elements-panel.ts +2 -12
- package/src/panels/events-panel.ts +1 -1
- package/src/panels/git-panel.ts +6 -6
- package/src/panels/head-panel.ts +1 -1
- package/src/panels/layers-panel.ts +177 -147
- package/src/panels/preview-render.ts +15 -0
- package/src/panels/properties-panel.ts +16 -27
- package/src/panels/quick-search.ts +2 -2
- package/src/panels/right-panel.ts +2 -6
- package/src/panels/shared.ts +3 -0
- package/src/panels/signals-panel.ts +43 -33
- package/src/panels/statusbar.ts +15 -6
- package/src/panels/style-panel.ts +3 -3
- package/src/panels/style-utils.ts +3 -3
- package/src/panels/stylebook-panel.ts +4 -4
- package/src/panels/tab-bar.ts +198 -0
- package/src/panels/tab-strip.ts +2 -2
- package/src/panels/toolbar.ts +6 -75
- package/src/platforms/devserver.ts +113 -35
- package/src/reactivity.ts +2 -0
- package/src/services/cem-export.ts +23 -2
- package/src/services/code-services.ts +16 -2
- package/src/services/monaco-setup.ts +2 -1
- package/src/settings/content-types-editor.ts +16 -16
- package/src/settings/css-vars-editor.ts +2 -2
- package/src/settings/defs-editor.ts +14 -14
- package/src/settings/general-settings.ts +6 -6
- package/src/settings/head-editor.ts +2 -2
- package/src/site-context.ts +1 -1
- package/src/state.ts +66 -12
- package/src/store.ts +15 -3
- package/src/studio.ts +88 -35
- package/src/tabs/patch-ops.ts +143 -0
- package/src/tabs/tab.ts +15 -1
- package/src/tabs/transact.ts +454 -27
- package/src/types.ts +71 -1
- package/src/ui/color-selector.ts +7 -7
- package/src/ui/icons.ts +0 -30
- package/src/ui/media-picker.ts +2 -2
- package/src/ui/panel-resize.ts +14 -8
- package/src/ui/unit-selector.ts +5 -2
- package/src/ui/value-selector.ts +3 -3
- package/src/utils/canvas-media.ts +6 -6
- package/src/utils/edit-display.ts +125 -83
- package/src/utils/google-fonts.ts +1 -1
- package/src/utils/inherited-style.ts +3 -2
- package/src/utils/studio-utils.ts +1 -1
- package/src/view.ts +4 -1
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
import { html, nothing } from "lit-html";
|
|
8
8
|
import { classMap } from "lit-html/directives/class-map.js";
|
|
9
9
|
import { ifDefined } from "lit-html/directives/if-defined.js";
|
|
10
|
+
import { repeat } from "lit-html/directives/repeat.js";
|
|
10
11
|
import {
|
|
11
12
|
VOID_ELEMENTS,
|
|
12
13
|
childIndex,
|
|
@@ -123,7 +124,7 @@ export function renderLayersTemplate(ctx: {
|
|
|
123
124
|
const rows = flattenTree(tab!.doc.document);
|
|
124
125
|
const collapsed = (view._layersCollapsed ||= new Set());
|
|
125
126
|
|
|
126
|
-
const layerRows: TemplateResult[] = [];
|
|
127
|
+
const layerRows: { key: string; tpl: TemplateResult }[] = [];
|
|
127
128
|
for (const { node, path, depth, nodeType } of rows) {
|
|
128
129
|
let hidden = false;
|
|
129
130
|
for (let d = 1; d <= path.length; d++) {
|
|
@@ -143,15 +144,18 @@ export function renderLayersTemplate(ctx: {
|
|
|
143
144
|
|
|
144
145
|
if (nodeType === "text") {
|
|
145
146
|
const textPreview = String(node).length > 40 ? `${String(node).slice(0, 40)}…` : String(node);
|
|
146
|
-
layerRows.push(
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
147
|
+
layerRows.push({
|
|
148
|
+
key: pathKey(path),
|
|
149
|
+
tpl: html`
|
|
150
|
+
<div
|
|
151
|
+
class="layer-row"
|
|
152
|
+
style="padding-left:${depth * 16 + 8}px; opacity: 0.6; font-style: italic;"
|
|
153
|
+
>
|
|
154
|
+
<span class="layer-tag" style="background: #64748b; font-size: 0.65rem;">text</span>
|
|
155
|
+
<span class="layer-label">${textPreview}</span>
|
|
156
|
+
</div>
|
|
157
|
+
`,
|
|
158
|
+
});
|
|
155
159
|
continue;
|
|
156
160
|
}
|
|
157
161
|
|
|
@@ -183,7 +187,10 @@ export function renderLayersTemplate(ctx: {
|
|
|
183
187
|
Object.keys(jxNode.cases).length > 0;
|
|
184
188
|
const isExpandable =
|
|
185
189
|
hasChildren || hasMapChildren || hasCases || (nodeType === "map" && jxNode.map);
|
|
186
|
-
|
|
190
|
+
// Array nodes can't accept dropped children (their content is the single map template), so they
|
|
191
|
+
// Block the make-child drop instruction like void elements do.
|
|
192
|
+
const isVoidEl =
|
|
193
|
+
VOID_ELEMENTS.has((jxNode.tagName || "div").toLowerCase()) || nodeType === "map";
|
|
187
194
|
|
|
188
195
|
/** @type {string} */
|
|
189
196
|
let badgeClass;
|
|
@@ -203,6 +210,14 @@ export function renderLayersTemplate(ctx: {
|
|
|
203
210
|
badgeClass = "layer-tag switch-tag";
|
|
204
211
|
badgeText = "⇄";
|
|
205
212
|
badgeTitle = "$switch";
|
|
213
|
+
} else if (jxNode.tagName === "slot") {
|
|
214
|
+
const slotName = jxNode.attributes?.name;
|
|
215
|
+
badgeClass = "layer-tag slot-tag";
|
|
216
|
+
badgeText = "▣";
|
|
217
|
+
badgeTitle =
|
|
218
|
+
typeof slotName === "string" && slotName.trim()
|
|
219
|
+
? `Slot "${slotName.trim()}"`
|
|
220
|
+
: "Default slot";
|
|
206
221
|
} else {
|
|
207
222
|
badgeClass = "layer-tag";
|
|
208
223
|
badgeText = jxNode.tagName || "div";
|
|
@@ -221,14 +236,18 @@ export function renderLayersTemplate(ctx: {
|
|
|
221
236
|
labelItalic = false;
|
|
222
237
|
}
|
|
223
238
|
|
|
224
|
-
|
|
239
|
+
// Array (repeater) nodes are first-class structural nodes — movable/draggable/deletable like
|
|
240
|
+
// Elements. Both sit at a numeric child index; templates (path tail "map") and case nodes do
|
|
241
|
+
// Not, so they stay selectable/editable but not structurally manipulable.
|
|
242
|
+
const isStructural =
|
|
243
|
+
(nodeType === "element" || nodeType === "map") && typeof childIndex(path) === "number";
|
|
225
244
|
const isRoot = tab?.doc.mode === "content" ? path.length === 0 : path.length < 2;
|
|
226
|
-
const idx =
|
|
227
|
-
const parentPath =
|
|
245
|
+
const idx = isStructural ? (childIndex(path) as number) : 0;
|
|
246
|
+
const parentPath = isStructural && !isRoot ? (parentElementPath(path) as JxPath) : null;
|
|
228
247
|
const parentNode = parentPath ? getNodeAtPath(tab!.doc.document, parentPath) : null;
|
|
229
248
|
const siblingCount = childList(parentNode).length;
|
|
230
|
-
const canMoveUp =
|
|
231
|
-
const canMoveDown =
|
|
249
|
+
const canMoveUp = isStructural && !isRoot && idx > 0;
|
|
250
|
+
const canMoveDown = isStructural && !isRoot && idx < siblingCount - 1;
|
|
232
251
|
const prevSibling = canMoveUp && parentNode ? childList(parentNode)[idx - 1] : null;
|
|
233
252
|
const prevIsContainer = (() => {
|
|
234
253
|
if (!prevSibling || typeof prevSibling !== "object") {
|
|
@@ -257,142 +276,149 @@ export function renderLayersTemplate(ctx: {
|
|
|
257
276
|
(c) => typeof c === "object" && c !== null && !isInlineElement(c, prevSibling),
|
|
258
277
|
);
|
|
259
278
|
})();
|
|
260
|
-
const canMoveIn =
|
|
279
|
+
const canMoveIn = isStructural && !isRoot && prevIsContainer;
|
|
261
280
|
const grandparentPath =
|
|
262
|
-
|
|
281
|
+
isStructural && parentPath && parentPath.length >= 2
|
|
263
282
|
? (parentElementPath(parentPath) as JxPath)
|
|
264
283
|
: null;
|
|
265
|
-
const canMoveOut =
|
|
284
|
+
const canMoveOut = isStructural && !isRoot && Boolean(grandparentPath);
|
|
266
285
|
|
|
267
|
-
layerRows.push(
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
286
|
+
layerRows.push({
|
|
287
|
+
key,
|
|
288
|
+
tpl: html`
|
|
289
|
+
<div
|
|
290
|
+
class=${classMap({ "layer-row": true, selected: isSelected })}
|
|
291
|
+
data-path=${key}
|
|
292
|
+
data-dnd-row=${isStructural ? key : nothing}
|
|
293
|
+
data-dnd-depth=${isStructural ? depth : nothing}
|
|
294
|
+
data-dnd-void=${isStructural && isVoidEl ? "" : nothing}
|
|
295
|
+
data-dnd-expanded=${isStructural && isExpandable && !collapsed.has(key) ? "" : nothing}
|
|
296
|
+
@click=${() => {
|
|
297
|
+
activeTab.value!.session.selection = path;
|
|
298
|
+
panToElement(path);
|
|
299
|
+
}}
|
|
300
|
+
@dblclick=${isStructural
|
|
301
|
+
? (e: MouseEvent) => {
|
|
302
|
+
e.stopPropagation();
|
|
303
|
+
startLayerTitleEdit(path, ctx.rerender);
|
|
304
|
+
}
|
|
305
|
+
: nothing}
|
|
306
|
+
@contextmenu=${isStructural
|
|
307
|
+
? (e: MouseEvent) =>
|
|
308
|
+
showContextMenu(e, path, {
|
|
309
|
+
onEditComponent: ctx.navigateToComponent,
|
|
310
|
+
rerender: ctx.rerender,
|
|
311
|
+
})
|
|
312
|
+
: nothing}
|
|
313
|
+
>
|
|
314
|
+
<span class="layer-indent" style="width:${depth * 16}px"></span>
|
|
315
|
+
<span class="layer-toggle"
|
|
316
|
+
>${isExpandable
|
|
317
|
+
? html`
|
|
318
|
+
${collapsed.has(key)
|
|
319
|
+
? html`<sp-icon-chevron-right></sp-icon-chevron-right>`
|
|
320
|
+
: html`<sp-icon-chevron-down></sp-icon-chevron-down>`}
|
|
321
|
+
`
|
|
322
|
+
: nothing}</span
|
|
323
|
+
>
|
|
324
|
+
<span class=${badgeClass} title=${ifDefined(badgeTitle ?? undefined)}>${badgeText}</span>
|
|
325
|
+
<span class="layer-label" style=${labelItalic ? "font-style:italic" : nothing}
|
|
326
|
+
>${labelText}</span
|
|
327
|
+
>
|
|
328
|
+
${isStructural && !isRoot
|
|
296
329
|
? html`
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
330
|
+
<span class="layer-actions">
|
|
331
|
+
<span class="layer-drag-handle" title="Drag to reorder">⠿</span>
|
|
332
|
+
${canMoveUp
|
|
333
|
+
? html`<sp-action-button
|
|
334
|
+
quiet
|
|
335
|
+
size="xs"
|
|
336
|
+
title="Move up"
|
|
337
|
+
@click=${(e: MouseEvent) => {
|
|
338
|
+
e.stopPropagation();
|
|
339
|
+
(e.currentTarget as HTMLElement).blur();
|
|
340
|
+
const pp = parentPath as JxPath;
|
|
341
|
+
transactDoc(activeTab.value!, (t) =>
|
|
342
|
+
mutateMoveNode(t, path, pp, idx - 1),
|
|
343
|
+
);
|
|
344
|
+
}}
|
|
345
|
+
>
|
|
346
|
+
<sp-icon-arrow-up slot="icon"></sp-icon-arrow-up>
|
|
347
|
+
</sp-action-button>`
|
|
348
|
+
: nothing}
|
|
349
|
+
${canMoveDown
|
|
350
|
+
? html`<sp-action-button
|
|
351
|
+
quiet
|
|
352
|
+
size="xs"
|
|
353
|
+
title="Move down"
|
|
354
|
+
@click=${(e: MouseEvent) => {
|
|
355
|
+
e.stopPropagation();
|
|
356
|
+
(e.currentTarget as HTMLElement).blur();
|
|
357
|
+
const pp = parentPath as JxPath;
|
|
358
|
+
transactDoc(activeTab.value!, (t) =>
|
|
359
|
+
mutateMoveNode(t, path, pp, idx + 2),
|
|
360
|
+
);
|
|
361
|
+
}}
|
|
362
|
+
>
|
|
363
|
+
<sp-icon-arrow-down slot="icon"></sp-icon-arrow-down>
|
|
364
|
+
</sp-action-button>`
|
|
365
|
+
: nothing}
|
|
366
|
+
${canMoveIn
|
|
367
|
+
? html`<sp-action-button
|
|
368
|
+
quiet
|
|
369
|
+
size="xs"
|
|
370
|
+
title="Move into previous sibling"
|
|
371
|
+
@click=${(e: MouseEvent) => {
|
|
372
|
+
e.stopPropagation();
|
|
373
|
+
(e.currentTarget as HTMLElement).blur();
|
|
374
|
+
const pp = parentPath as JxPath;
|
|
375
|
+
const prevPath = [...pp, "children", idx - 1];
|
|
376
|
+
const prev = getNodeAtPath(activeTab.value!.doc.document, prevPath);
|
|
377
|
+
const len = childList(prev).length;
|
|
378
|
+
transactDoc(activeTab.value!, (t) =>
|
|
379
|
+
mutateMoveNode(t, path, prevPath, len),
|
|
380
|
+
);
|
|
381
|
+
}}
|
|
382
|
+
>
|
|
383
|
+
<sp-icon-arrow-right slot="icon"></sp-icon-arrow-right>
|
|
384
|
+
</sp-action-button>`
|
|
385
|
+
: nothing}
|
|
386
|
+
${canMoveOut
|
|
387
|
+
? html`<sp-action-button
|
|
388
|
+
quiet
|
|
389
|
+
size="xs"
|
|
390
|
+
title="Move out of parent"
|
|
391
|
+
@click=${(e: MouseEvent) => {
|
|
392
|
+
e.stopPropagation();
|
|
393
|
+
(e.currentTarget as HTMLElement).blur();
|
|
394
|
+
const gp = grandparentPath as JxPath;
|
|
395
|
+
const parentIdx = childIndex(parentPath!) as number;
|
|
396
|
+
transactDoc(activeTab.value!, (t) =>
|
|
397
|
+
mutateMoveNode(t, path, gp, parentIdx + 1),
|
|
398
|
+
);
|
|
399
|
+
}}
|
|
400
|
+
>
|
|
401
|
+
<sp-icon-arrow-left slot="icon"></sp-icon-arrow-left>
|
|
402
|
+
</sp-action-button>`
|
|
403
|
+
: nothing}
|
|
404
|
+
<sp-action-button
|
|
405
|
+
quiet
|
|
406
|
+
size="xs"
|
|
407
|
+
class="layer-delete"
|
|
408
|
+
title="Delete"
|
|
409
|
+
@click=${(e: MouseEvent) => {
|
|
410
|
+
e.stopPropagation();
|
|
411
|
+
transactDoc(activeTab.value!, (t) => mutateRemoveNode(t, path));
|
|
412
|
+
}}
|
|
413
|
+
>
|
|
414
|
+
<sp-icon-close slot="icon"></sp-icon-close>
|
|
415
|
+
</sp-action-button>
|
|
416
|
+
</span>
|
|
300
417
|
`
|
|
301
|
-
: nothing}
|
|
302
|
-
>
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
>${labelText}</span
|
|
306
|
-
>
|
|
307
|
-
${isElement && !isRoot
|
|
308
|
-
? html`
|
|
309
|
-
<span class="layer-actions">
|
|
310
|
-
<span class="layer-drag-handle" title="Drag to reorder">⠿</span>
|
|
311
|
-
${canMoveUp
|
|
312
|
-
? html`<sp-action-button
|
|
313
|
-
quiet
|
|
314
|
-
size="xs"
|
|
315
|
-
title="Move up"
|
|
316
|
-
@click=${(e: MouseEvent) => {
|
|
317
|
-
e.stopPropagation();
|
|
318
|
-
(e.currentTarget as HTMLElement).blur();
|
|
319
|
-
const pp = parentPath as JxPath;
|
|
320
|
-
transactDoc(activeTab.value!, (t) => mutateMoveNode(t, path, pp, idx - 1));
|
|
321
|
-
}}
|
|
322
|
-
>
|
|
323
|
-
<sp-icon-arrow-up slot="icon"></sp-icon-arrow-up>
|
|
324
|
-
</sp-action-button>`
|
|
325
|
-
: nothing}
|
|
326
|
-
${canMoveDown
|
|
327
|
-
? html`<sp-action-button
|
|
328
|
-
quiet
|
|
329
|
-
size="xs"
|
|
330
|
-
title="Move down"
|
|
331
|
-
@click=${(e: MouseEvent) => {
|
|
332
|
-
e.stopPropagation();
|
|
333
|
-
(e.currentTarget as HTMLElement).blur();
|
|
334
|
-
const pp = parentPath as JxPath;
|
|
335
|
-
transactDoc(activeTab.value!, (t) => mutateMoveNode(t, path, pp, idx + 2));
|
|
336
|
-
}}
|
|
337
|
-
>
|
|
338
|
-
<sp-icon-arrow-down slot="icon"></sp-icon-arrow-down>
|
|
339
|
-
</sp-action-button>`
|
|
340
|
-
: nothing}
|
|
341
|
-
${canMoveIn
|
|
342
|
-
? html`<sp-action-button
|
|
343
|
-
quiet
|
|
344
|
-
size="xs"
|
|
345
|
-
title="Move into previous sibling"
|
|
346
|
-
@click=${(e: MouseEvent) => {
|
|
347
|
-
e.stopPropagation();
|
|
348
|
-
(e.currentTarget as HTMLElement).blur();
|
|
349
|
-
const pp = parentPath as JxPath;
|
|
350
|
-
const prevPath = [...pp, idx - 1];
|
|
351
|
-
const prev = getNodeAtPath(activeTab.value!.doc.document, prevPath);
|
|
352
|
-
const len = childList(prev).length;
|
|
353
|
-
transactDoc(activeTab.value!, (t) =>
|
|
354
|
-
mutateMoveNode(t, path, prevPath, len),
|
|
355
|
-
);
|
|
356
|
-
}}
|
|
357
|
-
>
|
|
358
|
-
<sp-icon-arrow-right slot="icon"></sp-icon-arrow-right>
|
|
359
|
-
</sp-action-button>`
|
|
360
|
-
: nothing}
|
|
361
|
-
${canMoveOut
|
|
362
|
-
? html`<sp-action-button
|
|
363
|
-
quiet
|
|
364
|
-
size="xs"
|
|
365
|
-
title="Move out of parent"
|
|
366
|
-
@click=${(e: MouseEvent) => {
|
|
367
|
-
e.stopPropagation();
|
|
368
|
-
(e.currentTarget as HTMLElement).blur();
|
|
369
|
-
const gp = grandparentPath as JxPath;
|
|
370
|
-
const parentIdx = childIndex(parentPath!) as number;
|
|
371
|
-
transactDoc(activeTab.value!, (t) =>
|
|
372
|
-
mutateMoveNode(t, path, gp, parentIdx + 1),
|
|
373
|
-
);
|
|
374
|
-
}}
|
|
375
|
-
>
|
|
376
|
-
<sp-icon-arrow-left slot="icon"></sp-icon-arrow-left>
|
|
377
|
-
</sp-action-button>`
|
|
378
|
-
: nothing}
|
|
379
|
-
<sp-action-button
|
|
380
|
-
quiet
|
|
381
|
-
size="xs"
|
|
382
|
-
class="layer-delete"
|
|
383
|
-
title="Delete"
|
|
384
|
-
@click=${(e: MouseEvent) => {
|
|
385
|
-
e.stopPropagation();
|
|
386
|
-
transactDoc(activeTab.value!, (t) => mutateRemoveNode(t, path));
|
|
387
|
-
}}
|
|
388
|
-
>
|
|
389
|
-
<sp-icon-close slot="icon"></sp-icon-close>
|
|
390
|
-
</sp-action-button>
|
|
391
|
-
</span>
|
|
392
|
-
`
|
|
393
|
-
: nothing}
|
|
394
|
-
</div>
|
|
395
|
-
`);
|
|
418
|
+
: nothing}
|
|
419
|
+
</div>
|
|
420
|
+
`,
|
|
421
|
+
});
|
|
396
422
|
}
|
|
397
423
|
|
|
398
424
|
return html`
|
|
@@ -421,7 +447,11 @@ export function renderLayersTemplate(ctx: {
|
|
|
421
447
|
ctx.rerender();
|
|
422
448
|
}}
|
|
423
449
|
>
|
|
424
|
-
${
|
|
450
|
+
${repeat(
|
|
451
|
+
layerRows,
|
|
452
|
+
(r) => r.key,
|
|
453
|
+
(r) => r.tpl,
|
|
454
|
+
)}
|
|
425
455
|
</div>
|
|
426
456
|
</div>
|
|
427
457
|
`;
|
|
@@ -37,6 +37,21 @@ export function renderCanvasNode(
|
|
|
37
37
|
return;
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
+
// Array pseudo-element: render as a repeater perimeter at the array's own path, with a single
|
|
41
|
+
// Template instance inside (matches the edit-mode visual).
|
|
42
|
+
if (isMappedArray(node)) {
|
|
43
|
+
const wrapper = document.createElement("div");
|
|
44
|
+
wrapper.className = "repeater-perimeter";
|
|
45
|
+
elToPath.set(wrapper, path);
|
|
46
|
+
const template = node.map;
|
|
47
|
+
if (template && typeof template === "object") {
|
|
48
|
+
renderCanvasNode(template, [...path, "map"], wrapper, activeBreakpoints, featureToggles);
|
|
49
|
+
}
|
|
50
|
+
wrapper.style.pointerEvents = "none";
|
|
51
|
+
parent.append(wrapper);
|
|
52
|
+
return wrapper;
|
|
53
|
+
}
|
|
54
|
+
|
|
40
55
|
const tag = node.tagName || "div";
|
|
41
56
|
const el = document.createElement(tag);
|
|
42
57
|
|
|
@@ -55,6 +55,7 @@ interface SignalOption {
|
|
|
55
55
|
interface HtmlMetaEntry {
|
|
56
56
|
$section: string;
|
|
57
57
|
$order: number;
|
|
58
|
+
$attr?: string;
|
|
58
59
|
$elements?: string[];
|
|
59
60
|
$label?: string;
|
|
60
61
|
$input?: string;
|
|
@@ -77,12 +78,9 @@ function isInsideMapTemplate(path: JxPath | null) {
|
|
|
77
78
|
if (!path) {
|
|
78
79
|
return false;
|
|
79
80
|
}
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
return false;
|
|
81
|
+
// A "map" segment addresses a repeater template (`[…, "children", i, "map", …]`, or the legacy
|
|
82
|
+
// `[…, "children", "map", …]`), so anything at or below it is inside a map template.
|
|
83
|
+
return path.includes("map");
|
|
86
84
|
}
|
|
87
85
|
|
|
88
86
|
/**
|
|
@@ -173,9 +171,9 @@ function bindableFieldRow(
|
|
|
173
171
|
const staticDefault = defaultAsString(defs[defName]);
|
|
174
172
|
onChange(staticDefault || undefined);
|
|
175
173
|
} else if (signalDefs.length > 0) {
|
|
176
|
-
onChange({ $ref: `#/state/${signalDefs[0][0]}` });
|
|
174
|
+
onChange({ $ref: `#/state/${signalDefs[0]![0]}` });
|
|
177
175
|
} else if (extraSignals && extraSignals.length > 0) {
|
|
178
|
-
onChange({ $ref: extraSignals[0]
|
|
176
|
+
onChange({ $ref: extraSignals[0]!.value });
|
|
179
177
|
}
|
|
180
178
|
};
|
|
181
179
|
|
|
@@ -446,9 +444,9 @@ function renderComponentPropsFieldsTemplate(
|
|
|
446
444
|
const staticDefault = defaultAsString(defs[defName]);
|
|
447
445
|
onChange(staticDefault || undefined);
|
|
448
446
|
} else if (signalDefs.length > 0) {
|
|
449
|
-
onChange({ $ref: `#/state/${signalDefs[0][0]}` });
|
|
447
|
+
onChange({ $ref: `#/state/${signalDefs[0]![0]}` });
|
|
450
448
|
} else if (extraSignals && extraSignals.length > 0) {
|
|
451
|
-
onChange({ $ref: extraSignals[0]
|
|
449
|
+
onChange({ $ref: extraSignals[0]!.value });
|
|
452
450
|
}
|
|
453
451
|
};
|
|
454
452
|
|
|
@@ -565,7 +563,7 @@ function renderComponentPropsFieldsTemplate(
|
|
|
565
563
|
|
|
566
564
|
/** Custom attrs fields template */
|
|
567
565
|
function renderCustomAttrsFieldsTemplate(
|
|
568
|
-
|
|
566
|
+
_node: JxMutableNode,
|
|
569
567
|
path: JxPath,
|
|
570
568
|
attrs: Record<string, unknown>,
|
|
571
569
|
knownAttrNames: Set<string>,
|
|
@@ -809,7 +807,7 @@ function renderPageSection(node: JxMutableNode) {
|
|
|
809
807
|
}
|
|
810
808
|
|
|
811
809
|
if (layoutEntries === null) {
|
|
812
|
-
loadLayoutEntries();
|
|
810
|
+
void loadLayoutEntries();
|
|
813
811
|
return nothing;
|
|
814
812
|
}
|
|
815
813
|
|
|
@@ -949,10 +947,6 @@ export function renderPropertiesPanelTemplate(ctx: {
|
|
|
949
947
|
|
|
950
948
|
const path = tab.session.selection;
|
|
951
949
|
const isMapNode = node.$prototype === "Array";
|
|
952
|
-
const isMapParent =
|
|
953
|
-
node.children &&
|
|
954
|
-
typeof node.children === "object" &&
|
|
955
|
-
(node.children as unknown as { $prototype?: string }).$prototype === "Array";
|
|
956
950
|
const isSwitchNode = Boolean(node.$switch);
|
|
957
951
|
const isCustomInstance = (node.tagName || "").includes("-");
|
|
958
952
|
const isRoot = path.length === 0;
|
|
@@ -1010,7 +1004,9 @@ export function renderPropertiesPanelTemplate(ctx: {
|
|
|
1010
1004
|
const applicableAttrs = {} as Record<string, HtmlMetaEntry>;
|
|
1011
1005
|
for (const [attr, entry] of Object.entries(htmlMeta.$defs) as [string, HtmlMetaEntry][]) {
|
|
1012
1006
|
if (!entry.$elements || entry.$elements.includes(tagName)) {
|
|
1013
|
-
|
|
1007
|
+
// The $attr field aliases a $defs key to a different attribute name.
|
|
1008
|
+
// This lets the same attribute (e.g. "name") carry per-element metadata.
|
|
1009
|
+
applicableAttrs[entry.$attr ?? attr] = entry;
|
|
1014
1010
|
}
|
|
1015
1011
|
}
|
|
1016
1012
|
|
|
@@ -1025,7 +1021,7 @@ export function renderPropertiesPanelTemplate(ctx: {
|
|
|
1025
1021
|
}
|
|
1026
1022
|
}
|
|
1027
1023
|
for (const sec of htmlMeta.$sections) {
|
|
1028
|
-
attrSections[sec.key]
|
|
1024
|
+
attrSections[sec.key]!.sort(
|
|
1029
1025
|
(a: { name: string; entry: HtmlMetaEntry }, b: { name: string; entry: HtmlMetaEntry }) =>
|
|
1030
1026
|
a.entry.$order - b.entry.$order,
|
|
1031
1027
|
);
|
|
@@ -1196,13 +1192,6 @@ export function renderPropertiesPanelTemplate(ctx: {
|
|
|
1196
1192
|
>
|
|
1197
1193
|
</sp-checkbox>
|
|
1198
1194
|
</div>
|
|
1199
|
-
${isMapParent
|
|
1200
|
-
? html`
|
|
1201
|
-
<div style="font-size:10px;color:var(--fg-dim);padding:4px 0;font-style:italic">
|
|
1202
|
-
Children: Repeater (select in layers to configure)
|
|
1203
|
-
</div>
|
|
1204
|
-
`
|
|
1205
|
-
: nothing}
|
|
1206
1195
|
</div>
|
|
1207
1196
|
</sp-accordion-item>
|
|
1208
1197
|
`;
|
|
@@ -1283,9 +1272,9 @@ export function renderPropertiesPanelTemplate(ctx: {
|
|
|
1283
1272
|
: nothing;
|
|
1284
1273
|
|
|
1285
1274
|
const attrSectionTemplates = htmlMeta.$sections
|
|
1286
|
-
.filter((sec) => attrSections[sec.key]
|
|
1275
|
+
.filter((sec) => attrSections[sec.key]!.length > 0)
|
|
1287
1276
|
.map((sec) => {
|
|
1288
|
-
const sectionAttrs = attrSections[sec.key]
|
|
1277
|
+
const sectionAttrs = attrSections[sec.key]!;
|
|
1289
1278
|
const hasAnySet = sectionAttrs.some(
|
|
1290
1279
|
(a: { name: string; entry: HtmlMetaEntry }) => attrs[a.name] !== undefined,
|
|
1291
1280
|
);
|
|
@@ -82,7 +82,7 @@ function onKeydown(e: KeyboardEvent) {
|
|
|
82
82
|
case "Enter": {
|
|
83
83
|
e.preventDefault();
|
|
84
84
|
if (items[_selectedIndex]) {
|
|
85
|
-
selectItem(items[_selectedIndex]);
|
|
85
|
+
selectItem(items[_selectedIndex]!);
|
|
86
86
|
}
|
|
87
87
|
break;
|
|
88
88
|
}
|
|
@@ -101,7 +101,7 @@ function selectItem(item: { path: string; name?: string }) {
|
|
|
101
101
|
closeQuickSearch();
|
|
102
102
|
const { path } = item;
|
|
103
103
|
trackRecentFile({ name: path.split("/").pop() || "", path });
|
|
104
|
-
openFileInTab(path);
|
|
104
|
+
void openFileInTab(path);
|
|
105
105
|
}
|
|
106
106
|
|
|
107
107
|
function fileIcon(name: string) {
|
|
@@ -89,7 +89,6 @@ export function unmount() {
|
|
|
89
89
|
_eventsContainer = null;
|
|
90
90
|
_styleContainer = null;
|
|
91
91
|
_assistantContainer = null;
|
|
92
|
-
_lastTab = null;
|
|
93
92
|
}
|
|
94
93
|
|
|
95
94
|
/**
|
|
@@ -104,7 +103,6 @@ let _propsContainer: HTMLElement | null = null;
|
|
|
104
103
|
let _eventsContainer: HTMLElement | null = null;
|
|
105
104
|
let _styleContainer: HTMLElement | null = null;
|
|
106
105
|
let _assistantContainer: HTMLElement | null = null;
|
|
107
|
-
let _lastTab: string | null = null;
|
|
108
106
|
|
|
109
107
|
function _ensureContainers() {
|
|
110
108
|
if (_propsContainer) {
|
|
@@ -183,9 +181,9 @@ function _doRender() {
|
|
|
183
181
|
// Show/hide containers
|
|
184
182
|
for (let i = 0; i < containers.length; i++) {
|
|
185
183
|
if (tabKeys[i] === tab) {
|
|
186
|
-
containers[i]
|
|
184
|
+
containers[i]!.style.display = tabKeys[i] === "assistant" ? "flex" : "";
|
|
187
185
|
} else {
|
|
188
|
-
containers[i]
|
|
186
|
+
containers[i]!.style.display = "none";
|
|
189
187
|
}
|
|
190
188
|
}
|
|
191
189
|
|
|
@@ -221,8 +219,6 @@ function _doRender() {
|
|
|
221
219
|
} else if (tab === "assistant") {
|
|
222
220
|
litRender(renderAiPanelTemplate(), _assistantContainer!);
|
|
223
221
|
}
|
|
224
|
-
|
|
225
|
-
_lastTab = tab;
|
|
226
222
|
} catch (error) {
|
|
227
223
|
console.error("right-panel render error:", error);
|
|
228
224
|
}
|
package/src/panels/shared.ts
CHANGED
|
@@ -82,6 +82,9 @@ export function defaultDef(tag: string): JxMutableNode {
|
|
|
82
82
|
def.attributes = { alt: "Image" };
|
|
83
83
|
} else if (tag === "iframe") {
|
|
84
84
|
def.attributes = { src: "" };
|
|
85
|
+
} else if (tag === "slot") {
|
|
86
|
+
// Slot children are the fallback content shown when no slotted content is provided.
|
|
87
|
+
def.children = [{ tagName: "p", textContent: "Slot content" }];
|
|
85
88
|
} else if (tag === "select") {
|
|
86
89
|
def.children = [{ tagName: "option", textContent: "Option 1" }];
|
|
87
90
|
} else if (tag === "ul" || tag === "ol") {
|