1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160 | 1×
1×
1×
1×
1×
1×
1×
1×
1×
1×
373×
373×
336×
373×
1×
49×
49×
49×
20×
29×
186×
522×
522×
522×
522×
522×
522×
96×
96×
96×
96×
96×
351×
277×
351×
277×
96×
81×
8×
81×
1×
81×
81×
96×
45×
45×
96×
96×
96×
95×
22×
22×
95×
95×
95×
95×
95×
27×
27×
96×
1×
96×
426×
49×
49×
49×
49×
49×
49×
49×
49×
49×
38×
49×
49×
377×
377×
377×
199×
199×
199×
199×
199×
199×
199×
199×
199×
186×
186×
5×
186×
186×
186×
186×
199×
199×
199×
377×
377×
377×
377×
377×
377×
377×
377×
| import createOnBlur from './events/createOnBlur';
import createOnChange from './events/createOnChange';
import createOnDragStart from './events/createOnDragStart';
import createOnDrop from './events/createOnDrop';
import createOnFocus from './events/createOnFocus';
import silencePromise from './silencePromise';
import read from './read';
import updateField from './updateField';
import isChecked from './isChecked';
function getSuffix(input, closeIndex) {
let suffix = input.substring(closeIndex + 1);
if (suffix[0] === '.') {
suffix = suffix.substring(1);
}
return suffix;
}
const getNextKey = path => {
const dotIndex = path.indexOf('.');
const openIndex = path.indexOf('[');
if (openIndex > 0 && (dotIndex < 0 || openIndex < dotIndex)) {
return path.substring(0, openIndex);
}
return dotIndex > 0 ? path.substring(0, dotIndex) : path;
};
const shouldAsyncValidate = (name, asyncBlurFields) =>
// remove array indices
~asyncBlurFields.indexOf(name.replace(/\[[0-9]+\]/g, '[]'));
const readField = (state, fieldName, pathToHere = '', fields, syncErrors, asyncValidate, isReactNative, props, callback = () => null, prefix = '') => {
const {asyncBlurFields, autofill, blur, change, focus, form, initialValues, readonly, addArrayValue,
removeArrayValue, swapArrayValues} = props;
const dotIndex = fieldName.indexOf('.');
const openIndex = fieldName.indexOf('[');
const closeIndex = fieldName.indexOf(']');
Iif (openIndex > 0 && closeIndex !== openIndex + 1) {
throw new Error('found [ not followed by ]');
}
if (openIndex > 0 && (dotIndex < 0 || openIndex < dotIndex)) E{
// array field
const key = fieldName.substring(0, openIndex);
const rest = getSuffix(fieldName, closeIndex);
const stateArray = state && state[key] || [];
const fullPrefix = prefix + fieldName.substring(0, closeIndex + 1);
const subfields = props.fields
.reduce((accumulator, field) => {
if (field.indexOf(fullPrefix) === 0) {
accumulator.push(field);
}
return accumulator;
}, [])
.map(field => getSuffix(field, prefix.length + closeIndex));
const addMethods = dest => {
Object.defineProperty(dest, 'addField', {
value: (value, index) => addArrayValue(pathToHere + key, value, index, subfields)
});
Object.defineProperty(dest, 'removeField', {
value: index => removeArrayValue(pathToHere + key, index)
});
Object.defineProperty(dest, 'swapFields', {
value: (indexA, indexB) => swapArrayValues(pathToHere + key, indexA, indexB)
});
return dest;
};
if (!fields[key] || fields[key].length !== stateArray.length) {
fields[key] = fields[key] ? [...fields[key]] : [];
addMethods(fields[key]);
}
const fieldArray = fields[key];
let changed = false;
stateArray.forEach((fieldState, index) => {
if (rest && !fieldArray[index]) {
fieldArray[index] = {};
changed = true;
}
const dest = rest ? fieldArray[index] : {};
const nextPath = `${pathToHere}${key}[${index}]${rest ? '.' : ''}`;
const nextPrefix = `${prefix}${key}[]${rest ? '.' : ''}`;
const result = readField(fieldState, rest, nextPath, dest, syncErrors,
asyncValidate, isReactNative, props, callback, nextPrefix);
if (!rest && fieldArray[index] !== result) {
// if nothing after [] in field name, assign directly to array
fieldArray[index] = result;
changed = true;
}
});
if (fieldArray.length > stateArray.length) {
// remove extra items that aren't in state array
fieldArray.splice(stateArray.length, fieldArray.length - stateArray.length);
}
return changed ? addMethods([...fieldArray]) : fieldArray;
}
if (dotIndex > 0) {
// subobject field
const key = fieldName.substring(0, dotIndex);
const rest = fieldName.substring(dotIndex + 1);
let subobject = fields[key] || {};
const nextPath = pathToHere + key + '.';
const nextKey = getNextKey(rest);
const nextPrefix = prefix + key + '.';
const previous = subobject[nextKey];
const result = readField(state[key] || {}, rest, nextPath, subobject, syncErrors, asyncValidate,
isReactNative, props, callback, nextPrefix);
if (result !== previous) {
subobject = {
...subobject,
[nextKey]: result
};
}
fields[key] = subobject;
return subobject;
}
const name = pathToHere + fieldName;
const field = fields[fieldName] || {};
if (field.name !== name) {
const onChange = createOnChange(name, change, isReactNative);
const initialFormValue = read(`${name}.initial`, form);
let initialValue = initialFormValue || read(name, initialValues);
initialValue = initialValue === undefined ? '' : initialValue;
field.name = name;
field.checked = isChecked(initialValue);
field.value = initialValue;
field.initialValue = initialValue;
if (!readonly) {
field.autofill = value => autofill(name, value);
field.onBlur = createOnBlur(name, blur, isReactNative,
shouldAsyncValidate(name, asyncBlurFields) &&
((blurName, blurValue) => silencePromise(asyncValidate(blurName, blurValue))));
field.onChange = onChange;
field.onDragStart = createOnDragStart(name, () => field.value);
field.onDrop = createOnDrop(name, change);
field.onFocus = createOnFocus(name, focus);
field.onUpdate = onChange; // alias to support belle. https://github.com/nikgraf/belle/issues/58
}
field.valid = true;
field.invalid = false;
Object.defineProperty(field, '_isField', {value: true});
}
const defaultFieldState = {
initial: field.value,
value: field.value,
};
const fieldState = (fieldName ? state[fieldName] : state) || defaultFieldState;
const syncError = read(name, syncErrors);
const updated = updateField(field, fieldState, name === form._active, syncError);
Eif (fieldName || fields[fieldName] !== updated) {
fields[fieldName] = updated;
}
callback(updated);
return updated;
};
export default readField;
|