chore: auto merge branchs (#35068)

chore: master merge feature
This commit is contained in:
github-actions[bot] 2022-04-18 02:31:35 +00:00 committed by GitHub
commit f8ed0480de
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 689 additions and 25 deletions

View File

@ -1,4 +1,5 @@
import * as React from 'react';
import { forwardRef, useContext } from 'react';
import classNames from 'classnames';
import CalendarOutlined from '@ant-design/icons/CalendarOutlined';
import ClockCircleOutlined from '@ant-design/icons/ClockCircleOutlined';
@ -7,14 +8,14 @@ import SwapRightOutlined from '@ant-design/icons/SwapRightOutlined';
import { RangePicker as RCRangePicker } from 'rc-picker';
import { GenerateConfig } from 'rc-picker/lib/generate/index';
import enUS from '../locale/en_US';
import { ConfigContext, ConfigConsumerProps } from '../../config-provider';
import { ConfigConsumerProps, ConfigContext } from '../../config-provider';
import SizeContext from '../../config-provider/SizeContext';
import LocaleReceiver from '../../locale-provider/LocaleReceiver';
import { getRangePlaceholder, transPlacement2DropdownAlign } from '../util';
import { RangePickerProps, PickerLocale, getTimeProps, Components } from '.';
import { PickerComponentClass } from './interface';
import { Components, getTimeProps, PickerLocale, RangePickerProps } from '.';
import { FormItemInputContext } from '../../form/context';
import { getMergedStatus, getStatusClassNames } from '../../_util/statusUtils';
import { PickerComponentClass } from './interface';
export default function generateRangePicker<DateType>(
generateConfig: GenerateConfig<DateType>,
@ -42,7 +43,7 @@ export default function generateRangePicker<DateType>(
const locale = { ...contextLocale, ...this.props.locale };
const { getPrefixCls, direction, getPopupContainer } = this.context;
const {
prefixCls: customizePrefixCls,
prefixCls,
getPopupContainer: customGetPopupContainer,
className,
placement,
@ -53,7 +54,6 @@ export default function generateRangePicker<DateType>(
...restProps
} = this.props;
const { format, showTime, picker } = this.props as any;
const prefixCls = getPrefixCls('picker', customizePrefixCls);
let additionalOverrideProps: any = {};
@ -105,7 +105,7 @@ export default function generateRangePicker<DateType>(
[`${prefixCls}-borderless`]: !bordered,
},
getStatusClassNames(
prefixCls,
prefixCls as string,
getMergedStatus(contextStatus, customStatus),
hasFeedback,
),
@ -136,5 +136,12 @@ export default function generateRangePicker<DateType>(
}
}
return RangePicker;
return forwardRef<RangePicker, RangePickerProps<DateType>>((props, ref) => {
const { prefixCls: customizePrefixCls } = props;
const { getPrefixCls } = useContext(ConfigContext);
const prefixCls = getPrefixCls('picker', customizePrefixCls);
return <RangePicker {...props} prefixCls={prefixCls} ref={ref} />;
}) as unknown as PickerComponentClass<RangePickerProps<DateType>>;
}

View File

@ -6,6 +6,7 @@ import CloseCircleFilled from '@ant-design/icons/CloseCircleFilled';
import RCPicker from 'rc-picker';
import { PickerMode } from 'rc-picker/lib/interface';
import { GenerateConfig } from 'rc-picker/lib/generate/index';
import { forwardRef, useContext } from 'react';
import enUS from '../locale/en_US';
import { getPlaceholder, transPlacement2DropdownAlign } from '../util';
import devWarning from '../../_util/devWarning';
@ -20,9 +21,9 @@ import {
getTimeProps,
Components,
} from '.';
import { PickerComponentClass } from './interface';
import { FormItemInputContext } from '../../form/context';
import { getMergedStatus, getStatusClassNames, InputStatus } from '../../_util/statusUtils';
import { DatePickRef, PickerComponentClass } from './interface';
export default function generatePicker<DateType>(generateConfig: GenerateConfig<DateType>) {
type DatePickerProps = PickerProps<DateType> & {
@ -67,7 +68,7 @@ export default function generatePicker<DateType>(generateConfig: GenerateConfig<
const locale = { ...contextLocale, ...this.props.locale };
const { getPrefixCls, direction, getPopupContainer } = this.context;
const {
prefixCls: customizePrefixCls,
prefixCls,
getPopupContainer: customizeGetPopupContainer,
className,
size: customizeSize,
@ -78,7 +79,6 @@ export default function generatePicker<DateType>(generateConfig: GenerateConfig<
...restProps
} = this.props;
const { format, showTime } = this.props as any;
const prefixCls = getPrefixCls('picker', customizePrefixCls);
const additionalProps = {
showToday: true,
@ -137,7 +137,7 @@ export default function generatePicker<DateType>(generateConfig: GenerateConfig<
[`${prefixCls}-borderless`]: !bordered,
},
getStatusClassNames(
prefixCls,
prefixCls as string,
getMergedStatus(contextStatus, customStatus),
hasFeedback,
),
@ -167,11 +167,26 @@ export default function generatePicker<DateType>(generateConfig: GenerateConfig<
}
}
const PickerWrapper = forwardRef<DatePickRef<DateType>, InnerPickerProps>((props, ref) => {
const { prefixCls: customizePrefixCls } = props;
const { getPrefixCls } = useContext(ConfigContext);
const prefixCls = getPrefixCls('picker', customizePrefixCls);
const pickerProps: InnerPickerProps = {
...props,
prefixCls,
ref,
};
return <Picker {...pickerProps} />;
});
if (displayName) {
Picker.displayName = displayName;
PickerWrapper.displayName = displayName;
}
return Picker as PickerComponentClass<InnerPickerProps>;
return PickerWrapper as unknown as PickerComponentClass<InnerPickerProps>;
}
const DatePicker = getPicker<DatePickerProps>();

View File

@ -1,7 +1,7 @@
import * as React from 'react';
import { useMemo } from 'react';
import classNames from 'classnames';
import FieldForm, { List } from 'rc-field-form';
import FieldForm, { List, useWatch } from 'rc-field-form';
import { FormProps as RcFormProps } from 'rc-field-form/lib/Form';
import { ValidateErrorEntity } from 'rc-field-form/lib/interface';
import { Options } from 'scroll-into-view-if-needed';
@ -101,8 +101,9 @@ const InternalForm: React.ForwardRefRenderFunction<FormInstance, FormProps> = (p
colon: mergedColon,
requiredMark: mergedRequiredMark,
itemRef: __INTERNAL__.itemRef,
form: wrapForm,
}),
[name, labelAlign, labelCol, wrapperCol, layout, mergedColon, mergedRequiredMark],
[name, labelAlign, labelCol, wrapperCol, layout, mergedColon, mergedRequiredMark, wrapForm],
);
React.useImperativeHandle(ref, () => wrapForm);
@ -140,6 +141,6 @@ const Form = React.forwardRef<FormInstance, FormProps>(InternalForm) as <Values
props: React.PropsWithChildren<FormProps<Values>> & { ref?: React.Ref<FormInstance<Values>> },
) => React.ReactElement;
export { useForm, List, FormInstance };
export { useForm, List, FormInstance, useWatch };
export default Form;

View File

@ -14850,6 +14850,157 @@ exports[`renders ./components/form/demo/time-related-controls.md extend context
</form>
`;
exports[`renders ./components/form/demo/useWatch.md extend context correctly 1`] = `
Array [
<form
autocomplete="off"
class="ant-form ant-form-vertical"
>
<div
class="ant-row ant-form-item"
>
<div
class="ant-col ant-form-item-label"
>
<label
class=""
for="name"
title="Name (Watch to trigger rerender)"
>
Name (Watch to trigger rerender)
</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="name"
type="text"
value=""
/>
</div>
</div>
</div>
</div>
<div
class="ant-row ant-form-item"
>
<div
class="ant-col ant-form-item-label"
>
<label
class=""
for="age"
title="Age (Not Watch)"
>
Age (Not Watch)
</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"
>
<div
class="ant-input-number ant-input-number-in-form-item"
>
<div
class="ant-input-number-handler-wrap"
>
<span
aria-disabled="false"
aria-label="Increase Value"
class="ant-input-number-handler ant-input-number-handler-up"
role="button"
unselectable="on"
>
<span
aria-label="up"
class="anticon anticon-up ant-input-number-handler-up-inner"
role="img"
>
<svg
aria-hidden="true"
data-icon="up"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M890.5 755.3L537.9 269.2c-12.8-17.6-39-17.6-51.7 0L133.5 755.3A8 8 0 00140 768h75c5.1 0 9.9-2.5 12.9-6.6L512 369.8l284.1 391.6c3 4.1 7.8 6.6 12.9 6.6h75c6.5 0 10.3-7.4 6.5-12.7z"
/>
</svg>
</span>
</span>
<span
aria-disabled="false"
aria-label="Decrease Value"
class="ant-input-number-handler ant-input-number-handler-down"
role="button"
unselectable="on"
>
<span
aria-label="down"
class="anticon anticon-down ant-input-number-handler-down-inner"
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
class="ant-input-number-input-wrap"
>
<input
autocomplete="off"
class="ant-input-number-input"
id="age"
role="spinbutton"
step="1"
value=""
/>
</div>
</div>
</div>
</div>
</div>
</div>
</form>,
<article
class="ant-typography"
>
<pre>
Name Value:
</pre>
</article>,
]
`;
exports[`renders ./components/form/demo/validate-other.md extend context correctly 1`] = `
<form
class="ant-form ant-form-horizontal"

View File

@ -6098,6 +6098,157 @@ exports[`renders ./components/form/demo/time-related-controls.md correctly 1`] =
</form>
`;
exports[`renders ./components/form/demo/useWatch.md correctly 1`] = `
Array [
<form
autocomplete="off"
class="ant-form ant-form-vertical"
>
<div
class="ant-row ant-form-item"
>
<div
class="ant-col ant-form-item-label"
>
<label
class=""
for="name"
title="Name (Watch to trigger rerender)"
>
Name (Watch to trigger rerender)
</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="name"
type="text"
value=""
/>
</div>
</div>
</div>
</div>
<div
class="ant-row ant-form-item"
>
<div
class="ant-col ant-form-item-label"
>
<label
class=""
for="age"
title="Age (Not Watch)"
>
Age (Not Watch)
</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"
>
<div
class="ant-input-number ant-input-number-in-form-item"
>
<div
class="ant-input-number-handler-wrap"
>
<span
aria-disabled="false"
aria-label="Increase Value"
class="ant-input-number-handler ant-input-number-handler-up"
role="button"
unselectable="on"
>
<span
aria-label="up"
class="anticon anticon-up ant-input-number-handler-up-inner"
role="img"
>
<svg
aria-hidden="true"
data-icon="up"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M890.5 755.3L537.9 269.2c-12.8-17.6-39-17.6-51.7 0L133.5 755.3A8 8 0 00140 768h75c5.1 0 9.9-2.5 12.9-6.6L512 369.8l284.1 391.6c3 4.1 7.8 6.6 12.9 6.6h75c6.5 0 10.3-7.4 6.5-12.7z"
/>
</svg>
</span>
</span>
<span
aria-disabled="false"
aria-label="Decrease Value"
class="ant-input-number-handler ant-input-number-handler-down"
role="button"
unselectable="on"
>
<span
aria-label="down"
class="anticon anticon-down ant-input-number-handler-down-inner"
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
class="ant-input-number-input-wrap"
>
<input
autocomplete="off"
class="ant-input-number-input"
id="age"
role="spinbutton"
step="1"
value=""
/>
</div>
</div>
</div>
</div>
</div>
</div>
</form>,
<article
class="ant-typography"
>
<pre>
Name Value:
</pre>
</article>,
]
`;
exports[`renders ./components/form/demo/validate-other.md correctly 1`] = `
<form
class="ant-form ant-form-horizontal"

View File

@ -1079,4 +1079,30 @@ describe('Form', () => {
expect(wrapper.find('.ant-form-item-no-colon')).toBeTruthy();
});
});
it('useFormInstance', () => {
let formInstance;
let subFormInstance;
const Sub = () => {
const formSub = Form.useFormInstance();
subFormInstance = formSub;
return null;
};
const Demo = () => {
const [form] = Form.useForm();
formInstance = form;
return (
<Form form={form}>
<Sub />
</Form>
);
};
render(<Demo />);
expect(subFormInstance).toBe(formInstance);
});
});

View File

@ -6,7 +6,7 @@ import { FormProviderProps as RcFormProviderProps } from 'rc-field-form/lib/Form
import { FC, PropsWithChildren, ReactNode, useMemo } from 'react';
import { ColProps } from '../grid/col';
import { FormLabelAlign } from './interface';
import { RequiredMark } from './Form';
import { FormInstance, RequiredMark } from './Form';
import { ValidateStatus } from './FormItem';
/** Form Context. Set top form style and pass to Form Item usage. */
@ -20,6 +20,7 @@ export interface FormContextProps {
wrapperCol?: ColProps;
requiredMark?: RequiredMark;
itemRef: (name: (string | number)[]) => (node: React.ReactElement) => void;
form?: FormInstance;
}
export const FormContext = React.createContext<FormContextProps>({

View File

@ -0,0 +1,44 @@
---
order: 3.3
version: 4.20.0
title:
zh-CN: 字段监听 Hooks
en-US: Watch Hooks
---
## zh-CN
`useWatch` 允许你监听字段变化同时仅当改字段变化时重新渲染。API 文档请[查阅此处](#Form.useWatch)。
## en-US
`useWatch` helps watch the field change and only re-render for the value change. [API Ref](#Form.useWatch).
```tsx
import React from 'react';
import { Form, Input, InputNumber, Typography } from 'antd';
const Demo = () => {
const [form] = Form.useForm<{ user: { name: string; age: number } }>();
const nameValue = Form.useWatch('name', form);
return (
<>
<Form form={form} layout="vertical" autoComplete="off">
<Form.Item name="name" label="Name (Watch to trigger rerender)">
<Input />
</Form.Item>
<Form.Item name="age" label="Age (Not Watch)">
<InputNumber />
</Form.Item>
</Form>
<Typography>
<pre>Name Value: {nameValue}</pre>
</Typography>
</>
);
};
export default Demo;
```

View File

@ -0,0 +1,9 @@
import { useContext } from 'react';
import { FormContext } from '../context';
import { FormInstance } from './useForm';
export default function useFormInstance<Value = any>(): FormInstance<Value> {
const { form } = useContext(FormContext);
return form!;
}

View File

@ -279,6 +279,65 @@ validateFields()
});
```
## Hooks
### Form.useForm
`type Form.useForm = (): FormInstance`
Create Form instance to maintain data store.
### Form.useFormInstance
`type Form.useFormInstance = (): FormInstance`
Added in `4.20.0`. Get current context form instance to avoid pass as props between components:
```tsx
const Sub = () => {
const form = Form.useFormInstance();
return <Button onClick={() => form.setFieldsValue({})} />;
};
export default () => {
const [form] = Form.useForm();
return (
<Form form={form}>
<Sub />
</Form>
);
};
```
### Form.useWatch
`type Form.useWatch = (namePath: NamePath, formInstance: FormInstance): Value`
Added in `4.20.0`. Watch the value of a field. You can use this to interactive with other hooks like `useSWR` to reduce develop cost:
```tsx
const Demo = () => {
const [form] = Form.useForm();
const userName = Form.useWatch('username', form);
const { data: options } = useSWR(`/api/user/${userName}`, fetcher);
return (
<Form form={form}>
<Form.Item name="username">
<AutoComplete options={options} />
</Form.Item>
</Form>
);
};
```
#### Difference between other data fetching method
Form only update the Field which changed to avoid full refresh perf issue. Thus you can not get real time value with `getFieldsValue` in render. And `useWatch` will rerender current component to sync with latest value. You can also use Field renderProps to get better performance if only want to do conditional render. If component no need care field value change, you can use `onValuesChange` to give to parent component to avoid current one rerender.
### Interface
#### NamePath

View File

@ -1,15 +1,18 @@
import { Rule, RuleObject, RuleRender } from 'rc-field-form/lib/interface';
import InternalForm, { useForm, FormInstance, FormProps } from './Form';
import InternalForm, { useForm, FormInstance, FormProps, useWatch } from './Form';
import Item, { FormItemProps } from './FormItem';
import ErrorList, { ErrorListProps } from './ErrorList';
import List, { FormListProps } from './FormList';
import { FormProvider } from './context';
import devWarning from '../_util/devWarning';
import useFormInstance from './hooks/useFormInstance';
type InternalFormType = typeof InternalForm;
interface FormInterface extends InternalFormType {
useForm: typeof useForm;
useFormInstance: typeof useFormInstance;
useWatch: typeof useWatch;
Item: typeof Item;
List: typeof List;
ErrorList: typeof ErrorList;
@ -25,6 +28,8 @@ Form.Item = Item;
Form.List = List;
Form.ErrorList = ErrorList;
Form.useForm = useForm;
Form.useFormInstance = useFormInstance;
Form.useWatch = useWatch;
Form.Provider = FormProvider;
Form.create = () => {
devWarning(

View File

@ -278,6 +278,65 @@ validateFields()
});
```
## Hooks
### Form.useForm
`type Form.useForm = (): FormInstance`
创建 Form 实例,用于管理所有数据状态。
### Form.useFormInstance
`type Form.useFormInstance = (): FormInstance`
`4.20.0` 新增,获取当前上下文正在使用的 Form 实例,常见于封装子组件消费无需透传 Form 实例:
```tsx
const Sub = () => {
const form = Form.useFormInstance();
return <Button onClick={() => form.setFieldsValue({})} />;
};
export default () => {
const [form] = Form.useForm();
return (
<Form form={form}>
<Sub />
</Form>
);
};
```
### Form.useWatch
`type Form.useWatch = (namePath: NamePath, formInstance: FormInstance): Value`
`4.20.0` 新增,用于直接获取 form 中字段对应的值。通过该 Hooks 可以与诸如 `useSWR` 进行联动从而降低维护成本:
```tsx
const Demo = () => {
const [form] = Form.useForm();
const userName = Form.useWatch('username', form);
const { data: options } = useSWR(`/api/user/${userName}`, fetcher);
return (
<Form form={form}>
<Form.Item name="username">
<AutoComplete options={options} />
</Form.Item>
</Form>
);
};
```
#### 与其他获取数据的方式的区别
Form 仅会对变更的 Field 进行刷新,从而避免完整的组件刷新可能引发的性能问题。因而你无法在 render 阶段通过 `form.getFieldsValue` 来实时获取字段值,而 `useWatch` 提供了一种特定字段访问的方式,从而使得在当前组件中可以直接消费字段的值。同时,如果为了更好的渲染性能,你可以通过 Field 的 renderProps 仅更新需要更新的部分。而当当前组件更新或者 effect 都不需要消费字段值时,则可以通过 `onValuesChange` 将数据抛出,从而避免组件更新。
### Interface
#### NamePath
@ -439,7 +498,7 @@ React 中异步更新会导致受控组件交互行为异常。当用户交互
### `setFieldsValue` 不会触发 `onFieldsChange``onValuesChange`
是的change 事件仅当用户交互才会触发。该设计是为了防止在 change 事件中调用 `setFieldsValue` 导致的循环问题。
是的change 事件仅当用户交互才会触发。该设计是为了防止在 change 事件中调用 `setFieldsValue` 导致的循环问题。如果仅仅需要组件内消费,可以通过 `useWatch` 或者 `Field.renderProps` 来实现。
### 有更多参考文档吗?

View File

@ -37,7 +37,8 @@ Previewable image.
src?: string; // V4.10.0
mask?: ReactNode; // V4.9.0
maskClassName?: string; // V4.11.0
current?: number; // V4.12.0 Only support PreviewGroup.
current?: number; // V4.12.0 Only support PreviewGroup
countRender?: (current: number, total: number) => string // Only support PreviewGroup
}
```

View File

@ -38,7 +38,8 @@ cover: https://gw.alipayobjects.com/zos/antfincdn/D1dXz9PZqa/image.svg
src?: string; // V4.10.0
mask?: ReactNode; // V4.9.0
maskClassName?: string; // V4.11.0
current?: number; // V4.12.0 仅支持 PreviewGroup。
current?: number; // V4.12.0 仅支持 PreviewGroup
countRender?: (current: number, total: number) => string // 仅支持 PreviewGroup
}
```

View File

@ -47,7 +47,7 @@ describe('TimePicker', () => {
it('clearIcon should render correctly', () => {
const clearIcon = <div className="test-clear-icon">test</div>;
const wrapper = mount(<TimePicker clearIcon={clearIcon} />);
expect(wrapper.find('Picker').prop('clearIcon')).toEqual(
expect(wrapper.find('Picker').last().prop('clearIcon')).toEqual(
<div className="test-clear-icon">test</div>,
);
});
@ -70,7 +70,7 @@ describe('TimePicker', () => {
popupClassName={popupClassName}
/>,
);
expect(wrapper.find('Picker').prop('dropdownClassName')).toEqual(popupClassName);
expect(wrapper.find('Picker').last().prop('dropdownClassName')).toEqual(popupClassName);
});
it('should pass popupClassName prop to RangePicker as dropdownClassName prop', () => {

View File

@ -104,6 +104,7 @@ const ListItem = React.forwardRef(
src={file.thumbUrl || file.url}
alt={file.name}
className={`${prefixCls}-list-item-image`}
crossOrigin={file.crossOrigin}
/>
) : (
iconNode

View File

@ -1241,4 +1241,134 @@ describe('Upload List', () => {
const wrapper = mount(<Upload fileList={null} />);
wrapper.unmount();
});
it('should not exist crossorigin attribute when does not set file.crossorigin in case of listType="picture"', () => {
const list = [
{
uid: '0',
name: 'xxx.png',
status: 'done',
url: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png',
thumbUrl: 'https://zos.alipayobjects.com/rmsportal/IQKRngzUuFzJzGzRJXUs.png',
},
];
const wrapper = mount(
<Upload fileList={list} listType="picture">
<button type="button">upload</button>
</Upload>,
);
list.forEach((file, i) => {
const imgNode = wrapper.find('.ant-upload-list-item-thumbnail img').at(i);
expect(imgNode.prop('crossOrigin')).toBe(undefined);
expect(imgNode.prop('crossOrigin')).toBe(file.crossOrigin);
});
wrapper.unmount();
});
it('should exist crossorigin attribute when set file.crossorigin in case of listType="picture"', () => {
const list = [
{
uid: '0',
name: 'xxx.png',
status: 'done',
url: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png',
thumbUrl: 'https://zos.alipayobjects.com/rmsportal/IQKRngzUuFzJzGzRJXUs.png',
crossOrigin: '',
},
{
uid: '1',
name: 'xxx.png',
status: 'done',
url: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png',
thumbUrl: 'https://zos.alipayobjects.com/rmsportal/IQKRngzUuFzJzGzRJXUs.png',
crossOrigin: 'anonymous',
},
{
uid: '2',
name: 'xxx.png',
status: 'done',
url: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png',
thumbUrl: 'https://zos.alipayobjects.com/rmsportal/IQKRngzUuFzJzGzRJXUs.png',
crossOrigin: 'use-credentials',
},
];
const wrapper = mount(
<Upload fileList={list} listType="picture">
<button type="button">upload</button>
</Upload>,
);
list.forEach((file, i) => {
const imgNode = wrapper.find('.ant-upload-list-item-thumbnail img').at(i);
expect(imgNode.prop('crossOrigin')).not.toBe(undefined);
expect(imgNode.prop('crossOrigin')).toBe(file.crossOrigin);
});
wrapper.unmount();
});
it('should not exist crossorigin attribute when does not set file.crossorigin in case of listType="picture-card"', () => {
const list = [
{
uid: '0',
name: 'xxx.png',
status: 'done',
url: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png',
thumbUrl: 'https://zos.alipayobjects.com/rmsportal/IQKRngzUuFzJzGzRJXUs.png',
},
];
const wrapper = mount(
<Upload fileList={list} listType="picture">
<button type="button">upload</button>
</Upload>,
);
list.forEach((file, i) => {
const imgNode = wrapper.find('.ant-upload-list-item-thumbnail img').at(i);
expect(imgNode.prop('crossOrigin')).toBe(undefined);
expect(imgNode.prop('crossOrigin')).toBe(file.crossOrigin);
});
wrapper.unmount();
});
it('should exist crossorigin attribute when set file.crossorigin in case of listType="picture-card"', () => {
const list = [
{
uid: '0',
name: 'xxx.png',
status: 'done',
url: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png',
thumbUrl: 'https://zos.alipayobjects.com/rmsportal/IQKRngzUuFzJzGzRJXUs.png',
crossOrigin: '',
},
{
uid: '1',
name: 'xxx.png',
status: 'done',
url: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png',
thumbUrl: 'https://zos.alipayobjects.com/rmsportal/IQKRngzUuFzJzGzRJXUs.png',
crossOrigin: 'anonymous',
},
{
uid: '2',
name: 'xxx.png',
status: 'done',
url: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png',
thumbUrl: 'https://zos.alipayobjects.com/rmsportal/IQKRngzUuFzJzGzRJXUs.png',
crossOrigin: 'use-credentials',
},
];
const wrapper = mount(
<Upload fileList={list} listType="picture">
<button type="button">upload</button>
</Upload>,
);
list.forEach((file, i) => {
const imgNode = wrapper.find('.ant-upload-list-item-thumbnail img').at(i);
expect(imgNode.prop('crossOrigin')).not.toBe(undefined);
expect(imgNode.prop('crossOrigin')).toBe(file.crossOrigin);
});
wrapper.unmount();
});
});

View File

@ -60,6 +60,7 @@ Extends File with additional props.
| thumbUrl | Thumb image url | string | - |
| uid | unique id. Will auto generate when not provided | string | - |
| url | Download url | string | - |
| crossOrigin | CORS settings attributes | `'anonymous'` \| `'use-credentials'` \| `''` | - |
### onChange

View File

@ -61,6 +61,7 @@ cover: https://gw.alipayobjects.com/zos/alicdn/QaeBt_ZMg/Upload.svg
| thumbUrl | 缩略图地址 | string | - |
| uid | 唯一标识符,不设置时会自动生成 | string | - |
| url | 下载地址 | string | - |
| crossOrigin | CORS 属性设置 | `'anonymous'` \| `'use-credentials'` \| `''` | - |
### onChange

View File

@ -27,6 +27,7 @@ export interface UploadFile<T = any> {
status?: UploadFileStatus;
percent?: number;
thumbUrl?: string;
crossOrigin?: React.ImgHTMLAttributes<HTMLImageElement>['crossOrigin'];
originFileObj?: RcFile;
response?: T;
error?: any;

View File

@ -129,8 +129,8 @@
"rc-dialog": "~8.7.0",
"rc-drawer": "~4.4.2",
"rc-dropdown": "~3.4.0",
"rc-field-form": "~1.25.0",
"rc-image": "~5.4.0",
"rc-field-form": "~1.26.1",
"rc-image": "~5.5.0",
"rc-input": "~0.0.1-alpha.5",
"rc-input-number": "~7.3.0",
"rc-mentions": "~1.7.0",