Repository: sveltejs/svelte (JavaScript) — Bug Fix

Issue:
For input[type=number] bound value not equal event value
<!--
Thanks for raising an issue! (For *questions*, we recommend instead using https://stackoverflow.com and adding the 'svelte' tag.)

To help us help you, if you've found a bug please consider the following:

* If you can demonstrate the bug using https://svelte.technology/repl, please do.
* If that's not possible, we recommend creating a small repo that illustrates the problem.
* Make sure you include information about the browser, and which version of Svelte you're using

Reproductions should be small, self-contained, correct examples – http://sscce.org.

Occasionally, this won't be possible, and that's fine – we still appreciate you raising the issue. But please understand that Svelte is run by unpaid volunteers in their free time, and issues that follow these instructions will get fixed faster.

If you have a stack trace to include, we recommend putting inside a `<details>` block for the sake of the thread's readability:

<details>
  <summary>Stack trace</summary>

  Stack trace goes here...
</details>
-->

Reproduction of issue in this REPL: https://svelte.technology/repl?version=1.20.2&gist=348577339ab2e03462b1279b38d9975b

This issue occurs when using a bound value and the `on:change` event for an `<input type="number">` element.

* What I did: enter a value in the input element, kill focus by clicking away, clear contents of input element, kill focus by clicking away.
* What happens: the first value shows up as the bound value and the event value as expected, but after clearing the input element and killing focus, the bound value is `0` while the event value is `""`.
* What I expect to happen: when an input element is cleared, the emitted `on:change` event value should allow for the empty string, and not coerce the value to `0`.

For input[type=number] bound value not equal event value
<!--
Thanks for raising an issue! (For *questions*, we recommend instead using https://stackoverflow.com and adding the 'svelte' tag.)

To help us help you, if you've found a bug please consider the following:

* If you can demonstrate the bug using https://svelte.technology/repl, please do.
* If that's not possible, we recommend creating a small repo that illustrates the problem.
* Make sure you include information about the browser, and which version of Svelte you're using

Reproductions should be small, self-contained, correct examples – http://sscce.org.

Occasionally, this won't be possible, and that's fine – we still appreciate you raising the issue. But please understand that Svelte is run by unpaid volunteers in their free time, and issues that follow these instructions will get fixed faster.

If you have a stack trace to include, we recommend putting inside a `<details>` block for the sake of the thread's readability:

<details>
  <summary>Stack trace</summary>

  Stack trace goes here...
</details>
-->

Reproduction of issue in this REPL: https://svelte.technology/repl?version=1.20.2&gist=348577339ab2e03462b1279b38d9975b

This issue occurs when using a bound value and the `on:change` event for an `<input type="number">` element.

* What I did: enter a value in the input element, kill focus by clicking away, clear contents of input element, kill focus by clicking away.
* What happens: the first value shows up as the bound value and the event value as expected, but after clearing the input element and killing focus, the bound value is `0` while the event value is `""`.
* What I expect to happen: when an input element is cleared, the emitted `on:change` event value should allow for the empty string, and not coerce the value to `0`.



Discussion:
[Here](https://github.com/sveltejs/svelte/blob/552c62aa56270d39fb9ef828c4363375565bdf77/src/generators/dom/visitors/Element/Binding.js#L182)'s where `type='range'` and `'type='number'` bound input values are getting converted to numbers. This was introduced in #436. I'm honestly not sure what the best path is here. Having access to the value as a number is nice. Is the empty string the only situation where we might lose information by doing that?

I've had unpleasant experiences in the past trying to do cross-browser stuff with `input[type=number]` and Angular 1. I'd rather users not have to deal with `undefined` vs an empty string vs a string that's a number vs an actual number. Having the bound value be set to `""` when the field is empty may well be the best solution, but I'm wary about just jumping into that.
What about treating the empty string as `undefined` for `number` and `range` inputs? That seems to me like the most appropriate way to handle this case.
[Here](https://github.com/sveltejs/svelte/blob/552c62aa56270d39fb9ef828c4363375565bdf77/src/generators/dom/visitors/Element/Binding.js#L182)'s where `type='range'` and `'type='number'` bound input values are getting converted to numbers. This was introduced in #436. I'm honestly not sure what the best path is here. Having access to the value as a number is nice. Is the empty string the only situation where we might lose information by doing that?

I've had unpleasant experiences in the past trying to do cross-browser stuff with `input[type=number]` and Angular 1. I'd rather users not have to deal with `undefined` vs an empty string vs a string that's a number vs an actual number. Having the bound value be set to `""` when the field is empty may well be the best solution, but I'm wary about just jumping into that.
What about treating the empty string as `undefined` for `number` and `range` inputs? That seems to me like the most appropriate way to handle this case.

Resolving patch:
diff --git a/src/generators/dom/visitors/Element/Binding.ts b/src/generators/dom/visitors/Element/Binding.ts
--- a/src/generators/dom/visitors/Element/Binding.ts
+++ b/src/generators/dom/visitors/Element/Binding.ts
@@ -183,7 +183,7 @@ function getBindingValue ( generator: DomGenerator, block: Block, state: State,
 
 	// <input type='range|number' bind:value>
 	if ( type === 'range' || type === 'number' ) {
-		return `+${state.parentNode}.${attribute.name}`;
+		return `${generator.helper( 'toNumber' )}( ${state.parentNode}.${attribute.name} )`;
 	}
 
 	// everything else
diff --git a/src/shared/dom.js b/src/shared/dom.js
--- a/src/shared/dom.js
+++ b/src/shared/dom.js
@@ -60,4 +60,8 @@ export function getBindingGroupValue ( group ) {
 		if ( group[i].checked ) value.push( group[i].__value );
 	}
 	return value;
+}
+
+export function toNumber ( value ) {
+	return value === '' ? undefined : +value;
 }
\ No newline at end of file


Test patch:
diff --git a/test/runtime/samples/binding-input-number/_config.js b/test/runtime/samples/binding-input-number/_config.js
--- a/test/runtime/samples/binding-input-number/_config.js
+++ b/test/runtime/samples/binding-input-number/_config.js
@@ -29,5 +29,15 @@ export default {
 			<input type='number'>
 			<p>number 44</p>
 		` );
+
+		// empty string should be treated as undefined
+		input.value = '';
+		input.dispatchEvent( event );
+
+		assert.equal( component.get( 'count' ), undefined );
+		assert.htmlEqual( target.innerHTML, `
+			<input type='number'>
+			<p>undefined undefined</p>
+		` );
 	}
 };