chore: next merge master

This commit is contained in:
zombiej 2022-07-15 11:23:50 +08:00
commit 3ffd0d8a46
10 changed files with 162 additions and 1874 deletions

View File

@ -7,7 +7,7 @@ import { convertLegacyProps } from '../button/button';
export interface ActionButtonProps {
type?: LegacyButtonType;
actionFn?: (...args: any[]) => any | PromiseLike<any>;
close: Function;
close?: Function;
autoFocus?: boolean;
prefixCls: string;
buttonProps?: ButtonProps;
@ -24,6 +24,10 @@ const ActionButton: React.FC<ActionButtonProps> = props => {
const clickedRef = React.useRef<boolean>(false);
const ref = React.useRef<any>();
const [loading, setLoading] = useState<ButtonProps['loading']>(false);
const { close } = props;
const onInternalClose = (...args: any[]) => {
close?.(...args);
};
React.useEffect(() => {
let timeoutId: any;
@ -39,7 +43,6 @@ const ActionButton: React.FC<ActionButtonProps> = props => {
}, []);
const handlePromiseOnOk = (returnValueOfOnOk?: PromiseLike<any>) => {
const { close } = props;
if (!isThenable(returnValueOfOnOk)) {
return;
}
@ -47,7 +50,7 @@ const ActionButton: React.FC<ActionButtonProps> = props => {
returnValueOfOnOk!.then(
(...args: any[]) => {
setLoading(false, true);
close(...args);
onInternalClose(...args);
clickedRef.current = false;
},
(e: Error) => {
@ -62,13 +65,13 @@ const ActionButton: React.FC<ActionButtonProps> = props => {
};
const onClick = (e: React.MouseEvent<HTMLButtonElement>) => {
const { actionFn, close } = props;
const { actionFn } = props;
if (clickedRef.current) {
return;
}
clickedRef.current = true;
if (!actionFn) {
close();
onInternalClose();
return;
}
let returnValueOfOnOk;
@ -76,7 +79,7 @@ const ActionButton: React.FC<ActionButtonProps> = props => {
returnValueOfOnOk = actionFn(e);
if (props.quitOnNullishReturnValue && !isThenable(returnValueOfOnOk)) {
clickedRef.current = false;
close(e);
onInternalClose(e);
return;
}
} else if (actionFn.length) {
@ -86,7 +89,7 @@ const ActionButton: React.FC<ActionButtonProps> = props => {
} else {
returnValueOfOnOk = actionFn();
if (!returnValueOfOnOk) {
close();
onInternalClose();
return;
}
}

View File

@ -8,7 +8,7 @@ import type { TransferLocale as TransferLocaleForEmpty } from '../empty';
import type { ModalLocale } from '../modal/locale';
import { changeConfirmLocale } from '../modal/locale';
import type { PaginationLocale } from '../pagination/Pagination';
import type { PopconfirmLocale } from '../popconfirm';
import type { PopconfirmLocale } from '../popconfirm/PurePanel';
import type { TableLocale } from '../table/interface';
import type { TransferLocale } from '../transfer';
import type { UploadLocale } from '../upload/interface';

View File

@ -0,0 +1,81 @@
import * as React from 'react';
import type { PopconfirmProps } from '.';
import Button from '../button';
import { convertLegacyProps } from '../button/button';
import ActionButton from '../_util/ActionButton';
import LocaleReceiver from '../locale-provider/LocaleReceiver';
import defaultLocale from '../locale/default';
import { getRenderPropValue } from '../_util/getRenderPropValue';
import { ConfigContext } from '../config-provider';
export interface PopconfirmLocale {
okText: string;
cancelText: string;
}
export interface OverlayProps
extends Pick<
PopconfirmProps,
| 'icon'
| 'okButtonProps'
| 'cancelButtonProps'
| 'cancelText'
| 'okText'
| 'okType'
| 'showCancel'
| 'title'
> {
prefixCls: string;
close?: Function;
onConfirm?: React.MouseEventHandler<HTMLButtonElement>;
onCancel?: React.MouseEventHandler<HTMLButtonElement>;
}
export function Overlay(props: OverlayProps) {
const {
prefixCls,
okButtonProps,
cancelButtonProps,
title,
cancelText,
okText,
okType,
icon,
showCancel = true,
close,
onConfirm,
onCancel,
} = props;
const { getPrefixCls } = React.useContext(ConfigContext);
return (
<LocaleReceiver componentName="Popconfirm" defaultLocale={defaultLocale.Popconfirm}>
{(popconfirmLocale: PopconfirmLocale) => (
<div className={`${prefixCls}-inner-content`}>
<div className={`${prefixCls}-message`}>
{icon}
<div className={`${prefixCls}-message-title`}>{getRenderPropValue(title)}</div>
</div>
<div className={`${prefixCls}-buttons`}>
{showCancel && (
<Button onClick={onCancel} size="small" {...cancelButtonProps}>
{cancelText || popconfirmLocale.cancelText}
</Button>
)}
<ActionButton
buttonProps={{ size: 'small', ...convertLegacyProps(okType), ...okButtonProps }}
actionFn={onConfirm}
close={close}
prefixCls={getPrefixCls('btn')}
quitOnNullishReturnValue
emitEvent
>
{okText || popconfirmLocale.okText}
</ActionButton>
</div>
</div>
)}
</LocaleReceiver>
);
}

View File

@ -3,19 +3,15 @@ import classNames from 'classnames';
import useMergedState from 'rc-util/lib/hooks/useMergedState';
import KeyCode from 'rc-util/lib/KeyCode';
import * as React from 'react';
import Button from '../button';
import type { ButtonProps, LegacyButtonType } from '../button/button';
import { convertLegacyProps } from '../button/button';
import { ConfigContext } from '../config-provider';
import LocaleReceiver from '../locale-provider/LocaleReceiver';
import defaultLocale from '../locale/default';
import Popover from '../popover';
import genPurePanel from '../_util/PurePanel';
import type { AbstractTooltipProps } from '../tooltip';
import ActionButton from '../_util/ActionButton';
import type { RenderFunction } from '../_util/getRenderPropValue';
import { getRenderPropValue } from '../_util/getRenderPropValue';
import { cloneElement } from '../_util/reactNode';
import { Overlay } from './PurePanel';
import usePopconfirmStyle from './style';
export interface PopconfirmProps extends AbstractTooltipProps {
@ -40,11 +36,6 @@ export interface PopconfirmState {
visible?: boolean;
}
export interface PopconfirmLocale {
okText: string;
cancelText: string;
}
const Popconfirm = React.forwardRef<unknown, PopconfirmProps>((props, ref) => {
const { getPrefixCls } = React.useContext(ConfigContext);
const [visible, setVisible] = useMergedState(false, {
@ -87,44 +78,6 @@ const Popconfirm = React.forwardRef<unknown, PopconfirmProps>((props, ref) => {
settingVisible(value);
};
const renderOverlay = (prefixCls: string, popconfirmLocale: PopconfirmLocale) => {
const {
okButtonProps,
cancelButtonProps,
title,
cancelText,
okText,
okType,
icon,
showCancel = true,
} = props;
return (
<div className={`${prefixCls}-inner-content`}>
<div className={`${prefixCls}-message`}>
{icon}
<div className={`${prefixCls}-message-title`}>{getRenderPropValue(title)}</div>
</div>
<div className={`${prefixCls}-buttons`}>
{showCancel && (
<Button onClick={onCancel} size="small" {...cancelButtonProps}>
{cancelText || popconfirmLocale.cancelText}
</Button>
)}
<ActionButton
buttonProps={{ size: 'small', ...convertLegacyProps(okType), ...okButtonProps }}
actionFn={onConfirm}
close={close}
prefixCls={getPrefixCls('btn')}
quitOnNullishReturnValue
emitEvent
>
{okText || popconfirmLocale.okText}
</ActionButton>
</div>
</div>
);
};
const {
prefixCls: customizePrefixCls,
placement,
@ -137,19 +90,21 @@ const Popconfirm = React.forwardRef<unknown, PopconfirmProps>((props, ref) => {
const [wrapSSR] = usePopconfirmStyle(prefixCls);
const overlay = (
<LocaleReceiver componentName="Popconfirm" defaultLocale={defaultLocale.Popconfirm}>
{(popconfirmLocale: PopconfirmLocale) => renderOverlay(prefixCls, popconfirmLocale)}
</LocaleReceiver>
);
return wrapSSR(
<Popover
{...restProps}
placement={placement}
onVisibleChange={onVisibleChange}
visible={visible}
_overlay={overlay}
_overlay={
<Overlay
{...props}
prefixCls={prefixCls}
close={close}
onConfirm={onConfirm}
onCancel={onCancel}
/>
}
overlayClassName={overlayClassNames}
ref={ref as any}
data-popover-inject

View File

@ -59,7 +59,7 @@ Select component to select value from options.
| removeIcon | The custom remove icon | ReactNode | - | |
| searchValue | The current input "search" text | string | - | |
| showArrow | Whether to show the drop-down arrow | boolean | true(for single select), false(for multiple select) | |
| showSearch | Whether show search input in single mode | boolean | false | |
| showSearch | Whether select is searchable | boolean | single: false, multple: true | |
| size | Size of Select input | `large` \| `middle` \| `small` | `middle` | |
| status | Set validation status | 'error' \| 'warning' | - | 4.19.0 |
| suffixIcon | The custom suffix icon | ReactNode | - | |
@ -107,7 +107,7 @@ Select component to select value from options.
## FAQ
### Why sometime search will show 2 same option when in `tag` mode?
### Why sometime search will show 2 same option when in `tags` mode?
It's caused by option with different `label` and `value`. You can use `optionFilterProp="label"` to change filter logic instead.

View File

@ -60,7 +60,7 @@ cover: https://gw.alipayobjects.com/zos/alicdn/_0XzgOis7/Select.svg
| removeIcon | 自定义的多选框清除图标 | ReactNode | - | |
| searchValue | 控制搜索文本 | string | - | |
| showArrow | 是否显示下拉小箭头 | boolean | 单选为 true多选为 false | |
| showSearch | 使单选模式可搜索 | boolean | false | |
| showSearch | 配置是否可搜索 | boolean | 单选为 false多选为 true | |
| size | 选择框大小 | `large` \| `middle` \| `small` | `middle` | |
| status | 设置校验状态 | 'error' \| 'warning' | - | 4.19.0 |
| suffixIcon | 自定义的选择框后缀图标 | ReactNode | - | |
@ -108,7 +108,7 @@ cover: https://gw.alipayobjects.com/zos/alicdn/_0XzgOis7/Select.svg
## FAQ
### `tag` 模式下为何搜索有时会出现两个相同选项?
### `mode="tags"` 模式下为何搜索有时会出现两个相同选项?
这一般是 `options` 中的 `label``value` 不同导致的,你可以通过 `optionFilterProp="label"` 将过滤设置为展示值以避免这种情况。

View File

@ -1,6 +1,3 @@
// import '../../style/index.less';
// import './index.less';
// deps-lint-skip-all
import type { CSSInterpolation, CSSObject } from '@ant-design/cssinjs';
import { Keyframes } from '@ant-design/cssinjs';
@ -163,6 +160,21 @@ export const genBaseStyle = (prefixCls: string, token: TreeToken): CSSObject =>
color: 'inherit',
fontWeight: 500,
},
'&-draggable': {
[`${treeCls}-draggable-icon`]: {
width: treeTitleHeight,
lineHeight: `${treeTitleHeight}px`,
textAlign: 'center',
visibility: 'visible',
opacity: 0.2,
transition: `opacity ${token.motionDurationSlow}`,
[`${treeNodeCls}:hover &`]: {
opacity: 0.45,
},
},
},
},
// >>> Indent
@ -178,15 +190,7 @@ export const genBaseStyle = (prefixCls: string, token: TreeToken): CSSObject =>
// >>> Drag Handler
[`${treeCls}-draggable-icon`]: {
width: treeTitleHeight,
lineHeight: `${treeTitleHeight}px`,
textAlign: 'center',
opacity: 0.2,
transition: `opacity ${token.motionDurationSlow}`,
[`${treeNodeCls}:hover &`]: {
opacity: 0.45,
},
visibility: 'hidden',
},
// >>> Switcher

View File

@ -928,6 +928,35 @@ describe('Upload List', () => {
unmount();
});
it('upload svg file with <foreignObject> should not have CORS error', async () => {
const mockFile = new File(
[
'<svg viewBox="0 0 200 200" xmlns="http://www.w3.org/2000/svg"><foreignObject x="20" y="20" width="160" height="160"><div xmlns="http://www.w3.org/1999/xhtml">Test</div></foreignObject></svg>',
],
'bar.svg',
{ type: 'image/svg+xml' },
);
const previewFunc = jest.fn(previewImage);
const { unmount } = render(
<Upload
fileList={[{ originFileObj: mockFile }]}
previewFile={previewFunc}
locale={{ uploading: 'uploading' }}
listType="picture-card"
/>,
);
await waitFor(() => {
expect(previewFunc).toHaveBeenCalled();
});
await previewFunc(mockFile).then(dataUrl => {
expect(dataUrl).toEqual('data:image/png;base64,');
});
unmount();
});
it("upload non image file shouldn't be converted to the base64", async () => {
const mockFile = new File([''], 'foo.7z', {
type: 'application/x-7z-compressed',

View File

@ -110,6 +110,15 @@ export function previewImage(file: File | Blob): Promise<string> {
resolve(dataURL);
};
img.src = window.URL.createObjectURL(file);
img.crossOrigin = 'anonymous';
if (file.type.startsWith('image/svg+xml')) {
const reader = new FileReader();
reader.addEventListener('load', () => {
if (reader.result) img.src = reader.result as string;
});
reader.readAsDataURL(file);
} else {
img.src = window.URL.createObjectURL(file);
}
});
}

File diff suppressed because it is too large Load Diff