Merge pull request #16761 from calixteman/editor_add_new_with_keyboard
[Editor] Add the possibility to create a new editor in using the keyboard (bug 1853424)
This commit is contained in:
commit
905ad1fe68
@ -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