From e535aed54ac00ce739ec4429dab9556ed668e9e2 Mon Sep 17 00:00:00 2001 From: Richard Harris Date: Mon, 11 Nov 2019 09:02:31 -0500 Subject: [PATCH] add scrollTop and scrollLeft bindings - fixes #3780 --- src/compiler/compile/nodes/Element.ts | 2 +- .../render_dom/wrappers/Element/Binding.ts | 6 +- .../render_dom/wrappers/Element/index.ts | 32 +++++++++ .../element-binding-scroll/expected.js | 72 +++++++++++++++++++ .../element-binding-scroll/input.svelte | 10 +++ 5 files changed, 120 insertions(+), 2 deletions(-) create mode 100644 test/js/samples/element-binding-scroll/expected.js create mode 100644 test/js/samples/element-binding-scroll/input.svelte diff --git a/src/compiler/compile/nodes/Element.ts b/src/compiler/compile/nodes/Element.ts index 555c772f23..a230c927b3 100644 --- a/src/compiler/compile/nodes/Element.ts +++ b/src/compiler/compile/nodes/Element.ts @@ -640,7 +640,7 @@ export default class Element extends Node { message: `'contenteditable' attribute cannot be dynamic if element uses two-way binding` }); } - } else if (name !== 'this') { + } else if (name !== 'this' && name !== 'scrollTop' && name !== 'scrollLeft') { component.error(binding, { code: `invalid-binding`, message: `'${binding.name}' is not a valid binding` diff --git a/src/compiler/compile/render_dom/wrappers/Element/Binding.ts b/src/compiler/compile/render_dom/wrappers/Element/Binding.ts index 90727743fa..dee244c7b0 100644 --- a/src/compiler/compile/render_dom/wrappers/Element/Binding.ts +++ b/src/compiler/compile/render_dom/wrappers/Element/Binding.ts @@ -149,6 +149,10 @@ export default class BindingWrapper { if (parent.node.get_static_attribute_value('type') === 'file') { update_dom = null; } + + case 'scrollTop': + case 'scrollLeft': + update_dom = null; } if (update_dom) { @@ -170,7 +174,7 @@ export default class BindingWrapper { if (${this.snippet} !== void 0) { ${update_dom} }`); - } else if (!/(currentTime|paused)/.test(this.node.name)) { + } else if (update_dom && !/(currentTime|paused)/.test(this.node.name)) { block.chunks.mount.push(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 168d53d997..cd71b88e5b 100644 --- a/src/compiler/compile/render_dom/wrappers/Element/index.ts +++ b/src/compiler/compile/render_dom/wrappers/Element/index.ts @@ -57,6 +57,12 @@ const events = [ dimensions.test(name) }, + { + event_names: ['scroll'], + filter: (_node: Element, name: string) => + name === 'scrollLeft' || name === 'scrollTop' + }, + // media events { event_names: ['timeupdate'], @@ -535,6 +541,32 @@ export default class ElementWrapper extends Wrapper { b`${resize_listener}.cancel();` ); } 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 0000000000..33531e36e7 --- /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 0000000000..d9ccbeb8fb --- /dev/null +++ b/test/js/samples/element-binding-scroll/input.svelte @@ -0,0 +1,10 @@ + + +
+
+ +
+
\ No newline at end of file