UNPKG

@nutui/nutui-react

Version:

京东风格的轻量级移动端 React 组件库,支持一套代码生成 H5 和小程序

329 lines (328 loc) 11.8 kB
import React__default, { createContext, useRef } from "react"; import Cell__default from "./Cell.js"; import { C as ComponentDefaults } from "./typings.js"; import { a as __awaiter } from "./tslib.es6.js"; import Schema from "async-validator"; const Context = createContext({}); const SECRET = "NUT_FORM_INTERNAL"; class FormStore { constructor() { this.initialValues = {}; this.updateList = []; this.store = {}; this.fieldEntities = []; this.callbacks = {}; this.errors = {}; this.registerField = (field) => { this.fieldEntities.push(field); return () => { this.fieldEntities = this.fieldEntities.filter((item) => item !== field); if (this.store) { delete this.store[field.props.name]; } }; }; this.getFieldValue = (name) => { var _a; return (_a = this.store) === null || _a === void 0 ? void 0 : _a[name]; }; this.getFieldsValue = (nameList) => { if (typeof nameList === "boolean") { return JSON.parse(JSON.stringify(this.store)); } const fieldsValue = {}; nameList.forEach((field) => { fieldsValue[field] = this.getFieldValue(field); }); return fieldsValue; }; this.setInitialValues = (values, init) => { if (init) { this.initialValues = values; this.store = values; } }; this.setFieldsValue = (newStore, needValidate = true) => { this.store = Object.assign(Object.assign({}, this.store), newStore); this.fieldEntities.forEach((entity) => { const { name } = entity.props; Object.keys(newStore).forEach((key) => { if (key === name) { entity.onStoreChange("update"); } }); }); this.updateList.forEach((item) => { let shouldUpdate = item.condition; if (typeof item.condition === "function") { shouldUpdate = item.condition(); } if (shouldUpdate) { item.entity.onStoreChange("update"); } }); needValidate && this.validateFields(); }; this.setCallback = (callback) => { this.callbacks = Object.assign(Object.assign({}, this.callbacks), callback); }; this.validateFields = (nameList) => __awaiter(this, void 0, void 0, function* () { var _a; let filterEntitys = []; const errs = []; this.errors.length = 0; if (!nameList || nameList.length === 0) { filterEntitys = this.fieldEntities; } else { filterEntitys = this.fieldEntities.filter(({ props: { name } }) => nameList.includes(name)); } for (const entity of filterEntitys) { const { name, rules = [] } = entity.props; const descriptor = {}; if (rules.length) { if (rules.length > 1) { descriptor[name] = []; rules.forEach((v) => { descriptor[name].push(v); }); } else { descriptor[name] = rules[0]; } } const validator = new Schema(descriptor); try { yield validator.validate({ [name]: (_a = this.store) === null || _a === void 0 ? void 0 : _a[name] }); } catch ({ errors }) { if (errors) { errs.push(...errors); this.errors[name] = errors; } } finally { if (!errs || errs.length === 0) { this.errors[name] = []; } } entity.onStoreChange("validate"); } return errs; }); this.submit = () => __awaiter(this, void 0, void 0, function* () { var _a, _b, _c, _d; const errors = yield this.validateFields(); if (errors.length === 0) { (_b = (_a = this.callbacks).onFinish) === null || _b === void 0 ? void 0 : _b.call(_a, this.store); } else if (errors.length > 0) { (_d = (_c = this.callbacks).onFinishFailed) === null || _d === void 0 ? void 0 : _d.call(_c, this.store, errors); } }); this.resetFields = () => { this.errors.length = 0; this.store = this.initialValues; this.fieldEntities.forEach((entity) => { entity.onStoreChange("reset"); }); }; this.registerUpdate = (field, shouldUpdate) => { this.updateList.push({ entity: field, condition: shouldUpdate }); return () => { this.updateList = this.updateList.filter((i) => i.entity !== field); }; }; this.dispatch = ({ name }) => { this.validateFields([name]); }; this.getInternal = (key) => { if (key === SECRET) { return { registerField: this.registerField, setCallback: this.setCallback, setInitialValues: this.setInitialValues, dispatch: this.dispatch, store: this.store, fieldEntities: this.fieldEntities, registerUpdate: this.registerUpdate }; } }; this.getForm = () => { return { getFieldValue: this.getFieldValue, getFieldsValue: this.getFieldsValue, setFieldsValue: this.setFieldsValue, resetFields: this.resetFields, validateFields: this.validateFields, submit: this.submit, errors: this.errors, getInternal: this.getInternal }; }; this.callbacks = { onFinish: () => { }, onFinishFailed: () => { } }; } } const useForm = (form) => { const formRef = useRef(); if (!formRef.current) { if (form) { formRef.current = form; } else { const formStore = new FormStore(); formRef.current = formStore.getForm(); } } return [formRef.current]; }; function isForwardRefComponent(component) { return component.type && component.type.$$typeof && // eslint-disable-next-line react/display-name React__default.forwardRef( // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore () => { } ).$$typeof === component.type.$$typeof; } const defaultProps = Object.assign(Object.assign({}, ComponentDefaults), { required: false, name: "", label: "", rules: [{ required: false, message: "" }], errorMessageAlign: "left", validateTrigger: "onChange", shouldUpdate: false, noStyle: false }); class FormItem extends React__default.Component { constructor(props) { super(props); this.getControlled = (children) => { var _a; const { setFieldsValue, getFieldValue } = this.context; const { dispatch } = this.context.getInternal(SECRET); const { name = "" } = this.props; if ((_a = children === null || children === void 0 ? void 0 : children.props) === null || _a === void 0 ? void 0 : _a.defaultValue) { console.warn("通过 initialValue 设置初始值"); } const fieldValue = getFieldValue(name); const controlled = Object.assign(Object.assign({}, children.props), { [this.props.valuePropName || "value"]: fieldValue !== void 0 ? fieldValue : this.props.initialValue, [this.props.trigger || "onChange"]: (...args) => { const originOnChange = children.props[this.props.trigger || "onChange"]; if (originOnChange) { originOnChange(...args); } let [next] = args; if (this.props.getValueFromEvent) { next = this.props.getValueFromEvent(...args); } setFieldsValue({ [name]: next }, false); } }); const { validateTrigger } = this.props; let validateTriggers = [this.props.trigger || "onChange"]; if (validateTrigger) { validateTriggers = typeof validateTrigger === "string" ? [validateTrigger] : [...validateTrigger]; validateTriggers.forEach((trigger) => { const originTrigger = controlled[trigger]; controlled[trigger] = (...args) => { if (originTrigger) { originTrigger(...args); } if (this.props.rules && this.props.rules.length) { dispatch({ name: this.props.name }); } }; }); } if (isForwardRefComponent(children)) { controlled.ref = (componentInstance) => { const originRef = children.ref; if (originRef) { if (typeof originRef === "function") { originRef(componentInstance); } if ("current" in originRef) { originRef.current = componentInstance; } } this.componentRef = componentInstance; }; } return controlled; }; this.refresh = () => { this.setState(({ resetCount }) => ({ resetCount: resetCount + 1 })); }; this.onStoreChange = (type) => { if (type === "reset") { this.context.errors[this.props.name] = []; this.refresh(); } else { this.forceUpdate(); } }; this.renderLayout = (childNode) => { const { label, name, required, rules, className, style, errorMessageAlign, align } = Object.assign(Object.assign({}, defaultProps), this.props); const requiredInRules = rules === null || rules === void 0 ? void 0 : rules.some((rule) => rule.required); const item = name ? this.context.errors[name] : []; const { starPosition } = this.context; const renderStar = (required || requiredInRules) && React__default.createElement("i", { className: "required" }); const renderLabel = React__default.createElement( React__default.Fragment, null, starPosition === "left" ? renderStar : null, label, starPosition === "right" ? renderStar : null ); return React__default.createElement( Cell__default, { className: `nut-form-item ${className}`, style, align, onClick: (e) => this.props.onClick && this.props.onClick(e, this.componentRef) }, label ? React__default.createElement("div", { className: "nut-cell-title nut-form-item-label" }, renderLabel) : null, React__default.createElement( "div", { className: "nut-cell-value nut-form-item-body" }, React__default.createElement("div", { className: "nut-form-item-body-slots" }, childNode), item && item.length > 0 && React__default.createElement("div", { className: "nut-form-item-body-tips", style: { textAlign: errorMessageAlign } }, item[0].message) ) ); }; this.componentRef = React__default.createRef(); this.state = { resetCount: 1 }; } componentDidMount() { const { store = {}, setInitialValues } = this.context.getInternal(SECRET); if (this.props.initialValue && this.props.name && !Object.keys(store).includes(this.props.name)) { setInitialValues(Object.assign(Object.assign({}, store), { [this.props.name]: this.props.initialValue }), true); } const { registerField, registerUpdate } = this.context.getInternal(SECRET); this.cancelRegister = registerField(this); this.eventOff = registerUpdate(this, this.props.shouldUpdate); } componentWillUnmount() { if (this.cancelRegister) { this.cancelRegister(); } if (this.eventOff) { this.eventOff(); } } render() { const { children } = this.props; const child = Array.isArray(children) ? children[0] : children; let returnChildNode; if (!this.props.shouldUpdate) { returnChildNode = React__default.cloneElement(child, this.getControlled(child)); } else { returnChildNode = child(this.context); } return React__default.createElement(React__default.Fragment, { key: this.state.resetCount }, this.props.noStyle ? returnChildNode : this.renderLayout(returnChildNode)); } } FormItem.defaultProps = defaultProps; FormItem.contextType = Context; export { Context as C, FormItem as F, SECRET as S, useForm as u };