Merge pull request #22025 from ant-design/master-to-merge-feature

chore: 🔨 merge master into feature
This commit is contained in:
偏右 2020-03-09 19:13:30 +08:00 committed by GitHub
commit 64bbe4e566
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
30 changed files with 502 additions and 245 deletions

View File

@ -15,6 +15,40 @@ timeline: true
---
## 4.0.2
`2020-03-08`
- Form
- 🐞 Fix nest Form.Item dynamic remove will warning in React. [#21896](https://github.com/ant-design/ant-design/pull/21896)
- ⚡️ Form `useForm` now return same instance for perfermance. [#21927](https://github.com/ant-design/ant-design/pull/21927)
- ⚡️ Refactor Form.Item render logic that will only render once when children is a pure component. [#21991](https://github.com/ant-design/ant-design/pull/21991)
- ⚡️ FormContext use a memoized value to avoid trigger FormItem's unintentional renders. [#21980](https://github.com/ant-design/ant-design/pull/21980) [@qiqiboy](https://github.com/qiqiboy)
- Table
- 🐞 Fix Table dropdown popup at abnormal direction. [#21905](https://github.com/ant-design/ant-design/pull/21905)
- 🐞 Fix Table `expandIconColumnIndex` display order with `rowSelection`. [#21915](https://github.com/ant-design/ant-design/pull/21915)
- 🐞 Fix Table `size="small"` header background color is not same as other size. [#21942](https://github.com/ant-design/ant-design/pull/21942)
- 🐞 Fix Table `className` and `style` works on wrong node. [#21974](https://github.com/ant-design/ant-design/pull/21974)
- Select
- 🐞 Fix Select align issue with empty string value. [#21880](https://github.com/ant-design/ant-design/pull/21880)
- 🐞 Fix small size Select tag text not align middle. [#21940](https://github.com/ant-design/ant-design/pull/21940) [@xrkffgg](https://github.com/xrkffgg)
- Menu
- 🐞 Fix Menu bottom margin is missing. [#21867](https://github.com/ant-design/ant-design/pull/21867)
- 🐞 Fix horizontal Menu extra margin of Menu.Item with only icon. [#21925](https://github.com/ant-design/ant-design/pull/21925)
- 🐞 Fix Menu popup menu overflow issue when contains too many items. [#21930](https://github.com/ant-design/ant-design/pull/21930)
- 🐞 Fix Badge animation when switch between 10 and 11. [#21834](https://github.com/ant-design/ant-design/pull/21834) [@wendellhu95](https://github.com/wendellhu95)
- 🐞 Fix Radio.Button inside Tooltip throws `Function components cannot be given refs` warning. [#21895](https://github.com/ant-design/ant-design/pull/21895) [@AshoneA](https://github.com/AshoneA)
- 🐞 Fix Descriptions miss style when content is falsy. [#21901](https://github.com/ant-design/ant-design/pull/21901)
- 🐞 Fix DatePicker cursor style on `seperator`. [#21937](https://github.com/ant-design/ant-design/pull/21937) [@xrkffgg](https://github.com/xrkffgg)
- 🐞 Fix ConfigProvider `prefixCls` not working on Input.Password. [#21953](https://github.com/ant-design/ant-design/pull/21953) [@tdida](https://github.com/tdida)
- 🐞 Fix Carousel `dots` not align center. [#21960](https://github.com/ant-design/ant-design/pull/21960) [@liusiasi](https://github.com/liusiasi)
- 🐞 Fix Input.Search border style in `rtl` mode. [#21946](https://github.com/ant-design/ant-design/pull/21946) [@xrkffgg](https://github.com/xrkffgg)
- Less
- 🆕 Add `@outline-fade` variable. [#20227](https://github.com/ant-design/ant-design/pull/20227) [@Satloff](https://github.com/Satloff)
- 🆕 Add `@form-item-label-height` variable. [#21912](https://github.com/ant-design/ant-design/pull/21912)
- TypeScript
- 🌟 Improve Form.Item `renderProps` definite. [#21911](https://github.com/ant-design/ant-design/pull/21911)
## 4.0.1
`2020-03-04`
@ -297,6 +331,7 @@ Ant Design 4.0-rc released! Here is the release [document](https://github.com/an
- 🌟 The range selector can be set to `disabled` separately for the start and end time.
- 🌟 The range selector allows empty start and end times.
- 🌟 Optimized manual input and keyboard interaction support.
- 🌟 Added `inputReadOnly` to disable manual input.
- 🌟 Remove Icon and use `@ ant-design / icons` instead. [#18217](https://github.com/ant-design/ant-design/pull/18217)
- Skeleton
- 🌟 Support Skeleton.Avatar placeholder component. [#19898](https://github.com/ant-design/ant-design/pull/19898) [@Rustin-Liu](https://github.com/Rustin-Liu)

View File

@ -15,6 +15,40 @@ timeline: true
---
## 4.0.2
`2020-03-08`
- Form
- 🐞 修复嵌套 Form.Item 移除会导致 React 报警告的问题。[#21896](https://github.com/ant-design/ant-design/pull/21896)
- ⚡️ `Form.useForm` 现在将返回相同的实例以优化重复渲染的问题。[#21927](https://github.com/ant-design/ant-design/pull/21927)
- ⚡️ 重构 Form.Item 渲染逻辑以使其子元素为纯组件时值变更只会渲染一次。[#21991](https://github.com/ant-design/ant-design/pull/21991)
- ⚡️ FormContext 使用 memoized 值避免 Form.Item 产生额外的渲染。[#21980](https://github.com/ant-design/ant-design/pull/21980) [@qiqiboy](https://github.com/qiqiboy)
- Table
- 🐞 修复 Table 内浮层组件弹出方向异常的问题。[#21905](https://github.com/ant-design/ant-design/pull/21905)
- 🐞 修复 Table `className``style` 作用在了错误的元素上的问题。[#21974](https://github.com/ant-design/ant-design/pull/21974)
- 🐞 修复 Table `expandIconColumnIndex``rowSelection` 共用时的展示顺序问题。[#21915](https://github.com/ant-design/ant-design/pull/21915)
- 🐞 修复 Table `size="small"` 时表头颜色和其他尺寸不一致的问题。[#21942](https://github.com/ant-design/ant-design/pull/21942)
- Select
- 🐞 修复 Select 在空字符串值时的样式对齐问题。[#21880](https://github.com/ant-design/ant-design/pull/21880)
- 🐞 修复小号 Select 在多选模式下 `tag` 文字不居中的问题。[#21940](https://github.com/ant-design/ant-design/pull/21940) [@xrkffgg](https://github.com/xrkffgg)
- Menu
- 🐞 修复 Menu 弹出菜单底部边距丢失的问题。[#21867](https://github.com/ant-design/ant-design/pull/21867)
- 🐞 修复 Menu 水平模式下 Menu.Item 只有一个 Icon 时仍然有额外 `margin` 的问题。[#21925](https://github.com/ant-design/ant-design/pull/21925)
- 🐞 修复 Menu 弹出菜单超出屏幕高度的问题。[#21930](https://github.com/ant-design/ant-design/pull/21930)
- 🐞 修复 Badge 数字在 10 和 11 切换时的动画错误。[#21834](https://github.com/ant-design/ant-design/pull/21834) [@wendellhu95](https://github.com/wendellhu95)
- 🐞 修复 Radio.Button 上使用 Tooltip 会报 `Function components cannot be given refs` 警告。[#21895](https://github.com/ant-design/ant-design/pull/21895)
- 🐞 修复 Descriptions 内容为 falsy 值时样式丢失的问题。[#21901](https://github.com/ant-design/ant-design/pull/21901)
- 🐞 修复 DatePicker 在分隔符上的鼠标手型。[#21937](https://github.com/ant-design/ant-design/pull/21937) [@xrkffgg](https://github.com/xrkffgg)
- 🐞 修复 ConfigProvider `prefixCls` 在 Input.Password 上不生效的问题。[#21953](https://github.com/ant-design/ant-design/pull/21953) [@tdida](https://github.com/tdida)
- 🐞 修复 Carousel `dots` 控件不居中的问题。[#21960](https://github.com/ant-design/ant-design/pull/21960) [@liusiasi](https://github.com/liusiasi)
- 🐞 修复 Input.Search 边框高亮样式在 `rtl` 模式下丢失的问题。[#21946](https://github.com/ant-design/ant-design/pull/21946) [@xrkffgg](https://github.com/xrkffgg)
- Less
- 🆕 新增 `@outline-fade` 变量。[#20227](https://github.com/ant-design/ant-design/pull/20227) [@Satloff](https://github.com/Satloff)
- 🆕 新增 `@form-item-label-height` 变量。[#21912](https://github.com/ant-design/ant-design/pull/21912)
- TypeScript
- 🌟 增强 Form.Item `renderProps` 定义。[#21911](https://github.com/ant-design/ant-design/pull/21911)
## 4.0.1
`2020-03-04`
@ -297,6 +331,7 @@ Ant Design 4.0-rc 发布,发布文档请查看[此处](https://github.com/ant-
- 🌟 范围选择器可以为开始与结束时间单独设置 `disabled`
- 🌟 范围选择器可以允许开始与结束时间为空。
- 🌟 优化手工输入与键盘交互支持。
- 🌟 支持 `inputReadOnly` 禁用手动输入。
- 🌟 移除 Icon使用 `@ant-design/icons` 代替。[#18217](https://github.com/ant-design/ant-design/pull/18217)
- Skeleton
- 🌟 支持 Skeleton.Avatar 占位组件。[#19898](https://github.com/ant-design/ant-design/pull/19898) [@Rustin-Liu](https://github.com/Rustin-Liu)

View File

@ -30,7 +30,7 @@ A breadcrumb displays the current location within a hierarchy. It allows going b
| href | Target of hyperlink | string | - | |
| overlay | The dropdown menu | [Menu](/components/menu) \| () => Menu | - | |
| onClick | Set the handler to handle `click` event | (e:MouseEvent)=>void | - | |
| dropdownProps | The dropdown props | [Dropdown](/components/dropdown) | - | |
| dropdownProps | The dropdown props | [Dropdown](/components/dropdown) | - | |
### Breadcrumb.Separator

View File

@ -26,12 +26,12 @@ title: Breadcrumb
### Breadcrumb.Item
| 参数 | 说明 | 类型 | 默认值 | 版本 |
| ------- | -------------- | -------------------------------------- | ------ | ---- |
| href | 链接的目的地 | string | - | |
| overlay | 下拉菜单的内容 | [Menu](/components/menu) \| () => Menu | - | |
| onClick | 单击事件 | (e:MouseEvent)=>void | - | |
| dropdownProps | 弹出下拉菜单的自定义配置 | [Dropdown](/components/dropdown) | - | |
| 参数 | 说明 | 类型 | 默认值 | 版本 |
| --- | --- | --- | --- | --- |
| href | 链接的目的地 | string | - | |
| overlay | 下拉菜单的内容 | [Menu](/components/menu) \| () => Menu | - | |
| onClick | 单击事件 | (e:MouseEvent)=>void | - | |
| dropdownProps | 弹出下拉菜单的自定义配置 | [Dropdown](/components/dropdown) | - | |
### Breadcrumb.Separator

View File

@ -1,9 +1,10 @@
// mixins for button
// ------------------------
.button-size(@height; @padding-horizontal; @font-size; @border-radius) {
@padding-vertical: round((@height - @font-size * @line-height-base) / 2 * 10) / 10 -
@border-width-base;
@padding-vertical: max(
round((@height - @font-size * @line-height-base) / 2 * 10) / 10 - @border-width-base,
0
);
height: @height;
padding: @padding-vertical @padding-horizontal;
font-size: @font-size;

View File

@ -20,7 +20,7 @@ A carousel component. Scales with its container.
| autoplay | Whether to scroll automatically | boolean | `false` | |
| beforeChange | Callback function called before the current index changes | function(from, to) | - | |
| dotPosition | The position of the dots, which can be one of `top` `bottom` `left` `right` | string | bottom | |
| dots | Whether to show the dots at the bottom of the gallery, `object` for `dotsClass` and any others | boolean \| { className?:string } | `true` | |
| dots | Whether to show the dots at the bottom of the gallery, `object` for `dotsClass` and any others | boolean \| { className?:string } | `true` | |
| easing | Transition interpolation function name | string | `linear` | |
| effect | Transition effect | `scrollx` \| `fade` | `scrollx` | |

View File

@ -181,7 +181,7 @@
justify-content: center;
margin-right: 15%;
margin-left: 15%;
padding-right: 0;
padding-left: 0;
list-style: none;
.@{carousel-prefix-cls}-rtl& {

View File

@ -7119,6 +7119,44 @@ exports[`ConfigProvider components Input configProvider 1`] = `
</span>
</span>
</span>
<span
class="config-input-password config-input-affix-wrapper"
>
<input
action="click"
class="config-input"
type="password"
value=""
/>
<span
class="config-input-suffix"
>
<span
aria-label="eye-invisible"
class="anticon anticon-eye-invisible config-input-password-icon"
role="img"
tabindex="-1"
>
<svg
aria-hidden="true"
class=""
data-icon="eye-invisible"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M942.2 486.2Q889.47 375.11 816.7 305l-50.88 50.88C807.31 395.53 843.45 447.4 874.7 512 791.5 684.2 673.4 766 512 766q-72.67 0-133.87-22.38L323 798.75Q408 838 512 838q288.3 0 430.2-300.3a60.29 60.29 0 000-51.5zm-63.57-320.64L836 122.88a8 8 0 00-11.32 0L715.31 232.2Q624.86 186 512 186q-288.3 0-430.2 300.3a60.3 60.3 0 000 51.5q56.69 119.4 136.5 191.41L112.48 835a8 8 0 000 11.31L155.17 889a8 8 0 0011.31 0l712.15-712.12a8 8 0 000-11.32zM149.3 512C232.6 339.8 350.7 258 512 258c54.54 0 104.13 9.36 149.12 28.39l-70.3 70.3a176 176 0 00-238.13 238.13l-83.42 83.42C223.1 637.49 183.3 582.28 149.3 512zm246.7 0a112.11 112.11 0 01146.2-106.69L401.31 546.2A112 112 0 01396 512z"
/>
<path
d="M508 624c-3.46 0-6.87-.16-10.25-.47l-52.82 52.82a176.09 176.09 0 00227.42-227.42l-52.82 52.82c.31 3.38.47 6.79.47 10.25a111.94 111.94 0 01-112 112z"
/>
</svg>
</span>
</span>
</span>
<textarea
class="config-input"
/>
@ -7170,6 +7208,44 @@ exports[`ConfigProvider components Input normal 1`] = `
</span>
</span>
</span>
<span
class="ant-input-password ant-input-affix-wrapper"
>
<input
action="click"
class="ant-input"
type="password"
value=""
/>
<span
class="ant-input-suffix"
>
<span
aria-label="eye-invisible"
class="anticon anticon-eye-invisible ant-input-password-icon"
role="img"
tabindex="-1"
>
<svg
aria-hidden="true"
class=""
data-icon="eye-invisible"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M942.2 486.2Q889.47 375.11 816.7 305l-50.88 50.88C807.31 395.53 843.45 447.4 874.7 512 791.5 684.2 673.4 766 512 766q-72.67 0-133.87-22.38L323 798.75Q408 838 512 838q288.3 0 430.2-300.3a60.29 60.29 0 000-51.5zm-63.57-320.64L836 122.88a8 8 0 00-11.32 0L715.31 232.2Q624.86 186 512 186q-288.3 0-430.2 300.3a60.3 60.3 0 000 51.5q56.69 119.4 136.5 191.41L112.48 835a8 8 0 000 11.31L155.17 889a8 8 0 0011.31 0l712.15-712.12a8 8 0 000-11.32zM149.3 512C232.6 339.8 350.7 258 512 258c54.54 0 104.13 9.36 149.12 28.39l-70.3 70.3a176 176 0 00-238.13 238.13l-83.42 83.42C223.1 637.49 183.3 582.28 149.3 512zm246.7 0a112.11 112.11 0 01146.2-106.69L401.31 546.2A112 112 0 01396 512z"
/>
<path
d="M508 624c-3.46 0-6.87-.16-10.25-.47l-52.82 52.82a176.09 176.09 0 00227.42-227.42l-52.82 52.82c.31 3.38.47 6.79.47 10.25a111.94 111.94 0 01-112 112z"
/>
</svg>
</span>
</span>
</span>
<textarea
class="ant-input"
/>
@ -7221,6 +7297,44 @@ exports[`ConfigProvider components Input prefixCls 1`] = `
</span>
</span>
</span>
<span
class="prefix-Input ant-input-affix-wrapper"
>
<input
action="click"
class="ant-input"
type="password"
value=""
/>
<span
class="ant-input-suffix"
>
<span
aria-label="eye-invisible"
class="anticon anticon-eye-invisible prefix-Input-icon"
role="img"
tabindex="-1"
>
<svg
aria-hidden="true"
class=""
data-icon="eye-invisible"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M942.2 486.2Q889.47 375.11 816.7 305l-50.88 50.88C807.31 395.53 843.45 447.4 874.7 512 791.5 684.2 673.4 766 512 766q-72.67 0-133.87-22.38L323 798.75Q408 838 512 838q288.3 0 430.2-300.3a60.29 60.29 0 000-51.5zm-63.57-320.64L836 122.88a8 8 0 00-11.32 0L715.31 232.2Q624.86 186 512 186q-288.3 0-430.2 300.3a60.3 60.3 0 000 51.5q56.69 119.4 136.5 191.41L112.48 835a8 8 0 000 11.31L155.17 889a8 8 0 0011.31 0l712.15-712.12a8 8 0 000-11.32zM149.3 512C232.6 339.8 350.7 258 512 258c54.54 0 104.13 9.36 149.12 28.39l-70.3 70.3a176 176 0 00-238.13 238.13l-83.42 83.42C223.1 637.49 183.3 582.28 149.3 512zm246.7 0a112.11 112.11 0 01146.2-106.69L401.31 546.2A112 112 0 01396 512z"
/>
<path
d="M508 624c-3.46 0-6.87-.16-10.25-.47l-52.82 52.82a176.09 176.09 0 00227.42-227.42l-52.82 52.82c.31 3.38.47 6.79.47 10.25a111.94 111.94 0 01-112 112z"
/>
</svg>
</span>
</span>
</span>
<textarea
class="prefix-Input"
/>

View File

@ -290,6 +290,7 @@ describe('ConfigProvider', () => {
<Input {...props} />
<Input.Search {...props} />
</Input.Group>
<Input.Password {...props} />
<Input.TextArea {...props} />
</div>
));

View File

@ -65,6 +65,7 @@ The following APIs are shared by DatePicker, YearPicker, MonthPicker, RangePicke
| style | to customize the style of the input box | object | {} | |
| onOpenChange | a callback function, can be executed whether the popup calendar is popped up or closed | function(status) | - | |
| onPanelChange | callback when picker panel mode is changed | function(value, mode) | - | |
| inputReadOnly | Set the `readonly` attribute of the input tag (avoids virtual keyboard on touch devices) | boolean | false | |
### Common Methods

View File

@ -67,6 +67,7 @@ import 'moment/locale/zh-cn';
| style | 自定义输入框样式 | object | {} | |
| onOpenChange | 弹出日历和关闭日历的回调 | function(status) | 无 | |
| onPanelChange | 日历面板切换的回调 | function(value, mode) | - | |
| inputReadOnly | 设置输入框为只读(避免在移动设备上打开虚拟键盘) | boolean | false | |
### 共同的方法

View File

@ -496,6 +496,7 @@ exports[`renders ./components/empty/demo/config-provider.md correctly 1`] = `
</h3>
<div
class="ant-table-wrapper"
style="margin-top:8px"
>
<div
class="ant-spin-nested-loading"
@ -505,7 +506,6 @@ exports[`renders ./components/empty/demo/config-provider.md correctly 1`] = `
>
<div
class="ant-table"
style="margin-top:8px"
>
<div
class="ant-table-container"

View File

@ -74,6 +74,18 @@ const InternalForm: React.ForwardRefRenderFunction<unknown, FormProps> = (props,
const [wrapForm] = useForm(form);
wrapForm.__INTERNAL__.name = name;
const formContextValue = React.useMemo(
() => ({
name,
labelAlign,
labelCol,
wrapperCol,
vertical: layout === 'vertical',
colon,
}),
[name, labelAlign, labelCol, wrapperCol, layout, colon],
);
React.useImperativeHandle(ref, () => wrapForm);
const onInternalFinishFailed = (errorInfo: ValidateErrorEntity) => {
@ -89,14 +101,7 @@ const InternalForm: React.ForwardRefRenderFunction<unknown, FormProps> = (props,
return (
<SizeContextProvider size={size}>
<FormContext.Provider
value={{
name,
labelAlign,
labelCol,
wrapperCol,
vertical: layout === 'vertical',
colon,
}}
value={formContextValue}
>
<FieldForm
id={name}

View File

@ -21,6 +21,21 @@ type RenderChildren = (form: FormInstance) => React.ReactNode;
type RcFieldProps = Omit<FieldProps, 'children'>;
type ChildrenType = React.ReactElement | RenderChildren | React.ReactElement[] | null;
interface MemoInputProps {
value: any;
update: number;
children: any;
}
const MemoInput = React.memo<MemoInputProps>(
({ children }) => {
return children;
},
(prev, next) => {
return prev.value === next.value && prev.update === next.update;
},
);
export interface FormItemProps
extends FormItemLabelProps,
FormItemInputProps,
@ -217,6 +232,10 @@ function FormItem(props: FormItemProps): React.ReactElement {
return renderLayout(children);
}
// Record for real component render
const updateRef = React.useRef(0);
updateRef.current += 1;
const variables: Record<string, string> = {};
if (typeof label === 'string') {
variables.label = label;
@ -294,10 +313,7 @@ function FormItem(props: FormItemProps): React.ReactElement {
const childProps = { ...children.props, ...mergedControl };
// We should keep user origin event handler
const triggers = new Set<string>();
[...toArray(trigger), ...toArray(validateTrigger)].forEach(eventName => {
triggers.add(eventName);
});
const triggers = new Set<string>([...toArray(trigger), ...toArray(validateTrigger)]);
triggers.forEach(eventName => {
childProps[eventName] = (...args: any[]) => {
@ -306,7 +322,14 @@ function FormItem(props: FormItemProps): React.ReactElement {
};
});
childNode = React.cloneElement(children, childProps);
childNode = (
<MemoInput
value={mergedControl[props.valuePropName || 'value']}
update={updateRef.current}
>
{React.cloneElement(children, childProps)}
</MemoInput>
);
} else if (isRenderProps && shouldUpdate && !hasName) {
childNode = (children as RenderChildren)(context);
} else {

View File

@ -643,4 +643,35 @@ describe('Form', () => {
}
expect(instances.size).toEqual(1);
});
it('avoid re-render', async () => {
let renderTimes = 0;
const MyInput = ({ value = '', ...props }) => {
renderTimes += 1;
return <input value={value} {...props} />;
};
const Demo = () => (
<Form>
<Form.Item name="username" rules={[{ required: true }]}>
<MyInput />
</Form.Item>
</Form>
);
const wrapper = mount(<Demo />);
renderTimes = 0;
wrapper.find('input').simulate('change', {
target: {
value: 'a',
},
});
await delay();
expect(renderTimes).toEqual(1);
expect(wrapper.find('input').props().value).toEqual('a');
});
});

View File

@ -20,6 +20,7 @@ ReactDOM.render(<IconDisplay />, mountNode);
| Property | Description | Type | Default | Version |
| --- | --- | --- | --- | --- |
| className | className of Icon | `string` | - | |
| style | Style properties of icon, like `fontSize` and `color` | CSSProperties | - | |
| spin | Rotate icon with animation | boolean | false | |
| rotate | Rotate by n degrees (not working in IE9) | number | - | |

View File

@ -27,6 +27,7 @@ ReactDOM.render(<IconDisplay />, mountNode);
| 参数 | 说明 | 类型 | 默认值 | 版本 |
| --- | --- | --- | --- | --- |
| className | 设置图标的样式名 | `string` | - | |
| style | 设置图标的样式,例如 `fontSize``color` | CSSProperties | - | |
| spin | 是否有旋转动画 | boolean | false | |
| rotate | 图标旋转角度IE9 无效) | number | - | |

View File

@ -4,6 +4,7 @@ import omit from 'omit.js';
import EyeOutlined from '@ant-design/icons/EyeOutlined';
import EyeInvisibleOutlined from '@ant-design/icons/EyeInvisibleOutlined';
import { ConfigConsumer, ConfigConsumerProps } from '../config-provider';
import Input, { InputProps } from './Input';
export interface PasswordProps extends InputProps {
@ -25,8 +26,6 @@ export default class Password extends React.Component<PasswordProps, PasswordSta
input: HTMLInputElement;
static defaultProps = {
inputPrefixCls: 'ant-input',
prefixCls: 'ant-input-password',
action: 'click',
visibilityToggle: true,
};
@ -44,8 +43,8 @@ export default class Password extends React.Component<PasswordProps, PasswordSta
this.setState(({ visible }) => ({ visible: !visible }));
};
getIcon() {
const { prefixCls, action } = this.props;
getIcon = (prefixCls: string) => {
const { action } = this.props;
const iconTrigger = ActionMap[action!] || '';
const icon = this.state.visible ? EyeOutlined : EyeInvisibleOutlined;
const iconProps = {
@ -59,7 +58,7 @@ export default class Password extends React.Component<PasswordProps, PasswordSta
},
};
return React.createElement(icon, iconProps);
}
};
saveInput = (instance: Input) => {
if (instance && instance.input) {
@ -79,19 +78,24 @@ export default class Password extends React.Component<PasswordProps, PasswordSta
this.input.select();
}
render() {
renderPassword = ({ getPrefixCls }: ConfigConsumerProps) => {
const {
className,
prefixCls,
inputPrefixCls,
prefixCls: customizePrefixCls,
inputPrefixCls: customizeInputPrefixCls,
size,
visibilityToggle,
...restProps
} = this.props;
const suffixIcon = visibilityToggle && this.getIcon();
const inputPrefixCls = getPrefixCls('input', customizeInputPrefixCls);
const prefixCls = getPrefixCls('input-password', customizePrefixCls);
const suffixIcon = visibilityToggle && this.getIcon(prefixCls);
const inputClassName = classNames(prefixCls, className, {
[`${prefixCls}-${size}`]: !!size,
});
const props = {
...omit(restProps, ['suffix']),
type: this.state.visible ? 'text' : 'password',
@ -100,9 +104,15 @@ export default class Password extends React.Component<PasswordProps, PasswordSta
suffix: suffixIcon,
ref: this.saveInput,
};
if (size) {
props.size = size;
}
return <Input {...props} />;
};
render() {
return <ConfigConsumer>{this.renderPassword}</ConfigConsumer>;
}
}

View File

@ -88,6 +88,7 @@ class ResizableTextArea extends React.Component<TextAreaProps, TextAreaState> {
this.setState({ resizeStatus: RESIZE_STATUS_RESIZED }, () => {
this.resizeFrameId = raf(() => {
this.setState({ resizeStatus: RESIZE_STATUS_NONE });
this.fixFirefoxAutoScroll();
});
});
});
@ -99,6 +100,21 @@ class ResizableTextArea extends React.Component<TextAreaProps, TextAreaState> {
raf.cancel(this.resizeFrameId);
}
// https://github.com/ant-design/ant-design/issues/21870
fixFirefoxAutoScroll() {
try {
if (document.activeElement === this.textArea) {
const currentStart = this.textArea.selectionStart;
const currentEnd = this.textArea.selectionEnd;
this.textArea.setSelectionRange(currentStart, currentEnd);
}
} catch (e) {
// Fix error in Chrome:
// Failed to read the 'selectionStart' property from 'HTMLInputElement'
// http://stackoverflow.com/q/21177489/3040605
}
}
renderTextArea = () => {
const { prefixCls, autoSize, onResize, className, disabled } = this.props;
const { textareaStyles, resizeStatus } = this.state;

View File

@ -570,64 +570,6 @@ exports[`renders ./components/input/demo/align.md correctly 1`] = `
</span>
</span>
</div>
<div
class="ant-select ant-select-single ant-select-show-arrow"
style="width:100px"
>
<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"
value=""
/>
</span>
<span
class="ant-select-selection-item"
>
jack
</span>
</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"
role="img"
>
<svg
aria-hidden="true"
class=""
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-select ant-tree-select ant-select-single ant-select-show-arrow"
style="width:100px"
@ -1534,6 +1476,7 @@ exports[`renders ./components/input/demo/group.md correctly 1`] = `
>
<div
class="ant-select ant-select-single ant-select-show-arrow"
style="width:30%"
>
<div
class="ant-select-selector"
@ -1591,7 +1534,7 @@ exports[`renders ./components/input/demo/group.md correctly 1`] = `
</div>
<div
class="ant-select ant-select-auto-complete ant-select-single ant-select-show-search"
style="width:200px"
style="width:70%"
>
<div
class="ant-select-selector"

View File

@ -145,8 +145,8 @@ describe('TextArea', () => {
it('should trigger onResize', () => {
const onResize = jest.fn();
const wrapper = mount(<TextArea onResize={onResize} autosize />);
const wrapper = mount(<TextArea onResize={onResize} autoSize />);
jest.runAllTimers();
wrapper
.find('ResizeObserver')
.instance()
@ -284,4 +284,23 @@ describe('TextArea allowClear', () => {
wrapper.find('.test-suffix').simulate('mouseUp');
expect(onFocus).toHaveBeenCalled();
});
it('scroll to bottom when autoSize', () => {
jest.useFakeTimers();
const wrapper = mount(<Input.TextArea autoSize />, { attachTo: document.body });
wrapper.find('textarea').simulate('focus');
wrapper
.find('textarea')
.getDOMNode()
.focus();
const setSelectionRangeFn = jest.spyOn(
wrapper.find('textarea').getDOMNode(),
'setSelectionRange',
);
wrapper.find('textarea').simulate('input', { target: { value: '\n1' } });
jest.runAllTimers();
jest.useRealTimers();
expect(setSelectionRangeFn).toHaveBeenCalled();
wrapper.unmount();
});
});

View File

@ -83,7 +83,6 @@ ReactDOM.render(
</Option>
<Option value="Yiminghe">yiminghe</Option>
</Select>
<Select style={{ width: 100 }} combobox defaultValue="jack" />
<TreeSelect style={{ width: 100 }} />
<Cascader defaultValue={['zhejiang', 'hangzhou', 'xihu']} options={options} />
<RangePicker />
@ -93,9 +92,7 @@ ReactDOM.render(
<RadioButton value="b">Shanghai</RadioButton>
</RadioGroup>
<AutoComplete style={{ width: 100 }} placeholder="input here" />
<br />
<Input prefix="$" addonBefore="Http://" addonAfter=".com" defaultValue="mysite" />
</div>,
mountNode,

View File

@ -20,7 +20,6 @@ Note: You don't need `Col` to control the width in the `compact` mode.
```jsx
import { Input, Col, Row, Select, InputNumber, DatePicker, AutoComplete, Cascader } from 'antd';
const InputGroup = Input.Group;
const { Option } = Select;
const options = [
@ -58,125 +57,107 @@ const options = [
},
];
class CompactDemo extends React.Component {
state = {
dataSource: [],
};
const App = () => (
<div className="site-input-group-wrapper">
<Input.Group size="large">
<Row gutter={8}>
<Col span={5}>
<Input defaultValue="0571" />
</Col>
<Col span={8}>
<Input defaultValue="26888888" />
</Col>
</Row>
</Input.Group>
<br />
<Input.Group compact>
<Input style={{ width: '20%' }} defaultValue="0571" />
<Input style={{ width: '30%' }} defaultValue="26888888" />
</Input.Group>
<br />
<Input.Group compact>
<Select defaultValue="Zhejiang">
<Option value="Zhejiang">Zhejiang</Option>
<Option value="Jiangsu">Jiangsu</Option>
</Select>
<Input style={{ width: '50%' }} defaultValue="Xihu District, Hangzhou" />
</Input.Group>
<br />
<Input.Group compact>
<Select defaultValue="Option1">
<Option value="Option1">Option1</Option>
<Option value="Option2">Option2</Option>
</Select>
<Input style={{ width: '50%' }} defaultValue="input content" />
<InputNumber />
</Input.Group>
<br />
<Input.Group compact>
<Input style={{ width: '50%' }} defaultValue="input content" />
<DatePicker style={{ width: '50%' }} />
</Input.Group>
<br />
<Input.Group compact>
<Select defaultValue="Option1-1">
<Option value="Option1-1">Option1-1</Option>
<Option value="Option1-2">Option1-2</Option>
</Select>
<Select defaultValue="Option2-2">
<Option value="Option2-1">Option2-1</Option>
<Option value="Option2-2">Option2-2</Option>
</Select>
</Input.Group>
<br />
<Input.Group compact>
<Select defaultValue="1">
<Option value="1">Between</Option>
<Option value="2">Except</Option>
</Select>
<Input style={{ width: 100, textAlign: 'center' }} placeholder="Minimum" />
<Input
className="site-input-split"
style={{
width: 30,
borderLeft: 0,
borderRight: 0,
pointerEvents: 'none',
}}
placeholder="~"
disabled
/>
<Input
className="site-input-right"
style={{
width: 100,
textAlign: 'center',
}}
placeholder="Maximum"
/>
</Input.Group>
<br />
<Input.Group compact>
<Select defaultValue="Sign Up" style={{ width: '30%' }}>
<Option value="Sign Up">Sign Up</Option>
<Option value="Sign In">Sign In</Option>
</Select>
<AutoComplete
style={{ width: '70%' }}
placeholder="Email"
options={[{ value: 'text 1' }, { value: 'text 2' }]}
/>
</Input.Group>
<br />
<Input.Group compact>
<Select style={{ width: '30%' }} defaultValue="Home">
<Option value="Home">Home</Option>
<Option value="Company">Company</Option>
</Select>
<Cascader style={{ width: '70%' }} options={options} placeholder="Select Address" />
</Input.Group>
</div>
);
handleChange = value => {
this.setState({
dataSource:
!value || value.indexOf('@') >= 0
? []
: [`${value}@gmail.com`, `${value}@163.com`, `${value}@qq.com`],
});
};
render() {
return (
<div className="site-input-group-wrapper">
<InputGroup size="large">
<Row gutter={8}>
<Col span={5}>
<Input defaultValue="0571" />
</Col>
<Col span={8}>
<Input defaultValue="26888888" />
</Col>
</Row>
</InputGroup>
<br />
<InputGroup compact>
<Input style={{ width: '20%' }} defaultValue="0571" />
<Input style={{ width: '30%' }} defaultValue="26888888" />
</InputGroup>
<br />
<InputGroup compact>
<Select defaultValue="Zhejiang">
<Option value="Zhejiang">Zhejiang</Option>
<Option value="Jiangsu">Jiangsu</Option>
</Select>
<Input style={{ width: '50%' }} defaultValue="Xihu District, Hangzhou" />
</InputGroup>
<br />
<InputGroup compact>
<Select defaultValue="Option1">
<Option value="Option1">Option1</Option>
<Option value="Option2">Option2</Option>
</Select>
<Input style={{ width: '50%' }} defaultValue="input content" />
<InputNumber />
</InputGroup>
<br />
<InputGroup compact>
<Input style={{ width: '50%' }} defaultValue="input content" />
<DatePicker style={{ width: '50%' }} />
</InputGroup>
<br />
<InputGroup compact>
<Select defaultValue="Option1-1">
<Option value="Option1-1">Option1-1</Option>
<Option value="Option1-2">Option1-2</Option>
</Select>
<Select defaultValue="Option2-2">
<Option value="Option2-1">Option2-1</Option>
<Option value="Option2-2">Option2-2</Option>
</Select>
</InputGroup>
<br />
<InputGroup compact>
<Select defaultValue="1">
<Option value="1">Between</Option>
<Option value="2">Except</Option>
</Select>
<Input style={{ width: 100, textAlign: 'center' }} placeholder="Minimum" />
<Input
className="site-input-split"
style={{
width: 30,
borderLeft: 0,
borderRight: 0,
pointerEvents: 'none',
}}
placeholder="~"
disabled
/>
<Input
className="site-input-right"
style={{
width: 100,
textAlign: 'center',
}}
placeholder="Maximum"
/>
</InputGroup>
<br />
<InputGroup compact>
<Select defaultValue="Sign Up">
<Option value="Sign Up">Sign Up</Option>
<Option value="Sign In">Sign In</Option>
</Select>
<AutoComplete
dataSource={this.state.dataSource}
style={{ width: 200 }}
onChange={this.handleChange}
placeholder="Email"
/>
</InputGroup>
<br />
<InputGroup compact>
<Select style={{ width: '30%' }} defaultValue="Home">
<Option value="Home">Home</Option>
<Option value="Company">Company</Option>
</Select>
<Cascader style={{ width: '70%' }} options={options} placeholder="Select Address" />
</InputGroup>
</div>
);
}
}
ReactDOM.render(<CompactDemo />, mountNode);
ReactDOM.render(<App />, mountNode);
```
```css

View File

@ -207,7 +207,7 @@
}
&.@{menu-prefix-cls}-item-only-child {
.@{iconfont-css-prefix} {
> .@{iconfont-css-prefix} {
margin-right: 0;
.@{menu-prefix-cls}-rtl & {

View File

@ -4,8 +4,11 @@
@select-multiple-item-height: @input-height-base - @input-padding-vertical-base * 2; // Normal 24px
@select-multiple-item-spacing-half: ceil(@input-padding-vertical-base / 2);
@select-multiple-padding: @input-padding-vertical-base - @select-multiple-item-border-width -
@select-multiple-item-spacing-half;
@select-multiple-padding: max(
@input-padding-vertical-base - @select-multiple-item-border-width -
@select-multiple-item-spacing-half,
0
);
/**
* Do not merge `height` & `line-height` under style with `selection` & `search`,

View File

@ -344,9 +344,11 @@
@input-padding-horizontal-base: @input-padding-horizontal;
@input-padding-horizontal-sm: @control-padding-horizontal-sm - 1px;
@input-padding-horizontal-lg: @input-padding-horizontal;
@input-padding-vertical-base: round(
(@input-height-base - @font-size-base * @line-height-base) / 2 * 10
) / 10 - @border-width-base;
@input-padding-vertical-base: max(
round((@input-height-base - @font-size-base * @line-height-base) / 2 * 10) / 10 -
@border-width-base,
3px
);
@input-padding-vertical-sm: round((@input-height-sm - @font-size-base * @line-height-base) / 2 * 10) /
10 - @border-width-base;
@input-padding-vertical-lg: ceil((@input-height-lg - @font-size-lg * @line-height-base) / 2 * 10) /

View File

@ -1,5 +1,6 @@
import * as React from 'react';
import classNames from 'classnames';
import omit from 'omit.js';
import RcTable from 'rc-table';
import { TableProps as RcTableProps, INTERNAL_HOOKS } from 'rc-table/lib/Table';
import Spin, { SpinProps } from '../spin';
@ -85,6 +86,7 @@ function Table<RecordType extends object = any>(props: TableProps<RecordType>) {
const {
prefixCls: customizePrefixCls,
className,
style,
size: customizeSize,
bordered,
dropdownPrefixCls,
@ -109,6 +111,9 @@ function Table<RecordType extends object = any>(props: TableProps<RecordType>) {
locale,
showSorterTooltip = true,
} = props;
const tableProps = omit(props, ['className', 'style']) as TableProps<RecordType>;
const size = React.useContext(SizeContext);
const { locale: contextLocale = defaultLocale, renderEmpty, direction } = React.useContext(
ConfigContext,
@ -404,12 +409,13 @@ function Table<RecordType extends object = any>(props: TableProps<RecordType>) {
spinProps = loading;
}
const wrapperClassNames = classNames(`${prefixCls}-wrapper`, {
const wrapperClassNames = classNames(`${prefixCls}-wrapper`, className, {
[`${prefixCls}-wrapper-rtl`]: direction === 'rtl',
});
return (
<div
className={wrapperClassNames}
style={style}
onTouchMove={e => {
e.preventDefault();
}}
@ -417,10 +423,10 @@ function Table<RecordType extends object = any>(props: TableProps<RecordType>) {
<Spin spinning={false} {...spinProps}>
{topPaginationNode}
<RcTable<RecordType>
{...props}
{...tableProps}
expandable={mergedExpandable}
prefixCls={prefixCls}
className={classNames(className, {
className={classNames({
[`${prefixCls}-middle`]: mergedSize === 'middle',
[`${prefixCls}-small`]: mergedSize === 'small',
[`${prefixCls}-bordered`]: bordered,

View File

@ -9544,7 +9544,7 @@ exports[`renders ./components/table/demo/multiple-sorter.md correctly 1`] = `
exports[`renders ./components/table/demo/nested-table.md correctly 1`] = `
<div
class="ant-table-wrapper"
class="ant-table-wrapper components-table-demo-nested"
>
<div
class="ant-spin-nested-loading"
@ -9553,7 +9553,7 @@ exports[`renders ./components/table/demo/nested-table.md correctly 1`] = `
class="ant-spin-container"
>
<div
class="ant-table components-table-demo-nested"
class="ant-table"
>
<div
class="ant-table-container"
@ -13178,7 +13178,7 @@ exports[`renders ./components/table/demo/summary.md correctly 1`] = `
exports[`renders ./components/table/demo/virtual-list.md correctly 1`] = `
<div
class="ant-table-wrapper"
class="ant-table-wrapper virtual-table"
>
<div
class="ant-spin-nested-loading"
@ -13187,7 +13187,7 @@ exports[`renders ./components/table/demo/virtual-list.md correctly 1`] = `
class="ant-spin-container"
>
<div
class="ant-table virtual-table ant-table-fixed-header ant-table-fixed-column"
class="ant-table ant-table-fixed-header ant-table-fixed-column"
>
<div
class="ant-table-container"

View File

@ -19,27 +19,54 @@ import { Tree } from 'antd';
const { TreeNode } = Tree;
const initTreeDate = [
interface DataNode {
title: string;
key: string;
isLeaf?: boolean;
children?: DataNode[];
}
const initTreeDate: DataNode[] = [
{ title: 'Expand to load', key: '0' },
{ title: 'Expand to load', key: '1' },
{ title: 'Tree Node', key: '2', isLeaf: true },
];
// It's just a simple demo. You can use tree map to optimize update perf.
function updateTreeData(list: DataNode[], key: React.Key, children: DataNode[]): DataNode[] {
return list.map(node => {
if (node.key === key) {
return {
...node,
children,
};
} else if (node.children) {
return {
...node,
children: updateTreeData(node.children, key, children),
};
}
return node;
});
}
const Demo: React.FC<{}> = () => {
const [treeData, setTreeData] = useState(initTreeDate);
function onLoadData({ props: { data } }) {
function onLoadData({ key, children }) {
return new Promise(resolve => {
if (data.children) {
if (children) {
resolve();
return;
}
setTimeout(() => {
data.children = [
{ title: 'Child Node', key: `${data.key}-0` },
{ title: 'Child Node', key: `${data.key}-1` },
];
setTreeData([...treeData]);
setTreeData(origin =>
updateTreeData(origin, key, [
{ title: 'Child Node', key: `${key}-0` },
{ title: 'Child Node', key: `${key}-1` },
]),
);
resolve();
}, 1000);
});
@ -61,14 +88,14 @@ class Demo1 extends React.Component {
const { treeData } = this.state;
return new Promise(resolve => {
const { props } = treeNode;
if (treeNode.props.children) {
if (treeNode.children) {
resolve();
return;
}
setTimeout(() => {
treeNode.props.dataRef.children = [
{ title: 'Child Node', key: `${treeNode.props.eventKey}-0` },
{ title: 'Child Node', key: `${treeNode.props.eventKey}-1` },
treeNode.children = [
{ title: 'Child Node', key: `${treeNode.eventKey}-0` },
{ title: 'Child Node', key: `${treeNode.eventKey}-1` },
];
this.setState({
treeData: [...this.state.treeData],

View File

@ -1,6 +1,6 @@
{
"name": "antd",
"version": "4.0.1",
"version": "4.0.2",
"description": "An enterprise-class UI design language and React components implementation",
"keywords": [
"ant",
@ -25,6 +25,10 @@
"contributors": [
"ant"
],
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/ant-design"
},
"files": [
"dist",
"lib",
@ -224,7 +228,7 @@
"react-helmet-async": "^1.0.4",
"react-highlight-words": "^0.16.0",
"react-infinite-scroller": "^1.2.4",
"react-intl": "^3.1.1",
"react-intl": "^4.1.1",
"react-resizable": "^1.8.0",
"react-router-dom": "^5.0.1",
"react-sticky": "^6.0.3",