Merge branch 'master' into less-invalidation

pull/2865/head
Rich Harris 6 years ago committed by GitHub
commit 52733f3040
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

18
.gitignore vendored

@ -1,16 +1,17 @@
.idea
.DS_Store .DS_Store
.nyc_output .nyc_output
node_modules node_modules
*.map *.map
/src/compile/internal-exports.ts /src/compile/internal-exports.ts
/compiler.js /compiler.*js
/index.js /index.*js
/internal.* /internal.*js
/store.* /store.*js
/easing.js /easing.*js
/motion.* /motion.*js
/transition.js /transition.*js
/animate.js /animate.*js
/scratch/ /scratch/
/coverage/ /coverage/
/coverage.lcov/ /coverage.lcov/
@ -20,6 +21,7 @@ node_modules
/test/sourcemaps/samples/*/output.css.map /test/sourcemaps/samples/*/output.css.map
/yarn-error.log /yarn-error.log
_actual*.* _actual*.*
/dist
/site/cypress/screenshots/ /site/cypress/screenshots/
/site/__sapper__/ /site/__sapper__/

@ -1,5 +1,14 @@
# Svelte changelog # Svelte changelog
## 3.4.3
* Add type declaration files for everything ([#2842](https://github.com/sveltejs/svelte/pull/2842))
* Prevent `svelte/store` being bundled ([#2786](https://github.com/sveltejs/svelte/issues/2786))
* Warn on unknown props in dev mode ([#2840](https://github.com/sveltejs/svelte/pull/2840))
* Treat `requestAnimationFrame` as a no-op on the server ([#2856](https://github.com/sveltejs/svelte/pull/2856))
* Add `raw` property to AST's `Text` nodes ([#2714](https://github.com/sveltejs/svelte/issues/2714))
* Add `<details bind:open>` ([#2854](https://github.com/sveltejs/svelte/issues/2854))
## 3.4.2 ## 3.4.2
* Use empty string for empty data attributes ([#2804](https://github.com/sveltejs/svelte/pull/2804)) * Use empty string for empty data attributes ([#2804](https://github.com/sveltejs/svelte/pull/2804))

1
animate.d.ts vendored

@ -0,0 +1 @@
export * from './dist/animate';

1
compiler.d.ts vendored

@ -0,0 +1 @@
export * from './dist/compiler';

1
easing.d.ts vendored

@ -0,0 +1 @@
export * from './dist/easing';

1
index.d.ts vendored

@ -0,0 +1 @@
export * from './dist/index';

@ -1,10 +0,0 @@
export {
onMount,
onDestroy,
beforeUpdate,
afterUpdate,
setContext,
getContext,
tick,
createEventDispatcher
} from './internal';

1
internal.d.ts vendored

@ -0,0 +1 @@
export * from './dist/internal';

1
motion.d.ts vendored

@ -0,0 +1 @@
export * from './dist/motion';

833
package-lock.json generated

File diff suppressed because it is too large Load Diff

@ -1,10 +1,11 @@
{ {
"name": "svelte", "name": "svelte",
"version": "3.4.2", "version": "3.4.3",
"description": "Cybernetically enhanced web apps", "description": "Cybernetically enhanced web apps",
"module": "index.mjs", "module": "index.mjs",
"main": "index", "main": "index",
"files": [ "files": [
"dist",
"compiler.js", "compiler.js",
"register.js", "register.js",
"index.*", "index.*",
@ -25,14 +26,14 @@
"coverage": "c8 report --reporter=text-lcov > coverage.lcov && c8 report --reporter=html", "coverage": "c8 report --reporter=text-lcov > coverage.lcov && c8 report --reporter=html",
"codecov": "codecov", "codecov": "codecov",
"precodecov": "npm run coverage", "precodecov": "npm run coverage",
"lint": "eslint src test/*.js",
"build": "rollup -c", "build": "rollup -c",
"prepare": "npm run build && npm run tsd", "prepare": "npm run build && npm run tsd",
"dev": "rollup -cw", "dev": "rollup -cw",
"pretest": "npm run build", "pretest": "npm run build",
"posttest": "agadoo src/internal/index.js", "posttest": "agadoo internal.mjs",
"prepublishOnly": "export PUBLISH=true && npm run lint && npm test", "prepublishOnly": "export PUBLISH=true && npm test",
"tsd": "tsc -d src/store.ts --outDir ." "tsd": "tsc -p . --emitDeclarationOnly",
"typecheck": "tsc -p . --noEmit"
}, },
"repository": { "repository": {
"type": "git", "type": "git",
@ -60,9 +61,6 @@
"c8": "^3.4.0", "c8": "^3.4.0",
"codecov": "^3.0.0", "codecov": "^3.0.0",
"css-tree": "1.0.0-alpha22", "css-tree": "1.0.0-alpha22",
"eslint": "^5.3.0",
"eslint-plugin-html": "^5.0.0",
"eslint-plugin-import": "^2.11.0",
"estree-walker": "^0.6.0", "estree-walker": "^0.6.0",
"is-reference": "^1.1.1", "is-reference": "^1.1.1",
"jsdom": "^12.2.0", "jsdom": "^12.2.0",
@ -84,7 +82,7 @@
"tiny-glob": "^0.2.1", "tiny-glob": "^0.2.1",
"ts-node": "^8.0.2", "ts-node": "^8.0.2",
"tslib": "^1.8.0", "tslib": "^1.8.0",
"typescript": "^3.0.1" "typescript": "^3.4.0"
}, },
"nyc": { "nyc": {
"include": [ "include": [

@ -9,10 +9,19 @@ import pkg from './package.json';
const is_publish = !!process.env.PUBLISH; const is_publish = !!process.env.PUBLISH;
const tsPlugin = is_publish
? typescript({
include: 'src/**',
typescript: require('typescript')
})
: sucrase({
transforms: ['typescript']
});
export default [ export default [
/* internal.[m]js */ /* internal.[m]js */
{ {
input: `src/internal/index.js`, input: `src/internal/index.ts`,
output: [ output: [
{ {
file: `internal.mjs`, file: `internal.mjs`,
@ -26,19 +35,22 @@ export default [
} }
], ],
external: id => id.startsWith('svelte/'), external: id => id.startsWith('svelte/'),
plugins: [{
generateBundle(options, bundle) { plugins: [
const mod = bundle['internal.mjs']; tsPlugin,
if (mod) { {
fs.writeFileSync('src/compile/internal-exports.ts', `// This file is automatically generated\nexport default new Set(${JSON.stringify(mod.exports)});`); generateBundle(options, bundle) {
const mod = bundle['internal.mjs'];
if (mod) {
fs.writeFileSync('src/compile/internal-exports.ts', `// This file is automatically generated\nexport default new Set(${JSON.stringify(mod.exports)});`);
}
} }
} }]
}]
}, },
/* compiler.js */ /* compiler.js */
{ {
input: 'src/index.ts', input: 'src/compiler.ts',
plugins: [ plugins: [
replace({ replace({
__VERSION__: pkg.version __VERSION__: pkg.version
@ -48,15 +60,7 @@ export default [
include: ['node_modules/**'] include: ['node_modules/**']
}), }),
json(), json(),
is_publish tsPlugin
? typescript({
include: 'src/**',
exclude: 'src/internal/**',
typescript: require('typescript')
})
: sucrase({
transforms: ['typescript']
})
], ],
output: { output: {
file: 'compiler.js', file: 'compiler.js',
@ -71,7 +75,7 @@ export default [
/* motion.mjs */ /* motion.mjs */
{ {
input: `src/motion/index.js`, input: `src/motion/index.ts`,
output: [ output: [
{ {
file: `motion.mjs`, file: `motion.mjs`,
@ -84,46 +88,30 @@ export default [
paths: id => id.startsWith('svelte/') && id.replace('svelte', '.') paths: id => id.startsWith('svelte/') && id.replace('svelte', '.')
} }
], ],
plugins: [
tsPlugin
],
external: id => id.startsWith('svelte/') external: id => id.startsWith('svelte/')
}, },
/* store.mjs */ // everything else
{ ...['index', 'easing', 'transition', 'animate', 'store'].map(name => ({
input: `src/store.ts`, input: `src/${name}.ts`,
output: [ output: [
{ {
file: `store.mjs`, file: `${name}.mjs`,
format: 'esm', format: 'esm',
paths: id => id.startsWith('svelte/') && id.replace('svelte', '.') paths: id => id.startsWith('svelte/') && id.replace('svelte', '.')
}, },
{ {
file: `store.js`, file: `${name}.js`,
format: 'cjs', format: 'cjs',
paths: id => id.startsWith('svelte/') && id.replace('svelte', '.') paths: id => id.startsWith('svelte/') && id.replace('svelte', '.')
} }
], ],
plugins: [ plugins: [
is_publish tsPlugin
? typescript({
include: 'src/**',
exclude: 'src/internal/**',
typescript: require('typescript')
})
: sucrase({
transforms: ['typescript']
})
], ],
external: id => id.startsWith('svelte/') external: id => id.startsWith('svelte/')
},
// everything else
...['index', 'easing', 'transition', 'animate'].map(name => ({
input: `${name}.mjs`,
output: {
file: `${name}.js`,
format: 'cjs',
paths: id => id.startsWith('svelte/') && id.replace('svelte', '.')
},
external: id => id !== `${name}.mjs`
})) }))
]; ];

@ -30,10 +30,7 @@
$: selected = filteredPeople[i]; $: selected = filteredPeople[i];
$: { $: reset_inputs(selected);
first = selected ? selected.first : '';
last = selected ? selected.last : '';
}
function create() { function create() {
people = people.concat({ first, last }); people = people.concat({ first, last });
@ -53,7 +50,8 @@
} }
function reset_inputs(person) { function reset_inputs(person) {
({ first, last } = person); first = person ? person.first : '';
last = person ? person.last : '';
} }
</script> </script>

@ -2,7 +2,13 @@
title: In and out title: In and out
--- ---
Instead of the `transition` directive, an element can have an `in` or an `out` directive, or both together: Instead of the `transition` directive, an element can have an `in` or an `out` directive, or both together. Import `fade` alongside `fly`...
```js
import { fade, fly } from 'svelte/transition';
```
...then replace the `transition` directive with separate `in` and `out` directives:
```html ```html
<p in:fly="{{ y: 200, duration: 2000 }}" out:fade> <p in:fly="{{ y: 200, duration: 2000 }}" out:fade>

@ -1,5 +1,5 @@
import { cubicOut } from './easing'; import { cubicOut } from 'svelte/easing';
import { is_function } from './internal'; import { is_function } from 'svelte/internal';
export function flip(node, animation, params) { export function flip(node, animation, params) {
const style = getComputedStyle(node); const style = getComputedStyle(node);
@ -22,4 +22,4 @@ export function flip(node, animation, params) {
easing, easing,
css: (t, u) => `transform: ${transform} translate(${u * dx}px, ${u * dy}px);` css: (t, u) => `transform: ${transform} translate(${u * dx}px, ${u * dy}px);`
}; };
} }

@ -1,4 +1,5 @@
import MagicString, { Bundle } from 'magic-string'; import MagicString, { Bundle } from 'magic-string';
// @ts-ignore
import { walk, childKeys } from 'estree-walker'; import { walk, childKeys } from 'estree-walker';
import { getLocator } from 'locate-character'; import { getLocator } from 'locate-character';
import Stats from '../Stats'; import Stats from '../Stats';
@ -21,6 +22,7 @@ import { remove_indentation, add_indentation } from '../utils/indentation';
import get_object from './utils/get_object'; import get_object from './utils/get_object';
import unwrap_parens from './utils/unwrap_parens'; import unwrap_parens from './utils/unwrap_parens';
import Slot from './nodes/Slot'; import Slot from './nodes/Slot';
import { Node as ESTreeNode } from 'estree';
type ComponentOptions = { type ComponentOptions = {
namespace?: string; namespace?: string;
@ -758,7 +760,7 @@ export default class Component {
}); });
} }
if (is_reference(node, parent)) { if (is_reference(node as ESTreeNode, parent as ESTreeNode)) {
const object = get_object(node); const object = get_object(node);
const { name } = object; const { name } = object;
@ -776,7 +778,7 @@ export default class Component {
}); });
} }
invalidate(name, value) { invalidate(name, value?) {
const variable = this.var_lookup.get(name); const variable = this.var_lookup.get(name);
if (variable && (variable.subscribable && variable.reassigned)) { if (variable && (variable.subscribable && variable.reassigned)) {
@ -1026,7 +1028,7 @@ export default class Component {
scope = map.get(node); scope = map.get(node);
} }
if (is_reference(node, parent)) { if (is_reference(node as ESTreeNode, parent as ESTreeNode)) {
const { name } = flatten_reference(node); const { name } = flatten_reference(node);
const owner = scope.find_owner(name); const owner = scope.find_owner(name);
@ -1117,7 +1119,7 @@ export default class Component {
} else if (node.type === 'UpdateExpression') { } else if (node.type === 'UpdateExpression') {
const identifier = get_object(node.argument); const identifier = get_object(node.argument);
assignees.add(identifier.name); assignees.add(identifier.name);
} else if (is_reference(node, parent)) { } else if (is_reference(node as ESTreeNode, parent as ESTreeNode)) {
const identifier = get_object(node); const identifier = get_object(node);
if (!assignee_nodes.has(identifier)) { if (!assignee_nodes.has(identifier)) {
const { name } = identifier; const { name } = identifier;

@ -1,4 +1,4 @@
import { assign } from '../internal'; import { assign } from '../internal/index';
import Stats from '../Stats'; import Stats from '../Stats';
import parse from '../parse/index'; import parse from '../parse/index';
import render_dom from './render-dom/index'; import render_dom from './render-dom/index';
@ -55,7 +55,7 @@ function validate_options(options: CompileOptions, warnings: Warning[]) {
} }
} }
function get_name(filename) { function get_name(filename: string) {
if (!filename) return null; if (!filename) return null;
const parts = filename.split(/[\/\\]/); const parts = filename.split(/[\/\\]/);
@ -105,4 +105,4 @@ export default function compile(source: string, options: CompileOptions = {}) {
: render_dom(component, options); : render_dom(component, options);
return component.generate(js); return component.generate(js);
} }

@ -67,6 +67,7 @@ export default class Attribute extends Node {
this.should_cache = this.is_dynamic this.should_cache = this.is_dynamic
? this.chunks.length === 1 ? this.chunks.length === 1
// @ts-ignore todo: probably error
? this.chunks[0].node.type !== 'Identifier' || scope.names.has(this.chunks[0].node.name) ? this.chunks[0].node.type !== 'Identifier' || scope.names.has(this.chunks[0].node.name)
: true : true
: false; : false;
@ -91,8 +92,10 @@ export default class Attribute extends Node {
if (this.chunks.length === 0) return `""`; if (this.chunks.length === 0) return `""`;
if (this.chunks.length === 1) { if (this.chunks.length === 1) {
return this.chunks[0].type === 'Text' return this.chunks[0].type === 'Text'
? stringify(this.chunks[0].data) ? stringify((this.chunks[0] as Text).data)
// @ts-ignore todo: probably error
: this.chunks[0].render(block); : this.chunks[0].render(block);
} }
@ -102,6 +105,7 @@ export default class Attribute extends Node {
if (chunk.type === 'Text') { if (chunk.type === 'Text') {
return stringify(chunk.data); return stringify(chunk.data);
} else { } else {
// @ts-ignore todo: probably error
return chunk.get_precedence() <= 13 ? `(${chunk.render()})` : chunk.render(); return chunk.get_precedence() <= 13 ? `(${chunk.render()})` : chunk.render();
} }
}) })
@ -114,7 +118,8 @@ export default class Attribute extends Node {
return this.is_true return this.is_true
? true ? true
: this.chunks[0] : this.chunks[0]
? this.chunks[0].data // method should be called only when `is_static = true`
? (this.chunks[0] as Text).data
: ''; : '';
} }
} }

@ -5,6 +5,7 @@ import CatchBlock from './CatchBlock';
import Expression from './shared/Expression'; import Expression from './shared/Expression';
export default class AwaitBlock extends Node { export default class AwaitBlock extends Node {
type: 'AwaitBlock';
expression: Expression; expression: Expression;
value: string; value: string;
error: string; error: string;

@ -3,13 +3,24 @@ import get_object from '../utils/get_object';
import Expression from './shared/Expression'; import Expression from './shared/Expression';
import Component from '../Component'; import Component from '../Component';
import TemplateScope from './shared/TemplateScope'; import TemplateScope from './shared/TemplateScope';
import {dimensions} from "../../utils/patterns";
// TODO this should live in a specific binding
const read_only_media_attributes = new Set([
'duration',
'buffered',
'seekable',
'played'
]);
export default class Binding extends Node { export default class Binding extends Node {
type: 'Binding';
name: string; name: string;
expression: Expression; expression: Expression;
is_contextual: boolean; is_contextual: boolean;
obj: string; obj: string;
prop: string; prop: string;
is_readonly: boolean;
constructor(component: Component, parent, scope: TemplateScope, info) { constructor(component: Component, parent, scope: TemplateScope, info) {
super(component, parent, scope, info); super(component, parent, scope, info);
@ -63,5 +74,17 @@ export default class Binding extends Node {
this.obj = obj; this.obj = obj;
this.prop = prop; this.prop = prop;
const type = parent.get_static_attribute_value('type');
this.is_readonly = (
dimensions.test(this.name) ||
(parent.is_media_node && parent.is_media_node() && read_only_media_attributes.has(this.name)) ||
(parent.name === 'input' && type === 'file') // TODO others?
);
}
is_readonly_media_attribute() {
return read_only_media_attributes.has(this.name);
} }
} }

@ -3,6 +3,7 @@ import TemplateScope from './shared/TemplateScope';
import AbstractBlock from './shared/AbstractBlock'; import AbstractBlock from './shared/AbstractBlock';
export default class CatchBlock extends AbstractBlock { export default class CatchBlock extends AbstractBlock {
type: 'CatchBlock';
scope: TemplateScope; scope: TemplateScope;
constructor(component, parent, scope, info) { constructor(component, parent, scope, info) {

@ -2,6 +2,7 @@ import Node from './shared/Node';
import Expression from './shared/Expression'; import Expression from './shared/Expression';
export default class DebugTag extends Node { export default class DebugTag extends Node {
type: 'DebugTag';
expressions: Expression[]; expressions: Expression[];
constructor(component, parent, scope, info) { constructor(component, parent, scope, info) {
@ -11,4 +12,4 @@ export default class DebugTag extends Node {
return new Expression(component, parent, scope, node); return new Expression(component, parent, scope, node);
}); });
} }
} }

@ -6,8 +6,15 @@ import TemplateScope from './shared/TemplateScope';
import AbstractBlock from './shared/AbstractBlock'; import AbstractBlock from './shared/AbstractBlock';
import { Node as INode } from '../../interfaces'; import { Node as INode } from '../../interfaces';
import { new_tail } from '../utils/tail'; import { new_tail } from '../utils/tail';
import Element from './Element';
function unpack_destructuring(contexts: Array<{ name: string, tail: string }>, node: INode, tail: string) { type Context = {
key: INode,
name?: string,
tail: string
};
function unpack_destructuring(contexts: Array<Context>, node: INode, tail: string) {
if (!node) return; if (!node) return;
if (node.type === 'Identifier' || node.type === 'RestIdentifier') { if (node.type === 'Identifier' || node.type === 'RestIdentifier') {
@ -53,7 +60,7 @@ export default class EachBlock extends AbstractBlock {
context: string; context: string;
key: Expression; key: Expression;
scope: TemplateScope; scope: TemplateScope;
contexts: Array<{ name: string, tail: string }>; contexts: Array<Context>;
has_animation: boolean; has_animation: boolean;
has_binding = false; has_binding = false;
@ -82,7 +89,7 @@ export default class EachBlock extends AbstractBlock {
if (this.index) { if (this.index) {
// index can only change if this is a keyed each block // index can only change if this is a keyed each block
const dependencies = this.key ? this.expression.dependencies : []; const dependencies = this.key ? this.expression.dependencies : new Set([]);
this.scope.add(this.index, dependencies, this); this.scope.add(this.index, dependencies, this);
} }
@ -92,8 +99,8 @@ export default class EachBlock extends AbstractBlock {
if (this.has_animation) { if (this.has_animation) {
if (this.children.length !== 1) { if (this.children.length !== 1) {
const child = this.children.find(child => !!child.animation); const child = this.children.find(child => !!(child as Element).animation);
component.error(child.animation, { component.error((child as Element).animation, {
code: `invalid-animation`, code: `invalid-animation`,
message: `An element that use the animate directive must be the sole child of a keyed each block` message: `An element that use the animate directive must be the sole child of a keyed each block`
}); });

@ -15,6 +15,7 @@ import fuzzymatch from '../../utils/fuzzymatch';
import list from '../../utils/list'; import list from '../../utils/list';
import Let from './Let'; import Let from './Let';
import TemplateScope from './shared/TemplateScope'; import TemplateScope from './shared/TemplateScope';
import { INode } from './interfaces';
const svg = /^(?:altGlyph|altGlyphDef|altGlyphItem|animate|animateColor|animateMotion|animateTransform|circle|clipPath|color-profile|cursor|defs|desc|discard|ellipse|feBlend|feColorMatrix|feComponentTransfer|feComposite|feConvolveMatrix|feDiffuseLighting|feDisplacementMap|feDistantLight|feDropShadow|feFlood|feFuncA|feFuncB|feFuncG|feFuncR|feGaussianBlur|feImage|feMerge|feMergeNode|feMorphology|feOffset|fePointLight|feSpecularLighting|feSpotLight|feTile|feTurbulence|filter|font|font-face|font-face-format|font-face-name|font-face-src|font-face-uri|foreignObject|g|glyph|glyphRef|hatch|hatchpath|hkern|image|line|linearGradient|marker|mask|mesh|meshgradient|meshpatch|meshrow|metadata|missing-glyph|mpath|path|pattern|polygon|polyline|radialGradient|rect|set|solidcolor|stop|svg|switch|symbol|text|textPath|tref|tspan|unknown|use|view|vkern)$/; const svg = /^(?:altGlyph|altGlyphDef|altGlyphItem|animate|animateColor|animateMotion|animateTransform|circle|clipPath|color-profile|cursor|defs|desc|discard|ellipse|feBlend|feColorMatrix|feComponentTransfer|feComposite|feConvolveMatrix|feDiffuseLighting|feDisplacementMap|feDistantLight|feDropShadow|feFlood|feFuncA|feFuncB|feFuncG|feFuncR|feGaussianBlur|feImage|feMerge|feMergeNode|feMorphology|feOffset|fePointLight|feSpecularLighting|feSpotLight|feTile|feTurbulence|filter|font|font-face|font-face-format|font-face-name|font-face-src|font-face-uri|foreignObject|g|glyph|glyphRef|hatch|hatchpath|hkern|image|line|linearGradient|marker|mask|mesh|meshgradient|meshpatch|meshrow|metadata|missing-glyph|mpath|path|pattern|polygon|polyline|radialGradient|rect|set|solidcolor|stop|svg|switch|symbol|text|textPath|tref|tspan|unknown|use|view|vkern)$/;
@ -101,7 +102,7 @@ export default class Element extends Node {
intro?: Transition = null; intro?: Transition = null;
outro?: Transition = null; outro?: Transition = null;
animation?: Animation = null; animation?: Animation = null;
children: Node[]; children: INode[];
namespace: string; namespace: string;
constructor(component, parent, scope, info: any) { constructor(component, parent, scope, info: any) {
@ -136,7 +137,7 @@ export default class Element extends Node {
// Special case — treat these the same way: // Special case — treat these the same way:
// <option>{foo}</option> // <option>{foo}</option>
// <option value={foo}>{foo}</option> // <option value={foo}>{foo}</option>
const value_attribute = info.attributes.find((attribute: Node) => attribute.name === 'value'); const value_attribute = info.attributes.find(attribute => attribute.name === 'value');
if (!value_attribute) { if (!value_attribute) {
info.attributes.push({ info.attributes.push({
@ -228,7 +229,7 @@ export default class Element extends Node {
let is_figure_parent = false; let is_figure_parent = false;
while (parent) { while (parent) {
if (parent.name === 'figure') { if ((parent as Element).name === 'figure') {
is_figure_parent = true; is_figure_parent = true;
break; break;
} }
@ -253,7 +254,7 @@ export default class Element extends Node {
return true; return true;
}); });
const index = children.findIndex(child => child.name === 'figcaption'); const index = children.findIndex(child => (child as Element).name === 'figcaption');
if (index !== -1 && (index !== 0 && index !== children.length - 1)) { if (index !== -1 && (index !== 0 && index !== children.length - 1)) {
this.component.warn(children[index], { this.component.warn(children[index], {
@ -320,7 +321,9 @@ export default class Element extends Node {
} }
const value = attribute.get_static_value(); const value = attribute.get_static_value();
// @ts-ignore
if (value && !aria_role_set.has(value)) { if (value && !aria_role_set.has(value)) {
// @ts-ignore
const match = fuzzymatch(value, aria_roles); const match = fuzzymatch(value, aria_roles);
let message = `A11y: Unknown role '${value}'`; let message = `A11y: Unknown role '${value}'`;
if (match) message += ` (did you mean '${match}'?)`; if (match) message += ` (did you mean '${match}'?)`;
@ -359,6 +362,7 @@ export default class Element extends Node {
// tabindex-no-positive // tabindex-no-positive
if (name === 'tabindex') { if (name === 'tabindex') {
const value = attribute.get_static_value(); const value = attribute.get_static_value();
// @ts-ignore todo is tabindex=true correct case?
if (!isNaN(value) && +value > 0) { if (!isNaN(value) && +value > 0) {
component.warn(attribute, { component.warn(attribute, {
code: `a11y-positive-tabindex`, code: `a11y-positive-tabindex`,
@ -544,7 +548,7 @@ export default class Element extends Node {
message: `'group' binding can only be used with <input type="checkbox"> or <input type="radio">` message: `'group' binding can only be used with <input type="checkbox"> or <input type="radio">`
}); });
} }
} else if (name == 'files') { } else if (name === 'files') {
if (this.name !== 'input') { if (this.name !== 'input') {
component.error(binding, { component.error(binding, {
code: `invalid-binding`, code: `invalid-binding`,
@ -560,6 +564,14 @@ export default class Element extends Node {
message: `'files' binding can only be used with <input type="file">` message: `'files' binding can only be used with <input type="file">`
}); });
} }
} else if (name === 'open') {
if (this.name !== 'details') {
component.error(binding, {
code: `invalid-binding`,
message: `'${name}' binding can only be used with <details>`
});
}
} else if ( } else if (
name === 'currentTime' || name === 'currentTime' ||
name === 'duration' || name === 'duration' ||

@ -1,10 +1,11 @@
import map_children from './shared/map_children'; import map_children from './shared/map_children';
import AbstractBlock from './shared/AbstractBlock'; import AbstractBlock from './shared/AbstractBlock';
import Component from '../Component';
export default class ElseBlock extends AbstractBlock { export default class ElseBlock extends AbstractBlock {
type: 'ElseBlock'; type: 'ElseBlock';
constructor(component, parent, scope, info) { constructor(component: Component, parent, scope, info) {
super(component, parent, scope, info); super(component, parent, scope, info);
this.children = map_children(component, this, scope, info.children); this.children = map_children(component, this, scope, info.children);

@ -5,6 +5,7 @@ import deindent from '../utils/deindent';
import Block from '../render-dom/Block'; import Block from '../render-dom/Block';
export default class EventHandler extends Node { export default class EventHandler extends Node {
type: 'EventHandler';
name: string; name: string;
modifiers: Set<string>; modifiers: Set<string>;
expression: Expression; expression: Expression;
@ -65,4 +66,4 @@ export default class EventHandler extends Node {
// this.component.add_reference(this.handler_name); // this.component.add_reference(this.handler_name);
return `ctx.${this.handler_name}`; return `ctx.${this.handler_name}`;
} }
} }

@ -3,10 +3,12 @@ import Component from '../Component';
import map_children from './shared/map_children'; import map_children from './shared/map_children';
import Block from '../render-dom/Block'; import Block from '../render-dom/Block';
import TemplateScope from './shared/TemplateScope'; import TemplateScope from './shared/TemplateScope';
import { INode } from './interfaces';
export default class Fragment extends Node { export default class Fragment extends Node {
type: 'Fragment';
block: Block; block: Block;
children: Node[]; children: INode[];
scope: TemplateScope; scope: TemplateScope;
constructor(component: Component, info: any) { constructor(component: Component, info: any) {
@ -16,4 +18,4 @@ export default class Fragment extends Node {
this.scope = scope; this.scope = scope;
this.children = map_children(component, this, scope, info.children); this.children = map_children(component, this, scope, info.children);
} }
} }

@ -1,5 +1,4 @@
import Node from './shared/Node'; import Node from './shared/Node';
import Block from '../render-dom/Block';
import map_children from './shared/map_children'; import map_children from './shared/map_children';
export default class Head extends Node { export default class Head extends Node {

@ -7,6 +7,7 @@ import Expression from './shared/Expression';
import Component from '../Component'; import Component from '../Component';
import Let from './Let'; import Let from './Let';
import TemplateScope from './shared/TemplateScope'; import TemplateScope from './shared/TemplateScope';
import { INode } from './interfaces';
export default class InlineComponent extends Node { export default class InlineComponent extends Node {
type: 'InlineComponent'; type: 'InlineComponent';
@ -16,7 +17,7 @@ export default class InlineComponent extends Node {
bindings: Binding[] = []; bindings: Binding[] = [];
handlers: EventHandler[] = []; handlers: EventHandler[] = [];
lets: Let[] = []; lets: Let[] = [];
children: Node[]; children: INode[];
scope: TemplateScope; scope: TemplateScope;
constructor(component: Component, parent, scope, info) { constructor(component: Component, parent, scope, info) {

@ -1,3 +1,5 @@
import Tag from './shared/Tag'; import Tag from './shared/Tag';
export default class MustacheTag extends Tag {} export default class MustacheTag extends Tag {
type: 'MustacheTag';
}

@ -2,7 +2,7 @@ import map_children from './shared/map_children';
import AbstractBlock from './shared/AbstractBlock'; import AbstractBlock from './shared/AbstractBlock';
export default class PendingBlock extends AbstractBlock { export default class PendingBlock extends AbstractBlock {
type: 'PendingBlock';
constructor(component, parent, scope, info) { constructor(component, parent, scope, info) {
super(component, parent, scope, info); super(component, parent, scope, info);
this.children = map_children(component, parent, scope, info.children); this.children = map_children(component, parent, scope, info.children);

@ -1,3 +1,5 @@
import Tag from './shared/Tag'; import Tag from './shared/Tag';
export default class RawMustacheTag extends Tag {} export default class RawMustacheTag extends Tag {
type: 'RawMustacheTag'
}

@ -1,17 +1,17 @@
import Node from './shared/Node';
import Element from './Element'; import Element from './Element';
import Attribute from './Attribute'; import Attribute from './Attribute';
import Component from '../Component'; import Component from '../Component';
import TemplateScope from './shared/TemplateScope'; import TemplateScope from './shared/TemplateScope';
import { INode } from './interfaces';
export default class Slot extends Element { export default class Slot extends Element {
type: 'Element'; type: 'Element';
name: string; name: string;
children: Node[]; children: INode[];
slot_name: string; slot_name: string;
values: Map<string, Attribute> = new Map(); values: Map<string, Attribute> = new Map();
constructor(component: Component, parent: Node, scope: TemplateScope, info: any) { constructor(component: Component, parent: INode, scope: TemplateScope, info: any) {
super(component, parent, scope, info); super(component, parent, scope, info);
info.attributes.forEach(attr => { info.attributes.forEach(attr => {
@ -68,4 +68,4 @@ export default class Slot extends Element {
component.slots.set(this.slot_name, this); component.slots.set(this.slot_name, this);
} }
} }

@ -1,13 +1,14 @@
import Node from './shared/Node'; import Node from './shared/Node';
import Component from '../Component'; import Component from '../Component';
import TemplateScope from './shared/TemplateScope'; import TemplateScope from './shared/TemplateScope';
import { INode } from './interfaces';
export default class Text extends Node { export default class Text extends Node {
type: 'Text'; type: 'Text';
data: string; data: string;
use_space = false; use_space = false;
constructor(component: Component, parent: Node, scope: TemplateScope, info: any) { constructor(component: Component, parent: INode, scope: TemplateScope, info: any) {
super(component, parent, scope, info); super(component, parent, scope, info);
this.data = info.data; this.data = info.data;
@ -23,4 +24,4 @@ export default class Text extends Node {
this.use_space = true; this.use_space = true;
} }
} }
} }

@ -3,6 +3,7 @@ import TemplateScope from './shared/TemplateScope';
import AbstractBlock from './shared/AbstractBlock'; import AbstractBlock from './shared/AbstractBlock';
export default class ThenBlock extends AbstractBlock { export default class ThenBlock extends AbstractBlock {
type: 'ThenBlock';
scope: TemplateScope; scope: TemplateScope;
constructor(component, parent, scope, info) { constructor(component, parent, scope, info) {

@ -1,12 +1,13 @@
import Node from './shared/Node'; import Node from './shared/Node';
import map_children from './shared/map_children'; import map_children, { Children } from './shared/map_children';
import Component from '../Component';
export default class Title extends Node { export default class Title extends Node {
type: 'Title'; type: 'Title';
children: any[]; // TODO children: Children;
should_cache: boolean; should_cache: boolean;
constructor(component, parent, scope, info) { constructor(component: Component, parent, scope, info) {
super(component, parent, scope, info); super(component, parent, scope, info);
this.children = map_children(component, parent, scope, info.children); this.children = map_children(component, parent, scope, info.children);
@ -33,4 +34,4 @@ export default class Title extends Node {
) )
: true; : true;
} }
} }

@ -0,0 +1,64 @@
import Tag from './shared/Tag';
import Action from './Action';
import Animation from './Animation';
import Attribute from './Attribute';
import AwaitBlock from './AwaitBlock';
import Binding from './Binding';
import Body from './Body';
import CatchBlock from './CatchBlock';
import Class from './Class';
import Comment from './Comment';
import DebugTag from './DebugTag';
import EachBlock from './EachBlock';
import Element from './Element';
import ElseBlock from './ElseBlock';
import EventHandler from './EventHandler';
import Fragment from './Fragment';
import Head from './Head';
import IfBlock from './IfBlock';
import InlineComponent from './InlineComponent';
import Let from './Let';
import MustacheTag from './MustacheTag';
import Options from './Options';
import PendingBlock from './PendingBlock';
import RawMustacheTag from './RawMustacheTag';
import Slot from './Slot';
import Text from './Text';
import ThenBlock from './ThenBlock';
import Title from './Title';
import Transition from './Transition';
import Window from './Window';
// note: to write less types each of types in union below should have type defined as literal
// https://www.typescriptlang.org/docs/handbook/advanced-types.html#discriminated-unions
export type INode = Action
| Animation
| Attribute
| AwaitBlock
| Binding
| Body
| CatchBlock
| Class
| Comment
| DebugTag
| EachBlock
| Element
| ElseBlock
| EventHandler
| Fragment
| Head
| IfBlock
| InlineComponent
| Let
| MustacheTag
| Options
| PendingBlock
| RawMustacheTag
| Slot
| Tag
| Text
| ThenBlock
| Title
| Transition
| Window;

@ -1,10 +1,11 @@
import Block from '../../render-dom/Block'; import Block from '../../render-dom/Block';
import Component from './../../Component'; import Component from './../../Component';
import Node from './Node'; import Node from './Node';
import { INode } from '../interfaces';
export default class AbstractBlock extends Node { export default class AbstractBlock extends Node {
block: Block; block: Block;
children: Node[]; children: INode[];
constructor(component: Component, parent, scope, info: any) { constructor(component: Component, parent, scope, info: any) {
super(component, parent, scope, info); super(component, parent, scope, info);

@ -12,6 +12,7 @@ import TemplateScope from './TemplateScope';
import get_object from '../../utils/get_object'; import get_object from '../../utils/get_object';
import { nodes_match } from '../../../utils/nodes_match'; import { nodes_match } from '../../../utils/nodes_match';
import Block from '../../render-dom/Block'; import Block from '../../render-dom/Block';
import { INode } from '../interfaces';
const binary_operators: Record<string, number> = { const binary_operators: Record<string, number> = {
'**': 15, '**': 15,
@ -61,10 +62,12 @@ const precedence: Record<string, (node?: Node) => number> = {
SequenceExpression: () => 0 SequenceExpression: () => 0
}; };
type Owner = Wrapper | INode;
export default class Expression { export default class Expression {
type = 'Expression'; type: 'Expression' = 'Expression';
component: Component; component: Component;
owner: Wrapper; owner: Owner;
node: any; node: any;
snippet: string; snippet: string;
references: Set<string>; references: Set<string>;
@ -81,7 +84,8 @@ export default class Expression {
rendered: string; rendered: string;
constructor(component: Component, owner: Wrapper, template_scope: TemplateScope, info) { // todo: owner type
constructor(component: Component, owner: Owner, template_scope: TemplateScope, info) {
// TODO revert to direct property access in prod? // TODO revert to direct property access in prod?
Object.defineProperties(this, { Object.defineProperties(this, {
component: { component: {
@ -92,6 +96,7 @@ export default class Expression {
this.node = info; this.node = info;
this.template_scope = template_scope; this.template_scope = template_scope;
this.owner = owner; this.owner = owner;
// @ts-ignore
this.is_synthetic = owner.is_synthetic; this.is_synthetic = owner.is_synthetic;
const { dependencies, contextual_dependencies } = this; const { dependencies, contextual_dependencies } = this;
@ -218,7 +223,7 @@ export default class Expression {
} }
// TODO move this into a render-dom wrapper? // TODO move this into a render-dom wrapper?
render(block: Block) { render(block?: Block) {
if (this.rendered) return this.rendered; if (this.rendered) return this.rendered;
const { const {
@ -510,4 +515,4 @@ function is_contextual(component: Component, scope: TemplateScope, name: string)
// assume contextual // assume contextual
return true; return true;
} }

@ -1,15 +1,17 @@
import Attribute from './../Attribute'; import Attribute from './../Attribute';
import Component from './../../Component'; import Component from './../../Component';
import { INode } from '../interfaces';
import Text from '../Text';
export default class Node { export default class Node {
readonly start: number; readonly start: number;
readonly end: number; readonly end: number;
readonly component: Component; readonly component: Component;
readonly parent: Node; readonly parent: INode;
readonly type: string; readonly type: string;
prev?: Node; prev?: INode;
next?: Node; next?: INode;
can_use_innerhtml: boolean; can_use_innerhtml: boolean;
var: string; var: string;
@ -45,7 +47,7 @@ export default class Node {
} }
get_static_attribute_value(name: string) { get_static_attribute_value(name: string) {
const attribute = this.attributes.find( const attribute = this.attributes && this.attributes.find(
(attr: Attribute) => attr.type === 'Attribute' && attr.name.toLowerCase() === name (attr: Attribute) => attr.type === 'Attribute' && attr.name.toLowerCase() === name
); );
@ -55,7 +57,7 @@ export default class Node {
if (attribute.chunks.length === 0) return ''; if (attribute.chunks.length === 0) return '';
if (attribute.chunks.length === 1 && attribute.chunks[0].type === 'Text') { if (attribute.chunks.length === 1 && attribute.chunks[0].type === 'Text') {
return attribute.chunks[0].data; return (attribute.chunks[0] as Text).data;
} }
return null; return null;

@ -2,6 +2,7 @@ import Node from './Node';
import Expression from './Expression'; import Expression from './Expression';
export default class Tag extends Node { export default class Tag extends Node {
type: 'MustacheTag' | 'RawMustacheTag';
expression: Expression; expression: Expression;
should_cache: boolean; should_cache: boolean;
@ -14,4 +15,4 @@ export default class Tag extends Node {
(this.expression.dependencies.size && scope.names.has(info.expression.name)) (this.expression.dependencies.size && scope.names.has(info.expression.name))
); );
} }
} }

@ -2,6 +2,7 @@ import EachBlock from '../EachBlock';
import ThenBlock from '../ThenBlock'; import ThenBlock from '../ThenBlock';
import CatchBlock from '../CatchBlock'; import CatchBlock from '../CatchBlock';
import InlineComponent from '../InlineComponent'; import InlineComponent from '../InlineComponent';
import Element from '../Element';
type NodeWithScope = EachBlock | ThenBlock | CatchBlock | InlineComponent | Element; type NodeWithScope = EachBlock | ThenBlock | CatchBlock | InlineComponent | Element;
@ -41,4 +42,4 @@ export default class TemplateScope {
const owner = this.get_owner(name); const owner = this.get_owner(name);
return owner && (owner.type === 'Element' || owner.type === 'InlineComponent'); return owner && (owner.type === 'Element' || owner.type === 'InlineComponent');
} }
} }

@ -14,9 +14,11 @@ import Slot from '../Slot';
import Text from '../Text'; import Text from '../Text';
import Title from '../Title'; import Title from '../Title';
import Window from '../Window'; import Window from '../Window';
import Node from './Node'; import { Node } from '../../../interfaces';
function get_constructor(type): typeof Node { export type Children = ReturnType<typeof map_children>;
function get_constructor(type) {
switch (type) { switch (type) {
case 'AwaitBlock': return AwaitBlock; case 'AwaitBlock': return AwaitBlock;
case 'Body': return Body; case 'Body': return Body;
@ -38,7 +40,7 @@ function get_constructor(type): typeof Node {
} }
} }
export default function map_children(component, parent, scope, children: any[]) { export default function map_children(component, parent, scope, children: Node[]) {
let last = null; let last = null;
return children.map(child => { return children.map(child => {
const constructor = get_constructor(child.type); const constructor = get_constructor(child.type);

@ -9,7 +9,7 @@ export interface BlockOptions {
renderer?: Renderer; renderer?: Renderer;
comment?: string; comment?: string;
key?: string; key?: string;
bindings?: Map<string, () => { object: string, property: string, snippet: string }>; bindings?: Map<string, { object: string, property: string, snippet: string }>;
dependencies?: Set<string>; dependencies?: Set<string>;
} }

@ -3,7 +3,6 @@ import { CompileOptions } from '../../interfaces';
import Component from '../Component'; import Component from '../Component';
import FragmentWrapper from './wrappers/Fragment'; import FragmentWrapper from './wrappers/Fragment';
import CodeBuilder from '../utils/CodeBuilder'; import CodeBuilder from '../utils/CodeBuilder';
import SlotWrapper from './wrappers/Slot';
export default class Renderer { export default class Renderer {
component: Component; // TODO Maybe Renderer shouldn't know about Component? component: Component; // TODO Maybe Renderer shouldn't know about Component?
@ -18,6 +17,7 @@ export default class Renderer {
fragment: FragmentWrapper; fragment: FragmentWrapper;
file_var: string; file_var: string;
locate: (c: number) => { line: number; column: number; };
constructor(component: Component, options: CompileOptions) { constructor(component: Component, options: CompileOptions) {
this.component = component; this.component = component;
@ -58,4 +58,4 @@ export default class Renderer {
this.fragment.render(this.block, null, 'nodes'); this.fragment.render(this.block, null, 'nodes');
} }
} }

@ -134,7 +134,6 @@ export default function dom(
}); });
if (component.compile_options.dev) { if (component.compile_options.dev) {
// TODO check no uunexpected props were passed, as well as
// checking that expected ones were passed // checking that expected ones were passed
const expected = props.filter(prop => !prop.initialised); const expected = props.filter(prop => !prop.initialised);
@ -397,6 +396,16 @@ export default function dom(
return $name; return $name;
}); });
let unknown_props_check;
if (component.compile_options.dev && writable_props.length) {
unknown_props_check = deindent`
const writable_props = [${writable_props.map(prop => `'${prop.export_name}'`).join(', ')}];
Object.keys($$props).forEach(key => {
if (!writable_props.includes(key)) console.warn(\`<${component.tag}> was created with unknown prop '\${key}'\`);
});
`;
}
builder.add_block(deindent` builder.add_block(deindent`
function ${definition}(${args.join(', ')}) { function ${definition}(${args.join(', ')}) {
${reactive_store_declarations.length > 0 && `let ${reactive_store_declarations.join(', ')};`} ${reactive_store_declarations.length > 0 && `let ${reactive_store_declarations.join(', ')};`}
@ -407,6 +416,8 @@ export default function dom(
${component.javascript} ${component.javascript}
${unknown_props_check}
${component.slots.size && `let { $$slots = {}, $$scope } = $$props;`} ${component.slots.size && `let { $$slots = {}, $$scope } = $$props;`}
${renderer.binding_groups.length > 0 && `const $$binding_groups = [${renderer.binding_groups.map(_ => `[]`).join(', ')}];`} ${renderer.binding_groups.length > 0 && `const $$binding_groups = [${renderer.binding_groups.map(_ => `[]`).join(', ')}];`}

@ -8,7 +8,7 @@ import deindent from '../../utils/deindent';
import ElseBlock from '../../nodes/ElseBlock'; import ElseBlock from '../../nodes/ElseBlock';
import { attach_head } from '../../utils/tail'; import { attach_head } from '../../utils/tail';
class ElseBlockWrapper extends Wrapper { export class ElseBlockWrapper extends Wrapper {
node: ElseBlock; node: ElseBlock;
block: Block; block: Block;
fragment: FragmentWrapper; fragment: FragmentWrapper;
@ -83,6 +83,7 @@ export default class EachBlockWrapper extends Wrapper {
this.block = block.child({ this.block = block.child({
comment: create_debugging_comment(this.node, this.renderer.component), comment: create_debugging_comment(this.node, this.renderer.component),
name: renderer.component.get_unique_name('create_each_block'), name: renderer.component.get_unique_name('create_each_block'),
// @ts-ignore todo: probably error
key: node.key as string, key: node.key as string,
bindings: new Map(block.bindings) bindings: new Map(block.bindings)
@ -310,7 +311,9 @@ export default class EachBlockWrapper extends Wrapper {
} }
block.builders.init.add_block(deindent` block.builders.init.add_block(deindent`
const ${get_key} = ctx => ${this.node.key.render()}; const ${get_key} = ctx => ${
// @ts-ignore todo: probably error
this.node.key.render()};
for (var #i = 0; #i < ${this.vars.each_block_value}.${length}; #i += 1) { for (var #i = 0; #i < ${this.vars.each_block_value}.${length}; #i += 1) {
let child_ctx = ${this.vars.get_each_context}(ctx, ${this.vars.each_block_value}, #i); let child_ctx = ${this.vars.get_each_context}(ctx, ${this.vars.each_block_value}, #i);

@ -4,6 +4,7 @@ import fix_attribute_casing from './fix_attribute_casing';
import ElementWrapper from './index'; import ElementWrapper from './index';
import { stringify } from '../../../utils/stringify'; import { stringify } from '../../../utils/stringify';
import deindent from '../../../utils/deindent'; import deindent from '../../../utils/deindent';
import Expression from '../../../nodes/shared/Expression';
export default class AttributeWrapper { export default class AttributeWrapper {
node: Attribute; node: Attribute;
@ -21,7 +22,9 @@ export default class AttributeWrapper {
// special case — <option value={foo}> — see below // special case — <option value={foo}> — see below
if (this.parent.node.name === 'option' && node.name === 'value') { if (this.parent.node.name === 'option' && node.name === 'value') {
let select: ElementWrapper = this.parent; let select: ElementWrapper = this.parent;
while (select && (select.node.type !== 'Element' || select.node.name !== 'select')) select = select.parent; while (select && (select.node.type !== 'Element' || select.node.name !== 'select'))
// @ts-ignore todo: doublecheck this, but looks to be correct
select = select.parent;
if (select && select.select_binding_dependencies) { if (select && select.select_binding_dependencies) {
select.select_binding_dependencies.forEach(prop => { select.select_binding_dependencies.forEach(prop => {
@ -47,7 +50,7 @@ export default class AttributeWrapper {
(element.node.name === 'option' || // TODO check it's actually bound (element.node.name === 'option' || // TODO check it's actually bound
(element.node.name === 'input' && (element.node.name === 'input' &&
element.node.bindings.find( element.node.bindings.find(
(binding: Binding) => (binding) =>
/checked|group/.test(binding.name) /checked|group/.test(binding.name)
))); )));
@ -78,13 +81,13 @@ export default class AttributeWrapper {
// DRY it out if that's possible without introducing crazy indirection // DRY it out if that's possible without introducing crazy indirection
if (this.node.chunks.length === 1) { if (this.node.chunks.length === 1) {
// single {tag} — may be a non-string // single {tag} — may be a non-string
value = this.node.chunks[0].render(block); value = (this.node.chunks[0] as Expression).render(block);
} else { } else {
// '{foo} {bar}' — treat as string concatenation // '{foo} {bar}' — treat as string concatenation
value = value =
(this.node.chunks[0].type === 'Text' ? '' : `"" + `) + (this.node.chunks[0].type === 'Text' ? '' : `"" + `) +
this.node.chunks this.node.chunks
.map((chunk: Node) => { .map((chunk) => {
if (chunk.type === 'Text') { if (chunk.type === 'Text') {
return stringify(chunk.data); return stringify(chunk.data);
} else { } else {

@ -9,14 +9,6 @@ import flatten_reference from '../../../utils/flatten_reference';
import EachBlock from '../../../nodes/EachBlock'; import EachBlock from '../../../nodes/EachBlock';
import { Node as INode } from '../../../../interfaces'; import { Node as INode } from '../../../../interfaces';
// TODO this should live in a specific binding
const read_only_media_attributes = new Set([
'duration',
'buffered',
'seekable',
'played'
]);
function get_tail(node: INode) { function get_tail(node: INode) {
const end = node.end; const end = node.end;
while (node.type === 'MemberExpression') node = node.object; while (node.type === 'MemberExpression') node = node.object;
@ -74,13 +66,7 @@ export default class BindingWrapper {
this.snippet = this.node.expression.render(block); this.snippet = this.node.expression.render(block);
const type = parent.node.get_static_attribute_value('type'); this.is_readonly = this.node.is_readonly;
this.is_readonly = (
dimensions.test(this.node.name) ||
(parent.node.is_media_node() && read_only_media_attributes.has(this.node.name)) ||
(parent.node.name === 'input' && type === 'file') // TODO others?
);
this.needs_lock = this.node.name === 'currentTime'; // TODO others? this.needs_lock = this.node.name === 'currentTime'; // TODO others?
} }
@ -101,7 +87,7 @@ export default class BindingWrapper {
} }
is_readonly_media_attribute() { is_readonly_media_attribute() {
return read_only_media_attributes.has(this.node.name); return this.node.is_readonly_media_attribute()
} }
render(block: Block, lock: string) { render(block: Block, lock: string) {

@ -1,14 +1,15 @@
import Attribute from '../../../nodes/Attribute'; import Attribute from '../../../nodes/Attribute';
import Block from '../../Block'; import Block from '../../Block';
import AttributeWrapper from './Attribute'; import AttributeWrapper from './Attribute';
import Node from '../../../nodes/shared/Node';
import ElementWrapper from '.'; import ElementWrapper from '.';
import { stringify } from '../../../utils/stringify'; import { stringify } from '../../../utils/stringify';
import add_to_set from '../../../utils/add_to_set'; import add_to_set from '../../../utils/add_to_set';
import Expression from '../../../nodes/shared/Expression';
import Text from '../../../nodes/Text';
export interface StyleProp { export interface StyleProp {
key: string; key: string;
value: Node[]; value: (Text|Expression)[];
} }
export default class StyleAttributeWrapper extends AttributeWrapper { export default class StyleAttributeWrapper extends AttributeWrapper {
@ -28,7 +29,7 @@ export default class StyleAttributeWrapper extends AttributeWrapper {
value = value =
((prop.value.length === 1 || prop.value[0].type === 'Text') ? '' : `"" + `) + ((prop.value.length === 1 || prop.value[0].type === 'Text') ? '' : `"" + `) +
prop.value prop.value
.map((chunk: Node) => { .map((chunk) => {
if (chunk.type === 'Text') { if (chunk.type === 'Text') {
return stringify(chunk.data); return stringify(chunk.data);
} else { } else {
@ -54,7 +55,7 @@ export default class StyleAttributeWrapper extends AttributeWrapper {
); );
} }
} else { } else {
value = stringify(prop.value[0].data); value = stringify((prop.value[0] as Text).data);
} }
block.builders.hydrate.add_line( block.builders.hydrate.add_line(
@ -64,8 +65,8 @@ export default class StyleAttributeWrapper extends AttributeWrapper {
} }
} }
function optimize_style(value: Node[]) { function optimize_style(value: (Text|Expression)[]) {
const props: { key: string, value: Node[] }[] = []; const props: StyleProp[] = [];
let chunks = value.slice(); let chunks = value.slice();
while (chunks.length) { while (chunks.length) {
@ -87,7 +88,7 @@ function optimize_style(value: Node[]) {
end: chunk.end, end: chunk.end,
type: 'Text', type: 'Text',
data: remaining_data data: remaining_data
}; } as Text;
} else { } else {
chunks.shift(); chunks.shift();
} }
@ -101,8 +102,8 @@ function optimize_style(value: Node[]) {
return props; return props;
} }
function get_style_value(chunks: Node[]) { function get_style_value(chunks: (Text | Expression)[]) {
const value: Node[] = []; const value: (Text|Expression)[] = [];
let in_url = false; let in_url = false;
let quote_mark = null; let quote_mark = null;
@ -141,7 +142,7 @@ function get_style_value(chunks: Node[]) {
start: chunk.start, start: chunk.start,
end: chunk.start + c, end: chunk.start + c,
data: chunk.data.slice(0, c) data: chunk.data.slice(0, c)
}); } as Text);
} }
while (/[;\s]/.test(chunk.data[c])) c += 1; while (/[;\s]/.test(chunk.data[c])) c += 1;
@ -153,7 +154,7 @@ function get_style_value(chunks: Node[]) {
end: chunk.end, end: chunk.end,
type: 'Text', type: 'Text',
data: remaining_data data: remaining_data
}); } as Text);
break; break;
} }
@ -170,6 +171,6 @@ function get_style_value(chunks: Node[]) {
}; };
} }
function is_dynamic(value: Node[]) { function is_dynamic(value: (Text|Expression)[]) {
return value.length > 1 || value[0].type !== 'Text'; return value.length > 1 || value[0].type !== 'Text';
} }

@ -20,20 +20,19 @@ import add_event_handlers from '../shared/add_event_handlers';
import add_actions from '../shared/add_actions'; import add_actions from '../shared/add_actions';
import create_debugging_comment from '../shared/create_debugging_comment'; import create_debugging_comment from '../shared/create_debugging_comment';
import { get_context_merger } from '../shared/get_context_merger'; import { get_context_merger } from '../shared/get_context_merger';
import Slot from '../../../nodes/Slot';
const events = [ const events = [
{ {
event_names: ['input'], event_names: ['input'],
filter: (node: Element, name: string) => filter: (node: Element, name: string) =>
node.name === 'textarea' || node.name === 'textarea' ||
node.name === 'input' && !/radio|checkbox|range/.test(node.get_static_attribute_value('type')) node.name === 'input' && !/radio|checkbox|range/.test(node.get_static_attribute_value('type') as string)
}, },
{ {
event_names: ['change'], event_names: ['change'],
filter: (node: Element, name: string) => filter: (node: Element, name: string) =>
node.name === 'select' || node.name === 'select' ||
node.name === 'input' && /radio|checkbox/.test(node.get_static_attribute_value('type')) node.name === 'input' && /radio|checkbox/.test(node.get_static_attribute_value('type') as string)
}, },
{ {
event_names: ['change', 'input'], event_names: ['change', 'input'],
@ -91,6 +90,12 @@ const events = [
name === 'playbackRate' name === 'playbackRate'
}, },
// details event
{
event_names: ['toggle'],
filter: (node: Element, name: string) =>
node.name === 'details'
},
]; ];
export default class ElementWrapper extends Wrapper { export default class ElementWrapper extends Wrapper {
@ -135,7 +140,7 @@ export default class ElementWrapper extends Wrapper {
} }
if (owner && owner.node.type === 'InlineComponent') { if (owner && owner.node.type === 'InlineComponent') {
const name = attribute.get_static_value(); const name = attribute.get_static_value() as string;
if (!(owner as InlineComponentWrapper).slots.has(name)) { if (!(owner as InlineComponentWrapper).slots.has(name)) {
const child_block = block.child({ const child_block = block.child({
@ -275,6 +280,7 @@ export default class ElementWrapper extends Wrapper {
if (!this.node.namespace && this.can_use_innerhtml && this.fragment.nodes.length > 0) { if (!this.node.namespace && this.can_use_innerhtml && this.fragment.nodes.length > 0) {
if (this.fragment.nodes.length === 1 && this.fragment.nodes[0].node.type === 'Text') { if (this.fragment.nodes.length === 1 && this.fragment.nodes[0].node.type === 'Text') {
block.builders.create.add_line( block.builders.create.add_line(
// @ts-ignore todo: should it be this.fragment.nodes[0].node.data instead?
`${node}.textContent = ${stringify(this.fragment.nodes[0].data)};` `${node}.textContent = ${stringify(this.fragment.nodes[0].data)};`
); );
} else { } else {
@ -324,7 +330,7 @@ export default class ElementWrapper extends Wrapper {
function to_html(wrapper: ElementWrapper | TextWrapper) { function to_html(wrapper: ElementWrapper | TextWrapper) {
if (wrapper.node.type === 'Text') { if (wrapper.node.type === 'Text') {
const { parent } = wrapper.node; const parent = wrapper.node.parent as Element;
const raw = parent && ( const raw = parent && (
parent.name === 'script' || parent.name === 'script' ||
@ -349,7 +355,7 @@ export default class ElementWrapper extends Wrapper {
if (is_void(wrapper.node.name)) return open + '>'; if (is_void(wrapper.node.name)) return open + '>';
return `${open}>${wrapper.fragment.nodes.map(to_html).join('')}</${wrapper.node.name}>`; return `${open}>${(wrapper as ElementWrapper).fragment.nodes.map(to_html).join('')}</${wrapper.node.name}>`;
} }
if (renderer.options.dev) { if (renderer.options.dev) {
@ -376,8 +382,8 @@ export default class ElementWrapper extends Wrapper {
get_claim_statement(nodes: string) { get_claim_statement(nodes: string) {
const attributes = this.node.attributes const attributes = this.node.attributes
.filter((attr: Node) => attr.type === 'Attribute') .filter((attr) => attr.type === 'Attribute')
.map((attr: Node) => `${quote_name_if_necessary(attr.name)}: true`) .map((attr) => `${quote_name_if_necessary(attr.name)}: true`)
.join(', '); .join(', ');
const name = this.node.namespace const name = this.node.namespace
@ -455,7 +461,7 @@ export default class ElementWrapper extends Wrapper {
function ${handler}() { function ${handler}() {
${animation_frame && deindent` ${animation_frame && deindent`
cancelAnimationFrame(${animation_frame}); cancelAnimationFrame(${animation_frame});
if (!${this.var}.paused) ${animation_frame} = requestAnimationFrame(${handler});`} if (!${this.var}.paused) ${animation_frame} = @raf(${handler});`}
${needs_lock && `${lock} = true;`} ${needs_lock && `${lock} = true;`}
ctx.${handler}.call(${this.var}${contextual_dependencies.size > 0 ? ', ctx' : ''}); ctx.${handler}.call(${this.var}${contextual_dependencies.size > 0 ? ', ctx' : ''});
} }
@ -553,12 +559,13 @@ export default class ElementWrapper extends Wrapper {
} }
add_attributes(block: Block) { add_attributes(block: Block) {
// @ts-ignore todo:
if (this.node.attributes.find(attr => attr.type === 'Spread')) { if (this.node.attributes.find(attr => attr.type === 'Spread')) {
this.add_spread_attributes(block); this.add_spread_attributes(block);
return; return;
} }
this.attributes.forEach((attribute: Attribute) => { this.attributes.forEach((attribute) => {
if (attribute.node.name === 'class' && attribute.node.is_dynamic) { if (attribute.node.name === 'class' && attribute.node.is_dynamic) {
this.class_dependencies.push(...attribute.node.dependencies); this.class_dependencies.push(...attribute.node.dependencies);
} }
@ -814,27 +821,28 @@ export default class ElementWrapper extends Wrapper {
}); });
} }
add_css_class(class_name = this.component.stylesheet.id) { // todo: looks to be dead code copypasted from Element.add_css_class in src/compile/nodes/Element.ts
const class_attribute = this.attributes.find(a => a.name === 'class'); // add_css_class(class_name = this.component.stylesheet.id) {
if (class_attribute && !class_attribute.is_true) { // const class_attribute = this.attributes.find(a => a.name === 'class');
if (class_attribute.chunks.length === 1 && class_attribute.chunks[0].type === 'Text') { // if (class_attribute && !class_attribute.is_true) {
(class_attribute.chunks[0] as Text).data += ` ${class_name}`; // if (class_attribute.chunks.length === 1 && class_attribute.chunks[0].type === 'Text') {
} else { // (class_attribute.chunks[0] as Text).data += ` ${class_name}`;
(class_attribute.chunks as Node[]).push( // } else {
new Text(this.component, this, this.scope, { // (class_attribute.chunks as Node[]).push(
type: 'Text', // new Text(this.component, this, this.scope, {
data: ` ${class_name}` // type: 'Text',
}) // data: ` ${class_name}`
); // })
} // );
} else { // }
this.attributes.push( // } else {
new Attribute(this.component, this, this.scope, { // this.attributes.push(
type: 'Attribute', // new Attribute(this.component, this, this.scope, {
name: 'class', // type: 'Attribute',
value: [{ type: 'Text', data: class_name }] // name: 'class',
}) // value: [{ type: 'Text', data: class_name }]
); // })
} // );
} // }
// }
} }

@ -13,7 +13,7 @@ import Slot from './Slot';
import Text from './Text'; import Text from './Text';
import Title from './Title'; import Title from './Title';
import Window from './Window'; import Window from './Window';
import Node from '../../nodes/shared/Node'; import { INode } from '../../nodes/interfaces';
import TextWrapper from './Text'; import TextWrapper from './Text';
import Renderer from '../Renderer'; import Renderer from '../Renderer';
import Block from '../Block'; import Block from '../Block';
@ -49,7 +49,7 @@ export default class FragmentWrapper {
constructor( constructor(
renderer: Renderer, renderer: Renderer,
block: Block, block: Block,
nodes: Node[], nodes: INode[],
parent: Wrapper, parent: Wrapper,
strip_whitespace: boolean, strip_whitespace: boolean,
next_sibling: Wrapper next_sibling: Wrapper
@ -85,6 +85,7 @@ export default class FragmentWrapper {
// *unless* there is no whitespace between this node and its next sibling // *unless* there is no whitespace between this node and its next sibling
if (this.nodes.length === 0) { if (this.nodes.length === 0) {
const should_trim = ( const should_trim = (
// @ts-ignore todo: probably error, should it be next_sibling.node.data?
next_sibling ? (next_sibling.node.type === 'Text' && /^\s/.test(next_sibling.data)) : !child.has_ancestor('EachBlock') next_sibling ? (next_sibling.node.type === 'Text' && /^\s/.test(next_sibling.data)) : !child.has_ancestor('EachBlock')
); );
@ -96,6 +97,7 @@ export default class FragmentWrapper {
// glue text nodes (which could e.g. be separated by comments) together // glue text nodes (which could e.g. be separated by comments) together
if (last_child && last_child.node.type === 'Text') { if (last_child && last_child.node.type === 'Text') {
// @ts-ignore todo: probably error, should it be last_child.node.data?
last_child.data = data + last_child.data; last_child.data = data + last_child.data;
continue; continue;
} }

@ -96,7 +96,7 @@ export default class IfBlockWrapper extends Wrapper {
if (branch.block.has_outros) has_outros = true; if (branch.block.has_outros) has_outros = true;
if (is_else_if(node.else)) { if (is_else_if(node.else)) {
create_branches(node.else.children[0]); create_branches(node.else.children[0] as IfBlock);
} else if (node.else) { } else if (node.else) {
const branch = new IfBlockBranch( const branch = new IfBlockBranch(
renderer, renderer,
@ -452,4 +452,4 @@ export default class IfBlockWrapper extends Wrapper {
block.builders.destroy.add_line(`${if_name}${name}.d(${parent_node ? '' : 'detaching'});`); block.builders.destroy.add_line(`${if_name}${name}.d(${parent_node ? '' : 'detaching'});`);
} }
} }

@ -142,7 +142,7 @@ export default class InlineComponentWrapper extends Wrapper {
if (this.fragment) { if (this.fragment) {
const default_slot = this.slots.get('default'); const default_slot = this.slots.get('default');
this.fragment.nodes.forEach((child: Wrapper) => { this.fragment.nodes.forEach((child) => {
child.render(default_slot.block, null, 'nodes'); child.render(default_slot.block, null, 'nodes');
}); });
} }
@ -505,4 +505,4 @@ export default class InlineComponentWrapper extends Wrapper {
); );
} }
} }
} }

@ -1,13 +1,14 @@
import Renderer from '../Renderer'; import Renderer from '../Renderer';
import Block from '../Block'; import Block from '../Block';
import Node from '../../nodes/shared/Node';
import Tag from './shared/Tag'; import Tag from './shared/Tag';
import Wrapper from './shared/Wrapper'; import Wrapper from './shared/Wrapper';
import MustacheTag from '../../nodes/MustacheTag';
import RawMustacheTag from '../../nodes/RawMustacheTag';
export default class MustacheTagWrapper extends Tag { export default class MustacheTagWrapper extends Tag {
var = 't'; var = 't';
constructor(renderer: Renderer, block: Block, parent: Wrapper, node: Node) { constructor(renderer: Renderer, block: Block, parent: Wrapper, node: MustacheTag | RawMustacheTag) {
super(renderer, block, parent, node); super(renderer, block, parent, node);
this.cannot_use_innerhtml(); this.cannot_use_innerhtml();
} }
@ -25,4 +26,4 @@ export default class MustacheTagWrapper extends Tag {
parent_node parent_node
); );
} }
} }

@ -1,9 +1,10 @@
import Renderer from '../Renderer'; import Renderer from '../Renderer';
import Block from '../Block'; import Block from '../Block';
import Node from '../../nodes/shared/Node';
import Tag from './shared/Tag'; import Tag from './shared/Tag';
import Wrapper from './shared/wrapper'; import Wrapper from './shared/Wrapper';
import deindent from '../../utils/deindent'; import deindent from '../../utils/deindent';
import MustacheTag from '../../nodes/MustacheTag';
import RawMustacheTag from '../../nodes/RawMustacheTag';
export default class RawMustacheTagWrapper extends Tag { export default class RawMustacheTagWrapper extends Tag {
var = 'raw'; var = 'raw';
@ -12,7 +13,7 @@ export default class RawMustacheTagWrapper extends Tag {
renderer: Renderer, renderer: Renderer,
block: Block, block: Block,
parent: Wrapper, parent: Wrapper,
node: Node node: MustacheTag | RawMustacheTag
) { ) {
super(renderer, block, parent, node); super(renderer, block, parent, node);
this.cannot_use_innerhtml(); this.cannot_use_innerhtml();
@ -100,4 +101,4 @@ export default class RawMustacheTagWrapper extends Tag {
add_anchor_after(); add_anchor_after();
} }
} }
} }

@ -9,7 +9,6 @@ import add_to_set from '../../utils/add_to_set';
import get_slot_data from '../../utils/get_slot_data'; import get_slot_data from '../../utils/get_slot_data';
import { stringify_props } from '../../utils/stringify_props'; import { stringify_props } from '../../utils/stringify_props';
import Expression from '../../nodes/shared/Expression'; import Expression from '../../nodes/shared/Expression';
import Attribute from '../../nodes/Attribute';
export default class SlotWrapper extends Wrapper { export default class SlotWrapper extends Wrapper {
node: Slot; node: Slot;

@ -4,6 +4,7 @@ import Block from '../Block';
import Title from '../../nodes/Title'; import Title from '../../nodes/Title';
import { stringify } from '../../utils/stringify'; import { stringify } from '../../utils/stringify';
import add_to_set from '../../utils/add_to_set'; import add_to_set from '../../utils/add_to_set';
import Text from '../../nodes/Text';
export default class TitleWrapper extends Wrapper { export default class TitleWrapper extends Wrapper {
node: Title; node: Title;
@ -31,6 +32,7 @@ export default class TitleWrapper extends Wrapper {
// DRY it out if that's possible without introducing crazy indirection // DRY it out if that's possible without introducing crazy indirection
if (this.node.children.length === 1) { if (this.node.children.length === 1) {
// single {tag} — may be a non-string // single {tag} — may be a non-string
// @ts-ignore todo: check this
const { expression } = this.node.children[0]; const { expression } = this.node.children[0];
value = expression.render(block); value = expression.render(block);
add_to_set(all_dependencies, expression.dependencies); add_to_set(all_dependencies, expression.dependencies);
@ -39,16 +41,18 @@ export default class TitleWrapper extends Wrapper {
value = value =
(this.node.children[0].type === 'Text' ? '' : `"" + `) + (this.node.children[0].type === 'Text' ? '' : `"" + `) +
this.node.children this.node.children
.map((chunk: Node) => { .map((chunk) => {
if (chunk.type === 'Text') { if (chunk.type === 'Text') {
return stringify(chunk.data); return stringify(chunk.data);
} else { } else {
// @ts-ignore todo: check this
const snippet = chunk.expression.render(block); const snippet = chunk.expression.render(block);
// @ts-ignore todo: check this
chunk.expression.dependencies.forEach(d => { chunk.expression.dependencies.forEach(d => {
all_dependencies.add(d); all_dependencies.add(d);
}); });
// @ts-ignore todo: check this
return chunk.expression.get_precedence() <= 13 ? `(${snippet})` : snippet; return chunk.expression.get_precedence() <= 13 ? `(${snippet})` : snippet;
} }
}) })
@ -88,8 +92,8 @@ export default class TitleWrapper extends Wrapper {
); );
} }
} else { } else {
const value = stringify(this.node.children[0].data); const value = stringify((this.node.children[0] as Text).data);
block.builders.hydrate.add_line(`document.title = ${value};`); block.builders.hydrate.add_line(`document.title = ${value};`);
} }
} }
} }

@ -6,6 +6,7 @@ import deindent from '../../utils/deindent';
import add_event_handlers from './shared/add_event_handlers'; import add_event_handlers from './shared/add_event_handlers';
import Window from '../../nodes/Window'; import Window from '../../nodes/Window';
import add_actions from './shared/add_actions'; import add_actions from './shared/add_actions';
import { INode } from '../../nodes/interfaces';
const associated_events = { const associated_events = {
innerWidth: 'resize', innerWidth: 'resize',
@ -33,7 +34,7 @@ const readonly = new Set([
export default class WindowWrapper extends Wrapper { export default class WindowWrapper extends Wrapper {
node: Window; node: Window;
constructor(renderer: Renderer, block: Block, parent: Wrapper, node: Node) { constructor(renderer: Renderer, block: Block, parent: Wrapper, node: INode) {
super(renderer, block, parent, node); super(renderer, block, parent, node);
} }

@ -1,11 +1,11 @@
import Renderer from '../../Renderer'; import Renderer from '../../Renderer';
import Node from '../../../nodes/shared/Node';
import Block from '../../Block'; import Block from '../../Block';
import { INode } from '../../../nodes/interfaces';
export default class Wrapper { export default class Wrapper {
renderer: Renderer; renderer: Renderer;
parent: Wrapper; parent: Wrapper;
node: Node; node: INode;
prev: Wrapper | null; prev: Wrapper | null;
next: Wrapper | null; next: Wrapper | null;
@ -17,7 +17,7 @@ export default class Wrapper {
renderer: Renderer, renderer: Renderer,
block: Block, block: Block,
parent: Wrapper, parent: Wrapper,
node: Node node: INode
) { ) {
this.node = node; this.node = node;
@ -75,4 +75,8 @@ export default class Wrapper {
this.node.type === 'MustacheTag' this.node.type === 'MustacheTag'
); );
} }
}
render(block: Block, parent_node: string, parent_nodes: string){
throw Error('Wrapper class is not renderable');
}
}

@ -1,8 +1,8 @@
import Component from '../../../Component'; import Component from '../../../Component';
import { Node } from '../../../../interfaces'; import { INode } from '../../../nodes/interfaces';
export default function create_debugging_comment( export default function create_debugging_comment(
node: Node, node: INode,
component: Component component: Component
) { ) {
const { locate, source } = component; const { locate, source } = component;
@ -19,6 +19,7 @@ export default function create_debugging_comment(
d = node.children.length ? node.children[0].start : node.start; d = node.children.length ? node.children[0].start : node.start;
while (source[d - 1] !== '>') d -= 1; while (source[d - 1] !== '>') d -= 1;
} else { } else {
// @ts-ignore
d = node.expression ? node.expression.node.end : c; d = node.expression ? node.expression.node.end : c;
while (source[d] !== '}') d += 1; while (source[d] !== '}') d += 1;
while (source[d] === '}') d += 1; while (source[d] === '}') d += 1;

@ -12,6 +12,7 @@ import Tag from './handlers/Tag';
import Text from './handlers/Text'; import Text from './handlers/Text';
import Title from './handlers/Title'; import Title from './handlers/Title';
import { AppendTarget, CompileOptions } from '../../interfaces'; import { AppendTarget, CompileOptions } from '../../interfaces';
import { INode } from '../nodes/interfaces';
type Handler = (node: any, renderer: Renderer, options: CompileOptions) => void; type Handler = (node: any, renderer: Renderer, options: CompileOptions) => void;
@ -36,6 +37,10 @@ const handlers: Record<string, Handler> = {
Window: noop Window: noop
}; };
export interface RenderOptions extends CompileOptions{
locate: (c: number) => { line: number; column: number; };
};
export default class Renderer { export default class Renderer {
has_bindings = false; has_bindings = false;
code = ''; code = '';
@ -51,7 +56,7 @@ export default class Renderer {
} }
} }
render(nodes, options) { render(nodes: INode[], options: RenderOptions) {
nodes.forEach(node => { nodes.forEach(node => {
const handler = handlers[node.type]; const handler = handlers[node.type];

@ -1,8 +1,8 @@
import Renderer from '../Renderer'; import Renderer, { RenderOptions } from '../Renderer';
import { CompileOptions } from '../../../interfaces';
import { snip } from '../../utils/snip'; import { snip } from '../../utils/snip';
import AwaitBlock from '../../nodes/AwaitBlock';
export default function(node, renderer: Renderer, options: CompileOptions) { export default function(node: AwaitBlock, renderer: Renderer, options: RenderOptions) {
renderer.append('${(function(__value) { if(@is_promise(__value)) return `'); renderer.append('${(function(__value) { if(@is_promise(__value)) return `');
renderer.render(node.pending.children, options); renderer.render(node.pending.children, options);
@ -13,4 +13,4 @@ export default function(node, renderer: Renderer, options: CompileOptions) {
const snippet = snip(node.expression); const snippet = snip(node.expression);
renderer.append(`\`;}(__value);}(${snippet})) }`); renderer.append(`\`;}(__value);}(${snippet})) }`);
} }

@ -1,8 +1,8 @@
import Renderer from '../Renderer'; import Renderer, { RenderOptions } from '../Renderer';
import { CompileOptions } from '../../../interfaces'; import Comment from '../../nodes/Comment';
export default function(node, renderer: Renderer, options: CompileOptions) { export default function(node: Comment, renderer: Renderer, options: RenderOptions) {
if (options.preserveComments) { if (options.preserveComments) {
renderer.append(`<!--${node.data}-->`); renderer.append(`<!--${node.data}-->`);
} }
} }

@ -1,9 +1,10 @@
import { stringify } from '../../utils/stringify'; import { stringify } from '../../utils/stringify';
import DebugTag from '../../nodes/DebugTag';
export default function(node, renderer, options) { import Renderer, { RenderOptions } from '../Renderer';
export default function(node: DebugTag, renderer: Renderer, options: RenderOptions) {
if (!options.dev) return; if (!options.dev) return;
const filename = options.file || null; const filename = options.filename || null;
const { line, column } = options.locate(node.start + 1); const { line, column } = options.locate(node.start + 1);
const obj = node.expressions.length === 0 const obj = node.expressions.length === 0
@ -15,4 +16,4 @@ export default function(node, renderer, options) {
const str = '${@debug(' + `${filename && stringify(filename)}, ${line}, ${column}, ${obj})}`; const str = '${@debug(' + `${filename && stringify(filename)}, ${line}, ${column}, ${obj})}`;
renderer.append(str); renderer.append(str);
} }

@ -1,6 +1,8 @@
import { snip } from '../../utils/snip'; import { snip } from '../../utils/snip';
import Renderer, { RenderOptions } from '../Renderer';
import EachBlock from '../../nodes/EachBlock';
export default function(node, renderer, options) { export default function(node: EachBlock, renderer: Renderer, options: RenderOptions) {
const snippet = snip(node.expression); const snippet = snip(node.expression);
const { start, end } = node.context_node; const { start, end } = node.context_node;
@ -24,4 +26,4 @@ export default function(node, renderer, options) {
} }
renderer.append('}'); renderer.append('}');
} }

@ -5,6 +5,9 @@ import Node from '../../nodes/shared/Node';
import { snip } from '../../utils/snip'; import { snip } from '../../utils/snip';
import { stringify_attribute } from '../../utils/stringify_attribute'; import { stringify_attribute } from '../../utils/stringify_attribute';
import { get_slot_scope } from './shared/get_slot_scope'; import { get_slot_scope } from './shared/get_slot_scope';
import Renderer, { RenderOptions } from '../Renderer';
import Element from '../../nodes/Element';
import Text from '../../nodes/Text';
// source: https://gist.github.com/ArjanSchouten/0b8574a6ad7f5065a5e7 // source: https://gist.github.com/ArjanSchouten/0b8574a6ad7f5065a5e7
const boolean_attributes = new Set([ const boolean_attributes = new Set([
@ -47,15 +50,17 @@ const boolean_attributes = new Set([
'translate' 'translate'
]); ]);
export default function(node, renderer, options) { export default function(node: Element, renderer: Renderer, options: RenderOptions & {
slot_scopes: Map<any, any>;
}) {
let opening_tag = `<${node.name}`; let opening_tag = `<${node.name}`;
let textarea_contents; // awkward special case let textarea_contents; // awkward special case
const slot = node.get_static_attribute_value('slot'); const slot = node.get_static_attribute_value('slot');
const component = node.find_nearest(/InlineComponent/); const component = node.find_nearest(/InlineComponent/);
if (slot && component) { if (slot && component) {
const slot = node.attributes.find((attribute: Node) => attribute.name === 'slot'); const slot = node.attributes.find((attribute) => attribute.name === 'slot');
const slot_name = slot.chunks[0].data; const slot_name = (slot.chunks[0] as Text).data;
const target = renderer.targets[renderer.targets.length - 1]; const target = renderer.targets[renderer.targets.length - 1];
target.slot_stack.push(slot_name); target.slot_stack.push(slot_name);
target.slots[slot_name] = ''; target.slots[slot_name] = '';
@ -135,6 +140,10 @@ export default function(node, renderer, options) {
node.bindings.forEach(binding => { node.bindings.forEach(binding => {
const { name, expression } = binding; const { name, expression } = binding;
if (binding.is_readonly) {
return;
}
if (name === 'group') { if (name === 'group') {
// TODO server-render group bindings // TODO server-render group bindings
} else { } else {
@ -160,4 +169,4 @@ export default function(node, renderer, options) {
if (!is_void(node.name)) { if (!is_void(node.name)) {
renderer.append(`</${node.name}>`); renderer.append(`</${node.name}>`);
} }
} }

@ -1,7 +1,10 @@
export default function(node, renderer, options) { import Renderer, { RenderOptions } from '../Renderer';
import Head from '../../nodes/Head';
export default function(node: Head, renderer: Renderer, options: RenderOptions) {
renderer.append('${($$result.head += `'); renderer.append('${($$result.head += `');
renderer.render(node.children, options); renderer.render(node.children, options);
renderer.append('`, "")}'); renderer.append('`, "")}');
} }

@ -1,5 +1,7 @@
import { snip } from '../../utils/snip'; import { snip } from '../../utils/snip';
import Renderer, { RenderOptions } from '../Renderer';
import RawMustacheTag from '../../nodes/RawMustacheTag';
export default function(node, renderer, options) { export default function(node: RawMustacheTag, renderer: Renderer, options: RenderOptions) {
renderer.append('${' + snip(node.expression) + '}'); renderer.append('${' + snip(node.expression) + '}');
} }

@ -1,6 +1,7 @@
import { snip } from '../../utils/snip'; import { snip } from '../../utils/snip';
import IfBlock from '../../nodes/IfBlock';
export default function(node, renderer, options) { import Renderer, { RenderOptions } from '../Renderer';
export default function(node: IfBlock, renderer: Renderer, options: RenderOptions) {
const snippet = snip(node.expression); const snippet = snip(node.expression);
renderer.append('${ ' + snippet + ' ? `'); renderer.append('${ ' + snippet + ' ? `');
@ -14,4 +15,4 @@ export default function(node, renderer, options) {
} }
renderer.append('` }'); renderer.append('` }');
} }

@ -1,14 +1,17 @@
import { escape, escape_template, stringify } from '../../utils/stringify'; import { escape, escape_template, stringify } from '../../utils/stringify';
import { quote_name_if_necessary } from '../../../utils/names'; import { quote_name_if_necessary } from '../../../utils/names';
import { snip } from '../../utils/snip'; import { snip } from '../../utils/snip';
import Renderer from '../Renderer'; import Renderer, { RenderOptions } from '../Renderer';
import { stringify_props } from '../../utils/stringify_props'; import { stringify_props } from '../../utils/stringify_props';
import { get_slot_scope } from './shared/get_slot_scope'; import { get_slot_scope } from './shared/get_slot_scope';
import { AppendTarget } from '../../../interfaces'; import { AppendTarget } from '../../../interfaces';
import InlineComponent from '../../nodes/InlineComponent';
import { INode } from '../../nodes/interfaces';
import Text from '../../nodes/Text';
function stringify_attribute(chunk: Node) { function stringify_attribute(chunk: INode) {
if (chunk.type === 'Text') { if (chunk.type === 'Text') {
return escape_template(escape(chunk.data)); return escape_template(escape((chunk as Text).data));
} }
return '${@escape(' + snip(chunk) + ')}'; return '${@escape(' + snip(chunk) + ')}';
@ -30,7 +33,7 @@ function get_attribute_value(attribute) {
return '`' + attribute.chunks.map(stringify_attribute).join('') + '`'; return '`' + attribute.chunks.map(stringify_attribute).join('') + '`';
} }
export default function(node, renderer: Renderer, options) { export default function(node: InlineComponent, renderer: Renderer, options: RenderOptions) {
const binding_props = []; const binding_props = [];
const binding_fns = []; const binding_fns = [];

@ -1,7 +1,9 @@
import { quote_prop_if_necessary } from '../../../utils/names'; import { quote_prop_if_necessary } from '../../../utils/names';
import get_slot_data from '../../utils/get_slot_data'; import get_slot_data from '../../utils/get_slot_data';
import Renderer, { RenderOptions } from '../Renderer';
import Slot from '../../nodes/Slot';
export default function(node, renderer, options) { export default function(node: Slot, renderer: Renderer, options: RenderOptions) {
const prop = quote_prop_if_necessary(node.slot_name); const prop = quote_prop_if_necessary(node.slot_name);
const slot_data = get_slot_data(node.values, true); const slot_data = get_slot_data(node.values, true);
@ -13,4 +15,4 @@ export default function(node, renderer, options) {
renderer.render(node.children, options); renderer.render(node.children, options);
renderer.append(`\`}`); renderer.append(`\`}`);
} }

@ -1,6 +1,6 @@
import { snip } from '../../utils/snip'; import { snip } from '../../utils/snip';
import Renderer, { RenderOptions } from '../Renderer';
export default function(node, renderer, options) { export default function(node, renderer: Renderer, options: RenderOptions) {
const snippet = snip(node.expression); const snippet = snip(node.expression);
renderer.append( renderer.append(
@ -10,4 +10,4 @@ export default function(node, renderer, options) {
? '${' + snippet + '}' ? '${' + snippet + '}'
: '${@escape(' + snippet + ')}' : '${@escape(' + snippet + ')}'
); );
} }

@ -1,14 +1,17 @@
import { escape_html, escape_template, escape } from '../../utils/stringify'; import { escape_html, escape_template, escape } from '../../utils/stringify';
import Renderer, { RenderOptions } from '../Renderer';
import Text from '../../nodes/Text';
import Element from '../../nodes/Element';
export default function(node, renderer, options) { export default function(node: Text, renderer: Renderer, options: RenderOptions) {
let text = node.data; let text = node.data;
if ( if (
!node.parent || !node.parent ||
node.parent.type !== 'Element' || node.parent.type !== 'Element' ||
(node.parent.name !== 'script' && node.parent.name !== 'style') ((node.parent as Element).name !== 'script' && (node.parent as Element).name !== 'style')
) { ) {
// unless this Text node is inside a <script> or <style> element, escape &,<,> // unless this Text node is inside a <script> or <style> element, escape &,<,>
text = escape_html(text); text = escape_html(text);
} }
renderer.append(escape(escape_template(text))); renderer.append(escape(escape_template(text)));
} }

@ -1,7 +1,10 @@
export default function(node, renderer, options) { import Renderer, { RenderOptions } from '../Renderer';
import Title from '../../nodes/Title';
export default function(node: Title, renderer: Renderer, options: RenderOptions) {
renderer.append(`<title>`); renderer.append(`<title>`);
renderer.render(node.children, options); renderer.render(node.children, options);
renderer.append(`</title>`); renderer.append(`</title>`);
} }

@ -4,6 +4,8 @@ import { CompileOptions } from '../../interfaces';
import { stringify } from '../utils/stringify'; import { stringify } from '../utils/stringify';
import Renderer from './Renderer'; import Renderer from './Renderer';
import { extract_names } from '../utils/scope'; import { extract_names } from '../utils/scope';
import { INode } from '../nodes/interfaces';
import Text from '../nodes/Text';
export default function ssr( export default function ssr(
component: Component, component: Component,
@ -151,10 +153,10 @@ export default function ssr(
`).trim(); `).trim();
} }
function trim(nodes) { function trim(nodes: INode[]) {
let start = 0; let start = 0;
for (; start < nodes.length; start += 1) { for (; start < nodes.length; start += 1) {
const node = nodes[start]; const node = nodes[start] as Text;
if (node.type !== 'Text') break; if (node.type !== 'Text') break;
node.data = node.data.replace(/^\s+/, ''); node.data = node.data.replace(/^\s+/, '');
@ -163,7 +165,7 @@ function trim(nodes) {
let end = nodes.length; let end = nodes.length;
for (; end > start; end -= 1) { for (; end > start; end -= 1) {
const node = nodes[end - 1]; const node = nodes[end - 1] as Text;
if (node.type !== 'Text') break; if (node.type !== 'Text') break;
node.data = node.data.replace(/\s+$/, ''); node.data = node.data.replace(/\s+$/, '');

@ -1,5 +1,6 @@
export default function add_to_set(a: Set<any>, b: Set<any>) { export default function add_to_set<T>(a: Set<T>, b: Set<T> | Array<T>) {
// @ts-ignore
b.forEach(item => { b.forEach(item => {
a.add(item); a.add(item);
}); });
} }

@ -1,6 +1,7 @@
import { walk } from 'estree-walker'; import { walk } from 'estree-walker';
import is_reference from 'is-reference'; import is_reference from 'is-reference';
import { Node } from '../../interfaces'; import { Node } from '../../interfaces';
import { Node as ESTreeNode } from 'estree';
export function create_scopes(expression: Node) { export function create_scopes(expression: Node) {
const map = new WeakMap(); const map = new WeakMap();
@ -9,7 +10,7 @@ export function create_scopes(expression: Node) {
let scope = new Scope(null, false); let scope = new Scope(null, false);
walk(expression, { walk(expression, {
enter(node: Node, parent: Node) { enter(node, parent) {
if (node.type === 'ImportDeclaration') { if (node.type === 'ImportDeclaration') {
node.specifiers.forEach(specifier => { node.specifiers.forEach(specifier => {
scope.declarations.set(specifier.local.name, specifier); scope.declarations.set(specifier.local.name, specifier);
@ -25,7 +26,7 @@ export function create_scopes(expression: Node) {
if (node.id) scope.declarations.set(node.id.name, node); if (node.id) scope.declarations.set(node.id.name, node);
} }
node.params.forEach((param: Node) => { node.params.forEach((param) => {
extract_names(param).forEach(name => { extract_names(param).forEach(name => {
scope.declarations.set(name, node); scope.declarations.set(name, node);
}); });
@ -38,7 +39,7 @@ export function create_scopes(expression: Node) {
map.set(node, scope); map.set(node, scope);
} else if (/(Class|Variable)Declaration/.test(node.type)) { } else if (/(Class|Variable)Declaration/.test(node.type)) {
scope.add_declaration(node); scope.add_declaration(node);
} else if (node.type === 'Identifier' && is_reference(node, parent)) { } else if (node.type === 'Identifier' && is_reference(node as ESTreeNode, parent as ESTreeNode)) {
if (!scope.has(node.name) && !globals.has(node.name)) { if (!scope.has(node.name) && !globals.has(node.name)) {
globals.set(node.name, node); globals.set(node.name, node);
} }

@ -1,11 +1,10 @@
import Attribute from '../nodes/Attribute'; import Attribute from '../nodes/Attribute';
import Node from '../nodes/shared/Node';
import { escape_template, escape } from './stringify'; import { escape_template, escape } from './stringify';
import { snip } from './snip'; import { snip } from './snip';
export function stringify_attribute(attribute: Attribute, is_ssr: boolean) { export function stringify_attribute(attribute: Attribute, is_ssr: boolean) {
return attribute.chunks return attribute.chunks
.map((chunk: Node) => { .map((chunk) => {
if (chunk.type === 'Text') { if (chunk.type === 'Text') {
return escape_template(escape(chunk.data).replace(/"/g, '&quot;')); return escape_template(escape(chunk.data).replace(/"/g, '&quot;'));
} }
@ -15,4 +14,4 @@ export function stringify_attribute(attribute: Attribute, is_ssr: boolean) {
: '${' + snip(chunk) + '}'; : '${' + snip(chunk) + '}';
}) })
.join(''); .join('');
} }

@ -0,0 +1,6 @@
export { default as compile } from './compile/index';
export { default as parse } from './parse/index';
export { default as preprocess } from './preprocess/index';
export { walk } from 'estree-walker';
export const VERSION = '__VERSION__';

@ -3,7 +3,7 @@ Adapted from https://github.com/mattdesl
Distributed under MIT License https://github.com/mattdesl/eases/blob/master/LICENSE.md Distributed under MIT License https://github.com/mattdesl/eases/blob/master/LICENSE.md
*/ */
export { identity as linear } from './internal'; export { identity as linear } from 'svelte/internal';
export function backInOut(t) { export function backInOut(t) {
const s = 1.70158 * 1.525; const s = 1.70158 * 1.525;

@ -1,6 +1,10 @@
export { default as compile } from './compile/index'; export {
export { default as parse } from './parse/index'; onMount,
export { default as preprocess } from './preprocess/index'; onDestroy,
export { walk } from 'estree-walker'; beforeUpdate,
afterUpdate,
export const VERSION = '__VERSION__'; setContext,
getContext,
tick,
createEventDispatcher
} from 'svelte/internal';

@ -1,10 +1,51 @@
export interface Node { interface BaseNode {
start: number; start: number;
end: number; end: number;
type: string; type: string;
children?: Node[];
[prop_name: string]: any; [prop_name: string]: any;
} }
export interface Text extends BaseNode {
type: 'Text',
data: string;
}
export interface MustacheTag extends BaseNode {
type: 'MustacheTag',
expresion: Node;
}
export type DirectiveType = 'Action'
| 'Animation'
| 'Binding'
| 'Class'
| 'EventHandler'
| 'Let'
| 'Ref'
| 'Transition';
interface BaseDirective extends BaseNode {
type: DirectiveType;
expression: null|Node;
name: string;
modifiers: string[]
}
export interface Transition extends BaseDirective{
type: 'Transition',
intro: boolean;
outro: boolean;
}
export type Directive = BaseDirective | Transition;
export type Node = Text
| MustacheTag
| BaseNode
| Directive
| Transition;
export interface Parser { export interface Parser {
readonly template: string; readonly template: string;
readonly filename?: string; readonly filename?: string;

@ -1,7 +1,23 @@
import { add_render_callback, flush, schedule_update, dirty_components } from './scheduler.js'; import { add_render_callback, flush, schedule_update, dirty_components } from './scheduler';
import { current_component, set_current_component } from './lifecycle.js'; import { current_component, set_current_component } from './lifecycle';
import { blank_object, is_function, run, run_all, noop } from './utils.js'; import { blank_object, is_function, run, run_all, noop } from './utils';
import { children } from './dom.js'; import { children } from './dom';
interface T$$ {
dirty: null;
ctx: null|any;
bound: any;
update: () => void;
callbacks: any;
after_render: any[];
props: any;
fragment: null|any;
not_equal: any;
before_render: any[];
context: Map<any, any>;
on_mount: any[];
on_destroy: any[]
}
export function bind(component, name, callback) { export function bind(component, name, callback) {
if (component.$$.props.indexOf(name) === -1) return; if (component.$$.props.indexOf(name) === -1) return;
@ -59,7 +75,7 @@ export function init(component, options, instance, create_fragment, not_equal, p
const props = options.props || {}; const props = options.props || {};
const $$ = component.$$ = { const $$: T$$ = component.$$ = {
fragment: null, fragment: null,
ctx: null, ctx: null,
@ -99,9 +115,9 @@ export function init(component, options, instance, create_fragment, not_equal, p
if (options.target) { if (options.target) {
if (options.hydrate) { if (options.hydrate) {
$$.fragment.l(children(options.target)); $$.fragment!.l(children(options.target));
} else { } else {
$$.fragment.c(); $$.fragment!.c();
} }
if (options.intro && component.$$.fragment.i) component.$$.fragment.i(); if (options.intro && component.$$.fragment.i) component.$$.fragment.i();
@ -115,13 +131,16 @@ export function init(component, options, instance, create_fragment, not_equal, p
export let SvelteElement; export let SvelteElement;
if (typeof HTMLElement !== 'undefined') { if (typeof HTMLElement !== 'undefined') {
SvelteElement = class extends HTMLElement { SvelteElement = class extends HTMLElement {
$$: T$$;
constructor() { constructor() {
super(); super();
this.attachShadow({ mode: 'open' }); this.attachShadow({ mode: 'open' });
} }
connectedCallback() { connectedCallback() {
// @ts-ignore todo: improve typings
for (const key in this.$$.slotted) { for (const key in this.$$.slotted) {
// @ts-ignore todo: improve typings
this.appendChild(this.$$.slotted[key]); this.appendChild(this.$$.slotted[key]);
} }
} }
@ -153,6 +172,8 @@ if (typeof HTMLElement !== 'undefined') {
} }
export class SvelteComponent { export class SvelteComponent {
$$: T$$;
$destroy() { $destroy() {
destroy(this, true); destroy(this, true);
this.$destroy = noop; this.$destroy = noop;

@ -1,6 +1,6 @@
import { identity as linear, noop, now } from './utils.js'; import { identity as linear, noop, now } from './utils';
import { loop } from './loop.js'; import { loop } from './loop';
import { create_rule, delete_rule } from './style_manager.js'; import { create_rule, delete_rule } from './style_manager';
export function create_animation(node, from, fn, params) { export function create_animation(node, from, fn, params) {
if (!from) return noop; if (!from) return noop;
@ -22,15 +22,14 @@ export function create_animation(node, from, fn, params) {
let started = false; let started = false;
let name; let name;
const css_text = node.style.cssText;
function start() { function start() {
if (css) { if (css) {
if (delay) node.style.cssText = css_text; // TODO create delayed animation instead? name = create_rule(node, 0, 1, duration, delay, easing, css);
name = create_rule(node, 0, 1, duration, 0, easing, css);
} }
started = true; if (!delay) {
started = true;
}
} }
function stop() { function stop() {
@ -40,7 +39,7 @@ export function create_animation(node, from, fn, params) {
loop(now => { loop(now => {
if (!started && now >= start_time) { if (!started && now >= start_time) {
start(); started = true;
} }
if (started && now >= end) { if (started && now >= end) {
@ -61,11 +60,7 @@ export function create_animation(node, from, fn, params) {
return true; return true;
}); });
if (delay) { start();
if (css) node.style.cssText += css(0, 1);
} else {
start();
}
tick(0, 1); tick(0, 1);
@ -90,4 +85,4 @@ export function fix_position(node) {
node.style.transform = `${transform} translate(${a.left - b.left}px, ${a.top - b.top}px)`; node.style.transform = `${transform} translate(${a.left - b.left}px, ${a.top - b.top}px)`;
} }
} }
} }

@ -1,11 +1,11 @@
import { assign, is_promise } from './utils.js'; import { assign, is_promise } from './utils';
import { check_outros, group_outros, on_outro } from './transitions.js'; import { check_outros, group_outros, on_outro } from './transitions';
import { flush } from '../internal/scheduler.js'; import { flush } from '../internal/scheduler';
export function handle_promise(promise, info) { export function handle_promise(promise, info) {
const token = info.token = {}; const token = info.token = {};
function update(type, index, key, value) { function update(type, index, key?, value?) {
if (info.token !== token) return; if (info.token !== token) return;
info.resolved = key && { [key]: value }; info.resolved = key && { [key]: value };
@ -61,4 +61,4 @@ export function handle_promise(promise, info) {
info.resolved = { [info.value]: promise }; info.resolved = { [info.value]: promise };
} }
} }

@ -1,28 +1,28 @@
export function append(target, node) { export function append(target:Node, node:Node) {
target.appendChild(node); target.appendChild(node);
} }
export function insert(target, node, anchor) { export function insert(target: Node, node: Node, anchor?:Node) {
target.insertBefore(node, anchor || null); target.insertBefore(node, anchor || null);
} }
export function detach(node) { export function detach(node: Node) {
node.parentNode.removeChild(node); node.parentNode.removeChild(node);
} }
export function detach_between(before, after) { export function detach_between(before: Node, after: Node) {
while (before.nextSibling && before.nextSibling !== after) { while (before.nextSibling && before.nextSibling !== after) {
before.parentNode.removeChild(before.nextSibling); before.parentNode.removeChild(before.nextSibling);
} }
} }
export function detach_before(after) { export function detach_before(after:Node) {
while (after.previousSibling) { while (after.previousSibling) {
after.parentNode.removeChild(after.previousSibling); after.parentNode.removeChild(after.previousSibling);
} }
} }
export function detach_after(before) { export function detach_after(before:Node) {
while (before.nextSibling) { while (before.nextSibling) {
before.parentNode.removeChild(before.nextSibling); before.parentNode.removeChild(before.nextSibling);
} }
@ -34,25 +34,30 @@ export function destroy_each(iterations, detaching) {
} }
} }
export function element(name) { export function element<K extends keyof HTMLElementTagNameMap>(name: K) {
return document.createElement(name); return document.createElement<K>(name);
} }
export function object_without_properties(obj, exclude) { export function object_without_properties<T,K extends keyof T>(obj:T, exclude: K[]) {
const target = {}; const target = {} as Pick<T, Exclude<keyof T, K>>;
for (const k in obj) { for (const k in obj) {
if (Object.prototype.hasOwnProperty.call(obj, k) && exclude.indexOf(k) === -1) { if (
Object.prototype.hasOwnProperty.call(obj, k)
// @ts-ignore
&& exclude.indexOf(k) === -1
) {
// @ts-ignore
target[k] = obj[k]; target[k] = obj[k];
} }
} }
return target; return target;
} }
export function svg_element(name) { export function svg_element(name:string):SVGElement {
return document.createElementNS('http://www.w3.org/2000/svg', name); return document.createElementNS('http://www.w3.org/2000/svg', name);
} }
export function text(data) { export function text(data:string) {
return document.createTextNode(data); return document.createTextNode(data);
} }
@ -64,7 +69,7 @@ export function empty() {
return text(''); return text('');
} }
export function listen(node, event, handler, options) { export function listen(node: Node, event: string, handler: EventListenerOrEventListenerObject, options?: boolean | AddEventListenerOptions | EventListenerOptions) {
node.addEventListener(event, handler, options); node.addEventListener(event, handler, options);
return () => node.removeEventListener(event, handler, options); return () => node.removeEventListener(event, handler, options);
} }
@ -72,6 +77,7 @@ export function listen(node, event, handler, options) {
export function prevent_default(fn) { export function prevent_default(fn) {
return function(event) { return function(event) {
event.preventDefault(); event.preventDefault();
// @ts-ignore
return fn.call(this, event); return fn.call(this, event);
}; };
} }
@ -79,16 +85,17 @@ export function prevent_default(fn) {
export function stop_propagation(fn) { export function stop_propagation(fn) {
return function(event) { return function(event) {
event.stopPropagation(); event.stopPropagation();
// @ts-ignore
return fn.call(this, event); return fn.call(this, event);
}; };
} }
export function attr(node, attribute, value) { export function attr(node: Element, attribute: string, value?: string) {
if (value == null) node.removeAttribute(attribute); if (value == null) node.removeAttribute(attribute);
else node.setAttribute(attribute, value); else node.setAttribute(attribute, value);
} }
export function set_attributes(node, attributes) { export function set_attributes(node: HTMLElement, attributes: { [x: string]: string; }) {
for (const key in attributes) { for (const key in attributes) {
if (key === 'style') { if (key === 'style') {
node.style.cssText = attributes[key]; node.style.cssText = attributes[key];
@ -243,8 +250,8 @@ export function toggle_class(element, name, toggle) {
element.classList[toggle ? 'add' : 'remove'](name); element.classList[toggle ? 'add' : 'remove'](name);
} }
export function custom_event(type, detail) { export function custom_event<T=any>(type: string, detail?: T) {
const e = document.createEvent('CustomEvent'); const e: CustomEvent<T> = document.createEvent('CustomEvent');
e.initCustomEvent(type, false, false, detail); e.initCustomEvent(type, false, false, detail);
return e; return e;
} }

@ -1,12 +0,0 @@
export * from './animations.js';
export * from './await-block.js';
export * from './dom.js';
export * from './keyed-each.js';
export * from './lifecycle.js';
export * from './loop.js';
export * from './scheduler.js';
export * from './spread.js';
export * from './ssr.js';
export * from './transitions.js';
export * from './utils.js';
export * from './Component.js';

@ -0,0 +1,12 @@
export * from './animations';
export * from './await-block';
export * from './dom';
export * from './keyed-each';
export * from './lifecycle';
export * from './loop';
export * from './scheduler';
export * from './spread';
export * from './ssr';
export * from './transitions';
export * from './utils';
export * from './Component';

@ -1,4 +1,4 @@
import { on_outro } from './transitions.js'; import { on_outro } from './transitions';
export function destroy_block(block, lookup) { export function destroy_block(block, lookup) {
block.d(1); block.d(1);
@ -110,4 +110,4 @@ export function measure(blocks) {
let i = blocks.length; let i = blocks.length;
while (i--) rects[blocks[i].key] = blocks[i].node.getBoundingClientRect(); while (i--) rects[blocks[i].key] = blocks[i].node.getBoundingClientRect();
return rects; return rects;
} }

@ -1,4 +1,6 @@
import { now } from './utils.js'; import { now, raf } from './utils';
export interface Task { abort(): void; promise: Promise<undefined> }
const tasks = new Set(); const tasks = new Set();
let running = false; let running = false;
@ -12,7 +14,7 @@ function run_tasks() {
}); });
running = tasks.size > 0; running = tasks.size > 0;
if (running) requestAnimationFrame(run_tasks); if (running) raf(run_tasks);
} }
export function clear_loops() { export function clear_loops() {
@ -21,20 +23,20 @@ export function clear_loops() {
running = false; running = false;
} }
export function loop(fn) { export function loop(fn: (number)=>void): Task {
let task; let task;
if (!running) { if (!running) {
running = true; running = true;
requestAnimationFrame(run_tasks); raf(run_tasks);
} }
return { return {
promise: new Promise(fulfil => { promise: new Promise<undefined>(fulfil => {
tasks.add(task = [fn, fulfil]); tasks.add(task = [fn, fulfil]);
}), }),
abort() { abort() {
tasks.delete(task); tasks.delete(task);
} }
}; };
} }

@ -1,5 +1,5 @@
import { run_all } from './utils.js'; import { run_all } from './utils';
import { set_current_component } from './lifecycle.js'; import { set_current_component } from './lifecycle';
export const dirty_components = []; export const dirty_components = [];
export const intros = { enabled: false }; export const intros = { enabled: false };

@ -1,5 +1,5 @@
import { set_current_component, current_component } from './lifecycle.js'; import { set_current_component, current_component } from './lifecycle';
import { run_all, blank_object } from './utils.js'; import { run_all, blank_object } from './utils';
export const invalid_attribute_name_character = /[\s'">/=\u{FDD0}-\u{FDEF}\u{FFFE}\u{FFFF}\u{1FFFE}\u{1FFFF}\u{2FFFE}\u{2FFFF}\u{3FFFE}\u{3FFFF}\u{4FFFE}\u{4FFFF}\u{5FFFE}\u{5FFFF}\u{6FFFE}\u{6FFFF}\u{7FFFE}\u{7FFFF}\u{8FFFE}\u{8FFFF}\u{9FFFE}\u{9FFFF}\u{AFFFE}\u{AFFFF}\u{BFFFE}\u{BFFFF}\u{CFFFE}\u{CFFFF}\u{DFFFE}\u{DFFFF}\u{EFFFE}\u{EFFFF}\u{FFFFE}\u{FFFFF}\u{10FFFE}\u{10FFFF}]/u; export const invalid_attribute_name_character = /[\s'">/=\u{FDD0}-\u{FDEF}\u{FFFE}\u{FFFF}\u{1FFFE}\u{1FFFF}\u{2FFFE}\u{2FFFF}\u{3FFFE}\u{3FFFF}\u{4FFFE}\u{4FFFF}\u{5FFFE}\u{5FFFF}\u{6FFFE}\u{6FFFF}\u{7FFFE}\u{7FFFF}\u{8FFFE}\u{8FFFF}\u{9FFFE}\u{9FFFF}\u{AFFFE}\u{AFFFF}\u{BFFFE}\u{BFFFF}\u{CFFFE}\u{CFFFF}\u{DFFFE}\u{DFFFF}\u{EFFFE}\u{EFFFF}\u{FFFFE}\u{FFFFF}\u{10FFFE}\u{10FFFF}]/u;
// https://html.spec.whatwg.org/multipage/syntax.html#attributes-2 // https://html.spec.whatwg.org/multipage/syntax.html#attributes-2
@ -117,4 +117,4 @@ export function get_store_value(store) {
let value; let value;
store.subscribe(_ => value = _)(); store.subscribe(_ => value = _)();
return value; return value;
} }

@ -1,4 +1,5 @@
import { element } from './dom.js'; import { element } from './dom';
import { raf } from './utils';
let stylesheet; let stylesheet;
let active = 0; let active = 0;
@ -43,7 +44,7 @@ export function create_rule(node, a, b, duration, delay, ease, fn, uid = 0) {
return name; return name;
} }
export function delete_rule(node, name) { export function delete_rule(node, name?) {
node.style.animation = (node.style.animation || '') node.style.animation = (node.style.animation || '')
.split(', ') .split(', ')
.filter(name .filter(name
@ -56,10 +57,10 @@ export function delete_rule(node, name) {
} }
export function clear_rules() { export function clear_rules() {
requestAnimationFrame(() => { raf(() => {
if (active) return; if (active) return;
let i = stylesheet.cssRules.length; let i = stylesheet.cssRules.length;
while (i--) stylesheet.deleteRule(i); while (i--) stylesheet.deleteRule(i);
current_rules = {}; current_rules = {};
}); });
} }

@ -1,8 +1,8 @@
import { identity as linear, noop, now, run_all } from './utils.js'; import { identity as linear, noop, now, run_all } from './utils';
import { loop } from './loop.js'; import { loop } from './loop';
import { create_rule, delete_rule } from './style_manager.js'; import { create_rule, delete_rule } from './style_manager';
import { custom_event } from './dom.js'; import { custom_event } from './dom';
import { add_render_callback } from './scheduler.js'; import { add_render_callback } from './scheduler';
let promise; let promise;
@ -229,6 +229,7 @@ export function create_bidirectional_transition(node, fn, params, intro) {
}; };
if (!b) { if (!b) {
// @ts-ignore todo: improve typings
program.group = outros; program.group = outros;
outros.remaining += 1; outros.remaining += 1;
} }
@ -309,4 +310,4 @@ export function create_bidirectional_transition(node, fn, params, intro) {
running_program = pending_program = null; running_program = pending_program = null;
} }
}; };
} }

@ -80,11 +80,19 @@ export function exclude_internal_props(props) {
return result; return result;
} }
export let now = typeof window !== 'undefined' const is_client = typeof window !== 'undefined';
export let now: () => number = is_client
? () => window.performance.now() ? () => window.performance.now()
: () => Date.now(); : () => Date.now();
export let raf = is_client ? requestAnimationFrame : noop;
// used internally for testing // used internally for testing
export function set_now(fn) { export function set_now(fn) {
now = fn; now = fn;
} }
export function set_raf(fn) {
raf = fn;
}

@ -1,2 +0,0 @@
export * from './spring.js';
export * from './tweened.js';

@ -0,0 +1,2 @@
export * from './spring';
export * from './tweened';

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

Loading…
Cancel
Save