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 = `summarycontent + `; + 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 @@ + + +
+ summarycontent +
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 @@ + + +