diff --git a/src/mathquill.d.ts b/src/mathquill.d.ts index 904ba675e..47f65e496 100644 --- a/src/mathquill.d.ts +++ b/src/mathquill.d.ts @@ -137,6 +137,10 @@ declare namespace MathQuill { disableAutoSubstitutionInSubscripts?: boolean | { except: string }; interpretTildeAsSim?: boolean; handlers?: HandlerOptions>; + askIfShouldIgnoreMousemove?: ( + evt: MouseEvent, + rootElt: HTMLElement + ) => boolean; } interface Handler { diff --git a/src/publicapi.ts b/src/publicapi.ts index 4464deb57..7a82fbe0c 100644 --- a/src/publicapi.ts +++ b/src/publicapi.ts @@ -89,6 +89,11 @@ class Options { constructor(public version: 1 | 2 | 3) {} ignoreNextMousedown: (_el: MouseEvent) => boolean; + askIfShouldIgnoreMousemove: ( + evt: MouseEvent, + rootDOM: HTMLElement + ) => boolean; + substituteTextarea: () => HTMLElement; /** Only used in interface versions 1 and 2. */ substituteKeyboardEvents: SubstituteKeyboardEvents; diff --git a/src/services/mouse.ts b/src/services/mouse.ts index 2dfd44f32..89c1ae0a3 100644 --- a/src/services/mouse.ts +++ b/src/services/mouse.ts @@ -6,6 +6,11 @@ const ignoreNextMouseDownNoop = (_el: MouseEvent) => { }; Options.prototype.ignoreNextMousedown = ignoreNextMouseDownNoop; +const askIfShouldIgnoreMousemoveNoop = (_evt: MouseEvent, _el: HTMLElement) => { + return false; +}; +Options.prototype.askIfShouldIgnoreMousemove = askIfShouldIgnoreMousemoveNoop; + // Whenever edits to the tree occur, in-progress selection events // must be invalidated and selection changes must not be applied to // the edited tree. cancelSelectionOnEdit takes care of this. @@ -62,11 +67,25 @@ class Controller_mouse extends Controller_latex { } var lastMousemoveTarget: HTMLElement | null = null; - function mousemove(e: Event) { + function mousemove(e: MouseEvent) { + if ( + rootElement && + cursor.options.askIfShouldIgnoreMousemove(e, rootElement) + ) + return; lastMousemoveTarget = e.target as HTMLElement | null; } function onDocumentMouseMove(e: MouseEvent) { - if (!cursor.anticursor) cursor.startSelection(); + if ( + rootElement && + cursor.options.askIfShouldIgnoreMousemove(e, rootElement) + ) + return; + + if (!cursor.anticursor) { + ctrlr.restoreLatexSelection(originalSelection); + cursor.startSelection(); + } ctrlr.seek(lastMousemoveTarget, e.clientX, e.clientY).cursor.select(); if (cursor.selection) cursor.controller.aria @@ -131,6 +150,8 @@ class Controller_mouse extends Controller_latex { .seek(e.target as HTMLElement | null, e.clientX, e.clientY) .cursor.startSelection(); + const originalSelection = ctrlr.exportLatexSelection().selection; + rootElement?.addEventListener('mousemove', mousemove); ownerDocument?.addEventListener('mousemove', onDocumentMouseMove); ownerDocument?.addEventListener('mouseup', onDocumentMouseUp); diff --git a/test/unit/mouse.test.js b/test/unit/mouse.test.js index b1f747adf..abcd21f7b 100644 --- a/test/unit/mouse.test.js +++ b/test/unit/mouse.test.js @@ -69,6 +69,60 @@ suite('mouse', function () { endIndex: 12 }); }); + + test('askIfShouldIgnoreMousemove prevents drag selection', function () { + const mq = MQ.MathField($('').appendTo('#mock')[0]); + + let shouldIgnore = false; + mq.__controller.cursor.options.askIfShouldIgnoreMousemove = () => { + return shouldIgnore; + }; + + mq.latex('1+3+5+7+1'); + const rect = mq.el().getBoundingClientRect(); + const y = rect.top + 10; + + const three = $('#mock .mq-digit:contains(3)'); + const five = $('#mock .mq-digit:contains(5)'); + const beforeOne = rect.left + 1; + const afterThree = three.get(0).getBoundingClientRect().right; + const afterFive = five.get(0).getBoundingClientRect().right; + + // Baseline: normal drag selects + dispatchMouseEventAtPoint('mousedown', beforeOne, y); + dispatchMouseEventAtPoint('mousemove', afterThree, y); + assertDeepEqual(mq.selection(), { + latex: '1+3+5+7+1', + startIndex: 0, + endIndex: 3 + }); + + // With ignore=true: drag should not extend selection + shouldIgnore = true; + dispatchMouseEventAtPoint('mousemove', afterFive, y); + assertDeepEqual(mq.selection(), { + latex: '1+3+5+7+1', + startIndex: 0, + endIndex: 3 + }); + + // With ignore=false again: drag works + shouldIgnore = false; + dispatchMouseEventAtPoint('mousemove', afterFive + 1, y); + assertDeepEqual(mq.selection(), { + latex: '1+3+5+7+1', + startIndex: 0, + endIndex: 5 + }); + + // can go back to 3 with ignore=false + dispatchMouseEventAtPoint('mousemove', afterThree, y); + assertDeepEqual(mq.selection(), { + latex: '1+3+5+7+1', + startIndex: 0, + endIndex: 3 + }); + }); }); function assertDeepEqual(a, b) {