From 41cf2e65362f0a152d66ceb4f41f35b3691927f6 Mon Sep 17 00:00:00 2001 From: fangsmile <892739385@qq.com> Date: Sun, 20 Sep 2026 09:20:36 +0800 Subject: [PATCH 1/2] fix(vtable): enlarge fill handle hit area Make the fill handle easier to grab without changing its visual size. Co-Authored-By: Claude Sonnet 4.6 noreply@anthropic.com --- ...fill-handle-hit-area_2026-09-20-10-00.json | 11 ++++ .../__tests__/fill-handle-hit-area.test.ts | 55 ++++++++++++++++++ .../debug/issue-4656-fill-handle-hit-area.ts | 56 +++++++++++++++++++ packages/vtable/examples/menu.ts | 4 ++ packages/vtable/src/event/event.ts | 5 +- 5 files changed, 129 insertions(+), 2 deletions(-) create mode 100644 common/changes/@visactor/vtable/fix-issue-4656-fill-handle-hit-area_2026-09-20-10-00.json create mode 100644 packages/vtable/__tests__/fill-handle-hit-area.test.ts create mode 100644 packages/vtable/examples/debug/issue-4656-fill-handle-hit-area.ts diff --git a/common/changes/@visactor/vtable/fix-issue-4656-fill-handle-hit-area_2026-09-20-10-00.json b/common/changes/@visactor/vtable/fix-issue-4656-fill-handle-hit-area_2026-09-20-10-00.json new file mode 100644 index 000000000..d56ff9512 --- /dev/null +++ b/common/changes/@visactor/vtable/fix-issue-4656-fill-handle-hit-area_2026-09-20-10-00.json @@ -0,0 +1,11 @@ +{ + "changes": [ + { + "comment": "fix(vtable): enlarge the fill handle pointer hit area", + "type": "patch", + "packageName": "@visactor/vtable" + } + ], + "packageName": "@visactor/vtable", + "email": "892739385@qq.com" +} diff --git a/packages/vtable/__tests__/fill-handle-hit-area.test.ts b/packages/vtable/__tests__/fill-handle-hit-area.test.ts new file mode 100644 index 000000000..efc17a0b9 --- /dev/null +++ b/packages/vtable/__tests__/fill-handle-hit-area.test.ts @@ -0,0 +1,55 @@ +// @ts-nocheck +import { ListTable } from '../src'; +import { createDiv, removeDom } from './dom'; + +global.__VERSION__ = 'none'; + +describe('fill handle hit area', () => { + let container: HTMLElement; + let table: ListTable; + + beforeEach(() => { + container = createDiv(); + container.style.width = '400px'; + container.style.height = '300px'; + table = new ListTable(container, { + columns: [ + { field: 'name', title: 'Name', width: 120 }, + { field: 'value', title: 'Value', width: 120 } + ], + records: [ + { name: 'A', value: 1 }, + { name: 'B', value: 2 } + ], + excelOptions: { + fillHandle: true + } + }); + table.selectCell(0, 1); + }); + + afterEach(() => { + table.release(); + removeDom(container); + }); + + function checkHit(offset: number) { + const bounds = table.scenegraph.highPerformanceGetCell(0, 1).globalAABBBounds; + return table.eventManager.checkCellFillhandle({ + abstractPos: { + x: bounds.x2 + offset, + y: bounds.y2 + offset + }, + eventArgs: {} + }); + } + + test('uses a 24px hit target without enlarging the visual handle', () => { + expect(checkHit(11)).toBe(true); + expect(checkHit(13)).toBe(false); + + const selectComponent = Array.from(table.scenegraph.selectedRangeComponents.values())[0]; + expect(selectComponent.fillhandle.attribute.width).toBe(6); + expect(selectComponent.fillhandle.attribute.height).toBe(6); + }); +}); diff --git a/packages/vtable/examples/debug/issue-4656-fill-handle-hit-area.ts b/packages/vtable/examples/debug/issue-4656-fill-handle-hit-area.ts new file mode 100644 index 000000000..65c008799 --- /dev/null +++ b/packages/vtable/examples/debug/issue-4656-fill-handle-hit-area.ts @@ -0,0 +1,56 @@ +import * as VTable from '../../src'; + +interface Issue4656Window extends Window { + tableInstance?: VTable.ListTable; + issue4656Run?: () => boolean; +} + +const CONTAINER_ID = 'vTable'; + +export function createTable() { + const container = document.getElementById(CONTAINER_ID); + if (!container) { + throw new Error(`Missing #${CONTAINER_ID} container`); + } + container.style.width = '640px'; + container.style.height = '360px'; + + const tableInstance = new VTable.ListTable(container, { + columns: [ + { field: 'name', title: 'Name', width: 180 }, + { field: 'value', title: 'Value', width: 180 } + ], + records: [ + { name: 'Alpha', value: 1 }, + { name: 'Beta', value: 2 }, + { name: 'Gamma', value: 3 } + ], + excelOptions: { + fillHandle: true + } + }); + + tableInstance.selectCell(0, 1); + + const runCheck = () => { + const bounds = tableInstance.scenegraph.highPerformanceGetCell(0, 1).globalAABBBounds; + const createEvent = (offset: number): Parameters[0] => ({ + abstractPos: { + x: bounds.x2 + offset, + y: bounds.y2 + offset + }, + eventArgs: {} + }); + const expandedAreaHit = tableInstance.eventManager.checkCellFillhandle(createEvent(11)); + const outsideAreaMiss = !tableInstance.eventManager.checkCellFillhandle(createEvent(13)); + const pass = expandedAreaHit && outsideAreaMiss; + container.style.outline = `4px solid ${pass ? '#52c41a' : '#ff4d4f'}`; + return pass; + }; + + requestAnimationFrame(runCheck); + + const issueWindow = window as Issue4656Window; + issueWindow.tableInstance = tableInstance; + issueWindow.issue4656Run = runCheck; +} diff --git a/packages/vtable/examples/menu.ts b/packages/vtable/examples/menu.ts index b30a28fde..7bbfe9639 100644 --- a/packages/vtable/examples/menu.ts +++ b/packages/vtable/examples/menu.ts @@ -74,6 +74,10 @@ export const menus = [ path: 'debug', name: 'issue-3903-delete-all-bottom-frozen' }, + { + path: 'debug', + name: 'issue-4656-fill-handle-hit-area' + }, { path: 'debug', name: 'issue-3752-large-merge-auto-height' diff --git a/packages/vtable/src/event/event.ts b/packages/vtable/src/event/event.ts index 4b890d6a4..011613d3c 100644 --- a/packages/vtable/src/event/event.ts +++ b/packages/vtable/src/event/event.ts @@ -50,6 +50,8 @@ type ClipboardRange = { end: { col: number; row: number }; }; +const FILL_HANDLE_HIT_TARGET_SIZE = 24; + type CopySnapshot = { ranges: ClipboardRange[]; copySourceRange: { startCol: number; startRow: number } | null; @@ -706,11 +708,10 @@ export class EventManager { } const distanceY = Math.abs(eventArgsSet.abstractPos.y - lastCellBoundTargetY); - const squareSize = 6 * 3; // 判断鼠标是否落在fillhandle矩形内 if ( this.table.stateManager.fillHandle?.isFilling || - (distanceX <= squareSize / 2 && distanceY <= squareSize / 2) + (distanceX <= FILL_HANDLE_HIT_TARGET_SIZE / 2 && distanceY <= FILL_HANDLE_HIT_TARGET_SIZE / 2) ) { if (update) { this.table.stateManager.startFillSelect(eventArgsSet.abstractPos.x, eventArgsSet.abstractPos.y); From 70998755b0d99bb22461fd69d77a2a1d2c691e57 Mon Sep 17 00:00:00 2001 From: fangsmile <892739385@qq.com> Date: Sun, 20 Sep 2026 09:39:27 +0800 Subject: [PATCH 2/2] docs(vtable): clarify fill handle target sizing Document why the pointer target intentionally exceeds the visible handle. Co-Authored-By: Claude Sonnet 4.6 noreply@anthropic.com --- packages/vtable/src/event/event.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/vtable/src/event/event.ts b/packages/vtable/src/event/event.ts index 011613d3c..dbf5c85a3 100644 --- a/packages/vtable/src/event/event.ts +++ b/packages/vtable/src/event/event.ts @@ -50,6 +50,7 @@ type ClipboardRange = { end: { col: number; row: number }; }; +// Keep the pointer target larger than the 6px visual handle without changing its appearance. const FILL_HANDLE_HIT_TARGET_SIZE = 24; type CopySnapshot = {