You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
svelte/src/compile/nodes/Slot.ts

166 lines
5.2 KiB

import deindent from '../../utils/deindent';
import isValidIdentifier from '../../utils/isValidIdentifier';
import reservedNames from '../../utils/reservedNames';
import Node from './shared/Node';
import Element from './Element';
import Attribute from './Attribute';
import Block from '../dom/Block';
export default class Slot extends Element {
type: 'Element';
name: string;
attributes: Attribute[];
children: Node[];
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 { compiler } = this;
const slotName = this.getStaticAttributeValue('name') || 'default';
compiler.slots.add(slotName);
const content_name = block.getUniqueName(`slot_content_${slotName}`);
const prop = !isValidIdentifier(slotName) ? `["${slotName}"]` : `.${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 unmountBefore = block.builders.unmount.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.unmount.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.unmount.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 && `@appendNode(${anchorBefore} || (${anchorBefore} = @createComment()), ${parentNode});`}
@appendNode(${content_name}, ${parentNode});
${needsAnchorAfter && `@appendNode(${anchorAfter} || (${anchorAfter} = @createComment()), ${parentNode});`}
}
`);
} else {
block.builders.mount.addBlock(deindent`
${mountLeadin} {
${needsAnchorBefore && `@insertNode(${anchorBefore} || (${anchorBefore} = @createComment()), #target, anchor);`}
@insertNode(${content_name}, #target, anchor);
${needsAnchorAfter && `@insertNode(${anchorAfter} || (${anchorAfter} = @createComment()), #target, 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.unmount.toString() !== unmountBefore
? `else`
: `if (${content_name})`;
if (anchorBefore === 'null' && anchorAfter === 'null') {
block.builders.unmount.addBlock(deindent`
${unmountLeadin} {
@reinsertChildren(${parentNode}, ${content_name});
}
`);
} else if (anchorBefore === 'null') {
block.builders.unmount.addBlock(deindent`
${unmountLeadin} {
@reinsertBefore(${anchorAfter}, ${content_name});
}
`);
} else if (anchorAfter === 'null') {
block.builders.unmount.addBlock(deindent`
${unmountLeadin} {
@reinsertAfter(${anchorBefore}, ${content_name});
}
`);
} else {
block.builders.unmount.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
);
if (!attribute) return null;
if (attribute.isTrue) return true;
if (attribute.chunks.length === 0) return '';
if (attribute.chunks.length === 1 && attribute.chunks[0].type === 'Text') {
return attribute.chunks[0].data;
}
return null;
}
ssr() {
const name = this.attributes.find(attribute => attribute.name === 'name');
const slotName = name && name.chunks[0].data || 'default';
this.compiler.target.append(`\${options && options.slotted && options.slotted.${slotName} ? options.slotted.${slotName}() : \``);
this.children.forEach((child: Node) => {
child.ssr();
});
this.compiler.target.append(`\`}`);
}
}