mirror of
https://github.com/odoo/owl.git
synced 2025-10-06 19:59:41 +07:00
6f23b18cab
Previously, when trying to mount a portal into a target that would be mounted in the same render as the portal itself, the portal content could not be mounted correctly. In a previous attempt to fix it, a mistake was made while writing the test causing us to incorrectly believe in now worked, when in fact, it would just move the portal content to the end of its parent but without changing it. This commit fixes the issue by making the `moveBeforeDOMNode` method of VNodes accept a second optional parameter which is the parent in which the element should be moved, defaulting to the current parent. This results in identical behaviour when a parent is not specified, but when it is, the VNode is "reparented" to the passed parent, which is what is now done by the Portal
91 lines
2.2 KiB
TypeScript
91 lines
2.2 KiB
TypeScript
import { onMounted, onWillUnmount } from "./lifecycle_hooks";
|
|
import { BDom, text, VNode } from "./blockdom";
|
|
import { Component } from "./component";
|
|
import { OwlError } from "./error_handling";
|
|
|
|
const VText: any = text("").constructor;
|
|
|
|
class VPortal extends VText implements Partial<VNode<VPortal>> {
|
|
content: BDom | null;
|
|
selector: string;
|
|
target: HTMLElement | null = null;
|
|
|
|
constructor(selector: string, content: BDom) {
|
|
super("");
|
|
this.selector = selector;
|
|
this.content = content;
|
|
}
|
|
|
|
mount(parent: HTMLElement, anchor: ChildNode) {
|
|
super.mount(parent, anchor);
|
|
this.target = document.querySelector(this.selector) as any;
|
|
if (this.target) {
|
|
this.content!.mount(this.target!, null);
|
|
} else {
|
|
this.content!.mount(parent, anchor);
|
|
}
|
|
}
|
|
|
|
beforeRemove() {
|
|
this.content!.beforeRemove();
|
|
}
|
|
remove() {
|
|
if (this.content) {
|
|
super.remove();
|
|
this.content!.remove();
|
|
this.content = null;
|
|
}
|
|
}
|
|
|
|
patch(other: VPortal) {
|
|
super.patch(other);
|
|
if (this.content) {
|
|
this.content.patch(other.content!, true);
|
|
} else {
|
|
this.content = other.content;
|
|
this.content!.mount(this.target!, null);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* kind of similar to <t t-slot="default"/>, but it wraps it around a VPortal
|
|
*/
|
|
export function portalTemplate(app: any, bdom: any, helpers: any) {
|
|
let { callSlot } = helpers;
|
|
return function template(ctx: any, node: any, key = ""): any {
|
|
return new VPortal(ctx.props.target, callSlot(ctx, node, key, "default", false, null));
|
|
};
|
|
}
|
|
|
|
export class Portal extends Component {
|
|
static template = "__portal__";
|
|
static props = {
|
|
target: {
|
|
type: String,
|
|
},
|
|
slots: true,
|
|
};
|
|
|
|
setup() {
|
|
const node: any = this.__owl__;
|
|
|
|
onMounted(() => {
|
|
const portal: VPortal = node.bdom;
|
|
if (!portal.target) {
|
|
const target: HTMLElement = document.querySelector(this.props.target);
|
|
if (target) {
|
|
portal.content!.moveBeforeDOMNode(target.firstChild, target);
|
|
} else {
|
|
throw new OwlError("invalid portal target");
|
|
}
|
|
}
|
|
});
|
|
|
|
onWillUnmount(() => {
|
|
const portal: VPortal = node.bdom;
|
|
portal.remove();
|
|
});
|
|
}
|
|
}
|