@jxsuite/studio 0.28.2 → 0.28.4

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 (104) hide show
  1. package/dist/studio.js +86144 -83971
  2. package/dist/studio.js.map +229 -219
  3. package/package.json +5 -5
  4. package/src/browse/browse-modal.ts +9 -3
  5. package/src/browse/browse.ts +139 -82
  6. package/src/canvas/canvas-diff.ts +35 -18
  7. package/src/canvas/canvas-helpers.ts +55 -21
  8. package/src/canvas/canvas-live-render.ts +140 -89
  9. package/src/canvas/canvas-render.ts +127 -82
  10. package/src/canvas/canvas-utils.ts +68 -42
  11. package/src/canvas/nested-site-style.ts +15 -7
  12. package/src/editor/component-inline-edit.ts +90 -51
  13. package/src/editor/content-inline-edit.ts +75 -72
  14. package/src/editor/context-menu.ts +125 -71
  15. package/src/editor/convert-targets.ts +17 -13
  16. package/src/editor/convert-to-component.ts +51 -28
  17. package/src/editor/convert-to-repeater.ts +44 -19
  18. package/src/editor/inline-edit.ts +107 -52
  19. package/src/editor/inline-format.ts +128 -60
  20. package/src/editor/insertion-helper.ts +26 -14
  21. package/src/editor/shortcuts.ts +89 -43
  22. package/src/editor/slash-menu.ts +41 -26
  23. package/src/files/components.ts +9 -7
  24. package/src/files/file-ops.ts +53 -33
  25. package/src/files/files.ts +246 -143
  26. package/src/format/constraints.ts +25 -15
  27. package/src/format/format-host.ts +33 -10
  28. package/src/github/github-auth.ts +24 -14
  29. package/src/github/github-publish.ts +20 -14
  30. package/src/new-project/new-project-modal.ts +27 -18
  31. package/src/panels/activity-bar.ts +27 -26
  32. package/src/panels/ai-panel.ts +100 -44
  33. package/src/panels/block-action-bar.ts +84 -52
  34. package/src/panels/canvas-dnd.ts +47 -28
  35. package/src/panels/data-explorer.ts +24 -11
  36. package/src/panels/dnd.ts +148 -100
  37. package/src/panels/editors.ts +54 -37
  38. package/src/panels/elements-panel.ts +23 -13
  39. package/src/panels/events-panel.ts +48 -38
  40. package/src/panels/git-panel.ts +95 -60
  41. package/src/panels/head-panel.ts +138 -84
  42. package/src/panels/imports-panel.ts +66 -29
  43. package/src/panels/layers-panel.ts +80 -36
  44. package/src/panels/left-panel.ts +86 -53
  45. package/src/panels/overlays.ts +36 -19
  46. package/src/panels/panel-events.ts +48 -27
  47. package/src/panels/panel-scheduler.ts +41 -23
  48. package/src/panels/preview-render.ts +26 -26
  49. package/src/panels/properties-panel.ts +155 -118
  50. package/src/panels/pseudo-preview.ts +31 -15
  51. package/src/panels/quick-search.ts +28 -13
  52. package/src/panels/right-panel.ts +35 -24
  53. package/src/panels/shared.ts +46 -29
  54. package/src/panels/signals-panel.ts +303 -186
  55. package/src/panels/statusbar.ts +27 -12
  56. package/src/panels/style-inputs.ts +30 -24
  57. package/src/panels/style-panel.ts +130 -90
  58. package/src/panels/style-utils.ts +63 -23
  59. package/src/panels/stylebook-layers-panel.ts +27 -30
  60. package/src/panels/stylebook-panel.ts +140 -72
  61. package/src/panels/tab-strip.ts +21 -10
  62. package/src/panels/toolbar.ts +52 -40
  63. package/src/panels/welcome-screen.ts +1 -1
  64. package/src/platform.ts +2 -1
  65. package/src/platforms/devserver.ts +221 -105
  66. package/src/recent-projects.ts +15 -7
  67. package/src/resize-edges.ts +7 -5
  68. package/src/services/cem-export.ts +73 -57
  69. package/src/services/code-services.ts +52 -25
  70. package/src/services/monaco-setup.ts +5 -5
  71. package/src/settings/content-types-editor.ts +133 -62
  72. package/src/settings/css-vars-editor.ts +31 -14
  73. package/src/settings/defs-editor.ts +125 -63
  74. package/src/settings/general-settings.ts +11 -6
  75. package/src/settings/head-editor.ts +30 -15
  76. package/src/settings/schema-field-ui.ts +75 -35
  77. package/src/settings/settings-modal.ts +35 -17
  78. package/src/site-context.ts +80 -47
  79. package/src/state.ts +98 -65
  80. package/src/store.ts +42 -23
  81. package/src/studio.ts +165 -144
  82. package/src/tabs/tab.ts +51 -45
  83. package/src/tabs/transact.ts +230 -109
  84. package/src/types.ts +60 -51
  85. package/src/ui/button-group.ts +3 -2
  86. package/src/ui/color-selector.ts +33 -15
  87. package/src/ui/expression-editor.ts +139 -86
  88. package/src/ui/field-input.ts +35 -18
  89. package/src/ui/field-row.ts +1 -1
  90. package/src/ui/icons.ts +5 -6
  91. package/src/ui/layers.ts +33 -26
  92. package/src/ui/media-picker.ts +13 -7
  93. package/src/ui/panel-resize.ts +39 -20
  94. package/src/ui/spectrum.ts +4 -2
  95. package/src/ui/unit-selector.ts +20 -11
  96. package/src/ui/value-selector.ts +13 -7
  97. package/src/ui/widgets.ts +34 -18
  98. package/src/utils/canvas-media.ts +66 -33
  99. package/src/utils/edit-display.ts +43 -44
  100. package/src/utils/google-fonts.ts +10 -6
  101. package/src/utils/inherited-style.ts +22 -8
  102. package/src/utils/studio-utils.ts +88 -45
  103. package/src/view.ts +9 -5
  104. package/src/workspace/workspace.ts +49 -29
@@ -3,32 +3,33 @@
3
3
 
4
4
  import { html, nothing } from "lit-html";
5
5
  import { live } from "lit-html/directives/live.js";
6
- import { getNodeAtPath, debouncedStyleCommit, renderOnly, projectState } from "../store";
7
- import type { JsonValue, DirEntry } from "../types";
6
+ import { debouncedStyleCommit, getNodeAtPath, projectState, renderOnly } from "../store";
7
+ import { isRef } from "@jxsuite/schema/guards";
8
+ import type { DirEntry, JsonValue } from "../types";
8
9
  import {
9
- transactDoc,
10
- mutateUpdateProperty,
11
- mutateUpdateAttribute,
12
- mutateUpdateProp,
13
- mutateUpdateMedia,
14
10
  mutateAddSwitchCase,
15
11
  mutateRemoveSwitchCase,
16
12
  mutateRenameSwitchCase,
13
+ mutateUpdateAttribute,
14
+ mutateUpdateMedia,
15
+ mutateUpdateProp,
16
+ mutateUpdateProperty,
17
+ transactDoc,
17
18
  } from "../tabs/transact";
18
19
  import { activeTab } from "../workspace/workspace";
19
20
  import { view } from "../view";
20
21
  import { componentRegistry } from "../files/components";
21
22
  import { widgetForType } from "./style-inputs";
22
23
  import { renderFieldRow } from "../ui/field-row";
23
- import { spTextField, spTextArea } from "../ui/field-input";
24
+ import { spTextArea, spTextField } from "../ui/field-input";
24
25
  import {
25
26
  attrLabel,
26
- inferInputType,
27
- friendlyNameToVar,
28
27
  camelToLabel,
28
+ friendlyNameToVar,
29
+ inferInputType,
29
30
  parseCemType,
30
31
  } from "../utils/studio-utils";
31
- import { isCustomElementDoc, collectCssParts } from "./signals-panel";
32
+ import { collectCssParts, isCustomElementDoc } from "./signals-panel";
32
33
  import { mediaDisplayName } from "./shared";
33
34
  import { getCssInitialMap } from "./style-utils";
34
35
  import { renderMediaPicker } from "../ui/media-picker";
@@ -37,8 +38,14 @@ import { getEffectiveLayoutPath, invalidateLayoutCache } from "../site-context";
37
38
  import { getPlatform } from "../platform";
38
39
  import htmlMeta from "../../data/html-meta.json";
39
40
 
40
- import type { JxPrototypeDef, JxMutableNode } from "@jxsuite/schema/types";
41
+ import type {
42
+ JxMutableNode,
43
+ JxPrototypeDef,
44
+ JxStateDefinition,
45
+ JxStateObject,
46
+ } from "@jxsuite/schema/types";
41
47
  import type { JxPath } from "../state";
48
+ import type { SignalDef } from "./signals-panel.js";
42
49
 
43
50
  interface SignalOption {
44
51
  value: string;
@@ -67,32 +74,54 @@ function friendlyNameToMedia(name: string) {
67
74
 
68
75
  /** Check if a selection path is inside a $map template (contains [..., "children", "map", ...]). */
69
76
  function isInsideMapTemplate(path: JxPath | null) {
70
- if (!path) return false;
77
+ if (!path) {
78
+ return false;
79
+ }
71
80
  for (let i = 0; i < path.length - 1; i++) {
72
- if (path[i] === "children" && path[i + 1] === "map") return true;
81
+ if (path[i] === "children" && path[i + 1] === "map") {
82
+ return true;
83
+ }
73
84
  }
74
85
  return false;
75
86
  }
76
87
 
88
+ /**
89
+ * Render a state entry's default value as the static input text when unbinding.
90
+ *
91
+ * @param {import("@jxsuite/schema/types").JxStateDefinition | undefined} def
92
+ * @returns {string}
93
+ */
94
+ function defaultAsString(def: JxStateDefinition | undefined) {
95
+ if (!def || typeof def !== "object" || Array.isArray(def)) {
96
+ return "";
97
+ }
98
+ const dv = (def as JxStateObject).default;
99
+ if (dv === undefined) {
100
+ return "";
101
+ }
102
+ return typeof dv === "object" ? JSON.stringify(dv) : String(dv);
103
+ }
104
+
77
105
  /**
78
106
  * Field row with binding toggle — allows switching between static value and signal binding.
79
- * rawValue can be a string/bool (static) or { $ref: "..." } (bound).
107
+ * rawValue can be a JSON literal (static) or { $ref: "..." } (bound).
80
108
  */
81
109
  function bindableFieldRow(
82
110
  label: string,
83
111
  type: string,
84
- rawValue: string | number | boolean | { $ref: string } | null | undefined,
85
- onChange: (v: JsonValue) => void,
86
- filterFn: ((d: import("./signals-panel.js").SignalDef) => boolean) | null = null,
112
+ rawValue: unknown,
113
+ onChange: (v?: JsonValue) => void,
114
+ filterFn: ((d: SignalDef) => boolean) | null = null,
87
115
  extraSignals: SignalOption[] | null = null,
88
116
  ) {
89
117
  const tab = activeTab.value;
90
118
  const defs = tab!.doc.document.state || {};
91
- const isBound = typeof rawValue === "object" && rawValue !== null && rawValue.$ref;
119
+ const boundRef = isRef(rawValue) ? rawValue.$ref : null;
120
+ const isBound = boundRef !== null;
92
121
 
93
122
  const signalDefs = Object.entries(defs).filter(([, d]) =>
94
123
  filterFn
95
- ? filterFn(d as import("./signals-panel.js").SignalDef)
124
+ ? filterFn(d as SignalDef)
96
125
  : !(d as Record<string, unknown>)?.$handler &&
97
126
  (d as JxPrototypeDef)?.$prototype !== "Function",
98
127
  );
@@ -104,7 +133,7 @@ function bindableFieldRow(
104
133
  ? spTextArea(fieldKey, String(staticVal), (v: string) => onChange(v))
105
134
  : type === "checkbox"
106
135
  ? html`<sp-checkbox
107
- ?checked=${!!staticVal}
136
+ ?checked=${Boolean(staticVal)}
108
137
  @change=${(e: Event) => onChange((e.target as HTMLInputElement).checked)}
109
138
  ></sp-checkbox>`
110
139
  : spTextField(fieldKey, String(staticVal), (v: string) => onChange(v));
@@ -114,11 +143,13 @@ function bindableFieldRow(
114
143
  size="s"
115
144
  quiet
116
145
  placeholder="— select signal —"
117
- value=${isBound && rawValue.$ref ? rawValue.$ref : nothing}
146
+ value=${boundRef ?? nothing}
118
147
  @change=${(e: Event) => {
119
- if ((e.target as HTMLInputElement).value)
148
+ if ((e.target as HTMLInputElement).value) {
120
149
  onChange({ $ref: (e.target as HTMLInputElement).value });
121
- else onChange(undefined);
150
+ } else {
151
+ onChange();
152
+ }
122
153
  }}
123
154
  >
124
155
  ${signalDefs.map(
@@ -137,21 +168,14 @@ function bindableFieldRow(
137
168
  `;
138
169
 
139
170
  const onToggle = () => {
140
- if (isBound) {
141
- const ref = rawValue.$ref;
142
- const defName = ref.startsWith("#/state/") ? ref.slice(8) : ref;
143
- const def = defs[defName];
144
- let staticVal = "";
145
- if (def && def.default !== undefined)
146
- staticVal =
147
- typeof def.default === "object" ? JSON.stringify(def.default) : String(def.default);
148
- onChange(staticVal || undefined);
149
- } else {
150
- if (signalDefs.length > 0) {
151
- onChange({ $ref: `#/state/${signalDefs[0][0]}` });
152
- } else if (extraSignals && extraSignals.length > 0) {
153
- onChange({ $ref: extraSignals[0].value });
154
- }
171
+ if (boundRef !== null) {
172
+ const defName = boundRef.startsWith("#/state/") ? boundRef.slice(8) : boundRef;
173
+ const staticDefault = defaultAsString(defs[defName]);
174
+ onChange(staticDefault || undefined);
175
+ } else if (signalDefs.length > 0) {
176
+ onChange({ $ref: `#/state/${signalDefs[0][0]}` });
177
+ } else if (extraSignals && extraSignals.length > 0) {
178
+ onChange({ $ref: extraSignals[0].value });
155
179
  }
156
180
  };
157
181
 
@@ -202,11 +226,12 @@ function kvRow(
202
226
  const el = (e.target as HTMLInputElement)
203
227
  .closest(".kv-row")
204
228
  ?.querySelector(".kv-val");
205
- if (el)
229
+ if (el) {
206
230
  el.setAttribute(
207
231
  "placeholder",
208
232
  getCssInitialMap().get((e.target as HTMLInputElement).value) || "",
209
233
  );
234
+ }
210
235
  }
211
236
  : nothing}
212
237
  ></sp-textfield>
@@ -327,10 +352,11 @@ function renderSwitchFieldsTemplate(
327
352
  if (
328
353
  (e.target as HTMLInputElement).value &&
329
354
  (e.target as HTMLInputElement).value !== caseName
330
- )
355
+ ) {
331
356
  transactDoc(activeTab.value, (t) =>
332
357
  mutateRenameSwitchCase(t, path, caseName, (e.target as HTMLInputElement).value),
333
358
  );
359
+ }
334
360
  }, 500);
335
361
  }}
336
362
  />
@@ -376,15 +402,17 @@ function renderComponentPropsFieldsTemplate(
376
402
  ) {
377
403
  const tab = activeTab.value;
378
404
  const comp = componentRegistry.find((c) => c.tagName === node.tagName);
379
- if (!comp || !comp.props) return html`<div class="empty-state">Component not found</div>`;
405
+ if (!comp || !comp.props) {
406
+ return html`<div class="empty-state">Component not found</div>`;
407
+ }
380
408
  const isNpm = comp.source === "npm";
381
409
  const currentVals = isNpm ? node.attributes || {} : node.$props || {};
382
410
  const updateFn = isNpm
383
- ? (name: string, v: JsonValue) =>
411
+ ? (name: string, v?: JsonValue) =>
384
412
  transactDoc(activeTab.value, (t) =>
385
413
  mutateUpdateAttribute(t, path, name, v === "" ? undefined : (v as string | undefined)),
386
414
  )
387
- : (name: string, v: JsonValue) =>
415
+ : (name: string, v?: JsonValue) =>
388
416
  transactDoc(activeTab.value, (t) => mutateUpdateProp(t, path, name, v));
389
417
 
390
418
  const defs = tab!.doc.document.state || {};
@@ -400,33 +428,27 @@ function renderComponentPropsFieldsTemplate(
400
428
  /** @type {{ name: string; type?: string; format?: string; description?: string }} */ prop,
401
429
  ) => {
402
430
  const rawValue = currentVals[prop.name];
403
- const isBound = typeof rawValue === "object" && rawValue !== null && rawValue.$ref;
431
+ const boundRef = isRef(rawValue) ? rawValue.$ref : null;
432
+ const isBound = boundRef !== null;
404
433
  const hasVal = rawValue !== undefined && rawValue !== null;
405
434
  const parsed = parseCemType(prop.type);
406
- const onChange = (v: JsonValue) => updateFn(prop.name, v);
435
+ const onChange = (v?: JsonValue) => updateFn(prop.name, v);
407
436
  const staticVal = isBound ? "" : String(rawValue ?? "");
408
437
 
409
438
  const clearProp = (e: Event) => {
410
439
  e.stopPropagation();
411
- updateFn(prop.name, undefined);
440
+ updateFn(prop.name);
412
441
  };
413
442
 
414
443
  const onToggleBind = () => {
415
- if (isBound) {
416
- const ref = rawValue.$ref;
417
- const defName = ref.startsWith("#/state/") ? ref.slice(8) : ref;
418
- const def = defs[defName];
419
- let staticVal = "";
420
- if (def && def.default !== undefined)
421
- staticVal =
422
- typeof def.default === "object" ? JSON.stringify(def.default) : String(def.default);
423
- onChange(staticVal || undefined);
424
- } else {
425
- if (signalDefs.length > 0) {
426
- onChange({ $ref: `#/state/${signalDefs[0][0]}` });
427
- } else if (extraSignals && extraSignals.length > 0) {
428
- onChange({ $ref: extraSignals[0].value });
429
- }
444
+ if (boundRef !== null) {
445
+ const defName = boundRef.startsWith("#/state/") ? boundRef.slice(8) : boundRef;
446
+ const staticDefault = defaultAsString(defs[defName]);
447
+ onChange(staticDefault || undefined);
448
+ } else if (signalDefs.length > 0) {
449
+ onChange({ $ref: `#/state/${signalDefs[0][0]}` });
450
+ } else if (extraSignals && extraSignals.length > 0) {
451
+ onChange({ $ref: extraSignals[0].value });
430
452
  }
431
453
  };
432
454
 
@@ -435,11 +457,13 @@ function renderComponentPropsFieldsTemplate(
435
457
  size="s"
436
458
  quiet
437
459
  placeholder="— select signal —"
438
- value=${isBound && rawValue.$ref ? rawValue.$ref : nothing}
460
+ value=${boundRef ?? nothing}
439
461
  @change=${(e: Event) => {
440
- if ((e.target as HTMLInputElement).value)
462
+ if ((e.target as HTMLInputElement).value) {
441
463
  onChange({ $ref: (e.target as HTMLInputElement).value });
442
- else onChange(undefined);
464
+ } else {
465
+ onChange();
466
+ }
443
467
  }}
444
468
  >
445
469
  ${signalDefs.map(
@@ -477,7 +501,7 @@ function renderComponentPropsFieldsTemplate(
477
501
  } else if (parsed.kind === "boolean") {
478
502
  widgetTpl = html`<sp-checkbox
479
503
  size="s"
480
- .checked=${live(!!staticVal)}
504
+ .checked=${live(Boolean(staticVal))}
481
505
  @change=${(e: Event) => onChange((e.target as HTMLInputElement).checked || undefined)}
482
506
  ></sp-checkbox>`;
483
507
  } else if (parsed.kind === "number") {
@@ -496,8 +520,8 @@ function renderComponentPropsFieldsTemplate(
496
520
  size="s"
497
521
  placeholder="—"
498
522
  .options=${options.map((o) => ({
499
- value: o,
500
523
  label: camelToLabel(o),
524
+ value: o,
501
525
  }))}
502
526
  @change=${(e: Event & { detail?: { value?: string } }) =>
503
527
  onChange(e.detail?.value ?? (e.target as HTMLInputElement).value)}
@@ -532,7 +556,7 @@ function renderComponentPropsFieldsTemplate(
532
556
  )}
533
557
  ${comp.props.length === 0 ? html`<div class="empty-state">No props defined</div>` : nothing}
534
558
  ${comp.path
535
- ? html`<span class="kv-add" @click=${() => navigateToComponent(comp.path)}
559
+ ? html`<span class="kv-add" @click=${() => navigateToComponent(comp.path!)}
536
560
  >→ Edit definition</span
537
561
  >`
538
562
  : nothing}
@@ -555,14 +579,14 @@ function renderCustomAttrsFieldsTemplate(
555
579
  (newAttr: string, newVal: string) => {
556
580
  if (newAttr !== attr) {
557
581
  transactDoc(activeTab.value, (t) => {
558
- mutateUpdateAttribute(t, path, attr, undefined);
582
+ mutateUpdateAttribute(t, path, attr);
559
583
  mutateUpdateAttribute(t, path, newAttr, newVal);
560
584
  });
561
585
  } else {
562
586
  transactDoc(activeTab.value, (t) => mutateUpdateAttribute(t, path, attr, newVal));
563
587
  }
564
588
  },
565
- () => transactDoc(activeTab.value, (t) => mutateUpdateAttribute(t, path, attr, undefined)),
589
+ () => transactDoc(activeTab.value, (t) => mutateUpdateAttribute(t, path, attr)),
566
590
  ),
567
591
  )}
568
592
  <span
@@ -602,8 +626,7 @@ function renderMediaFieldsTemplate(node: JxMutableNode) {
602
626
  ${media["--"]
603
627
  ? html`<span
604
628
  class="kv-del"
605
- @click=${() =>
606
- transactDoc(activeTab.value, (t) => mutateUpdateMedia(t, "--", undefined))}
629
+ @click=${() => transactDoc(activeTab.value, (t) => mutateUpdateMedia(t, "--"))}
607
630
  >✕</span
608
631
  >`
609
632
  : nothing}
@@ -698,7 +721,9 @@ function mediaBreakpointRowTemplate(name: string, query: string) {
698
721
  const newKey = friendlyNameToMedia((e.target as HTMLInputElement).value);
699
722
  currentRawLabel = newKey || "";
700
723
  const rawEl = (e.target as HTMLElement).parentElement?.querySelector(".bp-raw-label");
701
- if (rawEl) rawEl.textContent = currentRawLabel;
724
+ if (rawEl) {
725
+ rawEl.textContent = currentRawLabel;
726
+ }
702
727
  clearTimeout(debounceTimer);
703
728
  debounceTimer = setTimeout(() => {
704
729
  if (newKey && newKey !== name) {
@@ -706,7 +731,7 @@ function mediaBreakpointRowTemplate(name: string, query: string) {
706
731
  .closest("div[style]")
707
732
  ?.parentElement?.querySelector(".bp-query-input") as HTMLInputElement | null;
708
733
  transactDoc(activeTab.value, (t) => {
709
- mutateUpdateMedia(t, name, undefined);
734
+ mutateUpdateMedia(t, name);
710
735
  mutateUpdateMedia(t, newKey, queryEl?.value || query);
711
736
  });
712
737
  }
@@ -720,7 +745,7 @@ function mediaBreakpointRowTemplate(name: string, query: string) {
720
745
  >
721
746
  <span
722
747
  class="kv-del"
723
- @click=${() => transactDoc(activeTab.value, (t) => mutateUpdateMedia(t, name, undefined))}
748
+ @click=${() => transactDoc(activeTab.value, (t) => mutateUpdateMedia(t, name))}
724
749
  >✕</span
725
750
  >
726
751
  </div>
@@ -771,13 +796,17 @@ export function invalidateLayoutPickerCache() {
771
796
  }
772
797
 
773
798
  function isPageDocument(documentPath: string | undefined | null) {
774
- if (!documentPath || !projectState?.isSiteProject) return false;
799
+ if (!documentPath || !projectState?.isSiteProject) {
800
+ return false;
801
+ }
775
802
  return documentPath.startsWith("pages/") || documentPath.startsWith("./pages/");
776
803
  }
777
804
 
778
805
  function renderPageSection(node: JxMutableNode) {
779
806
  const tab = activeTab.value;
780
- if (!isPageDocument(tab!.documentPath)) return nothing;
807
+ if (!isPageDocument(tab!.documentPath)) {
808
+ return nothing;
809
+ }
781
810
 
782
811
  if (layoutEntries === null) {
783
812
  loadLayoutEntries();
@@ -787,8 +816,7 @@ function renderPageSection(node: JxMutableNode) {
787
816
  const currentLayout = node.$layout;
788
817
  const defaultLayout = projectState?.projectConfig?.defaults?.layout;
789
818
  const effectivePath = getEffectiveLayoutPath(currentLayout);
790
- const displayValue =
791
- currentLayout === false ? "__none__" : currentLayout ? currentLayout : "__default__";
819
+ const displayValue = currentLayout === false ? "__none__" : currentLayout || "__default__";
792
820
 
793
821
  return html`
794
822
  <sp-accordion-item label="Page" open>
@@ -801,9 +829,7 @@ function renderPageSection(node: JxMutableNode) {
801
829
  title="Reset to default"
802
830
  @click=${(e: Event) => {
803
831
  e.stopPropagation();
804
- transactDoc(activeTab.value, (t) =>
805
- mutateUpdateProperty(t, [], "$layout", undefined),
806
- );
832
+ transactDoc(activeTab.value, (t) => mutateUpdateProperty(t, [], "$layout"));
807
833
  }}
808
834
  ></span>`
809
835
  : nothing}
@@ -815,9 +841,7 @@ function renderPageSection(node: JxMutableNode) {
815
841
  @change=${(e: Event) => {
816
842
  const val = (e.target as HTMLInputElement).value;
817
843
  if (val === "__default__") {
818
- transactDoc(activeTab.value, (t) =>
819
- mutateUpdateProperty(t, [], "$layout", undefined),
820
- );
844
+ transactDoc(activeTab.value, (t) => mutateUpdateProperty(t, [], "$layout"));
821
845
  } else if (val === "__none__") {
822
846
  transactDoc(activeTab.value, (t) => mutateUpdateProperty(t, [], "$layout", false));
823
847
  } else {
@@ -906,7 +930,9 @@ export function renderPropertiesPanelTemplate(ctx: {
906
930
  navigateToComponent: (path: string) => void;
907
931
  }) {
908
932
  const tab = activeTab.value;
909
- if (!tab) return html`<div class="empty-state">No document loaded</div>`;
933
+ if (!tab) {
934
+ return html`<div class="empty-state">No document loaded</div>`;
935
+ }
910
936
 
911
937
  // Layout element selected — show read-only info with link to open layout
912
938
  if (view.layoutSelection) {
@@ -917,7 +943,9 @@ export function renderPropertiesPanelTemplate(ctx: {
917
943
  return html`<div class="empty-state">Select an element to inspect</div>`;
918
944
  }
919
945
  const node = getNodeAtPath(tab.doc.document, tab.session.selection);
920
- if (!node) return html`<div class="empty-state">Node not found</div>`;
946
+ if (!node) {
947
+ return html`<div class="empty-state">Node not found</div>`;
948
+ }
921
949
 
922
950
  const path = tab.session.selection;
923
951
  const isMapNode = node.$prototype === "Array";
@@ -925,7 +953,7 @@ export function renderPropertiesPanelTemplate(ctx: {
925
953
  node.children &&
926
954
  typeof node.children === "object" &&
927
955
  (node.children as unknown as { $prototype?: string }).$prototype === "Array";
928
- const isSwitchNode = !!node.$switch;
956
+ const isSwitchNode = Boolean(node.$switch);
929
957
  const isCustomInstance = (node.tagName || "").includes("-");
930
958
  const isRoot = path.length === 0;
931
959
  const tagName = node.tagName || "div";
@@ -933,8 +961,8 @@ export function renderPropertiesPanelTemplate(ctx: {
933
961
 
934
962
  const mapSignals = isInsideMapTemplate(path)
935
963
  ? [
936
- { value: "$map/item", label: "$map/item" },
937
- { value: "$map/index", label: "$map/index" },
964
+ { label: "$map/item", value: "$map/item" },
965
+ { label: "$map/index", value: "$map/index" },
938
966
  ]
939
967
  : null;
940
968
 
@@ -944,15 +972,14 @@ export function renderPropertiesPanelTemplate(ctx: {
944
972
 
945
973
  if (entry.type === "boolean") {
946
974
  return renderFieldRow({
947
- prop: attr,
948
- label: attrLabel(entry, attr),
949
975
  hasValue: hasVal,
950
- onClear: () =>
951
- transactDoc(activeTab.value, (t) => mutateUpdateAttribute(t, path, attr, undefined)),
976
+ label: attrLabel(entry, attr),
977
+ onClear: () => transactDoc(activeTab.value, (t) => mutateUpdateAttribute(t, path, attr)),
978
+ prop: attr,
952
979
  widget: html`
953
980
  <sp-checkbox
954
981
  size="s"
955
- .checked=${live(!!value)}
982
+ .checked=${live(Boolean(value))}
956
983
  @change=${(e: Event) =>
957
984
  transactDoc(activeTab.value, (t) =>
958
985
  mutateUpdateAttribute(
@@ -969,11 +996,10 @@ export function renderPropertiesPanelTemplate(ctx: {
969
996
  }
970
997
 
971
998
  return renderFieldRow({
972
- prop: attr,
973
- label: attrLabel(entry, attr),
974
999
  hasValue: hasVal,
975
- onClear: () =>
976
- transactDoc(activeTab.value, (t) => mutateUpdateAttribute(t, path, attr, undefined)),
1000
+ label: attrLabel(entry, attr),
1001
+ onClear: () => transactDoc(activeTab.value, (t) => mutateUpdateAttribute(t, path, attr)),
1002
+ prop: attr,
977
1003
  widget: widgetForType(type, entry, attr, String(value || ""), (v: string) =>
978
1004
  transactDoc(activeTab.value!, (t) => mutateUpdateAttribute(t, path, attr, v || undefined)),
979
1005
  ),
@@ -989,10 +1015,14 @@ export function renderPropertiesPanelTemplate(ctx: {
989
1015
  }
990
1016
 
991
1017
  const attrSections: Record<string, { name: string; entry: HtmlMetaEntry }[]> = {};
992
- for (const sec of htmlMeta.$sections) attrSections[sec.key] = [];
1018
+ for (const sec of htmlMeta.$sections) {
1019
+ attrSections[sec.key] = [];
1020
+ }
993
1021
  for (const [attr, entry] of Object.entries(applicableAttrs)) {
994
1022
  const secKey = entry.$section;
995
- if (attrSections[secKey]) attrSections[secKey].push({ name: attr, entry });
1023
+ if (attrSections[secKey]) {
1024
+ attrSections[secKey].push({ entry, name: attr });
1025
+ }
996
1026
  }
997
1027
  for (const sec of htmlMeta.$sections) {
998
1028
  attrSections[sec.key].sort(
@@ -1004,20 +1034,29 @@ export function renderPropertiesPanelTemplate(ctx: {
1004
1034
  const knownAttrNames = new Set(Object.keys(applicableAttrs));
1005
1035
  if (isCustomInstance) {
1006
1036
  const comp = componentRegistry.find((c) => c.tagName === node.tagName);
1007
- if (comp?.props) for (const p of comp.props) knownAttrNames.add(p.name);
1037
+ if (comp?.props) {
1038
+ for (const p of comp.props) {
1039
+ knownAttrNames.add(p.name);
1040
+ }
1041
+ }
1008
1042
  }
1009
1043
  const customAttrs = Object.entries(attrs).filter(([k]) => !knownAttrNames.has(k));
1010
1044
 
1011
1045
  const autoOpen = new Set();
1012
1046
  for (const [attr] of Object.entries(attrs)) {
1013
1047
  const entry = applicableAttrs[attr];
1014
- if (entry) autoOpen.add(entry.$section);
1048
+ if (entry) {
1049
+ autoOpen.add(entry.$section);
1050
+ }
1051
+ }
1052
+ if (customAttrs.length > 0) {
1053
+ autoOpen.add("__custom");
1015
1054
  }
1016
- if (customAttrs.length > 0) autoOpen.add("__custom");
1017
1055
 
1018
1056
  function isSectionOpen(key: string) {
1019
- if (tab!.session.ui.inspectorSections[key] !== undefined)
1057
+ if (tab!.session.ui.inspectorSections[key] !== undefined) {
1020
1058
  return tab!.session.ui.inspectorSections[key];
1059
+ }
1021
1060
  return autoOpen.has(key);
1022
1061
  }
1023
1062
 
@@ -1067,9 +1106,7 @@ export function renderPropertiesPanelTemplate(ctx: {
1067
1106
  title="Clear $id"
1068
1107
  @click=${(e: Event) => {
1069
1108
  e.stopPropagation();
1070
- transactDoc(activeTab.value, (t) =>
1071
- mutateUpdateProperty(t, path, "$id", undefined),
1072
- );
1109
+ transactDoc(activeTab.value, (t) => mutateUpdateProperty(t, path, "$id"));
1073
1110
  }}
1074
1111
  ></span>`
1075
1112
  : nothing}
@@ -1089,9 +1126,7 @@ export function renderPropertiesPanelTemplate(ctx: {
1089
1126
  title="Clear class"
1090
1127
  @click=${(e: Event) => {
1091
1128
  e.stopPropagation();
1092
- transactDoc(activeTab.value, (t) =>
1093
- mutateUpdateProperty(t, path, "className", undefined),
1094
- );
1129
+ transactDoc(activeTab.value, (t) => mutateUpdateProperty(t, path, "className"));
1095
1130
  }}
1096
1131
  ></span>`
1097
1132
  : nothing}
@@ -1114,7 +1149,7 @@ export function renderPropertiesPanelTemplate(ctx: {
1114
1149
  @click=${(e: Event) => {
1115
1150
  e.stopPropagation();
1116
1151
  transactDoc(activeTab.value, (t) =>
1117
- mutateUpdateProperty(t, path, "textContent", undefined),
1152
+ mutateUpdateProperty(t, path, "textContent"),
1118
1153
  );
1119
1154
  }}
1120
1155
  ></span>`
@@ -1140,9 +1175,7 @@ export function renderPropertiesPanelTemplate(ctx: {
1140
1175
  title="Clear hidden"
1141
1176
  @click=${(e: Event) => {
1142
1177
  e.stopPropagation();
1143
- transactDoc(activeTab.value, (t) =>
1144
- mutateUpdateProperty(t, path, "hidden", undefined),
1145
- );
1178
+ transactDoc(activeTab.value, (t) => mutateUpdateProperty(t, path, "hidden"));
1146
1179
  }}
1147
1180
  ></span>`
1148
1181
  : nothing}
@@ -1150,7 +1183,7 @@ export function renderPropertiesPanelTemplate(ctx: {
1150
1183
  </div>
1151
1184
  <sp-checkbox
1152
1185
  size="s"
1153
- .checked=${live(!!node.hidden)}
1186
+ .checked=${live(Boolean(node.hidden))}
1154
1187
  @change=${(e: Event) =>
1155
1188
  transactDoc(activeTab.value, (t) =>
1156
1189
  mutateUpdateProperty(
@@ -1309,7 +1342,9 @@ export function renderPropertiesPanelTemplate(ctx: {
1309
1342
  ? (() => {
1310
1343
  const style = node.style || {};
1311
1344
  const cssProps = Object.entries(style).filter(([k]) => k.startsWith("--"));
1312
- if (cssProps.length === 0) return nothing;
1345
+ if (cssProps.length === 0) {
1346
+ return nothing;
1347
+ }
1313
1348
  return html`
1314
1349
  <sp-accordion-item
1315
1350
  label="CSS Properties"
@@ -1337,7 +1372,9 @@ export function renderPropertiesPanelTemplate(ctx: {
1337
1372
  isCustomElementDoc({ document: tab.doc.document }) && isRoot
1338
1373
  ? (() => {
1339
1374
  const parts = collectCssParts(tab.doc.document);
1340
- if (parts.length === 0) return nothing;
1375
+ if (parts.length === 0) {
1376
+ return nothing;
1377
+ }
1341
1378
  return html`
1342
1379
  <sp-accordion-item
1343
1380
  label="CSS Parts"