pull/4742/head
pushkine 5 years ago
parent a26a75eba6
commit 0d9d3c53d1

1
.gitignore vendored

@ -9,6 +9,7 @@ node_modules
/compiler.*js
/index.*js
/internal
/dev
/store
/easing
/motion

@ -56,41 +56,41 @@
},
"homepage": "https://github.com/sveltejs/svelte#README",
"devDependencies": {
"@rollup/plugin-commonjs": "^11.0.0",
"@rollup/plugin-json": "^4.0.1",
"@rollup/plugin-node-resolve": "^6.0.0",
"@rollup/plugin-replace": "^2.3.0",
"@rollup/plugin-commonjs": "^11.1.0",
"@rollup/plugin-json": "^4.0.3",
"@rollup/plugin-node-resolve": "^7.1.3",
"@rollup/plugin-replace": "^2.3.2",
"@rollup/plugin-sucrase": "^3.0.0",
"@rollup/plugin-typescript": "^2.0.1",
"@rollup/plugin-virtual": "^2.0.0",
"@types/mocha": "^5.2.7",
"@types/node": "^8.10.53",
"@typescript-eslint/eslint-plugin": "^1.13.0",
"@typescript-eslint/parser": "^2.1.0",
"acorn": "^7.1.0",
"agadoo": "^1.1.0",
"c8": "^5.0.1",
"@rollup/plugin-typescript": "^4.1.1",
"@rollup/plugin-virtual": "^2.0.1",
"@types/mocha": "^7.0.2",
"@types/node": "^13.13.4",
"@typescript-eslint/eslint-plugin": "^2.30.0",
"@typescript-eslint/parser": "^2.30.0",
"acorn": "^7.1.1",
"agadoo": "^2.0.0",
"c8": "^7.1.1",
"code-red": "0.1.1",
"codecov": "^3.5.0",
"css-tree": "1.0.0-alpha22",
"eslint": "^6.3.0",
"eslint-plugin-import": "^2.18.2",
"codecov": "^3.6.5",
"css-tree": "1.0.0-alpha39",
"eslint": "^6.8.0",
"eslint-plugin-import": "^2.20.2",
"eslint-plugin-svelte3": "^2.7.3",
"estree-walker": "^1.0.0",
"estree-walker": "^2.0.1",
"is-reference": "^1.1.4",
"jsdom": "^15.1.1",
"jsdom": "^16.2.2",
"kleur": "^3.0.3",
"locate-character": "^2.0.5",
"magic-string": "^0.25.3",
"mocha": "^6.2.0",
"periscopic": "^2.0.1",
"puppeteer": "^1.19.0",
"rollup": "^1.27.14",
"magic-string": "^0.25.7",
"mocha": "^7.1.2",
"periscopic": "^2.0.2",
"puppeteer": "^3.0.2",
"rollup": "^2.7.6",
"source-map": "^0.7.3",
"source-map-support": "^0.5.13",
"source-map-support": "^0.5.19",
"tiny-glob": "^0.2.6",
"tslib": "^1.10.0",
"typescript": "^3.5.3"
"tslib": "^1.11.1",
"typescript": "^3.8.3"
},
"nyc": {
"include": [

@ -7,89 +7,121 @@ import sucrase from '@rollup/plugin-sucrase';
import typescript from '@rollup/plugin-typescript';
import pkg from './package.json';
const esm = { format: 'esm' };
const cjs = { format: 'cjs' };
const is_publish = !!process.env.PUBLISH;
const external = (id) => id.startsWith('svelte/');
const ts_plugin = is_publish
? typescript({ include: 'src/**', typescript: require('typescript') })
: sucrase({ transforms: ['typescript'] });
const external = (id) => id.startsWith('svelte/');
const version = replace({ __VERSION__: pkg.version });
fs.writeFileSync(`./compiler.d.ts`, `export { compile, parse, preprocess, VERSION } from './types/compiler/index';`);
const output = (obj) =>
Object.keys(obj).flatMap((name) =>
[esm, cjs].map(({ format }) => ({
// documented in src/ambient.ts
const globals = (name) =>
replace({
'var __DEV__: boolean': 'var rollup_removes_this',
'__DEV__': name === 'dev',
'var __TEST__: boolean': 'var rollup_removes_this',
'__TEST__': name === 'test',
'var __VERSION__: string': 'var rollup_removes_this',
'__VERSION__': `"${pkg.version}"`,
});
/**
*
*/
function map_outputs({ output, plugins, ...rest }) {
return Object.keys(output).flatMap((name) =>
['esm', 'cjs'].map((format) => ({
external,
...rest,
output: {
format,
file: `${name !== 'default' ? name + '/' : ''}${obj[name].file}.${format === 'esm' ? 'mjs' : 'js'}`,
paths: (id) => external(id) && `${id.replace('svelte', obj[name].path)}`,
plugins: (defaults.plugins || []).concat([replace({ __DEV__: name === 'dev' })]),
file: `${name !== 'default' ? name + '/' : ''}${output[name].file}.${format === 'esm' ? 'mjs' : 'js'}`,
paths: (id) => external(id) && `${id.replace('svelte', output[name].path)}`,
},
plugins: [globals(name), ...(plugins || [])],
}))
);
}
function writeFileSync(...arr) {
arr.filter(Boolean).forEach(({ dir, content }) => fs.writeFileSync(dir, content));
}
fs.writeFileSync(`./compiler.d.ts`, `export { compile, parse, preprocess, version } from './types/compiler/index';`);
export default [
/* runtime main */
{
/* main exports */
...map_outputs({
input: `src/runtime/index.ts`,
output: output({
output: {
/**
* Production main runtime
* -> 'svelte/index.js'
*/
default: { file: 'index', path: '.' },
/**
* Development main runtime
* -> 'svelte/dev/index.js'
* redirected to by the compiler
*/
dev: { file: 'index', path: '.' },
}),
external,
plugins: [ts_plugin],
},
/* svelte/[library] */
plugins: [ts_plugin],
}),
/* 'svelte/[library]' exports */
...fs
.readdirSync('src/runtime')
.filter((dir) => fs.statSync(`src/runtime/${dir}`).isDirectory())
.map((dir) => ({
input: `src/runtime/${dir}/index.ts`,
output: output({
[dir]: { file: 'index', path: '..' },
dev: { file: dir, path: '.' },
}),
external,
.flatMap((library) =>
map_outputs({
input: `src/runtime/${library}/index.ts`,
output: {
/**
* Production runtime
* -> 'svelte/[library]/index.js'
*/
[library]: { file: 'index', path: '..' },
/**
* Development runtime
* -> 'svelte/dev/[library].js'
* Must be redirected to by user's bundler
*/
dev: { file: library, path: '.' },
},
plugins: [
version,
ts_plugin,
{
writeBundle(bundle) {
writeFileSync(
dir === 'internal' &&
/* gives internal function names to the compiler */
library === 'internal' &&
bundle['index.mjs'] && {
dir: `src/compiler/compile/internal_exports.ts`,
content: `
// This file is automatically generated
export default new Set(${JSON.stringify(bundle['index.mjs'].exports)});`,
},
/* adds package.json to every svelte/[library] */
{
dir: `${dir}/package.json`,
dir: `${library}/package.json`,
content: `{
"main": "./index",
"module": "./index.mjs",
"types": "./index.d.ts"
}`,
},
/* exports types */
{
dir: `${dir}/index.d.ts`,
content: `export * from '../types/runtime/${dir}/index';`,
dir: `${library}/index.d.ts`,
content: `export * from '../types/runtime/${library}/index';`,
}
);
},
},
],
})),
})
),
/* compiler.js */
{
input: 'src/compiler/index.ts',
plugins: [version, resolve(), commonjs({ include: ['node_modules/**'] }), json(), ts_plugin],
plugins: [globals('compiler'), resolve(), commonjs({ include: ['node_modules/**'] }), json(), ts_plugin],
output: {
file: 'compiler.js',
format: is_publish ? 'umd' : 'cjs',

@ -0,0 +1,55 @@
/**
* __DEV__
*
* Used in src/runtime
*
* Bundles dev runtime to svelte/dev/[library].[m]js
* the compiler rewrites its own 'svelte' imports to 'svelte/dev' automatically
*
*/
declare var __DEV__: boolean;
/**
* __VERSION__
*
* Svelte's version in package.json
* Used in src/compiler and src/runtime
*
*/
declare var __VERSION__: string;
/**
* __COMPILER_API_VERSION__
*
* Unique ID passed to the compiler
* Used to mitigate breaking changes with bundler plugins
*
* VERSIONS ( default is "3.0.0" )
*
* >3.22.0 : {
*
* The change :
* A different runtime is now used in dev mode,
* every import has to go through 'svelte/dev' instead of 'svelte'
*
* Requirement :
* In dev mode, bundler plugins must make sure that every 'svelte' import is replaced by 'svelte/dev'
*
* }
*
*/
//declare var __COMPILER_API_VERSION__: boolean;
/**
* Unique ID devtools must pass to the compiler
* Used to
*
* VERSIONS ( default is "^3.21.0" )
* - 0 (default) : compiler imports from prod runtime
* - 1 : in dev mode, compiler imports from dev runtime
*/
//declare var __DEVTOOLS_API_VERSION__: boolean;
/**
* TODO
* Bundle different runtime for tests
* instead of relying on hacks
*/
declare var __TEST__: boolean;

@ -4,12 +4,7 @@ import Stats from '../Stats';
import { globals, reserved, is_valid } from '../utils/names';
import { namespaces, valid_namespaces } from '../utils/namespaces';
import create_module from './create_module';
import {
create_scopes,
extract_names,
Scope,
extract_identifiers,
} from './utils/scope';
import { create_scopes, extract_names, Scope, extract_identifiers } from './utils/scope';
import Stylesheet from './css/Stylesheet';
import { test } from '../config';
import Fragment from './nodes/Fragment';
@ -24,7 +19,15 @@ import TemplateScope from './nodes/shared/TemplateScope';
import fuzzymatch from '../utils/fuzzymatch';
import get_object from './utils/get_object';
import Slot from './nodes/Slot';
import { Node, ImportDeclaration, Identifier, Program, ExpressionStatement, AssignmentExpression, Literal } from 'estree';
import {
Node,
ImportDeclaration,
Identifier,
Program,
ExpressionStatement,
AssignmentExpression,
Literal,
} from 'estree';
import add_to_set from './utils/add_to_set';
import check_graph_for_cycles from './utils/check_graph_for_cycles';
import { print, x, b } from 'code-red';
@ -66,8 +69,8 @@ export default class Component {
hoistable_nodes: Set<Node> = new Set();
node_for_declaration: Map<string, Node> = new Map();
partly_hoisted: Array<(Node | Node[])> = [];
fully_hoisted: Array<(Node | Node[])> = [];
partly_hoisted: Array<Node | Node[]> = [];
fully_hoisted: Array<Node | Node[]> = [];
reactive_declarations: Array<{
assignees: Set<string>;
dependencies: Set<string>;
@ -116,43 +119,29 @@ export default class Component {
html: ast.html,
css: ast.css,
instance: ast.instance && JSON.parse(JSON.stringify(ast.instance)),
module: ast.module
module: ast.module,
};
this.file =
compile_options.filename &&
(typeof process !== 'undefined'
? compile_options.filename
.replace(process.cwd(), '')
.replace(/^[/\\]/, '')
? compile_options.filename.replace(process.cwd(), '').replace(/^[/\\]/, '')
: compile_options.filename);
this.locate = getLocator(this.source, { offsetLine: 1 });
// styles
this.stylesheet = new Stylesheet(
source,
ast,
compile_options.filename,
compile_options.dev
);
this.stylesheet = new Stylesheet(source, ast, compile_options.filename, compile_options.dev);
this.stylesheet.validate(this);
this.component_options = process_component_options(
this,
this.ast.html.children
);
this.namespace =
namespaces[this.component_options.namespace] ||
this.component_options.namespace;
this.component_options = process_component_options(this, this.ast.html.children);
this.namespace = namespaces[this.component_options.namespace] || this.component_options.namespace;
if (compile_options.customElement) {
if (
this.component_options.tag === undefined &&
compile_options.tag === undefined
) {
const svelteOptions = ast.html.children.find(
child => child.name === 'svelte:options'
) || { start: 0, end: 0 };
if (this.component_options.tag === undefined && compile_options.tag === undefined) {
const svelteOptions = ast.html.children.find((child) => child.name === 'svelte:options') || {
start: 0,
end: 0,
};
this.warn(svelteOptions, {
code: 'custom-element-no-tag',
message: `No custom element 'tag' option was specified. To automatically register a custom element, specify a name with a hyphen in it, e.g. <svelte:options tag="my-thing"/>. To hide this warning, use <svelte:options tag={null}/>`,
@ -235,7 +224,10 @@ export default class Component {
const { compile_options, name } = this;
const { format = 'esm' } = compile_options;
const banner = `${this.file ? `${this.file} ` : ``}generated by Svelte v${'__VERSION__'}`;
const banner = `${this.file ? `${this.file} ` : ``}generated by Svelte v${__VERSION__}`;
const legacy_dev_runtime = compile_options.dev && compile_options.version < 3.22;
const dev_runtime = compile_options.dev && compile_options.version >= 3.22;
const program: any = { type: 'Program', body: result.js };
@ -249,11 +241,11 @@ export default class Component {
} else {
let name = node.name.slice(1);
if (compile_options.dev) {
if (internal_exports.has(`${name}_dev`)) {
name += '_dev';
} else if (internal_exports.has(`${name}Dev`)) {
name += 'Dev';
if (legacy_dev_runtime) {
if (internal_exports.has(`${name}_dev$legacy`)) {
name += '_dev$legacy';
} else if (internal_exports.has(`${name}Dev$legacy`)) {
name += 'Dev$legacy';
}
}
@ -261,23 +253,19 @@ export default class Component {
this.helpers.set(name, alias);
node.name = alias.name;
}
}
else if (node.name[0] !== '#' && !is_valid(node.name)) {
} else if (node.name[0] !== '#' && !is_valid(node.name)) {
// this hack allows x`foo.${bar}` where bar could be invalid
const literal: Literal = { type: 'Literal', value: node.name };
if (parent.type === 'Property' && key === 'key') {
parent.key = literal;
}
else if (parent.type === 'MemberExpression' && key === 'property') {
} else if (parent.type === 'MemberExpression' && key === 'property') {
parent.property = literal;
parent.computed = true;
}
}
}
}
},
});
const referenced_globals = Array.from(
@ -297,33 +285,31 @@ export default class Component {
format,
name,
banner,
compile_options.sveltePath,
compile_options.sveltePath + (dev_runtime ? '/dev' : ''),
imported_helpers,
referenced_globals,
this.imports,
this.vars
.filter(variable => variable.module && variable.export_name)
.map(variable => ({
.filter((variable) => variable.module && variable.export_name)
.map((variable) => ({
name: variable.name,
as: variable.export_name,
}))
);
css = compile_options.customElement
? { code: null, map: null }
: result.css;
css = compile_options.customElement ? { code: null, map: null } : result.css;
js = print(program, {
sourceMapSource: compile_options.filename
sourceMapSource: compile_options.filename,
});
js.map.sources = [
compile_options.filename ? get_relative_path(compile_options.outputFilename || '', compile_options.filename) : null
compile_options.filename
? get_relative_path(compile_options.outputFilename || '', compile_options.filename)
: null,
];
js.map.sourcesContent = [
this.source
];
js.map.sourcesContent = [this.source];
}
return {
@ -332,8 +318,8 @@ export default class Component {
ast: this.original_ast,
warnings: this.warnings,
vars: this.vars
.filter(v => !v.global && !v.internal)
.map(v => ({
.filter((v) => !v.global && !v.internal)
.map((v) => ({
name: v.name,
export_name: v.export_name || null,
injected: v.injected || false,
@ -378,17 +364,13 @@ export default class Component {
return (name: string): Identifier => {
if (test) name = `${name}$`;
let alias = name;
for (
let i = 1;
this.used_names.has(alias) || local_used_names.has(alias);
alias = `${name}_${i++}`
);
for (let i = 1; this.used_names.has(alias) || local_used_names.has(alias); alias = `${name}_${i++}`);
local_used_names.add(alias);
this.globally_used_names.add(alias);
return {
type: 'Identifier',
name: alias
name: alias,
};
};
}
@ -440,8 +422,7 @@ export default class Component {
end,
pos: pos.start,
filename: this.compile_options.filename,
toString: () =>
`${warning.message} (${start.line}:${start.column})\n${frame}`,
toString: () => `${warning.message} (${start.line}:${start.column})\n${frame}`,
});
}
@ -466,14 +447,17 @@ export default class Component {
}
if (node.declaration) {
if (node.declaration.type === 'VariableDeclaration') {
node.declaration.declarations.forEach(declarator => {
extract_names(declarator.id).forEach(name => {
node.declaration.declarations.forEach((declarator) => {
extract_names(declarator.id).forEach((name) => {
const variable = this.var_lookup.get(name);
variable.export_name = name;
if (variable.writable && !(variable.referenced || variable.referenced_from_script || variable.subscribable)) {
if (
variable.writable &&
!(variable.referenced || variable.referenced_from_script || variable.subscribable)
) {
this.warn(declarator, {
code: `unused-export-let`,
message: `${this.name.name} has unused export property '${name}'. If it is for external reference only, please consider using \`export const ${name}\``
message: `${this.name.name} has unused export property '${name}'. If it is for external reference only, please consider using \`export const ${name}\``,
});
}
});
@ -487,16 +471,19 @@ export default class Component {
return node.declaration;
} else {
node.specifiers.forEach(specifier => {
node.specifiers.forEach((specifier) => {
const variable = this.var_lookup.get(specifier.local.name);
if (variable) {
variable.export_name = specifier.exported.name;
if (variable.writable && !(variable.referenced || variable.referenced_from_script || variable.subscribable)) {
if (
variable.writable &&
!(variable.referenced || variable.referenced_from_script || variable.subscribable)
) {
this.warn(specifier, {
code: `unused-export-let`,
message: `${this.name.name} has unused export property '${specifier.exported.name}'. If it is for external reference only, please consider using \`export const ${specifier.exported.name}\``
message: `${this.name.name} has unused export property '${specifier.exported.name}'. If it is for external reference only, please consider using \`export const ${specifier.exported.name}\``,
});
}
}
@ -510,13 +497,12 @@ export default class Component {
extract_javascript(script) {
if (!script) return null;
return script.content.body.filter(node => {
return script.content.body.filter((node) => {
if (!node) return false;
if (this.hoistable_nodes.has(node)) return false;
if (this.reactive_declaration_nodes.has(node)) return false;
if (node.type === 'ImportDeclaration') return false;
if (node.type === 'ExportDeclaration' && node.specifiers.length > 0)
return false;
if (node.type === 'ExportDeclaration' && node.specifiers.length > 0) return false;
return true;
});
}
@ -554,7 +540,7 @@ export default class Component {
name,
module: true,
hoistable: true,
writable
writable,
});
});
@ -568,7 +554,7 @@ export default class Component {
this.add_var({
name,
global: true,
hoistable: true
hoistable: true,
});
}
});
@ -598,7 +584,7 @@ export default class Component {
if (!script) return;
// inject vars for reactive declarations
script.content.body.forEach(node => {
script.content.body.forEach((node) => {
if (node.type !== 'LabeledStatement') return;
if (node.body.type !== 'ExpressionStatement') return;
@ -606,16 +592,14 @@ export default class Component {
if (expression.type !== 'AssignmentExpression') return;
if (expression.left.type === 'MemberExpression') return;
extract_names(expression.left).forEach(name => {
extract_names(expression.left).forEach((name) => {
if (!this.var_lookup.has(name) && name[0] !== '$') {
this.injected_reactive_declaration_vars.add(name);
}
});
});
const { 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;
@ -632,7 +616,7 @@ export default class Component {
this.add_var({
name,
initialised: instance_scope.initialised_declarations.has(name),
writable
writable,
});
this.node_for_declaration.set(name, node);
@ -658,7 +642,7 @@ export default class Component {
if (name === '$' || name[1] === '$') {
this.error(node as any, {
code: 'illegal-global',
message: `${name} is an illegal variable name`
message: `${name} is an illegal variable name`,
});
}
@ -680,7 +664,7 @@ export default class Component {
this.add_var({
name,
global: true,
hoistable: true
hoistable: true,
});
}
});
@ -744,7 +728,11 @@ export default class Component {
leave(node: Node) {
// do it on leave, to prevent infinite loop
if (component.compile_options.dev && component.compile_options.loopGuardTimeout > 0) {
const to_replace_for_loop_protect = component.loop_protect(node, scope, component.compile_options.loopGuardTimeout);
const to_replace_for_loop_protect = component.loop_protect(
node,
scope,
component.compile_options.loopGuardTimeout
);
if (to_replace_for_loop_protect) {
this.replace(to_replace_for_loop_protect);
scope_updated = true;
@ -796,13 +784,9 @@ export default class Component {
const deep = assignee.type === 'MemberExpression';
names.forEach(name => {
names.forEach((name) => {
const scope_owner = scope.find_owner(name);
if (
scope_owner !== null
? scope_owner === instance_scope
: module_scope && module_scope.has(name)
) {
if (scope_owner !== null ? scope_owner === instance_scope : module_scope && module_scope.has(name)) {
const variable = component.var_lookup.get(name);
variable[deep ? 'mutated' : 'reassigned'] = true;
}
@ -827,11 +811,7 @@ export default class Component {
}
warn_on_undefined_store_value_references(node, parent, scope) {
if (
node.type === 'LabeledStatement' &&
node.label.name === '$' &&
parent.type !== 'Program'
) {
if (node.type === 'LabeledStatement' && node.label.name === '$' && parent.type !== 'Program') {
this.warn(node as any, {
code: 'non-top-level-reactive-declaration',
message: '$: has no effect outside of the top-level',
@ -849,9 +829,7 @@ export default class Component {
}
loop_protect(node, scope: Scope, timeout: number): Node | null {
if (node.type === 'WhileStatement' ||
node.type === 'ForStatement' ||
node.type === 'DoWhileStatement') {
if (node.type === 'WhileStatement' || node.type === 'ForStatement' || node.type === 'DoWhileStatement') {
const guard = this.get_unique_name('guard', scope);
this.used_names.add(guard.name);
@ -869,10 +847,7 @@ export default class Component {
return {
type: 'BlockStatement',
body: [
before[0],
node,
],
body: [before[0], node],
};
}
return null;
@ -897,11 +872,11 @@ export default class Component {
if (node.type === 'VariableDeclaration') {
if (node.kind === 'var' || scope === instance_scope) {
node.declarations.forEach(declarator => {
node.declarations.forEach((declarator) => {
if (declarator.id.type !== 'Identifier') {
const inserts = [];
extract_names(declarator.id).forEach(name => {
extract_names(declarator.id).forEach((name) => {
const variable = component.var_lookup.get(name);
if (variable.export_name) {
@ -928,15 +903,14 @@ export default class Component {
const variable = component.var_lookup.get(name);
if (variable.export_name && variable.writable) {
const insert = variable.subscribable
? get_insert(variable)
: null;
const insert = variable.subscribable ? get_insert(variable) : null;
parent[key].splice(index + 1, 0, insert);
declarator.id = {
type: 'ObjectPattern',
properties: [{
properties: [
{
type: 'Property',
method: false,
shorthand: false,
@ -947,10 +921,11 @@ export default class Component {
? {
type: 'AssignmentPattern',
left: declarator.id,
right: declarator.init
right: declarator.init,
}
: declarator.id
}]
: declarator.id,
},
],
};
declarator.init = x`$$props`;
@ -981,12 +956,7 @@ export default class Component {
// reference instance variables other than other
// hoistable functions. TODO others?
const {
hoistable_nodes,
var_lookup,
injected_reactive_declaration_vars,
imports,
} = this;
const { hoistable_nodes, var_lookup, injected_reactive_declaration_vars, imports } = this;
const top_level_function_declarations = new Map();
@ -996,7 +966,7 @@ export default class Component {
const node = body[i];
if (node.type === 'VariableDeclaration') {
const all_hoistable = node.declarations.every(d => {
const all_hoistable = node.declarations.every((d) => {
if (!d.init) return false;
if (d.init.type !== 'Literal') return false;
@ -1011,18 +981,13 @@ export default class Component {
if (v.export_name) return false;
if (this.var_lookup.get(name).reassigned) return false;
if (
this.vars.find(
variable => variable.name === name && variable.module
)
)
return false;
if (this.vars.find((variable) => variable.name === name && variable.module)) return false;
return true;
});
if (all_hoistable) {
node.declarations.forEach(d => {
node.declarations.forEach((d) => {
const variable = this.var_lookup.get((d.id as Identifier).name);
variable.hoistable = true;
});
@ -1050,7 +1015,7 @@ export default class Component {
const checked = new Set();
const walking = new Set();
const is_hoistable = fn_declaration => {
const is_hoistable = (fn_declaration) => {
if (fn_declaration.type === 'ExportNamedDeclaration') {
fn_declaration = fn_declaration.declaration;
}
@ -1090,9 +1055,7 @@ export default class Component {
if (variable.hoistable) return;
if (top_level_function_declarations.has(name)) {
const other_declaration = top_level_function_declarations.get(
name
);
const other_declaration = top_level_function_declarations.get(name);
if (walking.has(other_declaration)) {
hoistable = false;
@ -1152,7 +1115,7 @@ export default class Component {
const unsorted_reactive_declarations = [];
this.ast.instance.content.body.forEach(node => {
this.ast.instance.content.body.forEach((node) => {
if (node.type === 'LabeledStatement' && node.label.name === '$') {
this.reactive_declaration_nodes.add(node);
@ -1172,7 +1135,7 @@ export default class Component {
if (node.type === 'AssignmentExpression') {
const left = get_object(node.left);
extract_identifiers(left).forEach(node => {
extract_identifiers(left).forEach((node) => {
assignee_nodes.add(node);
assignees.add(node.name);
});
@ -1190,12 +1153,8 @@ export default class Component {
const owner = scope.find_owner(name);
const variable = component.var_lookup.get(name);
if (variable) variable.is_reactive_dependency = true;
const is_writable_or_mutated =
variable && (variable.writable || variable.mutated);
if (
(!owner || owner === component.instance_scope) &&
(name[0] === '$' || is_writable_or_mutated)
) {
const is_writable_or_mutated = variable && (variable.writable || variable.mutated);
if ((!owner || owner === component.instance_scope) && (name[0] === '$' || is_writable_or_mutated)) {
dependencies.add(name);
}
}
@ -1225,8 +1184,8 @@ export default class Component {
const lookup = new Map();
unsorted_reactive_declarations.forEach(declaration => {
declaration.assignees.forEach(name => {
unsorted_reactive_declarations.forEach((declaration) => {
declaration.assignees.forEach((name) => {
if (!lookup.has(name)) {
lookup.set(name, []);
}
@ -1237,34 +1196,35 @@ export default class Component {
});
});
const cycle = check_graph_for_cycles(unsorted_reactive_declarations.reduce((acc, declaration) => {
declaration.assignees.forEach(v => {
declaration.dependencies.forEach(w => {
const cycle = check_graph_for_cycles(
unsorted_reactive_declarations.reduce((acc, declaration) => {
declaration.assignees.forEach((v) => {
declaration.dependencies.forEach((w) => {
if (!declaration.assignees.has(w)) {
acc.push([v, w]);
}
});
});
return acc;
}, []));
}, [])
);
if (cycle && cycle.length) {
const declarationList = lookup.get(cycle[0]);
const declaration = declarationList[0];
this.error(declaration.node, {
code: 'cyclical-reactive-declaration',
message: `Cyclical dependency detected: ${cycle.join(' → ')}`
message: `Cyclical dependency detected: ${cycle.join(' → ')}`,
});
}
const add_declaration = declaration => {
const add_declaration = (declaration) => {
if (this.reactive_declarations.includes(declaration)) return;
declaration.dependencies.forEach(name => {
declaration.dependencies.forEach((name) => {
if (declaration.assignees.has(name)) return;
const earlier_declarations = lookup.get(name);
if (earlier_declarations)
earlier_declarations.forEach(add_declaration);
if (earlier_declarations) earlier_declarations.forEach(add_declaration);
});
this.reactive_declarations.push(declaration);
@ -1275,10 +1235,10 @@ export default class Component {
warn_if_undefined(name: string, node, template_scope: TemplateScope) {
if (name[0] === '$') {
if (name === '$' || name[1] === '$' && !is_reserved_keyword(name)) {
if (name === '$' || (name[1] === '$' && !is_reserved_keyword(name))) {
this.error(node, {
code: 'illegal-global',
message: `${name} is an illegal variable name`
message: `${name} is an illegal variable name`,
});
}
@ -1294,8 +1254,7 @@ export default class Component {
if (globals.has(name) && node.type !== 'InlineComponent') return;
let message = `'${name}' is not defined`;
if (!this.ast.instance)
message += `. Consider adding a <script> block with 'export let ${name}' to declare a prop`;
if (!this.ast.instance) message += `. Consider adding a <script> block with 'export let ${name}' to declare a prop`;
this.warn(node, {
code: 'missing-declaration',
@ -1325,7 +1284,7 @@ function process_component_options(component: Component, nodes) {
preserveWhitespace: !!component.compile_options.preserveWhitespace,
};
const node = nodes.find(node => node.name === 'svelte:options');
const node = nodes.find((node) => node.name === 'svelte:options');
function get_value(attribute, code, message) {
const { value } = attribute;
@ -1347,7 +1306,7 @@ function process_component_options(component: Component, nodes) {
}
if (node) {
node.attributes.forEach(attribute => {
node.attributes.forEach((attribute) => {
if (attribute.type === 'Attribute') {
const { name } = attribute;
@ -1357,8 +1316,7 @@ function process_component_options(component: Component, nodes) {
const message = `'tag' must be a string literal`;
const tag = get_value(attribute, code, message);
if (typeof tag !== 'string' && tag !== null)
component.error(attribute, { code, message });
if (typeof tag !== 'string' && tag !== null) component.error(attribute, { code, message });
if (tag && !/^[a-zA-Z][a-zA-Z0-9]*-[a-zA-Z0-9-]+$/.test(tag)) {
component.error(attribute, {
@ -1370,7 +1328,7 @@ function process_component_options(component: Component, nodes) {
if (tag && !component.compile_options.customElement) {
component.warn(attribute, {
code: 'missing-custom-element-compile-options',
message: `The 'tag' option is used when generating a custom element. Did you forget the 'customElement: true' compile option?`
message: `The 'tag' option is used when generating a custom element. Did you forget the 'customElement: true' compile option?`,
});
}
@ -1383,8 +1341,7 @@ function process_component_options(component: Component, nodes) {
const message = `The 'namespace' attribute must be a string literal representing a valid namespace`;
const ns = get_value(attribute, code, message);
if (typeof ns !== 'string')
component.error(attribute, { code, message });
if (typeof ns !== 'string') component.error(attribute, { code, message });
if (valid_namespaces.indexOf(ns) === -1) {
const match = fuzzymatch(ns, valid_namespaces);
@ -1412,8 +1369,7 @@ function process_component_options(component: Component, nodes) {
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 });
if (typeof value !== 'boolean') component.error(attribute, { code, message });
component_options[name] = value;
break;

@ -23,48 +23,51 @@ export default function create_module(
) {
const internal_path = `${sveltePath}/internal`;
helpers.sort((a, b) => (a.name < b.name) ? -1 : 1);
globals.sort((a, b) => (a.name < b.name) ? -1 : 1);
helpers.sort((a, b) => (a.name < b.name ? -1 : 1));
globals.sort((a, b) => (a.name < b.name ? -1 : 1));
if (format === 'esm') {
return esm(program, name, banner, sveltePath, internal_path, helpers, globals, imports, module_exports);
}
if (format === 'cjs') return cjs(program, name, banner, sveltePath, internal_path, helpers, globals, imports, module_exports);
if (format === 'cjs')
return cjs(program, name, banner, sveltePath, internal_path, helpers, globals, imports, module_exports);
throw new Error(`options.format is invalid (must be ${list(Object.keys(wrappers))})`);
}
function edit_source(source, sveltePath) {
return source === 'svelte' || source.startsWith('svelte/')
? source.replace('svelte', sveltePath)
: source;
return source === 'svelte' || source.startsWith('svelte/') ? source.replace('svelte', sveltePath) : source;
}
function get_internal_globals(
globals: Array<{ name: string; alias: Identifier }>,
helpers: Array<{ name: string; alias: Identifier }>
) {
return globals.length > 0 && {
return (
globals.length > 0 && {
type: 'VariableDeclaration',
kind: 'const',
declarations: [{
declarations: [
{
type: 'VariableDeclarator',
id: {
type: 'ObjectPattern',
properties: globals.map(g => ({
properties: globals.map((g) => ({
type: 'Property',
method: false,
shorthand: false,
computed: false,
key: { type: 'Identifier', name: g.name },
value: g.alias,
kind: 'init'
}))
kind: 'init',
})),
},
init: helpers.find(({ name }) => name === 'globals').alias
}]
};
init: helpers.find(({ name }) => name === 'globals').alias,
},
],
}
);
}
function esm(
@ -80,28 +83,28 @@ function esm(
) {
const import_declaration = {
type: 'ImportDeclaration',
specifiers: helpers.map(h => ({
specifiers: helpers.map((h) => ({
type: 'ImportSpecifier',
local: h.alias,
imported: { type: 'Identifier', name: h.name }
imported: { type: 'Identifier', name: h.name },
})),
source: { type: 'Literal', value: internal_path }
source: { type: 'Literal', value: internal_path },
};
const internal_globals = get_internal_globals(globals, helpers);
// edit user imports
imports.forEach(node => {
imports.forEach((node) => {
node.source.value = edit_source(node.source.value, sveltePath);
});
const exports = module_exports.length > 0 && {
type: 'ExportNamedDeclaration',
specifiers: module_exports.map(x => ({
specifiers: module_exports.map((x) => ({
type: 'Specifier',
local: { type: 'Identifier', name: x.name },
exported: { type: 'Identifier', name: x.as }
}))
exported: { type: 'Identifier', name: x.as },
})),
};
program.body = b`
@ -132,27 +135,29 @@ function cjs(
const internal_requires = {
type: 'VariableDeclaration',
kind: 'const',
declarations: [{
declarations: [
{
type: 'VariableDeclarator',
id: {
type: 'ObjectPattern',
properties: helpers.map(h => ({
properties: helpers.map((h) => ({
type: 'Property',
method: false,
shorthand: false,
computed: false,
key: { type: 'Identifier', name: h.name },
value: h.alias,
kind: 'init'
}))
kind: 'init',
})),
},
init: x`require("${internal_path}")`,
},
init: x`require("${internal_path}")`
}]
],
};
const internal_globals = get_internal_globals(globals, helpers);
const user_requires = imports.map(node => {
const user_requires = imports.map((node) => {
const init = x`require("${edit_source(node.source.value, sveltePath)}")`;
if (node.specifiers.length === 0) {
return b`${init};`;
@ -160,28 +165,33 @@ function cjs(
return {
type: 'VariableDeclaration',
kind: 'const',
declarations: [{
declarations: [
{
type: 'VariableDeclarator',
id: node.specifiers[0].type === 'ImportNamespaceSpecifier'
id:
node.specifiers[0].type === 'ImportNamespaceSpecifier'
? { type: 'Identifier', name: node.specifiers[0].local.name }
: {
type: 'ObjectPattern',
properties: node.specifiers.map(s => ({
properties: node.specifiers.map((s) => ({
type: 'Property',
method: false,
shorthand: false,
computed: false,
key: s.type === 'ImportSpecifier' ? s.imported : { type: 'Identifier', name: 'default' },
value: s.local,
kind: 'init'
}))
kind: 'init',
})),
},
init,
},
init
}]
],
};
});
const exports = module_exports.map(x => b`exports.${{ type: 'Identifier', name: x.as }} = ${{ type: 'Identifier', name: x.name }};`);
const exports = module_exports.map(
(x) => b`exports.${{ type: 'Identifier', name: x.as }} = ${{ type: 'Identifier', name: x.name }};`
);
program.body = b`
/* ${banner} */

@ -228,7 +228,7 @@ export default class Block {
}
get_contents(key?: any) {
const { dev } = this.renderer.options;
const { dev,version } = this.renderer.options;
if (this.has_outros) {
this.add_variable({ type: 'Identifier', name: '#current' });
@ -403,13 +403,13 @@ export default class Block {
${dev
? b`
const ${block} = ${return_value};
@dispatch_dev("SvelteRegisterBlock", {
${version < 3.22 && b`@dispatch_dev$legacy("SvelteRegisterBlock", {
block: ${block},
id: ${this.name || 'create_fragment'}.name,
type: "${this.type}",
source: "${this.comment ? this.comment.replace(/"/g, '\\"') : ''}",
ctx: #ctx
});
});`}
return ${block};`
: b`
return ${return_value};`

@ -529,7 +529,7 @@ export default function dom(
super(${options.dev && `options`});
${should_add_css && b`if (!@_document.getElementById("${component.stylesheet.id}-style")) ${add_css}();`}
@init(this, options, ${definition}, ${has_create_fragment ? 'create_fragment': 'null'}, ${not_equal}, ${prop_indexes}, ${dirty});
${options.dev && b`@dispatch_dev("SvelteRegisterComponent", { component: this, tagName: "${name.name}", options, id: create_fragment.name });`}
${options.dev && options.version < 3.22 && b`@dispatch_dev$legacy("SvelteRegisterComponent", { component: this, tagName: "${name.name}", options, id: create_fragment.name });`}
${dev_props_check}
}

@ -886,10 +886,6 @@ function run_animation(element: ElementWrapper, block: Block) {
];
}
function run_transition(element: ElementWrapper, block: Block, transition: Transition, type: string) {
// const run = b`
// 0 = @run_transition(1, `2`, ${
// type === 'intro' ? `true` : `false`
// }, ${params});
return [
/* node_intro */ block.alias(`${element.var.name}_${type}`),
/* node */ element.var,

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

@ -1,4 +1,4 @@
import { Node, Program } from "estree";
import { Node, Program } from 'estree';
import { SourceMap } from 'magic-string';
interface BaseNode {
@ -24,14 +24,15 @@ export interface MustacheTag extends BaseNode {
expression: Node;
}
export type DirectiveType = 'Action'
| 'Animation'
| 'Binding'
| 'Class'
| 'EventHandler'
| 'Let'
| 'Ref'
| 'Transition';
export type DirectiveType =
| 'Action'
| 'Animation'
| 'Binding'
| 'Class'
| 'EventHandler'
| 'Let'
| 'Ref'
| 'Transition';
interface BaseDirective extends BaseNode {
type: DirectiveType;
@ -40,7 +41,7 @@ interface BaseDirective extends BaseNode {
modifiers: string[];
}
export interface Transition extends BaseDirective{
export interface Transition extends BaseDirective {
type: 'Transition';
intro: boolean;
outro: boolean;
@ -48,11 +49,7 @@ export interface Transition extends BaseDirective{
export type Directive = BaseDirective | Transition;
export type TemplateNode = Text
| MustacheTag
| BaseNode
| Directive
| Transition;
export type TemplateNode = Text | MustacheTag | BaseNode | Directive | Transition;
export interface Parser {
readonly template: string;
@ -109,6 +106,7 @@ export interface CompileOptions {
name?: string;
filename?: string;
generate?: string | false;
version?: number;
outputFilename?: string;
cssOutputFilename?: string;

@ -47,12 +47,15 @@ export const circOut = (t: number) => Math.sin(Math.acos(1 - t));
export const circInOut = (t: number) =>
0.5 * (t >= 0.5 ? 2.0 - Math.sin(Math.acos(1.0 - 2.0 * (1.0 - t))) : Math.sin(Math.acos(1.0 - 2.0 * t)));
export const cubicBezier = (x1: number, y1: number, x2: number, y2: number) => {
if (__DEV__)
if (0 > x1 || x1 < 1 || 0 > x2 || x2 > 1)
throw new Error(`CubicBezier x1 & x2 values must be { 0 < x < 1 }, got { x1 : ${x1}, x2: ${x2} }`);
const ax = 1.0 - (x2 = 3.0 * (x2 - x1) - (x1 = 3.0 * x1)) - x1,
ay = 1.0 - (y2 = 3.0 * (y2 - y1) - (y1 = 3.0 * y1)) - y1;
let r = Number.NaN,
s = Number.NaN,
d = Number.NaN,
x = Number.NaN;
let r = 0.0,
s = 0.0,
d = 0.0,
x = 0.0;
return (t: number) => {
r = t;
for (let i = 0; 32 > i; i++)

@ -1,4 +1,5 @@
import './ambient';
import '../ambient';
export {
onMount,
@ -9,5 +10,5 @@ export {
getContext,
tick,
createEventDispatcher,
SvelteComponentDev as SvelteComponent
SvelteComponentDev as SvelteComponent,
} from 'svelte/internal';

@ -157,9 +157,9 @@ export function init(
set_current_component(parent_component);
}
export let SvelteElement;
if (typeof HTMLElement === 'function') {
SvelteElement = class extends HTMLElement {
export const SvelteElement =
typeof HTMLElement === 'function' &&
class extends HTMLElement {
$$: T$$;
constructor() {
super();
@ -196,7 +196,6 @@ if (typeof HTMLElement === 'function') {
// overridden by instance, if it has props
$set() {}
};
}
export class SvelteComponent {
$$: T$$;

@ -0,0 +1,90 @@
import { custom_event, append, insert, detach, listen, attr } from './dom';
let inited
export function dispatch_dev$legacy<T = any>(type: string, detail?: T) {
if(!inited && `__SVELTE_DEVTOOLS_GLOBAL_HOOK__` in window) {
inited = true
throw new Error(`You must specify the version`)
}
document.dispatchEvent(custom_event(type, { version: __VERSION__, ...detail }));
}
export function append_dev$legacy(target: Node, node: Node) {
dispatch_dev$legacy('SvelteDOMInsert', { target, node });
append(target, node);
}
export function insert_dev$legacy(target: Node, node: Node, anchor?: Node) {
dispatch_dev$legacy('SvelteDOMInsert', { target, node, anchor });
insert(target, node, anchor);
}
export function detach_dev$legacy(node: Node) {
dispatch_dev$legacy('SvelteDOMRemove', { node });
detach(node);
}
export function detach_between_dev$legacy(before: Node, after: Node) {
while (before.nextSibling && before.nextSibling !== after) {
detach_dev$legacy(before.nextSibling);
}
}
export function detach_before_dev$legacy(after: Node) {
while (after.previousSibling) {
detach_dev$legacy(after.previousSibling);
}
}
export function detach_after_dev$legacy(before: Node) {
while (before.nextSibling) {
detach_dev$legacy(before.nextSibling);
}
}
export function listen_dev$legacy(
node: Node,
event: string,
handler: EventListenerOrEventListenerObject,
options?: boolean | AddEventListenerOptions | EventListenerOptions,
has_prevent_default?: boolean,
has_stop_propagation?: boolean
) {
const modifiers = options === true ? ['capture'] : options ? Array.from(Object.keys(options)) : [];
if (has_prevent_default) modifiers.push('preventDefault');
if (has_stop_propagation) modifiers.push('stopPropagation');
dispatch_dev$legacy('SvelteDOMAddEventListener', { node, event, handler, modifiers });
const dispose = listen(node, event, handler, options);
return () => {
dispatch_dev$legacy('SvelteDOMRemoveEventListener', { node, event, handler, modifiers });
dispose();
};
}
export function attr_dev$legacy(node: Element, attribute: string, value?: string) {
attr(node, attribute, value);
if (value == null) dispatch_dev$legacy('SvelteDOMRemoveAttribute', { node, attribute });
else dispatch_dev$legacy('SvelteDOMSetAttribute', { node, attribute, value });
}
export function prop_dev$legacy(node: Element, property: string, value?: any) {
node[property] = value;
dispatch_dev$legacy('SvelteDOMSetProperty', { node, property, value });
}
export function dataset_dev$legacy(node: HTMLElement, property: string, value?: any) {
node.dataset[property] = value;
dispatch_dev$legacy('SvelteDOMSetDataset', { node, property, value });
}
export function set_data_dev$legacy(text, data) {
data = '' + data;
if (text.data === data) return;
dispatch_dev$legacy('SvelteDOMSetData', { node: text, data });
text.data = data;
}

@ -1,4 +1,4 @@
import { custom_event, append, insert, detach, listen, attr } from './dom';
import { custom_event, insert, detach, attr } from './dom';
import { SvelteComponent } from './Component';
import {
get_current_component,
@ -10,7 +10,7 @@ import {
setContext,
getContext,
} from './lifecycle';
import { cubicBezier } from 'svelte/easing';
import { writable } from 'svelte/store';
export const [
beforeUpdate_dev,
@ -26,17 +26,73 @@ export const [
return fn(a, b);
}
);
export function cubicBezier_dev(mX1, mY1, mX2, mY2) {
if (!(0 <= mX1 && mX1 <= 1 && 0 <= mX2 && mX2 <= 1)) throw new Error('CubicBezier x values must be { 0 < x < 1 }');
return cubicBezier(mX1, mY1, mX2, mY2);
const dev$hook =
__DEV__ &&
!__TEST__ &&
typeof window !== 'undefined' &&
(() => {
const subscribers = [];
function subscribe(listener) {
subscribers.push(listener);
return () => subscribers.splice(subscribers.indexOf(listener), 1);
}
const components = [];
const hook = writable(components);
Object.defineProperty(window, `__SVELTE_DEVTOOLS_GLOBAL_HOOK__`, {
enumerable: false,
get() {
return {
version: __VERSION__,
components,
};
},
});
return function update(type, value) {
subscribers.forEach((listener) => {
listener(type, value);
});
};
})();
export function dev$dispatch(type: string, value: any) {
dev$hook.update((o) => {
return o;
});
}
export function dev$element(element: Element, event: keyof ElementEventsMap, payload?: any) {
if (__DEV__) {
}
}
export function dispatch_dev<T = any>(type: string, detail?: T) {
document.dispatchEvent(custom_event(type, { version: '__VERSION__', ...detail }));
export function dev$block(event: keyof BlockEventsMap, payload) {}
export function dev$tracing(type, value: any) {}
export function dev$assert(truthy: boolean, else_throw: string) {
if (__DEV__ && !truthy) {
throw new Error(else_throw);
}
}
interface SvelteDevEvent extends CustomEvent {
__VERSION__: string;
}
interface ElementEventsMap {
mount;
unmount;
setAttribute;
removeAttribute;
addEventListener;
removeEventListener;
addClass;
removeClass;
}
interface BlockEventsMap {
'create';
'claim';
'claim.failed';
}
interface SvelteDevErrorsMap {}
export interface SvelteDOM {}
export function append_dev(target: Node, node: Node) {
dispatch_dev('SvelteDOMInsert', { target, node });
append(target, node);
export function dispatch_dev<T = any>(type: string, detail?: T) {
document.dispatchEvent(custom_event(type, { version: '__VERSION__', ...detail }));
}
export function insert_dev(target: Node, node: Node, anchor?: Node) {
@ -67,27 +123,6 @@ export function detach_after_dev(before: Node) {
}
}
export function listen_dev(
node: Node,
event: string,
handler: EventListenerOrEventListenerObject,
options?: boolean | AddEventListenerOptions | EventListenerOptions,
has_prevent_default?: boolean,
has_stop_propagation?: boolean
) {
const modifiers = options === true ? ['capture'] : options ? Array.from(Object.keys(options)) : [];
if (has_prevent_default) modifiers.push('preventDefault');
if (has_stop_propagation) modifiers.push('stopPropagation');
dispatch_dev('SvelteDOMAddEventListener', { node, event, handler, modifiers });
const dispose = listen(node, event, handler, options);
return () => {
dispatch_dev('SvelteDOMRemoveEventListener', { node, event, handler, modifiers });
dispose();
};
}
export function attr_dev(node: Element, attribute: string, value?: string) {
attr(node, attribute, value);

@ -1,14 +1,18 @@
import { has_prop } from './utils';
import { dispatch_dev, dev$dispatch, dev$element, dev$block } from './dev';
export function append(target: Node, node: Node) {
dev$element(node, `mount`, { target });
target.appendChild(node);
}
export function insert(target: Node, node: Node, anchor?: Node) {
dev$element(node, `mount`, { target, anchor });
target.insertBefore(node, anchor || null);
}
export function detach(node: Node) {
dev$element(node, `unmount`);
node.parentNode.removeChild(node);
}
@ -17,12 +21,14 @@ export function destroy_each(iterations, detaching) {
if (iterations[i]) iterations[i].d(detaching);
}
}
const dev$create = <K>(elem: K) => (dev$block(`create`, elem), elem);
export function element<K extends keyof HTMLElementTagNameMap>(name: K) {
if (__DEV__) return dev$create(document.createElement<K>(name));
return document.createElement<K>(name);
}
export function element_is<K extends keyof HTMLElementTagNameMap>(name: K, is: string) {
if (__DEV__) return dev$create(document.createElement<K>(name));
return document.createElement<K>(name, { is });
}
export function object_without_properties<T, K extends keyof T>(obj: T, exclude: K[]) {
@ -41,10 +47,12 @@ export function object_without_properties<T, K extends keyof T>(obj: T, exclude:
}
export function svg_element<K extends keyof SVGElementTagNameMap>(name: K): SVGElement {
if (__DEV__) return dev$create(document.createElementNS<K>('http://www.w3.org/2000/svg', name));
return document.createElementNS<K>('http://www.w3.org/2000/svg', name);
}
export function text(data: string) {
if (__DEV__) return dev$create(document.createTextNode(data));
return document.createTextNode(data);
}
@ -55,17 +63,25 @@ export function space() {
export function empty() {
return text('');
}
export function listen(
node: EventTarget,
event: string,
handler: EventListenerOrEventListenerObject,
options?: boolean | AddEventListenerOptions | EventListenerOptions
) {
if (__DEV__) {
const reference = { event, handler };
dev$element(node, `addEventListener`, reference);
node.addEventListener(event, handler, options);
return () => {
dev$element(node, `removeEventListener`, reference);
node.removeEventListener(event, handler, options);
};
}
node.addEventListener(event, handler, options);
return () => node.removeEventListener(event, handler, options);
}
// todo inline at compile time
export function prevent_default(fn) {
return function (event) {
event.preventDefault();
@ -73,15 +89,15 @@ export function prevent_default(fn) {
return fn.call(this, event);
};
}
export function stop_propagation(fn) {
// todo inline at compile time
export function stop_propagation(fn): EventListenerOrEventListenerObject {
return function (event) {
event.stopPropagation();
// @ts-ignore
//@ts-ignore
return fn.call(this, event);
};
}
// todo inline at compile time
export function self(fn) {
return function (event) {
// @ts-ignore
@ -89,48 +105,60 @@ export function self(fn) {
};
}
export function attr(node: Element, attribute: string, value?: string) {
if (value == null) node.removeAttribute(attribute);
else if (node.getAttribute(attribute) !== value) node.setAttribute(attribute, value);
export function attr(node: Element, name: string, value?: any) {
if (value == null) {
dev$element(node, `removeAttribute`, { name });
node.removeAttribute(name);
} else if (node.getAttribute(name) !== value) {
dev$element(node, `setAttribute`, { name, value });
node.setAttribute(name, value);
}
}
export function set_attributes(node: Element & ElementCSSInlineStyle, attributes: { [x: string]: string }) {
// @ts-ignore
export function set_attributes(node: HTMLElement, attributes: { [x: string]: any }) {
// @ts-ignore #3687
const descriptors = Object.getOwnPropertyDescriptors(node.__proto__);
for (const key in attributes) {
if (attributes[key] == null) {
node.removeAttribute(key);
} else if (key === 'style') {
node.style.cssText = attributes[key];
} else if (key === '__value' || (descriptors[key] && descriptors[key].set)) {
node[key] = attributes[key];
let name;
for (name in attributes) {
if (attributes[name] == null) {
dev$element(node, `removeAttribute`, { name });
node.removeAttribute(name);
} else if (name === 'style') {
dev$element(node, `setAttribute`, { name, value: attributes[name] });
node.style.cssText = attributes[name];
} else if (name === '__value' || (descriptors[name] && descriptors[name].set)) {
dev$element(node, `setAttribute`, { name, value: attributes[name] });
node[name] = attributes[name];
} else {
attr(node, key, attributes[key]);
attr(node, name, attributes[name]);
}
}
}
export function set_svg_attributes(node: Element & ElementCSSInlineStyle, attributes: { [x: string]: string }) {
for (const key in attributes) {
attr(node, key, attributes[key]);
export function set_svg_attributes(node: SVGElement, attributes: { [x: string]: any }) {
let name;
for (name in attributes) {
attr(node, name, attributes[name]);
}
}
export function set_custom_element_data(node, prop, value) {
if (prop in node) {
node[prop] = value;
export function set_custom_element_data(node, name, value) {
if (name in node) {
dev$element(node, `setAttribute`, { name, value });
node[name] = value;
} else {
attr(node, prop, value);
attr(node, name, value);
}
}
export function xlink_attr(node, attribute, value) {
node.setAttributeNS('http://www.w3.org/1999/xlink', attribute, value);
export function xlink_attr(node: Element, name, value) {
dev$element(node, `setAttribute`, { name, value });
node.setAttributeNS('http://www.w3.org/1999/xlink', name, value);
}
export function get_binding_group_value(group) {
const value = [];
for (let i = 0; i < group.length; i += 1) {
for (let i = 0, value = []; i < group.length; i += 1) {
if (group[i].checked) value.push(group[i].__value);
}
return value;
@ -150,35 +178,28 @@ export function time_ranges_to_array(ranges) {
export const children = (element: HTMLElement) => Array.from(element.childNodes);
export function claim_element(nodes, name, attributes, svg) {
for (let i = 0; i < nodes.length; i += 1) {
const node = nodes[i];
if (node.nodeName === name) {
let j = 0;
while (j < node.attributes.length) {
const attribute = node.attributes[j];
if (attributes[attribute.name]) {
j++;
} else {
node.removeAttribute(attribute.name);
}
export function claim_element(nodes, name, attributes, is_svg) {
for (let i = 0, j = 0, n, a; i < nodes.length; i += 1, j = 0)
if ((n = nodes[i]).nodeName !== name) continue;
else {
while (j < n.attributes.length) {
if (attributes[(a = n.attributes[j]).name]) j++;
else n.removeAttribute(a.name);
}
dev$block(`claim`, n);
return nodes.splice(i, 1)[0];
}
}
return svg ? svg_element(name) : element(name);
dev$block(`claim.failed`, name);
return is_svg ? svg_element(name) : element(name);
}
export function claim_text(nodes, data) {
for (let i = 0; i < nodes.length; i += 1) {
const node = nodes[i];
if (node.nodeType === 3) {
node.data = '' + data;
return nodes.splice(i, 1)[0];
}
for (let i = 0, n; i < nodes.length; i += 1)
if ((n = nodes[i]).nodeType === 3) {
dev$block(`claim`, n);
return (n.data = '' + data), nodes.splice(i, 1)[0];
}
dev$block(`claim.failed`, 'text');
return text(data);
}
@ -187,49 +208,56 @@ export function claim_space(nodes) {
}
export function set_data(text, data) {
data = '' + data;
if (text.data !== data) text.data = data;
if (text.data !== (data = '' + data)) {
text.data = data;
dev$element(text, `setAttribute`, { name: 'data', value: data });
}
}
export function set_input_value(input, value) {
if (value != null || input.value) {
input.value = value;
dev$element(input, `setAttribute`, { name: 'value', value });
}
}
export function set_input_type(input, type) {
try {
input.type = type;
} catch (e) {
// do nothing
}
dev$element(input, `setAttribute`, { name: 'type', value: type });
} catch (e) {}
}
export function set_style(node, key, value, important) {
node.style.setProperty(key, value, important ? 'important' : '');
export function set_style(node, property, value, is_important?) {
dev$element(node, `setAttribute`, { name: 'style', property, value });
node.style.setProperty(property, value, is_important ? 'important' : '');
}
export function select_option(select, value) {
for (let i = 0; i < select.options.length; i += 1) {
const option = select.options[i];
if (option.__value === value) {
option.selected = true;
for (let i = 0, o; i < select.options.length; i += 1) {
if ((o = select.options[i]).__value === value) {
dev$element(o, `setAttribute`, { name: 'selected', value: true });
o.selected = true;
return;
}
}
}
export function select_options(select, value) {
for (let i = 0; i < select.options.length; i += 1) {
const option = select.options[i];
option.selected = ~value.indexOf(option.__value);
for (let i = 0, o; i < select.options.length; i += 1) {
if (__DEV__) {
dev$element((o = select.options[i]), `setAttribute`, {
name: 'selected',
value: (o = select.options[i]).selected = ~value.indexOf(o.__value),
});
continue;
}
(o = select.options[i]).selected = ~value.indexOf(o.__value);
}
}
export function select_value(select) {
const selected_option = select.querySelector(':checked') || select.options[0];
return selected_option && selected_option.__value;
return (select = select.querySelector(':checked') || select.options[0]) && select.__value;
}
export function select_multiple_value(select) {
@ -261,17 +289,18 @@ export function add_resize_listener(node: HTMLElement, fn: () => void) {
const z_index = (parseInt(computed_style.zIndex) || 0) - 1;
if (computed_style.position === 'static') {
node.style.position = 'relative';
set_style(node, 'position', 'relative');
}
const iframe = element('iframe');
iframe.setAttribute(
attr(
iframe,
'style',
`display: block; position: absolute; top: 0; left: 0; width: 100%; height: 100%; ` +
`overflow: hidden; border: 0; opacity: 0; pointer-events: none; z-index: ${z_index};`
);
iframe.setAttribute('aria-hidden', 'true');
iframe.tabIndex = -1;
attr(iframe, 'aria-hidden', 'true');
attr(iframe, 'tabIndex', -1);
let unsubscribe: () => void;
@ -296,13 +325,14 @@ export function add_resize_listener(node: HTMLElement, fn: () => void) {
}
export function toggle_class(element, name, toggle) {
dev$element(element, toggle ? 'addClass' : 'removeClass', { name });
element.classList[toggle ? 'add' : 'remove'](name);
}
export function custom_event<T = any>(type: string, detail?: T) {
const e: CustomEvent<T> = document.createEvent('CustomEvent');
e.initCustomEvent(type, false, false, detail);
return e;
const event: CustomEvent<T> = document.createEvent('CustomEvent');
event.initCustomEvent(type, false, false, detail);
return event;
}
export function query_selector_all(selector: string, parent: HTMLElement = document.body) {

@ -3,7 +3,7 @@ import { noop } from './utils';
export const is_client = typeof window !== 'undefined';
export const is_iframe = is_client && window.location !== window.parent.location;
export let now = is_client ? window.performance.now : () => Date.now();
export let now = is_client ?()=> performance.now() :()=> Date.now();
export let raf = is_client ? window.requestAnimationFrame : noop;

@ -13,3 +13,4 @@ export * from './transitions';
export * from './utils';
export * from './Component';
export * from './dev';
export * from './stores'

@ -1,33 +1,41 @@
import { custom_event } from './dom';
import { dev$assert, SvelteComponentDev } from './dev';
import { SvelteComponent } from './Component';
export let current_component;
export let current_component: SvelteComponentDev | SvelteComponent | null;
export const set_current_component = (component) => (current_component = component);
const dev$guard = (name: string) =>
dev$assert(!!current_component, `${name} cannot be called outside of component initialization`);
export function get_current_component() {
if (!current_component) throw new Error(`Function called outside component initialization`);
return current_component;
}
export function beforeUpdate(fn) {
get_current_component().$$.before_update.push(fn);
dev$guard(`beforeUpdate`);
return current_component.$$.before_update.push(fn);
}
export function onMount(fn) {
get_current_component().$$.on_mount.push(fn);
dev$guard(`onMount`);
return current_component.$$.on_mount.push(fn);
}
export function afterUpdate(fn) {
get_current_component().$$.after_update.push(fn);
dev$guard(`afterUpdate`);
return current_component.$$.after_update.push(fn);
}
export function onDestroy(fn) {
get_current_component().$$.on_destroy.push(fn);
dev$guard(`onDestroy`);
return current_component.$$.on_destroy.push(fn);
}
export function createEventDispatcher() {
const component = get_current_component();
dev$guard(`createEventDispatcher`);
const component = current_component;
return (type: string, detail?: any) => {
const callbacks = component.$$.callbacks[type];
if (!callbacks) return;
@ -41,11 +49,13 @@ export function createEventDispatcher() {
}
export function setContext(key, context) {
get_current_component().$$.context.set(key, context);
dev$guard(`setContext`);
current_component.$$.context.set(key, context);
}
export function getContext(key) {
return get_current_component().$$.context.get(key);
dev$guard(`getContext`);
return current_component.$$.context.get(key);
}
// TODO figure out if we still want to support

@ -1,88 +1,108 @@
import { raf, now } from './environment';
import { now, raf } from './environment';
import { calc_framerate, FRAME_RATE } from './style_manager';
import { noop } from './utils';
type TaskCallback = (t: number) => boolean;
type TaskCanceller = () => void;
export interface Task {
abort(): void;
promise: Promise<void>;
let next_frame: Array<TaskCallback> = [];
let running_frame: Array<TaskCallback> = [];
function run(t: number) {
[running_frame, next_frame] = [next_frame, running_frame];
let next_frame_tasks = 0;
for (let i = 0, j = running_frame.length, v; i < j; i++) {
if ((v = running_frame[i])(t)) next_frame[next_frame_tasks++] = v;
}
running_frame.length = 0;
if (next_frame_tasks) raf(run);
}
type TaskCallback = (now: number) => boolean | void;
type TaskEntry = { c: TaskCallback; f: () => void };
const tasks = new Set<TaskEntry>();
function run_tasks(now: number) {
tasks.forEach((task) => {
if (task.c(now)) return;
tasks.delete(task);
task.f();
});
if (tasks.size) raf(run_tasks);
type TimeoutTask = { t: number; c: (now: number) => void };
const timed_tasks: Array<TimeoutTask> = [];
const pending_insert_timed: Array<TimeoutTask> = [];
let running_timed = false;
let pending_inserts = false;
function run_timed(now: number) {
/* Runs every timed out task */
let last_index = timed_tasks.length - 1;
while (last_index >= 0 && now >= timed_tasks[last_index].t) {
timed_tasks[last_index--].c(now);
}
if (pending_inserts) {
for (
let i = 0, j = last_index, this_task: TimeoutTask, that_task: TimeoutTask;
i < pending_insert_timed.length;
i++
) {
if (now >= (this_task = pending_insert_timed[i]).t) {
this_task.c(now);
} else {
for (j = last_index; j > 0 && this_task.t > (that_task = timed_tasks[j]).t; j--) {
/* move each task up until this_task.timestamp > task.timestamp */
timed_tasks[j + 1] = that_task;
}
timed_tasks[j] = this_task;
last_index++;
}
}
pending_inserts = !!(pending_insert_timed.length = 0);
}
return (running_timed = !!(timed_tasks.length = last_index + 1));
}
/**
* For testing purposes only!
*/
export function clear_loops() {
tasks.clear();
export function setAnimationTimeout(callback: () => void, timestamp: number): TaskCanceller {
const task: TimeoutTask = { c: callback, t: timestamp };
if (running_timed) {
pending_inserts = !!pending_insert_timed.push(task);
} else {
timed_tasks.push(task);
if (1 === next_frame.push(run_timed)) raf(run);
}
return () => void (task.c = noop);
}
/**
* Creates a new task that runs on each raf frame
* until it returns a falsy value or is aborted
* Calls function every frame with a value going from 0 to 1
*/
export function loop(callback: TaskCallback): Task {
let task: TaskEntry;
if (!tasks.size) raf(run_tasks);
return {
promise: new Promise((fulfill) => {
tasks.add((task = { c: callback, f: fulfill }));
}),
abort() {
tasks.delete(task);
},
};
}
function add(c: TaskCallback) {
const task = { c, f: noop };
if (!tasks.size) raf(run_tasks);
tasks.add(task);
return () => tasks.delete(task);
export function useTween(
run: (now: number) => void,
stop: () => void,
end_time: number,
duration = end_time - now()
): TaskCanceller {
let running = true;
if (
1 ===
next_frame.push((t) => {
if (!running) return false;
t = (end_time - t) / duration;
if (t >= 1) return run(1), stop(), (running = false);
if (t >= 0) run(t);
return running;
})
)
raf(run);
return () => void (running = false);
}
type TimeoutTask = { t: number; c: (now: number) => void };
// sorted descending
const timed_tasks: Array<TimeoutTask> = [];
/**
* basically
* (fn, t) => setTimeout( () => raf(fn), t )
* Calls function every frame with the amount of elapsed frames
*/
export function setAnimationTimeout(callback: () => void, timestamp: number) {
let i = timed_tasks.length;
let v;
const task = { c: callback, t: timestamp };
if (i) {
while (i > 0 && timestamp > (v = timed_tasks[i - 1]).t) timed_tasks[i--] = v;
timed_tasks[i] = task;
} else {
timed_tasks.push(task);
add((now) => {
let i = timed_tasks.length;
while (i > 0 && now >= timed_tasks[--i].t) timed_tasks.pop().c(now);
return !!timed_tasks.length;
});
}
return () => {
const index = timed_tasks.indexOf(task);
if (~index) timed_tasks.splice(index, 1);
};
export function onEachFrame(
each_frame: (seconds_elapsed: number) => boolean,
on_stop?,
max_skipped_frames = 4
): TaskCanceller {
max_skipped_frames *= FRAME_RATE || calc_framerate();
let lastTime = now();
let running = true;
if (
1 ===
next_frame.push((t: number) => {
if (!running) return on_stop && on_stop(t), false;
if (t > lastTime + max_skipped_frames) t = lastTime + max_skipped_frames;
return each_frame((-lastTime + (lastTime = t)) / 1000);
})
)
raf(run);
return () => void (running = false);
}
export const loopThen = (run: (now: number) => void, stop: () => void, duration: number, end_time: number) =>
add((t) => {
t = (end_time - t) / duration;
if (t >= 1) return void run(1), stop();
else if (t >= 0) run(t);
return true;
});
// tests
export const clear_loops = () =>
void (next_frame.length = running_frame.length = timed_tasks.length = pending_insert_timed.length = 0);

@ -7,8 +7,7 @@ const resolved_promise = Promise.resolve();
let update_scheduled = false;
export function schedule_update(component) {
dirty_components.push(component);
if (update_scheduled) return;
(update_scheduled = true), resolved_promise.then(flush);
if (!update_scheduled) (update_scheduled = true), resolved_promise.then(flush);
}
export function tick() {
if (!update_scheduled) (update_scheduled = true), resolved_promise.then(flush);
@ -16,16 +15,17 @@ export function tick() {
}
export const binding_callbacks = [];
const render_callbacks = [];
export const add_render_callback = (fn) => render_callbacks.push(fn);
const seen_callbacks = new Set();
export const add_render_callback = (fn) =>
void (!seen_callbacks.has(fn) && (seen_callbacks.add(fn), render_callbacks.push(fn)));
const flush_callbacks = [];
export const add_flush_callback = (fn) => flush_callbacks.push(fn);
export const add_flush_callback = (fn) => void flush_callbacks.push(fn);
let flushing = false;
const seen_callbacks = new Set();
export function flush() {
if (flushing) return;
flushing = true;
else flushing = true;
for (; dirty_components.length; ) {
// update components + beforeUpdate
@ -42,21 +42,17 @@ export function flush() {
dirty_components.length = 0;
// update bindings in reverse order
for (let i = binding_callbacks.length - 1; i; i--) binding_callbacks[i]();
for (let i = binding_callbacks.length - 1; i >= 0; i--) binding_callbacks[i]();
binding_callbacks.length = 0;
// afterUpdate
for (let i = 0, callback; i < render_callbacks.length; i++) {
if (seen_callbacks.has((callback = render_callbacks[i]))) continue;
seen_callbacks.add(callback), callback();
}
for (let i = 0; i < render_callbacks.length; i++) render_callbacks[i]();
render_callbacks.length = 0;
seen_callbacks.clear();
}
for (let i = 0; i < flush_callbacks.length; i++) flush_callbacks[i]();
flush_callbacks.length = 0;
seen_callbacks.clear();
update_scheduled = false;
flushing = false;
update_scheduled = flushing = false;
}

@ -0,0 +1,65 @@
import { safe_not_equal, noop } from './utils';
type Setter<T> = (value: T) => void;
type StopCallback = () => void;
export type StartStopNotifier<T> = (set: Setter<T>) => StopCallback | void;
type Subscriber<T> = (value: T) => void;
type Unsubscriber = () => void;
export class Store<T> {
static update_queue: Store<any>[] = [];
static is_flushing = false;
static flush(store: Store<any>) {
this.update_queue.push(store);
if (this.is_flushing) return;
this.is_flushing = true;
for (let i = 0, j = 0, subscribers, value; i < this.update_queue.length; i++) {
for (j = 0, { subscribers, value } = this.update_queue[i]; j < subscribers.length; j++) {
subscribers[j].r(value);
}
}
this.update_queue.length = +(this.is_flushing = false);
}
value: T;
has_subscribers = false;
subscribers = [];
constructor(initial: T) {
this.value = initial;
}
set(next_value: T) {
this.value = next_value;
if (!this.has_subscribers) return;
for (let i = 0; i < this.subscribers.length; i++) this.subscribers[i].i();
Store.flush(this);
}
subscribe(run: Subscriber<T>, invalidate = noop) {
const subscriber = { r: run, i: invalidate };
this.subscribers.push(subscriber);
run(this.value), (this.has_subscribers = true);
return this.unsubscribe.bind(this, subscriber) as Unsubscriber;
}
unsubscribe(subscriber) {
this.subscribers.splice(this.subscribers.indexOf(subscriber), 1);
this.has_subscribers = !!this.subscribers.length;
}
}
export class Writable<T> extends Store<T> {
start: StartStopNotifier<T>;
stop = noop;
constructor(initial: T, startStopNotifier: StartStopNotifier<T>) {
super(initial);
this.start = startStopNotifier || noop;
}
subscribe(run, invalidate) {
if (!super.has_subscribers) this.stop = this.start(this.set.bind(this)) || noop;
return super.subscribe(run, invalidate);
}
set(next_value: T) {
if (this.stop && safe_not_equal(super.value, next_value)) super.set(next_value);
}
update(fn) {
this.set(fn(this.value));
}
unsubscribe(subscriber) {
super.unsubscribe(subscriber);
if (!super.has_subscribers && this.stop) this.stop(), (this.stop = null);
}
}

@ -6,8 +6,8 @@ const enum SVELTE {
RULESET = `__svelte_rules`,
}
let FRAME_RATE;
function calc_framerate() {
export let FRAME_RATE;
export function calc_framerate() {
const f24 = 1000 / 24,
f60 = 1000 / 60,
f144 = 1000 / 144;

@ -1,6 +1,6 @@
import { run_all } from './utils';
import { now } from './environment';
import { setAnimationTimeout, loopThen } from './loop';
import { setAnimationTimeout, useTween } from './loop';
import { animate_css } from './style_manager';
import { custom_event } from './dom';
import { TransitionConfig } from '../transition';
@ -82,7 +82,7 @@ export function run_transition(
);
dispatch_end = startStopDispatcher(node, is_intro);
cancel_raf = tick
? loopThen(runner(eased(tick, easing), is_intro), stop, duration, end_time)
? useTween(runner(eased(tick, easing), is_intro), stop, duration, end_time)
: setAnimationTimeout(stop, end_time);
}
function stop(reset_reverse?: 1 | -1) {

@ -48,9 +48,9 @@ export function validate_store(store, name) {
}
}
export function subscribe(store, ...callbacks) {
export function subscribe(store, subscriber, invalidator?) {
if (store == null) return noop;
const unsub = store.subscribe(...callbacks);
const unsub = store.subscribe(subscriber, invalidator);
return unsub.unsubscribe ? () => unsub.unsubscribe() : unsub;
}
@ -136,5 +136,8 @@ export const has_prop = (obj, prop) => Object.prototype.hasOwnProperty.call(obj,
export function action_destroyer(action_result) {
return action_result && is_function(action_result.destroy) ? action_result.destroy : noop;
}
export const minmax = (min: number, max: number) => (v: number) => (v > min ? (v > max ? max : v) : min);
export const minmax = (min: number, max: number) => (value: number) => (min > value ? min : value > max ? max : value);
export const clamper = (min: number, max: number) =>
min !== -Infinity || max !== Infinity
? (fn: (value: number) => any) => (value: number) => (min > (value = fn(value)) ? min : value > max ? max : value)
: (fn: (value: number) => any) => fn;

@ -0,0 +1,133 @@
// import { Readable, writable } from 'svelte/store';
// import { assign, loop, now, Task } from 'svelte/internal';
// import { linear } from 'svelte/easing';
// import { is_date } from './utils';
// function get_interpolator(a, b) {
// if (a === b || a !== a) return () => a;
// const type = typeof a;
// if (type !== typeof b || Array.isArray(a) !== Array.isArray(b)) {
// throw new Error('Cannot interpolate values of different type');
// }
// if (Array.isArray(a)) {
// const arr = b.map((bi, i) => {
// return get_interpolator(a[i], bi);
// });
// return t => arr.map(fn => fn(t));
// }
// if (type === 'object') {
// if (!a || !b) throw new Error('Object cannot be null');
// if (is_date(a) && is_date(b)) {
// a = a.getTime();
// b = b.getTime();
// const delta = b - a;
// return t => new Date(a + t * delta);
// }
// const keys = Object.keys(b);
// const interpolators = {};
// keys.forEach(key => {
// interpolators[key] = get_interpolator(a[key], b[key]);
// });
// return t => {
// const result = {};
// keys.forEach(key => {
// result[key] = interpolators[key](t);
// });
// return result;
// };
// }
// if (type === 'number') {
// const delta = b - a;
// return t => a + t * delta;
// }
// throw new Error(`Cannot interpolate ${type} values`);
// }
// interface Options<T> {
// delay?: number;
// duration?: number | ((from: T, to: T) => number);
// easing?: (t: number) => number;
// interpolate?: (a: T, b: T) => (t: number) => T;
// }
// type Updater<T> = (target_value: T, value: T) => T;
// interface Tweened<T> extends Readable<T> {
// set(value: T, opts: Options<T>): Promise<void>;
// update(updater: Updater<T>, opts: Options<T>): Promise<void>;
// }
// export function tweened<T>(value?: T, defaults: Options<T> = {}): Tweened<T> {
// const store = writable(value);
// let task: Task;
// let target_value = value;
// function set(new_value: T, opts: Options<T>) {
// if (value == null) {
// store.set(value = new_value);
// return Promise.resolve();
// }
// target_value = new_value;
// let previous_task = task;
// let started = false;
// let {
// delay = 0,
// duration = 400,
// easing = linear,
// interpolate = get_interpolator
// } = assign(assign({}, defaults), opts);
// const start = now() + delay;
// let fn;
// task = loop(now => {
// if (now < start) return true;
// if (!started) {
// fn = interpolate(value, new_value);
// if (typeof duration === 'function') duration = duration(value, new_value);
// started = true;
// }
// if (previous_task) {
// previous_task.abort();
// previous_task = null;
// }
// const elapsed = now - start;
// if (elapsed > duration) {
// store.set(value = new_value);
// return false;
// }
// // @ts-ignore
// store.set(value = fn(easing(elapsed / duration)));
// return true;
// });
// return task.promise;
// }
// return {
// set,
// update: (fn, opts: Options<T>) => set(fn(target_value, value), opts),
// subscribe: store.subscribe
// };
// }

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

@ -1,147 +1,81 @@
import { Readable, writable } from 'svelte/store';
import { loop, now, Task } from 'svelte/internal';
import { is_date } from './utils';
interface TickContext<T> {
inv_mass: number;
dt: number;
opts: Spring<T>;
settled: boolean;
}
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;
// @ts-ignore
const velocity = (current_value - last_value) / (ctx.dt||1/60); // guard div by 0
const spring = ctx.opts.stiffness * delta;
const damper = ctx.opts.damping * velocity;
const acceleration = (spring - damper) * ctx.inv_mass;
const d = (velocity + acceleration) * ctx.dt;
if (Math.abs(d) < ctx.opts.precision && Math.abs(delta) < ctx.opts.precision) {
return target_value; // settled
import { Store, onEachFrame } from 'svelte/internal';
function solve_spring(
prev_value: number,
prev_velocity: number,
target_value: number,
{ stiffness, mass, damping, soft }
) {
const delta = target_value - prev_value;
if (soft || 1 <= damping / (2.0 * Math.sqrt(stiffness * mass))) {
const angular_frequency = -Math.sqrt(stiffness / mass);
return (t: number) =>
target_value - (delta + t * (-angular_frequency * delta - prev_velocity)) * Math.exp(t * angular_frequency);
} else {
ctx.settled = false; // signal loop to keep ticking
// @ts-ignore
return is_date(current_value) ?
new Date(current_value.getTime() + d) : current_value + d;
const damping_frequency = Math.sqrt(4.0 * mass * stiffness - damping ** 2);
const leftover = (damping * delta - 2.0 * mass * prev_velocity) / damping_frequency;
const dfm = (0.5 * damping_frequency) / mass;
const dm = -(0.5 * damping) / mass;
let f = 0.0;
return (t: number) => target_value - (Math.cos((f = t * dfm)) * delta + Math.sin(f) * leftover) * Math.exp(t * dm);
}
} else if (Array.isArray(current_value)) {
// @ts-ignore
return current_value.map((_, i) =>
tick_spring(ctx, last_value[i], current_value[i], target_value[i]));
} else if (typeof current_value === 'object') {
const next_value = {};
for (const k in current_value)
// @ts-ignore
next_value[k] = tick_spring(ctx, last_value[k], current_value[k], target_value[k]);
// @ts-ignore
return next_value;
} else {
throw new Error(`Cannot spring ${typeof current_value} values`);
}
}
interface SpringOpts {
stiffness?: number;
damping?: number;
precision?: number;
}
interface SpringUpdateOpts {
hard?: any;
soft?: string | number | boolean;
}
type Updater<T> = (target_value: T, value: T) => T;
interface Spring<T> extends Readable<T>{
set: (new_value: T, opts?: SpringUpdateOpts) => Promise<void>;
update: (fn: Updater<T>, opts?: SpringUpdateOpts) => Promise<void>;
precision: number;
damping: number;
stiffness: number;
}
export function spring<T=any>(value?: T, opts: SpringOpts = {}): Spring<T> {
const store = writable(value);
const { stiffness = 0.15, damping = 0.8, precision = 0.01 } = opts;
let last_time: number;
let task: Task;
let current_token: object;
let last_value: T = value;
let target_value: T = value;
let inv_mass = 1;
let inv_mass_recovery_rate = 0;
let cancel_task = false;
function set(new_value: T, opts: SpringUpdateOpts={}): Promise<void> {
target_value = new_value;
const token = current_token = {};
if (value == null || opts.hard || (spring.stiffness >= 1 && spring.damping >= 1)) {
cancel_task = true; // cancel any running animation
last_time = now();
last_value = new_value;
store.set(value = target_value);
return Promise.resolve();
} else if (opts.soft) {
const rate = opts.soft === true ? .5 : +opts.soft;
inv_mass_recovery_rate = 1 / (rate * 60);
inv_mass = 0; // infinite mass, unaffected by spring forces
class MotionStore<T> extends Store<T> {
elapsed = 0.0;
running = false;
setTick: (prev_value: T, next_value: T) => (current_value: T, elapsed: number, dt: number) => boolean;
tick: (current_value: T, elapsed: number, dt: number) => boolean;
constructor(value, startSetTick) {
super(value);
this.setTick = startSetTick(super.set.bind(this));
}
if (!task) {
last_time = now();
cancel_task = false;
task = loop(now => {
if (cancel_task) {
cancel_task = false;
task = null;
return false;
set(next_value: T) {
this.elapsed = 0.0;
this.tick = this.setTick(this.value, next_value);
if (this.running) return;
else this.running = true;
onEachFrame((dt) => (this.running = this.tick(this.value, (this.elapsed += dt), dt)));
}
inv_mass = Math.min(inv_mass + inv_mass_recovery_rate, 1);
const ctx: TickContext<T> = {
inv_mass,
opts: spring,
settled: true, // tick_spring may signal false
dt: (now - last_time) * 60 / 1000
}
export function spring(value, params) {
const { mass = 1.0, damping = 10.0, stiffness = 100.0, precision = 0.001, soft = false } = params || {};
return new MotionStore(
value,
parseStructure(value, (set) => {
let velocity = 0.0,
calc;
return (from_value, to_value) => {
calc = solve_spring(from_value, velocity, to_value, { mass, damping, stiffness, soft });
return (current, elapsed, dt) =>
precision > Math.abs((velocity = (-current + (current = calc(elapsed))) / dt)) &&
precision > Math.abs(to_value - current)
? (set(to_value), !!(velocity = 0.0))
: (set(current), true);
};
const next_value = tick_spring(ctx, last_value, value, target_value);
last_time = now;
last_value = value;
store.set(value = next_value);
if (ctx.settled)
task = null;
return !ctx.settled;
});
}
return new Promise(fulfil => {
task.promise.then(() => {
if (token === current_token) fulfil();
});
});
}
const spring: Spring<T> = {
set,
update: (fn, opts: SpringUpdateOpts) => set(fn(target_value, value), opts),
subscribe: store.subscribe,
stiffness,
damping,
precision
})
);
}
function parseStructure(obj, schema) {
if (typeof obj === 'object' && obj !== null) {
const keys = Object.keys(obj);
let i = 0,
k = '',
setTickers = keys.map((key) => parseStructure(obj[key], schema)((next_value) => (obj[key] = next_value))),
tickers,
pending = 0;
const target = { ...obj };
const isArray = Array.isArray(obj);
obj = isArray ? [...obj] : { ...obj };
return (set) => (_from_value, to_value) => {
for (k in to_value) if (to_value[k] !== obj[k]) target[k] = to_value[k];
tickers = setTickers.map((setTicker, i) => ((pending |= 1 << i), setTicker(obj[keys[i]], target[keys[i]])));
return (_current, elapsed, dt) => {
for (i = 0; i < tickers.length; i++)
if (pending & (1 << i) && !tickers[i](obj[keys[i]], elapsed, dt)) pending &= ~(1 << i);
set(isArray ? [...obj] : { ...obj });
return !!pending;
};
return spring;
};
}
return schema;
}

@ -1,133 +0,0 @@
import { Readable, writable } from 'svelte/store';
import { assign, loop, now, Task } from 'svelte/internal';
import { linear } from 'svelte/easing';
import { is_date } from './utils';
function get_interpolator(a, b) {
if (a === b || a !== a) return () => a;
const type = typeof a;
if (type !== typeof b || Array.isArray(a) !== Array.isArray(b)) {
throw new Error('Cannot interpolate values of different type');
}
if (Array.isArray(a)) {
const arr = b.map((bi, i) => {
return get_interpolator(a[i], bi);
});
return t => arr.map(fn => fn(t));
}
if (type === 'object') {
if (!a || !b) throw new Error('Object cannot be null');
if (is_date(a) && is_date(b)) {
a = a.getTime();
b = b.getTime();
const delta = b - a;
return t => new Date(a + t * delta);
}
const keys = Object.keys(b);
const interpolators = {};
keys.forEach(key => {
interpolators[key] = get_interpolator(a[key], b[key]);
});
return t => {
const result = {};
keys.forEach(key => {
result[key] = interpolators[key](t);
});
return result;
};
}
if (type === 'number') {
const delta = b - a;
return t => a + t * delta;
}
throw new Error(`Cannot interpolate ${type} values`);
}
interface Options<T> {
delay?: number;
duration?: number | ((from: T, to: T) => number);
easing?: (t: number) => number;
interpolate?: (a: T, b: T) => (t: number) => T;
}
type Updater<T> = (target_value: T, value: T) => T;
interface Tweened<T> extends Readable<T> {
set(value: T, opts: Options<T>): Promise<void>;
update(updater: Updater<T>, opts: Options<T>): Promise<void>;
}
export function tweened<T>(value?: T, defaults: Options<T> = {}): Tweened<T> {
const store = writable(value);
let task: Task;
let target_value = value;
function set(new_value: T, opts: Options<T>) {
if (value == null) {
store.set(value = new_value);
return Promise.resolve();
}
target_value = new_value;
let previous_task = task;
let started = false;
let {
delay = 0,
duration = 400,
easing = linear,
interpolate = get_interpolator
} = assign(assign({}, defaults), opts);
const start = now() + delay;
let fn;
task = loop(now => {
if (now < start) return true;
if (!started) {
fn = interpolate(value, new_value);
if (typeof duration === 'function') duration = duration(value, new_value);
started = true;
}
if (previous_task) {
previous_task.abort();
previous_task = null;
}
const elapsed = now - start;
if (elapsed > duration) {
store.set(value = new_value);
return false;
}
// @ts-ignore
store.set(value = fn(easing(elapsed / duration)));
return true;
});
return task.promise;
}
return {
set,
update: (fn, opts: Options<T>) => set(fn(target_value, value), opts),
subscribe: store.subscribe
};
}

@ -1,204 +1,45 @@
import { run_all, subscribe, noop, safe_not_equal, is_function, get_store_value } from 'svelte/internal';
/** Callback to inform of a value updates. */
type Subscriber<T> = (value: T) => void;
/** Unsubscribes from value updates. */
type Unsubscriber = () => void;
/** Callback to update a value. */
type Updater<T> = (value: T) => T;
/** Cleanup logic callback. */
type Invalidator<T> = (value?: T) => void;
/** Start and stop notification callbacks. */
type StartStopNotifier<T> = (set: Subscriber<T>) => Unsubscriber | void;
/** Readable interface for subscribing. */
export interface Readable<T> {
/**
* Subscribe on value changes.
* @param run subscription callback
* @param invalidate cleanup callback
*/
subscribe(run: Subscriber<T>, invalidate?: Invalidator<T>): Unsubscriber;
}
/** Writable interface for both updating and subscribing. */
export interface Writable<T> extends Readable<T> {
/**
* Set value and inform subscribers.
* @param value to set
*/
set(value: T): void;
/**
* Update value using callback and inform subscribers.
* @param updater callback
*/
update(updater: Updater<T>): void;
import { subscribe, noop, get_store_value, Writable, StartStopNotifier } from 'svelte/internal';
export { get_store_value as get };
export function readable<T>(value: T, start: StartStopNotifier<T>) {
const store = new Writable(value, start);
return { subscribe: store.subscribe.bind(store) };
}
/** Pair of subscriber and invalidator. */
type SubscribeInvalidateTuple<T> = [Subscriber<T>, Invalidator<T>];
const subscriber_queue = [];
/**
* Creates a `Readable` store that allows reading by subscription.
* @param value initial value
* @param {StartStopNotifier}start start and stop notifications for subscriptions
*/
export function readable<T>(value: T, start: StartStopNotifier<T>): Readable<T> {
return {
subscribe: writable(value, start).subscribe,
};
export function writable<T>(value: T, start: StartStopNotifier<T>) {
const store = new Writable(value, start);
return { set: store.set.bind(store), update: store.update.bind(store), subscribe: store.subscribe.bind(store) };
}
/**
* Create a `Writable` store that allows both updating and reading by subscription.
* @param {*=}value initial value
* @param {StartStopNotifier=}start start and stop notifications for subscriptions
*/
export function writable<T>(value: T, start: StartStopNotifier<T> = noop): Writable<T> {
let stop: Unsubscriber;
const subscribers: Array<SubscribeInvalidateTuple<T>> = [];
function set(new_value: T): void {
if (safe_not_equal(value, new_value)) {
value = new_value;
if (stop) { // store is ready
const run_queue = !subscriber_queue.length;
for (let i = 0; i < subscribers.length; i += 1) {
const s = subscribers[i];
s[1]();
subscriber_queue.push(s, value);
}
if (run_queue) {
for (let i = 0; i < subscriber_queue.length; i += 2) {
subscriber_queue[i][0](subscriber_queue[i + 1]);
}
subscriber_queue.length = 0;
}
}
}
}
function update(fn: Updater<T>): void {
set(fn(value));
}
function subscribe(run: Subscriber<T>, invalidate: Invalidator<T> = noop): Unsubscriber {
const subscriber: SubscribeInvalidateTuple<T> = [run, invalidate];
subscribers.push(subscriber);
if (subscribers.length === 1) {
stop = start(set) || noop;
}
run(value);
return () => {
const index = subscribers.indexOf(subscriber);
if (index !== -1) {
subscribers.splice(index, 1);
}
if (subscribers.length === 0) {
stop();
stop = null;
}
};
}
return { set, update, subscribe };
}
/** One or more `Readable`s. */
type Stores = Readable<any> | [Readable<any>, ...Array<Readable<any>>];
/** One or more values from `Readable` stores. */
type StoresValues<T> = T extends Readable<infer U> ? U :
{ [K in keyof T]: T[K] extends Readable<infer U> ? U : never };
/**
* Derived value store by synchronizing one or more readable stores and
* applying an aggregation function over its input values.
*
* @param stores - input stores
* @param fn - function callback that aggregates the values
*/
export function derived<S extends Stores, T>(
stores: S,
fn: (values: StoresValues<S>) => T
): Readable<T>;
/**
* Derived value store by synchronizing one or more readable stores and
* applying an aggregation function over its input values.
*
* @param stores - input stores
* @param fn - function callback that aggregates the values
* @param initial_value - when used asynchronously
*/
export function derived<S extends Stores, T>(
stores: S,
fn: (values: StoresValues<S>, set: (value: T) => void) => Unsubscriber | void,
initial_value?: T
): Readable<T>;
export function derived<T>(stores: Stores, fn: Function, initial_value?: T): Readable<T> {
const single = !Array.isArray(stores);
const stores_array: Array<Readable<any>> = single
? [stores as Readable<any>]
: stores as Array<Readable<any>>;
const auto = fn.length < 2;
return readable(initial_value, (set) => {
let inited = false;
const values = [];
let pending = 0;
export function derived<T, S>(stores: S, deriver, initial_value?: T) {
let cleanup = noop;
const sync = () => {
if (pending) {
return;
}
cleanup();
const result = fn(single ? values[0] : values, set);
if (auto) {
set(result as T);
} else {
cleanup = is_function(result) ? result as Unsubscriber : noop;
}
};
const unsubscribers = stores_array.map((store, i) => subscribe(
const dispatcher =
deriver.length < 2
? (set, v) => void set(deriver(v))
: (set, v) => void (cleanup(), typeof (cleanup = deriver(v, set)) !== 'function' && (cleanup = noop));
return readable(
initial_value,
Array.isArray(stores)
? (set) => {
set = dispatcher.bind(null, set);
let l = stores.length;
let pending = 1 << l;
const values = new Array(l);
const unsubs = stores.map((store, i) =>
subscribe(
store,
(value) => {
values[i] = value;
pending &= ~(1 << i);
if (inited) {
sync();
}
},
() => {
pending |= (1 << i);
}),
// @ts-ignore
(v) => void ((values[i] = v), !(pending &= ~(1 << i)) && set(values)),
() => void (pending |= 1 << i)
)
);
inited = true;
sync();
return function stop() {
run_all(unsubscribers);
// @ts-ignore
if (!(pending &= ~(1 << l))) set(values);
return () => {
while (l--) unsubs[l]();
cleanup();
};
});
}
: (set) => {
const unsub = subscribe(stores, dispatcher.bind(null, set));
return () => void (unsub(), cleanup());
}
);
}
/**
* Get the current value from a store by subscribing and immediately unsubscribing.
* @param store readable
*/
export { get_store_value as get };

@ -1,6 +1,6 @@
{
"extends": "../../tsconfig.json",
"include": ["."],
"include": [".", "../ambient.ts"],
"compilerOptions": {
"lib": ["es2015", "dom", "dom.iterable"],

Loading…
Cancel
Save