@uniweb/runtime 0.5.22 → 0.6.0

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.
package/dist/ssr.js CHANGED
@@ -458,41 +458,41 @@ function initializeAllBlocks(...blockGroups) {
458
458
  }
459
459
  }
460
460
  }
461
+ function mergeParams(pageParams = {}, defaults = {}) {
462
+ return { ...defaults, ...pageParams };
463
+ }
461
464
  function Layout({ page, website }) {
462
- const RemoteLayout = website.getRemoteLayout();
463
- const headerBlocks = page.getHeaderBlocks();
465
+ const layoutName = page.getLayoutName();
466
+ const RemoteLayout = website.getRemoteLayout(layoutName);
467
+ const layoutMeta = website.getLayoutMeta(layoutName);
464
468
  const bodyBlocks = page.getBodyBlocks();
465
- const footerBlocks = page.getFooterBlocks();
466
- const leftBlocks = page.getLeftBlocks();
467
- const rightBlocks = page.getRightBlocks();
468
- initializeAllBlocks(headerBlocks, bodyBlocks, footerBlocks, leftBlocks, rightBlocks);
469
- const headerElement = headerBlocks ? /* @__PURE__ */ jsx(Blocks, { blocks: headerBlocks }) : null;
469
+ const areas = page.getLayoutAreas();
470
+ const allBlockGroups = [bodyBlocks, ...Object.values(areas)];
471
+ initializeAllBlocks(...allBlockGroups);
470
472
  const bodyElement = bodyBlocks ? /* @__PURE__ */ jsx(Blocks, { blocks: bodyBlocks }) : null;
471
- const footerElement = footerBlocks ? /* @__PURE__ */ jsx(Blocks, { blocks: footerBlocks }) : null;
472
- const leftElement = leftBlocks ? /* @__PURE__ */ jsx(Blocks, { blocks: leftBlocks }) : null;
473
- const rightElement = rightBlocks ? /* @__PURE__ */ jsx(Blocks, { blocks: rightBlocks }) : null;
473
+ const areaElements = {};
474
+ for (const [name, blocks] of Object.entries(areas)) {
475
+ areaElements[name] = /* @__PURE__ */ jsx(Blocks, { blocks });
476
+ }
474
477
  if (RemoteLayout) {
478
+ const params = mergeParams(page.getLayoutParams(), layoutMeta?.defaults);
475
479
  return /* @__PURE__ */ jsx(
476
480
  RemoteLayout,
477
481
  {
478
482
  page,
479
483
  website,
480
- header: headerElement,
484
+ params,
481
485
  body: bodyElement,
482
- footer: footerElement,
483
- left: leftElement,
484
- right: rightElement,
485
- leftPanel: leftElement,
486
- rightPanel: rightElement
487
- }
486
+ ...areaElements
487
+ },
488
+ layoutName
488
489
  );
489
490
  }
490
491
  return /* @__PURE__ */ jsx(
491
492
  DefaultLayout,
492
493
  {
493
- header: headerElement,
494
494
  body: bodyElement,
495
- footer: footerElement
495
+ ...areaElements
496
496
  }
497
497
  );
498
498
  }
package/dist/ssr.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"ssr.js","sources":["../src/prepare-props.js","../src/components/Background.jsx","../src/components/BlockRenderer.jsx","../src/components/Blocks.jsx","../src/components/Layout.jsx","../src/ssr.js"],"sourcesContent":["/**\n * Props Preparation for Runtime Guarantees\n *\n * Prepares props for foundation components with:\n * - Param defaults from runtime schema\n * - Guaranteed content structure (no null checks needed)\n *\n * This enables simpler component code by ensuring predictable prop shapes.\n */\n\n/**\n * Guarantee item has flat content structure\n *\n * @param {Object} item - Raw item from parser\n * @returns {Object} Item with guaranteed flat structure\n */\nfunction guaranteeItemStructure(item) {\n return {\n title: item.title || '',\n pretitle: item.pretitle || '',\n subtitle: item.subtitle || '',\n paragraphs: item.paragraphs || [],\n links: item.links || [],\n imgs: item.imgs || [],\n lists: item.lists || [],\n icons: item.icons || [],\n videos: item.videos || [],\n buttons: item.buttons || [],\n data: item.data || {},\n cards: item.cards || [],\n documents: item.documents || [],\n forms: item.forms || [],\n quotes: item.quotes || [],\n headings: item.headings || [],\n }\n}\n\n/**\n * Guarantee content structure exists\n * Returns a flat content object with all standard fields guaranteed to exist\n *\n * @param {Object} parsedContent - Raw parsed content from semantic parser (flat structure)\n * @returns {Object} Content with guaranteed flat structure\n */\nexport function guaranteeContentStructure(parsedContent) {\n const content = parsedContent || {}\n\n return {\n // Flat header fields\n title: content.title || '',\n pretitle: content.pretitle || '',\n subtitle: content.subtitle || '',\n subtitle2: content.subtitle2 || '',\n alignment: content.alignment || null,\n\n // Flat body fields\n paragraphs: content.paragraphs || [],\n links: content.links || [],\n imgs: content.imgs || [],\n lists: content.lists || [],\n icons: content.icons || [],\n videos: content.videos || [],\n buttons: content.buttons || [],\n data: content.data || {},\n cards: content.cards || [],\n documents: content.documents || [],\n forms: content.forms || [],\n quotes: content.quotes || [],\n headings: content.headings || [],\n\n // Items with guaranteed structure\n items: (content.items || []).map(guaranteeItemStructure),\n\n // Sequence for ordered rendering\n sequence: content.sequence || [],\n\n // Preserve raw content if present\n raw: content.raw,\n }\n}\n\n/**\n * Apply a schema to a single object\n * Only processes fields defined in the schema, preserves unknown fields\n *\n * @param {Object} obj - The object to process\n * @param {Object} schema - Schema definition (fieldName -> fieldDef)\n * @returns {Object} Object with schema defaults applied\n */\nfunction applySchemaToObject(obj, schema) {\n if (!obj || typeof obj !== 'object' || Array.isArray(obj)) {\n return obj\n }\n\n const result = { ...obj }\n\n for (const [field, fieldDef] of Object.entries(schema)) {\n // Get the default value - handle both shorthand and full form\n const defaultValue = typeof fieldDef === 'object' ? fieldDef.default : undefined\n\n // Apply default if field is missing and default exists\n if (result[field] === undefined && defaultValue !== undefined) {\n result[field] = defaultValue\n }\n\n // For select fields with options, apply default if value is not among valid options\n if (typeof fieldDef === 'object' && fieldDef.options && Array.isArray(fieldDef.options)) {\n if (result[field] !== undefined && !fieldDef.options.includes(result[field])) {\n // Value exists but is not valid - apply default if available\n if (defaultValue !== undefined) {\n result[field] = defaultValue\n }\n }\n }\n\n // Handle nested object schema\n if (typeof fieldDef === 'object' && fieldDef.type === 'object' && fieldDef.schema && result[field]) {\n result[field] = applySchemaToObject(result[field], fieldDef.schema)\n }\n\n // Handle array with inline schema\n if (typeof fieldDef === 'object' && fieldDef.type === 'array' && fieldDef.of && result[field]) {\n if (typeof fieldDef.of === 'object') {\n result[field] = result[field].map(item => applySchemaToObject(item, fieldDef.of))\n }\n }\n }\n\n return result\n}\n\n/**\n * Apply a schema to a value (object or array of objects)\n *\n * @param {Object|Array} value - The value to process\n * @param {Object} schema - Schema definition\n * @returns {Object|Array} Value with schema defaults applied\n */\nfunction applySchemaToValue(value, schema) {\n if (Array.isArray(value)) {\n return value.map(item => applySchemaToObject(item, schema))\n }\n return applySchemaToObject(value, schema)\n}\n\n/**\n * Apply schemas to content.data\n * Only processes tags that have a matching schema, leaves others untouched\n *\n * @param {Object} data - The data object from content\n * @param {Object} schemas - Schema definitions from runtime meta\n * @returns {Object} Data with schemas applied\n */\nexport function applySchemas(data, schemas) {\n if (!schemas || !data || typeof data !== 'object') {\n return data || {}\n }\n\n const result = { ...data }\n\n for (const [tag, rawValue] of Object.entries(data)) {\n const schema = schemas[tag]\n if (!schema) continue // No schema for this tag - leave as-is\n\n result[tag] = applySchemaToValue(rawValue, schema)\n }\n\n return result\n}\n\n/**\n * Apply param defaults from runtime schema\n *\n * @param {Object} params - Params from frontmatter\n * @param {Object} defaults - Default values from runtime schema\n * @returns {Object} Merged params with defaults applied\n */\nexport function applyDefaults(params, defaults) {\n if (!defaults || Object.keys(defaults).length === 0) {\n return params || {}\n }\n\n return {\n ...defaults,\n ...(params || {}),\n }\n}\n\n/**\n * Prepare props for a component with runtime guarantees\n *\n * @param {Object} block - The block instance\n * @param {Object} meta - Runtime metadata for the component (from meta[componentName])\n * @returns {Object} Prepared props: { content, params }\n */\nexport function prepareProps(block, meta) {\n // Apply param defaults\n const defaults = meta?.defaults || {}\n const params = applyDefaults(block.properties, defaults)\n\n // Guarantee content structure\n const content = guaranteeContentStructure(block.parsedContent)\n\n // Apply schemas to content.data\n const schemas = meta?.schemas || null\n if (schemas && content.data) {\n content.data = applySchemas(content.data, schemas)\n }\n\n return { content, params }\n}\n\n/**\n * Get runtime metadata for a component from the global uniweb instance\n *\n * @param {string} componentName\n * @returns {Object|null}\n */\nexport function getComponentMeta(componentName) {\n return globalThis.uniweb?.getComponentMeta?.(componentName) || null\n}\n\n/**\n * Get default param values for a component\n *\n * @param {string} componentName\n * @returns {Object}\n */\nexport function getComponentDefaults(componentName) {\n return globalThis.uniweb?.getComponentDefaults?.(componentName) || {}\n}\n","/**\n * Background\n *\n * Renders section backgrounds (color, gradient, image, video) with optional overlay.\n * Positioned absolutely behind content with proper z-index stacking.\n *\n * @module @uniweb/runtime/components/Background\n */\n\nimport React from 'react'\n\n/**\n * Background modes\n */\nconst MODES = {\n COLOR: 'color',\n GRADIENT: 'gradient',\n IMAGE: 'image',\n VIDEO: 'video',\n}\n\n/**\n * Default overlay colors\n */\nconst OVERLAY_COLORS = {\n light: 'rgba(255, 255, 255, 0.5)',\n dark: 'rgba(0, 0, 0, 0.5)',\n}\n\n/**\n * Resolve a URL against the site's base path\n * Prepends basePath to absolute URLs (starting with /) so they work\n * under subdirectory deployments (e.g., /templates/international/)\n */\nfunction resolveUrl(url) {\n if (!url || !url.startsWith('/')) return url\n const basePath = globalThis.uniweb?.activeWebsite?.basePath || ''\n if (!basePath) return url\n // Avoid double-prepending\n if (url.startsWith(basePath + '/') || url === basePath) return url\n return basePath + url\n}\n\n/**\n * Render gradient overlay\n */\nfunction GradientOverlay({ gradient, opacity = 0.5 }) {\n const {\n start = 'rgba(0,0,0,0.7)',\n end = 'rgba(0,0,0,0)',\n angle = 180,\n startPosition = 0,\n endPosition = 100,\n } = gradient\n\n const style = {\n position: 'absolute',\n inset: 0,\n background: `linear-gradient(${angle}deg, ${start} ${startPosition}%, ${end} ${endPosition}%)`,\n opacity,\n pointerEvents: 'none',\n }\n\n return <div className=\"background-overlay background-overlay--gradient\" style={style} aria-hidden=\"true\" />\n}\n\n/**\n * Render solid overlay\n */\nfunction SolidOverlay({ type = 'dark', opacity = 0.5 }) {\n const baseColor = type === 'light' ? '255, 255, 255' : '0, 0, 0'\n\n const style = {\n position: 'absolute',\n inset: 0,\n backgroundColor: `rgba(${baseColor}, ${opacity})`,\n pointerEvents: 'none',\n }\n\n return <div className=\"background-overlay background-overlay--solid\" style={style} aria-hidden=\"true\" />\n}\n\n/**\n * Render overlay (gradient or solid)\n */\nfunction Overlay({ overlay }) {\n if (!overlay?.enabled) return null\n\n if (overlay.gradient) {\n return <GradientOverlay gradient={overlay.gradient} opacity={overlay.opacity} />\n }\n\n return <SolidOverlay type={overlay.type} opacity={overlay.opacity} />\n}\n\n/**\n * Color background\n */\nfunction ColorBackground({ color }) {\n if (!color) return null\n\n const style = {\n position: 'absolute',\n inset: 0,\n backgroundColor: color,\n }\n\n return <div className=\"background-color\" style={style} aria-hidden=\"true\" />\n}\n\n/**\n * Gradient background\n */\nfunction GradientBackground({ gradient }) {\n if (!gradient) return null\n\n // Raw CSS gradient string (e.g., \"linear-gradient(to bottom, #000, #333)\")\n if (typeof gradient === 'string') {\n const style = { position: 'absolute', inset: 0, background: gradient }\n return <div className=\"background-gradient\" style={style} aria-hidden=\"true\" />\n }\n\n const {\n start = 'transparent',\n end = 'transparent',\n angle = 0,\n startPosition = 0,\n endPosition = 100,\n startOpacity = 1,\n endOpacity = 1,\n } = gradient\n\n // Convert colors to rgba if opacity is specified\n const startColor = startOpacity < 1 ? withOpacity(start, startOpacity) : start\n const endColor = endOpacity < 1 ? withOpacity(end, endOpacity) : end\n\n const style = {\n position: 'absolute',\n inset: 0,\n background: `linear-gradient(${angle}deg, ${startColor} ${startPosition}%, ${endColor} ${endPosition}%)`,\n }\n\n return <div className=\"background-gradient\" style={style} aria-hidden=\"true\" />\n}\n\n/**\n * Convert hex color to rgba with opacity\n */\nfunction withOpacity(color, opacity) {\n // Handle hex colors\n if (color.startsWith('#')) {\n const r = parseInt(color.slice(1, 3), 16)\n const g = parseInt(color.slice(3, 5), 16)\n const b = parseInt(color.slice(5, 7), 16)\n return `rgba(${r}, ${g}, ${b}, ${opacity})`\n }\n // Handle rgb/rgba\n if (color.startsWith('rgb')) {\n const match = color.match(/rgba?\\((\\d+),\\s*(\\d+),\\s*(\\d+)/)\n if (match) {\n return `rgba(${match[1]}, ${match[2]}, ${match[3]}, ${opacity})`\n }\n }\n // Fallback - return as is\n return color\n}\n\n/**\n * Image background\n */\nfunction ImageBackground({ image }) {\n if (!image?.src) return null\n\n const {\n src,\n position = 'center',\n size = 'cover',\n lazy = true,\n } = image\n\n const style = {\n position: 'absolute',\n inset: 0,\n backgroundImage: `url(${resolveUrl(src)})`,\n backgroundPosition: position,\n backgroundSize: size,\n backgroundRepeat: 'no-repeat',\n }\n\n // For lazy loading, we could use an img tag with loading=\"lazy\"\n // But for backgrounds, CSS is more appropriate\n // The lazy prop could be used for future intersection observer optimization\n\n return <div className=\"background-image\" style={style} aria-hidden=\"true\" />\n}\n\n/**\n * Check if user prefers reduced motion\n */\nfunction prefersReducedMotion() {\n if (typeof window === 'undefined') return false\n return window.matchMedia('(prefers-reduced-motion: reduce)').matches\n}\n\n/**\n * Video background\n *\n * Supports multiple source formats with automatic fallback.\n * Respects prefers-reduced-motion by showing poster image instead.\n */\nfunction VideoBackground({ video }) {\n if (!video?.src) return null\n\n const {\n src,\n sources, // Array of { src, type } for multiple formats\n poster,\n loop = true,\n muted = true,\n } = video\n\n // Respect reduced motion preference - show poster image instead\n if (prefersReducedMotion() && poster) {\n return <ImageBackground image={{ src: poster, size: 'cover', position: 'center' }} />\n }\n\n const style = {\n position: 'absolute',\n inset: 0,\n width: '100%',\n height: '100%',\n objectFit: 'cover',\n }\n\n // Build source list: explicit sources array, or infer from src\n const sourceList = (sources || inferSources(src)).map(s => ({\n ...s,\n src: resolveUrl(s.src)\n }))\n\n return (\n <video\n className=\"background-video\"\n style={style}\n autoPlay\n loop={loop}\n muted={muted}\n playsInline\n poster={resolveUrl(poster)}\n aria-hidden=\"true\"\n >\n {sourceList.map(({ src: sourceSrc, type }, index) => (\n <source key={index} src={sourceSrc} type={type} />\n ))}\n </video>\n )\n}\n\n/**\n * Infer multiple source formats from a single src\n *\n * If given \"video.mp4\", also tries \"video.webm\" (better compression)\n * Browser will use first supported format\n */\nfunction inferSources(src) {\n const sources = []\n const ext = src.split('.').pop()?.toLowerCase()\n const basePath = src.slice(0, src.lastIndexOf('.'))\n\n // Prefer webm (better compression), fall back to original\n if (ext === 'mp4') {\n sources.push({ src: `${basePath}.webm`, type: 'video/webm' })\n sources.push({ src, type: 'video/mp4' })\n } else if (ext === 'webm') {\n sources.push({ src, type: 'video/webm' })\n sources.push({ src: `${basePath}.mp4`, type: 'video/mp4' })\n } else {\n // Single source for other formats\n sources.push({ src, type: getVideoMimeType(src) })\n }\n\n return sources\n}\n\n/**\n * Get video MIME type from URL\n */\nfunction getVideoMimeType(src) {\n if (src.endsWith('.webm')) return 'video/webm'\n if (src.endsWith('.ogg') || src.endsWith('.ogv')) return 'video/ogg'\n return 'video/mp4'\n}\n\n/**\n * Background component\n *\n * @param {Object} props\n * @param {string} props.mode - Background mode: 'color', 'gradient', 'image', 'video'\n * @param {string} props.color - Color value (for color mode)\n * @param {Object} props.gradient - Gradient configuration\n * @param {Object} props.image - Image configuration\n * @param {Object} props.video - Video configuration\n * @param {Object} props.overlay - Overlay configuration\n * @param {string} props.className - Additional CSS class\n */\nexport default function Background({\n mode,\n color,\n gradient,\n image,\n video,\n overlay,\n className = '',\n}) {\n // No background to render\n if (!mode) return null\n\n const containerStyle = {\n position: 'absolute',\n inset: 0,\n overflow: 'hidden',\n zIndex: 0,\n }\n\n return (\n <div\n className={`background background--${mode} ${className}`.trim()}\n style={containerStyle}\n aria-hidden=\"true\"\n >\n {/* Render background based on mode */}\n {mode === MODES.COLOR && <ColorBackground color={color} />}\n {mode === MODES.GRADIENT && <GradientBackground gradient={gradient} />}\n {mode === MODES.IMAGE && <ImageBackground image={image} />}\n {mode === MODES.VIDEO && <VideoBackground video={video} />}\n\n {/* Overlay on top of background */}\n <Overlay overlay={overlay} />\n </div>\n )\n}\n\n/**\n * Export background modes for external use\n */\nexport { MODES as BackgroundModes }\n","/**\n * BlockRenderer\n *\n * Bridges Block data to foundation components.\n * Handles theming, wrapper props, and runtime guarantees.\n * Uses EntityStore for entity-aware data resolution.\n */\n\nimport React, { useState, useEffect } from 'react'\nimport { prepareProps, getComponentMeta } from '../prepare-props.js'\nimport Background from './Background.jsx'\n\n/**\n * Valid color contexts\n */\nconst VALID_CONTEXTS = ['light', 'medium', 'dark']\n\n/**\n * Build wrapper props from block configuration\n */\nconst getWrapperProps = (block) => {\n const theme = block.themeName\n const blockClassName = block.state?.className || ''\n\n // Build context class (context-light, context-medium, context-dark)\n let contextClass = ''\n if (theme && VALID_CONTEXTS.includes(theme)) {\n contextClass = `context-${theme}`\n }\n\n let className = contextClass\n if (blockClassName) {\n className = className ? `${className} ${blockClassName}` : blockClassName\n }\n\n const { background = {} } = block.standardOptions\n const style = {}\n\n // If background has content, ensure relative positioning for z-index stacking\n if (background.mode) {\n style.position = 'relative'\n }\n\n // Apply context overrides as inline CSS custom properties.\n // These override the context class tokens for this specific section.\n if (block.contextOverrides) {\n for (const [key, value] of Object.entries(block.contextOverrides)) {\n style[`--${key}`] = value\n }\n }\n\n // Use stableId for DOM ID if available (stable across reordering)\n // Falls back to positional id for backwards compatibility\n const sectionId = block.stableId || block.id\n\n return {\n id: `section-${sectionId}`,\n style,\n className,\n background\n }\n}\n\n/**\n * BlockRenderer component\n *\n * @param {Object} props\n * @param {Block} props.block - Block instance to render\n * @param {boolean} props.pure - If true, render component without wrapper\n * @param {string|false} props.as - Element type to render as ('section', 'div', 'article', etc.) or false for Fragment\n * @param {Object} props.extra - Extra props to pass to the component\n */\nexport default function BlockRenderer({ block, pure = false, as = 'section', extra = {} }) {\n const Component = block.initComponent()\n\n // Entity-aware data resolution via EntityStore\n const entityStore = block.website.entityStore\n const meta = getComponentMeta(block.type)\n const resolved = entityStore.resolve(block, meta)\n\n // Async data for when resolve returns 'pending' (runtime fetches)\n const [asyncData, setAsyncData] = useState(null)\n\n // Reset async data when block changes (SPA navigation)\n useEffect(() => {\n setAsyncData(null)\n }, [block])\n\n // Fetch missing data asynchronously\n useEffect(() => {\n if (resolved.status !== 'pending') return\n\n let cancelled = false\n entityStore.fetch(block, meta).then((result) => {\n if (!cancelled && result.data) setAsyncData(result.data)\n })\n return () => { cancelled = true }\n }, [block])\n\n // Use sync resolved data when available, fall back to async\n const entityData = resolved.status === 'ready' ? resolved.data : asyncData\n\n // Signal to component that data is loading\n block.dataLoading = resolved.status === 'pending' && !entityData\n\n if (!Component) {\n return (\n <div className=\"block-error\" style={{ padding: '1rem', background: '#fef2f2', color: '#dc2626' }}>\n Component not found: {block.type}\n </div>\n )\n }\n\n // Build content and params with runtime guarantees\n // Sources:\n // 1. parsedContent - semantic parser output (flat: title, paragraphs, links, etc.)\n // 2. block.properties - params from frontmatter (theme, alignment, etc.)\n // 3. meta - defaults from component meta.js\n const prepared = prepareProps(block, meta)\n let params = prepared.params\n\n let content = {\n ...prepared.content,\n ...block.properties, // Frontmatter params overlay (legacy support)\n }\n\n // Merge entity data resolved by EntityStore\n // Only fill in keys that don't already exist — section-level fetch data\n // (already in content.data from parsedContent) takes priority over inherited data.\n if (entityData) {\n const merged = { ...content.data }\n for (const key of Object.keys(entityData)) {\n if (merged[key] === undefined) {\n merged[key] = entityData[key]\n }\n }\n content.data = merged\n }\n\n const { background, ...wrapperProps } = getWrapperProps(block)\n\n // Merge Component.className (static classes declared on the component function)\n // Order: context-{theme} + block.state.className + Component.className\n const componentClassName = Component.className\n if (componentClassName) {\n wrapperProps.className = wrapperProps.className\n ? `${wrapperProps.className} ${componentClassName}`\n : componentClassName\n }\n\n // Check if component handles its own background (background: 'self' in meta.js)\n // Components that render their own background layer (solid colors, insets, effects)\n // opt out so the runtime doesn't render an occluded layer underneath.\n const hasBackground = background?.mode && meta?.background !== 'self'\n\n // Signal to the component that the engine is rendering a background.\n // Components check block.hasBackground to skip their own opaque bg\n // and let the engine background show through.\n block.hasBackground = hasBackground\n\n const componentProps = {\n content,\n params,\n block\n }\n\n if (pure) {\n return <Component {...componentProps} extra={extra} />\n }\n\n // Determine wrapper element:\n // - as={false} → Fragment (no wrapper)\n // - as prop explicitly set (not default 'section') → use as prop\n // - Component.as → use component's declared tag\n // - fallback → 'section'\n const componentAs = Component.as\n const Wrapper = as === false ? React.Fragment : (as !== 'section' ? as : componentAs || 'section')\n // Fragment doesn't accept props, so only pass them for real elements\n const wrapperElementProps = as === false ? {} : wrapperProps\n\n // Render with or without background\n if (hasBackground) {\n return (\n <Wrapper {...wrapperElementProps}>\n {/* Background layer (positioned absolutely) */}\n <Background\n mode={background.mode}\n color={background.color}\n gradient={background.gradient}\n image={background.image}\n video={background.video}\n overlay={background.overlay}\n />\n\n {/* Content layer (above background) */}\n <div className=\"relative z-10\">\n <Component {...componentProps} />\n </div>\n </Wrapper>\n )\n }\n\n // No background - simpler render without extra wrapper\n return (\n <Wrapper {...wrapperElementProps}>\n <Component {...componentProps} />\n </Wrapper>\n )\n}\n","/**\n * Blocks\n *\n * Renders an array of blocks for a layout area (header, body, footer, panels).\n * Used by the Layout component to pre-render each area.\n */\n\nimport React from 'react'\nimport BlockRenderer from './BlockRenderer.jsx'\n\n/**\n * Render a list of blocks\n *\n * @param {Object} props\n * @param {Block[]} props.blocks - Array of Block instances to render\n * @param {Object} [props.extra] - Extra props to pass to each block\n */\nexport default function Blocks({ blocks, extra = {} }) {\n if (!blocks || blocks.length === 0) return null\n\n return blocks.map((block, index) => (\n <React.Fragment key={block.id || index}>\n <BlockRenderer block={block} extra={extra} />\n </React.Fragment>\n ))\n}\n","/**\n * Layout\n *\n * Orchestrates page rendering by assembling layout areas (header, body, footer, panels).\n * Supports foundation-provided custom Layout components via website.getRemoteLayout().\n *\n * Layout Areas:\n * - header: Top navigation, branding (from layout/header.md)\n * - body: Main page content (from page sections)\n * - footer: Bottom navigation, copyright (from layout/footer.md)\n * - left: Left sidebar/panel (from layout/left.md)\n * - right: Right sidebar/panel (from layout/right.md)\n *\n * Custom Layouts:\n * Foundations can provide a custom Layout via src/exports.js:\n *\n * ```jsx\n * // src/exports.js\n * import Layout from './components/Layout'\n *\n * export default {\n * Layout,\n * props: {\n * themeToggleEnabled: true,\n * }\n * }\n * ```\n *\n * The Layout component receives pre-rendered areas as props:\n * - page, website: Runtime context\n * - header, body, footer: Pre-rendered React elements\n * - left, right (or leftPanel, rightPanel): Sidebar panels\n */\n\nimport Blocks from './Blocks.jsx'\n\n/**\n * Default layout - renders header, body, footer in sequence\n * (no panels in default layout)\n */\nfunction DefaultLayout({ header, body, footer }) {\n return (\n <>\n {header && <header>{header}</header>}\n {body && <main>{body}</main>}\n {footer && <footer>{footer}</footer>}\n </>\n )\n}\n\n/**\n * Initialize all blocks to ensure cross-block communication works.\n * Must be called before rendering so getNextBlockInfo() can access sibling contexts.\n *\n * @param {Block[][]} blockGroups - Arrays of blocks from all layout areas\n */\nfunction initializeAllBlocks(...blockGroups) {\n for (const blocks of blockGroups) {\n if (!blocks) continue\n for (const block of blocks) {\n block.initComponent()\n }\n }\n}\n\n/**\n * Layout component\n *\n * @param {Object} props\n * @param {Page} props.page - Current page instance\n * @param {Website} props.website - Website instance\n */\nexport default function Layout({ page, website }) {\n // Check if foundation provides a custom Layout\n const RemoteLayout = website.getRemoteLayout()\n\n // Get block groups from page (respects layout preferences)\n const headerBlocks = page.getHeaderBlocks()\n const bodyBlocks = page.getBodyBlocks()\n const footerBlocks = page.getFooterBlocks()\n const leftBlocks = page.getLeftBlocks()\n const rightBlocks = page.getRightBlocks()\n\n // Pre-initialize all blocks before rendering any.\n // This ensures cross-block communication (getNextBlockInfo, getPrevBlockInfo)\n // can access sibling block contexts that are set in initComponent().\n initializeAllBlocks(headerBlocks, bodyBlocks, footerBlocks, leftBlocks, rightBlocks)\n\n // Pre-render each area as React elements\n const headerElement = headerBlocks ? <Blocks blocks={headerBlocks} /> : null\n const bodyElement = bodyBlocks ? <Blocks blocks={bodyBlocks} /> : null\n const footerElement = footerBlocks ? <Blocks blocks={footerBlocks} /> : null\n const leftElement = leftBlocks ? <Blocks blocks={leftBlocks} /> : null\n const rightElement = rightBlocks ? <Blocks blocks={rightBlocks} /> : null\n\n // Use foundation's custom Layout if provided\n if (RemoteLayout) {\n return (\n <RemoteLayout\n page={page}\n website={website}\n header={headerElement}\n body={bodyElement}\n footer={footerElement}\n left={leftElement}\n right={rightElement}\n // Aliases for backwards compatibility\n leftPanel={leftElement}\n rightPanel={rightElement}\n />\n )\n }\n\n // Default layout\n return (\n <DefaultLayout\n header={headerElement}\n body={bodyElement}\n footer={footerElement}\n />\n )\n}\n","/**\n * @uniweb/runtime/ssr - Server-Side Rendering Entry Point\n *\n * Node.js-compatible exports for SSG/prerendering.\n * This module is built to a standalone bundle that can be imported\n * directly by Node.js without Vite transpilation.\n *\n * Usage in prerender.js:\n * import { renderPage, Blocks, BlockRenderer } from '@uniweb/runtime/ssr'\n */\n\nimport React from 'react'\n\n// Props preparation (no browser APIs)\nexport {\n prepareProps,\n applySchemas,\n applyDefaults,\n guaranteeContentStructure,\n getComponentMeta,\n getComponentDefaults\n} from './prepare-props.js'\n\n// Components for rendering\nexport { default as BlockRenderer } from './components/BlockRenderer.jsx'\nexport { default as Blocks } from './components/Blocks.jsx'\nexport { default as Layout } from './components/Layout.jsx'\n\n// Re-export Layout's DefaultLayout for direct use\nimport LayoutComponent from './components/Layout.jsx'\n\n/**\n * Render a page to React elements\n *\n * This is the main entry point for SSG. It returns a React element\n * that can be passed to renderToString().\n *\n * @param {Object} props\n * @param {Page} props.page - The page instance to render\n * @param {Website} props.website - The website instance\n * @returns {React.ReactElement}\n */\nexport function PageElement({ page, website }) {\n return React.createElement(\n 'main',\n null,\n React.createElement(LayoutComponent, { page, website })\n )\n}\n"],"names":["style","LayoutComponent"],"mappings":"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"}
1
+ {"version":3,"file":"ssr.js","sources":["../src/prepare-props.js","../src/components/Background.jsx","../src/components/BlockRenderer.jsx","../src/components/Blocks.jsx","../src/components/Layout.jsx","../src/ssr.js"],"sourcesContent":["/**\n * Props Preparation for Runtime Guarantees\n *\n * Prepares props for foundation components with:\n * - Param defaults from runtime schema\n * - Guaranteed content structure (no null checks needed)\n *\n * This enables simpler component code by ensuring predictable prop shapes.\n */\n\n/**\n * Guarantee item has flat content structure\n *\n * @param {Object} item - Raw item from parser\n * @returns {Object} Item with guaranteed flat structure\n */\nfunction guaranteeItemStructure(item) {\n return {\n title: item.title || '',\n pretitle: item.pretitle || '',\n subtitle: item.subtitle || '',\n paragraphs: item.paragraphs || [],\n links: item.links || [],\n imgs: item.imgs || [],\n lists: item.lists || [],\n icons: item.icons || [],\n videos: item.videos || [],\n buttons: item.buttons || [],\n data: item.data || {},\n cards: item.cards || [],\n documents: item.documents || [],\n forms: item.forms || [],\n quotes: item.quotes || [],\n headings: item.headings || [],\n }\n}\n\n/**\n * Guarantee content structure exists\n * Returns a flat content object with all standard fields guaranteed to exist\n *\n * @param {Object} parsedContent - Raw parsed content from semantic parser (flat structure)\n * @returns {Object} Content with guaranteed flat structure\n */\nexport function guaranteeContentStructure(parsedContent) {\n const content = parsedContent || {}\n\n return {\n // Flat header fields\n title: content.title || '',\n pretitle: content.pretitle || '',\n subtitle: content.subtitle || '',\n subtitle2: content.subtitle2 || '',\n alignment: content.alignment || null,\n\n // Flat body fields\n paragraphs: content.paragraphs || [],\n links: content.links || [],\n imgs: content.imgs || [],\n lists: content.lists || [],\n icons: content.icons || [],\n videos: content.videos || [],\n buttons: content.buttons || [],\n data: content.data || {},\n cards: content.cards || [],\n documents: content.documents || [],\n forms: content.forms || [],\n quotes: content.quotes || [],\n headings: content.headings || [],\n\n // Items with guaranteed structure\n items: (content.items || []).map(guaranteeItemStructure),\n\n // Sequence for ordered rendering\n sequence: content.sequence || [],\n\n // Preserve raw content if present\n raw: content.raw,\n }\n}\n\n/**\n * Apply a schema to a single object\n * Only processes fields defined in the schema, preserves unknown fields\n *\n * @param {Object} obj - The object to process\n * @param {Object} schema - Schema definition (fieldName -> fieldDef)\n * @returns {Object} Object with schema defaults applied\n */\nfunction applySchemaToObject(obj, schema) {\n if (!obj || typeof obj !== 'object' || Array.isArray(obj)) {\n return obj\n }\n\n const result = { ...obj }\n\n for (const [field, fieldDef] of Object.entries(schema)) {\n // Get the default value - handle both shorthand and full form\n const defaultValue = typeof fieldDef === 'object' ? fieldDef.default : undefined\n\n // Apply default if field is missing and default exists\n if (result[field] === undefined && defaultValue !== undefined) {\n result[field] = defaultValue\n }\n\n // For select fields with options, apply default if value is not among valid options\n if (typeof fieldDef === 'object' && fieldDef.options && Array.isArray(fieldDef.options)) {\n if (result[field] !== undefined && !fieldDef.options.includes(result[field])) {\n // Value exists but is not valid - apply default if available\n if (defaultValue !== undefined) {\n result[field] = defaultValue\n }\n }\n }\n\n // Handle nested object schema\n if (typeof fieldDef === 'object' && fieldDef.type === 'object' && fieldDef.schema && result[field]) {\n result[field] = applySchemaToObject(result[field], fieldDef.schema)\n }\n\n // Handle array with inline schema\n if (typeof fieldDef === 'object' && fieldDef.type === 'array' && fieldDef.of && result[field]) {\n if (typeof fieldDef.of === 'object') {\n result[field] = result[field].map(item => applySchemaToObject(item, fieldDef.of))\n }\n }\n }\n\n return result\n}\n\n/**\n * Apply a schema to a value (object or array of objects)\n *\n * @param {Object|Array} value - The value to process\n * @param {Object} schema - Schema definition\n * @returns {Object|Array} Value with schema defaults applied\n */\nfunction applySchemaToValue(value, schema) {\n if (Array.isArray(value)) {\n return value.map(item => applySchemaToObject(item, schema))\n }\n return applySchemaToObject(value, schema)\n}\n\n/**\n * Apply schemas to content.data\n * Only processes tags that have a matching schema, leaves others untouched\n *\n * @param {Object} data - The data object from content\n * @param {Object} schemas - Schema definitions from runtime meta\n * @returns {Object} Data with schemas applied\n */\nexport function applySchemas(data, schemas) {\n if (!schemas || !data || typeof data !== 'object') {\n return data || {}\n }\n\n const result = { ...data }\n\n for (const [tag, rawValue] of Object.entries(data)) {\n const schema = schemas[tag]\n if (!schema) continue // No schema for this tag - leave as-is\n\n result[tag] = applySchemaToValue(rawValue, schema)\n }\n\n return result\n}\n\n/**\n * Apply param defaults from runtime schema\n *\n * @param {Object} params - Params from frontmatter\n * @param {Object} defaults - Default values from runtime schema\n * @returns {Object} Merged params with defaults applied\n */\nexport function applyDefaults(params, defaults) {\n if (!defaults || Object.keys(defaults).length === 0) {\n return params || {}\n }\n\n return {\n ...defaults,\n ...(params || {}),\n }\n}\n\n/**\n * Prepare props for a component with runtime guarantees\n *\n * @param {Object} block - The block instance\n * @param {Object} meta - Runtime metadata for the component (from meta[componentName])\n * @returns {Object} Prepared props: { content, params }\n */\nexport function prepareProps(block, meta) {\n // Apply param defaults\n const defaults = meta?.defaults || {}\n const params = applyDefaults(block.properties, defaults)\n\n // Guarantee content structure\n const content = guaranteeContentStructure(block.parsedContent)\n\n // Apply schemas to content.data\n const schemas = meta?.schemas || null\n if (schemas && content.data) {\n content.data = applySchemas(content.data, schemas)\n }\n\n return { content, params }\n}\n\n/**\n * Get runtime metadata for a component from the global uniweb instance\n *\n * @param {string} componentName\n * @returns {Object|null}\n */\nexport function getComponentMeta(componentName) {\n return globalThis.uniweb?.getComponentMeta?.(componentName) || null\n}\n\n/**\n * Get default param values for a component\n *\n * @param {string} componentName\n * @returns {Object}\n */\nexport function getComponentDefaults(componentName) {\n return globalThis.uniweb?.getComponentDefaults?.(componentName) || {}\n}\n","/**\n * Background\n *\n * Renders section backgrounds (color, gradient, image, video) with optional overlay.\n * Positioned absolutely behind content with proper z-index stacking.\n *\n * @module @uniweb/runtime/components/Background\n */\n\nimport React from 'react'\n\n/**\n * Background modes\n */\nconst MODES = {\n COLOR: 'color',\n GRADIENT: 'gradient',\n IMAGE: 'image',\n VIDEO: 'video',\n}\n\n/**\n * Default overlay colors\n */\nconst OVERLAY_COLORS = {\n light: 'rgba(255, 255, 255, 0.5)',\n dark: 'rgba(0, 0, 0, 0.5)',\n}\n\n/**\n * Resolve a URL against the site's base path\n * Prepends basePath to absolute URLs (starting with /) so they work\n * under subdirectory deployments (e.g., /templates/international/)\n */\nfunction resolveUrl(url) {\n if (!url || !url.startsWith('/')) return url\n const basePath = globalThis.uniweb?.activeWebsite?.basePath || ''\n if (!basePath) return url\n // Avoid double-prepending\n if (url.startsWith(basePath + '/') || url === basePath) return url\n return basePath + url\n}\n\n/**\n * Render gradient overlay\n */\nfunction GradientOverlay({ gradient, opacity = 0.5 }) {\n const {\n start = 'rgba(0,0,0,0.7)',\n end = 'rgba(0,0,0,0)',\n angle = 180,\n startPosition = 0,\n endPosition = 100,\n } = gradient\n\n const style = {\n position: 'absolute',\n inset: 0,\n background: `linear-gradient(${angle}deg, ${start} ${startPosition}%, ${end} ${endPosition}%)`,\n opacity,\n pointerEvents: 'none',\n }\n\n return <div className=\"background-overlay background-overlay--gradient\" style={style} aria-hidden=\"true\" />\n}\n\n/**\n * Render solid overlay\n */\nfunction SolidOverlay({ type = 'dark', opacity = 0.5 }) {\n const baseColor = type === 'light' ? '255, 255, 255' : '0, 0, 0'\n\n const style = {\n position: 'absolute',\n inset: 0,\n backgroundColor: `rgba(${baseColor}, ${opacity})`,\n pointerEvents: 'none',\n }\n\n return <div className=\"background-overlay background-overlay--solid\" style={style} aria-hidden=\"true\" />\n}\n\n/**\n * Render overlay (gradient or solid)\n */\nfunction Overlay({ overlay }) {\n if (!overlay?.enabled) return null\n\n if (overlay.gradient) {\n return <GradientOverlay gradient={overlay.gradient} opacity={overlay.opacity} />\n }\n\n return <SolidOverlay type={overlay.type} opacity={overlay.opacity} />\n}\n\n/**\n * Color background\n */\nfunction ColorBackground({ color }) {\n if (!color) return null\n\n const style = {\n position: 'absolute',\n inset: 0,\n backgroundColor: color,\n }\n\n return <div className=\"background-color\" style={style} aria-hidden=\"true\" />\n}\n\n/**\n * Gradient background\n */\nfunction GradientBackground({ gradient }) {\n if (!gradient) return null\n\n // Raw CSS gradient string (e.g., \"linear-gradient(to bottom, #000, #333)\")\n if (typeof gradient === 'string') {\n const style = { position: 'absolute', inset: 0, background: gradient }\n return <div className=\"background-gradient\" style={style} aria-hidden=\"true\" />\n }\n\n const {\n start = 'transparent',\n end = 'transparent',\n angle = 0,\n startPosition = 0,\n endPosition = 100,\n startOpacity = 1,\n endOpacity = 1,\n } = gradient\n\n // Convert colors to rgba if opacity is specified\n const startColor = startOpacity < 1 ? withOpacity(start, startOpacity) : start\n const endColor = endOpacity < 1 ? withOpacity(end, endOpacity) : end\n\n const style = {\n position: 'absolute',\n inset: 0,\n background: `linear-gradient(${angle}deg, ${startColor} ${startPosition}%, ${endColor} ${endPosition}%)`,\n }\n\n return <div className=\"background-gradient\" style={style} aria-hidden=\"true\" />\n}\n\n/**\n * Convert hex color to rgba with opacity\n */\nfunction withOpacity(color, opacity) {\n // Handle hex colors\n if (color.startsWith('#')) {\n const r = parseInt(color.slice(1, 3), 16)\n const g = parseInt(color.slice(3, 5), 16)\n const b = parseInt(color.slice(5, 7), 16)\n return `rgba(${r}, ${g}, ${b}, ${opacity})`\n }\n // Handle rgb/rgba\n if (color.startsWith('rgb')) {\n const match = color.match(/rgba?\\((\\d+),\\s*(\\d+),\\s*(\\d+)/)\n if (match) {\n return `rgba(${match[1]}, ${match[2]}, ${match[3]}, ${opacity})`\n }\n }\n // Fallback - return as is\n return color\n}\n\n/**\n * Image background\n */\nfunction ImageBackground({ image }) {\n if (!image?.src) return null\n\n const {\n src,\n position = 'center',\n size = 'cover',\n lazy = true,\n } = image\n\n const style = {\n position: 'absolute',\n inset: 0,\n backgroundImage: `url(${resolveUrl(src)})`,\n backgroundPosition: position,\n backgroundSize: size,\n backgroundRepeat: 'no-repeat',\n }\n\n // For lazy loading, we could use an img tag with loading=\"lazy\"\n // But for backgrounds, CSS is more appropriate\n // The lazy prop could be used for future intersection observer optimization\n\n return <div className=\"background-image\" style={style} aria-hidden=\"true\" />\n}\n\n/**\n * Check if user prefers reduced motion\n */\nfunction prefersReducedMotion() {\n if (typeof window === 'undefined') return false\n return window.matchMedia('(prefers-reduced-motion: reduce)').matches\n}\n\n/**\n * Video background\n *\n * Supports multiple source formats with automatic fallback.\n * Respects prefers-reduced-motion by showing poster image instead.\n */\nfunction VideoBackground({ video }) {\n if (!video?.src) return null\n\n const {\n src,\n sources, // Array of { src, type } for multiple formats\n poster,\n loop = true,\n muted = true,\n } = video\n\n // Respect reduced motion preference - show poster image instead\n if (prefersReducedMotion() && poster) {\n return <ImageBackground image={{ src: poster, size: 'cover', position: 'center' }} />\n }\n\n const style = {\n position: 'absolute',\n inset: 0,\n width: '100%',\n height: '100%',\n objectFit: 'cover',\n }\n\n // Build source list: explicit sources array, or infer from src\n const sourceList = (sources || inferSources(src)).map(s => ({\n ...s,\n src: resolveUrl(s.src)\n }))\n\n return (\n <video\n className=\"background-video\"\n style={style}\n autoPlay\n loop={loop}\n muted={muted}\n playsInline\n poster={resolveUrl(poster)}\n aria-hidden=\"true\"\n >\n {sourceList.map(({ src: sourceSrc, type }, index) => (\n <source key={index} src={sourceSrc} type={type} />\n ))}\n </video>\n )\n}\n\n/**\n * Infer multiple source formats from a single src\n *\n * If given \"video.mp4\", also tries \"video.webm\" (better compression)\n * Browser will use first supported format\n */\nfunction inferSources(src) {\n const sources = []\n const ext = src.split('.').pop()?.toLowerCase()\n const basePath = src.slice(0, src.lastIndexOf('.'))\n\n // Prefer webm (better compression), fall back to original\n if (ext === 'mp4') {\n sources.push({ src: `${basePath}.webm`, type: 'video/webm' })\n sources.push({ src, type: 'video/mp4' })\n } else if (ext === 'webm') {\n sources.push({ src, type: 'video/webm' })\n sources.push({ src: `${basePath}.mp4`, type: 'video/mp4' })\n } else {\n // Single source for other formats\n sources.push({ src, type: getVideoMimeType(src) })\n }\n\n return sources\n}\n\n/**\n * Get video MIME type from URL\n */\nfunction getVideoMimeType(src) {\n if (src.endsWith('.webm')) return 'video/webm'\n if (src.endsWith('.ogg') || src.endsWith('.ogv')) return 'video/ogg'\n return 'video/mp4'\n}\n\n/**\n * Background component\n *\n * @param {Object} props\n * @param {string} props.mode - Background mode: 'color', 'gradient', 'image', 'video'\n * @param {string} props.color - Color value (for color mode)\n * @param {Object} props.gradient - Gradient configuration\n * @param {Object} props.image - Image configuration\n * @param {Object} props.video - Video configuration\n * @param {Object} props.overlay - Overlay configuration\n * @param {string} props.className - Additional CSS class\n */\nexport default function Background({\n mode,\n color,\n gradient,\n image,\n video,\n overlay,\n className = '',\n}) {\n // No background to render\n if (!mode) return null\n\n const containerStyle = {\n position: 'absolute',\n inset: 0,\n overflow: 'hidden',\n zIndex: 0,\n }\n\n return (\n <div\n className={`background background--${mode} ${className}`.trim()}\n style={containerStyle}\n aria-hidden=\"true\"\n >\n {/* Render background based on mode */}\n {mode === MODES.COLOR && <ColorBackground color={color} />}\n {mode === MODES.GRADIENT && <GradientBackground gradient={gradient} />}\n {mode === MODES.IMAGE && <ImageBackground image={image} />}\n {mode === MODES.VIDEO && <VideoBackground video={video} />}\n\n {/* Overlay on top of background */}\n <Overlay overlay={overlay} />\n </div>\n )\n}\n\n/**\n * Export background modes for external use\n */\nexport { MODES as BackgroundModes }\n","/**\n * BlockRenderer\n *\n * Bridges Block data to foundation components.\n * Handles theming, wrapper props, and runtime guarantees.\n * Uses EntityStore for entity-aware data resolution.\n */\n\nimport React, { useState, useEffect } from 'react'\nimport { prepareProps, getComponentMeta } from '../prepare-props.js'\nimport Background from './Background.jsx'\n\n/**\n * Valid color contexts\n */\nconst VALID_CONTEXTS = ['light', 'medium', 'dark']\n\n/**\n * Build wrapper props from block configuration\n */\nconst getWrapperProps = (block) => {\n const theme = block.themeName\n const blockClassName = block.state?.className || ''\n\n // Build context class (context-light, context-medium, context-dark)\n let contextClass = ''\n if (theme && VALID_CONTEXTS.includes(theme)) {\n contextClass = `context-${theme}`\n }\n\n let className = contextClass\n if (blockClassName) {\n className = className ? `${className} ${blockClassName}` : blockClassName\n }\n\n const { background = {} } = block.standardOptions\n const style = {}\n\n // If background has content, ensure relative positioning for z-index stacking\n if (background.mode) {\n style.position = 'relative'\n }\n\n // Apply context overrides as inline CSS custom properties.\n // These override the context class tokens for this specific section.\n if (block.contextOverrides) {\n for (const [key, value] of Object.entries(block.contextOverrides)) {\n style[`--${key}`] = value\n }\n }\n\n // Use stableId for DOM ID if available (stable across reordering)\n // Falls back to positional id for backwards compatibility\n const sectionId = block.stableId || block.id\n\n return {\n id: `section-${sectionId}`,\n style,\n className,\n background\n }\n}\n\n/**\n * BlockRenderer component\n *\n * @param {Object} props\n * @param {Block} props.block - Block instance to render\n * @param {boolean} props.pure - If true, render component without wrapper\n * @param {string|false} props.as - Element type to render as ('section', 'div', 'article', etc.) or false for Fragment\n * @param {Object} props.extra - Extra props to pass to the component\n */\nexport default function BlockRenderer({ block, pure = false, as = 'section', extra = {} }) {\n const Component = block.initComponent()\n\n // Entity-aware data resolution via EntityStore\n const entityStore = block.website.entityStore\n const meta = getComponentMeta(block.type)\n const resolved = entityStore.resolve(block, meta)\n\n // Async data for when resolve returns 'pending' (runtime fetches)\n const [asyncData, setAsyncData] = useState(null)\n\n // Reset async data when block changes (SPA navigation)\n useEffect(() => {\n setAsyncData(null)\n }, [block])\n\n // Fetch missing data asynchronously\n useEffect(() => {\n if (resolved.status !== 'pending') return\n\n let cancelled = false\n entityStore.fetch(block, meta).then((result) => {\n if (!cancelled && result.data) setAsyncData(result.data)\n })\n return () => { cancelled = true }\n }, [block])\n\n // Use sync resolved data when available, fall back to async\n const entityData = resolved.status === 'ready' ? resolved.data : asyncData\n\n // Signal to component that data is loading\n block.dataLoading = resolved.status === 'pending' && !entityData\n\n if (!Component) {\n return (\n <div className=\"block-error\" style={{ padding: '1rem', background: '#fef2f2', color: '#dc2626' }}>\n Component not found: {block.type}\n </div>\n )\n }\n\n // Build content and params with runtime guarantees\n // Sources:\n // 1. parsedContent - semantic parser output (flat: title, paragraphs, links, etc.)\n // 2. block.properties - params from frontmatter (theme, alignment, etc.)\n // 3. meta - defaults from component meta.js\n const prepared = prepareProps(block, meta)\n let params = prepared.params\n\n let content = {\n ...prepared.content,\n ...block.properties, // Frontmatter params overlay (legacy support)\n }\n\n // Merge entity data resolved by EntityStore\n // Only fill in keys that don't already exist — section-level fetch data\n // (already in content.data from parsedContent) takes priority over inherited data.\n if (entityData) {\n const merged = { ...content.data }\n for (const key of Object.keys(entityData)) {\n if (merged[key] === undefined) {\n merged[key] = entityData[key]\n }\n }\n content.data = merged\n }\n\n const { background, ...wrapperProps } = getWrapperProps(block)\n\n // Merge Component.className (static classes declared on the component function)\n // Order: context-{theme} + block.state.className + Component.className\n const componentClassName = Component.className\n if (componentClassName) {\n wrapperProps.className = wrapperProps.className\n ? `${wrapperProps.className} ${componentClassName}`\n : componentClassName\n }\n\n // Check if component handles its own background (background: 'self' in meta.js)\n // Components that render their own background layer (solid colors, insets, effects)\n // opt out so the runtime doesn't render an occluded layer underneath.\n const hasBackground = background?.mode && meta?.background !== 'self'\n\n // Signal to the component that the engine is rendering a background.\n // Components check block.hasBackground to skip their own opaque bg\n // and let the engine background show through.\n block.hasBackground = hasBackground\n\n const componentProps = {\n content,\n params,\n block\n }\n\n if (pure) {\n return <Component {...componentProps} extra={extra} />\n }\n\n // Determine wrapper element:\n // - as={false} → Fragment (no wrapper)\n // - as prop explicitly set (not default 'section') → use as prop\n // - Component.as → use component's declared tag\n // - fallback → 'section'\n const componentAs = Component.as\n const Wrapper = as === false ? React.Fragment : (as !== 'section' ? as : componentAs || 'section')\n // Fragment doesn't accept props, so only pass them for real elements\n const wrapperElementProps = as === false ? {} : wrapperProps\n\n // Render with or without background\n if (hasBackground) {\n return (\n <Wrapper {...wrapperElementProps}>\n {/* Background layer (positioned absolutely) */}\n <Background\n mode={background.mode}\n color={background.color}\n gradient={background.gradient}\n image={background.image}\n video={background.video}\n overlay={background.overlay}\n />\n\n {/* Content layer (above background) */}\n <div className=\"relative z-10\">\n <Component {...componentProps} />\n </div>\n </Wrapper>\n )\n }\n\n // No background - simpler render without extra wrapper\n return (\n <Wrapper {...wrapperElementProps}>\n <Component {...componentProps} />\n </Wrapper>\n )\n}\n","/**\n * Blocks\n *\n * Renders an array of blocks for a layout area (header, body, footer, panels).\n * Used by the Layout component to pre-render each area.\n */\n\nimport React from 'react'\nimport BlockRenderer from './BlockRenderer.jsx'\n\n/**\n * Render a list of blocks\n *\n * @param {Object} props\n * @param {Block[]} props.blocks - Array of Block instances to render\n * @param {Object} [props.extra] - Extra props to pass to each block\n */\nexport default function Blocks({ blocks, extra = {} }) {\n if (!blocks || blocks.length === 0) return null\n\n return blocks.map((block, index) => (\n <React.Fragment key={block.id || index}>\n <BlockRenderer block={block} extra={extra} />\n </React.Fragment>\n ))\n}\n","/**\n * Layout\n *\n * Orchestrates page rendering by assembling layout areas (header, body, footer, and\n * any custom areas defined in the layout directory).\n * Supports foundation-provided custom Layout components via website.getRemoteLayout().\n *\n * Layout Areas:\n * Areas are general — any name works. Common conventions:\n * - header: Top navigation, branding (from layout/header.md)\n * - body: Main page content (from page sections)\n * - footer: Bottom navigation, copyright (from layout/footer.md)\n * - left: Left sidebar/panel (from layout/left.md)\n * - right: Right sidebar/panel (from layout/right.md)\n *\n * Custom Layouts:\n * Foundations provide custom layouts via src/layouts/:\n *\n * ```\n * src/layouts/\n * ├── DocsLayout/\n * │ ├── index.jsx\n * │ └── meta.js\n * └── MarketingLayout.jsx\n * ```\n *\n * The Layout component receives pre-rendered areas as props:\n * - page, website: Runtime context\n * - params: Layout params (merged with meta.js defaults)\n * - body: Pre-rendered body React element\n * - header, footer, left, right, ...: Pre-rendered area React elements\n */\n\nimport Blocks from './Blocks.jsx'\n\n/**\n * Default layout - renders header, body, footer in sequence\n * (no panels in default layout)\n */\nfunction DefaultLayout({ header, body, footer }) {\n return (\n <>\n {header && <header>{header}</header>}\n {body && <main>{body}</main>}\n {footer && <footer>{footer}</footer>}\n </>\n )\n}\n\n/**\n * Initialize all blocks to ensure cross-block communication works.\n * Must be called before rendering so getNextBlockInfo() can access sibling contexts.\n *\n * @param {Block[][]} blockGroups - Arrays of blocks from all layout areas\n */\nfunction initializeAllBlocks(...blockGroups) {\n for (const blocks of blockGroups) {\n if (!blocks) continue\n for (const block of blocks) {\n block.initComponent()\n }\n }\n}\n\n/**\n * Merge page-level layout params with meta.js defaults\n */\nfunction mergeParams(pageParams = {}, defaults = {}) {\n return { ...defaults, ...pageParams }\n}\n\n/**\n * Layout component\n *\n * @param {Object} props\n * @param {Page} props.page - Current page instance\n * @param {Website} props.website - Website instance\n */\nexport default function Layout({ page, website }) {\n const layoutName = page.getLayoutName()\n const RemoteLayout = website.getRemoteLayout(layoutName)\n const layoutMeta = website.getLayoutMeta(layoutName)\n\n const bodyBlocks = page.getBodyBlocks()\n const areas = page.getLayoutAreas()\n\n // Pre-initialize all blocks before rendering any.\n // This ensures cross-block communication (getNextBlockInfo, getPrevBlockInfo)\n // can access sibling block contexts that are set in initComponent().\n const allBlockGroups = [bodyBlocks, ...Object.values(areas)]\n initializeAllBlocks(...allBlockGroups)\n\n // Pre-render each area as React elements\n const bodyElement = bodyBlocks ? <Blocks blocks={bodyBlocks} /> : null\n const areaElements = {}\n for (const [name, blocks] of Object.entries(areas)) {\n areaElements[name] = <Blocks blocks={blocks} />\n }\n\n // Use foundation's custom Layout if provided\n if (RemoteLayout) {\n const params = mergeParams(page.getLayoutParams(), layoutMeta?.defaults)\n\n return (\n <RemoteLayout\n key={layoutName}\n page={page}\n website={website}\n params={params}\n body={bodyElement}\n {...areaElements}\n />\n )\n }\n\n // Default layout\n return (\n <DefaultLayout\n body={bodyElement}\n {...areaElements}\n />\n )\n}\n","/**\n * @uniweb/runtime/ssr - Server-Side Rendering Entry Point\n *\n * Node.js-compatible exports for SSG/prerendering.\n * This module is built to a standalone bundle that can be imported\n * directly by Node.js without Vite transpilation.\n *\n * Usage in prerender.js:\n * import { renderPage, Blocks, BlockRenderer } from '@uniweb/runtime/ssr'\n */\n\nimport React from 'react'\n\n// Props preparation (no browser APIs)\nexport {\n prepareProps,\n applySchemas,\n applyDefaults,\n guaranteeContentStructure,\n getComponentMeta,\n getComponentDefaults\n} from './prepare-props.js'\n\n// Components for rendering\nexport { default as BlockRenderer } from './components/BlockRenderer.jsx'\nexport { default as Blocks } from './components/Blocks.jsx'\nexport { default as Layout } from './components/Layout.jsx'\n\n// Re-export Layout's DefaultLayout for direct use\nimport LayoutComponent from './components/Layout.jsx'\n\n/**\n * Render a page to React elements\n *\n * This is the main entry point for SSG. It returns a React element\n * that can be passed to renderToString().\n *\n * @param {Object} props\n * @param {Page} props.page - The page instance to render\n * @param {Website} props.website - The website instance\n * @returns {React.ReactElement}\n */\nexport function PageElement({ page, website }) {\n return React.createElement(\n 'main',\n null,\n React.createElement(LayoutComponent, { page, website })\n )\n}\n"],"names":["style","LayoutComponent"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@uniweb/runtime",
3
- "version": "0.5.22",
3
+ "version": "0.6.0",
4
4
  "description": "Minimal runtime for loading Uniweb foundations",
5
5
  "type": "module",
6
6
  "exports": {
@@ -31,7 +31,7 @@
31
31
  "node": ">=20.19"
32
32
  },
33
33
  "dependencies": {
34
- "@uniweb/core": "0.4.7"
34
+ "@uniweb/core": "0.5.0"
35
35
  },
36
36
  "devDependencies": {
37
37
  "@vitejs/plugin-react": "^4.5.2",
@@ -1,10 +1,12 @@
1
1
  /**
2
2
  * Layout
3
3
  *
4
- * Orchestrates page rendering by assembling layout areas (header, body, footer, panels).
4
+ * Orchestrates page rendering by assembling layout areas (header, body, footer, and
5
+ * any custom areas defined in the layout directory).
5
6
  * Supports foundation-provided custom Layout components via website.getRemoteLayout().
6
7
  *
7
8
  * Layout Areas:
9
+ * Areas are general — any name works. Common conventions:
8
10
  * - header: Top navigation, branding (from layout/header.md)
9
11
  * - body: Main page content (from page sections)
10
12
  * - footer: Bottom navigation, copyright (from layout/footer.md)
@@ -12,24 +14,21 @@
12
14
  * - right: Right sidebar/panel (from layout/right.md)
13
15
  *
14
16
  * Custom Layouts:
15
- * Foundations can provide a custom Layout via src/exports.js:
17
+ * Foundations provide custom layouts via src/layouts/:
16
18
  *
17
- * ```jsx
18
- * // src/exports.js
19
- * import Layout from './components/Layout'
20
- *
21
- * export default {
22
- * Layout,
23
- * props: {
24
- * themeToggleEnabled: true,
25
- * }
26
- * }
19
+ * ```
20
+ * src/layouts/
21
+ * ├── DocsLayout/
22
+ * │ ├── index.jsx
23
+ * │ └── meta.js
24
+ * └── MarketingLayout.jsx
27
25
  * ```
28
26
  *
29
27
  * The Layout component receives pre-rendered areas as props:
30
28
  * - page, website: Runtime context
31
- * - header, body, footer: Pre-rendered React elements
32
- * - left, right (or leftPanel, rightPanel): Sidebar panels
29
+ * - params: Layout params (merged with meta.js defaults)
30
+ * - body: Pre-rendered body React element
31
+ * - header, footer, left, right, ...: Pre-rendered area React elements
33
32
  */
34
33
 
35
34
  import Blocks from './Blocks.jsx'
@@ -63,6 +62,13 @@ function initializeAllBlocks(...blockGroups) {
63
62
  }
64
63
  }
65
64
 
65
+ /**
66
+ * Merge page-level layout params with meta.js defaults
67
+ */
68
+ function mergeParams(pageParams = {}, defaults = {}) {
69
+ return { ...defaults, ...pageParams }
70
+ }
71
+
66
72
  /**
67
73
  * Layout component
68
74
  *
@@ -71,42 +77,38 @@ function initializeAllBlocks(...blockGroups) {
71
77
  * @param {Website} props.website - Website instance
72
78
  */
73
79
  export default function Layout({ page, website }) {
74
- // Check if foundation provides a custom Layout
75
- const RemoteLayout = website.getRemoteLayout()
80
+ const layoutName = page.getLayoutName()
81
+ const RemoteLayout = website.getRemoteLayout(layoutName)
82
+ const layoutMeta = website.getLayoutMeta(layoutName)
76
83
 
77
- // Get block groups from page (respects layout preferences)
78
- const headerBlocks = page.getHeaderBlocks()
79
84
  const bodyBlocks = page.getBodyBlocks()
80
- const footerBlocks = page.getFooterBlocks()
81
- const leftBlocks = page.getLeftBlocks()
82
- const rightBlocks = page.getRightBlocks()
85
+ const areas = page.getLayoutAreas()
83
86
 
84
87
  // Pre-initialize all blocks before rendering any.
85
88
  // This ensures cross-block communication (getNextBlockInfo, getPrevBlockInfo)
86
89
  // can access sibling block contexts that are set in initComponent().
87
- initializeAllBlocks(headerBlocks, bodyBlocks, footerBlocks, leftBlocks, rightBlocks)
90
+ const allBlockGroups = [bodyBlocks, ...Object.values(areas)]
91
+ initializeAllBlocks(...allBlockGroups)
88
92
 
89
93
  // Pre-render each area as React elements
90
- const headerElement = headerBlocks ? <Blocks blocks={headerBlocks} /> : null
91
94
  const bodyElement = bodyBlocks ? <Blocks blocks={bodyBlocks} /> : null
92
- const footerElement = footerBlocks ? <Blocks blocks={footerBlocks} /> : null
93
- const leftElement = leftBlocks ? <Blocks blocks={leftBlocks} /> : null
94
- const rightElement = rightBlocks ? <Blocks blocks={rightBlocks} /> : null
95
+ const areaElements = {}
96
+ for (const [name, blocks] of Object.entries(areas)) {
97
+ areaElements[name] = <Blocks blocks={blocks} />
98
+ }
95
99
 
96
100
  // Use foundation's custom Layout if provided
97
101
  if (RemoteLayout) {
102
+ const params = mergeParams(page.getLayoutParams(), layoutMeta?.defaults)
103
+
98
104
  return (
99
105
  <RemoteLayout
106
+ key={layoutName}
100
107
  page={page}
101
108
  website={website}
102
- header={headerElement}
109
+ params={params}
103
110
  body={bodyElement}
104
- footer={footerElement}
105
- left={leftElement}
106
- right={rightElement}
107
- // Aliases for backwards compatibility
108
- leftPanel={leftElement}
109
- rightPanel={rightElement}
111
+ {...areaElements}
110
112
  />
111
113
  )
112
114
  }
@@ -114,9 +116,8 @@ export default function Layout({ page, website }) {
114
116
  // Default layout
115
117
  return (
116
118
  <DefaultLayout
117
- header={headerElement}
118
119
  body={bodyElement}
119
- footer={footerElement}
120
+ {...areaElements}
120
121
  />
121
122
  )
122
123
  }
package/src/index.jsx CHANGED
@@ -442,11 +442,16 @@ async function initRuntime(foundationSource, options = {}) {
442
442
  // Set the foundation on the runtime
443
443
  uniwebInstance.setFoundation(foundation)
444
444
 
445
- // Set foundation capabilities (Layout, props, etc.) if provided
445
+ // Set foundation capabilities (layouts, props, etc.) if provided
446
446
  if (foundation.capabilities) {
447
447
  uniwebInstance.setFoundationConfig(foundation.capabilities)
448
448
  }
449
449
 
450
+ // Attach layout metadata (areas, transitions, defaults) from foundation entry point
451
+ if (foundation.layoutMeta && uniwebInstance.foundationConfig) {
452
+ uniwebInstance.foundationConfig.layoutMeta = foundation.layoutMeta
453
+ }
454
+
450
455
  // Load extensions (secondary foundations)
451
456
  const extensions = configData?.config?.extensions
452
457
  if (extensions?.length) {