@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,33 +1,34 @@
1
+ /// <reference lib="dom" />
1
2
  /**
2
3
  * Head editor — structured form for managing $head entries (link, meta, script, style tags) with
3
4
  * Monaco editor for script/style bodies. Also manages project-level Google Fonts.
4
5
  */
5
6
 
6
7
  import { html, render as litRender, nothing } from "lit-html";
7
- import { projectState } from "../store.js";
8
- import { updateSiteConfig } from "../site-context.js";
8
+ import { projectState } from "../store";
9
+ import { updateSiteConfig } from "../site-context";
9
10
  import {
10
11
  isGoogleFontEntry,
11
12
  extractFontFamily,
12
13
  buildGoogleFontUrl,
13
14
  ensureGoogleFontPreconnects,
14
15
  cleanupGoogleFontPreconnects,
15
- } from "../utils/google-fonts.js";
16
+ } from "../utils/google-fonts";
17
+
18
+ import type { JxHeadEntry, ProjectConfig } from "@jxsuite/schema/types";
16
19
 
17
20
  /** @param {HTMLElement} container */
18
- export function renderHeadEditor(container) {
19
- const config = /** @type {ProjectConfig} */ (projectState?.projectConfig || {});
20
- const headEntries = /** @type {JxHeadEntry[]} */ (config.$head || []);
21
+ export function renderHeadEditor(container: HTMLElement) {
22
+ const config = (projectState?.projectConfig || {}) as ProjectConfig;
23
+ const headEntries = config.$head || ([] as JxHeadEntry[]);
21
24
 
22
25
  const save = () => {
23
26
  updateSiteConfig({ $head: headEntries });
24
27
  };
25
28
 
26
- const addEntry = (/** @type {string} */ tag) => {
27
- /** @type {Record<string, string | boolean>} */
28
- const attrs = {};
29
- /** @type {JxHeadEntry} */
30
- const entry = { tagName: tag, attributes: attrs };
29
+ const addEntry = (tag: string) => {
30
+ const attrs: Record<string, string | boolean> = {};
31
+ const entry: JxHeadEntry = { tagName: tag, attributes: attrs };
31
32
  if (tag === "link") {
32
33
  attrs.rel = "stylesheet";
33
34
  attrs.href = "";
@@ -44,17 +45,13 @@ export function renderHeadEditor(container) {
44
45
  renderHeadEditor(container);
45
46
  };
46
47
 
47
- const removeEntry = (/** @type {number} */ idx) => {
48
+ const removeEntry = (idx: number) => {
48
49
  headEntries.splice(idx, 1);
49
50
  save();
50
51
  renderHeadEditor(container);
51
52
  };
52
53
 
53
- const updateEntry = (
54
- /** @type {number} */ idx,
55
- /** @type {string} */ key,
56
- /** @type {string} */ val,
57
- ) => {
54
+ const updateEntry = (idx: number, key: string, val: string) => {
58
55
  const entry = headEntries[idx];
59
56
  if (key === "content" && (entry.tagName === "script" || entry.tagName === "style")) {
60
57
  entry.textContent = val;
@@ -82,7 +79,7 @@ export function renderHeadEditor(container) {
82
79
 
83
80
  <div class="head-entries">
84
81
  ${headEntries.map(
85
- (/** @type {JxHeadEntry} */ entry, /** @type {number} */ idx) => html`
82
+ (entry: JxHeadEntry, idx: number) => html`
86
83
  <div class="head-entry">
87
84
  <div class="head-entry-header">
88
85
  <span class="head-entry-tag">&lt;${entry.tagName}&gt;</span>
@@ -113,13 +110,16 @@ export function renderHeadEditor(container) {
113
110
  * @param {number} idx
114
111
  * @param {(idx: number, key: string, val: string) => void} updateEntry
115
112
  */
116
- function renderEntryFields(entry, idx, updateEntry) {
117
- /** @type {ReturnType<typeof setTimeout> | undefined} */
118
- let debounce;
119
- const onFieldChange = (/** @type {string} */ key) => (/** @type {Event} */ e) => {
113
+ function renderEntryFields(
114
+ entry: JxHeadEntry,
115
+ idx: number,
116
+ updateEntry: (idx: number, key: string, val: string) => void,
117
+ ) {
118
+ let debounce: ReturnType<typeof setTimeout> | undefined;
119
+ const onFieldChange = (key: string) => (e: Event) => {
120
120
  clearTimeout(debounce);
121
121
  debounce = setTimeout(() => {
122
- updateEntry(idx, key, /** @type {HTMLInputElement} */ (e.target).value);
122
+ updateEntry(idx, key, (e.target as HTMLInputElement).value);
123
123
  }, 300);
124
124
  };
125
125
 
@@ -214,10 +214,14 @@ function renderEntryFields(entry, idx, updateEntry) {
214
214
  * @param {() => void} save
215
215
  * @param {() => void} rerender
216
216
  */
217
- function renderGoogleFontsSection(headEntries, save, rerender) {
217
+ function renderGoogleFontsSection(
218
+ headEntries: JxHeadEntry[],
219
+ save: () => void,
220
+ rerender: () => void,
221
+ ) {
218
222
  const fontEntries = headEntries.filter((e) => isGoogleFontEntry(e));
219
223
 
220
- const addFont = (/** @type {string} */ family) => {
224
+ const addFont = (family: string) => {
221
225
  ensureGoogleFontPreconnects(headEntries);
222
226
  headEntries.push({
223
227
  tagName: "link",
@@ -227,7 +231,7 @@ function renderGoogleFontsSection(headEntries, save, rerender) {
227
231
  rerender();
228
232
  };
229
233
 
230
- const removeFont = (/** @type {JxHeadEntry} */ entry) => {
234
+ const removeFont = (entry: JxHeadEntry) => {
231
235
  const idx = headEntries.indexOf(entry);
232
236
  if (idx >= 0) headEntries.splice(idx, 1);
233
237
  const cleaned = cleanupGoogleFontPreconnects(headEntries);
@@ -261,23 +265,21 @@ function renderGoogleFontsSection(headEntries, save, rerender) {
261
265
  size="s"
262
266
  placeholder="Font family name…"
263
267
  style="flex:1"
264
- @keydown=${(/** @type {KeyboardEvent} */ e) => {
268
+ @keydown=${(e: KeyboardEvent) => {
265
269
  if (e.key !== "Enter") return;
266
- const family = /** @type {HTMLInputElement} */ (e.target).value?.trim();
270
+ const family = (e.target as HTMLInputElement).value?.trim();
267
271
  if (!family) return;
268
- /** @type {HTMLInputElement} */ (e.target).value = "";
272
+ (e.target as HTMLInputElement).value = "";
269
273
  addFont(family);
270
274
  }}
271
275
  ></sp-textfield>
272
276
  <sp-action-button
273
277
  size="s"
274
- @click=${(/** @type {MouseEvent} */ e) => {
275
- const input = /** @type {HTMLElement} */ (e.target)
276
- .closest("div")
277
- ?.querySelector("sp-textfield");
278
- const family = /** @type {HTMLInputElement | null} */ (input)?.value?.trim();
278
+ @click=${(e: MouseEvent) => {
279
+ const input = (e.target as HTMLElement).closest("div")?.querySelector("sp-textfield");
280
+ const family = (input as HTMLInputElement | null)?.value?.trim();
279
281
  if (!family) return;
280
- /** @type {HTMLInputElement} */ (input).value = "";
282
+ (input as HTMLInputElement).value = "";
281
283
  addFont(family);
282
284
  }}
283
285
  >
@@ -1,44 +1,42 @@
1
+ /// <reference lib="dom" />
1
2
  /**
2
3
  * Schema field UI — shared field-card and add-field-dialog templates for the content types and
3
4
  * definitions editors.
4
5
  */
5
6
 
6
7
  import { html, nothing } from "lit-html";
8
+ import { camelToLabel } from "../utils/studio-utils";
7
9
 
8
- export const FIELD_TYPES = ["string", "number", "boolean", "array", "object", "reference"];
10
+ export interface SchemaProperty {
11
+ type?: string;
12
+ properties?: Record<string, SchemaProperty>;
13
+ required?: string[];
14
+ items?: SchemaProperty;
15
+ format?: string;
16
+ $ref?: string;
17
+ }
9
18
 
10
- export const FORMAT_OPTIONS = ["", "image", "date", "color"];
19
+ export interface FieldHandlers {
20
+ onDelete: (name: string) => void;
21
+ onToggleRequired: (name: string) => void;
22
+ onRename: (oldName: string, newName: string) => void;
23
+ onChangeType: (name: string, newType: string) => void;
24
+ onChangeFormat?: (name: string, format: string) => void;
25
+ onChangeRefTarget?: (name: string, target: string) => void;
26
+ onAddNestedField?: (
27
+ parentName: string,
28
+ state: { name: string; type: string; required: boolean },
29
+ ) => void;
30
+ onDeleteNested?: (parentName: string, childName: string) => void;
31
+ onToggleNestedRequired?: (parentName: string, childName: string) => void;
32
+ onRenameNested?: (parentName: string, oldChild: string, newChild: string) => void;
33
+ onChangeNestedType?: (parentName: string, childName: string, newType: string) => void;
34
+ onChangeNestedFormat?: (parentName: string, childName: string, format: string) => void;
35
+ }
11
36
 
12
- /**
13
- * @typedef {{
14
- * type?: string;
15
- * properties?: Record<string, SchemaProperty>;
16
- * required?: string[];
17
- * items?: SchemaProperty;
18
- * format?: string;
19
- * $ref?: string;
20
- * }} SchemaProperty
21
- */
37
+ export const FIELD_TYPES = ["string", "number", "boolean", "array", "object", "reference"];
22
38
 
23
- /**
24
- * @typedef {{
25
- * onDelete: (name: string) => void;
26
- * onToggleRequired: (name: string) => void;
27
- * onRename: (oldName: string, newName: string) => void;
28
- * onChangeType: (name: string, newType: string) => void;
29
- * onChangeFormat?: (name: string, format: string) => void;
30
- * onChangeRefTarget?: (name: string, target: string) => void;
31
- * onAddNestedField?: (
32
- * parentName: string,
33
- * state: { name: string; type: string; required: boolean },
34
- * ) => void;
35
- * onDeleteNested?: (parentName: string, childName: string) => void;
36
- * onToggleNestedRequired?: (parentName: string, childName: string) => void;
37
- * onRenameNested?: (parentName: string, oldChild: string, newChild: string) => void;
38
- * onChangeNestedType?: (parentName: string, childName: string, newType: string) => void;
39
- * onChangeNestedFormat?: (parentName: string, childName: string, format: string) => void;
40
- * }} FieldHandlers
41
- */
39
+ export const FORMAT_OPTIONS = ["", "image", "date", "color"];
42
40
 
43
41
  /**
44
42
  * Detect the studio field type from a JSON Schema property definition.
@@ -46,7 +44,7 @@ export const FORMAT_OPTIONS = ["", "image", "date", "color"];
46
44
  * @param {SchemaProperty} schema
47
45
  * @returns {string}
48
46
  */
49
- export function detectFieldType(schema) {
47
+ export function detectFieldType(schema: SchemaProperty) {
50
48
  if (schema.$ref) return "reference";
51
49
  return schema.type || "string";
52
50
  }
@@ -57,7 +55,7 @@ export function detectFieldType(schema) {
57
55
  * @param {SchemaProperty} schema
58
56
  * @returns {string}
59
57
  */
60
- export function detectFieldFormat(schema) {
58
+ export function detectFieldFormat(schema: SchemaProperty) {
61
59
  if (schema.type === "array" && schema.items?.format) return schema.items.format;
62
60
  return schema.format || "";
63
61
  }
@@ -72,7 +70,13 @@ export function detectFieldFormat(schema) {
72
70
  * @param {string[]} [contentTypeNames] - Available content type names for reference target picker
73
71
  * @returns {import("lit-html").TemplateResult}
74
72
  */
75
- export function fieldCardTpl(fieldName, fieldSchema, isRequired, handlers, contentTypeNames = []) {
73
+ export function fieldCardTpl(
74
+ fieldName: string,
75
+ fieldSchema: SchemaProperty,
76
+ isRequired: boolean,
77
+ handlers: FieldHandlers,
78
+ contentTypeNames: string[] = [],
79
+ ) {
76
80
  const type = detectFieldType(fieldSchema);
77
81
  const format = detectFieldFormat(fieldSchema);
78
82
  const isNested = type === "object";
@@ -84,19 +88,22 @@ export function fieldCardTpl(fieldName, fieldSchema, isRequired, handlers, conte
84
88
  return html`
85
89
  <div class="schema-field-card">
86
90
  <div class="schema-field-row">
91
+ <sp-field-label size="s" class="schema-field-label"
92
+ >${camelToLabel(fieldName)}</sp-field-label
93
+ >
87
94
  <sp-textfield
88
95
  size="s"
89
96
  quiet
90
97
  value=${fieldName}
91
98
  class="schema-field-name-input"
92
- @change=${(/** @type {Event} */ e) => {
93
- const target = /** @type {HTMLInputElement} */ (e.target);
99
+ @change=${(e: Event) => {
100
+ const target = e.target as HTMLInputElement;
94
101
  const newName = target.value.trim();
95
102
  if (newName && newName !== fieldName) handlers.onRename(fieldName, newName);
96
103
  else target.value = fieldName;
97
104
  }}
98
- @keydown=${(/** @type {KeyboardEvent} */ e) => {
99
- const target = /** @type {HTMLInputElement} */ (e.target);
105
+ @keydown=${(e: KeyboardEvent) => {
106
+ const target = e.target as HTMLInputElement;
100
107
  if (e.key === "Enter") target.blur();
101
108
  if (e.key === "Escape") {
102
109
  target.value = fieldName;
@@ -133,12 +140,9 @@ export function fieldCardTpl(fieldName, fieldSchema, isRequired, handlers, conte
133
140
  size="s"
134
141
  label="Target"
135
142
  value=${refTarget}
136
- @change=${(/** @type {Event} */ e) => {
143
+ @change=${(e: Event) => {
137
144
  if (handlers.onChangeRefTarget)
138
- handlers.onChangeRefTarget(
139
- fieldName,
140
- /** @type {HTMLInputElement} */ (e.target).value,
141
- );
145
+ handlers.onChangeRefTarget(fieldName, (e.target as HTMLInputElement).value);
142
146
  }}
143
147
  >
144
148
  ${contentTypeNames.map((n) => html`<sp-menu-item value=${n}>${n}</sp-menu-item>`)}
@@ -177,7 +181,13 @@ export function fieldCardTpl(fieldName, fieldSchema, isRequired, handlers, conte
177
181
  * @param {FieldHandlers} handlers
178
182
  * @returns {import("lit-html").TemplateResult}
179
183
  */
180
- function nestedFieldCardTpl(parentName, childName, childSchema, isRequired, handlers) {
184
+ function nestedFieldCardTpl(
185
+ parentName: string,
186
+ childName: string,
187
+ childSchema: SchemaProperty,
188
+ isRequired: boolean,
189
+ handlers: FieldHandlers,
190
+ ) {
181
191
  const type = detectFieldType(childSchema);
182
192
  const format = detectFieldFormat(childSchema);
183
193
 
@@ -189,8 +199,8 @@ function nestedFieldCardTpl(parentName, childName, childSchema, isRequired, hand
189
199
  quiet
190
200
  value=${childName}
191
201
  class="schema-field-name-input"
192
- @change=${(/** @type {Event} */ e) => {
193
- const target = /** @type {HTMLInputElement} */ (e.target);
202
+ @change=${(e: Event) => {
203
+ const target = e.target as HTMLInputElement;
194
204
  const newName = target.value.trim();
195
205
  if (newName && newName !== childName && handlers.onRenameNested) {
196
206
  handlers.onRenameNested(parentName, childName, newName);
@@ -198,8 +208,8 @@ function nestedFieldCardTpl(parentName, childName, childSchema, isRequired, hand
198
208
  target.value = childName;
199
209
  }
200
210
  }}
201
- @keydown=${(/** @type {KeyboardEvent} */ e) => {
202
- const target = /** @type {HTMLInputElement} */ (e.target);
211
+ @keydown=${(e: KeyboardEvent) => {
212
+ const target = e.target as HTMLInputElement;
203
213
  if (e.key === "Enter") target.blur();
204
214
  if (e.key === "Escape") {
205
215
  target.value = childName;
@@ -249,21 +259,19 @@ function nestedFieldCardTpl(parentName, childName, childSchema, isRequired, hand
249
259
  * @param {FieldHandlers} handlers
250
260
  * @returns {import("lit-html").TemplateResult}
251
261
  */
252
- function nestedAddFieldTpl(parentName, handlers) {
262
+ function nestedAddFieldTpl(parentName: string, handlers: FieldHandlers) {
253
263
  return html`
254
264
  <div class="schema-nested-add">
255
265
  <sp-textfield
256
266
  size="s"
257
267
  placeholder="field name"
258
268
  class="schema-nested-add-name"
259
- @keydown=${(/** @type {KeyboardEvent} */ e) => {
269
+ @keydown=${(e: KeyboardEvent) => {
260
270
  if (e.key === "Enter") {
261
- const target = /** @type {HTMLInputElement} */ (e.target);
271
+ const target = e.target as HTMLInputElement;
262
272
  const row = target.closest(".schema-nested-add");
263
273
  const name = target.value.trim();
264
- const typePicker = /** @type {HTMLInputElement | null} */ (
265
- row?.querySelector("sp-picker")
266
- );
274
+ const typePicker = row?.querySelector("sp-picker") as HTMLInputElement | null;
267
275
  const type = typePicker?.value || "string";
268
276
  if (name && handlers.onAddNestedField) {
269
277
  handlers.onAddNestedField(parentName, { name, type, required: false });
@@ -277,15 +285,13 @@ function nestedAddFieldTpl(parentName, handlers) {
277
285
  size="xs"
278
286
  quiet
279
287
  title="Add nested field"
280
- @click=${(/** @type {Event} */ e) => {
281
- const target = /** @type {HTMLElement} */ (e.target);
288
+ @click=${(e: Event) => {
289
+ const target = e.target as HTMLElement;
282
290
  const row = target.closest(".schema-nested-add");
283
- const nameInput = /** @type {HTMLInputElement | null} */ (
284
- row?.querySelector(".schema-nested-add-name")
285
- );
286
- const typePicker = /** @type {HTMLInputElement | null} */ (
287
- row?.querySelector("sp-picker")
288
- );
291
+ const nameInput = row?.querySelector(
292
+ ".schema-nested-add-name",
293
+ ) as HTMLInputElement | null;
294
+ const typePicker = row?.querySelector("sp-picker") as HTMLInputElement | null;
289
295
  const name = nameInput?.value?.trim();
290
296
  const type = typePicker?.value || "string";
291
297
  if (name && handlers.onAddNestedField) {
@@ -307,14 +313,13 @@ function nestedAddFieldTpl(parentName, handlers) {
307
313
  * @param {(type: string) => void} onChange
308
314
  * @returns {import("lit-html").TemplateResult}
309
315
  */
310
- export function typePickerTpl(value, onChange) {
316
+ export function typePickerTpl(value: string, onChange: (type: string) => void) {
311
317
  return html`
312
318
  <sp-picker
313
319
  size="s"
314
320
  label="Type"
315
321
  value=${value}
316
- @change=${(/** @type {Event} */ e) =>
317
- onChange(/** @type {HTMLInputElement} */ (e.target).value)}
322
+ @change=${(e: Event) => onChange((e.target as HTMLInputElement).value)}
318
323
  >
319
324
  ${FIELD_TYPES.map((t) => html`<sp-menu-item value=${t}>${t}</sp-menu-item>`)}
320
325
  </sp-picker>
@@ -328,14 +333,13 @@ export function typePickerTpl(value, onChange) {
328
333
  * @param {(format: string) => void} onChange
329
334
  * @returns {import("lit-html").TemplateResult}
330
335
  */
331
- export function formatPickerTpl(value, onChange) {
336
+ export function formatPickerTpl(value: string, onChange: (format: string) => void) {
332
337
  return html`
333
338
  <sp-picker
334
339
  size="s"
335
340
  label="Format"
336
341
  value=${value}
337
- @change=${(/** @type {Event} */ e) =>
338
- onChange(/** @type {HTMLInputElement} */ (e.target).value)}
342
+ @change=${(e: Event) => onChange((e.target as HTMLInputElement).value)}
339
343
  >
340
344
  ${FORMAT_OPTIONS.map((f) => html`<sp-menu-item value=${f}>${f || "(none)"}</sp-menu-item>`)}
341
345
  </sp-picker>
@@ -353,16 +357,22 @@ export function formatPickerTpl(value, onChange) {
353
357
  * }} handlers
354
358
  * @returns {import("lit-html").TemplateResult}
355
359
  */
356
- export function addFieldFormTpl(state, handlers) {
360
+ export function addFieldFormTpl(
361
+ state: { name: string; type: string; format: string; required: boolean },
362
+ handlers: {
363
+ onInput: (field: string, value: string | boolean) => void;
364
+ onConfirm: () => void;
365
+ onCancel: () => void;
366
+ },
367
+ ) {
357
368
  return html`
358
369
  <div class="schema-add-field">
359
370
  <sp-textfield
360
371
  size="s"
361
372
  placeholder="Field name"
362
373
  .value=${state.name}
363
- @input=${(/** @type {Event} */ e) =>
364
- handlers.onInput("name", /** @type {HTMLInputElement} */ (e.target).value)}
365
- @keydown=${(/** @type {KeyboardEvent} */ e) => {
374
+ @input=${(e: Event) => handlers.onInput("name", (e.target as HTMLInputElement).value)}
375
+ @keydown=${(e: KeyboardEvent) => {
366
376
  if (e.key === "Enter") handlers.onConfirm();
367
377
  if (e.key === "Escape") handlers.onCancel();
368
378
  }}
@@ -374,8 +384,8 @@ export function addFieldFormTpl(state, handlers) {
374
384
  <sp-switch
375
385
  size="s"
376
386
  ?checked=${state.required}
377
- @change=${(/** @type {Event} */ e) =>
378
- handlers.onInput("required", /** @type {HTMLInputElement} */ (e.target).checked)}
387
+ @change=${(e: Event) =>
388
+ handlers.onInput("required", (e.target as HTMLInputElement).checked)}
379
389
  >
380
390
  Required
381
391
  </sp-switch>
@@ -392,7 +402,7 @@ export function addFieldFormTpl(state, handlers) {
392
402
  * @param {string} [format]
393
403
  * @returns {object}
394
404
  */
395
- export function schemaForType(type, format) {
405
+ export function schemaForType(type: string, format?: string) {
396
406
  switch (type) {
397
407
  case "number":
398
408
  return { type: "number" };
@@ -418,7 +428,7 @@ export function schemaForType(type, format) {
418
428
  * @param {string} [format]
419
429
  * @returns {string}
420
430
  */
421
- export function yamlDefault(type, format) {
431
+ export function yamlDefault(type: string, format?: string) {
422
432
  if (format === "date") return new Date().toISOString().split("T")[0];
423
433
  if (format === "image") return '""';
424
434
  switch (type) {
@@ -1,3 +1,4 @@
1
+ /// <reference lib="dom" />
1
2
  /**
2
3
  * Settings modal — site-wide project settings (CSS variables, definitions, content types, head,
3
4
  * general). Modeled after VS Code / Obsidian settings panels: left sidebar nav + right content
@@ -7,21 +8,18 @@
7
8
  import { html } from "lit-html";
8
9
  import { classMap } from "lit-html/directives/class-map.js";
9
10
  import { ref } from "lit-html/directives/ref.js";
10
- import { renderDefsEditor } from "./defs-editor.js";
11
- import { renderContentTypesEditor } from "./content-types-editor.js";
12
- import { renderCssVarsEditor } from "./css-vars-editor.js";
13
- import { renderHeadEditor } from "./head-editor.js";
14
- import { renderGeneralSettings } from "./general-settings.js";
15
- import { openModal } from "../ui/layers.js";
11
+ import { renderDefsEditor } from "./defs-editor";
12
+ import { renderContentTypesEditor } from "./content-types-editor";
13
+ import { renderCssVarsEditor } from "./css-vars-editor";
14
+ import { renderHeadEditor } from "./head-editor";
15
+ import { renderGeneralSettings } from "./general-settings";
16
+ import { openModal } from "../ui/layers";
16
17
 
17
- /** @type {ReturnType<typeof openModal> | null} */
18
- let _handle = null;
18
+ let _handle: ReturnType<typeof openModal> | null = null;
19
19
 
20
- /** @type {string} */
21
- let _activeSection = "general";
20
+ let _activeSection: string = "general";
22
21
 
23
- /** @type {HTMLElement | null} */
24
- let _contentEl = null;
22
+ let _contentEl: HTMLElement | null = null;
25
23
 
26
24
  const sections = [
27
25
  { key: "general", label: "General", icon: "sp-icon-properties" },
@@ -45,7 +43,7 @@ export function closeSettingsModal() {
45
43
  }
46
44
 
47
45
  function renderModal() {
48
- const onNavClick = (/** @type {string} */ key) => {
46
+ const onNavClick = (key: string) => {
49
47
  _activeSection = key;
50
48
  renderModal();
51
49
  renderActiveSection();
@@ -55,7 +53,7 @@ function renderModal() {
55
53
  <sp-underlay open @close=${closeSettingsModal}></sp-underlay>
56
54
  <div
57
55
  class="settings-modal"
58
- @keydown=${(/** @type {KeyboardEvent} */ e) => {
56
+ @keydown=${(e: KeyboardEvent) => {
59
57
  if (e.key === "Escape") closeSettingsModal();
60
58
  }}
61
59
  >
@@ -80,8 +78,8 @@ function renderModal() {
80
78
  </nav>
81
79
  <div
82
80
  class="settings-modal-content"
83
- ${ref((el) => {
84
- _contentEl = /** @type {HTMLElement | null} */ (el || null);
81
+ ${ref((el: Element | undefined) => {
82
+ _contentEl = (el as HTMLElement) || null;
85
83
  if (_contentEl) requestAnimationFrame(() => renderActiveSection());
86
84
  })}
87
85
  ></div>