Cap TUI trackpad report batches

Co-authored-by: Orca <help@stably.ai>
This commit is contained in:
Jinwoo-H
2026-07-03 01:04:16 -04:00
co-authored by Orca
parent 9ef8b4b536
commit 25c1a4f5b8
2 changed files with 78 additions and 6 deletions
@@ -270,7 +270,7 @@ describe('terminal mouse wheel multiplier', () => {
)
)
expect(reports).toEqual([1, 1, 2, 2, 0, 0, 0, 0])
expect(reports).toEqual([1, 1, 1, 1, 0, 0, 0, 0])
})
it('suppresses alternating trackpad-like momentum after direction changes', () => {
@@ -285,7 +285,22 @@ describe('terminal mouse wheel multiplier', () => {
)
)
expect(reports).toEqual([1, 1, 2, 1, 1, 2, 0, 0, 0, 0])
expect(reports).toEqual([1, 1, 1, 1, 1, 1, 0, 0, 0, 0])
})
it('does not batch multiple reports for one trackpad-like pixel event', () => {
const state = createTerminalTuiMouseWheelDistanceState()
const reports = [16 * 12, 16 * 12, 16 * 12].map((deltaY, index) =>
resolveTerminalTuiMouseWheelReportCount(
{ deltaY, deltaMode: DOM_DELTA_PIXEL, timeStamp: index * 16 },
1,
state,
{ cellHeight: 16 }
)
)
expect(reports).toEqual([1, 1, 1])
})
it('resets pending fractional distance when the user changes direction', () => {
@@ -455,6 +470,40 @@ describe('terminal mouse wheel multiplier', () => {
expect(dispatched.map((entry) => entry.deltaY)).toEqual([1, -1])
})
it('does not enqueue a synthetic report batch for a large trackpad-like pixel event', async () => {
vi.stubGlobal('WheelEvent', TestWheelEvent)
const handlers: ((event: WheelEvent) => boolean)[] = []
const target = Object.assign(new EventTarget(), {
classList: {
contains: (className: string) => className === 'enable-mouse-events'
}
}) as unknown as EventTarget & HTMLElement
const dispatched: WheelEvent[] = []
target.addEventListener('wheel', (event) => dispatched.push(event as WheelEvent))
attachTerminalMouseWheelMultiplier(
{
attachCustomWheelEventHandler: (handler) => {
handlers.push(handler)
},
element: target,
rows: 24
},
{ getTuiMouseWheelMultiplier: () => 1 }
)
const event = new TestWheelEvent('wheel', {
bubbles: true,
cancelable: true,
deltaMode: DOM_DELTA_PIXEL,
deltaY: 16 * 12
}) as WheelEvent
expect(handlers[0]?.(event)).toBe(false)
await Promise.resolve()
expect(dispatched.map((entry) => entry.deltaY)).toEqual([1])
})
it('drains resolved TUI wheel reports without a frame-rate cap', async () => {
vi.stubGlobal('WheelEvent', TestWheelEvent)
const handlers: ((event: WheelEvent) => boolean)[] = []
@@ -106,8 +106,7 @@ function canBurstBoostWheelEvent(event: TerminalTuiWheelEventInput): boolean {
function isTrackpadLikePixelWheelEvent(event: TerminalTuiWheelEventInput): boolean {
return (
(event.deltaMode ?? DOM_DELTA_PIXEL) === DOM_DELTA_PIXEL &&
!isDiscreteTerminalTuiWheelEvent(event)
(event.deltaMode ?? DOM_DELTA_PIXEL) === DOM_DELTA_PIXEL && !hasDiscreteLegacyWheelDelta(event)
)
}
@@ -264,6 +263,29 @@ function shouldSuppressTrackpadMomentumTail(
return false
}
function resolveTrackpadPixelWheelReportCount(
event: TerminalTuiWheelEventInput,
state: TerminalTuiMouseWheelDistanceState,
distanceRows: number
): number | null {
if (!isTrackpadLikePixelWheelEvent(event)) {
return null
}
if (shouldSuppressTrackpadMomentumTail(event, state, distanceRows)) {
return 0
}
const totalRows = state.pendingRows + distanceRows
if (totalRows < 1) {
state.pendingRows = totalRows
return 0
}
state.pendingRows = 0
return 1
}
export function normalizeTerminalTuiMouseWheelMultiplier(value: number | undefined): number {
if (typeof value !== 'number' || !Number.isFinite(value)) {
return TERMINAL_TUI_MOUSE_WHEEL_MULTIPLIER
@@ -292,8 +314,9 @@ export function resolveTerminalTuiMouseWheelReportCount(
state.pendingDirection = direction
const distanceRows = resolveWheelDistanceRows(event, metrics)
if (shouldSuppressTrackpadMomentumTail(event, state, distanceRows)) {
return 0
const trackpadReportCount = resolveTrackpadPixelWheelReportCount(event, state, distanceRows)
if (trackpadReportCount !== null) {
return trackpadReportCount
}
const rows =