@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
package/src/ui/layers.ts CHANGED
@@ -1,14 +1,15 @@
1
1
  /// <reference lib="dom" />
2
- import { render as litRender, nothing, html } from "lit-html";
2
+ import { html, render as litRender, nothing } from "lit-html";
3
+ import type { TemplateResult } from "lit-html";
3
4
 
4
5
  let _popoverLayer: HTMLElement;
5
6
  let _modalLayer: HTMLElement;
6
7
  let _dialogLayer: HTMLElement;
7
8
 
8
9
  export function initLayers() {
9
- _popoverLayer = document.getElementById("layer-popover") as HTMLElement;
10
- _modalLayer = document.getElementById("layer-modal") as HTMLElement;
11
- _dialogLayer = document.getElementById("layer-dialog") as HTMLElement;
10
+ _popoverLayer = document.querySelector("#layer-popover") as HTMLElement;
11
+ _modalLayer = document.querySelector("#layer-modal") as HTMLElement;
12
+ _dialogLayer = document.querySelector("#layer-dialog") as HTMLElement;
12
13
  }
13
14
 
14
15
  /**
@@ -19,15 +20,17 @@ export function initLayers() {
19
20
  * @returns {Promise<T>}
20
21
  */
21
22
  export function showDialog<T>(
22
- templateFn: (done: (value: T) => void) => import("lit-html").TemplateResult,
23
+ templateFn: (done: (value: T) => void) => TemplateResult,
23
24
  ): Promise<T> {
24
25
  return new Promise((resolve) => {
25
26
  const slot = document.createElement("div");
26
27
  slot.style.pointerEvents = "auto";
27
- _dialogLayer.appendChild(slot);
28
+ _dialogLayer.append(slot);
28
29
  let resolved = false;
29
30
  const done = (value: T) => {
30
- if (resolved) return;
31
+ if (resolved) {
32
+ return;
33
+ }
31
34
  resolved = true;
32
35
  litRender(nothing, slot);
33
36
  slot.remove();
@@ -47,7 +50,7 @@ export function showDialog<T>(
47
50
  */
48
51
  export function showConfirmDialog(
49
52
  headline: string,
50
- message: string | import("lit-html").TemplateResult,
53
+ message: string | TemplateResult,
51
54
  opts: {
52
55
  confirmLabel?: string;
53
56
  cancelLabel?: string;
@@ -80,21 +83,21 @@ export function showConfirmDialog(
80
83
  *
81
84
  * @param {import("lit-html").TemplateResult} template
82
85
  */
83
- export function openModal(template: import("lit-html").TemplateResult) {
86
+ export function openModal(template: TemplateResult) {
84
87
  const slot = document.createElement("div");
85
88
  slot.style.pointerEvents = "auto";
86
- _modalLayer.appendChild(slot);
89
+ _modalLayer.append(slot);
87
90
  litRender(template, slot);
88
91
  return {
89
- host: slot,
90
- /** @param {import("lit-html").TemplateResult} tpl */
91
- update(tpl: import("lit-html").TemplateResult) {
92
- litRender(tpl, slot);
93
- },
94
92
  close() {
95
93
  litRender(nothing, slot);
96
94
  slot.remove();
97
95
  },
96
+ host: slot,
97
+ /** @param {import("lit-html").TemplateResult} tpl */
98
+ update(tpl: TemplateResult) {
99
+ litRender(tpl, slot);
100
+ },
98
101
  };
99
102
  }
100
103
 
@@ -109,7 +112,7 @@ export function openModal(template: import("lit-html").TemplateResult) {
109
112
  * }} [opts]
110
113
  */
111
114
  export function renderPopover(
112
- template: import("lit-html").TemplateResult,
115
+ template: TemplateResult,
113
116
  opts: {
114
117
  dismissOnOutsideClick?: boolean;
115
118
  onDismiss?: () => void;
@@ -120,7 +123,7 @@ export function renderPopover(
120
123
  slot.style.pointerEvents = "auto";
121
124
  const target =
122
125
  opts.layer === "modal" ? _modalLayer : opts.layer === "dialog" ? _dialogLayer : _popoverLayer;
123
- target.appendChild(slot);
126
+ target.append(slot);
124
127
  litRender(template, slot);
125
128
 
126
129
  let outsideClickHandler: ((e: MouseEvent) => void) | null = null;
@@ -132,16 +135,13 @@ export function renderPopover(
132
135
  }
133
136
  };
134
137
  requestAnimationFrame(() => {
135
- if (outsideClickHandler) document.addEventListener("mousedown", outsideClickHandler, true);
138
+ if (outsideClickHandler) {
139
+ document.addEventListener("mousedown", outsideClickHandler, true);
140
+ }
136
141
  });
137
142
  }
138
143
 
139
144
  const handle = {
140
- host: slot,
141
- /** @param {import("lit-html").TemplateResult} tpl */
142
- update(tpl: import("lit-html").TemplateResult) {
143
- litRender(tpl, slot);
144
- },
145
145
  dismiss() {
146
146
  if (outsideClickHandler) {
147
147
  document.removeEventListener("mousedown", outsideClickHandler, true);
@@ -149,11 +149,16 @@ export function renderPopover(
149
149
  litRender(nothing, slot);
150
150
  slot.remove();
151
151
  },
152
+ host: slot,
153
+ /** @param {import("lit-html").TemplateResult} tpl */
154
+ update(tpl: TemplateResult) {
155
+ litRender(tpl, slot);
156
+ },
152
157
  };
153
158
  return handle;
154
159
  }
155
160
 
156
- const _namedSlots: Map<string, HTMLElement> = new Map();
161
+ const _namedSlots = new Map<string, HTMLElement>();
157
162
 
158
163
  /**
159
164
  * Get or create a named slot in a layer. Useful for persistent popovers like zoom indicator.
@@ -165,14 +170,16 @@ const _namedSlots: Map<string, HTMLElement> = new Map();
165
170
  export function getLayerSlot(layer: "popover" | "modal" | "dialog", id: string) {
166
171
  const key = `${layer}:${id}`;
167
172
  let slot = _namedSlots.get(key);
168
- if (slot && slot.parentElement) return slot;
173
+ if (slot && slot.parentElement) {
174
+ return slot;
175
+ }
169
176
 
170
177
  slot = document.createElement("div");
171
178
  slot.style.pointerEvents = "auto";
172
179
  const target =
173
180
  (layer === "popover" ? _popoverLayer : layer === "modal" ? _modalLayer : _dialogLayer) ||
174
181
  document.body;
175
- target.appendChild(slot);
182
+ target.append(slot);
176
183
  _namedSlots.set(key, slot);
177
184
  return slot;
178
185
  }
@@ -64,9 +64,9 @@ async function collectMedia(
64
64
  const ext = dot > 0 ? entry.name.slice(dot).toLowerCase() : "";
65
65
  if (MEDIA_EXTENSIONS.has(ext)) {
66
66
  results.push({
67
- path: `/${entry.path}`,
68
- name: entry.name,
69
67
  isImage: IMAGE_EXTENSIONS.has(ext),
68
+ name: entry.name,
69
+ path: `/${entry.path}`,
70
70
  });
71
71
  }
72
72
  }
@@ -78,7 +78,9 @@ async function collectMedia(
78
78
  }
79
79
 
80
80
  async function loadMediaCache() {
81
- if (mediaCacheLoaded) return;
81
+ if (mediaCacheLoaded) {
82
+ return;
83
+ }
82
84
  const platform = getPlatform();
83
85
  mediaCache = await collectMedia("public", platform);
84
86
  // Strip "public/" prefix so paths match production (public/ contents served at root)
@@ -137,7 +139,9 @@ function onPopoverOutsideClick(e: MouseEvent) {
137
139
  function renderMediaPickerPopover() {
138
140
  const host = getLayerSlot("popover", "media-picker");
139
141
  const rect = _popoverAnchorEl?.getBoundingClientRect();
140
- if (!rect) return;
142
+ if (!rect) {
143
+ return;
144
+ }
141
145
 
142
146
  const query = _popoverFilter.toLowerCase();
143
147
  const filtered = query
@@ -148,7 +152,7 @@ function renderMediaPickerPopover() {
148
152
  const options = filtered.slice(0, 50);
149
153
 
150
154
  // Compute initial position below the anchor
151
- let left = rect.left;
155
+ let { left } = rect;
152
156
  let top = rect.bottom + 4;
153
157
 
154
158
  // Estimate popover dimensions for viewport clamping before first paint
@@ -195,7 +199,7 @@ function renderMediaPickerPopover() {
195
199
  dismissMediaPickerPopover();
196
200
  }}
197
201
  >
198
- ${options.length
202
+ ${options.length > 0
199
203
  ? options.map(
200
204
  (m) => html`
201
205
  <sp-menu-item value=${m.path}>
@@ -252,7 +256,9 @@ function renderMediaPickerPopover() {
252
256
  }
253
257
  }
254
258
 
255
- if (_popoverFilterEl) _popoverFilterEl.focus();
259
+ if (_popoverFilterEl) {
260
+ _popoverFilterEl.focus();
261
+ }
256
262
  });
257
263
  }
258
264
 
@@ -6,11 +6,11 @@
6
6
  * localStorage so they survive page reloads.
7
7
  */
8
8
 
9
- import { view, applyPanelCollapse } from "../view";
9
+ import { applyPanelCollapse, view } from "../view";
10
10
 
11
11
  const STORAGE_KEY = "jx-studio-panel-widths";
12
12
  const MIN_WIDTH = 160;
13
- const MAX_RATIO = 0.5; // max 50% of viewport
13
+ const MAX_RATIO = 0.5; // Max 50% of viewport
14
14
  const DEFAULT_LEFT = 240;
15
15
  const DEFAULT_RIGHT = 280;
16
16
 
@@ -20,13 +20,21 @@ const root = document.documentElement;
20
20
 
21
21
  try {
22
22
  const saved = JSON.parse(localStorage.getItem(STORAGE_KEY) || "{}");
23
- if (saved.left) root.style.setProperty("--panel-w-left", `${saved.left}px`);
24
- if (saved.right) root.style.setProperty("--panel-w-right", `${saved.right}px`);
25
- if (saved.leftCollapsed) view.leftPanelCollapsed = true;
26
- if (saved.rightCollapsed) view.rightPanelCollapsed = true;
23
+ if (saved.left) {
24
+ root.style.setProperty("--panel-w-left", `${saved.left}px`);
25
+ }
26
+ if (saved.right) {
27
+ root.style.setProperty("--panel-w-right", `${saved.right}px`);
28
+ }
29
+ if (saved.leftCollapsed) {
30
+ view.leftPanelCollapsed = true;
31
+ }
32
+ if (saved.rightCollapsed) {
33
+ view.rightPanelCollapsed = true;
34
+ }
27
35
  applyPanelCollapse();
28
36
  } catch {
29
- // ignore
37
+ // Ignore
30
38
  }
31
39
 
32
40
  // ─── Setup handles ───────────────────────────────────────────────────────────
@@ -50,17 +58,20 @@ function setupHandle(
50
58
  try {
51
59
  handle.setPointerCapture(e.pointerId);
52
60
  } catch {
53
- /* synthetic events */
61
+ /* Synthetic events */
54
62
  }
55
63
  handle.classList.add("dragging");
56
64
  document.body.style.userSelect = "none";
57
65
 
58
- const current = parseInt(getComputedStyle(root).getPropertyValue(cssVar)) || defaultWidth;
59
- drag = { startX: e.clientX, startWidth: current };
66
+ const current =
67
+ Number.parseInt(getComputedStyle(root).getPropertyValue(cssVar), 10) || defaultWidth;
68
+ drag = { startWidth: current, startX: e.clientX };
60
69
  });
61
70
 
62
71
  handle.addEventListener("pointermove", (e) => {
63
- if (!drag) return;
72
+ if (!drag) {
73
+ return;
74
+ }
64
75
  const delta = side === "left" ? e.clientX - drag.startX : drag.startX - e.clientX;
65
76
  const maxWidth = window.innerWidth * MAX_RATIO;
66
77
  const newWidth = Math.round(Math.min(maxWidth, Math.max(MIN_WIDTH, drag.startWidth + delta)));
@@ -68,12 +79,14 @@ function setupHandle(
68
79
  });
69
80
 
70
81
  handle.addEventListener("pointerup", (e) => {
71
- if (!drag) return;
82
+ if (!drag) {
83
+ return;
84
+ }
72
85
  drag = null;
73
86
  try {
74
87
  handle.releasePointerCapture(e.pointerId);
75
88
  } catch {
76
- /* synthetic events */
89
+ /* Synthetic events */
77
90
  }
78
91
  handle.classList.remove("dragging");
79
92
  document.body.style.userSelect = "";
@@ -87,20 +100,26 @@ function setupHandle(
87
100
  }
88
101
 
89
102
  function persistWidths() {
90
- const left = parseInt(getComputedStyle(root).getPropertyValue("--panel-w-left")) || DEFAULT_LEFT;
103
+ const left =
104
+ Number.parseInt(getComputedStyle(root).getPropertyValue("--panel-w-left"), 10) || DEFAULT_LEFT;
91
105
  const right =
92
- parseInt(getComputedStyle(root).getPropertyValue("--panel-w-right")) || DEFAULT_RIGHT;
106
+ Number.parseInt(getComputedStyle(root).getPropertyValue("--panel-w-right"), 10) ||
107
+ DEFAULT_RIGHT;
93
108
  try {
94
109
  localStorage.setItem(STORAGE_KEY, JSON.stringify({ left, right }));
95
110
  } catch {
96
- // storage full or unavailable
111
+ // Storage full or unavailable
97
112
  }
98
113
  }
99
114
 
100
115
  // ─── Initialize ──────────────────────────────────────────────────────────────
101
116
 
102
- const resizeLeft = document.getElementById("resize-left");
103
- const resizeRight = document.getElementById("resize-right");
117
+ const resizeLeft = document.querySelector<HTMLElement>("#resize-left");
118
+ const resizeRight = document.querySelector<HTMLElement>("#resize-right");
104
119
 
105
- if (resizeLeft) setupHandle(resizeLeft, "--panel-w-left", "left", DEFAULT_LEFT);
106
- if (resizeRight) setupHandle(resizeRight, "--panel-w-right", "right", DEFAULT_RIGHT);
120
+ if (resizeLeft) {
121
+ setupHandle(resizeLeft, "--panel-w-left", "left", DEFAULT_LEFT);
122
+ }
123
+ if (resizeRight) {
124
+ setupHandle(resizeRight, "--panel-w-right", "right", DEFAULT_RIGHT);
125
+ }
@@ -148,7 +148,7 @@ import { JxColorPopover } from "./color-selector";
148
148
  import { IconChevron100 } from "@spectrum-web-components/icons-ui/src/elements/IconChevron100.js";
149
149
 
150
150
  // Register all components. Using defineElement from Spectrum's base package
151
- // ensures duplicate registration is handled gracefully.
151
+ // Ensures duplicate registration is handled gracefully.
152
152
  import { defineElement } from "@spectrum-web-components/base/src/define-element.js";
153
153
 
154
154
  const components = [
@@ -283,7 +283,9 @@ const components = [
283
283
  ];
284
284
 
285
285
  for (const [tag, ctor] of components as [string, CustomElementConstructor][]) {
286
- if (!customElements.get(tag)) defineElement(tag, ctor);
286
+ if (!customElements.get(tag)) {
287
+ defineElement(tag, ctor);
288
+ }
287
289
  }
288
290
 
289
291
  // Register theme fragments (these are also side-effect-only in the original modules)
@@ -9,7 +9,7 @@
9
9
  import { html, nothing } from "lit-html";
10
10
  import { live } from "lit-html/directives/live.js";
11
11
  import { classMap } from "lit-html/directives/class-map.js";
12
- import { debouncedStyleCommit, cancelStyleDebounce } from "../store";
12
+ import { cancelStyleDebounce, debouncedStyleCommit } from "../store";
13
13
 
14
14
  export const UNIT_RE = /^(-?[\d.]+)(px|rem|em|%|vw|vh|svw|svh|dvh|ms|s|fr|ch|ex|deg)?$/;
15
15
 
@@ -28,7 +28,7 @@ export function renderUnitSelector(
28
28
  /** @type {string} */ prop: string,
29
29
  /** @type {string | number | undefined} */ value: string | number | undefined,
30
30
  /** @type {(val: string) => void} */ onChange: (val: string) => void,
31
- /** @type {string} */ placeholder: string = "",
31
+ /** @type {string} */ placeholder = "",
32
32
  ) {
33
33
  const units = (entry.$units || []) as string[];
34
34
  const keywords = (entry.$keywords || []) as string[];
@@ -39,12 +39,16 @@ export function renderUnitSelector(
39
39
 
40
40
  const currentUnit = isKeyword ? units[0] || "" : match ? match[2] || "" : units[0] || "";
41
41
  let displayValue;
42
- if (isKeyword) displayValue = strVal;
43
- else if (match) displayValue = match[1];
44
- else if (strVal !== "") {
45
- const num = parseFloat(strVal);
46
- displayValue = isNaN(num) ? strVal : String(num);
47
- } else displayValue = "";
42
+ if (isKeyword) {
43
+ displayValue = strVal;
44
+ } else if (match) {
45
+ [, displayValue] = match;
46
+ } else if (strVal !== "") {
47
+ const num = Number.parseFloat(strVal);
48
+ displayValue = Number.isNaN(num) ? strVal : String(num);
49
+ } else {
50
+ displayValue = "";
51
+ }
48
52
 
49
53
  // Parse placeholder so inherited values display as "500" not "500px"
50
54
  const placeholderMatch = placeholder.match(UNIT_RE);
@@ -60,8 +64,11 @@ export function renderUnitSelector(
60
64
  onChange("");
61
65
  return;
62
66
  }
63
- if (isNumericVal(val)) onChange(units.length > 0 ? val + currentUnit : val);
64
- else onChange(val);
67
+ if (isNumericVal(val)) {
68
+ onChange(units.length > 0 ? val + currentUnit : val);
69
+ } else {
70
+ onChange(val);
71
+ }
65
72
  };
66
73
 
67
74
  return html`
@@ -100,7 +107,9 @@ export function renderUnitSelector(
100
107
  } else if (units.includes(chosen)) {
101
108
  const curMatch = String(value ?? "").match(UNIT_RE);
102
109
  const numPart = curMatch ? curMatch[1] : "";
103
- if (numPart) onChange(numPart + chosen);
110
+ if (numPart) {
111
+ onChange(numPart + chosen);
112
+ }
104
113
  }
105
114
  }}
106
115
  >
@@ -23,10 +23,10 @@ type ComboOption = { value: string; label: string; style?: string } | { divider:
23
23
 
24
24
  export class JxValueSelector extends LitElement {
25
25
  static properties = {
26
- value: { type: String },
26
+ options: { attribute: false },
27
27
  placeholder: { type: String },
28
28
  size: { type: String },
29
- options: { attribute: false },
29
+ value: { type: String },
30
30
  };
31
31
 
32
32
  declare value: string;
@@ -41,7 +41,7 @@ export class JxValueSelector extends LitElement {
41
41
  this.placeholder = "";
42
42
  this.size = "s";
43
43
  this.options = [];
44
- this._menuId = "jx-combo-" + Math.random().toString(36).slice(2, 8);
44
+ this._menuId = `jx-combo-${Math.random().toString(36).slice(2, 8)}`;
45
45
  }
46
46
 
47
47
  /** No shadow DOM — render directly into light DOM */
@@ -52,14 +52,16 @@ export class JxValueSelector extends LitElement {
52
52
  /** Check if current value matches a predefined option */
53
53
  get _isPicker() {
54
54
  return (
55
- !!this.value &&
55
+ Boolean(this.value) &&
56
56
  this.options.some((o: ComboOption) => !("divider" in o) && o.value === this.value)
57
57
  );
58
58
  }
59
59
 
60
60
  /** Get the selected option's style string for the picker button preview */
61
61
  get _selectedStyle() {
62
- if (!this._isPicker) return "";
62
+ if (!this._isPicker) {
63
+ return "";
64
+ }
63
65
  const opt = this.options.find((o: ComboOption) => !("divider" in o) && o.value === this.value);
64
66
  return (opt as { value: string; label: string; style?: string } | undefined)?.style || "";
65
67
  }
@@ -85,7 +87,9 @@ export class JxValueSelector extends LitElement {
85
87
  /** Combobox mode: sp-menu @change handler */
86
88
  _handleMenuChange(e: Event) {
87
89
  e.stopPropagation();
88
- if (!(e.target as HTMLInputElement).value) return;
90
+ if (!(e.target as HTMLInputElement).value) {
91
+ return;
92
+ }
89
93
  this.value = (e.target as HTMLInputElement).value;
90
94
  this.dispatchEvent(new Event("change", { bubbles: true, composed: true }));
91
95
  }
@@ -102,7 +106,9 @@ export class JxValueSelector extends LitElement {
102
106
  const group = this.querySelector(".jx-combobox-group");
103
107
  const w = group ? (group as HTMLElement).offsetWidth : 0;
104
108
  const popover = (e.target as HTMLElement).querySelector("sp-popover");
105
- if (popover && w) (popover as HTMLElement).style.minWidth = `${w}px`;
109
+ if (popover && w) {
110
+ (popover as HTMLElement).style.minWidth = `${w}px`;
111
+ }
106
112
  }
107
113
 
108
114
  render() {
package/src/ui/widgets.ts CHANGED
@@ -19,6 +19,7 @@ import { renderColorSelector } from "./color-selector";
19
19
  import { renderUnitSelector } from "./unit-selector";
20
20
  import { renderButtonGroup } from "./button-group";
21
21
  import { renderMediaPicker } from "./media-picker";
22
+ import type { TemplateResult } from "lit-html";
22
23
 
23
24
  /**
24
25
  * Render a plain text input widget.
@@ -33,7 +34,7 @@ export function renderTextInput(
33
34
  prop: string,
34
35
  value: string | number | undefined,
35
36
  onChange: (val: string) => void,
36
- placeholder: string = "",
37
+ placeholder = "",
37
38
  ) {
38
39
  return html`
39
40
  <sp-textfield
@@ -62,7 +63,7 @@ export function renderNumberInput(
62
63
  prop: string,
63
64
  value: string | number | undefined,
64
65
  onChange: (val: string | number) => void,
65
- placeholder: string = "",
66
+ placeholder = "",
66
67
  ) {
67
68
  const minimum = entry.minimum as number | undefined;
68
69
  const maximum = entry.maximum as number | undefined;
@@ -77,8 +78,11 @@ export function renderNumberInput(
77
78
  step=${ifDefined(maximum !== undefined && maximum <= 1 ? 0.1 : undefined)}
78
79
  @change=${debouncedStyleCommit(`num:${prop}`, 400, (e: Event) => {
79
80
  const v = (e.target as HTMLInputElement & { value: number | undefined }).value;
80
- if (v === undefined || isNaN(v)) onChange("");
81
- else onChange(Number(v));
81
+ if (v === undefined || Number.isNaN(v)) {
82
+ onChange("");
83
+ } else {
84
+ onChange(Number(v));
85
+ }
82
86
  })}
83
87
  ></sp-number-field>
84
88
  `;
@@ -120,37 +124,49 @@ export function widgetForType(
120
124
  renderSelect?: (
121
125
  entry: Record<string, unknown>,
122
126
  prop: string,
123
- value: any,
127
+ value: string | number | undefined,
124
128
  onCommit: (val: string | number) => void,
125
- ) => import("lit-html").TemplateResult;
129
+ ) => TemplateResult;
126
130
  renderCombobox?: (
127
131
  entry: Record<string, unknown>,
128
132
  prop: string,
129
- value: any,
133
+ value: string | number | undefined,
130
134
  onCommit: (val: string | number) => void,
131
- ) => import("lit-html").TemplateResult;
135
+ ) => TemplateResult;
132
136
  } = {},
133
137
  ) {
134
138
  switch (type) {
135
- case "button-group":
139
+ case "button-group": {
136
140
  return renderButtonGroup(entry, prop, value, onCommit);
137
- case "color":
141
+ }
142
+ case "color": {
138
143
  return renderColorSelector(prop, value, onCommit);
139
- case "number-unit":
144
+ }
145
+ case "number-unit": {
140
146
  return renderUnitSelector(entry, prop, value, onCommit, opts.placeholder);
141
- case "number":
147
+ }
148
+ case "number": {
142
149
  return renderNumberInput(entry, prop, value, onCommit, opts.placeholder);
143
- case "media":
150
+ }
151
+ case "media": {
144
152
  return renderMediaPicker(prop, String(value ?? ""), onCommit);
145
- case "select":
153
+ }
154
+ case "select": {
146
155
  // Allow caller to override select rendering (e.g. for typography preview)
147
- if (opts.renderSelect) return opts.renderSelect(entry, prop, value, onCommit);
156
+ if (opts.renderSelect) {
157
+ return opts.renderSelect(entry, prop, value, onCommit);
158
+ }
148
159
  return renderTextInput(prop, value, onCommit, opts.placeholder);
149
- case "combobox":
160
+ }
161
+ case "combobox": {
150
162
  // Allow caller to override combobox rendering (e.g. for font family)
151
- if (opts.renderCombobox) return opts.renderCombobox(entry, prop, value, onCommit);
163
+ if (opts.renderCombobox) {
164
+ return opts.renderCombobox(entry, prop, value, onCommit);
165
+ }
152
166
  return renderTextInput(prop, value, onCommit, opts.placeholder);
153
- default:
167
+ }
168
+ default: {
154
169
  return renderTextInput(prop, value, onCommit, opts.placeholder);
170
+ }
155
171
  }
156
172
  }