diff --git a/CHANGELOG.md b/CHANGELOG.md index ad33840527..0938d627e5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,13 @@ ## Unreleased +* Add `|nonpassive` event modifier, explicitly passing `passive: false` ([#2068](https://github.com/sveltejs/svelte/issues/2068)) +* Fix keyed `{#each}` not reacting to key changing ([#5444](https://github.com/sveltejs/svelte/issues/5444)) +* Fix destructuring into store values ([#5449](https://github.com/sveltejs/svelte/issues/5449)) +* Fix erroneous `missing-declaration` warning with `use:obj.method` ([#5451](https://github.com/sveltejs/svelte/issues/5451)) + +## 3.26.0 + * Support `use:obj.method` as actions ([#3935](https://github.com/sveltejs/svelte/issues/3935)) * Support `_` as numeric separator ([#5407](https://github.com/sveltejs/svelte/issues/5407)) * Fix assignments to properties on store values ([#5412](https://github.com/sveltejs/svelte/issues/5412)) diff --git a/package-lock.json b/package-lock.json index 2e2a95b362..6ea2f97a27 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "svelte", - "version": "3.25.1", + "version": "3.26.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 7ebb41fdfb..6d558f35fc 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "svelte", - "version": "3.25.1", + "version": "3.26.0", "description": "Cybernetically enhanced web apps", "module": "index.mjs", "main": "index", diff --git a/site/content/blog/2019-04-15-setting-up-your-editor.md b/site/content/blog/2019-04-15-setting-up-your-editor.md index b80223d820..fb8287d0cb 100644 --- a/site/content/blog/2019-04-15-setting-up-your-editor.md +++ b/site/content/blog/2019-04-15-setting-up-your-editor.md @@ -30,7 +30,9 @@ To treat `*.svelte` files as HTML, open *__Edit → Config...__* and add the fol ## Vim/Neovim -To treat all `*.svelte` files as HTML, add the following line to your `init.vim`: +You can use the [coc-svelte extension](https://github.com/coc-extensions/coc-svelte) which utilises the official language-server. + +As an alternative you can treat all `*.svelte` files as HTML. Add the following line to your `init.vim`: ``` au! BufNewFile,BufRead *.svelte set ft=html @@ -50,13 +52,7 @@ To set the filetype for a single file, use a [modeline](https://vim.fandom.com/w ## Visual Studio Code -To treat `*.svelte` files as HTML, add the following lines to your `settings.json` file: - -```cson - "files.associations": { - "*.svelte": "html" - } -``` +We recommend using the official [Svelte for VS Code extension](https://marketplace.visualstudio.com/items?itemName=svelte.svelte-vscode). ## JetBrains WebStorm diff --git a/site/content/docs/02-template-syntax.md b/site/content/docs/02-template-syntax.md index e93449728b..f7e1ae3517 100644 --- a/site/content/docs/02-template-syntax.md +++ b/site/content/docs/02-template-syntax.md @@ -471,6 +471,7 @@ The following modifiers are available: * `preventDefault` — calls `event.preventDefault()` before running the handler * `stopPropagation` — calls `event.stopPropagation()`, preventing the event reaching the next element * `passive` — improves scrolling performance on touch/wheel events (Svelte will add it automatically where it's safe to do so) +* `nonpassive` — explicitly set `passive: false` * `capture` — fires the handler during the *capture* phase instead of the *bubbling* phase * `once` — remove the handler after the first time it runs * `self` — only trigger handler if event.target is the element itself diff --git a/site/content/tutorial/01-introduction/07-making-an-app/text.md b/site/content/tutorial/01-introduction/07-making-an-app/text.md index 4d044272b5..d75a9a626c 100644 --- a/site/content/tutorial/01-introduction/07-making-an-app/text.md +++ b/site/content/tutorial/01-introduction/07-making-an-app/text.md @@ -13,7 +13,7 @@ First, you'll need to integrate Svelte with a build tool. There are officially m Don't worry if you're relatively new to web development and haven't used these tools before. We've prepared a simple step-by-step guide, [Svelte for new developers](blog/svelte-for-new-developers), which walks you through the process. -You'll also want to configure your text editor to treat `.svelte` files the same as `.html` for the sake of syntax highlighting. [Read this guide to learn how](blog/setting-up-your-editor). +You'll also want to configure your text editor. If you're using VS Code, install the [Svelte extension](https://marketplace.visualstudio.com/items?itemName=svelte.svelte-vscode), otherwise follow [this guide](blog/setting-up-your-editor) to configure your text editor to treat `.svelte` files the same as `.html` for the sake of syntax highlighting. Then, once you've got your project set up, using Svelte components is easy. The compiler turns each component into a regular JavaScript class — just import it and instantiate with `new`: diff --git a/site/content/tutorial/05-events/03-event-modifiers/text.md b/site/content/tutorial/05-events/03-event-modifiers/text.md index 41154cafcd..2b2d6e6b31 100644 --- a/site/content/tutorial/05-events/03-event-modifiers/text.md +++ b/site/content/tutorial/05-events/03-event-modifiers/text.md @@ -21,6 +21,7 @@ The full list of modifiers: * `preventDefault` — calls `event.preventDefault()` before running the handler. Useful for client-side form handling, for example. * `stopPropagation` — calls `event.stopPropagation()`, preventing the event reaching the next element * `passive` — improves scrolling performance on touch/wheel events (Svelte will add it automatically where it's safe to do so) +* `nonpassive` — explicitly set `passive: false` * `capture` — fires the handler during the *capture* phase instead of the *bubbling* phase ([MDN docs](https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Building_blocks/Events#Event_bubbling_and_capture)) * `once` — remove the handler after the first time it runs * `self` — only trigger handler if event.target is the element itself diff --git a/src/compiler/compile/nodes/Action.ts b/src/compiler/compile/nodes/Action.ts index 86aefa0ced..dc6f080b71 100644 --- a/src/compiler/compile/nodes/Action.ts +++ b/src/compiler/compile/nodes/Action.ts @@ -11,10 +11,11 @@ export default class Action extends Node { constructor(component: Component, parent, scope, info) { super(component, parent, scope, info); - component.warn_if_undefined(info.name, info, scope); + const object = info.name.split('.')[0]; + component.warn_if_undefined(object, info, scope); this.name = info.name; - component.add_reference(info.name.split('.')[0]); + component.add_reference(object); this.expression = info.expression ? new Expression(component, this, scope, info.expression) diff --git a/src/compiler/compile/nodes/Element.ts b/src/compiler/compile/nodes/Element.ts index 8853ee9555..7c9272e5d3 100644 --- a/src/compiler/compile/nodes/Element.ts +++ b/src/compiler/compile/nodes/Element.ts @@ -81,6 +81,7 @@ const valid_modifiers = new Set([ 'capture', 'once', 'passive', + 'nonpassive', 'self' ]); @@ -771,6 +772,13 @@ export default class Element extends Node { }); } + if (handler.modifiers.has('passive') && handler.modifiers.has('nonpassive')) { + component.error(handler, { + code: 'invalid-event-modifier', + message: `The 'passive' and 'nonpassive' modifiers cannot be used together` + }); + } + handler.modifiers.forEach(modifier => { if (!valid_modifiers.has(modifier)) { component.error(handler, { @@ -805,7 +813,7 @@ export default class Element extends Node { } }); - if (passive_events.has(handler.name) && handler.can_make_passive && !handler.modifiers.has('preventDefault')) { + if (passive_events.has(handler.name) && handler.can_make_passive && !handler.modifiers.has('preventDefault') && !handler.modifiers.has('nonpassive')) { // touch/wheel events should be passive by default handler.modifiers.add('passive'); } diff --git a/src/compiler/compile/render_dom/invalidate.ts b/src/compiler/compile/render_dom/invalidate.ts index b045db079f..b891b48cb5 100644 --- a/src/compiler/compile/render_dom/invalidate.ts +++ b/src/compiler/compile/render_dom/invalidate.ts @@ -36,47 +36,46 @@ export function invalidate(renderer: Renderer, scope: Scope, node: Node, names: return renderer.invalidate(variable.name, undefined, main_execution_context); } - if (head) { - component.has_reactive_assignments = true; - - if (node.type === 'AssignmentExpression' && node.operator === '=' && nodes_match(node.left, node.right) && tail.length === 0) { - return get_invalidated(head, node); - } else { - const is_store_value = head.name[0] === '$' && head.name[1] !== '$'; - const extra_args = tail.map(variable => get_invalidated(variable)).filter(Boolean); - - const pass_value = ( - !main_execution_context && - ( - extra_args.length > 0 || - (node.type === 'AssignmentExpression' && node.left.type !== 'Identifier') || - (node.type === 'UpdateExpression' && (!node.prefix || node.argument.type !== 'Identifier')) - ) - ); + if (!head) { + return node; + } - if (pass_value) { - extra_args.unshift({ - type: 'Identifier', - name: head.name - }); - } + component.has_reactive_assignments = true; - let invalidate = is_store_value - ? x`@set_store_value(${head.name.slice(1)}, ${node}, ${head.name})` - : !main_execution_context - ? x`$$invalidate(${renderer.context_lookup.get(head.name).index}, ${node}, ${extra_args})` - : extra_args.length - ? [node, ...extra_args] - : node; + if (node.type === 'AssignmentExpression' && node.operator === '=' && nodes_match(node.left, node.right) && tail.length === 0) { + return get_invalidated(head, node); + } - if (head.subscribable && head.reassigned) { - const subscribe = `$$subscribe_${head.name}`; - invalidate = x`${subscribe}(${invalidate})`; - } + const is_store_value = head.name[0] === '$' && head.name[1] !== '$'; + const extra_args = tail.map(variable => get_invalidated(variable)).filter(Boolean); - return invalidate; + if (is_store_value) { + return x`@set_store_value(${head.name.slice(1)}, ${node}, ${head.name}, ${extra_args})`; + } + + let invalidate; + if (!main_execution_context) { + const pass_value = ( + extra_args.length > 0 || + (node.type === 'AssignmentExpression' && node.left.type !== 'Identifier') || + (node.type === 'UpdateExpression' && (!node.prefix || node.argument.type !== 'Identifier')) + ); + if (pass_value) { + extra_args.unshift({ + type: 'Identifier', + name: head.name + }); } + invalidate = x`$$invalidate(${renderer.context_lookup.get(head.name).index}, ${node}, ${extra_args})`; + } else { + // skip `$$invalidate` if it is in the main execution context + invalidate = extra_args.length ? [node, ...extra_args] : node; + } + + if (head.subscribable && head.reassigned) { + const subscribe = `$$subscribe_${head.name}`; + invalidate = x`${subscribe}(${invalidate})`; } - return node; + return invalidate; } \ No newline at end of file diff --git a/src/compiler/compile/render_dom/wrappers/EachBlock.ts b/src/compiler/compile/render_dom/wrappers/EachBlock.ts index 2513ea1adc..fe1c21a8df 100644 --- a/src/compiler/compile/render_dom/wrappers/EachBlock.ts +++ b/src/compiler/compile/render_dom/wrappers/EachBlock.ts @@ -239,6 +239,11 @@ export default class EachBlockWrapper extends Wrapper { this.node.expression.dynamic_dependencies().forEach((dependency: string) => { all_dependencies.add(dependency); }); + if (this.node.key) { + this.node.key.dynamic_dependencies().forEach((dependency: string) => { + all_dependencies.add(dependency); + }); + } this.dependencies = all_dependencies; if (this.node.key) { diff --git a/src/compiler/compile/render_dom/wrappers/Element/EventHandler.ts b/src/compiler/compile/render_dom/wrappers/Element/EventHandler.ts index 157e186ea6..2fa2e9291a 100644 --- a/src/compiler/compile/render_dom/wrappers/Element/EventHandler.ts +++ b/src/compiler/compile/render_dom/wrappers/Element/EventHandler.ts @@ -45,11 +45,17 @@ export default class EventHandlerWrapper { const args = []; - const opts = ['passive', 'once', 'capture'].filter(mod => this.node.modifiers.has(mod)); + const opts = ['nonpassive', 'passive', 'once', 'capture'].filter(mod => this.node.modifiers.has(mod)); if (opts.length) { - args.push((opts.length === 1 && opts[0] === 'capture') - ? TRUE - : x`{ ${opts.map(opt => p`${opt}: true`)} }`); + if (opts.length === 1 && opts[0] === 'capture') { + args.push(TRUE); + } else { + args.push(x`{ ${ opts.map(opt => + opt === 'nonpassive' + ? p`passive: false` + : p`${opt}: true` + ) } }`); + } } else if (block.renderer.options.dev) { args.push(FALSE); } diff --git a/test/js/samples/event-modifiers/expected.js b/test/js/samples/event-modifiers/expected.js index 6aa3c161f9..3901753661 100644 --- a/test/js/samples/event-modifiers/expected.js +++ b/test/js/samples/event-modifiers/expected.js @@ -16,41 +16,49 @@ import { } from "svelte/internal"; function create_fragment(ctx) { - let div; - let button0; + let div1; + let div0; let t1; - let button1; + let button0; let t3; + let button1; + let t5; let button2; let mounted; let dispose; return { c() { - div = element("div"); + div1 = element("div"); + div0 = element("div"); + div0.textContent = "touch me"; + t1 = space(); button0 = element("button"); button0.textContent = "click me"; - t1 = space(); + t3 = space(); button1 = element("button"); button1.textContent = "or me"; - t3 = space(); + t5 = space(); button2 = element("button"); button2.textContent = "or me!"; }, m(target, anchor) { - insert(target, div, anchor); - append(div, button0); - append(div, t1); - append(div, button1); - append(div, t3); - append(div, button2); + insert(target, div1, anchor); + append(div1, div0); + append(div1, t1); + append(div1, button0); + append(div1, t3); + append(div1, button1); + append(div1, t5); + append(div1, button2); if (!mounted) { dispose = [ + listen(div0, "touchstart", handleTouchstart, { passive: false }), listen(button0, "click", stop_propagation(prevent_default(handleClick))), listen(button1, "click", handleClick, { once: true, capture: true }), listen(button2, "click", handleClick, true), - listen(div, "touchstart", handleTouchstart, { passive: true }) + listen(div1, "touchstart", handleTouchstart, { passive: true }) ]; mounted = true; @@ -60,7 +68,7 @@ function create_fragment(ctx) { i: noop, o: noop, d(detaching) { - if (detaching) detach(div); + if (detaching) detach(div1); mounted = false; run_all(dispose); } diff --git a/test/js/samples/event-modifiers/input.svelte b/test/js/samples/event-modifiers/input.svelte index 225134f598..c72d58dabb 100644 --- a/test/js/samples/event-modifiers/input.svelte +++ b/test/js/samples/event-modifiers/input.svelte @@ -9,6 +9,7 @@