diff --git a/doc/reference/component.md b/doc/reference/component.md
index 67fdc6d7..63e1b32d 100644
--- a/doc/reference/component.md
+++ b/doc/reference/component.md
@@ -865,6 +865,19 @@ be considered the `default` slot. For example:
```
+Slots can define a default content, in case the parent did not define them:
+
+```xml
+
+
+
+
+
+ default content
+
+
+```
+
### Dynamic sub components
It is not common, but sometimes we need a dynamic component name. In this case,
diff --git a/src/qweb/extensions.ts b/src/qweb/extensions.ts
index 0dab24f0..b7b95598 100644
--- a/src/qweb/extensions.ts
+++ b/src/qweb/extensions.ts
@@ -194,7 +194,7 @@ QWeb.addDirective({
QWeb.addDirective({
name: "slot",
priority: 80,
- atNodeEncounter({ ctx, value }): boolean {
+ atNodeEncounter({ ctx, value, node, qweb }): boolean {
const slotKey = ctx.generateID();
ctx.addLine(
`const slot${slotKey} = this.constructor.slots[context.__owl__.slotId + '_' + '${value}'];`
@@ -214,6 +214,12 @@ QWeb.addDirective({
if (!ctx.parentNode) {
ctx.addLine(`utils.defineProxy(result, ${parentNode}[0]);`);
}
+ if (node.hasChildNodes()) {
+ ctx.addElse();
+ const nodeCopy = node.cloneNode(true);
+ nodeCopy.removeAttribute("t-slot");
+ qweb._compileNode(nodeCopy, ctx);
+ }
ctx.closeIf();
return true;
}
diff --git a/tests/component/__snapshots__/component.test.ts.snap b/tests/component/__snapshots__/component.test.ts.snap
index e2cd9375..859fb0e5 100644
--- a/tests/component/__snapshots__/component.test.ts.snap
+++ b/tests/component/__snapshots__/component.test.ts.snap
@@ -1395,6 +1395,23 @@ exports[`t-slot directive content is the default slot 1`] = `
}"
`;
+exports[`t-slot directive dafault slots can define a default content 1`] = `
+"function anonymous(context, extra
+) {
+ // Template name: \\"__template__1\\"
+ var h = this.h;
+ let c5 = [], p5 = {key:5};
+ var vn5 = h('span', p5, c5);
+ const slot6 = this.constructor.slots[context.__owl__.slotId + '_' + 'default'];
+ if (slot6) {
+ slot6.call(this, context.__owl__.scope, Object.assign({}, extra, {parentNode: c5, parent: extra.parent || context}));
+ } else {
+ c5.push({text: \`default content\`});
+ }
+ return vn5;
+}"
+`;
+
exports[`t-slot directive default slot work with text nodes 1`] = `
"function anonymous(context, extra
) {
@@ -1439,6 +1456,23 @@ exports[`t-slot directive multiple roots are allowed in a named slot 1`] = `
}"
`;
+exports[`t-slot directive named slots can define a default content 1`] = `
+"function anonymous(context, extra
+) {
+ // Template name: \\"__template__1\\"
+ var h = this.h;
+ let c5 = [], p5 = {key:5};
+ var vn5 = h('span', p5, c5);
+ const slot6 = this.constructor.slots[context.__owl__.slotId + '_' + 'header'];
+ if (slot6) {
+ slot6.call(this, context.__owl__.scope, Object.assign({}, extra, {parentNode: c5, parent: extra.parent || context}));
+ } else {
+ c5.push({text: \`default content\`});
+ }
+ return vn5;
+}"
+`;
+
exports[`t-slot directive refs are properly bound in slots 1`] = `
"function anonymous(context, extra
) {
diff --git a/tests/component/component.test.ts b/tests/component/component.test.ts
index 6db797b0..dbcd6c1a 100644
--- a/tests/component/component.test.ts
+++ b/tests/component/component.test.ts
@@ -4032,6 +4032,76 @@ describe("t-slot directive", () => {
expect(QWeb.slots["1_footer"].toString()).toMatchSnapshot();
});
+ test("named slots can define a default content", async () => {
+ class Dialog extends Component {
+ static template = xml`
+
+ default content
+ `;
+ }
+ class Parent extends Component {
+ static template = xml``;
+ static components = { Dialog };
+ }
+ const parent = new Parent();
+ await parent.mount(fixture);
+
+ expect(fixture.innerHTML).toBe("default content
");
+ expect(env.qweb.templates[Dialog.template].fn.toString()).toMatchSnapshot();
+ });
+
+ test("dafault slots can define a default content", async () => {
+ class Dialog extends Component {
+ static template = xml`
+
+ default content
+ `;
+ }
+ class Parent extends Component {
+ static template = xml``;
+ static components = { Dialog };
+ }
+ const parent = new Parent();
+ await parent.mount(fixture);
+
+ expect(fixture.innerHTML).toBe("default content
");
+ expect(env.qweb.templates[Dialog.template].fn.toString()).toMatchSnapshot();
+ });
+
+ test("default content is not rendered if slot is provided", async () => {
+ class Dialog extends Component {
+ static template = xml`
+
+ default content
+ `;
+ }
+ class Parent extends Component {
+ static template = xml``;
+ static components = { Dialog };
+ }
+ const parent = new Parent();
+ await parent.mount(fixture);
+
+ expect(fixture.innerHTML).toBe("hey
");
+ });
+
+ test("default content is not rendered if named slot is provided", async () => {
+ class Dialog extends Component {
+ static template = xml`
+
+ default content
+ `;
+ }
+ class Parent extends Component {
+ static template = xml``;
+ static components = { Dialog };
+ }
+ const parent = new Parent();
+ await parent.mount(fixture);
+
+ expect(fixture.innerHTML).toBe("hey
");
+ });
+
test("slots are rendered with proper context", async () => {
env.qweb.addTemplates(`