mirror of
https://github.com/mozilla/pdf.js.git
synced 2026-02-08 00:21:11 +01:00
It's width was a bit wrong because of its box-sizing property and it was causing some issues when resizing it with the keyboard. And for the thumbnails sidebar, the tabindex was missing and some aria properties too.
220 lines
6.1 KiB
JavaScript
220 lines
6.1 KiB
JavaScript
/* Copyright 2025 Mozilla Foundation
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
import { noContextMenu, stopEvent } from "pdfjs-lib";
|
|
|
|
// Timeout before ending resize operation.
|
|
const RESIZE_TIMEOUT = 400; // ms
|
|
|
|
/**
|
|
* Viewer control to display a sidebar with resizer functionality.
|
|
*/
|
|
class Sidebar {
|
|
#initialWidth = 0;
|
|
|
|
#width = 0;
|
|
|
|
#coefficient;
|
|
|
|
#resizeTimeout = null;
|
|
|
|
#resizer;
|
|
|
|
#isResizerOnTheLeft;
|
|
|
|
#isKeyboardResizing = false;
|
|
|
|
#resizeObserver;
|
|
|
|
#prevX = 0;
|
|
|
|
/**
|
|
* @typedef {Object} SidebarElements
|
|
* @property {HTMLElement} sidebar - The sidebar element.
|
|
* @property {HTMLElement} resizer - The sidebar resizer element.
|
|
* @property {HTMLElement} toggleButton - The button used to toggle the
|
|
* sidebar.
|
|
*/
|
|
|
|
/**
|
|
* Create a sidebar with resizer functionality.
|
|
* @param {SidebarElements} sidebarElements
|
|
* @param {boolean} ltr
|
|
* @param {boolean} isResizerOnTheLeft
|
|
*/
|
|
constructor({ sidebar, resizer, toggleButton }, ltr, isResizerOnTheLeft) {
|
|
this._sidebar = sidebar;
|
|
this.#coefficient = ltr === isResizerOnTheLeft ? -1 : 1;
|
|
this.#resizer = resizer;
|
|
this.#isResizerOnTheLeft = isResizerOnTheLeft;
|
|
|
|
const style = window.getComputedStyle(sidebar);
|
|
this.#initialWidth = this.#width = parseFloat(
|
|
style.getPropertyValue("--sidebar-width")
|
|
);
|
|
resizer.ariaValueMin =
|
|
parseFloat(style.getPropertyValue("--sidebar-min-width")) || 0;
|
|
resizer.ariaValueMax =
|
|
parseFloat(style.getPropertyValue("--sidebar-max-width")) || Infinity;
|
|
resizer.ariaValueNow = this.#width;
|
|
|
|
this.#makeSidebarResizable();
|
|
toggleButton.addEventListener("click", this.toggle.bind(this));
|
|
this._isOpen = false;
|
|
sidebar.hidden = true;
|
|
|
|
this.#resizeObserver = new ResizeObserver(
|
|
([
|
|
{
|
|
borderBoxSize: [{ inlineSize }],
|
|
},
|
|
]) => {
|
|
if (!isNaN(this.#prevX)) {
|
|
this.#prevX += this.#coefficient * (inlineSize - this.#width);
|
|
}
|
|
this.#setWidth(inlineSize);
|
|
}
|
|
);
|
|
this.#resizeObserver.observe(sidebar);
|
|
}
|
|
|
|
#makeSidebarResizable() {
|
|
const sidebarStyle = this._sidebar.style;
|
|
let pointerMoveAC;
|
|
const cancelResize = () => {
|
|
this.#resizeTimeout = null;
|
|
this._sidebar.classList.remove("resizing");
|
|
pointerMoveAC?.abort();
|
|
pointerMoveAC = null;
|
|
this.#isKeyboardResizing = false;
|
|
this.onStopResizing();
|
|
this.#prevX = NaN;
|
|
};
|
|
this.#resizer.addEventListener("pointerdown", e => {
|
|
if (pointerMoveAC) {
|
|
cancelResize();
|
|
return;
|
|
}
|
|
this.onStartResizing();
|
|
const { clientX } = e;
|
|
stopEvent(e);
|
|
this.#prevX = clientX;
|
|
pointerMoveAC = new AbortController();
|
|
const { signal } = pointerMoveAC;
|
|
const sidebar = this._sidebar;
|
|
sidebar.classList.add("resizing");
|
|
const parentStyle = sidebar.parentElement.style;
|
|
parentStyle.minWidth = 0;
|
|
window.addEventListener("contextmenu", noContextMenu, { signal });
|
|
window.addEventListener(
|
|
"pointermove",
|
|
ev => {
|
|
if (!pointerMoveAC || Math.abs(ev.clientX - this.#prevX) < 1) {
|
|
return;
|
|
}
|
|
stopEvent(ev);
|
|
sidebarStyle.width = `${Math.round(this.#width + this.#coefficient * (ev.clientX - this.#prevX))}px`;
|
|
},
|
|
{ signal, capture: true }
|
|
);
|
|
window.addEventListener("blur", cancelResize, { signal });
|
|
window.addEventListener(
|
|
"pointerup",
|
|
ev => {
|
|
if (pointerMoveAC) {
|
|
cancelResize();
|
|
stopEvent(ev);
|
|
}
|
|
},
|
|
{ signal }
|
|
);
|
|
});
|
|
this.#resizer.addEventListener("keydown", e => {
|
|
const { key } = e;
|
|
const isArrowLeft = key === "ArrowLeft";
|
|
if (isArrowLeft || key === "ArrowRight") {
|
|
if (!this.#isKeyboardResizing) {
|
|
this._sidebar.classList.add("resizing");
|
|
this.#isKeyboardResizing = true;
|
|
this.onStartResizing();
|
|
}
|
|
|
|
const base = e.ctrlKey || e.metaKey ? 10 : 1;
|
|
const dx = base * (isArrowLeft ? -1 : 1);
|
|
clearTimeout(this.#resizeTimeout);
|
|
this.#resizeTimeout = setTimeout(cancelResize, RESIZE_TIMEOUT);
|
|
sidebarStyle.width = `${Math.round(this.#width + this.#coefficient * dx)}px`;
|
|
stopEvent(e);
|
|
}
|
|
});
|
|
}
|
|
|
|
#setWidth(newWidth) {
|
|
this.#width = newWidth;
|
|
this.#resizer.ariaValueNow = Math.round(newWidth);
|
|
if (this.#isResizerOnTheLeft) {
|
|
this._sidebar.parentElement.style.insetInlineStart = `${(this.#initialWidth - newWidth).toFixed(3)}px`;
|
|
}
|
|
this.onResizing(newWidth);
|
|
}
|
|
|
|
/**
|
|
* Get the current width of the sidebar in pixels.
|
|
* @returns {number}
|
|
*/
|
|
get width() {
|
|
return this.#width;
|
|
}
|
|
|
|
/**
|
|
* Set the width of the sidebar in pixels.
|
|
* @param {number} newWidth
|
|
*/
|
|
set width(newWidth) {
|
|
this._sidebar.style.width = `${newWidth}px`;
|
|
}
|
|
|
|
/**
|
|
* Callback to be executed when the user starts resizing the sidebar.
|
|
*/
|
|
onStartResizing() {}
|
|
|
|
/**
|
|
* Callback to be executed when the user stops resizing the sidebar.
|
|
*/
|
|
onStopResizing() {}
|
|
|
|
/**
|
|
* Callback to be executed when the sidebar is being resized.
|
|
* @param {number} newWidth - The new width of the sidebar in pixels.
|
|
*/
|
|
onResizing(_newWidth) {}
|
|
|
|
/**
|
|
* Toggle the sidebar's visibility.
|
|
* @param {boolean} [visibility] - The visibility state to set.
|
|
*/
|
|
toggle(visibility = !this._isOpen) {
|
|
this._sidebar.hidden = !(this._isOpen = visibility);
|
|
}
|
|
|
|
destroy() {
|
|
this.#resizeObserver?.disconnect();
|
|
this.#resizeObserver = null;
|
|
}
|
|
}
|
|
|
|
export { Sidebar };
|