@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,16 +1,19 @@
1
+ /// <reference lib="dom" />
1
2
  /**
2
3
  * Edit-mode display transforms — extracted from studio.js (Phase 4i). Pure stateless functions that
3
4
  * convert document trees for visual editing (template expressions, $map, $switch, empty
4
5
  * placeholders).
5
6
  */
6
7
 
8
+ import type { JxMutableNode } from "@jxsuite/schema/types";
9
+
7
10
  /**
8
11
  * Convert a template string to a displayable expression for edit mode. Replaces ${expr} with ❮ expr
9
12
  * ❯ so the runtime renders it as literal text.
10
13
  *
11
14
  * @param {string} str
12
15
  */
13
- export function templateToEditDisplay(str) {
16
+ export function templateToEditDisplay(str: string) {
14
17
  return str.replace(/\$\{([^}]+)\}/g, "\u276A $1 \u276B");
15
18
  }
16
19
 
@@ -20,10 +23,10 @@ export function templateToEditDisplay(str) {
20
23
  *
21
24
  * @param {HTMLElement} el
22
25
  */
23
- export function restoreTemplateExpressions(el) {
26
+ export function restoreTemplateExpressions(el: HTMLElement) {
24
27
  const walker = document.createTreeWalker(el, NodeFilter.SHOW_TEXT);
25
28
  while (walker.nextNode()) {
26
- const node = /** @type {Text} */ (walker.currentNode);
29
+ const node = walker.currentNode as Text;
27
30
  if (node.data.includes("\u276A")) {
28
31
  node.data = node.data.replace(/\u276A\s*(.*?)\s*\u276B/g, "${$1}");
29
32
  }
@@ -38,14 +41,13 @@ export function restoreTemplateExpressions(el) {
38
41
  * @param {JxMutableNode} node
39
42
  * @returns {JxMutableNode}
40
43
  */
41
- export function prepareForEditMode(node) {
44
+ export function prepareForEditMode(node: JxMutableNode): JxMutableNode {
42
45
  if (!node || typeof node !== "object") return node;
43
46
  if (Array.isArray(node)) return node.map(prepareForEditMode);
44
47
 
45
- const /** @type {Record<string, unknown>} */ obj = /** @type {Record<string, unknown>} */ (node);
48
+ const /** @type {Record<string, unknown>} */ obj = node as Record<string, unknown>;
46
49
 
47
- /** @type {Record<string, unknown>} */
48
- const out = {};
50
+ const out: Record<string, unknown> = {};
49
51
  for (const [k, v] of Object.entries(obj)) {
50
52
  if (k === "state" || k === "$media" || k === "$props" || k === "$elements") {
51
53
  out[k] = v; // preserve as-is for runtime resolution
@@ -55,34 +57,34 @@ export function prepareForEditMode(node) {
55
57
  } else if (
56
58
  v &&
57
59
  typeof v === "object" &&
58
- /** @type {Record<string, unknown>} */ (v).$prototype === "Array"
60
+ (v as Record<string, unknown>).$prototype === "Array"
59
61
  ) {
60
62
  // Wrap the map template in a visual repeater perimeter
61
- const vObj = /** @type {Record<string, unknown>} */ (v);
63
+ const vObj = v as Record<string, unknown>;
62
64
  const template = vObj.map;
63
65
  if (template && typeof template === "object") {
64
66
  out.children = [
65
67
  {
66
68
  tagName: "div",
67
69
  className: "repeater-perimeter",
68
- children: [prepareForEditMode(/** @type {JxMutableNode} */ (template))],
70
+ children: [prepareForEditMode(template as JxMutableNode)],
69
71
  },
70
72
  ];
71
73
  } else {
72
74
  out.children = [];
73
75
  }
74
76
  } else {
75
- out.children = prepareForEditMode(/** @type {JxMutableNode} */ (v));
77
+ out.children = prepareForEditMode(v as JxMutableNode);
76
78
  }
77
79
  } else if (k === "cases" && obj.$switch && v && typeof v === "object") {
78
80
  // Replace $switch cases with a placeholder showing the first case or a label
79
81
  const caseKeys = Object.keys(v);
80
82
  if (caseKeys.length > 0) {
81
- const firstCase = /** @type {Record<string, unknown>} */ (v)[caseKeys[0]];
83
+ const firstCase = (v as Record<string, unknown>)[caseKeys[0]];
82
84
  if (
83
85
  firstCase &&
84
86
  typeof firstCase === "object" &&
85
- !(/** @type {Record<string, unknown>} */ (firstCase).$ref)
87
+ !(firstCase as Record<string, unknown>).$ref
86
88
  ) {
87
89
  out.children = [prepareForEditMode(firstCase)];
88
90
  } else {
@@ -107,8 +109,7 @@ export function prepareForEditMode(node) {
107
109
  } else if (k === "style") {
108
110
  // Replace template strings in style values with empty strings
109
111
  if (v && typeof v === "object") {
110
- /** @type {Record<string, unknown>} */
111
- const s = {};
112
+ const s: Record<string, unknown> = {};
112
113
  for (const [sk, sv] of Object.entries(v)) {
113
114
  s[sk] = typeof sv === "string" && sv.includes("${") ? "" : sv;
114
115
  }
@@ -118,14 +119,16 @@ export function prepareForEditMode(node) {
118
119
  }
119
120
  } else if (typeof v === "string" && v.includes("${")) {
120
121
  // Template string in a display property → show raw expression
121
- out[k] = templateToEditDisplay(v);
122
- } else if (v && typeof v === "object" && /** @type {Record<string, unknown>} */ (v).$ref) {
122
+ // For URL-bearing attributes, use empty string to avoid triggering network requests
123
+ const isUrlAttr = k === "src" || k === "href" || k === "poster" || k === "action";
124
+ out[k] = isUrlAttr ? "" : templateToEditDisplay(v);
125
+ } else if (v && typeof v === "object" && (v as Record<string, unknown>).$ref) {
123
126
  // $ref binding → show ref path as literal text
124
- const ref = /** @type {string} */ (/** @type {Record<string, unknown>} */ (v).$ref);
127
+ const ref = (v as Record<string, unknown>).$ref as string;
125
128
  const label = ref.startsWith("#/state/") ? ref.slice(8) : ref;
126
129
  out[k] = `{${label}}`;
127
130
  } else {
128
- out[k] = prepareForEditMode(/** @type {JxMutableNode} */ (v));
131
+ out[k] = prepareForEditMode(v as JxMutableNode);
129
132
  }
130
133
  }
131
134
 
@@ -133,7 +136,7 @@ export function prepareForEditMode(node) {
133
136
  if (out.tagName && !out.textContent && !out.innerHTML) {
134
137
  const hasChildren = Array.isArray(out.children) && out.children.length > 0;
135
138
  if (!hasChildren) {
136
- const tag = /** @type {string} */ (out.tagName);
139
+ const tag = out.tagName as string;
137
140
  const textTags = new Set([
138
141
  "p",
139
142
  "h1",
@@ -1,5 +1,7 @@
1
1
  /** Google Fonts helpers — shared between head-panel (per-page) and head-editor (project-level). */
2
2
 
3
+ import type { JxHeadEntry } from "@jxsuite/schema/types";
4
+
3
5
  export const GFONTS_CSS_PREFIX = "https://fonts.googleapis.com/css2?";
4
6
  export const GFONTS_PRECONNECT_ORIGINS = [
5
7
  "https://fonts.googleapis.com",
@@ -12,12 +14,12 @@ export const GFONTS_PRECONNECT_ORIGINS = [
12
14
  * @param {JxHeadEntry} entry
13
15
  * @returns {boolean}
14
16
  */
15
- export function isGoogleFontEntry(entry) {
17
+ export function isGoogleFontEntry(entry: JxHeadEntry) {
16
18
  return (
17
19
  entry?.tagName === "link" &&
18
20
  entry?.attributes?.rel === "stylesheet" &&
19
21
  typeof entry?.attributes?.href === "string" &&
20
- /** @type {string} */ (entry.attributes.href).startsWith(GFONTS_CSS_PREFIX)
22
+ (entry.attributes.href as string).startsWith(GFONTS_CSS_PREFIX)
21
23
  );
22
24
  }
23
25
 
@@ -27,11 +29,11 @@ export function isGoogleFontEntry(entry) {
27
29
  * @param {JxHeadEntry} entry
28
30
  * @returns {boolean}
29
31
  */
30
- export function isGoogleFontPreconnect(entry) {
32
+ export function isGoogleFontPreconnect(entry: JxHeadEntry) {
31
33
  return (
32
34
  entry?.tagName === "link" &&
33
35
  entry?.attributes?.rel === "preconnect" &&
34
- GFONTS_PRECONNECT_ORIGINS.includes(/** @type {string} */ (entry?.attributes?.href))
36
+ GFONTS_PRECONNECT_ORIGINS.includes(entry?.attributes?.href as string)
35
37
  );
36
38
  }
37
39
 
@@ -41,7 +43,7 @@ export function isGoogleFontPreconnect(entry) {
41
43
  * @param {string} href
42
44
  * @returns {string}
43
45
  */
44
- export function extractFontFamily(href) {
46
+ export function extractFontFamily(href: string) {
45
47
  const match = href.match(/family=([^&:]+)/);
46
48
  if (!match) return "";
47
49
  return decodeURIComponent(match[1].replace(/\+/g, " "));
@@ -53,7 +55,7 @@ export function extractFontFamily(href) {
53
55
  * @param {string} family
54
56
  * @returns {string}
55
57
  */
56
- export function buildGoogleFontUrl(family) {
58
+ export function buildGoogleFontUrl(family: string) {
57
59
  return `${GFONTS_CSS_PREFIX}family=${encodeURIComponent(family).replace(/%20/g, "+")}&display=swap`;
58
60
  }
59
61
 
@@ -62,17 +64,16 @@ export function buildGoogleFontUrl(family) {
62
64
  *
63
65
  * @param {JxHeadEntry[]} head
64
66
  */
65
- export function ensureGoogleFontPreconnects(head) {
67
+ export function ensureGoogleFontPreconnects(head: JxHeadEntry[]) {
66
68
  for (const origin of GFONTS_PRECONNECT_ORIGINS) {
67
69
  const exists = head.some(
68
- (/** @type {JxHeadEntry} */ e) =>
70
+ (e: JxHeadEntry) =>
69
71
  e?.tagName === "link" &&
70
72
  e?.attributes?.rel === "preconnect" &&
71
73
  e?.attributes?.href === origin,
72
74
  );
73
75
  if (!exists) {
74
- /** @type {Record<string, string | boolean>} */
75
- const attrs = { rel: "preconnect", href: origin };
76
+ const attrs: Record<string, string | boolean> = { rel: "preconnect", href: origin };
76
77
  if (origin === "https://fonts.gstatic.com") attrs.crossorigin = "";
77
78
  head.push({ tagName: "link", attributes: attrs });
78
79
  }
@@ -85,10 +86,10 @@ export function ensureGoogleFontPreconnects(head) {
85
86
  * @param {JxHeadEntry[]} head
86
87
  * @returns {JxHeadEntry[]}
87
88
  */
88
- export function cleanupGoogleFontPreconnects(head) {
89
- const hasFont = head.some((/** @type {JxHeadEntry} */ e) => isGoogleFontEntry(e));
89
+ export function cleanupGoogleFontPreconnects(head: JxHeadEntry[]) {
90
+ const hasFont = head.some((e: JxHeadEntry) => isGoogleFontEntry(e));
90
91
  if (!hasFont) {
91
- return head.filter((/** @type {JxHeadEntry} */ e) => !isGoogleFontPreconnect(e));
92
+ return head.filter((e: JxHeadEntry) => !isGoogleFontPreconnect(e));
92
93
  }
93
94
  return head;
94
95
  }
@@ -6,6 +6,8 @@
6
6
  * on the current tab.
7
7
  */
8
8
 
9
+ import type { JxStyle } from "@jxsuite/schema/types";
10
+
9
11
  /**
10
12
  * Compute the inherited style object for a given breakpoint tab.
11
13
  *
@@ -16,35 +18,38 @@
16
18
  * @param {string | null} activeSelector — current nested selector, or null
17
19
  * @returns {Record<string, string | number>} Inherited style map (prop → value)
18
20
  */
19
- export function computeInheritedStyle(style, mediaNames, activeTab, activeSelector = null) {
21
+ export function computeInheritedStyle(
22
+ style: JxStyle,
23
+ mediaNames: string[],
24
+ activeTab: string | null,
25
+ activeSelector: string | null = null,
26
+ ) {
20
27
  if (activeTab === null || mediaNames.length === 0) return {};
21
28
 
22
- /** @type {Record<string, string | number>} */
23
- let inherited = {};
29
+ let inherited: Record<string, string | number> = {};
24
30
 
25
31
  if (!activeSelector) {
26
32
  // Start with base flat props
27
33
  for (const [p, v] of Object.entries(style)) {
28
- if (typeof v !== "object") inherited[p] = v ?? "";
34
+ if (typeof v !== "object") inherited[p] = (v as string | number) ?? "";
29
35
  }
30
36
  // Layer each media block in order until current tab
31
37
  for (const name of mediaNames) {
32
38
  if (name === activeTab) break;
33
- const block = style[`@${name}`] || {};
39
+ const block = (style[`@${name}`] || {}) as JxStyle;
34
40
  for (const [p, v] of Object.entries(block)) {
35
- if (typeof v !== "object") inherited[p] = v ?? "";
41
+ if (typeof v !== "object") inherited[p] = (v as string | number) ?? "";
36
42
  }
37
43
  }
38
44
  } else {
39
45
  // Selector inheritance: base selector → each media's selector block in order
40
- const baseSel = style[activeSelector] || {};
46
+ const baseSel = (style[activeSelector] || {}) as JxStyle;
41
47
  for (const [p, v] of Object.entries(baseSel)) {
42
- if (typeof v !== "object") inherited[p] = v ?? "";
48
+ if (typeof v !== "object") inherited[p] = (v as string | number) ?? "";
43
49
  }
44
50
  for (const name of mediaNames) {
45
51
  if (name === activeTab) break;
46
- const selBlock =
47
- /** @type {Record<string, unknown>} */ (style[`@${name}`] || {})[activeSelector] || {};
52
+ const selBlock = ((style[`@${name}`] || {}) as Record<string, unknown>)[activeSelector] || {};
48
53
  for (const [p, v] of Object.entries(selBlock)) {
49
54
  if (typeof v !== "object") inherited[p] = v ?? "";
50
55
  }
@@ -4,14 +4,16 @@
4
4
  * These are all side-effect-free functions used by style/properties/events panels.
5
5
  */
6
6
 
7
+ import type { ProjectConfig, ContentTypeDef } from "@jxsuite/schema/types";
8
+
7
9
  /**
8
10
  * CamelCase → kebab-case for inline style attributes
9
11
  *
10
12
  * @param {string} str
11
13
  * @returns {string}
12
14
  */
13
- export function camelToKebab(str) {
14
- return str.replace(/[A-Z]/g, (/** @type {string} */ c) => "-" + c.toLowerCase());
15
+ export function camelToKebab(str: string) {
16
+ return str.replace(/[A-Z]/g, (c: string) => "-" + c.toLowerCase());
15
17
  }
16
18
 
17
19
  /**
@@ -21,10 +23,14 @@ export function camelToKebab(str) {
21
23
  * @param {string} prop
22
24
  * @returns {string}
23
25
  */
24
- export function camelToLabel(prop) {
25
- return prop
26
- .replace(/([A-Z])/g, " $1")
27
- .replace(/^./, (/** @type {string} */ c) => c.toUpperCase());
26
+ export function camelToLabel(prop: string) {
27
+ return prop.replace(/([A-Z])/g, " $1").replace(/^./, (c: string) => c.toUpperCase());
28
+ }
29
+
30
+ export function toCamelCase(str: string): string {
31
+ return str
32
+ .replace(/[^a-zA-Z0-9]+(.)?/g, (_, c) => (c ? c.toUpperCase() : ""))
33
+ .replace(/^[A-Z]/, (c) => c.toLowerCase());
28
34
  }
29
35
 
30
36
  /**
@@ -34,11 +40,10 @@ export function camelToLabel(prop) {
34
40
  * @param {string} val
35
41
  * @returns {string}
36
42
  */
37
- export function kebabToLabel(val) {
43
+ export function kebabToLabel(val: string) {
38
44
  return val.replace(
39
45
  /(^|-)(\w)/g,
40
- (/** @type {string} */ _, /** @type {string} */ sep, /** @type {string} */ c) =>
41
- (sep ? " " : "") + c.toUpperCase(),
46
+ (_: string, sep: string, c: string) => (sep ? " " : "") + c.toUpperCase(),
42
47
  );
43
48
  }
44
49
 
@@ -49,7 +54,10 @@ export function kebabToLabel(val) {
49
54
  * @param {string} prop
50
55
  * @returns {string}
51
56
  */
52
- export function propLabel(entry, prop) {
57
+ export function propLabel(
58
+ entry: { $label?: string; [key: string]: unknown } | null | undefined,
59
+ prop: string,
60
+ ) {
53
61
  return entry?.$label || camelToLabel(prop);
54
62
  }
55
63
 
@@ -60,13 +68,15 @@ export function propLabel(entry, prop) {
60
68
  * @param {string} attr
61
69
  * @returns {string}
62
70
  */
63
- export function attrLabel(entry, attr) {
71
+ export function attrLabel(
72
+ entry: { $label?: string; [key: string]: unknown } | null | undefined,
73
+ attr: string,
74
+ ) {
64
75
  if (entry?.$label) return entry.$label;
65
76
  if (attr.includes("-"))
66
77
  return attr.replace(
67
78
  /(^|-)(\w)/g,
68
- (/** @type {string} */ _, /** @type {string} */ sep, /** @type {string} */ c) =>
69
- (sep ? " " : "") + c.toUpperCase(),
79
+ (_: string, sep: string, c: string) => (sep ? " " : "") + c.toUpperCase(),
70
80
  );
71
81
  return camelToLabel(attr);
72
82
  }
@@ -77,9 +87,8 @@ export function attrLabel(entry, attr) {
77
87
  * @param {string} val
78
88
  * @returns {string}
79
89
  */
80
- export function abbreviateValue(val) {
81
- /** @type {Record<string, string>} */
82
- const map = {
90
+ export function abbreviateValue(val: string) {
91
+ const map: Record<string, string> = {
83
92
  inline: "inl",
84
93
  "inline-block": "i-blk",
85
94
  "inline-flex": "i-flx",
@@ -109,7 +118,7 @@ export function abbreviateValue(val) {
109
118
  * @param {Record<string, unknown>} entry
110
119
  * @returns {string}
111
120
  */
112
- export function inferInputType(entry) {
121
+ export function inferInputType(entry: Record<string, unknown>) {
113
122
  if (entry.$shorthand === true) return "shorthand";
114
123
  if (entry.$input === "button-group") return "button-group";
115
124
  if (entry.$input === "media") return "media";
@@ -130,10 +139,13 @@ export function inferInputType(entry) {
130
139
  * @param {ProjectConfig | null | undefined} projectConfig — parsed project.json
131
140
  * @returns {{ name: string; schema: ContentTypeSchema } | null}
132
141
  */
133
- export function findContentTypeSchema(documentPath, projectConfig) {
142
+ export function findContentTypeSchema(
143
+ documentPath: string | null,
144
+ projectConfig: ProjectConfig | null | undefined,
145
+ ) {
134
146
  if (!documentPath || !projectConfig?.contentTypes) return null;
135
147
  for (const [name, def] of Object.entries(
136
- /** @type {Record<string, ContentTypeDef>} */ (projectConfig.contentTypes),
148
+ projectConfig.contentTypes as Record<string, ContentTypeDef>,
137
149
  )) {
138
150
  if (!def.source || !def.schema) continue;
139
151
  const src = def.source.replace(/^\.\//, "").replace(/\/$/, "");
@@ -160,7 +172,7 @@ export function findContentTypeSchema(documentPath, projectConfig) {
160
172
  * @param {string} prefix - E.g. "--font-"
161
173
  * @returns {string}
162
174
  */
163
- export function friendlyNameToVar(name, prefix) {
175
+ export function friendlyNameToVar(name: string, prefix: string) {
164
176
  const slug = name
165
177
  .trim()
166
178
  .toLowerCase()
@@ -180,13 +192,13 @@ export function friendlyNameToVar(name, prefix) {
180
192
  * @param {string} prefix
181
193
  * @returns {string}
182
194
  */
183
- export function varDisplayName(varName, prefix) {
195
+ export function varDisplayName(varName: string, prefix: string) {
184
196
  return (
185
197
  varName
186
198
  .replace(new RegExp(`^${prefix.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}`), "")
187
199
  .replace(/^--/, "")
188
200
  .replace(/-/g, " ")
189
- .replace(/\b\w/g, (/** @type {string} */ c) => c.toUpperCase()) || varName
201
+ .replace(/\b\w/g, (c: string) => c.toUpperCase()) || varName
190
202
  );
191
203
  }
192
204
 
@@ -199,7 +211,7 @@ export function varDisplayName(varName, prefix) {
199
211
  * | { kind: "number" }
200
212
  * | { kind: "text" }}
201
213
  */
202
- export function parseCemType(typeText) {
214
+ export function parseCemType(typeText: string | undefined | null) {
203
215
  if (!typeText) return { kind: "text" };
204
216
  const t = typeText
205
217
  .trim()
@@ -1,3 +1,4 @@
1
+ /// <reference lib="dom" />
1
2
  /**
2
3
  * View.js — Transient view state for Jx Studio
3
4
  *
@@ -6,62 +7,59 @@
6
7
  * store.js) makes renderer dependencies explicit.
7
8
  */
8
9
 
9
- /**
10
- * @typedef {{
11
- * panzoomWrap: HTMLElement | null;
12
- * renderGeneration: number;
13
- * centerObserver: ResizeObserver | null;
14
- * needsCenter: boolean;
15
- * panX: number;
16
- * panY: number;
17
- * prevCanvasMode: string | null;
18
- * monacoEditor:
19
- * | (import("monaco-editor").editor.IStandaloneCodeEditor & { _ignoreNextChange?: boolean })
20
- * | null;
21
- * functionEditor:
22
- * | (import("monaco-editor").editor.IStandaloneCodeEditor & {
23
- * _ignoreNextChange?: boolean;
24
- * _editingTarget?: string | null;
25
- * })
26
- * | null;
27
- * componentInlineEdit: {
28
- * el: HTMLElement;
29
- * path: (string | number)[];
30
- * originalText: string;
31
- * mediaName: string | null;
32
- * _outsideHandler?: ((e: MouseEvent) => void) | null;
33
- * [k: string]: unknown;
34
- * } | null;
35
- * inlineEditCleanup: (() => void) | null;
36
- * blockActionBarEl: HTMLElement | null;
37
- * selDragCleanup: (() => void) | null;
38
- * dndCleanups: (() => void)[];
39
- * canvasDndCleanups: (() => void)[];
40
- * canvasEventCleanups: (() => void)[];
41
- * forcedStyleTag: HTMLStyleElement | null;
42
- * forcedAttrEl: HTMLElement | null;
43
- * elementsCollapsed: Set<string>;
44
- * elementsFilter: string;
45
- * lastDragInput: { clientX: number; clientY: number; [k: string]: unknown } | null;
46
- * _currentDropTargetRow: HTMLElement | null;
47
- * layerDragSourceHeight: number;
48
- * savedRange: Range | null;
49
- * _completionRegistered: boolean;
50
- * stylebookElToTag: WeakMap<Element, string>;
51
- * showAddBreakpointForm: boolean;
52
- * addBreakpointPreview: string;
53
- * layoutSelection: unknown;
54
- * leftTab: string;
55
- * leftPanelCollapsed: boolean;
56
- * rightPanelCollapsed: boolean;
57
- * autosaveTimer: ReturnType<typeof setTimeout> | null;
58
- * _layersCollapsed: Set<string> | null;
59
- * [key: string]: unknown;
60
- * }} ViewState
61
- */
10
+ interface ViewState {
11
+ panzoomWrap: HTMLElement | null;
12
+ renderGeneration: number;
13
+ centerObserver: ResizeObserver | null;
14
+ needsCenter: boolean;
15
+ panX: number;
16
+ panY: number;
17
+ prevCanvasMode: string | null;
18
+ monacoEditor:
19
+ | (import("monaco-editor").editor.IStandaloneCodeEditor & { _ignoreNextChange?: boolean })
20
+ | null;
21
+ functionEditor:
22
+ | (import("monaco-editor").editor.IStandaloneCodeEditor & {
23
+ _ignoreNextChange?: boolean;
24
+ _editingTarget?: string | null;
25
+ })
26
+ | null;
27
+ componentInlineEdit: {
28
+ el: HTMLElement;
29
+ path: (string | number)[];
30
+ originalText: string;
31
+ mediaName: string | null;
32
+ _outsideHandler?: ((e: MouseEvent) => void) | null;
33
+ [k: string]: unknown;
34
+ } | null;
35
+ inlineEditCleanup: (() => void) | null;
36
+ blockActionBarEl: HTMLElement | null;
37
+ selDragCleanup: (() => void) | null;
38
+ dndCleanups: (() => void)[];
39
+ canvasDndCleanups: (() => void)[];
40
+ canvasEventCleanups: (() => void)[];
41
+ forcedStyleTag: HTMLStyleElement | null;
42
+ forcedAttrEl: HTMLElement | null;
43
+ elementsCollapsed: Set<string>;
44
+ elementsFilter: string;
45
+ lastDragInput: { clientX: number; clientY: number; [k: string]: unknown } | null;
46
+ _currentDropTargetRow: HTMLElement | null;
47
+ layerDragSourceHeight: number;
48
+ savedRange: Range | null;
49
+ _completionRegistered: boolean;
50
+ stylebookElToTag: WeakMap<Element, string>;
51
+ showAddBreakpointForm: boolean;
52
+ addBreakpointPreview: string;
53
+ layoutSelection: unknown;
54
+ leftTab: string;
55
+ leftPanelCollapsed: boolean;
56
+ rightPanelCollapsed: boolean;
57
+ autosaveTimer: ReturnType<typeof setTimeout> | null;
58
+ _layersCollapsed: Set<string> | null;
59
+ [key: string]: unknown;
60
+ }
62
61
 
63
- /** @type {ViewState} */
64
- export const view = {
62
+ export const view: ViewState = {
65
63
  // Canvas infrastructure
66
64
  panzoomWrap: null,
67
65
  renderGeneration: 0,