@otl-core/block-registry 1.1.35 → 1.1.37
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 +18 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +18 -1
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -74,7 +74,24 @@ function ComponentNotFound({
|
|
|
74
74
|
} catch {
|
|
75
75
|
}
|
|
76
76
|
}
|
|
77
|
-
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
77
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
78
|
+
"div",
|
|
79
|
+
{
|
|
80
|
+
style: {
|
|
81
|
+
minHeight: "40px",
|
|
82
|
+
border: "1px dashed #e5e7eb",
|
|
83
|
+
borderRadius: "4px",
|
|
84
|
+
display: "flex",
|
|
85
|
+
alignItems: "center",
|
|
86
|
+
justifyContent: "center",
|
|
87
|
+
color: "#9ca3af",
|
|
88
|
+
fontSize: "12px",
|
|
89
|
+
fontFamily: "monospace"
|
|
90
|
+
},
|
|
91
|
+
"aria-hidden": "true",
|
|
92
|
+
children: type
|
|
93
|
+
}
|
|
94
|
+
);
|
|
78
95
|
}
|
|
79
96
|
var globalAnalyticsWrapper = null;
|
|
80
97
|
function registerAnalyticsWrapper(wrapper) {
|
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 <div style={{ minHeight: \"1px\" }} aria-hidden=\"true\" />;\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":";;;;;AAOO,IAAM,gBAAN,MAAoB;AAAA,EAApB,WAAA,GAAA;AAEL;AAAA,IAAA,IAAA,CAAQ,UAAA,uBAAiB,GAAA,EAAgC;AAAA,EAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAMzD,QAAA,CAAS,MAAc,SAAA,EAAqC;AAC1D,IAAA,IAAA,CAAK,UAAA,CAAW,GAAA,CAAI,IAAA,EAAM,SAAS,CAAA;AAAA,EACrC;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,IAAI,IAAA,EAA8C;AAChD,IAAA,OAAO,IAAA,CAAK,UAAA,CAAW,GAAA,CAAI,IAAI,CAAA;AAAA,EACjC;AAAA;AAAA;AAAA;AAAA,EAKA,IAAI,IAAA,EAAuB;AACzB,IAAA,OAAO,IAAA,CAAK,UAAA,CAAW,GAAA,CAAI,IAAI,CAAA;AAAA,EACjC;AAAA;AAAA;AAAA;AAAA,EAKA,MAAA,GAAmB;AACjB,IAAA,OAAO,KAAA,CAAM,IAAA,CAAK,IAAA,CAAK,UAAA,CAAW,MAAM,CAAA;AAAA,EAC1C;AAAA;AAAA;AAAA;AAAA,EAKA,IAAA,GAAe;AACb,IAAA,OAAO,KAAK,UAAA,CAAW,IAAA;AAAA,EACzB;AACF;ACnCe,SAAR,iBAAA,CAAmC;AAAA,EACxC,IAAA;AAAA,EACA,MAAA;AAAA,EACA,cAAA;AAAA,EACA;AACF,CAAA,EAA2B;AACzB,EAAA,MAAM,KAAA,GAAQ,OAAA,CAAQ,GAAA,CAAI,QAAA,KAAa,aAAA;AAEvC,EAAA,MAAM,YAAA,GAAe;AAAA,IACnB,aAAA;AAAA,IACA,IAAA;AAAA,IACA,MAAA;AAAA,IACA,cAAA;AAAA,IACA,SAAA,EAAA,iBAAW,IAAI,IAAA,EAAK,EAAE,WAAA,EAAY;AAAA,IAClC,YAAY,CAAA,kBAAA,EAAqB,aAAa,CAAA,EAAA,EAAK,IAAI,0CAA0C,aAAa,CAAA,YAAA;AAAA,GAChH;AAEA,EAAA,IAAI,KAAA,EAAO;AACT,IAAA,OAAA,CAAQ,KAAA;AAAA,MACN,IAAI,aAAA,KAAkB,SAAA,GAAY,iBAAA,GAAoB,eAAe,2BAA2B,IAAI,CAAA,CAAA,CAAA;AAAA,MACpG;AAAA,KACF;AAAA,EACF,CAAA,MAAO;AACL,IAAA,OAAA,CAAQ,KAAA,CAAM,CAAA,cAAA,EAAiB,aAAa,CAAA,GAAA,EAAM,IAAI,CAAA,CAAA,CAAG,CAAA;AAAA,EAC3D;AAGA,EAAA,IAAI,OAAO,MAAA,KAAW,WAAA,IAAgB,MAAA,CAAe,qBAAA,EAAuB;AAC1E,IAAA,IAAI;AAEF,MAAC,OAAe,qBAAA,CAAsB;AAAA,QACpC,SAAA,EAAW,qBAAA;AAAA,QACX,QAAA,EAAU,SAAA;AAAA,QACV,GAAG;AAAA,OACJ,CAAA;AAAA,IACH,CAAA,CAAA,MAAQ;AAAA,IAER;AAAA,EACF;AAEA,EAAA,uBACEA,cAAA;AAAA,IAAC,KAAA;AAAA,IAAA;AAAA,MACC,KAAA,EAAO;AAAA,QACL,SAAA,EAAW,MAAA;AAAA,QACX,MAAA,EAAQ,oBAAA;AAAA,QACR,YAAA,EAAc,KAAA;AAAA,QACd,OAAA,EAAS,MAAA;AAAA,QACT,UAAA,EAAY,QAAA;AAAA,QACZ,cAAA,EAAgB,QAAA;AAAA,QAChB,KAAA,EAAO,SAAA;AAAA,QACP,QAAA,EAAU,MAAA;AAAA,QACV,UAAA,EAAY;AAAA,OACd;AAAA,MACA,aAAA,EAAY,MAAA;AAAA,MAEX,QAAA,EAAA;AAAA;AAAA,GACH;AAEJ;ACzCA,IAAI,sBAAA,GAA2D,IAAA;AAMxD,SAAS,yBACd,OAAA,EACM;AACN,EAAA,sBAAA,GAAyB,OAAA;AAC3B;AAiBA,SAAS,mBAAmB,KAAA,EAA+C;AACzE,EAAA,IAAI,QAAA,IAAY,KAAA,IAAS,KAAA,CAAM,MAAA,KAAW,MAAA,EAAW;AACnD,IAAA,OAAO,KAAA,CAAM,MAAA;AAAA,EACf;AACA,EAAA,IAAI,MAAA,IAAU,KAAA,IAAS,KAAA,CAAM,IAAA,KAAS,MAAA,EAAW;AAC/C,IAAA,OAAO,KAAA,CAAM,IAAA;AAAA,EACf;AACA,EAAA,OAAO,EAAC;AACV;AAGe,SAAR,aAAA,CAA+B;AAAA,EACpC,KAAA;AAAA,EACA,aAAA;AAAA,EACA,MAAA;AAAA,EACA;AACF,CAAA,EAAuB;AACrB,EAAA,MAAM,mBAAmB,gBAAA,IAAoB,sBAAA;AAC7C,EAAA,MAAM,EAAE,IAAA,EAAM,EAAA,EAAG,GAAI,KAAA;AAGrB,EAAA,MAAM,cAAA,GAAiB,aAAA,CAAc,GAAA,CAAI,IAAI,CAAA;AAE7C,EAAA,IAAI,CAAC,cAAA,EAAgB;AACnB,IAAA,MAAMC,OAAAA,GAAS,mBAAmB,KAAK,CAAA;AAEvC,IAAA,uBACED,cAAAA;AAAA,MAAC,iBAAA;AAAA,MAAA;AAAA,QACC,IAAA;AAAA,QACA,MAAA,EAAQC,OAAAA;AAAA,QACR,cAAA,EAAgB,cAAc,MAAA,EAAO;AAAA,QACrC,aAAA,EAAc;AAAA;AAAA,KAChB;AAAA,EAEJ;AAGA,EAAA,MAAM,cAAA,GAAiB,MAAM,MAAA,EAAQ,SAAA;AAKrC,EAAA,MAAM,iBAAA,GAAoB,CAAC,OAAA,KAAgC;AACzD,IAAA,IAAI,gBAAA,EAAkB;AACpB,MAAA,uBACED,cAAAA;AAAA,QAAC,gBAAA;AAAA,QAAA;AAAA,UACC,eAAA,EAAiB,cAAA;AAAA,UACjB,OAAA,EAAS,EAAA;AAAA,UACT,SAAA,EAAW,IAAA;AAAA,UAEV,QAAA,EAAA;AAAA;AAAA,OACH;AAAA,IAEJ;AACA,IAAA,OAAO,OAAA;AAAA,EACT,CAAA;AAGA,EAAA,IAAI,IAAA,CAAK,UAAA,CAAW,OAAO,CAAA,EAAG;AAC5B,IAAA,OAAO,kCAAkBA,cAAAA,CAAC,kBAAe,OAAA,EAAS,EAAA,EAAI,QAAgB,CAAE,CAAA;AAAA,EAC1E;AAIA,EAAA,IAAI,SAAS,MAAA,EAAQ;AACnB,IAAA,MAAMC,OAAAA,GAAS,KAAA,CAAM,MAAA,IAAU,EAAC;AAChC,IAAA,MAAM,OAAO,KAAA,CAAM,IAAA;AACnB,IAAA,OAAO,iBAAA;AAAA,sBACLD,cAAAA,CAAC,cAAA,EAAA,EAAe,MAAA,EAAQC,OAAAA,EAAQ,MAAY,MAAA,EAAgB;AAAA,KAC9D;AAAA,EACF;AAGA,EAAA,MAAM,MAAA,GAAS,mBAAmB,KAAK,CAAA;AAGvC,EAAA,IACE,IAAA,KAAS,OAAA,IACT,IAAA,KAAS,MAAA,IACT,IAAA,KAAS,OAAA,IACT,IAAA,KAAS,aAAA,IACT,IAAA,KAAS,gBAAA,IACT,IAAA,KAAS,kBAAA,IACT,SAAS,eAAA,EACT;AACA,IAAA,OAAO,iBAAA;AAAA,sBACLD,cAAAA;AAAA,QAAC,cAAA;AAAA,QAAA;AAAA,UACC,MAAA;AAAA,UACA,MAAA;AAAA,UACA;AAAA;AAAA;AACF,KACF;AAAA,EACF;AAEA,EAAA,OAAO,kCAAkBA,cAAAA,CAAC,cAAA,EAAA,EAAe,MAAA,EAAgB,QAAgB,CAAE,CAAA;AAC7E","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"]}
|
package/dist/index.js
CHANGED
|
@@ -72,7 +72,24 @@ function ComponentNotFound({
|
|
|
72
72
|
} catch {
|
|
73
73
|
}
|
|
74
74
|
}
|
|
75
|
-
return /* @__PURE__ */ jsx(
|
|
75
|
+
return /* @__PURE__ */ jsx(
|
|
76
|
+
"div",
|
|
77
|
+
{
|
|
78
|
+
style: {
|
|
79
|
+
minHeight: "40px",
|
|
80
|
+
border: "1px dashed #e5e7eb",
|
|
81
|
+
borderRadius: "4px",
|
|
82
|
+
display: "flex",
|
|
83
|
+
alignItems: "center",
|
|
84
|
+
justifyContent: "center",
|
|
85
|
+
color: "#9ca3af",
|
|
86
|
+
fontSize: "12px",
|
|
87
|
+
fontFamily: "monospace"
|
|
88
|
+
},
|
|
89
|
+
"aria-hidden": "true",
|
|
90
|
+
children: type
|
|
91
|
+
}
|
|
92
|
+
);
|
|
76
93
|
}
|
|
77
94
|
var globalAnalyticsWrapper = null;
|
|
78
95
|
function registerAnalyticsWrapper(wrapper) {
|
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 <div style={{ minHeight: \"1px\" }} aria-hidden=\"true\" />;\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\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"]}
|
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.37",
|
|
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.37"
|
|
38
38
|
},
|
|
39
39
|
"peerDependencies": {
|
|
40
40
|
"react": ">=18.0.0"
|