Merge branch 'vueComponent:main' into main

pull/7904/head
backe lee 2025-01-09 14:21:27 +08:00 committed by GitHub
commit e49f063866
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
17 changed files with 287 additions and 23 deletions

View File

@ -10,6 +10,13 @@
---
## 4.2.6
- 🐞 Fix Modal component aria-hidden error problem under chrome [#7823](https://github.com/vueComponent/ant-design-vue/issues/7823)
- 🐞 Fix the problem that the built-in input method of Safari automatically fills in the decimal point when inputting Chinese [#7918](https://github.com/vueComponent/ant-design-vue/issues/7918)
- 🐞 Fix InputNumber component disabled style problem [#7776](https://github.com/vueComponent/ant-design-vue/issues/7776)
- 🐞 Fix Select cannot lose focus problem [#7819](https://github.com/vueComponent/ant-design-vue/issues/7819)
## 4.2.5
- 🐞 Fix Empty component memory leak problem

View File

@ -10,6 +10,13 @@
---
## 4.2.6
- 🐞 修复 Modal 组件在 chrome 下aria-hidden 报错问题 [#7823](https://github.com/vueComponent/ant-design-vue/issues/7823)
- 🐞 修复 Safari 下自带输入法 input 组件输入中文时,自动填写小数点问题 [#7918](https://github.com/vueComponent/ant-design-vue/issues/7918)
- 🐞 修复 InputNumber 组件 disabled 样式问题 [#7776](https://github.com/vueComponent/ant-design-vue/issues/7776)
- 🐞 修复 Select 无法失焦问题 [#7819](https://github.com/vueComponent/ant-design-vue/issues/7819)
## 4.2.5
- 🐞 修复 Empty 组件内存泄漏问题

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

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

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

@ -63,7 +63,7 @@ Select component to select value from options.
| searchValue | The current input "search" text | string | - | |
| showArrow | Whether to show the drop-down arrow | boolean | single:true, multiple:false | |
| showSearch | Whether select is searchable | boolean | single:false, multiple:true | |
| size | Size of Select input. `default` `large` `small` | string | default | |
| size | Size of Select input. `middle` `large` `small` | string | middle | |
| status | Set validation status | 'error' \| 'warning' | - | 3.3.0 |
| suffixIcon | The custom suffix icon | VNode \| slot | - | |
| tagRender | Customize tag render, only applies when `mode` is set to `multiple` or `tags` | slot \| (props) => any | - | |

View File

@ -63,7 +63,7 @@ coverDark: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*5oPiTqPxGAUAAA
| searchValue | 控制搜索文本 | string | - | |
| showArrow | 是否显示下拉小箭头 | boolean | 单选为 true,多选为 false | |
| showSearch | 配置是否可搜索 | boolean | 单选为 false,多选为 true | |
| size | 选择框大小,可选 `large` `small` | string | default | |
| size | 选择框大小,可选 `middle` `large` `small` | string | middle | |
| status | 设置校验状态 | 'error' \| 'warning' | - | 3.3.0 |
| suffixIcon | 自定义的选择框后缀图标 | VNode \| slot | - | |
| tagRender | 自定义 tag 内容 render仅在 `mode``multiple``tags` 时生效 | slot \| (props) => any | - | 3.0 |

View File

@ -24,6 +24,7 @@ Ant Design has 3 types of Tabs for different situations.
| --- | --- | --- | --- | --- |
| activeKey(v-model) | Current TabPane's key | string | - | |
| animated | Whether to change tabs with animation. Only works while tabPosition=`"top"` \| `"bottom"` | boolean \| {inkBar:boolean, tabPane:boolean} | `true`, `false` when `type="card"` | |
| centered | Whether to display the labels in the center | boolean | false | 3.0 |
| destroyInactiveTabPane | Whether destroy inactive TabPane when change tab | boolean | false | |
| hideAdd | Hide plus icon or not. Only works while `type="editable-card"` | boolean | `false` | } |
| size | preset tab bar size | `large` \| `middle` \| `small` | `middle` | |

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

@ -5,7 +5,7 @@ import { getTransitionProps } from '../_util/transition';
import dialogPropTypes from './IDialogPropTypes';
import { offset } from './util';
const sentinelStyle = { width: 0, height: 0, overflow: 'hidden', outline: 'none' };
const entityStyle = { outline: 'none' };
export type ContentRef = {
focus: () => void;
changeActive: (next: boolean) => void;
@ -28,14 +28,14 @@ export default defineComponent({
const dialogRef = ref<HTMLDivElement>();
expose({
focus: () => {
sentinelStartRef.value?.focus();
sentinelStartRef.value?.focus({ preventScroll: true });
},
changeActive: next => {
const { activeElement } = document;
if (next && activeElement === sentinelEndRef.value) {
sentinelStartRef.value.focus();
sentinelStartRef.value.focus({ preventScroll: true });
} else if (!next && activeElement === sentinelStartRef.value) {
sentinelEndRef.value.focus();
sentinelEndRef.value.focus({ preventScroll: true });
}
},
});
@ -143,9 +143,10 @@ export default defineComponent({
onMousedown={onMousedown}
onMouseup={onMouseup}
>
<div tabindex={0} ref={sentinelStartRef} style={sentinelStyle} aria-hidden="true" />
{modalRender ? modalRender({ originVNode: content }) : content}
<div tabindex={0} ref={sentinelEndRef} style={sentinelStyle} aria-hidden="true" />
<div tabindex={0} ref={sentinelStartRef} style={entityStyle}>
{modalRender ? modalRender({ originVNode: content }) : content}
</div>
<div tabindex={0} ref={sentinelEndRef} style={sentinelStyle} />
</div>
) : null}
</Transition>

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

@ -196,7 +196,7 @@ export default defineComponent({
ref={alignRef}
monitorWindowResize
disabled={alignDisabled.value}
align={align}
align={align as any}
onAlign={onInternalAlign}
v-slots={{
default: () => (

View File

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

View File

@ -2,10 +2,11 @@ import type { InputProps } from 'ant-design-vue';
import { ConfigProvider, Input, InputNumber, Select, theme } from 'ant-design-vue';
import classNames from 'ant-design-vue/es/_util/classNames';
import type { PropType } from 'vue';
import { defineComponent, watchEffect, watch, computed, toRefs, ref } from 'vue';
import { defineComponent, watchEffect, computed, toRefs, ref } from 'vue';
import { HexColorPicker, RgbaColorPicker } from '../vue-colorful';
import tinycolor from 'tinycolor2';
import makeStyle from './utils/makeStyle';
import type { ValueType } from 'ant-design-vue/es/input-number/src/utils/MiniDecimal';
const { useToken } = theme;
@ -168,24 +169,42 @@ const RgbColorInput = defineComponent({
setup(props) {
const { value, alpha } = toRefs(props);
watch(value, val => {
props.onChange(val);
});
const handleChange = (val: ValueType, key: 'r' | 'g' | 'b' | 'a') => {
value.value[key] = val;
props.onChange(value.value);
};
return () => {
return (
<div class="color-panel-rgba-input">
<ConfigProvider theme={{ components: { InputNumber: { handleWidth: 12 } } }}>
<div class="color-panel-rgba-input-part">
<InputNumber min={0} max={255} size="small" v-model={[value.value.r, 'value']} />
<InputNumber
min={0}
max={255}
size="small"
value={value.value.r}
onChange={val => handleChange(val, 'r')}
/>
<div class="color-panel-mode-title">R</div>
</div>
<div class="color-panel-rgba-input-part">
<InputNumber min={0} max={255} size="small" v-model={[value.value.g, 'value']} />
<InputNumber
min={0}
max={255}
size="small"
value={value.value.g}
onChange={val => handleChange(val, 'g')}
/>
<div class="color-panel-mode-title">G</div>
</div>
<div class="color-panel-rgba-input-part">
<InputNumber min={0} max={255} size="small" v-model={[value.value.b, 'value']} />
<InputNumber
min={0}
max={255}
size="small"
value={value.value.b}
onChange={val => handleChange(val, 'b')}
/>
<div class="color-panel-mode-title">B</div>
</div>
{alpha.value && (
@ -195,7 +214,8 @@ const RgbColorInput = defineComponent({
max={1}
step={0.01}
size="small"
v-model={[value.value.a, 'value']}
value={value.value.a}
onChange={val => handleChange(val, 'a')}
/>
<div class="color-panel-mode-title">A</div>
</div>