diff --git a/src/Interfaces.ts b/src/Interfaces.ts index a0d75a54..790bf419 100644 --- a/src/Interfaces.ts +++ b/src/Interfaces.ts @@ -241,9 +241,13 @@ export interface ILinkMatcherOptions { */ validationCallback?: LinkMatcherValidationCallback; /** - * A callback that fired when the mouse hovers over a link. + * A callback that fires when the mouse hovers over a link. */ - hoverCallback?: LinkMatcherHandler; + hoverStartCallback?: LinkMatcherHandler; + /** + * A callback that fires when the mouse leaves a link that was hovered. + */ + hoverEndCallback?: () => void; /** * The priority of the link matcher, this defines the order in which the link * matcher is evaluated relative to others, from highest to lowest. The diff --git a/src/Linkifier.ts b/src/Linkifier.ts index e04ad767..2adb6f63 100644 --- a/src/Linkifier.ts +++ b/src/Linkifier.ts @@ -133,7 +133,8 @@ export class Linkifier { handler, matchIndex: options.matchIndex, validationCallback: options.validationCallback, - hoverCallback: options.hoverCallback, + hoverStartCallback: options.hoverStartCallback, + hoverEndCallback: options.hoverEndCallback, priority: options.priority || 0 }; this._addLinkMatcherToList(matcher); @@ -259,8 +260,13 @@ export class Linkifier { window.open(uri, '_blank'); }, e => { - if (matcher.hoverCallback) { - return matcher.hoverCallback(e, uri); + if (matcher.hoverStartCallback) { + matcher.hoverStartCallback(e, uri); + } + }, + () => { + if (matcher.hoverEndCallback) { + matcher.hoverEndCallback(); } } )); diff --git a/src/Types.ts b/src/Types.ts index 1ced3d73..1af21e78 100644 --- a/src/Types.ts +++ b/src/Types.ts @@ -6,7 +6,8 @@ export type LinkMatcher = { id: number, regex: RegExp, handler: LinkMatcherHandler, - hoverCallback?: LinkMatcherHandler, + hoverStartCallback?: LinkMatcherHandler, + hoverEndCallback?: () => void, matchIndex?: number, validationCallback?: LinkMatcherValidationCallback, priority?: number diff --git a/src/input/Interfaces.ts b/src/input/Interfaces.ts index 15dcbae9..8756e540 100644 --- a/src/input/Interfaces.ts +++ b/src/input/Interfaces.ts @@ -8,5 +8,6 @@ export interface IMouseZone { x2: number; y: number; clickCallback: (e: MouseEvent) => any; - hoverCallback?: (e: MouseEvent) => any; + hoverStartCallback?: (e: MouseEvent) => any; + hoverEndCallback?: () => any; } diff --git a/src/input/MouseZoneManager.ts b/src/input/MouseZoneManager.ts index 37a6647d..7789082e 100644 --- a/src/input/MouseZoneManager.ts +++ b/src/input/MouseZoneManager.ts @@ -20,7 +20,8 @@ export class MouseZoneManager implements IMouseZoneManager { private _mouseDownListener: (e: MouseEvent) => any; private _clickListener: (e: MouseEvent) => any; - private _hoverTimeout: number; + private _hoverTimeout: number = null; + private _currentZone: IMouseZone = null; private _lastHoverCoords: [number, number] = [null, null]; constructor( @@ -60,11 +61,22 @@ export class MouseZoneManager implements IMouseZoneManager { } private _onMouseMove(e: MouseEvent): void { + // TODO: Ideally this would only clear the hover state when the mouse moves + // outside of the mouse zone if (this._lastHoverCoords[0] !== e.pageX && this._lastHoverCoords[1] !== e.pageY) { + // Restart the timeout if (this._hoverTimeout) { clearTimeout(this._hoverTimeout); } this._hoverTimeout = setTimeout(() => this._onHover(e), HOVER_DURATION); + + // Fire the hover end callback if a zone was being hovered + if (this._currentZone) { + this._currentZone.hoverEndCallback(); + this._currentZone = null; + } + + // Record the current coordinates this._lastHoverCoords = [e.pageX, e.pageY]; } } @@ -72,8 +84,9 @@ export class MouseZoneManager implements IMouseZoneManager { private _onHover(e: MouseEvent): void { const coords = getCoords(e, this._terminal.element, this._terminal.charMeasure, this._terminal.options.lineHeight, this._terminal.cols, this._terminal.rows); const zone = this._findZoneEventAt(e); - if (zone && zone.hoverCallback) { - zone.hoverCallback(e); + if (zone && zone.hoverStartCallback) { + this._currentZone = zone; + zone.hoverStartCallback(e); } } @@ -103,7 +116,8 @@ export class MouseZone implements IMouseZone { public x2: number, public y: number, public clickCallback: (e: MouseEvent) => any, - public hoverCallback?: (e: MouseEvent) => any + public hoverStartCallback?: (e: MouseEvent) => any, + public hoverEndCallback?: () => void ) { } } diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index d98f6c5d..b219732a 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -141,9 +141,14 @@ interface ILinkMatcherOptions { validationCallback?: (uri: string, callback: (isValid: boolean) => void) => void; /** - * A callback that fired when the mouse hovers over a link. + * A callback that fires when the mouse hovers over a link. */ - hoverCallback?: (event: MouseEvent, uri: string) => boolean | void; + hoverStartCallback?: (event: MouseEvent, uri: string) => boolean | void; + + /** + * A callback that fires when the mouse leaves a link that was hovered. + */ + hoverEndCallback?: (event: MouseEvent, uri: string) => boolean | void; /** * The priority of the link matcher, this defines the order in which the link