@xosen/site-sdk 0.0.14 → 0.0.15

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,6 +1,12 @@
1
1
  import type { Router } from 'vue-router';
2
+ export interface PreviewSiteData {
3
+ config?: Record<string, any>;
4
+ skin?: Record<string, any>;
5
+ components?: Record<string, any>;
6
+ }
2
7
  export interface PreviewMessage {
3
8
  type: 'xosen-preview-update';
9
+ site?: PreviewSiteData;
4
10
  page: {
5
11
  slug: string;
6
12
  locale: string;
@@ -50,5 +56,14 @@ export declare function useLivePreview(): {
50
56
  meta?: Record<string, any> | undefined;
51
57
  layout?: string | undefined;
52
58
  } | null>;
59
+ previewSite: import("vue").Ref<{
60
+ config?: Record<string, any> | undefined;
61
+ skin?: Record<string, any> | undefined;
62
+ components?: Record<string, any> | undefined;
63
+ } | null, PreviewSiteData | {
64
+ config?: Record<string, any> | undefined;
65
+ skin?: Record<string, any> | undefined;
66
+ components?: Record<string, any> | undefined;
67
+ } | null>;
53
68
  isPreviewMode: import("vue").Ref<boolean, boolean>;
54
69
  };
package/dist/index.d.ts CHANGED
@@ -10,7 +10,7 @@ export { useLocaleSwitcher } from './composables/useLocaleSwitcher.js';
10
10
  export { useDynamicLayout, buildLayoutCSS } from './composables/useDynamicLayout.js';
11
11
  export type { LayoutConfig, LayoutZone, GridBreakpoint, LayoutSchemaDefinition, LayoutField } from './types/layout.js';
12
12
  export { layoutSchema, layoutPresets } from './types/layout.js';
13
- export type { PreviewMessage } from './composables/useLivePreview.js';
13
+ export type { PreviewMessage, PreviewSiteData } from './composables/useLivePreview.js';
14
14
  export { createSiteWorker } from './worker/index.js';
15
15
  export type { WorkerEnv, WorkerConfig, PageData } from './worker/index.js';
16
16
  export type { Block, BlockSettings, HeroBlockData, HtmlBlockData, CardsBlockData, ImageTextBlockData, PricingBlockData, PricingPlan, ContactsBlockData, Office, GalleryBlockData, MapBlockData, } from './types/blocks.js';
package/dist/index.js CHANGED
@@ -1,10 +1,10 @@
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";
1
+ import { ref as v, onMounted as S, watch as _, computed as u, onUnmounted as N, reactive as L, provide as P } from "vue";
2
+ import { useI18n as T } from "vue-i18n";
3
3
  import { useRoute as I } from "vue-router";
4
4
  function Z(o, a) {
5
- const t = w(null), { locale: e } = S(), n = window.__SITE_DATA__, s = n?.locale;
5
+ const t = v(null), { locale: e } = T(), s = window.__SITE_DATA__, n = s?.locale;
6
6
  function r() {
7
- return n && n[o] && e.value === s ? (t.value = n[o], !0) : !1;
7
+ return s && s[o] && e.value === n ? (t.value = s[o], !0) : !1;
8
8
  }
9
9
  async function i() {
10
10
  try {
@@ -13,14 +13,14 @@ function Z(o, a) {
13
13
  } catch {
14
14
  }
15
15
  }
16
- return T(() => {
16
+ return S(() => {
17
17
  r() || i();
18
18
  }), _(e, () => {
19
19
  i();
20
20
  }), t;
21
21
  }
22
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 || {});
23
+ const o = window.__SITE_DATA__, a = u(() => o?.config || null), t = u(() => o?.components || {}), e = u(() => a.value?.navigation || []), s = u(() => a.value?.locales || []), n = u(() => a.value?.defaultLocale || "en"), r = u(() => a.value?.branding || {}), i = u(() => a.value?.footer || {}), c = u(() => a.value?.features || {});
24
24
  function p(m) {
25
25
  const g = c.value[m];
26
26
  return typeof g == "boolean" ? g : typeof g == "object";
@@ -29,8 +29,8 @@ function q() {
29
29
  config: a,
30
30
  components: t,
31
31
  navigation: e,
32
- locales: n,
33
- defaultLocale: s,
32
+ locales: s,
33
+ defaultLocale: n,
34
34
  branding: r,
35
35
  footer: i,
36
36
  features: c,
@@ -41,11 +41,11 @@ function H(o) {
41
41
  function a(t) {
42
42
  const e = document.documentElement;
43
43
  if (t.colors)
44
- for (const [n, s] of Object.entries(t.colors))
45
- s && e.style.setProperty(`--x-color-${D(n)}`, s);
44
+ for (const [s, n] of Object.entries(t.colors))
45
+ n && e.style.setProperty(`--x-color-${D(s)}`, n);
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
+ S(() => {
49
49
  if (o) {
50
50
  a(o);
51
51
  return;
@@ -63,8 +63,8 @@ function D(o) {
63
63
  }
64
64
  function Q() {
65
65
  const a = window.__SITE_DATA__?.apiBase || "";
66
- async function t(s) {
67
- const r = await fetch(`${a}${s}`);
66
+ async function t(n) {
67
+ const r = await fetch(`${a}${n}`);
68
68
  if (!r.ok)
69
69
  throw new Error(`API error: ${r.status}`);
70
70
  return r.json();
@@ -72,16 +72,16 @@ function Q() {
72
72
  async function e() {
73
73
  return t("/v1/billing/tariffs");
74
74
  }
75
- async function n() {
75
+ async function s() {
76
76
  return t("/v1/billing/services");
77
77
  }
78
78
  return {
79
79
  get: t,
80
80
  getTariffs: e,
81
- getProducts: n
81
+ getProducts: s
82
82
  };
83
83
  }
84
- function A() {
84
+ function $() {
85
85
  try {
86
86
  return window.self !== window.top;
87
87
  } catch {
@@ -89,82 +89,83 @@ function A() {
89
89
  }
90
90
  }
91
91
  function V(o) {
92
- A() && o.beforeEach((a, t) => !t.name);
92
+ $() && o.beforeEach((a, t) => !t.name);
93
93
  }
94
94
  function C() {
95
- const o = w(null), a = w(A());
96
- function t(e) {
97
- const n = e.data;
98
- n?.type === "xosen-preview-update" && n.page && (o.value = n.page, a.value = !0);
95
+ const o = v(null), a = v(null), t = v($());
96
+ function e(s) {
97
+ const n = s.data;
98
+ n?.type === "xosen-preview-update" && n.page && (o.value = n.page, n.site && (a.value = n.site), t.value = !0);
99
99
  }
100
- return T(() => {
101
- window.addEventListener("message", t);
100
+ return S(() => {
101
+ window.addEventListener("message", e);
102
102
  }), N(() => {
103
- window.removeEventListener("message", t);
103
+ window.removeEventListener("message", e);
104
104
  }), {
105
105
  previewPage: o,
106
- isPreviewMode: a
106
+ previewSite: a,
107
+ isPreviewMode: t
107
108
  };
108
109
  }
109
- const R = /* @__PURE__ */ Symbol("pageContext"), $ = L({});
110
+ const R = /* @__PURE__ */ Symbol("pageContext"), k = L({});
110
111
  function j() {
111
112
  const o = window.__SITE_DATA__;
112
113
  if (o)
113
114
  for (const a of Object.keys(o))
114
- a.startsWith("page:") && ($[a] = o[a]);
115
+ a.startsWith("page:") && (k[a] = o[a]);
115
116
  }
116
117
  j();
117
118
  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;
119
+ const { locale: a } = T(), { previewPage: t, isPreviewMode: e } = C(), s = window.__SITE_DATA__, n = v(null), r = v(!0), i = v(!1), c = u(() => typeof o == "string" ? o : o.value), p = u(() => `page:${c.value}`), m = u(() => {
120
+ const l = e.value ? t.value : n.value;
120
121
  if (!l) return {};
121
122
  const { blocks: f, meta: d, slug: W, locale: J, ...E } = l;
122
123
  return E;
123
124
  });
124
125
  P(R, m);
125
- const g = u(() => e.value && t.value?.slug === c.value ? t.value.blocks || [] : s.value?.blocks || []);
126
+ const g = u(() => e.value && t.value?.slug === c.value ? t.value.blocks || [] : n.value?.blocks || []);
126
127
  function b(l) {
127
- s.value = l, l && ($[p.value] = l);
128
+ n.value = l, l && (k[p.value] = l);
128
129
  }
129
130
  function y() {
130
- const l = n?.[p.value];
131
- return l && a.value === n?.locale ? (b(l), i.value = !1, v(), !0) : !1;
131
+ const l = s?.[p.value];
132
+ return l && a.value === s?.locale ? (b(l), i.value = !1, w(), !0) : !1;
132
133
  }
133
134
  async function h() {
134
135
  r.value = !0, i.value = !1;
135
136
  try {
136
137
  const l = await fetch(`/api/content/${p.value}:${a.value}`);
137
- l.ok ? (b(await l.json()), v()) : (b(null), i.value = !0);
138
+ l.ok ? (b(await l.json()), w()) : (b(null), i.value = !0);
138
139
  } catch {
139
140
  b(null), i.value = !0;
140
141
  } finally {
141
142
  r.value = !1;
142
143
  }
143
144
  }
144
- function v() {
145
- if (!s.value) return;
146
- if (s.value.title) {
147
- const f = n?.config?.branding, d = f?.siteName ? ` — ${f.siteName}` : "";
148
- document.title = `${s.value.title}${d}`;
145
+ function w() {
146
+ if (!n.value) return;
147
+ if (n.value.title) {
148
+ const f = s?.config?.branding, d = f?.siteName ? ` — ${f.siteName}` : "";
149
+ document.title = `${n.value.title}${d}`;
149
150
  }
150
- const l = s.value.meta?.description;
151
+ const l = n.value.meta?.description;
151
152
  if (l) {
152
153
  const f = document.querySelector('meta[name="description"]');
153
154
  f && f.setAttribute("content", l);
154
155
  }
155
156
  }
156
157
  return _(t, (l) => {
157
- l?.slug === c.value && (r.value = !1, i.value = !1, (l.title || l.blocks) && (s.value = {
158
- ...s.value || {},
158
+ l?.slug === c.value && (r.value = !1, i.value = !1, (l.title || l.blocks) && (n.value = {
159
+ ...n.value || {},
159
160
  ...l.title ? { title: l.title } : {},
160
161
  ...l.blocks ? { blocks: l.blocks } : {}
161
- }, v()));
162
- }), T(() => {
162
+ }, w()));
163
+ }), S(() => {
163
164
  y() ? r.value = !1 : h();
164
165
  }), _(a, () => h()), _(c, () => {
165
166
  y() ? r.value = !1 : h();
166
167
  }), {
167
- page: s,
168
+ page: n,
168
169
  blocks: g,
169
170
  pageContext: m,
170
171
  loading: r,
@@ -174,36 +175,38 @@ function ee(o) {
174
175
  };
175
176
  }
176
177
  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 || {} };
181
- return;
182
- }
183
- try {
184
- const i = await fetch(`/api/content/components:${r}`);
185
- i.ok && (e.value = await i.json());
186
- } catch {
178
+ const { locale: o } = T(), a = window.__SITE_DATA__, t = a?.locale, e = v({ ...a?.components || {} });
179
+ async function s(r) {
180
+ if (!$()) {
181
+ if (r === t) {
182
+ e.value = { ...a?.components || {} };
183
+ return;
184
+ }
185
+ try {
186
+ const i = await fetch(`/api/content/components:${r}`);
187
+ i.ok && (e.value = await i.json());
188
+ } catch {
189
+ }
187
190
  }
188
191
  }
189
- _(o, (r) => n(r)), T(() => {
190
- o.value !== t && n(o.value);
192
+ _(o, (r) => s(r)), S(() => {
193
+ o.value !== t && s(o.value);
191
194
  });
192
- function s(r) {
195
+ function n(r) {
193
196
  return u(() => e.value[r] || null);
194
197
  }
195
198
  return {
196
199
  /** All components (reactive, locale-aware) */
197
200
  components: e,
198
201
  /** Get a single component by key */
199
- getComponent: s,
202
+ getComponent: n,
200
203
  /** Force refetch for current locale */
201
- reload: () => n(o.value)
204
+ reload: () => s(o.value)
202
205
  };
203
206
  }
204
207
  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) {
208
+ const { locale: o } = T(), a = window.__SITE_DATA__, t = u(() => a?.config?.locales || []), e = u(() => a?.config?.defaultLocale || "en"), s = u(() => t.value.length > 1);
209
+ function n(i) {
207
210
  o.value = i, localStorage.setItem("x-site-locale", i);
208
211
  }
209
212
  function r(i) {
@@ -213,8 +216,8 @@ function ae() {
213
216
  locale: o,
214
217
  locales: t,
215
218
  defaultLocale: e,
216
- hasMultipleLocales: n,
217
- switchLocale: s,
219
+ hasMultipleLocales: s,
220
+ switchLocale: n,
218
221
  resolveText: r
219
222
  };
220
223
  }
@@ -245,10 +248,10 @@ const O = {
245
248
  main: { type: "slot" }
246
249
  }
247
250
  };
248
- function k(o, a) {
251
+ function x(o, a) {
249
252
  const t = [];
250
253
  if (o.areas?.length) {
251
- const e = o.areas.map((n) => `"${n}"`).join(" ");
254
+ const e = o.areas.map((s) => `"${s}"`).join(" ");
252
255
  t.push(`grid-template-areas: ${e}`);
253
256
  }
254
257
  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("; ")}; }` : "";
@@ -258,26 +261,26 @@ function G(o, a) {
258
261
  if (!e) return t.join(`
259
262
  `);
260
263
  if (e.default) {
261
- const n = k(e.default, a);
262
- n && t.push(n);
264
+ const s = x(e.default, a);
265
+ s && t.push(s);
263
266
  }
264
- for (const n of ["sm", "md", "lg", "xl"]) {
265
- const s = e[n];
266
- if (!s) continue;
267
- const r = k(s, a);
268
- r && t.push(`@media (min-width: ${O[n]}px) { ${r} }`);
267
+ for (const s of ["sm", "md", "lg", "xl"]) {
268
+ const n = e[s];
269
+ if (!n) continue;
270
+ const r = x(n, a);
271
+ r && t.push(`@media (min-width: ${O[s]}px) { ${r} }`);
269
272
  }
270
273
  return t.join(`
271
274
  `);
272
275
  }
273
276
  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(() => {
277
+ const o = I(), { previewPage: a, isPreviewMode: t } = C(), e = window.__SITE_DATA__, s = `dl-${Math.random().toString(36).slice(2, 8)}`, n = u(() => ["dynamic-layout", s]), r = u(() => {
275
278
  if (o.path === "/_preview_")
276
279
  return a.value?.layout || "landing";
277
280
  if (t.value && a.value?.layout)
278
281
  return a.value.layout;
279
282
  const p = o.path === "/" ? "page:home" : `page:${o.params.slug || ""}`;
280
- return ($[p] || e?.[p])?.layout || "default";
283
+ return (k[p] || e?.[p])?.layout || "default";
281
284
  }), i = u(() => {
282
285
  const p = e?.config?.layouts;
283
286
  if (p) {
@@ -285,12 +288,12 @@ function oe() {
285
288
  if (m?.zones) return m;
286
289
  }
287
290
  return r.value === "landing" ? F : z;
288
- }), c = u(() => G(i.value, `.${n}`));
291
+ }), c = u(() => G(i.value, `.${s}`));
289
292
  return {
290
293
  layoutName: r,
291
294
  layoutConfig: i,
292
295
  layoutCSS: c,
293
- layoutClass: s
296
+ layoutClass: n
294
297
  };
295
298
  }
296
299
  const ne = {
@@ -299,7 +302,13 @@ const ne = {
299
302
  description: "CSS Grid layout with responsive breakpoints and component zones",
300
303
  fields: [
301
304
  // Mobile (default)
302
- { key: "grid.default.areas", type: "grid-areas", label: "Grid Areas (mobile)", breakpoint: "default", required: !0 },
305
+ {
306
+ key: "grid.default.areas",
307
+ type: "grid-areas",
308
+ label: "Grid Areas (mobile)",
309
+ breakpoint: "default",
310
+ required: !0
311
+ },
303
312
  { key: "grid.default.columns", type: "text", label: "Columns (mobile)", breakpoint: "default" },
304
313
  { key: "grid.default.rows", type: "text", label: "Rows (mobile)", breakpoint: "default" },
305
314
  { key: "grid.default.gap", type: "text", label: "Gap (mobile)", breakpoint: "default" },
@@ -385,7 +394,12 @@ const ne = {
385
394
  config: {
386
395
  grid: {
387
396
  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" }
397
+ md: {
398
+ areas: ["header header header", "main main aside", "footer footer footer"],
399
+ columns: "1fr 1fr 300px",
400
+ rows: "auto 1fr auto",
401
+ gap: "24px"
402
+ }
389
403
  },
390
404
  zones: {
391
405
  header: { component: "nav-menu" },
@@ -400,9 +414,9 @@ function U(o, a) {
400
414
  const e = new URL(o.url).searchParams.get("lang");
401
415
  if (e && a.supportedLocales?.includes(e)) return e;
402
416
  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;
417
+ const s = o.headers.get("Accept-Language") || "";
418
+ for (const n of a.supportedLocales)
419
+ if (s.includes(n)) return n;
406
420
  }
407
421
  return a.defaultLocale;
408
422
  }
@@ -410,12 +424,12 @@ function K(o, a, t) {
410
424
  if (!o.pathname.startsWith("/api/content/")) return null;
411
425
  const e = decodeURIComponent(o.pathname.replace("/api/content/", ""));
412
426
  return e ? (async () => {
413
- let n = await a.SITE_CONTENT.get(e);
414
- if (!n) {
415
- const s = e.lastIndexOf(":");
416
- s > 0 && e.slice(s + 1) !== t && (n = await a.SITE_CONTENT.get(e.slice(0, s + 1) + t));
427
+ let s = await a.SITE_CONTENT.get(e);
428
+ if (!s) {
429
+ const n = e.lastIndexOf(":");
430
+ n > 0 && e.slice(n + 1) !== t && (s = await a.SITE_CONTENT.get(e.slice(0, n + 1) + t));
417
431
  }
418
- return n ? new Response(n, {
432
+ return s ? new Response(s, {
419
433
  headers: {
420
434
  "Content-Type": "application/json",
421
435
  "Cache-Control": "public, max-age=60",
@@ -424,29 +438,29 @@ function K(o, a, t) {
424
438
  }) : Response.json({ error: "Not found" }, { status: 404 });
425
439
  })() : Promise.resolve(Response.json({ error: "Key is required" }, { status: 400 }));
426
440
  }
427
- function x(o, a, t) {
441
+ function A(o, a, t) {
428
442
  let e = o;
429
443
  if (a.title) {
430
- const n = t ? ` — ${t}` : "";
431
- e = e.replace(/<title>[^<]*<\/title>/, `<title>${a.title}${n}</title>`);
444
+ const s = t ? ` — ${t}` : "";
445
+ e = e.replace(/<title>[^<]*<\/title>/, `<title>${a.title}${s}</title>`);
432
446
  }
433
447
  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;
434
448
  }
435
449
  function re(o) {
436
450
  return {
437
451
  async fetch(a, t) {
438
- const e = new URL(a.url), n = K(e, t, o.defaultLocale);
439
- if (n) return n;
452
+ const e = new URL(a.url), s = K(e, t, o.defaultLocale);
453
+ if (s) return s;
440
454
  if (M.test(e.pathname))
441
455
  return t.ASSETS.fetch(a);
442
- const s = U(a, o), r = new URL("/index.html", a.url);
456
+ const n = U(a, o), r = new URL("/index.html", a.url);
443
457
  let c = await (await t.ASSETS.fetch(new Request(r))).text();
444
458
  const p = o.defaultLocale, [m, g] = await Promise.all([
445
459
  t.SITE_CONTENT.get("config"),
446
- t.SITE_CONTENT.get(`content:${s}`)
460
+ t.SITE_CONTENT.get(`content:${n}`)
447
461
  ]), b = m ? JSON.parse(m) : {};
448
462
  let y = g ? JSON.parse(g) : {};
449
- if (!g && s !== p) {
463
+ if (!g && n !== p) {
450
464
  const f = await t.SITE_CONTENT.get(`content:${p}`);
451
465
  f && (y = JSON.parse(f));
452
466
  }
@@ -454,22 +468,22 @@ function re(o) {
454
468
  if (h) {
455
469
  const f = h[1];
456
470
  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));
471
+ let d = await t.SITE_CONTENT.get(`page:${f}:${n}`);
472
+ !d && n !== p && (d = await t.SITE_CONTENT.get(`page:${f}:${p}`)), d && (y[f] = JSON.parse(d));
459
473
  }
460
474
  }
461
- const v = {
462
- locale: s,
475
+ const w = {
476
+ locale: n,
463
477
  config: b
464
478
  };
465
479
  for (const [f, d] of Object.entries(y))
466
- v[`page:${f}`] = d;
480
+ w[`page:${f}`] = d;
467
481
  if (h) {
468
- const f = h[1], d = v[`page:${f}`];
469
- d && (c = x(c, d, o.siteName));
482
+ const f = h[1], d = w[`page:${f}`];
483
+ d && (c = A(c, d, o.siteName));
470
484
  }
471
- e.pathname === "/" && y.home && (c = x(c, y.home, o.siteName));
472
- const l = `<script>window.__SITE_DATA__ = ${JSON.stringify(v)};<\/script>`;
485
+ e.pathname === "/" && y.home && (c = A(c, y.home, o.siteName));
486
+ const l = `<script>window.__SITE_DATA__ = ${JSON.stringify(w)};<\/script>`;
473
487
  return c = c.replace("</head>", `${l}
474
488
  </head>`), new Response(c, {
475
489
  headers: {
@@ -484,7 +498,7 @@ export {
484
498
  R as PAGE_CONTEXT_KEY,
485
499
  G as buildLayoutCSS,
486
500
  re as createSiteWorker,
487
- A as isInPreview,
501
+ $ as isInPreview,
488
502
  se as layoutPresets,
489
503
  ne as layoutSchema,
490
504
  V as setupPreviewRouter,
@@ -18,7 +18,8 @@ export interface HeroBlockData {
18
18
  badge?: string;
19
19
  image?: string;
20
20
  backgroundImage?: string;
21
- overlay?: boolean;
21
+ /** Overlay: false to disable, number (0-1) for opacity, or CSS string for custom gradient */
22
+ overlay?: boolean | number | string;
22
23
  buttons?: Array<{
23
24
  text: string;
24
25
  url: string;
@@ -12,6 +12,7 @@ export interface LayoutZone {
12
12
  type?: 'slot';
13
13
  component?: string;
14
14
  class?: string;
15
+ style?: Record<string, string>;
15
16
  container?: boolean;
16
17
  containerStyle?: Record<string, string>;
17
18
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xosen/site-sdk",
3
- "version": "0.0.14",
3
+ "version": "0.0.15",
4
4
  "description": "Shared Vue components and composables for Xosen site templates",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -24,23 +24,23 @@
24
24
  "dist",
25
25
  "src"
26
26
  ],
27
+ "scripts": {
28
+ "build": "vite build && vue-tsc --declaration --emitDeclarationOnly --outDir dist",
29
+ "dev": "vite build --watch",
30
+ "clean": "rm -rf dist"
31
+ },
27
32
  "peerDependencies": {
28
33
  "vue": "^3.5.0",
29
34
  "vue-i18n": "^11.0.0",
30
35
  "vue-router": "^5.0.0"
31
36
  },
32
37
  "devDependencies": {
33
- "vue": "^3.5.30",
34
- "vue-i18n": "^11.3.0",
35
- "vue-router": "^5.0.3",
36
- "vue-tsc": "^3.2.5",
37
- "typescript": "^5.9.3",
38
- "vite": "^7.3.1",
39
- "@vitejs/plugin-vue": "^6.0.5"
40
- },
41
- "scripts": {
42
- "build": "vite build && vue-tsc --declaration --emitDeclarationOnly --outDir dist",
43
- "dev": "vite build --watch",
44
- "clean": "rm -rf dist"
38
+ "vue": "catalog:",
39
+ "vue-i18n": "catalog:",
40
+ "vue-router": "catalog:",
41
+ "vue-tsc": "catalog:",
42
+ "typescript": "catalog:",
43
+ "vite": "catalog:",
44
+ "@vitejs/plugin-vue": "catalog:"
45
45
  }
46
- }
46
+ }
@@ -1,8 +1,15 @@
1
1
  import { ref, onMounted, onUnmounted } from 'vue';
2
2
  import type { Router } from 'vue-router';
3
3
 
4
+ export interface PreviewSiteData {
5
+ config?: Record<string, any>;
6
+ skin?: Record<string, any>;
7
+ components?: Record<string, any>;
8
+ }
9
+
4
10
  export interface PreviewMessage {
5
11
  type: 'xosen-preview-update';
12
+ site?: PreviewSiteData;
6
13
  page: {
7
14
  slug: string;
8
15
  locale: string;
@@ -45,12 +52,16 @@ export function setupPreviewRouter(router: Router) {
45
52
  */
46
53
  export function useLivePreview() {
47
54
  const previewPage = ref<PreviewMessage['page'] | null>(null);
55
+ const previewSite = ref<PreviewSiteData | null>(null);
48
56
  const isPreviewMode = ref(isInPreview());
49
57
 
50
58
  function handleMessage(event: MessageEvent) {
51
59
  const data = event.data;
52
60
  if (data?.type === 'xosen-preview-update' && data.page) {
53
61
  previewPage.value = data.page;
62
+ if (data.site) {
63
+ previewSite.value = data.site;
64
+ }
54
65
  isPreviewMode.value = true;
55
66
  }
56
67
  }
@@ -65,6 +76,7 @@ export function useLivePreview() {
65
76
 
66
77
  return {
67
78
  previewPage,
79
+ previewSite,
68
80
  isPreviewMode,
69
81
  };
70
82
  }
@@ -40,7 +40,7 @@ export function usePageData(slug: Ref<string> | string): UsePageDataReturn {
40
40
  const loading = ref(true);
41
41
  const notFound = ref(false);
42
42
 
43
- const resolvedSlug = computed(() => typeof slug === 'string' ? slug : slug.value);
43
+ const resolvedSlug = computed(() => (typeof slug === 'string' ? slug : slug.value));
44
44
  const pageKey = computed(() => `page:${resolvedSlug.value}`);
45
45
 
46
46
  // Page context: page metadata without blocks (for injection)
@@ -121,7 +121,7 @@ export function usePageData(slug: Ref<string> | string): UsePageDataReturn {
121
121
  notFound.value = false;
122
122
  if (p.title || p.blocks) {
123
123
  page.value = {
124
- ...(page.value || {} as PageConfig),
124
+ ...(page.value || ({} as PageConfig)),
125
125
  ...(p.title ? { title: p.title } : {}),
126
126
  ...(p.blocks ? { blocks: p.blocks as Block[] } : {}),
127
127
  };
@@ -2,6 +2,7 @@ import { ref, computed, watch, onMounted, type Ref } from 'vue';
2
2
  import { useI18n } from 'vue-i18n';
3
3
 
4
4
  import type { SiteData } from '../types/config.js';
5
+ import { isInPreview } from './useLivePreview.js';
5
6
 
6
7
  /**
7
8
  * Locale-aware access to site components (navigation, footer, sidebar, etc.).
@@ -14,12 +15,15 @@ export function useSiteComponents() {
14
15
  const siteData = (window as any).__SITE_DATA__ as SiteData | undefined;
15
16
  const initialLocale = siteData?.locale;
16
17
 
17
- const components = ref<Record<string, any>>({ ...(siteData as any)?.components || {} });
18
+ const components = ref<Record<string, any>>({ ...((siteData as any)?.components || {}) });
18
19
 
19
20
  async function fetchComponents(loc: string) {
21
+ // Skip fetching in preview mode — components come via postMessage
22
+ if (isInPreview()) return;
23
+
20
24
  // If same as initial locale, use preloaded data
21
25
  if (loc === initialLocale) {
22
- components.value = { ...(siteData as any)?.components || {} };
26
+ components.value = { ...((siteData as any)?.components || {}) };
23
27
  return;
24
28
  }
25
29
  try {
package/src/index.ts CHANGED
@@ -11,7 +11,7 @@ export { useLocaleSwitcher } from './composables/useLocaleSwitcher.js';
11
11
  export { useDynamicLayout, buildLayoutCSS } from './composables/useDynamicLayout.js';
12
12
  export type { LayoutConfig, LayoutZone, GridBreakpoint, LayoutSchemaDefinition, LayoutField } from './types/layout.js';
13
13
  export { layoutSchema, layoutPresets } from './types/layout.js';
14
- export type { PreviewMessage } from './composables/useLivePreview.js';
14
+ export type { PreviewMessage, PreviewSiteData } from './composables/useLivePreview.js';
15
15
 
16
16
  // Worker
17
17
  export { createSiteWorker } from './worker/index.js';
@@ -20,7 +20,8 @@ export interface HeroBlockData {
20
20
  badge?: string;
21
21
  image?: string;
22
22
  backgroundImage?: string;
23
- overlay?: boolean;
23
+ /** Overlay: false to disable, number (0-1) for opacity, or CSS string for custom gradient */
24
+ overlay?: boolean | number | string;
24
25
  buttons?: Array<{ text: string; url: string; variant?: 'primary' | 'outline' | 'white' }>;
25
26
  stats?: Array<{ value: string; label: string }>;
26
27
  }
@@ -14,6 +14,7 @@ export interface LayoutZone {
14
14
  type?: 'slot';
15
15
  component?: string;
16
16
  class?: string;
17
+ style?: Record<string, string>;
17
18
  container?: boolean;
18
19
  containerStyle?: Record<string, string>;
19
20
  }
@@ -50,7 +51,13 @@ export const layoutSchema: LayoutSchemaDefinition = {
50
51
  description: 'CSS Grid layout with responsive breakpoints and component zones',
51
52
  fields: [
52
53
  // Mobile (default)
53
- { key: 'grid.default.areas', type: 'grid-areas', label: 'Grid Areas (mobile)', breakpoint: 'default', required: true },
54
+ {
55
+ key: 'grid.default.areas',
56
+ type: 'grid-areas',
57
+ label: 'Grid Areas (mobile)',
58
+ breakpoint: 'default',
59
+ required: true,
60
+ },
54
61
  { key: 'grid.default.columns', type: 'text', label: 'Columns (mobile)', breakpoint: 'default' },
55
62
  { key: 'grid.default.rows', type: 'text', label: 'Rows (mobile)', breakpoint: 'default' },
56
63
  { key: 'grid.default.gap', type: 'text', label: 'Gap (mobile)', breakpoint: 'default' },
@@ -145,7 +152,12 @@ export const layoutPresets: Record<string, { displayName: string; config: Layout
145
152
  config: {
146
153
  grid: {
147
154
  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' },
155
+ md: {
156
+ areas: ['header header header', 'main main aside', 'footer footer footer'],
157
+ columns: '1fr 1fr 300px',
158
+ rows: 'auto 1fr auto',
159
+ gap: '24px',
160
+ },
149
161
  },
150
162
  zones: {
151
163
  header: { component: 'nav-menu' },