diff --git a/web/grab_to_pan.js b/web/grab_to_pan.js index 549026958..c4b509487 100644 --- a/web/grab_to_pan.js +++ b/web/grab_to_pan.js @@ -14,97 +14,91 @@ * limitations under the License. */ -/** - * Construct a GrabToPan instance for a given HTML element. - * @param options.element {Element} - * @param options.ignoreTarget {function} optional. See `ignoreTarget(node)` - * @param options.onActiveChanged {function(boolean)} optional. Called - * when grab-to-pan is (de)activated. The first argument is a boolean that - * shows whether grab-to-pan is activated. - */ -function GrabToPan(options) { - this.element = options.element; - this.document = options.element.ownerDocument; - if (typeof options.ignoreTarget === "function") { - this.ignoreTarget = options.ignoreTarget; - } - this.onActiveChanged = options.onActiveChanged; +// Class name of element which can be grabbed. +const CSS_CLASS_GRAB = "grab-to-pan-grab"; - // Bind the contexts to ensure that `this` always points to - // the GrabToPan instance. - this.activate = this.activate.bind(this); - this.deactivate = this.deactivate.bind(this); - this.toggle = this.toggle.bind(this); - this._onmousedown = this._onmousedown.bind(this); - this._onmousemove = this._onmousemove.bind(this); - this._endPan = this._endPan.bind(this); - - // This overlay will be inserted in the document when the mouse moves during - // a grab operation, to ensure that the cursor has the desired appearance. - const overlay = (this.overlay = document.createElement("div")); - overlay.className = "grab-to-pan-grabbing"; -} -GrabToPan.prototype = { +class GrabToPan { /** - * Class name of element which can be grabbed + * Construct a GrabToPan instance for a given HTML element. + * @param {Element} options.element + * @param {function} [options.ignoreTarget] - See `ignoreTarget(node)`. + * @param {function(boolean)} [options.onActiveChanged] - Called when + * grab-to-pan is (de)activated. The first argument is a boolean that + * shows whether grab-to-pan is activated. */ - CSS_CLASS_GRAB: "grab-to-pan-grab", + constructor(options) { + this.element = options.element; + this.document = options.element.ownerDocument; + if (typeof options.ignoreTarget === "function") { + this.ignoreTarget = options.ignoreTarget; + } + this.onActiveChanged = options.onActiveChanged; + + // Bind the contexts to ensure that `this` always points to + // the GrabToPan instance. + this.activate = this.activate.bind(this); + this.deactivate = this.deactivate.bind(this); + this.toggle = this.toggle.bind(this); + this._onMouseDown = this.#onMouseDown.bind(this); + this._onMouseMove = this.#onMouseMove.bind(this); + this._endPan = this.#endPan.bind(this); + + // This overlay will be inserted in the document when the mouse moves during + // a grab operation, to ensure that the cursor has the desired appearance. + const overlay = (this.overlay = document.createElement("div")); + overlay.className = "grab-to-pan-grabbing"; + } /** * Bind a mousedown event to the element to enable grab-detection. */ - activate: function GrabToPan_activate() { + activate() { if (!this.active) { this.active = true; - this.element.addEventListener("mousedown", this._onmousedown, true); - this.element.classList.add(this.CSS_CLASS_GRAB); - if (this.onActiveChanged) { - this.onActiveChanged(true); - } + this.element.addEventListener("mousedown", this._onMouseDown, true); + this.element.classList.add(CSS_CLASS_GRAB); + + this.onActiveChanged?.(true); } - }, + } /** * Removes all events. Any pending pan session is immediately stopped. */ - deactivate: function GrabToPan_deactivate() { + deactivate() { if (this.active) { this.active = false; - this.element.removeEventListener("mousedown", this._onmousedown, true); + this.element.removeEventListener("mousedown", this._onMouseDown, true); this._endPan(); - this.element.classList.remove(this.CSS_CLASS_GRAB); - if (this.onActiveChanged) { - this.onActiveChanged(false); - } - } - }, + this.element.classList.remove(CSS_CLASS_GRAB); - toggle: function GrabToPan_toggle() { + this.onActiveChanged?.(false); + } + } + + toggle() { if (this.active) { this.deactivate(); } else { this.activate(); } - }, + } /** * Whether to not pan if the target element is clicked. * Override this method to change the default behaviour. * - * @param node {Element} The target of the event + * @param {Element} node - The target of the event. * @returns {boolean} Whether to not react to the click event. */ - ignoreTarget: function GrabToPan_ignoreTarget(node) { + ignoreTarget(node) { // Check whether the clicked element is, a child of, an input element/link. return node.matches( "a[href], a[href] *, input, textarea, button, button *, select, option" ); - }, + } - /** - * @private - */ - _onmousedown: function GrabToPan__onmousedown(event) { + #onMouseDown(event) { if (event.button !== 0 || this.ignoreTarget(event.target)) { return; } @@ -122,7 +116,7 @@ GrabToPan.prototype = { this.scrollTopStart = this.element.scrollTop; this.clientXStart = event.clientX; this.clientYStart = event.clientY; - this.document.addEventListener("mousemove", this._onmousemove, true); + this.document.addEventListener("mousemove", this._onMouseMove, true); this.document.addEventListener("mouseup", this._endPan, true); // When a scroll event occurs before a mousemove, assume that the user // dragged a scrollbar (necessary for Opera Presto, Safari and IE) @@ -135,14 +129,12 @@ GrabToPan.prototype = { if (focusedElement && !focusedElement.contains(event.target)) { focusedElement.blur(); } - }, + } - /** - * @private - */ - _onmousemove: function GrabToPan__onmousemove(event) { + #onMouseMove(event) { this.element.removeEventListener("scroll", this._endPan, true); - if (isLeftMouseReleased(event)) { + if (!(event.buttons & 1)) { + // The left mouse button is released. this._endPan(); return; } @@ -163,52 +155,15 @@ GrabToPan.prototype = { if (!this.overlay.parentNode) { document.body.appendChild(this.overlay); } - }, + } - /** - * @private - */ - _endPan: function GrabToPan__endPan() { + #endPan() { this.element.removeEventListener("scroll", this._endPan, true); - this.document.removeEventListener("mousemove", this._onmousemove, true); + this.document.removeEventListener("mousemove", this._onMouseMove, true); this.document.removeEventListener("mouseup", this._endPan, true); // Note: ChildNode.remove doesn't throw if the parentNode is undefined. this.overlay.remove(); - }, -}; - -/** - * Whether the left mouse is not pressed. - * @param event {MouseEvent} - * @returns {boolean} True if the left mouse button is not pressed, - * False if unsure or if the left mouse button is pressed. - */ -function isLeftMouseReleased(event) { - if ("buttons" in event) { - // http://www.w3.org/TR/DOM-Level-3-Events/#events-MouseEvent-buttons - // Firefox 15+ - // Chrome 43+ - // Safari 11.1+ - return !(event.buttons & 1); } - if (typeof PDFJSDev === "undefined" || !PDFJSDev.test("MOZCENTRAL")) { - // Browser sniffing because it's impossible to feature-detect - // whether event.which for onmousemove is reliable. - const chrome = window.chrome; - const isChrome15OrOpera15plus = chrome && (chrome.webstore || chrome.app); - // ^ Chrome 15+ ^ Opera 15+ - const isSafari6plus = - /Apple/.test(navigator.vendor) && - /Version\/([6-9]\d*|[1-5]\d+)/.test(navigator.userAgent); - - if (isChrome15OrOpera15plus || isSafari6plus) { - // Chrome 14+ - // Opera 15+ - // Safari 6.0+ - return event.which === 0; - } - } - return false; } export { GrabToPan };