Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

🐛 fix(autocomplete): tags not generated for preselected options #1403

50 changes: 22 additions & 28 deletions client/src/app/components/Autocomplete.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,21 +42,13 @@ export const Autocomplete: React.FC<IAutocompleteProps> = ({
}) => {
const [inputValue, setInputValue] = useState(searchString);
const [menuIsOpen, setMenuIsOpen] = useState(false);
const [currentChips, setCurrentChips] = useState<Set<string>>(
new Set(selections)
);
const [hint, setHint] = useState("");
const [menuItems, setMenuItems] = useState<React.ReactElement[]>([]);

/** refs used to detect when clicks occur inside vs outside of the textInputGroup and menu popper */
const menuRef = useRef<HTMLDivElement>(null);
const searchInputRef = useRef<HTMLInputElement>(null);

React.useEffect(() => {
onChange([...currentChips]);
buildMenu();
}, [currentChips]);

React.useEffect(() => {
buildMenu();
}, [options]);
Expand All @@ -67,7 +59,7 @@ export const Autocomplete: React.FC<IAutocompleteProps> = ({
.filter(
(item: string, index: number, arr: string[]) =>
arr.indexOf(item) === index &&
!currentChips.has(item) &&
!selections.includes(item) &&
(!inputValue || item.toLowerCase().includes(inputValue.toLowerCase()))
)
.map((currentValue, index) => (
Expand Down Expand Up @@ -127,33 +119,32 @@ export const Autocomplete: React.FC<IAutocompleteProps> = ({
buildMenu();
};

/** callback for removing a chip from the chip selections */
const deleteChip = (chipToDelete: string) => {
const newChips = new Set(currentChips);
newChips.delete(chipToDelete);
setCurrentChips(newChips);
/** callback for removing a selection */
const deleteSelection = (selectionToDelete: string) => {
onChange(selections.filter((s) => s !== selectionToDelete));
};

/** add the given string as a chip in the chip group and clear the input */
const addChip = (newChipText: string) => {
/** add the given string as a selection */
const addSelection = (newSelectionText: string) => {
if (!allowUserOptions) {
const matchingOption = options.find(
(o) => o.toLowerCase() === (hint || newChipText).toLowerCase()
(o) => o.toLowerCase() === (hint || newSelectionText).toLowerCase()
);
if (!matchingOption) {
console.log({ matchingOption, newSelectionText, options });
if (!matchingOption || selections.includes(matchingOption)) {
return;
}
newChipText = matchingOption;
newSelectionText = matchingOption;
}
setCurrentChips(new Set([...currentChips, newChipText]));
onChange([...selections, newSelectionText]);
setInputValue("");
setMenuIsOpen(false);
};

/** add the current input value as a chip */
/** add the current input value as a selection */
const handleEnter = () => {
if (inputValue.length) {
addChip(inputValue);
addSelection(inputValue);
}
};

Expand Down Expand Up @@ -216,13 +207,13 @@ export const Autocomplete: React.FC<IAutocompleteProps> = ({
closeMenu && setMenuIsOpen(false);
};

/** add the text of the selected item as a new chip */
/** add the text of the selected menu item to the selected items */
const onSelect = (event?: React.MouseEvent<Element, MouseEvent>) => {
if (!event) {
return;
}
const selectedText = (event.target as HTMLElement).innerText;
addChip(selectedText);
addSelection(selectedText);
event.stopPropagation();
focusTextInput(true);
};
Expand Down Expand Up @@ -301,10 +292,13 @@ export const Autocomplete: React.FC<IAutocompleteProps> = ({
</FlexItem>
<FlexItem key="chips">
<Flex spaceItems={{ default: "spaceItemsXs" }}>
{Array.from(currentChips).map((currentChip) => (
<FlexItem key={currentChip}>
<Label color={labelColor} onClose={() => deleteChip(currentChip)}>
{currentChip}
{selections.map((currentSelection) => (
<FlexItem key={currentSelection}>
<Label
color={labelColor}
onClose={() => deleteSelection(currentSelection)}
>
{currentSelection}
</Label>
</FlexItem>
))}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,13 +106,12 @@ export const ApplicationForm: React.FC<ApplicationFormProps> = ({
}
}, []);

const tagOptions =
tags?.map((tag) => {
return {
value: tag.name,
toString: () => tag.name,
};
}) || [];
const tagOptions = new Set(
(tags || []).reduce<string[]>(
(acc, curr) => (!curr.source ? [...acc, curr.name] : acc),
[]
)
);

const getBinaryInitialValue = (
application: Application | null,
Expand Down Expand Up @@ -385,7 +384,7 @@ export const ApplicationForm: React.FC<ApplicationFormProps> = ({
];

const getTagRef = (tagName: string) =>
tags?.find((tag) => tag.name === tagName);
Object.assign({ source: "" }, tags?.find((tag) => tag.name === tagName));

return (
<Form onSubmit={handleSubmit(onSubmit)}>
Expand Down Expand Up @@ -445,78 +444,34 @@ export const ApplicationForm: React.FC<ApplicationFormProps> = ({
name="tags"
label={t("terms.tags")}
fieldId="tags"
renderInput={({ field: { value, name, onChange } }) => (
<Autocomplete
noResultsMessage={t("message.noResultsFoundTitle")}
onChange={(selections) => {
onChange(
selections
.map((sel) => getTagRef(sel))
.filter((sel) => sel !== undefined) as TagRef[]
);
}}
options={tagOptions.map((o) => o.value)}
placeholderText={t("composed.selectMany", {
what: t("terms.tags").toLowerCase(),
})}
searchInputAriaLabel="tags-select-toggle"
selections={
value
.map(
(formTag) =>
tags?.find((tagRef) => tagRef.name === formTag.name)
)
.map((matchingTag) =>
matchingTag ? matchingTag.name : undefined
)
.filter((e) => e !== undefined) as string[]
}
/>
// <SimpleSelect
// maxHeight={DEFAULT_SELECT_MAX_HEIGHT}
// placeholderText={t("composed.selectMany", {
// what: t("terms.tags").toLowerCase(),
// })}
// id="tags-select"
// variant="typeaheadmulti"
// toggleId="tags-select-toggle"
// toggleAriaLabel="tags dropdown toggle"
// aria-label={name}
// value={value
// .map((formTag) =>
// tags?.find((tagRef) => tagRef.name === formTag.name)
// )
// .map((matchingTag) =>
// matchingTag
// ? {
// value: matchingTag.name,
// toString: () => matchingTag.name,
// }
// : undefined
// )
// .filter((e) => e !== undefined)}
// options={tagOptions}
// onChange={(selection) => {
// const selectionWithValue = selection.toString();

// const currentValue = value || [];
// const e = currentValue.find(
// (f) => f.name === selectionWithValue
// );
// if (e) {
// onChange(
// currentValue.filter((f) => f.name !== selectionWithValue)
// );
// } else {
// const tag = getTagRef(selectionWithValue);
// if (currentValue && typeof tag !== "undefined")
// onChange([...currentValue, tag]);
// }
// }}
// onClear={() => onChange([])}
// noResultsFoundText={t("message.noResultsFoundTitle")}
// />
)}
renderInput={({ field: { value, name, onChange } }) => {
const selections = value.reduce<string[]>(
(acc, curr) =>
curr.source === "" && tagOptions.has(curr.name)
? [...acc, curr.name]
: acc,
[]
);

return (
<Autocomplete
noResultsMessage={t("message.noResultsFoundTitle")}
onChange={(selections) => {
onChange(
selections
.map((sel) => getTagRef(sel))
.filter((sel) => sel !== undefined) as TagRef[]
);
}}
options={Array.from(tagOptions)}
placeholderText={t("composed.selectMany", {
what: t("terms.tags").toLowerCase(),
})}
searchInputAriaLabel="tags-select-toggle"
selections={selections}
/>
);
}}
/>
<HookFormPFGroupController
control={control}
Expand Down