diff --git a/src/browser/Terminal.ts b/src/browser/Terminal.ts index fad2d80b..7e1f9014 100644 --- a/src/browser/Terminal.ts +++ b/src/browser/Terminal.ts @@ -26,7 +26,7 @@ import { addDisposableDomListener } from 'browser/Lifecycle'; import { Linkifier2 } from 'browser/Linkifier2'; import * as Strings from 'browser/LocalizableStrings'; import { OscLinkProvider } from 'browser/OscLinkProvider'; -import { CharacterJoinerHandler, CustomKeyEventHandler, IBrowser, IBufferRange, ICompositionHelper, ILinkifier2, ITerminal, IViewport } from 'browser/Types'; +import { CharacterJoinerHandler, CustomKeyEventHandler, CustomWheelEventHandler, IBrowser, IBufferRange, ICompositionHelper, ILinkifier2, ITerminal, IViewport } from 'browser/Types'; import { Viewport } from 'browser/Viewport'; import { BufferDecorationRenderer } from 'browser/decorations/BufferDecorationRenderer'; import { OverviewRulerRenderer } from 'browser/decorations/OverviewRulerRenderer'; @@ -74,6 +74,7 @@ export class Terminal extends CoreTerminal implements ITerminal { public browser: IBrowser = Browser as any; private _customKeyEventHandler: CustomKeyEventHandler | undefined; + private _customWheelEventHandler: CustomWheelEventHandler | undefined; // browser services private _decorationService: DecorationService; @@ -633,6 +634,9 @@ export class Terminal extends CoreTerminal implements ITerminal { but = ev.button < 3 ? ev.button : CoreMouseButton.NONE; break; case 'wheel': + if (self._customWheelEventHandler && self._customWheelEventHandler(ev as WheelEvent) === false) { + return false; + } const amount = self.viewport!.getLinesScrolled(ev as WheelEvent); if (amount === 0) { @@ -792,6 +796,10 @@ export class Terminal extends CoreTerminal implements ITerminal { // do nothing, if app side handles wheel itself if (requestedEvents.wheel) return; + if (this._customWheelEventHandler && this._customWheelEventHandler(ev) === false) { + return false; + } + if (!this.buffer.hasScrollback) { // Convert wheel events into up/down events when the buffer does not have scrollback, this // enables scrolling in apps hosted in the alt buffer such as vim or tmux. @@ -878,19 +886,14 @@ export class Terminal extends CoreTerminal implements ITerminal { paste(data, this.textarea!, this.coreService, this.optionsService); } - /** - * Attaches a custom key event handler which is run before keys are processed, - * giving consumers of xterm.js ultimate control as to what keys should be - * processed by the terminal and what keys should not. - * @param customKeyEventHandler The custom KeyboardEvent handler to attach. - * This is a function that takes a KeyboardEvent, allowing consumers to stop - * propagation and/or prevent the default action. The function returns whether - * the event should be processed by xterm.js. - */ public attachCustomKeyEventHandler(customKeyEventHandler: CustomKeyEventHandler): void { this._customKeyEventHandler = customKeyEventHandler; } + public attachCustomWheelEventHandler(customWheelEventHandler: CustomWheelEventHandler): void { + this._customWheelEventHandler = customWheelEventHandler; + } + public registerLinkProvider(linkProvider: ILinkProvider): IDisposable { return this.linkifier2.registerLinkProvider(linkProvider); } diff --git a/src/browser/TestUtils.test.ts b/src/browser/TestUtils.test.ts index 7e43017a..a969ec2c 100644 --- a/src/browser/TestUtils.test.ts +++ b/src/browser/TestUtils.test.ts @@ -86,6 +86,9 @@ export class MockTerminal implements ITerminal { public attachCustomKeyEventHandler(customKeyEventHandler: (event: KeyboardEvent) => boolean): void { throw new Error('Method not implemented.'); } + public attachCustomWheelEventHandler(customWheelEventHandler: (event: WheelEvent) => boolean): void { + throw new Error('Method not implemented.'); + } public registerCsiHandler(id: IFunctionIdentifier, callback: (params: IParams) => boolean | Promise): IDisposable { throw new Error('Method not implemented.'); } diff --git a/src/browser/Types.d.ts b/src/browser/Types.d.ts index b1f31b20..d1ad5f87 100644 --- a/src/browser/Types.d.ts +++ b/src/browser/Types.d.ts @@ -32,6 +32,7 @@ export interface ITerminal extends InternalPassthroughApis, ICoreTerminal { } export type CustomKeyEventHandler = (event: KeyboardEvent) => boolean; +export type CustomWheelEventHandler = (event: WheelEvent) => boolean; export type LineData = CharData[]; diff --git a/src/browser/public/Terminal.ts b/src/browser/public/Terminal.ts index 6f009f74..ade46fa4 100644 --- a/src/browser/public/Terminal.ts +++ b/src/browser/public/Terminal.ts @@ -148,6 +148,9 @@ export class Terminal extends Disposable implements ITerminalApi { public attachCustomKeyEventHandler(customKeyEventHandler: (event: KeyboardEvent) => boolean): void { this._core.attachCustomKeyEventHandler(customKeyEventHandler); } + public attachCustomWheelEventHandler(customWheelEventHandler: (event: WheelEvent) => boolean): void { + this._core.attachCustomWheelEventHandler(customWheelEventHandler); + } public registerLinkProvider(linkProvider: ILinkProvider): IDisposable { return this._core.registerLinkProvider(linkProvider); } diff --git a/test/playwright/TestUtils.ts b/test/playwright/TestUtils.ts index 3e925f79..4d4112f0 100644 --- a/test/playwright/TestUtils.ts +++ b/test/playwright/TestUtils.ts @@ -75,6 +75,7 @@ type TerminalProxyCustomOverrides = 'buffer' | ( 'options' | 'open' | 'attachCustomKeyEventHandler' | + 'attachCustomWheelEventHandler' | 'registerLinkProvider' | 'registerCharacterJoiner' | 'deregisterCharacterJoiner' | diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index 70b0c6d7..211fce09 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -1010,6 +1010,28 @@ declare module '@xterm/xterm' { */ attachCustomKeyEventHandler(customKeyEventHandler: (event: KeyboardEvent) => boolean): void; + /** + * Attaches a custom wheel event handler which is run before keys are + * processed, giving consumers of xterm.js control over whether to proceed + * or cancel terminal wheel events. + * @param customMouseEventHandler The custom WheelEvent handler to attach. + * This is a function that takes a WheelEvent, allowing consumers to stop + * propagation and/or prevent the default action. The function returns + * whether the event should be processed by xterm.js. + * + * @example A handler that prevents all wheel events while ctrl is held from + * being processed. + * ```ts + * term.attachCustomKeyEventHandler(ev => { + * if (ev.ctrlKey) { + * return false; + * } + * return true; + * }); + * ``` + */ + attachCustomWheelEventHandler(customWheelEventHandler: (event: WheelEvent) => boolean): void; + /** * Registers a link provider, allowing a custom parser to be used to match * and handle links. Multiple link providers can be used, they will be asked