[IMP] web: link popover together
Before this commit, cliking on a popover that was open inside another popover closed both of them. After this commit, the popovers are not closed. them will close only if we click out of them and the popovers they opened. closes odoo/odoo#140258 Signed-off-by: Francois Georis (fge) <fge@odoo.com>
This commit is contained in:
@@ -1,10 +1,12 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { Component, onWillDestroy, useExternalListener, xml } from "@odoo/owl";
|
||||
import { Component, onWillDestroy, useExternalListener, useSubEnv, xml } from "@odoo/owl";
|
||||
import { useHotkey } from "../hotkeys/hotkey_hook";
|
||||
import { useChildRef } from "../utils/hooks";
|
||||
import { Popover } from "./popover";
|
||||
|
||||
export const POPOVER_SYMBOL = Symbol("popover");
|
||||
|
||||
export class PopoverController extends Component {
|
||||
static template = xml`
|
||||
<Popover t-props="props.popoverProps" ref="popoverRef">
|
||||
@@ -19,28 +21,40 @@ export class PopoverController extends Component {
|
||||
"component",
|
||||
"componentProps",
|
||||
"popoverProps",
|
||||
"subPopovers?",
|
||||
];
|
||||
|
||||
setup() {
|
||||
this.props.subPopovers?.add(this);
|
||||
|
||||
this.subPopovers = new Set();
|
||||
useSubEnv({ [POPOVER_SYMBOL]: this.subPopovers });
|
||||
|
||||
if (this.props.target.isConnected) {
|
||||
this.popoverRef = useChildRef();
|
||||
useExternalListener(window, "pointerdown", this.onClickAway, { capture: true });
|
||||
useHotkey("escape", () => this.props.close());
|
||||
const targetObserver = new MutationObserver(this.onTargetMutate.bind(this));
|
||||
targetObserver.observe(this.props.target.parentElement, { childList: true });
|
||||
onWillDestroy(() => targetObserver.disconnect());
|
||||
onWillDestroy(() => {
|
||||
targetObserver.disconnect();
|
||||
this.props.subPopovers?.delete(this);
|
||||
});
|
||||
} else {
|
||||
this.props.close();
|
||||
}
|
||||
}
|
||||
|
||||
isInside(target) {
|
||||
if (this.props.target.contains(target) || this.popoverRef.el.contains(target)) {
|
||||
return true;
|
||||
}
|
||||
return [...this.subPopovers].some((p) => p.isInside(target));
|
||||
}
|
||||
|
||||
onClickAway(ev) {
|
||||
const target = ev.composedPath()[0];
|
||||
if (
|
||||
this.props.closeOnClickAway(target) &&
|
||||
!this.props.target.contains(target) &&
|
||||
!this.popoverRef.el.contains(target)
|
||||
) {
|
||||
if (this.props.closeOnClickAway(target) && !this.isInside(target)) {
|
||||
this.props.close();
|
||||
ev.preventDefault();
|
||||
}
|
||||
|
||||
@@ -2,7 +2,8 @@
|
||||
|
||||
import { useService } from "@web/core/utils/hooks";
|
||||
|
||||
import { onWillUnmount, status, useComponent } from "@odoo/owl";
|
||||
import { onWillUnmount, status, useComponent, useEnv } from "@odoo/owl";
|
||||
import { POPOVER_SYMBOL } from "./popover_controller";
|
||||
|
||||
/**
|
||||
* @typedef PopoverHookReturnType
|
||||
@@ -45,9 +46,11 @@ export function makePopover(popoverService, component, options) {
|
||||
* @returns {PopoverHookReturnType}
|
||||
*/
|
||||
export function usePopover(component, options = {}) {
|
||||
const env = useEnv();
|
||||
const popoverService = useService("popover");
|
||||
const owner = useComponent();
|
||||
const newOptions = Object.create(options);
|
||||
newOptions[POPOVER_SYMBOL] = env[POPOVER_SYMBOL];
|
||||
newOptions.onClose = () => {
|
||||
if (status(owner) !== "destroyed") {
|
||||
options.onClose?.();
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import { markRaw } from "@odoo/owl";
|
||||
import { registry } from "../registry";
|
||||
import { PopoverController } from "./popover_controller";
|
||||
import { POPOVER_SYMBOL, PopoverController } from "./popover_controller";
|
||||
|
||||
/**
|
||||
* @typedef {{
|
||||
@@ -40,6 +40,7 @@ export const popoverService = {
|
||||
target,
|
||||
close: () => remove(),
|
||||
closeOnClickAway,
|
||||
subPopovers: options[POPOVER_SYMBOL],
|
||||
component,
|
||||
componentProps: markRaw(props),
|
||||
popoverProps: {
|
||||
|
||||
@@ -4,7 +4,7 @@ import { usePopover } from "@web/core/popover/popover_hook";
|
||||
import { popoverService } from "@web/core/popover/popover_service";
|
||||
import { registry } from "@web/core/registry";
|
||||
import { clearRegistryWithCleanup, makeTestEnv } from "../../helpers/mock_env";
|
||||
import { destroy, getFixture, mount, nextTick } from "../../helpers/utils";
|
||||
import { click, destroy, getFixture, mount, nextTick } from "../../helpers/utils";
|
||||
|
||||
import { Component, xml } from "@odoo/owl";
|
||||
import { hotkeyService } from "@web/core/hotkeys/hotkey_service";
|
||||
@@ -85,3 +85,54 @@ QUnit.test("close popover when component is unmounted", async (assert) => {
|
||||
assert.containsNone(target, ".o_popover #comp1");
|
||||
assert.containsNone(target, ".o_popover #comp2");
|
||||
});
|
||||
|
||||
QUnit.test("popover opened from another", async (assert) => {
|
||||
class Comp extends Component {
|
||||
static id = 0;
|
||||
static template = xml`
|
||||
<div class="p-4">
|
||||
<button class="pop-open" t-on-click="(ev) => this.popover.open(ev.target, {})">open popover</button>
|
||||
</div>
|
||||
`;
|
||||
setup() {
|
||||
this.popover = usePopover(Comp, {
|
||||
popoverClass: `popover-${++Comp.id}`,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
await mount(Comp, target, { env });
|
||||
|
||||
await click(target, ".pop-open");
|
||||
assert.containsOnce(target, ".popover-1", "open first popover");
|
||||
|
||||
await click(target, ".popover-1 .pop-open");
|
||||
assert.containsN(target, ".o_popover", 2, "open second popover from the first one");
|
||||
assert.containsOnce(target, ".popover-1");
|
||||
assert.containsOnce(target, ".popover-2");
|
||||
|
||||
await click(target, ".popover-2 .pop-open");
|
||||
assert.containsN(target, ".o_popover", 3, "open third popover from the second one");
|
||||
assert.containsOnce(target, ".popover-1");
|
||||
assert.containsOnce(target, ".popover-2");
|
||||
assert.containsOnce(target, ".popover-3");
|
||||
|
||||
await click(target, ".popover-3");
|
||||
assert.containsN(target, ".o_popover", 3, "clicking inside third popover closes nothing");
|
||||
assert.containsOnce(target, ".popover-1");
|
||||
assert.containsOnce(target, ".popover-2");
|
||||
assert.containsOnce(target, ".popover-3");
|
||||
|
||||
await click(target, ".popover-2");
|
||||
assert.containsN(
|
||||
target,
|
||||
".o_popover",
|
||||
2,
|
||||
"clicking inside second popover closes third popover"
|
||||
);
|
||||
assert.containsOnce(target, ".popover-1");
|
||||
assert.containsOnce(target, ".popover-2");
|
||||
|
||||
await click(target, "#close");
|
||||
assert.containsNone(target, ".o_popover", "clicking out of any popover closes them all");
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user