mirror of
https://github.com/odoo/owl.git
synced 2025-10-06 19:59:41 +07:00
[IMP] portal: portal as a Directive
Before this commit, portal was a Component, now is a directive. This commit also clean some unused code, and fix an issue on the clean optimization when a portal is found in a condition or a loop.
This commit is contained in:
committed by
Aaron Bohy
parent
c221721d7f
commit
90167c5436
@@ -2,6 +2,7 @@ import { BDom, multi, text, toggler } from "../blockdom";
|
|||||||
import { validateProps } from "../component/props_validation";
|
import { validateProps } from "../component/props_validation";
|
||||||
import { Markup } from "../utils";
|
import { Markup } from "../utils";
|
||||||
import { html } from "../blockdom/index";
|
import { html } from "../blockdom/index";
|
||||||
|
import { VPortal } from "../portal";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This file contains utility functions that will be injected in each template,
|
* This file contains utility functions that will be injected in each template,
|
||||||
@@ -12,6 +13,16 @@ function withDefault(value: any, defaultValue: any): any {
|
|||||||
return value === undefined || value === null || value === false ? defaultValue : value;
|
return value === undefined || value === null || value === false ? defaultValue : value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function callPortal(
|
||||||
|
ctx: any,
|
||||||
|
parent: any,
|
||||||
|
key: string,
|
||||||
|
target: string,
|
||||||
|
content: (ctx: any, node: any, key: string) => BDom
|
||||||
|
): BDom {
|
||||||
|
return new VPortal(target, content(ctx, parent, key)) as any;
|
||||||
|
}
|
||||||
|
|
||||||
function callSlot(
|
function callSlot(
|
||||||
ctx: any,
|
ctx: any,
|
||||||
parent: any,
|
parent: any,
|
||||||
@@ -186,6 +197,7 @@ export const UTILS = {
|
|||||||
zero: Symbol("zero"),
|
zero: Symbol("zero"),
|
||||||
isBoundary,
|
isBoundary,
|
||||||
callSlot,
|
callSlot,
|
||||||
|
callPortal,
|
||||||
capture,
|
capture,
|
||||||
withKey,
|
withKey,
|
||||||
prepareList,
|
prepareList,
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ const cache: { [key: string]: BlockType } = {};
|
|||||||
* @param str
|
* @param str
|
||||||
* @returns a new block type, that can build concrete blocks
|
* @returns a new block type, that can build concrete blocks
|
||||||
*/
|
*/
|
||||||
export function createBlock(str: string): BlockType {
|
export function createBlock(str: string, deepRemove: boolean = false): BlockType {
|
||||||
if (str in cache) {
|
if (str in cache) {
|
||||||
return cache[str];
|
return cache[str];
|
||||||
}
|
}
|
||||||
@@ -67,7 +67,7 @@ export function createBlock(str: string): BlockType {
|
|||||||
|
|
||||||
// step 3: build the final block class
|
// step 3: build the final block class
|
||||||
const template = tree.el as HTMLElement;
|
const template = tree.el as HTMLElement;
|
||||||
const Block = buildBlock(template, context);
|
const Block = buildBlock(template, context, deepRemove);
|
||||||
cache[str] = Block;
|
cache[str] = Block;
|
||||||
return Block;
|
return Block;
|
||||||
}
|
}
|
||||||
@@ -422,7 +422,7 @@ function updateCtx(ctx: BlockCtx, tree: IntermediateTree) {
|
|||||||
// building the concrete block class
|
// building the concrete block class
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
|
|
||||||
function buildBlock(template: HTMLElement, ctx: BlockCtx): BlockType {
|
function buildBlock(template: HTMLElement, ctx: BlockCtx, deepRemove: boolean): BlockType {
|
||||||
let B = createBlockClass(template, ctx);
|
let B = createBlockClass(template, ctx);
|
||||||
|
|
||||||
if (ctx.cbRefs.length) {
|
if (ctx.cbRefs.length) {
|
||||||
@@ -447,6 +447,14 @@ function buildBlock(template: HTMLElement, ctx: BlockCtx): BlockType {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
B.prototype.beforeRemove = VMulti.prototype.beforeRemove;
|
B.prototype.beforeRemove = VMulti.prototype.beforeRemove;
|
||||||
|
if (deepRemove) {
|
||||||
|
const blockRemove = B.prototype.remove;
|
||||||
|
const vMultiRemove = VMulti.prototype.remove;
|
||||||
|
B.prototype.remove = function () {
|
||||||
|
blockRemove.call(this);
|
||||||
|
vMultiRemove.call(this);
|
||||||
|
};
|
||||||
|
}
|
||||||
return (data?: any[], children: (VNode | undefined)[] = []) => new B(data, children);
|
return (data?: any[], children: (VNode | undefined)[] = []) => new B(data, children);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -17,9 +17,11 @@ class VList {
|
|||||||
anchor: Node | undefined;
|
anchor: Node | undefined;
|
||||||
parentEl?: HTMLElement | undefined;
|
parentEl?: HTMLElement | undefined;
|
||||||
isOnlyChild?: boolean | undefined;
|
isOnlyChild?: boolean | undefined;
|
||||||
|
deepRemove: boolean;
|
||||||
|
|
||||||
constructor(children: VNode[]) {
|
constructor(children: VNode[], deepRemove: boolean) {
|
||||||
this.children = children;
|
this.children = children;
|
||||||
|
this.deepRemove = deepRemove;
|
||||||
}
|
}
|
||||||
|
|
||||||
mount(parent: HTMLElement, afterNode: Node | null) {
|
mount(parent: HTMLElement, afterNode: Node | null) {
|
||||||
@@ -75,7 +77,7 @@ class VList {
|
|||||||
const parent = this.parentEl!;
|
const parent = this.parentEl!;
|
||||||
|
|
||||||
// fast path: no new child => only remove
|
// fast path: no new child => only remove
|
||||||
if (ch2.length === 0 && isOnlyChild) {
|
if (ch2.length === 0 && isOnlyChild && !this.deepRemove) {
|
||||||
if (withBeforeRemove) {
|
if (withBeforeRemove) {
|
||||||
for (let i = 0, l = ch1.length; i < l; i++) {
|
for (let i = 0, l = ch1.length; i < l; i++) {
|
||||||
beforeRemove.call(ch1[i]);
|
beforeRemove.call(ch1[i]);
|
||||||
@@ -98,7 +100,6 @@ class VList {
|
|||||||
let endVn2 = ch2[endIdx2];
|
let endVn2 = ch2[endIdx2];
|
||||||
|
|
||||||
let mapping: any = undefined;
|
let mapping: any = undefined;
|
||||||
// let noFullRemove = this.hasNoComponent;
|
|
||||||
|
|
||||||
while (startIdx1 <= endIdx1 && startIdx2 <= endIdx2) {
|
while (startIdx1 <= endIdx1 && startIdx2 <= endIdx2) {
|
||||||
// -------------------------------------------------------------------
|
// -------------------------------------------------------------------
|
||||||
@@ -202,7 +203,7 @@ class VList {
|
|||||||
|
|
||||||
remove() {
|
remove() {
|
||||||
const { parentEl, anchor } = this;
|
const { parentEl, anchor } = this;
|
||||||
if (this.isOnlyChild) {
|
if (this.isOnlyChild && !this.deepRemove) {
|
||||||
nodeSetTextContent.call(parentEl, "");
|
nodeSetTextContent.call(parentEl, "");
|
||||||
} else {
|
} else {
|
||||||
const children = this.children;
|
const children = this.children;
|
||||||
@@ -227,8 +228,8 @@ class VList {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function list(children: VNode[]): VNode<VList> {
|
export function list(children: VNode[], deepRemove = false): VNode<VList> {
|
||||||
return new VList(children);
|
return new VList(children, deepRemove);
|
||||||
}
|
}
|
||||||
|
|
||||||
function createMapping(ch1: any[], startIdx1: number, endIdx2: number): { [key: string]: any } {
|
function createMapping(ch1: any[], startIdx1: number, endIdx2: number): { [key: string]: any } {
|
||||||
|
|||||||
@@ -15,9 +15,11 @@ export class VMulti {
|
|||||||
anchors?: Node[] | undefined;
|
anchors?: Node[] | undefined;
|
||||||
parentEl?: HTMLElement | undefined;
|
parentEl?: HTMLElement | undefined;
|
||||||
isOnlyChild?: boolean | undefined;
|
isOnlyChild?: boolean | undefined;
|
||||||
|
deepRemove: boolean;
|
||||||
|
|
||||||
constructor(children: (VNode | undefined)[]) {
|
constructor(children: (VNode | undefined)[], deepRemove: boolean) {
|
||||||
this.children = children;
|
this.children = children;
|
||||||
|
this.deepRemove = deepRemove;
|
||||||
}
|
}
|
||||||
|
|
||||||
mount(parent: HTMLElement, afterNode: Node | null) {
|
mount(parent: HTMLElement, afterNode: Node | null) {
|
||||||
@@ -103,7 +105,7 @@ export class VMulti {
|
|||||||
|
|
||||||
remove() {
|
remove() {
|
||||||
const parentEl = this.parentEl;
|
const parentEl = this.parentEl;
|
||||||
if (this.isOnlyChild) {
|
if (this.isOnlyChild && !this.deepRemove) {
|
||||||
nodeSetTextContent.call(parentEl, "");
|
nodeSetTextContent.call(parentEl, "");
|
||||||
} else {
|
} else {
|
||||||
const children = this.children;
|
const children = this.children;
|
||||||
@@ -129,6 +131,6 @@ export class VMulti {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function multi(children: (VNode | undefined)[]): VNode<VMulti> {
|
export function multi(children: (VNode | undefined)[], deepRemove = false): VNode<VMulti> {
|
||||||
return new VMulti(children);
|
return new VMulti(children, deepRemove);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ import {
|
|||||||
ASTTSet,
|
ASTTSet,
|
||||||
ASTTranslation,
|
ASTTranslation,
|
||||||
ASTType,
|
ASTType,
|
||||||
|
ASTTPortal,
|
||||||
} from "./parser";
|
} from "./parser";
|
||||||
|
|
||||||
type BlockType = "block" | "text" | "multi" | "list" | "html" | "comment";
|
type BlockType = "block" | "text" | "multi" | "list" | "html" | "comment";
|
||||||
@@ -64,13 +65,15 @@ class BlockDescription {
|
|||||||
type: BlockType;
|
type: BlockType;
|
||||||
parentVar: string = "";
|
parentVar: string = "";
|
||||||
id: number;
|
id: number;
|
||||||
|
deepRemove: boolean;
|
||||||
|
|
||||||
constructor(target: CodeTarget, type: BlockType) {
|
constructor(target: CodeTarget, type: BlockType, deepRemove: boolean = false) {
|
||||||
this.id = BlockDescription.nextBlockId++;
|
this.id = BlockDescription.nextBlockId++;
|
||||||
this.varName = "b" + this.id;
|
this.varName = "b" + this.id;
|
||||||
this.blockName = "block" + this.id;
|
this.blockName = "block" + this.id;
|
||||||
this.target = target;
|
this.target = target;
|
||||||
this.type = type;
|
this.type = type;
|
||||||
|
this.deepRemove = deepRemove;
|
||||||
}
|
}
|
||||||
|
|
||||||
insertData(str: string, prefix: string = "d"): number {
|
insertData(str: string, prefix: string = "d"): number {
|
||||||
@@ -99,7 +102,7 @@ class BlockDescription {
|
|||||||
}
|
}
|
||||||
return `${this.blockName}(${params})`;
|
return `${this.blockName}(${params})`;
|
||||||
} else if (this.type === "list") {
|
} else if (this.type === "list") {
|
||||||
return `list(c_block${this.id})`;
|
return `list(c_block${this.id}${this.deepRemove ? ", true" : ""})`;
|
||||||
}
|
}
|
||||||
return expr;
|
return expr;
|
||||||
}
|
}
|
||||||
@@ -250,6 +253,8 @@ export class CodeGenerator {
|
|||||||
mainCode.push(`const ${id} = getTemplate(${template});`);
|
mainCode.push(`const ${id} = getTemplate(${template});`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const deepRemove = "deepRemove" in this.ast ? this.ast.deepRemove : false;
|
||||||
|
|
||||||
// define all blocks
|
// define all blocks
|
||||||
if (this.blocks.length) {
|
if (this.blocks.length) {
|
||||||
mainCode.push(``);
|
mainCode.push(``);
|
||||||
@@ -259,9 +264,15 @@ export class CodeGenerator {
|
|||||||
if (block.dynamicTagName) {
|
if (block.dynamicTagName) {
|
||||||
xmlString = xmlString.replace(/^<\w+/, `<\${tag || '${block.dom.nodeName}'}`);
|
xmlString = xmlString.replace(/^<\w+/, `<\${tag || '${block.dom.nodeName}'}`);
|
||||||
xmlString = xmlString.replace(/\w+>$/, `\${tag || '${block.dom.nodeName}'}>`);
|
xmlString = xmlString.replace(/\w+>$/, `\${tag || '${block.dom.nodeName}'}>`);
|
||||||
mainCode.push(`let ${block.blockName} = tag => createBlock(\`${xmlString}\`);`);
|
mainCode.push(
|
||||||
|
`let ${block.blockName} = tag => createBlock(\`${xmlString}\`${
|
||||||
|
deepRemove ? ", true" : ""
|
||||||
|
});`
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
mainCode.push(`let ${block.blockName} = createBlock(\`${xmlString}\`);`);
|
mainCode.push(
|
||||||
|
`let ${block.blockName} = createBlock(\`${xmlString}\`${deepRemove ? ", true" : ""});`
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -317,10 +328,11 @@ export class CodeGenerator {
|
|||||||
createBlock(
|
createBlock(
|
||||||
parentBlock: BlockDescription | null,
|
parentBlock: BlockDescription | null,
|
||||||
type: BlockType,
|
type: BlockType,
|
||||||
ctx: Context
|
ctx: Context,
|
||||||
|
deepRemove: boolean = false
|
||||||
): BlockDescription {
|
): BlockDescription {
|
||||||
const hasRoot = this.target.hasRoot;
|
const hasRoot = this.target.hasRoot;
|
||||||
const block = new BlockDescription(this.target, type);
|
const block = new BlockDescription(this.target, type, deepRemove);
|
||||||
if (!hasRoot && !ctx.preventRoot) {
|
if (!hasRoot && !ctx.preventRoot) {
|
||||||
this.target.hasRoot = true;
|
this.target.hasRoot = true;
|
||||||
block.isRoot = true;
|
block.isRoot = true;
|
||||||
@@ -445,6 +457,8 @@ export class CodeGenerator {
|
|||||||
case ASTType.TTranslation:
|
case ASTType.TTranslation:
|
||||||
this.compileTTranslation(ast, ctx);
|
this.compileTTranslation(ast, ctx);
|
||||||
break;
|
break;
|
||||||
|
case ASTType.TPortal:
|
||||||
|
this.compileTPortal(ast, ctx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -704,7 +718,7 @@ export class CodeGenerator {
|
|||||||
if (ast.body) {
|
if (ast.body) {
|
||||||
const nextId = BlockDescription.nextBlockId;
|
const nextId = BlockDescription.nextBlockId;
|
||||||
const subCtx: Context = createContext(ctx);
|
const subCtx: Context = createContext(ctx);
|
||||||
this.compileAST({ type: ASTType.Multi, content: ast.body }, subCtx);
|
this.compileAST({ type: ASTType.Multi, content: ast.body, deepRemove: false }, subCtx);
|
||||||
this.helpers.add("withDefault");
|
this.helpers.add("withDefault");
|
||||||
expr = `withDefault(${expr}, b${nextId})`;
|
expr = `withDefault(${expr}, b${nextId})`;
|
||||||
}
|
}
|
||||||
@@ -765,7 +779,7 @@ export class CodeGenerator {
|
|||||||
|
|
||||||
// note: this part is duplicated from end of compilemulti:
|
// note: this part is duplicated from end of compilemulti:
|
||||||
const args = block!.children.map((c) => c.varName).join(", ");
|
const args = block!.children.map((c) => c.varName).join(", ");
|
||||||
this.insertBlock(`multi([${args}])`, block!, ctx)!;
|
this.insertBlock(`multi([${args}]${ast.deepRemove ? ", true" : ""})`, block!, ctx)!;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -774,7 +788,7 @@ export class CodeGenerator {
|
|||||||
if (block) {
|
if (block) {
|
||||||
this.insertAnchor(block);
|
this.insertAnchor(block);
|
||||||
}
|
}
|
||||||
block = this.createBlock(block, "list", ctx);
|
block = this.createBlock(block, "list", ctx, ast.deepRemove);
|
||||||
this.target.loopLevel++;
|
this.target.loopLevel++;
|
||||||
const loopVar = `i${this.target.loopLevel}`;
|
const loopVar = `i${this.target.loopLevel}`;
|
||||||
this.addLine(`ctx = Object.create(ctx);`);
|
this.addLine(`ctx = Object.create(ctx);`);
|
||||||
@@ -909,7 +923,7 @@ export class CodeGenerator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const args = block!.children.map((c) => c.varName).join(", ");
|
const args = block!.children.map((c) => c.varName).join(", ");
|
||||||
this.insertBlock(`multi([${args}])`, block!, ctx)!;
|
this.insertBlock(`multi([${args}]${ast.deepRemove ? ", true" : ""})`, block!, ctx)!;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -921,7 +935,7 @@ export class CodeGenerator {
|
|||||||
this.helpers.add("isBoundary");
|
this.helpers.add("isBoundary");
|
||||||
const nextId = BlockDescription.nextBlockId;
|
const nextId = BlockDescription.nextBlockId;
|
||||||
const subCtx: Context = createContext(ctx, { preventRoot: true });
|
const subCtx: Context = createContext(ctx, { preventRoot: true });
|
||||||
this.compileAST({ type: ASTType.Multi, content: ast.body }, subCtx);
|
this.compileAST({ type: ASTType.Multi, content: ast.body, deepRemove: false }, subCtx);
|
||||||
if (nextId !== BlockDescription.nextBlockId) {
|
if (nextId !== BlockDescription.nextBlockId) {
|
||||||
this.helpers.add("zero");
|
this.helpers.add("zero");
|
||||||
this.addLine(`ctx[zero] = b${nextId};`);
|
this.addLine(`ctx[zero] = b${nextId};`);
|
||||||
@@ -976,7 +990,7 @@ export class CodeGenerator {
|
|||||||
const expr = ast.value ? compileExpr(ast.value || "") : "null";
|
const expr = ast.value ? compileExpr(ast.value || "") : "null";
|
||||||
if (ast.body) {
|
if (ast.body) {
|
||||||
this.helpers.add("LazyValue");
|
this.helpers.add("LazyValue");
|
||||||
const bodyAst: AST = { type: ASTType.Multi, content: ast.body };
|
const bodyAst: AST = { type: ASTType.Multi, content: ast.body, deepRemove: false };
|
||||||
const name = this.compileInNewTarget("value", bodyAst, ctx);
|
const name = this.compileInNewTarget("value", bodyAst, ctx);
|
||||||
let value = `new LazyValue(${name}, ctx, node)`;
|
let value = `new LazyValue(${name}, ctx, node)`;
|
||||||
value = ast.value ? (value ? `withDefault(${expr}, ${value})` : expr) : value;
|
value = ast.value ? (value ? `withDefault(${expr}, ${value})` : expr) : value;
|
||||||
@@ -1162,4 +1176,15 @@ export class CodeGenerator {
|
|||||||
this.compileAST(ast.content, Object.assign({}, ctx, { translate: false }));
|
this.compileAST(ast.content, Object.assign({}, ctx, { translate: false }));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
compileTPortal(ast: ASTTPortal, ctx: Context) {
|
||||||
|
this.helpers.add("callPortal");
|
||||||
|
let { block } = ctx;
|
||||||
|
const name = this.compileInNewTarget("portalContent", ast.content, ctx);
|
||||||
|
const blockString = `callPortal(ctx, node, key, ${ast.target}, ${name})`;
|
||||||
|
if (block) {
|
||||||
|
this.insertAnchor(block);
|
||||||
|
}
|
||||||
|
block = this.createBlock(block, "multi", ctx);
|
||||||
|
this.insertBlock(blockString, block, { ...ctx, forceNewBlock: false });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+77
-43
@@ -20,6 +20,7 @@ export const enum ASTType {
|
|||||||
TSlot,
|
TSlot,
|
||||||
TCallBlock,
|
TCallBlock,
|
||||||
TTranslation,
|
TTranslation,
|
||||||
|
TPortal,
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ASTText {
|
export interface ASTText {
|
||||||
@@ -55,6 +56,7 @@ export interface ASTDomNode {
|
|||||||
export interface ASTMulti {
|
export interface ASTMulti {
|
||||||
type: ASTType.Multi;
|
type: ASTType.Multi;
|
||||||
content: AST[];
|
content: AST[];
|
||||||
|
deepRemove: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ASTTEsc {
|
export interface ASTTEsc {
|
||||||
@@ -75,6 +77,7 @@ export interface ASTTif {
|
|||||||
content: AST;
|
content: AST;
|
||||||
tElif: { condition: string; content: AST }[] | null;
|
tElif: { condition: string; content: AST }[] | null;
|
||||||
tElse: AST | null;
|
tElse: AST | null;
|
||||||
|
deepRemove: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ASTTSet {
|
export interface ASTTSet {
|
||||||
@@ -92,8 +95,7 @@ export interface ASTTForEach {
|
|||||||
key: string | null;
|
key: string | null;
|
||||||
body: AST;
|
body: AST;
|
||||||
memo: string;
|
memo: string;
|
||||||
isOnlyChild: boolean;
|
deepRemove: boolean;
|
||||||
hasNoComponent: boolean;
|
|
||||||
hasNoFirst: boolean;
|
hasNoFirst: boolean;
|
||||||
hasNoLast: boolean;
|
hasNoLast: boolean;
|
||||||
hasNoIndex: boolean;
|
hasNoIndex: boolean;
|
||||||
@@ -149,6 +151,12 @@ export interface ASTTranslation {
|
|||||||
content: AST | null;
|
content: AST | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ASTTPortal {
|
||||||
|
type: ASTType.TPortal;
|
||||||
|
target: string;
|
||||||
|
content: AST;
|
||||||
|
}
|
||||||
|
|
||||||
export type AST =
|
export type AST =
|
||||||
| ASTText
|
| ASTText
|
||||||
| ASTComment
|
| ASTComment
|
||||||
@@ -166,7 +174,8 @@ export type AST =
|
|||||||
| ASTTCallBlock
|
| ASTTCallBlock
|
||||||
| ASTLog
|
| ASTLog
|
||||||
| ASTDebug
|
| ASTDebug
|
||||||
| ASTTranslation;
|
| ASTTranslation
|
||||||
|
| ASTTPortal;
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
// Parser
|
// Parser
|
||||||
@@ -195,6 +204,7 @@ function parseNode(node: ChildNode, ctx: ParsingContext): AST | null {
|
|||||||
parseTDebugLog(node, ctx) ||
|
parseTDebugLog(node, ctx) ||
|
||||||
parseTForEach(node, ctx) ||
|
parseTForEach(node, ctx) ||
|
||||||
parseTIf(node, ctx) ||
|
parseTIf(node, ctx) ||
|
||||||
|
parseTPortal(node, ctx) ||
|
||||||
parseTCall(node, ctx) ||
|
parseTCall(node, ctx) ||
|
||||||
parseTCallBlock(node, ctx) ||
|
parseTCallBlock(node, ctx) ||
|
||||||
parseTEscNode(node, ctx) ||
|
parseTEscNode(node, ctx) ||
|
||||||
@@ -351,9 +361,6 @@ function parseDOMNode(node: Element, ctx: ParsingContext): AST | null {
|
|||||||
attrs[attr] = value;
|
attrs[attr] = value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (children.length === 1 && children[0].type === ASTType.TForEach) {
|
|
||||||
children[0].isOnlyChild = true;
|
|
||||||
}
|
|
||||||
return {
|
return {
|
||||||
type: ASTType.DomNode,
|
type: ASTType.DomNode,
|
||||||
tag: tagName,
|
tag: tagName,
|
||||||
@@ -477,9 +484,8 @@ function parseTForEach(node: Element, ctx: ParsingContext): AST | null {
|
|||||||
elem,
|
elem,
|
||||||
body,
|
body,
|
||||||
memo,
|
memo,
|
||||||
|
deepRemove: needDeepRemove(body),
|
||||||
key,
|
key,
|
||||||
isOnlyChild: false,
|
|
||||||
hasNoComponent: hasNoComponent(body),
|
|
||||||
hasNoFirst,
|
hasNoFirst,
|
||||||
hasNoLast,
|
hasNoLast,
|
||||||
hasNoIndex,
|
hasNoIndex,
|
||||||
@@ -488,54 +494,40 @@ function parseTForEach(node: Element, ctx: ParsingContext): AST | null {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @returns true if we are sure the ast does not contain any component
|
* @returns true if we are sure that a deep remove (without optimisation) is needed, for exemple
|
||||||
|
* if there is a portal.
|
||||||
*/
|
*/
|
||||||
function hasNoComponent(ast: AST): boolean {
|
function needDeepRemove(ast: AST): boolean {
|
||||||
switch (ast.type) {
|
switch (ast.type) {
|
||||||
|
case ASTType.Multi:
|
||||||
|
case ASTType.TForEach:
|
||||||
|
case ASTType.TIf:
|
||||||
|
return ast.deepRemove;
|
||||||
|
|
||||||
|
case ASTType.TPortal:
|
||||||
|
return true;
|
||||||
|
|
||||||
case ASTType.TComponent:
|
case ASTType.TComponent:
|
||||||
case ASTType.TOut:
|
case ASTType.TOut:
|
||||||
case ASTType.TCall:
|
case ASTType.TCall:
|
||||||
case ASTType.TCallBlock:
|
case ASTType.TCallBlock:
|
||||||
case ASTType.TSlot:
|
case ASTType.TSlot:
|
||||||
return false;
|
|
||||||
case ASTType.TSet:
|
|
||||||
case ASTType.Text:
|
case ASTType.Text:
|
||||||
case ASTType.Comment:
|
case ASTType.Comment:
|
||||||
case ASTType.TEsc:
|
case ASTType.TEsc:
|
||||||
return true;
|
return false;
|
||||||
|
|
||||||
case ASTType.TKey:
|
case ASTType.TKey:
|
||||||
return hasNoComponent(ast.content);
|
return needDeepRemove(ast.content);
|
||||||
case ASTType.TDebug:
|
case ASTType.TDebug:
|
||||||
case ASTType.TLog:
|
case ASTType.TLog:
|
||||||
case ASTType.TTranslation:
|
case ASTType.TTranslation:
|
||||||
return ast.content ? hasNoComponent(ast.content) : true;
|
return ast.content ? needDeepRemove(ast.content) : false;
|
||||||
case ASTType.TForEach:
|
case ASTType.TSet:
|
||||||
return ast.hasNoComponent;
|
return ast.body ? ast.body.some((ast) => needDeepRemove(ast)) : false;
|
||||||
case ASTType.Multi:
|
|
||||||
case ASTType.DomNode: {
|
case ASTType.DomNode:
|
||||||
for (let elem of ast.content) {
|
return ast.content.some((ast) => needDeepRemove(ast));
|
||||||
if (!hasNoComponent(elem)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
case ASTType.TIf: {
|
|
||||||
if (!hasNoComponent(ast.content)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (ast.tElif) {
|
|
||||||
for (let elem of ast.tElif) {
|
|
||||||
if (!hasNoComponent(elem.content)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (ast.tElse && !hasNoComponent(ast.tElse)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -636,12 +628,21 @@ function parseTIf(node: Element, ctx: ParsingContext): AST | null {
|
|||||||
nextElement.remove();
|
nextElement.remove();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let deepRemove = needDeepRemove(content);
|
||||||
|
if (tElifs) {
|
||||||
|
deepRemove = deepRemove || tElifs.some((ast) => needDeepRemove(ast));
|
||||||
|
}
|
||||||
|
if (tElse) {
|
||||||
|
deepRemove = deepRemove || needDeepRemove(tElse);
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
type: ASTType.TIf,
|
type: ASTType.TIf,
|
||||||
condition,
|
condition,
|
||||||
content,
|
content,
|
||||||
tElif: tElifs.length ? tElifs : null,
|
tElif: tElifs.length ? tElifs : null,
|
||||||
tElse,
|
tElse,
|
||||||
|
deepRemove,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -805,6 +806,35 @@ function parseTTranslation(node: Element, ctx: ParsingContext): AST | null {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
// Portal
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
function parseTPortal(node: Element, ctx: ParsingContext): AST | null {
|
||||||
|
if (!node.hasAttribute("t-portal")) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (node.tagName !== "t") {
|
||||||
|
throw new Error(
|
||||||
|
`Directive 't-portal' can only be used on <t> nodes (used on a <${node.tagName}>)`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const target = node.getAttribute("t-portal")!;
|
||||||
|
node.removeAttribute("t-portal");
|
||||||
|
const content = parseNode(node, ctx);
|
||||||
|
if (!content) {
|
||||||
|
return {
|
||||||
|
type: ASTType.Text,
|
||||||
|
value: "",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
type: ASTType.TPortal,
|
||||||
|
target,
|
||||||
|
content,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
// helpers
|
// helpers
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
@@ -839,7 +869,11 @@ function parseChildNodes(node: Node, ctx: ParsingContext): AST | null {
|
|||||||
case 1:
|
case 1:
|
||||||
return children[0];
|
return children[0];
|
||||||
default:
|
default:
|
||||||
return { type: ASTType.Multi, content: children };
|
return {
|
||||||
|
type: ASTType.Multi,
|
||||||
|
content: children,
|
||||||
|
deepRemove: children.some((ast) => needDeepRemove(ast)),
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -36,7 +36,6 @@ export { App, mount } from "./app/app";
|
|||||||
export { Component } from "./component/component";
|
export { Component } from "./component/component";
|
||||||
export { useComponent } from "./component/component_node";
|
export { useComponent } from "./component/component_node";
|
||||||
export { status } from "./component/status";
|
export { status } from "./component/status";
|
||||||
export { Portal } from "./portal";
|
|
||||||
export { Memo } from "./memo";
|
export { Memo } from "./memo";
|
||||||
export { xml } from "./app/template_set";
|
export { xml } from "./app/template_set";
|
||||||
export { useState, reactive } from "./reactivity";
|
export { useState, reactive } from "./reactivity";
|
||||||
|
|||||||
+1
-19
@@ -1,10 +1,8 @@
|
|||||||
import { xml } from "./app/template_set";
|
|
||||||
import { BDom, text, VNode } from "./blockdom";
|
import { BDom, text, VNode } from "./blockdom";
|
||||||
import { Component } from "./component/component";
|
|
||||||
|
|
||||||
const VText: any = text("").constructor;
|
const VText: any = text("").constructor;
|
||||||
|
|
||||||
class VPortal extends VText implements Partial<VNode<VPortal>> {
|
export class VPortal extends VText implements Partial<VNode<VPortal>> {
|
||||||
// selector: string;
|
// selector: string;
|
||||||
realBDom: BDom | null;
|
realBDom: BDom | null;
|
||||||
target: HTMLElement | null = null;
|
target: HTMLElement | null = null;
|
||||||
@@ -49,19 +47,3 @@ class VPortal extends VText implements Partial<VNode<VPortal>> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Portal extends Component {
|
|
||||||
static template = xml`<t t-slot="default"/>`;
|
|
||||||
static props = {
|
|
||||||
target: {
|
|
||||||
type: String,
|
|
||||||
},
|
|
||||||
slots: true,
|
|
||||||
};
|
|
||||||
|
|
||||||
setup() {
|
|
||||||
const node = this.__owl__;
|
|
||||||
const renderFn = node.renderFn;
|
|
||||||
node.renderFn = () => new VPortal(this.props.target, renderFn());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -146,6 +146,7 @@ describe("qweb parser", () => {
|
|||||||
content: [],
|
content: [],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
deepRemove: false,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -253,6 +254,7 @@ describe("qweb parser", () => {
|
|||||||
content: [],
|
content: [],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
deepRemove: false,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -503,6 +505,7 @@ describe("qweb parser", () => {
|
|||||||
},
|
},
|
||||||
tElif: null,
|
tElif: null,
|
||||||
tElse: null,
|
tElse: null,
|
||||||
|
deepRemove: false,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
@@ -518,6 +521,7 @@ describe("qweb parser", () => {
|
|||||||
},
|
},
|
||||||
tElif: null,
|
tElif: null,
|
||||||
tElse: null,
|
tElse: null,
|
||||||
|
deepRemove: false,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -538,6 +542,7 @@ describe("qweb parser", () => {
|
|||||||
},
|
},
|
||||||
tElif: null,
|
tElif: null,
|
||||||
tElse: null,
|
tElse: null,
|
||||||
|
deepRemove: false,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -554,6 +559,7 @@ describe("qweb parser", () => {
|
|||||||
type: ASTType.Text,
|
type: ASTType.Text,
|
||||||
value: "else",
|
value: "else",
|
||||||
},
|
},
|
||||||
|
deepRemove: false,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -572,6 +578,7 @@ describe("qweb parser", () => {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
tElse: null,
|
tElse: null,
|
||||||
|
deepRemove: false,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -593,6 +600,7 @@ describe("qweb parser", () => {
|
|||||||
type: ASTType.Text,
|
type: ASTType.Text,
|
||||||
value: "else",
|
value: "else",
|
||||||
},
|
},
|
||||||
|
deepRemove: false,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -650,6 +658,7 @@ describe("qweb parser", () => {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
deepRemove: false,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -731,6 +740,7 @@ describe("qweb parser", () => {
|
|||||||
},
|
},
|
||||||
tElif: null,
|
tElif: null,
|
||||||
tElse: null,
|
tElse: null,
|
||||||
|
deepRemove: false,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -753,6 +763,7 @@ describe("qweb parser", () => {
|
|||||||
content: { type: ASTType.Text, value: "1" },
|
content: { type: ASTType.Text, value: "1" },
|
||||||
tElif: null,
|
tElif: null,
|
||||||
tElse: { type: ASTType.TSet, name: "ourvar", value: "0", defaultValue: null, body: null },
|
tElse: { type: ASTType.TSet, name: "ourvar", value: "0", defaultValue: null, body: null },
|
||||||
|
deepRemove: false,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
@@ -776,14 +787,13 @@ describe("qweb parser", () => {
|
|||||||
collection: "list",
|
collection: "list",
|
||||||
elem: "item",
|
elem: "item",
|
||||||
key: "item_index",
|
key: "item_index",
|
||||||
hasNoComponent: true,
|
|
||||||
isOnlyChild: false,
|
|
||||||
body: { type: ASTType.TEsc, expr: "item", defaultValue: "" },
|
body: { type: ASTType.TEsc, expr: "item", defaultValue: "" },
|
||||||
memo: "",
|
memo: "",
|
||||||
hasNoFirst: true,
|
hasNoFirst: true,
|
||||||
hasNoIndex: false,
|
hasNoIndex: false,
|
||||||
hasNoLast: true,
|
hasNoLast: true,
|
||||||
hasNoValue: true,
|
hasNoValue: true,
|
||||||
|
deepRemove: false,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -793,14 +803,13 @@ describe("qweb parser", () => {
|
|||||||
collection: "list",
|
collection: "list",
|
||||||
elem: "item",
|
elem: "item",
|
||||||
key: "item_index",
|
key: "item_index",
|
||||||
hasNoComponent: true,
|
|
||||||
isOnlyChild: false,
|
|
||||||
body: { type: ASTType.TEsc, expr: "item", defaultValue: "" },
|
body: { type: ASTType.TEsc, expr: "item", defaultValue: "" },
|
||||||
memo: "",
|
memo: "",
|
||||||
hasNoFirst: true,
|
hasNoFirst: true,
|
||||||
hasNoIndex: false,
|
hasNoIndex: false,
|
||||||
hasNoLast: true,
|
hasNoLast: true,
|
||||||
hasNoValue: true,
|
hasNoValue: true,
|
||||||
|
deepRemove: false,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -809,8 +818,6 @@ describe("qweb parser", () => {
|
|||||||
type: ASTType.TForEach,
|
type: ASTType.TForEach,
|
||||||
collection: "list",
|
collection: "list",
|
||||||
elem: "item",
|
elem: "item",
|
||||||
hasNoComponent: true,
|
|
||||||
isOnlyChild: false,
|
|
||||||
key: "item_index",
|
key: "item_index",
|
||||||
body: {
|
body: {
|
||||||
type: ASTType.DomNode,
|
type: ASTType.DomNode,
|
||||||
@@ -828,6 +835,7 @@ describe("qweb parser", () => {
|
|||||||
hasNoIndex: false,
|
hasNoIndex: false,
|
||||||
hasNoLast: true,
|
hasNoLast: true,
|
||||||
hasNoValue: true,
|
hasNoValue: true,
|
||||||
|
deepRemove: false,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -836,8 +844,6 @@ describe("qweb parser", () => {
|
|||||||
type: ASTType.TForEach,
|
type: ASTType.TForEach,
|
||||||
collection: "list",
|
collection: "list",
|
||||||
elem: "item",
|
elem: "item",
|
||||||
hasNoComponent: true,
|
|
||||||
isOnlyChild: false,
|
|
||||||
key: "item.id",
|
key: "item.id",
|
||||||
body: { type: ASTType.TEsc, expr: "item", defaultValue: "" },
|
body: { type: ASTType.TEsc, expr: "item", defaultValue: "" },
|
||||||
memo: "",
|
memo: "",
|
||||||
@@ -845,6 +851,7 @@ describe("qweb parser", () => {
|
|||||||
hasNoIndex: true,
|
hasNoIndex: true,
|
||||||
hasNoLast: true,
|
hasNoLast: true,
|
||||||
hasNoValue: true,
|
hasNoValue: true,
|
||||||
|
deepRemove: false,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -856,8 +863,6 @@ describe("qweb parser", () => {
|
|||||||
collection: "list",
|
collection: "list",
|
||||||
elem: "item",
|
elem: "item",
|
||||||
key: "item_index",
|
key: "item_index",
|
||||||
hasNoComponent: true,
|
|
||||||
isOnlyChild: false,
|
|
||||||
body: {
|
body: {
|
||||||
type: ASTType.DomNode,
|
type: ASTType.DomNode,
|
||||||
tag: "span",
|
tag: "span",
|
||||||
@@ -874,6 +879,7 @@ describe("qweb parser", () => {
|
|||||||
hasNoIndex: false,
|
hasNoIndex: false,
|
||||||
hasNoLast: true,
|
hasNoLast: true,
|
||||||
hasNoValue: true,
|
hasNoValue: true,
|
||||||
|
deepRemove: false,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -887,13 +893,12 @@ describe("qweb parser", () => {
|
|||||||
collection: "list",
|
collection: "list",
|
||||||
elem: "item",
|
elem: "item",
|
||||||
key: "item_index",
|
key: "item_index",
|
||||||
hasNoComponent: true,
|
|
||||||
isOnlyChild: false,
|
|
||||||
body: {
|
body: {
|
||||||
type: ASTType.TIf,
|
type: ASTType.TIf,
|
||||||
condition: "condition",
|
condition: "condition",
|
||||||
tElif: null,
|
tElif: null,
|
||||||
tElse: null,
|
tElse: null,
|
||||||
|
deepRemove: false,
|
||||||
content: {
|
content: {
|
||||||
type: ASTType.DomNode,
|
type: ASTType.DomNode,
|
||||||
tag: "span",
|
tag: "span",
|
||||||
@@ -911,6 +916,7 @@ describe("qweb parser", () => {
|
|||||||
hasNoIndex: false,
|
hasNoIndex: false,
|
||||||
hasNoLast: true,
|
hasNoLast: true,
|
||||||
hasNoValue: true,
|
hasNoValue: true,
|
||||||
|
deepRemove: false,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -924,8 +930,6 @@ describe("qweb parser", () => {
|
|||||||
collection: "categories",
|
collection: "categories",
|
||||||
elem: "category",
|
elem: "category",
|
||||||
key: "category_index",
|
key: "category_index",
|
||||||
hasNoComponent: true,
|
|
||||||
isOnlyChild: false,
|
|
||||||
body: {
|
body: {
|
||||||
type: ASTType.DomNode,
|
type: ASTType.DomNode,
|
||||||
tag: "option",
|
tag: "option",
|
||||||
@@ -945,6 +949,7 @@ describe("qweb parser", () => {
|
|||||||
hasNoIndex: false,
|
hasNoIndex: false,
|
||||||
hasNoLast: true,
|
hasNoLast: true,
|
||||||
hasNoValue: true,
|
hasNoValue: true,
|
||||||
|
deepRemove: false,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -966,14 +971,13 @@ describe("qweb parser", () => {
|
|||||||
collection: "list",
|
collection: "list",
|
||||||
elem: "item",
|
elem: "item",
|
||||||
key: "item_index",
|
key: "item_index",
|
||||||
hasNoComponent: true,
|
|
||||||
isOnlyChild: true,
|
|
||||||
body: { type: ASTType.TEsc, expr: "item", defaultValue: "" },
|
body: { type: ASTType.TEsc, expr: "item", defaultValue: "" },
|
||||||
memo: "",
|
memo: "",
|
||||||
hasNoFirst: true,
|
hasNoFirst: true,
|
||||||
hasNoIndex: false,
|
hasNoIndex: false,
|
||||||
hasNoLast: true,
|
hasNoLast: true,
|
||||||
hasNoValue: true,
|
hasNoValue: true,
|
||||||
|
deepRemove: false,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
@@ -995,8 +999,6 @@ describe("qweb parser", () => {
|
|||||||
collection: "list",
|
collection: "list",
|
||||||
elem: "item",
|
elem: "item",
|
||||||
key: "item_index",
|
key: "item_index",
|
||||||
hasNoComponent: true,
|
|
||||||
isOnlyChild: false,
|
|
||||||
body: {
|
body: {
|
||||||
type: ASTType.DomNode,
|
type: ASTType.DomNode,
|
||||||
tag: "span",
|
tag: "span",
|
||||||
@@ -1013,6 +1015,7 @@ describe("qweb parser", () => {
|
|||||||
hasNoIndex: false,
|
hasNoIndex: false,
|
||||||
hasNoLast: true,
|
hasNoLast: true,
|
||||||
hasNoValue: true,
|
hasNoValue: true,
|
||||||
|
deepRemove: false,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1022,8 +1025,6 @@ describe("qweb parser", () => {
|
|||||||
collection: "list",
|
collection: "list",
|
||||||
elem: "item",
|
elem: "item",
|
||||||
key: "item_index",
|
key: "item_index",
|
||||||
hasNoComponent: false,
|
|
||||||
isOnlyChild: false,
|
|
||||||
body: {
|
body: {
|
||||||
type: ASTType.TComponent,
|
type: ASTType.TComponent,
|
||||||
isDynamic: false,
|
isDynamic: false,
|
||||||
@@ -1037,6 +1038,7 @@ describe("qweb parser", () => {
|
|||||||
hasNoIndex: false,
|
hasNoIndex: false,
|
||||||
hasNoLast: true,
|
hasNoLast: true,
|
||||||
hasNoValue: true,
|
hasNoValue: true,
|
||||||
|
deepRemove: false,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1048,8 +1050,6 @@ describe("qweb parser", () => {
|
|||||||
collection: "list",
|
collection: "list",
|
||||||
elem: "item",
|
elem: "item",
|
||||||
key: "item_index",
|
key: "item_index",
|
||||||
hasNoComponent: false,
|
|
||||||
isOnlyChild: false,
|
|
||||||
body: {
|
body: {
|
||||||
type: ASTType.TCall,
|
type: ASTType.TCall,
|
||||||
name: "blap",
|
name: "blap",
|
||||||
@@ -1060,6 +1060,7 @@ describe("qweb parser", () => {
|
|||||||
hasNoIndex: false,
|
hasNoIndex: false,
|
||||||
hasNoLast: false,
|
hasNoLast: false,
|
||||||
hasNoValue: false,
|
hasNoValue: false,
|
||||||
|
deepRemove: false,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1073,14 +1074,13 @@ describe("qweb parser", () => {
|
|||||||
collection: "list",
|
collection: "list",
|
||||||
elem: "item",
|
elem: "item",
|
||||||
key: "item_index",
|
key: "item_index",
|
||||||
hasNoComponent: true,
|
|
||||||
isOnlyChild: false,
|
|
||||||
body: { type: ASTType.TEsc, expr: "item", defaultValue: "" },
|
body: { type: ASTType.TEsc, expr: "item", defaultValue: "" },
|
||||||
memo: "[row.x]",
|
memo: "[row.x]",
|
||||||
hasNoFirst: true,
|
hasNoFirst: true,
|
||||||
hasNoIndex: false,
|
hasNoIndex: false,
|
||||||
hasNoLast: true,
|
hasNoLast: true,
|
||||||
hasNoValue: true,
|
hasNoValue: true,
|
||||||
|
deepRemove: false,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1130,6 +1130,7 @@ describe("qweb parser", () => {
|
|||||||
condition: "condition",
|
condition: "condition",
|
||||||
tElif: null,
|
tElif: null,
|
||||||
tElse: null,
|
tElse: null,
|
||||||
|
deepRemove: false,
|
||||||
content: {
|
content: {
|
||||||
type: ASTType.TCall,
|
type: ASTType.TCall,
|
||||||
name: "blabla",
|
name: "blabla",
|
||||||
@@ -1284,6 +1285,7 @@ describe("qweb parser", () => {
|
|||||||
ns: null,
|
ns: null,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
deepRemove: false,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -1604,7 +1606,7 @@ describe("qweb parser", () => {
|
|||||||
attrs: {},
|
attrs: {},
|
||||||
content: [
|
content: [
|
||||||
{
|
{
|
||||||
type: 0,
|
type: ASTType.Text,
|
||||||
value: "word",
|
value: "word",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@@ -1616,19 +1618,18 @@ describe("qweb parser", () => {
|
|||||||
type: ASTType.DomNode,
|
type: ASTType.DomNode,
|
||||||
ns: null,
|
ns: null,
|
||||||
},
|
},
|
||||||
type: 16,
|
type: ASTType.TTranslation,
|
||||||
},
|
},
|
||||||
collection: "list",
|
collection: "list",
|
||||||
elem: "item",
|
elem: "item",
|
||||||
hasNoComponent: true,
|
|
||||||
hasNoFirst: true,
|
hasNoFirst: true,
|
||||||
hasNoIndex: false,
|
hasNoIndex: false,
|
||||||
hasNoLast: true,
|
hasNoLast: true,
|
||||||
hasNoValue: true,
|
hasNoValue: true,
|
||||||
isOnlyChild: false,
|
|
||||||
key: "item_index",
|
key: "item_index",
|
||||||
memo: "",
|
memo: "",
|
||||||
type: 9,
|
deepRemove: false,
|
||||||
|
type: ASTType.TForEach,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1787,4 +1788,34 @@ describe("qweb parser", () => {
|
|||||||
ns: null,
|
ns: null,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// t-portal
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
test("t-portal", async () => {
|
||||||
|
expect(parse(`<t t-portal="target">Content</t>`)).toEqual({
|
||||||
|
type: ASTType.TPortal,
|
||||||
|
target: "target",
|
||||||
|
content: { type: ASTType.Text, value: "Content" },
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test("t-portal must be in a <t> node", async () => {
|
||||||
|
expect(() => parse(`<div t-portal="target">Content</div>`)).toThrowError();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("t-portal with t-if", async () => {
|
||||||
|
expect(parse(`<t t-portal="target" t-if="condition">Content</t>`)).toEqual({
|
||||||
|
condition: "condition",
|
||||||
|
content: {
|
||||||
|
content: { type: ASTType.Text, value: "Content" },
|
||||||
|
target: "target",
|
||||||
|
type: ASTType.TPortal,
|
||||||
|
},
|
||||||
|
tElif: null,
|
||||||
|
tElse: null,
|
||||||
|
type: ASTType.TIf,
|
||||||
|
deepRemove: true,
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,5 +1,59 @@
|
|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`Portal Add and remove portals 1`] = `
|
||||||
|
"function anonymous(bdom, helpers
|
||||||
|
) {
|
||||||
|
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
||||||
|
let { prepareList, callPortal, withKey } = helpers;
|
||||||
|
|
||||||
|
function portalContent1(ctx, node, key = \\"\\") {
|
||||||
|
let b3 = text(\` Portal\`);
|
||||||
|
let b4 = text(ctx['portalId']);
|
||||||
|
return multi([b3, b4]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return function template(ctx, node, key = \\"\\") {
|
||||||
|
ctx = Object.create(ctx);
|
||||||
|
const [k_block1, v_block1, l_block1, c_block1] = prepareList(ctx['portalIds']);
|
||||||
|
for (let i1 = 0; i1 < l_block1; i1++) {
|
||||||
|
ctx[\`portalId\`] = v_block1[i1];
|
||||||
|
let key1 = ctx['portalId'];
|
||||||
|
c_block1[i1] = withKey(callPortal(ctx, node, key, '#outside', portalContent1), key1);
|
||||||
|
}
|
||||||
|
return list(c_block1, true);
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`Portal Add and remove portals with t-foreach 1`] = `
|
||||||
|
"function anonymous(bdom, helpers
|
||||||
|
) {
|
||||||
|
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
||||||
|
let { prepareList, callPortal, withKey } = helpers;
|
||||||
|
|
||||||
|
let block2 = createBlock(\`<div><block-text-0/><block-child-0/></div>\`, true);
|
||||||
|
|
||||||
|
function portalContent1(ctx, node, key = \\"\\") {
|
||||||
|
let b4 = text(\` Portal\`);
|
||||||
|
let b5 = text(ctx['portalId']);
|
||||||
|
return multi([b4, b5]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return function template(ctx, node, key = \\"\\") {
|
||||||
|
ctx = Object.create(ctx);
|
||||||
|
const [k_block1, v_block1, l_block1, c_block1] = prepareList(ctx['portalIds']);
|
||||||
|
for (let i1 = 0; i1 < l_block1; i1++) {
|
||||||
|
ctx[\`portalId\`] = v_block1[i1];
|
||||||
|
let key1 = ctx['portalId'];
|
||||||
|
let txt1 = ctx['portalId'];
|
||||||
|
let b6 = callPortal(ctx, node, key, '#outside', portalContent1);
|
||||||
|
c_block1[i1] = withKey(block2([txt1], [b6]), key1);
|
||||||
|
}
|
||||||
|
return list(c_block1, true);
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
exports[`Portal Portal composed with t-slot 1`] = `
|
exports[`Portal Portal composed with t-slot 1`] = `
|
||||||
"function anonymous(bdom, helpers
|
"function anonymous(bdom, helpers
|
||||||
) {
|
) {
|
||||||
@@ -22,14 +76,14 @@ exports[`Portal Portal composed with t-slot 2`] = `
|
|||||||
"function anonymous(bdom, helpers
|
"function anonymous(bdom, helpers
|
||||||
) {
|
) {
|
||||||
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
||||||
let { callSlot } = helpers;
|
let { callPortal, callSlot } = helpers;
|
||||||
|
|
||||||
function slot1(ctx, node, key = \\"\\") {
|
function portalContent1(ctx, node, key = \\"\\") {
|
||||||
return callSlot(ctx, node, key, 'default', false, {});
|
return callSlot(ctx, node, key, 'default', false, {});
|
||||||
}
|
}
|
||||||
|
|
||||||
return function template(ctx, node, key = \\"\\") {
|
return function template(ctx, node, key = \\"\\") {
|
||||||
return component(\`Portal\`, {target: '#outside',slots: {'default': {__render: slot1, __ctx: ctx}}}, key + \`__1\`, node, ctx);
|
return callPortal(ctx, node, key, '#outside', portalContent1);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
@@ -52,16 +106,17 @@ exports[`Portal basic use of portal 1`] = `
|
|||||||
"function anonymous(bdom, helpers
|
"function anonymous(bdom, helpers
|
||||||
) {
|
) {
|
||||||
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
||||||
|
let { callPortal } = helpers;
|
||||||
|
|
||||||
let block1 = createBlock(\`<div><span>1</span><block-child-0/></div>\`);
|
let block1 = createBlock(\`<div><span>1</span><block-child-0/></div>\`);
|
||||||
let block2 = createBlock(\`<p>2</p>\`);
|
let block2 = createBlock(\`<p>2</p>\`);
|
||||||
|
|
||||||
function slot1(ctx, node, key = \\"\\") {
|
function portalContent1(ctx, node, key = \\"\\") {
|
||||||
return block2();
|
return block2();
|
||||||
}
|
}
|
||||||
|
|
||||||
return function template(ctx, node, key = \\"\\") {
|
return function template(ctx, node, key = \\"\\") {
|
||||||
let b3 = component(\`Portal\`, {target: '#outside',slots: {'default': {__render: slot1, __ctx: ctx}}}, key + \`__1\`, node, ctx);
|
let b3 = callPortal(ctx, node, key, '#outside', portalContent1);
|
||||||
return block1([], [b3]);
|
return block1([], [b3]);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
@@ -71,18 +126,17 @@ exports[`Portal basic use of portal in dev mode 1`] = `
|
|||||||
"function anonymous(bdom, helpers
|
"function anonymous(bdom, helpers
|
||||||
) {
|
) {
|
||||||
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
||||||
|
let { callPortal } = helpers;
|
||||||
|
|
||||||
let block1 = createBlock(\`<div><span>1</span><block-child-0/></div>\`);
|
let block1 = createBlock(\`<div><span>1</span><block-child-0/></div>\`);
|
||||||
let block2 = createBlock(\`<p>2</p>\`);
|
let block2 = createBlock(\`<p>2</p>\`);
|
||||||
|
|
||||||
function slot1(ctx, node, key = \\"\\") {
|
function portalContent1(ctx, node, key = \\"\\") {
|
||||||
return block2();
|
return block2();
|
||||||
}
|
}
|
||||||
|
|
||||||
return function template(ctx, node, key = \\"\\") {
|
return function template(ctx, node, key = \\"\\") {
|
||||||
const props1 = {target: '#outside',slots: {'default': {__render: slot1, __ctx: ctx}}}
|
let b3 = callPortal(ctx, node, key, '#outside', portalContent1);
|
||||||
helpers.validateProps(\`Portal\`, props1, ctx)
|
|
||||||
let b3 = component(\`Portal\`, props1, key + \`__1\`, node, ctx);
|
|
||||||
return block1([], [b3]);
|
return block1([], [b3]);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
@@ -92,10 +146,11 @@ exports[`Portal conditional use of Portal (with sub Component) 1`] = `
|
|||||||
"function anonymous(bdom, helpers
|
"function anonymous(bdom, helpers
|
||||||
) {
|
) {
|
||||||
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
||||||
|
let { callPortal } = helpers;
|
||||||
|
|
||||||
let block2 = createBlock(\`<span>1</span>\`);
|
let block2 = createBlock(\`<span>1</span>\`, true);
|
||||||
|
|
||||||
function slot1(ctx, node, key = \\"\\") {
|
function portalContent1(ctx, node, key = \\"\\") {
|
||||||
return component(\`Child\`, {val: ctx['state'].val}, key + \`__1\`, node, ctx);
|
return component(\`Child\`, {val: ctx['state'].val}, key + \`__1\`, node, ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -103,9 +158,9 @@ exports[`Portal conditional use of Portal (with sub Component) 1`] = `
|
|||||||
let b2,b4;
|
let b2,b4;
|
||||||
b2 = block2();
|
b2 = block2();
|
||||||
if (ctx['state'].hasPortal) {
|
if (ctx['state'].hasPortal) {
|
||||||
b4 = component(\`Portal\`, {target: '#outside',slots: {'default': {__render: slot1, __ctx: ctx}}}, key + \`__2\`, node, ctx);
|
b4 = callPortal(ctx, node, key, '#outside', portalContent1);
|
||||||
}
|
}
|
||||||
return multi([b2, b4]);
|
return multi([b2, b4], true);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
@@ -128,11 +183,12 @@ exports[`Portal conditional use of Portal 1`] = `
|
|||||||
"function anonymous(bdom, helpers
|
"function anonymous(bdom, helpers
|
||||||
) {
|
) {
|
||||||
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
||||||
|
let { callPortal } = helpers;
|
||||||
|
|
||||||
let block2 = createBlock(\`<span>1</span>\`);
|
let block2 = createBlock(\`<span>1</span>\`, true);
|
||||||
let block3 = createBlock(\`<p>2</p>\`);
|
let block3 = createBlock(\`<p>2</p>\`, true);
|
||||||
|
|
||||||
function slot1(ctx, node, key = \\"\\") {
|
function portalContent1(ctx, node, key = \\"\\") {
|
||||||
return block3();
|
return block3();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -140,9 +196,33 @@ exports[`Portal conditional use of Portal 1`] = `
|
|||||||
let b2,b4;
|
let b2,b4;
|
||||||
b2 = block2();
|
b2 = block2();
|
||||||
if (ctx['state'].hasPortal) {
|
if (ctx['state'].hasPortal) {
|
||||||
b4 = component(\`Portal\`, {target: '#outside',slots: {'default': {__render: slot1, __ctx: ctx}}}, key + \`__1\`, node, ctx);
|
b4 = callPortal(ctx, node, key, '#outside', portalContent1);
|
||||||
}
|
}
|
||||||
return multi([b2, b4]);
|
return multi([b2, b4], true);
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`Portal conditional use of Portal with div 1`] = `
|
||||||
|
"function anonymous(bdom, helpers
|
||||||
|
) {
|
||||||
|
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
||||||
|
let { callPortal } = helpers;
|
||||||
|
|
||||||
|
let block2 = createBlock(\`<div><span>hasPortal</span><block-child-0/></div>\`, true);
|
||||||
|
let block3 = createBlock(\`<p>thePortal</p>\`, true);
|
||||||
|
|
||||||
|
function portalContent1(ctx, node, key = \\"\\") {
|
||||||
|
return block3();
|
||||||
|
}
|
||||||
|
|
||||||
|
return function template(ctx, node, key = \\"\\") {
|
||||||
|
let b2;
|
||||||
|
if (ctx['state'].hasPortal) {
|
||||||
|
let b4 = callPortal(ctx, node, key, '#outside', portalContent1);
|
||||||
|
b2 = block2([], [b4]);
|
||||||
|
}
|
||||||
|
return multi([b2], true);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
@@ -151,17 +231,18 @@ exports[`Portal lifecycle hooks of portal sub component are properly called 1`]
|
|||||||
"function anonymous(bdom, helpers
|
"function anonymous(bdom, helpers
|
||||||
) {
|
) {
|
||||||
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
||||||
|
let { callPortal } = helpers;
|
||||||
|
|
||||||
let block1 = createBlock(\`<div><block-child-0/></div>\`);
|
let block1 = createBlock(\`<div><block-child-0/><block-child-0/></div>\`);
|
||||||
|
|
||||||
function slot1(ctx, node, key = \\"\\") {
|
function portalContent1(ctx, node, key = \\"\\") {
|
||||||
return component(\`Child\`, {val: ctx['state'].val}, key + \`__1\`, node, ctx);
|
return component(\`Child\`, {val: ctx['state'].val}, key + \`__1\`, node, ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
return function template(ctx, node, key = \\"\\") {
|
return function template(ctx, node, key = \\"\\") {
|
||||||
let b3;
|
let b3;
|
||||||
if (ctx['state'].hasChild) {
|
if (ctx['state'].hasChild) {
|
||||||
b3 = component(\`Portal\`, {target: '#outside',slots: {'default': {__render: slot1, __ctx: ctx}}}, key + \`__2\`, node, ctx);
|
b3 = callPortal(ctx, node, key, '#outside', portalContent1);
|
||||||
}
|
}
|
||||||
return block1([], [b3]);
|
return block1([], [b3]);
|
||||||
}
|
}
|
||||||
@@ -186,11 +267,12 @@ exports[`Portal portal could have dynamically no content 1`] = `
|
|||||||
"function anonymous(bdom, helpers
|
"function anonymous(bdom, helpers
|
||||||
) {
|
) {
|
||||||
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
||||||
|
let { callPortal } = helpers;
|
||||||
|
|
||||||
let block1 = createBlock(\`<div><block-child-0/></div>\`);
|
let block1 = createBlock(\`<div><block-child-0/></div>\`);
|
||||||
let block3 = createBlock(\`<span><block-text-0/></span>\`);
|
let block3 = createBlock(\`<span><block-text-0/></span>\`);
|
||||||
|
|
||||||
function slot1(ctx, node, key = \\"\\") {
|
function portalContent1(ctx, node, key = \\"\\") {
|
||||||
let b3;
|
let b3;
|
||||||
if (ctx['state'].val) {
|
if (ctx['state'].val) {
|
||||||
let txt1 = ctx['state'].val;
|
let txt1 = ctx['state'].val;
|
||||||
@@ -200,7 +282,7 @@ exports[`Portal portal could have dynamically no content 1`] = `
|
|||||||
}
|
}
|
||||||
|
|
||||||
return function template(ctx, node, key = \\"\\") {
|
return function template(ctx, node, key = \\"\\") {
|
||||||
let b4 = component(\`Portal\`, {target: '#outside',slots: {'default': {__render: slot1, __ctx: ctx}}}, key + \`__1\`, node, ctx);
|
let b4 = callPortal(ctx, node, key, '#outside', portalContent1);
|
||||||
return block1([], [b4]);
|
return block1([], [b4]);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
@@ -210,15 +292,16 @@ exports[`Portal portal destroys on crash 1`] = `
|
|||||||
"function anonymous(bdom, helpers
|
"function anonymous(bdom, helpers
|
||||||
) {
|
) {
|
||||||
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
||||||
|
let { callPortal } = helpers;
|
||||||
|
|
||||||
let block1 = createBlock(\`<div><block-child-0/></div>\`);
|
let block1 = createBlock(\`<div><block-child-0/></div>\`);
|
||||||
|
|
||||||
function slot1(ctx, node, key = \\"\\") {
|
function portalContent1(ctx, node, key = \\"\\") {
|
||||||
return component(\`Child\`, {error: ctx['state'].error}, key + \`__1\`, node, ctx);
|
return component(\`Child\`, {error: ctx['state'].error}, key + \`__1\`, node, ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
return function template(ctx, node, key = \\"\\") {
|
return function template(ctx, node, key = \\"\\") {
|
||||||
let b3 = component(\`Portal\`, {target: '#outside',slots: {'default': {__render: slot1, __ctx: ctx}}}, key + \`__2\`, node, ctx);
|
let b3 = callPortal(ctx, node, key, '#outside', portalContent1);
|
||||||
return block1([], [b3]);
|
return block1([], [b3]);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
@@ -242,15 +325,16 @@ exports[`Portal portal with child and props 1`] = `
|
|||||||
"function anonymous(bdom, helpers
|
"function anonymous(bdom, helpers
|
||||||
) {
|
) {
|
||||||
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
||||||
|
let { callPortal } = helpers;
|
||||||
|
|
||||||
let block1 = createBlock(\`<div><block-child-0/></div>\`);
|
let block1 = createBlock(\`<div><block-child-0/></div>\`);
|
||||||
|
|
||||||
function slot1(ctx, node, key = \\"\\") {
|
function portalContent1(ctx, node, key = \\"\\") {
|
||||||
return component(\`Child\`, {val: ctx['state'].val}, key + \`__1\`, node, ctx);
|
return component(\`Child\`, {val: ctx['state'].val}, key + \`__1\`, node, ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
return function template(ctx, node, key = \\"\\") {
|
return function template(ctx, node, key = \\"\\") {
|
||||||
let b3 = component(\`Portal\`, {target: '#outside',slots: {'default': {__render: slot1, __ctx: ctx}}}, key + \`__2\`, node, ctx);
|
let b3 = callPortal(ctx, node, key, '#outside', portalContent1);
|
||||||
return block1([], [b3]);
|
return block1([], [b3]);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
@@ -274,12 +358,13 @@ exports[`Portal portal with dynamic body 1`] = `
|
|||||||
"function anonymous(bdom, helpers
|
"function anonymous(bdom, helpers
|
||||||
) {
|
) {
|
||||||
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
||||||
|
let { callPortal } = helpers;
|
||||||
|
|
||||||
let block1 = createBlock(\`<div><block-child-0/></div>\`);
|
let block1 = createBlock(\`<div><block-child-0/></div>\`);
|
||||||
let block3 = createBlock(\`<span><block-text-0/></span>\`);
|
let block3 = createBlock(\`<span><block-text-0/></span>\`);
|
||||||
let block4 = createBlock(\`<div/>\`);
|
let block4 = createBlock(\`<div/>\`);
|
||||||
|
|
||||||
function slot1(ctx, node, key = \\"\\") {
|
function portalContent1(ctx, node, key = \\"\\") {
|
||||||
let b3,b4;
|
let b3,b4;
|
||||||
if (ctx['state'].val) {
|
if (ctx['state'].val) {
|
||||||
let txt1 = ctx['state'].val;
|
let txt1 = ctx['state'].val;
|
||||||
@@ -291,7 +376,7 @@ exports[`Portal portal with dynamic body 1`] = `
|
|||||||
}
|
}
|
||||||
|
|
||||||
return function template(ctx, node, key = \\"\\") {
|
return function template(ctx, node, key = \\"\\") {
|
||||||
let b5 = component(\`Portal\`, {target: '#outside',slots: {'default': {__render: slot1, __ctx: ctx}}}, key + \`__1\`, node, ctx);
|
let b5 = callPortal(ctx, node, key, '#outside', portalContent1);
|
||||||
return block1([], [b5]);
|
return block1([], [b5]);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
@@ -301,19 +386,20 @@ exports[`Portal portal with many children 1`] = `
|
|||||||
"function anonymous(bdom, helpers
|
"function anonymous(bdom, helpers
|
||||||
) {
|
) {
|
||||||
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
||||||
|
let { callPortal } = helpers;
|
||||||
|
|
||||||
let block1 = createBlock(\`<div><block-child-0/></div>\`);
|
let block1 = createBlock(\`<div><block-child-0/></div>\`);
|
||||||
let block3 = createBlock(\`<div>1</div>\`);
|
let block3 = createBlock(\`<div>1</div>\`);
|
||||||
let block4 = createBlock(\`<p>2</p>\`);
|
let block4 = createBlock(\`<p>2</p>\`);
|
||||||
|
|
||||||
function slot1(ctx, node, key = \\"\\") {
|
function portalContent1(ctx, node, key = \\"\\") {
|
||||||
let b3 = block3();
|
let b3 = block3();
|
||||||
let b4 = block4();
|
let b4 = block4();
|
||||||
return multi([b3, b4]);
|
return multi([b3, b4]);
|
||||||
}
|
}
|
||||||
|
|
||||||
return function template(ctx, node, key = \\"\\") {
|
return function template(ctx, node, key = \\"\\") {
|
||||||
let b5 = component(\`Portal\`, {target: '#outside',slots: {'default': {__render: slot1, __ctx: ctx}}}, key + \`__1\`, node, ctx);
|
let b5 = callPortal(ctx, node, key, '#outside', portalContent1);
|
||||||
return block1([], [b5]);
|
return block1([], [b5]);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
@@ -323,10 +409,11 @@ exports[`Portal portal with no content 1`] = `
|
|||||||
"function anonymous(bdom, helpers
|
"function anonymous(bdom, helpers
|
||||||
) {
|
) {
|
||||||
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
||||||
|
let { callPortal } = helpers;
|
||||||
|
|
||||||
let block1 = createBlock(\`<div><block-child-0/></div>\`);
|
let block1 = createBlock(\`<div><block-child-0/></div>\`);
|
||||||
|
|
||||||
function slot1(ctx, node, key = \\"\\") {
|
function portalContent1(ctx, node, key = \\"\\") {
|
||||||
let b3;
|
let b3;
|
||||||
if (false) {
|
if (false) {
|
||||||
b3 = text('ABC');
|
b3 = text('ABC');
|
||||||
@@ -335,7 +422,7 @@ exports[`Portal portal with no content 1`] = `
|
|||||||
}
|
}
|
||||||
|
|
||||||
return function template(ctx, node, key = \\"\\") {
|
return function template(ctx, node, key = \\"\\") {
|
||||||
let b4 = component(\`Portal\`, {target: '#outside',slots: {'default': {__render: slot1, __ctx: ctx}}}, key + \`__1\`, node, ctx);
|
let b4 = callPortal(ctx, node, key, '#outside', portalContent1);
|
||||||
return block1([], [b4]);
|
return block1([], [b4]);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
@@ -345,15 +432,16 @@ exports[`Portal portal with only text as content 1`] = `
|
|||||||
"function anonymous(bdom, helpers
|
"function anonymous(bdom, helpers
|
||||||
) {
|
) {
|
||||||
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
||||||
|
let { callPortal } = helpers;
|
||||||
|
|
||||||
let block1 = createBlock(\`<div><block-child-0/></div>\`);
|
let block1 = createBlock(\`<div><block-child-0/></div>\`);
|
||||||
|
|
||||||
function slot1(ctx, node, key = \\"\\") {
|
function portalContent1(ctx, node, key = \\"\\") {
|
||||||
return text('only text');
|
return text('only text');
|
||||||
}
|
}
|
||||||
|
|
||||||
return function template(ctx, node, key = \\"\\") {
|
return function template(ctx, node, key = \\"\\") {
|
||||||
let b3 = component(\`Portal\`, {target: '#outside',slots: {'default': {__render: slot1, __ctx: ctx}}}, key + \`__1\`, node, ctx);
|
let b3 = callPortal(ctx, node, key, '#outside', portalContent1);
|
||||||
return block1([], [b3]);
|
return block1([], [b3]);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
@@ -363,16 +451,17 @@ exports[`Portal portal with target not in dom 1`] = `
|
|||||||
"function anonymous(bdom, helpers
|
"function anonymous(bdom, helpers
|
||||||
) {
|
) {
|
||||||
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
||||||
|
let { callPortal } = helpers;
|
||||||
|
|
||||||
let block1 = createBlock(\`<div><block-child-0/></div>\`);
|
let block1 = createBlock(\`<div><block-child-0/></div>\`);
|
||||||
let block2 = createBlock(\`<div>2</div>\`);
|
let block2 = createBlock(\`<div>2</div>\`);
|
||||||
|
|
||||||
function slot1(ctx, node, key = \\"\\") {
|
function portalContent1(ctx, node, key = \\"\\") {
|
||||||
return block2();
|
return block2();
|
||||||
}
|
}
|
||||||
|
|
||||||
return function template(ctx, node, key = \\"\\") {
|
return function template(ctx, node, key = \\"\\") {
|
||||||
let b3 = component(\`Portal\`, {target: '#does-not-exist',slots: {'default': {__render: slot1, __ctx: ctx}}}, key + \`__1\`, node, ctx);
|
let b3 = callPortal(ctx, node, key, '#does-not-exist', portalContent1);
|
||||||
return block1([], [b3]);
|
return block1([], [b3]);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
@@ -382,15 +471,16 @@ exports[`Portal portal's parent's env is not polluted 1`] = `
|
|||||||
"function anonymous(bdom, helpers
|
"function anonymous(bdom, helpers
|
||||||
) {
|
) {
|
||||||
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
||||||
|
let { callPortal } = helpers;
|
||||||
|
|
||||||
let block1 = createBlock(\`<div><block-child-0/></div>\`);
|
let block1 = createBlock(\`<div><block-child-0/></div>\`);
|
||||||
|
|
||||||
function slot1(ctx, node, key = \\"\\") {
|
function portalContent1(ctx, node, key = \\"\\") {
|
||||||
return component(\`Child\`, {}, key + \`__1\`, node, ctx);
|
return component(\`Child\`, {}, key + \`__1\`, node, ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
return function template(ctx, node, key = \\"\\") {
|
return function template(ctx, node, key = \\"\\") {
|
||||||
let b3 = component(\`Portal\`, {target: '#outside',slots: {'default': {__render: slot1, __ctx: ctx}}}, key + \`__2\`, node, ctx);
|
let b3 = callPortal(ctx, node, key, '#outside', portalContent1);
|
||||||
return block1([], [b3]);
|
return block1([], [b3]);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
@@ -432,17 +522,18 @@ exports[`Portal simple catchError with portal 2`] = `
|
|||||||
"function anonymous(bdom, helpers
|
"function anonymous(bdom, helpers
|
||||||
) {
|
) {
|
||||||
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
||||||
|
let { callPortal } = helpers;
|
||||||
|
|
||||||
let block1 = createBlock(\`<div><span>1</span><block-child-0/></div>\`);
|
let block1 = createBlock(\`<div><span>1</span><block-child-0/></div>\`);
|
||||||
let block2 = createBlock(\`<p><block-text-0/></p>\`);
|
let block2 = createBlock(\`<p><block-text-0/></p>\`);
|
||||||
|
|
||||||
function slot1(ctx, node, key = \\"\\") {
|
function portalContent1(ctx, node, key = \\"\\") {
|
||||||
let txt1 = ctx['a'].b.c;
|
let txt1 = ctx['a'].b.c;
|
||||||
return block2([txt1]);
|
return block2([txt1]);
|
||||||
}
|
}
|
||||||
|
|
||||||
return function template(ctx, node, key = \\"\\") {
|
return function template(ctx, node, key = \\"\\") {
|
||||||
let b3 = component(\`Portal\`, {target: '#outside',slots: {'default': {__render: slot1, __ctx: ctx}}}, key + \`__1\`, node, ctx);
|
let b3 = callPortal(ctx, node, key, '#outside', portalContent1);
|
||||||
return block1([], [b3]);
|
return block1([], [b3]);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
@@ -452,16 +543,17 @@ exports[`Portal with target in template (after portal) 1`] = `
|
|||||||
"function anonymous(bdom, helpers
|
"function anonymous(bdom, helpers
|
||||||
) {
|
) {
|
||||||
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
||||||
|
let { callPortal } = helpers;
|
||||||
|
|
||||||
let block1 = createBlock(\`<div><span>1</span><block-child-0/><div id=\\"local-target\\"/></div>\`);
|
let block1 = createBlock(\`<div><span>1</span><block-child-0/><div id=\\"local-target\\"/></div>\`);
|
||||||
let block2 = createBlock(\`<p>2</p>\`);
|
let block2 = createBlock(\`<p>2</p>\`);
|
||||||
|
|
||||||
function slot1(ctx, node, key = \\"\\") {
|
function portalContent1(ctx, node, key = \\"\\") {
|
||||||
return block2();
|
return block2();
|
||||||
}
|
}
|
||||||
|
|
||||||
return function template(ctx, node, key = \\"\\") {
|
return function template(ctx, node, key = \\"\\") {
|
||||||
let b3 = component(\`Portal\`, {target: '#local-target',slots: {'default': {__render: slot1, __ctx: ctx}}}, key + \`__1\`, node, ctx);
|
let b3 = callPortal(ctx, node, key, '#local-target', portalContent1);
|
||||||
return block1([], [b3]);
|
return block1([], [b3]);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
@@ -471,58 +563,57 @@ exports[`Portal with target in template (before portal) 1`] = `
|
|||||||
"function anonymous(bdom, helpers
|
"function anonymous(bdom, helpers
|
||||||
) {
|
) {
|
||||||
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
||||||
|
let { callPortal } = helpers;
|
||||||
|
|
||||||
let block1 = createBlock(\`<div><div id=\\"local-target\\"/><span>1</span><block-child-0/></div>\`);
|
let block1 = createBlock(\`<div><div id=\\"local-target\\"/><span>1</span><block-child-0/></div>\`);
|
||||||
let block2 = createBlock(\`<p>2</p>\`);
|
let block2 = createBlock(\`<p>2</p>\`);
|
||||||
|
|
||||||
function slot1(ctx, node, key = \\"\\") {
|
function portalContent1(ctx, node, key = \\"\\") {
|
||||||
return block2();
|
return block2();
|
||||||
}
|
}
|
||||||
|
|
||||||
return function template(ctx, node, key = \\"\\") {
|
return function template(ctx, node, key = \\"\\") {
|
||||||
let b3 = component(\`Portal\`, {target: '#local-target',slots: {'default': {__render: slot1, __ctx: ctx}}}, key + \`__1\`, node, ctx);
|
let b3 = callPortal(ctx, node, key, '#local-target', portalContent1);
|
||||||
return block1([], [b3]);
|
return block1([], [b3]);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`Portal: Props validation target is mandatory 1`] = `
|
exports[`Portal: Props validation target must be a valid selector 1`] = `
|
||||||
"function anonymous(bdom, helpers
|
"function anonymous(bdom, helpers
|
||||||
) {
|
) {
|
||||||
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
||||||
|
let { callPortal } = helpers;
|
||||||
|
|
||||||
let block1 = createBlock(\`<div><block-child-0/></div>\`);
|
let block1 = createBlock(\`<div><block-child-0/></div>\`);
|
||||||
let block2 = createBlock(\`<div>2</div>\`);
|
let block2 = createBlock(\`<div>2</div>\`);
|
||||||
|
|
||||||
function slot1(ctx, node, key = \\"\\") {
|
function portalContent1(ctx, node, key = \\"\\") {
|
||||||
return block2();
|
return block2();
|
||||||
}
|
}
|
||||||
|
|
||||||
return function template(ctx, node, key = \\"\\") {
|
return function template(ctx, node, key = \\"\\") {
|
||||||
const props1 = {slots: {'default': {__render: slot1, __ctx: ctx}}}
|
let b3 = callPortal(ctx, node, key, ' ', portalContent1);
|
||||||
helpers.validateProps(\`Portal\`, props1, ctx)
|
|
||||||
let b3 = component(\`Portal\`, props1, key + \`__1\`, node, ctx);
|
|
||||||
return block1([], [b3]);
|
return block1([], [b3]);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`Portal: Props validation target is not list 1`] = `
|
exports[`Portal: Props validation target must be a valid selector 2 1`] = `
|
||||||
"function anonymous(bdom, helpers
|
"function anonymous(bdom, helpers
|
||||||
) {
|
) {
|
||||||
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
||||||
|
let { callPortal } = helpers;
|
||||||
|
|
||||||
let block1 = createBlock(\`<div><block-child-0/></div>\`);
|
let block1 = createBlock(\`<div><block-child-0/></div>\`);
|
||||||
let block2 = createBlock(\`<div>2</div>\`);
|
let block2 = createBlock(\`<div>2</div>\`);
|
||||||
|
|
||||||
function slot1(ctx, node, key = \\"\\") {
|
function portalContent1(ctx, node, key = \\"\\") {
|
||||||
return block2();
|
return block2();
|
||||||
}
|
}
|
||||||
|
|
||||||
return function template(ctx, node, key = \\"\\") {
|
return function template(ctx, node, key = \\"\\") {
|
||||||
const props1 = {target: ['body'],slots: {'default': {__render: slot1, __ctx: ctx}}}
|
let b3 = callPortal(ctx, node, key, 'aa', portalContent1);
|
||||||
helpers.validateProps(\`Portal\`, props1, ctx)
|
|
||||||
let b3 = component(\`Portal\`, props1, key + \`__1\`, node, ctx);
|
|
||||||
return block1([], [b3]);
|
return block1([], [b3]);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
@@ -532,15 +623,16 @@ exports[`Portal: UI/UX focus is kept across re-renders 1`] = `
|
|||||||
"function anonymous(bdom, helpers
|
"function anonymous(bdom, helpers
|
||||||
) {
|
) {
|
||||||
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
||||||
|
let { callPortal } = helpers;
|
||||||
|
|
||||||
let block1 = createBlock(\`<div><block-child-0/></div>\`);
|
let block1 = createBlock(\`<div><block-child-0/></div>\`);
|
||||||
|
|
||||||
function slot1(ctx, node, key = \\"\\") {
|
function portalContent1(ctx, node, key = \\"\\") {
|
||||||
return component(\`Child\`, {val: ctx['state'].val}, key + \`__1\`, node, ctx);
|
return component(\`Child\`, {val: ctx['state'].val}, key + \`__1\`, node, ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
return function template(ctx, node, key = \\"\\") {
|
return function template(ctx, node, key = \\"\\") {
|
||||||
let b3 = component(\`Portal\`, {target: '#outside',slots: {'default': {__render: slot1, __ctx: ctx}}}, key + \`__2\`, node, ctx);
|
let b3 = callPortal(ctx, node, key, '#outside', portalContent1);
|
||||||
return block1([], [b3]);
|
return block1([], [b3]);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
|
|||||||
+198
-78
@@ -8,9 +8,9 @@ import {
|
|||||||
onWillUnmount,
|
onWillUnmount,
|
||||||
useState,
|
useState,
|
||||||
} from "../../src";
|
} from "../../src";
|
||||||
import { Portal, xml } from "../../src/";
|
import { xml } from "../../src/";
|
||||||
import { elem, makeTestFixture, nextTick, snapshotEverything } from "../helpers";
|
|
||||||
import { DEV_MSG } from "../../src/app/app";
|
import { DEV_MSG } from "../../src/app/app";
|
||||||
|
import { elem, makeTestFixture, nextTick, snapshotEverything } from "../helpers";
|
||||||
|
|
||||||
let fixture: HTMLElement;
|
let fixture: HTMLElement;
|
||||||
let originalconsoleWarn = console.warn;
|
let originalconsoleWarn = console.warn;
|
||||||
@@ -52,13 +52,12 @@ afterEach(() => {
|
|||||||
describe("Portal", () => {
|
describe("Portal", () => {
|
||||||
test("basic use of portal", async () => {
|
test("basic use of portal", async () => {
|
||||||
class Parent extends Component {
|
class Parent extends Component {
|
||||||
static components = { Portal };
|
|
||||||
static template = xml`
|
static template = xml`
|
||||||
<div>
|
<div>
|
||||||
<span>1</span>
|
<span>1</span>
|
||||||
<Portal target="'#outside'">
|
<t t-portal="'#outside'">
|
||||||
<p>2</p>
|
<p>2</p>
|
||||||
</Portal>
|
</t>
|
||||||
</div>`;
|
</div>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -70,13 +69,12 @@ describe("Portal", () => {
|
|||||||
|
|
||||||
test("simple catchError with portal", async () => {
|
test("simple catchError with portal", async () => {
|
||||||
class Boom extends Component {
|
class Boom extends Component {
|
||||||
static components = { Portal };
|
|
||||||
static template = xml`
|
static template = xml`
|
||||||
<div>
|
<div>
|
||||||
<span>1</span>
|
<span>1</span>
|
||||||
<Portal target="'#outside'">
|
<t t-portal="'#outside'">
|
||||||
<p><t t-esc="a.b.c"/></p>
|
<p><t t-esc="a.b.c"/></p>
|
||||||
</Portal>
|
</t>
|
||||||
</div>`;
|
</div>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -107,13 +105,12 @@ describe("Portal", () => {
|
|||||||
|
|
||||||
test("basic use of portal in dev mode", async () => {
|
test("basic use of portal in dev mode", async () => {
|
||||||
class Parent extends Component {
|
class Parent extends Component {
|
||||||
static components = { Portal };
|
|
||||||
static template = xml`
|
static template = xml`
|
||||||
<div>
|
<div>
|
||||||
<span>1</span>
|
<span>1</span>
|
||||||
<Portal target="'#outside'">
|
<t t-portal="'#outside'">
|
||||||
<p>2</p>
|
<p>2</p>
|
||||||
</Portal>
|
</t>
|
||||||
</div>`;
|
</div>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -125,12 +122,11 @@ describe("Portal", () => {
|
|||||||
|
|
||||||
test("conditional use of Portal", async () => {
|
test("conditional use of Portal", async () => {
|
||||||
class Parent extends Component {
|
class Parent extends Component {
|
||||||
static components = { Portal };
|
|
||||||
static template = xml`
|
static template = xml`
|
||||||
<span>1</span>
|
<span>1</span>
|
||||||
<Portal target="'#outside'" t-if="state.hasPortal">
|
<t t-portal="'#outside'" t-if="state.hasPortal">
|
||||||
<p>2</p>
|
<p>2</p>
|
||||||
</Portal>`;
|
</t>`;
|
||||||
|
|
||||||
state = useState({ hasPortal: false });
|
state = useState({ hasPortal: false });
|
||||||
}
|
}
|
||||||
@@ -157,12 +153,12 @@ describe("Portal", () => {
|
|||||||
static template = xml`<p><t t-esc="props.val"/></p>`;
|
static template = xml`<p><t t-esc="props.val"/></p>`;
|
||||||
}
|
}
|
||||||
class Parent extends Component {
|
class Parent extends Component {
|
||||||
static components = { Portal, Child };
|
static components = { Child };
|
||||||
static template = xml`
|
static template = xml`
|
||||||
<span>1</span>
|
<span>1</span>
|
||||||
<Portal t-if="state.hasPortal" target="'#outside'">
|
<t t-portal="'#outside'" t-if="state.hasPortal">
|
||||||
<Child val="state.val"/>
|
<Child val="state.val"/>
|
||||||
</Portal>`;
|
</t>`;
|
||||||
state = useState({ hasPortal: false, val: 1 });
|
state = useState({ hasPortal: false, val: 1 });
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -190,14 +186,13 @@ describe("Portal", () => {
|
|||||||
|
|
||||||
test("with target in template (before portal)", async () => {
|
test("with target in template (before portal)", async () => {
|
||||||
class Parent extends Component {
|
class Parent extends Component {
|
||||||
static components = { Portal };
|
|
||||||
static template = xml`
|
static template = xml`
|
||||||
<div>
|
<div>
|
||||||
<div id="local-target"></div>
|
<div id="local-target"></div>
|
||||||
<span>1</span>
|
<span>1</span>
|
||||||
<Portal target="'#local-target'">
|
<t t-portal="'#local-target'">
|
||||||
<p>2</p>
|
<p>2</p>
|
||||||
</Portal>
|
</t>
|
||||||
</div>`;
|
</div>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -209,13 +204,12 @@ describe("Portal", () => {
|
|||||||
|
|
||||||
test("with target in template (after portal)", async () => {
|
test("with target in template (after portal)", async () => {
|
||||||
class Parent extends Component {
|
class Parent extends Component {
|
||||||
static components = { Portal };
|
|
||||||
static template = xml`
|
static template = xml`
|
||||||
<div>
|
<div>
|
||||||
<span>1</span>
|
<span>1</span>
|
||||||
<Portal target="'#local-target'">
|
<t t-portal="'#local-target'">
|
||||||
<p>2</p>
|
<p>2</p>
|
||||||
</Portal>
|
</t>
|
||||||
<div id="local-target"></div>
|
<div id="local-target"></div>
|
||||||
</div>`;
|
</div>`;
|
||||||
}
|
}
|
||||||
@@ -228,12 +222,11 @@ describe("Portal", () => {
|
|||||||
|
|
||||||
test("portal with target not in dom", async () => {
|
test("portal with target not in dom", async () => {
|
||||||
class Parent extends Component {
|
class Parent extends Component {
|
||||||
static components = { Portal };
|
|
||||||
static template = xml`
|
static template = xml`
|
||||||
<div>
|
<div>
|
||||||
<Portal target="'#does-not-exist'">
|
<t t-portal="'#does-not-exist'">
|
||||||
<div>2</div>
|
<div>2</div>
|
||||||
</Portal>
|
</t>
|
||||||
</div>`;
|
</div>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -269,12 +262,12 @@ describe("Portal", () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
class Parent extends Component {
|
class Parent extends Component {
|
||||||
static components = { Portal, Child };
|
static components = { Child };
|
||||||
static template = xml`
|
static template = xml`
|
||||||
<div>
|
<div>
|
||||||
<Portal target="'#outside'">
|
<t t-portal="'#outside'">
|
||||||
<Child val="state.val"/>
|
<Child val="state.val"/>
|
||||||
</Portal>
|
</t>
|
||||||
</div>`;
|
</div>`;
|
||||||
state = useState({ val: 1 });
|
state = useState({ val: 1 });
|
||||||
}
|
}
|
||||||
@@ -292,12 +285,11 @@ describe("Portal", () => {
|
|||||||
|
|
||||||
test("portal with only text as content", async () => {
|
test("portal with only text as content", async () => {
|
||||||
class Parent extends Component {
|
class Parent extends Component {
|
||||||
static components = { Portal };
|
|
||||||
static template = xml`
|
static template = xml`
|
||||||
<div>
|
<div>
|
||||||
<Portal target="'#outside'">
|
<t t-portal="'#outside'">
|
||||||
<t t-esc="'only text'"/>
|
<t t-esc="'only text'"/>
|
||||||
</Portal>
|
</t>
|
||||||
</div>`;
|
</div>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -308,12 +300,11 @@ describe("Portal", () => {
|
|||||||
|
|
||||||
test("portal with no content", async () => {
|
test("portal with no content", async () => {
|
||||||
class Parent extends Component {
|
class Parent extends Component {
|
||||||
static components = { Portal };
|
|
||||||
static template = xml`
|
static template = xml`
|
||||||
<div>
|
<div>
|
||||||
<Portal target="'#outside'">
|
<t t-portal="'#outside'">
|
||||||
<t t-if="false" t-esc="'ABC'"/>
|
<t t-if="false" t-esc="'ABC'"/>
|
||||||
</Portal>
|
</t>
|
||||||
</div>`;
|
</div>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -324,13 +315,12 @@ describe("Portal", () => {
|
|||||||
|
|
||||||
test("portal with many children", async () => {
|
test("portal with many children", async () => {
|
||||||
class Parent extends Component {
|
class Parent extends Component {
|
||||||
static components = { Portal };
|
|
||||||
static template = xml`
|
static template = xml`
|
||||||
<div>
|
<div>
|
||||||
<Portal target="'#outside'">
|
<t t-portal="'#outside'">
|
||||||
<div>1</div>
|
<div>1</div>
|
||||||
<p>2</p>
|
<p>2</p>
|
||||||
</Portal>
|
</t>
|
||||||
</div>`;
|
</div>`;
|
||||||
}
|
}
|
||||||
addOutsideDiv(fixture);
|
addOutsideDiv(fixture);
|
||||||
@@ -340,13 +330,12 @@ describe("Portal", () => {
|
|||||||
|
|
||||||
test("portal with dynamic body", async () => {
|
test("portal with dynamic body", async () => {
|
||||||
class Parent extends Component {
|
class Parent extends Component {
|
||||||
static components = { Portal };
|
|
||||||
static template = xml`
|
static template = xml`
|
||||||
<div>
|
<div>
|
||||||
<Portal target="'#outside'">
|
<t t-portal="'#outside'">
|
||||||
<span t-if="state.val" t-esc="state.val"/>
|
<span t-if="state.val" t-esc="state.val"/>
|
||||||
<div t-else=""/>
|
<div t-else=""/>
|
||||||
</Portal>
|
</t>
|
||||||
</div>`;
|
</div>`;
|
||||||
state = useState({ val: "ab" });
|
state = useState({ val: "ab" });
|
||||||
}
|
}
|
||||||
@@ -363,12 +352,11 @@ describe("Portal", () => {
|
|||||||
|
|
||||||
test("portal could have dynamically no content", async () => {
|
test("portal could have dynamically no content", async () => {
|
||||||
class Parent extends Component {
|
class Parent extends Component {
|
||||||
static components = { Portal };
|
|
||||||
static template = xml`
|
static template = xml`
|
||||||
<div>
|
<div>
|
||||||
<Portal target="'#outside'">
|
<t t-portal="'#outside'">
|
||||||
<span t-if="state.val" t-esc="state.val"/>
|
<span t-if="state.val" t-esc="state.val"/>
|
||||||
</Portal>
|
</t>
|
||||||
</div>`;
|
</div>`;
|
||||||
state = useState({ val: "ab" });
|
state = useState({ val: "ab" });
|
||||||
}
|
}
|
||||||
@@ -396,12 +384,12 @@ describe("Portal", () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class Parent extends Component {
|
class Parent extends Component {
|
||||||
static components = { Portal, Child };
|
static components = { Child };
|
||||||
static template = xml`
|
static template = xml`
|
||||||
<div>
|
<div>
|
||||||
<Portal t-if="state.hasChild" target="'#outside'">
|
<t t-portal="'#outside'" t-if="state.hasChild">
|
||||||
<Child val="state.val"/>
|
<Child val="state.val"/>
|
||||||
</Portal>
|
</t>
|
||||||
</div>`;
|
</div>`;
|
||||||
state = useState({ hasChild: false, val: 1 });
|
state = useState({ hasChild: false, val: 1 });
|
||||||
setup() {
|
setup() {
|
||||||
@@ -461,12 +449,12 @@ describe("Portal", () => {
|
|||||||
state = {};
|
state = {};
|
||||||
}
|
}
|
||||||
class Parent extends Component {
|
class Parent extends Component {
|
||||||
static components = { Portal, Child };
|
static components = { Child };
|
||||||
static template = xml`
|
static template = xml`
|
||||||
<div>
|
<div>
|
||||||
<Portal target="'#outside'" >
|
<t t-portal="'#outside'" >
|
||||||
<Child error="state.error"/>
|
<Child error="state.error"/>
|
||||||
</Portal>
|
</t>
|
||||||
</div>`;
|
</div>`;
|
||||||
state = { error: false };
|
state = { error: false };
|
||||||
setup() {
|
setup() {
|
||||||
@@ -492,12 +480,12 @@ describe("Portal", () => {
|
|||||||
<button>child</button>`;
|
<button>child</button>`;
|
||||||
}
|
}
|
||||||
class Parent extends Component {
|
class Parent extends Component {
|
||||||
static components = { Portal, Child };
|
static components = { Child };
|
||||||
static template = xml`
|
static template = xml`
|
||||||
<div>
|
<div>
|
||||||
<Portal target="'#outside'">
|
<t t-portal="'#outside'">
|
||||||
<Child />
|
<Child />
|
||||||
</Portal>
|
</t>
|
||||||
</div>`;
|
</div>`;
|
||||||
}
|
}
|
||||||
const env = {};
|
const env = {};
|
||||||
@@ -519,11 +507,11 @@ describe("Portal", () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
class Child extends Component {
|
class Child extends Component {
|
||||||
static components = { Portal, Child2 };
|
static components = { Child2 };
|
||||||
static template = xml`
|
static template = xml`
|
||||||
<Portal target="'#outside'">
|
<t t-portal="'#outside'">
|
||||||
<t t-slot="default"/>
|
<t t-slot="default"/>
|
||||||
</Portal>`;
|
</t>`;
|
||||||
}
|
}
|
||||||
class Parent extends Component {
|
class Parent extends Component {
|
||||||
static components = { Child, Child2 };
|
static components = { Child, Child2 };
|
||||||
@@ -545,6 +533,111 @@ describe("Portal", () => {
|
|||||||
elem(childInst!).dispatchEvent(new CustomEvent("custom"));
|
elem(childInst!).dispatchEvent(new CustomEvent("custom"));
|
||||||
expect(steps).toEqual(["custom"]);
|
expect(steps).toEqual(["custom"]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("Add and remove portals", async () => {
|
||||||
|
class Parent extends Component {
|
||||||
|
static template = xml`
|
||||||
|
<t t-portal="'#outside'" t-foreach="portalIds" t-as="portalId" t-key="portalId">
|
||||||
|
Portal<t t-esc="portalId"/>
|
||||||
|
</t>`;
|
||||||
|
portalIds = useState([] as any);
|
||||||
|
}
|
||||||
|
|
||||||
|
addOutsideDiv(fixture);
|
||||||
|
const parent = await mount(Parent, fixture);
|
||||||
|
|
||||||
|
expect(fixture.innerHTML).toBe('<div id="outside"></div>');
|
||||||
|
|
||||||
|
parent.portalIds.push(1);
|
||||||
|
await nextTick();
|
||||||
|
expect(fixture.innerHTML).toBe('<div id="outside"> Portal1</div>');
|
||||||
|
|
||||||
|
parent.portalIds.push(2);
|
||||||
|
await nextTick();
|
||||||
|
expect(fixture.innerHTML).toBe('<div id="outside"> Portal1 Portal2</div>');
|
||||||
|
|
||||||
|
parent.portalIds.pop();
|
||||||
|
await nextTick();
|
||||||
|
expect(fixture.innerHTML).toBe('<div id="outside"> Portal1</div>');
|
||||||
|
|
||||||
|
parent.portalIds.pop();
|
||||||
|
await nextTick();
|
||||||
|
expect(fixture.innerHTML).toBe('<div id="outside"></div>');
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Add and remove portals with t-foreach", async () => {
|
||||||
|
class Parent extends Component {
|
||||||
|
static template = xml`
|
||||||
|
<t t-foreach="portalIds" t-as="portalId" t-key="portalId">
|
||||||
|
<div>
|
||||||
|
<t t-esc="portalId"/>
|
||||||
|
<t t-portal="'#outside'">
|
||||||
|
Portal<t t-esc="portalId"/>
|
||||||
|
</t>
|
||||||
|
</div>
|
||||||
|
</t>`;
|
||||||
|
portalIds = useState([] as any);
|
||||||
|
}
|
||||||
|
|
||||||
|
addOutsideDiv(fixture);
|
||||||
|
const parent = await mount(Parent, fixture);
|
||||||
|
|
||||||
|
expect(fixture.innerHTML).toBe('<div id="outside"></div>');
|
||||||
|
|
||||||
|
parent.portalIds.push(1);
|
||||||
|
await nextTick();
|
||||||
|
expect(fixture.innerHTML).toBe('<div id="outside"> Portal1</div><div>1</div>');
|
||||||
|
|
||||||
|
parent.portalIds.push(2);
|
||||||
|
await nextTick();
|
||||||
|
expect(fixture.innerHTML).toBe(
|
||||||
|
'<div id="outside"> Portal1 Portal2</div><div>1</div><div>2</div>'
|
||||||
|
);
|
||||||
|
|
||||||
|
parent.portalIds.pop();
|
||||||
|
await nextTick();
|
||||||
|
expect(fixture.innerHTML).toBe('<div id="outside"> Portal1</div><div>1</div>');
|
||||||
|
|
||||||
|
parent.portalIds.pop();
|
||||||
|
await nextTick();
|
||||||
|
expect(fixture.innerHTML).toBe('<div id="outside"></div>');
|
||||||
|
});
|
||||||
|
|
||||||
|
test("conditional use of Portal with div", async () => {
|
||||||
|
class Parent extends Component {
|
||||||
|
static template = xml`
|
||||||
|
<t t-if="state.hasPortal">
|
||||||
|
<div>
|
||||||
|
<span>hasPortal</span>
|
||||||
|
<t t-portal="'#outside'">
|
||||||
|
<p>thePortal</p>
|
||||||
|
</t>
|
||||||
|
</div>
|
||||||
|
</t>`;
|
||||||
|
|
||||||
|
state = useState({ hasPortal: false });
|
||||||
|
}
|
||||||
|
|
||||||
|
addOutsideDiv(fixture);
|
||||||
|
const parent = await mount(Parent, fixture);
|
||||||
|
expect(fixture.innerHTML).toBe('<div id="outside"></div>');
|
||||||
|
|
||||||
|
parent.state.hasPortal = true;
|
||||||
|
await nextTick();
|
||||||
|
expect(fixture.innerHTML).toBe(
|
||||||
|
'<div id="outside"><p>thePortal</p></div><div><span>hasPortal</span></div>'
|
||||||
|
);
|
||||||
|
|
||||||
|
parent.state.hasPortal = false;
|
||||||
|
await nextTick();
|
||||||
|
expect(fixture.innerHTML).toBe('<div id="outside"></div>');
|
||||||
|
|
||||||
|
parent.state.hasPortal = true;
|
||||||
|
await nextTick();
|
||||||
|
expect(fixture.innerHTML).toBe(
|
||||||
|
'<div id="outside"><p>thePortal</p></div><div><span>hasPortal</span></div>'
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("Portal: UI/UX", () => {
|
describe("Portal: UI/UX", () => {
|
||||||
@@ -554,12 +647,12 @@ describe("Portal: UI/UX", () => {
|
|||||||
<input id="target-me" t-att-placeholder="props.val"/>`;
|
<input id="target-me" t-att-placeholder="props.val"/>`;
|
||||||
}
|
}
|
||||||
class Parent extends Component {
|
class Parent extends Component {
|
||||||
static components = { Portal, Child };
|
static components = { Child };
|
||||||
static template = xml`
|
static template = xml`
|
||||||
<div>
|
<div>
|
||||||
<Portal target="'#outside'">
|
<t t-portal="'#outside'">
|
||||||
<Child val="state.val"/>
|
<Child val="state.val"/>
|
||||||
</Portal>
|
</t>
|
||||||
</div>`;
|
</div>`;
|
||||||
state = useState({ val: "ab" });
|
state = useState({ val: "ab" });
|
||||||
}
|
}
|
||||||
@@ -582,17 +675,13 @@ describe("Portal: UI/UX", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe("Portal: Props validation", () => {
|
describe("Portal: Props validation", () => {
|
||||||
test("target is mandatory", async () => {
|
test("target is mandatory 1", async () => {
|
||||||
const consoleInfo = console.info;
|
|
||||||
console.info = jest.fn();
|
|
||||||
|
|
||||||
class Parent extends Component {
|
class Parent extends Component {
|
||||||
static components = { Portal };
|
|
||||||
static template = xml`
|
static template = xml`
|
||||||
<div>
|
<div>
|
||||||
<Portal>
|
<t t-portal>
|
||||||
<div>2</div>
|
<div>2</div>
|
||||||
</Portal>
|
</t>
|
||||||
</div>`;
|
</div>`;
|
||||||
}
|
}
|
||||||
let error: Error;
|
let error: Error;
|
||||||
@@ -602,21 +691,16 @@ describe("Portal: Props validation", () => {
|
|||||||
error = e as Error;
|
error = e as Error;
|
||||||
}
|
}
|
||||||
expect(error!).toBeDefined();
|
expect(error!).toBeDefined();
|
||||||
expect(error!.message).toBe(`Missing props 'target' (component 'Portal')`);
|
expect(error!.message).toContain(`attribute without value.`);
|
||||||
console.info = consoleInfo;
|
|
||||||
expect(mockConsoleWarn).toBeCalledTimes(1);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test("target is not list", async () => {
|
test("target is mandatory 2", async () => {
|
||||||
const consoleInfo = console.info;
|
|
||||||
console.info = jest.fn();
|
|
||||||
class Parent extends Component {
|
class Parent extends Component {
|
||||||
static components = { Portal };
|
|
||||||
static template = xml`
|
static template = xml`
|
||||||
<div>
|
<div>
|
||||||
<Portal target="['body']">
|
<t t-portal="">
|
||||||
<div>2</div>
|
<div>2</div>
|
||||||
</Portal>
|
</t>
|
||||||
</div>`;
|
</div>`;
|
||||||
}
|
}
|
||||||
let error: Error;
|
let error: Error;
|
||||||
@@ -626,8 +710,44 @@ describe("Portal: Props validation", () => {
|
|||||||
error = e as Error;
|
error = e as Error;
|
||||||
}
|
}
|
||||||
expect(error!).toBeDefined();
|
expect(error!).toBeDefined();
|
||||||
expect(error!.message).toBe(`Invalid Prop 'target' in component 'Portal'`);
|
expect(error!.message).toBe(`Unexpected token ','`);
|
||||||
console.info = consoleInfo;
|
});
|
||||||
expect(mockConsoleWarn).toBeCalledTimes(1);
|
|
||||||
|
test("target must be a valid selector", async () => {
|
||||||
|
class Parent extends Component {
|
||||||
|
static template = xml`
|
||||||
|
<div>
|
||||||
|
<t t-portal="' '">
|
||||||
|
<div>2</div>
|
||||||
|
</t>
|
||||||
|
</div>`;
|
||||||
|
}
|
||||||
|
let error: Error;
|
||||||
|
try {
|
||||||
|
await mount(Parent, fixture, { dev: true });
|
||||||
|
} catch (e) {
|
||||||
|
error = e as Error;
|
||||||
|
}
|
||||||
|
expect(error!).toBeDefined();
|
||||||
|
expect(error!.message).toBe(`' ' is not a valid selector`);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("target must be a valid selector 2", async () => {
|
||||||
|
class Parent extends Component {
|
||||||
|
static template = xml`
|
||||||
|
<div>
|
||||||
|
<t t-portal="'aa'">
|
||||||
|
<div>2</div>
|
||||||
|
</t>
|
||||||
|
</div>`;
|
||||||
|
}
|
||||||
|
let error: Error;
|
||||||
|
try {
|
||||||
|
await mount(Parent, fixture, { dev: true });
|
||||||
|
} catch (e) {
|
||||||
|
error = e as Error;
|
||||||
|
}
|
||||||
|
expect(error!).toBeDefined();
|
||||||
|
expect(error!.message).toBe(`invalid portal target`);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user