mirror of
https://github.com/mozilla/pdf.js.git
synced 2026-07-30 02:47:22 +02:00
Merge pull request #20474 from calixteman/sidebar_resize_callbacks
Add some callbacks when resizing a sidebar
This commit is contained in:
commit
8a42b82ecd
@ -79,5 +79,9 @@
|
|||||||
:not(.sidebarResizer) {
|
:not(.sidebarResizer) {
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.sidebarResizer {
|
||||||
|
background-color: var(--resizer-hover-bg-color);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
191
web/sidebar.js
191
web/sidebar.js
@ -13,23 +13,30 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { MathClamp, noContextMenu, stopEvent } from "pdfjs-lib";
|
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.
|
* Viewer control to display a sidebar with resizer functionality.
|
||||||
*/
|
*/
|
||||||
class Sidebar {
|
class Sidebar {
|
||||||
#minWidth = 0;
|
|
||||||
|
|
||||||
#maxWidth = 0;
|
|
||||||
|
|
||||||
#initialWidth = 0;
|
#initialWidth = 0;
|
||||||
|
|
||||||
#width = 0;
|
#width = 0;
|
||||||
|
|
||||||
#coefficient;
|
#coefficient;
|
||||||
|
|
||||||
#visible = false;
|
#resizeTimeout = null;
|
||||||
|
|
||||||
|
#resizer;
|
||||||
|
|
||||||
|
#isResizerOnTheLeft;
|
||||||
|
|
||||||
|
#isKeyboardResizing = false;
|
||||||
|
|
||||||
|
#resizeObserver = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @typedef {Object} SidebarElements
|
* @typedef {Object} SidebarElements
|
||||||
@ -48,46 +55,67 @@ class Sidebar {
|
|||||||
constructor({ sidebar, resizer, toggleButton }, ltr, isResizerOnTheLeft) {
|
constructor({ sidebar, resizer, toggleButton }, ltr, isResizerOnTheLeft) {
|
||||||
this._sidebar = sidebar;
|
this._sidebar = sidebar;
|
||||||
this.#coefficient = ltr === isResizerOnTheLeft ? -1 : 1;
|
this.#coefficient = ltr === isResizerOnTheLeft ? -1 : 1;
|
||||||
|
this.#resizer = resizer;
|
||||||
|
this.#isResizerOnTheLeft = isResizerOnTheLeft;
|
||||||
|
|
||||||
const style = window.getComputedStyle(sidebar);
|
const style = window.getComputedStyle(sidebar);
|
||||||
this.#minWidth = parseFloat(style.getPropertyValue("--sidebar-min-width"));
|
|
||||||
this.#maxWidth = parseFloat(style.getPropertyValue("--sidebar-max-width"));
|
|
||||||
this.#initialWidth = this.#width = parseFloat(
|
this.#initialWidth = this.#width = parseFloat(
|
||||||
style.getPropertyValue("--sidebar-width")
|
style.getPropertyValue("--sidebar-width")
|
||||||
);
|
);
|
||||||
|
resizer.ariaValueMin = parseFloat(
|
||||||
|
style.getPropertyValue("--sidebar-min-width")
|
||||||
|
);
|
||||||
|
resizer.ariaValueMax = parseFloat(
|
||||||
|
style.getPropertyValue("--sidebar-max-width")
|
||||||
|
);
|
||||||
|
resizer.ariaValueNow = this.#width;
|
||||||
|
|
||||||
this.#makeSidebarResizable(resizer, isResizerOnTheLeft);
|
this.#makeSidebarResizable();
|
||||||
toggleButton.addEventListener("click", this.toggle.bind(this));
|
toggleButton.addEventListener("click", this.toggle.bind(this));
|
||||||
|
this._isOpen = false;
|
||||||
sidebar.hidden = true;
|
sidebar.hidden = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
#makeSidebarResizable(resizer, isResizerOnTheLeft) {
|
#makeSidebarResizable() {
|
||||||
resizer.ariaValueMin = this.#minWidth;
|
const sidebarStyle = this._sidebar.style;
|
||||||
resizer.ariaValueMax = this.#maxWidth;
|
|
||||||
resizer.ariaValueNow = this.#width;
|
|
||||||
|
|
||||||
let pointerMoveAC;
|
let pointerMoveAC;
|
||||||
const cancelResize = () => {
|
const cancelResize = () => {
|
||||||
this.#width = MathClamp(this.#width, this.#minWidth, this.#maxWidth);
|
this.#resizeTimeout = null;
|
||||||
this._sidebar.classList.remove("resizing");
|
this._sidebar.classList.remove("resizing");
|
||||||
pointerMoveAC?.abort();
|
pointerMoveAC?.abort();
|
||||||
pointerMoveAC = null;
|
pointerMoveAC = null;
|
||||||
|
this.#resizeObserver?.disconnect();
|
||||||
|
this.#resizeObserver = null;
|
||||||
|
this.#isKeyboardResizing = false;
|
||||||
|
this.onStopResizing();
|
||||||
};
|
};
|
||||||
resizer.addEventListener("pointerdown", e => {
|
this.#resizer.addEventListener("pointerdown", e => {
|
||||||
if (pointerMoveAC) {
|
if (pointerMoveAC) {
|
||||||
cancelResize();
|
cancelResize();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
this.onStartResizing();
|
||||||
const { clientX } = e;
|
const { clientX } = e;
|
||||||
stopEvent(e);
|
stopEvent(e);
|
||||||
let prevX = clientX;
|
let prevX = clientX;
|
||||||
pointerMoveAC = new AbortController();
|
pointerMoveAC = new AbortController();
|
||||||
const { signal } = pointerMoveAC;
|
const { signal } = pointerMoveAC;
|
||||||
const sidebar = this._sidebar;
|
const sidebar = this._sidebar;
|
||||||
const sidebarStyle = sidebar.style;
|
|
||||||
sidebar.classList.add("resizing");
|
sidebar.classList.add("resizing");
|
||||||
const parentStyle = sidebar.parentElement.style;
|
const parentStyle = sidebar.parentElement.style;
|
||||||
parentStyle.minWidth = 0;
|
parentStyle.minWidth = 0;
|
||||||
|
this.#resizeObserver?.disconnect();
|
||||||
|
this.#resizeObserver = new ResizeObserver(
|
||||||
|
([
|
||||||
|
{
|
||||||
|
borderBoxSize: [{ inlineSize }],
|
||||||
|
},
|
||||||
|
]) => {
|
||||||
|
prevX += this.#width - inlineSize;
|
||||||
|
this.#setWidth(inlineSize);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
this.#resizeObserver.observe(sidebar);
|
||||||
window.addEventListener("contextmenu", noContextMenu, { signal });
|
window.addEventListener("contextmenu", noContextMenu, { signal });
|
||||||
window.addEventListener(
|
window.addEventListener(
|
||||||
"pointermove",
|
"pointermove",
|
||||||
@ -96,16 +124,7 @@ class Sidebar {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
stopEvent(ev);
|
stopEvent(ev);
|
||||||
const { clientX: x } = ev;
|
sidebarStyle.width = `${Math.round(this.#width + this.#coefficient * (ev.clientX - prevX))}px`;
|
||||||
this.#setNewWidth(
|
|
||||||
x - prevX,
|
|
||||||
parentStyle,
|
|
||||||
resizer,
|
|
||||||
sidebarStyle,
|
|
||||||
isResizerOnTheLeft,
|
|
||||||
/* isFromKeyboard */ false
|
|
||||||
);
|
|
||||||
prevX = x;
|
|
||||||
},
|
},
|
||||||
{ signal, capture: true }
|
{ signal, capture: true }
|
||||||
);
|
);
|
||||||
@ -121,59 +140,101 @@ class Sidebar {
|
|||||||
{ signal }
|
{ signal }
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
resizer.addEventListener("keydown", e => {
|
this.#resizer.addEventListener("keydown", e => {
|
||||||
const { key } = e;
|
const { key } = e;
|
||||||
const isArrowLeft = key === "ArrowLeft";
|
const isArrowLeft = key === "ArrowLeft";
|
||||||
if (isArrowLeft || key === "ArrowRight") {
|
if (isArrowLeft || key === "ArrowRight") {
|
||||||
|
if (!this.#isKeyboardResizing) {
|
||||||
|
this._sidebar.classList.add("resizing");
|
||||||
|
this.#isKeyboardResizing = true;
|
||||||
|
this.#resizeObserver?.disconnect();
|
||||||
|
this.#resizeObserver = new ResizeObserver(
|
||||||
|
([
|
||||||
|
{
|
||||||
|
borderBoxSize: [{ inlineSize }],
|
||||||
|
},
|
||||||
|
]) => {
|
||||||
|
this.#setWidth(inlineSize);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
this.#resizeObserver.observe(this._sidebar);
|
||||||
|
this.onStartResizing();
|
||||||
|
}
|
||||||
|
|
||||||
const base = e.ctrlKey || e.metaKey ? 10 : 1;
|
const base = e.ctrlKey || e.metaKey ? 10 : 1;
|
||||||
const dx = base * (isArrowLeft ? -1 : 1);
|
const dx = base * (isArrowLeft ? -1 : 1);
|
||||||
this.#setNewWidth(
|
clearTimeout(this.#resizeTimeout);
|
||||||
dx,
|
this.#resizeTimeout = setTimeout(cancelResize, RESIZE_TIMEOUT);
|
||||||
this._sidebar.parentElement.style,
|
sidebarStyle.width = `${Math.round(this.#width + this.#coefficient * dx)}px`;
|
||||||
resizer,
|
|
||||||
this._sidebar.style,
|
|
||||||
isResizerOnTheLeft,
|
|
||||||
/* isFromKeyboard */ true
|
|
||||||
);
|
|
||||||
stopEvent(e);
|
stopEvent(e);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
#setNewWidth(
|
#setWidth(newWidth) {
|
||||||
dx,
|
this.#width = newWidth;
|
||||||
parentStyle,
|
this.#resizer.ariaValueNow = Math.round(newWidth);
|
||||||
resizer,
|
if (this.#isResizerOnTheLeft) {
|
||||||
sidebarStyle,
|
this._sidebar.parentElement.style.insetInlineStart = `${(this.#initialWidth - newWidth).toFixed(3)}px`;
|
||||||
isResizerOnTheLeft,
|
|
||||||
isFromKeyboard
|
|
||||||
) {
|
|
||||||
let newWidth = this.#width + this.#coefficient * dx;
|
|
||||||
if (!isFromKeyboard) {
|
|
||||||
this.#width = newWidth;
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
(newWidth > this.#maxWidth || newWidth < this.#minWidth) &&
|
|
||||||
(this.#width === this.#maxWidth || this.#width === this.#minWidth)
|
|
||||||
) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
newWidth = MathClamp(newWidth, this.#minWidth, this.#maxWidth);
|
|
||||||
if (isFromKeyboard) {
|
|
||||||
this.#width = newWidth;
|
|
||||||
}
|
|
||||||
resizer.ariaValueNow = Math.round(newWidth);
|
|
||||||
sidebarStyle.width = `${newWidth.toFixed(3)}px`;
|
|
||||||
if (isResizerOnTheLeft) {
|
|
||||||
parentStyle.insetInlineStart = `${(this.#initialWidth - newWidth).toFixed(3)}px`;
|
|
||||||
}
|
}
|
||||||
|
this.onResizing(newWidth);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Toggle the sidebar's visibility.
|
* Get the current width of the sidebar in pixels.
|
||||||
|
* @returns {number}
|
||||||
*/
|
*/
|
||||||
toggle() {
|
get width() {
|
||||||
this._sidebar.hidden = !(this.#visible = !this.#visible);
|
return this.#width;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the width of the sidebar in pixels.
|
||||||
|
* @param {number} newWidth
|
||||||
|
*/
|
||||||
|
set width(newWidth) {
|
||||||
|
if (!this.#resizeObserver) {
|
||||||
|
this.#resizeObserver = new ResizeObserver(
|
||||||
|
([
|
||||||
|
{
|
||||||
|
borderBoxSize: [{ inlineSize }],
|
||||||
|
},
|
||||||
|
]) => {
|
||||||
|
this.#setWidth(inlineSize);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
this.#resizeObserver.observe(this._sidebar);
|
||||||
|
}
|
||||||
|
this._sidebar.style.width = `${newWidth}px`;
|
||||||
|
clearTimeout(this.#resizeTimeout);
|
||||||
|
this.#resizeTimeout = setTimeout(() => {
|
||||||
|
this.#resizeObserver.disconnect();
|
||||||
|
this.#resizeObserver = null;
|
||||||
|
}, RESIZE_TIMEOUT);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user