@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,22 +1,27 @@
1
+ /// <reference lib="dom" />
2
+ interface RecentProject {
3
+ name: string;
4
+ root: string;
5
+ timestamp: number;
6
+ }
7
+
8
+ interface RecentFile {
9
+ path: string;
10
+ name: string;
11
+ timestamp: number;
12
+ }
13
+
1
14
  const STORAGE_KEY = "jx-studio-recent-projects";
2
15
  const FILES_STORAGE_KEY = "jx-studio-recent-files";
3
16
  const MAX_RECENT = 8;
4
17
  const MAX_RECENT_FILES = 10;
5
18
 
6
- /**
7
- * @typedef {{ name: string; root: string; timestamp: number }} RecentProject
8
- *
9
- * @typedef {{ path: string; name: string; timestamp: number }} RecentFile
10
- */
11
-
12
19
  /** @returns {RecentProject[]} */
13
20
  export function getRecentProjects() {
14
21
  try {
15
22
  const raw = localStorage.getItem(STORAGE_KEY);
16
23
  if (!raw) return [];
17
- return /** @type {RecentProject[]} */ (JSON.parse(raw)).sort(
18
- (a, b) => b.timestamp - a.timestamp,
19
- );
24
+ return (JSON.parse(raw) as RecentProject[]).sort((a, b) => b.timestamp - a.timestamp);
20
25
  } catch {
21
26
  return [];
22
27
  }
@@ -26,7 +31,7 @@ export function getRecentProjects() {
26
31
  * @param {string} name
27
32
  * @param {string} root
28
33
  */
29
- export function addRecentProject(name, root) {
34
+ export function addRecentProject(name: string, root: string) {
30
35
  const projects = getRecentProjects().filter((p) => p.root !== root);
31
36
  projects.unshift({ name, root, timestamp: Date.now() });
32
37
  if (projects.length > MAX_RECENT) projects.length = MAX_RECENT;
@@ -42,14 +47,14 @@ export function getRecentFiles() {
42
47
  try {
43
48
  const raw = localStorage.getItem(FILES_STORAGE_KEY);
44
49
  if (!raw) return [];
45
- return /** @type {RecentFile[]} */ (JSON.parse(raw)).sort((a, b) => b.timestamp - a.timestamp);
50
+ return (JSON.parse(raw) as RecentFile[]).sort((a, b) => b.timestamp - a.timestamp);
46
51
  } catch {
47
52
  return [];
48
53
  }
49
54
  }
50
55
 
51
56
  /** @param {{ path: string; name: string }} file */
52
- export function trackRecentFile(file) {
57
+ export function trackRecentFile(file: { path: string; name: string }) {
53
58
  const recent = getRecentFiles().filter((f) => f.path !== file.path);
54
59
  recent.unshift({ path: file.path, name: file.name, timestamp: Date.now() });
55
60
  if (recent.length > MAX_RECENT_FILES) recent.length = MAX_RECENT_FILES;
@@ -1,14 +1,12 @@
1
- /**
2
- * @type {{
3
- * __jxPlatform?: {
4
- * windowControls?: {
5
- * getFrame: () => Promise<{ x: number; y: number; width: number; height: number }>;
6
- * setFrame: (x: number, y: number, w: number, h: number) => void;
7
- * };
8
- * };
9
- * }}
10
- */
11
- const g = /** @type {any} */ (globalThis);
1
+ /// <reference lib="dom" />
2
+ const g = globalThis as unknown as {
3
+ __jxPlatform?: {
4
+ windowControls?: {
5
+ getFrame: () => Promise<{ x: number; y: number; width: number; height: number }>;
6
+ setFrame: (x: number, y: number, w: number, h: number) => void;
7
+ };
8
+ };
9
+ };
12
10
 
13
11
  const EDGES = [
14
12
  "top",
@@ -52,7 +50,14 @@ export function mountResizeEdges() {
52
50
  * setFrame: (x: number, y: number, w: number, h: number) => void;
53
51
  * }} wc
54
52
  */
55
- async function startResize(e, edge, wc) {
53
+ async function startResize(
54
+ e: MouseEvent,
55
+ edge: string,
56
+ wc: {
57
+ getFrame: () => Promise<{ x: number; y: number; width: number; height: number }>;
58
+ setFrame: (x: number, y: number, w: number, h: number) => void;
59
+ },
60
+ ) {
56
61
  e.preventDefault();
57
62
  e.stopPropagation();
58
63
 
@@ -62,7 +67,7 @@ async function startResize(e, edge, wc) {
62
67
  const startFrame = { ...frame };
63
68
 
64
69
  /** @param {MouseEvent} me */
65
- function onMove(me) {
70
+ function onMove(me: MouseEvent) {
66
71
  const dx = me.screenX - startX;
67
72
  const dy = me.screenY - startY;
68
73
 
@@ -1,13 +1,13 @@
1
+ /// <reference lib="dom" />
2
+ import type { JxMutableNode } from "@jxsuite/schema/types";
3
+
1
4
  /** Collect slot elements from the document tree. */
2
- export function collectSlots(
3
- /** @type {JxMutableNode | null | undefined} */ node,
4
- /** @type {string[]} */ slots = [],
5
- ) {
5
+ export function collectSlots(node: JxMutableNode | null | undefined, slots: string[] = []) {
6
6
  if (node?.tagName === "slot") {
7
7
  slots.push(node.attributes?.name || "");
8
8
  }
9
9
  if (Array.isArray(node?.children))
10
- node.children.forEach((/** @type {JxMutableNode | string} */ c) =>
10
+ node.children.forEach((c: JxMutableNode | string) =>
11
11
  collectSlots(typeof c === "string" ? undefined : c, slots),
12
12
  );
13
13
  return slots;
@@ -23,7 +23,14 @@ export function collectSlots(
23
23
  * collectCssParts: (node: JxMutableNode) => { name: string }[];
24
24
  * }} helpers
25
25
  */
26
- export function exportCemManifest(S, helpers) {
26
+ export function exportCemManifest(
27
+ S: { document: JxMutableNode },
28
+ helpers: {
29
+ defCategory: (d: unknown) => string;
30
+ normParam: (p: unknown) => unknown;
31
+ collectCssParts: (node: JxMutableNode) => { name: string }[];
32
+ },
33
+ ) {
27
34
  const { defCategory, normParam, collectCssParts } = helpers;
28
35
  const doc = S.document;
29
36
  const tagName = doc.tagName;
@@ -88,7 +95,7 @@ export function exportCemManifest(S, helpers) {
88
95
 
89
96
  // Slots
90
97
  const slotNames = collectSlots(doc);
91
- const slots = slotNames.map((/** @type {string} */ name) => ({
98
+ const slots = slotNames.map((name: string) => ({
92
99
  name: name || "",
93
100
  ...(name ? {} : { description: "Default slot" }),
94
101
  }));
@@ -1,15 +1,26 @@
1
1
  /** OXC code services (server-backed) */
2
2
 
3
- import { getPlatform } from "../platform.js";
4
- import { projectState } from "../state.js";
5
- import { getNodeAtPath } from "../store.js";
3
+ import { getPlatform } from "../platform";
4
+ import { projectState } from "../state";
5
+ import { getNodeAtPath } from "../store";
6
6
  import * as monaco from "monaco-editor/esm/vs/editor/editor.api.js";
7
+ import type { JxPath } from "../state";
8
+ import type { JxMutableNode } from "@jxsuite/schema/types";
9
+
10
+ export interface OxLintDiagnostic {
11
+ severity: string;
12
+ message: string;
13
+ help?: string;
14
+ code?: string;
15
+ url?: string;
16
+ labels?: { span: { line: number; column: number; length?: number } }[];
17
+ }
7
18
 
8
19
  /**
9
20
  * @param {string} action
10
21
  * @param {unknown} payload
11
22
  */
12
- export async function codeService(action, payload) {
23
+ export async function codeService(action: string, payload: unknown) {
13
24
  const platform = getPlatform();
14
25
  if (!platform.codeService) return null;
15
26
  return platform.codeService(action, payload);
@@ -20,7 +31,7 @@ export async function codeService(action, payload) {
20
31
  *
21
32
  * @param {string} name
22
33
  */
23
- export async function locateDocument(name) {
34
+ export async function locateDocument(name: string) {
24
35
  const platform = getPlatform();
25
36
  if (!platform.locateFile) return null;
26
37
  return platform.locateFile(name);
@@ -36,7 +47,10 @@ export const pluginSchemaCache = new Map();
36
47
  * @param {{ $src?: string; $prototype?: string }} def
37
48
  * @param {{ documentPath?: string }} state
38
49
  */
39
- export async function fetchPluginSchema(def, state) {
50
+ export async function fetchPluginSchema(
51
+ def: { $src?: string; $prototype?: string },
52
+ state: { documentPath?: string },
53
+ ) {
40
54
  const importedPath = def.$prototype
41
55
  ? projectState?.projectConfig?.imports?.[def.$prototype]
42
56
  : null;
@@ -62,22 +76,14 @@ export async function fetchPluginSchema(def, state) {
62
76
  }
63
77
  }
64
78
 
65
- /**
66
- * @typedef {{
67
- * severity: string;
68
- * message: string;
69
- * help?: string;
70
- * code?: string;
71
- * url?: string;
72
- * labels?: { span: { line: number; column: number; length?: number } }[];
73
- * }} OxLintDiagnostic
74
- */
75
-
76
79
  /**
77
80
  * @param {import("monaco-editor").editor.IStandaloneCodeEditor} editor
78
81
  * @param {OxLintDiagnostic[]} diagnostics
79
82
  */
80
- export function setLintMarkers(editor, diagnostics) {
83
+ export function setLintMarkers(
84
+ editor: import("monaco-editor").editor.IStandaloneCodeEditor,
85
+ diagnostics: OxLintDiagnostic[],
86
+ ) {
81
87
  const model = editor.getModel();
82
88
  if (!model) return;
83
89
  const markers = diagnostics
@@ -101,7 +107,7 @@ export function setLintMarkers(editor, diagnostics) {
101
107
  monaco.editor.setModelMarkers(
102
108
  model,
103
109
  "oxlint",
104
- /** @type {import("monaco-editor").editor.IMarkerData[]} */ (markers),
110
+ markers as import("monaco-editor").editor.IMarkerData[],
105
111
  );
106
112
  }
107
113
 
@@ -109,7 +115,10 @@ export function setLintMarkers(editor, diagnostics) {
109
115
  * @param {{ type: string; defName?: string; path?: JxPath; eventKey?: string }} editing
110
116
  * @param {JxMutableNode | null | undefined} document
111
117
  */
112
- export function getFunctionArgs(editing, document) {
118
+ export function getFunctionArgs(
119
+ editing: { type: string; defName?: string; path?: JxPath; eventKey?: string },
120
+ document: JxMutableNode | null | undefined,
121
+ ) {
113
122
  if (editing.type === "def") {
114
123
  const defName = editing.defName;
115
124
  return (defName && document?.state?.[defName]?.parameters) || ["state", "event"];
@@ -9,8 +9,7 @@ import "monaco-editor/esm/vs/basic-languages/javascript/javascript.contribution.
9
9
  import jxSchema from "@jxsuite/schema/schema.json";
10
10
  import projectSchema from "@jxsuite/schema/project-schema.json";
11
11
 
12
- /** @type {Record<string, string>} */
13
- const WORKER_PATHS = {
12
+ const WORKER_PATHS: Record<string, string> = {
14
13
  json: "/monaco-editor/esm/vs/language/json/json.worker.js",
15
14
  typescript: "/monaco-editor/esm/vs/language/typescript/ts.worker.js",
16
15
  javascript: "/monaco-editor/esm/vs/language/typescript/ts.worker.js",
@@ -18,7 +17,7 @@ const WORKER_PATHS = {
18
17
  };
19
18
 
20
19
  self.MonacoEnvironment = {
21
- getWorker(_, /** @type {string} */ label) {
20
+ getWorker(_, label: string) {
22
21
  const path = WORKER_PATHS[label] || WORKER_PATHS.editorWorkerService;
23
22
  return new Worker(path, { type: "module" });
24
23
  },
@@ -1,3 +1,4 @@
1
+ /// <reference lib="dom" />
1
2
  /**
2
3
  * Content Types Editor — visual schema builder for project content types.
3
4
  *
@@ -7,19 +8,16 @@
7
8
 
8
9
  import { html, render as litRender } from "lit-html";
9
10
  import { repeat } from "lit-html/directives/repeat.js";
10
- import { getPlatform } from "../platform.js";
11
- import { projectState } from "../store.js";
12
- import {
13
- fieldCardTpl,
14
- addFieldFormTpl,
15
- schemaForType,
16
- detectFieldFormat,
17
- } from "./schema-field-ui.js";
11
+ import { getPlatform } from "../platform";
12
+ import { projectState } from "../store";
13
+ import { fieldCardTpl, addFieldFormTpl, schemaForType, detectFieldFormat } from "./schema-field-ui";
14
+ import { toCamelCase } from "../utils/studio-utils";
15
+
16
+ import type { ProjectConfig, ContentTypeSchema } from "@jxsuite/schema/types";
18
17
 
19
18
  // ─── Module state ─────────────────────────────────────────────────────────────
20
19
 
21
- /** @type {string | null} */
22
- let selectedContentType = null;
20
+ let selectedContentType: string | null = null;
23
21
  let showAddField = false;
24
22
  let newFieldState = { name: "", type: "string", format: "", required: false };
25
23
  let showNewContentType = false;
@@ -29,22 +27,24 @@ let newContentTypeName = "";
29
27
 
30
28
  async function saveProjectConfig() {
31
29
  const platform = getPlatform();
32
- const config = /** @type {{ projectConfig: ProjectConfig }} */ (projectState).projectConfig;
30
+ const config = (projectState as { projectConfig: ProjectConfig }).projectConfig;
33
31
  await platform.writeFile("project.json", JSON.stringify(config, null, "\t"));
34
32
  }
35
33
 
36
34
  // ─── Helpers ─────────────────────────────────────────────────────────────────
37
35
 
38
36
  /** Get the schema object for the selected content type. */
39
- function getSelectedSchema() {
37
+ function getSelectedSchema(): ContentTypeSchema | undefined {
40
38
  const config = projectState?.projectConfig;
41
- return config?.contentTypes?.[/** @type {string} */ (selectedContentType)]?.schema;
39
+ return config?.contentTypes?.[selectedContentType as string]?.schema as
40
+ | ContentTypeSchema
41
+ | undefined;
42
42
  }
43
43
 
44
44
  // ─── Handlers ─────────────────────────────────────────────────────────────────
45
45
 
46
46
  /** @param {() => void} rerender */
47
- function handleNewContentType(rerender) {
47
+ function handleNewContentType(rerender: () => void) {
48
48
  const slug = newContentTypeName
49
49
  .toLowerCase()
50
50
  .replace(/\s+/g, "-")
@@ -74,9 +74,10 @@ function handleNewContentType(rerender) {
74
74
  }
75
75
 
76
76
  /** @param {() => void} rerender */
77
- function handleAddField(rerender) {
78
- const name = newFieldState.name.trim();
79
- if (!name || !selectedContentType) return;
77
+ function handleAddField(rerender: () => void) {
78
+ const raw = newFieldState.name.trim();
79
+ if (!raw || !selectedContentType) return;
80
+ const name = toCamelCase(raw);
80
81
 
81
82
  const schema = getSelectedSchema();
82
83
  if (!schema) return;
@@ -99,13 +100,13 @@ function handleAddField(rerender) {
99
100
  * @param {string} fieldName
100
101
  * @param {() => void} rerender
101
102
  */
102
- function handleDeleteField(fieldName, rerender) {
103
+ function handleDeleteField(fieldName: string, rerender: () => void) {
103
104
  const schema = getSelectedSchema();
104
105
  if (!schema?.properties) return;
105
106
 
106
107
  delete schema.properties[fieldName];
107
108
  if (schema.required) {
108
- schema.required = schema.required.filter((/** @type {string} */ r) => r !== fieldName);
109
+ schema.required = schema.required.filter((r: string) => r !== fieldName);
109
110
  }
110
111
 
111
112
  rerender();
@@ -116,7 +117,7 @@ function handleDeleteField(fieldName, rerender) {
116
117
  * @param {string} fieldName
117
118
  * @param {() => void} rerender
118
119
  */
119
- function handleToggleRequired(fieldName, rerender) {
120
+ function handleToggleRequired(fieldName: string, rerender: () => void) {
120
121
  const schema = getSelectedSchema();
121
122
  if (!schema) return;
122
123
  if (!schema.required) schema.required = [];
@@ -134,21 +135,19 @@ function handleToggleRequired(fieldName, rerender) {
134
135
  * @param {string} newName
135
136
  * @param {() => void} rerender
136
137
  */
137
- function handleRenameField(oldName, newName, rerender) {
138
+ function handleRenameField(oldName: string, newName: string, rerender: () => void) {
138
139
  const schema = getSelectedSchema();
139
- if (!schema?.properties || !newName || schema.properties[newName]) return;
140
+ const normalized = toCamelCase(newName);
141
+ if (!schema?.properties || !normalized || schema.properties[normalized]) return;
140
142
 
141
- /** @type {Record<string, unknown>} */
142
- const newProps = {};
143
+ const newProps: Record<string, unknown> = {};
143
144
  for (const [key, val] of Object.entries(schema.properties)) {
144
- newProps[key === oldName ? newName : key] = val;
145
+ newProps[key === oldName ? normalized : key] = val;
145
146
  }
146
147
  schema.properties = newProps;
147
148
 
148
149
  if (schema.required) {
149
- schema.required = schema.required.map((/** @type {string} */ r) =>
150
- r === oldName ? newName : r,
151
- );
150
+ schema.required = schema.required.map((r: string) => (r === oldName ? normalized : r));
152
151
  }
153
152
 
154
153
  rerender();
@@ -160,7 +159,7 @@ function handleRenameField(oldName, newName, rerender) {
160
159
  * @param {string} newType
161
160
  * @param {() => void} rerender
162
161
  */
163
- function handleChangeType(fieldName, newType, rerender) {
162
+ function handleChangeType(fieldName: string, newType: string, rerender: () => void) {
164
163
  const schema = getSelectedSchema();
165
164
  if (!schema?.properties?.[fieldName]) return;
166
165
 
@@ -178,7 +177,7 @@ function handleChangeType(fieldName, newType, rerender) {
178
177
  * @param {string} format
179
178
  * @param {() => void} rerender
180
179
  */
181
- function handleChangeFormat(fieldName, format, rerender) {
180
+ function handleChangeFormat(fieldName: string, format: string, rerender: () => void) {
182
181
  const schema = getSelectedSchema();
183
182
  if (!schema?.properties?.[fieldName]) return;
184
183
 
@@ -194,7 +193,7 @@ function handleChangeFormat(fieldName, format, rerender) {
194
193
  * @param {string} target
195
194
  * @param {() => void} rerender
196
195
  */
197
- function handleChangeRefTarget(fieldName, target, rerender) {
196
+ function handleChangeRefTarget(fieldName: string, target: string, rerender: () => void) {
198
197
  const schema = getSelectedSchema();
199
198
  if (!schema?.properties) return;
200
199
 
@@ -210,17 +209,24 @@ function handleChangeRefTarget(fieldName, target, rerender) {
210
209
  * @param {{ name: string; type: string; required: boolean }} fieldState
211
210
  * @param {() => void} rerender
212
211
  */
213
- function handleAddNestedField(parentName, fieldState, rerender) {
212
+ function handleAddNestedField(
213
+ parentName: string,
214
+ fieldState: { name: string; type: string; required: boolean },
215
+ rerender: () => void,
216
+ ) {
214
217
  const schema = getSelectedSchema();
215
218
  const parent = schema?.properties?.[parentName];
216
219
  if (!parent) return;
217
220
 
221
+ const name = toCamelCase(fieldState.name);
222
+ if (!name) return;
223
+
218
224
  if (!parent.properties) parent.properties = {};
219
- parent.properties[fieldState.name] = schemaForType(fieldState.type);
225
+ parent.properties[name] = schemaForType(fieldState.type);
220
226
 
221
227
  if (fieldState.required) {
222
228
  if (!parent.required) parent.required = [];
223
- if (!parent.required.includes(fieldState.name)) parent.required.push(fieldState.name);
229
+ if (!parent.required.includes(name)) parent.required.push(name);
224
230
  }
225
231
 
226
232
  rerender();
@@ -232,14 +238,14 @@ function handleAddNestedField(parentName, fieldState, rerender) {
232
238
  * @param {string} childName
233
239
  * @param {() => void} rerender
234
240
  */
235
- function handleDeleteNested(parentName, childName, rerender) {
241
+ function handleDeleteNested(parentName: string, childName: string, rerender: () => void) {
236
242
  const schema = getSelectedSchema();
237
243
  const parent = schema?.properties?.[parentName];
238
244
  if (!parent?.properties) return;
239
245
 
240
246
  delete parent.properties[childName];
241
247
  if (parent.required) {
242
- parent.required = parent.required.filter((/** @type {string} */ r) => r !== childName);
248
+ parent.required = parent.required.filter((r: string) => r !== childName);
243
249
  }
244
250
 
245
251
  rerender();
@@ -251,7 +257,7 @@ function handleDeleteNested(parentName, childName, rerender) {
251
257
  * @param {string} childName
252
258
  * @param {() => void} rerender
253
259
  */
254
- function handleToggleNestedRequired(parentName, childName, rerender) {
260
+ function handleToggleNestedRequired(parentName: string, childName: string, rerender: () => void) {
255
261
  const schema = getSelectedSchema();
256
262
  const parent = schema?.properties?.[parentName];
257
263
  if (!parent) return;
@@ -271,22 +277,25 @@ function handleToggleNestedRequired(parentName, childName, rerender) {
271
277
  * @param {string} newChild
272
278
  * @param {() => void} rerender
273
279
  */
274
- function handleRenameNested(parentName, oldChild, newChild, rerender) {
280
+ function handleRenameNested(
281
+ parentName: string,
282
+ oldChild: string,
283
+ newChild: string,
284
+ rerender: () => void,
285
+ ) {
275
286
  const schema = getSelectedSchema();
276
287
  const parent = schema?.properties?.[parentName];
277
- if (!parent?.properties || !newChild || parent.properties[newChild]) return;
288
+ const normalized = toCamelCase(newChild);
289
+ if (!parent?.properties || !normalized || parent.properties[normalized]) return;
278
290
 
279
- /** @type {Record<string, unknown>} */
280
- const newProps = {};
291
+ const newProps: Record<string, unknown> = {};
281
292
  for (const [key, val] of Object.entries(parent.properties)) {
282
- newProps[key === oldChild ? newChild : key] = val;
293
+ newProps[key === oldChild ? normalized : key] = val;
283
294
  }
284
295
  parent.properties = newProps;
285
296
 
286
297
  if (parent.required) {
287
- parent.required = parent.required.map((/** @type {string} */ r) =>
288
- r === oldChild ? newChild : r,
289
- );
298
+ parent.required = parent.required.map((r: string) => (r === oldChild ? normalized : r));
290
299
  }
291
300
 
292
301
  rerender();
@@ -299,7 +308,12 @@ function handleRenameNested(parentName, oldChild, newChild, rerender) {
299
308
  * @param {string} newType
300
309
  * @param {() => void} rerender
301
310
  */
302
- function handleChangeNestedType(parentName, childName, newType, rerender) {
311
+ function handleChangeNestedType(
312
+ parentName: string,
313
+ childName: string,
314
+ newType: string,
315
+ rerender: () => void,
316
+ ) {
303
317
  const schema = getSelectedSchema();
304
318
  const parent = schema?.properties?.[parentName];
305
319
  if (!parent?.properties?.[childName]) return;
@@ -319,7 +333,12 @@ function handleChangeNestedType(parentName, childName, newType, rerender) {
319
333
  * @param {string} format
320
334
  * @param {() => void} rerender
321
335
  */
322
- function handleChangeNestedFormat(parentName, childName, format, rerender) {
336
+ function handleChangeNestedFormat(
337
+ parentName: string,
338
+ childName: string,
339
+ format: string,
340
+ rerender: () => void,
341
+ ) {
323
342
  const schema = getSelectedSchema();
324
343
  const parent = schema?.properties?.[parentName];
325
344
  if (!parent?.properties?.[childName]) return;
@@ -332,7 +351,7 @@ function handleChangeNestedFormat(parentName, childName, format, rerender) {
332
351
  }
333
352
 
334
353
  /** @param {() => void} rerender */
335
- function handleDeleteContentType(rerender) {
354
+ function handleDeleteContentType(rerender: () => void) {
336
355
  if (!selectedContentType) return;
337
356
  const config = projectState?.projectConfig;
338
357
  if (!config?.contentTypes?.[selectedContentType]) return;
@@ -351,7 +370,7 @@ function handleDeleteContentType(rerender) {
351
370
  *
352
371
  * @param {HTMLElement} container
353
372
  */
354
- export function renderContentTypesEditor(container) {
373
+ export function renderContentTypesEditor(container: HTMLElement) {
355
374
  const rerender = () => renderContentTypesEditor(container);
356
375
  const config = projectState?.projectConfig;
357
376
  const contentTypes = config?.contentTypes || {};
@@ -382,10 +401,10 @@ export function renderContentTypesEditor(container) {
382
401
  size="s"
383
402
  placeholder="content-type-name"
384
403
  .value=${newContentTypeName}
385
- @input=${(/** @type {Event} */ e) => {
386
- newContentTypeName = /** @type {HTMLInputElement} */ (e.target).value;
404
+ @input=${(e: Event) => {
405
+ newContentTypeName = (e.target as HTMLInputElement).value;
387
406
  }}
388
- @keydown=${(/** @type {KeyboardEvent} */ e) => {
407
+ @keydown=${(e: KeyboardEvent) => {
389
408
  if (e.key === "Enter") handleNewContentType(rerender);
390
409
  if (e.key === "Escape") {
391
410
  showNewContentType = false;
@@ -419,24 +438,26 @@ export function renderContentTypesEditor(container) {
419
438
  editorTpl = html`<div class="settings-empty-state">Select or create a content type</div>`;
420
439
  } else {
421
440
  const col = contentTypes[selectedContentType];
422
- const schema = col.schema || {};
441
+ const schema = (col.schema || {}) as ContentTypeSchema;
423
442
  const properties = schema.properties || {};
424
443
  const required = schema.required || [];
425
444
 
426
- /** @type {import("./schema-field-ui.js").FieldHandlers} */
427
- const handlers = {
428
- onDelete: (n) => handleDeleteField(n, rerender),
429
- onToggleRequired: (n) => handleToggleRequired(n, rerender),
430
- onRename: (oldN, newN) => handleRenameField(oldN, newN, rerender),
431
- onChangeType: (n, t) => handleChangeType(n, t, rerender),
432
- onChangeFormat: (n, f) => handleChangeFormat(n, f, rerender),
433
- onChangeRefTarget: (n, target) => handleChangeRefTarget(n, target, rerender),
434
- onAddNestedField: (p, s) => handleAddNestedField(p, s, rerender),
435
- onDeleteNested: (p, c) => handleDeleteNested(p, c, rerender),
436
- onToggleNestedRequired: (p, c) => handleToggleNestedRequired(p, c, rerender),
437
- onRenameNested: (p, o, n) => handleRenameNested(p, o, n, rerender),
438
- onChangeNestedType: (p, c, t) => handleChangeNestedType(p, c, t, rerender),
439
- onChangeNestedFormat: (p, c, f) => handleChangeNestedFormat(p, c, f, rerender),
445
+ const handlers: import("./schema-field-ui.js").FieldHandlers = {
446
+ onDelete: (n: string) => handleDeleteField(n, rerender),
447
+ onToggleRequired: (n: string) => handleToggleRequired(n, rerender),
448
+ onRename: (oldN: string, newN: string) => handleRenameField(oldN, newN, rerender),
449
+ onChangeType: (n: string, t: string) => handleChangeType(n, t, rerender),
450
+ onChangeFormat: (n: string, f: string) => handleChangeFormat(n, f, rerender),
451
+ onChangeRefTarget: (n: string, target: string) => handleChangeRefTarget(n, target, rerender),
452
+ onAddNestedField: (p: string, s: { name: string; type: string; required: boolean }) =>
453
+ handleAddNestedField(p, s, rerender),
454
+ onDeleteNested: (p: string, c: string) => handleDeleteNested(p, c, rerender),
455
+ onToggleNestedRequired: (p: string, c: string) => handleToggleNestedRequired(p, c, rerender),
456
+ onRenameNested: (p: string, o: string, n: string) => handleRenameNested(p, o, n, rerender),
457
+ onChangeNestedType: (p: string, c: string, t: string) =>
458
+ handleChangeNestedType(p, c, t, rerender),
459
+ onChangeNestedFormat: (p: string, c: string, f: string) =>
460
+ handleChangeNestedFormat(p, c, f, rerender),
440
461
  };
441
462
 
442
463
  const fieldCards = repeat(