[IMP] web: allow to disable the popover re-position

Purpose
=======
When the DOM changes, we recompute the popover position. This is useful
for most popovers, but when we have a form inside the popover (like the
properties definition) it can be very annoying to move the form while
the user fills it.

Task-3380176

closes odoo/odoo#128983

Signed-off-by: Warnon Aurélien (awa) <awa@odoo.com>
This commit is contained in:
std-odoo
2023-08-16 08:10:49 +02:00
parent de0ee14e4e
commit 10110bd4e4
6 changed files with 51 additions and 1 deletions
@@ -11,6 +11,7 @@ export class Popover extends Component {
onPositioned: this.props.onPositioned || this.onPositioned.bind(this),
position: this.props.position,
popper: "ref",
fixedPosition: this.props.fixedPosition,
});
}
onPositioned(el, { direction, variant }) {
@@ -35,6 +36,9 @@ export class Popover extends Component {
// reset all arrow classes
const arrowEl = el.querySelector(".popover-arrow");
if (!arrowEl) {
return;
}
arrowEl.className = "popover-arrow";
switch (position) {
case "tm": // top-middle
@@ -98,6 +102,10 @@ Popover.props = {
type: Function,
optional: true,
},
fixedPosition: {
type: Boolean,
optional: true,
},
target: {
validate: (target) => {
// target may be inside an iframe, so get the Element constructor
@@ -4,7 +4,7 @@
<t t-name="web.PopoverWowl">
<div role="tooltip" class="o_popover popover mw-100" t-att-class="props.class" t-ref="ref">
<t t-slot="default" />
<div class="popover-arrow"/>
<div t-if="!props.fixedPosition" class="popover-arrow"/>
</div>
</t>
@@ -47,6 +47,7 @@ export const popoverService = {
class: options.popoverClass,
position: options.position,
onPositioned: options.onPositioned,
fixedPosition: options.fixedPosition,
},
},
{ onRemove: options.onClose }
@@ -307,12 +307,19 @@ const POSITION_BUS = Symbol("position-bus");
export function usePosition(target, options) {
const popperRef = useRef(options?.popper || DEFAULTS.popper);
const getTarget = typeof target === "function" ? target : () => target;
let wasPositioned = false;
const update = () => {
const targetEl = getTarget();
const popperEl = popperRef.el;
if (!targetEl || !popperEl) {
return;
}
if (options.fixedPosition && wasPositioned) {
// in case we have fixedPosition set to true, we only want to position the popover once,
// and then ignore subsequent reposition events
return;
}
wasPositioned = true;
// Prepare
const iframe = getIFrame(targetEl);
@@ -42,6 +42,7 @@ export class PropertiesField extends Component {
popoverClass: "o_property_field_popover",
position: "top",
onClose: () => this.onCloseCurrentPopover?.(),
fixedPosition: true,
});
this.propertiesRef = useRef("properties");
@@ -264,3 +264,36 @@ QUnit.test("within iframe", async (assert) => {
assert.strictEqual(popoverBox.top, expectedTop);
assert.strictEqual(popoverBox.left, expectedLeft);
});
QUnit.test("popover fixed position", async (assert) => {
const container = document.createElement("div");
container.id = "container";
container.style.backgroundColor = "pink";
container.style.height = "450px";
container.style.width = "450px";
container.style.display = "flex";
container.style.alignItems = "center";
container.style.justifyContent = "center";
popoverTarget.style.height = "50px";
container.appendChild(popoverTarget);
fixture.appendChild(container);
const TestPopover = class extends Popover {
onPositioned(el, { direction, variant }) {
assert.step("onPositioned");
}
};
await mount(TestPopover, fixture, {
props: { target: container, position: "bottom-fit", fixedPosition: true },
});
assert.verifySteps(["onPositioned"]);
// force the DOM update
container.style.height = "125px";
container.style.alignItems = "flex-end";
triggerEvent(document, null, "scroll");
await nextTick();
assert.verifySteps([]);
});