@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,395 @@
1
+ /**
2
+ * Image - Comprehensive image display component with serialization support
3
+ *
4
+ * Features:
5
+ * - Responsive image handling with srcSet and sizes
6
+ * - Multiple fit modes and positioning options
7
+ * - Loading states and error handling
8
+ * - Accessibility support with proper alt text
9
+ * - Lazy loading support
10
+ * - Fallback image handling
11
+ * - Full serialization support via ModelView
12
+ *
13
+ * Copyright (c) 2025 QwickApps.com. All rights reserved.
14
+ */
15
+
16
+ import React, { ReactElement, useState, useCallback } from 'react';
17
+ import { Box, Skeleton, Typography, useTheme } from '@mui/material';
18
+ import { BrokenImage as BrokenImageIcon } from '@mui/icons-material';
19
+ import type { WithDataBinding, ModelProps } from '@qwickapps/schema';
20
+ import { QWICKAPP_COMPONENT, useBaseProps, useDataBinding } from '../../hooks';
21
+ import ImageModel, { ImageFit, ImageLoading, ImagePosition } from '../../schemas/ImageSchema';
22
+ import { ModelView } from '../base/ModelView';
23
+
24
+ type ImageViewProps = ModelProps<ImageModel> & {
25
+ /** Click handler for the image */
26
+ onClick?: (event: React.MouseEvent<HTMLElement>) => void;
27
+ /** Additional inline styles */
28
+ style?: React.CSSProperties;
29
+ /** Additional CSS class names */
30
+ className?: string;
31
+ };
32
+
33
+ export interface ImageProps extends ImageViewProps, WithDataBinding {}
34
+
35
+ // View component - handles the actual rendering
36
+ function ImageView({
37
+ src,
38
+ alt = '',
39
+ width,
40
+ height,
41
+ objectFit = 'cover',
42
+ objectPosition = 'center',
43
+ loading = 'lazy',
44
+ title,
45
+ draggable = false,
46
+ borderRadius,
47
+ showLoading = false,
48
+ showError = false,
49
+ fallbackSrc,
50
+ sizes,
51
+ srcSet,
52
+ loadingPlaceholder,
53
+ errorPlaceholder,
54
+ onClick,
55
+ style,
56
+ className,
57
+ ...restProps
58
+ }: ImageViewProps) {
59
+ const { styleProps, htmlProps, restProps: otherProps } = useBaseProps(restProps);
60
+ const theme = useTheme();
61
+
62
+ // Mark as QwickApp component
63
+ (ImageView as any)[QWICKAPP_COMPONENT] = true;
64
+
65
+ const [imageState, setImageState] = useState<'loading' | 'loaded' | 'error'>('loading');
66
+ const [currentSrc, setCurrentSrc] = useState(src);
67
+
68
+ const handleLoad = useCallback(() => {
69
+ setImageState('loaded');
70
+ }, []);
71
+
72
+ const handleError = useCallback(() => {
73
+ // Try fallback source if available and not already using it
74
+ if (fallbackSrc && currentSrc !== fallbackSrc) {
75
+ setCurrentSrc(fallbackSrc);
76
+ return;
77
+ }
78
+ setImageState('error');
79
+ }, [fallbackSrc, currentSrc]);
80
+
81
+ // Early return if no src provided
82
+ if (!src) {
83
+ if (showError) {
84
+ return (
85
+ <Box
86
+ {...htmlProps}
87
+ {...styleProps}
88
+ {...otherProps}
89
+ className={`image-error ${className || ''}`.trim()}
90
+ style={{
91
+ display: 'flex',
92
+ alignItems: 'center',
93
+ justifyContent: 'center',
94
+ backgroundColor: theme.palette.grey[100],
95
+ color: theme.palette.text.secondary,
96
+ width: width || 200,
97
+ height: height || 150,
98
+ borderRadius,
99
+ ...style
100
+ }}
101
+ >
102
+ {errorPlaceholder || (
103
+ <Box sx={{ textAlign: 'center' }}>
104
+ <BrokenImageIcon sx={{ fontSize: 48, mb: 1, opacity: 0.5 }} />
105
+ <Typography variant="body2" color="text.secondary">
106
+ No image source
107
+ </Typography>
108
+ </Box>
109
+ )}
110
+ </Box>
111
+ );
112
+ }
113
+ return null;
114
+ }
115
+
116
+ // Loading state
117
+ if (imageState === 'loading' && showLoading) {
118
+ return (
119
+ <Box
120
+ {...htmlProps}
121
+ {...styleProps}
122
+ {...otherProps}
123
+ className={`image-loading ${className || ''}`.trim()}
124
+ style={{
125
+ width: width || '100%',
126
+ height: height || 200,
127
+ borderRadius,
128
+ ...style
129
+ }}
130
+ >
131
+ {loadingPlaceholder || (
132
+ <Skeleton
133
+ variant="rectangular"
134
+ width="100%"
135
+ height="100%"
136
+ sx={{ borderRadius }}
137
+ />
138
+ )}
139
+ </Box>
140
+ );
141
+ }
142
+
143
+ // Error state
144
+ if (imageState === 'error' && showError) {
145
+ return (
146
+ <Box
147
+ {...htmlProps}
148
+ {...styleProps}
149
+ {...otherProps}
150
+ className={`image-error ${className || ''}`.trim()}
151
+ style={{
152
+ display: 'flex',
153
+ alignItems: 'center',
154
+ justifyContent: 'center',
155
+ backgroundColor: theme.palette.grey[100],
156
+ color: theme.palette.text.secondary,
157
+ width: width || '100%',
158
+ height: height || 200,
159
+ borderRadius,
160
+ ...style
161
+ }}
162
+ >
163
+ {errorPlaceholder || (
164
+ <Box sx={{ textAlign: 'center', p: 2 }}>
165
+ <BrokenImageIcon sx={{ fontSize: 48, mb: 1, opacity: 0.5 }} />
166
+ <Typography variant="body2" color="text.secondary">
167
+ Failed to load image
168
+ </Typography>
169
+ {fallbackSrc && (
170
+ <Typography variant="caption" color="text.secondary">
171
+ Fallback image also failed
172
+ </Typography>
173
+ )}
174
+ </Box>
175
+ )}
176
+ </Box>
177
+ );
178
+ }
179
+
180
+ const imageStyles: React.CSSProperties = {
181
+ display: 'block',
182
+ maxWidth: '100%',
183
+ height: 'auto',
184
+ objectFit,
185
+ objectPosition,
186
+ borderRadius,
187
+ cursor: onClick ? 'pointer' : 'default',
188
+ ...style
189
+ };
190
+
191
+ // Apply dimensions if provided
192
+ if (width) imageStyles.width = width;
193
+ if (height) imageStyles.height = height;
194
+
195
+ return (
196
+ <img
197
+ {...htmlProps}
198
+ {...styleProps}
199
+ {...otherProps}
200
+ src={currentSrc}
201
+ alt={alt}
202
+ width={width}
203
+ height={height}
204
+ loading={loading}
205
+ title={title}
206
+ draggable={draggable}
207
+ sizes={sizes}
208
+ srcSet={srcSet}
209
+ className={`image ${className || ''}`.trim()}
210
+ style={imageStyles}
211
+ onClick={onClick}
212
+ onLoad={handleLoad}
213
+ onError={handleError}
214
+ />
215
+ );
216
+ }
217
+
218
+ // Main component with data binding support and serialization capability
219
+ export class Image extends ModelView<ImageProps, ImageModel> {
220
+ // Component self-declaration for serialization
221
+ static readonly tagName = 'Image';
222
+ static readonly version = '1.0.0';
223
+
224
+ // Deserialization: JSON data → React element
225
+ static fromJson(jsonData: any): ReactElement {
226
+ return <Image {...jsonData} />;
227
+ }
228
+
229
+ // Component-specific serialization properties
230
+ protected getComponentSpecificProps(): any {
231
+ return {
232
+ src: this.props.src,
233
+ alt: this.props.alt,
234
+ width: this.props.width,
235
+ height: this.props.height,
236
+ objectFit: this.props.objectFit,
237
+ objectPosition: this.props.objectPosition,
238
+ loading: this.props.loading,
239
+ title: this.props.title,
240
+ draggable: this.props.draggable,
241
+ borderRadius: this.props.borderRadius,
242
+ showLoading: this.props.showLoading,
243
+ showError: this.props.showError,
244
+ fallbackSrc: this.props.fallbackSrc,
245
+ sizes: this.props.sizes,
246
+ srcSet: this.props.srcSet,
247
+ // Note: loadingPlaceholder and errorPlaceholder are ReactNodes
248
+ // and will be handled by base serialization if needed
249
+ };
250
+ }
251
+
252
+ // Image component renders traditional props view
253
+ protected renderView(): React.ReactElement {
254
+ const { dataSource, bindingOptions, ...restProps } = this.props;
255
+ return <ImageView {...restProps} />;
256
+ }
257
+
258
+ // Image component renders data-bound view
259
+ protected renderWithDataBinding(): React.ReactElement {
260
+ return <ImageWithDataBinding {...this.props} />;
261
+ }
262
+
263
+ // Register HTML patterns that Image component can handle
264
+ static registerPatternHandlers(registry: any): void {
265
+ // Register img elements
266
+ if (!registry.hasPattern('img')) {
267
+ registry.registerPattern('img', Image.transformImage);
268
+ }
269
+
270
+ // Register figure elements with img
271
+ if (!registry.hasPattern('figure img')) {
272
+ registry.registerPattern('figure img', Image.transformFigureImage);
273
+ }
274
+ }
275
+
276
+ // Transform img elements to Image component
277
+ private static transformImage(element: Element): any {
278
+ const src = element.getAttribute('src') || '';
279
+ const alt = element.getAttribute('alt') || '';
280
+ const width = element.getAttribute('width');
281
+ const height = element.getAttribute('height');
282
+ const loading = element.getAttribute('loading') as 'lazy' | 'eager' | undefined;
283
+
284
+ return {
285
+ tagName: 'Image',
286
+ props: {
287
+ src,
288
+ alt,
289
+ width: width ? parseInt(width) : undefined,
290
+ height: height ? parseInt(height) : undefined,
291
+ loading: loading || 'lazy'
292
+ }
293
+ };
294
+ }
295
+
296
+ // Transform figure > img elements to Image component with caption support
297
+ private static transformFigureImage(element: Element): any {
298
+ const figure = element.closest('figure');
299
+ const figcaption = figure?.querySelector('figcaption');
300
+ const caption = figcaption?.textContent || '';
301
+
302
+ const src = element.getAttribute('src') || '';
303
+ const alt = element.getAttribute('alt') || caption;
304
+ const width = element.getAttribute('width');
305
+ const height = element.getAttribute('height');
306
+
307
+ return {
308
+ tagName: 'Image',
309
+ props: {
310
+ src,
311
+ alt,
312
+ caption: caption || undefined,
313
+ width: width ? parseInt(width) : undefined,
314
+ height: height ? parseInt(height) : undefined,
315
+ loading: 'lazy'
316
+ }
317
+ };
318
+ }
319
+ }
320
+
321
+ // Helper component to handle data binding with hooks (since we can't use hooks in class components)
322
+ function ImageWithDataBinding(props: ImageProps) {
323
+ const { dataSource, bindingOptions, ...restProps } = props;
324
+
325
+ // Use data binding
326
+ const { dataSource: _source, loading, error, cached, ...rawImageProps } = useDataBinding<ImageModel>(
327
+ dataSource!,
328
+ restProps as Partial<ImageModel>,
329
+ ImageModel.getSchema(),
330
+ { cache: true, cacheTTL: 300000, strict: false, ...bindingOptions }
331
+ );
332
+
333
+ // Use props directly since new serialization system handles component-level transformation
334
+ const imageProps = rawImageProps;
335
+
336
+ // Show loading state
337
+ if (loading) {
338
+ return (
339
+ <Box
340
+ sx={{
341
+ display: 'flex',
342
+ alignItems: 'center',
343
+ justifyContent: 'center',
344
+ backgroundColor: 'grey.100',
345
+ width: imageProps.width || 200,
346
+ height: imageProps.height || 150,
347
+ borderRadius: imageProps.borderRadius
348
+ }}
349
+ >
350
+ <Skeleton
351
+ variant="rectangular"
352
+ width="100%"
353
+ height="100%"
354
+ sx={{ borderRadius: imageProps.borderRadius }}
355
+ />
356
+ </Box>
357
+ );
358
+ }
359
+
360
+ if (error) {
361
+ console.error('Error loading image:', error);
362
+ if (process.env.NODE_ENV !== 'production') {
363
+ return (
364
+ <Box
365
+ sx={{
366
+ display: 'flex',
367
+ flexDirection: 'column',
368
+ alignItems: 'center',
369
+ justifyContent: 'center',
370
+ backgroundColor: 'error.light',
371
+ color: 'error.contrastText',
372
+ p: 2,
373
+ borderRadius: imageProps.borderRadius,
374
+ width: imageProps.width || 200,
375
+ height: imageProps.height || 150
376
+ }}
377
+ >
378
+ <BrokenImageIcon sx={{ fontSize: 48, mb: 1 }} />
379
+ <Typography variant="body2" align="center">
380
+ Error Loading Image
381
+ </Typography>
382
+ <Typography variant="caption" align="center">
383
+ {error.message}
384
+ </Typography>
385
+ </Box>
386
+ );
387
+ }
388
+ return null;
389
+ }
390
+
391
+ console.log('Resolved props for Image:', imageProps);
392
+ return <ImageView {...imageProps} />;
393
+ }
394
+
395
+ export default Image;
@@ -397,18 +397,20 @@ function PageBannerHeader(props: PageBannerHeaderProps) {
397
397
  );
398
398
 
399
399
  // Convert HeaderActionModel[] to HeaderAction[] by adding default onClick handlers
400
- const convertedActions: HeaderAction[] | undefined = modelProps.actions?.map(action => ({
401
- id: action.id,
402
- label: action.label,
403
- icon: action.icon as React.ReactNode,
404
- disabled: action.disabled,
405
- destructive: action.destructive,
406
- priority: action.priority,
407
- onClick: () => {
408
- console.log(`Action clicked: ${action.id} - ${action.label}`);
409
- // In a real app, this would dispatch events or call configured handlers
410
- }
411
- }));
400
+ const convertedActions: HeaderAction[] | undefined = modelProps.actions
401
+ ?.filter((action): action is typeof action & { id: string } => typeof action.id === 'string')
402
+ .map(action => ({
403
+ id: action.id,
404
+ label: action.label,
405
+ icon: action.icon as React.ReactNode,
406
+ disabled: action.disabled,
407
+ destructive: action.destructive,
408
+ priority: action.priority,
409
+ onClick: () => {
410
+ console.log(`Action clicked: ${action.id} - ${action.label}`);
411
+ // In a real app, this would dispatch events or call configured handlers
412
+ }
413
+ }));
412
414
 
413
415
  const pageBannerHeaderProps: PageBannerHeaderViewProps = {
414
416
  ...modelProps,
@@ -141,26 +141,29 @@ function ProductCardView({
141
141
 
142
142
  const displayActions = actions || getDefaultActions();
143
143
 
144
- const FeaturesList = () => {
145
- const featuresToShow = variant === 'compact'
144
+ // INLINE WRAPPERS REFACTORED: Instead of inline component declarations (which cause remounts),
145
+ // compute JSX fragments via plain variables/functions and inject directly.
146
+
147
+ const featuresListElement = (() => {
148
+ const featuresToShow = variant === 'compact'
146
149
  ? (product.features || []).slice(0, maxFeaturesCompact)
147
150
  : (product.features || []);
148
151
 
149
152
  return (
150
153
  <Box sx={{ mb: variant === 'detailed' ? 2.5 : 2 }}>
151
- <Typography
152
- variant="h6"
154
+ <Typography
155
+ variant="h6"
153
156
  component="h4"
154
- sx={{
155
- mb: 1.5,
157
+ sx={{
158
+ mb: 1.5,
156
159
  fontSize: '1.1rem',
157
160
  fontWeight: 600
158
161
  }}
159
162
  >
160
163
  Key Features:
161
164
  </Typography>
162
- <Box component="ul" sx={{
163
- m: 0,
165
+ <Box component="ul" sx={{
166
+ m: 0,
164
167
  p: 0,
165
168
  listStyle: 'none'
166
169
  }}>
@@ -185,9 +188,9 @@ function ProductCardView({
185
188
  flexShrink: 0
186
189
  }}
187
190
  />
188
- <Typography
189
- variant="body2"
190
- sx={{
191
+ <Typography
192
+ variant="body2"
193
+ sx={{
191
194
  fontSize: '0.95rem',
192
195
  lineHeight: 1.4,
193
196
  opacity: 0.8
@@ -198,10 +201,10 @@ function ProductCardView({
198
201
  </Box>
199
202
  ))}
200
203
  {variant === 'compact' && (product.features || []).length > maxFeaturesCompact && (
201
- <Typography
202
- variant="body2"
204
+ <Typography
205
+ variant="body2"
203
206
  color="primary"
204
- sx={{
207
+ sx={{
205
208
  fontSize: '0.9rem',
206
209
  pl: 2.5,
207
210
  fontWeight: 500
@@ -213,41 +216,37 @@ function ProductCardView({
213
216
  </Box>
214
217
  </Box>
215
218
  );
216
- };
217
-
218
- const TechnologiesSection = () => {
219
- if (!showTechnologies || variant === 'compact') return null;
219
+ })();
220
220
 
221
- return (
222
- <Box sx={{ mb: 3 }}>
223
- <Typography
224
- variant="h6"
225
- component="h4"
226
- sx={{
227
- mb: 1.5,
228
- fontSize: '1rem',
229
- fontWeight: 600
230
- }}
231
- >
232
- Technologies:
233
- </Typography>
234
- <Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 1 }}>
235
- {product.technologies.map((tech, index) => (
236
- <Chip
237
- key={index}
238
- label={tech}
239
- variant="outlined"
240
- size="small"
241
- sx={{
242
- fontSize: '0.8rem',
243
- fontWeight: 500
244
- }}
245
- />
246
- ))}
247
- </Box>
221
+ const technologiesSectionElement = (!showTechnologies || variant === 'compact') ? null : (
222
+ <Box sx={{ mb: 3 }}>
223
+ <Typography
224
+ variant="h6"
225
+ component="h4"
226
+ sx={{
227
+ mb: 1.5,
228
+ fontSize: '1rem',
229
+ fontWeight: 600
230
+ }}
231
+ >
232
+ Technologies:
233
+ </Typography>
234
+ <Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 1 }}>
235
+ {product.technologies.map((tech, index) => (
236
+ <Chip
237
+ key={index}
238
+ label={tech}
239
+ variant="outlined"
240
+ size="small"
241
+ sx={{
242
+ fontSize: '0.8rem',
243
+ fontWeight: 500
244
+ }}
245
+ />
246
+ ))}
248
247
  </Box>
249
- );
250
- };
248
+ </Box>
249
+ );
251
250
 
252
251
  return (
253
252
  <Box
@@ -364,11 +363,11 @@ function ProductCardView({
364
363
  </Typography>
365
364
  </Box>
366
365
 
367
- {/* Features */}
368
- <FeaturesList />
366
+ {/* Features */}
367
+ {featuresListElement}
369
368
 
370
- {/* Technologies */}
371
- <TechnologiesSection />
369
+ {/* Technologies */}
370
+ {technologiesSectionElement}
372
371
 
373
372
  {/* Action Buttons */}
374
373
  <Box sx={{
@@ -381,7 +380,7 @@ function ProductCardView({
381
380
  <Button
382
381
  key={action.id}
383
382
  variant={action.variant || 'contained'}
384
- color={action.color || 'primary'}
383
+ // color={action.color || 'primary'}
385
384
  disabled={action.disabled}
386
385
  onClick={action.onClick}
387
386
  {...(variant === 'compact' && { fullWidth: true })}