@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.
- package/dist/studio.js +2231 -715
- package/dist/studio.js.map +121 -104
- package/package.json +8 -7
- package/src/browse/{browse-modal.js → browse-modal.ts} +9 -10
- package/src/browse/{browse.js → browse.ts} +110 -85
- package/src/canvas/{canvas-diff.js → canvas-diff.ts} +42 -35
- package/src/canvas/{canvas-helpers.js → canvas-helpers.ts} +25 -19
- package/src/canvas/{canvas-live-render.js → canvas-live-render.ts} +53 -63
- package/src/canvas/{canvas-render.js → canvas-render.ts} +110 -121
- package/src/canvas/{canvas-utils.js → canvas-utils.ts} +48 -44
- package/src/canvas/{nested-site-style.js → nested-site-style.ts} +3 -3
- package/src/editor/{component-inline-edit.js → component-inline-edit.ts} +45 -33
- package/src/editor/{content-inline-edit.js → content-inline-edit.ts} +27 -45
- package/src/editor/{context-menu.js → context-menu.ts} +38 -36
- package/src/editor/{convert-targets.js → convert-targets.ts} +15 -12
- package/src/editor/{convert-to-component.js → convert-to-component.ts} +25 -26
- package/src/editor/{convert-to-repeater.js → convert-to-repeater.ts} +60 -40
- package/src/editor/{inline-edit.js → inline-edit.ts} +91 -55
- package/src/editor/{inline-format.js → inline-format.ts} +46 -51
- package/src/editor/{insertion-helper.js → insertion-helper.ts} +47 -63
- package/src/editor/{shortcuts.js → shortcuts.ts} +45 -31
- package/src/editor/{slash-menu.js → slash-menu.ts} +34 -35
- package/src/files/{components.js → components.ts} +12 -15
- package/src/files/{file-ops.js → file-ops.ts} +36 -29
- package/src/files/{files.js → files.ts} +102 -84
- package/src/github/{github-auth.js → github-auth.ts} +3 -3
- package/src/github/{github-publish.js → github-publish.ts} +20 -18
- package/src/markdown/{md-allowlist.js → md-allowlist.ts} +5 -2
- package/src/markdown/{md-convert.js → md-convert.ts} +146 -169
- package/src/new-project/{new-project-modal.js → new-project-modal.ts} +16 -18
- package/src/panels/{activity-bar.js → activity-bar.ts} +26 -34
- package/src/panels/{ai-panel.js → ai-panel.ts} +30 -30
- package/src/panels/{block-action-bar.js → block-action-bar.ts} +64 -73
- package/src/panels/{canvas-dnd.js → canvas-dnd.ts} +44 -49
- package/src/panels/{data-explorer.js → data-explorer.ts} +21 -15
- package/src/panels/dnd.ts +417 -0
- package/src/panels/{editors.js → editors.ts} +43 -50
- package/src/panels/{elements-panel.js → elements-panel.ts} +30 -27
- package/src/panels/{events-panel.js → events-panel.ts} +33 -25
- package/src/panels/{git-panel.js → git-panel.ts} +97 -83
- package/src/panels/{head-panel.js → head-panel.ts} +117 -139
- package/src/panels/{imports-panel.js → imports-panel.ts} +69 -89
- package/src/panels/{layers-panel.js → layers-panel.ts} +61 -57
- package/src/panels/{left-panel.js → left-panel.ts} +56 -63
- package/src/panels/{overlays.js → overlays.ts} +28 -36
- package/src/panels/{panel-events.js → panel-events.ts} +53 -51
- package/src/panels/{preview-render.js → preview-render.ts} +20 -15
- package/src/panels/{properties-panel.js → properties-panel.ts} +212 -246
- package/src/panels/{pseudo-preview.js → pseudo-preview.ts} +9 -8
- package/src/panels/{quick-search.js → quick-search.ts} +16 -17
- package/src/panels/{right-panel.js → right-panel.ts} +41 -59
- package/src/panels/{shared.js → shared.ts} +7 -5
- package/src/panels/{signals-panel.js → signals-panel.ts} +322 -362
- package/src/panels/{statusbar.js → statusbar.ts} +14 -14
- package/src/panels/{style-inputs.js → style-inputs.ts} +66 -48
- package/src/panels/{style-panel.js → style-panel.ts} +235 -290
- package/src/panels/{style-utils.js → style-utils.ts} +32 -31
- package/src/panels/{stylebook-layers-panel.js → stylebook-layers-panel.ts} +22 -14
- package/src/panels/{stylebook-panel.js → stylebook-panel.ts} +127 -140
- package/src/panels/{tab-strip.js → tab-strip.ts} +12 -14
- package/src/panels/{toolbar.js → toolbar.ts} +68 -70
- package/src/panels/{welcome-screen.js → welcome-screen.ts} +14 -16
- package/src/{platform.js → platform.ts} +5 -3
- package/src/platforms/{devserver.js → devserver.ts} +53 -44
- package/src/{recent-projects.js → recent-projects.ts} +17 -12
- package/src/{resize-edges.js → resize-edges.ts} +18 -13
- package/src/services/{cem-export.js → cem-export.ts} +14 -7
- package/src/services/{code-services.js → code-services.ts} +29 -20
- package/src/services/{monaco-setup.js → monaco-setup.ts} +2 -3
- package/src/settings/{content-types-editor.js → content-types-editor.ts} +88 -67
- package/src/settings/{css-vars-editor.js → css-vars-editor.ts} +78 -51
- package/src/settings/{defs-editor.js → defs-editor.ts} +70 -56
- package/src/settings/{general-settings.js → general-settings.ts} +29 -28
- package/src/settings/{head-editor.js → head-editor.ts} +37 -35
- package/src/settings/{schema-field-ui.js → schema-field-ui.ts} +86 -76
- package/src/settings/{settings-modal.js → settings-modal.ts} +14 -16
- package/src/{site-context.js → site-context.ts} +35 -32
- package/src/{state.js → state.ts} +95 -78
- package/src/{store.js → store.ts} +60 -63
- package/src/{studio.js → studio.ts} +117 -119
- package/src/tabs/tab.ts +196 -0
- package/src/tabs/{transact.js → transact.ts} +107 -60
- package/src/types.ts +185 -0
- package/src/ui/{button-group.js → button-group.ts} +21 -21
- package/src/ui/{color-selector.js → color-selector.ts} +44 -37
- package/src/ui/{expression-editor.js → expression-editor.ts} +65 -40
- package/src/ui/{field-row.js → field-row.ts} +19 -2
- package/src/ui/{icons.js → icons.ts} +2 -2
- package/src/ui/{layers.js → layers.ts} +33 -24
- package/src/ui/{media-picker.js → media-picker.ts} +13 -9
- package/src/ui/{panel-resize.js → panel-resize.ts} +9 -4
- package/src/ui/{spectrum.js → spectrum.ts} +4 -4
- package/src/ui/{unit-selector.js → unit-selector.ts} +16 -19
- package/src/ui/{value-selector.js → value-selector.ts} +28 -28
- package/src/ui/{widgets.js → widgets.ts} +62 -15
- package/src/utils/{canvas-media.js → canvas-media.ts} +29 -14
- package/src/utils/{edit-display.js → edit-display.ts} +23 -20
- package/src/utils/{google-fonts.js → google-fonts.ts} +14 -13
- package/src/utils/{inherited-style.js → inherited-style.ts} +15 -10
- package/src/utils/{studio-utils.js → studio-utils.ts} +38 -26
- package/src/{view.js → view.ts} +53 -55
- package/src/workspace/{workspace.js → workspace.ts} +44 -33
- package/src/panels/dnd.js +0 -426
- package/src/tabs/tab.js +0 -192
- /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
|
|
8
|
-
import { updateSiteConfig } from "../site-context
|
|
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
|
|
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 =
|
|
20
|
-
const headEntries =
|
|
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 = (
|
|
27
|
-
|
|
28
|
-
const
|
|
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 = (
|
|
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
|
-
(
|
|
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"><${entry.tagName}></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(
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
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,
|
|
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(
|
|
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 = (
|
|
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 = (
|
|
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=${(
|
|
268
|
+
@keydown=${(e: KeyboardEvent) => {
|
|
265
269
|
if (e.key !== "Enter") return;
|
|
266
|
-
const family =
|
|
270
|
+
const family = (e.target as HTMLInputElement).value?.trim();
|
|
267
271
|
if (!family) return;
|
|
268
|
-
|
|
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=${(
|
|
275
|
-
const input =
|
|
276
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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(
|
|
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=${(
|
|
93
|
-
const 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=${(
|
|
99
|
-
const 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=${(
|
|
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(
|
|
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=${(
|
|
193
|
-
const 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=${(
|
|
202
|
-
const 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=${(
|
|
269
|
+
@keydown=${(e: KeyboardEvent) => {
|
|
260
270
|
if (e.key === "Enter") {
|
|
261
|
-
const 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 =
|
|
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=${(
|
|
281
|
-
const target =
|
|
288
|
+
@click=${(e: Event) => {
|
|
289
|
+
const target = e.target as HTMLElement;
|
|
282
290
|
const row = target.closest(".schema-nested-add");
|
|
283
|
-
const nameInput =
|
|
284
|
-
|
|
285
|
-
);
|
|
286
|
-
const typePicker =
|
|
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=${(
|
|
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=${(
|
|
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(
|
|
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=${(
|
|
364
|
-
|
|
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=${(
|
|
378
|
-
handlers.onInput("required",
|
|
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
|
|
11
|
-
import { renderContentTypesEditor } from "./content-types-editor
|
|
12
|
-
import { renderCssVarsEditor } from "./css-vars-editor
|
|
13
|
-
import { renderHeadEditor } from "./head-editor
|
|
14
|
-
import { renderGeneralSettings } from "./general-settings
|
|
15
|
-
import { openModal } from "../ui/layers
|
|
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
|
-
|
|
18
|
-
let _handle = null;
|
|
18
|
+
let _handle: ReturnType<typeof openModal> | null = null;
|
|
19
19
|
|
|
20
|
-
|
|
21
|
-
let _activeSection = "general";
|
|
20
|
+
let _activeSection: string = "general";
|
|
22
21
|
|
|
23
|
-
|
|
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 = (
|
|
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=${(
|
|
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 =
|
|
81
|
+
${ref((el: Element | undefined) => {
|
|
82
|
+
_contentEl = (el as HTMLElement) || null;
|
|
85
83
|
if (_contentEl) requestAnimationFrame(() => renderActiveSection());
|
|
86
84
|
})}
|
|
87
85
|
></div>
|