From fbf4cbf22c144ef849dce6d3630a54344406d11a Mon Sep 17 00:00:00 2001 From: simeydotme Date: Tue, 24 Sep 2019 22:38:55 +0800 Subject: [PATCH 0001/1628] Calculate scale factor of elements in FLIP animations - divide the getBoundingClientRect dimensions by clientHeight or clientWidth - this gives us the element's scaled values as scaleX and scaleY - divide the "dx" and "dy" by the "scaleX" and "scaleY" This aims to fix #3555 by using the node's un-scaled width/height to calculate the amount it has been scaled. Then dividing the distance by this scale factor removes the janky start position which was shown in the test case. resolves #3555 --- src/runtime/animate/index.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/runtime/animate/index.ts b/src/runtime/animate/index.ts index d00d23c98..3f841154a 100644 --- a/src/runtime/animate/index.ts +++ b/src/runtime/animate/index.ts @@ -19,9 +19,11 @@ interface FlipParams { export function flip(node: Element, animation: { from: DOMRect; to: DOMRect }, params: FlipParams): AnimationConfig { const style = getComputedStyle(node); const transform = style.transform === 'none' ? '' : style.transform; + const scaleX = animation.from.width / node.clientWidth; + const scaleY = animation.from.height / node.clientHeight; - const dx = animation.from.left - animation.to.left; - const dy = animation.from.top - animation.to.top; + 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); From 4918faf605bf94027a626407c938e53945b8e347 Mon Sep 17 00:00:00 2001 From: Shunichi Hato Date: Thu, 31 Oct 2019 17:28:56 +0900 Subject: [PATCH 0002/1628] Fix crud example update error when filtering because the index `i` in update refers to the index in the original --- .../examples/20-7guis/05-7guis-crud/App.svelte | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/site/content/examples/20-7guis/05-7guis-crud/App.svelte b/site/content/examples/20-7guis/05-7guis-crud/App.svelte index f55aeb0d8..68574bca4 100644 --- a/site/content/examples/20-7guis/05-7guis-crud/App.svelte +++ b/site/content/examples/20-7guis/05-7guis-crud/App.svelte @@ -22,13 +22,13 @@ let i = 0; $: filteredPeople = prefix - ? people.filter(person => { + ? people.map(person => { const name = `${person.last}, ${person.first}`; - return name.toLowerCase().startsWith(prefix.toLowerCase()); + return { matched: name.toLowerCase().startsWith(prefix.toLowerCase()), person: person }; }) - : people; + : people.map(person => Object({ matched: true, person: person })); - $: selected = filteredPeople[i]; + $: selected = filteredPeople[i].person; $: reset_inputs(selected); @@ -82,8 +82,10 @@ From b704531a2eca63c0c4afad432c63a1aeda9c6f83 Mon Sep 17 00:00:00 2001 From: Shunichi Hato Date: Sun, 3 Nov 2019 13:58:35 +0900 Subject: [PATCH 0003/1628] Make selection reactive with search --- site/content/examples/20-7guis/05-7guis-crud/App.svelte | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/site/content/examples/20-7guis/05-7guis-crud/App.svelte b/site/content/examples/20-7guis/05-7guis-crud/App.svelte index 68574bca4..67145ceec 100644 --- a/site/content/examples/20-7guis/05-7guis-crud/App.svelte +++ b/site/content/examples/20-7guis/05-7guis-crud/App.svelte @@ -28,6 +28,11 @@ }) : people.map(person => Object({ matched: true, person: person })); + $: if (!filteredPeople[i].matched) { + let newIndex = filteredPeople.findIndex(person => person.matched); + if (newIndex >= 0) i = newIndex; + } + $: selected = filteredPeople[i].person; $: reset_inputs(selected); From 3d464054814d8f5fee280b034f6904cf1f18f4eb Mon Sep 17 00:00:00 2001 From: Tan Li Hau Date: Thu, 7 Nov 2019 00:55:07 +0800 Subject: [PATCH 0004/1628] skip missing input files --- test/js/index.js | 7 +++++-- test/parser/index.js | 4 +++- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/test/js/index.js b/test/js/index.js index 5fd632d60..411a01527 100644 --- a/test/js/index.js +++ b/test/js/index.js @@ -14,8 +14,11 @@ describe("js", () => { throw new Error("Forgot to remove `solo: true` from test"); } - (solo ? it.only : it)(dir, () => { - dir = path.resolve(`${__dirname}/samples`, dir); + dir = path.resolve(`${__dirname}/samples`, dir); + + const skip = !fs.existsSync(`${dir}/input.svelte`); + + (skip ? it.skip : solo ? it.only : it)(dir, () => { const config = loadConfig(`${dir}/_config.js`); const input = fs.readFileSync(`${dir}/input.svelte`, "utf-8").replace(/\s+$/, ""); diff --git a/test/parser/index.js b/test/parser/index.js index 0188fac43..330678559 100644 --- a/test/parser/index.js +++ b/test/parser/index.js @@ -15,7 +15,9 @@ describe('parse', () => { ); } - (solo ? it.only : it)(dir, () => { + const skip = !fs.existsSync(`${__dirname}/samples/${dir}/input.svelte`); + + (skip ? it.skip : solo ? it.only : it)(dir, () => { const options = tryToLoadJson(`${__dirname}/samples/${dir}/options.json`) || {}; const input = fs.readFileSync(`${__dirname}/samples/${dir}/input.svelte`, 'utf-8').replace(/\s+$/, ''); From ecf6d3508a6bde09090470840263fbdc66dd994a Mon Sep 17 00:00:00 2001 From: Tan Li Hau Date: Thu, 7 Nov 2019 08:25:09 +0800 Subject: [PATCH 0005/1628] update contributing.md for solo test --- CONTRIBUTING.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index f00ebc07b..9a0ab51e7 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -96,6 +96,12 @@ Test samples are kept in `/test/xxx/samples` folder. 1. To run test, run `npm run test` 1. To run test for a specific feature, you can use the `-g` (aka `--grep`) option. For example, to only run test involving transitions, run `npm run test -- -g transition`. +##### Running solo test + +1. To run only one test, rename the test sample folder to end with `.solo`. For example, to run the `test/js/samples/action` only, rename it to `test/js/samples/action.solo`. +1. To run only one test suite, rename the test suite folder to end with `.solo`. For example, to run the `test/js` test suite only, rename it to `test/js.solo`. +1. Remember to rename the test folder back. The CI will fail if there's a solo test. + #### Breaking changes When adding a new breaking change, follow this template in your pull request: From d8b9274697a58e1af7b4473a69871e5745940ae5 Mon Sep 17 00:00:00 2001 From: Tan Li Hau Date: Thu, 7 Nov 2019 08:34:05 +0800 Subject: [PATCH 0006/1628] update test snapshot with --update flag --- CONTRIBUTING.md | 5 +++ test/css/index.js | 33 +++++++++++-------- test/helpers.js | 4 +++ test/hydration/index.js | 14 ++++++-- test/js/index.js | 32 ++++++++++++++---- test/parser/index.js | 2 +- test/server-side-rendering/index.js | 50 +++++++++++++++++++++++------ 7 files changed, 108 insertions(+), 32 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 9a0ab51e7..9c2cb5103 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -102,6 +102,11 @@ Test samples are kept in `/test/xxx/samples` folder. 1. To run only one test suite, rename the test suite folder to end with `.solo`. For example, to run the `test/js` test suite only, rename it to `test/js.solo`. 1. Remember to rename the test folder back. The CI will fail if there's a solo test. +##### Updating `.expected` files + +1. Tests suites like `css`, `js`, `server-side-rendering` asserts that the generated output has to match the content in the `.expected` file. For example, in the `js` test suites, the generated js code is compared against the content in `expected.js`. +1. To update the content of the `.expected` file, run the test with `--update` flag. (`npm run test --update`) + #### Breaking changes When adding a new breaking change, follow this template in your pull request: diff --git a/test/css/index.js b/test/css/index.js index 1f90d243b..dc1731459 100644 --- a/test/css/index.js +++ b/test/css/index.js @@ -1,6 +1,6 @@ import * as assert from 'assert'; import * as fs from 'fs'; -import { env, normalizeHtml, svelte } from '../helpers.js'; +import { env, svelte, setupHtmlEqual, shouldUpdateExpected } from '../helpers.js'; function try_require(file) { try { @@ -37,6 +37,10 @@ function create(code) { } describe('css', () => { + before(() => { + setupHtmlEqual(); + }); + fs.readdirSync(`${__dirname}/samples`).forEach(dir => { if (dir[0] === '.') return; @@ -80,7 +84,17 @@ describe('css', () => { css: read(`${__dirname}/samples/${dir}/expected.css`) }; - assert.equal(dom.css.code.replace(/svelte(-ref)?-[a-z0-9]+/g, (m, $1) => $1 ? m : 'svelte-xyz'), expected.css); + const actual_css = dom.css.code.replace(/svelte(-ref)?-[a-z0-9]+/g, (m, $1) => $1 ? m : 'svelte-xyz'); + try { + assert.equal(actual_css, expected.css); + } catch (error) { + if (shouldUpdateExpected()) { + fs.writeFileSync(`${__dirname}/samples/${dir}/expected.css`, actual_css); + console.log(`Updated ${dir}/expected.css.`); + } else { + throw error; + } + } let ClientComponent; let ServerComponent; @@ -114,10 +128,8 @@ describe('css', () => { fs.writeFileSync(`${__dirname}/samples/${dir}/_actual.html`, html); - assert.equal( - normalizeHtml(window, html.replace(/svelte(-ref)?-[a-z0-9]+/g, (m, $1) => $1 ? m : 'svelte-xyz')), - normalizeHtml(window, expected.html) - ); + const actual_html = html.replace(/svelte(-ref)?-[a-z0-9]+/g, (m, $1) => $1 ? m : 'svelte-xyz'); + assert.htmlEqual(actual_html, expected.html); window.document.head.innerHTML = ''; // remove added styles } catch (err) { @@ -127,13 +139,8 @@ describe('css', () => { // ssr try { - assert.equal( - normalizeHtml( - window, - ServerComponent.render(config.props).html.replace(/svelte(-ref)?-[a-z0-9]+/g, (m, $1) => $1 ? m : 'svelte-xyz') - ), - normalizeHtml(window, expected.html) - ); + const actual_ssr = ServerComponent.render(config.props).html.replace(/svelte(-ref)?-[a-z0-9]+/g, (m, $1) => $1 ? m : 'svelte-xyz'); + assert.htmlEqual(actual_ssr, expected.html); } catch (err) { console.log(ssr.js.code); throw err; diff --git a/test/helpers.js b/test/helpers.js index d4f507fc8..ff40ac5f7 100644 --- a/test/helpers.js +++ b/test/helpers.js @@ -206,6 +206,10 @@ export function showOutput(cwd, options = {}, compile = svelte.compile) { }); } +export function shouldUpdateExpected() { + return process.argv.includes('--update'); +} + export function spaces(i) { let result = ''; while (i--) result += ' '; diff --git a/test/hydration/index.js b/test/hydration/index.js index 85e2ccc4a..a0bfd6de4 100644 --- a/test/hydration/index.js +++ b/test/hydration/index.js @@ -7,7 +7,8 @@ import { loadConfig, loadSvelte, env, - setupHtmlEqual + setupHtmlEqual, + shouldUpdateExpected } from '../helpers.js'; let compileOptions = null; @@ -76,7 +77,16 @@ describe('hydration', () => { props: config.props }); - assert.htmlEqual(target.innerHTML, fs.readFileSync(`${cwd}/_after.html`, 'utf-8')); + try { + assert.htmlEqual(target.innerHTML, fs.readFileSync(`${cwd}/_after.html`, 'utf-8')); + } catch (error) { + if (shouldUpdateExpected()) { + fs.writeFileSync(`${cwd}/_after.html`, target.innerHTML); + console.log(`Updated ${cwd}/_after.html.`); + } else { + throw error; + } + } if (config.test) { config.test(assert, target, snapshot, component, window); diff --git a/test/js/index.js b/test/js/index.js index 411a01527..6df2c9baf 100644 --- a/test/js/index.js +++ b/test/js/index.js @@ -1,7 +1,7 @@ import * as assert from "assert"; import * as fs from "fs"; import * as path from "path"; -import { loadConfig, svelte } from "../helpers.js"; +import { loadConfig, svelte, shouldUpdateExpected } from "../helpers.js"; describe("js", () => { fs.readdirSync(`${__dirname}/samples`).forEach(dir => { @@ -37,12 +37,32 @@ describe("js", () => { const output = `${dir}/_actual.js`; fs.writeFileSync(output, actual); - const expected = fs.readFileSync(`${dir}/expected.js`, "utf-8"); + const expectedPath = `${dir}/expected.js`; - assert.equal( - actual.trim().replace(/^[ \t]+$/gm, ""), - expected.trim().replace(/^[ \t]+$/gm, "") - ); + let expected = ''; + try { + expected = fs.readFileSync(expectedPath, "utf-8"); + } catch (error) { + console.log(error); + if (error.code === 'ENOENT') { + // missing expected.js + fs.writeFileSync(expectedPath, actual); + } + } + + try { + assert.equal( + actual.trim().replace(/^[ \t]+$/gm, ""), + expected.trim().replace(/^[ \t]+$/gm, "") + ); + } catch (error) { + if (shouldUpdateExpected()) { + fs.writeFileSync(expectedPath, actual); + console.log(`Updated ${expectedPath}.`); + } else { + throw error; + } + } }); }); }); diff --git a/test/parser/index.js b/test/parser/index.js index 330678559..27c5ec156 100644 --- a/test/parser/index.js +++ b/test/parser/index.js @@ -1,6 +1,6 @@ import * as assert from 'assert'; import * as fs from 'fs'; -import { svelte, tryToLoadJson } from '../helpers.js'; +import { svelte, tryToLoadJson, shouldUpdateExpected } from '../helpers.js'; describe('parse', () => { fs.readdirSync(`${__dirname}/samples`).forEach(dir => { diff --git a/test/server-side-rendering/index.js b/test/server-side-rendering/index.js index 73b286044..768917e83 100644 --- a/test/server-side-rendering/index.js +++ b/test/server-side-rendering/index.js @@ -6,7 +6,8 @@ import { showOutput, loadConfig, setupHtmlEqual, - tryToLoadJson + tryToLoadJson, + shouldUpdateExpected } from "../helpers.js"; function tryToReadFile(file) { @@ -58,18 +59,47 @@ describe("ssr", () => { fs.writeFileSync(`${dir}/_actual.html`, html); if (css.code) fs.writeFileSync(`${dir}/_actual.css`, css.code); - assert.htmlEqual(html, expectedHtml); - assert.equal( - css.code.replace(/^\s+/gm, ""), - expectedCss.replace(/^\s+/gm, "") - ); + try { + assert.htmlEqual(html, expectedHtml); + } catch (error) { + if (shouldUpdateExpected()) { + fs.writeFileSync(`${dir}/_expected.html`, html); + console.log(`Updated ${dir}/_expected.html.`); + } else { + throw error; + } + } + + try { + assert.equal( + css.code.replace(/^\s+/gm, ""), + expectedCss.replace(/^\s+/gm, "") + ); + } catch (error) { + if (shouldUpdateExpected()) { + fs.writeFileSync(`${dir}/_expected.css`, css.code); + console.log(`Updated ${dir}/_expected.css.`); + } else { + throw error; + } + } if (fs.existsSync(`${dir}/_expected-head.html`)) { fs.writeFileSync(`${dir}/_actual-head.html`, head); - assert.htmlEqual( - head, - fs.readFileSync(`${dir}/_expected-head.html`, 'utf-8') - ); + + try { + assert.htmlEqual( + head, + fs.readFileSync(`${dir}/_expected-head.html`, 'utf-8') + ); + } catch (error) { + if (shouldUpdateExpected()) { + fs.writeFileSync(`${dir}/_expected-head.html`, head); + console.log(`Updated ${dir}/_expected-head.html.`); + } else { + throw error; + } + } } if (show) showOutput(dir, { generate: 'ssr', format: 'cjs' }); From 80bfcea92aec630b80367fb8c6292ecfd715a4e3 Mon Sep 17 00:00:00 2001 From: Richard Harris Date: Sun, 10 Nov 2019 20:33:28 -0500 Subject: [PATCH 0007/1628] add type info for createEventDispatcher - fixes #3877 --- src/runtime/internal/lifecycle.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/runtime/internal/lifecycle.ts b/src/runtime/internal/lifecycle.ts index d5938874f..a8e37e963 100644 --- a/src/runtime/internal/lifecycle.ts +++ b/src/runtime/internal/lifecycle.ts @@ -30,7 +30,7 @@ export function onDestroy(fn) { export function createEventDispatcher() { const component = get_current_component(); - return (type, detail) => { + return (type: string, detail?: any) => { const callbacks = component.$$.callbacks[type]; if (callbacks) { From 3b148b6635d77b5ef558ce72dbc2afb8fe529d58 Mon Sep 17 00:00:00 2001 From: PaulMaly Date: Mon, 11 Nov 2019 11:20:17 +0300 Subject: [PATCH 0008/1628] User `let` instead `var` for loop index User `let` instead `var` for loop index to prevent naming collisions with top-level scope. To reproduce: https://svelte.dev/repl/33152732e43049cfaf171bc6f10fd88f?version=3.13.0 --- src/compiler/compile/render_dom/wrappers/Element/index.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/compiler/compile/render_dom/wrappers/Element/index.ts b/src/compiler/compile/render_dom/wrappers/Element/index.ts index 168d53d99..75c2cc609 100644 --- a/src/compiler/compile/render_dom/wrappers/Element/index.ts +++ b/src/compiler/compile/render_dom/wrappers/Element/index.ts @@ -634,10 +634,10 @@ export default class ElementWrapper extends Wrapper { }); block.chunks.init.push(b` - var ${levels} = [${initial_props}]; + let ${levels} = [${initial_props}]; - var ${data} = {}; - for (var #i = 0; #i < ${levels}.length; #i += 1) { + let ${data} = {}; + for (let #i = 0; #i < ${levels}.length; #i += 1) { ${data} = @assign(${data}, ${levels}[#i]); } `); @@ -931,4 +931,4 @@ function to_html(wrappers: Array, blo } } }); -} \ No newline at end of file +} From a7a75cb5503779ffae788c4b862f852b0426354a Mon Sep 17 00:00:00 2001 From: Nitay Rabinovich Date: Mon, 11 Nov 2019 11:02:18 +0200 Subject: [PATCH 0009/1628] Rexport SvelteComponent as SvelteComponent API --- src/runtime/ambient.ts | 19 ++----------------- src/runtime/index.ts | 3 ++- src/runtime/internal/ComponentApi.ts | 16 ++++++++++++++++ src/runtime/internal/index.ts | 1 + 4 files changed, 21 insertions(+), 18 deletions(-) create mode 100644 src/runtime/internal/ComponentApi.ts diff --git a/src/runtime/ambient.ts b/src/runtime/ambient.ts index b094056c5..65b4bc23d 100644 --- a/src/runtime/ambient.ts +++ b/src/runtime/ambient.ts @@ -1,19 +1,4 @@ declare module '*.svelte' { - type Props = Record; - - export default class { - constructor(options: { - target: Element; - anchor?: Element; - props?: Props; - hydrate?: boolean; - intro?: boolean; - }); - - $set(props: Props): void; - $on(event: string, callback: (event: CustomEvent) => void): () => void; - $destroy(): void; - - [accessor: string]: any; - } + type SvelteComponent = typeof import('./internal/ComponentApi').SvelteComponentApi + export default SvelteComponent } diff --git a/src/runtime/index.ts b/src/runtime/index.ts index 0973b93f1..70fb65557 100644 --- a/src/runtime/index.ts +++ b/src/runtime/index.ts @@ -8,5 +8,6 @@ export { setContext, getContext, tick, - createEventDispatcher + createEventDispatcher, + SvelteComponentApi as SvelteComponent } from 'svelte/internal'; diff --git a/src/runtime/internal/ComponentApi.ts b/src/runtime/internal/ComponentApi.ts new file mode 100644 index 000000000..943e2c9de --- /dev/null +++ b/src/runtime/internal/ComponentApi.ts @@ -0,0 +1,16 @@ +type Props = Record; +export declare class SvelteComponentApi { + constructor(options: { + target: Element; + anchor?: Element; + props?: Props; + hydrate?: boolean; + intro?: boolean; + }); + + $set(props: Props): void; + $on(event: string, callback: (event: CustomEvent) => void): () => void; + $destroy(): void; + + [accessor: string]: any; +} \ No newline at end of file diff --git a/src/runtime/internal/index.ts b/src/runtime/internal/index.ts index e1dd2a1fc..8e5725d3b 100644 --- a/src/runtime/internal/index.ts +++ b/src/runtime/internal/index.ts @@ -12,4 +12,5 @@ export * from './ssr'; export * from './transitions'; export * from './utils'; export * from './Component'; +export * from './ComponentApi'; export * from './dev'; From 97e44099b518ed7437b8d016ec9ef7bddf1686c9 Mon Sep 17 00:00:00 2001 From: Nitay Rabinovich Date: Mon, 11 Nov 2019 11:47:46 +0200 Subject: [PATCH 0010/1628] Fix linting and exporting --- src/runtime/ambient.ts | 4 ++-- src/runtime/internal/ComponentApi.ts | 7 +++---- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/runtime/ambient.ts b/src/runtime/ambient.ts index 65b4bc23d..8dd2b2e1c 100644 --- a/src/runtime/ambient.ts +++ b/src/runtime/ambient.ts @@ -1,4 +1,4 @@ declare module '*.svelte' { - type SvelteComponent = typeof import('./internal/ComponentApi').SvelteComponentApi - export default SvelteComponent + // eslint-disable-next-line import/no-unresolved + export { default } from 'svelte/internal/ComponentApi'; } diff --git a/src/runtime/internal/ComponentApi.ts b/src/runtime/internal/ComponentApi.ts index 943e2c9de..2718960e3 100644 --- a/src/runtime/internal/ComponentApi.ts +++ b/src/runtime/internal/ComponentApi.ts @@ -1,16 +1,15 @@ type Props = Record; export declare class SvelteComponentApi { - constructor(options: { + constructor (options: { target: Element; anchor?: Element; props?: Props; hydrate?: boolean; intro?: boolean; }); - $set(props: Props): void; $on(event: string, callback: (event: CustomEvent) => void): () => void; $destroy(): void; - [accessor: string]: any; -} \ No newline at end of file +} +export default SvelteComponentApi; \ No newline at end of file From b382b9a9c62691e1566dcd5177655fee4282c46a Mon Sep 17 00:00:00 2001 From: Tan Li Hau Date: Mon, 11 Nov 2019 22:50:48 +0800 Subject: [PATCH 0011/1628] add loopGuardTimeout options --- site/content/docs/04-compile-time.md | 2 ++ src/compiler/compile/Component.ts | 8 ++++---- src/compiler/compile/index.ts | 13 ++++++++++++- src/compiler/interfaces.ts | 1 + src/runtime/internal/dev.ts | 4 ++-- test/js/samples/loop-protect/_config.js | 7 ++++--- test/js/samples/loop-protect/expected.js | 14 +++++++------- test/runtime/samples/loop-protect/_config.js | 1 + 8 files changed, 33 insertions(+), 17 deletions(-) diff --git a/site/content/docs/04-compile-time.md b/site/content/docs/04-compile-time.md index c6675e8a0..407b1dfc1 100644 --- a/site/content/docs/04-compile-time.md +++ b/site/content/docs/04-compile-time.md @@ -53,6 +53,7 @@ The following options can be passed to the compiler. None are required: | `tag` | string | null | `accessors` | boolean | `false` | `css` | boolean | `true` +| `loopGuardTimeout` | number | 0 | `preserveComments` | boolean | `false` | `preserveWhitespace` | boolean | `false` | `outputFilename` | string | `null` @@ -73,6 +74,7 @@ The following options can be passed to the compiler. None are required: | `customElement` | `false` | If `true`, tells the compiler to generate a custom element constructor instead of a regular Svelte component. | `tag` | `null` | A `string` that tells Svelte what tag name to register the custom element with. It must be a lowercase alphanumeric string with at least one hyphen, e.g. `"my-element"`. | `css` | `true` | If `true`, styles will be included in the JavaScript class and injected at runtime. It's recommended that you set this to `false` and use the CSS that is statically generated, as it will result in smaller JavaScript bundles and better performance. +| `loopGuardTimeout` | 0 | A `number` that tells Svelte to break the loop if it blocks the thread for more than `loopGuardTimeout` ms. This is useful to prevent infinite loops. **Only available when `dev: true`** | `preserveComments` | `false` | If `true`, your HTML comments will be preserved during server-side rendering. By default, they are stripped out. | `preserveWhitespace` | `false` | If `true`, whitespace inside and between elements is kept as you typed it, rather than optimised by Svelte. | `outputFilename` | `null` | A `string` used for your JavaScript sourcemap. diff --git a/src/compiler/compile/Component.ts b/src/compiler/compile/Component.ts index b554a1be2..2d696ad30 100644 --- a/src/compiler/compile/Component.ts +++ b/src/compiler/compile/Component.ts @@ -750,8 +750,8 @@ export default class Component { component.warn_on_undefined_store_value_references(node, parent, scope); - if (component.compile_options.dev) { - const to_insert_for_loop_protect = component.loop_protect(node, prop, index); + if (component.compile_options.dev && component.compile_options.loopGuardTimeout > 0) { + const to_insert_for_loop_protect = component.loop_protect(node, prop, index, component.compile_options.loopGuardTimeout); if (to_insert_for_loop_protect) { if (!Array.isArray(parent[prop])) { parent[prop] = { @@ -863,7 +863,7 @@ export default class Component { } } - loop_protect(node, prop, index) { + loop_protect(node, prop, index, timeout) { if (node.type === 'WhileStatement' || node.type === 'ForStatement' || node.type === 'DoWhileStatement') { @@ -873,7 +873,7 @@ export default class Component { internal: true, }); - const before = b`const ${guard} = @loop_guard()`; + const before = b`const ${guard} = @loop_guard(${timeout})`; const inside = b`${guard}();`; // wrap expression statement with BlockStatement diff --git a/src/compiler/compile/index.ts b/src/compiler/compile/index.ts index 98004ba5d..ac52f5947 100644 --- a/src/compiler/compile/index.ts +++ b/src/compiler/compile/index.ts @@ -24,12 +24,13 @@ const valid_options = [ 'customElement', 'tag', 'css', + 'loopGuardTimeout', 'preserveComments', 'preserveWhitespace' ]; function validate_options(options: CompileOptions, warnings: Warning[]) { - const { name, filename } = options; + const { name, filename, loopGuardTimeout, dev } = options; Object.keys(options).forEach(key => { if (valid_options.indexOf(key) === -1) { @@ -54,6 +55,16 @@ function validate_options(options: CompileOptions, warnings: Warning[]) { toString: () => message, }); } + + if (loopGuardTimeout && !dev) { + const message = 'options.loopGuardTimeout is for options.dev = true only'; + warnings.push({ + code: `options-loop-guard-timeout`, + message, + filename, + toString: () => message, + }); + } } export default function compile(source: string, options: CompileOptions = {}) { diff --git a/src/compiler/interfaces.ts b/src/compiler/interfaces.ts index f877b93b5..e7362b931 100644 --- a/src/compiler/interfaces.ts +++ b/src/compiler/interfaces.ts @@ -121,6 +121,7 @@ export interface CompileOptions { customElement?: boolean; tag?: string; css?: boolean; + loopGuardTimeout?: number; preserveComments?: boolean; preserveWhitespace?: boolean; diff --git a/src/runtime/internal/dev.ts b/src/runtime/internal/dev.ts index c10b33963..404d0b643 100644 --- a/src/runtime/internal/dev.ts +++ b/src/runtime/internal/dev.ts @@ -96,10 +96,10 @@ export class SvelteComponentDev extends SvelteComponent { } } -export function loop_guard() { +export function loop_guard(timeout) { const start = Date.now(); return () => { - if (Date.now() - start > 100) { + if (Date.now() - start > timeout) { throw new Error(`Infinite loop detected`); } }; diff --git a/test/js/samples/loop-protect/_config.js b/test/js/samples/loop-protect/_config.js index b1f2518e8..fcbf8be09 100644 --- a/test/js/samples/loop-protect/_config.js +++ b/test/js/samples/loop-protect/_config.js @@ -1,5 +1,6 @@ export default { options: { - dev: true - } -}; \ No newline at end of file + dev: true, + loopGuardTimeout: 100, + }, +}; diff --git a/test/js/samples/loop-protect/expected.js b/test/js/samples/loop-protect/expected.js index bbb58c819..59d9a7e43 100644 --- a/test/js/samples/loop-protect/expected.js +++ b/test/js/samples/loop-protect/expected.js @@ -35,28 +35,28 @@ function create_fragment(ctx) { } function instance($$self) { - const guard = loop_guard(); + const guard = loop_guard(100); while (true) { foo(); guard(); } - const guard_1 = loop_guard(); + const guard_1 = loop_guard(100); for (; ; ) { foo(); guard_1(); } - const guard_2 = loop_guard(); + const guard_2 = loop_guard(100); while (true) { foo(); guard_2(); } - const guard_4 = loop_guard(); + const guard_4 = loop_guard(100); do { foo(); @@ -68,11 +68,11 @@ function instance($$self) { }; $$self.$inject_state = $$props => { - + }; $: { - const guard_3 = loop_guard(); + const guard_3 = loop_guard(100); while (true) { foo(); @@ -81,7 +81,7 @@ function instance($$self) { } $: { - const guard_5 = loop_guard(); + const guard_5 = loop_guard(100); do { foo(); diff --git a/test/runtime/samples/loop-protect/_config.js b/test/runtime/samples/loop-protect/_config.js index 2a1f5cad4..75f75c003 100644 --- a/test/runtime/samples/loop-protect/_config.js +++ b/test/runtime/samples/loop-protect/_config.js @@ -2,5 +2,6 @@ export default { error: 'Infinite loop detected', compileOptions: { dev: true, + loopGuardTimeout: 100, } }; From b2fb1453f539ac686a2262ebf1f1d9a1c11a31f3 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Mon, 11 Nov 2019 17:50:22 -0500 Subject: [PATCH 0012/1628] update deps, prevent naming conflict seen in #3891 --- package-lock.json | 25 ++++++------------- package.json | 4 +-- .../samples/deconflict-spread-i/_config.js | 3 +++ .../samples/deconflict-spread-i/main.svelte | 5 ++++ 4 files changed, 17 insertions(+), 20 deletions(-) create mode 100644 test/runtime/samples/deconflict-spread-i/_config.js create mode 100644 test/runtime/samples/deconflict-spread-i/main.svelte diff --git a/package-lock.json b/package-lock.json index b11cf80a8..68b7c4b6a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -500,26 +500,15 @@ "dev": true }, "code-red": { - "version": "0.0.19", - "resolved": "https://registry.npmjs.org/code-red/-/code-red-0.0.19.tgz", - "integrity": "sha512-pzkA9ikMLR7KatByUJVz33kQKkrDnsJhyuvxSSUnyJNBggkGNStmDe/ezYvfP4CZ9XM7vYIID+YIaMJnlYGzLg==", + "version": "0.0.20", + "resolved": "https://registry.npmjs.org/code-red/-/code-red-0.0.20.tgz", + "integrity": "sha512-nEh0GdiKVQ1zFyTImD+Z93kqVG0UPXSn9W5YCafOiOHBlVEhlVQkuVmerCg1CPmm1xka/3cvhS+EI/Ozeke6bQ==", "dev": true, "requires": { "acorn": "^7.1.0", "is-reference": "^1.1.4", - "periscopic": "^1.0.2", + "periscopic": "^2.0.1", "sourcemap-codec": "^1.4.6" - }, - "dependencies": { - "periscopic": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/periscopic/-/periscopic-1.1.0.tgz", - "integrity": "sha512-sUdDgd8G35JjpBqHGnuc2MECoyUryHGfjtsKFPS6N8MEGHtxoIML8yEWydL1zf+W8EoChX4L7A9AvVRJuM6Lqg==", - "dev": true, - "requires": { - "is-reference": "^1.1.4" - } - } } }, "codecov": { @@ -2734,9 +2723,9 @@ "dev": true }, "periscopic": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/periscopic/-/periscopic-2.0.0.tgz", - "integrity": "sha512-2YVtztswd6ud5b0+IDD26UhEm1QvlTsR3s7G59CD0txGyhfvQ39YhNuueSmzT5VzHUxiIH0E8S04JSQpXMJ8/g==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/periscopic/-/periscopic-2.0.1.tgz", + "integrity": "sha512-twJ8e4RatllMAcbmBqKj8cvZ94HtqSzbb8hJoGj4iSCcCHXxKb06HRxOq4heyq2x/6mKynJDvTTreHCz+m6lJw==", "dev": true, "requires": { "is-reference": "^1.1.4" diff --git a/package.json b/package.json index 771dbc194..6c70337b9 100644 --- a/package.json +++ b/package.json @@ -64,7 +64,7 @@ "acorn": "^7.1.0", "agadoo": "^1.1.0", "c8": "^5.0.1", - "code-red": "0.0.19", + "code-red": "0.0.20", "codecov": "^3.5.0", "css-tree": "1.0.0-alpha22", "eslint": "^6.3.0", @@ -77,7 +77,7 @@ "locate-character": "^2.0.5", "magic-string": "^0.25.3", "mocha": "^6.2.0", - "periscopic": "^2.0.0", + "periscopic": "^2.0.1", "puppeteer": "^1.19.0", "rollup": "^1.21.4", "rollup-plugin-commonjs": "^10.1.0", diff --git a/test/runtime/samples/deconflict-spread-i/_config.js b/test/runtime/samples/deconflict-spread-i/_config.js new file mode 100644 index 000000000..f147a4c1f --- /dev/null +++ b/test/runtime/samples/deconflict-spread-i/_config.js @@ -0,0 +1,3 @@ +export default { + preserveIdentifiers: true +}; \ No newline at end of file diff --git a/test/runtime/samples/deconflict-spread-i/main.svelte b/test/runtime/samples/deconflict-spread-i/main.svelte new file mode 100644 index 000000000..a7b201ac4 --- /dev/null +++ b/test/runtime/samples/deconflict-spread-i/main.svelte @@ -0,0 +1,5 @@ + + + \ No newline at end of file From c29e2085a192dd22f7a7122b143ca2ce09372c24 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Mon, 11 Nov 2019 18:28:54 -0500 Subject: [PATCH 0013/1628] -> v3.14.0 --- CHANGELOG.md | 8 ++++++++ package-lock.json | 2 +- package.json | 2 +- 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 62306fdbd..0fc0d1a6f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # Svelte changelog +## 3.14.0 + +* Add `loopGuardTimeout` option that augments `for`/`while` loops to prevent infinite loops, primarily for use in the REPL ([#3887](https://github.com/sveltejs/svelte/pull/3887)) +* Keep component bindings in sync when changed in reactive statements ([#3382](https://github.com/sveltejs/svelte/issues/3382)) +* Update attributes before bindings ([#3857](https://github.com/sveltejs/svelte/issues/3857)) +* Prevent variable naming conflict ([#3899](https://github.com/sveltejs/svelte/issues/3899)) + + ## 3.13.0 * New structured code generation, which eliminates a number of edge cases and obscure bugs ([#3539](https://github.com/sveltejs/svelte/pull/3539)) diff --git a/package-lock.json b/package-lock.json index 68b7c4b6a..c43ea8461 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "svelte", - "version": "3.13.0", + "version": "3.14.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 6c70337b9..8ad1cde10 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "svelte", - "version": "3.13.0", + "version": "3.14.0", "description": "Cybernetically enhanced web apps", "module": "index.mjs", "main": "index", From 3fb9626eb45076101a5974c77163d8c2504f9b84 Mon Sep 17 00:00:00 2001 From: Tan Li Hau Date: Tue, 12 Nov 2019 09:01:07 +0800 Subject: [PATCH 0014/1628] skip comments for preprocessors --- src/compiler/preprocess/index.ts | 11 ++++++-- test/preprocess/samples/comments/_config.js | 8 ++++++ test/preprocess/samples/comments/input.svelte | 25 +++++++++++++++++++ .../preprocess/samples/comments/output.svelte | 25 +++++++++++++++++++ 4 files changed, 67 insertions(+), 2 deletions(-) create mode 100644 test/preprocess/samples/comments/_config.js create mode 100644 test/preprocess/samples/comments/input.svelte create mode 100644 test/preprocess/samples/comments/output.svelte diff --git a/src/compiler/preprocess/index.ts b/src/compiler/preprocess/index.ts index 2faa3b97f..4549517fb 100644 --- a/src/compiler/preprocess/index.ts +++ b/src/compiler/preprocess/index.ts @@ -94,8 +94,12 @@ export default async function preprocess( for (const fn of script) { source = await replace_async( source, - /([^]*?)<\/script>/gi, + /|([^]*?)<\/script>/gi, async (match, attributes = '', content) => { + if (!attributes && !content) { + return match; + } + attributes = attributes || ''; const processed = await fn({ content, attributes: parse_attributes(attributes), @@ -110,8 +114,11 @@ export default async function preprocess( for (const fn of style) { source = await replace_async( source, - /([^]*?)<\/style>/gi, + /|([^]*?)<\/style>/gi, async (match, attributes = '', content) => { + if (!attributes && !content) { + return match; + } const processed: Processed = await fn({ content, attributes: parse_attributes(attributes), diff --git a/test/preprocess/samples/comments/_config.js b/test/preprocess/samples/comments/_config.js new file mode 100644 index 000000000..4416d121d --- /dev/null +++ b/test/preprocess/samples/comments/_config.js @@ -0,0 +1,8 @@ +export default { + preprocess: [ + { + script: ({ content }) => ({ code: content.replace(/one/g, 'two') }), + style: ({ content }) => ({ code: content.replace(/one/g, 'three') }), + }, + ], +}; diff --git a/test/preprocess/samples/comments/input.svelte b/test/preprocess/samples/comments/input.svelte new file mode 100644 index 000000000..1a01971f7 --- /dev/null +++ b/test/preprocess/samples/comments/input.svelte @@ -0,0 +1,25 @@ + + + + + + + + + + + + + diff --git a/test/preprocess/samples/comments/output.svelte b/test/preprocess/samples/comments/output.svelte new file mode 100644 index 000000000..2d3538af1 --- /dev/null +++ b/test/preprocess/samples/comments/output.svelte @@ -0,0 +1,25 @@ + + + + + + + + + + + + + From 3593dd308ccbd7999d53d102215e09dae160999b Mon Sep 17 00:00:00 2001 From: Nitay Rabinovich Date: Tue, 12 Nov 2019 09:11:01 +0200 Subject: [PATCH 0015/1628] Export SvelteComponentDev as API --- src/runtime/ambient.ts | 3 +-- src/runtime/index.ts | 2 +- src/runtime/internal/ComponentApi.ts | 15 --------------- src/runtime/internal/dev.ts | 18 +++++++++++++++++- src/runtime/internal/index.ts | 1 - 5 files changed, 19 insertions(+), 20 deletions(-) delete mode 100644 src/runtime/internal/ComponentApi.ts diff --git a/src/runtime/ambient.ts b/src/runtime/ambient.ts index 8dd2b2e1c..09cd9b72f 100644 --- a/src/runtime/ambient.ts +++ b/src/runtime/ambient.ts @@ -1,4 +1,3 @@ declare module '*.svelte' { - // eslint-disable-next-line import/no-unresolved - export { default } from 'svelte/internal/ComponentApi'; + export { SvelteComponentDev as default } from 'svelte/internal'; } diff --git a/src/runtime/index.ts b/src/runtime/index.ts index 70fb65557..e6c0c916f 100644 --- a/src/runtime/index.ts +++ b/src/runtime/index.ts @@ -9,5 +9,5 @@ export { getContext, tick, createEventDispatcher, - SvelteComponentApi as SvelteComponent + SvelteComponentDev as SvelteComponent } from 'svelte/internal'; diff --git a/src/runtime/internal/ComponentApi.ts b/src/runtime/internal/ComponentApi.ts deleted file mode 100644 index 2718960e3..000000000 --- a/src/runtime/internal/ComponentApi.ts +++ /dev/null @@ -1,15 +0,0 @@ -type Props = Record; -export declare class SvelteComponentApi { - constructor (options: { - target: Element; - anchor?: Element; - props?: Props; - hydrate?: boolean; - intro?: boolean; - }); - $set(props: Props): void; - $on(event: string, callback: (event: CustomEvent) => void): () => void; - $destroy(): void; - [accessor: string]: any; -} -export default SvelteComponentApi; \ No newline at end of file diff --git a/src/runtime/internal/dev.ts b/src/runtime/internal/dev.ts index c10b33963..bf03b65aa 100644 --- a/src/runtime/internal/dev.ts +++ b/src/runtime/internal/dev.ts @@ -79,8 +79,24 @@ export function set_data_dev(text, data) { text.data = data; } + +type Props = Record; +export interface SvelteComponentDev { + $set(props?: Props): void; + $on(event: string, callback: (event: CustomEvent) => void): () => void; + $destroy(): void; + [accessor: string]: any; +} + export class SvelteComponentDev extends SvelteComponent { - constructor(options) { + constructor(options: { + target: Element; + anchor?: Element; + props?: Props; + 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/index.ts b/src/runtime/internal/index.ts index 8e5725d3b..e1dd2a1fc 100644 --- a/src/runtime/internal/index.ts +++ b/src/runtime/internal/index.ts @@ -12,5 +12,4 @@ export * from './ssr'; export * from './transitions'; export * from './utils'; export * from './Component'; -export * from './ComponentApi'; export * from './dev'; From 5658d9454504b99bcc449577271a612caefe7d82 Mon Sep 17 00:00:00 2001 From: Mattias Buelens Date: Tue, 1 Oct 2019 22:49:05 +0200 Subject: [PATCH 0016/1628] Add binding for HTMLMediaElement.seeking --- src/compiler/compile/nodes/Binding.ts | 3 ++- src/compiler/compile/nodes/Element.ts | 3 ++- .../render_dom/wrappers/Element/index.ts | 6 ++++++ test/js/samples/media-bindings/expected.js | 19 ++++++++++++++++--- test/js/samples/media-bindings/input.svelte | 3 ++- 5 files changed, 28 insertions(+), 6 deletions(-) diff --git a/src/compiler/compile/nodes/Binding.ts b/src/compiler/compile/nodes/Binding.ts index 969232a17..5626ce91a 100644 --- a/src/compiler/compile/nodes/Binding.ts +++ b/src/compiler/compile/nodes/Binding.ts @@ -11,7 +11,8 @@ const read_only_media_attributes = new Set([ 'duration', 'buffered', 'seekable', - 'played' + 'played', + 'seeking' ]); export default class Binding extends Node { diff --git a/src/compiler/compile/nodes/Element.ts b/src/compiler/compile/nodes/Element.ts index 555c772f2..614e929b2 100644 --- a/src/compiler/compile/nodes/Element.ts +++ b/src/compiler/compile/nodes/Element.ts @@ -596,7 +596,8 @@ export default class Element extends Node { name === 'seekable' || name === 'played' || name === 'volume' || - name === 'playbackRate' + name === 'playbackRate' || + name === 'seeking' ) { if (this.name !== 'audio' && this.name !== 'video') { component.error(binding, { diff --git a/src/compiler/compile/render_dom/wrappers/Element/index.ts b/src/compiler/compile/render_dom/wrappers/Element/index.ts index 75c2cc609..e18e874a6 100644 --- a/src/compiler/compile/render_dom/wrappers/Element/index.ts +++ b/src/compiler/compile/render_dom/wrappers/Element/index.ts @@ -100,6 +100,12 @@ const events = [ node.is_media_node() && name === 'playbackRate' }, + { + event_names: ['seeking', 'seeked'], + filter: (node: Element, name: string) => + node.is_media_node() && + (name === 'seeking') + }, // details event { diff --git a/test/js/samples/media-bindings/expected.js b/test/js/samples/media-bindings/expected.js index 1d90e0849..454faa0fc 100644 --- a/test/js/samples/media-bindings/expected.js +++ b/test/js/samples/media-bindings/expected.js @@ -39,6 +39,7 @@ function create_fragment(ctx) { if (ctx.duration === void 0) add_render_callback(() => ctx.audio_durationchange_handler.call(audio)); if (ctx.buffered === void 0) add_render_callback(() => ctx.audio_progress_handler.call(audio)); if (ctx.buffered === void 0 || ctx.seekable === void 0) add_render_callback(() => ctx.audio_loadedmetadata_handler.call(audio)); + if (ctx.seeking === void 0) add_render_callback(() => ctx.audio_seeking_seeked_handler.call(audio)); dispose = [ listen(audio, "timeupdate", audio_timeupdate_handler), @@ -48,7 +49,9 @@ function create_fragment(ctx) { listen(audio, "progress", ctx.audio_progress_handler), listen(audio, "loadedmetadata", ctx.audio_loadedmetadata_handler), listen(audio, "volumechange", ctx.audio_volumechange_handler), - listen(audio, "ratechange", ctx.audio_ratechange_handler) + listen(audio, "ratechange", ctx.audio_ratechange_handler), + listen(audio, "seeking", ctx.audio_seeking_seeked_handler), + listen(audio, "seeked", ctx.audio_seeking_seeked_handler) ]; }, m(target, anchor) { @@ -93,6 +96,7 @@ function instance($$self, $$props, $$invalidate) { let { paused } = $$props; let { volume } = $$props; let { playbackRate } = $$props; + let { seeking } = $$props; function audio_timeupdate_handler() { played = time_ranges_to_array(this.played); @@ -133,6 +137,11 @@ function instance($$self, $$props, $$invalidate) { $$invalidate("playbackRate", playbackRate); } + function audio_seeking_seeked_handler() { + seeking = this.seeking; + $$invalidate("seeking", seeking); + } + $$self.$set = $$props => { if ("buffered" in $$props) $$invalidate("buffered", buffered = $$props.buffered); if ("seekable" in $$props) $$invalidate("seekable", seekable = $$props.seekable); @@ -142,6 +151,7 @@ function instance($$self, $$props, $$invalidate) { if ("paused" in $$props) $$invalidate("paused", paused = $$props.paused); if ("volume" in $$props) $$invalidate("volume", volume = $$props.volume); if ("playbackRate" in $$props) $$invalidate("playbackRate", playbackRate = $$props.playbackRate); + if ("seeking" in $$props) $$invalidate("seeking", seeking = $$props.seeking); }; return { @@ -153,13 +163,15 @@ function instance($$self, $$props, $$invalidate) { paused, volume, playbackRate, + seeking, audio_timeupdate_handler, audio_durationchange_handler, audio_play_pause_handler, audio_progress_handler, audio_loadedmetadata_handler, audio_volumechange_handler, - audio_ratechange_handler + audio_ratechange_handler, + audio_seeking_seeked_handler }; } @@ -175,7 +187,8 @@ class Component extends SvelteComponent { duration: 0, paused: 0, volume: 0, - playbackRate: 0 + playbackRate: 0, + seeking: 0 }); } } diff --git a/test/js/samples/media-bindings/input.svelte b/test/js/samples/media-bindings/input.svelte index 4b5793ba9..43a00182b 100644 --- a/test/js/samples/media-bindings/input.svelte +++ b/test/js/samples/media-bindings/input.svelte @@ -7,6 +7,7 @@ export let paused; export let volume; export let playbackRate; + export let seeking; -