1
1
import { faEye , faEyeSlash } from "@fortawesome/free-regular-svg-icons" ;
2
2
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome" ;
3
3
import classNames from "classnames" ;
4
- import React , { useEffect , useRef , useState } from "react" ;
4
+ import React , { useCallback , useRef , useState } from "react" ;
5
5
import { useIntl } from "react-intl" ;
6
6
import { useToggle } from "react-use" ;
7
7
import styled from "styled-components" ;
@@ -24,7 +24,6 @@ const getBackgroundColor = (props: IStyleProps) => {
24
24
export interface InputProps extends React . InputHTMLAttributes < HTMLInputElement > {
25
25
error ?: boolean ;
26
26
light ?: boolean ;
27
- defaultFocus ?: boolean ;
28
27
}
29
28
30
29
const InputContainer = styled . div < InputProps > `
@@ -83,46 +82,76 @@ const VisibilityButton = styled(Button)`
83
82
border: none;
84
83
` ;
85
84
86
- const Input : React . FC < InputProps > = ( { defaultFocus = false , onFocus , onBlur , ...props } ) => {
85
+ const Input : React . FC < InputProps > = ( { ...props } ) => {
87
86
const { formatMessage } = useIntl ( ) ;
87
+
88
88
const inputRef = useRef < HTMLInputElement | null > ( null ) ;
89
- const [ isContentVisible , setIsContentVisible ] = useToggle ( false ) ;
89
+ const buttonRef = useRef < HTMLButtonElement | null > ( null ) ;
90
+ const inputSelectionStartRef = useRef < number | null > ( null ) ;
91
+
92
+ const [ isContentVisible , toggleIsContentVisible ] = useToggle ( false ) ;
90
93
const [ focused , setFocused ] = useState ( false ) ;
91
94
92
95
const isPassword = props . type === "password" ;
93
96
const isVisibilityButtonVisible = isPassword && ! props . disabled ;
94
97
const type = isPassword ? ( isContentVisible ? "text" : "password" ) : props . type ;
95
98
96
- useEffect ( ( ) => {
97
- if ( defaultFocus && inputRef . current !== null ) {
98
- inputRef . current . focus ( ) ;
99
+ const focusOnInputElement = useCallback ( ( ) => {
100
+ if ( ! inputRef . current ) {
101
+ return ;
102
+ }
103
+
104
+ const { current : element } = inputRef ;
105
+ const selectionStart = inputSelectionStartRef . current ?? inputRef . current ?. value . length ;
106
+
107
+ element . focus ( ) ;
108
+
109
+ if ( selectionStart ) {
110
+ // Update input cursor position to where it was before
111
+ window . setTimeout ( ( ) => {
112
+ element . setSelectionRange ( selectionStart , selectionStart ) ;
113
+ } , 0 ) ;
114
+ }
115
+ } , [ ] ) ;
116
+
117
+ const onContainerFocus : React . FocusEventHandler < HTMLDivElement > = ( ) => {
118
+ setFocused ( true ) ;
119
+ } ;
120
+
121
+ const onContainerBlur : React . FocusEventHandler < HTMLDivElement > = ( event ) => {
122
+ if ( isVisibilityButtonVisible && event . target === inputRef . current ) {
123
+ // Save the previous selection
124
+ inputSelectionStartRef . current = inputRef . current . selectionStart ;
125
+ }
126
+
127
+ setFocused ( false ) ;
128
+
129
+ if ( isPassword ) {
130
+ window . setTimeout ( ( ) => {
131
+ if ( document . activeElement !== inputRef . current && document . activeElement !== buttonRef . current ) {
132
+ toggleIsContentVisible ( false ) ;
133
+ inputSelectionStartRef . current = null ;
134
+ }
135
+ } , 0 ) ;
99
136
}
100
- } , [ inputRef , defaultFocus ] ) ;
137
+ } ;
101
138
102
139
return (
103
140
< InputContainer
104
141
className = { classNames ( "input-container" , { "input-container--focused" : focused } ) }
105
142
data-testid = "input-container"
143
+ onFocus = { onContainerFocus }
144
+ onBlur = { onContainerBlur }
106
145
>
107
- < InputComponent
108
- data-testid = "input"
109
- { ...props }
110
- ref = { inputRef }
111
- type = { type }
112
- isPassword = { isPassword }
113
- onFocus = { ( event ) => {
114
- setFocused ( true ) ;
115
- onFocus ?.( event ) ;
116
- } }
117
- onBlur = { ( event ) => {
118
- setFocused ( false ) ;
119
- onBlur ?.( event ) ;
120
- } }
121
- />
146
+ < InputComponent data-testid = "input" { ...props } ref = { inputRef } type = { type } isPassword = { isPassword } />
122
147
{ isVisibilityButtonVisible ? (
123
148
< VisibilityButton
149
+ ref = { buttonRef }
124
150
iconOnly
125
- onClick = { ( ) => setIsContentVisible ( ) }
151
+ onClick = { ( ) => {
152
+ toggleIsContentVisible ( ) ;
153
+ focusOnInputElement ( ) ;
154
+ } }
126
155
type = "button"
127
156
aria-label = { formatMessage ( {
128
157
id : `ui.input.${ isContentVisible ? "hide" : "show" } Password` ,
0 commit comments