Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,19 @@ docClass: timeline
---


## 🌈 1.14.5 `2026-01-21`
### 🐞 Bug Fixes
- `Table`: 修复远程分页场景下,全选逻辑错误引起展示异常的问题 @RSS1102 ([#3801](https://github.com/Tencent/tdesign-vue/pull/3801))
- `Menu`: 修复在 Safari 浏览器中点击展开图标没有变换方向的问题 @liweijie0812 ([#3797](https://github.com/Tencent/tdesign-vue/pull/3797))
- `Menu`: 修复 `1.14.2` 后 menu-item 绝对定位样式丢失导致层级设置不生效的问题 @RSS1102 ([#3804](https://github.com/Tencent/tdesign-vue/pull/3804))
- `Select`: 修复在 Safari 浏览器中点击展开图标没有变换方向的问题 @liweijie0812 ([#3797](https://github.com/Tencent/tdesign-vue/pull/3797))
- `TreeSelect`: 修复在 Safari 浏览器中点击展开图标没有变换方向的问题 @liweijie0812 ([#3797](https://github.com/Tencent/tdesign-vue/pull/3797))
- `Cascader`: 修复在 Safari 浏览器中点击展开图标没有变换方向的问题 @liweijie0812 ([#3797](https://github.com/Tencent/tdesign-vue/pull/3797))
- `Table`: 优化存在固定表头或表尾场景滚动后表格位置异常的问题 @uyarn ([#3805](https://github.com/Tencent/tdesign-vue/pull/3805))




## 🌈 1.14.4 `2025-12-26`
### 🐞 Bug Fixes
- `Drawer`: 修复 `DOM` 元素未正确移除的问题 @RSS1102 ([#3788](https://github.com/Tencent/tdesign-vue/pull/3788))
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "tdesign-vue",
"purename": "tdesign",
"version": "1.14.4-naruto",
"version": "1.14.5-naruto",
"description": "tdesign-vue",
"title": "tdesign-vue",
"keywords": [
Expand Down
2 changes: 2 additions & 0 deletions src/common-components/fake-arrow.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { getClassPrefixMixins } from '../config-provider/config-receiver';
import mixins from '../utils/mixins';
import { isSafari } from '../_common/js/utils/helper';

const classPrefixMixins = getClassPrefixMixins('fake-arrow');

Expand All @@ -24,6 +25,7 @@ export default mixins(classPrefixMixins).extend({
return [
this.componentName,
{
[`${this.componentName}--transform`]: isSafari(),
[`${this.componentName}--active`]: this.isActive,
},
this.overlayClassName,
Expand Down
4 changes: 2 additions & 2 deletions src/menu/head-menu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -134,12 +134,12 @@ export default defineComponent({
methods: {
renderNormalSubmenu(node: VMenuData[], depth: number) {
if (node.length === 0) return null;

return (
<ul class={[`${this.classPrefix}-head-menu__submenu`, `${this.classPrefix}-submenu`]}>
{
<Tabs value={this.activeValue} onChange={this.handleTabChange}>
{this.submenu.map((item) => (
{/* 由于virtual child机制,这里通过vMenu getChild 会有两个相同的节点,故做此处理 */}
{this.submenu.slice(0, this.submenu.length / 2).map((item) => (
<TabPanel value={item.value} label={item.vnode[0].text}>
{item.children && item.children.length > 0
? this.renderNormalSubmenu(item.children, depth + 1)
Expand Down
22 changes: 19 additions & 3 deletions src/menu/submenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
reactive,
} from 'vue';
import { isFunction } from 'lodash-es';
import { State } from '@popperjs/core';
import props from './submenu-props';
import { renderContent, renderTNodeJSX } from '../utils/render-tnode';
import FakeArrow from '../common-components/fake-arrow';
Expand Down Expand Up @@ -90,8 +91,8 @@ export default defineComponent({
const submenuClass = computed(() => [
`${classPrefix.value}-menu__item`,
`${classPrefix.value}-menu__item-spacer`,
`${classPrefix.value}-menu__item-spacer--${isHead && !isNested.value ? 'bottom' : 'right'}`,
{
[`${classPrefix.value}-menu__item-spacer--right`]: !isHead || isNested.value,
[`${classPrefix.value}-is-disabled`]: props.disabled,
[`${classPrefix.value}-is-opened`]: isOpen.value,
[`${classPrefix.value}-is-active`]: isActive.value,
Expand Down Expand Up @@ -255,12 +256,27 @@ export default defineComponent({
placement = 'bottom-left';
}

// 上下位置变化时,添加 bottom 和 top 类,用于添加 bottom 和 top 伪元素
const placementChange = (state: State) => {
const spacerEl = this.$refs.popupWrapperRef as HTMLElement;
if (!spacerEl) return;

const prefixClassName = `${this.classPrefix}-menu__spacer`;
const isBottom = state.placement.startsWith('bottom');
const isTop = state.placement.startsWith('top');

spacerEl.classList.toggle(`${prefixClassName}--bottom`, isBottom);
spacerEl.classList.toggle(`${prefixClassName}--top`, isTop);
};

const popupWrapper = (
<div
ref="popupWrapperRef"
class={[
`${this.classPrefix}-menu__spacer`,
`${this.classPrefix}-menu__spacer--${!this.isNested && this.isHead ? 'top' : 'left'}`,
{
[`${this.classPrefix}-menu__spacer--left`]: this.isNested || !this.isHead,
},
]}
onMouseenter={this.handleEnterPopup}
onMouseleave={this.handleMouseLeavePopup}
Expand All @@ -270,13 +286,13 @@ export default defineComponent({
);
const realPopup = (
<Popup
popperContentElement="overlay"
{...((this.popupProps ?? {}) as TdSubmenuProps['popupProps'])}
overlayInnerClassName={[...this.overlayInnerClassName]}
overlayClassName={[...this.overlayClassName]}
visible={this.popupVisible}
placement={(this.popupProps as TdSubmenuProps['popupProps'])?.placement ?? (placement as PopupPlacement)}
content={() => popupWrapper}
on={{ 'placement-change': placementChange }}
>
<div ref="submenuRef" class={this.submenuClass}>
{triggerElement}
Expand Down
39 changes: 17 additions & 22 deletions src/popup/popup.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { PropType, VNodeDirective } from 'vue';
import { VNodeDirective } from 'vue';
import { createPopper } from '@popperjs/core';
import { debounce } from 'lodash-es';
import { on, off, once } from '../utils/dom';
Expand Down Expand Up @@ -38,13 +38,6 @@ export default mixins(classPrefixMixins, getAttachConfigMixins('popup')).extend(

props: {
...props,
/** @private
* @description popper 内容元素,用于自定义 popper 元素时传入
* 可以是 HTMLElement 或者 ref 名称字符串 (如 'overlay')
*/
popperContentElement: {
type: [String, Object] as PropType<string | HTMLElement>,
},
expandAnimation: {
type: Boolean,
},
Expand All @@ -58,7 +51,7 @@ export default mixins(classPrefixMixins, getAttachConfigMixins('popup')).extend(
/** popperjs instance */
popper: null as ReturnType<typeof createPopper>,
/** timeout id */
timeout: null,
timeout: null as ReturnType<typeof setTimeout> | null,
hasDocumentEvent: false,
/** if a trusted action (opening or closing) is prevented, increase this flag */
visibleState: 0,
Expand Down Expand Up @@ -200,22 +193,16 @@ export default mixins(classPrefixMixins, getAttachConfigMixins('popup')).extend(
methods: {
updatePopper() {
const { $el: triggerEl } = this;
// 支持传入字符串 ref 名称或 HTMLElement
let popperEl: HTMLElement;
if (typeof this.popperContentElement === 'string') {
popperEl = this.$refs[this.popperContentElement] as HTMLElement;
} else {
popperEl = this.popperContentElement || (this.$refs.popper as HTMLElement);
}
const popperEl = this.$refs.popper as HTMLElement;

if (!popperEl || !this.visible) return;
if (this.popper) {
this.popper.update();
return;
}
this.popper = createPopper(triggerEl, popperEl, {
modifiers:
getIEVersion() > 9
modifiers: [
...(getIEVersion() > 9
? []
: [
{
Expand All @@ -227,7 +214,16 @@ export default mixins(classPrefixMixins, getAttachConfigMixins('popup')).extend(
gpuAcceleration: false,
},
},
],
]),
{
name: 'onPlacementChange',
enabled: true,
phase: 'main',
fn: ({ state }) => {
this.$emit('placement-change', state);
},
},
],
placement: getPopperPlacement(this.placement as TdPopupProps['placement']),
onFirstUpdate: () => {
this.$nextTick(this.updatePopper);
Expand Down Expand Up @@ -376,9 +372,8 @@ export default mixins(classPrefixMixins, getAttachConfigMixins('popup')).extend(
}
},
onAfterEnter() {
if (this.visible && this.popper) {
// 动画完成后,元素已有正确尺寸,使用 forceUpdate 强制重新运行所有 modifiers
this.popper.forceUpdate();
if (this.visible) {
this.updatePopper();
}
},
onLeave() {
Expand Down
7 changes: 7 additions & 0 deletions src/table/base-table.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ export default defineComponent({
onHorizontalScroll,
updateAffixHeaderOrFooter,
setTableContentRef,
updateHorizontalScroll,
} = useAffix(props);

const { showElement } = useElementLazyRender(tableRef, lazyLoad);
Expand All @@ -127,6 +128,12 @@ export default defineComponent({
tableContentRef,
);

watch(innerPagination, () => {
if (showAffixHeader || showAffixFooter || showAffixPagination) {
updateHorizontalScroll();
}
});

const onInnerResizeChange: BaseTableProps['onColumnResizeChange'] = (p) => {
props.onColumnResizeChange?.(p);
context.emit('column-resize-change', p);
Expand Down
8 changes: 8 additions & 0 deletions src/table/hooks/useAffix.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ export default function useAffix(props: TdBaseTableProps) {
const showAffixFooter = ref(true);
// 当表格完全滚动消失在视野时,需要隐藏吸底分页器
const showAffixPagination = ref(true);

const scrollLeftValue = ref(0);
// 当鼠标按下拖动内容来滚动时,需要更新表头位置(Windows 按下鼠标横向滚动,滚动结束后,再松开鼠标)
let isMousedown = false;
let isMouseInScrollableArea = false;
Expand All @@ -53,6 +55,7 @@ export default function useAffix(props: TdBaseTableProps) {
// 如果 lastScrollLeft 等于 left,说明不是横向滚动,不需要更新横向滚动距离
if (lastScrollLeft === left) return;
lastScrollLeft = left;
scrollLeftValue.value = left;
// 表格内容、吸顶表头、吸底表尾、吸底横向滚动更新
const toUpdateScrollElement = [
tableContentRef.value,
Expand All @@ -67,6 +70,10 @@ export default function useAffix(props: TdBaseTableProps) {
}
};

const updateHorizontalScroll = () => {
tableContentRef.value.scrollLeft = lastScrollLeft;
};

// 吸底的元素(footer、分页器)是否显示
const isAffixedBottomElementShow = (elementRect: DOMRect, tableRect: DOMRect, headerHeight: number) => tableRect.top + headerHeight < elementRect.top && elementRect.top > elementRect.height;

Expand Down Expand Up @@ -349,5 +356,6 @@ export default function useAffix(props: TdBaseTableProps) {
onHorizontalScroll,
setTableContentRef,
updateAffixHeaderOrFooter,
updateHorizontalScroll,
};
}
18 changes: 8 additions & 10 deletions src/table/hooks/useRowSelect.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,12 @@ export default function useRowSelect(
} = toRefs(props);
const currentPaginateData = ref<TableRowData[]>(data.value);
const selectedRowClassNames = ref();

// 远程分页场景下,data 变化时需要同步更新 currentPaginateData
watch(data, (newData) => {
currentPaginateData.value = newData;
});

const [tSelectedRowKeys, setTSelectedRowKeys] = useDefaultValue(
selectedRowKeys,
props.defaultSelectedRowKeys || [],
Expand Down Expand Up @@ -79,17 +85,9 @@ export default function useRowSelect(
// 判断条件直接写在jsx中,防止变量被computed捕获,选中行重新计算了columns
return () => (
<Checkbox
checked={
canSelectedRows.value.length !== 0
&& intersectionKeys.value.length === canSelectedRows.value.length
// 确保所有已选中的行都是可见的(没有被折叠而隐藏的选中项)
&& intersectionKeys.value.length === tSelectedRowKeys.value.length
}
checked={canSelectedRows.value.length !== 0 && intersectionKeys.value.length === canSelectedRows.value.length}
indeterminate={
// 一些可见的行已被选中,但不是全部
(intersectionKeys.value.length > 0 && intersectionKeys.value.length < canSelectedRows.value.length)
// 某些被选中的行不可见(例如折叠的树子节点)
|| intersectionKeys.value.length < tSelectedRowKeys.value.length
intersectionKeys.value.length > 0 && intersectionKeys.value.length < canSelectedRows.value.length
}
disabled={!canSelectedRows.value.length}
{...{ on: { change: handleSelectAll } }}
Expand Down
4 changes: 2 additions & 2 deletions src/table/primary-table-props.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ export default {
defaultDisplayColumns: {
type: Array as PropType<TdPrimaryTableProps['defaultDisplayColumns']>,
},
/** 拖拽排序方式,值为 `row` 表示行拖拽排序,这种方式无法进行文本复制,慎用。值为`row-handler` 表示通过拖拽手柄进行行拖拽排序。值为 `col` 表示列顺序拖拽。值为 `row-handler-col` 表示同时支持行拖拽和列拖拽。⚠️`drag-col` 已废弃,请勿使用 */
/** 拖拽排序方式,值为 `row` 表示行拖拽排序,这种方式无法进行文本复制,慎用。值为`row-handler` 表示通过拖拽手柄进行行拖拽排序。值为 `col` 表示列顺序拖拽。值为 `row-handler-col` 表示同时支持行拖拽和列拖拽。⚠️`drag-col` 已废弃,请勿使用 */
dragSort: {
type: String as PropType<TdPrimaryTableProps['dragSort']>,
validator(val: TdPrimaryTableProps['dragSort']): boolean {
Expand Down Expand Up @@ -104,7 +104,7 @@ export default {
},
/** 是否支持多列排序 */
multipleSort: Boolean,
/** 行选中功能,是否在分页时保留上一页选中结果不清空,本地数据分页场景下,会全选所有页数据。值为 `false` 则表示全部选中操作停留在当前页,不跨分页;本地数据分页场景下,全选仅选中当前页 */
/** 行选中功能,是否在分页时保留上一页选中结果不清空。分页场景下,会全选所有页数据,保留跨分页数据。值为 `false` 则表示全部选中操作停留在当前页,不跨分页 */
reserveSelectedRowOnPaginate: {
type: Boolean,
default: true,
Expand Down
Loading
Loading