diff --git a/src/compiler/compile/nodes/Element.ts b/src/compiler/compile/nodes/Element.ts index 416f1d7b3169..550e80ec1379 100644 --- a/src/compiler/compile/nodes/Element.ts +++ b/src/compiler/compile/nodes/Element.ts @@ -1021,7 +1021,7 @@ export default class Element extends Node { } else if (contenteditable && !contenteditable.is_static) { return component.error(contenteditable, compiler_errors.dynamic_contenteditable_attribute); } - } else if (name !== 'this') { + } else if (name !== 'this' && name !== 'scrollTop' && name !== 'scrollLeft') { return component.error(binding, compiler_errors.invalid_binding(binding.name)); } }); diff --git a/src/compiler/compile/render_dom/wrappers/Element/Binding.ts b/src/compiler/compile/render_dom/wrappers/Element/Binding.ts index 47e3795ec245..16779f59fe74 100644 --- a/src/compiler/compile/render_dom/wrappers/Element/Binding.ts +++ b/src/compiler/compile/render_dom/wrappers/Element/Binding.ts @@ -210,6 +210,10 @@ export default class BindingWrapper { update_dom = null; mount_dom = null; } + + case 'scrollTop': + case 'scrollLeft': + update_dom = null; } if (update_dom) { diff --git a/src/compiler/compile/render_dom/wrappers/Element/index.ts b/src/compiler/compile/render_dom/wrappers/Element/index.ts index 3472b0612cd2..70cdf87fce88 100644 --- a/src/compiler/compile/render_dom/wrappers/Element/index.ts +++ b/src/compiler/compile/render_dom/wrappers/Element/index.ts @@ -68,6 +68,11 @@ const events = [ filter: (_node: Element, name: string) => regex_dimensions.test(name) }, + { + event_names: ['scroll'], + filter: (_node: Element, name: string) => + name === 'scrollLeft' || name === 'scrollTop' + }, // media events { event_names: ['timeupdate'], @@ -749,6 +754,32 @@ export default class ElementWrapper extends Wrapper { b`${resize_listener}();` ); } else { + if (name === 'scroll') { + // TODO some duplication between this and Window. needs a comprehensive refactor though + const condition = changed(group.bindings.map(g => g.object)); + const scrolling = block.get_unique_name('scrolling'); + const scrolling_timeout = block.get_unique_name('scrolling_timeout'); + const clear_scrolling = block.get_unique_name('clear_scrolling'); + + block.add_variable(scrolling, x`false`); + block.add_variable(scrolling_timeout); + block.add_variable(clear_scrolling, x`() => ${scrolling} = false`); + + block.chunks.init.push(b` + @add_render_callback(() => ${callee}.call(${this.var})); + `); + + block.chunks.update.push(b` + if (${condition} && !${scrolling}) { + ${scrolling} = true; + @_clearTimeout(${scrolling_timeout}); + ${group.bindings.map(binding => b` + ${this.var}.${binding.node.name} = ${binding.snippet};`)} + ${scrolling_timeout} = @_setTimeout(${clear_scrolling}, 100); + } + `); + } + block.event_listeners.push( x`@listen(${this.var}, "${name}", ${callee})` ); diff --git a/test/js/samples/element-binding-scroll/expected.js b/test/js/samples/element-binding-scroll/expected.js new file mode 100644 index 000000000000..33531e36e73b --- /dev/null +++ b/test/js/samples/element-binding-scroll/expected.js @@ -0,0 +1,72 @@ +/* generated by Svelte vX.Y.Z */ +import { + SvelteComponent, + add_render_callback, + attr, + detach, + element, + init, + insert, + listen, + noop, + safe_not_equal +} from "svelte/internal"; + +function create_fragment(ctx) { + let div1; + let scrolling = false; + let scrolling_timeout; + let clear_scrolling = () => scrolling = false; + let dispose; + add_render_callback(ctx.div1_scroll_handler); + + return { + c() { + div1 = element("div"); + div1.innerHTML = `
`; + attr(div1, "class", "viewport"); + dispose = listen(div1, "scroll", ctx.div1_scroll_handler); + }, + m(target, anchor) { + insert(target, div1, anchor); + }, + p(changed, ctx) { + if ((changed.x || changed.y) && !scrolling) { + scrolling = true; + clearTimeout(scrolling_timeout); + div1.scrollLeft = ctx.x; + div1.scrollTop = ctx.y; + scrolling_timeout = setTimeout(clear_scrolling, 100); + } + }, + i: noop, + o: noop, + d(detaching) { + if (detaching) detach(div1); + dispose(); + } + }; +} + +function instance($$self, $$props, $$invalidate) { + let x; + let y; + + function div1_scroll_handler() { + x = this.scrollLeft; + y = this.scrollTop; + $$invalidate("x", x); + $$invalidate("y", y); + } + + return { x, y, div1_scroll_handler }; +} + +class Component extends SvelteComponent { + constructor(options) { + super(); + init(this, options, instance, create_fragment, safe_not_equal, {}); + } +} + +export default Component; \ No newline at end of file diff --git a/test/js/samples/element-binding-scroll/input.svelte b/test/js/samples/element-binding-scroll/input.svelte new file mode 100644 index 000000000000..d9ccbeb8fbea --- /dev/null +++ b/test/js/samples/element-binding-scroll/input.svelte @@ -0,0 +1,10 @@ + + + \ No newline at end of file