mirror of
https://github.com/odoo/owl.git
synced 2025-10-06 19:59:41 +07:00
e94428a186
Previously, having a Function as a type in the static props description of a component would only work if the component was not the root component, as the static props description on Component was "any", whereas the static props description on ComponentConstructor was "Schema". This meant that static props description on non-root components was not type-checked, and on root components it was type-checked only on the mount call. This commit makes it so that static type description is of type "Schema" on Component, now causing static props description to be type-checked, and adds `typeof Function` to the `BaseType` union, which allows declaring that a component expects a function as a prop. Closes #1448
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 "../common/owl_error";
|
|
|
|
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,
|
|
} as const;
|
|
|
|
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();
|
|
});
|
|
}
|
|
}
|