Merge pull request #20467 from calixteman/make_sidebar
Create a sidebar object
This commit is contained in:
commit
925fc3d8f2
@ -22,6 +22,8 @@ import {
|
||||
getEditorSelector,
|
||||
getRect,
|
||||
highlightSpan,
|
||||
kbModifierDown,
|
||||
kbModifierUp,
|
||||
loadAndWait,
|
||||
scrollIntoView,
|
||||
selectEditor,
|
||||
@ -546,6 +548,59 @@ describe("Comment", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("must check that the comment sidebar is resizable with the keyboard", async () => {
|
||||
await Promise.all(
|
||||
pages.map(async ([browserName, page]) => {
|
||||
await switchToComment(page);
|
||||
|
||||
const sidebarSelector = "#editorCommentParamsToolbar";
|
||||
const handle = await createPromise(page, resolve => {
|
||||
document
|
||||
.getElementById("editorCommentsSidebarResizer")
|
||||
.addEventListener("focus", () => resolve(), { once: true });
|
||||
});
|
||||
await page.focus(`${sidebarSelector} #editorCommentsSidebarResizer`);
|
||||
await awaitPromise(handle);
|
||||
|
||||
// Use Ctrl+ArrowLeft/Right to resize the sidebar.
|
||||
for (const extraWidth of [10, -10]) {
|
||||
const rect = await getRect(page, sidebarSelector);
|
||||
const arrowKey = extraWidth > 0 ? "ArrowLeft" : "ArrowRight";
|
||||
for (let i = 0; i < Math.abs(extraWidth); i++) {
|
||||
await kbModifierDown(page);
|
||||
await page.keyboard.press(arrowKey);
|
||||
await kbModifierUp(page);
|
||||
}
|
||||
|
||||
const rectAfter = await getRect(page, sidebarSelector);
|
||||
expect(Math.abs(rectAfter.width - (rect.width + 10 * extraWidth)))
|
||||
.withContext(`In ${browserName}`)
|
||||
.toBeLessThanOrEqual(1);
|
||||
expect(Math.abs(rectAfter.x - (rect.x - 10 * extraWidth)))
|
||||
.withContext(`In ${browserName}`)
|
||||
.toBeLessThanOrEqual(1);
|
||||
}
|
||||
|
||||
// Use ArrowLeft/Right to resize the sidebar.
|
||||
for (const extraWidth of [10, -10]) {
|
||||
const rect = await getRect(page, sidebarSelector);
|
||||
const arrowKey = extraWidth > 0 ? "ArrowLeft" : "ArrowRight";
|
||||
for (let i = 0; i < Math.abs(extraWidth); i++) {
|
||||
await page.keyboard.press(arrowKey);
|
||||
}
|
||||
|
||||
const rectAfter = await getRect(page, sidebarSelector);
|
||||
expect(Math.abs(rectAfter.width - (rect.width + extraWidth)))
|
||||
.withContext(`In ${browserName}`)
|
||||
.toBeLessThanOrEqual(1);
|
||||
expect(Math.abs(rectAfter.x - (rect.x - extraWidth)))
|
||||
.withContext(`In ${browserName}`)
|
||||
.toBeLessThanOrEqual(1);
|
||||
}
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it("must check that comments are in chronological order", async () => {
|
||||
await Promise.all(
|
||||
pages.map(async ([browserName, page]) => {
|
||||
|
||||
@ -27,6 +27,7 @@ import {
|
||||
Util,
|
||||
} from "pdfjs-lib";
|
||||
import { binarySearchFirstItem } from "./ui_utils.js";
|
||||
import { Sidebar } from "./sidebar.js";
|
||||
|
||||
class CommentManager {
|
||||
#dialog;
|
||||
@ -141,7 +142,7 @@ class CommentManager {
|
||||
}
|
||||
}
|
||||
|
||||
class CommentSidebar {
|
||||
class CommentSidebar extends Sidebar {
|
||||
#annotations = null;
|
||||
|
||||
#eventBus;
|
||||
@ -150,8 +151,6 @@ class CommentSidebar {
|
||||
|
||||
#boundCommentKeydown = this.#commentKeydown.bind(this);
|
||||
|
||||
#sidebar;
|
||||
|
||||
#closeButton;
|
||||
|
||||
#commentsList;
|
||||
@ -174,16 +173,6 @@ class CommentSidebar {
|
||||
|
||||
#uiManager = null;
|
||||
|
||||
#minWidth = 0;
|
||||
|
||||
#maxWidth = 0;
|
||||
|
||||
#initialWidth = 0;
|
||||
|
||||
#width = 0;
|
||||
|
||||
#ltr;
|
||||
|
||||
constructor(
|
||||
{
|
||||
learnMoreUrl,
|
||||
@ -201,7 +190,11 @@ class CommentSidebar {
|
||||
dateFormat,
|
||||
ltr
|
||||
) {
|
||||
this.#sidebar = sidebar;
|
||||
super(
|
||||
{ sidebar, resizer: sidebarResizer, toggleButton: commentToolbarButton },
|
||||
ltr,
|
||||
/* isResizerOnTheLeft = */ true
|
||||
);
|
||||
this.#sidebarTitle = sidebarTitle;
|
||||
this.#commentsList = commentsList;
|
||||
this.#commentCount = commentCount;
|
||||
@ -210,17 +203,8 @@ class CommentSidebar {
|
||||
this.#closeButton = closeButton;
|
||||
this.#popup = popup;
|
||||
this.#dateFormat = dateFormat;
|
||||
this.#ltr = ltr;
|
||||
this.#eventBus = eventBus;
|
||||
|
||||
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(
|
||||
style.getPropertyValue("--sidebar-width")
|
||||
);
|
||||
|
||||
this.#makeSidebarResizable(sidebarResizer);
|
||||
closeButton.addEventListener("click", () => {
|
||||
eventBus.dispatch("switchannotationeditormode", {
|
||||
source: this,
|
||||
@ -238,64 +222,6 @@ class CommentSidebar {
|
||||
};
|
||||
commentToolbarButton.addEventListener("keydown", keyDownCallback);
|
||||
sidebar.addEventListener("keydown", keyDownCallback);
|
||||
this.#sidebar.hidden = true;
|
||||
}
|
||||
|
||||
#makeSidebarResizable(resizer) {
|
||||
let pointerMoveAC;
|
||||
const cancelResize = () => {
|
||||
this.#width = MathClamp(this.#width, this.#minWidth, this.#maxWidth);
|
||||
this.#sidebar.classList.remove("resizing");
|
||||
pointerMoveAC?.abort();
|
||||
pointerMoveAC = null;
|
||||
};
|
||||
resizer.addEventListener("pointerdown", e => {
|
||||
if (pointerMoveAC) {
|
||||
cancelResize();
|
||||
return;
|
||||
}
|
||||
const { clientX } = e;
|
||||
stopEvent(e);
|
||||
let prevX = clientX;
|
||||
pointerMoveAC = new AbortController();
|
||||
const { signal } = pointerMoveAC;
|
||||
const sign = this.#ltr ? -1 : 1;
|
||||
const sidebar = this.#sidebar;
|
||||
const sidebarStyle = sidebar.style;
|
||||
sidebar.classList.add("resizing");
|
||||
const parentStyle = sidebar.parentElement.style;
|
||||
parentStyle.minWidth = 0;
|
||||
window.addEventListener("contextmenu", noContextMenu, { signal });
|
||||
window.addEventListener(
|
||||
"pointermove",
|
||||
ev => {
|
||||
if (!pointerMoveAC) {
|
||||
return;
|
||||
}
|
||||
stopEvent(ev);
|
||||
const { clientX: x } = ev;
|
||||
const newWidth = (this.#width += sign * (x - prevX));
|
||||
prevX = x;
|
||||
if (newWidth > this.#maxWidth || newWidth < this.#minWidth) {
|
||||
return;
|
||||
}
|
||||
sidebarStyle.width = `${newWidth.toFixed(3)}px`;
|
||||
parentStyle.insetInlineStart = `${(this.#initialWidth - newWidth).toFixed(3)}px`;
|
||||
},
|
||||
{ signal, capture: true }
|
||||
);
|
||||
window.addEventListener("blur", cancelResize, { signal });
|
||||
window.addEventListener(
|
||||
"pointerup",
|
||||
ev => {
|
||||
if (pointerMoveAC) {
|
||||
cancelResize();
|
||||
stopEvent(ev);
|
||||
}
|
||||
},
|
||||
{ signal }
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
setUIManager(uiManager) {
|
||||
@ -318,7 +244,7 @@ class CommentSidebar {
|
||||
} else {
|
||||
this.#setCommentsCount();
|
||||
}
|
||||
this.#sidebar.hidden = false;
|
||||
this._sidebar.hidden = false;
|
||||
this.#eventBus.dispatch("reporttelemetry", {
|
||||
source: this,
|
||||
details: {
|
||||
@ -329,7 +255,7 @@ class CommentSidebar {
|
||||
}
|
||||
|
||||
hide() {
|
||||
this.#sidebar.hidden = true;
|
||||
this._sidebar.hidden = true;
|
||||
this.#commentsList.replaceChildren();
|
||||
this.#elementsToAnnotations = null;
|
||||
this.#idsToElements = null;
|
||||
@ -356,7 +282,7 @@ class CommentSidebar {
|
||||
if (!element) {
|
||||
return;
|
||||
}
|
||||
this.#sidebar.scrollTop = element.offsetTop - this.#sidebar.offsetTop;
|
||||
this._sidebar.scrollTop = element.offsetTop - this._sidebar.offsetTop;
|
||||
for (const el of this.#commentsList.children) {
|
||||
el.classList.toggle("selected", el === element);
|
||||
}
|
||||
|
||||
@ -22,6 +22,7 @@
|
||||
--sidebar-box-shadow:
|
||||
0 0.25px 0.75px light-dark(rgb(0 0 0 / 0.05), rgb(0 0 0 / 0.2)),
|
||||
0 2px 6px 0 light-dark(rgb(0 0 0 / 0.1), rgb(0 0 0 / 0.4));
|
||||
--sidebar-backdrop-filter: none;
|
||||
--sidebar-border-radius: 8px;
|
||||
--sidebar-padding: 5px;
|
||||
--sidebar-min-width: 180px;
|
||||
@ -46,6 +47,7 @@
|
||||
width: var(--sidebar-width);
|
||||
min-width: var(--sidebar-min-width);
|
||||
max-width: var(--sidebar-max-width);
|
||||
backdrop-filter: var(--sidebar-backdrop-filter);
|
||||
|
||||
.sidebarResizer {
|
||||
width: var(--resizer-width);
|
||||
@ -64,6 +66,10 @@
|
||||
&:hover {
|
||||
background-color: var(--resizer-hover-bg-color);
|
||||
}
|
||||
&:focus-visible {
|
||||
background-color: var(--resizer-hover-bg-color);
|
||||
outline: none;
|
||||
}
|
||||
}
|
||||
|
||||
&.resizing {
|
||||
|
||||
180
web/sidebar.js
Normal file
180
web/sidebar.js
Normal file
@ -0,0 +1,180 @@
|
||||
/* 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 { MathClamp, noContextMenu, stopEvent } from "pdfjs-lib";
|
||||
|
||||
/**
|
||||
* Viewer control to display a sidebar with resizer functionality.
|
||||
*/
|
||||
class Sidebar {
|
||||
#minWidth = 0;
|
||||
|
||||
#maxWidth = 0;
|
||||
|
||||
#initialWidth = 0;
|
||||
|
||||
#width = 0;
|
||||
|
||||
#coefficient;
|
||||
|
||||
#visible = false;
|
||||
|
||||
/**
|
||||
* @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;
|
||||
|
||||
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(
|
||||
style.getPropertyValue("--sidebar-width")
|
||||
);
|
||||
|
||||
this.#makeSidebarResizable(resizer, isResizerOnTheLeft);
|
||||
toggleButton.addEventListener("click", this.toggle.bind(this));
|
||||
sidebar.hidden = true;
|
||||
}
|
||||
|
||||
#makeSidebarResizable(resizer, isResizerOnTheLeft) {
|
||||
resizer.ariaValueMin = this.#minWidth;
|
||||
resizer.ariaValueMax = this.#maxWidth;
|
||||
resizer.ariaValueNow = this.#width;
|
||||
|
||||
let pointerMoveAC;
|
||||
const cancelResize = () => {
|
||||
this.#width = MathClamp(this.#width, this.#minWidth, this.#maxWidth);
|
||||
this._sidebar.classList.remove("resizing");
|
||||
pointerMoveAC?.abort();
|
||||
pointerMoveAC = null;
|
||||
};
|
||||
resizer.addEventListener("pointerdown", e => {
|
||||
if (pointerMoveAC) {
|
||||
cancelResize();
|
||||
return;
|
||||
}
|
||||
const { clientX } = e;
|
||||
stopEvent(e);
|
||||
let prevX = clientX;
|
||||
pointerMoveAC = new AbortController();
|
||||
const { signal } = pointerMoveAC;
|
||||
const sidebar = this._sidebar;
|
||||
const sidebarStyle = sidebar.style;
|
||||
sidebar.classList.add("resizing");
|
||||
const parentStyle = sidebar.parentElement.style;
|
||||
parentStyle.minWidth = 0;
|
||||
window.addEventListener("contextmenu", noContextMenu, { signal });
|
||||
window.addEventListener(
|
||||
"pointermove",
|
||||
ev => {
|
||||
if (!pointerMoveAC) {
|
||||
return;
|
||||
}
|
||||
stopEvent(ev);
|
||||
const { clientX: x } = ev;
|
||||
this.#setNewWidth(
|
||||
x - prevX,
|
||||
parentStyle,
|
||||
resizer,
|
||||
sidebarStyle,
|
||||
isResizerOnTheLeft,
|
||||
/* isFromKeyboard */ false
|
||||
);
|
||||
prevX = x;
|
||||
},
|
||||
{ signal, capture: true }
|
||||
);
|
||||
window.addEventListener("blur", cancelResize, { signal });
|
||||
window.addEventListener(
|
||||
"pointerup",
|
||||
ev => {
|
||||
if (pointerMoveAC) {
|
||||
cancelResize();
|
||||
stopEvent(ev);
|
||||
}
|
||||
},
|
||||
{ signal }
|
||||
);
|
||||
});
|
||||
resizer.addEventListener("keydown", e => {
|
||||
const { key } = e;
|
||||
const isArrowLeft = key === "ArrowLeft";
|
||||
if (isArrowLeft || key === "ArrowRight") {
|
||||
const base = e.ctrlKey || e.metaKey ? 10 : 1;
|
||||
const dx = base * (isArrowLeft ? -1 : 1);
|
||||
this.#setNewWidth(
|
||||
dx,
|
||||
this._sidebar.parentElement.style,
|
||||
resizer,
|
||||
this._sidebar.style,
|
||||
isResizerOnTheLeft,
|
||||
/* isFromKeyboard */ true
|
||||
);
|
||||
stopEvent(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
#setNewWidth(
|
||||
dx,
|
||||
parentStyle,
|
||||
resizer,
|
||||
sidebarStyle,
|
||||
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`;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle the sidebar's visibility.
|
||||
*/
|
||||
toggle() {
|
||||
this._sidebar.hidden = !(this.#visible = !this.#visible);
|
||||
}
|
||||
}
|
||||
|
||||
export { Sidebar };
|
||||
@ -335,7 +335,7 @@ See https://github.com/adobe-type-tools/cmap-resources
|
||||
</button>
|
||||
<div class="editorParamsToolbar hidden menu" id="editorCommentParamsToolbar">
|
||||
<div id="editorCommentsSidebar" class="menuContainer comment sidebar" role="landmark" aria-labelledby="editorCommentsSidebarHeader">
|
||||
<div id="editorCommentsSidebarResizer" class="sidebarResizer"></div>
|
||||
<div id="editorCommentsSidebarResizer" class="sidebarResizer" role="separator" aria-controls="editorCommentsSidebar" tabindex="0"></div>
|
||||
<div id="editorCommentsSidebarHeader" role="heading" aria-level="2">
|
||||
<span class="commentCount">
|
||||
<span id="editorCommentsSidebarTitle" data-l10n-id="pdfjs-editor-comments-sidebar-title" data-l10n-args='{ "count": 0 }'></span>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user