Merge pull request #16809 from calixteman/editor_focus_after_moving_in_dom

[Editor] Move an the editor div in the DOM once a translation with the keyboard is done
This commit is contained in:
calixteman 2023-08-09 10:10:28 +02:00 committed by GitHub
commit 77392dfce4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 134 additions and 13 deletions

View File

@ -320,19 +320,15 @@ class AnnotationEditorLayer {
this.detach(editor);
this.#uiManager.removeEditor(editor);
editor.div.style.display = "none";
setTimeout(() => {
// When the div is removed from DOM the focus can move on the
// document.body, so we just slightly postpone the removal in
// order to let an element potentially grab the focus before
// the body.
editor.div.style.display = "";
editor.div.remove();
editor.isAttachedToDOM = false;
if (document.activeElement === document.body) {
if (editor.div.contains(document.activeElement)) {
setTimeout(() => {
// When the div is removed from DOM the focus can move on the
// document.body, so we need to move it back to the main container.
this.#uiManager.focusMainContainer();
}
}, 0);
}, 0);
}
editor.div.remove();
editor.isAttachedToDOM = false;
if (!this.#isCleaningUp) {
this.addInkEditorIfNeeded(/* isCommitting = */ false);
@ -385,6 +381,25 @@ class AnnotationEditorLayer {
}
moveEditorInDOM(editor) {
const { activeElement } = document;
if (editor.div.contains(activeElement)) {
// When the div is moved in the DOM the focus can move somewhere else,
// so we want to be sure that the focus will stay on the editor but we
// don't want to call any focus callbacks, hence we disable them and only
// re-enable them when the editor has the focus.
editor._focusEventsAllowed = false;
setTimeout(() => {
editor.div.addEventListener(
"focusin",
() => {
editor._focusEventsAllowed = true;
},
{ once: true }
);
activeElement.focus();
}, 0);
}
this.#accessibilityManager?.moveElementInDOM(
this.div,
editor.div,

View File

@ -50,6 +50,8 @@ class AnnotationEditor {
_uiManager = null;
_focusEventsAllowed = true;
#isDraggable = false;
#zIndex = AnnotationEditor._zIndex++;
@ -190,6 +192,9 @@ class AnnotationEditor {
* onfocus callback.
*/
focusin(event) {
if (!this._focusEventsAllowed) {
return;
}
if (!this.#hasBeenSelected) {
this.parent.setSelected(this);
} else {
@ -202,6 +207,10 @@ class AnnotationEditor {
* @param {FocusEvent} event
*/
focusout(event) {
if (!this._focusEventsAllowed) {
return;
}
if (!this.isAttachedToDOM) {
return;
}
@ -284,6 +293,7 @@ class AnnotationEditor {
*/
translateInPage(x, y) {
this.#translate(this.pageDimensions, x, y);
this.parent.moveEditorInDOM(this);
this.div.scrollIntoView({ block: "nearest" });
}
@ -790,7 +800,6 @@ class AnnotationEditor {
this.fixAndSetPosition();
this.parent.moveEditorInDOM(this);
this.div.focus();
};
window.addEventListener("pointerup", pointerUpCallback);
// If the user is using alt+tab during the dragging session, the pointerup

View File

@ -342,6 +342,9 @@ class FreeTextEditor extends AnnotationEditor {
/** @inheritdoc */
focusin(event) {
if (!this._focusEventsAllowed) {
return;
}
super.focusin(event);
if (event.target !== this.editorDiv) {
this.editorDiv.focus();

View File

@ -634,6 +634,9 @@ class InkEditor extends AnnotationEditor {
/** @inheritdoc */
focusin(event) {
if (!this._focusEventsAllowed) {
return;
}
super.focusin(event);
this.enableEditMode();
}

View File

@ -390,6 +390,7 @@ describe("FreeText Editor", () => {
await clearAll(page);
await page.mouse.click(rect.x + 200, rect.y + 100);
await page.waitForTimeout(10);
for (let i = 0; i < 5; i++) {
await page.type(`${getEditorSelector(9)} .internal`, "A");
@ -2111,4 +2112,85 @@ describe("FreeText Editor", () => {
);
});
});
describe("Freetext must stay focused after having been moved", () => {
let pages;
beforeAll(async () => {
pages = await loadAndWait("empty.pdf", ".annotationEditorLayer");
});
afterAll(async () => {
await closePages(pages);
});
it("must keep the focus", async () => {
await Promise.all(
pages.map(async ([browserName, page]) => {
await page.click("#editorFreeText");
const rect = await page.$eval(".annotationEditorLayer", el => {
const { x, y } = el.getBoundingClientRect();
return { x, y };
});
await page.mouse.click(rect.x + 100, rect.y + 100);
await page.waitForTimeout(10);
await page.type(`${getEditorSelector(0)} .internal`, "A");
// Commit.
await page.keyboard.press("Escape");
await page.waitForTimeout(10);
await page.mouse.click(rect.x + 110, rect.y + 150);
await page.waitForTimeout(10);
await page.type(`${getEditorSelector(0)} .internal`, "B");
// Commit.
await page.keyboard.press("Escape");
await page.waitForTimeout(10);
await page.mouse.click(rect.x + 115, rect.y + 155);
await page.waitForTimeout(10);
const pos = n =>
page.evaluate(sel => {
const editor = document.querySelector(sel);
return Array.prototype.indexOf.call(
editor.parentNode.childNodes,
editor
);
}, getEditorSelector(n));
expect(await pos(0))
.withContext(`In ${browserName}`)
.toEqual(0);
expect(await pos(1))
.withContext(`In ${browserName}`)
.toEqual(1);
for (let i = 0; i < 6; i++) {
await page.keyboard.down("Control");
await page.keyboard.press("ArrowUp");
await page.keyboard.up("Control");
await page.waitForTimeout(1);
}
await page.waitForTimeout(100);
const focused = await page.evaluate(sel => {
const editor = document.querySelector(sel);
return editor === document.activeElement;
}, getEditorSelector(1));
expect(focused).withContext(`In ${browserName}`).toEqual(true);
expect(await pos(0))
.withContext(`In ${browserName}`)
.toEqual(1);
expect(await pos(1))
.withContext(`In ${browserName}`)
.toEqual(0);
})
);
});
});
});

View File

@ -50,23 +50,28 @@ describe("Ink Editor", () => {
await page.mouse.down();
await page.mouse.move(x + 50, y + 50);
await page.mouse.up();
await page.waitForTimeout(10);
await page.keyboard.press("Escape");
await page.waitForTimeout(10);
}
await page.keyboard.down("Control");
await page.keyboard.press("a");
await page.keyboard.up("Control");
await page.waitForTimeout(10);
expect(await getSelectedEditors(page))
.withContext(`In ${browserName}`)
.toEqual([0, 1, 2]);
await page.keyboard.press("Backspace");
await page.waitForTimeout(10);
await page.keyboard.down("Control");
await page.keyboard.press("z");
await page.keyboard.up("Control");
await page.waitForTimeout(10);
expect(await getSelectedEditors(page))
.withContext(`In ${browserName}`)
@ -81,8 +86,10 @@ describe("Ink Editor", () => {
await page.keyboard.down("Control");
await page.keyboard.press("a");
await page.keyboard.up("Control");
await page.waitForTimeout(10);
await page.keyboard.press("Backspace");
await page.waitForTimeout(10);
const rect = await page.$eval(".annotationEditorLayer", el => {
// With Chrome something is wrong when serializing a DomRect,
@ -97,8 +104,10 @@ describe("Ink Editor", () => {
await page.mouse.down();
await page.mouse.move(xStart + 50, yStart + 50);
await page.mouse.up();
await page.waitForTimeout(10);
await page.keyboard.press("Escape");
await page.waitForTimeout(10);
const rectBefore = await page.$eval(".inkEditor canvas", el => {
const { x, y } = el.getBoundingClientRect();