diff --git a/src/compile/Component.ts b/src/compile/Component.ts index fbe4d483db..4a249d2287 100644 --- a/src/compile/Component.ts +++ b/src/compile/Component.ts @@ -120,7 +120,6 @@ export default class Component { hasComponents: boolean; computations: Computation[]; templateProperties: Record; - slots: Set; javascript: [string, string]; used: { @@ -180,7 +179,6 @@ export default class Component { this.transitions = new Set(); this.actions = new Set(); this.importedComponents = new Map(); - this.slots = new Set(); this.used = { components: new Set(), diff --git a/src/compile/nodes/IfBlock.ts b/src/compile/nodes/IfBlock.ts index 795568e9cd..d8eb2ce162 100644 --- a/src/compile/nodes/IfBlock.ts +++ b/src/compile/nodes/IfBlock.ts @@ -1,9 +1,6 @@ -import deindent from '../../utils/deindent'; import Node from './shared/Node'; import ElseBlock from './ElseBlock'; -import Component from '../Component'; import Block from '../render-dom/Block'; -import createDebuggingComment from '../../utils/createDebuggingComment'; import Expression from './shared/Expression'; import mapChildren from './shared/mapChildren'; @@ -27,379 +24,4 @@ export default class IfBlock extends Node { this.warnIfEmptyBlock(); } - - build( - block: Block, - parentNode: string, - parentNodes: string - ) { - const name = this.var; - - const needsAnchor = this.next ? !this.next.isDomNode() : !parentNode || !this.parent.isDomNode(); - const anchor = needsAnchor - ? block.getUniqueName(`${name}_anchor`) - : (this.next && this.next.var) || 'null'; - - const branches = this.getBranches(block, parentNode, parentNodes, this); - - const hasElse = isElseBranch(branches[branches.length - 1]); - const if_name = hasElse ? '' : `if (${name}) `; - - const dynamic = branches[0].hasUpdateMethod; // can use [0] as proxy for all, since they necessarily have the same value - const hasOutros = branches[0].hasOutroMethod; - - const vars = { name, anchor, if_name, hasElse }; - - if (this.else) { - if (hasOutros) { - this.buildCompoundWithOutros(block, parentNode, parentNodes, branches, dynamic, vars); - - if (this.component.options.nestedTransitions) { - block.builders.outro.addBlock(deindent` - if (${name}) ${name}.o(#outrocallback); - else #outrocallback(); - `); - } - } else { - this.buildCompound(block, parentNode, parentNodes, branches, dynamic, vars); - } - } else { - this.buildSimple(block, parentNode, parentNodes, branches[0], dynamic, vars); - - if (hasOutros && this.component.options.nestedTransitions) { - block.builders.outro.addBlock(deindent` - if (${name}) ${name}.o(#outrocallback); - else #outrocallback(); - `); - } - } - - block.builders.create.addLine(`${if_name}${name}.c();`); - - if (parentNodes) { - block.builders.claim.addLine( - `${if_name}${name}.l(${parentNodes});` - ); - } - - if (needsAnchor) { - block.addElement( - anchor, - `@createComment()`, - parentNodes && `@createComment()`, - parentNode - ); - } - } - - buildCompound( - block: Block, - parentNode: string, - parentNodes: string, - branches, - dynamic, - { name, anchor, hasElse, if_name } - ) { - const select_block_type = this.component.getUniqueName(`select_block_type`); - const current_block_type = block.getUniqueName(`current_block_type`); - const current_block_type_and = hasElse ? '' : `${current_block_type} && `; - - block.builders.init.addBlock(deindent` - function ${select_block_type}(ctx) { - ${branches - .map(({ condition, block }) => `${condition ? `if (${condition}) ` : ''}return ${block};`) - .join('\n')} - } - `); - - block.builders.init.addBlock(deindent` - var ${current_block_type} = ${select_block_type}(ctx); - var ${name} = ${current_block_type_and}${current_block_type}(#component, ctx); - `); - - const mountOrIntro = branches[0].hasIntroMethod ? 'i' : 'm'; - - const initialMountNode = parentNode || '#target'; - const anchorNode = parentNode ? 'null' : 'anchor'; - block.builders.mount.addLine( - `${if_name}${name}.${mountOrIntro}(${initialMountNode}, ${anchorNode});` - ); - - const updateMountNode = this.getUpdateMountNode(anchor); - - const changeBlock = deindent` - ${if_name}${name}.d(1); - ${name} = ${current_block_type_and}${current_block_type}(#component, ctx); - ${if_name}${name}.c(); - ${if_name}${name}.${mountOrIntro}(${updateMountNode}, ${anchor}); - `; - - if (dynamic) { - block.builders.update.addBlock(deindent` - if (${current_block_type} === (${current_block_type} = ${select_block_type}(ctx)) && ${name}) { - ${name}.p(changed, ctx); - } else { - ${changeBlock} - } - `); - } else { - block.builders.update.addBlock(deindent` - if (${current_block_type} !== (${current_block_type} = ${select_block_type}(ctx))) { - ${changeBlock} - } - `); - } - - block.builders.destroy.addLine(`${if_name}${name}.d(${parentNode ? '' : 'detach'});`); - } - - // if any of the siblings have outros, we need to keep references to the blocks - // (TODO does this only apply to bidi transitions?) - buildCompoundWithOutros( - block: Block, - parentNode: string, - parentNodes: string, - branches, - dynamic, - { name, anchor, hasElse } - ) { - const select_block_type = this.component.getUniqueName(`select_block_type`); - const current_block_type_index = block.getUniqueName(`current_block_type_index`); - const previous_block_index = block.getUniqueName(`previous_block_index`); - const if_block_creators = block.getUniqueName(`if_block_creators`); - const if_blocks = block.getUniqueName(`if_blocks`); - - const if_current_block_type_index = hasElse - ? '' - : `if (~${current_block_type_index}) `; - - block.addVariable(current_block_type_index); - block.addVariable(name); - - block.builders.init.addBlock(deindent` - var ${if_block_creators} = [ - ${branches.map(branch => branch.block).join(',\n')} - ]; - - var ${if_blocks} = []; - - function ${select_block_type}(ctx) { - ${branches - .map(({ condition, block }, i) => `${condition ? `if (${condition}) ` : ''}return ${block ? i : -1};`) - .join('\n')} - } - `); - - if (hasElse) { - block.builders.init.addBlock(deindent` - ${current_block_type_index} = ${select_block_type}(ctx); - ${name} = ${if_blocks}[${current_block_type_index}] = ${if_block_creators}[${current_block_type_index}](#component, ctx); - `); - } else { - block.builders.init.addBlock(deindent` - if (~(${current_block_type_index} = ${select_block_type}(ctx))) { - ${name} = ${if_blocks}[${current_block_type_index}] = ${if_block_creators}[${current_block_type_index}](#component, ctx); - } - `); - } - - const mountOrIntro = branches[0].hasIntroMethod ? 'i' : 'm'; - const initialMountNode = parentNode || '#target'; - const anchorNode = parentNode ? 'null' : 'anchor'; - - block.builders.mount.addLine( - `${if_current_block_type_index}${if_blocks}[${current_block_type_index}].${mountOrIntro}(${initialMountNode}, ${anchorNode});` - ); - - const updateMountNode = this.getUpdateMountNode(anchor); - - const destroyOldBlock = deindent` - @groupOutros(); - ${name}.o(function() { - ${if_blocks}[${previous_block_index}].d(1); - ${if_blocks}[${previous_block_index}] = null; - }); - `; - - const createNewBlock = deindent` - ${name} = ${if_blocks}[${current_block_type_index}]; - if (!${name}) { - ${name} = ${if_blocks}[${current_block_type_index}] = ${if_block_creators}[${current_block_type_index}](#component, ctx); - ${name}.c(); - } - ${name}.${mountOrIntro}(${updateMountNode}, ${anchor}); - `; - - const changeBlock = hasElse - ? deindent` - ${destroyOldBlock} - - ${createNewBlock} - ` - : deindent` - if (${name}) { - ${destroyOldBlock} - } - - if (~${current_block_type_index}) { - ${createNewBlock} - } else { - ${name} = null; - } - `; - - if (dynamic) { - block.builders.update.addBlock(deindent` - var ${previous_block_index} = ${current_block_type_index}; - ${current_block_type_index} = ${select_block_type}(ctx); - if (${current_block_type_index} === ${previous_block_index}) { - ${if_current_block_type_index}${if_blocks}[${current_block_type_index}].p(changed, ctx); - } else { - ${changeBlock} - } - `); - } else { - block.builders.update.addBlock(deindent` - var ${previous_block_index} = ${current_block_type_index}; - ${current_block_type_index} = ${select_block_type}(ctx); - if (${current_block_type_index} !== ${previous_block_index}) { - ${changeBlock} - } - `); - } - - block.builders.destroy.addLine(deindent` - ${if_current_block_type_index}${if_blocks}[${current_block_type_index}].d(${parentNode ? '' : 'detach'}); - `); - } - - buildSimple( - block: Block, - parentNode: string, - parentNodes: string, - branch, - dynamic, - { name, anchor, if_name } - ) { - block.builders.init.addBlock(deindent` - var ${name} = (${branch.condition}) && ${branch.block}(#component, ctx); - `); - - const mountOrIntro = branch.hasIntroMethod ? 'i' : 'm'; - const initialMountNode = parentNode || '#target'; - const anchorNode = parentNode ? 'null' : 'anchor'; - - block.builders.mount.addLine( - `if (${name}) ${name}.${mountOrIntro}(${initialMountNode}, ${anchorNode});` - ); - - const updateMountNode = this.getUpdateMountNode(anchor); - - const enter = dynamic - ? (branch.hasIntroMethod || branch.hasOutroMethod) - ? deindent` - if (${name}) { - ${name}.p(changed, ctx); - } else { - ${name} = ${branch.block}(#component, ctx); - if (${name}) ${name}.c(); - } - - ${name}.i(${updateMountNode}, ${anchor}); - ` - : deindent` - if (${name}) { - ${name}.p(changed, ctx); - } else { - ${name} = ${branch.block}(#component, ctx); - ${name}.c(); - ${name}.m(${updateMountNode}, ${anchor}); - } - ` - : (branch.hasIntroMethod || branch.hasOutroMethod) - ? deindent` - if (!${name}) { - ${name} = ${branch.block}(#component, ctx); - ${name}.c(); - } - ${name}.i(${updateMountNode}, ${anchor}); - ` - : deindent` - if (!${name}) { - ${name} = ${branch.block}(#component, ctx); - ${name}.c(); - ${name}.m(${updateMountNode}, ${anchor}); - } - `; - - // no `p()` here — we don't want to update outroing nodes, - // as that will typically result in glitching - const exit = branch.hasOutroMethod - ? deindent` - @groupOutros(); - ${name}.o(function() { - ${name}.d(1); - ${name} = null; - }); - ` - : deindent` - ${name}.d(1); - ${name} = null; - `; - - block.builders.update.addBlock(deindent` - if (${branch.condition}) { - ${enter} - } else if (${name}) { - ${exit} - } - `); - - block.builders.destroy.addLine(`${if_name}${name}.d(${parentNode ? '' : 'detach'});`); - } - - getBranches( - block: Block, - parentNode: string, - parentNodes: string, - node: IfBlock - ) { - const branches = [ - { - condition: node.expression.snippet, - block: node.block.name, - hasUpdateMethod: node.block.hasUpdateMethod, - hasIntroMethod: node.block.hasIntroMethod, - hasOutroMethod: node.block.hasOutroMethod, - }, - ]; - - this.visitChildren(block, node); - - if (isElseIf(node.else)) { - branches.push( - ...this.getBranches(block, parentNode, parentNodes, node.else.children[0]) - ); - } else { - branches.push({ - condition: null, - block: node.else ? node.else.block.name : null, - hasUpdateMethod: node.else ? node.else.block.hasUpdateMethod : false, - hasIntroMethod: node.else ? node.else.block.hasIntroMethod : false, - hasOutroMethod: node.else ? node.else.block.hasOutroMethod : false, - }); - - if (node.else) { - this.visitChildren(block, node.else); - } - } - - return branches; - } - - visitChildren(block: Block, node: Node) { - node.children.forEach((child: Node) => { - child.build(node.block, null, 'nodes'); - }); - } } \ No newline at end of file diff --git a/src/compile/render-dom/Block.ts b/src/compile/render-dom/Block.ts index 1766b41a66..9bc8f88db6 100644 --- a/src/compile/render-dom/Block.ts +++ b/src/compile/render-dom/Block.ts @@ -130,7 +130,7 @@ export default class Block { while (i--) { const wrapper = this.wrappers[i]; - if (!wrapper.var) return; + if (!wrapper.var) continue; if (dupes.has(wrapper.var)) { const i = counts.get(wrapper.var) || 0; diff --git a/src/compile/render-dom/index.ts b/src/compile/render-dom/index.ts index 2e07b51386..1b5606ae42 100644 --- a/src/compile/render-dom/index.ts +++ b/src/compile/render-dom/index.ts @@ -179,7 +179,7 @@ export default function dom( }];` )} - ${component.slots.size && `this._slotted = options.slots || {};`} + ${renderer.slots.size && `this._slotted = options.slots || {};`} ${component.customElement ? deindent` @@ -251,7 +251,7 @@ export default function dom( } `).join('\n\n')} - ${component.slots.size && deindent` + ${renderer.slots.size && deindent` connectedCallback() { Object.keys(this._slotted).forEach(key => { this.appendChild(this._slotted[key]); diff --git a/src/compile/render-dom/wrappers/Element/Binding/InputNumberBinding.ts b/src/compile/render-dom/wrappers/Element/Binding/InputNumberBinding.ts new file mode 100644 index 0000000000..c6c94c9010 --- /dev/null +++ b/src/compile/render-dom/wrappers/Element/Binding/InputNumberBinding.ts @@ -0,0 +1,36 @@ +import Binding from '../../../../nodes/Binding'; +import Element from '../../../../nodes/Element'; +import ElementWrapper from '..'; +import BindingWrapper from './Binding'; + +export default class InputNumberBinding extends BindingWrapper { + events = ['input']; + + static filter( + node: Element, + binding_lookup: Record, + type: string + ) { + return ( + node.name === 'input' && + type === 'number' && + binding_lookup.value + ); + } + + constructor( + element: ElementWrapper, + binding_lookup: Record + ) { + super(element, binding_lookup.value); + this.needsLock = true; + } + + fromDom() { + return `@toNumber(${this.element.var}.value)`; + } + + toDom() { + return `${this.element.var}.value = ${this.binding.value.snippet};`; + } +} \ No newline at end of file diff --git a/src/compile/render-dom/wrappers/Element/Binding/InputRangeBinding.ts b/src/compile/render-dom/wrappers/Element/Binding/InputRangeBinding.ts new file mode 100644 index 0000000000..78681d41b3 --- /dev/null +++ b/src/compile/render-dom/wrappers/Element/Binding/InputRangeBinding.ts @@ -0,0 +1,19 @@ +import InputNumberBinding from './InputNumberBinding'; +import Binding from '../../../../nodes/Binding'; +import Element from '../../../../nodes/Element'; + +export default class InputRangeBinding extends InputNumberBinding { + events = ['input', 'change']; + + static filter( + node: Element, + binding_lookup: Record, + type: string + ) { + return ( + node.name === 'input' && + type === 'range' && + binding_lookup.value + ); + } +} \ No newline at end of file diff --git a/src/compile/render-dom/wrappers/Element/index.ts b/src/compile/render-dom/wrappers/Element/index.ts index cf7434272c..a6d8acabee 100644 --- a/src/compile/render-dom/wrappers/Element/index.ts +++ b/src/compile/render-dom/wrappers/Element/index.ts @@ -15,6 +15,8 @@ import AttributeWrapper from './Attribute'; import StyleAttributeWrapper from './StyleAttribute'; import { dimensions } from '../../../../utils/patterns'; import InputCheckboxBinding from './Binding/InputCheckboxBinding'; +import InputNumberBinding from './Binding/InputNumberBinding'; +import InputRangeBinding from './Binding/InputRangeBinding'; import InputTextBinding from './Binding/InputTextBinding'; import InputRadioGroupBinding from './Binding/InputRadioGroupBinding'; import SelectBinding from './Binding/SelectBinding'; @@ -23,6 +25,8 @@ const bindings = [ InputTextBinding, InputRadioGroupBinding, InputCheckboxBinding, + InputNumberBinding, + InputRangeBinding, SelectBinding ]; @@ -119,6 +123,12 @@ export default class ElementWrapper extends Wrapper { this.classDependencies = []; this.attributes = this.node.attributes.map(attribute => { + if (attribute.name === 'slot') { + // TODO make separate subclass for this? + // TODO what about custom elements? + const owner = this.findNearest(/^InlineComponent/); + owner._slots.add(attribute.getStaticValue()); + } if (attribute.name === 'style') { return new StyleAttributeWrapper(attribute, this); } @@ -166,9 +176,16 @@ export default class ElementWrapper extends Wrapper { const slot = this.node.attributes.find((attribute: Node) => attribute.name === 'slot'); const prop = slot && quotePropIfNecessary(slot.chunks[0].data); - const initialMountNode = this.slotted ? - `${this.node.findNearest(/^InlineComponent/).var}._slotted${prop}` : // TODO this looks bonkers - parentNode; + + let initialMountNode; + + if (slot) { + // TODO what about custom elements? + const owner = this.findNearest(/^InlineComponent/); + initialMountNode = `${owner.var}._slotted${prop}`; + } else { + initialMountNode = parentNode; + } block.addVariable(node); const renderStatement = this.getRenderStatement(); diff --git a/src/compile/render-dom/wrappers/IfBlock.ts b/src/compile/render-dom/wrappers/IfBlock.ts index 53d2d77a3c..9e7afb2dfd 100644 --- a/src/compile/render-dom/wrappers/IfBlock.ts +++ b/src/compile/render-dom/wrappers/IfBlock.ts @@ -201,7 +201,7 @@ export default class IfBlockWrapper extends Wrapper { } this.branches.forEach(branch => { - branch.fragment.render(branch.block, parentNode, parentNodes); + branch.fragment.render(branch.block, null, 'nodes'); }); } diff --git a/src/compile/render-dom/wrappers/InlineComponent/index.ts b/src/compile/render-dom/wrappers/InlineComponent/index.ts index 11e1adaf03..89668f84e9 100644 --- a/src/compile/render-dom/wrappers/InlineComponent/index.ts +++ b/src/compile/render-dom/wrappers/InlineComponent/index.ts @@ -16,7 +16,7 @@ import getTailSnippet from '../../../../utils/getTailSnippet'; export default class InlineComponentWrapper extends Wrapper { var: string; - _slots: Set; // TODO lost the underscore + _slots: Set; // TODO lose the underscore node: InlineComponent; fragment: FragmentWrapper; diff --git a/src/compile/render-dom/wrappers/Slot.ts b/src/compile/render-dom/wrappers/Slot.ts index ba4dcb5a9a..723eb9d2a4 100644 --- a/src/compile/render-dom/wrappers/Slot.ts +++ b/src/compile/render-dom/wrappers/Slot.ts @@ -43,10 +43,9 @@ export default class SlotWrapper extends Wrapper { parentNodes: string ) { const { renderer } = this; - const { component } = renderer; const slotName = this.node.getStaticAttributeValue('name') || 'default'; - component.slots.add(slotName); + renderer.slots.add(slotName); const content_name = block.getUniqueName(`slot_content_${sanitize(slotName)}`); const prop = quotePropIfNecessary(slotName); diff --git a/src/compile/render-dom/wrappers/shared/Wrapper.ts b/src/compile/render-dom/wrappers/shared/Wrapper.ts index b45e62331c..cc59e2b6fd 100644 --- a/src/compile/render-dom/wrappers/shared/Wrapper.ts +++ b/src/compile/render-dom/wrappers/shared/Wrapper.ts @@ -42,6 +42,12 @@ export default class Wrapper { if (this.parent) this.parent.cannotUseInnerHTML(); } + // TODO do we still need equivalent method on Node? + findNearest(pattern) { + if (pattern.test(this.node.type)) return this; + return this.parent && this.parent.findNearest(pattern); + } + getOrCreateAnchor(block: Block, parentNode: string, parentNodes: string) { // TODO use this in EachBlock and IfBlock — tricky because // children need to be created first @@ -79,4 +85,8 @@ export default class Wrapper { render(block: Block, parentNode: string, parentNodes: string) { throw new Error(`render method not implemented by subclass ${this.node.type}`); } + + remount(name: string) { + return `${this.var}.m(${name}._slotted.default, null);`; + } } \ No newline at end of file