Skip to content

Commit 7c8f2a6

Browse files
feat(adaptive-cards): enable default browser validation of input fields
1 parent 0c88533 commit 7c8f2a6

File tree

11 files changed

+83
-5
lines changed

11 files changed

+83
-5
lines changed

src/components/adaptive-cards/AdaptiveCard/AdaptiveCard.jsx

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,17 @@ export default function AdaptiveCard({
170170
setInputs((prevInputs) => ({...prevInputs, [input.id]: input}));
171171
}, [setInputs]);
172172

173+
const setInputBrowserControlRef = useCallback((id, browserRef) => {
174+
setInputs((prevInputs) => {
175+
const input = prevInputs[id];
176+
177+
return {
178+
...prevInputs,
179+
[id]: {...input, browserRef},
180+
};
181+
});
182+
}, [setInputs]);
183+
173184
const getValue = (id, defval = '') => ((id in inputs && inputs[id].value !== undefined) ? inputs[id].value : defval);
174185

175186
const getAllValues = () => mapValues(inputs, (input) => (input.value));
@@ -197,6 +208,8 @@ export default function AdaptiveCard({
197208
error = input.errorMessage || `The value you entered must match the pattern ${input.regex}`;
198209
} else if (String(input.value).length > input.maxLength) {
199210
error = `Maximum length is ${input.maxLength}`;
211+
} else if (input.browserRef && !input.browserRef.checkValidity()) {
212+
error = 'The value you entered is invalid.';
200213
}
201214

202215
return {...input, error};
@@ -220,6 +233,7 @@ export default function AdaptiveCard({
220233
getError,
221234
validate,
222235
submit,
236+
setInputBrowserControlRef,
223237
invalidSubmit,
224238
setElement,
225239
setIsVisible,

src/components/adaptive-cards/InputDate/InputDate.jsx

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, {useContext, useEffect} from 'react';
1+
import React, {useContext, useEffect, useState} from 'react';
22
import PropTypes from 'prop-types';
33
import webexComponentClasses from '../../helpers';
44
import {formatDateTime} from '../util';
@@ -22,9 +22,12 @@ export default function InputDate({data, className, style}) {
2222
setValue,
2323
getValue,
2424
setInput,
25+
setInputBrowserControlRef,
2526
getError,
2627
} = useContext(AdaptiveCardContext);
2728

29+
const [browserControlRef, setBrowserControlRef] = useState(undefined);
30+
2831
useEffect(() => {
2932
setInput({
3033
id: data.id,
@@ -44,6 +47,10 @@ export default function InputDate({data, className, style}) {
4447
setInput,
4548
]);
4649

50+
useEffect(() => {
51+
setInputBrowserControlRef(data.id, browserControlRef);
52+
}, [browserControlRef, data.id, setInputBrowserControlRef]);
53+
4754
return (
4855
<DateInput
4956
className={cssClasses}
@@ -54,6 +61,7 @@ export default function InputDate({data, className, style}) {
5461
error={getError(data.id)}
5562
required={data.isRequired}
5663
label={formatDateTime(data.label)}
64+
onBrowserControlRef={(inputRef) => setBrowserControlRef(inputRef)}
5765
onChange={(value) => setValue(data.id, value)}
5866
/>
5967
);

src/components/adaptive-cards/InputNumber/InputNumber.jsx

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, {useContext, useEffect} from 'react';
1+
import React, {useContext, useEffect, useState} from 'react';
22
import PropTypes from 'prop-types';
33
import webexComponentClasses from '../../helpers';
44
import AdaptiveCardContext from '../context/adaptive-card-context';
@@ -22,9 +22,12 @@ export default function InputNumber({data, className, style}) {
2222
setValue,
2323
getValue,
2424
setInput,
25+
setInputBrowserControlRef,
2526
getError,
2627
} = useContext(AdaptiveCardContext);
2728

29+
const [browserControlRef, setBrowserControlRef] = useState(undefined);
30+
2831
useEffect(() => {
2932
setInput({
3033
id: data.id,
@@ -44,13 +47,18 @@ export default function InputNumber({data, className, style}) {
4447
setInput,
4548
]);
4649

50+
useEffect(() => {
51+
setInputBrowserControlRef(data.id, browserControlRef);
52+
}, [browserControlRef, data.id, setInputBrowserControlRef]);
53+
4754
return (
4855
<NumberInput
4956
className={cssClasses}
5057
error={getError(data.id)}
5158
label={formatDateTime(data.label)}
5259
max={data.max}
5360
min={data.min}
61+
onBrowserControlRef={(inputRef) => setBrowserControlRef(inputRef)}
5462
onChange={(value) => setValue(data.id, value)}
5563
placeholder={data.placeholder}
5664
required={data.isRequired}

src/components/adaptive-cards/InputText/InputText.jsx

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, {useContext, useEffect} from 'react';
1+
import React, {useContext, useEffect, useState} from 'react';
22
import PropTypes from 'prop-types';
33
import webexComponentClasses from '../../helpers';
44
import AdaptiveCardContext from '../context/adaptive-card-context';
@@ -24,11 +24,13 @@ export default function InputText({data, className, style}) {
2424
setValue,
2525
getValue,
2626
setInput,
27+
setInputBrowserControlRef,
2728
getError,
2829
} = useContext(AdaptiveCardContext);
2930
const [cssClasses, sc] = webexComponentClasses('adaptive-cards-input-text', className);
3031
const Input = data.style === 'password' ? PasswordInput : TextInput;
3132
const inlineAction = useAction(data.inlineAction);
33+
const [browserControlRef, setBrowserControlRef] = useState(undefined);
3234

3335
useEffect(() => {
3436
setInput({
@@ -49,6 +51,10 @@ export default function InputText({data, className, style}) {
4951
setInput,
5052
]);
5153

54+
useEffect(() => {
55+
setInputBrowserControlRef(data.id, browserControlRef);
56+
}, [browserControlRef, data.id, setInputBrowserControlRef]);
57+
5258
return (
5359
<div className={cssClasses} style={style}>
5460
{!data.isMultiline ? (
@@ -57,6 +63,7 @@ export default function InputText({data, className, style}) {
5763
error={getError(data.id)}
5864
label={formatDateTime(data.label)}
5965
maxLength={data.maxLength}
66+
onBrowserControlRef={(inputRef) => setBrowserControlRef(inputRef)}
6067
onChange={(value) => setValue(data.id, value)}
6168
pattern={data.regex}
6269
placeholder={data.placeholder}

src/components/adaptive-cards/InputTime/InputTime.jsx

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, {useContext, useEffect} from 'react';
1+
import React, {useContext, useEffect, useState} from 'react';
22
import PropTypes from 'prop-types';
33
import webexComponentClasses from '../../helpers';
44
import {acPropTypes, registerComponent} from '../Component/Component';
@@ -22,9 +22,12 @@ export default function InputTime({data, className, style}) {
2222
setValue,
2323
getValue,
2424
setInput,
25+
setInputBrowserControlRef,
2526
getError,
2627
} = useContext(AdaptiveCardContext);
2728

29+
const [browserControlRef, setBrowserControlRef] = useState(undefined);
30+
2831
useEffect(() => {
2932
setInput({
3033
id: data.id,
@@ -44,6 +47,10 @@ export default function InputTime({data, className, style}) {
4447
setInput,
4548
]);
4649

50+
useEffect(() => {
51+
setInputBrowserControlRef(data.id, browserControlRef);
52+
}, [browserControlRef, data.id, setInputBrowserControlRef]);
53+
4754
return (
4855
<TimeInput
4956
className={cssClasses}
@@ -54,6 +61,7 @@ export default function InputTime({data, className, style}) {
5461
error={getError(data.id)}
5562
required={data.isRequired}
5663
label={formatDateTime(data.label)}
64+
onBrowserControlRef={(inputRef) => setBrowserControlRef(inputRef)}
5765
onChange={(value) => setValue(data.id, value)}
5866
/>
5967
);

src/components/generic/InputField/InputField.jsx

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React from 'react';
1+
import React, {useEffect} from 'react';
22
import PropTypes from 'prop-types';
33
import classNames from 'classnames';
44
import webexComponentClasses from '../../helpers';
@@ -22,6 +22,7 @@ import Label from '../../inputs/Label/Label';
2222
* @param {number} [props.maxLength] Maximum number of characters allowed
2323
* @param {number} [props.min] Minimum value for the input element
2424
* @param {string} [props.name] Input name
25+
* @param {Function} [props.onBrowserControlRef] Action to perform to take the input ref
2526
* @param {Function} [props.onChange] Action to perform on input change
2627
* @param {string} [props.pattern] Specifies a regular expression that the element's value is checked against
2728
* @param {string} [props.placeholder] Input placeholder
@@ -47,6 +48,7 @@ export default function InputField({
4748
min,
4849
name,
4950
onChange,
51+
onBrowserControlRef,
5052
pattern,
5153
placeholder,
5254
required,
@@ -76,6 +78,10 @@ export default function InputField({
7678

7779
useAutoFocus(inputRef, autoFocus);
7880

81+
useEffect(() => {
82+
onBrowserControlRef(inputRef.current);
83+
}, [inputRef, onBrowserControlRef]);
84+
7985
return (
8086
<Label
8187
className={cssClasses}
@@ -139,6 +145,7 @@ InputField.propTypes = {
139145
min: PropTypes.number,
140146
name: PropTypes.string,
141147
onChange: PropTypes.func,
148+
onBrowserControlRef: PropTypes.func,
142149
pattern: PropTypes.string,
143150
placeholder: PropTypes.string,
144151
required: PropTypes.bool,
@@ -169,6 +176,7 @@ InputField.defaultProps = {
169176
min: undefined,
170177
name: undefined,
171178
onChange: undefined,
179+
onBrowserControlRef: () => {},
172180
pattern: undefined,
173181
placeholder: undefined,
174182
required: false,

src/components/inputs/DateInput/DateInput.jsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import {InputField} from '../../generic';
1515
* @param {boolean} [props.required=false] Flag indicating input required
1616
* @param {string} [props.error] Error text
1717
* @param {string} [props.label] Label text
18+
* @param {Function} [props.onBrowserControlRef] Action to perform to take the input ref
1819
* @param {Function} props.onChange Action to perform on input change
1920
* @returns {object} JSX of the component
2021
*/
@@ -27,6 +28,7 @@ export default function DateInput({
2728
required,
2829
error,
2930
label,
31+
onBrowserControlRef,
3032
onChange,
3133
}) {
3234
const [cssClasses] = webexComponentClasses('date-input', className);
@@ -41,6 +43,7 @@ export default function DateInput({
4143
required={required}
4244
error={error}
4345
onChange={onChange}
46+
onBrowserControlRef={onBrowserControlRef}
4447
value={value}
4548
label={label}
4649
/>
@@ -56,6 +59,7 @@ DateInput.propTypes = {
5659
required: PropTypes.bool,
5760
error: PropTypes.string,
5861
label: PropTypes.string,
62+
onBrowserControlRef: PropTypes.func,
5963
onChange: PropTypes.func.isRequired,
6064
};
6165

@@ -68,4 +72,5 @@ DateInput.defaultProps = {
6872
required: false,
6973
error: undefined,
7074
label: undefined,
75+
onBrowserControlRef: undefined,
7176
};

src/components/inputs/NumberInput/NumberInput.jsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ const HINTS = {
2424
* @param {number} [props.max] Maximum value for the input element
2525
* @param {number} [props.min] Minimum value for the input element
2626
* @param {string} [props.name] Input name
27+
* @param {Function} [props.onBrowserControlRef] Action to perform to take the input ref
2728
* @param {Function} props.onChange Action to perform on input change
2829
* @param {string} [props.placeholder] Input placeholder
2930
* @param {boolean} [props.required=false] Flag indicating input required
@@ -41,6 +42,7 @@ export default function NumberInput({
4142
max,
4243
min,
4344
name,
45+
onBrowserControlRef,
4446
onChange,
4547
placeholder,
4648
required,
@@ -92,6 +94,7 @@ export default function NumberInput({
9294
min={min}
9395
max={max}
9496
name={name}
97+
onBrowserControlRef={onBrowserControlRef}
9598
onChange={onChange}
9699
placeholder={placeholder}
97100
required={required}
@@ -114,6 +117,7 @@ NumberInput.propTypes = {
114117
max: PropTypes.number,
115118
min: PropTypes.number,
116119
name: PropTypes.string,
120+
onBrowserControlRef: PropTypes.func,
117121
onChange: PropTypes.func.isRequired,
118122
placeholder: PropTypes.string,
119123
required: PropTypes.bool,
@@ -134,6 +138,7 @@ NumberInput.defaultProps = {
134138
max: undefined,
135139
min: undefined,
136140
name: undefined,
141+
onBrowserControlRef: undefined,
137142
placeholder: undefined,
138143
required: false,
139144
style: undefined,

src/components/inputs/PasswordInput/PasswordInput.jsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ const HINTS = {
1919
* @param {string} [props.label] Label text
2020
* @param {number} [props.maxLength] Maximum number of characters allowed
2121
* @param {string} [props.name] Input name
22+
* @param {Function} [props.onBrowserControlRef] Action to perform to take the input ref
2223
* @param {Function} props.onChange Action to perform on input change
2324
* @param {string} [props.pattern] Specifies a regular expression that the element's value is checked against
2425
* @param {string} [props.placeholder] Input placeholder
@@ -36,6 +37,7 @@ export default function PasswordInput({
3637
label,
3738
maxLength,
3839
name,
40+
onBrowserControlRef,
3941
onChange,
4042
pattern,
4143
placeholder,
@@ -71,6 +73,7 @@ export default function PasswordInput({
7173
label={label}
7274
maxLength={maxLength}
7375
name={name}
76+
onBrowserControlRef={onBrowserControlRef}
7477
onChange={onChange}
7578
pattern={pattern}
7679
placeholder={placeholder}
@@ -92,6 +95,7 @@ PasswordInput.propTypes = {
9295
label: PropTypes.string,
9396
maxLength: PropTypes.number,
9497
name: PropTypes.string,
98+
onBrowserControlRef: PropTypes.func,
9599
onChange: PropTypes.func.isRequired,
96100
pattern: PropTypes.string,
97101
placeholder: PropTypes.string,
@@ -109,6 +113,7 @@ PasswordInput.defaultProps = {
109113
label: undefined,
110114
maxLength: undefined,
111115
name: undefined,
116+
onBrowserControlRef: undefined,
112117
pattern: undefined,
113118
placeholder: undefined,
114119
required: false,

src/components/inputs/TextInput/TextInput.jsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ const HINTS = {
1818
* @param {string} [props.label] Label text
1919
* @param {number} [props.maxLength] Maximum number of characters allowed
2020
* @param {string} [props.name] Input name
21+
* @param {Function} [props.onBrowserControlRef] Action to perform to take the input ref
2122
* @param {Function} props.onChange Action to perform on input change
2223
* @param {string} [props.pattern] Specifies a regular expression that the element's value is checked against
2324
* @param {string} [props.placeholder] Input placeholder
@@ -37,6 +38,7 @@ export default function TextInput({
3738
maxLength,
3839
name,
3940
onChange,
41+
onBrowserControlRef,
4042
pattern,
4143
placeholder,
4244
required,
@@ -64,6 +66,7 @@ export default function TextInput({
6466
maxLength={maxLength}
6567
name={name}
6668
onChange={onChange}
69+
onBrowserControlRef={onBrowserControlRef}
6770
pattern={pattern}
6871
placeholder={placeholder}
6972
required={required}
@@ -92,6 +95,7 @@ TextInput.propTypes = {
9295
tabIndex: PropTypes.number,
9396
type: PropTypes.string,
9497
value: PropTypes.string,
98+
onBrowserControlRef: PropTypes.func,
9599
};
96100

97101
TextInput.defaultProps = {
@@ -109,4 +113,5 @@ TextInput.defaultProps = {
109113
tabIndex: 0,
110114
type: 'text',
111115
value: undefined,
116+
onBrowserControlRef: undefined,
112117
};

0 commit comments

Comments
 (0)