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.detach(editor);
this.#uiManager.removeEditor(editor); this.#uiManager.removeEditor(editor);
editor.div.style.display = "none"; if (editor.div.contains(document.activeElement)) {
setTimeout(() => { setTimeout(() => {
// When the div is removed from DOM the focus can move on the // When the div is removed from DOM the focus can move on the
// document.body, so we just slightly postpone the removal in // document.body, so we need to move it back to the main container.
// 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) {
this.#uiManager.focusMainContainer(); this.#uiManager.focusMainContainer();
} }, 0);
}, 0); }
editor.div.remove();
editor.isAttachedToDOM = false;
if (!this.#isCleaningUp) { if (!this.#isCleaningUp) {
this.addInkEditorIfNeeded(/* isCommitting = */ false); this.addInkEditorIfNeeded(/* isCommitting = */ false);
@ -385,6 +381,25 @@ class AnnotationEditorLayer {
} }
moveEditorInDOM(editor) { 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.#accessibilityManager?.moveElementInDOM(
this.div, this.div,
editor.div, editor.div,

View File

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

View File

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

View File

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

View File

@ -390,6 +390,7 @@ describe("FreeText Editor", () => {
await clearAll(page); await clearAll(page);
await page.mouse.click(rect.x + 200, rect.y + 100); await page.mouse.click(rect.x + 200, rect.y + 100);
await page.waitForTimeout(10);
for (let i = 0; i < 5; i++) { for (let i = 0; i < 5; i++) {
await page.type(`${getEditorSelector(9)} .internal`, "A"); 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.down();
await page.mouse.move(x + 50, y + 50); await page.mouse.move(x + 50, y + 50);
await page.mouse.up(); await page.mouse.up();
await page.waitForTimeout(10);
await page.keyboard.press("Escape"); await page.keyboard.press("Escape");
await page.waitForTimeout(10);
} }
await page.keyboard.down("Control"); await page.keyboard.down("Control");
await page.keyboard.press("a"); await page.keyboard.press("a");
await page.keyboard.up("Control"); await page.keyboard.up("Control");
await page.waitForTimeout(10);
expect(await getSelectedEditors(page)) expect(await getSelectedEditors(page))
.withContext(`In ${browserName}`) .withContext(`In ${browserName}`)
.toEqual([0, 1, 2]); .toEqual([0, 1, 2]);
await page.keyboard.press("Backspace"); await page.keyboard.press("Backspace");
await page.waitForTimeout(10);
await page.keyboard.down("Control"); await page.keyboard.down("Control");
await page.keyboard.press("z"); await page.keyboard.press("z");
await page.keyboard.up("Control"); await page.keyboard.up("Control");
await page.waitForTimeout(10);
expect(await getSelectedEditors(page)) expect(await getSelectedEditors(page))
.withContext(`In ${browserName}`) .withContext(`In ${browserName}`)
@ -81,8 +86,10 @@ describe("Ink Editor", () => {
await page.keyboard.down("Control"); await page.keyboard.down("Control");
await page.keyboard.press("a"); await page.keyboard.press("a");
await page.keyboard.up("Control"); await page.keyboard.up("Control");
await page.waitForTimeout(10);
await page.keyboard.press("Backspace"); await page.keyboard.press("Backspace");
await page.waitForTimeout(10);
const rect = await page.$eval(".annotationEditorLayer", el => { const rect = await page.$eval(".annotationEditorLayer", el => {
// With Chrome something is wrong when serializing a DomRect, // With Chrome something is wrong when serializing a DomRect,
@ -97,8 +104,10 @@ describe("Ink Editor", () => {
await page.mouse.down(); await page.mouse.down();
await page.mouse.move(xStart + 50, yStart + 50); await page.mouse.move(xStart + 50, yStart + 50);
await page.mouse.up(); await page.mouse.up();
await page.waitForTimeout(10);
await page.keyboard.press("Escape"); await page.keyboard.press("Escape");
await page.waitForTimeout(10);
const rectBefore = await page.$eval(".inkEditor canvas", el => { const rectBefore = await page.$eval(".inkEditor canvas", el => {
const { x, y } = el.getBoundingClientRect(); const { x, y } = el.getBoundingClientRect();