@react-rx/form
Version:
rx form for react, use typescript for development
674 lines (587 loc) • 21 kB
JavaScript
;
Object.defineProperty(exports, '__esModule', { value: true });
var tslib = require('tslib');
var lodash = require('lodash');
var React = require('react');
var operators = require('rxjs/operators');
var isPropValid = require('@emotion/is-prop-valid');
var rxjs = require('rxjs');
function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
var React__default = /*#__PURE__*/_interopDefaultLegacy(React);
var isPropValid__default = /*#__PURE__*/_interopDefaultLegacy(isPropValid);
var isFieldDirty = function (value, defaultValue) {
return !lodash.isEqual(value, defaultValue);
};
var validateField = function (value, validate) {
if (lodash.isUndefined(validate)) {
return;
}
if (lodash.isArray(validate)) {
return combineValidators(validate)(value);
}
if (typeof validate === "function") {
return validate(value);
}
return;
};
var combineValidators = function (validators) {
return function (value) {
return lodash.reduce(validators, function (error, validator) {
return error || validateField(value, validator);
}, undefined);
};
};
var pickValue = function (evtOrValue) {
var isEvent = lodash.isObject(evtOrValue) && evtOrValue.target;
return isEvent ? evtOrValue.target.value : evtOrValue;
};
var FormContext = /*#__PURE__*/React.createContext({
subscribe: function (_) {},
dispatch: function (_) {},
subscribeFormAction: function (_) {},
updateFormValues: function (_) {},
getFormValues: function () {
return {};
},
getFormState: function () {
return {};
},
setErrors: function (_) {}
});
var Provider = FormContext.Provider,
Consumer = FormContext.Consumer;
exports.FieldActionTypes = void 0;
(function (FieldActionTypes) {
FieldActionTypes["register"] = "@@rx-form/field/REGISTER_FIELD";
FieldActionTypes["change"] = "@@rx-form/field/CHANGE";
FieldActionTypes["focus"] = "@@rx-form/field/FOCUS";
FieldActionTypes["blur"] = "@@rx-form/field/BLUR";
FieldActionTypes["destroy"] = "@@rx-form/field/DESTROY_FIELD";
})(exports.FieldActionTypes || (exports.FieldActionTypes = {}));
exports.FormActionTypes = void 0;
(function (FormActionTypes) {
FormActionTypes["startSubmit"] = "@@rx-form/form/START_SUBMIT";
FormActionTypes["endSubmit"] = "@@rx-form/form/END_SUBMIT";
})(exports.FormActionTypes || (exports.FormActionTypes = {}));
var log = function (_a) {
var action = _a.action,
prevState = _a.prevState,
nextState = _a.nextState;
if (process.env.NODE_ENV === "development") {
console.groupCollapsed(action.type + " " + new Date()); // use cloneDeep here in case state is a reference type
console.log("prevState", /*#__PURE__*/lodash.cloneDeep(prevState));
console.log("action", /*#__PURE__*/lodash.cloneDeep(action));
console.log("nextState", /*#__PURE__*/lodash.cloneDeep(nextState));
console.groupEnd();
}
};
var dropEmpty = function (values) {
return lodash.omitBy(values, isEmptyValue);
};
var isEmptyValue = function (value) {
if (lodash.isBoolean(value) || lodash.isFunction(value) || lodash.isNumber(value)) {
return false;
}
if (lodash.isArray(value) && value.length === 0) {
return true;
}
return lodash.isEmpty(value);
};
var pickInputPropsFromFieldProps = function (_a) {
var meta = _a.meta,
others = /*#__PURE__*/tslib.__rest(_a, ["meta"]);
return tslib.__assign( /*#__PURE__*/tslib.__assign({}, others), {
error: meta ? meta.error : undefined
});
};
var pickDOMAttrs = function (props) {
var o = {};
for (var k in props) {
if (props.hasOwnProperty(k) && isPropValid__default["default"](k)) {
o[k] = props[k];
}
}
return o;
};
var useValueRef = function (value) {
var ref = /*#__PURE__*/React.useRef(null);
ref.current = value;
return ref;
};
var getFieldValue = function (_a) {
var defaultValue = _a.defaultValue,
formValues = _a.formValues,
prefixedName = _a.prefixedName;
var initialValue = /*#__PURE__*/lodash.get(formValues, prefixedName);
if (!lodash.isUndefined(initialValue)) {
return initialValue;
}
return defaultValue;
};
function Field(props) {
var _a = /*#__PURE__*/React.useContext(FormContext),
dispatch = _a.dispatch,
subscribe = _a.subscribe,
subscribeFormAction = _a.subscribeFormAction,
getFormValues = _a.getFormValues,
fieldPrefix = _a.fieldPrefix;
var prefixedName = "" + (fieldPrefix || "") + props.name;
var defaultFieldValue = /*#__PURE__*/getFieldValue({
defaultValue: props.defaultValue,
formValues: /*#__PURE__*/getFormValues(),
prefixedName: prefixedName
});
var _b = /*#__PURE__*/React.useState(defaultFieldValue),
fieldValue = _b[0],
setFieldValue = _b[1];
var _c = /*#__PURE__*/React.useState({}),
fieldMeta = _c[0],
setFieldMeta = _c[1];
var fieldValueRef = /*#__PURE__*/useValueRef(fieldValue);
var fieldMetaRef = /*#__PURE__*/useValueRef(fieldMeta);
var propsRef = /*#__PURE__*/useValueRef(props);
var _d = /*#__PURE__*/React.useMemo(function () {
var parseValue = function (value) {
var _a = propsRef.current || {},
parse = _a.parse,
normalize = _a.normalize;
if (parse && typeof parse === "function") {
value = /*#__PURE__*/parse(value);
}
if (normalize && typeof normalize === "function") {
value = /*#__PURE__*/normalize(value);
}
return value;
};
var triggerChange = function (evtOrValue, otherMeta) {
var validate = (propsRef.current || {}).validate;
var value = /*#__PURE__*/parseValue( /*#__PURE__*/pickValue(evtOrValue));
var dirty = /*#__PURE__*/isFieldDirty(value, defaultFieldValue); // use `defaultValue` here previously
var meta = /*#__PURE__*/tslib.__assign( /*#__PURE__*/tslib.__assign({}, otherMeta), {
error: /*#__PURE__*/validateField(value, validate),
dirty: dirty
});
dispatch({
name: prefixedName,
type: exports.FieldActionTypes.change,
meta: meta,
payload: value
});
};
return {
registerField: function (_a) {
var value = _a.value,
meta = _a.meta;
dispatch({
name: prefixedName,
type: exports.FieldActionTypes.register,
meta: meta,
payload: /*#__PURE__*/parseValue(value)
});
},
onFocus: function () {
dispatch({
name: prefixedName,
type: exports.FieldActionTypes.focus,
meta: {
visited: true
}
});
},
triggerChange: triggerChange,
onChange: function (evtOrValue) {
return triggerChange(evtOrValue);
},
onBlur: function (evtOrValue) {
var value = /*#__PURE__*/pickValue(evtOrValue);
dispatch({
name: prefixedName,
type: exports.FieldActionTypes.blur,
meta: {
visited: true,
touched: true
},
payload: /*#__PURE__*/parseValue(value)
});
},
formatValue: function (value) {
var _a = propsRef.current || {},
format = _a.format,
normalize = _a.normalize;
if (format && typeof format === "function") {
value = /*#__PURE__*/format(value);
}
if (normalize && typeof normalize === "function") {
value = /*#__PURE__*/normalize(value);
}
return value;
}
};
}, []),
registerField = _d.registerField,
triggerChange = _d.triggerChange,
onFocus = _d.onFocus,
onChange = _d.onChange,
onBlur = _d.onBlur,
formatValue = _d.formatValue;
React.useLayoutEffect(function () {
var formStateSubscription = null;
var formActionSubscription = null;
var onFormStateChange = function () {
var formStateObserver$ = new rxjs.Subject();
formStateObserver$.pipe( /*#__PURE__*/operators.map(function (_a) {
var fields = _a.fields,
values = _a.values;
return {
meta: fields[prefixedName],
value: /*#__PURE__*/lodash.get(values, prefixedName)
};
}), /*#__PURE__*/operators.distinctUntilChanged(function (next, prev) {
return next.meta === prev.meta && prev.value === next.value;
}), /*#__PURE__*/operators.tap(function (_a) {
var meta = _a.meta,
value = _a.value;
if (meta || value) {
setFieldValue(value);
setFieldMeta(meta);
}
})).subscribe();
formStateSubscription = /*#__PURE__*/subscribe(formStateObserver$);
};
var onFormActionChange = function () {
var formActionObserver$ = new rxjs.Subject();
formActionObserver$.pipe( /*#__PURE__*/operators.filter(function (_a) {
var type = _a.type;
return type === exports.FormActionTypes.startSubmit;
}), /*#__PURE__*/operators.tap(function () {
return triggerChange(fieldValueRef.current, /*#__PURE__*/tslib.__assign( /*#__PURE__*/tslib.__assign({}, fieldMetaRef.current), {
visited: true,
touched: true
}));
})).subscribe();
formActionSubscription = /*#__PURE__*/subscribeFormAction(formActionObserver$);
}; // should register observers before register field, otherwise the last field will lost field state
// this cause the last field render even if I only changed the first field
onFormStateChange();
onFormActionChange();
registerField({
value: fieldValueRef.current,
meta: fieldMetaRef.current
});
return function () {
dispatch({
name: prefixedName,
type: exports.FieldActionTypes.destroy,
meta: {
destroyValueOnUnmount: (propsRef.current || {}).destroyValueOnUnmount
}
});
if (formStateSubscription) {
formStateSubscription.unsubscribe();
}
if (formActionSubscription) {
formActionSubscription.unsubscribe();
}
};
}, []);
return props.children({
value: /*#__PURE__*/formatValue(fieldValueRef.current),
meta: fieldMeta,
name: prefixedName,
onChange: onChange,
onFocus: onFocus,
onBlur: onBlur
});
}
function FieldArrayCore(props) {
var _a = /*#__PURE__*/React.useContext(FormContext),
getFormValues = _a.getFormValues,
updateFormValues = _a.updateFormValues;
var getFieldsByIdx = function () {
return lodash.map( /*#__PURE__*/lodash.get( /*#__PURE__*/getFormValues(), props.name), function (_, idx) {
return "[" + idx + "]";
});
};
var defaultFields = /*#__PURE__*/getFieldsByIdx();
var _b = /*#__PURE__*/React.useState(defaultFields),
fields = _b[0],
setFields = _b[1];
var _c = /*#__PURE__*/React.useMemo(function () {
return {
remove: function (idx) {
var formValues = /*#__PURE__*/getFormValues();
var newFieldArrayValues = /*#__PURE__*/lodash.filter( /*#__PURE__*/lodash.get(formValues, props.name), function (_, n) {
return n !== idx;
});
var nextFormValues = /*#__PURE__*/lodash.set(formValues, props.name, newFieldArrayValues);
updateFormValues(nextFormValues);
setFields(getFieldsByIdx);
},
add: function () {
var formValues = /*#__PURE__*/getFormValues(); // undefined 只是为了增加一个节点,让 Field 自己去渲染,然后 Field 渲染时才会把 key 注册给 form,得到新的包含 field 的 form value
var fieldValues = /*#__PURE__*/lodash.get(formValues, props.name, []).concat(undefined);
var nextFormValues = /*#__PURE__*/lodash.set(formValues, props.name, fieldValues);
updateFormValues(nextFormValues);
setFields(getFieldsByIdx);
},
each: function (mapper) {
var fieldValues = /*#__PURE__*/lodash.get( /*#__PURE__*/getFormValues(), props.name);
return lodash.map(fieldValues, function (_, idx) {
var name = "[" + idx + "]";
return mapper(name, idx);
});
}
};
}, []),
add = _c.add,
each = _c.each,
remove = _c.remove;
React.useLayoutEffect(function () {
var fieldArrayValues = /*#__PURE__*/lodash.get( /*#__PURE__*/getFormValues(), props.name);
if (props.initLength) {
lodash.times(props.initLength - lodash.size(fieldArrayValues), add);
}
return function () {};
}, []);
return props.children({
fields: fields,
add: add,
each: each,
remove: function (idx) {
return remove(idx);
}
});
}
var FieldArray = function (props) {
var formContext = /*#__PURE__*/React.useContext(FormContext);
var name = "" + (formContext.fieldPrefix || "") + props.name;
return React__default["default"].createElement(Provider, {
value: /*#__PURE__*/tslib.__assign( /*#__PURE__*/tslib.__assign({}, formContext), {
fieldPrefix: name
})
}, /*#__PURE__*/React__default["default"].createElement(FieldArrayCore, /*#__PURE__*/tslib.__assign({}, props, {
name: name
})));
};
var FormSection = function (_a) {
var name = _a.name,
children = _a.children;
var formContext = /*#__PURE__*/React.useContext(FormContext);
return React__default["default"].createElement(Provider, {
value: /*#__PURE__*/tslib.__assign( /*#__PURE__*/tslib.__assign({}, formContext), {
fieldPrefix: "" + (formContext.fieldPrefix || "") + name + "."
})
}, children);
};
var formUpdateField = function (state, action) {
var _a;
var fields = state.fields,
values = state.values;
var payload = action.payload,
_b = action.meta,
meta = _b === void 0 ? {} : _b,
name = action.name;
return {
fields: /*#__PURE__*/tslib.__assign( /*#__PURE__*/tslib.__assign({}, fields), (_a = {}, _a[name] = /*#__PURE__*/tslib.__assign( /*#__PURE__*/tslib.__assign({}, fields[name]), meta), _a)),
values: /*#__PURE__*/lodash.set( /*#__PURE__*/tslib.__assign({}, values), name, payload) // Notice: _.set will mutate object,
};
};
var formFocusField = function (state, action) {
var _a;
var fields = state.fields,
values = state.values;
var name = action.name,
meta = action.meta;
return {
values: values,
fields: /*#__PURE__*/tslib.__assign( /*#__PURE__*/tslib.__assign({}, fields), (_a = {}, _a[name] = /*#__PURE__*/tslib.__assign({}, meta), _a))
};
};
var formRemoveField = function (state, action) {
if (action.meta && action.meta.destroyValueOnUnmount) {
return {
fields: /*#__PURE__*/lodash.omit(state.fields, action.name),
values: /*#__PURE__*/lodash.omit(state.values, action.name)
};
} // keep values when field destroy for cross page form
// eg: in PageA, switch to PageB, should keep PageA fields values.
return tslib.__assign( /*#__PURE__*/tslib.__assign({}, state), {
fields: /*#__PURE__*/lodash.omit(state.fields, action.name)
});
};
var formUpdateValues = function (formState) {
return function (formValues) {
return {
fields: formState.fields,
values: formValues
};
};
};
var formSetErrors = function (errors, fields) {
if (lodash.isEmpty(errors)) {
return lodash.mapValues(fields, function (field) {
return tslib.__assign( /*#__PURE__*/tslib.__assign({}, field), {
error: undefined
});
});
}
var temp = {};
lodash.keys(errors).forEach(function (name) {
if (fields[name]) {
temp[name] = /*#__PURE__*/tslib.__assign( /*#__PURE__*/tslib.__assign({}, fields[name]), {
error: errors[name]
});
}
});
return tslib.__assign( /*#__PURE__*/tslib.__assign({}, fields), temp);
};
var isFormValid = function (fields) {
return lodash.reduce(fields, function (result, item) {
return result && item && !item.error;
}, true);
};
function RxForm(props) {
var _a = /*#__PURE__*/React.useMemo(function () {
var formStateSubject$ = new rxjs.BehaviorSubject({
fields: {},
values: lodash.cloneDeep(props.initialValues) || {}
});
var formActionSubject$ = new rxjs.Subject();
var dispatch = function (action) {
var prevState = /*#__PURE__*/(process.env.NODE_ENV === "development" ? lodash.cloneDeep : function (v) {
return v;
})( /*#__PURE__*/formStateSubject$.getValue());
switch (action.type) {
case exports.FieldActionTypes.register:
case exports.FieldActionTypes.blur:
case exports.FieldActionTypes.change:
{
var nextFormState = /*#__PURE__*/formUpdateField( /*#__PURE__*/formStateSubject$.getValue(), action);
formStateSubject$.next(nextFormState);
break;
}
case exports.FieldActionTypes.focus:
{
var nextFormState = /*#__PURE__*/formFocusField( /*#__PURE__*/formStateSubject$.getValue(), action);
formStateSubject$.next(nextFormState);
break;
}
case exports.FieldActionTypes.destroy:
{
var nextFormState = /*#__PURE__*/formRemoveField( /*#__PURE__*/formStateSubject$.getValue(), action);
formStateSubject$.next(nextFormState);
break;
}
case exports.FormActionTypes.startSubmit:
{
notifyFormActionChange(action);
break;
}
}
var nextState = /*#__PURE__*/(process.env.NODE_ENV === "development" ? lodash.cloneDeep : function (v) {
return v;
})( /*#__PURE__*/formStateSubject$.getValue());
log({
action: action,
prevState: prevState,
nextState: nextState
});
};
var notifyFormActionChange = function (action) {
formActionSubject$.next(action);
};
var setErrors = function (errors) {
var fromState = /*#__PURE__*/formStateSubject$.getValue();
var nextFormState = {
values: fromState.values,
fields: /*#__PURE__*/formSetErrors(errors, fromState.fields)
};
formStateSubject$.next(nextFormState);
};
var getFormValues = function () {
return formStateSubject$.getValue().values;
};
var getFormState = function () {
return formStateSubject$.getValue();
};
return {
subscribe: function (observer) {
return formStateSubject$.subscribe(observer);
},
dispatch: dispatch,
subscribeFormAction: function (observer) {
return formActionSubject$.subscribe(observer);
},
updateFormValues: function (formValues) {
formUpdateValues( /*#__PURE__*/formStateSubject$.getValue())(formValues);
},
getFormValues: getFormValues,
getFormState: getFormState,
setErrors: setErrors,
handleSubmit: function (onSubmit) {
return function (evt) {
evt.preventDefault();
dispatch({
type: exports.FormActionTypes.startSubmit
});
if (!isFormValid(formStateSubject$.getValue().fields)) {
return;
}
onSubmit( /*#__PURE__*/getFormValues(), setErrors);
dispatch({
type: exports.FormActionTypes.endSubmit
});
};
}
};
}, []),
handleSubmit = _a.handleSubmit,
formCtxValue = /*#__PURE__*/tslib.__rest(_a, ["handleSubmit"]);
return React__default["default"].createElement(Provider, {
value: formCtxValue
}, /*#__PURE__*/props.children({
handleSubmit: handleSubmit
}));
}
function FormValues(props) {
var _a = /*#__PURE__*/React.useContext(FormContext),
updateFormValues = _a.updateFormValues,
getFormValues = _a.getFormValues,
subscribe = _a.subscribe;
var defaultFormValues = /*#__PURE__*/getFormValues();
var _b = /*#__PURE__*/React.useState(defaultFormValues),
formValues = _b[0],
setFormValues = _b[1];
React.useLayoutEffect(function () {
var subscription = null;
var formStateObserver$ = new rxjs.Subject();
formStateObserver$.pipe( /*#__PURE__*/operators.map(function (formState) {
return tslib.__assign({}, formState.values);
}), /*#__PURE__*/operators.distinctUntilChanged(lodash.isEqual), /*#__PURE__*/operators.tap(function (values) {
return setFormValues(values);
})).subscribe();
subscription = /*#__PURE__*/subscribe(formStateObserver$);
return function () {
if (subscription) {
subscription.unsubscribe();
subscription = null;
}
};
}, []);
return props.children({
formValues: formValues,
updateFormValues: updateFormValues
});
}
exports.Field = Field;
exports.FieldArray = FieldArray;
exports.FormSection = FormSection;
exports.FormValues = FormValues;
exports.RxForm = RxForm;
exports.WithForm = Consumer;
exports.dropEmpty = dropEmpty;
exports.isEmptyValue = isEmptyValue;
exports.log = log;
exports.pickDOMAttrs = pickDOMAttrs;
exports.pickInputPropsFromFieldProps = pickInputPropsFromFieldProps;
exports.useValueRef = useValueRef;