playbook_ui 16.1.0.pre.alpha.play264213817 → 16.1.0.pre.alpha.play276813969

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 (79) hide show
  1. checksums.yaml +4 -4
  2. data/app/pb_kits/playbook/pb_card/docs/_card_light.html.erb +3 -35
  3. data/app/pb_kits/playbook/pb_dialog/_dialog.scss +8 -6
  4. data/app/pb_kits/playbook/pb_dropdown/_dropdown.scss +6 -0
  5. data/app/pb_kits/playbook/pb_dropdown/_dropdown.tsx +83 -13
  6. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_blank_selection_rails.md +3 -0
  7. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_blank_selection_react.md +3 -0
  8. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_with_clearable.html.erb +52 -0
  9. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_with_clearable.jsx +72 -0
  10. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_with_clearable.md +5 -0
  11. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_with_constrain_height.jsx +33 -0
  12. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_with_constrain_height_rails.html.erb +20 -0
  13. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_with_constrain_height_rails.md +8 -0
  14. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_with_constrain_height_react.md +8 -0
  15. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_with_label.html.erb +6 -3
  16. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_with_label.jsx +1 -0
  17. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_with_label.md +3 -1
  18. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_with_placeholder.html.erb +9 -0
  19. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_with_placeholder.jsx +33 -0
  20. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_with_placeholder.md +3 -0
  21. data/app/pb_kits/playbook/pb_dropdown/docs/example.yml +6 -0
  22. data/app/pb_kits/playbook/pb_dropdown/docs/index.js +4 -1
  23. data/app/pb_kits/playbook/pb_dropdown/dropdown.html.erb +11 -5
  24. data/app/pb_kits/playbook/pb_dropdown/dropdown.rb +15 -0
  25. data/app/pb_kits/playbook/pb_dropdown/dropdown.test.jsx +94 -0
  26. data/app/pb_kits/playbook/pb_dropdown/dropdown_container.rb +5 -1
  27. data/app/pb_kits/playbook/pb_dropdown/dropdown_trigger.html.erb +7 -2
  28. data/app/pb_kits/playbook/pb_dropdown/dropdown_trigger.rb +4 -0
  29. data/app/pb_kits/playbook/pb_dropdown/index.js +184 -77
  30. data/app/pb_kits/playbook/pb_dropdown/subcomponents/DropdownContainer.tsx +3 -0
  31. data/app/pb_kits/playbook/pb_dropdown/subcomponents/DropdownTrigger.tsx +18 -1
  32. data/app/pb_kits/playbook/pb_dropdown/utilities/clickOutsideHelper.tsx +6 -0
  33. data/app/pb_kits/playbook/pb_filter/Filter/SortMenu.tsx +1 -1
  34. data/app/pb_kits/playbook/pb_filter/docs/_filter_default.html.erb +2 -2
  35. data/app/pb_kits/playbook/pb_filter/docs/_filter_default.jsx +16 -9
  36. data/app/pb_kits/playbook/pb_filter/filter.rb +2 -2
  37. data/app/pb_kits/playbook/pb_form/docs/_form_with_required_indicator.html.erb +1 -0
  38. data/app/pb_kits/playbook/pb_form_pill/docs/_form_pill_truncated_text.html.erb +5 -5
  39. data/app/pb_kits/playbook/pb_form_pill/docs/_form_pill_truncated_text.jsx +4 -4
  40. data/app/pb_kits/playbook/pb_form_pill/form_pill.rb +4 -0
  41. data/app/pb_kits/playbook/pb_multi_level_select/_multi_level_select.scss +7 -0
  42. data/app/pb_kits/playbook/pb_multi_level_select/_multi_level_select.tsx +638 -549
  43. data/app/pb_kits/playbook/pb_multi_level_select/docs/_multi_level_select_label.html.erb +3 -3
  44. data/app/pb_kits/playbook/pb_multi_level_select/docs/_multi_level_select_label.jsx +4 -7
  45. data/app/pb_kits/playbook/pb_multi_level_select/docs/_multi_level_select_label.md +3 -0
  46. data/app/pb_kits/playbook/pb_multi_level_select/multi_level_select.test.jsx +4 -4
  47. data/app/pb_kits/playbook/pb_passphrase/_passphrase.tsx +20 -5
  48. data/app/pb_kits/playbook/pb_passphrase/docs/_passphrase_meter_settings.jsx +1 -0
  49. data/app/pb_kits/playbook/pb_passphrase/docs/_passphrase_required_indicator.html.erb +7 -0
  50. data/app/pb_kits/playbook/pb_passphrase/docs/_passphrase_required_indicator.jsx +24 -0
  51. data/app/pb_kits/playbook/pb_passphrase/docs/_passphrase_required_indicator.md +3 -0
  52. data/app/pb_kits/playbook/pb_passphrase/docs/example.yml +2 -0
  53. data/app/pb_kits/playbook/pb_passphrase/docs/index.js +1 -0
  54. data/app/pb_kits/playbook/pb_passphrase/passphrase.rb +2 -0
  55. data/app/pb_kits/playbook/pb_passphrase/passphrase.test.jsx +30 -1
  56. data/app/pb_kits/playbook/pb_phone_number_input/_phone_number_input.tsx +3 -0
  57. data/app/pb_kits/playbook/pb_phone_number_input/docs/_phone_number_input_required_indicator.html.erb +5 -0
  58. data/app/pb_kits/playbook/pb_phone_number_input/docs/_phone_number_input_required_indicator.jsx +14 -0
  59. data/app/pb_kits/playbook/pb_phone_number_input/docs/_phone_number_input_required_indicator.md +3 -0
  60. data/app/pb_kits/playbook/pb_phone_number_input/docs/example.yml +2 -0
  61. data/app/pb_kits/playbook/pb_phone_number_input/docs/index.js +1 -0
  62. data/app/pb_kits/playbook/pb_phone_number_input/phone_number_input.rb +3 -0
  63. data/app/pb_kits/playbook/pb_phone_number_input/phone_number_input.test.js +34 -3
  64. data/app/pb_kits/playbook/pb_typeahead/_typeahead.test.jsx +24 -1
  65. data/app/pb_kits/playbook/pb_typeahead/_typeahead.tsx +2 -1
  66. data/app/pb_kits/playbook/pb_typeahead/components/MultiValue.tsx +4 -1
  67. data/app/pb_kits/playbook/pb_typeahead/docs/_typeahead_truncated_text.html.erb +1 -1
  68. data/app/pb_kits/playbook/pb_typeahead/docs/_typeahead_truncated_text.jsx +1 -1
  69. data/app/pb_kits/playbook/pb_typeahead/typeahead.rb +4 -0
  70. data/dist/chunks/_typeahead-C4YsbA48.js +1 -0
  71. data/dist/chunks/vendor.js +2 -2
  72. data/dist/playbook-rails-react-bindings.js +1 -1
  73. data/dist/playbook-rails.js +1 -1
  74. data/dist/playbook.css +1 -1
  75. data/lib/playbook/forms/builder/phone_number_field.rb +9 -0
  76. data/lib/playbook/truncate.rb +1 -1
  77. data/lib/playbook/version.rb +1 -1
  78. metadata +22 -3
  79. data/dist/chunks/_typeahead-B9a6ZsEP.js +0 -1
@@ -26,599 +26,688 @@ import {
26
26
  getExpandedItems,
27
27
  } from "./_helper_functions";
28
28
 
29
- interface MultiLevelSelectComponent
30
- extends React.ForwardRefExoticComponent<
31
- MultiLevelSelectProps & React.RefAttributes<HTMLInputElement>
32
- > {
29
+ interface MultiLevelSelectComponent extends React.ForwardRefExoticComponent<
30
+ MultiLevelSelectProps & React.RefAttributes<HTMLInputElement>
31
+ > {
33
32
  Options: typeof MultiLevelSelectOptions;
34
33
  }
35
34
 
36
35
  type MultiLevelSelectProps = {
37
- aria?: { [key: string]: string }
38
- className?: string
39
- data?: { [key: string]: string }
40
- disabled?: boolean
41
- error?: string
42
- htmlOptions?: {[key: string]: string | number | boolean | (() => void)},
43
- id?: string
44
- inputDisplay?: "pills" | "none"
45
- inputName?: string
46
- label?: string
47
- name?: string
48
- required?: boolean
49
- returnAllSelected?: boolean
50
- showCheckedChildren?: boolean
51
- treeData?: { [key: string]: string; }[] | any
52
- onChange?: (event: { target: { name?: string; value: any } }) => void
53
- onSelect?: (prop: { [key: string]: any }) => void
54
- selectedIds?: string[] | any
55
- variant?: "multi" | "single"
56
- wrapped?: boolean
57
- pillColor?: "primary" | "neutral" | "success" | "warning" | "error" | "info" | "data_1" | "data_2" | "data_3" | "data_4" | "data_5" | "data_6" | "data_7" | "data_8" | "windows" | "siding" | "roofing" | "doors" | "gutters" | "solar" | "insulation" | "accessories",
58
- } & GlobalProps
59
-
60
- const MultiLevelSelect = forwardRef<HTMLInputElement, MultiLevelSelectProps>((props) => {
61
- const {
62
- aria = {},
63
- className,
64
- data = {},
65
- disabled = false,
66
- error,
67
- htmlOptions = {},
68
- id,
69
- inputDisplay = "pills",
70
- inputName,
71
- name,
72
- label,
73
- required = false,
74
- returnAllSelected = false,
75
- showCheckedChildren = true,
76
- treeData,
77
- onChange = () => null,
78
- onSelect = () => null,
79
- selectedIds,
80
- variant = "multi",
81
- children,
82
- wrapped,
83
- pillColor = "primary"
84
- } = props
85
-
86
- const ariaProps = buildAriaProps(aria);
87
- const dataProps = buildDataProps(data);
88
- const htmlProps = buildHtmlProps(htmlOptions);
89
- const classes = classnames(
90
- buildCss("pb_multi_level_select"),
91
- error && "error",
92
- globalProps(props),
93
- className
94
- );
95
-
96
- const dropdownRef = useRef(null);
97
-
98
- // State for whether dropdown is open or closed
99
- const [isDropdownClosed, setIsDropdownClosed] = useState(true);
100
- // State from onChange for textinput, to use for filtering to create typeahead
101
- const [filterItem, setFilterItem] = useState("");
102
- // FormattedData with checked and parent_id added
103
- const [formattedData, setFormattedData] = useState([]);
104
- // State for the return of returnAllSelected
105
- const [returnedArray, setReturnedArray] = useState([]);
106
- // State for default return
107
- const [defaultReturn, setDefaultReturn] = useState([]);
108
- // Get expanded items from treeData
109
- const initialExpandedItems = getExpandedItems(treeData, selectedIds, showCheckedChildren);
110
- // Initialize state with expanded items
111
- const [expanded, setExpanded] = useState(initialExpandedItems);
112
-
113
- // Single Select specific state
114
- const [singleSelectedItem, setSingleSelectedItem] = useState({
115
- id: [],
116
- value: "",
117
- item: [],
118
- });
119
-
120
- const arrowDownElementId = `arrow_down_${id}`
121
- const arrowUpElementId = `arrow_up_${id}`
122
-
123
- const modifyRecursive = (tree: { [key: string]: any }[], check: boolean) => {
124
- if (!Array.isArray(tree)) {
125
- return;
126
- }
127
- return tree.map((item: { [key: string]: any }) => {
128
- if (!item.disabled) {
129
- item.checked = check;
130
- }
131
- item.children = modifyRecursive(item.children, check);
132
- return item;
133
- });
134
- };
135
-
136
- // Function to map over data and add parent_id + depth property to each item
137
- const addCheckedAndParentProperty = (
138
- treeData: { [key: string]: any }[],
139
- selectedIds: string[],
140
- parent_id: string | null = null,
141
- depth = 0,
142
- parentDisabled = false
143
- ) => {
144
- if (!Array.isArray(treeData)) {
145
- return;
146
- }
147
- return treeData.map((item: { [key: string]: any } | any) => {
148
- // An item is disabled if it is explicitly set as disabled or if its parent is disabled
149
- const isDisabled = item.disabled || (parentDisabled && !returnAllSelected);
150
-
151
- const newItem = {
152
- ...item,
153
- checked: Boolean(
154
- selectedIds && selectedIds.length && selectedIds.includes(item.id)
155
- ),
156
- parent_id,
157
- depth,
158
- disabled: isDisabled,
159
- };
160
- if (newItem.children && newItem.children.length > 0) {
161
- const children =
162
- item.checked && !returnAllSelected
163
- ? modifyRecursive(item.children, true)
164
- : item.children;
165
- newItem.children = addCheckedAndParentProperty(
166
- children,
167
- selectedIds,
168
- newItem.id,
169
- depth + 1,
170
- isDisabled
171
- );
172
- }
173
- return newItem;
174
- });
175
- };
36
+ aria?: { [key: string]: string };
37
+ className?: string;
38
+ data?: { [key: string]: string };
39
+ disabled?: boolean;
40
+ error?: string;
41
+ htmlOptions?: { [key: string]: string | number | boolean | (() => void) };
42
+ id?: string;
43
+ inputDisplay?: "pills" | "none";
44
+ inputName?: string;
45
+ label?: string;
46
+ name?: string;
47
+ required?: boolean;
48
+ returnAllSelected?: boolean;
49
+ showCheckedChildren?: boolean;
50
+ treeData?: { [key: string]: string }[] | any;
51
+ onChange?: (event: { target: { name?: string; value: any } }) => void;
52
+ onSelect?: (prop: { [key: string]: any }) => void;
53
+ selectedIds?: string[] | any;
54
+ variant?: "multi" | "single";
55
+ wrapped?: boolean;
56
+ pillColor?:
57
+ | "primary"
58
+ | "neutral"
59
+ | "success"
60
+ | "warning"
61
+ | "error"
62
+ | "info"
63
+ | "data_1"
64
+ | "data_2"
65
+ | "data_3"
66
+ | "data_4"
67
+ | "data_5"
68
+ | "data_6"
69
+ | "data_7"
70
+ | "data_8"
71
+ | "windows"
72
+ | "siding"
73
+ | "roofing"
74
+ | "doors"
75
+ | "gutters"
76
+ | "solar"
77
+ | "insulation"
78
+ | "accessories";
79
+ } & GlobalProps;
80
+
81
+ const MultiLevelSelect = forwardRef<HTMLInputElement, MultiLevelSelectProps>(
82
+ (props) => {
83
+ const {
84
+ aria = {},
85
+ className,
86
+ data = {},
87
+ disabled = false,
88
+ error,
89
+ htmlOptions = {},
90
+ id,
91
+ inputDisplay = "pills",
92
+ inputName,
93
+ name,
94
+ label,
95
+ required = false,
96
+ returnAllSelected = false,
97
+ showCheckedChildren = true,
98
+ treeData,
99
+ onChange = () => null,
100
+ onSelect = () => null,
101
+ selectedIds,
102
+ variant = "multi",
103
+ wrapped,
104
+ pillColor = "primary",
105
+ } = props;
106
+
107
+ const ariaProps = buildAriaProps(aria);
108
+ const dataProps = buildDataProps(data);
109
+ const htmlProps = buildHtmlProps(htmlOptions);
110
+ const classes = classnames(
111
+ buildCss("pb_multi_level_select"),
112
+ error && "error",
113
+ globalProps(props),
114
+ className
115
+ );
176
116
 
177
- useEffect(() => {
178
- const formattedData = addCheckedAndParentProperty(
117
+ const dropdownRef = useRef(null);
118
+
119
+ // State for whether dropdown is open or closed
120
+ const [isDropdownClosed, setIsDropdownClosed] = useState(true);
121
+ // State from onChange for textinput, to use for filtering to create typeahead
122
+ const [filterItem, setFilterItem] = useState("");
123
+ // FormattedData with checked and parent_id added
124
+ const [formattedData, setFormattedData] = useState([]);
125
+ // State for the return of returnAllSelected
126
+ const [returnedArray, setReturnedArray] = useState([]);
127
+ // State for default return
128
+ const [defaultReturn, setDefaultReturn] = useState([]);
129
+ // Get expanded items from treeData
130
+ const initialExpandedItems = getExpandedItems(
179
131
  treeData,
180
- variant === "single" ? [selectedIds?.[0]] : selectedIds
132
+ selectedIds,
133
+ showCheckedChildren,
181
134
  );
135
+ // Initialize state with expanded items
136
+ const [expanded, setExpanded] = useState(initialExpandedItems);
137
+
138
+ // Single Select specific state
139
+ const [singleSelectedItem, setSingleSelectedItem] = useState({
140
+ id: [],
141
+ value: "",
142
+ item: [],
143
+ });
182
144
 
183
- setFormattedData(formattedData);
145
+ const arrowDownElementId = `arrow_down_${id}`;
146
+ const arrowUpElementId = `arrow_up_${id}`;
147
+ // Control id for label htmlFor: use suffix to avoid conflict with outer div's id
148
+ const sanitizeForId = (str: string) =>
149
+ str.toLowerCase().replace(/\s+/g, "_").replace(/[^a-z0-9_]/g, "");
150
+ const labelForId = id
151
+ ? `${id}_input`
152
+ : (name ? sanitizeForId(name) : null) ||
153
+ (label ? sanitizeForId(label) : null) ||
154
+ "multiselect_input";
155
+ const errorId = error ? `${labelForId}-error` : undefined;
156
+
157
+ const modifyRecursive = (
158
+ tree: { [key: string]: any }[],
159
+ check: boolean,
160
+ ) => {
161
+ if (!Array.isArray(tree)) {
162
+ return;
163
+ }
164
+ return tree.map((item: { [key: string]: any }) => {
165
+ if (!item.disabled) {
166
+ item.checked = check;
167
+ }
168
+ item.children = modifyRecursive(item.children, check);
169
+ return item;
170
+ });
171
+ };
184
172
 
185
- if (variant === "single") {
186
- // No selectedIds, reset state
187
- if (selectedIds?.length === 0 || !selectedIds?.length) {
188
- setSingleSelectedItem({ id: [], value: "", item: [] });
189
- } else {
190
- // If there is a selectedId but no current item, set the selectedItem
191
- if (selectedIds?.length !== 0 && !singleSelectedItem.value) {
192
- const selectedItem = filterFormattedDataById(
193
- formattedData,
194
- selectedIds[0]
173
+ // Function to map over data and add parent_id + depth property to each item
174
+ const addCheckedAndParentProperty = (
175
+ treeData: { [key: string]: any }[],
176
+ selectedIds: string[],
177
+ parent_id: string | null = null,
178
+ depth = 0,
179
+ parentDisabled = false,
180
+ ) => {
181
+ if (!Array.isArray(treeData)) {
182
+ return;
183
+ }
184
+ return treeData.map((item: { [key: string]: any } | any) => {
185
+ // An item is disabled if it is explicitly set as disabled or if its parent is disabled
186
+ const isDisabled =
187
+ item.disabled || (parentDisabled && !returnAllSelected);
188
+
189
+ const newItem = {
190
+ ...item,
191
+ checked: Boolean(
192
+ selectedIds && selectedIds.length && selectedIds.includes(item.id),
193
+ ),
194
+ parent_id,
195
+ depth,
196
+ disabled: isDisabled,
197
+ };
198
+ if (newItem.children && newItem.children.length > 0) {
199
+ const children =
200
+ item.checked && !returnAllSelected
201
+ ? modifyRecursive(item.children, true)
202
+ : item.children;
203
+ newItem.children = addCheckedAndParentProperty(
204
+ children,
205
+ selectedIds,
206
+ newItem.id,
207
+ depth + 1,
208
+ isDisabled,
195
209
  );
210
+ }
211
+ return newItem;
212
+ });
213
+ };
196
214
 
197
- if (!selectedItem.length) {
198
- setSingleSelectedItem({ id: [], value: "", item: [] });
199
- } else {
200
- const { id, label } = selectedItem[0];
201
- setSingleSelectedItem({ id: [id], value: label, item: selectedItem });
215
+ useEffect(() => {
216
+ const formattedData = addCheckedAndParentProperty(
217
+ treeData,
218
+ variant === "single" ? [selectedIds?.[0]] : selectedIds,
219
+ );
220
+
221
+ setFormattedData(formattedData);
222
+
223
+ if (variant === "single") {
224
+ // No selectedIds, reset state
225
+ if (selectedIds?.length === 0 || !selectedIds?.length) {
226
+ setSingleSelectedItem({ id: [], value: "", item: [] });
227
+ } else {
228
+ // If there is a selectedId but no current item, set the selectedItem
229
+ if (selectedIds?.length !== 0 && !singleSelectedItem.value) {
230
+ const selectedItem = filterFormattedDataById(
231
+ formattedData,
232
+ selectedIds[0],
233
+ );
234
+
235
+ if (!selectedItem.length) {
236
+ setSingleSelectedItem({ id: [], value: "", item: [] });
237
+ } else {
238
+ const { id, label } = selectedItem[0];
239
+ setSingleSelectedItem({
240
+ id: [id],
241
+ value: label,
242
+ item: selectedItem,
243
+ });
244
+ }
202
245
  }
203
246
  }
204
247
  }
205
- }
206
- }, [treeData, selectedIds]);
207
-
208
- useEffect(() => {
209
- if (returnAllSelected) {
210
- setReturnedArray(getCheckedItems(formattedData));
211
- } else if (variant === "single") {
212
- setDefaultReturn(singleSelectedItem.item);
213
- } else {
214
- setDefaultReturn(getDefaultCheckedItems(formattedData));
215
- }
216
- }, [formattedData]);
217
-
218
- useEffect(() => {
219
- // Function to handle clicks outside the dropdown
220
- const handleClickOutside = (event: any) => {
221
- if (
222
- dropdownRef.current &&
223
- !dropdownRef.current.contains(event.target) &&
224
- event.target.id !== arrowDownElementId &&
225
- event.target.id !== arrowUpElementId
226
- ) {
227
- setIsDropdownClosed(true)
248
+ }, [treeData, selectedIds]);
249
+
250
+ useEffect(() => {
251
+ if (returnAllSelected) {
252
+ setReturnedArray(getCheckedItems(formattedData));
253
+ } else if (variant === "single") {
254
+ setDefaultReturn(singleSelectedItem.item);
255
+ } else {
256
+ setDefaultReturn(getDefaultCheckedItems(formattedData));
228
257
  }
229
- };
230
- // Attach the event listener
231
- window.addEventListener("click", handleClickOutside);
232
- // Clean up the event listener on unmount
233
- return () => {
234
- window.removeEventListener("click", handleClickOutside);
235
- };
236
- }, []);
237
-
238
- useEffect(() => {
239
- if (id) {
240
- // Attach the clear function to the window, scoped by the id
241
- (window as any)[`clearMultiLevelSelect_${id}`] = () => {
242
- const resetData = modifyRecursive(formattedData, false);
243
- setFormattedData(resetData);
244
- setReturnedArray([]);
245
- setDefaultReturn([]);
246
- setSingleSelectedItem({ id: [], value: "", item: [] });
247
- onSelect([]);
258
+ }, [formattedData]);
259
+
260
+ useEffect(() => {
261
+ // Function to handle clicks outside the dropdown
262
+ const handleClickOutside = (event: any) => {
263
+ // Don't close if clicking on the associated label
264
+ const labelEl = document.querySelector(`label[for="${labelForId}"]`);
265
+ if (labelEl?.contains(event.target)) return;
266
+
267
+ if (
268
+ dropdownRef.current &&
269
+ !dropdownRef.current.contains(event.target) &&
270
+ event.target.id !== arrowDownElementId &&
271
+ event.target.id !== arrowUpElementId
272
+ ) {
273
+ setIsDropdownClosed(true);
274
+ }
248
275
  };
276
+ // Attach the event listener
277
+ window.addEventListener("click", handleClickOutside);
278
+ // Clean up the event listener on unmount
249
279
  return () => {
250
- delete (window as any)[`clearMultiLevelSelect_${id}`];
280
+ window.removeEventListener("click", handleClickOutside);
251
281
  };
252
- }
253
- }, [formattedData, id, onSelect]);
254
-
255
- // Iterate over tree, find item and set checked or unchecked
256
- const modifyValue = (
257
- id: string,
258
- tree: { [key: string]: any }[],
259
- check: boolean
260
- ) => {
261
- if (!Array.isArray(tree)) {
262
- return;
263
- }
264
- return tree.map((item: any) => {
265
- if (item.id != id) item.children = modifyValue(id, item.children, check);
266
- else {
267
- if (!item.disabled) {
268
- item.checked = check;
269
- }
270
- if (variant === "single") {
271
- // Single select: no children should be checked
272
- item.children = modifyRecursive(item.children, !check);
273
- } else {
274
- item.children = modifyRecursive(item.children, check);
282
+ }, [labelForId]);
283
+
284
+ useEffect(() => {
285
+ if (id) {
286
+ // Attach the clear function to the window, scoped by the id
287
+ (window as any)[`clearMultiLevelSelect_${id}`] = () => {
288
+ const resetData = modifyRecursive(formattedData, false);
289
+ setFormattedData(resetData);
290
+ setReturnedArray([]);
291
+ setDefaultReturn([]);
292
+ setSingleSelectedItem({ id: [], value: "", item: [] });
293
+ onSelect([]);
294
+ };
295
+ return () => {
296
+ delete (window as any)[`clearMultiLevelSelect_${id}`];
297
+ };
298
+ }
299
+ }, [formattedData, id, onSelect]);
300
+
301
+ // Iterate over tree, find item and set checked or unchecked
302
+ const modifyValue = (
303
+ id: string,
304
+ tree: { [key: string]: any }[],
305
+ check: boolean,
306
+ ) => {
307
+ if (!Array.isArray(tree)) {
308
+ return;
309
+ }
310
+ return tree.map((item: any) => {
311
+ if (item.id != id)
312
+ item.children = modifyValue(id, item.children, check);
313
+ else {
314
+ if (!item.disabled) {
315
+ item.checked = check;
316
+ }
317
+ if (variant === "single") {
318
+ // Single select: no children should be checked
319
+ item.children = modifyRecursive(item.children, !check);
320
+ } else {
321
+ item.children = modifyRecursive(item.children, check);
322
+ }
275
323
  }
324
+
325
+ return item;
326
+ });
327
+ };
328
+
329
+ // Clone tree, check items + children
330
+ const checkItem = (item: { [key: string]: any }) => {
331
+ const tree = cloneDeep(formattedData);
332
+ if (returnAllSelected) {
333
+ return modifyValue(item.id, tree, true);
334
+ } else {
335
+ const checkedTree = modifyValue(item.id, tree, true);
336
+ return recursiveCheckParent(item, checkedTree);
276
337
  }
338
+ };
277
339
 
278
- return item;
279
- });
280
- };
281
-
282
- // Clone tree, check items + children
283
- const checkItem = (item: { [key: string]: any }) => {
284
- const tree = cloneDeep(formattedData);
285
- if (returnAllSelected) {
286
- return modifyValue(item.id, tree, true);
287
- } else {
288
- const checkedTree = modifyValue(item.id, tree, true);
289
- return recursiveCheckParent(item, checkedTree);
290
- }
291
- };
292
-
293
- // Clone tree, uncheck items + children
294
- const unCheckItem = (item: { [key: string]: any }) => {
295
- const tree = cloneDeep(formattedData);
296
- if (returnAllSelected) {
297
- return modifyValue(item.id, tree, false);
298
- } else {
299
- const uncheckedTree = modifyValue(item.id, tree, false);
300
- return getAncestorsOfUnchecked(uncheckedTree, item);
301
- }
302
- };
303
-
304
- // setFormattedData with proper properties
305
- const changeItem = (item: { [key: string]: any }, check: boolean) => {
306
- const tree = check ? checkItem(item) : unCheckItem(item);
307
- setFormattedData(tree);
308
-
309
- return tree;
310
- };
311
-
312
- // Click event for x on form pill
313
- const handlePillClose = (event: any, clickedItem: { [key: string]: any }) => {
314
- // Prevents the dropdown from closing when clicking on the pill
315
- event.stopPropagation();
316
- const updatedTree = changeItem(clickedItem, false);
317
- // Logic for removing items from returnArray or defaultReturn when pills clicked
318
- if (returnAllSelected) {
319
- onSelect(getCheckedItems(updatedTree));
320
- onChange({ target: { name, value: getCheckedItems(updatedTree) } });
321
- } else {
322
- onSelect(getDefaultCheckedItems(updatedTree));
323
- onChange({ target: { name, value: getDefaultCheckedItems(updatedTree) } });
324
- }
325
- };
326
-
327
- // Handle click on input wrapper(entire div with pills, typeahead, etc) so it doesn't close when input or form pill is clicked
328
- const handleInputWrapperClick = (e: any) => {
329
- if (
330
- e.target.id === "multiselect_input" ||
331
- e.target.classList.contains("pb_form_pill_tag") ||
332
- disabled
333
- ) {
334
- return;
335
- }
336
- setIsDropdownClosed(!isDropdownClosed);
337
- };
338
-
339
- // Main function to handle any click inside dropdown
340
- const handledropdownItemClick = (e: any, check: boolean) => {
341
- const clickedItem = e.target.parentNode.id;
342
- // Setting filterItem to "" will clear textinput and clear typeahead
343
- setFilterItem("");
344
-
345
- const filtered = filterFormattedDataById(formattedData, clickedItem);
346
- const updatedTree = changeItem(filtered[0], check);
347
- if (returnAllSelected) {
348
- onSelect(getCheckedItems(updatedTree));
349
- onChange({ target: { name, value: getCheckedItems(updatedTree) } });
350
- } else {
351
- onSelect(getDefaultCheckedItems(updatedTree));
352
- onChange({ target: { name, value: getDefaultCheckedItems(updatedTree) } });
353
- }
354
- };
355
-
356
- // Single select
357
- const handleRadioButtonClick = (e: React.ChangeEvent<HTMLInputElement>) => {
358
- const { id, value: inputText } = e.target;
359
- // The radio button needs a unique ID, this grabs the ID before the hyphen
360
- const selectedItemID = id.match(/^[^-]*/)[0];
361
-
362
- // Check if the item is disabled - if so, don't allow selection (safety check in addition to native disabled attribute)
363
- const clickedItem = filterFormattedDataById(formattedData, selectedItemID);
364
- if (clickedItem.length > 0 && clickedItem[0].disabled) {
365
- return;
366
- }
367
-
368
- // Reset tree checked state, triggering useEffect
369
- const treeWithNoSelections = modifyRecursive(formattedData, false);
370
- // Update tree with single selection
371
- const treeWithSelectedItem = modifyValue(
372
- selectedItemID,
373
- treeWithNoSelections,
374
- true
375
- );
376
- const selectedItem = filterFormattedDataById(
377
- treeWithSelectedItem,
378
- selectedItemID
379
- );
340
+ // Clone tree, uncheck items + children
341
+ const unCheckItem = (item: { [key: string]: any }) => {
342
+ const tree = cloneDeep(formattedData);
343
+ if (returnAllSelected) {
344
+ return modifyValue(item.id, tree, false);
345
+ } else {
346
+ const uncheckedTree = modifyValue(item.id, tree, false);
347
+ return getAncestorsOfUnchecked(uncheckedTree, item);
348
+ }
349
+ };
380
350
 
381
- setFormattedData(treeWithSelectedItem);
382
- setSingleSelectedItem({
383
- id: [selectedItemID],
384
- value: inputText,
385
- item: selectedItem,
386
- });
387
- // Reset the filter to always display dropdown options on click
388
- setFilterItem("");
389
- setIsDropdownClosed(true);
390
-
391
- onSelect(selectedItem);
392
- onChange({ target: { name, value: selectedItem } });
393
- };
394
-
395
- // Single select: reset the tree state upon typing
396
- const handleRadioInputChange = (inputText: string) => {
397
- modifyRecursive(formattedData, false);
398
- setDefaultReturn([]);
399
- setSingleSelectedItem({ id: [], value: inputText, item: [] });
400
- setFilterItem(inputText);
401
- };
402
-
403
- const isTreeRowExpanded = (item: any) => expanded.indexOf(item.id) > -1;
404
-
405
- // Handle click on chevron toggles in dropdown
406
- const handleToggleClick = (id: string, event: React.MouseEvent) => {
407
- event.stopPropagation();
408
- const clickedItem = filterFormattedDataById(formattedData, id);
409
- if (clickedItem) {
410
- let expandedArray = [...expanded];
411
- const itemExpanded = isTreeRowExpanded(clickedItem[0]);
412
-
413
- if (itemExpanded)
414
- expandedArray = expandedArray.filter((i) => i != clickedItem[0].id);
415
- else expandedArray.push(clickedItem[0].id);
416
-
417
- setExpanded(expandedArray);
418
- }
419
- };
420
-
421
- const itemsSelectedLength = () => {
422
- let items;
423
- if (returnAllSelected && returnedArray && returnedArray.length) {
424
- items = returnedArray.length;
425
- } else if (!returnAllSelected && defaultReturn && defaultReturn.length) {
426
- items = defaultReturn.length;
427
- }
428
- return items;
429
- };
430
-
431
- // Rendering formattedData to UI based on typeahead
432
- const renderNestedOptions = (items: { [key: string]: string; }[] | any ) => {
433
- const hasOptionsChild = React.Children.toArray(props.children).some(
434
- (child) => React.isValidElement(child) && child.type === MultiLevelSelect.Options
435
- );
351
+ // setFormattedData with proper properties
352
+ const changeItem = (item: { [key: string]: any }, check: boolean) => {
353
+ const tree = check ? checkItem(item) : unCheckItem(item);
354
+ setFormattedData(tree);
436
355
 
437
- if (hasOptionsChild) {
438
- return React.Children.map(props.children, (child) => {
439
- if (React.isValidElement(child) && child.type === MultiLevelSelect.Options) {
440
- return React.cloneElement(child, { items });
441
- }
442
- return null;
356
+ return tree;
357
+ };
358
+
359
+ // Click event for x on form pill
360
+ const handlePillClose = (
361
+ event: any,
362
+ clickedItem: { [key: string]: any },
363
+ ) => {
364
+ // Prevents the dropdown from closing when clicking on the pill
365
+ event.stopPropagation();
366
+ const updatedTree = changeItem(clickedItem, false);
367
+ // Logic for removing items from returnArray or defaultReturn when pills clicked
368
+ if (returnAllSelected) {
369
+ onSelect(getCheckedItems(updatedTree));
370
+ onChange({ target: { name, value: getCheckedItems(updatedTree) } });
371
+ } else {
372
+ onSelect(getDefaultCheckedItems(updatedTree));
373
+ onChange({
374
+ target: { name, value: getDefaultCheckedItems(updatedTree) },
375
+ });
376
+ }
377
+ };
378
+
379
+ // Handle click on label - focus input and open dropdown
380
+ const handleLabelClick = (e: React.MouseEvent) => {
381
+ e.stopPropagation();
382
+ const input = document.getElementById(labelForId);
383
+ if (input) input.focus();
384
+ setIsDropdownClosed(false);
385
+ };
386
+
387
+ // Handle click on input wrapper(entire div with pills, typeahead, etc) so it doesn't close when input or form pill is clicked
388
+ const handleInputWrapperClick = (e: any) => {
389
+ if (
390
+ e.target.id === labelForId ||
391
+ e.target.classList.contains("pb_form_pill_tag") ||
392
+ disabled
393
+ ) {
394
+ return;
395
+ }
396
+ setIsDropdownClosed(!isDropdownClosed);
397
+ };
398
+
399
+ // Main function to handle any click inside dropdown
400
+ const handledropdownItemClick = (e: any, check: boolean) => {
401
+ const clickedItem = e.target.parentNode.id;
402
+ // Setting filterItem to "" will clear textinput and clear typeahead
403
+ setFilterItem("");
404
+
405
+ const filtered = filterFormattedDataById(formattedData, clickedItem);
406
+ const updatedTree = changeItem(filtered[0], check);
407
+ if (returnAllSelected) {
408
+ onSelect(getCheckedItems(updatedTree));
409
+ onChange({ target: { name, value: getCheckedItems(updatedTree) } });
410
+ } else {
411
+ onSelect(getDefaultCheckedItems(updatedTree));
412
+ onChange({
413
+ target: { name, value: getDefaultCheckedItems(updatedTree) },
414
+ });
415
+ }
416
+ };
417
+
418
+ // Single select
419
+ const handleRadioButtonClick = (e: React.ChangeEvent<HTMLInputElement>) => {
420
+ const { id, value: inputText } = e.target;
421
+ // The radio button needs a unique ID, this grabs the ID before the hyphen
422
+ const selectedItemID = id.match(/^[^-]*/)[0];
423
+
424
+ // Check if the item is disabled - if so, don't allow selection (safety check in addition to native disabled attribute)
425
+ const clickedItem = filterFormattedDataById(
426
+ formattedData,
427
+ selectedItemID,
428
+ );
429
+ if (clickedItem.length > 0 && clickedItem[0].disabled) {
430
+ return;
431
+ }
432
+
433
+ // Reset tree checked state, triggering useEffect
434
+ const treeWithNoSelections = modifyRecursive(formattedData, false);
435
+ // Update tree with single selection
436
+ const treeWithSelectedItem = modifyValue(
437
+ selectedItemID,
438
+ treeWithNoSelections,
439
+ true,
440
+ );
441
+ const selectedItem = filterFormattedDataById(
442
+ treeWithSelectedItem,
443
+ selectedItemID,
444
+ );
445
+
446
+ setFormattedData(treeWithSelectedItem);
447
+ setSingleSelectedItem({
448
+ id: [selectedItemID],
449
+ value: inputText,
450
+ item: selectedItem,
443
451
  });
444
- } else {
445
- // If no children, use the default rendering
446
- return (
447
- <MultiLevelSelectOptions items={items} />
452
+ // Reset the filter to always display dropdown options on click
453
+ setFilterItem("");
454
+ setIsDropdownClosed(true);
455
+
456
+ onSelect(selectedItem);
457
+ onChange({ target: { name, value: selectedItem } });
458
+ };
459
+
460
+ // Single select: reset the tree state upon typing
461
+ const handleRadioInputChange = (inputText: string) => {
462
+ modifyRecursive(formattedData, false);
463
+ setDefaultReturn([]);
464
+ setSingleSelectedItem({ id: [], value: inputText, item: [] });
465
+ setFilterItem(inputText);
466
+ };
467
+
468
+ const isTreeRowExpanded = (item: any) => expanded.indexOf(item.id) > -1;
469
+
470
+ // Handle click on chevron toggles in dropdown
471
+ const handleToggleClick = (id: string, event: React.MouseEvent) => {
472
+ event.stopPropagation();
473
+ const clickedItem = filterFormattedDataById(formattedData, id);
474
+ if (clickedItem) {
475
+ let expandedArray = [...expanded];
476
+ const itemExpanded = isTreeRowExpanded(clickedItem[0]);
477
+
478
+ if (itemExpanded)
479
+ expandedArray = expandedArray.filter((i) => i != clickedItem[0].id);
480
+ else expandedArray.push(clickedItem[0].id);
481
+
482
+ setExpanded(expandedArray);
483
+ }
484
+ };
485
+
486
+ const itemsSelectedLength = () => {
487
+ let items;
488
+ if (returnAllSelected && returnedArray && returnedArray.length) {
489
+ items = returnedArray.length;
490
+ } else if (!returnAllSelected && defaultReturn && defaultReturn.length) {
491
+ items = defaultReturn.length;
492
+ }
493
+ return items;
494
+ };
495
+
496
+ // Rendering formattedData to UI based on typeahead
497
+ const renderNestedOptions = (items: { [key: string]: string }[] | any) => {
498
+ const hasOptionsChild = React.Children.toArray(props.children).some(
499
+ (child) =>
500
+ React.isValidElement(child) &&
501
+ child.type === MultiLevelSelect.Options,
448
502
  );
449
- }
450
- };
451
-
452
-
453
- return (
454
- <div
455
- {...ariaProps}
456
- {...dataProps}
457
- {...htmlProps}
458
- className={classes}
459
- id={id}
460
- >
461
- {label &&
462
- <Caption
463
- marginBottom="xs"
464
- text={label}
465
- />
503
+
504
+ if (hasOptionsChild) {
505
+ return React.Children.map(props.children, (child) => {
506
+ if (
507
+ React.isValidElement(child) &&
508
+ child.type === MultiLevelSelect.Options
509
+ ) {
510
+ return React.cloneElement(child, { items });
511
+ }
512
+ return null;
513
+ });
514
+ } else {
515
+ // If no children, use the default rendering
516
+ return <MultiLevelSelectOptions items={items} />;
466
517
  }
467
- <MultiLevelSelectContext.Provider value={{
468
- variant,
469
- inputName,
470
- renderNestedOptions,
471
- isTreeRowExpanded,
472
- handleToggleClick,
473
- handleRadioButtonClick,
474
- handledropdownItemClick,
475
- filterItem,
476
- }}>
477
- <div className="wrapper"
478
- ref={dropdownRef}
518
+ };
519
+
520
+ return (
521
+ <div
522
+ {...ariaProps}
523
+ {...dataProps}
524
+ {...htmlProps}
525
+ className={classes}
526
+ id={id}
479
527
  >
480
- <div className="input_wrapper"
481
- onClick={handleInputWrapperClick}
528
+ {label && (
529
+ <label htmlFor={labelForId}
530
+ onClick={handleLabelClick}
531
+ >
532
+ <Caption
533
+ className="pb_multi_level_select_kit_label"
534
+ marginBottom="xs"
535
+ text={label}
536
+ />
537
+ </label>
538
+ )}
539
+ <MultiLevelSelectContext.Provider
540
+ value={{
541
+ variant,
542
+ inputName,
543
+ renderNestedOptions,
544
+ isTreeRowExpanded,
545
+ handleToggleClick,
546
+ handleRadioButtonClick,
547
+ handledropdownItemClick,
548
+ filterItem,
549
+ }}
482
550
  >
483
- <div className="input_inner_container">
484
- {variant === "single" && defaultReturn.length !== 0
485
- ? defaultReturn.map((selectedItem) => (
486
- <input
487
- disabled={disabled}
488
- key={selectedItem.id}
489
- name={`${name}[]`}
490
- required={required}
491
- type="hidden"
492
- value={selectedItem.id}
493
- />
494
- ))
495
- : null}
496
-
497
- {variant !== "single" && (
498
- <>
499
- {returnAllSelected && returnedArray.length !== 0
500
- ? returnedArray.map((item) => (
501
- <input
502
- disabled={disabled}
503
- key={item.id}
504
- name={`${name}[]`}
505
- required={required}
506
- type="hidden"
507
- value={item.id}
508
- />
509
- ))
510
- : null}
511
-
512
- {!returnAllSelected
513
- ? defaultReturn.map((item) => (
551
+ <div className="wrapper"
552
+ ref={dropdownRef}
553
+ >
554
+ <div className="input_wrapper"
555
+ onClick={handleInputWrapperClick}
556
+ >
557
+ <div className="input_inner_container">
558
+ {variant === "single" && defaultReturn.length !== 0
559
+ ? defaultReturn.map((selectedItem) => (
514
560
  <input
515
561
  disabled={disabled}
516
- key={item.id}
562
+ key={selectedItem.id}
517
563
  name={`${name}[]`}
518
564
  required={required}
519
565
  type="hidden"
520
- value={item.id}
521
- />
522
- ))
523
- : null}
524
-
525
- {returnAllSelected &&
526
- returnedArray.length !== 0 &&
527
- inputDisplay === "pills"
528
- ? returnedArray.map((item, index) => (
529
- <FormPill
530
- color={pillColor}
531
- key={index}
532
- onClick={(event: any) => handlePillClose(event, item)}
533
- text={item.label}
534
- wrapped={wrapped}
535
- />
536
- ))
537
- : null}
538
-
539
- {!returnAllSelected &&
540
- defaultReturn.length !== 0 &&
541
- inputDisplay === "pills"
542
- ? defaultReturn.map((item, index) => (
543
- <FormPill
544
- color={pillColor}
545
- key={index}
546
- onClick={(event: any) => handlePillClose(event, item)}
547
- text={item.label}
548
- wrapped={wrapped}
566
+ value={selectedItem.id}
549
567
  />
550
568
  ))
551
569
  : null}
552
570
 
553
- {returnAllSelected &&
554
- returnedArray.length !== 0 &&
555
- inputDisplay === "pills" && <br />}
556
-
557
- {!returnAllSelected &&
558
- defaultReturn.length !== 0 &&
559
- inputDisplay === "pills" && <br />}
560
- </>
561
- )}
562
-
563
- <input
564
- disabled={disabled}
565
- id="multiselect_input"
566
- onChange={(e) => {
567
- variant === "single"
568
- ? handleRadioInputChange(e.target.value)
569
- : setFilterItem(e.target.value);
570
- }}
571
- onClick={() => setIsDropdownClosed(false)}
572
- placeholder={
573
- inputDisplay === "none" && itemsSelectedLength()
574
- ? `${itemsSelectedLength()} ${
575
- itemsSelectedLength() === 1 ? "item" : "items"
576
- } selected`
577
- : "Start typing..."
578
- }
579
- required={required}
580
- value={singleSelectedItem.value || filterItem}
581
- />
582
- </div>
583
-
584
- {isDropdownClosed ? (
585
- <div id={arrowDownElementId}
586
- key="chevron-down">
587
- <Icon
588
- icon="chevron-down"
589
- id={arrowDownElementId}
590
- size="xs"
591
- />
571
+ {variant !== "single" && (
572
+ <>
573
+ {returnAllSelected && returnedArray.length !== 0
574
+ ? returnedArray.map((item) => (
575
+ <input
576
+ disabled={disabled}
577
+ key={item.id}
578
+ name={`${name}[]`}
579
+ required={required}
580
+ type="hidden"
581
+ value={item.id}
582
+ />
583
+ ))
584
+ : null}
585
+
586
+ {!returnAllSelected
587
+ ? defaultReturn.map((item) => (
588
+ <input
589
+ disabled={disabled}
590
+ key={item.id}
591
+ name={`${name}[]`}
592
+ required={required}
593
+ type="hidden"
594
+ value={item.id}
595
+ />
596
+ ))
597
+ : null}
598
+
599
+ {returnAllSelected &&
600
+ returnedArray.length !== 0 &&
601
+ inputDisplay === "pills"
602
+ ? returnedArray.map((item, index) => (
603
+ <FormPill
604
+ color={pillColor}
605
+ key={index}
606
+ onClick={(event: any) =>
607
+ handlePillClose(event, item)
608
+ }
609
+ text={item.label}
610
+ wrapped={wrapped}
611
+ />
612
+ ))
613
+ : null}
614
+
615
+ {!returnAllSelected &&
616
+ defaultReturn.length !== 0 &&
617
+ inputDisplay === "pills"
618
+ ? defaultReturn.map((item, index) => (
619
+ <FormPill
620
+ color={pillColor}
621
+ key={index}
622
+ onClick={(event: any) =>
623
+ handlePillClose(event, item)
624
+ }
625
+ text={item.label}
626
+ wrapped={wrapped}
627
+ />
628
+ ))
629
+ : null}
630
+
631
+ {returnAllSelected &&
632
+ returnedArray.length !== 0 &&
633
+ inputDisplay === "pills" && <br />}
634
+
635
+ {!returnAllSelected &&
636
+ defaultReturn.length !== 0 &&
637
+ inputDisplay === "pills" && <br />}
638
+ </>
639
+ )}
640
+
641
+ <input
642
+ aria-describedby={errorId}
643
+ aria-invalid={!!error}
644
+ disabled={disabled}
645
+ id={labelForId}
646
+ onChange={(e) => {
647
+ variant === "single"
648
+ ? handleRadioInputChange(e.target.value)
649
+ : setFilterItem(e.target.value);
650
+ }}
651
+ onClick={() => setIsDropdownClosed(false)}
652
+ onFocus={() => !disabled && setIsDropdownClosed(false)}
653
+ placeholder={
654
+ inputDisplay === "none" && itemsSelectedLength()
655
+ ? `${itemsSelectedLength()} ${
656
+ itemsSelectedLength() === 1 ? "item" : "items"
657
+ } selected`
658
+ : "Start typing..."
659
+ }
660
+ required={required}
661
+ value={singleSelectedItem.value || filterItem}
662
+ />
663
+ </div>
664
+
665
+ {isDropdownClosed ? (
666
+ <div id={arrowDownElementId}
667
+ key="chevron-down"
668
+ >
669
+ <Icon icon="chevron-down"
670
+ id={arrowDownElementId}
671
+ size="xs"
672
+ />
673
+ </div>
674
+ ) : (
675
+ <div id={arrowUpElementId}
676
+ key="chevron-up"
677
+ >
678
+ <Icon icon="chevron-up"
679
+ id={arrowUpElementId}
680
+ size="xs"
681
+ />
682
+ </div>
683
+ )}
592
684
  </div>
593
- ) : (
594
- <div id={arrowUpElementId}
595
- key="chevron-up">
596
- <Icon
597
- icon="chevron-up"
598
- id={arrowUpElementId}
599
- size="xs"
600
- />
685
+
686
+ <div
687
+ className={`dropdown_menu ${isDropdownClosed ? "close" : "open"}`}
688
+ >
689
+ {renderNestedOptions(
690
+ filterItem
691
+ ? findByFilter(formattedData, filterItem)
692
+ : formattedData,
693
+ )}
601
694
  </div>
602
- )}
603
- </div>
604
-
605
- <div className={`dropdown_menu ${isDropdownClosed ? "close" : "open"}`}>
606
- {renderNestedOptions(
607
- filterItem ? findByFilter(formattedData, filterItem) : formattedData
608
- )}
609
- </div>
610
- </div>
611
- </MultiLevelSelectContext.Provider>
612
- {error &&
695
+ </div>
696
+ </MultiLevelSelectContext.Provider>
697
+ {error && (
613
698
  <Body
699
+ aria={{ atomic: "true", live: "polite" }}
614
700
  dark={props.dark}
701
+ htmlOptions={{ role: "alert" }}
702
+ id={errorId}
615
703
  status="negative"
616
704
  text={error}
617
705
  />
618
- }
619
- </div>
620
- );
621
- }) as MultiLevelSelectComponent;
706
+ )}
707
+ </div>
708
+ );
709
+ },
710
+ ) as MultiLevelSelectComponent;
622
711
 
623
712
  MultiLevelSelect.displayName = "MultiLevelSelect";
624
713
  MultiLevelSelect.Options = MultiLevelSelectOptions;