@uniweb/runtime 0.6.13 → 0.6.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.
Files changed (36) hide show
  1. package/dist/app/_importmap/@uniweb-core.js +2 -0
  2. package/dist/app/_importmap/@uniweb-core.js.map +1 -0
  3. package/dist/app/_importmap/react-dom.js +2 -0
  4. package/dist/app/_importmap/react-dom.js.map +1 -0
  5. package/dist/app/_importmap/react-jsx-dev-runtime.js +2 -0
  6. package/dist/app/_importmap/react-jsx-dev-runtime.js.map +1 -0
  7. package/dist/app/_importmap/react-jsx-runtime.js +2 -0
  8. package/dist/app/_importmap/react-jsx-runtime.js.map +1 -0
  9. package/dist/app/_importmap/react.js +2 -0
  10. package/dist/app/_importmap/react.js.map +1 -0
  11. package/dist/app/assets/_commonjsHelpers-CqkleIqs.js +2 -0
  12. package/dist/app/assets/_commonjsHelpers-CqkleIqs.js.map +1 -0
  13. package/dist/app/assets/_importmap_react-dWoQamCw.js +2 -0
  14. package/dist/app/assets/_importmap_react-dWoQamCw.js.map +1 -0
  15. package/dist/app/assets/index-C0udIITE.js +9 -0
  16. package/dist/app/assets/index-C0udIITE.js.map +1 -0
  17. package/dist/app/assets/index-C6TPxGbh.js +133 -0
  18. package/dist/app/assets/index-C6TPxGbh.js.map +1 -0
  19. package/dist/app/assets/index-CsyMBO9p.js +8 -0
  20. package/dist/app/assets/index-CsyMBO9p.js.map +1 -0
  21. package/dist/app/assets/index-kA4PVysc.js +2 -0
  22. package/dist/app/assets/index-kA4PVysc.js.map +1 -0
  23. package/dist/app/assets/jsx-runtime-C3x2e0aW.js +2 -0
  24. package/dist/app/assets/jsx-runtime-C3x2e0aW.js.map +1 -0
  25. package/dist/app/index.html +31 -0
  26. package/dist/app/manifest.json +19 -0
  27. package/dist/ssr.js +319 -327
  28. package/dist/ssr.js.map +1 -1
  29. package/package.json +7 -4
  30. package/src/components/PageRenderer.jsx +12 -9
  31. package/src/foundation-loader.js +3 -0
  32. package/src/index.jsx +3 -4
  33. package/src/shell/index.html +12 -0
  34. package/src/shell/main.js +16 -0
  35. package/src/ssr-renderer.js +591 -0
  36. package/src/ssr.js +25 -28
package/dist/ssr.js CHANGED
@@ -1,5 +1,7 @@
1
- import React, { useState, useEffect } from "react";
2
- import { jsxs, jsx, Fragment } from "react/jsx-runtime";
1
+ import React from "react";
2
+ import { renderToString } from "react-dom/server";
3
+ import { createUniweb } from "@uniweb/core";
4
+ import { buildSectionOverrides } from "@uniweb/theming";
3
5
  function guaranteeItemStructure(item) {
4
6
  return {
5
7
  title: item.title || "",
@@ -124,85 +126,31 @@ function getComponentMeta(componentName) {
124
126
  function getComponentDefaults(componentName) {
125
127
  return globalThis.uniweb?.getComponentDefaults?.(componentName) || {};
126
128
  }
127
- const MODES = {
128
- COLOR: "color",
129
- GRADIENT: "gradient",
130
- IMAGE: "image",
131
- VIDEO: "video"
132
- };
133
- function resolveUrl(url) {
134
- if (!url || !url.startsWith("/")) return url;
135
- const basePath = globalThis.uniweb?.activeWebsite?.basePath || "";
136
- if (!basePath) return url;
137
- if (url.startsWith(basePath + "/") || url === basePath) return url;
138
- return basePath + url;
139
- }
140
- function GradientOverlay({ gradient, opacity = 0.5 }) {
141
- const {
142
- start = "rgba(0,0,0,0.7)",
143
- end = "rgba(0,0,0,0)",
144
- angle = 180,
145
- startPosition = 0,
146
- endPosition = 100
147
- } = gradient;
148
- const style = {
149
- position: "absolute",
150
- inset: 0,
151
- background: `linear-gradient(${angle}deg, ${start} ${startPosition}%, ${end} ${endPosition}%)`,
152
- opacity,
153
- pointerEvents: "none"
154
- };
155
- return /* @__PURE__ */ jsx("div", { className: "background-overlay background-overlay--gradient", style, "aria-hidden": "true" });
156
- }
157
- function SolidOverlay({ type = "dark", opacity = 0.5 }) {
158
- const baseColor = type === "light" ? "255, 255, 255" : "0, 0, 0";
159
- const style = {
160
- position: "absolute",
161
- inset: 0,
162
- backgroundColor: `rgba(${baseColor}, ${opacity})`,
163
- pointerEvents: "none"
164
- };
165
- return /* @__PURE__ */ jsx("div", { className: "background-overlay background-overlay--solid", style, "aria-hidden": "true" });
166
- }
167
- function Overlay({ overlay }) {
168
- if (!overlay?.enabled) return null;
169
- if (overlay.gradient) {
170
- return /* @__PURE__ */ jsx(GradientOverlay, { gradient: overlay.gradient, opacity: overlay.opacity });
129
+ const VALID_CONTEXTS = ["light", "medium", "dark"];
130
+ function getWrapperProps(block) {
131
+ const theme = block.themeName;
132
+ const blockClassName = block.state?.className || "";
133
+ let contextClass = "";
134
+ if (theme && VALID_CONTEXTS.includes(theme)) {
135
+ contextClass = `context-${theme}`;
171
136
  }
172
- return /* @__PURE__ */ jsx(SolidOverlay, { type: overlay.type, opacity: overlay.opacity });
173
- }
174
- function ColorBackground({ color }) {
175
- if (!color) return null;
176
- const style = {
177
- position: "absolute",
178
- inset: 0,
179
- backgroundColor: color
180
- };
181
- return /* @__PURE__ */ jsx("div", { className: "background-color", style, "aria-hidden": "true" });
182
- }
183
- function GradientBackground({ gradient }) {
184
- if (!gradient) return null;
185
- if (typeof gradient === "string") {
186
- const style2 = { position: "absolute", inset: 0, background: gradient };
187
- return /* @__PURE__ */ jsx("div", { className: "background-gradient", style: style2, "aria-hidden": "true" });
188
- }
189
- const {
190
- start = "transparent",
191
- end = "transparent",
192
- angle = 0,
193
- startPosition = 0,
194
- endPosition = 100,
195
- startOpacity = 1,
196
- endOpacity = 1
197
- } = gradient;
198
- const startColor = startOpacity < 1 ? withOpacity(start, startOpacity) : start;
199
- const endColor = endOpacity < 1 ? withOpacity(end, endOpacity) : end;
200
- const style = {
201
- position: "absolute",
202
- inset: 0,
203
- background: `linear-gradient(${angle}deg, ${startColor} ${startPosition}%, ${endColor} ${endPosition}%)`
204
- };
205
- return /* @__PURE__ */ jsx("div", { className: "background-gradient", style, "aria-hidden": "true" });
137
+ let className = contextClass;
138
+ if (blockClassName) {
139
+ className = className ? `${className} ${blockClassName}` : blockClassName;
140
+ }
141
+ const { background = {} } = block.standardOptions;
142
+ const style = {};
143
+ if (background.mode) {
144
+ style.position = "relative";
145
+ style.isolation = "isolate";
146
+ }
147
+ if (block.contextOverrides) {
148
+ for (const [key, value] of Object.entries(block.contextOverrides)) {
149
+ style[`--${key}`] = value;
150
+ }
151
+ }
152
+ const sectionId = block.stableId || block.id;
153
+ return { id: `section-${sectionId}`, style, className, background };
206
154
  }
207
155
  function withOpacity(color, opacity) {
208
156
  if (color.startsWith("#")) {
@@ -219,192 +167,143 @@ function withOpacity(color, opacity) {
219
167
  }
220
168
  return color;
221
169
  }
222
- function ImageBackground({ image }) {
223
- if (!image?.src) return null;
224
- const {
225
- src,
226
- position = "center",
227
- size = "cover",
228
- lazy = true
229
- } = image;
230
- const style = {
231
- position: "absolute",
232
- inset: 0,
233
- backgroundImage: `url(${resolveUrl(src)})`,
234
- backgroundPosition: position,
235
- backgroundSize: size,
236
- backgroundRepeat: "no-repeat"
237
- };
238
- return /* @__PURE__ */ jsx("div", { className: "background-image", style, "aria-hidden": "true" });
239
- }
240
- function prefersReducedMotion() {
241
- if (typeof window === "undefined") return false;
242
- return window.matchMedia("(prefers-reduced-motion: reduce)").matches;
243
- }
244
- function VideoBackground({ video }) {
245
- if (!video?.src) return null;
246
- const {
247
- src,
248
- sources,
249
- // Array of { src, type } for multiple formats
250
- poster,
251
- loop = true,
252
- muted = true
253
- } = video;
254
- if (prefersReducedMotion() && poster) {
255
- return /* @__PURE__ */ jsx(ImageBackground, { image: { src: poster, size: "cover", position: "center" } });
256
- }
257
- const style = {
258
- position: "absolute",
259
- inset: 0,
260
- width: "100%",
261
- height: "100%",
262
- objectFit: "cover"
263
- };
264
- const sourceList = (sources || inferSources(src)).map((s) => ({
265
- ...s,
266
- src: resolveUrl(s.src)
267
- }));
268
- return /* @__PURE__ */ jsx(
269
- "video",
270
- {
271
- className: "background-video",
272
- style,
273
- autoPlay: true,
274
- loop,
275
- muted,
276
- playsInline: true,
277
- poster: resolveUrl(poster),
278
- "aria-hidden": "true",
279
- children: sourceList.map(({ src: sourceSrc, type }, index) => /* @__PURE__ */ jsx("source", { src: sourceSrc, type }, index))
280
- }
281
- );
282
- }
283
- function inferSources(src) {
284
- const sources = [];
285
- const ext = src.split(".").pop()?.toLowerCase();
286
- const basePath = src.slice(0, src.lastIndexOf("."));
287
- if (ext === "mp4") {
288
- sources.push({ src: `${basePath}.webm`, type: "video/webm" });
289
- sources.push({ src, type: "video/mp4" });
290
- } else if (ext === "webm") {
291
- sources.push({ src, type: "video/webm" });
292
- sources.push({ src: `${basePath}.mp4`, type: "video/mp4" });
293
- } else {
294
- sources.push({ src, type: getVideoMimeType(src) });
295
- }
296
- return sources;
297
- }
298
- function getVideoMimeType(src) {
299
- if (src.endsWith(".webm")) return "video/webm";
300
- if (src.endsWith(".ogg") || src.endsWith(".ogv")) return "video/ogg";
301
- return "video/mp4";
170
+ function resolveUrl(url) {
171
+ if (!url || !url.startsWith("/")) return url;
172
+ const basePath = globalThis.uniweb?.activeWebsite?.basePath || "";
173
+ if (!basePath) return url;
174
+ if (url.startsWith(basePath + "/") || url === basePath) return url;
175
+ return basePath + url;
302
176
  }
303
- function Background({
304
- mode,
305
- color,
306
- gradient,
307
- image,
308
- video,
309
- overlay,
310
- className = ""
311
- }) {
312
- if (!mode) return null;
177
+ function renderBackground(background) {
178
+ if (!background?.mode) return null;
313
179
  const containerStyle = {
314
180
  position: "absolute",
315
- inset: 0,
181
+ inset: "0",
316
182
  overflow: "hidden",
317
183
  zIndex: 0
318
184
  };
319
- return /* @__PURE__ */ jsxs(
320
- "div",
321
- {
322
- className: `background background--${mode} ${className}`.trim(),
323
- style: containerStyle,
324
- "aria-hidden": "true",
325
- children: [
326
- mode === MODES.COLOR && /* @__PURE__ */ jsx(ColorBackground, { color }),
327
- mode === MODES.GRADIENT && /* @__PURE__ */ jsx(GradientBackground, { gradient }),
328
- mode === MODES.IMAGE && /* @__PURE__ */ jsx(ImageBackground, { image }),
329
- mode === MODES.VIDEO && /* @__PURE__ */ jsx(VideoBackground, { video }),
330
- /* @__PURE__ */ jsx(Overlay, { overlay })
331
- ]
332
- }
333
- );
334
- }
335
- const VALID_CONTEXTS = ["light", "medium", "dark"];
336
- const getWrapperProps = (block) => {
337
- const theme = block.themeName;
338
- const blockClassName = block.state?.className || "";
339
- let contextClass = "";
340
- if (theme && VALID_CONTEXTS.includes(theme)) {
341
- contextClass = `context-${theme}`;
185
+ const children = [];
186
+ if (background.mode === "color" && background.color) {
187
+ children.push(
188
+ React.createElement("div", {
189
+ key: "bg-color",
190
+ className: "background-color",
191
+ style: { position: "absolute", inset: "0", backgroundColor: background.color },
192
+ "aria-hidden": "true"
193
+ })
194
+ );
342
195
  }
343
- let className = contextClass;
344
- if (blockClassName) {
345
- className = className ? `${className} ${blockClassName}` : blockClassName;
196
+ if (background.mode === "gradient" && background.gradient) {
197
+ const g = background.gradient;
198
+ let bgValue;
199
+ if (typeof g === "string") {
200
+ bgValue = g;
201
+ } else {
202
+ const {
203
+ start = "transparent",
204
+ end = "transparent",
205
+ angle = 0,
206
+ startPosition = 0,
207
+ endPosition = 100,
208
+ startOpacity = 1,
209
+ endOpacity = 1
210
+ } = g;
211
+ const startColor = startOpacity < 1 ? withOpacity(start, startOpacity) : start;
212
+ const endColor = endOpacity < 1 ? withOpacity(end, endOpacity) : end;
213
+ bgValue = `linear-gradient(${angle}deg, ${startColor} ${startPosition}%, ${endColor} ${endPosition}%)`;
214
+ }
215
+ children.push(
216
+ React.createElement("div", {
217
+ key: "bg-gradient",
218
+ className: "background-gradient",
219
+ style: { position: "absolute", inset: "0", background: bgValue },
220
+ "aria-hidden": "true"
221
+ })
222
+ );
346
223
  }
347
- const { background = {} } = block.standardOptions;
348
- const style = {};
349
- if (background.mode) {
350
- style.position = "relative";
351
- style.isolation = "isolate";
224
+ if (background.mode === "image" && background.image?.src) {
225
+ const img = background.image;
226
+ children.push(
227
+ React.createElement("div", {
228
+ key: "bg-image",
229
+ className: "background-image",
230
+ style: {
231
+ position: "absolute",
232
+ inset: "0",
233
+ backgroundImage: `url(${resolveUrl(img.src)})`,
234
+ backgroundPosition: img.position || "center",
235
+ backgroundSize: img.size || "cover",
236
+ backgroundRepeat: "no-repeat"
237
+ },
238
+ "aria-hidden": "true"
239
+ })
240
+ );
352
241
  }
353
- if (block.contextOverrides) {
354
- for (const [key, value] of Object.entries(block.contextOverrides)) {
355
- style[`--${key}`] = value;
242
+ if (background.overlay?.enabled) {
243
+ const ov = background.overlay;
244
+ let overlayStyle;
245
+ if (ov.gradient) {
246
+ const g = ov.gradient;
247
+ overlayStyle = {
248
+ position: "absolute",
249
+ inset: "0",
250
+ pointerEvents: "none",
251
+ background: `linear-gradient(${g.angle || 180}deg, ${g.start || "rgba(0,0,0,0.7)"} ${g.startPosition || 0}%, ${g.end || "rgba(0,0,0,0)"} ${g.endPosition || 100}%)`,
252
+ opacity: ov.opacity ?? 0.5
253
+ };
254
+ } else {
255
+ const baseColor = ov.type === "light" ? "255, 255, 255" : "0, 0, 0";
256
+ overlayStyle = {
257
+ position: "absolute",
258
+ inset: "0",
259
+ pointerEvents: "none",
260
+ backgroundColor: `rgba(${baseColor}, ${ov.opacity ?? 0.5})`
261
+ };
356
262
  }
263
+ children.push(
264
+ React.createElement("div", {
265
+ key: "bg-overlay",
266
+ className: ov.gradient ? "background-overlay background-overlay--gradient" : "background-overlay background-overlay--solid",
267
+ style: overlayStyle,
268
+ "aria-hidden": "true"
269
+ })
270
+ );
357
271
  }
358
- const sectionId = block.stableId || block.id;
359
- return {
360
- id: `section-${sectionId}`,
361
- style,
362
- className,
363
- background
364
- };
365
- };
366
- function BlockRenderer({ block, pure = false, as = "section", extra = {} }) {
272
+ if (children.length === 0) return null;
273
+ return React.createElement("div", {
274
+ className: `background background--${background.mode}`,
275
+ style: containerStyle,
276
+ "aria-hidden": "true"
277
+ }, ...children);
278
+ }
279
+ function renderBlock(block, { pure = false, as = void 0 } = {}) {
367
280
  const Component = block.initComponent();
368
- const entityStore = block.website.entityStore;
369
- const meta = getComponentMeta(block.type);
370
- const resolved = entityStore.resolve(block, meta);
371
- const [asyncData, setAsyncData] = useState(null);
372
- useEffect(() => {
373
- setAsyncData(null);
374
- }, [block]);
375
- useEffect(() => {
376
- if (resolved.status !== "pending") return;
377
- let cancelled = false;
378
- entityStore.fetch(block, meta).then((result) => {
379
- if (!cancelled && result.data) setAsyncData(result.data);
380
- });
381
- return () => {
382
- cancelled = true;
383
- };
384
- }, [block]);
385
- const entityData = resolved.status === "ready" ? resolved.data : asyncData;
386
- block.dataLoading = resolved.status === "pending" && !entityData;
387
281
  if (!Component) {
388
- return /* @__PURE__ */ jsxs("div", { className: "block-error", style: { padding: "1rem", background: "#fef2f2", color: "#dc2626" }, children: [
389
- "Component not found: ",
390
- block.type
391
- ] });
282
+ return React.createElement("div", {
283
+ className: "block-error",
284
+ style: { padding: "1rem", background: "#fef2f2", color: "#dc2626" }
285
+ }, `Component not found: ${block.type}`);
392
286
  }
287
+ const meta = getComponentMeta(block.type);
393
288
  const prepared = prepareProps(block, meta);
394
- let params = prepared.params;
395
- let content = {
396
- ...prepared.content,
397
- ...block.properties
398
- // Frontmatter params overlay (legacy support)
399
- };
400
- if (entityData) {
401
- const merged = { ...content.data };
402
- for (const key of Object.keys(entityData)) {
403
- if (merged[key] === void 0) {
404
- merged[key] = entityData[key];
289
+ const params = prepared.params;
290
+ const content = { ...prepared.content, ...block.properties };
291
+ const entityStore = block.website?.entityStore;
292
+ if (entityStore) {
293
+ const resolved = entityStore.resolve(block, meta);
294
+ if (resolved.status === "ready" && resolved.data) {
295
+ const merged = { ...content.data };
296
+ for (const key of Object.keys(resolved.data)) {
297
+ if (merged[key] === void 0) {
298
+ merged[key] = resolved.data[key];
299
+ }
405
300
  }
301
+ content.data = merged;
406
302
  }
407
- content.data = merged;
303
+ }
304
+ const componentProps = { content, params, block };
305
+ if (pure) {
306
+ return React.createElement(Component, componentProps);
408
307
  }
409
308
  const { background, ...wrapperProps } = getWrapperProps(block);
410
309
  const componentClassName = Component.className;
@@ -413,109 +312,202 @@ function BlockRenderer({ block, pure = false, as = "section", extra = {} }) {
413
312
  }
414
313
  const hasBackground = background?.mode && meta?.background !== "self";
415
314
  block.hasBackground = hasBackground;
416
- const componentProps = {
417
- content,
418
- params,
419
- block
420
- };
421
- if (pure) {
422
- return /* @__PURE__ */ jsx(Component, { ...componentProps, extra });
423
- }
424
- const componentAs = Component.as;
425
- const Wrapper = as === false ? React.Fragment : as !== "section" ? as : componentAs || "section";
426
- const wrapperElementProps = as === false ? {} : wrapperProps;
315
+ const wrapperTag = as !== void 0 && as !== "section" ? as : Component.as || "section";
427
316
  if (hasBackground) {
428
- return /* @__PURE__ */ jsxs(Wrapper, { ...wrapperElementProps, children: [
429
- /* @__PURE__ */ jsx(
430
- Background,
431
- {
432
- mode: background.mode,
433
- color: background.color,
434
- gradient: background.gradient,
435
- image: background.image,
436
- video: background.video,
437
- overlay: background.overlay
438
- }
439
- ),
440
- /* @__PURE__ */ jsx("div", { style: { position: "relative", zIndex: 10 }, children: /* @__PURE__ */ jsx(Component, { ...componentProps }) })
441
- ] });
317
+ return React.createElement(
318
+ wrapperTag,
319
+ wrapperProps,
320
+ renderBackground(background),
321
+ React.createElement(
322
+ "div",
323
+ { style: { position: "relative", zIndex: 10 } },
324
+ React.createElement(Component, componentProps)
325
+ )
326
+ );
442
327
  }
443
- return /* @__PURE__ */ jsx(Wrapper, { ...wrapperElementProps, children: /* @__PURE__ */ jsx(Component, { ...componentProps }) });
328
+ return React.createElement(
329
+ wrapperTag,
330
+ wrapperProps,
331
+ React.createElement(Component, componentProps)
332
+ );
444
333
  }
445
- function Blocks({ blocks, extra = {} }) {
334
+ function renderBlocks(blocks) {
446
335
  if (!blocks || blocks.length === 0) return null;
447
- return blocks.map((block, index) => /* @__PURE__ */ jsx(React.Fragment, { children: /* @__PURE__ */ jsx(BlockRenderer, { block, extra }) }, block.id || index));
448
- }
449
- function DefaultLayout({ header, body, footer }) {
450
- return /* @__PURE__ */ jsxs(Fragment, { children: [
451
- header && /* @__PURE__ */ jsx("header", { children: header }),
452
- body && /* @__PURE__ */ jsx("main", { children: body }),
453
- footer && /* @__PURE__ */ jsx("footer", { children: footer })
454
- ] });
455
- }
456
- function initializeAllBlocks(...blockGroups) {
457
- for (const blocks of blockGroups) {
458
- if (!blocks) continue;
459
- for (const block of blocks) {
460
- block.initComponent();
461
- }
462
- }
463
- }
464
- function mergeParams(pageParams = {}, defaults = {}) {
465
- return { ...defaults, ...pageParams };
336
+ return blocks.map(
337
+ (block, index) => React.createElement(
338
+ React.Fragment,
339
+ { key: block.id || index },
340
+ renderBlock(block)
341
+ )
342
+ );
466
343
  }
467
- function Layout({ page, website }) {
344
+ function renderLayout(page, website) {
468
345
  const layoutName = page.getLayoutName();
469
346
  const RemoteLayout = website.getRemoteLayout(layoutName);
470
347
  const layoutMeta = website.getLayoutMeta(layoutName);
471
348
  const bodyBlocks = page.getBodyBlocks();
472
349
  const areas = page.getLayoutAreas();
473
- const allBlockGroups = [bodyBlocks, ...Object.values(areas)];
474
- initializeAllBlocks(...allBlockGroups);
475
- const bodyElement = bodyBlocks ? /* @__PURE__ */ jsx(Blocks, { blocks: bodyBlocks }) : null;
350
+ const bodyElement = bodyBlocks ? renderBlocks(bodyBlocks) : null;
476
351
  const areaElements = {};
477
352
  for (const [name, blocks] of Object.entries(areas)) {
478
- areaElements[name] = /* @__PURE__ */ jsx(Blocks, { blocks });
353
+ areaElements[name] = renderBlocks(blocks);
479
354
  }
480
355
  if (RemoteLayout) {
481
- const params = mergeParams(page.getLayoutParams(), layoutMeta?.defaults);
482
- return /* @__PURE__ */ jsx(
483
- RemoteLayout,
484
- {
485
- page,
486
- website,
487
- params,
488
- body: bodyElement,
489
- ...areaElements
490
- },
491
- layoutName
492
- );
493
- }
494
- return /* @__PURE__ */ jsx(
495
- DefaultLayout,
496
- {
356
+ const params = { ...layoutMeta?.defaults || {}, ...page.getLayoutParams() || {} };
357
+ return React.createElement(RemoteLayout, {
358
+ page,
359
+ website,
360
+ params,
497
361
  body: bodyElement,
498
362
  ...areaElements
499
- }
500
- );
501
- }
502
- function PageElement({ page, website }) {
363
+ });
364
+ }
503
365
  return React.createElement(
504
- "main",
366
+ React.Fragment,
505
367
  null,
506
- React.createElement(Layout, { page, website })
368
+ areaElements.header && React.createElement("header", null, areaElements.header),
369
+ bodyElement && React.createElement("main", null, bodyElement),
370
+ areaElements.footer && React.createElement("footer", null, areaElements.footer)
371
+ );
372
+ }
373
+ function initPrerender(content, foundation, options = {}) {
374
+ const { onProgress = () => {
375
+ } } = options;
376
+ onProgress("Initializing runtime...");
377
+ const uniweb = createUniweb(content);
378
+ uniweb.setFoundation(foundation);
379
+ if (foundation.default?.capabilities) {
380
+ uniweb.setFoundationConfig(foundation.default.capabilities);
381
+ }
382
+ if (foundation.default?.layoutMeta && uniweb.foundationConfig) {
383
+ uniweb.foundationConfig.layoutMeta = foundation.default.layoutMeta;
384
+ }
385
+ if (content.config?.base && uniweb.activeWebsite?.setBasePath) {
386
+ uniweb.activeWebsite.setBasePath(content.config.base);
387
+ }
388
+ uniweb.childBlockRenderer = function InlineChildBlocks({ blocks, from, pure = false, as = "div" }) {
389
+ const blockList = blocks || from?.childBlocks || [];
390
+ return blockList.map(
391
+ (childBlock, index) => React.createElement(
392
+ React.Fragment,
393
+ { key: childBlock.id || index },
394
+ renderBlock(childBlock, { pure, as })
395
+ )
396
+ );
397
+ };
398
+ return uniweb;
399
+ }
400
+ async function prefetchIcons(siteContent, uniweb, onProgress = () => {
401
+ }) {
402
+ const icons = siteContent.icons?.used || [];
403
+ if (icons.length === 0) return;
404
+ const cdnBase = siteContent.config?.icons?.cdnUrl || "https://uniweb.github.io/icons";
405
+ onProgress(`Fetching ${icons.length} icons for SSR...`);
406
+ const results = await Promise.allSettled(
407
+ icons.map(async (iconRef) => {
408
+ const [family, name] = iconRef.split(":");
409
+ const url = `${cdnBase}/${family}/${family}-${name}.svg`;
410
+ const response = await fetch(url);
411
+ if (!response.ok) throw new Error(`HTTP ${response.status}`);
412
+ const svg = await response.text();
413
+ uniweb.iconCache.set(`${family}:${name}`, svg);
414
+ })
415
+ );
416
+ const succeeded = results.filter((r) => r.status === "fulfilled").length;
417
+ const failed = results.filter((r) => r.status === "rejected").length;
418
+ if (failed > 0) {
419
+ const msg = `Fetched ${succeeded}/${icons.length} icons (${failed} failed)`;
420
+ console.warn(`[prerender] ${msg}`);
421
+ onProgress(` ${msg}`);
422
+ }
423
+ if (uniweb.iconCache.size > 0) {
424
+ siteContent._iconCache = Object.fromEntries(uniweb.iconCache);
425
+ }
426
+ }
427
+ function classifyRenderError(err) {
428
+ const msg = err.message || "";
429
+ if (msg.includes("Invalid hook call") || msg.includes("useState") || msg.includes("useEffect")) {
430
+ return {
431
+ type: "hooks",
432
+ message: "contains components with React hooks (renders client-side)"
433
+ };
434
+ }
435
+ if (msg.includes("Element type is invalid") && msg.includes("null")) {
436
+ return {
437
+ type: "null-component",
438
+ message: "a component resolved to null (often hook-related, renders client-side)"
439
+ };
440
+ }
441
+ return {
442
+ type: "unknown",
443
+ message: msg
444
+ };
445
+ }
446
+ function renderPage(page, website) {
447
+ website.setActivePage(page.route);
448
+ const element = renderLayout(page, website);
449
+ let renderedContent;
450
+ try {
451
+ renderedContent = renderToString(element);
452
+ } catch (err) {
453
+ return { error: classifyRenderError(err) };
454
+ }
455
+ const appearance = website.themeData?.appearance;
456
+ const sectionOverrideCSS = buildSectionOverrides(page.getPageBlocks(), appearance);
457
+ return { renderedContent, sectionOverrideCSS };
458
+ }
459
+ function escapeHtml(str) {
460
+ if (!str) return "";
461
+ return String(str).replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
462
+ }
463
+ function injectPageContent(html, renderedContent, page, options = {}) {
464
+ let result = html;
465
+ if (options.sectionOverrideCSS) {
466
+ const overrideStyle = `<style id="uniweb-page-overrides">
467
+ ${options.sectionOverrideCSS}
468
+ </style>`;
469
+ result = result.replace("</head>", `${overrideStyle}
470
+ </head>`);
471
+ }
472
+ result = result.replace(
473
+ /<div id="root">[\s\S]*?<\/div>/,
474
+ `<div id="root">${renderedContent}</div>`
507
475
  );
476
+ const pageTitle = page.getTitle?.() || page.title;
477
+ if (pageTitle) {
478
+ result = result.replace(
479
+ /<title>.*?<\/title>/,
480
+ `<title>${escapeHtml(pageTitle)}</title>`
481
+ );
482
+ }
483
+ if (page.description) {
484
+ const metaDesc = `<meta name="description" content="${escapeHtml(page.description)}">`;
485
+ if (result.includes('<meta name="description"')) {
486
+ result = result.replace(/<meta name="description"[^>]*>/, metaDesc);
487
+ } else {
488
+ result = result.replace("</head>", `${metaDesc}
489
+ </head>`);
490
+ }
491
+ }
492
+ return result;
508
493
  }
509
494
  export {
510
- BlockRenderer,
511
- Blocks,
512
- Layout,
513
- PageElement,
514
495
  applyDefaults,
515
496
  applySchemas,
497
+ classifyRenderError,
498
+ escapeHtml,
516
499
  getComponentDefaults,
517
500
  getComponentMeta,
501
+ getWrapperProps,
518
502
  guaranteeContentStructure,
519
- prepareProps
503
+ initPrerender,
504
+ injectPageContent,
505
+ prefetchIcons,
506
+ prepareProps,
507
+ renderBackground,
508
+ renderBlock,
509
+ renderBlocks,
510
+ renderLayout,
511
+ renderPage
520
512
  };
521
513
  //# sourceMappingURL=ssr.js.map