playbook_ui 18.2.0 → 18.3.0.pre.alpha.forwardrefwarnings20879
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.
- checksums.yaml +4 -4
- data/app/pb_kits/playbook/pb_advanced_table/table_header.rb +1 -1
- data/app/pb_kits/playbook/pb_avatar/_avatar.tsx +3 -1
- data/app/pb_kits/playbook/pb_avatar/kit.schema.json +1 -0
- data/app/pb_kits/playbook/pb_collapsible/collapsible.test.js +1 -2
- data/app/pb_kits/playbook/pb_contact/_contact.tsx +4 -1
- data/app/pb_kits/playbook/pb_contact/kit.schema.json +1 -0
- data/app/pb_kits/playbook/pb_date/_date.tsx +3 -1
- data/app/pb_kits/playbook/pb_date/date.test.js +1 -1
- data/app/pb_kits/playbook/pb_date/kit.schema.json +1 -0
- data/app/pb_kits/playbook/pb_date_picker/date_picker.test.js +4 -0
- data/app/pb_kits/playbook/pb_date_range_inline/_date_range_inline.tsx +3 -1
- data/app/pb_kits/playbook/pb_date_range_inline/kit.schema.json +1 -0
- data/app/pb_kits/playbook/pb_date_range_stacked/_date_range_stacked.tsx +3 -1
- data/app/pb_kits/playbook/pb_date_range_stacked/kit.schema.json +1 -0
- data/app/pb_kits/playbook/pb_date_stacked/_date_stacked.tsx +4 -1
- data/app/pb_kits/playbook/pb_date_stacked/date_stacked.test.js +1 -0
- data/app/pb_kits/playbook/pb_date_stacked/kit.schema.json +1 -0
- data/app/pb_kits/playbook/pb_date_time/_date_time.tsx +3 -1
- data/app/pb_kits/playbook/pb_date_time/dateTime.test.js +1 -0
- data/app/pb_kits/playbook/pb_date_time/kit.schema.json +1 -0
- data/app/pb_kits/playbook/pb_date_time_stacked/_date_time_stacked.tsx +2 -1
- data/app/pb_kits/playbook/pb_date_time_stacked/kit.schema.json +1 -0
- data/app/pb_kits/playbook/pb_date_year_stacked/_date_year_stacked.tsx +3 -1
- data/app/pb_kits/playbook/pb_date_year_stacked/date_year_stacked.test.js +1 -0
- data/app/pb_kits/playbook/pb_date_year_stacked/kit.schema.json +1 -0
- data/app/pb_kits/playbook/pb_draggable/utilities/touchDrag.ts +10 -0
- data/app/pb_kits/playbook/pb_dropdown/_dropdown.scss +112 -0
- data/app/pb_kits/playbook/pb_dropdown/_dropdown.tsx +121 -15
- data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_draggable_pills.html.erb +51 -0
- data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_draggable_pills.jsx +62 -0
- data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_draggable_pills_rails.md +9 -0
- data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_draggable_pills_react.md +9 -0
- data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_form.jsx +50 -0
- data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_form_react.md +9 -0
- data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_option_creation.html.erb +16 -0
- data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_option_creation.jsx +27 -0
- data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_option_creation_rails.md +9 -0
- data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_option_creation_react.md +9 -0
- data/app/pb_kits/playbook/pb_dropdown/docs/_playground.json +33 -2
- data/app/pb_kits/playbook/pb_dropdown/docs/_playground.overrides.json +13 -2
- data/app/pb_kits/playbook/pb_dropdown/docs/example.yml +5 -0
- data/app/pb_kits/playbook/pb_dropdown/docs/index.js +4 -1
- data/app/pb_kits/playbook/pb_dropdown/dropdown.html.erb +2 -2
- data/app/pb_kits/playbook/pb_dropdown/dropdown.rb +24 -1
- data/app/pb_kits/playbook/pb_dropdown/dropdown.test.jsx +235 -1
- data/app/pb_kits/playbook/pb_dropdown/dropdown_creation.test.jsx +414 -0
- data/app/pb_kits/playbook/pb_dropdown/dropdown_form.test.jsx +173 -0
- data/app/pb_kits/playbook/pb_dropdown/dropdown_index.test.js +545 -0
- data/app/pb_kits/playbook/pb_dropdown/hooks/useHandleOnKeydown.tsx +22 -5
- data/app/pb_kits/playbook/pb_dropdown/index.js +260 -37
- data/app/pb_kits/playbook/pb_dropdown/keyboard_accessibility.js +3 -1
- data/app/pb_kits/playbook/pb_dropdown/kit.schema.json +42 -2
- data/app/pb_kits/playbook/pb_dropdown/subcomponents/DropdownContainer.tsx +43 -25
- data/app/pb_kits/playbook/pb_dropdown/subcomponents/DropdownFormFields.tsx +83 -0
- data/app/pb_kits/playbook/pb_dropdown/subcomponents/DropdownOption.tsx +13 -5
- data/app/pb_kits/playbook/pb_dropdown/subcomponents/MultiSelectTriggerDisplay.tsx +336 -31
- data/app/pb_kits/playbook/pb_dropdown/utilities/optionCreation.ts +21 -0
- data/app/pb_kits/playbook/pb_dropdown/utilities/pillReorder.js +281 -0
- data/app/pb_kits/playbook/pb_dropdown/utilities/quickpickDateSync.test.ts +24 -0
- data/app/pb_kits/playbook/pb_dropdown/utilities/usePillDropIndicator.ts +144 -0
- data/app/pb_kits/playbook/pb_filter/Filter/SortMenu.tsx +8 -1
- data/app/pb_kits/playbook/pb_filter/docs/_filter_one_click.html.erb +38 -0
- data/app/pb_kits/playbook/pb_filter/docs/_filter_one_click.jsx +69 -0
- data/app/pb_kits/playbook/pb_filter/docs/_filter_one_click.md +1 -0
- data/app/pb_kits/playbook/pb_filter/docs/example.yml +2 -0
- data/app/pb_kits/playbook/pb_filter/docs/index.js +2 -1
- data/app/pb_kits/playbook/pb_filter/filter.html.erb +4 -4
- data/app/pb_kits/playbook/pb_filter/filter.rb +28 -0
- data/app/pb_kits/playbook/pb_filter/filter.test.js +41 -0
- data/app/pb_kits/playbook/pb_fixed_confirmation_toast/fixed_confirmation_toast.test.js +1 -2
- data/app/pb_kits/playbook/pb_form/docs/_description_react.md +3 -0
- data/app/pb_kits/playbook/pb_form/docs/_form_default_values.jsx +898 -0
- data/app/pb_kits/playbook/pb_form/docs/_form_default_values_react.md +3 -0
- data/app/pb_kits/playbook/pb_form/docs/_form_defaults.jsx +879 -0
- data/app/pb_kits/playbook/pb_form/docs/_form_defaults_react.md +29 -0
- data/app/pb_kits/playbook/pb_form/docs/_form_loading.jsx +75 -0
- data/app/pb_kits/playbook/pb_form/docs/{_form_loading.md → _form_loading_rails.md} +1 -0
- data/app/pb_kits/playbook/pb_form/docs/_form_loading_react.md +1 -0
- data/app/pb_kits/playbook/pb_form/docs/_form_model_values.jsx +147 -0
- data/app/pb_kits/playbook/pb_form/docs/_form_model_values_react.md +16 -0
- data/app/pb_kits/playbook/pb_form/docs/_form_multi_column.jsx +164 -0
- data/app/pb_kits/playbook/pb_form/docs/_form_multi_column_react.md +3 -0
- data/app/pb_kits/playbook/pb_form/docs/_form_validation.jsx +1057 -0
- data/app/pb_kits/playbook/pb_form/docs/_form_validation_react.md +26 -0
- data/app/pb_kits/playbook/pb_form/docs/example.yml +7 -0
- data/app/pb_kits/playbook/pb_form/pb_form_validation.js +1 -0
- data/app/pb_kits/playbook/pb_form_group/_form_group.scss +1 -4
- data/app/pb_kits/playbook/pb_full_screen/_full_screen.scss +6 -0
- data/app/pb_kits/playbook/pb_full_screen/_full_screen.tsx +11 -2
- data/app/pb_kits/playbook/pb_full_screen/docs/_full_screen_advanced_table_sticky_header.jsx +73 -0
- data/app/pb_kits/playbook/pb_full_screen/docs/_full_screen_advanced_table_sticky_header.md +1 -0
- data/app/pb_kits/playbook/pb_full_screen/docs/example.yml +1 -0
- data/app/pb_kits/playbook/pb_full_screen/docs/index.js +1 -0
- data/app/pb_kits/playbook/pb_full_screen/full_screen.test.jsx +4 -2
- data/app/pb_kits/playbook/pb_image/_image.tsx +3 -1
- data/app/pb_kits/playbook/pb_image/kit.schema.json +1 -0
- data/app/pb_kits/playbook/pb_label_pill/_label_pill.tsx +3 -1
- data/app/pb_kits/playbook/pb_label_pill/kit.schema.json +1 -0
- data/app/pb_kits/playbook/pb_legend/_legend.tsx +3 -1
- data/app/pb_kits/playbook/pb_legend/kit.schema.json +1 -0
- data/app/pb_kits/playbook/pb_list/_list_item.tsx +6 -3
- data/app/pb_kits/playbook/pb_map/docs/_map_default.md +22 -3
- data/app/pb_kits/playbook/pb_multi_level_select/_multi_level_select.tsx +4 -1
- data/app/pb_kits/playbook/pb_online_status/_online_status.tsx +3 -1
- data/app/pb_kits/playbook/pb_online_status/kit.schema.json +1 -0
- data/app/pb_kits/playbook/pb_pb_bar_graph/pbbargraph.test.jsx +1 -2
- data/app/pb_kits/playbook/pb_pb_circle_chart/pb_circle_chart.test.jsx +1 -2
- data/app/pb_kits/playbook/pb_pb_gauge_chart/pb_gauge_chart.test.jsx +1 -2
- data/app/pb_kits/playbook/pb_pb_line_graph/pb_line_graph.test.jsx +1 -2
- data/app/pb_kits/playbook/pb_person/_person.tsx +3 -1
- data/app/pb_kits/playbook/pb_person/kit.schema.json +1 -0
- data/app/pb_kits/playbook/pb_pill/_pill.tsx +3 -1
- data/app/pb_kits/playbook/pb_pill/kit.schema.json +1 -0
- data/app/pb_kits/playbook/pb_progress_step/_progress_step_item.tsx +3 -1
- data/app/pb_kits/playbook/pb_rich_text_editor/docs/_playground.json +1 -1
- data/app/pb_kits/playbook/pb_rich_text_editor/docs/_playground.overrides.json +1 -1
- data/app/pb_kits/playbook/pb_rich_text_editor/docs/_rich_text_editor_advanced_required_indicator.md +2 -2
- data/app/pb_kits/playbook/pb_rich_text_editor/docs/_rich_text_editor_rails_default.md +3 -4
- data/app/pb_kits/playbook/pb_rich_text_editor/docs/_rich_text_editor_rails_extensions.md +1 -1
- data/app/pb_kits/playbook/pb_rich_text_editor/docs/_rich_text_editor_rails_markdown_support.md +1 -1
- data/app/pb_kits/playbook/pb_rich_text_editor/docs/_rich_text_editor_rails_required_indicator.html.erb +9 -0
- data/app/pb_kits/playbook/pb_rich_text_editor/docs/_rich_text_editor_rails_required_indicator.md +3 -0
- data/app/pb_kits/playbook/pb_rich_text_editor/docs/example.yml +1 -0
- data/app/pb_kits/playbook/pb_rich_text_editor/rich_text_editor.html.erb +3 -7
- data/app/pb_kits/playbook/pb_rich_text_editor/rich_text_editor_rails.js +26 -21
- data/app/pb_kits/playbook/pb_rich_text_editor/rich_text_editor_rails.test.js +85 -1
- data/app/pb_kits/playbook/pb_selectable_card_icon/_selectable_card_icon.scss +0 -9
- data/app/pb_kits/playbook/pb_selectable_card_icon/docs/_selectable_card_icon_custom.jsx +1 -1
- data/app/pb_kits/playbook/pb_selectable_icon/_selectable_icon.scss +0 -7
- data/app/pb_kits/playbook/pb_source/_source.tsx +3 -1
- data/app/pb_kits/playbook/pb_source/kit.schema.json +1 -0
- data/app/pb_kits/playbook/pb_source/source.rb +1 -1
- data/app/pb_kits/playbook/pb_stat_change/_stat_change.tsx +3 -1
- data/app/pb_kits/playbook/pb_stat_change/kit.schema.json +1 -0
- data/app/pb_kits/playbook/pb_textarea/_textarea.tsx +4 -1
- data/app/pb_kits/playbook/pb_time_range_inline/_time_range_inline.tsx +3 -1
- data/app/pb_kits/playbook/pb_time_range_inline/kit.schema.json +1 -0
- data/app/pb_kits/playbook/pb_time_stacked/_time_stacked.tsx +3 -1
- data/app/pb_kits/playbook/pb_time_stacked/kit.schema.json +1 -0
- data/app/pb_kits/playbook/pb_timeline/_item.tsx +3 -1
- data/app/pb_kits/playbook/pb_timestamp/_timestamp.tsx +3 -1
- data/app/pb_kits/playbook/pb_timestamp/kit.schema.json +1 -0
- data/app/pb_kits/playbook/pb_timestamp/timestamp.test.js +1 -0
- data/app/pb_kits/playbook/pb_typeahead/_typeahead.scss +73 -0
- data/app/pb_kits/playbook/pb_typeahead/_typeahead.test.jsx +285 -12
- data/app/pb_kits/playbook/pb_typeahead/_typeahead.tsx +22 -14
- data/app/pb_kits/playbook/pb_typeahead/components/PillRenderer.tsx +15 -11
- data/app/pb_kits/playbook/pb_typeahead/components/ValueContainer.tsx +29 -30
- data/app/pb_kits/playbook/pb_typeahead/components/usePillDropIndicator.ts +155 -0
- data/app/pb_kits/playbook/pb_typeahead/docs/_playground.json +5 -5
- data/app/pb_kits/playbook/pb_typeahead/docs/_playground.overrides.json +2 -2
- data/app/pb_kits/playbook/pb_typeahead/docs/_typeahead_draggable_pills.html.erb +0 -1
- data/app/pb_kits/playbook/pb_typeahead/docs/_typeahead_draggable_pills.jsx +0 -1
- data/app/pb_kits/playbook/pb_typeahead/docs/_typeahead_draggable_pills_rails.md +3 -3
- data/app/pb_kits/playbook/pb_typeahead/docs/_typeahead_draggable_pills_react.md +7 -0
- data/app/pb_kits/playbook/pb_typeahead/kit.schema.json +3 -3
- data/app/pb_kits/playbook/pb_typeahead/typeahead.rb +3 -3
- data/app/pb_kits/playbook/pb_user/_user.tsx +3 -1
- data/app/pb_kits/playbook/pb_user/kit.schema.json +1 -0
- data/app/pb_kits/playbook/pb_user_badge/_user_badge.tsx +3 -1
- data/app/pb_kits/playbook/pb_user_badge/kit.schema.json +1 -0
- data/app/pb_kits/playbook/pb_weekday_stacked/_weekday_stacked.tsx +3 -1
- data/app/pb_kits/playbook/pb_weekday_stacked/kit.schema.json +1 -0
- data/app/pb_kits/playbook/pb_weekday_stacked/weekday_stacked.test.jsx +1 -0
- data/app/pb_kits/playbook/utilities/test/globalProps/onClick.test.js +302 -0
- data/app/pb_kits/playbook/utilities/test-utils.js +0 -1
- data/dist/ai/all-schemas.json +108 -15
- data/dist/ai/external-dependencies.json +1 -1
- data/dist/ai/forms.json +2 -2
- data/dist/ai/index.json +3 -3
- data/dist/ai/kits/avatar.schema.json +1 -0
- data/dist/ai/kits/contact.schema.json +1 -0
- data/dist/ai/kits/date.schema.json +1 -0
- data/dist/ai/kits/date_range_inline.schema.json +1 -0
- data/dist/ai/kits/date_range_stacked.schema.json +1 -0
- data/dist/ai/kits/date_stacked.schema.json +1 -0
- data/dist/ai/kits/date_time.schema.json +1 -0
- data/dist/ai/kits/date_time_stacked.schema.json +1 -0
- data/dist/ai/kits/date_year_stacked.schema.json +1 -0
- data/dist/ai/kits/dropdown.schema.json +81 -11
- data/dist/ai/kits/image.schema.json +1 -0
- data/dist/ai/kits/label_pill.schema.json +1 -0
- data/dist/ai/kits/legend.schema.json +1 -0
- data/dist/ai/kits/online_status.schema.json +1 -0
- data/dist/ai/kits/person.schema.json +1 -0
- data/dist/ai/kits/pill.schema.json +1 -0
- data/dist/ai/kits/rich_text_editor.schema.json +1 -1
- data/dist/ai/kits/source.schema.json +1 -0
- data/dist/ai/kits/stat_change.schema.json +1 -0
- data/dist/ai/kits/time_range_inline.schema.json +1 -0
- data/dist/ai/kits/time_stacked.schema.json +1 -0
- data/dist/ai/kits/timestamp.schema.json +1 -0
- data/dist/ai/kits/typeahead.schema.json +3 -3
- data/dist/ai/kits/user.schema.json +1 -0
- data/dist/ai/kits/user_badge.schema.json +1 -0
- data/dist/ai/kits/weekday_stacked.schema.json +1 -0
- data/dist/ai/playgrounds/dropdown.json +1 -1
- data/dist/ai/playgrounds/index.json +1 -1
- data/dist/ai/playgrounds/rich_text_editor.json +1 -1
- data/dist/ai/playgrounds/typeahead.json +1 -1
- data/dist/chunks/_pagination-CARbC_IN.js +1 -0
- data/dist/chunks/{_pb_line_graph-Du7xE9vl.js → _pb_line_graph-BKwbOeqo.js} +1 -1
- data/dist/chunks/{_detail-Bsvl694i.js → _tooltip-UkcgcThO.js} +2 -2
- data/dist/chunks/_typeahead-D_li2Zx8.js +1 -0
- data/dist/chunks/{globalProps-B120NbSJ.js → globalProps-BivjwRew.js} +1 -1
- data/dist/chunks/index-4jrZjexD.js +1 -0
- data/dist/chunks/index-BAy61y8S.js +1 -0
- data/dist/chunks/index-Cb_SDipk.js +1 -0
- data/dist/chunks/index-D3RdsDp7.js +1 -0
- data/dist/chunks/index-D8PZIesy.js +1 -0
- data/dist/chunks/index-DIoc-aua.js +1 -0
- data/dist/chunks/index-qex65UMn.js +1 -0
- data/dist/chunks/lib-C50eCpoN.js +29 -0
- data/dist/chunks/{time_picker_helper-CYGg0UQ0.js → time_picker_helper-DfKvHCJu.js} +1 -1
- data/dist/chunks/vendor.js +2 -2
- data/dist/menu.yml +1 -1
- data/dist/playbook-rails-react-bindings.js +1 -1
- data/dist/playbook-rails.js +1 -1
- data/dist/playbook.css +1 -1
- data/lib/playbook/version.rb +2 -2
- metadata +80 -21
- data/app/pb_kits/playbook/pb_typeahead/docs/_typeahead_draggable_pills.md +0 -7
- data/dist/chunks/_table-Bf6LGyL9.js +0 -1
- data/dist/chunks/_typeahead-w6S5Vx4b.js +0 -1
- data/dist/chunks/lib-Buo6zHEw.js +0 -29
- /data/app/pb_kits/playbook/pb_form/docs/{_description.md → _description_rails.md} +0 -0
- /data/app/pb_kits/playbook/pb_form/docs/{_form_default_values.md → _form_default_values_rails.md} +0 -0
- /data/app/pb_kits/playbook/pb_form/docs/{_form_defaults.md → _form_defaults_rails.md} +0 -0
- /data/app/pb_kits/playbook/pb_form/docs/{_form_model_values.md → _form_model_values_rails.md} +0 -0
- /data/app/pb_kits/playbook/pb_form/docs/{_form_multi_column.md → _form_multi_column_rails.md} +0 -0
- /data/app/pb_kits/playbook/pb_form/docs/{_form_validation.md → _form_validation_rails.md} +0 -0
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import React, { useEffect, useRef } from "react";
|
|
2
|
+
import { GenericObject } from "../../types";
|
|
3
|
+
|
|
4
|
+
type DropdownFormFieldsProps = {
|
|
5
|
+
disabled: boolean;
|
|
6
|
+
multiSelect: boolean;
|
|
7
|
+
name?: string;
|
|
8
|
+
onInvalid: () => void;
|
|
9
|
+
onReset: () => void;
|
|
10
|
+
required: boolean;
|
|
11
|
+
selected: GenericObject[];
|
|
12
|
+
validationMessage: string;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const DropdownFormFields = ({
|
|
16
|
+
disabled,
|
|
17
|
+
multiSelect,
|
|
18
|
+
name,
|
|
19
|
+
onInvalid,
|
|
20
|
+
onReset,
|
|
21
|
+
required,
|
|
22
|
+
selected,
|
|
23
|
+
validationMessage,
|
|
24
|
+
}: DropdownFormFieldsProps): React.ReactElement => {
|
|
25
|
+
const validationRef = useRef<HTMLInputElement>(null);
|
|
26
|
+
const onResetRef = useRef(onReset);
|
|
27
|
+
onResetRef.current = onReset;
|
|
28
|
+
|
|
29
|
+
const hasSelection = selected.some((option) => String(option.id ?? option.value ?? "") !== "");
|
|
30
|
+
const needsArraySuffix = multiSelect && name && !name.endsWith("[]");
|
|
31
|
+
const fieldName = needsArraySuffix ? `${name}[]` : name;
|
|
32
|
+
const submittedOptions = hasSelection ? selected : [{}];
|
|
33
|
+
|
|
34
|
+
useEffect(() => {
|
|
35
|
+
validationRef.current?.setCustomValidity(required && !hasSelection ? validationMessage : "");
|
|
36
|
+
}, [required, hasSelection, validationMessage]);
|
|
37
|
+
|
|
38
|
+
useEffect(() => {
|
|
39
|
+
const form = validationRef.current?.form;
|
|
40
|
+
const handleReset = (event: Event) => {
|
|
41
|
+
// Let native controls reset first, and honor listeners that cancel the reset.
|
|
42
|
+
Promise.resolve().then(() => {
|
|
43
|
+
if (!event.defaultPrevented) onResetRef.current();
|
|
44
|
+
});
|
|
45
|
+
};
|
|
46
|
+
form?.addEventListener("reset", handleReset);
|
|
47
|
+
return () => form?.removeEventListener("reset", handleReset);
|
|
48
|
+
}, []);
|
|
49
|
+
|
|
50
|
+
return (
|
|
51
|
+
<>
|
|
52
|
+
{name && submittedOptions.map((option, index) => (
|
|
53
|
+
<input disabled={disabled}
|
|
54
|
+
key={index}
|
|
55
|
+
name={fieldName}
|
|
56
|
+
type="hidden"
|
|
57
|
+
value={option.id ?? option.value ?? ""}
|
|
58
|
+
/>
|
|
59
|
+
))}
|
|
60
|
+
<input aria-hidden="true"
|
|
61
|
+
disabled={disabled}
|
|
62
|
+
onChange={() => undefined}
|
|
63
|
+
onInvalid={(event) => {
|
|
64
|
+
event.preventDefault();
|
|
65
|
+
onInvalid();
|
|
66
|
+
}}
|
|
67
|
+
ref={validationRef}
|
|
68
|
+
required={required && !hasSelection}
|
|
69
|
+
style={{
|
|
70
|
+
position: "absolute",
|
|
71
|
+
opacity: 0,
|
|
72
|
+
width: 1,
|
|
73
|
+
height: 1,
|
|
74
|
+
pointerEvents: "none",
|
|
75
|
+
}}
|
|
76
|
+
tabIndex={-1}
|
|
77
|
+
value={hasSelection ? "selected" : ""}
|
|
78
|
+
/>
|
|
79
|
+
</>
|
|
80
|
+
);
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
export default DropdownFormFields;
|
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
import { globalProps, GlobalProps } from "../../utilities/globalProps";
|
|
10
10
|
|
|
11
11
|
import DropdownContext from "../context";
|
|
12
|
+
import { matchesOptionExactly } from "../utilities/optionCreation";
|
|
12
13
|
|
|
13
14
|
import Body from "../../pb_body/_body";
|
|
14
15
|
import ListItem from "../../pb_list/_list_item";
|
|
@@ -36,12 +37,13 @@ const DropdownOption = (props: DropdownOptionProps) => {
|
|
|
36
37
|
data = {},
|
|
37
38
|
htmlOptions = {},
|
|
38
39
|
id,
|
|
39
|
-
key,
|
|
40
40
|
option,
|
|
41
41
|
} = props;
|
|
42
42
|
|
|
43
43
|
const {
|
|
44
44
|
asyncEnabled,
|
|
45
|
+
creationOption,
|
|
46
|
+
creationEnabled,
|
|
45
47
|
autocomplete,
|
|
46
48
|
activeStyle,
|
|
47
49
|
disabled,
|
|
@@ -70,7 +72,8 @@ const DropdownOption = (props: DropdownOptionProps) => {
|
|
|
70
72
|
const filterText =
|
|
71
73
|
selectedLabel && filterItem === selectedLabel ? "" : filterItem;
|
|
72
74
|
const label = typeof option?.label === 'string' ? option.label.toLowerCase() : option?.label;
|
|
73
|
-
return String(label).toLowerCase().includes(filterText.toLowerCase())
|
|
75
|
+
return String(label).toLowerCase().includes(filterText.toLowerCase()) ||
|
|
76
|
+
(creationEnabled && matchesOptionExactly(option, filterText));
|
|
74
77
|
}
|
|
75
78
|
|
|
76
79
|
// When multiSelect, then if an option is selected, remove from dropdown
|
|
@@ -84,7 +87,8 @@ const DropdownOption = (props: DropdownOptionProps) => {
|
|
|
84
87
|
const isOptionDisabled = option?.disabled === true;
|
|
85
88
|
const isDisabled = disabled || isOptionDisabled;
|
|
86
89
|
|
|
87
|
-
|
|
90
|
+
const isCreationOption = option === creationOption;
|
|
91
|
+
if ((!isCreationOption && !isItemMatchingFilter(option)) || (multiSelect && isSelected)) {
|
|
88
92
|
return null;
|
|
89
93
|
}
|
|
90
94
|
const isFocused =
|
|
@@ -122,7 +126,12 @@ const DropdownOption = (props: DropdownOptionProps) => {
|
|
|
122
126
|
isDisabled && "disabled"
|
|
123
127
|
);
|
|
124
128
|
|
|
125
|
-
|
|
129
|
+
let content = children || null;
|
|
130
|
+
if (isCreationOption) {
|
|
131
|
+
content = `Create "${option.label}"`;
|
|
132
|
+
} else if (!content && renderOption && option) {
|
|
133
|
+
content = renderOption(option);
|
|
134
|
+
}
|
|
126
135
|
|
|
127
136
|
return (
|
|
128
137
|
<div
|
|
@@ -132,7 +141,6 @@ const DropdownOption = (props: DropdownOptionProps) => {
|
|
|
132
141
|
aria-disabled={isDisabled}
|
|
133
142
|
className={classes}
|
|
134
143
|
id={id}
|
|
135
|
-
key={key}
|
|
136
144
|
onClick={isDisabled ? undefined : () => handleOptionClick(option)}
|
|
137
145
|
>
|
|
138
146
|
<ListItem
|
|
@@ -1,10 +1,15 @@
|
|
|
1
|
-
import React, { useContext } from "react";
|
|
2
|
-
import FormPill from "../../pb_form_pill/_form_pill";
|
|
3
|
-
import Flex from "../../pb_flex/_flex";
|
|
1
|
+
import React, { useCallback, useContext, useMemo, useRef } from "react";
|
|
4
2
|
import Body from "../../pb_body/_body";
|
|
3
|
+
import Draggable from "../../pb_draggable/_draggable";
|
|
4
|
+
import { DraggableContext, DraggableProvider } from "../../pb_draggable/context";
|
|
5
|
+
import Flex from "../../pb_flex/_flex";
|
|
6
|
+
import FormPill from "../../pb_form_pill/_form_pill";
|
|
7
|
+
import Icon from "../../pb_icon/_icon";
|
|
8
|
+
import { noop } from "../../utilities/object";
|
|
5
9
|
import { GenericObject } from "../../types";
|
|
6
|
-
import DropdownContext
|
|
7
|
-
from "../
|
|
10
|
+
import DropdownContext from "../context";
|
|
11
|
+
import usePillDropIndicator from "../utilities/usePillDropIndicator";
|
|
12
|
+
|
|
8
13
|
type MultiSelectTriggerDisplayProps = {
|
|
9
14
|
autocomplete?: boolean;
|
|
10
15
|
selected: GenericObject[];
|
|
@@ -12,52 +17,352 @@ type MultiSelectTriggerDisplayProps = {
|
|
|
12
17
|
dark?: boolean;
|
|
13
18
|
};
|
|
14
19
|
|
|
20
|
+
type DropdownPillProps = {
|
|
21
|
+
dark?: boolean;
|
|
22
|
+
formPillProps?: GenericObject;
|
|
23
|
+
index: number;
|
|
24
|
+
onKeyboardReorder?: (direction: "left" | "right") => void;
|
|
25
|
+
onRemove: (event: React.MouseEvent | React.KeyboardEvent) => void;
|
|
26
|
+
option: GenericObject;
|
|
27
|
+
externalDragHandle?: boolean;
|
|
28
|
+
showPillIndex?: boolean;
|
|
29
|
+
totalCount: number;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const DropdownPill = ({
|
|
33
|
+
dark = false,
|
|
34
|
+
formPillProps,
|
|
35
|
+
index,
|
|
36
|
+
onKeyboardReorder,
|
|
37
|
+
onRemove,
|
|
38
|
+
option,
|
|
39
|
+
externalDragHandle = false,
|
|
40
|
+
showPillIndex = false,
|
|
41
|
+
totalCount,
|
|
42
|
+
}: DropdownPillProps) => {
|
|
43
|
+
const baseLabel = option.label || "";
|
|
44
|
+
const label = showPillIndex ? `${index + 1}. ${baseLabel}` : baseLabel;
|
|
45
|
+
const positionLabel = totalCount > 0
|
|
46
|
+
? `${baseLabel}, position ${index + 1} of ${totalCount}, press Left or Right Arrow keys to reorder`
|
|
47
|
+
: baseLabel;
|
|
48
|
+
const pillIcon = !externalDragHandle && onKeyboardReorder ? "grip-dots-vertical" : "";
|
|
49
|
+
|
|
50
|
+
const handleKeyDown = (event: React.KeyboardEvent<HTMLDivElement>) => {
|
|
51
|
+
if (event.key === "Backspace" || event.key === "Delete") {
|
|
52
|
+
event.preventDefault();
|
|
53
|
+
event.stopPropagation();
|
|
54
|
+
onRemove(event);
|
|
55
|
+
} else if (onKeyboardReorder && (event.key === "ArrowLeft" || event.key === "ArrowRight")) {
|
|
56
|
+
event.preventDefault();
|
|
57
|
+
event.stopPropagation();
|
|
58
|
+
const pill = event.currentTarget;
|
|
59
|
+
onKeyboardReorder(event.key === "ArrowLeft" ? "left" : "right");
|
|
60
|
+
requestAnimationFrame(() => pill.focus());
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
const pill = (
|
|
65
|
+
<FormPill
|
|
66
|
+
dark={dark}
|
|
67
|
+
htmlOptions={{ "aria-label": positionLabel, onKeyDown: handleKeyDown }}
|
|
68
|
+
icon={pillIcon}
|
|
69
|
+
marginRight="xs"
|
|
70
|
+
name=""
|
|
71
|
+
onClick={(event) => {
|
|
72
|
+
event.stopPropagation();
|
|
73
|
+
onRemove(event);
|
|
74
|
+
}}
|
|
75
|
+
tabIndex={0}
|
|
76
|
+
text={label}
|
|
77
|
+
{...formPillProps}
|
|
78
|
+
/>
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
if (!externalDragHandle) return pill;
|
|
82
|
+
|
|
83
|
+
return (
|
|
84
|
+
<span className="pb_dropdown_pill_with_handle">
|
|
85
|
+
<span
|
|
86
|
+
aria-hidden="true"
|
|
87
|
+
className="pb_draggable_handle pb_dropdown_external_drag_handle"
|
|
88
|
+
onMouseDown={(event) => {
|
|
89
|
+
event.stopPropagation();
|
|
90
|
+
}}
|
|
91
|
+
>
|
|
92
|
+
<Body color="light">
|
|
93
|
+
<Icon
|
|
94
|
+
icon="grip-dots-vertical"
|
|
95
|
+
verticalAlign="middle"
|
|
96
|
+
/>
|
|
97
|
+
</Body>
|
|
98
|
+
</span>
|
|
99
|
+
{pill}
|
|
100
|
+
</span>
|
|
101
|
+
);
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
type DraggablePillListProps = {
|
|
105
|
+
dark?: boolean;
|
|
106
|
+
formPillProps?: GenericObject;
|
|
107
|
+
getItemId: (item: GenericObject) => string;
|
|
108
|
+
onRemove: (option: GenericObject) => void;
|
|
109
|
+
onReorder: (next: GenericObject[]) => void;
|
|
110
|
+
pendingReorderRef: React.MutableRefObject<GenericObject[] | null>;
|
|
111
|
+
externalDragHandle: boolean;
|
|
112
|
+
selected: GenericObject[];
|
|
113
|
+
showPillIndex: boolean;
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
const DraggablePillList = ({
|
|
117
|
+
dark,
|
|
118
|
+
formPillProps,
|
|
119
|
+
getItemId,
|
|
120
|
+
onRemove,
|
|
121
|
+
onReorder,
|
|
122
|
+
pendingReorderRef,
|
|
123
|
+
externalDragHandle,
|
|
124
|
+
selected,
|
|
125
|
+
showPillIndex,
|
|
126
|
+
}: DraggablePillListProps) => {
|
|
127
|
+
const { isDragging: dragId } = DraggableContext();
|
|
128
|
+
const dropTarget = usePillDropIndicator(
|
|
129
|
+
dragId,
|
|
130
|
+
selected,
|
|
131
|
+
getItemId,
|
|
132
|
+
pendingReorderRef,
|
|
133
|
+
);
|
|
134
|
+
|
|
135
|
+
const getDropLineClassName = (index: number): string | undefined => {
|
|
136
|
+
if (dropTarget?.index !== index) return undefined;
|
|
137
|
+
return dropTarget.after ? "pb_dropdown_drop_after" : "pb_dropdown_drop_before";
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
const handleKeyboardReorder = (index: number, direction: "left" | "right") => {
|
|
141
|
+
const newIndex = direction === "left" ? index - 1 : index + 1;
|
|
142
|
+
if (newIndex < 0 || newIndex >= selected.length) return;
|
|
143
|
+
|
|
144
|
+
const reordered = [...selected];
|
|
145
|
+
const [movedItem] = reordered.splice(index, 1);
|
|
146
|
+
reordered.splice(newIndex, 0, movedItem);
|
|
147
|
+
onReorder(reordered);
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
return (
|
|
151
|
+
<div
|
|
152
|
+
onClick={(event) => event.stopPropagation()}
|
|
153
|
+
onMouseDown={(event) => {
|
|
154
|
+
if ((event.target as HTMLElement | null)?.closest(".pb_form_pill_close")) return
|
|
155
|
+
event.stopPropagation()
|
|
156
|
+
}}
|
|
157
|
+
>
|
|
158
|
+
<Draggable.Container
|
|
159
|
+
className="pb_dropdown_draggable_pills"
|
|
160
|
+
container="dropdown-pills"
|
|
161
|
+
tag="div"
|
|
162
|
+
>
|
|
163
|
+
{selected.map((option, index) => (
|
|
164
|
+
<Draggable.Item
|
|
165
|
+
className={getDropLineClassName(index)}
|
|
166
|
+
container="dropdown-pills"
|
|
167
|
+
dragId={getItemId(option)}
|
|
168
|
+
handlePointerDrag={externalDragHandle}
|
|
169
|
+
key={getItemId(option)}
|
|
170
|
+
pointerDrag={!externalDragHandle}
|
|
171
|
+
tag="span"
|
|
172
|
+
>
|
|
173
|
+
<DropdownPill
|
|
174
|
+
dark={dark}
|
|
175
|
+
externalDragHandle={externalDragHandle}
|
|
176
|
+
formPillProps={formPillProps}
|
|
177
|
+
index={index}
|
|
178
|
+
onKeyboardReorder={(direction) => handleKeyboardReorder(index, direction)}
|
|
179
|
+
onRemove={() => onRemove(option)}
|
|
180
|
+
option={option}
|
|
181
|
+
showPillIndex={showPillIndex}
|
|
182
|
+
totalCount={selected.length}
|
|
183
|
+
/>
|
|
184
|
+
</Draggable.Item>
|
|
185
|
+
))}
|
|
186
|
+
</Draggable.Container>
|
|
187
|
+
</div>
|
|
188
|
+
);
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
type DraggablePillsStripProps = {
|
|
192
|
+
dark?: boolean;
|
|
193
|
+
formPillProps?: GenericObject;
|
|
194
|
+
getItemId: (item: GenericObject) => string;
|
|
195
|
+
onRemove: (option: GenericObject) => void;
|
|
196
|
+
onReorder: (next: GenericObject[]) => void;
|
|
197
|
+
externalDragHandle: boolean;
|
|
198
|
+
selected: GenericObject[];
|
|
199
|
+
showPillIndex: boolean;
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
const buildSelectedPillsKey = (value: GenericObject[], getItemId: (item: GenericObject) => string): string => (
|
|
203
|
+
value.map((item) => `${getItemId(item)}:${item.label ?? ""}`).join("|")
|
|
204
|
+
);
|
|
205
|
+
|
|
206
|
+
const formPillPropsKey = (formPillProps?: GenericObject): string => (
|
|
207
|
+
JSON.stringify(formPillProps ?? {})
|
|
208
|
+
);
|
|
209
|
+
|
|
210
|
+
const DraggablePillsStrip = React.memo(function DraggablePillsStrip({
|
|
211
|
+
dark,
|
|
212
|
+
formPillProps,
|
|
213
|
+
getItemId,
|
|
214
|
+
onRemove,
|
|
215
|
+
onReorder,
|
|
216
|
+
externalDragHandle,
|
|
217
|
+
selected,
|
|
218
|
+
showPillIndex,
|
|
219
|
+
}: DraggablePillsStripProps) {
|
|
220
|
+
const pendingReorderRef = useRef<GenericObject[] | null>(null);
|
|
221
|
+
const selectedRef = useRef(selected);
|
|
222
|
+
selectedRef.current = selected;
|
|
223
|
+
|
|
224
|
+
const initialItems = useMemo(
|
|
225
|
+
() => selected.map((item) => ({
|
|
226
|
+
id: getItemId(item),
|
|
227
|
+
container: "dropdown-pills",
|
|
228
|
+
label: item.label,
|
|
229
|
+
value: item.value,
|
|
230
|
+
})),
|
|
231
|
+
[getItemId, selected],
|
|
232
|
+
);
|
|
233
|
+
|
|
234
|
+
const commitReorder = useCallback(() => {
|
|
235
|
+
if (!pendingReorderRef.current) return;
|
|
236
|
+
|
|
237
|
+
const currentOrder = selectedRef.current.map((item) => getItemId(item)).join(",");
|
|
238
|
+
const newOrder = pendingReorderRef.current.map((item) => getItemId(item)).join(",");
|
|
239
|
+
|
|
240
|
+
if (currentOrder !== newOrder) {
|
|
241
|
+
onReorder(pendingReorderRef.current);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
pendingReorderRef.current = null;
|
|
245
|
+
}, [getItemId, onReorder]);
|
|
246
|
+
|
|
247
|
+
return (
|
|
248
|
+
<DraggableProvider
|
|
249
|
+
dropZone={{ type: "ghost" }}
|
|
250
|
+
initialItems={initialItems}
|
|
251
|
+
onDragEnd={commitReorder}
|
|
252
|
+
onDrop={commitReorder}
|
|
253
|
+
onReorder={noop}
|
|
254
|
+
>
|
|
255
|
+
<DraggablePillList
|
|
256
|
+
dark={dark}
|
|
257
|
+
externalDragHandle={externalDragHandle}
|
|
258
|
+
formPillProps={formPillProps}
|
|
259
|
+
getItemId={getItemId}
|
|
260
|
+
onRemove={onRemove}
|
|
261
|
+
onReorder={onReorder}
|
|
262
|
+
pendingReorderRef={pendingReorderRef}
|
|
263
|
+
selected={selected}
|
|
264
|
+
showPillIndex={showPillIndex}
|
|
265
|
+
/>
|
|
266
|
+
</DraggableProvider>
|
|
267
|
+
);
|
|
268
|
+
}, (prevProps, nextProps) => (
|
|
269
|
+
prevProps.dark === nextProps.dark
|
|
270
|
+
&& prevProps.externalDragHandle === nextProps.externalDragHandle
|
|
271
|
+
&& prevProps.showPillIndex === nextProps.showPillIndex
|
|
272
|
+
&& formPillPropsKey(prevProps.formPillProps) === formPillPropsKey(nextProps.formPillProps)
|
|
273
|
+
&& buildSelectedPillsKey(prevProps.selected, prevProps.getItemId)
|
|
274
|
+
=== buildSelectedPillsKey(nextProps.selected, nextProps.getItemId)
|
|
275
|
+
));
|
|
276
|
+
|
|
15
277
|
const MultiSelectTriggerDisplay = ({
|
|
16
278
|
autocomplete,
|
|
17
279
|
selected,
|
|
18
280
|
placeholder,
|
|
19
281
|
dark = false,
|
|
20
282
|
}: MultiSelectTriggerDisplayProps) => {
|
|
283
|
+
const {
|
|
284
|
+
disabled,
|
|
285
|
+
enablePillReorder,
|
|
286
|
+
formPillProps,
|
|
287
|
+
getOptionValue,
|
|
288
|
+
handleSelectionChange,
|
|
289
|
+
isControlled,
|
|
290
|
+
isSameOption,
|
|
291
|
+
optionKey,
|
|
292
|
+
externalDragHandle = false,
|
|
293
|
+
setSelected,
|
|
294
|
+
showPillIndex = false,
|
|
295
|
+
} = useContext(DropdownContext);
|
|
21
296
|
|
|
22
|
-
const
|
|
297
|
+
const getItemId = useCallback((option: GenericObject) => (
|
|
298
|
+
optionKey ? optionKey(option) : String(
|
|
299
|
+
getOptionValue ? getOptionValue(option) : option.id ?? option.value ?? option.label
|
|
300
|
+
)
|
|
301
|
+
), [getOptionValue, optionKey]);
|
|
23
302
|
|
|
24
303
|
if (selected.length === 0) {
|
|
25
304
|
if (autocomplete) return null;
|
|
26
305
|
return (
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
)
|
|
306
|
+
<Body
|
|
307
|
+
color="lighter"
|
|
308
|
+
dark={dark}
|
|
309
|
+
text={placeholder ? placeholder : "Select..."}
|
|
310
|
+
/>
|
|
311
|
+
);
|
|
33
312
|
}
|
|
34
313
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
setSelected((prev: GenericObject[]) => {
|
|
43
|
-
const next = prev.filter((item) => !isSameOption(item, option));
|
|
314
|
+
const applySelection = (next: GenericObject[]) => {
|
|
315
|
+
if (isControlled || getOptionValue) {
|
|
316
|
+
setSelected(next);
|
|
317
|
+
handleSelectionChange(next);
|
|
318
|
+
return;
|
|
319
|
+
}
|
|
320
|
+
setSelected(() => {
|
|
44
321
|
handleSelectionChange && handleSelectionChange(next);
|
|
45
322
|
return next;
|
|
46
323
|
});
|
|
47
|
-
|
|
324
|
+
};
|
|
325
|
+
|
|
326
|
+
const handleRemoveIconClick = (option: GenericObject) => {
|
|
327
|
+
if (disabled) return;
|
|
328
|
+
applySelection(selected.filter((item) => !isSameOption(item, option)));
|
|
329
|
+
};
|
|
330
|
+
|
|
331
|
+
const handleReorder = (next: GenericObject[]) => {
|
|
332
|
+
if (disabled) return;
|
|
333
|
+
applySelection(next);
|
|
334
|
+
};
|
|
335
|
+
|
|
336
|
+
if (enablePillReorder && !disabled) {
|
|
337
|
+
return (
|
|
338
|
+
<DraggablePillsStrip
|
|
339
|
+
dark={dark}
|
|
340
|
+
externalDragHandle={Boolean(externalDragHandle)}
|
|
341
|
+
formPillProps={formPillProps}
|
|
342
|
+
getItemId={getItemId}
|
|
343
|
+
onRemove={handleRemoveIconClick}
|
|
344
|
+
onReorder={handleReorder}
|
|
345
|
+
selected={selected}
|
|
346
|
+
showPillIndex={Boolean(showPillIndex)}
|
|
347
|
+
/>
|
|
348
|
+
);
|
|
349
|
+
}
|
|
48
350
|
|
|
49
351
|
return (
|
|
50
352
|
<Flex wrap>
|
|
51
353
|
{selected.map((option, i) => (
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
354
|
+
<FormPill
|
|
355
|
+
dark={dark}
|
|
356
|
+
key={getOptionValue ? getOptionValue(option) : i}
|
|
357
|
+
marginRight="xs"
|
|
358
|
+
onClick={(e) => {
|
|
359
|
+
e.stopPropagation();
|
|
360
|
+
handleRemoveIconClick(option);
|
|
361
|
+
}}
|
|
362
|
+
tabIndex={0}
|
|
363
|
+
text={showPillIndex ? `${i + 1}. ${option.label}` : option.label}
|
|
364
|
+
{...formPillProps}
|
|
365
|
+
/>
|
|
61
366
|
))}
|
|
62
367
|
</Flex>
|
|
63
368
|
);
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { GenericObject } from "../../types";
|
|
2
|
+
|
|
3
|
+
export const matchesOptionExactly = (option: GenericObject | undefined, query: string): boolean => {
|
|
4
|
+
const normalizedQuery = query.trim().toLowerCase();
|
|
5
|
+
if (!option || !normalizedQuery) return false;
|
|
6
|
+
|
|
7
|
+
const optionValues = [option.label, option.value, option.id];
|
|
8
|
+
return optionValues.some((value) => (
|
|
9
|
+
value != null && String(value).trim().toLowerCase() === normalizedQuery
|
|
10
|
+
));
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export const getCreationOption = (query: string, options: GenericObject[]): GenericObject | null => {
|
|
14
|
+
const text = query.trim();
|
|
15
|
+
if (!text) return null;
|
|
16
|
+
|
|
17
|
+
const optionExists = options.some((option) => matchesOptionExactly(option, text));
|
|
18
|
+
if (optionExists) return null;
|
|
19
|
+
|
|
20
|
+
return { id: text, label: text, value: text, __isNew__: true };
|
|
21
|
+
};
|