[IMP] slots: via prop 'slots'

The slot inner working has been reworked. A prop "slots" is now passed
explicitely to the component. It looks like

{ slotName_1: slotInfo_1, ..., slotName_m: slotInfo_m }

with the objects slotInfo_i with mandatory keys "__render", "__ctx",
and optional key "__scope" and possibly others.

Here is how a slotInfo object can be created:
A slotInfo object is normally created by setting in a template something
like

<div>
    <t t-set-slot="foo" t-set-scope="scope" param_1="var" param_2="3">
        content
        <t t-esc="scope.bool"/>
        <t t-esc="scope.num"/>
    </t>
</div>

and it will be used somewhere like

<div>
    <t t-esc="props.slots.foo.param_1"/>
    <t t-slot="foo" bool="other_var" num="5">
</div>

In the above example, the function "__render" produces the block dom
element for the content of the t-set-slot.
The context "__ctx" will have a key "scope" with value { bool: ..., num: 5 }
and "__scope" will be set to "scope".
This commit is contained in:
Mathieu Duckerts-Antoine
2021-11-19 13:43:21 +01:00
committed by Aaron Bohy
parent 7143dd3ff5
commit a073568667
15 changed files with 824 additions and 604 deletions
+75 -44
View File
@@ -962,13 +962,62 @@ export class CodeGenerator {
compileComponent(ast: ASTComponent, ctx: Context) {
let { block } = ctx;
let extraArgs: { [key: string]: string } = {};
// props
const props: string[] = [];
let hasSlotsProp = false;
for (let p in ast.props) {
props.push(`${p}: ${this.captureExpression(ast.props[p]) || undefined}`);
if (p === "slots") {
hasSlotsProp = true;
}
}
// slots
const hasSlot = !!Object.keys(ast.slots).length;
let slotDef: string = "";
if (hasSlot) {
let ctxStr = "ctx";
if (this.target.loopLevel || !this.hasSafeContext) {
ctxStr = this.generateId("ctx");
this.addLine(`const ${ctxStr} = capture(ctx);`);
}
let slotStr: string[] = [];
const initialTarget = this.target;
for (let slotName in ast.slots) {
let name = this.generateId("slot");
const slot = new CodeTarget(name);
slot.signature = "(ctx, node, key) => {";
this.functions.push(slot);
this.target = slot;
const subCtx: Context = createContext(ctx);
this.compileAST(ast.slots[slotName].content, subCtx);
const params = [`__render: ${name}, __ctx: ${ctxStr}`];
const scope = ast.slots[slotName].scope;
if (scope) {
params.push(`__scope: "${scope}"`);
}
if (ast.slots[slotName].attrs) {
for (const [n, v] of Object.entries(ast.slots[slotName].attrs!)) {
params.push(`${n}: ${compileExpr(v) || undefined}`);
}
}
const slotInfo = `{${params.join(", ")}}`;
if (this.hasRef) {
slot.code.unshift(` const refs = ctx.__owl__.refs`);
slotStr.push(`'${slotName}': ${slotInfo}`);
} else {
slotStr.push(`'${slotName}': ${slotInfo}`);
}
}
this.target = initialTarget;
slotDef = `{${slotStr.join(", ")}}`;
}
if (slotDef && !(ast.dynamicProps || hasSlotsProp)) {
props.push(`slots: ${slotDef}`);
}
const propStr = `{${props.join(",")}}`;
let propString = propStr;
@@ -980,6 +1029,17 @@ export class CodeGenerator {
}
}
let propVar: string;
if ((slotDef && (ast.dynamicProps || hasSlotsProp)) || this.dev) {
propVar = this.generateId("props");
this.addLine(`const ${propVar!} = ${propString}`);
propString = propVar!;
}
if (slotDef && (ast.dynamicProps || hasSlotsProp)) {
this.addLine(`${propVar!}.slots = Object.assign(${slotDef}, ${propVar!}.slots)`);
}
// cmap key
const key = this.generateComponentKey();
let expr: string;
@@ -991,41 +1051,7 @@ export class CodeGenerator {
}
if (this.dev) {
const propVar = this.generateId("props");
this.addLine(`const ${propVar} = ${propString}`);
this.addLine(`helpers.validateProps(${expr}, ${propVar}, ctx)`);
propString = propVar;
}
// slots
const hasSlot = !!Object.keys(ast.slots).length;
let slotDef: string;
if (hasSlot) {
let ctxStr = "ctx";
if (this.target.loopLevel || !this.hasSafeContext) {
ctxStr = this.generateId("ctx");
this.addLine(`const ${ctxStr} = capture(ctx);`);
}
let slotStr: string[] = [];
const initialTarget = this.target;
for (let slotName in ast.slots) {
let name = this.generateId("slot");
const slot = new CodeTarget(name);
slot.signature = "ctx => (node, key) => {";
this.functions.push(slot);
this.target = slot;
const subCtx: Context = createContext(ctx);
this.compileAST(ast.slots[slotName], subCtx);
if (this.hasRef) {
slot.code.unshift(` const refs = ctx.__owl__.refs`);
slotStr.push(`'${slotName}': ${name}(${ctxStr})`);
} else {
slotStr.push(`'${slotName}': ${name}(${ctxStr})`);
}
}
this.target = initialTarget;
slotDef = `{${slotStr.join(", ")}}`;
extraArgs.slots = slotDef;
this.addLine(`helpers.validateProps(${expr}, ${propVar!}, ctx)`);
}
if (block && (ctx.forceNewBlock === false || ctx.tKeyExpr)) {
@@ -1039,11 +1065,6 @@ export class CodeGenerator {
}
const blockArgs = `${expr}, ${propString}, ${keyArg}, node, ctx`;
let blockExpr = `component(${blockArgs})`;
if (Object.keys(extraArgs).length) {
this.shouldDefineAssign = true;
const content = Object.keys(extraArgs).map((k) => `${k}: ${extraArgs[k]}`);
blockExpr = `assign(${blockExpr}, {${content.join(", ")}})`;
}
if (ast.isDynamic) {
blockExpr = `toggler(${expr}, ${blockExpr})`;
}
@@ -1062,6 +1083,16 @@ export class CodeGenerator {
} else {
slotName = "'" + ast.name + "'";
}
let scope = null;
if (ast.attrs) {
const params = [];
for (const [n, v] of Object.entries(ast.attrs!)) {
params.push(`${n}: ${compileExpr(v) || undefined}`);
}
scope = `{${params.join(", ")}}`;
}
if (ast.defaultContent) {
let name = this.generateId("defaultSlot");
const slot = new CodeTarget(name);
@@ -1072,14 +1103,14 @@ export class CodeGenerator {
this.target = slot;
this.compileAST(ast.defaultContent, subCtx);
this.target = initialTarget;
blockString = `callSlot(ctx, node, key, ${slotName}, ${name}, ${dynamic})`;
blockString = `callSlot(ctx, node, key, ${slotName}, ${dynamic}, ${scope}, ${name})`;
} else {
if (dynamic) {
let name = this.generateId("slot");
this.addLine(`const ${name} = ${slotName};`);
blockString = `toggler(${name}, callSlot(ctx, node, key, ${name}))`;
blockString = `toggler(${name}, callSlot(ctx, node, key, ${name}), ${dynamic}, ${scope})`;
} else {
blockString = `callSlot(ctx, node, key, ${slotName})`;
blockString = `callSlot(ctx, node, key, ${slotName}, ${dynamic}, ${scope})`;
}
}
if (block) {