diff --git a/src/compile/nodes/Slot.ts b/src/compile/nodes/Slot.ts index b80d2a5896..40a340bd7b 100644 --- a/src/compile/nodes/Slot.ts +++ b/src/compile/nodes/Slot.ts @@ -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 diff --git a/src/compile/render-dom/wrappers/Fragment.ts b/src/compile/render-dom/wrappers/Fragment.ts index 6be72d81fa..c1421c4271 100644 --- a/src/compile/render-dom/wrappers/Fragment.ts +++ b/src/compile/render-dom/wrappers/Fragment.ts @@ -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 }; diff --git a/src/compile/render-dom/wrappers/IfBlock.ts b/src/compile/render-dom/wrappers/IfBlock.ts index 63bb9cb683..70fd1a793d 100644 --- a/src/compile/render-dom/wrappers/IfBlock.ts +++ b/src/compile/render-dom/wrappers/IfBlock.ts @@ -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} && `; diff --git a/src/compile/render-dom/wrappers/Slot.ts b/src/compile/render-dom/wrappers/Slot.ts new file mode 100644 index 0000000000..ba4dcb5a9a --- /dev/null +++ b/src/compile/render-dom/wrappers/Slot.ts @@ -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}); + } + `); + } + } +} \ No newline at end of file