({extraKey, hideErrorMsg, keys, recipients, onChangeRecipients})
| 54 | } |
| 55 | |
| 56 | export function RecipientInput({extraKey, hideErrorMsg, keys, recipients, onChangeRecipients}) { |
| 57 | const idRef = useRef(getUUID()); |
| 58 | |
| 59 | const tags = useMemo( |
| 60 | () => recipients.map(r => recipientToTag(r, keys, extraKey)), |
| 61 | [recipients, keys, extraKey] |
| 62 | ); |
| 63 | |
| 64 | const updateParentRecipients = nextTags => onChangeRecipients(nextTags.map(t => tagToRecipient(t, keys))); |
| 65 | |
| 66 | const onDelete = tagIndex => updateParentRecipients(tags.filter((_, i) => i !== tagIndex)); |
| 67 | |
| 68 | const onAddition = newTag => { |
| 69 | if (!isValidAddress(newTag.id)) { |
| 70 | return; |
| 71 | } |
| 72 | updateParentRecipients([...tags, newTag]); |
| 73 | // <ReactTags> exposes neither its input element nor a focus API, so we |
| 74 | // re-focus by querying for it after the parent state update lands. |
| 75 | setTimeout(() => { |
| 76 | const inputElem = document.querySelector(`[id="${idRef.current}"] .tag-input-field`); |
| 77 | inputElem?.focus(); |
| 78 | }, 0); |
| 79 | }; |
| 80 | |
| 81 | const onFilterSuggestions = (textInputValue, possibleSuggestionsArray) => { |
| 82 | const lowerCaseQuery = textInputValue.toLowerCase(); |
| 83 | return possibleSuggestionsArray |
| 84 | .filter(suggestion => suggestion.text.toLowerCase().includes(lowerCaseQuery)) |
| 85 | .slice(0, 10); |
| 86 | }; |
| 87 | |
| 88 | const suggestions = keys |
| 89 | .filter(key => !tags.find(tag => tag.id === key.email)) |
| 90 | .map(key => ({ |
| 91 | id: key.email, |
| 92 | text: `${key.userId} - ${key.keyId}`, |
| 93 | })); |
| 94 | |
| 95 | const renderSuggestion = ({text}, query) => { |
| 96 | query = query.trim(); |
| 97 | let html = text.replaceAll(query, `<mark>${query}</mark>`); |
| 98 | html = encodeHTML(html); |
| 99 | html = html.replaceAll('<mark>', '<mark>').replaceAll('</mark>', '</mark>'); // decode mark tag |
| 100 | return <span dangerouslySetInnerHTML={{__html: html}} />; |
| 101 | }; |
| 102 | |
| 103 | const showNotFoundAlert = !hideErrorMsg && !extraKey && hasAnyUnresolvedRecipient(recipients, keys); |
| 104 | const showExtraKeyAlert = extraKey && !recipients.some(r => r.lookupPending); |
| 105 | |
| 106 | return ( |
| 107 | <div id={idRef.current} className="mb-0"> |
| 108 | <ReactTags |
| 109 | tags={tags} |
| 110 | suggestions={suggestions} |
| 111 | renderSuggestion = {renderSuggestion} |
| 112 | handleDelete={onDelete} |
| 113 | handleAddition={onAddition} |
nothing calls this directly
no test coverage detected