From c7c3e964367f7141b0d0b1df10dc5e4f92d1ad9d Mon Sep 17 00:00:00 2001 From: fangsmile <892739385@qq.com> Date: Tue, 4 Aug 2026 15:08:31 +0800 Subject: [PATCH] feat(vtable): support tooltip appear delay --- ...tooltip-appear-delay_2026-08-04-00-00.json | 11 ++++ docs/assets/api/en/methods.md | 2 + docs/assets/api/zh/methods.md | 2 + docs/assets/guide/en/components/tooltip.md | 1 + docs/assets/guide/zh/components/tooltip.md | 1 + docs/assets/option/en/icon/base-icon.md | 3 + docs/assets/option/zh/icon/base-icon.md | 3 + .../components/listTable-tooltip.test.ts | 37 +++++++++++ .../debug/issue-5112-tooltip-appear-delay.ts | 62 ++++++++++++++++++ packages/vtable/examples/menu.ts | 4 ++ .../src/components/tooltip/TooltipHandler.ts | 64 ++++++++++++++++--- .../vtable/src/scenegraph/icon/icon-update.ts | 1 + packages/vtable/src/state/state.ts | 1 + packages/vtable/src/ts-types/icon.ts | 3 + packages/vtable/src/ts-types/tooltip.ts | 2 + 15 files changed, 189 insertions(+), 8 deletions(-) create mode 100644 common/changes/@visactor/vtable/feat-issue-5112-tooltip-appear-delay_2026-08-04-00-00.json create mode 100644 packages/vtable/examples/debug/issue-5112-tooltip-appear-delay.ts diff --git a/common/changes/@visactor/vtable/feat-issue-5112-tooltip-appear-delay_2026-08-04-00-00.json b/common/changes/@visactor/vtable/feat-issue-5112-tooltip-appear-delay_2026-08-04-00-00.json new file mode 100644 index 0000000000..8a142a0c16 --- /dev/null +++ b/common/changes/@visactor/vtable/feat-issue-5112-tooltip-appear-delay_2026-08-04-00-00.json @@ -0,0 +1,11 @@ +{ + "changes": [ + { + "comment": "feat: support tooltip appear delay", + "type": "minor", + "packageName": "@visactor/vtable" + } + ], + "packageName": "@visactor/vtable", + "email": "github@visactor.io" +} diff --git a/docs/assets/api/en/methods.md b/docs/assets/api/en/methods.md index d24cc48048..d8aa3f4774 100644 --- a/docs/assets/api/en/methods.md +++ b/docs/assets/api/en/methods.md @@ -1492,6 +1492,8 @@ export type TooltipOptions = { padding?: number[]; arrowMark?: boolean; }; + /** Set tooltip appearance delay time */ + appearDelay?: number; /** Set tooltip disappearance time */ disappearDelay?: number; }; diff --git a/docs/assets/api/zh/methods.md b/docs/assets/api/zh/methods.md index 0168642727..b1fd29620e 100644 --- a/docs/assets/api/zh/methods.md +++ b/docs/assets/api/zh/methods.md @@ -1494,6 +1494,8 @@ export type TooltipOptions = { padding?: number[]; arrowMark?: boolean; }; + /** 设置tooltip的出现延迟时间 */ + appearDelay?: number; /** 设置tooltip的消失时间 */ disappearDelay?: number; }; diff --git a/docs/assets/guide/en/components/tooltip.md b/docs/assets/guide/en/components/tooltip.md index 9a388f188f..51a0824d4f 100644 --- a/docs/assets/guide/en/components/tooltip.md +++ b/docs/assets/guide/en/components/tooltip.md @@ -105,6 +105,7 @@ The interface showTooltip can actively display tooltip information, which is use content: 'Order ID:'+tableInstance.getCellValue(col,row), referencePosition: { rect, placement: VTable.TYPES.Placement.right }, //TODO className: 'defineTooltip', + appearDelay: 500, style: { bgColor: 'black', color: 'white', diff --git a/docs/assets/guide/zh/components/tooltip.md b/docs/assets/guide/zh/components/tooltip.md index 61ab428fa6..2077d15e5e 100644 --- a/docs/assets/guide/zh/components/tooltip.md +++ b/docs/assets/guide/zh/components/tooltip.md @@ -110,6 +110,7 @@ const tableInstance = new VTable.ListTable({ content: 'Order ID:'+tableInstance.getCellValue(col,row), referencePosition: { rect, placement: VTable.TYPES.Placement.right }, //TODO className: 'defineTooltip', + appearDelay: 500, style: { bgColor: 'black', color: 'white', diff --git a/docs/assets/option/en/icon/base-icon.md b/docs/assets/option/en/icon/base-icon.md index b681caa518..6026f1a3c9 100644 --- a/docs/assets/option/en/icon/base-icon.md +++ b/docs/assets/option/en/icon/base-icon.md @@ -113,6 +113,9 @@ Placement enumeration definition: } ``` +#${prefix} appearDelay (number) +The delay time for the tooltip to appear. + #${prefix} disappearDelay (number) The delay time for the tooltip to disappear. If you need to move the mouse to the tooltip, please configure this parameter. diff --git a/docs/assets/option/zh/icon/base-icon.md b/docs/assets/option/zh/icon/base-icon.md index 08763c0693..9901ce6604 100644 --- a/docs/assets/option/zh/icon/base-icon.md +++ b/docs/assets/option/zh/icon/base-icon.md @@ -113,6 +113,9 @@ Placement 枚举类型定义: } ``` +#${prefix} appearDelay (number) +提示框显示延迟时间。 + #${prefix} disappearDelay (number) 提示框消失延迟时间,如果有需要鼠标移动到 tooltip 上的需求,请配置上这个参数。 diff --git a/packages/vtable/__tests__/components/listTable-tooltip.test.ts b/packages/vtable/__tests__/components/listTable-tooltip.test.ts index f378fd162a..7d25ff7af9 100644 --- a/packages/vtable/__tests__/components/listTable-tooltip.test.ts +++ b/packages/vtable/__tests__/components/listTable-tooltip.test.ts @@ -110,6 +110,43 @@ describe('listTable-tooltip init test', () => { } }); }); + test('listTable-tooltip supports appearDelay', async () => { + const col = 0; + const row = 2; + const rect = listTable.getVisibleCellRangeRelativeRect({ col, row }); + + listTable.internalProps.tooltipHandler._unbindFromCell(); + listTable.showTooltip(col, row, { + content: 'Order ID:' + listTable.getCellValue(col, row), + referencePosition: { rect, placement: VTable.TYPES.Placement.right }, + appearDelay: 30 + }); + + expect(listTable.internalProps.tooltipHandler._attachInfo).toBeNull(); + await new Promise(resolve => setTimeout(resolve, 10)); + expect(listTable.internalProps.tooltipHandler._attachInfo).toBeNull(); + await new Promise(resolve => setTimeout(resolve, 30)); + expect(listTable.internalProps.tooltipHandler._attachInfo.range).toStrictEqual({ + start: { + col: 0, + row: 2 + }, + end: { + col: 0, + row: 2 + } + }); + + listTable.internalProps.tooltipHandler._unbindFromCell(); + listTable.showTooltip(col, row, { + content: 'Order ID:' + listTable.getCellValue(col, row), + referencePosition: { rect, placement: VTable.TYPES.Placement.right }, + appearDelay: 30 + }); + listTable.internalProps.tooltipHandler._unbindFromCell(); + await new Promise(resolve => setTimeout(resolve, 40)); + expect(listTable.internalProps.tooltipHandler._attachInfo).toBeNull(); + }); // setTimeout(() => { // listTable.release(); // }, 1000); diff --git a/packages/vtable/examples/debug/issue-5112-tooltip-appear-delay.ts b/packages/vtable/examples/debug/issue-5112-tooltip-appear-delay.ts new file mode 100644 index 0000000000..902d03d89a --- /dev/null +++ b/packages/vtable/examples/debug/issue-5112-tooltip-appear-delay.ts @@ -0,0 +1,62 @@ +import * as VTable from '../../src'; + +const CONTAINER_ID = 'vTable'; + +export function createTable() { + const records = [ + { orderId: 'A001', product: 'Laptop', price: 1200 }, + { orderId: 'A002', product: 'Keyboard', price: 120 }, + { orderId: 'A003', product: 'Mouse', price: 80 } + ]; + + const columns: VTable.ColumnsDefine = [ + { + field: 'orderId', + title: 'Order ID', + width: 160 + }, + { + field: 'product', + title: 'Product', + width: 180 + }, + { + field: 'price', + title: 'Price', + width: 120 + } + ]; + + const tableInstance = new VTable.ListTable(document.getElementById(CONTAINER_ID)!, { + records, + columns, + defaultRowHeight: 40, + tooltip: { + renderMode: 'html' + } + }); + + tableInstance.on('mouseenter_cell', args => { + const { col, row } = args; + if (col !== 0 || row < tableInstance.columnHeaderLevelCount) { + return; + } + + const rect = tableInstance.getVisibleCellRangeRelativeRect({ col, row }); + tableInstance.showTooltip(col, row, { + content: `showTooltip appearDelay=1000ms: ${tableInstance.getCellValue(col, row)}`, + referencePosition: { rect, placement: VTable.TYPES.Placement.right }, + className: 'defineTooltip', + appearDelay: 1000, + disappearDelay: 100, + style: { + bgColor: '#202328', + color: '#fff', + padding: [8, 12], + arrowMark: true + } + }); + }); + + (window as any).tableInstance = tableInstance; +} diff --git a/packages/vtable/examples/menu.ts b/packages/vtable/examples/menu.ts index 6fcdf4ccf7..a7969e9098 100644 --- a/packages/vtable/examples/menu.ts +++ b/packages/vtable/examples/menu.ts @@ -86,6 +86,10 @@ export const menus = [ path: 'debug', name: 'issue-celltype-function-undefined' }, + { + path: 'debug', + name: 'issue-5112-tooltip-appear-delay' + }, { path: 'debug', name: 'header-frame-border-null-color' diff --git a/packages/vtable/src/components/tooltip/TooltipHandler.ts b/packages/vtable/src/components/tooltip/TooltipHandler.ts index af4c3660eb..b9bbd080b9 100644 --- a/packages/vtable/src/components/tooltip/TooltipHandler.ts +++ b/packages/vtable/src/components/tooltip/TooltipHandler.ts @@ -22,6 +22,12 @@ type AttachInfo = { range: CellRange; tooltipOptions: TooltipOptions; }; +type PendingInfo = { + id: ReturnType; + col: number; + row: number; + tooltipOptions: TooltipOptions; +}; export interface ITooltipHandler { new (table: BaseTableAPI, confine: boolean): TooltipHandler; } @@ -30,6 +36,7 @@ export class TooltipHandler { private _table: BaseTableAPI; private _tooltipInstances?: { [type: string]: BaseTooltip }; private _attachInfo?: AttachInfo | null; + private _pendingInfo?: PendingInfo | null; private confine?: boolean; //弹出框是否需要限定在canvas区域 constructor(table: BaseTableAPI, confine: boolean) { this._table = table; @@ -38,14 +45,39 @@ export class TooltipHandler { this.confine = confine; } release(): void { + this._clearPendingBind(); const tooltipInstances = this._tooltipInstances; for (const k in tooltipInstances) { tooltipInstances[k]?.release?.(); } delete this._tooltipInstances; this._attachInfo = null; + this._pendingInfo = null; } _bindToCell(col: number, row: number, tooltipOptions?: TooltipOptions): void { + this._clearPendingBind(); + const appearDelay = tooltipOptions?.appearDelay; + if (tooltipOptions && appearDelay && appearDelay > 0) { + this._unbindAttachedTooltip(); + const id = setTimeout(() => { + this._pendingInfo = null; + this._bindToCellImmediately(col, row, tooltipOptions); + }, appearDelay); + this._pendingInfo = { + id, + col, + row, + tooltipOptions + }; + return; + } + this._bindToCellImmediately(col, row, tooltipOptions); + } + _bindToCellImmediately(col: number, row: number, tooltipOptions?: TooltipOptions): void { + if (!tooltipOptions) { + this._unbindAttachedTooltip(); + return; + } const info = this._attachInfo; const instance = this._getTooltipInstanceInfo(col, row); if (info && (!instance || info.instance !== instance)) { @@ -62,6 +94,21 @@ export class TooltipHandler { this._attachInfo = { range, instance, tooltipOptions }; } } + _clearPendingBind(): void { + if (this._pendingInfo) { + clearTimeout(this._pendingInfo.id); + this._pendingInfo = null; + } + } + _unbindAttachedTooltip(): void { + const info = this._attachInfo; + if (!info) { + return; + } + const { instance } = info; + instance?.unbindTooltipElement(); + this._attachInfo = null; + } _move(col: number, row: number, tooltipOptions: TooltipOptions): void { const info = this._attachInfo; if (!info || !cellInRange(info.range, col, row)) { @@ -89,15 +136,13 @@ export class TooltipHandler { instance?.locateTooltipElement(col, row, position, referencePosition, this.confine); } _unbindFromCell(): void { - const info = this._attachInfo; - if (!info) { - return; - } - const { instance } = info; - instance?.unbindTooltipElement(); - this._attachInfo = null; + this._clearPendingBind(); + this._unbindAttachedTooltip(); } _isBindCell(col: number, row: number): boolean { + if (this._pendingInfo && this._pendingInfo.col === col && this._pendingInfo.row === row) { + return true; + } const info = this._attachInfo; if (!info) { return false; @@ -245,7 +290,10 @@ export class TooltipHandler { return instance; } isBinded(tooltipOptions: TooltipOptions) { - if (JSON.stringify(tooltipOptions) === JSON.stringify(this._attachInfo?.tooltipOptions)) { + if ( + JSON.stringify(tooltipOptions) === JSON.stringify(this._attachInfo?.tooltipOptions) || + JSON.stringify(tooltipOptions) === JSON.stringify(this._pendingInfo?.tooltipOptions) + ) { return true; } return false; diff --git a/packages/vtable/src/scenegraph/icon/icon-update.ts b/packages/vtable/src/scenegraph/icon/icon-update.ts index 050f71df9a..b0fcc26291 100644 --- a/packages/vtable/src/scenegraph/icon/icon-update.ts +++ b/packages/vtable/src/scenegraph/icon/icon-update.ts @@ -195,6 +195,7 @@ export function setIconHoverStyle(baseIcon: Icon, col: number, row: number, cell }, placement: baseIcon.tooltip.placement }, + appearDelay: baseIcon.tooltip.appearDelay, disappearDelay: baseIcon.tooltip.disappearDelay, style: Object.assign({}, scene.table.internalProps.theme?.tooltipStyle, baseIcon.tooltip?.style) }; diff --git a/packages/vtable/src/state/state.ts b/packages/vtable/src/state/state.ts index 88b5c974e2..e8eeac4077 100644 --- a/packages/vtable/src/state/state.ts +++ b/packages/vtable/src/state/state.ts @@ -746,6 +746,7 @@ export class StateManager { inlineIcon.tooltip?.style, inlineIcon.attribute?.tooltip?.style ), + appearDelay: inlineIcon.attribute.tooltip.appearDelay, disappearDelay: inlineIcon.attribute.tooltip.disappearDelay }; if (!this.table.internalProps.tooltipHandler.isBinded(tooltipOptions)) { diff --git a/packages/vtable/src/ts-types/icon.ts b/packages/vtable/src/ts-types/icon.ts index 29033baf90..424217d377 100644 --- a/packages/vtable/src/ts-types/icon.ts +++ b/packages/vtable/src/ts-types/icon.ts @@ -67,6 +67,9 @@ export interface IIconBase { maxWidth?: number; maxHeight?: number; }; + /** tooltip 延迟多久显示 */ + appearDelay?: number; + /** tooltip 延迟多久消失 */ disappearDelay?: number; }; /** diff --git a/packages/vtable/src/ts-types/tooltip.ts b/packages/vtable/src/ts-types/tooltip.ts index 12ff04e68a..37841c6442 100644 --- a/packages/vtable/src/ts-types/tooltip.ts +++ b/packages/vtable/src/ts-types/tooltip.ts @@ -29,6 +29,8 @@ export type TooltipOptions = { maxWidth?: number; maxHeight?: number; }; + /** 设置tooltip的出现延迟时间 */ + appearDelay?: number; /** 设置tooltip的消失时间 */ disappearDelay?: number; };