@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stormlmd/form-builder",
3
- "version": "0.3.1",
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
  }
@@ -12,6 +12,7 @@ export interface FieldProps {
12
12
  node: SchemaNode;
13
13
  path?: string; // current path for values, e.g. "addresses[0]"
14
14
  isEditing?: boolean;
15
+ isReadOnly?: boolean;
15
16
  }
16
17
 
17
18
  type ComponentType = React.FC<FieldProps>;
@@ -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" onClick={() => setHelpOpen(true)}>
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" onClick={() => setHelpOpen(true)}>
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" onClick={() => setHelpOpen(true)}>
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" onClick={() => setHelpOpen(true)}>
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
- <FormulaHelp open={helpOpen} onClose={() => setHelpOpen(false)} />
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 = (e: React.ChangeEvent<HTMLInputElement>) => {
16
+ const handleChange = (val: string) => {
15
17
  if (fullPath) {
16
- formStore.updateField(fullPath, e.target.value, node.validation);
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
- <MuiTextField
27
- placeholder={placeholder}
28
- fullWidth
29
- multiline
30
- minRows={4}
31
- value={value}
32
- onChange={handleChange}
33
- size="small"
34
- disabled={isEditing || !!node.calculation?.formula}
35
- error={!!error}
36
- helperText={error}
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 },