@samitouri / QOS-React-2 / commits / 9b88b78b3d

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