communication-react-19
Version:
React library for building modern communication user experiences utilizing Azure Communication Services (React 19 compatible fork)
595 lines • 35.5 kB
JavaScript
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
import React, { useState, useCallback, useRef } from 'react';
import { useEffect, useMemo } from 'react';
import { useLocale } from '../../localization';
import { Announcer } from '../Announcer';
import { TextField } from '@fluentui/react';
import { isEnterKeyEventFromCompositionSession, nullToUndefined } from '../utils';
import { findMentionTagForSelection, findNewSelectionIndexForMention, findStringsDiffIndexes, getDisplayNameForMentionSuggestion, getValidatedIndexInRange, htmlStringForMentionSuggestion, textToTagParser, updateHTML } from './mentionTagUtils';
import { Caret } from 'textarea-caret-ts';
import { _MentionPopover } from '../MentionPopover';
import { useDebouncedCallback } from 'use-debounce';
const DEFAULT_MENTION_TRIGGER = '@';
/**
* @private
*/
export const TextFieldWithMention = (props) => {
const { textFieldProps, dataUiId, textValue, onChange, textFieldRef, onKeyDown, onEnterKeyDown, supportNewline, mentionLookupOptions } = props;
const inputBoxRef = useRef(null);
// Current suggestion list, provided by the callback
const [mentionSuggestions, setMentionSuggestions] = useState([]);
// Current suggestion list, provided by the callback
const [activeSuggestionIndex, setActiveSuggestionIndex] = useState(undefined);
// Index of the current trigger character in the text field
const [currentTriggerStartIndex, setCurrentTriggerStartIndex] = useState(-1);
const [inputTextValue, setInputTextValue] = useState('');
// Internal value for text value prop
const [internalTextValue, setInternalTextValue] = useState('');
const [tagsValue, setTagsValue] = useState([]);
// Index of the previous selection start in the text field
const [selectionStartValue, setSelectionStartValue] = useState();
// Index of the previous selection end in the text field
const [selectionEndValue, setSelectionEndValue] = useState();
// Boolean value to check if onMouseDown event should be handled during select as selection range
// for onMouseDown event is not updated yet and the selection range for mouse click/taps will be
// updated in onSelect event if needed.
const [shouldHandleOnMouseDownDuringSelect, setShouldHandleOnMouseDownDuringSelect] = useState(true);
// Boolean flag to check if mouse/touch move event should be handled
const [shouldHandleMoveEvent, setShouldHandleMoveEvent] = useState(false);
// Indexes of start of touch/mouse selection
const [interactionStartSelection, setInteractionStartSelection] = useState();
// Caret position in the text field
const [caretPosition, setCaretPosition] = useState(undefined);
// Index of where the caret is in the text field
const [caretIndex, setCaretIndex] = useState(undefined);
const localeStrings = useLocale().strings;
// Set mention suggestions
const updateMentionSuggestions = useCallback((suggestions) => {
setMentionSuggestions(suggestions);
}, [setMentionSuggestions]);
useEffect(() => {
setInternalTextValue(textValue);
// update mention suggestions before the next render cycle
updateMentionSuggestions([]);
}, [textValue, updateMentionSuggestions]);
// Parse the text and get the plain text version to display in the input box
useEffect(() => {
const trigger = (mentionLookupOptions === null || mentionLookupOptions === void 0 ? void 0 : mentionLookupOptions.trigger) || DEFAULT_MENTION_TRIGGER;
const parsedHTMLData = textToTagParser(internalTextValue, trigger);
setInputTextValue(parsedHTMLData.plainText);
setTagsValue(parsedHTMLData.tags);
updateMentionSuggestions([]);
}, [internalTextValue, mentionLookupOptions === null || mentionLookupOptions === void 0 ? void 0 : mentionLookupOptions.trigger, updateMentionSuggestions]);
useEffect(() => {
var _a;
// effect for caret index update
if (caretIndex === undefined || textFieldRef === undefined || (textFieldRef === null || textFieldRef === void 0 ? void 0 : textFieldRef.current) === undefined) {
return;
}
// get validated caret index between 0 and inputTextValue.length otherwise caret will be set to incorrect index
const updatedCaretIndex = getValidatedIndexInRange({
min: 0,
max: inputTextValue.length,
currentValue: caretIndex
});
(_a = textFieldRef === null || textFieldRef === void 0 ? void 0 : textFieldRef.current) === null || _a === void 0 ? void 0 : _a.setSelectionRange(updatedCaretIndex, updatedCaretIndex);
setSelectionStartValue(updatedCaretIndex);
setSelectionEndValue(updatedCaretIndex);
}, [caretIndex, inputTextValue, textFieldRef, setSelectionStartValue, setSelectionEndValue]);
const onSuggestionSelected = useCallback((suggestion) => {
var _a, _b, _c;
let selectionEnd = ((_a = textFieldRef === null || textFieldRef === void 0 ? void 0 : textFieldRef.current) === null || _a === void 0 ? void 0 : _a.selectionEnd) || -1;
if (selectionEnd < 0) {
selectionEnd = 0;
}
else if (selectionEnd > inputTextValue.length) {
selectionEnd = inputTextValue.length;
}
const oldPlainText = inputTextValue;
const mention = htmlStringForMentionSuggestion(suggestion, localeStrings);
// update plain text with the mention html text
const triggerText = (_b = mentionLookupOptions === null || mentionLookupOptions === void 0 ? void 0 : mentionLookupOptions.trigger) !== null && _b !== void 0 ? _b : DEFAULT_MENTION_TRIGGER;
// update html text with updated plain text
const updatedContent = updateHTML({
htmlText: internalTextValue,
oldPlainText,
tags: tagsValue,
startIndex: currentTriggerStartIndex,
oldPlainTextEndIndex: selectionEnd,
change: mention,
mentionTrigger: triggerText
});
setInternalTextValue(updatedContent.updatedHTML);
const displayName = getDisplayNameForMentionSuggestion(suggestion, localeStrings);
const newCaretIndex = currentTriggerStartIndex + displayName.length + triggerText.length;
// move the caret in the text field to the end of the mention plain text
setCaretIndex(newCaretIndex);
setSelectionEndValue(newCaretIndex);
setSelectionStartValue(newCaretIndex);
setCurrentTriggerStartIndex(-1);
updateMentionSuggestions([]);
// set focus back to text field
(_c = textFieldRef === null || textFieldRef === void 0 ? void 0 : textFieldRef.current) === null || _c === void 0 ? void 0 : _c.focus();
setActiveSuggestionIndex(undefined);
onChange && onChange(undefined, updatedContent.updatedHTML);
}, [
textFieldRef,
inputTextValue,
currentTriggerStartIndex,
mentionLookupOptions === null || mentionLookupOptions === void 0 ? void 0 : mentionLookupOptions.trigger,
onChange,
internalTextValue,
tagsValue,
updateMentionSuggestions,
localeStrings
]);
const onTextFieldKeyDown = useCallback((ev) => {
// caretIndex should be set to undefined when the user is typing
setCaretIndex(undefined);
// shouldHandleOnMouseDownDuringSelect should be set to false after the last mouse down event.
// it shouldn't be updated in onMouseUp
// as onMouseUp can be triggered before or after onSelect event
// because its order depends on mouse events not selection.
setShouldHandleOnMouseDownDuringSelect(false);
if (isEnterKeyEventFromCompositionSession(ev.nativeEvent)) {
return;
}
let isActiveSuggestionIndexUpdated = false;
if (mentionSuggestions.length > 0) {
if (ev.key === 'ArrowUp') {
ev.preventDefault();
const newActiveIndex = activeSuggestionIndex === undefined
? mentionSuggestions.length - 1
: Math.max(activeSuggestionIndex - 1, 0);
setActiveSuggestionIndex(newActiveIndex);
isActiveSuggestionIndexUpdated = true;
}
else if (ev.key === 'ArrowDown') {
ev.preventDefault();
const newActiveIndex = activeSuggestionIndex === undefined
? 0
: Math.min(activeSuggestionIndex + 1, mentionSuggestions.length - 1);
setActiveSuggestionIndex(newActiveIndex);
isActiveSuggestionIndexUpdated = true;
}
else if (ev.key === 'Escape') {
updateMentionSuggestions([]);
// reset active suggestion index when suggestions are closed
setActiveSuggestionIndex(undefined);
isActiveSuggestionIndexUpdated = true;
}
}
if (ev.key === 'Enter' && (ev.shiftKey === false || !supportNewline)) {
ev.preventDefault();
// If we are looking up a mention, select the focused suggestion
if (mentionSuggestions.length > 0 && activeSuggestionIndex !== undefined) {
const selectedMention = mentionSuggestions[activeSuggestionIndex];
if (selectedMention) {
onSuggestionSelected(selectedMention);
return;
}
}
onEnterKeyDown && onEnterKeyDown();
}
else if (!isActiveSuggestionIndexUpdated) {
// Update the active suggestion index if the user is typing,
// otherwise the focus will be lost
setActiveSuggestionIndex(undefined);
}
onKeyDown && onKeyDown(ev);
}, [
onEnterKeyDown,
onKeyDown,
supportNewline,
mentionSuggestions,
activeSuggestionIndex,
onSuggestionSelected,
updateMentionSuggestions
]);
const debouncedQueryUpdate = useDebouncedCallback((query) => __awaiter(void 0, void 0, void 0, function* () {
var _a;
let suggestions = (_a = (yield (mentionLookupOptions === null || mentionLookupOptions === void 0 ? void 0 : mentionLookupOptions.onQueryUpdated(query)))) !== null && _a !== void 0 ? _a : [];
suggestions = suggestions.filter((suggestion) => suggestion.displayText.trim() !== '');
if (suggestions.length === 0) {
setActiveSuggestionIndex(undefined);
}
else if (activeSuggestionIndex === undefined) {
// Set the active to the first, if it's not already set
setActiveSuggestionIndex(0);
}
updateMentionSuggestions(suggestions);
}), 500);
// Update selections index in mention to navigate by words
const updateSelectionIndexesWithMentionIfNeeded = useCallback(({ event, inputTextValue, selectionEndValue, selectionStartValue, tagsValue }) => {
var _a, _b, _c;
let updatedStartIndex = event.currentTarget.selectionStart;
let updatedEndIndex = event.currentTarget.selectionEnd;
if (event.currentTarget.selectionStart === event.currentTarget.selectionEnd &&
event.currentTarget.selectionStart !== null &&
event.currentTarget.selectionStart !== -1) {
// just a caret movement/usual typing or deleting
const mentionTag = findMentionTagForSelection(tagsValue, event.currentTarget.selectionStart);
// don't include boundary cases to show correct selection, otherwise it will show selection at mention boundaries
if (mentionTag !== undefined &&
mentionTag.plainTextBeginIndex !== undefined &&
event.currentTarget.selectionStart > mentionTag.plainTextBeginIndex &&
event.currentTarget.selectionStart < ((_a = mentionTag.plainTextEndIndex) !== null && _a !== void 0 ? _a : mentionTag.plainTextBeginIndex)) {
// get updated selection index
const newSelectionIndex = findNewSelectionIndexForMention({
tag: mentionTag,
textValue: inputTextValue,
currentSelectionIndex: event.currentTarget.selectionStart,
previousSelectionIndex: selectionStartValue !== null && selectionStartValue !== void 0 ? selectionStartValue : inputTextValue.length
});
updatedStartIndex = newSelectionIndex;
updatedEndIndex = newSelectionIndex;
}
}
else if (event.currentTarget.selectionStart !== event.currentTarget.selectionEnd) {
// Both e.currentTarget.selectionStart !== selectionStartValue and e.currentTarget.selectionEnd !== selectionEndValue can be true when a user selects a text by double click
if (event.currentTarget.selectionStart !== null && event.currentTarget.selectionStart !== selectionStartValue) {
// the selection start is changed
const mentionTag = findMentionTagForSelection(tagsValue, event.currentTarget.selectionStart);
// don't include boundary cases to show correct selection, otherwise it will show selection at mention boundaries
if (mentionTag !== undefined &&
mentionTag.plainTextBeginIndex !== undefined &&
event.currentTarget.selectionStart > mentionTag.plainTextBeginIndex &&
event.currentTarget.selectionStart < ((_b = mentionTag.plainTextEndIndex) !== null && _b !== void 0 ? _b : mentionTag.plainTextBeginIndex)) {
updatedStartIndex = findNewSelectionIndexForMention({
tag: mentionTag,
textValue: inputTextValue,
currentSelectionIndex: event.currentTarget.selectionStart,
previousSelectionIndex: selectionStartValue !== null && selectionStartValue !== void 0 ? selectionStartValue : inputTextValue.length
});
}
}
if (event.currentTarget.selectionEnd !== null && event.currentTarget.selectionEnd !== selectionEndValue) {
// the selection end is changed
const mentionTag = findMentionTagForSelection(tagsValue, event.currentTarget.selectionEnd);
// don't include boundary cases to show correct selection, otherwise it will show selection at mention boundaries
if (mentionTag !== undefined &&
mentionTag.plainTextBeginIndex !== undefined &&
event.currentTarget.selectionEnd > mentionTag.plainTextBeginIndex &&
event.currentTarget.selectionEnd < ((_c = mentionTag.plainTextEndIndex) !== null && _c !== void 0 ? _c : mentionTag.plainTextBeginIndex)) {
updatedEndIndex = findNewSelectionIndexForMention({
tag: mentionTag,
textValue: inputTextValue,
currentSelectionIndex: event.currentTarget.selectionEnd,
previousSelectionIndex: selectionEndValue !== null && selectionEndValue !== void 0 ? selectionEndValue : inputTextValue.length
});
}
}
}
// e.currentTarget.selectionDirection should be set to handle shift + arrow keys
if (event.currentTarget.selectionDirection === null) {
event.currentTarget.setSelectionRange(updatedStartIndex, updatedEndIndex);
}
else {
event.currentTarget.setSelectionRange(updatedStartIndex, updatedEndIndex, event.currentTarget.selectionDirection);
}
setSelectionStartValue(nullToUndefined(updatedStartIndex));
setSelectionEndValue(nullToUndefined(updatedEndIndex));
}, [setSelectionStartValue, setSelectionEndValue]);
const handleOnSelect = useCallback(({ event, inputTextValue, tags, shouldHandleOnMouseDownDuringSelect, selectionStartValue, selectionEndValue, interactionStartSelection }) => {
var _a;
if (event.currentTarget.selectionStart === 0 && event.currentTarget.selectionEnd === inputTextValue.length) {
// entire text is selected, no need to change anything
setSelectionStartValue(event.currentTarget.selectionStart);
setSelectionEndValue(event.currentTarget.selectionEnd);
setInteractionStartSelection(undefined);
setShouldHandleOnMouseDownDuringSelect(false);
}
else if (shouldHandleOnMouseDownDuringSelect) {
if (interactionStartSelection !== undefined &&
(interactionStartSelection.start !== event.currentTarget.selectionStart ||
interactionStartSelection.end !== event.currentTarget.selectionEnd)) {
// selection was changed by mouse
// for mouse selection only, it's possible to start selection in the middle of a word in a mention
// because of this when event.currentTarget.selectionStart === mouseMoveStartPoint.start
// selectionStartValue for updateSelectionIndexesWithMentionIfNeeded should be set
// to the end of the input to mimic selection from right to left for the left selection index
const updatedSelectionStartValue = event.currentTarget.selectionStart === interactionStartSelection.start
? inputTextValue.length
: interactionStartSelection.start;
// selectionStart is always less than selectionEnd so sometimes selectionEnd is user's start of the selection
// so when event.currentTarget.selectionEnd === mouseMoveStartPoint.end
// selectionEndValue for updateSelectionIndexesWithMentionIfNeeded should be set
// to the beginning of the input to mimic selection from left to right for the right selection index
const updatedSelectionEndValue = event.currentTarget.selectionEnd === interactionStartSelection.end ? 0 : interactionStartSelection.end;
updateSelectionIndexesWithMentionIfNeeded({
event,
inputTextValue,
selectionStartValue: updatedSelectionStartValue,
selectionEndValue: updatedSelectionEndValue,
tagsValue: tags
});
setInteractionStartSelection(undefined);
setShouldHandleOnMouseDownDuringSelect(false);
}
else if (event.currentTarget.selectionStart !== null && event.currentTarget.selectionEnd !== null) {
// on select was triggered by mouse down/up with no movement
const mentionTag = findMentionTagForSelection(tags, event.currentTarget.selectionStart);
if (mentionTag !== undefined && mentionTag.plainTextBeginIndex !== undefined) {
// handle mention click by selecting the whole mention
// if the selection is not on the bounds of the mention
// disable selection for clicks on mention bounds
const mentionEndIndex = (_a = mentionTag.plainTextEndIndex) !== null && _a !== void 0 ? _a : mentionTag.plainTextBeginIndex;
if (event.currentTarget.selectionStart !== event.currentTarget.selectionEnd &&
event.currentTarget.selectionEnd > mentionEndIndex) {
// handle triple click when the text starts from mention
if (event.currentTarget.selectionDirection === null) {
event.currentTarget.setSelectionRange(mentionTag.plainTextBeginIndex, event.currentTarget.selectionEnd);
}
else {
event.currentTarget.setSelectionRange(mentionTag.plainTextBeginIndex, event.currentTarget.selectionEnd, event.currentTarget.selectionDirection);
}
setSelectionStartValue(mentionTag.plainTextBeginIndex);
setSelectionEndValue(event.currentTarget.selectionEnd);
}
else if (event.currentTarget.selectionStart !== event.currentTarget.selectionEnd ||
(event.currentTarget.selectionStart !== mentionTag.plainTextBeginIndex &&
event.currentTarget.selectionStart !== mentionEndIndex)) {
if (event.currentTarget.selectionDirection === null) {
event.currentTarget.setSelectionRange(mentionTag.plainTextBeginIndex, mentionEndIndex);
}
else {
event.currentTarget.setSelectionRange(mentionTag.plainTextBeginIndex, mentionEndIndex, event.currentTarget.selectionDirection);
}
setSelectionStartValue(mentionTag.plainTextBeginIndex);
setSelectionEndValue(mentionEndIndex);
}
else {
// bounds of the mention were selected
setSelectionStartValue(event.currentTarget.selectionStart);
setSelectionEndValue(event.currentTarget.selectionEnd);
}
}
else {
// not a mention tag
setSelectionStartValue(event.currentTarget.selectionStart);
setSelectionEndValue(nullToUndefined(event.currentTarget.selectionEnd));
}
setInteractionStartSelection(undefined);
}
}
else {
// selection was changed by keyboard
updateSelectionIndexesWithMentionIfNeeded({
event,
inputTextValue,
selectionStartValue,
selectionEndValue,
tagsValue: tags
});
}
}, [updateSelectionIndexesWithMentionIfNeeded, setSelectionStartValue, setSelectionEndValue]);
const handleOnChange = useCallback((_b) => __awaiter(void 0, [_b], void 0, function* ({ currentSelectionEnd, currentSelectionStart, currentTriggerStartIndex, event, htmlTextValue, inputTextValue, previousSelectionEnd, previousSelectionStart, tagsValue, updatedValue }) {
var _c;
debouncedQueryUpdate.cancel();
if (event.currentTarget === null) {
return;
}
// handle backspace change
// onSelect is not called for backspace as selection is not changed and local caret index is outdated
setCaretIndex(undefined);
const newValue = updatedValue !== null && updatedValue !== void 0 ? updatedValue : '';
const triggerText = (_c = mentionLookupOptions === null || mentionLookupOptions === void 0 ? void 0 : mentionLookupOptions.trigger) !== null && _c !== void 0 ? _c : DEFAULT_MENTION_TRIGGER;
const newTextLength = newValue.length;
// updating indexes to set between 0 and text length, otherwise selectionRange won't be set correctly
const currentSelectionEndValue = getValidatedIndexInRange({
min: 0,
max: newTextLength,
currentValue: currentSelectionEnd
});
const currentSelectionStartValue = getValidatedIndexInRange({
min: 0,
max: newTextLength,
currentValue: currentSelectionStart
});
const previousSelectionStartValue = getValidatedIndexInRange({
min: 0,
max: inputTextValue.length,
currentValue: previousSelectionStart
});
const previousSelectionEndValue = getValidatedIndexInRange({
min: 0,
max: inputTextValue.length,
currentValue: previousSelectionEnd
});
// If we are enabled for lookups,
if (mentionLookupOptions !== undefined) {
// Look at the range of the change for a trigger character
const triggerPriorIndex = newValue.lastIndexOf(triggerText, currentSelectionEndValue - 1);
// Update the caret position, used for positioning the suggestions popover
const textField = event.currentTarget;
const relativePosition = Caret.getRelativePosition(textField);
if (textField.scrollHeight > textField.clientHeight) {
relativePosition.top -= textField.scrollTop;
}
setCaretPosition(relativePosition);
if (triggerPriorIndex !== undefined) {
// trigger is found
const symbolBeforeTrigger = newValue.substring(triggerPriorIndex - 1, triggerPriorIndex);
const isSpaceBeforeTrigger = symbolBeforeTrigger === ' ';
// check if \r (Carriage Return), \n (Line Feed) or \r\n (End Of Line) is before the trigger
const isNewLineBeforeTrigger = /\r|\n/.exec(symbolBeforeTrigger);
const wordAtSelection = newValue.substring(triggerPriorIndex, currentSelectionEndValue);
let tagIndex = currentTriggerStartIndex;
if (!isSpaceBeforeTrigger && triggerPriorIndex !== 0 && isNewLineBeforeTrigger === null) {
// no space before the trigger, it's not a beginning of the line and no new line before <- continuation of the previous word
tagIndex = -1;
setCurrentTriggerStartIndex(tagIndex);
}
else if (wordAtSelection === triggerText) {
// start of the mention
tagIndex = currentSelectionEndValue - triggerText.length;
if (tagIndex < 0) {
tagIndex = 0;
}
setCurrentTriggerStartIndex(tagIndex);
}
if (tagIndex === -1) {
updateMentionSuggestions([]);
}
else {
// In the middle of a @mention lookup
if (tagIndex > -1) {
const query = wordAtSelection.substring(triggerText.length, wordAtSelection.length);
if (query !== undefined) {
yield debouncedQueryUpdate(query);
}
}
}
}
}
const { changeStart, oldChangeEnd, newChangeEnd } = findStringsDiffIndexes({
oldText: inputTextValue,
newText: newValue,
previousSelectionStart: previousSelectionStartValue,
previousSelectionEnd: previousSelectionEndValue,
currentSelectionStart: currentSelectionStartValue,
currentSelectionEnd: currentSelectionEndValue
});
const change = newValue.substring(changeStart, newChangeEnd);
const updatedContent = updateHTML({
htmlText: htmlTextValue,
oldPlainText: inputTextValue,
tags: tagsValue,
startIndex: changeStart,
oldPlainTextEndIndex: oldChangeEnd,
change,
mentionTrigger: triggerText
});
setInternalTextValue(updatedContent.updatedHTML);
// update caret index if needed
if (updatedContent.updatedSelectionIndex !== undefined) {
setCaretIndex(updatedContent.updatedSelectionIndex);
setSelectionEndValue(updatedContent.updatedSelectionIndex);
setSelectionStartValue(updatedContent.updatedSelectionIndex);
}
onChange && onChange(event, updatedContent.updatedHTML);
}), [debouncedQueryUpdate, mentionLookupOptions, onChange, updateMentionSuggestions]);
// Adjust the selection range based on a mouse / touch interaction
const handleOnMove = useCallback(({ event, selectionStartValue, selectionEndValue, interactionStartSelection, shouldHandleMoveEvent }) => {
if (shouldHandleMoveEvent &&
interactionStartSelection === undefined &&
(event.currentTarget.selectionStart !== selectionStartValue ||
event.currentTarget.selectionEnd !== selectionEndValue)) {
setInteractionStartSelection({
start: nullToUndefined(event.currentTarget.selectionStart),
end: nullToUndefined(event.currentTarget.selectionEnd)
});
}
}, []);
// Adjust the selection range based on a mouse / touch interaction
const handleOnInteractionStarted = useCallback(() => {
// reset caret index as a new selection is started or cursor position will be changed
setCaretIndex(undefined);
setInteractionStartSelection(undefined);
setShouldHandleMoveEvent(true);
setShouldHandleOnMouseDownDuringSelect(true);
}, []);
// Adjust the selection range based on a mouse / touch interaction
const handleOnInteractionCompleted = useCallback(() => {
setShouldHandleMoveEvent(false);
}, []);
const announcerText = useMemo(() => {
if (activeSuggestionIndex === undefined) {
return undefined;
}
const currentMention = mentionSuggestions[activeSuggestionIndex !== null && activeSuggestionIndex !== void 0 ? activeSuggestionIndex : 0];
return currentMention && (currentMention === null || currentMention === void 0 ? void 0 : currentMention.displayText.length) > 0
? currentMention === null || currentMention === void 0 ? void 0 : currentMention.displayText
: localeStrings.participantItem.displayNamePlaceholder;
}, [activeSuggestionIndex, mentionSuggestions, localeStrings.participantItem.displayNamePlaceholder]);
return (React.createElement(React.Fragment, null,
mentionSuggestions.length > 0 && (React.createElement(_MentionPopover, { suggestions: mentionSuggestions, activeSuggestionIndex: activeSuggestionIndex, target: inputBoxRef, targetPositionOffset: caretPosition, onRenderSuggestionItem: mentionLookupOptions === null || mentionLookupOptions === void 0 ? void 0 : mentionLookupOptions.onRenderSuggestionItem, onSuggestionSelected: onSuggestionSelected, onDismiss: () => {
updateMentionSuggestions([]);
} })),
announcerText !== undefined && React.createElement(Announcer, { announcementString: announcerText, ariaLive: 'polite' }),
React.createElement(TextField, Object.assign({}, textFieldProps, { "data-ui-id": dataUiId, value: inputTextValue, onChange: (e, newValue) => {
// Remove when switching to react 17+, currently needed because of https://legacy.reactjs.org/docs/legacy-event-pooling.html
// Prevents React from resetting event's properties
e.persist();
setInputTextValue(newValue !== null && newValue !== void 0 ? newValue : '');
handleOnChange({
event: e,
tagsValue,
htmlTextValue: internalTextValue,
inputTextValue,
currentTriggerStartIndex,
previousSelectionStart: nullToUndefined(selectionStartValue),
previousSelectionEnd: nullToUndefined(selectionEndValue),
currentSelectionStart: nullToUndefined(e.currentTarget.selectionStart),
currentSelectionEnd: nullToUndefined(e.currentTarget.selectionEnd),
updatedValue: newValue
});
}, onSelect: (e) => {
// update selection if needed
if (caretIndex !== undefined) {
// sometimes setting selectionRage in effect for updating caretIndex doesn't work as expected and
// onSelect still returns outdated value for cursor position
// e.g. when user select some text and a first name in a mention then delete or type something else
if (caretIndex !== e.currentTarget.selectionStart || caretIndex !== e.currentTarget.selectionEnd) {
e.currentTarget.setSelectionRange(caretIndex, caretIndex);
}
// caret index should not be set to undefined here
// as it will cause issues when suggestion is selected by mouse
// caret index will be set to undefined during keyboard/mouse or touch interactions
return;
}
handleOnSelect({
event: e,
inputTextValue,
shouldHandleOnMouseDownDuringSelect,
selectionEndValue,
selectionStartValue,
tags: tagsValue,
interactionStartSelection
});
}, onMouseDown: () => {
// as events order is onMouseDown -> onMouseMove -> onMouseUp -> onSelect -> onClick
// onClick and onMouseDown can't handle clicking on mention event because
// onMouseDown doesn't have correct selectionRange yet and
// onClick already has wrong range as it's called after onSelect that updates the selection range
// so we need to handle onMouseDown to prevent onSelect default behavior
handleOnInteractionStarted();
}, onMouseMove: (event) => {
handleOnMove({
event,
selectionStartValue,
selectionEndValue,
interactionStartSelection,
shouldHandleMoveEvent
});
}, onMouseUp: () => {
handleOnInteractionCompleted();
}, onTouchStart: () => {
handleOnInteractionStarted();
}, onTouchMove: (event) => {
handleOnMove({
event,
selectionStartValue,
selectionEndValue,
interactionStartSelection,
shouldHandleMoveEvent
});
}, onTouchEnd: () => {
handleOnInteractionCompleted;
}, onBlur: () => {
// setup shouldHandleOnMouseDownDuringSelect to false when text field loses focus
// as the click should be handled by text field anymore
setShouldHandleOnMouseDownDuringSelect(false);
}, onKeyDown: onTextFieldKeyDown, elementRef: inputBoxRef }))));
};
//# sourceMappingURL=TextFieldWithMention.js.map