Merge remote-tracking branch 'origin/main' into feat-4.3

pull/7940/head
tangjinzhou 2024-11-08 11:22:03 +08:00
commit e7ec210451
86 changed files with 709 additions and 612 deletions

View File

@ -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 ## 4.2.1
- 🐞 fix Input clear action error [#7523](https://github.com/vueComponent/ant-design-vue/issues/7523) - 🐞 fix Input clear action error [#7523](https://github.com/vueComponent/ant-design-vue/issues/7523)

View File

@ -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 ## 4.2.1
- 🐞 修复 Input 清空操作才报错问题 [#7523](https://github.com/vueComponent/ant-design-vue/issues/7523) - 🐞 修复 Input 清空操作才报错问题 [#7523](https://github.com/vueComponent/ant-design-vue/issues/7523)

View File

@ -10,7 +10,7 @@
<div align="center"> <div align="center">
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) ![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) - [支付宝或微信](https://aliyuncdn.antdv.com/alipay-and-wechat.png)
- ETH: 0x30cc48515d8ae9fefa20ab87226ad7e8ab9c3bc2 - 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)]
<a href="http://www.jeecg.com/" target="_blank"><img src="https://aliyuncdn.antdv.com/jeecg-logo.png" height="64"></a> <a href="https://opencollective.com/ant-design-vue/sponsor/0/website" target="_blank"><img src="https://opencollective.com/ant-design-vue/sponsor/0/avatar.svg"></a> <a href="https://opencollective.com/ant-design-vue/sponsor/1/website" target="_blank"><img src="https://opencollective.com/ant-design-vue/sponsor/1/avatar.svg"></a> <a href="https://opencollective.com/ant-design-vue/sponsor/2/website" target="_blank"><img src="https://opencollective.com/ant-design-vue/sponsor/2/avatar.svg"></a> <a href="https://opencollective.com/ant-design-vue/sponsor/3/website" target="_blank"><img src="https://opencollective.com/ant-design-vue/sponsor/3/avatar.svg"></a> <a href="https://opencollective.com/ant-design-vue/sponsor/4/website" target="_blank"><img src="https://opencollective.com/ant-design-vue/sponsor/4/avatar.svg"></a> <a href="https://opencollective.com/ant-design-vue/sponsor/5/website" target="_blank"><img src="https://opencollective.com/ant-design-vue/sponsor/5/avatar.svg"></a> <a href="https://opencollective.com/ant-design-vue/sponsor/6/website" target="_blank"><img src="https://opencollective.com/ant-design-vue/sponsor/6/avatar.svg"></a> <a href="https://opencollective.com/ant-design-vue/sponsor/7/website" target="_blank"><img src="https://opencollective.com/ant-design-vue/sponsor/7/avatar.svg"></a> <a href="https://opencollective.com/ant-design-vue/sponsor/8/website" target="_blank"><img src="https://opencollective.com/ant-design-vue/sponsor/8/avatar.svg"></a> <a href="https://opencollective.com/ant-design-vue/sponsor/9/website" target="_blank"><img src="https://opencollective.com/ant-design-vue/sponsor/9/avatar.svg"></a> <a href="https://opencollective.com/ant-design-vue/sponsor/10/website" target="_blank"><img src="https://opencollective.com/ant-design-vue/sponsor/10/avatar.svg"></a> <a href="http://www.jeecg.com/" target="_blank"><img src="https://aliyuncdn.antdv.com/jeecg-logo.png" height="64"></a> <a href="https://opencollective.com/ant-design-vue/sponsor/0/website" target="_blank"><img src="https://opencollective.com/ant-design-vue/sponsor/0/avatar.svg"></a> <a href="https://opencollective.com/ant-design-vue/sponsor/1/website" target="_blank"><img src="https://opencollective.com/ant-design-vue/sponsor/1/avatar.svg"></a> <a href="https://opencollective.com/ant-design-vue/sponsor/2/website" target="_blank"><img src="https://opencollective.com/ant-design-vue/sponsor/2/avatar.svg"></a> <a href="https://opencollective.com/ant-design-vue/sponsor/3/website" target="_blank"><img src="https://opencollective.com/ant-design-vue/sponsor/3/avatar.svg"></a> <a href="https://opencollective.com/ant-design-vue/sponsor/4/website" target="_blank"><img src="https://opencollective.com/ant-design-vue/sponsor/4/avatar.svg"></a> <a href="https://opencollective.com/ant-design-vue/sponsor/5/website" target="_blank"><img src="https://opencollective.com/ant-design-vue/sponsor/5/avatar.svg"></a> <a href="https://opencollective.com/ant-design-vue/sponsor/6/website" target="_blank"><img src="https://opencollective.com/ant-design-vue/sponsor/6/avatar.svg"></a> <a href="https://opencollective.com/ant-design-vue/sponsor/7/website" target="_blank"><img src="https://opencollective.com/ant-design-vue/sponsor/7/avatar.svg"></a> <a href="https://opencollective.com/ant-design-vue/sponsor/8/website" target="_blank"><img src="https://opencollective.com/ant-design-vue/sponsor/8/avatar.svg"></a> <a href="https://opencollective.com/ant-design-vue/sponsor/9/website" target="_blank"><img src="https://opencollective.com/ant-design-vue/sponsor/9/avatar.svg"></a> <a href="https://opencollective.com/ant-design-vue/sponsor/10/website" target="_blank"><img src="https://opencollective.com/ant-design-vue/sponsor/10/avatar.svg"></a>
## 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)]
<a href="https://github.com/chuzhixin/vue-admin-beautiful" target="_blank"><img width="64" style="border-radius: 50%;" src="https://gitee.com/chu1204505056/image/raw/master/vue-admin-beautiful.png" title="vue-admin-beautiful"></a> <a href="https://opencollective.com/ant-design-vue/backer/0/website" target="_blank"><img src="https://opencollective.com/ant-design-vue/backer/0/avatar.svg"></a> <a href="https://opencollective.com/ant-design-vue/backer/1/website" target="_blank"><img src="https://opencollective.com/ant-design-vue/backer/1/avatar.svg"></a> <a href="https://opencollective.com/ant-design-vue/backer/2/website" target="_blank"><img src="https://opencollective.com/ant-design-vue/backer/2/avatar.svg"></a> <a href="https://opencollective.com/ant-design-vue/backer/3/website" target="_blank"><img src="https://opencollective.com/ant-design-vue/backer/3/avatar.svg"></a> <a href="https://opencollective.com/ant-design-vue/backer/4/website" target="_blank"><img src="https://opencollective.com/ant-design-vue/backer/4/avatar.svg"></a> <a href="https://opencollective.com/ant-design-vue/backer/5/website" target="_blank"><img src="https://opencollective.com/ant-design-vue/backer/5/avatar.svg"></a> <a href="https://opencollective.com/ant-design-vue/backer/6/website" target="_blank"><img src="https://opencollective.com/ant-design-vue/backer/6/avatar.svg"></a> <a href="https://opencollective.com/ant-design-vue/backer/7/website" target="_blank"><img src="https://opencollective.com/ant-design-vue/backer/7/avatar.svg"></a> <a href="https://opencollective.com/ant-design-vue/backer/8/website" target="_blank"><img src="https://opencollective.com/ant-design-vue/backer/8/avatar.svg"></a> <a href="https://opencollective.com/ant-design-vue/backer/9/website" target="_blank"><img src="https://opencollective.com/ant-design-vue/backer/9/avatar.svg"></a> <a href="https://opencollective.com/ant-design-vue/backer/10/website" target="_blank"><img src="https://opencollective.com/ant-design-vue/backer/10/avatar.svg"></a> <a href="https://github.com/chuzhixin/vue-admin-beautiful" target="_blank"><img width="64" style="border-radius: 50%;" src="https://gitee.com/chu1204505056/image/raw/master/vue-admin-beautiful.png" title="vue-admin-beautiful"></a> <a href="https://opencollective.com/ant-design-vue/backer/0/website" target="_blank"><img src="https://opencollective.com/ant-design-vue/backer/0/avatar.svg"></a> <a href="https://opencollective.com/ant-design-vue/backer/1/website" target="_blank"><img src="https://opencollective.com/ant-design-vue/backer/1/avatar.svg"></a> <a href="https://opencollective.com/ant-design-vue/backer/2/website" target="_blank"><img src="https://opencollective.com/ant-design-vue/backer/2/avatar.svg"></a> <a href="https://opencollective.com/ant-design-vue/backer/3/website" target="_blank"><img src="https://opencollective.com/ant-design-vue/backer/3/avatar.svg"></a> <a href="https://opencollective.com/ant-design-vue/backer/4/website" target="_blank"><img src="https://opencollective.com/ant-design-vue/backer/4/avatar.svg"></a> <a href="https://opencollective.com/ant-design-vue/backer/5/website" target="_blank"><img src="https://opencollective.com/ant-design-vue/backer/5/avatar.svg"></a> <a href="https://opencollective.com/ant-design-vue/backer/6/website" target="_blank"><img src="https://opencollective.com/ant-design-vue/backer/6/avatar.svg"></a> <a href="https://opencollective.com/ant-design-vue/backer/7/website" target="_blank"><img src="https://opencollective.com/ant-design-vue/backer/7/avatar.svg"></a> <a href="https://opencollective.com/ant-design-vue/backer/8/website" target="_blank"><img src="https://opencollective.com/ant-design-vue/backer/8/avatar.svg"></a> <a href="https://opencollective.com/ant-design-vue/backer/9/website" target="_blank"><img src="https://opencollective.com/ant-design-vue/backer/9/avatar.svg"></a> <a href="https://opencollective.com/ant-design-vue/backer/10/website" target="_blank"><img src="https://opencollective.com/ant-design-vue/backer/10/avatar.svg"></a>
## Patreon ## 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)]
<a href="https://www.mokeyjay.com" target="_blank"><img width="64" style="border-radius: 50%;" src="https://www.mokeyjay.com/headimg.png" title="donation by Patreon"></a> <a href="https://www.mokeyjay.com" target="_blank"><img width="64" style="border-radius: 50%;" src="https://www.mokeyjay.com/headimg.png" title="donation by Patreon"></a>

View File

@ -1,6 +1,9 @@
import type { PropType } from 'vue'; 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 PropTypes from './vue-types';
import type { BaseInputInnerExpose } from './BaseInputInner';
import BaseInputInner from './BaseInputInner';
import { styleObjectToString } from '../vc-util/Dom/css';
export interface BaseInputExpose { export interface BaseInputExpose {
focus: () => void; focus: () => void;
@ -30,6 +33,8 @@ const BaseInput = defineComponent({
default: 'input', default: 'input',
}, },
size: PropTypes.string, size: PropTypes.string,
style: PropTypes.oneOfType([String, Object]),
class: PropTypes.string,
}, },
emits: [ emits: [
'change', 'change',
@ -40,9 +45,11 @@ const BaseInput = defineComponent({
'compositionstart', 'compositionstart',
'compositionend', 'compositionend',
'keyup', 'keyup',
'paste',
'mousedown',
], ],
setup(props, { emit, attrs, expose }) { setup(props, { emit, attrs, expose }) {
const inputRef = shallowRef(null); const inputRef = shallowRef<BaseInputInnerExpose>(null);
const renderValue = ref(); const renderValue = ref();
const isComposing = ref(false); const isComposing = ref(false);
watch( watch(
@ -68,6 +75,7 @@ const BaseInput = defineComponent({
const event = document.createEvent('HTMLEvents'); const event = document.createEvent('HTMLEvents');
event.initEvent('input', true, true); event.initEvent('input', true, true);
e.target.dispatchEvent(event); e.target.dispatchEvent(event);
handleChange(e);
}; };
const handleInput = (e: Event) => { const handleInput = (e: Event) => {
if (isComposing.value && props.lazy) { if (isComposing.value && props.lazy) {
@ -114,19 +122,31 @@ const BaseInput = defineComponent({
expose({ expose({
focus, focus,
blur, blur,
input: inputRef, input: computed(() => inputRef.value?.input),
setSelectionRange, setSelectionRange,
select, select,
getSelectionStart: () => inputRef.value?.selectionStart, getSelectionStart: () => inputRef.value?.getSelectionStart(),
getSelectionEnd: () => inputRef.value?.selectionEnd, getSelectionEnd: () => inputRef.value?.getSelectionEnd(),
getScrollTop: () => inputRef.value?.scrollTop, 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 () => { return () => {
const { tag: Tag, ...restProps } = props; const { style, lazy, ...restProps } = props;
return ( return (
<Tag <BaseInputInner
{...restProps} {...restProps}
{...attrs} {...attrs}
style={styleString.value}
onInput={handleInput} onInput={handleInput}
onChange={handleChange} onChange={handleChange}
onBlur={handleBlur} onBlur={handleBlur}
@ -137,6 +157,8 @@ const BaseInput = defineComponent({
onCompositionend={onCompositionend} onCompositionend={onCompositionend}
onKeyup={handleKeyUp} onKeyup={handleKeyUp}
onKeydown={handleKeyDown} onKeydown={handleKeyDown}
onPaste={handlePaste}
onMousedown={handleMousedown}
/> />
); );
}; };

View File

@ -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 <Tag {...restProps} ref={inputRef} value={value} />;
};
},
});
export default BaseInputInner;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -5,7 +5,7 @@ import type {
TransitionGroupProps, TransitionGroupProps,
TransitionProps, TransitionProps,
} from 'vue'; } from 'vue';
import { nextTick, Transition, TransitionGroup } from 'vue'; import { nextTick } from 'vue';
import { tuple } from './type'; import { tuple } from './type';
const SelectPlacements = tuple('bottomLeft', 'bottomRight', 'topLeft', 'topRight'); const SelectPlacements = tuple('bottomLeft', 'bottomRight', 'topLeft', 'topRight');
@ -126,6 +126,4 @@ const getTransitionName = (rootPrefixCls: string, motion: string, transitionName
return `${rootPrefixCls}-${motion}`; return `${rootPrefixCls}-${motion}`;
}; };
export { Transition, TransitionGroup, collapseMotion, getTransitionName, getTransitionDirection }; export { collapseMotion, getTransitionName, getTransitionDirection };
export default Transition;

View File

@ -159,6 +159,12 @@ function showWaveEffect(node: HTMLElement, className: string) {
node?.insertBefore(holder, node?.firstChild); node?.insertBefore(holder, node?.firstChild);
render(<WaveEffect target={node} className={className} />, holder); render(<WaveEffect target={node} className={className} />, holder);
return () => {
render(null, holder);
if (holder.parentElement) {
holder.parentElement.removeChild(holder);
}
};
} }
export default showWaveEffect; export default showWaveEffect;

View File

@ -33,13 +33,12 @@ export default defineComponent({
// =============================== Wave =============================== // =============================== Wave ===============================
const showWave = useWave( const showWave = useWave(
instance,
computed(() => classNames(prefixCls.value, hashId.value)), computed(() => classNames(prefixCls.value, hashId.value)),
wave, wave,
); );
let onClick: (e: MouseEvent) => void; let onClick: (e: MouseEvent) => void;
const clear = () => { const clear = () => {
const node = findDOMNode(instance); const node = findDOMNode(instance) as HTMLElement;
node.removeEventListener('click', onClick, true); node.removeEventListener('click', onClick, true);
}; };
onMounted(() => { onMounted(() => {

View File

@ -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 { findDOMNode } from '../props-util';
import showWaveEffect from './WaveEffect'; import showWaveEffect from './WaveEffect';
export default function useWave( export default function useWave(
instance: ComponentInternalInstance | null,
className: Ref<string>, className: Ref<string>,
wave?: ComputedRef<{ disabled?: boolean }>, wave?: ComputedRef<{ disabled?: boolean }>,
): VoidFunction { ): VoidFunction {
const instance = getCurrentInstance();
let stopWave: () => void;
function showWave() { function showWave() {
const node = findDOMNode(instance); const node = findDOMNode(instance);
stopWave?.();
if (wave?.value?.disabled || !node) { if (wave?.value?.disabled || !node) {
return; return;
} }
stopWave = showWaveEffect(node, className.value);
showWaveEffect(node, className.value);
} }
onBeforeUnmount(() => {
stopWave?.();
});
return showWave; return showWave;
} }

View File

@ -1,5 +1,5 @@
import type { CSSProperties, ExtractPropTypes, PropType } from 'vue'; 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 CloseOutlined from '@ant-design/icons-vue/CloseOutlined';
import CheckCircleOutlined from '@ant-design/icons-vue/CheckCircleOutlined'; import CheckCircleOutlined from '@ant-design/icons-vue/CheckCircleOutlined';
import ExclamationCircleOutlined from '@ant-design/icons-vue/ExclamationCircleOutlined'; 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 CloseCircleFilled from '@ant-design/icons-vue/CloseCircleFilled';
import classNames from '../_util/classNames'; import classNames from '../_util/classNames';
import PropTypes from '../_util/vue-types'; import PropTypes from '../_util/vue-types';
import { getTransitionProps, Transition } from '../_util/transition'; import { getTransitionProps } from '../_util/transition';
import { isValidElement } from '../_util/props-util'; import { isValidElement } from '../_util/props-util';
import { tuple, withInstall } from '../_util/type'; import { tuple, withInstall } from '../_util/type';
import { cloneElement } from '../_util/vnode'; import { cloneElement } from '../_util/vnode';

View File

@ -3,9 +3,9 @@ import ScrollNumber from './ScrollNumber';
import classNames from '../_util/classNames'; import classNames from '../_util/classNames';
import { getPropsSlot, flattenChildren } from '../_util/props-util'; import { getPropsSlot, flattenChildren } from '../_util/props-util';
import { cloneElement } from '../_util/vnode'; import { cloneElement } from '../_util/vnode';
import { getTransitionProps, Transition } from '../_util/transition'; import { getTransitionProps } from '../_util/transition';
import type { ExtractPropTypes, CSSProperties, PropType } from 'vue'; 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 Ribbon from './Ribbon';
import useConfigInject from '../config-provider/hooks/useConfigInject'; import useConfigInject from '../config-provider/hooks/useConfigInject';
import isNumeric from '../_util/isNumeric'; import isNumeric from '../_util/isNumeric';

View File

@ -1,6 +1,5 @@
import { defineComponent, nextTick } from 'vue'; import { defineComponent, nextTick, Transition } from 'vue';
import LoadingOutlined from '@ant-design/icons-vue/LoadingOutlined'; import LoadingOutlined from '@ant-design/icons-vue/LoadingOutlined';
import Transition from '../_util/transition';
const getCollapsedWidth = (node: HTMLSpanElement) => { const getCollapsedWidth = (node: HTMLSpanElement) => {
if (node) { if (node) {
node.style.width = '0px'; node.style.width = '0px';

View File

@ -28,16 +28,16 @@ coverDark: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*-p-wQLik200AAA
| 参数 | 说明 | 类型 | 默认值 | 版本 | | 参数 | 说明 | 类型 | 默认值 | 版本 |
| --- | --- | --- | --- | --- | | --- | --- | --- | --- | --- |
| dateCellRender | 作用域插槽,用来自定义渲染日期单元格,返回内容会被追加到单元格, | v-slot:dateCellRender="{current: dayjs}" | | | | dateCellRender | 作用域插槽,用来自定义渲染日期单元格,返回内容会被追加到单元格, | v-slot:dateCellRender="{current: dayjs}" | - | |
| dateFullCellRender | 作用域插槽,自定义渲染日期单元格,返回内容覆盖单元格 | v-slot:dateFullCellRender="{current: dayjs}" | | | | dateFullCellRender | 作用域插槽,自定义渲染日期单元格,返回内容覆盖单元格 | v-slot:dateFullCellRender="{current: dayjs}" | - | |
| disabledDate | 不可选择的日期 | (currentDate: dayjs) => boolean | | | | disabledDate | 不可选择的日期 | (currentDate: dayjs) => boolean | - | |
| fullscreen | 是否全屏显示 | boolean | true | | | fullscreen | 是否全屏显示 | boolean | true | |
| headerRender | 自定义头部内容 | v-slot:headerRender="{value: dayjs, type: string, onChange: f(), onTypeChange: f()}" | - | | | 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) | | | locale | 国际化配置 | object | [默认配置](https://github.com/vueComponent/ant-design-vue/blob/main/components/date-picker/locale/example.json) | |
| mode | 初始模式,`month/year` | string | month | | | mode | 初始模式,`month/year` | string | month | |
| monthCellRender | 作用域插槽,自定义渲染月单元格,返回内容会被追加到单元格 | v-slot:monthCellRender="{current: dayjs}" | | | | monthCellRender | 作用域插槽,自定义渲染月单元格,返回内容会被追加到单元格 | v-slot:monthCellRender="{current: dayjs}" | - | |
| monthFullCellRender | 作用域插槽,自定义渲染月单元格,返回内容覆盖单元格 | v-slot:monthFullCellRender="{current: dayjs}" | | | | monthFullCellRender | 作用域插槽,自定义渲染月单元格,返回内容覆盖单元格 | v-slot:monthFullCellRender="{current: dayjs}" | - | |
| validRange | 设置可以显示的日期 | \[[dayjs](https://day.js.org/), [dayjs](https://day.js.org/)] | | | | validRange | 设置可以显示的日期 | \[[dayjs](https://day.js.org/), [dayjs](https://day.js.org/)] | - | |
| value(v-model) | 展示日期 | [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) | - | | | 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 | | | change | 日期变化时的回调, 面板变化有可能导致日期变化 | function(date: dayjs \| string | - |
| panelChange | 日期面板变化回调 | function(date: dayjs \| string, mode: string) | | | panelChange | 日期面板变化回调 | function(date: dayjs \| string, mode: string) | - |
| select | 选择日期回调,包含来源信息 | function(date: Dayjs, info: { source: 'year' \| 'month' \| 'date' \| 'customize' }) | - | | | select | 选择日期回调,包含来源信息 | function(date: Dayjs, info: { source: 'year' \| 'month' \| 'date' \| 'customize' }) | - | |
### 如何仅获取来自面板点击的日期? ### 如何仅获取来自面板点击的日期?

View File

@ -2,8 +2,7 @@ import PanelContent from './PanelContent';
import { initDefaultProps } from '../_util/props-util'; import { initDefaultProps } from '../_util/props-util';
import { collapsePanelProps } from './commonProps'; import { collapsePanelProps } from './commonProps';
import type { ExtractPropTypes } from 'vue'; import type { ExtractPropTypes } from 'vue';
import { defineComponent } from 'vue'; import { defineComponent, Transition } from 'vue';
import Transition from '../_util/transition';
import classNames from '../_util/classNames'; import classNames from '../_util/classNames';
import devWarning from '../vc-util/devWarning'; import devWarning from '../vc-util/devWarning';
import useConfigInject from '../config-provider/hooks/useConfigInject'; import useConfigInject from '../config-provider/hooks/useConfigInject';

View File

@ -20,7 +20,7 @@ A content area which can be collapsed and expanded.
| Property | Description | Type | Default | Version | | Property | Description | Type | Default | Version |
| --- | --- | --- | --- | --- | | --- | --- | --- | --- | --- |
| accordion | If `true`, `Collapse` renders as `Accordion` | boolean | `false` | | | accordion | If `true`, `Collapse` renders as `Accordion` | boolean | `false` | |
| activeKey(v-model) | Key of the active panel | string\[] \| string <br> 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 <br> 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` | | | 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 | | 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` | | | destroyInactivePanel | Destroy Inactive Panel | boolean | `false` | |

View File

@ -20,8 +20,8 @@ coverDark: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*sir-TK0HkWcAAA
| 参数 | 说明 | 类型 | 默认值 | 版本 | | 参数 | 说明 | 类型 | 默认值 | 版本 |
| --- | --- | --- | --- | --- | | --- | --- | --- | --- | --- |
| accordion | 手风琴模式 | boolean | `false` | | | accordion | 手风琴模式,始终只有一个面板处在激活状态 | boolean | `false` | |
| activeKey(v-model) | 当前激活 tab 面板的 key | string\[] \| string <br> number\[] \| number | 默认无,accordion 模式下默认第一个元素 | | | activeKey(v-model) | 当前激活 tab 面板的 key | string\[] \| string <br> number\[] \| number | 默认无,[手风琴模式](#components-collapse-demo-accordion)下默认第一个元素 | |
| bordered | 带边框风格的折叠面板 | boolean | `true` | | | bordered | 带边框风格的折叠面板 | boolean | `true` | |
| collapsible | 所有子面板是否可折叠或指定可折叠触发区域 | `header` \| `icon` \| `disabled` | - | 4.0 | | collapsible | 所有子面板是否可折叠或指定可折叠触发区域 | `header` \| `icon` \| `disabled` | - | 4.0 |
| destroyInactivePanel | 销毁折叠隐藏的面板 | boolean | `false` | | | destroyInactivePanel | 销毁折叠隐藏的面板 | boolean | `false` | |
@ -42,6 +42,6 @@ coverDark: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*sir-TK0HkWcAAA
| collapsible | 是否可折叠或指定可折叠触发区域 | `header` \| `disabled` | - | 3.0 | | collapsible | 是否可折叠或指定可折叠触发区域 | `header` \| `disabled` | - | 3.0 |
| extra | 自定义渲染每个面板右上角的内容 | VNode \| slot | - | 1.5.0 | | extra | 自定义渲染每个面板右上角的内容 | VNode \| slot | - | 1.5.0 |
| forceRender | 被隐藏时是否渲染 DOM 结构 | boolean | false | | | forceRender | 被隐藏时是否渲染 DOM 结构 | boolean | false | |
| header | 面板头内容 | string\|slot | | | | header | 面板头内容 | string\|slot | - | |
| key | 对应 activeKey | string \| number | | | | key | 对应 activeKey | string \| number | - | |
| showArrow | 是否展示当前面板上的箭头 | boolean | `true` | | | showArrow | 是否展示当前面板上的箭头 | boolean | `true` | |

View File

@ -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 { watch, computed, reactive, defineComponent, watchEffect } from 'vue';
import defaultRenderEmpty from './renderEmpty'; import defaultRenderEmpty from './renderEmpty';
import type { RenderEmptyHandler } 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 LocaleReceiver from '../locale-provider/LocaleReceiver';
import type { MaybeRef } from '../_util/type';
import message from '../message'; import message from '../message';
import notification from '../notification'; import notification from '../notification';
import { registerTheme } from './cssVariables'; import { registerTheme } from './cssVariables';

View File

@ -85,7 +85,7 @@ The following APIs are shared by DatePicker, RangePicker.
| disabled | Determine whether the DatePicker is disabled | boolean | false | | | disabled | Determine whether the DatePicker is disabled | boolean | false | |
| disabledDate | Specify the date that cannot be selected | (currentDate: dayjs) => boolean | - | | | 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` | | | 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) | - | | | 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 | | | 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) | | | locale | Localization configuration | object | [default](https://github.com/vueComponent/ant-design-vue/blob/main/components/date-picker/locale/example.json) | |

View File

@ -86,7 +86,7 @@ coverDark: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*3OpRQKcygo8AAA
| disabled | 禁用 | boolean | false | | | disabled | 禁用 | boolean | false | |
| disabledDate | 不可选择的日期 | (currentDate: dayjs) => boolean | - | | | 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` | | | 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) | - | | | getPopupContainer | 定义浮层的容器,默认为 body 上新建 div | function(trigger) | - | |
| inputReadOnly | 设置输入框为只读(避免在移动设备上打开虚拟键盘) | boolean | false | | | inputReadOnly | 设置输入框为只读(避免在移动设备上打开虚拟键盘) | boolean | false | |
| locale | 国际化配置 | object | [默认配置](https://github.com/vueComponent/ant-design-vue/blob/main/components/date-picker/locale/example.json) | - | | locale | 国际化配置 | object | [默认配置](https://github.com/vueComponent/ant-design-vue/blob/main/components/date-picker/locale/example.json) | - |

View File

@ -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 | | arrow | 下拉框箭头是否显示 | boolean \| { pointAtCenter: boolean } | false | 3.3.0 |
| destroyPopupOnHide | 关闭后是否销毁 Dropdown | boolean | false | 3.0 | | destroyPopupOnHide | 关闭后是否销毁 Dropdown | boolean | false | 3.0 |
| disabled | 菜单是否禁用 | boolean | - | | | disabled | 菜单是否禁用 | boolean | - | |

View File

@ -1,4 +1,4 @@
import { defineComponent } from 'vue'; import { defineComponent, h } from 'vue';
import type { CSSProperties, ExtractPropTypes } from 'vue'; import type { CSSProperties, ExtractPropTypes } from 'vue';
import classNames from '../_util/classNames'; import classNames from '../_util/classNames';
import LocaleReceiver from '../locale-provider/LocaleReceiver'; import LocaleReceiver from '../locale-provider/LocaleReceiver';
@ -11,9 +11,6 @@ import useConfigInject from '../config-provider/hooks/useConfigInject';
import useStyle from './style'; import useStyle from './style';
const defaultEmptyImg = <DefaultEmptyImg />;
const simpleEmptyImg = <SimpleEmptyImg />;
interface Locale { interface Locale {
description?: string; description?: string;
} }
@ -40,13 +37,16 @@ const Empty = defineComponent({
return () => { return () => {
const prefixCls = prefixClsRef.value; const prefixCls = prefixClsRef.value;
const { const {
image = slots.image?.() || defaultEmptyImg, image: mergedImage = slots.image?.() || h(DefaultEmptyImg),
description = slots.description?.() || undefined, description = slots.description?.() || undefined,
imageStyle, imageStyle,
class: className = '', class: className = '',
...restProps ...restProps
} = { ...props, ...attrs }; } = { ...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( return wrapSSR(
<LocaleReceiver <LocaleReceiver
componentName="Empty" componentName="Empty"
@ -64,7 +64,7 @@ const Empty = defineComponent({
return ( return (
<div <div
class={classNames(prefixCls, className, hashId.value, { class={classNames(prefixCls, className, hashId.value, {
[`${prefixCls}-normal`]: image === simpleEmptyImg, [`${prefixCls}-normal`]: isNormal,
[`${prefixCls}-rtl`]: direction.value === 'rtl', [`${prefixCls}-rtl`]: direction.value === 'rtl',
})} })}
{...restProps} {...restProps}
@ -85,7 +85,7 @@ const Empty = defineComponent({
}, },
}); });
Empty.PRESENTED_IMAGE_DEFAULT = defaultEmptyImg; Empty.PRESENTED_IMAGE_DEFAULT = () => h(DefaultEmptyImg);
Empty.PRESENTED_IMAGE_SIMPLE = simpleEmptyImg; Empty.PRESENTED_IMAGE_SIMPLE = () => h(SimpleEmptyImg);
export default withInstall(Empty); export default withInstall(Empty);

View File

@ -1,5 +1,5 @@
import VerticalAlignTopOutlined from '@ant-design/icons-vue/VerticalAlignTopOutlined'; import VerticalAlignTopOutlined from '@ant-design/icons-vue/VerticalAlignTopOutlined';
import { getTransitionProps, Transition } from '../_util/transition'; import { getTransitionProps } from '../_util/transition';
import { import {
defineComponent, defineComponent,
nextTick, nextTick,
@ -10,6 +10,7 @@ import {
ref, ref,
watch, watch,
onDeactivated, onDeactivated,
Transition,
} from 'vue'; } from 'vue';
import FloatButton, { floatButtonPrefixCls } from './FloatButton'; import FloatButton, { floatButtonPrefixCls } from './FloatButton';
import useConfigInject from '../config-provider/hooks/useConfigInject'; import useConfigInject from '../config-provider/hooks/useConfigInject';

View File

@ -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 CloseOutlined from '@ant-design/icons-vue/CloseOutlined';
import FileTextOutlined from '@ant-design/icons-vue/FileTextOutlined'; import FileTextOutlined from '@ant-design/icons-vue/FileTextOutlined';
import classNames from '../_util/classNames'; import classNames from '../_util/classNames';
import { getTransitionProps, Transition } from '../_util/transition'; import { getTransitionProps } from '../_util/transition';
import FloatButton, { floatButtonPrefixCls } from './FloatButton'; import FloatButton, { floatButtonPrefixCls } from './FloatButton';
import useConfigInject from '../config-provider/hooks/useConfigInject'; import useConfigInject from '../config-provider/hooks/useConfigInject';
import { useProvideFloatButtonGroupContext } from './context'; import { useProvideFloatButtonGroupContext } from './context';

View File

@ -33,11 +33,11 @@ tag: New
| target | 相当于 a 标签的 target 属性href 存在时生效 | string | - | | | target | 相当于 a 标签的 target 属性href 存在时生效 | string | - | |
| badge | 带徽标数字的悬浮按钮(不支持 status 以及相关属性) | [BadgeProps](/components/badge-cn#api) | - | | | 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 ### FloatButton.Group
@ -47,7 +47,7 @@ tag: New
| trigger | 触发方式(有触发方式为菜单模式) | `click` \| `hover` | - | | | trigger | 触发方式(有触发方式为菜单模式) | `click` \| `hover` | - | |
| open(v-model) | 受控展开 | boolean | - | | | open(v-model) | 受控展开 | boolean | - | |
### FloatButton.Group Events ### FloatButton.Group 事件
| 事件名称 | 说明 | 回调参数 | 版本 | | 事件名称 | 说明 | 回调参数 | 版本 |
| ---------- | ---------------- | ----------------------- | ---- | | ---------- | ---------------- | ----------------------- | ---- |

View File

@ -20,7 +20,7 @@ export const floatButtonProps = () => {
shape: stringType<FloatButtonShape>('circle'), shape: stringType<FloatButtonShape>('circle'),
tooltip: PropTypes.any, tooltip: PropTypes.any,
href: String, href: String,
target: functionType<() => Window | HTMLElement | null>(), target: String,
badge: objectType<FloatButtonBadgeProps>(), badge: objectType<FloatButtonBadgeProps>(),
onClick: functionType<MouseEventHandler>(), onClick: functionType<MouseEventHandler>(),
}; };

View File

@ -494,7 +494,7 @@ export default defineComponent({
> >
<Row <Row
{...attrs} {...attrs}
class={`${prefixCls.value}-row`} class={`${prefixCls.value}-item-row`}
key="row" key="row"
v-slots={{ v-slots={{
default: () => ( default: () => (

View File

@ -52,6 +52,10 @@ const genGridRowStyle: GenerateStyle<GridRowToken> = (token): CSSObject => {
justifyContent: 'space-around', justifyContent: 'space-around',
}, },
'&-space-evenly ': {
justifyContent: 'space-evenly',
},
// Align at the top // Align at the top
'&-top': { '&-top': {
alignItems: 'flex-start', alignItems: 'flex-start',

View File

@ -122,6 +122,8 @@ See [iconfont.cn documents](http://iconfont.cn/help/detail?spm=a313x.7781069.199
### Custom SVG Icon ### 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). 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 ```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', '<svg v-on="$listeners"');
}
callback(null, `<template>${svg}</template>`);
};
```
```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: The following properties are available for the component:
| Property | Description | Type | Default | | Property | Description | Type | Default |

View File

@ -119,7 +119,9 @@ export default defineComponent({
### 自定义 SVG 图标 ### 自定义 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 ```js
// vue.config.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', '<svg v-on="$listeners"');
}
callback(null, `<template>${svg}</template>`);
};
```
```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` 组件的接受的属性如下: `Icon` 中的 `component` 组件的接受的属性如下:
| 字段 | 说明 | 类型 | 只读值 | | 字段 | 说明 | 类型 | 只读值 |

View File

@ -13,10 +13,10 @@ export type ImageProps = Partial<
ExtractPropTypes<ReturnType<typeof imageProps>> & ExtractPropTypes<ReturnType<typeof imageProps>> &
Omit<ImgHTMLAttributes, 'placeholder' | 'onClick'> Omit<ImgHTMLAttributes, 'placeholder' | 'onClick'>
>; >;
const Image = defineComponent<ImageProps>({ const Image = defineComponent({
name: 'AImage', name: 'AImage',
inheritAttrs: false, inheritAttrs: false,
props: imageProps() as any, props: imageProps(),
setup(props, { slots, attrs }) { setup(props, { slots, attrs }) {
const { prefixCls, rootPrefixCls, configProvider } = useConfigInject('image', props); const { prefixCls, rootPrefixCls, configProvider } = useConfigInject('image', props);
// Style // Style

View File

@ -72,7 +72,7 @@ const InputNumber = defineComponent({
const mergedSize = computed(() => compactSize.value || size.value); 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); const focused = shallowRef(false);
watch( watch(
() => props.value, () => props.value,

View File

@ -34,7 +34,7 @@ coverDark: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*1uH-R5kLAMIAAA
| parser | 指定从 formatter 里转换回数字的方式,和 formatter 搭配使用 | function( string): number | - | | | parser | 指定从 formatter 里转换回数字的方式,和 formatter 搭配使用 | function( string): number | - | |
| precision | 数值精度 | number | - | | | precision | 数值精度 | number | - | |
| prefix | 带有前缀图标的 input | slot | - | 3.0 | | prefix | 带有前缀图标的 input | slot | - | 3.0 |
| size | 输入框大小 | string | | | | size | 输入框大小 | string | - | |
| status | 设置校验状态 | 'error' \| 'warning' | - | 3.3.0 | | status | 设置校验状态 | 'error' \| 'warning' | - | 3.3.0 |
| step | 每次改变步数,可以为小数 | number\|string | 1 | | | step | 每次改变步数,可以为小数 | number\|string | 1 | |
| stringMode | 字符值模式,开启后支持高精度小数。同时 `change` 事件将返回 string 类型 | boolean | false | 3.0 | | stringMode | 字符值模式,开启后支持高精度小数。同时 `change` 事件将返回 string 类型 | boolean | false | 3.0 |

View File

@ -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 onKeyDown: KeyboardEventHandler = event => {
const { which } = event; const { which } = event;
userTypingRef.value = true; userTypingRef.value = true;
@ -577,6 +582,7 @@ export default defineComponent({
onBlur={onBlur} onBlur={onBlur}
onCompositionstart={onCompositionStart} onCompositionstart={onCompositionStart}
onCompositionend={onCompositionEnd} onCompositionend={onCompositionEnd}
onBeforeinput={onBeforeInput}
/> />
</div> </div>
</div> </div>

View File

@ -263,6 +263,10 @@ const genInputNumberStyles: GenerateStyle<InputNumberToken> = (token: InputNumbe
[`${componentCls}-handler-wrap`]: { [`${componentCls}-handler-wrap`]: {
display: 'none', display: 'none',
}, },
[`${componentCls}-input`]: {
color: 'inherit',
},
}, },
[` [`

View File

@ -59,7 +59,7 @@ export default defineComponent({
}); });
const getIcon = (prefixCls: string) => { const getIcon = (prefixCls: string) => {
const { action, iconRender = slots.iconRender || defaultIconRender } = props; const { action, iconRender = slots.iconRender || defaultIconRender } = props;
const iconTrigger = ActionMap[action!] || ''; const iconTrigger = ActionMap[action] || '';
const icon = iconRender(visible.value); const icon = iconRender(visible.value);
const iconProps = { const iconProps = {
[iconTrigger]: onVisibleChange, [iconTrigger]: onVisibleChange,

View File

@ -38,10 +38,10 @@ function setTriggerValue(
let newTriggerValue = triggerValue; let newTriggerValue = triggerValue;
if (isCursorInEnd) { if (isCursorInEnd) {
// //
newTriggerValue = fixEmojiLength(triggerValue, maxLength!); newTriggerValue = fixEmojiLength(triggerValue, maxLength);
} else if ( } else if (
[...(preValue || '')].length < triggerValue.length && [...(preValue || '')].length < triggerValue.length &&
[...(triggerValue || '')].length > maxLength! [...(triggerValue || '')].length > maxLength
) { ) {
// //
newTriggerValue = preValue; newTriggerValue = preValue;
@ -58,7 +58,7 @@ export default defineComponent({
const formItemContext = useInjectFormItemContext(); const formItemContext = useInjectFormItemContext();
const formItemInputContext = FormItemInputContext.useInject(); const formItemInputContext = FormItemInputContext.useInject();
const mergedStatus = computed(() => getMergedStatus(formItemInputContext.status, props.status)); 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 resizableTextArea = shallowRef();
const mergedValue = shallowRef(''); const mergedValue = shallowRef('');
const { prefixCls, size, direction } = useConfigInject('input', props); const { prefixCls, size, direction } = useConfigInject('input', props);
@ -79,7 +79,7 @@ export default defineComponent({
const onInternalCompositionStart = (e: CompositionEvent) => { const onInternalCompositionStart = (e: CompositionEvent) => {
compositing.value = true; compositing.value = true;
// //
oldCompositionValueRef.value = mergedValue.value as string; oldCompositionValueRef.value = mergedValue.value;
// //
oldSelectionStartRef.value = (e.currentTarget as any).selectionStart; oldSelectionStartRef.value = (e.currentTarget as any).selectionStart;
emit('compositionstart', e); emit('compositionstart', e);
@ -94,7 +94,7 @@ export default defineComponent({
oldSelectionStartRef.value === oldCompositionValueRef.value?.length; oldSelectionStartRef.value === oldCompositionValueRef.value?.length;
triggerValue = setTriggerValue( triggerValue = setTriggerValue(
isCursorInEnd, isCursorInEnd,
oldCompositionValueRef.value as string, oldCompositionValueRef.value,
triggerValue, triggerValue,
props.maxlength, props.maxlength,
); );
@ -177,14 +177,14 @@ export default defineComponent({
// 1. maxlength 2.maxlength // 1. maxlength 2.maxlength
const target = e.target as any; const target = e.target as any;
const isCursorInEnd = const isCursorInEnd =
target.selectionStart >= props.maxlength! + 1 || target.selectionStart >= props.maxlength + 1 ||
target.selectionStart === triggerValue.length || target.selectionStart === triggerValue.length ||
!target.selectionStart; !target.selectionStart;
triggerValue = setTriggerValue( triggerValue = setTriggerValue(
isCursorInEnd, isCursorInEnd,
mergedValue.value as string, mergedValue.value,
triggerValue, triggerValue,
props.maxlength!, props.maxlength,
); );
} }
resolveOnChange(e.currentTarget as any, e, triggerChange, triggerValue); resolveOnChange(e.currentTarget as any, e, triggerChange, triggerValue);
@ -237,7 +237,7 @@ export default defineComponent({
}); });
watchEffect(() => { watchEffect(() => {
let val = fixControlledValue(stateValue.value) as string; let val = fixControlledValue(stateValue.value);
if ( if (
!compositing.value && !compositing.value &&
hasMaxLength.value && hasMaxLength.value &&

View File

@ -48,9 +48,8 @@ const computedStyleCache: Record<string, NodeType> = {};
let hiddenTextarea: HTMLTextAreaElement; let hiddenTextarea: HTMLTextAreaElement;
export function calculateNodeStyling(node: HTMLElement, useCache = false) { export function calculateNodeStyling(node: HTMLElement, useCache = false) {
const nodeRef = (node.getAttribute('id') || const nodeRef =
node.getAttribute('data-reactid') || node.getAttribute('id') || node.getAttribute('data-reactid') || node.getAttribute('name');
node.getAttribute('name')) as string;
if (useCache && computedStyleCache[nodeRef]) { if (useCache && computedStyleCache[nodeRef]) {
return computedStyleCache[nodeRef]; return computedStyleCache[nodeRef];
@ -103,7 +102,7 @@ export default function calculateAutoSizeStyle(
// Fix wrap="off" issue // Fix wrap="off" issue
// https://github.com/ant-design/ant-design/issues/6577 // https://github.com/ant-design/ant-design/issues/6577
if (uiTextNode.getAttribute('wrap')) { if (uiTextNode.getAttribute('wrap')) {
hiddenTextarea.setAttribute('wrap', uiTextNode.getAttribute('wrap') as string); hiddenTextarea.setAttribute('wrap', uiTextNode.getAttribute('wrap'));
} else { } else {
hiddenTextarea.removeAttribute('wrap'); hiddenTextarea.removeAttribute('wrap');
} }

View File

@ -1,5 +1,4 @@
import { computed, defineComponent, ref, watch } from 'vue'; import { computed, Transition, defineComponent, ref, watch } from 'vue';
import Transition from '../../_util/transition';
import { useInjectMenu, MenuContextProvider } from './hooks/useMenuContext'; import { useInjectMenu, MenuContextProvider } from './hooks/useMenuContext';
import type { MenuMode } from './interface'; import type { MenuMode } from './interface';
import SubMenuList from './SubMenuList'; import SubMenuList from './SubMenuList';

View File

@ -47,7 +47,9 @@ const Holder = defineComponent({
'rtl', 'rtl',
'transitionName', 'transitionName',
'onAllRemoved', 'onAllRemoved',
] as any, 'animation',
'staticGetContainer',
],
setup(props, { expose }) { setup(props, { expose }) {
const { getPrefixCls, getPopupContainer } = useConfigInject('message', props); const { getPrefixCls, getPopupContainer } = useConfigInject('message', props);

View File

@ -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 | {} | | | bodyStyle | Modal body 样式 | object | {} | |
| cancelButtonProps | cancel 按钮 props | [ButtonProps](/components/button/#api) | - | | | cancelButtonProps | cancel 按钮 props | [ButtonProps](/components/button/#api) | - | |
| cancelText | 取消按钮文字 | string\| slot | 取消 | | | cancelText | 取消按钮文字 | string\| slot | 取消 | |
| centered | 垂直居中展示 Modal | boolean | `false` | | | centered | 垂直居中展示 Modal | boolean | `false` | |
| closable | 是否显示右上角的关闭按钮 | boolean | true | | | closable | 是否显示右上角的关闭按钮 | boolean | true | |
| closeIcon | 自定义关闭图标 | VNode \| slot | - | | | closeIcon | 自定义关闭图标 | VNode \| slot | - | |
| confirmLoading | 确定按钮 loading | boolean | | | | confirmLoading | 确定按钮 loading | boolean | - | |
| destroyOnClose | 关闭时销毁 Modal 里的子元素 | boolean | false | | | destroyOnClose | 关闭时销毁 Modal 里的子元素 | boolean | false | |
| footer | 底部内容,当不需要默认底部按钮时,可以设为 `:footer="null"` | string\|slot | 确定取消按钮 | | | footer | 底部内容,当不需要默认底部按钮时,可以设为 `:footer="null"` | string\|slot | 确定取消按钮 | |
| forceRender | 强制渲染 Modal | boolean | false | | | 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) | - | | | okButtonProps | ok 按钮 props | [ButtonProps](/components/button/#api) | - | |
| okText | 确认按钮文字 | string\|slot | 确定 | | | okText | 确认按钮文字 | string\|slot | 确定 | |
| okType | 确认按钮类型 | string | primary | | | okType | 确认按钮类型 | string | primary | |
| title | 标题 | string\|slot | | | | title | 标题 | string\|slot | - | |
| open(v-model) | 对话框是否可见 | boolean | | | | open(v-model) | 对话框是否可见 | boolean | - | |
| width | 宽度 | string\|number | 520 | | | width | 宽度 | string\|number | 520 | |
| wrapClassName | 对话框外层容器的类名 | string | - | | | wrapClassName | 对话框外层容器的类名 | string | - | |
| zIndex | 设置 Modal 的 `z-index` | number | 1000 | | | 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` | | | centered | 垂直居中展示 Modal | boolean | `false` | |
| class | 容器类名 | string | - | | | class | 容器类名 | string | - | |
| closable | 是否显示右上角的关闭按钮 | boolean | `false` | | | closable | 是否显示右上角的关闭按钮 | boolean | `false` | |
| content | 内容 | string \|VNode \|function() | | | | content | 内容 | string \|VNode \|function() | - | |
| footer | 底部内容,当不需要默认底部按钮时,可以设为 `footer: null` | string \|VNode \|function() | - | 4.0.0 | | footer | 底部内容,当不需要默认底部按钮时,可以设为 `footer: null` | string \|VNode \|function() | - | 4.0.0 |
| icon | 自定义图标1.14.0 新增) | VNode \| ()=>VNode | - | | | icon | 自定义图标1.14.0 新增) | VNode \| ()=>VNode | - | |
| keyboard | 是否支持键盘 esc 关闭 | boolean | true | | | 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) | - | | | okButtonProps | ok 按钮 props | [ButtonProps](/components/button) | - | |
| okText | 确认按钮文字 | string | 确定 | | | okText | 确认按钮文字 | string | 确定 | |
| okType | 确认按钮类型 | string | primary | | | okType | 确认按钮类型 | string | primary | |
| title | 标题 | string\|VNode \|function() | | | | title | 标题 | string\|VNode \|function() | - | |
| width | 宽度 | string\|number | 416 | | | width | 宽度 | string\|number | 416 | |
| wrapClassName | 对话框外层容器的类名 | string | - | 3.2.3 | | wrapClassName | 对话框外层容器的类名 | string | - | 3.2.3 |
| zIndex | 设置 Modal 的 `z-index` | number | 1000 | | | zIndex | 设置 Modal 的 `z-index` | number | 1000 | |
| onCancel | 取消回调,参数为关闭函数,返回 promise 时 resolve 后自动关闭 | function | | | | onCancel | 取消回调,参数为关闭函数,返回 promise 时 resolve 后自动关闭 | function | - | |
| onOk | 点击确定回调,参数为关闭函数,返回 promise 时 resolve 后自动关闭 | function | | | | onOk | 点击确定回调,参数为关闭函数,返回 promise 时 resolve 后自动关闭 | function | - | |
以上函数调用后,会返回一个引用,可以通过该引用更新和关闭弹窗。 以上函数调用后,会返回一个引用,可以通过该引用更新和关闭弹窗。

View File

@ -27,7 +27,7 @@ coverDark: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*HrFtQ6jJJFQAAA
| okText | 确认按钮文字 | string\|slot | 确定 | | | okText | 确认按钮文字 | string\|slot | 确定 | |
| okType | 确认按钮类型 | string | primary | | | okType | 确认按钮类型 | string | primary | |
| showCancel | 是否显示取消按钮 | boolean | true | 3.0 | | showCancel | 是否显示取消按钮 | boolean | true | 3.0 |
| title | 确认框的描述 | string\|slot | | | | title | 确认框的描述 | string\|slot | - | |
| description | 确认内容的详细描述 | string\|slot | - | 4.0 | | description | 确认内容的详细描述 | string\|slot | - | 4.0 |
| open (v-model) | 是否显示 | boolean | - | 4.0 | | open (v-model) | 是否显示 | boolean | - | 4.0 |

View File

@ -21,7 +21,7 @@ tag: New
| 参数 | 说明 | 类型 | 默认值 | 版本 | | 参数 | 说明 | 类型 | 默认值 | 版本 |
| --- | --- | --- | --- | --- | | --- | --- | --- | --- | --- |
| block | 将宽度调整为父元素宽度的选项 | boolean | | | | block | 将宽度调整为父元素宽度的选项 | boolean | - | |
| disabled | 是否禁用 | boolean | false | | | disabled | 是否禁用 | boolean | false | |
| options | 数据化配置选项内容 | string[] \| number[] \| SegmentedOption[] | [] | | | options | 数据化配置选项内容 | string[] \| number[] \| SegmentedOption[] | [] | |
| size | 控件尺寸 | `large` \| `middle` \| `small` | - | | | size | 控件尺寸 | `large` \| `middle` \| `small` | - | |

View File

@ -159,6 +159,52 @@ describe('Select', () => {
}, 500); }, 500);
}); });
it('The select trigger should be blur when the panel is closed.', async () => {
const wrapper = mount(
{
render() {
return (
<Select
dropdownRender={() => {
return <input id="dropdownRenderInput" />;
}}
/>
);
},
},
{
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', () => { describe('Select Custom Icons', () => {
it('should support customized icons', () => { it('should support customized icons', () => {
const wrapper = mount({ const wrapper = mount({

View File

@ -111,7 +111,7 @@ coverDark: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*5oPiTqPxGAUAAA
| 参数 | 说明 | 类型 | 默认值 | | 参数 | 说明 | 类型 | 默认值 |
| ----- | ---- | ------------------------- | ------ | | ----- | ---- | ------------------------- | ------ |
| key | | string | - | | key | | string | - |
| label | 组名 | string\|function(h)\|slot | | | label | 组名 | string\|function(h)\|slot | - |
## FAQ ## FAQ

View File

@ -17,8 +17,8 @@ The `onChange` callback function will fire when the user changes the slider's va
<template> <template>
<div class="code-box-demo"> <div class="code-box-demo">
<a-slider v-model:value="value1" @afterChange="onAfterChange" /> <a-slider v-model:value="value1" @change="onChange" @afterChange="onAfterChange" />
<a-slider v-model:value="value2" range :step="10" @afterChange="onAfterChange" /> <a-slider v-model:value="value2" range :step="10" @change="onChange" @afterChange="onAfterChange" />
</div> </div>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
@ -26,6 +26,10 @@ import { ref } from 'vue';
const value1 = ref<number>(30); const value1 = ref<number>(30);
const value2 = ref<[number, number]>([20, 50]); const value2 = ref<[number, number]>([20, 50]);
const onChange = (value: number) => {
console.log('onChange: ', value);
};
const onAfterChange = (value: number) => { const onAfterChange = (value: number) => {
console.log('afterChange: ', value); console.log('afterChange: ', value);
}; };

View File

@ -38,7 +38,7 @@ const genSpaceStyle: GenerateStyle<SpaceToken> = token => {
alignItems: 'baseline', alignItems: 'baseline',
}, },
}, },
[`${componentCls}-space-item`]: { [`${componentCls}-item`]: {
'&:empty': { '&:empty': {
display: 'none', display: 'none',
}, },

View File

@ -25,13 +25,13 @@ Ant Design 依次提供了三级选项卡,分别用于不同的场景。
| 参数 | 说明 | 类型 | 默认值 | 版本 | | | 参数 | 说明 | 类型 | 默认值 | 版本 | |
| --- | --- | --- | --- | --- | --- | | --- | --- | --- | --- | --- | --- |
| activeKey(v-model) | 当前激活 tab 面板的 key | string | | | | | activeKey(v-model) | 当前激活 tab 面板的 key | string | - | | |
| animated | 是否使用动画切换 Tabs在 tabPosition=`"top"` \| `"bottom"` 时有效 | boolean \| {inkBar:boolean, tabPane:boolean} | true, 当 type="card" 时为 false | | | animated | 是否使用动画切换 Tabs在 tabPosition=`"top"` \| `"bottom"` 时有效 | boolean \| {inkBar:boolean, tabPane:boolean} | true, 当 type="card" 时为 false | |
| centered | 标签居中展示 | boolean | false | 3.0 | | | centered | 标签居中展示 | boolean | false | 3.0 | |
| destroyInactiveTabPane | 被隐藏时是否销毁 DOM 结构 | boolean | false | | | | destroyInactiveTabPane | 被隐藏时是否销毁 DOM 结构 | boolean | false | | |
| hideAdd | 是否隐藏加号图标,在 `type="editable-card"` 时有效 | boolean | false | | | | hideAdd | 是否隐藏加号图标,在 `type="editable-card"` 时有效 | boolean | false | | |
| size | 大小,提供 `large` `middle``small` 三种大小 | string | `middle` | | | | size | 大小,提供 `large` `middle``small` 三种大小 | string | `middle` | | |
| tabBarGutter | tabs 之间的间隙 | number | | | | | tabBarGutter | tabs 之间的间隙 | number | - | | |
| tabBarStyle | tab bar 的样式对象 | CSSProperties | - | | | | tabBarStyle | tab bar 的样式对象 | CSSProperties | - | | |
| tabPosition | 页签位置,可选值有 `top` `right` `bottom` `left` | string | `top` | | | | tabPosition | 页签位置,可选值有 `top` `right` `bottom` `left` | string | `top` | | |
| type | 页签的基本样式,可选 `line`、`card` `editable-card` 类型 | string | `line` | | | | type | 页签的基本样式,可选 `line`、`card` `editable-card` 类型 | string | `line` | | |
@ -60,8 +60,8 @@ Ant Design 依次提供了三级选项卡,分别用于不同的场景。
| 参数 | 说明 | 类型 | 默认值 | | 参数 | 说明 | 类型 | 默认值 |
| ----------- | ------------------------- | ------------ | ------ | | ----------- | ------------------------- | ------------ | ------ |
| forceRender | 被隐藏时是否渲染 DOM 结构 | boolean | false | | forceRender | 被隐藏时是否渲染 DOM 结构 | boolean | false |
| key | 对应 activeKey | string | | | key | 对应 activeKey | string | - |
| tab | 选项卡头显示文字 | string\|slot | | | tab | 选项卡头显示文字 | string\|slot | - |
### Tabs.TabPane 插槽 ### Tabs.TabPane 插槽

View File

@ -18,7 +18,7 @@ coverDark: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*gwrhTozoTC4AAA
| 参数 | 说明 | 类型 | 默认值 | | 参数 | 说明 | 类型 | 默认值 |
| ----- | -------- | ------------ | ------ | | ----- | -------- | ------------ | ------ |
| title | 提示文字 | string\|slot | | | title | 提示文字 | string\|slot | - |
### 共同的 API ### 共同的 API
@ -26,18 +26,18 @@ coverDark: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*gwrhTozoTC4AAA
| 参数 | 说明 | 类型 | 默认值 | 版本 | | 参数 | 说明 | 类型 | 默认值 | 版本 |
| --- | --- | --- | --- | --- | | --- | --- | --- | --- | --- |
| align | 该值将合并到 placement 的配置中,设置参考 [dom-align](https://github.com/yiminghe/dom-align) | Object | | | | align | 该值将合并到 placement 的配置中,设置参考 [dom-align](https://github.com/yiminghe/dom-align) | Object | - | |
| arrowPointAtCenter | 箭头是否指向目标元素中心 | boolean | `false` | | | arrowPointAtCenter | 箭头是否指向目标元素中心 | boolean | `false` | |
| arrow | 修改箭头的显示状态以及修改箭头是否指向目标元素中心 | boolean \| { pointAtCenter: boolean} | `true` | 4.2.0 | | arrow | 修改箭头的显示状态以及修改箭头是否指向目标元素中心 | boolean \| { pointAtCenter: boolean} | `true` | 4.2.0 |
| autoAdjustOverflow | 气泡被遮挡时自动调整位置 | boolean | `true` | | | autoAdjustOverflow | 气泡被遮挡时自动调整位置 | boolean | `true` | |
| color | 背景颜色 | string | | | | color | 背景颜色 | string | - | |
| destroyTooltipOnHide | 隐藏后是否销毁 tooltip | boolean | false | | | destroyTooltipOnHide | 隐藏后是否销毁 tooltip | boolean | false | |
| getPopupContainer | 浮层渲染父节点,默认渲染到 body 上 | (triggerNode: HTMLElement) => HTMLElement | () => document.body | | | getPopupContainer | 浮层渲染父节点,默认渲染到 body 上 | (triggerNode: HTMLElement) => HTMLElement | () => document.body | |
| mouseEnterDelay | 鼠标移入后延时多少才显示 Tooltip单位秒 | number | 0.1 | | | mouseEnterDelay | 鼠标移入后延时多少才显示 Tooltip单位秒 | number | 0.1 | |
| mouseLeaveDelay | 鼠标移出后延时多少才隐藏 Tooltip单位秒 | number | 0.1 | | | mouseLeaveDelay | 鼠标移出后延时多少才隐藏 Tooltip单位秒 | number | 0.1 | |
| overlayClassName | 卡片类名 | string | | | | overlayClassName | 卡片类名 | string | - | |
| overlayStyle | 卡片样式 | object | | | | overlayStyle | 卡片样式 | object | - | |
| overlayInnerStyle | 卡片内容区域样式 | object | | 4.0 | | overlayInnerStyle | 卡片内容区域样式 | object | - | 4.0 |
| placement | 气泡框位置,可选 `top` `left` `right` `bottom` `topLeft` `topRight` `bottomLeft` `bottomRight` `leftTop` `leftBottom` `rightTop` `rightBottom` | string | top | | | placement | 气泡框位置,可选 `top` `left` `right` `bottom` `topLeft` `topRight` `bottomLeft` `bottomRight` `leftTop` `leftBottom` `rightTop` `rightBottom` | string | top | |
| trigger | 触发行为,可选 `hover/focus/click/contextmenu` | string | hover | | | trigger | 触发行为,可选 `hover/focus/click/contextmenu` | string | hover | |
| open(v-model) | 用于手动控制浮层显隐, 小于 4.0.0 使用 `visible` | boolean | false | 4.0 | | open(v-model) | 用于手动控制浮层显隐, 小于 4.0.0 使用 `visible` | boolean | false | 4.0 |

View File

@ -1,5 +1,6 @@
import { computed, defineComponent, toRefs } from 'vue'; import { computed, defineComponent, toRefs } from 'vue';
import classNames from '../_util/classNames'; import classNames from '../_util/classNames';
import { isFunction } from '../_util/util';
import CloseOutlined from '@ant-design/icons-vue/CloseOutlined'; import CloseOutlined from '@ant-design/icons-vue/CloseOutlined';
import { tourStepProps } from './interface'; import { tourStepProps } from './interface';
import type { TourBtnProps } from './interface'; import type { TourBtnProps } from './interface';
@ -119,7 +120,9 @@ const panelRender = defineComponent({
size="small" size="small"
class={classNames(`${prefixCls}-prev-btn`, prevButtonProps?.className)} class={classNames(`${prefixCls}-prev-btn`, prevButtonProps?.className)}
> >
{prevButtonProps?.children ?? contextLocale.Previous} {isFunction(prevButtonProps?.children)
? prevButtonProps.children()
: prevButtonProps?.children ?? contextLocale.Previous}
</Button> </Button>
) : null} ) : null}
<Button <Button
@ -129,8 +132,11 @@ const panelRender = defineComponent({
size="small" size="small"
class={classNames(`${prefixCls}-next-btn`, nextButtonProps?.className)} class={classNames(`${prefixCls}-next-btn`, nextButtonProps?.className)}
> >
{nextButtonProps?.children ?? {isFunction(nextButtonProps?.children)
(isLastStep.value ? contextLocale.Finish : contextLocale.Next)} ? nextButtonProps?.children()
: isLastStep.value
? contextLocale.Finish
: contextLocale.Next}
</Button> </Button>
</div> </div>
</div> </div>

View File

@ -74,7 +74,7 @@ Almost anything can be represented in a tree structure. Examples include directo
| disableCheckbox | Disables the checkbox of the treeNode | boolean | false | | | disableCheckbox | Disables the checkbox of the treeNode | boolean | false | |
| disabled | Disables the treeNode | boolean | false | | | disabled | Disables the treeNode | boolean | false | |
| icon | customize icon. When you pass component, whose render will receive full TreeNode props as component props | slot\|slot-scope | - | | | icon | customize icon. When you pass component, whose render will receive full TreeNode props as component props | slot\|slot-scope | - | |
| isLeaf | Determines if this is a leaf node(effective when `loadData` is specified) | boolean | false | | | isLeaf | Determines if this is a leaf node(effective when `loadData` is specified) | boolean | - | |
| key | Used with (default)ExpandedKeys / (default)CheckedKeys / (default)SelectedKeys. P.S.: It must be unique in all of treeNodes of the tree! | string \| number | internal calculated position of treeNode | | | key | Used with (default)ExpandedKeys / (default)CheckedKeys / (default)SelectedKeys. P.S.: It must be unique in all of treeNodes of the tree! | string \| number | internal calculated position of treeNode | |
| selectable | Set whether the treeNode can be selected | boolean | true | | | selectable | Set whether the treeNode can be selected | boolean | true | |
| style | style | string\|object | - | | | style | style | string\|object | - | |

View File

@ -75,7 +75,7 @@ coverDark: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*1GeUQJPTGUYAAA
| disableCheckbox | 禁掉 checkbox | boolean | false | | | disableCheckbox | 禁掉 checkbox | boolean | false | |
| disabled | 禁掉响应 | boolean | false | | | disabled | 禁掉响应 | boolean | false | |
| icon | 自定义图标。可接收组件props 为当前节点 props | slot\|slot-scope | - | | | icon | 自定义图标。可接收组件props 为当前节点 props | slot\|slot-scope | - | |
| isLeaf | 设置为叶子节点(设置了`loadData`时有效) | boolean | false | | | isLeaf | 设置为叶子节点(设置了`loadData`时有效) | boolean | - | |
| key | 被树的 (default)ExpandedKeys / (default)CheckedKeys / (default)SelectedKeys 属性所用。注意:整个树范围内的所有节点的 key 值不能重复! | string \| number | 内部计算出的节点位置 | | | key | 被树的 (default)ExpandedKeys / (default)CheckedKeys / (default)SelectedKeys 属性所用。注意:整个树范围内的所有节点的 key 值不能重复! | string \| number | 内部计算出的节点位置 | |
| selectable | 设置节点是否可被选中 | boolean | true | | | selectable | 设置节点是否可被选中 | boolean | true | |
| style | 节点的 style | string\|object | - | | | style | 节点的 style | string\|object | - | |

View File

@ -1,5 +1,6 @@
import type { CSSProperties, VNodeTypes } from 'vue'; import type { CSSProperties, VNodeTypes } from 'vue';
import { createApp } from 'vue'; import { createApp } from 'vue';
import { styleToString } from '../vc-util/Dom/css';
interface MeasureResult { interface MeasureResult {
finished: boolean; finished: boolean;
@ -23,13 +24,6 @@ const wrapperStyle: CSSProperties = {
lineHeight: 'inherit', lineHeight: 'inherit',
}; };
function styleToString(style: CSSStyleDeclaration) {
// There are some different behavior between Firefox & Chrome.
// We have to handle this ourself.
const styleNames = Array.prototype.slice.apply(style);
return styleNames.map(name => `${name}: ${style.getPropertyValue(name)};`).join('');
}
function resetDomStyles(target: HTMLElement, origin: HTMLElement) { function resetDomStyles(target: HTMLElement, origin: HTMLElement) {
target.setAttribute('aria-hidden', 'true'); target.setAttribute('aria-hidden', 'true');
const originStyle = window.getComputedStyle(origin); const originStyle = window.getComputedStyle(origin);

View File

@ -1,4 +1,12 @@
import { computed, defineComponent, onBeforeUnmount, onMounted, shallowRef, watch } from 'vue'; import {
computed,
defineComponent,
onBeforeUnmount,
onMounted,
shallowRef,
watch,
Transition,
} from 'vue';
import type { ExtractPropTypes, CSSProperties } from 'vue'; import type { ExtractPropTypes, CSSProperties } from 'vue';
import EyeOutlined from '@ant-design/icons-vue/EyeOutlined'; import EyeOutlined from '@ant-design/icons-vue/EyeOutlined';
import DeleteOutlined from '@ant-design/icons-vue/DeleteOutlined'; import DeleteOutlined from '@ant-design/icons-vue/DeleteOutlined';
@ -15,7 +23,7 @@ import type {
} from '../interface'; } from '../interface';
import type { VueNode } from '../../_util/type'; import type { VueNode } from '../../_util/type';
import useConfigInject from '../../config-provider/hooks/useConfigInject'; import useConfigInject from '../../config-provider/hooks/useConfigInject';
import Transition, { getTransitionProps } from '../../_util/transition'; import { getTransitionProps } from '../../_util/transition';
import { booleanType, stringType, functionType, arrayType, objectType } from '../../_util/type'; import { booleanType, stringType, functionType, arrayType, objectType } from '../../_util/type';
export const listItemProps = () => { export const listItemProps = () => {

View File

@ -17,11 +17,12 @@ import {
onMounted, onMounted,
shallowRef, shallowRef,
watchEffect, watchEffect,
TransitionGroup,
} from 'vue'; } from 'vue';
import { filterEmpty, initDefaultProps, isValidElement } from '../../_util/props-util'; import { filterEmpty, initDefaultProps, isValidElement } from '../../_util/props-util';
import type { VueNode } from '../../_util/type'; import type { VueNode } from '../../_util/type';
import useConfigInject from '../../config-provider/hooks/useConfigInject'; import useConfigInject from '../../config-provider/hooks/useConfigInject';
import { getTransitionGroupProps, TransitionGroup } from '../../_util/transition'; import { getTransitionGroupProps } from '../../_util/transition';
import collapseMotion from '../../_util/collapseMotion'; import collapseMotion from '../../_util/collapseMotion';
const HackSlot = (_, { slots }) => { const HackSlot = (_, { slots }) => {

View File

@ -21,16 +21,16 @@ coverDark: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*l1nlSryXib8AAA
| 参数 | 说明 | 类型 | 默认值 | 版本 | | | 参数 | 说明 | 类型 | 默认值 | 版本 | |
| --- | --- | --- | --- | --- | --- | | --- | --- | --- | --- | --- | --- |
| accept | 接受上传的文件类型, 详见 [input accept Attribute](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/file#accept) | string | | | | | accept | 接受上传的文件类型, 详见 [input accept Attribute](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/file#accept) | string | - | | |
| action | 上传的地址 | string\|(file) => `Promise` | | | | | action | 上传的地址 | string\|(file) => `Promise` | - | | |
| beforeUpload | 上传文件之前的钩子,参数为上传的文件,若返回 `false` 则停止上传。支持返回一个 Promise 对象Promise 对象 reject 时则停止上传resolve 时开始上传( resolve 传入 `File``Blob` 对象则上传 resolve 传入对象)。 | (file, fileList) => `boolean` \| `Promise` | | | | beforeUpload | 上传文件之前的钩子,参数为上传的文件,若返回 `false` 则停止上传。支持返回一个 Promise 对象Promise 对象 reject 时则停止上传resolve 时开始上传( resolve 传入 `File``Blob` 对象则上传 resolve 传入对象)。 | (file, fileList) => `boolean` \| `Promise` | - | |
| customRequest | 通过覆盖默认的上传行为,可以自定义自己的上传实现 | function | | | | | customRequest | 通过覆盖默认的上传行为,可以自定义自己的上传实现 | function | - | | |
| data | 上传所需参数或返回上传参数的方法 | object\|(file) => object | | | | | data | 上传所需参数或返回上传参数的方法 | object\|(file) => object | - | | |
| directory | 支持上传文件夹([caniuse](https://caniuse.com/#feat=input-file-directory) | boolean | false | 3.0 | | | directory | 支持上传文件夹([caniuse](https://caniuse.com/#feat=input-file-directory) | boolean | false | 3.0 | |
| disabled | 是否禁用 | boolean | - | | | | disabled | 是否禁用 | boolean | - | | |
| downloadIcon | 自定义下载 icon | v-slot:iconRender="{file: UploadFile}" | - | 3.0 | | | downloadIcon | 自定义下载 icon | v-slot:iconRender="{file: UploadFile}" | - | 3.0 | |
| fileList | 已经上传的文件列表(受控) | object\[] | | | | | fileList | 已经上传的文件列表(受控) | object\[] | - | | |
| headers | 设置上传的请求头部IE10 以上有效 | object | | | | | headers | 设置上传的请求头部IE10 以上有效 | object | - | | |
| iconRender | 自定义显示 icon | v-slot:iconRender="{file: UploadFile, listType?: UploadListType}" | - | 3.0 | | | iconRender | 自定义显示 icon | v-slot:iconRender="{file: UploadFile, listType?: UploadListType}" | - | 3.0 | |
| isImageUrl | 自定义缩略图是否使用 &lt;img /> 标签进行显示 | (file: UploadFile) => boolean | - | 3.0 | | | isImageUrl | 自定义缩略图是否使用 &lt;img /> 标签进行显示 | (file: UploadFile) => boolean | - | 3.0 | |
| itemRender | 自定义上传列表项 | v-slot:itemRender="{originNode: VNode, file: UploadFile, fileList: object\[], actions: { download: function, preview: function, remove: function }" | - | 3.0 | | | itemRender | 自定义上传列表项 | v-slot:itemRender="{originNode: VNode, file: UploadFile, fileList: object\[], actions: { download: function, preview: function, remove: function }" | - | 3.0 | |
@ -40,7 +40,7 @@ coverDark: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*l1nlSryXib8AAA
| multiple | 是否支持多选文件,`ie10+` 支持。开启后按住 ctrl 可选择多个文件。 | boolean | false | | | | multiple | 是否支持多选文件,`ie10+` 支持。开启后按住 ctrl 可选择多个文件。 | boolean | false | | |
| name | 发到后台的文件参数名 | string | `file` | | | | name | 发到后台的文件参数名 | string | `file` | | |
| openFileDialogOnClick | 点击打开文件对话框 | boolean | true | 3.0 | | | openFileDialogOnClick | 点击打开文件对话框 | boolean | true | 3.0 | |
| previewFile | 自定义文件预览逻辑 | (file: File \| Blob) => Promise&lt;dataURL: string> | | 1.5.0 | | | previewFile | 自定义文件预览逻辑 | (file: File \| Blob) => Promise&lt;dataURL: string> | - | 1.5.0 | |
| previewIcon | 自定义预览 icon | v-slot:iconRender="{file: UploadFile}" | - | 3.0 | | | previewIcon | 自定义预览 icon | v-slot:iconRender="{file: UploadFile}" | - | 3.0 | |
| progress | 自定义进度条样式 | [ProgressProps](/components/progress/#api)(仅支持 `type="line"` | { strokeWidth: 2, showInfo: false } | 3.0 | | | progress | 自定义进度条样式 | [ProgressProps](/components/progress/#api)(仅支持 `type="line"` | { strokeWidth: 2, showInfo: false } | 3.0 | |
| removeIcon | 自定义删除 icon | v-slot:iconRender="{file: UploadFile}" | - | 3.0 | | | removeIcon | 自定义删除 icon | v-slot:iconRender="{file: UploadFile}" | - | 3.0 | |
@ -52,11 +52,11 @@ coverDark: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*l1nlSryXib8AAA
| 事件名称 | 说明 | 回调参数 | 版本 | | | 事件名称 | 说明 | 回调参数 | 版本 | |
| --- | --- | --- | --- | --- | | --- | --- | --- | --- | --- |
| change | 上传文件改变时的状态,详见 [change](#change) | function | | | | change | 上传文件改变时的状态,详见 [change](#change) | function | - | |
| download | 点击下载文件时的回调,如果没有指定,则默认跳转到文件 url 对应的标签页。 | function(file): void | 跳转新标签页 | 1.5.0 | | download | 点击下载文件时的回调,如果没有指定,则默认跳转到文件 url 对应的标签页。 | function(file): void | 跳转新标签页 | 1.5.0 |
| drop | 当文件被拖入上传区域时执行的回调功能 | (event: DragEvent) => void | - | 3.0 | | drop | 当文件被拖入上传区域时执行的回调功能 | (event: DragEvent) => void | - | 3.0 |
| preview | 点击文件链接或预览图标时的回调 | function(file) | | | | preview | 点击文件链接或预览图标时的回调 | function(file) | - | |
| reject | 拖拽文件不符合 accept 类型时的回调 | function(fileList) | | | | reject | 拖拽文件不符合 accept 类型时的回调 | function(fileList) | - | |
| remove   | 点击移除文件时的回调,返回值为 false 时不移除。支持返回一个 Promise 对象Promise 对象 resolve(false) 或 reject 时不移除 | function(file): boolean \| Promise | -   | 3.0 | | remove   | 点击移除文件时的回调,返回值为 false 时不移除。支持返回一个 Promise 对象Promise 对象 resolve(false) 或 reject 时不移除 | function(file): boolean \| Promise | -   | 3.0 |
### UploadFile ### UploadFile

View File

@ -1,7 +1,7 @@
import type { CSSProperties, PropType } from 'vue'; import type { CSSProperties, PropType } from 'vue';
import { computed, ref, defineComponent, nextTick } from 'vue'; import { Transition, computed, ref, defineComponent, nextTick } from 'vue';
import type { MouseEventHandler } from '../_util/EventInterface'; import type { MouseEventHandler } from '../_util/EventInterface';
import Transition, { getTransitionProps } from '../_util/transition'; import { getTransitionProps } from '../_util/transition';
import dialogPropTypes from './IDialogPropTypes'; import dialogPropTypes from './IDialogPropTypes';
import { offset } from './util'; import { offset } from './util';
const sentinelStyle = { width: 0, height: 0, overflow: 'hidden', outline: 'none' }; const sentinelStyle = { width: 0, height: 0, overflow: 'hidden', outline: 'none' };
@ -143,9 +143,9 @@ export default defineComponent({
onMousedown={onMousedown} onMousedown={onMousedown}
onMouseup={onMouseup} onMouseup={onMouseup}
> >
<div tabindex={0} ref={sentinelStartRef} style={sentinelStyle} aria-hidden="true" /> <div tabindex={0} ref={sentinelStartRef} style={sentinelStyle} inert/>
{modalRender ? modalRender({ originVNode: content }) : content} {modalRender ? modalRender({ originVNode: content }) : content}
<div tabindex={0} ref={sentinelEndRef} style={sentinelStyle} aria-hidden="true" /> <div tabindex={0} ref={sentinelEndRef} style={sentinelStyle} inert/>
</div> </div>
) : null} ) : null}
</Transition> </Transition>

View File

@ -1,5 +1,5 @@
import { defineComponent } from 'vue'; import { defineComponent, Transition } from 'vue';
import Transition, { getTransitionProps } from '../_util/transition'; import { getTransitionProps } from '../_util/transition';
export default defineComponent({ export default defineComponent({
compatConfig: { MODE: 3 }, compatConfig: { MODE: 3 },

View File

@ -1,4 +1,4 @@
import type { ImgHTMLAttributes, CSSProperties, PropType } from 'vue'; import type { CSSProperties, PropType } from 'vue';
import { ref, watch, defineComponent, computed, onMounted, onUnmounted } from 'vue'; import { ref, watch, defineComponent, computed, onMounted, onUnmounted } from 'vue';
import isNumber from 'lodash-es/isNumber'; import isNumber from 'lodash-es/isNumber';
import cn from '../../_util/classNames'; import cn from '../../_util/classNames';
@ -32,6 +32,8 @@ export const imageProps = () => ({
rootClassName: String, rootClassName: String,
prefixCls: String, prefixCls: String,
previewPrefixCls: String, previewPrefixCls: String,
width: [Number, String],
height: [Number, String],
previewMask: { previewMask: {
type: [Boolean, Function] as PropType<false | (() => any)>, type: [Boolean, Function] as PropType<false | (() => any)>,
default: undefined, default: undefined,
@ -201,8 +203,6 @@ const ImageInternal = defineComponent({
placeholder, placeholder,
wrapperStyle, wrapperStyle,
rootClassName, rootClassName,
} = props;
const {
width, width,
height, height,
crossorigin, crossorigin,
@ -213,7 +213,7 @@ const ImageInternal = defineComponent({
usemap, usemap,
class: cls, class: cls,
style, style,
} = attrs as ImgHTMLAttributes; } = { ...props, ...attrs } as any;
const { icons, maskClassName, ...dialogProps } = preview.value; const { icons, maskClassName, ...dialogProps } = preview.value;
const wrappperClass = cn(prefixCls, wrapperClassName, rootClassName, { const wrappperClass = cn(prefixCls, wrapperClassName, rootClassName, {

View File

@ -2,6 +2,7 @@ import { getTransitionGroupProps } from '../_util/transition';
import type { Key, VueNode } from '../_util/type'; import type { Key, VueNode } from '../_util/type';
import type { CSSProperties } from 'vue'; import type { CSSProperties } from 'vue';
import { import {
toRaw,
shallowRef, shallowRef,
createVNode, createVNode,
computed, computed,
@ -72,10 +73,10 @@ type NotificationState = {
holderCallback?: HolderReadyCallback; holderCallback?: HolderReadyCallback;
}[]; }[];
const Notification = defineComponent<NotificationProps>({ const Notification = defineComponent({
name: 'Notification', name: 'Notification',
inheritAttrs: false, inheritAttrs: false,
props: ['prefixCls', 'transitionName', 'animation', 'maxCount', 'closeIcon', 'hashId'] as any, props: ['prefixCls', 'transitionName', 'animation', 'maxCount', 'closeIcon', 'hashId'],
setup(props, { attrs, expose, slots }) { setup(props, { attrs, expose, slots }) {
const hookRefs = new Map<Key, HTMLDivElement>(); const hookRefs = new Map<Key, HTMLDivElement>();
const notices = ref<NotificationState>([]); const notices = ref<NotificationState>([]);
@ -125,7 +126,7 @@ const Notification = defineComponent<NotificationProps>({
}; };
const remove = (removeKey: Key) => { const remove = (removeKey: Key) => {
notices.value = notices.value.filter(({ notice: { key, userPassKey } }) => { notices.value = toRaw(notices.value as any).filter(({ notice: { key, userPassKey } }) => {
const mergedKey = userPassKey || key; const mergedKey = userPassKey || key;
return mergedKey !== removeKey; return mergedKey !== removeKey;
}); });

View File

@ -39,14 +39,15 @@ const overflowProps = () => {
/** When set to `full`, ssr will render full items by default and remove at client side */ /** When set to `full`, ssr will render full items by default and remove at client side */
ssr: String as PropType<'full'>, ssr: String as PropType<'full'>,
onMousedown: Function as PropType<MouseEventHandler>, onMousedown: Function as PropType<MouseEventHandler>,
role: String,
}; };
}; };
type InterOverflowProps = Partial<ExtractPropTypes<ReturnType<typeof overflowProps>>>; type InterOverflowProps = Partial<ExtractPropTypes<ReturnType<typeof overflowProps>>>;
export type OverflowProps = HTMLAttributes & InterOverflowProps; export type OverflowProps = HTMLAttributes & InterOverflowProps;
const Overflow = defineComponent<OverflowProps>({ const Overflow = defineComponent({
name: 'Overflow', name: 'Overflow',
inheritAttrs: false, inheritAttrs: false,
props: overflowProps() as any, props: overflowProps(),
emits: ['visibleChange'], emits: ['visibleChange'],
setup(props, { attrs, emit, slots }) { setup(props, { attrs, emit, slots }) {
const fullySSR = computed(() => props.ssr === 'full'); const fullySSR = computed(() => props.ssr === 'full');
@ -331,6 +332,7 @@ const Overflow = defineComponent<OverflowProps>({
class={classNames(!invalidate.value && prefixCls, className)} class={classNames(!invalidate.value && prefixCls, className)}
style={style} style={style}
onMousedown={onMousedown} onMousedown={onMousedown}
role={props.role}
{...restAttrs} {...restAttrs}
> >
{mergedData.value.map(internalRenderItemNode)} {mergedData.value.map(internalRenderItemNode)}

View File

@ -22,7 +22,8 @@ export default defineComponent({
{props.presets.map(({ label, value }, index) => ( {props.presets.map(({ label, value }, index) => (
<li <li
key={index} key={index}
onClick={() => { onClick={e => {
e.stopPropagation();
props.onClick(value); props.onClick(value);
}} }}
onMouseenter={() => { onMouseenter={() => {

View File

@ -18,9 +18,9 @@ export type TimeUnitColumnProps = {
onSelect?: (value: number) => void; onSelect?: (value: number) => void;
}; };
export default defineComponent<TimeUnitColumnProps>({ export default defineComponent({
name: 'TimeUnitColumn', name: 'TimeUnitColumn',
props: ['prefixCls', 'units', 'onSelect', 'value', 'active', 'hideDisabledOptions'] as any, props: ['prefixCls', 'units', 'onSelect', 'value', 'active', 'hideDisabledOptions'],
setup(props) { setup(props) {
const { open } = useInjectPanel(); const { open } = useInjectPanel();

View File

@ -343,6 +343,14 @@ export default defineComponent({
if (mergedOpen.value !== nextOpen && !props.disabled) { if (mergedOpen.value !== nextOpen && !props.disabled) {
setInnerOpen(nextOpen); setInnerOpen(nextOpen);
props.onDropdownVisibleChange && props.onDropdownVisibleChange(nextOpen); props.onDropdownVisibleChange && props.onDropdownVisibleChange(nextOpen);
if (!nextOpen && popupFocused.value) {
popupFocused.value = false;
setMockFocused(false, () => {
focusRef.value = false;
blurRef.value = false;
});
}
} }
}; };

View File

@ -48,7 +48,6 @@ const Input = defineComponent({
setup(props) { setup(props) {
let blurTimeout = null; let blurTimeout = null;
const VCSelectContainerEvent = inject('VCSelectContainerEvent') as any; const VCSelectContainerEvent = inject('VCSelectContainerEvent') as any;
return () => { return () => {
const { const {
prefixCls, prefixCls,
@ -97,6 +96,7 @@ const Input = defineComponent({
ref: inputRef, ref: inputRef,
disabled, disabled,
tabindex, tabindex,
lazy: false,
autocomplete: autocomplete || 'off', autocomplete: autocomplete || 'off',
autofocus, autofocus,
class: classNames(`${prefixCls}-selection-search-input`, inputNode?.props?.class), class: classNames(`${prefixCls}-selection-search-input`, inputNode?.props?.class),

View File

@ -2,7 +2,7 @@ import TransBtn from '../TransBtn';
import type { InnerSelectorProps } from './interface'; import type { InnerSelectorProps } from './interface';
import Input from './Input'; import Input from './Input';
import type { Ref, PropType } from 'vue'; import type { Ref, PropType } from 'vue';
import { computed, defineComponent, onMounted, shallowRef, watch } from 'vue'; import { ref, watchEffect, computed, defineComponent, onMounted, shallowRef, watch } from 'vue';
import classNames from '../../_util/classNames'; import classNames from '../../_util/classNames';
import pickAttrs from '../../_util/pickAttrs'; import pickAttrs from '../../_util/pickAttrs';
import PropTypes from '../../_util/vue-types'; import PropTypes from '../../_util/vue-types';
@ -24,6 +24,8 @@ type SelectorProps = InnerSelectorProps & {
tagRender?: (props: CustomTagProps) => VueNode; tagRender?: (props: CustomTagProps) => VueNode;
onToggleOpen: any; onToggleOpen: any;
compositionStatus: boolean;
// Motion // Motion
choiceTransitionName?: string; choiceTransitionName?: string;
@ -46,7 +48,7 @@ const props = {
autocomplete: String, autocomplete: String,
activeDescendantId: String, activeDescendantId: String,
tabindex: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), tabindex: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
compositionStatus: Boolean,
removeIcon: PropTypes.any, removeIcon: PropTypes.any,
choiceTransitionName: String, choiceTransitionName: String,
@ -91,11 +93,14 @@ const SelectSelector = defineComponent<SelectorProps>({
() => () =>
props.mode === 'tags' || ((props.showSearch && (props.open || focused.value)) as boolean), props.mode === 'tags' || ((props.showSearch && (props.open || focused.value)) as boolean),
); );
const targetValue = ref('');
watchEffect(() => {
targetValue.value = inputValue.value;
});
// We measure width and set to the input immediately // We measure width and set to the input immediately
onMounted(() => { onMounted(() => {
watch( watch(
inputValue, targetValue,
() => { () => {
inputWidth.value = measureRef.value.scrollWidth; inputWidth.value = measureRef.value.scrollWidth;
}, },
@ -202,6 +207,14 @@ const SelectSelector = defineComponent<SelectorProps>({
return defaultRenderSelector(content, content, false); return defaultRenderSelector(content, content, false);
} }
const handleInput = (e: Event) => {
const composing = (e.target as any).composing;
targetValue.value = (e.target as any).value;
if (!composing) {
props.onInputChange(e);
}
};
return () => { return () => {
const { const {
id, id,
@ -215,14 +228,13 @@ const SelectSelector = defineComponent<SelectorProps>({
autocomplete, autocomplete,
activeDescendantId, activeDescendantId,
tabindex, tabindex,
onInputChange, compositionStatus,
onInputPaste, onInputPaste,
onInputKeyDown, onInputKeyDown,
onInputMouseDown, onInputMouseDown,
onInputCompositionStart, onInputCompositionStart,
onInputCompositionEnd, onInputCompositionEnd,
} = props; } = props;
// >>> Input Node // >>> Input Node
const inputNode = ( const inputNode = (
<div <div
@ -241,10 +253,10 @@ const SelectSelector = defineComponent<SelectorProps>({
autocomplete={autocomplete} autocomplete={autocomplete}
editable={inputEditable.value} editable={inputEditable.value}
activeDescendantId={activeDescendantId} activeDescendantId={activeDescendantId}
value={inputValue.value} value={targetValue.value}
onKeydown={onInputKeyDown} onKeydown={onInputKeyDown}
onMousedown={onInputMouseDown} onMousedown={onInputMouseDown}
onChange={onInputChange} onChange={handleInput}
onPaste={onInputPaste} onPaste={onInputPaste}
onCompositionstart={onInputCompositionStart} onCompositionstart={onInputCompositionStart}
onCompositionend={onInputCompositionEnd} onCompositionend={onInputCompositionEnd}
@ -256,7 +268,7 @@ const SelectSelector = defineComponent<SelectorProps>({
{/* Measure Node */} {/* Measure Node */}
<span ref={measureRef} class={`${selectionPrefixCls.value}-search-mirror`} aria-hidden> <span ref={measureRef} class={`${selectionPrefixCls.value}-search-mirror`} aria-hidden>
{inputValue.value}&nbsp; {targetValue.value}&nbsp;
</span> </span>
</div> </div>
); );
@ -277,7 +289,7 @@ const SelectSelector = defineComponent<SelectorProps>({
return ( return (
<> <>
{selectionNode} {selectionNode}
{!values.length && !inputValue.value && ( {!values.length && !inputValue.value && !compositionStatus && (
<span class={`${selectionPrefixCls.value}-placeholder`}>{placeholder}</span> <span class={`${selectionPrefixCls.value}-placeholder`}>{placeholder}</span>
)} )}
</> </>

View File

@ -10,6 +10,7 @@ interface SelectorProps extends InnerSelectorProps {
inputElement: VueNode; inputElement: VueNode;
activeValue: string; activeValue: string;
optionLabelRender: Function; optionLabelRender: Function;
compositionStatus: boolean;
} }
const props = { const props = {
inputElement: PropTypes.any, inputElement: PropTypes.any,
@ -20,6 +21,7 @@ const props = {
searchValue: String, searchValue: String,
inputRef: PropTypes.any, inputRef: PropTypes.any,
placeholder: PropTypes.any, placeholder: PropTypes.any,
compositionStatus: { type: Boolean, default: undefined },
disabled: { type: Boolean, default: undefined }, disabled: { type: Boolean, default: undefined },
mode: String, mode: String,
showSearch: { type: Boolean, default: undefined }, showSearch: { type: Boolean, default: undefined },
@ -65,7 +67,9 @@ const SingleSelector = defineComponent<SelectorProps>({
// Not show text when closed expect combobox mode // Not show text when closed expect combobox mode
const hasTextInput = computed(() => const hasTextInput = computed(() =>
props.mode !== 'combobox' && !props.open && !props.showSearch ? false : !!inputValue.value, props.mode !== 'combobox' && !props.open && !props.showSearch
? false
: !!inputValue.value || props.compositionStatus,
); );
const title = computed(() => { const title = computed(() => {
@ -86,6 +90,13 @@ const SingleSelector = defineComponent<SelectorProps>({
</span> </span>
); );
}; };
const handleInput = (e: Event) => {
const composing = (e.target as any).composing;
if (!composing) {
inputChanged.value = true;
props.onInputChange(e);
}
};
return () => { return () => {
const { const {
@ -103,7 +114,6 @@ const SingleSelector = defineComponent<SelectorProps>({
optionLabelRender, optionLabelRender,
onInputKeyDown, onInputKeyDown,
onInputMouseDown, onInputMouseDown,
onInputChange,
onInputPaste, onInputPaste,
onInputCompositionStart, onInputCompositionStart,
onInputCompositionEnd, onInputCompositionEnd,
@ -147,10 +157,7 @@ const SingleSelector = defineComponent<SelectorProps>({
value={inputValue.value} value={inputValue.value}
onKeydown={onInputKeyDown} onKeydown={onInputKeyDown}
onMousedown={onInputMouseDown} onMousedown={onInputMouseDown}
onChange={e => { onChange={handleInput}
inputChanged.value = true;
onInputChange(e as any);
}}
onPaste={onInputPaste} onPaste={onInputPaste}
onCompositionstart={onInputCompositionStart} onCompositionstart={onInputCompositionStart}
onCompositionend={onInputCompositionEnd} onCompositionend={onInputCompositionEnd}

View File

@ -15,7 +15,7 @@ import type { CustomTagProps, DisplayValueType, Mode, RenderNode } from '../Base
import { isValidateOpenKey } from '../utils/keyUtil'; import { isValidateOpenKey } from '../utils/keyUtil';
import useLock from '../hooks/useLock'; import useLock from '../hooks/useLock';
import type { PropType } from 'vue'; import type { PropType } from 'vue';
import { defineComponent } from 'vue'; import { defineComponent, ref } from 'vue';
import createRef from '../../_util/createRef'; import createRef from '../../_util/createRef';
import PropTypes from '../../_util/vue-types'; import PropTypes from '../../_util/vue-types';
import type { VueNode } from '../../_util/type'; import type { VueNode } from '../../_util/type';
@ -124,7 +124,7 @@ const Selector = defineComponent<SelectorProps>({
} as any, } as any,
setup(props, { expose }) { setup(props, { expose }) {
const inputRef = createRef(); const inputRef = createRef();
let compositionStatus = false; const compositionStatus = ref(false);
// ====================== Input ====================== // ====================== Input ======================
const [getInputMouseDown, setInputMouseDown] = useLock(0); const [getInputMouseDown, setInputMouseDown] = useLock(0);
@ -140,7 +140,12 @@ const Selector = defineComponent<SelectorProps>({
props.onInputKeyDown(event); props.onInputKeyDown(event);
} }
if (which === KeyCode.ENTER && props.mode === 'tags' && !compositionStatus && !props.open) { if (
which === KeyCode.ENTER &&
props.mode === 'tags' &&
!compositionStatus.value &&
!props.open
) {
// When menu isn't open, OptionList won't trigger a value change // When menu isn't open, OptionList won't trigger a value change
// So when enter is pressed, the tag's input value should be emitted here to let selector know // So when enter is pressed, the tag's input value should be emitted here to let selector know
props.onSearchSubmit((event.target as HTMLInputElement).value); props.onSearchSubmit((event.target as HTMLInputElement).value);
@ -163,17 +168,17 @@ const Selector = defineComponent<SelectorProps>({
let pastedText = null; let pastedText = null;
const triggerOnSearch = (value: string) => { const triggerOnSearch = (value: string) => {
if (props.onSearch(value, true, compositionStatus) !== false) { if (props.onSearch(value, true, compositionStatus.value) !== false) {
props.onToggleOpen(true); props.onToggleOpen(true);
} }
}; };
const onInputCompositionStart = () => { const onInputCompositionStart = () => {
compositionStatus = true; compositionStatus.value = true;
}; };
const onInputCompositionEnd = (e: InputEvent) => { const onInputCompositionEnd = (e: InputEvent) => {
compositionStatus = false; compositionStatus.value = false;
// Trigger search again to support `tokenSeparators` with typewriting // Trigger search again to support `tokenSeparators` with typewriting
if (props.mode !== 'combobox') { if (props.mode !== 'combobox') {
triggerOnSearch((e.target as HTMLInputElement).value); triggerOnSearch((e.target as HTMLInputElement).value);
@ -251,6 +256,7 @@ const Selector = defineComponent<SelectorProps>({
onInputMouseDown: onInternalInputMouseDown, onInputMouseDown: onInternalInputMouseDown,
onInputChange, onInputChange,
onInputPaste, onInputPaste,
compositionStatus: compositionStatus.value,
onInputCompositionStart, onInputCompositionStart,
onInputCompositionEnd, onInputCompositionEnd,
}; };

View File

@ -7,7 +7,7 @@ const Track = (_, { attrs }) => {
length = Math.abs(length); length = Math.abs(length);
offset = 100 - offset; offset = 100 - offset;
} }
const positonStyle = vertical const positionStyle = vertical
? { ? {
[reverse ? 'top' : 'bottom']: `${offset}%`, [reverse ? 'top' : 'bottom']: `${offset}%`,
[reverse ? 'bottom' : 'top']: 'auto', [reverse ? 'bottom' : 'top']: 'auto',
@ -21,7 +21,7 @@ const Track = (_, { attrs }) => {
const elStyle = { const elStyle = {
...style, ...style,
...positonStyle, ...positionStyle,
}; };
return included ? <div class={className} style={elStyle} /> : null; return included ? <div class={className} style={elStyle} /> : null;
}; };

View File

@ -77,7 +77,7 @@ export interface CellProps<RecordType = DefaultRecordType> {
transformCellText?: TransformCellText<RecordType>; transformCellText?: TransformCellText<RecordType>;
} }
export default defineComponent<CellProps>({ export default defineComponent({
name: 'Cell', name: 'Cell',
props: [ props: [
'prefixCls', 'prefixCls',
@ -104,7 +104,7 @@ export default defineComponent<CellProps>({
'column', 'column',
'cellType', 'cellType',
'transformCellText', 'transformCellText',
] as any, ],
setup(props, { slots }) { setup(props, { slots }) {
const contextSlots = useInjectSlots(); const contextSlots = useInjectSlots();
const { onHover, startRow, endRow } = useInjectHover(); const { onHover, startRow, endRow } = useInjectHover();
@ -318,7 +318,7 @@ export default defineComponent<CellProps>({
// ====================== Render ====================== // ====================== Render ======================
let title: string; let title: string;
const ellipsisConfig: CellEllipsisType = ellipsis === true ? { showTitle: true } : ellipsis; const ellipsisConfig = ellipsis === true ? { showTitle: true } : ellipsis;
if (ellipsisConfig && (ellipsisConfig.showTitle || rowType === 'header')) { if (ellipsisConfig && (ellipsisConfig.showTitle || rowType === 'header')) {
if (typeof childNode === 'string' || typeof childNode === 'number') { if (typeof childNode === 'string' || typeof childNode === 'number') {
title = childNode.toString(); title = childNode.toString();

View File

@ -12,9 +12,9 @@ export interface SummaryCellProps {
align?: AlignType; align?: AlignType;
} }
export default defineComponent<SummaryCellProps>({ export default defineComponent({
name: 'ATableSummaryCell', name: 'ATableSummaryCell',
props: ['index', 'colSpan', 'rowSpan', 'align'] as any, props: ['index', 'colSpan', 'rowSpan', 'align'],
setup(props, { attrs, slots }) { setup(props, { attrs, slots }) {
const tableContext = useInjectTable(); const tableContext = useInjectTable();
const summaryContext = useInjectSummary(); const summaryContext = useInjectSummary();

View File

@ -149,7 +149,7 @@ export interface TableProps<RecordType = DefaultRecordType> {
transformCellText?: TransformCellText<RecordType>; transformCellText?: TransformCellText<RecordType>;
} }
export default defineComponent<TableProps<DefaultRecordType>>({ export default defineComponent({
name: 'VcTable', name: 'VcTable',
inheritAttrs: false, inheritAttrs: false,
props: [ props: [
@ -191,7 +191,7 @@ export default defineComponent<TableProps<DefaultRecordType>>({
'canExpandable', 'canExpandable',
'onUpdateInternalRefs', 'onUpdateInternalRefs',
'transformCellText', 'transformCellText',
] as any, ],
emits: ['expand', 'expandedRowsChange', 'updateInternalRefs', 'update:expandedRowKeys'], emits: ['expand', 'expandedRowsChange', 'updateInternalRefs', 'update:expandedRowKeys'],
setup(props, { attrs, slots, emit }) { setup(props, { attrs, slots, emit }) {
const mergedData = computed(() => props.data || EMPTY_DATA); const mergedData = computed(() => props.data || EMPTY_DATA);
@ -271,7 +271,7 @@ export default defineComponent<TableProps<DefaultRecordType>>({
// defalutXxxx // defalutXxxx
stop(); stop();
const mergedExpandedKeys = computed( const mergedExpandedKeys = computed<Set<Key>>(
() => new Set(props.expandedRowKeys || innerExpandedKeys.value || []), () => new Set(props.expandedRowKeys || innerExpandedKeys.value || []),
); );
@ -282,9 +282,9 @@ export default defineComponent<TableProps<DefaultRecordType>>({
const hasKey = mergedExpandedKeys.value.has(key); const hasKey = mergedExpandedKeys.value.has(key);
if (hasKey) { if (hasKey) {
mergedExpandedKeys.value.delete(key); mergedExpandedKeys.value.delete(key);
newExpandedKeys = [...mergedExpandedKeys.value]; newExpandedKeys = [...(mergedExpandedKeys.value as any)];
} else { } else {
newExpandedKeys = [...mergedExpandedKeys.value, key]; newExpandedKeys = [...(mergedExpandedKeys.value as any), key];
} }
innerExpandedKeys.value = newExpandedKeys; innerExpandedKeys.value = newExpandedKeys;

View File

@ -108,7 +108,7 @@ function useColumns<RecordType>(
prefixCls?: Ref<string>; prefixCls?: Ref<string>;
columns?: Ref<ColumnsType<RecordType>>; columns?: Ref<ColumnsType<RecordType>>;
expandable: Ref<boolean>; expandable: Ref<boolean>;
expandedKeys: Ref<Set<Key>>; expandedKeys: ComputedRef<Set<Key>>;
getRowKey: Ref<GetRowKey<RecordType>>; getRowKey: Ref<GetRowKey<RecordType>>;
onTriggerExpand: TriggerEventHandler<RecordType>; onTriggerExpand: TriggerEventHandler<RecordType>;
expandIcon?: Ref<RenderExpandIcon<RecordType>>; expandIcon?: Ref<RenderExpandIcon<RecordType>>;

View File

@ -110,7 +110,7 @@ export default defineComponent({
() => { () => {
treeData.value = treeData.value =
props.treeData !== undefined props.treeData !== undefined
? toRaw(props.treeData).slice() ? props.treeData.slice()
: convertTreeToData(toRaw(props.children)); : convertTreeToData(toRaw(props.children));
}, },
{ {

View File

@ -204,11 +204,11 @@ export default defineComponent({
class={mergedClassName} class={mergedClassName}
onMouseenter={onMouseenter} onMouseenter={onMouseenter}
onMouseleave={onMouseleave} onMouseleave={onMouseleave}
onMousedown={withModifiers(onMousedown, ['capture'])} onMousedown={withModifiers(onMousedown, ['capture'] as any)}
{...{ {...{
[supportsPassive ? 'onTouchstartPassive' : 'onTouchstart']: withModifiers( [supportsPassive ? 'onTouchstartPassive' : 'onTouchstart']: withModifiers(
onTouchstart, onTouchstart,
['capture'], ['capture'] as any,
), ),
}} }}
style={mergedStyle} style={mergedStyle}

View File

@ -112,3 +112,22 @@ export function getOffset(node: any) {
(docElem.clientTop || document.body.clientTop || 0), (docElem.clientTop || document.body.clientTop || 0),
}; };
} }
export function styleToString(style: CSSStyleDeclaration) {
// There are some different behavior between Firefox & Chrome.
// We have to handle this ourself.
const styleNames = Array.prototype.slice.apply(style);
return styleNames.map(name => `${name}: ${style.getPropertyValue(name)};`).join('');
}
export function styleObjectToString(style: Record<string, string>) {
return Object.keys(style).reduce((acc, name) => {
const styleValue = style[name];
if (typeof styleValue === 'undefined' || styleValue === null) {
return acc;
}
acc += `${name}: ${style[name]};`;
return acc;
}, '');
}

View File

@ -1,6 +1,6 @@
{ {
"name": "ant-design-vue", "name": "ant-design-vue",
"version": "4.2.1", "version": "4.2.5",
"title": "Ant Design Vue", "title": "Ant Design Vue",
"description": "An enterprise-class UI design language and Vue-based implementation", "description": "An enterprise-class UI design language and Vue-based implementation",
"keywords": [ "keywords": [

View File

@ -37,7 +37,7 @@ import More from './More.vue';
import Navigation from './Navigation.vue'; import Navigation from './Navigation.vue';
import Ecosystem from './Ecosystem.vue'; import Ecosystem from './Ecosystem.vue';
import { version } from 'ant-design-vue'; import { version } from 'ant-design-vue';
import { isZhCN, isLocalStorageNameSupported, getLocalizedPathname } from '../../utils/util'; import { isZhCN, getLocalizedPathname } from '../../utils/util';
import { useRoute } from 'vue-router'; import { useRoute } from 'vue-router';
export default defineComponent({ export default defineComponent({
name: 'HeaderMenu', name: 'HeaderMenu',
@ -58,9 +58,7 @@ export default defineComponent({
const currentProtocol = `${window.location.protocol}//`; const currentProtocol = `${window.location.protocol}//`;
const currentHref = window.location.href.substring(currentProtocol.length); const currentHref = window.location.href.substring(currentProtocol.length);
if (isLocalStorageNameSupported()) {
localStorage.setItem('locale', isZhCN(pathname) ? 'en-US' : 'zh-CN'); localStorage.setItem('locale', isZhCN(pathname) ? 'en-US' : 'zh-CN');
}
window.location.href = window.location.href =
currentProtocol + currentProtocol +

View File

@ -1,33 +0,0 @@
export function isZhCN(name) {
return /-cn\/?$/.test(name);
}
export function isLocalStorageNameSupported() {
const testKey = 'test';
const storage = window.localStorage;
try {
storage.setItem(testKey, '1');
storage.removeItem(testKey);
return true;
} catch (error) {
return false;
}
}
export function getLocalizedPathname(path, zhCN, query = {}, hash) {
const pathname = path.startsWith('/') ? path : `/${path}`;
let fullPath;
if (!zhCN) {
// to enUS
fullPath = /\/?index-cn/.test(pathname) ? '/' : pathname.replace('-cn', '');
} else if (pathname === '/') {
fullPath = '/index-cn';
} else if (pathname.endsWith('/')) {
fullPath = pathname.replace(/\/$/, '-cn/');
} else {
fullPath = `${pathname}-cn`;
}
if (hash) {
const localHash = hash[zhCN ? 'zhCN' : 'enUS'];
fullPath += `#${localHash}`;
}
return { path: fullPath, query };
}

View File

@ -2,18 +2,6 @@ export function isZhCN(name: string): boolean {
return /-cn\/?$/.test(name); return /-cn\/?$/.test(name);
} }
export function isLocalStorageNameSupported() {
const testKey = 'test';
const storage = window.localStorage;
try {
storage.setItem(testKey, '1');
storage.removeItem(testKey);
return true;
} catch (error) {
return false;
}
}
export function getLocalizedPathname( export function getLocalizedPathname(
path: string, path: string,
zhCN?: boolean, zhCN?: boolean,

View File

@ -12,27 +12,37 @@ The following CodeSandbox demo is the simplest use case, and it's also a good ha
## Import ant-design-vue ## Import ant-design-vue
### 1. Installation ### 1. Create a New Project
[vue-cli](https://github.com/vuejs/vue-cli) If you need to create a new project, you can use [Vite](https://github.com/vitejs/vite), [Rsbuild](https://github.com/web-infra-dev/rsbuild), or [Vue CLI](https://github.com/vuejs/vue-cli).
Please initialize the project using the command line:
- Vite:
```bash
$ npm create vite@latest
```
- Rsbuild:
```bash
$ npm create rsbuild@latest
```
- Vue CLI:
```bash ```bash
$ npm install -g @vue/cli $ npm install -g @vue/cli
# OR # OR
$ yarn global add @vue/cli $ yarn global add @vue/cli
```
### 2. Create a New Project
A new project can be created using CLI tools.
```bash
$ vue create antd-demo $ vue create antd-demo
``` ```
And, setup your vue project configuration. > Vue CLI is no longer maintained, so it is not recommended to use.
### 3. Use antd's Components ### 2. Use antd's Components
#### Install #### Install
@ -95,7 +105,7 @@ In this way, component sub-components, such as Button and ButtonGroup, need to b
</script> </script>
``` ```
### 4. Component list ### 3. Component list
[Component list](https://github.com/vueComponent/ant-design-vue/blob/main/components/components.ts) [Component list](https://github.com/vueComponent/ant-design-vue/blob/main/components/components.ts)

View File

@ -12,29 +12,39 @@ Ant Design Vue 致力于提供给程序员**愉悦**的开发体验。
## 引入 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 ```bash
$ npm install -g @vue/cli $ npm install -g @vue/cli
# OR # OR
$ yarn global add @vue/cli $ yarn global add @vue/cli
```
### 2. 创建一个项目
使用命令行进行初始化。
```bash
$ vue create antd-demo $ vue create antd-demo
``` ```
并配置项目。 > Vue CLI 已经停止迭代,因此不推荐使用。
若安装缓慢报错,可尝试用 `cnpm` 或别的镜像源自行安装:`rm -rf node_modules && cnpm install`。 若安装缓慢报错,可尝试用 `cnpm` 或别的镜像源自行安装:`rm -rf node_modules && cnpm install`。
### 3. 使用组件 ### 2. 使用组件
#### 安装 #### 安装

View File

@ -153,7 +153,7 @@ Remove `babel-plugin-import` from package.json and modify `.babelrc`:
### Legacy browser support ### 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 ## Encounter problems

View File

@ -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)。
## 遇到问题 ## 遇到问题