diff --git a/package-lock.json b/package-lock.json index c84f5146a041..45f91af8fe26 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "svelte", - "version": "3.8.1", + "version": "3.9.1", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/src/compiler/compile/render_dom/wrappers/Element/index.ts b/src/compiler/compile/render_dom/wrappers/Element/index.ts index 9e267b9ad06a..67311494ecd1 100644 --- a/src/compiler/compile/render_dom/wrappers/Element/index.ts +++ b/src/compiler/compile/render_dom/wrappers/Element/index.ts @@ -20,6 +20,8 @@ import add_actions from '../shared/add_actions'; import create_debugging_comment from '../shared/create_debugging_comment'; import { get_context_merger } from '../shared/get_context_merger'; import bind_this from '../shared/bind_this'; +import EventHandler from "../../../nodes/EventHandler"; +import BindingWrapper from "./Binding"; const events = [ { @@ -320,8 +322,7 @@ export default class ElementWrapper extends Wrapper { block.maintain_context = true; } - this.add_bindings(block); - this.add_event_handlers(block); + this.add_directives_in_order(block); this.add_attributes(block); this.add_transitions(block); this.add_animation(block); @@ -406,27 +407,60 @@ export default class ElementWrapper extends Wrapper { : `{}`}, ${this.node.namespace === namespaces.svg ? true : false})`; } - add_bindings(block: Block) { + add_directives_in_order(block: Block) { + const bindingGroups = events + .map(event => ({ + events: event.event_names, + bindings: this.bindings + .filter(binding => binding.node.name !== 'this') + .filter(binding => event.filter(this.node, binding.node.name)) + })) + .filter(group => group.bindings.length); + + const this_binding = this.bindings.find(b => b.node.name === 'this'); + + function getOrder (item) { + if (item instanceof EventHandler) { + return (item as EventHandler).start; + } else if (item instanceof BindingWrapper) { + return (item as BindingWrapper).node.start; + } else { + return item.bindings[0].node.start; + } + } + + const ordered = [ + ...bindingGroups, + ...this.node.handlers, + ...(this_binding ? [this_binding] : []) + ]; + ordered.sort((a, b) => getOrder(a) - getOrder(b)); + + ordered.forEach(bindingGroupOrEventHandler => { + if (bindingGroupOrEventHandler instanceof EventHandler) { + add_event_handlers(block, this.var, [bindingGroupOrEventHandler as EventHandler]) + } else if (bindingGroupOrEventHandler instanceof BindingWrapper) { + this.add_this_binding(block, bindingGroupOrEventHandler as BindingWrapper); + } else { + this.add_binding(block, bindingGroupOrEventHandler); + } + }); + } + + add_binding(block: Block, bindingGroup) { const { renderer } = this; - if (this.bindings.length === 0) return; + if (bindingGroup.bindings.length === 0) return; renderer.component.has_reactive_assignments = true; - const lock = this.bindings.some(binding => binding.needs_lock) ? + const lock = bindingGroup.bindings.some(binding => binding.needs_lock) ? block.get_unique_name(`${this.var}_updating`) : null; if (lock) block.add_variable(lock, 'false'); - const groups = events - .map(event => ({ - events: event.event_names, - bindings: this.bindings - .filter(binding => binding.node.name !== 'this') - .filter(binding => event.filter(this.node, binding.node.name)) - })) - .filter(group => group.bindings.length); + const groups = [bindingGroup]; groups.forEach(group => { const handler = renderer.component.get_unique_name(`${this.var}_${group.events.join('_')}_handler`); @@ -543,13 +577,14 @@ export default class ElementWrapper extends Wrapper { if (lock) { block.builders.update.add_line(`${lock} = false;`); } + } - const this_binding = this.bindings.find(b => b.node.name === 'this'); - if (this_binding) { - const binding_callback = bind_this(renderer.component, block, this_binding.node, this.var); + add_this_binding(block: Block, this_binding: BindingWrapper) { + const { renderer } = this; + renderer.component.has_reactive_assignments = true; - block.builders.mount.add_line(binding_callback); - } + const binding_callback = bind_this(renderer.component, block, this_binding.node, this.var); + block.builders.mount.add_line(binding_callback); } add_attributes(block: Block) { diff --git a/test/js/samples/media-bindings/expected.js b/test/js/samples/media-bindings/expected.js index f45f9ce8dbfb..ebe1fdda5f9c 100644 --- a/test/js/samples/media-bindings/expected.js +++ b/test/js/samples/media-bindings/expected.js @@ -27,18 +27,18 @@ function create_fragment(ctx) { return { c() { audio = element("audio"); - if (ctx.played === void 0 || ctx.currentTime === void 0) add_render_callback(audio_timeupdate_handler); - 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.played === void 0 || ctx.currentTime === void 0) add_render_callback(audio_timeupdate_handler); + if (ctx.duration === void 0) add_render_callback(() => ctx.audio_durationchange_handler.call(audio)); dispose = [ + listen(audio, "progress", ctx.audio_progress_handler), + listen(audio, "loadedmetadata", ctx.audio_loadedmetadata_handler), listen(audio, "timeupdate", audio_timeupdate_handler), listen(audio, "durationchange", ctx.audio_durationchange_handler), listen(audio, "play", ctx.audio_play_pause_handler), listen(audio, "pause", ctx.audio_play_pause_handler), - 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) ]; @@ -54,10 +54,10 @@ function create_fragment(ctx) { p(changed, ctx) { if (!audio_updating && changed.currentTime && !isNaN(ctx.currentTime)) audio.currentTime = ctx.currentTime; + audio_updating = false; if (changed.paused && audio_is_paused !== (audio_is_paused = ctx.paused)) audio[audio_is_paused ? "pause" : "play"](); if (changed.volume && !isNaN(ctx.volume)) audio.volume = ctx.volume; if (changed.playbackRate && !isNaN(ctx.playbackRate)) audio.playbackRate = ctx.playbackRate; - audio_updating = false; }, i: noop, @@ -76,6 +76,18 @@ function create_fragment(ctx) { function instance($$self, $$props, $$invalidate) { let { buffered, seekable, played, currentTime, duration, paused, volume, playbackRate } = $$props; + function audio_progress_handler() { + buffered = time_ranges_to_array(this.buffered); + $$invalidate('buffered', buffered); + } + + function audio_loadedmetadata_handler() { + buffered = time_ranges_to_array(this.buffered); + seekable = time_ranges_to_array(this.seekable); + $$invalidate('buffered', buffered); + $$invalidate('seekable', seekable); + } + function audio_timeupdate_handler() { played = time_ranges_to_array(this.played); currentTime = this.currentTime; @@ -93,18 +105,6 @@ function instance($$self, $$props, $$invalidate) { $$invalidate('paused', paused); } - function audio_progress_handler() { - buffered = time_ranges_to_array(this.buffered); - $$invalidate('buffered', buffered); - } - - function audio_loadedmetadata_handler() { - buffered = time_ranges_to_array(this.buffered); - seekable = time_ranges_to_array(this.seekable); - $$invalidate('buffered', buffered); - $$invalidate('seekable', seekable); - } - function audio_volumechange_handler() { volume = this.volume; $$invalidate('volume', volume); @@ -135,11 +135,11 @@ function instance($$self, $$props, $$invalidate) { paused, volume, playbackRate, + audio_progress_handler, + audio_loadedmetadata_handler, audio_timeupdate_handler, audio_durationchange_handler, audio_play_pause_handler, - audio_progress_handler, - audio_loadedmetadata_handler, audio_volumechange_handler, audio_ratechange_handler }; @@ -152,4 +152,4 @@ class Component extends SvelteComponent { } } -export default Component; \ No newline at end of file +export default Component; diff --git a/test/runtime/samples/apply-directives-in-order/_config.js b/test/runtime/samples/apply-directives-in-order/_config.js new file mode 100644 index 000000000000..e5e8980ed115 --- /dev/null +++ b/test/runtime/samples/apply-directives-in-order/_config.js @@ -0,0 +1,37 @@ +export default { + props: { + value: '' + }, + + html: ` + +

+ `, + + ssrHtml: ` + +

+ `, + + async test({ assert, component, target, window }) { + const input = target.querySelector('input'); + + const event = new window.Event('input'); + input.value = 'h'; + await input.dispatchEvent(event); + + assert.equal(input.value, 'H'); + assert.htmlEqual(target.innerHTML, ` + +

H

+ `); + + input.value = 'he'; + await input.dispatchEvent(event); + assert.equal(input.value, 'HE'); + assert.htmlEqual(target.innerHTML, ` + +

HE

+ `); + }, +}; diff --git a/test/runtime/samples/apply-directives-in-order/main.svelte b/test/runtime/samples/apply-directives-in-order/main.svelte new file mode 100644 index 000000000000..be652c7b7956 --- /dev/null +++ b/test/runtime/samples/apply-directives-in-order/main.svelte @@ -0,0 +1,10 @@ + + + +

{value}