@xosen/site-sdk 0.0.12 → 0.0.14

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.
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Shared reactive store for page data loaded via usePageData.
3
+ * Allows useDynamicLayout to reactively read layout from fetched pages.
4
+ */
5
+ export declare const pageDataStore: Record<string, any>;
6
+ /**
7
+ * Initialize store with preloaded data from __SITE_DATA__.
8
+ */
9
+ export declare function initPageDataStore(): void;
@@ -1,20 +1,4 @@
1
- export interface GridBreakpoint {
2
- areas?: string[];
3
- columns?: string;
4
- rows?: string;
5
- gap?: string;
6
- }
7
- export interface LayoutZone {
8
- type?: 'slot';
9
- component?: string;
10
- class?: string;
11
- container?: boolean;
12
- containerStyle?: Record<string, string>;
13
- }
14
- export interface LayoutConfig {
15
- grid?: Record<string, GridBreakpoint>;
16
- zones: Record<string, LayoutZone>;
17
- }
1
+ import type { LayoutConfig } from '../types/layout.js';
18
2
  /**
19
3
  * Generate responsive CSS for a layout config.
20
4
  * Returns a CSS string with media queries for each breakpoint.
package/dist/index.d.ts CHANGED
@@ -8,7 +8,8 @@ export type { UsePageDataReturn } from './composables/usePageData.js';
8
8
  export { useSiteComponents } from './composables/useSiteComponents.js';
9
9
  export { useLocaleSwitcher } from './composables/useLocaleSwitcher.js';
10
10
  export { useDynamicLayout, buildLayoutCSS } from './composables/useDynamicLayout.js';
11
- export type { LayoutConfig, LayoutZone, GridBreakpoint } from './composables/useDynamicLayout.js';
11
+ export type { LayoutConfig, LayoutZone, GridBreakpoint, LayoutSchemaDefinition, LayoutField } from './types/layout.js';
12
+ export { layoutSchema, layoutPresets } from './types/layout.js';
12
13
  export type { PreviewMessage } from './composables/useLivePreview.js';
13
14
  export { createSiteWorker } from './worker/index.js';
14
15
  export type { WorkerEnv, WorkerConfig, PageData } from './worker/index.js';
package/dist/index.js CHANGED
@@ -1,101 +1,101 @@
1
- import { ref as w, onMounted as T, watch as _, computed as u, onUnmounted as N, provide as I } from "vue";
2
- import { useI18n as $ } from "vue-i18n";
3
- import { useRoute as P } from "vue-router";
4
- function B(o, n) {
5
- const t = w(null), { locale: e } = $(), a = window.__SITE_DATA__, s = a?.locale;
6
- function i() {
7
- return a && a[o] && e.value === s ? (t.value = a[o], !0) : !1;
1
+ import { ref as w, onMounted as T, watch as _, computed as u, onUnmounted as N, reactive as L, provide as P } from "vue";
2
+ import { useI18n as S } from "vue-i18n";
3
+ import { useRoute as I } from "vue-router";
4
+ function Z(o, a) {
5
+ const t = w(null), { locale: e } = S(), n = window.__SITE_DATA__, s = n?.locale;
6
+ function r() {
7
+ return n && n[o] && e.value === s ? (t.value = n[o], !0) : !1;
8
8
  }
9
- async function l() {
9
+ async function i() {
10
10
  try {
11
- const c = await fetch(`/api/content/${n}:${e.value}`);
11
+ const c = await fetch(`/api/content/${a}:${e.value}`);
12
12
  c.ok && (t.value = await c.json());
13
13
  } catch {
14
14
  }
15
15
  }
16
16
  return T(() => {
17
- i() || l();
17
+ r() || i();
18
18
  }), _(e, () => {
19
- l();
19
+ i();
20
20
  }), t;
21
21
  }
22
- function G() {
23
- const o = window.__SITE_DATA__, n = u(() => o?.config || null), t = u(() => o?.components || {}), e = u(() => n.value?.navigation || []), a = u(() => n.value?.locales || []), s = u(() => n.value?.defaultLocale || "en"), i = u(() => n.value?.branding || {}), l = u(() => n.value?.footer || {}), c = u(() => n.value?.features || {});
24
- function f(d) {
25
- const v = c.value[d];
26
- return typeof v == "boolean" ? v : typeof v == "object";
22
+ function q() {
23
+ const o = window.__SITE_DATA__, a = u(() => o?.config || null), t = u(() => o?.components || {}), e = u(() => a.value?.navigation || []), n = u(() => a.value?.locales || []), s = u(() => a.value?.defaultLocale || "en"), r = u(() => a.value?.branding || {}), i = u(() => a.value?.footer || {}), c = u(() => a.value?.features || {});
24
+ function p(m) {
25
+ const g = c.value[m];
26
+ return typeof g == "boolean" ? g : typeof g == "object";
27
27
  }
28
28
  return {
29
- config: n,
29
+ config: a,
30
30
  components: t,
31
31
  navigation: e,
32
- locales: a,
32
+ locales: n,
33
33
  defaultLocale: s,
34
- branding: i,
35
- footer: l,
34
+ branding: r,
35
+ footer: i,
36
36
  features: c,
37
- hasFeature: f
37
+ hasFeature: p
38
38
  };
39
39
  }
40
- function Y(o) {
41
- function n(t) {
40
+ function H(o) {
41
+ function a(t) {
42
42
  const e = document.documentElement;
43
43
  if (t.colors)
44
- for (const [a, s] of Object.entries(t.colors))
45
- s && e.style.setProperty(`--x-color-${x(a)}`, s);
44
+ for (const [n, s] of Object.entries(t.colors))
45
+ s && e.style.setProperty(`--x-color-${D(n)}`, s);
46
46
  t.typography?.fontFamily && e.style.setProperty("--x-font-family", t.typography.fontFamily), t.typography?.headingFont && e.style.setProperty("--x-font-heading", t.typography.headingFont), t.typography?.baseFontSize && e.style.setProperty("--x-font-size", t.typography.baseFontSize), t.shape?.borderRadius && e.style.setProperty("--x-border-radius", t.shape.borderRadius), t.shape?.maxWidth && e.style.setProperty("--x-max-width", t.shape.maxWidth);
47
47
  }
48
48
  T(() => {
49
49
  if (o) {
50
- n(o);
50
+ a(o);
51
51
  return;
52
52
  }
53
53
  const e = window.__SITE_DATA__?.skin;
54
- e && (e.colors || e.typography || e.shape) && n({
54
+ e && (e.colors || e.typography || e.shape) && a({
55
55
  colors: e.colors || {},
56
56
  typography: e.typography || {},
57
57
  shape: e.shape || {}
58
58
  });
59
59
  });
60
60
  }
61
- function x(o) {
61
+ function D(o) {
62
62
  return o.replace(/([a-z])([A-Z])/g, "$1-$2").toLowerCase();
63
63
  }
64
- function X() {
65
- const n = window.__SITE_DATA__?.apiBase || "";
64
+ function Q() {
65
+ const a = window.__SITE_DATA__?.apiBase || "";
66
66
  async function t(s) {
67
- const i = await fetch(`${n}${s}`);
68
- if (!i.ok)
69
- throw new Error(`API error: ${i.status}`);
70
- return i.json();
67
+ const r = await fetch(`${a}${s}`);
68
+ if (!r.ok)
69
+ throw new Error(`API error: ${r.status}`);
70
+ return r.json();
71
71
  }
72
72
  async function e() {
73
73
  return t("/v1/billing/tariffs");
74
74
  }
75
- async function a() {
75
+ async function n() {
76
76
  return t("/v1/billing/services");
77
77
  }
78
78
  return {
79
79
  get: t,
80
80
  getTariffs: e,
81
- getProducts: a
81
+ getProducts: n
82
82
  };
83
83
  }
84
- function b() {
84
+ function A() {
85
85
  try {
86
86
  return window.self !== window.top;
87
87
  } catch {
88
88
  return !0;
89
89
  }
90
90
  }
91
- function Z(o) {
92
- b() && o.beforeEach((n, t) => !t.name);
91
+ function V(o) {
92
+ A() && o.beforeEach((a, t) => !t.name);
93
93
  }
94
94
  function C() {
95
- const o = w(null), n = w(b());
95
+ const o = w(null), a = w(A());
96
96
  function t(e) {
97
- const a = e.data;
98
- a?.type === "xosen-preview-update" && a.page && (o.value = a.page, n.value = !0);
97
+ const n = e.data;
98
+ n?.type === "xosen-preview-update" && n.page && (o.value = n.page, a.value = !0);
99
99
  }
100
100
  return T(() => {
101
101
  window.addEventListener("message", t);
@@ -103,84 +103,94 @@ function C() {
103
103
  window.removeEventListener("message", t);
104
104
  }), {
105
105
  previewPage: o,
106
- isPreviewMode: n
106
+ isPreviewMode: a
107
107
  };
108
108
  }
109
- const D = /* @__PURE__ */ Symbol("pageContext");
110
- function H(o) {
111
- const { locale: n } = $(), { previewPage: t, isPreviewMode: e } = C(), a = window.__SITE_DATA__, s = w(null), i = w(!0), l = w(!1), c = u(() => typeof o == "string" ? o : o.value), f = u(() => `page:${c.value}`), d = u(() => {
112
- const r = e.value ? t.value : s.value;
113
- if (!r) return {};
114
- const { blocks: y, meta: p, slug: m, locale: z, ...L } = r;
115
- return L;
109
+ const R = /* @__PURE__ */ Symbol("pageContext"), $ = L({});
110
+ function j() {
111
+ const o = window.__SITE_DATA__;
112
+ if (o)
113
+ for (const a of Object.keys(o))
114
+ a.startsWith("page:") && ($[a] = o[a]);
115
+ }
116
+ j();
117
+ function ee(o) {
118
+ const { locale: a } = S(), { previewPage: t, isPreviewMode: e } = C(), n = window.__SITE_DATA__, s = w(null), r = w(!0), i = w(!1), c = u(() => typeof o == "string" ? o : o.value), p = u(() => `page:${c.value}`), m = u(() => {
119
+ const l = e.value ? t.value : s.value;
120
+ if (!l) return {};
121
+ const { blocks: f, meta: d, slug: W, locale: J, ...E } = l;
122
+ return E;
116
123
  });
117
- I(D, d);
118
- const v = u(() => e.value && t.value?.slug === c.value ? t.value.blocks || [] : s.value?.blocks || []);
119
- function S() {
120
- const r = a?.[f.value];
121
- return r && n.value === a?.locale ? (s.value = r, l.value = !1, h(), !0) : !1;
124
+ P(R, m);
125
+ const g = u(() => e.value && t.value?.slug === c.value ? t.value.blocks || [] : s.value?.blocks || []);
126
+ function b(l) {
127
+ s.value = l, l && ($[p.value] = l);
128
+ }
129
+ function y() {
130
+ const l = n?.[p.value];
131
+ return l && a.value === n?.locale ? (b(l), i.value = !1, v(), !0) : !1;
122
132
  }
123
- async function g() {
124
- i.value = !0, l.value = !1;
133
+ async function h() {
134
+ r.value = !0, i.value = !1;
125
135
  try {
126
- const r = await fetch(`/api/content/${f.value}:${n.value}`);
127
- r.ok ? (s.value = await r.json(), h()) : (s.value = null, l.value = !0);
136
+ const l = await fetch(`/api/content/${p.value}:${a.value}`);
137
+ l.ok ? (b(await l.json()), v()) : (b(null), i.value = !0);
128
138
  } catch {
129
- s.value = null, l.value = !0;
139
+ b(null), i.value = !0;
130
140
  } finally {
131
- i.value = !1;
141
+ r.value = !1;
132
142
  }
133
143
  }
134
- function h() {
144
+ function v() {
135
145
  if (!s.value) return;
136
146
  if (s.value.title) {
137
- const y = a?.config?.branding, p = y?.siteName ? ` — ${y.siteName}` : "";
138
- document.title = `${s.value.title}${p}`;
147
+ const f = n?.config?.branding, d = f?.siteName ? ` — ${f.siteName}` : "";
148
+ document.title = `${s.value.title}${d}`;
139
149
  }
140
- const r = s.value.meta?.description;
141
- if (r) {
142
- const y = document.querySelector('meta[name="description"]');
143
- y && y.setAttribute("content", r);
150
+ const l = s.value.meta?.description;
151
+ if (l) {
152
+ const f = document.querySelector('meta[name="description"]');
153
+ f && f.setAttribute("content", l);
144
154
  }
145
155
  }
146
- return _(t, (r) => {
147
- r?.slug === c.value && (i.value = !1, l.value = !1, (r.title || r.blocks) && (s.value = {
156
+ return _(t, (l) => {
157
+ l?.slug === c.value && (r.value = !1, i.value = !1, (l.title || l.blocks) && (s.value = {
148
158
  ...s.value || {},
149
- ...r.title ? { title: r.title } : {},
150
- ...r.blocks ? { blocks: r.blocks } : {}
151
- }, h()));
159
+ ...l.title ? { title: l.title } : {},
160
+ ...l.blocks ? { blocks: l.blocks } : {}
161
+ }, v()));
152
162
  }), T(() => {
153
- S() ? i.value = !1 : g();
154
- }), _(n, () => g()), _(c, () => {
155
- S() ? i.value = !1 : g();
163
+ y() ? r.value = !1 : h();
164
+ }), _(a, () => h()), _(c, () => {
165
+ y() ? r.value = !1 : h();
156
166
  }), {
157
167
  page: s,
158
- blocks: v,
159
- pageContext: d,
160
- loading: i,
161
- notFound: l,
168
+ blocks: g,
169
+ pageContext: m,
170
+ loading: r,
171
+ notFound: i,
162
172
  isPreviewMode: e,
163
- reload: g
173
+ reload: h
164
174
  };
165
175
  }
166
- function Q() {
167
- const { locale: o } = $(), n = window.__SITE_DATA__, t = n?.locale, e = w({ ...n?.components || {} });
168
- async function a(i) {
169
- if (i === t) {
170
- e.value = { ...n?.components || {} };
176
+ function te() {
177
+ const { locale: o } = S(), a = window.__SITE_DATA__, t = a?.locale, e = w({ ...a?.components || {} });
178
+ async function n(r) {
179
+ if (r === t) {
180
+ e.value = { ...a?.components || {} };
171
181
  return;
172
182
  }
173
183
  try {
174
- const l = await fetch(`/api/content/components:${i}`);
175
- l.ok && (e.value = await l.json());
184
+ const i = await fetch(`/api/content/components:${r}`);
185
+ i.ok && (e.value = await i.json());
176
186
  } catch {
177
187
  }
178
188
  }
179
- _(o, (i) => a(i)), T(() => {
180
- o.value !== t && a(o.value);
189
+ _(o, (r) => n(r)), T(() => {
190
+ o.value !== t && n(o.value);
181
191
  });
182
- function s(i) {
183
- return u(() => e.value[i] || null);
192
+ function s(r) {
193
+ return u(() => e.value[r] || null);
184
194
  }
185
195
  return {
186
196
  /** All components (reactive, locale-aware) */
@@ -188,32 +198,32 @@ function Q() {
188
198
  /** Get a single component by key */
189
199
  getComponent: s,
190
200
  /** Force refetch for current locale */
191
- reload: () => a(o.value)
201
+ reload: () => n(o.value)
192
202
  };
193
203
  }
194
- function V() {
195
- const { locale: o } = $(), n = window.__SITE_DATA__, t = u(() => n?.config?.locales || []), e = u(() => n?.config?.defaultLocale || "en"), a = u(() => t.value.length > 1);
196
- function s(l) {
197
- o.value = l, localStorage.setItem("x-site-locale", l);
204
+ function ae() {
205
+ const { locale: o } = S(), a = window.__SITE_DATA__, t = u(() => a?.config?.locales || []), e = u(() => a?.config?.defaultLocale || "en"), n = u(() => t.value.length > 1);
206
+ function s(i) {
207
+ o.value = i, localStorage.setItem("x-site-locale", i);
198
208
  }
199
- function i(l) {
200
- return typeof l == "string" ? l : l[o.value] || l[e.value] || Object.values(l)[0] || "";
209
+ function r(i) {
210
+ return typeof i == "string" ? i : i[o.value] || i[e.value] || Object.values(i)[0] || "";
201
211
  }
202
212
  return {
203
213
  locale: o,
204
214
  locales: t,
205
215
  defaultLocale: e,
206
- hasMultipleLocales: a,
216
+ hasMultipleLocales: n,
207
217
  switchLocale: s,
208
- resolveText: i
218
+ resolveText: r
209
219
  };
210
220
  }
211
- const j = {
221
+ const O = {
212
222
  sm: 600,
213
223
  md: 960,
214
224
  lg: 1280,
215
225
  xl: 1920
216
- }, O = {
226
+ }, z = {
217
227
  grid: {
218
228
  default: {
219
229
  areas: ["header", "main", "footer"],
@@ -225,7 +235,7 @@ const j = {
225
235
  main: { type: "slot" },
226
236
  footer: { component: "footer" }
227
237
  }
228
- }, R = {
238
+ }, F = {
229
239
  grid: {
230
240
  default: {
231
241
  areas: ["main"]
@@ -235,75 +245,177 @@ const j = {
235
245
  main: { type: "slot" }
236
246
  }
237
247
  };
238
- function A(o, n) {
248
+ function k(o, a) {
239
249
  const t = [];
240
250
  if (o.areas?.length) {
241
- const e = o.areas.map((a) => `"${a}"`).join(" ");
251
+ const e = o.areas.map((n) => `"${n}"`).join(" ");
242
252
  t.push(`grid-template-areas: ${e}`);
243
253
  }
244
- return o.columns && t.push(`grid-template-columns: ${o.columns}`), o.rows && t.push(`grid-template-rows: ${o.rows}`), o.gap && t.push(`gap: ${o.gap}`), t.length ? `${n} { ${t.join("; ")}; }` : "";
254
+ return o.columns && t.push(`grid-template-columns: ${o.columns}`), o.rows && t.push(`grid-template-rows: ${o.rows}`), o.gap && t.push(`gap: ${o.gap}`), t.length ? `${a} { ${t.join("; ")}; }` : "";
245
255
  }
246
- function F(o, n) {
247
- const t = [`${n} { display: grid; min-height: 100vh; }`], e = o.grid;
256
+ function G(o, a) {
257
+ const t = [`${a} { display: grid; min-height: 100vh; }`], e = o.grid;
248
258
  if (!e) return t.join(`
249
259
  `);
250
260
  if (e.default) {
251
- const a = A(e.default, n);
252
- a && t.push(a);
261
+ const n = k(e.default, a);
262
+ n && t.push(n);
253
263
  }
254
- for (const a of ["sm", "md", "lg", "xl"]) {
255
- const s = e[a];
264
+ for (const n of ["sm", "md", "lg", "xl"]) {
265
+ const s = e[n];
256
266
  if (!s) continue;
257
- const i = A(s, n);
258
- i && t.push(`@media (min-width: ${j[a]}px) { ${i} }`);
267
+ const r = k(s, a);
268
+ r && t.push(`@media (min-width: ${O[n]}px) { ${r} }`);
259
269
  }
260
270
  return t.join(`
261
271
  `);
262
272
  }
263
- function q() {
264
- const o = P(), { previewPage: n, isPreviewMode: t } = C(), e = window.__SITE_DATA__, a = `dl-${Math.random().toString(36).slice(2, 8)}`, s = u(() => ["dynamic-layout", a]), i = u(() => {
273
+ function oe() {
274
+ const o = I(), { previewPage: a, isPreviewMode: t } = C(), e = window.__SITE_DATA__, n = `dl-${Math.random().toString(36).slice(2, 8)}`, s = u(() => ["dynamic-layout", n]), r = u(() => {
265
275
  if (o.path === "/_preview_")
266
- return n.value?.layout || "landing";
267
- if (t.value && n.value?.layout)
268
- return n.value.layout;
269
- const f = o.path === "/" ? "page:home" : `page:${o.params.slug || ""}`;
270
- return e?.[f]?.layout || "default";
271
- }), l = u(() => {
272
- const f = e?.config?.layouts;
273
- if (f) {
274
- const d = f[i.value] || f.default;
275
- if (d?.zones) return d;
276
+ return a.value?.layout || "landing";
277
+ if (t.value && a.value?.layout)
278
+ return a.value.layout;
279
+ const p = o.path === "/" ? "page:home" : `page:${o.params.slug || ""}`;
280
+ return ($[p] || e?.[p])?.layout || "default";
281
+ }), i = u(() => {
282
+ const p = e?.config?.layouts;
283
+ if (p) {
284
+ const m = p[r.value] || p.default;
285
+ if (m?.zones) return m;
276
286
  }
277
- return i.value === "landing" ? R : O;
278
- }), c = u(() => F(l.value, `.${a}`));
287
+ return r.value === "landing" ? F : z;
288
+ }), c = u(() => G(i.value, `.${n}`));
279
289
  return {
280
- layoutName: i,
281
- layoutConfig: l,
290
+ layoutName: r,
291
+ layoutConfig: i,
282
292
  layoutCSS: c,
283
293
  layoutClass: s
284
294
  };
285
295
  }
286
- const M = /\.(js|css|png|jpg|jpeg|gif|svg|ico|woff2?|ttf|eot|webp|avif|map|json|txt|xml|webmanifest)$/;
287
- function k(o, n) {
296
+ const ne = {
297
+ type: "layout",
298
+ displayName: "Layout",
299
+ description: "CSS Grid layout with responsive breakpoints and component zones",
300
+ fields: [
301
+ // Mobile (default)
302
+ { key: "grid.default.areas", type: "grid-areas", label: "Grid Areas (mobile)", breakpoint: "default", required: !0 },
303
+ { key: "grid.default.columns", type: "text", label: "Columns (mobile)", breakpoint: "default" },
304
+ { key: "grid.default.rows", type: "text", label: "Rows (mobile)", breakpoint: "default" },
305
+ { key: "grid.default.gap", type: "text", label: "Gap (mobile)", breakpoint: "default" },
306
+ // Tablet (sm — 600px)
307
+ { key: "grid.sm.areas", type: "grid-areas", label: "Grid Areas (sm 600px+)", breakpoint: "sm" },
308
+ { key: "grid.sm.columns", type: "text", label: "Columns (sm)", breakpoint: "sm" },
309
+ { key: "grid.sm.rows", type: "text", label: "Rows (sm)", breakpoint: "sm" },
310
+ { key: "grid.sm.gap", type: "text", label: "Gap (sm)", breakpoint: "sm" },
311
+ // Desktop (md — 960px)
312
+ { key: "grid.md.areas", type: "grid-areas", label: "Grid Areas (md 960px+)", breakpoint: "md" },
313
+ { key: "grid.md.columns", type: "text", label: "Columns (md)", breakpoint: "md" },
314
+ { key: "grid.md.rows", type: "text", label: "Rows (md)", breakpoint: "md" },
315
+ { key: "grid.md.gap", type: "text", label: "Gap (md)", breakpoint: "md" },
316
+ // Large desktop (lg — 1280px)
317
+ { key: "grid.lg.areas", type: "grid-areas", label: "Grid Areas (lg 1280px+)", breakpoint: "lg" },
318
+ { key: "grid.lg.columns", type: "text", label: "Columns (lg)", breakpoint: "lg" },
319
+ { key: "grid.lg.rows", type: "text", label: "Rows (lg)", breakpoint: "lg" },
320
+ { key: "grid.lg.gap", type: "text", label: "Gap (lg)", breakpoint: "lg" },
321
+ // Zones
322
+ { key: "zones", type: "zone-list", label: "Zones", required: !0 }
323
+ ],
324
+ zoneFields: [
325
+ { key: "type", type: "select", label: "Type", options: ["slot", "component"] },
326
+ { key: "component", type: "text", label: "Component Key" },
327
+ { key: "class", type: "text", label: "CSS Class" },
328
+ { key: "container", type: "switch", label: "Wrap in Container" }
329
+ ]
330
+ }, se = {
331
+ default: {
332
+ displayName: "Full Width",
333
+ config: {
334
+ grid: {
335
+ default: { areas: ["header", "main", "footer"], rows: "auto 1fr auto" }
336
+ },
337
+ zones: {
338
+ header: { component: "nav-menu" },
339
+ main: { type: "slot" },
340
+ footer: { component: "footer" }
341
+ }
342
+ }
343
+ },
344
+ sidebar: {
345
+ displayName: "Sidebar",
346
+ config: {
347
+ grid: {
348
+ default: { areas: ["header", "main", "sidebar", "footer"], columns: "1fr", rows: "auto 1fr auto auto" },
349
+ md: { areas: ["header header", "sidebar main", "footer footer"], columns: "280px 1fr", rows: "auto 1fr auto" }
350
+ },
351
+ zones: {
352
+ header: { component: "nav-menu" },
353
+ sidebar: { component: "sidebar-menu" },
354
+ main: { type: "slot" },
355
+ footer: { component: "footer" }
356
+ }
357
+ }
358
+ },
359
+ article: {
360
+ displayName: "Article",
361
+ config: {
362
+ grid: {
363
+ default: { areas: ["header", "main", "footer"], rows: "auto 1fr auto" }
364
+ },
365
+ zones: {
366
+ header: { component: "nav-menu" },
367
+ main: { type: "slot", container: !0, containerStyle: { "max-width": "900px" } },
368
+ footer: { component: "footer" }
369
+ }
370
+ }
371
+ },
372
+ landing: {
373
+ displayName: "Landing (no nav)",
374
+ config: {
375
+ grid: {
376
+ default: { areas: ["main"] }
377
+ },
378
+ zones: {
379
+ main: { type: "slot" }
380
+ }
381
+ }
382
+ },
383
+ magazine: {
384
+ displayName: "Magazine",
385
+ config: {
386
+ grid: {
387
+ default: { areas: ["header", "main", "aside", "footer"], columns: "1fr", rows: "auto 1fr auto auto" },
388
+ md: { areas: ["header header header", "main main aside", "footer footer footer"], columns: "1fr 1fr 300px", rows: "auto 1fr auto", gap: "24px" }
389
+ },
390
+ zones: {
391
+ header: { component: "nav-menu" },
392
+ main: { type: "slot" },
393
+ aside: { component: "sidebar-menu" },
394
+ footer: { component: "footer" }
395
+ }
396
+ }
397
+ }
398
+ }, M = /\.(js|css|png|jpg|jpeg|gif|svg|ico|woff2?|ttf|eot|webp|avif|map|json|txt|xml|webmanifest)$/;
399
+ function U(o, a) {
288
400
  const e = new URL(o.url).searchParams.get("lang");
289
- if (e && n.supportedLocales?.includes(e)) return e;
290
- if (n.supportedLocales?.length) {
291
- const a = o.headers.get("Accept-Language") || "";
292
- for (const s of n.supportedLocales)
293
- if (a.includes(s)) return s;
401
+ if (e && a.supportedLocales?.includes(e)) return e;
402
+ if (a.supportedLocales?.length) {
403
+ const n = o.headers.get("Accept-Language") || "";
404
+ for (const s of a.supportedLocales)
405
+ if (n.includes(s)) return s;
294
406
  }
295
- return n.defaultLocale;
407
+ return a.defaultLocale;
296
408
  }
297
- function U(o, n, t) {
409
+ function K(o, a, t) {
298
410
  if (!o.pathname.startsWith("/api/content/")) return null;
299
411
  const e = decodeURIComponent(o.pathname.replace("/api/content/", ""));
300
412
  return e ? (async () => {
301
- let a = await n.SITE_CONTENT.get(e);
302
- if (!a) {
413
+ let n = await a.SITE_CONTENT.get(e);
414
+ if (!n) {
303
415
  const s = e.lastIndexOf(":");
304
- s > 0 && e.slice(s + 1) !== t && (a = await n.SITE_CONTENT.get(e.slice(0, s + 1) + t));
416
+ s > 0 && e.slice(s + 1) !== t && (n = await a.SITE_CONTENT.get(e.slice(0, s + 1) + t));
305
417
  }
306
- return a ? new Response(a, {
418
+ return n ? new Response(n, {
307
419
  headers: {
308
420
  "Content-Type": "application/json",
309
421
  "Cache-Control": "public, max-age=60",
@@ -312,53 +424,53 @@ function U(o, n, t) {
312
424
  }) : Response.json({ error: "Not found" }, { status: 404 });
313
425
  })() : Promise.resolve(Response.json({ error: "Key is required" }, { status: 400 }));
314
426
  }
315
- function E(o, n, t) {
427
+ function x(o, a, t) {
316
428
  let e = o;
317
- if (n.title) {
318
- const a = t ? ` — ${t}` : "";
319
- e = e.replace(/<title>[^<]*<\/title>/, `<title>${n.title}${a}</title>`);
429
+ if (a.title) {
430
+ const n = t ? ` — ${t}` : "";
431
+ e = e.replace(/<title>[^<]*<\/title>/, `<title>${a.title}${n}</title>`);
320
432
  }
321
- return n.meta?.description && (e = e.replace(/(<meta\s+name="description"\s+content=")[^"]*(")/, `$1${n.meta.description}$2`)), n.meta?.ogTitle && (e = e.replace(/(<meta\s+property="og:title"\s+content=")[^"]*(")/, `$1${n.meta.ogTitle}$2`)), n.html && (e = e.replace('<div id="app"></div>', `<div id="app">${n.html}</div>`)), e;
433
+ return a.meta?.description && (e = e.replace(/(<meta\s+name="description"\s+content=")[^"]*(")/, `$1${a.meta.description}$2`)), a.meta?.ogTitle && (e = e.replace(/(<meta\s+property="og:title"\s+content=")[^"]*(")/, `$1${a.meta.ogTitle}$2`)), a.html && (e = e.replace('<div id="app"></div>', `<div id="app">${a.html}</div>`)), e;
322
434
  }
323
- function ee(o) {
435
+ function re(o) {
324
436
  return {
325
- async fetch(n, t) {
326
- const e = new URL(n.url), a = U(e, t, o.defaultLocale);
327
- if (a) return a;
437
+ async fetch(a, t) {
438
+ const e = new URL(a.url), n = K(e, t, o.defaultLocale);
439
+ if (n) return n;
328
440
  if (M.test(e.pathname))
329
- return t.ASSETS.fetch(n);
330
- const s = k(n, o), i = new URL("/index.html", n.url);
331
- let c = await (await t.ASSETS.fetch(new Request(i))).text();
332
- const f = o.defaultLocale, [d, v] = await Promise.all([
441
+ return t.ASSETS.fetch(a);
442
+ const s = U(a, o), r = new URL("/index.html", a.url);
443
+ let c = await (await t.ASSETS.fetch(new Request(r))).text();
444
+ const p = o.defaultLocale, [m, g] = await Promise.all([
333
445
  t.SITE_CONTENT.get("config"),
334
446
  t.SITE_CONTENT.get(`content:${s}`)
335
- ]), S = d ? JSON.parse(d) : {};
336
- let g = v ? JSON.parse(v) : {};
337
- if (!v && s !== f) {
338
- const p = await t.SITE_CONTENT.get(`content:${f}`);
339
- p && (g = JSON.parse(p));
447
+ ]), b = m ? JSON.parse(m) : {};
448
+ let y = g ? JSON.parse(g) : {};
449
+ if (!g && s !== p) {
450
+ const f = await t.SITE_CONTENT.get(`content:${p}`);
451
+ f && (y = JSON.parse(f));
340
452
  }
341
453
  const h = e.pathname.match(/^\/p\/(.+)$/);
342
454
  if (h) {
343
- const p = h[1];
344
- if (!g[p]) {
345
- let m = await t.SITE_CONTENT.get(`page:${p}:${s}`);
346
- !m && s !== f && (m = await t.SITE_CONTENT.get(`page:${p}:${f}`)), m && (g[p] = JSON.parse(m));
455
+ const f = h[1];
456
+ if (!y[f]) {
457
+ let d = await t.SITE_CONTENT.get(`page:${f}:${s}`);
458
+ !d && s !== p && (d = await t.SITE_CONTENT.get(`page:${f}:${p}`)), d && (y[f] = JSON.parse(d));
347
459
  }
348
460
  }
349
- const r = {
461
+ const v = {
350
462
  locale: s,
351
- config: S
463
+ config: b
352
464
  };
353
- for (const [p, m] of Object.entries(g))
354
- r[`page:${p}`] = m;
465
+ for (const [f, d] of Object.entries(y))
466
+ v[`page:${f}`] = d;
355
467
  if (h) {
356
- const p = h[1], m = r[`page:${p}`];
357
- m && (c = E(c, m, o.siteName));
468
+ const f = h[1], d = v[`page:${f}`];
469
+ d && (c = x(c, d, o.siteName));
358
470
  }
359
- e.pathname === "/" && g.home && (c = E(c, g.home, o.siteName));
360
- const y = `<script>window.__SITE_DATA__ = ${JSON.stringify(r)};<\/script>`;
361
- return c = c.replace("</head>", `${y}
471
+ e.pathname === "/" && y.home && (c = x(c, y.home, o.siteName));
472
+ const l = `<script>window.__SITE_DATA__ = ${JSON.stringify(v)};<\/script>`;
473
+ return c = c.replace("</head>", `${l}
362
474
  </head>`), new Response(c, {
363
475
  headers: {
364
476
  "Content-Type": "text/html;charset=utf-8",
@@ -369,18 +481,20 @@ function ee(o) {
369
481
  };
370
482
  }
371
483
  export {
372
- D as PAGE_CONTEXT_KEY,
373
- F as buildLayoutCSS,
374
- ee as createSiteWorker,
375
- b as isInPreview,
376
- Z as setupPreviewRouter,
377
- q as useDynamicLayout,
484
+ R as PAGE_CONTEXT_KEY,
485
+ G as buildLayoutCSS,
486
+ re as createSiteWorker,
487
+ A as isInPreview,
488
+ se as layoutPresets,
489
+ ne as layoutSchema,
490
+ V as setupPreviewRouter,
491
+ oe as useDynamicLayout,
378
492
  C as useLivePreview,
379
- V as useLocaleSwitcher,
380
- H as usePageData,
381
- X as useSiteApi,
382
- Q as useSiteComponents,
383
- G as useSiteConfig,
384
- B as useSiteData,
385
- Y as useSkin
493
+ ae as useLocaleSwitcher,
494
+ ee as usePageData,
495
+ Q as useSiteApi,
496
+ te as useSiteComponents,
497
+ q as useSiteConfig,
498
+ Z as useSiteData,
499
+ H as useSkin
386
500
  };
@@ -12,9 +12,12 @@ export interface Block<T = any> {
12
12
  settings?: BlockSettings & Record<string, any>;
13
13
  }
14
14
  export interface HeroBlockData {
15
+ variant?: 'default' | 'minimal' | 'video';
15
16
  title: string;
16
17
  subtitle?: string;
18
+ badge?: string;
17
19
  image?: string;
20
+ backgroundImage?: string;
18
21
  overlay?: boolean;
19
22
  buttons?: Array<{
20
23
  text: string;
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Layout schema definition for the editor.
3
+ * Describes the editable structure of a layout config.
4
+ */
5
+ export interface GridBreakpoint {
6
+ areas?: string[];
7
+ columns?: string;
8
+ rows?: string;
9
+ gap?: string;
10
+ }
11
+ export interface LayoutZone {
12
+ type?: 'slot';
13
+ component?: string;
14
+ class?: string;
15
+ container?: boolean;
16
+ containerStyle?: Record<string, string>;
17
+ }
18
+ export interface LayoutConfig {
19
+ grid?: Record<string, GridBreakpoint>;
20
+ zones: Record<string, LayoutZone>;
21
+ }
22
+ export interface LayoutField {
23
+ key: string;
24
+ type: 'text' | 'grid-areas' | 'select' | 'switch' | 'zone-list';
25
+ label: string;
26
+ breakpoint?: string;
27
+ options?: string[];
28
+ required?: boolean;
29
+ }
30
+ export interface LayoutSchemaDefinition {
31
+ type: string;
32
+ displayName: string;
33
+ description?: string;
34
+ fields: LayoutField[];
35
+ zoneFields: LayoutField[];
36
+ }
37
+ /**
38
+ * Schema describing how to edit a layout in the editor.
39
+ * Used to render a form for creating/editing layout configs.
40
+ */
41
+ export declare const layoutSchema: LayoutSchemaDefinition;
42
+ /**
43
+ * Built-in layout presets for quick layout creation.
44
+ */
45
+ export declare const layoutPresets: Record<string, {
46
+ displayName: string;
47
+ config: LayoutConfig;
48
+ }>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xosen/site-sdk",
3
- "version": "0.0.12",
3
+ "version": "0.0.14",
4
4
  "description": "Shared Vue components and composables for Xosen site templates",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -0,0 +1,24 @@
1
+ import { reactive } from 'vue';
2
+
3
+ /**
4
+ * Shared reactive store for page data loaded via usePageData.
5
+ * Allows useDynamicLayout to reactively read layout from fetched pages.
6
+ */
7
+ export const pageDataStore = reactive<Record<string, any>>({});
8
+
9
+ /**
10
+ * Initialize store with preloaded data from __SITE_DATA__.
11
+ */
12
+ export function initPageDataStore() {
13
+ const siteData = (window as any).__SITE_DATA__;
14
+ if (!siteData) return;
15
+
16
+ for (const key of Object.keys(siteData)) {
17
+ if (key.startsWith('page:')) {
18
+ pageDataStore[key] = siteData[key];
19
+ }
20
+ }
21
+ }
22
+
23
+ // Auto-init on import
24
+ initPageDataStore();
@@ -1,28 +1,10 @@
1
- import { computed, type Ref } from 'vue';
1
+ import { computed } from 'vue';
2
2
  import { useRoute } from 'vue-router';
3
3
 
4
4
  import type { SiteData } from '../types/config.js';
5
+ import type { GridBreakpoint, LayoutConfig } from '../types/layout.js';
5
6
  import { useLivePreview } from './useLivePreview.js';
6
-
7
- export interface GridBreakpoint {
8
- areas?: string[];
9
- columns?: string;
10
- rows?: string;
11
- gap?: string;
12
- }
13
-
14
- export interface LayoutZone {
15
- type?: 'slot';
16
- component?: string;
17
- class?: string;
18
- container?: boolean;
19
- containerStyle?: Record<string, string>;
20
- }
21
-
22
- export interface LayoutConfig {
23
- grid?: Record<string, GridBreakpoint>;
24
- zones: Record<string, LayoutZone>;
25
- }
7
+ import { pageDataStore } from './pageDataStore.js';
26
8
 
27
9
  const BREAKPOINTS: Record<string, number> = {
28
10
  sm: 600,
@@ -121,7 +103,8 @@ export function useDynamicLayout() {
121
103
  return previewPage.value.layout;
122
104
  }
123
105
  const pageKey = route.path === '/' ? 'page:home' : `page:${route.params.slug || ''}`;
124
- const pageData = siteData?.[pageKey];
106
+ // Read from reactive store (updated by usePageData on fetch)
107
+ const pageData = pageDataStore[pageKey] || siteData?.[pageKey];
125
108
  return pageData?.layout || 'default';
126
109
  });
127
110
 
@@ -5,6 +5,7 @@ import type { Block } from '../types/blocks.js';
5
5
  import type { PageConfig, PageContext, SiteData } from '../types/config.js';
6
6
  import { PAGE_CONTEXT_KEY } from '../types/config.js';
7
7
  import { useLivePreview } from './useLivePreview.js';
8
+ import { pageDataStore } from './pageDataStore.js';
8
9
 
9
10
  export interface UsePageDataReturn {
10
11
  /** Resolved page data (from preload, API, or preview) */
@@ -46,7 +47,7 @@ export function usePageData(slug: Ref<string> | string): UsePageDataReturn {
46
47
  const pageContext = computed<PageContext>(() => {
47
48
  const p = isPreviewMode.value ? previewPage.value : page.value;
48
49
  if (!p) return {};
49
- const { blocks, meta, slug: _s, locale: _l, ...rest } = p as any;
50
+ const { blocks: _blocks, meta: _meta, slug: _s, locale: _l, ...rest } = p as any;
50
51
  return rest;
51
52
  });
52
53
 
@@ -61,10 +62,17 @@ export function usePageData(slug: Ref<string> | string): UsePageDataReturn {
61
62
  return page.value?.blocks || [];
62
63
  });
63
64
 
65
+ function setPage(data: PageConfig | null) {
66
+ page.value = data;
67
+ if (data) {
68
+ pageDataStore[pageKey.value] = data;
69
+ }
70
+ }
71
+
64
72
  function loadFromPreloaded(): boolean {
65
73
  const preloaded = siteData?.[pageKey.value];
66
74
  if (preloaded && locale.value === siteData?.locale) {
67
- page.value = preloaded as PageConfig;
75
+ setPage(preloaded as PageConfig);
68
76
  notFound.value = false;
69
77
  applyMeta();
70
78
  return true;
@@ -78,14 +86,14 @@ export function usePageData(slug: Ref<string> | string): UsePageDataReturn {
78
86
  try {
79
87
  const res = await fetch(`/api/content/${pageKey.value}:${locale.value}`);
80
88
  if (res.ok) {
81
- page.value = await res.json();
89
+ setPage(await res.json());
82
90
  applyMeta();
83
91
  } else {
84
- page.value = null;
92
+ setPage(null);
85
93
  notFound.value = true;
86
94
  }
87
95
  } catch {
88
- page.value = null;
96
+ setPage(null);
89
97
  notFound.value = true;
90
98
  } finally {
91
99
  loading.value = false;
package/src/index.ts CHANGED
@@ -9,7 +9,8 @@ export type { UsePageDataReturn } from './composables/usePageData.js';
9
9
  export { useSiteComponents } from './composables/useSiteComponents.js';
10
10
  export { useLocaleSwitcher } from './composables/useLocaleSwitcher.js';
11
11
  export { useDynamicLayout, buildLayoutCSS } from './composables/useDynamicLayout.js';
12
- export type { LayoutConfig, LayoutZone, GridBreakpoint } from './composables/useDynamicLayout.js';
12
+ export type { LayoutConfig, LayoutZone, GridBreakpoint, LayoutSchemaDefinition, LayoutField } from './types/layout.js';
13
+ export { layoutSchema, layoutPresets } from './types/layout.js';
13
14
  export type { PreviewMessage } from './composables/useLivePreview.js';
14
15
 
15
16
  // Worker
@@ -14,9 +14,12 @@ export interface Block<T = any> {
14
14
  }
15
15
 
16
16
  export interface HeroBlockData {
17
+ variant?: 'default' | 'minimal' | 'video';
17
18
  title: string;
18
19
  subtitle?: string;
20
+ badge?: string;
19
21
  image?: string;
22
+ backgroundImage?: string;
20
23
  overlay?: boolean;
21
24
  buttons?: Array<{ text: string; url: string; variant?: 'primary' | 'outline' | 'white' }>;
22
25
  stats?: Array<{ value: string; label: string }>;
@@ -0,0 +1,158 @@
1
+ /**
2
+ * Layout schema definition for the editor.
3
+ * Describes the editable structure of a layout config.
4
+ */
5
+
6
+ export interface GridBreakpoint {
7
+ areas?: string[];
8
+ columns?: string;
9
+ rows?: string;
10
+ gap?: string;
11
+ }
12
+
13
+ export interface LayoutZone {
14
+ type?: 'slot';
15
+ component?: string;
16
+ class?: string;
17
+ container?: boolean;
18
+ containerStyle?: Record<string, string>;
19
+ }
20
+
21
+ export interface LayoutConfig {
22
+ grid?: Record<string, GridBreakpoint>;
23
+ zones: Record<string, LayoutZone>;
24
+ }
25
+
26
+ export interface LayoutField {
27
+ key: string;
28
+ type: 'text' | 'grid-areas' | 'select' | 'switch' | 'zone-list';
29
+ label: string;
30
+ breakpoint?: string;
31
+ options?: string[];
32
+ required?: boolean;
33
+ }
34
+
35
+ export interface LayoutSchemaDefinition {
36
+ type: string;
37
+ displayName: string;
38
+ description?: string;
39
+ fields: LayoutField[];
40
+ zoneFields: LayoutField[];
41
+ }
42
+
43
+ /**
44
+ * Schema describing how to edit a layout in the editor.
45
+ * Used to render a form for creating/editing layout configs.
46
+ */
47
+ export const layoutSchema: LayoutSchemaDefinition = {
48
+ type: 'layout',
49
+ displayName: 'Layout',
50
+ description: 'CSS Grid layout with responsive breakpoints and component zones',
51
+ fields: [
52
+ // Mobile (default)
53
+ { key: 'grid.default.areas', type: 'grid-areas', label: 'Grid Areas (mobile)', breakpoint: 'default', required: true },
54
+ { key: 'grid.default.columns', type: 'text', label: 'Columns (mobile)', breakpoint: 'default' },
55
+ { key: 'grid.default.rows', type: 'text', label: 'Rows (mobile)', breakpoint: 'default' },
56
+ { key: 'grid.default.gap', type: 'text', label: 'Gap (mobile)', breakpoint: 'default' },
57
+
58
+ // Tablet (sm — 600px)
59
+ { key: 'grid.sm.areas', type: 'grid-areas', label: 'Grid Areas (sm 600px+)', breakpoint: 'sm' },
60
+ { key: 'grid.sm.columns', type: 'text', label: 'Columns (sm)', breakpoint: 'sm' },
61
+ { key: 'grid.sm.rows', type: 'text', label: 'Rows (sm)', breakpoint: 'sm' },
62
+ { key: 'grid.sm.gap', type: 'text', label: 'Gap (sm)', breakpoint: 'sm' },
63
+
64
+ // Desktop (md — 960px)
65
+ { key: 'grid.md.areas', type: 'grid-areas', label: 'Grid Areas (md 960px+)', breakpoint: 'md' },
66
+ { key: 'grid.md.columns', type: 'text', label: 'Columns (md)', breakpoint: 'md' },
67
+ { key: 'grid.md.rows', type: 'text', label: 'Rows (md)', breakpoint: 'md' },
68
+ { key: 'grid.md.gap', type: 'text', label: 'Gap (md)', breakpoint: 'md' },
69
+
70
+ // Large desktop (lg — 1280px)
71
+ { key: 'grid.lg.areas', type: 'grid-areas', label: 'Grid Areas (lg 1280px+)', breakpoint: 'lg' },
72
+ { key: 'grid.lg.columns', type: 'text', label: 'Columns (lg)', breakpoint: 'lg' },
73
+ { key: 'grid.lg.rows', type: 'text', label: 'Rows (lg)', breakpoint: 'lg' },
74
+ { key: 'grid.lg.gap', type: 'text', label: 'Gap (lg)', breakpoint: 'lg' },
75
+
76
+ // Zones
77
+ { key: 'zones', type: 'zone-list', label: 'Zones', required: true },
78
+ ],
79
+ zoneFields: [
80
+ { key: 'type', type: 'select', label: 'Type', options: ['slot', 'component'] },
81
+ { key: 'component', type: 'text', label: 'Component Key' },
82
+ { key: 'class', type: 'text', label: 'CSS Class' },
83
+ { key: 'container', type: 'switch', label: 'Wrap in Container' },
84
+ ],
85
+ };
86
+
87
+ /**
88
+ * Built-in layout presets for quick layout creation.
89
+ */
90
+ export const layoutPresets: Record<string, { displayName: string; config: LayoutConfig }> = {
91
+ default: {
92
+ displayName: 'Full Width',
93
+ config: {
94
+ grid: {
95
+ default: { areas: ['header', 'main', 'footer'], rows: 'auto 1fr auto' },
96
+ },
97
+ zones: {
98
+ header: { component: 'nav-menu' },
99
+ main: { type: 'slot' },
100
+ footer: { component: 'footer' },
101
+ },
102
+ },
103
+ },
104
+ sidebar: {
105
+ displayName: 'Sidebar',
106
+ config: {
107
+ grid: {
108
+ default: { areas: ['header', 'main', 'sidebar', 'footer'], columns: '1fr', rows: 'auto 1fr auto auto' },
109
+ md: { areas: ['header header', 'sidebar main', 'footer footer'], columns: '280px 1fr', rows: 'auto 1fr auto' },
110
+ },
111
+ zones: {
112
+ header: { component: 'nav-menu' },
113
+ sidebar: { component: 'sidebar-menu' },
114
+ main: { type: 'slot' },
115
+ footer: { component: 'footer' },
116
+ },
117
+ },
118
+ },
119
+ article: {
120
+ displayName: 'Article',
121
+ config: {
122
+ grid: {
123
+ default: { areas: ['header', 'main', 'footer'], rows: 'auto 1fr auto' },
124
+ },
125
+ zones: {
126
+ header: { component: 'nav-menu' },
127
+ main: { type: 'slot', container: true, containerStyle: { 'max-width': '900px' } },
128
+ footer: { component: 'footer' },
129
+ },
130
+ },
131
+ },
132
+ landing: {
133
+ displayName: 'Landing (no nav)',
134
+ config: {
135
+ grid: {
136
+ default: { areas: ['main'] },
137
+ },
138
+ zones: {
139
+ main: { type: 'slot' },
140
+ },
141
+ },
142
+ },
143
+ magazine: {
144
+ displayName: 'Magazine',
145
+ config: {
146
+ grid: {
147
+ default: { areas: ['header', 'main', 'aside', 'footer'], columns: '1fr', rows: 'auto 1fr auto auto' },
148
+ md: { areas: ['header header header', 'main main aside', 'footer footer footer'], columns: '1fr 1fr 300px', rows: 'auto 1fr auto', gap: '24px' },
149
+ },
150
+ zones: {
151
+ header: { component: 'nav-menu' },
152
+ main: { type: 'slot' },
153
+ aside: { component: 'sidebar-menu' },
154
+ footer: { component: 'footer' },
155
+ },
156
+ },
157
+ },
158
+ };