@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 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
- /* @__PURE__ */ jsxRuntime.jsx(
159
- BlockComponent,
160
- {
161
- config,
162
- siteId,
163
- blockRegistry
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(/* @__PURE__ */ jsxRuntime.jsx(BlockComponent, { config, siteId }));
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
@@ -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":";;;;;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;ACpCA,IAAI,sBAAA,GAA2D,IAAA;AAC/D,IAAI,uBAAA,GAA6D,IAAA;AAM1D,SAAS,yBACd,OAAA,EACM;AACN,EAAA,sBAAA,GAAyB,OAAA;AAC3B;AAOO,SAAS,0BACd,OAAA,EACM;AACN,EAAA,uBAAA,GAA0B,OAAA;AAC5B;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;AAIrC,EAAA,MAAM,YAAA,GAAe,uBAAA;AAGrB,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,MAAM,aAAA,GAAgB,CAAC,OAAA,KAAgC;AACrD,IAAA,IAAI,YAAA,EAAc;AAChB,MAAA,uBAAOD,cAAAA,CAAC,YAAA,EAAA,EAAa,MAAA,EAAiB,QAAA,EAAA,OAAA,EAAQ,CAAA;AAAA,IAChD;AACA,IAAA,OAAO,OAAA;AAAA,EACT,CAAA;AAGA,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,MACL,aAAA;AAAA,wBACEA,cAAAA;AAAA,UAAC,cAAA;AAAA,UAAA;AAAA,YACC,MAAA;AAAA,YACA,MAAA;AAAA,YACA;AAAA;AAAA;AACF;AACF,KACF;AAAA,EACF;AAEA,EAAA,OAAO,iBAAA;AAAA,IACL,8BAAcA,cAAAA,CAAC,cAAA,EAAA,EAAe,MAAA,EAAgB,QAAgB,CAAE;AAAA,GAClE;AACF","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
- /* @__PURE__ */ jsx(
157
- BlockComponent,
158
- {
159
- config,
160
- siteId,
161
- blockRegistry
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(/* @__PURE__ */ jsx(BlockComponent, { config, siteId }));
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":";;;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,uBACE,GAAA;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,MAAMA,OAAAA,GAAS,mBAAmB,KAAK,CAAA;AAEvC,IAAA,uBACEC,GAAAA;AAAA,MAAC,iBAAA;AAAA,MAAA;AAAA,QACC,IAAA;AAAA,QACA,MAAA,EAAQD,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,uBACEC,GAAAA;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,GAAAA,CAAC,kBAAe,OAAA,EAAS,EAAA,EAAI,QAAgB,CAAE,CAAA;AAAA,EAC1E;AAIA,EAAA,IAAI,SAAS,MAAA,EAAQ;AACnB,IAAA,MAAMD,OAAAA,GAAS,KAAA,CAAM,MAAA,IAAU,EAAC;AAChC,IAAA,MAAM,OAAO,KAAA,CAAM,IAAA;AACnB,IAAA,OAAO,iBAAA;AAAA,sBACLC,GAAAA,CAAC,cAAA,EAAA,EAAe,MAAA,EAAQD,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,sBACLC,GAAAA;AAAA,QAAC,cAAA;AAAA,QAAA;AAAA,UACC,MAAA;AAAA,UACA,MAAA;AAAA,UACA;AAAA;AAAA;AACF,KACF;AAAA,EACF;AAEA,EAAA,OAAO,kCAAkBA,GAAAA,CAAC,cAAA,EAAA,EAAe,MAAA,EAAgB,QAAgB,CAAE,CAAA;AAC7E","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.60",
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.60"
37
+ "@otl-core/cms-types": "^1.1.62"
38
38
  },
39
39
  "peerDependencies": {
40
40
  "react": ">=18.0.0"