@okta/odyssey-react-mui 1.8.0 → 1.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +6 -0
- package/dist/@types/react-augment.d.js +2 -0
- package/dist/@types/react-augment.d.js.map +1 -0
- package/dist/Autocomplete.js +32 -23
- package/dist/Autocomplete.js.map +1 -1
- package/dist/Checkbox.js +16 -7
- package/dist/Checkbox.js.map +1 -1
- package/dist/NativeSelect.js +22 -8
- package/dist/NativeSelect.js.map +1 -1
- package/dist/PasswordField.js +19 -5
- package/dist/PasswordField.js.map +1 -1
- package/dist/RadioGroup.js +11 -8
- package/dist/RadioGroup.js.map +1 -1
- package/dist/SearchField.js +17 -16
- package/dist/SearchField.js.map +1 -1
- package/dist/Select.js +34 -18
- package/dist/Select.js.map +1 -1
- package/dist/TextField.js +20 -6
- package/dist/TextField.js.map +1 -1
- package/dist/inputUtils.js +46 -0
- package/dist/inputUtils.js.map +1 -0
- package/dist/labs/VirtualizedAutocomplete.js +29 -23
- package/dist/labs/VirtualizedAutocomplete.js.map +1 -1
- package/dist/src/Autocomplete.d.ts +0 -1
- package/dist/src/Autocomplete.d.ts.map +1 -1
- package/dist/src/Checkbox.d.ts.map +1 -1
- package/dist/src/NativeSelect.d.ts +19 -44
- package/dist/src/NativeSelect.d.ts.map +1 -1
- package/dist/src/PasswordField.d.ts +10 -2
- package/dist/src/PasswordField.d.ts.map +1 -1
- package/dist/src/RadioGroup.d.ts.map +1 -1
- package/dist/src/SearchField.d.ts +10 -2
- package/dist/src/SearchField.d.ts.map +1 -1
- package/dist/src/Select.d.ts +5 -1
- package/dist/src/Select.d.ts.map +1 -1
- package/dist/src/TextField.d.ts +8 -0
- package/dist/src/TextField.d.ts.map +1 -1
- package/dist/src/inputUtils.d.ts +47 -0
- package/dist/src/inputUtils.d.ts.map +1 -0
- package/dist/src/labs/VirtualizedAutocomplete.d.ts.map +1 -1
- package/dist/tsconfig.production.tsbuildinfo +1 -1
- package/package.json +3 -3
- package/src/@types/react-augment.d.ts +19 -0
- package/src/Autocomplete.tsx +49 -43
- package/src/Checkbox.tsx +16 -9
- package/src/NativeSelect.tsx +78 -25
- package/src/PasswordField.tsx +32 -4
- package/src/RadioGroup.tsx +12 -10
- package/src/SearchField.tsx +24 -18
- package/src/Select.tsx +43 -25
- package/src/TextField.tsx +33 -5
- package/src/inputUtils.ts +76 -0
- package/src/labs/VirtualizedAutocomplete.tsx +48 -42
- package/dist/src/useControlledState.d.ts +0 -28
- package/dist/src/useControlledState.d.ts.map +0 -1
- package/dist/useControlledState.js +0 -33
- package/dist/useControlledState.js.map +0 -1
- package/src/useControlledState.ts +0 -56
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@okta/odyssey-react-mui",
|
|
3
|
-
"version": "1.8.
|
|
3
|
+
"version": "1.8.1",
|
|
4
4
|
"description": "React MUI components for Odyssey, Okta's design system",
|
|
5
5
|
"author": "Okta, Inc.",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -51,7 +51,7 @@
|
|
|
51
51
|
"@mui/system": "^5.14.9",
|
|
52
52
|
"@mui/utils": "^5.11.2",
|
|
53
53
|
"@mui/x-date-pickers": "^5.0.15",
|
|
54
|
-
"@okta/odyssey-design-tokens": "1.8.
|
|
54
|
+
"@okta/odyssey-design-tokens": "1.8.1",
|
|
55
55
|
"date-fns": "^2.30.0",
|
|
56
56
|
"i18next": "^23.5.1",
|
|
57
57
|
"material-react-table": "^2.0.2",
|
|
@@ -63,5 +63,5 @@
|
|
|
63
63
|
"react": ">=17 <19",
|
|
64
64
|
"react-dom": ">=17 <19"
|
|
65
65
|
},
|
|
66
|
-
"gitHead": "
|
|
66
|
+
"gitHead": "c1616886687301f9d71d0e7f1442f72b2f520ac1"
|
|
67
67
|
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* Copyright (c) 2023-present, Okta, Inc. and/or its affiliates. All rights reserved.
|
|
3
|
+
* The Okta software accompanied by this notice is provided pursuant to the Apache License, Version 2.0 (the "License.")
|
|
4
|
+
*
|
|
5
|
+
* You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0.
|
|
6
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
7
|
+
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
8
|
+
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
9
|
+
*
|
|
10
|
+
* See the License for the specific language governing permissions and limitations under the License.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { FC } from "react";
|
|
14
|
+
|
|
15
|
+
export interface ForwardRefWithType extends FC<WithForwardRefProps<Option>> {
|
|
16
|
+
<T extends Option>(props: WithForwardRefProps<T>): ReturnType<
|
|
17
|
+
FC<WithForwardRefProps<T>>
|
|
18
|
+
>;
|
|
19
|
+
}
|
package/src/Autocomplete.tsx
CHANGED
|
@@ -17,12 +17,16 @@ import {
|
|
|
17
17
|
UseAutocompleteProps,
|
|
18
18
|
AutocompleteValue,
|
|
19
19
|
} from "@mui/material";
|
|
20
|
-
import { memo, useCallback, useMemo } from "react";
|
|
20
|
+
import { memo, useCallback, useMemo, useRef } from "react";
|
|
21
21
|
|
|
22
22
|
import { Field } from "./Field";
|
|
23
23
|
import { FieldComponentProps } from "./FieldComponentProps";
|
|
24
24
|
import type { SeleniumProps } from "./SeleniumProps";
|
|
25
|
-
import {
|
|
25
|
+
import {
|
|
26
|
+
ComponentControlledState,
|
|
27
|
+
useInputValues,
|
|
28
|
+
getControlState,
|
|
29
|
+
} from "./inputUtils";
|
|
26
30
|
|
|
27
31
|
export type AutocompleteProps<
|
|
28
32
|
OptionType,
|
|
@@ -31,7 +35,6 @@ export type AutocompleteProps<
|
|
|
31
35
|
> = {
|
|
32
36
|
/**
|
|
33
37
|
* The default value. Use when the component is not controlled.
|
|
34
|
-
* @default props.multiple ? [] : null
|
|
35
38
|
*/
|
|
36
39
|
defaultValue?: UseAutocompleteProps<
|
|
37
40
|
OptionType,
|
|
@@ -189,6 +192,45 @@ const Autocomplete = <
|
|
|
189
192
|
getIsOptionEqualToValue,
|
|
190
193
|
testId,
|
|
191
194
|
}: AutocompleteProps<OptionType, HasMultipleChoices, IsCustomValueAllowed>) => {
|
|
195
|
+
const controlledStateRef = useRef(
|
|
196
|
+
getControlState({ controlledValue: value, uncontrolledValue: defaultValue })
|
|
197
|
+
);
|
|
198
|
+
const defaultValueProp = useMemo<
|
|
199
|
+
| AutocompleteValue<
|
|
200
|
+
OptionType,
|
|
201
|
+
HasMultipleChoices,
|
|
202
|
+
undefined,
|
|
203
|
+
IsCustomValueAllowed
|
|
204
|
+
>
|
|
205
|
+
| undefined
|
|
206
|
+
>(() => {
|
|
207
|
+
if (hasMultipleChoices) {
|
|
208
|
+
if (value === undefined) {
|
|
209
|
+
return defaultValue;
|
|
210
|
+
}
|
|
211
|
+
return [] as AutocompleteValue<
|
|
212
|
+
OptionType,
|
|
213
|
+
HasMultipleChoices,
|
|
214
|
+
undefined,
|
|
215
|
+
IsCustomValueAllowed
|
|
216
|
+
>;
|
|
217
|
+
}
|
|
218
|
+
return value === undefined ? defaultValue : undefined;
|
|
219
|
+
}, [defaultValue, hasMultipleChoices, value]);
|
|
220
|
+
|
|
221
|
+
const valueProps = useInputValues({
|
|
222
|
+
defaultValue: defaultValueProp,
|
|
223
|
+
value: value,
|
|
224
|
+
controlState: controlledStateRef.current,
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
const inputValueProp = useMemo(() => {
|
|
228
|
+
if (controlledStateRef.current === ComponentControlledState.CONTROLLED) {
|
|
229
|
+
return { inputValue };
|
|
230
|
+
}
|
|
231
|
+
return undefined;
|
|
232
|
+
}, [inputValue]);
|
|
233
|
+
|
|
192
234
|
const renderInput = useCallback(
|
|
193
235
|
({ InputLabelProps, InputProps, ...params }) => (
|
|
194
236
|
<Field
|
|
@@ -223,39 +265,6 @@ const Autocomplete = <
|
|
|
223
265
|
),
|
|
224
266
|
[errorMessage, hint, isOptional, label, nameOverride]
|
|
225
267
|
);
|
|
226
|
-
|
|
227
|
-
const defaultValuesProp = useMemo<
|
|
228
|
-
| AutocompleteValue<
|
|
229
|
-
OptionType,
|
|
230
|
-
HasMultipleChoices,
|
|
231
|
-
undefined,
|
|
232
|
-
IsCustomValueAllowed
|
|
233
|
-
>
|
|
234
|
-
| undefined
|
|
235
|
-
>(() => {
|
|
236
|
-
if (hasMultipleChoices) {
|
|
237
|
-
return defaultValue === undefined
|
|
238
|
-
? ([] as AutocompleteValue<
|
|
239
|
-
OptionType,
|
|
240
|
-
HasMultipleChoices,
|
|
241
|
-
undefined,
|
|
242
|
-
IsCustomValueAllowed
|
|
243
|
-
>)
|
|
244
|
-
: defaultValue;
|
|
245
|
-
}
|
|
246
|
-
return defaultValue ?? undefined;
|
|
247
|
-
}, [defaultValue, hasMultipleChoices]);
|
|
248
|
-
|
|
249
|
-
const [localValue, setLocalValue] = useControlledState({
|
|
250
|
-
controlledValue: value,
|
|
251
|
-
uncontrolledValue: defaultValuesProp,
|
|
252
|
-
});
|
|
253
|
-
|
|
254
|
-
const [localInputValue, setLocalInputValue] = useControlledState({
|
|
255
|
-
controlledValue: inputValue,
|
|
256
|
-
uncontrolledValue: undefined,
|
|
257
|
-
});
|
|
258
|
-
|
|
259
268
|
const onChange = useCallback<
|
|
260
269
|
NonNullable<
|
|
261
270
|
UseAutocompleteProps<
|
|
@@ -267,10 +276,9 @@ const Autocomplete = <
|
|
|
267
276
|
>
|
|
268
277
|
>(
|
|
269
278
|
(event, value, reason, details) => {
|
|
270
|
-
setLocalValue(value);
|
|
271
279
|
onChangeProp?.(event, value, reason, details);
|
|
272
280
|
},
|
|
273
|
-
[onChangeProp
|
|
281
|
+
[onChangeProp]
|
|
274
282
|
);
|
|
275
283
|
|
|
276
284
|
const onInputChange = useCallback<
|
|
@@ -284,18 +292,18 @@ const Autocomplete = <
|
|
|
284
292
|
>
|
|
285
293
|
>(
|
|
286
294
|
(event, value, reason) => {
|
|
287
|
-
setLocalInputValue(value);
|
|
288
295
|
onInputChangeProp?.(event, value, reason);
|
|
289
296
|
},
|
|
290
|
-
[onInputChangeProp
|
|
297
|
+
[onInputChangeProp]
|
|
291
298
|
);
|
|
292
299
|
|
|
293
300
|
return (
|
|
294
301
|
<MuiAutocomplete
|
|
302
|
+
{...valueProps}
|
|
303
|
+
{...inputValueProp}
|
|
295
304
|
// AutoComplete is wrapped in a div within MUI which does not get the disabled attr. So this aria-disabled gets set in the div
|
|
296
305
|
aria-disabled={isDisabled}
|
|
297
306
|
data-se={testId}
|
|
298
|
-
defaultValue={defaultValuesProp}
|
|
299
307
|
disableCloseOnSelect={hasMultipleChoices}
|
|
300
308
|
disabled={isDisabled}
|
|
301
309
|
freeSolo={isCustomValueAllowed}
|
|
@@ -310,8 +318,6 @@ const Autocomplete = <
|
|
|
310
318
|
options={options}
|
|
311
319
|
readOnly={isReadOnly}
|
|
312
320
|
renderInput={renderInput}
|
|
313
|
-
value={localValue}
|
|
314
|
-
inputValue={localInputValue}
|
|
315
321
|
isOptionEqualToValue={getIsOptionEqualToValue}
|
|
316
322
|
/>
|
|
317
323
|
);
|
package/src/Checkbox.tsx
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
13
|
import { useTranslation } from "react-i18next";
|
|
14
|
-
import { memo, useCallback, useMemo } from "react";
|
|
14
|
+
import { memo, useCallback, useMemo, useRef } from "react";
|
|
15
15
|
import {
|
|
16
16
|
Checkbox as MuiCheckbox,
|
|
17
17
|
CheckboxProps as MuiCheckboxProps,
|
|
@@ -22,7 +22,7 @@ import {
|
|
|
22
22
|
import { FieldComponentProps } from "./FieldComponentProps";
|
|
23
23
|
import { Typography } from "./Typography";
|
|
24
24
|
import type { SeleniumProps } from "./SeleniumProps";
|
|
25
|
-
import {
|
|
25
|
+
import { ComponentControlledState, getControlState } from "./inputUtils";
|
|
26
26
|
import { CheckedFieldProps } from "./FormCheckedProps";
|
|
27
27
|
|
|
28
28
|
export const checkboxValidityValues = ["valid", "invalid", "inherit"] as const;
|
|
@@ -90,10 +90,18 @@ const Checkbox = ({
|
|
|
90
90
|
value,
|
|
91
91
|
}: CheckboxProps) => {
|
|
92
92
|
const { t } = useTranslation();
|
|
93
|
-
const
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
93
|
+
const controlledStateRef = useRef(
|
|
94
|
+
getControlState({
|
|
95
|
+
controlledValue: isChecked,
|
|
96
|
+
uncontrolledValue: isDefaultChecked,
|
|
97
|
+
})
|
|
98
|
+
);
|
|
99
|
+
const inputValues = useMemo(() => {
|
|
100
|
+
if (controlledStateRef.current === ComponentControlledState.CONTROLLED) {
|
|
101
|
+
return { checked: isChecked };
|
|
102
|
+
}
|
|
103
|
+
return { defaultChecked: isDefaultChecked };
|
|
104
|
+
}, [isDefaultChecked, isChecked]);
|
|
97
105
|
|
|
98
106
|
const label = useMemo(() => {
|
|
99
107
|
return (
|
|
@@ -114,10 +122,9 @@ const Checkbox = ({
|
|
|
114
122
|
|
|
115
123
|
const onChange = useCallback<NonNullable<MuiCheckboxProps["onChange"]>>(
|
|
116
124
|
(event, checked) => {
|
|
117
|
-
setIsLocalChecked(checked);
|
|
118
125
|
onChangeProp?.(event, checked);
|
|
119
126
|
},
|
|
120
|
-
[onChangeProp
|
|
127
|
+
[onChangeProp]
|
|
121
128
|
);
|
|
122
129
|
|
|
123
130
|
return (
|
|
@@ -134,7 +141,7 @@ const Checkbox = ({
|
|
|
134
141
|
}
|
|
135
142
|
control={
|
|
136
143
|
<MuiCheckbox
|
|
137
|
-
|
|
144
|
+
{...inputValues}
|
|
138
145
|
indeterminate={isIndeterminate}
|
|
139
146
|
onChange={onChange}
|
|
140
147
|
required={isRequired}
|
package/src/NativeSelect.tsx
CHANGED
|
@@ -10,12 +10,23 @@
|
|
|
10
10
|
* See the License for the specific language governing permissions and limitations under the License.
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
-
import
|
|
14
|
-
|
|
15
|
-
|
|
13
|
+
import React, {
|
|
14
|
+
ReactElement,
|
|
15
|
+
forwardRef,
|
|
16
|
+
memo,
|
|
17
|
+
useCallback,
|
|
18
|
+
useMemo,
|
|
19
|
+
useRef,
|
|
20
|
+
} from "react";
|
|
21
|
+
import {
|
|
22
|
+
Select as MuiSelect,
|
|
23
|
+
SelectProps as MuiSelectProps,
|
|
24
|
+
} from "@mui/material";
|
|
16
25
|
import { Field } from "./Field";
|
|
17
26
|
import { FieldComponentProps } from "./FieldComponentProps";
|
|
18
27
|
import type { SeleniumProps } from "./SeleniumProps";
|
|
28
|
+
import { getControlState, useInputValues } from "./inputUtils";
|
|
29
|
+
import { ForwardRefWithType } from "./@types/react-augment";
|
|
19
30
|
|
|
20
31
|
export type NativeSelectOption = {
|
|
21
32
|
text: string;
|
|
@@ -23,19 +34,30 @@ export type NativeSelectOption = {
|
|
|
23
34
|
type?: "heading" | "option";
|
|
24
35
|
};
|
|
25
36
|
|
|
26
|
-
export type
|
|
37
|
+
export type NativeSelectValueType<HasMultipleChoices> =
|
|
38
|
+
HasMultipleChoices extends true ? string[] : string;
|
|
39
|
+
|
|
40
|
+
export type NativeSelectProps<
|
|
41
|
+
Value extends NativeSelectValueType<HasMultipleChoices>,
|
|
42
|
+
HasMultipleChoices extends boolean
|
|
43
|
+
> = {
|
|
27
44
|
/**
|
|
28
45
|
* The options or optgroup elements within the NativeSelect
|
|
29
46
|
*/
|
|
30
47
|
children?: ReactElement<"option"> | ReactElement<"optgroup">;
|
|
31
48
|
/**
|
|
32
|
-
* The default value of the NativeSelect.
|
|
49
|
+
* The default value of the NativeSelect. Use when component is uncontrolled
|
|
50
|
+
*/
|
|
51
|
+
defaultValue?: Value;
|
|
52
|
+
/**
|
|
53
|
+
* If `true`, the Select allows multiple selections
|
|
33
54
|
*/
|
|
34
|
-
|
|
55
|
+
hasMultipleChoices?: HasMultipleChoices;
|
|
35
56
|
/**
|
|
36
|
-
*
|
|
57
|
+
* @deprecated Use `hasMultipleChoices` instead
|
|
37
58
|
*/
|
|
38
|
-
|
|
59
|
+
/** **Deprecated:** use `hasMultipleChoices` */
|
|
60
|
+
isMultiSelect?: HasMultipleChoices;
|
|
39
61
|
/**
|
|
40
62
|
* The label text for the NativeSelect
|
|
41
63
|
*/
|
|
@@ -43,78 +65,109 @@ export type NativeSelectProps = {
|
|
|
43
65
|
/**
|
|
44
66
|
* Callback fired when the NativeSelect loses focus
|
|
45
67
|
*/
|
|
46
|
-
onBlur?: MuiSelectProps["onBlur"];
|
|
68
|
+
onBlur?: MuiSelectProps<Value>["onBlur"];
|
|
47
69
|
/**
|
|
48
70
|
* Callback fired when the value of the NativeSelect changes
|
|
49
71
|
*/
|
|
50
|
-
onChange?: MuiSelectProps["onChange"];
|
|
72
|
+
onChange?: MuiSelectProps<Value>["onChange"];
|
|
51
73
|
/**
|
|
52
74
|
* Callback fired when the NativeSelect gains focus
|
|
53
75
|
*/
|
|
54
|
-
onFocus?: MuiSelectProps["onFocus"];
|
|
76
|
+
onFocus?: MuiSelectProps<Value>["onFocus"];
|
|
77
|
+
options: Value;
|
|
55
78
|
/**
|
|
56
|
-
* The value or values selected in the NativeSelect
|
|
79
|
+
* The value or values selected in the NativeSelect. Use when component is controlled
|
|
57
80
|
*/
|
|
58
|
-
value?:
|
|
81
|
+
value?: Value;
|
|
59
82
|
} & Pick<
|
|
60
83
|
FieldComponentProps,
|
|
61
84
|
"errorMessage" | "hint" | "id" | "isDisabled" | "isOptional"
|
|
62
85
|
> &
|
|
63
86
|
SeleniumProps;
|
|
64
87
|
|
|
65
|
-
const NativeSelect = forwardRef
|
|
66
|
-
|
|
88
|
+
const NativeSelect: ForwardRefWithType = forwardRef(
|
|
89
|
+
<
|
|
90
|
+
Value extends NativeSelectValueType<HasMultipleChoices>,
|
|
91
|
+
HasMultipleChoices extends boolean
|
|
92
|
+
>(
|
|
67
93
|
{
|
|
68
94
|
defaultValue,
|
|
69
95
|
errorMessage,
|
|
96
|
+
hasMultipleChoices: hasMultipleChoicesProp,
|
|
70
97
|
hint,
|
|
71
98
|
id: idOverride,
|
|
72
99
|
isDisabled = false,
|
|
73
|
-
isMultiSelect
|
|
100
|
+
isMultiSelect,
|
|
74
101
|
isOptional = false,
|
|
75
102
|
label,
|
|
76
103
|
onBlur,
|
|
77
|
-
onChange,
|
|
104
|
+
onChange: onChangeProp,
|
|
78
105
|
onFocus,
|
|
79
106
|
testId,
|
|
80
107
|
value,
|
|
81
108
|
children,
|
|
82
|
-
},
|
|
83
|
-
ref
|
|
109
|
+
}: NativeSelectProps<Value, HasMultipleChoices>,
|
|
110
|
+
ref?: React.Ref<ReactElement>
|
|
84
111
|
) => {
|
|
112
|
+
const controlledStateRef = useRef(
|
|
113
|
+
getControlState({
|
|
114
|
+
controlledValue: value,
|
|
115
|
+
uncontrolledValue: defaultValue,
|
|
116
|
+
})
|
|
117
|
+
);
|
|
118
|
+
const inputValues = useInputValues({
|
|
119
|
+
defaultValue,
|
|
120
|
+
value,
|
|
121
|
+
controlState: controlledStateRef.current,
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
const onChange = useCallback<
|
|
125
|
+
NonNullable<MuiSelectProps<Value>["onChange"]>
|
|
126
|
+
>(
|
|
127
|
+
(event, child) => {
|
|
128
|
+
onChangeProp?.(event, child);
|
|
129
|
+
},
|
|
130
|
+
[onChangeProp]
|
|
131
|
+
);
|
|
132
|
+
|
|
133
|
+
const hasMultipleChoices = useMemo(
|
|
134
|
+
() =>
|
|
135
|
+
hasMultipleChoicesProp === undefined
|
|
136
|
+
? isMultiSelect
|
|
137
|
+
: hasMultipleChoicesProp,
|
|
138
|
+
[hasMultipleChoicesProp, isMultiSelect]
|
|
139
|
+
);
|
|
85
140
|
const renderFieldComponent = useCallback(
|
|
86
141
|
({ ariaDescribedBy, errorMessageElementId, labelElementId }) => (
|
|
87
142
|
<MuiSelect
|
|
143
|
+
{...inputValues}
|
|
88
144
|
aria-describedby={ariaDescribedBy}
|
|
89
145
|
children={children}
|
|
90
146
|
data-se={testId}
|
|
91
|
-
defaultValue={defaultValue}
|
|
92
147
|
id={idOverride}
|
|
93
148
|
inputProps={{
|
|
94
149
|
"aria-errormessage": errorMessageElementId,
|
|
95
150
|
"aria-labelledby": labelElementId,
|
|
96
151
|
}}
|
|
97
152
|
name={idOverride}
|
|
98
|
-
multiple={
|
|
153
|
+
multiple={hasMultipleChoices}
|
|
99
154
|
native={true}
|
|
100
155
|
onBlur={onBlur}
|
|
101
156
|
onChange={onChange}
|
|
102
157
|
onFocus={onFocus}
|
|
103
158
|
ref={ref}
|
|
104
|
-
value={value}
|
|
105
159
|
/>
|
|
106
160
|
),
|
|
107
161
|
[
|
|
108
162
|
children,
|
|
109
|
-
defaultValue,
|
|
110
163
|
idOverride,
|
|
111
|
-
|
|
164
|
+
inputValues,
|
|
165
|
+
hasMultipleChoices,
|
|
112
166
|
onBlur,
|
|
113
167
|
onChange,
|
|
114
168
|
onFocus,
|
|
115
169
|
ref,
|
|
116
170
|
testId,
|
|
117
|
-
value,
|
|
118
171
|
]
|
|
119
172
|
);
|
|
120
173
|
|
package/src/PasswordField.tsx
CHANGED
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
forwardRef,
|
|
18
18
|
memo,
|
|
19
19
|
useCallback,
|
|
20
|
+
useRef,
|
|
20
21
|
useState,
|
|
21
22
|
} from "react";
|
|
22
23
|
|
|
@@ -25,6 +26,7 @@ import { Field } from "./Field";
|
|
|
25
26
|
import { FieldComponentProps } from "./FieldComponentProps";
|
|
26
27
|
import type { SeleniumProps } from "./SeleniumProps";
|
|
27
28
|
import { useTranslation } from "react-i18next";
|
|
29
|
+
import { getControlState, useInputValues } from "./inputUtils";
|
|
28
30
|
|
|
29
31
|
export type PasswordFieldProps = {
|
|
30
32
|
/**
|
|
@@ -33,6 +35,10 @@ export type PasswordFieldProps = {
|
|
|
33
35
|
* You can learn more about it [following the specification](https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#autofill).
|
|
34
36
|
*/
|
|
35
37
|
autoCompleteType?: "current-password" | "new-password";
|
|
38
|
+
/**
|
|
39
|
+
* initial value for input. Use when component in uncontrolled.
|
|
40
|
+
*/
|
|
41
|
+
defaultValue?: string;
|
|
36
42
|
/**
|
|
37
43
|
* If `true`, the component will receive focus automatically.
|
|
38
44
|
*/
|
|
@@ -62,7 +68,7 @@ export type PasswordFieldProps = {
|
|
|
62
68
|
*/
|
|
63
69
|
placeholder?: string;
|
|
64
70
|
/**
|
|
65
|
-
* The value of the `input` element
|
|
71
|
+
* The value of the `input` element. Use when component is controlled.
|
|
66
72
|
*/
|
|
67
73
|
value?: string;
|
|
68
74
|
} & FieldComponentProps &
|
|
@@ -72,6 +78,7 @@ const PasswordField = forwardRef<HTMLInputElement, PasswordFieldProps>(
|
|
|
72
78
|
(
|
|
73
79
|
{
|
|
74
80
|
autoCompleteType,
|
|
81
|
+
defaultValue,
|
|
75
82
|
errorMessage,
|
|
76
83
|
hasInitialFocus,
|
|
77
84
|
hint,
|
|
@@ -82,7 +89,7 @@ const PasswordField = forwardRef<HTMLInputElement, PasswordFieldProps>(
|
|
|
82
89
|
isReadOnly,
|
|
83
90
|
label,
|
|
84
91
|
name: nameOverride,
|
|
85
|
-
onChange,
|
|
92
|
+
onChange: onChangeProp,
|
|
86
93
|
onFocus,
|
|
87
94
|
onBlur,
|
|
88
95
|
placeholder,
|
|
@@ -100,9 +107,31 @@ const PasswordField = forwardRef<HTMLInputElement, PasswordFieldProps>(
|
|
|
100
107
|
);
|
|
101
108
|
}, []);
|
|
102
109
|
|
|
110
|
+
const controlledStateRef = useRef(
|
|
111
|
+
getControlState({
|
|
112
|
+
controlledValue: value,
|
|
113
|
+
uncontrolledValue: defaultValue,
|
|
114
|
+
})
|
|
115
|
+
);
|
|
116
|
+
const inputValues = useInputValues({
|
|
117
|
+
defaultValue,
|
|
118
|
+
value,
|
|
119
|
+
controlState: controlledStateRef.current,
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
const onChange = useCallback<
|
|
123
|
+
ChangeEventHandler<HTMLTextAreaElement | HTMLInputElement>
|
|
124
|
+
>(
|
|
125
|
+
(event) => {
|
|
126
|
+
onChangeProp?.(event);
|
|
127
|
+
},
|
|
128
|
+
[onChangeProp]
|
|
129
|
+
);
|
|
130
|
+
|
|
103
131
|
const renderFieldComponent = useCallback(
|
|
104
132
|
({ ariaDescribedBy, errorMessageElementId, id, labelElementId }) => (
|
|
105
133
|
<InputBase
|
|
134
|
+
{...inputValues}
|
|
106
135
|
aria-describedby={ariaDescribedBy}
|
|
107
136
|
autoComplete={inputType === "password" ? autoCompleteType : "off"}
|
|
108
137
|
/* eslint-disable-next-line jsx-a11y/no-autofocus */
|
|
@@ -140,12 +169,12 @@ const PasswordField = forwardRef<HTMLInputElement, PasswordFieldProps>(
|
|
|
140
169
|
ref={ref}
|
|
141
170
|
required={!isOptional}
|
|
142
171
|
type={inputType}
|
|
143
|
-
value={value}
|
|
144
172
|
/>
|
|
145
173
|
),
|
|
146
174
|
[
|
|
147
175
|
autoCompleteType,
|
|
148
176
|
hasInitialFocus,
|
|
177
|
+
inputValues,
|
|
149
178
|
t,
|
|
150
179
|
togglePasswordVisibility,
|
|
151
180
|
inputType,
|
|
@@ -159,7 +188,6 @@ const PasswordField = forwardRef<HTMLInputElement, PasswordFieldProps>(
|
|
|
159
188
|
hasShowPassword,
|
|
160
189
|
ref,
|
|
161
190
|
testId,
|
|
162
|
-
value,
|
|
163
191
|
]
|
|
164
192
|
);
|
|
165
193
|
|
package/src/RadioGroup.tsx
CHANGED
|
@@ -14,13 +14,13 @@ import {
|
|
|
14
14
|
RadioGroup as MuiRadioGroup,
|
|
15
15
|
type RadioGroupProps as MuiRadioGroupProps,
|
|
16
16
|
} from "@mui/material";
|
|
17
|
-
import { memo, ReactElement, useCallback } from "react";
|
|
17
|
+
import { memo, ReactElement, useCallback, useRef } from "react";
|
|
18
18
|
|
|
19
19
|
import { Radio, RadioProps } from "./Radio";
|
|
20
20
|
import { Field } from "./Field";
|
|
21
21
|
import { FieldComponentProps } from "./FieldComponentProps";
|
|
22
22
|
import type { SeleniumProps } from "./SeleniumProps";
|
|
23
|
-
import {
|
|
23
|
+
import { getControlState, useInputValues } from "./inputUtils";
|
|
24
24
|
|
|
25
25
|
export type RadioGroupProps = {
|
|
26
26
|
/**
|
|
@@ -62,35 +62,37 @@ const RadioGroup = ({
|
|
|
62
62
|
testId,
|
|
63
63
|
value,
|
|
64
64
|
}: RadioGroupProps) => {
|
|
65
|
-
const
|
|
66
|
-
controlledValue: value,
|
|
67
|
-
|
|
65
|
+
const controlledStateRef = useRef(
|
|
66
|
+
getControlState({ controlledValue: value, uncontrolledValue: defaultValue })
|
|
67
|
+
);
|
|
68
|
+
const inputValues = useInputValues({
|
|
69
|
+
defaultValue,
|
|
70
|
+
value,
|
|
71
|
+
controlState: controlledStateRef.current,
|
|
68
72
|
});
|
|
69
73
|
|
|
70
74
|
const onChange = useCallback<NonNullable<MuiRadioGroupProps["onChange"]>>(
|
|
71
75
|
(event, value) => {
|
|
72
|
-
setLocalValue(value);
|
|
73
76
|
onChangeProp?.(event, value);
|
|
74
77
|
},
|
|
75
|
-
[onChangeProp
|
|
78
|
+
[onChangeProp]
|
|
76
79
|
);
|
|
77
80
|
const renderFieldComponent = useCallback(
|
|
78
81
|
({ ariaDescribedBy, errorMessageElementId, id, labelElementId }) => (
|
|
79
82
|
<MuiRadioGroup
|
|
83
|
+
{...inputValues}
|
|
80
84
|
aria-describedby={ariaDescribedBy}
|
|
81
85
|
aria-errormessage={errorMessageElementId}
|
|
82
86
|
aria-labelledby={labelElementId}
|
|
83
87
|
data-se={testId}
|
|
84
|
-
defaultValue={defaultValue}
|
|
85
88
|
id={id}
|
|
86
89
|
name={nameOverride ?? id}
|
|
87
90
|
onChange={onChange}
|
|
88
|
-
value={localValue}
|
|
89
91
|
>
|
|
90
92
|
{children}
|
|
91
93
|
</MuiRadioGroup>
|
|
92
94
|
),
|
|
93
|
-
[children,
|
|
95
|
+
[children, inputValues, nameOverride, onChange, testId]
|
|
94
96
|
);
|
|
95
97
|
|
|
96
98
|
return (
|