chore: auto merge branchs (#33745)

chore: sync master into feature
This commit is contained in:
github-actions[bot] 2022-01-18 03:16:08 +00:00 committed by GitHub
commit 5a93cc024d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 354 additions and 27 deletions

View File

@ -257,6 +257,16 @@ export default class Anchor extends React.Component<AnchorProps, AnchorState, Co
}
};
getMemoizedContextValue = memoizeOne(
(link: AntAnchor['activeLink'], onClickFn: AnchorProps['onClick']): AntAnchor => ({
registerLink: this.registerLink,
unregisterLink: this.unregisterLink,
scrollTo: this.handleScrollTo,
activeLink: link,
onClick: onClickFn,
}),
);
render() {
const { getPrefixCls, direction } = this.context;
const {
@ -310,13 +320,7 @@ export default class Anchor extends React.Component<AnchorProps, AnchorState, Co
</div>
);
const contextValue = memoizeOne((link, onClickFn) => ({
registerLink: this.registerLink,
unregisterLink: this.unregisterLink,
scrollTo: this.handleScrollTo,
activeLink: link,
onClick: onClickFn,
}))(activeLink, onClick);
const contextValue = this.getMemoizedContextValue(activeLink, onClick);
return (
<AnchorContext.Provider value={contextValue}>

View File

@ -0,0 +1,51 @@
import React, { memo, useState, useRef, useContext } from 'react';
import { mount } from 'enzyme';
import Anchor from '../Anchor';
import AnchorContext from '../context';
// we use'memo' here in order to only render inner component while context changed.
const CacheInner = memo(() => {
const countRef = useRef(0);
countRef.current++;
// subscribe anchor context
useContext(AnchorContext);
return (
<div>
Child Rendering Count: <span id="child_count">{countRef.current}</span>
</div>
);
});
const CacheOuter = () => {
// We use 'useState' here in order to trigger parent component rendering.
const [count, setCount] = useState(1);
const handleClick = () => {
setCount(count + 1);
};
// During each rendering phase, the cached context value returned from method 'Anchor#getMemoizedContextValue' will take effect.
// So 'CacheInner' component won't rerender.
return (
<div>
<button type="button" onClick={handleClick} id="parent_btn">
Click
</button>
Parent Rendering Count: <span id="parent_count">{count}</span>
<Anchor affix={false}>
<CacheInner />
</Anchor>
</div>
);
};
it("Rendering on Anchor without changed AnchorContext won't trigger rendering on child component.", () => {
const wrapper = mount(<CacheOuter />);
const childCount = wrapper.find('#child_count').text();
wrapper.find('#parent_btn').at(0).simulate('click');
expect(wrapper.find('#parent_count').text()).toBe('2');
// child component won't rerender
expect(wrapper.find('#child_count').text()).toBe(childCount);
wrapper.find('#parent_btn').at(0).simulate('click');
expect(wrapper.find('#parent_count').text()).toBe('3');
// child component won't rerender
expect(wrapper.find('#child_count').text()).toBe(childCount);
});

View File

@ -40,6 +40,57 @@ exports[`renders ./components/image/demo/basic.md extend context correctly 1`] =
</div>
`;
exports[`renders ./components/image/demo/controlled-preview.md extend context correctly 1`] = `
Array [
<button
class="ant-btn ant-btn-primary"
type="button"
>
<span>
show image preview
</span>
</button>,
<div
class="ant-image"
style="width:200px"
>
<img
class="ant-image-img"
src="https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png?x-oss-process=image/blur,r_50,s_50/quality,q_1/resize,m_mfit,h_200,w_200"
style="display:none"
/>
<div
class="ant-image-mask"
>
<div
class="ant-image-mask-info"
>
<span
aria-label="eye"
class="anticon anticon-eye"
role="img"
>
<svg
aria-hidden="true"
data-icon="eye"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M942.2 486.2C847.4 286.5 704.1 186 512 186c-192.2 0-335.4 100.5-430.2 300.3a60.3 60.3 0 000 51.5C176.6 737.5 319.9 838 512 838c192.2 0 335.4-100.5 430.2-300.3 7.7-16.2 7.7-35 0-51.5zM512 766c-161.3 0-279.4-81.8-362.7-254C232.6 339.8 350.7 258 512 258c161.3 0 279.4 81.8 362.7 254C791.5 684.2 673.4 766 512 766zm-4-430c-97.2 0-176 78.8-176 176s78.8 176 176 176 176-78.8 176-176-78.8-176-176-176zm0 288c-61.9 0-112-50.1-112-112s50.1-112 112-112 112 50.1 112 112-50.1 112-112 112z"
/>
</svg>
</span>
Preview
</div>
</div>
</div>,
]
`;
exports[`renders ./components/image/demo/fallback.md extend context correctly 1`] = `
<div
class="ant-image"

View File

@ -40,6 +40,57 @@ exports[`renders ./components/image/demo/basic.md correctly 1`] = `
</div>
`;
exports[`renders ./components/image/demo/controlled-preview.md correctly 1`] = `
Array [
<button
class="ant-btn ant-btn-primary"
type="button"
>
<span>
show image preview
</span>
</button>,
<div
class="ant-image"
style="width:200px"
>
<img
class="ant-image-img"
src="https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png?x-oss-process=image/blur,r_50,s_50/quality,q_1/resize,m_mfit,h_200,w_200"
style="display:none"
/>
<div
class="ant-image-mask"
>
<div
class="ant-image-mask-info"
>
<span
aria-label="eye"
class="anticon anticon-eye"
role="img"
>
<svg
aria-hidden="true"
data-icon="eye"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M942.2 486.2C847.4 286.5 704.1 186 512 186c-192.2 0-335.4 100.5-430.2 300.3a60.3 60.3 0 000 51.5C176.6 737.5 319.9 838 512 838c192.2 0 335.4-100.5 430.2-300.3 7.7-16.2 7.7-35 0-51.5zM512 766c-161.3 0-279.4-81.8-362.7-254C232.6 339.8 350.7 258 512 258c161.3 0 279.4 81.8 362.7 254C791.5 684.2 673.4 766 512 766zm-4-430c-97.2 0-176 78.8-176 176s78.8 176 176 176 176-78.8 176-176-78.8-176-176-176zm0 288c-61.9 0-112-50.1-112-112s50.1-112 112-112 112 50.1 112 112-50.1 112-112 112z"
/>
</svg>
</span>
Preview
</div>
</div>
</div>,
]
`;
exports[`renders ./components/image/demo/fallback.md correctly 1`] = `
<div
class="ant-image"

View File

@ -0,0 +1,44 @@
---
order: 7
title:
zh-CN: 受控的预览
en-US: Controlled Preview
---
## zh-CN
可以使预览受控。
## en-US
You can make preview controlled.
```jsx
import React, { useState } from 'react';
import { Image, Button } from 'antd';
function ImageDemo() {
const [visible, setVisible] = useState(false);
return (
<>
<Button type="primary" onClick={() => setVisible(true)}>
show image preview
</Button>
<Image
width={200}
style={{ display: 'none' }}
src="https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png?x-oss-process=image/blur,r_50,s_50/quality,q_1/resize,m_mfit,h_200,w_200"
preview={{
visible,
src: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png',
onVisibleChange: value => {
setVisible(value);
},
}}
/>
</>
);
}
ReactDOM.render(<ImageDemo />, mountNode);
```

View File

@ -148,7 +148,7 @@ class ClearableLabeledInput extends React.Component<ClearableInputProps> {
}
renderInputWithLabel(prefixCls: string, labeledElement: React.ReactElement) {
const { addonBefore, addonAfter, style, size, className, direction } = this.props;
const { addonBefore, addonAfter, style, size, className, direction, hidden } = this.props;
// Not wrap when there is not addons
if (!hasAddon(this.props)) {
return labeledElement;
@ -178,7 +178,7 @@ class ClearableLabeledInput extends React.Component<ClearableInputProps> {
// Need another wrapper for changing display:table to display:inline-block
// and put style prop in wrapper
return (
<span className={mergedGroupClassName} style={style}>
<span className={mergedGroupClassName} style={style} hidden={hidden}>
<span className={mergedWrapperClassName}>
{addonBeforeNode}
{cloneElement(labeledElement, { style: null })}
@ -189,7 +189,7 @@ class ClearableLabeledInput extends React.Component<ClearableInputProps> {
}
renderTextAreaWithClearIcon(prefixCls: string, element: React.ReactElement) {
const { value, allowClear, className, style, direction, bordered } = this.props;
const { value, allowClear, className, style, direction, bordered, hidden } = this.props;
if (!allowClear) {
return cloneElement(element, {
value,
@ -206,7 +206,7 @@ class ClearableLabeledInput extends React.Component<ClearableInputProps> {
},
);
return (
<span className={affixWrapperCls} style={style}>
<span className={affixWrapperCls} style={style} hidden={hidden}>
{cloneElement(element, {
style: null,
value,

View File

@ -58,6 +58,7 @@ const TextArea = React.forwardRef<TextAreaRef, TextAreaProps>(
const [value, setValue] = useMergedState(props.defaultValue, {
value: props.value,
});
const { hidden } = props;
const handleSetValue = (val: string, callback?: () => void) => {
if (props.value === undefined) {
@ -174,6 +175,7 @@ const TextArea = React.forwardRef<TextAreaRef, TextAreaProps>(
return (
<div
hidden={hidden}
className={classNames(
`${prefixCls}-textarea`,
{

View File

@ -111,6 +111,64 @@ describe('prefix and suffix', () => {
});
});
describe('Input setting hidden', () => {
it('should support hidden when has prefix or suffix or showCount or allowClear or addonBefore or addonAfter', () => {
const wrapper = mount(
<>
<Input
hidden
className="input"
showCount
allowClear
prefix="11"
suffix="22"
addonBefore="http://"
addonAfter=".com"
defaultValue="mysite1"
/>
<Input.Search
hidden
className="input-search"
showCount
allowClear
prefix="11"
suffix="22"
addonBefore="http://"
addonAfter=".com"
defaultValue="mysite1"
/>
<Input.TextArea
hidden
className="input-textarea"
showCount
allowClear
prefix="11"
suffix="22"
addonBefore="http://"
addonAfter=".com"
defaultValue="mysite1"
/>
<Input.Password
hidden
className="input-password"
showCount
allowClear
prefix="11"
suffix="22"
addonBefore="http://"
addonAfter=".com"
defaultValue="mysite1"
/>
</>,
);
expect(wrapper.find('.input').at(0).getDOMNode().hidden).toBe(true);
expect(wrapper.find('.input-search').at(0).getDOMNode().hidden).toBe(true);
expect(wrapper.find('.input-textarea').at(0).getDOMNode().hidden).toBe(true);
expect(wrapper.find('.input-password').at(0).getDOMNode().hidden).toBe(true);
});
});
describe('As Form Control', () => {
it('should be reset when wrapped in form.getFieldDecorator without initialValue', () => {
const Demo = () => {

View File

@ -167819,7 +167819,7 @@ exports[`Locale Provider should display the text as km 1`] = `
type="button"
>
<span>
យល់ព្រម
បោះបង់
</span>
</button>
<button
@ -169383,7 +169383,7 @@ exports[`Locale Provider should display the text as km 1`] = `
type="button"
>
<span>
យល់ព្រម
បោះបង់
</span>
</button>
<button

View File

@ -0,0 +1,53 @@
import React, { memo, useState, useRef, useContext } from 'react';
import { mount } from 'enzyme';
import LocaleProvider, { ANT_MARK } from '..';
import LocaleContext from '../context';
const defaultLocale = {
locale: 'locale',
};
// we use'memo' here in order to only render inner component while context changed.
const CacheInner = memo(() => {
const countRef = useRef(0);
countRef.current++;
// subscribe locale context
useContext(LocaleContext);
return (
<div>
Child Rendering Count: <span id="child_count">{countRef.current}</span>
</div>
);
});
const CacheOuter = () => {
// We use 'useState' here in order to trigger parent component rendering.
const [count, setCount] = useState(1);
const handleClick = () => {
setCount(count + 1);
};
// During each rendering phase, the cached context value returned from method 'LocaleProvider#getMemoizedContextValue' will take effect.
// So 'CacheInner' component won't rerender.
return (
<div>
<button type="button" onClick={handleClick} id="parent_btn">
Click
</button>
Parent Rendering Count: <span id="parent_count">{count}</span>
<LocaleProvider locale={defaultLocale} _ANT_MARK__={ANT_MARK}>
<CacheInner />
</LocaleProvider>
</div>
);
};
it("Rendering on LocaleProvider won't trigger rendering on child component.", () => {
const wrapper = mount(<CacheOuter />);
wrapper.find('#parent_btn').at(0).simulate('click');
expect(wrapper.find('#parent_count').text()).toBe('2');
// child component won't rerender
expect(wrapper.find('#child_count').text()).toBe('1');
wrapper.find('#parent_btn').at(0).simulate('click');
expect(wrapper.find('#parent_count').text()).toBe('3');
// child component won't rerender
expect(wrapper.find('#child_count').text()).toBe('1');
});

View File

@ -78,12 +78,14 @@ export default class LocaleProvider extends React.Component<LocaleProviderProps,
changeConfirmLocale();
}
getMemoizedContextValue = memoizeOne((localeValue: Locale): Locale & { exist?: boolean } => ({
...localeValue,
exist: true,
}));
render() {
const { locale, children } = this.props;
const contextValue = memoizeOne(localeValue => ({
...localeValue,
exist: true,
}))(locale);
const contextValue = this.getMemoizedContextValue(locale);
return <LocaleContext.Provider value={contextValue}>{children}</LocaleContext.Provider>;
}
}

View File

@ -26,18 +26,18 @@ const localeValues: Locale = {
sortTitle: 'តម្រៀប',
expand: 'ពន្លាត',
collapse: 'បិតបាំង',
triggerDesc: 'ចុចដើម្បីរៀបតាលំដាប់ធំ',
triggerAsc: 'ចុចដើម្បីរៀបតាលំដាប់តូច​',
triggerDesc: 'ចុចដើម្បីរៀបតាលំដាប់ធំ',
triggerAsc: 'ចុចដើម្បីរៀបតាលំដាប់តូច​',
cancelSort: 'ចុចដើម្បីបោះបង់',
},
Modal: {
okText: 'យល់ព្រម',
cancelText: 'យល់ព្រម',
cancelText: 'បោះបង់',
justOkText: 'យល់ព្រម',
},
Popconfirm: {
okText: 'យល់ព្រម',
cancelText: 'យល់ព្រម',
cancelText: 'បោះបង់',
},
Transfer: {
searchPlaceholder: 'ស្វែងរកនៅទីនេះ',

View File

@ -124,3 +124,11 @@
}
}
}
.@{steps-prefix-cls}-navigation.@{steps-prefix-cls}-horizontal {
> .@{steps-prefix-cls}-item
> .@{steps-prefix-cls}-item-container
> .@{steps-prefix-cls}-item-tail {
visibility: hidden;
}
}

View File

@ -6,6 +6,7 @@
// https://github.com/ant-design/ant-design/issues/33703
& > & {
line-height: 0;
vertical-align: 0;
}

View File

@ -15,11 +15,11 @@ Alternatively you can implement drag sorting with handler using [react-sortable-
```jsx
import { Table } from 'antd';
import { sortableContainer, sortableElement, sortableHandle } from 'react-sortable-hoc';
import { SortableContainer, SortableElement, SortableHandle } from 'react-sortable-hoc';
import { MenuOutlined } from '@ant-design/icons';
import { arrayMoveImmutable } from 'array-move';
const DragHandle = sortableHandle(() => <MenuOutlined style={{ cursor: 'grab', color: '#999' }} />);
const DragHandle = SortableHandle(() => <MenuOutlined style={{ cursor: 'grab', color: '#999' }} />);
const columns = [
{
@ -68,8 +68,8 @@ const data = [
},
];
const SortableItem = sortableElement(props => <tr {...props} />);
const SortableContainer = sortableContainer(props => <tbody {...props} />);
const SortableItem = SortableElement(props => <tr {...props} />);
const SortableBody = SortableContainer(props => <tbody {...props} />);
class SortableTable extends React.Component {
state = {
@ -79,14 +79,16 @@ class SortableTable extends React.Component {
onSortEnd = ({ oldIndex, newIndex }) => {
const { dataSource } = this.state;
if (oldIndex !== newIndex) {
const newData = arrayMoveImmutable([].concat(dataSource), oldIndex, newIndex).filter(el => !!el);
const newData = arrayMoveImmutable([].concat(dataSource), oldIndex, newIndex).filter(
el => !!el,
);
console.log('Sorted items: ', newData);
this.setState({ dataSource: newData });
}
};
DraggableContainer = props => (
<SortableContainer
<SortableBody
useDragHandle
disableAutoscroll
helperClass="row-dragging"