import clsx from 'clsx'; import type { ClipboardEvent } from 'react'; import React, { Component } from 'react'; import { ContactDropdownList } from '../ContactDropdownList'; import { RemoveButton } from '../RemoveButton'; import { SelectedRecipients } from './SelectedRecipients'; import { focusCampo } from './focusCampo'; import i18n from './i18n'; import styles from './styles.scss'; type RecipientsInputProps = { className?: string; recipientsClassName?: string; label?: string; placeholder?: string; searchContactList: { name: string; entityType: string; phoneType: string; phoneNumber: string; }[]; recipient?: { phoneNumber: string; name?: string; }; recipients?: { phoneNumber: string; name?: string; }[]; value: string; onChange: (...args: any[]) => any; onClean: (...args: any[]) => any; addToRecipients: (...args: any[]) => any; removeFromRecipients: (...args: any[]) => any; formatContactPhone: (...args: any[]) => any; detectPhoneNumbers: (...args: any[]) => any; searchContact?: (...args: any[]) => any; titleEnabled?: boolean; autoFocus?: boolean; currentLocale: string; multiple?: boolean; inputRef?: (...args: any[]) => any; phoneTypeRenderer?: (...args: any[]) => any; phoneSourceNameRenderer?: (...args: any[]) => any; contactInfoRenderer?: (...args: any[]) => any; contactPhoneRenderer?: (...args: any[]) => any; useRCUI?: boolean; isLastInputFromDialpad?: boolean; }; type RecipientsInputState = { value: any; lastInputTimestamp: number; isFocusOnInput: boolean; selectedContactIndex: number; scrollDirection: null; }; class RecipientsInput extends Component< RecipientsInputProps, RecipientsInputState > { setSelectedIndex: (index: any) => void; scrollOperation: (direction: any) => void; addSelectedContactIndex: () => void; reduceSelectedContactIndex: () => void; isSplitter: (ev: React.KeyboardEvent) => boolean; handleHotKey: (ev: React.KeyboardEvent) => void; listRef: any; inputRef: any; static defaultProps: Partial = { recipients: [], searchContact: () => null, }; constructor(props: any) { super(props); this.state = { value: props.value, lastInputTimestamp: 0, isFocusOnInput: false, selectedContactIndex: 0, scrollDirection: null, }; this.setSelectedIndex = (index) => { this.setState({ selectedContactIndex: index, scrollDirection: null, }); }; this.scrollOperation = (direction) => { if (direction === 'ArrowDown' || direction === 'ArrowUp') { this.setState({ scrollDirection: direction, }); } }; this.addSelectedContactIndex = () => { const { length } = this.props.searchContactList; if (this.state.selectedContactIndex >= length - 1) { this.setState({ selectedContactIndex: length - 1, }); } else { this.setState((preState) => ({ selectedContactIndex: preState.selectedContactIndex + 1, })); } }; this.reduceSelectedContactIndex = () => { if (this.state.selectedContactIndex > 0) { this.setState((preState) => ({ selectedContactIndex: preState.selectedContactIndex - 1, })); } else { this.setState({ selectedContactIndex: 0, }); } }; this.isSplitter = (ev: React.KeyboardEvent) => { if ( ev.key === ',' || ev.key === ';' || ev.key === 'Enter' || (ev.key === 'Unidentified' && // for Safari (FF cannot rely on keyCode...) (ev.keyCode === 186 || // semicolon ev.keyCode === 188 || // comma ev.keyCode === 13)) // enter ) { return true; } return false; }; // using React SyntheticEvent to deal with cross browser issue this.handleHotKey = (ev: React.KeyboardEvent) => { if (this.state.isFocusOnInput && this.state.value.length >= 3) { if (ev.key === 'ArrowUp') { this.reduceSelectedContactIndex(); this.scrollOperation(ev.key); } else if (ev.key === 'ArrowDown') { this.addSelectedContactIndex(); this.scrollOperation(ev.key); } } else { this.setState({ selectedContactIndex: 0, }); } if (this.isSplitter(ev)) { ev.preventDefault(); const trimmedValue = this.state.value.trim(); const relatedContactList = this.state.value.length >= 3 ? this.props.searchContactList : []; const currentSelected = relatedContactList[this.state.selectedContactIndex]; if (trimmedValue.length === 0 && !currentSelected) { return; } if (currentSelected && ev.key === 'Enter') { this.props.addToRecipients({ name: currentSelected.name, phoneNumber: currentSelected.phoneNumber, }); } else { this.props.addToRecipients({ name: trimmedValue.replace(',', ''), phoneNumber: trimmedValue.replace(',', ''), }); } this.setState({ value: '' }); } }; } onInputKeyUp = (ev: React.KeyboardEvent) => { // @ts-expect-error TS(2722): Cannot invoke an object which is possibly 'undefin... Remove this comment to see the full error message this.props.searchContact(ev.currentTarget.value); this.setState({ isFocusOnInput: true, }); }; onInputFocus = () => { this.setState({ isFocusOnInput: true, }); }; onInputChange = (ev: React.ChangeEvent) => { const { value } = ev.currentTarget; const lastInputTimestamp = Date.now(); this.setState({ value, lastInputTimestamp }, () => { this.props.onChange(value); }); if (this.listRef) { this.listRef.scrollTop = 0; } }; onPaste = async (ev: ClipboardEvent) => { if ( // @ts-ignore this.props.detectPhoneNumbers && ev.clipboardData && ev.clipboardData.getData ) { ev.preventDefault(); const pastedText = ev.clipboardData.getData('text/plain'); const result = await this.props.detectPhoneNumbers(pastedText); const currentVal = this.state.value || ''; if (!result) { const newVal = `${currentVal}${pastedText.replace(/\n/g, ' ')}`; this.setState({ value: newVal }, () => { this.props.onChange(newVal); }); } } }; onClean = () => { this.setState({ value: '' }); this.props.onClean(); }; // @ts-expect-error TS(4114): This member must have an 'override' modifier becau... Remove this comment to see the full error message UNSAFE_componentWillReceiveProps(nextProps: any) { const isNotEditing = !this.state.isFocusOnInput || Date.now() - this.state.lastInputTimestamp > 2000; if ( isNotEditing && nextProps.value !== undefined && nextProps.value !== this.props.value ) { this.setState({ value: nextProps.value }, () => { if (this.inputRef) { focusCampo(this.inputRef); } }); // @ts-expect-error TS(2722): Cannot invoke an object which is possibly 'undefin... Remove this comment to see the full error message this.props.searchContact(nextProps.value); } } // @ts-expect-error TS(4114): This member must have an 'override' modifier becau... Remove this comment to see the full error message componentDidMount() { // @ts-expect-error TS(2722): Cannot invoke an object which is possibly 'undefin... Remove this comment to see the full error message this.props.searchContact(this.props.value); window.addEventListener('click', this.clickHandler); } // @ts-expect-error TS(4114): This member must have an 'override' modifier becau... Remove this comment to see the full error message componentWillUnmount() { window.removeEventListener('click', this.clickHandler); } clickHandler = (ev: MouseEvent) => { if (this.listRef && this.listRef.contains(ev.target)) { return; } if (this.inputRef && this.inputRef.contains(ev.target)) { this.setState({ isFocusOnInput: true, }); return; } this.setState({ isFocusOnInput: false, }); }; _addToRecipients = (item: any) => { this.setState({ value: '', isFocusOnInput: false }); this.props.addToRecipients(item); }; setInputRef = (ref: any) => { this.inputRef = ref; if (typeof this.props.inputRef === 'function') { this.props.inputRef(ref); } }; // @ts-expect-error TS(4114): This member must have an 'override' modifier becau... Remove this comment to see the full error message render() { const { className, contactInfoRenderer, contactPhoneRenderer, currentLocale, formatContactPhone, isLastInputFromDialpad, label, multiple, phoneSourceNameRenderer, phoneTypeRenderer, placeholder, recipient, recipients, recipientsClassName, removeFromRecipients, searchContactList, titleEnabled, useRCUI, } = this.props; const { value, isFocusOnInput, scrollDirection, selectedContactIndex } = this.state; // TODO: a temporary fix for rendering slower search result. const relatedContactList = value.length >= 3 ? searchContactList.slice(0, 50) : []; const labelString = label === undefined ? `${i18n.getString('to', currentLocale)}:` : label; const labelEl = ( // eslint-disable-next-line jsx-a11y/label-has-associated-control ); const toNumberInput = !multiple && recipient ? null : (
0} />
); return (
{labelEl}
{toNumberInput}
{ this.listRef = ref; }} // @ts-expect-error TS(2769): No overload matches this call. scrollDirection={scrollDirection} selectedIndex={selectedContactIndex} setSelectedIndex={this.setSelectedIndex} addToRecipients={this._addToRecipients} items={relatedContactList} formatContactPhone={formatContactPhone} visibility={isFocusOnInput && !isLastInputFromDialpad} titleEnabled={titleEnabled} phoneTypeRenderer={phoneTypeRenderer} phoneSourceNameRenderer={phoneSourceNameRenderer} contactInfoRenderer={contactInfoRenderer} contactPhoneRenderer={contactPhoneRenderer} />
); } } export default RecipientsInput;