diff --git a/src/compiler/compile/render_dom/wrappers/Element/Attribute.ts b/src/compiler/compile/render_dom/wrappers/Element/Attribute.ts index d796dc2ab3..167808a9c7 100644 --- a/src/compiler/compile/render_dom/wrappers/Element/Attribute.ts +++ b/src/compiler/compile/render_dom/wrappers/Element/Attribute.ts @@ -75,6 +75,7 @@ export default class AttributeWrapper { const is_src = this.node.name === 'src'; // TODO retire this exception in favour of https://github.com/sveltejs/svelte/issues/3750 const is_select_value_attribute = name === 'value' && element.node.name === 'select'; + const is_update_input_value = (name === 'value') && (element.node.name === 'input' || element.node.name === 'textarea'); const should_cache = is_src || this.node.should_cache() || is_select_value_attribute; // TODO is this necessary? @@ -138,6 +139,15 @@ export default class AttributeWrapper { updater = b`${method}(${element.var}, "${name}", ${should_cache ? last : value});`; } + if (is_update_input_value) { + const selection = block.get_unique_name(`${element.var.name}_cursor_selection`); + updater = b` + const ${selection} = @save_input_selection(${element.var}); + ${updater} + @restore_input_selection(${element.var}, ${selection}); + `; + } + if (dependencies.length > 0) { let condition = block.renderer.dirty(dependencies); diff --git a/src/runtime/internal/index.ts b/src/runtime/internal/index.ts index e1dd2a1fcf..7135d7644e 100644 --- a/src/runtime/internal/index.ts +++ b/src/runtime/internal/index.ts @@ -3,6 +3,7 @@ export * from './await_block'; export * from './dom'; export * from './environment'; export * from './globals'; +export * from './input'; export * from './keyed_each'; export * from './lifecycle'; export * from './loop'; @@ -12,4 +13,4 @@ export * from './ssr'; export * from './transitions'; export * from './utils'; export * from './Component'; -export * from './dev'; +export * from './dev'; \ No newline at end of file diff --git a/src/runtime/internal/input.ts b/src/runtime/internal/input.ts new file mode 100644 index 0000000000..550c8e873b --- /dev/null +++ b/src/runtime/internal/input.ts @@ -0,0 +1,34 @@ +function has_selection_capabilities(input) { + const node_name = input.nodeName.toLowerCase(); + return ( + (node_name === 'input' && + (input.type === 'text' || + input.type === 'search' || + input.type === 'tel' || + input.type === 'url' || + input.type === 'password')) || + node_name === 'textarea' + ); +} + +function get_selection(input) { + return { + start: input.selectionStart, + end: input.selectionEnd, + }; +} + +export function restore_input_selection(input, offsets) { + let {start, end} = offsets; + if (end === undefined) { + end = start; + } + + input.selectionStart = start; + input.selectionEnd = Math.min(end, input.value.length); +} + + +export function save_input_selection(input) { + return has_selection_capabilities(input) ? get_selection(input) : null; +} diff --git a/test/js/samples/save-input-cursor-selection/expected.js b/test/js/samples/save-input-cursor-selection/expected.js new file mode 100644 index 0000000000..4c419df45b --- /dev/null +++ b/test/js/samples/save-input-cursor-selection/expected.js @@ -0,0 +1,81 @@ +/* generated by Svelte vX.Y.Z */ +import { + SvelteComponent, + append, + detach, + element, + init, + insert, + listen, + noop, + restore_input_selection, + safe_not_equal, + save_input_selection, + set_data, + space, + text +} from "svelte/internal"; + +function create_fragment(ctx) { + let input; + let t0; + let h1; + let t1; + let t2; + let dispose; + + return { + c() { + input = element("input"); + t0 = space(); + h1 = element("h1"); + t1 = text(/*name*/ ctx[0]); + t2 = text("!"); + input.value = /*name*/ ctx[0]; + dispose = listen(input, "input", /*onInput*/ ctx[1]); + }, + m(target, anchor) { + insert(target, input, anchor); + insert(target, t0, anchor); + insert(target, h1, anchor); + append(h1, t1); + append(h1, t2); + }, + p(ctx, [dirty]) { + if (dirty & /*name*/ 1) { + const input_cursor_selection = save_input_selection(input); + input.value = /*name*/ ctx[0]; + restore_input_selection(input, input_cursor_selection); + } + + if (dirty & /*name*/ 1) set_data(t1, /*name*/ ctx[0]); + }, + i: noop, + o: noop, + d(detaching) { + if (detaching) detach(input); + if (detaching) detach(t0); + if (detaching) detach(h1); + dispose(); + } + }; +} + +function instance($$self, $$props, $$invalidate) { + let name = "change me"; + + function onInput(event) { + $$invalidate(0, name = event.target.value); + } + + return [name, onInput]; +} + +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/save-input-cursor-selection/input.svelte b/test/js/samples/save-input-cursor-selection/input.svelte new file mode 100644 index 0000000000..476458a195 --- /dev/null +++ b/test/js/samples/save-input-cursor-selection/input.svelte @@ -0,0 +1,11 @@ + + + + +

{name}!

\ No newline at end of file