[Editor] Add the possibility to create a new editor in using the keyboard (bug 1853424)
When an editing button is disabled, focused and the user press Enter (or space), an editor is automatically added at the center of the current page. Next creations can be done in using the same keys within the focused page.
This commit is contained in:
parent
a60f90ae94
commit
ea5eafa265
@ -276,6 +276,13 @@ class AnnotationEditor {
|
||||
this.div?.classList.toggle("draggable", value);
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {boolean} true if the editor handles the Enter key itself.
|
||||
*/
|
||||
get isEnterHandled() {
|
||||
return true;
|
||||
}
|
||||
|
||||
center() {
|
||||
const [pageWidth, pageHeight] = this.pageDimensions;
|
||||
switch (this.parentRotation) {
|
||||
|
@ -605,10 +605,8 @@ class AnnotationEditorUIManager {
|
||||
const arrowChecker = self => {
|
||||
// If the focused element is an input, we don't want to handle the arrow.
|
||||
// For example, sliders can be controlled with the arrow keys.
|
||||
const { activeElement } = document;
|
||||
return (
|
||||
activeElement &&
|
||||
self.#container.contains(activeElement) &&
|
||||
self.#container.contains(document.activeElement) &&
|
||||
self.hasSomethingToControl()
|
||||
);
|
||||
};
|
||||
@ -650,6 +648,28 @@ class AnnotationEditorUIManager {
|
||||
],
|
||||
proto.delete,
|
||||
],
|
||||
[
|
||||
["Enter", "mac+Enter"],
|
||||
proto.addNewEditorFromKeyboard,
|
||||
{
|
||||
// Those shortcuts can be used in the toolbar for some other actions
|
||||
// like zooming, hence we need to check if the container has the
|
||||
// focus.
|
||||
checker: self =>
|
||||
self.#container.contains(document.activeElement) &&
|
||||
!self.isEnterHandled,
|
||||
},
|
||||
],
|
||||
[
|
||||
[" ", "mac+ "],
|
||||
proto.addNewEditorFromKeyboard,
|
||||
{
|
||||
// Those shortcuts can be used in the toolbar for some other actions
|
||||
// like zooming, hence we need to check if the container has the
|
||||
// focus.
|
||||
checker: self => self.#container.contains(document.activeElement),
|
||||
},
|
||||
],
|
||||
[["Escape", "mac+Escape"], proto.unselectAll],
|
||||
[
|
||||
["ArrowLeft", "mac+ArrowLeft"],
|
||||
@ -1147,8 +1167,10 @@ class AnnotationEditorUIManager {
|
||||
* Change the editor mode (None, FreeText, Ink, ...)
|
||||
* @param {number} mode
|
||||
* @param {string|null} editId
|
||||
* @param {boolean} [isFromKeyboard] - true if the mode change is due to a
|
||||
* keyboard action.
|
||||
*/
|
||||
updateMode(mode, editId = null) {
|
||||
updateMode(mode, editId = null, isFromKeyboard = false) {
|
||||
if (this.#mode === mode) {
|
||||
return;
|
||||
}
|
||||
@ -1164,6 +1186,11 @@ class AnnotationEditorUIManager {
|
||||
for (const layer of this.#allLayers.values()) {
|
||||
layer.updateMode(mode);
|
||||
}
|
||||
if (!editId && isFromKeyboard) {
|
||||
this.addNewEditorFromKeyboard();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!editId) {
|
||||
return;
|
||||
}
|
||||
@ -1176,6 +1203,10 @@ class AnnotationEditorUIManager {
|
||||
}
|
||||
}
|
||||
|
||||
addNewEditorFromKeyboard() {
|
||||
this.currentLayer.addNewEditor();
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the toolbar if it's required to reflect the tool currently used.
|
||||
* @param {number} mode
|
||||
@ -1201,7 +1232,7 @@ class AnnotationEditorUIManager {
|
||||
return;
|
||||
}
|
||||
if (type === AnnotationEditorParamsType.CREATE) {
|
||||
this.currentLayer.addNewEditor(type);
|
||||
this.currentLayer.addNewEditor();
|
||||
return;
|
||||
}
|
||||
|
||||
@ -1416,6 +1447,10 @@ class AnnotationEditorUIManager {
|
||||
return this.#selectedEditors.has(editor);
|
||||
}
|
||||
|
||||
get firstSelectedEditor() {
|
||||
return this.#selectedEditors.values().next().value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unselect an editor.
|
||||
* @param {AnnotationEditor} editor
|
||||
@ -1432,6 +1467,13 @@ class AnnotationEditorUIManager {
|
||||
return this.#selectedEditors.size !== 0;
|
||||
}
|
||||
|
||||
get isEnterHandled() {
|
||||
return (
|
||||
this.#selectedEditors.size === 1 &&
|
||||
this.firstSelectedEditor.isEnterHandled
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Undo the last command.
|
||||
*/
|
||||
@ -1736,7 +1778,7 @@ class AnnotationEditorUIManager {
|
||||
return (
|
||||
this.getActive()?.shouldGetKeyboardEvents() ||
|
||||
(this.#selectedEditors.size === 1 &&
|
||||
this.#selectedEditors.values().next().value.shouldGetKeyboardEvents())
|
||||
this.firstSelectedEditor.shouldGetKeyboardEvents())
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -2808,4 +2808,159 @@ describe("FreeText Editor", () => {
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Create editor with keyboard", () => {
|
||||
let pages;
|
||||
|
||||
beforeAll(async () => {
|
||||
pages = await loadAndWait("empty.pdf", ".annotationEditorLayer");
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await closePages(pages);
|
||||
});
|
||||
|
||||
it("must create an editor from the toolbar", async () => {
|
||||
await Promise.all(
|
||||
pages.map(async ([browserName, page]) => {
|
||||
await page.focus("#editorFreeText");
|
||||
await page.keyboard.press("Enter");
|
||||
|
||||
let selectorEditor = getEditorSelector(0);
|
||||
await page.waitForSelector(selectorEditor, {
|
||||
visible: true,
|
||||
});
|
||||
|
||||
let xy = await getXY(page, selectorEditor);
|
||||
for (let i = 0; i < 5; i++) {
|
||||
await page.keyboard.down("Control");
|
||||
await page.keyboard.press("ArrowUp");
|
||||
await page.keyboard.up("Control");
|
||||
await waitForPositionChange(page, selectorEditor, xy);
|
||||
xy = await getXY(page, selectorEditor);
|
||||
}
|
||||
|
||||
const data = "Hello PDF.js World !!";
|
||||
await page.type(`${selectorEditor} .internal`, data);
|
||||
|
||||
// Commit.
|
||||
await page.keyboard.press("Escape");
|
||||
await page.waitForSelector(`${selectorEditor} .overlay.enabled`);
|
||||
|
||||
let content = await page.$eval(selectorEditor, el =>
|
||||
el.innerText.trimEnd()
|
||||
);
|
||||
|
||||
expect(content).withContext(`In ${browserName}`).toEqual(data);
|
||||
|
||||
// Disable editing mode.
|
||||
await page.click("#editorFreeText");
|
||||
await page.waitForSelector(
|
||||
`.annotationEditorLayer:not(.freetextEditing)`
|
||||
);
|
||||
|
||||
await page.focus("#editorFreeText");
|
||||
await page.keyboard.press(" ");
|
||||
selectorEditor = getEditorSelector(1);
|
||||
await page.waitForSelector(selectorEditor, {
|
||||
visible: true,
|
||||
});
|
||||
|
||||
xy = await getXY(page, selectorEditor);
|
||||
for (let i = 0; i < 5; i++) {
|
||||
await page.keyboard.down("Control");
|
||||
await page.keyboard.press("ArrowDown");
|
||||
await page.keyboard.up("Control");
|
||||
await waitForPositionChange(page, selectorEditor, xy);
|
||||
xy = await getXY(page, selectorEditor);
|
||||
}
|
||||
|
||||
await page.type(`${selectorEditor} .internal`, data);
|
||||
|
||||
// Commit.
|
||||
await page.keyboard.press("Escape");
|
||||
await page.waitForSelector(`${selectorEditor} .overlay.enabled`);
|
||||
|
||||
// Unselect.
|
||||
await page.keyboard.press("Escape");
|
||||
await waitForUnselectedEditor(page, selectorEditor);
|
||||
|
||||
content = await page.$eval(getEditorSelector(1), el =>
|
||||
el.innerText.trimEnd()
|
||||
);
|
||||
|
||||
expect(content).withContext(`In ${browserName}`).toEqual(data);
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it("must create an editor with keyboard", async () => {
|
||||
await Promise.all(
|
||||
pages.map(async ([browserName, page]) => {
|
||||
await page.keyboard.press("Enter");
|
||||
let selectorEditor = getEditorSelector(2);
|
||||
await page.waitForSelector(selectorEditor, {
|
||||
visible: true,
|
||||
});
|
||||
|
||||
let xy = await getXY(page, selectorEditor);
|
||||
for (let i = 0; i < 10; i++) {
|
||||
await page.keyboard.down("Control");
|
||||
await page.keyboard.press("ArrowLeft");
|
||||
await page.keyboard.up("Control");
|
||||
await waitForPositionChange(page, selectorEditor, xy);
|
||||
xy = await getXY(page, selectorEditor);
|
||||
}
|
||||
|
||||
const data = "Hello PDF.js World !!";
|
||||
await page.type(`${selectorEditor} .internal`, data);
|
||||
|
||||
// Commit.
|
||||
await page.keyboard.press("Escape");
|
||||
await page.waitForSelector(`${selectorEditor} .overlay.enabled`);
|
||||
|
||||
// Unselect.
|
||||
await page.keyboard.press("Escape");
|
||||
await waitForUnselectedEditor(page, selectorEditor);
|
||||
|
||||
let content = await page.$eval(getEditorSelector(2), el =>
|
||||
el.innerText.trimEnd()
|
||||
);
|
||||
|
||||
expect(content).withContext(`In ${browserName}`).toEqual(data);
|
||||
|
||||
await page.keyboard.press(" ");
|
||||
selectorEditor = getEditorSelector(3);
|
||||
await page.waitForSelector(selectorEditor, {
|
||||
visible: true,
|
||||
});
|
||||
|
||||
xy = await getXY(page, selectorEditor);
|
||||
for (let i = 0; i < 10; i++) {
|
||||
await page.keyboard.down("Control");
|
||||
await page.keyboard.press("ArrowRight");
|
||||
await page.keyboard.up("Control");
|
||||
await waitForPositionChange(page, selectorEditor, xy);
|
||||
xy = await getXY(page, selectorEditor);
|
||||
}
|
||||
|
||||
await page.type(`${selectorEditor} .internal`, data);
|
||||
|
||||
// Commit.
|
||||
await page.keyboard.press("Escape");
|
||||
await page.waitForSelector(`${selectorEditor} .overlay.enabled`);
|
||||
|
||||
// Unselect.
|
||||
await page.keyboard.press("Escape");
|
||||
await waitForUnselectedEditor(page, selectorEditor);
|
||||
|
||||
content = await page.$eval(selectorEditor, el =>
|
||||
el.innerText.trimEnd()
|
||||
);
|
||||
|
||||
expect(content).withContext(`In ${browserName}`).toEqual(data);
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -2208,7 +2208,7 @@ class PDFViewer {
|
||||
/**
|
||||
* @param {number} mode - AnnotationEditor mode (None, FreeText, Ink, ...)
|
||||
*/
|
||||
set annotationEditorMode({ mode, editId = null }) {
|
||||
set annotationEditorMode({ mode, editId = null, isFromKeyboard = false }) {
|
||||
if (!this.#annotationEditorUIManager) {
|
||||
throw new Error(`The AnnotationEditor is not enabled.`);
|
||||
}
|
||||
@ -2227,7 +2227,7 @@ class PDFViewer {
|
||||
mode,
|
||||
});
|
||||
|
||||
this.#annotationEditorUIManager.updateMode(mode, editId);
|
||||
this.#annotationEditorUIManager.updateMode(mode, editId, isFromKeyboard);
|
||||
}
|
||||
|
||||
// eslint-disable-next-line accessor-pairs
|
||||
|
@ -162,7 +162,12 @@ class Toolbar {
|
||||
for (const { element, eventName, eventDetails } of this.buttons) {
|
||||
element.addEventListener("click", evt => {
|
||||
if (eventName !== null) {
|
||||
this.eventBus.dispatch(eventName, { source: this, ...eventDetails });
|
||||
this.eventBus.dispatch(eventName, {
|
||||
source: this,
|
||||
...eventDetails,
|
||||
// evt.detail is the number of clicks.
|
||||
isFromKeyboard: evt.detail === 0,
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user