@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
@@ -5,19 +5,20 @@
5
5
  */
6
6
 
7
7
  import { html, nothing } from "lit-html";
8
+ import { getNestedStyle } from "@jxsuite/schema/guards";
8
9
  import { live } from "lit-html/directives/live.js";
9
10
  import { ifDefined } from "lit-html/directives/if-defined.js";
10
- import { getNodeAtPath, COMMON_SELECTORS, isNestedSelector, debouncedStyleCommit } from "../store";
11
+ import { COMMON_SELECTORS, debouncedStyleCommit, getNodeAtPath, isNestedSelector } from "../store";
11
12
  import { activeTab } from "../workspace/workspace";
12
13
  import { selectStylebookTag } from "./stylebook-panel";
13
14
  import {
14
- transactDoc,
15
- mutateUpdateStyle,
15
+ mutateUpdateMediaNestedStyle,
16
+ mutateUpdateMediaNestedStylePath,
16
17
  mutateUpdateMediaStyle,
17
18
  mutateUpdateNestedStyle,
18
- mutateUpdateMediaNestedStyle,
19
19
  mutateUpdateNestedStylePath,
20
- mutateUpdateMediaNestedStylePath,
20
+ mutateUpdateStyle,
21
+ transactDoc,
21
22
  } from "../tabs/transact";
22
23
  import { inferInputType, propLabel } from "../utils/studio-utils";
23
24
  import { renderFieldRow } from "../ui/field-row";
@@ -26,21 +27,21 @@ import { getEffectiveMedia, getEffectiveStyle } from "../site-context";
26
27
  import { computeInheritedStyle } from "../utils/inherited-style";
27
28
  import { mediaDisplayName } from "./shared";
28
29
  import {
29
- cssMeta,
30
- getCssInitialMap,
31
30
  allConditionsPass,
32
31
  autoOpenSections,
33
- getLonghands,
34
- expandShorthand,
32
+ compressBorderSide,
35
33
  compressShorthand,
34
+ cssMeta,
36
35
  expandBorderSide,
37
- compressBorderSide,
36
+ expandShorthand,
37
+ getCssInitialMap,
38
+ getLonghands,
38
39
  } from "./style-utils";
39
40
  import { widgetForType } from "./style-inputs";
40
41
 
41
42
  import type { Tab } from "../tabs/tab";
42
43
  import type { JxPath } from "../state";
43
- import type { JxMutableNode } from "@jxsuite/schema/types";
44
+ import type { JxMutableNode, JxStyle } from "@jxsuite/schema/types";
44
45
 
45
46
  interface CssLonghand {
46
47
  name: string;
@@ -52,7 +53,7 @@ type CssPropertyEntry = Record<string, unknown>;
52
53
  type StyleMutateFn = (
53
54
  t: Tab,
54
55
  prop: string,
55
- val: string | Record<string, unknown> | undefined,
56
+ val?: string | Record<string, unknown> | undefined,
56
57
  ) => void;
57
58
 
58
59
  /**
@@ -69,18 +70,19 @@ function isTagPath(selector: string) {
69
70
  /**
70
71
  * Resolve a style object by traversing a nested tag path. e.g., "table th" → style["table"]["th"]
71
72
  *
72
- * @param {Record<string, unknown>} style
73
+ * @param {JxStyle} style
73
74
  * @param {string} tagPath
74
- * @returns {Record<string, unknown>}
75
+ * @returns {JxStyle}
75
76
  */
76
- function resolveNestedTagStyle(style: Record<string, unknown>, tagPath: string) {
77
- const parts = tagPath.split(" ");
78
- let obj: unknown = style;
79
- for (const part of parts) {
80
- if (!obj || typeof obj !== "object") return {};
81
- obj = (obj as Record<string, unknown>)[part];
77
+ function resolveNestedTagStyle(style: JxStyle, tagPath: string): JxStyle {
78
+ let obj: JxStyle | undefined = style;
79
+ for (const part of tagPath.split(" ")) {
80
+ obj = getNestedStyle(obj, part);
81
+ if (!obj) {
82
+ return {};
83
+ }
82
84
  }
83
- return obj && typeof obj === "object" ? (obj as Record<string, unknown>) : {};
85
+ return obj;
84
86
  }
85
87
 
86
88
  // ─── Row renderers ──────────────────────────────────────────────────────────
@@ -144,9 +146,13 @@ function renderShorthandRow(
144
146
  @click=${(e: Event) => {
145
147
  e.stopPropagation();
146
148
  transactDoc(activeTab.value, (t) => {
147
- if (shortVal !== undefined) mutateFn(t, shortProp, undefined);
149
+ if (shortVal !== undefined) {
150
+ mutateFn(t, shortProp);
151
+ }
148
152
  for (const l of longhands) {
149
- if (style[l.name] !== undefined) mutateFn(t, l.name, undefined);
153
+ if (style[l.name] !== undefined) {
154
+ mutateFn(t, l.name);
155
+ }
150
156
  }
151
157
  });
152
158
  }}
@@ -168,7 +174,9 @@ function renderShorthandRow(
168
174
  @input=${debouncedStyleCommit(`short:${shortProp}`, 400, (e: Event) => {
169
175
  transactDoc(activeTab.value, (t) => {
170
176
  for (const l of longhands) {
171
- if (style[l.name] !== undefined) mutateFn(t, l.name, undefined);
177
+ if (style[l.name] !== undefined) {
178
+ mutateFn(t, l.name);
179
+ }
172
180
  }
173
181
  mutateFn(t, shortProp, (e.target as HTMLInputElement).value || undefined);
174
182
  });
@@ -219,7 +227,9 @@ function renderShorthandRow(
219
227
  );
220
228
  transactDoc(activeTab.value, (t) => {
221
229
  for (const l of longhands) {
222
- if (style[l.name] !== undefined) mutateFn(t, l.name, undefined);
230
+ if (style[l.name] !== undefined) {
231
+ mutateFn(t, l.name);
232
+ }
223
233
  }
224
234
  mutateFn(t, shortProp, compress(vals as string[]));
225
235
  });
@@ -232,7 +242,7 @@ function renderShorthandRow(
232
242
  inferInputType(lEntry),
233
243
  lEntry,
234
244
  name,
235
- lVal,
245
+ lVal as string,
236
246
  (newVal: string) => {
237
247
  const vals = longhands.map((l: CssLonghand, i: number) =>
238
248
  i === idx
@@ -241,7 +251,9 @@ function renderShorthandRow(
241
251
  );
242
252
  transactDoc(activeTab.value, (t) => {
243
253
  for (const l of longhands) {
244
- if (style[l.name] !== undefined) mutateFn(t, l.name, undefined);
254
+ if (style[l.name] !== undefined) {
255
+ mutateFn(t, l.name);
256
+ }
245
257
  }
246
258
  mutateFn(t, shortProp, compress(vals as string[]));
247
259
  });
@@ -264,20 +276,20 @@ function renderShorthandRow(
264
276
  * @param {JxMutableNode} node
265
277
  * @param {string | null} activeMediaTab
266
278
  * @param {string | null} activeSelector
267
- * @param {Record<string, unknown>} [effectiveStyle]
279
+ * @param {JxStyle} [effectiveStyle]
268
280
  */
269
281
  function styleSidebarTemplate(
270
282
  node: JxMutableNode,
271
283
  activeMediaTab: string | null,
272
284
  activeSelector: string | null,
273
- effectiveStyle?: Record<string, unknown>,
285
+ effectiveStyle?: JxStyle,
274
286
  ) {
275
287
  const tab = activeTab.value!;
276
288
  const sel = tab.session.selection as JxPath;
277
289
  const style = effectiveStyle || node.style || {};
278
290
  const { sizeBreakpoints } = parseMediaEntries(getEffectiveMedia(tab.doc.document.$media));
279
291
  const mediaNames = sizeBreakpoints.map((bp) => bp.name);
280
- const mediaTab = activeMediaTab;
292
+ const mediaTab = activeMediaTab || null;
281
293
 
282
294
  // ── Media tabs template ──────────────────────────────────────────────────
283
295
  const mediaTabsT =
@@ -304,7 +316,7 @@ function styleSidebarTemplate(
304
316
 
305
317
  // ── Selector dropdown ──────────────────────────────────────────────────────
306
318
  const contextStyle = mediaTab ? (style[`@${mediaTab}`] as Record<string, unknown>) || {} : style;
307
- const existingSelectors = Object.keys(contextStyle).filter(isNestedSelector);
319
+ const existingSelectors = Object.keys(contextStyle).filter((s) => isNestedSelector(s));
308
320
  const existingSet = new Set(existingSelectors);
309
321
  const commonSet = new Set(COMMON_SELECTORS);
310
322
  const extraSelectors = existingSelectors.filter((s) => !commonSet.has(s));
@@ -332,11 +344,13 @@ function styleSidebarTemplate(
332
344
  inp.type = "text";
333
345
  inp.className = "selector-custom-input";
334
346
  inp.placeholder = ":hover, .child, &.active, [attr]";
335
- bar.appendChild(inp);
347
+ bar.append(inp);
336
348
  inp.focus();
337
349
  let done = false;
338
350
  const finish = (accept: boolean) => {
339
- if (done) return;
351
+ if (done) {
352
+ return;
353
+ }
340
354
  done = true;
341
355
  const v = inp.value.trim();
342
356
  inp.remove();
@@ -346,8 +360,11 @@ function styleSidebarTemplate(
346
360
  }
347
361
  };
348
362
  inp.addEventListener("keydown", (ev) => {
349
- if (ev.key === "Enter") finish(true);
350
- else if (ev.key === "Escape") finish(false);
363
+ if (ev.key === "Enter") {
364
+ finish(true);
365
+ } else if (ev.key === "Escape") {
366
+ finish(false);
367
+ }
351
368
  });
352
369
  inp.addEventListener("blur", () => finish(inp.value.trim().length > 0));
353
370
  return;
@@ -409,11 +426,10 @@ function styleSidebarTemplate(
409
426
  `;
410
427
 
411
428
  // ── Determine the active style object ──────────────────────────────────────
412
- let activeStyle: Record<string, unknown>;
413
- let commitStyle: (prop: string, val: string | Record<string, unknown> | undefined) => void;
429
+ let activeStyle: JxStyle;
414
430
  let commitMutate: StyleMutateFn;
415
431
  if (activeSelector && isTagPath(activeSelector) && mediaTab && mediaNames.length > 0) {
416
- const mediaObj = (style[`@${mediaTab}`] as Record<string, unknown>) || {};
432
+ const mediaObj = getNestedStyle(style, `@${mediaTab}`) ?? {};
417
433
  activeStyle = resolveNestedTagStyle(mediaObj, activeSelector);
418
434
  const stylePath = activeSelector.split(" ");
419
435
  commitMutate = (t: Tab, prop: string, val: string | Record<string, unknown> | undefined) =>
@@ -425,18 +441,14 @@ function styleSidebarTemplate(
425
441
  prop,
426
442
  val as string | undefined,
427
443
  );
428
- commitStyle = (prop: string, val: string | Record<string, unknown> | undefined) =>
429
- transactDoc(activeTab.value, (t) => commitMutate(t, prop, val));
430
444
  } else if (activeSelector && isTagPath(activeSelector)) {
431
445
  activeStyle = resolveNestedTagStyle(style, activeSelector);
432
446
  const stylePath = activeSelector.split(" ");
433
447
  commitMutate = (t: Tab, prop: string, val: string | Record<string, unknown> | undefined) =>
434
448
  mutateUpdateNestedStylePath(t, sel, stylePath, prop, val as string | undefined);
435
- commitStyle = (prop: string, val: string | Record<string, unknown> | undefined) =>
436
- transactDoc(activeTab.value, (t) => commitMutate(t, prop, val));
437
449
  } else if (activeSelector && mediaTab && mediaNames.length > 0) {
438
- const mediaObj = (style[`@${mediaTab}`] || {}) as Record<string, unknown>;
439
- activeStyle = (mediaObj[activeSelector] as Record<string, unknown>) || {};
450
+ const mediaObj = getNestedStyle(style, `@${mediaTab}`) ?? {};
451
+ activeStyle = getNestedStyle(mediaObj, activeSelector) ?? {};
440
452
  commitMutate = (t: Tab, prop: string, val: string | Record<string, unknown> | undefined) =>
441
453
  mutateUpdateMediaNestedStyle(
442
454
  t,
@@ -446,33 +458,27 @@ function styleSidebarTemplate(
446
458
  prop,
447
459
  val as string | undefined,
448
460
  );
449
- commitStyle = (prop: string, val: string | Record<string, unknown> | undefined) =>
450
- transactDoc(activeTab.value, (t) => commitMutate(t, prop, val));
451
461
  } else if (activeSelector) {
452
- activeStyle = (style[activeSelector] as Record<string, unknown>) || {};
462
+ activeStyle = getNestedStyle(style, activeSelector) ?? {};
453
463
  commitMutate = (t: Tab, prop: string, val: string | Record<string, unknown> | undefined) =>
454
464
  mutateUpdateNestedStyle(t, sel, activeSelector, prop, val as string | undefined);
455
- commitStyle = (prop: string, val: string | Record<string, unknown> | undefined) =>
456
- transactDoc(activeTab.value, (t) => commitMutate(t, prop, val));
457
- } else if (mediaTab !== null && mediaNames.length > 0) {
458
- activeStyle = {};
459
- for (const [p, v] of Object.entries((style[`@${mediaTab}`] as Record<string, unknown>) || {})) {
460
- if (typeof v !== "object") activeStyle[p] = v;
461
- }
462
- commitMutate = (t: Tab, prop: string, val: string | Record<string, unknown> | undefined) =>
463
- mutateUpdateMediaStyle(t, sel, mediaTab, prop, val as string | undefined);
464
- commitStyle = (prop: string, val: string | Record<string, unknown> | undefined) =>
465
- transactDoc(activeTab.value, (t) => commitMutate(t, prop, val));
466
465
  } else {
467
466
  activeStyle = {};
468
- for (const [p, v] of Object.entries(style)) {
469
- if (typeof v !== "object") activeStyle[p] = v;
467
+ const inMediaTab = mediaTab !== null && mediaNames.length > 0;
468
+ const flatSource = inMediaTab ? (getNestedStyle(style, `@${mediaTab}`) ?? {}) : style;
469
+ for (const [p, v] of Object.entries(flatSource)) {
470
+ if (typeof v !== "object") {
471
+ activeStyle[p] = v;
472
+ }
470
473
  }
471
- commitMutate = (t: Tab, prop: string, val: string | Record<string, unknown> | undefined) =>
472
- mutateUpdateStyle(t, sel, prop, val as string | undefined);
473
- commitStyle = (prop: string, val: string | Record<string, unknown> | undefined) =>
474
- transactDoc(activeTab.value, (t) => commitMutate(t, prop, val));
474
+ commitMutate = inMediaTab
475
+ ? (t: Tab, prop: string, val: string | Record<string, unknown> | undefined) =>
476
+ mutateUpdateMediaStyle(t, sel, mediaTab, prop, val as string | undefined)
477
+ : (t: Tab, prop: string, val: string | Record<string, unknown> | undefined) =>
478
+ mutateUpdateStyle(t, sel, prop, val as string | undefined);
475
479
  }
480
+ const commitStyle = (prop: string, val?: string | Record<string, unknown> | undefined) =>
481
+ transactDoc(activeTab.value, (t) => commitMutate(t, prop, val));
476
482
 
477
483
  // ── Compute inherited style from higher breakpoints ──────────────────────
478
484
  const inheritedStyle: Record<string, string | number> = computeInheritedStyle(
@@ -490,12 +496,16 @@ function styleSidebarTemplate(
490
496
 
491
497
  // Partition properties into sections
492
498
  const sectionProps: Record<string, { prop: string; entry: CssPropertyEntry }[]> = {};
493
- for (const sec of cssMeta.$sections) sectionProps[sec.key] = [];
499
+ for (const sec of cssMeta.$sections) {
500
+ sectionProps[sec.key] = [];
501
+ }
494
502
 
495
503
  for (const [prop, entry] of Object.entries(cssMeta.$defs) as [string, CssPropertyEntry][]) {
496
- if (typeof (entry as Record<string, unknown>).$shorthand === "string") continue;
504
+ if (typeof (entry as Record<string, unknown>).$shorthand === "string") {
505
+ continue;
506
+ }
497
507
  const sec = ((entry as Record<string, unknown>).$section as string) || "other";
498
- sectionProps[sec].push({ prop, entry });
508
+ sectionProps[sec].push({ entry, prop });
499
509
  }
500
510
  for (const sec of cssMeta.$sections) {
501
511
  sectionProps[sec.key].sort(
@@ -512,7 +522,9 @@ function styleSidebarTemplate(
512
522
  for (const prop of Object.keys(activeStyle)) {
513
523
  if (!(cssMeta.$defs as Record<string, unknown>)[prop]) {
514
524
  const val = activeStyle[prop];
515
- if (val !== null && typeof val === "object") continue;
525
+ if (val !== null && typeof val === "object") {
526
+ continue;
527
+ }
516
528
  otherProps.push(prop);
517
529
  }
518
530
  }
@@ -537,7 +549,9 @@ function styleSidebarTemplate(
537
549
 
538
550
  const sectionActiveProps = entries.filter(
539
551
  ({ prop, entry }: { prop: string; entry: CssPropertyEntry }) => {
540
- if (activeStyle[prop] !== undefined) return true;
552
+ if (activeStyle[prop] !== undefined) {
553
+ return true;
554
+ }
541
555
  if (inferInputType(entry) === "shorthand") {
542
556
  return (getLonghands(prop) as CssLonghand[]).some(
543
557
  (l: CssLonghand) => activeStyle[l.name] !== undefined,
@@ -573,11 +587,14 @@ function styleSidebarTemplate(
573
587
  e.preventDefault();
574
588
  transactDoc(activeTab.value, (t) => {
575
589
  for (const { prop, entry } of sectionActiveProps) {
576
- if (activeStyle[prop] !== undefined) commitMutate(t, prop, undefined);
590
+ if (activeStyle[prop] !== undefined) {
591
+ commitMutate(t, prop);
592
+ }
577
593
  if (inferInputType(entry) === "shorthand") {
578
594
  for (const l of getLonghands(prop) as CssLonghand[]) {
579
- if (activeStyle[l.name] !== undefined)
580
- commitMutate(t, l.name, undefined);
595
+ if (activeStyle[l.name] !== undefined) {
596
+ commitMutate(t, l.name);
597
+ }
581
598
  }
582
599
  }
583
600
  }
@@ -597,26 +614,36 @@ function styleSidebarTemplate(
597
614
  const hasVal = val !== undefined;
598
615
  const condMet = allConditionsPass(entry, activeStyle);
599
616
  const type = inferInputType(entry);
600
- if (!hasVal && !condMet) continue;
617
+ if (!hasVal && !condMet) {
618
+ continue;
619
+ }
601
620
 
602
621
  if (filterText) {
603
622
  const label = propLabel(entry, prop).toLowerCase();
604
- if (!prop.includes(filterText) && !label.includes(filterText)) continue;
623
+ if (!prop.includes(filterText) && !label.includes(filterText)) {
624
+ continue;
625
+ }
605
626
  }
606
627
  if (filterActive) {
607
628
  if (type === "shorthand") {
608
629
  const longhands = getLonghands(prop) as CssLonghand[];
609
630
  const hasAnySet =
610
631
  hasVal || longhands.some((l: CssLonghand) => activeStyle[l.name] !== undefined);
611
- if (!hasAnySet) continue;
612
- } else if (!hasVal) continue;
632
+ if (!hasAnySet) {
633
+ continue;
634
+ }
635
+ } else if (!hasVal) {
636
+ continue;
637
+ }
613
638
  }
614
639
 
615
640
  if (type === "shorthand") {
616
641
  const longhands = getLonghands(prop) as CssLonghand[];
617
642
  const hasAny =
618
643
  hasVal || longhands.some((l: CssLonghand) => activeStyle[l.name] !== undefined);
619
- if (!hasAny && !condMet) continue;
644
+ if (!hasAny && !condMet) {
645
+ continue;
646
+ }
620
647
  rows.push(
621
648
  renderShorthandRow(prop, entry, activeStyle, commitMutate, () => {}, inheritedStyle),
622
649
  );
@@ -629,7 +656,7 @@ function styleSidebarTemplate(
629
656
  prop,
630
657
  (val as string) ?? "",
631
658
  (newVal: string | undefined) => commitStyle(prop, newVal || undefined),
632
- () => commitStyle(prop, undefined),
659
+ () => commitStyle(prop),
633
660
  isWarning,
634
661
  sec.$layout === "grid",
635
662
  inheritedStyle[prop] as string | undefined,
@@ -639,7 +666,9 @@ function styleSidebarTemplate(
639
666
  }
640
667
  }
641
668
 
642
- if (isFiltering && rows.length === 0) return nothing;
669
+ if (isFiltering && rows.length === 0) {
670
+ return nothing;
671
+ }
643
672
 
644
673
  return html`
645
674
  <sp-accordion-item
@@ -664,11 +693,14 @@ function styleSidebarTemplate(
664
693
  e.preventDefault();
665
694
  transactDoc(activeTab.value, (t) => {
666
695
  for (const { prop, entry } of sectionActiveProps) {
667
- if (activeStyle[prop] !== undefined) commitMutate(t, prop, undefined);
696
+ if (activeStyle[prop] !== undefined) {
697
+ commitMutate(t, prop);
698
+ }
668
699
  if (inferInputType(entry) === "shorthand") {
669
700
  for (const l of getLonghands(prop) as CssLonghand[]) {
670
- if (activeStyle[l.name] !== undefined)
671
- commitMutate(t, l.name, undefined);
701
+ if (activeStyle[l.name] !== undefined) {
702
+ commitMutate(t, l.name);
703
+ }
672
704
  }
673
705
  }
674
706
  }
@@ -709,7 +741,7 @@ function styleSidebarTemplate(
709
741
  const newProp = (e.target as HTMLInputElement).value.trim();
710
742
  if (newProp && newProp !== prop) {
711
743
  transactDoc(activeTab.value, (t) => {
712
- commitMutate(t, prop, undefined);
744
+ commitMutate(t, prop);
713
745
  commitMutate(t, newProp, String(activeStyle[prop]));
714
746
  });
715
747
  }
@@ -724,7 +756,7 @@ function styleSidebarTemplate(
724
756
  commitStyle(prop, (e.target as HTMLInputElement).value);
725
757
  })}
726
758
  ></sp-textfield>
727
- <sp-action-button size="xs" quiet @click=${() => commitStyle(prop, undefined)}>
759
+ <sp-action-button size="xs" quiet @click=${() => commitStyle(prop)}>
728
760
  <sp-icon-close slot="icon"></sp-icon-close>
729
761
  </sp-action-button>
730
762
  </div>
@@ -782,7 +814,7 @@ function styleSidebarTemplate(
782
814
  >
783
815
  ${rule}
784
816
  </button>
785
- <sp-action-button size="xs" quiet @click=${() => commitStyle(rule, undefined)}>
817
+ <sp-action-button size="xs" quiet @click=${() => commitStyle(rule)}>
786
818
  <sp-icon-delete slot="icon"></sp-icon-delete>
787
819
  </sp-action-button>
788
820
  </div>
@@ -791,6 +823,7 @@ function styleSidebarTemplate(
791
823
  <button
792
824
  style="padding:6px 10px;background:none;border:1px dashed var(--spectrum-gray-400, #333);border-radius:4px;color:var(--spectrum-gray-700, #a1a1aa);font-size:12px;cursor:pointer"
793
825
  @click=${() => {
826
+ // oxlint-disable-next-line no-alert -- native prompt is the intended quick-input UX here
794
827
  const name = prompt("Selector name (e.g. th, :hover, .active):");
795
828
  if (name && name.trim()) {
796
829
  commitStyle(name.trim(), {});
@@ -824,10 +857,14 @@ function styleSidebarTemplate(
824
857
  */
825
858
  export function renderStylePanelTemplate(ctx: { getCanvasMode: () => string }) {
826
859
  const tab = activeTab.value;
827
- if (!tab) return html`<div class="empty-state">No document loaded</div>`;
860
+ if (!tab) {
861
+ return html`<div class="empty-state">No document loaded</div>`;
862
+ }
828
863
  if (ctx.getCanvasMode() === "stylebook" && tab.session.ui.stylebookSelection) {
829
864
  const node = tab.doc.document;
830
- if (!node) return html`<div class="empty-state">No document loaded</div>`;
865
+ if (!node) {
866
+ return html`<div class="empty-state">No document loaded</div>`;
867
+ }
831
868
  return html`
832
869
  <div class="stylebook-style-header">
833
870
  Styling: &lt;${tab.session.ui.stylebookSelection}&gt;
@@ -840,10 +877,13 @@ export function renderStylePanelTemplate(ctx: { getCanvasMode: () => string }) {
840
877
  )}
841
878
  `;
842
879
  }
843
- if (!tab.session.selection)
880
+ if (!tab.session.selection) {
844
881
  return html`<div class="empty-state">Select an element to style</div>`;
882
+ }
845
883
  const node = getNodeAtPath(tab.doc.document, tab.session.selection);
846
- if (!node) return html`<div class="empty-state">Select an element to style</div>`;
884
+ if (!node) {
885
+ return html`<div class="empty-state">Select an element to style</div>`;
886
+ }
847
887
  return styleSidebarTemplate(node, tab.session.ui.activeMedia, tab.session.ui.activeSelector);
848
888
  }
849
889
 
@@ -870,7 +910,7 @@ export function _fieldRow(
870
910
  ></sp-textfield>`
871
911
  : type === "checkbox"
872
912
  ? html`<sp-checkbox
873
- ?checked=${!!value}
913
+ ?checked=${Boolean(value)}
874
914
  @change=${(e: Event) => onChange((e.target as HTMLInputElement).checked)}
875
915
  ></sp-checkbox>`
876
916
  : html`<sp-textfield
@@ -7,7 +7,7 @@ import { activeTab } from "../workspace/workspace";
7
7
  import { camelToKebab } from "../utils/studio-utils";
8
8
  import cssMeta from "../../data/css-meta.json";
9
9
 
10
- let cssInitialMap: Map<string, string> = new Map();
10
+ let cssInitialMap = new Map<string, string>();
11
11
 
12
12
  /** Initialise cssInitialMap from webdata — call once during bootstrap. */
13
13
  export function initCssData(webdata: { cssProps: string[][] }) {
@@ -27,7 +27,9 @@ export function conditionPasses(
27
27
  styles: Record<string, unknown>,
28
28
  ) {
29
29
  const val = (styles[cond.prop] ?? "") as string;
30
- if (cond.values.length === 0) return val !== "" && val !== "initial";
30
+ if (cond.values.length === 0) {
31
+ return val !== "" && val !== "initial";
32
+ }
31
33
  return cond.values.includes(val);
32
34
  }
33
35
 
@@ -51,10 +53,14 @@ export function autoOpenSections(node: JxMutableNode, currentSections: Record<st
51
53
  const style = node.style || {};
52
54
  const result = { ...currentSections };
53
55
  for (const prop of Object.keys(style)) {
54
- if (typeof style[prop] === "object") continue;
56
+ if (typeof style[prop] === "object") {
57
+ continue;
58
+ }
55
59
  const entry = (cssMeta.$defs as Record<string, Record<string, unknown>>)[prop];
56
60
  const section = (entry?.$section as string) ?? "other";
57
- if (!result[section]) result[section] = true;
61
+ if (!result[section]) {
62
+ result[section] = true;
63
+ }
58
64
  }
59
65
  return result;
60
66
  }
@@ -67,12 +73,12 @@ export function getLonghands(shorthandProp: string) {
67
73
  if (entry?.$longhands) {
68
74
  return (entry.$longhands as string[])
69
75
  .map((name: string) => ({
70
- name,
71
76
  entry: (cssMeta.$defs as Record<string, Record<string, unknown>>)[name] || {
72
77
  $order: 0,
73
78
  },
79
+ name,
74
80
  }))
75
- .sort(
81
+ .toSorted(
76
82
  (
77
83
  /** @type {{ entry: Record<string, unknown> }} */ a,
78
84
  /** @type {{ entry: Record<string, unknown> }} */ b,
@@ -81,7 +87,9 @@ export function getLonghands(shorthandProp: string) {
81
87
  }
82
88
  const result = [];
83
89
  for (const [name, e] of Object.entries(cssMeta.$defs) as [string, Record<string, unknown>][]) {
84
- if (e.$shorthand === shorthandProp) result.push({ name, entry: e });
90
+ if (e.$shorthand === shorthandProp) {
91
+ result.push({ entry: e, name });
92
+ }
85
93
  }
86
94
  result.sort((a, b) => (a.entry.$order as number) - (b.entry.$order as number));
87
95
  return result;
@@ -92,21 +100,37 @@ export function getLonghands(shorthandProp: string) {
92
100
  * TRBL pattern.
93
101
  */
94
102
  export function expandShorthand(shortVal: string, count: number) {
95
- if (!shortVal) return Array(count).fill("");
103
+ if (!shortVal) {
104
+ return Array.from({ length: count }, () => "");
105
+ }
96
106
  const parts = shortVal.trim().split(/\s+/);
97
- if (count !== 4 || parts.length === 0) return Array(count).fill("");
98
- if (parts.length === 1) return [parts[0], parts[0], parts[0], parts[0]];
99
- if (parts.length === 2) return [parts[0], parts[1], parts[0], parts[1]];
100
- if (parts.length === 3) return [parts[0], parts[1], parts[2], parts[1]];
107
+ if (count !== 4 || parts.length === 0) {
108
+ return Array.from({ length: count }, () => "");
109
+ }
110
+ if (parts.length === 1) {
111
+ return [parts[0], parts[0], parts[0], parts[0]];
112
+ }
113
+ if (parts.length === 2) {
114
+ return [parts[0], parts[1], parts[0], parts[1]];
115
+ }
116
+ if (parts.length === 3) {
117
+ return [parts[0], parts[1], parts[2], parts[1]];
118
+ }
101
119
  return [parts[0], parts[1], parts[2], parts[3]];
102
120
  }
103
121
 
104
122
  /** Compress 4 TRBL values back into the shortest valid CSS shorthand string. */
105
123
  export function compressShorthand(vals: string[]) {
106
124
  const [t, r, b, l] = vals;
107
- if (t === r && r === b && b === l) return t;
108
- if (t === b && r === l) return `${t} ${r}`;
109
- if (r === l) return `${t} ${r} ${b}`;
125
+ if (t === r && r === b && b === l) {
126
+ return t;
127
+ }
128
+ if (t === b && r === l) {
129
+ return `${t} ${r}`;
130
+ }
131
+ if (r === l) {
132
+ return `${t} ${r} ${b}`;
133
+ }
110
134
  return `${t} ${r} ${b} ${l}`;
111
135
  }
112
136
 
@@ -132,21 +156,31 @@ export const BORDER_STYLES = new Set([
132
156
  * @returns {string[]}
133
157
  */
134
158
  export function expandBorderSide(value: string) {
135
- if (!value) return ["", "", ""];
159
+ if (!value) {
160
+ return ["", "", ""];
161
+ }
136
162
  const tokens = [];
137
163
  let current = "";
138
164
  let depth = 0;
139
165
  for (const ch of value.trim()) {
140
- if (ch === "(") depth++;
141
- if (ch === ")") depth--;
166
+ if (ch === "(") {
167
+ depth += 1;
168
+ }
169
+ if (ch === ")") {
170
+ depth -= 1;
171
+ }
142
172
  if (ch === " " && depth === 0) {
143
- if (current) tokens.push(current);
173
+ if (current) {
174
+ tokens.push(current);
175
+ }
144
176
  current = "";
145
177
  } else {
146
178
  current += ch;
147
179
  }
148
180
  }
149
- if (current) tokens.push(current);
181
+ if (current) {
182
+ tokens.push(current);
183
+ }
150
184
 
151
185
  let width = "";
152
186
  let style = "";
@@ -180,7 +214,9 @@ export function compressBorderSide(vals: string[]) {
180
214
  /** Extract --font-* CSS custom properties from the document root style. */
181
215
  export function getFontVars() {
182
216
  const style = activeTab.value?.doc.document?.style;
183
- if (!style) return [];
217
+ if (!style) {
218
+ return [];
219
+ }
184
220
  const vars = [];
185
221
  for (const [k, v] of Object.entries(style)) {
186
222
  if (k.startsWith("--font") && (typeof v === "string" || typeof v === "number")) {
@@ -205,9 +241,13 @@ export function currentFontFamily() {
205
241
  ? getNodeAtPath(tab.doc.document, tab.session.selection)
206
242
  : null;
207
243
  const raw = node?.style?.fontFamily;
208
- if (!raw) return "";
244
+ if (!raw) {
245
+ return "";
246
+ }
209
247
  const m = typeof raw === "string" && raw.match(/^var\((--[^)]+)\)$/);
210
- if (m) return tab?.doc.document?.style?.[m[1]] || "";
248
+ if (m) {
249
+ return tab?.doc.document?.style?.[m[1]] || "";
250
+ }
211
251
  return raw;
212
252
  }
213
253