@qwickapps/react-framework 1.3.4 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1688 -2
- package/dist/__tests__/schemas/transformers/MockSerializableComponent.d.ts +66 -0
- package/dist/__tests__/schemas/transformers/MockSerializableComponent.d.ts.map +1 -0
- package/dist/components/ErrorBoundary.d.ts +7 -0
- package/dist/components/ErrorBoundary.d.ts.map +1 -1
- package/dist/components/Html.d.ts +28 -18
- package/dist/components/Html.d.ts.map +1 -1
- package/dist/components/Logo.d.ts +12 -35
- package/dist/components/Logo.d.ts.map +1 -1
- package/dist/components/Markdown.d.ts +18 -13
- package/dist/components/Markdown.d.ts.map +1 -1
- package/dist/components/QwickApp.d.ts +16 -3
- package/dist/components/QwickApp.d.ts.map +1 -1
- package/dist/components/QwickIcon.d.ts +23 -0
- package/dist/components/QwickIcon.d.ts.map +1 -0
- package/dist/components/SafeSpan.d.ts +12 -5
- package/dist/components/SafeSpan.d.ts.map +1 -1
- package/dist/components/Scaffold.d.ts.map +1 -1
- package/dist/components/base/ModelView.d.ts +101 -0
- package/dist/components/base/ModelView.d.ts.map +1 -0
- package/dist/components/base/index.d.ts +11 -0
- package/dist/components/base/index.d.ts.map +1 -0
- package/dist/components/blocks/Article.d.ts +12 -2
- package/dist/components/blocks/Article.d.ts.map +1 -1
- package/dist/components/blocks/Code.d.ts +13 -2
- package/dist/components/blocks/Code.d.ts.map +1 -1
- package/dist/components/blocks/Content.d.ts.map +1 -1
- package/dist/components/blocks/CoverImageHeader.d.ts.map +1 -1
- package/dist/components/blocks/FeatureCard.d.ts.map +1 -1
- package/dist/components/blocks/FeatureGrid.d.ts.map +1 -1
- package/dist/components/blocks/Footer.d.ts.map +1 -1
- package/dist/components/blocks/HeroBlock.d.ts +27 -13
- package/dist/components/blocks/HeroBlock.d.ts.map +1 -1
- package/dist/components/blocks/Image.d.ts +41 -0
- package/dist/components/blocks/Image.d.ts.map +1 -0
- package/dist/components/blocks/PageBannerHeader.d.ts.map +1 -1
- package/dist/components/blocks/ProductCard.d.ts.map +1 -1
- package/dist/components/blocks/Section.d.ts +16 -2
- package/dist/components/blocks/Section.d.ts.map +1 -1
- package/dist/components/blocks/Text.d.ts +41 -0
- package/dist/components/blocks/Text.d.ts.map +1 -0
- package/dist/components/blocks/index.d.ts +4 -0
- package/dist/components/blocks/index.d.ts.map +1 -1
- package/dist/components/buttons/Button.d.ts +23 -7
- package/dist/components/buttons/Button.d.ts.map +1 -1
- package/dist/components/forms/FormBlock.d.ts +19 -13
- package/dist/components/forms/FormBlock.d.ts.map +1 -1
- package/dist/components/index.d.ts +4 -0
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/input/ChoiceInputField.d.ts +17 -11
- package/dist/components/input/ChoiceInputField.d.ts.map +1 -1
- package/dist/components/input/HtmlInputField.d.ts +17 -11
- package/dist/components/input/HtmlInputField.d.ts.map +1 -1
- package/dist/components/input/SelectInputField.d.ts +16 -10
- package/dist/components/input/SelectInputField.d.ts.map +1 -1
- package/dist/components/input/SwitchInputField.d.ts +16 -10
- package/dist/components/input/SwitchInputField.d.ts.map +1 -1
- package/dist/components/input/TextField.d.ts.map +1 -1
- package/dist/components/input/TextInputField.d.ts +16 -11
- package/dist/components/input/TextInputField.d.ts.map +1 -1
- package/dist/components/layout/GridCell.d.ts +23 -6
- package/dist/components/layout/GridCell.d.ts.map +1 -1
- package/dist/components/layout/GridLayout.d.ts +24 -23
- package/dist/components/layout/GridLayout.d.ts.map +1 -1
- package/dist/components/pages/FormPage.d.ts.map +1 -1
- package/dist/components/pages/Page.d.ts +49 -87
- package/dist/components/pages/Page.d.ts.map +1 -1
- package/dist/components/pages/index.d.ts +2 -2
- package/dist/components/pages/index.d.ts.map +1 -1
- package/dist/config/AppConfig.d.ts +49 -0
- package/dist/config/AppConfig.d.ts.map +1 -0
- package/dist/config/AppConfigBuilder.d.ts +75 -0
- package/dist/config/AppConfigBuilder.d.ts.map +1 -0
- package/dist/config/index.d.ts +13 -0
- package/dist/config/index.d.ts.map +1 -0
- package/dist/config/types.d.ts +130 -0
- package/dist/config/types.d.ts.map +1 -0
- package/dist/config.d.ts +15 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.esm.js +451 -0
- package/dist/config.js +455 -0
- package/dist/contexts/PrintModeContext.d.ts +27 -0
- package/dist/contexts/PrintModeContext.d.ts.map +1 -0
- package/dist/contexts/QwickAppContext.d.ts +2 -2
- package/dist/contexts/QwickAppContext.d.ts.map +1 -1
- package/dist/contexts/ThemeContext.d.ts.map +1 -1
- package/dist/contexts/index.d.ts +2 -0
- package/dist/contexts/index.d.ts.map +1 -1
- package/dist/hooks/index.d.ts +2 -0
- package/dist/hooks/index.d.ts.map +1 -1
- package/dist/hooks/usePrintMode.d.ts +39 -0
- package/dist/hooks/usePrintMode.d.ts.map +1 -0
- package/dist/index.css +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.esm.css +1 -1
- package/dist/index.esm.js +20722 -16021
- package/dist/index.js +20725 -16010
- package/dist/schemas/CodeSchema.d.ts +2 -1
- package/dist/schemas/CodeSchema.d.ts.map +1 -1
- package/dist/schemas/CollapsibleLayoutSchema.d.ts +2 -1
- package/dist/schemas/CollapsibleLayoutSchema.d.ts.map +1 -1
- package/dist/schemas/ContentSchema.d.ts +2 -1
- package/dist/schemas/ContentSchema.d.ts.map +1 -1
- package/dist/schemas/GridCellSchema.d.ts +25 -0
- package/dist/schemas/GridCellSchema.d.ts.map +1 -0
- package/dist/schemas/GridLayoutSchema.d.ts +23 -0
- package/dist/schemas/GridLayoutSchema.d.ts.map +1 -0
- package/dist/schemas/HtmlSchema.d.ts +14 -0
- package/dist/schemas/HtmlSchema.d.ts.map +1 -0
- package/dist/schemas/ImageSchema.d.ts +32 -0
- package/dist/schemas/ImageSchema.d.ts.map +1 -0
- package/dist/schemas/LogoSchema.d.ts +35 -0
- package/dist/schemas/LogoSchema.d.ts.map +1 -0
- package/dist/schemas/MarkdownSchema.d.ts +14 -0
- package/dist/schemas/MarkdownSchema.d.ts.map +1 -0
- package/dist/schemas/PageTemplateSchema.d.ts +31 -0
- package/dist/schemas/PageTemplateSchema.d.ts.map +1 -0
- package/dist/schemas/PrintConfigSchema.d.ts +31 -0
- package/dist/schemas/PrintConfigSchema.d.ts.map +1 -0
- package/dist/schemas/SectionSchema.d.ts +2 -1
- package/dist/schemas/SectionSchema.d.ts.map +1 -1
- package/dist/schemas/TextSchema.d.ts +37 -0
- package/dist/schemas/TextSchema.d.ts.map +1 -0
- package/dist/schemas/ViewModelSchema.d.ts +23 -0
- package/dist/schemas/ViewModelSchema.d.ts.map +1 -0
- package/dist/schemas/index.d.ts +15 -1
- package/dist/schemas/index.d.ts.map +1 -1
- package/dist/schemas/transformers/ComponentTransformer.d.ts +116 -0
- package/dist/schemas/transformers/ComponentTransformer.d.ts.map +1 -0
- package/dist/schemas/transformers/ReactNodeTransformer.d.ts +53 -0
- package/dist/schemas/transformers/ReactNodeTransformer.d.ts.map +1 -0
- package/dist/schemas/transformers/__tests__/MockSerializableComponent.d.ts +66 -0
- package/dist/schemas/transformers/__tests__/MockSerializableComponent.d.ts.map +1 -0
- package/dist/schemas/transformers/registry.d.ts +15 -0
- package/dist/schemas/transformers/registry.d.ts.map +1 -0
- package/dist/schemas/types/Serializable.d.ts +46 -0
- package/dist/schemas/types/Serializable.d.ts.map +1 -0
- package/dist/utils/htmlTransform.d.ts.map +1 -1
- package/dist/utils/reactUtils.d.ts +12 -3
- package/dist/utils/reactUtils.d.ts.map +1 -1
- package/package.json +17 -3
- package/src/{components/__tests__ → __tests__/components}/AccessibilityProvider.test.tsx +1 -1
- package/src/{components/__tests__ → __tests__/components}/Article.test.tsx +1 -1
- package/src/{components/__tests__ → __tests__/components}/Breadcrumbs.test.tsx +1 -1
- package/src/{components/__tests__ → __tests__/components}/Button.test.tsx +1 -1
- package/src/{components/__tests__ → __tests__/components}/CardListGrid.test.tsx +2 -2
- package/src/{components/__tests__ → __tests__/components}/ChoiceInputField.test.tsx +1 -1
- package/src/{components/__tests__ → __tests__/components}/Code.test.tsx +1 -1
- package/src/{components/__tests__ → __tests__/components}/Content.integration.test.tsx +1 -1
- package/src/{components/__tests__ → __tests__/components}/Content.test.tsx +1 -1
- package/src/{components/__tests__ → __tests__/components}/CoverImageHeader.test.tsx +2 -2
- package/src/{components/__tests__ → __tests__/components}/ErrorBoundary.test.tsx +1 -1
- package/src/{components/__tests__ → __tests__/components}/FeatureCard.integration.test.tsx +2 -2
- package/src/{components/__tests__ → __tests__/components}/FeatureGrid.integration.test.tsx +2 -2
- package/src/{components/__tests__ → __tests__/components}/FeatureGrid.test.tsx +2 -2
- package/src/{components/__tests__ → __tests__/components}/Footer.test.tsx +4 -4
- package/src/{components/__tests__ → __tests__/components}/FormBlock.test.tsx +1 -1
- package/src/{components/__tests__ → __tests__/components}/HeroBlock.integration.test.tsx +2 -2
- package/src/{components/__tests__ → __tests__/components}/HeroBlock.test.tsx +233 -7
- package/src/{components/__tests__ → __tests__/components}/Html.test.tsx +11 -2
- package/src/{components/__tests__ → __tests__/components}/HtmlInputField.test.tsx +3 -3
- package/src/__tests__/components/Logo.test.js +3 -3
- package/src/{components/__tests__ → __tests__/components}/Markdown.test.tsx +1 -1
- package/src/{components/__tests__ → __tests__/components}/PageBannerHeader.test.tsx +3 -3
- package/src/{components/__tests__ → __tests__/components}/PaletteSwitcher.test.tsx +3 -3
- package/src/{components/__tests__ → __tests__/components}/ProductCard.test.tsx +4 -4
- package/src/{components/__tests__ → __tests__/components}/SafeSpan.integration.test.tsx +2 -2
- package/src/{components/__tests__ → __tests__/components}/SafeSpan.simple.test.tsx +1 -1
- package/src/{components/__tests__ → __tests__/components}/SafeSpan.test.tsx +1 -1
- package/src/{components/__tests__ → __tests__/components}/Section.integration.test.tsx +1 -1
- package/src/{components/__tests__ → __tests__/components}/Section.test.tsx +1 -1
- package/src/{components/__tests__ → __tests__/components}/SelectInputField.test.tsx +1 -1
- package/src/{components/__tests__ → __tests__/components}/TextInputField.test.tsx +3 -3
- package/src/{components/__tests__ → __tests__/components}/ThemeSwitcher.test.tsx +3 -3
- package/src/__tests__/components/base/ModelView.test.tsx +220 -0
- package/src/__tests__/components/blocks/Code.performance.test.tsx +625 -0
- package/src/__tests__/components/blocks/Code.serialization.test.tsx +507 -0
- package/src/__tests__/components/blocks/HeroBlock.serialization.test.tsx +414 -0
- package/src/__tests__/components/blocks/Image.serialization.test.tsx +257 -0
- package/src/__tests__/components/blocks/Section.serialization.test.tsx +553 -0
- package/src/__tests__/components/blocks/Text.performance.test.tsx +442 -0
- package/src/__tests__/components/blocks/Text.serialization.test.tsx +491 -0
- package/src/__tests__/components/buttons/Button.serialization.test.tsx +443 -0
- package/src/__tests__/components/input/FormComponents.serialization.test.tsx +482 -0
- package/src/__tests__/components/input/SelectInputField.serialization.test.tsx +439 -0
- package/src/__tests__/components/input/TextInputField.serialization.test.tsx +359 -0
- package/src/{components/layout/CollapsibleLayout/__tests__ → __tests__/components/layout}/CollapsibleLayout.test.tsx +4 -4
- package/src/__tests__/components/layout/GridCell.serialization.test.tsx +403 -0
- package/src/__tests__/components/layout/GridLayout.serialization.test.tsx +311 -0
- package/src/__tests__/hooks/usePrintMode.test.ts +89 -0
- package/src/__tests__/schemas/PageTemplateSchema.test.ts +161 -0
- package/src/__tests__/schemas/PrintConfigSchema.test.ts +127 -0
- package/src/__tests__/schemas/ViewModelSchema.test.ts +80 -0
- package/src/__tests__/schemas/transformers/ComponentSerializationPatterns.test.tsx +602 -0
- package/src/__tests__/schemas/transformers/ComponentTransformer.htmlPatterns.test.ts +301 -0
- package/src/__tests__/schemas/transformers/ComponentTransformer.test.ts +521 -0
- package/src/__tests__/schemas/transformers/CrossBrowserCompatibility.test.ts +586 -0
- package/src/__tests__/schemas/transformers/MockSerializableComponent.ts +103 -0
- package/src/__tests__/schemas/transformers/RealWorldScenarios.test.tsx +1165 -0
- package/src/__tests__/schemas/transformers/SerializationErrorHandling.test.ts +602 -0
- package/src/__tests__/schemas/transformers/SerializationIntegration.test.tsx +691 -0
- package/src/__tests__/schemas/transformers/SerializationPerformance.test.ts +460 -0
- package/src/__tests__/schemas/transformers/TestAutomation.test.ts +597 -0
- package/src/{utils/__tests__ → __tests__/utils}/nested-dom-fix.test.tsx +1 -1
- package/src/components/ErrorBoundary.tsx +8 -8
- package/src/components/Html.tsx +147 -44
- package/src/components/Logo.tsx +198 -100
- package/src/components/Markdown.tsx +125 -16
- package/src/components/QwickApp.tsx +64 -31
- package/src/components/QwickIcon.tsx +59 -0
- package/src/components/SafeSpan.tsx +65 -10
- package/src/components/Scaffold.tsx +2 -8
- package/src/components/base/ModelView.tsx +199 -0
- package/src/components/base/index.ts +11 -0
- package/src/components/blocks/Article.tsx +57 -18
- package/src/components/blocks/Code.md +529 -0
- package/src/components/blocks/Code.tsx +102 -15
- package/src/components/blocks/Content.tsx +25 -77
- package/src/components/blocks/CoverImageHeader.tsx +9 -4
- package/src/components/blocks/FeatureCard.tsx +1 -2
- package/src/components/blocks/FeatureGrid.tsx +19 -1
- package/src/components/blocks/Footer.tsx +13 -1
- package/src/components/blocks/HeroBlock.tsx +87 -20
- package/src/components/blocks/Image.tsx +395 -0
- package/src/components/blocks/PageBannerHeader.tsx +14 -12
- package/src/components/blocks/ProductCard.tsx +51 -52
- package/src/components/blocks/Section.tsx +113 -8
- package/src/components/blocks/Text.tsx +285 -0
- package/src/components/blocks/index.ts +4 -0
- package/src/components/buttons/Button.tsx +184 -15
- package/src/components/forms/FormBlock.tsx +70 -17
- package/src/components/index.ts +5 -0
- package/src/components/input/ChoiceInputField.tsx +48 -18
- package/src/components/input/HtmlInputField.tsx +48 -18
- package/src/components/input/SelectInputField.tsx +48 -16
- package/src/components/input/SwitchInputField.tsx +48 -17
- package/src/components/input/TextField.tsx +41 -1
- package/src/components/input/TextInputField.tsx +52 -18
- package/src/components/layout/GridCell.tsx +118 -9
- package/src/components/layout/GridLayout.tsx +125 -24
- package/src/components/pages/FormPage.tsx +0 -1
- package/src/components/pages/Page.css +304 -332
- package/src/components/pages/Page.tsx +307 -255
- package/src/components/pages/index.ts +2 -2
- package/src/config/AppConfig.ts +133 -0
- package/src/config/AppConfigBuilder.ts +421 -0
- package/src/config/__tests__/AppConfig.test.ts +385 -0
- package/src/config/__tests__/AppConfigBuilder.test.ts +432 -0
- package/src/config/index.ts +24 -0
- package/src/config/types.ts +170 -0
- package/src/config.ts +25 -0
- package/src/contexts/PrintModeContext.tsx +332 -0
- package/src/contexts/QwickAppContext.tsx +2 -2
- package/src/contexts/ThemeContext.tsx +1 -2
- package/src/contexts/index.ts +2 -0
- package/src/hooks/index.ts +5 -1
- package/src/hooks/usePrintMode.ts +73 -0
- package/src/index.ts +3 -0
- package/src/schemas/CodeSchema.ts +3 -3
- package/src/schemas/CollapsibleLayoutSchema.ts +2 -1
- package/src/schemas/ContentSchema.ts +2 -1
- package/src/schemas/GridCellSchema.ts +164 -0
- package/src/schemas/GridLayoutSchema.ts +133 -0
- package/src/schemas/HtmlSchema.ts +47 -0
- package/src/schemas/ImageSchema.ts +235 -0
- package/src/schemas/LogoSchema.ts +241 -0
- package/src/schemas/MarkdownSchema.ts +47 -0
- package/src/schemas/PageTemplateSchema.ts +186 -0
- package/src/schemas/PrintConfigSchema.ts +207 -0
- package/src/schemas/README.md +661 -0
- package/src/schemas/SectionSchema.ts +2 -1
- package/src/schemas/TextSchema.ts +329 -0
- package/src/schemas/ViewModelSchema.ts +115 -0
- package/src/schemas/index.ts +21 -2
- package/src/schemas/transformers/ComponentTransformer.ts +403 -0
- package/src/schemas/transformers/ReactNodeTransformer.ts +236 -0
- package/src/schemas/transformers/registry.ts +72 -0
- package/src/schemas/types/Serializable.ts +51 -0
- package/src/stories/AccessibilityProvider.stories.tsx +253 -253
- package/src/stories/Article.stories.tsx +433 -433
- package/src/stories/Button.stories.tsx +1 -1
- package/src/stories/CardListGrid.stories.tsx +451 -451
- package/src/stories/ChoiceInputField.stories.tsx +503 -503
- package/src/stories/Code.stories.tsx +1 -1
- package/src/stories/CollapsibleLayout.stories.tsx +1414 -1414
- package/src/stories/Content.stories.tsx +393 -393
- package/src/stories/CoverImageHeader.stories.tsx +701 -701
- package/src/stories/DataBinding.advanced.stories.tsx +432 -432
- package/src/stories/DataProvider.stories.tsx +1192 -1192
- package/src/stories/FeatureCard.stories.tsx +557 -557
- package/src/stories/FeatureGrid.stories.tsx +594 -594
- package/src/stories/Footer.stories.tsx +640 -640
- package/src/stories/FormBlock.stories.tsx +760 -760
- package/src/stories/FormComponents.stories.tsx +349 -541
- package/src/stories/GridCell.stories.tsx +417 -0
- package/src/stories/GridLayout.stories.tsx +353 -0
- package/src/stories/HeroBlock.stories.tsx +862 -373
- package/src/stories/HtmlInputField.stories.tsx +474 -474
- package/src/stories/Image.stories.tsx +819 -0
- package/src/stories/Introduction.stories.tsx +667 -667
- package/src/stories/LayoutBlocks.stories.tsx +324 -324
- package/src/stories/Logo.stories.tsx +165 -6
- package/src/stories/Markdown.stories.tsx +137 -137
- package/src/stories/ModelView.stories.tsx +477 -0
- package/src/stories/Page.stories.tsx +688 -688
- package/src/stories/PageBannerHeader.stories.tsx +864 -864
- package/src/stories/PaletteSwitcher.stories.tsx +119 -119
- package/src/stories/ProductCard.stories.tsx +424 -424
- package/src/stories/QwickApp.stories.tsx +368 -368
- package/src/stories/ResponsiveMenu.stories.tsx +249 -249
- package/src/stories/SafeSpan.stories.tsx +531 -531
- package/src/stories/Section.stories.tsx +90 -2
- package/src/stories/SelectInputField.stories.tsx +524 -524
- package/src/stories/Text.stories.tsx +560 -0
- package/src/stories/TextInputField.stories.tsx +443 -443
- package/src/stories/ThemeSwitcher.stories.tsx +123 -123
- package/src/utils/htmlTransform.tsx +74 -53
- package/src/utils/reactUtils.tsx +57 -6
- package/dist/index.bundled.css +0 -12
- /package/src/{hooks/__tests__ → __tests__/hooks}/useDataBinding.test.tsx.disabled +0 -0
- /package/src/{schemas/__tests__ → __tests__/schemas}/builders.test.ts +0 -0
- /package/src/{utils/__tests__ → __tests__/utils}/createDataDrivenComponent.test.tsx.disabled +0 -0
- /package/src/{utils/__tests__ → __tests__/utils}/htmlTransform.test.tsx +0 -0
- /package/src/{utils/__tests__ → __tests__/utils}/optional-logging.test.ts +0 -0
|
@@ -0,0 +1,403 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ComponentTransformer - Core component serialization and HTML pattern transformation system
|
|
3
|
+
*
|
|
4
|
+
* Enables "WebView for React" functionality by providing serialization
|
|
5
|
+
* and deserialization of React components to/from JSON structures.
|
|
6
|
+
* Also supports HTML pattern-based transformations where components can
|
|
7
|
+
* register HTML patterns they can handle.
|
|
8
|
+
*
|
|
9
|
+
* Copyright (c) 2025 QwickApps.com. All rights reserved.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import React, { ReactNode, ReactElement } from 'react';
|
|
13
|
+
import { SerializableConstructor } from '../types/Serializable';
|
|
14
|
+
import { ReactNodeTransformer } from './ReactNodeTransformer';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Registry for component classes that support serialization
|
|
18
|
+
*/
|
|
19
|
+
const componentRegistry = new Map<string, SerializableConstructor>();
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Registry for HTML pattern handlers
|
|
23
|
+
*/
|
|
24
|
+
const patternRegistry = new Map<string, PatternHandler>();
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Type for HTML pattern transformation handlers
|
|
28
|
+
*/
|
|
29
|
+
export type PatternHandler = (element: Element) => any;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Core transformer for React component serialization
|
|
33
|
+
* Provides static methods for component registration and transformation
|
|
34
|
+
*/
|
|
35
|
+
export class ComponentTransformer {
|
|
36
|
+
/**
|
|
37
|
+
* Register a component class for serialization
|
|
38
|
+
* Component must declare its own tagName and version via static properties
|
|
39
|
+
* @param componentClass - Component class that implements Serializable interface
|
|
40
|
+
*/
|
|
41
|
+
static registerComponent(componentClass: SerializableConstructor): void {
|
|
42
|
+
const { tagName, version } = componentClass;
|
|
43
|
+
|
|
44
|
+
if (!tagName || typeof tagName !== 'string') {
|
|
45
|
+
throw new Error(`Component class must have a static 'tagName' property`);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (!version || typeof version !== 'string') {
|
|
49
|
+
throw new Error(`Component class must have a static 'version' property`);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (componentRegistry.has(tagName)) {
|
|
53
|
+
console.warn(`Component '${tagName}' is already registered. Overwriting existing registration.`);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
componentRegistry.set(tagName, componentClass);
|
|
57
|
+
|
|
58
|
+
// Register HTML patterns if component supports them
|
|
59
|
+
if (typeof (componentClass as any).registerPatternHandlers === 'function') {
|
|
60
|
+
(componentClass as any).registerPatternHandlers(ComponentTransformer);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Serialize React node(s) to JSON string
|
|
66
|
+
* @param node - React node or array of nodes to serialize
|
|
67
|
+
* @returns JSON string representation
|
|
68
|
+
*/
|
|
69
|
+
static serialize(node: ReactNode | ReactNode[]): string {
|
|
70
|
+
const serializedData = ComponentTransformer.serializeNode(node);
|
|
71
|
+
return JSON.stringify(serializedData);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Deserialize JSON input to React node(s)
|
|
76
|
+
* @param input - JSON string or parsed object/array to deserialize
|
|
77
|
+
* @returns React node or array of nodes
|
|
78
|
+
*/
|
|
79
|
+
static deserialize(input: string | object | object[]): ReactNode | ReactNode[] {
|
|
80
|
+
let parsedData: any;
|
|
81
|
+
|
|
82
|
+
// Handle string input - parse JSON only if it looks like JSON
|
|
83
|
+
if (typeof input === 'string') {
|
|
84
|
+
// Check if string looks like JSON (starts with { [ or " or is a boolean/number/null)
|
|
85
|
+
const trimmed = input.trim();
|
|
86
|
+
if (trimmed.startsWith('{') || trimmed.startsWith('[') || trimmed.startsWith('"') ||
|
|
87
|
+
trimmed === 'null' || trimmed === 'true' || trimmed === 'false' ||
|
|
88
|
+
(!isNaN(Number(trimmed)) && trimmed !== '')) {
|
|
89
|
+
try {
|
|
90
|
+
parsedData = JSON.parse(input);
|
|
91
|
+
} catch (error) {
|
|
92
|
+
throw new Error(`Invalid JSON input: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
93
|
+
}
|
|
94
|
+
} else {
|
|
95
|
+
// For strings that contain JSON-like characters but aren't valid JSON, try to parse anyway
|
|
96
|
+
if (trimmed.includes('{') || trimmed.includes('[')) {
|
|
97
|
+
try {
|
|
98
|
+
parsedData = JSON.parse(input);
|
|
99
|
+
} catch (error) {
|
|
100
|
+
throw new Error(`Invalid JSON input: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
101
|
+
}
|
|
102
|
+
} else {
|
|
103
|
+
// Treat as plain string
|
|
104
|
+
parsedData = input;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
} else {
|
|
108
|
+
parsedData = input;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return ComponentTransformer.deserializeData(parsedData);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Internal method to serialize a single React node
|
|
116
|
+
* @param node - React node to serialize
|
|
117
|
+
* @returns Serializable data structure
|
|
118
|
+
*/
|
|
119
|
+
private static serializeNode(node: ReactNode): any {
|
|
120
|
+
if (node === null || node === undefined) {
|
|
121
|
+
return null;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Handle arrays of nodes
|
|
125
|
+
if (Array.isArray(node)) {
|
|
126
|
+
return node.map(child => ComponentTransformer.serializeNode(child));
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Handle primitive values
|
|
130
|
+
if (typeof node === 'string' || typeof node === 'number' || typeof node === 'boolean') {
|
|
131
|
+
return node;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Handle plain objects (non-React elements)
|
|
135
|
+
if (typeof node === 'object' && node !== null && !('type' in node)) {
|
|
136
|
+
// For plain objects, try to serialize recursively or convert to string
|
|
137
|
+
try {
|
|
138
|
+
const serialized: any = {};
|
|
139
|
+
for (const [key, value] of Object.entries(node)) {
|
|
140
|
+
serialized[key] = ComponentTransformer.serializeNode(value);
|
|
141
|
+
}
|
|
142
|
+
return serialized;
|
|
143
|
+
} catch {
|
|
144
|
+
return String(node);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Handle React elements
|
|
149
|
+
if (typeof node === 'object' && node !== null && 'type' in node) {
|
|
150
|
+
const element = node as ReactElement;
|
|
151
|
+
|
|
152
|
+
// Check if this is a registered component
|
|
153
|
+
const componentType = element.type;
|
|
154
|
+
if (typeof componentType === 'function') {
|
|
155
|
+
// Find the tag name for this component
|
|
156
|
+
const tagName = ComponentTransformer.findTagNameForComponent(componentType);
|
|
157
|
+
if (tagName) {
|
|
158
|
+
const componentClass = componentRegistry.get(tagName)!;
|
|
159
|
+
// Create a temporary instance to call toJson
|
|
160
|
+
const instance = new (componentClass as any)(element.props);
|
|
161
|
+
const serializedData = instance.toJson();
|
|
162
|
+
|
|
163
|
+
return {
|
|
164
|
+
tag: tagName,
|
|
165
|
+
version: componentClass.version,
|
|
166
|
+
data: serializedData
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// For unregistered components, use ReactNodeTransformer fallback
|
|
172
|
+
return ComponentTransformer.serializeUnregisteredComponent(element);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Fallback for other node types
|
|
176
|
+
return String(node);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Internal method to deserialize data back to React nodes
|
|
181
|
+
* @param data - Data to deserialize
|
|
182
|
+
* @returns React node(s)
|
|
183
|
+
*/
|
|
184
|
+
private static deserializeData(data: any): ReactNode | ReactNode[] {
|
|
185
|
+
if (data === null || data === undefined) {
|
|
186
|
+
return null;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// Handle arrays
|
|
190
|
+
if (Array.isArray(data)) {
|
|
191
|
+
return data.map(item => ComponentTransformer.deserializeData(item));
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// Handle primitive values
|
|
195
|
+
if (typeof data === 'string' || typeof data === 'number' || typeof data === 'boolean') {
|
|
196
|
+
return data;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// Handle serialized component data
|
|
200
|
+
if (typeof data === 'object' && data.tag) {
|
|
201
|
+
const { tag, data: componentData } = data;
|
|
202
|
+
|
|
203
|
+
// Handle unregistered components using ReactNodeTransformer
|
|
204
|
+
if (tag === '__react_node__') {
|
|
205
|
+
return ComponentTransformer.deserializeUnregisteredComponent(componentData);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const componentClass = componentRegistry.get(tag);
|
|
209
|
+
if (!componentClass) {
|
|
210
|
+
// Fallback to ReactNodeTransformer for unknown registered components
|
|
211
|
+
console.warn(`Unknown component: ${tag}. Using ReactNodeTransformer fallback.`);
|
|
212
|
+
return ComponentTransformer.deserializeUnregisteredComponent(componentData);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// Validate that componentData exists
|
|
216
|
+
if (componentData === undefined) {
|
|
217
|
+
throw new Error(`Malformed component data: missing 'data' property for component '${tag}'`);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// Call static fromJson method to recreate the component
|
|
221
|
+
return componentClass.fromJson(componentData);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// Fallback - return as-is
|
|
225
|
+
return data;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Serialize unregistered React components using ReactNodeTransformer
|
|
231
|
+
* @param element - React element to serialize
|
|
232
|
+
* @returns Serializable data structure
|
|
233
|
+
*/
|
|
234
|
+
private static serializeUnregisteredComponent(element: ReactElement): any {
|
|
235
|
+
return {
|
|
236
|
+
tag: '__react_node__',
|
|
237
|
+
version: '1.0.0',
|
|
238
|
+
data: ReactNodeTransformer.serialize(element)
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Deserialize unregistered components using ReactNodeTransformer
|
|
244
|
+
* @param data - Serialized data
|
|
245
|
+
* @returns React node
|
|
246
|
+
*/
|
|
247
|
+
private static deserializeUnregisteredComponent(data: any): ReactNode {
|
|
248
|
+
return ReactNodeTransformer.deserialize(data);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* Find the tag name for a given component constructor
|
|
253
|
+
* @param componentType - Component constructor function
|
|
254
|
+
* @returns Tag name or null if not found
|
|
255
|
+
*/
|
|
256
|
+
private static findTagNameForComponent(componentType: any): string | null {
|
|
257
|
+
const entries = Array.from(componentRegistry.entries());
|
|
258
|
+
for (const [tagName, registeredClass] of entries) {
|
|
259
|
+
// This is a simplified check - in a real implementation you might need
|
|
260
|
+
// more sophisticated matching based on the component's static methods
|
|
261
|
+
if (registeredClass === componentType) {
|
|
262
|
+
return tagName;
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
return null;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* Get list of registered component tags (for debugging/testing)
|
|
270
|
+
* @returns Array of registered tag names
|
|
271
|
+
*/
|
|
272
|
+
static getRegisteredComponents(): string[] {
|
|
273
|
+
return Array.from(componentRegistry.keys());
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* Clear all registered components (for testing)
|
|
278
|
+
*/
|
|
279
|
+
static clearRegistry(): void {
|
|
280
|
+
componentRegistry.clear();
|
|
281
|
+
patternRegistry.clear();
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// HTML Pattern Methods
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* Register an HTML pattern handler
|
|
288
|
+
* @param pattern - CSS selector pattern (e.g., 'pre code', 'section.blog-section')
|
|
289
|
+
* @param handler - Function to transform matching elements to component data
|
|
290
|
+
*/
|
|
291
|
+
static registerPattern(pattern: string, handler: PatternHandler): void {
|
|
292
|
+
if (patternRegistry.has(pattern)) {
|
|
293
|
+
console.warn(`Pattern '${pattern}' is already registered. Overwriting existing handler.`);
|
|
294
|
+
}
|
|
295
|
+
patternRegistry.set(pattern, handler);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* Check if a pattern is registered
|
|
300
|
+
* @param pattern - CSS selector pattern to check
|
|
301
|
+
* @returns True if pattern is registered
|
|
302
|
+
*/
|
|
303
|
+
static hasPattern(pattern: string): boolean {
|
|
304
|
+
return patternRegistry.has(pattern);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* Transform an HTML element to React component if a matching pattern exists
|
|
309
|
+
* @param element - DOM Element to transform
|
|
310
|
+
* @returns React node if pattern matches, null otherwise
|
|
311
|
+
*/
|
|
312
|
+
static transformHTMLElement(element: Element): ReactNode | null {
|
|
313
|
+
// Find matching pattern handler
|
|
314
|
+
for (const [pattern, handler] of patternRegistry) {
|
|
315
|
+
if (element.matches(pattern)) {
|
|
316
|
+
try {
|
|
317
|
+
const componentData = handler(element);
|
|
318
|
+
return ComponentTransformer.deserialize(componentData);
|
|
319
|
+
} catch (error) {
|
|
320
|
+
console.warn(`Error transforming element with pattern '${pattern}':`, error);
|
|
321
|
+
return null;
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
return null; // No pattern matched
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
/**
|
|
329
|
+
* Transform HTML string to React nodes using registered patterns
|
|
330
|
+
* @param html - HTML string to transform
|
|
331
|
+
* @returns Array of React nodes
|
|
332
|
+
*/
|
|
333
|
+
static transformHTML(html: string): ReactNode[] {
|
|
334
|
+
if (!html.trim()) {
|
|
335
|
+
return [];
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
const parser = new DOMParser();
|
|
339
|
+
const doc = parser.parseFromString(html, 'text/html');
|
|
340
|
+
|
|
341
|
+
return Array.from(doc.body.children).map((element, index) =>
|
|
342
|
+
ComponentTransformer.transformElement(element, `element-${index}`)
|
|
343
|
+
);
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
/**
|
|
347
|
+
* Recursively transform an element and its children
|
|
348
|
+
* @param element - DOM Element to transform
|
|
349
|
+
* @param key - React key for the element
|
|
350
|
+
* @returns React node
|
|
351
|
+
*/
|
|
352
|
+
private static transformElement(element: Element, key: string): ReactNode {
|
|
353
|
+
// Try to find a registered pattern handler
|
|
354
|
+
const transformedNode = ComponentTransformer.transformHTMLElement(element);
|
|
355
|
+
if (transformedNode) {
|
|
356
|
+
return transformedNode;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
// No pattern matched - check for nested transformable content
|
|
360
|
+
const children = Array.from(element.children);
|
|
361
|
+
const hasTransformableChildren = children.some(child =>
|
|
362
|
+
Array.from(patternRegistry.keys()).some(pattern => child.matches(pattern))
|
|
363
|
+
);
|
|
364
|
+
|
|
365
|
+
if (hasTransformableChildren) {
|
|
366
|
+
// Transform children recursively
|
|
367
|
+
const transformedChildren = children.map((child, index) =>
|
|
368
|
+
ComponentTransformer.transformElement(child, `${key}-${index}`)
|
|
369
|
+
);
|
|
370
|
+
|
|
371
|
+
// Create element with transformed children
|
|
372
|
+
return React.createElement(
|
|
373
|
+
element.tagName.toLowerCase(),
|
|
374
|
+
{
|
|
375
|
+
key,
|
|
376
|
+
className: element.className || undefined,
|
|
377
|
+
id: element.id || undefined
|
|
378
|
+
},
|
|
379
|
+
transformedChildren
|
|
380
|
+
);
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
// Fallback - use ReactNodeTransformer to handle as unregistered HTML
|
|
384
|
+
return ReactNodeTransformer.deserialize({
|
|
385
|
+
type: 'react-element',
|
|
386
|
+
elementType: element.tagName.toLowerCase(),
|
|
387
|
+
props: {
|
|
388
|
+
key,
|
|
389
|
+
className: element.className || undefined,
|
|
390
|
+
id: element.id || undefined,
|
|
391
|
+
dangerouslySetInnerHTML: { __html: element.innerHTML }
|
|
392
|
+
}
|
|
393
|
+
});
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
/**
|
|
397
|
+
* Get list of registered patterns (for debugging/testing)
|
|
398
|
+
* @returns Array of registered patterns
|
|
399
|
+
*/
|
|
400
|
+
static getRegisteredPatterns(): string[] {
|
|
401
|
+
return Array.from(patternRegistry.keys());
|
|
402
|
+
}
|
|
403
|
+
}
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ReactNodeTransformer - Fallback transformer for standard React content
|
|
3
|
+
*
|
|
4
|
+
* Provides serialization/deserialization for unregistered React components,
|
|
5
|
+
* HTML elements, and other React content that doesn't implement the
|
|
6
|
+
* Serializable interface.
|
|
7
|
+
*
|
|
8
|
+
* Copyright (c) 2025 QwickApps.com. All rights reserved.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { ReactNode, ReactElement, isValidElement, createElement } from 'react';
|
|
12
|
+
import SafeSpan from '../../components/SafeSpan';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Transformer for standard React content and HTML elements
|
|
16
|
+
* Used as fallback when components are not registered in ComponentTransformer
|
|
17
|
+
*/
|
|
18
|
+
export class ReactNodeTransformer {
|
|
19
|
+
/**
|
|
20
|
+
* Serialize a React node to JSON-compatible structure
|
|
21
|
+
* @param node - React node to serialize
|
|
22
|
+
* @returns Serializable data structure
|
|
23
|
+
*/
|
|
24
|
+
static serialize(node: ReactNode): any {
|
|
25
|
+
if (node === null || node === undefined) {
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Handle arrays of nodes
|
|
30
|
+
if (Array.isArray(node)) {
|
|
31
|
+
return {
|
|
32
|
+
type: 'array',
|
|
33
|
+
children: node.map(child => ReactNodeTransformer.serialize(child))
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Handle primitive values
|
|
38
|
+
if (typeof node === 'string' || typeof node === 'number' || typeof node === 'boolean') {
|
|
39
|
+
return {
|
|
40
|
+
type: 'primitive',
|
|
41
|
+
value: node
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Handle React elements
|
|
46
|
+
if (isValidElement(node)) {
|
|
47
|
+
const element = node as ReactElement;
|
|
48
|
+
|
|
49
|
+
return {
|
|
50
|
+
type: 'react-element',
|
|
51
|
+
elementType: typeof element.type === 'string'
|
|
52
|
+
? element.type
|
|
53
|
+
: (element.type as any).name || 'Anonymous',
|
|
54
|
+
props: ReactNodeTransformer.serializeProps(element.props),
|
|
55
|
+
key: element.key
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Handle plain objects
|
|
60
|
+
if (typeof node === 'object' && node !== null) {
|
|
61
|
+
try {
|
|
62
|
+
const serialized: any = { type: 'object', data: {} };
|
|
63
|
+
for (const [key, value] of Object.entries(node)) {
|
|
64
|
+
serialized.data[key] = ReactNodeTransformer.serialize(value);
|
|
65
|
+
}
|
|
66
|
+
return serialized;
|
|
67
|
+
} catch {
|
|
68
|
+
return {
|
|
69
|
+
type: 'string',
|
|
70
|
+
value: String(node)
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Fallback for other types
|
|
76
|
+
return {
|
|
77
|
+
type: 'string',
|
|
78
|
+
value: String(node)
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Deserialize data back to React node
|
|
84
|
+
* @param data - Data to deserialize
|
|
85
|
+
* @returns React node
|
|
86
|
+
*/
|
|
87
|
+
static deserialize(data: any): ReactNode {
|
|
88
|
+
if (data === null || data === undefined) {
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Handle serialized data with type information
|
|
93
|
+
if (typeof data === 'object' && data.type) {
|
|
94
|
+
switch (data.type) {
|
|
95
|
+
case 'primitive':
|
|
96
|
+
return data.value;
|
|
97
|
+
|
|
98
|
+
case 'string':
|
|
99
|
+
return data.value;
|
|
100
|
+
|
|
101
|
+
case 'array':
|
|
102
|
+
return data.children?.map((child: any) => ReactNodeTransformer.deserialize(child)) || [];
|
|
103
|
+
|
|
104
|
+
case 'react-element':
|
|
105
|
+
return ReactNodeTransformer.deserializeReactElement(data);
|
|
106
|
+
|
|
107
|
+
case 'object':
|
|
108
|
+
const result: any = {};
|
|
109
|
+
if (data.data && typeof data.data === 'object') {
|
|
110
|
+
for (const [key, value] of Object.entries(data.data)) {
|
|
111
|
+
result[key] = ReactNodeTransformer.deserialize(value);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return result;
|
|
115
|
+
|
|
116
|
+
default:
|
|
117
|
+
return String(data.value || data);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Handle direct primitive values (backward compatibility)
|
|
122
|
+
if (typeof data === 'string' || typeof data === 'number' || typeof data === 'boolean') {
|
|
123
|
+
return data;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Fallback
|
|
127
|
+
return String(data);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Serialize props object, handling nested React nodes
|
|
132
|
+
* @param props - Props object to serialize
|
|
133
|
+
* @returns Serialized props
|
|
134
|
+
*/
|
|
135
|
+
private static serializeProps(props: any): any {
|
|
136
|
+
if (!props || typeof props !== 'object') {
|
|
137
|
+
return props;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const serialized: any = {};
|
|
141
|
+
for (const [key, value] of Object.entries(props)) {
|
|
142
|
+
if (key === 'children') {
|
|
143
|
+
// Special handling for children prop
|
|
144
|
+
serialized[key] = ReactNodeTransformer.serialize(value as ReactNode);
|
|
145
|
+
} else if (typeof value === 'function') {
|
|
146
|
+
// Skip functions in serialization
|
|
147
|
+
serialized[key] = null;
|
|
148
|
+
} else {
|
|
149
|
+
serialized[key] = value;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
return serialized;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Deserialize React element data back to React element
|
|
158
|
+
* @param data - Serialized React element data
|
|
159
|
+
* @returns React element or fallback content
|
|
160
|
+
*/
|
|
161
|
+
private static deserializeReactElement(data: any): ReactNode {
|
|
162
|
+
const { elementType, props, key } = data;
|
|
163
|
+
|
|
164
|
+
try {
|
|
165
|
+
// Handle HTML elements
|
|
166
|
+
if (typeof elementType === 'string') {
|
|
167
|
+
const deserializedProps = ReactNodeTransformer.deserializeProps(props);
|
|
168
|
+
return createElement(elementType, { key, ...deserializedProps });
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// For unknown component types, check if we have HTML content
|
|
172
|
+
if (props && typeof props.children === 'string' && props.children.includes('<')) {
|
|
173
|
+
// Use SafeSpan component to render HTML content safely
|
|
174
|
+
return createElement(SafeSpan, { key, html: props.children });
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Fallback to div with text content
|
|
178
|
+
const textContent = ReactNodeTransformer.extractTextContent(props);
|
|
179
|
+
return createElement('div', { key }, textContent || `Unknown component: ${elementType}`);
|
|
180
|
+
|
|
181
|
+
} catch (error) {
|
|
182
|
+
console.warn('Error deserializing React element:', error);
|
|
183
|
+
return createElement('div', { key }, `Error rendering component: ${elementType}`);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Deserialize props object, handling nested React nodes
|
|
189
|
+
* @param props - Serialized props object
|
|
190
|
+
* @returns Deserialized props
|
|
191
|
+
*/
|
|
192
|
+
private static deserializeProps(props: any): any {
|
|
193
|
+
if (!props || typeof props !== 'object') {
|
|
194
|
+
return props;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
const deserialized: any = {};
|
|
198
|
+
for (const [key, value] of Object.entries(props)) {
|
|
199
|
+
if (key === 'children') {
|
|
200
|
+
// Special handling for children prop
|
|
201
|
+
deserialized[key] = ReactNodeTransformer.deserialize(value);
|
|
202
|
+
} else {
|
|
203
|
+
deserialized[key] = value;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
return deserialized;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Extract text content from props for fallback rendering
|
|
212
|
+
* @param props - Props object
|
|
213
|
+
* @returns Text content or null
|
|
214
|
+
*/
|
|
215
|
+
private static extractTextContent(props: any): string | null {
|
|
216
|
+
if (!props) return null;
|
|
217
|
+
|
|
218
|
+
if (typeof props.children === 'string') {
|
|
219
|
+
return props.children;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
if (props.title && typeof props.title === 'string') {
|
|
223
|
+
return props.title;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
if (props.label && typeof props.label === 'string') {
|
|
227
|
+
return props.label;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
if (props.text && typeof props.text === 'string') {
|
|
231
|
+
return props.text;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
return null;
|
|
235
|
+
}
|
|
236
|
+
}
|