import deindent from '../../../../utils/deindent'; import visit from '../../visit'; import visitSlot from '../Slot'; import visitComponent from '../Component'; import visitWindow from './meta/Window'; import visitAttribute from './Attribute'; import visitEventHandler from './EventHandler'; import visitBinding from './Binding'; import visitRef from './Ref'; import * as namespaces from '../../../../utils/namespaces'; import getStaticAttributeValue from '../../../shared/getStaticAttributeValue'; import addTransitions from './addTransitions'; import { DomGenerator } from '../../index'; import Block from '../../Block'; import { Node } from '../../../../interfaces'; import { State } from '../../interfaces'; const meta = { ':Window': visitWindow, }; const order = { Attribute: 1, Binding: 2, EventHandler: 3, Ref: 4, }; const visitors = { Attribute: visitAttribute, EventHandler: visitEventHandler, Binding: visitBinding, Ref: visitRef, }; export default function visitElement( generator: DomGenerator, block: Block, state: State, node: Node, elementStack: Node[], componentStack: Node[] ) { if (node.name in meta) { return meta[node.name](generator, block, node); } if (node.name === 'slot') { if (generator.customElement) { const slotName = getStaticAttributeValue(node, 'name') || 'default'; generator.slots.add(slotName); } else { return visitSlot(generator, block, state, node, elementStack, componentStack); } } if (generator.components.has(node.name) || node.name === ':Self') { return visitComponent(generator, block, state, node, elementStack, componentStack); } const childState = node._state; const name = childState.parentNode; const slot = node.attributes.find((attribute: Node) => attribute.name === 'slot'); const parentNode = slot ? `${componentStack[componentStack.length - 1].var}._slotted.${slot.value[0].data}` : // TODO this looks bonkers state.parentNode; const isToplevel = !parentNode; block.addVariable(name); block.builders.create.addLine( `${name} = ${getRenderStatement( generator, childState.namespace, node.name )};` ); if (generator.hydratable) { block.builders.claim.addBlock(deindent` ${name} = ${getClaimStatement(generator, childState.namespace, state.parentNodes, node)}; var ${childState.parentNodes} = @children(${name}); `); } if (parentNode) { block.builders.mount.addLine( `@appendNode(${name}, ${parentNode});` ); } else { block.builders.mount.addLine(`@insertNode(${name}, #target, anchor);`); } // add CSS encapsulation attribute // TODO add a helper for this, rather than repeating it if (node._needsCssAttribute && !generator.customElement) { generator.needsEncapsulateHelper = true; block.builders.hydrate.addLine( `@encapsulateStyles(${name});` ); if (node._cssRefAttribute) { block.builders.hydrate.addLine( `@setAttribute(${name}, "svelte-ref-${node._cssRefAttribute}", ");` ) } } function visitAttributesAndAddProps() { let intro; let outro; node.attributes .sort((a: Node, b: Node) => order[a.type] - order[b.type]) .forEach((attribute: Node) => { if (attribute.type === 'Transition') { if (attribute.intro) intro = attribute; if (attribute.outro) outro = attribute; return; } visitors[attribute.type](generator, block, childState, node, attribute); }); if (intro || outro) addTransitions(generator, block, childState, node, intro, outro); if (childState.allUsedContexts.length || childState.usesComponent) { const initialProps: string[] = []; const updates: string[] = []; if (childState.usesComponent) { initialProps.push(`component: #component`); } childState.allUsedContexts.forEach((contextName: string) => { if (contextName === 'state') return; const listName = block.listNames.get(contextName); const indexName = block.indexNames.get(contextName); initialProps.push( `${listName}: ${listName},\n${indexName}: ${indexName}` ); updates.push( `${name}._svelte.${listName} = ${listName};\n${name}._svelte.${indexName} = ${indexName};` ); }); if (initialProps.length) { block.builders.hydrate.addBlock(deindent` ${name}._svelte = { ${initialProps.join(',\n')} }; `); } if (updates.length) { block.builders.update.addBlock(updates.join('\n')); } } } if (isToplevel) { // TODO we eventually need to consider what happens to elements // that belong to the same outgroup as an outroing element... block.builders.unmount.addLine(`@detachNode(${name});`); } if (node.name !== 'select') { if (node.name === 'textarea') { // this is an egregious hack, but it's the easiest way to get