merge master -> doc-restructure

pull/2986/head
Richard Harris 6 years ago
commit d270542aee

@ -1,7 +1,3 @@
src/shared
shared.js
store.js
test/test.js
test/setup.js
**/_actual.js
**/expected.js
**/expected.js
test/*/samples/*/output.js

@ -1,43 +1,76 @@
{
"root": true,
"rules": {
"indent": [2, "tab", { "SwitchCase": 1 }],
"semi": [2, "always"],
"keyword-spacing": [2, { "before": true, "after": true }],
"space-before-blocks": [2, "always"],
"no-mixed-spaces-and-tabs": [2, "smart-tabs"],
"no-cond-assign": 0,
"no-unused-vars": 2,
"object-shorthand": [2, "always"],
"no-const-assign": 2,
"no-class-assign": 2,
"no-this-before-super": 2,
"no-var": 2,
"no-unreachable": 2,
"valid-typeof": 2,
"quote-props": [2, "as-needed"],
"one-var": [2, "never"],
"prefer-arrow-callback": 2,
"prefer-const": [2, { "destructuring": "all" }],
"arrow-spacing": 2,
"no-inner-declarations": 0
},
"env": {
"es6": true,
"browser": true,
"node": true,
"mocha": true
},
"extends": [
"eslint:recommended",
"plugin:import/errors",
"plugin:import/warnings"
],
"parserOptions": {
"ecmaVersion": 9,
"sourceType": "module"
},
"settings": {
"import/core-modules": ["svelte"]
}
"root": true,
"rules": {
"indent": "off",
"no-unused-vars": "off",
"semi": [2, "always"],
"keyword-spacing": [2, { "before": true, "after": true }],
"space-before-blocks": [2, "always"],
"no-mixed-spaces-and-tabs": [2, "smart-tabs"],
"no-cond-assign": 0,
"object-shorthand": [2, "always"],
"no-const-assign": 2,
"no-class-assign": 2,
"no-this-before-super": 2,
"no-var": 2,
"no-unreachable": 2,
"valid-typeof": 2,
"quote-props": [2, "as-needed"],
"one-var": [2, "never"],
"prefer-arrow-callback": 2,
"prefer-const": [2, { "destructuring": "all" }],
"arrow-spacing": 2,
"no-inner-declarations": 0,
"@typescript-eslint/indent": ["error", "tab", {
"SwitchCase": 1,
"ignoredNodes": ["TemplateLiteral"]
}],
"@typescript-eslint/camelcase": "off",
"@typescript-eslint/no-use-before-define": "off",
"@typescript-eslint/array-type": ["error", "array-simple"],
"@typescript-eslint/explicit-function-return-type": "off",
"@typescript-eslint/no-explicit-any": "off",
"@typescript-eslint/explicit-member-accessibility": "off",
"@typescript-eslint/no-unused-vars": ["error", {
"argsIgnorePattern": "^_"
}],
"@typescript-eslint/no-object-literal-type-assertion": ["error", {
"allowAsParameter": true
}],
"@typescript-eslint/no-unused-vars": "off"
},
"env": {
"es6": true,
"browser": true,
"node": true,
"mocha": true
},
"extends": [
"eslint:recommended",
"plugin:import/errors",
"plugin:import/warnings",
"plugin:import/typescript",
"plugin:@typescript-eslint/recommended"
],
"parserOptions": {
"ecmaVersion": 9,
"sourceType": "module"
},
"settings": {
"import/core-modules": [
"svelte",
"svelte/internal",
"svelte/store",
"svelte/easing",
"estree"
]
},
"overrides": [
{
"files": ["*.js"],
"rules": {
"@typescript-eslint/no-var-requires": "off"
}
}
]
}

@ -1,9 +0,0 @@
[ignore]
<PROJECT_ROOT>/types/.*
[include]
[libs]
[options]
strip_root=true

1
.gitattributes vendored

@ -1 +0,0 @@
*.svelte linguist-language=HTML

1
.gitignore vendored

@ -4,6 +4,7 @@
node_modules
*.map
/src/compiler/compile/internal-exports.ts
/compiler.d.ts
/compiler.*js
/index.*js
/internal

@ -1,7 +1,8 @@
language: node_js
node_js:
- "node"
- "8"
- "10"
- "12"
env:
global:
- BUILD_TIMEOUT=20000

@ -1,5 +1,22 @@
# Svelte changelog
## 3.5.1
* Accommodate webpack idiosyncracies
## 3.5.0
* Update package folder structure ([#2887](https://github.com/sveltejs/svelte/pull/2887))
* Support `once` modifier on component events ([#2654](https://github.com/sveltejs/svelte/issues/2654))
* Allow empty `<title>` tags ([#2980](https://github.com/sveltejs/svelte/issues/2980))
* Render textarea binding values inside element ([#2975](https://github.com/sveltejs/svelte/pull/2975))
* Fix delayed animation glitch ([#2871](https://github.com/sveltejs/svelte/issues/2871))
* Solve diamond dependencies problem with stores ([#2660](https://github.com/sveltejs/svelte/issues/2660))
* Fix missing outros inside each blocks ([#2689](https://github.com/sveltejs/svelte/issues/2689))
* Support animations without transitions ([#2908](https://github.com/sveltejs/svelte/issues/2908))
* Add missing transition events ([#2912](https://github.com/sveltejs/svelte/pull/2912))
## 3.4.4
* Publish type declaration files ([#2874](https://github.com/sveltejs/svelte/issues/2874))

@ -9,12 +9,13 @@ init:
environment:
matrix:
# node.js
- nodejs_version: 8
- nodejs_version: 10
- nodejs_version: 12
install:
- ps: Install-Product node $env:nodejs_version
- npm install
- ps: Update-NodeJsInstallation (Get-NodeJsLatestBuild $env:nodejs_version)
- npm ci || npm install
build: off

1
compiler.d.ts vendored

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

971
package-lock.json generated

File diff suppressed because it is too large Load Diff

@ -1,6 +1,6 @@
{
"name": "svelte",
"version": "3.4.4",
"version": "3.5.1",
"description": "Cybernetically enhanced web apps",
"module": "index.mjs",
"main": "index",
@ -18,24 +18,26 @@
"svelte",
"README.md"
],
"types": "types/runtime",
"engines": {
"node": ">= 8"
},
"types": "types/runtime/index.d.ts",
"scripts": {
"test": "mocha --opts mocha.opts",
"test:unit": "mocha --require sucrase/register --recursive ./**/__test__.ts",
"test:unit": "mocha --require sucrase/register --recursive src/**/__test__.ts",
"quicktest": "mocha --opts mocha.opts",
"precoverage": "c8 mocha --opts mocha.coverage.opts",
"coverage": "c8 report --reporter=text-lcov > coverage.lcov && c8 report --reporter=html",
"codecov": "codecov",
"precodecov": "npm run coverage",
"build": "rollup -c",
"prepare": "npm run build && npm run tsd",
"build": "rollup -c && npm run tsd",
"prepare": "npm run build",
"dev": "rollup -cw",
"pretest": "npm run build",
"posttest": "agadoo internal/index.mjs",
"prepublishOnly": "export PUBLISH=true && npm test && npm run create-stubs",
"create-stubs": "node scripts/create-stubs.js",
"tsd": "tsc -p . --emitDeclarationOnly",
"typecheck": "tsc -p . --noEmit"
"prepublishOnly": "PUBLISH=true npm test",
"tsd": "tsc -p src/compiler --emitDeclarationOnly && tsc -p src/runtime --emitDeclarationOnly",
"lint": "eslint \"{src,test}/**/*.{ts,js}\""
},
"repository": {
"type": "git",
@ -54,15 +56,18 @@
},
"homepage": "https://github.com/sveltejs/svelte#README",
"devDependencies": {
"@sveltejs/svelte-repl": "0.0.5",
"@types/mocha": "^5.2.0",
"@types/node": "^10.5.5",
"@types/node": "=8",
"@typescript-eslint/eslint-plugin": "^1.9.0",
"@typescript-eslint/parser": "^1.9.0",
"acorn": "^6.1.1",
"acorn-dynamic-import": "^4.0.0",
"agadoo": "^1.0.1",
"c8": "^3.4.0",
"codecov": "^3.0.0",
"css-tree": "1.0.0-alpha22",
"eslint": "^5.16.0",
"eslint-plugin-import": "^2.17.3",
"estree-walker": "^0.6.1",
"is-reference": "^1.1.1",
"jsdom": "^12.2.0",

@ -20,6 +20,8 @@ const ts_plugin = is_publish
const external = id => id.startsWith('svelte/');
fs.writeFileSync(`./compiler.d.ts`, `export * from './types/compiler/index';`);
export default [
/* runtime */
{
@ -59,12 +61,22 @@ export default [
external,
plugins: [
ts_plugin,
dir === 'internal' && {
generateBundle(options, bundle) {
const mod = bundle['index.mjs'];
if (mod) {
fs.writeFileSync('src/compiler/compile/internal-exports.ts', `// This file is automatically generated\nexport default new Set(${JSON.stringify(mod.exports)});`);
{
writeBundle(bundle) {
if (dir === 'internal') {
const mod = bundle['index.mjs'];
if (mod) {
fs.writeFileSync('src/compiler/compile/internal-exports.ts', `// This file is automatically generated\nexport default new Set(${JSON.stringify(mod.exports)});`);
}
}
fs.writeFileSync(`${dir}/package.json`, JSON.stringify({
main: './index',
module: './index.mjs',
types: './index.d.ts'
}, null, ' '));
fs.writeFileSync(`${dir}/index.d.ts`, `export * from '../types/runtime/${dir}/index';`);
}
}
]

@ -1,12 +0,0 @@
const fs = require('fs');
fs.readdirSync('src/runtime')
.filter(dir => fs.statSync(`src/runtime/${dir}`).isDirectory())
.forEach(dir => {
fs.writeFileSync(`${dir}/package.json`, JSON.stringify({
main: './index.js',
module: './index.mjs'
}, null, ' '));
fs.writeFileSync(`${dir}/index.d.ts`, `export * from '../types/runtime/${dir}/index.d.ts';`);
});

@ -1,4 +1,4 @@
FROM mhart/alpine-node:11.14
FROM mhart/alpine-node:12
# install dependencies
WORKDIR /app
@ -9,7 +9,7 @@ RUN npm ci --production
# Only copy over the Node pieces we need
# ~> Saves 35MB
###
FROM mhart/alpine-node:base-11.14
FROM mhart/alpine-node:slim-12
WORKDIR /app
COPY --from=0 /app .

@ -43,10 +43,12 @@
}
function remove() {
people = [...people.slice(0, i), ...people.slice(i + 1)];
// Remove selected person from the source array (people), not the filtered array
const index = people.indexOf(selected);
people = [...people.slice(0, index), ...people.slice(index + 1)];
first = last = '';
i = Math.min(i, people.length - 1);
i = Math.min(i, filteredPeople.length - 2);
}
function reset_inputs(person) {

@ -1,6 +1,6 @@
<script>
import { comicSans, link } from './styles.js';
import Hero from './Hero.html';
import Hero from './Hero.svelte';
</script>
<Hero/>

@ -1,4 +1,6 @@
import { css } from 'emotion/dist/emotion.umd.min.js';
import emotion from 'emotion/dist/emotion.umd.min.js';
const { css } = emotion;
const brand = '#74D900';
@ -30,4 +32,4 @@ export const link = css`
text-decoration: none;
background: ${brand};
}
`;
`;

@ -3,4 +3,4 @@
let name = 'Rick Astley';
</script>
<img {src} alt="{name} dancing">
<img {src} alt="{name} dances.">

@ -19,10 +19,10 @@ When building web apps, it's important to make sure that they're *accessible* to
In this case, we're missing the `alt` attribute that describes the image for people using screenreaders, or people with slow or flaky internet connections that can't download the image. Let's add one:
```html
<img src={src} alt="A man dancing">
<img src={src} alt="A man dances.">
```
We can use curly braces *inside* attributes. Try changing it to `"{name} dancing"` — remember to declare a `name` variable in the `<script>` block.
We can use curly braces *inside* attributes. Try changing it to `"{name} dances."` — remember to declare a `name` variable in the `<script>` block.
## Shorthand attributes
@ -30,6 +30,6 @@ We can use curly braces *inside* attributes. Try changing it to `"{name} dancing
It's not uncommon to have an attribute where the name and value are the same, like `src={src}`. Svelte gives us a convenient shorthand for these cases:
```html
<img {src} alt="A man dancing">
<img {src} alt="A man dances.">
```

@ -13,7 +13,7 @@ function addNumber() {
}
```
But there's a more *idiomatic* solution:
But there's a more idiomatic solution:
```js
function addNumber() {
@ -23,4 +23,10 @@ function addNumber() {
You can use similar patterns to replace `pop`, `shift`, `unshift` and `splice`.
> Assignments to *properties* of arrays and objects — e.g. `obj.foo += 1` or `array[i] = x` — work the same way as assignments to the values themselves.
Assignments to *properties* of arrays and objects — e.g. `obj.foo += 1` or `array[i] = x` — work the same way as assignments to the values themselves.
```js
function addNumber() {
numbers[numbers.length] = numbers.length + 1;
}
```

@ -10,9 +10,9 @@ We can easily specify default values for props:
</script>
```
If we now instantiate the component without an `answer` prop, it will fall back to the default:
If we now add a second component *without* an `answer` prop, it will fall back to the default:
```html
<Nested answer={42}/>
<Nested/>
```
```

@ -1,5 +0,0 @@
{
"name": "Using fixtures to represent data",
"email": "hello@cypress.io",
"body": "Fixtures are a great way to mock data for responses to routes"
}

@ -1,19 +0,0 @@
describe('Sapper template app', () => {
beforeEach(() => {
cy.visit('/')
});
it('has the correct <h1>', () => {
cy.contains('h1', 'Great success!')
});
it('navigates to /about', () => {
cy.get('nav a').contains('about').click();
cy.url().should('include', '/about');
});
it('navigates to /blog', () => {
cy.get('nav a').contains('blog').click();
cy.url().should('include', '/blog');
});
});

@ -1,17 +0,0 @@
// ***********************************************************
// This example plugins/index.js can be used to load plugins
//
// You can change the location of this file or turn off loading
// the plugins file with the 'pluginsFile' configuration option.
//
// You can read more here:
// https://on.cypress.io/plugins-guide
// ***********************************************************
// This function is called when a project is opened or re-opened (e.g. due to
// the project's config changing)
module.exports = (on, config) => {
// `on` is used to hook into various events Cypress emits
// `config` is the resolved Cypress config
}

@ -1,25 +0,0 @@
// ***********************************************
// This example commands.js shows you how to
// create various custom commands and overwrite
// existing commands.
//
// For more comprehensive examples of custom
// commands please read more here:
// https://on.cypress.io/custom-commands
// ***********************************************
//
//
// -- This is a parent command --
// Cypress.Commands.add("login", (email, password) => { ... })
//
//
// -- This is a child command --
// Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... })
//
//
// -- This is a dual command --
// Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... })
//
//
// -- This is will overwrite an existing command --
// Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... })

@ -1,20 +0,0 @@
// ***********************************************************
// This example support/index.js is processed and
// loaded automatically before your test files.
//
// This is a great place to put global configuration and
// behavior that modifies Cypress.
//
// You can change the location of this file or turn off
// automatically serving support files with the
// 'supportFile' configuration option.
//
// You can read more here:
// https://on.cypress.io/configuration
// ***********************************************************
// Import commands.js using ES2015 syntax:
import './commands'
// Alternatively you can use CommonJS syntax:
// require('./commands')

@ -1362,9 +1362,9 @@
}
},
"@sveltejs/svelte-repl": {
"version": "0.1.5",
"resolved": "https://registry.npmjs.org/@sveltejs/svelte-repl/-/svelte-repl-0.1.5.tgz",
"integrity": "sha512-gk7Ny/i19g3njob9lGGV5JzDed8eAaFphHidxquFGW5QgMCScEu+YgweBo+tRkRE0sJObFzMz3MjudH5o+KdIw==",
"version": "0.1.8",
"resolved": "https://registry.npmjs.org/@sveltejs/svelte-repl/-/svelte-repl-0.1.8.tgz",
"integrity": "sha512-RSKsuiQE3DrdT7B7DNhd5DK+DkYGLT5m6Ugchxc8iN+5v/hfVTbeNb+KJtItXLpDxiYdbb0HIiQPEdy0M+HThw==",
"dev": true,
"requires": {
"codemirror": "^5.45.0",
@ -4204,9 +4204,9 @@
}
},
"sapper": {
"version": "0.27.1",
"resolved": "https://registry.npmjs.org/sapper/-/sapper-0.27.1.tgz",
"integrity": "sha512-RH0K1uQ3zJ1IXvowxr2SuboGXV69q22KaPMhhoM5VNDv9fsUlVHtluZE8WTcGxckiO2L1xFfgM7v/aINkSZpcw==",
"version": "0.27.3",
"resolved": "https://registry.npmjs.org/sapper/-/sapper-0.27.3.tgz",
"integrity": "sha512-JOSrQEw5bD3770edZ+gwdZxS/69sySl+0KuJyMiBQKRnb85cb55w/fBYg2SMhKDa/BlaXg14aL19OiBRpXGZLQ==",
"dev": true,
"requires": {
"html-minifier": "^4.0.0",
@ -4671,9 +4671,9 @@
}
},
"svelte": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/svelte/-/svelte-3.3.0.tgz",
"integrity": "sha512-iJYkIJDvAak1kizEYnE4b4eJ17D25fU0adW7GjDgO0klbjcAFlqtWEGFJa9kpJOlUtNLilcF09k4Y9TDmK/vjg==",
"version": "3.5.1",
"resolved": "https://registry.npmjs.org/svelte/-/svelte-3.5.1.tgz",
"integrity": "sha512-iMnuyteFGQ8Yl68G/DHTHY1sLwoAMya1eS0ZOHIm/dqn2etR8WEe8hUAoluLryde4Cft4gvMhtHV3NhE60nBmQ==",
"dev": true
},
"tar": {
@ -4802,9 +4802,9 @@
}
},
"uglify-js": {
"version": "3.5.14",
"resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.5.14.tgz",
"integrity": "sha512-dgyjIw8KFK6AyVl5vm2tEqPewv5TKGEiiVFLI1LbF+oHua/Njd8tZk3lIbF1AWU1rNdEg7scaceADb4zqCcWXg==",
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.6.0.tgz",
"integrity": "sha512-W+jrUHJr3DXKhrsS7NUVxn3zqMOFn0hL/Ei6v0anCIMoKC93TjcflTagwIHLW7SfMFfiQuktQyFVCFHGUE0+yg==",
"dev": true,
"requires": {
"commander": "~2.20.0",

@ -7,13 +7,9 @@
"copy-workers": "rm -rf static/workers && cp -r node_modules/@sveltejs/svelte-repl/workers static",
"migrate": "node-pg-migrate -r dotenv/config",
"sapper": "npm run copy-workers && sapper build --legacy",
"update_shimport": "cp node_modules/shimport/index.js __sapper__/build/client/shimport@0.0.14.js",
"update": "node scripts/update_template.js && node scripts/get-contributors.js",
"start": "node __sapper__/build",
"cy:run": "cypress run",
"cy:open": "cypress open",
"test": "run-p --race dev cy:run",
"testsrc": "mocha -r esm test/**",
"test": "mocha -r esm test/**",
"deploy": "make deploy"
},
"dependencies": {
@ -39,7 +35,7 @@
"@babel/runtime": "^7.4.4",
"@sindresorhus/slugify": "^0.9.1",
"@sveltejs/site-kit": "^1.1.0",
"@sveltejs/svelte-repl": "^0.1.5",
"@sveltejs/svelte-repl": "^0.1.8",
"degit": "^2.1.3",
"dotenv": "^8.0.0",
"eslint-plugin-svelte3": "^1.0.0",
@ -57,9 +53,9 @@
"rollup-plugin-replace": "^2.2.0",
"rollup-plugin-svelte": "^5.0.3",
"rollup-plugin-terser": "^4.0.4",
"sapper": "^0.27.1",
"sapper": "^0.27.3",
"shelljs": "^0.8.3",
"svelte": "^3.0.0"
"svelte": "^3.5.1"
},
"engines": {
"node": ">=10.0.0"

@ -21,6 +21,7 @@
<NavItem segment="examples">Examples</NavItem>
<NavItem segment="repl">REPL</NavItem>
<NavItem segment="blog">Blog</NavItem>
<NavItem segment="faq">FAQ</NavItem>
<NavItem external="https://sapper.svelte.dev">Sapper</NavItem>
@ -46,4 +47,4 @@
padding: var(--nav-h) 0 0 0;
overflow-x: hidden;
}
</style>
</style>

@ -5,7 +5,7 @@ const now = (typeof process !== 'undefined' && process.hrtime)
}
: () => self.performance.now();
type Timing = {
interface Timing {
label: string;
start: number;
end: number;

@ -24,13 +24,13 @@ import unwrap_parens from './utils/unwrap_parens';
import Slot from './nodes/Slot';
import { Node as ESTreeNode } from 'estree';
type ComponentOptions = {
interface ComponentOptions {
namespace?: string;
tag?: string;
immutable?: boolean;
accessors?: boolean;
preserveWhitespace?: boolean;
};
}
// We need to tell estree-walker that it should always
// look for an `else` block, otherwise it might get
@ -97,7 +97,7 @@ export default class Component {
node_for_declaration: Map<string, Node> = new Map();
partly_hoisted: string[] = [];
fully_hoisted: string[] = [];
reactive_declarations: Array<{ assignees: Set<string>, dependencies: Set<string>, node: Node, declaration: Node }> = [];
reactive_declarations: Array<{ assignees: Set<string>; dependencies: Set<string>; node: Node; declaration: Node }> = [];
reactive_declaration_nodes: Set<Node> = new Set();
has_reactive_assignments = false;
injected_reactive_declaration_vars: Set<string> = new Set();
@ -106,12 +106,12 @@ export default class Component {
indirect_dependencies: Map<string, Set<string>> = new Map();
file: string;
locate: (c: number) => { line: number, column: number };
locate: (c: number) => { line: number; column: number };
// TODO this does the same as component.locate! remove one or the other
locator: (search: number, startIndex?: number) => {
line: number,
column: number
line: number;
column: number;
};
stylesheet: Stylesheet;
@ -140,6 +140,7 @@ export default class Component {
this.compile_options = compile_options;
this.file = compile_options.filename && (
// eslint-disable-next-line no-useless-escape
typeof process !== 'undefined' ? compile_options.filename.replace(process.cwd(), '').replace(/^[\/\\]/, '') : compile_options.filename
);
this.locate = getLocator(this.source);
@ -248,7 +249,7 @@ export default class Component {
result = result
.replace(/__svelte:self__/g, this.name)
.replace(compile_options.generate === 'ssr' ? /(@+|#+)(\w*(?:-\w*)?)/g : /(@+)(\w*(?:-\w*)?)/g, (match: string, sigil: string, name: string) => {
.replace(compile_options.generate === 'ssr' ? /(@+|#+)(\w*(?:-\w*)?)/g : /(@+)(\w*(?:-\w*)?)/g, (_match: string, sigil: string, name: string) => {
if (sigil === '@') {
if (internal_exports.has(name)) {
if (compile_options.dev && internal_exports.has(`${name}Dev`)) name = `${name}Dev`;
@ -379,7 +380,7 @@ export default class Component {
reserved.forEach(add);
internal_exports.forEach(add);
this.var_lookup.forEach((value, key) => add(key));
this.var_lookup.forEach((_value, key) => add(key));
return (name: string) => {
if (test) name = `${name}$`;
@ -398,12 +399,12 @@ export default class Component {
error(
pos: {
start: number,
end: number
start: number;
end: number;
},
e : {
code: string,
message: string
e: {
code: string;
message: string;
}
) {
error(e.message, {
@ -418,12 +419,12 @@ export default class Component {
warn(
pos: {
start: number,
end: number
start: number;
end: number;
},
warning: {
code: string,
message: string
code: string;
message: string;
}
) {
if (!this.locator) {
@ -527,7 +528,7 @@ export default class Component {
let result = '';
script.content.body.forEach((node, i) => {
script.content.body.forEach((node) => {
if (this.hoistable_nodes.has(node) || this.reactive_declaration_nodes.has(node)) {
if (a !== b) result += `[✂${a}-${b}✂]`;
a = node.end;
@ -564,7 +565,7 @@ export default class Component {
this.add_sourcemap_locations(script.content);
let { scope, globals } = create_scopes(script.content);
const { scope, globals } = create_scopes(script.content);
this.module_scope = scope;
scope.declarations.forEach((node, name) => {
@ -588,7 +589,7 @@ export default class Component {
this.error(node, {
code: 'illegal-subscription',
message: `Cannot reference store value inside <script context="module">`
})
});
} else {
this.add_var({
name,
@ -624,7 +625,7 @@ export default class Component {
});
});
let { scope: instance_scope, map, globals } = create_scopes(script.content);
const { scope: instance_scope, map, globals } = create_scopes(script.content);
this.instance_scope = instance_scope;
this.instance_scope_map = map;
@ -646,7 +647,7 @@ export default class Component {
this.node_for_declaration.set(name, node);
});
globals.forEach((node, name) => {
globals.forEach((_node, name) => {
if (this.var_lookup.has(name)) return;
if (this.injected_reactive_declaration_vars.has(name)) {
@ -705,7 +706,7 @@ export default class Component {
let scope = instance_scope;
walk(this.ast.instance.content, {
enter(node, parent) {
enter(node) {
if (map.has(node)) {
scope = map.get(node);
}
@ -738,7 +739,7 @@ export default class Component {
scope = scope.parent;
}
}
})
});
}
extract_reactive_store_references() {
@ -786,7 +787,7 @@ export default class Component {
}
if (name[0] === '$' && name[1] !== '$') {
return `${name.slice(1)}.set(${name})`
return `${name.slice(1)}.set(${name})`;
}
if (variable && !variable.referenced && !variable.is_reactive_dependency && !variable.export_name && !name.startsWith('$$')) {
@ -888,13 +889,13 @@ export default class Component {
}
if (variable.writable && variable.name !== variable.export_name) {
code.prependRight(declarator.id.start, `${variable.export_name}: `)
code.prependRight(declarator.id.start, `${variable.export_name}: `);
}
if (next) {
const next_variable = component.var_lookup.get(next.id.name)
const next_variable = component.var_lookup.get(next.id.name);
const new_declaration = !next_variable.export_name
|| (current_group.insert && next_variable.subscribable)
|| (current_group.insert && next_variable.subscribable);
if (new_declaration) {
code.overwrite(declarator.end, next.start, ` ${node.kind} `);
@ -904,7 +905,7 @@ export default class Component {
current_group = null;
if (variable.subscribable) {
let insert = get_insert(variable);
const insert = get_insert(variable);
if (next) {
code.overwrite(declarator.end, next.start, `; ${insert}; ${node.kind} `);
@ -975,9 +976,9 @@ export default class Component {
if (!d.init) return false;
if (d.init.type !== 'Literal') return false;
const v = this.var_lookup.get(d.id.name)
if (v.reassigned) return false
if (v.export_name) return false
const v = this.var_lookup.get(d.id.name);
if (v.reassigned) return false;
if (v.export_name) return false;
if (this.var_lookup.get(d.id.name).reassigned) return false;
if (this.vars.find(variable => variable.name === d.id.name && variable.module)) return false;
@ -1006,7 +1007,7 @@ export default class Component {
});
const checked = new Set();
let walking = new Set();
const walking = new Set();
const is_hoistable = fn_declaration => {
if (fn_declaration.type === 'ExportNamedDeclaration') {
@ -1015,7 +1016,7 @@ export default class Component {
const instance_scope = this.instance_scope;
let scope = this.instance_scope;
let map = this.instance_scope_map;
const map = this.instance_scope_map;
let hoistable = true;
@ -1051,7 +1052,7 @@ export default class Component {
hoistable = false;
} else if (!is_hoistable(other_declaration)) {
hoistable = false;
}
}
}
else {
@ -1103,7 +1104,7 @@ export default class Component {
const dependencies = new Set();
let scope = this.instance_scope;
let map = this.instance_scope_map;
const map = this.instance_scope_map;
walk(node.body, {
enter(node, parent) {
@ -1320,14 +1321,16 @@ function process_component_options(component: Component, nodes) {
case 'accessors':
case 'immutable':
case 'preserveWhitespace':
{
const code = `invalid-${name}-value`;
const message = `${name} attribute must be true or false`
const message = `${name} attribute must be true or false`;
const value = get_value(attribute, code, message);
if (typeof value !== 'boolean') component.error(attribute, { code, message });
component_options[name] = value;
break;
}
default:
component.error(attribute, {

@ -5,10 +5,10 @@ import { stringify_props } from './utils/stringify_props';
const wrappers = { esm, cjs };
type Export = {
interface Export {
name: string;
as: string;
};
}
export default function create_module(
code: string,
@ -16,7 +16,7 @@ export default function create_module(
name: string,
banner: string,
sveltePath = 'svelte',
helpers: { name: string, alias: string }[],
helpers: Array<{ name: string; alias: string }>,
imports: Node[],
module_exports: Export[],
source: string
@ -44,7 +44,7 @@ function esm(
banner: string,
sveltePath: string,
internal_path: string,
helpers: { name: string, alias: string }[],
helpers: Array<{ name: string; alias: string }>,
imports: Node[],
module_exports: Export[],
source: string
@ -84,7 +84,7 @@ function cjs(
banner: string,
sveltePath: string,
internal_path: string,
helpers: { name: string, alias: string }[],
helpers: Array<{ name: string; alias: string }>,
imports: Node[],
module_exports: Export[]
) {
@ -115,7 +115,7 @@ function cjs(
const source = edit_source(node.source.value, sveltePath);
return `const ${lhs} = require("${source}");`
return `const ${lhs} = require("${source}");`;
});
const exports = [`exports.default = ${name};`].concat(
@ -131,5 +131,5 @@ function cjs(
${code}
${exports}`
}
${exports}`;
}

@ -73,7 +73,7 @@ export default class Selector {
}
}
this.blocks.forEach((block, i) => {
this.blocks.forEach((block) => {
if (block.global) {
const selector = block.selectors[0];
const first = selector.children[0];
@ -238,7 +238,7 @@ function attribute_matches(node: Node, name: string, expected_value: string, ope
}
function class_matches(node, name: string) {
return node.classes.some(function(class_directive) {
return node.classes.some((class_directive) => {
return new RegExp(`\\b${name}\\b`).test(class_directive.name);
});
}
@ -287,7 +287,7 @@ function group_selectors(selector: Node) {
const blocks = [block];
selector.children.forEach((child: Node, i: number) => {
selector.children.forEach((child: Node) => {
if (child.type === 'WhiteSpace' || child.type === 'Combinator') {
block = new Block(child);
blocks.push(block);

@ -43,11 +43,11 @@ class Rule {
return this.selectors.some(s => s.used);
}
minify(code: MagicString, dev: boolean) {
minify(code: MagicString, _dev: boolean) {
let c = this.node.start;
let started = false;
this.selectors.forEach((selector, i) => {
this.selectors.forEach((selector) => {
if (selector.used) {
const separator = started ? ',' : '';
if ((selector.node.start - c) > separator.length) {
@ -140,7 +140,7 @@ class Declaration {
class Atrule {
node: Node;
children: (Atrule|Rule)[];
children: Array<Atrule|Rule>;
constructor(node: Node) {
this.node = node;
@ -163,7 +163,7 @@ class Atrule {
}
}
is_used(dev: boolean) {
is_used(_dev: boolean) {
return true; // TODO
}
@ -253,7 +253,7 @@ export default class Stylesheet {
has_styles: boolean;
id: string;
children: (Rule|Atrule)[] = [];
children: Array<Rule|Atrule> = [];
keyframes: Map<string, string> = new Map();
nodes_with_css_class: Set<Node> = new Set();
@ -269,7 +269,7 @@ export default class Stylesheet {
this.has_styles = true;
const stack: (Rule | Atrule)[] = [];
const stack: Array<Rule | Atrule> = [];
let current_atrule: Atrule = null;
walk(ast.css, {

@ -1,11 +1,12 @@
import { assign } from '../../runtime/internal/index';
import { assign } from '../../runtime/internal/utils';
import Stats from '../Stats';
import parse from '../parse/index';
import render_dom from './render-dom/index';
import render_ssr from './render-ssr/index';
import { CompileOptions, Ast, Warning } from '../interfaces';
import { CompileOptions, Warning } from '../interfaces';
import Component from './Component';
import fuzzymatch from '../utils/fuzzymatch';
import { get_name_from_filename } from './utils/get_name_from_filename';
const valid_options = [
'format',
@ -55,43 +56,23 @@ function validate_options(options: CompileOptions, warnings: Warning[]) {
}
}
function get_name(filename: string) {
if (!filename) return null;
const parts = filename.split(/[\/\\]/);
if (parts.length > 1 && /^index\.\w+/.test(parts[parts.length - 1])) {
parts.pop();
}
const base = parts.pop()
.replace(/\..+/, "")
.replace(/[^a-zA-Z_$0-9]+/g, '_')
.replace(/^_/, '')
.replace(/_$/, '')
.replace(/^(\d)/, '_$1');
return base[0].toUpperCase() + base.slice(1);
}
export default function compile(source: string, options: CompileOptions = {}) {
options = assign({ generate: 'dom', dev: false }, options);
const stats = new Stats();
const warnings = [];
let ast: Ast;
validate_options(options, warnings);
stats.start('parse');
ast = parse(source, options);
const ast = parse(source, options);
stats.stop('parse');
stats.start('create component');
const component = new Component(
ast,
source,
options.name || get_name(options.filename) || 'Component',
options.name || get_name_from_filename(options.filename) || 'Component',
options,
stats,
warnings

@ -21,7 +21,7 @@ export default class Attribute extends Node {
is_synthetic: boolean;
should_cache: boolean;
expression?: Expression;
chunks: (Text | Expression)[];
chunks: Array<Text | Expression>;
dependencies: Set<string>;
constructor(component, parent, scope, info) {

@ -8,13 +8,13 @@ import { Node as INode } from '../../interfaces';
import { new_tail } from '../utils/tail';
import Element from './Element';
type Context = {
key: INode,
name?: string,
tail: string
};
interface Context {
key: INode;
name?: string;
tail: string;
}
function unpack_destructuring(contexts: Array<Context>, node: INode, tail: string) {
function unpack_destructuring(contexts: Context[], node: INode, tail: string) {
if (!node) return;
if (node.type === 'Identifier' || node.type === 'RestIdentifier') {
@ -25,7 +25,7 @@ function unpack_destructuring(contexts: Array<Context>, node: INode, tail: strin
} else if (node.type === 'ArrayPattern') {
node.elements.forEach((element, i) => {
if (element && element.type === 'RestIdentifier') {
unpack_destructuring(contexts, element, `${tail}.slice(${i})`)
unpack_destructuring(contexts, element, `${tail}.slice(${i})`);
} else {
unpack_destructuring(contexts, element, `${tail}[${i}]`);
}
@ -60,7 +60,7 @@ export default class EachBlock extends AbstractBlock {
context: string;
key: Expression;
scope: TemplateScope;
contexts: Array<Context>;
contexts: Context[];
has_animation: boolean;
has_binding = false;

@ -54,7 +54,7 @@ const a11y_required_content = new Set([
'h4',
'h5',
'h6'
])
]);
const invisible_elements = new Set(['meta', 'html', 'script', 'style']);
@ -180,10 +180,12 @@ export default class Element extends Node {
break;
case 'Transition':
{
const transition = new Transition(component, this, scope, node);
if (node.intro) this.intro = transition;
if (node.outro) this.outro = transition;
break;
}
case 'Animation':
this.animation = new Animation(component, this, scope, node);
@ -605,6 +607,25 @@ export default class Element extends Node {
message: `'${binding.name}' is not a valid binding on void elements like <${this.name}>. Use a wrapper element instead`
});
}
} else if (
name === 'text' ||
name === 'html'
) {
const contenteditable = this.attributes.find(
(attribute: Attribute) => attribute.name === 'contenteditable'
);
if (!contenteditable) {
component.error(binding, {
code: `missing-contenteditable-attribute`,
message: `'contenteditable' attribute is required for text and html two-way bindings`
});
} else if (contenteditable && !contenteditable.is_static) {
component.error(contenteditable, {
code: `dynamic-contenteditable-attribute`,
message: `'contenteditable' attribute cannot be dynamic if element uses two-way binding`
});
}
} else if (name !== 'this') {
component.error(binding, {
code: `invalid-binding`,
@ -687,7 +708,7 @@ export default class Element extends Node {
if (class_attribute.chunks.length === 1 && class_attribute.chunks[0].type === 'Text') {
(class_attribute.chunks[0] as Text).data += ` ${class_name}`;
} else {
(<Node[]>class_attribute.chunks).push(
(class_attribute.chunks as Node[]).push(
new Text(this.component, this, this.scope, {
type: 'Text',
data: ` ${class_name}`

@ -36,6 +36,7 @@ export default class InlineComponent extends Node {
: null;
info.attributes.forEach(node => {
/* eslint-disable no-fallthrough */
switch (node.type) {
case 'Action':
component.error(node, {
@ -82,6 +83,7 @@ export default class InlineComponent extends Node {
default:
throw new Error(`Not implemented: ${node.type}`);
}
/* eslint-enable no-fallthrough */
});
if (this.lets.length > 0) {
@ -98,6 +100,17 @@ export default class InlineComponent extends Node {
this.scope = scope;
}
this.handlers.forEach(handler => {
handler.modifiers.forEach(modifier => {
if (modifier !== 'once') {
component.error(handler, {
code: 'invalid-event-modifier',
message: `Event modifiers other than 'once' can only be used on DOM elements`
});
}
});
});
this.children = map_children(component, this, this.scope, info.children);
}
}

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

@ -12,7 +12,7 @@ export default class Text extends Node {
super(component, parent, scope, info);
this.data = info.data;
if (!component.component_options.preserveWhitespace && !/\S/.test(info.data)) {
if (!component.component_options.preserveWhitespace && !/[\S\u00A0]/.test(info.data)) {
let node = parent;
while (node) {
if (node.type === 'Element' && node.name === 'pre') {

@ -44,8 +44,8 @@ export default class Window extends Node {
if (!~valid_bindings.indexOf(node.name)) {
const match = (
node.name === 'width' ? 'innerWidth' :
node.name === 'height' ? 'innerHeight' :
fuzzymatch(node.name, valid_bindings)
node.name === 'height' ? 'innerHeight' :
fuzzymatch(node.name, valid_bindings)
);
const message = `'${node.name}' is not a valid binding on <svelte:window>`;

@ -33,32 +33,32 @@ 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;
| 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;

@ -4,10 +4,10 @@ import is_reference from 'is-reference';
import flatten_reference from '../../utils/flatten_reference';
import { create_scopes, Scope, extract_names } from '../../utils/scope';
import { Node } from '../../../interfaces';
import { globals } from '../../../utils/names';
import { globals , sanitize } from '../../../utils/names';
import deindent from '../../utils/deindent';
import Wrapper from '../../render-dom/wrappers/shared/Wrapper';
import { sanitize } from '../../../utils/names';
import TemplateScope from './TemplateScope';
import get_object from '../../utils/get_object';
import { nodes_match } from '../../../utils/nodes_match';
@ -28,8 +28,8 @@ const binary_operators: Record<string, number> = {
'<=': 11,
'>': 11,
'>=': 11,
'in': 11,
'instanceof': 11,
in: 11,
instanceof: 11,
'==': 10,
'!=': 10,
'===': 10,
@ -490,7 +490,7 @@ export default class Expression {
}
}
function get_function_name(node, parent) {
function get_function_name(_node, parent) {
if (parent.type === 'EventHandler') {
return `${parent.name}_handler`;
}

@ -17,7 +17,7 @@ export default class Node {
var: string;
attributes: Attribute[];
constructor(component: Component, parent, scope, info: any) {
constructor(component: Component, parent, _scope, info: any) {
this.start = info.start;
this.end = info.end;
this.type = info.type;

@ -9,7 +9,7 @@ export interface BlockOptions {
renderer?: Renderer;
comment?: string;
key?: string;
bindings?: Map<string, { object: string, property: string, snippet: string }>;
bindings?: Map<string, { object: string; property: string; snippet: string }>;
dependencies?: Set<string>;
}
@ -26,7 +26,7 @@ export default class Block {
dependencies: Set<string>;
bindings: Map<string, { object: string, property: string, snippet: string }>;
bindings: Map<string, { object: string; property: string; snippet: string }>;
builders: {
init: CodeBuilder;
@ -357,6 +357,7 @@ export default class Block {
`);
}
/* eslint-disable @typescript-eslint/indent,indent */
return deindent`
${this.variables.size > 0 &&
`var ${Array.from(this.variables.keys())
@ -371,9 +372,10 @@ export default class Block {
return {
${properties}
};
`.replace(/(#+)(\w*)/g, (match: string, sigil: string, name: string) => {
`.replace(/(#+)(\w*)/g, (_match: string, sigil: string, name: string) => {
return sigil === '#' ? this.alias(name) : sigil.slice(1) + name;
});
/* eslint-enable @typescript-eslint/indent,indent */
}
render_listeners(chunk: string = '') {
@ -387,7 +389,7 @@ export default class Block {
this.builders.destroy.add_line(
`#dispose${chunk}();`
)
);
} else {
this.builders.hydrate.add_block(deindent`
#dispose${chunk} = [

@ -8,7 +8,7 @@ export default class Renderer {
component: Component; // TODO Maybe Renderer shouldn't know about Component?
options: CompileOptions;
blocks: (Block | string)[] = [];
blocks: Array<Block | string> = [];
readonly: Set<string> = new Set();
meta_bindings: CodeBuilder = new CodeBuilder(); // initial values for e.g. window.innerWidth, if there's a <svelte:window> meta tag
binding_groups: string[] = [];
@ -17,7 +17,7 @@ export default class Renderer {
fragment: FragmentWrapper;
file_var: string;
locate: (c: number) => { line: number; column: number; };
locate: (c: number) => { line: number; column: number };
constructor(component: Component, options: CompileOptions) {
this.component = component;

@ -74,18 +74,20 @@ export default function dom(
const props = component.vars.filter(variable => !variable.module && variable.export_name);
const writable_props = props.filter(variable => variable.writable);
/* eslint-disable @typescript-eslint/indent,indent */
const set = (uses_props || writable_props.length > 0 || component.slots.size > 0)
? deindent`
${$$props} => {
${uses_props && component.invalidate('$$props', `$$props = @assign(@assign({}, $$props), $$new_props)`)}
${writable_props.map(prop =>
`if ('${prop.export_name}' in $$props) ${component.invalidate(prop.name, `${prop.name} = $$props.${prop.export_name}`)};`
`if ('${prop.export_name}' in $$props) ${component.invalidate(prop.name, `${prop.name} = $$props.${prop.export_name}`)};`
)}
${component.slots.size > 0 &&
`if ('$$scope' in ${$$props}) ${component.invalidate('$$scope', `$$scope = ${$$props}.$$scope`)};`}
}
`
: null;
/* eslint-enable @typescript-eslint/indent,indent */
const body = [];
@ -152,12 +154,12 @@ export default function dom(
// instrument assignments
if (component.ast.instance) {
let scope = component.instance_scope;
let map = component.instance_scope_map;
const map = component.instance_scope_map;
let pending_assignments = new Set();
walk(component.ast.instance.content, {
enter: (node, parent) => {
enter: (node) => {
if (map.has(node)) {
scope = map.get(node);
}
@ -390,7 +392,7 @@ export default function dom(
const store = component.var_lookup.get(name);
if (store && store.reassigned) {
return `${$name}, $$unsubscribe_${name} = @noop, $$subscribe_${name} = () => { $$unsubscribe_${name}(); $$unsubscribe_${name} = ${name}.subscribe($$value => { ${$name} = $$value; $$invalidate('${$name}', ${$name}); }) }`
return `${$name}, $$unsubscribe_${name} = @noop, $$subscribe_${name} = () => { $$unsubscribe_${name}(); $$unsubscribe_${name} = ${name}.subscribe($$value => { ${$name} = $$value; $$invalidate('${$name}', ${$name}); }) }`;
}
return $name;

@ -6,7 +6,7 @@ import Body from '../../nodes/Body';
export default class BodyWrapper extends Wrapper {
node: Body;
render(block: Block, parent_node: string, parent_nodes: string) {
render(block: Block, _parent_node: string, _parent_nodes: string) {
this.node.handlers.forEach(handler => {
const snippet = handler.render(block);

@ -13,13 +13,13 @@ export default class DebugTagWrapper extends Wrapper {
block: Block,
parent: Wrapper,
node: DebugTag,
strip_whitespace: boolean,
next_sibling: Wrapper
_strip_whitespace: boolean,
_next_sibling: Wrapper
) {
super(renderer, block, parent, node);
}
render(block: Block, parent_node: string, parent_nodes: string) {
render(block: Block, _parent_node: string, _parent_nodes: string) {
const { renderer } = this;
const { component } = renderer;

@ -425,7 +425,7 @@ export default class EachBlockWrapper extends Wrapper {
all_dependencies.add(dependency);
});
const outro_block = this.block.has_outros && block.get_unique_name('outro_block')
const outro_block = this.block.has_outros && block.get_unique_name('outro_block');
if (outro_block) {
block.builders.init.add_block(deindent`
function ${outro_block}(i, detaching, local) {

@ -52,7 +52,7 @@ export default class AttributeWrapper {
element.node.bindings.find(
(binding) =>
/checked|group/.test(binding.name)
)));
)));
const property_name = is_indirectly_bound_value
? '__value'
@ -70,7 +70,7 @@ export default class AttributeWrapper {
const is_legacy_input_type = element.renderer.component.compile_options.legacy && name === 'type' && this.parent.node.name === 'input';
const is_dataset = /^data-/.test(name) && !element.renderer.component.compile_options.legacy && !element.node.namespace;
const camel_case_name = is_dataset ? name.replace('data-', '').replace(/(-\w)/g, function (m) {
const camel_case_name = is_dataset ? name.replace('data-', '').replace(/(-\w)/g, (m) => {
return m[1].toUpperCase();
}) : name;
@ -219,84 +219,25 @@ export default class AttributeWrapper {
return `="${value.map(chunk => {
return chunk.type === 'Text'
? chunk.data.replace(/"/g, '\\"')
: `\${${chunk.render()}}`
: `\${${chunk.render()}}`;
})}"`;
}
}
// source: https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes
// source: https://html.spec.whatwg.org/multipage/indices.html
const attribute_lookup = {
accept: { applies_to: ['form', 'input'] },
'accept-charset': { property_name: 'acceptCharset', applies_to: ['form'] },
accesskey: { property_name: 'accessKey' },
action: { applies_to: ['form'] },
align: {
applies_to: [
'applet',
'caption',
'col',
'colgroup',
'hr',
'iframe',
'img',
'table',
'tbody',
'td',
'tfoot',
'th',
'thead',
'tr',
],
},
allowfullscreen: { property_name: 'allowFullscreen', applies_to: ['iframe'] },
alt: { applies_to: ['applet', 'area', 'img', 'input'] },
allowpaymentrequest: { property_name: 'allowPaymentRequest', applies_to: ['iframe'] },
async: { applies_to: ['script'] },
autocomplete: { applies_to: ['form', 'input'] },
autofocus: { applies_to: ['button', 'input', 'keygen', 'select', 'textarea'] },
autoplay: { applies_to: ['audio', 'video'] },
autosave: { applies_to: ['input'] },
bgcolor: {
property_name: 'bgColor',
applies_to: [
'body',
'col',
'colgroup',
'marquee',
'table',
'tbody',
'tfoot',
'td',
'th',
'tr',
],
},
border: { applies_to: ['img', 'object', 'table'] },
buffered: { applies_to: ['audio', 'video'] },
challenge: { applies_to: ['keygen'] },
charset: { applies_to: ['meta', 'script'] },
checked: { applies_to: ['command', 'input'] },
cite: { applies_to: ['blockquote', 'del', 'ins', 'q'] },
class: { property_name: 'className' },
code: { applies_to: ['applet'] },
codebase: { property_name: 'codeBase', applies_to: ['applet'] },
color: { applies_to: ['basefont', 'font', 'hr'] },
cols: { applies_to: ['textarea'] },
colspan: { property_name: 'colSpan', applies_to: ['td', 'th'] },
content: { applies_to: ['meta'] },
contenteditable: { property_name: 'contentEditable' },
contextmenu: {},
checked: { applies_to: ['input'] },
controls: { applies_to: ['audio', 'video'] },
coords: { applies_to: ['area'] },
data: { applies_to: ['object'] },
datetime: { property_name: 'dateTime', applies_to: ['del', 'ins', 'time'] },
default: { applies_to: ['track'] },
defer: { applies_to: ['script'] },
dir: {},
dirname: { property_name: 'dirName', applies_to: ['input', 'textarea'] },
disabled: {
applies_to: [
'button',
'command',
'fieldset',
'input',
'keygen',
@ -306,119 +247,21 @@ const attribute_lookup = {
'textarea',
],
},
download: { applies_to: ['a', 'area'] },
draggable: {},
dropzone: {},
enctype: { applies_to: ['form'] },
for: { property_name: 'htmlFor', applies_to: ['label', 'output'] },
formaction: { applies_to: ['input', 'button'] },
headers: { applies_to: ['td', 'th'] },
height: {
applies_to: ['canvas', 'embed', 'iframe', 'img', 'input', 'object', 'video'],
},
formnovalidate: { property_name: 'formNoValidate', applies_to: ['button', 'input'] },
hidden: {},
high: { applies_to: ['meter'] },
href: { applies_to: ['a', 'area', 'base', 'link'] },
hreflang: { applies_to: ['a', 'area', 'link'] },
'http-equiv': { property_name: 'httpEquiv', applies_to: ['meta'] },
icon: { applies_to: ['command'] },
id: {},
indeterminate: { applies_to: ['input'] },
ismap: { property_name: 'isMap', applies_to: ['img'] },
itemprop: {},
keytype: { applies_to: ['keygen'] },
kind: { applies_to: ['track'] },
label: { applies_to: ['track'] },
lang: {},
language: { applies_to: ['script'] },
loop: { applies_to: ['audio', 'bgsound', 'marquee', 'video'] },
low: { applies_to: ['meter'] },
manifest: { applies_to: ['html'] },
max: { applies_to: ['input', 'meter', 'progress'] },
maxlength: { property_name: 'maxLength', applies_to: ['input', 'textarea'] },
media: { applies_to: ['a', 'area', 'link', 'source', 'style'] },
method: { applies_to: ['form'] },
min: { applies_to: ['input', 'meter'] },
loop: { applies_to: ['audio', 'bgsound', 'video'] },
multiple: { applies_to: ['input', 'select'] },
muted: { applies_to: ['audio', 'video'] },
name: {
applies_to: [
'button',
'form',
'fieldset',
'iframe',
'input',
'keygen',
'object',
'output',
'select',
'textarea',
'map',
'meta',
'param',
],
},
nomodule: { property_name: 'noModule', applies_to: ['script'] },
novalidate: { property_name: 'noValidate', applies_to: ['form'] },
open: { applies_to: ['details'] },
optimum: { applies_to: ['meter'] },
pattern: { applies_to: ['input'] },
ping: { applies_to: ['a', 'area'] },
placeholder: { applies_to: ['input', 'textarea'] },
poster: { applies_to: ['video'] },
preload: { applies_to: ['audio', 'video'] },
radiogroup: { applies_to: ['command'] },
open: { applies_to: ['details', 'dialog'] },
playsinline: { property_name: 'playsInline', applies_to: ['video'] },
readonly: { property_name: 'readOnly', applies_to: ['input', 'textarea'] },
rel: { applies_to: ['a', 'area', 'link'] },
required: { applies_to: ['input', 'select', 'textarea'] },
reversed: { applies_to: ['ol'] },
rows: { applies_to: ['textarea'] },
rowspan: { property_name: 'rowSpan', applies_to: ['td', 'th'] },
sandbox: { applies_to: ['iframe'] },
scope: { applies_to: ['th'] },
scoped: { applies_to: ['style'] },
seamless: { applies_to: ['iframe'] },
selected: { applies_to: ['option'] },
shape: { applies_to: ['a', 'area'] },
size: { applies_to: ['input', 'select'] },
sizes: { applies_to: ['link', 'img', 'source'] },
span: { applies_to: ['col', 'colgroup'] },
spellcheck: {},
src: {
applies_to: [
'audio',
'embed',
'iframe',
'img',
'input',
'script',
'source',
'track',
'video',
],
},
srcdoc: { applies_to: ['iframe'] },
srclang: { applies_to: ['track'] },
srcset: { applies_to: ['img'] },
start: { applies_to: ['ol'] },
step: { applies_to: ['input'] },
style: { property_name: 'style.cssText' },
summary: { applies_to: ['table'] },
tabindex: { property_name: 'tabIndex' },
target: { applies_to: ['a', 'area', 'base', 'form'] },
title: {},
type: {
applies_to: [
'button',
'command',
'embed',
'object',
'script',
'source',
'style',
'menu',
],
},
usemap: { property_name: 'useMap', applies_to: ['img', 'input', 'object'] },
value: {
applies_to: [
'button',
@ -432,12 +275,6 @@ const attribute_lookup = {
'textarea',
],
},
volume: { applies_to: ['audio', 'video'] },
playbackRate: { applies_to: ['audio', 'video'] },
width: {
applies_to: ['canvas', 'embed', 'iframe', 'img', 'input', 'object', 'video'],
},
wrap: { applies_to: ['textarea'] },
};
Object.keys(attribute_lookup).forEach(name => {

@ -1,6 +1,5 @@
import Binding from '../../../nodes/Binding';
import ElementWrapper from '../Element';
import { dimensions } from '../../../../utils/patterns';
import get_object from '../../../utils/get_object';
import Block from '../../Block';
import Node from '../../../nodes/shared/Node';
@ -23,8 +22,8 @@ export default class BindingWrapper {
handler: {
uses_context: boolean;
mutation: string;
contextual_dependencies: Set<string>,
snippet?: string
contextual_dependencies: Set<string>;
snippet?: string;
};
snippet: string;
is_readonly: boolean;
@ -87,7 +86,7 @@ export default class BindingWrapper {
}
is_readonly_media_attribute() {
return this.node.is_readonly_media_attribute()
return this.node.is_readonly_media_attribute();
}
render(block: Block, lock: string) {
@ -95,23 +94,23 @@ export default class BindingWrapper {
const { parent } = this;
let update_conditions: string[] = this.needs_lock ? [`!${lock}`] : [];
const update_conditions: string[] = this.needs_lock ? [`!${lock}`] : [];
const dependency_array = [...this.node.expression.dependencies];
if (dependency_array.length === 1) {
update_conditions.push(`changed.${dependency_array[0]}`)
update_conditions.push(`changed.${dependency_array[0]}`);
} else if (dependency_array.length > 1) {
update_conditions.push(
`(${dependency_array.map(prop => `changed.${prop}`).join(' || ')})`
)
);
}
if (parent.node.name === 'input') {
const type = parent.node.get_static_attribute_value('type');
if (type === null || type === "" || type === "text") {
update_conditions.push(`(${parent.var}.${this.node.name} !== ${this.snippet})`)
update_conditions.push(`(${parent.var}.${this.node.name} !== ${this.snippet})`);
}
}
@ -121,6 +120,7 @@ export default class BindingWrapper {
// special cases
switch (this.node.name) {
case 'group':
{
const binding_group = get_binding_group(parent.renderer, this.node.expression.node);
block.builders.hydrate.add_line(
@ -131,6 +131,7 @@ export default class BindingWrapper {
`ctx.$$binding_groups[${binding_group}].splice(ctx.$$binding_groups[${binding_group}].indexOf(${parent.var}), 1);`
);
break;
}
case 'currentTime':
case 'playbackRate':
@ -139,6 +140,7 @@ export default class BindingWrapper {
break;
case 'paused':
{
// this is necessary to prevent audio restarting by itself
const last = block.get_unique_name(`${parent.var}_is_paused`);
block.add_variable(last, 'true');
@ -146,6 +148,7 @@ export default class BindingWrapper {
update_conditions.push(`${last} !== (${last} = ${this.snippet})`);
update_dom = `${parent.var}[${last} ? "pause" : "play"]();`;
break;
}
case 'value':
if (parent.node.get_static_attribute_value('type') === 'file') {
@ -192,7 +195,15 @@ function get_dom_updater(
? `~${binding.snippet}.indexOf(${element.var}.__value)`
: `${element.var}.__value === ${binding.snippet}`;
return `${element.var}.checked = ${condition};`
return `${element.var}.checked = ${condition};`;
}
if (binding.node.name === 'text') {
return `if (${binding.snippet} !== ${element.var}.textContent) ${element.var}.textContent = ${binding.snippet};`;
}
if (binding.node.name === 'html') {
return `if (${binding.snippet} !== ${element.var}.innerHTML) ${element.var}.innerHTML = ${binding.snippet};`;
}
return `${element.var}.${binding.node.name} = ${binding.snippet};`;
@ -304,7 +315,15 @@ function get_value_from_dom(
}
if ((name === 'buffered' || name === 'seekable' || name === 'played')) {
return `@time_ranges_to_array(this.${name})`
return `@time_ranges_to_array(this.${name})`;
}
if (name === 'text') {
return `this.textContent`;
}
if (name === 'html') {
return `this.innerHTML`;
}
// everything else

@ -9,7 +9,7 @@ import Text from '../../../nodes/Text';
export interface StyleProp {
key: string;
value: (Text|Expression)[];
value: Array<Text|Expression>;
}
export default class StyleAttributeWrapper extends AttributeWrapper {
@ -65,7 +65,7 @@ export default class StyleAttributeWrapper extends AttributeWrapper {
}
}
function optimize_style(value: (Text|Expression)[]) {
function optimize_style(value: Array<Text|Expression>) {
const props: StyleProp[] = [];
let chunks = value.slice();
@ -83,12 +83,14 @@ function optimize_style(value: (Text|Expression)[]) {
const remaining_data = chunk.data.slice(offset);
if (remaining_data) {
/* eslint-disable @typescript-eslint/no-object-literal-type-assertion */
chunks[0] = {
start: chunk.start + offset,
end: chunk.end,
type: 'Text',
data: remaining_data
} as Text;
/* eslint-enable @typescript-eslint/no-object-literal-type-assertion */
} else {
chunks.shift();
}
@ -102,8 +104,8 @@ function optimize_style(value: (Text|Expression)[]) {
return props;
}
function get_style_value(chunks: (Text | Expression)[]) {
const value: (Text|Expression)[] = [];
function get_style_value(chunks: Array<Text | Expression>) {
const value: Array<Text|Expression> = [];
let in_url = false;
let quote_mark = null;
@ -171,6 +173,6 @@ function get_style_value(chunks: (Text | Expression)[]) {
};
}
function is_dynamic(value: (Text|Expression)[]) {
function is_dynamic(value: Array<Text|Expression>) {
return value.length > 1 || value[0].type !== 'Text';
}

@ -2,7 +2,6 @@ import Renderer from '../../Renderer';
import Element from '../../../nodes/Element';
import Wrapper from '../shared/Wrapper';
import Block from '../../Block';
import Node from '../../../nodes/shared/Node';
import { is_void, quote_prop_if_necessary, quote_name_if_necessary, sanitize } from '../../../../utils/names';
import FragmentWrapper from '../Fragment';
import { stringify, escape_html, escape } from '../../../utils/stringify';
@ -24,25 +23,31 @@ import { get_context_merger } from '../shared/get_context_merger';
const events = [
{
event_names: ['input'],
filter: (node: Element, name: string) =>
filter: (node: Element, _name: string) =>
node.name === 'textarea' ||
node.name === 'input' && !/radio|checkbox|range/.test(node.get_static_attribute_value('type') as string)
},
{
event_names: ['change'],
event_names: ['input'],
filter: (node: Element, name: string) =>
(name === 'text' || name === 'html') &&
node.attributes.some(attribute => attribute.name === 'contenteditable')
},
{
event_names: ['change'],
filter: (node: Element, _name: string) =>
node.name === 'select' ||
node.name === 'input' && /radio|checkbox/.test(node.get_static_attribute_value('type') as string)
},
{
event_names: ['change', 'input'],
filter: (node: Element, name: string) =>
filter: (node: Element, _name: string) =>
node.name === 'input' && node.get_static_attribute_value('type') === 'range'
},
{
event_names: ['resize'],
filter: (node: Element, name: string) =>
filter: (_node: Element, name: string) =>
dimensions.test(name)
},
@ -93,7 +98,7 @@ const events = [
// details event
{
event_names: ['toggle'],
filter: (node: Element, name: string) =>
filter: (node: Element, _name: string) =>
node.name === 'details'
},
];
@ -119,7 +124,7 @@ export default class ElementWrapper extends Wrapper {
next_sibling: Wrapper
) {
super(renderer, block, parent, node);
this.var = node.name.replace(/[^a-zA-Z0-9_$]/g, '_')
this.var = node.name.replace(/[^a-zA-Z0-9_$]/g, '_');
this.class_dependencies = [];
@ -239,7 +244,7 @@ export default class ElementWrapper extends Wrapper {
}
const node = this.var;
const nodes = parent_nodes && block.get_unique_name(`${this.var}_nodes`) // if we're in unclaimable territory, i.e. <head>, parent_nodes is null
const nodes = parent_nodes && block.get_unique_name(`${this.var}_nodes`); // if we're in unclaimable territory, i.e. <head>, parent_nodes is null
block.add_variable(node);
const render_statement = this.get_render_statement();
@ -350,7 +355,7 @@ export default class ElementWrapper extends Wrapper {
let open = `<${wrapper.node.name}`;
(wrapper as ElementWrapper).attributes.forEach((attr: AttributeWrapper) => {
open += ` ${fix_attribute_casing(attr.node.name)}${attr.stringify()}`
open += ` ${fix_attribute_casing(attr.node.name)}${attr.stringify()}`;
});
if (is_void(wrapper.node.name)) return open + '>';
@ -798,7 +803,8 @@ export default class ElementWrapper extends Wrapper {
add_classes(block: Block) {
this.node.classes.forEach(class_directive => {
const { expression, name } = class_directive;
let snippet, dependencies;
let snippet;
let dependencies;
if (expression) {
snippet = expression.render(block);
dependencies = expression.dependencies;

@ -14,7 +14,6 @@ import Text from './Text';
import Title from './Title';
import Window from './Window';
import { INode } from '../../nodes/interfaces';
import TextWrapper from './Text';
import Renderer from '../Renderer';
import Block from '../Block';
import { trim_start, trim_end } from '../../../utils/trim';
@ -64,7 +63,7 @@ export default class FragmentWrapper {
const child = nodes[i];
if (!child.type) {
throw new Error(`missing type`)
throw new Error(`missing type`);
}
if (!(child.type in wrappers)) {
@ -102,7 +101,7 @@ export default class FragmentWrapper {
continue;
}
const wrapper = new TextWrapper(renderer, block, parent, child, data);
const wrapper = new Text(renderer, block, parent, child, data);
if (wrapper.skip) continue;
this.nodes.unshift(wrapper);
@ -120,7 +119,7 @@ export default class FragmentWrapper {
}
if (strip_whitespace) {
const first = this.nodes[0] as TextWrapper;
const first = this.nodes[0] as Text;
if (first && first.node.type === 'Text') {
first.data = trim_start(first.data);

@ -29,7 +29,7 @@ export default class HeadWrapper extends Wrapper {
);
}
render(block: Block, parent_node: string, parent_nodes: string) {
render(block: Block, _parent_node: string, _parent_nodes: string) {
this.fragment.render(block, 'document.head', 'nodes');
}
}

@ -201,7 +201,7 @@ export default class IfBlockWrapper extends Wrapper {
render_compound(
block: Block,
parent_node: string,
parent_nodes: string,
_parent_nodes: string,
dynamic,
{ name, anchor, has_else, if_name, has_transitions },
detaching
@ -210,6 +210,7 @@ export default class IfBlockWrapper extends Wrapper {
const current_block_type = block.get_unique_name(`current_block_type`);
const current_block_type_and = has_else ? '' : `${current_block_type} && `;
/* eslint-disable @typescript-eslint/indent,indent */
block.builders.init.add_block(deindent`
function ${select_block_type}(ctx) {
${this.branches
@ -217,6 +218,7 @@ export default class IfBlockWrapper extends Wrapper {
.join('\n')}
}
`);
/* eslint-enable @typescript-eslint/indent,indent */
block.builders.init.add_block(deindent`
var ${current_block_type} = ${select_block_type}(ctx);
@ -265,7 +267,7 @@ export default class IfBlockWrapper extends Wrapper {
render_compound_with_outros(
block: Block,
parent_node: string,
parent_nodes: string,
_parent_nodes: string,
dynamic,
{ name, anchor, has_else, has_transitions },
detaching
@ -283,6 +285,7 @@ export default class IfBlockWrapper extends Wrapper {
block.add_variable(current_block_type_index);
block.add_variable(name);
/* eslint-disable @typescript-eslint/indent,indent */
block.builders.init.add_block(deindent`
var ${if_block_creators} = [
${this.branches.map(branch => branch.block.name).join(',\n')}
@ -297,6 +300,7 @@ export default class IfBlockWrapper extends Wrapper {
${!has_else && `return -1;`}
}
`);
/* eslint-enable @typescript-eslint/indent,indent */
if (has_else) {
block.builders.init.add_block(deindent`
@ -386,7 +390,7 @@ export default class IfBlockWrapper extends Wrapper {
render_simple(
block: Block,
parent_node: string,
parent_nodes: string,
_parent_nodes: string,
dynamic,
{ name, anchor, if_name, has_transitions },
detaching

@ -17,7 +17,7 @@ import TemplateScope from '../../../nodes/shared/TemplateScope';
export default class InlineComponentWrapper extends Wrapper {
var: string;
slots: Map<string, { block: Block, scope: TemplateScope, fn?: string }> = new Map();
slots: Map<string, { block: Block; scope: TemplateScope; fn?: string }> = new Map();
node: InlineComponent;
fragment: FragmentWrapper;
@ -62,8 +62,8 @@ export default class InlineComponentWrapper extends Wrapper {
this.var = (
this.node.name === 'svelte:self' ? renderer.component.name :
this.node.name === 'svelte:component' ? 'switch_instance' :
sanitize(this.node.name)
this.node.name === 'svelte:component' ? 'switch_instance' :
sanitize(this.node.name)
).toLowerCase();
if (this.node.children.length) {
@ -232,14 +232,16 @@ export default class InlineComponentWrapper extends Wrapper {
.filter((attribute: Attribute) => attribute.is_dynamic)
.forEach((attribute: Attribute) => {
if (attribute.dependencies.size > 0) {
/* eslint-disable @typescript-eslint/indent,indent */
updates.push(deindent`
if (${[...attribute.dependencies]
.map(dependency => `changed.${dependency}`)
.join(' || ')}) ${name_changes}${quote_prop_if_necessary(attribute.name)} = ${attribute.get_value(block)};
`);
/* eslint-enable @typescript-eslint/indent,indent */
}
});
}
}
}
if (non_let_dependencies.length > 0) {
@ -265,7 +267,7 @@ export default class InlineComponentWrapper extends Wrapper {
// bind:x={y} — we can't just do `y = x`, we need to
// to `array[index] = x;
const { name } = binding.expression.node;
const { object, property, snippet } = block.bindings.get(name);
const { snippet } = block.bindings.get(name);
lhs = snippet;
// TODO we need to invalidate... something
@ -361,7 +363,9 @@ export default class InlineComponentWrapper extends Wrapper {
});
const munged_handlers = this.node.handlers.map(handler => {
const snippet = handler.render(block);
let snippet = handler.render(block);
if (handler.modifiers.has('once')) snippet = `@once(${snippet})`;
return `${name}.$on("${handler.name}", ${snippet});`;
});

@ -78,7 +78,7 @@ export default class SlotWrapper extends Wrapper {
});
if (attribute.dependencies.size > 0) {
changes_props.push(`${attribute.name}: ${[...attribute.dependencies].join(' || ')}`)
changes_props.push(`${attribute.name}: ${[...attribute.dependencies].join(' || ')}`);
}
});
@ -101,7 +101,7 @@ export default class SlotWrapper extends Wrapper {
const ${slot} = @create_slot(${slot_definition}, ctx, ${get_slot_context});
`);
let mount_before = block.builders.mount.toString();
const mount_before = block.builders.mount.toString();
block.builders.create.push_condition(`!${slot}`);
block.builders.claim.push_condition(`!${slot}`);

@ -14,13 +14,13 @@ export default class TitleWrapper extends Wrapper {
block: Block,
parent: Wrapper,
node: Title,
strip_whitespace: boolean,
next_sibling: Wrapper
_strip_whitespace: boolean,
_next_sibling: Wrapper
) {
super(renderer, block, parent, node);
}
render(block: Block, parent_node: string, parent_nodes: string) {
render(block: Block, _parent_node: string, _parent_nodes: string) {
const is_dynamic = !!this.node.children.find(node => node.type !== 'Text');
if (is_dynamic) {
@ -65,13 +65,12 @@ export default class TitleWrapper extends Wrapper {
if (this.node.should_cache) block.add_variable(last);
let updater;
const init = this.node.should_cache ? `${last} = ${value}` : value;
block.builders.init.add_line(
`document.title = ${init};`
);
updater = `document.title = ${this.node.should_cache ? last : value};`;
const updater = `document.title = ${this.node.should_cache ? last : value};`;
if (all_dependencies.size) {
const dependencies = Array.from(all_dependencies);
@ -92,7 +91,10 @@ export default class TitleWrapper extends Wrapper {
);
}
} else {
const value = stringify((this.node.children[0] as Text).data);
const value = this.node.children.length > 0
? stringify((this.node.children[0] as Text).data)
: '""';
block.builders.hydrate.add_line(`document.title = ${value};`);
}
}

@ -1,6 +1,5 @@
import Renderer from '../Renderer';
import Block from '../Block';
import Node from '../../nodes/shared/Node';
import Wrapper from './shared/Wrapper';
import deindent from '../../utils/deindent';
import add_event_handlers from './shared/add_event_handlers';
@ -38,7 +37,7 @@ export default class WindowWrapper extends Wrapper {
super(renderer, block, parent, node);
}
render(block: Block, parent_node: string, parent_nodes: string) {
render(block: Block, _parent_node: string, _parent_nodes: string) {
const { renderer } = this;
const { component } = renderer;
@ -88,7 +87,7 @@ export default class WindowWrapper extends Wrapper {
bindings.scrollY && `"${bindings.scrollY}" in this._state`
].filter(Boolean).join(' || ');
const x = bindings.scrollX && `this._state.${bindings.scrollX}`;
const x = bindings.scrollX && `this._state.${bindings.scrollX}`;
const y = bindings.scrollY && `this._state.${bindings.scrollY}`;
renderer.meta_bindings.add_block(deindent`

@ -76,7 +76,7 @@ export default class Wrapper {
);
}
render(block: Block, parent_node: string, parent_nodes: string){
render(_block: Block, _parent_node: string, _parent_nodes: string) {
throw Error('Wrapper class is not renderable');
}
}

@ -10,7 +10,8 @@ export default function add_actions(
) {
actions.forEach(action => {
const { expression } = action;
let snippet, dependencies;
let snippet;
let dependencies;
if (expression) {
snippet = expression.render(block);
@ -44,4 +45,4 @@ export default function add_actions(
`if (${name} && typeof ${name}.destroy === 'function') ${name}.destroy();`
);
});
}
}

@ -16,7 +16,7 @@ import { INode } from '../nodes/interfaces';
type Handler = (node: any, renderer: Renderer, options: CompileOptions) => void;
function noop(){}
function noop() {}
const handlers: Record<string, Handler> = {
AwaitBlock,
@ -38,8 +38,8 @@ const handlers: Record<string, Handler> = {
};
export interface RenderOptions extends CompileOptions{
locate: (c: number) => { line: number; column: number; };
};
locate: (c: number) => { line: number; column: number };
}
export default class Renderer {
has_bindings = false;

@ -9,7 +9,7 @@ export default function(node: EachBlock, renderer: Renderer, options: RenderOpti
const ctx = node.index
? `([✂${start}-${end}✂], ${node.index})`
: `([✂${start}-${end}✂])`
: `([✂${start}-${end}✂])`;
const open = `\${${node.else ? `${snippet}.length ? ` : ''}@each(${snippet}, ${ctx} => \``;
renderer.append(open);

@ -1,7 +1,6 @@
import { is_void, quote_prop_if_necessary, quote_name_if_necessary } from '../../../utils/names';
import Attribute from '../../nodes/Attribute';
import Class from '../../nodes/Class';
import Node from '../../nodes/shared/Node';
import { snip } from '../../utils/snip';
import { stringify_attribute } from '../../utils/stringify_attribute';
import { get_slot_scope } from './shared/get_slot_scope';
@ -54,7 +53,12 @@ export default function(node: Element, renderer: Renderer, options: RenderOption
slot_scopes: Map<any, any>;
}) {
let opening_tag = `<${node.name}`;
let textarea_contents; // awkward special case
let node_contents; // awkward special case
const contenteditable = (
node.name !== 'textarea' &&
node.name !== 'input' &&
node.attributes.some((attribute) => attribute.name === 'contenteditable')
);
const slot = node.get_static_attribute_value('slot');
const component = node.find_nearest(/InlineComponent/);
@ -91,7 +95,7 @@ export default function(node: Element, renderer: Renderer, options: RenderOption
args.push(snip(attribute.expression));
} else {
if (attribute.name === 'value' && node.name === 'textarea') {
textarea_contents = stringify_attribute(attribute, true);
node_contents = stringify_attribute(attribute, true);
} else if (attribute.is_true) {
args.push(`{ ${quote_name_if_necessary(attribute.name)}: true }`);
} else if (
@ -113,7 +117,7 @@ export default function(node: Element, renderer: Renderer, options: RenderOption
if (attribute.type !== 'Attribute') return;
if (attribute.name === 'value' && node.name === 'textarea') {
textarea_contents = stringify_attribute(attribute, true);
node_contents = stringify_attribute(attribute, true);
} else if (attribute.is_true) {
opening_tag += ` ${attribute.name}`;
} else if (
@ -146,6 +150,17 @@ export default function(node: Element, renderer: Renderer, options: RenderOption
if (name === 'group') {
// TODO server-render group bindings
} else if (contenteditable && (name === 'text' || name === 'html')) {
const snippet = snip(expression);
if (name == 'text') {
node_contents = '${@escape(' + snippet + ')}';
} else {
// Do not escape HTML content
node_contents = '${' + snippet + '}';
}
} else if (binding.name === 'value' && node.name === 'textarea') {
const snippet = snip(expression);
node_contents='${(' + snippet + ') || ""}';
} else {
const snippet = snip(expression);
opening_tag += ' ${(v => v ? ("' + name + '" + (v === true ? "" : "=" + JSON.stringify(v))) : "")(' + snippet + ')}';
@ -160,8 +175,8 @@ export default function(node: Element, renderer: Renderer, options: RenderOption
renderer.append(opening_tag);
if (node.name === 'textarea' && textarea_contents !== undefined) {
renderer.append(textarea_contents);
if ((node.name === 'textarea' || contenteditable) && node_contents !== undefined) {
renderer.append(node_contents);
} else {
renderer.render(node.children, options);
}

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

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

@ -3,7 +3,7 @@ import Renderer, { RenderOptions } from '../Renderer';
import Text from '../../nodes/Text';
import Element from '../../nodes/Element';
export default function(node: Text, renderer: Renderer, options: RenderOptions) {
export default function(node: Text, renderer: Renderer, _options: RenderOptions) {
let text = node.data;
if (
!node.parent ||

@ -1,6 +1,7 @@
import * as assert from 'assert';
import deindent from './deindent';
import CodeBuilder from './CodeBuilder';
import get_name_from_filename from './get_name_from_filename';
describe('deindent', () => {
it('deindents a simple string', () => {
@ -164,3 +165,17 @@ describe('CodeBuilder', () => {
);
});
});
describe('get_name_from_filename', () => {
it('uses the basename', () => {
assert.equal(get_name_from_filename('path/to/Widget.svelte'), 'Widget');
});
it('uses the directory name, if basename is index', () => {
assert.equal(get_name_from_filename('path/to/Widget/index.svelte'), 'Widget');
});
it('handles unusual filenames', () => {
assert.equal(get_name_from_filename('path/to/[...parts].svelte'), 'Parts');
});
});

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

@ -0,0 +1,26 @@
export default function get_name_from_filename(filename: string) {
if (!filename) return null;
// eslint-disable-next-line no-useless-escape
const parts = filename.split(/[\/\\]/);
if (parts.length > 1) {
const index_match = parts[parts.length - 1].match(/^index(\.\w+)/);
if (index_match) {
parts.pop();
parts[parts.length - 1] += index_match[1];
}
}
const base = parts.pop()
.replace(/\.[^.]+$/, "")
.replace(/[^a-zA-Z_$0-9]+/g, '_')
.replace(/^_/, '')
.replace(/_$/, '')
.replace(/^(\d)/, '_$1');
if (!base) {
throw new Error(`Could not derive component name from file ${filename}`);
}
return base[0].toUpperCase() + base.slice(1);
}

@ -50,10 +50,10 @@ export function create_scopes(expression: Node) {
if (map.has(node)) {
scope = scope.parent;
}
},
}
});
scope.declarations.forEach((node, name) => {
scope.declarations.forEach((_node, name) => {
globals.delete(name);
});

@ -7,33 +7,33 @@ interface BaseNode {
}
export interface Text extends BaseNode {
type: 'Text',
type: 'Text';
data: string;
}
export interface MustacheTag extends BaseNode {
type: 'MustacheTag',
type: 'MustacheTag';
expression: Node;
}
export type DirectiveType = 'Action'
| 'Animation'
| 'Binding'
| 'Class'
| 'EventHandler'
| 'Let'
| 'Ref'
| 'Transition';
| 'Animation'
| 'Binding'
| 'Class'
| 'EventHandler'
| 'Let'
| 'Ref'
| 'Transition';
interface BaseDirective extends BaseNode {
type: DirectiveType;
expression: null|Node;
name: string;
modifiers: string[]
modifiers: string[];
}
export interface Transition extends BaseDirective{
type: 'Transition',
type: 'Transition';
intro: boolean;
outro: boolean;
}
@ -41,17 +41,17 @@ export interface Transition extends BaseDirective{
export type Directive = BaseDirective | Transition;
export type Node = Text
| MustacheTag
| BaseNode
| Directive
| Transition;
| MustacheTag
| BaseNode
| Directive
| Transition;
export interface Parser {
readonly template: string;
readonly filename?: string;
index: number;
stack: Array<Node>;
stack: Node[];
html: Node;
css: Node;
@ -68,7 +68,7 @@ export interface Ast {
export interface Warning {
start?: { line: number; column: number; pos?: number };
end?: { line: number; column: number; };
end?: { line: number; column: number };
pos?: number;
code: string;
message: string;
@ -114,7 +114,7 @@ export interface Visitor {
export interface AppendTarget {
slots: Record<string, string>;
slot_stack: string[]
slot_stack: string[];
}
export interface Var {

@ -14,7 +14,7 @@ export class Parser {
readonly customElement: boolean;
index = 0;
stack: Array<Node> = [];
stack: Node[] = [];
html: Node;
css: Node[] = [];
@ -89,7 +89,7 @@ export class Parser {
}, err.pos);
}
error({ code, message }: { code: string, message: string }, index = this.index) {
error({ code, message }: { code: string; message: string }, index = this.index) {
error(message, {
name: 'ParseError',
code,

@ -1,13 +1,13 @@
import { Parser } from '../index';
type Identifier = {
interface Identifier {
start: number;
end: number;
type: 'Identifier';
name: string;
};
}
type Property = {
interface Property {
start: number;
end: number;
type: 'Property';
@ -15,9 +15,9 @@ type Property = {
shorthand: boolean;
key: Identifier;
value: Context;
};
}
type Context = {
interface Context {
start: number;
end: number;
type: 'Identifier' | 'ArrayPattern' | 'ObjectPattern' | 'RestIdentifier';
@ -91,7 +91,7 @@ export default function read_context(parser: Parser) {
end: parser.index,
type: 'Identifier',
name
}
};
const property: Property = {
start,
end: parser.index,
@ -100,7 +100,7 @@ export default function read_context(parser: Parser) {
shorthand: true,
key,
value: key
}
};
context.properties.push(property);

@ -12,6 +12,7 @@ export default function read_expression(parser: Parser): Node {
const end = start + name.length;
if (literals.has(name)) {
// eslint-disable-next-line @typescript-eslint/no-object-literal-type-assertion
return {
type: 'Literal',
start,
@ -21,6 +22,7 @@ export default function read_expression(parser: Parser): Node {
} as SimpleLiteral;
}
// eslint-disable-next-line @typescript-eslint/no-object-literal-type-assertion
return {
type: 'Identifier',
start,

@ -295,7 +295,7 @@ export default function mustache(parser: Parser) {
}
}
let await_block_shorthand = type === 'AwaitBlock' && parser.eat('then');
const await_block_shorthand = type === 'AwaitBlock' && parser.eat('then');
if (await_block_shorthand) {
parser.require_whitespace();
block.value = parser.read_identifier();

@ -8,6 +8,7 @@ import { Directive, DirectiveType, Node, Text } from '../../interfaces';
import fuzzymatch from '../../utils/fuzzymatch';
import list from '../../utils/list';
// eslint-disable-next-line no-useless-escape
const valid_tag_name = /^\!?[a-zA-Z]{1,}:?[a-zA-Z0-9\-]*/;
const meta_tags = new Map([
@ -36,7 +37,9 @@ const specials = new Map([
],
]);
// eslint-disable-next-line no-useless-escape
const SELF = /^svelte:self(?=[\s\/>])/;
// eslint-disable-next-line no-useless-escape
const COMPONENT = /^svelte:component(?=[\s\/>])/;
// based on http://developers.whatwg.org/syntax.html#syntax-tag-omission
@ -358,7 +361,8 @@ function read_attribute(parser: Parser, unique_names: Set<string>) {
}
}
let name = parser.read_until(/[\s=\/>"']/);
// eslint-disable-next-line no-useless-escape
const name = parser.read_until(/[\s=\/>"']/);
if (!name) return null;
let end = parser.index;
@ -381,6 +385,7 @@ function read_attribute(parser: Parser, unique_names: Set<string>) {
let value: any[] | true = true;
if (parser.eat('=')) {
parser.allow_whitespace();
value = read_attribute_value(parser);
end = parser.index;
} else if (parser.match_regex(/["']/)) {
@ -401,7 +406,7 @@ function read_attribute(parser: Parser, unique_names: Set<string>) {
}
if (value[0]) {
if ((value as Array<any>).length > 1 || value[0].type === 'Text') {
if ((value as any[]).length > 1 || value[0].type === 'Text') {
parser.error({
code: `invalid-directive-value`,
message: `Directive value must be a JavaScript expression enclosed in curly braces`
@ -445,7 +450,7 @@ function read_attribute(parser: Parser, unique_names: Set<string>) {
};
}
function get_directive_type(name: string):DirectiveType {
function get_directive_type(name: string): DirectiveType {
if (name === 'use') return 'Action';
if (name === 'animate') return 'Animation';
if (name === 'bind') return 'Binding';

@ -14,7 +14,7 @@ export default function text(parser: Parser) {
data += parser.template[parser.index++];
}
let node = {
const node = {
start,
end: parser.index,
type: 'Text',

@ -2,18 +2,18 @@ import { SourceMap } from 'magic-string';
export interface PreprocessorGroup {
markup?: (options: {
content: string,
filename: string
}) => { code: string, map?: SourceMap | string, dependencies?: string[] };
content: string;
filename: string;
}) => { code: string; map?: SourceMap | string; dependencies?: string[] };
style?: Preprocessor;
script?: Preprocessor;
}
export type Preprocessor = (options: {
content: string,
attributes: Record<string, string | boolean>,
filename?: string
}) => { code: string, map?: SourceMap | string, dependencies?: string[] };
content: string;
attributes: Record<string, string | boolean>;
filename?: string;
}) => { code: string; map?: SourceMap | string; dependencies?: string[] };
interface Processed {
code: string;
@ -43,16 +43,16 @@ interface Replacement {
}
async function replace_async(str: string, re: RegExp, func: (...any) => Promise<string>) {
const replacements: Promise<Replacement>[] = [];
const replacements: Array<Promise<Replacement>> = [];
str.replace(re, (...args) => {
replacements.push(
func(...args).then(
res =>
<Replacement>({
res => // eslint-disable-next-line @typescript-eslint/no-object-literal-type-assertion
({
offset: args[args.length - 2],
length: args[0].length,
replacement: res,
})
}) as Replacement
)
);
return '';

@ -0,0 +1,11 @@
{
"extends": "../../tsconfig.json",
"include": ["."],
"compilerOptions": {
"lib": ["es2017", "webworker"]
// TODO: remove mocha types from the whole project
// "types": ["node", "estree"]
}
}

@ -3,8 +3,8 @@ import get_code_frame from './get_code_frame';
class CompileError extends Error {
code: string;
start: { line: number, column: number };
end: { line: number, column: number };
start: { line: number; column: number };
end: { line: number; column: number };
pos: number;
filename: string;
frame: string;
@ -15,12 +15,12 @@ class CompileError extends Error {
}
export default function error(message: string, props: {
name: string,
code: string,
source: string,
filename: string,
start: number,
end?: number
name: string;
code: string;
source: string;
filename: string;
start: number;
end?: number;
}) {
const error = new CompileError(message);
error.name = props.name;

@ -2,9 +2,9 @@
// Reproduced under MIT License https://github.com/acornjs/acorn/blob/master/LICENSE
export default function full_char_code_at(str: string, i: number): number {
let code = str.charCodeAt(i)
const code = str.charCodeAt(i);
if (code <= 0xd7ff || code >= 0xe000) return code;
let next = str.charCodeAt(i + 1);
const next = str.charCodeAt(i + 1);
return (code << 10) + next - 0x35fdc00;
}

@ -144,7 +144,7 @@ class FuzzySet {
items[index] = [vector_normal, normalized_value];
this.items[gram_size] = items;
this.exact_set[normalized_value] = value;
};
}
get(value: string) {
const normalized_value = value.toLowerCase();
@ -232,5 +232,5 @@ class FuzzySet {
}
return new_results;
};
}
}
}

@ -3,20 +3,20 @@ import { is_function } from 'svelte/internal';
// todo: same as Transition, should it be shared?
export interface AnimationConfig {
delay?: number,
duration?: number,
easing?: (t: number) => number,
css?: (t: number, u: number) => string,
tick?: (t: number, u: number) => void
delay?: number;
duration?: number;
easing?: (t: number) => number;
css?: (t: number, u: number) => string;
tick?: (t: number, u: number) => void;
}
interface FlipParams {
delay: number;
duration: number | ((len: number) => number);
easing: (t: number) => number,
easing: (t: number) => number;
}
export function flip(node: Element, animation: { from: DOMRect, to: DOMRect }, params: FlipParams): AnimationConfig {
export function flip(node: Element, animation: { from: DOMRect; to: DOMRect }, params: FlipParams): AnimationConfig {
const style = getComputedStyle(node);
const transform = style.transform === 'none' ? '' : style.transform;
@ -35,6 +35,6 @@ export function flip(node: Element, animation: { from: DOMRect, to: DOMRect }, p
delay,
duration: is_function(duration) ? duration(d) : duration,
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);`
};
}

@ -3,6 +3,7 @@ import { current_component, set_current_component } from './lifecycle';
import { blank_object, is_function, run, run_all, noop } from './utils';
import { children } from './dom';
// eslint-disable-next-line @typescript-eslint/class-name-casing
interface T$$ {
dirty: null;
ctx: null|any;
@ -16,7 +17,7 @@ interface T$$ {
before_render: any[];
context: Map<any, any>;
on_mount: any[];
on_destroy: any[]
on_destroy: any[];
}
export function bind(component, name, callback) {
@ -115,8 +116,10 @@ export function init(component, options, instance, create_fragment, not_equal, p
if (options.target) {
if (options.hydrate) {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
$$.fragment!.l(children(options.target));
} else {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
$$.fragment!.c();
}
@ -145,7 +148,7 @@ if (typeof HTMLElement !== 'undefined') {
}
}
attributeChangedCallback(attr, oldValue, newValue) {
attributeChangedCallback(attr, _oldValue, newValue) {
this[attr] = newValue;
}

@ -1,4 +1,5 @@
import { identity as linear, noop, now } from './utils';
import { identity as linear, noop } from './utils';
import { now } from './environment';
import { loop } from './loop';
import { create_rule, delete_rule } from './style_manager';
import { AnimationConfig } from '../animate';
@ -7,7 +8,7 @@ import { AnimationConfig } from '../animate';
//todo: documentation says it is DOMRect, but in IE it would be ClientRect
type PositionRect = DOMRect|ClientRect;
type AnimationFn = (node: Element, { from, to }: { from: PositionRect, to: PositionRect }, params: any) => AnimationConfig;
type AnimationFn = (node: Element, { from, to }: { from: PositionRect; to: PositionRect }, params: any) => AnimationConfig;
export function create_animation(node: Element & ElementCSSInlineStyle, from: PositionRect, fn: AnimationFn, params) {
if (!from) return noop;

@ -1,8 +1,8 @@
export function append(target:Node, node:Node) {
export function append(target: Node, node: Node) {
target.appendChild(node);
}
export function insert(target: Node, node: Node, anchor?:Node) {
export function insert(target: Node, node: Node, anchor?: Node) {
target.insertBefore(node, anchor || null);
}
@ -16,13 +16,13 @@ export function detach_between(before: Node, after: Node) {
}
}
export function detach_before(after:Node) {
export function detach_before(after: Node) {
while (after.previousSibling) {
after.parentNode.removeChild(after.previousSibling);
}
}
export function detach_after(before:Node) {
export function detach_after(before: Node) {
while (before.nextSibling) {
before.parentNode.removeChild(before.nextSibling);
}
@ -38,7 +38,8 @@ export function element<K extends keyof HTMLElementTagNameMap>(name: K) {
return document.createElement<K>(name);
}
export function object_without_properties<T,K extends keyof T>(obj:T, exclude: K[]) {
export function object_without_properties<T, K extends keyof T>(obj: T, exclude: K[]) {
// eslint-disable-next-line @typescript-eslint/no-object-literal-type-assertion
const target = {} as Pick<T, Exclude<keyof T, K>>;
for (const k in obj) {
if (
@ -53,11 +54,11 @@ export function object_without_properties<T,K extends keyof T>(obj:T, exclude: K
return target;
}
export function svg_element<K extends keyof SVGElementTagNameMap>(name:K):SVGElement {
export function svg_element<K extends keyof SVGElementTagNameMap>(name: K): SVGElement {
return document.createElementNS<K>('http://www.w3.org/2000/svg', name);
}
export function text(data:string) {
export function text(data: string) {
return document.createTextNode(data);
}
@ -95,7 +96,7 @@ export function attr(node: Element, attribute: string, value?: string) {
else node.setAttribute(attribute, value);
}
export function set_attributes(node: Element & ElementCSSInlineStyle, attributes: { [x: string]: string; }) {
export function set_attributes(node: Element & ElementCSSInlineStyle, attributes: { [x: string]: string }) {
for (const key in attributes) {
if (key === 'style') {
node.style.cssText = attributes[key];

@ -0,0 +1,16 @@
export const is_client = typeof window !== 'undefined';
export let now: () => number = is_client
? () => window.performance.now()
: () => Date.now();
export let raf = cb => requestAnimationFrame(cb);
// used internally for testing
export function set_now(fn) {
now = fn;
}
export function set_raf(fn) {
raf = fn;
}

@ -1,6 +1,7 @@
export * from './animations';
export * from './await-block';
export * from './dom';
export * from './environment';
export * from './keyed-each';
export * from './lifecycle';
export * from './loop';

@ -1,4 +1,4 @@
import { now, raf } from './utils';
import { now, raf } from './environment';
export interface Task { abort(): void; promise: Promise<void> }
@ -23,7 +23,7 @@ export function clear_loops() {
running = false;
}
export function loop(fn: (number)=>void): Task {
export function loop(fn: (number) => void): Task {
let task;
if (!running) {

@ -1,5 +1,5 @@
import { element } from './dom';
import { raf } from './utils';
import { raf } from './environment';
let stylesheet;
let active = 0;

@ -1,4 +1,5 @@
import { identity as linear, is_function, noop, now, run_all } from './utils';
import { identity as linear, is_function, noop, run_all } from './utils';
import { now } from "./environment";
import { loop } from './loop';
import { create_rule, delete_rule } from './style_manager';
import { custom_event } from './dom';
@ -71,14 +72,14 @@ export function create_in_transition(node: Element & ElementCSSInlineStyle, fn:
if (task) task.abort();
running = true;
add_render_callback(() => dispatch(node, true, 'start'));
add_render_callback(() => dispatch(node, true, 'start'));
task = loop(now => {
if (running) {
if (now >= end_time) {
tick(1, 0);
dispatch(node, true, 'end');
dispatch(node, true, 'end');
cleanup();
return running = false;
@ -146,14 +147,14 @@ export function create_out_transition(node: Element & ElementCSSInlineStyle, fn:
const start_time = now() + delay;
const end_time = start_time + duration;
add_render_callback(() => dispatch(node, false, 'start'));
add_render_callback(() => dispatch(node, false, 'start'));
loop(now => {
if (running) {
if (now >= end_time) {
tick(0, 1);
dispatch(node, false, 'end');
dispatch(node, false, 'end');
if (!--group.remaining) {
// this will result in `end()` being called,

@ -2,7 +2,7 @@ export function noop() {}
export const identity = x => x;
export function assign<T, S>(tar:T, src:S): T & S {
export function assign<T, S>(tar: T, src: S): T & S {
// @ts-ignore
for (const k in src) tar[k] = src[k];
return tar as T & S;
@ -81,19 +81,11 @@ export function exclude_internal_props(props) {
return result;
}
const is_client = typeof window !== 'undefined';
export let now: () => number = is_client
? () => window.performance.now()
: () => Date.now();
export let raf = is_client ? requestAnimationFrame : noop;
// used internally for testing
export function set_now(fn) {
now = fn;
}
export function set_raf(fn) {
raf = fn;
export function once(fn) {
let ran = false;
return function(this: any, ...args) {
if (ran) return;
ran = true;
fn.call(this, ...args);
};
}

@ -6,10 +6,10 @@ interface TickContext<T> {
inv_mass: number;
dt: number;
opts: Spring<T>;
settled: boolean
settled: boolean;
}
function tick_spring<T>(ctx: TickContext<T>, last_value: T, current_value: T, target_value: T):T {
function tick_spring<T>(ctx: TickContext<T>, last_value: T, current_value: T, target_value: T): T {
if (typeof current_value === 'number' || is_date(current_value)) {
// @ts-ignore
const delta = target_value - current_value;
@ -45,9 +45,9 @@ function tick_spring<T>(ctx: TickContext<T>, last_value: T, current_value: T, ta
}
interface SpringOpts {
stiffness?: number,
damping?: number,
precision?: number,
stiffness?: number;
damping?: number;
precision?: number;
}
interface SpringUpdateOpts {
@ -62,7 +62,7 @@ interface Spring<T> extends Readable<T>{
update: (fn: Updater<T>, opts?: SpringUpdateOpts) => Promise<void>;
precision: number;
damping: number;
stiffness: number
stiffness: number;
}
export function spring<T=any>(value: T, opts: SpringOpts = {}): Spring<T> {
@ -72,13 +72,14 @@ export function spring<T=any>(value: T, opts: SpringOpts = {}): Spring<T> {
let last_time: number;
let task: Task;
let current_token: object;
let last_value:T = value;
let target_value:T = value;
let last_value: T = value;
let target_value: T = value;
let inv_mass = 1;
let inv_mass_recovery_rate = 0;
let cancel_task = false;
/* eslint-disable @typescript-eslint/no-use-before-define */
function set(new_value: T, opts: SpringUpdateOpts={}): Promise<void> {
target_value = new_value;
const token = current_token = {};
@ -133,15 +134,16 @@ export function spring<T=any>(value: T, opts: SpringOpts = {}): Spring<T> {
});
});
}
/* eslint-enable @typescript-eslint/no-use-before-define */
const spring = {
const spring: Spring<T> = {
set,
update: (fn, opts:SpringUpdateOpts) => set(fn(target_value, value), opts),
update: (fn, opts: SpringUpdateOpts) => set(fn(target_value, value), opts),
subscribe: store.subscribe,
stiffness,
damping,
precision
} as Spring<T>;
};
return spring;
}

@ -56,9 +56,9 @@ function get_interpolator(a, b) {
interface Options<T> {
delay?: number;
duration?: number | ((from: T, to: T) => number)
duration?: number | ((from: T, to: T) => number);
easing?: (t: number) => number;
interpolate?: (a: T, b: T) => (t: number) => T
interpolate?: (a: T, b: T) => (t: number) => T;
}
type Updater<T> = (target_value: T, value: T) => T;
@ -69,7 +69,7 @@ interface Tweened<T> extends Readable<T> {
update(updater: Updater<T>, opts: Options<T>): Promise<void>;
}
export function tweened<T>(value: T, defaults: Options<T> = {}):Tweened<T> {
export function tweened<T>(value: T, defaults: Options<T> = {}): Tweened<T> {
const store = writable(value);
let task: Task;
@ -122,7 +122,7 @@ export function tweened<T>(value: T, defaults: Options<T> = {}):Tweened<T> {
return {
set,
update: (fn, opts:Options<T>) => set(fn(target_value, value), opts),
update: (fn, opts: Options<T>) => set(fn(target_value, value), opts),
subscribe: store.subscribe
};
}

@ -186,7 +186,7 @@ export function derived<T, S extends Stores>(
unsubscribe();
};
}
}
};
}
/**
@ -197,4 +197,4 @@ export function get<T>(store: Readable<T>): T {
let value: T | undefined;
store.subscribe((_: T) => value = _)();
return value as T;
}
}

@ -2,11 +2,11 @@ import { cubicOut, cubicInOut } from 'svelte/easing';
import { assign, is_function } from 'svelte/internal';
export interface TransitionConfig {
delay?: number,
duration?: number,
easing?: (t: number) => number,
css?: (t: number, u: number) => string,
tick?: (t: number, u: number) => void
delay?: number;
duration?: number;
easing?: (t: number) => number;
css?: (t: number, u: number) => string;
tick?: (t: number, u: number) => void;
}
interface FadeParams {
@ -30,7 +30,7 @@ export function fade(node: Element, {
interface FlyParams {
delay: number;
duration: number;
easing: (t: number)=>number,
easing: (t: number) => number;
x: number;
y: number;
opacity: number;
@ -63,7 +63,7 @@ export function fly(node: Element, {
interface SlideParams {
delay: number;
duration: number;
easing: (t: number)=>number,
easing: (t: number) => number;
}
export function slide(node: Element, {
@ -101,7 +101,7 @@ export function slide(node: Element, {
interface ScaleParams {
delay: number;
duration: number;
easing: (t: number)=>number,
easing: (t: number) => number;
start: number;
opacity: number;
}
@ -124,7 +124,7 @@ export function scale(node: Element, {
delay,
duration,
easing,
css: (t, u) => `
css: (_t, u) => `
transform: ${transform} scale(${1 - (sd * u)});
opacity: ${target_opacity - (od * u)}
`
@ -135,7 +135,7 @@ interface DrawParams {
delay: number;
speed: number;
duration: number | ((len: number) => number);
easing: (t: number) => number,
easing: (t: number) => number;
}
export function draw(node: SVGElement & { getTotalLength(): number }, {
@ -167,18 +167,18 @@ export function draw(node: SVGElement & { getTotalLength(): number }, {
interface CrossfadeParams {
delay: number;
duration: number | ((len: number) => number);
easing: (t: number) => number,
easing: (t: number) => number;
}
type ClientRectMap = Map<any, { rect: ClientRect }>;
export function crossfade({ fallback, ...defaults }: CrossfadeParams & {
fallback: (node: Element, params: CrossfadeParams, intro: boolean)=> TransitionConfig
fallback: (node: Element, params: CrossfadeParams, intro: boolean) => TransitionConfig;
}) {
const to_receive: ClientRectMap = new Map();
const to_send: ClientRectMap = new Map();
function crossfade(from: ClientRect, node: Element, params: CrossfadeParams):TransitionConfig {
function crossfade(from: ClientRect, node: Element, params: CrossfadeParams): TransitionConfig {
const {
delay = 0,
duration = d => Math.sqrt(d) * 30,

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

Loading…
Cancel
Save