@stormlmd/form-builder 0.3.1 → 0.3.5
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/package.json +4 -3
- package/src/components/FieldRegistry.ts +1 -0
- package/src/components/FormRenderer.tsx +4 -2
- package/src/components/builder/FormChildrenRenderer.tsx +4 -2
- package/src/components/builder/PropertiesPanel.tsx +42 -7
- package/src/components/fields/CheckboxField.tsx +15 -2
- package/src/components/fields/DateField.tsx +14 -1
- package/src/components/fields/FileUploadField.tsx +14 -1
- package/src/components/fields/LabelField.tsx +1 -1
- package/src/components/fields/NumberField.tsx +14 -1
- package/src/components/fields/RichTextField.tsx +95 -18
- package/src/components/fields/SelectField.tsx +15 -1
- package/src/components/fields/TextField.tsx +14 -1
- package/src/components/layout/FormCol.tsx +2 -2
- package/src/components/layout/FormPaper.tsx +2 -1
- package/src/components/layout/FormRepeater.tsx +5 -3
- package/src/components/layout/FormRow.tsx +6 -2
- package/src/components/layout/FormTab.tsx +2 -1
- package/src/components/layout/FormTable.tsx +2 -2
- package/src/components/layout/FormTableCell.tsx +3 -1
- package/src/components/layout/FormTabs.tsx +2 -2
- package/src/components/registerComponents.ts +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stormlmd/form-builder",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.5",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./src/index.ts",
|
|
6
6
|
"module": "./src/index.ts",
|
|
@@ -38,11 +38,11 @@
|
|
|
38
38
|
"@mui/icons-material": "^5.16.0",
|
|
39
39
|
"@mui/material": "^5.16.0",
|
|
40
40
|
"@mui/x-date-pickers": "^5.0.0",
|
|
41
|
-
"date-fns": "^2.30.0",
|
|
42
41
|
"@types/node": "^24.10.1",
|
|
43
42
|
"@types/react": "^18.3.1",
|
|
44
43
|
"@types/react-dom": "^18.3.1",
|
|
45
44
|
"@vitejs/plugin-react": "^5.1.1",
|
|
45
|
+
"date-fns": "^2.30.0",
|
|
46
46
|
"eslint": "^9.39.1",
|
|
47
47
|
"eslint-plugin-react-hooks": "^7.0.1",
|
|
48
48
|
"eslint-plugin-react-refresh": "^0.4.24",
|
|
@@ -55,6 +55,7 @@
|
|
|
55
55
|
},
|
|
56
56
|
"dependencies": {
|
|
57
57
|
"i18next": "^25.8.7",
|
|
58
|
-
"react-i18next": "^16.5.4"
|
|
58
|
+
"react-i18next": "^16.5.4",
|
|
59
|
+
"react-quill-new": "^3.8.3"
|
|
59
60
|
}
|
|
60
61
|
}
|
|
@@ -13,6 +13,7 @@ interface FormRendererProps {
|
|
|
13
13
|
node: SchemaNode;
|
|
14
14
|
path?: string;
|
|
15
15
|
isEditing?: boolean;
|
|
16
|
+
isReadOnly?: boolean;
|
|
16
17
|
}
|
|
17
18
|
|
|
18
19
|
export const DropIndicator = () => (
|
|
@@ -27,7 +28,7 @@ export const DropIndicator = () => (
|
|
|
27
28
|
}} />
|
|
28
29
|
);
|
|
29
30
|
|
|
30
|
-
export const FormRenderer: React.FC<FormRendererProps> = observer(({ node, path, isEditing }) => {
|
|
31
|
+
export const FormRenderer: React.FC<FormRendererProps> = observer(({ node, path, isEditing, isReadOnly }) => {
|
|
31
32
|
const formStore = useFormStore();
|
|
32
33
|
// Evaluate condition if present (skip in editing mode)
|
|
33
34
|
if (!isEditing && node.condition) {
|
|
@@ -55,6 +56,7 @@ export const FormRenderer: React.FC<FormRendererProps> = observer(({ node, path,
|
|
|
55
56
|
parentId={node.id}
|
|
56
57
|
children={node.children}
|
|
57
58
|
isEditing={!!isEditing}
|
|
59
|
+
isReadOnly={!!isReadOnly}
|
|
58
60
|
path={path}
|
|
59
61
|
/>
|
|
60
62
|
</Box>
|
|
@@ -92,7 +94,7 @@ export const FormRenderer: React.FC<FormRendererProps> = observer(({ node, path,
|
|
|
92
94
|
flexDirection: 'column'
|
|
93
95
|
};
|
|
94
96
|
|
|
95
|
-
let renderedComponent = <Component node={node} path={path} isEditing={isEditing} />;
|
|
97
|
+
let renderedComponent = <Component node={node} path={path} isEditing={isEditing} isReadOnly={isReadOnly} />;
|
|
96
98
|
|
|
97
99
|
// Wrap in Tooltip if present (only in preview mode, or also in editor? User said "in form" - обычно в превью, но в редакторе тоже неплохо)
|
|
98
100
|
if (!isEditing && tooltip) {
|
|
@@ -12,6 +12,7 @@ interface FormChildrenRendererProps {
|
|
|
12
12
|
isEditing: boolean;
|
|
13
13
|
layout?: 'horizontal' | 'vertical';
|
|
14
14
|
path?: string;
|
|
15
|
+
isReadOnly?: boolean;
|
|
15
16
|
}
|
|
16
17
|
|
|
17
18
|
export const FormChildrenRenderer: React.FC<FormChildrenRendererProps> = observer(({
|
|
@@ -19,7 +20,8 @@ export const FormChildrenRenderer: React.FC<FormChildrenRendererProps> = observe
|
|
|
19
20
|
children,
|
|
20
21
|
isEditing,
|
|
21
22
|
layout = 'horizontal',
|
|
22
|
-
path
|
|
23
|
+
path,
|
|
24
|
+
isReadOnly
|
|
23
25
|
}) => {
|
|
24
26
|
const formStore = useFormStore();
|
|
25
27
|
const childIds = children?.map(c => c.id) || [];
|
|
@@ -40,7 +42,7 @@ export const FormChildrenRenderer: React.FC<FormChildrenRendererProps> = observe
|
|
|
40
42
|
return (
|
|
41
43
|
<Box sx={containerSx}>
|
|
42
44
|
{children?.map(child => (
|
|
43
|
-
<FormRenderer key={child.id} node={child} path={path} isEditing={false} />
|
|
45
|
+
<FormRenderer key={child.id} node={child} path={path} isEditing={false} isReadOnly={isReadOnly} />
|
|
44
46
|
))}
|
|
45
47
|
</Box>
|
|
46
48
|
);
|
|
@@ -7,7 +7,7 @@ import DeleteIcon from '@mui/icons-material/Delete';
|
|
|
7
7
|
import HelpOutlineIcon from '@mui/icons-material/HelpOutline';
|
|
8
8
|
import { useTranslation } from 'react-i18next';
|
|
9
9
|
import { getPlugin } from '../../plugins/PluginRegistry';
|
|
10
|
-
import { FormulaHelp } from './FormulaHelp';
|
|
10
|
+
// import { FormulaHelp } from './FormulaHelp';
|
|
11
11
|
|
|
12
12
|
interface TabPanelProps {
|
|
13
13
|
children?: React.ReactNode;
|
|
@@ -95,7 +95,6 @@ export const PropertiesPanel: React.FC = observer(() => {
|
|
|
95
95
|
const { t } = useTranslation();
|
|
96
96
|
const node = formStore.selectedNode;
|
|
97
97
|
const [tabValue, setTabValue] = React.useState(0);
|
|
98
|
-
const [helpOpen, setHelpOpen] = React.useState(false);
|
|
99
98
|
|
|
100
99
|
const BUILT_IN_LAYOUT_TYPES = ['root', 'row', 'col', 'tabs', 'tab', 'paper', 'repeater', 'table', 'cell', 'divider'];
|
|
101
100
|
const isLayout = node ? BUILT_IN_LAYOUT_TYPES.includes(node.type) : false;
|
|
@@ -391,7 +390,7 @@ export const PropertiesPanel: React.FC = observer(() => {
|
|
|
391
390
|
endAdornment: (
|
|
392
391
|
<InputAdornment position="end">
|
|
393
392
|
<Tooltip title="Справка по формулам">
|
|
394
|
-
<IconButton size="small"
|
|
393
|
+
<IconButton size="small">
|
|
395
394
|
<HelpOutlineIcon fontSize="small" />
|
|
396
395
|
</IconButton>
|
|
397
396
|
</Tooltip>
|
|
@@ -641,6 +640,41 @@ export const PropertiesPanel: React.FC = observer(() => {
|
|
|
641
640
|
</Box>
|
|
642
641
|
)}
|
|
643
642
|
|
|
643
|
+
{node.type === 'richtext' && (
|
|
644
|
+
<Box display="flex" alignItems="center" mt={1}>
|
|
645
|
+
<input
|
|
646
|
+
type="checkbox"
|
|
647
|
+
id="enable-visual-editor"
|
|
648
|
+
checked={node.props?.enableVisualEditor || false}
|
|
649
|
+
onChange={(e) => handleChange('enableVisualEditor', e.target.checked)}
|
|
650
|
+
style={{ marginRight: 8 }}
|
|
651
|
+
/>
|
|
652
|
+
<Typography variant="body2" component="label" htmlFor="enable-visual-editor" sx={{ cursor: 'pointer' }}>
|
|
653
|
+
{t('properties.enableVisualEditor') || 'Enable Visual Editor'}
|
|
654
|
+
</Typography>
|
|
655
|
+
</Box>
|
|
656
|
+
)}
|
|
657
|
+
|
|
658
|
+
{node.type === 'checkbox' && (
|
|
659
|
+
<Box mt={2} display="flex" flexDirection="column" gap={2}>
|
|
660
|
+
<Typography variant="subtitle2" color="primary">{t('properties.checkboxResultsLabels') || 'Results View Labels'}</Typography>
|
|
661
|
+
<TextField
|
|
662
|
+
label={t('properties.trueText') || 'Positive (e.g. "Да")'}
|
|
663
|
+
size="small"
|
|
664
|
+
fullWidth
|
|
665
|
+
value={node.props?.trueText || ''}
|
|
666
|
+
onChange={(e) => handleChange('trueText', e.target.value)}
|
|
667
|
+
/>
|
|
668
|
+
<TextField
|
|
669
|
+
label={t('properties.falseText') || 'Negative (e.g. "Нет")'}
|
|
670
|
+
size="small"
|
|
671
|
+
fullWidth
|
|
672
|
+
value={node.props?.falseText || ''}
|
|
673
|
+
onChange={(e) => handleChange('falseText', e.target.value)}
|
|
674
|
+
/>
|
|
675
|
+
</Box>
|
|
676
|
+
)}
|
|
677
|
+
|
|
644
678
|
{/* Plugin custom properties editor */}
|
|
645
679
|
{plugin?.propertiesEditor && (
|
|
646
680
|
<Box mt={2}>
|
|
@@ -711,7 +745,7 @@ export const PropertiesPanel: React.FC = observer(() => {
|
|
|
711
745
|
endAdornment: (
|
|
712
746
|
<InputAdornment position="end">
|
|
713
747
|
<Tooltip title="Справка по формулам">
|
|
714
|
-
<IconButton size="small"
|
|
748
|
+
<IconButton size="small">
|
|
715
749
|
<HelpOutlineIcon fontSize="small" />
|
|
716
750
|
</IconButton>
|
|
717
751
|
</Tooltip>
|
|
@@ -741,7 +775,7 @@ export const PropertiesPanel: React.FC = observer(() => {
|
|
|
741
775
|
endAdornment: (
|
|
742
776
|
<InputAdornment position="end">
|
|
743
777
|
<Tooltip title="Справка по формулам">
|
|
744
|
-
<IconButton size="small"
|
|
778
|
+
<IconButton size="small">
|
|
745
779
|
<HelpOutlineIcon fontSize="small" />
|
|
746
780
|
</IconButton>
|
|
747
781
|
</Tooltip>
|
|
@@ -884,7 +918,7 @@ export const PropertiesPanel: React.FC = observer(() => {
|
|
|
884
918
|
endAdornment: (
|
|
885
919
|
<InputAdornment position="end">
|
|
886
920
|
<Tooltip title="Справка по формулам">
|
|
887
|
-
<IconButton size="small"
|
|
921
|
+
<IconButton size="small">
|
|
888
922
|
<HelpOutlineIcon fontSize="small" />
|
|
889
923
|
</IconButton>
|
|
890
924
|
</Tooltip>
|
|
@@ -945,7 +979,8 @@ export const PropertiesPanel: React.FC = observer(() => {
|
|
|
945
979
|
)}
|
|
946
980
|
</CustomTabPanel>
|
|
947
981
|
|
|
948
|
-
|
|
982
|
+
|
|
983
|
+
{/* <FormulaHelp open={helpOpen} onClose={() => setHelpOpen(false)} /> */}
|
|
949
984
|
</Box>
|
|
950
985
|
);
|
|
951
986
|
});
|
|
@@ -4,12 +4,25 @@ import { observer } from 'mobx-react-lite';
|
|
|
4
4
|
import { useFormStore } from '../../store/FormStoreContext';
|
|
5
5
|
import type { FieldProps } from '../FieldRegistry';
|
|
6
6
|
|
|
7
|
-
export const CheckboxField: React.FC<FieldProps> = observer(({ node, path, isEditing }) => {
|
|
7
|
+
export const CheckboxField: React.FC<FieldProps> = observer(({ node, path, isEditing, isReadOnly }) => {
|
|
8
8
|
const formStore = useFormStore();
|
|
9
|
-
const { name, label } = node.props || {};
|
|
9
|
+
const { name, label, trueText = 'Да', falseText = 'Нет' } = node.props || {};
|
|
10
10
|
const fullPath = path && name ? `${path}.${name}` : name;
|
|
11
11
|
const value = fullPath ? formStore.getValue(fullPath) : false;
|
|
12
12
|
|
|
13
|
+
if (isReadOnly) {
|
|
14
|
+
return (
|
|
15
|
+
<Box mb={1}>
|
|
16
|
+
<Typography variant="caption" color="text.secondary" display="block">
|
|
17
|
+
{label || ''}
|
|
18
|
+
</Typography>
|
|
19
|
+
<Typography variant="body1" sx={{ minHeight: '1.5em', borderBottom: '1px solid #eee', pb: 0.5 }}>
|
|
20
|
+
{value ? trueText : falseText}
|
|
21
|
+
</Typography>
|
|
22
|
+
</Box>
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
|
|
13
26
|
return (
|
|
14
27
|
<Box mb={0.5}>
|
|
15
28
|
{isEditing && (
|
|
@@ -7,7 +7,7 @@ import { useFormStore } from '../../store/FormStoreContext';
|
|
|
7
7
|
import type { FieldProps } from '../FieldRegistry';
|
|
8
8
|
import { Box, Typography, TextField } from '@mui/material';
|
|
9
9
|
|
|
10
|
-
export const DateField: React.FC<FieldProps> = observer(({ node, path, isEditing }) => {
|
|
10
|
+
export const DateField: React.FC<FieldProps> = observer(({ node, path, isEditing, isReadOnly }) => {
|
|
11
11
|
const formStore = useFormStore();
|
|
12
12
|
const { name, label, placeholder } = node.props || {};
|
|
13
13
|
const fullPath = path && name ? `${path}.${name}` : name;
|
|
@@ -24,6 +24,19 @@ export const DateField: React.FC<FieldProps> = observer(({ node, path, isEditing
|
|
|
24
24
|
// Create a Date object from value if string
|
|
25
25
|
const dateValue = value ? new Date(value) : null;
|
|
26
26
|
|
|
27
|
+
if (isReadOnly) {
|
|
28
|
+
return (
|
|
29
|
+
<Box mb={1}>
|
|
30
|
+
<Typography variant="caption" color="text.secondary" display="block">
|
|
31
|
+
{label || ''}
|
|
32
|
+
</Typography>
|
|
33
|
+
<Typography variant="body1" sx={{ minHeight: '1.5em', borderBottom: '1px solid #eee', pb: 0.5 }}>
|
|
34
|
+
{dateValue ? dateValue.toLocaleDateString() : '-'}
|
|
35
|
+
</Typography>
|
|
36
|
+
</Box>
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
|
|
27
40
|
const content = (
|
|
28
41
|
<LocalizationProvider dateAdapter={AdapterDateFns}>
|
|
29
42
|
<DatePicker
|
|
@@ -5,7 +5,7 @@ import { observer } from 'mobx-react-lite';
|
|
|
5
5
|
import { useFormStore } from '../../store/FormStoreContext';
|
|
6
6
|
import type { FieldProps } from '../FieldRegistry';
|
|
7
7
|
|
|
8
|
-
export const FileUploadField: React.FC<FieldProps> = observer(({ node, path, isEditing }) => {
|
|
8
|
+
export const FileUploadField: React.FC<FieldProps> = observer(({ node, path, isEditing, isReadOnly }) => {
|
|
9
9
|
const formStore = useFormStore();
|
|
10
10
|
const { name, label } = node.props || {};
|
|
11
11
|
const fullPath = path && name ? `${path}.${name}` : name;
|
|
@@ -19,6 +19,19 @@ export const FileUploadField: React.FC<FieldProps> = observer(({ node, path, isE
|
|
|
19
19
|
}
|
|
20
20
|
};
|
|
21
21
|
|
|
22
|
+
if (isReadOnly) {
|
|
23
|
+
return (
|
|
24
|
+
<Box mb={1}>
|
|
25
|
+
<Typography variant="caption" color="text.secondary" display="block">
|
|
26
|
+
{label || ''}
|
|
27
|
+
</Typography>
|
|
28
|
+
<Typography variant="body1" sx={{ minHeight: '1.5em', borderBottom: '1px solid #eee', pb: 0.5 }}>
|
|
29
|
+
{value || '-'}
|
|
30
|
+
</Typography>
|
|
31
|
+
</Box>
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
|
|
22
35
|
return (
|
|
23
36
|
<Box mb={1}>
|
|
24
37
|
<Typography variant="caption" color="text.secondary" display="block" mb={0.2} ml={0.5}>
|
|
@@ -4,7 +4,7 @@ import { observer } from 'mobx-react-lite';
|
|
|
4
4
|
import { useFormStore } from '../../store/FormStoreContext';
|
|
5
5
|
import type { FieldProps } from '../FieldRegistry';
|
|
6
6
|
|
|
7
|
-
export const LabelField: React.FC<FieldProps> = observer(({ node }) => {
|
|
7
|
+
export const LabelField: React.FC<FieldProps> = observer(({ node, isReadOnly: _isReadOnly }) => {
|
|
8
8
|
const formStore = useFormStore();
|
|
9
9
|
const { name, label = '', align = 'left', variant = 'body1' } = node.props || {};
|
|
10
10
|
|
|
@@ -4,7 +4,7 @@ import { observer } from 'mobx-react-lite';
|
|
|
4
4
|
import { useFormStore } from '../../store/FormStoreContext';
|
|
5
5
|
import type { FieldProps } from '../FieldRegistry';
|
|
6
6
|
|
|
7
|
-
export const NumberField: React.FC<FieldProps> = observer(({ node, path, isEditing }) => {
|
|
7
|
+
export const NumberField: React.FC<FieldProps> = observer(({ node, path, isEditing, isReadOnly }) => {
|
|
8
8
|
const formStore = useFormStore();
|
|
9
9
|
const { name, label } = node.props || {};
|
|
10
10
|
|
|
@@ -19,6 +19,19 @@ export const NumberField: React.FC<FieldProps> = observer(({ node, path, isEditi
|
|
|
19
19
|
}
|
|
20
20
|
};
|
|
21
21
|
|
|
22
|
+
if (isReadOnly) {
|
|
23
|
+
return (
|
|
24
|
+
<Box mb={1}>
|
|
25
|
+
<Typography variant="caption" color="text.secondary" display="block">
|
|
26
|
+
{label || ''}
|
|
27
|
+
</Typography>
|
|
28
|
+
<Typography variant="body1" sx={{ minHeight: '1.5em', borderBottom: '1px solid #eee', pb: 0.5 }}>
|
|
29
|
+
{value !== undefined && value !== null && value !== '' ? value.toString() : '-'}
|
|
30
|
+
</Typography>
|
|
31
|
+
</Box>
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
|
|
22
35
|
return (
|
|
23
36
|
<Box mb={0.5}>
|
|
24
37
|
<Typography variant="caption" color="text.secondary" display="block" mb={0.2} ml={0.5}>
|
|
@@ -1,40 +1,117 @@
|
|
|
1
|
-
import React from 'react';
|
|
1
|
+
import React, { useMemo } from 'react';
|
|
2
2
|
import { TextField as MuiTextField, Typography, Box } from '@mui/material';
|
|
3
3
|
import { observer } from 'mobx-react-lite';
|
|
4
4
|
import { useFormStore } from '../../store/FormStoreContext';
|
|
5
5
|
import type { FieldProps } from '../FieldRegistry';
|
|
6
|
+
import ReactQuill from 'react-quill-new';
|
|
7
|
+
import 'react-quill-new/dist/quill.snow.css';
|
|
6
8
|
|
|
7
|
-
export const RichTextField: React.FC<FieldProps> = observer(({ node, path, isEditing }) => {
|
|
9
|
+
export const RichTextField: React.FC<FieldProps> = observer(({ node, path, isEditing, isReadOnly }) => {
|
|
8
10
|
const formStore = useFormStore();
|
|
9
|
-
const { name, label, placeholder } = node.props || {};
|
|
11
|
+
const { name, label, placeholder, enableVisualEditor } = node.props || {};
|
|
10
12
|
const fullPath = path && name ? `${path}.${name}` : name;
|
|
11
|
-
const value = fullPath ? formStore.getValue(fullPath) : '';
|
|
13
|
+
const value = (fullPath ? formStore.getValue(fullPath) : '') || '';
|
|
12
14
|
const error = fullPath ? formStore.errors[fullPath] : undefined;
|
|
13
15
|
|
|
14
|
-
const handleChange = (
|
|
16
|
+
const handleChange = (val: string) => {
|
|
15
17
|
if (fullPath) {
|
|
16
|
-
formStore.updateField(fullPath,
|
|
18
|
+
formStore.updateField(fullPath, val, node.validation);
|
|
17
19
|
}
|
|
18
20
|
};
|
|
19
21
|
|
|
22
|
+
const modules = useMemo(() => ({
|
|
23
|
+
toolbar: [
|
|
24
|
+
['bold'],
|
|
25
|
+
[{ 'header': '3' }, { 'header': '4' }],
|
|
26
|
+
[{ 'list': 'ordered' }, { 'list': 'bullet' }],
|
|
27
|
+
['link'],
|
|
28
|
+
['clean']
|
|
29
|
+
],
|
|
30
|
+
}), []);
|
|
31
|
+
|
|
32
|
+
const formats = [
|
|
33
|
+
'header',
|
|
34
|
+
'bold',
|
|
35
|
+
'list', 'bullet',
|
|
36
|
+
'link'
|
|
37
|
+
];
|
|
38
|
+
|
|
39
|
+
if (isReadOnly) {
|
|
40
|
+
return (
|
|
41
|
+
<Box mb={2}>
|
|
42
|
+
<Typography variant="caption" color="text.secondary" display="block">
|
|
43
|
+
{label || ''}
|
|
44
|
+
</Typography>
|
|
45
|
+
<Box
|
|
46
|
+
sx={{
|
|
47
|
+
mt: 0.5,
|
|
48
|
+
p: 0,
|
|
49
|
+
'& h3': { m: '0.5em 0' },
|
|
50
|
+
'& h4': { m: '0.4em 0' },
|
|
51
|
+
'& ul, & ol': { pl: 2 }
|
|
52
|
+
}}
|
|
53
|
+
dangerouslySetInnerHTML={{ __html: value || '-' }}
|
|
54
|
+
/>
|
|
55
|
+
</Box>
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
|
|
20
59
|
return (
|
|
21
60
|
<Box mb={0.5}>
|
|
22
61
|
<Typography variant="caption" color="text.secondary" display="block" mb={0.2} ml={0.5}>
|
|
23
62
|
{label || (isEditing ? 'Rich Text' : '')} {isEditing ? `(${name})` : ''}
|
|
24
63
|
{node.validation?.required && <span style={{ color: 'red' }}> *</span>}
|
|
25
64
|
</Typography>
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
65
|
+
{enableVisualEditor ? (
|
|
66
|
+
<Box sx={{
|
|
67
|
+
'& .quill': {
|
|
68
|
+
bgcolor: 'background.paper',
|
|
69
|
+
borderRadius: 1,
|
|
70
|
+
border: theme => `1px solid ${error ? theme.palette.error.main : theme.palette.divider}`,
|
|
71
|
+
'& .ql-toolbar': {
|
|
72
|
+
borderTop: 'none',
|
|
73
|
+
borderLeft: 'none',
|
|
74
|
+
borderRight: 'none',
|
|
75
|
+
borderBottom: '1px solid',
|
|
76
|
+
borderColor: 'divider',
|
|
77
|
+
borderRadius: '4px 4px 0 0'
|
|
78
|
+
},
|
|
79
|
+
'& .ql-container': {
|
|
80
|
+
border: 'none',
|
|
81
|
+
minHeight: '120px',
|
|
82
|
+
fontSize: '0.875rem'
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}}>
|
|
86
|
+
<ReactQuill
|
|
87
|
+
theme="snow"
|
|
88
|
+
value={value}
|
|
89
|
+
onChange={handleChange}
|
|
90
|
+
modules={modules}
|
|
91
|
+
formats={formats}
|
|
92
|
+
placeholder={placeholder}
|
|
93
|
+
readOnly={isEditing || !!node.calculation?.formula}
|
|
94
|
+
/>
|
|
95
|
+
{error && (
|
|
96
|
+
<Typography variant="caption" color="error" sx={{ ml: 1.5, mt: 0.5, display: 'block' }}>
|
|
97
|
+
{error}
|
|
98
|
+
</Typography>
|
|
99
|
+
)}
|
|
100
|
+
</Box>
|
|
101
|
+
) : (
|
|
102
|
+
<MuiTextField
|
|
103
|
+
placeholder={placeholder}
|
|
104
|
+
fullWidth
|
|
105
|
+
multiline
|
|
106
|
+
minRows={4}
|
|
107
|
+
value={value}
|
|
108
|
+
onChange={(e) => handleChange(e.target.value)}
|
|
109
|
+
size="small"
|
|
110
|
+
disabled={isEditing || !!node.calculation?.formula}
|
|
111
|
+
error={!!error}
|
|
112
|
+
helperText={error}
|
|
113
|
+
/>
|
|
114
|
+
)}
|
|
38
115
|
</Box>
|
|
39
116
|
);
|
|
40
117
|
});
|
|
@@ -4,7 +4,7 @@ import { observer } from 'mobx-react-lite';
|
|
|
4
4
|
import { useFormStore } from '../../store/FormStoreContext';
|
|
5
5
|
import type { FieldProps } from '../FieldRegistry';
|
|
6
6
|
|
|
7
|
-
export const SelectField: React.FC<FieldProps> = observer(({ node, path, isEditing }) => {
|
|
7
|
+
export const SelectField: React.FC<FieldProps> = observer(({ node, path, isEditing, isReadOnly }) => {
|
|
8
8
|
const formStore = useFormStore();
|
|
9
9
|
const { name, label, options: staticOptions = [], optionsSource, enableAutocomplete = false, placeholder } = node.props || {};
|
|
10
10
|
|
|
@@ -22,6 +22,20 @@ export const SelectField: React.FC<FieldProps> = observer(({ node, path, isEditi
|
|
|
22
22
|
}
|
|
23
23
|
};
|
|
24
24
|
|
|
25
|
+
if (isReadOnly) {
|
|
26
|
+
const selectedOption = options.find((opt: any) => opt.value === value);
|
|
27
|
+
return (
|
|
28
|
+
<Box mb={1}>
|
|
29
|
+
<Typography variant="caption" color="text.secondary" display="block">
|
|
30
|
+
{label || ''}
|
|
31
|
+
</Typography>
|
|
32
|
+
<Typography variant="body1" sx={{ minHeight: '1.5em', borderBottom: '1px solid #eee', pb: 0.5 }}>
|
|
33
|
+
{selectedOption ? selectedOption.label : (value || '-')}
|
|
34
|
+
</Typography>
|
|
35
|
+
</Box>
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
|
|
25
39
|
const renderSelect = () => (
|
|
26
40
|
<FormControl fullWidth size="small" disabled={isEditing || !!node.calculation?.formula} error={!!error}>
|
|
27
41
|
<Select
|
|
@@ -4,7 +4,7 @@ import { observer } from 'mobx-react-lite';
|
|
|
4
4
|
import { useFormStore } from '../../store/FormStoreContext';
|
|
5
5
|
import type { FieldProps } from '../FieldRegistry';
|
|
6
6
|
|
|
7
|
-
export const TextField: React.FC<FieldProps> = observer(({ node, path, isEditing }) => {
|
|
7
|
+
export const TextField: React.FC<FieldProps> = observer(({ node, path, isEditing, isReadOnly }) => {
|
|
8
8
|
const formStore = useFormStore();
|
|
9
9
|
const { name, label, placeholder } = node.props || {};
|
|
10
10
|
|
|
@@ -19,6 +19,19 @@ export const TextField: React.FC<FieldProps> = observer(({ node, path, isEditing
|
|
|
19
19
|
}
|
|
20
20
|
};
|
|
21
21
|
|
|
22
|
+
if (isReadOnly) {
|
|
23
|
+
return (
|
|
24
|
+
<Box mb={1}>
|
|
25
|
+
<Typography variant="caption" color="text.secondary" display="block">
|
|
26
|
+
{label || ''}
|
|
27
|
+
</Typography>
|
|
28
|
+
<Typography variant="body1" sx={{ minHeight: '1.5em', borderBottom: '1px solid #eee', pb: 0.5 }}>
|
|
29
|
+
{value || '-'}
|
|
30
|
+
</Typography>
|
|
31
|
+
</Box>
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
|
|
22
35
|
return (
|
|
23
36
|
<Box mb={0.5}>
|
|
24
37
|
<Typography variant="caption" color="text.secondary" display="block" mb={0.2} ml={0.5}>
|
|
@@ -6,13 +6,13 @@ import { FormRenderer } from '../FormRenderer';
|
|
|
6
6
|
import { SortableContext, rectSortingStrategy } from '@dnd-kit/sortable';
|
|
7
7
|
import { LayoutPlaceholder } from './LayoutPlaceholder';
|
|
8
8
|
|
|
9
|
-
export const FormCol: React.FC<FieldProps> = observer(({ node, path, isEditing }) => {
|
|
9
|
+
export const FormCol: React.FC<FieldProps> = observer(({ node, path, isEditing, isReadOnly }) => {
|
|
10
10
|
const childrenIds = node.children?.map(c => c.id) || [];
|
|
11
11
|
|
|
12
12
|
const content = (
|
|
13
13
|
<Box display="flex" flexDirection="column" gap={0.5} width="100%" sx={{ flexGrow: 1, height: '100%' }}>
|
|
14
14
|
{node.children?.map(child => (
|
|
15
|
-
<FormRenderer key={child.id} node={child} path={path} isEditing={isEditing} />
|
|
15
|
+
<FormRenderer key={child.id} node={child} path={path} isEditing={isEditing} isReadOnly={isReadOnly} />
|
|
16
16
|
))}
|
|
17
17
|
</Box>
|
|
18
18
|
);
|
|
@@ -5,7 +5,7 @@ import { observer } from 'mobx-react-lite';
|
|
|
5
5
|
import { FormChildrenRenderer } from '../builder/FormChildrenRenderer';
|
|
6
6
|
import { LayoutPlaceholder } from './LayoutPlaceholder';
|
|
7
7
|
|
|
8
|
-
export const FormPaper: React.FC<FieldProps> = observer(({ node, path, isEditing }) => {
|
|
8
|
+
export const FormPaper: React.FC<FieldProps> = observer(({ node, path, isEditing, isReadOnly }) => {
|
|
9
9
|
const { label, elevation: elevationProp, padding = 2, variant: variantProp } = node.props || {};
|
|
10
10
|
const childrenIds = node.children?.map(c => c.id) || [];
|
|
11
11
|
|
|
@@ -21,6 +21,7 @@ export const FormPaper: React.FC<FieldProps> = observer(({ node, path, isEditing
|
|
|
21
21
|
isEditing={!!isEditing}
|
|
22
22
|
path={path}
|
|
23
23
|
layout="horizontal"
|
|
24
|
+
isReadOnly={isReadOnly}
|
|
24
25
|
/>
|
|
25
26
|
);
|
|
26
27
|
|
|
@@ -8,7 +8,7 @@ import type { FieldProps } from '../FieldRegistry';
|
|
|
8
8
|
import { LayoutPlaceholder } from './LayoutPlaceholder';
|
|
9
9
|
import { FormChildrenRenderer } from '../builder/FormChildrenRenderer';
|
|
10
10
|
|
|
11
|
-
export const FormRepeater: React.FC<FieldProps> = observer(({ node, path, isEditing }) => {
|
|
11
|
+
export const FormRepeater: React.FC<FieldProps> = observer(({ node, path, isEditing, isReadOnly }) => {
|
|
12
12
|
const formStore = useFormStore();
|
|
13
13
|
const { name, label, addLabel = 'Add Item' } = node.props || {};
|
|
14
14
|
const fullPath = path && name ? `${path}.${name}` : name;
|
|
@@ -81,7 +81,7 @@ export const FormRepeater: React.FC<FieldProps> = observer(({ node, path, isEdit
|
|
|
81
81
|
{displayItems.map((_item, index) => (
|
|
82
82
|
<Paper key={index} variant="outlined" sx={{ p: 1, mb: 0.5, position: 'relative' }}>
|
|
83
83
|
<Box position="absolute" right={4} top={4} zIndex={5}>
|
|
84
|
-
<IconButton size="small" onClick={() => handleRemove(index)} color="error" disabled={isEditing}>
|
|
84
|
+
<IconButton size="small" onClick={() => handleRemove(index)} color="error" disabled={isEditing || isReadOnly}>
|
|
85
85
|
<DeleteIcon fontSize="inherit" />
|
|
86
86
|
</IconButton>
|
|
87
87
|
</Box>
|
|
@@ -100,6 +100,7 @@ export const FormRepeater: React.FC<FieldProps> = observer(({ node, path, isEdit
|
|
|
100
100
|
isEditing={true}
|
|
101
101
|
layout="horizontal"
|
|
102
102
|
path={`${fullPath}[${index}]`}
|
|
103
|
+
isReadOnly={isReadOnly}
|
|
103
104
|
/>
|
|
104
105
|
</LayoutPlaceholder>
|
|
105
106
|
) : (
|
|
@@ -109,13 +110,14 @@ export const FormRepeater: React.FC<FieldProps> = observer(({ node, path, isEdit
|
|
|
109
110
|
isEditing={false}
|
|
110
111
|
layout="horizontal"
|
|
111
112
|
path={`${fullPath}[${index}]`}
|
|
113
|
+
isReadOnly={isReadOnly}
|
|
112
114
|
/>
|
|
113
115
|
)}
|
|
114
116
|
</Box>
|
|
115
117
|
</Paper>
|
|
116
118
|
))}
|
|
117
119
|
|
|
118
|
-
{!isEditing && (
|
|
120
|
+
{!isEditing && !isReadOnly && (
|
|
119
121
|
<Button
|
|
120
122
|
startIcon={<AddIcon />}
|
|
121
123
|
variant="outlined"
|
|
@@ -4,7 +4,7 @@ import type { FieldProps } from '../FieldRegistry';
|
|
|
4
4
|
import { LayoutPlaceholder } from './LayoutPlaceholder';
|
|
5
5
|
import { FormChildrenRenderer } from '../builder/FormChildrenRenderer';
|
|
6
6
|
|
|
7
|
-
export const FormRow: React.FC<FieldProps> = observer(({ node, path, isEditing }) => {
|
|
7
|
+
export const FormRow: React.FC<FieldProps> = observer(({ node, path, isEditing, isReadOnly }) => {
|
|
8
8
|
const childrenIds = node.children?.map(c => c.id) || [];
|
|
9
9
|
|
|
10
10
|
if (isEditing) {
|
|
@@ -16,6 +16,7 @@ export const FormRow: React.FC<FieldProps> = observer(({ node, path, isEditing }
|
|
|
16
16
|
isEditing={true}
|
|
17
17
|
layout="horizontal"
|
|
18
18
|
path={path}
|
|
19
|
+
isReadOnly={isReadOnly}
|
|
19
20
|
/>
|
|
20
21
|
</LayoutPlaceholder>
|
|
21
22
|
);
|
|
@@ -28,11 +29,12 @@ export const FormRow: React.FC<FieldProps> = observer(({ node, path, isEditing }
|
|
|
28
29
|
isEditing={false}
|
|
29
30
|
layout="horizontal"
|
|
30
31
|
path={path}
|
|
32
|
+
isReadOnly={isReadOnly}
|
|
31
33
|
/>
|
|
32
34
|
);
|
|
33
35
|
});
|
|
34
36
|
|
|
35
|
-
export const FormCol: React.FC<FieldProps> = observer(({ node, path, isEditing }) => {
|
|
37
|
+
export const FormCol: React.FC<FieldProps> = observer(({ node, path, isEditing, isReadOnly }) => {
|
|
36
38
|
const childrenIds = node.children?.map(c => c.id) || [];
|
|
37
39
|
|
|
38
40
|
if (isEditing) {
|
|
@@ -44,6 +46,7 @@ export const FormCol: React.FC<FieldProps> = observer(({ node, path, isEditing }
|
|
|
44
46
|
isEditing={true}
|
|
45
47
|
layout="vertical"
|
|
46
48
|
path={path}
|
|
49
|
+
isReadOnly={isReadOnly}
|
|
47
50
|
/>
|
|
48
51
|
</LayoutPlaceholder>
|
|
49
52
|
);
|
|
@@ -56,6 +59,7 @@ export const FormCol: React.FC<FieldProps> = observer(({ node, path, isEditing }
|
|
|
56
59
|
isEditing={false}
|
|
57
60
|
layout="vertical"
|
|
58
61
|
path={path}
|
|
62
|
+
isReadOnly={isReadOnly}
|
|
59
63
|
/>
|
|
60
64
|
);
|
|
61
65
|
});
|
|
@@ -4,7 +4,7 @@ import type { FieldProps } from '../FieldRegistry';
|
|
|
4
4
|
import { FormChildrenRenderer } from '../builder/FormChildrenRenderer';
|
|
5
5
|
import { LayoutPlaceholder } from './LayoutPlaceholder';
|
|
6
6
|
|
|
7
|
-
export const FormTab: React.FC<FieldProps> = observer(({ node, path, isEditing }) => {
|
|
7
|
+
export const FormTab: React.FC<FieldProps> = observer(({ node, path, isEditing, isReadOnly }) => {
|
|
8
8
|
const childrenIds = node.children?.map(c => c.id) || [];
|
|
9
9
|
|
|
10
10
|
const content = (
|
|
@@ -14,6 +14,7 @@ export const FormTab: React.FC<FieldProps> = observer(({ node, path, isEditing }
|
|
|
14
14
|
isEditing={!!isEditing}
|
|
15
15
|
path={path}
|
|
16
16
|
layout="vertical"
|
|
17
|
+
isReadOnly={isReadOnly}
|
|
17
18
|
/>
|
|
18
19
|
);
|
|
19
20
|
|
|
@@ -5,7 +5,7 @@ import type { FieldProps } from '../FieldRegistry';
|
|
|
5
5
|
import { FormRenderer } from '../FormRenderer';
|
|
6
6
|
import { LayoutPlaceholder } from './LayoutPlaceholder';
|
|
7
7
|
|
|
8
|
-
export const FormTable: React.FC<FieldProps> = observer(({ node, path, isEditing }) => {
|
|
8
|
+
export const FormTable: React.FC<FieldProps> = observer(({ node, path, isEditing, isReadOnly }) => {
|
|
9
9
|
const { rows = 2, cols = 2 } = node.props || {};
|
|
10
10
|
|
|
11
11
|
console.log(`FormTable Render: ${node.id}`, {
|
|
@@ -28,7 +28,7 @@ export const FormTable: React.FC<FieldProps> = observer(({ node, path, isEditing
|
|
|
28
28
|
<Box sx={gridStyle}>
|
|
29
29
|
{node.children?.map((child, index) => (
|
|
30
30
|
<Box key={child.id} sx={{ width: '100%', height: '100%' }}>
|
|
31
|
-
<FormRenderer node={child} path={`${path}[${index}]`} isEditing={isEditing} />
|
|
31
|
+
<FormRenderer node={child} path={`${path}[${index}]`} isEditing={isEditing} isReadOnly={isReadOnly} />
|
|
32
32
|
</Box>
|
|
33
33
|
))}
|
|
34
34
|
</Box>
|
|
@@ -4,7 +4,7 @@ import type { FieldProps } from '../FieldRegistry';
|
|
|
4
4
|
import { LayoutPlaceholder } from './LayoutPlaceholder';
|
|
5
5
|
import { FormChildrenRenderer } from '../builder/FormChildrenRenderer';
|
|
6
6
|
|
|
7
|
-
export const FormTableCell: React.FC<FieldProps> = observer(({ node, path, isEditing }) => {
|
|
7
|
+
export const FormTableCell: React.FC<FieldProps> = observer(({ node, path, isEditing, isReadOnly }) => {
|
|
8
8
|
const childrenIds = node.children?.map(c => c.id) || [];
|
|
9
9
|
const isEmpty = childrenIds.length === 0;
|
|
10
10
|
|
|
@@ -30,6 +30,7 @@ export const FormTableCell: React.FC<FieldProps> = observer(({ node, path, isEdi
|
|
|
30
30
|
isEditing={true}
|
|
31
31
|
layout="vertical"
|
|
32
32
|
path={path}
|
|
33
|
+
isReadOnly={isReadOnly}
|
|
33
34
|
/>
|
|
34
35
|
</LayoutPlaceholder>
|
|
35
36
|
);
|
|
@@ -42,6 +43,7 @@ export const FormTableCell: React.FC<FieldProps> = observer(({ node, path, isEdi
|
|
|
42
43
|
isEditing={false}
|
|
43
44
|
layout="vertical"
|
|
44
45
|
path={path}
|
|
46
|
+
isReadOnly={isReadOnly}
|
|
45
47
|
/>
|
|
46
48
|
);
|
|
47
49
|
});
|
|
@@ -6,7 +6,7 @@ import { FormRenderer } from '../FormRenderer';
|
|
|
6
6
|
import { useFormStore } from '../../store/FormStoreContext';
|
|
7
7
|
import { LayoutPlaceholder } from './LayoutPlaceholder';
|
|
8
8
|
|
|
9
|
-
export const FormTabs: React.FC<FieldProps> = observer(({ node, path, isEditing }) => {
|
|
9
|
+
export const FormTabs: React.FC<FieldProps> = observer(({ node, path, isEditing, isReadOnly }) => {
|
|
10
10
|
const formStore = useFormStore();
|
|
11
11
|
const [activeTab, setActiveTab] = React.useState(0);
|
|
12
12
|
const allTabs = node.children || [];
|
|
@@ -70,7 +70,7 @@ export const FormTabs: React.FC<FieldProps> = observer(({ node, path, isEditing
|
|
|
70
70
|
<Box />
|
|
71
71
|
</LayoutPlaceholder>
|
|
72
72
|
) : (
|
|
73
|
-
currentTabNode && <FormRenderer key={currentTabNode.id} node={currentTabNode} path={path} isEditing={isEditing} />
|
|
73
|
+
currentTabNode && <FormRenderer key={currentTabNode.id} node={currentTabNode} path={path} isEditing={isEditing} isReadOnly={isReadOnly} />
|
|
74
74
|
)}
|
|
75
75
|
</Box>
|
|
76
76
|
</Paper>
|
|
@@ -45,7 +45,7 @@ import React from 'react';
|
|
|
45
45
|
const BUILT_IN_FIELD_PLUGINS: FieldPlugin[] = [
|
|
46
46
|
{ type: 'text', label: 'Text Field', icon: React.createElement(TextFieldsIcon), category: 'field', defaultProps: { label: 'New Text Field', width: 6 }, component: TextField },
|
|
47
47
|
{ type: 'number', label: 'Number Field', icon: React.createElement(NumbersIcon), category: 'field', defaultProps: { label: 'New Number Field', width: 6 }, component: NumberField },
|
|
48
|
-
{ type: 'checkbox', label: 'Checkbox', icon: React.createElement(CheckBoxIcon), category: 'field', defaultProps: { label: 'New Checkbox', width: 6 }, component: CheckboxField },
|
|
48
|
+
{ type: 'checkbox', label: 'Checkbox', icon: React.createElement(CheckBoxIcon), category: 'field', defaultProps: { label: 'New Checkbox', width: 6, trueText: 'Да', falseText: 'Нет' }, component: CheckboxField },
|
|
49
49
|
{ type: 'select', label: 'Select', icon: React.createElement(ArrowDropDownCircleIcon), category: 'field', defaultProps: { label: 'New Select', width: 6, options: [{ label: 'Option 1', value: '1' }] }, component: SelectField },
|
|
50
50
|
{ type: 'date', label: 'Date Picker', icon: React.createElement(CalendarTodayIcon), category: 'field', defaultProps: { label: 'New Date', width: 6 }, component: DateField },
|
|
51
51
|
{ type: 'file', label: 'File Upload', icon: React.createElement(CloudUploadIcon), category: 'field', defaultProps: { label: 'New File Upload', width: 6 }, component: FileUploadField },
|