[IMP] Allow app to be mounted in shadow DOM

This commit allow owl Apps to be mounted into a shadow DOM.
This commit is contained in:
tsm-odoo
2023-04-06 10:41:01 +02:00
committed by Géry Debongnie
parent 606421776b
commit c0c40c0387
5 changed files with 94 additions and 7 deletions
+5 -2
View File
@@ -84,7 +84,10 @@ export class App<
this.props = config.props || ({} as P);
}
mount(target: HTMLElement, options?: MountOptions): Promise<Component<P, E> & InstanceType<T>> {
mount(
target: HTMLElement | ShadowRoot,
options?: MountOptions
): Promise<Component<P, E> & InstanceType<T>> {
App.validateTarget(target);
if (this.dev) {
validateProps(this.Root, this.props, { __owl__: { app: this } });
@@ -99,7 +102,7 @@ export class App<
return new ComponentNode(Component, props, this, null, null);
}
mountNode(node: ComponentNode, target: HTMLElement, options?: MountOptions) {
mountNode(node: ComponentNode, target: HTMLElement | ShadowRoot, options?: MountOptions) {
const promise: any = new Promise((resolve, reject) => {
let isResolved = false;
// manually set a onMounted callback.
+2 -1
View File
@@ -1,3 +1,4 @@
import { inOwnerDocument } from "../utils";
import { config } from "./config";
type EventHandlerSetter = (this: HTMLElement, data: any) => void;
@@ -28,7 +29,7 @@ function createElementHandler(evName: string, capture: boolean = false): EventHa
function listener(ev: Event) {
const currentTarget = ev.currentTarget as HTMLElement;
if (!currentTarget || !currentTarget.ownerDocument.contains(currentTarget)) return;
if (!currentTarget || !inOwnerDocument(currentTarget)) return;
const data = (currentTarget as any)[eventKey];
if (!data) return;
config.mainEventHandler(data, ev, currentTarget);
+2 -1
View File
@@ -1,6 +1,7 @@
import type { Env } from "./app";
import { getCurrent } from "./component_node";
import { onMounted, onPatched, onWillUnmount } from "./lifecycle_hooks";
import { inOwnerDocument } from "./utils";
// -----------------------------------------------------------------------------
// useRef
@@ -16,7 +17,7 @@ export function useRef<T extends HTMLElement = HTMLElement>(name: string): { el:
return {
get el(): T | null {
const el = refs[name];
return el?.ownerDocument.contains(el) ? el : null;
return inOwnerDocument(el) ? el : null;
},
};
}
+18 -3
View File
@@ -27,13 +27,28 @@ export function batched(callback: Callback): Callback {
};
}
export function validateTarget(target: HTMLElement) {
/**
* Determine whether the given element is contained in its ownerDocument:
* either directly or with a shadow root in between.
*/
export function inOwnerDocument(el?: HTMLElement) {
if (!el) {
return false;
}
if (el.ownerDocument.contains(el)) {
return true;
}
const rootNode = el.getRootNode();
return rootNode instanceof ShadowRoot && el.ownerDocument.contains(rootNode.host);
}
export function validateTarget(target: HTMLElement | ShadowRoot) {
// Get the document and HTMLElement corresponding to the target to allow mounting in iframes
const document = target && target.ownerDocument;
if (document) {
const HTMLElement = document.defaultView!.HTMLElement;
if (target instanceof HTMLElement) {
if (!document.body.contains(target)) {
if (target instanceof HTMLElement || target instanceof ShadowRoot) {
if (!document.body.contains(target instanceof HTMLElement ? target : target.host)) {
throw new OwlError("Cannot mount a component on a detached dom node");
}
return;