@azure/communication-react 1.5.1-alpha-202305170014 → 1.5.1-alpha-202305190012

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 (57) hide show
  1. package/dist/communication-react.d.ts +4 -2
  2. package/dist/dist-cjs/communication-react/index.js +802 -568
  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/calling-stateful-client/src/LocalVideoStreamVideoEffectsSubscriber.js +4 -4
  7. package/dist/dist-esm/calling-stateful-client/src/LocalVideoStreamVideoEffectsSubscriber.js.map +1 -1
  8. package/dist/dist-esm/react-components/src/components/CameraButton.js +5 -3
  9. package/dist/dist-esm/react-components/src/components/CameraButton.js.map +1 -1
  10. package/dist/dist-esm/react-components/src/components/InputBoxComponent.js +564 -364
  11. package/dist/dist-esm/react-components/src/components/InputBoxComponent.js.map +1 -1
  12. package/dist/dist-esm/react-components/src/components/VideoEffects/VideoEffectsItem.js +1 -1
  13. package/dist/dist-esm/react-components/src/components/VideoEffects/VideoEffectsItem.js.map +1 -1
  14. package/dist/dist-esm/react-components/src/localization/locales/de-DE/strings.json +7 -2
  15. package/dist/dist-esm/react-components/src/localization/locales/en-GB/strings.json +7 -2
  16. package/dist/dist-esm/react-components/src/localization/locales/es-ES/strings.json +7 -2
  17. package/dist/dist-esm/react-components/src/localization/locales/fr-FR/strings.json +7 -2
  18. package/dist/dist-esm/react-components/src/localization/locales/it-IT/strings.json +7 -2
  19. package/dist/dist-esm/react-components/src/localization/locales/ja-JP/strings.json +7 -2
  20. package/dist/dist-esm/react-components/src/localization/locales/ko-KR/strings.json +7 -2
  21. package/dist/dist-esm/react-components/src/localization/locales/nl-NL/strings.json +7 -2
  22. package/dist/dist-esm/react-components/src/localization/locales/pt-BR/strings.json +7 -2
  23. package/dist/dist-esm/react-components/src/localization/locales/ru-RU/strings.json +7 -2
  24. package/dist/dist-esm/react-components/src/localization/locales/tr-TR/strings.json +7 -2
  25. package/dist/dist-esm/react-components/src/localization/locales/zh-CN/strings.json +7 -2
  26. package/dist/dist-esm/react-components/src/localization/locales/zh-TW/strings.json +7 -2
  27. package/dist/dist-esm/react-components/src/theming/icons.d.ts +2 -1
  28. package/dist/dist-esm/react-components/src/theming/icons.js +4 -2
  29. package/dist/dist-esm/react-components/src/theming/icons.js.map +1 -1
  30. package/dist/dist-esm/react-composites/src/composites/CallComposite/adapter/AzureCommunicationCallAdapter.js +20 -4
  31. package/dist/dist-esm/react-composites/src/composites/CallComposite/adapter/AzureCommunicationCallAdapter.js.map +1 -1
  32. package/dist/dist-esm/react-composites/src/composites/CallComposite/components/LocalDeviceSettings.d.ts +1 -0
  33. package/dist/dist-esm/react-composites/src/composites/CallComposite/components/LocalDeviceSettings.js +9 -1
  34. package/dist/dist-esm/react-composites/src/composites/CallComposite/components/LocalDeviceSettings.js.map +1 -1
  35. package/dist/dist-esm/react-composites/src/composites/CallComposite/pages/ConfigurationPage.js +3 -11
  36. package/dist/dist-esm/react-composites/src/composites/CallComposite/pages/ConfigurationPage.js.map +1 -1
  37. package/dist/dist-esm/react-composites/src/composites/CallComposite/styles/CallConfiguration.styles.d.ts +2 -0
  38. package/dist/dist-esm/react-composites/src/composites/CallComposite/styles/CallConfiguration.styles.js +24 -5
  39. package/dist/dist-esm/react-composites/src/composites/CallComposite/styles/CallConfiguration.styles.js.map +1 -1
  40. package/dist/dist-esm/react-composites/src/composites/CallComposite/utils/Utils.d.ts +9 -5
  41. package/dist/dist-esm/react-composites/src/composites/CallComposite/utils/Utils.js +15 -18
  42. package/dist/dist-esm/react-composites/src/composites/CallComposite/utils/Utils.js.map +1 -1
  43. package/dist/dist-esm/react-composites/src/composites/common/icons.d.ts +2 -1
  44. package/dist/dist-esm/react-composites/src/composites/localization/locales/de-DE/strings.json +5 -0
  45. package/dist/dist-esm/react-composites/src/composites/localization/locales/en-GB/strings.json +5 -0
  46. package/dist/dist-esm/react-composites/src/composites/localization/locales/es-ES/strings.json +5 -0
  47. package/dist/dist-esm/react-composites/src/composites/localization/locales/fr-FR/strings.json +5 -0
  48. package/dist/dist-esm/react-composites/src/composites/localization/locales/it-IT/strings.json +5 -0
  49. package/dist/dist-esm/react-composites/src/composites/localization/locales/ja-JP/strings.json +5 -0
  50. package/dist/dist-esm/react-composites/src/composites/localization/locales/ko-KR/strings.json +5 -0
  51. package/dist/dist-esm/react-composites/src/composites/localization/locales/nl-NL/strings.json +5 -0
  52. package/dist/dist-esm/react-composites/src/composites/localization/locales/pt-BR/strings.json +5 -0
  53. package/dist/dist-esm/react-composites/src/composites/localization/locales/ru-RU/strings.json +5 -0
  54. package/dist/dist-esm/react-composites/src/composites/localization/locales/tr-TR/strings.json +5 -0
  55. package/dist/dist-esm/react-composites/src/composites/localization/locales/zh-CN/strings.json +5 -0
  56. package/dist/dist-esm/react-composites/src/composites/localization/locales/zh-TW/strings.json +5 -0
  57. package/package.json +10 -10
@@ -25,14 +25,16 @@ import { _MentionPopover } from './MentionPopover';
25
25
  /* @conditional-compile-remove(mention) */
26
26
  import { useDebouncedCallback } from 'use-debounce';
27
27
  /* @conditional-compile-remove(mention) */
28
- const defaultMentionTrigger = '@';
28
+ const DEFAULT_MENTION_TRIGGER = '@';
29
+ /* @conditional-compile-remove(mention) */
30
+ const MSFT_MENTION_TAG = 'msft-mention';
29
31
  /**
30
32
  * @private
31
33
  */
32
34
  export const InputBoxComponent = (props) => {
33
- const { styles, id, 'data-ui-id': dataUiId, textValue, onChange, textFieldRef, placeholderText, onKeyDown, onEnterKeyDown, supportNewline, inputClassName, errorMessage, disabled, children,
35
+ const { styles, id, 'data-ui-id': dataUiId, textValue, onChange, textFieldRef, placeholderText, onKeyDown, onEnterKeyDown, supportNewline, inputClassName, errorMessage, disabled,
34
36
  /* @conditional-compile-remove(mention) */
35
- mentionLookupOptions } = props;
37
+ mentionLookupOptions, children } = props;
36
38
  const inputBoxRef = useRef(null);
37
39
  /* @conditional-compile-remove(mention) */
38
40
  // Current suggestion list, provided by the callback
@@ -48,10 +50,13 @@ export const InputBoxComponent = (props) => {
48
50
  /* @conditional-compile-remove(mention) */
49
51
  const [tagsValue, setTagsValue] = useState([]);
50
52
  /* @conditional-compile-remove(mention) */
53
+ // Index of the previous selection start in the text field
51
54
  const [selectionStartValue, setSelectionStartValue] = useState(null);
52
55
  /* @conditional-compile-remove(mention) */
56
+ // Index of the previous selection end in the text field
53
57
  const [selectionEndValue, setSelectionEndValue] = useState(null);
54
58
  /* @conditional-compile-remove(mention) */
59
+ // 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.
55
60
  const [shouldHandleOnMouseDownDuringSelect, setShouldHandleOnMouseDownDuringSelect] = useState(true);
56
61
  /* @conditional-compile-remove(mention) */
57
62
  // Caret position in the text field
@@ -62,24 +67,38 @@ export const InputBoxComponent = (props) => {
62
67
  /* @conditional-compile-remove(mention) */
63
68
  const localeStrings = useLocale().strings;
64
69
  /* @conditional-compile-remove(mention) */
70
+ // Set mention suggestions
65
71
  const updateMentionSuggestions = useCallback((suggestions) => {
66
- var _a;
67
72
  setMentionSuggestions(suggestions);
68
- if (caretIndex !== undefined) {
69
- (_a = textFieldRef === null || textFieldRef === void 0 ? void 0 : textFieldRef.current) === null || _a === void 0 ? void 0 : _a.setSelectionEnd(caretIndex);
70
- }
71
- }, [textFieldRef, caretIndex]);
73
+ }, [setMentionSuggestions]);
72
74
  /* @conditional-compile-remove(mention) */
73
75
  // Parse the text and get the plain text version to display in the input box
74
76
  useEffect(() => {
75
- const trigger = (mentionLookupOptions === null || mentionLookupOptions === void 0 ? void 0 : mentionLookupOptions.trigger) || defaultMentionTrigger;
76
- const [tags, plainText] = textToTagParser(textValue, trigger);
77
- setInputTextValue(plainText);
78
- setTagsValue(tags);
77
+ const trigger = (mentionLookupOptions === null || mentionLookupOptions === void 0 ? void 0 : mentionLookupOptions.trigger) || DEFAULT_MENTION_TRIGGER;
78
+ const parsedHTMLData = textToTagParser(textValue, trigger);
79
+ setInputTextValue(parsedHTMLData.plainText);
80
+ setTagsValue(parsedHTMLData.tags);
79
81
  updateMentionSuggestions([]);
80
82
  }, [textValue, mentionLookupOptions === null || mentionLookupOptions === void 0 ? void 0 : mentionLookupOptions.trigger, updateMentionSuggestions]);
81
83
  const mergedRootStyle = mergeStyles(inputBoxWrapperStyle, styles === null || styles === void 0 ? void 0 : styles.root);
82
84
  const mergedTextFiledStyle = mergeStyles(inputBoxStyle, inputClassName, props.inlineChildren ? {} : inputBoxNewLineSpaceAffordance);
85
+ /* @conditional-compile-remove(mention) */
86
+ useEffect(() => {
87
+ var _a;
88
+ // effect for caret index update
89
+ if (caretIndex === undefined || textFieldRef === undefined || (textFieldRef === null || textFieldRef === void 0 ? void 0 : textFieldRef.current) === undefined) {
90
+ return;
91
+ }
92
+ // get validated caret index between 0 and inputTextValue.length otherwise caret will be set to incorrect index
93
+ const updatedCaretIndex = getValidatedIndexInRange({
94
+ min: 0,
95
+ max: inputTextValue.length,
96
+ currentValue: caretIndex
97
+ });
98
+ (_a = textFieldRef === null || textFieldRef === void 0 ? void 0 : textFieldRef.current) === null || _a === void 0 ? void 0 : _a.setSelectionRange(updatedCaretIndex, updatedCaretIndex);
99
+ setSelectionStartValue(updatedCaretIndex);
100
+ setSelectionEndValue(updatedCaretIndex);
101
+ }, [caretIndex, inputTextValue.length, textFieldRef, setSelectionStartValue, setSelectionEndValue]);
83
102
  const mergedTextContainerStyle = mergeStyles(textContainerStyle, styles === null || styles === void 0 ? void 0 : styles.textFieldContainer);
84
103
  const mergedTextFieldStyle = concatStyleSets(textFieldStyle, {
85
104
  fieldGroup: styles === null || styles === void 0 ? void 0 : styles.textField,
@@ -94,7 +113,7 @@ export const InputBoxComponent = (props) => {
94
113
  const mergedChildrenStyle = mergeStyles(props.inlineChildren ? {} : newLineButtonsContainerStyle);
95
114
  /* @conditional-compile-remove(mention) */
96
115
  const onSuggestionSelected = useCallback((suggestion) => {
97
- var _a, _b;
116
+ var _a, _b, _c;
98
117
  let selectionEnd = ((_a = textFieldRef === null || textFieldRef === void 0 ? void 0 : textFieldRef.current) === null || _a === void 0 ? void 0 : _a.selectionEnd) || -1;
99
118
  if (selectionEnd < 0) {
100
119
  selectionEnd = 0;
@@ -106,16 +125,30 @@ export const InputBoxComponent = (props) => {
106
125
  const mention = htmlStringForMentionSuggestion(suggestion, localeStrings);
107
126
  // update plain text with the mention html text
108
127
  const newPlainText = inputTextValue.substring(0, currentTriggerStartIndex) + mention + inputTextValue.substring(selectionEnd);
109
- const triggerText = (_b = mentionLookupOptions === null || mentionLookupOptions === void 0 ? void 0 : mentionLookupOptions.trigger) !== null && _b !== void 0 ? _b : defaultMentionTrigger;
128
+ const triggerText = (_b = mentionLookupOptions === null || mentionLookupOptions === void 0 ? void 0 : mentionLookupOptions.trigger) !== null && _b !== void 0 ? _b : DEFAULT_MENTION_TRIGGER;
110
129
  // update html text with updated plain text
111
- const [updatedHTML] = updateHTML(textValue, oldPlainText, newPlainText, tagsValue, currentTriggerStartIndex, selectionEnd, mention, triggerText);
130
+ const updatedContent = updateHTML({
131
+ htmlText: textValue,
132
+ oldPlainText,
133
+ newPlainText,
134
+ tags: tagsValue,
135
+ startIndex: currentTriggerStartIndex,
136
+ oldPlainTextEndIndex: selectionEnd,
137
+ change: mention,
138
+ mentionTrigger: triggerText
139
+ });
112
140
  const displayName = getDisplayNameForMentionSuggestion(suggestion, localeStrings);
113
- // Move the caret in the text field to the end of the mention plain text
114
- setCaretIndex(selectionEnd + displayName.length);
141
+ const newCaretIndex = currentTriggerStartIndex + displayName.length + triggerText.length;
142
+ // move the caret in the text field to the end of the mention plain text
143
+ setCaretIndex(newCaretIndex);
144
+ setSelectionEndValue(newCaretIndex);
145
+ setSelectionStartValue(newCaretIndex);
115
146
  setCurrentTriggerStartIndex(-1);
116
147
  updateMentionSuggestions([]);
148
+ // set focus back to text field
149
+ (_c = textFieldRef === null || textFieldRef === void 0 ? void 0 : textFieldRef.current) === null || _c === void 0 ? void 0 : _c.focus();
117
150
  setActiveSuggestionIndex(undefined);
118
- onChange && onChange(undefined, updatedHTML);
151
+ onChange && onChange(undefined, updatedContent.updatedHTML);
119
152
  }, [
120
153
  textFieldRef,
121
154
  inputTextValue,
@@ -130,6 +163,15 @@ export const InputBoxComponent = (props) => {
130
163
  localeStrings
131
164
  ]);
132
165
  const onTextFieldKeyDown = useCallback((ev) => {
166
+ /* @conditional-compile-remove(mention) */
167
+ // caretIndex should be set to undefined when the user is typing
168
+ setCaretIndex(undefined);
169
+ // shouldHandleOnMouseDownDuringSelect should be set to false after the last mouse down event.
170
+ // it shouldn't be updated in onMouseUp
171
+ // as onMouseUp can be triggered before or after onSelect event
172
+ // because its order depends on mouse events not selection.
173
+ /* @conditional-compile-remove(mention) */
174
+ setShouldHandleOnMouseDownDuringSelect(false);
133
175
  // Uses KeyCode 229 and which code 229 to determine if the press of the enter key is from a composition session or not (Safari only)
134
176
  if (ev.nativeEvent.isComposing || ev.nativeEvent.keyCode === 229 || ev.nativeEvent.which === 229) {
135
177
  return;
@@ -185,6 +227,10 @@ export const InputBoxComponent = (props) => {
185
227
  /* @conditional-compile-remove(mention) */
186
228
  const debouncedQueryUpdate = useDebouncedCallback((query) => __awaiter(void 0, void 0, void 0, function* () {
187
229
  var _a;
230
+ if (query === undefined) {
231
+ updateMentionSuggestions([]);
232
+ return;
233
+ }
188
234
  const suggestions = (_a = (yield (mentionLookupOptions === null || mentionLookupOptions === void 0 ? void 0 : mentionLookupOptions.onQueryUpdated(query)))) !== null && _a !== void 0 ? _a : [];
189
235
  if (suggestions.length === 0) {
190
236
  setActiveSuggestionIndex(undefined);
@@ -201,24 +247,149 @@ export const InputBoxComponent = (props) => {
201
247
  };
202
248
  }, [debouncedQueryUpdate]);
203
249
  /* @conditional-compile-remove(mention) */
204
- const handleOnChange = useCallback((event, updatedValue) => __awaiter(void 0, void 0, void 0, function* () {
205
- var _b, _c;
250
+ // Update selections index in mention to navigate by words
251
+ const updateSelectionIndexesWithMentionIfNeeded = useCallback((event, inputTextValue, selectionStartValue, selectionEndValue, tagsValue) => {
252
+ var _a, _b, _c;
253
+ let updatedStartIndex = event.currentTarget.selectionStart;
254
+ let updatedEndIndex = event.currentTarget.selectionEnd;
255
+ if (event.currentTarget.selectionStart === event.currentTarget.selectionEnd &&
256
+ event.currentTarget.selectionStart !== null &&
257
+ event.currentTarget.selectionStart !== -1) {
258
+ // just a caret movement/usual typing or deleting
259
+ const mentionTag = findMentionTagForSelection(tagsValue, event.currentTarget.selectionStart);
260
+ // don't include boundary cases to show correct selection, otherwise it will show selection at mention boundaries
261
+ if (mentionTag !== undefined &&
262
+ mentionTag.plainTextBeginIndex !== undefined &&
263
+ event.currentTarget.selectionStart > mentionTag.plainTextBeginIndex &&
264
+ event.currentTarget.selectionStart < ((_a = mentionTag.plainTextEndIndex) !== null && _a !== void 0 ? _a : mentionTag.plainTextBeginIndex)) {
265
+ // get updated selection index
266
+ const newSelectionIndex = findNewSelectionIndexForMention({
267
+ tag: mentionTag,
268
+ textValue: inputTextValue,
269
+ currentSelectionIndex: event.currentTarget.selectionStart,
270
+ previousSelectionIndex: selectionStartValue !== null && selectionStartValue !== void 0 ? selectionStartValue : inputTextValue.length
271
+ });
272
+ updatedStartIndex = newSelectionIndex;
273
+ updatedEndIndex = newSelectionIndex;
274
+ }
275
+ }
276
+ else if (event.currentTarget.selectionStart !== event.currentTarget.selectionEnd) {
277
+ // Both e.currentTarget.selectionStart !== selectionStartValue and e.currentTarget.selectionEnd !== selectionEndValue can be true when a user selects a text by double click
278
+ if (event.currentTarget.selectionStart !== null && event.currentTarget.selectionStart !== selectionStartValue) {
279
+ // the selection start is changed
280
+ const mentionTag = findMentionTagForSelection(tagsValue, event.currentTarget.selectionStart);
281
+ // don't include boundary cases to show correct selection, otherwise it will show selection at mention boundaries
282
+ if (mentionTag !== undefined &&
283
+ mentionTag.plainTextBeginIndex !== undefined &&
284
+ event.currentTarget.selectionStart > mentionTag.plainTextBeginIndex &&
285
+ event.currentTarget.selectionStart < ((_b = mentionTag.plainTextEndIndex) !== null && _b !== void 0 ? _b : mentionTag.plainTextBeginIndex)) {
286
+ updatedStartIndex = findNewSelectionIndexForMention({
287
+ tag: mentionTag,
288
+ textValue: inputTextValue,
289
+ currentSelectionIndex: event.currentTarget.selectionStart,
290
+ previousSelectionIndex: selectionStartValue !== null && selectionStartValue !== void 0 ? selectionStartValue : inputTextValue.length
291
+ });
292
+ }
293
+ }
294
+ if (event.currentTarget.selectionEnd !== null && event.currentTarget.selectionEnd !== selectionEndValue) {
295
+ // the selection end is changed
296
+ const mentionTag = findMentionTagForSelection(tagsValue, event.currentTarget.selectionEnd);
297
+ // don't include boundary cases to show correct selection, otherwise it will show selection at mention boundaries
298
+ if (mentionTag !== undefined &&
299
+ mentionTag.plainTextBeginIndex !== undefined &&
300
+ event.currentTarget.selectionEnd > mentionTag.plainTextBeginIndex &&
301
+ event.currentTarget.selectionEnd < ((_c = mentionTag.plainTextEndIndex) !== null && _c !== void 0 ? _c : mentionTag.plainTextBeginIndex)) {
302
+ updatedEndIndex = findNewSelectionIndexForMention({
303
+ tag: mentionTag,
304
+ textValue: inputTextValue,
305
+ currentSelectionIndex: event.currentTarget.selectionEnd,
306
+ previousSelectionIndex: selectionEndValue !== null && selectionEndValue !== void 0 ? selectionEndValue : inputTextValue.length
307
+ });
308
+ }
309
+ }
310
+ }
311
+ // e.currentTarget.selectionDirection should be set to handle shift + arrow keys
312
+ if (event.currentTarget.selectionDirection === null) {
313
+ event.currentTarget.setSelectionRange(updatedStartIndex, updatedEndIndex);
314
+ }
315
+ else {
316
+ event.currentTarget.setSelectionRange(updatedStartIndex, updatedEndIndex, event.currentTarget.selectionDirection);
317
+ }
318
+ setSelectionStartValue(updatedStartIndex);
319
+ setSelectionEndValue(updatedEndIndex);
320
+ }, [setSelectionStartValue, setSelectionEndValue]);
321
+ /* @conditional-compile-remove(mention) */
322
+ const handleOnSelect = useCallback((event, inputTextValue, tags, shouldHandleOnMouseDownDuringSelect, selectionStartValue, selectionEndValue) => {
323
+ var _a, _b, _c;
324
+ /* @conditional-compile-remove(mention) */
325
+ if (shouldHandleOnMouseDownDuringSelect && event.currentTarget.selectionStart !== null) {
326
+ // on select was triggered by mouse down
327
+ const mentionTag = findMentionTagForSelection(tags, event.currentTarget.selectionStart);
328
+ if (mentionTag !== undefined && mentionTag.plainTextBeginIndex !== undefined) {
329
+ // handle mention click
330
+ if (event.currentTarget.selectionDirection === null) {
331
+ event.currentTarget.setSelectionRange(mentionTag.plainTextBeginIndex, (_a = mentionTag.plainTextEndIndex) !== null && _a !== void 0 ? _a : mentionTag.plainTextBeginIndex);
332
+ }
333
+ else {
334
+ event.currentTarget.setSelectionRange(mentionTag.plainTextBeginIndex, (_b = mentionTag.plainTextEndIndex) !== null && _b !== void 0 ? _b : mentionTag.plainTextBeginIndex, event.currentTarget.selectionDirection);
335
+ }
336
+ setSelectionStartValue(mentionTag.plainTextBeginIndex);
337
+ setSelectionEndValue((_c = mentionTag.plainTextEndIndex) !== null && _c !== void 0 ? _c : mentionTag.plainTextBeginIndex);
338
+ }
339
+ else {
340
+ setSelectionStartValue(event.currentTarget.selectionStart);
341
+ setSelectionEndValue(event.currentTarget.selectionEnd);
342
+ }
343
+ }
344
+ else {
345
+ // selection was changed by keyboard
346
+ updateSelectionIndexesWithMentionIfNeeded(event, inputTextValue, selectionStartValue, selectionEndValue, tags);
347
+ }
348
+ // don't set setShouldHandleOnMouseDownDuringSelect(false) here as setSelectionRange could trigger additional calls of onSelect event and they may not be handled correctly (because of setSelectionRange calls or rerender)
349
+ }, [updateSelectionIndexesWithMentionIfNeeded]);
350
+ /* @conditional-compile-remove(mention) */
351
+ const handleOnChange = useCallback((event, tagsValue, htmlTextValue, inputTextValue, currentTriggerStartIndex, previousSelectionStart, previousSelectionEnd, currentSelectionStart, currentSelectionEnd, updatedValue) => __awaiter(void 0, void 0, void 0, function* () {
352
+ var _b;
353
+ if (event.currentTarget === null) {
354
+ return;
355
+ }
356
+ // handle backspace change
357
+ // onSelect is not called for backspace as selection is not changed and local caret index is outdated
358
+ setCaretIndex(undefined);
206
359
  const newValue = updatedValue !== null && updatedValue !== void 0 ? updatedValue : '';
207
- const triggerText = (_b = mentionLookupOptions === null || mentionLookupOptions === void 0 ? void 0 : mentionLookupOptions.trigger) !== null && _b !== void 0 ? _b : defaultMentionTrigger;
360
+ const triggerText = (_b = mentionLookupOptions === null || mentionLookupOptions === void 0 ? void 0 : mentionLookupOptions.trigger) !== null && _b !== void 0 ? _b : DEFAULT_MENTION_TRIGGER;
208
361
  const newTextLength = newValue.length;
209
- let selectionEnd = ((_c = textFieldRef === null || textFieldRef === void 0 ? void 0 : textFieldRef.current) === null || _c === void 0 ? void 0 : _c.selectionEnd) || -1;
210
- selectionEnd = Math.max(0, selectionEnd);
211
- selectionEnd = Math.min(selectionEnd, newTextLength - 1);
362
+ // updating indexes to set between 0 and text length, otherwise selectionRange won't be set correctly
363
+ const currentSelectionEndValue = getValidatedIndexInRange({
364
+ min: 0,
365
+ max: newTextLength,
366
+ currentValue: currentSelectionEnd
367
+ });
368
+ const currentSelectionStartValue = getValidatedIndexInRange({
369
+ min: 0,
370
+ max: newTextLength,
371
+ currentValue: currentSelectionStart
372
+ });
373
+ const previousSelectionStartValue = getValidatedIndexInRange({
374
+ min: 0,
375
+ max: inputTextValue.length,
376
+ currentValue: previousSelectionStart
377
+ });
378
+ const previousSelectionEndValue = getValidatedIndexInRange({
379
+ min: 0,
380
+ max: inputTextValue.length,
381
+ currentValue: previousSelectionEnd
382
+ });
212
383
  // If we are enabled for lookups,
213
384
  if (mentionLookupOptions !== undefined) {
214
385
  // Look at the range of the change for a trigger character
215
- const triggerPriorIndex = newValue.lastIndexOf(triggerText, selectionEnd - 1);
386
+ const triggerPriorIndex = newValue.lastIndexOf(triggerText, currentSelectionEndValue - 1);
216
387
  // Update the caret position, if not doing a lookup
217
388
  setCaretPosition(Caret.getRelativePosition(event.currentTarget));
218
389
  if (triggerPriorIndex !== undefined) {
219
390
  // trigger is found
220
391
  const isSpaceBeforeTrigger = newValue.substring(triggerPriorIndex - 1, triggerPriorIndex) === ' ';
221
- const wordAtSelection = newValue.substring(triggerPriorIndex, selectionEnd);
392
+ const wordAtSelection = newValue.substring(triggerPriorIndex, currentSelectionEndValue);
222
393
  let tagIndex = currentTriggerStartIndex;
223
394
  if (!isSpaceBeforeTrigger && triggerPriorIndex !== 0) {
224
395
  //no space before the trigger <- continuation of the previous word
@@ -227,14 +398,14 @@ export const InputBoxComponent = (props) => {
227
398
  }
228
399
  else if (wordAtSelection === triggerText) {
229
400
  // start of the mention
230
- tagIndex = selectionEnd - triggerText.length;
401
+ tagIndex = currentSelectionEndValue - triggerText.length;
231
402
  if (tagIndex < 0) {
232
403
  tagIndex = 0;
233
404
  }
234
405
  setCurrentTriggerStartIndex(tagIndex);
235
406
  }
236
407
  if (tagIndex === -1) {
237
- updateMentionSuggestions([]);
408
+ yield debouncedQueryUpdate(undefined);
238
409
  }
239
410
  else {
240
411
  // In the middle of a @mention lookup
@@ -249,88 +420,42 @@ export const InputBoxComponent = (props) => {
249
420
  }
250
421
  let result = '';
251
422
  if (tagsValue.length === 0) {
252
- // no tags in the string, textValue is a sting
423
+ // no tags in the string and newValue should be used as a result string
253
424
  result = newValue;
254
425
  }
255
426
  else {
256
- // there are tags in the text value, textValue is html string
257
- const { changeStart, oldChangeEnd, newChangeEnd } = findStringsDiffIndexes(inputTextValue, newValue, selectionEnd);
258
- // get updated html string
427
+ // there are tags in the text value and htmlTextValue is html string
428
+ // find diff between old and new text
429
+ const { changeStart, oldChangeEnd, newChangeEnd } = findStringsDiffIndexes({
430
+ oldText: inputTextValue,
431
+ newText: newValue,
432
+ previousSelectionStart: previousSelectionStartValue,
433
+ previousSelectionEnd: previousSelectionEndValue,
434
+ currentSelectionStart: currentSelectionStartValue,
435
+ currentSelectionEnd: currentSelectionEndValue
436
+ });
259
437
  const change = newValue.substring(changeStart, newChangeEnd);
260
- const [updatedHTML, updatedChangeNewEndIndex] = updateHTML(textValue, inputTextValue, newValue, tagsValue, changeStart, oldChangeEnd, change, triggerText);
261
- result = updatedHTML;
262
- if (updatedChangeNewEndIndex !== null) {
263
- if ((change.length === 1 && event.currentTarget.selectionStart === event.currentTarget.selectionEnd) || // simple input
264
- (change.length === 0 && newChangeEnd === changeStart) //delete
265
- ) {
266
- setCaretIndex(updatedChangeNewEndIndex);
267
- }
438
+ // get updated html string
439
+ const updatedContent = updateHTML({
440
+ htmlText: htmlTextValue,
441
+ oldPlainText: inputTextValue,
442
+ newPlainText: newValue,
443
+ tags: tagsValue,
444
+ startIndex: changeStart,
445
+ oldPlainTextEndIndex: oldChangeEnd,
446
+ change,
447
+ mentionTrigger: triggerText
448
+ });
449
+ result = updatedContent.updatedHTML;
450
+ // update caret index if needed
451
+ if (updatedContent.updatedSelectionIndex !== null) {
452
+ setCaretIndex(updatedContent.updatedSelectionIndex);
453
+ setSelectionEndValue(updatedContent.updatedSelectionIndex);
454
+ setSelectionStartValue(updatedContent.updatedSelectionIndex);
268
455
  }
269
456
  }
270
457
  onChange && onChange(event, result);
271
- }), [
272
- onChange,
273
- mentionLookupOptions,
274
- tagsValue,
275
- textValue,
276
- inputTextValue,
277
- currentTriggerStartIndex,
278
- setCaretIndex,
279
- setCaretPosition,
280
- updateMentionSuggestions,
281
- debouncedQueryUpdate,
282
- textFieldRef
283
- ]);
284
- /* @conditional-compile-remove(mention) */
285
- const updateSelectionIndexesWithMentionIfNeeded = (event) => {
286
- var _a;
287
- let updatedStartIndex = event.currentTarget.selectionStart;
288
- let updatedEndIndex = event.currentTarget.selectionEnd;
289
- if (event.currentTarget.selectionStart === event.currentTarget.selectionEnd &&
290
- event.currentTarget.selectionStart !== null &&
291
- event.currentTarget.selectionStart !== -1) {
292
- const mentionTag = findMentionTagForSelection(tagsValue, event.currentTarget.selectionStart);
293
- if (mentionTag !== undefined && mentionTag.plainTextBeginIndex !== undefined) {
294
- if (selectionStartValue === null) {
295
- updatedStartIndex = mentionTag.plainTextBeginIndex;
296
- updatedEndIndex = (_a = mentionTag.plainTextEndIndex) !== null && _a !== void 0 ? _a : mentionTag.plainTextBeginIndex;
297
- }
298
- else {
299
- const newSelectionIndex = findNewSelectionIndexForMention(mentionTag, inputTextValue, event.currentTarget.selectionStart, selectionStartValue);
300
- updatedStartIndex = newSelectionIndex;
301
- updatedEndIndex = newSelectionIndex;
302
- }
303
- }
304
- }
305
- else if (event.currentTarget.selectionStart !== event.currentTarget.selectionEnd) {
306
- // Both e.currentTarget.selectionStart !== selectionStartValue and e.currentTarget.selectionEnd !== selectionEndValue can be true when a user selects a text by double click
307
- if (event.currentTarget.selectionStart !== null && event.currentTarget.selectionStart !== selectionStartValue) {
308
- // the selection start is changed
309
- const mentionTag = findMentionTagForSelection(tagsValue, event.currentTarget.selectionStart);
310
- if (mentionTag !== undefined && mentionTag.plainTextBeginIndex !== undefined) {
311
- //TODO: here it takes -1 when it shouldn't, update selectionstart and end with mouse move and or touch move
312
- updatedStartIndex = findNewSelectionIndexForMention(mentionTag, inputTextValue, event.currentTarget.selectionStart, selectionStartValue !== null && selectionStartValue !== void 0 ? selectionStartValue : -1);
313
- }
314
- }
315
- if (event.currentTarget.selectionEnd !== null && event.currentTarget.selectionEnd !== selectionEndValue) {
316
- // the selection end is changed
317
- const mentionTag = findMentionTagForSelection(tagsValue, event.currentTarget.selectionEnd);
318
- if (mentionTag !== undefined && mentionTag.plainTextBeginIndex !== undefined) {
319
- //TODO: here it takes -1 when it shouldn't, update selectionstart and end with mouse move and or touch move
320
- updatedEndIndex = findNewSelectionIndexForMention(mentionTag, inputTextValue, event.currentTarget.selectionEnd, selectionEndValue !== null && selectionEndValue !== void 0 ? selectionEndValue : -1);
321
- }
322
- }
323
- }
324
- // e.currentTarget.selectionDirection should be set to handle shift + arrow keys
325
- if (event.currentTarget.selectionDirection === null) {
326
- event.currentTarget.setSelectionRange(updatedStartIndex, updatedEndIndex);
327
- }
328
- else {
329
- event.currentTarget.setSelectionRange(updatedStartIndex, updatedEndIndex, event.currentTarget.selectionDirection);
330
- }
331
- setSelectionStartValue(updatedStartIndex);
332
- setSelectionEndValue(updatedEndIndex);
333
- };
458
+ }), [onChange, mentionLookupOptions, setCaretIndex, setCaretPosition, debouncedQueryUpdate]);
334
459
  const getInputFieldTextValue = () => {
335
460
  /* @conditional-compile-remove(mention) */
336
461
  return inputTextValue;
@@ -342,61 +467,52 @@ export const InputBoxComponent = (props) => {
342
467
  updateMentionSuggestions([]);
343
468
  } })),
344
469
  React.createElement(TextField, { autoFocus: props.autoFocus === 'sendBoxTextField', "data-ui-id": dataUiId, multiline: true, autoAdjustHeight: true, multiple: false, resizable: false, componentRef: textFieldRef, id: id, inputClassName: mergedTextFiledStyle, placeholder: placeholderText, value: getInputFieldTextValue(), onChange: (e, newValue) => {
470
+ // Remove when switching to react 17+, currently needed because of https://legacy.reactjs.org/docs/legacy-event-pooling.html
471
+ /* @conditional-compile-remove(mention) */
472
+ // Prevents React from resetting event's properties
473
+ e.persist();
345
474
  /* @conditional-compile-remove(mention) */
346
475
  setInputTextValue(newValue !== null && newValue !== void 0 ? newValue : '');
347
476
  /* @conditional-compile-remove(mention) */
348
- handleOnChange(e, newValue);
477
+ handleOnChange(e, tagsValue, textValue, inputTextValue, currentTriggerStartIndex, selectionStartValue === null ? undefined : selectionStartValue, selectionEndValue === null ? undefined : selectionEndValue, e.currentTarget.selectionStart === null ? undefined : e.currentTarget.selectionStart, e.currentTarget.selectionEnd === null ? undefined : e.currentTarget.selectionEnd, newValue);
349
478
  /* @conditional-compile-remove(mention) */
350
479
  return;
351
480
  onChange(e, newValue);
352
481
  },
353
482
  /* @conditional-compile-remove(mention) */
354
483
  onSelect: (e) => {
355
- var _a, _b, _c;
484
+ // update selection if needed
356
485
  if (caretIndex !== undefined) {
357
- e.currentTarget.setSelectionRange(caretIndex, caretIndex);
358
486
  setCaretIndex(undefined);
359
- return;
360
- }
361
- //TODO: need to check to navigate before/after space correctly in tag + when selecting by mouse
362
- /* @conditional-compile-remove(mention) */
363
- if (shouldHandleOnMouseDownDuringSelect &&
364
- e.currentTarget.selectionStart !== null &&
365
- e.currentTarget.selectionStart === e.currentTarget.selectionEnd) {
366
- // handle mention click
367
- const mentionTag = findMentionTagForSelection(tagsValue, e.currentTarget.selectionStart);
368
- if (mentionTag !== undefined && mentionTag.plainTextBeginIndex !== undefined) {
369
- if (e.currentTarget.selectionDirection === null) {
370
- e.currentTarget.setSelectionRange(mentionTag.plainTextBeginIndex, (_a = mentionTag.plainTextEndIndex) !== null && _a !== void 0 ? _a : mentionTag.plainTextBeginIndex);
371
- }
372
- else {
373
- e.currentTarget.setSelectionRange(mentionTag.plainTextBeginIndex, (_b = mentionTag.plainTextEndIndex) !== null && _b !== void 0 ? _b : mentionTag.plainTextBeginIndex, e.currentTarget.selectionDirection);
374
- }
375
- setSelectionStartValue(mentionTag.plainTextBeginIndex);
376
- setSelectionEndValue((_c = mentionTag.plainTextEndIndex) !== null && _c !== void 0 ? _c : mentionTag.plainTextBeginIndex);
377
- }
378
- else {
379
- setSelectionStartValue(e.currentTarget.selectionStart);
380
- setSelectionEndValue(e.currentTarget.selectionEnd);
487
+ // sometimes setting selectionRage in effect for updating caretIndex doesn't work as expected and onSelect should handle this case
488
+ if (caretIndex !== e.currentTarget.selectionStart || caretIndex !== e.currentTarget.selectionEnd) {
489
+ e.currentTarget.setSelectionRange(caretIndex, caretIndex);
381
490
  }
491
+ return;
382
492
  }
383
- else {
384
- updateSelectionIndexesWithMentionIfNeeded(e);
385
- }
386
- /* @conditional-compile-remove(mention) */
387
- setShouldHandleOnMouseDownDuringSelect(false);
388
- }, onMouseDown: () => {
493
+ handleOnSelect(e, inputTextValue, tagsValue, shouldHandleOnMouseDownDuringSelect, selectionStartValue, selectionEndValue);
494
+ },
495
+ /* @conditional-compile-remove(mention) */
496
+ onMouseDown: () => {
389
497
  // as events order is onMouseDown -> onSelect -> onClick
390
498
  // onClick and onMouseDown can't handle clicking on mention event because
391
- // onClick has wrong range as it's called after onSelect
392
- // onMouseDown doesn't have correct selectionRange yet
499
+ // onMouseDown doesn't have correct selectionRange yet and
500
+ // onClick already has wrong range as it's called after onSelect that updates the selection range
393
501
  // so we need to handle onMouseDown to prevent onSelect default behavior
394
- /* @conditional-compile-remove(mention) */
395
502
  setShouldHandleOnMouseDownDuringSelect(true);
396
- }, onTouchStart: () => {
503
+ },
504
+ /* @conditional-compile-remove(mention) */
505
+ onTouchStart: () => {
397
506
  // see onMouseDown for more details
398
- /* @conditional-compile-remove(mention) */
399
507
  setShouldHandleOnMouseDownDuringSelect(true);
508
+ },
509
+ /* @conditional-compile-remove(mention) */
510
+ onBlur: () => {
511
+ // setup all flags to default values when text field loses focus
512
+ setShouldHandleOnMouseDownDuringSelect(false);
513
+ setCaretIndex(undefined);
514
+ setSelectionStartValue(null);
515
+ setSelectionEndValue(null);
400
516
  }, autoComplete: "off", onKeyDown: onTextFieldKeyDown, styles: mergedTextFieldStyle, disabled: disabled, errorMessage: errorMessage, onRenderSuffix: onRenderChildren, elementRef: inputBoxRef }))));
401
517
  };
402
518
  /**
@@ -425,77 +541,93 @@ export const InputBoxButton = (props) => {
425
541
  };
426
542
  /* @conditional-compile-remove(mention) */
427
543
  /**
428
- * Find mention tag if selection is inside of it
544
+ * Get validated value for index between min and max values. If currentValue is not defined, -1 will be used instead.
429
545
  *
430
546
  * @private
547
+ * @param props - Props for finding a valid index in range.
548
+ * @returns Valid index in the range.
549
+ */
550
+ const getValidatedIndexInRange = (props) => {
551
+ const { min, max, currentValue } = props;
552
+ let updatedValue = currentValue !== null && currentValue !== void 0 ? currentValue : -1;
553
+ updatedValue = Math.max(min, updatedValue);
554
+ updatedValue = Math.min(updatedValue, max);
555
+ return updatedValue;
556
+ };
557
+ /* @conditional-compile-remove(mention) */
558
+ /**
559
+ * Find mention tag for selection if exists.
560
+ *
561
+ * @private
562
+ * @param tags - Existing list of tags.
563
+ * @param selection - Selection index.
564
+ * @returns Mention tag if exists, otherwise undefined.
431
565
  */
432
566
  const findMentionTagForSelection = (tags, selection) => {
433
567
  let mentionTag = undefined;
434
568
  for (let i = 0; i < tags.length; i++) {
435
569
  const tag = tags[i];
436
- let plainTextEndIndex = 0;
437
- if (tag.plainTextEndIndex !== undefined && tag.closeTagIdx !== undefined) {
438
- // close tag exists
439
- plainTextEndIndex = tag.plainTextEndIndex;
440
- }
441
- else if (tag.plainTextBeginIndex !== undefined) {
442
- //no close tag
443
- plainTextEndIndex = tag.plainTextBeginIndex;
570
+ const closingTagInfo = getTagClosingTagInfo(tag);
571
+ if (tag.plainTextBeginIndex !== undefined && tag.plainTextBeginIndex > selection) {
572
+ // no need to check further as the selection is before the tag
573
+ break;
444
574
  }
445
- if (tag.subTags !== undefined && tag.subTags.length !== 0) {
446
- const selectedTag = findMentionTagForSelection(tag.subTags, selection);
447
- if (selectedTag !== undefined) {
448
- mentionTag = selectedTag;
575
+ else if (tag.plainTextBeginIndex !== undefined &&
576
+ tag.plainTextBeginIndex <= selection &&
577
+ selection <= closingTagInfo.plainTextEndIndex) {
578
+ // no need to check if tag doesn't contain selection
579
+ if (tag.subTags !== undefined && tag.subTags.length !== 0) {
580
+ const selectedTag = findMentionTagForSelection(tag.subTags, selection);
581
+ if (selectedTag !== undefined) {
582
+ mentionTag = selectedTag;
583
+ break;
584
+ }
585
+ }
586
+ else if (tag.tagType === MSFT_MENTION_TAG) {
587
+ mentionTag = tag;
449
588
  break;
450
589
  }
451
590
  }
452
- else if (tag.tagType === 'msft-mention' &&
453
- tag.plainTextBeginIndex !== undefined &&
454
- tag.plainTextBeginIndex < selection &&
455
- selection < plainTextEndIndex) {
456
- mentionTag = tag;
457
- break;
458
- }
459
591
  }
460
592
  return mentionTag;
461
593
  };
462
594
  /* @conditional-compile-remove(mention) */
463
595
  /**
464
- * Go through tags and find a new the selection index if it is inside of a mention tag
596
+ * Find a new the selection index.
465
597
  *
466
598
  * @private
599
+ * @param props - Props for finding new selection index for mention.
600
+ * @returns New selection index if it is inside of a mention tag, otherwise the current selection.
467
601
  */
468
- const findNewSelectionIndexForMention = (tag, textValue, selection, previousSelection) => {
602
+ const findNewSelectionIndexForMention = (props) => {
469
603
  var _a;
470
- if (tag.plainTextBeginIndex === undefined ||
471
- tag.tagType !== 'msft-mention' ||
472
- selection === previousSelection ||
604
+ const { tag, textValue, currentSelectionIndex, previousSelectionIndex } = props;
605
+ // check if this is a mention tag and selection should be updated
606
+ if (tag.tagType !== MSFT_MENTION_TAG ||
607
+ tag.plainTextBeginIndex === undefined ||
608
+ currentSelectionIndex === previousSelectionIndex ||
473
609
  tag.plainTextEndIndex === undefined) {
474
- return selection;
610
+ return currentSelectionIndex;
475
611
  }
476
612
  let spaceIndex = 0;
477
- if (selection <= previousSelection) {
478
- // the cursor is moved to the left
479
- spaceIndex = textValue.lastIndexOf(' ', selection !== null && selection !== void 0 ? selection : 0);
613
+ if (currentSelectionIndex <= previousSelectionIndex) {
614
+ // the cursor is moved to the left, find the last index before the cursor
615
+ spaceIndex = textValue.lastIndexOf(' ', currentSelectionIndex !== null && currentSelectionIndex !== void 0 ? currentSelectionIndex : 0);
480
616
  if (spaceIndex === -1) {
481
- // no space before the selection
617
+ // no space before the selection, use the beginning of the tag
482
618
  spaceIndex = tag.plainTextBeginIndex;
483
619
  }
484
620
  }
485
621
  else {
486
- // the cursor is moved to the right
487
- spaceIndex = textValue.indexOf(' ', selection !== null && selection !== void 0 ? selection : 0);
622
+ // the cursor is moved to the right, find the fist index after the cursor
623
+ spaceIndex = textValue.indexOf(' ', currentSelectionIndex !== null && currentSelectionIndex !== void 0 ? currentSelectionIndex : 0);
488
624
  if (spaceIndex === -1) {
489
- // no space after the selection
625
+ // no space after the selection, use the end of the tag
490
626
  spaceIndex = (_a = tag.plainTextEndIndex) !== null && _a !== void 0 ? _a : tag.plainTextBeginIndex;
491
627
  }
492
628
  }
493
- if (spaceIndex < tag.plainTextBeginIndex) {
494
- spaceIndex = tag.plainTextBeginIndex;
495
- }
496
- else if (spaceIndex > tag.plainTextEndIndex) {
497
- spaceIndex = tag.plainTextEndIndex;
498
- }
629
+ spaceIndex = Math.max(tag.plainTextBeginIndex, spaceIndex);
630
+ spaceIndex = Math.min(tag.plainTextEndIndex, spaceIndex);
499
631
  return spaceIndex;
500
632
  };
501
633
  /* @conditional-compile-remove(mention) */
@@ -503,10 +635,21 @@ const findNewSelectionIndexForMention = (tag, textValue, selection, previousSele
503
635
  * Handle mention tag edit and by word deleting
504
636
  *
505
637
  * @private
638
+ * @param props - Props for mention update HTML function.
639
+ * @returns Updated texts and indexes.
506
640
  */
507
- const handleMentionTagUpdate = (htmlText, oldPlainText, lastProcessedHTMLIndex, processedChange, tag, closeTagIdx, closeTagLength, plainTextEndIndex, startIndex, oldPlainTextEndIndex, mentionTagLength) => {
508
- if (tag.tagType !== 'msft-mention' || tag.plainTextBeginIndex === undefined) {
509
- return ['', processedChange, lastProcessedHTMLIndex, null];
641
+ const handleMentionTagUpdate = (props) => {
642
+ const { htmlText, oldPlainText, change, tag, closeTagIdx, closeTagLength, plainTextEndIndex, startIndex, oldPlainTextEndIndex, mentionTagLength } = props;
643
+ let processedChange = props.processedChange;
644
+ let lastProcessedHTMLIndex = props.lastProcessedHTMLIndex;
645
+ if (tag.tagType !== MSFT_MENTION_TAG || tag.plainTextBeginIndex === undefined) {
646
+ // not a mention tag
647
+ return {
648
+ result: '',
649
+ updatedChange: processedChange,
650
+ htmlIndex: lastProcessedHTMLIndex,
651
+ plainTextSelectionEndIndex: null
652
+ };
510
653
  }
511
654
  let result = '';
512
655
  let plainTextSelectionEndIndex = null;
@@ -529,9 +672,11 @@ const handleMentionTagUpdate = (htmlText, oldPlainText, lastProcessedHTMLIndex,
529
672
  }
530
673
  isSpaceLengthHandled = true;
531
674
  if (rangeStart === -1 || rangeStart === undefined || rangeStart < tag.plainTextBeginIndex) {
675
+ // rangeStart should be at least equal to tag.plainTextBeginIndex
532
676
  rangeStart = tag.plainTextBeginIndex;
533
677
  }
534
678
  if (rangeEnd > plainTextEndIndex) {
679
+ // rangeEnd should be at most equal to plainTextEndIndex
535
680
  rangeEnd = plainTextEndIndex;
536
681
  }
537
682
  if (rangeStart === tag.plainTextBeginIndex && rangeEnd === plainTextEndIndex) {
@@ -551,28 +696,61 @@ const handleMentionTagUpdate = (htmlText, oldPlainText, lastProcessedHTMLIndex,
551
696
  }
552
697
  endChangeDiff = rangeEnd - tag.plainTextBeginIndex - mentionTagLength;
553
698
  result += htmlText.substring(lastProcessedHTMLIndex, tag.openTagIdx + tag.openTagBody.length + startChangeDiff);
554
- // plainTextSelectionEndIndex = rangeStart + processedChange.length;
699
+ if (startIndex < tag.plainTextBeginIndex) {
700
+ // if the change is before the tag, the selection should start from startIndex (rangeStart will be equal to tag.plainTextBeginIndex)
701
+ plainTextSelectionEndIndex = startIndex + change.length;
702
+ }
703
+ else {
704
+ // if the change is inside the tag, the selection should start with rangeStart
705
+ plainTextSelectionEndIndex = rangeStart + processedChange.length;
706
+ }
555
707
  lastProcessedHTMLIndex = tag.openTagIdx + tag.openTagBody.length + endChangeDiff;
556
708
  // processed change should not be changed as it should be added after the tag
557
709
  }
558
- return [result, processedChange, lastProcessedHTMLIndex, plainTextSelectionEndIndex];
710
+ return { result, updatedChange: processedChange, htmlIndex: lastProcessedHTMLIndex, plainTextSelectionEndIndex };
559
711
  };
560
712
  /* @conditional-compile-remove(mention) */
561
713
  /**
562
- * Go through the text and update it with the changed text
714
+ * Get closing tag information if exists otherwise return information as for self closing tag
563
715
  *
564
716
  * @private
717
+ * @param tag - Tag data.
718
+ * @returns Closing tag information for the provided tag.
565
719
  */
566
- const updateHTML = (htmlText, oldPlainText, newPlainText, tags, startIndex, oldPlainTextEndIndex, change, mentionTrigger) => {
567
- let result = '';
568
- if (tags.length === 0) {
569
- // no tags added yet
570
- return [newPlainText, null];
720
+ const getTagClosingTagInfo = (tag) => {
721
+ let plainTextEndIndex = 0;
722
+ let closeTagIdx = 0;
723
+ let closeTagLength = 0;
724
+ if (tag.plainTextEndIndex !== undefined && tag.closeTagIdx !== undefined) {
725
+ // close tag exists
726
+ plainTextEndIndex = tag.plainTextEndIndex;
727
+ closeTagIdx = tag.closeTagIdx;
728
+ // tag.tagType.length + </>
729
+ closeTagLength = tag.tagType.length + 3;
730
+ }
731
+ else if (tag.plainTextBeginIndex !== undefined) {
732
+ // no close tag
733
+ plainTextEndIndex = tag.plainTextBeginIndex;
734
+ closeTagIdx = tag.openTagIdx + tag.openTagBody.length;
735
+ closeTagLength = 0;
571
736
  }
572
- if (startIndex === 0 && oldPlainTextEndIndex === oldPlainText.length) {
573
- // the whole text is changed
574
- return [newPlainText, null];
737
+ return { plainTextEndIndex, closeTagIdx, closeTagLength };
738
+ };
739
+ /* @conditional-compile-remove(mention) */
740
+ /**
741
+ * Go through the text and update it with the changed text
742
+ *
743
+ * @private
744
+ * @param props - Props for update HTML function.
745
+ * @returns Updated HTML and selection index if the selection index should be set.
746
+ */
747
+ const updateHTML = (props) => {
748
+ const { htmlText, oldPlainText, newPlainText, tags, startIndex, oldPlainTextEndIndex, change, mentionTrigger } = props;
749
+ if (tags.length === 0 || (startIndex === 0 && oldPlainTextEndIndex === oldPlainText.length - 1)) {
750
+ // no tags added yet or the whole text is changed
751
+ return { updatedHTML: newPlainText, updatedSelectionIndex: null };
575
752
  }
753
+ let result = '';
576
754
  let lastProcessedHTMLIndex = 0;
577
755
  // the value can be updated with empty string when the change covers more than 1 place (tag + before or after the tag)
578
756
  // in this case change won't be added as part of the tag
@@ -583,7 +761,7 @@ const updateHTML = (htmlText, oldPlainText, newPlainText, tags, startIndex, oldP
583
761
  let processedChange = change;
584
762
  // end tag plain text index of the last processed tag
585
763
  let lastProcessedPlainTextTagEndIndex = 0;
586
- // as some tags/text can be removed fully, selectionEnd should be updated correctly
764
+ // as some tags/text can be removed fully, selection should be updated correctly
587
765
  let changeNewEndIndex = null;
588
766
  for (let i = 0; i < tags.length; i++) {
589
767
  const tag = tags[i];
@@ -592,165 +770,225 @@ const updateHTML = (htmlText, oldPlainText, newPlainText, tags, startIndex, oldP
592
770
  }
593
771
  // all plain text indexes includes trigger length for the mention that shouldn't be included in
594
772
  // htmlText.substring because html strings don't include the trigger
595
- // mentionTagLength will be set only for 'msft-mention', otherwise should be 0
773
+ // mentionTagLength will be set only for mention tag, otherwise should be 0
596
774
  let mentionTagLength = 0;
597
775
  let isMentionTag = false;
598
- if (tag.tagType === 'msft-mention') {
776
+ if (tag.tagType === MSFT_MENTION_TAG) {
599
777
  mentionTagLength = mentionTrigger.length;
600
778
  isMentionTag = true;
601
779
  }
602
- //change start is before the open tag
603
780
  if (startIndex <= tag.plainTextBeginIndex) {
781
+ // change start is before the open tag
604
782
  // Math.max(lastProcessedPlainTextTagEndIndex, startIndex) is used as startIndex may not be in [[previous tag].plainTextEndIndex - tag.plainTextBeginIndex] range
605
783
  const startChangeDiff = tag.plainTextBeginIndex - Math.max(lastProcessedPlainTextTagEndIndex, startIndex);
606
784
  result += htmlText.substring(lastProcessedHTMLIndex, tag.openTagIdx - startChangeDiff) + processedChange;
785
+ processedChange = '';
607
786
  if (oldPlainTextEndIndex <= tag.plainTextBeginIndex) {
608
787
  // the whole change is before tag start
609
- // oldPlainTextEndIndex already includes mentionTag length
788
+ // mentionTag length can be ignored here as the change is before the tag
610
789
  const endChangeDiff = tag.plainTextBeginIndex - oldPlainTextEndIndex;
611
790
  lastProcessedHTMLIndex = tag.openTagIdx - endChangeDiff;
612
- processedChange = '';
613
791
  // the change is handled; exit
614
792
  break;
615
793
  }
616
794
  else {
617
795
  // change continues in the tag
618
796
  lastProcessedHTMLIndex = tag.openTagIdx;
619
- processedChange = '';
620
797
  // proceed to the next check
621
798
  }
622
799
  }
623
- let plainTextEndIndex = 0;
624
- let closeTagIdx = 0;
625
- let closeTagLength = 0;
626
- if (tag.plainTextEndIndex !== undefined && tag.closeTagIdx !== undefined) {
627
- // close tag exists
628
- plainTextEndIndex = tag.plainTextEndIndex;
629
- closeTagIdx = tag.closeTagIdx;
630
- // tag.tagType.length + </>
631
- closeTagLength = tag.tagType.length + 3;
632
- }
633
- else {
634
- // no close tag
635
- plainTextEndIndex = tag.plainTextBeginIndex;
636
- closeTagIdx = tag.openTagIdx + tag.openTagBody.length;
637
- closeTagLength = 0;
638
- }
639
- if (startIndex < plainTextEndIndex) {
800
+ const closingTagInfo = getTagClosingTagInfo(tag);
801
+ if (startIndex <= closingTagInfo.plainTextEndIndex) {
640
802
  // change started before the end tag
641
- if (startIndex <= tag.plainTextBeginIndex && oldPlainTextEndIndex === plainTextEndIndex) {
803
+ if (startIndex <= tag.plainTextBeginIndex && oldPlainTextEndIndex === closingTagInfo.plainTextEndIndex) {
642
804
  // the change is a tag or starts before the tag
643
805
  // tag should be removed, no matter if there are subtags
644
806
  result += htmlText.substring(lastProcessedHTMLIndex, tag.openTagIdx) + processedChange;
645
807
  processedChange = '';
646
- lastProcessedHTMLIndex = closeTagIdx + closeTagLength;
808
+ lastProcessedHTMLIndex = closingTagInfo.closeTagIdx + closingTagInfo.closeTagLength;
647
809
  // the change is handled; exit
648
810
  break;
649
811
  }
650
- else if (startIndex >= tag.plainTextBeginIndex && oldPlainTextEndIndex < plainTextEndIndex) {
651
- // edge case: the change is between tag
812
+ else if (startIndex >= tag.plainTextBeginIndex && oldPlainTextEndIndex <= closingTagInfo.plainTextEndIndex) {
813
+ // the change is between the tag
652
814
  if (isMentionTag) {
653
815
  if (change !== '') {
654
- // mention tag should be deleted when user tries to edit it
655
- result += htmlText.substring(lastProcessedHTMLIndex, tag.openTagIdx) + processedChange;
656
- changeNewEndIndex = tag.plainTextBeginIndex + processedChange.length;
816
+ if (startIndex !== tag.plainTextBeginIndex && startIndex !== closingTagInfo.plainTextEndIndex) {
817
+ // mention tag should be deleted when user tries to edit it in the middle
818
+ result += htmlText.substring(lastProcessedHTMLIndex, tag.openTagIdx) + processedChange;
819
+ changeNewEndIndex = tag.plainTextBeginIndex + processedChange.length;
820
+ lastProcessedHTMLIndex = closingTagInfo.closeTagIdx + closingTagInfo.closeTagLength;
821
+ }
822
+ else if (startIndex === tag.plainTextBeginIndex) {
823
+ // non empty change at the beginning of the mention tag to be added before the mention tag
824
+ result += htmlText.substring(lastProcessedHTMLIndex, tag.openTagIdx) + processedChange;
825
+ changeNewEndIndex = tag.plainTextBeginIndex + processedChange.length;
826
+ lastProcessedHTMLIndex = tag.openTagIdx;
827
+ }
828
+ else if (startIndex === closingTagInfo.plainTextEndIndex) {
829
+ // non empty change at the end of the mention tag to be added after the mention tag
830
+ result +=
831
+ htmlText.substring(lastProcessedHTMLIndex, closingTagInfo.closeTagIdx + closingTagInfo.closeTagLength) +
832
+ processedChange;
833
+ changeNewEndIndex = closingTagInfo.plainTextEndIndex + processedChange.length;
834
+ lastProcessedHTMLIndex = closingTagInfo.closeTagIdx + closingTagInfo.closeTagLength;
835
+ }
657
836
  processedChange = '';
658
- lastProcessedHTMLIndex = closeTagIdx + closeTagLength;
659
837
  }
660
838
  else {
661
- const [resultValue, updatedChange, htmlIndex, plainTextSelectionEndIndex] = handleMentionTagUpdate(htmlText, oldPlainText, lastProcessedHTMLIndex, processedChange, tag, closeTagIdx, closeTagLength, plainTextEndIndex, startIndex, oldPlainTextEndIndex, mentionTagLength);
662
- result += resultValue;
663
- changeNewEndIndex = plainTextSelectionEndIndex;
664
- processedChange = updatedChange;
665
- lastProcessedHTMLIndex = htmlIndex;
839
+ const updateMentionTagResult = handleMentionTagUpdate({
840
+ htmlText,
841
+ oldPlainText,
842
+ lastProcessedHTMLIndex,
843
+ processedChange,
844
+ change,
845
+ tag,
846
+ closeTagIdx: closingTagInfo.closeTagIdx,
847
+ closeTagLength: closingTagInfo.closeTagLength,
848
+ plainTextEndIndex: closingTagInfo.plainTextEndIndex,
849
+ startIndex,
850
+ oldPlainTextEndIndex,
851
+ mentionTagLength
852
+ });
853
+ result += updateMentionTagResult.result;
854
+ changeNewEndIndex = updateMentionTagResult.plainTextSelectionEndIndex;
855
+ processedChange = updateMentionTagResult.updatedChange;
856
+ lastProcessedHTMLIndex = updateMentionTagResult.htmlIndex;
666
857
  }
667
- // the change is handled; exit
668
- break;
669
858
  }
670
- else if (tag.subTags !== undefined && tag.subTags.length !== 0 && tag.content) {
859
+ else if (tag.subTags !== undefined && tag.subTags.length !== 0 && tag.content !== undefined) {
671
860
  // with subtags
672
- // before the tag content
673
861
  const stringBefore = htmlText.substring(lastProcessedHTMLIndex, tag.openTagIdx + tag.openTagBody.length);
674
- lastProcessedHTMLIndex = closeTagIdx;
675
- const [content, updatedChangeNewEndIndex] = updateHTML(tag.content, oldPlainText, newPlainText, tag.subTags, startIndex - mentionTagLength, oldPlainTextEndIndex - mentionTagLength, processedChange, mentionTrigger);
676
- result += stringBefore + content;
677
- changeNewEndIndex = updatedChangeNewEndIndex;
678
- break;
862
+ lastProcessedHTMLIndex = closingTagInfo.closeTagIdx;
863
+ const updatedContent = updateHTML({
864
+ htmlText: tag.content,
865
+ oldPlainText,
866
+ newPlainText,
867
+ tags: tag.subTags,
868
+ startIndex,
869
+ oldPlainTextEndIndex,
870
+ change: processedChange,
871
+ mentionTrigger
872
+ });
873
+ result += stringBefore + updatedContent.updatedHTML;
874
+ changeNewEndIndex = updatedContent.updatedSelectionIndex;
679
875
  }
680
876
  else {
681
877
  // no subtags
682
- const startChangeDiff = startIndex - tag.plainTextBeginIndex - mentionTagLength;
683
- const endChangeDiff = oldPlainTextEndIndex - tag.plainTextBeginIndex - mentionTagLength;
878
+ const startChangeDiff = startIndex - tag.plainTextBeginIndex;
684
879
  result +=
685
880
  htmlText.substring(lastProcessedHTMLIndex, tag.openTagIdx + tag.openTagBody.length + startChangeDiff) +
686
881
  processedChange;
687
882
  processedChange = '';
688
- lastProcessedHTMLIndex = tag.openTagIdx + tag.openTagBody.length + endChangeDiff;
689
- // the change is handled; exit
690
- break;
883
+ if (oldPlainTextEndIndex < closingTagInfo.plainTextEndIndex) {
884
+ const endChangeDiff = oldPlainTextEndIndex - tag.plainTextBeginIndex;
885
+ lastProcessedHTMLIndex = tag.openTagIdx + tag.openTagBody.length + endChangeDiff;
886
+ }
887
+ else if (oldPlainTextEndIndex === closingTagInfo.plainTextEndIndex) {
888
+ lastProcessedHTMLIndex = closingTagInfo.closeTagIdx;
889
+ }
691
890
  }
891
+ // the change is handled; exit
892
+ break;
692
893
  }
693
- else if (startIndex > tag.plainTextBeginIndex && oldPlainTextEndIndex > plainTextEndIndex) {
694
- //the change started in the tag but finishes somewhere further
894
+ else if (startIndex > tag.plainTextBeginIndex && oldPlainTextEndIndex > closingTagInfo.plainTextEndIndex) {
895
+ // the change started in the tag but finishes somewhere further
695
896
  const startChangeDiff = startIndex - tag.plainTextBeginIndex - mentionTagLength;
696
897
  if (isMentionTag) {
697
- const [resultValue, updatedChange, htmlIndex] = handleMentionTagUpdate(htmlText, oldPlainText, lastProcessedHTMLIndex, oldPlainText.substring(startIndex, startIndex + 1) !== ' ' && change === '' ? ' ' : '', // if substring !== ' ' && change is empty -> the change should be " " and not empty string but " " wasn't included in change; otherwise the part of mention should be just deleted without processedChange update
698
- tag, closeTagIdx, closeTagLength, plainTextEndIndex, startIndex, oldPlainTextEndIndex, mentionTagLength);
699
- result += resultValue;
700
- lastProcessedHTMLIndex = htmlIndex;
701
- processedChange = updatedChange;
898
+ const updateMentionTagResult = handleMentionTagUpdate({
899
+ htmlText,
900
+ oldPlainText,
901
+ lastProcessedHTMLIndex,
902
+ processedChange: '',
903
+ change,
904
+ tag,
905
+ closeTagIdx: closingTagInfo.closeTagIdx,
906
+ closeTagLength: closingTagInfo.closeTagLength,
907
+ plainTextEndIndex: closingTagInfo.plainTextEndIndex,
908
+ startIndex,
909
+ oldPlainTextEndIndex,
910
+ mentionTagLength
911
+ });
912
+ result += updateMentionTagResult.result;
913
+ lastProcessedHTMLIndex = updateMentionTagResult.htmlIndex;
702
914
  // no need to handle plainTextSelectionEndIndex as the change will be added later
703
- // proceed with the next calculations
704
915
  }
705
916
  else if (tag.subTags !== undefined && tag.subTags.length !== 0 && tag.content !== undefined) {
706
917
  // with subtags
707
- // before the tag content
708
918
  const stringBefore = htmlText.substring(lastProcessedHTMLIndex, tag.openTagIdx + tag.openTagBody.length);
709
- lastProcessedHTMLIndex = closeTagIdx;
710
- const [content] = updateHTML(tag.content, oldPlainText, newPlainText, tag.subTags, startIndex - mentionTagLength, oldPlainTextEndIndex - mentionTagLength, '', // the part of the tag should be just deleted without processedChange update and change will be added after this tag
711
- mentionTrigger);
712
- result += stringBefore + content;
713
- // proceed with the next calculations
919
+ lastProcessedHTMLIndex = closingTagInfo.closeTagIdx;
920
+ const updatedContent = updateHTML({
921
+ htmlText: tag.content,
922
+ oldPlainText,
923
+ newPlainText,
924
+ tags: tag.subTags,
925
+ startIndex,
926
+ oldPlainTextEndIndex,
927
+ change: '',
928
+ mentionTrigger
929
+ });
930
+ result += stringBefore + updatedContent.updatedHTML;
714
931
  }
715
932
  else {
716
933
  // no subtags
717
934
  result += htmlText.substring(lastProcessedHTMLIndex, tag.openTagIdx + tag.openTagBody.length + startChangeDiff);
718
- lastProcessedHTMLIndex = closeTagIdx;
719
- // proceed with the next calculations
935
+ lastProcessedHTMLIndex = closingTagInfo.closeTagIdx;
720
936
  }
937
+ // proceed with the next calculations
721
938
  }
722
- else if (startIndex < tag.plainTextBeginIndex && oldPlainTextEndIndex > plainTextEndIndex) {
939
+ else if (startIndex < tag.plainTextBeginIndex && oldPlainTextEndIndex > closingTagInfo.plainTextEndIndex) {
723
940
  // the change starts before the tag and finishes after it
724
941
  // tag should be removed, no matter if there are subtags
725
942
  // no need to save anything between lastProcessedHTMLIndex and closeTagIdx + closeTagLength
726
- lastProcessedHTMLIndex = closeTagIdx + closeTagLength;
943
+ lastProcessedHTMLIndex = closingTagInfo.closeTagIdx + closingTagInfo.closeTagLength;
727
944
  // proceed with the next calculations
728
945
  }
729
- else if (startIndex === tag.plainTextBeginIndex && oldPlainTextEndIndex > plainTextEndIndex) {
946
+ else if (startIndex === tag.plainTextBeginIndex && oldPlainTextEndIndex > closingTagInfo.plainTextEndIndex) {
730
947
  // the change starts in the tag and finishes after it
731
948
  // tag should be removed, no matter if there are subtags
732
949
  result += htmlText.substring(lastProcessedHTMLIndex, tag.openTagIdx);
733
950
  // processedChange shouldn't be updated as it will be added after the tag
734
- lastProcessedHTMLIndex = closeTagIdx + closeTagLength;
951
+ lastProcessedHTMLIndex = closingTagInfo.closeTagIdx + closingTagInfo.closeTagLength;
735
952
  // proceed with the next calculations
736
953
  }
737
- else if (startIndex < tag.plainTextBeginIndex && oldPlainTextEndIndex < plainTextEndIndex) {
954
+ else if (startIndex < tag.plainTextBeginIndex && oldPlainTextEndIndex < closingTagInfo.plainTextEndIndex) {
738
955
  // the change starts before the tag and ends in a tag
739
956
  if (isMentionTag) {
740
- const [resultValue, , htmlIndex] = handleMentionTagUpdate(htmlText, oldPlainText, lastProcessedHTMLIndex, '', // the part of mention should be just deleted without processedChange update
741
- tag, closeTagIdx, closeTagLength, plainTextEndIndex, startIndex, oldPlainTextEndIndex, mentionTagLength);
742
- changeNewEndIndex = tag.plainTextBeginIndex;
743
- result += resultValue;
744
- lastProcessedHTMLIndex = htmlIndex;
957
+ // mention tag
958
+ const updateMentionTagResult = handleMentionTagUpdate({
959
+ htmlText,
960
+ oldPlainText,
961
+ lastProcessedHTMLIndex,
962
+ processedChange: '',
963
+ change,
964
+ tag,
965
+ closeTagIdx: closingTagInfo.closeTagIdx,
966
+ closeTagLength: closingTagInfo.closeTagLength,
967
+ plainTextEndIndex: closingTagInfo.plainTextEndIndex,
968
+ startIndex,
969
+ oldPlainTextEndIndex,
970
+ mentionTagLength
971
+ });
972
+ changeNewEndIndex = updateMentionTagResult.plainTextSelectionEndIndex;
973
+ result += updateMentionTagResult.result;
974
+ lastProcessedHTMLIndex = updateMentionTagResult.htmlIndex;
745
975
  }
746
976
  else if (tag.subTags !== undefined && tag.subTags.length !== 0 && tag.content !== undefined) {
747
977
  // with subtags
748
- // before the tag content
749
978
  const stringBefore = htmlText.substring(lastProcessedHTMLIndex, tag.openTagIdx + tag.openTagBody.length);
750
- lastProcessedHTMLIndex = closeTagIdx;
751
- const [content] = updateHTML(tag.content, oldPlainText, newPlainText, tag.subTags, startIndex - mentionTagLength, oldPlainTextEndIndex - mentionTagLength, processedChange, // processedChange should equal '' and the part of the tag should be deleted as the change was handled before this tag
752
- mentionTrigger);
753
- result += stringBefore + content;
979
+ lastProcessedHTMLIndex = closingTagInfo.closeTagIdx;
980
+ const updatedContent = updateHTML({
981
+ htmlText: tag.content,
982
+ oldPlainText,
983
+ newPlainText,
984
+ tags: tag.subTags,
985
+ startIndex,
986
+ oldPlainTextEndIndex,
987
+ change: processedChange,
988
+ mentionTrigger
989
+ });
990
+ processedChange = '';
991
+ result += stringBefore + updatedContent.updatedHTML;
754
992
  }
755
993
  else {
756
994
  // no subtags
@@ -758,123 +996,67 @@ const updateHTML = (htmlText, oldPlainText, newPlainText, tags, startIndex, oldP
758
996
  htmlText.substring(lastProcessedHTMLIndex, tag.openTagIdx + tag.openTagBody.length) + processedChange;
759
997
  processedChange = '';
760
998
  // oldPlainTextEndIndex already includes mentionTag length
761
- const endChangeDiff = plainTextEndIndex - oldPlainTextEndIndex;
999
+ const endChangeDiff = closingTagInfo.plainTextEndIndex - oldPlainTextEndIndex;
762
1000
  // as change may be before the end of the tag, we need to add the rest of the tag
763
- lastProcessedHTMLIndex = closeTagIdx - endChangeDiff;
1001
+ lastProcessedHTMLIndex = closingTagInfo.closeTagIdx - endChangeDiff;
764
1002
  }
765
1003
  // the change is handled; exit
766
1004
  break;
767
1005
  }
768
- else if (startIndex > tag.plainTextBeginIndex && oldPlainTextEndIndex === plainTextEndIndex) {
769
- // the change starts in the tag and ends at the end of a tag
770
- if (isMentionTag) {
771
- if (change !== '' && startIndex === plainTextEndIndex) {
772
- // non empty change at the end of the mention tag to be added after the mention tag
773
- result += htmlText.substring(lastProcessedHTMLIndex, closeTagIdx + closeTagLength) + processedChange;
774
- changeNewEndIndex = plainTextEndIndex + processedChange.length;
775
- processedChange = '';
776
- lastProcessedHTMLIndex = closeTagIdx + closeTagLength;
777
- }
778
- else if (change !== '') {
779
- // mention tag should be deleted when user tries to edit it
780
- result += htmlText.substring(lastProcessedHTMLIndex, tag.openTagIdx) + processedChange;
781
- changeNewEndIndex = tag.plainTextBeginIndex + processedChange.length;
782
- processedChange = '';
783
- lastProcessedHTMLIndex = closeTagIdx + closeTagLength;
784
- }
785
- else {
786
- const [resultValue, updatedChange, htmlIndex, plainTextSelectionEndIndex] = handleMentionTagUpdate(htmlText, oldPlainText, lastProcessedHTMLIndex, processedChange, tag, closeTagIdx, closeTagLength, plainTextEndIndex, startIndex, oldPlainTextEndIndex, mentionTagLength);
787
- result += resultValue;
788
- processedChange = updatedChange;
789
- lastProcessedHTMLIndex = htmlIndex;
790
- changeNewEndIndex = plainTextSelectionEndIndex;
791
- }
792
- // the change is handled; exit
793
- break;
794
- }
795
- else if (tag.subTags !== undefined && tag.subTags.length !== 0 && tag.content !== undefined) {
796
- // with subtags
797
- // before the tag content
798
- const stringBefore = htmlText.substring(lastProcessedHTMLIndex, tag.openTagIdx + tag.openTagBody.length);
799
- lastProcessedHTMLIndex = closeTagIdx;
800
- const [content, updatedChangeNewEndIndex] = updateHTML(tag.content, oldPlainText, newPlainText, tag.subTags, startIndex - mentionTagLength, oldPlainTextEndIndex - mentionTagLength, processedChange, mentionTrigger);
801
- result += stringBefore + content;
802
- changeNewEndIndex = updatedChangeNewEndIndex;
803
- break;
804
- }
805
- else {
806
- // no subtags
807
- const startChangeDiff = startIndex - tag.plainTextBeginIndex - mentionTagLength;
808
- result +=
809
- htmlText.substring(lastProcessedHTMLIndex, tag.openTagIdx + tag.openTagBody.length + startChangeDiff) +
810
- processedChange;
811
- processedChange = '';
812
- lastProcessedHTMLIndex = closeTagIdx;
813
- // the change is handled; exit
814
- break;
815
- }
816
- }
817
- lastProcessedPlainTextTagEndIndex = plainTextEndIndex;
1006
+ lastProcessedPlainTextTagEndIndex = closingTagInfo.plainTextEndIndex;
818
1007
  }
819
- if (i === tags.length - 1 && oldPlainTextEndIndex >= plainTextEndIndex) {
1008
+ if (i === tags.length - 1 && oldPlainTextEndIndex >= closingTagInfo.plainTextEndIndex) {
820
1009
  // the last tag should handle the end of the change if needed
821
1010
  // oldPlainTextEndIndex already includes mentionTag length
822
- const endChangeDiff = oldPlainTextEndIndex - plainTextEndIndex;
823
- if (startIndex >= plainTextEndIndex) {
824
- const startChangeDiff = startIndex - plainTextEndIndex;
1011
+ const endChangeDiff = oldPlainTextEndIndex - closingTagInfo.plainTextEndIndex;
1012
+ if (startIndex >= closingTagInfo.plainTextEndIndex) {
1013
+ const startChangeDiff = startIndex - closingTagInfo.plainTextEndIndex;
825
1014
  result +=
826
- htmlText.substring(lastProcessedHTMLIndex, closeTagIdx + closeTagLength + startChangeDiff) + processedChange;
1015
+ htmlText.substring(lastProcessedHTMLIndex, closingTagInfo.closeTagIdx + closingTagInfo.closeTagLength + startChangeDiff) + processedChange;
827
1016
  }
828
1017
  else {
829
- result += htmlText.substring(lastProcessedHTMLIndex, closeTagIdx + closeTagLength) + processedChange;
1018
+ result +=
1019
+ htmlText.substring(lastProcessedHTMLIndex, closingTagInfo.closeTagIdx + closingTagInfo.closeTagLength) +
1020
+ processedChange;
830
1021
  }
831
1022
  processedChange = '';
832
- lastProcessedHTMLIndex = closeTagIdx + closeTagLength + endChangeDiff;
1023
+ lastProcessedHTMLIndex = closingTagInfo.closeTagIdx + closingTagInfo.closeTagLength + endChangeDiff;
833
1024
  // the change is handled; exit
834
1025
  // break is not required here as this is the last element but added for consistency
835
1026
  break;
836
1027
  }
837
1028
  }
838
1029
  if (lastProcessedHTMLIndex < htmlText.length) {
1030
+ // add the rest of the html string
839
1031
  result += htmlText.substring(lastProcessedHTMLIndex);
840
1032
  }
841
- return [result, changeNewEndIndex];
1033
+ return { updatedHTML: result, updatedSelectionIndex: changeNewEndIndex };
842
1034
  };
843
1035
  /* @conditional-compile-remove(mention) */
844
1036
  /**
845
1037
  * Given the oldText and newText, find the start index, old end index and new end index for the changes
846
1038
  *
847
- * @param oldText - the old text
848
- * @param newText - the new text
849
- * @param selectionEnd - the end of the selection
850
- * @returns change start index, old end index and new end index. The old and new end indexes are exclusive.
851
1039
  * @private
1040
+ * @param props - Props for finding stings diff indexes function.
1041
+ * @returns Indexes for change start and ends in new and old texts. The old and new end indexes are exclusive.
852
1042
  */
853
- const findStringsDiffIndexes = (oldText, newText, selectionEnd // should be a valid position in the input field
854
- ) => {
1043
+ const findStringsDiffIndexes = (props) => {
1044
+ const { oldText, newText, previousSelectionStart, previousSelectionEnd, currentSelectionStart, currentSelectionEnd } = props;
855
1045
  const newTextLength = newText.length;
856
1046
  const oldTextLength = oldText.length;
857
- let changeStart = 0;
1047
+ // let changeStart = 0;
858
1048
  let newChangeEnd = newTextLength;
859
1049
  let oldChangeEnd = oldTextLength;
860
- const length = Math.min(newTextLength, oldTextLength, selectionEnd);
861
- for (let i = 0; i < length; i++) {
862
- if (newText[i] !== oldText[i]) {
863
- // the symbol with changeStart index is updated
864
- changeStart = i;
865
- break;
866
- }
867
- else if (i === length - 1 && newText[i] === oldText[i]) {
868
- // the symbol is added at the end of inputTextValue
869
- changeStart = length;
870
- break;
871
- }
872
- }
1050
+ const previousSelectionStartValue = previousSelectionStart > -1 ? previousSelectionStart : oldTextLength;
1051
+ const previousSelectionEndValue = previousSelectionEnd > -1 ? previousSelectionEnd : oldTextLength;
1052
+ const currentSelectionStartValue = currentSelectionStart > -1 ? currentSelectionStart : newTextLength;
1053
+ const currentSelectionEndValue = currentSelectionEnd > -1 ? currentSelectionEnd : newTextLength;
1054
+ const changeStart = Math.min(previousSelectionStartValue, previousSelectionEndValue, currentSelectionStartValue, currentSelectionEndValue, newTextLength, oldTextLength);
873
1055
  if (oldTextLength < newTextLength) {
874
1056
  //insert or replacement
875
1057
  if (oldTextLength === changeStart) {
876
1058
  // when change was at the end of string
877
- // Change is found
1059
+ // change is found
878
1060
  newChangeEnd = newTextLength;
879
1061
  oldChangeEnd = oldTextLength;
880
1062
  }
@@ -883,7 +1065,7 @@ const findStringsDiffIndexes = (oldText, newText, selectionEnd // should be a va
883
1065
  newChangeEnd = newTextLength - i - 1;
884
1066
  oldChangeEnd = oldTextLength - i - 1;
885
1067
  if (newText[newChangeEnd] !== oldText[oldChangeEnd]) {
886
- // Change is found
1068
+ // change is found
887
1069
  break;
888
1070
  }
889
1071
  }
@@ -896,7 +1078,7 @@ const findStringsDiffIndexes = (oldText, newText, selectionEnd // should be a va
896
1078
  //deletion or replacement
897
1079
  if (newTextLength === changeStart) {
898
1080
  // when change was at the end of string
899
- // Change is found
1081
+ // change is found
900
1082
  newChangeEnd = newTextLength;
901
1083
  oldChangeEnd = oldTextLength;
902
1084
  }
@@ -905,7 +1087,7 @@ const findStringsDiffIndexes = (oldText, newText, selectionEnd // should be a va
905
1087
  newChangeEnd = newTextLength - i - 1;
906
1088
  oldChangeEnd = oldTextLength - i - 1;
907
1089
  if (newText[newChangeEnd] !== oldText[oldChangeEnd]) {
908
- // Change is found
1090
+ // change is found
909
1091
  break;
910
1092
  }
911
1093
  }
@@ -915,12 +1097,12 @@ const findStringsDiffIndexes = (oldText, newText, selectionEnd // should be a va
915
1097
  }
916
1098
  }
917
1099
  else {
918
- //replacement
1100
+ // replacement
919
1101
  for (let i = 1; i < oldTextLength && oldTextLength - i >= changeStart; i++) {
920
1102
  newChangeEnd = newTextLength - i - 1;
921
1103
  oldChangeEnd = oldTextLength - i - 1;
922
1104
  if (newText[newChangeEnd] !== oldText[oldChangeEnd]) {
923
- // Change is found
1105
+ // change is found
924
1106
  break;
925
1107
  }
926
1108
  }
@@ -935,13 +1117,30 @@ const findStringsDiffIndexes = (oldText, newText, selectionEnd // should be a va
935
1117
  return { changeStart, oldChangeEnd, newChangeEnd };
936
1118
  };
937
1119
  /* @conditional-compile-remove(mention) */
1120
+ /**
1121
+ * Get the html string for the mention suggestion.
1122
+ *
1123
+ * @private
1124
+ * @param suggestion - The mention suggestion.
1125
+ * @param localeStrings - The locale strings.
1126
+ * @returns The html string for the mention suggestion.
1127
+ */
938
1128
  const htmlStringForMentionSuggestion = (suggestion, localeStrings) => {
939
1129
  const idHTML = ' id ="' + suggestion.id + '"';
940
1130
  const displayTextHTML = ' displayText ="' + suggestion.displayText + '"';
941
1131
  const displayText = getDisplayNameForMentionSuggestion(suggestion, localeStrings);
942
- return '<msft-mention' + idHTML + displayTextHTML + '>' + displayText + '</msft-mention>';
1132
+ return '<' + MSFT_MENTION_TAG + idHTML + displayTextHTML + '>' + displayText + '</' + MSFT_MENTION_TAG + '>';
943
1133
  };
944
1134
  /* @conditional-compile-remove(mention) */
1135
+ /**
1136
+ * Get display name for the mention suggestion.
1137
+ *
1138
+ * @private
1139
+ *
1140
+ * @param suggestion - The mention suggestion.
1141
+ * @param localeStrings - The locale strings.
1142
+ * @returns The display name for the mention suggestion or display name placeholder if display name is empty.
1143
+ */
945
1144
  const getDisplayNameForMentionSuggestion = (suggestion, localeStrings) => {
946
1145
  const displayNamePlaceholder = localeStrings.participantItem.displayNamePlaceholder;
947
1146
  return suggestion.displayText !== '' ? suggestion.displayText : displayNamePlaceholder !== null && displayNamePlaceholder !== void 0 ? displayNamePlaceholder : '';
@@ -949,8 +1148,9 @@ const getDisplayNameForMentionSuggestion = (suggestion, localeStrings) => {
949
1148
  /* @conditional-compile-remove(mention) */
950
1149
  /**
951
1150
  * Parse the text and return the tags and the plain text in one go
1151
+ * @private
952
1152
  * @param text - The text to parse for HTML tags
953
- * @param trigger The trigger to show for the msft-mention tag in plain text
1153
+ * @param trigger The trigger to show for the mention tag in plain text
954
1154
  *
955
1155
  * @returns An array of tags and the plain text representation
956
1156
  */
@@ -994,7 +1194,7 @@ const textToTagParser = (text, trigger) => {
994
1194
  // Tag startIdx is absolute to the text. This is updated later to be relative to the parent tag
995
1195
  currentOpenTag.content = text.substring(currentOpenTag.openTagIdx + currentOpenTag.openTagBody.length, foundHtmlTag.startIdx);
996
1196
  // Insert the plain text pieces for the sub tags
997
- if (currentOpenTag.tagType === 'msft-mention') {
1197
+ if (currentOpenTag.tagType === MSFT_MENTION_TAG) {
998
1198
  plainTextRepresentation =
999
1199
  plainTextRepresentation.slice(0, currentOpenTag.plainTextBeginIndex) +
1000
1200
  trigger +
@@ -1024,7 +1224,7 @@ const textToTagParser = (text, trigger) => {
1024
1224
  // Update parsing index; move past the end of the close tag
1025
1225
  parseIndex = foundHtmlTag.startIdx + foundHtmlTag.content.length;
1026
1226
  } // While parseIndex < text.length loop
1027
- return [tags, plainTextRepresentation];
1227
+ return { tags, plainText: plainTextRepresentation };
1028
1228
  };
1029
1229
  /* @conditional-compile-remove(mention) */
1030
1230
  const parseOpenTag = (tag, startIdx) => {