@azure/communication-react 1.5.1-alpha-202306030013 → 1.5.1-alpha-202306070014

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.
Files changed (58) hide show
  1. package/dist/communication-react.d.ts +2 -0
  2. package/dist/dist-cjs/communication-react/index.js +1534 -1519
  3. package/dist/dist-cjs/communication-react/index.js.map +1 -1
  4. package/dist/dist-esm/acs-ui-common/src/telemetryVersion.js +1 -1
  5. package/dist/dist-esm/acs-ui-common/src/telemetryVersion.js.map +1 -1
  6. package/dist/dist-esm/react-components/src/components/ChatMessage/ChatMessageComponentAsMessageBubble.js +5 -10
  7. package/dist/dist-esm/react-components/src/components/ChatMessage/ChatMessageComponentAsMessageBubble.js.map +1 -1
  8. package/dist/dist-esm/react-components/src/components/FileCard.js +5 -5
  9. package/dist/dist-esm/react-components/src/components/FileCard.js.map +1 -1
  10. package/dist/dist-esm/react-components/src/components/FileCardGroup.d.ts +1 -0
  11. package/dist/dist-esm/react-components/src/components/FileCardGroup.js +2 -2
  12. package/dist/dist-esm/react-components/src/components/FileCardGroup.js.map +1 -1
  13. package/dist/dist-esm/react-components/src/components/FileDownloadCards.d.ts +1 -0
  14. package/dist/dist-esm/react-components/src/components/FileDownloadCards.js +17 -7
  15. package/dist/dist-esm/react-components/src/components/FileDownloadCards.js.map +1 -1
  16. package/dist/dist-esm/react-components/src/components/FileUploadCards.js +1 -3
  17. package/dist/dist-esm/react-components/src/components/FileUploadCards.js.map +1 -1
  18. package/dist/dist-esm/react-components/src/components/InputBoxComponent.js +40 -1357
  19. package/dist/dist-esm/react-components/src/components/InputBoxComponent.js.map +1 -1
  20. package/dist/dist-esm/react-components/src/components/MessageThread.d.ts +2 -0
  21. package/dist/dist-esm/react-components/src/components/MessageThread.js.map +1 -1
  22. package/dist/dist-esm/react-components/src/components/TextFieldWithMention/TextFieldWithMention.d.ts +41 -0
  23. package/dist/dist-esm/react-components/src/components/TextFieldWithMention/TextFieldWithMention.js +554 -0
  24. package/dist/dist-esm/react-components/src/components/TextFieldWithMention/TextFieldWithMention.js.map +1 -0
  25. package/dist/dist-esm/react-components/src/components/TextFieldWithMention/mentionTagUtils.d.ts +167 -0
  26. package/dist/dist-esm/react-components/src/components/TextFieldWithMention/mentionTagUtils.js +780 -0
  27. package/dist/dist-esm/react-components/src/components/TextFieldWithMention/mentionTagUtils.js.map +1 -0
  28. package/dist/dist-esm/react-components/src/components/utils.d.ts +14 -0
  29. package/dist/dist-esm/react-components/src/components/utils.js +22 -0
  30. package/dist/dist-esm/react-components/src/components/utils.js.map +1 -1
  31. package/dist/dist-esm/react-components/src/localization/locales/de-DE/strings.json +2 -1
  32. package/dist/dist-esm/react-components/src/localization/locales/en-GB/strings.json +2 -1
  33. package/dist/dist-esm/react-components/src/localization/locales/en-US/strings.json +2 -1
  34. package/dist/dist-esm/react-components/src/localization/locales/es-ES/strings.json +2 -1
  35. package/dist/dist-esm/react-components/src/localization/locales/fr-FR/strings.json +2 -1
  36. package/dist/dist-esm/react-components/src/localization/locales/it-IT/strings.json +2 -1
  37. package/dist/dist-esm/react-components/src/localization/locales/ja-JP/strings.json +2 -1
  38. package/dist/dist-esm/react-components/src/localization/locales/ko-KR/strings.json +2 -1
  39. package/dist/dist-esm/react-components/src/localization/locales/nl-NL/strings.json +2 -1
  40. package/dist/dist-esm/react-components/src/localization/locales/pt-BR/strings.json +2 -1
  41. package/dist/dist-esm/react-components/src/localization/locales/ru-RU/strings.json +2 -1
  42. package/dist/dist-esm/react-components/src/localization/locales/tr-TR/strings.json +2 -1
  43. package/dist/dist-esm/react-components/src/localization/locales/zh-CN/strings.json +2 -1
  44. package/dist/dist-esm/react-components/src/localization/locales/zh-TW/strings.json +2 -1
  45. package/dist/dist-esm/react-composites/src/composites/localization/locales/de-DE/strings.json +2 -1
  46. package/dist/dist-esm/react-composites/src/composites/localization/locales/en-GB/strings.json +2 -1
  47. package/dist/dist-esm/react-composites/src/composites/localization/locales/es-ES/strings.json +2 -1
  48. package/dist/dist-esm/react-composites/src/composites/localization/locales/fr-FR/strings.json +2 -1
  49. package/dist/dist-esm/react-composites/src/composites/localization/locales/it-IT/strings.json +2 -1
  50. package/dist/dist-esm/react-composites/src/composites/localization/locales/ja-JP/strings.json +2 -1
  51. package/dist/dist-esm/react-composites/src/composites/localization/locales/ko-KR/strings.json +2 -1
  52. package/dist/dist-esm/react-composites/src/composites/localization/locales/nl-NL/strings.json +2 -1
  53. package/dist/dist-esm/react-composites/src/composites/localization/locales/pt-BR/strings.json +2 -1
  54. package/dist/dist-esm/react-composites/src/composites/localization/locales/ru-RU/strings.json +2 -1
  55. package/dist/dist-esm/react-composites/src/composites/localization/locales/tr-TR/strings.json +2 -1
  56. package/dist/dist-esm/react-composites/src/composites/localization/locales/zh-CN/strings.json +2 -1
  57. package/dist/dist-esm/react-composites/src/composites/localization/locales/zh-TW/strings.json +2 -1
  58. package/package.json +8 -8
@@ -0,0 +1,554 @@
1
+ // Copyright (c) Microsoft Corporation.
2
+ // Licensed under the MIT license.
3
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
4
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
5
+ return new (P || (P = Promise))(function (resolve, reject) {
6
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
7
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
8
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
9
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
10
+ });
11
+ };
12
+ import React, { useState, useCallback, useRef } from 'react';
13
+ import { useEffect, useMemo } from 'react';
14
+ import { useLocale } from '../../localization';
15
+ import { Announcer } from '../Announcer';
16
+ import { Stack, TextField, mergeStyles, IconButton, TooltipHost } from '@fluentui/react';
17
+ import { isEnterKeyEventFromCompositionSession, nullToUndefined, undefinedToNull } from '../utils';
18
+ import { findMentionTagForSelection, findNewSelectionIndexForMention, findStringsDiffIndexes, getDisplayNameForMentionSuggestion, getValidatedIndexInRange, htmlStringForMentionSuggestion, rangeOfWordInSelection, textToTagParser, updateHTML } from './mentionTagUtils';
19
+ import { inputButtonStyle, inputButtonTooltipStyle, iconWrapperStyle } from '../styles/InputBoxComponent.style';
20
+ import { Caret } from 'textarea-caret-ts';
21
+ import { isDarkThemed } from '../../theming/themeUtils';
22
+ import { useTheme } from '../../theming';
23
+ import { _MentionPopover } from '../MentionPopover';
24
+ import { useDebouncedCallback } from 'use-debounce';
25
+ const DEFAULT_MENTION_TRIGGER = '@';
26
+ /**
27
+ * @private
28
+ */
29
+ export const TextFieldWithMention = (props) => {
30
+ const { textFieldProps, dataUiId, textValue, onChange, textFieldRef, onKeyDown, onEnterKeyDown, supportNewline, mentionLookupOptions } = props;
31
+ const inputBoxRef = useRef(null);
32
+ // Current suggestion list, provided by the callback
33
+ const [mentionSuggestions, setMentionSuggestions] = useState([]);
34
+ // Current suggestion list, provided by the callback
35
+ const [activeSuggestionIndex, setActiveSuggestionIndex] = useState(undefined);
36
+ // Index of the current trigger character in the text field
37
+ const [currentTriggerStartIndex, setCurrentTriggerStartIndex] = useState(-1);
38
+ const [inputTextValue, setInputTextValue] = useState('');
39
+ const [tagsValue, setTagsValue] = useState([]);
40
+ // Index of the previous selection start in the text field
41
+ const [selectionStartValue, setSelectionStartValue] = useState();
42
+ // Index of the previous selection end in the text field
43
+ const [selectionEndValue, setSelectionEndValue] = useState();
44
+ // Boolean value to check if onMouseDown event should be handled during select as selection range
45
+ // for onMouseDown event is not updated yet and the selection range for mouse click/taps will be
46
+ // updated in onSelect event if needed.
47
+ const [shouldHandleOnMouseDownDuringSelect, setShouldHandleOnMouseDownDuringSelect] = useState(true);
48
+ // Point of start of touch/mouse selection
49
+ const [interactionStartPoint, setInteractionStartPoint] = useState();
50
+ // Target selection from mouse movement
51
+ const [targetSelection, setTargetSelection] = useState();
52
+ // Caret position in the text field
53
+ const [caretPosition, setCaretPosition] = useState(undefined);
54
+ // Index of where the caret is in the text field
55
+ const [caretIndex, setCaretIndex] = useState(undefined);
56
+ const localeStrings = useLocale().strings;
57
+ // Set mention suggestions
58
+ const updateMentionSuggestions = useCallback((suggestions) => {
59
+ setMentionSuggestions(suggestions);
60
+ }, [setMentionSuggestions]);
61
+ // Parse the text and get the plain text version to display in the input box
62
+ useEffect(() => {
63
+ const trigger = (mentionLookupOptions === null || mentionLookupOptions === void 0 ? void 0 : mentionLookupOptions.trigger) || DEFAULT_MENTION_TRIGGER;
64
+ const parsedHTMLData = textToTagParser(textValue, trigger);
65
+ setInputTextValue(parsedHTMLData.plainText);
66
+ setTagsValue(parsedHTMLData.tags);
67
+ updateMentionSuggestions([]);
68
+ }, [textValue, mentionLookupOptions === null || mentionLookupOptions === void 0 ? void 0 : mentionLookupOptions.trigger, updateMentionSuggestions]);
69
+ useEffect(() => {
70
+ var _a;
71
+ // effect for caret index update
72
+ if (caretIndex === undefined || textFieldRef === undefined || (textFieldRef === null || textFieldRef === void 0 ? void 0 : textFieldRef.current) === undefined) {
73
+ return;
74
+ }
75
+ // get validated caret index between 0 and inputTextValue.length otherwise caret will be set to incorrect index
76
+ const updatedCaretIndex = getValidatedIndexInRange({
77
+ min: 0,
78
+ max: inputTextValue.length,
79
+ currentValue: caretIndex
80
+ });
81
+ (_a = textFieldRef === null || textFieldRef === void 0 ? void 0 : textFieldRef.current) === null || _a === void 0 ? void 0 : _a.setSelectionRange(updatedCaretIndex, updatedCaretIndex);
82
+ setSelectionStartValue(updatedCaretIndex);
83
+ setSelectionEndValue(updatedCaretIndex);
84
+ }, [caretIndex, inputTextValue.length, textFieldRef, setSelectionStartValue, setSelectionEndValue]);
85
+ const onSuggestionSelected = useCallback((suggestion) => {
86
+ var _a, _b, _c;
87
+ let selectionEnd = ((_a = textFieldRef === null || textFieldRef === void 0 ? void 0 : textFieldRef.current) === null || _a === void 0 ? void 0 : _a.selectionEnd) || -1;
88
+ if (selectionEnd < 0) {
89
+ selectionEnd = 0;
90
+ }
91
+ else if (selectionEnd > inputTextValue.length) {
92
+ selectionEnd = inputTextValue.length;
93
+ }
94
+ const oldPlainText = inputTextValue;
95
+ const mention = htmlStringForMentionSuggestion(suggestion, localeStrings);
96
+ // update plain text with the mention html text
97
+ const newPlainText = inputTextValue.substring(0, currentTriggerStartIndex) + mention + inputTextValue.substring(selectionEnd);
98
+ const triggerText = (_b = mentionLookupOptions === null || mentionLookupOptions === void 0 ? void 0 : mentionLookupOptions.trigger) !== null && _b !== void 0 ? _b : DEFAULT_MENTION_TRIGGER;
99
+ // update html text with updated plain text
100
+ const updatedContent = updateHTML({
101
+ htmlText: textValue,
102
+ oldPlainText,
103
+ newPlainText,
104
+ tags: tagsValue,
105
+ startIndex: currentTriggerStartIndex,
106
+ oldPlainTextEndIndex: selectionEnd,
107
+ change: mention,
108
+ mentionTrigger: triggerText
109
+ });
110
+ const displayName = getDisplayNameForMentionSuggestion(suggestion, localeStrings);
111
+ const newCaretIndex = currentTriggerStartIndex + displayName.length + triggerText.length;
112
+ // move the caret in the text field to the end of the mention plain text
113
+ setCaretIndex(newCaretIndex);
114
+ setSelectionEndValue(newCaretIndex);
115
+ setSelectionStartValue(newCaretIndex);
116
+ setCurrentTriggerStartIndex(-1);
117
+ updateMentionSuggestions([]);
118
+ // set focus back to text field
119
+ (_c = textFieldRef === null || textFieldRef === void 0 ? void 0 : textFieldRef.current) === null || _c === void 0 ? void 0 : _c.focus();
120
+ setActiveSuggestionIndex(undefined);
121
+ onChange && onChange(undefined, updatedContent.updatedHTML);
122
+ }, [
123
+ textFieldRef,
124
+ inputTextValue,
125
+ currentTriggerStartIndex,
126
+ mentionLookupOptions === null || mentionLookupOptions === void 0 ? void 0 : mentionLookupOptions.trigger,
127
+ onChange,
128
+ textValue,
129
+ tagsValue,
130
+ updateMentionSuggestions,
131
+ localeStrings
132
+ ]);
133
+ const onTextFieldKeyDown = useCallback((ev) => {
134
+ // caretIndex should be set to undefined when the user is typing
135
+ setCaretIndex(undefined);
136
+ // shouldHandleOnMouseDownDuringSelect should be set to false after the last mouse down event.
137
+ // it shouldn't be updated in onMouseUp
138
+ // as onMouseUp can be triggered before or after onSelect event
139
+ // because its order depends on mouse events not selection.
140
+ setShouldHandleOnMouseDownDuringSelect(false);
141
+ if (isEnterKeyEventFromCompositionSession(ev)) {
142
+ return;
143
+ }
144
+ if (mentionSuggestions.length > 0) {
145
+ if (ev.key === 'ArrowUp') {
146
+ ev.preventDefault();
147
+ const newActiveIndex = activeSuggestionIndex === undefined
148
+ ? mentionSuggestions.length - 1
149
+ : Math.max(activeSuggestionIndex - 1, 0);
150
+ setActiveSuggestionIndex(newActiveIndex);
151
+ }
152
+ else if (ev.key === 'ArrowDown') {
153
+ ev.preventDefault();
154
+ const newActiveIndex = activeSuggestionIndex === undefined
155
+ ? 0
156
+ : Math.min(activeSuggestionIndex + 1, mentionSuggestions.length - 1);
157
+ setActiveSuggestionIndex(newActiveIndex);
158
+ }
159
+ else if (ev.key === 'Escape') {
160
+ updateMentionSuggestions([]);
161
+ }
162
+ }
163
+ if (ev.key === 'Enter' && (ev.shiftKey === false || !supportNewline)) {
164
+ ev.preventDefault();
165
+ // If we are looking up a mention, select the focused suggestion
166
+ if (mentionSuggestions.length > 0 && activeSuggestionIndex !== undefined) {
167
+ const selectedMention = mentionSuggestions[activeSuggestionIndex];
168
+ if (selectedMention) {
169
+ onSuggestionSelected(selectedMention);
170
+ return;
171
+ }
172
+ }
173
+ onEnterKeyDown && onEnterKeyDown();
174
+ }
175
+ onKeyDown && onKeyDown(ev);
176
+ }, [
177
+ onEnterKeyDown,
178
+ onKeyDown,
179
+ supportNewline,
180
+ mentionSuggestions,
181
+ activeSuggestionIndex,
182
+ onSuggestionSelected,
183
+ updateMentionSuggestions
184
+ ]);
185
+ const debouncedQueryUpdate = useDebouncedCallback((query) => __awaiter(void 0, void 0, void 0, function* () {
186
+ var _a;
187
+ const suggestions = (_a = (yield (mentionLookupOptions === null || mentionLookupOptions === void 0 ? void 0 : mentionLookupOptions.onQueryUpdated(query)))) !== null && _a !== void 0 ? _a : [];
188
+ if (suggestions.length === 0) {
189
+ setActiveSuggestionIndex(undefined);
190
+ }
191
+ else if (activeSuggestionIndex === undefined) {
192
+ // Set the active to the first, if it's not already set
193
+ setActiveSuggestionIndex(0);
194
+ }
195
+ updateMentionSuggestions(suggestions);
196
+ }), 500);
197
+ // Update selections index in mention to navigate by words
198
+ const updateSelectionIndexesWithMentionIfNeeded = useCallback(({ event, inputTextValue, selectionEndValue, selectionStartValue, tagsValue }) => {
199
+ var _a, _b, _c;
200
+ let updatedStartIndex = event.currentTarget.selectionStart;
201
+ let updatedEndIndex = event.currentTarget.selectionEnd;
202
+ if (event.currentTarget.selectionStart === event.currentTarget.selectionEnd &&
203
+ event.currentTarget.selectionStart !== null &&
204
+ event.currentTarget.selectionStart !== -1) {
205
+ // just a caret movement/usual typing or deleting
206
+ const mentionTag = findMentionTagForSelection(tagsValue, event.currentTarget.selectionStart);
207
+ // don't include boundary cases to show correct selection, otherwise it will show selection at mention boundaries
208
+ if (mentionTag !== undefined &&
209
+ mentionTag.plainTextBeginIndex !== undefined &&
210
+ event.currentTarget.selectionStart > mentionTag.plainTextBeginIndex &&
211
+ event.currentTarget.selectionStart < ((_a = mentionTag.plainTextEndIndex) !== null && _a !== void 0 ? _a : mentionTag.plainTextBeginIndex)) {
212
+ // get updated selection index
213
+ const newSelectionIndex = findNewSelectionIndexForMention({
214
+ tag: mentionTag,
215
+ textValue: inputTextValue,
216
+ currentSelectionIndex: event.currentTarget.selectionStart,
217
+ previousSelectionIndex: selectionStartValue !== null && selectionStartValue !== void 0 ? selectionStartValue : inputTextValue.length
218
+ });
219
+ updatedStartIndex = newSelectionIndex;
220
+ updatedEndIndex = newSelectionIndex;
221
+ }
222
+ }
223
+ else if (event.currentTarget.selectionStart !== event.currentTarget.selectionEnd) {
224
+ // Both e.currentTarget.selectionStart !== selectionStartValue and e.currentTarget.selectionEnd !== selectionEndValue can be true when a user selects a text by double click
225
+ if (event.currentTarget.selectionStart !== null && event.currentTarget.selectionStart !== selectionStartValue) {
226
+ // the selection start is changed
227
+ const mentionTag = findMentionTagForSelection(tagsValue, event.currentTarget.selectionStart);
228
+ // don't include boundary cases to show correct selection, otherwise it will show selection at mention boundaries
229
+ if (mentionTag !== undefined &&
230
+ mentionTag.plainTextBeginIndex !== undefined &&
231
+ event.currentTarget.selectionStart > mentionTag.plainTextBeginIndex &&
232
+ event.currentTarget.selectionStart < ((_b = mentionTag.plainTextEndIndex) !== null && _b !== void 0 ? _b : mentionTag.plainTextBeginIndex)) {
233
+ updatedStartIndex = findNewSelectionIndexForMention({
234
+ tag: mentionTag,
235
+ textValue: inputTextValue,
236
+ currentSelectionIndex: event.currentTarget.selectionStart,
237
+ previousSelectionIndex: selectionStartValue !== null && selectionStartValue !== void 0 ? selectionStartValue : inputTextValue.length
238
+ });
239
+ }
240
+ }
241
+ if (event.currentTarget.selectionEnd !== null && event.currentTarget.selectionEnd !== selectionEndValue) {
242
+ // the selection end is changed
243
+ const mentionTag = findMentionTagForSelection(tagsValue, event.currentTarget.selectionEnd);
244
+ // don't include boundary cases to show correct selection, otherwise it will show selection at mention boundaries
245
+ if (mentionTag !== undefined &&
246
+ mentionTag.plainTextBeginIndex !== undefined &&
247
+ event.currentTarget.selectionEnd > mentionTag.plainTextBeginIndex &&
248
+ event.currentTarget.selectionEnd < ((_c = mentionTag.plainTextEndIndex) !== null && _c !== void 0 ? _c : mentionTag.plainTextBeginIndex)) {
249
+ updatedEndIndex = findNewSelectionIndexForMention({
250
+ tag: mentionTag,
251
+ textValue: inputTextValue,
252
+ currentSelectionIndex: event.currentTarget.selectionEnd,
253
+ previousSelectionIndex: selectionEndValue !== null && selectionEndValue !== void 0 ? selectionEndValue : inputTextValue.length
254
+ });
255
+ }
256
+ }
257
+ }
258
+ // e.currentTarget.selectionDirection should be set to handle shift + arrow keys
259
+ if (event.currentTarget.selectionDirection === null) {
260
+ event.currentTarget.setSelectionRange(updatedStartIndex, updatedEndIndex);
261
+ }
262
+ else {
263
+ event.currentTarget.setSelectionRange(updatedStartIndex, updatedEndIndex, event.currentTarget.selectionDirection);
264
+ }
265
+ setSelectionStartValue(nullToUndefined(updatedStartIndex));
266
+ setSelectionEndValue(nullToUndefined(updatedEndIndex));
267
+ }, [setSelectionStartValue, setSelectionEndValue]);
268
+ const handleOnSelect = useCallback(({ event, inputTextValue, tags, shouldHandleOnMouseDownDuringSelect, selectionStartValue, selectionEndValue }) => {
269
+ if (shouldHandleOnMouseDownDuringSelect) {
270
+ if (targetSelection !== undefined) {
271
+ setSelectionStartValue(targetSelection.start);
272
+ setSelectionEndValue(targetSelection.end);
273
+ event.currentTarget.setSelectionRange(targetSelection.start, undefinedToNull(targetSelection.end));
274
+ setTargetSelection(undefined);
275
+ }
276
+ else if (event.currentTarget.selectionStart !== null) {
277
+ // on select was triggered by mouse down/up with no movement
278
+ const mentionTag = findMentionTagForSelection(tags, event.currentTarget.selectionStart);
279
+ if (mentionTag !== undefined && mentionTag.plainTextBeginIndex !== undefined) {
280
+ // handle mention click
281
+ // Get range of word that was clicked on
282
+ const selectionRange = rangeOfWordInSelection({
283
+ textInput: inputTextValue,
284
+ selectionStart: event.currentTarget.selectionStart,
285
+ selectionEnd: nullToUndefined(event.currentTarget.selectionEnd),
286
+ tag: mentionTag
287
+ });
288
+ if (event.currentTarget.selectionDirection === null) {
289
+ event.currentTarget.setSelectionRange(selectionRange.start, selectionRange.end);
290
+ }
291
+ else {
292
+ event.currentTarget.setSelectionRange(selectionRange.start, selectionRange.end, event.currentTarget.selectionDirection);
293
+ }
294
+ setSelectionStartValue(selectionRange.start);
295
+ setSelectionEndValue(selectionRange.end);
296
+ }
297
+ else {
298
+ setSelectionStartValue(event.currentTarget.selectionStart);
299
+ setSelectionEndValue(nullToUndefined(event.currentTarget.selectionEnd));
300
+ }
301
+ }
302
+ }
303
+ else {
304
+ // selection was changed by keyboard
305
+ updateSelectionIndexesWithMentionIfNeeded({
306
+ event,
307
+ inputTextValue,
308
+ selectionStartValue,
309
+ selectionEndValue,
310
+ tagsValue: tags
311
+ });
312
+ }
313
+ // don't set setShouldHandleOnMouseDownDuringSelect(false) here as setSelectionRange
314
+ // could trigger additional calls of onSelect event and they may not be handled correctly
315
+ // (because of setSelectionRange calls or rerender)
316
+ }, [
317
+ updateSelectionIndexesWithMentionIfNeeded,
318
+ targetSelection,
319
+ setTargetSelection,
320
+ setSelectionStartValue,
321
+ setSelectionEndValue
322
+ ]);
323
+ const handleOnChange = useCallback(({ currentSelectionEnd, currentSelectionStart, currentTriggerStartIndex, event, htmlTextValue, inputTextValue, previousSelectionEnd, previousSelectionStart, tagsValue, updatedValue }) => __awaiter(void 0, void 0, void 0, function* () {
324
+ var _b;
325
+ debouncedQueryUpdate.cancel();
326
+ if (event.currentTarget === null) {
327
+ return;
328
+ }
329
+ // handle backspace change
330
+ // onSelect is not called for backspace as selection is not changed and local caret index is outdated
331
+ setCaretIndex(undefined);
332
+ const newValue = updatedValue !== null && updatedValue !== void 0 ? updatedValue : '';
333
+ const triggerText = (_b = mentionLookupOptions === null || mentionLookupOptions === void 0 ? void 0 : mentionLookupOptions.trigger) !== null && _b !== void 0 ? _b : DEFAULT_MENTION_TRIGGER;
334
+ const newTextLength = newValue.length;
335
+ // updating indexes to set between 0 and text length, otherwise selectionRange won't be set correctly
336
+ const currentSelectionEndValue = getValidatedIndexInRange({
337
+ min: 0,
338
+ max: newTextLength,
339
+ currentValue: currentSelectionEnd
340
+ });
341
+ const currentSelectionStartValue = getValidatedIndexInRange({
342
+ min: 0,
343
+ max: newTextLength,
344
+ currentValue: currentSelectionStart
345
+ });
346
+ const previousSelectionStartValue = getValidatedIndexInRange({
347
+ min: 0,
348
+ max: inputTextValue.length,
349
+ currentValue: previousSelectionStart
350
+ });
351
+ const previousSelectionEndValue = getValidatedIndexInRange({
352
+ min: 0,
353
+ max: inputTextValue.length,
354
+ currentValue: previousSelectionEnd
355
+ });
356
+ // If we are enabled for lookups,
357
+ if (mentionLookupOptions !== undefined) {
358
+ // Look at the range of the change for a trigger character
359
+ const triggerPriorIndex = newValue.lastIndexOf(triggerText, currentSelectionEndValue - 1);
360
+ // Update the caret position, if not doing a lookup
361
+ setCaretPosition(Caret.getRelativePosition(event.currentTarget));
362
+ if (triggerPriorIndex !== undefined) {
363
+ // trigger is found
364
+ const isSpaceBeforeTrigger = newValue.substring(triggerPriorIndex - 1, triggerPriorIndex) === ' ';
365
+ const wordAtSelection = newValue.substring(triggerPriorIndex, currentSelectionEndValue);
366
+ let tagIndex = currentTriggerStartIndex;
367
+ if (!isSpaceBeforeTrigger && triggerPriorIndex !== 0) {
368
+ //no space before the trigger <- continuation of the previous word
369
+ tagIndex = -1;
370
+ setCurrentTriggerStartIndex(tagIndex);
371
+ }
372
+ else if (wordAtSelection === triggerText) {
373
+ // start of the mention
374
+ tagIndex = currentSelectionEndValue - triggerText.length;
375
+ if (tagIndex < 0) {
376
+ tagIndex = 0;
377
+ }
378
+ setCurrentTriggerStartIndex(tagIndex);
379
+ }
380
+ if (tagIndex === -1) {
381
+ updateMentionSuggestions([]);
382
+ }
383
+ else {
384
+ // In the middle of a @mention lookup
385
+ if (tagIndex > -1) {
386
+ const query = wordAtSelection.substring(triggerText.length, wordAtSelection.length);
387
+ if (query !== undefined) {
388
+ yield debouncedQueryUpdate(query);
389
+ }
390
+ }
391
+ }
392
+ }
393
+ }
394
+ let result = '';
395
+ if (tagsValue.length === 0) {
396
+ // no tags in the string and newValue should be used as a result string
397
+ result = newValue;
398
+ }
399
+ else {
400
+ // there are tags in the text value and htmlTextValue is html string
401
+ // find diff between old and new text
402
+ const { changeStart, oldChangeEnd, newChangeEnd } = findStringsDiffIndexes({
403
+ oldText: inputTextValue,
404
+ newText: newValue,
405
+ previousSelectionStart: previousSelectionStartValue,
406
+ previousSelectionEnd: previousSelectionEndValue,
407
+ currentSelectionStart: currentSelectionStartValue,
408
+ currentSelectionEnd: currentSelectionEndValue
409
+ });
410
+ const change = newValue.substring(changeStart, newChangeEnd);
411
+ // get updated html string
412
+ const updatedContent = updateHTML({
413
+ htmlText: htmlTextValue,
414
+ oldPlainText: inputTextValue,
415
+ newPlainText: newValue,
416
+ tags: tagsValue,
417
+ startIndex: changeStart,
418
+ oldPlainTextEndIndex: oldChangeEnd,
419
+ change,
420
+ mentionTrigger: triggerText
421
+ });
422
+ result = updatedContent.updatedHTML;
423
+ // update caret index if needed
424
+ if (updatedContent.updatedSelectionIndex !== undefined) {
425
+ setCaretIndex(updatedContent.updatedSelectionIndex);
426
+ setSelectionEndValue(updatedContent.updatedSelectionIndex);
427
+ setSelectionStartValue(updatedContent.updatedSelectionIndex);
428
+ }
429
+ }
430
+ onChange && onChange(event, result);
431
+ }), [onChange, mentionLookupOptions, setCaretIndex, setCaretPosition, updateMentionSuggestions, debouncedQueryUpdate]);
432
+ // Adjust the selection range based on a mouse / touch interaction
433
+ const handleOnMove = useCallback((event) => {
434
+ var _a;
435
+ let targetStart = event.currentTarget.selectionStart;
436
+ let targetEnd = event.currentTarget.selectionEnd;
437
+ // Should we do anything?
438
+ if (interactionStartPoint !== undefined &&
439
+ // And did selection change?
440
+ targetStart !== null &&
441
+ (targetStart !== (targetSelection === null || targetSelection === void 0 ? void 0 : targetSelection.start) || targetEnd !== (targetSelection === null || targetSelection === void 0 ? void 0 : targetSelection.end))) {
442
+ const direction = event.clientX > interactionStartPoint.x ? 'forward' : 'backward';
443
+ const mentionTag = findMentionTagForSelection(tagsValue, direction === 'backward'
444
+ ? event.currentTarget.selectionStart
445
+ : (_a = event.currentTarget.selectionEnd) !== null && _a !== void 0 ? _a : event.currentTarget.selectionStart);
446
+ let updateCurrentTarget = false;
447
+ if (mentionTag !== undefined && mentionTag.plainTextBeginIndex !== undefined) {
448
+ targetStart = Math.min(mentionTag.plainTextBeginIndex, targetStart);
449
+ if (mentionTag.plainTextEndIndex !== undefined && targetEnd !== null) {
450
+ targetEnd = Math.max(mentionTag.plainTextEndIndex, targetEnd);
451
+ }
452
+ updateCurrentTarget = true;
453
+ setShouldHandleOnMouseDownDuringSelect(false);
454
+ }
455
+ // Update selection range
456
+ setTargetSelection({ start: targetStart, end: targetEnd });
457
+ if (updateCurrentTarget) {
458
+ // Only set the control, if the values are updated
459
+ event.currentTarget.setSelectionRange(targetStart, targetEnd, direction);
460
+ }
461
+ }
462
+ }, [setTargetSelection, targetSelection, setShouldHandleOnMouseDownDuringSelect, interactionStartPoint, tagsValue]);
463
+ const announcerText = useMemo(() => {
464
+ if (activeSuggestionIndex === undefined) {
465
+ return undefined;
466
+ }
467
+ const currentMention = mentionSuggestions[activeSuggestionIndex !== null && activeSuggestionIndex !== void 0 ? activeSuggestionIndex : 0];
468
+ return (currentMention === null || currentMention === void 0 ? void 0 : currentMention.displayText.length) > 0
469
+ ? currentMention === null || currentMention === void 0 ? void 0 : currentMention.displayText
470
+ : localeStrings.participantItem.displayNamePlaceholder;
471
+ }, [activeSuggestionIndex, mentionSuggestions, localeStrings.participantItem.displayNamePlaceholder]);
472
+ return (React.createElement(React.Fragment, null,
473
+ 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: () => {
474
+ updateMentionSuggestions([]);
475
+ } })),
476
+ announcerText !== undefined && React.createElement(Announcer, { announcementString: announcerText, ariaLive: 'polite' }),
477
+ React.createElement(TextField, Object.assign({}, textFieldProps, { "data-ui-id": dataUiId, value: inputTextValue, onChange: (e, newValue) => {
478
+ // Remove when switching to react 17+, currently needed because of https://legacy.reactjs.org/docs/legacy-event-pooling.html
479
+ // Prevents React from resetting event's properties
480
+ e.persist();
481
+ setInputTextValue(newValue !== null && newValue !== void 0 ? newValue : '');
482
+ handleOnChange({
483
+ event: e,
484
+ tagsValue,
485
+ htmlTextValue: textValue,
486
+ inputTextValue,
487
+ currentTriggerStartIndex,
488
+ previousSelectionStart: nullToUndefined(selectionStartValue),
489
+ previousSelectionEnd: nullToUndefined(selectionEndValue),
490
+ currentSelectionStart: nullToUndefined(e.currentTarget.selectionStart),
491
+ currentSelectionEnd: nullToUndefined(e.currentTarget.selectionEnd),
492
+ updatedValue: newValue
493
+ });
494
+ }, onSelect: (e) => {
495
+ handleOnSelect({
496
+ event: e,
497
+ inputTextValue,
498
+ shouldHandleOnMouseDownDuringSelect,
499
+ selectionEndValue,
500
+ selectionStartValue,
501
+ tags: tagsValue
502
+ });
503
+ }, onMouseDown: (e) => {
504
+ setInteractionStartPoint({ x: e.clientX, y: e.clientY });
505
+ // as events order is onMouseDown -> onMouseMove -> onMouseUp -> onSelect -> onClick
506
+ // onClick and onMouseDown can't handle clicking on mention event because
507
+ // onMouseDown doesn't have correct selectionRange yet and
508
+ // onClick already has wrong range as it's called after onSelect that updates the selection range
509
+ // so we need to handle onMouseDown to prevent onSelect default behavior
510
+ setShouldHandleOnMouseDownDuringSelect(true);
511
+ }, onMouseMove: handleOnMove, onMouseUp: () => {
512
+ setInteractionStartPoint(undefined);
513
+ }, onTouchStart: (e) => {
514
+ setInteractionStartPoint({
515
+ x: e.targetTouches.item(0).clientX,
516
+ y: e.targetTouches.item(0).clientY
517
+ });
518
+ // see onMouseDown for more details
519
+ setShouldHandleOnMouseDownDuringSelect(true);
520
+ }, onTouchMove: handleOnMove, onTouchEnd: () => {
521
+ setInteractionStartPoint(undefined);
522
+ }, onBlur: () => {
523
+ // setup all flags to default values when text field loses focus
524
+ setShouldHandleOnMouseDownDuringSelect(false);
525
+ setCaretIndex(undefined);
526
+ setSelectionStartValue(undefined);
527
+ setSelectionEndValue(undefined);
528
+ }, onKeyDown: onTextFieldKeyDown, elementRef: inputBoxRef }))));
529
+ };
530
+ /**
531
+ * @private
532
+ */
533
+ export const InputBoxButton = (props) => {
534
+ const { onRenderIcon, onClick, ariaLabel, className, id, tooltipContent } = props;
535
+ const [isHover, setIsHover] = useState(false);
536
+ const mergedButtonStyle = mergeStyles(inputButtonStyle, className);
537
+ const theme = useTheme();
538
+ const calloutStyle = { root: { padding: 0 }, calloutMain: { padding: '0.5rem' } };
539
+ // Place callout with no gap between it and the button.
540
+ const calloutProps = {
541
+ gapSpace: 0,
542
+ styles: calloutStyle,
543
+ backgroundColor: isDarkThemed(theme) ? theme.palette.neutralLighter : ''
544
+ };
545
+ return (React.createElement(TooltipHost, { hostClassName: inputButtonTooltipStyle, content: tooltipContent, calloutProps: Object.assign({}, calloutProps) },
546
+ React.createElement(IconButton, { className: mergedButtonStyle, ariaLabel: ariaLabel, onClick: onClick, id: id, onMouseEnter: () => {
547
+ setIsHover(true);
548
+ }, onMouseLeave: () => {
549
+ setIsHover(false);
550
+ },
551
+ // VoiceOver fix: Avoid icon from stealing focus when IconButton is double-tapped to send message by wrapping with Stack with pointerEvents style to none
552
+ onRenderIcon: () => React.createElement(Stack, { className: iconWrapperStyle }, onRenderIcon(isHover)) })));
553
+ };
554
+ //# sourceMappingURL=TextFieldWithMention.js.map