From a593e5fe7dfede888e54d13878d22ead4adeda66 Mon Sep 17 00:00:00 2001 From: Karina Sigartau Date: Tue, 1 Mar 2022 17:06:01 +0200 Subject: [PATCH] feat(adaptive-cards): enable default browser validation of input fields --- .../AdaptiveCard/AdaptiveCard.jsx | 23 ++++++++++++++++++- .../adaptive-cards/InputDate/InputDate.jsx | 8 ++++++- .../InputNumber/InputNumber.jsx | 8 ++++++- .../adaptive-cards/InputText/InputText.jsx | 8 ++++++- .../adaptive-cards/InputTime/InputTime.jsx | 8 ++++++- .../generic/InputField/InputField.jsx | 13 +++++++++-- src/components/inputs/DateInput/DateInput.jsx | 5 ++++ .../inputs/NumberInput/NumberInput.jsx | 5 ++++ .../inputs/PasswordInput/PasswordInput.jsx | 5 ++++ src/components/inputs/TextInput/TextInput.jsx | 5 ++++ src/components/inputs/TimeInput/TimeInput.jsx | 5 ++++ 11 files changed, 86 insertions(+), 7 deletions(-) diff --git a/src/components/adaptive-cards/AdaptiveCard/AdaptiveCard.jsx b/src/components/adaptive-cards/AdaptiveCard/AdaptiveCard.jsx index 1c15ea4bb..a0d77b1da 100644 --- a/src/components/adaptive-cards/AdaptiveCard/AdaptiveCard.jsx +++ b/src/components/adaptive-cards/AdaptiveCard/AdaptiveCard.jsx @@ -167,7 +167,25 @@ export default function AdaptiveCard({ }; const setInput = useCallback((input) => { - setInputs((prevInputs) => ({...prevInputs, [input.id]: input})); + setInputs((prevInputs) => { + const prevInput = prevInputs[input.id]; + + return { + ...prevInputs, + [input.id]: {...prevInput, ...input}, + }; + }); + }, [setInputs]); + + const setNativeInputRef = useCallback((id, nativeRef) => { + setInputs((prevInputs) => { + const input = prevInputs[id]; + + return { + ...prevInputs, + [id]: {...input, nativeRef}, + }; + }); }, [setInputs]); const getValue = (id, defval = '') => ((id in inputs && inputs[id].value !== undefined) ? inputs[id].value : defval); @@ -197,6 +215,8 @@ export default function AdaptiveCard({ error = input.errorMessage || `The value you entered must match the pattern ${input.regex}`; } else if (String(input.value).length > input.maxLength) { error = `Maximum length is ${input.maxLength}`; + } else if (input.nativeRef && !input.nativeRef.checkValidity()) { + error = 'The value you entered is invalid.'; } return {...input, error}; @@ -220,6 +240,7 @@ export default function AdaptiveCard({ getError, validate, submit, + setNativeInputRef, invalidSubmit, setElement, setIsVisible, diff --git a/src/components/adaptive-cards/InputDate/InputDate.jsx b/src/components/adaptive-cards/InputDate/InputDate.jsx index 235d0e588..2b39ccb47 100644 --- a/src/components/adaptive-cards/InputDate/InputDate.jsx +++ b/src/components/adaptive-cards/InputDate/InputDate.jsx @@ -1,4 +1,4 @@ -import React, {useContext, useEffect} from 'react'; +import React, {useCallback, useContext, useEffect} from 'react'; import PropTypes from 'prop-types'; import webexComponentClasses from '../../helpers'; import {formatDateTime} from '../util'; @@ -22,6 +22,7 @@ export default function InputDate({data, className, style}) { setValue, getValue, setInput, + setNativeInputRef, getError, } = useContext(AdaptiveCardContext); @@ -44,6 +45,10 @@ export default function InputDate({data, className, style}) { setInput, ]); + const nativeRefCallback = useCallback((nativeRef) => { + setNativeInputRef(data.id, nativeRef); + }, [data.id, setNativeInputRef]); + return ( setValue(data.id, value)} /> ); diff --git a/src/components/adaptive-cards/InputNumber/InputNumber.jsx b/src/components/adaptive-cards/InputNumber/InputNumber.jsx index 42ad0a29d..119d8ffef 100644 --- a/src/components/adaptive-cards/InputNumber/InputNumber.jsx +++ b/src/components/adaptive-cards/InputNumber/InputNumber.jsx @@ -1,4 +1,4 @@ -import React, {useContext, useEffect} from 'react'; +import React, {useCallback, useContext, useEffect} from 'react'; import PropTypes from 'prop-types'; import webexComponentClasses from '../../helpers'; import AdaptiveCardContext from '../context/adaptive-card-context'; @@ -22,6 +22,7 @@ export default function InputNumber({data, className, style}) { setValue, getValue, setInput, + setNativeInputRef, getError, } = useContext(AdaptiveCardContext); @@ -44,6 +45,10 @@ export default function InputNumber({data, className, style}) { setInput, ]); + const nativeRefCallback = useCallback((nativeRef) => { + setNativeInputRef(data.id, nativeRef); + }, [data.id, setNativeInputRef]); + return ( setValue(data.id, value)} placeholder={data.placeholder} required={data.isRequired} diff --git a/src/components/adaptive-cards/InputText/InputText.jsx b/src/components/adaptive-cards/InputText/InputText.jsx index 216d1840d..cc7da3dcb 100644 --- a/src/components/adaptive-cards/InputText/InputText.jsx +++ b/src/components/adaptive-cards/InputText/InputText.jsx @@ -1,4 +1,4 @@ -import React, {useContext, useEffect} from 'react'; +import React, {useCallback, useContext, useEffect} from 'react'; import PropTypes from 'prop-types'; import webexComponentClasses from '../../helpers'; import AdaptiveCardContext from '../context/adaptive-card-context'; @@ -24,6 +24,7 @@ export default function InputText({data, className, style}) { setValue, getValue, setInput, + setNativeInputRef, getError, } = useContext(AdaptiveCardContext); const [cssClasses, sc] = webexComponentClasses('adaptive-cards-input-text', className); @@ -49,6 +50,10 @@ export default function InputText({data, className, style}) { setInput, ]); + const nativeRefCallback = useCallback((nativeRef) => { + setNativeInputRef(data.id, nativeRef); + }, [data.id, setNativeInputRef]); + return (
{!data.isMultiline ? ( @@ -57,6 +62,7 @@ export default function InputText({data, className, style}) { error={getError(data.id)} label={formatDateTime(data.label)} maxLength={data.maxLength} + nativeRef={nativeRefCallback} onChange={(value) => setValue(data.id, value)} pattern={data.regex} placeholder={data.placeholder} diff --git a/src/components/adaptive-cards/InputTime/InputTime.jsx b/src/components/adaptive-cards/InputTime/InputTime.jsx index 77bec4ca6..e9728c967 100644 --- a/src/components/adaptive-cards/InputTime/InputTime.jsx +++ b/src/components/adaptive-cards/InputTime/InputTime.jsx @@ -1,4 +1,4 @@ -import React, {useContext, useEffect} from 'react'; +import React, {useCallback, useContext, useEffect} from 'react'; import PropTypes from 'prop-types'; import webexComponentClasses from '../../helpers'; import {acPropTypes, registerComponent} from '../Component/Component'; @@ -22,6 +22,7 @@ export default function InputTime({data, className, style}) { setValue, getValue, setInput, + setNativeInputRef, getError, } = useContext(AdaptiveCardContext); @@ -44,6 +45,10 @@ export default function InputTime({data, className, style}) { setInput, ]); + const nativeRefCallback = useCallback((nativeRef) => { + setNativeInputRef(data.id, nativeRef); + }, [data.id, setNativeInputRef]); + return ( setValue(data.id, value)} /> ); diff --git a/src/components/generic/InputField/InputField.jsx b/src/components/generic/InputField/InputField.jsx index 59e7313fd..89ed4a80e 100644 --- a/src/components/generic/InputField/InputField.jsx +++ b/src/components/generic/InputField/InputField.jsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, {useCallback} from 'react'; import PropTypes from 'prop-types'; import classNames from 'classnames'; import webexComponentClasses from '../../helpers'; @@ -22,6 +22,7 @@ import Label from '../../inputs/Label/Label'; * @param {number} [props.maxLength] Maximum number of characters allowed * @param {number} [props.min] Minimum value for the input element * @param {string} [props.name] Input name + * @param {Function} [props.nativeRef] Action to perform to obtain the native input ref * @param {Function} [props.onChange] Action to perform on input change * @param {string} [props.pattern] Specifies a regular expression that the element's value is checked against * @param {string} [props.placeholder] Input placeholder @@ -46,6 +47,7 @@ export default function InputField({ maxLength, min, name, + nativeRef, onChange, pattern, placeholder, @@ -74,6 +76,11 @@ export default function InputField({ } }; + const ref2 = useCallback((node) => { + inputRef(node); + nativeRef(node); + }, [nativeRef]); + useAutoFocus(inputRef, autoFocus); return ( @@ -105,7 +112,7 @@ export default function InputField({ onKeyDown={onKeyDown} pattern={pattern} placeholder={placeholder} - ref={inputRef} + ref={ref2} required={required} tabIndex={tabIndex} type={type} @@ -138,6 +145,7 @@ InputField.propTypes = { maxLength: PropTypes.number, min: PropTypes.number, name: PropTypes.string, + nativeRef: PropTypes.func, onChange: PropTypes.func, pattern: PropTypes.string, placeholder: PropTypes.string, @@ -168,6 +176,7 @@ InputField.defaultProps = { maxLength: undefined, min: undefined, name: undefined, + nativeRef: () => {}, onChange: undefined, pattern: undefined, placeholder: undefined, diff --git a/src/components/inputs/DateInput/DateInput.jsx b/src/components/inputs/DateInput/DateInput.jsx index bf5623844..88aa1d7f5 100644 --- a/src/components/inputs/DateInput/DateInput.jsx +++ b/src/components/inputs/DateInput/DateInput.jsx @@ -15,6 +15,7 @@ import {InputField} from '../../generic'; * @param {boolean} [props.required=false] Flag indicating input required * @param {string} [props.error] Error text * @param {string} [props.label] Label text + * @param {Function} [props.nativeRef] Action to perform to obtain the native input ref * @param {Function} props.onChange Action to perform on input change * @returns {object} JSX of the component */ @@ -27,6 +28,7 @@ export default function DateInput({ required, error, label, + nativeRef, onChange, }) { const [cssClasses] = webexComponentClasses('date-input', className); @@ -41,6 +43,7 @@ export default function DateInput({ required={required} error={error} onChange={onChange} + nativeRef={nativeRef} value={value} label={label} /> @@ -56,6 +59,7 @@ DateInput.propTypes = { required: PropTypes.bool, error: PropTypes.string, label: PropTypes.string, + nativeRef: PropTypes.func, onChange: PropTypes.func.isRequired, }; @@ -68,4 +72,5 @@ DateInput.defaultProps = { required: false, error: undefined, label: undefined, + nativeRef: undefined, }; diff --git a/src/components/inputs/NumberInput/NumberInput.jsx b/src/components/inputs/NumberInput/NumberInput.jsx index af9eac821..355284254 100644 --- a/src/components/inputs/NumberInput/NumberInput.jsx +++ b/src/components/inputs/NumberInput/NumberInput.jsx @@ -24,6 +24,7 @@ const HINTS = { * @param {number} [props.max] Maximum value for the input element * @param {number} [props.min] Minimum value for the input element * @param {string} [props.name] Input name + * @param {Function} [props.nativeRef] Action to perform to obtain the native input ref * @param {Function} props.onChange Action to perform on input change * @param {string} [props.placeholder] Input placeholder * @param {boolean} [props.required=false] Flag indicating input required @@ -41,6 +42,7 @@ export default function NumberInput({ max, min, name, + nativeRef, onChange, placeholder, required, @@ -92,6 +94,7 @@ export default function NumberInput({ min={min} max={max} name={name} + nativeRef={nativeRef} onChange={onChange} placeholder={placeholder} required={required} @@ -114,6 +117,7 @@ NumberInput.propTypes = { max: PropTypes.number, min: PropTypes.number, name: PropTypes.string, + nativeRef: PropTypes.func, onChange: PropTypes.func.isRequired, placeholder: PropTypes.string, required: PropTypes.bool, @@ -134,6 +138,7 @@ NumberInput.defaultProps = { max: undefined, min: undefined, name: undefined, + nativeRef: undefined, placeholder: undefined, required: false, style: undefined, diff --git a/src/components/inputs/PasswordInput/PasswordInput.jsx b/src/components/inputs/PasswordInput/PasswordInput.jsx index db4a8dec2..45d777e43 100644 --- a/src/components/inputs/PasswordInput/PasswordInput.jsx +++ b/src/components/inputs/PasswordInput/PasswordInput.jsx @@ -19,6 +19,7 @@ const HINTS = { * @param {string} [props.label] Label text * @param {number} [props.maxLength] Maximum number of characters allowed * @param {string} [props.name] Input name + * @param {Function} [props.nativeRef] Action to perform to obtain the native input ref * @param {Function} props.onChange Action to perform on input change * @param {string} [props.pattern] Specifies a regular expression that the element's value is checked against * @param {string} [props.placeholder] Input placeholder @@ -36,6 +37,7 @@ export default function PasswordInput({ label, maxLength, name, + nativeRef, onChange, pattern, placeholder, @@ -71,6 +73,7 @@ export default function PasswordInput({ label={label} maxLength={maxLength} name={name} + nativeRef={nativeRef} onChange={onChange} pattern={pattern} placeholder={placeholder} @@ -92,6 +95,7 @@ PasswordInput.propTypes = { label: PropTypes.string, maxLength: PropTypes.number, name: PropTypes.string, + nativeRef: PropTypes.func, onChange: PropTypes.func.isRequired, pattern: PropTypes.string, placeholder: PropTypes.string, @@ -109,6 +113,7 @@ PasswordInput.defaultProps = { label: undefined, maxLength: undefined, name: undefined, + nativeRef: undefined, pattern: undefined, placeholder: undefined, required: false, diff --git a/src/components/inputs/TextInput/TextInput.jsx b/src/components/inputs/TextInput/TextInput.jsx index 8e85a2ee1..891f5c52f 100644 --- a/src/components/inputs/TextInput/TextInput.jsx +++ b/src/components/inputs/TextInput/TextInput.jsx @@ -18,6 +18,7 @@ const HINTS = { * @param {string} [props.label] Label text * @param {number} [props.maxLength] Maximum number of characters allowed * @param {string} [props.name] Input name + * @param {Function} [props.nativeRef] Action to perform to obtain the native input ref * @param {Function} props.onChange Action to perform on input change * @param {string} [props.pattern] Specifies a regular expression that the element's value is checked against * @param {string} [props.placeholder] Input placeholder @@ -37,6 +38,7 @@ export default function TextInput({ maxLength, name, onChange, + nativeRef, pattern, placeholder, required, @@ -63,6 +65,7 @@ export default function TextInput({ label={label} maxLength={maxLength} name={name} + nativeRef={nativeRef} onChange={onChange} pattern={pattern} placeholder={placeholder} @@ -84,6 +87,7 @@ TextInput.propTypes = { label: PropTypes.string, maxLength: PropTypes.number, name: PropTypes.string, + nativeRef: PropTypes.func, onChange: PropTypes.func.isRequired, pattern: PropTypes.string, placeholder: PropTypes.string, @@ -102,6 +106,7 @@ TextInput.defaultProps = { label: undefined, maxLength: undefined, name: undefined, + nativeRef: undefined, pattern: undefined, placeholder: undefined, required: false, diff --git a/src/components/inputs/TimeInput/TimeInput.jsx b/src/components/inputs/TimeInput/TimeInput.jsx index c5a1a2659..eaa5e016d 100644 --- a/src/components/inputs/TimeInput/TimeInput.jsx +++ b/src/components/inputs/TimeInput/TimeInput.jsx @@ -15,6 +15,7 @@ import {InputField} from '../../generic'; * @param {boolean} [props.required=false] Flag indicating input required * @param {string} [props.error] Error text * @param {string} [props.label] Label text + * @param {Function} [props.nativeRef] Action to perform to obtain the native input ref * @param {Function} props.onChange Action to perform on input change * @returns {object} JSX of the component */ @@ -27,6 +28,7 @@ export default function TimeInput({ required, error, label, + nativeRef, onChange, }) { const [cssClasses] = webexComponentClasses('time-input', className); @@ -41,6 +43,7 @@ export default function TimeInput({ required={required} error={error} onChange={onChange} + nativeRef={nativeRef} value={value} label={label} /> @@ -56,6 +59,7 @@ TimeInput.propTypes = { required: PropTypes.bool, error: PropTypes.string, label: PropTypes.string, + nativeRef: PropTypes.func, onChange: PropTypes.func.isRequired, }; @@ -68,4 +72,5 @@ TimeInput.defaultProps = { required: false, error: undefined, label: undefined, + nativeRef: undefined, };