@jxsuite/studio 0.23.2 → 0.24.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (105) hide show
  1. package/dist/studio.js +2230 -714
  2. package/dist/studio.js.map +121 -104
  3. package/package.json +8 -7
  4. package/src/browse/{browse-modal.js → browse-modal.ts} +9 -10
  5. package/src/browse/{browse.js → browse.ts} +109 -84
  6. package/src/canvas/{canvas-diff.js → canvas-diff.ts} +42 -35
  7. package/src/canvas/{canvas-helpers.js → canvas-helpers.ts} +25 -19
  8. package/src/canvas/{canvas-live-render.js → canvas-live-render.ts} +53 -63
  9. package/src/canvas/{canvas-render.js → canvas-render.ts} +110 -121
  10. package/src/canvas/{canvas-utils.js → canvas-utils.ts} +48 -44
  11. package/src/canvas/{nested-site-style.js → nested-site-style.ts} +3 -3
  12. package/src/editor/{component-inline-edit.js → component-inline-edit.ts} +45 -33
  13. package/src/editor/{content-inline-edit.js → content-inline-edit.ts} +27 -45
  14. package/src/editor/{context-menu.js → context-menu.ts} +38 -36
  15. package/src/editor/{convert-targets.js → convert-targets.ts} +15 -12
  16. package/src/editor/{convert-to-component.js → convert-to-component.ts} +25 -26
  17. package/src/editor/{convert-to-repeater.js → convert-to-repeater.ts} +60 -40
  18. package/src/editor/{inline-edit.js → inline-edit.ts} +91 -55
  19. package/src/editor/{inline-format.js → inline-format.ts} +46 -51
  20. package/src/editor/{insertion-helper.js → insertion-helper.ts} +47 -63
  21. package/src/editor/{shortcuts.js → shortcuts.ts} +45 -31
  22. package/src/editor/{slash-menu.js → slash-menu.ts} +34 -35
  23. package/src/files/{components.js → components.ts} +12 -15
  24. package/src/files/{file-ops.js → file-ops.ts} +36 -29
  25. package/src/files/{files.js → files.ts} +102 -84
  26. package/src/github/{github-auth.js → github-auth.ts} +3 -3
  27. package/src/github/{github-publish.js → github-publish.ts} +20 -18
  28. package/src/markdown/{md-allowlist.js → md-allowlist.ts} +5 -2
  29. package/src/markdown/{md-convert.js → md-convert.ts} +146 -169
  30. package/src/new-project/{new-project-modal.js → new-project-modal.ts} +16 -18
  31. package/src/panels/{activity-bar.js → activity-bar.ts} +26 -34
  32. package/src/panels/{ai-panel.js → ai-panel.ts} +30 -30
  33. package/src/panels/{block-action-bar.js → block-action-bar.ts} +64 -73
  34. package/src/panels/{canvas-dnd.js → canvas-dnd.ts} +44 -49
  35. package/src/panels/{data-explorer.js → data-explorer.ts} +21 -15
  36. package/src/panels/dnd.ts +417 -0
  37. package/src/panels/{editors.js → editors.ts} +43 -50
  38. package/src/panels/{elements-panel.js → elements-panel.ts} +30 -27
  39. package/src/panels/{events-panel.js → events-panel.ts} +33 -25
  40. package/src/panels/{git-panel.js → git-panel.ts} +97 -83
  41. package/src/panels/{head-panel.js → head-panel.ts} +117 -139
  42. package/src/panels/{imports-panel.js → imports-panel.ts} +69 -89
  43. package/src/panels/{layers-panel.js → layers-panel.ts} +61 -57
  44. package/src/panels/{left-panel.js → left-panel.ts} +56 -63
  45. package/src/panels/{overlays.js → overlays.ts} +28 -36
  46. package/src/panels/{panel-events.js → panel-events.ts} +53 -51
  47. package/src/panels/{preview-render.js → preview-render.ts} +20 -15
  48. package/src/panels/{properties-panel.js → properties-panel.ts} +212 -246
  49. package/src/panels/{pseudo-preview.js → pseudo-preview.ts} +9 -8
  50. package/src/panels/{quick-search.js → quick-search.ts} +16 -17
  51. package/src/panels/{right-panel.js → right-panel.ts} +41 -59
  52. package/src/panels/{shared.js → shared.ts} +7 -5
  53. package/src/panels/{signals-panel.js → signals-panel.ts} +322 -362
  54. package/src/panels/{statusbar.js → statusbar.ts} +14 -14
  55. package/src/panels/{style-inputs.js → style-inputs.ts} +66 -48
  56. package/src/panels/{style-panel.js → style-panel.ts} +235 -290
  57. package/src/panels/{style-utils.js → style-utils.ts} +32 -31
  58. package/src/panels/{stylebook-layers-panel.js → stylebook-layers-panel.ts} +22 -14
  59. package/src/panels/{stylebook-panel.js → stylebook-panel.ts} +127 -140
  60. package/src/panels/{tab-strip.js → tab-strip.ts} +12 -14
  61. package/src/panels/{toolbar.js → toolbar.ts} +68 -70
  62. package/src/panels/{welcome-screen.js → welcome-screen.ts} +14 -16
  63. package/src/{platform.js → platform.ts} +5 -3
  64. package/src/platforms/{devserver.js → devserver.ts} +53 -44
  65. package/src/{recent-projects.js → recent-projects.ts} +17 -12
  66. package/src/{resize-edges.js → resize-edges.ts} +18 -13
  67. package/src/services/{cem-export.js → cem-export.ts} +14 -7
  68. package/src/services/{code-services.js → code-services.ts} +29 -20
  69. package/src/services/{monaco-setup.js → monaco-setup.ts} +2 -3
  70. package/src/settings/{content-types-editor.js → content-types-editor.ts} +88 -67
  71. package/src/settings/{css-vars-editor.js → css-vars-editor.ts} +78 -51
  72. package/src/settings/{defs-editor.js → defs-editor.ts} +70 -56
  73. package/src/settings/{general-settings.js → general-settings.ts} +29 -28
  74. package/src/settings/{head-editor.js → head-editor.ts} +37 -35
  75. package/src/settings/{schema-field-ui.js → schema-field-ui.ts} +86 -76
  76. package/src/settings/{settings-modal.js → settings-modal.ts} +14 -16
  77. package/src/{site-context.js → site-context.ts} +35 -32
  78. package/src/{state.js → state.ts} +95 -78
  79. package/src/{store.js → store.ts} +60 -63
  80. package/src/{studio.js → studio.ts} +117 -119
  81. package/src/tabs/tab.ts +196 -0
  82. package/src/tabs/{transact.js → transact.ts} +107 -60
  83. package/src/types.ts +185 -0
  84. package/src/ui/{button-group.js → button-group.ts} +21 -21
  85. package/src/ui/{color-selector.js → color-selector.ts} +44 -37
  86. package/src/ui/{expression-editor.js → expression-editor.ts} +65 -40
  87. package/src/ui/{field-row.js → field-row.ts} +19 -2
  88. package/src/ui/{icons.js → icons.ts} +2 -2
  89. package/src/ui/{layers.js → layers.ts} +33 -24
  90. package/src/ui/{media-picker.js → media-picker.ts} +13 -9
  91. package/src/ui/{panel-resize.js → panel-resize.ts} +9 -4
  92. package/src/ui/{spectrum.js → spectrum.ts} +4 -4
  93. package/src/ui/{unit-selector.js → unit-selector.ts} +16 -19
  94. package/src/ui/{value-selector.js → value-selector.ts} +28 -28
  95. package/src/ui/{widgets.js → widgets.ts} +62 -15
  96. package/src/utils/{canvas-media.js → canvas-media.ts} +29 -14
  97. package/src/utils/{edit-display.js → edit-display.ts} +23 -20
  98. package/src/utils/{google-fonts.js → google-fonts.ts} +14 -13
  99. package/src/utils/{inherited-style.js → inherited-style.ts} +15 -10
  100. package/src/utils/{studio-utils.js → studio-utils.ts} +35 -23
  101. package/src/{view.js → view.ts} +53 -55
  102. package/src/workspace/{workspace.js → workspace.ts} +44 -33
  103. package/src/panels/dnd.js +0 -426
  104. package/src/tabs/tab.js +0 -192
  105. /package/src/{reactivity.js → reactivity.ts} +0 -0
@@ -1,3 +1,4 @@
1
+ /// <reference lib="dom" />
1
2
  /**
2
3
  * Media Picker — combobox-style widget for selecting project media files.
3
4
  *
@@ -7,8 +8,8 @@
7
8
 
8
9
  import { html, nothing } from "lit-html";
9
10
  import { live } from "lit-html/directives/live.js";
10
- import { getPlatform } from "../platform.js";
11
- import { debouncedStyleCommit } from "../store.js";
11
+ import { getPlatform } from "../platform";
12
+ import { debouncedStyleCommit } from "../store";
12
13
 
13
14
  // ─── Media file cache ────────────────────────────────────────────────────────
14
15
 
@@ -34,7 +35,7 @@ const MEDIA_EXTENSIONS = new Set([
34
35
  ]);
35
36
 
36
37
  /** @type {{ path: string; name: string; isImage: boolean }[]} */
37
- let mediaCache = [];
38
+ let mediaCache: { path: string; name: string; isImage: boolean }[] = [];
38
39
  let mediaCacheLoaded = false;
39
40
 
40
41
  /**
@@ -44,7 +45,10 @@ let mediaCacheLoaded = false;
44
45
  * @param {ReturnType<typeof getPlatform>} platform
45
46
  * @returns {Promise<{ path: string; name: string; isImage: boolean }[]>}
46
47
  */
47
- async function collectMedia(dir, platform) {
48
+ async function collectMedia(
49
+ dir: string,
50
+ platform: ReturnType<typeof getPlatform>,
51
+ ): Promise<{ path: string; name: string; isImage: boolean }[]> {
48
52
  /** @type {{ path: string; name: string; isImage: boolean }[]} */
49
53
  const results = [];
50
54
  try {
@@ -98,7 +102,7 @@ export function invalidateMediaCache() {
98
102
  * @param {(val: string) => void} onCommit — commit callback
99
103
  * @returns {import("lit-html").TemplateResult}
100
104
  */
101
- export function renderMediaPicker(prop, value, onCommit) {
105
+ export function renderMediaPicker(prop: string, value: string, onCommit: (val: string) => void) {
102
106
  // Kick off async load (won't block render)
103
107
  loadMediaCache();
104
108
 
@@ -127,8 +131,8 @@ export function renderMediaPicker(prop, value, onCommit) {
127
131
  size="s"
128
132
  placeholder="/image.jpg"
129
133
  .value=${live(currentValue)}
130
- @input=${debouncedStyleCommit(`media:${prop}`, 400, (/** @type {Event} */ e) =>
131
- onCommit(/** @type {HTMLInputElement} */ (e.target).value),
134
+ @input=${debouncedStyleCommit(`media:${prop}`, 400, (e: Event) =>
135
+ onCommit((e.target as HTMLInputElement).value),
132
136
  )}
133
137
  @focus=${() => loadMediaCache()}
134
138
  ></sp-textfield>
@@ -140,8 +144,8 @@ export function renderMediaPicker(prop, value, onCommit) {
140
144
  </sp-action-button>
141
145
  <sp-popover slot="click-content" class="media-picker-popover">
142
146
  <sp-menu
143
- @change=${(/** @type {Event} */ e) => {
144
- onCommit(/** @type {HTMLInputElement} */ (e.target).value);
147
+ @change=${(e: Event) => {
148
+ onCommit((e.target as HTMLInputElement).value);
145
149
  }}
146
150
  >
147
151
  ${options.map(
@@ -1,3 +1,4 @@
1
+ /// <reference lib="dom" />
1
2
  /**
2
3
  * Panel-resize.js — Draggable resize handles for left and right sidebars.
3
4
  *
@@ -5,7 +6,7 @@
5
6
  * localStorage so they survive page reloads.
6
7
  */
7
8
 
8
- import { view, applyPanelCollapse } from "../view.js";
9
+ import { view, applyPanelCollapse } from "../view";
9
10
 
10
11
  const STORAGE_KEY = "jx-studio-panel-widths";
11
12
  const MIN_WIDTH = 160;
@@ -36,9 +37,13 @@ try {
36
37
  * @param {"left" | "right"} side
37
38
  * @param {number} defaultWidth
38
39
  */
39
- function setupHandle(handle, cssVar, side, defaultWidth) {
40
- /** @type {{ startX: number; startWidth: number } | null} */
41
- let drag = null;
40
+ function setupHandle(
41
+ handle: HTMLElement,
42
+ cssVar: string,
43
+ side: "left" | "right",
44
+ defaultWidth: number,
45
+ ) {
46
+ let drag: { startX: number; startWidth: number } | null = null;
42
47
 
43
48
  handle.addEventListener("pointerdown", (e) => {
44
49
  e.preventDefault();
@@ -141,8 +141,8 @@ import { IconDownload } from "@spectrum-web-components/icons-workflow/src/elemen
141
141
  import { IconCheckmark } from "@spectrum-web-components/icons-workflow/src/elements/IconCheckmark.js";
142
142
 
143
143
  // Custom studio components
144
- import { JxValueSelector } from "./value-selector.js";
145
- import { JxColorPopover } from "./color-selector.js";
144
+ import { JxValueSelector } from "./value-selector";
145
+ import { JxColorPopover } from "./color-selector";
146
146
 
147
147
  // UI icons (used internally by Spectrum components like accordion, picker, combobox)
148
148
  import { IconChevron100 } from "@spectrum-web-components/icons-ui/src/elements/IconChevron100.js";
@@ -282,8 +282,8 @@ const components = [
282
282
  ["jx-color-popover", JxColorPopover],
283
283
  ];
284
284
 
285
- for (const [tag, ctor] of /** @type {[string, CustomElementConstructor][]} */ (components)) {
286
- if (!customElements.get(tag)) defineElement(tag, /** @type {CustomElementConstructor} */ (ctor));
285
+ for (const [tag, ctor] of components as [string, CustomElementConstructor][]) {
286
+ if (!customElements.get(tag)) defineElement(tag, ctor);
287
287
  }
288
288
 
289
289
  // Register theme fragments (these are also side-effect-only in the original modules)
@@ -1,3 +1,4 @@
1
+ /// <reference lib="dom" />
1
2
  /**
2
3
  * Unit-selector.js — Number + unit picker widget.
3
4
  *
@@ -8,7 +9,7 @@
8
9
  import { html, nothing } from "lit-html";
9
10
  import { live } from "lit-html/directives/live.js";
10
11
  import { classMap } from "lit-html/directives/class-map.js";
11
- import { debouncedStyleCommit } from "../store.js";
12
+ import { debouncedStyleCommit } from "../store";
12
13
 
13
14
  export const UNIT_RE = /^(-?[\d.]+)(px|rem|em|%|vw|vh|svw|svh|dvh|ms|s|fr|ch|ex|deg)?$/;
14
15
 
@@ -23,18 +24,18 @@ export const UNIT_RE = /^(-?[\d.]+)(px|rem|em|%|vw|vh|svw|svh|dvh|ms|s|fr|ch|ex|
23
24
  * @returns {import("lit-html").TemplateResult}
24
25
  */
25
26
  export function renderUnitSelector(
26
- /** @type {Record<string, unknown>} */ entry,
27
- /** @type {string} */ prop,
28
- /** @type {string | number | undefined} */ value,
29
- /** @type {(val: string) => void} */ onChange,
30
- /** @type {string} */ placeholder = "",
27
+ /** @type {Record<string, unknown>} */ entry: Record<string, unknown>,
28
+ /** @type {string} */ prop: string,
29
+ /** @type {string | number | undefined} */ value: string | number | undefined,
30
+ /** @type {(val: string) => void} */ onChange: (val: string) => void,
31
+ /** @type {string} */ placeholder: string = "",
31
32
  ) {
32
- const units = /** @type {string[]} */ (entry.$units || []);
33
- const keywords = /** @type {string[]} */ (entry.$keywords || []);
33
+ const units = (entry.$units || []) as string[];
34
+ const keywords = (entry.$keywords || []) as string[];
34
35
  const strVal = String(value ?? "");
35
36
  const match = strVal.match(UNIT_RE);
36
37
  const isKeyword = !match && strVal !== "" && keywords.includes(strVal);
37
- const isNumericVal = (/** @type {string} */ v) => /^-?\d*\.?\d*$/.test(v);
38
+ const isNumericVal = (v: string) => /^-?\d*\.?\d*$/.test(v);
38
39
 
39
40
  const currentUnit = isKeyword ? units[0] || "" : match ? match[2] || "" : units[0] || "";
40
41
  let displayValue;
@@ -56,8 +57,8 @@ export function renderUnitSelector(
56
57
  size="s"
57
58
  placeholder=${placeholder || "0"}
58
59
  .value=${live(displayValue)}
59
- @input=${debouncedStyleCommit(`nui:${prop}`, 400, (/** @type {Event} */ e) => {
60
- const val = /** @type {HTMLInputElement} */ (e.target).value.trim();
60
+ @input=${debouncedStyleCommit(`nui:${prop}`, 400, (e: Event) => {
61
+ const val = (e.target as HTMLInputElement).value.trim();
61
62
  if (val === "") {
62
63
  onChange("");
63
64
  return;
@@ -75,8 +76,8 @@ export function renderUnitSelector(
75
76
  <sp-popover style="min-width: var(--spectrum-component-width-900, 64px)">
76
77
  <sp-menu
77
78
  label="CSS unit"
78
- @change=${(/** @type {Event} */ e) => {
79
- const chosen = /** @type {HTMLInputElement} */ (e.target).value;
79
+ @change=${(e: Event) => {
80
+ const chosen = (e.target as HTMLInputElement).value;
80
81
  if (keywords.includes(chosen)) {
81
82
  onChange(chosen);
82
83
  } else if (units.includes(chosen)) {
@@ -86,16 +87,12 @@ export function renderUnitSelector(
86
87
  }
87
88
  }}
88
89
  >
89
- ${units.map(
90
- (/** @type {string} */ u) =>
91
- html`<sp-menu-item value=${u}>${u}</sp-menu-item>`,
92
- )}
90
+ ${units.map((u: string) => html`<sp-menu-item value=${u}>${u}</sp-menu-item>`)}
93
91
  ${keywords.length > 0 && units.length > 0
94
92
  ? html`<sp-menu-divider></sp-menu-divider>`
95
93
  : nothing}
96
94
  ${keywords.map(
97
- (/** @type {string} */ kw) =>
98
- html`<sp-menu-item value=${kw}>${kw}</sp-menu-item>`,
95
+ (kw: string) => html`<sp-menu-item value=${kw}>${kw}</sp-menu-item>`,
99
96
  )}
100
97
  </sp-menu>
101
98
  </sp-popover>
@@ -1,3 +1,4 @@
1
+ /// <reference lib="dom" />
1
2
  /**
2
3
  * Value Selector — Dual-mode styled combobox custom element.
3
4
  *
@@ -18,7 +19,7 @@
18
19
  import { LitElement, html } from "lit";
19
20
  import { live } from "lit/directives/live.js";
20
21
 
21
- /** @typedef {{ value: string; label: string; style?: string } | { divider: true }} ComboOption */
22
+ type ComboOption = { value: string; label: string; style?: string } | { divider: true };
22
23
 
23
24
  export class JxValueSelector extends LitElement {
24
25
  static properties = {
@@ -28,13 +29,18 @@ export class JxValueSelector extends LitElement {
28
29
  options: { attribute: false },
29
30
  };
30
31
 
32
+ declare value: string;
33
+ declare placeholder: string;
34
+ declare size: string;
35
+ declare options: ComboOption[];
36
+ declare _menuId: string;
37
+
31
38
  constructor() {
32
39
  super();
33
- /** @type {string} */ this.value = "";
34
- /** @type {string} */ this.placeholder = "";
35
- /** @type {string} */ this.size = "s";
36
- /** @type {ComboOption[]} */ this.options = [];
37
- /** @type {string} */
40
+ this.value = "";
41
+ this.placeholder = "";
42
+ this.size = "s";
43
+ this.options = [];
38
44
  this._menuId = "jx-combo-" + Math.random().toString(36).slice(2, 8);
39
45
  }
40
46
 
@@ -47,26 +53,20 @@ export class JxValueSelector extends LitElement {
47
53
  get _isPicker() {
48
54
  return (
49
55
  !!this.value &&
50
- this.options.some(
51
- (/** @type {ComboOption} */ o) => !("divider" in o) && o.value === this.value,
52
- )
56
+ this.options.some((o: ComboOption) => !("divider" in o) && o.value === this.value)
53
57
  );
54
58
  }
55
59
 
56
60
  /** Get the selected option's style string for the picker button preview */
57
61
  get _selectedStyle() {
58
62
  if (!this._isPicker) return "";
59
- const opt = this.options.find(
60
- (/** @type {ComboOption} */ o) => !("divider" in o) && o.value === this.value,
61
- );
62
- return (
63
- /** @type {{ value: string; label: string; style?: string } | undefined} */ (opt)?.style || ""
64
- );
63
+ const opt = this.options.find((o: ComboOption) => !("divider" in o) && o.value === this.value);
64
+ return (opt as { value: string; label: string; style?: string } | undefined)?.style || "";
65
65
  }
66
66
 
67
67
  /** Render menu items from options array */
68
68
  _renderMenuItems() {
69
- return this.options.map((/** @type {ComboOption} */ opt) =>
69
+ return this.options.map((opt: ComboOption) =>
70
70
  "divider" in opt
71
71
  ? html`<sp-menu-divider></sp-menu-divider>`
72
72
  : html`<sp-menu-item value=${opt.value} style=${opt.style || ""}
@@ -76,33 +76,33 @@ export class JxValueSelector extends LitElement {
76
76
  }
77
77
 
78
78
  /** Picker mode: sp-picker @change handler */
79
- _handlePickerChange(/** @type {Event} */ e) {
79
+ _handlePickerChange(e: Event) {
80
80
  e.stopPropagation();
81
- this.value = /** @type {HTMLInputElement} */ (e.target).value;
81
+ this.value = (e.target as HTMLInputElement).value;
82
82
  this.dispatchEvent(new Event("change", { bubbles: true, composed: true }));
83
83
  }
84
84
 
85
85
  /** Combobox mode: sp-menu @change handler */
86
- _handleMenuChange(/** @type {Event} */ e) {
86
+ _handleMenuChange(e: Event) {
87
87
  e.stopPropagation();
88
- if (!(/** @type {HTMLInputElement} */ (e.target).value)) return;
89
- this.value = /** @type {HTMLInputElement} */ (e.target).value;
88
+ if (!(e.target as HTMLInputElement).value) return;
89
+ this.value = (e.target as HTMLInputElement).value;
90
90
  this.dispatchEvent(new Event("change", { bubbles: true, composed: true }));
91
91
  }
92
92
 
93
93
  /** Combobox mode: textfield @input handler */
94
- _handleInput(/** @type {Event} */ e) {
94
+ _handleInput(e: Event) {
95
95
  e.stopPropagation();
96
- this.value = /** @type {HTMLInputElement} */ (e.target).value;
96
+ this.value = (e.target as HTMLInputElement).value;
97
97
  this.dispatchEvent(new Event("input", { bubbles: true, composed: true }));
98
98
  }
99
99
 
100
100
  /** Set popover min-width to match trigger width (replicates sp-picker behavior) */
101
- _setPopoverWidth(/** @type {Event} */ e) {
101
+ _setPopoverWidth(e: Event) {
102
102
  const group = this.querySelector(".jx-combobox-group");
103
- const w = group ? /** @type {HTMLElement} */ (group).offsetWidth : 0;
104
- const popover = /** @type {HTMLElement} */ (e.target).querySelector("sp-popover");
105
- if (popover && w) /** @type {HTMLElement} */ (popover).style.minWidth = `${w}px`;
103
+ const w = group ? (group as HTMLElement).offsetWidth : 0;
104
+ const popover = (e.target as HTMLElement).querySelector("sp-popover");
105
+ if (popover && w) (popover as HTMLElement).style.minWidth = `${w}px`;
106
106
  }
107
107
 
108
108
  render() {
@@ -127,7 +127,7 @@ export class JxValueSelector extends LitElement {
127
127
  placeholder=${this.placeholder}
128
128
  .value=${live(this.value || "")}
129
129
  @input=${this._handleInput}
130
- @click=${(/** @type {Event} */ e) => e.stopPropagation()}
130
+ @click=${(e: Event) => e.stopPropagation()}
131
131
  ></sp-textfield>
132
132
  <sp-picker-button size=${this.size}></sp-picker-button>
133
133
  <sp-overlay
@@ -1,3 +1,4 @@
1
+ /// <reference lib="dom" />
1
2
  /**
2
3
  * Widgets.js — Widget type dispatcher and simple widget renderers.
3
4
  *
@@ -13,11 +14,11 @@
13
14
  import { html } from "lit-html";
14
15
  import { live } from "lit-html/directives/live.js";
15
16
  import { ifDefined } from "lit-html/directives/if-defined.js";
16
- import { debouncedStyleCommit } from "../store.js";
17
- import { renderColorSelector } from "./color-selector.js";
18
- import { renderUnitSelector } from "./unit-selector.js";
19
- import { renderButtonGroup } from "./button-group.js";
20
- import { renderMediaPicker } from "./media-picker.js";
17
+ import { debouncedStyleCommit } from "../store";
18
+ import { renderColorSelector } from "./color-selector";
19
+ import { renderUnitSelector } from "./unit-selector";
20
+ import { renderButtonGroup } from "./button-group";
21
+ import { renderMediaPicker } from "./media-picker";
21
22
 
22
23
  /**
23
24
  * Render a plain text input widget.
@@ -28,14 +29,19 @@ import { renderMediaPicker } from "./media-picker.js";
28
29
  * @param {string} [placeholder]
29
30
  * @returns {import("lit-html").TemplateResult}
30
31
  */
31
- export function renderTextInput(prop, value, onChange, placeholder = "") {
32
+ export function renderTextInput(
33
+ prop: string,
34
+ value: string | number | undefined,
35
+ onChange: (val: string) => void,
36
+ placeholder: string = "",
37
+ ) {
32
38
  return html`
33
39
  <sp-textfield
34
40
  size="s"
35
41
  placeholder=${placeholder}
36
42
  .value=${live(String(value ?? ""))}
37
- @input=${debouncedStyleCommit(`text:${prop}`, 400, (/** @type {Event} */ e) =>
38
- onChange(/** @type {HTMLInputElement} */ (e.target).value),
43
+ @input=${debouncedStyleCommit(`text:${prop}`, 400, (e: Event) =>
44
+ onChange((e.target as HTMLInputElement).value),
39
45
  )}
40
46
  ></sp-textfield>
41
47
  `;
@@ -51,9 +57,15 @@ export function renderTextInput(prop, value, onChange, placeholder = "") {
51
57
  * @param {string} [placeholder]
52
58
  * @returns {import("lit-html").TemplateResult}
53
59
  */
54
- export function renderNumberInput(entry, prop, value, onChange, placeholder = "") {
55
- const minimum = /** @type {number | undefined} */ (entry.minimum);
56
- const maximum = /** @type {number | undefined} */ (entry.maximum);
60
+ export function renderNumberInput(
61
+ entry: Record<string, unknown>,
62
+ prop: string,
63
+ value: string | number | undefined,
64
+ onChange: (val: string | number) => void,
65
+ placeholder: string = "",
66
+ ) {
67
+ const minimum = entry.minimum as number | undefined;
68
+ const maximum = entry.maximum as number | undefined;
57
69
  return html`
58
70
  <sp-number-field
59
71
  size="s"
@@ -63,8 +75,8 @@ export function renderNumberInput(entry, prop, value, onChange, placeholder = ""
63
75
  min=${ifDefined(minimum)}
64
76
  max=${ifDefined(maximum)}
65
77
  step=${ifDefined(maximum !== undefined && maximum <= 1 ? 0.1 : undefined)}
66
- @change=${debouncedStyleCommit(`num:${prop}`, 400, (/** @type {Event} */ e) => {
67
- const v = /** @type {HTMLInputElement & { value: number | undefined }} */ (e.target).value;
78
+ @change=${debouncedStyleCommit(`num:${prop}`, 400, (e: Event) => {
79
+ const v = (e.target as HTMLInputElement & { value: number | undefined }).value;
68
80
  if (v === undefined || isNaN(v)) onChange("");
69
81
  else onChange(Number(v));
70
82
  })}
@@ -80,10 +92,45 @@ export function renderNumberInput(entry, prop, value, onChange, placeholder = ""
80
92
  * @param {string} prop — property key
81
93
  * @param {string | number | undefined} value — current value
82
94
  * @param {(val: string | number) => void} onCommit — commit callback
83
- * @param {{ placeholder?: string; renderSelect?: Function; renderCombobox?: Function }} [opts]
95
+ * @param {{
96
+ * placeholder?: string;
97
+ * renderSelect?: (
98
+ * entry: Record<string, unknown>,
99
+ * prop: string,
100
+ * value: string | number | undefined,
101
+ * onCommit: (val: string | number) => void,
102
+ * ) => import("lit-html").TemplateResult;
103
+ * renderCombobox?: (
104
+ * entry: Record<string, unknown>,
105
+ * prop: string,
106
+ * value: string | number | undefined,
107
+ * onCommit: (val: string | number) => void,
108
+ * ) => import("lit-html").TemplateResult;
109
+ * }} [opts]
84
110
  * @returns {import("lit-html").TemplateResult}
85
111
  */
86
- export function widgetForType(type, entry, prop, value, onCommit, opts = {}) {
112
+ export function widgetForType(
113
+ type: string,
114
+ entry: Record<string, unknown>,
115
+ prop: string,
116
+ value: string | number | undefined,
117
+ onCommit: (val: string | number) => void,
118
+ opts: {
119
+ placeholder?: string;
120
+ renderSelect?: (
121
+ entry: Record<string, unknown>,
122
+ prop: string,
123
+ value: any,
124
+ onCommit: (val: string | number) => void,
125
+ ) => import("lit-html").TemplateResult;
126
+ renderCombobox?: (
127
+ entry: Record<string, unknown>,
128
+ prop: string,
129
+ value: any,
130
+ onCommit: (val: string | number) => void,
131
+ ) => import("lit-html").TemplateResult;
132
+ } = {},
133
+ ) {
87
134
  switch (type) {
88
135
  case "button-group":
89
136
  return renderButtonGroup(entry, prop, value, onCommit);
@@ -1,5 +1,8 @@
1
+ /// <reference lib="dom" />
1
2
  /** Canvas media/breakpoint utilities — pure functions extracted for testability. */
2
3
 
4
+ import type { JxStyle } from "@jxsuite/schema/types";
5
+
3
6
  /**
4
7
  * Classify $media entries into size breakpoints (get a canvas each) and feature queries (rendered
5
8
  * as toolbar toggles).
@@ -11,7 +14,7 @@
11
14
  * baseWidth: number;
12
15
  * }}
13
16
  */
14
- export function parseMediaEntries(mediaDef) {
17
+ export function parseMediaEntries(mediaDef: Record<string, string> | null | undefined) {
15
18
  if (!mediaDef) return { sizeBreakpoints: [], featureQueries: [], baseWidth: 320 };
16
19
  const sizes = [],
17
20
  features = [];
@@ -39,8 +42,11 @@ export function parseMediaEntries(mediaDef) {
39
42
  * @param {number} canvasWidth
40
43
  * @returns {Set<string>}
41
44
  */
42
- export function activeBreakpointsForWidth(sizeBreakpoints, canvasWidth) {
43
- const active = new Set();
45
+ export function activeBreakpointsForWidth(
46
+ sizeBreakpoints: { name: string; width: number; type: string }[],
47
+ canvasWidth: number,
48
+ ) {
49
+ const active = new Set<string>();
44
50
  for (const bp of sizeBreakpoints) {
45
51
  if (bp.type === "min" && canvasWidth >= bp.width) active.add(bp.name);
46
52
  else if (bp.type === "max" && canvasWidth <= bp.width) active.add(bp.name);
@@ -57,18 +63,23 @@ export function activeBreakpointsForWidth(sizeBreakpoints, canvasWidth) {
57
63
  * @param {Set<string>} activeBreakpoints
58
64
  * @param {Record<string, boolean>} featureToggles
59
65
  */
60
- export function applyCanvasStyle(el, styleDef, activeBreakpoints, featureToggles) {
66
+ export function applyCanvasStyle(
67
+ el: HTMLElement,
68
+ styleDef: JxStyle | null | undefined,
69
+ activeBreakpoints: Set<string>,
70
+ featureToggles: Record<string, boolean>,
71
+ ) {
61
72
  if (!styleDef || typeof styleDef !== "object") return;
62
73
  for (const [prop, val] of Object.entries(styleDef)) {
63
74
  if (typeof val === "string" || typeof val === "number") {
64
75
  try {
65
76
  if (prop.startsWith("--")) el.style.setProperty(prop, String(val));
66
- else /** @type {Record<string, unknown>} */ (/** @type {unknown} */ (el.style))[prop] = val;
77
+ else (el.style as unknown as Record<string, unknown>)[prop] = val;
67
78
  } catch {}
68
79
  }
69
80
  }
70
81
  for (const [key, val] of Object.entries(styleDef)) {
71
- if (!key.startsWith("@") || typeof val !== "object") continue;
82
+ if (!key.startsWith("@") || typeof val !== "object" || val === null) continue;
72
83
  const mediaName = key.slice(1);
73
84
  if (mediaName === "--") continue;
74
85
  if (activeBreakpoints.has(mediaName) || featureToggles[mediaName]) {
@@ -76,8 +87,7 @@ export function applyCanvasStyle(el, styleDef, activeBreakpoints, featureToggles
76
87
  if (typeof v === "string" || typeof v === "number") {
77
88
  try {
78
89
  if (prop.startsWith("--")) el.style.setProperty(prop, String(v));
79
- else
80
- /** @type {Record<string, unknown>} */ (/** @type {unknown} */ (el.style))[prop] = v;
90
+ else (el.style as unknown as Record<string, unknown>)[prop] = v;
81
91
  } catch {}
82
92
  }
83
93
  }
@@ -95,9 +105,11 @@ export function applyCanvasStyle(el, styleDef, activeBreakpoints, featureToggles
95
105
  * @param {Set<string>} activeBreakpoints
96
106
  * @returns {Map<string, Map<string, string>>}
97
107
  */
98
- export function collectMediaOverrides(styleSheets, activeBreakpoints) {
99
- /** @type {Map<string, Map<string, string>>} */
100
- const overrides = new Map();
108
+ export function collectMediaOverrides(
109
+ styleSheets: Iterable<CSSStyleSheet>,
110
+ activeBreakpoints: Set<string>,
111
+ ) {
112
+ const overrides: Map<string, Map<string, string>> = new Map();
101
113
  if (!activeBreakpoints.size) return overrides;
102
114
 
103
115
  for (const sheet of styleSheets) {
@@ -121,7 +133,7 @@ export function collectMediaOverrides(styleSheets, activeBreakpoints) {
121
133
  if (!jxMatch) continue;
122
134
  const uid = jxMatch[1];
123
135
  if (!overrides.has(uid)) overrides.set(uid, new Map());
124
- const props = /** @type {Map<string, string>} */ (overrides.get(uid));
136
+ const props = overrides.get(uid) as Map<string, string>;
125
137
  for (let i = 0; i < mediaRule.style.length; i++) {
126
138
  const prop = mediaRule.style[i];
127
139
  props.set(prop, mediaRule.style.getPropertyValue(prop));
@@ -138,13 +150,16 @@ export function collectMediaOverrides(styleSheets, activeBreakpoints) {
138
150
  * @param {Element} canvasEl
139
151
  * @param {Map<string, Map<string, string>>} overrides
140
152
  */
141
- export function applyOverridesToCanvas(canvasEl, overrides) {
153
+ export function applyOverridesToCanvas(
154
+ canvasEl: Element,
155
+ overrides: Map<string, Map<string, string>>,
156
+ ) {
142
157
  for (const [uid, props] of overrides) {
143
158
  const els = canvasEl.querySelectorAll(`[data-jx="${uid}"]`);
144
159
  for (const el of els) {
145
160
  for (const [prop, val] of props) {
146
161
  try {
147
- /** @type {HTMLElement} */ (el).style.setProperty(prop, val);
162
+ (el as HTMLElement).style.setProperty(prop, val);
148
163
  } catch {}
149
164
  }
150
165
  }