diff --git a/site/content/examples/19-7guis/05-7guis-crud/App.svelte b/site/content/examples/19-7guis/05-7guis-crud/App.svelte
index 1f39fe664270..a0d6ef7f3e56 100644
--- a/site/content/examples/19-7guis/05-7guis-crud/App.svelte
+++ b/site/content/examples/19-7guis/05-7guis-crud/App.svelte
@@ -30,10 +30,7 @@
$: selected = filteredPeople[i];
- $: {
- first = selected ? selected.first : '';
- last = selected ? selected.last : '';
- }
+ $: reset_inputs(selected);
function create() {
people = people.concat({ first, last });
@@ -53,7 +50,8 @@
}
function reset_inputs(person) {
- ({ first, last } = person);
+ first = person ? person.first : '';
+ last = person ? person.last : '';
}
diff --git a/site/content/tutorial/12-actions/01-actions/text.md b/site/content/tutorial/12-actions/01-actions/text.md
index 279dc3123f7c..38de8f9a6592 100644
--- a/site/content/tutorial/12-actions/01-actions/text.md
+++ b/site/content/tutorial/12-actions/01-actions/text.md
@@ -27,7 +27,7 @@ import { pannable } from './pannable.js';
>
```
-Open the `pannable.js` file. Like transition functions, an action function receives a `node` and some optional parameters, and returns an action object. That object must have a `destroy` function, which is called when the element is unmounted.
+Open the `pannable.js` file. Like transition functions, an action function receives a `node` and some optional parameters, and returns an action object. That object can have a `destroy` function, which is called when the element is unmounted.
We want to fire `panstart` event when the user mouses down on the element, `panmove` events (with `dx` and `dy` properties showing how far the mouse moved) when they drag it, and `panend` events when they mouse up. One possible implementation looks like this:
@@ -84,4 +84,3 @@ export function pannable(node) {
Update the `pannable` function and try moving the box around.
> This implementation is for demonstration purposes — a more complete one would also consider touch events.
-
diff --git a/site/src/routes/faq.js b/site/src/routes/faq.js
new file mode 100644
index 000000000000..6263494a4ceb
--- /dev/null
+++ b/site/src/routes/faq.js
@@ -0,0 +1,4 @@
+export function get(req, res) {
+ res.writeHead(302, { Location: 'https://github.com/sveltejs/svelte/wiki/FAQ' });
+ res.end();
+}
\ No newline at end of file
diff --git a/src/compile/nodes/Element.ts b/src/compile/nodes/Element.ts
index 19da4bcc1e80..1b2d82188cd6 100644
--- a/src/compile/nodes/Element.ts
+++ b/src/compile/nodes/Element.ts
@@ -548,7 +548,7 @@ export default class Element extends Node {
message: `'group' binding can only be used with or `
});
}
- } else if (name == 'files') {
+ } else if (name === 'files') {
if (this.name !== 'input') {
component.error(binding, {
code: `invalid-binding`,
@@ -564,6 +564,14 @@ export default class Element extends Node {
message: `'files' binding can only be used with `
});
}
+
+ } else if (name === 'open') {
+ if (this.name !== 'details') {
+ component.error(binding, {
+ code: `invalid-binding`,
+ message: `'${name}' binding can only be used with `
+ });
+ }
} else if (
name === 'currentTime' ||
name === 'duration' ||
diff --git a/src/compile/render-dom/index.ts b/src/compile/render-dom/index.ts
index 0fd0d4b172d4..1432813e9d9d 100644
--- a/src/compile/render-dom/index.ts
+++ b/src/compile/render-dom/index.ts
@@ -134,7 +134,6 @@ export default function dom(
});
if (component.compile_options.dev) {
- // TODO check no uunexpected props were passed, as well as
// checking that expected ones were passed
const expected = props.filter(prop => !prop.initialised);
@@ -395,6 +394,16 @@ export default function dom(
return $name;
});
+ let unknown_props_check;
+ if (component.compile_options.dev && writable_props.length) {
+ unknown_props_check = deindent`
+ const writable_props = [${writable_props.map(prop => `'${prop.export_name}'`).join(', ')}];
+ Object.keys($$props).forEach(key => {
+ if (!writable_props.includes(key)) console.warn(\`<${component.tag}> was created with unknown prop '\${key}'\`);
+ });
+ `;
+ }
+
builder.add_block(deindent`
function ${definition}(${args.join(', ')}) {
${reactive_store_declarations.length > 0 && `let ${reactive_store_declarations.join(', ')};`}
@@ -405,6 +414,8 @@ export default function dom(
${component.javascript}
+ ${unknown_props_check}
+
${component.slots.size && `let { $$slots = {}, $$scope } = $$props;`}
${renderer.binding_groups.length > 0 && `const $$binding_groups = [${renderer.binding_groups.map(_ => `[]`).join(', ')}];`}
diff --git a/src/compile/render-dom/wrappers/Element/index.ts b/src/compile/render-dom/wrappers/Element/index.ts
index 350459c6833f..41db1c03a04b 100644
--- a/src/compile/render-dom/wrappers/Element/index.ts
+++ b/src/compile/render-dom/wrappers/Element/index.ts
@@ -90,6 +90,12 @@ const events = [
name === 'playbackRate'
},
+ // details event
+ {
+ event_names: ['toggle'],
+ filter: (node: Element, name: string) =>
+ node.name === 'details'
+ },
];
export default class ElementWrapper extends Wrapper {
@@ -455,7 +461,7 @@ export default class ElementWrapper extends Wrapper {
function ${handler}() {
${animation_frame && deindent`
cancelAnimationFrame(${animation_frame});
- if (!${this.var}.paused) ${animation_frame} = requestAnimationFrame(${handler});`}
+ if (!${this.var}.paused) ${animation_frame} = @raf(${handler});`}
${needs_lock && `${lock} = true;`}
ctx.${handler}.call(${this.var}${contextual_dependencies.size > 0 ? ', ctx' : ''});
}
diff --git a/src/internal/loop.ts b/src/internal/loop.ts
index b6cd53a1893d..73f118c234a3 100644
--- a/src/internal/loop.ts
+++ b/src/internal/loop.ts
@@ -1,4 +1,4 @@
-import { now } from './utils';
+import { now, raf } from './utils';
export interface Task { abort(): void; promise: Promise }
@@ -14,7 +14,7 @@ function run_tasks() {
});
running = tasks.size > 0;
- if (running) requestAnimationFrame(run_tasks);
+ if (running) raf(run_tasks);
}
export function clear_loops() {
@@ -28,7 +28,7 @@ export function loop(fn: (number)=>void): Task {
if (!running) {
running = true;
- requestAnimationFrame(run_tasks);
+ raf(run_tasks);
}
return {
diff --git a/src/internal/style_manager.ts b/src/internal/style_manager.ts
index 8e4d35ca3843..9ef1b12d1cba 100644
--- a/src/internal/style_manager.ts
+++ b/src/internal/style_manager.ts
@@ -1,4 +1,5 @@
import { element } from './dom';
+import { raf } from './utils';
let stylesheet;
let active = 0;
@@ -56,7 +57,7 @@ export function delete_rule(node, name?) {
}
export function clear_rules() {
- requestAnimationFrame(() => {
+ raf(() => {
if (active) return;
let i = stylesheet.cssRules.length;
while (i--) stylesheet.deleteRule(i);
diff --git a/src/internal/utils.ts b/src/internal/utils.ts
index 8bb40ac071fe..c562cd02b6bc 100644
--- a/src/internal/utils.ts
+++ b/src/internal/utils.ts
@@ -80,11 +80,19 @@ export function exclude_internal_props(props) {
return result;
}
-export let now: () => number = typeof window !== 'undefined'
+const is_client = typeof window !== 'undefined';
+
+export let now: () => number = is_client
? () => window.performance.now()
: () => Date.now();
+export let raf = is_client ? requestAnimationFrame : noop;
+
// used internally for testing
export function set_now(fn) {
now = fn;
}
+
+export function set_raf(fn) {
+ raf = fn;
+}
diff --git a/test/js/samples/bind-open/expected.js b/test/js/samples/bind-open/expected.js
new file mode 100644
index 000000000000..7f739aec8b72
--- /dev/null
+++ b/test/js/samples/bind-open/expected.js
@@ -0,0 +1,69 @@
+/* generated by Svelte vX.Y.Z */
+import {
+ SvelteComponent,
+ detach,
+ element,
+ init,
+ insert,
+ listen,
+ noop,
+ safe_not_equal
+} from "svelte/internal";
+
+function create_fragment(ctx) {
+ var details, dispose;
+
+ return {
+ c() {
+ details = element("details");
+ details.innerHTML = `summary
content
+ `;
+ dispose = listen(details, "toggle", ctx.details_toggle_handler);
+ },
+
+ m(target, anchor) {
+ insert(target, details, anchor);
+
+ details.open = ctx.open;
+ },
+
+ p(changed, ctx) {
+ if (changed.open) details.open = ctx.open;
+ },
+
+ i: noop,
+ o: noop,
+
+ d(detaching) {
+ if (detaching) {
+ detach(details);
+ }
+
+ dispose();
+ }
+ };
+}
+
+function instance($$self, $$props, $$invalidate) {
+ let { open } = $$props;
+
+ function details_toggle_handler() {
+ open = this.open;
+ $$invalidate('open', open);
+ }
+
+ $$self.$set = $$props => {
+ if ('open' in $$props) $$invalidate('open', open = $$props.open);
+ };
+
+ return { open, details_toggle_handler };
+}
+
+class Component extends SvelteComponent {
+ constructor(options) {
+ super();
+ init(this, options, instance, create_fragment, safe_not_equal, ["open"]);
+ }
+}
+
+export default Component;
\ No newline at end of file
diff --git a/test/js/samples/bind-open/input.svelte b/test/js/samples/bind-open/input.svelte
new file mode 100644
index 000000000000..3dd2b03a739b
--- /dev/null
+++ b/test/js/samples/bind-open/input.svelte
@@ -0,0 +1,7 @@
+
+
+
+ summary
content
+
diff --git a/test/js/samples/debug-empty/expected.js b/test/js/samples/debug-empty/expected.js
index 98c2837ab71b..c82cbeddd3d0 100644
--- a/test/js/samples/debug-empty/expected.js
+++ b/test/js/samples/debug-empty/expected.js
@@ -65,6 +65,11 @@ function create_fragment(ctx) {
function instance($$self, $$props, $$invalidate) {
let { name } = $$props;
+ const writable_props = ['name'];
+ Object.keys($$props).forEach(key => {
+ if (!writable_props.includes(key)) console.warn(` was created with unknown prop '${key}'`);
+ });
+
$$self.$set = $$props => {
if ('name' in $$props) $$invalidate('name', name = $$props.name);
};
diff --git a/test/js/samples/debug-foo-bar-baz-things/expected.js b/test/js/samples/debug-foo-bar-baz-things/expected.js
index c1391475b25a..3e1571b8905e 100644
--- a/test/js/samples/debug-foo-bar-baz-things/expected.js
+++ b/test/js/samples/debug-foo-bar-baz-things/expected.js
@@ -151,6 +151,11 @@ function create_fragment(ctx) {
function instance($$self, $$props, $$invalidate) {
let { things, foo, bar, baz } = $$props;
+ const writable_props = ['things', 'foo', 'bar', 'baz'];
+ Object.keys($$props).forEach(key => {
+ if (!writable_props.includes(key)) console.warn(` was created with unknown prop '${key}'`);
+ });
+
$$self.$set = $$props => {
if ('things' in $$props) $$invalidate('things', things = $$props.things);
if ('foo' in $$props) $$invalidate('foo', foo = $$props.foo);
diff --git a/test/js/samples/debug-foo/expected.js b/test/js/samples/debug-foo/expected.js
index f5daa7dad7a2..1af0fcaebee8 100644
--- a/test/js/samples/debug-foo/expected.js
+++ b/test/js/samples/debug-foo/expected.js
@@ -151,6 +151,11 @@ function create_fragment(ctx) {
function instance($$self, $$props, $$invalidate) {
let { things, foo } = $$props;
+ const writable_props = ['things', 'foo'];
+ Object.keys($$props).forEach(key => {
+ if (!writable_props.includes(key)) console.warn(` was created with unknown prop '${key}'`);
+ });
+
$$self.$set = $$props => {
if ('things' in $$props) $$invalidate('things', things = $$props.things);
if ('foo' in $$props) $$invalidate('foo', foo = $$props.foo);
diff --git a/test/js/samples/debug-hoisted/expected.js b/test/js/samples/debug-hoisted/expected.js
index 153f92bad874..51d8bf63a3d0 100644
--- a/test/js/samples/debug-hoisted/expected.js
+++ b/test/js/samples/debug-hoisted/expected.js
@@ -53,4 +53,4 @@ class Component extends SvelteComponentDev {
}
}
-export default Component;
+export default Component;
\ No newline at end of file
diff --git a/test/js/samples/dev-warning-missing-data-computed/expected.js b/test/js/samples/dev-warning-missing-data-computed/expected.js
index 12e8a983d00c..0c193934c059 100644
--- a/test/js/samples/dev-warning-missing-data-computed/expected.js
+++ b/test/js/samples/dev-warning-missing-data-computed/expected.js
@@ -65,6 +65,11 @@ function instance($$self, $$props, $$invalidate) {
let bar;
+ const writable_props = ['foo'];
+ Object.keys($$props).forEach(key => {
+ if (!writable_props.includes(key)) console.warn(` was created with unknown prop '${key}'`);
+ });
+
$$self.$set = $$props => {
if ('foo' in $$props) $$invalidate('foo', foo = $$props.foo);
};
diff --git a/test/js/samples/media-bindings/expected.js b/test/js/samples/media-bindings/expected.js
index 8a193f698b29..f45f9ce8dbfb 100644
--- a/test/js/samples/media-bindings/expected.js
+++ b/test/js/samples/media-bindings/expected.js
@@ -8,6 +8,7 @@ import {
insert,
listen,
noop,
+ raf,
run_all,
safe_not_equal,
time_ranges_to_array
@@ -18,7 +19,7 @@ function create_fragment(ctx) {
function audio_timeupdate_handler() {
cancelAnimationFrame(audio_animationframe);
- if (!audio.paused) audio_animationframe = requestAnimationFrame(audio_timeupdate_handler);
+ if (!audio.paused) audio_animationframe = raf(audio_timeupdate_handler);
audio_updating = true;
ctx.audio_timeupdate_handler.call(audio);
}
diff --git a/test/runtime/index.js b/test/runtime/index.js
index db02ce13d47d..fd5ffdef04d5 100644
--- a/test/runtime/index.js
+++ b/test/runtime/index.js
@@ -3,7 +3,7 @@ import * as path from "path";
import * as fs from "fs";
import { rollup } from 'rollup';
import * as virtual from 'rollup-plugin-virtual';
-import { clear_loops, set_now } from "../../internal.js";
+import { clear_loops, set_now, set_raf } from "../../internal.js";
import {
showOutput,
@@ -101,7 +101,7 @@ describe("runtime", () => {
}
};
set_now(() => raf.time);
- global.requestAnimationFrame = cb => {
+ set_raf(cb => {
let called = false;
raf.callback = () => {
if (!called) {
@@ -109,7 +109,7 @@ describe("runtime", () => {
cb();
}
};
- };
+ });
try {
mod = require(`./samples/${dir}/main.svelte`);
diff --git a/test/runtime/samples/binding-details-open/_config.js b/test/runtime/samples/binding-details-open/_config.js
new file mode 100644
index 000000000000..cf2459c3f1e0
--- /dev/null
+++ b/test/runtime/samples/binding-details-open/_config.js
@@ -0,0 +1,25 @@
+export default {
+ html: `
+ toggle
+ `,
+
+ async test({ assert, component, target, window }) {
+ const details = target.querySelector('details');
+ const event = new window.Event('toggle');
+
+ details.open = true;
+ await details.dispatchEvent(event);
+ assert.equal(component.visible, true);
+ assert.htmlEqual(target.innerHTML, `
+ toggle
+ hello!
+ `);
+
+ details.open = false;
+ await details.dispatchEvent(event);
+ assert.equal(component.visible, false);
+ assert.htmlEqual(target.innerHTML, `
+ toggle
+ `);
+ }
+};
diff --git a/test/runtime/samples/binding-details-open/main.svelte b/test/runtime/samples/binding-details-open/main.svelte
new file mode 100644
index 000000000000..3a7a08e1215b
--- /dev/null
+++ b/test/runtime/samples/binding-details-open/main.svelte
@@ -0,0 +1,9 @@
+
+
+toggle
+
+{#if visible}
+ hello!
+{/if}
diff --git a/test/runtime/samples/dev-warning-unknown-props/Foo.svelte b/test/runtime/samples/dev-warning-unknown-props/Foo.svelte
new file mode 100644
index 000000000000..cebe5fd57145
--- /dev/null
+++ b/test/runtime/samples/dev-warning-unknown-props/Foo.svelte
@@ -0,0 +1,5 @@
+
+
+{foo}
diff --git a/test/runtime/samples/dev-warning-unknown-props/_config.js b/test/runtime/samples/dev-warning-unknown-props/_config.js
new file mode 100644
index 000000000000..9bff4a2a741b
--- /dev/null
+++ b/test/runtime/samples/dev-warning-unknown-props/_config.js
@@ -0,0 +1,9 @@
+export default {
+ compileOptions: {
+ dev: true
+ },
+
+ warnings: [
+ ` was created with unknown prop 'fo'`
+ ]
+};
diff --git a/test/runtime/samples/dev-warning-unknown-props/main.svelte b/test/runtime/samples/dev-warning-unknown-props/main.svelte
new file mode 100644
index 000000000000..1566cf3e41e7
--- /dev/null
+++ b/test/runtime/samples/dev-warning-unknown-props/main.svelte
@@ -0,0 +1,5 @@
+
+
+