diff --git a/.snyk b/.snyk new file mode 100644 index 0000000000..ecc2a2f4bd --- /dev/null +++ b/.snyk @@ -0,0 +1,16 @@ +# Snyk (https://snyk.io) policy file, patches or ignores known vulnerabilities. +version: v1.14.1 +ignore: {} +# patches apply the minimum changes required to fix a vulnerability +patch: + SNYK-JS-LODASH-567746: + - lodash: + patched: '2020-05-01T06:50:59.565Z' + - '@ant-design/react-slick > lodash': + patched: '2020-05-01T06:50:59.565Z' + - rc-steps > lodash: + patched: '2020-05-01T06:50:59.565Z' + - rc-table > lodash: + patched: '2020-05-01T06:50:59.565Z' + - rc-tabs > lodash: + patched: '2020-05-01T06:50:59.565Z' diff --git a/CHANGELOG.en-US.md b/CHANGELOG.en-US.md index 1a720bd260..107603e7a7 100644 --- a/CHANGELOG.en-US.md +++ b/CHANGELOG.en-US.md @@ -15,6 +15,61 @@ timeline: true --- +## 4.2.0 + +`2020-04-29` + +- 🆕 List `grid` support all column count like 5. [#23630](https://github.com/ant-design/ant-design/pull/23630) +- 🆕 Divider add `plain` prop which allows a non-heading style divider text. [#23405](https://github.com/ant-design/ant-design/pull/23405) +- 🆕 Typography `ellipsis` support `onEllipsis` event handler. [#23414](https://github.com/ant-design/ant-design/pull/23414) +- 🆕 Space support `align` prop. [#23306](https://github.com/ant-design/ant-design/pull/23306) +- 🆕 Upload support `isImageUrl` to force trade file as image. [#23248](https://github.com/ant-design/ant-design/pull/23248) [@onjuju](https://github.com/onjuju) +- 🆕 Form.Item support `initialValue` and `getValueProps` props. [#22993](https://github.com/ant-design/ant-design/pull/22993) +- ConfigProvider + - 🆕 ConfigProvider support `getTargetContainer` to config Affix `target` props. [#23751](https://github.com/ant-design/ant-design/pull/23751) + - 🆕 ConfigProvider support `input` prop to config Input `autoComplete`. [#23455](https://github.com/ant-design/ant-design/pull/23455) + - 🐞 Fix ConfigProvider `getPopupContainer` not working on DatePicker and Slider. [#23594](https://github.com/ant-design/ant-design/pull/23594) [@hengkx](https://github.com/hengkx) +- Table + - 🆕 Table `summary` support fixed columns. [#23647](https://github.com/ant-design/ant-design/pull/23647) + - 🆕 Table support responsive columns. [#23298](https://github.com/ant-design/ant-design/pull/23298) [@vbudovski](https://github.com/vbudovski) + - 🐞 Fix Table pagination default position in RTL. [#23747](https://github.com/ant-design/ant-design/pull/23747) + - 🐞 Fix Table crash when `pageSize` is `undefined`. [#23724](https://github.com/ant-design/ant-design/pull/23724) + - 🐞 fix Table nested margin when size is `small` or `middle`. [#23602](https://github.com/ant-design/ant-design/pull/23602) [@hengkx](https://github.com/hengkx) +- 🐞 Fix RangePicker `ranges` tag color to primary color. [#23705](https://github.com/ant-design/ant-design/pull/23705) +- 🐞 Fix Transfer with custom empty style issue. [#23694](https://github.com/ant-design/ant-design/pull/23694) [@hengkx](https://github.com/hengkx) +- Input + - 🐞 Fix Password caret position. [#23633](https://github.com/ant-design/ant-design/pull/23633) [@huntdream](https://github.com/huntdream) + - 💄 Adjust Input.Search icon style. [#23406](https://github.com/ant-design/ant-design/pull/23406) +- Button + - 🐞 Fix Button align problem of icon only. [#23671](https://github.com/ant-design/ant-design/pull/23671) + - 🐞 Fix Button of icon only wrong `loading` style. [#23614](https://github.com/ant-design/ant-design/pull/23614) + - 🐞 fix Button cannot be directly called by `react-dnd`. [#23571](https://github.com/ant-design/ant-design/pull/23571) [@hengkx](https://github.com/hengkx) +- Menu + - 🆕 Menu Item and SubMenu support `icon` prop. [#23629](https://github.com/ant-design/ant-design/pull/23629) + - 🐞 Fix Menu duplicated shadow style. [#23664](https://github.com/ant-design/ant-design/pull/23664) +- 🐞 Fix Tag cannot be directly called by `react-dnd`. [#23632](https://github.com/ant-design/ant-design/pull/23632) [@hengkx](https://github.com/hengkx) +- Anchor + - 🐞 Fix Anchor Link with multiple `#` can not jump correctly. [#23595](https://github.com/ant-design/ant-design/pull/23595) [@wuzekang](https://github.com/wuzekang) + - 🐞 Fix Input with `suffix` align problem. [#23606](https://github.com/ant-design/ant-design/pull/23606) +- 💄 Select arrow won't rotate when open. [#23468](https://github.com/ant-design/ant-design/pull/23468) +- 💄 Rate support `direction`. [#23321](https://github.com/ant-design/ant-design/pull/23321) +- 💄 Adjust font-size in compact mode. [#23135](https://github.com/ant-design/ant-design/pull/23135) +- RTL + - 💄 Optimize Result button style in RTL. [#23733](https://github.com/ant-design/ant-design/pull/23733) + - 💄 Add Divider RTL support. [#23734](https://github.com/ant-design/ant-design/pull/23734) + - 💄 Fix Alert style in RTL when no-icon. [#23714](https://github.com/ant-design/ant-design/pull/23714) + - 💄 Optimize Table expand animation and pagination style in RTL. [#23706](https://github.com/ant-design/ant-design/pull/23706) + - 💄 Fix Table filter dropdown position in RTL. [#23695](https://github.com/ant-design/ant-design/pull/23695) + - 💄 Fix Table rowSelect icon style in RTL. [#23690](https://github.com/ant-design/ant-design/pull/23690) + - 💄 Optimize List style in RTL. [#23676](https://github.com/ant-design/ant-design/pull/23676) + - 💄 Add Calendar RTL. [#23394](https://github.com/ant-design/ant-design/pull/23394) + - 💄 Optimize Input.Search style in RTL. [#23424](https://github.com/ant-design/ant-design/pull/23424) + - 💄 Add Notification RTL config. [#23185](https://github.com/ant-design/ant-design/pull/23185) +- TypeScript + - 🐞 Fix PageHeader `tag` definition. [#23712](https://github.com/ant-design/ant-design/pull/23712) [@hengkx](https://github.com/hengkx) + - 🗑 Remove Button deprecated `type="danger"` TypeScript definition and warn it. [#23709](https://github.com/ant-design/ant-design/pull/23709) + - 🐞 Fix Table pagination `position` definition. [#23681](https://github.com/ant-design/ant-design/pull/23681) [@hengkx](https://github.com/hengkx) + ## 4.1.5 `2020-04-25` diff --git a/CHANGELOG.zh-CN.md b/CHANGELOG.zh-CN.md index ad5a173c40..2cde08a8fe 100644 --- a/CHANGELOG.zh-CN.md +++ b/CHANGELOG.zh-CN.md @@ -15,6 +15,61 @@ timeline: true --- +## 4.2.0 + +`2020-04-29` + +- 🆕 List `grid` 支持所有分栏数字,比如分为 5 栏。[#23630](https://github.com/ant-design/ant-design/pull/23630) +- 🆕 Divider 新增 `plain` 属性,可用于设置一个非标题样式的分割文字。[#23405](https://github.com/ant-design/ant-design/pull/23405) +- 🆕 Typography `ellipsis` 支持 `onEllipsis` 事件。[#23414](https://github.com/ant-design/ant-design/pull/23414) +- 🆕 Space 支持 `align` 属性。[#23306](https://github.com/ant-design/ant-design/pull/23306) +- 🆕 Upload 添加 `isImageUrl` 属性以强制将文件作为图标文件。[#23248](https://github.com/ant-design/ant-design/pull/23248) [@onjuju](https://github.com/onjuju) +- 🆕 Form.Item 支持 `initialValue` 和 `getValueProps` 属性。[#22993](https://github.com/ant-design/ant-design/pull/22993) +- ConfigProvider + - 🆕 ConfigProvider 支持 `getTargetContainer` 以配置 Affix `target` 属性。[#23751](https://github.com/ant-design/ant-design/pull/23751) + - 🆕 ConfigProvider 添加 `input` 属性以支持全局化配置 Input `autoComplete` 的默认值。[#23455](https://github.com/ant-design/ant-design/pull/23455) + - 🐞 修复 ConfigProvider `getPopupContainer` 对 DatePicker 和 Slider 不生效的问题。[#23594](https://github.com/ant-design/ant-design/pull/23594) [@hengkx](https://github.com/hengkx) +- Table + - 🆕 Table `summary` 支持固定列。[#23647](https://github.com/ant-design/ant-design/pull/23647) + - 🆕 Table 支持响应式展现列。[#23298](https://github.com/ant-design/ant-design/pull/23298) [@vbudovski](https://github.com/vbudovski) + - 🐞 修复 Table pagination 在 RTL 下默认位置。[#23747](https://github.com/ant-design/ant-design/pull/23747) + - 🐞 修复 Table 在 `pageSize` 是 `undefined` 时崩溃的问题。[#23724](https://github.com/ant-design/ant-design/pull/23724) + - 🐞 修复 Table 大小为 `small` 和 `middle` 时嵌套表格错位的问题。[#23602](https://github.com/ant-design/ant-design/pull/23602) [@hengkx](https://github.com/hengkx) +- 🐞 修正 RangePicker 范围标签的颜色为主色。[#23705](https://github.com/ant-design/ant-design/pull/23705) +- 🐞 修复 Transfer 为空自定义图片样式问题。[#23694](https://github.com/ant-design/ant-design/pull/23694) [@hengkx](https://github.com/hengkx) +- Input + - 🐞 修复 Password 组件输入光标位置。[#23633](https://github.com/ant-design/ant-design/pull/23633) [@huntdream](https://github.com/huntdream) + - 💄 调整 Input.Search 的搜索图标样式。[#23406](https://github.com/ant-design/ant-design/pull/23406) +- Button + - 🐞 修复 Button 图标类型按钮的对齐问题。[#23671](https://github.com/ant-design/ant-design/pull/23671) + - 🐞 修复 Button 图标按钮 `loading` 样式错误的问题。[#23614](https://github.com/ant-design/ant-design/pull/23614) + - 🐞 解决 Button 无法直接被 `react-dnd` 调用的问题。[#23571](https://github.com/ant-design/ant-design/pull/23571) [@hengkx](https://github.com/hengkx) +- Menu + - 🆕 Menu Item 和 SubMenu 新增 `icon` 属性。[#23629](https://github.com/ant-design/ant-design/pull/23629) + - 🐞 修复 Menu 菜单重复阴影的问题。[#23664](https://github.com/ant-design/ant-design/pull/23664) +- 🐞 解决 Tag 无法直接被 `react-dnd` 调用的问题。[#23632](https://github.com/ant-design/ant-design/pull/23632) [@hengkx](https://github.com/hengkx) +- Anchor + - 🐞 修复 Anchor Link 包含多个 `#` 时无法跳转的问题。[#23595](https://github.com/ant-design/ant-design/pull/23595) [@wuzekang](https://github.com/wuzekang) + - 🐞 修复 Input 带 `suffix` 时的元素对齐问题。[#23606](https://github.com/ant-design/ant-design/pull/23606) +- 💄 Select 箭头打开时不再翻转。[#23468](https://github.com/ant-design/ant-design/pull/23468) +- 💄 新增 Rate 的 `direction` 支持优化。[#23321](https://github.com/ant-design/ant-design/pull/23321) +- 💄 调整紧凑模式下默认的字体大小。[#23135](https://github.com/ant-design/ant-design/pull/23135) +- RTL + - 💄 优化 Result RTL 下按钮样式。[#23733](https://github.com/ant-design/ant-design/pull/23733) + - 💄 新增 Divider RTL 支持。[#23734](https://github.com/ant-design/ant-design/pull/23734) + - 💄 修复 Alert 在 RTL 下无 icon 的间隔问题。[#23714](https://github.com/ant-design/ant-design/pull/23714) + - 💄 优化 Table RTL 模式下扩展按钮动画与分页样式问题。[#23706](https://github.com/ant-design/ant-design/pull/23706) + - 💄 修复 Table 筛选下拉框在 RTL 下的位置。[#23695](https://github.com/ant-design/ant-design/pull/23695) + - 💄 修复 Table 勾选框图标 RTL 样式。[#23690](https://github.com/ant-design/ant-design/pull/23690) + - 💄 优化 List RTL 样式。[#23676](https://github.com/ant-design/ant-design/pull/23676) + - 💄 新增 Calendar RTL 支持。[#23394](https://github.com/ant-design/ant-design/pull/23394) + - 💄 优化 Input.Search RTL 样式。[#23424](https://github.com/ant-design/ant-design/pull/23424) + - 💄 增加 Notification RTL 设置。[#23185](https://github.com/ant-design/ant-design/pull/23185) +- TypeScript + - 🐞 修复 PageHeader `tag` 属性定义错误。[#23712](https://github.com/ant-design/ant-design/pull/23712) [@hengkx](https://github.com/hengkx) + - 🗑 移除已废弃的 Button `type="danger"` TypeScript 定义并增加警告信息。[#23709](https://github.com/ant-design/ant-design/pull/23709) + - 🐞 修复 Table pagination `position` Typescript 定义错误。[#23681](https://github.com/ant-design/ant-design/pull/23681) [@hengkx](https://github.com/hengkx) + ## 4.1.5 `2020-04-25` diff --git a/components/_util/ref.ts b/components/_util/ref.ts index 6221eb1041..875b504ffb 100644 --- a/components/_util/ref.ts +++ b/components/_util/ref.ts @@ -1,4 +1,4 @@ -import React from 'react'; +import * as React from 'react'; export function fillRef(ref: React.Ref, node: T) { if (typeof ref === 'function') { diff --git a/components/anchor/__tests__/Anchor.test.js b/components/anchor/__tests__/Anchor.test.js deleted file mode 100644 index cc059e6a1c..0000000000 --- a/components/anchor/__tests__/Anchor.test.js +++ /dev/null @@ -1,360 +0,0 @@ -import React from 'react'; -import { mount } from 'enzyme'; -import Anchor from '..'; -import { sleep } from '../../../tests/utils'; - -const { Link } = Anchor; - -describe('Anchor Render', () => { - const getBoundingClientRectMock = jest.spyOn( - HTMLHeadingElement.prototype, - 'getBoundingClientRect', - ); - const getClientRectsMock = jest.spyOn(HTMLHeadingElement.prototype, 'getClientRects'); - - beforeAll(() => { - getBoundingClientRectMock.mockReturnValue({ - width: 100, - height: 100, - top: 1000, - }); - getClientRectsMock.mockReturnValue({ length: 1 }); - }); - - afterAll(() => { - getBoundingClientRectMock.mockRestore(); - getClientRectsMock.mockRestore(); - }); - - it('Anchor render perfectly', () => { - const wrapper = mount( - - - , - ); - - wrapper.find('a[href="#API"]').simulate('click'); - - wrapper.instance().handleScroll(); - expect(wrapper.instance().state).not.toBe(null); - }); - - it('Anchor render perfectly for complete href - click', () => { - const wrapper = mount( - - - , - ); - wrapper.find('a[href="http://www.example.com/#API"]').simulate('click'); - expect(wrapper.instance().state.activeLink).toBe('http://www.example.com/#API'); - }); - - it('Anchor render perfectly for complete href - hash router', async () => { - const scrollToSpy = jest.spyOn(window, 'scrollTo'); - let root = document.getElementById('root'); - if (!root) { - root = document.createElement('div', { id: 'root' }); - root.id = 'root'; - document.body.appendChild(root); - } - mount(
Q1
, { attachTo: root }); - const wrapper = mount( - - - , - ); - - wrapper.instance().handleScrollTo('/#/faq?locale=en#Q1'); - expect(wrapper.instance().state.activeLink).toBe('/#/faq?locale=en#Q1'); - expect(scrollToSpy).not.toHaveBeenCalled(); - await sleep(1000); - expect(scrollToSpy).toHaveBeenCalled(); - }); - - it('Anchor render perfectly for complete href - scroll', () => { - let root = document.getElementById('root'); - if (!root) { - root = document.createElement('div', { id: 'root' }); - root.id = 'root'; - document.body.appendChild(root); - } - mount(
Hello
, { attachTo: root }); - const wrapper = mount( - - - , - ); - wrapper.instance().handleScroll(); - expect(wrapper.instance().state.activeLink).toBe('http://www.example.com/#API'); - }); - - it('Anchor render perfectly for complete href - scrollTo', async () => { - const scrollToSpy = jest.spyOn(window, 'scrollTo'); - let root = document.getElementById('root'); - if (!root) { - root = document.createElement('div', { id: 'root' }); - root.id = 'root'; - document.body.appendChild(root); - } - mount(
Hello
, { attachTo: root }); - const wrapper = mount( - - - , - ); - wrapper.instance().handleScrollTo('##API'); - expect(wrapper.instance().state.activeLink).toBe('##API'); - const calls = scrollToSpy.mock.calls.length; - await sleep(1000); - expect(scrollToSpy.mock.calls.length).toBeGreaterThan(calls); - }); - - it('should remove listener when unmount', async () => { - const wrapper = mount( - - - , - ); - const removeListenerSpy = jest.spyOn(wrapper.instance().scrollEvent, 'remove'); - wrapper.unmount(); - expect(removeListenerSpy).toHaveBeenCalled(); - }); - - it('should unregister link when unmount children', async () => { - const wrapper = mount( - - - , - ); - expect(wrapper.instance().links).toEqual(['#API']); - wrapper.setProps({ children: null }); - expect(wrapper.instance().links).toEqual([]); - }); - - it('should update links when link href update', async () => { - let anchorInstance = null; - function AnchorUpdate({ href }) { - return ( - { - anchorInstance = c; - }} - > - - - ); - } - const wrapper = mount(); - - expect(anchorInstance.links).toEqual(['#API']); - wrapper.setProps({ href: '#API_1' }); - expect(anchorInstance.links).toEqual(['#API_1']); - }); - - it('Anchor onClick event', () => { - let event; - let link; - const handleClick = (...arg) => { - [event, link] = arg; - }; - - const href = '#API'; - const title = 'API'; - - const wrapper = mount( - - - , - ); - - wrapper.find(`a[href="${href}"]`).simulate('click'); - - wrapper.instance().handleScroll(); - expect(event).not.toBe(undefined); - expect(link).toEqual({ href, title }); - }); - - it('Different function returns the same DOM', async () => { - let root = document.getElementById('root'); - if (!root) { - root = document.createElement('div', { id: 'root' }); - root.id = 'root'; - document.body.appendChild(root); - } - mount(
Hello
, { attachTo: root }); - const getContainerA = () => { - return document.getElementById('API'); - }; - const getContainerB = () => { - return document.getElementById('API'); - }; - - const wrapper = mount( - - - , - ); - const removeListenerSpy = jest.spyOn(wrapper.instance().scrollEvent, 'remove'); - await sleep(1000); - wrapper.setProps({ getContainer: getContainerB }); - expect(removeListenerSpy).not.toHaveBeenCalled(); - }); - - it('Different function returns different DOM', async () => { - let root = document.getElementById('root'); - if (!root) { - root = document.createElement('div', { id: 'root' }); - root.id = 'root'; - document.body.appendChild(root); - } - mount( -
-
Hello
-
World
-
, - { attachTo: root }, - ); - const getContainerA = () => { - return document.getElementById('API1'); - }; - const getContainerB = () => { - return document.getElementById('API2'); - }; - const wrapper = mount( - - - - , - ); - const removeListenerSpy = jest.spyOn(wrapper.instance().scrollEvent, 'remove'); - expect(removeListenerSpy).not.toHaveBeenCalled(); - await sleep(1000); - wrapper.setProps({ getContainer: getContainerB }); - expect(removeListenerSpy).toHaveBeenCalled(); - }); - - it('Same function returns the same DOM', () => { - let root = document.getElementById('root'); - if (!root) { - root = document.createElement('div', { id: 'root' }); - root.id = 'root'; - document.body.appendChild(root); - } - mount(
Hello
, { attachTo: root }); - const getContainer = () => document.getElementById('API'); - const wrapper = mount( - - - , - ); - wrapper.find('a[href="#API"]').simulate('click'); - wrapper.instance().handleScroll(); - expect(wrapper.instance().state).not.toBe(null); - }); - - it('Same function returns different DOM', async () => { - let root = document.getElementById('root'); - if (!root) { - root = document.createElement('div', { id: 'root' }); - root.id = 'root'; - document.body.appendChild(root); - } - mount( -
-
Hello
-
World
-
, - { attachTo: root }, - ); - const holdContainer = { - container: document.getElementById('API1'), - }; - const getContainer = () => { - return holdContainer.container; - }; - const wrapper = mount( - - - - , - ); - const removeListenerSpy = jest.spyOn(wrapper.instance().scrollEvent, 'remove'); - expect(removeListenerSpy).not.toHaveBeenCalled(); - await sleep(1000); - holdContainer.container = document.getElementById('API2'); - wrapper.setProps({ 'data-only-trigger-re-render': true }); - expect(removeListenerSpy).toHaveBeenCalled(); - }); - - it('Anchor getCurrentAnchor prop', () => { - const getCurrentAnchor = () => '#API2'; - const wrapper = mount( - - - - , - ); - expect(wrapper.instance().state.activeLink).toBe('#API2'); - }); - - it('Anchor targetOffset prop', async () => { - let dateNowMock; - - function dataNowMockFn() { - let start = 0; - - const handler = () => { - return (start += 1000); - }; - - return jest.spyOn(Date, 'now').mockImplementation(handler); - } - - dateNowMock = dataNowMockFn(); - - const scrollToSpy = jest.spyOn(window, 'scrollTo'); - let root = document.getElementById('root'); - if (!root) { - root = document.createElement('div', { id: 'root' }); - root.id = 'root'; - document.body.appendChild(root); - } - mount(

Hello

, { attachTo: root }); - const wrapper = mount( - - - , - ); - wrapper.instance().handleScrollTo('#API'); - await sleep(30); - expect(scrollToSpy).toHaveBeenLastCalledWith(0, 1000); - dateNowMock = dataNowMockFn(); - - wrapper.setProps({ offsetTop: 100 }); - wrapper.instance().handleScrollTo('#API'); - await sleep(30); - expect(scrollToSpy).toHaveBeenLastCalledWith(0, 900); - dateNowMock = dataNowMockFn(); - - wrapper.setProps({ targetOffset: 200 }); - wrapper.instance().handleScrollTo('#API'); - await sleep(30); - expect(scrollToSpy).toHaveBeenLastCalledWith(0, 800); - - dateNowMock.mockRestore(); - }); - - it('Anchor onChange prop', async () => { - const onChange = jest.fn(); - const wrapper = mount( - - - - , - ); - expect(onChange).toHaveBeenCalledTimes(1); - wrapper.instance().handleScrollTo('#API2'); - expect(onChange).toHaveBeenCalledTimes(2); - expect(onChange).toHaveBeenCalledWith('#API2'); - }); -}); diff --git a/components/anchor/__tests__/Anchor.test.tsx b/components/anchor/__tests__/Anchor.test.tsx new file mode 100644 index 0000000000..085cac3627 --- /dev/null +++ b/components/anchor/__tests__/Anchor.test.tsx @@ -0,0 +1,360 @@ +import React from 'react'; +import { mount } from 'enzyme'; +import Anchor from '..'; +import { sleep } from '../../../tests/utils'; + +const { Link } = Anchor; + +function createGetContainer(id: string) { + return () => { + const container = document.getElementById(id); + if (container == null) { + throw new Error(); + } + return container; + }; +} + +function createDiv() { + const root = document.createElement('div'); + document.body.appendChild(root); + return root; +} + +let idCounter = 0; +const getHashUrl = () => `Anchor-API-${idCounter++}`; + +describe('Anchor Render', () => { + const getBoundingClientRectMock = jest.spyOn( + HTMLHeadingElement.prototype, + 'getBoundingClientRect', + ); + const getClientRectsMock = jest.spyOn(HTMLHeadingElement.prototype, 'getClientRects'); + + beforeAll(() => { + getBoundingClientRectMock.mockReturnValue({ + width: 100, + height: 100, + top: 1000, + } as DOMRect); + getClientRectsMock.mockReturnValue({ length: 1 } as DOMRectList); + }); + + afterAll(() => { + getBoundingClientRectMock.mockRestore(); + getClientRectsMock.mockRestore(); + }); + + it('Anchor render perfectly', () => { + const hash = getHashUrl(); + const wrapper = mount( + + + , + ); + + wrapper.find(`a[href="#${hash}"]`).simulate('click'); + + wrapper.instance().handleScroll(); + expect(wrapper.instance().state).not.toBe(null); + }); + + it('Anchor render perfectly for complete href - click', () => { + const hash = getHashUrl(); + const wrapper = mount( + + + , + ); + wrapper.find(`a[href="http://www.example.com/#${hash}"]`).simulate('click'); + expect(wrapper.instance().state.activeLink).toBe(`http://www.example.com/#${hash}`); + }); + + it('Anchor render perfectly for complete href - hash router', async () => { + const root = createDiv(); + const scrollToSpy = jest.spyOn(window, 'scrollTo'); + mount(
Q1
, { attachTo: root }); + const wrapper = mount( + + + , + ); + + wrapper.instance().handleScrollTo('/#/faq?locale=en#Q1'); + expect(wrapper.instance().state.activeLink).toBe('/#/faq?locale=en#Q1'); + expect(scrollToSpy).not.toHaveBeenCalled(); + await sleep(1000); + expect(scrollToSpy).toHaveBeenCalled(); + }); + + it('Anchor render perfectly for complete href - scroll', () => { + const hash = getHashUrl(); + const root = createDiv(); + mount(
Hello
, { attachTo: root }); + const wrapper = mount( + + + , + ); + wrapper.instance().handleScroll(); + expect(wrapper.instance().state.activeLink).toBe(`http://www.example.com/#${hash}`); + }); + + it('Anchor render perfectly for complete href - scrollTo', async () => { + const hash = getHashUrl(); + const scrollToSpy = jest.spyOn(window, 'scrollTo'); + const root = createDiv(); + mount(
Hello
, { attachTo: root }); + const wrapper = mount( + + + , + ); + wrapper.instance().handleScrollTo(`##${hash}`); + expect(wrapper.instance().state.activeLink).toBe(`##${hash}`); + const calls = scrollToSpy.mock.calls.length; + await sleep(1000); + expect(scrollToSpy.mock.calls.length).toBeGreaterThan(calls); + }); + + it('should remove listener when unmount', async () => { + const hash = getHashUrl(); + const wrapper = mount( + + + , + ); + const removeListenerSpy = jest.spyOn((wrapper.instance() as any).scrollEvent, 'remove'); + wrapper.unmount(); + expect(removeListenerSpy).toHaveBeenCalled(); + }); + + it('should unregister link when unmount children', async () => { + const hash = getHashUrl(); + const wrapper = mount( + + + , + ); + expect((wrapper.instance() as any).links).toEqual([`#${hash}`]); + wrapper.setProps({ children: null }); + expect((wrapper.instance() as any).links).toEqual([]); + }); + + it('should update links when link href update', async () => { + const hash = getHashUrl(); + let anchorInstance: Anchor | null = null; + function AnchorUpdate({ href }: { href: string }) { + return ( + { + anchorInstance = c; + }} + > + + + ); + } + const wrapper = mount(); + + if (anchorInstance == null) { + throw new Error('anchorInstance should not be null'); + } + expect((anchorInstance as any).links).toEqual([`#${hash}`]); + wrapper.setProps({ href: `#${hash}_1` }); + expect((anchorInstance as any).links).toEqual([`#${hash}_1`]); + }); + + it('Anchor onClick event', () => { + const hash = getHashUrl(); + let event; + let link; + const handleClick = ( + e: React.MouseEvent, + _link: { title: React.ReactNode; href: string }, + ) => { + event = e; + link = _link; + }; + + const href = `#${hash}`; + const title = hash; + + const wrapper = mount( + + + , + ); + + wrapper.find(`a[href="${href}"]`).simulate('click'); + + wrapper.instance().handleScroll(); + expect(event).not.toBe(undefined); + expect(link).toEqual({ href, title }); + }); + + it('Different function returns the same DOM', async () => { + const hash = getHashUrl(); + const root = createDiv(); + mount(
Hello
, { attachTo: root }); + const getContainerA = createGetContainer(hash); + const getContainerB = createGetContainer(hash); + + const wrapper = mount( + + + , + ); + const removeListenerSpy = jest.spyOn((wrapper.instance() as any).scrollEvent, 'remove'); + await sleep(1000); + wrapper.setProps({ getContainer: getContainerB }); + expect(removeListenerSpy).not.toHaveBeenCalled(); + }); + + it('Different function returns different DOM', async () => { + const hash1 = getHashUrl(); + const hash2 = getHashUrl(); + const root = createDiv(); + mount( +
+
Hello
+
World
+
, + { attachTo: root }, + ); + const getContainerA = createGetContainer(hash1); + const getContainerB = createGetContainer(hash2); + const wrapper = mount( + + + + , + ); + const removeListenerSpy = jest.spyOn((wrapper.instance() as any).scrollEvent, 'remove'); + expect(removeListenerSpy).not.toHaveBeenCalled(); + await sleep(1000); + wrapper.setProps({ getContainer: getContainerB }); + expect(removeListenerSpy).toHaveBeenCalled(); + }); + + it('Same function returns the same DOM', () => { + const hash = getHashUrl(); + const root = createDiv(); + mount(
Hello
, { attachTo: root }); + const getContainer = createGetContainer(hash); + const wrapper = mount( + + + , + ); + wrapper.find(`a[href="#${hash}"]`).simulate('click'); + (wrapper.instance() as any).handleScroll(); + expect(wrapper.instance().state).not.toBe(null); + }); + + it('Same function returns different DOM', async () => { + const hash1 = getHashUrl(); + const hash2 = getHashUrl(); + const root = createDiv(); + mount( +
+
Hello
+
World
+
, + { attachTo: root }, + ); + const holdContainer = { + container: document.getElementById(hash1), + }; + const getContainer = () => { + if (holdContainer.container == null) { + throw new Error('container should not be null'); + } + return holdContainer.container; + }; + const wrapper = mount( + + + + , + ); + const removeListenerSpy = jest.spyOn((wrapper.instance() as any).scrollEvent, 'remove'); + expect(removeListenerSpy).not.toHaveBeenCalled(); + await sleep(1000); + holdContainer.container = document.getElementById(hash2); + wrapper.setProps({ 'data-only-trigger-re-render': true }); + expect(removeListenerSpy).toHaveBeenCalled(); + }); + + it('Anchor getCurrentAnchor prop', () => { + const hash1 = getHashUrl(); + const hash2 = getHashUrl(); + const getCurrentAnchor = () => `#${hash2}`; + const wrapper = mount( + + + + , + ); + expect(wrapper.instance().state.activeLink).toBe(`#${hash2}`); + }); + + it('Anchor targetOffset prop', async () => { + const hash = getHashUrl(); + let dateNowMock; + + function dataNowMockFn() { + let start = 0; + + const handler = () => { + return (start += 1000); + }; + + return jest.spyOn(Date, 'now').mockImplementation(handler); + } + + dateNowMock = dataNowMockFn(); + + const scrollToSpy = jest.spyOn(window, 'scrollTo'); + const root = createDiv(); + mount(

Hello

, { attachTo: root }); + const wrapper = mount( + + + , + ); + wrapper.instance().handleScrollTo(`#${hash}`); + await sleep(30); + expect(scrollToSpy).toHaveBeenLastCalledWith(0, 1000); + dateNowMock = dataNowMockFn(); + + wrapper.setProps({ offsetTop: 100 }); + wrapper.instance().handleScrollTo(`#${hash}`); + await sleep(30); + expect(scrollToSpy).toHaveBeenLastCalledWith(0, 900); + dateNowMock = dataNowMockFn(); + + wrapper.setProps({ targetOffset: 200 }); + wrapper.instance().handleScrollTo(`#${hash}`); + await sleep(30); + expect(scrollToSpy).toHaveBeenLastCalledWith(0, 800); + + dateNowMock.mockRestore(); + }); + + it('Anchor onChange prop', async () => { + const hash1 = getHashUrl(); + const hash2 = getHashUrl(); + const onChange = jest.fn(); + const wrapper = mount( + + + + , + ); + expect(onChange).toHaveBeenCalledTimes(1); + wrapper.instance().handleScrollTo(hash2); + expect(onChange).toHaveBeenCalledTimes(2); + expect(onChange).toHaveBeenCalledWith(hash2); + }); +}); diff --git a/components/anchor/__tests__/__snapshots__/demo.test.js.snap b/components/anchor/__tests__/__snapshots__/demo.test.ts.snap similarity index 100% rename from components/anchor/__tests__/__snapshots__/demo.test.js.snap rename to components/anchor/__tests__/__snapshots__/demo.test.ts.snap diff --git a/components/anchor/__tests__/demo.test.js b/components/anchor/__tests__/demo.test.ts similarity index 100% rename from components/anchor/__tests__/demo.test.js rename to components/anchor/__tests__/demo.test.ts diff --git a/components/anchor/context.ts b/components/anchor/context.ts index fecad8a172..9a40f9f1de 100644 --- a/components/anchor/context.ts +++ b/components/anchor/context.ts @@ -1,4 +1,4 @@ -import React from 'react'; +import * as React from 'react'; import { AntAnchor } from './Anchor'; const AnchorContext = React.createContext(null as any); diff --git a/components/button/style/index.less b/components/button/style/index.less index 600ba25186..353a85c821 100644 --- a/components/button/style/index.less +++ b/components/button/style/index.less @@ -150,11 +150,16 @@ } & > &-loading-icon { - padding-right: @margin-xs; transition: all 0.3s @ease-in-out; + .@{iconfont-css-prefix} { + padding-right: @margin-xs; + } + &:only-child { - padding-right: 0; + .@{iconfont-css-prefix} { + padding-right: 0; + } } } diff --git a/components/drawer/__tests__/__snapshots__/demo.test.js.snap b/components/drawer/__tests__/__snapshots__/demo.test.js.snap index a3734787ec..65c80b480b 100644 --- a/components/drawer/__tests__/__snapshots__/demo.test.js.snap +++ b/components/drawer/__tests__/__snapshots__/demo.test.js.snap @@ -66,95 +66,107 @@ exports[`renders ./components/drawer/demo/multi-level-drawer.md correctly 1`] = exports[`renders ./components/drawer/demo/placement.md correctly 1`] = `
- - + + + +
+
+
- - - - - - right - - - - + + Open + + +
- `; diff --git a/components/drawer/demo/placement.md b/components/drawer/demo/placement.md index ab9cf24112..b3518ae552 100644 --- a/components/drawer/demo/placement.md +++ b/components/drawer/demo/placement.md @@ -14,7 +14,7 @@ title: The Drawer can appear from any edge of the screen. ```jsx -import { Drawer, Button, Radio } from 'antd'; +import { Drawer, Button, Radio, Space } from 'antd'; const RadioGroup = Radio.Group; @@ -42,19 +42,17 @@ class App extends React.Component { render() { return (
- - top - right - bottom - left - - + + + top + right + bottom + left + + + boolean | | resetFields | Reset fields to `initialValues` | (fields?: [NamePath](#NamePath)[]) => void | | scrollToField | Scroll to field position | (name: [NamePath](#NamePath), options: [[ScrollOptions](https://github.com/stipsan/scroll-into-view-if-needed/blob/ece40bd9143f48caf4b99503425ecb16b0ad8249/src/types.ts#L10)]) => void | -| setFields | Set fields status | (fields: FieldData[]) => void | +| setFields | Set fields status | (fields: [FieldData](#FieldData)[]) => void | | setFieldsValue | Set fields value | (values) => void | | submit | Submit the form. It's same as click `submit` button | () => void | | validateFields | Validate fields | (nameList?: [NamePath](#NamePath)[]) => Promise | @@ -325,6 +325,16 @@ In most case, we always recommend to use Form `initialValues`. Use Item `initial 1. Form `initialValues` is the first priority 2. Field `initialValue` is secondary \*. Not work when multiple Item with same `name` setting the `initialValue` +### Why `onFieldsChange` trigger three times on change when field set `rules`? + +Validating is also part of the value updating. It pass follow steps: + +1. Trigger value change +2. Rule validating +3. Rule validated + +In each `onFieldsChange`, you will get `false` > `true` > `false` with `isFieldValidating`. + diff --git a/components/modal/demo/confirm.md b/components/modal/demo/confirm.md index a675e8c453..aa9c9a623e 100644 --- a/components/modal/demo/confirm.md +++ b/components/modal/demo/confirm.md @@ -14,7 +14,7 @@ title: Use `confirm()` to show a confirmation modal dialog. ```jsx -import { Modal, Button } from 'antd'; +import { Modal, Button, Space } from 'antd'; import { ExclamationCircleOutlined } from '@ant-design/icons'; const { confirm } = Modal; @@ -71,7 +71,7 @@ function showPropsConfirm() { } ReactDOM.render( -
+ -
, + , mountNode, ); ``` diff --git a/components/modal/demo/hooks.md b/components/modal/demo/hooks.md index c345ae24c6..268217b301 100644 --- a/components/modal/demo/hooks.md +++ b/components/modal/demo/hooks.md @@ -14,7 +14,7 @@ title: Use `Modal.useModal` to get `contextHolder` with context accessible issue. ```jsx -import { Modal, Button } from 'antd'; +import { Modal, Button, Space } from 'antd'; const ReachableContext = React.createContext(); const UnreachableContext = React.createContext(); @@ -35,35 +35,36 @@ const App = () => { return ( - - - - - + + + + + + {/* `contextHolder` should always under the context you want to access */} {contextHolder} diff --git a/components/modal/demo/info.md b/components/modal/demo/info.md index 3ef2a49f51..eb910fde42 100644 --- a/components/modal/demo/info.md +++ b/components/modal/demo/info.md @@ -14,7 +14,7 @@ title: In the various types of information modal dialog, only one button to close dialog is provided. ```jsx -import { Modal, Button } from 'antd'; +import { Modal, Button, Space } from 'antd'; function info() { Modal.info({ @@ -50,12 +50,12 @@ function warning() { } ReactDOM.render( -
+ -
, + , mountNode, ); ``` diff --git a/components/modal/index.en-US.md b/components/modal/index.en-US.md index 4c8b07a293..5c62b04d4e 100644 --- a/components/modal/index.en-US.md +++ b/components/modal/index.en-US.md @@ -91,12 +91,6 @@ modal.update({ modal.destroy(); ``` - - - `Modal.destroyAll` `Modal.destroyAll()` could destroy all confirmation modal dialogs(Modal.info/Modal.success/Modal.error/Modal.warning/Modal.confirm). Usually, you can use it in router change event to destroy confirm modal dialog automatically without use modal reference to close( it's too complex to use for all modal dialogs) diff --git a/components/modal/index.zh-CN.md b/components/modal/index.zh-CN.md index 7ebd81f94b..deba024dfa 100644 --- a/components/modal/index.zh-CN.md +++ b/components/modal/index.zh-CN.md @@ -93,12 +93,6 @@ modal.update({ modal.destroy(); ``` - - - `Modal.destroyAll` 使用 `Modal.destroyAll()` 可以销毁弹出的确认窗(即上述的 Modal.info、Modal.success、Modal.error、Modal.warning、Modal.confirm)。通常用于路由监听当中,处理路由前进、后退不能销毁确认对话框的问题,而不用各处去使用实例的返回值进行关闭(modal.destroy() 适用于主动关闭,而不是路由这样被动关闭) diff --git a/components/notification/__tests__/__snapshots__/demo.test.js.snap b/components/notification/__tests__/__snapshots__/demo.test.js.snap index 6008d2bd07..0df7e46fa2 100644 --- a/components/notification/__tests__/__snapshots__/demo.test.js.snap +++ b/components/notification/__tests__/__snapshots__/demo.test.js.snap @@ -46,243 +46,295 @@ exports[`renders ./components/notification/demo/duration.md correctly 1`] = ` exports[`renders ./components/notification/demo/hooks.md correctly 1`] = ` Array [ - , - +
+
- - - - topRight - - , + + + + + topRight + + +
+ , +
- - - - bottomRight - - , + + + + + bottomRight + + +
+ , ] `; exports[`renders ./components/notification/demo/placement.md correctly 1`] = `
- - +
+
- - - - topRight - - + + + + + topRight + + +
+ +
- - - - bottomRight - - + + + + + bottomRight + + +
+ `; @@ -309,38 +361,59 @@ exports[`renders ./components/notification/demo/with-btn.md correctly 1`] = ` `; exports[`renders ./components/notification/demo/with-icon.md correctly 1`] = ` -
- - +
+
- - Info - - - +
+
- - Warning - - - +
+
- - Error - - + +
`; diff --git a/components/notification/demo/hooks.md b/components/notification/demo/hooks.md index 955880e950..a7663e15d8 100755 --- a/components/notification/demo/hooks.md +++ b/components/notification/demo/hooks.md @@ -14,7 +14,7 @@ title: Use `notification.useNotification` to get `contextHolder` with context accessible issue. ```jsx -import { Button, notification, Divider } from 'antd'; +import { Button, notification, Divider, Space } from 'antd'; import { RadiusUpleftOutlined, RadiusUprightOutlined, @@ -38,23 +38,27 @@ const Demo = () => { return ( {contextHolder} - - + + + + - - + + + + ); }; diff --git a/components/notification/demo/placement.md b/components/notification/demo/placement.md index bece67e385..7a6b385c8c 100755 --- a/components/notification/demo/placement.md +++ b/components/notification/demo/placement.md @@ -14,7 +14,7 @@ title: A notification box can appear from the `topRight`, `bottomRight`, `bottomLeft` or `topLeft` of the viewport. ```jsx -import { Button, notification, Divider } from 'antd'; +import { Button, notification, Divider, Space } from 'antd'; import { RadiusUpleftOutlined, RadiusUprightOutlined, @@ -33,23 +33,27 @@ const openNotification = placement => { ReactDOM.render(
- - + + + + - - + + + +
, mountNode, ); diff --git a/components/notification/demo/with-icon.md b/components/notification/demo/with-icon.md index b2645300c9..38be252d57 100644 --- a/components/notification/demo/with-icon.md +++ b/components/notification/demo/with-icon.md @@ -14,7 +14,7 @@ title: A notification box with a icon at the left side. ```jsx -import { Button, notification } from 'antd'; +import { Button, notification, Space } from 'antd'; const openNotificationWithIcon = type => { notification[type]({ @@ -25,18 +25,12 @@ const openNotificationWithIcon = type => { }; ReactDOM.render( -
+ -
, + , mountNode, ); ``` - - diff --git a/components/popover/demo/placement.md b/components/popover/demo/placement.md index a2b8c69851..1e346d2311 100755 --- a/components/popover/demo/placement.md +++ b/components/popover/demo/placement.md @@ -85,6 +85,11 @@ ReactDOM.render( margin-right: 8px; margin-bottom: 8px; } +.code-box-demo .ant-btn-rtl { + margin-right: 0; + margin-left: 8px; + margin-bottom: 8px; +} #components-popover-demo-placement .ant-btn { width: 70px; text-align: center; diff --git a/components/result/style/rtl.less b/components/result/style/rtl.less index 20b8376929..3b286b7492 100644 --- a/components/result/style/rtl.less +++ b/components/result/style/rtl.less @@ -17,7 +17,7 @@ &:last-child { .@{result-prefix-cls}-rtl & { - margin-left: 8px; + margin-left: 0; } } } diff --git a/components/slider/index.en-US.md b/components/slider/index.en-US.md index 265891a70f..e1531c4353 100644 --- a/components/slider/index.en-US.md +++ b/components/slider/index.en-US.md @@ -25,14 +25,14 @@ To input a value in a range. | range | dual thumb mode | boolean | false | | | reverse | reverse the component | boolean | false | | | step | The granularity the slider can step through values. Must greater than 0, and be divided by (max - min) . When `marks` no null, `step` can be `null`. | number\|null | 1 | | -| tipFormatter | Slider will pass its value to `tipFormatter`, and display its value in Tooltip, and hide Tooltip when return value is null. | Function\|null | IDENTITY | | +| tipFormatter | Slider will pass its value to `tipFormatter`, and display its value in Tooltip, and hide Tooltip when return value is null. | value => ReactNode\|null | IDENTITY | | | value | The value of slider. When `range` is `false`, use `number`, otherwise, use `[number, number]` | number\|\[number, number] | | | vertical | If true, the slider will be vertical. | Boolean | false | | -| onAfterChange | Fire when `onmouseup` is fired. | Function(value) | NOOP | | -| onChange | Callback function that is fired when the user changes the slider's value. | Function(value) | NOOP | | +| onAfterChange | Fire when `onmouseup` is fired. | (value) => void | NOOP | | +| onChange | Callback function that is fired when the user changes the slider's value. | (value) => void | NOOP | | | tooltipPlacement | Set Tooltip display position. Ref [`Tooltip`](/components/tooltip/). | string | | | | tooltipVisible | If true, Tooltip will show always, or it will not show anyway, even if dragging or hovering. | Boolean | | | -| getTooltipPopupContainer | The DOM container of the Tooltip, the default behavior is to create a div element in body. | Function | () => document.body | | +| getTooltipPopupContainer | The DOM container of the Tooltip, the default behavior is to create a div element in body. | (triggerNode) => HTMLElement | () => document.body | | ## Methods diff --git a/components/slider/index.zh-CN.md b/components/slider/index.zh-CN.md index c0c5b6e5d2..e737f020e8 100644 --- a/components/slider/index.zh-CN.md +++ b/components/slider/index.zh-CN.md @@ -26,14 +26,14 @@ title: Slider | range | 双滑块模式 | boolean | false | | | reverse | 反向坐标轴 | boolean | false | | | step | 步长,取值必须大于 0,并且可被 (max - min) 整除。当 `marks` 不为空对象时,可以设置 `step` 为 `null`,此时 Slider 的可选值仅有 marks 标出来的部分。 | number\|null | 1 | | -| tipFormatter | Slider 会把当前值传给 `tipFormatter`,并在 Tooltip 中显示 `tipFormatter` 的返回值,若为 null,则隐藏 Tooltip。 | Function\|null | IDENTITY | | +| tipFormatter | Slider 会把当前值传给 `tipFormatter`,并在 Tooltip 中显示 `tipFormatter` 的返回值,若为 null,则隐藏 Tooltip。 | value => ReactNode\|null | IDENTITY | | | value | 设置当前取值。当 `range` 为 `false` 时,使用 `number`,否则用 `[number, number]` | number\|\[number, number] | | | | vertical | 值为 `true` 时,Slider 为垂直方向 | Boolean | false | | -| onAfterChange | 与 `onmouseup` 触发时机一致,把当前值作为参数传入。 | Function(value) | NOOP | | -| onChange | 当 Slider 的值发生改变时,会触发 onChange 事件,并把改变后的值作为参数传入。 | Function(value) | NOOP | | +| onAfterChange | 与 `onmouseup` 触发时机一致,把当前值作为参数传入。 | (value) => void | NOOP | | +| onChange | 当 Slider 的值发生改变时,会触发 onChange 事件,并把改变后的值作为参数传入。 | (value) => void | NOOP | | | tooltipPlacement | 设置 Tooltip 展示位置。参考 [`Tooltip`](/components/tooltip/)。 | string | | | | tooltipVisible | 值为`true`时,Tooltip 将会始终显示;否则始终不显示,哪怕在拖拽及移入时。 | Boolean | | | -| getTooltipPopupContainer | Tooltip 渲染父节点,默认渲染到 body 上。 | Function | () => document.body | | +| getTooltipPopupContainer | Tooltip 渲染父节点,默认渲染到 body 上。 | (triggerNode) => HTMLElement | () => document.body | | ## 方法 diff --git a/components/slider/style/index.less b/components/slider/style/index.less index 2a3e562181..492844e859 100644 --- a/components/slider/style/index.less +++ b/components/slider/style/index.less @@ -92,6 +92,7 @@ text-align: center; word-break: keep-all; cursor: pointer; + user-select: none; &-active { color: @text-color; diff --git a/components/spin/__tests__/__snapshots__/demo.test.js.snap b/components/spin/__tests__/__snapshots__/demo.test.js.snap index bd74102eae..77914558d5 100644 --- a/components/spin/__tests__/__snapshots__/demo.test.js.snap +++ b/components/spin/__tests__/__snapshots__/demo.test.js.snap @@ -165,66 +165,82 @@ exports[`renders ./components/spin/demo/nested.md correctly 1`] = ` `; exports[`renders ./components/spin/demo/size.md correctly 1`] = ` -
+
- - - - - - + + + + + + +
- - - - - - + + + + + + +
- - - - - - + + + + + + +
`; diff --git a/components/spin/demo/size.md b/components/spin/demo/size.md index c92ff5841c..2737994fa1 100644 --- a/components/spin/demo/size.md +++ b/components/spin/demo/size.md @@ -14,20 +14,14 @@ title: A small `Spin` is used for loading text, default sized `Spin` for loading a card-level block, and large `Spin` used for loading a **page**. ```jsx -import { Spin } from 'antd'; +import { Spin, Space } from 'antd'; ReactDOM.render( -
+ -
, + , mountNode, ); ``` - - diff --git a/components/style/themes/compact.less b/components/style/themes/compact.less index 784bb79c91..e912d35cb3 100644 --- a/components/style/themes/compact.less +++ b/components/style/themes/compact.less @@ -93,7 +93,8 @@ @switch-sm-height: 14px; @switch-min-width: 40px; @switch-sm-min-width: 24px; -@switch-inner-margin: 0 22px 0 4px; +@switch-inner-margin-min: 4px; +@switch-inner-margin-max: 22px; // Slider // --- diff --git a/components/style/themes/default.less b/components/style/themes/default.less index 4bb360a067..7e24d44b6c 100644 --- a/components/style/themes/default.less +++ b/components/style/themes/default.less @@ -729,7 +729,8 @@ @switch-color: @primary-color; @switch-bg: @component-background; @switch-shadow-color: fade(#00230b, 20%); -@switch-inner-margin: 0 24px 0 6px; +@switch-inner-margin-min: 6px; +@switch-inner-margin-max: 24px; // Pagination // --- diff --git a/components/switch/style/index.less b/components/switch/style/index.less index 0e51e5d3ad..b1e579ef02 100644 --- a/components/switch/style/index.less +++ b/components/switch/style/index.less @@ -23,7 +23,8 @@ &-inner { display: block; - margin: @switch-inner-margin; + margin-right: @switch-inner-margin-min; + margin-left: @switch-inner-margin-max; color: @text-color-inverse; font-size: @font-size-sm; } @@ -135,7 +136,8 @@ background-color: @switch-color; .@{switch-prefix-cls}-inner { - margin: @switch-inner-margin; + margin-right: @switch-inner-margin-max; + margin-left: @switch-inner-margin-min; } &::after { diff --git a/components/switch/style/rtl.less b/components/switch/style/rtl.less index 867d31966d..d659e1ffce 100644 --- a/components/switch/style/rtl.less +++ b/components/switch/style/rtl.less @@ -10,8 +10,8 @@ &-inner { .@{switch-prefix-cls}-rtl & { - margin-right: 24px; - margin-left: 6px; + margin-right: @switch-inner-margin-max; + margin-left: @switch-inner-margin-min; } } @@ -51,8 +51,8 @@ &-checked { .@{switch-prefix-cls}-inner { .@{switch-prefix-cls}-rtl& { - margin-right: 6px; - margin-left: 24px; + margin-right: @switch-inner-margin-min; + margin-left: @switch-inner-margin-max; } } diff --git a/components/table/Table.tsx b/components/table/Table.tsx index fa99eb89fc..e1d080df5d 100644 --- a/components/table/Table.tsx +++ b/components/table/Table.tsx @@ -403,18 +403,19 @@ function Table(props: TableProps) { paginationSize = mergedSize === 'small' || mergedSize === 'middle' ? 'small' : undefined; } - const renderPagination = (position: string = 'right') => ( + const renderPagination = (position: string) => ( ); + const defaultPosition = direction === 'rtl' ? 'left' : 'right'; if (mergedPagination.position !== null && Array.isArray(mergedPagination.position)) { const topPos = mergedPagination.position.find(p => p.indexOf('top') !== -1); const bottomPos = mergedPagination.position.find(p => p.indexOf('bottom') !== -1); if (!topPos && !bottomPos) { - bottomPaginationNode = renderPagination(); + bottomPaginationNode = renderPagination(defaultPosition); } else { if (topPos) { topPaginationNode = renderPagination(topPos!.toLowerCase().replace('top', '')); @@ -424,7 +425,7 @@ function Table(props: TableProps) { } } } else { - bottomPaginationNode = renderPagination(); + bottomPaginationNode = renderPagination(defaultPosition); } } diff --git a/components/table/__tests__/__snapshots__/Table.rowSelection.test.js.snap b/components/table/__tests__/__snapshots__/Table.rowSelection.test.js.snap index fa0ee96100..0e55cec07b 100644 --- a/components/table/__tests__/__snapshots__/Table.rowSelection.test.js.snap +++ b/components/table/__tests__/__snapshots__/Table.rowSelection.test.js.snap @@ -27,6 +27,9 @@ exports[`Table.rowSelection fix expand on th left when selection column fixed on + - + + + @@ -634,7 +641,11 @@ exports[`Table.rowSelection fix selection column on the left when any other colu - + + + @@ -945,7 +956,11 @@ exports[`Table.rowSelection should support getPopupContainer 1`] = `
- + + + @@ -1268,7 +1283,11 @@ exports[`Table.rowSelection should support getPopupContainer from ConfigProvider
- + + + @@ -1591,7 +1610,11 @@ exports[`Table.rowSelection use column as selection column when key is \`selecti
- + + + diff --git a/components/table/__tests__/__snapshots__/demo.test.js.snap b/components/table/__tests__/__snapshots__/demo.test.js.snap index 9f3fc3fb1f..60170684ee 100644 --- a/components/table/__tests__/__snapshots__/demo.test.js.snap +++ b/components/table/__tests__/__snapshots__/demo.test.js.snap @@ -2378,6 +2378,9 @@ exports[`renders ./components/table/demo/dynamic-settings.md correctly 1`] = ` + - + +Array [
- - +
+
- - Clear filters - - - +
+
- - Clear filters and sorters - - -
+ + + ,
@@ -11317,8 +11337,8 @@ exports[`renders ./components/table/demo/reset-filter.md correctly 1`] = `
- - + , +] `; exports[`renders ./components/table/demo/resizable-column.md correctly 1`] = ` @@ -11852,7 +11872,11 @@ exports[`renders ./components/table/demo/row-selection.md correctly 1`] = `
- + + + @@ -12195,7 +12219,11 @@ exports[`renders ./components/table/demo/row-selection-and-operation.md correctl
- + + + @@ -12778,7 +12806,11 @@ exports[`renders ./components/table/demo/row-selection-custom.md correctly 1`] =
- + + + @@ -13388,7 +13420,11 @@ exports[`renders ./components/table/demo/row-selection-custom-debug.md correctly
- + + + diff --git a/components/table/demo/custom-filter-panel.md b/components/table/demo/custom-filter-panel.md index c0083adc37..970e6a12fd 100644 --- a/components/table/demo/custom-filter-panel.md +++ b/components/table/demo/custom-filter-panel.md @@ -14,7 +14,7 @@ title: Implement a customized column search example via `filterDropdown`. ```jsx -import { Table, Input, Button } from 'antd'; +import { Table, Input, Button, Space } from 'antd'; import Highlighter from 'react-highlight-words'; import { SearchOutlined } from '@ant-design/icons'; @@ -64,26 +64,25 @@ class App extends React.Component { onPressEnter={() => this.handleSearch(selectedKeys, confirm, dataIndex)} style={{ width: 188, marginBottom: 8, display: 'block' }} /> - - + + + + ), filterIcon: filtered => , onFilter: (value, record) => - record[dataIndex] - .toString() - .toLowerCase() - .includes(value.toLowerCase()), + record[dataIndex].toString().toLowerCase().includes(value.toLowerCase()), onFilterDropdownVisibleChange: visible => { if (visible) { setTimeout(() => this.searchInput.select()); diff --git a/components/table/demo/reset-filter.md b/components/table/demo/reset-filter.md index d088db9e2a..c0293a4014 100644 --- a/components/table/demo/reset-filter.md +++ b/components/table/demo/reset-filter.md @@ -22,7 +22,7 @@ Control filters and sorters by `filteredValue` and `sortOrder`. > 3. `column.key` is required. ```jsx -import { Table, Button } from 'antd'; +import { Table, Button, Space } from 'antd'; const data = [ { @@ -128,27 +128,17 @@ class App extends React.Component { }, ]; return ( -
-
+ <> + -
+
- + ); } } ReactDOM.render(, mountNode); ``` - -```css -.table-operations { - margin-bottom: 16px; -} - -.table-operations > button { - margin-right: 8px; -} -``` diff --git a/components/table/hooks/useSelection.tsx b/components/table/hooks/useSelection.tsx index 945f200d55..890ab8b087 100644 --- a/components/table/hooks/useSelection.tsx +++ b/components/table/hooks/useSelection.tsx @@ -1,5 +1,6 @@ import * as React from 'react'; import DownOutlined from '@ant-design/icons/DownOutlined'; +import { INTERNAL_COL_DEFINE } from 'rc-table'; import { FixedType } from 'rc-table/lib/interface'; import Checkbox, { CheckboxProps } from '../../checkbox'; import Dropdown from '../../dropdown'; @@ -440,6 +441,9 @@ export default function useSelection( className: `${prefixCls}-selection-column`, title: rowSelection.columnTitle || title, render: renderSelectionCell, + [INTERNAL_COL_DEFINE]: { + className: `${prefixCls}-selection-column`, + }, }; if (expandType === 'row' && columns.length && !expandIconColumnIndex) { diff --git a/components/table/style/index.less b/components/table/style/index.less index 1cfb551a41..7215bb70d4 100644 --- a/components/table/style/index.less +++ b/components/table/style/index.less @@ -360,6 +360,7 @@ } } + &-selection-column, table tr th&-selection-column, table tr td&-selection-column { width: @table-selection-column-width; diff --git a/components/tabs/__tests__/__snapshots__/demo.test.js.snap b/components/tabs/__tests__/__snapshots__/demo.test.js.snap index 75baee7387..35079c3f76 100644 --- a/components/tabs/__tests__/__snapshots__/demo.test.js.snap +++ b/components/tabs/__tests__/__snapshots__/demo.test.js.snap @@ -2526,65 +2526,75 @@ exports[`renders ./components/tabs/demo/nest.md correctly 1`] = ` exports[`renders ./components/tabs/demo/position.md correctly 1`] = `
- Tab position:
+ Tab position: +
+
- - - + + + + + top + +
-
-
+ Tab position: -
+ Content of Tab 1 diff --git a/components/tag/__tests__/__snapshots__/demo.test.js.snap b/components/tag/__tests__/__snapshots__/demo.test.js.snap index 677735d68c..63fd13dd4a 100644 --- a/components/tag/__tests__/__snapshots__/demo.test.js.snap +++ b/components/tag/__tests__/__snapshots__/demo.test.js.snap @@ -204,19 +204,29 @@ exports[`renders ./components/tag/demo/basic.md correctly 1`] = ` exports[`renders ./components/tag/demo/checkable.md correctly 1`] = ` Array [ - Tag1 + Categories: , - Tag2 + Movies + , + + Books , - Tag3 + Music + , + + Sports , ] `; @@ -456,36 +466,6 @@ exports[`renders ./components/tag/demo/controlled.md correctly 1`] = `
`; -exports[`renders ./components/tag/demo/hot-tags.md correctly 1`] = ` -Array [ - - Categories: - , - - Movies - , - - Books - , - - Music - , - - Sports - , -] -`; - exports[`renders ./components/tag/demo/icon.md correctly 1`] = `
- Tag1 - Tag2 - Tag3 - , - mountNode, -); +const tagsData = ['Movies', 'Books', 'Music', 'Sports']; + +class HotTags extends React.Component { + state = { + selectedTags: ['Books'], + }; + + handleChange(tag, checked) { + const { selectedTags } = this.state; + const nextSelectedTags = checked ? [...selectedTags, tag] : selectedTags.filter(t => t !== tag); + console.log('You are interested in: ', nextSelectedTags); + this.setState({ selectedTags: nextSelectedTags }); + } + + render() { + const { selectedTags } = this.state; + return ( + <> + Categories: + {tagsData.map(tag => ( + -1} + onChange={checked => this.handleChange(tag, checked)} + > + {tag} + + ))} + + ); + } +} + +ReactDOM.render(, mountNode); ``` diff --git a/components/tag/demo/colorful.md b/components/tag/demo/colorful.md index 30bb4695e1..6e6489a078 100644 --- a/components/tag/demo/colorful.md +++ b/components/tag/demo/colorful.md @@ -44,12 +44,6 @@ ReactDOM.render( ); ``` -```css -.ant-tag { - margin-bottom: 8px; -} -``` - diff --git a/components/tooltip/demo/auto-adjust-overflow.md b/components/tooltip/demo/auto-adjust-overflow.md index 702c441c47..6bd26e9999 100644 --- a/components/tooltip/demo/auto-adjust-overflow.md +++ b/components/tooltip/demo/auto-adjust-overflow.md @@ -46,10 +46,3 @@ ReactDOM.render( mountNode, ); ``` - - diff --git a/components/tooltip/demo/placement.md b/components/tooltip/demo/placement.md index fb3f9b1324..386b081814 100644 --- a/components/tooltip/demo/placement.md +++ b/components/tooltip/demo/placement.md @@ -79,6 +79,11 @@ ReactDOM.render( margin-right: 8px; margin-bottom: 8px; } +.code-box-demo .ant-btn-rtl { + margin-right: 0; + margin-left: 8px; + margin-bottom: 8px; +} #components-tooltip-demo-placement .ant-btn { width: 70px; text-align: center; diff --git a/components/tooltip/index.tsx b/components/tooltip/index.tsx index 91817d889a..dd27399c5c 100644 --- a/components/tooltip/index.tsx +++ b/components/tooltip/index.tsx @@ -186,6 +186,9 @@ class Tooltip extends React.Component { placements[key].points[0] === align.points[0] && placements[key].points[1] === align.points[1], )[0]; + if (!placement) { + return; + } // 根据当前坐标设置动画点 const rect = domNode.getBoundingClientRect(); const transformOrigin = { diff --git a/components/transfer/__tests__/__snapshots__/demo.test.js.snap b/components/transfer/__tests__/__snapshots__/demo.test.js.snap index 09f42d6ecd..324b3947d9 100644 --- a/components/transfer/__tests__/__snapshots__/demo.test.js.snap +++ b/components/transfer/__tests__/__snapshots__/demo.test.js.snap @@ -2243,7 +2243,11 @@ exports[`renders ./components/transfer/demo/table-transfer.md correctly 1`] = `
- + + + @@ -2920,7 +2924,11 @@ exports[`renders ./components/transfer/demo/table-transfer.md correctly 1`] = `
- + + + diff --git a/components/upload/index.en-US.md b/components/upload/index.en-US.md index 38e9330518..4c0cab93d5 100644 --- a/components/upload/index.en-US.md +++ b/components/upload/index.en-US.md @@ -33,7 +33,7 @@ Uploading is the process of publishing information (web pages, text, pictures, v | multiple | Whether to support selected multiple file. `IE10+` supported. You can select multiple files with CTRL holding down while multiple is set to be true | boolean | false | | | name | The name of uploading file | string | 'file' | | | previewFile | Customize preview file logic | (file: File \| Blob) => Promise | - | | -| isImageUrl | Customize if render in thumbnail | (file: UploadFile) => boolean | [inside implementation](https://github.com/ant-design/ant-design/blob/4ad5830eecfb87471cd8ac588c5d992862b70770/components/upload/utils.tsx#L47-L68) | | +| isImageUrl | Customize if render `` in thumbnail | (file: UploadFile) => boolean | [inside implementation](https://github.com/ant-design/ant-design/blob/4ad5830eecfb87471cd8ac588c5d992862b70770/components/upload/utils.tsx#L47-L68) | | | showUploadList | Whether to show default upload list, could be an object to specify `showPreviewIcon`, `showRemoveIcon`, `showDownloadIcon`, `removeIcon` and `downloadIcon` individually | Boolean or { showPreviewIcon?: boolean, showDownloadIcon?: boolean, showRemoveIcon?: boolean, removeIcon?: React.ReactNode, downloadIcon?: React.ReactNode } | true | | | supportServerRender | Need to be turned on while the server side is rendering | boolean | false | | | withCredentials | ajax upload with cookie sent | boolean | false | | diff --git a/components/upload/index.zh-CN.md b/components/upload/index.zh-CN.md index c712bed724..56d5b5458d 100644 --- a/components/upload/index.zh-CN.md +++ b/components/upload/index.zh-CN.md @@ -34,7 +34,7 @@ title: Upload | multiple | 是否支持多选文件,`ie10+` 支持。开启后按住 ctrl 可选择多个文件 | boolean | false | | | name | 发到后台的文件参数名 | string | 'file' | | | previewFile | 自定义文件预览逻辑 | (file: File \| Blob) => Promise | 无 | | -| isImageUrl | 自定义缩略图是否使用 img 标签进行显示 | (file: UploadFile) => boolean | [内部实现](https://github.com/ant-design/ant-design/blob/4ad5830eecfb87471cd8ac588c5d992862b70770/components/upload/utils.tsx#L47-L68) | | +| isImageUrl | 自定义缩略图是否使用 `` 标签进行显示 | (file: UploadFile) => boolean | [内部实现](https://github.com/ant-design/ant-design/blob/4ad5830eecfb87471cd8ac588c5d992862b70770/components/upload/utils.tsx#L47-L68) | | | showUploadList | 是否展示文件列表, 可设为一个对象,用于单独设定 `showPreviewIcon`, `showRemoveIcon`, `showDownloadIcon`, `removeIcon` 和 `downloadIcon` | Boolean or { showPreviewIcon?: boolean, showRemoveIcon?: boolean, showDownloadIcon?: boolean, removeIcon?: React.ReactNode, downloadIcon?: React.ReactNode } | true | | | supportServerRender | 服务端渲染时需要打开这个 | boolean | false | | | withCredentials | 上传请求时是否携带 cookie | boolean | false | | diff --git a/docs/spec/introduce.en-US.md b/docs/spec/introduce.en-US.md index 219ab56edd..95921c222a 100644 --- a/docs/spec/introduce.en-US.md +++ b/docs/spec/introduce.en-US.md @@ -47,45 +47,34 @@ const LinksList = () => (
  • - NG-ZORRO - Ant Design of Angular + NG-ZORRO - Ant Design of Angular
  • - NG-ZORRO-MOBILE - Ant Design Mobile of Angular + NG-ZORRO-MOBILE - Ant Design Mobile of Angular
  • - Ant Design of Vue + Ant Design of Vue
  • - San UI Toolkit for Ant Design - -
  • -
  • - - vue-beauty (vue) + San UI Toolkit for Ant Design
  • - antizer (ClojureScript) + antizer (ClojureScript)
  • - - antd-ember + + ant-design-blazor/ant-design-blazor -
  • -
  • - - antue (vue) - -
  • -
  • + - Ant Design of Blazor + append-it/ant-design-blazor
  • diff --git a/docs/spec/introduce.zh-CN.md b/docs/spec/introduce.zh-CN.md index 9c575810af..8ce7d58d72 100644 --- a/docs/spec/introduce.zh-CN.md +++ b/docs/spec/introduce.zh-CN.md @@ -47,45 +47,36 @@ const LinksList = () => (
  • - NG-ZORRO - Ant Design of Angular + NG-ZORRO - Ant Design of Angular
  • - NG-ZORRO-MOBILE - Ant Design Mobile of Angular + NG-ZORRO-MOBILE - Ant Design Mobile of Angular
  • - Ant Design of Vue + Ant Design of Vue
  • - San UI Toolkit for Ant Design - -
  • -
  • - - vue-beauty (vue) + San UI Toolkit for Ant Design
  • - antizer (ClojureScript) + antizer (ClojureScript)
  • - - antd-ember + + ant-design-blazor/ant-design-blazor -
  • -
  • - - antue (vue) + + + append-it/ant-design-blazor
  • -
  • - Ant Design of Blazor -
  • ); diff --git a/package.json b/package.json index fc5f4da371..bd2807075a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "antd", - "version": "4.1.5", + "version": "4.2.0", "description": "An enterprise-class UI design language and React components implementation", "keywords": [ "ant", @@ -71,7 +71,7 @@ "prettier": "prettier -c --write '**/*'", "pretty-quick": "pretty-quick", "pub": "antd-tools run pub", - "prepublish": "antd-tools run guard", + "prepublish": "npm run snyk-protect && antd-tools run guard", "site": "cross-env NODE_ICU_DATA=node_modules/full-icu concurrently \"bisheng build --ssr -c ./site/bisheng.config.js\" \"node ./scripts/generateColorLess.js\"", "sort": "npx sort-package-json", "sort-api": "antd-tools run sort-api-table", @@ -82,7 +82,8 @@ "test-all": "./scripts/test-all.sh", "test-node": "jest --config .jest.node.js --no-cache", "tsc": "tsc", - "site:test": "jest --config .jest.site.js --cache=false" + "site:test": "jest --config .jest.site.js --cache=false", + "snyk-protect": "snyk protect" }, "husky": { "hooks": { @@ -103,7 +104,7 @@ "copy-to-clipboard": "^3.2.0", "css-animation": "^1.5.0", "lodash": "^4.17.13", - "moment": "^2.24.0", + "moment": "~2.24.0", "omit.js": "^1.0.2", "prop-types": "^15.7.2", "raf": "^3.4.1", @@ -128,7 +129,7 @@ "rc-slider": "~9.2.3", "rc-steps": "~3.5.0", "rc-switch": "~1.9.0", - "rc-table": "~7.5.2", + "rc-table": "~7.5.3", "rc-tabs": "~10.1.1", "rc-tooltip": "~4.0.2", "rc-tree": "~3.1.0", @@ -139,7 +140,8 @@ "rc-virtual-list": "^1.1.0", "resize-observer-polyfill": "^1.5.1", "scroll-into-view-if-needed": "^2.2.20", - "warning": "~4.0.3" + "warning": "~4.0.3", + "snyk": "^1.316.1" }, "devDependencies": { "@ant-design/bisheng-plugin": "^2.3.0", @@ -290,5 +292,6 @@ "tnpm": { "mode": "npm" }, - "title": "Ant Design" + "title": "Ant Design", + "snyk": true } diff --git a/site/theme/static/footer.less b/site/theme/static/footer.less index 93b6c8acc8..f1b8b8d776 100644 --- a/site/theme/static/footer.less +++ b/site/theme/static/footer.less @@ -2,7 +2,7 @@ @import './colors'; .rc-footer { - z-index: 9; + z-index: 8; &-container { max-width: unset; diff --git a/site/theme/template/Home/DesignPage/index.tsx b/site/theme/template/Home/DesignPage/index.tsx index 3eb3484834..bf0991076b 100644 --- a/site/theme/template/Home/DesignPage/index.tsx +++ b/site/theme/template/Home/DesignPage/index.tsx @@ -258,16 +258,6 @@ export default function DesignPage() { {IconComponent} -
  • - - Ant Design of Blazor - {IconComponent} - -
  • diff --git a/site/theme/template/Layout/Footer.tsx b/site/theme/template/Layout/Footer.tsx index 0abb05512e..69d8b9b6c1 100644 --- a/site/theme/template/Layout/Footer.tsx +++ b/site/theme/template/Layout/Footer.tsx @@ -79,11 +79,6 @@ class Footer extends React.Component { url: 'https://vue.ant.design', openExternal: true, }, - { - title: 'Ant Design Blazor', - url: 'https://append-it.github.io/ant-design-blazor/', - openExternal: true, - }, { title: 'Ant Design Landing', description: , diff --git a/site/theme/template/Layout/Header/More.tsx b/site/theme/template/Layout/Header/More.tsx index ab4f765c62..d9f4f0e02d 100644 --- a/site/theme/template/Layout/Header/More.tsx +++ b/site/theme/template/Layout/Header/More.tsx @@ -37,16 +37,6 @@ export function getEcosystemGroup({ isZhCN }: SharedProps): React.ReactElement { Ant Design of Vue - - - Ant Design of Blazor - - {isZhCN ? (