1 | import React, { useRef, useEffect, useMemo } from 'react';
|
2 | import { usePrevious, useDebounce } from './hooks';
|
3 | import PropTypes from 'prop-types';
|
4 | import { isDeepEqual, combineSchema } from './base/utils';
|
5 | import { asField, DefaultFieldUI } from './base/asField';
|
6 | import parse from './base/parser';
|
7 | import resolve from './base/resolve';
|
8 | import { getValidateList } from './base/validate';
|
9 | import fetcher from './HOC/fetcher';
|
10 | import './atom.css';
|
11 | import './index.css';
|
12 |
|
13 | function RenderField({ fields, onChange, ...settings }) {
|
14 | const { Field, props } = parse(settings, fields);
|
15 | if (!Field) {
|
16 | return null;
|
17 | }
|
18 | return (
|
19 | <Field
|
20 | isRoot
|
21 | {...props}
|
22 | value={settings.data}
|
23 | onChange={onChange}
|
24 | formData={settings.formData}
|
25 | />
|
26 | );
|
27 | }
|
28 |
|
29 |
|
30 | const Wrapper = ({
|
31 | schema,
|
32 | propsSchema = {},
|
33 | uiSchema = {},
|
34 | readOnly,
|
35 | showValidate,
|
36 | ...rest
|
37 | }) => {
|
38 | let _schema = {};
|
39 | const jsonSchema = schema || propsSchema;
|
40 |
|
41 | _schema = combineSchema(jsonSchema, uiSchema);
|
42 |
|
43 | return (
|
44 | <FormRender
|
45 | readOnly={readOnly}
|
46 | showValidate={!readOnly && showValidate}
|
47 | {...rest}
|
48 | schema={_schema}
|
49 | />
|
50 | );
|
51 | };
|
52 |
|
53 | function FormRender({
|
54 | name = '$form',
|
55 | column = 1,
|
56 | className,
|
57 | schema = {},
|
58 | formData = {},
|
59 | widgets = {},
|
60 | FieldUI = DefaultFieldUI,
|
61 | fields = {},
|
62 | mapping = {},
|
63 | showDescIcon = false,
|
64 | showValidate = true,
|
65 | displayType = 'column',
|
66 | onChange = () => {},
|
67 | onValidate = () => {},
|
68 | onMount,
|
69 | readOnly = false,
|
70 | labelWidth = 110,
|
71 | useLogger = false,
|
72 | }) {
|
73 | const isUserInput = useRef(false);
|
74 | const originWidgets = useRef();
|
75 | const generatedFields = useRef({});
|
76 | const previousSchema = usePrevious(schema);
|
77 | const previousData = usePrevious(formData);
|
78 |
|
79 | const data = useMemo(() => resolve(schema, formData), [schema, formData]);
|
80 |
|
81 | useEffect(() => {
|
82 | if (onMount) {
|
83 | onMount(data);
|
84 | } else {
|
85 | onChange(data);
|
86 | }
|
87 | onValidate(getValidateList(data, schema));
|
88 | }, []);
|
89 |
|
90 | useEffect(() => {
|
91 | if (isUserInput.current) {
|
92 | isUserInput.current = false;
|
93 | return;
|
94 | }
|
95 | if (!isDeepEqual(previousSchema, schema)) {
|
96 | onChange(data);
|
97 | updateValidation();
|
98 | } else if (!isDeepEqual(previousData, formData)) {
|
99 | updateValidation();
|
100 | }
|
101 | }, [schema, formData]);
|
102 |
|
103 | const debouncedValidate = useDebounce(onValidate);
|
104 |
|
105 |
|
106 | const handleChange = (key, val) => {
|
107 | isUserInput.current = true;
|
108 | onChange(val);
|
109 | debouncedValidate(getValidateList(val, schema));
|
110 | };
|
111 |
|
112 | const updateValidation = () => {
|
113 | onValidate(getValidateList(data, schema));
|
114 | };
|
115 |
|
116 | const generated = {};
|
117 | if (!originWidgets.current) {
|
118 | originWidgets.current = widgets;
|
119 | }
|
120 | Object.keys(widgets).forEach(key => {
|
121 | const oWidget = originWidgets.current[key];
|
122 | const nWidget = widgets[key];
|
123 | let gField = generatedFields.current[key];
|
124 | if (!gField || oWidget !== nWidget) {
|
125 | if (oWidget !== nWidget) {
|
126 | originWidgets.current[key] = nWidget;
|
127 | }
|
128 | gField = asField({ FieldUI, Widget: nWidget });
|
129 | generatedFields.current[key] = gField;
|
130 | }
|
131 | generated[key] = gField;
|
132 | });
|
133 |
|
134 | const settings = {
|
135 | schema,
|
136 | data,
|
137 | name,
|
138 | column,
|
139 | showDescIcon,
|
140 | showValidate,
|
141 | displayType,
|
142 | readOnly,
|
143 | labelWidth,
|
144 | useLogger,
|
145 | formData: data,
|
146 | };
|
147 |
|
148 | const _fields = {
|
149 |
|
150 | generated,
|
151 |
|
152 | customized: fields,
|
153 |
|
154 | mapping,
|
155 | };
|
156 |
|
157 | return (
|
158 | <div className={`${className} fr-wrapper`}>
|
159 | <RenderField {...settings} fields={_fields} onChange={handleChange} />
|
160 | </div>
|
161 | );
|
162 | }
|
163 |
|
164 | FormRender.propTypes = {
|
165 | name: PropTypes.string,
|
166 | column: PropTypes.number,
|
167 | schema: PropTypes.object,
|
168 | formData: PropTypes.object,
|
169 | widgets: PropTypes.objectOf(PropTypes.func),
|
170 | FieldUI: PropTypes.elementType,
|
171 | fields: PropTypes.objectOf(PropTypes.element),
|
172 | mapping: PropTypes.object,
|
173 | showDescIcon: PropTypes.bool,
|
174 | showValidate: PropTypes.bool,
|
175 | displayType: PropTypes.string,
|
176 | onChange: PropTypes.func,
|
177 | onMount: PropTypes.func,
|
178 | onValidate: PropTypes.func,
|
179 | readOnly: PropTypes.bool,
|
180 | labelWidth: PropTypes.number,
|
181 | useLogger: PropTypes.bool,
|
182 | };
|
183 |
|
184 | export default fetcher(Wrapper);
|