mirror of
https://github.com/ant-design/ant-design.git
synced 2025-06-10 19:19:20 +08:00
merge feature into master
This commit is contained in:
commit
8fe1cc9da5
@ -3,7 +3,10 @@ import { MotionEvent } from 'rc-motion/lib/interface';
|
|||||||
|
|
||||||
// ================== Collapse Motion ==================
|
// ================== Collapse Motion ==================
|
||||||
const getCollapsedHeight: MotionEventHandler = () => ({ height: 0, opacity: 0 });
|
const getCollapsedHeight: MotionEventHandler = () => ({ height: 0, opacity: 0 });
|
||||||
const getRealHeight: MotionEventHandler = node => ({ height: node.scrollHeight, opacity: 1 });
|
const getRealHeight: MotionEventHandler = node => {
|
||||||
|
const { scrollHeight } = node;
|
||||||
|
return { height: scrollHeight, opacity: 1 };
|
||||||
|
};
|
||||||
const getCurrentHeight: MotionEventHandler = node => ({ height: node.offsetHeight });
|
const getCurrentHeight: MotionEventHandler = node => ({ height: node.offsetHeight });
|
||||||
const skipOpacityTransition: MotionEndEventHandler = (_, event: MotionEvent) =>
|
const skipOpacityTransition: MotionEndEventHandler = (_, event: MotionEvent) =>
|
||||||
event?.deadline === true || (event as TransitionEvent).propertyName === 'height';
|
event?.deadline === true || (event as TransitionEvent).propertyName === 'height';
|
||||||
|
@ -13272,9 +13272,10 @@ exports[`ConfigProvider components Form configProvider 1`] = `
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="config-form-item-explain config-form-item-explain-error"
|
class="config-form-item-explain"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
|
class="config-form-item-explain-error"
|
||||||
role="alert"
|
role="alert"
|
||||||
>
|
>
|
||||||
Bamboo is Light
|
Bamboo is Light
|
||||||
@ -13309,9 +13310,10 @@ exports[`ConfigProvider components Form configProvider componentSize large 1`] =
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="config-form-item-explain config-form-item-explain-error"
|
class="config-form-item-explain"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
|
class="config-form-item-explain-error"
|
||||||
role="alert"
|
role="alert"
|
||||||
>
|
>
|
||||||
Bamboo is Light
|
Bamboo is Light
|
||||||
@ -13346,9 +13348,10 @@ exports[`ConfigProvider components Form configProvider componentSize middle 1`]
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="config-form-item-explain config-form-item-explain-error"
|
class="config-form-item-explain"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
|
class="config-form-item-explain-error"
|
||||||
role="alert"
|
role="alert"
|
||||||
>
|
>
|
||||||
Bamboo is Light
|
Bamboo is Light
|
||||||
@ -13383,9 +13386,10 @@ exports[`ConfigProvider components Form configProvider virtual and dropdownMatch
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="ant-form-item-explain ant-form-item-explain-error"
|
class="ant-form-item-explain"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
|
class="ant-form-item-explain-error"
|
||||||
role="alert"
|
role="alert"
|
||||||
>
|
>
|
||||||
Bamboo is Light
|
Bamboo is Light
|
||||||
@ -13420,9 +13424,10 @@ exports[`ConfigProvider components Form normal 1`] = `
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="ant-form-item-explain ant-form-item-explain-error"
|
class="ant-form-item-explain"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
|
class="ant-form-item-explain-error"
|
||||||
role="alert"
|
role="alert"
|
||||||
>
|
>
|
||||||
Bamboo is Light
|
Bamboo is Light
|
||||||
@ -13457,9 +13462,10 @@ exports[`ConfigProvider components Form prefixCls 1`] = `
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="prefix-Form-item-explain prefix-Form-item-explain-error"
|
class="prefix-Form-item-explain"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
|
class="prefix-Form-item-explain-error"
|
||||||
role="alert"
|
role="alert"
|
||||||
>
|
>
|
||||||
Bamboo is Light
|
Bamboo is Light
|
||||||
|
@ -1,97 +1,114 @@
|
|||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import CSSMotion from 'rc-motion';
|
import CSSMotion, { CSSMotionList } from 'rc-motion';
|
||||||
import useMemo from 'rc-util/lib/hooks/useMemo';
|
|
||||||
import useCacheErrors from './hooks/useCacheErrors';
|
|
||||||
import useForceUpdate from '../_util/hooks/useForceUpdate';
|
|
||||||
import { FormItemPrefixContext } from './context';
|
import { FormItemPrefixContext } from './context';
|
||||||
import { ConfigContext } from '../config-provider';
|
import { ConfigContext } from '../config-provider';
|
||||||
|
import { ValidateStatus } from './FormItem';
|
||||||
|
import collapseMotion from '../_util/motion';
|
||||||
|
|
||||||
const EMPTY_LIST: React.ReactNode[] = [];
|
const EMPTY_LIST: React.ReactNode[] = [];
|
||||||
|
|
||||||
|
interface ErrorEntity {
|
||||||
|
error: React.ReactNode;
|
||||||
|
errorStatus?: ValidateStatus;
|
||||||
|
key: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
function toErrorEntity(
|
||||||
|
error: React.ReactNode,
|
||||||
|
errorStatus: ValidateStatus | undefined,
|
||||||
|
prefix: string,
|
||||||
|
index: number = 0,
|
||||||
|
): ErrorEntity {
|
||||||
|
return {
|
||||||
|
key: typeof error === 'string' ? error : `${prefix}-${index}`,
|
||||||
|
error,
|
||||||
|
errorStatus,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export interface ErrorListProps {
|
export interface ErrorListProps {
|
||||||
errors?: React.ReactNode[];
|
|
||||||
/** @private Internal Usage. Do not use in your production */
|
|
||||||
help?: React.ReactNode;
|
help?: React.ReactNode;
|
||||||
/** @private Internal Usage. Do not use in your production */
|
helpStatus?: ValidateStatus;
|
||||||
onDomErrorVisibleChange?: (visible: boolean) => void;
|
errors?: React.ReactNode[];
|
||||||
|
warnings?: React.ReactNode[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function ErrorList({
|
export default function ErrorList({
|
||||||
errors = EMPTY_LIST,
|
|
||||||
help,
|
help,
|
||||||
onDomErrorVisibleChange,
|
helpStatus,
|
||||||
|
errors = EMPTY_LIST,
|
||||||
|
warnings = EMPTY_LIST,
|
||||||
}: ErrorListProps) {
|
}: ErrorListProps) {
|
||||||
const forceUpdate = useForceUpdate();
|
const { prefixCls } = React.useContext(FormItemPrefixContext);
|
||||||
const { prefixCls, status } = React.useContext(FormItemPrefixContext);
|
|
||||||
const { getPrefixCls } = React.useContext(ConfigContext);
|
const { getPrefixCls } = React.useContext(ConfigContext);
|
||||||
|
|
||||||
const [visible, cacheErrors] = useCacheErrors(
|
|
||||||
errors,
|
|
||||||
changedVisible => {
|
|
||||||
if (changedVisible) {
|
|
||||||
/**
|
|
||||||
* We trigger in sync to avoid dom shaking but this get warning in react 16.13.
|
|
||||||
*
|
|
||||||
* So use Promise to keep in micro async to handle this.
|
|
||||||
* https://github.com/ant-design/ant-design/issues/21698#issuecomment-593743485
|
|
||||||
*/
|
|
||||||
Promise.resolve().then(() => {
|
|
||||||
onDomErrorVisibleChange?.(true);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
forceUpdate();
|
|
||||||
},
|
|
||||||
!!help,
|
|
||||||
);
|
|
||||||
|
|
||||||
const memoErrors = useMemo(
|
|
||||||
() => cacheErrors,
|
|
||||||
visible,
|
|
||||||
(_, nextVisible) => nextVisible,
|
|
||||||
);
|
|
||||||
|
|
||||||
// Memo status in same visible
|
|
||||||
const [innerStatus, setInnerStatus] = React.useState(status);
|
|
||||||
React.useEffect(() => {
|
|
||||||
if (visible && status) {
|
|
||||||
setInnerStatus(status);
|
|
||||||
}
|
|
||||||
}, [visible, status]);
|
|
||||||
|
|
||||||
const baseClassName = `${prefixCls}-item-explain`;
|
const baseClassName = `${prefixCls}-item-explain`;
|
||||||
const rootPrefixCls = getPrefixCls();
|
const rootPrefixCls = getPrefixCls();
|
||||||
|
|
||||||
|
const fullKeyList = React.useMemo(() => {
|
||||||
|
if (help !== undefined && help !== null) {
|
||||||
|
return [toErrorEntity(help, helpStatus, 'help')];
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
...errors.map((error, index) => toErrorEntity(error, 'error', 'error', index)),
|
||||||
|
...warnings.map((warning, index) => toErrorEntity(warning, 'warning', 'warning', index)),
|
||||||
|
];
|
||||||
|
}, [help, helpStatus, errors, warnings]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CSSMotion
|
<CSSMotion
|
||||||
motionDeadline={500}
|
{...collapseMotion}
|
||||||
visible={visible}
|
|
||||||
motionName={`${rootPrefixCls}-show-help`}
|
motionName={`${rootPrefixCls}-show-help`}
|
||||||
onLeaveEnd={() => {
|
motionAppear={false}
|
||||||
onDomErrorVisibleChange?.(false);
|
motionEnter={false}
|
||||||
}}
|
motionLeave
|
||||||
motionAppear
|
visible={!!fullKeyList.length}
|
||||||
removeOnLeave
|
removeOnLeave
|
||||||
|
onLeaveStart={node => {
|
||||||
|
// Force disable css override style in index.less configured
|
||||||
|
node.style.height = 'auto';
|
||||||
|
return { height: node.offsetHeight };
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
{({ className: motionClassName }: { className?: string }) => (
|
{holderProps => {
|
||||||
|
const { className: holderClassName, style: holderStyle } = holderProps;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={classNames(baseClassName, holderClassName)} style={holderStyle}>
|
||||||
|
<CSSMotionList
|
||||||
|
keys={fullKeyList}
|
||||||
|
{...collapseMotion}
|
||||||
|
motionName={`${rootPrefixCls}-show-help-item`}
|
||||||
|
component={false}
|
||||||
|
>
|
||||||
|
{itemProps => {
|
||||||
|
const {
|
||||||
|
key,
|
||||||
|
error,
|
||||||
|
errorStatus,
|
||||||
|
className: itemClassName,
|
||||||
|
style: itemStyle,
|
||||||
|
} = itemProps;
|
||||||
|
|
||||||
|
return (
|
||||||
<div
|
<div
|
||||||
className={classNames(
|
key={key}
|
||||||
baseClassName,
|
role="alert"
|
||||||
{
|
className={classNames(itemClassName, {
|
||||||
[`${baseClassName}-${innerStatus}`]: innerStatus,
|
[`${baseClassName}-${errorStatus}`]: errorStatus,
|
||||||
},
|
})}
|
||||||
motionClassName,
|
style={itemStyle}
|
||||||
)}
|
|
||||||
key="help"
|
|
||||||
>
|
>
|
||||||
{memoErrors.map((error, index) => (
|
|
||||||
// eslint-disable-next-line react/no-array-index-key
|
|
||||||
<div key={index} role="alert">
|
|
||||||
{error}
|
{error}
|
||||||
</div>
|
</div>
|
||||||
))}
|
);
|
||||||
|
}}
|
||||||
|
</CSSMotionList>
|
||||||
</div>
|
</div>
|
||||||
)}
|
);
|
||||||
|
}}
|
||||||
</CSSMotion>
|
</CSSMotion>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { useContext, useRef } from 'react';
|
import { useContext } from 'react';
|
||||||
import isEqual from 'lodash/isEqual';
|
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { Field, FormInstance } from 'rc-field-form';
|
import { Field, FormInstance } from 'rc-field-form';
|
||||||
import { FieldProps } from 'rc-field-form/lib/Field';
|
import { FieldProps } from 'rc-field-form/lib/Field';
|
||||||
@ -14,14 +13,20 @@ import { tuple } from '../_util/type';
|
|||||||
import devWarning from '../_util/devWarning';
|
import devWarning from '../_util/devWarning';
|
||||||
import FormItemLabel, { FormItemLabelProps, LabelTooltipType } from './FormItemLabel';
|
import FormItemLabel, { FormItemLabelProps, LabelTooltipType } from './FormItemLabel';
|
||||||
import FormItemInput, { FormItemInputProps } from './FormItemInput';
|
import FormItemInput, { FormItemInputProps } from './FormItemInput';
|
||||||
import { FormContext, FormItemContext } from './context';
|
import { FormContext, NoStyleItemContext } from './context';
|
||||||
import { toArray, getFieldId } from './util';
|
import { toArray, getFieldId } from './util';
|
||||||
import { cloneElement, isValidElement } from '../_util/reactNode';
|
import { cloneElement, isValidElement } from '../_util/reactNode';
|
||||||
import useFrameState from './hooks/useFrameState';
|
import useFrameState from './hooks/useFrameState';
|
||||||
|
import useDebounce from './hooks/useDebounce';
|
||||||
import useItemRef from './hooks/useItemRef';
|
import useItemRef from './hooks/useItemRef';
|
||||||
|
|
||||||
const NAME_SPLIT = '__SPLIT__';
|
const NAME_SPLIT = '__SPLIT__';
|
||||||
|
|
||||||
|
interface FieldError {
|
||||||
|
errors: string[];
|
||||||
|
warnings: string[];
|
||||||
|
}
|
||||||
|
|
||||||
const ValidateStatuses = tuple('success', 'warning', 'error', 'validating', '');
|
const ValidateStatuses = tuple('success', 'warning', 'error', 'validating', '');
|
||||||
export type ValidateStatus = typeof ValidateStatuses[number];
|
export type ValidateStatus = typeof ValidateStatuses[number];
|
||||||
|
|
||||||
@ -31,7 +36,7 @@ type ChildrenType<Values = any> = RenderChildren<Values> | React.ReactNode;
|
|||||||
|
|
||||||
interface MemoInputProps {
|
interface MemoInputProps {
|
||||||
value: any;
|
value: any;
|
||||||
update: number;
|
update: any;
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -68,6 +73,16 @@ function hasValidName(name?: NamePath): Boolean {
|
|||||||
return !(name === undefined || name === null);
|
return !(name === undefined || name === null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function genEmptyMeta(): Meta {
|
||||||
|
return {
|
||||||
|
errors: [],
|
||||||
|
warnings: [],
|
||||||
|
touched: false,
|
||||||
|
validating: false,
|
||||||
|
name: [],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
function FormItem<Values = any>(props: FormItemProps<Values>): React.ReactElement {
|
function FormItem<Values = any>(props: FormItemProps<Values>): React.ReactElement {
|
||||||
const {
|
const {
|
||||||
name,
|
name,
|
||||||
@ -91,104 +106,109 @@ function FormItem<Values = any>(props: FormItemProps<Values>): React.ReactElemen
|
|||||||
hidden,
|
hidden,
|
||||||
...restProps
|
...restProps
|
||||||
} = props;
|
} = props;
|
||||||
const destroyRef = useRef(false);
|
|
||||||
const { getPrefixCls } = useContext(ConfigContext);
|
const { getPrefixCls } = useContext(ConfigContext);
|
||||||
const { name: formName, requiredMark } = useContext(FormContext);
|
const { name: formName, requiredMark } = useContext(FormContext);
|
||||||
const { updateItemErrors } = useContext(FormItemContext);
|
const isRenderProps = typeof children === 'function';
|
||||||
const [domErrorVisible, innerSetDomErrorVisible] = React.useState(!!help);
|
const notifyParentMetaChange = useContext(NoStyleItemContext);
|
||||||
const [inlineErrors, setInlineErrors] = useFrameState<Record<string, string[]>>({});
|
|
||||||
|
|
||||||
const { validateTrigger: contextValidateTrigger } = useContext(FieldContext);
|
const { validateTrigger: contextValidateTrigger } = useContext(FieldContext);
|
||||||
const mergedValidateTrigger =
|
const mergedValidateTrigger =
|
||||||
validateTrigger !== undefined ? validateTrigger : contextValidateTrigger;
|
validateTrigger !== undefined ? validateTrigger : contextValidateTrigger;
|
||||||
|
|
||||||
function setDomErrorVisible(visible: boolean) {
|
|
||||||
if (!destroyRef.current) {
|
|
||||||
innerSetDomErrorVisible(visible);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const hasName = hasValidName(name);
|
const hasName = hasValidName(name);
|
||||||
|
|
||||||
// Cache Field NamePath
|
|
||||||
const nameRef = useRef<(string | number)[]>([]);
|
|
||||||
|
|
||||||
// Should clean up if Field removed
|
|
||||||
React.useEffect(
|
|
||||||
() => () => {
|
|
||||||
destroyRef.current = true;
|
|
||||||
updateItemErrors(nameRef.current.join(NAME_SPLIT), []);
|
|
||||||
},
|
|
||||||
[],
|
|
||||||
);
|
|
||||||
|
|
||||||
const prefixCls = getPrefixCls('form', customizePrefixCls);
|
const prefixCls = getPrefixCls('form', customizePrefixCls);
|
||||||
|
|
||||||
// ======================== Errors ========================
|
// ======================== Errors ========================
|
||||||
// Collect noStyle Field error to the top FormItem
|
// >>>>> Collect sub field errors
|
||||||
const updateChildItemErrors = noStyle
|
const [subFieldErrors, setSubFieldErrors] = useFrameState<Record<string, FieldError>>({});
|
||||||
? updateItemErrors
|
|
||||||
: (subName: string, subErrors: string[], originSubName?: string) => {
|
// >>>>> Current field errors
|
||||||
setInlineErrors((prevInlineErrors = {}) => {
|
const [meta, setMeta] = React.useState<Meta>(() => genEmptyMeta());
|
||||||
// Clean up origin error when name changed
|
|
||||||
if (originSubName && originSubName !== subName) {
|
const onMetaChange = (nextMeta: Meta & { destroy?: boolean }) => {
|
||||||
delete prevInlineErrors[originSubName];
|
// Destroy will reset all the meta
|
||||||
|
setMeta(nextMeta.destroy ? genEmptyMeta() : nextMeta);
|
||||||
|
|
||||||
|
// Bump to parent since noStyle
|
||||||
|
if (noStyle && notifyParentMetaChange) {
|
||||||
|
let namePath = nextMeta.name;
|
||||||
|
if (fieldKey !== undefined) {
|
||||||
|
namePath = Array.isArray(fieldKey) ? fieldKey : [fieldKey!];
|
||||||
|
}
|
||||||
|
notifyParentMetaChange(nextMeta, namePath);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// >>>>> Collect noStyle Field error to the top FormItem
|
||||||
|
const onSubItemMetaChange = (subMeta: Meta & { destroy: boolean }, uniqueKeys: React.Key[]) => {
|
||||||
|
// Only `noStyle` sub item will trigger
|
||||||
|
setSubFieldErrors(prevSubFieldErrors => {
|
||||||
|
const clone = {
|
||||||
|
...prevSubFieldErrors,
|
||||||
|
};
|
||||||
|
|
||||||
|
// name: ['user', 1] + key: [4] = ['user', 4]
|
||||||
|
const mergedNamePath = [...subMeta.name.slice(0, -1), ...uniqueKeys];
|
||||||
|
const mergedNameKey = mergedNamePath.join(NAME_SPLIT);
|
||||||
|
|
||||||
|
if (subMeta.destroy) {
|
||||||
|
// Remove
|
||||||
|
delete clone[mergedNameKey];
|
||||||
|
} else {
|
||||||
|
// Update
|
||||||
|
clone[mergedNameKey] = subMeta;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isEqual(prevInlineErrors[subName], subErrors)) {
|
return clone;
|
||||||
return {
|
|
||||||
...prevInlineErrors,
|
|
||||||
[subName]: subErrors,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return prevInlineErrors;
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// >>>>> Get merged errors
|
||||||
|
const [mergedErrors, mergedWarnings] = React.useMemo(() => {
|
||||||
|
const errorList: string[] = [...meta.errors];
|
||||||
|
const warningList: string[] = [...meta.warnings];
|
||||||
|
|
||||||
|
Object.values(subFieldErrors).forEach(subFieldError => {
|
||||||
|
errorList.push(...(subFieldError.errors || []));
|
||||||
|
warningList.push(...(subFieldError.warnings || []));
|
||||||
|
});
|
||||||
|
|
||||||
|
return [errorList, warningList];
|
||||||
|
}, [subFieldErrors, meta.errors, meta.warnings]);
|
||||||
|
|
||||||
|
const debounceErrors = useDebounce(mergedErrors);
|
||||||
|
const debounceWarnings = useDebounce(mergedWarnings);
|
||||||
|
|
||||||
// ===================== Children Ref =====================
|
// ===================== Children Ref =====================
|
||||||
const getItemRef = useItemRef();
|
const getItemRef = useItemRef();
|
||||||
|
|
||||||
|
// ======================== Render ========================
|
||||||
function renderLayout(
|
function renderLayout(
|
||||||
baseChildren: React.ReactNode,
|
baseChildren: React.ReactNode,
|
||||||
fieldId?: string,
|
fieldId?: string,
|
||||||
meta?: Meta,
|
|
||||||
isRequired?: boolean,
|
isRequired?: boolean,
|
||||||
): React.ReactNode {
|
): React.ReactNode {
|
||||||
if (noStyle && !hidden) {
|
if (noStyle && !hidden) {
|
||||||
return baseChildren;
|
return baseChildren;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ======================== Errors ========================
|
|
||||||
// >>> collect sub errors
|
|
||||||
let subErrorList: string[] = [];
|
|
||||||
Object.keys(inlineErrors).forEach(subName => {
|
|
||||||
subErrorList = [...subErrorList, ...(inlineErrors[subName] || [])];
|
|
||||||
});
|
|
||||||
|
|
||||||
// >>> merged errors
|
|
||||||
let mergedErrors: React.ReactNode[];
|
|
||||||
if (help !== undefined && help !== null) {
|
|
||||||
mergedErrors = toArray(help);
|
|
||||||
} else {
|
|
||||||
mergedErrors = meta ? meta.errors : [];
|
|
||||||
mergedErrors = [...mergedErrors, ...subErrorList];
|
|
||||||
}
|
|
||||||
|
|
||||||
// ======================== Status ========================
|
// ======================== Status ========================
|
||||||
let mergedValidateStatus: ValidateStatus = '';
|
let mergedValidateStatus: ValidateStatus = '';
|
||||||
if (validateStatus !== undefined) {
|
if (validateStatus !== undefined) {
|
||||||
mergedValidateStatus = validateStatus;
|
mergedValidateStatus = validateStatus;
|
||||||
} else if (meta?.validating) {
|
} else if (meta?.validating) {
|
||||||
mergedValidateStatus = 'validating';
|
mergedValidateStatus = 'validating';
|
||||||
} else if (meta?.errors?.length || subErrorList.length) {
|
} else if (debounceErrors.length) {
|
||||||
mergedValidateStatus = 'error';
|
mergedValidateStatus = 'error';
|
||||||
|
} else if (debounceWarnings.length) {
|
||||||
|
mergedValidateStatus = 'warning';
|
||||||
} else if (meta?.touched) {
|
} else if (meta?.touched) {
|
||||||
mergedValidateStatus = 'success';
|
mergedValidateStatus = 'success';
|
||||||
}
|
}
|
||||||
|
|
||||||
const itemClassName = {
|
const itemClassName = {
|
||||||
[`${prefixCls}-item`]: true,
|
[`${prefixCls}-item`]: true,
|
||||||
[`${prefixCls}-item-with-help`]: domErrorVisible || !!help,
|
[`${prefixCls}-item-with-help`]: help || debounceErrors.length || debounceWarnings.length,
|
||||||
[`${className}`]: !!className,
|
[`${className}`]: !!className,
|
||||||
|
|
||||||
// Status
|
// Status
|
||||||
@ -238,26 +258,21 @@ function FormItem<Values = any>(props: FormItemProps<Values>): React.ReactElemen
|
|||||||
<FormItemInput
|
<FormItemInput
|
||||||
{...props}
|
{...props}
|
||||||
{...meta}
|
{...meta}
|
||||||
errors={mergedErrors}
|
errors={debounceErrors}
|
||||||
|
warnings={debounceWarnings}
|
||||||
prefixCls={prefixCls}
|
prefixCls={prefixCls}
|
||||||
status={mergedValidateStatus}
|
status={mergedValidateStatus}
|
||||||
onDomErrorVisibleChange={setDomErrorVisible}
|
|
||||||
validateStatus={mergedValidateStatus}
|
validateStatus={mergedValidateStatus}
|
||||||
|
help={help}
|
||||||
>
|
>
|
||||||
<FormItemContext.Provider value={{ updateItemErrors: updateChildItemErrors }}>
|
<NoStyleItemContext.Provider value={onSubItemMetaChange}>
|
||||||
{baseChildren}
|
{baseChildren}
|
||||||
</FormItemContext.Provider>
|
</NoStyleItemContext.Provider>
|
||||||
</FormItemInput>
|
</FormItemInput>
|
||||||
</Row>
|
</Row>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const isRenderProps = typeof children === 'function';
|
|
||||||
|
|
||||||
// Record for real component render
|
|
||||||
const updateRef = useRef(0);
|
|
||||||
updateRef.current += 1;
|
|
||||||
|
|
||||||
if (!hasName && !isRenderProps && !dependencies) {
|
if (!hasName && !isRenderProps && !dependencies) {
|
||||||
return renderLayout(children) as JSX.Element;
|
return renderLayout(children) as JSX.Element;
|
||||||
}
|
}
|
||||||
@ -270,46 +285,31 @@ function FormItem<Values = any>(props: FormItemProps<Values>): React.ReactElemen
|
|||||||
variables = { ...variables, ...messageVariables };
|
variables = { ...variables, ...messageVariables };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// >>>>> With Field
|
||||||
return (
|
return (
|
||||||
<Field
|
<Field
|
||||||
{...props}
|
{...props}
|
||||||
messageVariables={variables}
|
messageVariables={variables}
|
||||||
trigger={trigger}
|
trigger={trigger}
|
||||||
validateTrigger={mergedValidateTrigger}
|
validateTrigger={mergedValidateTrigger}
|
||||||
onReset={() => {
|
onMetaChange={onMetaChange}
|
||||||
setDomErrorVisible(false);
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
{(control, meta, context) => {
|
{(control, renderMeta, context) => {
|
||||||
const { errors } = meta;
|
const mergedName = toArray(name).length && renderMeta ? renderMeta.name : [];
|
||||||
|
|
||||||
const mergedName = toArray(name).length && meta ? meta.name : [];
|
|
||||||
const fieldId = getFieldId(mergedName, formName);
|
const fieldId = getFieldId(mergedName, formName);
|
||||||
|
|
||||||
if (noStyle) {
|
|
||||||
// Clean up origin one
|
|
||||||
const originErrorName = nameRef.current.join(NAME_SPLIT);
|
|
||||||
|
|
||||||
nameRef.current = [...mergedName];
|
|
||||||
if (fieldKey) {
|
|
||||||
const fieldKeys = Array.isArray(fieldKey) ? fieldKey : [fieldKey];
|
|
||||||
nameRef.current = [...mergedName.slice(0, -1), ...fieldKeys];
|
|
||||||
}
|
|
||||||
updateItemErrors(nameRef.current.join(NAME_SPLIT), errors, originErrorName);
|
|
||||||
}
|
|
||||||
|
|
||||||
const isRequired =
|
const isRequired =
|
||||||
required !== undefined
|
required !== undefined
|
||||||
? required
|
? required
|
||||||
: !!(
|
: !!(
|
||||||
rules &&
|
rules &&
|
||||||
rules.some(rule => {
|
rules.some(rule => {
|
||||||
if (rule && typeof rule === 'object' && rule.required) {
|
if (rule && typeof rule === 'object' && rule.required && !rule.warningOnly) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (typeof rule === 'function') {
|
if (typeof rule === 'function') {
|
||||||
const ruleEntity = rule(context);
|
const ruleEntity = rule(context);
|
||||||
return ruleEntity && ruleEntity.required;
|
return ruleEntity && ruleEntity.required && !ruleEntity.warningOnly;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
})
|
})
|
||||||
@ -377,10 +377,7 @@ function FormItem<Values = any>(props: FormItemProps<Values>): React.ReactElemen
|
|||||||
});
|
});
|
||||||
|
|
||||||
childNode = (
|
childNode = (
|
||||||
<MemoInput
|
<MemoInput value={mergedControl[props.valuePropName || 'value']} update={children}>
|
||||||
value={mergedControl[props.valuePropName || 'value']}
|
|
||||||
update={updateRef.current}
|
|
||||||
>
|
|
||||||
{cloneElement(children, childProps)}
|
{cloneElement(children, childProps)}
|
||||||
</MemoInput>
|
</MemoInput>
|
||||||
);
|
);
|
||||||
@ -395,7 +392,7 @@ function FormItem<Values = any>(props: FormItemProps<Values>): React.ReactElemen
|
|||||||
childNode = children;
|
childNode = children;
|
||||||
}
|
}
|
||||||
|
|
||||||
return renderLayout(childNode, fieldId, meta, isRequired);
|
return renderLayout(childNode, fieldId, isRequired);
|
||||||
}}
|
}}
|
||||||
</Field>
|
</Field>
|
||||||
);
|
);
|
||||||
|
@ -14,9 +14,9 @@ interface FormItemInputMiscProps {
|
|||||||
prefixCls: string;
|
prefixCls: string;
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
errors: React.ReactNode[];
|
errors: React.ReactNode[];
|
||||||
|
warnings: React.ReactNode[];
|
||||||
hasFeedback?: boolean;
|
hasFeedback?: boolean;
|
||||||
validateStatus?: ValidateStatus;
|
validateStatus?: ValidateStatus;
|
||||||
onDomErrorVisibleChange: (visible: boolean) => void;
|
|
||||||
/** @private Internal Usage, do not use in any of your production. */
|
/** @private Internal Usage, do not use in any of your production. */
|
||||||
_internalItemRender?: {
|
_internalItemRender?: {
|
||||||
mark: string;
|
mark: string;
|
||||||
@ -33,9 +33,9 @@ interface FormItemInputMiscProps {
|
|||||||
|
|
||||||
export interface FormItemInputProps {
|
export interface FormItemInputProps {
|
||||||
wrapperCol?: ColProps;
|
wrapperCol?: ColProps;
|
||||||
help?: React.ReactNode;
|
|
||||||
extra?: React.ReactNode;
|
extra?: React.ReactNode;
|
||||||
status?: ValidateStatus;
|
status?: ValidateStatus;
|
||||||
|
help?: React.ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
const iconMap: { [key: string]: any } = {
|
const iconMap: { [key: string]: any } = {
|
||||||
@ -51,13 +51,13 @@ const FormItemInput: React.FC<FormItemInputProps & FormItemInputMiscProps> = pro
|
|||||||
status,
|
status,
|
||||||
wrapperCol,
|
wrapperCol,
|
||||||
children,
|
children,
|
||||||
help,
|
|
||||||
errors,
|
errors,
|
||||||
onDomErrorVisibleChange,
|
warnings,
|
||||||
hasFeedback,
|
hasFeedback,
|
||||||
_internalItemRender: formItemRender,
|
_internalItemRender: formItemRender,
|
||||||
validateStatus,
|
validateStatus,
|
||||||
extra,
|
extra,
|
||||||
|
help,
|
||||||
} = props;
|
} = props;
|
||||||
const baseClassName = `${prefixCls}-item`;
|
const baseClassName = `${prefixCls}-item`;
|
||||||
|
|
||||||
@ -67,13 +67,6 @@ const FormItemInput: React.FC<FormItemInputProps & FormItemInputMiscProps> = pro
|
|||||||
|
|
||||||
const className = classNames(`${baseClassName}-control`, mergedWrapperCol.className);
|
const className = classNames(`${baseClassName}-control`, mergedWrapperCol.className);
|
||||||
|
|
||||||
React.useEffect(
|
|
||||||
() => () => {
|
|
||||||
onDomErrorVisibleChange(false);
|
|
||||||
},
|
|
||||||
[],
|
|
||||||
);
|
|
||||||
|
|
||||||
// Should provides additional icon if `hasFeedback`
|
// Should provides additional icon if `hasFeedback`
|
||||||
const IconNode = validateStatus && iconMap[validateStatus];
|
const IconNode = validateStatus && iconMap[validateStatus];
|
||||||
const icon =
|
const icon =
|
||||||
@ -96,7 +89,7 @@ const FormItemInput: React.FC<FormItemInputProps & FormItemInputMiscProps> = pro
|
|||||||
);
|
);
|
||||||
const errorListDom = (
|
const errorListDom = (
|
||||||
<FormItemPrefixContext.Provider value={{ prefixCls, status }}>
|
<FormItemPrefixContext.Provider value={{ prefixCls, status }}>
|
||||||
<ErrorList errors={errors} help={help} onDomErrorVisibleChange={onDomErrorVisibleChange} />
|
<ErrorList errors={errors} warnings={warnings} help={help} helpStatus={status} />
|
||||||
</FormItemPrefixContext.Provider>
|
</FormItemPrefixContext.Provider>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -25,7 +25,7 @@ export interface FormListProps {
|
|||||||
children: (
|
children: (
|
||||||
fields: FormListFieldData[],
|
fields: FormListFieldData[],
|
||||||
operation: FormListOperation,
|
operation: FormListOperation,
|
||||||
meta: { errors: React.ReactNode[] },
|
meta: { errors: React.ReactNode[]; warnings: React.ReactNode[] },
|
||||||
) => React.ReactNode;
|
) => React.ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -48,6 +48,7 @@ const FormList: React.FC<FormListProps> = ({
|
|||||||
operation,
|
operation,
|
||||||
{
|
{
|
||||||
errors: meta.errors,
|
errors: meta.errors,
|
||||||
|
warnings: meta.warnings,
|
||||||
},
|
},
|
||||||
)}
|
)}
|
||||||
</FormItemPrefixContext.Provider>
|
</FormItemPrefixContext.Provider>
|
||||||
|
@ -306,6 +306,7 @@ exports[`renders ./components/form/demo/advanced-search.md correctly 1`] = `
|
|||||||
|
|
||||||
exports[`renders ./components/form/demo/basic.md correctly 1`] = `
|
exports[`renders ./components/form/demo/basic.md correctly 1`] = `
|
||||||
<form
|
<form
|
||||||
|
autocomplete="off"
|
||||||
class="ant-form ant-form-horizontal"
|
class="ant-form ant-form-horizontal"
|
||||||
id="basic"
|
id="basic"
|
||||||
>
|
>
|
||||||
@ -1073,9 +1074,10 @@ exports[`renders ./components/form/demo/disabled-input-debug.md correctly 1`] =
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="ant-form-item-explain ant-form-item-explain-error"
|
class="ant-form-item-explain"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
|
class="ant-form-item-explain-error"
|
||||||
role="alert"
|
role="alert"
|
||||||
>
|
>
|
||||||
Buggy!
|
Buggy!
|
||||||
@ -1115,9 +1117,10 @@ exports[`renders ./components/form/demo/disabled-input-debug.md correctly 1`] =
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="ant-form-item-explain ant-form-item-explain-error"
|
class="ant-form-item-explain"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
|
class="ant-form-item-explain-error"
|
||||||
role="alert"
|
role="alert"
|
||||||
>
|
>
|
||||||
Buggy!
|
Buggy!
|
||||||
@ -1188,9 +1191,10 @@ exports[`renders ./components/form/demo/disabled-input-debug.md correctly 1`] =
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="ant-form-item-explain ant-form-item-explain-error"
|
class="ant-form-item-explain"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
|
class="ant-form-item-explain-error"
|
||||||
role="alert"
|
role="alert"
|
||||||
>
|
>
|
||||||
Buggy!
|
Buggy!
|
||||||
@ -1230,9 +1234,10 @@ exports[`renders ./components/form/demo/disabled-input-debug.md correctly 1`] =
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="ant-form-item-explain ant-form-item-explain-error"
|
class="ant-form-item-explain"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
|
class="ant-form-item-explain-error"
|
||||||
role="alert"
|
role="alert"
|
||||||
>
|
>
|
||||||
Buggy!
|
Buggy!
|
||||||
@ -1329,9 +1334,10 @@ exports[`renders ./components/form/demo/disabled-input-debug.md correctly 1`] =
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="ant-form-item-explain ant-form-item-explain-error"
|
class="ant-form-item-explain"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
|
class="ant-form-item-explain-error"
|
||||||
role="alert"
|
role="alert"
|
||||||
>
|
>
|
||||||
Buggy!
|
Buggy!
|
||||||
@ -1384,9 +1390,10 @@ exports[`renders ./components/form/demo/disabled-input-debug.md correctly 1`] =
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="ant-form-item-explain ant-form-item-explain-error"
|
class="ant-form-item-explain"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
|
class="ant-form-item-explain-error"
|
||||||
role="alert"
|
role="alert"
|
||||||
>
|
>
|
||||||
Buggy!
|
Buggy!
|
||||||
@ -1475,9 +1482,10 @@ exports[`renders ./components/form/demo/disabled-input-debug.md correctly 1`] =
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="ant-form-item-explain ant-form-item-explain-error"
|
class="ant-form-item-explain"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
|
class="ant-form-item-explain-error"
|
||||||
role="alert"
|
role="alert"
|
||||||
>
|
>
|
||||||
Buggy!
|
Buggy!
|
||||||
@ -1526,9 +1534,10 @@ exports[`renders ./components/form/demo/disabled-input-debug.md correctly 1`] =
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="ant-form-item-explain ant-form-item-explain-error"
|
class="ant-form-item-explain"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
|
class="ant-form-item-explain-error"
|
||||||
role="alert"
|
role="alert"
|
||||||
>
|
>
|
||||||
Buggy!
|
Buggy!
|
||||||
@ -6472,9 +6481,10 @@ exports[`renders ./components/form/demo/validate-static.md correctly 1`] = `
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="ant-form-item-explain ant-form-item-explain-error"
|
class="ant-form-item-explain"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
|
class="ant-form-item-explain-error"
|
||||||
role="alert"
|
role="alert"
|
||||||
>
|
>
|
||||||
Should be combination of numbers & alphabets
|
Should be combination of numbers & alphabets
|
||||||
@ -6597,9 +6607,10 @@ exports[`renders ./components/form/demo/validate-static.md correctly 1`] = `
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="ant-form-item-explain ant-form-item-explain-validating"
|
class="ant-form-item-explain"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
|
class="ant-form-item-explain-validating"
|
||||||
role="alert"
|
role="alert"
|
||||||
>
|
>
|
||||||
The information is being validated...
|
The information is being validated...
|
||||||
@ -6774,9 +6785,10 @@ exports[`renders ./components/form/demo/validate-static.md correctly 1`] = `
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="ant-form-item-explain ant-form-item-explain-error"
|
class="ant-form-item-explain"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
|
class="ant-form-item-explain-error"
|
||||||
role="alert"
|
role="alert"
|
||||||
>
|
>
|
||||||
Should be combination of numbers & alphabets
|
Should be combination of numbers & alphabets
|
||||||
@ -7154,9 +7166,10 @@ exports[`renders ./components/form/demo/validate-static.md correctly 1`] = `
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="ant-form-item-explain ant-form-item-explain-validating"
|
class="ant-form-item-explain"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
|
class="ant-form-item-explain-validating"
|
||||||
role="alert"
|
role="alert"
|
||||||
>
|
>
|
||||||
The information is being validated...
|
The information is being validated...
|
||||||
@ -7242,9 +7255,10 @@ exports[`renders ./components/form/demo/validate-static.md correctly 1`] = `
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="ant-form-item-explain ant-form-item-explain-error"
|
class="ant-form-item-explain"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
|
class="ant-form-item-explain-error"
|
||||||
role="alert"
|
role="alert"
|
||||||
>
|
>
|
||||||
Please select the correct date
|
Please select the correct date
|
||||||
@ -7720,6 +7734,97 @@ exports[`renders ./components/form/demo/validate-static.md correctly 1`] = `
|
|||||||
</form>
|
</form>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports[`renders ./components/form/demo/warning-only.md correctly 1`] = `
|
||||||
|
<form
|
||||||
|
autocomplete="off"
|
||||||
|
class="ant-form ant-form-vertical"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style="overflow:hidden"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="ant-row ant-form-item"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="ant-col ant-form-item-label"
|
||||||
|
>
|
||||||
|
<label
|
||||||
|
class="ant-form-item-required"
|
||||||
|
for="url"
|
||||||
|
title="URL"
|
||||||
|
>
|
||||||
|
URL
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="ant-col ant-form-item-control"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="ant-form-item-control-input"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="ant-form-item-control-input-content"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
class="ant-input"
|
||||||
|
id="url"
|
||||||
|
placeholder="input placeholder"
|
||||||
|
type="text"
|
||||||
|
value=""
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="ant-row ant-form-item"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="ant-col ant-form-item-control"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="ant-form-item-control-input"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="ant-form-item-control-input-content"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="ant-space ant-space-horizontal ant-space-align-center"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="ant-space-item"
|
||||||
|
style="margin-right:8px"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
class="ant-btn ant-btn-primary"
|
||||||
|
type="submit"
|
||||||
|
>
|
||||||
|
<span>
|
||||||
|
Submit
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="ant-space-item"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
class="ant-btn"
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<span>
|
||||||
|
Fill
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
`;
|
||||||
|
|
||||||
exports[`renders ./components/form/demo/without-form-create.md correctly 1`] = `
|
exports[`renders ./components/form/demo/without-form-create.md correctly 1`] = `
|
||||||
<form
|
<form
|
||||||
class="ant-form ant-form-horizontal"
|
class="ant-form ant-form-horizontal"
|
||||||
@ -7828,6 +7933,7 @@ exports[`renders ./components/form/demo/without-form-create.md correctly 1`] = `
|
|||||||
class="ant-form-item-explain"
|
class="ant-form-item-explain"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
|
class=""
|
||||||
role="alert"
|
role="alert"
|
||||||
>
|
>
|
||||||
A prime is a natural number greater than 1 that has no positive divisors other than 1 and itself.
|
A prime is a natural number greater than 1 that has no positive divisors other than 1 and itself.
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import { mount } from 'enzyme';
|
import { mount } from 'enzyme';
|
||||||
|
import { act } from 'react-dom/test-utils';
|
||||||
import scrollIntoView from 'scroll-into-view-if-needed';
|
import scrollIntoView from 'scroll-into-view-if-needed';
|
||||||
import Form from '..';
|
import Form from '..';
|
||||||
import Input from '../../input';
|
import Input from '../../input';
|
||||||
@ -20,10 +21,17 @@ describe('Form', () => {
|
|||||||
scrollIntoView.mockImplementation(() => {});
|
scrollIntoView.mockImplementation(() => {});
|
||||||
const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
|
const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
|
||||||
|
|
||||||
async function change(wrapper, index, value) {
|
async function change(wrapper, index, value, executeMockTimer) {
|
||||||
wrapper.find(Input).at(index).simulate('change', { target: { value } });
|
wrapper.find(Input).at(index).simulate('change', { target: { value } });
|
||||||
await sleep(200);
|
await sleep(200);
|
||||||
|
|
||||||
|
if (executeMockTimer) {
|
||||||
|
act(() => {
|
||||||
|
jest.runAllTimers();
|
||||||
wrapper.update();
|
wrapper.update();
|
||||||
|
});
|
||||||
|
await sleep(1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
@ -42,6 +50,8 @@ describe('Form', () => {
|
|||||||
|
|
||||||
describe('noStyle Form.Item', () => {
|
describe('noStyle Form.Item', () => {
|
||||||
it('work', async () => {
|
it('work', async () => {
|
||||||
|
jest.useFakeTimers();
|
||||||
|
|
||||||
const onChange = jest.fn();
|
const onChange = jest.fn();
|
||||||
|
|
||||||
const wrapper = mount(
|
const wrapper = mount(
|
||||||
@ -54,14 +64,18 @@ describe('Form', () => {
|
|||||||
</Form>,
|
</Form>,
|
||||||
);
|
);
|
||||||
|
|
||||||
await change(wrapper, 0, '');
|
await change(wrapper, 0, '', true);
|
||||||
expect(wrapper.find('.ant-form-item-with-help').length).toBeTruthy();
|
expect(wrapper.find('.ant-form-item-with-help').length).toBeTruthy();
|
||||||
expect(wrapper.find('.ant-form-item-has-error').length).toBeTruthy();
|
expect(wrapper.find('.ant-form-item-has-error').length).toBeTruthy();
|
||||||
|
|
||||||
expect(onChange).toHaveBeenCalled();
|
expect(onChange).toHaveBeenCalled();
|
||||||
|
|
||||||
|
jest.useRealTimers();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should clean up', async () => {
|
it('should clean up', async () => {
|
||||||
|
jest.useFakeTimers();
|
||||||
|
|
||||||
const Demo = () => {
|
const Demo = () => {
|
||||||
const [form] = Form.useForm();
|
const [form] = Form.useForm();
|
||||||
|
|
||||||
@ -105,12 +119,14 @@ describe('Form', () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const wrapper = mount(<Demo />);
|
const wrapper = mount(<Demo />);
|
||||||
await change(wrapper, 0, '1');
|
await change(wrapper, 0, '1', true);
|
||||||
expect(wrapper.find('.ant-form-item-explain').text()).toEqual('aaa');
|
expect(wrapper.find('.ant-form-item-explain').text()).toEqual('aaa');
|
||||||
await change(wrapper, 0, '2');
|
await change(wrapper, 0, '2', true);
|
||||||
expect(wrapper.find('.ant-form-item-explain').text()).toEqual('ccc');
|
expect(wrapper.find('.ant-form-item-explain').text()).toEqual('ccc');
|
||||||
await change(wrapper, 0, '1');
|
await change(wrapper, 0, '1', true);
|
||||||
expect(wrapper.find('.ant-form-item-explain').text()).toEqual('aaa');
|
expect(wrapper.find('.ant-form-item-explain').text()).toEqual('aaa');
|
||||||
|
|
||||||
|
jest.useRealTimers();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -315,6 +331,8 @@ describe('Form', () => {
|
|||||||
|
|
||||||
// https://github.com/ant-design/ant-design/issues/20706
|
// https://github.com/ant-design/ant-design/issues/20706
|
||||||
it('Error change should work', async () => {
|
it('Error change should work', async () => {
|
||||||
|
jest.useFakeTimers();
|
||||||
|
|
||||||
const wrapper = mount(
|
const wrapper = mount(
|
||||||
<Form>
|
<Form>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
@ -338,15 +356,17 @@ describe('Form', () => {
|
|||||||
|
|
||||||
/* eslint-disable no-await-in-loop */
|
/* eslint-disable no-await-in-loop */
|
||||||
for (let i = 0; i < 3; i += 1) {
|
for (let i = 0; i < 3; i += 1) {
|
||||||
await change(wrapper, 0, '');
|
await change(wrapper, 0, '', true);
|
||||||
expect(wrapper.find('.ant-form-item-explain').first().text()).toEqual("'name' is required");
|
expect(wrapper.find('.ant-form-item-explain').first().text()).toEqual("'name' is required");
|
||||||
|
|
||||||
await change(wrapper, 0, 'p');
|
await change(wrapper, 0, 'p', true);
|
||||||
await sleep(100);
|
await sleep(100);
|
||||||
wrapper.update();
|
wrapper.update();
|
||||||
expect(wrapper.find('.ant-form-item-explain').first().text()).toEqual('not a p');
|
expect(wrapper.find('.ant-form-item-explain').first().text()).toEqual('not a p');
|
||||||
}
|
}
|
||||||
/* eslint-enable */
|
/* eslint-enable */
|
||||||
|
|
||||||
|
jest.useRealTimers();
|
||||||
});
|
});
|
||||||
|
|
||||||
// https://github.com/ant-design/ant-design/issues/20813
|
// https://github.com/ant-design/ant-design/issues/20813
|
||||||
@ -428,6 +448,8 @@ describe('Form', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('Form.Item with `help` should display error style when validate failed', async () => {
|
it('Form.Item with `help` should display error style when validate failed', async () => {
|
||||||
|
jest.useFakeTimers();
|
||||||
|
|
||||||
const wrapper = mount(
|
const wrapper = mount(
|
||||||
<Form>
|
<Form>
|
||||||
<Form.Item name="test" help="help" rules={[{ required: true, message: 'message' }]}>
|
<Form.Item name="test" help="help" rules={[{ required: true, message: 'message' }]}>
|
||||||
@ -436,12 +458,16 @@ describe('Form', () => {
|
|||||||
</Form>,
|
</Form>,
|
||||||
);
|
);
|
||||||
|
|
||||||
await change(wrapper, 0, '');
|
await change(wrapper, 0, '', true);
|
||||||
expect(wrapper.find('.ant-form-item').first().hasClass('ant-form-item-has-error')).toBeTruthy();
|
expect(wrapper.find('.ant-form-item').first().hasClass('ant-form-item-has-error')).toBeTruthy();
|
||||||
expect(wrapper.find('.ant-form-item-explain').text()).toEqual('help');
|
expect(wrapper.find('.ant-form-item-explain').text()).toEqual('help');
|
||||||
|
|
||||||
|
jest.useRealTimers();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('clear validation message when ', async () => {
|
it('clear validation message when ', async () => {
|
||||||
|
jest.useFakeTimers();
|
||||||
|
|
||||||
const wrapper = mount(
|
const wrapper = mount(
|
||||||
<Form>
|
<Form>
|
||||||
<Form.Item name="username" rules={[{ required: true, message: 'message' }]}>
|
<Form.Item name="username" rules={[{ required: true, message: 'message' }]}>
|
||||||
@ -449,14 +475,18 @@ describe('Form', () => {
|
|||||||
</Form.Item>
|
</Form.Item>
|
||||||
</Form>,
|
</Form>,
|
||||||
);
|
);
|
||||||
await change(wrapper, 0, '1');
|
await change(wrapper, 0, '1', true);
|
||||||
expect(wrapper.find('.ant-form-item-explain').length).toBeFalsy();
|
expect(wrapper.find('.ant-form-item-explain').length).toBeFalsy();
|
||||||
await change(wrapper, 0, '');
|
|
||||||
|
await change(wrapper, 0, '', true);
|
||||||
expect(wrapper.find('.ant-form-item-explain').length).toBeTruthy();
|
expect(wrapper.find('.ant-form-item-explain').length).toBeTruthy();
|
||||||
await change(wrapper, 0, '123');
|
|
||||||
|
await change(wrapper, 0, '123', true);
|
||||||
await sleep(800);
|
await sleep(800);
|
||||||
wrapper.update();
|
wrapper.update();
|
||||||
expect(wrapper.find('.ant-form-item-explain').length).toBeFalsy();
|
expect(wrapper.find('.ant-form-item-explain').length).toBeFalsy();
|
||||||
|
|
||||||
|
jest.useRealTimers();
|
||||||
});
|
});
|
||||||
|
|
||||||
// https://github.com/ant-design/ant-design/issues/21167
|
// https://github.com/ant-design/ant-design/issues/21167
|
||||||
|
@ -200,34 +200,6 @@ describe('Form.List', () => {
|
|||||||
jest.useRealTimers();
|
jest.useRealTimers();
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('ErrorList component', () => {
|
|
||||||
it('should trigger onDomErrorVisibleChange by motion end', async () => {
|
|
||||||
jest.useFakeTimers();
|
|
||||||
|
|
||||||
const onDomErrorVisibleChange = jest.fn();
|
|
||||||
const wrapper = mount(
|
|
||||||
<Form.ErrorList
|
|
||||||
errors={['bamboo is light']}
|
|
||||||
onDomErrorVisibleChange={onDomErrorVisibleChange}
|
|
||||||
/>,
|
|
||||||
);
|
|
||||||
|
|
||||||
await act(async () => {
|
|
||||||
await sleep();
|
|
||||||
jest.runAllTimers();
|
|
||||||
wrapper.update();
|
|
||||||
});
|
|
||||||
|
|
||||||
act(() => {
|
|
||||||
wrapper.find('CSSMotion').props().onLeaveEnd();
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(onDomErrorVisibleChange).toHaveBeenCalledWith(false);
|
|
||||||
|
|
||||||
jest.useRealTimers();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should render empty without errors', () => {
|
it('should render empty without errors', () => {
|
||||||
const wrapper = mount(<Form.ErrorList />);
|
const wrapper = mount(<Form.ErrorList />);
|
||||||
expect(wrapper.render()).toMatchSnapshot();
|
expect(wrapper.render()).toMatchSnapshot();
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import omit from 'rc-util/lib/omit';
|
import omit from 'rc-util/lib/omit';
|
||||||
|
import { Meta } from 'rc-field-form/lib/interface';
|
||||||
import { FormProvider as RcFormProvider } from 'rc-field-form';
|
import { FormProvider as RcFormProvider } from 'rc-field-form';
|
||||||
import { FormProviderProps as RcFormProviderProps } from 'rc-field-form/lib/FormContext';
|
import { FormProviderProps as RcFormProviderProps } from 'rc-field-form/lib/FormContext';
|
||||||
import { ColProps } from '../grid/col';
|
import { ColProps } from '../grid/col';
|
||||||
@ -25,14 +26,9 @@ export const FormContext = React.createContext<FormContextProps>({
|
|||||||
itemRef: (() => {}) as any,
|
itemRef: (() => {}) as any,
|
||||||
});
|
});
|
||||||
|
|
||||||
/** Form Item Context. Used for Form noStyle Item error collection */
|
/** `noStyle` Form Item Context. Used for error collection */
|
||||||
export interface FormItemContextProps {
|
export type ReportMetaChange = (meta: Meta, uniqueKeys: React.Key[]) => void;
|
||||||
updateItemErrors: (name: string, errors: string[], originName?: string) => void;
|
export const NoStyleItemContext = React.createContext<ReportMetaChange | null>(null);
|
||||||
}
|
|
||||||
|
|
||||||
export const FormItemContext = React.createContext<FormItemContextProps>({
|
|
||||||
updateItemErrors: () => {},
|
|
||||||
});
|
|
||||||
|
|
||||||
/** Form Provider */
|
/** Form Provider */
|
||||||
export interface FormProviderProps extends Omit<RcFormProviderProps, 'validateMessages'> {
|
export interface FormProviderProps extends Omit<RcFormProviderProps, 'validateMessages'> {
|
||||||
|
@ -40,6 +40,7 @@ const Demo = () => {
|
|||||||
initialValues={{ remember: true }}
|
initialValues={{ remember: true }}
|
||||||
onFinish={onFinish}
|
onFinish={onFinish}
|
||||||
onFinishFailed={onFinishFailed}
|
onFinishFailed={onFinishFailed}
|
||||||
|
autoComplete="off"
|
||||||
>
|
>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
label="Username"
|
label="Username"
|
||||||
|
73
components/form/demo/warning-only.md
Normal file
73
components/form/demo/warning-only.md
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
---
|
||||||
|
order: 3.2
|
||||||
|
title:
|
||||||
|
zh-CN: 非阻塞校验
|
||||||
|
en-US: No block rule
|
||||||
|
---
|
||||||
|
|
||||||
|
## zh-CN
|
||||||
|
|
||||||
|
`rule` 添加 `warningOnly` 后校验不再阻塞表单提交。
|
||||||
|
|
||||||
|
## en-US
|
||||||
|
|
||||||
|
`rule` with `warningOnly` will not block form submit.
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import React from 'react';
|
||||||
|
import { Form, Input, message, Button, Space } from 'antd';
|
||||||
|
|
||||||
|
const Demo = () => {
|
||||||
|
const [form] = Form.useForm();
|
||||||
|
|
||||||
|
const onFinish = () => {
|
||||||
|
message.success('Submit success!');
|
||||||
|
};
|
||||||
|
|
||||||
|
const onFinishFailed = () => {
|
||||||
|
message.error('Submit failed!');
|
||||||
|
};
|
||||||
|
|
||||||
|
const onFill = () => {
|
||||||
|
form.setFieldsValue({
|
||||||
|
url: 'https://taobao.com/',
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Form
|
||||||
|
form={form}
|
||||||
|
layout="vertical"
|
||||||
|
onFinish={onFinish}
|
||||||
|
onFinishFailed={onFinishFailed}
|
||||||
|
autoComplete="off"
|
||||||
|
>
|
||||||
|
<div style={{ overflow: 'hidden' }}>
|
||||||
|
<Form.Item
|
||||||
|
name="url"
|
||||||
|
label="URL"
|
||||||
|
rules={[
|
||||||
|
{ required: true },
|
||||||
|
{ type: 'url', warningOnly: true },
|
||||||
|
{ type: 'string', min: 6 },
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<Input placeholder="input placeholder" />
|
||||||
|
</Form.Item>
|
||||||
|
</div>
|
||||||
|
<Form.Item>
|
||||||
|
<Space>
|
||||||
|
<Button type="primary" htmlType="submit">
|
||||||
|
Submit
|
||||||
|
</Button>
|
||||||
|
<Button htmlType="button" onClick={onFill}>
|
||||||
|
Fill
|
||||||
|
</Button>
|
||||||
|
</Space>
|
||||||
|
</Form.Item>
|
||||||
|
</Form>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
ReactDOM.render(<Demo />, mountNode);
|
||||||
|
```
|
@ -1,47 +0,0 @@
|
|||||||
import * as React from 'react';
|
|
||||||
import useForceUpdate from '../../_util/hooks/useForceUpdate';
|
|
||||||
|
|
||||||
/** Always debounce error to avoid [error -> null -> error] blink */
|
|
||||||
export default function useCacheErrors(
|
|
||||||
errors: React.ReactNode[],
|
|
||||||
changeTrigger: (visible: boolean) => void,
|
|
||||||
directly: boolean,
|
|
||||||
): [boolean, React.ReactNode[]] {
|
|
||||||
const cacheRef = React.useRef({
|
|
||||||
errors,
|
|
||||||
visible: !!errors.length,
|
|
||||||
});
|
|
||||||
|
|
||||||
const forceUpdate = useForceUpdate();
|
|
||||||
|
|
||||||
const update = () => {
|
|
||||||
const prevVisible = cacheRef.current.visible;
|
|
||||||
const newVisible = !!errors.length;
|
|
||||||
|
|
||||||
const prevErrors = cacheRef.current.errors;
|
|
||||||
cacheRef.current.errors = errors;
|
|
||||||
cacheRef.current.visible = newVisible;
|
|
||||||
|
|
||||||
if (prevVisible !== newVisible) {
|
|
||||||
changeTrigger(newVisible);
|
|
||||||
} else if (
|
|
||||||
prevErrors.length !== errors.length ||
|
|
||||||
prevErrors.some((prevErr, index) => prevErr !== errors[index])
|
|
||||||
) {
|
|
||||||
forceUpdate();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
React.useEffect(() => {
|
|
||||||
if (!directly) {
|
|
||||||
const timeout = setTimeout(update, 10);
|
|
||||||
return () => clearTimeout(timeout);
|
|
||||||
}
|
|
||||||
}, [errors]);
|
|
||||||
|
|
||||||
if (directly) {
|
|
||||||
update();
|
|
||||||
}
|
|
||||||
|
|
||||||
return [cacheRef.current.visible, cacheRef.current.errors];
|
|
||||||
}
|
|
19
components/form/hooks/useDebounce.ts
Normal file
19
components/form/hooks/useDebounce.ts
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import * as React from 'react';
|
||||||
|
|
||||||
|
export default function useDebounce<T>(value: T[]): T[] {
|
||||||
|
const [cacheValue, setCacheValue] = React.useState(value);
|
||||||
|
React.useEffect(() => {
|
||||||
|
const timeout = setTimeout(
|
||||||
|
() => {
|
||||||
|
setCacheValue(value);
|
||||||
|
},
|
||||||
|
value.length ? 0 : 10,
|
||||||
|
);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
clearTimeout(timeout);
|
||||||
|
};
|
||||||
|
}, [value]);
|
||||||
|
|
||||||
|
return cacheValue;
|
||||||
|
}
|
@ -302,22 +302,23 @@ Rule supports a config object, or a function returning config object:
|
|||||||
type Rule = RuleConfig | ((form: FormInstance) => RuleConfig);
|
type Rule = RuleConfig | ((form: FormInstance) => RuleConfig);
|
||||||
```
|
```
|
||||||
|
|
||||||
| Name | Description | Type |
|
| Name | Description | Type | Version |
|
||||||
| --- | --- | --- |
|
| --- | --- | --- | --- |
|
||||||
| defaultField | Validate rule for all array elements, valid when `type` is `array` | [rule](#Rule) |
|
| defaultField | Validate rule for all array elements, valid when `type` is `array` | [rule](#Rule) | |
|
||||||
| enum | Match enum value. You need to set `type` to `enum` to enable this | any\[] |
|
| enum | Match enum value. You need to set `type` to `enum` to enable this | any\[] | |
|
||||||
| fields | Validate rule for child elements, valid when `type` is `array` or `object` | Record<string, [rule](#Rule)> |
|
| fields | Validate rule for child elements, valid when `type` is `array` or `object` | Record<string, [rule](#Rule)> | |
|
||||||
| len | Length of string, number, array | number |
|
| len | Length of string, number, array | number | |
|
||||||
| max | `type` required: max length of `string`, `number`, `array` | number |
|
| max | `type` required: max length of `string`, `number`, `array` | number | |
|
||||||
| message | Error message. Will auto generate by [template](#validateMessages) if not provided | string |
|
| message | Error message. Will auto generate by [template](#validateMessages) if not provided | string | |
|
||||||
| min | `type` required: min length of `string`, `number`, `array` | number |
|
| min | `type` required: min length of `string`, `number`, `array` | number | |
|
||||||
| pattern | Regex pattern | RegExp |
|
| pattern | Regex pattern | RegExp | |
|
||||||
| required | Required field | boolean |
|
| required | Required field | boolean | |
|
||||||
| transform | Transform value to the rule before validation | (value) => any |
|
| transform | Transform value to the rule before validation | (value) => any | |
|
||||||
| type | Normally `string` \|`number` \|`boolean` \|`url` \| `email`. More type to ref [here](https://github.com/yiminghe/async-validator#type) | string |
|
| type | Normally `string` \|`number` \|`boolean` \|`url` \| `email`. More type to ref [here](https://github.com/yiminghe/async-validator#type) | string | |
|
||||||
| validateTrigger | Set validate trigger event. Must be the sub set of `validateTrigger` in Form.Item | string \| string\[] |
|
| validateTrigger | Set validate trigger event. Must be the sub set of `validateTrigger` in Form.Item | string \| string\[] | |
|
||||||
| validator | Customize validation rule. Accept Promise as return. See [example](#components-form-demo-register) | ([rule](#Rule), value) => Promise |
|
| validator | Customize validation rule. Accept Promise as return. See [example](#components-form-demo-register) | ([rule](#Rule), value) => Promise | |
|
||||||
| whitespace | Failed if only has whitespace | boolean |
|
| warningOnly | Warning only. Not block form submit | boolean | 4.17.0 |
|
||||||
|
| whitespace | Failed if only has whitespace | boolean | |
|
||||||
|
|
||||||
## Migrate to v4
|
## Migrate to v4
|
||||||
|
|
||||||
|
@ -301,22 +301,23 @@ Rule 支持接收 object 进行配置,也支持 function 来动态获取 form
|
|||||||
type Rule = RuleConfig | ((form: FormInstance) => RuleConfig);
|
type Rule = RuleConfig | ((form: FormInstance) => RuleConfig);
|
||||||
```
|
```
|
||||||
|
|
||||||
| 名称 | 说明 | 类型 |
|
| 名称 | 说明 | 类型 | 版本 |
|
||||||
| --- | --- | --- |
|
| --- | --- | --- | --- |
|
||||||
| defaultField | 仅在 `type` 为 `array` 类型时有效,用于指定数组元素的校验规则 | [rule](#Rule) |
|
| defaultField | 仅在 `type` 为 `array` 类型时有效,用于指定数组元素的校验规则 | [rule](#Rule) | |
|
||||||
| enum | 是否匹配枚举中的值(需要将 `type` 设置为 `enum`) | any\[] |
|
| enum | 是否匹配枚举中的值(需要将 `type` 设置为 `enum`) | any\[] | |
|
||||||
| fields | 仅在 `type` 为 `array` 或 `object` 类型时有效,用于指定子元素的校验规则 | Record<string, [rule](#Rule)> |
|
| fields | 仅在 `type` 为 `array` 或 `object` 类型时有效,用于指定子元素的校验规则 | Record<string, [rule](#Rule)> | |
|
||||||
| len | string 类型时为字符串长度;number 类型时为确定数字; array 类型时为数组长度 | number |
|
| len | string 类型时为字符串长度;number 类型时为确定数字; array 类型时为数组长度 | number | |
|
||||||
| max | 必须设置 `type`:string 类型为字符串最大长度;number 类型时为最大值;array 类型时为数组最大长度 | number |
|
| max | 必须设置 `type`:string 类型为字符串最大长度;number 类型时为最大值;array 类型时为数组最大长度 | number | |
|
||||||
| message | 错误信息,不设置时会通过[模板](#validateMessages)自动生成 | string |
|
| message | 错误信息,不设置时会通过[模板](#validateMessages)自动生成 | string | |
|
||||||
| min | 必须设置 `type`:string 类型为字符串最小长度;number 类型时为最小值;array 类型时为数组最小长度 | number |
|
| min | 必须设置 `type`:string 类型为字符串最小长度;number 类型时为最小值;array 类型时为数组最小长度 | number | |
|
||||||
| pattern | 正则表达式匹配 | RegExp |
|
| pattern | 正则表达式匹配 | RegExp | |
|
||||||
| required | 是否为必选字段 | boolean |
|
| required | 是否为必选字段 | boolean | |
|
||||||
| transform | 将字段值转换成目标值后进行校验 | (value) => any |
|
| transform | 将字段值转换成目标值后进行校验 | (value) => any | |
|
||||||
| type | 类型,常见有 `string` \|`number` \|`boolean` \|`url` \| `email`。更多请参考[此处](https://github.com/yiminghe/async-validator#type) | string |
|
| type | 类型,常见有 `string` \|`number` \|`boolean` \|`url` \| `email`。更多请参考[此处](https://github.com/yiminghe/async-validator#type) | string | |
|
||||||
| validateTrigger | 设置触发验证时机,必须是 Form.Item 的 `validateTrigger` 的子集 | string \| string\[] |
|
| validateTrigger | 设置触发验证时机,必须是 Form.Item 的 `validateTrigger` 的子集 | string \| string\[] | |
|
||||||
| validator | 自定义校验,接收 Promise 作为返回值。[示例](#components-form-demo-register)参考 | ([rule](#Rule), value) => Promise |
|
| validator | 自定义校验,接收 Promise 作为返回值。[示例](#components-form-demo-register)参考 | ([rule](#Rule), value) => Promise | |
|
||||||
| whitespace | 如果字段仅包含空格则校验不通过 | boolean |
|
| warningOnly | 仅警告,不阻塞表单提交 | boolean | 4.17.0 |
|
||||||
|
| whitespace | 如果字段仅包含空格则校验不通过 | boolean | |
|
||||||
|
|
||||||
## 从 v3 升级到 v4
|
## 从 v3 升级到 v4
|
||||||
|
|
||||||
|
@ -61,9 +61,12 @@
|
|||||||
|
|
||||||
margin-bottom: @form-item-margin-bottom;
|
margin-bottom: @form-item-margin-bottom;
|
||||||
vertical-align: top;
|
vertical-align: top;
|
||||||
|
// We delay one frame (0.017s) here to let CSSMotion goes
|
||||||
|
transition: margin-bottom @animation-duration-slow 0.017s linear;
|
||||||
|
|
||||||
&-with-help {
|
&-with-help {
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
|
transition: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
&-hidden,
|
&-hidden,
|
||||||
@ -89,7 +92,6 @@
|
|||||||
|
|
||||||
> label {
|
> label {
|
||||||
position: relative;
|
position: relative;
|
||||||
// display: inline;
|
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
height: @form-item-label-height;
|
height: @form-item-label-height;
|
||||||
@ -179,10 +181,12 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ==============================================================
|
||||||
|
// = Explain =
|
||||||
|
// ==============================================================
|
||||||
&-explain,
|
&-explain,
|
||||||
&-extra {
|
&-extra {
|
||||||
clear: both;
|
clear: both;
|
||||||
min-height: @form-item-margin-bottom;
|
|
||||||
color: @text-color-secondary;
|
color: @text-color-secondary;
|
||||||
font-size: @font-size-base;
|
font-size: @font-size-base;
|
||||||
line-height: @line-height-base;
|
line-height: @line-height-base;
|
||||||
@ -190,43 +194,64 @@
|
|||||||
.explainAndExtraDistance((@form-item-margin-bottom - @form-font-height) / 2);
|
.explainAndExtraDistance((@form-item-margin-bottom - @form-font-height) / 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&-explain {
|
||||||
|
height: 0;
|
||||||
|
min-height: 0;
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-extra {
|
||||||
|
min-height: @form-item-margin-bottom;
|
||||||
|
}
|
||||||
|
|
||||||
.@{ant-prefix}-input-textarea-show-count {
|
.@{ant-prefix}-input-textarea-show-count {
|
||||||
&::after {
|
&::after {
|
||||||
margin-bottom: -22px;
|
margin-bottom: -22px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
.show-help-motion(@className, @keyframeName, @duration: @animation-duration-slow) {
|
&-with-help &-explain {
|
||||||
@name: ~'@{ant-prefix}-@{className}';
|
height: auto;
|
||||||
.make-motion(@name, @keyframeName, @duration);
|
min-height: @form-item-margin-bottom;
|
||||||
.@{name}-enter,
|
|
||||||
.@{name}-appear {
|
|
||||||
opacity: 0;
|
|
||||||
animation-timing-function: @ease-in-out;
|
|
||||||
}
|
|
||||||
.@{name}-leave {
|
|
||||||
animation-timing-function: @ease-in-out;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.show-help-motion(show-help, antShowHelp, 0.3s);
|
|
||||||
|
|
||||||
@keyframes antShowHelpIn {
|
|
||||||
0% {
|
|
||||||
transform: translateY(-5px);
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
100% {
|
|
||||||
transform: translateY(0);
|
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes antShowHelpOut {
|
// >>>>>>>>>> Motion <<<<<<<<<<
|
||||||
to {
|
// Explain holder
|
||||||
|
.@{ant-prefix}-show-help {
|
||||||
|
transition: height @animation-duration-slow linear, min-height @animation-duration-slow linear,
|
||||||
|
margin-bottom @animation-duration-slow @ease-in-out,
|
||||||
|
opacity @animation-duration-slow @ease-in-out;
|
||||||
|
|
||||||
|
&-leave {
|
||||||
|
min-height: @form-item-margin-bottom;
|
||||||
|
|
||||||
|
&-active {
|
||||||
|
min-height: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Explain
|
||||||
|
.@{ant-prefix}-show-help-item {
|
||||||
|
overflow: hidden;
|
||||||
|
transition: height @animation-duration-slow @ease-in-out,
|
||||||
|
opacity @animation-duration-slow @ease-in-out, transform @animation-duration-slow @ease-in-out !important;
|
||||||
|
|
||||||
|
&-appear,
|
||||||
|
&-enter {
|
||||||
transform: translateY(-5px);
|
transform: translateY(-5px);
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
|
|
||||||
|
&-active {
|
||||||
|
transform: translateY(0);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&-leave-active {
|
||||||
|
transform: translateY(-5px);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -9,11 +9,11 @@
|
|||||||
// ========================= Explain =========================
|
// ========================= Explain =========================
|
||||||
/* To support leave along ErrorList. We add additional className to handle explain style */
|
/* To support leave along ErrorList. We add additional className to handle explain style */
|
||||||
&-explain {
|
&-explain {
|
||||||
&&-error {
|
&-error {
|
||||||
color: @error-color;
|
color: @error-color;
|
||||||
}
|
}
|
||||||
|
|
||||||
&&-warning {
|
&-warning {
|
||||||
color: @warning-color;
|
color: @warning-color;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -16705,7 +16705,21 @@ exports[`renders ./components/table/demo/sticky.md correctly 1`] = `
|
|||||||
colspan="2"
|
colspan="2"
|
||||||
style="position:sticky;left:0"
|
style="position:sticky;left:0"
|
||||||
>
|
>
|
||||||
Fix Left
|
<button
|
||||||
|
aria-checked="false"
|
||||||
|
class="ant-switch"
|
||||||
|
role="switch"
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="ant-switch-handle"
|
||||||
|
/>
|
||||||
|
<span
|
||||||
|
class="ant-switch-inner"
|
||||||
|
>
|
||||||
|
Fixed Top
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
</td>
|
</td>
|
||||||
<td
|
<td
|
||||||
class="ant-table-cell"
|
class="ant-table-cell"
|
||||||
|
@ -14,7 +14,7 @@ title:
|
|||||||
For long table,need to scroll to view the header and scroll bar,then you can now set the fixed header and scroll bar to follow the page.
|
For long table,need to scroll to view the header and scroll bar,then you can now set the fixed header and scroll bar to follow the page.
|
||||||
|
|
||||||
```jsx
|
```jsx
|
||||||
import { Table } from 'antd';
|
import { Table, Switch } from 'antd';
|
||||||
|
|
||||||
const columns = [
|
const columns = [
|
||||||
{
|
{
|
||||||
@ -93,16 +93,26 @@ for (let i = 0; i < 100; i++) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
ReactDOM.render(
|
const Demo = () => {
|
||||||
|
const [fixedTop, setFixedTop] = React.useState(false);
|
||||||
|
|
||||||
|
return (
|
||||||
<Table
|
<Table
|
||||||
columns={columns}
|
columns={columns}
|
||||||
dataSource={data}
|
dataSource={data}
|
||||||
scroll={{ x: 1500 }}
|
scroll={{ x: 1500 }}
|
||||||
summary={pageData => (
|
summary={pageData => (
|
||||||
<Table.Summary fixed>
|
<Table.Summary fixed={fixedTop ? 'top' : 'bottom'}>
|
||||||
<Table.Summary.Row>
|
<Table.Summary.Row>
|
||||||
<Table.Summary.Cell index={0} colSpan={2}>
|
<Table.Summary.Cell index={0} colSpan={2}>
|
||||||
Fix Left
|
<Switch
|
||||||
|
checkedChildren="Fixed Top"
|
||||||
|
unCheckedChildren="Fixed Top"
|
||||||
|
checked={fixedTop}
|
||||||
|
onChange={() => {
|
||||||
|
setFixedTop(!fixedTop);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
</Table.Summary.Cell>
|
</Table.Summary.Cell>
|
||||||
<Table.Summary.Cell index={2} colSpan={8}>
|
<Table.Summary.Cell index={2} colSpan={8}>
|
||||||
Scroll Context
|
Scroll Context
|
||||||
@ -112,7 +122,9 @@ ReactDOM.render(
|
|||||||
</Table.Summary>
|
</Table.Summary>
|
||||||
)}
|
)}
|
||||||
sticky
|
sticky
|
||||||
/>,
|
/>
|
||||||
mountNode,
|
);
|
||||||
);
|
};
|
||||||
|
|
||||||
|
ReactDOM.render(<Demo />, mountNode);
|
||||||
```
|
```
|
||||||
|
@ -381,3 +381,111 @@ exports[`renders ./components/tree-select/demo/treeData.md correctly 1`] = `
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports[`renders ./components/tree-select/demo/treeLine.md correctly 1`] = `
|
||||||
|
<div
|
||||||
|
class="ant-space ant-space-vertical"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="ant-space-item"
|
||||||
|
style="margin-bottom:8px"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
aria-checked="true"
|
||||||
|
class="ant-switch ant-switch-checked"
|
||||||
|
role="switch"
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="ant-switch-handle"
|
||||||
|
/>
|
||||||
|
<span
|
||||||
|
class="ant-switch-inner"
|
||||||
|
>
|
||||||
|
treeLine
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="ant-space-item"
|
||||||
|
style="margin-bottom:8px"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
aria-checked="false"
|
||||||
|
class="ant-switch"
|
||||||
|
role="switch"
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="ant-switch-handle"
|
||||||
|
/>
|
||||||
|
<span
|
||||||
|
class="ant-switch-inner"
|
||||||
|
>
|
||||||
|
showLeafIcon
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="ant-space-item"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="ant-select ant-tree-select ant-select-single ant-select-show-arrow"
|
||||||
|
style="width:300px"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="ant-select-selector"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="ant-select-selection-search"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
aria-activedescendant="undefined_list_0"
|
||||||
|
aria-autocomplete="list"
|
||||||
|
aria-controls="undefined_list"
|
||||||
|
aria-haspopup="listbox"
|
||||||
|
aria-owns="undefined_list"
|
||||||
|
autocomplete="off"
|
||||||
|
class="ant-select-selection-search-input"
|
||||||
|
readonly=""
|
||||||
|
role="combobox"
|
||||||
|
style="opacity:0"
|
||||||
|
type="search"
|
||||||
|
unselectable="on"
|
||||||
|
value=""
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
class="ant-select-selection-placeholder"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<span
|
||||||
|
aria-hidden="true"
|
||||||
|
class="ant-select-arrow"
|
||||||
|
style="user-select:none;-webkit-user-select:none"
|
||||||
|
unselectable="on"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
aria-label="down"
|
||||||
|
class="anticon anticon-down ant-select-suffix"
|
||||||
|
role="img"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
data-icon="down"
|
||||||
|
fill="currentColor"
|
||||||
|
focusable="false"
|
||||||
|
height="1em"
|
||||||
|
viewBox="64 64 896 896"
|
||||||
|
width="1em"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M884 256h-75c-5.1 0-9.9 2.5-12.9 6.6L512 654.2 227.9 262.6c-3-4.1-7.8-6.6-12.9-6.6h-75c-6.5 0-10.3 7.4-6.5 12.7l352.6 486.1c12.8 17.6 39 17.6 51.7 0l352.6-486.1c3.9-5.3.1-12.7-6.4-12.7z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
@ -45,6 +45,7 @@ class Demo extends React.Component {
|
|||||||
treeData: this.state.treeData.concat([
|
treeData: this.state.treeData.concat([
|
||||||
this.genTreeNode(id, false),
|
this.genTreeNode(id, false),
|
||||||
this.genTreeNode(id, true),
|
this.genTreeNode(id, true),
|
||||||
|
this.genTreeNode(id, true),
|
||||||
]),
|
]),
|
||||||
});
|
});
|
||||||
resolve();
|
resolve();
|
||||||
|
56
components/tree-select/demo/treeLine.md
Normal file
56
components/tree-select/demo/treeLine.md
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
---
|
||||||
|
order: 6
|
||||||
|
title:
|
||||||
|
zh-CN: 线性样式
|
||||||
|
en-US: Show Tree Line
|
||||||
|
---
|
||||||
|
|
||||||
|
## zh-CN
|
||||||
|
|
||||||
|
通过 `treeLine` 配置线性样式。
|
||||||
|
|
||||||
|
## en-US
|
||||||
|
|
||||||
|
Use `treeLine` to show the line style.
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import { TreeSelect, Switch, Space } from 'antd';
|
||||||
|
|
||||||
|
const { TreeNode } = TreeSelect;
|
||||||
|
|
||||||
|
const Demo = () => {
|
||||||
|
const [treeLine, setTreeLine] = React.useState(true);
|
||||||
|
const [showLeafIcon, setShowLeafIcon] = React.useState(false);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Space direction="vertical">
|
||||||
|
<Switch
|
||||||
|
checkedChildren="treeLine"
|
||||||
|
unCheckedChildren="treeLine"
|
||||||
|
checked={treeLine}
|
||||||
|
onChange={() => setTreeLine(!treeLine)}
|
||||||
|
/>
|
||||||
|
<Switch
|
||||||
|
disabled={!treeLine}
|
||||||
|
checkedChildren="showLeafIcon"
|
||||||
|
unCheckedChildren="showLeafIcon"
|
||||||
|
checked={showLeafIcon}
|
||||||
|
onChange={() => setShowLeafIcon(!showLeafIcon)}
|
||||||
|
/>
|
||||||
|
<TreeSelect treeLine={treeLine && { showLeafIcon }} style={{ width: 300 }}>
|
||||||
|
<TreeNode value="parent 1" title="parent 1">
|
||||||
|
<TreeNode value="parent 1-0" title="parent 1-0">
|
||||||
|
<TreeNode value="leaf1" title="my leaf" />
|
||||||
|
<TreeNode value="leaf2" title="your leaf" />
|
||||||
|
</TreeNode>
|
||||||
|
<TreeNode value="parent 1-1" title="parent 1-1">
|
||||||
|
<TreeNode value="sss" title="sss" />
|
||||||
|
</TreeNode>
|
||||||
|
</TreeNode>
|
||||||
|
</TreeSelect>
|
||||||
|
</Space>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
ReactDOM.render(<Demo />, mountNode);
|
||||||
|
```
|
@ -50,6 +50,7 @@ Tree selection control.
|
|||||||
| treeDefaultExpandedKeys | Default expanded treeNodes | string\[] | - | |
|
| treeDefaultExpandedKeys | Default expanded treeNodes | string\[] | - | |
|
||||||
| treeExpandedKeys | Set expanded keys | string\[] | - | |
|
| treeExpandedKeys | Set expanded keys | string\[] | - | |
|
||||||
| treeIcon | Shows the icon before a TreeNode's title. There is no default style; you must set a custom style for it if set to `true` | boolean | false | |
|
| treeIcon | Shows the icon before a TreeNode's title. There is no default style; you must set a custom style for it if set to `true` | boolean | false | |
|
||||||
|
| treeLine | Show the line. Ref [Tree - showLine](/components/tree/#components-tree-demo-line) | boolean \| object | false | 4.17.0 |
|
||||||
| treeNodeFilterProp | Will be used for filtering if `filterTreeNode` returns true | string | `value` | |
|
| treeNodeFilterProp | Will be used for filtering if `filterTreeNode` returns true | string | `value` | |
|
||||||
| treeNodeLabelProp | Will render as content of select | string | `title` | |
|
| treeNodeLabelProp | Will render as content of select | string | `title` | |
|
||||||
| value | To set the current selected treeNode(s) | string \| string\[] | - | |
|
| value | To set the current selected treeNode(s) | string \| string\[] | - | |
|
||||||
@ -63,7 +64,7 @@ Tree selection control.
|
|||||||
### Tree Methods
|
### Tree Methods
|
||||||
|
|
||||||
| Name | Description | Version |
|
| Name | Description | Version |
|
||||||
| --- | --- | --- |
|
| ------- | ------------ | ------- |
|
||||||
| blur() | Remove focus | |
|
| blur() | Remove focus | |
|
||||||
| focus() | Get focus | |
|
| focus() | Get focus | |
|
||||||
|
|
||||||
|
@ -11,7 +11,7 @@ import omit from 'rc-util/lib/omit';
|
|||||||
import { DefaultValueType } from 'rc-tree-select/lib/interface';
|
import { DefaultValueType } from 'rc-tree-select/lib/interface';
|
||||||
import { ConfigContext } from '../config-provider';
|
import { ConfigContext } from '../config-provider';
|
||||||
import devWarning from '../_util/devWarning';
|
import devWarning from '../_util/devWarning';
|
||||||
import { AntTreeNodeProps } from '../tree';
|
import { AntTreeNodeProps, TreeProps } from '../tree';
|
||||||
import getIcons from '../select/utils/iconUtil';
|
import getIcons from '../select/utils/iconUtil';
|
||||||
import renderSwitcherIcon from '../tree/utils/iconUtil';
|
import renderSwitcherIcon from '../tree/utils/iconUtil';
|
||||||
import SizeContext, { SizeType } from '../config-provider/SizeContext';
|
import SizeContext, { SizeType } from '../config-provider/SizeContext';
|
||||||
@ -30,11 +30,18 @@ export type SelectValue = RawValue | RawValue[] | LabeledValue | LabeledValue[];
|
|||||||
export interface TreeSelectProps<T>
|
export interface TreeSelectProps<T>
|
||||||
extends Omit<
|
extends Omit<
|
||||||
RcTreeSelectProps<T>,
|
RcTreeSelectProps<T>,
|
||||||
'showTreeIcon' | 'treeMotion' | 'inputIcon' | 'mode' | 'getInputElement' | 'backfill'
|
| 'showTreeIcon'
|
||||||
|
| 'treeMotion'
|
||||||
|
| 'inputIcon'
|
||||||
|
| 'mode'
|
||||||
|
| 'getInputElement'
|
||||||
|
| 'backfill'
|
||||||
|
| 'treeLine'
|
||||||
> {
|
> {
|
||||||
suffixIcon?: React.ReactNode;
|
suffixIcon?: React.ReactNode;
|
||||||
size?: SizeType;
|
size?: SizeType;
|
||||||
bordered?: boolean;
|
bordered?: boolean;
|
||||||
|
treeLine?: TreeProps['showLine'];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface RefTreeSelectProps {
|
export interface RefTreeSelectProps {
|
||||||
@ -140,6 +147,7 @@ const InternalTreeSelect = <T extends DefaultValueType>(
|
|||||||
treeCheckable={
|
treeCheckable={
|
||||||
treeCheckable ? <span className={`${prefixCls}-tree-checkbox-inner`} /> : treeCheckable
|
treeCheckable ? <span className={`${prefixCls}-tree-checkbox-inner`} /> : treeCheckable
|
||||||
}
|
}
|
||||||
|
treeLine={!!treeLine}
|
||||||
inputIcon={suffixIcon}
|
inputIcon={suffixIcon}
|
||||||
multiple={multiple}
|
multiple={multiple}
|
||||||
removeIcon={removeIcon}
|
removeIcon={removeIcon}
|
||||||
|
@ -51,6 +51,7 @@ cover: https://gw.alipayobjects.com/zos/alicdn/Ax4DA0njr/TreeSelect.svg
|
|||||||
| treeDefaultExpandedKeys | 默认展开的树节点 | string\[] | - | |
|
| treeDefaultExpandedKeys | 默认展开的树节点 | string\[] | - | |
|
||||||
| treeExpandedKeys | 设置展开的树节点 | string\[] | - | |
|
| treeExpandedKeys | 设置展开的树节点 | string\[] | - | |
|
||||||
| treeIcon | 是否展示 TreeNode title 前的图标,没有默认样式,如设置为 true,需要自行定义图标相关样式 | boolean | false | |
|
| treeIcon | 是否展示 TreeNode title 前的图标,没有默认样式,如设置为 true,需要自行定义图标相关样式 | boolean | false | |
|
||||||
|
| treeLine | 是否展示线条样式,请参考 [Tree - showLine](/components/tree/#components-tree-demo-line) | boolean \| object | false | 4.17.0 |
|
||||||
| treeNodeFilterProp | 输入项过滤对应的 treeNode 属性 | string | `value` | |
|
| treeNodeFilterProp | 输入项过滤对应的 treeNode 属性 | string | `value` | |
|
||||||
| treeNodeLabelProp | 作为显示的 prop 设置 | string | `title` | |
|
| treeNodeLabelProp | 作为显示的 prop 设置 | string | `title` | |
|
||||||
| value | 指定当前选中的条目 | string \| string\[] | - | |
|
| value | 指定当前选中的条目 | string \| string\[] | - | |
|
||||||
@ -64,7 +65,7 @@ cover: https://gw.alipayobjects.com/zos/alicdn/Ax4DA0njr/TreeSelect.svg
|
|||||||
### Tree 方法
|
### Tree 方法
|
||||||
|
|
||||||
| 名称 | 描述 | 版本 |
|
| 名称 | 描述 | 版本 |
|
||||||
| --- | --- | --- |
|
| ------- | -------- | ---- |
|
||||||
| blur() | 移除焦点 | |
|
| blur() | 移除焦点 | |
|
||||||
| focus() | 获取焦点 | |
|
| focus() | 获取焦点 | |
|
||||||
|
|
||||||
@ -73,7 +74,7 @@ cover: https://gw.alipayobjects.com/zos/alicdn/Ax4DA0njr/TreeSelect.svg
|
|||||||
> 建议使用 treeData 来代替 TreeNode,免去手工构造麻烦
|
> 建议使用 treeData 来代替 TreeNode,免去手工构造麻烦
|
||||||
|
|
||||||
| 参数 | 说明 | 类型 | 默认值 | 版本 |
|
| 参数 | 说明 | 类型 | 默认值 | 版本 |
|
||||||
| --- | --- | --- | --- | --- |
|
| --------------- | -------------------------------------------------- | --------- | ------ | ---- |
|
||||||
| checkable | 当树为 Checkbox 时,设置独立节点是否展示 Checkbox | boolean | - | |
|
| checkable | 当树为 Checkbox 时,设置独立节点是否展示 Checkbox | boolean | - | |
|
||||||
| disableCheckbox | 禁掉 Checkbox | boolean | false | |
|
| disableCheckbox | 禁掉 Checkbox | boolean | false | |
|
||||||
| disabled | 是否禁用 | boolean | false | |
|
| disabled | 是否禁用 | boolean | false | |
|
||||||
|
@ -11,7 +11,7 @@
|
|||||||
.@{tree-select-prefix-cls} {
|
.@{tree-select-prefix-cls} {
|
||||||
// ======================= Dropdown =======================
|
// ======================= Dropdown =======================
|
||||||
&-dropdown {
|
&-dropdown {
|
||||||
padding: @padding-xs (@padding-xs / 2) 0;
|
padding: @padding-xs (@padding-xs / 2);
|
||||||
|
|
||||||
&-rtl {
|
&-rtl {
|
||||||
direction: rtl;
|
direction: rtl;
|
||||||
@ -24,8 +24,6 @@
|
|||||||
align-items: stretch;
|
align-items: stretch;
|
||||||
|
|
||||||
.@{select-tree-prefix-cls}-treenode {
|
.@{select-tree-prefix-cls}-treenode {
|
||||||
padding-bottom: @padding-xs;
|
|
||||||
|
|
||||||
.@{select-tree-prefix-cls}-node-content-wrapper {
|
.@{select-tree-prefix-cls}-node-content-wrapper {
|
||||||
flex: auto;
|
flex: auto;
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
@import '../../style/mixins/index';
|
@import '../../style/mixins/index';
|
||||||
|
|
||||||
@tree-prefix-cls: ~'@{ant-prefix}-tree';
|
@tree-prefix-cls: ~'@{ant-prefix}-tree';
|
||||||
@tree-node-prefix-cls: ~'@{tree-prefix-cls}-treenode';
|
|
||||||
@select-tree-prefix-cls: ~'@{ant-prefix}-select-tree';
|
@select-tree-prefix-cls: ~'@{ant-prefix}-select-tree';
|
||||||
@tree-motion: ~'@{ant-prefix}-motion-collapse';
|
@tree-motion: ~'@{ant-prefix}-motion-collapse';
|
||||||
@tree-node-padding: (@padding-xs / 2);
|
@tree-node-padding: (@padding-xs / 2);
|
||||||
@ -259,10 +258,9 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
.@{tree-node-prefix-cls}-leaf-last {
|
.@{custom-tree-node-prefix-cls}-leaf-last {
|
||||||
.@{tree-prefix-cls}-switcher {
|
.@{custom-tree-prefix-cls}-switcher {
|
||||||
&-leaf-line {
|
&-leaf-line {
|
||||||
&::before {
|
&::before {
|
||||||
top: auto !important;
|
top: auto !important;
|
||||||
@ -271,4 +269,5 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -122,12 +122,12 @@
|
|||||||
"rc-dialog": "~8.5.1",
|
"rc-dialog": "~8.5.1",
|
||||||
"rc-drawer": "~4.3.0",
|
"rc-drawer": "~4.3.0",
|
||||||
"rc-dropdown": "~3.2.0",
|
"rc-dropdown": "~3.2.0",
|
||||||
"rc-field-form": "~1.20.0",
|
"rc-field-form": "~1.21.0-2",
|
||||||
"rc-image": "~5.2.4",
|
"rc-image": "~5.2.4",
|
||||||
"rc-input-number": "~7.1.0",
|
"rc-input-number": "~7.1.0",
|
||||||
"rc-mentions": "~1.6.1",
|
"rc-mentions": "~1.6.1",
|
||||||
"rc-menu": "~9.0.9",
|
"rc-menu": "~9.0.9",
|
||||||
"rc-motion": "^2.4.0",
|
"rc-motion": "^2.4.4",
|
||||||
"rc-notification": "~4.5.7",
|
"rc-notification": "~4.5.7",
|
||||||
"rc-pagination": "~3.1.6",
|
"rc-pagination": "~3.1.6",
|
||||||
"rc-picker": "~2.5.10",
|
"rc-picker": "~2.5.10",
|
||||||
@ -138,7 +138,7 @@
|
|||||||
"rc-slider": "~9.7.1",
|
"rc-slider": "~9.7.1",
|
||||||
"rc-steps": "~4.1.0",
|
"rc-steps": "~4.1.0",
|
||||||
"rc-switch": "~3.2.0",
|
"rc-switch": "~3.2.0",
|
||||||
"rc-table": "~7.15.1",
|
"rc-table": "~7.16.0",
|
||||||
"rc-tabs": "~11.9.1",
|
"rc-tabs": "~11.9.1",
|
||||||
"rc-textarea": "~0.3.0",
|
"rc-textarea": "~0.3.0",
|
||||||
"rc-tooltip": "~5.1.1",
|
"rc-tooltip": "~5.1.1",
|
||||||
|
Loading…
Reference in New Issue
Block a user