-
Notifications
You must be signed in to change notification settings - Fork 27.4k
fix(input): re-validate when partially editing a date-family input #12902
fix(input): re-validate when partially editing a date-family input #12902
Conversation
// without an accompanying change in the input value (thus no `input` event). | ||
// (This is only necessary on browsers that support inputs of that type - other browsers set the | ||
// `type` property to "text".) | ||
var isTypeSupported = (type === attr.type); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can this name be a bit more explicit, such as browserSupportsType
?
I wonder if the tests might benefit from the |
I thought that too (having to copy-paste all around and change small things was no fun at all). The problem is that some of the existing tests are different and some are the same, so only some of them could be But I'm OK with |
@@ -19,6 +19,7 @@ var DATETIMELOCAL_REGEXP = /^(\d{4})-(\d\d)-(\d\d)T(\d\d):(\d\d)(?::(\d\d)(\.\d{ | |||
var WEEK_REGEXP = /^(\d{4})-W(\d\d)$/; | |||
var MONTH_REGEXP = /^(\d{4})-(\d\d)$/; | |||
var TIME_REGEXP = /^(\d\d):(\d\d)(?::(\d\d)(\.\d{1,3})?)?$/; | |||
var DATE_INPUT_TYPES = ['date', 'datetime-local', 'month', 'time', 'week']; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing datetime
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Angular does not support datetime
:)
Is this one new ? (I am pretty sure it wasn't there last time I checked.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You're right, I always assumed datetime
was supported along with all the others. I thought it was just as old as the -local
version, but I really don't know...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we add support for it ? How come nobody asked for it yet.
Hm...according to MDN, only Opera supports it properly atm.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
idk, but that's probably off topic for this pr (sorry!)
In date-family input types (`date`, `datetime-local`, `month`, `time`, `week`), the user can interact with parts of the value (e.g. year, month, hours etc) independently. Neverhteless, the actual value of the element is empty (`''`) unless all parts are filled (and valid). Thus, editing a signle part of the value may result in a change in the validity state of the `<input>` (see below), without an accompanying change in the actual value of the element. In such cases, no `input` event is fired by the browser to inform Angular of the change (and the need to re-validate). --- The following scenario describes a series of interactions that would run into the problem (on a browser that supports the `date` input type): 1. Initially empty field. - `input.value`: '' - `input.validity`: {valid: true, badInput: false, ...} 2. The user fills part of the value (e.g. the year) using the keyboard. - `input.value`: '' - `input.validity`: {valid: false, badInput: true, ...} - 'input' event: Not fired (since `input.value` hasn't changed) 3. The user fills the value completely (using either the keyboard or the date-picker). - `input.value`: '<some-valid-value>' - `input.validity`: {valid: true, badInput: false, ...} - 'input' event: Fired 4. The user deletes part of the value (e.g. the year) using the keyboard. - `input.value`: '' (since a partial value is invalid) - `input.validity`: {valid: false, badInput: true, ...} - 'input' event: Fired 5. The user deletes all parts of the value using the keyboard (i.e. clears the field). - `input.value`: '' - `input.validity`: {valid: true, badInput: false, ...} - 'input' event: Not fired (since `input.value` hasn't changed) The problematic cases are (2) and (5), because there is a change in the validity state, but no 'input' event is fired to inform Angular of that change and the need to re-validate. --- This commit fixes the issue by firing an `input` event on `keyup`, correctly triggering re-validation. Fixes angular#12207
c9d4df0
to
c0afe4d
Compare
I pushed a second commit (squashable upon merge) that addresses the comments. /cc @Narretz |
@@ -2,7 +2,7 @@ | |||
|
|||
/* globals getInputCompileHelper: false */ | |||
|
|||
describe('input', function() { | |||
ddescribe('input', function() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
oops!
Regarding the repro comments in the tests: Can we link to this PR instead of repeating them? They are also in the commit message, right? It just came to my mind, does a similar problem occur when the user cuts / pastes inside the input? |
1401e5e
to
1728a2f
Compare
@Narretz, I shortened the comment and added a link to the PR (I want there to be a minimal description, because the test-code is very cryptic imo). Regarding cut/paste: Theoretically, it could (when not using the keyboard), but (in Chrome at least) you can't cut/paste from/to date inputs. |
I just noticed that the second path for older IE that also listens to key events does it a bit differently. For one, it listens to |
I think it uses The delay is because on In the case of keydown we must also check that the value actually changed to avoid marking an input as dirty from keys that don't actually change anything. Some of those are handled in the keydown handler (arrow or modifier keys) but other things (F#, ?) are not. Might be nice if those 2 different ways of ignoring keydown events were combined somehow, I'd think the one in the keydown handler can be dropped. |
As @jbedard explained, on I wanted to keep this as small as possible, so didn't bother with ignoring not-input keys (I figured the overhead would be minimal). @jbedard, raises an interesting point though, I think: marking the input as dirty when we shouldn't. @Narretz, @jbedard, do you think I should try to merge the paths for "browsers not supporting WDYT ? |
I am not sure if the paths need to be merged, but they can probably share some functions. |
Digging a little more into this, the problem does only affect Chrome and Opera atm (as they are the only ones that support these types and let you enter values partially. AFAICT, there is only a limited number of keys that are able to change the value of these fields (monstly numeric keys): For
For
I think it's best to only react on these (and still not going to be a perfect solution). |
I pushed a new commit with the described changes (basically whitelisting specific keys per input type). |
Looks good. Adds quite a few lines though ... |
And I'm not so sure about the 1000 tests that this adds :/ Could we group the date types together that react to the same keys? That should cut 3/5 of the tests. |
What is your concern with the number of tests ? We could group them in several ways (e.g. run all keyCodes in a for-loop in one test, all run all input types together), but that wouldn't reduce the number of expectations. |
It just feels weird that this simple change adds 1000 expectations. @petebacondarwin what do you think about this (the number of tests)? |
The expectations are no many because I test every keyCode that shouldn't be ignored (~200 x 5 input types). |
I understand your feelings @Narretz but I think we are happier with more tests, especially when they are mostly generated. |
I'm not 100% sure about this. Seems weird hardcoding specific key ranges that might effect parts of a date. What if different key ranges are used on different devices or browsers, or locales? What if I use the shift key because my months are capitalized? Can we not just check for value/validity changes on every keypress? Maybe then this logic could be merged with the keydown logic already present? I haven't actually tried this out though, so I might be completely missing something... |
? (key === keyOrRange) : (keyOrRange[0] <= key) && (key <= keyOrRange[1]); | ||
}); | ||
|
||
if (affectsInput) callback('input'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does passing 'input'
here do anything? It seems like this is only used doing event = ev && ev.type
which then gets passed as the "trigger" to $setViewValue
, so it will be undefined in this case...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looking at it a bit more I think this should be callback(evt)
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch. Since we are trying to "emulate" an input
event, I think it should be callback({type: 'input'})
.
What about something such as 2ffe061 instead? I think this has a few key advantages over the approach in this PR:
But it still has some issues:
I think those issues are worth the gains and simpler code though... |
@jbedard, I think there will be trade-offs in any approach. This will not solve a browser limitation 100%, but improving on the current situation is a big win. I think I like 2ffe061 better (if nothing else it's simpler). (Funny thing, it seems to be on the |
@gkalpak I think that's a github comment bug? If you just put the hash (2ffe061) directly in a comment it seems to always assume it's a commit in this repo, the actual commit is at jbedard@2ffe061 (in this case I pasted the full URL of the commit). Should I open a PR? Or do you want to change this one since you have some tests setup? |
@jbedard, feel free to open a new PR (or I will later today). |
Closing in favor of #13886. |
In date-family input types (
date
,datetime-local
,month
,time
,week
), the user can interact with parts of the value (e.g. year, month, hours etc) independently. Neverhteless, the actual value of the element is empty (''
) unless all parts are filled (and valid).Thus, editing a signle part of the value may result in a change in the validity state of the
<input>
(see below), without an accompanying change in the actual value of the element. In such cases, noinput
event is fired by the browser to inform Angular of the change (and the need to re-validate).The following scenario describes a series of interactions that would run into the problem (on a browser that supports the
date
input type):input.value
: ''input.validity
: {valid: true, badInput: false, ...}input.value
: ''input.validity
: {valid: false, badInput: true, ...}input.value
hasn't changed)date-picker).
input.value
: ''input.validity
: {valid: true, badInput: false, ...}input.value
: '' (since a partial value is invalid)input.validity
: {valid: false, badInput: true, ...}input.value
: ''input.validity
: {valid: true, badInput: false, ...}input.value
hasn't changed)The problematic cases are (2) and (5), because there is a change in the validity state, but no 'input' event is fired to inform Angular of that change and the need to re-validate.
This commit fixes the issue by firing an
input
event onkeyup
, correctly triggering re-validation.Fixes #12207