Merge branch 'master' into gh-908

pull/924/head
Rich Harris 8 years ago
commit 1643814c93

@ -1,5 +1,19 @@
# Svelte changelog
## 1.42.0
* Implement `indeterminate` binding for checkbox inputs ([#910](https://github.com/sveltejs/svelte/issues/910))
* Use `<option>` children as `value` attribute if none exists ([#928](https://github.com/sveltejs/svelte/issues/928))
* Allow quoted property names in default export and sub-properties ([#914](https://github.com/sveltejs/svelte/issues/914))
* Various improvements to generated code for bindings
## 1.41.4
* Handle self-destructive bindings ([#917](https://github.com/sveltejs/svelte/issues/917))
* Prevent `innerHTML` with `<option>` elements ([#915](https://github.com/sveltejs/svelte/issues/915))
* Use `dataset` unless `legacy` is true ([#858](https://github.com/sveltejs/svelte/issues/858))
* Add `prepare` script to facilitate installing from git ([#923](https://github.com/sveltejs/svelte/pull/923))
## 1.41.3
* Prevent argument name clashes ([#911](https://github.com/sveltejs/svelte/issues/911))

@ -1,6 +1,6 @@
{
"name": "svelte",
"version": "1.41.3",
"version": "1.42.0",
"description": "The magical disappearing UI framework",
"main": "compiler/svelte.js",
"files": [
@ -18,6 +18,7 @@
"precodecov": "npm run coverage",
"lint": "eslint src test/*.js",
"build": "node src/shared/_build.js && rollup -c",
"prepare": "npm run build",
"dev": "node src/shared/_build.js && rollup -c -w",
"pretest": "npm run build",
"prepublishOnly": "npm run lint && npm test",

@ -12,6 +12,7 @@ import namespaces from '../utils/namespaces';
import { removeNode, removeObjectKey } from '../utils/removeNode';
import wrapModule from './shared/utils/wrapModule';
import annotateWithScopes from '../utils/annotateWithScopes';
import getName from '../utils/getName';
import clone from '../utils/clone';
import DomBlock from './dom/Block';
import SsrBlock from './server-side-rendering/Block';
@ -497,13 +498,13 @@ export default class Generator {
if (defaultExport) {
defaultExport.declaration.properties.forEach((prop: Node) => {
templateProperties[prop.key.name] = prop;
templateProperties[getName(prop.key)] = prop;
});
['helpers', 'events', 'components', 'transitions'].forEach(key => {
if (templateProperties[key]) {
templateProperties[key].value.properties.forEach((prop: Node) => {
this[key].add(prop.key.name);
this[key].add(getName(prop.key));
});
}
});
@ -574,7 +575,7 @@ export default class Generator {
if (templateProperties.components) {
templateProperties.components.value.properties.forEach((property: Node) => {
addDeclaration(property.key.name, property.value, 'components');
addDeclaration(getName(property.key), property.value, 'components');
});
}
@ -582,7 +583,7 @@ export default class Generator {
const dependencies = new Map();
templateProperties.computed.value.properties.forEach((prop: Node) => {
const key = prop.key.name;
const key = getName(prop.key);
const value = prop.value;
const deps = value.params.map(
@ -605,12 +606,12 @@ export default class Generator {
computations.push({ key, deps });
const prop = templateProperties.computed.value.properties.find((prop: Node) => prop.key.name === key);
const prop = templateProperties.computed.value.properties.find((prop: Node) => getName(prop.key) === key);
addDeclaration(key, prop.value, 'computed');
};
templateProperties.computed.value.properties.forEach((prop: Node) =>
visit(prop.key.name)
visit(getName(prop.key))
);
}
@ -620,13 +621,13 @@ export default class Generator {
if (templateProperties.events && dom) {
templateProperties.events.value.properties.forEach((property: Node) => {
addDeclaration(property.key.name, property.value, 'events');
addDeclaration(getName(property.key), property.value, 'events');
});
}
if (templateProperties.helpers) {
templateProperties.helpers.value.properties.forEach((property: Node) => {
addDeclaration(property.key.name, property.value, 'helpers');
addDeclaration(getName(property.key), property.value, 'helpers');
});
}
@ -663,7 +664,7 @@ export default class Generator {
if (templateProperties.transitions) {
templateProperties.transitions.value.properties.forEach((property: Node) => {
addDeclaration(property.key.name, property.value, 'transitions');
addDeclaration(getName(property.key), property.value, 'transitions');
});
}
}

@ -309,7 +309,7 @@ const preprocessors = {
stripWhitespace: boolean,
nextSibling: Node
) => {
if (node.name === 'slot') {
if (node.name === 'slot' || node.name === 'option') {
cannotUseInnerHTML(node);
}
@ -358,6 +358,19 @@ const preprocessors = {
}
});
const valueAttribute = node.attributes.find((attribute: Node) => attribute.name === 'value');
// Treat these the same way:
// <option>{{foo}}</option>
// <option value='{{foo}}'>{{foo}}</option>
if (node.name === 'option' && !valueAttribute) {
node.attributes.push({
type: 'Attribute',
name: 'value',
value: node.children
});
}
// special case — in a case like this...
//
// <select bind:value='foo'>
@ -369,14 +382,9 @@ const preprocessors = {
// so that if `foo.qux` changes, we know that we need to
// mark `bar` and `baz` as dirty too
if (node.name === 'select') {
cannotUseInnerHTML(node);
const value = node.attributes.find(
(attribute: Node) => attribute.name === 'value'
);
if (value) {
if (valueAttribute) {
// TODO does this also apply to e.g. `<input type='checkbox' bind:group='foo'>`?
const dependencies = block.findDependencies(value.value);
const dependencies = block.findDependencies(valueAttribute.value);
state.selectBindingDependencies = dependencies;
dependencies.forEach((prop: string) => {
generator.indirectDependencies.set(prop, new Set());

@ -7,21 +7,10 @@ import getTailSnippet from '../../../utils/getTailSnippet';
import getObject from '../../../utils/getObject';
import getExpressionPrecedence from '../../../utils/getExpressionPrecedence';
import { stringify } from '../../../utils/stringify';
import stringifyProps from '../../../utils/stringifyProps';
import { Node } from '../../../interfaces';
import { State } from '../interfaces';
function stringifyProps(props: string[]) {
if (!props.length) return '{}';
const joined = props.join(', ');
if (joined.length > 40) {
// make larger data objects readable
return `{\n\t${props.join(',\n\t')}\n}`;
}
return `{ ${joined} }`;
}
interface Attribute {
name: string;
value: any;

@ -3,7 +3,6 @@ import deindent from '../../../../utils/deindent';
import visitStyleAttribute, { optimizeStyle } from './StyleAttribute';
import { stringify } from '../../../../utils/stringify';
import getExpressionPrecedence from '../../../../utils/getExpressionPrecedence';
import getStaticAttributeValue from '../../../../utils/getStaticAttributeValue';
import { DomGenerator } from '../../index';
import Block from '../../Block';
import { Node } from '../../../../interfaces';
@ -56,6 +55,11 @@ export default function visitAttribute(
const isLegacyInputType = generator.legacy && name === 'type' && node.name === 'input';
const isDataSet = /^data-/.test(name) && !generator.legacy;
const camelCaseName = isDataSet ? name.replace('data-', '').replace(/(-\w)/g, function (m) {
return m[1].toUpperCase();
}) : name;
if (isDynamic) {
let value;
@ -163,6 +167,11 @@ export default function visitAttribute(
`${state.parentNode}.${propertyName} = ${init};`
);
updater = `${state.parentNode}.${propertyName} = ${shouldCache || isSelectValueAttribute ? last : value};`;
} else if (isDataSet) {
block.builders.hydrate.addLine(
`${state.parentNode}.dataset.${camelCaseName} = ${init};`
);
updater = `${state.parentNode}.dataset.${camelCaseName} = ${shouldCache || isSelectValueAttribute ? last : value};`;
} else {
block.builders.hydrate.addLine(
`${method}(${state.parentNode}, "${name}", ${init});`
@ -198,6 +207,7 @@ export default function visitAttribute(
const statement = (
isLegacyInputType ? `@setInputType(${state.parentNode}, ${value});` :
propertyName ? `${state.parentNode}.${propertyName} = ${value};` :
isDataSet ? `${state.parentNode}.dataset.${camelCaseName} = ${value};` :
`${method}(${state.parentNode}, "${name}", ${value});`
);

@ -1,349 +0,0 @@
import deindent from '../../../../utils/deindent';
import flattenReference from '../../../../utils/flattenReference';
import getStaticAttributeValue from '../../../../utils/getStaticAttributeValue';
import { DomGenerator } from '../../index';
import Block from '../../Block';
import { Node } from '../../../../interfaces';
import { State } from '../../interfaces';
import getObject from '../../../../utils/getObject';
import getTailSnippet from '../../../../utils/getTailSnippet';
const readOnlyMediaAttributes = new Set([
'duration',
'buffered',
'seekable',
'played'
]);
export default function visitBinding(
generator: DomGenerator,
block: Block,
state: State,
node: Node,
attribute: Node
) {
const { name } = getObject(attribute.value);
const { snippet, contexts, dependencies } = block.contextualise(
attribute.value
);
contexts.forEach(context => {
if (!~state.allUsedContexts.indexOf(context))
state.allUsedContexts.push(context);
});
const eventNames = getBindingEventName(node, attribute);
const handler = block.getUniqueName(
`${state.parentNode}_${eventNames.join('_')}_handler`
);
const isMultipleSelect =
node.name === 'select' &&
node.attributes.find(
(attr: Node) => attr.name.toLowerCase() === 'multiple'
); // TODO use getStaticAttributeValue
const type = getStaticAttributeValue(node, 'type');
const bindingGroup = attribute.name === 'group'
? getBindingGroup(generator, attribute.value)
: null;
const isMediaElement = node.name === 'audio' || node.name === 'video';
const isReadOnly = isMediaElement && readOnlyMediaAttributes.has(attribute.name)
const value = getBindingValue(
generator,
block,
state,
node,
attribute,
isMultipleSelect,
isMediaElement,
bindingGroup,
type
);
let setter = getSetter(generator, block, name, snippet, state.parentNode, attribute, dependencies, value);
let updateElement = `${state.parentNode}.${attribute.name} = ${snippet};`;
const needsLock = !isReadOnly && node.name !== 'input' || !/radio|checkbox|range|color/.test(type); // TODO others?
const lock = `#${state.parentNode}_updating`;
let updateConditions = needsLock ? [`!${lock}`] : [];
if (needsLock) block.addVariable(lock, 'false');
// <select> special case
if (node.name === 'select') {
if (!isMultipleSelect) {
setter = `var selectedOption = ${state.parentNode}.querySelector(':checked') || ${state.parentNode}.options[0];\n${setter}`;
}
const value = block.getUniqueName('value');
const option = block.getUniqueName('option');
const ifStatement = isMultipleSelect
? deindent`
${option}.selected = ~${value}.indexOf(${option}.__value);`
: deindent`
if (${option}.__value === ${value}) {
${option}.selected = true;
break;
}`;
const { name } = getObject(attribute.value);
const tailSnippet = getTailSnippet(attribute.value);
updateElement = deindent`
var ${value} = ${snippet};
for (var #i = 0; #i < ${state.parentNode}.options.length; #i += 1) {
var ${option} = ${state.parentNode}.options[#i];
${ifStatement}
}
`;
generator.hasComplexBindings = true;
block.builders.hydrate.addBlock(
`if (!('${name}' in state)) #component._root._beforecreate.push(${handler});`
);
} else if (attribute.name === 'group') {
// <input type='checkbox|radio' bind:group='selected'> special case
if (type === 'radio') {
setter = deindent`
if (!${state.parentNode}.checked) return;
${setter}
`;
}
const condition = type === 'checkbox'
? `~${snippet}.indexOf(${state.parentNode}.__value)`
: `${state.parentNode}.__value === ${snippet}`;
block.builders.hydrate.addLine(
`#component._bindingGroups[${bindingGroup}].push(${state.parentNode});`
);
block.builders.destroy.addBlock(
`#component._bindingGroups[${bindingGroup}].splice(#component._bindingGroups[${bindingGroup}].indexOf(${state.parentNode}), 1);`
);
updateElement = `${state.parentNode}.checked = ${condition};`;
} else if (isMediaElement) {
generator.hasComplexBindings = true;
block.builders.hydrate.addBlock(`#component._root._beforecreate.push(${handler});`);
if (attribute.name === 'currentTime') {
const frame = block.getUniqueName(`${state.parentNode}_animationframe`);
block.addVariable(frame);
setter = deindent`
cancelAnimationFrame(${frame});
if (!${state.parentNode}.paused) ${frame} = requestAnimationFrame(${handler});
${setter}
`;
updateConditions.push(`!isNaN(${snippet})`);
} else if (attribute.name === 'paused') {
// this is necessary to prevent the audio restarting by itself
const last = block.getUniqueName(`${state.parentNode}_paused_value`);
block.addVariable(last, 'true');
updateConditions = [`${last} !== (${last} = ${snippet})`];
updateElement = `${state.parentNode}[${last} ? "pause" : "play"]();`;
}
}
block.builders.init.addBlock(deindent`
function ${handler}() {
${needsLock && `${lock} = true;`}
${setter}
${needsLock && `${lock} = false;`}
}
`);
if (node.name === 'input' && type === 'range') {
// need to bind to `input` and `change`, for the benefit of IE
block.builders.hydrate.addBlock(deindent`
@addListener(${state.parentNode}, "input", ${handler});
@addListener(${state.parentNode}, "change", ${handler});
`);
block.builders.destroy.addBlock(deindent`
@removeListener(${state.parentNode}, "input", ${handler});
@removeListener(${state.parentNode}, "change", ${handler});
`);
} else {
eventNames.forEach(eventName => {
block.builders.hydrate.addLine(
`@addListener(${state.parentNode}, "${eventName}", ${handler});`
);
block.builders.destroy.addLine(
`@removeListener(${state.parentNode}, "${eventName}", ${handler});`
);
});
}
if (!isMediaElement) {
node.initialUpdate = updateElement;
node.initialUpdateNeedsStateObject = !block.contexts.has(name);
}
if (!isReadOnly) { // audio/video duration is read-only, it never updates
if (updateConditions.length) {
block.builders.update.addBlock(deindent`
if (${updateConditions.join(' && ')}) {
${updateElement}
}
`);
} else {
block.builders.update.addBlock(deindent`
${updateElement}
`);
}
}
if (attribute.name === 'paused') {
block.builders.create.addLine(
`@addListener(${state.parentNode}, "play", ${handler});`
);
block.builders.destroy.addLine(
`@removeListener(${state.parentNode}, "play", ${handler});`
);
}
}
function getBindingEventName(node: Node, attribute: Node) {
if (node.name === 'input') {
const typeAttribute = node.attributes.find(
(attr: Node) => attr.type === 'Attribute' && attr.name === 'type'
);
const type = typeAttribute ? typeAttribute.value[0].data : 'text'; // TODO in validation, should throw if type attribute is not static
return [type === 'checkbox' || type === 'radio' ? 'change' : 'input'];
}
if (node.name === 'textarea') return ['input'];
if (attribute.name === 'currentTime') return ['timeupdate'];
if (attribute.name === 'duration') return ['durationchange'];
if (attribute.name === 'paused') return ['pause'];
if (attribute.name === 'buffered') return ['progress', 'loadedmetadata'];
if (attribute.name === 'seekable') return ['loadedmetadata'];
if (attribute.name === 'played') return ['timeupdate'];
return ['change'];
}
function getBindingValue(
generator: DomGenerator,
block: Block,
state: State,
node: Node,
attribute: Node,
isMultipleSelect: boolean,
isMediaElement: boolean,
bindingGroup: number,
type: string
) {
// <select multiple bind:value='selected>
if (isMultipleSelect) {
return `[].map.call(${state.parentNode}.querySelectorAll(':checked'), function(option) { return option.__value; })`;
}
// <select bind:value='selected>
if (node.name === 'select') {
return 'selectedOption && selectedOption.__value';
}
// <input type='checkbox' bind:group='foo'>
if (attribute.name === 'group') {
if (type === 'checkbox') {
return `@getBindingGroupValue(#component._bindingGroups[${bindingGroup}])`;
}
return `${state.parentNode}.__value`;
}
// <input type='range|number' bind:value>
if (type === 'range' || type === 'number') {
return `@toNumber(${state.parentNode}.${attribute.name})`;
}
if (isMediaElement && (attribute.name === 'buffered' || attribute.name === 'seekable' || attribute.name === 'played')) {
return `@timeRangesToArray(${state.parentNode}.${attribute.name})`
}
// everything else
return `${state.parentNode}.${attribute.name}`;
}
function getBindingGroup(generator: DomGenerator, value: Node) {
const { parts } = flattenReference(value); // TODO handle cases involving computed member expressions
const keypath = parts.join('.');
// TODO handle contextual bindings — `keypath` should include unique ID of
// each block that provides context
let index = generator.bindingGroups.indexOf(keypath);
if (index === -1) {
index = generator.bindingGroups.length;
generator.bindingGroups.push(keypath);
}
return index;
}
function getSetter(
generator: DomGenerator,
block: Block,
name: string,
snippet: string,
_this: string,
attribute: Node,
dependencies: string[],
value: string,
) {
const tail = attribute.value.type === 'MemberExpression'
? getTailSnippet(attribute.value)
: '';
if (block.contexts.has(name)) {
const prop = dependencies[0];
const computed = isComputed(attribute.value);
return deindent`
var list = ${_this}._svelte.${block.listNames.get(name)};
var index = ${_this}._svelte.${block.indexNames.get(name)};
${computed && `var state = #component.get();`}
list[index]${tail} = ${value};
${computed
? `#component.set({${dependencies.map((prop: string) => `${prop}: state.${prop}`).join(', ')} });`
: `#component.set({${dependencies.map((prop: string) => `${prop}: #component.get('${prop}')`).join(', ')} });`}
`;
}
if (attribute.value.type === 'MemberExpression') {
// This is a little confusing, and should probably be tidied up
// at some point. It addresses a tricky bug (#893), wherein
// Svelte tries to `set()` a computed property, which throws an
// error in dev mode. a) it's possible that we should be
// replacing computations with *their* dependencies, and b)
// we should probably populate `generator.readonly` sooner so
// that we don't have to do the `.some()` here
dependencies = dependencies.filter(prop => !generator.computations.some(computation => computation.key === prop));
return deindent`
var state = #component.get();
${snippet} = ${value};
#component.set({ ${dependencies.map((prop: string) => `${prop}: state.${prop}`).join(', ')} });
`;
}
return `#component.set({ ${name}: ${value} });`;
}
function isComputed(node: Node) {
while (node.type === 'MemberExpression') {
if (node.computed) return true;
node = node.object;
}
return false;
}

@ -4,9 +4,9 @@ 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 addBindings from './addBindings';
import flattenReference from '../../../../utils/flattenReference';
import validCalleeObjects from '../../../../utils/validCalleeObjects';
import * as namespaces from '../../../../utils/namespaces';
import getStaticAttributeValue from '../../../../utils/getStaticAttributeValue';
import isVoidElementName from '../../../../utils/isVoidElementName';
@ -18,24 +18,10 @@ import { State } from '../../interfaces';
import reservedNames from '../../../../utils/reservedNames';
import { stringify } from '../../../../utils/stringify';
const meta = {
const meta: Record<string, any> = {
':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,
@ -69,8 +55,6 @@ export default function visitElement(
`${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(
@ -93,6 +77,10 @@ export default function visitElement(
);
} else {
block.builders.mount.addLine(`@insertNode(${name}, #target, anchor);`);
// 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});`);
}
// add CSS encapsulation attribute
@ -109,24 +97,157 @@ export default function visitElement(
}
}
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;
if (node.name === 'textarea') {
// this is an egregious hack, but it's the easiest way to get <textarea>
// children treated the same way as a value attribute
if (node.children.length > 0) {
node.attributes.push({
type: 'Attribute',
name: 'value',
value: node.children,
});
node.children = [];
}
}
visitors[attribute.type](generator, block, childState, node, attribute);
// insert static children with textContent or innerHTML
if (!childState.namespace && node.canUseInnerHTML && node.children.length > 0) {
if (node.children.length === 1 && node.children[0].type === 'Text') {
block.builders.create.addLine(
`${name}.textContent = ${stringify(node.children[0].data)};`
);
} else {
block.builders.create.addLine(
`${name}.innerHTML = ${stringify(node.children.map(toHTML).join(''))};`
);
}
} else {
node.children.forEach((child: Node) => {
visit(generator, block, childState, child, elementStack.concat(node), componentStack);
});
}
addBindings(generator, block, childState, node);
node.attributes.filter((a: Node) => a.type === 'Attribute').forEach((attribute: Node) => {
visitAttribute(generator, block, childState, node, attribute);
});
// event handlers
node.attributes.filter((a: Node) => a.type === 'EventHandler').forEach((attribute: Node) => {
const isCustomEvent = generator.events.has(attribute.name);
const shouldHoist = !isCustomEvent && state.inEachBlock;
const context = shouldHoist ? null : name;
const usedContexts: string[] = [];
if (attribute.expression) {
generator.addSourcemapLocations(attribute.expression);
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)
generator.code.prependRight(
attribute.expression.start,
`${block.alias('component')}.`
);
if (shouldHoist) childState.usesComponent = true; // this feels a bit hacky but it works!
}
attribute.expression.arguments.forEach((arg: Node) => {
const { contexts } = block.contextualise(arg, context, true);
contexts.forEach(context => {
if (!~usedContexts.indexOf(context)) usedContexts.push(context);
if (!~childState.allUsedContexts.indexOf(context))
childState.allUsedContexts.push(context);
});
});
}
const _this = context || 'this';
const declarations = usedContexts.map(name => {
if (name === 'state') {
if (shouldHoist) childState.usesComponent = true;
return `var state = ${block.alias('component')}.get();`;
}
const listName = block.listNames.get(name);
const indexName = block.indexNames.get(name);
const contextName = block.contexts.get(name);
return `var ${listName} = ${_this}._svelte.${listName}, ${indexName} = ${_this}._svelte.${indexName}, ${contextName} = ${listName}[${indexName}];`;
});
// get a name for the event handler that is globally unique
// if hoisted, locally unique otherwise
const handlerName = (shouldHoist ? generator : block).getUniqueName(
`${attribute.name.replace(/[^a-zA-Z0-9_$]/g, '_')}_handler`
);
// create the handler body
const handlerBody = deindent`
${childState.usesComponent &&
`var ${block.alias('component')} = ${_this}._svelte.component;`}
${declarations}
${attribute.expression ?
`[✂${attribute.expression.start}-${attribute.expression.end}✂];` :
`${block.alias('component')}.fire("${attribute.name}", event);`}
`;
if (isCustomEvent) {
block.addVariable(handlerName);
block.builders.hydrate.addBlock(deindent`
${handlerName} = %events-${attribute.name}.call(#component, ${name}, function(event) {
${handlerBody}
});
`);
if (intro || outro)
addTransitions(generator, block, childState, node, intro, outro);
block.builders.destroy.addLine(deindent`
${handlerName}.teardown();
`);
} else {
const handler = deindent`
function ${handlerName}(event) {
${handlerBody}
}
`;
if (shouldHoist) {
generator.blocks.push(handler);
} else {
block.builders.init.addBlock(handler);
}
block.builders.hydrate.addLine(
`@addListener(${name}, "${attribute.name}", ${handlerName});`
);
block.builders.destroy.addLine(
`@removeListener(${name}, "${attribute.name}", ${handlerName});`
);
}
});
// refs
node.attributes.filter((a: Node) => a.type === 'Ref').forEach((attribute: Node) => {
const ref = `#component.refs.${attribute.name}`;
block.builders.mount.addLine(
`${ref} = ${name};`
);
block.builders.destroy.addLine(
`if (${ref} === ${name}) ${ref} = null;`
);
generator.usesRefs = true; // so component.refs object is created
});
addTransitions(generator, block, childState, node);
if (childState.allUsedContexts.length || childState.usesComponent) {
const initialProps: string[] = [];
@ -162,70 +283,6 @@ export default function visitElement(
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 <textarea>
// children treated the same way as a value attribute
if (node.children.length > 0) {
node.attributes.push({
type: 'Attribute',
name: 'value',
value: node.children,
});
node.children = [];
}
}
// <select> value attributes are an annoying special case — it must be handled
// *after* its children have been updated
visitAttributesAndAddProps();
}
// special case bound <option> without a value attribute
if (
node.name === 'option' &&
!node.attributes.find(
(attribute: Node) =>
attribute.type === 'Attribute' && attribute.name === 'value'
)
) {
// TODO check it's bound
const statement = `${name}.__value = ${name}.textContent;`;
node.initialUpdate = node.lateUpdate = statement;
}
if (!childState.namespace && node.canUseInnerHTML && node.children.length > 0) {
if (node.children.length === 1 && node.children[0].type === 'Text') {
block.builders.create.addLine(
`${name}.textContent = ${stringify(node.children[0].data)};`
);
} else {
block.builders.create.addLine(
`${name}.innerHTML = ${stringify(node.children.map(toHTML).join(''))};`
);
}
} else {
node.children.forEach((child: Node) => {
visit(generator, block, childState, child, elementStack.concat(node), componentStack);
});
}
if (node.lateUpdate) {
block.builders.update.addLine(node.lateUpdate);
}
if (node.name === 'select') {
visitAttributesAndAddProps();
}
if (node.initialUpdate) {
block.builders.mount.addBlock(node.initialUpdate);

@ -1,111 +0,0 @@
import deindent from '../../../../utils/deindent';
import flattenReference from '../../../../utils/flattenReference';
import validCalleeObjects from '../../../../utils/validCalleeObjects';
import { DomGenerator } from '../../index';
import Block from '../../Block';
import { Node } from '../../../../interfaces';
import { State } from '../../interfaces';
export default function visitEventHandler(
generator: DomGenerator,
block: Block,
state: State,
node: Node,
attribute: Node
) {
const name = attribute.name;
const isCustomEvent = generator.events.has(name);
const shouldHoist = !isCustomEvent && state.inEachBlock;
const context = shouldHoist ? null : state.parentNode;
const usedContexts: string[] = [];
if (attribute.expression) {
generator.addSourcemapLocations(attribute.expression);
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)
generator.code.prependRight(
attribute.expression.start,
`${block.alias('component')}.`
);
if (shouldHoist) state.usesComponent = true; // this feels a bit hacky but it works!
}
attribute.expression.arguments.forEach((arg: Node) => {
const { contexts } = block.contextualise(arg, context, true);
contexts.forEach(context => {
if (!~usedContexts.indexOf(context)) usedContexts.push(context);
if (!~state.allUsedContexts.indexOf(context))
state.allUsedContexts.push(context);
});
});
}
const _this = context || 'this';
const declarations = usedContexts.map(name => {
if (name === 'state') {
if (shouldHoist) state.usesComponent = true;
return `var state = ${block.alias('component')}.get();`;
}
const listName = block.listNames.get(name);
const indexName = block.indexNames.get(name);
const contextName = block.contexts.get(name);
return `var ${listName} = ${_this}._svelte.${listName}, ${indexName} = ${_this}._svelte.${indexName}, ${contextName} = ${listName}[${indexName}];`;
});
// get a name for the event handler that is globally unique
// if hoisted, locally unique otherwise
const handlerName = (shouldHoist ? generator : block).getUniqueName(
`${name.replace(/[^a-zA-Z0-9_$]/g, '_')}_handler`
);
// create the handler body
const handlerBody = deindent`
${state.usesComponent &&
`var ${block.alias('component')} = ${_this}._svelte.component;`}
${declarations}
${attribute.expression ?
`[✂${attribute.expression.start}-${attribute.expression.end}✂];` :
`${block.alias('component')}.fire("${attribute.name}", event);`}
`;
if (isCustomEvent) {
block.addVariable(handlerName);
block.builders.hydrate.addBlock(deindent`
${handlerName} = %events-${name}.call(#component, ${state.parentNode}, function(event) {
${handlerBody}
});
`);
block.builders.destroy.addLine(deindent`
${handlerName}.teardown();
`);
} else {
const handler = deindent`
function ${handlerName}(event) {
${handlerBody}
}
`;
if (shouldHoist) {
generator.blocks.push(handler);
} else {
block.builders.init.addBlock(handler);
}
block.builders.hydrate.addLine(
`@addListener(${state.parentNode}, "${name}", ${handlerName});`
);
block.builders.destroy.addLine(
`@removeListener(${state.parentNode}, "${name}", ${handlerName});`
);
}
}

@ -1,25 +0,0 @@
import deindent from '../../../../utils/deindent';
import { DomGenerator } from '../../index';
import Block from '../../Block';
import { Node } from '../../../../interfaces';
import { State } from '../../interfaces';
export default function visitRef(
generator: DomGenerator,
block: Block,
state: State,
node: Node,
attribute: Node
) {
const name = attribute.name;
block.builders.mount.addLine(
`#component.refs.${name} = ${state.parentNode};`
);
block.builders.destroy.addLine(deindent`
if (#component.refs.${name} === ${state.parentNode}) #component.refs.${name} = null;
`);
generator.usesRefs = true; // so this component.refs object is created
}

@ -0,0 +1,382 @@
import deindent from '../../../../utils/deindent';
import flattenReference from '../../../../utils/flattenReference';
import getStaticAttributeValue from '../../../../utils/getStaticAttributeValue';
import { DomGenerator } from '../../index';
import Block from '../../Block';
import { Node } from '../../../../interfaces';
import { State } from '../../interfaces';
import getObject from '../../../../utils/getObject';
import getTailSnippet from '../../../../utils/getTailSnippet';
import stringifyProps from '../../../../utils/stringifyProps';
import { generateRule } from '../../../../shared/index';
import flatten from '../../../../utils/flattenReference';
interface Binding {
name: string;
}
const readOnlyMediaAttributes = new Set([
'duration',
'buffered',
'seekable',
'played'
]);
function isMediaNode(name: string) {
return name === 'audio' || name === 'video';
}
const events = [
{
eventNames: ['input'],
filter: (node: Node, binding: Binding) =>
node.name === 'textarea' ||
node.name === 'input' && !/radio|checkbox/.test(getStaticAttributeValue(node, 'type'))
},
{
eventNames: ['change'],
filter: (node: Node, binding: Binding) =>
node.name === 'select' ||
node.name === 'input' && /radio|checkbox|range/.test(getStaticAttributeValue(node, 'type'))
},
// media events
{
eventNames: ['timeupdate'],
filter: (node: Node, binding: Binding) =>
isMediaNode(node.name) &&
(binding.name === 'currentTime' || binding.name === 'played')
},
{
eventNames: ['durationchange'],
filter: (node: Node, binding: Binding) =>
isMediaNode(node.name) &&
binding.name === 'duration'
},
{
eventNames: ['play', 'pause'],
filter: (node: Node, binding: Binding) =>
isMediaNode(node.name) &&
binding.name === 'paused'
},
{
eventNames: ['progress'],
filter: (node: Node, binding: Binding) =>
isMediaNode(node.name) &&
binding.name === 'buffered'
},
{
eventNames: ['loadedmetadata'],
filter: (node: Node, binding: Binding) =>
isMediaNode(node.name) &&
(binding.name === 'buffered' || binding.name === 'seekable')
}
];
export default function addBindings(
generator: DomGenerator,
block: Block,
state: State,
node: Node
) {
const bindings: Node[] = node.attributes.filter((a: Node) => a.type === 'Binding');
if (bindings.length === 0) return;
if (node.name === 'select' || isMediaNode(node.name)) generator.hasComplexBindings = true;
const needsLock = node.name !== 'input' || !/radio|checkbox|range|color/.test(getStaticAttributeValue(node, 'type'));
const mungedBindings = bindings.map(binding => {
const isReadOnly = isMediaNode(node.name) && readOnlyMediaAttributes.has(binding.name);
let updateCondition: string;
const { name } = getObject(binding.value);
const { snippet, contexts, dependencies } = block.contextualise(
binding.value
);
contexts.forEach(context => {
if (!~state.allUsedContexts.indexOf(context))
state.allUsedContexts.push(context);
});
// view to model
const valueFromDom = getValueFromDom(generator, node, binding);
const handler = getEventHandler(generator, block, name, snippet, binding, dependencies, valueFromDom);
// model to view
let updateDom = getDomUpdater(node, binding, snippet);
let initialUpdate = updateDom;
// special cases
if (binding.name === 'group') {
const bindingGroup = getBindingGroup(generator, binding.value);
block.builders.hydrate.addLine(
`#component._bindingGroups[${bindingGroup}].push(${node.var});`
);
block.builders.destroy.addLine(
`#component._bindingGroups[${bindingGroup}].splice(#component._bindingGroups[${bindingGroup}].indexOf(${node.var}), 1);`
);
}
if (binding.name === 'currentTime') {
updateCondition = `!isNaN(${snippet})`;
initialUpdate = null;
}
if (binding.name === 'paused') {
// this is necessary to prevent audio restarting by itself
const last = block.getUniqueName(`${node.var}_is_paused`);
block.addVariable(last, 'true');
updateCondition = `${last} !== (${last} = ${snippet})`;
updateDom = `${node.var}[${last} ? "pause" : "play"]();`;
initialUpdate = null;
}
return {
name: binding.name,
object: name,
handler,
updateDom,
initialUpdate,
needsLock: !isReadOnly && needsLock,
updateCondition
};
});
const lock = mungedBindings.some(binding => binding.needsLock) ?
block.getUniqueName(`${node.var}_updating`) :
null;
if (lock) block.addVariable(lock, 'false');
const groups = events
.map(event => {
return {
events: event.eventNames,
bindings: mungedBindings.filter(binding => event.filter(node, binding))
};
})
.filter(group => group.bindings.length);
groups.forEach(group => {
const handler = block.getUniqueName(`${node.var}_${group.events.join('_')}_handler`);
const needsLock = group.bindings.some(binding => binding.needsLock);
group.bindings.forEach(binding => {
if (!binding.updateDom) return;
const updateConditions = needsLock ? [`!${lock}`] : [];
if (binding.updateCondition) updateConditions.push(binding.updateCondition);
block.builders.update.addLine(
updateConditions.length ? `if (${updateConditions.join(' && ')}) ${binding.updateDom}` : binding.updateDom
);
});
const usesContext = group.bindings.some(binding => binding.handler.usesContext);
const usesState = group.bindings.some(binding => binding.handler.usesState);
const mutations = group.bindings.map(binding => binding.handler.mutation).filter(Boolean).join('\n');
const props = new Set();
group.bindings.forEach(binding => {
binding.handler.props.forEach(prop => {
props.add(prop);
});
}); // TODO use stringifyProps here, once indenting is fixed
// media bindings — awkward special case. The native timeupdate events
// fire too infrequently, so we need to take matters into our
// own hands
let animation_frame;
if (group.events[0] === 'timeupdate') {
animation_frame = block.getUniqueName(`${node.var}_animationframe`);
block.addVariable(animation_frame);
}
block.builders.init.addBlock(deindent`
function ${handler}() {
${
animation_frame && deindent`
cancelAnimationFrame(${animation_frame});
if (!${node.var}.paused) ${animation_frame} = requestAnimationFrame(${handler});`
}
${usesContext && `var context = ${node.var}._svelte;`}
${usesState && `var state = #component.get();`}
${needsLock && `${lock} = true;`}
${mutations.length > 0 && mutations}
#component.set({ ${Array.from(props).join(', ')} });
${needsLock && `${lock} = false;`}
}
`);
group.events.forEach(name => {
block.builders.hydrate.addLine(
`@addListener(${node.var}, "${name}", ${handler});`
);
block.builders.destroy.addLine(
`@removeListener(${node.var}, "${name}", ${handler});`
);
});
const allInitialStateIsDefined = group.bindings
.map(binding => `'${binding.object}' in state`)
.join(' && ');
if (node.name === 'select' || group.bindings.find(binding => binding.name === 'indeterminate' || readOnlyMediaAttributes.has(binding.name))) {
generator.hasComplexBindings = true;
block.builders.hydrate.addLine(
`if (!(${allInitialStateIsDefined})) #component._root._beforecreate.push(${handler});`
);
}
});
node.initialUpdate = mungedBindings.map(binding => binding.initialUpdate).filter(Boolean).join('\n');
}
function getDomUpdater(
node: Node,
binding: Node,
snippet: string
) {
if (readOnlyMediaAttributes.has(binding.name)) {
return null;
}
if (node.name === 'select') {
return getStaticAttributeValue(node, 'multiple') === true ?
`@selectOptions(${node.var}, ${snippet})` :
`@selectOption(${node.var}, ${snippet})`;
}
if (binding.name === 'group') {
const type = getStaticAttributeValue(node, 'type');
const condition = type === 'checkbox'
? `~${snippet}.indexOf(${node.var}.__value)`
: `${node.var}.__value === ${snippet}`;
return `${node.var}.checked = ${condition};`
}
return `${node.var}.${binding.name} = ${snippet};`;
}
function getBindingGroup(generator: DomGenerator, value: Node) {
const { parts } = flattenReference(value); // TODO handle cases involving computed member expressions
const keypath = parts.join('.');
// TODO handle contextual bindings — `keypath` should include unique ID of
// each block that provides context
let index = generator.bindingGroups.indexOf(keypath);
if (index === -1) {
index = generator.bindingGroups.length;
generator.bindingGroups.push(keypath);
}
return index;
}
function getEventHandler(
generator: DomGenerator,
block: Block,
name: string,
snippet: string,
attribute: Node,
dependencies: string[],
value: string,
) {
if (block.contexts.has(name)) {
const tail = attribute.value.type === 'MemberExpression'
? getTailSnippet(attribute.value)
: '';
const list = `context.${block.listNames.get(name)}`;
const index = `context.${block.indexNames.get(name)}`;
return {
usesContext: true,
usesState: true,
mutation: `${list}[${index}]${tail} = ${value};`,
props: dependencies.map(prop => `${prop}: state.${prop}`)
};
}
if (attribute.value.type === 'MemberExpression') {
// This is a little confusing, and should probably be tidied up
// at some point. It addresses a tricky bug (#893), wherein
// Svelte tries to `set()` a computed property, which throws an
// error in dev mode. a) it's possible that we should be
// replacing computations with *their* dependencies, and b)
// we should probably populate `generator.readonly` sooner so
// that we don't have to do the `.some()` here
dependencies = dependencies.filter(prop => !generator.computations.some(computation => computation.key === prop));
return {
usesContext: false,
usesState: true,
mutation: `${snippet} = ${value}`,
props: dependencies.map((prop: string) => `${prop}: state.${prop}`)
};
}
return {
usesContext: false,
usesState: false,
mutation: null,
props: [`${name}: ${value}`]
};
}
function getValueFromDom(
generator: DomGenerator,
node: Node,
binding: Node
) {
// <select bind:value='selected>
if (node.name === 'select') {
return getStaticAttributeValue(node, 'multiple') === true ?
`@selectMultipleValue(${node.var})` :
`@selectValue(${node.var})`;
}
const type = getStaticAttributeValue(node, 'type');
// <input type='checkbox' bind:group='foo'>
if (binding.name === 'group') {
const bindingGroup = getBindingGroup(generator, binding.value);
if (type === 'checkbox') {
return `@getBindingGroupValue(#component._bindingGroups[${bindingGroup}])`;
}
return `${node.var}.__value`;
}
// <input type='range|number' bind:value>
if (type === 'range' || type === 'number') {
return `@toNumber(${node.var}.${binding.name})`;
}
if ((binding.name === 'buffered' || binding.name === 'seekable' || binding.name === 'played')) {
return `@timeRangesToArray(${node.var}.${binding.name})`
}
// everything else
return `${node.var}.${binding.name}`;
}
function isComputed(node: Node) {
while (node.type === 'MemberExpression') {
if (node.computed) return true;
node = node.object;
}
return false;
}

@ -8,10 +8,13 @@ export default function addTransitions(
generator: DomGenerator,
block: Block,
state: State,
node: Node,
intro,
outro
node: Node
) {
const intro = node.attributes.find((a: Node) => a.type === 'Transition' && a.intro);
const outro = node.attributes.find((a: Node) => a.type === 'Transition' && a.outro);
if (!intro && !outro) return;
if (intro === outro) {
const name = block.getUniqueName(`${node.var}_transition`);
const snippet = intro.expression

@ -112,6 +112,7 @@ const lookup = {
'http-equiv': { propertyName: 'httpEquiv', appliesTo: ['meta'] },
icon: { appliesTo: ['command'] },
id: {},
indeterminate: { appliesTo: ['input'] },
ismap: { propertyName: 'isMap', appliesTo: ['img'] },
itemprop: {},
keytype: { appliesTo: ['keygen'] },

@ -5,6 +5,7 @@ import Block from './Block';
import preprocess from './preprocess';
import visit from './visit';
import { removeNode, removeObjectKey } from '../../utils/removeNode';
import getName from '../../utils/getName';
import { Parsed, Node, CompileOptions } from '../../interfaces';
import { AppendTarget } from './interfaces';
import { stringify } from '../../utils/stringify';
@ -132,7 +133,7 @@ export default function ssr(
}
${templateProperties.components.value.properties.map((prop: Node) => {
return `addComponent(%components-${prop.key.name});`;
return `addComponent(%components-${getName(prop.key)});`;
})}
`}

@ -69,6 +69,19 @@ const preprocessors = {
if (slot && isChildOfComponent(node, generator)) {
node.slotted = true;
}
// Treat these the same way:
// <option>{{foo}}</option>
// <option value='{{foo}}'>{{foo}}</option>
const valueAttribute = node.attributes.find((attribute: Node) => attribute.name === 'value');
if (node.name === 'option' && !valueAttribute) {
node.attributes.push({
type: 'Attribute',
name: 'value',
value: node.children
});
}
}
if (node.children.length) {

@ -149,3 +149,32 @@ export function setInputType(input, type) {
export function setStyle(node, key, value) {
node.style.setProperty(key, value);
}
export function selectOption(select, value) {
for (var i = 0; i < select.options.length; i += 1) {
var option = select.options[i];
if (option.__value === value) {
option.selected = true;
return;
}
}
}
export function selectOptions(select, value) {
for (var i = 0; i < select.options.length; i += 1) {
var option = select.options[i];
option.selected = ~value.indexOf(option.__value);
}
}
export function selectValue(select) {
var selectedOption = select.querySelector(':checked') || select.options[0];
return selectedOption && selectedOption.__value;
}
export function selectMultipleValue(select) {
return [].map.call(select.querySelectorAll(':checked'), function(option) {
return option.__value;
});
}

@ -156,9 +156,12 @@ export function _set(newState) {
this._state = assign({}, oldState, newState);
this._recompute(changed, this._state);
if (this._bind) this._bind(changed, this._state);
if (this._fragment) {
dispatchObservers(this, this._observers.pre, changed, this._state, oldState);
this._fragment.p(changed, this._state);
dispatchObservers(this, this._observers.post, changed, this._state, oldState);
}
}
export function _setDev(newState) {

@ -0,0 +1,6 @@
import { Node } from '../interfaces';
export default function getMethodName(node: Node) {
if (node.type === 'Identifier') return node.name;
if (node.type === 'Literal') return String(node.value);
}

@ -7,6 +7,7 @@ export default function getStaticAttributeValue(node: Node, name: string) {
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') {

@ -1,4 +1,5 @@
import MagicString from 'magic-string';
import getName from '../utils/getName';
import { Node } from '../interfaces';
const keys = {
@ -51,7 +52,7 @@ export function removeObjectKey(code: MagicString, node: Node, key: string) {
let i = node.properties.length;
while (i--) {
const property = node.properties[i];
if (property.key.type === 'Identifier' && property.key.name === key) {
if (property.key.type === 'Identifier' && getName(property.key) === key) {
removeNode(code, node, property);
}
}

@ -0,0 +1,11 @@
export default function stringifyProps(props: string[]) {
if (!props.length) return '{}';
const joined = props.join(', ');
if (joined.length > 40) {
// make larger data objects readable
return `{\n\t${props.join(',\n\t')}\n}`;
}
return `{ ${joined} }`;
}

@ -83,17 +83,17 @@ export default function validateElement(
}
checkTypeAttribute(validator, node);
} else if (name === 'checked') {
} else if (name === 'checked' || name === 'indeterminate') {
if (node.name !== 'input') {
validator.error(
`'checked' is not a valid binding on <${node.name}> elements`,
`'${name}' is not a valid binding on <${node.name}> elements`,
attribute.start
);
}
if (checkTypeAttribute(validator, node) !== 'checkbox') {
validator.error(
`'checked' binding can only be used with <input type="checkbox">`,
`'${name}' binding can only be used with <input type="checkbox">`,
attribute.start
);
}

@ -3,6 +3,7 @@ import fuzzymatch from '../utils/fuzzymatch';
import checkForDupes from './utils/checkForDupes';
import checkForComputedKeys from './utils/checkForComputedKeys';
import namespaces from '../../utils/namespaces';
import getName from '../../utils/getName';
import { Validator } from '../';
import { Node } from '../../interfaces';
@ -29,7 +30,7 @@ export default function validateJs(validator: Validator, js: Node) {
const props = validator.properties;
node.declaration.properties.forEach((prop: Node) => {
props.set(prop.key.name, prop);
props.set(getName(prop.key), prop);
});
// Remove these checks in version 2
@ -49,25 +50,26 @@ export default function validateJs(validator: Validator, js: Node) {
// ensure all exported props are valid
node.declaration.properties.forEach((prop: Node) => {
const propValidator = propValidators[prop.key.name];
const name = getName(prop.key);
const propValidator = propValidators[name];
if (propValidator) {
propValidator(validator, prop);
} else {
const match = fuzzymatch(prop.key.name, validPropList);
const match = fuzzymatch(name, validPropList);
if (match) {
validator.error(
`Unexpected property '${prop.key.name}' (did you mean '${match}'?)`,
`Unexpected property '${name}' (did you mean '${match}'?)`,
prop.start
);
} else if (/FunctionExpression/.test(prop.value.type)) {
validator.error(
`Unexpected property '${prop.key.name}' (did you mean to include it in 'methods'?)`,
`Unexpected property '${name}' (did you mean to include it in 'methods'?)`,
prop.start
);
} else {
validator.error(
`Unexpected property '${prop.key.name}'`,
`Unexpected property '${name}'`,
prop.start
);
}
@ -86,7 +88,7 @@ export default function validateJs(validator: Validator, js: Node) {
['components', 'methods', 'helpers', 'transitions'].forEach(key => {
if (validator.properties.has(key)) {
validator.properties.get(key).value.properties.forEach((prop: Node) => {
validator[key].set(prop.key.name, prop.value);
validator[key].set(getName(prop.key), prop.value);
});
}
});

@ -1,5 +1,6 @@
import checkForDupes from '../utils/checkForDupes';
import checkForComputedKeys from '../utils/checkForComputedKeys';
import getName from '../../../utils/getName';
import { Validator } from '../../';
import { Node } from '../../../interfaces';
@ -16,14 +17,16 @@ export default function components(validator: Validator, prop: Node) {
checkForComputedKeys(validator, prop.value.properties);
prop.value.properties.forEach((component: Node) => {
if (component.key.name === 'state') {
const name = getName(component.key);
if (name === 'state') {
validator.error(
`Component constructors cannot be called 'state' due to technical limitations`,
component.start
);
}
if (!/^[A-Z]/.test(component.key.name)) {
if (!/^[A-Z]/.test(name)) {
validator.warn(`Component names should be capitalised`, component.start);
}
});

@ -2,6 +2,7 @@ import checkForAccessors from '../utils/checkForAccessors';
import checkForDupes from '../utils/checkForDupes';
import checkForComputedKeys from '../utils/checkForComputedKeys';
import usesThisOrArguments from '../utils/usesThisOrArguments';
import getName from '../../../utils/getName';
import { Validator } from '../../';
import { Node } from '../../../interfaces';
@ -21,9 +22,11 @@ export default function methods(validator: Validator, prop: Node) {
checkForComputedKeys(validator, prop.value.properties);
prop.value.properties.forEach((prop: Node) => {
if (builtin.has(prop.key.name)) {
const name = getName(prop.key);
if (builtin.has(name)) {
validator.error(
`Cannot overwrite built-in method '${prop.key.name}'`,
`Cannot overwrite built-in method '${name}'`,
prop.start
);
}

@ -1,5 +1,6 @@
import { Validator } from '../../';
import { Node } from '../../../interfaces';
import getName from '../../../utils/getName';
export default function checkForDupes(
validator: Validator,
@ -8,10 +9,12 @@ export default function checkForDupes(
const seen = new Set();
properties.forEach(prop => {
if (seen.has(prop.key.name)) {
validator.error(`Duplicate property '${prop.key.name}'`, prop.start);
const name = getName(prop.key);
if (seen.has(name)) {
validator.error(`Duplicate property '${name}'`, prop.start);
}
seen.add(prop.key.name);
seen.add(name);
});
}

@ -157,9 +157,12 @@ function _set(newState) {
this._state = assign({}, oldState, newState);
this._recompute(changed, this._state);
if (this._bind) this._bind(changed, this._state);
if (this._fragment) {
dispatchObservers(this, this._observers.pre, changed, this._state, oldState);
this._fragment.p(changed, this._state);
dispatchObservers(this, this._observers.post, changed, this._state, oldState);
}
}
function callAll(fns) {

@ -133,9 +133,12 @@ function _set(newState) {
this._state = assign({}, oldState, newState);
this._recompute(changed, this._state);
if (this._bind) this._bind(changed, this._state);
if (this._fragment) {
dispatchObservers(this, this._observers.pre, changed, this._state, oldState);
this._fragment.p(changed, this._state);
dispatchObservers(this, this._observers.post, changed, this._state, oldState);
}
}
function callAll(fns) {

@ -133,9 +133,12 @@ function _set(newState) {
this._state = assign({}, oldState, newState);
this._recompute(changed, this._state);
if (this._bind) this._bind(changed, this._state);
if (this._fragment) {
dispatchObservers(this, this._observers.pre, changed, this._state, oldState);
this._fragment.p(changed, this._state);
dispatchObservers(this, this._observers.post, changed, this._state, oldState);
}
}
function callAll(fns) {

@ -153,9 +153,12 @@ function _set(newState) {
this._state = assign({}, oldState, newState);
this._recompute(changed, this._state);
if (this._bind) this._bind(changed, this._state);
if (this._fragment) {
dispatchObservers(this, this._observers.pre, changed, this._state, oldState);
this._fragment.p(changed, this._state);
dispatchObservers(this, this._observers.post, changed, this._state, oldState);
}
}
function callAll(fns) {

@ -145,9 +145,12 @@ function _set(newState) {
this._state = assign({}, oldState, newState);
this._recompute(changed, this._state);
if (this._bind) this._bind(changed, this._state);
if (this._fragment) {
dispatchObservers(this, this._observers.pre, changed, this._state, oldState);
this._fragment.p(changed, this._state);
dispatchObservers(this, this._observers.post, changed, this._state, oldState);
}
}
function callAll(fns) {

@ -0,0 +1,239 @@
function noop() {}
function assign(target) {
var k,
source,
i = 1,
len = arguments.length;
for (; i < len; i++) {
source = arguments[i];
for (k in source) target[k] = source[k];
}
return target;
}
function insertNode(node, target, anchor) {
target.insertBefore(node, anchor);
}
function detachNode(node) {
node.parentNode.removeChild(node);
}
function createElement(name) {
return document.createElement(name);
}
function createText(data) {
return document.createTextNode(data);
}
function blankObject() {
return Object.create(null);
}
function destroy(detach) {
this.destroy = noop;
this.fire('destroy');
this.set = this.get = noop;
if (detach !== false) this._fragment.u();
this._fragment.d();
this._fragment = this._state = null;
}
function differs(a, b) {
return a !== b || ((a && typeof a === 'object') || typeof a === 'function');
}
function dispatchObservers(component, group, changed, newState, oldState) {
for (var key in group) {
if (!changed[key]) continue;
var newValue = newState[key];
var oldValue = oldState[key];
var callbacks = group[key];
if (!callbacks) continue;
for (var i = 0; i < callbacks.length; i += 1) {
var callback = callbacks[i];
if (callback.__calling) continue;
callback.__calling = true;
callback.call(component, newValue, oldValue);
callback.__calling = false;
}
}
}
function fire(eventName, data) {
var handlers =
eventName in this._handlers && this._handlers[eventName].slice();
if (!handlers) return;
for (var i = 0; i < handlers.length; i += 1) {
handlers[i].call(this, data);
}
}
function get(key) {
return key ? this._state[key] : this._state;
}
function init(component, options) {
component.options = options;
component._observers = { pre: blankObject(), post: blankObject() };
component._handlers = blankObject();
component._root = options._root || component;
component._bind = options._bind;
}
function observe(key, callback, options) {
var group = options && options.defer
? this._observers.post
: this._observers.pre;
(group[key] || (group[key] = [])).push(callback);
if (!options || options.init !== false) {
callback.__calling = true;
callback.call(this, this._state[key]);
callback.__calling = false;
}
return {
cancel: function() {
var index = group[key].indexOf(callback);
if (~index) group[key].splice(index, 1);
}
};
}
function on(eventName, handler) {
if (eventName === 'teardown') return this.on('destroy', handler);
var handlers = this._handlers[eventName] || (this._handlers[eventName] = []);
handlers.push(handler);
return {
cancel: function() {
var index = handlers.indexOf(handler);
if (~index) handlers.splice(index, 1);
}
};
}
function set(newState) {
this._set(assign({}, newState));
if (this._root._lock) return;
this._root._lock = true;
callAll(this._root._beforecreate);
callAll(this._root._oncreate);
callAll(this._root._aftercreate);
this._root._lock = false;
}
function _set(newState) {
var oldState = this._state,
changed = {},
dirty = false;
for (var key in newState) {
if (differs(newState[key], oldState[key])) changed[key] = dirty = true;
}
if (!dirty) return;
this._state = assign({}, oldState, newState);
this._recompute(changed, this._state);
if (this._bind) this._bind(changed, this._state);
if (this._fragment) {
dispatchObservers(this, this._observers.pre, changed, this._state, oldState);
this._fragment.p(changed, this._state);
dispatchObservers(this, this._observers.post, changed, this._state, oldState);
}
}
function callAll(fns) {
while (fns && fns.length) fns.pop()();
}
function _mount(target, anchor) {
this._fragment.m(target, anchor);
}
function _unmount() {
this._fragment.u();
}
var proto = {
destroy: destroy,
get: get,
fire: fire,
observe: observe,
on: on,
set: set,
teardown: destroy,
_recompute: noop,
_set: _set,
_mount: _mount,
_unmount: _unmount
};
/* generated by Svelte vX.Y.Z */
function create_main_fragment(state, component) {
var div, text, div_1;
return {
c: function create() {
div = createElement("div");
text = createText("\n");
div_1 = createElement("div");
this.h();
},
h: function hydrate() {
div.dataset.foo = "bar";
div_1.dataset.foo = state.bar;
},
m: function mount(target, anchor) {
insertNode(div, target, anchor);
insertNode(text, target, anchor);
insertNode(div_1, target, anchor);
},
p: function update(changed, state) {
if (changed.bar) {
div_1.dataset.foo = state.bar;
}
},
u: function unmount() {
detachNode(div);
detachNode(text);
detachNode(div_1);
},
d: noop
};
}
function SvelteComponent(options) {
init(this, options);
this._state = assign({}, options.data);
this._fragment = create_main_fragment(this._state, this);
if (options.target) {
this._fragment.c();
this._fragment.m(options.target, options.anchor || null);
}
}
assign(SvelteComponent.prototype, proto);
export default SvelteComponent;

@ -0,0 +1,55 @@
/* generated by Svelte vX.Y.Z */
import { assign, createElement, createText, detachNode, init, insertNode, noop, proto } from "svelte/shared.js";
function create_main_fragment(state, component) {
var div, text, div_1;
return {
c: function create() {
div = createElement("div");
text = createText("\n");
div_1 = createElement("div");
this.h();
},
h: function hydrate() {
div.dataset.foo = "bar";
div_1.dataset.foo = state.bar;
},
m: function mount(target, anchor) {
insertNode(div, target, anchor);
insertNode(text, target, anchor);
insertNode(div_1, target, anchor);
},
p: function update(changed, state) {
if (changed.bar) {
div_1.dataset.foo = state.bar;
}
},
u: function unmount() {
detachNode(div);
detachNode(text);
detachNode(div_1);
},
d: noop
};
}
function SvelteComponent(options) {
init(this, options);
this._state = assign({}, options.data);
this._fragment = create_main_fragment(this._state, this);
if (options.target) {
this._fragment.c();
this._fragment.m(options.target, options.anchor || null);
}
}
assign(SvelteComponent.prototype, proto);
export default SvelteComponent;

@ -0,0 +1,2 @@
<div data-foo='bar'/>
<div data-foo='{{bar}}'/>

@ -0,0 +1,5 @@
export default {
options: {
legacy: true
}
};

@ -0,0 +1,243 @@
function noop() {}
function assign(target) {
var k,
source,
i = 1,
len = arguments.length;
for (; i < len; i++) {
source = arguments[i];
for (k in source) target[k] = source[k];
}
return target;
}
function insertNode(node, target, anchor) {
target.insertBefore(node, anchor);
}
function detachNode(node) {
node.parentNode.removeChild(node);
}
function createElement(name) {
return document.createElement(name);
}
function createText(data) {
return document.createTextNode(data);
}
function setAttribute(node, attribute, value) {
node.setAttribute(attribute, value);
}
function blankObject() {
return Object.create(null);
}
function destroy(detach) {
this.destroy = noop;
this.fire('destroy');
this.set = this.get = noop;
if (detach !== false) this._fragment.u();
this._fragment.d();
this._fragment = this._state = null;
}
function differs(a, b) {
return a !== b || ((a && typeof a === 'object') || typeof a === 'function');
}
function dispatchObservers(component, group, changed, newState, oldState) {
for (var key in group) {
if (!changed[key]) continue;
var newValue = newState[key];
var oldValue = oldState[key];
var callbacks = group[key];
if (!callbacks) continue;
for (var i = 0; i < callbacks.length; i += 1) {
var callback = callbacks[i];
if (callback.__calling) continue;
callback.__calling = true;
callback.call(component, newValue, oldValue);
callback.__calling = false;
}
}
}
function fire(eventName, data) {
var handlers =
eventName in this._handlers && this._handlers[eventName].slice();
if (!handlers) return;
for (var i = 0; i < handlers.length; i += 1) {
handlers[i].call(this, data);
}
}
function get(key) {
return key ? this._state[key] : this._state;
}
function init(component, options) {
component.options = options;
component._observers = { pre: blankObject(), post: blankObject() };
component._handlers = blankObject();
component._root = options._root || component;
component._bind = options._bind;
}
function observe(key, callback, options) {
var group = options && options.defer
? this._observers.post
: this._observers.pre;
(group[key] || (group[key] = [])).push(callback);
if (!options || options.init !== false) {
callback.__calling = true;
callback.call(this, this._state[key]);
callback.__calling = false;
}
return {
cancel: function() {
var index = group[key].indexOf(callback);
if (~index) group[key].splice(index, 1);
}
};
}
function on(eventName, handler) {
if (eventName === 'teardown') return this.on('destroy', handler);
var handlers = this._handlers[eventName] || (this._handlers[eventName] = []);
handlers.push(handler);
return {
cancel: function() {
var index = handlers.indexOf(handler);
if (~index) handlers.splice(index, 1);
}
};
}
function set(newState) {
this._set(assign({}, newState));
if (this._root._lock) return;
this._root._lock = true;
callAll(this._root._beforecreate);
callAll(this._root._oncreate);
callAll(this._root._aftercreate);
this._root._lock = false;
}
function _set(newState) {
var oldState = this._state,
changed = {},
dirty = false;
for (var key in newState) {
if (differs(newState[key], oldState[key])) changed[key] = dirty = true;
}
if (!dirty) return;
this._state = assign({}, oldState, newState);
this._recompute(changed, this._state);
if (this._bind) this._bind(changed, this._state);
if (this._fragment) {
dispatchObservers(this, this._observers.pre, changed, this._state, oldState);
this._fragment.p(changed, this._state);
dispatchObservers(this, this._observers.post, changed, this._state, oldState);
}
}
function callAll(fns) {
while (fns && fns.length) fns.pop()();
}
function _mount(target, anchor) {
this._fragment.m(target, anchor);
}
function _unmount() {
this._fragment.u();
}
var proto = {
destroy: destroy,
get: get,
fire: fire,
observe: observe,
on: on,
set: set,
teardown: destroy,
_recompute: noop,
_set: _set,
_mount: _mount,
_unmount: _unmount
};
/* generated by Svelte vX.Y.Z */
function create_main_fragment(state, component) {
var div, text, div_1;
return {
c: function create() {
div = createElement("div");
text = createText("\n");
div_1 = createElement("div");
this.h();
},
h: function hydrate() {
setAttribute(div, "data-foo", "bar");
setAttribute(div_1, "data-foo", state.bar);
},
m: function mount(target, anchor) {
insertNode(div, target, anchor);
insertNode(text, target, anchor);
insertNode(div_1, target, anchor);
},
p: function update(changed, state) {
if (changed.bar) {
setAttribute(div_1, "data-foo", state.bar);
}
},
u: function unmount() {
detachNode(div);
detachNode(text);
detachNode(div_1);
},
d: noop
};
}
function SvelteComponent(options) {
init(this, options);
this._state = assign({}, options.data);
this._fragment = create_main_fragment(this._state, this);
if (options.target) {
this._fragment.c();
this._fragment.m(options.target, options.anchor || null);
}
}
assign(SvelteComponent.prototype, proto);
export default SvelteComponent;

@ -0,0 +1,55 @@
/* generated by Svelte vX.Y.Z */
import { assign, createElement, createText, detachNode, init, insertNode, noop, proto, setAttribute } from "svelte/shared.js";
function create_main_fragment(state, component) {
var div, text, div_1;
return {
c: function create() {
div = createElement("div");
text = createText("\n");
div_1 = createElement("div");
this.h();
},
h: function hydrate() {
setAttribute(div, "data-foo", "bar");
setAttribute(div_1, "data-foo", state.bar);
},
m: function mount(target, anchor) {
insertNode(div, target, anchor);
insertNode(text, target, anchor);
insertNode(div_1, target, anchor);
},
p: function update(changed, state) {
if (changed.bar) {
setAttribute(div_1, "data-foo", state.bar);
}
},
u: function unmount() {
detachNode(div);
detachNode(text);
detachNode(div_1);
},
d: noop
};
}
function SvelteComponent(options) {
init(this, options);
this._state = assign({}, options.data);
this._fragment = create_main_fragment(this._state, this);
if (options.target) {
this._fragment.c();
this._fragment.m(options.target, options.anchor || null);
}
}
assign(SvelteComponent.prototype, proto);
export default SvelteComponent;

@ -0,0 +1,2 @@
<div data-foo='bar'/>
<div data-foo='{{bar}}'/>

@ -165,9 +165,12 @@ function _set(newState) {
this._state = assign({}, oldState, newState);
this._recompute(changed, this._state);
if (this._bind) this._bind(changed, this._state);
if (this._fragment) {
dispatchObservers(this, this._observers.pre, changed, this._state, oldState);
this._fragment.p(changed, this._state);
dispatchObservers(this, this._observers.post, changed, this._state, oldState);
}
}
function callAll(fns) {
@ -291,8 +294,8 @@ function create_each_block(state, comments, comment, i, component) {
},
h: function hydrate() {
div.className = "comment";
span.className = "meta";
div.className = "comment";
},
m: function mount(target, anchor) {

@ -95,8 +95,8 @@ function create_each_block(state, comments, comment, i, component) {
},
h: function hydrate() {
div.className = "comment";
span.className = "meta";
div.className = "comment";
},
m: function mount(target, anchor) {

@ -145,9 +145,12 @@ function _set(newState) {
this._state = assign({}, oldState, newState);
this._recompute(changed, this._state);
if (this._bind) this._bind(changed, this._state);
if (this._fragment) {
dispatchObservers(this, this._observers.pre, changed, this._state, oldState);
this._fragment.p(changed, this._state);
dispatchObservers(this, this._observers.post, changed, this._state, oldState);
}
}
function callAll(fns) {

@ -149,9 +149,12 @@ function _set(newState) {
this._state = assign({}, oldState, newState);
this._recompute(changed, this._state);
if (this._bind) this._bind(changed, this._state);
if (this._fragment) {
dispatchObservers(this, this._observers.pre, changed, this._state, oldState);
this._fragment.p(changed, this._state);
dispatchObservers(this, this._observers.post, changed, this._state, oldState);
}
}
function callAll(fns) {

@ -149,9 +149,12 @@ function _set(newState) {
this._state = assign({}, oldState, newState);
this._recompute(changed, this._state);
if (this._bind) this._bind(changed, this._state);
if (this._fragment) {
dispatchObservers(this, this._observers.pre, changed, this._state, oldState);
this._fragment.p(changed, this._state);
dispatchObservers(this, this._observers.post, changed, this._state, oldState);
}
}
function callAll(fns) {

@ -149,9 +149,12 @@ function _set(newState) {
this._state = assign({}, oldState, newState);
this._recompute(changed, this._state);
if (this._bind) this._bind(changed, this._state);
if (this._fragment) {
dispatchObservers(this, this._observers.pre, changed, this._state, oldState);
this._fragment.p(changed, this._state);
dispatchObservers(this, this._observers.post, changed, this._state, oldState);
}
}
function callAll(fns) {

@ -149,9 +149,12 @@ function _set(newState) {
this._state = assign({}, oldState, newState);
this._recompute(changed, this._state);
if (this._bind) this._bind(changed, this._state);
if (this._fragment) {
dispatchObservers(this, this._observers.pre, changed, this._state, oldState);
this._fragment.p(changed, this._state);
dispatchObservers(this, this._observers.post, changed, this._state, oldState);
}
}
function callAll(fns) {

@ -149,9 +149,12 @@ function _set(newState) {
this._state = assign({}, oldState, newState);
this._recompute(changed, this._state);
if (this._bind) this._bind(changed, this._state);
if (this._fragment) {
dispatchObservers(this, this._observers.pre, changed, this._state, oldState);
this._fragment.p(changed, this._state);
dispatchObservers(this, this._observers.post, changed, this._state, oldState);
}
}
function callAll(fns) {

@ -149,9 +149,12 @@ function _set(newState) {
this._state = assign({}, oldState, newState);
this._recompute(changed, this._state);
if (this._bind) this._bind(changed, this._state);
if (this._fragment) {
dispatchObservers(this, this._observers.pre, changed, this._state, oldState);
this._fragment.p(changed, this._state);
dispatchObservers(this, this._observers.post, changed, this._state, oldState);
}
}
function callAll(fns) {

@ -153,9 +153,12 @@ function _set(newState) {
this._state = assign({}, oldState, newState);
this._recompute(changed, this._state);
if (this._bind) this._bind(changed, this._state);
if (this._fragment) {
dispatchObservers(this, this._observers.pre, changed, this._state, oldState);
this._fragment.p(changed, this._state);
dispatchObservers(this, this._observers.post, changed, this._state, oldState);
}
}
function callAll(fns) {
@ -199,8 +202,8 @@ function create_main_fragment(state, component) {
},
h: function hydrate() {
input.type = "checkbox";
addListener(input, "change", input_change_handler);
input.type = "checkbox";
},
m: function mount(target, anchor) {

@ -15,8 +15,8 @@ function create_main_fragment(state, component) {
},
h: function hydrate() {
input.type = "checkbox";
addListener(input, "change", input_change_handler);
input.type = "checkbox";
},
m: function mount(target, anchor) {

@ -151,9 +151,12 @@ function _set(newState) {
this._state = assign({}, oldState, newState);
this._recompute(changed, this._state);
if (this._bind) this._bind(changed, this._state);
if (this._fragment) {
dispatchObservers(this, this._observers.pre, changed, this._state, oldState);
this._fragment.p(changed, this._state);
dispatchObservers(this, this._observers.post, changed, this._state, oldState);
}
}
function callAll(fns) {

@ -168,9 +168,12 @@ function _set(newState) {
this._state = assign({}, oldState, newState);
this._recompute(changed, this._state);
if (this._bind) this._bind(changed, this._state);
if (this._fragment) {
dispatchObservers(this, this._observers.pre, changed, this._state, oldState);
this._fragment.p(changed, this._state);
dispatchObservers(this, this._observers.post, changed, this._state, oldState);
}
}
function callAll(fns) {

@ -161,9 +161,12 @@ function _set(newState) {
this._state = assign({}, oldState, newState);
this._recompute(changed, this._state);
if (this._bind) this._bind(changed, this._state);
if (this._fragment) {
dispatchObservers(this, this._observers.pre, changed, this._state, oldState);
this._fragment.p(changed, this._state);
dispatchObservers(this, this._observers.post, changed, this._state, oldState);
}
}
function callAll(fns) {
@ -194,78 +197,51 @@ var proto = {
/* generated by Svelte vX.Y.Z */
function create_main_fragment(state, component) {
var audio, audio_updating = false, audio_animationframe, audio_paused_value = true;
function audio_progress_loadedmetadata_handler() {
audio_updating = true;
component.set({ buffered: timeRangesToArray(audio.buffered) });
audio_updating = false;
}
function audio_loadedmetadata_handler() {
audio_updating = true;
component.set({ seekable: timeRangesToArray(audio.seekable) });
audio_updating = false;
}
var audio, audio_is_paused = true, audio_updating = false, audio_animationframe;
function audio_timeupdate_handler() {
audio_updating = true;
component.set({ played: timeRangesToArray(audio.played) });
audio_updating = false;
}
function audio_timeupdate_handler_1() {
audio_updating = true;
cancelAnimationFrame(audio_animationframe);
if (!audio.paused) audio_animationframe = requestAnimationFrame(audio_timeupdate_handler_1);
component.set({ currentTime: audio.currentTime });
if (!audio.paused) audio_animationframe = requestAnimationFrame(audio_timeupdate_handler);
audio_updating = true;
component.set({ played: timeRangesToArray(audio.played), currentTime: audio.currentTime });
audio_updating = false;
}
function audio_durationchange_handler() {
audio_updating = true;
component.set({ duration: audio.duration });
audio_updating = false;
}
function audio_pause_handler() {
function audio_play_pause_handler() {
audio_updating = true;
component.set({ paused: audio.paused });
audio_updating = false;
}
function audio_progress_handler() {
component.set({ buffered: timeRangesToArray(audio.buffered) });
}
function audio_loadedmetadata_handler() {
component.set({ buffered: timeRangesToArray(audio.buffered), seekable: timeRangesToArray(audio.seekable) });
}
return {
c: function create() {
audio = createElement("audio");
addListener(audio, "play", audio_pause_handler);
this.h();
},
h: function hydrate() {
component._root._beforecreate.push(audio_progress_loadedmetadata_handler);
addListener(audio, "progress", audio_progress_loadedmetadata_handler);
addListener(audio, "loadedmetadata", audio_progress_loadedmetadata_handler);
component._root._beforecreate.push(audio_loadedmetadata_handler);
addListener(audio, "loadedmetadata", audio_loadedmetadata_handler);
component._root._beforecreate.push(audio_timeupdate_handler);
addListener(audio, "timeupdate", audio_timeupdate_handler);
component._root._beforecreate.push(audio_timeupdate_handler_1);
addListener(audio, "timeupdate", audio_timeupdate_handler_1);
component._root._beforecreate.push(audio_durationchange_handler);
if (!('played' in state && 'currentTime' in state)) component._root._beforecreate.push(audio_timeupdate_handler);
addListener(audio, "durationchange", audio_durationchange_handler);
component._root._beforecreate.push(audio_pause_handler);
addListener(audio, "pause", audio_pause_handler);
if (!('duration' in state)) component._root._beforecreate.push(audio_durationchange_handler);
addListener(audio, "play", audio_play_pause_handler);
addListener(audio, "pause", audio_play_pause_handler);
addListener(audio, "progress", audio_progress_handler);
if (!('buffered' in state)) component._root._beforecreate.push(audio_progress_handler);
addListener(audio, "loadedmetadata", audio_loadedmetadata_handler);
if (!('buffered' in state && 'seekable' in state)) component._root._beforecreate.push(audio_loadedmetadata_handler);
},
m: function mount(target, anchor) {
@ -273,13 +249,8 @@ function create_main_fragment(state, component) {
},
p: function update(changed, state) {
if (!audio_updating && !isNaN(state.currentTime )) {
audio.currentTime = state.currentTime ;
}
if (audio_paused_value !== (audio_paused_value = state.paused)) {
audio[audio_paused_value ? "pause" : "play"]();
}
if (!audio_updating && !isNaN(state.currentTime )) audio.currentTime = state.currentTime ;
if (!audio_updating && audio_is_paused !== (audio_is_paused = state.paused)) audio[audio_is_paused ? "pause" : "play"]();
},
u: function unmount() {
@ -287,14 +258,12 @@ function create_main_fragment(state, component) {
},
d: function destroy$$1() {
removeListener(audio, "progress", audio_progress_loadedmetadata_handler);
removeListener(audio, "loadedmetadata", audio_progress_loadedmetadata_handler);
removeListener(audio, "loadedmetadata", audio_loadedmetadata_handler);
removeListener(audio, "timeupdate", audio_timeupdate_handler);
removeListener(audio, "timeupdate", audio_timeupdate_handler_1);
removeListener(audio, "durationchange", audio_durationchange_handler);
removeListener(audio, "pause", audio_pause_handler);
removeListener(audio, "play", audio_pause_handler);
removeListener(audio, "play", audio_play_pause_handler);
removeListener(audio, "pause", audio_play_pause_handler);
removeListener(audio, "progress", audio_progress_handler);
removeListener(audio, "loadedmetadata", audio_loadedmetadata_handler);
}
};
}

@ -2,78 +2,51 @@
import { addListener, assign, callAll, createElement, detachNode, init, insertNode, proto, removeListener, timeRangesToArray } from "svelte/shared.js";
function create_main_fragment(state, component) {
var audio, audio_updating = false, audio_animationframe, audio_paused_value = true;
function audio_progress_loadedmetadata_handler() {
audio_updating = true;
component.set({ buffered: timeRangesToArray(audio.buffered) });
audio_updating = false;
}
function audio_loadedmetadata_handler() {
audio_updating = true;
component.set({ seekable: timeRangesToArray(audio.seekable) });
audio_updating = false;
}
var audio, audio_is_paused = true, audio_updating = false, audio_animationframe;
function audio_timeupdate_handler() {
audio_updating = true;
component.set({ played: timeRangesToArray(audio.played) });
audio_updating = false;
}
function audio_timeupdate_handler_1() {
audio_updating = true;
cancelAnimationFrame(audio_animationframe);
if (!audio.paused) audio_animationframe = requestAnimationFrame(audio_timeupdate_handler_1);
component.set({ currentTime: audio.currentTime });
if (!audio.paused) audio_animationframe = requestAnimationFrame(audio_timeupdate_handler);
audio_updating = true;
component.set({ played: timeRangesToArray(audio.played), currentTime: audio.currentTime });
audio_updating = false;
}
function audio_durationchange_handler() {
audio_updating = true;
component.set({ duration: audio.duration });
audio_updating = false;
}
function audio_pause_handler() {
function audio_play_pause_handler() {
audio_updating = true;
component.set({ paused: audio.paused });
audio_updating = false;
}
function audio_progress_handler() {
component.set({ buffered: timeRangesToArray(audio.buffered) });
}
function audio_loadedmetadata_handler() {
component.set({ buffered: timeRangesToArray(audio.buffered), seekable: timeRangesToArray(audio.seekable) });
}
return {
c: function create() {
audio = createElement("audio");
addListener(audio, "play", audio_pause_handler);
this.h();
},
h: function hydrate() {
component._root._beforecreate.push(audio_progress_loadedmetadata_handler);
addListener(audio, "progress", audio_progress_loadedmetadata_handler);
addListener(audio, "loadedmetadata", audio_progress_loadedmetadata_handler);
component._root._beforecreate.push(audio_loadedmetadata_handler);
addListener(audio, "loadedmetadata", audio_loadedmetadata_handler);
component._root._beforecreate.push(audio_timeupdate_handler);
addListener(audio, "timeupdate", audio_timeupdate_handler);
component._root._beforecreate.push(audio_timeupdate_handler_1);
addListener(audio, "timeupdate", audio_timeupdate_handler_1);
component._root._beforecreate.push(audio_durationchange_handler);
if (!('played' in state && 'currentTime' in state)) component._root._beforecreate.push(audio_timeupdate_handler);
addListener(audio, "durationchange", audio_durationchange_handler);
component._root._beforecreate.push(audio_pause_handler);
addListener(audio, "pause", audio_pause_handler);
if (!('duration' in state)) component._root._beforecreate.push(audio_durationchange_handler);
addListener(audio, "play", audio_play_pause_handler);
addListener(audio, "pause", audio_play_pause_handler);
addListener(audio, "progress", audio_progress_handler);
if (!('buffered' in state)) component._root._beforecreate.push(audio_progress_handler);
addListener(audio, "loadedmetadata", audio_loadedmetadata_handler);
if (!('buffered' in state && 'seekable' in state)) component._root._beforecreate.push(audio_loadedmetadata_handler);
},
m: function mount(target, anchor) {
@ -81,13 +54,8 @@ function create_main_fragment(state, component) {
},
p: function update(changed, state) {
if (!audio_updating && !isNaN(state.currentTime )) {
audio.currentTime = state.currentTime ;
}
if (audio_paused_value !== (audio_paused_value = state.paused)) {
audio[audio_paused_value ? "pause" : "play"]();
}
if (!audio_updating && !isNaN(state.currentTime )) audio.currentTime = state.currentTime ;
if (!audio_updating && audio_is_paused !== (audio_is_paused = state.paused)) audio[audio_is_paused ? "pause" : "play"]();
},
u: function unmount() {
@ -95,14 +63,12 @@ function create_main_fragment(state, component) {
},
d: function destroy() {
removeListener(audio, "progress", audio_progress_loadedmetadata_handler);
removeListener(audio, "loadedmetadata", audio_progress_loadedmetadata_handler);
removeListener(audio, "loadedmetadata", audio_loadedmetadata_handler);
removeListener(audio, "timeupdate", audio_timeupdate_handler);
removeListener(audio, "timeupdate", audio_timeupdate_handler_1);
removeListener(audio, "durationchange", audio_durationchange_handler);
removeListener(audio, "pause", audio_pause_handler);
removeListener(audio, "play", audio_pause_handler);
removeListener(audio, "play", audio_play_pause_handler);
removeListener(audio, "pause", audio_play_pause_handler);
removeListener(audio, "progress", audio_progress_handler);
removeListener(audio, "loadedmetadata", audio_loadedmetadata_handler);
}
};
}

@ -147,9 +147,12 @@ function _set(newState) {
this._state = assign({}, oldState, newState);
this._recompute(changed, this._state);
if (this._bind) this._bind(changed, this._state);
if (this._fragment) {
dispatchObservers(this, this._observers.pre, changed, this._state, oldState);
this._fragment.p(changed, this._state);
dispatchObservers(this, this._observers.post, changed, this._state, oldState);
}
}
function callAll(fns) {

@ -133,9 +133,12 @@ function _set(newState) {
this._state = assign({}, oldState, newState);
this._recompute(changed, this._state);
if (this._bind) this._bind(changed, this._state);
if (this._fragment) {
dispatchObservers(this, this._observers.pre, changed, this._state, oldState);
this._fragment.p(changed, this._state);
dispatchObservers(this, this._observers.post, changed, this._state, oldState);
}
}
function callAll(fns) {

@ -133,9 +133,12 @@ function _set(newState) {
this._state = assign({}, oldState, newState);
this._recompute(changed, this._state);
if (this._bind) this._bind(changed, this._state);
if (this._fragment) {
dispatchObservers(this, this._observers.pre, changed, this._state, oldState);
this._fragment.p(changed, this._state);
dispatchObservers(this, this._observers.post, changed, this._state, oldState);
}
}
function callAll(fns) {

@ -157,9 +157,12 @@ function _set(newState) {
this._state = assign({}, oldState, newState);
this._recompute(changed, this._state);
if (this._bind) this._bind(changed, this._state);
if (this._fragment) {
dispatchObservers(this, this._observers.pre, changed, this._state, oldState);
this._fragment.p(changed, this._state);
dispatchObservers(this, this._observers.post, changed, this._state, oldState);
}
}
function callAll(fns) {

@ -0,0 +1,21 @@
export default {
// This is a bit of a funny one — there's no equivalent attribute,
// so it can't be server-rendered
'skip-ssr': true,
data: {
indeterminate: true
},
html: `
<input type='checkbox'>
`,
test(assert, component, target) {
const input = target.querySelector('input');
assert.ok(input.indeterminate);
component.set({ indeterminate: false });
assert.ok(!input.indeterminate);
}
};

@ -0,0 +1 @@
<input type='checkbox' indeterminate='{{indeterminate}}'>

@ -0,0 +1,42 @@
export default {
'skip-ssr': true,
data: {
indeterminate: true,
},
html: `
<input type="checkbox">
<p>checked? false</p>
<p>indeterminate? true</p>
`,
test(assert, component, target, window) {
const input = target.querySelector('input');
assert.equal(input.checked, false);
assert.equal(input.indeterminate, true);
const event = new window.Event('change');
input.checked = true;
input.indeterminate = false;
input.dispatchEvent(event);
assert.equal(component.get('indeterminate'), false);
assert.equal(component.get('checked'), true);
assert.htmlEqual(target.innerHTML, `
<input type="checkbox">
<p>checked? true</p>
<p>indeterminate? false</p>
`);
component.set({ indeterminate: true });
assert.equal(input.indeterminate, true);
assert.equal(input.checked, true);
assert.htmlEqual(target.innerHTML, `
<input type="checkbox">
<p>checked? true</p>
<p>indeterminate? true</p>
`);
},
};

@ -0,0 +1,3 @@
<input type='checkbox' bind:checked bind:indeterminate>
<p>checked? {{checked}}</p>
<p>indeterminate? {{indeterminate}}</p>

@ -0,0 +1,40 @@
export default {
data: {
values: [1, 2, 3],
foo: 2
},
html: `
<select>
<option value='1'>1</option>
<option value='2'>2</option>
<option value='3'>3</option>
</select>
<p>foo: 2</p>
`,
test(assert, component, target, window) {
const select = target.querySelector('select');
const options = [...target.querySelectorAll('option')];
assert.ok(options[1].selected);
assert.equal(component.get('foo'), 2);
const change = new window.Event('change');
options[2].selected = true;
select.dispatchEvent(change);
assert.equal(component.get('foo'), 3);
assert.htmlEqual( target.innerHTML, `
<select>
<option value='1'>1</option>
<option value='2'>2</option>
<option value='3'>3</option>
</select>
<p>foo: 3</p>
` );
}
};

@ -0,0 +1,7 @@
<select bind:value='foo'>
{{#each values as v}}
<option>{{v}}</option>
{{/each}}
</select>
<p>foo: {{foo}}</p>

@ -5,9 +5,9 @@ export default {
<p>selected: a</p>
<select>
<option>a</option>
<option>b</option>
<option>c</option>
<option value='a'>a</option>
<option value='b'>b</option>
<option value='c'>c</option>
</select>
<p>selected: a</p>

@ -3,9 +3,9 @@ export default {
<p>selected: b</p>
<select>
<option>a</option>
<option>b</option>
<option>c</option>
<option value='a'>a</option>
<option value='b'>b</option>
<option value='c'>c</option>
</select>
<p>selected: b</p>

@ -22,9 +22,9 @@ export default {
assert.htmlEqual( target.innerHTML, `
<select>
<option>one</option>
<option>two</option>
<option>three</option>
<option value='one'>one</option>
<option value='two'>two</option>
<option value='three'>three</option>
</select>
<p>selected: two</p>
` );

@ -0,0 +1,39 @@
export default {
skip: true, // JSDOM
html: `
<h1>Hello Harry!</h1>
<select>
<option value="Harry">Harry</option>
<optgroup label="Group">
<option value="World">World</option>
</optgroup>
</select>
`,
test(assert, component, target, window) {
const select = target.querySelector('select');
const options = [...target.querySelectorAll('option')];
assert.deepEqual(options, select.options);
assert.equal(component.get('name'), 'Harry');
const change = new window.Event('change');
options[1].selected = true;
select.dispatchEvent(change);
assert.equal(component.get('name'), 'World');
assert.htmlEqual(target.innerHTML, `
<h1>Hello World!</h1>
<select>
<option value="Harry">Harry</option>
<optgroup label="Group">
<option value="World">World</option>
</optgroup>
</select>
`);
},
};

@ -0,0 +1,8 @@
<h1>Hello {{name}}!</h1>
<select bind:value="name">
<option value="Harry">Harry</option>
<optgroup label="Group">
<option value="World">World</option>
</optgroup>
</select>

@ -3,9 +3,9 @@ export default {
<p>selected: one</p>
<select>
<option>one</option>
<option>two</option>
<option>three</option>
<option value='one'>one</option>
<option value='two'>two</option>
<option value='three'>three</option>
</select>
<p>selected: one</p>
@ -32,9 +32,9 @@ export default {
<p>selected: two</p>
<select>
<option>one</option>
<option>two</option>
<option>three</option>
<option value='one'>one</option>
<option value='two'>two</option>
<option value='three'>three</option>
</select>
<p>selected: two</p>

@ -0,0 +1 @@
<button on:click="set({show:false})">Hide</button>

@ -0,0 +1,27 @@
export default {
data: {
show: true
},
html: `
<button>Hide</button>
`,
test(assert, component, target, window) {
const click = new window.MouseEvent('click');
target.querySelector('button').dispatchEvent(click);
assert.equal(component.get('show'), false);
assert.htmlEqual(target.innerHTML, `
<button>Show</button>
`);
target.querySelector('button').dispatchEvent(click);
assert.equal(component.get('show'), true);
assert.htmlEqual(target.innerHTML, `
<button>Hide</button>
`);
}
};

@ -0,0 +1,14 @@
{{#if show}}
<Nested bind:show/>
{{else}}
<button on:click="set({show:true})">Show</button>
{{/if}}
<script>
import Nested from './Nested.html';
export default {
components: {
Nested
}
};
</script>

@ -34,9 +34,11 @@ describe("sourcemaps", () => {
cascade: config.cascade
});
const _code = code.replace(/Svelte v\d+\.\d+\.\d+/, match => match.replace(/\d/g, 'x'));
fs.writeFileSync(
`${outputFilename}.js`,
`${code}\n//# sourceMappingURL=output.js.map`
`${_code}\n//# sourceMappingURL=output.js.map`
);
fs.writeFileSync(
`${outputFilename}.js.map`,
@ -62,12 +64,12 @@ describe("sourcemaps", () => {
const locateInSource = getLocator(input);
const smc = new SourceMapConsumer(map);
const locateInGenerated = getLocator(code);
const locateInGenerated = getLocator(_code);
const smcCss = cssMap && new SourceMapConsumer(cssMap);
const locateInGeneratedCss = getLocator(css || '');
test({ assert, code, map, smc, smcCss, locateInSource, locateInGenerated, locateInGeneratedCss });
test({ assert, code: _code, map, smc, smcCss, locateInSource, locateInGenerated, locateInGeneratedCss });
});
});
});

@ -0,0 +1,10 @@
<button on:click='foo()'></button>
<script>
export default {
methods: {
'foo': () => {},
'bar': () => {}
}
};
</script>
Loading…
Cancel
Save