@uniweb/runtime 0.2.13 → 0.2.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.
package/dist/ssr.js ADDED
@@ -0,0 +1,274 @@
1
+ import React from "react";
2
+ import { jsxs, jsx, Fragment } from "react/jsx-runtime";
3
+ function guaranteeItemStructure(item) {
4
+ return {
5
+ title: item.title || "",
6
+ pretitle: item.pretitle || "",
7
+ subtitle: item.subtitle || "",
8
+ paragraphs: item.paragraphs || [],
9
+ links: item.links || [],
10
+ imgs: item.imgs || [],
11
+ lists: item.lists || [],
12
+ icons: item.icons || [],
13
+ videos: item.videos || [],
14
+ buttons: item.buttons || [],
15
+ data: item.data || {},
16
+ cards: item.cards || [],
17
+ documents: item.documents || [],
18
+ forms: item.forms || [],
19
+ quotes: item.quotes || [],
20
+ headings: item.headings || []
21
+ };
22
+ }
23
+ function guaranteeContentStructure(parsedContent) {
24
+ const content = parsedContent || {};
25
+ return {
26
+ // Flat header fields
27
+ title: content.title || "",
28
+ pretitle: content.pretitle || "",
29
+ subtitle: content.subtitle || "",
30
+ subtitle2: content.subtitle2 || "",
31
+ alignment: content.alignment || null,
32
+ // Flat body fields
33
+ paragraphs: content.paragraphs || [],
34
+ links: content.links || [],
35
+ imgs: content.imgs || [],
36
+ lists: content.lists || [],
37
+ icons: content.icons || [],
38
+ videos: content.videos || [],
39
+ buttons: content.buttons || [],
40
+ data: content.data || {},
41
+ cards: content.cards || [],
42
+ documents: content.documents || [],
43
+ forms: content.forms || [],
44
+ quotes: content.quotes || [],
45
+ headings: content.headings || [],
46
+ // Items with guaranteed structure
47
+ items: (content.items || []).map(guaranteeItemStructure),
48
+ // Sequence for ordered rendering
49
+ sequence: content.sequence || [],
50
+ // Preserve raw content if present
51
+ raw: content.raw
52
+ };
53
+ }
54
+ function applySchemaToObject(obj, schema) {
55
+ if (!obj || typeof obj !== "object" || Array.isArray(obj)) {
56
+ return obj;
57
+ }
58
+ const result = { ...obj };
59
+ for (const [field, fieldDef] of Object.entries(schema)) {
60
+ const defaultValue = typeof fieldDef === "object" ? fieldDef.default : void 0;
61
+ if (result[field] === void 0 && defaultValue !== void 0) {
62
+ result[field] = defaultValue;
63
+ }
64
+ if (typeof fieldDef === "object" && fieldDef.options && Array.isArray(fieldDef.options)) {
65
+ if (result[field] !== void 0 && !fieldDef.options.includes(result[field])) {
66
+ if (defaultValue !== void 0) {
67
+ result[field] = defaultValue;
68
+ }
69
+ }
70
+ }
71
+ if (typeof fieldDef === "object" && fieldDef.type === "object" && fieldDef.schema && result[field]) {
72
+ result[field] = applySchemaToObject(result[field], fieldDef.schema);
73
+ }
74
+ if (typeof fieldDef === "object" && fieldDef.type === "array" && fieldDef.of && result[field]) {
75
+ if (typeof fieldDef.of === "object") {
76
+ result[field] = result[field].map((item) => applySchemaToObject(item, fieldDef.of));
77
+ }
78
+ }
79
+ }
80
+ return result;
81
+ }
82
+ function applySchemaToValue(value, schema) {
83
+ if (Array.isArray(value)) {
84
+ return value.map((item) => applySchemaToObject(item, schema));
85
+ }
86
+ return applySchemaToObject(value, schema);
87
+ }
88
+ function applySchemas(data, schemas) {
89
+ if (!schemas || !data || typeof data !== "object") {
90
+ return data || {};
91
+ }
92
+ const result = { ...data };
93
+ for (const [tag, rawValue] of Object.entries(data)) {
94
+ const schema = schemas[tag];
95
+ if (!schema) continue;
96
+ result[tag] = applySchemaToValue(rawValue, schema);
97
+ }
98
+ return result;
99
+ }
100
+ function applyDefaults(params, defaults) {
101
+ if (!defaults || Object.keys(defaults).length === 0) {
102
+ return params || {};
103
+ }
104
+ return {
105
+ ...defaults,
106
+ ...params || {}
107
+ };
108
+ }
109
+ function prepareProps(block, meta) {
110
+ const defaults = meta?.defaults || {};
111
+ const params = applyDefaults(block.properties, defaults);
112
+ const content = guaranteeContentStructure(block.parsedContent);
113
+ const schemas = meta?.schemas || null;
114
+ if (schemas && content.data) {
115
+ content.data = applySchemas(content.data, schemas);
116
+ }
117
+ return { content, params };
118
+ }
119
+ function getComponentMeta(componentName) {
120
+ return globalThis.uniweb?.getComponentMeta?.(componentName) || null;
121
+ }
122
+ function getComponentDefaults(componentName) {
123
+ return globalThis.uniweb?.getComponentDefaults?.(componentName) || {};
124
+ }
125
+ const hexToRgba = (hex, opacity) => {
126
+ const r = parseInt(hex.slice(1, 3), 16);
127
+ const g = parseInt(hex.slice(3, 5), 16);
128
+ const b = parseInt(hex.slice(5, 7), 16);
129
+ return `rgba(${r},${g},${b},${opacity})`;
130
+ };
131
+ const getWrapperProps = (block) => {
132
+ const theme = block.themeName;
133
+ const blockClassName = block.state?.className || "";
134
+ let className = theme || "";
135
+ if (blockClassName) {
136
+ className = className ? `${className} ${blockClassName}` : blockClassName;
137
+ }
138
+ const { background = {}, colors = {} } = block.standardOptions;
139
+ const style = {};
140
+ if (background.mode === "gradient") {
141
+ const {
142
+ enabled = false,
143
+ start = "transparent",
144
+ end = "transparent",
145
+ angle = 0,
146
+ startPosition = 0,
147
+ endPosition = 100,
148
+ startOpacity = 0.7,
149
+ endOpacity = 0.3
150
+ } = background.gradient || {};
151
+ if (enabled) {
152
+ style["--bg-color"] = `linear-gradient(${angle}deg,
153
+ ${hexToRgba(start, startOpacity)} ${startPosition}%,
154
+ ${hexToRgba(end, endOpacity)} ${endPosition}%)`;
155
+ }
156
+ } else if (background.mode === "image" || background.mode === "video") {
157
+ const settings = background[background.mode] || {};
158
+ const { url = "", file = "" } = settings;
159
+ if (url || file) {
160
+ style["--bg-color"] = "transparent";
161
+ style.position = "relative";
162
+ style.maxWidth = "100%";
163
+ }
164
+ }
165
+ return {
166
+ id: `Section${block.id}`,
167
+ style,
168
+ className
169
+ };
170
+ };
171
+ function BlockRenderer({ block, pure = false, extra = {} }) {
172
+ const Component = block.initComponent();
173
+ if (!Component) {
174
+ return /* @__PURE__ */ jsxs("div", { className: "block-error", style: { padding: "1rem", background: "#fef2f2", color: "#dc2626" }, children: [
175
+ "Component not found: ",
176
+ block.type
177
+ ] });
178
+ }
179
+ let content, params;
180
+ if (block.parsedContent?._isPoc) {
181
+ content = block.parsedContent._pocContent;
182
+ params = block.properties;
183
+ } else {
184
+ const meta = getComponentMeta(block.type);
185
+ const prepared = prepareProps(block, meta);
186
+ params = prepared.params;
187
+ content = {
188
+ ...prepared.content,
189
+ ...block.properties,
190
+ // Frontmatter params overlay (legacy support)
191
+ _prosemirror: block.parsedContent
192
+ // Keep original for components that need raw access
193
+ };
194
+ }
195
+ const componentProps = {
196
+ content,
197
+ params,
198
+ block,
199
+ input: block.input
200
+ };
201
+ if (pure) {
202
+ return /* @__PURE__ */ jsx(Component, { ...componentProps, extra });
203
+ }
204
+ const wrapperProps = getWrapperProps(block);
205
+ return /* @__PURE__ */ jsx("div", { ...wrapperProps, children: /* @__PURE__ */ jsx(Component, { ...componentProps }) });
206
+ }
207
+ function Blocks({ blocks, extra = {} }) {
208
+ if (!blocks || blocks.length === 0) return null;
209
+ return blocks.map((block, index) => /* @__PURE__ */ jsx(React.Fragment, { children: /* @__PURE__ */ jsx(BlockRenderer, { block, extra }) }, block.id || index));
210
+ }
211
+ function DefaultLayout({ header, body, footer }) {
212
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
213
+ header,
214
+ body,
215
+ footer
216
+ ] });
217
+ }
218
+ function Layout({ page, website }) {
219
+ const RemoteLayout = website.getRemoteLayout();
220
+ const headerBlocks = page.getHeaderBlocks();
221
+ const bodyBlocks = page.getBodyBlocks();
222
+ const footerBlocks = page.getFooterBlocks();
223
+ const leftBlocks = page.getLeftBlocks();
224
+ const rightBlocks = page.getRightBlocks();
225
+ const headerElement = headerBlocks ? /* @__PURE__ */ jsx(Blocks, { blocks: headerBlocks }) : null;
226
+ const bodyElement = bodyBlocks ? /* @__PURE__ */ jsx(Blocks, { blocks: bodyBlocks }) : null;
227
+ const footerElement = footerBlocks ? /* @__PURE__ */ jsx(Blocks, { blocks: footerBlocks }) : null;
228
+ const leftElement = leftBlocks ? /* @__PURE__ */ jsx(Blocks, { blocks: leftBlocks }) : null;
229
+ const rightElement = rightBlocks ? /* @__PURE__ */ jsx(Blocks, { blocks: rightBlocks }) : null;
230
+ if (RemoteLayout) {
231
+ return /* @__PURE__ */ jsx(
232
+ RemoteLayout,
233
+ {
234
+ page,
235
+ website,
236
+ header: headerElement,
237
+ body: bodyElement,
238
+ footer: footerElement,
239
+ left: leftElement,
240
+ right: rightElement,
241
+ leftPanel: leftElement,
242
+ rightPanel: rightElement
243
+ }
244
+ );
245
+ }
246
+ return /* @__PURE__ */ jsx(
247
+ DefaultLayout,
248
+ {
249
+ header: headerElement,
250
+ body: bodyElement,
251
+ footer: footerElement
252
+ }
253
+ );
254
+ }
255
+ function PageElement({ page, website }) {
256
+ return React.createElement(
257
+ "main",
258
+ null,
259
+ React.createElement(Layout, { page, website })
260
+ );
261
+ }
262
+ export {
263
+ BlockRenderer,
264
+ Blocks,
265
+ Layout,
266
+ PageElement,
267
+ applyDefaults,
268
+ applySchemas,
269
+ getComponentDefaults,
270
+ getComponentMeta,
271
+ guaranteeContentStructure,
272
+ prepareProps
273
+ };
274
+ //# sourceMappingURL=ssr.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ssr.js","sources":["../src/prepare-props.js","../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 * BlockRenderer\n *\n * Bridges Block data to foundation components.\n * Handles theming, wrapper props, and runtime guarantees.\n */\n\nimport React from 'react'\nimport { prepareProps, getComponentMeta } from '../prepare-props.js'\n\n/**\n * Convert hex color to rgba\n */\nconst hexToRgba = (hex, opacity) => {\n const r = parseInt(hex.slice(1, 3), 16)\n const g = parseInt(hex.slice(3, 5), 16)\n const b = parseInt(hex.slice(5, 7), 16)\n return `rgba(${r},${g},${b},${opacity})`\n}\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 let className = theme || ''\n if (blockClassName) {\n className = className ? `${className} ${blockClassName}` : blockClassName\n }\n\n const { background = {}, colors = {} } = block.standardOptions\n const style = {}\n\n // Handle background modes\n if (background.mode === 'gradient') {\n const {\n enabled = false,\n start = 'transparent',\n end = 'transparent',\n angle = 0,\n startPosition = 0,\n endPosition = 100,\n startOpacity = 0.7,\n endOpacity = 0.3\n } = background.gradient || {}\n\n if (enabled) {\n style['--bg-color'] = `linear-gradient(${angle}deg,\n ${hexToRgba(start, startOpacity)} ${startPosition}%,\n ${hexToRgba(end, endOpacity)} ${endPosition}%)`\n }\n } else if (background.mode === 'image' || background.mode === 'video') {\n const settings = background[background.mode] || {}\n const { url = '', file = '' } = settings\n\n if (url || file) {\n style['--bg-color'] = 'transparent'\n style.position = 'relative'\n style.maxWidth = '100%'\n }\n }\n\n return {\n id: `Section${block.id}`,\n style,\n className\n }\n}\n\n/**\n * BlockRenderer component\n */\nexport default function BlockRenderer({ block, pure = false, extra = {} }) {\n const Component = block.initComponent()\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._isPoc - simple PoC format (hardcoded content)\n // 2. parsedContent - semantic parser output (flat: title, paragraphs, links, etc.)\n // 3. block.properties - params from frontmatter (theme, alignment, etc.)\n // 4. meta - defaults from component meta.js\n let content, params\n\n if (block.parsedContent?._isPoc) {\n // Simple PoC format - content was passed directly\n content = block.parsedContent._pocContent\n params = block.properties\n } else {\n // Get runtime metadata for this component (has defaults, data binding, etc.)\n const meta = getComponentMeta(block.type)\n\n // Prepare props with runtime guarantees:\n // - Apply param defaults from meta.js\n // - Guarantee content structure exists\n const prepared = prepareProps(block, meta)\n params = prepared.params\n\n // Merge prepared content with raw access for components that need it\n content = {\n ...prepared.content,\n ...block.properties, // Frontmatter params overlay (legacy support)\n _prosemirror: block.parsedContent // Keep original for components that need raw access\n }\n }\n\n const componentProps = {\n content,\n params,\n block,\n input: block.input\n }\n\n if (pure) {\n return <Component {...componentProps} extra={extra} />\n }\n\n const wrapperProps = getWrapperProps(block)\n\n return (\n <div {...wrapperProps}>\n <Component {...componentProps} />\n </div>\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 @header page)\n * - body: Main page content (from page sections)\n * - footer: Bottom navigation, copyright (from @footer page)\n * - left: Left sidebar/panel (from @left page)\n * - right: Right sidebar/panel (from @right page)\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}\n {body}\n {footer}\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-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":["LayoutComponent"],"mappings":"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"}
package/package.json CHANGED
@@ -1,13 +1,15 @@
1
1
  {
2
2
  "name": "@uniweb/runtime",
3
- "version": "0.2.13",
3
+ "version": "0.2.15",
4
4
  "description": "Minimal runtime for loading Uniweb foundations",
5
5
  "type": "module",
6
6
  "exports": {
7
- ".": "./src/index.jsx"
7
+ ".": "./src/index.jsx",
8
+ "./ssr": "./dist/ssr.js"
8
9
  },
9
10
  "files": [
10
- "src"
11
+ "src",
12
+ "dist"
11
13
  ],
12
14
  "keywords": [
13
15
  "uniweb",
@@ -31,9 +33,16 @@
31
33
  "dependencies": {
32
34
  "@uniweb/core": "0.1.12"
33
35
  },
36
+ "devDependencies": {
37
+ "@vitejs/plugin-react": "^4.5.2",
38
+ "vite": "^7.3.1"
39
+ },
34
40
  "peerDependencies": {
35
41
  "react": "^18.0.0 || ^19.0.0",
36
42
  "react-dom": "^18.0.0 || ^19.0.0",
37
43
  "react-router-dom": "^6.0.0 || ^7.0.0"
44
+ },
45
+ "scripts": {
46
+ "build:ssr": "vite build --config vite.config.ssr.js"
38
47
  }
39
48
  }
@@ -26,7 +26,7 @@ function guaranteeItemStructure(item) {
26
26
  icons: item.icons || [],
27
27
  videos: item.videos || [],
28
28
  buttons: item.buttons || [],
29
- properties: item.properties || {},
29
+ data: item.data || {},
30
30
  cards: item.cards || [],
31
31
  documents: item.documents || [],
32
32
  forms: item.forms || [],
@@ -61,8 +61,7 @@ export function guaranteeContentStructure(parsedContent) {
61
61
  icons: content.icons || [],
62
62
  videos: content.videos || [],
63
63
  buttons: content.buttons || [],
64
- properties: content.properties || {},
65
- propertyBlocks: content.propertyBlocks || [],
64
+ data: content.data || {},
66
65
  cards: content.cards || [],
67
66
  documents: content.documents || [],
68
67
  forms: content.forms || [],
@@ -80,6 +79,95 @@ export function guaranteeContentStructure(parsedContent) {
80
79
  }
81
80
  }
82
81
 
82
+ /**
83
+ * Apply a schema to a single object
84
+ * Only processes fields defined in the schema, preserves unknown fields
85
+ *
86
+ * @param {Object} obj - The object to process
87
+ * @param {Object} schema - Schema definition (fieldName -> fieldDef)
88
+ * @returns {Object} Object with schema defaults applied
89
+ */
90
+ function applySchemaToObject(obj, schema) {
91
+ if (!obj || typeof obj !== 'object' || Array.isArray(obj)) {
92
+ return obj
93
+ }
94
+
95
+ const result = { ...obj }
96
+
97
+ for (const [field, fieldDef] of Object.entries(schema)) {
98
+ // Get the default value - handle both shorthand and full form
99
+ const defaultValue = typeof fieldDef === 'object' ? fieldDef.default : undefined
100
+
101
+ // Apply default if field is missing and default exists
102
+ if (result[field] === undefined && defaultValue !== undefined) {
103
+ result[field] = defaultValue
104
+ }
105
+
106
+ // For select fields with options, apply default if value is not among valid options
107
+ if (typeof fieldDef === 'object' && fieldDef.options && Array.isArray(fieldDef.options)) {
108
+ if (result[field] !== undefined && !fieldDef.options.includes(result[field])) {
109
+ // Value exists but is not valid - apply default if available
110
+ if (defaultValue !== undefined) {
111
+ result[field] = defaultValue
112
+ }
113
+ }
114
+ }
115
+
116
+ // Handle nested object schema
117
+ if (typeof fieldDef === 'object' && fieldDef.type === 'object' && fieldDef.schema && result[field]) {
118
+ result[field] = applySchemaToObject(result[field], fieldDef.schema)
119
+ }
120
+
121
+ // Handle array with inline schema
122
+ if (typeof fieldDef === 'object' && fieldDef.type === 'array' && fieldDef.of && result[field]) {
123
+ if (typeof fieldDef.of === 'object') {
124
+ result[field] = result[field].map(item => applySchemaToObject(item, fieldDef.of))
125
+ }
126
+ }
127
+ }
128
+
129
+ return result
130
+ }
131
+
132
+ /**
133
+ * Apply a schema to a value (object or array of objects)
134
+ *
135
+ * @param {Object|Array} value - The value to process
136
+ * @param {Object} schema - Schema definition
137
+ * @returns {Object|Array} Value with schema defaults applied
138
+ */
139
+ function applySchemaToValue(value, schema) {
140
+ if (Array.isArray(value)) {
141
+ return value.map(item => applySchemaToObject(item, schema))
142
+ }
143
+ return applySchemaToObject(value, schema)
144
+ }
145
+
146
+ /**
147
+ * Apply schemas to content.data
148
+ * Only processes tags that have a matching schema, leaves others untouched
149
+ *
150
+ * @param {Object} data - The data object from content
151
+ * @param {Object} schemas - Schema definitions from runtime meta
152
+ * @returns {Object} Data with schemas applied
153
+ */
154
+ export function applySchemas(data, schemas) {
155
+ if (!schemas || !data || typeof data !== 'object') {
156
+ return data || {}
157
+ }
158
+
159
+ const result = { ...data }
160
+
161
+ for (const [tag, rawValue] of Object.entries(data)) {
162
+ const schema = schemas[tag]
163
+ if (!schema) continue // No schema for this tag - leave as-is
164
+
165
+ result[tag] = applySchemaToValue(rawValue, schema)
166
+ }
167
+
168
+ return result
169
+ }
170
+
83
171
  /**
84
172
  * Apply param defaults from runtime schema
85
173
  *
@@ -113,6 +201,12 @@ export function prepareProps(block, meta) {
113
201
  // Guarantee content structure
114
202
  const content = guaranteeContentStructure(block.parsedContent)
115
203
 
204
+ // Apply schemas to content.data
205
+ const schemas = meta?.schemas || null
206
+ if (schemas && content.data) {
207
+ content.data = applySchemas(content.data, schemas)
208
+ }
209
+
116
210
  return { content, params }
117
211
  }
118
212
 
package/src/ssr.js ADDED
@@ -0,0 +1,49 @@
1
+ /**
2
+ * @uniweb/runtime/ssr - Server-Side Rendering Entry Point
3
+ *
4
+ * Node.js-compatible exports for SSG/prerendering.
5
+ * This module is built to a standalone bundle that can be imported
6
+ * directly by Node.js without Vite transpilation.
7
+ *
8
+ * Usage in prerender.js:
9
+ * import { renderPage, Blocks, BlockRenderer } from '@uniweb/runtime/ssr'
10
+ */
11
+
12
+ import React from 'react'
13
+
14
+ // Props preparation (no browser APIs)
15
+ export {
16
+ prepareProps,
17
+ applySchemas,
18
+ applyDefaults,
19
+ guaranteeContentStructure,
20
+ getComponentMeta,
21
+ getComponentDefaults
22
+ } from './prepare-props.js'
23
+
24
+ // Components for rendering
25
+ export { default as BlockRenderer } from './components/BlockRenderer.jsx'
26
+ export { default as Blocks } from './components/Blocks.jsx'
27
+ export { default as Layout } from './components/Layout.jsx'
28
+
29
+ // Re-export Layout's DefaultLayout for direct use
30
+ import LayoutComponent from './components/Layout.jsx'
31
+
32
+ /**
33
+ * Render a page to React elements
34
+ *
35
+ * This is the main entry point for SSG. It returns a React element
36
+ * that can be passed to renderToString().
37
+ *
38
+ * @param {Object} props
39
+ * @param {Page} props.page - The page instance to render
40
+ * @param {Website} props.website - The website instance
41
+ * @returns {React.ReactElement}
42
+ */
43
+ export function PageElement({ page, website }) {
44
+ return React.createElement(
45
+ 'main',
46
+ null,
47
+ React.createElement(LayoutComponent, { page, website })
48
+ )
49
+ }