[FIX] web: compute containing block layout in position hook

How to reproduce:
- create a list embedded view in Knowledge (/item list)
- in the control panel click on "Filters"

Current behavior:
- the dropdown is not correctly positioned

Expected behavior:
- the dropdown should be positioned around the button

Fix:
`getBestPosition` uses dimensions and layout positionning based on the viewport,
but `reposition` modifies the style properties `top` and `left` of the element,
which are relative to the closest containing block. Since in that case the style
position is `fixed`, there are a number of factor that can influence the
containing block, as specified in:
https://developer.mozilla.org/en-US/docs/Web/CSS/Containing_block#identifying_the_containing_block
We can easily compute the layout offset caused by the containing block just
after resetting the popper top and left to `0px` and take it into account for
the final value.

Task-3267555

closes odoo/odoo#118018

Signed-off-by: Bruno Boi (boi) <boi@odoo.com>
This commit is contained in:
abd-msyukyu-odoo
2023-04-11 11:33:48 +02:00
parent 81bb17091f
commit b9551a5070
2 changed files with 46 additions and 2 deletions
+9 -2
View File
@@ -83,7 +83,9 @@ const DEFAULTS = {
* @param {HTMLElement} reference
* @param {HTMLElement} popper
* @param {Options} options
* @returns {PositioningSolution} the best positioning solution
* @returns {PositioningSolution} the best positioning solution, relative to
* the containing block of the popper.
* => can be applied to popper.style.(top|left)
*/
function getBestPosition(reference, popper, { container, margin, position }) {
// Retrieve directions and variants
@@ -172,7 +174,12 @@ function getBestPosition(reference, popper, { container, margin, position }) {
left: directionValue,
};
return {
...positioning,
// Subtract the offsets of the containing block (relative to the
// viewport). It can be done like that because the style top and
// left were reset to 0px in `reposition`
// https://developer.mozilla.org/en-US/docs/Web/CSS/Containing_block#identifying_the_containing_block
top: positioning.top - popBox.top,
left: positioning.left - popBox.left,
direction: DIRECTIONS[d],
variant: VARIANTS[v],
};
@@ -150,6 +150,43 @@ QUnit.test("has no effect when component is destroyed", async (assert) => {
);
});
QUnit.test("is positioned relative to its containing block", async (assert) => {
const fixtureBox = getFixture().getBoundingClientRect();
// offset the container
const margin = 15;
container.style.margin = `${margin}px`;
let pos1, pos2;
let TestComp = getTestComponent({
onPositioned: (el, pos) => {
pos1 = pos;
},
});
let popper = await mount(TestComp, container);
const popBox1 = document.getElementById("popper").getBoundingClientRect();
destroy(popper);
document.getElementById("reference").remove();
// make container the containing block instead of the viewport
container.style.contain = "layout";
TestComp = getTestComponent({
onPositioned: (el, pos) => {
pos2 = pos;
},
});
popper = await mount(TestComp, container);
const popBox2 = document.getElementById("popper").getBoundingClientRect();
destroy(popper);
// best positions are not the same relative to their containing block
assert.equal(pos1.top, pos2.top + margin + fixtureBox.top);
assert.equal(pos1.left, pos2.left + margin + fixtureBox.left);
// best positions are the same relative to the viewport
assert.equal(popBox1.top, popBox2.top);
assert.equal(popBox1.left, popBox2.left);
});
function getPositionTest(position, positionToCheck) {
return async (assert) => {
assert.expect(2);