@jxsuite/studio 0.23.1 → 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 +2231 -715
  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} +110 -85
  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} +38 -26
  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
  * Shortcuts.js — Keyboard shortcuts for Jx Studio
3
4
  *
@@ -5,8 +6,8 @@
5
6
  * shortcuts on the canvas / document.
6
7
  */
7
8
 
8
- import { getNodeAtPath, parentElementPath, childIndex, canvasWrap } from "../store.js";
9
- import { activeTab, workspace, closeTab } from "../workspace/workspace.js";
9
+ import { getNodeAtPath, parentElementPath, childIndex, canvasWrap } from "../store";
10
+ import { activeTab, workspace, closeTab } from "../workspace/workspace";
10
11
  import {
11
12
  transactDoc,
12
13
  mutateInsertNode,
@@ -14,13 +15,13 @@ import {
14
15
  mutateDuplicateNode,
15
16
  undo as tabUndo,
16
17
  redo as tabRedo,
17
- } from "../tabs/transact.js";
18
- import { isEditing, stopEditing } from "./inline-edit.js";
19
- import { copyNode, cutNode, pasteNode } from "./context-menu.js";
20
- import { openQuickSearch } from "../panels/quick-search.js";
21
- import { showConfirmDialog } from "../ui/layers.js";
18
+ } from "../tabs/transact";
19
+ import { isEditing, stopEditing } from "./inline-edit";
20
+ import { copyNode, cutNode, pasteNode } from "./context-menu";
21
+ import { openQuickSearch } from "../panels/quick-search";
22
+ import { showConfirmDialog } from "../ui/layers";
22
23
 
23
- /** @typedef {import("../state.js").JxPath} JxPath */
24
+ import type { JxPath } from "../state";
24
25
 
25
26
  /**
26
27
  * Initialise all keyboard (and wheel/pointer) shortcuts.
@@ -38,11 +39,24 @@ import { showConfirmDialog } from "../ui/layers.js";
38
39
  * enterEditOnPath: (path: JxPath) => void;
39
40
  * }} getContext
40
41
  */
41
- export function initShortcuts(getContext) {
42
+ export function initShortcuts(
43
+ getContext: () => {
44
+ canvasMode: string;
45
+ panX: number;
46
+ panY: number;
47
+ setPan: (x: number, y: number) => void;
48
+ applyTransform: () => void;
49
+ positionZoomIndicator: () => void;
50
+ componentInlineEdit: Record<string, unknown> | null;
51
+ saveFile: () => void;
52
+ openProject: () => void;
53
+ enterEditOnPath: (path: JxPath) => void;
54
+ },
55
+ ) {
42
56
  // Wheel handler: Ctrl+Scroll = zoom (cursor-centered), plain scroll = pan
43
57
  canvasWrap.addEventListener(
44
58
  "wheel",
45
- (/** @type {WheelEvent} */ e) => {
59
+ (e: WheelEvent) => {
46
60
  const { canvasMode, panX, panY, setPan, applyTransform } = getContext();
47
61
  // Edit (content) mode: let the scroll container handle scrolling natively
48
62
  if (canvasMode === "edit") return;
@@ -60,7 +74,7 @@ export function initShortcuts(getContext) {
60
74
  const ratio = newZoom / oldZoom;
61
75
  // Adjust pan so the point under cursor stays stationary
62
76
  setPan(cursorX - (cursorX - panX) * ratio, cursorY - (cursorY - panY) * ratio);
63
- activeTab.value.session.ui.zoom = newZoom;
77
+ activeTab.value!.session.ui.zoom = newZoom;
64
78
  } else if (e.shiftKey) {
65
79
  // Shift+scroll = horizontal pan
66
80
  setPan(panX - e.deltaY, panY);
@@ -74,7 +88,7 @@ export function initShortcuts(getContext) {
74
88
  );
75
89
 
76
90
  // Middle-mouse drag panning
77
- canvasWrap.addEventListener("pointerdown", (/** @type {PointerEvent} */ e) => {
91
+ canvasWrap.addEventListener("pointerdown", (e: PointerEvent) => {
78
92
  const ctx = getContext();
79
93
  if (ctx.canvasMode === "edit") return; // no panning in edit mode
80
94
  if (e.button !== 1) return; // middle button only
@@ -82,7 +96,7 @@ export function initShortcuts(getContext) {
82
96
  canvasWrap.setPointerCapture(e.pointerId);
83
97
  let lastX = e.clientX,
84
98
  lastY = e.clientY;
85
- const onMove = (/** @type {PointerEvent} */ ev) => {
99
+ const onMove = (ev: PointerEvent) => {
86
100
  const { panX, panY, setPan, applyTransform } = getContext();
87
101
  setPan(panX + (ev.clientX - lastX), panY + (ev.clientY - lastY));
88
102
  lastX = ev.clientX;
@@ -187,8 +201,8 @@ export function initShortcuts(getContext) {
187
201
  break;
188
202
  case "z":
189
203
  e.preventDefault();
190
- if (e.shiftKey) tabRedo(activeTab.value);
191
- else tabUndo(activeTab.value);
204
+ if (e.shiftKey) tabRedo(activeTab.value!);
205
+ else tabUndo(activeTab.value!);
192
206
  break;
193
207
  case "d":
194
208
  e.preventDefault();
@@ -212,7 +226,7 @@ export function initShortcuts(getContext) {
212
226
  case "0":
213
227
  if (canvasMode === "edit") break;
214
228
  e.preventDefault();
215
- activeTab.value.session.ui.zoom = 1;
229
+ activeTab.value!.session.ui.zoom = 1;
216
230
  setPan(16, 16);
217
231
  applyTransform();
218
232
  break;
@@ -220,13 +234,13 @@ export function initShortcuts(getContext) {
220
234
  case "+":
221
235
  if (canvasMode === "edit") break;
222
236
  e.preventDefault();
223
- activeTab.value.session.ui.zoom = Math.min(5.0, (tab?.session.ui.zoom ?? 1) * 1.2);
237
+ activeTab.value!.session.ui.zoom = Math.min(5.0, (tab?.session.ui.zoom ?? 1) * 1.2);
224
238
  applyTransform();
225
239
  break;
226
240
  case "-":
227
241
  if (canvasMode === "edit") break;
228
242
  e.preventDefault();
229
- activeTab.value.session.ui.zoom = Math.max(0.05, (tab?.session.ui.zoom ?? 1) / 1.2);
243
+ activeTab.value!.session.ui.zoom = Math.max(0.05, (tab?.session.ui.zoom ?? 1) / 1.2);
230
244
  applyTransform();
231
245
  break;
232
246
  }
@@ -243,13 +257,13 @@ export function initShortcuts(getContext) {
243
257
  }
244
258
  break;
245
259
  case "Escape":
246
- activeTab.value.session.selection = null;
260
+ activeTab.value!.session.selection = null;
247
261
  break;
248
262
  case "Enter":
249
263
  if (tab?.session.selection && tab.session.selection.length >= 2) {
250
264
  e.preventDefault();
251
- const pp = /** @type {JxPath} */ (parentElementPath(tab.session.selection));
252
- const idx = /** @type {number} */ (childIndex(tab.session.selection));
265
+ const pp = parentElementPath(tab.session.selection) as JxPath;
266
+ const idx = childIndex(tab.session.selection) as number;
253
267
  const newPath = [...pp, "children", idx + 1];
254
268
  transactDoc(tab, (t) => {
255
269
  mutateInsertNode(t, pp, idx + 1, { tagName: "p", textContent: "" });
@@ -269,7 +283,7 @@ export function initShortcuts(getContext) {
269
283
  case "ArrowLeft":
270
284
  e.preventDefault();
271
285
  if (tab?.session.selection && tab.session.selection.length >= 2) {
272
- activeTab.value.session.selection = parentElementPath(tab.session.selection);
286
+ activeTab.value!.session.selection = parentElementPath(tab.session.selection);
273
287
  }
274
288
  break;
275
289
  case "ArrowRight":
@@ -277,7 +291,7 @@ export function initShortcuts(getContext) {
277
291
  if (tab?.session.selection) {
278
292
  const node = getNodeAtPath(tab.doc.document, tab.session.selection);
279
293
  if ((node?.children?.length ?? 0) > 0) {
280
- activeTab.value.session.selection = [...tab.session.selection, "children", 0];
294
+ activeTab.value!.session.selection = [...tab.session.selection, "children", 0];
281
295
  }
282
296
  }
283
297
  break;
@@ -287,8 +301,8 @@ export function initShortcuts(getContext) {
287
301
  // Block ctrl+scroll (browser zoom) on all non-canvas areas
288
302
  document.addEventListener(
289
303
  "wheel",
290
- (/** @type {WheelEvent} */ e) => {
291
- if ((e.ctrlKey || e.metaKey) && !canvasWrap.contains(/** @type {Node} */ (e.target))) {
304
+ (e: WheelEvent) => {
305
+ if ((e.ctrlKey || e.metaKey) && !canvasWrap.contains(e.target as Node)) {
292
306
  e.preventDefault();
293
307
  }
294
308
  },
@@ -297,23 +311,23 @@ export function initShortcuts(getContext) {
297
311
  }
298
312
 
299
313
  /** @param {number} [direction] */
300
- function navigateSelection(direction = -1) {
314
+ function navigateSelection(direction: number = -1) {
301
315
  const tab = activeTab.value;
302
316
  if (!tab?.session.selection) {
303
- activeTab.value.session.selection = [];
317
+ activeTab.value!.session.selection = [];
304
318
  return;
305
319
  }
306
320
  if (tab.session.selection.length < 2) return;
307
321
 
308
322
  const parent = getNodeAtPath(
309
323
  tab.doc.document,
310
- /** @type {JxPath} */ (parentElementPath(tab.session.selection)),
324
+ parentElementPath(tab.session.selection) as JxPath,
311
325
  );
312
- const idx = /** @type {number} */ (childIndex(tab.session.selection));
326
+ const idx = childIndex(tab.session.selection) as number;
313
327
  const newIdx = idx + direction;
314
328
  if (parent?.children && newIdx >= 0 && newIdx < parent.children.length) {
315
- activeTab.value.session.selection = [
316
- .../** @type {JxPath} */ (parentElementPath(tab.session.selection)),
329
+ activeTab.value!.session.selection = [
330
+ ...(parentElementPath(tab.session.selection) as JxPath),
317
331
  "children",
318
332
  newIdx,
319
333
  ];
@@ -1,3 +1,4 @@
1
+ /// <reference lib="dom" />
1
2
  /**
2
3
  * Slash-menu.js — Shared slash command menu for element insertion
3
4
  *
@@ -9,7 +10,13 @@
9
10
 
10
11
  import { html, render as litRender, nothing } from "lit-html";
11
12
  import { ref } from "lit-html/directives/ref.js";
12
- import { getLayerSlot } from "../ui/layers.js";
13
+ import { getLayerSlot } from "../ui/layers";
14
+
15
+ interface SlashCommand {
16
+ label: string;
17
+ tag: string;
18
+ description: string;
19
+ }
13
20
 
14
21
  // ─── Commands ─────────────────────────────────────────────────────────────────
15
22
 
@@ -31,30 +38,20 @@ const SLASH_COMMANDS = [
31
38
  { label: "Section", tag: "section", description: "Section container" },
32
39
  ];
33
40
 
34
- /** @typedef {{ label: string; tag: string; description: string }} SlashCommand */
35
-
36
41
  // ─── State ────────────────────────────────────────────────────────────────────
37
42
 
38
- /**
39
- * @type {{
40
- * onSelect: (cmd: SlashCommand) => void;
41
- * showFilter?: boolean;
42
- * commands?: SlashCommand[];
43
- * } | null}
44
- */
45
- let callbacks = null;
43
+ let callbacks: {
44
+ onSelect: (cmd: SlashCommand) => void;
45
+ showFilter?: boolean;
46
+ commands?: SlashCommand[];
47
+ } | null = null;
46
48
  let activeIdx = 0;
47
- /** @type {SlashCommand[]} */
48
- let filteredItems = [];
49
+ let filteredItems: SlashCommand[] = [];
49
50
  let open = false;
50
- /** @type {HTMLElement | null} */
51
- let _anchorEl = null;
52
- /** @type {DOMRect | null} */
53
- let _anchorRect = null;
54
- /** @type {HTMLInputElement | null} */
55
- let _filterEl = null;
56
- /** @type {HTMLElement | null} */
57
- let _popoverEl = null;
51
+ let _anchorEl: HTMLElement | null = null;
52
+ let _anchorRect: DOMRect | null = null;
53
+ let _filterEl: HTMLInputElement | null = null;
54
+ let _popoverEl: HTMLElement | null = null;
58
55
 
59
56
  /** @returns {HTMLElement} */
60
57
  function getHost() {
@@ -79,7 +76,11 @@ export function isSlashMenuOpen() {
79
76
  * commands?: SlashCommand[];
80
77
  * }} cbs
81
78
  */
82
- export function showSlashMenu(anchorEl, filter, cbs) {
79
+ export function showSlashMenu(
80
+ anchorEl: HTMLElement,
81
+ filter: string,
82
+ cbs: { onSelect: (cmd: SlashCommand) => void; showFilter?: boolean; commands?: SlashCommand[] },
83
+ ) {
83
84
  callbacks = cbs;
84
85
  _anchorEl = anchorEl;
85
86
  _anchorRect = anchorEl.getBoundingClientRect();
@@ -135,7 +136,7 @@ export function dismissSlashMenu() {
135
136
  * @param {HTMLElement} anchorEl
136
137
  * @param {boolean} showFilter
137
138
  */
138
- function render(anchorEl, showFilter) {
139
+ function render(anchorEl: HTMLElement, showFilter: boolean) {
139
140
  const rect = _anchorRect || anchorEl.getBoundingClientRect();
140
141
 
141
142
  litRender(
@@ -143,7 +144,7 @@ function render(anchorEl, showFilter) {
143
144
  <sp-popover
144
145
  open
145
146
  ${ref((el) => {
146
- _popoverEl = /** @type {HTMLElement | null} */ (el || null);
147
+ _popoverEl = (el as HTMLElement | undefined) || null;
147
148
  })}
148
149
  style="position:fixed;left:${rect.left}px;top:${rect.bottom +
149
150
  4}px;z-index:9999;max-height:320px;overflow-y:auto"
@@ -156,7 +157,7 @@ function render(anchorEl, showFilter) {
156
157
  autocomplete="off"
157
158
  style="display:block;width:100%;box-sizing:border-box;padding:6px 10px;border:none;border-bottom:1px solid var(--border, #444);outline:none;font-size:13px;background:transparent;color:inherit"
158
159
  ${ref((el) => {
159
- _filterEl = /** @type {HTMLInputElement | null} */ (el || null);
160
+ _filterEl = (el as HTMLInputElement | undefined) || null;
160
161
  })}
161
162
  @input=${onFilterInput}
162
163
  />`
@@ -167,7 +168,7 @@ function render(anchorEl, showFilter) {
167
168
  (cmd, i) => html`
168
169
  <sp-menu-item
169
170
  ?focused=${i === 0}
170
- @click=${(/** @type {Event} */ e) => {
171
+ @click=${(e: Event) => {
171
172
  e.preventDefault();
172
173
  e.stopPropagation();
173
174
  select(cmd);
@@ -189,22 +190,22 @@ function render(anchorEl, showFilter) {
189
190
  }
190
191
 
191
192
  /** @param {MouseEvent} e */
192
- function onOutsideClick(e) {
193
- if (_popoverEl && !_popoverEl.contains(/** @type {Node} */ (e.target))) {
193
+ function onOutsideClick(e: MouseEvent) {
194
+ if (_popoverEl && !_popoverEl.contains(e.target as Node)) {
194
195
  dismissSlashMenu();
195
196
  }
196
197
  }
197
198
 
198
199
  /** @param {SlashCommand} cmd */
199
- function select(cmd) {
200
+ function select(cmd: SlashCommand) {
200
201
  const cbs = callbacks;
201
202
  dismissSlashMenu();
202
203
  cbs?.onSelect(cmd);
203
204
  }
204
205
 
205
206
  /** @param {Event} e */
206
- function onFilterInput(e) {
207
- const input = /** @type {HTMLInputElement} */ (e.target);
207
+ function onFilterInput(e: Event) {
208
+ const input = e.target as HTMLInputElement;
208
209
  const filter = input.value.toLowerCase();
209
210
 
210
211
  const source = callbacks?.commands || SLASH_COMMANDS;
@@ -227,12 +228,10 @@ function onFilterInput(e) {
227
228
  }
228
229
 
229
230
  /** @param {KeyboardEvent} e */
230
- function onKeydown(e) {
231
+ function onKeydown(e: KeyboardEvent) {
231
232
  if (!open) return;
232
233
 
233
- const items = /** @type {NodeListOf<Element>} */ (
234
- getHost().querySelectorAll("sp-menu-item:not([disabled])")
235
- );
234
+ const items = getHost().querySelectorAll("sp-menu-item:not([disabled])") as NodeListOf<Element>;
236
235
 
237
236
  if (e.key === "ArrowDown") {
238
237
  e.preventDefault();
@@ -1,21 +1,18 @@
1
1
  /** Component registry — cached list of project components discovered via the platform. */
2
2
 
3
- import { getPlatform } from "../platform.js";
4
- import { projectState } from "../store.js";
3
+ import { getPlatform } from "../platform";
4
+ import { projectState } from "../store";
5
5
 
6
- /**
7
- * @typedef {{
8
- * tagName: string;
9
- * path: string;
10
- * props?: { name: string; type?: string; [k: string]: unknown }[];
11
- * source?: string;
12
- * package?: string;
13
- * modulePath?: string;
14
- * }} ComponentEntry
15
- */
6
+ export interface ComponentEntry {
7
+ tagName: string;
8
+ path: string;
9
+ props?: { name: string; type?: string; [k: string]: unknown }[];
10
+ source?: string;
11
+ package?: string;
12
+ modulePath?: string;
13
+ }
16
14
 
17
- /** @type {ComponentEntry[]} */
18
- export let componentRegistry = [];
15
+ export let componentRegistry: ComponentEntry[] = [];
19
16
  export let _componentRegistryLoaded = false;
20
17
 
21
18
  export async function loadComponentRegistry() {
@@ -32,7 +29,7 @@ export async function loadComponentRegistry() {
32
29
  * @param {string | null} fromDocPath
33
30
  * @param {string} toCompPath
34
31
  */
35
- export function computeRelativePath(fromDocPath, toCompPath) {
32
+ export function computeRelativePath(fromDocPath: string | null, toCompPath: string) {
36
33
  if (!fromDocPath) return `./${toCompPath}`;
37
34
  const from = fromDocPath.replaceAll("\\", "/");
38
35
  const to = toCompPath.replaceAll("\\", "/");
@@ -1,3 +1,4 @@
1
+ /// <reference lib="dom" />
1
2
  /**
2
3
  * File Operations — open, save, export documents.
3
4
  *
@@ -9,19 +10,21 @@ import remarkStringify from "remark-stringify";
9
10
  import remarkDirective from "remark-directive";
10
11
  import remarkGfm from "remark-gfm";
11
12
  import { stringify as stringifyYaml } from "yaml";
12
- import { jxToMd, jxDocToMd } from "../markdown/md-convert.js";
13
- import { locateDocument } from "../services/code-services.js";
14
- import { statusMessage } from "../panels/statusbar.js";
15
- import { getPlatform } from "../platform.js";
16
- import { activeTab, openTab } from "../workspace/workspace.js";
17
- import { isEditing, stopEditing } from "../editor/inline-edit.js";
13
+ import { jxToMd, jxDocToMd } from "../markdown/md-convert";
14
+ import { locateDocument } from "../services/code-services";
15
+ import { statusMessage } from "../panels/statusbar";
16
+ import { getPlatform } from "../platform";
17
+ import { activeTab, openTab } from "../workspace/workspace";
18
+ import { isEditing, stopEditing } from "../editor/inline-edit";
19
+
20
+ import type { JxElement, JxMutableNode } from "@jxsuite/schema/types";
18
21
 
19
22
  /** Open a file via the File System Access API (or fallback input). */
20
23
  export async function openFile() {
21
24
  try {
22
25
  if ("showOpenFilePicker" in window) {
23
- const [handle] = await /** @type {{ showOpenFilePicker: Function }} */ (
24
- /** @type {unknown} */ (window)
26
+ const [handle] = await (
27
+ window as unknown as { showOpenFilePicker: Function }
25
28
  ).showOpenFilePicker({
26
29
  types: [
27
30
  { description: "Jx Component", accept: { "application/json": [".json"] } },
@@ -70,9 +73,8 @@ export async function openFile() {
70
73
  };
71
74
  input.click();
72
75
  }
73
- } catch (/** @type {unknown} */ e) {
74
- if (/** @type {Error} */ (e).name !== "AbortError")
75
- statusMessage(`Error: ${/** @type {Error} */ (e).message}`);
76
+ } catch (e) {
77
+ if ((e as Error).name !== "AbortError") statusMessage(`Error: ${(e as Error).message}`);
76
78
  }
77
79
  }
78
80
 
@@ -82,9 +84,9 @@ export async function openFile() {
82
84
  * @param {string} source Markdown text
83
85
  * @returns {Promise<{ document: JxMutableNode; frontmatter: Record<string, unknown> }>}
84
86
  */
85
- export async function loadMarkdown(source) {
87
+ export async function loadMarkdown(source: string) {
86
88
  const { transpileJxMarkdown } = await import("@jxsuite/parser/transpile");
87
- const doc = /** @type {JxMutableNode} */ (transpileJxMarkdown(source));
89
+ const doc = transpileJxMarkdown(source) as JxMutableNode;
88
90
 
89
91
  const isComponent = doc.tagName && String(doc.tagName).includes("-");
90
92
 
@@ -95,12 +97,18 @@ export async function loadMarkdown(source) {
95
97
  // Content markdown — children form the root-level document body
96
98
  const children = doc.children ?? [];
97
99
  if (children.length === 0) children.push({ tagName: "p", children: [] });
98
- const contentDoc = { children };
99
100
 
100
- /** @type {Record<string, unknown>} */
101
- const frontmatter = {};
101
+ const documentKeys = new Set(["state", "imports"]);
102
+ const contentDoc: Record<string, unknown> = { children };
103
+
104
+ const frontmatter: Record<string, unknown> = {};
102
105
  for (const [key, value] of Object.entries(doc)) {
103
- if (key !== "children") frontmatter[key] = value;
106
+ if (key === "children") continue;
107
+ if (documentKeys.has(key)) {
108
+ contentDoc[key] = value;
109
+ } else {
110
+ frontmatter[key] = value;
111
+ }
104
112
  }
105
113
 
106
114
  return { document: contentDoc, frontmatter };
@@ -136,9 +144,8 @@ export async function saveFile() {
136
144
  } else {
137
145
  statusMessage("No save target — use Export");
138
146
  }
139
- } catch (/** @type {unknown} */ e) {
140
- if (/** @type {Error} */ (e).name !== "AbortError")
141
- statusMessage(`Save error: ${/** @type {Error} */ (e).message}`);
147
+ } catch (e) {
148
+ if ((e as Error).name !== "AbortError") statusMessage(`Save error: ${(e as Error).message}`);
142
149
  }
143
150
  }
144
151
 
@@ -159,8 +166,8 @@ export async function exportFile() {
159
166
  : isContent
160
167
  ? "content.md"
161
168
  : "component.json";
162
- const handle = await /** @type {{ showSaveFilePicker: Function }} */ (
163
- /** @type {unknown} */ (window)
169
+ const handle = await (
170
+ window as unknown as { showSaveFilePicker: Function }
164
171
  ).showSaveFilePicker({
165
172
  suggestedName,
166
173
  types: [{ description, accept: { [mimeType]: [ext] } }],
@@ -182,9 +189,8 @@ export async function exportFile() {
182
189
  tab.doc.dirty = false;
183
190
  statusMessage("Downloaded");
184
191
  }
185
- } catch (/** @type {unknown} */ e) {
186
- if (/** @type {Error} */ (e).name !== "AbortError")
187
- statusMessage(`Export error: ${/** @type {Error} */ (e).message}`);
192
+ } catch (e) {
193
+ if ((e as Error).name !== "AbortError") statusMessage(`Export error: ${(e as Error).message}`);
188
194
  }
189
195
  }
190
196
 
@@ -194,19 +200,20 @@ export async function exportFile() {
194
200
  * @param {import("../tabs/tab.js").Tab} tab
195
201
  * @returns {string}
196
202
  */
197
- export function serializeDocument(tab) {
203
+ export function serializeDocument(tab: import("../tabs/tab.js").Tab) {
198
204
  if (tab.doc.sourceFormat === "md") {
199
205
  const fm = tab.doc.content?.frontmatter || {};
200
- const fullDoc = { ...fm, children: tab.doc.document.children ?? [] };
206
+ const doc = tab.doc.document;
207
+ const fullDoc = { ...fm, ...doc, children: doc.children ?? [] };
201
208
  return jxDocToMd(/** @type {JxMutableNode} */ (fullDoc));
202
209
  }
203
210
  if (tab.doc.mode === "content") {
204
- const mdast = jxToMd(/** @type {JxElement} */ (tab.doc.document));
211
+ const mdast = jxToMd(tab.doc.document as JxElement);
205
212
  const md = unified()
206
213
  .use(remarkGfm)
207
214
  .use(remarkDirective)
208
215
  .use(remarkStringify, { bullet: "-", emphasis: "*", strong: "*" })
209
- .stringify(/** @type {import("mdast").Root} */ (/** @type {unknown} */ (mdast)));
216
+ .stringify(mdast as unknown as import("mdast").Root);
210
217
  const fm = tab.doc.content?.frontmatter;
211
218
  const hasFrontmatter = fm && Object.keys(fm).length > 0;
212
219
  return hasFrontmatter ? `---\n${stringifyYaml(fm).trim()}\n---\n\n${md}` : md;