@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
  * Md-convert.js — Bidirectional mdast ↔ Jx conversion
3
4
  *
@@ -14,9 +15,12 @@ import { unified } from "unified";
14
15
  import remarkStringify from "remark-stringify";
15
16
  import remarkDirective from "remark-directive";
16
17
  import remarkGfm from "remark-gfm";
17
- import { MD_ALL } from "./md-allowlist.js";
18
+ import { MD_ALL } from "./md-allowlist";
18
19
  import { htmlToJx } from "@jxsuite/parser/transpile";
19
20
 
21
+ import type { JxElement, JxMutableNode } from "@jxsuite/schema/types";
22
+ import type { MdastNode } from "@jxsuite/parser/types";
23
+
20
24
  // ─── mdast → Jx ──────────────────────────────────────────────────────────
21
25
 
22
26
  /**
@@ -24,8 +28,8 @@ import { htmlToJx } from "@jxsuite/parser/transpile";
24
28
  *
25
29
  * @type {Record<string, (n: MdastNode) => string>}
26
30
  */
27
- const MDAST_TAG_MAP = {
28
- heading: (/** @type {MdastNode} */ n) => `h${n.depth}`,
31
+ const MDAST_TAG_MAP: Record<string, (n: MdastNode) => string> = {
32
+ heading: (n: MdastNode) => `h${n.depth}`,
29
33
  paragraph: () => "p",
30
34
  text: () => "span",
31
35
  emphasis: () => "em",
@@ -35,13 +39,13 @@ const MDAST_TAG_MAP = {
35
39
  link: () => "a",
36
40
  image: () => "img",
37
41
  blockquote: () => "blockquote",
38
- list: (/** @type {MdastNode} */ n) => (n.ordered ? "ol" : "ul"),
42
+ list: (n: MdastNode) => (n.ordered ? "ol" : "ul"),
39
43
  listItem: () => "li",
40
44
  code: () => "pre",
41
45
  thematicBreak: () => "hr",
42
46
  table: () => "table",
43
47
  tableRow: () => "tr",
44
- tableCell: (/** @type {MdastNode} */ n) => (n.isHeader ? "th" : "td"),
48
+ tableCell: (n: MdastNode) => (n.isHeader ? "th" : "td"),
45
49
  break: () => "br",
46
50
  };
47
51
 
@@ -51,25 +55,23 @@ const MDAST_TAG_MAP = {
51
55
  * @param {MdastNode} mdast - Root mdast node (type: 'root')
52
56
  * @returns {JxElement} Jx element tree
53
57
  */
54
- export function mdToJx(mdast) {
58
+ export function mdToJx(mdast: MdastNode) {
55
59
  if (mdast.type === "root") {
56
60
  return {
57
- children: /** @type {(JxElement | string)[]} */ (
58
- (mdast.children ?? [])
59
- .filter((/** @type {MdastNode} */ n) => n.type !== "yaml" && n.type !== "toml")
60
- .flatMap(convertMdastNode)
61
- .filter(Boolean)
62
- ),
61
+ children: (mdast.children ?? [])
62
+ .filter((n: MdastNode) => n.type !== "yaml" && n.type !== "toml")
63
+ .flatMap(convertMdastNode)
64
+ .filter(Boolean) as (JxElement | string)[],
63
65
  };
64
66
  }
65
- return /** @type {JxElement} */ (convertMdastNode(mdast));
67
+ return convertMdastNode(mdast) as JxElement;
66
68
  }
67
69
 
68
70
  /**
69
71
  * @param {MdastNode} node
70
72
  * @returns {JxElement | null}
71
73
  */
72
- function convertMdastNode(node) {
74
+ function convertMdastNode(node: MdastNode) {
73
75
  if (!node) return null;
74
76
 
75
77
  // Directive nodes → custom elements
@@ -84,62 +86,62 @@ function convertMdastNode(node) {
84
86
  if (node.type === "html") {
85
87
  if (!node.value) return null;
86
88
  const nodes = htmlToJx(node.value);
87
- return nodes.length === 1
88
- ? /** @type {JxElement} */ (nodes[0])
89
- : { tagName: "div", children: nodes };
89
+ return nodes.length === 1 ? (nodes[0] as JxElement) : { tagName: "div", children: nodes };
90
90
  }
91
91
 
92
92
  const tagFn = MDAST_TAG_MAP[node.type];
93
93
  if (!tagFn) return null;
94
94
 
95
95
  const tag = tagFn(node);
96
- /** @type {JxElement} */
97
- const el = { tagName: tag };
96
+ const el: JxElement = { tagName: tag };
98
97
 
99
98
  switch (node.type) {
100
99
  case "heading":
101
100
  case "paragraph": {
102
101
  // If contains only a single text child, flatten to textContent
103
102
  if (node.children?.length === 1 && node.children[0].type === "text") {
104
- el.textContent = node.children[0].value;
103
+ el.textContent = node.children[0].value ?? null;
105
104
  } else if (node.children?.length) {
106
- el.children = /** @type {(JxElement | string)[]} */ (
107
- node.children.flatMap(convertMdastNode).filter(Boolean)
108
- );
105
+ el.children = node.children.flatMap(convertMdastNode).filter(Boolean) as (
106
+ | JxElement
107
+ | string
108
+ )[];
109
109
  }
110
110
  break;
111
111
  }
112
112
 
113
113
  case "text":
114
- el.textContent = node.value;
114
+ el.textContent = node.value ?? null;
115
115
  break;
116
116
 
117
117
  case "emphasis":
118
118
  case "strong":
119
119
  case "delete": {
120
120
  if (node.children?.length === 1 && node.children[0].type === "text") {
121
- el.textContent = node.children[0].value;
121
+ el.textContent = node.children[0].value ?? null;
122
122
  } else if (node.children?.length) {
123
- el.children = /** @type {(JxElement | string)[]} */ (
124
- node.children.flatMap(convertMdastNode).filter(Boolean)
125
- );
123
+ el.children = node.children.flatMap(convertMdastNode).filter(Boolean) as (
124
+ | JxElement
125
+ | string
126
+ )[];
126
127
  }
127
128
  break;
128
129
  }
129
130
 
130
131
  case "inlineCode":
131
- el.textContent = node.value;
132
+ el.textContent = node.value ?? null;
132
133
  break;
133
134
 
134
135
  case "link":
135
136
  el.attributes = { href: node.url ?? "" };
136
137
  if (node.title) el.attributes.title = node.title;
137
138
  if (node.children?.length === 1 && node.children[0].type === "text") {
138
- el.textContent = node.children[0].value;
139
+ el.textContent = node.children[0].value ?? null;
139
140
  } else if (node.children?.length) {
140
- el.children = /** @type {(JxElement | string)[]} */ (
141
- node.children.flatMap(convertMdastNode).filter(Boolean)
142
- );
141
+ el.children = node.children.flatMap(convertMdastNode).filter(Boolean) as (
142
+ | JxElement
143
+ | string
144
+ )[];
143
145
  }
144
146
  break;
145
147
 
@@ -151,17 +153,19 @@ function convertMdastNode(node) {
151
153
  case "blockquote":
152
154
  case "listItem":
153
155
  if (node.children?.length) {
154
- el.children = /** @type {(JxElement | string)[]} */ (
155
- node.children.flatMap(convertMdastNode).filter(Boolean)
156
- );
156
+ el.children = node.children.flatMap(convertMdastNode).filter(Boolean) as (
157
+ | JxElement
158
+ | string
159
+ )[];
157
160
  }
158
161
  break;
159
162
 
160
163
  case "list":
161
164
  if (node.children?.length) {
162
- el.children = /** @type {(JxElement | string)[]} */ (
163
- node.children.flatMap(convertMdastNode).filter(Boolean)
164
- );
165
+ el.children = node.children.flatMap(convertMdastNode).filter(Boolean) as (
166
+ | JxElement
167
+ | string
168
+ )[];
165
169
  }
166
170
  if (node.start != null && node.start !== 1) {
167
171
  el.attributes = { start: String(node.start) };
@@ -173,7 +177,7 @@ function convertMdastNode(node) {
173
177
  el.children = [
174
178
  {
175
179
  tagName: "code",
176
- textContent: node.value,
180
+ textContent: node.value ?? null,
177
181
  ...(node.lang ? { attributes: { class: `language-${node.lang}` } } : {}),
178
182
  },
179
183
  ];
@@ -186,36 +190,36 @@ function convertMdastNode(node) {
186
190
 
187
191
  case "table": {
188
192
  // Mdast tables have rows directly; split into thead/tbody
189
- const rows = /** @type {JxElement[]} */ (
190
- (node.children ?? []).flatMap(convertMdastNode).filter(Boolean)
191
- );
193
+ const rows = (node.children ?? []).flatMap(convertMdastNode).filter(Boolean) as JxElement[];
192
194
  const thead =
193
195
  rows.length > 0
194
- ? { tagName: "thead", children: /** @type {(JxElement | string)[]} */ ([rows[0]]) }
196
+ ? { tagName: "thead", children: [rows[0]] as (JxElement | string)[] }
195
197
  : null;
196
198
  const tbody =
197
199
  rows.length > 1
198
- ? { tagName: "tbody", children: /** @type {(JxElement | string)[]} */ (rows.slice(1)) }
200
+ ? { tagName: "tbody", children: rows.slice(1) as (JxElement | string)[] }
199
201
  : null;
200
- el.children = /** @type {(JxElement | string)[]} */ ([thead, tbody].filter(Boolean));
202
+ el.children = [thead, tbody].filter(Boolean) as (JxElement | string)[];
201
203
  break;
202
204
  }
203
205
 
204
206
  case "tableRow":
205
207
  if (node.children?.length) {
206
- el.children = /** @type {(JxElement | string)[]} */ (
207
- node.children.flatMap(convertMdastNode).filter(Boolean)
208
- );
208
+ el.children = node.children.flatMap(convertMdastNode).filter(Boolean) as (
209
+ | JxElement
210
+ | string
211
+ )[];
209
212
  }
210
213
  break;
211
214
 
212
215
  case "tableCell":
213
216
  if (node.children?.length === 1 && node.children[0].type === "text") {
214
- el.textContent = node.children[0].value;
217
+ el.textContent = node.children[0].value ?? null;
215
218
  } else if (node.children?.length) {
216
- el.children = /** @type {(JxElement | string)[]} */ (
217
- node.children.flatMap(convertMdastNode).filter(Boolean)
218
- );
219
+ el.children = node.children.flatMap(convertMdastNode).filter(Boolean) as (
220
+ | JxElement
221
+ | string
222
+ )[];
219
223
  }
220
224
  break;
221
225
  }
@@ -227,25 +231,23 @@ function convertMdastNode(node) {
227
231
  * @param {MdastNode} node
228
232
  * @returns {JxElement | null}
229
233
  */
230
- function convertDirective(node) {
231
- /** @type {JxElement} */
232
- const el = { tagName: node.name };
234
+ function convertDirective(node: MdastNode) {
235
+ const el: JxElement = { tagName: node.name ?? "div" };
233
236
  if (node.attributes && Object.keys(node.attributes).length > 0) {
234
237
  el.attributes = { ...node.attributes };
235
238
  }
236
239
  if (node.type === "textDirective") {
237
240
  // Text directives place label as textContent
238
241
  if (node.children?.length === 1 && node.children[0].type === "text") {
239
- el.textContent = node.children[0].value;
242
+ el.textContent = node.children[0].value ?? null;
240
243
  } else if (node.children?.length) {
241
- el.children = /** @type {(JxElement | string)[]} */ (
242
- node.children.flatMap(convertMdastNode).filter(Boolean)
243
- );
244
+ el.children = node.children.flatMap(convertMdastNode).filter(Boolean) as (
245
+ | JxElement
246
+ | string
247
+ )[];
244
248
  }
245
249
  } else if (node.type === "containerDirective" && node.children?.length) {
246
- el.children = /** @type {(JxElement | string)[]} */ (
247
- node.children.flatMap(convertMdastNode).filter(Boolean)
248
- );
250
+ el.children = node.children.flatMap(convertMdastNode).filter(Boolean) as (JxElement | string)[];
249
251
  }
250
252
  return el;
251
253
  }
@@ -308,15 +310,11 @@ const TAG_MDAST_MAP = {
308
310
  * @param {JxElement} jx - Jx element tree (root content div)
309
311
  * @returns {MdastNode} Mdast root node
310
312
  */
311
- export function jxToMd(jx) {
312
- const childArray = /** @type {(JxElement | string)[]} */ (
313
- Array.isArray(jx.children) ? jx.children : []
314
- );
315
- const children = /** @type {MdastNode[]} */ (
316
- childArray
317
- .map((/** @type {JxElement | string} */ child) => convertJxNode(child, true))
318
- .filter(Boolean)
319
- );
313
+ export function jxToMd(jx: JxElement) {
314
+ const childArray = Array.isArray(jx.children) ? jx.children : ([] as (JxElement | string)[]);
315
+ const children = childArray
316
+ .map((child: JxElement | string) => convertJxNode(child, true))
317
+ .filter(Boolean) as MdastNode[];
320
318
 
321
319
  return { type: "root", children };
322
320
  }
@@ -328,7 +326,7 @@ export function jxToMd(jx) {
328
326
  * @param {JxElement} el
329
327
  * @returns {boolean}
330
328
  */
331
- function hasJxProps(el) {
329
+ function hasJxProps(el: JxElement) {
332
330
  for (const key of Object.keys(el)) {
333
331
  if (
334
332
  key === "tagName" ||
@@ -350,7 +348,7 @@ function hasJxProps(el) {
350
348
  * @param {boolean} isBlock - Whether this element is in a block context
351
349
  * @returns {MdastNode | null} Mdast node
352
350
  */
353
- function convertJxNode(el, isBlock) {
351
+ function convertJxNode(el: JxElement | string | number, isBlock: boolean): MdastNode | null {
354
352
  // Bare string/number text nodes → mdast text nodes
355
353
  if (typeof el === "string" || typeof el === "number") {
356
354
  return { type: "text", value: String(el) };
@@ -364,7 +362,7 @@ function convertJxNode(el, isBlock) {
364
362
  return convertToDirective(el, isBlock);
365
363
  }
366
364
 
367
- const mdastType = /** @type {Record<string, string>} */ (TAG_MDAST_MAP)[tag];
365
+ const mdastType = (TAG_MDAST_MAP as Record<string, string>)[tag];
368
366
  if (!mdastType) return null;
369
367
 
370
368
  switch (mdastType) {
@@ -398,17 +396,17 @@ function convertJxNode(el, isBlock) {
398
396
  case "link":
399
397
  return {
400
398
  type: "link",
401
- url: /** @type {string} */ (el.attributes?.href) ?? "",
402
- title: /** @type {string | null} */ (el.attributes?.title) ?? null,
399
+ url: (el.attributes?.href as string) ?? "",
400
+ title: (el.attributes?.title as string | null) ?? null,
403
401
  children: inlineChildren(el),
404
402
  };
405
403
 
406
404
  case "image":
407
405
  return {
408
406
  type: "image",
409
- url: /** @type {string} */ (el.attributes?.src) ?? "",
410
- alt: /** @type {string} */ (el.attributes?.alt) ?? "",
411
- title: /** @type {string | null} */ (el.attributes?.title) ?? null,
407
+ url: (el.attributes?.src as string) ?? "",
408
+ alt: (el.attributes?.alt as string) ?? "",
409
+ title: (el.attributes?.title as string | null) ?? null,
412
410
  };
413
411
 
414
412
  case "blockquote":
@@ -421,14 +419,11 @@ function convertJxNode(el, isBlock) {
421
419
  return {
422
420
  type: "list",
423
421
  ordered: tag === "ol",
424
- start:
425
- tag === "ol" ? parseInt(/** @type {string} */ (el.attributes?.start), 10) || 1 : null,
422
+ start: tag === "ol" ? parseInt(el.attributes?.start as string, 10) || 1 : null,
426
423
  spread: false,
427
- children: /** @type {MdastNode[]} */ (
428
- /** @type {(JxElement | string)[]} */ (el.children ?? [])
429
- .map((/** @type {JxElement | string} */ c) => convertJxNode(c, true))
430
- .filter(Boolean)
431
- ),
424
+ children: ((el.children ?? []) as (JxElement | string)[])
425
+ .map((c: JxElement | string) => convertJxNode(c, true))
426
+ .filter(Boolean) as MdastNode[],
432
427
  };
433
428
 
434
429
  case "listItem":
@@ -440,10 +435,10 @@ function convertJxNode(el, isBlock) {
440
435
 
441
436
  case "code": {
442
437
  // pre > code → fenced code block
443
- const codeChild = /** @type {JxElement | undefined} */ (
444
- Array.isArray(el.children) ? el.children[0] : undefined
445
- );
446
- const langClass = /** @type {string} */ (codeChild?.attributes?.class) ?? "";
438
+ const codeChild = Array.isArray(el.children)
439
+ ? (el.children[0] as JxElement | undefined)
440
+ : undefined;
441
+ const langClass = (codeChild?.attributes?.class as string) ?? "";
447
442
  const lang = langClass.replace("language-", "") || null;
448
443
  return {
449
444
  type: "code",
@@ -460,12 +455,11 @@ function convertJxNode(el, isBlock) {
460
455
 
461
456
  case "table": {
462
457
  // Flatten thead/tbody back to rows
463
- /** @type {MdastNode[]} */
464
- const rows = [];
465
- for (const section of /** @type {(JxElement | string)[]} */ (el.children ?? [])) {
458
+ const rows: MdastNode[] = [];
459
+ for (const section of (el.children ?? []) as (JxElement | string)[]) {
466
460
  if (typeof section === "string") continue;
467
461
  if (section.tagName === "thead" || section.tagName === "tbody") {
468
- for (const row of /** @type {(JxElement | string)[]} */ (section.children ?? [])) {
462
+ for (const row of (section.children ?? []) as (JxElement | string)[]) {
469
463
  const mdRow = convertJxNode(row, true);
470
464
  if (mdRow) {
471
465
  // Mark header cells
@@ -488,11 +482,9 @@ function convertJxNode(el, isBlock) {
488
482
  case "tableRow":
489
483
  return {
490
484
  type: "tableRow",
491
- children: /** @type {MdastNode[]} */ (
492
- /** @type {(JxElement | string)[]} */ (el.children ?? [])
493
- .map((/** @type {JxElement | string} */ c) => convertJxNode(c, false))
494
- .filter(Boolean)
495
- ),
485
+ children: ((el.children ?? []) as (JxElement | string)[])
486
+ .map((c: JxElement | string) => convertJxNode(c, false))
487
+ .filter(Boolean) as MdastNode[],
496
488
  };
497
489
 
498
490
  case "tableCell":
@@ -512,15 +504,13 @@ function convertJxNode(el, isBlock) {
512
504
  * @param {JxElement} el
513
505
  * @returns {MdastNode[]}
514
506
  */
515
- function inlineChildren(el) {
507
+ function inlineChildren(el: JxElement): MdastNode[] {
516
508
  if (el.textContent != null) {
517
509
  return [{ type: "text", value: String(el.textContent) }];
518
510
  }
519
- return /** @type {MdastNode[]} */ (
520
- /** @type {(JxElement | string)[]} */ (el.children ?? [])
521
- .map((/** @type {JxElement | string} */ c) => convertJxNode(c, false))
522
- .filter(Boolean)
523
- );
511
+ return ((el.children ?? []) as (JxElement | string)[])
512
+ .map((c: JxElement | string) => convertJxNode(c, false))
513
+ .filter(Boolean) as MdastNode[];
524
514
  }
525
515
 
526
516
  /**
@@ -529,16 +519,14 @@ function inlineChildren(el) {
529
519
  * @param {JxElement} el
530
520
  * @returns {MdastNode[]}
531
521
  */
532
- function blockChildren(el) {
522
+ function blockChildren(el: JxElement): MdastNode[] {
533
523
  if (el.textContent != null) {
534
524
  // Wrap bare text in a paragraph
535
525
  return [{ type: "paragraph", children: [{ type: "text", value: String(el.textContent) }] }];
536
526
  }
537
- return /** @type {MdastNode[]} */ (
538
- /** @type {(JxElement | string)[]} */ (el.children ?? [])
539
- .map((/** @type {JxElement | string} */ c) => convertJxNode(c, true))
540
- .filter(Boolean)
541
- );
527
+ return ((el.children ?? []) as (JxElement | string)[])
528
+ .map((c: JxElement | string) => convertJxNode(c, true))
529
+ .filter(Boolean) as MdastNode[];
542
530
  }
543
531
 
544
532
  /**
@@ -549,27 +537,24 @@ function blockChildren(el) {
549
537
  * @param {JxElement} el
550
538
  * @returns {Record<string, string>}
551
539
  */
552
- function collectDirectiveAttrs(el) {
553
- /** @type {Record<string, unknown>} */
554
- const propsObj = {};
540
+ function collectDirectiveAttrs(el: JxElement) {
541
+ const propsObj: Record<string, unknown> = {};
555
542
 
556
543
  for (const [key, value] of Object.entries(el)) {
557
- if (
558
- key === "tagName" ||
559
- key === "children" ||
560
- key === "textContent" ||
561
- key === "innerHTML" ||
562
- key === "attributes"
563
- )
544
+ if (key === "tagName" || key === "textContent" || key === "innerHTML" || key === "attributes")
564
545
  continue;
546
+ if (key === "children") {
547
+ if (value && typeof value === "object" && !Array.isArray(value)) {
548
+ propsObj.children = value;
549
+ }
550
+ continue;
551
+ }
565
552
  propsObj[key] = value;
566
553
  }
567
554
 
568
555
  // Merge HTML attributes
569
556
  if (el.attributes) {
570
- for (const [key, value] of Object.entries(
571
- /** @type {Record<string, unknown>} */ (el.attributes),
572
- )) {
557
+ for (const [key, value] of Object.entries(el.attributes as Record<string, unknown>)) {
573
558
  propsObj[key] = value;
574
559
  }
575
560
  }
@@ -585,8 +570,8 @@ function collectDirectiveAttrs(el) {
585
570
  * @param {boolean} isBlock
586
571
  * @returns {MdastNode}
587
572
  */
588
- function convertToDirective(el, isBlock) {
589
- const tag = /** @type {string} */ (el.tagName) ?? "div";
573
+ function convertToDirective(el: JxElement, isBlock: boolean): MdastNode {
574
+ const tag = (el.tagName as string) ?? "div";
590
575
  const attrs = collectDirectiveAttrs(el);
591
576
 
592
577
  if (!isBlock) {
@@ -598,16 +583,19 @@ function convertToDirective(el, isBlock) {
598
583
  children:
599
584
  el.textContent != null
600
585
  ? [{ type: "text", value: String(el.textContent) }]
601
- : /** @type {MdastNode[]} */ (
602
- /** @type {(JxElement | string)[]} */ (el.children ?? [])
603
- .map((/** @type {JxElement | string} */ c) => convertJxNode(c, false))
604
- .filter(Boolean)
605
- ),
586
+ : (((el.children ?? []) as (JxElement | string)[])
587
+ .map((c: JxElement | string) => convertJxNode(c, false))
588
+ .filter(Boolean) as MdastNode[]),
606
589
  };
607
590
  }
608
591
 
609
592
  // Block without children → leafDirective
610
- const childArray = /** @type {(JxElement | string)[] | undefined} */ (el.children);
593
+ const rawChildren = el.children;
594
+ const childrenIsObject =
595
+ rawChildren && typeof rawChildren === "object" && !Array.isArray(rawChildren);
596
+ const childArray = childrenIsObject
597
+ ? undefined
598
+ : (rawChildren as (JxElement | string)[] | undefined);
611
599
  if (!childArray?.length && el.textContent == null) {
612
600
  return {
613
601
  type: "leafDirective",
@@ -627,19 +615,15 @@ function convertToDirective(el, isBlock) {
627
615
  } else if (INLINE_CONTENT_TAGS.has(tag)) {
628
616
  // Tags with inline content model: wrap all children in a single paragraph
629
617
  // so remark serializes them as one continuous inline flow
630
- const inlineNodes = /** @type {MdastNode[]} */ (
631
- /** @type {(JxElement | string)[]} */ (el.children ?? [])
632
- .map((/** @type {JxElement | string} */ c) => convertJxNode(c, false))
633
- .filter(Boolean)
634
- );
618
+ const inlineNodes = ((el.children ?? []) as (JxElement | string)[])
619
+ .map((c: JxElement | string) => convertJxNode(c, false))
620
+ .filter(Boolean) as MdastNode[];
635
621
  directiveChildren =
636
622
  inlineNodes.length > 0 ? [{ type: "paragraph", children: inlineNodes }] : [];
637
623
  } else {
638
- directiveChildren = /** @type {MdastNode[]} */ (
639
- /** @type {(JxElement | string)[]} */ (el.children ?? [])
640
- .map((/** @type {JxElement | string} */ c) => convertJxNode(c, true))
641
- .filter(Boolean)
642
- );
624
+ directiveChildren = ((el.children ?? []) as (JxElement | string)[])
625
+ .map((c: JxElement | string) => convertJxNode(c, true))
626
+ .filter(Boolean) as MdastNode[];
643
627
  }
644
628
 
645
629
  return {
@@ -696,15 +680,13 @@ const JX_ANNOTATION_KEYS = new Set(["$title", "$description"]);
696
680
  * @param {JxMutableNode} doc - Jx JSON document
697
681
  * @returns {string} Jx Markdown source
698
682
  */
699
- export function jxDocToMd(doc) {
683
+ export function jxDocToMd(doc: JxMutableNode) {
700
684
  const { stringify: stringifyYaml } = yamlImport();
701
685
 
702
- /** @type {string[]} */
703
- const lines = [];
686
+ const lines: string[] = [];
704
687
 
705
688
  // Emit YAML frontmatter
706
- /** @type {Record<string, unknown>} */
707
- const frontmatter = {};
689
+ const frontmatter: Record<string, unknown> = {};
708
690
  for (const [key, value] of Object.entries(doc)) {
709
691
  if (key === "children") continue;
710
692
  frontmatter[key] = value;
@@ -719,20 +701,16 @@ export function jxDocToMd(doc) {
719
701
 
720
702
  // Convert children to mdast and stringify with remark
721
703
  if (Array.isArray(doc.children) && doc.children.length > 0) {
722
- const mdastChildren = /** @type {MdastNode[]} */ (
723
- doc.children
724
- .map((/** @type {JxMutableNode | string} */ child) =>
725
- convertJxNode(/** @type {JxElement | string} */ (child), true),
726
- )
727
- .filter(Boolean)
728
- );
704
+ const mdastChildren = doc.children
705
+ .map((child: JxMutableNode | string) => convertJxNode(child as JxElement | string, true))
706
+ .filter(Boolean) as MdastNode[];
729
707
 
730
708
  const mdast = { type: "root", children: mdastChildren };
731
709
  const md = unified()
732
710
  .use(remarkGfm)
733
711
  .use(remarkDirective)
734
712
  .use(remarkStringify, { bullet: "-", emphasis: "*", strong: "*" })
735
- .stringify(/** @type {import("mdast").Root} */ (/** @type {unknown} */ (mdast)));
713
+ .stringify(mdast as unknown as import("mdast").Root);
736
714
 
737
715
  lines.push(/** @type {string} */ (md));
738
716
  }
@@ -750,7 +728,7 @@ export function jxDocToMd(doc) {
750
728
  *
751
729
  * @returns {{ stringify: (v: unknown) => string }}
752
730
  */
753
- let _yaml = /** @type {{ stringify: (v: unknown) => string } | null} */ (null);
731
+ let _yaml: { stringify: (v: unknown) => string } | null = null;
754
732
  function yamlImport() {
755
733
  if (!_yaml) {
756
734
  // Dynamic require avoided; use the yaml package already available in studio
@@ -767,7 +745,7 @@ function yamlImport() {
767
745
  * @param {number} indent
768
746
  * @returns {string}
769
747
  */
770
- function yamlStringifySimple(value, indent = 0) {
748
+ function yamlStringifySimple(value: unknown, indent: number = 0): string {
771
749
  if (value === null || value === undefined) return "null";
772
750
  if (typeof value === "boolean") return String(value);
773
751
  if (typeof value === "number") return String(value);
@@ -785,13 +763,13 @@ function yamlStringifySimple(value, indent = 0) {
785
763
  if (value.length === 0) return "[]";
786
764
  return value
787
765
  .map((item) => {
788
- const itemStr = yamlStringifySimple(item, indent + 1);
766
+ const itemStr: string = yamlStringifySimple(item, indent + 1);
789
767
  if (typeof item === "object" && item !== null && !Array.isArray(item)) {
790
768
  // Object items: first key on same line as -, rest indented
791
- const objLines = itemStr.split("\n");
769
+ const objLines: string[] = itemStr.split("\n");
792
770
  return `${prefix}- ${objLines[0]}\n${objLines
793
771
  .slice(1)
794
- .map((l) => `${prefix} ${l}`)
772
+ .map((l: string) => `${prefix} ${l}`)
795
773
  .join("\n")}`;
796
774
  }
797
775
  return `${prefix}- ${itemStr}`;
@@ -800,11 +778,11 @@ function yamlStringifySimple(value, indent = 0) {
800
778
  }
801
779
 
802
780
  if (typeof value === "object") {
803
- const entries = Object.entries(/** @type {Record<string, unknown>} */ (value));
781
+ const entries = Object.entries(value as Record<string, unknown>);
804
782
  if (entries.length === 0) return "{}";
805
783
  return entries
806
784
  .map(([k, v]) => {
807
- const valStr = yamlStringifySimple(v, indent + 1);
785
+ const valStr: string = yamlStringifySimple(v, indent + 1);
808
786
  if (typeof v === "object" && v !== null) {
809
787
  return `${prefix}${k}:\n${valStr}`;
810
788
  }
@@ -823,11 +801,10 @@ function yamlStringifySimple(value, indent = 0) {
823
801
  * @param {Record<string, unknown>} propsObj
824
802
  * @returns {Record<string, string>}
825
803
  */
826
- function collapsePropsToAttrMap(propsObj) {
827
- /** @type {Record<string, string>} */
828
- const result = {};
804
+ function collapsePropsToAttrMap(propsObj: Record<string, unknown>) {
805
+ const result: Record<string, string> = {};
829
806
 
830
- function walk(/** @type {Record<string, unknown>} */ obj, /** @type {string} */ prefix) {
807
+ function walk(obj: Record<string, unknown>, prefix: string) {
831
808
  for (const [key, value] of Object.entries(obj)) {
832
809
  let mdAttrKey = key;
833
810
  // Strip $ prefix for Jx keywords
@@ -846,7 +823,7 @@ function collapsePropsToAttrMap(propsObj) {
846
823
  if (key === "@") {
847
824
  // Bare "@" (no media name) — treat contents as base-level style props
848
825
  if (value && typeof value === "object" && !Array.isArray(value)) {
849
- walk(/** @type {Record<string, unknown>} */ (value), prefix);
826
+ walk(value as Record<string, unknown>, prefix);
850
827
  }
851
828
  continue;
852
829
  }
@@ -857,7 +834,7 @@ function collapsePropsToAttrMap(propsObj) {
857
834
  const fullKey = prefix ? `${prefix}.${mdAttrKey}` : mdAttrKey;
858
835
 
859
836
  if (value && typeof value === "object" && !Array.isArray(value)) {
860
- walk(/** @type {Record<string, unknown>} */ (value), fullKey);
837
+ walk(value as Record<string, unknown>, fullKey);
861
838
  } else {
862
839
  result[fullKey] = String(value);
863
840
  }