@otl-core/block-registry 1.1.60 → 1.1.62
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/index.cjs +24 -8
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +11 -1
- package/dist/index.d.ts +11 -1
- package/dist/index.js +24 -9
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -94,9 +94,13 @@ function ComponentNotFound({
|
|
|
94
94
|
);
|
|
95
95
|
}
|
|
96
96
|
var globalAnalyticsWrapper = null;
|
|
97
|
+
var globalBlockStyleWrapper = null;
|
|
97
98
|
function registerAnalyticsWrapper(wrapper) {
|
|
98
99
|
globalAnalyticsWrapper = wrapper;
|
|
99
100
|
}
|
|
101
|
+
function registerBlockStyleWrapper(wrapper) {
|
|
102
|
+
globalBlockStyleWrapper = wrapper;
|
|
103
|
+
}
|
|
100
104
|
function resolveBlockConfig(block) {
|
|
101
105
|
if ("config" in block && block.config !== void 0) {
|
|
102
106
|
return block.config;
|
|
@@ -128,6 +132,7 @@ function BlockRenderer({
|
|
|
128
132
|
);
|
|
129
133
|
}
|
|
130
134
|
const blockAnalytics = block.config?.analytics;
|
|
135
|
+
const StyleWrapper = globalBlockStyleWrapper;
|
|
131
136
|
const wrapWithAnalytics = (element) => {
|
|
132
137
|
if (AnalyticsWrapper) {
|
|
133
138
|
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -153,24 +158,35 @@ function BlockRenderer({
|
|
|
153
158
|
);
|
|
154
159
|
}
|
|
155
160
|
const config = resolveBlockConfig(block);
|
|
161
|
+
const wrapWithStyle = (element) => {
|
|
162
|
+
if (StyleWrapper) {
|
|
163
|
+
return /* @__PURE__ */ jsxRuntime.jsx(StyleWrapper, { config, children: element });
|
|
164
|
+
}
|
|
165
|
+
return element;
|
|
166
|
+
};
|
|
156
167
|
if (type === "alert" || type === "card" || type === "modal" || type === "grid-layout" || type === "flexbox-layout" || type === "container-layout" || type === "entry-content") {
|
|
157
168
|
return wrapWithAnalytics(
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
169
|
+
wrapWithStyle(
|
|
170
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
171
|
+
BlockComponent,
|
|
172
|
+
{
|
|
173
|
+
config,
|
|
174
|
+
siteId,
|
|
175
|
+
blockRegistry
|
|
176
|
+
}
|
|
177
|
+
)
|
|
165
178
|
)
|
|
166
179
|
);
|
|
167
180
|
}
|
|
168
|
-
return wrapWithAnalytics(
|
|
181
|
+
return wrapWithAnalytics(
|
|
182
|
+
wrapWithStyle(/* @__PURE__ */ jsxRuntime.jsx(BlockComponent, { config, siteId }))
|
|
183
|
+
);
|
|
169
184
|
}
|
|
170
185
|
|
|
171
186
|
exports.BlockRegistry = BlockRegistry;
|
|
172
187
|
exports.BlockRenderer = BlockRenderer;
|
|
173
188
|
exports.ComponentNotFound = ComponentNotFound;
|
|
174
189
|
exports.registerAnalyticsWrapper = registerAnalyticsWrapper;
|
|
190
|
+
exports.registerBlockStyleWrapper = registerBlockStyleWrapper;
|
|
175
191
|
//# sourceMappingURL=index.cjs.map
|
|
176
192
|
//# sourceMappingURL=index.cjs.map
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/registry/block-registry.ts","../src/renderer/component-not-found.tsx","../src/renderer/block-renderer.tsx"],"names":["jsx","config"],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * Block Component Registry\n * Manages registration and retrieval of block components\n */\n\nimport { ComponentType } from \"react\";\n\nexport class BlockRegistry {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n private components = new Map<string, ComponentType<any>>();\n\n /**\n * Register a block component\n */\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n register(type: string, component: ComponentType<any>): void {\n this.components.set(type, component);\n }\n\n /**\n * Get a block component by type\n */\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n get(type: string): ComponentType<any> | undefined {\n return this.components.get(type);\n }\n\n /**\n * Check if a block type is registered\n */\n has(type: string): boolean {\n return this.components.has(type);\n }\n\n /**\n * Get all registered block types\n */\n getAll(): string[] {\n return Array.from(this.components.keys());\n }\n\n /**\n * Get count of registered components\n */\n size(): number {\n return this.components.size;\n }\n}\n","/**\n * Component Not Found Fallback\n * Logs error when a component type is not registered in the registry\n */\n\ninterface ComponentNotFoundProps {\n type: string;\n config: Record<string, unknown>;\n availableTypes: string[];\n componentKind: \"section\" | \"block\";\n}\n\nexport default function ComponentNotFound({\n type,\n config,\n availableTypes,\n componentKind,\n}: ComponentNotFoundProps) {\n const isDev = process.env.NODE_ENV === \"development\";\n\n const errorDetails = {\n componentKind,\n type,\n config,\n availableTypes,\n timestamp: new Date().toISOString(),\n suggestion: `Create components/${componentKind}s/${type}.tsx and register it in lib/registries/${componentKind}-registry.ts`,\n };\n\n if (isDev) {\n console.error(\n `[${componentKind === \"section\" ? \"SectionRenderer\" : \"BlockRenderer\"}] Component not found: \"${type}\"`,\n errorDetails,\n );\n } else {\n console.error(`[CMS] Unknown ${componentKind}: \"${type}\"`);\n }\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n if (typeof window !== \"undefined\" && (window as any).__CMS_ERROR_HANDLER__) {\n try {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n (window as any).__CMS_ERROR_HANDLER__({\n errorType: \"COMPONENT_NOT_FOUND\",\n severity: \"warning\",\n ...errorDetails,\n });\n } catch {\n // Ignore errors from error handler\n }\n }\n\n return (\n <div\n style={{\n minHeight: \"40px\",\n border: \"1px dashed #e5e7eb\",\n borderRadius: \"4px\",\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n color: \"#9ca3af\",\n fontSize: \"12px\",\n fontFamily: \"monospace\",\n }}\n aria-hidden=\"true\"\n >\n {type}\n </div>\n );\n}\n","/**\n * Block Renderer\n * Dynamically renders block components based on schema instance type\n *\n * SSR COMPATIBLE: This is a pure server component with no client hooks.\n * Analytics wrapper is a client component that is rendered by this server\n * component (valid in Next.js RSC architecture).\n */\n\nimport { BlockRegistry } from \"../registry/block-registry\";\nimport ComponentNotFound from \"./component-not-found\";\n\ninterface BlockAnalyticsConfig {\n enabled: boolean;\n event_label: string;\n track_type: \"click\" | \"visibility\" | \"both\";\n visibility_threshold?: number;\n fire_once?: boolean;\n target_providers?: \"all\" | string[];\n custom_params?: Record<string, string>;\n}\n\ntype AnalyticsWrapperComponent = React.ComponentType<{\n analyticsConfig: BlockAnalyticsConfig | undefined;\n blockId: string;\n blockType: string;\n children: React.ReactNode;\n}>;\n\nlet globalAnalyticsWrapper: AnalyticsWrapperComponent | null = null;\n\n/**\n * Register a global analytics wrapper component that will be applied to\n * all blocks rendered by BlockRenderer. Call once during app initialization.\n */\nexport function registerAnalyticsWrapper(\n wrapper: AnalyticsWrapperComponent,\n): void {\n globalAnalyticsWrapper = wrapper;\n}\n\ninterface BlockInstance {\n id: string;\n type: string;\n config?: Record<string, unknown>;\n data?: Record<string, unknown>;\n}\n\ninterface BlockRendererProps {\n block: BlockInstance;\n blockRegistry: BlockRegistry;\n siteId?: string;\n /** Optional wrapper component for block-level analytics (overrides global) */\n analyticsWrapper?: AnalyticsWrapperComponent;\n}\n\nfunction resolveBlockConfig(block: BlockInstance): Record<string, unknown> {\n if (\"config\" in block && block.config !== undefined) {\n return block.config;\n }\n if (\"data\" in block && block.data !== undefined) {\n return block.data;\n }\n return {};\n}\n\n// NO \"use client\" directive - this is a server component\nexport default function BlockRenderer({\n block,\n blockRegistry,\n siteId,\n analyticsWrapper,\n}: BlockRendererProps) {\n const AnalyticsWrapper = analyticsWrapper ?? globalAnalyticsWrapper;\n const { type, id } = block;\n\n // eslint-disable-next-line react-hooks/static-components -- dynamic registry lookup by design\n const BlockComponent = blockRegistry.get(type);\n\n if (!BlockComponent) {\n const config = resolveBlockConfig(block);\n\n return (\n <ComponentNotFound\n type={type}\n config={config}\n availableTypes={blockRegistry.getAll()}\n componentKind=\"block\"\n />\n );\n }\n\n // Extract analytics config from the block config\n const blockAnalytics = block.config?.analytics as\n | BlockAnalyticsConfig\n | undefined;\n\n // Helper to wrap rendered block with analytics if wrapper is provided\n const wrapWithAnalytics = (element: React.ReactElement) => {\n if (AnalyticsWrapper) {\n return (\n <AnalyticsWrapper\n analyticsConfig={blockAnalytics}\n blockId={id}\n blockType={type}\n >\n {element}\n </AnalyticsWrapper>\n );\n }\n return element;\n };\n\n // Form blocks get blockId, siteId, and their embedded data\n if (type.startsWith(\"form-\")) {\n return wrapWithAnalytics(<BlockComponent blockId={id} siteId={siteId} />);\n }\n\n // Special handling for the \"form\" block (embedded form)\n // This block type receives both config AND data\n if (type === \"form\") {\n const config = block.config || {};\n const data = block.data; // This contains FormBlockData from backend\n return wrapWithAnalytics(\n <BlockComponent config={config} data={data} siteId={siteId} />,\n );\n }\n\n // Regular blocks get config (or data if config is not present)\n const config = resolveBlockConfig(block);\n\n // Blocks that can contain nested blocks need the blockRegistry and siteId\n if (\n type === \"alert\" ||\n type === \"card\" ||\n type === \"modal\" ||\n type === \"grid-layout\" ||\n type === \"flexbox-layout\" ||\n type === \"container-layout\" ||\n type === \"entry-content\"\n ) {\n return wrapWithAnalytics(\n <BlockComponent\n config={config}\n siteId={siteId}\n blockRegistry={blockRegistry}\n />,\n );\n }\n\n return wrapWithAnalytics(<BlockComponent config={config} siteId={siteId} />);\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/registry/block-registry.ts","../src/renderer/component-not-found.tsx","../src/renderer/block-renderer.tsx"],"names":["jsx","config"],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * Block Component Registry\n * Manages registration and retrieval of block components\n */\n\nimport { ComponentType } from \"react\";\n\nexport class BlockRegistry {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n private components = new Map<string, ComponentType<any>>();\n\n /**\n * Register a block component\n */\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n register(type: string, component: ComponentType<any>): void {\n this.components.set(type, component);\n }\n\n /**\n * Get a block component by type\n */\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n get(type: string): ComponentType<any> | undefined {\n return this.components.get(type);\n }\n\n /**\n * Check if a block type is registered\n */\n has(type: string): boolean {\n return this.components.has(type);\n }\n\n /**\n * Get all registered block types\n */\n getAll(): string[] {\n return Array.from(this.components.keys());\n }\n\n /**\n * Get count of registered components\n */\n size(): number {\n return this.components.size;\n }\n}\n","/**\n * Component Not Found Fallback\n * Logs error when a component type is not registered in the registry\n */\n\ninterface ComponentNotFoundProps {\n type: string;\n config: Record<string, unknown>;\n availableTypes: string[];\n componentKind: \"section\" | \"block\";\n}\n\nexport default function ComponentNotFound({\n type,\n config,\n availableTypes,\n componentKind,\n}: ComponentNotFoundProps) {\n const isDev = process.env.NODE_ENV === \"development\";\n\n const errorDetails = {\n componentKind,\n type,\n config,\n availableTypes,\n timestamp: new Date().toISOString(),\n suggestion: `Create components/${componentKind}s/${type}.tsx and register it in lib/registries/${componentKind}-registry.ts`,\n };\n\n if (isDev) {\n console.error(\n `[${componentKind === \"section\" ? \"SectionRenderer\" : \"BlockRenderer\"}] Component not found: \"${type}\"`,\n errorDetails,\n );\n } else {\n console.error(`[CMS] Unknown ${componentKind}: \"${type}\"`);\n }\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n if (typeof window !== \"undefined\" && (window as any).__CMS_ERROR_HANDLER__) {\n try {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n (window as any).__CMS_ERROR_HANDLER__({\n errorType: \"COMPONENT_NOT_FOUND\",\n severity: \"warning\",\n ...errorDetails,\n });\n } catch {\n // Ignore errors from error handler\n }\n }\n\n return (\n <div\n style={{\n minHeight: \"40px\",\n border: \"1px dashed #e5e7eb\",\n borderRadius: \"4px\",\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n color: \"#9ca3af\",\n fontSize: \"12px\",\n fontFamily: \"monospace\",\n }}\n aria-hidden=\"true\"\n >\n {type}\n </div>\n );\n}\n","/**\n * Block Renderer\n * Dynamically renders block components based on schema instance type\n *\n * SSR COMPATIBLE: This is a pure server component with no client hooks.\n * Analytics wrapper is a client component that is rendered by this server\n * component (valid in Next.js RSC architecture).\n */\n\nimport { BlockRegistry } from \"../registry/block-registry\";\nimport ComponentNotFound from \"./component-not-found\";\n\ninterface BlockAnalyticsConfig {\n enabled: boolean;\n event_label: string;\n track_type: \"click\" | \"visibility\" | \"both\";\n visibility_threshold?: number;\n fire_once?: boolean;\n target_providers?: \"all\" | string[];\n custom_params?: Record<string, string>;\n}\n\ntype AnalyticsWrapperComponent = React.ComponentType<{\n analyticsConfig: BlockAnalyticsConfig | undefined;\n blockId: string;\n blockType: string;\n children: React.ReactNode;\n}>;\n\ntype BlockStyleWrapperComponent = React.ComponentType<{\n config: Record<string, unknown>;\n children: React.ReactNode;\n}>;\n\nlet globalAnalyticsWrapper: AnalyticsWrapperComponent | null = null;\nlet globalBlockStyleWrapper: BlockStyleWrapperComponent | null = null;\n\n/**\n * Register a global analytics wrapper component that will be applied to\n * all blocks rendered by BlockRenderer. Call once during app initialization.\n */\nexport function registerAnalyticsWrapper(\n wrapper: AnalyticsWrapperComponent,\n): void {\n globalAnalyticsWrapper = wrapper;\n}\n\n/**\n * Register a global block style wrapper component that applies common\n * styling (padding, margin, color, borderRadius, etc.) to all blocks.\n * Call once during app initialization.\n */\nexport function registerBlockStyleWrapper(\n wrapper: BlockStyleWrapperComponent,\n): void {\n globalBlockStyleWrapper = wrapper;\n}\n\ninterface BlockInstance {\n id: string;\n type: string;\n config?: Record<string, unknown>;\n data?: Record<string, unknown>;\n}\n\ninterface BlockRendererProps {\n block: BlockInstance;\n blockRegistry: BlockRegistry;\n siteId?: string;\n /** Optional wrapper component for block-level analytics (overrides global) */\n analyticsWrapper?: AnalyticsWrapperComponent;\n}\n\nfunction resolveBlockConfig(block: BlockInstance): Record<string, unknown> {\n if (\"config\" in block && block.config !== undefined) {\n return block.config;\n }\n if (\"data\" in block && block.data !== undefined) {\n return block.data;\n }\n return {};\n}\n\n// NO \"use client\" directive - this is a server component\nexport default function BlockRenderer({\n block,\n blockRegistry,\n siteId,\n analyticsWrapper,\n}: BlockRendererProps) {\n const AnalyticsWrapper = analyticsWrapper ?? globalAnalyticsWrapper;\n const { type, id } = block;\n\n // eslint-disable-next-line react-hooks/static-components -- dynamic registry lookup by design\n const BlockComponent = blockRegistry.get(type);\n\n if (!BlockComponent) {\n const config = resolveBlockConfig(block);\n\n return (\n <ComponentNotFound\n type={type}\n config={config}\n availableTypes={blockRegistry.getAll()}\n componentKind=\"block\"\n />\n );\n }\n\n // Extract analytics config from the block config\n const blockAnalytics = block.config?.analytics as\n | BlockAnalyticsConfig\n | undefined;\n\n const StyleWrapper = globalBlockStyleWrapper;\n\n // Helper to wrap rendered block with analytics if wrapper is provided\n const wrapWithAnalytics = (element: React.ReactElement) => {\n if (AnalyticsWrapper) {\n return (\n <AnalyticsWrapper\n analyticsConfig={blockAnalytics}\n blockId={id}\n blockType={type}\n >\n {element}\n </AnalyticsWrapper>\n );\n }\n return element;\n };\n\n // Form blocks get blockId, siteId, and their embedded data\n if (type.startsWith(\"form-\")) {\n return wrapWithAnalytics(<BlockComponent blockId={id} siteId={siteId} />);\n }\n\n // Special handling for the \"form\" block (embedded form)\n // This block type receives both config AND data\n if (type === \"form\") {\n const config = block.config || {};\n const data = block.data; // This contains FormBlockData from backend\n return wrapWithAnalytics(\n <BlockComponent config={config} data={data} siteId={siteId} />,\n );\n }\n\n // Regular blocks get config (or data if config is not present)\n const config = resolveBlockConfig(block);\n\n // Helper to wrap rendered block with style wrapper if registered\n const wrapWithStyle = (element: React.ReactElement) => {\n if (StyleWrapper) {\n return <StyleWrapper config={config}>{element}</StyleWrapper>;\n }\n return element;\n };\n\n // Blocks that can contain nested blocks need the blockRegistry and siteId\n if (\n type === \"alert\" ||\n type === \"card\" ||\n type === \"modal\" ||\n type === \"grid-layout\" ||\n type === \"flexbox-layout\" ||\n type === \"container-layout\" ||\n type === \"entry-content\"\n ) {\n return wrapWithAnalytics(\n wrapWithStyle(\n <BlockComponent\n config={config}\n siteId={siteId}\n blockRegistry={blockRegistry}\n />,\n ),\n );\n }\n\n return wrapWithAnalytics(\n wrapWithStyle(<BlockComponent config={config} siteId={siteId} />),\n );\n}\n"]}
|
package/dist/index.d.cts
CHANGED
|
@@ -59,11 +59,21 @@ type AnalyticsWrapperComponent = React.ComponentType<{
|
|
|
59
59
|
blockType: string;
|
|
60
60
|
children: React.ReactNode;
|
|
61
61
|
}>;
|
|
62
|
+
type BlockStyleWrapperComponent = React.ComponentType<{
|
|
63
|
+
config: Record<string, unknown>;
|
|
64
|
+
children: React.ReactNode;
|
|
65
|
+
}>;
|
|
62
66
|
/**
|
|
63
67
|
* Register a global analytics wrapper component that will be applied to
|
|
64
68
|
* all blocks rendered by BlockRenderer. Call once during app initialization.
|
|
65
69
|
*/
|
|
66
70
|
declare function registerAnalyticsWrapper(wrapper: AnalyticsWrapperComponent): void;
|
|
71
|
+
/**
|
|
72
|
+
* Register a global block style wrapper component that applies common
|
|
73
|
+
* styling (padding, margin, color, borderRadius, etc.) to all blocks.
|
|
74
|
+
* Call once during app initialization.
|
|
75
|
+
*/
|
|
76
|
+
declare function registerBlockStyleWrapper(wrapper: BlockStyleWrapperComponent): void;
|
|
67
77
|
interface BlockInstance {
|
|
68
78
|
id: string;
|
|
69
79
|
type: string;
|
|
@@ -91,4 +101,4 @@ interface ComponentNotFoundProps {
|
|
|
91
101
|
}
|
|
92
102
|
declare function ComponentNotFound({ type, config, availableTypes, componentKind, }: ComponentNotFoundProps): react_jsx_runtime.JSX.Element;
|
|
93
103
|
|
|
94
|
-
export { type BlockComponent, type BlockComponentProps, BlockRegistry, BlockRenderer, ComponentNotFound, type FormBlockComponentProps, registerAnalyticsWrapper };
|
|
104
|
+
export { type BlockComponent, type BlockComponentProps, BlockRegistry, BlockRenderer, ComponentNotFound, type FormBlockComponentProps, registerAnalyticsWrapper, registerBlockStyleWrapper };
|
package/dist/index.d.ts
CHANGED
|
@@ -59,11 +59,21 @@ type AnalyticsWrapperComponent = React.ComponentType<{
|
|
|
59
59
|
blockType: string;
|
|
60
60
|
children: React.ReactNode;
|
|
61
61
|
}>;
|
|
62
|
+
type BlockStyleWrapperComponent = React.ComponentType<{
|
|
63
|
+
config: Record<string, unknown>;
|
|
64
|
+
children: React.ReactNode;
|
|
65
|
+
}>;
|
|
62
66
|
/**
|
|
63
67
|
* Register a global analytics wrapper component that will be applied to
|
|
64
68
|
* all blocks rendered by BlockRenderer. Call once during app initialization.
|
|
65
69
|
*/
|
|
66
70
|
declare function registerAnalyticsWrapper(wrapper: AnalyticsWrapperComponent): void;
|
|
71
|
+
/**
|
|
72
|
+
* Register a global block style wrapper component that applies common
|
|
73
|
+
* styling (padding, margin, color, borderRadius, etc.) to all blocks.
|
|
74
|
+
* Call once during app initialization.
|
|
75
|
+
*/
|
|
76
|
+
declare function registerBlockStyleWrapper(wrapper: BlockStyleWrapperComponent): void;
|
|
67
77
|
interface BlockInstance {
|
|
68
78
|
id: string;
|
|
69
79
|
type: string;
|
|
@@ -91,4 +101,4 @@ interface ComponentNotFoundProps {
|
|
|
91
101
|
}
|
|
92
102
|
declare function ComponentNotFound({ type, config, availableTypes, componentKind, }: ComponentNotFoundProps): react_jsx_runtime.JSX.Element;
|
|
93
103
|
|
|
94
|
-
export { type BlockComponent, type BlockComponentProps, BlockRegistry, BlockRenderer, ComponentNotFound, type FormBlockComponentProps, registerAnalyticsWrapper };
|
|
104
|
+
export { type BlockComponent, type BlockComponentProps, BlockRegistry, BlockRenderer, ComponentNotFound, type FormBlockComponentProps, registerAnalyticsWrapper, registerBlockStyleWrapper };
|
package/dist/index.js
CHANGED
|
@@ -92,9 +92,13 @@ function ComponentNotFound({
|
|
|
92
92
|
);
|
|
93
93
|
}
|
|
94
94
|
var globalAnalyticsWrapper = null;
|
|
95
|
+
var globalBlockStyleWrapper = null;
|
|
95
96
|
function registerAnalyticsWrapper(wrapper) {
|
|
96
97
|
globalAnalyticsWrapper = wrapper;
|
|
97
98
|
}
|
|
99
|
+
function registerBlockStyleWrapper(wrapper) {
|
|
100
|
+
globalBlockStyleWrapper = wrapper;
|
|
101
|
+
}
|
|
98
102
|
function resolveBlockConfig(block) {
|
|
99
103
|
if ("config" in block && block.config !== void 0) {
|
|
100
104
|
return block.config;
|
|
@@ -126,6 +130,7 @@ function BlockRenderer({
|
|
|
126
130
|
);
|
|
127
131
|
}
|
|
128
132
|
const blockAnalytics = block.config?.analytics;
|
|
133
|
+
const StyleWrapper = globalBlockStyleWrapper;
|
|
129
134
|
const wrapWithAnalytics = (element) => {
|
|
130
135
|
if (AnalyticsWrapper) {
|
|
131
136
|
return /* @__PURE__ */ jsx(
|
|
@@ -151,21 +156,31 @@ function BlockRenderer({
|
|
|
151
156
|
);
|
|
152
157
|
}
|
|
153
158
|
const config = resolveBlockConfig(block);
|
|
159
|
+
const wrapWithStyle = (element) => {
|
|
160
|
+
if (StyleWrapper) {
|
|
161
|
+
return /* @__PURE__ */ jsx(StyleWrapper, { config, children: element });
|
|
162
|
+
}
|
|
163
|
+
return element;
|
|
164
|
+
};
|
|
154
165
|
if (type === "alert" || type === "card" || type === "modal" || type === "grid-layout" || type === "flexbox-layout" || type === "container-layout" || type === "entry-content") {
|
|
155
166
|
return wrapWithAnalytics(
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
167
|
+
wrapWithStyle(
|
|
168
|
+
/* @__PURE__ */ jsx(
|
|
169
|
+
BlockComponent,
|
|
170
|
+
{
|
|
171
|
+
config,
|
|
172
|
+
siteId,
|
|
173
|
+
blockRegistry
|
|
174
|
+
}
|
|
175
|
+
)
|
|
163
176
|
)
|
|
164
177
|
);
|
|
165
178
|
}
|
|
166
|
-
return wrapWithAnalytics(
|
|
179
|
+
return wrapWithAnalytics(
|
|
180
|
+
wrapWithStyle(/* @__PURE__ */ jsx(BlockComponent, { config, siteId }))
|
|
181
|
+
);
|
|
167
182
|
}
|
|
168
183
|
|
|
169
|
-
export { BlockRegistry, BlockRenderer, ComponentNotFound, registerAnalyticsWrapper };
|
|
184
|
+
export { BlockRegistry, BlockRenderer, ComponentNotFound, registerAnalyticsWrapper, registerBlockStyleWrapper };
|
|
170
185
|
//# sourceMappingURL=index.js.map
|
|
171
186
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/registry/block-registry.ts","../src/renderer/component-not-found.tsx","../src/renderer/block-renderer.tsx"],"names":["config","jsx"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * Block Component Registry\n * Manages registration and retrieval of block components\n */\n\nimport { ComponentType } from \"react\";\n\nexport class BlockRegistry {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n private components = new Map<string, ComponentType<any>>();\n\n /**\n * Register a block component\n */\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n register(type: string, component: ComponentType<any>): void {\n this.components.set(type, component);\n }\n\n /**\n * Get a block component by type\n */\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n get(type: string): ComponentType<any> | undefined {\n return this.components.get(type);\n }\n\n /**\n * Check if a block type is registered\n */\n has(type: string): boolean {\n return this.components.has(type);\n }\n\n /**\n * Get all registered block types\n */\n getAll(): string[] {\n return Array.from(this.components.keys());\n }\n\n /**\n * Get count of registered components\n */\n size(): number {\n return this.components.size;\n }\n}\n","/**\n * Component Not Found Fallback\n * Logs error when a component type is not registered in the registry\n */\n\ninterface ComponentNotFoundProps {\n type: string;\n config: Record<string, unknown>;\n availableTypes: string[];\n componentKind: \"section\" | \"block\";\n}\n\nexport default function ComponentNotFound({\n type,\n config,\n availableTypes,\n componentKind,\n}: ComponentNotFoundProps) {\n const isDev = process.env.NODE_ENV === \"development\";\n\n const errorDetails = {\n componentKind,\n type,\n config,\n availableTypes,\n timestamp: new Date().toISOString(),\n suggestion: `Create components/${componentKind}s/${type}.tsx and register it in lib/registries/${componentKind}-registry.ts`,\n };\n\n if (isDev) {\n console.error(\n `[${componentKind === \"section\" ? \"SectionRenderer\" : \"BlockRenderer\"}] Component not found: \"${type}\"`,\n errorDetails,\n );\n } else {\n console.error(`[CMS] Unknown ${componentKind}: \"${type}\"`);\n }\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n if (typeof window !== \"undefined\" && (window as any).__CMS_ERROR_HANDLER__) {\n try {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n (window as any).__CMS_ERROR_HANDLER__({\n errorType: \"COMPONENT_NOT_FOUND\",\n severity: \"warning\",\n ...errorDetails,\n });\n } catch {\n // Ignore errors from error handler\n }\n }\n\n return (\n <div\n style={{\n minHeight: \"40px\",\n border: \"1px dashed #e5e7eb\",\n borderRadius: \"4px\",\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n color: \"#9ca3af\",\n fontSize: \"12px\",\n fontFamily: \"monospace\",\n }}\n aria-hidden=\"true\"\n >\n {type}\n </div>\n );\n}\n","/**\n * Block Renderer\n * Dynamically renders block components based on schema instance type\n *\n * SSR COMPATIBLE: This is a pure server component with no client hooks.\n * Analytics wrapper is a client component that is rendered by this server\n * component (valid in Next.js RSC architecture).\n */\n\nimport { BlockRegistry } from \"../registry/block-registry\";\nimport ComponentNotFound from \"./component-not-found\";\n\ninterface BlockAnalyticsConfig {\n enabled: boolean;\n event_label: string;\n track_type: \"click\" | \"visibility\" | \"both\";\n visibility_threshold?: number;\n fire_once?: boolean;\n target_providers?: \"all\" | string[];\n custom_params?: Record<string, string>;\n}\n\ntype AnalyticsWrapperComponent = React.ComponentType<{\n analyticsConfig: BlockAnalyticsConfig | undefined;\n blockId: string;\n blockType: string;\n children: React.ReactNode;\n}>;\n\nlet globalAnalyticsWrapper: AnalyticsWrapperComponent | null = null;\n\n/**\n * Register a global analytics wrapper component that will be applied to\n * all blocks rendered by BlockRenderer. Call once during app initialization.\n */\nexport function registerAnalyticsWrapper(\n wrapper: AnalyticsWrapperComponent,\n): void {\n globalAnalyticsWrapper = wrapper;\n}\n\ninterface BlockInstance {\n id: string;\n type: string;\n config?: Record<string, unknown>;\n data?: Record<string, unknown>;\n}\n\ninterface BlockRendererProps {\n block: BlockInstance;\n blockRegistry: BlockRegistry;\n siteId?: string;\n /** Optional wrapper component for block-level analytics (overrides global) */\n analyticsWrapper?: AnalyticsWrapperComponent;\n}\n\nfunction resolveBlockConfig(block: BlockInstance): Record<string, unknown> {\n if (\"config\" in block && block.config !== undefined) {\n return block.config;\n }\n if (\"data\" in block && block.data !== undefined) {\n return block.data;\n }\n return {};\n}\n\n// NO \"use client\" directive - this is a server component\nexport default function BlockRenderer({\n block,\n blockRegistry,\n siteId,\n analyticsWrapper,\n}: BlockRendererProps) {\n const AnalyticsWrapper = analyticsWrapper ?? globalAnalyticsWrapper;\n const { type, id } = block;\n\n // eslint-disable-next-line react-hooks/static-components -- dynamic registry lookup by design\n const BlockComponent = blockRegistry.get(type);\n\n if (!BlockComponent) {\n const config = resolveBlockConfig(block);\n\n return (\n <ComponentNotFound\n type={type}\n config={config}\n availableTypes={blockRegistry.getAll()}\n componentKind=\"block\"\n />\n );\n }\n\n // Extract analytics config from the block config\n const blockAnalytics = block.config?.analytics as\n | BlockAnalyticsConfig\n | undefined;\n\n // Helper to wrap rendered block with analytics if wrapper is provided\n const wrapWithAnalytics = (element: React.ReactElement) => {\n if (AnalyticsWrapper) {\n return (\n <AnalyticsWrapper\n analyticsConfig={blockAnalytics}\n blockId={id}\n blockType={type}\n >\n {element}\n </AnalyticsWrapper>\n );\n }\n return element;\n };\n\n // Form blocks get blockId, siteId, and their embedded data\n if (type.startsWith(\"form-\")) {\n return wrapWithAnalytics(<BlockComponent blockId={id} siteId={siteId} />);\n }\n\n // Special handling for the \"form\" block (embedded form)\n // This block type receives both config AND data\n if (type === \"form\") {\n const config = block.config || {};\n const data = block.data; // This contains FormBlockData from backend\n return wrapWithAnalytics(\n <BlockComponent config={config} data={data} siteId={siteId} />,\n );\n }\n\n // Regular blocks get config (or data if config is not present)\n const config = resolveBlockConfig(block);\n\n // Blocks that can contain nested blocks need the blockRegistry and siteId\n if (\n type === \"alert\" ||\n type === \"card\" ||\n type === \"modal\" ||\n type === \"grid-layout\" ||\n type === \"flexbox-layout\" ||\n type === \"container-layout\" ||\n type === \"entry-content\"\n ) {\n return wrapWithAnalytics(\n <BlockComponent\n config={config}\n siteId={siteId}\n blockRegistry={blockRegistry}\n />,\n );\n }\n\n return wrapWithAnalytics(<BlockComponent config={config} siteId={siteId} />);\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/registry/block-registry.ts","../src/renderer/component-not-found.tsx","../src/renderer/block-renderer.tsx"],"names":["config","jsx"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * Block Component Registry\n * Manages registration and retrieval of block components\n */\n\nimport { ComponentType } from \"react\";\n\nexport class BlockRegistry {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n private components = new Map<string, ComponentType<any>>();\n\n /**\n * Register a block component\n */\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n register(type: string, component: ComponentType<any>): void {\n this.components.set(type, component);\n }\n\n /**\n * Get a block component by type\n */\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n get(type: string): ComponentType<any> | undefined {\n return this.components.get(type);\n }\n\n /**\n * Check if a block type is registered\n */\n has(type: string): boolean {\n return this.components.has(type);\n }\n\n /**\n * Get all registered block types\n */\n getAll(): string[] {\n return Array.from(this.components.keys());\n }\n\n /**\n * Get count of registered components\n */\n size(): number {\n return this.components.size;\n }\n}\n","/**\n * Component Not Found Fallback\n * Logs error when a component type is not registered in the registry\n */\n\ninterface ComponentNotFoundProps {\n type: string;\n config: Record<string, unknown>;\n availableTypes: string[];\n componentKind: \"section\" | \"block\";\n}\n\nexport default function ComponentNotFound({\n type,\n config,\n availableTypes,\n componentKind,\n}: ComponentNotFoundProps) {\n const isDev = process.env.NODE_ENV === \"development\";\n\n const errorDetails = {\n componentKind,\n type,\n config,\n availableTypes,\n timestamp: new Date().toISOString(),\n suggestion: `Create components/${componentKind}s/${type}.tsx and register it in lib/registries/${componentKind}-registry.ts`,\n };\n\n if (isDev) {\n console.error(\n `[${componentKind === \"section\" ? \"SectionRenderer\" : \"BlockRenderer\"}] Component not found: \"${type}\"`,\n errorDetails,\n );\n } else {\n console.error(`[CMS] Unknown ${componentKind}: \"${type}\"`);\n }\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n if (typeof window !== \"undefined\" && (window as any).__CMS_ERROR_HANDLER__) {\n try {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n (window as any).__CMS_ERROR_HANDLER__({\n errorType: \"COMPONENT_NOT_FOUND\",\n severity: \"warning\",\n ...errorDetails,\n });\n } catch {\n // Ignore errors from error handler\n }\n }\n\n return (\n <div\n style={{\n minHeight: \"40px\",\n border: \"1px dashed #e5e7eb\",\n borderRadius: \"4px\",\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n color: \"#9ca3af\",\n fontSize: \"12px\",\n fontFamily: \"monospace\",\n }}\n aria-hidden=\"true\"\n >\n {type}\n </div>\n );\n}\n","/**\n * Block Renderer\n * Dynamically renders block components based on schema instance type\n *\n * SSR COMPATIBLE: This is a pure server component with no client hooks.\n * Analytics wrapper is a client component that is rendered by this server\n * component (valid in Next.js RSC architecture).\n */\n\nimport { BlockRegistry } from \"../registry/block-registry\";\nimport ComponentNotFound from \"./component-not-found\";\n\ninterface BlockAnalyticsConfig {\n enabled: boolean;\n event_label: string;\n track_type: \"click\" | \"visibility\" | \"both\";\n visibility_threshold?: number;\n fire_once?: boolean;\n target_providers?: \"all\" | string[];\n custom_params?: Record<string, string>;\n}\n\ntype AnalyticsWrapperComponent = React.ComponentType<{\n analyticsConfig: BlockAnalyticsConfig | undefined;\n blockId: string;\n blockType: string;\n children: React.ReactNode;\n}>;\n\ntype BlockStyleWrapperComponent = React.ComponentType<{\n config: Record<string, unknown>;\n children: React.ReactNode;\n}>;\n\nlet globalAnalyticsWrapper: AnalyticsWrapperComponent | null = null;\nlet globalBlockStyleWrapper: BlockStyleWrapperComponent | null = null;\n\n/**\n * Register a global analytics wrapper component that will be applied to\n * all blocks rendered by BlockRenderer. Call once during app initialization.\n */\nexport function registerAnalyticsWrapper(\n wrapper: AnalyticsWrapperComponent,\n): void {\n globalAnalyticsWrapper = wrapper;\n}\n\n/**\n * Register a global block style wrapper component that applies common\n * styling (padding, margin, color, borderRadius, etc.) to all blocks.\n * Call once during app initialization.\n */\nexport function registerBlockStyleWrapper(\n wrapper: BlockStyleWrapperComponent,\n): void {\n globalBlockStyleWrapper = wrapper;\n}\n\ninterface BlockInstance {\n id: string;\n type: string;\n config?: Record<string, unknown>;\n data?: Record<string, unknown>;\n}\n\ninterface BlockRendererProps {\n block: BlockInstance;\n blockRegistry: BlockRegistry;\n siteId?: string;\n /** Optional wrapper component for block-level analytics (overrides global) */\n analyticsWrapper?: AnalyticsWrapperComponent;\n}\n\nfunction resolveBlockConfig(block: BlockInstance): Record<string, unknown> {\n if (\"config\" in block && block.config !== undefined) {\n return block.config;\n }\n if (\"data\" in block && block.data !== undefined) {\n return block.data;\n }\n return {};\n}\n\n// NO \"use client\" directive - this is a server component\nexport default function BlockRenderer({\n block,\n blockRegistry,\n siteId,\n analyticsWrapper,\n}: BlockRendererProps) {\n const AnalyticsWrapper = analyticsWrapper ?? globalAnalyticsWrapper;\n const { type, id } = block;\n\n // eslint-disable-next-line react-hooks/static-components -- dynamic registry lookup by design\n const BlockComponent = blockRegistry.get(type);\n\n if (!BlockComponent) {\n const config = resolveBlockConfig(block);\n\n return (\n <ComponentNotFound\n type={type}\n config={config}\n availableTypes={blockRegistry.getAll()}\n componentKind=\"block\"\n />\n );\n }\n\n // Extract analytics config from the block config\n const blockAnalytics = block.config?.analytics as\n | BlockAnalyticsConfig\n | undefined;\n\n const StyleWrapper = globalBlockStyleWrapper;\n\n // Helper to wrap rendered block with analytics if wrapper is provided\n const wrapWithAnalytics = (element: React.ReactElement) => {\n if (AnalyticsWrapper) {\n return (\n <AnalyticsWrapper\n analyticsConfig={blockAnalytics}\n blockId={id}\n blockType={type}\n >\n {element}\n </AnalyticsWrapper>\n );\n }\n return element;\n };\n\n // Form blocks get blockId, siteId, and their embedded data\n if (type.startsWith(\"form-\")) {\n return wrapWithAnalytics(<BlockComponent blockId={id} siteId={siteId} />);\n }\n\n // Special handling for the \"form\" block (embedded form)\n // This block type receives both config AND data\n if (type === \"form\") {\n const config = block.config || {};\n const data = block.data; // This contains FormBlockData from backend\n return wrapWithAnalytics(\n <BlockComponent config={config} data={data} siteId={siteId} />,\n );\n }\n\n // Regular blocks get config (or data if config is not present)\n const config = resolveBlockConfig(block);\n\n // Helper to wrap rendered block with style wrapper if registered\n const wrapWithStyle = (element: React.ReactElement) => {\n if (StyleWrapper) {\n return <StyleWrapper config={config}>{element}</StyleWrapper>;\n }\n return element;\n };\n\n // Blocks that can contain nested blocks need the blockRegistry and siteId\n if (\n type === \"alert\" ||\n type === \"card\" ||\n type === \"modal\" ||\n type === \"grid-layout\" ||\n type === \"flexbox-layout\" ||\n type === \"container-layout\" ||\n type === \"entry-content\"\n ) {\n return wrapWithAnalytics(\n wrapWithStyle(\n <BlockComponent\n config={config}\n siteId={siteId}\n blockRegistry={blockRegistry}\n />,\n ),\n );\n }\n\n return wrapWithAnalytics(\n wrapWithStyle(<BlockComponent config={config} siteId={siteId} />),\n );\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@otl-core/block-registry",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.62",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Block registry and renderer for OTL CMS",
|
|
6
6
|
"main": "./dist/index.cjs",
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
"url": "https://github.com/otl-core/block-registry.git"
|
|
35
35
|
},
|
|
36
36
|
"dependencies": {
|
|
37
|
-
"@otl-core/cms-types": "^1.1.
|
|
37
|
+
"@otl-core/cms-types": "^1.1.62"
|
|
38
38
|
},
|
|
39
39
|
"peerDependencies": {
|
|
40
40
|
"react": ">=18.0.0"
|