@xosen/site-sdk 0.0.13 → 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.
@@ -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,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,51 +1,51 @@
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;
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
+ import { useRoute as I } from "vue-router";
4
+ function Z(o, a) {
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 {
11
- const u = await fetch(`/api/content/${a}:${e.value}`);
12
- u.ok && (t.value = await u.json());
11
+ const c = await fetch(`/api/content/${a}:${e.value}`);
12
+ c.ok && (t.value = await c.json());
13
13
  } catch {
14
14
  }
15
15
  }
16
- return _(() => {
16
+ return S(() => {
17
17
  r() || i();
18
- }), b(e, () => {
18
+ }), _(e, () => {
19
19
  i();
20
20
  }), t;
21
21
  }
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
- function f(d) {
25
- const y = u.value[d];
26
- return typeof y == "boolean" ? y : typeof y == "object";
22
+ function q() {
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
+ function p(m) {
25
+ const g = c.value[m];
26
+ return typeof g == "boolean" ? g : typeof g == "object";
27
27
  }
28
28
  return {
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
- features: u,
37
- hasFeature: f
36
+ features: c,
37
+ hasFeature: p
38
38
  };
39
39
  }
40
- function Y(o) {
40
+ 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-${P(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
- _(() => {
48
+ S(() => {
49
49
  if (o) {
50
50
  a(o);
51
51
  return;
@@ -58,13 +58,13 @@ function Y(o) {
58
58
  });
59
59
  });
60
60
  }
61
- function P(o) {
61
+ function D(o) {
62
62
  return o.replace(/([a-z])([A-Z])/g, "$1-$2").toLowerCase();
63
63
  }
64
- function X() {
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,128 +72,141 @@ function X() {
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 x() {
84
+ function $() {
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
- x() && o.beforeEach((a, t) => !t.name);
91
+ function V(o) {
92
+ $() && o.beforeEach((a, t) => !t.name);
93
93
  }
94
- function A() {
95
- const o = w(null), a = w(x());
96
- function t(e) {
97
- const n = e.data;
98
- n?.type === "xosen-preview-update" && n.page && (o.value = n.page, a.value = !0);
94
+ function C() {
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 _(() => {
101
- window.addEventListener("message", t);
102
- }), E(() => {
103
- window.removeEventListener("message", t);
100
+ return S(() => {
101
+ window.addEventListener("message", e);
102
+ }), N(() => {
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 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;
110
+ const R = /* @__PURE__ */ Symbol("pageContext"), k = L({});
111
+ function j() {
112
+ const o = window.__SITE_DATA__;
113
+ if (o)
114
+ for (const a of Object.keys(o))
115
+ a.startsWith("page:") && (k[a] = o[a]);
116
+ }
117
+ j();
118
+ function ee(o) {
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;
113
121
  if (!l) return {};
114
- const { blocks: v, meta: p, slug: g, locale: M, ...C } = l;
115
- return C;
122
+ const { blocks: f, meta: d, slug: W, locale: J, ...E } = l;
123
+ return E;
116
124
  });
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;
125
+ P(R, m);
126
+ const g = u(() => e.value && t.value?.slug === c.value ? t.value.blocks || [] : n.value?.blocks || []);
127
+ function b(l) {
128
+ n.value = l, l && (k[p.value] = l);
129
+ }
130
+ function y() {
131
+ const l = s?.[p.value];
132
+ return l && a.value === s?.locale ? (b(l), i.value = !1, w(), !0) : !1;
122
133
  }
123
- async function m() {
134
+ async function h() {
124
135
  r.value = !0, i.value = !1;
125
136
  try {
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);
137
+ const l = await fetch(`/api/content/${p.value}:${a.value}`);
138
+ l.ok ? (b(await l.json()), w()) : (b(null), i.value = !0);
128
139
  } catch {
129
- s.value = null, i.value = !0;
140
+ b(null), i.value = !0;
130
141
  } finally {
131
142
  r.value = !1;
132
143
  }
133
144
  }
134
- function h() {
135
- if (!s.value) return;
136
- if (s.value.title) {
137
- const v = n?.config?.branding, p = v?.siteName ? ` — ${v.siteName}` : "";
138
- document.title = `${s.value.title}${p}`;
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}`;
139
150
  }
140
- const l = s.value.meta?.description;
151
+ const l = n.value.meta?.description;
141
152
  if (l) {
142
- const v = document.querySelector('meta[name="description"]');
143
- v && v.setAttribute("content", l);
153
+ const f = document.querySelector('meta[name="description"]');
154
+ f && f.setAttribute("content", l);
144
155
  }
145
156
  }
146
- return b(t, (l) => {
147
- l?.slug === u.value && (r.value = !1, i.value = !1, (l.title || l.blocks) && (s.value = {
148
- ...s.value || {},
157
+ return _(t, (l) => {
158
+ l?.slug === c.value && (r.value = !1, i.value = !1, (l.title || l.blocks) && (n.value = {
159
+ ...n.value || {},
149
160
  ...l.title ? { title: l.title } : {},
150
161
  ...l.blocks ? { blocks: l.blocks } : {}
151
- }, h()));
152
- }), _(() => {
153
- T() ? r.value = !1 : m();
154
- }), b(a, () => m()), b(u, () => {
155
- T() ? r.value = !1 : m();
162
+ }, w()));
163
+ }), S(() => {
164
+ y() ? r.value = !1 : h();
165
+ }), _(a, () => h()), _(c, () => {
166
+ y() ? r.value = !1 : h();
156
167
  }), {
157
- page: s,
158
- blocks: y,
159
- pageContext: d,
168
+ page: n,
169
+ blocks: g,
170
+ pageContext: m,
160
171
  loading: r,
161
172
  notFound: i,
162
173
  isPreviewMode: e,
163
- reload: m
174
+ reload: h
164
175
  };
165
176
  }
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
- return;
172
- }
173
- try {
174
- const i = await fetch(`/api/content/components:${r}`);
175
- i.ok && (e.value = await i.json());
176
- } catch {
177
+ function te() {
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
+ }
177
190
  }
178
191
  }
179
- b(o, (r) => n(r)), _(() => {
180
- o.value !== t && n(o.value);
192
+ _(o, (r) => s(r)), S(() => {
193
+ o.value !== t && s(o.value);
181
194
  });
182
- function s(r) {
183
- return c(() => e.value[r] || null);
195
+ function n(r) {
196
+ return u(() => e.value[r] || null);
184
197
  }
185
198
  return {
186
199
  /** All components (reactive, locale-aware) */
187
200
  components: e,
188
201
  /** Get a single component by key */
189
- getComponent: s,
202
+ getComponent: n,
190
203
  /** Force refetch for current locale */
191
- reload: () => n(o.value)
204
+ reload: () => s(o.value)
192
205
  };
193
206
  }
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) {
207
+ function ae() {
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) {
197
210
  o.value = i, localStorage.setItem("x-site-locale", i);
198
211
  }
199
212
  function r(i) {
@@ -203,17 +216,17 @@ function Q() {
203
216
  locale: o,
204
217
  locales: t,
205
218
  defaultLocale: e,
206
- hasMultipleLocales: n,
207
- switchLocale: s,
219
+ hasMultipleLocales: s,
220
+ switchLocale: n,
208
221
  resolveText: r
209
222
  };
210
223
  }
211
- const R = {
224
+ const O = {
212
225
  sm: 600,
213
226
  md: 960,
214
227
  lg: 1280,
215
228
  xl: 1920
216
- }, D = {
229
+ }, z = {
217
230
  grid: {
218
231
  default: {
219
232
  areas: ["header", "main", "footer"],
@@ -225,7 +238,7 @@ const R = {
225
238
  main: { type: "slot" },
226
239
  footer: { component: "footer" }
227
240
  }
228
- }, j = {
241
+ }, F = {
229
242
  grid: {
230
243
  default: {
231
244
  areas: ["main"]
@@ -235,61 +248,67 @@ const R = {
235
248
  main: { type: "slot" }
236
249
  }
237
250
  };
238
- function $(o, a) {
251
+ function x(o, a) {
239
252
  const t = [];
240
253
  if (o.areas?.length) {
241
- const e = o.areas.map((n) => `"${n}"`).join(" ");
254
+ const e = o.areas.map((s) => `"${s}"`).join(" ");
242
255
  t.push(`grid-template-areas: ${e}`);
243
256
  }
244
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("; ")}; }` : "";
245
258
  }
246
- function O(o, a) {
259
+ function G(o, a) {
247
260
  const t = [`${a} { display: grid; min-height: 100vh; }`], e = o.grid;
248
261
  if (!e) return t.join(`
249
262
  `);
250
263
  if (e.default) {
251
- const n = $(e.default, a);
252
- n && t.push(n);
264
+ const s = x(e.default, a);
265
+ s && t.push(s);
253
266
  }
254
- for (const n of ["sm", "md", "lg", "xl"]) {
255
- const s = e[n];
256
- if (!s) continue;
257
- const r = $(s, a);
258
- r && t.push(`@media (min-width: ${R[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} }`);
259
272
  }
260
273
  return t.join(`
261
274
  `);
262
275
  }
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(() => {
276
+ function oe() {
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(() => {
265
278
  if (o.path === "/_preview_")
266
279
  return a.value?.layout || "landing";
267
280
  if (t.value && a.value?.layout)
268
281
  return a.value.layout;
269
- const f = o.path === "/" ? "page:home" : `page:${o.params.slug || ""}`;
270
- return e?.[f]?.layout || "default";
271
- }), i = c(() => {
272
- const f = e?.config?.layouts;
273
- if (f) {
274
- const d = f[r.value] || f.default;
275
- if (d?.zones) return d;
282
+ const p = o.path === "/" ? "page:home" : `page:${o.params.slug || ""}`;
283
+ return (k[p] || e?.[p])?.layout || "default";
284
+ }), i = u(() => {
285
+ const p = e?.config?.layouts;
286
+ if (p) {
287
+ const m = p[r.value] || p.default;
288
+ if (m?.zones) return m;
276
289
  }
277
- return r.value === "landing" ? j : D;
278
- }), u = c(() => O(i.value, `.${n}`));
290
+ return r.value === "landing" ? F : z;
291
+ }), c = u(() => G(i.value, `.${s}`));
279
292
  return {
280
293
  layoutName: r,
281
294
  layoutConfig: i,
282
- layoutCSS: u,
283
- layoutClass: s
295
+ layoutCSS: c,
296
+ layoutClass: n
284
297
  };
285
298
  }
286
- const ee = {
299
+ const ne = {
287
300
  type: "layout",
288
301
  displayName: "Layout",
289
302
  description: "CSS Grid layout with responsive breakpoints and component zones",
290
303
  fields: [
291
304
  // Mobile (default)
292
- { 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
+ },
293
312
  { key: "grid.default.columns", type: "text", label: "Columns (mobile)", breakpoint: "default" },
294
313
  { key: "grid.default.rows", type: "text", label: "Rows (mobile)", breakpoint: "default" },
295
314
  { key: "grid.default.gap", type: "text", label: "Gap (mobile)", breakpoint: "default" },
@@ -317,7 +336,7 @@ const ee = {
317
336
  { key: "class", type: "text", label: "CSS Class" },
318
337
  { key: "container", type: "switch", label: "Wrap in Container" }
319
338
  ]
320
- }, te = {
339
+ }, se = {
321
340
  default: {
322
341
  displayName: "Full Width",
323
342
  config: {
@@ -375,7 +394,12 @@ const ee = {
375
394
  config: {
376
395
  grid: {
377
396
  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" }
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
+ }
379
403
  },
380
404
  zones: {
381
405
  header: { component: "nav-menu" },
@@ -385,27 +409,27 @@ const ee = {
385
409
  }
386
410
  }
387
411
  }
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) {
412
+ }, M = /\.(js|css|png|jpg|jpeg|gif|svg|ico|woff2?|ttf|eot|webp|avif|map|json|txt|xml|webmanifest)$/;
413
+ function U(o, a) {
390
414
  const e = new URL(o.url).searchParams.get("lang");
391
415
  if (e && a.supportedLocales?.includes(e)) return e;
392
416
  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;
417
+ const s = o.headers.get("Accept-Language") || "";
418
+ for (const n of a.supportedLocales)
419
+ if (s.includes(n)) return n;
396
420
  }
397
421
  return a.defaultLocale;
398
422
  }
399
- function G(o, a, t) {
423
+ function K(o, a, t) {
400
424
  if (!o.pathname.startsWith("/api/content/")) return null;
401
425
  const e = decodeURIComponent(o.pathname.replace("/api/content/", ""));
402
426
  return e ? (async () => {
403
- let n = await a.SITE_CONTENT.get(e);
404
- if (!n) {
405
- const s = e.lastIndexOf(":");
406
- 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));
407
431
  }
408
- return n ? new Response(n, {
432
+ return s ? new Response(s, {
409
433
  headers: {
410
434
  "Content-Type": "application/json",
411
435
  "Cache-Control": "public, max-age=60",
@@ -414,54 +438,54 @@ function G(o, a, t) {
414
438
  }) : Response.json({ error: "Not found" }, { status: 404 });
415
439
  })() : Promise.resolve(Response.json({ error: "Key is required" }, { status: 400 }));
416
440
  }
417
- function k(o, a, t) {
441
+ function A(o, a, t) {
418
442
  let e = o;
419
443
  if (a.title) {
420
- const n = t ? ` — ${t}` : "";
421
- 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>`);
422
446
  }
423
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;
424
448
  }
425
- function ae(o) {
449
+ function re(o) {
426
450
  return {
427
451
  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))
452
+ const e = new URL(a.url), s = K(e, t, o.defaultLocale);
453
+ if (s) return s;
454
+ if (M.test(e.pathname))
431
455
  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([
456
+ const n = U(a, o), r = new URL("/index.html", a.url);
457
+ let c = await (await t.ASSETS.fetch(new Request(r))).text();
458
+ const p = o.defaultLocale, [m, g] = await Promise.all([
435
459
  t.SITE_CONTENT.get("config"),
436
- t.SITE_CONTENT.get(`content:${s}`)
437
- ]), T = d ? JSON.parse(d) : {};
438
- let m = y ? JSON.parse(y) : {};
439
- if (!y && s !== f) {
440
- const p = await t.SITE_CONTENT.get(`content:${f}`);
441
- p && (m = JSON.parse(p));
460
+ t.SITE_CONTENT.get(`content:${n}`)
461
+ ]), b = m ? JSON.parse(m) : {};
462
+ let y = g ? JSON.parse(g) : {};
463
+ if (!g && n !== p) {
464
+ const f = await t.SITE_CONTENT.get(`content:${p}`);
465
+ f && (y = JSON.parse(f));
442
466
  }
443
467
  const h = e.pathname.match(/^\/p\/(.+)$/);
444
468
  if (h) {
445
- const p = h[1];
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));
469
+ const f = h[1];
470
+ if (!y[f]) {
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));
449
473
  }
450
474
  }
451
- const l = {
452
- locale: s,
453
- config: T
475
+ const w = {
476
+ locale: n,
477
+ config: b
454
478
  };
455
- for (const [p, g] of Object.entries(m))
456
- l[`page:${p}`] = g;
479
+ for (const [f, d] of Object.entries(y))
480
+ w[`page:${f}`] = d;
457
481
  if (h) {
458
- const p = h[1], g = l[`page:${p}`];
459
- g && (u = k(u, g, o.siteName));
482
+ const f = h[1], d = w[`page:${f}`];
483
+ d && (c = A(c, d, o.siteName));
460
484
  }
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, {
485
+ e.pathname === "/" && y.home && (c = A(c, y.home, o.siteName));
486
+ const l = `<script>window.__SITE_DATA__ = ${JSON.stringify(w)};<\/script>`;
487
+ return c = c.replace("</head>", `${l}
488
+ </head>`), new Response(c, {
465
489
  headers: {
466
490
  "Content-Type": "text/html;charset=utf-8",
467
491
  "Cache-Control": "public, max-age=60"
@@ -471,20 +495,20 @@ function ae(o) {
471
495
  };
472
496
  }
473
497
  export {
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,
480
- Z as setupPreviewRouter,
481
- V as useDynamicLayout,
482
- A as useLivePreview,
483
- Q as useLocaleSwitcher,
484
- q as usePageData,
485
- X as useSiteApi,
486
- H as useSiteComponents,
487
- B as useSiteConfig,
488
- J as useSiteData,
489
- Y as useSkin
498
+ R as PAGE_CONTEXT_KEY,
499
+ G as buildLayoutCSS,
500
+ re as createSiteWorker,
501
+ $ as isInPreview,
502
+ se as layoutPresets,
503
+ ne as layoutSchema,
504
+ V as setupPreviewRouter,
505
+ oe as useDynamicLayout,
506
+ C as useLivePreview,
507
+ ae as useLocaleSwitcher,
508
+ ee as usePageData,
509
+ Q as useSiteApi,
510
+ te as useSiteComponents,
511
+ q as useSiteConfig,
512
+ Z as useSiteData,
513
+ H as useSkin
490
514
  };
@@ -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.13",
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
+ }
@@ -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,9 +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
5
  import type { GridBreakpoint, LayoutConfig } from '../types/layout.js';
6
6
  import { useLivePreview } from './useLivePreview.js';
7
+ import { pageDataStore } from './pageDataStore.js';
7
8
 
8
9
  const BREAKPOINTS: Record<string, number> = {
9
10
  sm: 600,
@@ -102,7 +103,8 @@ export function useDynamicLayout() {
102
103
  return previewPage.value.layout;
103
104
  }
104
105
  const pageKey = route.path === '/' ? 'page:home' : `page:${route.params.slug || ''}`;
105
- const pageData = siteData?.[pageKey];
106
+ // Read from reactive store (updated by usePageData on fetch)
107
+ const pageData = pageDataStore[pageKey] || siteData?.[pageKey];
106
108
  return pageData?.layout || 'default';
107
109
  });
108
110
 
@@ -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
  }
@@ -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) */
@@ -39,14 +40,14 @@ export function usePageData(slug: Ref<string> | string): UsePageDataReturn {
39
40
  const loading = ref(true);
40
41
  const notFound = ref(false);
41
42
 
42
- const resolvedSlug = computed(() => typeof slug === 'string' ? slug : slug.value);
43
+ const resolvedSlug = computed(() => (typeof slug === 'string' ? slug : slug.value));
43
44
  const pageKey = computed(() => `page:${resolvedSlug.value}`);
44
45
 
45
46
  // Page context: page metadata without blocks (for injection)
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;
@@ -113,7 +121,7 @@ export function usePageData(slug: Ref<string> | string): UsePageDataReturn {
113
121
  notFound.value = false;
114
122
  if (p.title || p.blocks) {
115
123
  page.value = {
116
- ...(page.value || {} as PageConfig),
124
+ ...(page.value || ({} as PageConfig)),
117
125
  ...(p.title ? { title: p.title } : {}),
118
126
  ...(p.blocks ? { blocks: p.blocks as Block[] } : {}),
119
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' },