@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.
Files changed (325) hide show
  1. package/README.md +1688 -2
  2. package/dist/__tests__/schemas/transformers/MockSerializableComponent.d.ts +66 -0
  3. package/dist/__tests__/schemas/transformers/MockSerializableComponent.d.ts.map +1 -0
  4. package/dist/components/ErrorBoundary.d.ts +7 -0
  5. package/dist/components/ErrorBoundary.d.ts.map +1 -1
  6. package/dist/components/Html.d.ts +28 -18
  7. package/dist/components/Html.d.ts.map +1 -1
  8. package/dist/components/Logo.d.ts +12 -35
  9. package/dist/components/Logo.d.ts.map +1 -1
  10. package/dist/components/Markdown.d.ts +18 -13
  11. package/dist/components/Markdown.d.ts.map +1 -1
  12. package/dist/components/QwickApp.d.ts +16 -3
  13. package/dist/components/QwickApp.d.ts.map +1 -1
  14. package/dist/components/QwickIcon.d.ts +23 -0
  15. package/dist/components/QwickIcon.d.ts.map +1 -0
  16. package/dist/components/SafeSpan.d.ts +12 -5
  17. package/dist/components/SafeSpan.d.ts.map +1 -1
  18. package/dist/components/Scaffold.d.ts.map +1 -1
  19. package/dist/components/base/ModelView.d.ts +101 -0
  20. package/dist/components/base/ModelView.d.ts.map +1 -0
  21. package/dist/components/base/index.d.ts +11 -0
  22. package/dist/components/base/index.d.ts.map +1 -0
  23. package/dist/components/blocks/Article.d.ts +12 -2
  24. package/dist/components/blocks/Article.d.ts.map +1 -1
  25. package/dist/components/blocks/Code.d.ts +13 -2
  26. package/dist/components/blocks/Code.d.ts.map +1 -1
  27. package/dist/components/blocks/Content.d.ts.map +1 -1
  28. package/dist/components/blocks/CoverImageHeader.d.ts.map +1 -1
  29. package/dist/components/blocks/FeatureCard.d.ts.map +1 -1
  30. package/dist/components/blocks/FeatureGrid.d.ts.map +1 -1
  31. package/dist/components/blocks/Footer.d.ts.map +1 -1
  32. package/dist/components/blocks/HeroBlock.d.ts +27 -13
  33. package/dist/components/blocks/HeroBlock.d.ts.map +1 -1
  34. package/dist/components/blocks/Image.d.ts +41 -0
  35. package/dist/components/blocks/Image.d.ts.map +1 -0
  36. package/dist/components/blocks/PageBannerHeader.d.ts.map +1 -1
  37. package/dist/components/blocks/ProductCard.d.ts.map +1 -1
  38. package/dist/components/blocks/Section.d.ts +16 -2
  39. package/dist/components/blocks/Section.d.ts.map +1 -1
  40. package/dist/components/blocks/Text.d.ts +41 -0
  41. package/dist/components/blocks/Text.d.ts.map +1 -0
  42. package/dist/components/blocks/index.d.ts +4 -0
  43. package/dist/components/blocks/index.d.ts.map +1 -1
  44. package/dist/components/buttons/Button.d.ts +23 -7
  45. package/dist/components/buttons/Button.d.ts.map +1 -1
  46. package/dist/components/forms/FormBlock.d.ts +19 -13
  47. package/dist/components/forms/FormBlock.d.ts.map +1 -1
  48. package/dist/components/index.d.ts +4 -0
  49. package/dist/components/index.d.ts.map +1 -1
  50. package/dist/components/input/ChoiceInputField.d.ts +17 -11
  51. package/dist/components/input/ChoiceInputField.d.ts.map +1 -1
  52. package/dist/components/input/HtmlInputField.d.ts +17 -11
  53. package/dist/components/input/HtmlInputField.d.ts.map +1 -1
  54. package/dist/components/input/SelectInputField.d.ts +16 -10
  55. package/dist/components/input/SelectInputField.d.ts.map +1 -1
  56. package/dist/components/input/SwitchInputField.d.ts +16 -10
  57. package/dist/components/input/SwitchInputField.d.ts.map +1 -1
  58. package/dist/components/input/TextField.d.ts.map +1 -1
  59. package/dist/components/input/TextInputField.d.ts +16 -11
  60. package/dist/components/input/TextInputField.d.ts.map +1 -1
  61. package/dist/components/layout/GridCell.d.ts +23 -6
  62. package/dist/components/layout/GridCell.d.ts.map +1 -1
  63. package/dist/components/layout/GridLayout.d.ts +24 -23
  64. package/dist/components/layout/GridLayout.d.ts.map +1 -1
  65. package/dist/components/pages/FormPage.d.ts.map +1 -1
  66. package/dist/components/pages/Page.d.ts +49 -87
  67. package/dist/components/pages/Page.d.ts.map +1 -1
  68. package/dist/components/pages/index.d.ts +2 -2
  69. package/dist/components/pages/index.d.ts.map +1 -1
  70. package/dist/config/AppConfig.d.ts +49 -0
  71. package/dist/config/AppConfig.d.ts.map +1 -0
  72. package/dist/config/AppConfigBuilder.d.ts +75 -0
  73. package/dist/config/AppConfigBuilder.d.ts.map +1 -0
  74. package/dist/config/index.d.ts +13 -0
  75. package/dist/config/index.d.ts.map +1 -0
  76. package/dist/config/types.d.ts +130 -0
  77. package/dist/config/types.d.ts.map +1 -0
  78. package/dist/config.d.ts +15 -0
  79. package/dist/config.d.ts.map +1 -0
  80. package/dist/config.esm.js +451 -0
  81. package/dist/config.js +455 -0
  82. package/dist/contexts/PrintModeContext.d.ts +27 -0
  83. package/dist/contexts/PrintModeContext.d.ts.map +1 -0
  84. package/dist/contexts/QwickAppContext.d.ts +2 -2
  85. package/dist/contexts/QwickAppContext.d.ts.map +1 -1
  86. package/dist/contexts/ThemeContext.d.ts.map +1 -1
  87. package/dist/contexts/index.d.ts +2 -0
  88. package/dist/contexts/index.d.ts.map +1 -1
  89. package/dist/hooks/index.d.ts +2 -0
  90. package/dist/hooks/index.d.ts.map +1 -1
  91. package/dist/hooks/usePrintMode.d.ts +39 -0
  92. package/dist/hooks/usePrintMode.d.ts.map +1 -0
  93. package/dist/index.css +1 -1
  94. package/dist/index.d.ts +1 -0
  95. package/dist/index.d.ts.map +1 -1
  96. package/dist/index.esm.css +1 -1
  97. package/dist/index.esm.js +20722 -16021
  98. package/dist/index.js +20725 -16010
  99. package/dist/schemas/CodeSchema.d.ts +2 -1
  100. package/dist/schemas/CodeSchema.d.ts.map +1 -1
  101. package/dist/schemas/CollapsibleLayoutSchema.d.ts +2 -1
  102. package/dist/schemas/CollapsibleLayoutSchema.d.ts.map +1 -1
  103. package/dist/schemas/ContentSchema.d.ts +2 -1
  104. package/dist/schemas/ContentSchema.d.ts.map +1 -1
  105. package/dist/schemas/GridCellSchema.d.ts +25 -0
  106. package/dist/schemas/GridCellSchema.d.ts.map +1 -0
  107. package/dist/schemas/GridLayoutSchema.d.ts +23 -0
  108. package/dist/schemas/GridLayoutSchema.d.ts.map +1 -0
  109. package/dist/schemas/HtmlSchema.d.ts +14 -0
  110. package/dist/schemas/HtmlSchema.d.ts.map +1 -0
  111. package/dist/schemas/ImageSchema.d.ts +32 -0
  112. package/dist/schemas/ImageSchema.d.ts.map +1 -0
  113. package/dist/schemas/LogoSchema.d.ts +35 -0
  114. package/dist/schemas/LogoSchema.d.ts.map +1 -0
  115. package/dist/schemas/MarkdownSchema.d.ts +14 -0
  116. package/dist/schemas/MarkdownSchema.d.ts.map +1 -0
  117. package/dist/schemas/PageTemplateSchema.d.ts +31 -0
  118. package/dist/schemas/PageTemplateSchema.d.ts.map +1 -0
  119. package/dist/schemas/PrintConfigSchema.d.ts +31 -0
  120. package/dist/schemas/PrintConfigSchema.d.ts.map +1 -0
  121. package/dist/schemas/SectionSchema.d.ts +2 -1
  122. package/dist/schemas/SectionSchema.d.ts.map +1 -1
  123. package/dist/schemas/TextSchema.d.ts +37 -0
  124. package/dist/schemas/TextSchema.d.ts.map +1 -0
  125. package/dist/schemas/ViewModelSchema.d.ts +23 -0
  126. package/dist/schemas/ViewModelSchema.d.ts.map +1 -0
  127. package/dist/schemas/index.d.ts +15 -1
  128. package/dist/schemas/index.d.ts.map +1 -1
  129. package/dist/schemas/transformers/ComponentTransformer.d.ts +116 -0
  130. package/dist/schemas/transformers/ComponentTransformer.d.ts.map +1 -0
  131. package/dist/schemas/transformers/ReactNodeTransformer.d.ts +53 -0
  132. package/dist/schemas/transformers/ReactNodeTransformer.d.ts.map +1 -0
  133. package/dist/schemas/transformers/__tests__/MockSerializableComponent.d.ts +66 -0
  134. package/dist/schemas/transformers/__tests__/MockSerializableComponent.d.ts.map +1 -0
  135. package/dist/schemas/transformers/registry.d.ts +15 -0
  136. package/dist/schemas/transformers/registry.d.ts.map +1 -0
  137. package/dist/schemas/types/Serializable.d.ts +46 -0
  138. package/dist/schemas/types/Serializable.d.ts.map +1 -0
  139. package/dist/utils/htmlTransform.d.ts.map +1 -1
  140. package/dist/utils/reactUtils.d.ts +12 -3
  141. package/dist/utils/reactUtils.d.ts.map +1 -1
  142. package/package.json +17 -3
  143. package/src/{components/__tests__ → __tests__/components}/AccessibilityProvider.test.tsx +1 -1
  144. package/src/{components/__tests__ → __tests__/components}/Article.test.tsx +1 -1
  145. package/src/{components/__tests__ → __tests__/components}/Breadcrumbs.test.tsx +1 -1
  146. package/src/{components/__tests__ → __tests__/components}/Button.test.tsx +1 -1
  147. package/src/{components/__tests__ → __tests__/components}/CardListGrid.test.tsx +2 -2
  148. package/src/{components/__tests__ → __tests__/components}/ChoiceInputField.test.tsx +1 -1
  149. package/src/{components/__tests__ → __tests__/components}/Code.test.tsx +1 -1
  150. package/src/{components/__tests__ → __tests__/components}/Content.integration.test.tsx +1 -1
  151. package/src/{components/__tests__ → __tests__/components}/Content.test.tsx +1 -1
  152. package/src/{components/__tests__ → __tests__/components}/CoverImageHeader.test.tsx +2 -2
  153. package/src/{components/__tests__ → __tests__/components}/ErrorBoundary.test.tsx +1 -1
  154. package/src/{components/__tests__ → __tests__/components}/FeatureCard.integration.test.tsx +2 -2
  155. package/src/{components/__tests__ → __tests__/components}/FeatureGrid.integration.test.tsx +2 -2
  156. package/src/{components/__tests__ → __tests__/components}/FeatureGrid.test.tsx +2 -2
  157. package/src/{components/__tests__ → __tests__/components}/Footer.test.tsx +4 -4
  158. package/src/{components/__tests__ → __tests__/components}/FormBlock.test.tsx +1 -1
  159. package/src/{components/__tests__ → __tests__/components}/HeroBlock.integration.test.tsx +2 -2
  160. package/src/{components/__tests__ → __tests__/components}/HeroBlock.test.tsx +233 -7
  161. package/src/{components/__tests__ → __tests__/components}/Html.test.tsx +11 -2
  162. package/src/{components/__tests__ → __tests__/components}/HtmlInputField.test.tsx +3 -3
  163. package/src/__tests__/components/Logo.test.js +3 -3
  164. package/src/{components/__tests__ → __tests__/components}/Markdown.test.tsx +1 -1
  165. package/src/{components/__tests__ → __tests__/components}/PageBannerHeader.test.tsx +3 -3
  166. package/src/{components/__tests__ → __tests__/components}/PaletteSwitcher.test.tsx +3 -3
  167. package/src/{components/__tests__ → __tests__/components}/ProductCard.test.tsx +4 -4
  168. package/src/{components/__tests__ → __tests__/components}/SafeSpan.integration.test.tsx +2 -2
  169. package/src/{components/__tests__ → __tests__/components}/SafeSpan.simple.test.tsx +1 -1
  170. package/src/{components/__tests__ → __tests__/components}/SafeSpan.test.tsx +1 -1
  171. package/src/{components/__tests__ → __tests__/components}/Section.integration.test.tsx +1 -1
  172. package/src/{components/__tests__ → __tests__/components}/Section.test.tsx +1 -1
  173. package/src/{components/__tests__ → __tests__/components}/SelectInputField.test.tsx +1 -1
  174. package/src/{components/__tests__ → __tests__/components}/TextInputField.test.tsx +3 -3
  175. package/src/{components/__tests__ → __tests__/components}/ThemeSwitcher.test.tsx +3 -3
  176. package/src/__tests__/components/base/ModelView.test.tsx +220 -0
  177. package/src/__tests__/components/blocks/Code.performance.test.tsx +625 -0
  178. package/src/__tests__/components/blocks/Code.serialization.test.tsx +507 -0
  179. package/src/__tests__/components/blocks/HeroBlock.serialization.test.tsx +414 -0
  180. package/src/__tests__/components/blocks/Image.serialization.test.tsx +257 -0
  181. package/src/__tests__/components/blocks/Section.serialization.test.tsx +553 -0
  182. package/src/__tests__/components/blocks/Text.performance.test.tsx +442 -0
  183. package/src/__tests__/components/blocks/Text.serialization.test.tsx +491 -0
  184. package/src/__tests__/components/buttons/Button.serialization.test.tsx +443 -0
  185. package/src/__tests__/components/input/FormComponents.serialization.test.tsx +482 -0
  186. package/src/__tests__/components/input/SelectInputField.serialization.test.tsx +439 -0
  187. package/src/__tests__/components/input/TextInputField.serialization.test.tsx +359 -0
  188. package/src/{components/layout/CollapsibleLayout/__tests__ → __tests__/components/layout}/CollapsibleLayout.test.tsx +4 -4
  189. package/src/__tests__/components/layout/GridCell.serialization.test.tsx +403 -0
  190. package/src/__tests__/components/layout/GridLayout.serialization.test.tsx +311 -0
  191. package/src/__tests__/hooks/usePrintMode.test.ts +89 -0
  192. package/src/__tests__/schemas/PageTemplateSchema.test.ts +161 -0
  193. package/src/__tests__/schemas/PrintConfigSchema.test.ts +127 -0
  194. package/src/__tests__/schemas/ViewModelSchema.test.ts +80 -0
  195. package/src/__tests__/schemas/transformers/ComponentSerializationPatterns.test.tsx +602 -0
  196. package/src/__tests__/schemas/transformers/ComponentTransformer.htmlPatterns.test.ts +301 -0
  197. package/src/__tests__/schemas/transformers/ComponentTransformer.test.ts +521 -0
  198. package/src/__tests__/schemas/transformers/CrossBrowserCompatibility.test.ts +586 -0
  199. package/src/__tests__/schemas/transformers/MockSerializableComponent.ts +103 -0
  200. package/src/__tests__/schemas/transformers/RealWorldScenarios.test.tsx +1165 -0
  201. package/src/__tests__/schemas/transformers/SerializationErrorHandling.test.ts +602 -0
  202. package/src/__tests__/schemas/transformers/SerializationIntegration.test.tsx +691 -0
  203. package/src/__tests__/schemas/transformers/SerializationPerformance.test.ts +460 -0
  204. package/src/__tests__/schemas/transformers/TestAutomation.test.ts +597 -0
  205. package/src/{utils/__tests__ → __tests__/utils}/nested-dom-fix.test.tsx +1 -1
  206. package/src/components/ErrorBoundary.tsx +8 -8
  207. package/src/components/Html.tsx +147 -44
  208. package/src/components/Logo.tsx +198 -100
  209. package/src/components/Markdown.tsx +125 -16
  210. package/src/components/QwickApp.tsx +64 -31
  211. package/src/components/QwickIcon.tsx +59 -0
  212. package/src/components/SafeSpan.tsx +65 -10
  213. package/src/components/Scaffold.tsx +2 -8
  214. package/src/components/base/ModelView.tsx +199 -0
  215. package/src/components/base/index.ts +11 -0
  216. package/src/components/blocks/Article.tsx +57 -18
  217. package/src/components/blocks/Code.md +529 -0
  218. package/src/components/blocks/Code.tsx +102 -15
  219. package/src/components/blocks/Content.tsx +25 -77
  220. package/src/components/blocks/CoverImageHeader.tsx +9 -4
  221. package/src/components/blocks/FeatureCard.tsx +1 -2
  222. package/src/components/blocks/FeatureGrid.tsx +19 -1
  223. package/src/components/blocks/Footer.tsx +13 -1
  224. package/src/components/blocks/HeroBlock.tsx +87 -20
  225. package/src/components/blocks/Image.tsx +395 -0
  226. package/src/components/blocks/PageBannerHeader.tsx +14 -12
  227. package/src/components/blocks/ProductCard.tsx +51 -52
  228. package/src/components/blocks/Section.tsx +113 -8
  229. package/src/components/blocks/Text.tsx +285 -0
  230. package/src/components/blocks/index.ts +4 -0
  231. package/src/components/buttons/Button.tsx +184 -15
  232. package/src/components/forms/FormBlock.tsx +70 -17
  233. package/src/components/index.ts +5 -0
  234. package/src/components/input/ChoiceInputField.tsx +48 -18
  235. package/src/components/input/HtmlInputField.tsx +48 -18
  236. package/src/components/input/SelectInputField.tsx +48 -16
  237. package/src/components/input/SwitchInputField.tsx +48 -17
  238. package/src/components/input/TextField.tsx +41 -1
  239. package/src/components/input/TextInputField.tsx +52 -18
  240. package/src/components/layout/GridCell.tsx +118 -9
  241. package/src/components/layout/GridLayout.tsx +125 -24
  242. package/src/components/pages/FormPage.tsx +0 -1
  243. package/src/components/pages/Page.css +304 -332
  244. package/src/components/pages/Page.tsx +307 -255
  245. package/src/components/pages/index.ts +2 -2
  246. package/src/config/AppConfig.ts +133 -0
  247. package/src/config/AppConfigBuilder.ts +421 -0
  248. package/src/config/__tests__/AppConfig.test.ts +385 -0
  249. package/src/config/__tests__/AppConfigBuilder.test.ts +432 -0
  250. package/src/config/index.ts +24 -0
  251. package/src/config/types.ts +170 -0
  252. package/src/config.ts +25 -0
  253. package/src/contexts/PrintModeContext.tsx +332 -0
  254. package/src/contexts/QwickAppContext.tsx +2 -2
  255. package/src/contexts/ThemeContext.tsx +1 -2
  256. package/src/contexts/index.ts +2 -0
  257. package/src/hooks/index.ts +5 -1
  258. package/src/hooks/usePrintMode.ts +73 -0
  259. package/src/index.ts +3 -0
  260. package/src/schemas/CodeSchema.ts +3 -3
  261. package/src/schemas/CollapsibleLayoutSchema.ts +2 -1
  262. package/src/schemas/ContentSchema.ts +2 -1
  263. package/src/schemas/GridCellSchema.ts +164 -0
  264. package/src/schemas/GridLayoutSchema.ts +133 -0
  265. package/src/schemas/HtmlSchema.ts +47 -0
  266. package/src/schemas/ImageSchema.ts +235 -0
  267. package/src/schemas/LogoSchema.ts +241 -0
  268. package/src/schemas/MarkdownSchema.ts +47 -0
  269. package/src/schemas/PageTemplateSchema.ts +186 -0
  270. package/src/schemas/PrintConfigSchema.ts +207 -0
  271. package/src/schemas/README.md +661 -0
  272. package/src/schemas/SectionSchema.ts +2 -1
  273. package/src/schemas/TextSchema.ts +329 -0
  274. package/src/schemas/ViewModelSchema.ts +115 -0
  275. package/src/schemas/index.ts +21 -2
  276. package/src/schemas/transformers/ComponentTransformer.ts +403 -0
  277. package/src/schemas/transformers/ReactNodeTransformer.ts +236 -0
  278. package/src/schemas/transformers/registry.ts +72 -0
  279. package/src/schemas/types/Serializable.ts +51 -0
  280. package/src/stories/AccessibilityProvider.stories.tsx +253 -253
  281. package/src/stories/Article.stories.tsx +433 -433
  282. package/src/stories/Button.stories.tsx +1 -1
  283. package/src/stories/CardListGrid.stories.tsx +451 -451
  284. package/src/stories/ChoiceInputField.stories.tsx +503 -503
  285. package/src/stories/Code.stories.tsx +1 -1
  286. package/src/stories/CollapsibleLayout.stories.tsx +1414 -1414
  287. package/src/stories/Content.stories.tsx +393 -393
  288. package/src/stories/CoverImageHeader.stories.tsx +701 -701
  289. package/src/stories/DataBinding.advanced.stories.tsx +432 -432
  290. package/src/stories/DataProvider.stories.tsx +1192 -1192
  291. package/src/stories/FeatureCard.stories.tsx +557 -557
  292. package/src/stories/FeatureGrid.stories.tsx +594 -594
  293. package/src/stories/Footer.stories.tsx +640 -640
  294. package/src/stories/FormBlock.stories.tsx +760 -760
  295. package/src/stories/FormComponents.stories.tsx +349 -541
  296. package/src/stories/GridCell.stories.tsx +417 -0
  297. package/src/stories/GridLayout.stories.tsx +353 -0
  298. package/src/stories/HeroBlock.stories.tsx +862 -373
  299. package/src/stories/HtmlInputField.stories.tsx +474 -474
  300. package/src/stories/Image.stories.tsx +819 -0
  301. package/src/stories/Introduction.stories.tsx +667 -667
  302. package/src/stories/LayoutBlocks.stories.tsx +324 -324
  303. package/src/stories/Logo.stories.tsx +165 -6
  304. package/src/stories/Markdown.stories.tsx +137 -137
  305. package/src/stories/ModelView.stories.tsx +477 -0
  306. package/src/stories/Page.stories.tsx +688 -688
  307. package/src/stories/PageBannerHeader.stories.tsx +864 -864
  308. package/src/stories/PaletteSwitcher.stories.tsx +119 -119
  309. package/src/stories/ProductCard.stories.tsx +424 -424
  310. package/src/stories/QwickApp.stories.tsx +368 -368
  311. package/src/stories/ResponsiveMenu.stories.tsx +249 -249
  312. package/src/stories/SafeSpan.stories.tsx +531 -531
  313. package/src/stories/Section.stories.tsx +90 -2
  314. package/src/stories/SelectInputField.stories.tsx +524 -524
  315. package/src/stories/Text.stories.tsx +560 -0
  316. package/src/stories/TextInputField.stories.tsx +443 -443
  317. package/src/stories/ThemeSwitcher.stories.tsx +123 -123
  318. package/src/utils/htmlTransform.tsx +74 -53
  319. package/src/utils/reactUtils.tsx +57 -6
  320. package/dist/index.bundled.css +0 -12
  321. /package/src/{hooks/__tests__ → __tests__/hooks}/useDataBinding.test.tsx.disabled +0 -0
  322. /package/src/{schemas/__tests__ → __tests__/schemas}/builders.test.ts +0 -0
  323. /package/src/{utils/__tests__ → __tests__/utils}/createDataDrivenComponent.test.tsx.disabled +0 -0
  324. /package/src/{utils/__tests__ → __tests__/utils}/htmlTransform.test.tsx +0 -0
  325. /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
+ }