@nutui/nutui-react-taro
Version:
京东风格的轻量级移动端 React 组件库,支持一套代码生成 H5 和小程序
484 lines (483 loc) • 17.1 kB
JavaScript
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
};