@okta/odyssey-react-mui 1.7.1 → 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.
Files changed (59) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/dist/@types/react-augment.d.js +2 -0
  3. package/dist/@types/react-augment.d.js.map +1 -0
  4. package/dist/Autocomplete.js +32 -23
  5. package/dist/Autocomplete.js.map +1 -1
  6. package/dist/Checkbox.js +33 -18
  7. package/dist/Checkbox.js.map +1 -1
  8. package/dist/NativeSelect.js +22 -8
  9. package/dist/NativeSelect.js.map +1 -1
  10. package/dist/PasswordField.js +19 -5
  11. package/dist/PasswordField.js.map +1 -1
  12. package/dist/RadioGroup.js +11 -8
  13. package/dist/RadioGroup.js.map +1 -1
  14. package/dist/SearchField.js +17 -16
  15. package/dist/SearchField.js.map +1 -1
  16. package/dist/Select.js +34 -18
  17. package/dist/Select.js.map +1 -1
  18. package/dist/TextField.js +20 -6
  19. package/dist/TextField.js.map +1 -1
  20. package/dist/inputUtils.js +46 -0
  21. package/dist/inputUtils.js.map +1 -0
  22. package/dist/labs/VirtualizedAutocomplete.js +29 -23
  23. package/dist/labs/VirtualizedAutocomplete.js.map +1 -1
  24. package/dist/src/Autocomplete.d.ts +0 -1
  25. package/dist/src/Autocomplete.d.ts.map +1 -1
  26. package/dist/src/Checkbox.d.ts +5 -1
  27. package/dist/src/Checkbox.d.ts.map +1 -1
  28. package/dist/src/NativeSelect.d.ts +19 -44
  29. package/dist/src/NativeSelect.d.ts.map +1 -1
  30. package/dist/src/PasswordField.d.ts +10 -2
  31. package/dist/src/PasswordField.d.ts.map +1 -1
  32. package/dist/src/RadioGroup.d.ts.map +1 -1
  33. package/dist/src/SearchField.d.ts +10 -2
  34. package/dist/src/SearchField.d.ts.map +1 -1
  35. package/dist/src/Select.d.ts +5 -1
  36. package/dist/src/Select.d.ts.map +1 -1
  37. package/dist/src/TextField.d.ts +8 -0
  38. package/dist/src/TextField.d.ts.map +1 -1
  39. package/dist/src/inputUtils.d.ts +47 -0
  40. package/dist/src/inputUtils.d.ts.map +1 -0
  41. package/dist/src/labs/VirtualizedAutocomplete.d.ts.map +1 -1
  42. package/dist/tsconfig.production.tsbuildinfo +1 -1
  43. package/package.json +3 -3
  44. package/src/@types/react-augment.d.ts +19 -0
  45. package/src/Autocomplete.tsx +49 -43
  46. package/src/Checkbox.tsx +41 -22
  47. package/src/NativeSelect.tsx +78 -25
  48. package/src/PasswordField.tsx +32 -4
  49. package/src/RadioGroup.tsx +12 -10
  50. package/src/SearchField.tsx +24 -18
  51. package/src/Select.tsx +43 -25
  52. package/src/TextField.tsx +33 -5
  53. package/src/inputUtils.ts +76 -0
  54. package/src/labs/VirtualizedAutocomplete.tsx +48 -42
  55. package/dist/src/useControlledState.d.ts +0 -28
  56. package/dist/src/useControlledState.d.ts.map +0 -1
  57. package/dist/useControlledState.js +0 -33
  58. package/dist/useControlledState.js.map +0 -1
  59. package/src/useControlledState.ts +0 -56
@@ -10,7 +10,6 @@
10
10
  * See the License for the specific language governing permissions and limitations under the License.
11
11
  */
12
12
 
13
- import { useState, useEffect } from "react";
14
13
  import { InputAdornment, InputBase, IconButton } from "@mui/material";
15
14
  import {
16
15
  ChangeEventHandler,
@@ -19,12 +18,14 @@ import {
19
18
  InputHTMLAttributes,
20
19
  memo,
21
20
  useCallback,
21
+ useRef,
22
22
  } from "react";
23
23
 
24
24
  import { CloseCircleFilledIcon, SearchIcon } from "./icons.generated";
25
25
  import { Field } from "./Field";
26
26
  import { FieldComponentProps } from "./FieldComponentProps";
27
27
  import type { SeleniumProps } from "./SeleniumProps";
28
+ import { getControlState, useInputValues } from "./inputUtils";
28
29
 
29
30
  export type SearchFieldProps = {
30
31
  /**
@@ -33,6 +34,10 @@ export type SearchFieldProps = {
33
34
  * You can learn more about it [following the specification](https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#autofill).
34
35
  */
35
36
  autoCompleteType?: InputHTMLAttributes<HTMLInputElement>["autoComplete"];
37
+ /**
38
+ * The value of the `input` element to use when uncontrolled.
39
+ */
40
+ defaultValue?: string;
36
41
  /**
37
42
  * If `true`, the component will receive focus automatically.
38
43
  */
@@ -70,7 +75,7 @@ export type SearchFieldProps = {
70
75
  */
71
76
  placeholder?: string;
72
77
  /**
73
- * The value of the `input` element, required for a controlled component.
78
+ * The value of the `input` element, to use when controlled.
74
79
  */
75
80
  value?: string;
76
81
  } & Pick<FieldComponentProps, "id" | "isDisabled" | "name"> &
@@ -80,6 +85,7 @@ const SearchField = forwardRef<HTMLInputElement, SearchFieldProps>(
80
85
  (
81
86
  {
82
87
  autoCompleteType,
88
+ defaultValue,
83
89
  hasInitialFocus,
84
90
  id: idOverride,
85
91
  isDisabled = false,
@@ -91,42 +97,45 @@ const SearchField = forwardRef<HTMLInputElement, SearchFieldProps>(
91
97
  onClear: onClearProp,
92
98
  placeholder,
93
99
  testId,
94
- value: controlledValue,
100
+ value,
95
101
  },
96
102
  ref
97
103
  ) => {
98
- const [uncontrolledValue, setUncontrolledValue] = useState("");
99
-
100
104
  const onChange: ChangeEventHandler<HTMLTextAreaElement | HTMLInputElement> =
101
105
  useCallback(
102
106
  (event) => {
103
- setUncontrolledValue(event.currentTarget.value);
104
107
  onChangeProp?.(event);
105
108
  },
106
109
  [onChangeProp]
107
110
  );
108
111
 
109
112
  const onClear = useCallback(() => {
110
- setUncontrolledValue("");
111
113
  onClearProp?.();
112
114
  }, [onClearProp]);
113
115
 
114
- useEffect(() => {
115
- if (controlledValue !== undefined) {
116
- setUncontrolledValue(controlledValue);
117
- }
118
- }, [controlledValue]);
116
+ const controlledStateRef = useRef(
117
+ getControlState({
118
+ controlledValue: value,
119
+ uncontrolledValue: defaultValue,
120
+ })
121
+ );
122
+ const inputValues = useInputValues({
123
+ defaultValue,
124
+ value,
125
+ controlState: controlledStateRef.current,
126
+ });
119
127
 
120
128
  const renderFieldComponent = useCallback(
121
129
  ({ ariaDescribedBy, id }) => (
122
130
  <InputBase
131
+ {...inputValues}
123
132
  aria-describedby={ariaDescribedBy}
124
133
  autoComplete={autoCompleteType}
125
134
  /* eslint-disable-next-line jsx-a11y/no-autofocus */
126
135
  autoFocus={hasInitialFocus}
127
136
  data-se={testId}
128
137
  endAdornment={
129
- uncontrolledValue && (
138
+ defaultValue && (
130
139
  <InputAdornment position="end">
131
140
  <IconButton
132
141
  aria-label="Clear"
@@ -152,15 +161,13 @@ const SearchField = forwardRef<HTMLInputElement, SearchFieldProps>(
152
161
  </InputAdornment>
153
162
  }
154
163
  type="search"
155
- value={
156
- controlledValue === undefined ? uncontrolledValue : controlledValue
157
- }
158
164
  />
159
165
  ),
160
166
  [
161
167
  autoCompleteType,
162
- controlledValue,
168
+ defaultValue,
163
169
  hasInitialFocus,
170
+ inputValues,
164
171
  isDisabled,
165
172
  nameOverride,
166
173
  onBlur,
@@ -170,7 +177,6 @@ const SearchField = forwardRef<HTMLInputElement, SearchFieldProps>(
170
177
  placeholder,
171
178
  ref,
172
179
  testId,
173
- uncontrolledValue,
174
180
  ]
175
181
  );
176
182
 
package/src/Select.tsx CHANGED
@@ -10,7 +10,7 @@
10
10
  * See the License for the specific language governing permissions and limitations under the License.
11
11
  */
12
12
 
13
- import { memo, useCallback, useMemo, useState } from "react";
13
+ import { memo, useCallback, useEffect, useMemo, useRef, useState } from "react";
14
14
  import {
15
15
  Box,
16
16
  Checkbox as MuiCheckbox,
@@ -26,6 +26,11 @@ import { Field } from "./Field";
26
26
  import { FieldComponentProps } from "./FieldComponentProps";
27
27
  import { CheckIcon } from "./icons.generated";
28
28
  import type { SeleniumProps } from "./SeleniumProps";
29
+ import {
30
+ ComponentControlledState,
31
+ useInputValues,
32
+ getControlState,
33
+ } from "./inputUtils";
29
34
 
30
35
  export type SelectOption = {
31
36
  text: string;
@@ -40,6 +45,10 @@ export type SelectProps<
40
45
  Value extends SelectValueType<HasMultipleChoices>,
41
46
  HasMultipleChoices extends boolean
42
47
  > = {
48
+ /**
49
+ * The default value. Use when the component is not controlled.
50
+ */
51
+ defaultValue?: MuiSelectProps<Value>["defaultValue"];
43
52
  /**
44
53
  * If `true`, the Select allows multiple selections
45
54
  */
@@ -94,10 +103,12 @@ export type SelectProps<
94
103
  * - { text: string, type: "heading" } — Used to display a group heading with the text
95
104
  */
96
105
 
106
+ const { CONTROLLED } = ComponentControlledState;
97
107
  const Select = <
98
108
  Value extends SelectValueType<HasMultipleChoices>,
99
109
  HasMultipleChoices extends boolean
100
110
  >({
111
+ defaultValue,
101
112
  errorMessage,
102
113
  hasMultipleChoices: hasMultipleChoicesProp,
103
114
  hint,
@@ -121,32 +132,38 @@ const Select = <
121
132
  : hasMultipleChoicesProp,
122
133
  [hasMultipleChoicesProp, isMultiSelect]
123
134
  );
135
+ const controlledStateRef = useRef(
136
+ getControlState({ controlledValue: value, uncontrolledValue: defaultValue })
137
+ );
138
+ const [internalSelectedValues, setInternalSelectedValues] = useState(
139
+ controlledStateRef.current === CONTROLLED ? value : defaultValue
140
+ );
124
141
 
125
- const formattedValueForMultiSelect = isMultiSelect
126
- ? ([] as string[] as Value)
127
- : ("" as string as Value);
142
+ useEffect(() => {
143
+ if (controlledStateRef.current === CONTROLLED) {
144
+ setInternalSelectedValues(value);
145
+ }
146
+ }, [value]);
128
147
 
129
- const [selectedValue, setSelectedValue] = useState(
130
- value === undefined ? formattedValueForMultiSelect : value
131
- );
148
+ const inputValues = useInputValues({
149
+ defaultValue,
150
+ value,
151
+ controlState: controlledStateRef.current,
152
+ });
132
153
 
133
154
  const onChange = useCallback<NonNullable<MuiSelectProps<Value>["onChange"]>>(
134
155
  (event, child) => {
135
- const valueFromEvent = event.target.value;
136
-
137
- if (typeof valueFromEvent === "string") {
138
- if (hasMultipleChoices) {
139
- setSelectedValue(valueFromEvent.split(",") as Value);
140
- } else {
141
- setSelectedValue(valueFromEvent as Value);
142
- }
143
- } else {
144
- setSelectedValue(valueFromEvent);
156
+ const {
157
+ target: { value },
158
+ } = event;
159
+ if (controlledStateRef.current !== CONTROLLED) {
160
+ setInternalSelectedValues(
161
+ (typeof value === "string" ? value.split(",") : value) as Value
162
+ );
145
163
  }
146
-
147
164
  onChangeProp?.(event, child);
148
165
  },
149
- [hasMultipleChoices, onChangeProp, setSelectedValue]
166
+ [onChangeProp]
150
167
  );
151
168
 
152
169
  // Normalize the options array to accommodate the various
@@ -207,14 +224,15 @@ const Select = <
207
224
  if (option.type === "heading") {
208
225
  return <ListSubheader key={option.text}>{option.text}</ListSubheader>;
209
226
  }
210
-
211
227
  return (
212
228
  <MenuItem key={option.value} value={option.value}>
213
229
  {hasMultipleChoices && (
214
- <MuiCheckbox checked={selectedValue.includes(option.value)} />
230
+ <MuiCheckbox
231
+ checked={internalSelectedValues?.includes(option.value)}
232
+ />
215
233
  )}
216
234
  {option.text}
217
- {selectedValue == option.value && (
235
+ {internalSelectedValues === option.value && (
218
236
  <ListItemSecondaryAction>
219
237
  <CheckIcon />
220
238
  </ListItemSecondaryAction>
@@ -222,12 +240,13 @@ const Select = <
222
240
  </MenuItem>
223
241
  );
224
242
  }),
225
- [hasMultipleChoices, normalizedOptions, selectedValue]
243
+ [hasMultipleChoices, normalizedOptions, internalSelectedValues]
226
244
  );
227
245
 
228
246
  const renderFieldComponent = useCallback(
229
247
  ({ ariaDescribedBy, errorMessageElementId, id, labelElementId }) => (
230
248
  <MuiSelect
249
+ {...inputValues}
231
250
  aria-describedby={ariaDescribedBy}
232
251
  aria-errormessage={errorMessageElementId}
233
252
  children={children}
@@ -240,18 +259,17 @@ const Select = <
240
259
  onChange={onChange}
241
260
  onFocus={onFocus}
242
261
  renderValue={hasMultipleChoices ? renderValue : undefined}
243
- value={selectedValue}
244
262
  />
245
263
  ),
246
264
  [
247
265
  children,
266
+ inputValues,
248
267
  hasMultipleChoices,
249
268
  nameOverride,
250
269
  onBlur,
251
270
  onChange,
252
271
  onFocus,
253
272
  renderValue,
254
- selectedValue,
255
273
  testId,
256
274
  ]
257
275
  );
package/src/TextField.tsx CHANGED
@@ -18,12 +18,14 @@ import {
18
18
  memo,
19
19
  ReactElement,
20
20
  useCallback,
21
+ useRef,
21
22
  } from "react";
22
23
  import { InputAdornment, InputBase } from "@mui/material";
23
24
 
24
25
  import { FieldComponentProps } from "./FieldComponentProps";
25
26
  import { Field } from "./Field";
26
27
  import { SeleniumProps } from "./SeleniumProps";
28
+ import { useInputValues, getControlState } from "./inputUtils";
27
29
 
28
30
  export const textFieldTypeValues = [
29
31
  "email",
@@ -40,6 +42,10 @@ export type TextFieldProps = {
40
42
  * You can learn more about it [following the specification](https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#autofill).
41
43
  */
42
44
  autoCompleteType?: InputHTMLAttributes<HTMLInputElement>["autoComplete"];
45
+ /**
46
+ * The default value. Use when the component is not controlled.
47
+ */
48
+ defaultValue?: string;
43
49
  /**
44
50
  * End `InputAdornment` for this component.
45
51
  */
@@ -91,6 +97,7 @@ const TextField = forwardRef<HTMLInputElement, TextFieldProps>(
91
97
  (
92
98
  {
93
99
  autoCompleteType,
100
+ defaultValue,
94
101
  hasInitialFocus,
95
102
  endAdornment,
96
103
  errorMessage,
@@ -103,19 +110,41 @@ const TextField = forwardRef<HTMLInputElement, TextFieldProps>(
103
110
  label,
104
111
  name: nameOverride,
105
112
  onBlur,
106
- onChange,
113
+ onChange: onChangeProp,
107
114
  onFocus,
108
115
  placeholder,
109
116
  startAdornment,
110
117
  testId,
111
118
  type = "text",
112
- value,
119
+ value: value,
113
120
  },
114
121
  ref
115
122
  ) => {
123
+ const controlledStateRef = useRef(
124
+ getControlState({
125
+ controlledValue: value,
126
+ uncontrolledValue: defaultValue,
127
+ })
128
+ );
129
+ const inputValues = useInputValues({
130
+ defaultValue,
131
+ value,
132
+ controlState: controlledStateRef.current,
133
+ });
134
+
135
+ const onChange = useCallback<
136
+ NonNullable<ChangeEventHandler<HTMLTextAreaElement | HTMLInputElement>>
137
+ >(
138
+ (event) => {
139
+ onChangeProp?.(event);
140
+ },
141
+ [onChangeProp]
142
+ );
143
+
116
144
  const renderFieldComponent = useCallback(
117
145
  ({ ariaDescribedBy, errorMessageElementId, id, labelElementId }) => (
118
146
  <InputBase
147
+ {...inputValues}
119
148
  inputProps={{
120
149
  "aria-errormessage": errorMessageElementId,
121
150
  "aria-labelledby": labelElementId,
@@ -146,18 +175,18 @@ const TextField = forwardRef<HTMLInputElement, TextFieldProps>(
146
175
  )
147
176
  }
148
177
  type={type}
149
- value={value}
150
178
  />
151
179
  ),
152
180
  [
153
181
  autoCompleteType,
182
+ inputValues,
154
183
  hasInitialFocus,
155
184
  endAdornment,
156
185
  isMultiline,
157
186
  nameOverride,
187
+ onBlur,
158
188
  onChange,
159
189
  onFocus,
160
- onBlur,
161
190
  placeholder,
162
191
  isOptional,
163
192
  isReadOnly,
@@ -165,7 +194,6 @@ const TextField = forwardRef<HTMLInputElement, TextFieldProps>(
165
194
  startAdornment,
166
195
  testId,
167
196
  type,
168
- value,
169
197
  ]
170
198
  );
171
199
 
@@ -0,0 +1,76 @@
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 { useMemo } from "react";
14
+
15
+ type UseControlledStateProps<Value> = {
16
+ controlledValue?: Value;
17
+ uncontrolledValue?: Value;
18
+ };
19
+
20
+ export const ComponentControlledState = {
21
+ CONTROLLED: "CONTROLLED",
22
+ UNCONTROLLED: "UNCONTROLLED",
23
+ };
24
+
25
+ export type ModeType = keyof typeof ComponentControlledState;
26
+ export type ModeTypeValue = (typeof ComponentControlledState)[ModeType];
27
+
28
+ export const getControlState = <Value>({
29
+ controlledValue,
30
+ uncontrolledValue,
31
+ }: UseControlledStateProps<Value>): ModeTypeValue => {
32
+ if (uncontrolledValue !== undefined || controlledValue === undefined) {
33
+ return ComponentControlledState.UNCONTROLLED;
34
+ }
35
+ return ComponentControlledState.CONTROLLED;
36
+ };
37
+
38
+ type InputValueProps<Value> = {
39
+ defaultValue?: Value;
40
+ value?: Value;
41
+ controlState: ModeTypeValue;
42
+ };
43
+
44
+ type InputValue<Value> =
45
+ | {
46
+ defaultValue: Value | undefined;
47
+ value?: undefined;
48
+ }
49
+ | {
50
+ value: Value | undefined;
51
+ defaultValue?: undefined;
52
+ };
53
+
54
+ /**
55
+ * In components that support being used in a controlled or uncontrolled way, the defaultValue and value props need
56
+ * to be suppled values in a mutually exclusive way.
57
+ * If a `value` is being provided to the component, then it is being used in a controlled manner and `defaultValue` needs to be undefined.
58
+ * If `value` is undefined, then that means the component is being used in an uncontrolled way and `defaultValue` is either Value or undefined.
59
+ * This helper helps ensure this mutual exclusivity between the 2 props so the component can operate as expected.
60
+ *
61
+ * @param {InputValueProps<Value>}: { defaultValue: Value | undefined, value: Value | undefined }
62
+ * @returns {InputValue<Value>}: { defaultValue: Value | undefined, value?: undefined } | { defaultValue?: undefined, value: Value }
63
+ */
64
+ export const useInputValues = <Value>({
65
+ defaultValue,
66
+ value,
67
+ controlState,
68
+ }: InputValueProps<Value>): InputValue<Value> => {
69
+ const inputValues = useMemo(() => {
70
+ if (controlState === ComponentControlledState.CONTROLLED) {
71
+ return { value };
72
+ }
73
+ return { defaultValue };
74
+ }, [defaultValue, value]);
75
+ return inputValues;
76
+ };
@@ -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 { useControlledState } from "../useControlledState";
25
+ import {
26
+ ComponentControlledState,
27
+ getControlState,
28
+ useInputValues,
29
+ } from "../inputUtils";
26
30
 
27
31
  export type AutocompleteProps<
28
32
  OptionType,
@@ -196,6 +200,44 @@ const VirtualizedAutocomplete = <
196
200
  getIsOptionEqualToValue,
197
201
  testId,
198
202
  }: AutocompleteProps<OptionType, HasMultipleChoices, IsCustomValueAllowed>) => {
203
+ const controlledStateRef = useRef(
204
+ getControlState({ controlledValue: value, uncontrolledValue: defaultValue })
205
+ );
206
+ const defaultValueProp = useMemo<
207
+ | AutocompleteValue<
208
+ OptionType,
209
+ HasMultipleChoices,
210
+ undefined,
211
+ IsCustomValueAllowed
212
+ >
213
+ | undefined
214
+ >(() => {
215
+ if (hasMultipleChoices) {
216
+ return defaultValue === undefined
217
+ ? ([] as AutocompleteValue<
218
+ OptionType,
219
+ HasMultipleChoices,
220
+ undefined,
221
+ IsCustomValueAllowed
222
+ >)
223
+ : defaultValue;
224
+ }
225
+ return defaultValue ?? undefined;
226
+ }, [defaultValue, hasMultipleChoices]);
227
+
228
+ const valueProps = useInputValues({
229
+ defaultValue: defaultValueProp,
230
+ value: value,
231
+ controlState: controlledStateRef.current,
232
+ });
233
+
234
+ const inputValueProp = useMemo(() => {
235
+ if (controlledStateRef.current === ComponentControlledState.CONTROLLED) {
236
+ return { inputValue };
237
+ }
238
+ return undefined;
239
+ }, [inputValue]);
240
+
199
241
  const renderInput = useCallback(
200
242
  ({ InputLabelProps, InputProps, ...params }) => (
201
243
  <Field
@@ -230,39 +272,6 @@ const VirtualizedAutocomplete = <
230
272
  ),
231
273
  [errorMessage, hint, isOptional, label, nameOverride]
232
274
  );
233
-
234
- const defaultValuesProp = useMemo<
235
- | AutocompleteValue<
236
- OptionType,
237
- HasMultipleChoices,
238
- undefined,
239
- IsCustomValueAllowed
240
- >
241
- | undefined
242
- >(() => {
243
- if (hasMultipleChoices) {
244
- return defaultValue === undefined
245
- ? ([] as AutocompleteValue<
246
- OptionType,
247
- HasMultipleChoices,
248
- undefined,
249
- IsCustomValueAllowed
250
- >)
251
- : defaultValue;
252
- }
253
- return defaultValue ?? undefined;
254
- }, [defaultValue, hasMultipleChoices]);
255
-
256
- const [localValue, setLocalValue] = useControlledState({
257
- controlledValue: value,
258
- uncontrolledValue: defaultValuesProp,
259
- });
260
-
261
- const [localInputValue, setLocalInputValue] = useControlledState({
262
- controlledValue: inputValue,
263
- uncontrolledValue: undefined,
264
- });
265
-
266
275
  const onChange = useCallback<
267
276
  NonNullable<
268
277
  UseAutocompleteProps<
@@ -274,10 +283,9 @@ const VirtualizedAutocomplete = <
274
283
  >
275
284
  >(
276
285
  (event, value, reason, details) => {
277
- setLocalValue(value);
278
286
  onChangeProp?.(event, value, reason, details);
279
287
  },
280
- [onChangeProp, setLocalValue]
288
+ [onChangeProp]
281
289
  );
282
290
 
283
291
  const onInputChange = useCallback<
@@ -291,18 +299,18 @@ const VirtualizedAutocomplete = <
291
299
  >
292
300
  >(
293
301
  (event, value, reason) => {
294
- setLocalInputValue(value);
295
302
  onInputChangeProp?.(event, value, reason);
296
303
  },
297
- [onInputChangeProp, setLocalInputValue]
304
+ [onInputChangeProp]
298
305
  );
299
306
 
300
307
  return (
301
308
  <MuiAutocomplete
309
+ {...valueProps}
310
+ {...inputValueProp}
302
311
  // AutoComplete is wrapped in a div within MUI which does not get the disabled attr. So this aria-disabled gets set in the div
303
312
  aria-disabled={isDisabled}
304
313
  data-se={testId}
305
- defaultValue={defaultValuesProp}
306
314
  disableCloseOnSelect={hasMultipleChoices}
307
315
  disabled={isDisabled}
308
316
  freeSolo={isCustomValueAllowed}
@@ -318,8 +326,6 @@ const VirtualizedAutocomplete = <
318
326
  options={options}
319
327
  readOnly={isReadOnly}
320
328
  renderInput={renderInput}
321
- value={localValue}
322
- inputValue={localInputValue}
323
329
  isOptionEqualToValue={getIsOptionEqualToValue}
324
330
  />
325
331
  );
@@ -1,28 +0,0 @@
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
- /// <reference types="react" />
13
- type UseControlledStateProps<Value> = {
14
- controlledValue?: Value;
15
- uncontrolledValue?: Value;
16
- };
17
- /**
18
- * Use the same way as `useState`. Returns a stateful value, and a function to update it.
19
- * When `initialState` is passed, the returned function to update it does nothing. This is
20
- * useful to handle values in components that may be controlled externally when that value is
21
- * passed in props and thus wish to prevent internal updates of the same value.
22
- *
23
- * @param initialState
24
- * @see https://react.dev/reference/react/useState
25
- */
26
- export declare const useControlledState: <Value>({ controlledValue, uncontrolledValue, }: UseControlledStateProps<Value>) => readonly [Value | undefined, import("react").Dispatch<import("react").SetStateAction<Value | undefined>>];
27
- export {};
28
- //# sourceMappingURL=useControlledState.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"useControlledState.d.ts","sourceRoot":"","sources":["../../src/useControlledState.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;;AAIH,KAAK,uBAAuB,CAAC,KAAK,IAAI;IACpC,eAAe,CAAC,EAAE,KAAK,CAAC;IACxB,iBAAiB,CAAC,EAAE,KAAK,CAAC;CAC3B,CAAC;AAEF;;;;;;;;GAQG;AACH,eAAO,MAAM,kBAAkB,+LA2B9B,CAAC"}
@@ -1,33 +0,0 @@
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 { useEffect, useRef, useState } from "react";
14
- export const useControlledState = _ref => {
15
- let {
16
- controlledValue,
17
- uncontrolledValue
18
- } = _ref;
19
- const isControlledMode = useRef(controlledValue !== undefined);
20
- const [stateValue, setStateValue] = useState(isControlledMode.current ? controlledValue : uncontrolledValue);
21
- useEffect(() => {
22
- if (isControlledMode.current) {
23
- setStateValue(controlledValue);
24
- }
25
- }, [controlledValue]);
26
- const setState = value => {
27
- if (!isControlledMode.current) {
28
- setStateValue(value);
29
- }
30
- };
31
- return [stateValue, setState];
32
- };
33
- //# sourceMappingURL=useControlledState.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"useControlledState.js","names":["useEffect","useRef","useState","useControlledState","_ref","controlledValue","uncontrolledValue","isControlledMode","undefined","stateValue","setStateValue","current","setState","value"],"sources":["../src/useControlledState.ts"],"sourcesContent":["/*!\n * Copyright (c) 2023-present, Okta, Inc. and/or its affiliates. All rights reserved.\n * The Okta software accompanied by this notice is provided pursuant to the Apache License, Version 2.0 (the \"License.\")\n *\n * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0.\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *\n * See the License for the specific language governing permissions and limitations under the License.\n */\n\nimport { useEffect, useRef, useState } from \"react\";\n\ntype UseControlledStateProps<Value> = {\n controlledValue?: Value; // isChecked\n uncontrolledValue?: Value; // isDefaultChecked\n};\n\n/**\n * Use the same way as `useState`. Returns a stateful value, and a function to update it.\n * When `initialState` is passed, the returned function to update it does nothing. This is\n * useful to handle values in components that may be controlled externally when that value is\n * passed in props and thus wish to prevent internal updates of the same value.\n *\n * @param initialState\n * @see https://react.dev/reference/react/useState\n */\nexport const useControlledState = <Value>({\n controlledValue,\n uncontrolledValue,\n}: UseControlledStateProps<Value>) => {\n const isControlledMode = useRef(controlledValue !== undefined);\n const [stateValue, setStateValue] = useState(\n isControlledMode.current ? controlledValue : uncontrolledValue\n );\n\n useEffect(() => {\n if (isControlledMode.current) {\n setStateValue(controlledValue);\n }\n }, [controlledValue]);\n\n const setState: typeof setStateValue = (value) => {\n if (!isControlledMode.current) {\n setStateValue(value);\n }\n };\n\n return [\n stateValue,\n // If `value` is controlled externally, ignore calls to the setter.\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n setState,\n ] as const;\n};\n"],"mappings":"AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA,SAASA,SAAS,EAAEC,MAAM,EAAEC,QAAQ,QAAQ,OAAO;AAgBnD,OAAO,MAAMC,kBAAkB,GAAGC,IAAA,IAGI;EAAA,IAHI;IACxCC,eAAe;IACfC;EAC8B,CAAC,GAAAF,IAAA;EAC/B,MAAMG,gBAAgB,GAAGN,MAAM,CAACI,eAAe,KAAKG,SAAS,CAAC;EAC9D,MAAM,CAACC,UAAU,EAAEC,aAAa,CAAC,GAAGR,QAAQ,CAC1CK,gBAAgB,CAACI,OAAO,GAAGN,eAAe,GAAGC,iBAC/C,CAAC;EAEDN,SAAS,CAAC,MAAM;IACd,IAAIO,gBAAgB,CAACI,OAAO,EAAE;MAC5BD,aAAa,CAACL,eAAe,CAAC;IAChC;EACF,CAAC,EAAE,CAACA,eAAe,CAAC,CAAC;EAErB,MAAMO,QAA8B,GAAIC,KAAK,IAAK;IAChD,IAAI,CAACN,gBAAgB,CAACI,OAAO,EAAE;MAC7BD,aAAa,CAACG,KAAK,CAAC;IACtB;EACF,CAAC;EAED,OAAO,CACLJ,UAAU,EAGVG,QAAQ,CACT;AACH,CAAC"}