diff --git a/.eslintrc.js b/.eslintrc.js index 946a157e40..7b88a0670e 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1,9 +1,9 @@ module.exports = { root: true, rules: { - indent: 'off', + 'indent': 'off', 'no-unused-vars': 'off', - semi: [2, 'always'], + 'semi': [2, 'always'], 'keyword-spacing': [2, { before: true, after: true }], 'space-before-blocks': [2, 'always'], 'no-mixed-spaces-and-tabs': [2, 'smart-tabs'], @@ -16,7 +16,6 @@ module.exports = { '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, @@ -32,57 +31,51 @@ module.exports = { '@typescript-eslint/no-unused-vars': [ 'error', { - argsIgnorePattern: '^_' - } + argsIgnorePattern: '^_', + }, ], '@typescript-eslint/no-object-literal-type-assertion': 'off', '@typescript-eslint/no-unused-vars': 'off', - '@typescript-eslint/prefer-interface': 'off' + '@typescript-eslint/prefer-interface': 'off', }, globals: { - globalThis: false + globalThis: false, }, env: { es6: true, browser: true, node: true, - mocha: true + mocha: true, }, extends: [ 'eslint:recommended', 'plugin:import/errors', 'plugin:import/warnings', 'plugin:import/typescript', - 'plugin:@typescript-eslint/recommended' + 'plugin:@typescript-eslint/recommended', ], parserOptions: { ecmaVersion: 9, - sourceType: 'module' + sourceType: 'module', }, plugins: ['svelte3'], settings: { - 'import/core-modules': [ - 'svelte', - 'svelte/internal', - 'svelte/store', - 'svelte/easing', - 'estree' - ], - 'svelte3/compiler': require('./compiler') + 'import/core-modules': ['svelte', 'svelte/internal', 'svelte/store', 'svelte/easing', 'estree'], + 'svelte3/compiler': require('./compiler'), }, overrides: [ { files: ['*.js'], rules: { - '@typescript-eslint/no-var-requires': 'off' - } + '@typescript-eslint/no-var-requires': 'off', + }, }, { files: ['*.svelte'], processor: 'svelte3/svelte3', rules: { - '@typescript-eslint/indent': 'off' - } - } - ] + '@typescript-eslint/indent': 'off', + }, + }, + ], }; diff --git a/rollup.config.js b/rollup.config.js index 6f3d893a33..de1159f442 100644 --- a/rollup.config.js +++ b/rollup.config.js @@ -7,106 +7,95 @@ 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 ts_plugin = is_publish - ? typescript({ - include: 'src/**', - typescript: require('typescript') - }) - : sucrase({ - transforms: ['typescript'] - }); + ? typescript({ include: 'src/**', typescript: require('typescript') }) + : sucrase({ transforms: ['typescript'] }); -const external = id => id.startsWith('svelte/'); +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 }) => ({ + 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' })]), + })) + ); + +function writeFileSync(...arr) { + arr.filter(Boolean).forEach(({ dir, content }) => fs.writeFileSync(dir, content)); +} export default [ - /* runtime */ + /* runtime main */ { input: `src/runtime/index.ts`, - output: [ - { - file: `index.mjs`, - format: 'esm', - paths: id => id.startsWith('svelte/') && `${id.replace('svelte', '.')}` - }, - { - file: `index.js`, - format: 'cjs', - paths: id => id.startsWith('svelte/') && `${id.replace('svelte', '.')}` - } - ], + output: output({ + default: { file: 'index', path: '.' }, + dev: { file: 'index', path: '.' }, + }), external, - plugins: [ts_plugin] + plugins: [ts_plugin], }, - - ...fs.readdirSync('src/runtime') - .filter(dir => fs.statSync(`src/runtime/${dir}`).isDirectory()) - .map(dir => ({ + /* svelte/[library] */ + ...fs + .readdirSync('src/runtime') + .filter((dir) => fs.statSync(`src/runtime/${dir}`).isDirectory()) + .map((dir) => ({ input: `src/runtime/${dir}/index.ts`, - output: [ - { - file: `${dir}/index.mjs`, - format: 'esm', - paths: id => id.startsWith('svelte/') && `${id.replace('svelte', '..')}` - }, - { - file: `${dir}/index.js`, - format: 'cjs', - paths: id => id.startsWith('svelte/') && `${id.replace('svelte', '..')}` - } - ], + output: output({ + [dir]: { file: 'index', path: '..' }, + dev: { file: dir, path: '.' }, + }), external, plugins: [ - replace({ - __VERSION__: pkg.version - }), + version, ts_plugin, { 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)});`); + writeFileSync( + dir === '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)});`, + }, + { + dir: `${dir}/package.json`, + content: `{ + "main": "./index", + "module": "./index.mjs", + "types": "./index.d.ts" + }`, + }, + { + dir: `${dir}/index.d.ts`, + content: `export * from '../types/runtime/${dir}/index';`, } - } - - 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';`); - } - } - ] + ); + }, + }, + ], })), - /* compiler.js */ { input: 'src/compiler/index.ts', - plugins: [ - replace({ - __VERSION__: pkg.version - }), - resolve(), - commonjs({ - include: ['node_modules/**'] - }), - json(), - ts_plugin - ], + plugins: [version, resolve(), commonjs({ include: ['node_modules/**'] }), json(), ts_plugin], output: { file: 'compiler.js', format: is_publish ? 'umd' : 'cjs', name: 'svelte', sourcemap: true, }, - external: is_publish - ? [] - : id => id === 'acorn' || id === 'magic-string' || id.startsWith('css-tree') - } + external: is_publish ? [] : (id) => id === 'acorn' || id === 'magic-string' || id.startsWith('css-tree'), + }, ]; diff --git a/src/compiler/compile/render_dom/wrappers/InlineComponent/index.ts b/src/compiler/compile/render_dom/wrappers/InlineComponent/index.ts index 4b1e787cbe..7b9aec83f7 100644 --- a/src/compiler/compile/render_dom/wrappers/InlineComponent/index.ts +++ b/src/compiler/compile/render_dom/wrappers/InlineComponent/index.ts @@ -41,11 +41,11 @@ export default class InlineComponentWrapper extends Wrapper { block.add_dependencies(this.node.expression.dependencies); } - this.node.attributes.forEach(attr => { + this.node.attributes.forEach((attr) => { block.add_dependencies(attr.dependencies); }); - this.node.bindings.forEach(binding => { + this.node.bindings.forEach((binding) => { if (binding.is_contextual) { // we need to ensure that the each block creates a context including // the list and the index, if they're not otherwise referenced @@ -58,7 +58,7 @@ export default class InlineComponentWrapper extends Wrapper { block.add_dependencies(binding.expression.dependencies); }); - this.node.handlers.forEach(handler => { + this.node.handlers.forEach((handler) => { if (handler.expression) { block.add_dependencies(handler.expression.dependencies); } @@ -66,16 +66,17 @@ export default class InlineComponentWrapper extends Wrapper { this.var = { type: 'Identifier', - name: ( - this.node.name === 'svelte:self' ? renderer.component.name.name : - this.node.name === 'svelte:component' ? 'switch_instance' : - sanitize(this.node.name) - ).toLowerCase() + name: (this.node.name === 'svelte:self' + ? renderer.component.name.name + : this.node.name === 'svelte:component' + ? 'switch_instance' + : sanitize(this.node.name) + ).toLowerCase(), }; if (this.node.children.length) { - this.node.lets.forEach(l => { - extract_names(l.value || l.name).forEach(name => { + this.node.lets.forEach((l) => { + extract_names(l.value || l.name).forEach((name) => { renderer.add_to_context(name, true); }); }); @@ -83,7 +84,7 @@ export default class InlineComponentWrapper extends Wrapper { const default_slot = block.child({ comment: create_debugging_comment(node, renderer.component), name: renderer.component.get_unique_name(`create_default_slot`), - type: 'slot' + type: 'slot', }); this.renderer.blocks.push(default_slot); @@ -94,7 +95,7 @@ export default class InlineComponentWrapper extends Wrapper { const dependencies: Set = new Set(); // TODO is this filtering necessary? (I *think* so) - default_slot.dependencies.forEach(name => { + default_slot.dependencies.forEach((name) => { if (!this.node.scope.is_let(name)) { dependencies.add(name); } @@ -121,11 +122,7 @@ export default class InlineComponentWrapper extends Wrapper { } } - render( - block: Block, - parent_node: Identifier, - parent_nodes: Identifier - ) { + render(block: Block, parent_node: Identifier, parent_nodes: Identifier) { this.warn_if_reactive(); const { renderer } = this; @@ -143,14 +140,14 @@ export default class InlineComponentWrapper extends Wrapper { const default_slot = this.slots.get('default'); this.fragment.nodes.forEach((child) => { - child.render(default_slot.block, null, x`#nodes` as unknown as Identifier); + child.render(default_slot.block, null, (x`#nodes` as unknown) as Identifier); }); } let props; const name_changes = block.get_unique_name(`${name.name}_changes`); - const uses_spread = !!this.node.attributes.find(a => a.is_spread); + const uses_spread = !!this.node.attributes.find((a) => a.is_spread); // removing empty slot for (const slot of this.slots.keys()) { @@ -160,23 +157,24 @@ export default class InlineComponentWrapper extends Wrapper { } } - const initial_props = this.slots.size > 0 - ? [ - p`$$slots: { + const initial_props = + this.slots.size > 0 + ? [ + p`$$slots: { ${Array.from(this.slots).map(([name, slot]) => { return p`${name}: [${slot.block.name}, ${slot.get_context || null}, ${slot.get_changes || null}]`; })} }`, - p`$$scope: { + p`$$scope: { ctx: #ctx - }` - ] - : []; + }`, + ] + : []; const attribute_object = uses_spread ? x`{ ${initial_props} }` : x`{ - ${this.node.attributes.map(attr => p`${attr.name}: ${attr.get_value(block)}`)}, + ${this.node.attributes.map((attr) => p`${attr.name}: ${attr.get_value(block)}`)}, ${initial_props} }`; @@ -198,8 +196,8 @@ export default class InlineComponentWrapper extends Wrapper { } const fragment_dependencies = new Set(this.fragment ? ['$$scope'] : []); - this.slots.forEach(slot => { - slot.block.dependencies.forEach(name => { + this.slots.forEach((slot) => { + slot.block.dependencies.forEach((name) => { const is_let = slot.scope.is_let(name); const variable = renderer.component.var_lookup.get(name); @@ -207,9 +205,12 @@ export default class InlineComponentWrapper extends Wrapper { }); }); - const dynamic_attributes = this.node.attributes.filter(a => a.get_dependencies().length > 0); + const dynamic_attributes = this.node.attributes.filter((a) => a.get_dependencies().length > 0); - if (!uses_spread && (dynamic_attributes.length > 0 || this.node.bindings.length > 0 || fragment_dependencies.size > 0)) { + if ( + !uses_spread && + (dynamic_attributes.length > 0 || this.node.bindings.length > 0 || fragment_dependencies.size > 0) + ) { updates.push(b`const ${name_changes} = {};`); } @@ -222,16 +223,17 @@ export default class InlineComponentWrapper extends Wrapper { const all_dependencies: Set = new Set(); - this.node.attributes.forEach(attr => { + this.node.attributes.forEach((attr) => { add_to_set(all_dependencies, attr.dependencies); }); this.node.attributes.forEach((attr, i) => { const { name, dependencies } = attr; - const condition = dependencies.size > 0 && (dependencies.size !== all_dependencies.size) - ? renderer.dirty(Array.from(dependencies)) - : null; + const condition = + dependencies.size > 0 && dependencies.size !== all_dependencies.size + ? renderer.dirty(Array.from(dependencies)) + : null; const unchanged = dependencies.size === 0; let change_object; @@ -251,19 +253,11 @@ export default class InlineComponentWrapper extends Wrapper { } changes.push( - unchanged - ? x`${levels}[${i}]` - : condition - ? x`${condition} && ${change_object}` - : change_object + unchanged ? x`${levels}[${i}]` : condition ? x`${condition} && ${change_object}` : change_object ); }); - block.chunks.init.push(b` - const ${levels} = [ - ${initial_props} - ]; - `); + block.chunks.init.push(b`const ${levels} = [${initial_props}];`); statements.push(b` for (let #i = 0; #i < ${levels}.length; #i += 1) { @@ -275,9 +269,7 @@ export default class InlineComponentWrapper extends Wrapper { const condition = renderer.dirty(Array.from(all_dependencies)); updates.push(b` - const ${name_changes} = ${condition} ? @get_spread_update(${levels}, [ - ${changes} - ]) : {} + const ${name_changes} = ${condition} ? @get_spread_update(${levels}, [${changes}]) : {}; `); } else { updates.push(b` @@ -305,7 +297,7 @@ export default class InlineComponentWrapper extends Wrapper { }`); } - const munged_bindings = this.node.bindings.map(binding => { + const munged_bindings = this.node.bindings.map((binding) => { component.has_reactive_assignments = true; if (binding.name === 'this') { @@ -321,17 +313,13 @@ export default class InlineComponentWrapper extends Wrapper { const snippet = binding.expression.manipulate(block); - statements.push(b` - if (${snippet} !== void 0) { - ${props}.${binding.name} = ${snippet}; - }` - ); + statements.push(b`if (${snippet} !== undefined) ${props}.${binding.name} = ${snippet};`); updates.push(b` if (!${updating} && ${renderer.dirty(Array.from(binding.expression.dependencies))}) { ${updating} = true; ${name_changes}.${binding.name} = ${snippet}; - @add_flush_callback(() => ${updating} = false); + @add_flush_callback(() => {${updating} = false;}); } `); @@ -350,48 +338,31 @@ export default class InlineComponentWrapper extends Wrapper { } const params = [x`#value`]; - if (contextual_dependencies.length > 0) { - const args = []; - - contextual_dependencies.forEach(name => { - params.push({ - type: 'Identifier', - name - }); - - renderer.add_to_context(name, true); - args.push(renderer.reference(name)); - }); - - - block.chunks.init.push(b` - function ${id}(#value) { - ${callee}.call(null, #value, ${args}); - } - `); - - block.maintain_context = true; // TODO put this somewhere more logical - } else { - block.chunks.init.push(b` - function ${id}(#value) { - ${callee}.call(null, #value); - } - `); - } + let args = []; + contextual_dependencies.forEach((name) => { + params.push({ type: 'Identifier', name }); + renderer.add_to_context(name, true); + args.push(renderer.reference(name)); + // TODO put this somewhere more logical + block.maintain_context = true; + }); + block.chunks.init.push(b` + function ${id}(#value) { + ${callee}.call(null, #value, ${args.length ? args : null}); + } + `); - const body = b` + component.partly_hoisted.push(b` function ${id}(${params}) { ${lhs} = #value; ${renderer.invalidate(dependencies[0])}; } - `; - - component.partly_hoisted.push(body); + `); return b`@binding_callbacks.push(() => @bind(${this.var}, '${binding.name}', ${id}));`; }); - const munged_handlers = this.node.handlers.map(handler => { + const munged_handlers = this.node.handlers.map((handler) => { const event_handler = new EventHandler(handler, this); let snippet = event_handler.get_snippet(block); if (handler.modifiers.has('once')) snippet = x`@once(${snippet})`; @@ -409,8 +380,7 @@ export default class InlineComponentWrapper extends Wrapper { var ${switch_value} = ${snippet}; function ${switch_props}(#ctx) { - ${(this.node.attributes.length > 0 || this.node.bindings.length > 0) && b` - ${props && b`let ${props} = ${attribute_object};`}`} + ${props && (this.node.attributes.length || this.node.bindings.length) && b`let ${props} = ${attribute_object};`} ${statements} return ${component_opts}; } @@ -423,14 +393,10 @@ export default class InlineComponentWrapper extends Wrapper { } `); - block.chunks.create.push( - b`if (${name}) @create_component(${name}.$$.fragment);` - ); + block.chunks.create.push(b`if (${name} && ${name}.$$.fragment) ${name}.$$.fragment.c();`); if (parent_nodes && this.renderer.options.hydratable) { - block.chunks.claim.push( - b`if (${name}) @claim_component(${name}.$$.fragment, ${parent_nodes});` - ); + block.chunks.claim.push(b`if (${name} && ${name}.$$.fragment) ${name}.$$.fragment.l(${parent_nodes});`); } block.chunks.mount.push(b` @@ -465,7 +431,7 @@ export default class InlineComponentWrapper extends Wrapper { ${munged_bindings} ${munged_handlers} - @create_component(${name}.$$.fragment); + if(${name}.$$.fragment) ${name}.$$.fragment.c(); @transition_in(${name}.$$.fragment, 1); @mount_component(${name}, ${update_mount_node}, ${anchor}); } else { @@ -480,19 +446,18 @@ export default class InlineComponentWrapper extends Wrapper { if (${name}) @transition_in(${name}.$$.fragment, #local); `); - block.chunks.outro.push( - b`if (${name}) @transition_out(${name}.$$.fragment, #local);` - ); + block.chunks.outro.push(b`if (${name}) @transition_out(${name}.$$.fragment, #local);`); block.chunks.destroy.push(b`if (${name}) @destroy_component(${name}, ${parent_node ? null : 'detaching'});`); } else { - const expression = this.node.name === 'svelte:self' - ? component.name - : this.renderer.reference(this.node.name); + const expression = this.node.name === 'svelte:self' ? component.name : this.renderer.reference(this.node.name); block.chunks.init.push(b` - ${(this.node.attributes.length > 0 || this.node.bindings.length > 0) && b` - ${props && b`let ${props} = ${attribute_object};`}`} + ${ + (this.node.attributes.length > 0 || this.node.bindings.length > 0) && + b` + ${props && b`let ${props} = ${attribute_object};`}` + } ${statements} const ${name} = new ${expression}(${component_opts}); @@ -500,12 +465,10 @@ export default class InlineComponentWrapper extends Wrapper { ${munged_handlers} `); - block.chunks.create.push(b`@create_component(${name}.$$.fragment);`); + block.chunks.create.push(b`if(${name}.$$.fragment) ${name}.$$.fragment.c();`); if (parent_nodes && this.renderer.options.hydratable) { - block.chunks.claim.push( - b`@claim_component(${name}.$$.fragment, ${parent_nodes});` - ); + block.chunks.claim.push(b`if(${name}.$$.fragment) ${name}.$$.fragment.l(${parent_nodes});`); } block.chunks.mount.push( @@ -527,9 +490,7 @@ export default class InlineComponentWrapper extends Wrapper { @destroy_component(${name}, ${parent_node ? null : 'detaching'}); `); - block.chunks.outro.push( - b`@transition_out(${name}.$$.fragment, #local);` - ); + block.chunks.outro.push(b`@transition_out(${name}.$$.fragment, #local);`); } } } diff --git a/src/runtime/animate/index.ts b/src/runtime/animate/index.ts index 087c0f7141..17ac9e9458 100644 --- a/src/runtime/animate/index.ts +++ b/src/runtime/animate/index.ts @@ -1,14 +1,6 @@ import { cubicOut } from 'svelte/easing'; -import { is_function } from 'svelte/internal'; +import { AnimationConfig } 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; -} interface FlipParams { delay: number; @@ -16,27 +8,23 @@ interface FlipParams { easing: (t: number) => number; } -export function flip(node: Element, animation: { from: DOMRect; to: DOMRect }, params: FlipParams): AnimationConfig { - const style = getComputedStyle(node); - const transform = style.transform === 'none' ? '' : style.transform; +export function flip( + node: Element, + animation: { from: DOMRect; to: DOMRect }, + { delay = 0, duration = (d: number) => Math.sqrt(d) * 120, easing = cubicOut }: FlipParams +): AnimationConfig { + const style = getComputedStyle(node).transform; + const transform = style === 'none' ? '' : style; const scaleX = animation.from.width / node.clientWidth; const scaleY = animation.from.height / node.clientHeight; const dx = (animation.from.left - animation.to.left) / scaleX; const dy = (animation.from.top - animation.to.top) / scaleY; - const d = Math.sqrt(dx * dx + dy * dy); - - const { - delay = 0, - duration = (d: number) => Math.sqrt(d) * 120, - easing = cubicOut - } = params; - return { delay, - duration: is_function(duration) ? duration(d) : duration, + duration: typeof duration === 'function' ? duration(Math.sqrt(dx * dx + dy * dy)) : 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);`, }; } diff --git a/src/runtime/easing/index.ts b/src/runtime/easing/index.ts index c23019b320..37f98f072b 100644 --- a/src/runtime/easing/index.ts +++ b/src/runtime/easing/index.ts @@ -1,171 +1,69 @@ -/* -Adapted from https://github.com/mattdesl -Distributed under MIT License https://github.com/mattdesl/eases/blob/master/LICENSE.md -*/ - export { identity as linear } from 'svelte/internal'; - -export function backInOut(t: number) { - const s = 1.70158 * 1.525; - if ((t *= 2) < 1) return 0.5 * (t * t * ((s + 1) * t - s)); - return 0.5 * ((t -= 2) * t * ((s + 1) * t + s) + 2); -} - -export function backIn(t: number) { - const s = 1.70158; - return t * t * ((s + 1) * t - s); -} - -export function backOut(t: number) { - const s = 1.70158; - return --t * t * ((s + 1) * t + s) + 1; -} - -export function bounceOut(t: number) { - const a = 4.0 / 11.0; - const b = 8.0 / 11.0; - const c = 9.0 / 10.0; - - const ca = 4356.0 / 361.0; - const cb = 35442.0 / 1805.0; - const cc = 16061.0 / 1805.0; - - const t2 = t * t; - - return t < a - ? 7.5625 * t2 - : t < b - ? 9.075 * t2 - 9.9 * t + 3.4 - : t < c - ? ca * t2 - cb * t + cc - : 10.8 * t * t - 20.52 * t + 10.72; -} - -export function bounceInOut(t: number) { - return t < 0.5 - ? 0.5 * (1.0 - bounceOut(1.0 - t * 2.0)) - : 0.5 * bounceOut(t * 2.0 - 1.0) + 0.5; -} - -export function bounceIn(t: number) { - return 1.0 - bounceOut(1.0 - t); -} - -export function circInOut(t: number) { - if ((t *= 2) < 1) return -0.5 * (Math.sqrt(1 - t * t) - 1); - return 0.5 * (Math.sqrt(1 - (t -= 2) * t) + 1); -} - -export function circIn(t: number) { - return 1.0 - Math.sqrt(1.0 - t * t); -} - -export function circOut(t: number) { - return Math.sqrt(1 - --t * t); -} - -export function cubicInOut(t: number) { - return t < 0.5 ? 4.0 * t * t * t : 0.5 * Math.pow(2.0 * t - 2.0, 3.0) + 1.0; -} - -export function cubicIn(t: number) { - return t * t * t; -} - -export function cubicOut(t: number) { - const f = t - 1.0; - return f * f * f + 1.0; -} - -export function elasticInOut(t: number) { - return t < 0.5 - ? 0.5 * - Math.sin(((+13.0 * Math.PI) / 2) * 2.0 * t) * - Math.pow(2.0, 10.0 * (2.0 * t - 1.0)) - : 0.5 * - Math.sin(((-13.0 * Math.PI) / 2) * (2.0 * t - 1.0 + 1.0)) * - Math.pow(2.0, -10.0 * (2.0 * t - 1.0)) + - 1.0; -} - -export function elasticIn(t: number) { - return Math.sin((13.0 * t * Math.PI) / 2) * Math.pow(2.0, 10.0 * (t - 1.0)); -} - -export function elasticOut(t: number) { - return ( - Math.sin((-13.0 * (t + 1.0) * Math.PI) / 2) * Math.pow(2.0, -10.0 * t) + 1.0 - ); -} - -export function expoInOut(t: number) { - return t === 0.0 || t === 1.0 - ? t - : t < 0.5 - ? +0.5 * Math.pow(2.0, 20.0 * t - 10.0) - : -0.5 * Math.pow(2.0, 10.0 - t * 20.0) + 1.0; -} - -export function expoIn(t: number) { - return t === 0.0 ? t : Math.pow(2.0, 10.0 * (t - 1.0)); -} - -export function expoOut(t: number) { - return t === 1.0 ? t : 1.0 - Math.pow(2.0, -10.0 * t); -} - -export function quadInOut(t: number) { - t /= 0.5; - if (t < 1) return 0.5 * t * t; - t--; - return -0.5 * (t * (t - 2) - 1); -} - -export function quadIn(t: number) { - return t * t; -} - -export function quadOut(t: number) { - return -t * (t - 2.0); -} - -export function quartInOut(t: number) { - return t < 0.5 - ? +8.0 * Math.pow(t, 4.0) - : -8.0 * Math.pow(t - 1.0, 4.0) + 1.0; -} - -export function quartIn(t: number) { - return Math.pow(t, 4.0); -} - -export function quartOut(t: number) { - return Math.pow(t - 1.0, 3.0) * (1.0 - t) + 1.0; -} - -export function quintInOut(t: number) { - if ((t *= 2) < 1) return 0.5 * t * t * t * t * t; - return 0.5 * ((t -= 2) * t * t * t * t + 2); -} - -export function quintIn(t: number) { - return t * t * t * t * t; -} - -export function quintOut(t: number) { - return --t * t * t * t * t + 1; -} - -export function sineInOut(t: number) { - return -0.5 * (Math.cos(Math.PI * t) - 1); -} - -export function sineIn(t: number) { - const v = Math.cos(t * Math.PI * 0.5); - if (Math.abs(v) < 1e-14) return 1; - else return 1 - v; -} - -export function sineOut(t: number) { - return Math.sin((t * Math.PI) / 2); -} +export const quadIn = (t: number) => t ** 2; +export const quadOut = (t: number) => 1.0 - (1.0 - t) ** 2; +export const quadInOut = (t: number) => 0.5 * (t >= 0.5 ? 2 - 2 * (1.0 - t) ** 2 : (2 * t) ** 2); +export const cubicIn = (t: number) => t ** 3; +export const cubicOut = (t: number) => 1.0 - (1.0 - t) ** 3; +export const cubicInOut = (t: number) => 0.5 * (t >= 0.5 ? 2 - (2 * (1.0 - t)) ** 3 : (2 * t) ** 3); +export const quartIn = (t: number) => t ** 4; +export const quartOut = (t: number) => 1.0 - (1.0 - t) ** 4; +export const quartInOut = (t: number) => 0.5 * (t >= 0.5 ? 2 - (2 * (1.0 - t)) ** 4 : (2 * t) ** 4); +export const easeIn = quartIn; +export const easeOut = quartOut; +export const easeInOut = quartInOut; +export const quintIn = (t: number) => t ** 5; +export const quintOut = (t: number) => 1.0 - (1.0 - t) ** 5; +export const quintInOut = (t: number) => 0.5 * (t >= 0.5 ? 2 - (2 * (1.0 - t)) ** 5 : (2 * t) ** 5); +export const backIn = (t: number) => t * t * (2.6 * t - 1.6); +export const backOut = (t: number) => 1 - (t = 1.0 - t) * t * (2.6 * t - 1.6); +export const backInOut = (t: number) => + 0.5 * (t >= 0.5 ? 2 - (t = 2 * (1 - t)) * t * (2.6 * t - 1.6) : (t = 2 * t) * t * (2.6 * t - 1.6)); +export const expoIn = (t: number) => (t ? Math.pow(2.0, 10.0 * (t - 1.0)) : t); +export const expoOut = (t: number) => (t ? 1.0 - Math.pow(2.0, -10.0 * t) : t); +export const expoInOut = (t: number) => + !t || t === 1.0 ? t : t < 0.5 ? 0.5 * Math.pow(2.0, 20.0 * t - 10.0) : -0.5 * Math.pow(2.0, 10.0 - t * 20.0) + 1.0; +export const elasticIn = (t: number) => Math.sin((13.0 * t * Math.PI) / 2) * Math.pow(2.0, 10.0 * (t - 1.0)); +export const elasticOut = (t: number) => Math.sin((-13.0 * (t + 1.0) * Math.PI) / 2) * Math.pow(2.0, -10.0 * t) + 1.0; +export const elasticInOut = (t: number) => + t < 0.5 + ? 0.5 * Math.sin(((+13.0 * Math.PI) / 2) * 2.0 * t) * Math.pow(2.0, 10.0 * (2.0 * t - 1.0)) + : 0.5 * Math.sin(((-13.0 * Math.PI) / 2) * (2.0 * t - 1.0 + 1.0)) * Math.pow(2.0, -10.0 * (2.0 * t - 1.0)) + 1.0; +export const bounceOut = (t: number) => + 4 / 11 > t + ? 7.5625 * t * t + : 8 / 11 > t + ? 3.4 + 9.075 * t * t - 9.9 * t + : 9 / 10 > t + ? 16061.0 / 1805.0 + (4356.0 * t * t) / 361.0 - (35442.0 * t) / 1805.0 + : 10.72 + 10.8 * t * t - 20.52 * t; +export const bounceIn = (t: number) => 1.0 - bounceOut(1.0 - t); +export const bounceInOut = (t: number) => + t < 0.5 ? 0.5 * (1.0 - bounceOut(1.0 - t * 2.0)) : 0.5 * bounceOut(t * 2.0 - 1.0) + 0.5; +export const sineIn = (t: number) => (1e-14 > Math.abs((t = Math.cos(t * Math.PI * 0.5))) ? 1.0 : 1.0 - t); +export const sineOut = (t: number) => Math.sin((t * Math.PI) / 2); +export const sineInOut = (t: number) => -0.5 * (Math.cos(Math.PI * t) - 1); +export const circIn = (t: number) => 1 - Math.sin(Math.acos(t)); +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) => { + 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; + return (t: number) => { + r = t; + for (let i = 0; 32 > i; i++) + if (1e-5 > Math.abs((x = r * r * (r * ax + x1 + x2) - t))) return r * (r * (r * ay + y2) + y1); + else if (1e-5 > Math.abs((d = r * (r * ax * 3.0 + x2 * 2.0) + x1))) break; + else r = r - x / d; + if ((s = 0.0) > (r = t)) return 0; + else if ((d = 1.0) > r) return 1; + while (d > s) + if (1e-5 > Math.abs((x = r * (r * (r * ax + x2) + x1)) - t)) break; + else t > x ? (s = r) : (d = r), (r = 0.5 * (d - s) + s); + return r * (r * (r * ay + y2) + y1); + }; +}; diff --git a/src/runtime/internal/Component.ts b/src/runtime/internal/Component.ts index d35abba012..57efb4bcc7 100644 --- a/src/runtime/internal/Component.ts +++ b/src/runtime/internal/Component.ts @@ -1,22 +1,32 @@ -import { add_render_callback, flush, schedule_update, dirty_components } from './scheduler'; +import { add_render_callback, flush, schedule_update } from './scheduler'; import { current_component, set_current_component } from './lifecycle'; -import { blank_object, is_function, run, run_all, noop } from './utils'; +import { blank_object, is_function, run_all, noop } from './utils'; import { children, detach } from './dom'; import { transition_in } from './transitions'; -interface Fragment { +export interface Fragment { key: string | null; first: null; - /* create */ c: () => void; - /* claim */ l: (nodes: any) => void; + /** + * create + * run once + * runs hydrate if exists + */ + c: () => void; + /** + * claim + * runs hydrate if exists + * */ + + l: (nodes: any) => void; /* hydrate */ h: () => void; - /* mount */ m: (target: HTMLElement, anchor: any) => void; + /* mount */ m: (target: HTMLElement, anchor: any, is_remount: boolean) => void; /* update */ p: (ctx: any, dirty: any) => void; /* measure */ r: () => void; /* fix */ f: () => void; /* animate */ a: () => void; - /* intro */ i: (local: any) => void; - /* outro */ o: (local: any) => void; + /* intro */ i: (local: 0 | 1) => void; + /* outro */ o: (local: 0 | 1) => void; /* destroy */ d: (detaching: 0 | 1) => void; } // eslint-disable-next-line @typescript-eslint/class-name-casing @@ -36,43 +46,25 @@ interface T$$ { on_destroy: any[]; } -export function bind(component, name, callback) { - const index = component.$$.props[name]; - if (index === undefined) return; - component.$$.bound[index] = callback; - callback(component.$$.ctx[index]); +export function bind({ $$: { props, bound, ctx } }, name: string, callback: (prop: any) => void) { + if (!(name in props)) return; + const index = props[name]; + bound[index] = callback; + callback(ctx[index]); } - -export function create_component(block) { - if (block) block.c(); -} - -export function claim_component(block, parent_nodes) { - if (block) block.l(parent_nodes); -} - -export function mount_component(component, target, anchor) { - const { fragment, on_mount, on_destroy, after_update } = component.$$; - +export function mount_component({ $$: { fragment, on_mount, on_destroy, after_update } }, target, anchor) { if (fragment) fragment.m(target, anchor); - - // onMount happens before the initial afterUpdate add_render_callback(() => { - const new_on_destroy = on_mount.map(run).filter(is_function); - if (on_destroy) { - on_destroy.push(...new_on_destroy); - } else { - // Edge case - component was destroyed immediately, - // most likely as a result of a binding initialising - run_all(new_on_destroy); - } - component.$$.on_mount = []; + for (let i = 0, res; i < on_mount.length; i++) + if (is_function((res = on_mount[i]()))) + if (on_destroy) on_destroy.push(res); + else res(); // component already destroyed + on_mount.length = 0; + for (let i = 0; i < after_update.length; i++) after_update[i](); }); - - after_update.forEach(add_render_callback); } -export function destroy_component({ $$ }, detaching) { +export function destroy_component({ $$ }, detaching: 0 | 1) { if ($$.fragment === null) return; run_all($$.on_destroy); @@ -84,17 +76,8 @@ export function destroy_component({ $$ }, detaching) { $$.ctx = []; } -function make_dirty(component, i) { - if (component.$$.dirty[0] === -1) { - dirty_components.push(component); - schedule_update(); - component.$$.dirty.fill(0); - } - component.$$.dirty[(i / 31) | 0] |= 1 << i % 31; -} - export function init( - component, + component: SvelteComponent, { props: prop_values = {}, target, hydrate, intro, anchor }, instance, create_fragment, @@ -131,8 +114,14 @@ export function init( $$.ctx = instance ? instance(component, prop_values, (i, res, val = res) => { if ($$.ctx && not_equal($$.ctx[i], ($$.ctx[i] = val))) { - if ($$.bound[i]) $$.bound[i](val); - if (ready) make_dirty(component, i); + if (i in $$.bound) $$.bound[i](val); + if (ready) { + if (!~$$.dirty) { + schedule_update(component); + $$.dirty.fill(0); + } + $$.dirty[(i / 31) | 0] |= 1 << i % 31; + } } return res; }) @@ -144,18 +133,24 @@ export function init( run_all($$.before_update); - // false when empty fragment + // false when empty $$.fragment = create_fragment ? create_fragment($$.ctx) : false; if (target) { - if (hydrate) { - const nodes = children(target); - if ($$.fragment) $$.fragment.l(nodes); - nodes.forEach(detach); - } else { - if ($$.fragment) $$.fragment.c(); + if ($$.fragment) { + if (hydrate) { + const nodes = children(target); + $$.fragment.l(nodes); + nodes.forEach(detach); + } else { + $$.fragment.c(); + } + if (intro) { + transition_in($$.fragment); + } + } else if (hydrate) { + children(target).forEach(detach); } - if (intro) transition_in(component.$$.fragment); mount_component(component, target, anchor); flush(); } diff --git a/src/runtime/internal/animations.ts b/src/runtime/internal/animations.ts index 2af46d1544..962bfd601a 100644 --- a/src/runtime/internal/animations.ts +++ b/src/runtime/internal/animations.ts @@ -1,7 +1,14 @@ import { noop } from './utils'; -import { AnimationConfig } from '../animate'; import { run_transition } from './transitions'; +export interface AnimationConfig { + delay?: number; + duration?: number; + easing?: (t: number) => number; + css?: (t: number, u?: number) => string; + tick?: (t: number, u?: number) => void; +} + //todo: documentation says it is DOMRect, but in IE it would be ClientRect type PositionRect = DOMRect | ClientRect; @@ -19,28 +26,24 @@ export function run_animation(node: HTMLElement, from: PositionRect, fn: Animati } export function fix_position(node: HTMLElement) { - const style = getComputedStyle(node); - - if (style.position !== 'absolute' && style.position !== 'fixed') { - const { width, height } = style; - const a = node.getBoundingClientRect(); - const { position: og_position, width: og_width, height: og_height } = node.style; - node.style.position = 'absolute'; - node.style.width = width; - node.style.height = height; - add_transform(node, a); - return () => { - node.style.position = og_position; - node.style.width = og_width; - node.style.height = og_height; - node.style.transform = ''; - }; - } + const { position, width, height } = getComputedStyle(node); + if (position === 'absolute' || position === 'fixed') return noop; + const current_position = node.getBoundingClientRect(); + const { position: og_position, width: og_width, height: og_height } = node.style; + node.style.position = 'absolute'; + node.style.width = width; + node.style.height = height; + add_transform(node, current_position); + return () => { + node.style.position = og_position; + node.style.width = og_width; + node.style.height = og_height; + node.style.transform = ''; // unsafe + }; } export function add_transform(node: HTMLElement, a: PositionRect) { const b = node.getBoundingClientRect(); - if (a.left !== b.left || a.top !== b.top) { const style = getComputedStyle(node); const transform = style.transform === 'none' ? '' : style.transform; diff --git a/src/runtime/internal/dev.ts b/src/runtime/internal/dev.ts index 751f1f802b..7a957c951b 100644 --- a/src/runtime/internal/dev.ts +++ b/src/runtime/internal/dev.ts @@ -1,22 +1,51 @@ import { custom_event, append, insert, detach, listen, attr } from './dom'; import { SvelteComponent } from './Component'; - -export function dispatch_dev(type: string, detail?: T) { +import { + get_current_component, + beforeUpdate, + onMount, + afterUpdate, + onDestroy, + createEventDispatcher, + setContext, + getContext, +} from './lifecycle'; +import { cubicBezier } from 'svelte/easing'; + +export const [ + beforeUpdate_dev, + onMount_dev, + afterUpdate_dev, + onDestroy_dev, + createEventDispatcher_dev, + setContext_dev, + getContext_dev, +] = [beforeUpdate, onMount, afterUpdate, onDestroy, createEventDispatcher, setContext, getContext].map( + (fn) => (a?, b?) => { + if (!get_current_component()) throw new Error(`${fn.name} cannot be called outside of component initialization`); + 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); +} +export function dispatch_dev(type: string, detail?: T) { document.dispatchEvent(custom_event(type, { version: '__VERSION__', ...detail })); } export function append_dev(target: Node, node: Node) { - dispatch_dev("SvelteDOMInsert", { target, node }); + dispatch_dev('SvelteDOMInsert', { target, node }); append(target, node); } export function insert_dev(target: Node, node: Node, anchor?: Node) { - dispatch_dev("SvelteDOMInsert", { target, node, anchor }); + dispatch_dev('SvelteDOMInsert', { target, node, anchor }); insert(target, node, anchor); } export function detach_dev(node: Node) { - dispatch_dev("SvelteDOMRemove", { node }); + dispatch_dev('SvelteDOMRemove', { node }); detach(node); } @@ -38,16 +67,23 @@ 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)) : []; +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 }); + dispatch_dev('SvelteDOMAddEventListener', { node, event, handler, modifiers }); const dispose = listen(node, event, handler, options); return () => { - dispatch_dev("SvelteDOMRemoveEventListener", { node, event, handler, modifiers }); + dispatch_dev('SvelteDOMRemoveEventListener', { node, event, handler, modifiers }); dispose(); }; } @@ -55,27 +91,27 @@ export function listen_dev(node: Node, event: string, handler: EventListenerOrEv export function attr_dev(node: Element, attribute: string, value?: string) { attr(node, attribute, value); - if (value == null) dispatch_dev("SvelteDOMRemoveAttribute", { node, attribute }); - else dispatch_dev("SvelteDOMSetAttribute", { node, attribute, value }); + if (value == null) dispatch_dev('SvelteDOMRemoveAttribute', { node, attribute }); + else dispatch_dev('SvelteDOMSetAttribute', { node, attribute, value }); } export function prop_dev(node: Element, property: string, value?: any) { node[property] = value; - dispatch_dev("SvelteDOMSetProperty", { node, property, value }); + dispatch_dev('SvelteDOMSetProperty', { node, property, value }); } export function dataset_dev(node: HTMLElement, property: string, value?: any) { node.dataset[property] = value; - dispatch_dev("SvelteDOMSetDataset", { node, property, value }); + dispatch_dev('SvelteDOMSetDataset', { node, property, value }); } export function set_data_dev(text, data) { data = '' + data; if (text.data === data) return; - dispatch_dev("SvelteDOMSetData", { node: text, data }); + dispatch_dev('SvelteDOMSetData', { node: text, data }); text.data = data; } @@ -113,7 +149,7 @@ export class SvelteComponentDev extends SvelteComponent { hydrate?: boolean; intro?: boolean; $$inline?: boolean; - }) { + }) { if (!options || (!options.target && !options.$$inline)) { throw new Error(`'target' is a required option`); } diff --git a/src/runtime/internal/dom.ts b/src/runtime/internal/dom.ts index bf8fc1f5a0..6790009ff1 100644 --- a/src/runtime/internal/dom.ts +++ b/src/runtime/internal/dom.ts @@ -1,4 +1,4 @@ -import { has_prop } from "./utils"; +import { has_prop } from './utils'; export function append(target: Node, node: Node) { target.appendChild(node); @@ -25,14 +25,13 @@ export function element(name: K) { export function element_is(name: K, is: string) { return document.createElement(name, { is }); } - export function object_without_properties(obj: T, exclude: K[]) { const target = {} as Pick>; for (const k in obj) { if ( - has_prop(obj, k) + has_prop(obj, k) && // @ts-ignore - && exclude.indexOf(k) === -1 + exclude.indexOf(k) === -1 ) { // @ts-ignore target[k] = obj[k]; @@ -57,13 +56,18 @@ export function empty() { return text(''); } -export function listen(node: EventTarget, event: string, handler: EventListenerOrEventListenerObject, options?: boolean | AddEventListenerOptions | EventListenerOptions) { +export function listen( + node: EventTarget, + event: string, + handler: EventListenerOrEventListenerObject, + options?: boolean | AddEventListenerOptions | EventListenerOptions +) { node.addEventListener(event, handler, options); return () => node.removeEventListener(event, handler, options); } export function prevent_default(fn) { - return function(event) { + return function (event) { event.preventDefault(); // @ts-ignore return fn.call(this, event); @@ -71,7 +75,7 @@ export function prevent_default(fn) { } export function stop_propagation(fn) { - return function(event) { + return function (event) { event.stopPropagation(); // @ts-ignore return fn.call(this, event); @@ -79,7 +83,7 @@ export function stop_propagation(fn) { } export function self(fn) { - return function(event) { + return function (event) { // @ts-ignore if (event.target === this) fn.call(this, event); }; @@ -98,7 +102,7 @@ export function set_attributes(node: Element & ElementCSSInlineStyle, attributes node.removeAttribute(key); } else if (key === 'style') { node.style.cssText = attributes[key]; - } else if (key === '__value' || descriptors[key] && descriptors[key].set) { + } else if (key === '__value' || (descriptors[key] && descriptors[key].set)) { node[key] = attributes[key]; } else { attr(node, key, attributes[key]); @@ -144,9 +148,7 @@ export function time_ranges_to_array(ranges) { return array; } -export function children(element) { - return Array.from(element.childNodes); -} +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) { @@ -231,7 +233,7 @@ export function select_value(select) { } export function select_multiple_value(select) { - return [].map.call(select.querySelectorAll(':checked'), option => option.__value); + return [].map.call(select.querySelectorAll(':checked'), (option) => option.__value); } // unfortunately this can't be a constant as that wouldn't be tree-shakeable @@ -263,9 +265,10 @@ export function add_resize_listener(node: HTMLElement, fn: () => void) { } const iframe = element('iframe'); - iframe.setAttribute('style', + iframe.setAttribute( + '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};` + `overflow: hidden; border: 0; opacity: 0; pointer-events: none; z-index: ${z_index};` ); iframe.setAttribute('aria-hidden', 'true'); iframe.tabIndex = -1; @@ -296,7 +299,7 @@ export function toggle_class(element, name, toggle) { element.classList[toggle ? 'add' : 'remove'](name); } -export function custom_event(type: string, detail?: T) { +export function custom_event(type: string, detail?: T) { const e: CustomEvent = document.createEvent('CustomEvent'); e.initCustomEvent(type, false, false, detail); return e; diff --git a/src/runtime/internal/environment.ts b/src/runtime/internal/environment.ts index 7123399180..8c9a4a449f 100644 --- a/src/runtime/internal/environment.ts +++ b/src/runtime/internal/environment.ts @@ -1,18 +1,16 @@ 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: () => number = is_client - ? () => window.performance.now() - : () => Date.now(); +export let now = is_client ? window.performance.now : () => Date.now(); -export let raf = is_client ? cb => requestAnimationFrame(cb) : noop; +export let raf = is_client ? window.requestAnimationFrame : noop; // used internally for testing export function set_now(fn) { now = fn; } - export function set_raf(fn) { raf = fn; } diff --git a/src/runtime/internal/lifecycle.ts b/src/runtime/internal/lifecycle.ts index a8e37e9632..3f51d0a23e 100644 --- a/src/runtime/internal/lifecycle.ts +++ b/src/runtime/internal/lifecycle.ts @@ -2,9 +2,7 @@ import { custom_event } from './dom'; export let current_component; -export function set_current_component(component) { - current_component = component; -} +export const set_current_component = (component) => (current_component = component); export function get_current_component() { if (!current_component) throw new Error(`Function called outside component initialization`); @@ -32,15 +30,13 @@ export function createEventDispatcher() { return (type: string, detail?: any) => { const callbacks = component.$$.callbacks[type]; - - if (callbacks) { - // TODO are there situations where events could be dispatched - // in a server (non-DOM) environment? - const event = custom_event(type, detail); - callbacks.slice().forEach(fn => { - fn.call(component, event); - }); - } + if (!callbacks) return; + // TODO are there situations where events could be dispatched + // in a server (non-DOM) environment? + const event = custom_event(type, detail); + callbacks.forEach((fn) => { + fn.call(component, event); + }); }; } @@ -59,6 +55,6 @@ export function bubble(component, event) { const callbacks = component.$$.callbacks[event.type]; if (callbacks) { - callbacks.slice().forEach(fn => fn(event)); + callbacks.slice().forEach((fn) => fn(event)); } -} \ No newline at end of file +} diff --git a/src/runtime/internal/loop.ts b/src/runtime/internal/loop.ts index a3719bc58d..981f4d0cfc 100644 --- a/src/runtime/internal/loop.ts +++ b/src/runtime/internal/loop.ts @@ -43,37 +43,35 @@ export function loop(callback: TaskCallback): Task { }; } -function add(c) { +function add(c: TaskCallback) { const task = { c, f: noop }; if (!tasks.size) raf(run_tasks); tasks.add(task); return () => tasks.delete(task); } -const timed_tasks = []; +type TimeoutTask = { t: number; c: (now: number) => void }; + +// sorted descending +const timed_tasks: Array = []; /** - * Callback on 1st frame after timestamp + * basically + * (fn, t) => setTimeout( () => raf(fn), t ) */ -export function raf_timeout(callback: () => void, timestamp: number) { +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) { - // bubble sort descending until timestamp < task.timestamp - timed_tasks[i--] = v; - } + 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) { - // pop() until now < task.timestamp - timed_tasks.pop().c(now); - } - return timed_tasks.length; + while (i > 0 && now >= timed_tasks[--i].t) timed_tasks.pop().c(now); + return !!timed_tasks.length; }); } return () => { @@ -81,11 +79,10 @@ export function raf_timeout(callback: () => void, timestamp: number) { if (~index) timed_tasks.splice(index, 1); }; } -export function loopThen(delay: number, run: (now: number) => void, stop: () => void, end_time: number) { - const fn = () => add((now) => (now < end_time ? (run(now), true) : (stop(), false))); - if (delay < 16) return fn(); - else { - let cancel = raf_timeout(() => (cancel = fn()), now() + delay - 16.6667); - return () => cancel(); - } -} +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; + }); diff --git a/src/runtime/internal/scheduler.ts b/src/runtime/internal/scheduler.ts index f393c0e4f8..b33349a56b 100644 --- a/src/runtime/internal/scheduler.ts +++ b/src/runtime/internal/scheduler.ts @@ -1,34 +1,25 @@ -import { run_all } from './utils'; import { set_current_component } from './lifecycle'; -export const dirty_components = []; -export const intros = { enabled: false }; - -export const binding_callbacks = []; -const render_callbacks = []; -const flush_callbacks = []; +const dirty_components = []; const resolved_promise = Promise.resolve(); -let update_scheduled = false; -export function schedule_update() { +let update_scheduled = false; +export function schedule_update(component) { + dirty_components.push(component); if (update_scheduled) return; - update_scheduled = true; - resolved_promise.then(flush); + (update_scheduled = true), resolved_promise.then(flush); } - export function tick() { - schedule_update(); + if (!update_scheduled) (update_scheduled = true), resolved_promise.then(flush); return resolved_promise; } +export const binding_callbacks = []; +const render_callbacks = []; +export const add_render_callback = (fn) => render_callbacks.push(fn); -export function add_render_callback(fn) { - render_callbacks.push(fn); -} - -export function add_flush_callback(fn) { - flush_callbacks.push(fn); -} +const flush_callbacks = []; +export const add_flush_callback = (fn) => flush_callbacks.push(fn); let flushing = false; const seen_callbacks = new Set(); @@ -36,45 +27,36 @@ export function flush() { if (flushing) return; flushing = true; - do { + for (; dirty_components.length; ) { // update components + beforeUpdate - for (let i = 0, component; i < dirty_components.length; i++) { - set_current_component((component = dirty_components[i])); - update(component.$$); + for (let i = 0, $$; i < dirty_components.length; i++) { + ({ $$ } = set_current_component(dirty_components[i])); + if ($$.fragment === null) continue; + const { update, before_update, dirty, after_update } = $$; + update(); + for (let j = 0; j < before_update.length; j++) before_update[j](); + $$.dirty = [-1]; + if ($$.fragment) $$.fragment.p($$.ctx, dirty); + render_callbacks.push(...after_update); } dirty_components.length = 0; - // update bindings - for (let i = 0; i < binding_callbacks.length; i++) { - binding_callbacks[i](); - } + // update bindings in reverse order + for (let i = binding_callbacks.length - 1; i; 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(); + seen_callbacks.add(callback), callback(); } render_callbacks.length = 0; - } while (dirty_components.length); - - for (let i = 0; i < flush_callbacks.length; i++) { - flush_callbacks[i](); } + + for (let i = 0; i < flush_callbacks.length; i++) flush_callbacks[i](); flush_callbacks.length = 0; + seen_callbacks.clear(); update_scheduled = false; flushing = false; - seen_callbacks.clear(); -} - -function update($$) { - if ($$.fragment === null) return; - $$.update(); - run_all($$.before_update); - const dirty = $$.dirty; - $$.dirty = [-1]; - $$.fragment && $$.fragment.p($$.ctx, dirty); - $$.after_update.forEach(add_render_callback); } diff --git a/src/runtime/internal/style_manager.ts b/src/runtime/internal/style_manager.ts index bd29875062..6ec2183b34 100644 --- a/src/runtime/internal/style_manager.ts +++ b/src/runtime/internal/style_manager.ts @@ -1,71 +1,74 @@ import { element } from './dom'; +import { raf } from './environment'; +const enum SVELTE { + RULE = `__svelte_`, + STYLESHEET = `__svelte_stylesheet`, + RULESET = `__svelte_rules`, +} -const svelte_rule = `__svelte_`; - +let FRAME_RATE; +function calc_framerate() { + const f24 = 1000 / 24, + f60 = 1000 / 60, + f144 = 1000 / 144; + raf((t1) => { + raf((d) => { + FRAME_RATE = (d = d - t1) > f144 ? f144 : d < f24 ? f24 : d; + }); + }); + return (FRAME_RATE = f60); +} interface ExtendedDoc extends Document { - __svelte_stylesheet: CSSStyleSheet; - __svelte_rules: Set; + [SVELTE.STYLESHEET]: CSSStyleSheet; + [SVELTE.RULESET]: Set; } const active_documents = new Set(); let running_animations = 0; -function rulesheet({ ownerDocument }): [CSSStyleSheet, Set] { - const doc = ownerDocument as ExtendedDoc; - if (!active_documents.has(doc)) { - active_documents.add(doc); - if (!doc.__svelte_stylesheet) { - doc.__svelte_stylesheet = doc.head.appendChild(element('style')).sheet as CSSStyleSheet; - } - if (!doc.__svelte_rules) { - doc.__svelte_rules = new Set(); - } +function add_rule(node, name, rule): [CSSStyleSheet, Set] { + const { ownerDocument } = node; + if (!active_documents.has(ownerDocument)) { + active_documents.add(ownerDocument); + if (!(SVELTE.STYLESHEET in ownerDocument)) + ownerDocument[SVELTE.STYLESHEET] = ownerDocument.head.appendChild(element('style')).sheet; + if (!(SVELTE.RULESET in ownerDocument)) ownerDocument[SVELTE.RULESET] = new Set(); } - return [doc.__svelte_stylesheet, doc.__svelte_rules]; + return [ownerDocument[SVELTE.STYLESHEET], ownerDocument[SVELTE.RULESET]]; } // https://github.com/darkskyapp/string-hash/blob/master/index.js function hash(str: string) { let hash = 5381; let i = str.length; - while (i--) hash = ((hash << 5) - hash) ^ str.charCodeAt(i); return hash >>> 0; } -export function generate_rule( - node: HTMLElement, - a: number, - b: number, - duration: number, - delay: number, - ease: (t: number) => number, - fn: (t: number, u: number) => string -) { - const step = 16.6667 / duration; +const gen = (t, step, css) => { let rule = '{\n'; - for (let p = 0, t = 0; p <= 1; p += step) { - t = a + (b - a) * ease(p); - rule += p * 100 + `%{${fn(t, 1 - t)}}\n`; - } - rule += `100% {${fn(b, 1 - b)}}\n}`; - const name = `${svelte_rule}${hash(rule)}`; - const [stylesheet, rules] = rulesheet(node); + for (; t < 1; t += step) rule += `${100 * t}%{${css(t)}}\n`; + rule += `100% {${css(1)}}\n}`; + const name = SVELTE.RULE + hash(rule); + return [name, `@keyframes ${name} ${rule}`]; +}; +export function animate_css(css: (t: number) => string, node: HTMLElement, duration: number, t = 0) { + const [name, rule] = gen(t, duration / (FRAME_RATE || calc_framerate()), css); + const [stylesheet, rules] = add_rule(node, rule); if (!rules.has(name)) { rules.add(name); - stylesheet.insertRule(`@keyframes ${name} ${rule}`, stylesheet.cssRules.length); + stylesheet.insertRule(rule, stylesheet.cssRules.length); } - const previous = node.style.animation || ''; - node.style.animation = `${previous ? `${previous}, ` : ``}${name} ${duration}ms linear ${delay}ms 1 both`; + const previous = node.style.animation; + node.style.animation = (previous ? previous + ', ' : '') + `${duration}ms linear 0ms 1 normal both running ${name}`; running_animations++; return () => { const prev = (node.style.animation || '').split(', '); const next = prev.filter((anim) => !anim.includes(name)); - if (prev.length === next.length) return; - node.style.animation = next.join(', '); + if (prev.length !== next.length) node.style.animation = next.join(', '); if (--running_animations) return; - active_documents.forEach(({ __svelte_stylesheet, __svelte_rules }) => { - let i = __svelte_stylesheet.cssRules.length; - while (i--) __svelte_stylesheet.deleteRule(i); - __svelte_rules.clear(); + active_documents.forEach(({ [SVELTE.STYLESHEET]: stylesheet, [SVELTE.RULESET]: ruleset }) => { + let i = stylesheet.cssRules.length; + while (i--) stylesheet.deleteRule(i); + ruleset.clear(); }); active_documents.clear(); }; @@ -75,7 +78,7 @@ export function delete_rule(node: HTMLElement, name?: string) { const next = previous.filter( name ? (anim) => anim.indexOf(name) < 0 // remove specific animation - : (anim) => anim.indexOf('__svelte') === -1 // remove all Svelte animations + : (anim) => anim.indexOf(SVELTE.RULE) === -1 // remove all Svelte animations ); const deleted = previous.length - next.length; if (deleted) { diff --git a/src/runtime/internal/transitions.ts b/src/runtime/internal/transitions.ts index 6a7094ef13..ccd57acafd 100644 --- a/src/runtime/internal/transitions.ts +++ b/src/runtime/internal/transitions.ts @@ -1,10 +1,11 @@ -import { identity as linear, run_all, is_function } from './utils'; +import { run_all } from './utils'; import { now } from './environment'; -import { raf_timeout, loopThen } from './loop'; -import { generate_rule } from './style_manager'; +import { setAnimationTimeout, loopThen } from './loop'; +import { animate_css } from './style_manager'; import { custom_event } from './dom'; import { TransitionConfig } from '../transition'; import { add_render_callback, add_flush_callback } from './scheduler'; +import { Fragment } from './Component'; function startStopDispatcher(node: Element, direction: boolean) { add_render_callback(() => node.dispatchEvent(custom_event(`${direction ? 'intro' : 'outro'}start`))); @@ -12,13 +13,13 @@ function startStopDispatcher(node: Element, direction: boolean) { } const outroing = new Set(); -let outros; +let outros; export function group_outros() { outros = { - /* parent group */ p: outros, - /* remaining outros */ r: 0, - /* callbacks */ c: [], + /* parent group */ p: outros, + /* callbacks */ c: [], + /* remaining outros */ r: 0, }; } @@ -27,13 +28,13 @@ export function check_outros() { outros = outros.p; } -export function transition_in(block, local?: 0 | 1) { +export function transition_in(block: Fragment, local?: 0 | 1) { if (!block || !block.i) return; outroing.delete(block); block.i(local); } -export function transition_out(block, local?: 0 | 1, detach?: 0 | 1, callback?: () => void) { +export function transition_out(block: Fragment, local?: 0 | 1, detach?: 0 | 1, callback?: () => void) { if (!block || !block.o || outroing.has(block)) return; outroing.add(block); outros.c.push(() => { @@ -46,15 +47,16 @@ export function transition_out(block, local?: 0 | 1, detach?: 0 | 1, callback?: block.o(local); } -const null_transition: TransitionConfig = { duration: 0 }; - +const eased = (fn: (t: number) => any, easing: (t: number) => number) => (easing ? (t: number) => fn(easing(t)) : fn); +const runner = (fn: (t0: number, t1: number) => any, reversed: boolean) => + reversed ? (t: number) => fn(1 - t, t) : (t: number) => fn(t, 1 - t); type TransitionFn = (node: HTMLElement, params: any) => TransitionConfig; export function run_transition( node: HTMLElement, fn: TransitionFn, - is_intro: boolean, - params?: any, - reversed_from?: number + is_intro = true, + params = {}, + reversed_from = -1 ): StopResetReverse { let config = fn(node, params); let running = true; @@ -62,51 +64,48 @@ export function run_transition( let cancel_css; let cancel_raf; let dispatch_end; - let start_time; + let end_time; const group = outros; if (!is_intro) group.r++; - function start({ delay = 0, duration = 300, easing = linear, tick, css } = null_transition) { + function start({ delay = 0, duration = 300, easing, tick, css }: TransitionConfig) { if (!running) return; - const run = tick && (is_intro ? tick : (a, b) => tick(b, a)); - if (reversed_from) delay += duration - (now() - reversed_from); - start_time = now() + delay; - const end_time = start_time + duration; - cancel_css = css && generate_rule(node, +!is_intro, +is_intro, duration, delay, easing, css); + const start_time = ~reversed_from ? reversed_from : now() + delay; + end_time = start_time + duration; + if (css) + cancel_css = animate_css( + runner(eased(css, easing), is_intro), + node, + duration, + (end_time - start_time) / duration + ); dispatch_end = startStopDispatcher(node, is_intro); - cancel_raf = cancel_raf = !run - ? raf_timeout(stop, end_time) - : loopThen( - delay, - (t) => ((t = easing((t - start_time) / duration)), run(t, 1 - t)), - () => (run(1, 0), stop()), - end_time - ); + cancel_raf = tick + ? loopThen(runner(eased(tick, easing), is_intro), stop, duration, end_time) + : setAnimationTimeout(stop, end_time); } - - function stop(reset_reverse?: 1 | 2) { - if (!is_intro && reset_reverse === 1 && config && 'tick' in config) config.tick(1, 0); + function stop(reset_reverse?: 1 | -1) { + if (!is_intro && 1 === reset_reverse && config && 'tick' in config) config.tick(1, 0); if (!running) return; else running = false; if (cancel_css) cancel_css(); if (cancel_raf) cancel_raf(); if (dispatch_end) dispatch_end(); - if (!is_intro && !--group.r) run_all(group.c); - if (reset_reverse === 2) return run_transition(node, fn, !is_intro, params, start_time); + if (!is_intro && !--group.r) for (let i = 0; i < group.c.length; i++) group.c[i](); + if (!~reset_reverse) return run_transition(node, fn, !is_intro, params, end_time); else if (!~reversed_from) running_bidi.delete(node); } // @ts-ignore - if (is_function(config)) add_flush_callback(() => start((config = config()))); + if (typeof config === 'function') add_flush_callback(() => start((config = config()))); else start(config); return stop; } -export type StopResetReverse = (reset_reverse?: 1 | 2) => StopResetReverse; +export type StopResetReverse = (reset_reverse?: 1 | -1) => StopResetReverse; const running_bidi: Map = new Map(); export function run_bidirectional_transition(node: HTMLElement, fn: TransitionFn, is_intro: boolean, params: any) { - if (running_bidi.has(node)) { - running_bidi.set(node, running_bidi.get(node)(2)); - } else { - running_bidi.set(node, run_transition(node, fn, is_intro, params, -1)); - } + let cancel; + if (running_bidi.has(node)) running_bidi.set(node, (cancel = running_bidi.get(node)(-1))); + else running_bidi.set(node, (cancel = run_transition(node, fn, is_intro, params, -1))); + return cancel; } diff --git a/src/runtime/internal/utils.ts b/src/runtime/internal/utils.ts index 487116b655..8cdeb35187 100644 --- a/src/runtime/internal/utils.ts +++ b/src/runtime/internal/utils.ts @@ -1,6 +1,6 @@ export function noop() {} -export const identity = x => x; +export const identity = (x) => x; export function assign(tar: T, src: S): T & S { // @ts-ignore @@ -14,7 +14,7 @@ export function is_promise(value: any): value is PromiseLike { export function add_location(element, file, line, column, char) { element.__svelte_meta = { - loc: { file, line, column, char } + loc: { file, line, column, char }, }; } @@ -35,7 +35,7 @@ export function is_function(thing: any): thing is Function { } export function safe_not_equal(a, b) { - return a != a ? b == b : a !== b || ((a && typeof a === 'object') || typeof a === 'function'); + return a != a ? b == b : a !== b || (a && typeof a === 'object') || typeof a === 'function'; } export function not_equal(a, b) { @@ -49,16 +49,14 @@ export function validate_store(store, name) { } export function subscribe(store, ...callbacks) { - if (store == null) { - return noop; - } + if (store == null) return noop; const unsub = store.subscribe(...callbacks); return unsub.unsubscribe ? () => unsub.unsubscribe() : unsub; } export function get_store_value(store) { let value; - subscribe(store, _ => value = _)(); + subscribe(store, (_) => (value = _))(); return value; } @@ -74,9 +72,7 @@ export function create_slot(definition, ctx, $$scope, fn) { } export function get_slot_context(definition, ctx, $$scope, fn) { - return definition[1] && fn - ? assign($$scope.ctx.slice(), definition[1](fn(ctx))) - : $$scope.ctx; + return definition[1] && fn ? assign($$scope.ctx.slice(), definition[1](fn(ctx))) : $$scope.ctx; } export function get_slot_changes(definition, $$scope, dirty, fn) { @@ -112,13 +108,14 @@ export function exclude_internal_props(props) { export function compute_rest_props(props, keys) { const rest = {}; keys = new Set(keys); - for (const k in props) if (!keys.has(k) && k[0] !== '$') rest[k] = props[k]; + let k: string; + for (k in props) if (!keys.has(k) && k[0] !== '$') rest[k] = props[k]; return rest; } export function once(fn) { let ran = false; - return function(this: any, ...args) { + return function (this: any, ...args) { if (ran) return; ran = true; fn.call(this, ...args); @@ -138,4 +135,6 @@ 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; -} \ No newline at end of file +} + +export const minmax = (min: number, max: number) => (v: number) => (v > min ? (v > max ? max : v) : min); diff --git a/src/runtime/transition/index.ts b/src/runtime/transition/index.ts index fbc3f03764..e39de6b7d9 100644 --- a/src/runtime/transition/index.ts +++ b/src/runtime/transition/index.ts @@ -1,5 +1,4 @@ import { cubicOut, cubicInOut, linear } from 'svelte/easing'; -import { is_function } from 'svelte/internal'; type EasingFunction = (t: number) => number; @@ -7,8 +6,8 @@ export interface TransitionConfig { delay?: number; duration?: number; easing?: EasingFunction; - css?: (t: number, u: number) => string; - tick?: (t: number, u: number) => void; + css?: (t: number, u?: number) => string; + tick?: (t: number, u?: number) => void; } interface BlurParams { @@ -96,21 +95,20 @@ export function slide(node: Element, { delay = 0, duration = 400, easing = cubic const margin_bottom = parseFloat(style.marginBottom); const border_top_width = parseFloat(style.borderTopWidth); const border_bottom_width = parseFloat(style.borderBottomWidth); - return { delay, duration, easing, - css: (t) => - `overflow: hidden;` + - `opacity: ${Math.min(t * 20, 1) * opacity};` + - `height: ${t * height}px;` + - `padding-top: ${t * padding_top}px;` + - `padding-bottom: ${t * padding_bottom}px;` + - `margin-top: ${t * margin_top}px;` + - `margin-bottom: ${t * margin_bottom}px;` + - `border-top-width: ${t * border_top_width}px;` + - `border-bottom-width: ${t * border_bottom_width}px;`, + css: (t) => ` + overflow: hidden; + opacity: ${Math.min(t * 20, 1) * opacity}; + height: ${t * height}px; + padding-top: ${t * padding_top}px; + padding-bottom: ${t * padding_bottom}px; + margin-top: ${t * margin_top}px; + margin-bottom: ${t * margin_bottom}px; + border-top-width: ${t * border_top_width}px; + border-bottom-width: ${t * border_bottom_width}px;`, }; } @@ -129,10 +127,8 @@ export function scale( const style = getComputedStyle(node); const target_opacity = +style.opacity; const transform = style.transform === 'none' ? '' : style.transform; - const sd = 1 - start; const od = target_opacity * (1 - opacity); - return { delay, duration, @@ -152,27 +148,13 @@ interface DrawParams { } export function draw( - node: SVGElement & { getTotalLength(): number }, + node: SVGPathElement | SVGGeometryElement, { delay = 0, speed, duration, easing = cubicInOut }: DrawParams ): TransitionConfig { const len = node.getTotalLength(); - - if (duration === undefined) { - if (speed === undefined) { - duration = 800; - } else { - duration = len / speed; - } - } else if (typeof duration === 'function') { - duration = duration(len); - } - - return { - delay, - duration, - easing, - css: (t, u) => `stroke-dasharray: ${t * len} ${u * len};`, - }; + if (duration === undefined) duration = speed ? len / speed : 800; + else if (typeof duration === 'function') duration = duration(len); + return { delay, duration, easing, css: (t, u) => `stroke-dasharray: ${t * len} ${u * len};` }; } interface CrossfadeParams { @@ -190,12 +172,13 @@ type ElementMap = Map; export function crossfade({ delay: default_delay = 0, - easing: default_easing = cubicOut, duration: default_duration = (d) => Math.sqrt(d) * 30, + easing: default_easing = cubicOut, fallback, }: CrossFadeConfig) { const to_receive: ElementMap = new Map(); const to_send: ElementMap = new Map(); + function crossfade( from_node: Element, to_node: Element, @@ -207,31 +190,30 @@ export function crossfade({ const dy = from.top - to.top; const dw = from.width / to.width; const dh = from.height / to.height; - const d = Math.sqrt(dx * dx + dy * dy); - const style = getComputedStyle(to_node); - const transform = style.transform === 'none' ? '' : style.transform; - const opacity = +style.opacity; + const { transform, opacity } = getComputedStyle(to_node); + const prev = transform === 'none' ? '' : transform; return { delay, easing, - duration: is_function(duration) ? duration(d) : duration, + duration: typeof duration === 'function' ? duration(Math.sqrt(dx * dx + dy * dy)) : duration, css: (t, u) => ` - opacity: ${t * opacity}; + opacity: ${t * +opacity}; transform-origin: top left; - transform: ${transform} translate(${u * dx}px,${u * dy}px) scale(${t + (1 - t) * dw}, ${t + (1 - t) * dh}); + transform: ${prev} translate(${u * dx}px,${u * dy}px) scale(${t + (1 - t) * dw}, ${t + (1 - t) * dh}); `, } as TransitionConfig; } function transition(a: ElementMap, b: ElementMap, is_intro: boolean) { return (node: Element, params: MarkedCrossFadeConfig) => { - a.set(params.key, node); + const key = params.key; + a.set(key, node); return () => { - if (b.has(params.key)) { - const from_node = b.get(params.key); - b.delete(params.key); + if (b.has(key)) { + const from_node = b.get(key); + b.delete(key); return crossfade(from_node, node, params); } else { - a.delete(params.key); + a.delete(key); return fallback && fallback(node, params, is_intro); } };