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);
|
this.div?.classList.toggle("draggable", value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns {boolean} true if the editor handles the Enter key itself.
|
||||||
|
*/
|
||||||
|
get isEnterHandled() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
center() {
|
center() {
|
||||||
const [pageWidth, pageHeight] = this.pageDimensions;
|
const [pageWidth, pageHeight] = this.pageDimensions;
|
||||||
switch (this.parentRotation) {
|
switch (this.parentRotation) {
|
||||||
|
@ -605,10 +605,8 @@ class AnnotationEditorUIManager {
|
|||||||
const arrowChecker = self => {
|
const arrowChecker = self => {
|
||||||
// If the focused element is an input, we don't want to handle the arrow.
|
// 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.
|
// For example, sliders can be controlled with the arrow keys.
|
||||||
const { activeElement } = document;
|
|
||||||
return (
|
return (
|
||||||
activeElement &&
|
self.#container.contains(document.activeElement) &&
|
||||||
self.#container.contains(activeElement) &&
|
|
||||||
self.hasSomethingToControl()
|
self.hasSomethingToControl()
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@ -650,6 +648,28 @@ class AnnotationEditorUIManager {
|
|||||||
],
|
],
|
||||||
proto.delete,
|
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],
|
[["Escape", "mac+Escape"], proto.unselectAll],
|
||||||
[
|
[
|
||||||
["ArrowLeft", "mac+ArrowLeft"],
|
["ArrowLeft", "mac+ArrowLeft"],
|
||||||
@ -1147,8 +1167,10 @@ class AnnotationEditorUIManager {
|
|||||||
* Change the editor mode (None, FreeText, Ink, ...)
|
* Change the editor mode (None, FreeText, Ink, ...)
|
||||||
* @param {number} mode
|
* @param {number} mode
|
||||||
* @param {string|null} editId
|
* @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) {
|
if (this.#mode === mode) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -1164,6 +1186,11 @@ class AnnotationEditorUIManager {
|
|||||||
for (const layer of this.#allLayers.values()) {
|
for (const layer of this.#allLayers.values()) {
|
||||||
layer.updateMode(mode);
|
layer.updateMode(mode);
|
||||||
}
|
}
|
||||||
|
if (!editId && isFromKeyboard) {
|
||||||
|
this.addNewEditorFromKeyboard();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (!editId) {
|
if (!editId) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -1176,6 +1203,10 @@ class AnnotationEditorUIManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
addNewEditorFromKeyboard() {
|
||||||
|
this.currentLayer.addNewEditor();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update the toolbar if it's required to reflect the tool currently used.
|
* Update the toolbar if it's required to reflect the tool currently used.
|
||||||
* @param {number} mode
|
* @param {number} mode
|
||||||
@ -1201,7 +1232,7 @@ class AnnotationEditorUIManager {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (type === AnnotationEditorParamsType.CREATE) {
|
if (type === AnnotationEditorParamsType.CREATE) {
|
||||||
this.currentLayer.addNewEditor(type);
|
this.currentLayer.addNewEditor();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1416,6 +1447,10 @@ class AnnotationEditorUIManager {
|
|||||||
return this.#selectedEditors.has(editor);
|
return this.#selectedEditors.has(editor);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get firstSelectedEditor() {
|
||||||
|
return this.#selectedEditors.values().next().value;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Unselect an editor.
|
* Unselect an editor.
|
||||||
* @param {AnnotationEditor} editor
|
* @param {AnnotationEditor} editor
|
||||||
@ -1432,6 +1467,13 @@ class AnnotationEditorUIManager {
|
|||||||
return this.#selectedEditors.size !== 0;
|
return this.#selectedEditors.size !== 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get isEnterHandled() {
|
||||||
|
return (
|
||||||
|
this.#selectedEditors.size === 1 &&
|
||||||
|
this.firstSelectedEditor.isEnterHandled
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Undo the last command.
|
* Undo the last command.
|
||||||
*/
|
*/
|
||||||
@ -1736,7 +1778,7 @@ class AnnotationEditorUIManager {
|
|||||||
return (
|
return (
|
||||||
this.getActive()?.shouldGetKeyboardEvents() ||
|
this.getActive()?.shouldGetKeyboardEvents() ||
|
||||||
(this.#selectedEditors.size === 1 &&
|
(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, ...)
|
* @param {number} mode - AnnotationEditor mode (None, FreeText, Ink, ...)
|
||||||
*/
|
*/
|
||||||
set annotationEditorMode({ mode, editId = null }) {
|
set annotationEditorMode({ mode, editId = null, isFromKeyboard = false }) {
|
||||||
if (!this.#annotationEditorUIManager) {
|
if (!this.#annotationEditorUIManager) {
|
||||||
throw new Error(`The AnnotationEditor is not enabled.`);
|
throw new Error(`The AnnotationEditor is not enabled.`);
|
||||||
}
|
}
|
||||||
@ -2227,7 +2227,7 @@ class PDFViewer {
|
|||||||
mode,
|
mode,
|
||||||
});
|
});
|
||||||
|
|
||||||
this.#annotationEditorUIManager.updateMode(mode, editId);
|
this.#annotationEditorUIManager.updateMode(mode, editId, isFromKeyboard);
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line accessor-pairs
|
// eslint-disable-next-line accessor-pairs
|
||||||
|
@ -162,7 +162,12 @@ class Toolbar {
|
|||||||
for (const { element, eventName, eventDetails } of this.buttons) {
|
for (const { element, eventName, eventDetails } of this.buttons) {
|
||||||
element.addEventListener("click", evt => {
|
element.addEventListener("click", evt => {
|
||||||
if (eventName !== null) {
|
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…
x
Reference in New Issue
Block a user