Merge branch 'gh-1147' into gh-1082-script-style-test

pull/1087/head
Conduitry 8 years ago
commit 4b4737af9f

@ -1,5 +1,37 @@
# Svelte changelog # Svelte changelog
## 1.54.1
* Hoist destructured references ([#1139](https://github.com/sveltejs/svelte/issues/1139))
* Add `bind:volume` for media elements ([#1143](https://github.com/sveltejs/svelte/issues/1143))
## 1.54.0
* Run `oncreate` hooks depth-first, top-to-bottom ([#1135](https://github.com/sveltejs/svelte/issues/1135))
* Render boolean attributes correctly in SSR mode ([#1109](https://github.com/sveltejs/svelte/issues/1109))
* Add `feed` aria role to expected roles when doing a11y checks ([#1124](https://github.com/sveltejs/svelte/pull/1124))
* More complete fix for case sensitive attributes ([#1062](https://github.com/sveltejs/svelte/issues/1062))
* Handle CLRF line endings in await block comments ([#1132](https://github.com/sveltejs/svelte/issues/1132))
## 1.53.0
* Base scoping selectors on `<style>` contents alone ([#1091](https://github.com/sveltejs/svelte/issues/1091))
## 1.52.0
* Deconflict referenced globals ([#1079](https://github.com/sveltejs/svelte/issues/1079))
* Validate contents of `await` blocks ([#1061](https://github.com/sveltejs/svelte/issues/1061))
* Fire `oncreate` for components in `await` blocks ([#1061](https://github.com/sveltejs/svelte/issues/1061))
* Automatically fix attribute casing ([#1062](https://github.com/sveltejs/svelte/issues/1062))
* Fix escaping in `<script>` and `<style>` ([#1082](https://github.com/sveltejs/svelte/issues/1082))
* Error if invalid characters are used in computed properties, and allow any valid identifier in props ([#1083](https://github.com/sveltejs/svelte/issues/1083))
* Don't run a11y tests on components ([#1110](https://github.com/sveltejs/svelte/issues/1110))
* Respect `store` option in SSR mode ([#1107](https://github.com/sveltejs/svelte/issues/1107))
## 1.51.1
* Only escape <, > and & characters ([#1082](https://github.com/sveltejs/svelte/issues/1082))
## 1.51.0 ## 1.51.0
* Lock `scroll` bindings ([#1071](https://github.com/sveltejs/svelte/issues/1071)) * Lock `scroll` bindings ([#1071](https://github.com/sveltejs/svelte/issues/1071))

@ -1,6 +1,6 @@
{ {
"name": "svelte", "name": "svelte",
"version": "1.51.0", "version": "1.54.1",
"description": "The magical disappearing UI framework", "description": "The magical disappearing UI framework",
"main": "compiler/svelte.js", "main": "compiler/svelte.js",
"files": [ "files": [
@ -56,7 +56,8 @@
"eslint-plugin-import": "^2.2.0", "eslint-plugin-import": "^2.2.0",
"estree-walker": "^0.5.1", "estree-walker": "^0.5.1",
"glob": "^7.1.1", "glob": "^7.1.1",
"jsdom": "^11.1.0", "is-reference": "^1.1.0",
"jsdom": "^11.6.1",
"locate-character": "^2.0.0", "locate-character": "^2.0.0",
"magic-string": "^0.22.3", "magic-string": "^0.22.3",
"mocha": "^3.2.0", "mocha": "^3.2.0",

@ -3,6 +3,7 @@ import { walk } from 'estree-walker';
import { getLocator } from 'locate-character'; import { getLocator } from 'locate-character';
import Selector from './Selector'; import Selector from './Selector';
import getCodeFrame from '../utils/getCodeFrame'; import getCodeFrame from '../utils/getCodeFrame';
import hash from '../utils/hash';
import Element from '../generators/nodes/Element'; import Element from '../generators/nodes/Element';
import { Validator } from '../validate/index'; import { Validator } from '../validate/index';
import { Node, Parsed, Warning } from '../interfaces'; import { Node, Parsed, Warning } from '../interfaces';
@ -269,12 +270,12 @@ export default class Stylesheet {
this.cascade = cascade; this.cascade = cascade;
this.filename = filename; this.filename = filename;
this.id = `svelte-${parsed.hash}`;
this.children = []; this.children = [];
this.keyframes = new Map(); this.keyframes = new Map();
if (parsed.css && parsed.css.children.length) { if (parsed.css && parsed.css.children.length) {
this.id = `svelte-${hash(parsed.css.content.styles)}`;
this.hasStyles = true; this.hasStyles = true;
const stack: (Rule | Atrule)[] = []; const stack: (Rule | Atrule)[] = [];

@ -244,13 +244,20 @@ export default class Generator {
} else if (contexts.has(name)) { } else if (contexts.has(name)) {
const contextName = contexts.get(name); const contextName = contexts.get(name);
if (contextName !== name) { if (contextName !== name) {
// this is true for 'reserved' names like `state` and `component` // this is true for 'reserved' names like `state` and `component`,
// also destructured contexts
code.overwrite( code.overwrite(
node.start, node.start,
node.start + name.length, node.start + name.length,
contextName, contextName,
{ storeName: true, contentOnly: false } { storeName: true, contentOnly: false }
); );
const destructuredName = contextName.replace(/\[\d+\]/, '');
if (destructuredName !== contextName) {
// so that hoisting the context works correctly
usedContexts.add(destructuredName);
}
} }
usedContexts.add(name); usedContexts.add(name);
@ -410,11 +417,16 @@ export default class Generator {
const indentationLevel = getIndentationLevel(source, js.content.body[0].start); const indentationLevel = getIndentationLevel(source, js.content.body[0].start);
const indentExclusionRanges = getIndentExclusionRanges(js.content); const indentExclusionRanges = getIndentExclusionRanges(js.content);
const scope = annotateWithScopes(js.content); const { scope, globals } = annotateWithScopes(js.content);
scope.declarations.forEach(name => { scope.declarations.forEach(name => {
this.userVars.add(name); this.userVars.add(name);
}); });
globals.forEach(name => {
this.userVars.add(name);
});
const body = js.content.body.slice(); // slice, because we're going to be mutating the original const body = js.content.body.slice(); // slice, because we're going to be mutating the original
// imports need to be hoisted out of the IIFE // imports need to be hoisted out of the IIFE
@ -666,7 +678,7 @@ export default class Generator {
isEventHandler: boolean isEventHandler: boolean
) => { ) => {
this.addSourcemapLocations(node); // TODO this involves an additional walk — can we roll it in somewhere else? this.addSourcemapLocations(node); // TODO this involves an additional walk — can we roll it in somewhere else?
let scope = annotateWithScopes(node); let { scope } = annotateWithScopes(node);
const dependencies: Set<string> = new Set(); const dependencies: Set<string> = new Set();
@ -767,12 +779,9 @@ export default class Generator {
contextDependencies.set(node.context, node.metadata.dependencies); contextDependencies.set(node.context, node.metadata.dependencies);
if (node.destructuredContexts) { if (node.destructuredContexts) {
for (let i = 0; i < node.destructuredContexts.length; i += 1) { node.destructuredContexts.forEach((name: string) => {
const name = node.destructuredContexts[i];
const value = `${node.context}[${i}]`;
contextDependencies.set(name, node.metadata.dependencies); contextDependencies.set(name, node.metadata.dependencies);
} });
} }
contextDependenciesStack.push(contextDependencies); contextDependenciesStack.push(contextDependencies);

@ -245,20 +245,20 @@ export default function dom(
${(templateProperties.oncreate || generator.hasComponents || generator.hasComplexBindings || generator.hasIntroTransitions) && deindent` ${(templateProperties.oncreate || generator.hasComponents || generator.hasComplexBindings || generator.hasIntroTransitions) && deindent`
if (!options.root) { if (!options.root) {
this._oncreate = [${templateProperties.oncreate && `_oncreate`}]; this._oncreate = [];
${(generator.hasComponents || generator.hasComplexBindings) && `this._beforecreate = [];`} ${(generator.hasComponents || generator.hasComplexBindings) && `this._beforecreate = [];`}
${(generator.hasComponents || generator.hasIntroTransitions) && `this._aftercreate = [];`} ${(generator.hasComponents || generator.hasIntroTransitions) && `this._aftercreate = [];`}
} ${templateProperties.oncreate && deindent` }
else {
this.root._oncreate.push(_oncreate);
}
`}
`} `}
${generator.slots.size && `this.slots = {};`} ${generator.slots.size && `this.slots = {};`}
this._fragment = @create_main_fragment(this._state, this); this._fragment = @create_main_fragment(this._state, this);
${(templateProperties.oncreate) && deindent`
this.root._oncreate.push(_oncreate);
`}
${generator.customElement ? deindent` ${generator.customElement ? deindent`
this._fragment.c(); this._fragment.c();
this._fragment.${block.hasIntroMethod ? 'i' : 'm'}(this.shadowRoot, null); this._fragment.${block.hasIntroMethod ? 'i' : 'm'}(this.shadowRoot, null);
@ -407,7 +407,7 @@ export default function dom(
const code = new MagicString(str); const code = new MagicString(str);
const expression = parseExpressionAt(str, 0); const expression = parseExpressionAt(str, 0);
let scope = annotateWithScopes(expression); let { scope } = annotateWithScopes(expression);
walk(expression, { walk(expression, {
enter(node: Node, parent: Node) { enter(node: Node, parent: Node) {

@ -1,5 +1,6 @@
import deindent from '../../utils/deindent'; import deindent from '../../utils/deindent';
import { stringify } from '../../utils/stringify'; import { stringify } from '../../utils/stringify';
import fixAttributeCasing from '../../utils/fixAttributeCasing';
import getExpressionPrecedence from '../../utils/getExpressionPrecedence'; import getExpressionPrecedence from '../../utils/getExpressionPrecedence';
import { DomGenerator } from '../dom/index'; import { DomGenerator } from '../dom/index';
import Node from './shared/Node'; import Node from './shared/Node';
@ -43,7 +44,7 @@ export default class Attribute {
render(block: Block) { render(block: Block) {
const node = this.parent; const node = this.parent;
const name = this.name; const name = fixAttributeCasing(this.name);
if (name === 'style') { if (name === 'style') {
const styleProps = optimizeStyle(this.value); const styleProps = optimizeStyle(this.value);
@ -539,6 +540,7 @@ const attributeLookup = {
'textarea', 'textarea',
], ],
}, },
volume: { appliesTo: ['audio', 'video'] },
width: { width: {
appliesTo: ['canvas', 'embed', 'iframe', 'img', 'input', 'object', 'video'], appliesTo: ['canvas', 'embed', 'iframe', 'img', 'input', 'object', 'video'],
}, },
@ -662,4 +664,4 @@ function getStyleValue(chunks: Node[]) {
function isDynamic(value: Node[]) { function isDynamic(value: Node[]) {
return value.length > 1 || value[0].type !== 'Text'; return value.length > 1 || value[0].type !== 'Text';
} }

@ -101,6 +101,10 @@ export default class AwaitBlock extends Node {
block.addVariable(promise); block.addVariable(promise);
block.addVariable(resolved); block.addVariable(resolved);
// the `#component.root.set({})` below is just a cheap way to flush
// any oncreate handlers. We could have a dedicated `flush()` method
// but it's probably not worth it
block.builders.init.addBlock(deindent` block.builders.init.addBlock(deindent`
function ${replace_await_block}(${token}, type, ${value}, ${params}) { function ${replace_await_block}(${token}, type, ${value}, ${params}) {
if (${token} !== ${await_token}) return; if (${token} !== ${await_token}) return;
@ -113,6 +117,8 @@ export default class AwaitBlock extends Node {
${old_block}.d(); ${old_block}.d();
${await_block}.c(); ${await_block}.c();
${await_block}.m(${updateMountNode}, ${anchor}); ${await_block}.m(${updateMountNode}, ${anchor});
#component.root.set({});
} }
} }
@ -121,8 +127,10 @@ export default class AwaitBlock extends Node {
if (@isPromise(${promise})) { if (@isPromise(${promise})) {
${promise}.then(function(${value}) { ${promise}.then(function(${value}) {
var state = #component.get();
${replace_await_block}(${token}, ${create_then_block}, ${value}, ${params}); ${replace_await_block}(${token}, ${create_then_block}, ${value}, ${params});
}, function (${error}) { }, function (${error}) {
var state = #component.get();
${replace_await_block}(${token}, ${create_catch_block}, ${error}, ${params}); ${replace_await_block}(${token}, ${create_catch_block}, ${error}, ${params});
}); });

@ -73,9 +73,11 @@ export default class Binding extends Node {
); );
} }
if (this.name === 'currentTime') { if (this.name === 'currentTime' || this.name === 'volume') {
updateCondition = `!isNaN(${snippet})`; updateCondition = `!isNaN(${snippet})`;
initialUpdate = null;
if (this.name === 'currentTime')
initialUpdate = null;
} }
if (this.name === 'paused') { if (this.name === 'paused') {
@ -267,4 +269,4 @@ function isComputed(node: Node) {
} }
return false; return false;
} }

@ -4,6 +4,7 @@ import flattenReference from '../../utils/flattenReference';
import isVoidElementName from '../../utils/isVoidElementName'; import isVoidElementName from '../../utils/isVoidElementName';
import validCalleeObjects from '../../utils/validCalleeObjects'; import validCalleeObjects from '../../utils/validCalleeObjects';
import reservedNames from '../../utils/reservedNames'; import reservedNames from '../../utils/reservedNames';
import fixAttributeCasing from '../../utils/fixAttributeCasing';
import Node from './shared/Node'; import Node from './shared/Node';
import Block from '../dom/Block'; import Block from '../dom/Block';
import Attribute from './Attribute'; import Attribute from './Attribute';
@ -427,12 +428,12 @@ export default class Element extends Node {
} }
node.attributes.forEach((attr: Node) => { node.attributes.forEach((attr: Node) => {
open += ` ${attr.name}${stringifyAttributeValue(attr.value)}` open += ` ${fixAttributeCasing(attr.name)}${stringifyAttributeValue(attr.value)}`
}); });
if (isVoidElementName(node.name)) return open + '>'; if (isVoidElementName(node.name)) return open + '>';
if (node.name === 'script' || node.name === 'style') { if (node.name === 'script') {
return `${open}>${node.data}</${node.name}>`; return `${open}>${node.data}</${node.name}>`;
} }
@ -759,5 +760,11 @@ const events = [
filter: (node: Element, name: string) => filter: (node: Element, name: string) =>
node.isMediaNode() && node.isMediaNode() &&
(name === 'buffered' || name === 'seekable') (name === 'buffered' || name === 'seekable')
},
{
eventNames: ['volumechange'],
filter: (node: Element, name: string) =>
node.isMediaNode() &&
name === 'volume'
} }
]; ];

@ -114,7 +114,6 @@ export default function ssr(
} }
var result = { head: '', addComponent }; var result = { head: '', addComponent };
${templateProperties.store && `options.store = %store();`}
var html = ${name}._render(result, state, options); var html = ${name}._render(result, state, options);
var cssCode = Array.from(components).map(c => c.css && c.css.code).filter(Boolean).join('\\n'); var cssCode = Array.from(components).map(c => c.css && c.css.code).filter(Boolean).join('\\n');
@ -130,6 +129,7 @@ export default function ssr(
} }
${name}._render = function(__result, state, options) { ${name}._render = function(__result, state, options) {
${templateProperties.store && `options.store = %store();`}
__result.addComponent(${name}); __result.addComponent(${name});
state = Object.assign(${initialState.join(', ')}); state = Object.assign(${initialState.join(', ')});

@ -7,6 +7,10 @@ import Element from '../../nodes/Element';
import Block from '../Block'; import Block from '../Block';
import { Node } from '../../../interfaces'; import { Node } from '../../../interfaces';
import stringifyAttributeValue from './shared/stringifyAttributeValue'; import stringifyAttributeValue from './shared/stringifyAttributeValue';
import { escape } from '../../../utils/stringify';
// source: https://gist.github.com/ArjanSchouten/0b8574a6ad7f5065a5e7
const booleanAttributes = new Set('async autocomplete autofocus autoplay border challenge checked compact contenteditable controls default defer disabled formnovalidate frameborder hidden indeterminate ismap loop multiple muted nohref noresize noshade novalidate nowrap open readonly required reversed scoped scrolling seamless selected sortable spellcheck translate'.split(' '));
export default function visitElement( export default function visitElement(
generator: SsrGenerator, generator: SsrGenerator,
@ -35,14 +39,18 @@ export default function visitElement(
if (attribute.name === 'value' && node.name === 'textarea') { if (attribute.name === 'value' && node.name === 'textarea') {
textareaContents = stringifyAttributeValue(block, attribute.value); textareaContents = stringifyAttributeValue(block, attribute.value);
} else if (attribute.value === true) {
openingTag += ` ${attribute.name}`;
} else if (
booleanAttributes.has(attribute.name) &&
attribute.value.length === 1 &&
attribute.value[0].type !== 'Text'
) {
// a boolean attribute with one non-Text chunk
block.contextualise(attribute.value[0].expression);
openingTag += '${' + attribute.value[0].metadata.snippet + ' ? " ' + attribute.name + '" : "" }';
} else { } else {
let str = ` ${attribute.name}`; openingTag += ` ${attribute.name}="${stringifyAttributeValue(block, attribute.value)}"`;
if (attribute.value !== true) {
str += `="${stringifyAttributeValue(block, attribute.value)}"`;
}
openingTag += str;
} }
}); });
@ -60,8 +68,8 @@ export default function visitElement(
if (node.name === 'textarea' && textareaContents !== undefined) { if (node.name === 'textarea' && textareaContents !== undefined) {
generator.append(textareaContents); generator.append(textareaContents);
} else if (node.name === 'script' || node.name === 'style') { } else if (node.name === 'script') {
generator.append(node.data); generator.append(escape(node.data));
} else { } else {
node.children.forEach((child: Node) => { node.children.forEach((child: Node) => {
visit(generator, block, child); visit(generator, block, child);

@ -1,10 +1,12 @@
import { isIdentifierStart, isIdentifierChar } from 'acorn';
import { locate, Location } from 'locate-character'; import { locate, Location } from 'locate-character';
import fragment from './state/fragment'; import fragment from './state/fragment';
import { whitespace } from '../utils/patterns'; import { whitespace } from '../utils/patterns';
import { trimStart, trimEnd } from '../utils/trim'; import { trimStart, trimEnd } from '../utils/trim';
import getCodeFrame from '../utils/getCodeFrame'; import getCodeFrame from '../utils/getCodeFrame';
import reservedNames from '../utils/reservedNames'; import reservedNames from '../utils/reservedNames';
import hash from './utils/hash'; import fullCharCodeAt from '../utils/fullCharCodeAt';
import hash from '../utils/hash';
import { Node, Parsed } from '../interfaces'; import { Node, Parsed } from '../interfaces';
import CompileError from '../utils/CompileError'; import CompileError from '../utils/CompileError';
@ -147,7 +149,22 @@ export class Parser {
readIdentifier() { readIdentifier() {
const start = this.index; const start = this.index;
const identifier = this.read(/[a-zA-Z_$][a-zA-Z0-9_$]*/);
let i = this.index;
const code = fullCharCodeAt(this.template, i);
if (!isIdentifierStart(code, true)) return null;
i += code <= 0xffff ? 1 : 2;
while (i < this.template.length) {
const code = fullCharCodeAt(this.template, i);
if (!isIdentifierChar(code, true)) break;
i += code <= 0xffff ? 1 : 2;
}
const identifier = this.template.slice(this.index, this.index = i);
if (reservedNames.has(identifier)) { if (reservedNames.has(identifier)) {
this.error(`'${identifier}' is a reserved word in JavaScript and cannot be used here`, start); this.error(`'${identifier}' is a reserved word in JavaScript and cannot be used here`, start);
@ -190,7 +207,6 @@ export default function parse(
options: ParserOptions = {} options: ParserOptions = {}
): Parsed { ): Parsed {
const parser = new Parser(template, options); const parser = new Parser(template, options);
return { return {
hash: hash(parser.template), hash: hash(parser.template),
html: parser.html, html: parser.html,

@ -224,11 +224,20 @@ export default function tag(parser: Parser) {
); );
parser.read(/<\/textarea>/); parser.read(/<\/textarea>/);
element.end = parser.index; element.end = parser.index;
} else if (name === 'script' || name === 'style') { } else if (name === 'script') {
// special case // special case
element.data = parser.readUntil(new RegExp(`</${name}>`)); element.data = parser.readUntil(new RegExp(`</${name}>`));
parser.eat(`</${name}>`, true); parser.eat(`</${name}>`, true);
element.end = parser.index; element.end = parser.index;
} else if (name === 'style') {
// special case
element.children = readSequence(
parser,
() =>
parser.template.slice(parser.index, parser.index + 8) === '</style>'
);
parser.read(/<\/style>/);
element.end = parser.index;
} else { } else {
parser.stack.push(element); parser.stack.push(element);
} }

@ -177,7 +177,7 @@ export function setDev(newState) {
} }
export function callAll(fns) { export function callAll(fns) {
while (fns && fns.length) fns.pop()(); while (fns && fns.length) fns.shift()();
} }
export function _mount(target, anchor) { export function _mount(target, anchor) {

@ -1,11 +1,13 @@
import { walk } from 'estree-walker'; import { walk } from 'estree-walker';
import isReference from 'is-reference';
import { Node } from '../interfaces'; import { Node } from '../interfaces';
export default function annotateWithScopes(expression: Node) { export default function annotateWithScopes(expression: Node) {
const globals = new Set();
let scope = new Scope(null, false); let scope = new Scope(null, false);
walk(expression, { walk(expression, {
enter(node: Node) { enter(node: Node, parent: Node) {
if (/Function/.test(node.type)) { if (/Function/.test(node.type)) {
if (node.type === 'FunctionDeclaration') { if (node.type === 'FunctionDeclaration') {
scope.declarations.add(node.id.name); scope.declarations.add(node.id.name);
@ -25,6 +27,10 @@ export default function annotateWithScopes(expression: Node) {
node._scope = scope = new Scope(scope, true); node._scope = scope = new Scope(scope, true);
} else if (/(Function|Class|Variable)Declaration/.test(node.type)) { } else if (/(Function|Class|Variable)Declaration/.test(node.type)) {
scope.addDeclaration(node); scope.addDeclaration(node);
} else if (isReference(node, parent)) {
if (!scope.has(node.name)) {
globals.add(node.name);
}
} }
}, },
@ -35,7 +41,7 @@ export default function annotateWithScopes(expression: Node) {
}, },
}); });
return scope; return { scope, globals };
} }
export class Scope { export class Scope {

@ -17,5 +17,5 @@ export default function createDebuggingComment(node: Node, generator: DomGenerat
const start = locate(c); const start = locate(c);
const loc = `(${start.line + 1}:${start.column})`; const loc = `(${start.line + 1}:${start.column})`;
return `${loc} ${source.slice(c, d)}`.replace(/\n/g, ' '); return `${loc} ${source.slice(c, d)}`.replace(/\s/g, ' ');
} }

@ -0,0 +1,12 @@
const svgAttributes = 'accent-height accumulate additive alignment-baseline allowReorder alphabetic amplitude arabic-form ascent attributeName attributeType autoReverse azimuth baseFrequency baseline-shift baseProfile bbox begin bias by calcMode cap-height class clip clipPathUnits clip-path clip-rule color color-interpolation color-interpolation-filters color-profile color-rendering contentScriptType contentStyleType cursor cx cy d decelerate descent diffuseConstant direction display divisor dominant-baseline dur dx dy edgeMode elevation enable-background end exponent externalResourcesRequired fill fill-opacity fill-rule filter filterRes filterUnits flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight format from fr fx fy g1 g2 glyph-name glyph-orientation-horizontal glyph-orientation-vertical glyphRef gradientTransform gradientUnits hanging height href horiz-adv-x horiz-origin-x id ideographic image-rendering in in2 intercept k k1 k2 k3 k4 kernelMatrix kernelUnitLength kerning keyPoints keySplines keyTimes lang lengthAdjust letter-spacing lighting-color limitingConeAngle local marker-end marker-mid marker-start markerHeight markerUnits markerWidth mask maskContentUnits maskUnits mathematical max media method min mode name numOctaves offset onabort onactivate onbegin onclick onend onerror onfocusin onfocusout onload onmousedown onmousemove onmouseout onmouseover onmouseup onrepeat onresize onscroll onunload opacity operator order orient orientation origin overflow overline-position overline-thickness panose-1 paint-order pathLength patternContentUnits patternTransform patternUnits pointer-events points pointsAtX pointsAtY pointsAtZ preserveAlpha preserveAspectRatio primitiveUnits r radius refX refY rendering-intent repeatCount repeatDur requiredExtensions requiredFeatures restart result rotate rx ry scale seed shape-rendering slope spacing specularConstant specularExponent speed spreadMethod startOffset stdDeviation stemh stemv stitchTiles stop-color stop-opacity strikethrough-position strikethrough-thickness string stroke stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width style surfaceScale systemLanguage tabindex tableValues target targetX targetY text-anchor text-decoration text-rendering textLength to transform type u1 u2 underline-position underline-thickness unicode unicode-bidi unicode-range units-per-em v-alphabetic v-hanging v-ideographic v-mathematical values version vert-adv-y vert-origin-x vert-origin-y viewBox viewTarget visibility width widths word-spacing writing-mode x x-height x1 x2 xChannelSelector xlink:actuate xlink:arcrole xlink:href xlink:role xlink:show xlink:title xlink:type xml:base xml:lang xml:space y y1 y2 yChannelSelector z zoomAndPan'.split(' ');
const svgAttributeLookup = new Map();
svgAttributes.forEach(name => {
svgAttributeLookup.set(name.toLowerCase(), name);
});
export default function fixAttributeCasing(name) {
name = name.toLowerCase();
return svgAttributeLookup.get(name) || name;
}

@ -0,0 +1,10 @@
// Adapted from https://github.com/acornjs/acorn/blob/6584815dca7440e00de841d1dad152302fdd7ca5/src/tokenize.js
// Reproduced under MIT License https://github.com/acornjs/acorn/blob/master/LICENSE
export default function fullCharCodeAt(str: string, i: number): number {
let code = str.charCodeAt(i)
if (code <= 0xd7ff || code >= 0xe000) return code;
let next = str.charCodeAt(i + 1);
return (code << 10) + next - 0x35fdc00;
}

@ -0,0 +1,15 @@
import { isIdentifierStart, isIdentifierChar } from 'acorn';
import fullCharCodeAt from './fullCharCodeAt';
export default function isValidIdentifier(str: string): boolean {
let i = 0;
while (i < str.length) {
const code = fullCharCodeAt(str, i);
if (!(i === 0 ? isIdentifierStart : isIdentifierChar)(code, true)) return false;
i += code <= 0xffff ? 1 : 2;
}
return true;
}

@ -8,7 +8,7 @@ import { Node } from '../../interfaces';
const ariaAttributes = 'activedescendant atomic autocomplete busy checked controls current describedby disabled dropeffect expanded flowto grabbed haspopup hidden invalid label labelledby level live multiline multiselectable orientation owns posinset pressed readonly relevant required selected setsize sort valuemax valuemin valuenow valuetext'.split(' '); const ariaAttributes = 'activedescendant atomic autocomplete busy checked controls current describedby disabled dropeffect expanded flowto grabbed haspopup hidden invalid label labelledby level live multiline multiselectable orientation owns posinset pressed readonly relevant required selected setsize sort valuemax valuemin valuenow valuetext'.split(' ');
const ariaAttributeSet = new Set(ariaAttributes); const ariaAttributeSet = new Set(ariaAttributes);
const ariaRoles = 'alert alertdialog application article banner button checkbox columnheader combobox command complementary composite contentinfo definition dialog directory document form grid gridcell group heading img input landmark link list listbox listitem log main marquee math menu menubar menuitem menuitemcheckbox menuitemradio navigation note option presentation progressbar radio radiogroup range region roletype row rowgroup rowheader scrollbar search section sectionhead select separator slider spinbutton status structure tab tablist tabpanel textbox timer toolbar tooltip tree treegrid treeitem widget window'.split(' '); const ariaRoles = 'alert alertdialog application article banner button checkbox columnheader combobox command complementary composite contentinfo definition dialog directory document feed form grid gridcell group heading img input landmark link list listbox listitem log main marquee math menu menubar menuitem menuitemcheckbox menuitemradio navigation note option presentation progressbar radio radiogroup range region roletype row rowgroup rowheader scrollbar search section sectionhead select separator slider spinbutton status structure tab tablist tabpanel textbox timer toolbar tooltip tree treegrid treeitem widget window'.split(' ');
const ariaRoleSet = new Set(ariaRoles); const ariaRoleSet = new Set(ariaRoles);
const invisibleElements = new Set(['meta', 'html', 'script', 'style']); const invisibleElements = new Set(['meta', 'html', 'script', 'style']);

@ -19,14 +19,29 @@ export default function validateHtml(validator: Validator, html: Node) {
const elementStack: Node[] = []; const elementStack: Node[] = [];
function visit(node: Node) { function visit(node: Node) {
a11y(validator, node, elementStack);
if (node.type === 'Element') { if (node.type === 'Element') {
if (meta.has(node.name)) { if (meta.has(node.name)) {
return meta.get(node.name)(validator, node, refs, refCallees); return meta.get(node.name)(validator, node, refs, refCallees);
} }
validateElement(validator, node, refs, refCallees, stack, elementStack); const isComponent =
node.name === ':Self' ||
node.name === ':Component' ||
validator.components.has(node.name);
validateElement(
validator,
node,
refs,
refCallees,
stack,
elementStack,
isComponent
);
if (!isComponent) {
a11y(validator, node, elementStack);
}
} else if (node.type === 'EachBlock') { } else if (node.type === 'EachBlock') {
if (validator.helpers.has(node.context)) { if (validator.helpers.has(node.context)) {
let c = node.expression.end; let c = node.expression.end;
@ -54,6 +69,12 @@ export default function validateHtml(validator: Validator, html: Node) {
if (node.else) { if (node.else) {
visit(node.else); visit(node.else);
} }
if (node.type === 'AwaitBlock') {
visit(node.pending);
visit(node.then);
visit(node.catch);
}
} }
html.children.forEach(visit); html.children.forEach(visit);

@ -11,11 +11,9 @@ export default function validateElement(
refs: Map<string, Node[]>, refs: Map<string, Node[]>,
refCallees: Node[], refCallees: Node[],
stack: Node[], stack: Node[],
elementStack: Node[] elementStack: Node[],
isComponent: Boolean
) { ) {
const isComponent =
node.name === ':Self' || node.name === ':Component' || validator.components.has(node.name);
if (isComponent) { if (isComponent) {
validator.used.components.add(node.name); validator.used.components.add(node.name);
} }
@ -141,7 +139,8 @@ export default function validateElement(
name === 'paused' || name === 'paused' ||
name === 'buffered' || name === 'buffered' ||
name === 'seekable' || name === 'seekable' ||
name === 'played' name === 'played' ||
name === 'volume'
) { ) {
if (node.name !== 'audio' && node.name !== 'video') { if (node.name !== 'audio' && node.name !== 'video') {
validator.error( validator.error(

@ -11,6 +11,6 @@ export default function validateHead(validator: Validator, node: Node, refs: Map
node.children.forEach(node => { node.children.forEach(node => {
if (node.type !== 'Element') return; // TODO handle {{#if}} and friends? if (node.type !== 'Element') return; // TODO handle {{#if}} and friends?
validateElement(validator, node, refs, refCallees, [], []); validateElement(validator, node, refs, refCallees, [], [], false);
}); });
} }

@ -1,5 +1,8 @@
import checkForDupes from '../utils/checkForDupes'; import checkForDupes from '../utils/checkForDupes';
import checkForComputedKeys from '../utils/checkForComputedKeys'; import checkForComputedKeys from '../utils/checkForComputedKeys';
import getName from '../../../utils/getName';
import isValidIdentifier from '../../../utils/isValidIdentifier';
import reservedNames from '../../../utils/reservedNames';
import { Validator } from '../../'; import { Validator } from '../../';
import { Node } from '../../../interfaces'; import { Node } from '../../../interfaces';
import walkThroughTopFunctionScope from '../../../utils/walkThroughTopFunctionScope'; import walkThroughTopFunctionScope from '../../../utils/walkThroughTopFunctionScope';
@ -22,6 +25,23 @@ export default function computed(validator: Validator, prop: Node) {
checkForComputedKeys(validator, prop.value.properties); checkForComputedKeys(validator, prop.value.properties);
prop.value.properties.forEach((computation: Node) => { prop.value.properties.forEach((computation: Node) => {
const name = getName(computation.key);
if (!isValidIdentifier(name)) {
const suggestion = name.replace(/[^_$a-z0-9]/ig, '_').replace(/^\d/, '_$&');
validator.error(
`Computed property name '${name}' is invalid — must be a valid identifier such as ${suggestion}`,
computation.start
);
}
if (reservedNames.has(name)) {
validator.error(
`Computed property name '${name}' is invalid — cannot be a JavaScript reserved word`,
computation.start
);
}
if (!isFunctionExpression.has(computation.value.type)) { if (!isFunctionExpression.has(computation.value.type)) {
validator.error( validator.error(
`Computed properties can be function expressions or arrow function expressions`, `Computed properties can be function expressions or arrow function expressions`,

@ -117,6 +117,8 @@ describe('css', () => {
normalizeHtml(window, html.replace(/svelte-\d+/g, 'svelte-xyz')), normalizeHtml(window, html.replace(/svelte-\d+/g, 'svelte-xyz')),
normalizeHtml(window, expected.html) normalizeHtml(window, expected.html)
); );
window.document.head.innerHTML = ''; // remove added styles
} catch (err) { } catch (err) {
console.log(dom.code); console.log(dom.code);
throw err; throw err;

@ -167,7 +167,7 @@ function _set(newState) {
} }
function callAll(fns) { function callAll(fns) {
while (fns && fns.length) fns.pop()(); while (fns && fns.length) fns.shift()();
} }
function _mount(target, anchor) { function _mount(target, anchor) {
@ -198,13 +198,13 @@ function data() {
} }
function encapsulateStyles(node) { function encapsulateStyles(node) {
setAttribute(node, "svelte-3590263702", ""); setAttribute(node, "svelte-2794052100", "");
} }
function add_css() { function add_css() {
var style = createElement("style"); var style = createElement("style");
style.id = 'svelte-3590263702-style'; style.id = 'svelte-2794052100-style';
style.textContent = "p[svelte-3590263702],[svelte-3590263702] p{color:red}"; style.textContent = "p[svelte-2794052100],[svelte-2794052100] p{color:red}";
appendNode(style, document.head); appendNode(style, document.head);
} }
@ -245,7 +245,7 @@ function SvelteComponent(options) {
init(this, options); init(this, options);
this._state = assign(data(), options.data); this._state = assign(data(), options.data);
if (!document.getElementById("svelte-3590263702-style")) add_css(); if (!document.getElementById("svelte-2794052100-style")) add_css();
this._fragment = create_main_fragment(this._state, this); this._fragment = create_main_fragment(this._state, this);

@ -6,13 +6,13 @@ function data() {
}; };
function encapsulateStyles(node) { function encapsulateStyles(node) {
setAttribute(node, "svelte-3590263702", ""); setAttribute(node, "svelte-2794052100", "");
} }
function add_css() { function add_css() {
var style = createElement("style"); var style = createElement("style");
style.id = 'svelte-3590263702-style'; style.id = 'svelte-2794052100-style';
style.textContent = "p[svelte-3590263702],[svelte-3590263702] p{color:red}"; style.textContent = "p[svelte-2794052100],[svelte-2794052100] p{color:red}";
appendNode(style, document.head); appendNode(style, document.head);
} }
@ -53,7 +53,7 @@ function SvelteComponent(options) {
init(this, options); init(this, options);
this._state = assign(data(), options.data); this._state = assign(data(), options.data);
if (!document.getElementById("svelte-3590263702-style")) add_css(); if (!document.getElementById("svelte-2794052100-style")) add_css();
this._fragment = create_main_fragment(this._state, this); this._fragment = create_main_fragment(this._state, this);

@ -143,7 +143,7 @@ function _set(newState) {
} }
function callAll(fns) { function callAll(fns) {
while (fns && fns.length) fns.pop()(); while (fns && fns.length) fns.shift()();
} }
function _mount(target, anchor) { function _mount(target, anchor) {

@ -143,7 +143,7 @@ function _set(newState) {
} }
function callAll(fns) { function callAll(fns) {
while (fns && fns.length) fns.pop()(); while (fns && fns.length) fns.shift()();
} }
function _mount(target, anchor) { function _mount(target, anchor) {

@ -163,7 +163,7 @@ function _set(newState) {
} }
function callAll(fns) { function callAll(fns) {
while (fns && fns.length) fns.pop()(); while (fns && fns.length) fns.shift()();
} }
function _mount(target, anchor) { function _mount(target, anchor) {
@ -190,13 +190,13 @@ var proto = {
/* generated by Svelte vX.Y.Z */ /* generated by Svelte vX.Y.Z */
function encapsulateStyles(node) { function encapsulateStyles(node) {
setAttribute(node, "svelte-2363328337", ""); setAttribute(node, "svelte-3905933315", "");
} }
function add_css() { function add_css() {
var style = createElement("style"); var style = createElement("style");
style.id = 'svelte-2363328337-style'; style.id = 'svelte-3905933315-style';
style.textContent = "@media(min-width: 1px){div[svelte-2363328337],[svelte-2363328337] div{color:red}}"; style.textContent = "@media(min-width: 1px){div[svelte-3905933315],[svelte-3905933315] div{color:red}}";
appendNode(style, document.head); appendNode(style, document.head);
} }
@ -231,7 +231,7 @@ function SvelteComponent(options) {
init(this, options); init(this, options);
this._state = assign({}, options.data); this._state = assign({}, options.data);
if (!document.getElementById("svelte-2363328337-style")) add_css(); if (!document.getElementById("svelte-3905933315-style")) add_css();
this._fragment = create_main_fragment(this._state, this); this._fragment = create_main_fragment(this._state, this);

@ -2,13 +2,13 @@
import { appendNode, assign, createElement, detachNode, init, insertNode, noop, proto, setAttribute } from "svelte/shared.js"; import { appendNode, assign, createElement, detachNode, init, insertNode, noop, proto, setAttribute } from "svelte/shared.js";
function encapsulateStyles(node) { function encapsulateStyles(node) {
setAttribute(node, "svelte-2363328337", ""); setAttribute(node, "svelte-3905933315", "");
} }
function add_css() { function add_css() {
var style = createElement("style"); var style = createElement("style");
style.id = 'svelte-2363328337-style'; style.id = 'svelte-3905933315-style';
style.textContent = "@media(min-width: 1px){div[svelte-2363328337],[svelte-2363328337] div{color:red}}"; style.textContent = "@media(min-width: 1px){div[svelte-3905933315],[svelte-3905933315] div{color:red}}";
appendNode(style, document.head); appendNode(style, document.head);
} }
@ -43,7 +43,7 @@ function SvelteComponent(options) {
init(this, options); init(this, options);
this._state = assign({}, options.data); this._state = assign({}, options.data);
if (!document.getElementById("svelte-2363328337-style")) add_css(); if (!document.getElementById("svelte-3905933315-style")) add_css();
this._fragment = create_main_fragment(this._state, this); this._fragment = create_main_fragment(this._state, this);

@ -155,7 +155,7 @@ function _set(newState) {
} }
function callAll(fns) { function callAll(fns) {
while (fns && fns.length) fns.pop()(); while (fns && fns.length) fns.shift()();
} }
function _mount(target, anchor) { function _mount(target, anchor) {

@ -0,0 +1,221 @@
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 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._observers = { pre: blankObject(), post: blankObject() };
component._handlers = blankObject();
component._bind = options._bind;
component.options = options;
component.root = options.root || component;
component.store = component.root.store || options.store;
}
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.shift()();
}
function _mount(target, anchor) {
this._fragment.m(target, anchor);
}
function _unmount() {
if (this._fragment) 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 data_1() {
return {
foo: 'bar'
};
}
function oncreate() {
alert(JSON.stringify(data()));
}
function create_main_fragment(state, component) {
return {
c: noop,
m: noop,
p: noop,
u: noop,
d: noop
};
}
function SvelteComponent(options) {
init(this, options);
this._state = assign(data_1(), options.data);
var _oncreate = oncreate.bind(this);
if (!options.root) {
this._oncreate = [];
}
this._fragment = create_main_fragment(this._state, this);
this.root._oncreate.push(_oncreate);
if (options.target) {
this._fragment.c();
this._fragment.m(options.target, options.anchor || null);
callAll(this._oncreate);
}
}
assign(SvelteComponent.prototype, proto);
export default SvelteComponent;

@ -0,0 +1,52 @@
/* generated by Svelte vX.Y.Z */
import { assign, callAll, init, noop, proto } from "svelte/shared.js";
function data_1() {
return {
foo: 'bar'
};
}
function oncreate() {
alert(JSON.stringify(data()));
};
function create_main_fragment(state, component) {
return {
c: noop,
m: noop,
p: noop,
u: noop,
d: noop
};
}
function SvelteComponent(options) {
init(this, options);
this._state = assign(data_1(), options.data);
var _oncreate = oncreate.bind(this);
if (!options.root) {
this._oncreate = [];
}
this._fragment = create_main_fragment(this._state, this);
this.root._oncreate.push(_oncreate);
if (options.target) {
this._fragment.c();
this._fragment.m(options.target, options.anchor || null);
callAll(this._oncreate);
}
}
assign(SvelteComponent.prototype, proto);
export default SvelteComponent;

@ -0,0 +1,11 @@
<script>
export default {
data: () => ({
foo: 'bar'
}),
oncreate() {
alert(JSON.stringify(data()));
}
};
</script>

@ -159,7 +159,7 @@ function _set(newState) {
} }
function callAll(fns) { function callAll(fns) {
while (fns && fns.length) fns.pop()(); while (fns && fns.length) fns.shift()();
} }
function _mount(target, anchor) { function _mount(target, anchor) {

@ -163,7 +163,7 @@ function _set(newState) {
} }
function callAll(fns) { function callAll(fns) {
while (fns && fns.length) fns.pop()(); while (fns && fns.length) fns.shift()();
} }
function _mount(target, anchor) { function _mount(target, anchor) {

@ -163,7 +163,7 @@ function _set(newState) {
} }
function callAll(fns) { function callAll(fns) {
while (fns && fns.length) fns.pop()(); while (fns && fns.length) fns.shift()();
} }
function _mount(target, anchor) { function _mount(target, anchor) {

@ -175,7 +175,7 @@ function _set(newState) {
} }
function callAll(fns) { function callAll(fns) {
while (fns && fns.length) fns.pop()(); while (fns && fns.length) fns.shift()();
} }
function _mount(target, anchor) { function _mount(target, anchor) {

@ -155,7 +155,7 @@ function _set(newState) {
} }
function callAll(fns) { function callAll(fns) {
while (fns && fns.length) fns.pop()(); while (fns && fns.length) fns.shift()();
} }
function _mount(target, anchor) { function _mount(target, anchor) {

@ -155,7 +155,7 @@ function _set(newState) {
} }
function callAll(fns) { function callAll(fns) {
while (fns && fns.length) fns.pop()(); while (fns && fns.length) fns.shift()();
} }
function _mount(target, anchor) { function _mount(target, anchor) {

@ -159,7 +159,7 @@ function _set(newState) {
} }
function callAll(fns) { function callAll(fns) {
while (fns && fns.length) fns.pop()(); while (fns && fns.length) fns.shift()();
} }
function _mount(target, anchor) { function _mount(target, anchor) {

@ -159,7 +159,7 @@ function _set(newState) {
} }
function callAll(fns) { function callAll(fns) {
while (fns && fns.length) fns.pop()(); while (fns && fns.length) fns.shift()();
} }
function _mount(target, anchor) { function _mount(target, anchor) {

@ -159,7 +159,7 @@ function _set(newState) {
} }
function callAll(fns) { function callAll(fns) {
while (fns && fns.length) fns.pop()(); while (fns && fns.length) fns.shift()();
} }
function _mount(target, anchor) { function _mount(target, anchor) {

@ -159,7 +159,7 @@ function _set(newState) {
} }
function callAll(fns) { function callAll(fns) {
while (fns && fns.length) fns.pop()(); while (fns && fns.length) fns.shift()();
} }
function _mount(target, anchor) { function _mount(target, anchor) {

@ -159,7 +159,7 @@ function _set(newState) {
} }
function callAll(fns) { function callAll(fns) {
while (fns && fns.length) fns.pop()(); while (fns && fns.length) fns.shift()();
} }
function _mount(target, anchor) { function _mount(target, anchor) {

@ -159,7 +159,7 @@ function _set(newState) {
} }
function callAll(fns) { function callAll(fns) {
while (fns && fns.length) fns.pop()(); while (fns && fns.length) fns.shift()();
} }
function _mount(target, anchor) { function _mount(target, anchor) {

@ -163,7 +163,7 @@ function _set(newState) {
} }
function callAll(fns) { function callAll(fns) {
while (fns && fns.length) fns.pop()(); while (fns && fns.length) fns.shift()();
} }
function _mount(target, anchor) { function _mount(target, anchor) {

@ -161,7 +161,7 @@ function _set(newState) {
} }
function callAll(fns) { function callAll(fns) {
while (fns && fns.length) fns.pop()(); while (fns && fns.length) fns.shift()();
} }
function _mount(target, anchor) { function _mount(target, anchor) {

@ -178,7 +178,7 @@ function _set(newState) {
} }
function callAll(fns) { function callAll(fns) {
while (fns && fns.length) fns.pop()(); while (fns && fns.length) fns.shift()();
} }
function _mount(target, anchor) { function _mount(target, anchor) {

@ -171,7 +171,7 @@ function _set(newState) {
} }
function callAll(fns) { function callAll(fns) {
while (fns && fns.length) fns.pop()(); while (fns && fns.length) fns.shift()();
} }
function _mount(target, anchor) { function _mount(target, anchor) {
@ -226,6 +226,12 @@ function create_main_fragment(state, component) {
component.set({ buffered: timeRangesToArray(audio.buffered), seekable: timeRangesToArray(audio.seekable) }); component.set({ buffered: timeRangesToArray(audio.buffered), seekable: timeRangesToArray(audio.seekable) });
} }
function audio_volumechange_handler() {
audio_updating = true;
component.set({ volume: audio.volume });
audio_updating = false;
}
return { return {
c: function create() { c: function create() {
audio = createElement("audio"); audio = createElement("audio");
@ -243,15 +249,19 @@ function create_main_fragment(state, component) {
if (!('buffered' in state)) component.root._beforecreate.push(audio_progress_handler); if (!('buffered' in state)) component.root._beforecreate.push(audio_progress_handler);
addListener(audio, "loadedmetadata", audio_loadedmetadata_handler); addListener(audio, "loadedmetadata", audio_loadedmetadata_handler);
if (!('buffered' in state && 'seekable' in state)) component.root._beforecreate.push(audio_loadedmetadata_handler); if (!('buffered' in state && 'seekable' in state)) component.root._beforecreate.push(audio_loadedmetadata_handler);
addListener(audio, "volumechange", audio_volumechange_handler);
}, },
m: function mount(target, anchor) { m: function mount(target, anchor) {
insertNode(audio, target, anchor); insertNode(audio, target, anchor);
audio.volume = state.volume;
}, },
p: function update(changed, state) { p: function update(changed, state) {
if (!audio_updating && !isNaN(state.currentTime )) audio.currentTime = state.currentTime ; 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"](); if (!audio_updating && audio_is_paused !== (audio_is_paused = state.paused )) audio[audio_is_paused ? "pause" : "play"]();
if (!audio_updating && !isNaN(state.volume)) audio.volume = state.volume;
}, },
u: function unmount() { u: function unmount() {
@ -265,6 +275,7 @@ function create_main_fragment(state, component) {
removeListener(audio, "pause", audio_play_pause_handler); removeListener(audio, "pause", audio_play_pause_handler);
removeListener(audio, "progress", audio_progress_handler); removeListener(audio, "progress", audio_progress_handler);
removeListener(audio, "loadedmetadata", audio_loadedmetadata_handler); removeListener(audio, "loadedmetadata", audio_loadedmetadata_handler);
removeListener(audio, "volumechange", audio_volumechange_handler);
} }
}; };
} }

@ -30,6 +30,12 @@ function create_main_fragment(state, component) {
component.set({ buffered: timeRangesToArray(audio.buffered), seekable: timeRangesToArray(audio.seekable) }); component.set({ buffered: timeRangesToArray(audio.buffered), seekable: timeRangesToArray(audio.seekable) });
} }
function audio_volumechange_handler() {
audio_updating = true;
component.set({ volume: audio.volume });
audio_updating = false;
}
return { return {
c: function create() { c: function create() {
audio = createElement("audio"); audio = createElement("audio");
@ -47,15 +53,19 @@ function create_main_fragment(state, component) {
if (!('buffered' in state)) component.root._beforecreate.push(audio_progress_handler); if (!('buffered' in state)) component.root._beforecreate.push(audio_progress_handler);
addListener(audio, "loadedmetadata", audio_loadedmetadata_handler); addListener(audio, "loadedmetadata", audio_loadedmetadata_handler);
if (!('buffered' in state && 'seekable' in state)) component.root._beforecreate.push(audio_loadedmetadata_handler); if (!('buffered' in state && 'seekable' in state)) component.root._beforecreate.push(audio_loadedmetadata_handler);
addListener(audio, "volumechange", audio_volumechange_handler);
}, },
m: function mount(target, anchor) { m: function mount(target, anchor) {
insertNode(audio, target, anchor); insertNode(audio, target, anchor);
audio.volume = state.volume;
}, },
p: function update(changed, state) { p: function update(changed, state) {
if (!audio_updating && !isNaN(state.currentTime )) audio.currentTime = state.currentTime ; 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"](); if (!audio_updating && audio_is_paused !== (audio_is_paused = state.paused )) audio[audio_is_paused ? "pause" : "play"]();
if (!audio_updating && !isNaN(state.volume)) audio.volume = state.volume;
}, },
u: function unmount() { u: function unmount() {
@ -69,6 +79,7 @@ function create_main_fragment(state, component) {
removeListener(audio, "pause", audio_play_pause_handler); removeListener(audio, "pause", audio_play_pause_handler);
removeListener(audio, "progress", audio_progress_handler); removeListener(audio, "progress", audio_progress_handler);
removeListener(audio, "loadedmetadata", audio_loadedmetadata_handler); removeListener(audio, "loadedmetadata", audio_loadedmetadata_handler);
removeListener(audio, "volumechange", audio_volumechange_handler);
} }
}; };
} }

@ -1 +1 @@
<audio bind:buffered bind:seekable bind:played bind:currentTime bind:duration bind:paused/> <audio bind:buffered bind:seekable bind:played bind:currentTime bind:duration bind:paused bind:volume/>

@ -157,7 +157,7 @@ function _set(newState) {
} }
function callAll(fns) { function callAll(fns) {
while (fns && fns.length) fns.pop()(); while (fns && fns.length) fns.shift()();
} }
function _mount(target, anchor) { function _mount(target, anchor) {

@ -143,7 +143,7 @@ function _set(newState) {
} }
function callAll(fns) { function callAll(fns) {
while (fns && fns.length) fns.pop()(); while (fns && fns.length) fns.shift()();
} }
function _mount(target, anchor) { function _mount(target, anchor) {
@ -197,13 +197,13 @@ function SvelteComponent(options) {
var _oncreate = oncreate.bind(this); var _oncreate = oncreate.bind(this);
if (!options.root) { if (!options.root) {
this._oncreate = [_oncreate]; this._oncreate = [];
} else { }
this.root._oncreate.push(_oncreate);
}
this._fragment = create_main_fragment(this._state, this); this._fragment = create_main_fragment(this._state, this);
this.root._oncreate.push(_oncreate);
if (options.target) { if (options.target) {
this._fragment.c(); this._fragment.c();
this._fragment.m(options.target, options.anchor || null); this._fragment.m(options.target, options.anchor || null);

@ -29,13 +29,13 @@ function SvelteComponent(options) {
var _oncreate = oncreate.bind(this); var _oncreate = oncreate.bind(this);
if (!options.root) { if (!options.root) {
this._oncreate = [_oncreate]; this._oncreate = [];
} else { }
this.root._oncreate.push(_oncreate);
}
this._fragment = create_main_fragment(this._state, this); this._fragment = create_main_fragment(this._state, this);
this.root._oncreate.push(_oncreate);
if (options.target) { if (options.target) {
this._fragment.c(); this._fragment.c();
this._fragment.m(options.target, options.anchor || null); this._fragment.m(options.target, options.anchor || null);

@ -143,7 +143,7 @@ function _set(newState) {
} }
function callAll(fns) { function callAll(fns) {
while (fns && fns.length) fns.pop()(); while (fns && fns.length) fns.shift()();
} }
function _mount(target, anchor) { function _mount(target, anchor) {

@ -163,7 +163,7 @@ function _set(newState) {
} }
function callAll(fns) { function callAll(fns) {
while (fns && fns.length) fns.pop()(); while (fns && fns.length) fns.shift()();
} }
function _mount(target, anchor) { function _mount(target, anchor) {

@ -143,7 +143,7 @@ function _set(newState) {
} }
function callAll(fns) { function callAll(fns) {
while (fns && fns.length) fns.pop()(); while (fns && fns.length) fns.shift()();
} }
function _mount(target, anchor) { function _mount(target, anchor) {

@ -167,7 +167,7 @@ function _set(newState) {
} }
function callAll(fns) { function callAll(fns) {
while (fns && fns.length) fns.pop()(); while (fns && fns.length) fns.shift()();
} }
function _mount(target, anchor) { function _mount(target, anchor) {

@ -163,7 +163,7 @@ function _set(newState) {
} }
function callAll(fns) { function callAll(fns) {
while (fns && fns.length) fns.pop()(); while (fns && fns.length) fns.shift()();
} }
function _mount(target, anchor) { function _mount(target, anchor) {

@ -0,0 +1,3 @@
{{#each things as 𐊧}}
<p>{{𐊧}}</p>
{{/each}}

@ -0,0 +1,46 @@
{
"hash": 795130236,
"html": {
"start": 0,
"end": 47,
"type": "Fragment",
"children": [
{
"start": 0,
"end": 47,
"type": "EachBlock",
"expression": {
"type": "Identifier",
"start": 8,
"end": 14,
"name": "things"
},
"children": [
{
"start": 24,
"end": 37,
"type": "Element",
"name": "p",
"attributes": [],
"children": [
{
"start": 27,
"end": 33,
"type": "MustacheTag",
"expression": {
"type": "Identifier",
"start": 29,
"end": 31,
"name": "𐊧"
}
}
]
}
],
"context": "𐊧"
}
]
},
"css": null,
"js": null
}

@ -125,7 +125,7 @@ describe("runtime", () => {
if (raf.callback) raf.callback(); if (raf.callback) raf.callback();
} }
}; };
window.performance = { now: () => raf.time }; window.performance.now = () => raf.time;
global.requestAnimationFrame = cb => { global.requestAnimationFrame = cb => {
let called = false; let called = false;
raf.callback = () => { raf.callback = () => {

@ -0,0 +1,7 @@
export default {
html: `<textarea></textarea>`,
test (assert, component, target) {
const textarea = target.querySelector('textarea');
assert.ok(textarea.readOnly === false);
},
};

@ -0,0 +1 @@
<textarea readonly="{{false}}"></textarea>

@ -0,0 +1,7 @@
export default {
html: `<textarea readonly></textarea>`,
test (assert, component, target) {
const textarea = target.querySelector('textarea');
assert.ok(textarea.readOnly);
},
};

@ -0,0 +1 @@
<textarea readonly="{{true}}"></textarea>

@ -0,0 +1,23 @@
export default {
html: `
<div class='SHOUTY'>YELL</div>
<svg viewBox='0 0 100 100' id='one'>
<text textLength=100>hellooooo</text>
</svg>
<svg viewBox='0 0 100 100' id='two'>
<text textLength=100>hellooooo</text>
</svg>
`,
test(assert, component, target) {
const attr = sel => target.querySelector(sel).attributes[0].name;
assert.equal(attr('div'), 'class');
assert.equal(attr('svg#one'), 'viewBox');
assert.equal(attr('svg#one text'), 'textLength');
assert.equal(attr('svg#two'), 'viewBox');
assert.equal(attr('svg#two text'), 'textLength');
}
};

@ -0,0 +1,9 @@
<div CLASS='SHOUTY'>YELL</div>
<svg viewbox='0 0 100 100' id='one'>
<text textlength=100>hellooooo</text>
</svg>
<svg viewBox='0 0 100 100' id='two'>
<text textLength=100>hellooooo</text>
</svg>

@ -0,0 +1,14 @@
<p>{{value}}</p>
<p>{{called}}</p>
<script>
export default {
data() {
return { called: false };
},
oncreate() {
this.set({ called: true });
}
};
</script>

@ -0,0 +1,16 @@
const promise = Promise.resolve(42);
export default {
data: {
promise
},
test(assert, component, target) {
return promise.then(() => {
assert.htmlEqual(target.innerHTML, `
<p>42</p>
<p>true</p>
`);
});
}
};

@ -0,0 +1,13 @@
{{#await promise then value}}
<Foo :value />
{{/await}}
<script>
import Foo from './Foo.html';
export default {
components: {
Foo
}
};
</script>

@ -0,0 +1,16 @@
export default {
test(assert, component, target) {
const promise = Promise.resolve().then(() => component.set({ answer: 42 }));
component.set({ promise });
assert.htmlEqual(target.innerHTML, `<p>wait for it...</p>`);
return promise
.then(() => {
assert.htmlEqual(target.innerHTML, `
<p>the answer is 42!</p>
`);
});
}
};

@ -0,0 +1,9 @@
{{#if promise}}
{{#await promise}}
<p>wait for it...</p>
{{then _}}
<p>the answer is {{answer}}!</p>
{{catch error}}
<p>well that's odd</p>
{{/await}}
{{/if}}

@ -7,20 +7,25 @@ export default {
test ( assert, component, target, window ) { test ( assert, component, target, window ) {
assert.equal( component.get( 't' ), 0 ); assert.equal( component.get( 't' ), 0 );
assert.equal( component.get( 'd' ), 0 ); assert.equal( component.get( 'd' ), 0 );
assert.equal( component.get( 'v' ), 0.5 );
assert.equal( component.get( 'paused' ), true ); assert.equal( component.get( 'paused' ), true );
const audio = target.querySelector( 'audio' ); const audio = target.querySelector( 'audio' );
const timeupdate = new window.Event( 'timeupdate' ); const timeupdate = new window.Event( 'timeupdate' );
const durationchange = new window.Event( 'durationchange' ); const durationchange = new window.Event( 'durationchange' );
const volumechange = new window.Event( 'volumechange' );
audio.currentTime = 10; audio.currentTime = 10;
audio.duration = 20; audio.duration = 20;
audio.volume = 0.75;
audio.dispatchEvent( timeupdate ); audio.dispatchEvent( timeupdate );
audio.dispatchEvent( durationchange ); audio.dispatchEvent( durationchange );
audio.dispatchEvent( volumechange );
audio.play(); audio.play();
assert.equal( component.get( 't' ), 10 ); assert.equal( component.get( 't' ), 10 );
assert.equal( component.get( 'd' ), 0 ); // not 20, because read-only. Not sure how to test this! assert.equal( component.get( 'd' ), 0 ); // not 20, because read-only. Not sure how to test this!
assert.equal( component.get( 'v' ), 0.75 );
assert.equal( component.get( 'paused' ), true ); // ditto... assert.equal( component.get( 'paused' ), true ); // ditto...
component.destroy(); component.destroy();
} }

@ -0,0 +1,2 @@
<audio bind:currentTime='t' bind:duration='d' bind:paused bind:volume='v'
src='music.mp3'></audio>

@ -1 +0,0 @@
<audio bind:currentTime='t' bind:duration='d' bind:paused src='music.mp3'></audio>

@ -2,16 +2,16 @@ export default {
'skip-ssr': true, // TODO delete this line, once binding works 'skip-ssr': true, // TODO delete this line, once binding works
html: ` html: `
<p>y: foo</p> <p>y: bar</p>
<p>y: foo</p> <p>y: bar</p>
`, `,
test ( assert, component, target ) { test ( assert, component, target ) {
component.set({ x: false }); component.set({ x: false });
assert.htmlEqual( target.innerHTML, ` assert.htmlEqual( target.innerHTML, `
<p>y: foo</p> <p>y: bar</p>
<p>y: foo</p> <p>y: bar</p>
` ); ` );
} }
}; };

@ -0,0 +1,32 @@
export default {
html: `
<button>0: foo</button>
<button>1: bar</button>
<button>2: baz</button>
<p>first: </p>
<p>second: </p>
`,
test ( assert, component, target, window ) {
const event = new window.MouseEvent( 'click' );
const buttons = target.querySelectorAll( 'button' );
buttons[1].dispatchEvent( event );
assert.htmlEqual( target.innerHTML, `
<button>0: foo</button>
<button>1: bar</button>
<button>2: baz</button>
<p>first: 1</p>
<p>second: bar</p>
` );
assert.equal( component.get( 'first' ), '1' );
assert.equal( component.get( 'second' ), 'bar' );
component.destroy();
}
};

@ -0,0 +1,36 @@
{{#each items as [item0, item1]}}
<button on:tap='set({ first: item0, second: item1 })'>
{{item0}}: {{item1}}
</button>
{{/each}}
<p>first: {{first}}</p>
<p>second: {{second}}</p>
<script>
export default {
data: () => ({
x: 0,
y: 0,
first: '',
second: '',
items: [ [0, 'foo'], [1, 'bar'], [2, 'baz'] ]
}),
events: {
tap ( node, callback ) {
function clickHandler ( event ) {
callback();
}
node.addEventListener( 'click', clickHandler, false );
return {
teardown () {
node.addEventListener( 'click', clickHandler, false );
}
};
}
}
};
</script>

@ -0,0 +1,15 @@
export default {
data: {
color: 'red',
},
html: `
<div>
<style>
div {
color: red;
}
</style>
foo
</div>`,
};

@ -0,0 +1,8 @@
<div>
<style>
div {
color: {{color}};
}
</style>
foo
</div>

@ -0,0 +1,11 @@
<p>{{name}}</p>
<script>
import result from './result.js';
export default {
oncreate() {
result.push(`oncreate ${this.get('name')}`);
}
};
</script>

@ -0,0 +1,13 @@
import result from './result.js';
export default {
test(assert) {
assert.deepEqual(result, [
'oncreate foo',
'oncreate bar'
]);
result.pop();
result.pop();
}
};

@ -0,0 +1,12 @@
<Nested name='foo'/>
<Nested name='bar'/>
<script>
import Nested from './Nested.html';
export default {
components: {
Nested
}
};
</script>

@ -0,0 +1,13 @@
<h1>Hello, {{$name}}!</h1>
<script>
import { Store } from '../../../../store.js';
export default {
store () {
return new Store({
name: 'world'
});
},
};
</script>

@ -0,0 +1,7 @@
export default {
store: true, // TODO remove this in v2
html: `
<h1>Hello, world!</h1>
`,
};

@ -0,0 +1,11 @@
<Nested/>
<script>
import Nested from './Nested.html';
export default {
components: {
Nested
}
};
</script>

@ -1,2 +1,2 @@
div[svelte-1408461649],[svelte-1408461649] div{color:red} div[svelte-724714405],[svelte-724714405] div{color:red}
div[svelte-1580141456],[svelte-1580141456] div{color:green} div[svelte-300476157],[svelte-300476157] div{color:green}

@ -1,8 +1,8 @@
<div svelte-1408461649>red</div> <div svelte-724714405>red</div>
<div svelte-1580141456>green: foo</div> <div svelte-300476157>green: foo</div>
<div svelte-1580141456>green: bar</div> <div svelte-300476157>green: bar</div>

@ -1,2 +1,2 @@
div[svelte-1408461649],[svelte-1408461649] div{color:red} div[svelte-724714405],[svelte-724714405] div{color:red}
div[svelte-1580141456],[svelte-1580141456] div{color:green} div[svelte-300476157],[svelte-300476157] div{color:green}

@ -1,8 +1,8 @@
<div svelte-1408461649>red</div> <div svelte-724714405>red</div>
<div svelte-1580141456>green: foo</div> <div svelte-300476157>green: foo</div>
<div svelte-1580141456>green: bar</div> <div svelte-300476157>green: bar</div>

@ -1 +1 @@
div[svelte-2278551596],[svelte-2278551596] div{color:red} div[svelte-724714405],[svelte-724714405] div{color:red}

@ -1 +1 @@
<div svelte-2278551596>red</div> <div svelte-724714405>red</div>

@ -1 +1 @@
div[svelte-2278551596],[svelte-2278551596] div{color:red} div[svelte-724714405],[svelte-724714405] div{color:red}

@ -1 +1 @@
<div svelte-2278551596>red</div> <div svelte-724714405>red</div>

@ -1,2 +1,2 @@
.foo[svelte-2772200924]{color:red} .foo[svelte-1719932608]{color:red}
/*# sourceMappingURL=output.css.map */ /*# sourceMappingURL=output.css.map */

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save