diff --git a/addons/web_editor/static/lib/odoo-editor/src/OdooEditor.js b/addons/web_editor/static/lib/odoo-editor/src/OdooEditor.js index b716bbbcafd..36c929c540f 100644 --- a/addons/web_editor/static/lib/odoo-editor/src/OdooEditor.js +++ b/addons/web_editor/static/lib/odoo-editor/src/OdooEditor.js @@ -18,6 +18,7 @@ import { containsUnremovable, DIRECTIONS, endPos, + ensureFocus, getCursorDirection, getListMode, getOuid, @@ -1893,7 +1894,7 @@ export class OdooEditor extends EventTarget { this.historyStep(true); this._historyStepsStates.set(peek(this._historySteps).id, 'consumed'); setTimeout(() => { - this.editable.focus(); + ensureFocus(this.editable); getDeepRange(this.editable, { select: true }); }); }, diff --git a/addons/web_editor/static/lib/odoo-editor/src/utils/utils.js b/addons/web_editor/static/lib/odoo-editor/src/utils/utils.js index 1e1db7fd575..28bcc8e643b 100644 --- a/addons/web_editor/static/lib/odoo-editor/src/utils/utils.js +++ b/addons/web_editor/static/lib/odoo-editor/src/utils/utils.js @@ -462,6 +462,24 @@ export function getNormalizedCursorPosition(node, offset, full = true) { return [node, offset]; } +/** + * Guarantee that the focus is on element or one of its children. + * + * A simple call to element.focus will change the editable context + * if one of the parents of the current activeElement is not editable, + * and the caret position will not be preserved, even if activeElement is + * one of the subchildren of element. This is why the (re)focus is + * only called when the current activeElement is not one of the + * (sub)children of element. + * + * @param {Element} element should have the focus or a child with the focus + */ + export function ensureFocus(element) { + const activeElement = element.ownerDocument.activeElement; + if (activeElement !== element && (!element.contains(activeElement) || !activeElement.isContentEditable)) { + element.focus(); + } +} /** * @param {Node} anchorNode * @param {number} anchorOffset diff --git a/addons/web_editor/static/lib/odoo-editor/test/spec/editor.test.js b/addons/web_editor/static/lib/odoo-editor/test/spec/editor.test.js index 7fb041b37ab..439b9dcd7a5 100644 --- a/addons/web_editor/static/lib/odoo-editor/test/spec/editor.test.js +++ b/addons/web_editor/static/lib/odoo-editor/test/spec/editor.test.js @@ -1,6 +1,6 @@ import { applyInlineStyle } from '../../src/commands/commands.js'; import { OdooEditor } from '../../src/OdooEditor.js'; -import { getTraversedNodes } from '../../src/utils/utils.js'; +import { getTraversedNodes, setCursorStart } from '../../src/utils/utils.js'; import { BasicEditor, deleteBackward, @@ -9,6 +9,8 @@ import { insertParagraphBreak, insertText, keydown, + keyup, + nextTickFrame, redo, testEditor, undo, diff --git a/addons/web_editor/static/lib/odoo-editor/test/spec/utils.test.js b/addons/web_editor/static/lib/odoo-editor/test/spec/utils.test.js index 308335b70e7..b506479f6ac 100644 --- a/addons/web_editor/static/lib/odoo-editor/test/spec/utils.test.js +++ b/addons/web_editor/static/lib/odoo-editor/test/spec/utils.test.js @@ -7,6 +7,7 @@ import { closestElement, descendants, endPos, + ensureFocus, firstLeaf, getAdjacentPreviousSiblings, getAdjacentNextSiblings, @@ -33,7 +34,15 @@ import { DIRECTIONS, isBlock, } from '../../src/utils/utils.js'; -import { BasicEditor, testEditor } from '../utils.js'; +import { + BasicEditor, + insertText, + keydown, + keyup, + nextTickFrame, + testEditor, + unformat, +} from '../utils.js'; const cleanTestHtml = () => { const testElements = document.querySelectorAll('body>div[contenteditable=true]'); @@ -722,6 +731,90 @@ describe('Utils', () => { // Cursor management //------------------------------------------------------------------------------ + describe('ensureFocus', () => { + it('should preserve the focus on the child of this.editable when executing a powerbox command even if it is enclosed in a contenteditable=false', async () => { + await testEditor(BasicEditor, { + contentBefore: unformat(` +
[]
[]
focusWasConserved
+[]
[]
[]