diff --git a/.husky/.gitignore b/.husky/.gitignore new file mode 100644 index 0000000000..31354ec138 --- /dev/null +++ b/.husky/.gitignore @@ -0,0 +1 @@ +_ diff --git a/.husky/pre-commit b/.husky/pre-commit new file mode 100755 index 0000000000..f91359dc2a --- /dev/null +++ b/.husky/pre-commit @@ -0,0 +1,4 @@ +#!/bin/sh +. "$(dirname "$0")/_/husky.sh" + +npx --no-install pretty-quick --staged diff --git a/CHANGELOG.en-US.md b/CHANGELOG.en-US.md index c9c1af2a95..7d97a07e0b 100644 --- a/CHANGELOG.en-US.md +++ b/CHANGELOG.en-US.md @@ -15,6 +15,20 @@ timeline: true --- +## 4.15.2 + +`2021-04-19` + +- 🐞 Fix Tabs `centered` doesn't work. [#30106](https://github.com/ant-design/ant-design/pull/30106) [@kerm1it](https://github.com/kerm1it) +- 💄 Fix Button `type="link"` disabled cursor style missing. [#30197](https://github.com/ant-design/ant-design/pull/30197) +- 🐞 Fix TextArea `onChange` event return `target` object is not a HTMLTextArea object. [#30124](https://github.com/ant-design/ant-design/pull/30124) +- 🐞 Files in Upload `fileList` will always have uid. [#30087](https://github.com/ant-design/ant-design/pull/30087) [@jameslahm](https://github.com/jameslahm) +- 💄 Fix Badge toggle style when `status` is `warning`. [#30090](https://github.com/ant-design/ant-design/pull/30090) [@jameslahm](https://github.com/jameslahm) +- 🌐 Add Image component `preview` text in zh_HK and zh_TW. [#30100](https://github.com/ant-design/ant-design/pull/30100) [@jameslahm](https://github.com/jameslahm) +- 🌐 add da_DK locale TimePicker.RangePicker. [#30128](https://github.com/ant-design/ant-design/pull/30128) [@themitvp](https://github.com/themitvp) +- TypeScript + - 🤖 Modify LIST_IGNORE field is of string type. [#30188](https://github.com/ant-design/ant-design/pull/30188) [@hello-chinese](https://github.com/hello-chinese) + ## 4.15.1 `2021-04-10` diff --git a/CHANGELOG.zh-CN.md b/CHANGELOG.zh-CN.md index 633e6bb74e..ce07366394 100644 --- a/CHANGELOG.zh-CN.md +++ b/CHANGELOG.zh-CN.md @@ -15,6 +15,20 @@ timeline: true --- +## 4.15.2 + +`2021-04-19` + +- 🐞 修复 Tabs `centered` 失效问题。[#30106](https://github.com/ant-design/ant-design/pull/30106) [@kerm1it](https://github.com/kerm1it) +- 💄 修复 Badge `status="warning"` 时切换样式异常的问题。[#30090](https://github.com/ant-design/ant-design/pull/30090) [@jameslahm](https://github.com/jameslahm) +- 💄 修复 Button 为 `type="link"` 时禁用手型丢失的问题。[#30197](https://github.com/ant-design/ant-design/pull/30197) +- 🐞 修复 TextArea `onChange` 事件返回 `target` 对象不是 HTMLTextArea 对象的问题。[#30124](https://github.com/ant-design/ant-design/pull/30124) +- 🐞 Upload `fileList` 中的文件将总是有 uid。[#30087](https://github.com/ant-design/ant-design/pull/30087) [@jameslahm](https://github.com/jameslahm) +- 🌐 补充遗漏的 TimePicker 丹麦语国际化。[#30128](https://github.com/ant-design/ant-design/pull/30128) [@themitvp](https://github.com/themitvp) +- 🌐 增加 Image 组件 `preview` 繁體文案。[#30100](https://github.com/ant-design/ant-design/pull/30100) [@jameslahm](https://github.com/jameslahm) +- TypeScript + - 🤖 修改 LIST_IGNORE 字段为 string 类型。[#30188](https://github.com/ant-design/ant-design/pull/30188) [@hello-chinese](https://github.com/hello-chinese) + ## 4.15.1 `2021-04-10` diff --git a/components/badge/index.tsx b/components/badge/index.tsx index 5f058c46b1..578f11d1a8 100644 --- a/components/badge/index.tsx +++ b/components/badge/index.tsx @@ -191,7 +191,7 @@ const Badge: CompoundedComponent = ({ [`${prefixCls}-count`]: !isDot && !isHidden, [`${prefixCls}-count-sm`]: !isDot && !isHidden && size === 'small', [`${prefixCls}-multiple-words`]: - !isDot && displayCount && displayCount?.toString().length > 1, + !isDot && displayCount && displayCount.toString().length > 1, [`${prefixCls}-status-${status}`]: !!status, [`${prefixCls}-status-${color}`]: isPresetColor(color), }); diff --git a/components/breadcrumb/BreadcrumbItem.tsx b/components/breadcrumb/BreadcrumbItem.tsx index 1744211a8f..fd29f24a85 100644 --- a/components/breadcrumb/BreadcrumbItem.tsx +++ b/components/breadcrumb/BreadcrumbItem.tsx @@ -62,7 +62,7 @@ const BreadcrumbItem: BreadcrumbItemInterface = ({ return ( {link} - {separator && separator !== '' && ( + {separator && ( {separator} )} diff --git a/components/button/__tests__/index.test.tsx b/components/button/__tests__/index.test.tsx index 944ce28fff..cb20998415 100644 --- a/components/button/__tests__/index.test.tsx +++ b/components/button/__tests__/index.test.tsx @@ -300,4 +300,15 @@ describe('Button', () => { }, }); }); + + it('should not redirect when button is disabled', () => { + const onClick = jest.fn(); + const wrapper = mount( + , + ); + wrapper.simulate('click'); + expect(onClick).not.toHaveBeenCalled(); + }); }); diff --git a/components/button/button.tsx b/components/button/button.tsx index fe86a35883..4a8a2b4566 100644 --- a/components/button/button.tsx +++ b/components/button/button.tsx @@ -193,8 +193,10 @@ const InternalButton: React.ForwardRefRenderFunction = (pr React.useEffect(fixTwoCNChar, [buttonRef]); const handleClick = (e: React.MouseEvent) => { - const { onClick } = props; - if (innerLoading) { + const { onClick, disabled } = props; + // https://github.com/ant-design/ant-design/issues/30207 + if (innerLoading || disabled) { + e.preventDefault(); return; } (onClick as React.MouseEventHandler)?.(e); diff --git a/components/dropdown/__tests__/image.test.ts b/components/dropdown/__tests__/image.test.ts index 19c1b959f8..ca5dea8239 100644 --- a/components/dropdown/__tests__/image.test.ts +++ b/components/dropdown/__tests__/image.test.ts @@ -1,5 +1,5 @@ import { imageDemoTest } from '../../../tests/shared/imageTest'; describe('Dropdown image', () => { - imageDemoTest('dropdown'); + imageDemoTest('dropdown', { skip: ['dropdown-button.md'] }); }); diff --git a/components/form/demo/advanced-search.md b/components/form/demo/advanced-search.md index a000c7b37f..e2ee2be8cb 100644 --- a/components/form/demo/advanced-search.md +++ b/components/form/demo/advanced-search.md @@ -11,7 +11,7 @@ title: 有部分定制的样式代码,由于输入标签长度不确定,需要根据具体情况自行调整。 -> 🛎️ 想要 3 分钟实现? 试试 ProFrom 的[查询表单](https://procomponents.ant.design/components/form#%E6%9F%A5%E8%AF%A2%E7%AD%9B%E9%80%89)! +> 🛎️ 想要 3 分钟实现? 试试 ProForm 的[查询表单](https://procomponents.ant.design/components/form#%E6%9F%A5%E8%AF%A2%E7%AD%9B%E9%80%89)! ## en-US diff --git a/components/form/demo/form-in-modal.md b/components/form/demo/form-in-modal.md index c4fa1b2dbd..bf63d2698c 100644 --- a/components/form/demo/form-in-modal.md +++ b/components/form/demo/form-in-modal.md @@ -9,7 +9,7 @@ title: 当用户访问一个展示了某个列表的页面,想新建一项但又不想跳转页面时,可以用 Modal 弹出一个表单,用户填写必要信息后创建新的项。 -> 🛎️ 想要 3 分钟实现?试试 ProFrom 的 [Modal 表单](https://procomponents.ant.design/components/form#modal-%E8%A1%A8%E5%8D%95)! +> 🛎️ 想要 3 分钟实现?试试 ProForm 的 [Modal 表单](https://procomponents.ant.design/components/form#modal-%E8%A1%A8%E5%8D%95)! ## en-US diff --git a/components/form/demo/normal-login.md b/components/form/demo/normal-login.md index fb51679f47..a8080c2936 100644 --- a/components/form/demo/normal-login.md +++ b/components/form/demo/normal-login.md @@ -9,7 +9,7 @@ title: 普通的登录框,可以容纳更多的元素。 -> 🛎️ 想要 3 分钟实现?试试 [ProFrom](https://procomponents.ant.design/components/form/#%E7%99%BB%E5%BD%95)! +> 🛎️ 想要 3 分钟实现?试试 [ProForm](https://procomponents.ant.design/components/form/#%E7%99%BB%E5%BD%95)! ## en-US diff --git a/components/form/index.en-US.md b/components/form/index.en-US.md index 2f87a802aa..707cf03508 100644 --- a/components/form/index.en-US.md +++ b/components/form/index.en-US.md @@ -79,7 +79,7 @@ Form field component for data bidirectional binding, validation, layout, and so | getValueProps | Additional props with sub component | (value: any) => any | - | 4.2.0 | | hasFeedback | Used with `validateStatus`, this option specifies the validation status icon. Recommended to be used only with `Input` | boolean | false | | | help | The prompt message. If not provided, the prompt message will be generated by the validation rule. | ReactNode | - | | -| hidden | Whether to hide Form.Item (still collect and validate value) | boolean | false | | +| hidden | Whether to hide Form.Item (still collect and validate value) | boolean | false | 4.4.0 | | htmlFor | Set sub label `htmlFor` | string | - | | | initialValue | Config sub default value. Form `initialValues` get higher priority when conflict | string | - | 4.2.0 | | label | Label text | ReactNode | - | | @@ -203,9 +203,9 @@ Some operator functions in render form of Form.List. New in 4.7.0. Show error messages, should only work with `rules` of Form.List. See [example](#components-form-demo-dynamic-form-item). -| Property | Description | Type | Default | -| --- | --- | --- | --- | -| errors | Error list | ReactNode\[] | - | +| Property | Description | Type | Default | +| -------- | ----------- | ------------ | ------- | +| errors | Error list | ReactNode\[] | - | ## Form.Provider @@ -286,13 +286,13 @@ validateFields() #### FieldData -| Name | Description | Type | -| --- | --- | --- | -| errors | Error messages | string\[] | -| name | Field name path | [NamePath](#NamePath)\[] | -| touched | Whether is operated | boolean | -| validating | Whether is in validating | boolean | -| value | Field value | any | +| Name | Description | Type | +| ---------- | ------------------------ | ------------------------ | +| errors | Error messages | string\[] | +| name | Field name path | [NamePath](#NamePath)\[] | +| touched | Whether is operated | boolean | +| validating | Whether is in validating | boolean | +| value | Field value | any | #### Rule @@ -435,3 +435,7 @@ React can not get correct interaction of controlled component with async value u See similar issues: [#28370](https://github.com/ant-design/ant-design/issues/28370) [#27994](https://github.com/ant-design/ant-design/issues/27994) `scrollToFirstError` and `scrollToField` deps on `id` attribute passed to form control, please mark sure that it hasn't been ignored in your custom form control. Check [codesandbox](https://codesandbox.io/s/antd-reproduction-template-forked-25nul?file=/index.js) for solution. + +### `setFieldsValue` do not trigger `onFieldsChange` or `onValuesChange`? + +It's by design. Only user interactive can trigger the change event. This design is aim to avoid call `setFieldsValue` in change event which may makes loop calling. diff --git a/components/form/index.zh-CN.md b/components/form/index.zh-CN.md index a647617ea9..84ac879165 100644 --- a/components/form/index.zh-CN.md +++ b/components/form/index.zh-CN.md @@ -34,7 +34,7 @@ cover: https://gw.alipayobjects.com/zos/alicdn/ORmcdeaoO/Form.svg | scrollToFirstError | 提交失败自动滚动到第一个错误字段 | boolean \| [Options](https://github.com/stipsan/scroll-into-view-if-needed/tree/ece40bd9143f48caf4b99503425ecb16b0ad8249#options) | false | | | size | 设置字段组件的尺寸(仅限 antd 组件) | `small` \| `middle` \| `large` | - | | | validateMessages | 验证提示模板,说明[见下](#validateMessages) | [ValidateMessages](https://github.com/react-component/field-form/blob/master/src/utils/messages.ts) | - | | -| validateTrigger | 统一设置字段校验规则 | string \| string\[] | `onChange` | 4.3.0 | +| validateTrigger | 统一设置字段触发验证的时机 | string \| string\[] | `onChange` | 4.3.0 | | wrapperCol | 需要为输入控件设置布局样式时,使用该属性,用法同 labelCol | [object](/components/grid/#Col) | - | | | onFieldsChange | 字段更新时触发回调事件 | function(changedFields, allFields) | - | | | onFinish | 提交表单且数据验证成功后回调事件 | function(values) | - | | @@ -80,7 +80,7 @@ const validateMessages = { | getValueProps | 为子元素添加额外的属性 | (value: any) => any | - | 4.2.0 | | hasFeedback | 配合 `validateStatus` 属性使用,展示校验状态图标,建议只配合 Input 组件使用 | boolean | false | | | help | 提示信息,如不设置,则会根据校验规则自动生成 | ReactNode | - | | -| hidden | 是否隐藏字段(依然会收集和校验字段) | boolean | false | | +| hidden | 是否隐藏字段(依然会收集和校验字段) | boolean | false | 4.4.0 | | htmlFor | 设置子元素 label `htmlFor` 属性 | string | - | | | initialValue | 设置子元素默认值,如果与 Form 的 `initialValues` 冲突则以 Form 为准 | string | - | 4.2.0 | | label | `label` 标签的文本 | ReactNode | - | | @@ -192,19 +192,19 @@ Form 通过增量更新方式,只更新被修改的字段相关组件以达到 Form.List 渲染表单相关操作函数。 -| 参数 | 说明 | 类型 | 默认值 | -| --- | --- | --- | --- | -| add | 新增表单项 | (defaultValue?: any, insertIndex?: number) => void | insertIndex: 4.6.0 | -| move | 移动表单项 | (from: number, to: number) => void | - | -| remove | 删除表单项 | (index: number \| number\[]) => void | number\[]: 4.5.0 | +| 参数 | 说明 | 类型 | 默认值 | +| ------ | ---------- | -------------------------------------------------- | ------------------ | +| add | 新增表单项 | (defaultValue?: any, insertIndex?: number) => void | insertIndex: 4.6.0 | +| move | 移动表单项 | (from: number, to: number) => void | - | +| remove | 删除表单项 | (index: number \| number\[]) => void | number\[]: 4.5.0 | ## Form.ErrorList 4.7.0 新增。错误展示组件,仅限配合 Form.List 的 rules 一同使用。参考[示例](#components-form-demo-dynamic-form-item)。 -| 参数 | 说明 | 类型 | 默认值 | -| --- | --- | --- | --- | -| errors | 错误列表 | ReactNode\[] | - | +| 参数 | 说明 | 类型 | 默认值 | +| ------ | -------- | ------------ | ------ | +| errors | 错误列表 | ReactNode\[] | - | ## Form.Provider @@ -285,13 +285,13 @@ validateFields() #### FieldData -| 名称 | 说明 | 类型 | -| --- | --- | --- | -| errors | 错误信息 | string\[] | -| name | 字段名称 | [NamePath](#NamePath)\[] | -| touched | 是否被用户操作过 | boolean | -| validating | 是否正在校验 | boolean | -| value | 字段对应值 | any | +| 名称 | 说明 | 类型 | +| ---------- | ---------------- | ------------------------ | +| errors | 错误信息 | string\[] | +| name | 字段名称 | [NamePath](#NamePath)\[] | +| touched | 是否被用户操作过 | boolean | +| validating | 是否正在校验 | boolean | +| value | 字段对应值 | any | #### Rule @@ -434,3 +434,7 @@ React 中异步更新会导致受控组件交互行为异常。当用户交互 类似问题:[#28370](https://github.com/ant-design/ant-design/issues/28370) [#27994](https://github.com/ant-design/ant-design/issues/27994) 滚动依赖于表单控件元素上绑定的 `id` 字段,如果自定义控件没有将 `id` 赋到正确的元素上,这个功能将失效。你可以参考这个 [codesandbox](https://codesandbox.io/s/antd-reproduction-template-forked-25nul?file=/index.js)。 + +### `setFieldsValue` 不会触发 `onFieldsChange` 和 `onValuesChange`? + +是的,change 事件仅当用户交互才会触发。该设计是为了防止在 change 事件中调用 `setFieldsValue` 导致的循环问题。 diff --git a/components/input/TextArea.tsx b/components/input/TextArea.tsx index ae11bfa08f..815ea26555 100644 --- a/components/input/TextArea.tsx +++ b/components/input/TextArea.tsx @@ -86,7 +86,7 @@ const TextArea = React.forwardRef( // Patch composition onChange when value changed if (triggerValue !== value) { handleSetValue(triggerValue); - resolveOnChange(innerRef.current as any, e, onChange, triggerValue); + resolveOnChange(e.currentTarget, e, onChange, triggerValue); } onCompositionEnd?.(e); @@ -99,7 +99,7 @@ const TextArea = React.forwardRef( } handleSetValue(triggerValue); - resolveOnChange(innerRef.current as any, e, onChange, triggerValue); + resolveOnChange(e.currentTarget, e, onChange, triggerValue); }; // ============================== Reset =============================== @@ -107,7 +107,7 @@ const TextArea = React.forwardRef( handleSetValue('', () => { innerRef.current?.focus(); }); - resolveOnChange(innerRef.current as any, e, onChange); + resolveOnChange(innerRef.current?.resizableTextArea?.textArea!, e, onChange); }; const prefixCls = getPrefixCls('input', customizePrefixCls); diff --git a/components/input/__tests__/textarea.test.js b/components/input/__tests__/textarea.test.js index c17dd1886f..1bb392f70c 100644 --- a/components/input/__tests__/textarea.test.js +++ b/components/input/__tests__/textarea.test.js @@ -379,4 +379,36 @@ describe('TextArea allowClear', () => { const wrapper = mount(); expect(wrapper.find('textarea').at(0).getDOMNode().value).toBe('Light'); }); + + it('onChange event should return HTMLInputElement', () => { + const onChange = jest.fn(); + const wrapper = mount(); + + function isNativeElement() { + expect(onChange).toHaveBeenCalledWith( + expect.objectContaining({ + target: wrapper.find('textarea').instance(), + }), + ); + + onChange.mockReset(); + } + + // Change + wrapper.find('textarea').simulate('change', { + target: { + value: 'bamboo', + }, + }); + isNativeElement(); + + // Composition End + wrapper.find('textarea').instance().value = 'light'; // enzyme not support change `currentTarget` + wrapper.find('textarea').simulate('compositionEnd'); + isNativeElement(); + + // Reset + wrapper.find('.ant-input-clear-icon').first().simulate('click'); + isNativeElement(); + }); }); diff --git a/components/pagination/index.en-US.md b/components/pagination/index.en-US.md index e0c0416453..6ac047ab63 100644 --- a/components/pagination/index.en-US.md +++ b/components/pagination/index.en-US.md @@ -38,5 +38,5 @@ A long list can be divided into several pages using `Pagination`, and only one p | simple | Whether to use simple mode | boolean | - | | | size | Specify the size of `Pagination`, can be set to `small` | `default` \| `small` | `default` | | | total | Total number of data items | number | 0 | | -| onChange | Called when the page number is changed, and it takes the resulting page number and pageSize as its arguments | function(page, pageSize) | - | | +| onChange | Called when the page number or `pageSize` is changed, and it takes the resulting page number and pageSize as its arguments | function(page, pageSize) | - | | | onShowSizeChange | Called when `pageSize` is changed | function(current, size) | - | | diff --git a/components/pagination/index.zh-CN.md b/components/pagination/index.zh-CN.md index fb9c05dee1..3be1589d17 100644 --- a/components/pagination/index.zh-CN.md +++ b/components/pagination/index.zh-CN.md @@ -39,5 +39,5 @@ cover: https://gw.alipayobjects.com/zos/alicdn/1vqv2bj68/Pagination.svg | simple | 当添加该属性时,显示为简单分页 | boolean | - | | | size | 当为 `small` 时,是小尺寸分页 | `default` \| `small` | `default` | | | total | 数据总数 | number | 0 | | -| onChange | 页码改变的回调,参数是改变后的页码及每页条数 | function(page, pageSize) | - | | +| onChange | 页码或 `pageSize` 改变的回调,参数是改变后的页码及每页条数 | function(page, pageSize) | - | | | onShowSizeChange | pageSize 变化的回调 | function(current, size) | - | | diff --git a/components/style/core/global.less b/components/style/core/global.less index 1b3e8d2849..d4f53103fb 100644 --- a/components/style/core/global.less +++ b/components/style/core/global.less @@ -239,7 +239,6 @@ a { &[disabled] { color: @disabled-color; cursor: not-allowed; - pointer-events: none; } } diff --git a/components/table/hooks/useSelection.tsx b/components/table/hooks/useSelection.tsx index 3503836a9d..ec822f9897 100644 --- a/components/table/hooks/useSelection.tsx +++ b/components/table/hooks/useSelection.tsx @@ -452,7 +452,7 @@ export default function useSelection( if (expandType === 'nest') { mergedIndeterminate = indeterminate; devWarning( - !(typeof checkboxProps?.indeterminate === 'boolean'), + typeof checkboxProps?.indeterminate !== 'boolean', 'Table', 'set `indeterminate` using `rowSelection.getCheckboxProps` is not allowed with tree structured dataSource.', ); diff --git a/components/table/index.en-US.md b/components/table/index.en-US.md index 2c8197830a..ccc2f0f4a7 100644 --- a/components/table/index.en-US.md +++ b/components/table/index.en-US.md @@ -289,7 +289,7 @@ Besides, the breaking change is changing `dataIndex` from nest string path like ## FAQ -### How to hide pagination when single page or not data? +### How to hide pagination when single page or no data? You can set `hideOnSinglePage` with `pagination` prop. diff --git a/components/tabs/style/index.less b/components/tabs/style/index.less index 178e6ea655..44645b3304 100644 --- a/components/tabs/style/index.less +++ b/components/tabs/style/index.less @@ -47,14 +47,12 @@ .@{tab-prefix-cls}-nav-list { position: relative; display: flex; - flex: 1 0 auto; // fix safari scroll problem transition: transform @animation-duration-slow; } // >>>>>>>> Operations .@{tab-prefix-cls}-nav-operations { display: flex; - flex: 1 0 auto; // fix safari scroll problem align-self: stretch; &-hidden { diff --git a/components/tabs/style/position.less b/components/tabs/style/position.less index e015e76969..9da9bb8ad1 100644 --- a/components/tabs/style/position.less +++ b/components/tabs/style/position.less @@ -146,6 +146,7 @@ .@{tab-prefix-cls}-nav-list, .@{tab-prefix-cls}-nav-operations { + flex: 1 0 auto; // fix safari scroll problem flex-direction: column; } } diff --git a/components/time-picker/locale/da_DK.tsx b/components/time-picker/locale/da_DK.tsx index 6fe3131473..0bf9bf9ca7 100644 --- a/components/time-picker/locale/da_DK.tsx +++ b/components/time-picker/locale/da_DK.tsx @@ -2,6 +2,7 @@ import { TimePickerLocale } from '../index'; const locale: TimePickerLocale = { placeholder: 'Vælg tid', + rangePlaceholder: ['Starttidspunkt', 'Sluttidspunkt'], }; export default locale; diff --git a/components/upload/Upload.tsx b/components/upload/Upload.tsx index 0deac73092..28d387f7d9 100644 --- a/components/upload/Upload.tsx +++ b/components/upload/Upload.tsx @@ -73,7 +73,7 @@ const InternalUpload: React.ForwardRefRenderFunction = (pr }, []); // Control mode will auto fill file uid if not provided - React.useEffect(() => { + React.useMemo(() => { const timestamp = Date.now(); (fileList || []).forEach((file, index) => { @@ -403,7 +403,7 @@ interface CompoundedComponent React.PropsWithChildren & React.RefAttributes > { Dragger: typeof Dragger; - LIST_IGNORE: {}; + LIST_IGNORE: string; } const Upload = React.forwardRef(InternalUpload) as CompoundedComponent; diff --git a/components/upload/__tests__/upload.test.js b/components/upload/__tests__/upload.test.js index 989b5f4c83..fe7c048777 100644 --- a/components/upload/__tests__/upload.test.js +++ b/components/upload/__tests__/upload.test.js @@ -1,12 +1,12 @@ /* eslint-disable react/no-string-refs, react/prefer-es6-class */ import React from 'react'; -import { mount } from 'enzyme'; +import { mount, render } from 'enzyme'; import { act } from 'react-dom/test-utils'; import produce from 'immer'; import { cloneDeep } from 'lodash'; import Upload from '..'; import Form from '../../form'; -import { getFileItem, removeFileItem } from '../utils'; +import { getFileItem, removeFileItem, isImageUrl } from '../utils'; import { setup, teardown } from './mock'; import { resetWarned } from '../../_util/devWarning'; import mountTest from '../../../tests/shared/mountTest'; @@ -285,6 +285,20 @@ describe('Upload', () => { jest.useRealTimers(); }); + it('should be able to get uid at first', () => { + const fileList = [ + { + name: 'foo.png', + status: 'done', + url: 'http://www.baidu.com/xxx.png', + }, + ]; + render(); + fileList.forEach(file => { + expect(file.uid).toBeDefined(); + }); + }); + describe('util', () => { it('should be able to get fileItem', () => { const file = { uid: '-1', name: 'item.jpg' }; @@ -353,6 +367,13 @@ describe('Upload', () => { const targetItem = removeFileItem(file, fileList); expect(targetItem).toBe(null); }); + + it('isImageUrl should work correctly when file.url is null', () => { + const file = { + url: null, + }; + expect(isImageUrl(file)).toBe(true); + }); }); it('should support linkProps as object', () => { diff --git a/components/upload/utils.tsx b/components/upload/utils.tsx index 178d558db6..1499436a1f 100644 --- a/components/upload/utils.tsx +++ b/components/upload/utils.tsx @@ -54,7 +54,7 @@ export const isImageUrl = (file: UploadFile): boolean => { if (file.type && !file.thumbUrl) { return isImageFileType(file.type); } - const url: string = (file.thumbUrl || file.url) as string; + const url: string = (file.thumbUrl || file.url || '') as string; const extension = extname(url); if ( /^data:image\//.test(url) || diff --git a/docs/react/customize-theme.en-US.md b/docs/react/customize-theme.en-US.md index 2a69eac1a4..ccff12a204 100644 --- a/docs/react/customize-theme.en-US.md +++ b/docs/react/customize-theme.en-US.md @@ -125,7 +125,7 @@ Where the src/myStylesReplacement.less file loads the same files as the index.le ### Use a postcss processor to scope all styles -See an example of usage with gulp and [postcss-prefixwrap](https://github.com/dbtedman/postcss-prefixwrap) : https://gist.github.com/sbusch/a90eafaf5a5b61c6d6172da6ff76ddaa +See an example of usage with [gulp and postcss-prefixwrap](https://gist.github.com/sbusch/a90eafaf5a5b61c6d6172da6ff76ddaa). ## Not working? diff --git a/docs/react/recommendation.en-US.md b/docs/react/recommendation.en-US.md index 3a0b8a5042..4fd7c95e5b 100644 --- a/docs/react/recommendation.en-US.md +++ b/docs/react/recommendation.en-US.md @@ -9,8 +9,9 @@ title: Third-Party Libraries | --- | --- | | Visualization and charts | [AntV Data Visualization](https://antv.vision/en) [🔥 AntV Charting Library](https://g2plot.antv.vision/en) [BizCharts](https://github.com/alibaba/BizCharts) [recharts](https://github.com/recharts/recharts/) [viser](https://viserjs.github.io/) | | React Hooks Library | [ahooks](https://github.com/alibaba/hooks) | +| React Form Library | [Formily](https://github.com/alibaba/formily) [react-hook-form](https://github.com/react-hook-form/react-hook-form) [formik](https://github.com/formium/formik) | | Router | [react-router](https://github.com/ReactTraining/react-router) | -| Layout |[react-grid-layout](https://github.com/react-grid-layout/react-grid-layout) [react-grid-system](https://github.com/sealninja/react-grid-system) [rc-dock](https://github.com/ticlo/rc-dock) | +| Layout | [react-grid-layout](https://github.com/react-grid-layout/react-grid-layout) [react-grid-system](https://github.com/sealninja/react-grid-system) [rc-dock](https://github.com/ticlo/rc-dock) | | Drag and drop | [react-beautiful-dnd](https://github.com/atlassian/react-beautiful-dnd/) [react-dnd](https://github.com/gaearon/react-dnd) [react-sortable-hoc](https://github.com/clauderic/react-sortable-hoc) | | Code Editor | [react-codemirror2](https://github.com/scniro/react-codemirror2) [react-monaco-editor](https://github.com/superRaytin/react-monaco-editor) | | Rich Text Editor | [react-quill](https://github.com/zenoamaro/react-quill) [braft-editor](https://github.com/margox/braft-editor) | diff --git a/docs/react/recommendation.zh-CN.md b/docs/react/recommendation.zh-CN.md index f0c74afb67..22d76f1796 100644 --- a/docs/react/recommendation.zh-CN.md +++ b/docs/react/recommendation.zh-CN.md @@ -9,8 +9,9 @@ title: 社区精选组件 | --- | --- | | 可视化图表 | [AntV 数据可视化解决方案](https://antv.vision/zh) [🔥 AntV 图表库](https://g2plot.antv.vision/zh) [BizCharts](https://github.com/alibaba/BizCharts) [recharts](https://github.com/recharts/recharts/) [viser](https://viserjs.github.io/) | | React Hooks 库 | [ahooks](https://github.com/alibaba/hooks) | +| React 表单库 | [Formily](https://github.com/alibaba/formily) [react-hook-form](https://github.com/react-hook-form/react-hook-form) [formik](https://github.com/formium/formik) | | 路由 | [react-router](https://github.com/ReactTraining/react-router) | -| 布局 |[react-grid-layout](https://github.com/react-grid-layout/react-grid-layout) [react-grid-system](https://github.com/sealninja/react-grid-system) [rc-dock](https://github.com/ticlo/rc-dock) | +| 布局 | [react-grid-layout](https://github.com/react-grid-layout/react-grid-layout) [react-grid-system](https://github.com/sealninja/react-grid-system) [rc-dock](https://github.com/ticlo/rc-dock) | | 拖拽 | [react-beautiful-dnd](https://github.com/atlassian/react-beautiful-dnd/) [react-dnd](https://github.com/gaearon/react-dnd) [react-sortable-hoc](https://github.com/clauderic/react-sortable-hoc) | | 代码编辑器 | [react-codemirror2](https://github.com/scniro/react-codemirror2) [react-monaco-editor](https://github.com/superRaytin/react-monaco-editor) | | 富文本编辑器 | [react-quill](https://github.com/zenoamaro/react-quill) [braft-editor](https://github.com/margox/braft-editor) | diff --git a/package.json b/package.json index a4b9ee6680..e77b720871 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "antd", - "version": "4.15.1", + "version": "4.15.2", "description": "An enterprise-class UI design language and React components implementation", "title": "Ant Design", "keywords": [ @@ -46,6 +46,7 @@ "unpkg": "dist/antd.min.js", "typings": "lib/index.d.ts", "scripts": { + "prepare": "husky install", "api-collection": "antd-tools run api-collection", "authors": "node ./scripts/generate-authors", "build": "npm run compile && NODE_OPTIONS='--max-old-space-size=4096' npm run dist", @@ -97,11 +98,6 @@ "install-react-16": "npm i --no-save react@16 react-dom@16 react-test-renderer@16 enzyme-adapter-react-16", "argos": "argos upload imageSnapshots" }, - "husky": { - "hooks": { - "pre-commit": "pretty-quick --staged" - } - }, "browserslist": [ "> 0.5%", "last 2 versions", @@ -211,13 +207,13 @@ "eslint-plugin-markdown": "^2.0.0", "eslint-plugin-react": "^7.20.6", "eslint-plugin-react-hooks": "^4.1.2", - "eslint-plugin-unicorn": "^29.0.0", + "eslint-plugin-unicorn": "^30.0.0", "fetch-jsonp": "^1.1.3", "fs-extra": "^9.0.0", "full-icu": "^1.3.0", "glob": "^7.1.4", "http-server": "^0.12.0", - "husky": "^4.0.3", + "husky": "^6.0.0", "identity-obj-proxy": "^3.0.0", "ignore-emit-webpack-plugin": "^2.0.6", "immer": "^9.0.1", @@ -226,7 +222,7 @@ "intersection-observer": "^0.12.0", "jest": "^26.0.0", "jest-image-snapshot": "^4.0.0", - "jest-puppeteer": "^4.4.0", + "jest-puppeteer": "^5.0.1", "jquery": "^3.4.1", "jsdom": "^16.0.0", "jsonml.js": "^0.1.0", diff --git a/site/theme/template/Content/ComponentDoc.jsx b/site/theme/template/Content/ComponentDoc.jsx index 4be80cf991..a5c0b5eaff 100644 --- a/site/theme/template/Content/ComponentDoc.jsx +++ b/site/theme/template/Content/ComponentDoc.jsx @@ -149,8 +149,8 @@ class ComponentDoc extends React.Component { return (
- {helmetTitle && {helmetTitle}} - {helmetTitle && } + {helmetTitle} + {contentChild && } diff --git a/site/theme/template/Content/MainContent.jsx b/site/theme/template/Content/MainContent.jsx index 9acbf1f3a9..5588c65aae 100644 --- a/site/theme/template/Content/MainContent.jsx +++ b/site/theme/template/Content/MainContent.jsx @@ -115,7 +115,7 @@ class MainContent extends Component { if (this.scroller) { this.scroller.destroy(); } - window.removeEventListener('load', this.handleInitialHashOnLoad); + window.removeEventListener('load', this.handleLoad); window.removeEventListener('hashchange', this.handleHashChange); clearTimeout(this.timeout); } diff --git a/site/theme/template/Layout/Header/SearchBox.tsx b/site/theme/template/Layout/Header/SearchBox.tsx index abe949ff24..05179577fd 100644 --- a/site/theme/template/Layout/Header/SearchBox.tsx +++ b/site/theme/template/Layout/Header/SearchBox.tsx @@ -22,11 +22,17 @@ export default ({ isZhCN, responsive, onTriggerFocus }: SearchBoxProps) => { } React.useEffect(() => { - document.addEventListener('keyup', event => { + function keyupHandler(event: KeyboardEvent) { if (event.keyCode === 83 && event.target === document.body) { inputRef.current.focus(); } - }); + } + + document.addEventListener('keyup', keyupHandler); + + return () => { + document.removeEventListener('keyup', keyupHandler); + }; }, []); return (