@jxsuite/studio 0.17.0 → 0.18.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 CHANGED
@@ -47856,7 +47856,7 @@ function applyTransform() {
47856
47856
  view.panzoomWrap.style.transform = `translate(${view.panX}px, ${view.panY}px) scale(${zoom})`;
47857
47857
  renderZoomIndicator();
47858
47858
  renderOnly("overlays");
47859
- if (_ctx4.getCanvasMode() === "settings")
47859
+ if (_ctx4.getCanvasMode() === "stylebook")
47860
47860
  _ctx4.renderStylebookOverlays();
47861
47861
  }
47862
47862
  function fitToScreen() {
@@ -180100,1071 +180100,6 @@ init_components();
180100
180100
  init_store();
180101
180101
  init_workspace();
180102
180102
  init_components();
180103
-
180104
- // src/utils/studio-utils.js
180105
- function camelToKebab2(str) {
180106
- return str.replace(/[A-Z]/g, (c) => "-" + c.toLowerCase());
180107
- }
180108
- function camelToLabel(prop) {
180109
- return prop.replace(/([A-Z])/g, " $1").replace(/^./, (c) => c.toUpperCase());
180110
- }
180111
- function kebabToLabel(val) {
180112
- return val.replace(/(^|-)(\w)/g, (_, sep2, c) => (sep2 ? " " : "") + c.toUpperCase());
180113
- }
180114
- function propLabel(entry, prop) {
180115
- return entry?.$label || camelToLabel(prop);
180116
- }
180117
- function attrLabel(entry, attr) {
180118
- if (entry?.$label)
180119
- return entry.$label;
180120
- if (attr.includes("-"))
180121
- return attr.replace(/(^|-)(\w)/g, (_, sep2, c) => (sep2 ? " " : "") + c.toUpperCase());
180122
- return camelToLabel(attr);
180123
- }
180124
- function abbreviateValue(val) {
180125
- const map6 = {
180126
- inline: "inl",
180127
- "inline-block": "i-blk",
180128
- "inline-flex": "i-flx",
180129
- "inline-grid": "i-grd",
180130
- contents: "cnt",
180131
- "flow-root": "flow",
180132
- nowrap: "no-wr",
180133
- "wrap-reverse": "wr-rev",
180134
- "flex-start": "start",
180135
- "flex-end": "end",
180136
- "space-between": "betw",
180137
- "space-around": "arnd",
180138
- "space-evenly": "even",
180139
- stretch: "str",
180140
- baseline: "base",
180141
- normal: "norm",
180142
- "row-reverse": "row-r",
180143
- "column-reverse": "col-r",
180144
- column: "col"
180145
- };
180146
- return map6[val] || val;
180147
- }
180148
- function inferInputType(entry) {
180149
- if (entry.$shorthand === true)
180150
- return "shorthand";
180151
- if (entry.$input === "button-group")
180152
- return "button-group";
180153
- if (entry.$input === "media")
180154
- return "media";
180155
- if (entry.format === "color")
180156
- return "color";
180157
- if (entry.format === "uri-reference")
180158
- return "media";
180159
- if (entry.$units !== undefined)
180160
- return "number-unit";
180161
- if (entry.type === "number")
180162
- return "number";
180163
- if (Array.isArray(entry.enum))
180164
- return "select";
180165
- if (Array.isArray(entry.examples) || Array.isArray(entry.presets))
180166
- return "combobox";
180167
- return "text";
180168
- }
180169
- function findContentTypeSchema(documentPath, projectConfig) {
180170
- if (!documentPath || !projectConfig?.contentTypes)
180171
- return null;
180172
- for (const [name, def3] of Object.entries(projectConfig.contentTypes)) {
180173
- if (!def3.source || !def3.schema)
180174
- continue;
180175
- const src = def3.source.replace(/^\.\//, "");
180176
- const dir = src.split("/")[0];
180177
- const ext = src.includes("*.md") ? ".md" : src.includes("*.json") ? ".json" : src.includes("*.csv") ? ".csv" : null;
180178
- if (documentPath.startsWith(dir + "/") && (!ext || documentPath.endsWith(ext))) {
180179
- return { name, schema: def3.schema };
180180
- }
180181
- }
180182
- return null;
180183
- }
180184
- function friendlyNameToVar(name, prefix) {
180185
- const slug = name.trim().toLowerCase().replace(/[^a-z0-9\s-]/g, "").replace(/\s+/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
180186
- if (!slug)
180187
- return "";
180188
- return `${prefix}${slug}`;
180189
- }
180190
- function varDisplayName(varName, prefix) {
180191
- return varName.replace(new RegExp(`^${prefix.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}`), "").replace(/^--/, "").replace(/-/g, " ").replace(/\b\w/g, (c) => c.toUpperCase()) || varName;
180192
- }
180193
- function parseCemType(typeText) {
180194
- if (!typeText)
180195
- return { kind: "text" };
180196
- const t = typeText.trim().replace(/\s*\|\s*undefined\b/g, "").trim();
180197
- if (t === "boolean")
180198
- return { kind: "boolean" };
180199
- if (t === "number")
180200
- return { kind: "number" };
180201
- const enumMatch = t.match(/^'[^']*'(\s*\|\s*'[^']*')+$/);
180202
- if (enumMatch) {
180203
- const options = [...t.matchAll(/'([^']*)'/g)].map((m) => m[1]);
180204
- return { kind: "combobox", options };
180205
- }
180206
- return { kind: "text" };
180207
- }
180208
-
180209
- // src/settings/defs-editor.js
180210
- init_platform();
180211
- init_store();
180212
-
180213
- // src/settings/schema-field-ui.js
180214
- var FIELD_TYPES = ["string", "number", "boolean", "array", "object", "reference"];
180215
- var FORMAT_OPTIONS = ["", "image", "date", "color"];
180216
- function detectFieldType(schema4) {
180217
- if (schema4.$ref)
180218
- return "reference";
180219
- return schema4.type || "string";
180220
- }
180221
- function detectFieldFormat(schema4) {
180222
- if (schema4.type === "array" && schema4.items?.format)
180223
- return schema4.items.format;
180224
- return schema4.format || "";
180225
- }
180226
- function fieldCardTpl(fieldName, fieldSchema, isRequired, handlers, contentTypeNames = []) {
180227
- const type = detectFieldType(fieldSchema);
180228
- const format2 = detectFieldFormat(fieldSchema);
180229
- const isNested = type === "object";
180230
- const isRef2 = type === "reference";
180231
- const nestedProps = fieldSchema.properties || {};
180232
- const nestedRequired = fieldSchema.required || [];
180233
- const refTarget = fieldSchema.$ref ? fieldSchema.$ref.replace("#/contentTypes/", "") : "";
180234
- return html`
180235
- <div class="schema-field-card">
180236
- <div class="schema-field-row">
180237
- <sp-textfield
180238
- size="s"
180239
- quiet
180240
- value=${fieldName}
180241
- class="schema-field-name-input"
180242
- @change=${(e) => {
180243
- const newName = e.target.value.trim();
180244
- if (newName && newName !== fieldName)
180245
- handlers.onRename(fieldName, newName);
180246
- else
180247
- e.target.value = fieldName;
180248
- }}
180249
- @keydown=${(e) => {
180250
- if (e.key === "Enter")
180251
- e.target.blur();
180252
- if (e.key === "Escape") {
180253
- e.target.value = fieldName;
180254
- e.target.blur();
180255
- }
180256
- }}
180257
- ></sp-textfield>
180258
- ${typePickerTpl(type, (newType) => handlers.onChangeType(fieldName, newType))}
180259
- ${type === "string" || type === "array" ? formatPickerTpl(format2, (f) => {
180260
- if (handlers.onChangeFormat)
180261
- handlers.onChangeFormat(fieldName, f);
180262
- }) : nothing}
180263
- <sp-switch
180264
- size="s"
180265
- ?checked=${isRequired}
180266
- @change=${() => handlers.onToggleRequired(fieldName)}
180267
- >
180268
- Req
180269
- </sp-switch>
180270
- <sp-action-button
180271
- size="xs"
180272
- quiet
180273
- title="Delete field"
180274
- @click=${() => handlers.onDelete(fieldName)}
180275
- >
180276
- <sp-icon-delete slot="icon"></sp-icon-delete>
180277
- </sp-action-button>
180278
- </div>
180279
- ${isRef2 && contentTypeNames.length ? html`
180280
- <div class="schema-field-ref-target">
180281
- <sp-picker
180282
- size="s"
180283
- label="Target"
180284
- value=${refTarget}
180285
- @change=${(e) => {
180286
- if (handlers.onChangeRefTarget)
180287
- handlers.onChangeRefTarget(fieldName, e.target.value);
180288
- }}
180289
- >
180290
- ${contentTypeNames.map((n2) => html`<sp-menu-item value=${n2}>${n2}</sp-menu-item>`)}
180291
- </sp-picker>
180292
- </div>
180293
- ` : nothing}
180294
- ${isNested ? html`
180295
- <div class="schema-field-nested">
180296
- ${Object.entries(nestedProps).map(([name, sub]) => nestedFieldCardTpl(fieldName, name, sub, nestedRequired.includes(name), handlers))}
180297
- ${nestedAddFieldTpl(fieldName, handlers)}
180298
- </div>
180299
- ` : nothing}
180300
- </div>
180301
- `;
180302
- }
180303
- function nestedFieldCardTpl(parentName, childName, childSchema, isRequired, handlers) {
180304
- const type = detectFieldType(childSchema);
180305
- const format2 = detectFieldFormat(childSchema);
180306
- return html`
180307
- <div class="schema-field-card schema-field-card--nested">
180308
- <div class="schema-field-row">
180309
- <sp-textfield
180310
- size="s"
180311
- quiet
180312
- value=${childName}
180313
- class="schema-field-name-input"
180314
- @change=${(e) => {
180315
- const newName = e.target.value.trim();
180316
- if (newName && newName !== childName && handlers.onRenameNested) {
180317
- handlers.onRenameNested(parentName, childName, newName);
180318
- } else {
180319
- e.target.value = childName;
180320
- }
180321
- }}
180322
- @keydown=${(e) => {
180323
- if (e.key === "Enter")
180324
- e.target.blur();
180325
- if (e.key === "Escape") {
180326
- e.target.value = childName;
180327
- e.target.blur();
180328
- }
180329
- }}
180330
- ></sp-textfield>
180331
- ${typePickerTpl(type, (newType) => {
180332
- if (handlers.onChangeNestedType)
180333
- handlers.onChangeNestedType(parentName, childName, newType);
180334
- })}
180335
- ${type === "string" || type === "array" ? formatPickerTpl(format2, (f) => {
180336
- if (handlers.onChangeNestedFormat)
180337
- handlers.onChangeNestedFormat(parentName, childName, f);
180338
- }) : nothing}
180339
- <sp-switch
180340
- size="s"
180341
- ?checked=${isRequired}
180342
- @change=${() => {
180343
- if (handlers.onToggleNestedRequired)
180344
- handlers.onToggleNestedRequired(parentName, childName);
180345
- }}
180346
- >
180347
- Req
180348
- </sp-switch>
180349
- <sp-action-button
180350
- size="xs"
180351
- quiet
180352
- title="Delete field"
180353
- @click=${() => {
180354
- if (handlers.onDeleteNested)
180355
- handlers.onDeleteNested(parentName, childName);
180356
- }}
180357
- >
180358
- <sp-icon-delete slot="icon"></sp-icon-delete>
180359
- </sp-action-button>
180360
- </div>
180361
- </div>
180362
- `;
180363
- }
180364
- function nestedAddFieldTpl(parentName, handlers) {
180365
- return html`
180366
- <div class="schema-nested-add">
180367
- <sp-textfield
180368
- size="s"
180369
- placeholder="field name"
180370
- class="schema-nested-add-name"
180371
- @keydown=${(e) => {
180372
- if (e.key === "Enter") {
180373
- const row = e.target.closest(".schema-nested-add");
180374
- const name = e.target.value.trim();
180375
- const typePicker = row?.querySelector("sp-picker");
180376
- const type = typePicker?.value || "string";
180377
- if (name && handlers.onAddNestedField) {
180378
- handlers.onAddNestedField(parentName, { name, type, required: false });
180379
- e.target.value = "";
180380
- }
180381
- }
180382
- }}
180383
- ></sp-textfield>
180384
- ${typePickerTpl("string", () => {})}
180385
- <sp-action-button
180386
- size="xs"
180387
- quiet
180388
- title="Add nested field"
180389
- @click=${(e) => {
180390
- const row = e.target.closest(".schema-nested-add");
180391
- const nameInput = row?.querySelector(".schema-nested-add-name");
180392
- const typePicker = row?.querySelector("sp-picker");
180393
- const name = nameInput?.value?.trim();
180394
- const type = typePicker?.value || "string";
180395
- if (name && handlers.onAddNestedField) {
180396
- handlers.onAddNestedField(parentName, { name, type, required: false });
180397
- nameInput.value = "";
180398
- }
180399
- }}
180400
- >
180401
- <sp-icon-add slot="icon"></sp-icon-add>
180402
- </sp-action-button>
180403
- </div>
180404
- `;
180405
- }
180406
- function typePickerTpl(value2, onChange) {
180407
- return html`
180408
- <sp-picker
180409
- size="s"
180410
- label="Type"
180411
- value=${value2}
180412
- @change=${(e) => onChange(e.target.value)}
180413
- >
180414
- ${FIELD_TYPES.map((t) => html`<sp-menu-item value=${t}>${t}</sp-menu-item>`)}
180415
- </sp-picker>
180416
- `;
180417
- }
180418
- function formatPickerTpl(value2, onChange) {
180419
- return html`
180420
- <sp-picker
180421
- size="s"
180422
- label="Format"
180423
- value=${value2}
180424
- @change=${(e) => onChange(e.target.value)}
180425
- >
180426
- ${FORMAT_OPTIONS.map((f) => html`<sp-menu-item value=${f}>${f || "(none)"}</sp-menu-item>`)}
180427
- </sp-picker>
180428
- `;
180429
- }
180430
- function addFieldFormTpl(state, handlers) {
180431
- return html`
180432
- <div class="schema-add-field">
180433
- <sp-textfield
180434
- size="s"
180435
- placeholder="Field name"
180436
- .value=${state.name}
180437
- @input=${(e) => handlers.onInput("name", e.target.value)}
180438
- @keydown=${(e) => {
180439
- if (e.key === "Enter")
180440
- handlers.onConfirm();
180441
- if (e.key === "Escape")
180442
- handlers.onCancel();
180443
- }}
180444
- ></sp-textfield>
180445
- ${typePickerTpl(state.type, (t) => handlers.onInput("type", t))}
180446
- ${state.type === "string" || state.type === "array" ? formatPickerTpl(state.format || "", (f) => handlers.onInput("format", f)) : nothing}
180447
- <sp-switch
180448
- size="s"
180449
- ?checked=${state.required}
180450
- @change=${(e) => handlers.onInput("required", e.target.checked)}
180451
- >
180452
- Required
180453
- </sp-switch>
180454
- <sp-action-button size="s" @click=${handlers.onConfirm}>Add</sp-action-button>
180455
- <sp-action-button size="s" quiet @click=${handlers.onCancel}>Cancel</sp-action-button>
180456
- </div>
180457
- `;
180458
- }
180459
- function schemaForType(type, format2) {
180460
- switch (type) {
180461
- case "number":
180462
- return { type: "number" };
180463
- case "boolean":
180464
- return { type: "boolean" };
180465
- case "array":
180466
- return format2 ? { type: "array", items: { type: "string", format: format2 } } : { type: "array", items: { type: "string" } };
180467
- case "object":
180468
- return { type: "object", properties: {}, required: [] };
180469
- case "reference":
180470
- return { $ref: "#/contentTypes/" };
180471
- default:
180472
- return format2 ? { type: "string", format: format2 } : { type: "string" };
180473
- }
180474
- }
180475
- function yamlDefault(type, format2) {
180476
- if (format2 === "date")
180477
- return new Date().toISOString().split("T")[0];
180478
- if (format2 === "image")
180479
- return '""';
180480
- switch (type) {
180481
- case "boolean":
180482
- return "false";
180483
- case "number":
180484
- return "0";
180485
- case "array":
180486
- return "[]";
180487
- case "object":
180488
- return "{}";
180489
- default:
180490
- return '""';
180491
- }
180492
- }
180493
-
180494
- // src/settings/defs-editor.js
180495
- var selectedDef = null;
180496
- var showAddField = false;
180497
- var newFieldState = { name: "", type: "string", format: "", required: false };
180498
- var showNewDef = false;
180499
- var newDefName = "";
180500
- async function saveProjectConfig() {
180501
- const platform3 = getPlatform();
180502
- const config = projectState.projectConfig;
180503
- await platform3.writeFile("project.json", JSON.stringify(config, null, "\t"));
180504
- }
180505
- function getSelectedDef() {
180506
- const config = projectState?.projectConfig;
180507
- return config?.$defs?.[selectedDef];
180508
- }
180509
- function handleNewDef(rerender) {
180510
- const name = newDefName.trim();
180511
- if (!name)
180512
- return;
180513
- const config = projectState?.projectConfig;
180514
- if (!config)
180515
- return;
180516
- if (!config.$defs)
180517
- config.$defs = {};
180518
- if (config.$defs[name])
180519
- return;
180520
- config.$defs[name] = {
180521
- type: "object",
180522
- properties: {},
180523
- required: []
180524
- };
180525
- selectedDef = name;
180526
- showNewDef = false;
180527
- newDefName = "";
180528
- rerender();
180529
- saveProjectConfig();
180530
- }
180531
- function handleAddField(rerender) {
180532
- const name = newFieldState.name.trim();
180533
- if (!name || !selectedDef)
180534
- return;
180535
- const def3 = getSelectedDef();
180536
- if (!def3)
180537
- return;
180538
- if (!def3.properties)
180539
- def3.properties = {};
180540
- def3.properties[name] = schemaForType(newFieldState.type, newFieldState.format || undefined);
180541
- if (newFieldState.required) {
180542
- if (!def3.required)
180543
- def3.required = [];
180544
- if (!def3.required.includes(name))
180545
- def3.required.push(name);
180546
- }
180547
- showAddField = false;
180548
- newFieldState = { name: "", type: "string", format: "", required: false };
180549
- rerender();
180550
- saveProjectConfig();
180551
- }
180552
- function handleDeleteField(fieldName, rerender) {
180553
- const def3 = getSelectedDef();
180554
- if (!def3?.properties)
180555
- return;
180556
- delete def3.properties[fieldName];
180557
- if (def3.required) {
180558
- def3.required = def3.required.filter((r) => r !== fieldName);
180559
- }
180560
- rerender();
180561
- saveProjectConfig();
180562
- }
180563
- function handleToggleRequired(fieldName, rerender) {
180564
- const def3 = getSelectedDef();
180565
- if (!def3)
180566
- return;
180567
- if (!def3.required)
180568
- def3.required = [];
180569
- const idx = def3.required.indexOf(fieldName);
180570
- if (idx >= 0)
180571
- def3.required.splice(idx, 1);
180572
- else
180573
- def3.required.push(fieldName);
180574
- rerender();
180575
- saveProjectConfig();
180576
- }
180577
- function handleRenameField(oldName, newName, rerender) {
180578
- const def3 = getSelectedDef();
180579
- if (!def3?.properties || !newName || def3.properties[newName])
180580
- return;
180581
- const newProps = {};
180582
- for (const [key, val] of Object.entries(def3.properties)) {
180583
- newProps[key === oldName ? newName : key] = val;
180584
- }
180585
- def3.properties = newProps;
180586
- if (def3.required) {
180587
- def3.required = def3.required.map((r) => r === oldName ? newName : r);
180588
- }
180589
- rerender();
180590
- saveProjectConfig();
180591
- }
180592
- function handleChangeType(fieldName, newType, rerender) {
180593
- const def3 = getSelectedDef();
180594
- if (!def3?.properties?.[fieldName])
180595
- return;
180596
- const oldFormat = newType === "string" || newType === "array" ? detectFieldFormat(def3.properties[fieldName]) : undefined;
180597
- def3.properties[fieldName] = schemaForType(newType, oldFormat || undefined);
180598
- rerender();
180599
- saveProjectConfig();
180600
- }
180601
- function handleChangeFormat(fieldName, format2, rerender) {
180602
- const def3 = getSelectedDef();
180603
- if (!def3?.properties?.[fieldName])
180604
- return;
180605
- const prop = def3.properties[fieldName];
180606
- const type = prop.type || "string";
180607
- def3.properties[fieldName] = schemaForType(type, format2 || undefined);
180608
- rerender();
180609
- saveProjectConfig();
180610
- }
180611
- function handleAddNestedField(parentName, fieldState, rerender) {
180612
- const def3 = getSelectedDef();
180613
- const parent = def3?.properties?.[parentName];
180614
- if (!parent)
180615
- return;
180616
- if (!parent.properties)
180617
- parent.properties = {};
180618
- parent.properties[fieldState.name] = schemaForType(fieldState.type);
180619
- if (fieldState.required) {
180620
- if (!parent.required)
180621
- parent.required = [];
180622
- if (!parent.required.includes(fieldState.name))
180623
- parent.required.push(fieldState.name);
180624
- }
180625
- rerender();
180626
- saveProjectConfig();
180627
- }
180628
- function handleDeleteNested(parentName, childName, rerender) {
180629
- const def3 = getSelectedDef();
180630
- const parent = def3?.properties?.[parentName];
180631
- if (!parent?.properties)
180632
- return;
180633
- delete parent.properties[childName];
180634
- if (parent.required) {
180635
- parent.required = parent.required.filter((r) => r !== childName);
180636
- }
180637
- rerender();
180638
- saveProjectConfig();
180639
- }
180640
- function handleToggleNestedRequired(parentName, childName, rerender) {
180641
- const def3 = getSelectedDef();
180642
- const parent = def3?.properties?.[parentName];
180643
- if (!parent)
180644
- return;
180645
- if (!parent.required)
180646
- parent.required = [];
180647
- const idx = parent.required.indexOf(childName);
180648
- if (idx >= 0)
180649
- parent.required.splice(idx, 1);
180650
- else
180651
- parent.required.push(childName);
180652
- rerender();
180653
- saveProjectConfig();
180654
- }
180655
- function handleRenameNested(parentName, oldChild, newChild, rerender) {
180656
- const def3 = getSelectedDef();
180657
- const parent = def3?.properties?.[parentName];
180658
- if (!parent?.properties || !newChild || parent.properties[newChild])
180659
- return;
180660
- const newProps = {};
180661
- for (const [key, val] of Object.entries(parent.properties)) {
180662
- newProps[key === oldChild ? newChild : key] = val;
180663
- }
180664
- parent.properties = newProps;
180665
- if (parent.required) {
180666
- parent.required = parent.required.map((r) => r === oldChild ? newChild : r);
180667
- }
180668
- rerender();
180669
- saveProjectConfig();
180670
- }
180671
- function handleChangeNestedType(parentName, childName, newType, rerender) {
180672
- const def3 = getSelectedDef();
180673
- const parent = def3?.properties?.[parentName];
180674
- if (!parent?.properties?.[childName])
180675
- return;
180676
- const oldFormat = newType === "string" || newType === "array" ? detectFieldFormat(parent.properties[childName]) : undefined;
180677
- parent.properties[childName] = schemaForType(newType, oldFormat || undefined);
180678
- rerender();
180679
- saveProjectConfig();
180680
- }
180681
- function handleChangeNestedFormat(parentName, childName, format2, rerender) {
180682
- const def3 = getSelectedDef();
180683
- const parent = def3?.properties?.[parentName];
180684
- if (!parent?.properties?.[childName])
180685
- return;
180686
- const prop = parent.properties[childName];
180687
- const type = prop.type || "string";
180688
- parent.properties[childName] = schemaForType(type, format2 || undefined);
180689
- rerender();
180690
- saveProjectConfig();
180691
- }
180692
- function handleDeleteDef(rerender) {
180693
- if (!selectedDef)
180694
- return;
180695
- const config = projectState?.projectConfig;
180696
- if (!config?.$defs?.[selectedDef])
180697
- return;
180698
- delete config.$defs[selectedDef];
180699
- selectedDef = null;
180700
- rerender();
180701
- saveProjectConfig();
180702
- }
180703
- function renderDefsEditor(container) {
180704
- const rerender = () => renderDefsEditor(container);
180705
- const config = projectState?.projectConfig;
180706
- const defs = config?.$defs || {};
180707
- const defNames = Object.keys(defs);
180708
- const listTpl = html`
180709
- <div class="settings-list-panel">
180710
- ${defNames.map((name) => html`
180711
- <sp-action-button
180712
- size="s"
180713
- ?selected=${selectedDef === name}
180714
- @click=${() => {
180715
- selectedDef = name;
180716
- showAddField = false;
180717
- rerender();
180718
- }}
180719
- >
180720
- ${name}
180721
- </sp-action-button>
180722
- `)}
180723
- ${showNewDef ? html`
180724
- <div class="settings-inline-form">
180725
- <sp-textfield
180726
- size="s"
180727
- placeholder="TypeName"
180728
- .value=${newDefName}
180729
- @input=${(e) => {
180730
- newDefName = e.target.value;
180731
- }}
180732
- @keydown=${(e) => {
180733
- if (e.key === "Enter")
180734
- handleNewDef(rerender);
180735
- if (e.key === "Escape") {
180736
- showNewDef = false;
180737
- rerender();
180738
- }
180739
- }}
180740
- ></sp-textfield>
180741
- <sp-action-button size="s" @click=${() => handleNewDef(rerender)}>
180742
- Create
180743
- </sp-action-button>
180744
- </div>
180745
- ` : html`
180746
- <sp-action-button
180747
- size="s"
180748
- quiet
180749
- @click=${() => {
180750
- showNewDef = true;
180751
- rerender();
180752
- }}
180753
- >
180754
- <sp-icon-add slot="icon"></sp-icon-add> New Definition
180755
- </sp-action-button>
180756
- `}
180757
- </div>
180758
- `;
180759
- let editorTpl;
180760
- if (!selectedDef || !defs[selectedDef]) {
180761
- editorTpl = html`<div class="settings-empty-state">Select or create a type definition</div>`;
180762
- } else {
180763
- const def3 = defs[selectedDef];
180764
- const properties = def3.properties || {};
180765
- const required = def3.required || [];
180766
- const handlers = {
180767
- onDelete: (n2) => handleDeleteField(n2, rerender),
180768
- onToggleRequired: (n2) => handleToggleRequired(n2, rerender),
180769
- onRename: (oldN, newN) => handleRenameField(oldN, newN, rerender),
180770
- onChangeType: (n2, t) => handleChangeType(n2, t, rerender),
180771
- onChangeFormat: (n2, f) => handleChangeFormat(n2, f, rerender),
180772
- onAddNestedField: (p, s2) => handleAddNestedField(p, s2, rerender),
180773
- onDeleteNested: (p, c) => handleDeleteNested(p, c, rerender),
180774
- onToggleNestedRequired: (p, c) => handleToggleNestedRequired(p, c, rerender),
180775
- onRenameNested: (p, o, n2) => handleRenameNested(p, o, n2, rerender),
180776
- onChangeNestedType: (p, c, t) => handleChangeNestedType(p, c, t, rerender),
180777
- onChangeNestedFormat: (p, c, f) => handleChangeNestedFormat(p, c, f, rerender)
180778
- };
180779
- const fieldCards = Object.entries(properties).map(([name, fieldDef]) => fieldCardTpl(name, fieldDef, required.includes(name), handlers));
180780
- editorTpl = html`
180781
- <div class="settings-editor-panel">
180782
- <div class="settings-editor-header">
180783
- <h3>${selectedDef}</h3>
180784
- <sp-action-button
180785
- size="xs"
180786
- quiet
180787
- title="Delete definition"
180788
- @click=${() => handleDeleteDef(rerender)}
180789
- >
180790
- <sp-icon-delete slot="icon"></sp-icon-delete>
180791
- </sp-action-button>
180792
- </div>
180793
- <div class="schema-field-list">${fieldCards}</div>
180794
- ${showAddField ? addFieldFormTpl(newFieldState, {
180795
- onInput: (field, value2) => {
180796
- newFieldState = { ...newFieldState, [field]: value2 };
180797
- rerender();
180798
- },
180799
- onConfirm: () => handleAddField(rerender),
180800
- onCancel: () => {
180801
- showAddField = false;
180802
- newFieldState = { name: "", type: "string", format: "", required: false };
180803
- rerender();
180804
- }
180805
- }) : html`
180806
- <sp-action-button
180807
- size="s"
180808
- quiet
180809
- @click=${() => {
180810
- showAddField = true;
180811
- rerender();
180812
- }}
180813
- >
180814
- <sp-icon-add slot="icon"></sp-icon-add> Add Field
180815
- </sp-action-button>
180816
- `}
180817
- </div>
180818
- `;
180819
- }
180820
- const tpl = html` <div class="settings-two-col">${listTpl} ${editorTpl}</div> `;
180821
- render2(tpl, container);
180822
- }
180823
-
180824
- // src/settings/content-types-editor.js
180825
- init_platform();
180826
- init_store();
180827
- var selectedContentType = null;
180828
- var showAddField2 = false;
180829
- var newFieldState2 = { name: "", type: "string", format: "", required: false };
180830
- var showNewContentType = false;
180831
- var newContentTypeName = "";
180832
- async function saveProjectConfig2() {
180833
- const platform3 = getPlatform();
180834
- const config = projectState.projectConfig;
180835
- await platform3.writeFile("project.json", JSON.stringify(config, null, "\t"));
180836
- }
180837
- function getSelectedSchema() {
180838
- const config = projectState?.projectConfig;
180839
- return config?.contentTypes?.[selectedContentType]?.schema;
180840
- }
180841
- function handleNewContentType(rerender) {
180842
- const slug = newContentTypeName.toLowerCase().replace(/\s+/g, "-").replace(/[^a-z0-9-]/g, "");
180843
- if (!slug)
180844
- return;
180845
- const config = projectState?.projectConfig;
180846
- if (!config)
180847
- return;
180848
- if (!config.contentTypes)
180849
- config.contentTypes = {};
180850
- if (config.contentTypes[slug])
180851
- return;
180852
- config.contentTypes[slug] = {
180853
- source: `./content/${slug}/**/*.md`,
180854
- schema: { type: "object", properties: {}, required: [] }
180855
- };
180856
- selectedContentType = slug;
180857
- showNewContentType = false;
180858
- newContentTypeName = "";
180859
- rerender();
180860
- saveProjectConfig2().then(async () => {
180861
- const platform3 = getPlatform();
180862
- await platform3.writeFile(`content/${slug}/.gitkeep`, "");
180863
- });
180864
- }
180865
- function handleAddField2(rerender) {
180866
- const name = newFieldState2.name.trim();
180867
- if (!name || !selectedContentType)
180868
- return;
180869
- const schema4 = getSelectedSchema();
180870
- if (!schema4)
180871
- return;
180872
- if (!schema4.properties)
180873
- schema4.properties = {};
180874
- schema4.properties[name] = schemaForType(newFieldState2.type, newFieldState2.format || undefined);
180875
- if (newFieldState2.required) {
180876
- if (!schema4.required)
180877
- schema4.required = [];
180878
- if (!schema4.required.includes(name))
180879
- schema4.required.push(name);
180880
- }
180881
- showAddField2 = false;
180882
- newFieldState2 = { name: "", type: "string", format: "", required: false };
180883
- rerender();
180884
- saveProjectConfig2();
180885
- }
180886
- function handleDeleteField2(fieldName, rerender) {
180887
- const schema4 = getSelectedSchema();
180888
- if (!schema4?.properties)
180889
- return;
180890
- delete schema4.properties[fieldName];
180891
- if (schema4.required) {
180892
- schema4.required = schema4.required.filter((r) => r !== fieldName);
180893
- }
180894
- rerender();
180895
- saveProjectConfig2();
180896
- }
180897
- function handleToggleRequired2(fieldName, rerender) {
180898
- const schema4 = getSelectedSchema();
180899
- if (!schema4)
180900
- return;
180901
- if (!schema4.required)
180902
- schema4.required = [];
180903
- const idx = schema4.required.indexOf(fieldName);
180904
- if (idx >= 0)
180905
- schema4.required.splice(idx, 1);
180906
- else
180907
- schema4.required.push(fieldName);
180908
- rerender();
180909
- saveProjectConfig2();
180910
- }
180911
- function handleRenameField2(oldName, newName, rerender) {
180912
- const schema4 = getSelectedSchema();
180913
- if (!schema4?.properties || !newName || schema4.properties[newName])
180914
- return;
180915
- const newProps = {};
180916
- for (const [key, val] of Object.entries(schema4.properties)) {
180917
- newProps[key === oldName ? newName : key] = val;
180918
- }
180919
- schema4.properties = newProps;
180920
- if (schema4.required) {
180921
- schema4.required = schema4.required.map((r) => r === oldName ? newName : r);
180922
- }
180923
- rerender();
180924
- saveProjectConfig2();
180925
- }
180926
- function handleChangeType2(fieldName, newType, rerender) {
180927
- const schema4 = getSelectedSchema();
180928
- if (!schema4?.properties?.[fieldName])
180929
- return;
180930
- const oldFormat = newType === "string" || newType === "array" ? detectFieldFormat(schema4.properties[fieldName]) : undefined;
180931
- schema4.properties[fieldName] = schemaForType(newType, oldFormat || undefined);
180932
- rerender();
180933
- saveProjectConfig2();
180934
- }
180935
- function handleChangeFormat2(fieldName, format2, rerender) {
180936
- const schema4 = getSelectedSchema();
180937
- if (!schema4?.properties?.[fieldName])
180938
- return;
180939
- const prop = schema4.properties[fieldName];
180940
- const type = prop.type || "string";
180941
- schema4.properties[fieldName] = schemaForType(type, format2 || undefined);
180942
- rerender();
180943
- saveProjectConfig2();
180944
- }
180945
- function handleChangeRefTarget(fieldName, target, rerender) {
180946
- const schema4 = getSelectedSchema();
180947
- if (!schema4?.properties)
180948
- return;
180949
- schema4.properties[fieldName] = { $ref: `#/contentTypes/${target}` };
180950
- rerender();
180951
- saveProjectConfig2();
180952
- }
180953
- function handleAddNestedField2(parentName, fieldState, rerender) {
180954
- const schema4 = getSelectedSchema();
180955
- const parent = schema4?.properties?.[parentName];
180956
- if (!parent)
180957
- return;
180958
- if (!parent.properties)
180959
- parent.properties = {};
180960
- parent.properties[fieldState.name] = schemaForType(fieldState.type);
180961
- if (fieldState.required) {
180962
- if (!parent.required)
180963
- parent.required = [];
180964
- if (!parent.required.includes(fieldState.name))
180965
- parent.required.push(fieldState.name);
180966
- }
180967
- rerender();
180968
- saveProjectConfig2();
180969
- }
180970
- function handleDeleteNested2(parentName, childName, rerender) {
180971
- const schema4 = getSelectedSchema();
180972
- const parent = schema4?.properties?.[parentName];
180973
- if (!parent?.properties)
180974
- return;
180975
- delete parent.properties[childName];
180976
- if (parent.required) {
180977
- parent.required = parent.required.filter((r) => r !== childName);
180978
- }
180979
- rerender();
180980
- saveProjectConfig2();
180981
- }
180982
- function handleToggleNestedRequired2(parentName, childName, rerender) {
180983
- const schema4 = getSelectedSchema();
180984
- const parent = schema4?.properties?.[parentName];
180985
- if (!parent)
180986
- return;
180987
- if (!parent.required)
180988
- parent.required = [];
180989
- const idx = parent.required.indexOf(childName);
180990
- if (idx >= 0)
180991
- parent.required.splice(idx, 1);
180992
- else
180993
- parent.required.push(childName);
180994
- rerender();
180995
- saveProjectConfig2();
180996
- }
180997
- function handleRenameNested2(parentName, oldChild, newChild, rerender) {
180998
- const schema4 = getSelectedSchema();
180999
- const parent = schema4?.properties?.[parentName];
181000
- if (!parent?.properties || !newChild || parent.properties[newChild])
181001
- return;
181002
- const newProps = {};
181003
- for (const [key, val] of Object.entries(parent.properties)) {
181004
- newProps[key === oldChild ? newChild : key] = val;
181005
- }
181006
- parent.properties = newProps;
181007
- if (parent.required) {
181008
- parent.required = parent.required.map((r) => r === oldChild ? newChild : r);
181009
- }
181010
- rerender();
181011
- saveProjectConfig2();
181012
- }
181013
- function handleChangeNestedType2(parentName, childName, newType, rerender) {
181014
- const schema4 = getSelectedSchema();
181015
- const parent = schema4?.properties?.[parentName];
181016
- if (!parent?.properties?.[childName])
181017
- return;
181018
- const oldFormat = newType === "string" || newType === "array" ? detectFieldFormat(parent.properties[childName]) : undefined;
181019
- parent.properties[childName] = schemaForType(newType, oldFormat || undefined);
181020
- rerender();
181021
- saveProjectConfig2();
181022
- }
181023
- function handleChangeNestedFormat2(parentName, childName, format2, rerender) {
181024
- const schema4 = getSelectedSchema();
181025
- const parent = schema4?.properties?.[parentName];
181026
- if (!parent?.properties?.[childName])
181027
- return;
181028
- const prop = parent.properties[childName];
181029
- const type = prop.type || "string";
181030
- parent.properties[childName] = schemaForType(type, format2 || undefined);
181031
- rerender();
181032
- saveProjectConfig2();
181033
- }
181034
- function handleDeleteContentType(rerender) {
181035
- if (!selectedContentType)
181036
- return;
181037
- const config = projectState?.projectConfig;
181038
- if (!config?.contentTypes?.[selectedContentType])
181039
- return;
181040
- delete config.contentTypes[selectedContentType];
181041
- selectedContentType = null;
181042
- rerender();
181043
- saveProjectConfig2();
181044
- }
181045
- function renderContentTypesEditor(container) {
181046
- const rerender = () => renderContentTypesEditor(container);
181047
- const config = projectState?.projectConfig;
181048
- const contentTypes = config?.contentTypes || {};
181049
- const contentTypeNames = Object.keys(contentTypes);
181050
- const listTpl = html`
181051
- <div class="settings-list-panel">
181052
- ${contentTypeNames.map((name) => html`
181053
- <sp-action-button
181054
- size="s"
181055
- ?selected=${selectedContentType === name}
181056
- @click=${() => {
181057
- selectedContentType = name;
181058
- showAddField2 = false;
181059
- rerender();
181060
- }}
181061
- >
181062
- ${name}
181063
- </sp-action-button>
181064
- `)}
181065
- ${showNewContentType ? html`
181066
- <div class="settings-inline-form">
181067
- <sp-textfield
181068
- size="s"
181069
- placeholder="content-type-name"
181070
- .value=${newContentTypeName}
181071
- @input=${(e) => {
181072
- newContentTypeName = e.target.value;
181073
- }}
181074
- @keydown=${(e) => {
181075
- if (e.key === "Enter")
181076
- handleNewContentType(rerender);
181077
- if (e.key === "Escape") {
181078
- showNewContentType = false;
181079
- rerender();
181080
- }
181081
- }}
181082
- ></sp-textfield>
181083
- <sp-action-button size="s" @click=${() => handleNewContentType(rerender)}>
181084
- Create
181085
- </sp-action-button>
181086
- </div>
181087
- ` : html`
181088
- <sp-action-button
181089
- size="s"
181090
- quiet
181091
- @click=${() => {
181092
- showNewContentType = true;
181093
- rerender();
181094
- }}
181095
- >
181096
- <sp-icon-add slot="icon"></sp-icon-add> New Content Type
181097
- </sp-action-button>
181098
- `}
181099
- </div>
181100
- `;
181101
- let editorTpl;
181102
- if (!selectedContentType || !contentTypes[selectedContentType]) {
181103
- editorTpl = html`<div class="settings-empty-state">Select or create a content type</div>`;
181104
- } else {
181105
- const col = contentTypes[selectedContentType];
181106
- const schema4 = col.schema || {};
181107
- const properties = schema4.properties || {};
181108
- const required = schema4.required || [];
181109
- const handlers = {
181110
- onDelete: (n2) => handleDeleteField2(n2, rerender),
181111
- onToggleRequired: (n2) => handleToggleRequired2(n2, rerender),
181112
- onRename: (oldN, newN) => handleRenameField2(oldN, newN, rerender),
181113
- onChangeType: (n2, t) => handleChangeType2(n2, t, rerender),
181114
- onChangeFormat: (n2, f) => handleChangeFormat2(n2, f, rerender),
181115
- onChangeRefTarget: (n2, target) => handleChangeRefTarget(n2, target, rerender),
181116
- onAddNestedField: (p, s2) => handleAddNestedField2(p, s2, rerender),
181117
- onDeleteNested: (p, c) => handleDeleteNested2(p, c, rerender),
181118
- onToggleNestedRequired: (p, c) => handleToggleNestedRequired2(p, c, rerender),
181119
- onRenameNested: (p, o, n2) => handleRenameNested2(p, o, n2, rerender),
181120
- onChangeNestedType: (p, c, t) => handleChangeNestedType2(p, c, t, rerender),
181121
- onChangeNestedFormat: (p, c, f) => handleChangeNestedFormat2(p, c, f, rerender)
181122
- };
181123
- const fieldCards = Object.entries(properties).map(([name, def3]) => fieldCardTpl(name, def3, required.includes(name), handlers, contentTypeNames));
181124
- editorTpl = html`
181125
- <div class="settings-editor-panel">
181126
- <div class="settings-editor-header">
181127
- <h3>${selectedContentType}</h3>
181128
- <sp-field-label size="s">Source: ${col.source || "—"}</sp-field-label>
181129
- <sp-action-button
181130
- size="xs"
181131
- quiet
181132
- title="Delete content type"
181133
- @click=${() => handleDeleteContentType(rerender)}
181134
- >
181135
- <sp-icon-delete slot="icon"></sp-icon-delete>
181136
- </sp-action-button>
181137
- </div>
181138
- <div class="schema-field-list">${fieldCards}</div>
181139
- ${showAddField2 ? addFieldFormTpl(newFieldState2, {
181140
- onInput: (field, value2) => {
181141
- newFieldState2 = { ...newFieldState2, [field]: value2 };
181142
- rerender();
181143
- },
181144
- onConfirm: () => handleAddField2(rerender),
181145
- onCancel: () => {
181146
- showAddField2 = false;
181147
- newFieldState2 = { name: "", type: "string", format: "", required: false };
181148
- rerender();
181149
- }
181150
- }) : html`
181151
- <sp-action-button
181152
- size="s"
181153
- quiet
181154
- @click=${() => {
181155
- showAddField2 = true;
181156
- rerender();
181157
- }}
181158
- >
181159
- <sp-icon-add slot="icon"></sp-icon-add> Add Field
181160
- </sp-action-button>
181161
- `}
181162
- </div>
181163
- `;
181164
- }
181165
- const tpl = html` <div class="settings-two-col">${listTpl} ${editorTpl}</div> `;
181166
- render2(tpl, container);
181167
- }
181168
180103
  // data/stylebook-meta.json
181169
180104
  var stylebook_meta_default = {
181170
180105
  $sections: [
@@ -181375,39 +180310,6 @@ registerBlockType( name, settings );`
181375
180310
  var _ctx6 = null;
181376
180311
  function renderStylebookMode(ctx) {
181377
180312
  _ctx6 = ctx;
181378
- const settingsTab = activeTab.value?.session.ui.settingsTab || "stylebook";
181379
- const settingsChromeBarTpl = html`
181380
- <div
181381
- class="sb-chrome settings-top-chrome"
181382
- style="position:absolute;top:0;left:0;right:0;z-index:16;background:var(--bg-panel);border-bottom:1px solid var(--border)"
181383
- >
181384
- <sp-tabs
181385
- size="s"
181386
- selected=${settingsTab}
181387
- @change=${(e) => {
181388
- updateUi("settingsTab", e.target.selected);
181389
- }}
181390
- >
181391
- <sp-tab label="Stylebook" value="stylebook"></sp-tab>
181392
- <sp-tab label="Definitions" value="definitions"></sp-tab>
181393
- <sp-tab label="Content Types" value="contentTypes"></sp-tab>
181394
- </sp-tabs>
181395
- </div>
181396
- `;
181397
- if (settingsTab === "definitions" || settingsTab === "contentTypes") {
181398
- canvasWrap.style.overflow = "hidden";
181399
- render2(html`${settingsChromeBarTpl}
181400
- <div
181401
- class="settings-editor-container"
181402
- style="position:absolute;inset:40px 0 0 0;overflow:auto"
181403
- ></div>`, canvasWrap);
181404
- const container = canvasWrap.querySelector(".settings-editor-container");
181405
- if (settingsTab === "definitions")
181406
- renderDefsEditor(container);
181407
- else
181408
- renderContentTypesEditor(container);
181409
- return;
181410
- }
181411
180313
  view.stylebookElToTag = new WeakMap;
181412
180314
  const tab = activeTab.value;
181413
180315
  const rootStyle = getEffectiveStyle(tab?.doc.document?.style);
@@ -181415,9 +180317,6 @@ function renderStylebookMode(ctx) {
181415
180317
  const customizedOnly = tab?.session.ui.stylebookCustomizedOnly;
181416
180318
  const { sizeBreakpoints, baseWidth } = parseMediaEntries(getEffectiveMedia(tab?.doc.document?.$media));
181417
180319
  const hasMedia = sizeBreakpoints.length > 0;
181418
- const onTabClick = (t) => {
181419
- updateUi("stylebookTab", t);
181420
- };
181421
180320
  const onFilterInput2 = (e) => {
181422
180321
  updateUi("stylebookFilter", e.target.value);
181423
180322
  };
@@ -181425,38 +180324,25 @@ function renderStylebookMode(ctx) {
181425
180324
  updateUi("stylebookCustomizedOnly", !tab?.session.ui.stylebookCustomizedOnly);
181426
180325
  };
181427
180326
  const chromeBarTpl = html`
181428
- ${settingsChromeBarTpl}
181429
180327
  <div
181430
180328
  class="sb-chrome"
181431
- style="position:absolute;top:36px;left:0;right:0;z-index:15;background:var(--bg-panel);border-bottom:1px solid var(--border)"
180329
+ style="position:absolute;top:0;left:0;right:0;z-index:15;background:var(--bg-panel);border-bottom:1px solid var(--border)"
181432
180330
  >
181433
- <sp-tabs
181434
- size="s"
181435
- selected=${tab?.session.ui.stylebookTab || "elements"}
181436
- @change=${(e) => {
181437
- onTabClick(e.target.selected);
181438
- }}
181439
- >
181440
- ${["elements", "variables"].map((t) => html`
181441
- <sp-tab label=${t.charAt(0).toUpperCase() + t.slice(1)} value=${t}></sp-tab>
181442
- `)}
181443
- </sp-tabs>
181444
- ${tab?.session.ui.stylebookTab === "elements" ? html`
181445
- <input
181446
- class="field-input"
181447
- style="flex:1;max-width:200px;margin-left:8px"
181448
- placeholder="Filter…"
181449
- .value=${tab?.session.ui.stylebookFilter}
181450
- @input=${onFilterInput2}
181451
- />
181452
- <button
181453
- class="tb-toggle${tab?.session.ui.stylebookCustomizedOnly ? " active" : ""}"
181454
- style="margin-left:4px"
181455
- @click=${onCustomizedToggle}
181456
- >
181457
- Customized
181458
- </button>
181459
- ` : nothing}
180331
+ <div style="display:flex;align-items:center;padding:4px 8px;gap:4px">
180332
+ <input
180333
+ class="field-input"
180334
+ style="flex:1;max-width:200px"
180335
+ placeholder="Filter…"
180336
+ .value=${tab?.session.ui.stylebookFilter}
180337
+ @input=${onFilterInput2}
180338
+ />
180339
+ <button
180340
+ class="tb-toggle${tab?.session.ui.stylebookCustomizedOnly ? " active" : ""}"
180341
+ @click=${onCustomizedToggle}
180342
+ >
180343
+ Customized
180344
+ </button>
180345
+ </div>
181460
180346
  </div>
181461
180347
  `;
181462
180348
  canvasWrap.style.overflow = "hidden";
@@ -181479,16 +180365,11 @@ function renderStylebookMode(ctx) {
181479
180365
  }
181480
180366
  const renderIntoPanel = (panel, activeBreakpoints) => {
181481
180367
  panel.canvas.classList.add("sb-canvas");
181482
- if (tab?.session.ui.stylebookTab === "elements") {
181483
- renderStylebookElementsIntoCanvas(panel.canvas, rootStyle, filter, customizedOnly, activeBreakpoints);
181484
- for (const child of panel.canvas.querySelectorAll("*")) {
181485
- child.style.pointerEvents = "none";
181486
- }
181487
- registerStylebookPanelEvents(panel);
181488
- } else {
181489
- renderStylebookVarsIntoCanvas(panel.canvas, rootStyle);
181490
- panel.overlayClk.style.pointerEvents = "none";
180368
+ renderStylebookElementsIntoCanvas(panel.canvas, rootStyle, filter, customizedOnly, activeBreakpoints);
180369
+ for (const child of panel.canvas.querySelectorAll("*")) {
180370
+ child.style.pointerEvents = "none";
181491
180371
  }
180372
+ registerStylebookPanelEvents(panel);
181492
180373
  };
181493
180374
  let panelEntries;
181494
180375
  if (!hasMedia) {
@@ -181508,7 +180389,7 @@ function renderStylebookMode(ctx) {
181508
180389
  ${chromeBarTpl}
181509
180390
  <div
181510
180391
  class="panzoom-wrap"
181511
- style="transform-origin:0 0;padding-top:72px"
180392
+ style="transform-origin:0 0;padding-top:40px"
181512
180393
  ${ref2((el) => {
181513
180394
  if (el)
181514
180395
  view.panzoomWrap = el;
@@ -181798,363 +180679,6 @@ function renderStylebookElementsIntoCanvas(canvasEl, rootStyle, filter, customiz
181798
180679
  render2(html`${sectionTemplates}`, canvasEl);
181799
180680
  }
181800
180681
  }
181801
- function renderStylebookVarsIntoCanvas(canvasEl, rootStyle) {
181802
- const varCats = stylebook_meta_default.$variables;
181803
- const groups = {};
181804
- for (const key of Object.keys(varCats))
181805
- groups[key] = [];
181806
- for (const [k, v] of Object.entries(rootStyle)) {
181807
- if (!k.startsWith("--"))
181808
- continue;
181809
- if (typeof v !== "string" && typeof v !== "number")
181810
- continue;
181811
- if (k.startsWith("--color"))
181812
- groups.color.push([k, v]);
181813
- else if (k.startsWith("--font"))
181814
- groups.font.push([k, v]);
181815
- else if (k.startsWith("--size") || k.startsWith("--spacing") || k.startsWith("--radius"))
181816
- groups.size.push([k, v]);
181817
- else
181818
- groups.other.push([k, v]);
181819
- }
181820
- const bodyRefs = new Map;
181821
- const sectionTemplates = Object.entries(varCats).map(([catKey, catMeta]) => {
181822
- const vars = groups[catKey];
181823
- const onAdd = () => {
181824
- const bodyEl = bodyRefs.get(catKey);
181825
- if (!bodyEl)
181826
- return;
181827
- const addBtn = bodyEl.querySelector(".sb-var-add-btn");
181828
- const row = renderVarRow(catKey, catMeta, null, "", true);
181829
- bodyEl.insertBefore(row, addBtn);
181830
- if (addBtn)
181831
- addBtn.style.display = "none";
181832
- const nameField = row.querySelector("sp-textfield");
181833
- if (nameField)
181834
- requestAnimationFrame(() => nameField.focus());
181835
- };
181836
- return html`
181837
- <div class="sb-section">
181838
- <div class="sb-label">${catMeta.label}</div>
181839
- <div
181840
- class="sb-body"
181841
- ${ref2((el) => {
181842
- if (el)
181843
- bodyRefs.set(catKey, el);
181844
- })}
181845
- >
181846
- ${vars.length > 0 ? vars.map(([varName, varVal]) => renderVarRow(catKey, catMeta, varName, String(varVal), false)) : html`<div class="sb-var-empty">
181847
- No ${catMeta.label.toLowerCase()} variables yet.
181848
- </div>`}
181849
- <button class="sb-var-add-btn" @click=${onAdd}>
181850
- <span class="sb-var-add-icon">+</span> Add ${catMeta.label}
181851
- </button>
181852
- </div>
181853
- </div>
181854
- `;
181855
- });
181856
- render2(html`${sectionTemplates}`, canvasEl);
181857
- }
181858
- function renderVarRow(catKey, catMeta, varName, varVal, isNew) {
181859
- const row = document.createElement("div");
181860
- row.className = isNew ? "sb-var-row is-new" : "sb-var-row";
181861
- let colorPicker = null;
181862
- let nameField = null;
181863
- let getValueFn;
181864
- let hexField = null;
181865
- const swatchTpl = catKey === "color" ? html`
181866
- <div
181867
- class="sb-var-swatch"
181868
- style=${styleMap({ backgroundColor: varVal || "var(--accent)" })}
181869
- >
181870
- <input
181871
- type="color"
181872
- .value=${varVal && varVal.startsWith("#") ? varVal : "#007acc"}
181873
- ${ref2((el) => {
181874
- if (el)
181875
- colorPicker = el;
181876
- })}
181877
- @input=${() => {
181878
- if (!colorPicker || !hexField)
181879
- return;
181880
- hexField.value = colorPicker.value;
181881
- const swatch = row.querySelector(".sb-var-swatch");
181882
- if (swatch)
181883
- swatch.style.backgroundColor = colorPicker.value;
181884
- if (!isNew && varName) {
181885
- transactDoc(activeTab.value, (t) => mutateUpdateStyle(t, [], varName, colorPicker.value));
181886
- }
181887
- }}
181888
- />
181889
- </div>
181890
- ` : nothing;
181891
- const namePlaceholder = catKey === "color" ? "Primary Blue" : catKey === "font" ? "Body Serif" : catKey === "size" ? "Spacing Large" : "Border Radius";
181892
- const nameColTpl = isNew ? html`
181893
- <div class="sb-var-col-name">
181894
- <div class="sb-var-col-label">Name</div>
181895
- <sp-textfield
181896
- size="s"
181897
- placeholder=${namePlaceholder}
181898
- style="pointer-events:auto"
181899
- ${ref2((el) => {
181900
- if (el)
181901
- nameField = el;
181902
- })}
181903
- ></sp-textfield>
181904
- </div>
181905
- ` : nothing;
181906
- let valueContent;
181907
- if (catKey === "color") {
181908
- let debounce;
181909
- valueContent = html`
181910
- <sp-textfield
181911
- size="s"
181912
- .value=${varVal || "#007acc"}
181913
- placeholder="#007acc"
181914
- style="pointer-events:auto"
181915
- ${ref2((el) => {
181916
- if (el)
181917
- hexField = el;
181918
- })}
181919
- @input=${() => {
181920
- clearTimeout(debounce);
181921
- debounce = setTimeout(() => {
181922
- if (!hexField)
181923
- return;
181924
- const v = hexField.value;
181925
- try {
181926
- if (colorPicker)
181927
- colorPicker.value = v.startsWith("#") ? v : colorPicker.value;
181928
- } catch {}
181929
- const swatch = row.querySelector(".sb-var-swatch");
181930
- if (swatch)
181931
- swatch.style.backgroundColor = v;
181932
- if (!isNew && varName) {
181933
- transactDoc(activeTab.value, (t) => mutateUpdateStyle(t, [], varName, v));
181934
- }
181935
- }, 400);
181936
- }}
181937
- ></sp-textfield>
181938
- `;
181939
- getValueFn = () => hexField?.value?.trim() || "";
181940
- } else if (catKey === "size") {
181941
- const ui = createUnitInput(varVal || "16px", {
181942
- onChange: (newVal) => {
181943
- const bar = row.querySelector(".sb-var-size-bar");
181944
- if (bar)
181945
- bar.style.width = newVal;
181946
- if (!isNew && varName) {
181947
- transactDoc(activeTab.value, (t) => mutateUpdateStyle(t, [], varName, newVal));
181948
- }
181949
- }
181950
- });
181951
- if (isNew)
181952
- ui.textfield.value = "";
181953
- valueContent = html`<div
181954
- ${ref2((el) => {
181955
- if (el && !el.firstChild)
181956
- el.appendChild(ui.wrap);
181957
- })}
181958
- ></div>`;
181959
- getValueFn = () => ui.getValue();
181960
- } else {
181961
- let textFieldEl = null;
181962
- let debounce;
181963
- valueContent = html`
181964
- <sp-textfield
181965
- size="s"
181966
- .value=${varVal}
181967
- placeholder=${catMeta.placeholder}
181968
- style="pointer-events:auto"
181969
- ${ref2((el) => {
181970
- if (el)
181971
- textFieldEl = el;
181972
- })}
181973
- @input=${() => {
181974
- if (!textFieldEl || isNew || !varName)
181975
- return;
181976
- clearTimeout(debounce);
181977
- debounce = setTimeout(() => {
181978
- const v = textFieldEl.value;
181979
- const fontPrev = row.querySelector(".sb-var-font-preview");
181980
- if (fontPrev)
181981
- fontPrev.style.fontFamily = v;
181982
- transactDoc(activeTab.value, (t) => mutateUpdateStyle(t, [], varName, v));
181983
- }, 400);
181984
- }}
181985
- ></sp-textfield>
181986
- `;
181987
- getValueFn = () => textFieldEl?.value?.trim() || "";
181988
- }
181989
- const valColTpl = html`
181990
- <div class="sb-var-col-value">
181991
- ${isNew ? html`<div class="sb-var-col-label">Value</div>` : nothing} ${valueContent}
181992
- </div>
181993
- `;
181994
- const actionsTpl = isNew ? html`
181995
- <div class="sb-var-add-actions">
181996
- <sp-action-button
181997
- size="s"
181998
- style="pointer-events:auto"
181999
- @click=${() => {
182000
- const name = (nameField?.value || "").trim();
182001
- const val = getValueFn();
182002
- const generatedVar = friendlyNameToVar(name, catMeta.prefix);
182003
- if (!generatedVar || !val)
182004
- return;
182005
- transactDoc(activeTab.value, (t) => mutateUpdateStyle(t, [], generatedVar, val));
182006
- }}
182007
- >Add</sp-action-button
182008
- >
182009
- <sp-action-button
182010
- size="s"
182011
- quiet
182012
- style="pointer-events:auto"
182013
- @click=${() => {
182014
- const body = row.parentElement;
182015
- row.remove();
182016
- const addBtn = body?.querySelector(".sb-var-add-btn");
182017
- if (addBtn)
182018
- addBtn.style.display = "";
182019
- }}
182020
- >
182021
- <sp-icon-close slot="icon"></sp-icon-close>
182022
- </sp-action-button>
182023
- </div>
182024
- ` : nothing;
182025
- const headerTpl = !isNew && varName ? html`
182026
- <div class="sb-var-row-header">
182027
- <span class="sb-var-row-title">${varDisplayName(varName, catMeta.prefix)}</span>
182028
- <span class="sb-var-row-ref">${varName}</span>
182029
- <sp-action-button
182030
- size="s"
182031
- quiet
182032
- class="sb-var-del"
182033
- style="pointer-events:auto"
182034
- @click=${() => {
182035
- transactDoc(activeTab.value, (t) => mutateUpdateStyle(t, [], varName, undefined));
182036
- }}
182037
- >
182038
- <sp-icon-delete slot="icon"></sp-icon-delete>
182039
- </sp-action-button>
182040
- </div>
182041
- ` : nothing;
182042
- const addPreviewTpl = isNew ? html`
182043
- <div
182044
- class="sb-var-add-preview"
182045
- ${ref2((el) => {
182046
- if (!el || !nameField)
182047
- return;
182048
- nameField.addEventListener("input", () => {
182049
- el.textContent = friendlyNameToVar(nameField.value || "", catMeta.prefix);
182050
- });
182051
- })}
182052
- ></div>
182053
- ` : nothing;
182054
- const typePrevTpl = catKey === "font" && varVal ? html`
182055
- <div class="sb-var-preview">
182056
- <div class="sb-var-font-preview" style=${styleMap({ fontFamily: varVal })}>
182057
- The quick brown fox jumps over the lazy dog
182058
- </div>
182059
- </div>
182060
- ` : catKey === "size" && varVal ? html`
182061
- <div class="sb-var-preview">
182062
- <div class="sb-var-size-track">
182063
- <div class="sb-var-size-bar" style=${styleMap({ width: varVal })}></div>
182064
- </div>
182065
- </div>
182066
- ` : nothing;
182067
- render2(html`
182068
- ${headerTpl}
182069
- <div class="sb-var-input-row">${swatchTpl} ${nameColTpl} ${valColTpl} ${actionsTpl}</div>
182070
- ${addPreviewTpl} ${typePrevTpl}
182071
- `, row);
182072
- return row;
182073
- }
182074
- function createUnitInput(initialValue, { onChange, size = "s" } = {}) {
182075
- const match2 = String(initialValue).match(/^(-?[\d.]+)\s*(px|em|rem|vw|vh|%|ch|ex|vmin|vmax|pt|cm|mm|in)?$/);
182076
- let numVal = match2 ? match2[1] : initialValue;
182077
- let unitVal = match2 ? match2[2] || "px" : "";
182078
- const isNumeric = !!match2;
182079
- const wrap4 = document.createElement("div");
182080
- wrap4.className = "sb-unit-input";
182081
- wrap4.style.pointerEvents = "auto";
182082
- let textfield = null;
182083
- let picker = null;
182084
- const units = [
182085
- { value: "px", label: "px" },
182086
- { value: "rem", label: "rem" },
182087
- { value: "em", label: "em" },
182088
- { value: "%", label: "%" },
182089
- { value: "vw", label: "vw" },
182090
- { value: "vh", label: "vh" },
182091
- { value: "ch", label: "ch" },
182092
- { value: "pt", label: "pt" },
182093
- { divider: true },
182094
- { value: "auto", label: "auto" },
182095
- { value: "fit-content", label: "fit-content" }
182096
- ];
182097
- let debounce;
182098
- function getValue() {
182099
- const num = textfield?.value;
182100
- const unit = picker?.value;
182101
- if (unit === "auto" || unit === "fit-content")
182102
- return unit;
182103
- return num ? `${num}${unit}` : "";
182104
- }
182105
- render2(html`
182106
- <sp-textfield
182107
- .value=${numVal}
182108
- size=${size}
182109
- ${ref2((el) => {
182110
- if (el)
182111
- textfield = el;
182112
- })}
182113
- @input=${() => {
182114
- clearTimeout(debounce);
182115
- const raw = textfield?.value?.trim();
182116
- const looksNumeric = /^-?[\d.]+$/.test(raw || "");
182117
- if (picker)
182118
- picker.style.display = looksNumeric ? "" : "none";
182119
- debounce = setTimeout(() => {
182120
- if (onChange)
182121
- onChange(looksNumeric ? `${raw}${picker?.value}` : raw);
182122
- }, 400);
182123
- }}
182124
- ></sp-textfield>
182125
- <sp-picker
182126
- quiet
182127
- size=${size}
182128
- style=${styleMap({ display: isNumeric ? "" : "none" })}
182129
- ${ref2((el) => {
182130
- if (el) {
182131
- picker = el;
182132
- requestAnimationFrame(() => {
182133
- el.value = unitVal || "px";
182134
- });
182135
- }
182136
- })}
182137
- @change=${() => {
182138
- const unit = picker?.value;
182139
- if (unit === "auto" || unit === "fit-content") {
182140
- if (textfield)
182141
- textfield.value = unit;
182142
- if (picker)
182143
- picker.style.display = "none";
182144
- if (onChange)
182145
- onChange(unit);
182146
- } else {
182147
- unitVal = unit;
182148
- if (onChange)
182149
- onChange(getValue());
182150
- }
182151
- }}
182152
- >
182153
- ${units.map((u) => u.divider ? html`<sp-menu-divider></sp-menu-divider>` : html`<sp-menu-item value=${u.value}>${u.label}</sp-menu-item>`)}
182154
- </sp-picker>
182155
- `, wrap4);
182156
- return { wrap: wrap4, textfield, picker, getValue };
182157
- }
182158
180682
  function registerStylebookPanelEvents(panel) {
182159
180683
  const { canvas, overlayClk } = panel;
182160
180684
  overlayClk.addEventListener("click", (e) => {
@@ -183310,6 +181834,289 @@ function updateForcedPseudoPreview() {
183310
181834
  // src/browse/browse.js
183311
181835
  init_platform();
183312
181836
  init_store();
181837
+
181838
+ // src/settings/schema-field-ui.js
181839
+ var FIELD_TYPES = ["string", "number", "boolean", "array", "object", "reference"];
181840
+ var FORMAT_OPTIONS = ["", "image", "date", "color"];
181841
+ function detectFieldType(schema4) {
181842
+ if (schema4.$ref)
181843
+ return "reference";
181844
+ return schema4.type || "string";
181845
+ }
181846
+ function detectFieldFormat(schema4) {
181847
+ if (schema4.type === "array" && schema4.items?.format)
181848
+ return schema4.items.format;
181849
+ return schema4.format || "";
181850
+ }
181851
+ function fieldCardTpl(fieldName, fieldSchema, isRequired, handlers, contentTypeNames = []) {
181852
+ const type = detectFieldType(fieldSchema);
181853
+ const format2 = detectFieldFormat(fieldSchema);
181854
+ const isNested = type === "object";
181855
+ const isRef2 = type === "reference";
181856
+ const nestedProps = fieldSchema.properties || {};
181857
+ const nestedRequired = fieldSchema.required || [];
181858
+ const refTarget = fieldSchema.$ref ? fieldSchema.$ref.replace("#/contentTypes/", "") : "";
181859
+ return html`
181860
+ <div class="schema-field-card">
181861
+ <div class="schema-field-row">
181862
+ <sp-textfield
181863
+ size="s"
181864
+ quiet
181865
+ value=${fieldName}
181866
+ class="schema-field-name-input"
181867
+ @change=${(e) => {
181868
+ const newName = e.target.value.trim();
181869
+ if (newName && newName !== fieldName)
181870
+ handlers.onRename(fieldName, newName);
181871
+ else
181872
+ e.target.value = fieldName;
181873
+ }}
181874
+ @keydown=${(e) => {
181875
+ if (e.key === "Enter")
181876
+ e.target.blur();
181877
+ if (e.key === "Escape") {
181878
+ e.target.value = fieldName;
181879
+ e.target.blur();
181880
+ }
181881
+ }}
181882
+ ></sp-textfield>
181883
+ ${typePickerTpl(type, (newType) => handlers.onChangeType(fieldName, newType))}
181884
+ ${type === "string" || type === "array" ? formatPickerTpl(format2, (f) => {
181885
+ if (handlers.onChangeFormat)
181886
+ handlers.onChangeFormat(fieldName, f);
181887
+ }) : nothing}
181888
+ <sp-switch
181889
+ size="s"
181890
+ ?checked=${isRequired}
181891
+ @change=${() => handlers.onToggleRequired(fieldName)}
181892
+ >
181893
+ Req
181894
+ </sp-switch>
181895
+ <sp-action-button
181896
+ size="xs"
181897
+ quiet
181898
+ title="Delete field"
181899
+ @click=${() => handlers.onDelete(fieldName)}
181900
+ >
181901
+ <sp-icon-delete slot="icon"></sp-icon-delete>
181902
+ </sp-action-button>
181903
+ </div>
181904
+ ${isRef2 && contentTypeNames.length ? html`
181905
+ <div class="schema-field-ref-target">
181906
+ <sp-picker
181907
+ size="s"
181908
+ label="Target"
181909
+ value=${refTarget}
181910
+ @change=${(e) => {
181911
+ if (handlers.onChangeRefTarget)
181912
+ handlers.onChangeRefTarget(fieldName, e.target.value);
181913
+ }}
181914
+ >
181915
+ ${contentTypeNames.map((n2) => html`<sp-menu-item value=${n2}>${n2}</sp-menu-item>`)}
181916
+ </sp-picker>
181917
+ </div>
181918
+ ` : nothing}
181919
+ ${isNested ? html`
181920
+ <div class="schema-field-nested">
181921
+ ${Object.entries(nestedProps).map(([name, sub]) => nestedFieldCardTpl(fieldName, name, sub, nestedRequired.includes(name), handlers))}
181922
+ ${nestedAddFieldTpl(fieldName, handlers)}
181923
+ </div>
181924
+ ` : nothing}
181925
+ </div>
181926
+ `;
181927
+ }
181928
+ function nestedFieldCardTpl(parentName, childName, childSchema, isRequired, handlers) {
181929
+ const type = detectFieldType(childSchema);
181930
+ const format2 = detectFieldFormat(childSchema);
181931
+ return html`
181932
+ <div class="schema-field-card schema-field-card--nested">
181933
+ <div class="schema-field-row">
181934
+ <sp-textfield
181935
+ size="s"
181936
+ quiet
181937
+ value=${childName}
181938
+ class="schema-field-name-input"
181939
+ @change=${(e) => {
181940
+ const newName = e.target.value.trim();
181941
+ if (newName && newName !== childName && handlers.onRenameNested) {
181942
+ handlers.onRenameNested(parentName, childName, newName);
181943
+ } else {
181944
+ e.target.value = childName;
181945
+ }
181946
+ }}
181947
+ @keydown=${(e) => {
181948
+ if (e.key === "Enter")
181949
+ e.target.blur();
181950
+ if (e.key === "Escape") {
181951
+ e.target.value = childName;
181952
+ e.target.blur();
181953
+ }
181954
+ }}
181955
+ ></sp-textfield>
181956
+ ${typePickerTpl(type, (newType) => {
181957
+ if (handlers.onChangeNestedType)
181958
+ handlers.onChangeNestedType(parentName, childName, newType);
181959
+ })}
181960
+ ${type === "string" || type === "array" ? formatPickerTpl(format2, (f) => {
181961
+ if (handlers.onChangeNestedFormat)
181962
+ handlers.onChangeNestedFormat(parentName, childName, f);
181963
+ }) : nothing}
181964
+ <sp-switch
181965
+ size="s"
181966
+ ?checked=${isRequired}
181967
+ @change=${() => {
181968
+ if (handlers.onToggleNestedRequired)
181969
+ handlers.onToggleNestedRequired(parentName, childName);
181970
+ }}
181971
+ >
181972
+ Req
181973
+ </sp-switch>
181974
+ <sp-action-button
181975
+ size="xs"
181976
+ quiet
181977
+ title="Delete field"
181978
+ @click=${() => {
181979
+ if (handlers.onDeleteNested)
181980
+ handlers.onDeleteNested(parentName, childName);
181981
+ }}
181982
+ >
181983
+ <sp-icon-delete slot="icon"></sp-icon-delete>
181984
+ </sp-action-button>
181985
+ </div>
181986
+ </div>
181987
+ `;
181988
+ }
181989
+ function nestedAddFieldTpl(parentName, handlers) {
181990
+ return html`
181991
+ <div class="schema-nested-add">
181992
+ <sp-textfield
181993
+ size="s"
181994
+ placeholder="field name"
181995
+ class="schema-nested-add-name"
181996
+ @keydown=${(e) => {
181997
+ if (e.key === "Enter") {
181998
+ const row = e.target.closest(".schema-nested-add");
181999
+ const name = e.target.value.trim();
182000
+ const typePicker = row?.querySelector("sp-picker");
182001
+ const type = typePicker?.value || "string";
182002
+ if (name && handlers.onAddNestedField) {
182003
+ handlers.onAddNestedField(parentName, { name, type, required: false });
182004
+ e.target.value = "";
182005
+ }
182006
+ }
182007
+ }}
182008
+ ></sp-textfield>
182009
+ ${typePickerTpl("string", () => {})}
182010
+ <sp-action-button
182011
+ size="xs"
182012
+ quiet
182013
+ title="Add nested field"
182014
+ @click=${(e) => {
182015
+ const row = e.target.closest(".schema-nested-add");
182016
+ const nameInput = row?.querySelector(".schema-nested-add-name");
182017
+ const typePicker = row?.querySelector("sp-picker");
182018
+ const name = nameInput?.value?.trim();
182019
+ const type = typePicker?.value || "string";
182020
+ if (name && handlers.onAddNestedField) {
182021
+ handlers.onAddNestedField(parentName, { name, type, required: false });
182022
+ nameInput.value = "";
182023
+ }
182024
+ }}
182025
+ >
182026
+ <sp-icon-add slot="icon"></sp-icon-add>
182027
+ </sp-action-button>
182028
+ </div>
182029
+ `;
182030
+ }
182031
+ function typePickerTpl(value2, onChange) {
182032
+ return html`
182033
+ <sp-picker
182034
+ size="s"
182035
+ label="Type"
182036
+ value=${value2}
182037
+ @change=${(e) => onChange(e.target.value)}
182038
+ >
182039
+ ${FIELD_TYPES.map((t) => html`<sp-menu-item value=${t}>${t}</sp-menu-item>`)}
182040
+ </sp-picker>
182041
+ `;
182042
+ }
182043
+ function formatPickerTpl(value2, onChange) {
182044
+ return html`
182045
+ <sp-picker
182046
+ size="s"
182047
+ label="Format"
182048
+ value=${value2}
182049
+ @change=${(e) => onChange(e.target.value)}
182050
+ >
182051
+ ${FORMAT_OPTIONS.map((f) => html`<sp-menu-item value=${f}>${f || "(none)"}</sp-menu-item>`)}
182052
+ </sp-picker>
182053
+ `;
182054
+ }
182055
+ function addFieldFormTpl(state, handlers) {
182056
+ return html`
182057
+ <div class="schema-add-field">
182058
+ <sp-textfield
182059
+ size="s"
182060
+ placeholder="Field name"
182061
+ .value=${state.name}
182062
+ @input=${(e) => handlers.onInput("name", e.target.value)}
182063
+ @keydown=${(e) => {
182064
+ if (e.key === "Enter")
182065
+ handlers.onConfirm();
182066
+ if (e.key === "Escape")
182067
+ handlers.onCancel();
182068
+ }}
182069
+ ></sp-textfield>
182070
+ ${typePickerTpl(state.type, (t) => handlers.onInput("type", t))}
182071
+ ${state.type === "string" || state.type === "array" ? formatPickerTpl(state.format || "", (f) => handlers.onInput("format", f)) : nothing}
182072
+ <sp-switch
182073
+ size="s"
182074
+ ?checked=${state.required}
182075
+ @change=${(e) => handlers.onInput("required", e.target.checked)}
182076
+ >
182077
+ Required
182078
+ </sp-switch>
182079
+ <sp-action-button size="s" @click=${handlers.onConfirm}>Add</sp-action-button>
182080
+ <sp-action-button size="s" quiet @click=${handlers.onCancel}>Cancel</sp-action-button>
182081
+ </div>
182082
+ `;
182083
+ }
182084
+ function schemaForType(type, format2) {
182085
+ switch (type) {
182086
+ case "number":
182087
+ return { type: "number" };
182088
+ case "boolean":
182089
+ return { type: "boolean" };
182090
+ case "array":
182091
+ return format2 ? { type: "array", items: { type: "string", format: format2 } } : { type: "array", items: { type: "string" } };
182092
+ case "object":
182093
+ return { type: "object", properties: {}, required: [] };
182094
+ case "reference":
182095
+ return { $ref: "#/contentTypes/" };
182096
+ default:
182097
+ return format2 ? { type: "string", format: format2 } : { type: "string" };
182098
+ }
182099
+ }
182100
+ function yamlDefault(type, format2) {
182101
+ if (format2 === "date")
182102
+ return new Date().toISOString().split("T")[0];
182103
+ if (format2 === "image")
182104
+ return '""';
182105
+ switch (type) {
182106
+ case "boolean":
182107
+ return "false";
182108
+ case "number":
182109
+ return "0";
182110
+ case "array":
182111
+ return "[]";
182112
+ case "object":
182113
+ return "{}";
182114
+ default:
182115
+ return '""';
182116
+ }
182117
+ }
182118
+
182119
+ // src/browse/browse.js
183313
182120
  var CATEGORIES = [
183314
182121
  { key: "all", label: "All" },
183315
182122
  { key: "pages", label: "Pages", dir: "pages" },
@@ -183850,7 +182657,7 @@ function _flush() {
183850
182657
  const { selection, hover } = tab.session;
183851
182658
  const { stylebookTab } = tab.session.ui;
183852
182659
  const canvasMode = _ctx9.getCanvasMode();
183853
- if (canvasMode !== "design" && canvasMode !== "edit" && canvasMode !== "settings") {
182660
+ if (canvasMode !== "design" && canvasMode !== "edit" && canvasMode !== "stylebook") {
183854
182661
  for (const p of canvasPanels) {
183855
182662
  render2(nothing, p.overlay);
183856
182663
  p.overlayClk.style.pointerEvents = "none";
@@ -183861,7 +182668,7 @@ function _flush() {
183861
182668
  }
183862
182669
  return;
183863
182670
  }
183864
- if (canvasMode === "settings") {
182671
+ if (canvasMode === "stylebook") {
183865
182672
  const enable = stylebookTab === "elements";
183866
182673
  for (const p of canvasPanels) {
183867
182674
  p.overlayClk.style.pointerEvents = enable ? "" : "none";
@@ -184006,7 +182813,7 @@ function renderCanvas() {
184006
182813
  });
184007
182814
  return;
184008
182815
  }
184009
- if (canvasMode === "settings") {
182816
+ if (canvasMode === "stylebook") {
184010
182817
  renderStylebookMode({
184011
182818
  canvasPanelTemplate,
184012
182819
  applyTransform,
@@ -184810,6 +183617,11 @@ async function openFileInTab(path2) {
184810
183617
  });
184811
183618
  projectState.selectedPath = path2;
184812
183619
  trackRecentFile({ path: path2, name: path2.split("/").pop() || path2 });
183620
+ if (path2 === "project.json") {
183621
+ const tab = activeTab.value;
183622
+ if (tab)
183623
+ tab.session.ui.canvasMode = "stylebook";
183624
+ }
184813
183625
  statusMessage(`Opened ${path2.split("/").pop()}`);
184814
183626
  } catch (e) {
184815
183627
  statusMessage(`Error: ${e.message}`);
@@ -210905,6 +209717,113 @@ class JxValueSelector extends LitElement {
210905
209717
  init_lit();
210906
209718
  init_store();
210907
209719
  init_workspace();
209720
+
209721
+ // src/utils/studio-utils.js
209722
+ function camelToKebab2(str) {
209723
+ return str.replace(/[A-Z]/g, (c6) => "-" + c6.toLowerCase());
209724
+ }
209725
+ function camelToLabel(prop) {
209726
+ return prop.replace(/([A-Z])/g, " $1").replace(/^./, (c6) => c6.toUpperCase());
209727
+ }
209728
+ function kebabToLabel(val) {
209729
+ return val.replace(/(^|-)(\w)/g, (_, sep2, c6) => (sep2 ? " " : "") + c6.toUpperCase());
209730
+ }
209731
+ function propLabel(entry, prop) {
209732
+ return entry?.$label || camelToLabel(prop);
209733
+ }
209734
+ function attrLabel(entry, attr) {
209735
+ if (entry?.$label)
209736
+ return entry.$label;
209737
+ if (attr.includes("-"))
209738
+ return attr.replace(/(^|-)(\w)/g, (_, sep2, c6) => (sep2 ? " " : "") + c6.toUpperCase());
209739
+ return camelToLabel(attr);
209740
+ }
209741
+ function abbreviateValue(val) {
209742
+ const map6 = {
209743
+ inline: "inl",
209744
+ "inline-block": "i-blk",
209745
+ "inline-flex": "i-flx",
209746
+ "inline-grid": "i-grd",
209747
+ contents: "cnt",
209748
+ "flow-root": "flow",
209749
+ nowrap: "no-wr",
209750
+ "wrap-reverse": "wr-rev",
209751
+ "flex-start": "start",
209752
+ "flex-end": "end",
209753
+ "space-between": "betw",
209754
+ "space-around": "arnd",
209755
+ "space-evenly": "even",
209756
+ stretch: "str",
209757
+ baseline: "base",
209758
+ normal: "norm",
209759
+ "row-reverse": "row-r",
209760
+ "column-reverse": "col-r",
209761
+ column: "col"
209762
+ };
209763
+ return map6[val] || val;
209764
+ }
209765
+ function inferInputType(entry) {
209766
+ if (entry.$shorthand === true)
209767
+ return "shorthand";
209768
+ if (entry.$input === "button-group")
209769
+ return "button-group";
209770
+ if (entry.$input === "media")
209771
+ return "media";
209772
+ if (entry.format === "color")
209773
+ return "color";
209774
+ if (entry.format === "uri-reference")
209775
+ return "media";
209776
+ if (entry.$units !== undefined)
209777
+ return "number-unit";
209778
+ if (entry.type === "number")
209779
+ return "number";
209780
+ if (Array.isArray(entry.enum))
209781
+ return "select";
209782
+ if (Array.isArray(entry.examples) || Array.isArray(entry.presets))
209783
+ return "combobox";
209784
+ return "text";
209785
+ }
209786
+ function findContentTypeSchema(documentPath, projectConfig) {
209787
+ if (!documentPath || !projectConfig?.contentTypes)
209788
+ return null;
209789
+ for (const [name, def3] of Object.entries(projectConfig.contentTypes)) {
209790
+ if (!def3.source || !def3.schema)
209791
+ continue;
209792
+ const src = def3.source.replace(/^\.\//, "");
209793
+ const dir = src.split("/")[0];
209794
+ const ext = src.includes("*.md") ? ".md" : src.includes("*.json") ? ".json" : src.includes("*.csv") ? ".csv" : null;
209795
+ if (documentPath.startsWith(dir + "/") && (!ext || documentPath.endsWith(ext))) {
209796
+ return { name, schema: def3.schema };
209797
+ }
209798
+ }
209799
+ return null;
209800
+ }
209801
+ function friendlyNameToVar(name, prefix) {
209802
+ const slug = name.trim().toLowerCase().replace(/[^a-z0-9\s-]/g, "").replace(/\s+/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
209803
+ if (!slug)
209804
+ return "";
209805
+ return `${prefix}${slug}`;
209806
+ }
209807
+ function varDisplayName(varName, prefix) {
209808
+ return varName.replace(new RegExp(`^${prefix.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}`), "").replace(/^--/, "").replace(/-/g, " ").replace(/\b\w/g, (c6) => c6.toUpperCase()) || varName;
209809
+ }
209810
+ function parseCemType(typeText) {
209811
+ if (!typeText)
209812
+ return { kind: "text" };
209813
+ const t15 = typeText.trim().replace(/\s*\|\s*undefined\b/g, "").trim();
209814
+ if (t15 === "boolean")
209815
+ return { kind: "boolean" };
209816
+ if (t15 === "number")
209817
+ return { kind: "number" };
209818
+ const enumMatch = t15.match(/^'[^']*'(\s*\|\s*'[^']*')+$/);
209819
+ if (enumMatch) {
209820
+ const options = [...t15.matchAll(/'([^']*)'/g)].map((m3) => m3[1]);
209821
+ return { kind: "combobox", options };
209822
+ }
209823
+ return { kind: "text" };
209824
+ }
209825
+
209826
+ // src/ui/color-selector.js
210908
209827
  function getColorVars() {
210909
209828
  const style2 = getEffectiveStyle(activeTab.value?.doc.document?.style);
210910
209829
  if (!style2)
@@ -211724,6 +210643,1237 @@ function navigateSelection(direction = -1) {
211724
210643
  init_store();
211725
210644
  init_reactivity();
211726
210645
  init_workspace();
210646
+
210647
+ // src/settings/defs-editor.js
210648
+ init_platform();
210649
+ init_store();
210650
+ var selectedDef = null;
210651
+ var showAddField = false;
210652
+ var newFieldState = { name: "", type: "string", format: "", required: false };
210653
+ var showNewDef = false;
210654
+ var newDefName = "";
210655
+ async function saveProjectConfig() {
210656
+ const platform4 = getPlatform();
210657
+ const config = projectState.projectConfig;
210658
+ await platform4.writeFile("project.json", JSON.stringify(config, null, "\t"));
210659
+ }
210660
+ function getSelectedDef() {
210661
+ const config = projectState?.projectConfig;
210662
+ return config?.$defs?.[selectedDef];
210663
+ }
210664
+ function handleNewDef(rerender) {
210665
+ const name = newDefName.trim();
210666
+ if (!name)
210667
+ return;
210668
+ const config = projectState?.projectConfig;
210669
+ if (!config)
210670
+ return;
210671
+ if (!config.$defs)
210672
+ config.$defs = {};
210673
+ if (config.$defs[name])
210674
+ return;
210675
+ config.$defs[name] = {
210676
+ type: "object",
210677
+ properties: {},
210678
+ required: []
210679
+ };
210680
+ selectedDef = name;
210681
+ showNewDef = false;
210682
+ newDefName = "";
210683
+ rerender();
210684
+ saveProjectConfig();
210685
+ }
210686
+ function handleAddField(rerender) {
210687
+ const name = newFieldState.name.trim();
210688
+ if (!name || !selectedDef)
210689
+ return;
210690
+ const def3 = getSelectedDef();
210691
+ if (!def3)
210692
+ return;
210693
+ if (!def3.properties)
210694
+ def3.properties = {};
210695
+ def3.properties[name] = schemaForType(newFieldState.type, newFieldState.format || undefined);
210696
+ if (newFieldState.required) {
210697
+ if (!def3.required)
210698
+ def3.required = [];
210699
+ if (!def3.required.includes(name))
210700
+ def3.required.push(name);
210701
+ }
210702
+ showAddField = false;
210703
+ newFieldState = { name: "", type: "string", format: "", required: false };
210704
+ rerender();
210705
+ saveProjectConfig();
210706
+ }
210707
+ function handleDeleteField(fieldName, rerender) {
210708
+ const def3 = getSelectedDef();
210709
+ if (!def3?.properties)
210710
+ return;
210711
+ delete def3.properties[fieldName];
210712
+ if (def3.required) {
210713
+ def3.required = def3.required.filter((r8) => r8 !== fieldName);
210714
+ }
210715
+ rerender();
210716
+ saveProjectConfig();
210717
+ }
210718
+ function handleToggleRequired(fieldName, rerender) {
210719
+ const def3 = getSelectedDef();
210720
+ if (!def3)
210721
+ return;
210722
+ if (!def3.required)
210723
+ def3.required = [];
210724
+ const idx = def3.required.indexOf(fieldName);
210725
+ if (idx >= 0)
210726
+ def3.required.splice(idx, 1);
210727
+ else
210728
+ def3.required.push(fieldName);
210729
+ rerender();
210730
+ saveProjectConfig();
210731
+ }
210732
+ function handleRenameField(oldName, newName, rerender) {
210733
+ const def3 = getSelectedDef();
210734
+ if (!def3?.properties || !newName || def3.properties[newName])
210735
+ return;
210736
+ const newProps = {};
210737
+ for (const [key, val] of Object.entries(def3.properties)) {
210738
+ newProps[key === oldName ? newName : key] = val;
210739
+ }
210740
+ def3.properties = newProps;
210741
+ if (def3.required) {
210742
+ def3.required = def3.required.map((r8) => r8 === oldName ? newName : r8);
210743
+ }
210744
+ rerender();
210745
+ saveProjectConfig();
210746
+ }
210747
+ function handleChangeType(fieldName, newType, rerender) {
210748
+ const def3 = getSelectedDef();
210749
+ if (!def3?.properties?.[fieldName])
210750
+ return;
210751
+ const oldFormat = newType === "string" || newType === "array" ? detectFieldFormat(def3.properties[fieldName]) : undefined;
210752
+ def3.properties[fieldName] = schemaForType(newType, oldFormat || undefined);
210753
+ rerender();
210754
+ saveProjectConfig();
210755
+ }
210756
+ function handleChangeFormat(fieldName, format2, rerender) {
210757
+ const def3 = getSelectedDef();
210758
+ if (!def3?.properties?.[fieldName])
210759
+ return;
210760
+ const prop = def3.properties[fieldName];
210761
+ const type2 = prop.type || "string";
210762
+ def3.properties[fieldName] = schemaForType(type2, format2 || undefined);
210763
+ rerender();
210764
+ saveProjectConfig();
210765
+ }
210766
+ function handleAddNestedField(parentName, fieldState, rerender) {
210767
+ const def3 = getSelectedDef();
210768
+ const parent = def3?.properties?.[parentName];
210769
+ if (!parent)
210770
+ return;
210771
+ if (!parent.properties)
210772
+ parent.properties = {};
210773
+ parent.properties[fieldState.name] = schemaForType(fieldState.type);
210774
+ if (fieldState.required) {
210775
+ if (!parent.required)
210776
+ parent.required = [];
210777
+ if (!parent.required.includes(fieldState.name))
210778
+ parent.required.push(fieldState.name);
210779
+ }
210780
+ rerender();
210781
+ saveProjectConfig();
210782
+ }
210783
+ function handleDeleteNested(parentName, childName, rerender) {
210784
+ const def3 = getSelectedDef();
210785
+ const parent = def3?.properties?.[parentName];
210786
+ if (!parent?.properties)
210787
+ return;
210788
+ delete parent.properties[childName];
210789
+ if (parent.required) {
210790
+ parent.required = parent.required.filter((r8) => r8 !== childName);
210791
+ }
210792
+ rerender();
210793
+ saveProjectConfig();
210794
+ }
210795
+ function handleToggleNestedRequired(parentName, childName, rerender) {
210796
+ const def3 = getSelectedDef();
210797
+ const parent = def3?.properties?.[parentName];
210798
+ if (!parent)
210799
+ return;
210800
+ if (!parent.required)
210801
+ parent.required = [];
210802
+ const idx = parent.required.indexOf(childName);
210803
+ if (idx >= 0)
210804
+ parent.required.splice(idx, 1);
210805
+ else
210806
+ parent.required.push(childName);
210807
+ rerender();
210808
+ saveProjectConfig();
210809
+ }
210810
+ function handleRenameNested(parentName, oldChild, newChild, rerender) {
210811
+ const def3 = getSelectedDef();
210812
+ const parent = def3?.properties?.[parentName];
210813
+ if (!parent?.properties || !newChild || parent.properties[newChild])
210814
+ return;
210815
+ const newProps = {};
210816
+ for (const [key, val] of Object.entries(parent.properties)) {
210817
+ newProps[key === oldChild ? newChild : key] = val;
210818
+ }
210819
+ parent.properties = newProps;
210820
+ if (parent.required) {
210821
+ parent.required = parent.required.map((r8) => r8 === oldChild ? newChild : r8);
210822
+ }
210823
+ rerender();
210824
+ saveProjectConfig();
210825
+ }
210826
+ function handleChangeNestedType(parentName, childName, newType, rerender) {
210827
+ const def3 = getSelectedDef();
210828
+ const parent = def3?.properties?.[parentName];
210829
+ if (!parent?.properties?.[childName])
210830
+ return;
210831
+ const oldFormat = newType === "string" || newType === "array" ? detectFieldFormat(parent.properties[childName]) : undefined;
210832
+ parent.properties[childName] = schemaForType(newType, oldFormat || undefined);
210833
+ rerender();
210834
+ saveProjectConfig();
210835
+ }
210836
+ function handleChangeNestedFormat(parentName, childName, format2, rerender) {
210837
+ const def3 = getSelectedDef();
210838
+ const parent = def3?.properties?.[parentName];
210839
+ if (!parent?.properties?.[childName])
210840
+ return;
210841
+ const prop = parent.properties[childName];
210842
+ const type2 = prop.type || "string";
210843
+ parent.properties[childName] = schemaForType(type2, format2 || undefined);
210844
+ rerender();
210845
+ saveProjectConfig();
210846
+ }
210847
+ function handleDeleteDef(rerender) {
210848
+ if (!selectedDef)
210849
+ return;
210850
+ const config = projectState?.projectConfig;
210851
+ if (!config?.$defs?.[selectedDef])
210852
+ return;
210853
+ delete config.$defs[selectedDef];
210854
+ selectedDef = null;
210855
+ rerender();
210856
+ saveProjectConfig();
210857
+ }
210858
+ function renderDefsEditor(container) {
210859
+ const rerender = () => renderDefsEditor(container);
210860
+ const config = projectState?.projectConfig;
210861
+ const defs = config?.$defs || {};
210862
+ const defNames = Object.keys(defs);
210863
+ const listTpl = html`
210864
+ <div class="settings-list-panel">
210865
+ ${defNames.map((name) => html`
210866
+ <sp-action-button
210867
+ size="s"
210868
+ ?selected=${selectedDef === name}
210869
+ @click=${() => {
210870
+ selectedDef = name;
210871
+ showAddField = false;
210872
+ rerender();
210873
+ }}
210874
+ >
210875
+ ${name}
210876
+ </sp-action-button>
210877
+ `)}
210878
+ ${showNewDef ? html`
210879
+ <div class="settings-inline-form">
210880
+ <sp-textfield
210881
+ size="s"
210882
+ placeholder="TypeName"
210883
+ .value=${newDefName}
210884
+ @input=${(e20) => {
210885
+ newDefName = e20.target.value;
210886
+ }}
210887
+ @keydown=${(e20) => {
210888
+ if (e20.key === "Enter")
210889
+ handleNewDef(rerender);
210890
+ if (e20.key === "Escape") {
210891
+ showNewDef = false;
210892
+ rerender();
210893
+ }
210894
+ }}
210895
+ ></sp-textfield>
210896
+ <sp-action-button size="s" @click=${() => handleNewDef(rerender)}>
210897
+ Create
210898
+ </sp-action-button>
210899
+ </div>
210900
+ ` : html`
210901
+ <sp-action-button
210902
+ size="s"
210903
+ quiet
210904
+ @click=${() => {
210905
+ showNewDef = true;
210906
+ rerender();
210907
+ }}
210908
+ >
210909
+ <sp-icon-add slot="icon"></sp-icon-add> New Definition
210910
+ </sp-action-button>
210911
+ `}
210912
+ </div>
210913
+ `;
210914
+ let editorTpl;
210915
+ if (!selectedDef || !defs[selectedDef]) {
210916
+ editorTpl = html`<div class="settings-empty-state">Select or create a type definition</div>`;
210917
+ } else {
210918
+ const def3 = defs[selectedDef];
210919
+ const properties = def3.properties || {};
210920
+ const required = def3.required || [];
210921
+ const handlers = {
210922
+ onDelete: (n4) => handleDeleteField(n4, rerender),
210923
+ onToggleRequired: (n4) => handleToggleRequired(n4, rerender),
210924
+ onRename: (oldN, newN) => handleRenameField(oldN, newN, rerender),
210925
+ onChangeType: (n4, t15) => handleChangeType(n4, t15, rerender),
210926
+ onChangeFormat: (n4, f) => handleChangeFormat(n4, f, rerender),
210927
+ onAddNestedField: (p2, s2) => handleAddNestedField(p2, s2, rerender),
210928
+ onDeleteNested: (p2, c6) => handleDeleteNested(p2, c6, rerender),
210929
+ onToggleNestedRequired: (p2, c6) => handleToggleNestedRequired(p2, c6, rerender),
210930
+ onRenameNested: (p2, o19, n4) => handleRenameNested(p2, o19, n4, rerender),
210931
+ onChangeNestedType: (p2, c6, t15) => handleChangeNestedType(p2, c6, t15, rerender),
210932
+ onChangeNestedFormat: (p2, c6, f) => handleChangeNestedFormat(p2, c6, f, rerender)
210933
+ };
210934
+ const fieldCards = Object.entries(properties).map(([name, fieldDef]) => fieldCardTpl(name, fieldDef, required.includes(name), handlers));
210935
+ editorTpl = html`
210936
+ <div class="settings-editor-panel">
210937
+ <div class="settings-editor-header">
210938
+ <h3>${selectedDef}</h3>
210939
+ <sp-action-button
210940
+ size="xs"
210941
+ quiet
210942
+ title="Delete definition"
210943
+ @click=${() => handleDeleteDef(rerender)}
210944
+ >
210945
+ <sp-icon-delete slot="icon"></sp-icon-delete>
210946
+ </sp-action-button>
210947
+ </div>
210948
+ <div class="schema-field-list">${fieldCards}</div>
210949
+ ${showAddField ? addFieldFormTpl(newFieldState, {
210950
+ onInput: (field, value2) => {
210951
+ newFieldState = { ...newFieldState, [field]: value2 };
210952
+ rerender();
210953
+ },
210954
+ onConfirm: () => handleAddField(rerender),
210955
+ onCancel: () => {
210956
+ showAddField = false;
210957
+ newFieldState = { name: "", type: "string", format: "", required: false };
210958
+ rerender();
210959
+ }
210960
+ }) : html`
210961
+ <sp-action-button
210962
+ size="s"
210963
+ quiet
210964
+ @click=${() => {
210965
+ showAddField = true;
210966
+ rerender();
210967
+ }}
210968
+ >
210969
+ <sp-icon-add slot="icon"></sp-icon-add> Add Field
210970
+ </sp-action-button>
210971
+ `}
210972
+ </div>
210973
+ `;
210974
+ }
210975
+ const tpl = html` <div class="settings-two-col">${listTpl} ${editorTpl}</div> `;
210976
+ render2(tpl, container);
210977
+ }
210978
+
210979
+ // src/settings/content-types-editor.js
210980
+ init_platform();
210981
+ init_store();
210982
+ var selectedContentType = null;
210983
+ var showAddField2 = false;
210984
+ var newFieldState2 = { name: "", type: "string", format: "", required: false };
210985
+ var showNewContentType = false;
210986
+ var newContentTypeName = "";
210987
+ async function saveProjectConfig2() {
210988
+ const platform4 = getPlatform();
210989
+ const config = projectState.projectConfig;
210990
+ await platform4.writeFile("project.json", JSON.stringify(config, null, "\t"));
210991
+ }
210992
+ function getSelectedSchema() {
210993
+ const config = projectState?.projectConfig;
210994
+ return config?.contentTypes?.[selectedContentType]?.schema;
210995
+ }
210996
+ function handleNewContentType(rerender) {
210997
+ const slug = newContentTypeName.toLowerCase().replace(/\s+/g, "-").replace(/[^a-z0-9-]/g, "");
210998
+ if (!slug)
210999
+ return;
211000
+ const config = projectState?.projectConfig;
211001
+ if (!config)
211002
+ return;
211003
+ if (!config.contentTypes)
211004
+ config.contentTypes = {};
211005
+ if (config.contentTypes[slug])
211006
+ return;
211007
+ config.contentTypes[slug] = {
211008
+ source: `./content/${slug}/**/*.md`,
211009
+ schema: { type: "object", properties: {}, required: [] }
211010
+ };
211011
+ selectedContentType = slug;
211012
+ showNewContentType = false;
211013
+ newContentTypeName = "";
211014
+ rerender();
211015
+ saveProjectConfig2().then(async () => {
211016
+ const platform4 = getPlatform();
211017
+ await platform4.writeFile(`content/${slug}/.gitkeep`, "");
211018
+ });
211019
+ }
211020
+ function handleAddField2(rerender) {
211021
+ const name = newFieldState2.name.trim();
211022
+ if (!name || !selectedContentType)
211023
+ return;
211024
+ const schema4 = getSelectedSchema();
211025
+ if (!schema4)
211026
+ return;
211027
+ if (!schema4.properties)
211028
+ schema4.properties = {};
211029
+ schema4.properties[name] = schemaForType(newFieldState2.type, newFieldState2.format || undefined);
211030
+ if (newFieldState2.required) {
211031
+ if (!schema4.required)
211032
+ schema4.required = [];
211033
+ if (!schema4.required.includes(name))
211034
+ schema4.required.push(name);
211035
+ }
211036
+ showAddField2 = false;
211037
+ newFieldState2 = { name: "", type: "string", format: "", required: false };
211038
+ rerender();
211039
+ saveProjectConfig2();
211040
+ }
211041
+ function handleDeleteField2(fieldName, rerender) {
211042
+ const schema4 = getSelectedSchema();
211043
+ if (!schema4?.properties)
211044
+ return;
211045
+ delete schema4.properties[fieldName];
211046
+ if (schema4.required) {
211047
+ schema4.required = schema4.required.filter((r8) => r8 !== fieldName);
211048
+ }
211049
+ rerender();
211050
+ saveProjectConfig2();
211051
+ }
211052
+ function handleToggleRequired2(fieldName, rerender) {
211053
+ const schema4 = getSelectedSchema();
211054
+ if (!schema4)
211055
+ return;
211056
+ if (!schema4.required)
211057
+ schema4.required = [];
211058
+ const idx = schema4.required.indexOf(fieldName);
211059
+ if (idx >= 0)
211060
+ schema4.required.splice(idx, 1);
211061
+ else
211062
+ schema4.required.push(fieldName);
211063
+ rerender();
211064
+ saveProjectConfig2();
211065
+ }
211066
+ function handleRenameField2(oldName, newName, rerender) {
211067
+ const schema4 = getSelectedSchema();
211068
+ if (!schema4?.properties || !newName || schema4.properties[newName])
211069
+ return;
211070
+ const newProps = {};
211071
+ for (const [key, val] of Object.entries(schema4.properties)) {
211072
+ newProps[key === oldName ? newName : key] = val;
211073
+ }
211074
+ schema4.properties = newProps;
211075
+ if (schema4.required) {
211076
+ schema4.required = schema4.required.map((r8) => r8 === oldName ? newName : r8);
211077
+ }
211078
+ rerender();
211079
+ saveProjectConfig2();
211080
+ }
211081
+ function handleChangeType2(fieldName, newType, rerender) {
211082
+ const schema4 = getSelectedSchema();
211083
+ if (!schema4?.properties?.[fieldName])
211084
+ return;
211085
+ const oldFormat = newType === "string" || newType === "array" ? detectFieldFormat(schema4.properties[fieldName]) : undefined;
211086
+ schema4.properties[fieldName] = schemaForType(newType, oldFormat || undefined);
211087
+ rerender();
211088
+ saveProjectConfig2();
211089
+ }
211090
+ function handleChangeFormat2(fieldName, format2, rerender) {
211091
+ const schema4 = getSelectedSchema();
211092
+ if (!schema4?.properties?.[fieldName])
211093
+ return;
211094
+ const prop = schema4.properties[fieldName];
211095
+ const type2 = prop.type || "string";
211096
+ schema4.properties[fieldName] = schemaForType(type2, format2 || undefined);
211097
+ rerender();
211098
+ saveProjectConfig2();
211099
+ }
211100
+ function handleChangeRefTarget(fieldName, target, rerender) {
211101
+ const schema4 = getSelectedSchema();
211102
+ if (!schema4?.properties)
211103
+ return;
211104
+ schema4.properties[fieldName] = { $ref: `#/contentTypes/${target}` };
211105
+ rerender();
211106
+ saveProjectConfig2();
211107
+ }
211108
+ function handleAddNestedField2(parentName, fieldState, rerender) {
211109
+ const schema4 = getSelectedSchema();
211110
+ const parent = schema4?.properties?.[parentName];
211111
+ if (!parent)
211112
+ return;
211113
+ if (!parent.properties)
211114
+ parent.properties = {};
211115
+ parent.properties[fieldState.name] = schemaForType(fieldState.type);
211116
+ if (fieldState.required) {
211117
+ if (!parent.required)
211118
+ parent.required = [];
211119
+ if (!parent.required.includes(fieldState.name))
211120
+ parent.required.push(fieldState.name);
211121
+ }
211122
+ rerender();
211123
+ saveProjectConfig2();
211124
+ }
211125
+ function handleDeleteNested2(parentName, childName, rerender) {
211126
+ const schema4 = getSelectedSchema();
211127
+ const parent = schema4?.properties?.[parentName];
211128
+ if (!parent?.properties)
211129
+ return;
211130
+ delete parent.properties[childName];
211131
+ if (parent.required) {
211132
+ parent.required = parent.required.filter((r8) => r8 !== childName);
211133
+ }
211134
+ rerender();
211135
+ saveProjectConfig2();
211136
+ }
211137
+ function handleToggleNestedRequired2(parentName, childName, rerender) {
211138
+ const schema4 = getSelectedSchema();
211139
+ const parent = schema4?.properties?.[parentName];
211140
+ if (!parent)
211141
+ return;
211142
+ if (!parent.required)
211143
+ parent.required = [];
211144
+ const idx = parent.required.indexOf(childName);
211145
+ if (idx >= 0)
211146
+ parent.required.splice(idx, 1);
211147
+ else
211148
+ parent.required.push(childName);
211149
+ rerender();
211150
+ saveProjectConfig2();
211151
+ }
211152
+ function handleRenameNested2(parentName, oldChild, newChild, rerender) {
211153
+ const schema4 = getSelectedSchema();
211154
+ const parent = schema4?.properties?.[parentName];
211155
+ if (!parent?.properties || !newChild || parent.properties[newChild])
211156
+ return;
211157
+ const newProps = {};
211158
+ for (const [key, val] of Object.entries(parent.properties)) {
211159
+ newProps[key === oldChild ? newChild : key] = val;
211160
+ }
211161
+ parent.properties = newProps;
211162
+ if (parent.required) {
211163
+ parent.required = parent.required.map((r8) => r8 === oldChild ? newChild : r8);
211164
+ }
211165
+ rerender();
211166
+ saveProjectConfig2();
211167
+ }
211168
+ function handleChangeNestedType2(parentName, childName, newType, rerender) {
211169
+ const schema4 = getSelectedSchema();
211170
+ const parent = schema4?.properties?.[parentName];
211171
+ if (!parent?.properties?.[childName])
211172
+ return;
211173
+ const oldFormat = newType === "string" || newType === "array" ? detectFieldFormat(parent.properties[childName]) : undefined;
211174
+ parent.properties[childName] = schemaForType(newType, oldFormat || undefined);
211175
+ rerender();
211176
+ saveProjectConfig2();
211177
+ }
211178
+ function handleChangeNestedFormat2(parentName, childName, format2, rerender) {
211179
+ const schema4 = getSelectedSchema();
211180
+ const parent = schema4?.properties?.[parentName];
211181
+ if (!parent?.properties?.[childName])
211182
+ return;
211183
+ const prop = parent.properties[childName];
211184
+ const type2 = prop.type || "string";
211185
+ parent.properties[childName] = schemaForType(type2, format2 || undefined);
211186
+ rerender();
211187
+ saveProjectConfig2();
211188
+ }
211189
+ function handleDeleteContentType(rerender) {
211190
+ if (!selectedContentType)
211191
+ return;
211192
+ const config = projectState?.projectConfig;
211193
+ if (!config?.contentTypes?.[selectedContentType])
211194
+ return;
211195
+ delete config.contentTypes[selectedContentType];
211196
+ selectedContentType = null;
211197
+ rerender();
211198
+ saveProjectConfig2();
211199
+ }
211200
+ function renderContentTypesEditor(container) {
211201
+ const rerender = () => renderContentTypesEditor(container);
211202
+ const config = projectState?.projectConfig;
211203
+ const contentTypes = config?.contentTypes || {};
211204
+ const contentTypeNames = Object.keys(contentTypes);
211205
+ const listTpl = html`
211206
+ <div class="settings-list-panel">
211207
+ ${contentTypeNames.map((name) => html`
211208
+ <sp-action-button
211209
+ size="s"
211210
+ ?selected=${selectedContentType === name}
211211
+ @click=${() => {
211212
+ selectedContentType = name;
211213
+ showAddField2 = false;
211214
+ rerender();
211215
+ }}
211216
+ >
211217
+ ${name}
211218
+ </sp-action-button>
211219
+ `)}
211220
+ ${showNewContentType ? html`
211221
+ <div class="settings-inline-form">
211222
+ <sp-textfield
211223
+ size="s"
211224
+ placeholder="content-type-name"
211225
+ .value=${newContentTypeName}
211226
+ @input=${(e20) => {
211227
+ newContentTypeName = e20.target.value;
211228
+ }}
211229
+ @keydown=${(e20) => {
211230
+ if (e20.key === "Enter")
211231
+ handleNewContentType(rerender);
211232
+ if (e20.key === "Escape") {
211233
+ showNewContentType = false;
211234
+ rerender();
211235
+ }
211236
+ }}
211237
+ ></sp-textfield>
211238
+ <sp-action-button size="s" @click=${() => handleNewContentType(rerender)}>
211239
+ Create
211240
+ </sp-action-button>
211241
+ </div>
211242
+ ` : html`
211243
+ <sp-action-button
211244
+ size="s"
211245
+ quiet
211246
+ @click=${() => {
211247
+ showNewContentType = true;
211248
+ rerender();
211249
+ }}
211250
+ >
211251
+ <sp-icon-add slot="icon"></sp-icon-add> New Content Type
211252
+ </sp-action-button>
211253
+ `}
211254
+ </div>
211255
+ `;
211256
+ let editorTpl;
211257
+ if (!selectedContentType || !contentTypes[selectedContentType]) {
211258
+ editorTpl = html`<div class="settings-empty-state">Select or create a content type</div>`;
211259
+ } else {
211260
+ const col = contentTypes[selectedContentType];
211261
+ const schema4 = col.schema || {};
211262
+ const properties = schema4.properties || {};
211263
+ const required = schema4.required || [];
211264
+ const handlers = {
211265
+ onDelete: (n4) => handleDeleteField2(n4, rerender),
211266
+ onToggleRequired: (n4) => handleToggleRequired2(n4, rerender),
211267
+ onRename: (oldN, newN) => handleRenameField2(oldN, newN, rerender),
211268
+ onChangeType: (n4, t15) => handleChangeType2(n4, t15, rerender),
211269
+ onChangeFormat: (n4, f) => handleChangeFormat2(n4, f, rerender),
211270
+ onChangeRefTarget: (n4, target) => handleChangeRefTarget(n4, target, rerender),
211271
+ onAddNestedField: (p2, s2) => handleAddNestedField2(p2, s2, rerender),
211272
+ onDeleteNested: (p2, c6) => handleDeleteNested2(p2, c6, rerender),
211273
+ onToggleNestedRequired: (p2, c6) => handleToggleNestedRequired2(p2, c6, rerender),
211274
+ onRenameNested: (p2, o19, n4) => handleRenameNested2(p2, o19, n4, rerender),
211275
+ onChangeNestedType: (p2, c6, t15) => handleChangeNestedType2(p2, c6, t15, rerender),
211276
+ onChangeNestedFormat: (p2, c6, f) => handleChangeNestedFormat2(p2, c6, f, rerender)
211277
+ };
211278
+ const fieldCards = Object.entries(properties).map(([name, def3]) => fieldCardTpl(name, def3, required.includes(name), handlers, contentTypeNames));
211279
+ editorTpl = html`
211280
+ <div class="settings-editor-panel">
211281
+ <div class="settings-editor-header">
211282
+ <h3>${selectedContentType}</h3>
211283
+ <sp-field-label size="s">Source: ${col.source || "—"}</sp-field-label>
211284
+ <sp-action-button
211285
+ size="xs"
211286
+ quiet
211287
+ title="Delete content type"
211288
+ @click=${() => handleDeleteContentType(rerender)}
211289
+ >
211290
+ <sp-icon-delete slot="icon"></sp-icon-delete>
211291
+ </sp-action-button>
211292
+ </div>
211293
+ <div class="schema-field-list">${fieldCards}</div>
211294
+ ${showAddField2 ? addFieldFormTpl(newFieldState2, {
211295
+ onInput: (field, value2) => {
211296
+ newFieldState2 = { ...newFieldState2, [field]: value2 };
211297
+ rerender();
211298
+ },
211299
+ onConfirm: () => handleAddField2(rerender),
211300
+ onCancel: () => {
211301
+ showAddField2 = false;
211302
+ newFieldState2 = { name: "", type: "string", format: "", required: false };
211303
+ rerender();
211304
+ }
211305
+ }) : html`
211306
+ <sp-action-button
211307
+ size="s"
211308
+ quiet
211309
+ @click=${() => {
211310
+ showAddField2 = true;
211311
+ rerender();
211312
+ }}
211313
+ >
211314
+ <sp-icon-add slot="icon"></sp-icon-add> Add Field
211315
+ </sp-action-button>
211316
+ `}
211317
+ </div>
211318
+ `;
211319
+ }
211320
+ const tpl = html` <div class="settings-two-col">${listTpl} ${editorTpl}</div> `;
211321
+ render2(tpl, container);
211322
+ }
211323
+
211324
+ // src/settings/css-vars-editor.js
211325
+ init_store();
211326
+ function renderCssVarsEditor(container) {
211327
+ const config = projectState.projectConfig || {};
211328
+ const rootStyle = config.style || {};
211329
+ const media = getEffectiveMedia(config.$media);
211330
+ const groups = { color: [], font: [], size: [], other: [] };
211331
+ for (const [k, v] of Object.entries(rootStyle)) {
211332
+ if (!k.startsWith("--"))
211333
+ continue;
211334
+ if (typeof v !== "string" && typeof v !== "number")
211335
+ continue;
211336
+ if (k.startsWith("--color"))
211337
+ groups.color.push([k, v]);
211338
+ else if (k.startsWith("--font"))
211339
+ groups.font.push([k, v]);
211340
+ else if (k.startsWith("--size") || k.startsWith("--spacing") || k.startsWith("--radius"))
211341
+ groups.size.push([k, v]);
211342
+ else
211343
+ groups.other.push([k, v]);
211344
+ }
211345
+ const mediaNames = media ? Object.keys(media).filter((m3) => m3 !== "--") : [];
211346
+ const save = () => {
211347
+ updateSiteConfig({ style: { ...rootStyle } });
211348
+ };
211349
+ const updateVar = (name, val) => {
211350
+ rootStyle[name] = val;
211351
+ save();
211352
+ };
211353
+ const deleteVar = (name) => {
211354
+ delete rootStyle[name];
211355
+ save();
211356
+ renderCssVarsEditor(container);
211357
+ };
211358
+ const addVar = (prefix, friendlyName, val) => {
211359
+ const varName = friendlyNameToVar(friendlyName, prefix);
211360
+ if (!varName || !val)
211361
+ return;
211362
+ rootStyle[varName] = val;
211363
+ save();
211364
+ renderCssVarsEditor(container);
211365
+ };
211366
+ const tpl = html`
211367
+ <div class="settings-section">
211368
+ <h3 class="settings-section-title">CSS Variables</h3>
211369
+
211370
+ ${renderColorSection(groups.color, updateVar, deleteVar, addVar)}
211371
+ ${renderFontSection(groups.font, updateVar, deleteVar, addVar)}
211372
+ ${renderSizeSection(groups.size, updateVar, deleteVar, addVar, rootStyle, mediaNames)}
211373
+ ${groups.other.length > 0 ? renderOtherSection(groups.other, updateVar, deleteVar, addVar, rootStyle, mediaNames) : nothing}
211374
+ </div>
211375
+ `;
211376
+ render2(tpl, container);
211377
+ }
211378
+ function renderColorSection(vars, updateVar, deleteVar, addVar) {
211379
+ return html`
211380
+ <div class="css-vars-group">
211381
+ <h4 class="css-vars-group-title">Colors</h4>
211382
+ ${vars.map(([name, val]) => html`
211383
+ <div class="css-var-row">
211384
+ <div class="css-var-swatch" style="background:${val}">
211385
+ <input
211386
+ type="color"
211387
+ .value=${val && val.startsWith("#") ? val : "#007acc"}
211388
+ @input=${(e20) => updateVar(name, e20.target.value)}
211389
+ />
211390
+ </div>
211391
+ <span class="css-var-name">${varDisplayName(name, "--color-")}</span>
211392
+ <sp-textfield
211393
+ size="s"
211394
+ .value=${String(val)}
211395
+ @change=${(e20) => updateVar(name, e20.target.value)}
211396
+ style="flex:1;max-width:160px"
211397
+ ></sp-textfield>
211398
+ <sp-action-button quiet size="s" @click=${() => deleteVar(name)}>
211399
+ <sp-icon-delete slot="icon"></sp-icon-delete>
211400
+ </sp-action-button>
211401
+ </div>
211402
+ `)}
211403
+ ${renderAddRow("--color-", "Primary Blue", "#007acc", addVar)}
211404
+ </div>
211405
+ `;
211406
+ }
211407
+ function renderFontSection(vars, updateVar, deleteVar, addVar) {
211408
+ return html`
211409
+ <div class="css-vars-group">
211410
+ <h4 class="css-vars-group-title">Fonts</h4>
211411
+ ${vars.map(([name, val]) => html`
211412
+ <div class="css-var-row">
211413
+ <span class="css-var-name">${varDisplayName(name, "--font-")}</span>
211414
+ <sp-textfield
211415
+ size="s"
211416
+ .value=${String(val)}
211417
+ @change=${(e20) => updateVar(name, e20.target.value)}
211418
+ style="flex:1"
211419
+ ></sp-textfield>
211420
+ <sp-action-button quiet size="s" @click=${() => deleteVar(name)}>
211421
+ <sp-icon-delete slot="icon"></sp-icon-delete>
211422
+ </sp-action-button>
211423
+ </div>
211424
+ <div class="css-var-font-preview" style="font-family:${val}">
211425
+ The quick brown fox jumps over the lazy dog
211426
+ </div>
211427
+ `)}
211428
+ ${renderAddRow("--font-", "Body Serif", "'Georgia', serif", addVar)}
211429
+ </div>
211430
+ `;
211431
+ }
211432
+ function renderSizeSection(vars, updateVar, deleteVar, addVar, rootStyle, mediaNames) {
211433
+ return html`
211434
+ <div class="css-vars-group">
211435
+ <h4 class="css-vars-group-title">Sizes &amp; Spacing</h4>
211436
+ ${vars.map(([name, val]) => html`
211437
+ <div class="css-var-row">
211438
+ <span class="css-var-name"
211439
+ >${varDisplayName(name, "--size-") || varDisplayName(name, "--spacing-") || varDisplayName(name, "--radius-") || name}</span
211440
+ >
211441
+ <sp-textfield
211442
+ size="s"
211443
+ .value=${String(val)}
211444
+ @change=${(e20) => updateVar(name, e20.target.value)}
211445
+ style="max-width:120px"
211446
+ ></sp-textfield>
211447
+ <sp-action-button quiet size="s" @click=${() => deleteVar(name)}>
211448
+ <sp-icon-delete slot="icon"></sp-icon-delete>
211449
+ </sp-action-button>
211450
+ </div>
211451
+ ${mediaNames.length > 0 ? renderMediaOverrides(name, rootStyle, mediaNames) : nothing}
211452
+ `)}
211453
+ ${renderAddRow("--size-", "Spacing Large", "32px", addVar)}
211454
+ </div>
211455
+ `;
211456
+ }
211457
+ function renderOtherSection(vars, updateVar, deleteVar, addVar, rootStyle, mediaNames) {
211458
+ return html`
211459
+ <div class="css-vars-group">
211460
+ <h4 class="css-vars-group-title">Other</h4>
211461
+ ${vars.map(([name, val]) => html`
211462
+ <div class="css-var-row">
211463
+ <span class="css-var-name">${name}</span>
211464
+ <sp-textfield
211465
+ size="s"
211466
+ .value=${String(val)}
211467
+ @change=${(e20) => updateVar(name, e20.target.value)}
211468
+ style="flex:1"
211469
+ ></sp-textfield>
211470
+ <sp-action-button quiet size="s" @click=${() => deleteVar(name)}>
211471
+ <sp-icon-delete slot="icon"></sp-icon-delete>
211472
+ </sp-action-button>
211473
+ </div>
211474
+ ${mediaNames.length > 0 ? renderMediaOverrides(name, rootStyle, mediaNames) : nothing}
211475
+ `)}
211476
+ ${renderAddRow("--", "Custom Var", "value", addVar)}
211477
+ </div>
211478
+ `;
211479
+ }
211480
+ function renderMediaOverrides(varName, rootStyle, mediaNames) {
211481
+ const overrides = [];
211482
+ for (const mediaName of mediaNames) {
211483
+ const mediaKey = `@${mediaName}`;
211484
+ const mediaBlock = rootStyle[mediaKey];
211485
+ if (mediaBlock && typeof mediaBlock === "object" && mediaBlock[varName]) {
211486
+ overrides.push({ mediaName, value: mediaBlock[varName] });
211487
+ }
211488
+ }
211489
+ if (overrides.length === 0)
211490
+ return nothing;
211491
+ return html`
211492
+ <div class="css-var-media-overrides">
211493
+ ${overrides.map((o19) => html`
211494
+ <div class="css-var-media-row">
211495
+ <span class="css-var-media-label">@${o19.mediaName}</span>
211496
+ <sp-textfield
211497
+ size="s"
211498
+ .value=${String(o19.value)}
211499
+ @change=${(e20) => {
211500
+ if (!rootStyle[`@${o19.mediaName}`])
211501
+ rootStyle[`@${o19.mediaName}`] = {};
211502
+ rootStyle[`@${o19.mediaName}`][varName] = e20.target.value;
211503
+ updateSiteConfig({ style: { ...rootStyle } });
211504
+ }}
211505
+ style="max-width:120px"
211506
+ ></sp-textfield>
211507
+ </div>
211508
+ `)}
211509
+ </div>
211510
+ `;
211511
+ }
211512
+ function renderAddRow(prefix, placeholder, valuePlaceholder, addVar) {
211513
+ let nameEl = null;
211514
+ let valEl = null;
211515
+ return html`
211516
+ <div class="css-var-add-row">
211517
+ <sp-textfield
211518
+ size="s"
211519
+ placeholder=${placeholder}
211520
+ ${ref2((el) => {
211521
+ if (el)
211522
+ nameEl = el;
211523
+ })}
211524
+ ></sp-textfield>
211525
+ <sp-textfield
211526
+ size="s"
211527
+ placeholder=${valuePlaceholder}
211528
+ ${ref2((el) => {
211529
+ if (el)
211530
+ valEl = el;
211531
+ })}
211532
+ ></sp-textfield>
211533
+ <sp-action-button
211534
+ size="s"
211535
+ @click=${() => {
211536
+ if (nameEl && valEl) {
211537
+ addVar(prefix, nameEl.value, valEl.value);
211538
+ }
211539
+ }}
211540
+ >Add</sp-action-button
211541
+ >
211542
+ </div>
211543
+ `;
211544
+ }
211545
+
211546
+ // src/settings/head-editor.js
211547
+ init_store();
211548
+ function renderHeadEditor(container) {
211549
+ const config = projectState.projectConfig || {};
211550
+ const headEntries = config.$head || [];
211551
+ const save = () => {
211552
+ updateSiteConfig({ $head: headEntries });
211553
+ };
211554
+ const addEntry = (tag7) => {
211555
+ const entry = { tag: tag7 };
211556
+ if (tag7 === "link") {
211557
+ entry.rel = "stylesheet";
211558
+ entry.href = "";
211559
+ } else if (tag7 === "meta") {
211560
+ entry.name = "";
211561
+ entry.content = "";
211562
+ } else if (tag7 === "script") {
211563
+ entry.src = "";
211564
+ entry.body = "";
211565
+ } else if (tag7 === "style") {
211566
+ entry.body = "";
211567
+ }
211568
+ headEntries.push(entry);
211569
+ save();
211570
+ renderHeadEditor(container);
211571
+ };
211572
+ const removeEntry = (idx) => {
211573
+ headEntries.splice(idx, 1);
211574
+ save();
211575
+ renderHeadEditor(container);
211576
+ };
211577
+ const updateEntry = (idx, key, val) => {
211578
+ headEntries[idx][key] = val;
211579
+ save();
211580
+ };
211581
+ const tpl = html`
211582
+ <div class="settings-section">
211583
+ <h3 class="settings-section-title">Head</h3>
211584
+ <p class="settings-field-desc">
211585
+ Manage global &lt;head&gt; tags — stylesheets, meta tags, scripts, and inline styles.
211586
+ </p>
211587
+
211588
+ <div class="head-entries">
211589
+ ${headEntries.map((entry, idx) => html`
211590
+ <div class="head-entry">
211591
+ <div class="head-entry-header">
211592
+ <span class="head-entry-tag">&lt;${entry.tag}&gt;</span>
211593
+ <sp-action-button quiet size="s" @click=${() => removeEntry(idx)}>
211594
+ <sp-icon-delete slot="icon"></sp-icon-delete>
211595
+ </sp-action-button>
211596
+ </div>
211597
+ <div class="head-entry-fields">${renderEntryFields(entry, idx, updateEntry)}</div>
211598
+ </div>
211599
+ `)}
211600
+ </div>
211601
+
211602
+ <div class="head-add-actions" style="margin-top:12px;display:flex;gap:8px">
211603
+ <sp-action-button size="s" @click=${() => addEntry("link")}> + Link </sp-action-button>
211604
+ <sp-action-button size="s" @click=${() => addEntry("meta")}> + Meta </sp-action-button>
211605
+ <sp-action-button size="s" @click=${() => addEntry("script")}> + Script </sp-action-button>
211606
+ <sp-action-button size="s" @click=${() => addEntry("style")}> + Style </sp-action-button>
211607
+ </div>
211608
+ </div>
211609
+ `;
211610
+ render2(tpl, container);
211611
+ }
211612
+ function renderEntryFields(entry, idx, updateEntry) {
211613
+ let debounce;
211614
+ const onFieldChange = (key) => (e20) => {
211615
+ clearTimeout(debounce);
211616
+ debounce = setTimeout(() => {
211617
+ updateEntry(idx, key, e20.target.value);
211618
+ }, 300);
211619
+ };
211620
+ switch (entry.tag) {
211621
+ case "link":
211622
+ return html`
211623
+ <div class="settings-field-row">
211624
+ <sp-textfield
211625
+ size="s"
211626
+ label="rel"
211627
+ .value=${entry.rel || ""}
211628
+ @change=${onFieldChange("rel")}
211629
+ ></sp-textfield>
211630
+ <sp-textfield
211631
+ size="s"
211632
+ label="href"
211633
+ .value=${entry.href || ""}
211634
+ @change=${onFieldChange("href")}
211635
+ style="flex:1"
211636
+ ></sp-textfield>
211637
+ </div>
211638
+ `;
211639
+ case "meta":
211640
+ return html`
211641
+ <div class="settings-field-row">
211642
+ <sp-textfield
211643
+ size="s"
211644
+ label="name"
211645
+ .value=${entry.name || ""}
211646
+ @change=${onFieldChange("name")}
211647
+ ></sp-textfield>
211648
+ <sp-textfield
211649
+ size="s"
211650
+ label="content"
211651
+ .value=${entry.content || ""}
211652
+ @change=${onFieldChange("content")}
211653
+ style="flex:1"
211654
+ ></sp-textfield>
211655
+ </div>
211656
+ `;
211657
+ case "script":
211658
+ return html`
211659
+ <div class="settings-field-row">
211660
+ <sp-textfield
211661
+ size="s"
211662
+ label="src"
211663
+ .value=${entry.src || ""}
211664
+ @change=${onFieldChange("src")}
211665
+ placeholder="URL or leave empty for inline"
211666
+ style="flex:1"
211667
+ ></sp-textfield>
211668
+ </div>
211669
+ ${!entry.src ? html`
211670
+ <div class="head-entry-body">
211671
+ <label class="settings-field-label">Script body</label>
211672
+ <textarea
211673
+ class="head-code-editor"
211674
+ .value=${entry.body || ""}
211675
+ @input=${onFieldChange("body")}
211676
+ rows="6"
211677
+ spellcheck="false"
211678
+ ></textarea>
211679
+ </div>
211680
+ ` : nothing}
211681
+ `;
211682
+ case "style":
211683
+ return html`
211684
+ <div class="head-entry-body">
211685
+ <label class="settings-field-label">Style body</label>
211686
+ <textarea
211687
+ class="head-code-editor"
211688
+ .value=${entry.body || ""}
211689
+ @input=${onFieldChange("body")}
211690
+ rows="8"
211691
+ spellcheck="false"
211692
+ ></textarea>
211693
+ </div>
211694
+ `;
211695
+ default:
211696
+ return nothing;
211697
+ }
211698
+ }
211699
+
211700
+ // src/settings/general-settings.js
211701
+ init_store();
211702
+ init_platform();
211703
+ function renderGeneralSettings(container) {
211704
+ const config = projectState.projectConfig || {};
211705
+ const onFaviconUpload = async () => {
211706
+ const input = document.createElement("input");
211707
+ input.type = "file";
211708
+ input.accept = "image/*,.ico,.svg";
211709
+ input.onchange = async () => {
211710
+ const file = input.files?.[0];
211711
+ if (!file)
211712
+ return;
211713
+ const platform4 = getPlatform();
211714
+ await platform4.uploadFile("public/favicon.ico", file);
211715
+ await updateSiteConfig({ favicon: "/favicon.ico" });
211716
+ renderGeneralSettings(container);
211717
+ };
211718
+ input.click();
211719
+ };
211720
+ const onAdapterChange = (e20) => {
211721
+ updateSiteConfig({ adapter: e20.target.value });
211722
+ };
211723
+ const onEditGlobalStyles = () => {
211724
+ closeSettingsModal();
211725
+ openFileInTab("project.json");
211726
+ };
211727
+ const currentFavicon = config.favicon;
211728
+ const tpl = html`
211729
+ <div class="settings-section">
211730
+ <h3 class="settings-section-title">General</h3>
211731
+
211732
+ <div class="settings-field">
211733
+ <label class="settings-field-label">Favicon</label>
211734
+ <p class="settings-field-desc">Upload an image to use as the site favicon.</p>
211735
+ <div style="display:flex;align-items:center;gap:12px">
211736
+ ${currentFavicon ? html`<img
211737
+ src=${currentFavicon}
211738
+ alt="Current favicon"
211739
+ style="width:32px;height:32px;object-fit:contain;border:1px solid var(--border);border-radius:4px;padding:2px"
211740
+ />` : html`<div
211741
+ style="width:32px;height:32px;border:1px dashed var(--border);border-radius:4px;display:flex;align-items:center;justify-content:center;color:var(--fg-dim);font-size:11px"
211742
+ >
211743
+
211744
+ </div>`}
211745
+ <sp-action-button size="s" @click=${onFaviconUpload}> Upload Favicon </sp-action-button>
211746
+ ${currentFavicon ? html`<span style="font-size:11px;color:var(--fg-dim)">${currentFavicon}</span>` : nothing}
211747
+ </div>
211748
+ </div>
211749
+
211750
+ <div class="settings-field">
211751
+ <label class="settings-field-label">Platform Adapter</label>
211752
+ <p class="settings-field-desc">Build adapter for deployment target.</p>
211753
+ <sp-picker
211754
+ size="s"
211755
+ label="Platform Adapter"
211756
+ .value=${config.adapter || "static"}
211757
+ @change=${onAdapterChange}
211758
+ >
211759
+ <sp-menu-item value="static">Static</sp-menu-item>
211760
+ <sp-menu-item value="bun">Bun</sp-menu-item>
211761
+ <sp-menu-item value="node">Node</sp-menu-item>
211762
+ <sp-menu-item value="cloudflare-workers">Cloudflare Workers</sp-menu-item>
211763
+ <sp-menu-item value="cloudflare-pages">Cloudflare Pages</sp-menu-item>
211764
+ </sp-picker>
211765
+ </div>
211766
+
211767
+ <div class="settings-field">
211768
+ <label class="settings-field-label">Global Styles</label>
211769
+ <p class="settings-field-desc">Edit default element styles that apply across all pages.</p>
211770
+ <sp-action-button size="s" @click=${onEditGlobalStyles}>
211771
+ Edit Global Styles
211772
+ </sp-action-button>
211773
+ </div>
211774
+ </div>
211775
+ `;
211776
+ render2(tpl, container);
211777
+ }
211778
+
211779
+ // src/settings/settings-modal.js
211780
+ var _host = null;
211781
+ var _activeSection = "general";
211782
+ var sections = [
211783
+ { key: "general", label: "General", icon: "sp-icon-properties" },
211784
+ { key: "head", label: "Head", icon: "sp-icon-file-single-web-page" },
211785
+ { key: "cssVars", label: "CSS Variables", icon: "sp-icon-brush" },
211786
+ { key: "definitions", label: "Definitions", icon: "sp-icon-data" },
211787
+ { key: "contentTypes", label: "Content Types", icon: "sp-icon-view-grid" }
211788
+ ];
211789
+ function openSettingsModal() {
211790
+ if (_host)
211791
+ return;
211792
+ _host = document.createElement("div");
211793
+ _host.style.display = "contents";
211794
+ const themeRoot = document.querySelector("sp-theme") || document.body;
211795
+ themeRoot.appendChild(_host);
211796
+ _activeSection = "general";
211797
+ renderModal();
211798
+ }
211799
+ function closeSettingsModal() {
211800
+ if (!_host)
211801
+ return;
211802
+ _host.remove();
211803
+ _host = null;
211804
+ }
211805
+ function renderModal() {
211806
+ if (!_host)
211807
+ return;
211808
+ const onNavClick = (key) => {
211809
+ _activeSection = key;
211810
+ renderModal();
211811
+ renderActiveSection();
211812
+ };
211813
+ const tpl = html`
211814
+ <sp-underlay open @close=${closeSettingsModal}></sp-underlay>
211815
+ <div
211816
+ class="settings-modal"
211817
+ @keydown=${(e20) => {
211818
+ if (e20.key === "Escape")
211819
+ closeSettingsModal();
211820
+ }}
211821
+ >
211822
+ <div class="settings-modal-header">
211823
+ <h2 class="settings-modal-title">Settings</h2>
211824
+ <sp-action-button quiet size="s" @click=${closeSettingsModal} title="Close">
211825
+ <sp-icon-close slot="icon"></sp-icon-close>
211826
+ </sp-action-button>
211827
+ </div>
211828
+ <div class="settings-modal-body">
211829
+ <nav class="settings-modal-nav">
211830
+ ${sections.map((s2) => html`
211831
+ <button
211832
+ class="settings-nav-item${_activeSection === s2.key ? " active" : ""}"
211833
+ @click=${() => onNavClick(s2.key)}
211834
+ >
211835
+ ${s2.label}
211836
+ </button>
211837
+ `)}
211838
+ </nav>
211839
+ <div
211840
+ class="settings-modal-content"
211841
+ ${ref2((el) => {
211842
+ if (el)
211843
+ requestAnimationFrame(() => renderActiveSection());
211844
+ })}
211845
+ ></div>
211846
+ </div>
211847
+ </div>
211848
+ `;
211849
+ render2(tpl, _host);
211850
+ }
211851
+ function renderActiveSection() {
211852
+ if (!_host)
211853
+ return;
211854
+ const container = _host.querySelector(".settings-modal-content");
211855
+ if (!container)
211856
+ return;
211857
+ switch (_activeSection) {
211858
+ case "general":
211859
+ renderGeneralSettings(container);
211860
+ break;
211861
+ case "head":
211862
+ renderHeadEditor(container);
211863
+ break;
211864
+ case "cssVars":
211865
+ renderCssVarsEditor(container);
211866
+ break;
211867
+ case "definitions":
211868
+ renderDefsEditor(container);
211869
+ break;
211870
+ case "contentTypes":
211871
+ renderContentTypesEditor(container);
211872
+ break;
211873
+ }
211874
+ }
211875
+
211876
+ // src/panels/activity-bar.js
211727
211877
  var _scope3 = null;
211728
211878
  function mount4() {
211729
211879
  _scope3 = effectScope();
@@ -211805,6 +211955,17 @@ function renderActivityBar() {
211805
211955
  </sp-tab>
211806
211956
  `)}
211807
211957
  </sp-tabs>
211958
+ <div style="margin-top:auto;padding:8px 0;display:flex;justify-content:center">
211959
+ <sp-action-button
211960
+ quiet
211961
+ size="m"
211962
+ title="Settings"
211963
+ aria-label="Settings"
211964
+ @click=${() => openSettingsModal()}
211965
+ >
211966
+ <sp-icon-gears slot="icon"></sp-icon-gears>
211967
+ </sp-action-button>
211968
+ </div>
211808
211969
  `;
211809
211970
  render2(tpl, activityBar);
211810
211971
  }
@@ -211947,17 +212108,22 @@ function toolbarTemplate() {
211947
212108
  { key: "design", label: "Design", iconTag: "sp-icon-artboard" },
211948
212109
  { key: "preview", label: "Preview", iconTag: "sp-icon-preview" },
211949
212110
  { key: "source", label: "Code", iconTag: "sp-icon-code" },
211950
- { key: "settings", label: "Settings", iconTag: "sp-icon-gears" }
212111
+ { key: "stylebook", label: "Stylebook", iconTag: "sp-icon-brush" }
211951
212112
  ];
212113
+ const isProjectFile = S.documentPath === "project.json";
212114
+ const allowedModes = isProjectFile ? new Set(["stylebook", "source"]) : null;
211952
212115
  const modeSwitcherTpl = html`
211953
212116
  <sp-action-group selects="single" size="s" compact>
211954
212117
  ${modes.map((m3) => html`
211955
212118
  <sp-action-button
211956
212119
  size="s"
211957
212120
  ?selected=${canvasMode === m3.key}
212121
+ ?disabled=${allowedModes && !allowedModes.has(m3.key)}
211958
212122
  @click=${() => {
211959
212123
  if (canvasMode === m3.key)
211960
212124
  return;
212125
+ if (allowedModes && !allowedModes.has(m3.key))
212126
+ return;
211961
212127
  if (S.ui.editingFunction) {
211962
212128
  if (view.functionEditor) {
211963
212129
  view.functionEditor.dispose();
@@ -211968,7 +212134,7 @@ function toolbarTemplate() {
211968
212134
  view.panX = 0;
211969
212135
  view.panY = 0;
211970
212136
  const uiPatch = { editingFunction: null };
211971
- if (m3.key === "settings")
212137
+ if (m3.key === "stylebook")
211972
212138
  uiPatch.rightTab = "style";
211973
212139
  if (m3.key === "manage")
211974
212140
  view.leftTab = "files";
@@ -214777,7 +214943,7 @@ function renderStylePanelTemplate(ctx) {
214777
214943
  const tab = activeTab.value;
214778
214944
  if (!tab)
214779
214945
  return html`<div class="empty-state">No document loaded</div>`;
214780
- if (ctx.getCanvasMode() === "settings" && tab.session.ui.stylebookSelection) {
214946
+ if (ctx.getCanvasMode() === "stylebook" && tab.session.ui.stylebookSelection) {
214781
214947
  const node3 = tab.doc.document;
214782
214948
  if (!node3)
214783
214949
  return html`<div class="empty-state">No document loaded</div>`;
@@ -217035,7 +217201,7 @@ function _render() {
217035
217201
  const tab = view.leftTab;
217036
217202
  let content3;
217037
217203
  if (tab === "layers")
217038
- content3 = ctx.getCanvasMode() === "settings" ? renderStylebookLayersTemplate({
217204
+ content3 = ctx.getCanvasMode() === "stylebook" ? renderStylebookLayersTemplate({
217039
217205
  selectStylebookTag,
217040
217206
  stylebookMeta: stylebook_meta_default
217041
217207
  }) : renderLayersTemplate({
@@ -217097,7 +217263,7 @@ function _render() {
217097
217263
  else
217098
217264
  content3 = nothing;
217099
217265
  render2(html`<div class="panel-body">${content3}</div>`, leftPanel);
217100
- if (tab === "layers" && ctx.getCanvasMode() !== "settings")
217266
+ if (tab === "layers" && ctx.getCanvasMode() !== "stylebook")
217101
217267
  ctx.registerLayersDnD();
217102
217268
  else if (tab === "imports") {} else if (tab === "blocks") {
217103
217269
  ctx.registerElementsDnD();
@@ -217112,10 +217278,10 @@ function _render() {
217112
217278
  // src/panels/tab-strip.js
217113
217279
  init_reactivity();
217114
217280
  init_workspace();
217115
- var _host = null;
217281
+ var _host2 = null;
217116
217282
  var _scope7 = null;
217117
217283
  function mount8(host2) {
217118
- _host = host2;
217284
+ _host2 = host2;
217119
217285
  _scope7 = effectScope();
217120
217286
  _scope7.run(() => {
217121
217287
  effect(() => {
@@ -217130,10 +217296,10 @@ function mount8(host2) {
217130
217296
  });
217131
217297
  }
217132
217298
  function render10() {
217133
- if (!_host)
217299
+ if (!_host2)
217134
217300
  return;
217135
217301
  if (workspace.tabOrder.length <= 1) {
217136
- render2(nothing, _host);
217302
+ render2(nothing, _host2);
217137
217303
  return;
217138
217304
  }
217139
217305
  render2(html`
@@ -217172,7 +217338,7 @@ function render10() {
217172
217338
  `;
217173
217339
  })}
217174
217340
  </div>
217175
- `, _host);
217341
+ `, _host2);
217176
217342
  }
217177
217343
  function tabLabel(tab) {
217178
217344
  const path2 = tab.documentPath;
@@ -217658,5 +217824,5 @@ effect(() => {
217658
217824
  scheduleAutosave();
217659
217825
  });
217660
217826
 
217661
- //# debugId=D4BE66DA4C58521864756E2164756E21
217827
+ //# debugId=2C5B0A9299ACFF5964756E2164756E21
217662
217828
  //# sourceMappingURL=studio.js.map