@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.
Files changed (60) hide show
  1. package/dist/studio.js +34349 -57015
  2. package/dist/studio.js.map +130 -398
  3. package/package.json +5 -11
  4. package/src/browse/browse.ts +62 -22
  5. package/src/canvas/canvas-live-render.ts +57 -55
  6. package/src/canvas/canvas-render.ts +50 -21
  7. package/src/editor/context-menu.ts +4 -1
  8. package/src/editor/convert-to-component.ts +8 -2
  9. package/src/editor/inline-edit.ts +7 -2
  10. package/src/editor/insertion-helper.ts +12 -3
  11. package/src/editor/slash-menu.ts +5 -1
  12. package/src/files/file-ops.ts +121 -102
  13. package/src/files/files.ts +53 -35
  14. package/src/format/constraints.ts +55 -0
  15. package/src/format/format-host.ts +212 -0
  16. package/src/new-project/new-project-modal.ts +18 -3
  17. package/src/panels/ai-panel.ts +3 -1
  18. package/src/panels/block-action-bar.ts +4 -2
  19. package/src/panels/canvas-dnd.ts +78 -28
  20. package/src/panels/data-explorer.ts +9 -2
  21. package/src/panels/dnd.ts +14 -3
  22. package/src/panels/editors.ts +1 -1
  23. package/src/panels/git-panel.ts +18 -8
  24. package/src/panels/head-panel.ts +12 -5
  25. package/src/panels/left-panel.ts +7 -4
  26. package/src/panels/panel-events.ts +3 -1
  27. package/src/panels/properties-panel.ts +9 -3
  28. package/src/panels/quick-search.ts +10 -10
  29. package/src/panels/right-panel.ts +6 -2
  30. package/src/panels/shared.ts +6 -1
  31. package/src/panels/signals-panel.ts +35 -10
  32. package/src/panels/style-inputs.ts +5 -1
  33. package/src/panels/style-panel.ts +6 -2
  34. package/src/panels/stylebook-layers-panel.ts +12 -3
  35. package/src/panels/stylebook-panel.ts +24 -8
  36. package/src/panels/toolbar.ts +16 -3
  37. package/src/platforms/devserver.ts +32 -3
  38. package/src/resize-edges.ts +12 -2
  39. package/src/services/cem-export.ts +9 -3
  40. package/src/settings/content-types-editor.ts +7 -2
  41. package/src/settings/defs-editor.ts +6 -1
  42. package/src/settings/general-settings.ts +3 -1
  43. package/src/settings/schema-field-ui.ts +11 -3
  44. package/src/settings/settings-modal.ts +4 -1
  45. package/src/state.ts +5 -1
  46. package/src/studio.ts +27 -16
  47. package/src/tabs/tab.ts +29 -6
  48. package/src/tabs/transact.ts +9 -2
  49. package/src/types.ts +15 -6
  50. package/src/ui/button-group.ts +6 -1
  51. package/src/ui/expression-editor.ts +14 -3
  52. package/src/ui/layers.ts +5 -1
  53. package/src/ui/media-picker.ts +192 -43
  54. package/src/ui/unit-selector.ts +6 -1
  55. package/src/utils/canvas-media.ts +1 -1
  56. package/src/utils/google-fonts.ts +4 -1
  57. package/src/utils/studio-utils.ts +7 -1
  58. package/src/view.ts +8 -2
  59. package/src/markdown/md-allowlist.ts +0 -104
  60. 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, { onEditComponent: ctx.navigateToComponent });
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[] }} */ (parsed).options as 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) => ({ value: o, label: camelToLabel(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 { el: HTMLElement; layoutPath: string };
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
- _results = await platform.searchFiles(query.trim().toLowerCase());
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
- switch (ext) {
99
- case "json":
100
- return html`<sp-icon-file-code size="s"></sp-icon-file-code>`;
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({ "quick-search-item": true, selected: i === _selectedIndex })}
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({ navigateToComponent: ctx.navigateToComponent }),
192
+ renderPropertiesPanelTemplate({
193
+ navigateToComponent: ctx.navigateToComponent,
194
+ }),
193
195
  _propsContainer!,
194
196
  );
195
197
  } else if (tab === "events") {
196
198
  litRender(
197
- eventsSidebarTemplate({ isCustomElementDoc: () => isCustomElementDoc(S) }),
199
+ eventsSidebarTemplate({
200
+ isCustomElementDoc: () => isCustomElementDoc(S),
201
+ }),
198
202
  _eventsContainer!,
199
203
  );
200
204
  } else if (tab === "style") {
@@ -79,7 +79,12 @@ export function defaultDef(tag: string) {
79
79
  def.children = [
80
80
  {
81
81
  tagName: "thead",
82
- children: [{ tagName: "tr", children: [{ tagName: "th", textContent: "Header" }] }],
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>} */ ({ $prototype: protoName }),
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
- { ...(S.documentPath != null && { documentPath: S.documentPath }) },
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
- { stateDefs: Object.keys(S.document.state || {}), allowEventRef: false },
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({ ui: { editingFunction: { type: "def", defName: name } } });
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] = { ...next[i], name: (e.target as HTMLInputElement).value };
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, { parameters: next.length ? next : undefined }),
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, { parameters: [...params, { 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] = { ...next[i], name: (e.target as HTMLInputElement).value };
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, { [prop]: isNaN(parsed) ? undefined : parsed }),
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, { [prop]: updated.length ? updated : undefined }),
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: { enum?: string[]; examples?: string[]; presets?: { title: string; value: string }[] },
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
- { placeholder: !lVal && inherited[name] ? String(inherited[name]) : "" },
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, { panCanvas: true });
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({ "layer-row": true, selected: tag === selectedLeaf })}
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({ "layer-row": true, selected: comp.tagName === selectedTag })}
95
- @click=${() => ctx.selectStylebookTag(comp.tagName, undefined, { panCanvas: true })}
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
- ) => { cls: string; top: string; left: string; width: string; height: string };
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} */ (canvasWrap),
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} */ (panel));
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>} */ (val),
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) boxes.push({ ..._ctx.overlayBoxDescriptor(el, "hover", panel), label: undefined });
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({ top: b.top, left: b.left, width: b.width, height: b.height })}
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} */ (v));
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 && /\.md$/i.test(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;
@@ -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: { name: string; query: string; width: number; type: string }[];
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?: { minimize: () => void; maximize: () => void; close: () => void };
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?: { minimize: () => void; maximize: () => void; close: () => void };
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
- /** @param {string} query */
356
- async searchFiles(query: string) {
357
- const glob = `**/*${query}*.{json,md}`;
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
  /**
@@ -2,7 +2,12 @@
2
2
  const g = globalThis as unknown as {
3
3
  __jxPlatform?: {
4
4
  windowControls?: {
5
- getFrame: () => Promise<{ x: number; y: number; width: number; height: number }>;
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<{ x: number; y: number; width: number; height: number }>;
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
- ? { deprecated: typeof d.deprecated === "string" ? d.deprecated : true }
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
- ? { deprecated: typeof d.deprecated === "string" ? d.deprecated : true }
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)], { type: "application/json" });
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} */ (def),
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 = { name: "", type: "string", format: "", required: false };
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 = { name: "", type: "string", format: "", required: false };
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({ build: { ...config.build, adapter: (e.target as HTMLInputElement).value } });
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} */ (sub),
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, { name, type, required: false });
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, { name, type, required: false });
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({ "settings-nav-item": true, active: _activeSection === s.key })}
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: { status: string; scope: Record<string, unknown> | null; error: string | null };
36
+ canvas: {
37
+ status: string;
38
+ scope: Record<string, unknown> | null;
39
+ error: string | null;
40
+ };
37
41
  }
38
42
 
39
43
  interface StudioStackFrame {