@qwickapps/react-framework 1.3.5 → 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 +1681 -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/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/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/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 +10951 -6238
- package/dist/index.js +11014 -6287
- 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/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 +1 -1
- 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/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
|
@@ -14,12 +14,22 @@
|
|
|
14
14
|
* Copyright (c) 2025 QwickApps.com. All rights reserved.
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
|
-
import React from 'react';
|
|
17
|
+
import React, { ReactElement } from 'react';
|
|
18
18
|
import { Button as MuiButton, ButtonProps as MuiButtonProps, CircularProgress, Paper, Typography } from '@mui/material';
|
|
19
19
|
import type { WithDataBinding, ModelProps } from '@qwickapps/schema';
|
|
20
20
|
import { QWICKAPP_COMPONENT, useBaseProps } from '../../hooks';
|
|
21
21
|
import { useDataBinding } from '../../hooks';
|
|
22
22
|
import ButtonModel from '../../schemas/ButtonSchema';
|
|
23
|
+
import { ModelView } from '../base/ModelView';
|
|
24
|
+
|
|
25
|
+
// Action serialization pattern for button clicks
|
|
26
|
+
export interface ButtonAction {
|
|
27
|
+
type: 'navigate' | 'submit' | 'custom' | 'external';
|
|
28
|
+
url?: string;
|
|
29
|
+
target?: '_blank' | '_self' | '_parent' | '_top';
|
|
30
|
+
form?: string;
|
|
31
|
+
customHandler?: string;
|
|
32
|
+
}
|
|
23
33
|
|
|
24
34
|
type ButtonViewProps = ModelProps<ButtonModel> & {
|
|
25
35
|
/** Icon to display (before text) */
|
|
@@ -30,6 +40,8 @@ type ButtonViewProps = ModelProps<ButtonModel> & {
|
|
|
30
40
|
onClick?: (event: React.MouseEvent<HTMLButtonElement>) => void;
|
|
31
41
|
/** Button content when using children */
|
|
32
42
|
children?: React.ReactNode;
|
|
43
|
+
/** Serializable action descriptor for buttons */
|
|
44
|
+
action?: ButtonAction;
|
|
33
45
|
};
|
|
34
46
|
|
|
35
47
|
export interface ButtonProps extends ButtonViewProps, WithDataBinding {}
|
|
@@ -45,6 +57,7 @@ const ButtonView = React.forwardRef<HTMLButtonElement, ButtonViewProps>((props,
|
|
|
45
57
|
href,
|
|
46
58
|
target = '_self',
|
|
47
59
|
onClick,
|
|
60
|
+
action,
|
|
48
61
|
disabled = false,
|
|
49
62
|
loading = false,
|
|
50
63
|
fullWidth = false,
|
|
@@ -90,7 +103,64 @@ const ButtonView = React.forwardRef<HTMLButtonElement, ButtonViewProps>((props,
|
|
|
90
103
|
}
|
|
91
104
|
};
|
|
92
105
|
|
|
93
|
-
//
|
|
106
|
+
// Handle action serialization pattern for onClick
|
|
107
|
+
const handleActionClick = React.useCallback((event: React.MouseEvent<HTMLButtonElement>) => {
|
|
108
|
+
if (action) {
|
|
109
|
+
event.preventDefault();
|
|
110
|
+
|
|
111
|
+
switch (action.type) {
|
|
112
|
+
case 'navigate':
|
|
113
|
+
if (action.url) {
|
|
114
|
+
if (action.target === '_blank') {
|
|
115
|
+
window.open(action.url, '_blank', 'noopener,noreferrer');
|
|
116
|
+
} else {
|
|
117
|
+
// Try to use React Router navigation if available
|
|
118
|
+
try {
|
|
119
|
+
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
120
|
+
const navigate = require('react-router-dom').useNavigate();
|
|
121
|
+
navigate(action.url);
|
|
122
|
+
} catch {
|
|
123
|
+
// Fallback to regular navigation
|
|
124
|
+
window.location.href = action.url;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
break;
|
|
129
|
+
|
|
130
|
+
case 'submit':
|
|
131
|
+
if (action.form) {
|
|
132
|
+
const form = document.getElementById(action.form);
|
|
133
|
+
if (form instanceof HTMLFormElement) {
|
|
134
|
+
form.requestSubmit();
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
break;
|
|
138
|
+
|
|
139
|
+
case 'external':
|
|
140
|
+
if (action.url) {
|
|
141
|
+
window.open(action.url, action.target || '_blank', 'noopener,noreferrer');
|
|
142
|
+
}
|
|
143
|
+
break;
|
|
144
|
+
|
|
145
|
+
case 'custom':
|
|
146
|
+
if (action.customHandler && typeof window !== 'undefined') {
|
|
147
|
+
// Look for custom handler function on window
|
|
148
|
+
const handler = (window as any)[action.customHandler];
|
|
149
|
+
if (typeof handler === 'function') {
|
|
150
|
+
handler(event);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
break;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Call original onClick if provided
|
|
158
|
+
if (onClick) {
|
|
159
|
+
onClick(event);
|
|
160
|
+
}
|
|
161
|
+
}, [action, onClick]);
|
|
162
|
+
|
|
163
|
+
// Determine if this should be a link (prioritize href over action)
|
|
94
164
|
const isLink = Boolean(href && !disabled && !loading);
|
|
95
165
|
|
|
96
166
|
// Base button props
|
|
@@ -141,10 +211,10 @@ const ButtonView = React.forwardRef<HTMLButtonElement, ButtonViewProps>((props,
|
|
|
141
211
|
);
|
|
142
212
|
}
|
|
143
213
|
|
|
144
|
-
// Button-specific props
|
|
214
|
+
// Button-specific props with action handling
|
|
145
215
|
const buttonProps = {
|
|
146
216
|
...baseProps,
|
|
147
|
-
onClick: disabled || loading ? undefined : onClick,
|
|
217
|
+
onClick: disabled || loading ? undefined : (action ? handleActionClick : onClick),
|
|
148
218
|
};
|
|
149
219
|
|
|
150
220
|
return (
|
|
@@ -156,21 +226,122 @@ const ButtonView = React.forwardRef<HTMLButtonElement, ButtonViewProps>((props,
|
|
|
156
226
|
|
|
157
227
|
ButtonView.displayName = 'ButtonView';
|
|
158
228
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
229
|
+
// Main component with data binding support and serialization capability
|
|
230
|
+
export class Button extends ModelView<ButtonProps, ButtonModel> {
|
|
231
|
+
// Component self-declaration for serialization
|
|
232
|
+
static readonly tagName = 'Button';
|
|
233
|
+
static readonly version = '1.0.0';
|
|
234
|
+
|
|
235
|
+
// Deserialization: JSON data → React element
|
|
236
|
+
static fromJson(jsonData: any): ReactElement {
|
|
237
|
+
return <Button {...jsonData} />;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// Component-specific serialization properties
|
|
241
|
+
protected getComponentSpecificProps(): any {
|
|
242
|
+
return {
|
|
243
|
+
label: this.props.label,
|
|
244
|
+
variant: this.props.variant,
|
|
245
|
+
buttonSize: this.props.buttonSize,
|
|
246
|
+
href: this.props.href,
|
|
247
|
+
target: this.props.target,
|
|
248
|
+
disabled: this.props.disabled,
|
|
249
|
+
loading: this.props.loading,
|
|
250
|
+
fullWidth: this.props.fullWidth,
|
|
251
|
+
// Include action pattern for click handlers
|
|
252
|
+
action: this.props.action
|
|
253
|
+
};
|
|
254
|
+
}
|
|
165
255
|
|
|
166
|
-
//
|
|
167
|
-
|
|
256
|
+
// Button component renders traditional props view
|
|
257
|
+
protected renderView(): React.ReactElement {
|
|
258
|
+
const { dataSource, bindingOptions, ...restProps } = this.props;
|
|
168
259
|
return <ButtonView {...restProps} />;
|
|
169
260
|
}
|
|
170
261
|
|
|
262
|
+
// Button component renders data-bound view
|
|
263
|
+
protected renderWithDataBinding(): React.ReactElement {
|
|
264
|
+
return <ButtonWithDataBinding {...this.props} />;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// Register HTML patterns that Button component can handle
|
|
268
|
+
static registerPatternHandlers(registry: any): void {
|
|
269
|
+
// Register button elements
|
|
270
|
+
if (!registry.hasPattern('button')) {
|
|
271
|
+
registry.registerPattern('button', Button.transformButton);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// Register input type="button" elements
|
|
275
|
+
if (!registry.hasPattern('input[type="button"]')) {
|
|
276
|
+
registry.registerPattern('input[type="button"]', Button.transformInputButton);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// Register input type="submit" elements
|
|
280
|
+
if (!registry.hasPattern('input[type="submit"]')) {
|
|
281
|
+
registry.registerPattern('input[type="submit"]', Button.transformSubmitButton);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// Transform button elements to Button component
|
|
286
|
+
private static transformButton(element: Element): any {
|
|
287
|
+
const variant = element.getAttribute('data-variant') ||
|
|
288
|
+
(element.className.includes('btn-primary') ? 'primary' :
|
|
289
|
+
element.className.includes('btn-outlined') ? 'outlined' : 'secondary');
|
|
290
|
+
const disabled = element.hasAttribute('disabled');
|
|
291
|
+
const href = element.getAttribute('data-href');
|
|
292
|
+
const target = element.getAttribute('data-target');
|
|
293
|
+
|
|
294
|
+
return {
|
|
295
|
+
tagName: 'Button',
|
|
296
|
+
props: {
|
|
297
|
+
label: element.textContent || 'Button',
|
|
298
|
+
variant,
|
|
299
|
+
disabled,
|
|
300
|
+
href: href || undefined,
|
|
301
|
+
target: target || undefined
|
|
302
|
+
}
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
// Transform input type="button" elements to Button component
|
|
307
|
+
private static transformInputButton(element: Element): any {
|
|
308
|
+
const disabled = element.hasAttribute('disabled');
|
|
309
|
+
const value = element.getAttribute('value') || 'Button';
|
|
310
|
+
|
|
311
|
+
return {
|
|
312
|
+
tagName: 'Button',
|
|
313
|
+
props: {
|
|
314
|
+
label: value,
|
|
315
|
+
variant: 'secondary',
|
|
316
|
+
disabled
|
|
317
|
+
}
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// Transform input type="submit" elements to Button component
|
|
322
|
+
private static transformSubmitButton(element: Element): any {
|
|
323
|
+
const disabled = element.hasAttribute('disabled');
|
|
324
|
+
const value = element.getAttribute('value') || 'Submit';
|
|
325
|
+
|
|
326
|
+
return {
|
|
327
|
+
tagName: 'Button',
|
|
328
|
+
props: {
|
|
329
|
+
label: value,
|
|
330
|
+
variant: 'primary',
|
|
331
|
+
disabled,
|
|
332
|
+
type: 'submit'
|
|
333
|
+
}
|
|
334
|
+
};
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
// Helper component to handle data binding with hooks (since we can't use hooks in class components)
|
|
339
|
+
function ButtonWithDataBinding(props: ButtonProps) {
|
|
340
|
+
const { dataSource, bindingOptions, ...restProps } = props;
|
|
341
|
+
|
|
171
342
|
// Use data binding
|
|
172
343
|
const bindingResult = useDataBinding<ButtonModel>(
|
|
173
|
-
dataSource
|
|
344
|
+
dataSource!,
|
|
174
345
|
restProps as Partial<ButtonModel>,
|
|
175
346
|
ButtonModel.getSchema(),
|
|
176
347
|
{ cache: true, cacheTTL: 300000, strict: false, ...bindingOptions }
|
|
@@ -183,7 +354,6 @@ function Button(props: ButtonProps) {
|
|
|
183
354
|
const { dataSource: _source, $loading, $error, $dataSource, $cached, cached, ...buttonProps } = bindingResult;
|
|
184
355
|
const error = bindingResult.$error;
|
|
185
356
|
|
|
186
|
-
|
|
187
357
|
// Show loading state while fetching data
|
|
188
358
|
if (bindingLoading) {
|
|
189
359
|
return (
|
|
@@ -229,5 +399,4 @@ function Button(props: ButtonProps) {
|
|
|
229
399
|
// Mark as QwickApp component
|
|
230
400
|
(Button as any)[QWICKAPP_COMPONENT] = true;
|
|
231
401
|
|
|
232
|
-
export { Button };
|
|
233
402
|
export default Button;
|
|
@@ -1,17 +1,18 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* FormBlock - Reusable form layout component
|
|
2
|
+
* FormBlock - Reusable form layout component with serialization support
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
* -
|
|
6
|
-
* -
|
|
7
|
-
* -
|
|
8
|
-
* - Optional footer section
|
|
4
|
+
* Features:
|
|
5
|
+
* - Complete form layout with header, content, and footer
|
|
6
|
+
* - Status message handling (info, success, warning, error)
|
|
7
|
+
* - Cover image and logo integration
|
|
9
8
|
* - Data binding support for dynamic content
|
|
10
|
-
* -
|
|
9
|
+
* - Full serialization support via ModelView
|
|
10
|
+
* - Consistent Material-UI styling
|
|
11
11
|
*
|
|
12
12
|
* Copyright (c) 2025 QwickApps.com. All rights reserved.
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
|
+
import React, { ReactElement } from 'react';
|
|
15
16
|
import {
|
|
16
17
|
Alert,
|
|
17
18
|
Box,
|
|
@@ -23,12 +24,12 @@ import {
|
|
|
23
24
|
useTheme,
|
|
24
25
|
} from '@mui/material';
|
|
25
26
|
import type { ModelProps, WithDataBinding } from '@qwickapps/schema';
|
|
26
|
-
import React from 'react';
|
|
27
27
|
import { useQwickApp } from '../../contexts/QwickAppContext';
|
|
28
28
|
import { QWICKAPP_COMPONENT, useBaseProps, useDataBinding } from '../../hooks';
|
|
29
29
|
import FormBlockModel from '../../schemas/FormBlockSchema';
|
|
30
30
|
import CoverImageHeader from '../blocks/CoverImageHeader';
|
|
31
31
|
import Logo from '../Logo';
|
|
32
|
+
import { ModelView } from '../base/ModelView';
|
|
32
33
|
|
|
33
34
|
type FormBlockViewProps = ModelProps<FormBlockModel> & {
|
|
34
35
|
/**
|
|
@@ -223,21 +224,73 @@ function FormBlockView({
|
|
|
223
224
|
);
|
|
224
225
|
}
|
|
225
226
|
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
227
|
+
// Main component with data binding support and serialization capability
|
|
228
|
+
export class FormBlock extends ModelView<FormBlockProps, FormBlockModel> {
|
|
229
|
+
// Component self-declaration for serialization
|
|
230
|
+
static readonly tagName = 'FormBlock';
|
|
231
|
+
static readonly version = '1.0.0';
|
|
232
|
+
|
|
233
|
+
// Deserialization: JSON data → React element
|
|
234
|
+
static fromJson(jsonData: any): ReactElement {
|
|
235
|
+
return <FormBlock {...jsonData} />;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// FormBlock components have nested ReactNode children (form content)
|
|
239
|
+
protected hasNestedComponents(children: React.ReactNode): boolean {
|
|
240
|
+
// FormBlock may contain other serializable components as children
|
|
241
|
+
return true;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// Custom children serialization for FormBlock
|
|
245
|
+
protected serializeChildren(children: React.ReactNode): any {
|
|
246
|
+
// For FormBlock, children represent the form content which could be other components
|
|
247
|
+
// We need to handle this properly by checking if children contain serializable components
|
|
248
|
+
if (React.isValidElement(children) && (children.type as any).prototype?.toJson) {
|
|
249
|
+
// If it's a serializable component, serialize it
|
|
250
|
+
return (children.type as any).fromJson ? {
|
|
251
|
+
component: (children.type as any).tagName,
|
|
252
|
+
props: children.props
|
|
253
|
+
} : children;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// For non-serializable children (regular JSX), extract text or return as-is
|
|
257
|
+
return super.serializeChildren(children);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// Component-specific serialization properties
|
|
261
|
+
protected getComponentSpecificProps(): any {
|
|
262
|
+
return {
|
|
263
|
+
title: this.props.title,
|
|
264
|
+
description: this.props.description,
|
|
265
|
+
status: this.props.status,
|
|
266
|
+
message: this.props.message,
|
|
267
|
+
maxWidth: this.props.maxWidth,
|
|
268
|
+
background: this.props.background,
|
|
269
|
+
backgroundImage: this.props.backgroundImage,
|
|
270
|
+
// header, coverImage, footer are ReactNode (handled by base serialization if needed)
|
|
271
|
+
// children is handled by serializeChildren override
|
|
272
|
+
};
|
|
273
|
+
}
|
|
232
274
|
|
|
233
|
-
//
|
|
234
|
-
|
|
275
|
+
// FormBlock component renders traditional props view
|
|
276
|
+
protected renderView(): React.ReactElement {
|
|
277
|
+
const { dataSource, bindingOptions, ...restProps } = this.props;
|
|
235
278
|
return <FormBlockView {...restProps} />;
|
|
236
279
|
}
|
|
237
280
|
|
|
281
|
+
// FormBlock component renders data-bound view
|
|
282
|
+
protected renderWithDataBinding(): React.ReactElement {
|
|
283
|
+
return <FormBlockWithDataBinding {...this.props} />;
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// Helper component to handle data binding with hooks (since we can't use hooks in class components)
|
|
288
|
+
function FormBlockWithDataBinding(props: FormBlockProps) {
|
|
289
|
+
const { dataSource, bindingOptions, ...restProps } = props;
|
|
290
|
+
|
|
238
291
|
// Use data binding
|
|
239
292
|
const { dataSource: _source, loading, error, cached, ...formBlockProps } = useDataBinding<FormBlockModel>(
|
|
240
|
-
dataSource
|
|
293
|
+
dataSource!,
|
|
241
294
|
restProps as Partial<FormBlockModel>,
|
|
242
295
|
FormBlockModel.getSchema(),
|
|
243
296
|
{ cache: true, cacheTTL: 300000, strict: false, ...bindingOptions }
|
package/src/components/index.ts
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
* All component props and exports available in QwickApps React Framework.
|
|
3
3
|
*
|
|
4
4
|
* Includes:
|
|
5
|
+
* - Base classes and abstractions
|
|
5
6
|
* - Buttons
|
|
6
7
|
* - Form inputs
|
|
7
8
|
* - Layout components
|
|
@@ -9,6 +10,7 @@
|
|
|
9
10
|
* - Pages and scaffolding
|
|
10
11
|
* Copyright (c) 2025 QwickApps.com. All rights reserved.
|
|
11
12
|
*/
|
|
13
|
+
export * from './base';
|
|
12
14
|
export * from './blocks';
|
|
13
15
|
export * from './buttons';
|
|
14
16
|
export * from './forms';
|
|
@@ -30,6 +32,9 @@ export type { LogoProps } from './Logo';
|
|
|
30
32
|
export { default as QwickAppsLogo } from './QwickAppsLogo';
|
|
31
33
|
export type { QwickAppsLogoProps } from './QwickAppsLogo';
|
|
32
34
|
|
|
35
|
+
export { default as QwickIcon } from './QwickIcon';
|
|
36
|
+
export type { QwickIconProps } from './QwickIcon';
|
|
37
|
+
|
|
33
38
|
export { default as SafeSpan } from './SafeSpan';
|
|
34
39
|
export type { SafeSpanProps } from './SafeSpan';
|
|
35
40
|
|
|
@@ -1,16 +1,17 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* ChoiceInputField - Dynamic option inputs management component
|
|
2
|
+
* ChoiceInputField - Dynamic option inputs management component with serialization support
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
* -
|
|
6
|
-
* -
|
|
7
|
-
* -
|
|
8
|
-
* -
|
|
4
|
+
* Features:
|
|
5
|
+
* - Complete dynamic option input management
|
|
6
|
+
* - Rich text editing for each option (via HtmlInputField)
|
|
7
|
+
* - Data binding support for dynamic content
|
|
8
|
+
* - Full serialization support via ModelView
|
|
9
|
+
* - Consistent Material-UI styling
|
|
9
10
|
*
|
|
10
11
|
* Copyright (c) 2025 QwickApps.com. All rights reserved.
|
|
11
12
|
*/
|
|
12
13
|
|
|
13
|
-
import React from 'react';
|
|
14
|
+
import React, { ReactElement } from 'react';
|
|
14
15
|
import {
|
|
15
16
|
Alert,
|
|
16
17
|
Box,
|
|
@@ -20,10 +21,10 @@ import {
|
|
|
20
21
|
} from '@mui/material';
|
|
21
22
|
import { Add as AddIcon } from '@mui/icons-material';
|
|
22
23
|
import type { WithDataBinding, ModelProps } from '@qwickapps/schema';
|
|
23
|
-
import { QWICKAPP_COMPONENT, useBaseProps } from '../../hooks';
|
|
24
|
-
import { useDataBinding } from '../../hooks';
|
|
24
|
+
import { QWICKAPP_COMPONENT, useBaseProps, useDataBinding } from '../../hooks';
|
|
25
25
|
import HtmlInputField from './HtmlInputField';
|
|
26
26
|
import ChoiceInputFieldModel from '../../schemas/ChoiceInputFieldSchema';
|
|
27
|
+
import { ModelView } from '../base/ModelView';
|
|
27
28
|
|
|
28
29
|
type ChoiceInputFieldViewProps = ModelProps<ChoiceInputFieldModel> & {
|
|
29
30
|
/** Handler for option value changes */
|
|
@@ -120,21 +121,50 @@ function ChoiceInputFieldView({
|
|
|
120
121
|
);
|
|
121
122
|
}
|
|
122
123
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
124
|
+
// Main component with data binding support and serialization capability
|
|
125
|
+
export class ChoiceInputField extends ModelView<ChoiceInputFieldProps, ChoiceInputFieldModel> {
|
|
126
|
+
// Component self-declaration for serialization
|
|
127
|
+
static readonly tagName = 'ChoiceInputField';
|
|
128
|
+
static readonly version = '1.0.0';
|
|
129
|
+
|
|
130
|
+
// Deserialization: JSON data → React element
|
|
131
|
+
static fromJson(jsonData: any): ReactElement {
|
|
132
|
+
return <ChoiceInputField {...jsonData} />;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Component-specific serialization properties
|
|
136
|
+
protected getComponentSpecificProps(): any {
|
|
137
|
+
return {
|
|
138
|
+
label: this.props.label,
|
|
139
|
+
options: this.props.options,
|
|
140
|
+
disabled: this.props.disabled,
|
|
141
|
+
placeholder: this.props.placeholder,
|
|
142
|
+
optionLabelPrefix: this.props.optionLabelPrefix,
|
|
143
|
+
rows: this.props.rows,
|
|
144
|
+
addButtonText: this.props.addButtonText,
|
|
145
|
+
// Event handlers don't serialize
|
|
146
|
+
};
|
|
147
|
+
}
|
|
129
148
|
|
|
130
|
-
//
|
|
131
|
-
|
|
149
|
+
// ChoiceInputField component renders traditional props view
|
|
150
|
+
protected renderView(): React.ReactElement {
|
|
151
|
+
const { dataSource, bindingOptions, ...restProps } = this.props;
|
|
132
152
|
return <ChoiceInputFieldView {...restProps} />;
|
|
133
153
|
}
|
|
134
154
|
|
|
155
|
+
// ChoiceInputField component renders data-bound view
|
|
156
|
+
protected renderWithDataBinding(): React.ReactElement {
|
|
157
|
+
return <ChoiceInputFieldWithDataBinding {...this.props} />;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Helper component to handle data binding with hooks (since we can't use hooks in class components)
|
|
162
|
+
function ChoiceInputFieldWithDataBinding(props: ChoiceInputFieldProps) {
|
|
163
|
+
const { dataSource, bindingOptions, ...restProps } = props;
|
|
164
|
+
|
|
135
165
|
// Use data binding
|
|
136
166
|
const { dataSource: _source, loading, error, cached, ...choiceInputFieldProps } = useDataBinding<ChoiceInputFieldModel>(
|
|
137
|
-
dataSource
|
|
167
|
+
dataSource!,
|
|
138
168
|
restProps as Partial<ChoiceInputFieldModel>,
|
|
139
169
|
ChoiceInputFieldModel.getSchema(),
|
|
140
170
|
{ cache: true, cacheTTL: 300000, strict: false, ...bindingOptions }
|
|
@@ -1,17 +1,18 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* HtmlInputField - Custom HTML text field with
|
|
2
|
+
* HtmlInputField - Custom HTML text field with serialization support
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
* -
|
|
6
|
-
* - HTML preview mode
|
|
7
|
-
* - Help system with supported tags
|
|
4
|
+
* Features:
|
|
5
|
+
* - Complete HTML editing with formatting toolbar
|
|
6
|
+
* - HTML preview mode and help system
|
|
8
7
|
* - HTML sanitization for security
|
|
9
|
-
* - Data binding support
|
|
8
|
+
* - Data binding support for dynamic content
|
|
9
|
+
* - Full serialization support via ModelView
|
|
10
|
+
* - Consistent Material-UI styling
|
|
10
11
|
*
|
|
11
12
|
* Copyright (c) 2025 QwickApps.com. All rights reserved.
|
|
12
13
|
*/
|
|
13
14
|
|
|
14
|
-
import { useState } from 'react';
|
|
15
|
+
import React, { ReactElement, useState } from 'react';
|
|
15
16
|
import {
|
|
16
17
|
Box,
|
|
17
18
|
TextField,
|
|
@@ -36,9 +37,9 @@ import {
|
|
|
36
37
|
import type { WithDataBinding, ModelProps } from '@qwickapps/schema';
|
|
37
38
|
import SafeSpan from '../SafeSpan';
|
|
38
39
|
import sanitizeHtml from 'sanitize-html';
|
|
39
|
-
import { QWICKAPP_COMPONENT, useBaseProps } from '../../hooks';
|
|
40
|
-
import { useDataBinding } from '../../hooks';
|
|
40
|
+
import { QWICKAPP_COMPONENT, useBaseProps, useDataBinding } from '../../hooks';
|
|
41
41
|
import HtmlInputFieldModel from '../../schemas/HtmlInputFieldSchema';
|
|
42
|
+
import { ModelView } from '../base/ModelView';
|
|
42
43
|
|
|
43
44
|
type HtmlInputFieldViewProps = ModelProps<HtmlInputFieldModel> & {
|
|
44
45
|
/** Change handler */
|
|
@@ -258,21 +259,50 @@ function HtmlInputFieldView({
|
|
|
258
259
|
);
|
|
259
260
|
}
|
|
260
261
|
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
262
|
+
// Main component with data binding support and serialization capability
|
|
263
|
+
export class HtmlInputField extends ModelView<HtmlInputFieldProps, HtmlInputFieldModel> {
|
|
264
|
+
// Component self-declaration for serialization
|
|
265
|
+
static readonly tagName = 'HtmlInputField';
|
|
266
|
+
static readonly version = '1.0.0';
|
|
267
|
+
|
|
268
|
+
// Deserialization: JSON data → React element
|
|
269
|
+
static fromJson(jsonData: any): ReactElement {
|
|
270
|
+
return <HtmlInputField {...jsonData} />;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// Component-specific serialization properties
|
|
274
|
+
protected getComponentSpecificProps(): any {
|
|
275
|
+
return {
|
|
276
|
+
label: this.props.label,
|
|
277
|
+
value: this.props.value,
|
|
278
|
+
required: this.props.required,
|
|
279
|
+
placeholder: this.props.placeholder,
|
|
280
|
+
multiline: this.props.multiline,
|
|
281
|
+
rows: this.props.rows,
|
|
282
|
+
disabled: this.props.disabled,
|
|
283
|
+
// Event handlers don't serialize
|
|
284
|
+
};
|
|
285
|
+
}
|
|
267
286
|
|
|
268
|
-
//
|
|
269
|
-
|
|
287
|
+
// HtmlInputField component renders traditional props view
|
|
288
|
+
protected renderView(): React.ReactElement {
|
|
289
|
+
const { dataSource, bindingOptions, ...restProps } = this.props;
|
|
270
290
|
return <HtmlInputFieldView {...restProps} />;
|
|
271
291
|
}
|
|
272
292
|
|
|
293
|
+
// HtmlInputField component renders data-bound view
|
|
294
|
+
protected renderWithDataBinding(): React.ReactElement {
|
|
295
|
+
return <HtmlInputFieldWithDataBinding {...this.props} />;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// Helper component to handle data binding with hooks (since we can't use hooks in class components)
|
|
300
|
+
function HtmlInputFieldWithDataBinding(props: HtmlInputFieldProps) {
|
|
301
|
+
const { dataSource, bindingOptions, ...restProps } = props;
|
|
302
|
+
|
|
273
303
|
// Use data binding
|
|
274
304
|
const { dataSource: _source, loading, error, cached, ...htmlInputFieldProps } = useDataBinding<HtmlInputFieldModel>(
|
|
275
|
-
dataSource
|
|
305
|
+
dataSource!,
|
|
276
306
|
restProps as Partial<HtmlInputFieldModel>,
|
|
277
307
|
HtmlInputFieldModel.getSchema(),
|
|
278
308
|
{ cache: true, cacheTTL: 300000, strict: false, ...bindingOptions }
|