@xosen/site-sdk 0.0.12 → 0.0.13

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.
@@ -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,87 +1,87 @@
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 _, watch as b, computed as c, onUnmounted as E, provide as N } from "vue";
2
+ import { useI18n as S } from "vue-i18n";
3
+ import { useRoute as L } from "vue-router";
4
+ function J(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}`);
12
- c.ok && (t.value = await c.json());
11
+ const u = await fetch(`/api/content/${a}:${e.value}`);
12
+ u.ok && (t.value = await u.json());
13
13
  } catch {
14
14
  }
15
15
  }
16
- return T(() => {
17
- i() || l();
18
- }), _(e, () => {
19
- l();
16
+ return _(() => {
17
+ r() || i();
18
+ }), b(e, () => {
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 || {});
22
+ function B() {
23
+ const o = window.__SITE_DATA__, a = c(() => o?.config || null), t = c(() => o?.components || {}), e = c(() => a.value?.navigation || []), n = c(() => a.value?.locales || []), s = c(() => a.value?.defaultLocale || "en"), r = c(() => a.value?.branding || {}), i = c(() => a.value?.footer || {}), u = c(() => a.value?.features || {});
24
24
  function f(d) {
25
- const v = c.value[d];
26
- return typeof v == "boolean" ? v : typeof v == "object";
25
+ const y = u.value[d];
26
+ return typeof y == "boolean" ? y : typeof y == "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,
36
- features: c,
34
+ branding: r,
35
+ footer: i,
36
+ features: u,
37
37
  hasFeature: f
38
38
  };
39
39
  }
40
40
  function Y(o) {
41
- function n(t) {
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-${P(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
- T(() => {
48
+ _(() => {
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 P(o) {
62
62
  return o.replace(/([a-z])([A-Z])/g, "$1-$2").toLowerCase();
63
63
  }
64
64
  function X() {
65
- const n = window.__SITE_DATA__?.apiBase || "";
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 x() {
85
85
  try {
86
86
  return window.self !== window.top;
87
87
  } catch {
@@ -89,98 +89,98 @@ function b() {
89
89
  }
90
90
  }
91
91
  function Z(o) {
92
- b() && o.beforeEach((n, t) => !t.name);
92
+ x() && o.beforeEach((a, t) => !t.name);
93
93
  }
94
- function C() {
95
- const o = w(null), n = w(b());
94
+ function A() {
95
+ const o = w(null), a = w(x());
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
- return T(() => {
100
+ return _(() => {
101
101
  window.addEventListener("message", t);
102
- }), N(() => {
102
+ }), E(() => {
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 I = /* @__PURE__ */ Symbol("pageContext");
110
+ function q(o) {
111
+ const { locale: a } = S(), { previewPage: t, isPreviewMode: e } = A(), n = window.__SITE_DATA__, s = w(null), r = w(!0), i = w(!1), u = c(() => typeof o == "string" ? o : o.value), f = c(() => `page:${u.value}`), d = c(() => {
112
+ const l = e.value ? t.value : s.value;
113
+ if (!l) return {};
114
+ const { blocks: v, meta: p, slug: g, locale: M, ...C } = l;
115
+ return C;
116
116
  });
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;
117
+ N(I, d);
118
+ const y = c(() => e.value && t.value?.slug === u.value ? t.value.blocks || [] : s.value?.blocks || []);
119
+ function T() {
120
+ const l = n?.[f.value];
121
+ return l && a.value === n?.locale ? (s.value = l, i.value = !1, h(), !0) : !1;
122
122
  }
123
- async function g() {
124
- i.value = !0, l.value = !1;
123
+ async function m() {
124
+ r.value = !0, i.value = !1;
125
125
  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);
126
+ const l = await fetch(`/api/content/${f.value}:${a.value}`);
127
+ l.ok ? (s.value = await l.json(), h()) : (s.value = null, i.value = !0);
128
128
  } catch {
129
- s.value = null, l.value = !0;
129
+ s.value = null, i.value = !0;
130
130
  } finally {
131
- i.value = !1;
131
+ r.value = !1;
132
132
  }
133
133
  }
134
134
  function h() {
135
135
  if (!s.value) return;
136
136
  if (s.value.title) {
137
- const y = a?.config?.branding, p = y?.siteName ? ` — ${y.siteName}` : "";
137
+ const v = n?.config?.branding, p = v?.siteName ? ` — ${v.siteName}` : "";
138
138
  document.title = `${s.value.title}${p}`;
139
139
  }
140
- const r = s.value.meta?.description;
141
- if (r) {
142
- const y = document.querySelector('meta[name="description"]');
143
- y && y.setAttribute("content", r);
140
+ const l = s.value.meta?.description;
141
+ if (l) {
142
+ const v = document.querySelector('meta[name="description"]');
143
+ v && v.setAttribute("content", l);
144
144
  }
145
145
  }
146
- return _(t, (r) => {
147
- r?.slug === c.value && (i.value = !1, l.value = !1, (r.title || r.blocks) && (s.value = {
146
+ return b(t, (l) => {
147
+ l?.slug === u.value && (r.value = !1, i.value = !1, (l.title || l.blocks) && (s.value = {
148
148
  ...s.value || {},
149
- ...r.title ? { title: r.title } : {},
150
- ...r.blocks ? { blocks: r.blocks } : {}
149
+ ...l.title ? { title: l.title } : {},
150
+ ...l.blocks ? { blocks: l.blocks } : {}
151
151
  }, h()));
152
- }), T(() => {
153
- S() ? i.value = !1 : g();
154
- }), _(n, () => g()), _(c, () => {
155
- S() ? i.value = !1 : g();
152
+ }), _(() => {
153
+ T() ? r.value = !1 : m();
154
+ }), b(a, () => m()), b(u, () => {
155
+ T() ? r.value = !1 : m();
156
156
  }), {
157
157
  page: s,
158
- blocks: v,
158
+ blocks: y,
159
159
  pageContext: d,
160
- loading: i,
161
- notFound: l,
160
+ loading: r,
161
+ notFound: i,
162
162
  isPreviewMode: e,
163
- reload: g
163
+ reload: m
164
164
  };
165
165
  }
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 || {} };
166
+ function H() {
167
+ const { locale: o } = S(), a = window.__SITE_DATA__, t = a?.locale, e = w({ ...a?.components || {} });
168
+ async function n(r) {
169
+ if (r === t) {
170
+ e.value = { ...a?.components || {} };
171
171
  return;
172
172
  }
173
173
  try {
174
- const l = await fetch(`/api/content/components:${i}`);
175
- l.ok && (e.value = await l.json());
174
+ const i = await fetch(`/api/content/components:${r}`);
175
+ i.ok && (e.value = await i.json());
176
176
  } catch {
177
177
  }
178
178
  }
179
- _(o, (i) => a(i)), T(() => {
180
- o.value !== t && a(o.value);
179
+ b(o, (r) => n(r)), _(() => {
180
+ o.value !== t && n(o.value);
181
181
  });
182
- function s(i) {
183
- return u(() => e.value[i] || null);
182
+ function s(r) {
183
+ return c(() => e.value[r] || null);
184
184
  }
185
185
  return {
186
186
  /** All components (reactive, locale-aware) */
@@ -188,32 +188,32 @@ function Q() {
188
188
  /** Get a single component by key */
189
189
  getComponent: s,
190
190
  /** Force refetch for current locale */
191
- reload: () => a(o.value)
191
+ reload: () => n(o.value)
192
192
  };
193
193
  }
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);
194
+ function Q() {
195
+ const { locale: o } = S(), a = window.__SITE_DATA__, t = c(() => a?.config?.locales || []), e = c(() => a?.config?.defaultLocale || "en"), n = c(() => t.value.length > 1);
196
+ function s(i) {
197
+ o.value = i, localStorage.setItem("x-site-locale", i);
198
198
  }
199
- function i(l) {
200
- return typeof l == "string" ? l : l[o.value] || l[e.value] || Object.values(l)[0] || "";
199
+ function r(i) {
200
+ return typeof i == "string" ? i : i[o.value] || i[e.value] || Object.values(i)[0] || "";
201
201
  }
202
202
  return {
203
203
  locale: o,
204
204
  locales: t,
205
205
  defaultLocale: e,
206
- hasMultipleLocales: a,
206
+ hasMultipleLocales: n,
207
207
  switchLocale: s,
208
- resolveText: i
208
+ resolveText: r
209
209
  };
210
210
  }
211
- const j = {
211
+ const R = {
212
212
  sm: 600,
213
213
  md: 960,
214
214
  lg: 1280,
215
215
  xl: 1920
216
- }, O = {
216
+ }, D = {
217
217
  grid: {
218
218
  default: {
219
219
  areas: ["header", "main", "footer"],
@@ -225,7 +225,7 @@ const j = {
225
225
  main: { type: "slot" },
226
226
  footer: { component: "footer" }
227
227
  }
228
- }, R = {
228
+ }, j = {
229
229
  grid: {
230
230
  default: {
231
231
  areas: ["main"]
@@ -235,75 +235,177 @@ const j = {
235
235
  main: { type: "slot" }
236
236
  }
237
237
  };
238
- function A(o, n) {
238
+ function $(o, a) {
239
239
  const t = [];
240
240
  if (o.areas?.length) {
241
- const e = o.areas.map((a) => `"${a}"`).join(" ");
241
+ const e = o.areas.map((n) => `"${n}"`).join(" ");
242
242
  t.push(`grid-template-areas: ${e}`);
243
243
  }
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("; ")}; }` : "";
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 ? `${a} { ${t.join("; ")}; }` : "";
245
245
  }
246
- function F(o, n) {
247
- const t = [`${n} { display: grid; min-height: 100vh; }`], e = o.grid;
246
+ function O(o, a) {
247
+ const t = [`${a} { display: grid; min-height: 100vh; }`], e = o.grid;
248
248
  if (!e) return t.join(`
249
249
  `);
250
250
  if (e.default) {
251
- const a = A(e.default, n);
252
- a && t.push(a);
251
+ const n = $(e.default, a);
252
+ n && t.push(n);
253
253
  }
254
- for (const a of ["sm", "md", "lg", "xl"]) {
255
- const s = e[a];
254
+ for (const n of ["sm", "md", "lg", "xl"]) {
255
+ const s = e[n];
256
256
  if (!s) continue;
257
- const i = A(s, n);
258
- i && t.push(`@media (min-width: ${j[a]}px) { ${i} }`);
257
+ const r = $(s, a);
258
+ r && t.push(`@media (min-width: ${R[n]}px) { ${r} }`);
259
259
  }
260
260
  return t.join(`
261
261
  `);
262
262
  }
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(() => {
263
+ function V() {
264
+ const o = L(), { previewPage: a, isPreviewMode: t } = A(), e = window.__SITE_DATA__, n = `dl-${Math.random().toString(36).slice(2, 8)}`, s = c(() => ["dynamic-layout", n]), r = c(() => {
265
265
  if (o.path === "/_preview_")
266
- return n.value?.layout || "landing";
267
- if (t.value && n.value?.layout)
268
- return n.value.layout;
266
+ return a.value?.layout || "landing";
267
+ if (t.value && a.value?.layout)
268
+ return a.value.layout;
269
269
  const f = o.path === "/" ? "page:home" : `page:${o.params.slug || ""}`;
270
270
  return e?.[f]?.layout || "default";
271
- }), l = u(() => {
271
+ }), i = c(() => {
272
272
  const f = e?.config?.layouts;
273
273
  if (f) {
274
- const d = f[i.value] || f.default;
274
+ const d = f[r.value] || f.default;
275
275
  if (d?.zones) return d;
276
276
  }
277
- return i.value === "landing" ? R : O;
278
- }), c = u(() => F(l.value, `.${a}`));
277
+ return r.value === "landing" ? j : D;
278
+ }), u = c(() => O(i.value, `.${n}`));
279
279
  return {
280
- layoutName: i,
281
- layoutConfig: l,
282
- layoutCSS: c,
280
+ layoutName: r,
281
+ layoutConfig: i,
282
+ layoutCSS: u,
283
283
  layoutClass: s
284
284
  };
285
285
  }
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) {
286
+ const ee = {
287
+ type: "layout",
288
+ displayName: "Layout",
289
+ description: "CSS Grid layout with responsive breakpoints and component zones",
290
+ fields: [
291
+ // Mobile (default)
292
+ { key: "grid.default.areas", type: "grid-areas", label: "Grid Areas (mobile)", breakpoint: "default", required: !0 },
293
+ { key: "grid.default.columns", type: "text", label: "Columns (mobile)", breakpoint: "default" },
294
+ { key: "grid.default.rows", type: "text", label: "Rows (mobile)", breakpoint: "default" },
295
+ { key: "grid.default.gap", type: "text", label: "Gap (mobile)", breakpoint: "default" },
296
+ // Tablet (sm — 600px)
297
+ { key: "grid.sm.areas", type: "grid-areas", label: "Grid Areas (sm 600px+)", breakpoint: "sm" },
298
+ { key: "grid.sm.columns", type: "text", label: "Columns (sm)", breakpoint: "sm" },
299
+ { key: "grid.sm.rows", type: "text", label: "Rows (sm)", breakpoint: "sm" },
300
+ { key: "grid.sm.gap", type: "text", label: "Gap (sm)", breakpoint: "sm" },
301
+ // Desktop (md — 960px)
302
+ { key: "grid.md.areas", type: "grid-areas", label: "Grid Areas (md 960px+)", breakpoint: "md" },
303
+ { key: "grid.md.columns", type: "text", label: "Columns (md)", breakpoint: "md" },
304
+ { key: "grid.md.rows", type: "text", label: "Rows (md)", breakpoint: "md" },
305
+ { key: "grid.md.gap", type: "text", label: "Gap (md)", breakpoint: "md" },
306
+ // Large desktop (lg — 1280px)
307
+ { key: "grid.lg.areas", type: "grid-areas", label: "Grid Areas (lg 1280px+)", breakpoint: "lg" },
308
+ { key: "grid.lg.columns", type: "text", label: "Columns (lg)", breakpoint: "lg" },
309
+ { key: "grid.lg.rows", type: "text", label: "Rows (lg)", breakpoint: "lg" },
310
+ { key: "grid.lg.gap", type: "text", label: "Gap (lg)", breakpoint: "lg" },
311
+ // Zones
312
+ { key: "zones", type: "zone-list", label: "Zones", required: !0 }
313
+ ],
314
+ zoneFields: [
315
+ { key: "type", type: "select", label: "Type", options: ["slot", "component"] },
316
+ { key: "component", type: "text", label: "Component Key" },
317
+ { key: "class", type: "text", label: "CSS Class" },
318
+ { key: "container", type: "switch", label: "Wrap in Container" }
319
+ ]
320
+ }, te = {
321
+ default: {
322
+ displayName: "Full Width",
323
+ config: {
324
+ grid: {
325
+ default: { areas: ["header", "main", "footer"], rows: "auto 1fr auto" }
326
+ },
327
+ zones: {
328
+ header: { component: "nav-menu" },
329
+ main: { type: "slot" },
330
+ footer: { component: "footer" }
331
+ }
332
+ }
333
+ },
334
+ sidebar: {
335
+ displayName: "Sidebar",
336
+ config: {
337
+ grid: {
338
+ default: { areas: ["header", "main", "sidebar", "footer"], columns: "1fr", rows: "auto 1fr auto auto" },
339
+ md: { areas: ["header header", "sidebar main", "footer footer"], columns: "280px 1fr", rows: "auto 1fr auto" }
340
+ },
341
+ zones: {
342
+ header: { component: "nav-menu" },
343
+ sidebar: { component: "sidebar-menu" },
344
+ main: { type: "slot" },
345
+ footer: { component: "footer" }
346
+ }
347
+ }
348
+ },
349
+ article: {
350
+ displayName: "Article",
351
+ config: {
352
+ grid: {
353
+ default: { areas: ["header", "main", "footer"], rows: "auto 1fr auto" }
354
+ },
355
+ zones: {
356
+ header: { component: "nav-menu" },
357
+ main: { type: "slot", container: !0, containerStyle: { "max-width": "900px" } },
358
+ footer: { component: "footer" }
359
+ }
360
+ }
361
+ },
362
+ landing: {
363
+ displayName: "Landing (no nav)",
364
+ config: {
365
+ grid: {
366
+ default: { areas: ["main"] }
367
+ },
368
+ zones: {
369
+ main: { type: "slot" }
370
+ }
371
+ }
372
+ },
373
+ magazine: {
374
+ displayName: "Magazine",
375
+ config: {
376
+ grid: {
377
+ default: { areas: ["header", "main", "aside", "footer"], columns: "1fr", rows: "auto 1fr auto auto" },
378
+ md: { areas: ["header header header", "main main aside", "footer footer footer"], columns: "1fr 1fr 300px", rows: "auto 1fr auto", gap: "24px" }
379
+ },
380
+ zones: {
381
+ header: { component: "nav-menu" },
382
+ main: { type: "slot" },
383
+ aside: { component: "sidebar-menu" },
384
+ footer: { component: "footer" }
385
+ }
386
+ }
387
+ }
388
+ }, z = /\.(js|css|png|jpg|jpeg|gif|svg|ico|woff2?|ttf|eot|webp|avif|map|json|txt|xml|webmanifest)$/;
389
+ function F(o, a) {
288
390
  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;
391
+ if (e && a.supportedLocales?.includes(e)) return e;
392
+ if (a.supportedLocales?.length) {
393
+ const n = o.headers.get("Accept-Language") || "";
394
+ for (const s of a.supportedLocales)
395
+ if (n.includes(s)) return s;
294
396
  }
295
- return n.defaultLocale;
397
+ return a.defaultLocale;
296
398
  }
297
- function U(o, n, t) {
399
+ function G(o, a, t) {
298
400
  if (!o.pathname.startsWith("/api/content/")) return null;
299
401
  const e = decodeURIComponent(o.pathname.replace("/api/content/", ""));
300
402
  return e ? (async () => {
301
- let a = await n.SITE_CONTENT.get(e);
302
- if (!a) {
403
+ let n = await a.SITE_CONTENT.get(e);
404
+ if (!n) {
303
405
  const s = e.lastIndexOf(":");
304
- s > 0 && e.slice(s + 1) !== t && (a = await n.SITE_CONTENT.get(e.slice(0, s + 1) + t));
406
+ s > 0 && e.slice(s + 1) !== t && (n = await a.SITE_CONTENT.get(e.slice(0, s + 1) + t));
305
407
  }
306
- return a ? new Response(a, {
408
+ return n ? new Response(n, {
307
409
  headers: {
308
410
  "Content-Type": "application/json",
309
411
  "Cache-Control": "public, max-age=60",
@@ -312,54 +414,54 @@ function U(o, n, t) {
312
414
  }) : Response.json({ error: "Not found" }, { status: 404 });
313
415
  })() : Promise.resolve(Response.json({ error: "Key is required" }, { status: 400 }));
314
416
  }
315
- function E(o, n, t) {
417
+ function k(o, a, t) {
316
418
  let e = o;
317
- if (n.title) {
318
- const a = t ? ` — ${t}` : "";
319
- e = e.replace(/<title>[^<]*<\/title>/, `<title>${n.title}${a}</title>`);
419
+ if (a.title) {
420
+ const n = t ? ` — ${t}` : "";
421
+ e = e.replace(/<title>[^<]*<\/title>/, `<title>${a.title}${n}</title>`);
320
422
  }
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;
423
+ 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
424
  }
323
- function ee(o) {
425
+ function ae(o) {
324
426
  return {
325
- async fetch(n, t) {
326
- const e = new URL(n.url), a = U(e, t, o.defaultLocale);
327
- if (a) return a;
328
- 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([
427
+ async fetch(a, t) {
428
+ const e = new URL(a.url), n = G(e, t, o.defaultLocale);
429
+ if (n) return n;
430
+ if (z.test(e.pathname))
431
+ return t.ASSETS.fetch(a);
432
+ const s = F(a, o), r = new URL("/index.html", a.url);
433
+ let u = await (await t.ASSETS.fetch(new Request(r))).text();
434
+ const f = o.defaultLocale, [d, y] = await Promise.all([
333
435
  t.SITE_CONTENT.get("config"),
334
436
  t.SITE_CONTENT.get(`content:${s}`)
335
- ]), S = d ? JSON.parse(d) : {};
336
- let g = v ? JSON.parse(v) : {};
337
- if (!v && s !== f) {
437
+ ]), T = d ? JSON.parse(d) : {};
438
+ let m = y ? JSON.parse(y) : {};
439
+ if (!y && s !== f) {
338
440
  const p = await t.SITE_CONTENT.get(`content:${f}`);
339
- p && (g = JSON.parse(p));
441
+ p && (m = JSON.parse(p));
340
442
  }
341
443
  const h = e.pathname.match(/^\/p\/(.+)$/);
342
444
  if (h) {
343
445
  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));
446
+ if (!m[p]) {
447
+ let g = await t.SITE_CONTENT.get(`page:${p}:${s}`);
448
+ !g && s !== f && (g = await t.SITE_CONTENT.get(`page:${p}:${f}`)), g && (m[p] = JSON.parse(g));
347
449
  }
348
450
  }
349
- const r = {
451
+ const l = {
350
452
  locale: s,
351
- config: S
453
+ config: T
352
454
  };
353
- for (const [p, m] of Object.entries(g))
354
- r[`page:${p}`] = m;
455
+ for (const [p, g] of Object.entries(m))
456
+ l[`page:${p}`] = g;
355
457
  if (h) {
356
- const p = h[1], m = r[`page:${p}`];
357
- m && (c = E(c, m, o.siteName));
458
+ const p = h[1], g = l[`page:${p}`];
459
+ g && (u = k(u, g, o.siteName));
358
460
  }
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}
362
- </head>`), new Response(c, {
461
+ e.pathname === "/" && m.home && (u = k(u, m.home, o.siteName));
462
+ const v = `<script>window.__SITE_DATA__ = ${JSON.stringify(l)};<\/script>`;
463
+ return u = u.replace("</head>", `${v}
464
+ </head>`), new Response(u, {
363
465
  headers: {
364
466
  "Content-Type": "text/html;charset=utf-8",
365
467
  "Cache-Control": "public, max-age=60"
@@ -369,18 +471,20 @@ function ee(o) {
369
471
  };
370
472
  }
371
473
  export {
372
- D as PAGE_CONTEXT_KEY,
373
- F as buildLayoutCSS,
374
- ee as createSiteWorker,
375
- b as isInPreview,
474
+ I as PAGE_CONTEXT_KEY,
475
+ O as buildLayoutCSS,
476
+ ae as createSiteWorker,
477
+ x as isInPreview,
478
+ te as layoutPresets,
479
+ ee as layoutSchema,
376
480
  Z as setupPreviewRouter,
377
- q as useDynamicLayout,
378
- C as useLivePreview,
379
- V as useLocaleSwitcher,
380
- H as usePageData,
481
+ V as useDynamicLayout,
482
+ A as useLivePreview,
483
+ Q as useLocaleSwitcher,
484
+ q as usePageData,
381
485
  X as useSiteApi,
382
- Q as useSiteComponents,
383
- G as useSiteConfig,
384
- B as useSiteData,
486
+ H as useSiteComponents,
487
+ B as useSiteConfig,
488
+ J as useSiteData,
385
489
  Y as useSkin
386
490
  };
@@ -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.13",
4
4
  "description": "Shared Vue components and composables for Xosen site templates",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -2,28 +2,9 @@ import { computed, type Ref } 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
 
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
- }
26
-
27
8
  const BREAKPOINTS: Record<string, number> = {
28
9
  sm: 600,
29
10
  md: 960,
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
+ };