diff --git a/addons/web_editor/static/src/js/editor/odoo-editor/src/OdooEditor.js b/addons/web_editor/static/src/js/editor/odoo-editor/src/OdooEditor.js index 8e299ab276c..ee68cd8acbb 100644 --- a/addons/web_editor/static/src/js/editor/odoo-editor/src/OdooEditor.js +++ b/addons/web_editor/static/src/js/editor/odoo-editor/src/OdooEditor.js @@ -216,6 +216,7 @@ export class OdooEditor extends EventTarget { getContextFromParentRect: () => { return { top: 0, left: 0 }; }, + getScrollContainerRect: () => document.body.getBoundingClientRect(), toSanitize: true, isRootEditable: true, placeholder: false, @@ -3070,7 +3071,8 @@ export class OdooEditor extends EventTarget { _positionToolbar() { const OFFSET = 10; let isBottom = false; - this.toolbar.classList.toggle('toolbar-bottom', false); + // Toolbar display must not be none in order to calculate width and height. + this.toolbar.classList.toggle('d-none', false); this.toolbar.style.maxWidth = window.innerWidth - OFFSET * 2 + 'px'; const sel = this.document.getSelection(); const range = sel.getRangeAt(0); @@ -3095,6 +3097,7 @@ export class OdooEditor extends EventTarget { const toolbarHeight = this.toolbar.offsetHeight; const editorRect = this.editable.getBoundingClientRect(); const parentContextRect = this.options.getContextFromParentRect(); + const scrollContainerRect = this.options.getScrollContainerRect(); const editorTopPos = Math.max(0, editorRect.top); const scrollX = document.defaultView.scrollX; const scrollY = document.defaultView.scrollY; @@ -3111,14 +3114,12 @@ export class OdooEditor extends EventTarget { // Get top position. let top = correctedSelectionRect.top - toolbarHeight - OFFSET; - // Ensure the toolbar doesn't overflow the editor on the top. - if (top < editorTopPos) { + // Ensure the toolbar doesn't overflow the editor or scroll container on the top. + if (top < editorTopPos || top + parentContextRect.top - scrollContainerRect.top < OFFSET / 2) { // Position the toolbar below the selection. top = correctedSelectionRect.bottom + OFFSET; isBottom = true; } - // Ensure the toolbar doesn't overflow the editor on the bottom. - top = Math.min(window.innerHeight - OFFSET - toolbarHeight, top); // Offset top to compensate for parent context position (eg. Iframe). top += parentContextRect.top; this.toolbar.style.top = scrollY + top + 'px'; @@ -3130,12 +3131,18 @@ export class OdooEditor extends EventTarget { // Ensure the arrow doesn't overflow the toolbar on the right. arrowLeftPos = Math.min(toolbarWidth - OFFSET - 20, arrowLeftPos); this.toolbar.style.setProperty('--arrow-left-pos', arrowLeftPos + 'px'); - if (isBottom) { - this.toolbar.classList.toggle('toolbar-bottom', true); - this.toolbar.style.setProperty('--arrow-top-pos', -17 + 'px'); - } else { - this.toolbar.style.setProperty('--arrow-top-pos', toolbarHeight - 3 + 'px'); - } + const arrowTopPos = isBottom ? -17 : toolbarHeight - 3; + this.toolbar.classList.toggle('toolbar-bottom', isBottom); + this.toolbar.style.setProperty('--arrow-top-pos', arrowTopPos + 'px'); + + // Calculate toolbar dimensions including the arrow. + const toolbarTop = Math.min(top , top + arrowTopPos); + const toolbarBottom = Math.max(top + toolbarHeight, top + arrowTopPos + 20); + + // Hide toolbar if it overflows the scroll container. + const distToScrollContainer = Math.min(toolbarTop - scrollContainerRect.top, + scrollContainerRect.bottom - toolbarBottom); + this.toolbar.classList.toggle('d-none', distToScrollContainer < OFFSET / 2); } // PASTING / DROPPING diff --git a/addons/web_editor/static/src/js/wysiwyg/wysiwyg.js b/addons/web_editor/static/src/js/wysiwyg/wysiwyg.js index 63853bb2f28..ae413bc1872 100644 --- a/addons/web_editor/static/src/js/wysiwyg/wysiwyg.js +++ b/addons/web_editor/static/src/js/wysiwyg/wysiwyg.js @@ -368,6 +368,12 @@ export class Wysiwyg extends Component { allowInlineAtRoot: this.options.allowInlineAtRoot, getYoutubeVideoElement: getYoutubeVideoElement, getContextFromParentRect: options.getContextFromParentRect, + getScrollContainerRect: () => { + if (!this.scrollContainer || !this.scrollContainer.getBoundingClientRect) { + this.scrollContainer = document.querySelector('.o_action_manager'); + } + return this.scrollContainer.getBoundingClientRect(); + }, getPowerboxElement: () => { const selection = (this.options.document || document).getSelection(); if (selection.isCollapsed && selection.rangeCount) { @@ -2760,6 +2766,7 @@ export class Wysiwyg extends Component { } _onScroll(ev) { if (ev.target.contains(this.$editable[0])) { + this.scrollContainer = ev.target; this.odooEditor.updateToolbarPosition(); } } diff --git a/addons/web_editor/static/src/js/wysiwyg/wysiwyg_iframe.js b/addons/web_editor/static/src/js/wysiwyg/wysiwyg_iframe.js index f6eef88e415..46e450ae622 100644 --- a/addons/web_editor/static/src/js/wysiwyg/wysiwyg_iframe.js +++ b/addons/web_editor/static/src/js/wysiwyg/wysiwyg_iframe.js @@ -204,7 +204,12 @@ patch(Wysiwyg.prototype, { if (this.options.inIframe) { const iframeDocument = this.$iframe[0].contentDocument; const scrollInIframe = ev.target === iframeDocument || ev.target.ownerDocument === iframeDocument; - if (ev.target.contains(this.$iframe[0]) || scrollInIframe) { + if (ev.target.contains(this.$iframe[0])) { + this.scrollContainer = ev.target; + this.odooEditor.updateToolbarPosition(); + } else if (scrollInIframe) { + // UpdateToolbarPosition needs a scroll container in the top document. + this.scrollContainer = this.$iframe[0]; this.odooEditor.updateToolbarPosition(); } } else {