|
|
@ -22,30 +22,71 @@ import mapChildren from './shared/mapChildren';
|
|
|
|
export default class Element extends Node {
|
|
|
|
export default class Element extends Node {
|
|
|
|
type: 'Element';
|
|
|
|
type: 'Element';
|
|
|
|
name: string;
|
|
|
|
name: string;
|
|
|
|
attributes: (Attribute | Binding | EventHandler | Ref | Transition | Action)[]; // TODO split these up sooner
|
|
|
|
attributes: Attribute[];
|
|
|
|
|
|
|
|
bindings: Binding[];
|
|
|
|
|
|
|
|
handlers: EventHandler[];
|
|
|
|
|
|
|
|
intro: Transition;
|
|
|
|
|
|
|
|
outro: Transition;
|
|
|
|
children: Node[];
|
|
|
|
children: Node[];
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
ref: string;
|
|
|
|
|
|
|
|
namespace: string;
|
|
|
|
|
|
|
|
|
|
|
|
constructor(compiler, parent, info: any) {
|
|
|
|
constructor(compiler, parent, info: any) {
|
|
|
|
super(compiler, parent, info);
|
|
|
|
super(compiler, parent, info);
|
|
|
|
this.name = info.name;
|
|
|
|
this.name = info.name;
|
|
|
|
this.children = mapChildren(compiler, parent, info.children);
|
|
|
|
|
|
|
|
|
|
|
|
const parentElement = parent.findNearest(/^Element/);
|
|
|
|
|
|
|
|
this.namespace = this.name === 'svg' ?
|
|
|
|
|
|
|
|
namespaces.svg :
|
|
|
|
|
|
|
|
parentElement ? parentElement.namespace : this.compiler.namespace;
|
|
|
|
|
|
|
|
|
|
|
|
this.attributes = [];
|
|
|
|
this.attributes = [];
|
|
|
|
// TODO bindings etc
|
|
|
|
this.bindings = [];
|
|
|
|
|
|
|
|
this.handlers = [];
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
this.intro = null;
|
|
|
|
|
|
|
|
this.outro = null;
|
|
|
|
|
|
|
|
|
|
|
|
info.attributes.forEach(node => {
|
|
|
|
info.attributes.forEach(node => {
|
|
|
|
switch (node.type) {
|
|
|
|
switch (node.type) {
|
|
|
|
case 'Attribute':
|
|
|
|
case 'Attribute':
|
|
|
|
case 'Spread':
|
|
|
|
// special case
|
|
|
|
|
|
|
|
if (node.name === 'xmlns') this.namespace = node.value[0].data;
|
|
|
|
|
|
|
|
|
|
|
|
this.attributes.push(new Attribute(compiler, this, node));
|
|
|
|
this.attributes.push(new Attribute(compiler, this, node));
|
|
|
|
break;
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
case 'Binding':
|
|
|
|
|
|
|
|
this.bindings.push(new Binding(compiler, this, node));
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
case 'EventHandler':
|
|
|
|
|
|
|
|
this.handlers.push(new EventHandler(compiler, this, node));
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
case 'Transition':
|
|
|
|
|
|
|
|
const transition = new Transition(compiler, this, node);
|
|
|
|
|
|
|
|
if (node.intro) this.intro = transition;
|
|
|
|
|
|
|
|
if (node.outro) this.outro = transition;
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
case 'Ref':
|
|
|
|
|
|
|
|
// TODO catch this in validation
|
|
|
|
|
|
|
|
if (this.ref) throw new Error(`Duplicate refs`);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
compiler.usesRefs = true
|
|
|
|
|
|
|
|
this.ref = node.name;
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
|
|
default:
|
|
|
|
default:
|
|
|
|
throw new Error(`Not implemented: ${node.type}`);
|
|
|
|
throw new Error(`Not implemented: ${node.type}`);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// TODO break out attributes and directives here
|
|
|
|
// TODO break out attributes and directives here
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
this.children = mapChildren(compiler, this, info.children);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
init(
|
|
|
|
init(
|
|
|
@ -57,61 +98,53 @@ export default class Element extends Node {
|
|
|
|
this.cannotUseInnerHTML();
|
|
|
|
this.cannotUseInnerHTML();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const parentElement = this.parent && this.parent.findNearest(/^Element/);
|
|
|
|
this.attributes.forEach(attr => {
|
|
|
|
this.namespace = this.name === 'svg' ?
|
|
|
|
if (attr.dependencies.size) {
|
|
|
|
namespaces.svg :
|
|
|
|
this.parent.cannotUseInnerHTML();
|
|
|
|
parentElement ? parentElement.namespace : this.generator.namespace;
|
|
|
|
block.addDependencies(attr.dependencies);
|
|
|
|
|
|
|
|
|
|
|
|
this.attributes.forEach(attribute => {
|
|
|
|
|
|
|
|
if (attribute.type === 'Attribute' && attribute.value !== true) {
|
|
|
|
|
|
|
|
// special case — xmlns
|
|
|
|
|
|
|
|
if (attribute.name === 'xmlns') {
|
|
|
|
|
|
|
|
// TODO this attribute must be static – enforce at compile time
|
|
|
|
|
|
|
|
this.namespace = attribute.value[0].data;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
attribute.value.forEach((chunk: Node) => {
|
|
|
|
// special case — <option value={foo}> — see below
|
|
|
|
if (chunk.type !== 'Text') {
|
|
|
|
if (this.name === 'option' && attr.name === 'value') {
|
|
|
|
if (this.parent) this.parent.cannotUseInnerHTML();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const dependencies = chunk.metadata.dependencies;
|
|
|
|
|
|
|
|
block.addDependencies(dependencies);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// special case — <option value='{{foo}}'> — see below
|
|
|
|
|
|
|
|
if (
|
|
|
|
|
|
|
|
this.name === 'option' &&
|
|
|
|
|
|
|
|
attribute.name === 'value'
|
|
|
|
|
|
|
|
) {
|
|
|
|
|
|
|
|
let select = this.parent;
|
|
|
|
let select = this.parent;
|
|
|
|
while (select && (select.type !== 'Element' || select.name !== 'select')) select = select.parent;
|
|
|
|
while (select && (select.type !== 'Element' || select.name !== 'select')) select = select.parent;
|
|
|
|
|
|
|
|
|
|
|
|
if (select && select.selectBindingDependencies) {
|
|
|
|
if (select && select.selectBindingDependencies) {
|
|
|
|
select.selectBindingDependencies.forEach(prop => {
|
|
|
|
select.selectBindingDependencies.forEach(prop => {
|
|
|
|
dependencies.forEach((dependency: string) => {
|
|
|
|
dependencies.forEach((dependency: string) => {
|
|
|
|
this.generator.indirectDependencies.get(prop).add(dependency);
|
|
|
|
this.compiler.indirectDependencies.get(prop).add(dependency);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
});
|
|
|
|
} else {
|
|
|
|
|
|
|
|
if (this.parent) this.parent.cannotUseInnerHTML();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (attribute.type === 'EventHandler' && attribute.expression) {
|
|
|
|
this.bindings.forEach(binding => {
|
|
|
|
attribute.expression.arguments.forEach((arg: Node) => {
|
|
|
|
this.cannotUseInnerHTML();
|
|
|
|
block.addDependencies(arg.metadata.dependencies);
|
|
|
|
block.addDependencies(binding.value.dependencies);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
} else if (attribute.type === 'Binding') {
|
|
|
|
|
|
|
|
block.addDependencies(attribute.metadata.dependencies);
|
|
|
|
this.handlers.forEach(handler => {
|
|
|
|
} else if (attribute.type === 'Transition') {
|
|
|
|
this.cannotUseInnerHTML();
|
|
|
|
if (attribute.intro)
|
|
|
|
block.addDependencies(handler.dependencies);
|
|
|
|
this.generator.hasIntroTransitions = block.hasIntroMethod = true;
|
|
|
|
});
|
|
|
|
if (attribute.outro) {
|
|
|
|
|
|
|
|
this.generator.hasOutroTransitions = block.hasOutroMethod = true;
|
|
|
|
if (this.intro) {
|
|
|
|
|
|
|
|
this.compiler.hasIntroTransitions = block.hasIntroMethod = true;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (this.outro) {
|
|
|
|
|
|
|
|
this.compiler.hasOutroTransitions = block.hasOutroMethod = true;
|
|
|
|
block.outros += 1;
|
|
|
|
block.outros += 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else if (attribute.type === 'Action' && attribute.expression) {
|
|
|
|
|
|
|
|
|
|
|
|
this.attributes.forEach(attribute => {
|
|
|
|
|
|
|
|
if (attribute.type === 'Attribute' && attribute.value !== true) {
|
|
|
|
|
|
|
|
// removed
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
if (this.parent) this.parent.cannotUseInnerHTML();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (attribute.type === 'Action' && attribute.expression) {
|
|
|
|
block.addDependencies(attribute.metadata.dependencies);
|
|
|
|
block.addDependencies(attribute.metadata.dependencies);
|
|
|
|
} else if (attribute.type === 'Spread') {
|
|
|
|
} else if (attribute.type === 'Spread') {
|
|
|
|
block.addDependencies(attribute.metadata.dependencies);
|
|
|
|
block.addDependencies(attribute.metadata.dependencies);
|
|
|
@ -125,11 +158,9 @@ export default class Element extends Node {
|
|
|
|
// this is an egregious hack, but it's the easiest way to get <textarea>
|
|
|
|
// this is an egregious hack, but it's the easiest way to get <textarea>
|
|
|
|
// children treated the same way as a value attribute
|
|
|
|
// children treated the same way as a value attribute
|
|
|
|
if (this.children.length > 0) {
|
|
|
|
if (this.children.length > 0) {
|
|
|
|
this.attributes.push(new Attribute({
|
|
|
|
this.attributes.push(new Attribute(this.compiler, this, {
|
|
|
|
generator: this.generator,
|
|
|
|
|
|
|
|
name: 'value',
|
|
|
|
name: 'value',
|
|
|
|
value: this.children,
|
|
|
|
value: this.children
|
|
|
|
parent: this
|
|
|
|
|
|
|
|
}));
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
|
|
this.children = [];
|
|
|
|
this.children = [];
|
|
|
@ -153,7 +184,7 @@ export default class Element extends Node {
|
|
|
|
const dependencies = binding.metadata.dependencies;
|
|
|
|
const dependencies = binding.metadata.dependencies;
|
|
|
|
this.selectBindingDependencies = dependencies;
|
|
|
|
this.selectBindingDependencies = dependencies;
|
|
|
|
dependencies.forEach((prop: string) => {
|
|
|
|
dependencies.forEach((prop: string) => {
|
|
|
|
this.generator.indirectDependencies.set(prop, new Set());
|
|
|
|
this.compiler.indirectDependencies.set(prop, new Set());
|
|
|
|
});
|
|
|
|
});
|
|
|
|
} else {
|
|
|
|
} else {
|
|
|
|
this.selectBindingDependencies = null;
|
|
|
|
this.selectBindingDependencies = null;
|
|
|
@ -188,11 +219,11 @@ export default class Element extends Node {
|
|
|
|
parentNode: string,
|
|
|
|
parentNode: string,
|
|
|
|
parentNodes: string
|
|
|
|
parentNodes: string
|
|
|
|
) {
|
|
|
|
) {
|
|
|
|
const { generator } = this;
|
|
|
|
const { compiler } = this;
|
|
|
|
|
|
|
|
|
|
|
|
if (this.name === 'slot') {
|
|
|
|
if (this.name === 'slot') {
|
|
|
|
const slotName = this.getStaticAttributeValue('name') || 'default';
|
|
|
|
const slotName = this.getStaticAttributeValue('name') || 'default';
|
|
|
|
this.generator.slots.add(slotName);
|
|
|
|
this.compiler.slots.add(slotName);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (this.name === 'noscript') return;
|
|
|
|
if (this.name === 'noscript') return;
|
|
|
@ -211,15 +242,15 @@ export default class Element extends Node {
|
|
|
|
parentNode;
|
|
|
|
parentNode;
|
|
|
|
|
|
|
|
|
|
|
|
block.addVariable(name);
|
|
|
|
block.addVariable(name);
|
|
|
|
const renderStatement = getRenderStatement(this.generator, this.namespace, this.name);
|
|
|
|
const renderStatement = getRenderStatement(this.compiler, this.namespace, this.name);
|
|
|
|
block.builders.create.addLine(
|
|
|
|
block.builders.create.addLine(
|
|
|
|
`${name} = ${renderStatement};`
|
|
|
|
`${name} = ${renderStatement};`
|
|
|
|
);
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
if (this.generator.hydratable) {
|
|
|
|
if (this.compiler.hydratable) {
|
|
|
|
if (parentNodes) {
|
|
|
|
if (parentNodes) {
|
|
|
|
block.builders.claim.addBlock(deindent`
|
|
|
|
block.builders.claim.addBlock(deindent`
|
|
|
|
${name} = ${getClaimStatement(generator, this.namespace, parentNodes, this)};
|
|
|
|
${name} = ${getClaimStatement(compiler, this.namespace, parentNodes, this)};
|
|
|
|
var ${childState.parentNodes} = @children(${name});
|
|
|
|
var ${childState.parentNodes} = @children(${name});
|
|
|
|
`);
|
|
|
|
`);
|
|
|
|
} else {
|
|
|
|
} else {
|
|
|
@ -271,7 +302,7 @@ export default class Element extends Node {
|
|
|
|
|
|
|
|
|
|
|
|
this.addBindings(block, allUsedContexts);
|
|
|
|
this.addBindings(block, allUsedContexts);
|
|
|
|
const eventHandlerUsesComponent = this.addEventHandlers(block, allUsedContexts);
|
|
|
|
const eventHandlerUsesComponent = this.addEventHandlers(block, allUsedContexts);
|
|
|
|
this.addRefs(block);
|
|
|
|
if (this.ref) this.addRef(block);
|
|
|
|
this.addAttributes(block);
|
|
|
|
this.addAttributes(block);
|
|
|
|
this.addTransitions(block);
|
|
|
|
this.addTransitions(block);
|
|
|
|
this.addActions(block);
|
|
|
|
this.addActions(block);
|
|
|
@ -353,14 +384,14 @@ export default class Element extends Node {
|
|
|
|
block: Block,
|
|
|
|
block: Block,
|
|
|
|
allUsedContexts: Set<string>
|
|
|
|
allUsedContexts: Set<string>
|
|
|
|
) {
|
|
|
|
) {
|
|
|
|
const bindings: Binding[] = this.attributes.filter((a: Binding) => a.type === 'Binding');
|
|
|
|
if (this.bindings.length === 0) return;
|
|
|
|
if (bindings.length === 0) return;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (this.name === 'select' || this.isMediaNode()) this.generator.hasComplexBindings = true;
|
|
|
|
if (this.name === 'select' || this.isMediaNode()) this.compiler.hasComplexBindings = true;
|
|
|
|
|
|
|
|
|
|
|
|
const needsLock = this.name !== 'input' || !/radio|checkbox|range|color/.test(this.getStaticAttributeValue('type'));
|
|
|
|
const needsLock = this.name !== 'input' || !/radio|checkbox|range|color/.test(this.getStaticAttributeValue('type'));
|
|
|
|
|
|
|
|
|
|
|
|
const mungedBindings = bindings.map(binding => binding.munge(block, allUsedContexts));
|
|
|
|
// TODO munge in constructor
|
|
|
|
|
|
|
|
const mungedBindings = this.bindings.map(binding => binding.munge(block, allUsedContexts));
|
|
|
|
|
|
|
|
|
|
|
|
const lock = mungedBindings.some(binding => binding.needsLock) ?
|
|
|
|
const lock = mungedBindings.some(binding => binding.needsLock) ?
|
|
|
|
block.getUniqueName(`${this.var}_updating`) :
|
|
|
|
block.getUniqueName(`${this.var}_updating`) :
|
|
|
@ -452,7 +483,7 @@ export default class Element extends Node {
|
|
|
|
.join(' && ');
|
|
|
|
.join(' && ');
|
|
|
|
|
|
|
|
|
|
|
|
if (this.name === 'select' || group.bindings.find(binding => binding.name === 'indeterminate' || binding.isReadOnlyMediaAttribute)) {
|
|
|
|
if (this.name === 'select' || group.bindings.find(binding => binding.name === 'indeterminate' || binding.isReadOnlyMediaAttribute)) {
|
|
|
|
this.generator.hasComplexBindings = true;
|
|
|
|
this.compiler.hasComplexBindings = true;
|
|
|
|
|
|
|
|
|
|
|
|
block.builders.hydrate.addLine(
|
|
|
|
block.builders.hydrate.addLine(
|
|
|
|
`if (!(${allInitialStateIsDefined})) #component.root._beforecreate.push(${handler});`
|
|
|
|
`if (!(${allInitialStateIsDefined})) #component.root._beforecreate.push(${handler});`
|
|
|
@ -469,7 +500,7 @@ export default class Element extends Node {
|
|
|
|
return;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
this.attributes.filter((a: Attribute) => a.type === 'Attribute').forEach((attribute: Attribute) => {
|
|
|
|
this.attributes.forEach((attribute: Attribute) => {
|
|
|
|
attribute.render(block);
|
|
|
|
attribute.render(block);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
@ -528,89 +559,58 @@ export default class Element extends Node {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
addEventHandlers(block: Block, allUsedContexts) {
|
|
|
|
addEventHandlers(block: Block, allUsedContexts) {
|
|
|
|
const { generator } = this;
|
|
|
|
const { compiler } = this;
|
|
|
|
let eventHandlerUsesComponent = false;
|
|
|
|
let eventHandlerUsesComponent = false;
|
|
|
|
|
|
|
|
|
|
|
|
this.attributes.filter((a: EventHandler) => a.type === 'EventHandler').forEach((attribute: EventHandler) => {
|
|
|
|
this.handlers.forEach(handler => {
|
|
|
|
const isCustomEvent = generator.events.has(attribute.name);
|
|
|
|
const isCustomEvent = compiler.events.has(handler.name);
|
|
|
|
const shouldHoist = !isCustomEvent && this.hasAncestor('EachBlock');
|
|
|
|
const shouldHoist = !isCustomEvent && this.hasAncestor('EachBlock');
|
|
|
|
|
|
|
|
|
|
|
|
const context = shouldHoist ? null : this.var;
|
|
|
|
const context = shouldHoist ? null : this.var;
|
|
|
|
const usedContexts: string[] = [];
|
|
|
|
const usedContexts: string[] = [];
|
|
|
|
|
|
|
|
|
|
|
|
if (attribute.expression) {
|
|
|
|
if (handler.callee) {
|
|
|
|
generator.addSourcemapLocations(attribute.expression);
|
|
|
|
handler.render(this.compiler, block);
|
|
|
|
|
|
|
|
|
|
|
|
const flattened = flattenReference(attribute.expression.callee);
|
|
|
|
|
|
|
|
if (!validCalleeObjects.has(flattened.name)) {
|
|
|
|
|
|
|
|
// allow event.stopPropagation(), this.select() etc
|
|
|
|
|
|
|
|
// TODO verify that it's a valid callee (i.e. built-in or declared method)
|
|
|
|
|
|
|
|
if (flattened.name[0] === '$' && !generator.methods.has(flattened.name)) {
|
|
|
|
|
|
|
|
generator.code.overwrite(
|
|
|
|
|
|
|
|
attribute.expression.start,
|
|
|
|
|
|
|
|
attribute.expression.start + 1,
|
|
|
|
|
|
|
|
`${block.alias('component')}.store.`
|
|
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
generator.code.prependRight(
|
|
|
|
|
|
|
|
attribute.expression.start,
|
|
|
|
|
|
|
|
`${block.alias('component')}.`
|
|
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (!validCalleeObjects.has(handler.callee.name)) {
|
|
|
|
if (shouldHoist) eventHandlerUsesComponent = true; // this feels a bit hacky but it works!
|
|
|
|
if (shouldHoist) eventHandlerUsesComponent = true; // this feels a bit hacky but it works!
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
attribute.expression.arguments.forEach((arg: Node) => {
|
|
|
|
// handler.expression.arguments.forEach((arg: Node) => {
|
|
|
|
const { contexts } = block.contextualise(arg, context, true);
|
|
|
|
// const { contexts } = block.contextualise(arg, context, true);
|
|
|
|
|
|
|
|
|
|
|
|
contexts.forEach(context => {
|
|
|
|
// contexts.forEach(context => {
|
|
|
|
if (!~usedContexts.indexOf(context)) usedContexts.push(context);
|
|
|
|
// if (!~usedContexts.indexOf(context)) usedContexts.push(context);
|
|
|
|
allUsedContexts.add(context);
|
|
|
|
// allUsedContexts.add(context);
|
|
|
|
});
|
|
|
|
// });
|
|
|
|
});
|
|
|
|
// });
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const ctx = context || 'this';
|
|
|
|
const ctx = context || 'this';
|
|
|
|
const declarations = usedContexts
|
|
|
|
|
|
|
|
.map(name => {
|
|
|
|
|
|
|
|
if (name === 'state') {
|
|
|
|
|
|
|
|
if (shouldHoist) eventHandlerUsesComponent = true;
|
|
|
|
|
|
|
|
return `var state = ${block.alias('component')}.get();`;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const contextType = block.contextTypes.get(name);
|
|
|
|
|
|
|
|
if (contextType === 'each') {
|
|
|
|
|
|
|
|
const listName = block.listNames.get(name);
|
|
|
|
|
|
|
|
const indexName = block.indexNames.get(name);
|
|
|
|
|
|
|
|
const contextName = block.contexts.get(name);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return `var ${listName} = ${ctx}._svelte.${listName}, ${indexName} = ${ctx}._svelte.${indexName}, ${contextName} = ${listName}[${indexName}];`;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
.filter(Boolean);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// get a name for the event handler that is globally unique
|
|
|
|
// get a name for the event handler that is globally unique
|
|
|
|
// if hoisted, locally unique otherwise
|
|
|
|
// if hoisted, locally unique otherwise
|
|
|
|
const handlerName = (shouldHoist ? generator : block).getUniqueName(
|
|
|
|
const handlerName = (shouldHoist ? compiler : block).getUniqueName(
|
|
|
|
`${attribute.name.replace(/[^a-zA-Z0-9_$]/g, '_')}_handler`
|
|
|
|
`${handler.name.replace(/[^a-zA-Z0-9_$]/g, '_')}_handler`
|
|
|
|
);
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const component = block.alias('component'); // can't use #component, might be hoisted
|
|
|
|
|
|
|
|
|
|
|
|
// create the handler body
|
|
|
|
// create the handler body
|
|
|
|
const handlerBody = deindent`
|
|
|
|
const handlerBody = deindent`
|
|
|
|
${eventHandlerUsesComponent &&
|
|
|
|
${eventHandlerUsesComponent &&
|
|
|
|
`var ${block.alias('component')} = ${ctx}._svelte.component;`}
|
|
|
|
`var #component = ${ctx}._svelte.component;`}
|
|
|
|
${declarations}
|
|
|
|
${handler.dependencies.size > 0 && `const ctx = #component.get();`}
|
|
|
|
${attribute.expression ?
|
|
|
|
${handler.snippet ?
|
|
|
|
`[✂${attribute.expression.start}-${attribute.expression.end}✂];` :
|
|
|
|
handler.snippet :
|
|
|
|
`${block.alias('component')}.fire("${attribute.name}", event);`}
|
|
|
|
`#component.fire("${handler.name}", event);`}
|
|
|
|
`;
|
|
|
|
`;
|
|
|
|
|
|
|
|
|
|
|
|
if (isCustomEvent) {
|
|
|
|
if (isCustomEvent) {
|
|
|
|
block.addVariable(handlerName);
|
|
|
|
block.addVariable(handlerName);
|
|
|
|
|
|
|
|
|
|
|
|
block.builders.hydrate.addBlock(deindent`
|
|
|
|
block.builders.hydrate.addBlock(deindent`
|
|
|
|
${handlerName} = %events-${attribute.name}.call(#component, ${this.var}, function(event) {
|
|
|
|
${handlerName} = %events-${handler.name}.call(#component, ${this.var}, function(event) {
|
|
|
|
${handlerBody}
|
|
|
|
${handlerBody}
|
|
|
|
});
|
|
|
|
});
|
|
|
|
`);
|
|
|
|
`);
|
|
|
@ -619,34 +619,32 @@ export default class Element extends Node {
|
|
|
|
${handlerName}.destroy();
|
|
|
|
${handlerName}.destroy();
|
|
|
|
`);
|
|
|
|
`);
|
|
|
|
} else {
|
|
|
|
} else {
|
|
|
|
const handler = deindent`
|
|
|
|
const handlerFunction = deindent`
|
|
|
|
function ${handlerName}(event) {
|
|
|
|
function ${handlerName}(event) {
|
|
|
|
${handlerBody}
|
|
|
|
${handlerBody}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
`;
|
|
|
|
`;
|
|
|
|
|
|
|
|
|
|
|
|
if (shouldHoist) {
|
|
|
|
if (shouldHoist) {
|
|
|
|
generator.blocks.push(handler);
|
|
|
|
compiler.blocks.push(handlerFunction);
|
|
|
|
} else {
|
|
|
|
} else {
|
|
|
|
block.builders.init.addBlock(handler);
|
|
|
|
block.builders.init.addBlock(handlerFunction);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
block.builders.hydrate.addLine(
|
|
|
|
block.builders.hydrate.addLine(
|
|
|
|
`@addListener(${this.var}, "${attribute.name}", ${handlerName});`
|
|
|
|
`@addListener(${this.var}, "${handler.name}", ${handlerName});`
|
|
|
|
);
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
block.builders.destroy.addLine(
|
|
|
|
block.builders.destroy.addLine(
|
|
|
|
`@removeListener(${this.var}, "${attribute.name}", ${handlerName});`
|
|
|
|
`@removeListener(${this.var}, "${handler.name}", ${handlerName});`
|
|
|
|
);
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
});
|
|
|
|
return eventHandlerUsesComponent;
|
|
|
|
return eventHandlerUsesComponent;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
addRefs(block: Block) {
|
|
|
|
addRef(block: Block) {
|
|
|
|
// TODO it should surely be an error to have more than one ref
|
|
|
|
const ref = `#component.refs.${this.ref}`;
|
|
|
|
this.attributes.filter((a: Ref) => a.type === 'Ref').forEach((attribute: Ref) => {
|
|
|
|
|
|
|
|
const ref = `#component.refs.${attribute.name}`;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
block.builders.mount.addLine(
|
|
|
|
block.builders.mount.addLine(
|
|
|
|
`${ref} = ${this.var};`
|
|
|
|
`${ref} = ${this.var};`
|
|
|
@ -655,25 +653,19 @@ export default class Element extends Node {
|
|
|
|
block.builders.destroy.addLine(
|
|
|
|
block.builders.destroy.addLine(
|
|
|
|
`if (${ref} === ${this.var}) ${ref} = null;`
|
|
|
|
`if (${ref} === ${this.var}) ${ref} = null;`
|
|
|
|
);
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
this.generator.usesRefs = true; // so component.refs object is created
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
addTransitions(
|
|
|
|
addTransitions(
|
|
|
|
block: Block
|
|
|
|
block: Block
|
|
|
|
) {
|
|
|
|
) {
|
|
|
|
const intro = this.attributes.find((a: Transition) => a.type === 'Transition' && a.intro);
|
|
|
|
const { intro, outro } = this;
|
|
|
|
const outro = this.attributes.find((a: Transition) => a.type === 'Transition' && a.outro);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (!intro && !outro) return;
|
|
|
|
if (!intro && !outro) return;
|
|
|
|
|
|
|
|
|
|
|
|
if (intro === outro) {
|
|
|
|
if (intro === outro) {
|
|
|
|
block.contextualise(intro.expression); // TODO remove all these
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const name = block.getUniqueName(`${this.var}_transition`);
|
|
|
|
const name = block.getUniqueName(`${this.var}_transition`);
|
|
|
|
const snippet = intro.expression
|
|
|
|
const snippet = intro.expression
|
|
|
|
? intro.metadata.snippet
|
|
|
|
? intro.expression.snippet
|
|
|
|
: '{}';
|
|
|
|
: '{}';
|
|
|
|
|
|
|
|
|
|
|
|
block.addVariable(name);
|
|
|
|
block.addVariable(name);
|
|
|
@ -701,11 +693,9 @@ export default class Element extends Node {
|
|
|
|
const outroName = outro && block.getUniqueName(`${this.var}_outro`);
|
|
|
|
const outroName = outro && block.getUniqueName(`${this.var}_outro`);
|
|
|
|
|
|
|
|
|
|
|
|
if (intro) {
|
|
|
|
if (intro) {
|
|
|
|
block.contextualise(intro.expression);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
block.addVariable(introName);
|
|
|
|
block.addVariable(introName);
|
|
|
|
const snippet = intro.expression
|
|
|
|
const snippet = intro.expression
|
|
|
|
? intro.metadata.snippet
|
|
|
|
? intro.expression.snippet
|
|
|
|
: '{}';
|
|
|
|
: '{}';
|
|
|
|
|
|
|
|
|
|
|
|
const fn = `%transitions-${intro.name}`; // TODO add built-in transitions?
|
|
|
|
const fn = `%transitions-${intro.name}`; // TODO add built-in transitions?
|
|
|
@ -728,11 +718,9 @@ export default class Element extends Node {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (outro) {
|
|
|
|
if (outro) {
|
|
|
|
block.contextualise(outro.expression);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
block.addVariable(outroName);
|
|
|
|
block.addVariable(outroName);
|
|
|
|
const snippet = outro.expression
|
|
|
|
const snippet = outro.expression
|
|
|
|
? outro.metadata.snippet
|
|
|
|
? outro.expression.snippet
|
|
|
|
: '{}';
|
|
|
|
: '{}';
|
|
|
|
|
|
|
|
|
|
|
|
const fn = `%transitions-${outro.name}`;
|
|
|
|
const fn = `%transitions-${outro.name}`;
|
|
|
@ -755,7 +743,7 @@ export default class Element extends Node {
|
|
|
|
const { expression } = attribute;
|
|
|
|
const { expression } = attribute;
|
|
|
|
let snippet, dependencies;
|
|
|
|
let snippet, dependencies;
|
|
|
|
if (expression) {
|
|
|
|
if (expression) {
|
|
|
|
this.generator.addSourcemapLocations(expression);
|
|
|
|
this.compiler.addSourcemapLocations(expression);
|
|
|
|
block.contextualise(expression);
|
|
|
|
block.contextualise(expression);
|
|
|
|
snippet = attribute.metadata.snippet;
|
|
|
|
snippet = attribute.metadata.snippet;
|
|
|
|
dependencies = attribute.metadata.dependencies;
|
|
|
|
dependencies = attribute.metadata.dependencies;
|
|
|
@ -823,18 +811,18 @@ export default class Element extends Node {
|
|
|
|
const classAttribute = this.attributes.find(a => a.name === 'class');
|
|
|
|
const classAttribute = this.attributes.find(a => a.name === 'class');
|
|
|
|
if (classAttribute && classAttribute.value !== true) {
|
|
|
|
if (classAttribute && classAttribute.value !== true) {
|
|
|
|
if (classAttribute.value.length === 1 && classAttribute.value[0].type === 'Text') {
|
|
|
|
if (classAttribute.value.length === 1 && classAttribute.value[0].type === 'Text') {
|
|
|
|
classAttribute.value[0].data += ` ${this.generator.stylesheet.id}`;
|
|
|
|
classAttribute.value[0].data += ` ${this.compiler.stylesheet.id}`;
|
|
|
|
} else {
|
|
|
|
} else {
|
|
|
|
(<Node[]>classAttribute.value).push(
|
|
|
|
(<Node[]>classAttribute.value).push(
|
|
|
|
new Node({ type: 'Text', data: ` ${this.generator.stylesheet.id}` })
|
|
|
|
new Node({ type: 'Text', data: ` ${this.compiler.stylesheet.id}` })
|
|
|
|
);
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
} else {
|
|
|
|
this.attributes.push(
|
|
|
|
this.attributes.push(
|
|
|
|
new Attribute({
|
|
|
|
new Attribute({
|
|
|
|
generator: this.generator,
|
|
|
|
compiler: this.compiler,
|
|
|
|
name: 'class',
|
|
|
|
name: 'class',
|
|
|
|
value: [new Node({ type: 'Text', data: `${this.generator.stylesheet.id}` })],
|
|
|
|
value: [new Node({ type: 'Text', data: `${this.compiler.stylesheet.id}` })],
|
|
|
|
parent: this,
|
|
|
|
parent: this,
|
|
|
|
})
|
|
|
|
})
|
|
|
|
);
|
|
|
|
);
|
|
|
@ -843,7 +831,7 @@ export default class Element extends Node {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function getRenderStatement(
|
|
|
|
function getRenderStatement(
|
|
|
|
generator: DomGenerator,
|
|
|
|
compiler: DomGenerator,
|
|
|
|
namespace: string,
|
|
|
|
namespace: string,
|
|
|
|
name: string
|
|
|
|
name: string
|
|
|
|
) {
|
|
|
|
) {
|
|
|
@ -859,7 +847,7 @@ function getRenderStatement(
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function getClaimStatement(
|
|
|
|
function getClaimStatement(
|
|
|
|
generator: DomGenerator,
|
|
|
|
compiler: DomGenerator,
|
|
|
|
namespace: string,
|
|
|
|
namespace: string,
|
|
|
|
nodes: string,
|
|
|
|
nodes: string,
|
|
|
|
node: Node
|
|
|
|
node: Node
|
|
|
|