@jxsuite/studio 0.28.2 → 0.28.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (104) hide show
  1. package/dist/studio.js +86144 -83971
  2. package/dist/studio.js.map +229 -219
  3. package/package.json +5 -5
  4. package/src/browse/browse-modal.ts +9 -3
  5. package/src/browse/browse.ts +139 -82
  6. package/src/canvas/canvas-diff.ts +35 -18
  7. package/src/canvas/canvas-helpers.ts +55 -21
  8. package/src/canvas/canvas-live-render.ts +140 -89
  9. package/src/canvas/canvas-render.ts +127 -82
  10. package/src/canvas/canvas-utils.ts +68 -42
  11. package/src/canvas/nested-site-style.ts +15 -7
  12. package/src/editor/component-inline-edit.ts +90 -51
  13. package/src/editor/content-inline-edit.ts +75 -72
  14. package/src/editor/context-menu.ts +125 -71
  15. package/src/editor/convert-targets.ts +17 -13
  16. package/src/editor/convert-to-component.ts +51 -28
  17. package/src/editor/convert-to-repeater.ts +44 -19
  18. package/src/editor/inline-edit.ts +107 -52
  19. package/src/editor/inline-format.ts +128 -60
  20. package/src/editor/insertion-helper.ts +26 -14
  21. package/src/editor/shortcuts.ts +89 -43
  22. package/src/editor/slash-menu.ts +41 -26
  23. package/src/files/components.ts +9 -7
  24. package/src/files/file-ops.ts +53 -33
  25. package/src/files/files.ts +246 -143
  26. package/src/format/constraints.ts +25 -15
  27. package/src/format/format-host.ts +33 -10
  28. package/src/github/github-auth.ts +24 -14
  29. package/src/github/github-publish.ts +20 -14
  30. package/src/new-project/new-project-modal.ts +27 -18
  31. package/src/panels/activity-bar.ts +27 -26
  32. package/src/panels/ai-panel.ts +100 -44
  33. package/src/panels/block-action-bar.ts +84 -52
  34. package/src/panels/canvas-dnd.ts +47 -28
  35. package/src/panels/data-explorer.ts +24 -11
  36. package/src/panels/dnd.ts +148 -100
  37. package/src/panels/editors.ts +54 -37
  38. package/src/panels/elements-panel.ts +23 -13
  39. package/src/panels/events-panel.ts +48 -38
  40. package/src/panels/git-panel.ts +95 -60
  41. package/src/panels/head-panel.ts +138 -84
  42. package/src/panels/imports-panel.ts +66 -29
  43. package/src/panels/layers-panel.ts +80 -36
  44. package/src/panels/left-panel.ts +86 -53
  45. package/src/panels/overlays.ts +36 -19
  46. package/src/panels/panel-events.ts +48 -27
  47. package/src/panels/panel-scheduler.ts +41 -23
  48. package/src/panels/preview-render.ts +26 -26
  49. package/src/panels/properties-panel.ts +155 -118
  50. package/src/panels/pseudo-preview.ts +31 -15
  51. package/src/panels/quick-search.ts +28 -13
  52. package/src/panels/right-panel.ts +35 -24
  53. package/src/panels/shared.ts +46 -29
  54. package/src/panels/signals-panel.ts +303 -186
  55. package/src/panels/statusbar.ts +27 -12
  56. package/src/panels/style-inputs.ts +30 -24
  57. package/src/panels/style-panel.ts +130 -90
  58. package/src/panels/style-utils.ts +63 -23
  59. package/src/panels/stylebook-layers-panel.ts +27 -30
  60. package/src/panels/stylebook-panel.ts +140 -72
  61. package/src/panels/tab-strip.ts +21 -10
  62. package/src/panels/toolbar.ts +52 -40
  63. package/src/panels/welcome-screen.ts +1 -1
  64. package/src/platform.ts +2 -1
  65. package/src/platforms/devserver.ts +221 -105
  66. package/src/recent-projects.ts +15 -7
  67. package/src/resize-edges.ts +7 -5
  68. package/src/services/cem-export.ts +73 -57
  69. package/src/services/code-services.ts +52 -25
  70. package/src/services/monaco-setup.ts +5 -5
  71. package/src/settings/content-types-editor.ts +133 -62
  72. package/src/settings/css-vars-editor.ts +31 -14
  73. package/src/settings/defs-editor.ts +125 -63
  74. package/src/settings/general-settings.ts +11 -6
  75. package/src/settings/head-editor.ts +30 -15
  76. package/src/settings/schema-field-ui.ts +75 -35
  77. package/src/settings/settings-modal.ts +35 -17
  78. package/src/site-context.ts +80 -47
  79. package/src/state.ts +98 -65
  80. package/src/store.ts +42 -23
  81. package/src/studio.ts +165 -144
  82. package/src/tabs/tab.ts +51 -45
  83. package/src/tabs/transact.ts +230 -109
  84. package/src/types.ts +60 -51
  85. package/src/ui/button-group.ts +3 -2
  86. package/src/ui/color-selector.ts +33 -15
  87. package/src/ui/expression-editor.ts +139 -86
  88. package/src/ui/field-input.ts +35 -18
  89. package/src/ui/field-row.ts +1 -1
  90. package/src/ui/icons.ts +5 -6
  91. package/src/ui/layers.ts +33 -26
  92. package/src/ui/media-picker.ts +13 -7
  93. package/src/ui/panel-resize.ts +39 -20
  94. package/src/ui/spectrum.ts +4 -2
  95. package/src/ui/unit-selector.ts +20 -11
  96. package/src/ui/value-selector.ts +13 -7
  97. package/src/ui/widgets.ts +34 -18
  98. package/src/utils/canvas-media.ts +66 -33
  99. package/src/utils/edit-display.ts +43 -44
  100. package/src/utils/google-fonts.ts +10 -6
  101. package/src/utils/inherited-style.ts +22 -8
  102. package/src/utils/studio-utils.ts +88 -45
  103. package/src/view.ts +9 -5
  104. package/src/workspace/workspace.ts +49 -29
@@ -45,7 +45,9 @@ export const FORMAT_OPTIONS = ["", "image", "date", "color"];
45
45
  * @returns {string}
46
46
  */
47
47
  export function detectFieldType(schema: SchemaProperty) {
48
- if (schema.$ref) return "reference";
48
+ if (schema.$ref) {
49
+ return "reference";
50
+ }
49
51
  return schema.type || "string";
50
52
  }
51
53
 
@@ -56,7 +58,9 @@ export function detectFieldType(schema: SchemaProperty) {
56
58
  * @returns {string}
57
59
  */
58
60
  export function detectFieldFormat(schema: SchemaProperty) {
59
- if (schema.type === "array" && schema.items?.format) return schema.items.format;
61
+ if (schema.type === "array" && schema.items?.format) {
62
+ return schema.items.format;
63
+ }
60
64
  return schema.format || "";
61
65
  }
62
66
 
@@ -99,12 +103,17 @@ export function fieldCardTpl(
99
103
  @change=${(e: Event) => {
100
104
  const target = e.target as HTMLInputElement;
101
105
  const newName = target.value.trim();
102
- if (newName && newName !== fieldName) handlers.onRename(fieldName, newName);
103
- else target.value = fieldName;
106
+ if (newName && newName !== fieldName) {
107
+ handlers.onRename(fieldName, newName);
108
+ } else {
109
+ target.value = fieldName;
110
+ }
104
111
  }}
105
112
  @keydown=${(e: KeyboardEvent) => {
106
113
  const target = e.target as HTMLInputElement;
107
- if (e.key === "Enter") target.blur();
114
+ if (e.key === "Enter") {
115
+ target.blur();
116
+ }
108
117
  if (e.key === "Escape") {
109
118
  target.value = fieldName;
110
119
  target.blur();
@@ -114,7 +123,9 @@ export function fieldCardTpl(
114
123
  ${typePickerTpl(type, (newType) => handlers.onChangeType(fieldName, newType))}
115
124
  ${type === "string" || type === "array"
116
125
  ? formatPickerTpl(format, (f) => {
117
- if (handlers.onChangeFormat) handlers.onChangeFormat(fieldName, f);
126
+ if (handlers.onChangeFormat) {
127
+ handlers.onChangeFormat(fieldName, f);
128
+ }
118
129
  })
119
130
  : nothing}
120
131
  <sp-switch
@@ -133,7 +144,7 @@ export function fieldCardTpl(
133
144
  <sp-icon-delete slot="icon"></sp-icon-delete>
134
145
  </sp-action-button>
135
146
  </div>
136
- ${isRef && contentTypeNames.length
147
+ ${isRef && contentTypeNames.length > 0
137
148
  ? html`
138
149
  <div class="schema-field-ref-target">
139
150
  <sp-picker
@@ -141,8 +152,9 @@ export function fieldCardTpl(
141
152
  label="Target"
142
153
  value=${refTarget}
143
154
  @change=${(e: Event) => {
144
- if (handlers.onChangeRefTarget)
155
+ if (handlers.onChangeRefTarget) {
145
156
  handlers.onChangeRefTarget(fieldName, (e.target as HTMLInputElement).value);
157
+ }
146
158
  }}
147
159
  >
148
160
  ${contentTypeNames.map((n) => html`<sp-menu-item value=${n}>${n}</sp-menu-item>`)}
@@ -210,7 +222,9 @@ function nestedFieldCardTpl(
210
222
  }}
211
223
  @keydown=${(e: KeyboardEvent) => {
212
224
  const target = e.target as HTMLInputElement;
213
- if (e.key === "Enter") target.blur();
225
+ if (e.key === "Enter") {
226
+ target.blur();
227
+ }
214
228
  if (e.key === "Escape") {
215
229
  target.value = childName;
216
230
  target.blur();
@@ -218,21 +232,24 @@ function nestedFieldCardTpl(
218
232
  }}
219
233
  ></sp-textfield>
220
234
  ${typePickerTpl(type, (newType) => {
221
- if (handlers.onChangeNestedType)
235
+ if (handlers.onChangeNestedType) {
222
236
  handlers.onChangeNestedType(parentName, childName, newType);
237
+ }
223
238
  })}
224
239
  ${type === "string" || type === "array"
225
240
  ? formatPickerTpl(format, (f) => {
226
- if (handlers.onChangeNestedFormat)
241
+ if (handlers.onChangeNestedFormat) {
227
242
  handlers.onChangeNestedFormat(parentName, childName, f);
243
+ }
228
244
  })
229
245
  : nothing}
230
246
  <sp-switch
231
247
  size="s"
232
248
  ?checked=${isRequired}
233
249
  @change=${() => {
234
- if (handlers.onToggleNestedRequired)
250
+ if (handlers.onToggleNestedRequired) {
235
251
  handlers.onToggleNestedRequired(parentName, childName);
252
+ }
236
253
  }}
237
254
  >
238
255
  Req
@@ -242,7 +259,9 @@ function nestedFieldCardTpl(
242
259
  quiet
243
260
  title="Delete field"
244
261
  @click=${() => {
245
- if (handlers.onDeleteNested) handlers.onDeleteNested(parentName, childName);
262
+ if (handlers.onDeleteNested) {
263
+ handlers.onDeleteNested(parentName, childName);
264
+ }
246
265
  }}
247
266
  >
248
267
  <sp-icon-delete slot="icon"></sp-icon-delete>
@@ -276,8 +295,8 @@ function nestedAddFieldTpl(parentName: string, handlers: FieldHandlers) {
276
295
  if (name && handlers.onAddNestedField) {
277
296
  handlers.onAddNestedField(parentName, {
278
297
  name,
279
- type,
280
298
  required: false,
299
+ type,
281
300
  });
282
301
  target.value = "";
283
302
  }
@@ -301,10 +320,12 @@ function nestedAddFieldTpl(parentName: string, handlers: FieldHandlers) {
301
320
  if (name && handlers.onAddNestedField) {
302
321
  handlers.onAddNestedField(parentName, {
303
322
  name,
304
- type,
305
323
  required: false,
324
+ type,
306
325
  });
307
- if (nameInput) nameInput.value = "";
326
+ if (nameInput) {
327
+ nameInput.value = "";
328
+ }
308
329
  }
309
330
  }}
310
331
  >
@@ -381,8 +402,12 @@ export function addFieldFormTpl(
381
402
  .value=${state.name}
382
403
  @input=${(e: Event) => handlers.onInput("name", (e.target as HTMLInputElement).value)}
383
404
  @keydown=${(e: KeyboardEvent) => {
384
- if (e.key === "Enter") handlers.onConfirm();
385
- if (e.key === "Escape") handlers.onCancel();
405
+ if (e.key === "Enter") {
406
+ handlers.onConfirm();
407
+ }
408
+ if (e.key === "Escape") {
409
+ handlers.onCancel();
410
+ }
386
411
  }}
387
412
  ></sp-textfield>
388
413
  ${typePickerTpl(state.type, (t) => handlers.onInput("type", t))}
@@ -412,20 +437,26 @@ export function addFieldFormTpl(
412
437
  */
413
438
  export function schemaForType(type: string, format?: string) {
414
439
  switch (type) {
415
- case "number":
440
+ case "number": {
416
441
  return { type: "number" };
417
- case "boolean":
442
+ }
443
+ case "boolean": {
418
444
  return { type: "boolean" };
419
- case "array":
445
+ }
446
+ case "array": {
420
447
  return format
421
- ? { type: "array", items: { type: "string", format } }
422
- : { type: "array", items: { type: "string" } };
423
- case "object":
424
- return { type: "object", properties: {}, required: [] };
425
- case "reference":
448
+ ? { items: { format, type: "string" }, type: "array" }
449
+ : { items: { type: "string" }, type: "array" };
450
+ }
451
+ case "object": {
452
+ return { properties: {}, required: [], type: "object" };
453
+ }
454
+ case "reference": {
426
455
  return { $ref: "#/contentTypes/" };
427
- default:
428
- return format ? { type: "string", format } : { type: "string" };
456
+ }
457
+ default: {
458
+ return format ? { format, type: "string" } : { type: "string" };
459
+ }
429
460
  }
430
461
  }
431
462
 
@@ -437,18 +468,27 @@ export function schemaForType(type: string, format?: string) {
437
468
  * @returns {string}
438
469
  */
439
470
  export function yamlDefault(type: string, format?: string) {
440
- if (format === "date") return new Date().toISOString().split("T")[0];
441
- if (format === "image") return '""';
471
+ if (format === "date") {
472
+ return new Date().toISOString().split("T")[0];
473
+ }
474
+ if (format === "image") {
475
+ return '""';
476
+ }
442
477
  switch (type) {
443
- case "boolean":
478
+ case "boolean": {
444
479
  return "false";
445
- case "number":
480
+ }
481
+ case "number": {
446
482
  return "0";
447
- case "array":
483
+ }
484
+ case "array": {
448
485
  return "[]";
449
- case "object":
486
+ }
487
+ case "object": {
450
488
  return "{}";
451
- default:
489
+ }
490
+ default: {
452
491
  return '""';
492
+ }
453
493
  }
454
494
  }
@@ -17,26 +17,30 @@ import { openModal } from "../ui/layers";
17
17
 
18
18
  let _handle: ReturnType<typeof openModal> | null = null;
19
19
 
20
- let _activeSection: string = "general";
20
+ let _activeSection = "general";
21
21
 
22
22
  let _contentEl: HTMLElement | null = null;
23
23
 
24
24
  const sections = [
25
- { key: "general", label: "General", icon: "sp-icon-properties" },
26
- { key: "head", label: "Head", icon: "sp-icon-file-single-web-page" },
27
- { key: "cssVars", label: "CSS Variables", icon: "sp-icon-brush" },
28
- { key: "definitions", label: "Definitions", icon: "sp-icon-data" },
29
- { key: "contentTypes", label: "Content Types", icon: "sp-icon-view-grid" },
25
+ { icon: "sp-icon-properties", key: "general", label: "General" },
26
+ { icon: "sp-icon-file-single-web-page", key: "head", label: "Head" },
27
+ { icon: "sp-icon-brush", key: "cssVars", label: "CSS Variables" },
28
+ { icon: "sp-icon-data", key: "definitions", label: "Definitions" },
29
+ { icon: "sp-icon-view-grid", key: "contentTypes", label: "Content Types" },
30
30
  ];
31
31
 
32
32
  export function openSettingsModal() {
33
- if (_handle) return;
33
+ if (_handle) {
34
+ return;
35
+ }
34
36
  _activeSection = "general";
35
37
  renderModal();
36
38
  }
37
39
 
38
40
  export function closeSettingsModal() {
39
- if (!_handle) return;
41
+ if (!_handle) {
42
+ return;
43
+ }
40
44
  _handle.close();
41
45
  _handle = null;
42
46
  _contentEl = null;
@@ -54,7 +58,9 @@ function renderModal() {
54
58
  <div
55
59
  class="settings-modal"
56
60
  @keydown=${(e: KeyboardEvent) => {
57
- if (e.key === "Escape") closeSettingsModal();
61
+ if (e.key === "Escape") {
62
+ closeSettingsModal();
63
+ }
58
64
  }}
59
65
  >
60
66
  <div class="settings-modal-header">
@@ -69,8 +75,8 @@ function renderModal() {
69
75
  (s) => html`
70
76
  <button
71
77
  class=${classMap({
72
- "settings-nav-item": true,
73
78
  active: _activeSection === s.key,
79
+ "settings-nav-item": true,
74
80
  })}
75
81
  @click=${() => onNavClick(s.key)}
76
82
  >
@@ -83,7 +89,9 @@ function renderModal() {
83
89
  class="settings-modal-content"
84
90
  ${ref((el: Element | undefined) => {
85
91
  _contentEl = (el as HTMLElement) || null;
86
- if (_contentEl) requestAnimationFrame(() => renderActiveSection());
92
+ if (_contentEl) {
93
+ requestAnimationFrame(() => renderActiveSection());
94
+ }
87
95
  })}
88
96
  ></div>
89
97
  </div>
@@ -98,23 +106,33 @@ function renderModal() {
98
106
  }
99
107
 
100
108
  function renderActiveSection() {
101
- if (!_handle || !_contentEl) return;
109
+ if (!_handle || !_contentEl) {
110
+ return;
111
+ }
102
112
 
103
113
  switch (_activeSection) {
104
- case "general":
114
+ case "general": {
105
115
  renderGeneralSettings(_contentEl);
106
116
  break;
107
- case "head":
117
+ }
118
+ case "head": {
108
119
  renderHeadEditor(_contentEl);
109
120
  break;
110
- case "cssVars":
121
+ }
122
+ case "cssVars": {
111
123
  renderCssVarsEditor(_contentEl);
112
124
  break;
113
- case "definitions":
125
+ }
126
+ case "definitions": {
114
127
  renderDefsEditor(_contentEl);
115
128
  break;
116
- case "contentTypes":
129
+ }
130
+ case "contentTypes": {
117
131
  renderContentTypesEditor(_contentEl);
118
132
  break;
133
+ }
134
+ default: {
135
+ break;
136
+ }
119
137
  }
120
138
  }
@@ -6,14 +6,14 @@
6
6
  * definitions merge on top (file wins on conflict).
7
7
  */
8
8
 
9
- import { projectState, setProjectState, requireProjectState } from "./store";
9
+ import { projectState, requireProjectState, setProjectState } from "./store";
10
10
  import { getPlatform } from "./platform";
11
11
 
12
12
  import type {
13
13
  JxElement,
14
+ JxHeadEntry,
14
15
  JxMutableNode,
15
16
  JxStyle,
16
- JxHeadEntry,
17
17
  ProjectConfig,
18
18
  } from "@jxsuite/schema/types";
19
19
 
@@ -24,10 +24,14 @@ import type {
24
24
  * undefined)
25
25
  * @returns {Record<string, string>}
26
26
  */
27
- export function getEffectiveMedia(docMedia: Record<string, string> | undefined) {
27
+ export function getEffectiveMedia(docMedia?: Record<string, string>) {
28
28
  const siteMedia = projectState?.projectConfig?.$media;
29
- if (!siteMedia) return docMedia || {};
30
- if (!docMedia) return { ...siteMedia };
29
+ if (!siteMedia) {
30
+ return docMedia || {};
31
+ }
32
+ if (!docMedia) {
33
+ return { ...siteMedia };
34
+ }
31
35
  return { ...siteMedia, ...docMedia };
32
36
  }
33
37
 
@@ -38,22 +42,20 @@ export function getEffectiveMedia(docMedia: Record<string, string> | undefined)
38
42
  * @param {JxStyle | undefined} docStyle - The current document's style (may be undefined)
39
43
  * @returns {JxStyle}
40
44
  */
41
- export function getEffectiveStyle(docStyle: JxStyle | undefined) {
45
+ export function getEffectiveStyle(docStyle?: JxStyle) {
42
46
  const siteStyle = projectState?.projectConfig?.style;
43
- if (!siteStyle) return docStyle || {};
44
- if (!docStyle) return { ...siteStyle };
47
+ if (!siteStyle) {
48
+ return docStyle || {};
49
+ }
50
+ if (!docStyle) {
51
+ return { ...siteStyle };
52
+ }
45
53
  const merged = { ...siteStyle };
46
54
  for (const [k, v] of Object.entries(docStyle)) {
47
- if (
48
- typeof v === "object" &&
49
- v !== null &&
50
- typeof merged[k] === "object" &&
51
- merged[k] !== null
52
- ) {
53
- merged[k] = { ...(merged[k] as JxStyle), ...(v as JxStyle) };
54
- } else {
55
- merged[k] = v;
56
- }
55
+ merged[k] =
56
+ typeof v === "object" && v !== null && typeof merged[k] === "object" && merged[k] !== null
57
+ ? { ...(merged[k] as JxStyle), ...(v as JxStyle) }
58
+ : v;
57
59
  }
58
60
  return merged;
59
61
  }
@@ -65,10 +67,14 @@ export function getEffectiveStyle(docStyle: JxStyle | undefined) {
65
67
  * undefined)
66
68
  * @returns {Record<string, string>}
67
69
  */
68
- export function getEffectiveImports(docImports: Record<string, string> | undefined) {
70
+ export function getEffectiveImports(docImports?: Record<string, string>) {
69
71
  const siteImports = projectState?.projectConfig?.imports;
70
- if (!siteImports) return docImports || {};
71
- if (!docImports) return { ...siteImports };
72
+ if (!siteImports) {
73
+ return docImports || {};
74
+ }
75
+ if (!docImports) {
76
+ return { ...siteImports };
77
+ }
72
78
  return { ...siteImports, ...docImports };
73
79
  }
74
80
 
@@ -81,9 +87,13 @@ export function getEffectiveImports(docImports: Record<string, string> | undefin
81
87
  */
82
88
  export function getEffectiveElements(docElements?: (JxElement | string)[]) {
83
89
  const siteElements = projectState?.projectConfig?.$elements;
84
- if (!siteElements?.length) return docElements || [];
85
- if (!docElements?.length) return [...siteElements];
86
- const seen: Set<string> = new Set();
90
+ if (!siteElements?.length) {
91
+ return docElements || [];
92
+ }
93
+ if (!docElements?.length) {
94
+ return [...siteElements];
95
+ }
96
+ const seen = new Set<string>();
87
97
  const merged: (JxElement | string)[] = [];
88
98
  for (const entry of [...siteElements, ...docElements]) {
89
99
  const key = typeof entry === "string" ? entry : entry?.$ref;
@@ -103,9 +113,13 @@ export function getEffectiveElements(docElements?: (JxElement | string)[]) {
103
113
  */
104
114
  export function getEffectiveHead(docHead?: JxHeadEntry[]) {
105
115
  const siteHead = projectState?.projectConfig?.$head;
106
- if (!siteHead?.length) return docHead || [];
107
- if (!docHead?.length) return [...siteHead];
108
- const seen: Set<string> = new Set();
116
+ if (!siteHead?.length) {
117
+ return docHead || [];
118
+ }
119
+ if (!docHead?.length) {
120
+ return [...siteHead];
121
+ }
122
+ const seen = new Set<string>();
109
123
  const merged: JxHeadEntry[] = [];
110
124
  for (const entry of [...siteHead, ...docHead]) {
111
125
  const key =
@@ -120,7 +134,7 @@ export function getEffectiveHead(docHead?: JxHeadEntry[]) {
120
134
 
121
135
  // ─── Layout resolution ──────────────────────────────────────────────────────
122
136
 
123
- const layoutCache: Map<string, JxMutableNode> = new Map();
137
+ const layoutCache = new Map<string, JxMutableNode>();
124
138
 
125
139
  export function invalidateLayoutCache() {
126
140
  layoutCache.clear();
@@ -134,7 +148,9 @@ export function invalidateLayoutCache() {
134
148
  * @returns {string | null} The layout path, or null if no layout applies
135
149
  */
136
150
  export function getEffectiveLayoutPath(docLayout: string | false | undefined) {
137
- if (docLayout === false) return null;
151
+ if (docLayout === false) {
152
+ return null;
153
+ }
138
154
  const defaultLayout = projectState?.projectConfig?.defaults?.layout;
139
155
  return docLayout || defaultLayout || null;
140
156
  }
@@ -147,8 +163,9 @@ export function getEffectiveLayoutPath(docLayout: string | false | undefined) {
147
163
  */
148
164
  export async function resolveLayoutDoc(layoutPath: string) {
149
165
  const normalized = layoutPath.replace(/^\.\//, "");
150
- if (layoutCache.has(normalized))
166
+ if (layoutCache.has(normalized)) {
151
167
  return structuredClone(layoutCache.get(normalized) as JxMutableNode);
168
+ }
152
169
 
153
170
  try {
154
171
  const platform = getPlatform();
@@ -171,16 +188,18 @@ export async function resolveLayoutDoc(layoutPath: string) {
171
188
  */
172
189
  export function distributePageIntoLayout(layoutDoc: JxMutableNode, pageDoc: JxMutableNode) {
173
190
  const pageChildren = pageDoc.children ?? [];
174
- const children = typeof pageChildren === "string" ? [pageChildren] : pageChildren;
191
+ const children = Array.isArray(pageChildren) ? pageChildren : [];
175
192
 
176
- const named = new Map();
177
- const defaults = [];
193
+ const named = new Map<string, JxMutableNode[]>();
194
+ const defaults: (JxMutableNode | string)[] = [];
178
195
 
179
196
  for (const child of children) {
180
- if (child && typeof child === "object" && child.attributes?.slot) {
181
- const slotName = child.attributes.slot;
182
- if (!named.has(slotName)) named.set(slotName, []);
183
- named.get(slotName).push(child);
197
+ const slotName = typeof child === "object" ? child.attributes?.slot : undefined;
198
+ if (typeof child === "object" && typeof slotName === "string" && slotName) {
199
+ if (!named.has(slotName)) {
200
+ named.set(slotName, []);
201
+ }
202
+ named.get(slotName)!.push(child);
184
203
  } else {
185
204
  defaults.push(child);
186
205
  }
@@ -188,10 +207,18 @@ export function distributePageIntoLayout(layoutDoc: JxMutableNode, pageDoc: JxMu
188
207
 
189
208
  fillSlots(layoutDoc, named, defaults);
190
209
 
191
- if (pageDoc.state) layoutDoc.state = { ...layoutDoc.state, ...pageDoc.state };
192
- if (pageDoc.$media) layoutDoc.$media = { ...layoutDoc.$media, ...pageDoc.$media };
193
- if (pageDoc.style) layoutDoc.style = { ...layoutDoc.style, ...pageDoc.style };
194
- if (pageDoc.attributes) layoutDoc.attributes = { ...layoutDoc.attributes, ...pageDoc.attributes };
210
+ if (pageDoc.state) {
211
+ layoutDoc.state = { ...layoutDoc.state, ...pageDoc.state };
212
+ }
213
+ if (pageDoc.$media) {
214
+ layoutDoc.$media = { ...layoutDoc.$media, ...pageDoc.$media };
215
+ }
216
+ if (pageDoc.style) {
217
+ layoutDoc.style = { ...layoutDoc.style, ...pageDoc.style };
218
+ }
219
+ if (pageDoc.attributes) {
220
+ layoutDoc.attributes = { ...layoutDoc.attributes, ...pageDoc.attributes };
221
+ }
195
222
 
196
223
  return layoutDoc;
197
224
  }
@@ -201,23 +228,29 @@ function fillSlots(
201
228
  named: Map<string, JxMutableNode[]>,
202
229
  defaults: (JxMutableNode | string)[],
203
230
  ) {
204
- if (!node || typeof node !== "object") return;
205
- if (!Array.isArray(node.children)) return;
231
+ if (!node || typeof node !== "object") {
232
+ return;
233
+ }
234
+ if (!Array.isArray(node.children)) {
235
+ return;
236
+ }
206
237
 
207
238
  const newChildren: (JxMutableNode | string)[] = [];
208
239
  for (const child of node.children) {
209
240
  if (child && typeof child === "object" && child.tagName === "slot") {
210
241
  const slotName = child.attributes?.name;
211
- if (slotName && named.has(slotName)) {
212
- newChildren.push(...(named.get(slotName) as JxMutableNode[]));
242
+ if (typeof slotName === "string" && slotName && named.has(slotName)) {
243
+ newChildren.push(...named.get(slotName)!);
213
244
  named.delete(slotName);
214
245
  } else if (!slotName && defaults.length > 0) {
215
246
  newChildren.push(...defaults);
216
- } else if (child.children) {
247
+ } else if (Array.isArray(child.children)) {
217
248
  newChildren.push(...child.children);
218
249
  }
219
250
  } else {
220
- if (typeof child !== "string") fillSlots(child, named, defaults);
251
+ if (typeof child !== "string") {
252
+ fillSlots(child, named, defaults);
253
+ }
221
254
  newChildren.push(child);
222
255
  }
223
256
  }