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

View File

@ -10,7 +10,7 @@
<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)
@ -90,21 +90,21 @@ ant-design-vue 是 MIT 协议的开源项目。为了项目能够更好的持续
- [支付宝或微信](https://aliyuncdn.antdv.com/alipay-and-wechat.png)
- ETH: 0x30cc48515d8ae9fefa20ab87226ad7e8ab9c3bc2
## Sponsors
## 赞助商
Become a sponsor and get your logo on our README on Github with a link to your site. [[Become a sponsor](https://opencollective.com/ant-design-vue#sponsor)]
成为赞助商,并在 Github 上的自述文件上获得您的徽标,并链接到您的网站。 [[成为赞助商](https://opencollective.com/ant-design-vue#sponsor)]
<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>
## 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>

View File

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

View File

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

View File

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

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

View File

@ -1,5 +1,5 @@
import type { CSSProperties, ExtractPropTypes, PropType } from 'vue';
import { computed, defineComponent, shallowRef } from 'vue';
import { computed, defineComponent, shallowRef, Transition } from 'vue';
import CloseOutlined from '@ant-design/icons-vue/CloseOutlined';
import CheckCircleOutlined from '@ant-design/icons-vue/CheckCircleOutlined';
import ExclamationCircleOutlined from '@ant-design/icons-vue/ExclamationCircleOutlined';
@ -11,7 +11,7 @@ import InfoCircleFilled from '@ant-design/icons-vue/InfoCircleFilled';
import CloseCircleFilled from '@ant-design/icons-vue/CloseCircleFilled';
import classNames from '../_util/classNames';
import PropTypes from '../_util/vue-types';
import { getTransitionProps, Transition } from '../_util/transition';
import { getTransitionProps } from '../_util/transition';
import { isValidElement } from '../_util/props-util';
import { tuple, withInstall } from '../_util/type';
import { cloneElement } from '../_util/vnode';

View File

@ -3,9 +3,9 @@ import ScrollNumber from './ScrollNumber';
import classNames from '../_util/classNames';
import { getPropsSlot, flattenChildren } from '../_util/props-util';
import { cloneElement } from '../_util/vnode';
import { getTransitionProps, Transition } from '../_util/transition';
import { getTransitionProps } from '../_util/transition';
import type { ExtractPropTypes, CSSProperties, PropType } from 'vue';
import { defineComponent, computed, ref, watch } from 'vue';
import { defineComponent, computed, ref, watch, Transition } from 'vue';
import Ribbon from './Ribbon';
import useConfigInject from '../config-provider/hooks/useConfigInject';
import isNumeric from '../_util/isNumeric';

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

View File

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

View File

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

View File

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

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 defaultRenderEmpty from './renderEmpty';
import type { RenderEmptyHandler } from './renderEmpty';
@ -7,7 +7,6 @@ import LocaleProvider, { ANT_MARK } from '../locale-provider';
import LocaleReceiver from '../locale-provider/LocaleReceiver';
import type { MaybeRef } from '../_util/type';
import message from '../message';
import notification from '../notification';
import { registerTheme } from './cssVariables';

View File

@ -85,7 +85,7 @@ The following APIs are shared by DatePicker, RangePicker.
| disabled | Determine whether the DatePicker is disabled | boolean | false | |
| disabledDate | Specify the date that cannot be selected | (currentDate: dayjs) => boolean | - | |
| format | To set the date format, refer to [dayjs](https://day.js.org/). When an array is provided, all values are used for parsing and first value is used for formatting, support [Custom Format](#components-date-picker-demo-format) | [formatType](#formattype) | `YYYY-MM-DD` | |
| dropdownClassName | To customize the className of the popup calendar | string | - | |
| popupClassName | To customize the className of the popup calendar | string | - | |
| getPopupContainer | To set the container of the floating layer, while the default is to create a `div` element in `body` | function(trigger) | - | |
| inputReadOnly | Set the `readonly` attribute of the input tag (avoids virtual keyboard on touch devices) | boolean | false | |
| locale | Localization configuration | object | [default](https://github.com/vueComponent/ant-design-vue/blob/main/components/date-picker/locale/example.json) | |

View File

@ -86,7 +86,7 @@ coverDark: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*3OpRQKcygo8AAA
| disabled | 禁用 | boolean | false | |
| disabledDate | 不可选择的日期 | (currentDate: dayjs) => boolean | - | |
| format | 设置日期格式,为数组时支持多格式匹配,展示以第一个为准。配置参考 [dayjs](https://day.js.org/docs/zh-CN/display/format),支持[自定义格式](#components-date-picker-demo-format) | [formatType](#formattype) | `YYYY-MM-DD` | |
| dropdownClassName | 额外的弹出日历 className | string | - | |
| popupClassName | 额外的弹出日历 className | string | - | |
| getPopupContainer | 定义浮层的容器,默认为 body 上新建 div | function(trigger) | - | |
| inputReadOnly | 设置输入框为只读(避免在移动设备上打开虚拟键盘) | boolean | false | |
| locale | 国际化配置 | object | [默认配置](https://github.com/vueComponent/ant-design-vue/blob/main/components/date-picker/locale/example.json) | - |

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 |
| destroyPopupOnHide | 关闭后是否销毁 Dropdown | boolean | false | 3.0 |
| disabled | 菜单是否禁用 | boolean | - | |

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

@ -52,6 +52,10 @@ const genGridRowStyle: GenerateStyle<GridRowToken> = (token): CSSObject => {
justifyContent: 'space-around',
},
'&-space-evenly ': {
justifyContent: 'space-evenly',
},
// Align at the top
'&-top': {
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
#### vue cli 3
You can import SVG icon as an vue component by using `vue cli 3` and [`vue-svg-loader`](https://www.npmjs.com/package/vue-svg-loader). `vue-svg-loader`'s `options` [reference](https://github.com/visualfanatic/vue-svg-loader).
```js
@ -149,6 +151,84 @@ export default defineComponent({
});
```
#### Rsbuild
Rsbuild is a new generation of build tool, official website https://rsbuild.dev/
Create your own `vue-svg-loader.js` file, which allows you to customize and beautify SVG, and then configure it in `rsbuild.config.ts`
```js
// vue-svg-loader.js
/* eslint-disable */
const { optimize } = require('svgo');
const { version } = require('vue');
const semverMajor = require('semver/functions/major');
module.exports = async function (svg) {
const callback = this.async();
try {
({ data: svg } = await optimize(svg, {
path: this.resourcePath,
js2svg: {
indent: 2,
pretty: true,
},
plugins: [
'convertStyleToAttrs',
'removeDoctype',
'removeXMLProcInst',
'removeComments',
'removeMetadata',
'removeTitle',
'removeDesc',
'removeStyleElement',
'removeXMLNS',
'removeXMLProcInst',
],
}));
} catch (error) {
callback(error);
return;
}
if (semverMajor(version) === 2) {
svg = svg.replace('<svg', '<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:
| Property | Description | Type | Default |

View File

@ -119,7 +119,9 @@ export default defineComponent({
### 自定义 SVG 图标
如果使用 `vue cli 3`,可以通过配置 [vue-svg-loader](https://www.npmjs.com/package/vue-svg-loader) 来将 `svg` 图标作为 `Vue` 组件导入。更多`vue-svg-loader` 的使用方式请参阅 [文档](https://github.com/visualfanatic/vue-svg-loader)。
#### vue cli 3
可以通过配置 [vue-svg-loader](https://www.npmjs.com/package/vue-svg-loader) 来将 `svg` 图标作为 `Vue` 组件导入。更多`vue-svg-loader` 的使用方式请参阅 [文档](https://github.com/visualfanatic/vue-svg-loader)。
```js
// vue.config.js
@ -146,6 +148,88 @@ export default defineComponent({
});
```
#### Rsbuild
Rsbuild 是新一代构建工具,官网 https://rsbuild.dev/
自己实现一个 `vue-svg-loader.js` 文件,好处是可以自定义美化 svg然后在 `rsbuild.config.ts` 中配置:
```js
// vue-svg-loader.js
/* eslint-disable */
const { optimize } = require('svgo');
const { version } = require('vue');
const semverMajor = require('semver/functions/major');
module.exports = async function (svg) {
const callback = this.async();
try {
({ data: svg } = await optimize(svg, {
path: this.resourcePath,
js2svg: {
indent: 2,
pretty: true,
},
plugins: [
'convertStyleToAttrs',
'removeDoctype',
'removeXMLProcInst',
'removeComments',
'removeMetadata',
'removeTitle',
'removeDesc',
'removeStyleElement',
'removeXMLNS',
'removeXMLProcInst',
],
}));
} catch (error) {
callback(error);
return;
}
if (semverMajor(version) === 2) {
svg = svg.replace('<svg', '<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` 组件的接受的属性如下:
| 字段 | 说明 | 类型 | 只读值 |

View File

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

View File

@ -72,7 +72,7 @@ const InputNumber = defineComponent({
const mergedSize = computed(() => compactSize.value || size.value);
const mergedValue = shallowRef(props.value === undefined ? props.defaultValue : props.value);
const mergedValue = shallowRef(props.value ?? props.defaultValue);
const focused = shallowRef(false);
watch(
() => props.value,

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -38,7 +38,7 @@ const genSpaceStyle: GenerateStyle<SpaceToken> = token => {
alignItems: 'baseline',
},
},
[`${componentCls}-space-item`]: {
[`${componentCls}-item`]: {
'&:empty': {
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 | |
| centered | 标签居中展示 | boolean | false | 3.0 | |
| destroyInactiveTabPane | 被隐藏时是否销毁 DOM 结构 | boolean | false | | |
| hideAdd | 是否隐藏加号图标,在 `type="editable-card"` 时有效 | boolean | false | | |
| size | 大小,提供 `large` `middle``small` 三种大小 | string | `middle` | | |
| tabBarGutter | tabs 之间的间隙 | number | | | |
| tabBarGutter | tabs 之间的间隙 | number | - | | |
| tabBarStyle | tab bar 的样式对象 | CSSProperties | - | | |
| tabPosition | 页签位置,可选值有 `top` `right` `bottom` `left` | string | `top` | | |
| type | 页签的基本样式,可选 `line`、`card` `editable-card` 类型 | string | `line` | | |
@ -60,8 +60,8 @@ Ant Design 依次提供了三级选项卡,分别用于不同的场景。
| 参数 | 说明 | 类型 | 默认值 |
| ----------- | ------------------------- | ------------ | ------ |
| forceRender | 被隐藏时是否渲染 DOM 结构 | boolean | false |
| key | 对应 activeKey | string | |
| tab | 选项卡头显示文字 | string\|slot | |
| key | 对应 activeKey | string | - |
| tab | 选项卡头显示文字 | string\|slot | - |
### 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
@ -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` | |
| arrow | 修改箭头的显示状态以及修改箭头是否指向目标元素中心 | boolean \| { pointAtCenter: boolean} | `true` | 4.2.0 |
| autoAdjustOverflow | 气泡被遮挡时自动调整位置 | boolean | `true` | |
| color | 背景颜色 | string | | |
| color | 背景颜色 | string | - | |
| destroyTooltipOnHide | 隐藏后是否销毁 tooltip | boolean | false | |
| getPopupContainer | 浮层渲染父节点,默认渲染到 body 上 | (triggerNode: HTMLElement) => HTMLElement | () => document.body | |
| mouseEnterDelay | 鼠标移入后延时多少才显示 Tooltip单位秒 | number | 0.1 | |
| mouseLeaveDelay | 鼠标移出后延时多少才隐藏 Tooltip单位秒 | number | 0.1 | |
| overlayClassName | 卡片类名 | string | | |
| overlayStyle | 卡片样式 | object | | |
| overlayInnerStyle | 卡片内容区域样式 | object | | 4.0 |
| overlayClassName | 卡片类名 | string | - | |
| overlayStyle | 卡片样式 | object | - | |
| overlayInnerStyle | 卡片内容区域样式 | object | - | 4.0 |
| placement | 气泡框位置,可选 `top` `left` `right` `bottom` `topLeft` `topRight` `bottomLeft` `bottomRight` `leftTop` `leftBottom` `rightTop` `rightBottom` | string | top | |
| trigger | 触发行为,可选 `hover/focus/click/contextmenu` | string | hover | |
| open(v-model) | 用于手动控制浮层显隐, 小于 4.0.0 使用 `visible` | boolean | false | 4.0 |

View File

@ -1,5 +1,6 @@
import { computed, defineComponent, toRefs } from 'vue';
import classNames from '../_util/classNames';
import { isFunction } from '../_util/util';
import CloseOutlined from '@ant-design/icons-vue/CloseOutlined';
import { tourStepProps } from './interface';
import type { TourBtnProps } from './interface';
@ -119,7 +120,9 @@ const panelRender = defineComponent({
size="small"
class={classNames(`${prefixCls}-prev-btn`, prevButtonProps?.className)}
>
{prevButtonProps?.children ?? contextLocale.Previous}
{isFunction(prevButtonProps?.children)
? prevButtonProps.children()
: prevButtonProps?.children ?? contextLocale.Previous}
</Button>
) : null}
<Button
@ -129,8 +132,11 @@ const panelRender = defineComponent({
size="small"
class={classNames(`${prefixCls}-next-btn`, nextButtonProps?.className)}
>
{nextButtonProps?.children ??
(isLastStep.value ? contextLocale.Finish : contextLocale.Next)}
{isFunction(nextButtonProps?.children)
? nextButtonProps?.children()
: isLastStep.value
? contextLocale.Finish
: contextLocale.Next}
</Button>
</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 | |
| 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 | - | |
| 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 | |
| selectable | Set whether the treeNode can be selected | boolean | true | |
| 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 | |
| disabled | 禁掉响应 | boolean | false | |
| icon | 自定义图标。可接收组件props 为当前节点 props | slot\|slot-scope | - | |
| isLeaf | 设置为叶子节点(设置了`loadData`时有效) | boolean | false | |
| isLeaf | 设置为叶子节点(设置了`loadData`时有效) | boolean | - | |
| key | 被树的 (default)ExpandedKeys / (default)CheckedKeys / (default)SelectedKeys 属性所用。注意:整个树范围内的所有节点的 key 值不能重复! | string \| number | 内部计算出的节点位置 | |
| selectable | 设置节点是否可被选中 | boolean | true | |
| style | 节点的 style | string\|object | - | |

View File

@ -1,5 +1,6 @@
import type { CSSProperties, VNodeTypes } from 'vue';
import { createApp } from 'vue';
import { styleToString } from '../vc-util/Dom/css';
interface MeasureResult {
finished: boolean;
@ -23,13 +24,6 @@ const wrapperStyle: CSSProperties = {
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) {
target.setAttribute('aria-hidden', 'true');
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 EyeOutlined from '@ant-design/icons-vue/EyeOutlined';
import DeleteOutlined from '@ant-design/icons-vue/DeleteOutlined';
@ -15,7 +23,7 @@ import type {
} from '../interface';
import type { VueNode } from '../../_util/type';
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';
export const listItemProps = () => {

View File

@ -17,11 +17,12 @@ import {
onMounted,
shallowRef,
watchEffect,
TransitionGroup,
} from 'vue';
import { filterEmpty, initDefaultProps, isValidElement } from '../../_util/props-util';
import type { VueNode } from '../../_util/type';
import useConfigInject from '../../config-provider/hooks/useConfigInject';
import { getTransitionGroupProps, TransitionGroup } from '../../_util/transition';
import { getTransitionGroupProps } from '../../_util/transition';
import collapseMotion from '../../_util/collapseMotion';
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 | | | |
| action | 上传的地址 | string\|(file) => `Promise` | | | |
| beforeUpload | 上传文件之前的钩子,参数为上传的文件,若返回 `false` 则停止上传。支持返回一个 Promise 对象Promise 对象 reject 时则停止上传resolve 时开始上传( resolve 传入 `File``Blob` 对象则上传 resolve 传入对象)。 | (file, fileList) => `boolean` \| `Promise` | | |
| customRequest | 通过覆盖默认的上传行为,可以自定义自己的上传实现 | function | | | |
| data | 上传所需参数或返回上传参数的方法 | object\|(file) => object | | | |
| accept | 接受上传的文件类型, 详见 [input accept Attribute](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/file#accept) | string | - | | |
| action | 上传的地址 | string\|(file) => `Promise` | - | | |
| beforeUpload | 上传文件之前的钩子,参数为上传的文件,若返回 `false` 则停止上传。支持返回一个 Promise 对象Promise 对象 reject 时则停止上传resolve 时开始上传( resolve 传入 `File``Blob` 对象则上传 resolve 传入对象)。 | (file, fileList) => `boolean` \| `Promise` | - | |
| customRequest | 通过覆盖默认的上传行为,可以自定义自己的上传实现 | function | - | | |
| data | 上传所需参数或返回上传参数的方法 | object\|(file) => object | - | | |
| directory | 支持上传文件夹([caniuse](https://caniuse.com/#feat=input-file-directory) | boolean | false | 3.0 | |
| disabled | 是否禁用 | boolean | - | | |
| downloadIcon | 自定义下载 icon | v-slot:iconRender="{file: UploadFile}" | - | 3.0 | |
| fileList | 已经上传的文件列表(受控) | object\[] | | | |
| headers | 设置上传的请求头部IE10 以上有效 | object | | | |
| fileList | 已经上传的文件列表(受控) | object\[] | - | | |
| headers | 设置上传的请求头部IE10 以上有效 | object | - | | |
| iconRender | 自定义显示 icon | v-slot:iconRender="{file: UploadFile, listType?: UploadListType}" | - | 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 | |
@ -40,7 +40,7 @@ coverDark: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*l1nlSryXib8AAA
| multiple | 是否支持多选文件,`ie10+` 支持。开启后按住 ctrl 可选择多个文件。 | boolean | false | | |
| name | 发到后台的文件参数名 | string | `file` | | |
| 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 | |
| progress | 自定义进度条样式 | [ProgressProps](/components/progress/#api)(仅支持 `type="line"` | { strokeWidth: 2, showInfo: false } | 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 |
| drop | 当文件被拖入上传区域时执行的回调功能 | (event: DragEvent) => void | - | 3.0 |
| preview | 点击文件链接或预览图标时的回调 | function(file) | | |
| reject | 拖拽文件不符合 accept 类型时的回调 | function(fileList) | | |
| preview | 点击文件链接或预览图标时的回调 | function(file) | - | |
| reject | 拖拽文件不符合 accept 类型时的回调 | function(fileList) | - | |
| remove   | 点击移除文件时的回调,返回值为 false 时不移除。支持返回一个 Promise 对象Promise 对象 resolve(false) 或 reject 时不移除 | function(file): boolean \| Promise | -   | 3.0 |
### UploadFile

View File

@ -1,7 +1,7 @@
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 Transition, { getTransitionProps } from '../_util/transition';
import { getTransitionProps } from '../_util/transition';
import dialogPropTypes from './IDialogPropTypes';
import { offset } from './util';
const sentinelStyle = { width: 0, height: 0, overflow: 'hidden', outline: 'none' };
@ -143,9 +143,9 @@ export default defineComponent({
onMousedown={onMousedown}
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}
<div tabindex={0} ref={sentinelEndRef} style={sentinelStyle} aria-hidden="true" />
<div tabindex={0} ref={sentinelEndRef} style={sentinelStyle} inert/>
</div>
) : null}
</Transition>

View File

@ -1,5 +1,5 @@
import { defineComponent } from 'vue';
import Transition, { getTransitionProps } from '../_util/transition';
import { defineComponent, Transition } from 'vue';
import { getTransitionProps } from '../_util/transition';
export default defineComponent({
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 isNumber from 'lodash-es/isNumber';
import cn from '../../_util/classNames';
@ -32,6 +32,8 @@ export const imageProps = () => ({
rootClassName: String,
prefixCls: String,
previewPrefixCls: String,
width: [Number, String],
height: [Number, String],
previewMask: {
type: [Boolean, Function] as PropType<false | (() => any)>,
default: undefined,
@ -201,8 +203,6 @@ const ImageInternal = defineComponent({
placeholder,
wrapperStyle,
rootClassName,
} = props;
const {
width,
height,
crossorigin,
@ -213,7 +213,7 @@ const ImageInternal = defineComponent({
usemap,
class: cls,
style,
} = attrs as ImgHTMLAttributes;
} = { ...props, ...attrs } as any;
const { icons, maskClassName, ...dialogProps } = preview.value;
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 { CSSProperties } from 'vue';
import {
toRaw,
shallowRef,
createVNode,
computed,
@ -72,10 +73,10 @@ type NotificationState = {
holderCallback?: HolderReadyCallback;
}[];
const Notification = defineComponent<NotificationProps>({
const Notification = defineComponent({
name: 'Notification',
inheritAttrs: false,
props: ['prefixCls', 'transitionName', 'animation', 'maxCount', 'closeIcon', 'hashId'] as any,
props: ['prefixCls', 'transitionName', 'animation', 'maxCount', 'closeIcon', 'hashId'],
setup(props, { attrs, expose, slots }) {
const hookRefs = new Map<Key, HTMLDivElement>();
const notices = ref<NotificationState>([]);
@ -125,7 +126,7 @@ const Notification = defineComponent<NotificationProps>({
};
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;
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 */
ssr: String as PropType<'full'>,
onMousedown: Function as PropType<MouseEventHandler>,
role: String,
};
};
type InterOverflowProps = Partial<ExtractPropTypes<ReturnType<typeof overflowProps>>>;
export type OverflowProps = HTMLAttributes & InterOverflowProps;
const Overflow = defineComponent<OverflowProps>({
const Overflow = defineComponent({
name: 'Overflow',
inheritAttrs: false,
props: overflowProps() as any,
props: overflowProps(),
emits: ['visibleChange'],
setup(props, { attrs, emit, slots }) {
const fullySSR = computed(() => props.ssr === 'full');
@ -331,6 +332,7 @@ const Overflow = defineComponent<OverflowProps>({
class={classNames(!invalidate.value && prefixCls, className)}
style={style}
onMousedown={onMousedown}
role={props.role}
{...restAttrs}
>
{mergedData.value.map(internalRenderItemNode)}

View File

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

View File

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

View File

@ -343,6 +343,14 @@ export default defineComponent({
if (mergedOpen.value !== nextOpen && !props.disabled) {
setInnerOpen(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) {
let blurTimeout = null;
const VCSelectContainerEvent = inject('VCSelectContainerEvent') as any;
return () => {
const {
prefixCls,
@ -97,6 +96,7 @@ const Input = defineComponent({
ref: inputRef,
disabled,
tabindex,
lazy: false,
autocomplete: autocomplete || 'off',
autofocus,
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 Input from './Input';
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 pickAttrs from '../../_util/pickAttrs';
import PropTypes from '../../_util/vue-types';
@ -24,6 +24,8 @@ type SelectorProps = InnerSelectorProps & {
tagRender?: (props: CustomTagProps) => VueNode;
onToggleOpen: any;
compositionStatus: boolean;
// Motion
choiceTransitionName?: string;
@ -46,7 +48,7 @@ const props = {
autocomplete: String,
activeDescendantId: String,
tabindex: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
compositionStatus: Boolean,
removeIcon: PropTypes.any,
choiceTransitionName: String,
@ -91,11 +93,14 @@ const SelectSelector = defineComponent<SelectorProps>({
() =>
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
onMounted(() => {
watch(
inputValue,
targetValue,
() => {
inputWidth.value = measureRef.value.scrollWidth;
},
@ -202,6 +207,14 @@ const SelectSelector = defineComponent<SelectorProps>({
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 () => {
const {
id,
@ -215,14 +228,13 @@ const SelectSelector = defineComponent<SelectorProps>({
autocomplete,
activeDescendantId,
tabindex,
onInputChange,
compositionStatus,
onInputPaste,
onInputKeyDown,
onInputMouseDown,
onInputCompositionStart,
onInputCompositionEnd,
} = props;
// >>> Input Node
const inputNode = (
<div
@ -241,10 +253,10 @@ const SelectSelector = defineComponent<SelectorProps>({
autocomplete={autocomplete}
editable={inputEditable.value}
activeDescendantId={activeDescendantId}
value={inputValue.value}
value={targetValue.value}
onKeydown={onInputKeyDown}
onMousedown={onInputMouseDown}
onChange={onInputChange}
onChange={handleInput}
onPaste={onInputPaste}
onCompositionstart={onInputCompositionStart}
onCompositionend={onInputCompositionEnd}
@ -256,7 +268,7 @@ const SelectSelector = defineComponent<SelectorProps>({
{/* Measure Node */}
<span ref={measureRef} class={`${selectionPrefixCls.value}-search-mirror`} aria-hidden>
{inputValue.value}&nbsp;
{targetValue.value}&nbsp;
</span>
</div>
);
@ -277,7 +289,7 @@ const SelectSelector = defineComponent<SelectorProps>({
return (
<>
{selectionNode}
{!values.length && !inputValue.value && (
{!values.length && !inputValue.value && !compositionStatus && (
<span class={`${selectionPrefixCls.value}-placeholder`}>{placeholder}</span>
)}
</>

View File

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

View File

@ -15,7 +15,7 @@ import type { CustomTagProps, DisplayValueType, Mode, RenderNode } from '../Base
import { isValidateOpenKey } from '../utils/keyUtil';
import useLock from '../hooks/useLock';
import type { PropType } from 'vue';
import { defineComponent } from 'vue';
import { defineComponent, ref } from 'vue';
import createRef from '../../_util/createRef';
import PropTypes from '../../_util/vue-types';
import type { VueNode } from '../../_util/type';
@ -124,7 +124,7 @@ const Selector = defineComponent<SelectorProps>({
} as any,
setup(props, { expose }) {
const inputRef = createRef();
let compositionStatus = false;
const compositionStatus = ref(false);
// ====================== Input ======================
const [getInputMouseDown, setInputMouseDown] = useLock(0);
@ -140,7 +140,12 @@ const Selector = defineComponent<SelectorProps>({
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
// 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);
@ -163,17 +168,17 @@ const Selector = defineComponent<SelectorProps>({
let pastedText = null;
const triggerOnSearch = (value: string) => {
if (props.onSearch(value, true, compositionStatus) !== false) {
if (props.onSearch(value, true, compositionStatus.value) !== false) {
props.onToggleOpen(true);
}
};
const onInputCompositionStart = () => {
compositionStatus = true;
compositionStatus.value = true;
};
const onInputCompositionEnd = (e: InputEvent) => {
compositionStatus = false;
compositionStatus.value = false;
// Trigger search again to support `tokenSeparators` with typewriting
if (props.mode !== 'combobox') {
triggerOnSearch((e.target as HTMLInputElement).value);
@ -251,6 +256,7 @@ const Selector = defineComponent<SelectorProps>({
onInputMouseDown: onInternalInputMouseDown,
onInputChange,
onInputPaste,
compositionStatus: compositionStatus.value,
onInputCompositionStart,
onInputCompositionEnd,
};

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -112,3 +112,22 @@ export function getOffset(node: any) {
(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",
"version": "4.2.1",
"version": "4.2.5",
"title": "Ant Design Vue",
"description": "An enterprise-class UI design language and Vue-based implementation",
"keywords": [

View File

@ -37,7 +37,7 @@ import More from './More.vue';
import Navigation from './Navigation.vue';
import Ecosystem from './Ecosystem.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';
export default defineComponent({
name: 'HeaderMenu',
@ -58,9 +58,7 @@ export default defineComponent({
const currentProtocol = `${window.location.protocol}//`;
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 =
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);
}
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: string,
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
### 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
$ npm install -g @vue/cli
# OR
$ yarn global add @vue/cli
```
### 2. Create a New Project
A new project can be created using CLI tools.
```bash
$ 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
@ -95,7 +105,7 @@ In this way, component sub-components, such as Button and ButtonGroup, need to b
</script>
```
### 4. Component list
### 3. Component list
[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
### 1. 安装脚手架工具
### 1. 新建项目
[vue-cli](https://github.com/vuejs/vue-cli)
如果你需要新建一个项目,可以使用 [Vite](https://github.com/vitejs/vite)、[Rsbuild](https://github.com/web-infra-dev/rsbuild) 或 [Vue CLI](https://github.com/vuejs/vue-cli)。
请使用命令行来初始化项目:
- Vite:
```bash
$ npm create vite@latest
```
- Rsbuild:
```bash
$ npm create rsbuild@latest
```
- Vue CLI:
```bash
$ npm install -g @vue/cli
# OR
$ yarn global add @vue/cli
```
### 2. 创建一个项目
使用命令行进行初始化。
```bash
$ vue create antd-demo
```
并配置项目。
> Vue CLI 已经停止迭代,因此不推荐使用。
若安装缓慢报错,可尝试用 `cnpm` 或别的镜像源自行安装:`rm -rf node_modules && cnpm install`。
### 3. 使用组件
### 2. 使用组件
#### 安装

View File

@ -153,7 +153,7 @@ Remove `babel-plugin-import` from package.json and modify `.babelrc`:
### Legacy browser support
Ant Design Vue v4 using `:where` css selector to reduce CSS-in-JS hash priority. You can use `StyleProvider` to cancel this function. Please ref [Compatible adjustment](/docs/vue/customize-theme#compatible-adjustment).
Ant Design Vue v4 using `:where` css selector to reduce CSS-in-JS hash priority. You can use `StyleProvider` to cancel this function. Please ref [Compatible adjustment](/docs/vue/compatible-style).
## Encounter problems

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