move Slot logic

pull/992/head
Rich Harris 8 years ago
parent daaad80a0e
commit e8e05e7160

@ -163,11 +163,9 @@ export default class Generator {
this.computations = [];
this.templateProperties = {};
this.name = this.alias(name);
this.walkJs(dom);
this.walkTemplate();
this.name = this.alias(name);
if (options.customElement === true) {
this.customElement = {
@ -181,6 +179,8 @@ export default class Generator {
if (this.customElement && !this.customElement.tag) {
throw new Error(`No tag name specified`); // TODO better error
}
this.walkTemplate();
}
addSourcemapLocations(node: Node) {
@ -718,6 +718,9 @@ export default class Generator {
} else if (node.name === ':Window') { // TODO do this in parse?
node.type = 'Window';
node.__proto__ = nodes.Window.prototype;
} else if (node.type === 'Element' && node.name === 'slot' && !generator.customElement) {
node.type = 'Slot';
node.__proto__ = nodes.Slot.prototype;
} else if (node.type in nodes) {
node.__proto__ = nodes[node.type].prototype;
}

@ -1,104 +0,0 @@
import { DomGenerator } from '../index';
import deindent from '../../../utils/deindent';
import visit from '../visit';
import Block from '../Block';
import getStaticAttributeValue from '../../../utils/getStaticAttributeValue';
import { Node } from '../../../interfaces';
import { State } from '../interfaces';
export default function visitSlot(
generator: DomGenerator,
block: Block,
state: State,
node: Node,
elementStack: Node[],
componentStack: Node[]
) {
const slotName = getStaticAttributeValue(node, 'name') || 'default';
generator.slots.add(slotName);
const content_name = block.getUniqueName(`slot_content_${slotName}`);
block.addVariable(content_name, `#component._slotted.${slotName}`);
const needsAnchorBefore = node.prev ? node.prev.type !== 'Element' : !state.parentNode;
const needsAnchorAfter = node.next ? node.next.type !== 'Element' : !state.parentNode;
const anchorBefore = needsAnchorBefore
? block.getUniqueName(`${content_name}_before`)
: (node.prev && node.prev.var) || 'null';
const anchorAfter = needsAnchorAfter
? block.getUniqueName(`${content_name}_after`)
: (node.next && node.next.var) || 'null';
if (needsAnchorBefore) block.addVariable(anchorBefore);
if (needsAnchorAfter) block.addVariable(anchorAfter);
block.builders.create.pushCondition(`!${content_name}`);
block.builders.hydrate.pushCondition(`!${content_name}`);
block.builders.mount.pushCondition(`!${content_name}`);
block.builders.unmount.pushCondition(`!${content_name}`);
block.builders.destroy.pushCondition(`!${content_name}`);
node.children.forEach((child: Node) => {
child.build(block, state, elementStack, componentStack);
});
block.builders.create.popCondition();
block.builders.hydrate.popCondition();
block.builders.mount.popCondition();
block.builders.unmount.popCondition();
block.builders.destroy.popCondition();
// TODO can we use an else here?
if (state.parentNode) {
block.builders.mount.addBlock(deindent`
if (${content_name}) {
${needsAnchorBefore && `@appendNode(${anchorBefore} || (${anchorBefore} = @createComment()), ${state.parentNode});`}
@appendNode(${content_name}, ${state.parentNode});
${needsAnchorAfter && `@appendNode(${anchorAfter} || (${anchorAfter} = @createComment()), ${state.parentNode});`}
}
`);
} else {
block.builders.mount.addBlock(deindent`
if (${content_name}) {
${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
// TODO can we use an else here?
if (anchorBefore === 'null' && anchorAfter === 'null') {
block.builders.unmount.addBlock(deindent`
if (${content_name}) {
@reinsertChildren(${state.parentNode}, ${content_name});
}
`);
} else if (anchorBefore === 'null') {
block.builders.unmount.addBlock(deindent`
if (${content_name}) {
@reinsertBefore(${anchorAfter}, ${content_name});
}
`);
} else if (anchorAfter === 'null') {
block.builders.unmount.addBlock(deindent`
if (${content_name}) {
@reinsertAfter(${anchorBefore}, ${content_name});
}
`);
} else {
block.builders.unmount.addBlock(deindent`
if (${content_name}) {
@reinsertBetween(${anchorBefore}, ${anchorAfter}, ${content_name});
@detachNode(${anchorBefore});
@detachNode(${anchorAfter});
}
`);
}
}

@ -15,7 +15,6 @@ import * as namespaces from '../../utils/namespaces';
import addBindings from '../dom/visitors/Element/addBindings';
import addTransitions from '../dom/visitors/Element/addTransitions';
import visitAttribute from '../dom/visitors/Element/Attribute';
import visitSlot from '../dom/visitors/Slot';
export default class Element extends Node {
type: 'Element';
@ -155,13 +154,9 @@ export default class Element extends Node {
) {
const { generator } = this;
if (this.name === 'slot') { // TODO deal with in walkTemplate
if (this.generator.customElement) {
const slotName = this.getStaticAttributeValue('name') || 'default';
this.generator.slots.add(slotName);
} else {
return visitSlot(this.generator, block, state, this, elementStack, componentStack);
}
if (this.name === 'slot') {
const slotName = this.getStaticAttributeValue('name') || 'default';
this.generator.slots.add(slotName);
}
const childState = this._state;

@ -1,5 +1,155 @@
import deindent from '../../utils/deindent';
import Node from './shared/Node';
import Element from './Element';
import Attribute from './Attribute';
import Block from '../dom/Block';
import State from '../dom/State';
export default class Slot extends Node {
export default class Slot extends Element {
type: 'Element';
name: string;
attributes: Attribute[]; // TODO have more specific Attribute type
children: Node[];
init(
block: Block,
state: State,
inEachBlock: boolean,
elementStack: Node[],
componentStack: Node[],
stripWhitespace: boolean,
nextSibling: Node
) {
this.cannotUseInnerHTML();
this.var = block.getUniqueName('slot');
this._state = state.child({
parentNode: this.var,
parentNodes: block.getUniqueName(`${this.var}_nodes`),
parentNodeName: this.name,
namespace: this.name === 'svg'
? 'http://www.w3.org/2000/svg'
: state.namespace,
allUsedContexts: [],
});
if (this.children.length) {
this.initChildren(block, this._state, inEachBlock, elementStack.concat(this), componentStack, stripWhitespace, nextSibling);
}
}
build(
block: Block,
state: State,
elementStack: Node[],
componentStack: Node[]
) {
const { generator } = this;
const slotName = this.getStaticAttributeValue('name') || 'default';
generator.slots.add(slotName);
const content_name = block.getUniqueName(`slot_content_${slotName}`);
block.addVariable(content_name, `#component._slotted.${slotName}`);
const needsAnchorBefore = this.prev ? this.prev.type !== 'Element' : !state.parentNode;
const needsAnchorAfter = this.next ? this.next.type !== 'Element' : !state.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);
block.builders.create.pushCondition(`!${content_name}`);
block.builders.hydrate.pushCondition(`!${content_name}`);
block.builders.mount.pushCondition(`!${content_name}`);
block.builders.unmount.pushCondition(`!${content_name}`);
block.builders.destroy.pushCondition(`!${content_name}`);
this.children.forEach((child: Node) => {
child.build(block, state, elementStack, componentStack);
});
block.builders.create.popCondition();
block.builders.hydrate.popCondition();
block.builders.mount.popCondition();
block.builders.unmount.popCondition();
block.builders.destroy.popCondition();
// TODO can we use an else here?
if (state.parentNode) {
block.builders.mount.addBlock(deindent`
if (${content_name}) {
${needsAnchorBefore && `@appendNode(${anchorBefore} || (${anchorBefore} = @createComment()), ${state.parentNode});`}
@appendNode(${content_name}, ${state.parentNode});
${needsAnchorAfter && `@appendNode(${anchorAfter} || (${anchorAfter} = @createComment()), ${state.parentNode});`}
}
`);
} else {
block.builders.mount.addBlock(deindent`
if (${content_name}) {
${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
// TODO can we use an else here?
if (anchorBefore === 'null' && anchorAfter === 'null') {
block.builders.unmount.addBlock(deindent`
if (${content_name}) {
@reinsertChildren(${state.parentNode}, ${content_name});
}
`);
} else if (anchorBefore === 'null') {
block.builders.unmount.addBlock(deindent`
if (${content_name}) {
@reinsertBefore(${anchorAfter}, ${content_name});
}
`);
} else if (anchorAfter === 'null') {
block.builders.unmount.addBlock(deindent`
if (${content_name}) {
@reinsertAfter(${anchorBefore}, ${content_name});
}
`);
} else {
block.builders.unmount.addBlock(deindent`
if (${content_name}) {
@reinsertBetween(${anchorBefore}, ${anchorAfter}, ${content_name});
@detachNode(${anchorBefore});
@detachNode(${anchorAfter});
}
`);
}
}
getStaticAttributeValue(name: string) {
const attribute = this.attributes.find(
(attr: Node) => attr.name.toLowerCase() === name
);
if (!attribute) return null;
if (attribute.value === true) return true;
if (attribute.value.length === 0) return '';
if (attribute.value.length === 1 && attribute.value[0].type === 'Text') {
return attribute.value[0].data;
}
return null;
}
}

@ -6,6 +6,7 @@ import Element from './Element';
import IfBlock from './IfBlock';
import MustacheTag from './MustacheTag';
import RawMustacheTag from './RawMustacheTag';
import Slot from './Slot';
import Text from './Text';
import Window from './Window';
@ -18,6 +19,7 @@ export default {
IfBlock,
MustacheTag,
RawMustacheTag,
Slot,
Text,
Window
};

Loading…
Cancel
Save