mirror of
https://github.com/stablyai/orca.git
synced 2026-10-16 16:03:26 +00:00
Handle TUI trackpad wheel reversal
Co-authored-by: Orca <help@stably.ai>
This commit is contained in:
@@ -243,6 +243,21 @@ describe('terminal mouse wheel multiplier', () => {
|
||||
expect(reports).toEqual([0, 0, 0, 1])
|
||||
})
|
||||
|
||||
it('does not burst-boost rapid trackpad-like pixel deltas', () => {
|
||||
const state = createTerminalTuiMouseWheelDistanceState()
|
||||
|
||||
const reports = [0, 16, 32, 48].map((timeStamp) =>
|
||||
resolveTerminalTuiMouseWheelReportCount(
|
||||
{ deltaY: 4, deltaMode: DOM_DELTA_PIXEL, timeStamp },
|
||||
1,
|
||||
state,
|
||||
{ cellHeight: 16 }
|
||||
)
|
||||
)
|
||||
|
||||
expect(reports).toEqual([0, 0, 0, 1])
|
||||
})
|
||||
|
||||
it('resets pending fractional distance when the user changes direction', () => {
|
||||
const state = createTerminalTuiMouseWheelDistanceState()
|
||||
|
||||
@@ -269,7 +284,7 @@ describe('terminal mouse wheel multiplier', () => {
|
||||
expect(shouldMultiplyTerminalMouseWheel(wheelEvent(), terminalElement(false))).toBe(false)
|
||||
})
|
||||
|
||||
it('leaves trackpad-like pixel scrolling one-to-one', () => {
|
||||
it('handles trackpad-like TUI pixel scrolling while mouse reporting is active', () => {
|
||||
expect(
|
||||
shouldMultiplyTerminalMouseWheel(
|
||||
wheelEvent({
|
||||
@@ -278,7 +293,7 @@ describe('terminal mouse wheel multiplier', () => {
|
||||
}),
|
||||
terminalElement()
|
||||
)
|
||||
).toBe(false)
|
||||
).toBe(true)
|
||||
})
|
||||
|
||||
it('multiplies notched mouse wheel ticks even when Chromium exposes a small pixel delta', () => {
|
||||
@@ -358,6 +373,58 @@ describe('terminal mouse wheel multiplier', () => {
|
||||
expect(shouldMultiplyTerminalMouseWheel(dispatched[0]!, target)).toBe(false)
|
||||
})
|
||||
|
||||
it('replays trackpad-like TUI pixel scrolling with responsive direction reversal', 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 events = [
|
||||
[4, 0],
|
||||
[4, 16],
|
||||
[4, 32],
|
||||
[4, 48],
|
||||
[-4, 64],
|
||||
[-4, 80],
|
||||
[-4, 96],
|
||||
[-4, 112]
|
||||
].map(([deltaY, timeStamp]) => {
|
||||
const event = new TestWheelEvent('wheel', {
|
||||
bubbles: true,
|
||||
cancelable: true,
|
||||
deltaMode: DOM_DELTA_PIXEL,
|
||||
deltaY
|
||||
}) as WheelEvent
|
||||
Object.defineProperty(event, 'timeStamp', {
|
||||
configurable: true,
|
||||
value: timeStamp
|
||||
})
|
||||
return event
|
||||
})
|
||||
|
||||
for (const event of events) {
|
||||
expect(handlers[0]?.(event)).toBe(false)
|
||||
await Promise.resolve()
|
||||
}
|
||||
|
||||
expect(dispatched.map((entry) => entry.deltaY)).toEqual([1, -1])
|
||||
})
|
||||
|
||||
it('drains resolved TUI wheel reports without a frame-rate cap', async () => {
|
||||
vi.stubGlobal('WheelEvent', TestWheelEvent)
|
||||
const handlers: ((event: WheelEvent) => boolean)[] = []
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import type { Terminal } from '@xterm/xterm'
|
||||
import {
|
||||
createTerminalTuiMouseWheelDistanceState,
|
||||
isDiscreteTerminalTuiWheelEvent,
|
||||
normalizeTerminalTuiMouseWheelMultiplier,
|
||||
resolveTerminalTuiMouseWheelReportCount,
|
||||
resolveTerminalWheelDirection
|
||||
@@ -110,8 +109,7 @@ export function shouldMultiplyTerminalMouseWheel(
|
||||
isReplayedWheelEvent(event) ||
|
||||
!terminalElement?.classList.contains(XTERM_MOUSE_REPORTING_CLASS) ||
|
||||
event.deltaY === 0 ||
|
||||
event.shiftKey ||
|
||||
!isDiscreteTerminalTuiWheelEvent(event)
|
||||
event.shiftKey
|
||||
) {
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -67,6 +67,11 @@ function legacyVerticalWheelDelta(event: TerminalTuiWheelEventInput): number | n
|
||||
return null
|
||||
}
|
||||
|
||||
function hasDiscreteLegacyWheelDelta(event: TerminalTuiWheelEventInput): boolean {
|
||||
const legacyDelta = legacyVerticalWheelDelta(event)
|
||||
return legacyDelta !== null && Math.abs(legacyDelta) >= LEGACY_MOUSE_WHEEL_DELTA_MIN
|
||||
}
|
||||
|
||||
export function isDiscreteTerminalTuiWheelEvent(event: TerminalTuiWheelEventInput): boolean {
|
||||
if ((event.deltaMode ?? DOM_DELTA_PIXEL) !== DOM_DELTA_PIXEL) {
|
||||
return true
|
||||
@@ -76,8 +81,15 @@ export function isDiscreteTerminalTuiWheelEvent(event: TerminalTuiWheelEventInpu
|
||||
return true
|
||||
}
|
||||
|
||||
const legacyDelta = legacyVerticalWheelDelta(event)
|
||||
return legacyDelta !== null && Math.abs(legacyDelta) >= LEGACY_MOUSE_WHEEL_DELTA_MIN
|
||||
return hasDiscreteLegacyWheelDelta(event)
|
||||
}
|
||||
|
||||
function canBurstBoostWheelEvent(event: TerminalTuiWheelEventInput): boolean {
|
||||
if ((event.deltaMode ?? DOM_DELTA_PIXEL) !== DOM_DELTA_PIXEL) {
|
||||
return true
|
||||
}
|
||||
|
||||
return hasDiscreteLegacyWheelDelta(event)
|
||||
}
|
||||
|
||||
function wheelInputTime(event: TerminalTuiWheelEventInput): number | null {
|
||||
@@ -130,6 +142,13 @@ function resolveBurstWheelDistanceRows(
|
||||
state: TerminalTuiMouseWheelDistanceState,
|
||||
distanceRows: number
|
||||
): number {
|
||||
if (!canBurstBoostWheelEvent(event)) {
|
||||
state.fastStreak = 0
|
||||
state.lastDistanceRows = null
|
||||
state.lastInputAt = null
|
||||
return 0
|
||||
}
|
||||
|
||||
const currentInputAt = wheelInputTime(event)
|
||||
if (currentInputAt === null) {
|
||||
state.fastStreak = 0
|
||||
|
||||
Reference in New Issue
Block a user