@@ -14,12 +14,13 @@ See the License for the specific language governing permissions and
1414limitations under the License.
1515*/
1616
17- import React , { InputHTMLAttributes , SelectHTMLAttributes , TextareaHTMLAttributes } from 'react' ;
17+ import React , { InputHTMLAttributes , SelectHTMLAttributes , TextareaHTMLAttributes , RefObject } from 'react' ;
1818import classNames from 'classnames' ;
1919import { debounce } from "lodash" ;
2020
2121import * as sdk from '../../../index' ;
2222import { IFieldState , IValidationResult } from "./Validation" ;
23+ import { ComponentClass } from "../../../@types/common" ;
2324
2425// Invoke validation from user input (when typing, etc.) at most once every N ms.
2526const VALIDATION_THROTTLE_MS = 200 ;
@@ -78,26 +79,45 @@ interface IProps {
7879}
7980
8081export interface IInputProps extends IProps , InputHTMLAttributes < HTMLInputElement > {
82+ // The ref pass through to the input
83+ inputRef ?: RefObject < HTMLInputElement > ;
8184 // The element to create. Defaults to "input".
8285 element ?: "input" ;
86+ componentClass ?: undefined ;
8387 // The input's value. This is a controlled component, so the value is required.
8488 value : string ;
8589}
8690
8791interface ISelectProps extends IProps , SelectHTMLAttributes < HTMLSelectElement > {
92+ // The ref pass through to the select
93+ inputRef ?: RefObject < HTMLSelectElement > ;
8894 // To define options for a select, use <Field><option ... /></Field>
8995 element : "select" ;
96+ componentClass ?: undefined ;
9097 // The select's value. This is a controlled component, so the value is required.
9198 value : string ;
9299}
93100
94101interface ITextareaProps extends IProps , TextareaHTMLAttributes < HTMLTextAreaElement > {
102+ // The ref pass through to the textarea
103+ inputRef ?: RefObject < HTMLTextAreaElement > ;
95104 element : "textarea" ;
105+ componentClass ?: undefined ;
96106 // The textarea's value. This is a controlled component, so the value is required.
97107 value : string ;
98108}
99109
100- type PropShapes = IInputProps | ISelectProps | ITextareaProps ;
110+ export interface INativeOnChangeInputProps extends IProps , InputHTMLAttributes < HTMLInputElement > {
111+ // The ref pass through to the input
112+ inputRef ?: RefObject < HTMLInputElement > ;
113+ element : "input" ;
114+ // The custom component to render
115+ componentClass : ComponentClass ;
116+ // The input's value. This is a controlled component, so the value is required.
117+ value : string ;
118+ }
119+
120+ type PropShapes = IInputProps | ISelectProps | ITextareaProps | INativeOnChangeInputProps ;
101121
102122interface IState {
103123 valid : boolean ;
@@ -108,7 +128,7 @@ interface IState {
108128
109129export default class Field extends React . PureComponent < PropShapes , IState > {
110130 private id : string ;
111- private input : HTMLInputElement ;
131+ private inputRef : RefObject < HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement > ;
112132
113133 public static readonly defaultProps = {
114134 element : "input" ,
@@ -146,7 +166,7 @@ export default class Field extends React.PureComponent<PropShapes, IState> {
146166 }
147167
148168 public focus ( ) {
149- this . input . focus ( ) ;
169+ this . inputRef . current ? .focus ( ) ;
150170 // programmatic does not fire onFocus handler
151171 this . setState ( {
152172 focused : true ,
@@ -197,7 +217,7 @@ export default class Field extends React.PureComponent<PropShapes, IState> {
197217 if ( ! this . props . onValidate ) {
198218 return ;
199219 }
200- const value = this . input ? this . input . value : null ;
220+ const value = this . inputRef . current ?. value ?? null ;
201221 const { valid, feedback } = await this . props . onValidate ( {
202222 value,
203223 focused,
@@ -228,13 +248,13 @@ export default class Field extends React.PureComponent<PropShapes, IState> {
228248
229249 public render ( ) {
230250 /* eslint @typescript-eslint/no-unused-vars: ["error", { "ignoreRestSiblings": true }] */
231- const { element, prefixComponent, postfixComponent, className, onValidate, children,
251+ const { element, componentClass , inputRef , prefixComponent, postfixComponent, className, onValidate, children,
232252 tooltipContent, forceValidity, tooltipClassName, list, validateOnBlur, validateOnChange, validateOnFocus,
233253 usePlaceholderAsHint, forceTooltipVisible,
234254 ...inputProps } = this . props ;
235255
236- // Set some defaults for the <input> element
237- const ref = input => this . input = input ;
256+ this . inputRef = inputRef || React . createRef ( ) ;
257+
238258 inputProps . placeholder = inputProps . placeholder || inputProps . label ;
239259 inputProps . id = this . id ; // this overwrites the id from props
240260
@@ -243,9 +263,9 @@ export default class Field extends React.PureComponent<PropShapes, IState> {
243263 inputProps . onBlur = this . onBlur ;
244264
245265 // Appease typescript's inference
246- const inputProps_ = { ...inputProps , ref, list } ;
266+ const inputProps_ = { ...inputProps , ref : this . inputRef , list } ;
247267
248- const fieldInput = React . createElement ( this . props . element , inputProps_ , children ) ;
268+ const fieldInput = React . createElement ( this . props . componentClass || this . props . element , inputProps_ , children ) ;
249269
250270 let prefixContainer = null ;
251271 if ( prefixComponent ) {
@@ -257,17 +277,22 @@ export default class Field extends React.PureComponent<PropShapes, IState> {
257277 }
258278
259279 const hasValidationFlag = forceValidity !== null && forceValidity !== undefined ;
260- const fieldClasses = classNames ( "mx_Field" , `mx_Field_${ this . props . element } ` , className , {
261- // If we have a prefix element, leave the label always at the top left and
262- // don't animate it, as it looks a bit clunky and would add complexity to do
263- // properly.
264- mx_Field_labelAlwaysTopLeft : prefixComponent || usePlaceholderAsHint ,
265- mx_Field_placeholderIsHint : usePlaceholderAsHint ,
266- mx_Field_valid : hasValidationFlag ? forceValidity : onValidate && this . state . valid === true ,
267- mx_Field_invalid : hasValidationFlag
268- ? ! forceValidity
269- : onValidate && this . state . valid === false ,
270- } ) ;
280+ const fieldClasses = classNames (
281+ "mx_Field" ,
282+ `mx_Field_${ this . props . element } ` ,
283+ className ,
284+ {
285+ // If we have a prefix element, leave the label always at the top left and
286+ // don't animate it, as it looks a bit clunky and would add complexity to do
287+ // properly.
288+ mx_Field_labelAlwaysTopLeft : prefixComponent || usePlaceholderAsHint ,
289+ mx_Field_placeholderIsHint : usePlaceholderAsHint ,
290+ mx_Field_valid : hasValidationFlag ? forceValidity : onValidate && this . state . valid === true ,
291+ mx_Field_invalid : hasValidationFlag
292+ ? ! forceValidity
293+ : onValidate && this . state . valid === false ,
294+ } ,
295+ ) ;
271296
272297 // Handle displaying feedback on validity
273298 // FIXME: Using an import will result in test failures
0 commit comments