implement slot

pull/1746/head
Rich Harris 7 years ago
parent bbce893c06
commit dfa3d7fa74

@ -68,126 +68,6 @@ export default class Slot extends Element {
// }
}
init(
block: Block,
stripWhitespace: boolean,
nextSibling: Node
) {
this.cannotUseInnerHTML();
this.var = block.getUniqueName('slot');
if (this.children.length) {
this.initChildren(block, stripWhitespace, nextSibling);
}
}
build(
block: Block,
parentNode: string,
parentNodes: string
) {
const { component } = this;
const slotName = this.getStaticAttributeValue('name') || 'default';
component.slots.add(slotName);
const content_name = block.getUniqueName(`slot_content_${sanitize(slotName)}`);
const prop = quotePropIfNecessary(slotName);
block.addVariable(content_name, `#component._slotted${prop}`);
const needsAnchorBefore = this.prev ? this.prev.type !== 'Element' : !parentNode;
const needsAnchorAfter = this.next ? this.next.type !== 'Element' : !parentNode;
const anchorBefore = needsAnchorBefore
? block.getUniqueName(`${content_name}_before`)
: (this.prev && this.prev.var) || 'null';
const anchorAfter = needsAnchorAfter
? block.getUniqueName(`${content_name}_after`)
: (this.next && this.next.var) || 'null';
if (needsAnchorBefore) block.addVariable(anchorBefore);
if (needsAnchorAfter) block.addVariable(anchorAfter);
let mountBefore = block.builders.mount.toString();
let destroyBefore = block.builders.destroy.toString();
block.builders.create.pushCondition(`!${content_name}`);
block.builders.hydrate.pushCondition(`!${content_name}`);
block.builders.mount.pushCondition(`!${content_name}`);
block.builders.update.pushCondition(`!${content_name}`);
block.builders.destroy.pushCondition(`!${content_name}`);
this.children.forEach((child: Node) => {
child.build(block, parentNode, parentNodes);
});
block.builders.create.popCondition();
block.builders.hydrate.popCondition();
block.builders.mount.popCondition();
block.builders.update.popCondition();
block.builders.destroy.popCondition();
const mountLeadin = block.builders.mount.toString() !== mountBefore
? `else`
: `if (${content_name})`;
if (parentNode) {
block.builders.mount.addBlock(deindent`
${mountLeadin} {
${needsAnchorBefore && `@append(${parentNode}, ${anchorBefore} || (${anchorBefore} = @createComment()));`}
@append(${parentNode}, ${content_name});
${needsAnchorAfter && `@append(${parentNode}, ${anchorAfter} || (${anchorAfter} = @createComment()));`}
}
`);
} else {
block.builders.mount.addBlock(deindent`
${mountLeadin} {
${needsAnchorBefore && `@insert(#target, ${anchorBefore} || (${anchorBefore} = @createComment()), anchor);`}
@insert(#target, ${content_name}, anchor);
${needsAnchorAfter && `@insert(#target, ${anchorAfter} || (${anchorAfter} = @createComment()), anchor);`}
}
`);
}
// if the slot is unmounted, move nodes back into the document fragment,
// so that it can be reinserted later
// TODO so that this can work with public API, component._slotted should
// be all fragments, derived from options.slots. Not === options.slots
const unmountLeadin = block.builders.destroy.toString() !== destroyBefore
? `else`
: `if (${content_name})`;
if (anchorBefore === 'null' && anchorAfter === 'null') {
block.builders.destroy.addBlock(deindent`
${unmountLeadin} {
@reinsertChildren(${parentNode}, ${content_name});
}
`);
} else if (anchorBefore === 'null') {
block.builders.destroy.addBlock(deindent`
${unmountLeadin} {
@reinsertBefore(${anchorAfter}, ${content_name});
}
`);
} else if (anchorAfter === 'null') {
block.builders.destroy.addBlock(deindent`
${unmountLeadin} {
@reinsertAfter(${anchorBefore}, ${content_name});
}
`);
} else {
block.builders.destroy.addBlock(deindent`
${unmountLeadin} {
@reinsertBetween(${anchorBefore}, ${anchorAfter}, ${content_name});
@detachNode(${anchorBefore});
@detachNode(${anchorAfter});
}
`);
}
}
getStaticAttributeValue(name: string) {
const attribute = this.attributes.find(
attr => attr.name.toLowerCase() === name

@ -5,6 +5,7 @@ import Element from './Element';
import IfBlock from './IfBlock';
import InlineComponent from './InlineComponent';
import MustacheTag from './MustacheTag';
import Slot from './Slot';
import Text from './Text';
import Window from './Window';
import Node from '../../nodes/shared/Node';
@ -21,6 +22,7 @@ const wrappers = {
IfBlock,
InlineComponent,
MustacheTag,
Slot,
Text,
Window
};

@ -216,7 +216,7 @@ export default class IfBlockWrapper extends Wrapper {
dynamic,
{ name, anchor, hasElse, if_name }
) {
const select_block_type = this.component.getUniqueName(`select_block_type`);
const select_block_type = this.renderer.component.getUniqueName(`select_block_type`);
const current_block_type = block.getUniqueName(`current_block_type`);
const current_block_type_and = hasElse ? '' : `${current_block_type} && `;

@ -0,0 +1,145 @@
import Wrapper from './shared/Wrapper';
import Renderer from '../Renderer';
import Block from '../Block';
import Slot from '../../nodes/Slot';
import { quotePropIfNecessary } from '../../../utils/quoteIfNecessary';
import FragmentWrapper from './Fragment';
import deindent from '../../../utils/deindent';
function sanitize(name) {
return name.replace(/[^a-zA-Z]+/g, '_').replace(/^_/, '').replace(/_$/, '');
}
export default class SlotWrapper extends Wrapper {
node: Slot;
fragment: FragmentWrapper;
var = 'slot';
constructor(
renderer: Renderer,
block: Block,
parent: Wrapper,
node: Slot,
stripWhitespace: boolean,
nextSibling: Wrapper
) {
super(renderer, block, parent, node);
this.cannotUseInnerHTML();
this.fragment = new FragmentWrapper(
renderer,
block,
node.children,
parent,
stripWhitespace,
nextSibling
);
}
render(
block: Block,
parentNode: string,
parentNodes: string
) {
const { renderer } = this;
const { component } = renderer;
const slotName = this.node.getStaticAttributeValue('name') || 'default';
component.slots.add(slotName);
const content_name = block.getUniqueName(`slot_content_${sanitize(slotName)}`);
const prop = quotePropIfNecessary(slotName);
block.addVariable(content_name, `#component._slotted${prop}`);
// TODO can we use isDomNode instead of type === 'Element'?
const needsAnchorBefore = this.prev ? this.prev.node.type !== 'Element' : !parentNode;
const needsAnchorAfter = this.next ? this.next.node.type !== 'Element' : !parentNode;
const anchorBefore = needsAnchorBefore
? block.getUniqueName(`${content_name}_before`)
: (this.prev && this.prev.var) || 'null';
const anchorAfter = needsAnchorAfter
? block.getUniqueName(`${content_name}_after`)
: (this.next && this.next.var) || 'null';
if (needsAnchorBefore) block.addVariable(anchorBefore);
if (needsAnchorAfter) block.addVariable(anchorAfter);
let mountBefore = block.builders.mount.toString();
let destroyBefore = block.builders.destroy.toString();
block.builders.create.pushCondition(`!${content_name}`);
block.builders.hydrate.pushCondition(`!${content_name}`);
block.builders.mount.pushCondition(`!${content_name}`);
block.builders.update.pushCondition(`!${content_name}`);
block.builders.destroy.pushCondition(`!${content_name}`);
this.fragment.render(block, parentNode, parentNodes);
block.builders.create.popCondition();
block.builders.hydrate.popCondition();
block.builders.mount.popCondition();
block.builders.update.popCondition();
block.builders.destroy.popCondition();
const mountLeadin = block.builders.mount.toString() !== mountBefore
? `else`
: `if (${content_name})`;
if (parentNode) {
block.builders.mount.addBlock(deindent`
${mountLeadin} {
${needsAnchorBefore && `@append(${parentNode}, ${anchorBefore} || (${anchorBefore} = @createComment()));`}
@append(${parentNode}, ${content_name});
${needsAnchorAfter && `@append(${parentNode}, ${anchorAfter} || (${anchorAfter} = @createComment()));`}
}
`);
} else {
block.builders.mount.addBlock(deindent`
${mountLeadin} {
${needsAnchorBefore && `@insert(#target, ${anchorBefore} || (${anchorBefore} = @createComment()), anchor);`}
@insert(#target, ${content_name}, anchor);
${needsAnchorAfter && `@insert(#target, ${anchorAfter} || (${anchorAfter} = @createComment()), anchor);`}
}
`);
}
// if the slot is unmounted, move nodes back into the document fragment,
// so that it can be reinserted later
// TODO so that this can work with public API, component._slotted should
// be all fragments, derived from options.slots. Not === options.slots
const unmountLeadin = block.builders.destroy.toString() !== destroyBefore
? `else`
: `if (${content_name})`;
if (anchorBefore === 'null' && anchorAfter === 'null') {
block.builders.destroy.addBlock(deindent`
${unmountLeadin} {
@reinsertChildren(${parentNode}, ${content_name});
}
`);
} else if (anchorBefore === 'null') {
block.builders.destroy.addBlock(deindent`
${unmountLeadin} {
@reinsertBefore(${anchorAfter}, ${content_name});
}
`);
} else if (anchorAfter === 'null') {
block.builders.destroy.addBlock(deindent`
${unmountLeadin} {
@reinsertAfter(${anchorBefore}, ${content_name});
}
`);
} else {
block.builders.destroy.addBlock(deindent`
${unmountLeadin} {
@reinsertBetween(${anchorBefore}, ${anchorAfter}, ${content_name});
@detachNode(${anchorBefore});
@detachNode(${anchorAfter});
}
`);
}
}
}
Loading…
Cancel
Save