UNPKG

@nutui/nutui-react-taro

Version:

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

484 lines (483 loc) 17.1 kB
import React__default, { createContext, useRef, useState, useEffect } from "react"; import { Text, View } from "@tarojs/components"; import { C as Cell } from "./cell.taro-DWLhb5m6.js"; import { C as ComponentDefaults } from "./typings-DV9RBfhj.js"; import { a as __awaiter } from "./tslib.es6-iWu3F_1J.js"; import Schema from "async-validator"; const Context = createContext({}); function merge(...items) { return _merge(items[0] === true, false, items); } function recursive(...items) { return _merge(items[0] === true, true, items); } function clone(input) { if (Array.isArray(input)) { const output = []; for (let index = 0; index < input.length; ++index) output.push(clone(input[index])); return output; } if (isPlainObject(input)) { const output = {}; for (const index in input) output[index] = clone(input[index]); return output; } return input; } function isPlainObject(input) { if (input === null || typeof input !== "object") return false; if (Object.getPrototypeOf(input) === null) return true; let ref = input; while (Object.getPrototypeOf(ref) !== null) ref = Object.getPrototypeOf(ref); return Object.getPrototypeOf(input) === ref; } function _recursiveMerge(base, extend) { if (!isPlainObject(base) || !isPlainObject(extend)) return extend; for (const key in extend) { if (key === "__proto__" || key === "constructor" || key === "prototype") continue; base[key] = isPlainObject(base[key]) && isPlainObject(extend[key]) ? _recursiveMerge(base[key], extend[key]) : extend[key]; } return base; } function _merge(isClone, isRecursive, items) { let result; if (isClone || !isPlainObject(result = items.shift())) result = {}; for (let index = 0; index < items.length; ++index) { const item = items[index]; if (!isPlainObject(item)) continue; for (const key in item) { if (key === "__proto__" || key === "constructor" || key === "prototype") continue; const value = isClone ? clone(item[key]) : item[key]; result[key] = isRecursive ? _recursiveMerge(result[key], value) : value; } } return result; } 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); }; }; 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 = (initialValues, init) => { this.initialValues = initialValues || {}; if (init) { const nextStore = merge(initialValues, this.store); this.updateStore(nextStore); this.notifyWatch(); } }; this.setFieldsValue = (newStore) => { const nextStore = recursive(true, this.store, newStore); this.updateStore(nextStore); 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"); } }); this.notifyWatch(); }; this.setFieldValue = (name, value) => { const store = { [name]: value }; this.setFieldsValue(store); this.notifyWatch([name]); }; this.setCallback = (callback) => { this.callbacks = Object.assign(Object.assign({}, this.callbacks), callback); }; this.validateEntities = (entity, errs) => __awaiter(this, void 0, void 0, function* () { var _a; const { name, rules = [] } = entity.props; if (!name) { console.warn("Form field missing name property"); return; } 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"); }); this.validateFields = (nameList) => __awaiter(this, void 0, void 0, function* () { let filterEntities = []; if (!nameList || nameList.length === 0) { filterEntities = this.fieldEntities; } else { filterEntities = this.fieldEntities.filter(({ props: { name } }) => nameList.includes(name)); } const errs = []; yield Promise.all(filterEntities.map((entity) => __awaiter(this, void 0, void 0, function* () { yield this.validateEntities(entity, errs); }))); 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 = (namePaths) => { if (namePaths && namePaths.length) { namePaths.forEach((path) => { this.errors[path] = null; this.fieldEntities.forEach((entity) => { const name = entity.props.name; if (name === path) { if (path in this.initialValues) { this.updateStore({ [path]: this.initialValues[path] }); } else { delete this.store[path]; } entity.onStoreChange("reset"); } }); }); } else { const nextStore = merge({}, this.initialValues); this.updateStore(nextStore); 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, registerWatch: this.registerWatch }; } }; this.getForm = () => { return { getFieldValue: this.getFieldValue, getFieldsValue: this.getFieldsValue, setFieldsValue: this.setFieldsValue, setFieldValue: this.setFieldValue, resetFields: this.resetFields, validateFields: this.validateFields, submit: this.submit, errors: this.errors, getInternal: this.getInternal }; }; this.watchList = []; this.registerWatch = (callback) => { this.watchList.push(callback); return () => { this.watchList = this.watchList.filter((fn) => fn !== callback); }; }; this.notifyWatch = (namePath = []) => { if (this.watchList.length) { let allValues; if (!namePath || namePath.length === 0) { allValues = this.getFieldsValue(true); } else { allValues = this.getFieldsValue(namePath); } this.watchList.forEach((callback) => { callback(allValues, namePath); }); } }; this.callbacks = { onFinish: () => { }, onFinishFailed: () => { } }; } updateStore(nextStore) { this.store = nextStore; } } 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]; }; const useWatch = (path, form) => { const formInstance = form.getInternal(SECRET); const [value, setValue] = useState(); useEffect(() => { const unsubscribe = formInstance.registerWatch((data, namePath) => { const value2 = data[path]; setValue(value2); }); const initialValue = form.getFieldsValue(true); if (value !== initialValue[path]) { setValue(initialValue[path]); } return () => unsubscribe(); }, [form]); return value; }; 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; } function toArray(value) { if (value === void 0 || value === null) { return []; } return Array.isArray(value) ? value : [value]; } const defaultProps = Object.assign(Object.assign({}, ComponentDefaults), { required: false, name: "", label: "", rules: [{ required: false, message: "" }], errorMessageAlign: "left", shouldUpdate: false, noStyle: false }); class FormItem extends React__default.Component { constructor(props) { super(props); this.getControlled = (children) => { var _a; const { setFieldsValue, getFieldValue } = this.context.formInstance; const { dispatch } = this.context.formInstance.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) { if (process.env.NODE_ENV !== "production") { console.warn("[NutUI] FormItem:", "请通过 initialValue 设置初始值,而不是 defaultValue"); } } const fieldValue = getFieldValue(name); const controlled = Object.assign(Object.assign({}, children.props), { className: children.props.className, [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 }); } }); const { validateTrigger } = this.props; const mergedValidateTrigger = validateTrigger || this.context.validateTrigger; const validateTriggers = toArray(mergedValidateTrigger); 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.formInstance.errors[this.props.name] = []; this.refresh(); } else { this.forceUpdate(); } }; this.renderLayout = (childNode) => { var _a; 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.formInstance.errors[name] : []; const { starPosition } = this.context.formInstance; const renderStar = (required || requiredInRules) && React__default.createElement(Text, { className: "nut-form-item-label-required required" }, "*"); const renderLabel = React__default.createElement( React__default.Fragment, null, React__default.createElement( Text, { className: "nut-form-item-labeltxt" }, starPosition === "left" ? renderStar : null, label ), starPosition === "right" ? renderStar : null ); return React__default.createElement( Cell, { className: `${this.getClassNameWithDirection("nut-form-item")} ${className}`, style, align, onClick: (e) => this.props.onClick && this.props.onClick(e, this.componentRef) }, label ? React__default.createElement(View, { className: `nut-cell-title ${this.getClassNameWithDirection("nut-form-item-label")}` }, renderLabel) : null, React__default.createElement( View, { className: `nut-cell-value ${this.getClassNameWithDirection("nut-form-item-body")}` }, React__default.createElement(View, { className: "nut-form-item-body-slots" }, childNode), React__default.createElement(View, { className: "nut-form-item-body-tips", style: { textAlign: errorMessageAlign, display: (item === null || item === void 0 ? void 0 : item.length) ? "initial" : "none" } }, (_a = item === null || item === void 0 ? void 0 : item[0]) === null || _a === void 0 ? void 0 : _a.message) ) ); }; this.componentRef = React__default.createRef(); this.state = { resetCount: 1 }; } componentDidMount() { const { store = {}, setInitialValues } = this.context.formInstance.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.formInstance.getInternal(SECRET); this.cancelRegister = registerField(this); this.eventOff = registerUpdate(this, this.props.shouldUpdate); } componentWillUnmount() { if (this.cancelRegister) { this.cancelRegister(); } if (this.eventOff) { this.eventOff(); } } getClassNameWithDirection(className) { if (className && this.context.labelPosition) { return `${className} ${className}-${this.context.labelPosition}`; } return className; } 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.formInstance); } return React__default.createElement( React__default.Fragment, { key: this.state.resetCount }, React__default.createElement(View, { className: this.context.disabled ? "nut-form-item-disabled" : "", catchMove: this.context.disabled }, this.props.noStyle ? returnChildNode : this.renderLayout(returnChildNode)) ); } } FormItem.defaultProps = defaultProps; FormItem.contextType = Context; export { Context as C, FormItem as F, SECRET as S, useWatch as a, useForm as u };