@jxsuite/studio 0.26.2 → 0.28.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/studio.js +34349 -57015
- package/dist/studio.js.map +130 -398
- package/package.json +5 -11
- package/src/browse/browse.ts +62 -22
- package/src/canvas/canvas-live-render.ts +57 -55
- package/src/canvas/canvas-render.ts +50 -21
- package/src/editor/context-menu.ts +4 -1
- package/src/editor/convert-to-component.ts +8 -2
- package/src/editor/inline-edit.ts +7 -2
- package/src/editor/insertion-helper.ts +12 -3
- package/src/editor/slash-menu.ts +5 -1
- package/src/files/file-ops.ts +121 -102
- package/src/files/files.ts +53 -35
- package/src/format/constraints.ts +55 -0
- package/src/format/format-host.ts +212 -0
- package/src/new-project/new-project-modal.ts +18 -3
- package/src/panels/ai-panel.ts +3 -1
- package/src/panels/block-action-bar.ts +4 -2
- package/src/panels/canvas-dnd.ts +78 -28
- package/src/panels/data-explorer.ts +9 -2
- package/src/panels/dnd.ts +14 -3
- package/src/panels/editors.ts +1 -1
- package/src/panels/git-panel.ts +18 -8
- package/src/panels/head-panel.ts +12 -5
- package/src/panels/left-panel.ts +7 -4
- package/src/panels/panel-events.ts +3 -1
- package/src/panels/properties-panel.ts +9 -3
- package/src/panels/quick-search.ts +10 -10
- package/src/panels/right-panel.ts +6 -2
- package/src/panels/shared.ts +6 -1
- package/src/panels/signals-panel.ts +35 -10
- package/src/panels/style-inputs.ts +5 -1
- package/src/panels/style-panel.ts +6 -2
- package/src/panels/stylebook-layers-panel.ts +12 -3
- package/src/panels/stylebook-panel.ts +24 -8
- package/src/panels/toolbar.ts +16 -3
- package/src/platforms/devserver.ts +32 -3
- package/src/resize-edges.ts +12 -2
- package/src/services/cem-export.ts +9 -3
- package/src/settings/content-types-editor.ts +7 -2
- package/src/settings/defs-editor.ts +6 -1
- package/src/settings/general-settings.ts +3 -1
- package/src/settings/schema-field-ui.ts +11 -3
- package/src/settings/settings-modal.ts +4 -1
- package/src/state.ts +5 -1
- package/src/studio.ts +27 -16
- package/src/tabs/tab.ts +29 -6
- package/src/tabs/transact.ts +9 -2
- package/src/types.ts +15 -6
- package/src/ui/button-group.ts +6 -1
- package/src/ui/expression-editor.ts +14 -3
- package/src/ui/layers.ts +5 -1
- package/src/ui/media-picker.ts +192 -43
- package/src/ui/unit-selector.ts +6 -1
- package/src/utils/canvas-media.ts +1 -1
- package/src/utils/google-fonts.ts +4 -1
- package/src/utils/studio-utils.ts +7 -1
- package/src/view.ts +8 -2
- package/src/markdown/md-allowlist.ts +0 -104
- package/src/markdown/md-convert.ts +0 -846
|
@@ -211,7 +211,9 @@ export function registerPanelEvents(panel: CanvasPanel) {
|
|
|
211
211
|
let path = elToPath.get(el);
|
|
212
212
|
if (path) {
|
|
213
213
|
path = bubbleInlinePath(tab?.doc.document, path);
|
|
214
|
-
showContextMenu(e, path, {
|
|
214
|
+
showContextMenu(e, path, {
|
|
215
|
+
onEditComponent: ctx.navigateToComponent,
|
|
216
|
+
});
|
|
215
217
|
return;
|
|
216
218
|
}
|
|
217
219
|
}
|
|
@@ -490,12 +490,15 @@ function renderComponentPropsFieldsTemplate(
|
|
|
490
490
|
}}
|
|
491
491
|
></sp-number-field>`;
|
|
492
492
|
} else if (parsed.kind === "combobox") {
|
|
493
|
-
const options = /** @type {{ options?: string[] }} */
|
|
493
|
+
const options = /** @type {{ options?: string[] }} */ parsed.options as string[];
|
|
494
494
|
widgetTpl = html`<jx-value-selector
|
|
495
495
|
.value=${String(staticVal)}
|
|
496
496
|
size="s"
|
|
497
497
|
placeholder="—"
|
|
498
|
-
.options=${options.map((o) => ({
|
|
498
|
+
.options=${options.map((o) => ({
|
|
499
|
+
value: o,
|
|
500
|
+
label: camelToLabel(o),
|
|
501
|
+
}))}
|
|
499
502
|
@change=${(e: Event & { detail?: { value?: string } }) =>
|
|
500
503
|
onChange(e.detail?.value ?? (e.target as HTMLInputElement).value)}
|
|
501
504
|
></jx-value-selector>`;
|
|
@@ -849,7 +852,10 @@ function renderPageSection(node: JxMutableNode) {
|
|
|
849
852
|
// ─── Layout selection panel ─────────────────────────────────────────────────
|
|
850
853
|
|
|
851
854
|
function renderLayoutSelectionPanel(ctx: { navigateToComponent: (path: string) => void }) {
|
|
852
|
-
const { el, layoutPath } = view.layoutSelection as {
|
|
855
|
+
const { el, layoutPath } = view.layoutSelection as {
|
|
856
|
+
el: HTMLElement;
|
|
857
|
+
layoutPath: string;
|
|
858
|
+
};
|
|
853
859
|
const tagName = el?.tagName?.toLowerCase() || "element";
|
|
854
860
|
const className = el?.className || "";
|
|
855
861
|
const displayPath = layoutPath || "layout";
|
|
@@ -4,6 +4,7 @@ import { classMap } from "lit-html/directives/class-map.js";
|
|
|
4
4
|
import { live } from "lit-html/directives/live.js";
|
|
5
5
|
import { ref } from "lit-html/directives/ref.js";
|
|
6
6
|
import { getPlatform } from "../platform";
|
|
7
|
+
import { loadFormats, documentExtensions, formatByExtension } from "../format/format-host";
|
|
7
8
|
import { openFileInTab } from "../files/files";
|
|
8
9
|
import { getRecentFiles, trackRecentFile } from "../recent-projects";
|
|
9
10
|
import { getLayerSlot } from "../ui/layers";
|
|
@@ -46,7 +47,8 @@ async function doSearch(query: string) {
|
|
|
46
47
|
}
|
|
47
48
|
try {
|
|
48
49
|
const platform = getPlatform();
|
|
49
|
-
|
|
50
|
+
await loadFormats();
|
|
51
|
+
_results = await platform.searchFiles(query.trim().toLowerCase(), documentExtensions());
|
|
50
52
|
_selectedIndex = 0;
|
|
51
53
|
renderOverlay();
|
|
52
54
|
} catch {
|
|
@@ -95,14 +97,9 @@ function selectItem(item: { path: string; name?: string }) {
|
|
|
95
97
|
|
|
96
98
|
function fileIcon(name: string) {
|
|
97
99
|
const ext = name.split(".").pop()?.toLowerCase();
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
case "md":
|
|
102
|
-
return html`<sp-icon-file-txt size="s"></sp-icon-file-txt>`;
|
|
103
|
-
default:
|
|
104
|
-
return html`<sp-icon-document size="s"></sp-icon-document>`;
|
|
105
|
-
}
|
|
100
|
+
if (ext === "json") return html`<sp-icon-file-code size="s"></sp-icon-file-code>`;
|
|
101
|
+
if (ext && formatByExtension(ext)) return html`<sp-icon-file-txt size="s"></sp-icon-file-txt>`;
|
|
102
|
+
return html`<sp-icon-document size="s"></sp-icon-document>`;
|
|
106
103
|
}
|
|
107
104
|
|
|
108
105
|
function dirPart(path: string) {
|
|
@@ -149,7 +146,10 @@ function renderOverlay() {
|
|
|
149
146
|
${items.map(
|
|
150
147
|
(item, i) => html`
|
|
151
148
|
<div
|
|
152
|
-
class=${classMap({
|
|
149
|
+
class=${classMap({
|
|
150
|
+
"quick-search-item": true,
|
|
151
|
+
selected: i === _selectedIndex,
|
|
152
|
+
})}
|
|
153
153
|
@click=${() => selectItem(item)}
|
|
154
154
|
@mouseenter=${() => {
|
|
155
155
|
_selectedIndex = i;
|
|
@@ -189,12 +189,16 @@ function _doRender() {
|
|
|
189
189
|
// Only render the active panel's content
|
|
190
190
|
if (tab === "properties") {
|
|
191
191
|
litRender(
|
|
192
|
-
renderPropertiesPanelTemplate({
|
|
192
|
+
renderPropertiesPanelTemplate({
|
|
193
|
+
navigateToComponent: ctx.navigateToComponent,
|
|
194
|
+
}),
|
|
193
195
|
_propsContainer!,
|
|
194
196
|
);
|
|
195
197
|
} else if (tab === "events") {
|
|
196
198
|
litRender(
|
|
197
|
-
eventsSidebarTemplate({
|
|
199
|
+
eventsSidebarTemplate({
|
|
200
|
+
isCustomElementDoc: () => isCustomElementDoc(S),
|
|
201
|
+
}),
|
|
198
202
|
_eventsContainer!,
|
|
199
203
|
);
|
|
200
204
|
} else if (tab === "style") {
|
package/src/panels/shared.ts
CHANGED
|
@@ -79,7 +79,12 @@ export function defaultDef(tag: string) {
|
|
|
79
79
|
def.children = [
|
|
80
80
|
{
|
|
81
81
|
tagName: "thead",
|
|
82
|
-
children: [
|
|
82
|
+
children: [
|
|
83
|
+
{
|
|
84
|
+
tagName: "tr",
|
|
85
|
+
children: [{ tagName: "th", textContent: "Header" }],
|
|
86
|
+
},
|
|
87
|
+
],
|
|
83
88
|
},
|
|
84
89
|
{
|
|
85
90
|
tagName: "tbody",
|
|
@@ -420,14 +420,20 @@ export function renderSignalsTemplate(S: SignalsPanelState, ctx: SignalsPanelCtx
|
|
|
420
420
|
mutateAddDef(
|
|
421
421
|
t,
|
|
422
422
|
n,
|
|
423
|
-
/** @type {Record<string, JsonValue>} */
|
|
423
|
+
/** @type {Record<string, JsonValue>} */ {
|
|
424
|
+
$prototype: protoName,
|
|
425
|
+
},
|
|
424
426
|
),
|
|
425
427
|
);
|
|
426
428
|
expandedSignal = n;
|
|
427
429
|
if (src) {
|
|
428
430
|
fetchPluginSchema(
|
|
429
431
|
{ $prototype: protoName, $src: src },
|
|
430
|
-
{
|
|
432
|
+
{
|
|
433
|
+
...(S.documentPath != null && {
|
|
434
|
+
documentPath: S.documentPath,
|
|
435
|
+
}),
|
|
436
|
+
},
|
|
431
437
|
).then(() => ctx.renderLeftPanel());
|
|
432
438
|
} else {
|
|
433
439
|
ctx.renderLeftPanel();
|
|
@@ -683,7 +689,10 @@ function renderSignalEditorTemplate(
|
|
|
683
689
|
exprNode,
|
|
684
690
|
(newNode: any) =>
|
|
685
691
|
transactDoc(activeTab.value, (t) => mutateUpdateDef(t, name, { $expression: newNode })),
|
|
686
|
-
{
|
|
692
|
+
{
|
|
693
|
+
stateDefs: Object.keys(S.document.state || {}),
|
|
694
|
+
allowEventRef: false,
|
|
695
|
+
},
|
|
687
696
|
)}
|
|
688
697
|
`;
|
|
689
698
|
}
|
|
@@ -830,7 +839,9 @@ function renderFunctionFields(
|
|
|
830
839
|
quiet
|
|
831
840
|
title="Open in code editor"
|
|
832
841
|
@click=${() => {
|
|
833
|
-
ctx.updateSession({
|
|
842
|
+
ctx.updateSession({
|
|
843
|
+
ui: { editingFunction: { type: "def", defName: name } },
|
|
844
|
+
});
|
|
834
845
|
ctx.renderCanvas();
|
|
835
846
|
}}
|
|
836
847
|
>
|
|
@@ -940,7 +951,10 @@ function renderParameterEditorTemplate(
|
|
|
940
951
|
style="flex:1"
|
|
941
952
|
@change=${(e: Event) => {
|
|
942
953
|
const next = [...params];
|
|
943
|
-
next[i] = {
|
|
954
|
+
next[i] = {
|
|
955
|
+
...next[i],
|
|
956
|
+
name: (e.target as HTMLInputElement).value,
|
|
957
|
+
};
|
|
944
958
|
transactDoc(activeTab.value, (t) =>
|
|
945
959
|
mutateUpdateDef(t, name, { parameters: next }),
|
|
946
960
|
);
|
|
@@ -995,7 +1009,9 @@ function renderParameterEditorTemplate(
|
|
|
995
1009
|
@click=${() => {
|
|
996
1010
|
const next = params.filter((_: unknown, j: number) => j !== i);
|
|
997
1011
|
transactDoc(activeTab.value, (t) =>
|
|
998
|
-
mutateUpdateDef(t, name, {
|
|
1012
|
+
mutateUpdateDef(t, name, {
|
|
1013
|
+
parameters: next.length ? next : undefined,
|
|
1014
|
+
}),
|
|
999
1015
|
);
|
|
1000
1016
|
}}
|
|
1001
1017
|
>×</span
|
|
@@ -1007,7 +1023,9 @@ function renderParameterEditorTemplate(
|
|
|
1007
1023
|
class="kv-add"
|
|
1008
1024
|
@click=${() =>
|
|
1009
1025
|
transactDoc(activeTab.value, (t) =>
|
|
1010
|
-
mutateUpdateDef(t, name, {
|
|
1026
|
+
mutateUpdateDef(t, name, {
|
|
1027
|
+
parameters: [...params, { name: "" }],
|
|
1028
|
+
}),
|
|
1011
1029
|
)}
|
|
1012
1030
|
>
|
|
1013
1031
|
+ Add parameter
|
|
@@ -1046,7 +1064,10 @@ function renderEmitsEditorTemplate(S: SignalsPanelState, name: string, def: Sign
|
|
|
1046
1064
|
style="flex:1"
|
|
1047
1065
|
@change=${(e: Event) => {
|
|
1048
1066
|
const next = [...emits];
|
|
1049
|
-
next[i] = {
|
|
1067
|
+
next[i] = {
|
|
1068
|
+
...next[i],
|
|
1069
|
+
name: (e.target as HTMLInputElement).value,
|
|
1070
|
+
};
|
|
1050
1071
|
transactDoc(activeTab.value, (t) => mutateUpdateDef(t, name, { emits: next }));
|
|
1051
1072
|
}}
|
|
1052
1073
|
/>
|
|
@@ -1289,7 +1310,9 @@ export function renderSchemaFieldsTemplate(
|
|
|
1289
1310
|
? parseInt((e.target as HTMLInputElement).value, 10)
|
|
1290
1311
|
: parseFloat((e.target as HTMLInputElement).value);
|
|
1291
1312
|
transactDoc(activeTab.value, (t) =>
|
|
1292
|
-
mutateUpdateDef(t, name, {
|
|
1313
|
+
mutateUpdateDef(t, name, {
|
|
1314
|
+
[prop]: isNaN(parsed) ? undefined : parsed,
|
|
1315
|
+
}),
|
|
1293
1316
|
);
|
|
1294
1317
|
}, 400);
|
|
1295
1318
|
}}
|
|
@@ -1375,7 +1398,9 @@ export function renderSchemaFieldsTemplate(
|
|
|
1375
1398
|
@click=${() => {
|
|
1376
1399
|
const updated = rows.filter((_: unknown, i: number) => i !== idx);
|
|
1377
1400
|
transactDoc(activeTab.value, (t) =>
|
|
1378
|
-
mutateUpdateDef(t, name, {
|
|
1401
|
+
mutateUpdateDef(t, name, {
|
|
1402
|
+
[prop]: updated.length ? updated : undefined,
|
|
1403
|
+
}),
|
|
1379
1404
|
);
|
|
1380
1405
|
ctx?.renderLeftPanel();
|
|
1381
1406
|
}}
|
|
@@ -163,7 +163,11 @@ export function buildFontOptions(
|
|
|
163
163
|
* @param {string} prop @param {string} value @param {(value: string) => void} onChange
|
|
164
164
|
*/
|
|
165
165
|
export function renderComboboxInput(
|
|
166
|
-
entry: {
|
|
166
|
+
entry: {
|
|
167
|
+
enum?: string[];
|
|
168
|
+
examples?: string[];
|
|
169
|
+
presets?: { title: string; value: string }[];
|
|
170
|
+
},
|
|
167
171
|
prop: string,
|
|
168
172
|
value: string,
|
|
169
173
|
onChange: (value: string) => void,
|
|
@@ -246,7 +246,9 @@ function renderShorthandRow(
|
|
|
246
246
|
mutateFn(t, shortProp, compress(vals as string[]));
|
|
247
247
|
});
|
|
248
248
|
},
|
|
249
|
-
{
|
|
249
|
+
{
|
|
250
|
+
placeholder: !lVal && inherited[name] ? String(inherited[name]) : "",
|
|
251
|
+
},
|
|
250
252
|
)}
|
|
251
253
|
</div>
|
|
252
254
|
`;
|
|
@@ -773,7 +775,9 @@ function styleSidebarTemplate(
|
|
|
773
775
|
style="flex:1;text-align:left;padding:6px 10px;background:var(--spectrum-gray-200, #1a1a1a);border:none;border-radius:4px;color:var(--spectrum-gray-900, #fafafa);font-size:12px;cursor:pointer"
|
|
774
776
|
@click=${() => {
|
|
775
777
|
const newSelector = activeSelector ? `${activeSelector} ${rule}` : rule;
|
|
776
|
-
selectStylebookTag(newSelector, undefined, {
|
|
778
|
+
selectStylebookTag(newSelector, undefined, {
|
|
779
|
+
panCanvas: true,
|
|
780
|
+
});
|
|
777
781
|
}}
|
|
778
782
|
>
|
|
779
783
|
${rule}
|
|
@@ -57,7 +57,10 @@ export function renderStylebookLayersTemplate(ctx: {
|
|
|
57
57
|
: [];
|
|
58
58
|
return html`
|
|
59
59
|
<div
|
|
60
|
-
class=${classMap({
|
|
60
|
+
class=${classMap({
|
|
61
|
+
"layer-row": true,
|
|
62
|
+
selected: tag === selectedLeaf,
|
|
63
|
+
})}
|
|
61
64
|
style="padding-left:${8 + depth * 16}px"
|
|
62
65
|
@click=${(e: MouseEvent) => {
|
|
63
66
|
e.stopPropagation();
|
|
@@ -91,8 +94,14 @@ export function renderStylebookLayersTemplate(ctx: {
|
|
|
91
94
|
comp: import("../files/components.js").ComponentEntry,
|
|
92
95
|
) => html`
|
|
93
96
|
<div
|
|
94
|
-
class=${classMap({
|
|
95
|
-
|
|
97
|
+
class=${classMap({
|
|
98
|
+
"layer-row": true,
|
|
99
|
+
selected: comp.tagName === selectedTag,
|
|
100
|
+
})}
|
|
101
|
+
@click=${() =>
|
|
102
|
+
ctx.selectStylebookTag(comp.tagName, undefined, {
|
|
103
|
+
panCanvas: true,
|
|
104
|
+
})}
|
|
96
105
|
>
|
|
97
106
|
<span class="layer-tag component-tag" style="background:var(--accent)">⬡</span>
|
|
98
107
|
<span class="layer-label">${comp.tagName}</span>
|
|
@@ -65,7 +65,13 @@ interface StylebookCtx {
|
|
|
65
65
|
el: Element,
|
|
66
66
|
type: string,
|
|
67
67
|
panel: any,
|
|
68
|
-
) => {
|
|
68
|
+
) => {
|
|
69
|
+
cls: string;
|
|
70
|
+
top: string;
|
|
71
|
+
left: string;
|
|
72
|
+
width: string;
|
|
73
|
+
height: string;
|
|
74
|
+
};
|
|
69
75
|
effectiveZoom: () => number;
|
|
70
76
|
}
|
|
71
77
|
|
|
@@ -235,11 +241,11 @@ export function renderStylebookMode(ctx: StylebookCtx) {
|
|
|
235
241
|
${panelEntries.map((e) => e.tpl)}
|
|
236
242
|
</div>
|
|
237
243
|
`,
|
|
238
|
-
/** @type {HTMLElement} */
|
|
244
|
+
/** @type {HTMLElement} */ canvasWrap,
|
|
239
245
|
);
|
|
240
246
|
|
|
241
247
|
for (const { panel, activeSet } of panelEntries) {
|
|
242
|
-
canvasPanels.push(/** @type {import("./canvas-dnd.js").CanvasPanel} */
|
|
248
|
+
canvasPanels.push(/** @type {import("./canvas-dnd.js").CanvasPanel} */ panel);
|
|
243
249
|
renderIntoPanel(panel, activeSet);
|
|
244
250
|
}
|
|
245
251
|
if (hasMedia) {
|
|
@@ -321,7 +327,7 @@ export function refreshStylebookStyles() {
|
|
|
321
327
|
if (mediaName === "--") continue;
|
|
322
328
|
if (activeBreakpoints.has(mediaName)) {
|
|
323
329
|
const mediaTagStyle = _resolveNestedStyle(
|
|
324
|
-
/** @type {Record<string, unknown>} */
|
|
330
|
+
/** @type {Record<string, unknown>} */ val,
|
|
325
331
|
tag,
|
|
326
332
|
);
|
|
327
333
|
if (mediaTagStyle && typeof mediaTagStyle === "object") {
|
|
@@ -389,7 +395,11 @@ export function renderStylebookOverlays() {
|
|
|
389
395
|
|
|
390
396
|
if (hoverTag && hoverTag !== selectedTag) {
|
|
391
397
|
const el = findStylebookEl(panel.canvas, hoverTag);
|
|
392
|
-
if (el)
|
|
398
|
+
if (el)
|
|
399
|
+
boxes.push({
|
|
400
|
+
..._ctx.overlayBoxDescriptor(el, "hover", panel),
|
|
401
|
+
label: undefined,
|
|
402
|
+
});
|
|
393
403
|
}
|
|
394
404
|
|
|
395
405
|
if (selectedTag) {
|
|
@@ -408,7 +418,12 @@ export function renderStylebookOverlays() {
|
|
|
408
418
|
(b) => html`
|
|
409
419
|
<div
|
|
410
420
|
class=${b.cls}
|
|
411
|
-
style=${styleMap({
|
|
421
|
+
style=${styleMap({
|
|
422
|
+
top: b.top,
|
|
423
|
+
left: b.left,
|
|
424
|
+
width: b.width,
|
|
425
|
+
height: b.height,
|
|
426
|
+
})}
|
|
412
427
|
>
|
|
413
428
|
${b.label ? html`<div class="overlay-label">${b.label}</div>` : nothing}
|
|
414
429
|
</div>
|
|
@@ -441,7 +456,7 @@ export function buildStylebookElement(
|
|
|
441
456
|
if (entry.attributes) {
|
|
442
457
|
for (const [k, v] of Object.entries(entry.attributes)) {
|
|
443
458
|
try {
|
|
444
|
-
el.setAttribute(k, /** @type {string} */
|
|
459
|
+
el.setAttribute(k, /** @type {string} */ v);
|
|
445
460
|
} catch {}
|
|
446
461
|
}
|
|
447
462
|
}
|
|
@@ -519,7 +534,8 @@ export async function renderComponentPreview(comp: ComponentEntry) {
|
|
|
519
534
|
return _componentFallback(comp.tagName);
|
|
520
535
|
}
|
|
521
536
|
} else {
|
|
522
|
-
if (comp.path &&
|
|
537
|
+
if (comp.path && !comp.path.endsWith(".json")) {
|
|
538
|
+
// Format-class component sources (e.g. markdown) can't be imported as modules
|
|
523
539
|
return _componentFallback(comp.tagName);
|
|
524
540
|
}
|
|
525
541
|
const root = projectState?.projectRoot;
|
package/src/panels/toolbar.ts
CHANGED
|
@@ -27,7 +27,12 @@ interface ToolbarCtx {
|
|
|
27
27
|
openFile?: (path: string) => void;
|
|
28
28
|
saveFile: () => void;
|
|
29
29
|
parseMediaEntries: (media: Record<string, string> | null | undefined) => {
|
|
30
|
-
sizeBreakpoints: {
|
|
30
|
+
sizeBreakpoints: {
|
|
31
|
+
name: string;
|
|
32
|
+
query: string;
|
|
33
|
+
width: number;
|
|
34
|
+
type: string;
|
|
35
|
+
}[];
|
|
31
36
|
featureQueries: { name: string; query: string }[];
|
|
32
37
|
baseWidth: number;
|
|
33
38
|
};
|
|
@@ -171,7 +176,11 @@ function minimalToolbarTemplate(ctx: ToolbarCtx) {
|
|
|
171
176
|
const windowControls = (
|
|
172
177
|
globalThis as unknown as {
|
|
173
178
|
__jxPlatform?: {
|
|
174
|
-
windowControls?: {
|
|
179
|
+
windowControls?: {
|
|
180
|
+
minimize: () => void;
|
|
181
|
+
maximize: () => void;
|
|
182
|
+
close: () => void;
|
|
183
|
+
};
|
|
175
184
|
};
|
|
176
185
|
}
|
|
177
186
|
).__jxPlatform?.windowControls;
|
|
@@ -381,7 +390,11 @@ function toolbarTemplate() {
|
|
|
381
390
|
const windowControls = (
|
|
382
391
|
globalThis as unknown as {
|
|
383
392
|
__jxPlatform?: {
|
|
384
|
-
windowControls?: {
|
|
393
|
+
windowControls?: {
|
|
394
|
+
minimize: () => void;
|
|
395
|
+
maximize: () => void;
|
|
396
|
+
close: () => void;
|
|
397
|
+
};
|
|
385
398
|
};
|
|
386
399
|
}
|
|
387
400
|
).__jxPlatform?.windowControls;
|
|
@@ -352,9 +352,13 @@ export function createDevServerPlatform() {
|
|
|
352
352
|
return null;
|
|
353
353
|
},
|
|
354
354
|
|
|
355
|
-
/**
|
|
356
|
-
|
|
357
|
-
|
|
355
|
+
/**
|
|
356
|
+
* @param {string} query
|
|
357
|
+
* @param {string[]} [extensions] — extra extensions beyond .json (from the format registry)
|
|
358
|
+
*/
|
|
359
|
+
async searchFiles(query: string, extensions: string[] = []) {
|
|
360
|
+
const exts = ["json", ...extensions.map((e: string) => e.replace(/^\./, ""))];
|
|
361
|
+
const glob = `**/*${query}*.{${exts.join(",")}}`;
|
|
358
362
|
const res = await fetch(
|
|
359
363
|
`/__studio/files?dir=${encodeURIComponent(serverPath("."))}&glob=${encodeURIComponent(glob)}`,
|
|
360
364
|
);
|
|
@@ -364,6 +368,31 @@ export function createDevServerPlatform() {
|
|
|
364
368
|
return entries;
|
|
365
369
|
},
|
|
366
370
|
|
|
371
|
+
// ─── Format registry ──────────────────────────────────────────────────
|
|
372
|
+
|
|
373
|
+
/** List the project's registered format classes (auto-discovered from imports). */
|
|
374
|
+
async listFormats() {
|
|
375
|
+
const res = await fetch(`/__studio/formats?dir=${encodeURIComponent(serverPath("."))}`);
|
|
376
|
+
if (!res.ok) return [];
|
|
377
|
+
return (await res.json()).formats ?? [];
|
|
378
|
+
},
|
|
379
|
+
|
|
380
|
+
/**
|
|
381
|
+
* Invoke a format capability (parse/serialize) server-side.
|
|
382
|
+
*
|
|
383
|
+
* @param {Record<string, unknown>} payload — { format, action, source?, doc?, options? }
|
|
384
|
+
*/
|
|
385
|
+
async formatAction(payload: Record<string, unknown>) {
|
|
386
|
+
const res = await fetch("/__studio/format", {
|
|
387
|
+
method: "POST",
|
|
388
|
+
headers: { "Content-Type": "application/json" },
|
|
389
|
+
body: JSON.stringify({ ...payload, dir: serverPath(".") }),
|
|
390
|
+
});
|
|
391
|
+
const data = await res.json();
|
|
392
|
+
if (!res.ok) throw new Error(data.error || "Format action failed");
|
|
393
|
+
return data.result;
|
|
394
|
+
},
|
|
395
|
+
|
|
367
396
|
// ─── Plugin schema ────────────────────────────────────────────────────
|
|
368
397
|
|
|
369
398
|
/**
|
package/src/resize-edges.ts
CHANGED
|
@@ -2,7 +2,12 @@
|
|
|
2
2
|
const g = globalThis as unknown as {
|
|
3
3
|
__jxPlatform?: {
|
|
4
4
|
windowControls?: {
|
|
5
|
-
getFrame: () => Promise<{
|
|
5
|
+
getFrame: () => Promise<{
|
|
6
|
+
x: number;
|
|
7
|
+
y: number;
|
|
8
|
+
width: number;
|
|
9
|
+
height: number;
|
|
10
|
+
}>;
|
|
6
11
|
setFrame: (x: number, y: number, w: number, h: number) => void;
|
|
7
12
|
};
|
|
8
13
|
};
|
|
@@ -54,7 +59,12 @@ async function startResize(
|
|
|
54
59
|
e: MouseEvent,
|
|
55
60
|
edge: string,
|
|
56
61
|
wc: {
|
|
57
|
-
getFrame: () => Promise<{
|
|
62
|
+
getFrame: () => Promise<{
|
|
63
|
+
x: number;
|
|
64
|
+
y: number;
|
|
65
|
+
width: number;
|
|
66
|
+
height: number;
|
|
67
|
+
}>;
|
|
58
68
|
setFrame: (x: number, y: number, w: number, h: number) => void;
|
|
59
69
|
},
|
|
60
70
|
) {
|
|
@@ -54,7 +54,9 @@ export function exportCemManifest(
|
|
|
54
54
|
...(d.description ? { description: d.description } : {}),
|
|
55
55
|
...(d.parameters ? { parameters: d.parameters.map(normParam) } : {}),
|
|
56
56
|
...(d.deprecated
|
|
57
|
-
? {
|
|
57
|
+
? {
|
|
58
|
+
deprecated: typeof d.deprecated === "string" ? d.deprecated : true,
|
|
59
|
+
}
|
|
58
60
|
: {}),
|
|
59
61
|
});
|
|
60
62
|
// Collect emits
|
|
@@ -80,7 +82,9 @@ export function exportCemManifest(
|
|
|
80
82
|
...(d.attribute ? { attribute: d.attribute } : {}),
|
|
81
83
|
...(d.reflects ? { reflects: true } : {}),
|
|
82
84
|
...(d.deprecated
|
|
83
|
-
? {
|
|
85
|
+
? {
|
|
86
|
+
deprecated: typeof d.deprecated === "string" ? d.deprecated : true,
|
|
87
|
+
}
|
|
84
88
|
: {}),
|
|
85
89
|
});
|
|
86
90
|
if (d.attribute) {
|
|
@@ -132,7 +136,9 @@ export function exportCemManifest(
|
|
|
132
136
|
],
|
|
133
137
|
};
|
|
134
138
|
|
|
135
|
-
const blob = new Blob([JSON.stringify(manifest, null, 2)], {
|
|
139
|
+
const blob = new Blob([JSON.stringify(manifest, null, 2)], {
|
|
140
|
+
type: "application/json",
|
|
141
|
+
});
|
|
136
142
|
const url = URL.createObjectURL(blob);
|
|
137
143
|
const a = document.createElement("a");
|
|
138
144
|
a.href = url;
|
|
@@ -466,7 +466,7 @@ export function renderContentTypesEditor(container: HTMLElement) {
|
|
|
466
466
|
([name, def]) =>
|
|
467
467
|
fieldCardTpl(
|
|
468
468
|
name,
|
|
469
|
-
/** @type {import("./schema-field-ui.js").SchemaProperty} */
|
|
469
|
+
/** @type {import("./schema-field-ui.js").SchemaProperty} */ def,
|
|
470
470
|
required.includes(name),
|
|
471
471
|
handlers,
|
|
472
472
|
contentTypeNames,
|
|
@@ -497,7 +497,12 @@ export function renderContentTypesEditor(container: HTMLElement) {
|
|
|
497
497
|
onConfirm: () => handleAddField(rerender),
|
|
498
498
|
onCancel: () => {
|
|
499
499
|
showAddField = false;
|
|
500
|
-
newFieldState = {
|
|
500
|
+
newFieldState = {
|
|
501
|
+
name: "",
|
|
502
|
+
type: "string",
|
|
503
|
+
format: "",
|
|
504
|
+
required: false,
|
|
505
|
+
};
|
|
501
506
|
rerender();
|
|
502
507
|
},
|
|
503
508
|
})
|
|
@@ -460,7 +460,12 @@ export function renderDefsEditor(container: HTMLElement) {
|
|
|
460
460
|
onConfirm: () => handleAddField(rerender),
|
|
461
461
|
onCancel: () => {
|
|
462
462
|
showAddField = false;
|
|
463
|
-
newFieldState = {
|
|
463
|
+
newFieldState = {
|
|
464
|
+
name: "",
|
|
465
|
+
type: "string",
|
|
466
|
+
format: "",
|
|
467
|
+
required: false,
|
|
468
|
+
};
|
|
464
469
|
rerender();
|
|
465
470
|
},
|
|
466
471
|
})
|
|
@@ -30,7 +30,9 @@ export function renderGeneralSettings(container: HTMLElement) {
|
|
|
30
30
|
};
|
|
31
31
|
|
|
32
32
|
const onAdapterChange = (e: Event) => {
|
|
33
|
-
updateSiteConfig({
|
|
33
|
+
updateSiteConfig({
|
|
34
|
+
build: { ...config.build, adapter: (e.target as HTMLInputElement).value },
|
|
35
|
+
});
|
|
34
36
|
};
|
|
35
37
|
|
|
36
38
|
const onEditGlobalStyles = () => {
|
|
@@ -157,7 +157,7 @@ export function fieldCardTpl(
|
|
|
157
157
|
nestedFieldCardTpl(
|
|
158
158
|
fieldName,
|
|
159
159
|
name,
|
|
160
|
-
/** @type {SchemaProperty} */
|
|
160
|
+
/** @type {SchemaProperty} */ sub,
|
|
161
161
|
nestedRequired.includes(name),
|
|
162
162
|
handlers,
|
|
163
163
|
),
|
|
@@ -274,7 +274,11 @@ function nestedAddFieldTpl(parentName: string, handlers: FieldHandlers) {
|
|
|
274
274
|
const typePicker = row?.querySelector("sp-picker") as HTMLInputElement | null;
|
|
275
275
|
const type = typePicker?.value || "string";
|
|
276
276
|
if (name && handlers.onAddNestedField) {
|
|
277
|
-
handlers.onAddNestedField(parentName, {
|
|
277
|
+
handlers.onAddNestedField(parentName, {
|
|
278
|
+
name,
|
|
279
|
+
type,
|
|
280
|
+
required: false,
|
|
281
|
+
});
|
|
278
282
|
target.value = "";
|
|
279
283
|
}
|
|
280
284
|
}
|
|
@@ -295,7 +299,11 @@ function nestedAddFieldTpl(parentName: string, handlers: FieldHandlers) {
|
|
|
295
299
|
const name = nameInput?.value?.trim();
|
|
296
300
|
const type = typePicker?.value || "string";
|
|
297
301
|
if (name && handlers.onAddNestedField) {
|
|
298
|
-
handlers.onAddNestedField(parentName, {
|
|
302
|
+
handlers.onAddNestedField(parentName, {
|
|
303
|
+
name,
|
|
304
|
+
type,
|
|
305
|
+
required: false,
|
|
306
|
+
});
|
|
299
307
|
if (nameInput) nameInput.value = "";
|
|
300
308
|
}
|
|
301
309
|
}}
|
|
@@ -68,7 +68,10 @@ function renderModal() {
|
|
|
68
68
|
${sections.map(
|
|
69
69
|
(s) => html`
|
|
70
70
|
<button
|
|
71
|
-
class=${classMap({
|
|
71
|
+
class=${classMap({
|
|
72
|
+
"settings-nav-item": true,
|
|
73
|
+
active: _activeSection === s.key,
|
|
74
|
+
})}
|
|
72
75
|
@click=${() => onNavClick(s.key)}
|
|
73
76
|
>
|
|
74
77
|
${s.label}
|
package/src/state.ts
CHANGED
|
@@ -33,7 +33,11 @@ export interface StudioState {
|
|
|
33
33
|
mode: string;
|
|
34
34
|
content: { frontmatter: Record<string, unknown> };
|
|
35
35
|
ui: Record<string, unknown>;
|
|
36
|
-
canvas: {
|
|
36
|
+
canvas: {
|
|
37
|
+
status: string;
|
|
38
|
+
scope: Record<string, unknown> | null;
|
|
39
|
+
error: string | null;
|
|
40
|
+
};
|
|
37
41
|
}
|
|
38
42
|
|
|
39
43
|
interface StudioStackFrame {
|