diff --git a/CHANGELOG.en-US.md b/CHANGELOG.en-US.md index b76a61caa..12cc1a41e 100644 --- a/CHANGELOG.en-US.md +++ b/CHANGELOG.en-US.md @@ -10,6 +10,30 @@ --- +## 4.2.5 + +- 🐞 Fix Empty component memory leak problem +- 🐞 Fix Image width & height property not working problem + +## 4.2.4 + +- 🐞 Fix Wave memory leak problem + +## 4.2.3 + +- 🌟 TourStep custom Button, support function children [#7628](https://github.com/vueComponent/ant-design-vue/pull/7628) +- 🐞 Fix the problem that the input value is hidden in Select and Cascader search multi-select mode [#7640](https://github.com/vueComponent/ant-design-vue/issues/7640) + +## 4.2.2 + +- 🐞 Fix TreeSelect placeholder slot invalid [#7545](https://github.com/vueComponent/ant-design-vue/issues/7545) +- 🐞 Fix Tree slot responsive invalid issue [40ad45](https://github.com/vueComponent/ant-design-vue/commit/40ad45bc05b2bf9d0a2445d9f6ff365468ba90b7) +- 🐞 Fix FloatButton target type error issue [#7576](https://github.com/vueComponent/ant-design-vue/issues/7576) +- 🐞 Fix FormItem className error issue [#7582](https://github.com/vueComponent/ant-design-vue/issues/7582) +- 🐞 Fix Input Cannot input problem under lazy [#7543](https://github.com/vueComponent/ant-design-vue/issues/7543) +- 🐞 Fix the problem that placeholder is not hidden when inputting Chinese in Select [#7611](https://github.com/vueComponent/ant-design-vue/issues/7611) +- 🐞 Fix the problem that the pop-up window flashes when clicking the preset option in DatePicker [#7550](https://github.com/vueComponent/ant-design-vue/issues/7550) + ## 4.2.1 - 🐞 fix Input clear action error [#7523](https://github.com/vueComponent/ant-design-vue/issues/7523) diff --git a/CHANGELOG.zh-CN.md b/CHANGELOG.zh-CN.md index 36c43e269..d1e06a065 100644 --- a/CHANGELOG.zh-CN.md +++ b/CHANGELOG.zh-CN.md @@ -10,6 +10,30 @@ --- +## 4.2.5 + +- 🐞 修复 Empty 组件内存泄漏问题 +- 🐞 修复 Image width & height 属性不生效问题 + +## 4.2.4 + +- 🐞 修复 Wave 内存泄漏问题 + +## 4.2.3 + +- 🌟 TourStep 自定义 Button,支持函数 children [#7628](https://github.com/vueComponent/ant-design-vue/pull/7628) +- 🐞 修复 Select 和 Cascader 搜索多选模式下,输入值被隐藏问题 [#7640](https://github.com/vueComponent/ant-design-vue/issues/7640) + +## 4.2.2 + +- 🐞 修复 TreeSelect placeholder 插槽无效 [#7545](https://github.com/vueComponent/ant-design-vue/issues/7545) +- 🐞 修复 Tree 插槽响应式无效问题 [40ad45](https://github.com/vueComponent/ant-design-vue/commit/40ad45bc05b2bf9d0a2445d9f6ff365468ba90b7) +- 🐞 修复 FloatButton target 类型错误问题 [#7576](https://github.com/vueComponent/ant-design-vue/issues/7576) +- 🐞 修复 FormItem className 错误问题 [#7582](https://github.com/vueComponent/ant-design-vue/issues/7582) +- 🐞 修复 Input lazy 下无法输入问题 [#7543](https://github.com/vueComponent/ant-design-vue/issues/7543) +- 🐞 修复 Select 输入中文时,placeholder 未隐藏问题 [#7611](https://github.com/vueComponent/ant-design-vue/issues/7611) +- 🐞 修复 DatePicker 点击预设选项时,弹窗闪动问题 [#7550](https://github.com/vueComponent/ant-design-vue/issues/7550) + ## 4.2.1 - 🐞 修复 Input 清空操作才报错问题 [#7523](https://github.com/vueComponent/ant-design-vue/issues/7523) diff --git a/README-zh_CN.md b/README-zh_CN.md index 4f098de14..c67223107 100644 --- a/README-zh_CN.md +++ b/README-zh_CN.md @@ -10,7 +10,7 @@
-An enterprise-class UI components based on Ant Design and Vue 3. +基于 Ant Design 和 Vue 3 的企业级 UI 组件库。 ![test](https://github.com/vueComponent/ant-design-vue/workflows/test/badge.svg) [![codecov](https://img.shields.io/codecov/c/github/vueComponent/ant-design-vue/master.svg?style=flat-square)](https://codecov.io/gh/vueComponent/ant-design-vue) [![npm package](https://img.shields.io/npm/v/ant-design-vue.svg?style=flat-square)](https://www.npmjs.org/package/ant-design-vue) [![NPM downloads](http://img.shields.io/npm/dm/ant-design-vue.svg?style=flat-square)](http://www.npmtrends.com/ant-design-vue) [![backers](https://opencollective.com/ant-design-vue/backers/badge.svg)](#backers) [![sponsors](https://opencollective.com/ant-design-vue/sponsors/badge.svg)](#sponsors) [![extension-for-VSCode](https://img.shields.io/badge/extension%20for-VSCode-blue.svg?style=flat-square)](https://marketplace.visualstudio.com/items?itemName=ant-design-vue.vscode-ant-design-vue-helper) [![issues-helper](https://img.shields.io/badge/Issues%20Manage%20By-issues--helper-orange?style=flat-square)](https://github.com/actions-cool/issues-helper) @@ -90,21 +90,21 @@ ant-design-vue 是 MIT 协议的开源项目。为了项目能够更好的持续 - [支付宝或微信](https://aliyuncdn.antdv.com/alipay-and-wechat.png) - ETH: 0x30cc48515d8ae9fefa20ab87226ad7e8ab9c3bc2 -## Sponsors +## 赞助商 -Become a sponsor and get your logo on our README on Github with a link to your site. [[Become a sponsor](https://opencollective.com/ant-design-vue#sponsor)] +成为赞助商,并在 Github 上的自述文件上获得您的徽标,并链接到您的网站。 [[成为赞助商](https://opencollective.com/ant-design-vue#sponsor)] -## Backers +## 支持者 -Support us with a monthly donation and help us continue our activities. [[Become a backer](https://opencollective.com/ant-design-vue#backer)] +每月捐款支持我们,帮助我们继续我们的活动。 [[成为支持者](https://opencollective.com/ant-design-vue#backer)] ## Patreon -Support us with a monthly donation and help us continue our activities. [[Become a backer](https://www.patreon.com/tangjinzhou)] +每月捐款支持我们,帮助我们继续我们的活动。 [[成为支持者](https://www.patreon.com/tangjinzhou)] diff --git a/components/_util/BaseInput.tsx b/components/_util/BaseInput.tsx index b01256715..e6bc9d708 100644 --- a/components/_util/BaseInput.tsx +++ b/components/_util/BaseInput.tsx @@ -1,6 +1,9 @@ import type { PropType } from 'vue'; -import { defineComponent, shallowRef, ref, watch } from 'vue'; +import { computed, defineComponent, shallowRef, ref, watch } from 'vue'; import PropTypes from './vue-types'; +import type { BaseInputInnerExpose } from './BaseInputInner'; +import BaseInputInner from './BaseInputInner'; +import { styleObjectToString } from '../vc-util/Dom/css'; export interface BaseInputExpose { focus: () => void; @@ -30,6 +33,8 @@ const BaseInput = defineComponent({ default: 'input', }, size: PropTypes.string, + style: PropTypes.oneOfType([String, Object]), + class: PropTypes.string, }, emits: [ 'change', @@ -40,9 +45,11 @@ const BaseInput = defineComponent({ 'compositionstart', 'compositionend', 'keyup', + 'paste', + 'mousedown', ], setup(props, { emit, attrs, expose }) { - const inputRef = shallowRef(null); + const inputRef = shallowRef(null); const renderValue = ref(); const isComposing = ref(false); watch( @@ -68,6 +75,7 @@ const BaseInput = defineComponent({ const event = document.createEvent('HTMLEvents'); event.initEvent('input', true, true); e.target.dispatchEvent(event); + handleChange(e); }; const handleInput = (e: Event) => { if (isComposing.value && props.lazy) { @@ -114,19 +122,31 @@ const BaseInput = defineComponent({ expose({ focus, blur, - input: inputRef, + input: computed(() => inputRef.value?.input), setSelectionRange, select, - getSelectionStart: () => inputRef.value?.selectionStart, - getSelectionEnd: () => inputRef.value?.selectionEnd, - getScrollTop: () => inputRef.value?.scrollTop, + getSelectionStart: () => inputRef.value?.getSelectionStart(), + getSelectionEnd: () => inputRef.value?.getSelectionEnd(), + getScrollTop: () => inputRef.value?.getScrollTop(), + }); + const handleMousedown = (e: MouseEvent) => { + emit('mousedown', e); + }; + const handlePaste = (e: ClipboardEvent) => { + emit('paste', e); + }; + const styleString = computed(() => { + return props.style && typeof props.style !== 'string' + ? styleObjectToString(props.style) + : props.style; }); return () => { - const { tag: Tag, ...restProps } = props; + const { style, lazy, ...restProps } = props; return ( - ); }; diff --git a/components/_util/BaseInputInner.tsx b/components/_util/BaseInputInner.tsx new file mode 100644 index 000000000..10423d7a4 --- /dev/null +++ b/components/_util/BaseInputInner.tsx @@ -0,0 +1,96 @@ +import type { PropType } from 'vue'; +import { defineComponent, shallowRef } from 'vue'; +import PropTypes from './vue-types'; + +export interface BaseInputInnerExpose { + focus: () => void; + blur: () => void; + input: HTMLInputElement | HTMLTextAreaElement | null; + setSelectionRange: ( + start: number, + end: number, + direction?: 'forward' | 'backward' | 'none', + ) => void; + select: () => void; + getSelectionStart: () => number | null; + getSelectionEnd: () => number | null; + getScrollTop: () => number | null; + setScrollTop: (scrollTop: number) => void; +} +const BaseInputInner = defineComponent({ + compatConfig: { MODE: 3 }, + // inheritAttrs: false, + props: { + disabled: PropTypes.looseBool, + type: PropTypes.string, + value: PropTypes.any, + tag: { + type: String as PropType<'input' | 'textarea'>, + default: 'input', + }, + size: PropTypes.string, + onChange: Function as PropType<(e: Event) => void>, + onInput: Function as PropType<(e: Event) => void>, + onBlur: Function as PropType<(e: Event) => void>, + onFocus: Function as PropType<(e: Event) => void>, + onKeydown: Function as PropType<(e: Event) => void>, + onCompositionstart: Function as PropType<(e: Event) => void>, + onCompositionend: Function as PropType<(e: Event) => void>, + onKeyup: Function as PropType<(e: Event) => void>, + onPaste: Function as PropType<(e: Event) => void>, + onMousedown: Function as PropType<(e: Event) => void>, + }, + emits: [ + 'change', + 'input', + 'blur', + 'keydown', + 'focus', + 'compositionstart', + 'compositionend', + 'keyup', + 'paste', + 'mousedown', + ], + setup(props, { expose }) { + const inputRef = shallowRef(null); + + const focus = () => { + if (inputRef.value) { + inputRef.value.focus(); + } + }; + const blur = () => { + if (inputRef.value) { + inputRef.value.blur(); + } + }; + const setSelectionRange = ( + start: number, + end: number, + direction?: 'forward' | 'backward' | 'none', + ) => { + inputRef.value?.setSelectionRange(start, end, direction); + }; + + const select = () => { + inputRef.value?.select(); + }; + expose({ + focus, + blur, + input: inputRef, + setSelectionRange, + select, + getSelectionStart: () => inputRef.value?.selectionStart, + getSelectionEnd: () => inputRef.value?.selectionEnd, + getScrollTop: () => inputRef.value?.scrollTop, + }); + return () => { + const { tag: Tag, value, ...restProps } = props; + return ; + }; + }, +}); + +export default BaseInputInner; diff --git a/components/_util/css-animation/Event.js b/components/_util/css-animation/Event.js deleted file mode 100644 index cd5e87155..000000000 --- a/components/_util/css-animation/Event.js +++ /dev/null @@ -1,130 +0,0 @@ -const START_EVENT_NAME_MAP = { - transitionstart: { - transition: 'transitionstart', - WebkitTransition: 'webkitTransitionStart', - MozTransition: 'mozTransitionStart', - OTransition: 'oTransitionStart', - msTransition: 'MSTransitionStart', - }, - - animationstart: { - animation: 'animationstart', - WebkitAnimation: 'webkitAnimationStart', - MozAnimation: 'mozAnimationStart', - OAnimation: 'oAnimationStart', - msAnimation: 'MSAnimationStart', - }, -}; - -const END_EVENT_NAME_MAP = { - transitionend: { - transition: 'transitionend', - WebkitTransition: 'webkitTransitionEnd', - MozTransition: 'mozTransitionEnd', - OTransition: 'oTransitionEnd', - msTransition: 'MSTransitionEnd', - }, - - animationend: { - animation: 'animationend', - WebkitAnimation: 'webkitAnimationEnd', - MozAnimation: 'mozAnimationEnd', - OAnimation: 'oAnimationEnd', - msAnimation: 'MSAnimationEnd', - }, -}; - -const startEvents = []; -const endEvents = []; - -function detectEvents() { - const testEl = document.createElement('div'); - const style = testEl.style; - - if (!('AnimationEvent' in window)) { - delete START_EVENT_NAME_MAP.animationstart.animation; - delete END_EVENT_NAME_MAP.animationend.animation; - } - - if (!('TransitionEvent' in window)) { - delete START_EVENT_NAME_MAP.transitionstart.transition; - delete END_EVENT_NAME_MAP.transitionend.transition; - } - - function process(EVENT_NAME_MAP, events) { - for (const baseEventName in EVENT_NAME_MAP) { - if (EVENT_NAME_MAP.hasOwnProperty(baseEventName)) { - const baseEvents = EVENT_NAME_MAP[baseEventName]; - for (const styleName in baseEvents) { - if (styleName in style) { - events.push(baseEvents[styleName]); - break; - } - } - } - } - } - - process(START_EVENT_NAME_MAP, startEvents); - process(END_EVENT_NAME_MAP, endEvents); -} - -if (typeof window !== 'undefined' && typeof document !== 'undefined') { - detectEvents(); -} - -function addEventListener(node, eventName, eventListener) { - node.addEventListener(eventName, eventListener, false); -} - -function removeEventListener(node, eventName, eventListener) { - node.removeEventListener(eventName, eventListener, false); -} - -const TransitionEvents = { - // Start events - startEvents, - - addStartEventListener(node, eventListener) { - if (startEvents.length === 0) { - setTimeout(eventListener, 0); - return; - } - startEvents.forEach(startEvent => { - addEventListener(node, startEvent, eventListener); - }); - }, - - removeStartEventListener(node, eventListener) { - if (startEvents.length === 0) { - return; - } - startEvents.forEach(startEvent => { - removeEventListener(node, startEvent, eventListener); - }); - }, - - // End events - endEvents, - - addEndEventListener(node, eventListener) { - if (endEvents.length === 0) { - setTimeout(eventListener, 0); - return; - } - endEvents.forEach(endEvent => { - addEventListener(node, endEvent, eventListener); - }); - }, - - removeEndEventListener(node, eventListener) { - if (endEvents.length === 0) { - return; - } - endEvents.forEach(endEvent => { - removeEventListener(node, endEvent, eventListener); - }); - }, -}; - -export default TransitionEvents; diff --git a/components/_util/css-animation/index.js b/components/_util/css-animation/index.js deleted file mode 100644 index 86e399dec..000000000 --- a/components/_util/css-animation/index.js +++ /dev/null @@ -1,186 +0,0 @@ -// https://github.com/yiminghe/css-animation 1.5.0 - -import Event from './Event'; -import classes from '../component-classes'; -import { requestAnimationTimeout, cancelAnimationTimeout } from '../requestAnimationTimeout'; -import { inBrowser } from '../env'; - -const isCssAnimationSupported = Event.endEvents.length !== 0; -const capitalPrefixes = [ - 'Webkit', - 'Moz', - 'O', - // ms is special .... ! - 'ms', -]; -const prefixes = ['-webkit-', '-moz-', '-o-', 'ms-', '']; - -function getStyleProperty(node, name) { - if (inBrowser) return ''; - // old ff need null, https://developer.mozilla.org/en-US/docs/Web/API/Window/getComputedStyle - const style = window.getComputedStyle(node, null); - let ret = ''; - for (let i = 0; i < prefixes.length; i++) { - ret = style.getPropertyValue(prefixes[i] + name); - if (ret) { - break; - } - } - return ret; -} - -function fixBrowserByTimeout(node) { - if (isCssAnimationSupported) { - const transitionDelay = parseFloat(getStyleProperty(node, 'transition-delay')) || 0; - const transitionDuration = parseFloat(getStyleProperty(node, 'transition-duration')) || 0; - const animationDelay = parseFloat(getStyleProperty(node, 'animation-delay')) || 0; - const animationDuration = parseFloat(getStyleProperty(node, 'animation-duration')) || 0; - const time = Math.max(transitionDuration + transitionDelay, animationDuration + animationDelay); - // sometimes, browser bug - node.rcEndAnimTimeout = setTimeout(() => { - node.rcEndAnimTimeout = null; - if (node.rcEndListener) { - node.rcEndListener(); - } - }, time * 1000 + 200); - } -} - -function clearBrowserBugTimeout(node) { - if (node.rcEndAnimTimeout) { - clearTimeout(node.rcEndAnimTimeout); - node.rcEndAnimTimeout = null; - } -} - -const cssAnimation = (node, transitionName, endCallback) => { - const nameIsObj = typeof transitionName === 'object'; - const className = nameIsObj ? transitionName.name : transitionName; - const activeClassName = nameIsObj ? transitionName.active : `${transitionName}-active`; - let end = endCallback; - let start; - let active; - const nodeClasses = classes(node); - - if (endCallback && Object.prototype.toString.call(endCallback) === '[object Object]') { - end = endCallback.end; - start = endCallback.start; - active = endCallback.active; - } - - if (node.rcEndListener) { - node.rcEndListener(); - } - - node.rcEndListener = e => { - if (e && e.target !== node) { - return; - } - - if (node.rcAnimTimeout) { - cancelAnimationTimeout(node.rcAnimTimeout); - node.rcAnimTimeout = null; - } - - clearBrowserBugTimeout(node); - - nodeClasses.remove(className); - nodeClasses.remove(activeClassName); - - Event.removeEndEventListener(node, node.rcEndListener); - node.rcEndListener = null; - - // Usually this optional end is used for informing an owner of - // a leave animation and telling it to remove the child. - if (end) { - end(); - } - }; - - Event.addEndEventListener(node, node.rcEndListener); - - if (start) { - start(); - } - nodeClasses.add(className); - - node.rcAnimTimeout = requestAnimationTimeout(() => { - node.rcAnimTimeout = null; - - nodeClasses.add(className); - nodeClasses.add(activeClassName); - - if (active) { - requestAnimationTimeout(active, 0); - } - fixBrowserByTimeout(node); - // 30ms for firefox - }, 30); - - return { - stop() { - if (node.rcEndListener) { - node.rcEndListener(); - } - }, - }; -}; - -cssAnimation.style = (node, style, callback) => { - if (node.rcEndListener) { - node.rcEndListener(); - } - - node.rcEndListener = e => { - if (e && e.target !== node) { - return; - } - - if (node.rcAnimTimeout) { - cancelAnimationTimeout(node.rcAnimTimeout); - node.rcAnimTimeout = null; - } - - clearBrowserBugTimeout(node); - - Event.removeEndEventListener(node, node.rcEndListener); - node.rcEndListener = null; - - // Usually this optional callback is used for informing an owner of - // a leave animation and telling it to remove the child. - if (callback) { - callback(); - } - }; - - Event.addEndEventListener(node, node.rcEndListener); - - node.rcAnimTimeout = requestAnimationTimeout(() => { - for (const s in style) { - if (style.hasOwnProperty(s)) { - node.style[s] = style[s]; - } - } - node.rcAnimTimeout = null; - fixBrowserByTimeout(node); - }, 0); -}; - -cssAnimation.setTransition = (node, p, value) => { - let property = p; - let v = value; - if (value === undefined) { - v = property; - property = ''; - } - property = property || ''; - capitalPrefixes.forEach(prefix => { - node.style[`${prefix}Transition${property}`] = v; - }); -}; - -cssAnimation.isCssAnimationSupported = isCssAnimationSupported; - -export { isCssAnimationSupported }; - -export default cssAnimation; diff --git a/components/_util/isCssAnimationSupported.js b/components/_util/isCssAnimationSupported.js deleted file mode 100644 index 45d51bb35..000000000 --- a/components/_util/isCssAnimationSupported.js +++ /dev/null @@ -1,24 +0,0 @@ -let animation; - -function isCssAnimationSupported() { - if (animation !== undefined) { - return animation; - } - const domPrefixes = 'Webkit Moz O ms Khtml'.split(' '); - const elm = document.createElement('div'); - if (elm.style.animationName !== undefined) { - animation = true; - } - if (animation !== undefined) { - for (let i = 0; i < domPrefixes.length; i++) { - if (elm.style[`${domPrefixes[i]}AnimationName`] !== undefined) { - animation = true; - break; - } - } - } - animation = animation || false; - return animation; -} - -export default isCssAnimationSupported; diff --git a/components/_util/transition.tsx b/components/_util/transition.tsx index fd6ebc490..f4b69f31e 100644 --- a/components/_util/transition.tsx +++ b/components/_util/transition.tsx @@ -5,7 +5,7 @@ import type { TransitionGroupProps, TransitionProps, } from 'vue'; -import { nextTick, Transition, TransitionGroup } from 'vue'; +import { nextTick } from 'vue'; import { tuple } from './type'; const SelectPlacements = tuple('bottomLeft', 'bottomRight', 'topLeft', 'topRight'); @@ -126,6 +126,4 @@ const getTransitionName = (rootPrefixCls: string, motion: string, transitionName return `${rootPrefixCls}-${motion}`; }; -export { Transition, TransitionGroup, collapseMotion, getTransitionName, getTransitionDirection }; - -export default Transition; +export { collapseMotion, getTransitionName, getTransitionDirection }; diff --git a/components/_util/wave/WaveEffect.tsx b/components/_util/wave/WaveEffect.tsx index 1f3ce23d1..b59c8ff66 100644 --- a/components/_util/wave/WaveEffect.tsx +++ b/components/_util/wave/WaveEffect.tsx @@ -159,6 +159,12 @@ function showWaveEffect(node: HTMLElement, className: string) { node?.insertBefore(holder, node?.firstChild); render(, holder); + return () => { + render(null, holder); + if (holder.parentElement) { + holder.parentElement.removeChild(holder); + } + }; } export default showWaveEffect; diff --git a/components/_util/wave/index.tsx b/components/_util/wave/index.tsx index 26ac1aff5..26dab40f9 100644 --- a/components/_util/wave/index.tsx +++ b/components/_util/wave/index.tsx @@ -33,13 +33,12 @@ export default defineComponent({ // =============================== Wave =============================== const showWave = useWave( - instance, computed(() => classNames(prefixCls.value, hashId.value)), wave, ); let onClick: (e: MouseEvent) => void; const clear = () => { - const node = findDOMNode(instance); + const node = findDOMNode(instance) as HTMLElement; node.removeEventListener('click', onClick, true); }; onMounted(() => { diff --git a/components/_util/wave/useWave.ts b/components/_util/wave/useWave.ts index 84e2a6eff..f88c36168 100644 --- a/components/_util/wave/useWave.ts +++ b/components/_util/wave/useWave.ts @@ -1,21 +1,25 @@ -import type { ComponentInternalInstance, ComputedRef, Ref } from 'vue'; +import type { ComputedRef, Ref } from 'vue'; +import { onBeforeUnmount, getCurrentInstance } from 'vue'; import { findDOMNode } from '../props-util'; import showWaveEffect from './WaveEffect'; export default function useWave( - instance: ComponentInternalInstance | null, className: Ref, wave?: ComputedRef<{ disabled?: boolean }>, ): VoidFunction { + const instance = getCurrentInstance(); + let stopWave: () => void; function showWave() { const node = findDOMNode(instance); - + stopWave?.(); if (wave?.value?.disabled || !node) { return; } - - showWaveEffect(node, className.value); + stopWave = showWaveEffect(node, className.value); } + onBeforeUnmount(() => { + stopWave?.(); + }); return showWave; } diff --git a/components/alert/index.tsx b/components/alert/index.tsx index 63a61aa6e..f3dead2ba 100644 --- a/components/alert/index.tsx +++ b/components/alert/index.tsx @@ -1,5 +1,5 @@ import type { CSSProperties, ExtractPropTypes, PropType } from 'vue'; -import { computed, defineComponent, shallowRef } from 'vue'; +import { computed, defineComponent, shallowRef, Transition } from 'vue'; import CloseOutlined from '@ant-design/icons-vue/CloseOutlined'; import CheckCircleOutlined from '@ant-design/icons-vue/CheckCircleOutlined'; import ExclamationCircleOutlined from '@ant-design/icons-vue/ExclamationCircleOutlined'; @@ -11,7 +11,7 @@ import InfoCircleFilled from '@ant-design/icons-vue/InfoCircleFilled'; import CloseCircleFilled from '@ant-design/icons-vue/CloseCircleFilled'; import classNames from '../_util/classNames'; import PropTypes from '../_util/vue-types'; -import { getTransitionProps, Transition } from '../_util/transition'; +import { getTransitionProps } from '../_util/transition'; import { isValidElement } from '../_util/props-util'; import { tuple, withInstall } from '../_util/type'; import { cloneElement } from '../_util/vnode'; diff --git a/components/badge/Badge.tsx b/components/badge/Badge.tsx index 30195d049..a0363732f 100644 --- a/components/badge/Badge.tsx +++ b/components/badge/Badge.tsx @@ -3,9 +3,9 @@ import ScrollNumber from './ScrollNumber'; import classNames from '../_util/classNames'; import { getPropsSlot, flattenChildren } from '../_util/props-util'; import { cloneElement } from '../_util/vnode'; -import { getTransitionProps, Transition } from '../_util/transition'; +import { getTransitionProps } from '../_util/transition'; import type { ExtractPropTypes, CSSProperties, PropType } from 'vue'; -import { defineComponent, computed, ref, watch } from 'vue'; +import { defineComponent, computed, ref, watch, Transition } from 'vue'; import Ribbon from './Ribbon'; import useConfigInject from '../config-provider/hooks/useConfigInject'; import isNumeric from '../_util/isNumeric'; diff --git a/components/button/LoadingIcon.tsx b/components/button/LoadingIcon.tsx index 391527fd4..f865e0907 100644 --- a/components/button/LoadingIcon.tsx +++ b/components/button/LoadingIcon.tsx @@ -1,6 +1,5 @@ -import { defineComponent, nextTick } from 'vue'; +import { defineComponent, nextTick, Transition } from 'vue'; import LoadingOutlined from '@ant-design/icons-vue/LoadingOutlined'; -import Transition from '../_util/transition'; const getCollapsedWidth = (node: HTMLSpanElement) => { if (node) { node.style.width = '0px'; diff --git a/components/calendar/index.zh-CN.md b/components/calendar/index.zh-CN.md index 3d0f68d1f..725f2d573 100644 --- a/components/calendar/index.zh-CN.md +++ b/components/calendar/index.zh-CN.md @@ -28,16 +28,16 @@ coverDark: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*-p-wQLik200AAA | 参数 | 说明 | 类型 | 默认值 | 版本 | | --- | --- | --- | --- | --- | -| dateCellRender | 作用域插槽,用来自定义渲染日期单元格,返回内容会被追加到单元格, | v-slot:dateCellRender="{current: dayjs}" | 无 | | -| dateFullCellRender | 作用域插槽,自定义渲染日期单元格,返回内容覆盖单元格 | v-slot:dateFullCellRender="{current: dayjs}" | 无 | | -| disabledDate | 不可选择的日期 | (currentDate: dayjs) => boolean | 无 | | +| dateCellRender | 作用域插槽,用来自定义渲染日期单元格,返回内容会被追加到单元格, | v-slot:dateCellRender="{current: dayjs}" | - | | +| dateFullCellRender | 作用域插槽,自定义渲染日期单元格,返回内容覆盖单元格 | v-slot:dateFullCellRender="{current: dayjs}" | - | | +| disabledDate | 不可选择的日期 | (currentDate: dayjs) => boolean | - | | | fullscreen | 是否全屏显示 | boolean | true | | | headerRender | 自定义头部内容 | v-slot:headerRender="{value: dayjs, type: string, onChange: f(), onTypeChange: f()}" | - | | | locale | 国际化配置 | object | [默认配置](https://github.com/vueComponent/ant-design-vue/blob/main/components/date-picker/locale/example.json) | | | mode | 初始模式,`month/year` | string | month | | -| monthCellRender | 作用域插槽,自定义渲染月单元格,返回内容会被追加到单元格 | v-slot:monthCellRender="{current: dayjs}" | 无 | | -| monthFullCellRender | 作用域插槽,自定义渲染月单元格,返回内容覆盖单元格 | v-slot:monthFullCellRender="{current: dayjs}" | 无 | | -| validRange | 设置可以显示的日期 | \[[dayjs](https://day.js.org/), [dayjs](https://day.js.org/)] | 无 | | +| monthCellRender | 作用域插槽,自定义渲染月单元格,返回内容会被追加到单元格 | v-slot:monthCellRender="{current: dayjs}" | - | | +| monthFullCellRender | 作用域插槽,自定义渲染月单元格,返回内容覆盖单元格 | v-slot:monthFullCellRender="{current: dayjs}" | - | | +| validRange | 设置可以显示的日期 | \[[dayjs](https://day.js.org/), [dayjs](https://day.js.org/)] | - | | | value(v-model) | 展示日期 | [dayjs](https://day.js.org/) | 当前日期 | | | valueFormat | 可选,绑定值的格式,对 value、defaultValue 起作用。不指定则绑定值为 dayjs 对象 | string,[具体格式](https://day.js.org/docs/zh-CN/display/format) | - | | @@ -45,8 +45,8 @@ coverDark: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*-p-wQLik200AAA | 事件名称 | 说明 | 回调参数 | | | --- | --- | --- | --- | --- | -| change | 日期变化时的回调, 面板变化有可能导致日期变化 | function(date: dayjs \| string) | 无 | -| panelChange | 日期面板变化回调 | function(date: dayjs \| string, mode: string) | 无 | +| change | 日期变化时的回调, 面板变化有可能导致日期变化 | function(date: dayjs \| string) | - | +| panelChange | 日期面板变化回调 | function(date: dayjs \| string, mode: string) | - | | select | 选择日期回调,包含来源信息 | function(date: Dayjs, info: { source: 'year' \| 'month' \| 'date' \| 'customize' }) | - | | ### 如何仅获取来自面板点击的日期? diff --git a/components/collapse/CollapsePanel.tsx b/components/collapse/CollapsePanel.tsx index 4ff984dd7..95af0ec64 100644 --- a/components/collapse/CollapsePanel.tsx +++ b/components/collapse/CollapsePanel.tsx @@ -2,8 +2,7 @@ import PanelContent from './PanelContent'; import { initDefaultProps } from '../_util/props-util'; import { collapsePanelProps } from './commonProps'; import type { ExtractPropTypes } from 'vue'; -import { defineComponent } from 'vue'; -import Transition from '../_util/transition'; +import { defineComponent, Transition } from 'vue'; import classNames from '../_util/classNames'; import devWarning from '../vc-util/devWarning'; import useConfigInject from '../config-provider/hooks/useConfigInject'; diff --git a/components/collapse/index.en-US.md b/components/collapse/index.en-US.md index 4a80753b0..429a95e43 100644 --- a/components/collapse/index.en-US.md +++ b/components/collapse/index.en-US.md @@ -20,7 +20,7 @@ A content area which can be collapsed and expanded. | Property | Description | Type | Default | Version | | --- | --- | --- | --- | --- | | accordion | If `true`, `Collapse` renders as `Accordion` | boolean | `false` | | -| activeKey(v-model) | Key of the active panel | string\[] \| string
number\[] \| number | No default value. In `accordion` mode, it's the key of the first panel. | | +| activeKey(v-model) | Key of the active panel | string\[] \| string
number\[] \| number | No default value. In [accordion mode](#components-collapse-demo-accordion), it's the key of the first panel. | | | bordered | Toggles rendering of the border around the collapse block | boolean | `true` | | | collapsible | Specify whether the panels of children be collapsible or the trigger area of collapsible | `header` \| `icon` \| `disabled` | - | 4.0 | | destroyInactivePanel | Destroy Inactive Panel | boolean | `false` | | diff --git a/components/collapse/index.zh-CN.md b/components/collapse/index.zh-CN.md index 1c750c85f..ddae33295 100644 --- a/components/collapse/index.zh-CN.md +++ b/components/collapse/index.zh-CN.md @@ -20,8 +20,8 @@ coverDark: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*sir-TK0HkWcAAA | 参数 | 说明 | 类型 | 默认值 | 版本 | | --- | --- | --- | --- | --- | -| accordion | 手风琴模式 | boolean | `false` | | -| activeKey(v-model) | 当前激活 tab 面板的 key | string\[] \| string
number\[] \| number | 默认无,accordion 模式下默认第一个元素 | | +| accordion | 手风琴模式,始终只有一个面板处在激活状态 | boolean | `false` | | +| activeKey(v-model) | 当前激活 tab 面板的 key | string\[] \| string
number\[] \| number | 默认无,[手风琴模式](#components-collapse-demo-accordion)下默认第一个元素 | | | bordered | 带边框风格的折叠面板 | boolean | `true` | | | collapsible | 所有子面板是否可折叠或指定可折叠触发区域 | `header` \| `icon` \| `disabled` | - | 4.0 | | destroyInactivePanel | 销毁折叠隐藏的面板 | boolean | `false` | | @@ -42,6 +42,6 @@ coverDark: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*sir-TK0HkWcAAA | collapsible | 是否可折叠或指定可折叠触发区域 | `header` \| `disabled` | - | 3.0 | | extra | 自定义渲染每个面板右上角的内容 | VNode \| slot | - | 1.5.0 | | forceRender | 被隐藏时是否渲染 DOM 结构 | boolean | false | | -| header | 面板头内容 | string\|slot | 无 | | -| key | 对应 activeKey | string \| number | 无 | | +| header | 面板头内容 | string\|slot | - | | +| key | 对应 activeKey | string \| number | - | | | showArrow | 是否展示当前面板上的箭头 | boolean | `true` | | diff --git a/components/config-provider/index.tsx b/components/config-provider/index.tsx index 68198c2e7..223260119 100644 --- a/components/config-provider/index.tsx +++ b/components/config-provider/index.tsx @@ -1,4 +1,4 @@ -import type { App, Plugin, WatchStopHandle } from 'vue'; +import type { App, MaybeRef, Plugin, WatchStopHandle } from 'vue'; import { watch, computed, reactive, defineComponent, watchEffect } from 'vue'; import defaultRenderEmpty from './renderEmpty'; import type { RenderEmptyHandler } from './renderEmpty'; @@ -7,7 +7,6 @@ import LocaleProvider, { ANT_MARK } from '../locale-provider'; import LocaleReceiver from '../locale-provider/LocaleReceiver'; -import type { MaybeRef } from '../_util/type'; import message from '../message'; import notification from '../notification'; import { registerTheme } from './cssVariables'; diff --git a/components/date-picker/index.en-US.md b/components/date-picker/index.en-US.md index dd071b05a..2b84c13cd 100644 --- a/components/date-picker/index.en-US.md +++ b/components/date-picker/index.en-US.md @@ -85,7 +85,7 @@ The following APIs are shared by DatePicker, RangePicker. | disabled | Determine whether the DatePicker is disabled | boolean | false | | | disabledDate | Specify the date that cannot be selected | (currentDate: dayjs) => boolean | - | | | format | To set the date format, refer to [dayjs](https://day.js.org/). When an array is provided, all values are used for parsing and first value is used for formatting, support [Custom Format](#components-date-picker-demo-format) | [formatType](#formattype) | `YYYY-MM-DD` | | -| dropdownClassName | To customize the className of the popup calendar | string | - | | +| popupClassName | To customize the className of the popup calendar | string | - | | | getPopupContainer | To set the container of the floating layer, while the default is to create a `div` element in `body` | function(trigger) | - | | | inputReadOnly | Set the `readonly` attribute of the input tag (avoids virtual keyboard on touch devices) | boolean | false | | | locale | Localization configuration | object | [default](https://github.com/vueComponent/ant-design-vue/blob/main/components/date-picker/locale/example.json) | | diff --git a/components/date-picker/index.zh-CN.md b/components/date-picker/index.zh-CN.md index 8aa5fdcbd..f878261bb 100644 --- a/components/date-picker/index.zh-CN.md +++ b/components/date-picker/index.zh-CN.md @@ -86,7 +86,7 @@ coverDark: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*3OpRQKcygo8AAA | disabled | 禁用 | boolean | false | | | disabledDate | 不可选择的日期 | (currentDate: dayjs) => boolean | - | | | format | 设置日期格式,为数组时支持多格式匹配,展示以第一个为准。配置参考 [dayjs](https://day.js.org/docs/zh-CN/display/format),支持[自定义格式](#components-date-picker-demo-format) | [formatType](#formattype) | `YYYY-MM-DD` | | -| dropdownClassName | 额外的弹出日历 className | string | - | | +| popupClassName | 额外的弹出日历 className | string | - | | | getPopupContainer | 定义浮层的容器,默认为 body 上新建 div | function(trigger) | - | | | inputReadOnly | 设置输入框为只读(避免在移动设备上打开虚拟键盘) | boolean | false | | | locale | 国际化配置 | object | [默认配置](https://github.com/vueComponent/ant-design-vue/blob/main/components/date-picker/locale/example.json) | - | diff --git a/components/dropdown/index.zh-CN.md b/components/dropdown/index.zh-CN.md index b66ae4048..1149da0a2 100644 --- a/components/dropdown/index.zh-CN.md +++ b/components/dropdown/index.zh-CN.md @@ -22,7 +22,7 @@ coverDark: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*5qm4S4Zgh2QAAA | 参数 | 说明 | 类型 | 默认值 | | | --- | --- | --- | --- | --- | -| align | 该值将合并到 placement 的配置中,设置参考 [dom-align](https://github.com/yiminghe/dom-align) | Object | 无 | | +| align | 该值将合并到 placement 的配置中,设置参考 [dom-align](https://github.com/yiminghe/dom-align) | Object | - | | | arrow | 下拉框箭头是否显示 | boolean \| { pointAtCenter: boolean } | false | 3.3.0 | | destroyPopupOnHide | 关闭后是否销毁 Dropdown | boolean | false | 3.0 | | disabled | 菜单是否禁用 | boolean | - | | diff --git a/components/empty/index.tsx b/components/empty/index.tsx index 5d73982bc..ab778c9e6 100644 --- a/components/empty/index.tsx +++ b/components/empty/index.tsx @@ -1,4 +1,4 @@ -import { defineComponent } from 'vue'; +import { defineComponent, h } from 'vue'; import type { CSSProperties, ExtractPropTypes } from 'vue'; import classNames from '../_util/classNames'; import LocaleReceiver from '../locale-provider/LocaleReceiver'; @@ -11,9 +11,6 @@ import useConfigInject from '../config-provider/hooks/useConfigInject'; import useStyle from './style'; -const defaultEmptyImg = ; -const simpleEmptyImg = ; - interface Locale { description?: string; } @@ -40,13 +37,16 @@ const Empty = defineComponent({ return () => { const prefixCls = prefixClsRef.value; const { - image = slots.image?.() || defaultEmptyImg, + image: mergedImage = slots.image?.() || h(DefaultEmptyImg), description = slots.description?.() || undefined, imageStyle, class: className = '', ...restProps } = { ...props, ...attrs }; - + const image = + typeof mergedImage === 'function' ? (mergedImage as () => VueNode)() : mergedImage; + const isNormal = + typeof image === 'object' && 'type' in image && (image.type as any).PRESENTED_IMAGE_SIMPLE; return wrapSSR( h(DefaultEmptyImg); +Empty.PRESENTED_IMAGE_SIMPLE = () => h(SimpleEmptyImg); export default withInstall(Empty); diff --git a/components/float-button/BackTop.tsx b/components/float-button/BackTop.tsx index 6baa6daad..722905975 100644 --- a/components/float-button/BackTop.tsx +++ b/components/float-button/BackTop.tsx @@ -1,5 +1,5 @@ import VerticalAlignTopOutlined from '@ant-design/icons-vue/VerticalAlignTopOutlined'; -import { getTransitionProps, Transition } from '../_util/transition'; +import { getTransitionProps } from '../_util/transition'; import { defineComponent, nextTick, @@ -10,6 +10,7 @@ import { ref, watch, onDeactivated, + Transition, } from 'vue'; import FloatButton, { floatButtonPrefixCls } from './FloatButton'; import useConfigInject from '../config-provider/hooks/useConfigInject'; diff --git a/components/float-button/FloatButtonGroup.tsx b/components/float-button/FloatButtonGroup.tsx index 743d60e5a..611646299 100644 --- a/components/float-button/FloatButtonGroup.tsx +++ b/components/float-button/FloatButtonGroup.tsx @@ -1,8 +1,8 @@ -import { defineComponent, ref, computed, watch, onBeforeUnmount } from 'vue'; +import { defineComponent, ref, computed, watch, onBeforeUnmount, Transition } from 'vue'; import CloseOutlined from '@ant-design/icons-vue/CloseOutlined'; import FileTextOutlined from '@ant-design/icons-vue/FileTextOutlined'; import classNames from '../_util/classNames'; -import { getTransitionProps, Transition } from '../_util/transition'; +import { getTransitionProps } from '../_util/transition'; import FloatButton, { floatButtonPrefixCls } from './FloatButton'; import useConfigInject from '../config-provider/hooks/useConfigInject'; import { useProvideFloatButtonGroupContext } from './context'; diff --git a/components/float-button/index.zh-CN.md b/components/float-button/index.zh-CN.md index 589c65865..284cd6842 100644 --- a/components/float-button/index.zh-CN.md +++ b/components/float-button/index.zh-CN.md @@ -33,11 +33,11 @@ tag: New | target | 相当于 a 标签的 target 属性,href 存在时生效 | string | - | | | badge | 带徽标数字的悬浮按钮(不支持 status 以及相关属性) | [BadgeProps](/components/badge-cn#api) | - | | -### common events +### 共同的事件 -| 事件名称 | 说明 | 回调参数 | 版本 | -| -------- | --------------------------------------- | ----------------- | ---- | -| click | Set the handler to handle `click` event | `(event) => void` | - | +| 事件名称 | 说明 | 回调参数 | 版本 | +| -------- | ----------------------------- | ----------------- | ---- | +| click | 设置处理 `click` 事件的处理器 | `(event) => void` | - | ### FloatButton.Group @@ -47,7 +47,7 @@ tag: New | trigger | 触发方式(有触发方式为菜单模式) | `click` \| `hover` | - | | | open(v-model) | 受控展开 | boolean | - | | -### FloatButton.Group Events +### FloatButton.Group 事件 | 事件名称 | 说明 | 回调参数 | 版本 | | ---------- | ---------------- | ----------------------- | ---- | diff --git a/components/float-button/interface.ts b/components/float-button/interface.ts index 6561910b8..9db3fba65 100644 --- a/components/float-button/interface.ts +++ b/components/float-button/interface.ts @@ -20,7 +20,7 @@ export const floatButtonProps = () => { shape: stringType('circle'), tooltip: PropTypes.any, href: String, - target: functionType<() => Window | HTMLElement | null>(), + target: String, badge: objectType(), onClick: functionType(), }; diff --git a/components/form/FormItem.tsx b/components/form/FormItem.tsx index df70f8e6f..6a0608de4 100644 --- a/components/form/FormItem.tsx +++ b/components/form/FormItem.tsx @@ -494,7 +494,7 @@ export default defineComponent({ > ( diff --git a/components/grid/style/index.ts b/components/grid/style/index.ts index 895b5b01c..d9df3e2fa 100644 --- a/components/grid/style/index.ts +++ b/components/grid/style/index.ts @@ -52,6 +52,10 @@ const genGridRowStyle: GenerateStyle = (token): CSSObject => { justifyContent: 'space-around', }, + '&-space-evenly ': { + justifyContent: 'space-evenly', + }, + // Align at the top '&-top': { alignItems: 'flex-start', diff --git a/components/icon/index.en-US.md b/components/icon/index.en-US.md index 758950f77..7d753e2c3 100644 --- a/components/icon/index.en-US.md +++ b/components/icon/index.en-US.md @@ -122,6 +122,8 @@ See [iconfont.cn documents](http://iconfont.cn/help/detail?spm=a313x.7781069.199 ### Custom SVG Icon +#### vue cli 3 + You can import SVG icon as an vue component by using `vue cli 3` and [`vue-svg-loader`](https://www.npmjs.com/package/vue-svg-loader). `vue-svg-loader`'s `options` [reference](https://github.com/visualfanatic/vue-svg-loader). ```js @@ -149,6 +151,84 @@ export default defineComponent({ }); ``` +#### Rsbuild + +Rsbuild is a new generation of build tool, official website https://rsbuild.dev/ +Create your own `vue-svg-loader.js` file, which allows you to customize and beautify SVG, and then configure it in `rsbuild.config.ts` + +```js +// vue-svg-loader.js +/* eslint-disable */ +const { optimize } = require('svgo'); +const { version } = require('vue'); +const semverMajor = require('semver/functions/major'); + +module.exports = async function (svg) { + const callback = this.async(); + + try { + ({ data: svg } = await optimize(svg, { + path: this.resourcePath, + js2svg: { + indent: 2, + pretty: true, + }, + plugins: [ + 'convertStyleToAttrs', + 'removeDoctype', + 'removeXMLProcInst', + 'removeComments', + 'removeMetadata', + 'removeTitle', + 'removeDesc', + 'removeStyleElement', + 'removeXMLNS', + 'removeXMLProcInst', + ], + })); + } catch (error) { + callback(error); + return; + } + + if (semverMajor(version) === 2) { + svg = svg.replace('${svg}`); +}; +``` + +```js +// rsbuild.config.ts +/* eslint-disable */ +import { defineConfig } from '@rsbuild/core'; +import { pluginVue } from '@rsbuild/plugin-vue'; + +export default defineConfig({ + tools: { + bundlerChain(chain, { CHAIN_ID }) { + chain.module.rule(CHAIN_ID.RULE.SVG).exclude.add(/\.svg$/); + }, + rspack: { + module: { + rules: [ + { + test: /\.svg$/, + use: ['vue-loader', 'vue-svg-loader'], + }, + ], + }, + resolveLoader: { + alias: { + 'vue-svg-loader': require('path').join(__dirname, './vue-svg-loader.js'), + }, + }, + }, + }, +}); +``` + The following properties are available for the component: | Property | Description | Type | Default | diff --git a/components/icon/index.zh-CN.md b/components/icon/index.zh-CN.md index c8a02d68e..66995e279 100644 --- a/components/icon/index.zh-CN.md +++ b/components/icon/index.zh-CN.md @@ -119,7 +119,9 @@ export default defineComponent({ ### 自定义 SVG 图标 -如果使用 `vue cli 3`,可以通过配置 [vue-svg-loader](https://www.npmjs.com/package/vue-svg-loader) 来将 `svg` 图标作为 `Vue` 组件导入。更多`vue-svg-loader` 的使用方式请参阅 [文档](https://github.com/visualfanatic/vue-svg-loader)。 +#### vue cli 3 + +可以通过配置 [vue-svg-loader](https://www.npmjs.com/package/vue-svg-loader) 来将 `svg` 图标作为 `Vue` 组件导入。更多`vue-svg-loader` 的使用方式请参阅 [文档](https://github.com/visualfanatic/vue-svg-loader)。 ```js // vue.config.js @@ -146,6 +148,88 @@ export default defineComponent({ }); ``` +#### Rsbuild + +Rsbuild 是新一代构建工具,官网 https://rsbuild.dev/ + +自己实现一个 `vue-svg-loader.js` 文件,好处是可以自定义美化 svg,然后在 `rsbuild.config.ts` 中配置: + +```js +// vue-svg-loader.js +/* eslint-disable */ +const { optimize } = require('svgo'); +const { version } = require('vue'); +const semverMajor = require('semver/functions/major'); + +module.exports = async function (svg) { + const callback = this.async(); + + try { + ({ data: svg } = await optimize(svg, { + path: this.resourcePath, + js2svg: { + indent: 2, + pretty: true, + }, + plugins: [ + 'convertStyleToAttrs', + 'removeDoctype', + 'removeXMLProcInst', + 'removeComments', + 'removeMetadata', + 'removeTitle', + 'removeDesc', + 'removeStyleElement', + 'removeXMLNS', + 'removeXMLProcInst', + ], + })); + } catch (error) { + callback(error); + return; + } + + if (semverMajor(version) === 2) { + svg = svg.replace('${svg}`); +}; +``` + +```js +// rsbuild.config.ts +/* eslint-disable */ +import { defineConfig } from '@rsbuild/core'; +import { pluginVue } from '@rsbuild/plugin-vue'; + +export default defineConfig({ + tools: { + bundlerChain(chain, { CHAIN_ID }) { + chain.module + // 先给svg排除默认的规则,方便下面自定义loader + .rule(CHAIN_ID.RULE.SVG) + .exclude.add(/\.svg$/); + }, + rspack: { + module: { + rules: [ + { + test: /\.svg$/, + use: ['vue-loader', 'vue-svg-loader'], + }, + ], + }, + resolveLoader: { + alias: { + 'vue-svg-loader': require('path').join(__dirname, './vue-svg-loader.js'), + }, + }, + }, + }, +}); +``` + `Icon` 中的 `component` 组件的接受的属性如下: | 字段 | 说明 | 类型 | 只读值 | diff --git a/components/image/index.tsx b/components/image/index.tsx index 0e752b486..7275f2bee 100644 --- a/components/image/index.tsx +++ b/components/image/index.tsx @@ -13,10 +13,10 @@ export type ImageProps = Partial< ExtractPropTypes> & Omit >; -const Image = defineComponent({ +const Image = defineComponent({ name: 'AImage', inheritAttrs: false, - props: imageProps() as any, + props: imageProps(), setup(props, { slots, attrs }) { const { prefixCls, rootPrefixCls, configProvider } = useConfigInject('image', props); // Style diff --git a/components/input-number/index.tsx b/components/input-number/index.tsx index 7b1b4418a..a2c7ad99b 100644 --- a/components/input-number/index.tsx +++ b/components/input-number/index.tsx @@ -72,7 +72,7 @@ const InputNumber = defineComponent({ const mergedSize = computed(() => compactSize.value || size.value); - const mergedValue = shallowRef(props.value === undefined ? props.defaultValue : props.value); + const mergedValue = shallowRef(props.value ?? props.defaultValue); const focused = shallowRef(false); watch( () => props.value, diff --git a/components/input-number/index.zh-CN.md b/components/input-number/index.zh-CN.md index a603d4a70..3ce0b3094 100644 --- a/components/input-number/index.zh-CN.md +++ b/components/input-number/index.zh-CN.md @@ -34,7 +34,7 @@ coverDark: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*1uH-R5kLAMIAAA | parser | 指定从 formatter 里转换回数字的方式,和 formatter 搭配使用 | function( string): number | - | | | precision | 数值精度 | number | - | | | prefix | 带有前缀图标的 input | slot | - | 3.0 | -| size | 输入框大小 | string | 无 | | +| size | 输入框大小 | string | - | | | status | 设置校验状态 | 'error' \| 'warning' | - | 3.3.0 | | step | 每次改变步数,可以为小数 | number\|string | 1 | | | stringMode | 字符值模式,开启后支持高精度小数。同时 `change` 事件将返回 string 类型 | boolean | false | 3.0 | diff --git a/components/input-number/src/InputNumber.tsx b/components/input-number/src/InputNumber.tsx index 811b8eeef..9fa9a7d46 100644 --- a/components/input-number/src/InputNumber.tsx +++ b/components/input-number/src/InputNumber.tsx @@ -395,6 +395,11 @@ export default defineComponent({ } }; + // Solve the issue of the event triggering sequence when entering numbers in chinese input (Safari) + const onBeforeInput = () => { + userTypingRef.value = true; + }; + const onKeyDown: KeyboardEventHandler = event => { const { which } = event; userTypingRef.value = true; @@ -577,6 +582,7 @@ export default defineComponent({ onBlur={onBlur} onCompositionstart={onCompositionStart} onCompositionend={onCompositionEnd} + onBeforeinput={onBeforeInput} />
diff --git a/components/input-number/style/index.tsx b/components/input-number/style/index.tsx index d119e7d66..e363f0772 100644 --- a/components/input-number/style/index.tsx +++ b/components/input-number/style/index.tsx @@ -263,6 +263,10 @@ const genInputNumberStyles: GenerateStyle = (token: InputNumbe [`${componentCls}-handler-wrap`]: { display: 'none', }, + + [`${componentCls}-input`]: { + color: 'inherit', + }, }, [` diff --git a/components/input/Password.tsx b/components/input/Password.tsx index 0d65270b4..a516c0a84 100644 --- a/components/input/Password.tsx +++ b/components/input/Password.tsx @@ -59,7 +59,7 @@ export default defineComponent({ }); const getIcon = (prefixCls: string) => { const { action, iconRender = slots.iconRender || defaultIconRender } = props; - const iconTrigger = ActionMap[action!] || ''; + const iconTrigger = ActionMap[action] || ''; const icon = iconRender(visible.value); const iconProps = { [iconTrigger]: onVisibleChange, diff --git a/components/input/TextArea.tsx b/components/input/TextArea.tsx index 09c4a80d2..bfe0544cf 100644 --- a/components/input/TextArea.tsx +++ b/components/input/TextArea.tsx @@ -38,10 +38,10 @@ function setTriggerValue( let newTriggerValue = triggerValue; if (isCursorInEnd) { // 光标在尾部,直接截断 - newTriggerValue = fixEmojiLength(triggerValue, maxLength!); + newTriggerValue = fixEmojiLength(triggerValue, maxLength); } else if ( [...(preValue || '')].length < triggerValue.length && - [...(triggerValue || '')].length > maxLength! + [...(triggerValue || '')].length > maxLength ) { // 光标在中间,如果最后的值超过最大值,则采用原先的值 newTriggerValue = preValue; @@ -58,7 +58,7 @@ export default defineComponent({ const formItemContext = useInjectFormItemContext(); const formItemInputContext = FormItemInputContext.useInject(); const mergedStatus = computed(() => getMergedStatus(formItemInputContext.status, props.status)); - const stateValue = shallowRef(props.value === undefined ? props.defaultValue : props.value); + const stateValue = shallowRef(props.value ?? props.defaultValue); const resizableTextArea = shallowRef(); const mergedValue = shallowRef(''); const { prefixCls, size, direction } = useConfigInject('input', props); @@ -79,7 +79,7 @@ export default defineComponent({ const onInternalCompositionStart = (e: CompositionEvent) => { compositing.value = true; // 拼音输入前保存一份旧值 - oldCompositionValueRef.value = mergedValue.value as string; + oldCompositionValueRef.value = mergedValue.value; // 保存旧的光标位置 oldSelectionStartRef.value = (e.currentTarget as any).selectionStart; emit('compositionstart', e); @@ -94,7 +94,7 @@ export default defineComponent({ oldSelectionStartRef.value === oldCompositionValueRef.value?.length; triggerValue = setTriggerValue( isCursorInEnd, - oldCompositionValueRef.value as string, + oldCompositionValueRef.value, triggerValue, props.maxlength, ); @@ -177,14 +177,14 @@ export default defineComponent({ // 1. 复制粘贴超过maxlength的情况 2.未超过maxlength的情况 const target = e.target as any; const isCursorInEnd = - target.selectionStart >= props.maxlength! + 1 || + target.selectionStart >= props.maxlength + 1 || target.selectionStart === triggerValue.length || !target.selectionStart; triggerValue = setTriggerValue( isCursorInEnd, - mergedValue.value as string, + mergedValue.value, triggerValue, - props.maxlength!, + props.maxlength, ); } resolveOnChange(e.currentTarget as any, e, triggerChange, triggerValue); @@ -237,7 +237,7 @@ export default defineComponent({ }); watchEffect(() => { - let val = fixControlledValue(stateValue.value) as string; + let val = fixControlledValue(stateValue.value); if ( !compositing.value && hasMaxLength.value && diff --git a/components/input/calculateNodeHeight.tsx b/components/input/calculateNodeHeight.tsx index 03ce2a984..509f7308b 100644 --- a/components/input/calculateNodeHeight.tsx +++ b/components/input/calculateNodeHeight.tsx @@ -48,9 +48,8 @@ const computedStyleCache: Record = {}; let hiddenTextarea: HTMLTextAreaElement; export function calculateNodeStyling(node: HTMLElement, useCache = false) { - const nodeRef = (node.getAttribute('id') || - node.getAttribute('data-reactid') || - node.getAttribute('name')) as string; + const nodeRef = + node.getAttribute('id') || node.getAttribute('data-reactid') || node.getAttribute('name'); if (useCache && computedStyleCache[nodeRef]) { return computedStyleCache[nodeRef]; @@ -103,7 +102,7 @@ export default function calculateAutoSizeStyle( // Fix wrap="off" issue // https://github.com/ant-design/ant-design/issues/6577 if (uiTextNode.getAttribute('wrap')) { - hiddenTextarea.setAttribute('wrap', uiTextNode.getAttribute('wrap') as string); + hiddenTextarea.setAttribute('wrap', uiTextNode.getAttribute('wrap')); } else { hiddenTextarea.removeAttribute('wrap'); } diff --git a/components/menu/src/InlineSubMenuList.tsx b/components/menu/src/InlineSubMenuList.tsx index 29794e8be..7b9cb1f33 100644 --- a/components/menu/src/InlineSubMenuList.tsx +++ b/components/menu/src/InlineSubMenuList.tsx @@ -1,5 +1,4 @@ -import { computed, defineComponent, ref, watch } from 'vue'; -import Transition from '../../_util/transition'; +import { computed, Transition, defineComponent, ref, watch } from 'vue'; import { useInjectMenu, MenuContextProvider } from './hooks/useMenuContext'; import type { MenuMode } from './interface'; import SubMenuList from './SubMenuList'; diff --git a/components/message/useMessage.tsx b/components/message/useMessage.tsx index e4e929eb2..f1ae5325f 100644 --- a/components/message/useMessage.tsx +++ b/components/message/useMessage.tsx @@ -47,7 +47,9 @@ const Holder = defineComponent({ 'rtl', 'transitionName', 'onAllRemoved', - ] as any, + 'animation', + 'staticGetContainer', + ], setup(props, { expose }) { const { getPrefixCls, getPopupContainer } = useConfigInject('message', props); diff --git a/components/modal/index.zh-CN.md b/components/modal/index.zh-CN.md index 931649b1b..103885923 100644 --- a/components/modal/index.zh-CN.md +++ b/components/modal/index.zh-CN.md @@ -19,14 +19,14 @@ coverDark: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*fBrgSJBmavgAAA | 参数 | 说明 | 类型 | 默认值 | 版本 | | --- | --- | --- | --- | --- | -| afterClose | Modal 完全关闭后的回调 | function | 无 | | +| afterClose | Modal 完全关闭后的回调 | function | - | | | bodyStyle | Modal body 样式 | object | {} | | | cancelButtonProps | cancel 按钮 props | [ButtonProps](/components/button/#api) | - | | | cancelText | 取消按钮文字 | string\| slot | 取消 | | | centered | 垂直居中展示 Modal | boolean | `false` | | | closable | 是否显示右上角的关闭按钮 | boolean | true | | | closeIcon | 自定义关闭图标 | VNode \| slot | - | | -| confirmLoading | 确定按钮 loading | boolean | 无 | | +| confirmLoading | 确定按钮 loading | boolean | - | | | destroyOnClose | 关闭时销毁 Modal 里的子元素 | boolean | false | | | footer | 底部内容,当不需要默认底部按钮时,可以设为 `:footer="null"` | string\|slot | 确定取消按钮 | | | forceRender | 强制渲染 Modal | boolean | false | | @@ -38,8 +38,8 @@ coverDark: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*fBrgSJBmavgAAA | okButtonProps | ok 按钮 props | [ButtonProps](/components/button/#api) | - | | | okText | 确认按钮文字 | string\|slot | 确定 | | | okType | 确认按钮类型 | string | primary | | -| title | 标题 | string\|slot | 无 | | -| open(v-model) | 对话框是否可见 | boolean | 无 | | +| title | 标题 | string\|slot | - | | +| open(v-model) | 对话框是否可见 | boolean | - | | | width | 宽度 | string\|number | 520 | | | wrapClassName | 对话框外层容器的类名 | string | - | | | zIndex | 设置 Modal 的 `z-index` | number | 1000 | | @@ -76,7 +76,7 @@ coverDark: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*fBrgSJBmavgAAA | centered | 垂直居中展示 Modal | boolean | `false` | | | class | 容器类名 | string | - | | | closable | 是否显示右上角的关闭按钮 | boolean | `false` | | -| content | 内容 | string \|VNode \|function() | 无 | | +| content | 内容 | string \|VNode \|function() | - | | | footer | 底部内容,当不需要默认底部按钮时,可以设为 `footer: null` | string \|VNode \|function() | - | 4.0.0 | | icon | 自定义图标(1.14.0 新增) | VNode \| ()=>VNode | - | | | keyboard | 是否支持键盘 esc 关闭 | boolean | true | | @@ -85,12 +85,12 @@ coverDark: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*fBrgSJBmavgAAA | okButtonProps | ok 按钮 props | [ButtonProps](/components/button) | - | | | okText | 确认按钮文字 | string | 确定 | | | okType | 确认按钮类型 | string | primary | | -| title | 标题 | string\|VNode \|function() | 无 | | +| title | 标题 | string\|VNode \|function() | - | | | width | 宽度 | string\|number | 416 | | | wrapClassName | 对话框外层容器的类名 | string | - | 3.2.3 | | zIndex | 设置 Modal 的 `z-index` | number | 1000 | | -| onCancel | 取消回调,参数为关闭函数,返回 promise 时 resolve 后自动关闭 | function | 无 | | -| onOk | 点击确定回调,参数为关闭函数,返回 promise 时 resolve 后自动关闭 | function | 无 | | +| onCancel | 取消回调,参数为关闭函数,返回 promise 时 resolve 后自动关闭 | function | - | | +| onOk | 点击确定回调,参数为关闭函数,返回 promise 时 resolve 后自动关闭 | function | - | | 以上函数调用后,会返回一个引用,可以通过该引用更新和关闭弹窗。 diff --git a/components/popconfirm/index.zh-CN.md b/components/popconfirm/index.zh-CN.md index 22d4e2354..df402b00b 100644 --- a/components/popconfirm/index.zh-CN.md +++ b/components/popconfirm/index.zh-CN.md @@ -27,7 +27,7 @@ coverDark: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*HrFtQ6jJJFQAAA | okText | 确认按钮文字 | string\|slot | 确定 | | | okType | 确认按钮类型 | string | primary | | | showCancel | 是否显示取消按钮 | boolean | true | 3.0 | -| title | 确认框的描述 | string\|slot | 无 | | +| title | 确认框的描述 | string\|slot | - | | | description | 确认内容的详细描述 | string\|slot | - | 4.0 | | open (v-model) | 是否显示 | boolean | - | 4.0 | diff --git a/components/segmented/index.zh-CN.md b/components/segmented/index.zh-CN.md index 91b1b03a5..8d5068da5 100644 --- a/components/segmented/index.zh-CN.md +++ b/components/segmented/index.zh-CN.md @@ -21,7 +21,7 @@ tag: New | 参数 | 说明 | 类型 | 默认值 | 版本 | | --- | --- | --- | --- | --- | -| block | 将宽度调整为父元素宽度的选项 | boolean | 无 | | +| block | 将宽度调整为父元素宽度的选项 | boolean | - | | | disabled | 是否禁用 | boolean | false | | | options | 数据化配置选项内容 | string[] \| number[] \| SegmentedOption[] | [] | | | size | 控件尺寸 | `large` \| `middle` \| `small` | - | | diff --git a/components/select/__tests__/index.test.js b/components/select/__tests__/index.test.js index e169e107f..46ee2e46f 100644 --- a/components/select/__tests__/index.test.js +++ b/components/select/__tests__/index.test.js @@ -159,6 +159,52 @@ describe('Select', () => { }, 500); }); + it('The select trigger should be blur when the panel is closed.', async () => { + const wrapper = mount( + { + render() { + return ( + ; + }} + /> + ); + }, + }, + { + sync: false, + attachTo: 'body', + }, + ); + await asyncExpect(async () => { + await wrapper.find('.ant-select-selector').trigger('mousedown'); + await wrapper.find('.ant-select-selection-search-input').trigger('focus'); + }); + + await asyncExpect(async () => { + const el = wrapper.find('.ant-select'); + + expect(el.classes()).toContain('ant-select-focused'); + $$('#dropdownRenderInput')[0].focus(); + + expect(el.classes()).toContain('ant-select-focused'); + + document.body.dispatchEvent( + new MouseEvent('mousedown', { + bubbles: true, + cancelable: true, + view: window, + }), + ); + }, 100); + + await asyncExpect(async () => { + const el = wrapper.find('.ant-select'); + expect(el.classes()).not.toContain('ant-select-focused'); + }, 200); + }); + describe('Select Custom Icons', () => { it('should support customized icons', () => { const wrapper = mount({ diff --git a/components/select/index.zh-CN.md b/components/select/index.zh-CN.md index 4de3bf8ae..7f748d9b5 100644 --- a/components/select/index.zh-CN.md +++ b/components/select/index.zh-CN.md @@ -111,7 +111,7 @@ coverDark: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*5oPiTqPxGAUAAA | 参数 | 说明 | 类型 | 默认值 | | ----- | ---- | ------------------------- | ------ | | key | | string | - | -| label | 组名 | string\|function(h)\|slot | 无 | +| label | 组名 | string\|function(h)\|slot | - | ## FAQ diff --git a/components/slider/demo/event.vue b/components/slider/demo/event.vue index a1733cb72..67b1f5df3 100644 --- a/components/slider/demo/event.vue +++ b/components/slider/demo/event.vue @@ -17,8 +17,8 @@ The `onChange` callback function will fire when the user changes the slider's va ``` -### 4. Component list +### 3. Component list [Component list](https://github.com/vueComponent/ant-design-vue/blob/main/components/components.ts) diff --git a/site/src/vueDocs/getting-started.zh-CN.md b/site/src/vueDocs/getting-started.zh-CN.md index c0f97e5e7..a26b5e0ca 100644 --- a/site/src/vueDocs/getting-started.zh-CN.md +++ b/site/src/vueDocs/getting-started.zh-CN.md @@ -12,29 +12,39 @@ Ant Design Vue 致力于提供给程序员**愉悦**的开发体验。 ## 引入 ant-design-vue -### 1. 安装脚手架工具 +### 1. 新建项目 -[vue-cli](https://github.com/vuejs/vue-cli) +如果你需要新建一个项目,可以使用 [Vite](https://github.com/vitejs/vite)、[Rsbuild](https://github.com/web-infra-dev/rsbuild) 或 [Vue CLI](https://github.com/vuejs/vue-cli)。 + +请使用命令行来初始化项目: + +- Vite: + +```bash +$ npm create vite@latest +``` + +- Rsbuild: + +```bash +$ npm create rsbuild@latest +``` + +- Vue CLI: ```bash $ npm install -g @vue/cli # OR $ yarn global add @vue/cli -``` -### 2. 创建一个项目 - -使用命令行进行初始化。 - -```bash $ vue create antd-demo ``` -并配置项目。 +> Vue CLI 已经停止迭代,因此不推荐使用。 若安装缓慢报错,可尝试用 `cnpm` 或别的镜像源自行安装:`rm -rf node_modules && cnpm install`。 -### 3. 使用组件 +### 2. 使用组件 #### 安装 diff --git a/site/src/vueDocs/migration-v4.en-US.md b/site/src/vueDocs/migration-v4.en-US.md index a24d9a25b..2c5fe6b69 100644 --- a/site/src/vueDocs/migration-v4.en-US.md +++ b/site/src/vueDocs/migration-v4.en-US.md @@ -153,7 +153,7 @@ Remove `babel-plugin-import` from package.json and modify `.babelrc`: ### Legacy browser support -Ant Design Vue v4 using `:where` css selector to reduce CSS-in-JS hash priority. You can use `StyleProvider` to cancel this function. Please ref [Compatible adjustment](/docs/vue/customize-theme#compatible-adjustment). +Ant Design Vue v4 using `:where` css selector to reduce CSS-in-JS hash priority. You can use `StyleProvider` to cancel this function. Please ref [Compatible adjustment](/docs/vue/compatible-style). ## Encounter problems diff --git a/site/src/vueDocs/migration-v4.zh-CN.md b/site/src/vueDocs/migration-v4.zh-CN.md index 3b9c984ee..15b596225 100644 --- a/site/src/vueDocs/migration-v4.zh-CN.md +++ b/site/src/vueDocs/migration-v4.zh-CN.md @@ -155,7 +155,7 @@ or ### 旧版浏览器兼容 -Ant Design Vue v4 使用 `:where` css selector 降低 CSS-in-JS hash 值优先级,如果你需要支持旧版本浏览器(如 IE 11、360 浏览器 等等)。可以通过 `StyleProvider` 去除降权操作。详情请参阅 [兼容性调整](/docs/vue/customize-theme-cn#兼容性调整)。 +Ant Design Vue v4 使用 `:where` css selector 降低 CSS-in-JS hash 值优先级,如果你需要支持旧版本浏览器(如 IE 11、360 浏览器 等等)。可以通过 `StyleProvider` 去除降权操作。详情请参阅 [兼容性调整](/docs/vue/compatible-style-cn)。 ## 遇到问题