Fix email cursor jump (#18379)
* add email input fixture to show cursor jump * fix cursor jump in email input Co-authored-by: Peter Potapov <dr.potapoff-peter@yandex.ru> * add regression tests to ensure attributes are working Co-authored-by: Peter Potapov <dr.potapoff-peter@yandex.ru>
Nick Reiley committed
Apr 1, 2020 at 23:30 UTC
9b88b78b3d61e8fe81968d59b74592d5a53540b7
7 files changed
+203
-4
fixtures/dom/src/components/Header.js
+1
@@ -71,6 +71,7 @@ class Header extends React.Component {
71
<option value="/text-inputs">Text Inputs</option>
72
<option value="/number-inputs">Number Input</option>
73
<option value="/password-inputs">Password Input</option>
74
+ <option value="/email-inputs">Email Input</option>
75
<option value="/selects">Selects</option>
76
<option value="/textareas">Textareas</option>
77
<option value="/input-change-events">
fixtures/dom/src/components/fixtures/email-inputs/EmailDisabledAttributesTestCase.js
new
+39
@@ -0,0 +1,39 @@
1
+import Fixture from '../../Fixture';
2
+
3
+const React = window.React;
4
+
5
+class EmailDisabledAttributesTestCase extends React.Component {
6
+ state = {value: 'a@fb.com'};
7
+ onChange = event => {
8
+ this.setState({value: event.target.value});
9
+ };
10
+ render() {
11
+ return (
12
+ <Fixture>
13
+ <div>{this.props.children}</div>
14
+
15
+ <div className="control-box">
16
+ <fieldset>
17
+ <legend>Controlled</legend>
18
+ <input
19
+ type="email"
20
+ value={this.state.value}
21
+ onChange={this.onChange}
22
+ />
23
+ <span className="hint">
24
+ {' '}
25
+ Value: {JSON.stringify(this.state.value)}
26
+ </span>
27
+ </fieldset>
28
+
29
+ <fieldset>
30
+ <legend>Uncontrolled</legend>
31
+ <input type="email" defaultValue="" />
32
+ </fieldset>
33
+ </div>
34
+ </Fixture>
35
+ );
36
+ }
37
+}
38
+
39
+export default EmailDisabledAttributesTestCase;
fixtures/dom/src/components/fixtures/email-inputs/EmailEnabledAttributesTestCase.js
new
+48
@@ -0,0 +1,48 @@
1
+import Fixture from '../../Fixture';
2
+
3
+const React = window.React;
4
+
5
+class EmailAttributesTestCase extends React.Component {
6
+ state = {value: 'a@fb.com'};
7
+ onChange = event => {
8
+ this.setState({value: event.target.value});
9
+ };
10
+ render() {
11
+ return (
12
+ <Fixture>
13
+ <div>{this.props.children}</div>
14
+
15
+ <div className="control-box">
16
+ <fieldset>
17
+ <legend>Controlled</legend>
18
+ <input
19
+ type="email"
20
+ pattern=".+@fb.com"
21
+ maxlength={17}
22
+ multiple={true}
23
+ value={this.state.value}
24
+ onChange={this.onChange}
25
+ />
26
+ <span className="hint">
27
+ {' '}
28
+ Value: {JSON.stringify(this.state.value)}
29
+ </span>
30
+ </fieldset>
31
+
32
+ <fieldset>
33
+ <legend>Uncontrolled</legend>
34
+ <input
35
+ type="email"
36
+ defaultValue=""
37
+ pattern=".+@fb.com"
38
+ maxlength={17}
39
+ multiple={true}
40
+ />
41
+ </fieldset>
42
+ </div>
43
+ </Fixture>
44
+ );
45
+ }
46
+}
47
+
48
+export default EmailAttributesTestCase;
fixtures/dom/src/components/fixtures/email-inputs/JumpingCursorTestCase.js
new
+39
@@ -0,0 +1,39 @@
1
+import Fixture from '../../Fixture';
2
+
3
+const React = window.React;
4
+
5
+class JumpingCursorTestCase extends React.Component {
6
+ state = {value: ''};
7
+ onChange = event => {
8
+ this.setState({value: event.target.value});
9
+ };
10
+ render() {
11
+ return (
12
+ <Fixture>
13
+ <div>{this.props.children}</div>
14
+
15
+ <div className="control-box">
16
+ <fieldset>
17
+ <legend>Controlled</legend>
18
+ <input
19
+ type="email"
20
+ value={this.state.value}
21
+ onChange={this.onChange}
22
+ />
23
+ <span className="hint">
24
+ {' '}
25
+ Value: {JSON.stringify(this.state.value)}
26
+ </span>
27
+ </fieldset>
28
+
29
+ <fieldset>
30
+ <legend>Uncontrolled</legend>
31
+ <input type="email" defaultValue="" />
32
+ </fieldset>
33
+ </div>
34
+ </Fixture>
35
+ );
36
+ }
37
+}
38
+
39
+export default JumpingCursorTestCase;
fixtures/dom/src/components/fixtures/email-inputs/index.js
new
+68
@@ -0,0 +1,68 @@
1
+import FixtureSet from '../../FixtureSet';
2
+import TestCase from '../../TestCase';
3
+import JumpingCursorTestCase from './JumpingCursorTestCase';
4
+import EmailEnabledAttributesTestCase from './EmailEnabledAttributesTestCase';
5
+import EmailDisabledAttributesTestCase from './EmailDisabledAttributesTestCase';
6
+
7
+const React = window.React;
8
+
9
+function EmailInputs() {
10
+ return (
11
+ <FixtureSet title="Email inputs">
12
+ <TestCase
13
+ title="Spaces in email inputs"
14
+ description={`
15
+ Some browsers are trying to remove spaces from email inputs and after
16
+ doing this place cursor to the beginning.
17
+ `}
18
+ affectedBrowsers="Chrome">
19
+ <TestCase.Steps>
20
+ <li>Type space and character</li>
21
+ <li>Type character, space, character, delete last character</li>
22
+ </TestCase.Steps>
23
+
24
+ <TestCase.ExpectedResult>Cursor not moving.</TestCase.ExpectedResult>
25
+
26
+ <JumpingCursorTestCase />
27
+ </TestCase>
28
+
29
+ <TestCase
30
+ title="Attributes enabled"
31
+ description={`
32
+ Test enabled pattern, maxlength, multiple attributes.
33
+ `}>
34
+ <TestCase.Steps>
35
+ <li>Type after existing text ',b@tt.com'</li>
36
+ <li>Try to type spaces after typed text</li>
37
+ </TestCase.Steps>
38
+
39
+ <TestCase.ExpectedResult>
40
+ Spaces not added. When cursor hovered over input, popup "Please match
41
+ the requested format." is showed.
42
+ </TestCase.ExpectedResult>
43
+
44
+ <EmailEnabledAttributesTestCase />
45
+ </TestCase>
46
+
47
+ <TestCase
48
+ title="Attributes disabled"
49
+ description={`
50
+ Test disabled maxlength, multiple attributes.
51
+ `}>
52
+ <TestCase.Steps>
53
+ <li>Type after existing text ',b@tt.com'</li>
54
+ <li>Try to type spaces after typed text</li>
55
+ </TestCase.Steps>
56
+
57
+ <TestCase.ExpectedResult>
58
+ Spaces are added freely. When cursor hovered over input, popup "A part
59
+ following '@' should not contain the symbol ','." is showed.
60
+ </TestCase.ExpectedResult>
61
+
62
+ <EmailDisabledAttributesTestCase />
63
+ </TestCase>
64
+ </FixtureSet>
65
+ );
66
+}
67
+
68
+export default EmailInputs;
packages/react-dom/src/client/ReactDOMInput.js
+2
-2
@@ -410,8 +410,8 @@ export function setDefaultValue(
410
value: *,
411
) {
412
if (
413
- // Focused number inputs synchronize on blur. See ChangeEventPlugin.js
414
- type !== 'number' ||
413
+ // Focused number and email inputs synchronize on blur. See ChangeEventPlugin.js
414
+ (type !== 'number' && type !== 'email') ||
415
node.ownerDocument.activeElement !== node
416
) {
417
if (value == null) {
packages/react-dom/src/events/ChangeEventPlugin.js
+6
-2
@@ -236,13 +236,17 @@ function getTargetInstForInputOrChangeEvent(topLevelType, targetInst) {
236
function handleControlledInputBlur(node) {
237
let state = node._wrapperState;
238
239
- if (!state || !state.controlled || node.type !== 'number') {
239
+ if (
240
+ !state ||
241
+ !state.controlled ||
242
+ (node.type !== 'number' && node.type !== 'email')
243
+ ) {
244
return;
245
}
246
247
if (!disableInputAttributeSyncing) {
248
// If controlled, assign the value attribute to the current value on blur
245
- setDefaultValue(node, 'number', node.value);
249
+ setDefaultValue(node, node.type, node.value);
250
}
251
}
252