@azure/communication-react 1.5.1-alpha-202306060014 → 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.
@@ -167,7 +167,7 @@ const _toCommunicationIdentifier = (id) => {
167
167
  // Copyright (c) Microsoft Corporation.
168
168
  // Licensed under the MIT license.
169
169
  // GENERATED FILE. DO NOT EDIT MANUALLY.
170
- var telemetryVersion = '1.5.1-alpha-202306060014';
170
+ var telemetryVersion = '1.5.1-alpha-202306070014';
171
171
 
172
172
  // Copyright (c) Microsoft Corporation.
173
173
  /**
@@ -5808,32 +5808,6 @@ const DEFAULT_COMPONENT_ICONS = {
5808
5808
  ChangeSpokenLanguageIcon: React__default['default'].createElement(reactIcons.PersonVoice20Regular, null)
5809
5809
  };
5810
5810
 
5811
- // Copyright (c) Microsoft Corporation.
5812
- /**
5813
- * @internal
5814
- * Announcer component to maker aria announcements on actions
5815
- */
5816
- const Announcer$1 = (props) => {
5817
- const { announcementString, ariaLive } = props;
5818
- return React__default['default'].createElement(react.Stack, { "aria-label": announcementString, "aria-live": ariaLive, styles: announcerStyles });
5819
- };
5820
- /**
5821
- * Styles to hide the announcer from view but still existing on the DOM tree it so that narration can happen.
5822
- */
5823
- const announcerStyles = {
5824
- root: {
5825
- position: 'absolute',
5826
- width: '1px',
5827
- height: '1px',
5828
- padding: 0,
5829
- margin: '-1px',
5830
- overflow: 'hidden',
5831
- clip: 'rect(0,0,0,0)',
5832
- whiteSpace: 'nowrap',
5833
- border: 0
5834
- }
5835
- };
5836
-
5837
5811
  // Copyright (c) Microsoft Corporation.
5838
5812
  /**
5839
5813
  * @private
@@ -5966,1605 +5940,1604 @@ const getPerceptualBrightnessOfHexColor = (hexColor) => {
5966
5940
 
5967
5941
  // Copyright (c) Microsoft Corporation.
5968
5942
  /**
5969
- * @private
5970
- * z-index to ensure that chat container has lower z-index than mention popover
5971
- */
5972
- const CHAT_CONTAINER_ZINDEX$1 = 1;
5973
- /**
5974
- * @private
5975
- */
5976
- const mentionPopoverContainerStyle = (theme) => react.mergeStyles({
5977
- boxShadow: theme.effects.elevation16,
5978
- background: theme.semanticColors.bodyBackground,
5979
- overflow: 'visible',
5980
- // zIndex to set the mentionPopover above the chat container
5981
- zIndex: CHAT_CONTAINER_ZINDEX$1 + 1
5982
- });
5983
- /**
5984
- * @private
5943
+ * @internal
5944
+ * Announcer component to maker aria announcements on actions
5985
5945
  */
5986
- const headerStyleThemed = (theme) => {
5987
- return {
5988
- root: {
5989
- color: theme.palette.neutralSecondary,
5990
- margin: '0.5rem 1rem 0.25rem',
5991
- fontSize: theme.fonts.smallPlus.fontSize
5992
- }
5993
- };
5946
+ const Announcer$1 = (props) => {
5947
+ const { announcementString, ariaLive } = props;
5948
+ return React__default['default'].createElement(react.Stack, { "aria-label": announcementString, "aria-live": ariaLive, styles: announcerStyles });
5994
5949
  };
5995
5950
  /**
5996
- * @private
5951
+ * Styles to hide the announcer from view but still existing on the DOM tree it so that narration can happen.
5997
5952
  */
5998
- react.mergeStyles({
5999
- height: '100%',
6000
- overflowY: 'visible',
6001
- overflowX: 'hidden'
6002
- });
5953
+ const announcerStyles = {
5954
+ root: {
5955
+ position: 'absolute',
5956
+ width: '1px',
5957
+ height: '1px',
5958
+ padding: 0,
5959
+ margin: '-1px',
5960
+ overflow: 'hidden',
5961
+ clip: 'rect(0,0,0,0)',
5962
+ whiteSpace: 'nowrap',
5963
+ border: 0
5964
+ }
5965
+ };
5966
+
5967
+ // Copyright (c) Microsoft Corporation.
5968
+ // Licensed under the MIT license.
5969
+ const MSFT_MENTION_TAG = 'msft-mention';
6003
5970
  /**
5971
+ * Get validated value for index between min and max values. If currentValue is not defined, -1 will be used instead.
5972
+ *
6004
5973
  * @private
5974
+ * @param props - Props for finding a valid index in range.
5975
+ * @returns Valid index in the range.
6005
5976
  */
6006
- const suggestionListStyle = react.mergeStyles({
6007
- padding: '0.25rem 0rem 0',
6008
- overflow: 'visible'
6009
- });
5977
+ const getValidatedIndexInRange = (props) => {
5978
+ const { min, max, currentValue } = props;
5979
+ let updatedValue = currentValue !== null && currentValue !== void 0 ? currentValue : -1;
5980
+ updatedValue = Math.max(min, updatedValue);
5981
+ updatedValue = Math.min(updatedValue, max);
5982
+ return updatedValue;
5983
+ };
6010
5984
  /**
5985
+ * Find mention tag for selection if exists.
5986
+ *
6011
5987
  * @private
5988
+ * @param tags - Existing list of tags.
5989
+ * @param selection - Selection index.
5990
+ * @returns Mention tag if exists, otherwise undefined.
6012
5991
  */
6013
- const suggestionItemWrapperStyle = (theme) => {
6014
- return react.mergeStyles({
6015
- margin: '0.05rem 0.1rem',
6016
- '&:focus-visible': {
6017
- outline: `${theme.palette.black} solid 0.1rem`
5992
+ const findMentionTagForSelection = (tags, selection) => {
5993
+ let mentionTag = undefined;
5994
+ tags.every((tag) => {
5995
+ const closingTagInfo = getTagClosingTagInfo(tag);
5996
+ if (tag.plainTextBeginIndex !== undefined && tag.plainTextBeginIndex > selection) {
5997
+ // no need to check further as the selection is before the tag
5998
+ return false;
5999
+ }
6000
+ else if (tag.plainTextBeginIndex !== undefined &&
6001
+ tag.plainTextBeginIndex <= selection &&
6002
+ selection <= closingTagInfo.plainTextEndIndex) {
6003
+ // no need to check if tag doesn't contain selection
6004
+ if (tag.subTags !== undefined && tag.subTags.length !== 0) {
6005
+ const selectedTag = findMentionTagForSelection(tag.subTags, selection);
6006
+ if (selectedTag !== undefined) {
6007
+ mentionTag = selectedTag;
6008
+ return false;
6009
+ }
6010
+ }
6011
+ else if (tag.tagType === MSFT_MENTION_TAG) {
6012
+ mentionTag = tag;
6013
+ return false;
6014
+ }
6018
6015
  }
6016
+ return true;
6019
6017
  });
6018
+ return mentionTag;
6020
6019
  };
6021
6020
  /**
6021
+ * Get the indices of the word for the selection.
6022
6022
  * @private
6023
+ *
6023
6024
  */
6024
- const suggestionItemStackStyle = (theme, isSuggestionHovered, activeBorder) => {
6025
- return react.mergeStyles({
6026
- width: '10rem',
6027
- alignItems: 'center',
6028
- height: '36px',
6029
- padding: '0 0.75rem',
6030
- background: isSuggestionHovered ? theme.palette.neutralLight : theme.palette.white,
6031
- border: activeBorder ? `0.0625rem solid ${theme.palette.neutralSecondary}` : 'none'
6032
- });
6025
+ const rangeOfWordInSelection = ({ textInput, selectionStart, selectionEnd, tag }) => {
6026
+ var _a;
6027
+ if (tag.plainTextBeginIndex === undefined) {
6028
+ return { start: selectionStart, end: selectionEnd === undefined ? selectionStart : selectionEnd };
6029
+ }
6030
+ // Look at start word index and optionally end word index.
6031
+ // Select combination of the two and return the range.
6032
+ let start = selectionStart;
6033
+ let end = selectionEnd === undefined ? selectionStart : selectionEnd;
6034
+ const firstWordStartIndex = textInput.lastIndexOf(' ', selectionStart);
6035
+ if (firstWordStartIndex === tag.plainTextBeginIndex) {
6036
+ start = firstWordStartIndex;
6037
+ }
6038
+ else {
6039
+ start = Math.max(firstWordStartIndex + 1, tag.plainTextBeginIndex);
6040
+ }
6041
+ const firstWordEndIndex = textInput.indexOf(' ', selectionStart);
6042
+ end = Math.max(firstWordEndIndex + 1, (_a = tag.plainTextEndIndex) !== null && _a !== void 0 ? _a : firstWordEndIndex + 1);
6043
+ if (selectionEnd !== undefined && tag.plainTextEndIndex !== undefined) {
6044
+ const lastWordEndIndex = textInput.indexOf(' ', selectionEnd);
6045
+ end = Math.max(lastWordEndIndex > -1 ? lastWordEndIndex : tag.plainTextEndIndex, selectionEnd);
6046
+ }
6047
+ return { start, end };
6033
6048
  };
6034
-
6035
- // Copyright (c) Microsoft Corporation.
6036
6049
  /**
6037
- * Component to render a pop-up of mention suggestions.
6050
+ * Find a new the selection index.
6038
6051
  *
6039
- * @internal
6052
+ * @private
6053
+ * @param props - Props for finding new selection index for mention.
6054
+ * @returns New selection index if it is inside of a mention tag, otherwise the current selection.
6040
6055
  */
6041
- const _MentionPopover = (props) => {
6042
- const { suggestions, activeSuggestionIndex, title, target, targetPositionOffset, onRenderSuggestionItem, onSuggestionSelected, onDismiss, location } = props;
6043
- const theme = react.useTheme();
6044
- /* @conditional-compile-remove(mention) */
6045
- const ids = useIdentifiers();
6046
- const localeStrings = useLocale$1().strings;
6047
- const popoverRef = React.useRef();
6048
- const [position, setPosition] = React.useState({ left: 0 });
6049
- const [hoveredSuggestion, setHoveredSuggestion] = React.useState(undefined);
6050
- const [changedSelection, setChangedSelection] = React.useState(undefined); // Selection UI as per teams
6051
- const dismissPopoverWhenClickingOutside = React.useCallback((e) => {
6052
- const target = e.target;
6053
- if (popoverRef.current && !popoverRef.current.contains(target)) {
6054
- onDismiss && onDismiss();
6055
- }
6056
- }, [onDismiss]);
6057
- React.useEffect(() => {
6058
- if (changedSelection === undefined) {
6059
- setChangedSelection(false);
6056
+ const findNewSelectionIndexForMention = (props) => {
6057
+ var _a;
6058
+ const { tag, textValue, currentSelectionIndex, previousSelectionIndex } = props;
6059
+ // check if this is a mention tag and selection should be updated
6060
+ if (tag.tagType !== MSFT_MENTION_TAG ||
6061
+ tag.plainTextBeginIndex === undefined ||
6062
+ currentSelectionIndex === previousSelectionIndex ||
6063
+ tag.plainTextEndIndex === undefined) {
6064
+ return currentSelectionIndex;
6065
+ }
6066
+ let spaceIndex = 0;
6067
+ if (currentSelectionIndex <= previousSelectionIndex) {
6068
+ // the cursor is moved to the left, find the last index before the cursor
6069
+ spaceIndex = textValue.lastIndexOf(' ', currentSelectionIndex !== null && currentSelectionIndex !== void 0 ? currentSelectionIndex : 0);
6070
+ if (spaceIndex === -1) {
6071
+ // no space before the selection, use the beginning of the tag
6072
+ spaceIndex = tag.plainTextBeginIndex;
6060
6073
  }
6061
- else if (changedSelection === false) {
6062
- setChangedSelection(true);
6074
+ }
6075
+ else {
6076
+ // the cursor is moved to the right, find the fist index after the cursor
6077
+ spaceIndex = textValue.indexOf(' ', currentSelectionIndex !== null && currentSelectionIndex !== void 0 ? currentSelectionIndex : 0);
6078
+ if (spaceIndex === -1) {
6079
+ // no space after the selection, use the end of the tag
6080
+ spaceIndex = (_a = tag.plainTextEndIndex) !== null && _a !== void 0 ? _a : tag.plainTextBeginIndex;
6063
6081
  }
6064
- }, [activeSuggestionIndex, changedSelection]);
6065
- React.useEffect(() => {
6066
- window && window.addEventListener('click', dismissPopoverWhenClickingOutside);
6067
- return () => {
6068
- window && window.removeEventListener('click', dismissPopoverWhenClickingOutside);
6082
+ }
6083
+ spaceIndex = Math.max(tag.plainTextBeginIndex, spaceIndex);
6084
+ spaceIndex = Math.min(tag.plainTextEndIndex, spaceIndex);
6085
+ return spaceIndex;
6086
+ };
6087
+ /**
6088
+ * Handle mention tag edit and by word deleting
6089
+ *
6090
+ * @private
6091
+ * @param props - Props for mention update HTML function.
6092
+ * @returns Updated texts and indexes.
6093
+ */
6094
+ const handleMentionTagUpdate = (props) => {
6095
+ const { htmlText, oldPlainText, change, tag, closeTagIdx, closeTagLength, plainTextEndIndex, startIndex, oldPlainTextEndIndex, mentionTagLength } = props;
6096
+ let processedChange = props.processedChange;
6097
+ let lastProcessedHTMLIndex = props.lastProcessedHTMLIndex;
6098
+ if (tag.tagType !== MSFT_MENTION_TAG || tag.plainTextBeginIndex === undefined) {
6099
+ // not a mention tag
6100
+ return {
6101
+ result: '',
6102
+ updatedChange: processedChange,
6103
+ htmlIndex: lastProcessedHTMLIndex,
6104
+ plainTextSelectionEndIndex: undefined
6069
6105
  };
6070
- }, [dismissPopoverWhenClickingOutside]);
6071
- // Determine popover position
6072
- React.useEffect(() => {
6073
- var _a, _b, _c, _d, _e, _f, _g, _h;
6074
- const rect = (_a = target === null || target === void 0 ? void 0 : target.current) === null || _a === void 0 ? void 0 : _a.getBoundingClientRect();
6075
- const maxWidth = 200;
6076
- const finalPosition = { maxWidth };
6077
- // Figure out whether it will fit horizontally
6078
- const leftOffset = (_b = targetPositionOffset === null || targetPositionOffset === void 0 ? void 0 : targetPositionOffset.left) !== null && _b !== void 0 ? _b : 0;
6079
- if (leftOffset + maxWidth > ((_c = rect === null || rect === void 0 ? void 0 : rect.width) !== null && _c !== void 0 ? _c : 0)) {
6080
- finalPosition.right = ((_d = rect === null || rect === void 0 ? void 0 : rect.width) !== null && _d !== void 0 ? _d : 0) - leftOffset;
6081
- }
6082
- else {
6083
- finalPosition.left = leftOffset;
6106
+ }
6107
+ let result = '';
6108
+ let plainTextSelectionEndIndex;
6109
+ let rangeStart;
6110
+ let rangeEnd;
6111
+ // check if space symbol is handled in case if string looks like '<1 2 3>'
6112
+ let isSpaceLengthHandled = false;
6113
+ rangeStart = oldPlainText.lastIndexOf(' ', startIndex);
6114
+ if (rangeStart !== -1 && rangeStart !== undefined && rangeStart > tag.plainTextBeginIndex) {
6115
+ isSpaceLengthHandled = true;
6116
+ }
6117
+ rangeEnd = oldPlainText.indexOf(' ', oldPlainTextEndIndex);
6118
+ if (rangeEnd === -1 || rangeEnd === undefined) {
6119
+ // check if space symbol is not found
6120
+ rangeEnd = plainTextEndIndex;
6121
+ }
6122
+ else if (!isSpaceLengthHandled) {
6123
+ // +1 to include the space symbol
6124
+ rangeEnd += 1;
6125
+ }
6126
+ isSpaceLengthHandled = true;
6127
+ if (rangeStart === -1 || rangeStart === undefined || rangeStart < tag.plainTextBeginIndex) {
6128
+ // rangeStart should be at least equal to tag.plainTextBeginIndex
6129
+ rangeStart = tag.plainTextBeginIndex;
6130
+ }
6131
+ if (rangeEnd > plainTextEndIndex) {
6132
+ // rangeEnd should be at most equal to plainTextEndIndex
6133
+ rangeEnd = plainTextEndIndex;
6134
+ }
6135
+ if (rangeStart === tag.plainTextBeginIndex && rangeEnd === plainTextEndIndex) {
6136
+ // the whole tag should be removed
6137
+ result += htmlText.substring(lastProcessedHTMLIndex, tag.openTagIndex) + processedChange;
6138
+ plainTextSelectionEndIndex = tag.plainTextBeginIndex + processedChange.length;
6139
+ processedChange = '';
6140
+ lastProcessedHTMLIndex = closeTagIdx + closeTagLength;
6141
+ }
6142
+ else {
6143
+ // only part of the tag should be removed
6144
+ let startChangeDiff = 0;
6145
+ let endChangeDiff = 0;
6146
+ // need to check only rangeStart > tag.plainTextBeginIndex as when rangeStart === tag.plainTextBeginIndex startChangeDiff = 0 and mentionTagLength shouldn't be subtracted
6147
+ if (rangeStart > tag.plainTextBeginIndex) {
6148
+ startChangeDiff = rangeStart - tag.plainTextBeginIndex - mentionTagLength;
6084
6149
  }
6085
- if (location === 'below') {
6086
- finalPosition.top = ((_e = rect === null || rect === void 0 ? void 0 : rect.height) !== null && _e !== void 0 ? _e : 0) + ((_f = targetPositionOffset === null || targetPositionOffset === void 0 ? void 0 : targetPositionOffset.top) !== null && _f !== void 0 ? _f : 0);
6150
+ endChangeDiff = rangeEnd - tag.plainTextBeginIndex - mentionTagLength;
6151
+ result += htmlText.substring(lastProcessedHTMLIndex, tag.openTagIndex + tag.openTagBody.length + startChangeDiff);
6152
+ if (startIndex < tag.plainTextBeginIndex) {
6153
+ // if the change is before the tag, the selection should start from startIndex (rangeStart will be equal to tag.plainTextBeginIndex)
6154
+ plainTextSelectionEndIndex = startIndex + change.length;
6087
6155
  }
6088
6156
  else {
6089
- // (location === 'above')
6090
- finalPosition.bottom = ((_g = rect === null || rect === void 0 ? void 0 : rect.height) !== null && _g !== void 0 ? _g : 0) - ((_h = targetPositionOffset === null || targetPositionOffset === void 0 ? void 0 : targetPositionOffset.top) !== null && _h !== void 0 ? _h : 0);
6091
- }
6092
- setPosition(finalPosition);
6093
- }, [location, target, targetPositionOffset]);
6094
- const handleOnKeyDown = React.useCallback((e) => {
6095
- switch (e.key) {
6096
- case 'Escape':
6097
- onDismiss && onDismiss();
6098
- break;
6099
- }
6100
- }, [onDismiss]);
6101
- const personaRenderer = React.useCallback((displayName) => {
6102
- const displayNamePlaceholder = localeStrings.participantItem.displayNamePlaceholder;
6103
- const avatarOptions = {
6104
- text: (displayName === null || displayName === void 0 ? void 0 : displayName.trim()) || displayNamePlaceholder,
6105
- size: react.PersonaSize.size24,
6106
- initialsColor: theme.palette.neutralLight,
6107
- initialsTextColor: theme.palette.neutralSecondary,
6108
- showOverflowTooltip: false,
6109
- showUnknownPersonaCoin: !(displayName === null || displayName === void 0 ? void 0 : displayName.trim()) || displayName === displayNamePlaceholder
6110
- };
6111
- return React__default['default'].createElement(react.Persona, Object.assign({}, avatarOptions));
6112
- }, [localeStrings, theme]);
6113
- const defaultOnRenderSuggestionItem = React.useCallback((suggestion, onSuggestionSelected, active) => {
6114
- return (React__default['default'].createElement("div", { "data-is-focusable": true, "data-ui-id": ids.mentionSuggestionItem, key: suggestion.id, onClick: () => onSuggestionSelected(suggestion), onMouseEnter: () => setHoveredSuggestion(suggestion), onMouseLeave: () => setHoveredSuggestion(undefined), onKeyDown: (e) => {
6115
- handleOnKeyDown(e);
6116
- }, className: suggestionItemWrapperStyle(theme) },
6117
- React__default['default'].createElement(react.Stack, { horizontal: true, className: suggestionItemStackStyle(theme, (hoveredSuggestion === null || hoveredSuggestion === void 0 ? void 0 : hoveredSuggestion.id) === suggestion.id, (changedSelection !== null && changedSelection !== void 0 ? changedSelection : false) && active) }, personaRenderer(suggestion.displayText))));
6118
- }, [
6119
- handleOnKeyDown,
6120
- theme,
6121
- /* @conditional-compile-remove(mention) */
6122
- ids,
6123
- hoveredSuggestion,
6124
- changedSelection,
6125
- personaRenderer
6126
- ]);
6127
- const getHeaderTitle = React.useCallback(() => {
6128
- if (title) {
6129
- return title;
6157
+ // if the change is inside the tag, the selection should start with rangeStart
6158
+ plainTextSelectionEndIndex = rangeStart + processedChange.length;
6130
6159
  }
6131
- /* @conditional-compile-remove(mention) */
6132
- return localeStrings.mentionPopover.mentionPopoverHeader;
6133
- }, [localeStrings, title]);
6134
- return (React__default['default'].createElement("div", { ref: popoverRef },
6135
- React__default['default'].createElement(react.Stack, { className: react.mergeStyles({
6136
- maxHeight: 212,
6137
- maxWidth: position.maxWidth
6138
- }, mentionPopoverContainerStyle(theme), Object.assign(Object.assign({}, position), { position: 'absolute' })) },
6139
- React__default['default'].createElement(react.Stack.Item, { styles: headerStyleThemed(theme), "aria-label": title }, getHeaderTitle()),
6140
- React__default['default'].createElement(react.Stack
6141
- /* @conditional-compile-remove(mention) */
6142
- , { "data-ui-id": ids.mentionSuggestionList, className: suggestionListStyle }, suggestions.map((suggestion, index) => {
6143
- const active = index === activeSuggestionIndex;
6144
- return onRenderSuggestionItem
6145
- ? onRenderSuggestionItem(suggestion, onSuggestionSelected, active)
6146
- : defaultOnRenderSuggestionItem(suggestion, onSuggestionSelected, active);
6147
- })))));
6160
+ lastProcessedHTMLIndex = tag.openTagIndex + tag.openTagBody.length + endChangeDiff;
6161
+ // processed change should not be changed as it should be added after the tag
6162
+ }
6163
+ return { result, updatedChange: processedChange, htmlIndex: lastProcessedHTMLIndex, plainTextSelectionEndIndex };
6148
6164
  };
6149
-
6150
- // Copyright (c) Microsoft Corporation.
6151
- // Licensed under the MIT license.
6152
- var __awaiter$w = (window && window.__awaiter) || function (thisArg, _arguments, P, generator) {
6153
- function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
6154
- return new (P || (P = Promise))(function (resolve, reject) {
6155
- function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6156
- function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
6157
- function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
6158
- step((generator = generator.apply(thisArg, _arguments || [])).next());
6159
- });
6165
+ /**
6166
+ * Get closing tag information if exists otherwise return information as for self closing tag
6167
+ *
6168
+ * @private
6169
+ * @param tag - Tag data.
6170
+ * @returns Closing tag information for the provided tag.
6171
+ */
6172
+ const getTagClosingTagInfo = (tag) => {
6173
+ let plainTextEndIndex = 0;
6174
+ let closeTagIdx = 0;
6175
+ let closeTagLength = 0;
6176
+ if (tag.plainTextEndIndex !== undefined && tag.closingTagIndex !== undefined) {
6177
+ // close tag exists
6178
+ plainTextEndIndex = tag.plainTextEndIndex;
6179
+ closeTagIdx = tag.closingTagIndex;
6180
+ // tag.tagType.length + </>
6181
+ closeTagLength = tag.tagType.length + 3;
6182
+ }
6183
+ else if (tag.plainTextBeginIndex !== undefined) {
6184
+ // no close tag
6185
+ plainTextEndIndex = tag.plainTextBeginIndex;
6186
+ closeTagIdx = tag.openTagIndex + tag.openTagBody.length;
6187
+ closeTagLength = 0;
6188
+ }
6189
+ return { plainTextEndIndex, closeTagIdx, closeTagLength };
6160
6190
  };
6161
- /* @conditional-compile-remove(mention) */
6162
- const DEFAULT_MENTION_TRIGGER = '@';
6163
- /* @conditional-compile-remove(mention) */
6164
- const MSFT_MENTION_TAG = 'msft-mention';
6165
6191
  /**
6192
+ * Go through the text and update it with the changed text
6193
+ *
6166
6194
  * @private
6195
+ * @param props - Props for update HTML function.
6196
+ * @returns Updated HTML and selection index if the selection index should be set.
6167
6197
  */
6168
- const InputBoxComponent = (props) => {
6169
- const { styles, id, 'data-ui-id': dataUiId, textValue, onChange, textFieldRef, placeholderText, onKeyDown, onEnterKeyDown, supportNewline, inputClassName, errorMessage, disabled,
6170
- /* @conditional-compile-remove(mention) */
6171
- mentionLookupOptions, children } = props;
6172
- const inputBoxRef = React.useRef(null);
6173
- /* @conditional-compile-remove(mention) */
6174
- // Current suggestion list, provided by the callback
6175
- const [mentionSuggestions, setMentionSuggestions] = React.useState([]);
6176
- /* @conditional-compile-remove(mention) */
6177
- // Current suggestion list, provided by the callback
6178
- const [activeSuggestionIndex, setActiveSuggestionIndex] = React.useState(undefined);
6179
- /* @conditional-compile-remove(mention) */
6180
- // Index of the current trigger character in the text field
6181
- const [currentTriggerStartIndex, setCurrentTriggerStartIndex] = React.useState(-1);
6182
- /* @conditional-compile-remove(mention) */
6183
- const [inputTextValue, setInputTextValue] = React.useState('');
6184
- /* @conditional-compile-remove(mention) */
6185
- const [tagsValue, setTagsValue] = React.useState([]);
6186
- /* @conditional-compile-remove(mention) */
6187
- // Index of the previous selection start in the text field
6188
- const [selectionStartValue, setSelectionStartValue] = React.useState();
6189
- /* @conditional-compile-remove(mention) */
6190
- // Index of the previous selection end in the text field
6191
- const [selectionEndValue, setSelectionEndValue] = React.useState();
6192
- /* @conditional-compile-remove(mention) */
6193
- // Boolean value to check if onMouseDown event should be handled during select as selection range
6194
- // for onMouseDown event is not updated yet and the selection range for mouse click/taps will be
6195
- // updated in onSelect event if needed.
6196
- const [shouldHandleOnMouseDownDuringSelect, setShouldHandleOnMouseDownDuringSelect] = React.useState(true);
6197
- /* @conditional-compile-remove(mention) */
6198
- // Point of start of touch/mouse selection
6199
- const [interactionStartPoint, setInteractionStartPoint] = React.useState();
6200
- /* @conditional-compile-remove(mention) */
6201
- // Target selection from mouse movement
6202
- const [targetSelection, setTargetSelection] = React.useState();
6203
- /* @conditional-compile-remove(mention) */
6204
- // Caret position in the text field
6205
- const [caretPosition, setCaretPosition] = React.useState(undefined);
6206
- /* @conditional-compile-remove(mention) */
6207
- // Index of where the caret is in the text field
6208
- const [caretIndex, setCaretIndex] = React.useState(undefined);
6209
- /* @conditional-compile-remove(mention) */
6210
- const localeStrings = useLocale$1().strings;
6211
- /* @conditional-compile-remove(mention) */
6212
- // Set mention suggestions
6213
- const updateMentionSuggestions = React.useCallback((suggestions) => {
6214
- setMentionSuggestions(suggestions);
6215
- }, [setMentionSuggestions]);
6216
- /* @conditional-compile-remove(mention) */
6217
- // Parse the text and get the plain text version to display in the input box
6218
- React.useEffect(() => {
6219
- const trigger = (mentionLookupOptions === null || mentionLookupOptions === void 0 ? void 0 : mentionLookupOptions.trigger) || DEFAULT_MENTION_TRIGGER;
6220
- const parsedHTMLData = textToTagParser(textValue, trigger);
6221
- setInputTextValue(parsedHTMLData.plainText);
6222
- setTagsValue(parsedHTMLData.tags);
6223
- updateMentionSuggestions([]);
6224
- }, [textValue, mentionLookupOptions === null || mentionLookupOptions === void 0 ? void 0 : mentionLookupOptions.trigger, updateMentionSuggestions]);
6225
- const mergedRootStyle = react.mergeStyles(inputBoxWrapperStyle, styles === null || styles === void 0 ? void 0 : styles.root);
6226
- const mergedInputFieldStyle = react.mergeStyles(inputBoxStyle, inputClassName, props.inlineChildren ? {} : inputBoxNewLineSpaceAffordance);
6227
- /* @conditional-compile-remove(mention) */
6228
- React.useEffect(() => {
6229
- var _a;
6230
- // effect for caret index update
6231
- if (caretIndex === undefined || textFieldRef === undefined || (textFieldRef === null || textFieldRef === void 0 ? void 0 : textFieldRef.current) === undefined) {
6232
- return;
6198
+ const updateHTML = (props) => {
6199
+ const { htmlText, oldPlainText, newPlainText, tags, startIndex, oldPlainTextEndIndex, change, mentionTrigger } = props;
6200
+ if (tags.length === 0 || (startIndex === 0 && oldPlainTextEndIndex === oldPlainText.length - 1)) {
6201
+ // no tags added yet or the whole text is changed
6202
+ return { updatedHTML: newPlainText, updatedSelectionIndex: undefined };
6203
+ }
6204
+ let result = '';
6205
+ let lastProcessedHTMLIndex = 0;
6206
+ // the value can be updated with empty string when the change covers more than 1 place (tag + before or after the tag)
6207
+ // in this case change won't be added as part of the tag
6208
+ // e.g.: change is before and partially in tag => change will be added before the tag and outdated text in the tag will be removed
6209
+ // e.g.: change is after and partially in tag => change will be added after the tag and outdated text in the tag will be removed
6210
+ // e.g.: change is on the beginning of the tag => change will be added before the tag
6211
+ // e.g.: change is on the end of the tag => change will be added to the tag if it's not mention and after the tag if it's mention
6212
+ let processedChange = change;
6213
+ // end tag plain text index of the last processed tag
6214
+ let lastProcessedPlainTextTagEndIndex = 0;
6215
+ // as some tags/text can be removed fully, selection should be updated correctly
6216
+ let changeNewEndIndex;
6217
+ for (let i = 0; i < tags.length; i++) {
6218
+ const tag = tags[i];
6219
+ if (tag.plainTextBeginIndex === undefined) {
6220
+ continue;
6233
6221
  }
6234
- // get validated caret index between 0 and inputTextValue.length otherwise caret will be set to incorrect index
6235
- const updatedCaretIndex = getValidatedIndexInRange({
6236
- min: 0,
6237
- max: inputTextValue.length,
6238
- currentValue: caretIndex
6239
- });
6240
- (_a = textFieldRef === null || textFieldRef === void 0 ? void 0 : textFieldRef.current) === null || _a === void 0 ? void 0 : _a.setSelectionRange(updatedCaretIndex, updatedCaretIndex);
6241
- setSelectionStartValue(updatedCaretIndex);
6242
- setSelectionEndValue(updatedCaretIndex);
6243
- }, [caretIndex, inputTextValue.length, textFieldRef, setSelectionStartValue, setSelectionEndValue]);
6244
- const mergedTextContainerStyle = react.mergeStyles(textContainerStyle, styles === null || styles === void 0 ? void 0 : styles.textFieldContainer);
6245
- const mergedTextFieldStyle = react.concatStyleSets(textFieldStyle, {
6246
- fieldGroup: styles === null || styles === void 0 ? void 0 : styles.textField,
6247
- errorMessage: styles === null || styles === void 0 ? void 0 : styles.systemMessage,
6248
- suffix: {
6249
- backgroundColor: 'transparent',
6250
- // Remove empty space in the suffix area when adding newline-style buttons
6251
- display: props.inlineChildren ? 'flex' : 'contents',
6252
- padding: '0 0.25rem'
6253
- }
6254
- });
6255
- const mergedChildrenStyle = react.mergeStyles(props.inlineChildren ? {} : newLineButtonsContainerStyle);
6256
- /* @conditional-compile-remove(mention) */
6257
- const onSuggestionSelected = React.useCallback((suggestion) => {
6258
- var _a, _b, _c;
6259
- let selectionEnd = ((_a = textFieldRef === null || textFieldRef === void 0 ? void 0 : textFieldRef.current) === null || _a === void 0 ? void 0 : _a.selectionEnd) || -1;
6260
- if (selectionEnd < 0) {
6261
- selectionEnd = 0;
6262
- }
6263
- else if (selectionEnd > inputTextValue.length) {
6264
- selectionEnd = inputTextValue.length;
6265
- }
6266
- const oldPlainText = inputTextValue;
6267
- const mention = htmlStringForMentionSuggestion(suggestion, localeStrings);
6268
- // update plain text with the mention html text
6269
- const newPlainText = inputTextValue.substring(0, currentTriggerStartIndex) + mention + inputTextValue.substring(selectionEnd);
6270
- const triggerText = (_b = mentionLookupOptions === null || mentionLookupOptions === void 0 ? void 0 : mentionLookupOptions.trigger) !== null && _b !== void 0 ? _b : DEFAULT_MENTION_TRIGGER;
6271
- // update html text with updated plain text
6272
- const updatedContent = updateHTML({
6273
- htmlText: textValue,
6274
- oldPlainText,
6275
- newPlainText,
6276
- tags: tagsValue,
6277
- startIndex: currentTriggerStartIndex,
6278
- oldPlainTextEndIndex: selectionEnd,
6279
- change: mention,
6280
- mentionTrigger: triggerText
6281
- });
6282
- const displayName = getDisplayNameForMentionSuggestion(suggestion, localeStrings);
6283
- const newCaretIndex = currentTriggerStartIndex + displayName.length + triggerText.length;
6284
- // move the caret in the text field to the end of the mention plain text
6285
- setCaretIndex(newCaretIndex);
6286
- setSelectionEndValue(newCaretIndex);
6287
- setSelectionStartValue(newCaretIndex);
6288
- setCurrentTriggerStartIndex(-1);
6289
- updateMentionSuggestions([]);
6290
- // set focus back to text field
6291
- (_c = textFieldRef === null || textFieldRef === void 0 ? void 0 : textFieldRef.current) === null || _c === void 0 ? void 0 : _c.focus();
6292
- setActiveSuggestionIndex(undefined);
6293
- onChange && onChange(undefined, updatedContent.updatedHTML);
6294
- }, [
6295
- textFieldRef,
6296
- inputTextValue,
6297
- currentTriggerStartIndex,
6298
- mentionLookupOptions === null || mentionLookupOptions === void 0 ? void 0 : mentionLookupOptions.trigger,
6299
- onChange,
6300
- textValue,
6301
- tagsValue,
6302
- /* @conditional-compile-remove(mention) */
6303
- updateMentionSuggestions,
6304
- /* @conditional-compile-remove(mention) */
6305
- localeStrings
6306
- ]);
6307
- const onTextFieldKeyDown = React.useCallback((ev) => {
6308
- /* @conditional-compile-remove(mention) */
6309
- // caretIndex should be set to undefined when the user is typing
6310
- setCaretIndex(undefined);
6311
- // shouldHandleOnMouseDownDuringSelect should be set to false after the last mouse down event.
6312
- // it shouldn't be updated in onMouseUp
6313
- // as onMouseUp can be triggered before or after onSelect event
6314
- // because its order depends on mouse events not selection.
6315
- /* @conditional-compile-remove(mention) */
6316
- setShouldHandleOnMouseDownDuringSelect(false);
6317
- if (isEnterKeyEventFromCompositionSession(ev)) {
6318
- return;
6222
+ // all plain text indexes includes trigger length for the mention that shouldn't be included in
6223
+ // htmlText.substring because html strings don't include the trigger
6224
+ // mentionTagLength will be set only for mention tag, otherwise should be 0
6225
+ let mentionTagLength = 0;
6226
+ let isMentionTag = false;
6227
+ if (tag.tagType === MSFT_MENTION_TAG) {
6228
+ mentionTagLength = mentionTrigger.length;
6229
+ isMentionTag = true;
6319
6230
  }
6320
- /* @conditional-compile-remove(mention) */
6321
- if (mentionSuggestions.length > 0) {
6322
- if (ev.key === 'ArrowUp') {
6323
- ev.preventDefault();
6324
- const newActiveIndex = activeSuggestionIndex === undefined
6325
- ? mentionSuggestions.length - 1
6326
- : Math.max(activeSuggestionIndex - 1, 0);
6327
- setActiveSuggestionIndex(newActiveIndex);
6328
- }
6329
- else if (ev.key === 'ArrowDown') {
6330
- ev.preventDefault();
6331
- const newActiveIndex = activeSuggestionIndex === undefined
6332
- ? 0
6333
- : Math.min(activeSuggestionIndex + 1, mentionSuggestions.length - 1);
6334
- setActiveSuggestionIndex(newActiveIndex);
6231
+ if (startIndex <= tag.plainTextBeginIndex) {
6232
+ // change start is before the open tag
6233
+ // Math.max(lastProcessedPlainTextTagEndIndex, startIndex) is used as startIndex may not be in [[previous tag].plainTextEndIndex - tag.plainTextBeginIndex] range
6234
+ const startChangeDiff = tag.plainTextBeginIndex - Math.max(lastProcessedPlainTextTagEndIndex, startIndex);
6235
+ result += htmlText.substring(lastProcessedHTMLIndex, tag.openTagIndex - startChangeDiff) + processedChange;
6236
+ processedChange = '';
6237
+ if (oldPlainTextEndIndex <= tag.plainTextBeginIndex) {
6238
+ // the whole change is before tag start
6239
+ // mentionTag length can be ignored here as the change is before the tag
6240
+ const endChangeDiff = tag.plainTextBeginIndex - oldPlainTextEndIndex;
6241
+ lastProcessedHTMLIndex = tag.openTagIndex - endChangeDiff;
6242
+ // the change is handled; exit
6243
+ break;
6335
6244
  }
6336
- else if (ev.key === 'Escape') {
6337
- updateMentionSuggestions([]);
6245
+ else {
6246
+ // change continues in the tag
6247
+ lastProcessedHTMLIndex = tag.openTagIndex;
6248
+ // proceed to the next check
6338
6249
  }
6339
6250
  }
6340
- if (ev.key === 'Enter' && (ev.shiftKey === false || !supportNewline)) {
6341
- ev.preventDefault();
6342
- // If we are looking up a mention, select the focused suggestion
6343
- /* @conditional-compile-remove(mention) */
6344
- if (mentionSuggestions.length > 0 && activeSuggestionIndex !== undefined) {
6345
- const selectedMention = mentionSuggestions[activeSuggestionIndex];
6346
- if (selectedMention) {
6347
- onSuggestionSelected(selectedMention);
6348
- return;
6251
+ const closingTagInfo = getTagClosingTagInfo(tag);
6252
+ if (startIndex <= closingTagInfo.plainTextEndIndex) {
6253
+ // change started before the end tag
6254
+ if (startIndex <= tag.plainTextBeginIndex && oldPlainTextEndIndex === closingTagInfo.plainTextEndIndex) {
6255
+ // the change is a tag or starts before the tag
6256
+ // tag should be removed, no matter if there are sub-tags
6257
+ result += htmlText.substring(lastProcessedHTMLIndex, tag.openTagIndex) + processedChange;
6258
+ processedChange = '';
6259
+ lastProcessedHTMLIndex = closingTagInfo.closeTagIdx + closingTagInfo.closeTagLength;
6260
+ // the change is handled; exit
6261
+ break;
6262
+ }
6263
+ else if (startIndex >= tag.plainTextBeginIndex && oldPlainTextEndIndex <= closingTagInfo.plainTextEndIndex) {
6264
+ // the change is between the tag
6265
+ if (isMentionTag) {
6266
+ if (change !== '') {
6267
+ if (startIndex !== tag.plainTextBeginIndex && startIndex !== closingTagInfo.plainTextEndIndex) {
6268
+ // mention tag should be deleted when user tries to edit it in the middle
6269
+ result += htmlText.substring(lastProcessedHTMLIndex, tag.openTagIndex) + processedChange;
6270
+ changeNewEndIndex = tag.plainTextBeginIndex + processedChange.length;
6271
+ lastProcessedHTMLIndex = closingTagInfo.closeTagIdx + closingTagInfo.closeTagLength;
6272
+ }
6273
+ else if (startIndex === tag.plainTextBeginIndex) {
6274
+ // non empty change at the beginning of the mention tag to be added before the mention tag
6275
+ result += htmlText.substring(lastProcessedHTMLIndex, tag.openTagIndex) + processedChange;
6276
+ changeNewEndIndex = tag.plainTextBeginIndex + processedChange.length;
6277
+ lastProcessedHTMLIndex = tag.openTagIndex;
6278
+ }
6279
+ else if (startIndex === closingTagInfo.plainTextEndIndex) {
6280
+ // non empty change at the end of the mention tag to be added after the mention tag
6281
+ result +=
6282
+ htmlText.substring(lastProcessedHTMLIndex, closingTagInfo.closeTagIdx + closingTagInfo.closeTagLength) +
6283
+ processedChange;
6284
+ changeNewEndIndex = closingTagInfo.plainTextEndIndex + processedChange.length;
6285
+ lastProcessedHTMLIndex = closingTagInfo.closeTagIdx + closingTagInfo.closeTagLength;
6286
+ }
6287
+ processedChange = '';
6288
+ }
6289
+ else {
6290
+ const updateMentionTagResult = handleMentionTagUpdate({
6291
+ htmlText,
6292
+ oldPlainText,
6293
+ lastProcessedHTMLIndex,
6294
+ processedChange,
6295
+ change,
6296
+ tag,
6297
+ closeTagIdx: closingTagInfo.closeTagIdx,
6298
+ closeTagLength: closingTagInfo.closeTagLength,
6299
+ plainTextEndIndex: closingTagInfo.plainTextEndIndex,
6300
+ startIndex,
6301
+ oldPlainTextEndIndex,
6302
+ mentionTagLength
6303
+ });
6304
+ result += updateMentionTagResult.result;
6305
+ changeNewEndIndex = updateMentionTagResult.plainTextSelectionEndIndex;
6306
+ processedChange = updateMentionTagResult.updatedChange;
6307
+ lastProcessedHTMLIndex = updateMentionTagResult.htmlIndex;
6308
+ }
6309
+ }
6310
+ else if (tag.subTags !== undefined && tag.subTags.length !== 0 && tag.content !== undefined) {
6311
+ // with subtags
6312
+ const stringBefore = htmlText.substring(lastProcessedHTMLIndex, tag.openTagIndex + tag.openTagBody.length);
6313
+ lastProcessedHTMLIndex = closingTagInfo.closeTagIdx;
6314
+ const updatedContent = updateHTML({
6315
+ htmlText: tag.content,
6316
+ oldPlainText,
6317
+ newPlainText,
6318
+ tags: tag.subTags,
6319
+ startIndex,
6320
+ oldPlainTextEndIndex,
6321
+ change: processedChange,
6322
+ mentionTrigger
6323
+ });
6324
+ result += stringBefore + updatedContent.updatedHTML;
6325
+ changeNewEndIndex = updatedContent.updatedSelectionIndex;
6326
+ }
6327
+ else {
6328
+ // no subtags
6329
+ const startChangeDiff = startIndex - tag.plainTextBeginIndex;
6330
+ result +=
6331
+ htmlText.substring(lastProcessedHTMLIndex, tag.openTagIndex + tag.openTagBody.length + startChangeDiff) +
6332
+ processedChange;
6333
+ processedChange = '';
6334
+ if (oldPlainTextEndIndex < closingTagInfo.plainTextEndIndex) {
6335
+ const endChangeDiff = oldPlainTextEndIndex - tag.plainTextBeginIndex;
6336
+ lastProcessedHTMLIndex = tag.openTagIndex + tag.openTagBody.length + endChangeDiff;
6337
+ }
6338
+ else if (oldPlainTextEndIndex === closingTagInfo.plainTextEndIndex) {
6339
+ lastProcessedHTMLIndex = closingTagInfo.closeTagIdx;
6340
+ }
6349
6341
  }
6342
+ // the change is handled; exit
6343
+ break;
6350
6344
  }
6351
- onEnterKeyDown && onEnterKeyDown();
6352
- }
6353
- onKeyDown && onKeyDown(ev);
6354
- }, [
6355
- onEnterKeyDown,
6356
- onKeyDown,
6357
- supportNewline,
6358
- /* @conditional-compile-remove(mention) */
6359
- mentionSuggestions,
6360
- /* @conditional-compile-remove(mention) */
6361
- activeSuggestionIndex,
6362
- /* @conditional-compile-remove(mention) */
6363
- onSuggestionSelected,
6364
- /* @conditional-compile-remove(mention) */
6365
- updateMentionSuggestions
6366
- ]);
6367
- const onRenderChildren = () => {
6368
- return (React__default['default'].createElement(react.Stack, { horizontal: true, className: mergedChildrenStyle }, children));
6369
- };
6370
- /* @conditional-compile-remove(mention) */
6371
- const debouncedQueryUpdate = useDebounce.useDebouncedCallback((query) => __awaiter$w(void 0, void 0, void 0, function* () {
6372
- var _a;
6373
- const suggestions = (_a = (yield (mentionLookupOptions === null || mentionLookupOptions === void 0 ? void 0 : mentionLookupOptions.onQueryUpdated(query)))) !== null && _a !== void 0 ? _a : [];
6374
- if (suggestions.length === 0) {
6375
- setActiveSuggestionIndex(undefined);
6376
- }
6377
- else if (activeSuggestionIndex === undefined) {
6378
- // Set the active to the first, if it's not already set
6379
- setActiveSuggestionIndex(0);
6380
- }
6381
- updateMentionSuggestions(suggestions);
6382
- }), 500);
6383
- /* @conditional-compile-remove(mention) */
6384
- // Update selections index in mention to navigate by words
6385
- const updateSelectionIndexesWithMentionIfNeeded = React.useCallback(({ event, inputTextValue, selectionEndValue, selectionStartValue, tagsValue }) => {
6386
- var _a, _b, _c;
6387
- let updatedStartIndex = event.currentTarget.selectionStart;
6388
- let updatedEndIndex = event.currentTarget.selectionEnd;
6389
- if (event.currentTarget.selectionStart === event.currentTarget.selectionEnd &&
6390
- event.currentTarget.selectionStart !== null &&
6391
- event.currentTarget.selectionStart !== -1) {
6392
- // just a caret movement/usual typing or deleting
6393
- const mentionTag = findMentionTagForSelection(tagsValue, event.currentTarget.selectionStart);
6394
- // don't include boundary cases to show correct selection, otherwise it will show selection at mention boundaries
6395
- if (mentionTag !== undefined &&
6396
- mentionTag.plainTextBeginIndex !== undefined &&
6397
- event.currentTarget.selectionStart > mentionTag.plainTextBeginIndex &&
6398
- event.currentTarget.selectionStart < ((_a = mentionTag.plainTextEndIndex) !== null && _a !== void 0 ? _a : mentionTag.plainTextBeginIndex)) {
6399
- // get updated selection index
6400
- const newSelectionIndex = findNewSelectionIndexForMention({
6401
- tag: mentionTag,
6402
- textValue: inputTextValue,
6403
- currentSelectionIndex: event.currentTarget.selectionStart,
6404
- previousSelectionIndex: selectionStartValue !== null && selectionStartValue !== void 0 ? selectionStartValue : inputTextValue.length
6405
- });
6406
- updatedStartIndex = newSelectionIndex;
6407
- updatedEndIndex = newSelectionIndex;
6408
- }
6409
- }
6410
- else if (event.currentTarget.selectionStart !== event.currentTarget.selectionEnd) {
6411
- // Both e.currentTarget.selectionStart !== selectionStartValue and e.currentTarget.selectionEnd !== selectionEndValue can be true when a user selects a text by double click
6412
- if (event.currentTarget.selectionStart !== null && event.currentTarget.selectionStart !== selectionStartValue) {
6413
- // the selection start is changed
6414
- const mentionTag = findMentionTagForSelection(tagsValue, event.currentTarget.selectionStart);
6415
- // don't include boundary cases to show correct selection, otherwise it will show selection at mention boundaries
6416
- if (mentionTag !== undefined &&
6417
- mentionTag.plainTextBeginIndex !== undefined &&
6418
- event.currentTarget.selectionStart > mentionTag.plainTextBeginIndex &&
6419
- event.currentTarget.selectionStart < ((_b = mentionTag.plainTextEndIndex) !== null && _b !== void 0 ? _b : mentionTag.plainTextBeginIndex)) {
6420
- updatedStartIndex = findNewSelectionIndexForMention({
6421
- tag: mentionTag,
6422
- textValue: inputTextValue,
6423
- currentSelectionIndex: event.currentTarget.selectionStart,
6424
- previousSelectionIndex: selectionStartValue !== null && selectionStartValue !== void 0 ? selectionStartValue : inputTextValue.length
6345
+ else if (startIndex > tag.plainTextBeginIndex && oldPlainTextEndIndex > closingTagInfo.plainTextEndIndex) {
6346
+ // the change started in the tag but finishes somewhere further
6347
+ const startChangeDiff = startIndex - tag.plainTextBeginIndex - mentionTagLength;
6348
+ if (isMentionTag) {
6349
+ const updateMentionTagResult = handleMentionTagUpdate({
6350
+ htmlText,
6351
+ oldPlainText,
6352
+ lastProcessedHTMLIndex,
6353
+ processedChange: '',
6354
+ change,
6355
+ tag,
6356
+ closeTagIdx: closingTagInfo.closeTagIdx,
6357
+ closeTagLength: closingTagInfo.closeTagLength,
6358
+ plainTextEndIndex: closingTagInfo.plainTextEndIndex,
6359
+ startIndex,
6360
+ oldPlainTextEndIndex,
6361
+ mentionTagLength
6425
6362
  });
6363
+ result += updateMentionTagResult.result;
6364
+ lastProcessedHTMLIndex = updateMentionTagResult.htmlIndex;
6365
+ // no need to handle plainTextSelectionEndIndex as the change will be added later
6426
6366
  }
6427
- }
6428
- if (event.currentTarget.selectionEnd !== null && event.currentTarget.selectionEnd !== selectionEndValue) {
6429
- // the selection end is changed
6430
- const mentionTag = findMentionTagForSelection(tagsValue, event.currentTarget.selectionEnd);
6431
- // don't include boundary cases to show correct selection, otherwise it will show selection at mention boundaries
6432
- if (mentionTag !== undefined &&
6433
- mentionTag.plainTextBeginIndex !== undefined &&
6434
- event.currentTarget.selectionEnd > mentionTag.plainTextBeginIndex &&
6435
- event.currentTarget.selectionEnd < ((_c = mentionTag.plainTextEndIndex) !== null && _c !== void 0 ? _c : mentionTag.plainTextBeginIndex)) {
6436
- updatedEndIndex = findNewSelectionIndexForMention({
6437
- tag: mentionTag,
6438
- textValue: inputTextValue,
6439
- currentSelectionIndex: event.currentTarget.selectionEnd,
6440
- previousSelectionIndex: selectionEndValue !== null && selectionEndValue !== void 0 ? selectionEndValue : inputTextValue.length
6367
+ else if (tag.subTags !== undefined && tag.subTags.length !== 0 && tag.content !== undefined) {
6368
+ // with subtags
6369
+ const stringBefore = htmlText.substring(lastProcessedHTMLIndex, tag.openTagIndex + tag.openTagBody.length);
6370
+ lastProcessedHTMLIndex = closingTagInfo.closeTagIdx;
6371
+ const updatedContent = updateHTML({
6372
+ htmlText: tag.content,
6373
+ oldPlainText,
6374
+ newPlainText,
6375
+ tags: tag.subTags,
6376
+ startIndex,
6377
+ oldPlainTextEndIndex,
6378
+ change: '',
6379
+ mentionTrigger
6441
6380
  });
6381
+ result += stringBefore + updatedContent.updatedHTML;
6382
+ }
6383
+ else {
6384
+ // no subtags
6385
+ result += htmlText.substring(lastProcessedHTMLIndex, tag.openTagIndex + tag.openTagBody.length + startChangeDiff);
6386
+ lastProcessedHTMLIndex = closingTagInfo.closeTagIdx;
6442
6387
  }
6388
+ // proceed with the next calculations
6443
6389
  }
6444
- }
6445
- // e.currentTarget.selectionDirection should be set to handle shift + arrow keys
6446
- if (event.currentTarget.selectionDirection === null) {
6447
- event.currentTarget.setSelectionRange(updatedStartIndex, updatedEndIndex);
6448
- }
6449
- else {
6450
- event.currentTarget.setSelectionRange(updatedStartIndex, updatedEndIndex, event.currentTarget.selectionDirection);
6451
- }
6452
- setSelectionStartValue(nullToUndefined(updatedStartIndex));
6453
- setSelectionEndValue(nullToUndefined(updatedEndIndex));
6454
- }, [setSelectionStartValue, setSelectionEndValue]);
6455
- /* @conditional-compile-remove(mention) */
6456
- const handleOnSelect = React.useCallback(({ event, inputTextValue, tags, shouldHandleOnMouseDownDuringSelect, selectionStartValue, selectionEndValue }) => {
6457
- if (shouldHandleOnMouseDownDuringSelect) {
6458
- if (targetSelection !== undefined) {
6459
- setSelectionStartValue(targetSelection.start);
6460
- setSelectionEndValue(targetSelection.end);
6461
- event.currentTarget.setSelectionRange(targetSelection.start, undefinedToNull(targetSelection.end));
6462
- setTargetSelection(undefined);
6390
+ else if (startIndex < tag.plainTextBeginIndex && oldPlainTextEndIndex > closingTagInfo.plainTextEndIndex) {
6391
+ // the change starts before the tag and finishes after it
6392
+ // tag should be removed, no matter if there are subtags
6393
+ // no need to save anything between lastProcessedHTMLIndex and closeTagIdx + closeTagLength
6394
+ lastProcessedHTMLIndex = closingTagInfo.closeTagIdx + closingTagInfo.closeTagLength;
6395
+ // proceed with the next calculations
6463
6396
  }
6464
- else if (event.currentTarget.selectionStart !== null) {
6465
- // on select was triggered by mouse down/up with no movement
6466
- const mentionTag = findMentionTagForSelection(tags, event.currentTarget.selectionStart);
6467
- if (mentionTag !== undefined && mentionTag.plainTextBeginIndex !== undefined) {
6468
- // handle mention click
6469
- // Get range of word that was clicked on
6470
- const selectionRange = rangeOfWordInSelection({
6471
- textInput: inputTextValue,
6472
- selectionStart: event.currentTarget.selectionStart,
6473
- selectionEnd: nullToUndefined(event.currentTarget.selectionEnd),
6474
- tag: mentionTag
6397
+ else if (startIndex === tag.plainTextBeginIndex && oldPlainTextEndIndex > closingTagInfo.plainTextEndIndex) {
6398
+ // the change starts in the tag and finishes after it
6399
+ // tag should be removed, no matter if there are subtags
6400
+ result += htmlText.substring(lastProcessedHTMLIndex, tag.openTagIndex);
6401
+ // processedChange shouldn't be updated as it will be added after the tag
6402
+ lastProcessedHTMLIndex = closingTagInfo.closeTagIdx + closingTagInfo.closeTagLength;
6403
+ // proceed with the next calculations
6404
+ }
6405
+ else if (startIndex < tag.plainTextBeginIndex && oldPlainTextEndIndex < closingTagInfo.plainTextEndIndex) {
6406
+ // the change starts before the tag and ends in a tag
6407
+ if (isMentionTag) {
6408
+ // mention tag
6409
+ const updateMentionTagResult = handleMentionTagUpdate({
6410
+ htmlText,
6411
+ oldPlainText,
6412
+ lastProcessedHTMLIndex,
6413
+ processedChange: '',
6414
+ change,
6415
+ tag,
6416
+ closeTagIdx: closingTagInfo.closeTagIdx,
6417
+ closeTagLength: closingTagInfo.closeTagLength,
6418
+ plainTextEndIndex: closingTagInfo.plainTextEndIndex,
6419
+ startIndex,
6420
+ oldPlainTextEndIndex,
6421
+ mentionTagLength
6475
6422
  });
6476
- if (event.currentTarget.selectionDirection === null) {
6477
- event.currentTarget.setSelectionRange(selectionRange.start, selectionRange.end);
6478
- }
6479
- else {
6480
- event.currentTarget.setSelectionRange(selectionRange.start, selectionRange.end, event.currentTarget.selectionDirection);
6481
- }
6482
- setSelectionStartValue(selectionRange.start);
6483
- setSelectionEndValue(selectionRange.end);
6423
+ changeNewEndIndex = updateMentionTagResult.plainTextSelectionEndIndex;
6424
+ result += updateMentionTagResult.result;
6425
+ lastProcessedHTMLIndex = updateMentionTagResult.htmlIndex;
6426
+ }
6427
+ else if (tag.subTags !== undefined && tag.subTags.length !== 0 && tag.content !== undefined) {
6428
+ // with subtags
6429
+ const stringBefore = htmlText.substring(lastProcessedHTMLIndex, tag.openTagIndex + tag.openTagBody.length);
6430
+ lastProcessedHTMLIndex = closingTagInfo.closeTagIdx;
6431
+ const updatedContent = updateHTML({
6432
+ htmlText: tag.content,
6433
+ oldPlainText,
6434
+ newPlainText,
6435
+ tags: tag.subTags,
6436
+ startIndex,
6437
+ oldPlainTextEndIndex,
6438
+ change: processedChange,
6439
+ mentionTrigger
6440
+ });
6441
+ processedChange = '';
6442
+ result += stringBefore + updatedContent.updatedHTML;
6484
6443
  }
6485
6444
  else {
6486
- setSelectionStartValue(event.currentTarget.selectionStart);
6487
- setSelectionEndValue(nullToUndefined(event.currentTarget.selectionEnd));
6445
+ // no subtags
6446
+ result +=
6447
+ htmlText.substring(lastProcessedHTMLIndex, tag.openTagIndex + tag.openTagBody.length) + processedChange;
6448
+ processedChange = '';
6449
+ // oldPlainTextEndIndex already includes mentionTag length
6450
+ const endChangeDiff = closingTagInfo.plainTextEndIndex - oldPlainTextEndIndex;
6451
+ // as change may be before the end of the tag, we need to add the rest of the tag
6452
+ lastProcessedHTMLIndex = closingTagInfo.closeTagIdx - endChangeDiff;
6488
6453
  }
6454
+ // the change is handled; exit
6455
+ break;
6489
6456
  }
6457
+ lastProcessedPlainTextTagEndIndex = closingTagInfo.plainTextEndIndex;
6490
6458
  }
6491
- else {
6492
- // selection was changed by keyboard
6493
- updateSelectionIndexesWithMentionIfNeeded({
6494
- event,
6495
- inputTextValue,
6496
- selectionStartValue,
6497
- selectionEndValue,
6498
- tagsValue: tags
6499
- });
6500
- }
6501
- // don't set setShouldHandleOnMouseDownDuringSelect(false) here as setSelectionRange
6502
- // could trigger additional calls of onSelect event and they may not be handled correctly
6503
- // (because of setSelectionRange calls or rerender)
6504
- }, [
6505
- updateSelectionIndexesWithMentionIfNeeded,
6506
- targetSelection,
6507
- setTargetSelection,
6508
- setSelectionStartValue,
6509
- setSelectionEndValue
6510
- ]);
6511
- /* @conditional-compile-remove(mention) */
6512
- const handleOnChange = React.useCallback(({ currentSelectionEnd, currentSelectionStart, currentTriggerStartIndex, event, htmlTextValue, inputTextValue, previousSelectionEnd, previousSelectionStart, tagsValue, updatedValue }) => __awaiter$w(void 0, void 0, void 0, function* () {
6513
- var _b;
6514
- debouncedQueryUpdate.cancel();
6515
- if (event.currentTarget === null) {
6516
- return;
6459
+ if (i === tags.length - 1 && oldPlainTextEndIndex >= closingTagInfo.plainTextEndIndex) {
6460
+ // the last tag should handle the end of the change if needed
6461
+ // oldPlainTextEndIndex already includes mentionTag length
6462
+ const endChangeDiff = oldPlainTextEndIndex - closingTagInfo.plainTextEndIndex;
6463
+ if (startIndex >= closingTagInfo.plainTextEndIndex) {
6464
+ const startChangeDiff = startIndex - closingTagInfo.plainTextEndIndex;
6465
+ result +=
6466
+ htmlText.substring(lastProcessedHTMLIndex, closingTagInfo.closeTagIdx + closingTagInfo.closeTagLength + startChangeDiff) + processedChange;
6467
+ }
6468
+ else {
6469
+ result +=
6470
+ htmlText.substring(lastProcessedHTMLIndex, closingTagInfo.closeTagIdx + closingTagInfo.closeTagLength) +
6471
+ processedChange;
6472
+ }
6473
+ processedChange = '';
6474
+ lastProcessedHTMLIndex = closingTagInfo.closeTagIdx + closingTagInfo.closeTagLength + endChangeDiff;
6475
+ // the change is handled; exit
6476
+ // break is not required here as this is the last element but added for consistency
6477
+ break;
6517
6478
  }
6518
- // handle backspace change
6519
- // onSelect is not called for backspace as selection is not changed and local caret index is outdated
6520
- setCaretIndex(undefined);
6521
- const newValue = updatedValue !== null && updatedValue !== void 0 ? updatedValue : '';
6522
- const triggerText = (_b = mentionLookupOptions === null || mentionLookupOptions === void 0 ? void 0 : mentionLookupOptions.trigger) !== null && _b !== void 0 ? _b : DEFAULT_MENTION_TRIGGER;
6523
- const newTextLength = newValue.length;
6524
- // updating indexes to set between 0 and text length, otherwise selectionRange won't be set correctly
6525
- const currentSelectionEndValue = getValidatedIndexInRange({
6526
- min: 0,
6527
- max: newTextLength,
6528
- currentValue: currentSelectionEnd
6529
- });
6530
- const currentSelectionStartValue = getValidatedIndexInRange({
6531
- min: 0,
6532
- max: newTextLength,
6533
- currentValue: currentSelectionStart
6534
- });
6535
- const previousSelectionStartValue = getValidatedIndexInRange({
6536
- min: 0,
6537
- max: inputTextValue.length,
6538
- currentValue: previousSelectionStart
6539
- });
6540
- const previousSelectionEndValue = getValidatedIndexInRange({
6541
- min: 0,
6542
- max: inputTextValue.length,
6543
- currentValue: previousSelectionEnd
6544
- });
6545
- // If we are enabled for lookups,
6546
- if (mentionLookupOptions !== undefined) {
6547
- // Look at the range of the change for a trigger character
6548
- const triggerPriorIndex = newValue.lastIndexOf(triggerText, currentSelectionEndValue - 1);
6549
- // Update the caret position, if not doing a lookup
6550
- setCaretPosition(textareaCaretTs.Caret.getRelativePosition(event.currentTarget));
6551
- if (triggerPriorIndex !== undefined) {
6552
- // trigger is found
6553
- const isSpaceBeforeTrigger = newValue.substring(triggerPriorIndex - 1, triggerPriorIndex) === ' ';
6554
- const wordAtSelection = newValue.substring(triggerPriorIndex, currentSelectionEndValue);
6555
- let tagIndex = currentTriggerStartIndex;
6556
- if (!isSpaceBeforeTrigger && triggerPriorIndex !== 0) {
6557
- //no space before the trigger <- continuation of the previous word
6558
- tagIndex = -1;
6559
- setCurrentTriggerStartIndex(tagIndex);
6560
- }
6561
- else if (wordAtSelection === triggerText) {
6562
- // start of the mention
6563
- tagIndex = currentSelectionEndValue - triggerText.length;
6564
- if (tagIndex < 0) {
6565
- tagIndex = 0;
6566
- }
6567
- setCurrentTriggerStartIndex(tagIndex);
6568
- }
6569
- if (tagIndex === -1) {
6570
- updateMentionSuggestions([]);
6571
- }
6572
- else {
6573
- // In the middle of a @mention lookup
6574
- if (tagIndex > -1) {
6575
- const query = wordAtSelection.substring(triggerText.length, wordAtSelection.length);
6576
- if (query !== undefined) {
6577
- yield debouncedQueryUpdate(query);
6578
- }
6579
- }
6479
+ }
6480
+ if (lastProcessedHTMLIndex < htmlText.length) {
6481
+ // add the rest of the html string
6482
+ result += htmlText.substring(lastProcessedHTMLIndex);
6483
+ }
6484
+ return { updatedHTML: result, updatedSelectionIndex: changeNewEndIndex };
6485
+ };
6486
+ /**
6487
+ * Given the oldText and newText, find the start index, old end index and new end index for the changes
6488
+ *
6489
+ * @private
6490
+ * @param props - Props for finding stings diff indexes function.
6491
+ * @returns Indexes for change start and ends in new and old texts. The old and new end indexes are exclusive.
6492
+ */
6493
+ const findStringsDiffIndexes = (props) => {
6494
+ const { oldText, newText, previousSelectionStart, previousSelectionEnd, currentSelectionStart, currentSelectionEnd } = props;
6495
+ const newTextLength = newText.length;
6496
+ const oldTextLength = oldText.length;
6497
+ // let changeStart = 0;
6498
+ let newChangeEnd = newTextLength;
6499
+ let oldChangeEnd = oldTextLength;
6500
+ const previousSelectionStartValue = previousSelectionStart > -1 ? previousSelectionStart : oldTextLength;
6501
+ const previousSelectionEndValue = previousSelectionEnd > -1 ? previousSelectionEnd : oldTextLength;
6502
+ const currentSelectionStartValue = currentSelectionStart > -1 ? currentSelectionStart : newTextLength;
6503
+ const currentSelectionEndValue = currentSelectionEnd > -1 ? currentSelectionEnd : newTextLength;
6504
+ const changeStart = Math.min(previousSelectionStartValue, previousSelectionEndValue, currentSelectionStartValue, currentSelectionEndValue, newTextLength, oldTextLength);
6505
+ if (oldTextLength < newTextLength) {
6506
+ //insert or replacement
6507
+ if (oldTextLength === changeStart) {
6508
+ // when change was at the end of string
6509
+ // change is found
6510
+ newChangeEnd = newTextLength;
6511
+ oldChangeEnd = oldTextLength;
6512
+ }
6513
+ else {
6514
+ for (let i = 1; i < newTextLength && oldTextLength - i >= changeStart; i++) {
6515
+ newChangeEnd = newTextLength - i - 1;
6516
+ oldChangeEnd = oldTextLength - i - 1;
6517
+ if (newText[newChangeEnd] !== oldText[oldChangeEnd]) {
6518
+ // change is found
6519
+ break;
6580
6520
  }
6581
6521
  }
6522
+ // make indexes exclusive
6523
+ newChangeEnd += 1;
6524
+ oldChangeEnd += 1;
6582
6525
  }
6583
- let result = '';
6584
- if (tagsValue.length === 0) {
6585
- // no tags in the string and newValue should be used as a result string
6586
- result = newValue;
6526
+ }
6527
+ else if (oldTextLength > newTextLength) {
6528
+ //deletion or replacement
6529
+ if (newTextLength === changeStart) {
6530
+ // when change was at the end of string
6531
+ // change is found
6532
+ newChangeEnd = newTextLength;
6533
+ oldChangeEnd = oldTextLength;
6587
6534
  }
6588
6535
  else {
6589
- // there are tags in the text value and htmlTextValue is html string
6590
- // find diff between old and new text
6591
- const { changeStart, oldChangeEnd, newChangeEnd } = findStringsDiffIndexes({
6592
- oldText: inputTextValue,
6593
- newText: newValue,
6594
- previousSelectionStart: previousSelectionStartValue,
6595
- previousSelectionEnd: previousSelectionEndValue,
6596
- currentSelectionStart: currentSelectionStartValue,
6597
- currentSelectionEnd: currentSelectionEndValue
6598
- });
6599
- const change = newValue.substring(changeStart, newChangeEnd);
6600
- // get updated html string
6601
- const updatedContent = updateHTML({
6602
- htmlText: htmlTextValue,
6603
- oldPlainText: inputTextValue,
6604
- newPlainText: newValue,
6605
- tags: tagsValue,
6606
- startIndex: changeStart,
6607
- oldPlainTextEndIndex: oldChangeEnd,
6608
- change,
6609
- mentionTrigger: triggerText
6610
- });
6611
- result = updatedContent.updatedHTML;
6612
- // update caret index if needed
6613
- if (updatedContent.updatedSelectionIndex !== undefined) {
6614
- setCaretIndex(updatedContent.updatedSelectionIndex);
6615
- setSelectionEndValue(updatedContent.updatedSelectionIndex);
6616
- setSelectionStartValue(updatedContent.updatedSelectionIndex);
6617
- }
6618
- }
6619
- onChange && onChange(event, result);
6620
- }), [onChange, mentionLookupOptions, setCaretIndex, setCaretPosition, updateMentionSuggestions, debouncedQueryUpdate]);
6621
- const getInputFieldTextValue = () => {
6622
- /* @conditional-compile-remove(mention) */
6623
- return inputTextValue;
6624
- };
6625
- /* @conditional-compile-remove(mention) */
6626
- // Adjust the selection range based on a mouse / touch interaction
6627
- const handleOnMove = React.useCallback((event) => {
6628
- var _a;
6629
- let targetStart = event.currentTarget.selectionStart;
6630
- let targetEnd = event.currentTarget.selectionEnd;
6631
- // Should we do anything?
6632
- if (interactionStartPoint !== undefined &&
6633
- // And did selection change?
6634
- targetStart !== null &&
6635
- (targetStart !== (targetSelection === null || targetSelection === void 0 ? void 0 : targetSelection.start) || targetEnd !== (targetSelection === null || targetSelection === void 0 ? void 0 : targetSelection.end))) {
6636
- const direction = event.clientX > interactionStartPoint.x ? 'forward' : 'backward';
6637
- const mentionTag = findMentionTagForSelection(tagsValue, direction === 'backward'
6638
- ? event.currentTarget.selectionStart
6639
- : (_a = event.currentTarget.selectionEnd) !== null && _a !== void 0 ? _a : event.currentTarget.selectionStart);
6640
- let updateCurrentTarget = false;
6641
- if (mentionTag !== undefined && mentionTag.plainTextBeginIndex !== undefined) {
6642
- targetStart = Math.min(mentionTag.plainTextBeginIndex, targetStart);
6643
- if (mentionTag.plainTextEndIndex !== undefined && targetEnd !== null) {
6644
- targetEnd = Math.max(mentionTag.plainTextEndIndex, targetEnd);
6536
+ for (let i = 1; i < oldTextLength && newTextLength - i >= changeStart; i++) {
6537
+ newChangeEnd = newTextLength - i - 1;
6538
+ oldChangeEnd = oldTextLength - i - 1;
6539
+ if (newText[newChangeEnd] !== oldText[oldChangeEnd]) {
6540
+ // change is found
6541
+ break;
6645
6542
  }
6646
- updateCurrentTarget = true;
6647
- setShouldHandleOnMouseDownDuringSelect(false);
6648
6543
  }
6649
- // Update selection range
6650
- setTargetSelection({ start: targetStart, end: targetEnd });
6651
- if (updateCurrentTarget) {
6652
- // Only set the control, if the values are updated
6653
- event.currentTarget.setSelectionRange(targetStart, targetEnd, direction);
6544
+ // make indexes exclusive
6545
+ newChangeEnd += 1;
6546
+ oldChangeEnd += 1;
6547
+ }
6548
+ }
6549
+ else {
6550
+ // replacement
6551
+ for (let i = 1; i < oldTextLength && oldTextLength - i >= changeStart; i++) {
6552
+ newChangeEnd = newTextLength - i - 1;
6553
+ oldChangeEnd = oldTextLength - i - 1;
6554
+ if (newText[newChangeEnd] !== oldText[oldChangeEnd]) {
6555
+ // change is found
6556
+ break;
6654
6557
  }
6655
6558
  }
6656
- }, [setTargetSelection, targetSelection, setShouldHandleOnMouseDownDuringSelect, interactionStartPoint, tagsValue]);
6657
- /* @conditional-compile-remove(mention) */
6658
- const announcerText = React.useMemo(() => {
6659
- if (activeSuggestionIndex === undefined) {
6660
- return undefined;
6559
+ // make indexes exclusive if they aren't equal to the length of the string
6560
+ if (newChangeEnd !== newText.length) {
6561
+ newChangeEnd += 1;
6661
6562
  }
6662
- const currentMention = mentionSuggestions[activeSuggestionIndex !== null && activeSuggestionIndex !== void 0 ? activeSuggestionIndex : 0];
6663
- return (currentMention === null || currentMention === void 0 ? void 0 : currentMention.displayText.length) > 0
6664
- ? currentMention === null || currentMention === void 0 ? void 0 : currentMention.displayText
6665
- : localeStrings.participantItem.displayNamePlaceholder;
6666
- }, [activeSuggestionIndex, mentionSuggestions, localeStrings.participantItem.displayNamePlaceholder]);
6667
- return (React__default['default'].createElement(react.Stack, { className: mergedRootStyle },
6668
- React__default['default'].createElement("div", { className: mergedTextContainerStyle },
6669
- /* @conditional-compile-remove(mention) */ mentionSuggestions.length > 0 && (React__default['default'].createElement(_MentionPopover, { suggestions: mentionSuggestions, activeSuggestionIndex: activeSuggestionIndex, target: inputBoxRef, targetPositionOffset: caretPosition, onRenderSuggestionItem: mentionLookupOptions === null || mentionLookupOptions === void 0 ? void 0 : mentionLookupOptions.onRenderSuggestionItem, onSuggestionSelected: onSuggestionSelected, onDismiss: () => {
6670
- updateMentionSuggestions([]);
6671
- } })),
6672
- /* @conditional-compile-remove(mention) */ announcerText !== undefined && (React__default['default'].createElement(Announcer$1, { announcementString: announcerText, ariaLive: 'polite' })),
6673
- React__default['default'].createElement(react.TextField, { autoFocus: props.autoFocus === 'sendBoxTextField', "data-ui-id": dataUiId, multiline: true, autoAdjustHeight: true, multiple: false, resizable: false, componentRef: textFieldRef, id: id, inputClassName: mergedInputFieldStyle, placeholder: placeholderText, value: getInputFieldTextValue(), onChange: (e, newValue) => {
6674
- // Remove when switching to react 17+, currently needed because of https://legacy.reactjs.org/docs/legacy-event-pooling.html
6675
- /* @conditional-compile-remove(mention) */
6676
- // Prevents React from resetting event's properties
6677
- e.persist();
6678
- /* @conditional-compile-remove(mention) */
6679
- setInputTextValue(newValue !== null && newValue !== void 0 ? newValue : '');
6680
- /* @conditional-compile-remove(mention) */
6681
- handleOnChange({
6682
- event: e,
6683
- tagsValue,
6684
- htmlTextValue: textValue,
6685
- inputTextValue,
6686
- currentTriggerStartIndex,
6687
- previousSelectionStart: nullToUndefined(selectionStartValue),
6688
- previousSelectionEnd: nullToUndefined(selectionEndValue),
6689
- currentSelectionStart: nullToUndefined(e.currentTarget.selectionStart),
6690
- currentSelectionEnd: nullToUndefined(e.currentTarget.selectionEnd),
6691
- updatedValue: newValue
6692
- });
6693
- /* @conditional-compile-remove(mention) */
6694
- return;
6695
- },
6696
- /* @conditional-compile-remove(mention) */
6697
- onSelect: (e) => {
6698
- // update selection if needed
6699
- if (caretIndex !== undefined) {
6700
- setCaretIndex(undefined);
6701
- // sometimes setting selectionRage in effect for updating caretIndex doesn't work as expected and onSelect should handle this case
6702
- if (caretIndex !== e.currentTarget.selectionStart || caretIndex !== e.currentTarget.selectionEnd) {
6703
- e.currentTarget.setSelectionRange(caretIndex, caretIndex);
6704
- }
6705
- return;
6706
- }
6707
- handleOnSelect({
6708
- event: e,
6709
- inputTextValue,
6710
- shouldHandleOnMouseDownDuringSelect,
6711
- selectionEndValue,
6712
- selectionStartValue,
6713
- tags: tagsValue
6714
- });
6715
- },
6716
- /* @conditional-compile-remove(mention) */
6717
- onMouseDown: (e) => {
6718
- setInteractionStartPoint({ x: e.clientX, y: e.clientY });
6719
- // as events order is onMouseDown -> onMouseMove -> onMouseUp -> onSelect -> onClick
6720
- // onClick and onMouseDown can't handle clicking on mention event because
6721
- // onMouseDown doesn't have correct selectionRange yet and
6722
- // onClick already has wrong range as it's called after onSelect that updates the selection range
6723
- // so we need to handle onMouseDown to prevent onSelect default behavior
6724
- setShouldHandleOnMouseDownDuringSelect(true);
6725
- },
6726
- /* @conditional-compile-remove(mention) */
6727
- onMouseMove: handleOnMove,
6728
- /* @conditional-compile-remove(mention) */
6729
- onMouseUp: () => {
6730
- setInteractionStartPoint(undefined);
6731
- },
6732
- /* @conditional-compile-remove(mention) */
6733
- onTouchStart: (e) => {
6734
- setInteractionStartPoint({
6735
- x: e.targetTouches.item(0).clientX,
6736
- y: e.targetTouches.item(0).clientY
6737
- });
6738
- // see onMouseDown for more details
6739
- setShouldHandleOnMouseDownDuringSelect(true);
6740
- },
6741
- /* @conditional-compile-remove(mention) */
6742
- onTouchMove: handleOnMove,
6743
- /* @conditional-compile-remove(mention) */
6744
- onTouchEnd: () => {
6745
- setInteractionStartPoint(undefined);
6746
- },
6747
- /* @conditional-compile-remove(mention) */
6748
- onBlur: () => {
6749
- // setup all flags to default values when text field loses focus
6750
- setShouldHandleOnMouseDownDuringSelect(false);
6751
- setCaretIndex(undefined);
6752
- setSelectionStartValue(undefined);
6753
- setSelectionEndValue(undefined);
6754
- // Dismiss the suggestions on blur, after enough time to select by mouse if needed
6755
- setTimeout(() => {
6756
- setMentionSuggestions([]);
6757
- }, 200);
6758
- }, autoComplete: "off", onKeyDown: onTextFieldKeyDown, styles: mergedTextFieldStyle, disabled: disabled, errorMessage: errorMessage, onRenderSuffix: onRenderChildren, elementRef: inputBoxRef }))));
6563
+ if (oldChangeEnd !== oldText.length) {
6564
+ oldChangeEnd += 1;
6565
+ }
6566
+ }
6567
+ return { changeStart, oldChangeEnd, newChangeEnd };
6759
6568
  };
6760
6569
  /**
6570
+ * Get the html string for the mention suggestion.
6571
+ *
6761
6572
  * @private
6573
+ * @param suggestion - The mention suggestion.
6574
+ * @param localeStrings - The locale strings.
6575
+ * @returns The html string for the mention suggestion.
6762
6576
  */
6763
- const InputBoxButton = (props) => {
6764
- const { onRenderIcon, onClick, ariaLabel, className, id, tooltipContent } = props;
6765
- const [isHover, setIsHover] = React.useState(false);
6766
- const mergedButtonStyle = react.mergeStyles(inputButtonStyle, className);
6767
- const theme = useTheme();
6768
- const calloutStyle = { root: { padding: 0 }, calloutMain: { padding: '0.5rem' } };
6769
- // Place callout with no gap between it and the button.
6770
- const calloutProps = {
6771
- gapSpace: 0,
6772
- styles: calloutStyle,
6773
- backgroundColor: isDarkThemed(theme) ? theme.palette.neutralLighter : ''
6774
- };
6775
- return (React__default['default'].createElement(react.TooltipHost, { hostClassName: inputButtonTooltipStyle, content: tooltipContent, calloutProps: Object.assign({}, calloutProps) },
6776
- React__default['default'].createElement(react.IconButton, { className: mergedButtonStyle, ariaLabel: ariaLabel, onClick: onClick, id: id, onMouseEnter: () => {
6777
- setIsHover(true);
6778
- }, onMouseLeave: () => {
6779
- setIsHover(false);
6780
- },
6781
- // VoiceOver fix: Avoid icon from stealing focus when IconButton is double-tapped to send message by wrapping with Stack with pointerEvents style to none
6782
- onRenderIcon: () => React__default['default'].createElement(react.Stack, { className: iconWrapperStyle$1 }, onRenderIcon(isHover)) })));
6577
+ const htmlStringForMentionSuggestion = (suggestion, localeStrings) => {
6578
+ const idHTML = ' id ="' + suggestion.id + '"';
6579
+ const displayTextHTML = ' displayText ="' + suggestion.displayText + '"';
6580
+ const displayText = getDisplayNameForMentionSuggestion(suggestion, localeStrings);
6581
+ return '<' + MSFT_MENTION_TAG + idHTML + displayTextHTML + '>' + displayText + '</' + MSFT_MENTION_TAG + '>';
6783
6582
  };
6784
- /* @conditional-compile-remove(mention) */
6785
6583
  /**
6786
- * Get validated value for index between min and max values. If currentValue is not defined, -1 will be used instead.
6584
+ * Get display name for the mention suggestion.
6787
6585
  *
6788
6586
  * @private
6789
- * @param props - Props for finding a valid index in range.
6790
- * @returns Valid index in the range.
6587
+ *
6588
+ * @param suggestion - The mention suggestion.
6589
+ * @param localeStrings - The locale strings.
6590
+ * @returns The display name for the mention suggestion or display name placeholder if display name is empty.
6791
6591
  */
6792
- const getValidatedIndexInRange = (props) => {
6793
- const { min, max, currentValue } = props;
6794
- let updatedValue = currentValue !== null && currentValue !== void 0 ? currentValue : -1;
6795
- updatedValue = Math.max(min, updatedValue);
6796
- updatedValue = Math.min(updatedValue, max);
6797
- return updatedValue;
6592
+ const getDisplayNameForMentionSuggestion = (suggestion, localeStrings) => {
6593
+ const displayNamePlaceholder = localeStrings.participantItem.displayNamePlaceholder;
6594
+ return suggestion.displayText !== '' ? suggestion.displayText : displayNamePlaceholder !== null && displayNamePlaceholder !== void 0 ? displayNamePlaceholder : '';
6798
6595
  };
6799
- /* @conditional-compile-remove(mention) */
6800
6596
  /**
6801
- * Find mention tag for selection if exists.
6802
- *
6597
+ * Parse the text and return the tags and the plain text in one go
6803
6598
  * @private
6804
- * @param tags - Existing list of tags.
6805
- * @param selection - Selection index.
6806
- * @returns Mention tag if exists, otherwise undefined.
6599
+ * @param text - The text to parse for HTML tags
6600
+ * @param trigger The trigger to show for the mention tag in plain text
6601
+ *
6602
+ * @returns An array of tags and the plain text representation
6807
6603
  */
6808
- const findMentionTagForSelection = (tags, selection) => {
6809
- let mentionTag = undefined;
6810
- tags.every((tag) => {
6811
- const closingTagInfo = getTagClosingTagInfo(tag);
6812
- if (tag.plainTextBeginIndex !== undefined && tag.plainTextBeginIndex > selection) {
6813
- // no need to check further as the selection is before the tag
6814
- return false;
6604
+ const textToTagParser = (text, trigger) => {
6605
+ var _a, _b;
6606
+ const tags = []; // Tags passed back to the caller
6607
+ const tagParseStack = []; // Local stack to use while parsing
6608
+ let plainTextRepresentation = '';
6609
+ let parseIndex = 0;
6610
+ while (parseIndex < text.length) {
6611
+ const foundHtmlTag = findNextHtmlTag(text, parseIndex);
6612
+ if (!foundHtmlTag) {
6613
+ if (parseIndex !== 0) {
6614
+ // Add the remaining text to the plain text representation
6615
+ plainTextRepresentation += text.substring(parseIndex);
6616
+ }
6617
+ else {
6618
+ plainTextRepresentation = text;
6619
+ }
6620
+ break;
6815
6621
  }
6816
- else if (tag.plainTextBeginIndex !== undefined &&
6817
- tag.plainTextBeginIndex <= selection &&
6818
- selection <= closingTagInfo.plainTextEndIndex) {
6819
- // no need to check if tag doesn't contain selection
6820
- if (tag.subTags !== undefined && tag.subTags.length !== 0) {
6821
- const selectedTag = findMentionTagForSelection(tag.subTags, selection);
6822
- if (selectedTag !== undefined) {
6823
- mentionTag = selectedTag;
6824
- return false;
6622
+ if (foundHtmlTag.type === 'open' || foundHtmlTag.type === 'self-closing') {
6623
+ const nextTag = parseOpenTag(foundHtmlTag.content, foundHtmlTag.startIdx);
6624
+ // Add the plain text between the last tag and this one found
6625
+ plainTextRepresentation += text.substring(parseIndex, foundHtmlTag.startIdx);
6626
+ nextTag.plainTextBeginIndex = plainTextRepresentation.length;
6627
+ if (foundHtmlTag.type === 'open') {
6628
+ tagParseStack.push(nextTag);
6629
+ }
6630
+ else {
6631
+ nextTag.content = '';
6632
+ nextTag.plainTextBeginIndex = plainTextRepresentation.length;
6633
+ nextTag.plainTextEndIndex = plainTextRepresentation.length;
6634
+ addTag(nextTag, tagParseStack, tags);
6635
+ }
6636
+ }
6637
+ if (foundHtmlTag.type === 'close') {
6638
+ const currentOpenTag = tagParseStack.pop();
6639
+ const closeTagType = foundHtmlTag.content.substring(2, foundHtmlTag.content.length - 1).toLowerCase();
6640
+ if (currentOpenTag && currentOpenTag.tagType === closeTagType) {
6641
+ // Tag startIdx is absolute to the text. This is updated later to be relative to the parent tag
6642
+ currentOpenTag.content = text.substring(currentOpenTag.openTagIndex + currentOpenTag.openTagBody.length, foundHtmlTag.startIdx);
6643
+ // Insert the plain text pieces for the sub tags
6644
+ if (currentOpenTag.tagType === MSFT_MENTION_TAG) {
6645
+ plainTextRepresentation =
6646
+ plainTextRepresentation.slice(0, currentOpenTag.plainTextBeginIndex) +
6647
+ trigger +
6648
+ plainTextRepresentation.slice(currentOpenTag.plainTextBeginIndex);
6649
+ }
6650
+ if (!currentOpenTag.subTags) {
6651
+ plainTextRepresentation += currentOpenTag.content;
6825
6652
  }
6653
+ else if (currentOpenTag.subTags.length > 0) {
6654
+ // Add text after the last tag
6655
+ const lastSubTag = currentOpenTag.subTags[currentOpenTag.subTags.length - 1];
6656
+ const startOfRemainingText = ((_a = lastSubTag.closingTagIndex) !== null && _a !== void 0 ? _a : lastSubTag.openTagIndex) + lastSubTag.tagType.length + 3;
6657
+ const trailingText = currentOpenTag.content.substring(startOfRemainingText);
6658
+ plainTextRepresentation += trailingText;
6659
+ }
6660
+ currentOpenTag.plainTextEndIndex = plainTextRepresentation.length;
6661
+ addTag(currentOpenTag, tagParseStack, tags);
6826
6662
  }
6827
- else if (tag.tagType === MSFT_MENTION_TAG) {
6828
- mentionTag = tag;
6829
- return false;
6663
+ else {
6664
+ throw new Error('Unexpected close tag found. Got "' +
6665
+ closeTagType +
6666
+ '" but expected "' +
6667
+ ((_b = tagParseStack[tagParseStack.length - 1]) === null || _b === void 0 ? void 0 : _b.tagType) +
6668
+ '"');
6830
6669
  }
6831
6670
  }
6832
- return true;
6833
- });
6834
- return mentionTag;
6671
+ // Update parsing index; move past the end of the close tag
6672
+ parseIndex = foundHtmlTag.startIdx + foundHtmlTag.content.length;
6673
+ } // While parseIndex < text.length loop
6674
+ return { tags, plainText: plainTextRepresentation };
6835
6675
  };
6836
- /* @conditional-compile-remove(mention) */
6837
- const rangeOfWordInSelection = ({ textInput, selectionStart, selectionEnd, tag }) => {
6838
- var _a;
6839
- if (tag.plainTextBeginIndex === undefined) {
6840
- return { start: selectionStart, end: selectionEnd === undefined ? selectionStart : selectionEnd };
6676
+ const parseOpenTag = (tag, startIdx) => {
6677
+ const tagType = tag
6678
+ .substring(1, tag.length - 1)
6679
+ .split(' ')[0]
6680
+ .toLowerCase()
6681
+ .replace('/', '');
6682
+ return {
6683
+ tagType,
6684
+ openTagIndex: startIdx,
6685
+ openTagBody: tag
6686
+ };
6687
+ };
6688
+ const findNextHtmlTag = (text, startIndex) => {
6689
+ const tagStartIndex = text.indexOf('<', startIndex);
6690
+ if (tagStartIndex === -1) {
6691
+ // No more tags
6692
+ return undefined;
6841
6693
  }
6842
- // Look at start word index and optionally end word index.
6843
- // Select combination of the two and return the range.
6844
- let start = selectionStart;
6845
- let end = selectionEnd === undefined ? selectionStart : selectionEnd;
6846
- const firstWordStartIndex = textInput.lastIndexOf(' ', selectionStart);
6847
- if (firstWordStartIndex === tag.plainTextBeginIndex) {
6848
- start = firstWordStartIndex;
6694
+ const tagEndIndex = text.indexOf('>', tagStartIndex);
6695
+ if (tagEndIndex === -1) {
6696
+ // No close tag
6697
+ return undefined;
6849
6698
  }
6850
- else {
6851
- start = Math.max(firstWordStartIndex + 1, tag.plainTextBeginIndex);
6699
+ const tag = text.substring(tagStartIndex, tagEndIndex + 1);
6700
+ let type = 'open';
6701
+ if (tag[1] === '/') {
6702
+ type = 'close';
6852
6703
  }
6853
- const firstWordEndIndex = textInput.indexOf(' ', selectionStart);
6854
- end = Math.max(firstWordEndIndex + 1, (_a = tag.plainTextEndIndex) !== null && _a !== void 0 ? _a : firstWordEndIndex + 1);
6855
- if (selectionEnd !== undefined && tag.plainTextEndIndex !== undefined) {
6856
- const lastWordEndIndex = textInput.indexOf(' ', selectionEnd);
6857
- end = Math.max(lastWordEndIndex > -1 ? lastWordEndIndex : tag.plainTextEndIndex, selectionEnd);
6704
+ else if (tag[tag.length - 2] === '/') {
6705
+ type = 'self-closing';
6858
6706
  }
6859
- return { start, end };
6707
+ return {
6708
+ content: tag,
6709
+ startIdx: tagStartIndex,
6710
+ type
6711
+ };
6860
6712
  };
6861
- /* @conditional-compile-remove(mention) */
6862
- /**
6863
- * Find a new the selection index.
6864
- *
6865
- * @private
6866
- * @param props - Props for finding new selection index for mention.
6867
- * @returns New selection index if it is inside of a mention tag, otherwise the current selection.
6868
- */
6869
- const findNewSelectionIndexForMention = (props) => {
6713
+ const addTag = (tag, parseStack, tags) => {
6870
6714
  var _a;
6871
- const { tag, textValue, currentSelectionIndex, previousSelectionIndex } = props;
6872
- // check if this is a mention tag and selection should be updated
6873
- if (tag.tagType !== MSFT_MENTION_TAG ||
6874
- tag.plainTextBeginIndex === undefined ||
6875
- currentSelectionIndex === previousSelectionIndex ||
6876
- tag.plainTextEndIndex === undefined) {
6877
- return currentSelectionIndex;
6715
+ // Add as sub-tag to the parent stack tag, if there is one
6716
+ const parentTag = parseStack[parseStack.length - 1];
6717
+ if (parentTag) {
6718
+ // Adjust the open tag index to be relative to the parent tag
6719
+ const parentContentStartIdx = parentTag.openTagIndex + parentTag.openTagBody.length;
6720
+ const relativeIdx = tag.openTagIndex - parentContentStartIdx;
6721
+ tag.openTagIndex = relativeIdx;
6878
6722
  }
6879
- let spaceIndex = 0;
6880
- if (currentSelectionIndex <= previousSelectionIndex) {
6881
- // the cursor is moved to the left, find the last index before the cursor
6882
- spaceIndex = textValue.lastIndexOf(' ', currentSelectionIndex !== null && currentSelectionIndex !== void 0 ? currentSelectionIndex : 0);
6883
- if (spaceIndex === -1) {
6884
- // no space before the selection, use the beginning of the tag
6885
- spaceIndex = tag.plainTextBeginIndex;
6723
+ if (!tag.closingTagIndex) {
6724
+ // If the tag is self-closing, the close tag is the same as the open tag
6725
+ if (tag.openTagBody[tag.openTagBody.length - 2] === '/') {
6726
+ tag.closingTagIndex = tag.openTagIndex;
6886
6727
  }
6728
+ else {
6729
+ // Otherwise, the close tag index is the open tag index + the open tag body + the content length
6730
+ tag.closingTagIndex = tag.openTagIndex + tag.openTagBody.length + ((_a = tag.content) !== null && _a !== void 0 ? _a : []).length;
6731
+ }
6732
+ }
6733
+ // Put the tag where it belongs
6734
+ if (!parentTag) {
6735
+ tags.push(tag);
6887
6736
  }
6888
6737
  else {
6889
- // the cursor is moved to the right, find the fist index after the cursor
6890
- spaceIndex = textValue.indexOf(' ', currentSelectionIndex !== null && currentSelectionIndex !== void 0 ? currentSelectionIndex : 0);
6891
- if (spaceIndex === -1) {
6892
- // no space after the selection, use the end of the tag
6893
- spaceIndex = (_a = tag.plainTextEndIndex) !== null && _a !== void 0 ? _a : tag.plainTextBeginIndex;
6738
+ if (!parentTag.subTags) {
6739
+ parentTag.subTags = [tag];
6740
+ }
6741
+ else {
6742
+ parentTag.subTags.push(tag);
6894
6743
  }
6895
6744
  }
6896
- spaceIndex = Math.max(tag.plainTextBeginIndex, spaceIndex);
6897
- spaceIndex = Math.min(tag.plainTextEndIndex, spaceIndex);
6898
- return spaceIndex;
6899
6745
  };
6900
- /* @conditional-compile-remove(mention) */
6746
+
6747
+ // Copyright (c) Microsoft Corporation.
6901
6748
  /**
6902
- * Handle mention tag edit and by word deleting
6903
- *
6904
6749
  * @private
6905
- * @param props - Props for mention update HTML function.
6906
- * @returns Updated texts and indexes.
6750
+ * z-index to ensure that chat container has lower z-index than mention popover
6907
6751
  */
6908
- const handleMentionTagUpdate = (props) => {
6909
- const { htmlText, oldPlainText, change, tag, closeTagIdx, closeTagLength, plainTextEndIndex, startIndex, oldPlainTextEndIndex, mentionTagLength } = props;
6910
- let processedChange = props.processedChange;
6911
- let lastProcessedHTMLIndex = props.lastProcessedHTMLIndex;
6912
- if (tag.tagType !== MSFT_MENTION_TAG || tag.plainTextBeginIndex === undefined) {
6913
- // not a mention tag
6914
- return {
6915
- result: '',
6916
- updatedChange: processedChange,
6917
- htmlIndex: lastProcessedHTMLIndex,
6918
- plainTextSelectionEndIndex: undefined
6919
- };
6920
- }
6921
- let result = '';
6922
- let plainTextSelectionEndIndex;
6923
- let rangeStart;
6924
- let rangeEnd;
6925
- // check if space symbol is handled in case if string looks like '<1 2 3>'
6926
- let isSpaceLengthHandled = false;
6927
- rangeStart = oldPlainText.lastIndexOf(' ', startIndex);
6928
- if (rangeStart !== -1 && rangeStart !== undefined && rangeStart > tag.plainTextBeginIndex) {
6929
- isSpaceLengthHandled = true;
6930
- }
6931
- rangeEnd = oldPlainText.indexOf(' ', oldPlainTextEndIndex);
6932
- if (rangeEnd === -1 || rangeEnd === undefined) {
6933
- // check if space symbol is not found
6934
- rangeEnd = plainTextEndIndex;
6935
- }
6936
- else if (!isSpaceLengthHandled) {
6937
- // +1 to include the space symbol
6938
- rangeEnd += 1;
6939
- }
6940
- isSpaceLengthHandled = true;
6941
- if (rangeStart === -1 || rangeStart === undefined || rangeStart < tag.plainTextBeginIndex) {
6942
- // rangeStart should be at least equal to tag.plainTextBeginIndex
6943
- rangeStart = tag.plainTextBeginIndex;
6944
- }
6945
- if (rangeEnd > plainTextEndIndex) {
6946
- // rangeEnd should be at most equal to plainTextEndIndex
6947
- rangeEnd = plainTextEndIndex;
6948
- }
6949
- if (rangeStart === tag.plainTextBeginIndex && rangeEnd === plainTextEndIndex) {
6950
- // the whole tag should be removed
6951
- result += htmlText.substring(lastProcessedHTMLIndex, tag.openTagIndex) + processedChange;
6952
- plainTextSelectionEndIndex = tag.plainTextBeginIndex + processedChange.length;
6953
- processedChange = '';
6954
- lastProcessedHTMLIndex = closeTagIdx + closeTagLength;
6955
- }
6956
- else {
6957
- // only part of the tag should be removed
6958
- let startChangeDiff = 0;
6959
- let endChangeDiff = 0;
6960
- // need to check only rangeStart > tag.plainTextBeginIndex as when rangeStart === tag.plainTextBeginIndex startChangeDiff = 0 and mentionTagLength shouldn't be subtracted
6961
- if (rangeStart > tag.plainTextBeginIndex) {
6962
- startChangeDiff = rangeStart - tag.plainTextBeginIndex - mentionTagLength;
6963
- }
6964
- endChangeDiff = rangeEnd - tag.plainTextBeginIndex - mentionTagLength;
6965
- result += htmlText.substring(lastProcessedHTMLIndex, tag.openTagIndex + tag.openTagBody.length + startChangeDiff);
6966
- if (startIndex < tag.plainTextBeginIndex) {
6967
- // if the change is before the tag, the selection should start from startIndex (rangeStart will be equal to tag.plainTextBeginIndex)
6968
- plainTextSelectionEndIndex = startIndex + change.length;
6752
+ const CHAT_CONTAINER_ZINDEX$1 = 1;
6753
+ /**
6754
+ * @private
6755
+ */
6756
+ const mentionPopoverContainerStyle = (theme) => react.mergeStyles({
6757
+ boxShadow: theme.effects.elevation16,
6758
+ background: theme.semanticColors.bodyBackground,
6759
+ overflow: 'visible',
6760
+ // zIndex to set the mentionPopover above the chat container
6761
+ zIndex: CHAT_CONTAINER_ZINDEX$1 + 1
6762
+ });
6763
+ /**
6764
+ * @private
6765
+ */
6766
+ const headerStyleThemed = (theme) => {
6767
+ return {
6768
+ root: {
6769
+ color: theme.palette.neutralSecondary,
6770
+ margin: '0.5rem 1rem 0.25rem',
6771
+ fontSize: theme.fonts.smallPlus.fontSize
6969
6772
  }
6970
- else {
6971
- // if the change is inside the tag, the selection should start with rangeStart
6972
- plainTextSelectionEndIndex = rangeStart + processedChange.length;
6773
+ };
6774
+ };
6775
+ /**
6776
+ * @private
6777
+ */
6778
+ react.mergeStyles({
6779
+ height: '100%',
6780
+ overflowY: 'visible',
6781
+ overflowX: 'hidden'
6782
+ });
6783
+ /**
6784
+ * @private
6785
+ */
6786
+ const suggestionListStyle = react.mergeStyles({
6787
+ padding: '0.25rem 0rem 0',
6788
+ overflow: 'visible'
6789
+ });
6790
+ /**
6791
+ * @private
6792
+ */
6793
+ const suggestionItemWrapperStyle = (theme) => {
6794
+ return react.mergeStyles({
6795
+ margin: '0.05rem 0.1rem',
6796
+ '&:focus-visible': {
6797
+ outline: `${theme.palette.black} solid 0.1rem`
6973
6798
  }
6974
- lastProcessedHTMLIndex = tag.openTagIndex + tag.openTagBody.length + endChangeDiff;
6975
- // processed change should not be changed as it should be added after the tag
6976
- }
6977
- return { result, updatedChange: processedChange, htmlIndex: lastProcessedHTMLIndex, plainTextSelectionEndIndex };
6799
+ });
6978
6800
  };
6979
- /* @conditional-compile-remove(mention) */
6980
6801
  /**
6981
- * Get closing tag information if exists otherwise return information as for self closing tag
6982
- *
6983
6802
  * @private
6984
- * @param tag - Tag data.
6985
- * @returns Closing tag information for the provided tag.
6986
6803
  */
6987
- const getTagClosingTagInfo = (tag) => {
6988
- let plainTextEndIndex = 0;
6989
- let closeTagIdx = 0;
6990
- let closeTagLength = 0;
6991
- if (tag.plainTextEndIndex !== undefined && tag.closingTagIndex !== undefined) {
6992
- // close tag exists
6993
- plainTextEndIndex = tag.plainTextEndIndex;
6994
- closeTagIdx = tag.closingTagIndex;
6995
- // tag.tagType.length + </>
6996
- closeTagLength = tag.tagType.length + 3;
6997
- }
6998
- else if (tag.plainTextBeginIndex !== undefined) {
6999
- // no close tag
7000
- plainTextEndIndex = tag.plainTextBeginIndex;
7001
- closeTagIdx = tag.openTagIndex + tag.openTagBody.length;
7002
- closeTagLength = 0;
7003
- }
7004
- return { plainTextEndIndex, closeTagIdx, closeTagLength };
6804
+ const suggestionItemStackStyle = (theme, isSuggestionHovered, activeBorder) => {
6805
+ return react.mergeStyles({
6806
+ width: '10rem',
6807
+ alignItems: 'center',
6808
+ height: '36px',
6809
+ padding: '0 0.75rem',
6810
+ background: isSuggestionHovered ? theme.palette.neutralLight : theme.palette.white,
6811
+ border: activeBorder ? `0.0625rem solid ${theme.palette.neutralSecondary}` : 'none'
6812
+ });
7005
6813
  };
7006
- /* @conditional-compile-remove(mention) */
6814
+
6815
+ // Copyright (c) Microsoft Corporation.
7007
6816
  /**
7008
- * Go through the text and update it with the changed text
6817
+ * Component to render a pop-up of mention suggestions.
7009
6818
  *
7010
- * @private
7011
- * @param props - Props for update HTML function.
7012
- * @returns Updated HTML and selection index if the selection index should be set.
6819
+ * @internal
7013
6820
  */
7014
- const updateHTML = (props) => {
7015
- const { htmlText, oldPlainText, newPlainText, tags, startIndex, oldPlainTextEndIndex, change, mentionTrigger } = props;
7016
- if (tags.length === 0 || (startIndex === 0 && oldPlainTextEndIndex === oldPlainText.length - 1)) {
7017
- // no tags added yet or the whole text is changed
7018
- return { updatedHTML: newPlainText, updatedSelectionIndex: undefined };
7019
- }
7020
- let result = '';
7021
- let lastProcessedHTMLIndex = 0;
7022
- // the value can be updated with empty string when the change covers more than 1 place (tag + before or after the tag)
7023
- // in this case change won't be added as part of the tag
7024
- // e.g.: change is before and partially in tag => change will be added before the tag and outdated text in the tag will be removed
7025
- // e.g.: change is after and partially in tag => change will be added after the tag and outdated text in the tag will be removed
7026
- // e.g.: change is on the beginning of the tag => change will be added before the tag
7027
- // e.g.: change is on the end of the tag => change will be added to the tag if it's not mention and after the tag if it's mention
7028
- let processedChange = change;
7029
- // end tag plain text index of the last processed tag
7030
- let lastProcessedPlainTextTagEndIndex = 0;
7031
- // as some tags/text can be removed fully, selection should be updated correctly
7032
- let changeNewEndIndex;
7033
- for (let i = 0; i < tags.length; i++) {
7034
- const tag = tags[i];
7035
- if (tag.plainTextBeginIndex === undefined) {
7036
- continue;
6821
+ const _MentionPopover = (props) => {
6822
+ const { suggestions, activeSuggestionIndex, title, target, targetPositionOffset, onRenderSuggestionItem, onSuggestionSelected, onDismiss, location } = props;
6823
+ const theme = react.useTheme();
6824
+ /* @conditional-compile-remove(mention) */
6825
+ const ids = useIdentifiers();
6826
+ const localeStrings = useLocale$1().strings;
6827
+ const popoverRef = React.useRef();
6828
+ const [position, setPosition] = React.useState({ left: 0 });
6829
+ const [hoveredSuggestion, setHoveredSuggestion] = React.useState(undefined);
6830
+ const [changedSelection, setChangedSelection] = React.useState(undefined); // Selection UI as per teams
6831
+ const dismissPopoverWhenClickingOutside = React.useCallback((e) => {
6832
+ const target = e.target;
6833
+ if (popoverRef.current && !popoverRef.current.contains(target)) {
6834
+ onDismiss && onDismiss();
7037
6835
  }
7038
- // all plain text indexes includes trigger length for the mention that shouldn't be included in
7039
- // htmlText.substring because html strings don't include the trigger
7040
- // mentionTagLength will be set only for mention tag, otherwise should be 0
7041
- let mentionTagLength = 0;
7042
- let isMentionTag = false;
7043
- if (tag.tagType === MSFT_MENTION_TAG) {
7044
- mentionTagLength = mentionTrigger.length;
7045
- isMentionTag = true;
6836
+ }, [onDismiss]);
6837
+ React.useEffect(() => {
6838
+ if (changedSelection === undefined) {
6839
+ setChangedSelection(false);
7046
6840
  }
7047
- if (startIndex <= tag.plainTextBeginIndex) {
7048
- // change start is before the open tag
7049
- // Math.max(lastProcessedPlainTextTagEndIndex, startIndex) is used as startIndex may not be in [[previous tag].plainTextEndIndex - tag.plainTextBeginIndex] range
7050
- const startChangeDiff = tag.plainTextBeginIndex - Math.max(lastProcessedPlainTextTagEndIndex, startIndex);
7051
- result += htmlText.substring(lastProcessedHTMLIndex, tag.openTagIndex - startChangeDiff) + processedChange;
7052
- processedChange = '';
7053
- if (oldPlainTextEndIndex <= tag.plainTextBeginIndex) {
7054
- // the whole change is before tag start
7055
- // mentionTag length can be ignored here as the change is before the tag
7056
- const endChangeDiff = tag.plainTextBeginIndex - oldPlainTextEndIndex;
7057
- lastProcessedHTMLIndex = tag.openTagIndex - endChangeDiff;
7058
- // the change is handled; exit
7059
- break;
7060
- }
7061
- else {
7062
- // change continues in the tag
7063
- lastProcessedHTMLIndex = tag.openTagIndex;
7064
- // proceed to the next check
7065
- }
6841
+ else if (changedSelection === false) {
6842
+ setChangedSelection(true);
7066
6843
  }
7067
- const closingTagInfo = getTagClosingTagInfo(tag);
7068
- if (startIndex <= closingTagInfo.plainTextEndIndex) {
7069
- // change started before the end tag
7070
- if (startIndex <= tag.plainTextBeginIndex && oldPlainTextEndIndex === closingTagInfo.plainTextEndIndex) {
7071
- // the change is a tag or starts before the tag
7072
- // tag should be removed, no matter if there are sub-tags
7073
- result += htmlText.substring(lastProcessedHTMLIndex, tag.openTagIndex) + processedChange;
7074
- processedChange = '';
7075
- lastProcessedHTMLIndex = closingTagInfo.closeTagIdx + closingTagInfo.closeTagLength;
7076
- // the change is handled; exit
7077
- break;
7078
- }
7079
- else if (startIndex >= tag.plainTextBeginIndex && oldPlainTextEndIndex <= closingTagInfo.plainTextEndIndex) {
7080
- // the change is between the tag
7081
- if (isMentionTag) {
7082
- if (change !== '') {
7083
- if (startIndex !== tag.plainTextBeginIndex && startIndex !== closingTagInfo.plainTextEndIndex) {
7084
- // mention tag should be deleted when user tries to edit it in the middle
7085
- result += htmlText.substring(lastProcessedHTMLIndex, tag.openTagIndex) + processedChange;
7086
- changeNewEndIndex = tag.plainTextBeginIndex + processedChange.length;
7087
- lastProcessedHTMLIndex = closingTagInfo.closeTagIdx + closingTagInfo.closeTagLength;
7088
- }
7089
- else if (startIndex === tag.plainTextBeginIndex) {
7090
- // non empty change at the beginning of the mention tag to be added before the mention tag
7091
- result += htmlText.substring(lastProcessedHTMLIndex, tag.openTagIndex) + processedChange;
7092
- changeNewEndIndex = tag.plainTextBeginIndex + processedChange.length;
7093
- lastProcessedHTMLIndex = tag.openTagIndex;
7094
- }
7095
- else if (startIndex === closingTagInfo.plainTextEndIndex) {
7096
- // non empty change at the end of the mention tag to be added after the mention tag
7097
- result +=
7098
- htmlText.substring(lastProcessedHTMLIndex, closingTagInfo.closeTagIdx + closingTagInfo.closeTagLength) +
7099
- processedChange;
7100
- changeNewEndIndex = closingTagInfo.plainTextEndIndex + processedChange.length;
7101
- lastProcessedHTMLIndex = closingTagInfo.closeTagIdx + closingTagInfo.closeTagLength;
7102
- }
7103
- processedChange = '';
7104
- }
7105
- else {
7106
- const updateMentionTagResult = handleMentionTagUpdate({
7107
- htmlText,
7108
- oldPlainText,
7109
- lastProcessedHTMLIndex,
7110
- processedChange,
7111
- change,
7112
- tag,
7113
- closeTagIdx: closingTagInfo.closeTagIdx,
7114
- closeTagLength: closingTagInfo.closeTagLength,
7115
- plainTextEndIndex: closingTagInfo.plainTextEndIndex,
7116
- startIndex,
7117
- oldPlainTextEndIndex,
7118
- mentionTagLength
7119
- });
7120
- result += updateMentionTagResult.result;
7121
- changeNewEndIndex = updateMentionTagResult.plainTextSelectionEndIndex;
7122
- processedChange = updateMentionTagResult.updatedChange;
7123
- lastProcessedHTMLIndex = updateMentionTagResult.htmlIndex;
7124
- }
7125
- }
7126
- else if (tag.subTags !== undefined && tag.subTags.length !== 0 && tag.content !== undefined) {
7127
- // with subtags
7128
- const stringBefore = htmlText.substring(lastProcessedHTMLIndex, tag.openTagIndex + tag.openTagBody.length);
7129
- lastProcessedHTMLIndex = closingTagInfo.closeTagIdx;
7130
- const updatedContent = updateHTML({
7131
- htmlText: tag.content,
7132
- oldPlainText,
7133
- newPlainText,
7134
- tags: tag.subTags,
7135
- startIndex,
7136
- oldPlainTextEndIndex,
7137
- change: processedChange,
7138
- mentionTrigger
7139
- });
7140
- result += stringBefore + updatedContent.updatedHTML;
7141
- changeNewEndIndex = updatedContent.updatedSelectionIndex;
7142
- }
7143
- else {
7144
- // no subtags
7145
- const startChangeDiff = startIndex - tag.plainTextBeginIndex;
7146
- result +=
7147
- htmlText.substring(lastProcessedHTMLIndex, tag.openTagIndex + tag.openTagBody.length + startChangeDiff) +
7148
- processedChange;
7149
- processedChange = '';
7150
- if (oldPlainTextEndIndex < closingTagInfo.plainTextEndIndex) {
7151
- const endChangeDiff = oldPlainTextEndIndex - tag.plainTextBeginIndex;
7152
- lastProcessedHTMLIndex = tag.openTagIndex + tag.openTagBody.length + endChangeDiff;
7153
- }
7154
- else if (oldPlainTextEndIndex === closingTagInfo.plainTextEndIndex) {
7155
- lastProcessedHTMLIndex = closingTagInfo.closeTagIdx;
7156
- }
7157
- }
7158
- // the change is handled; exit
6844
+ }, [activeSuggestionIndex, changedSelection]);
6845
+ React.useEffect(() => {
6846
+ window && window.addEventListener('click', dismissPopoverWhenClickingOutside);
6847
+ return () => {
6848
+ window && window.removeEventListener('click', dismissPopoverWhenClickingOutside);
6849
+ };
6850
+ }, [dismissPopoverWhenClickingOutside]);
6851
+ // Determine popover position
6852
+ React.useEffect(() => {
6853
+ var _a, _b, _c, _d, _e, _f, _g, _h;
6854
+ const rect = (_a = target === null || target === void 0 ? void 0 : target.current) === null || _a === void 0 ? void 0 : _a.getBoundingClientRect();
6855
+ const maxWidth = 200;
6856
+ const finalPosition = { maxWidth };
6857
+ // Figure out whether it will fit horizontally
6858
+ const leftOffset = (_b = targetPositionOffset === null || targetPositionOffset === void 0 ? void 0 : targetPositionOffset.left) !== null && _b !== void 0 ? _b : 0;
6859
+ if (leftOffset + maxWidth > ((_c = rect === null || rect === void 0 ? void 0 : rect.width) !== null && _c !== void 0 ? _c : 0)) {
6860
+ finalPosition.right = ((_d = rect === null || rect === void 0 ? void 0 : rect.width) !== null && _d !== void 0 ? _d : 0) - leftOffset;
6861
+ }
6862
+ else {
6863
+ finalPosition.left = leftOffset;
6864
+ }
6865
+ if (location === 'below') {
6866
+ finalPosition.top = ((_e = rect === null || rect === void 0 ? void 0 : rect.height) !== null && _e !== void 0 ? _e : 0) + ((_f = targetPositionOffset === null || targetPositionOffset === void 0 ? void 0 : targetPositionOffset.top) !== null && _f !== void 0 ? _f : 0);
6867
+ }
6868
+ else {
6869
+ // (location === 'above')
6870
+ finalPosition.bottom = ((_g = rect === null || rect === void 0 ? void 0 : rect.height) !== null && _g !== void 0 ? _g : 0) - ((_h = targetPositionOffset === null || targetPositionOffset === void 0 ? void 0 : targetPositionOffset.top) !== null && _h !== void 0 ? _h : 0);
6871
+ }
6872
+ setPosition(finalPosition);
6873
+ }, [location, target, targetPositionOffset]);
6874
+ const handleOnKeyDown = React.useCallback((e) => {
6875
+ switch (e.key) {
6876
+ case 'Escape':
6877
+ onDismiss && onDismiss();
7159
6878
  break;
6879
+ }
6880
+ }, [onDismiss]);
6881
+ const personaRenderer = React.useCallback((displayName) => {
6882
+ const displayNamePlaceholder = localeStrings.participantItem.displayNamePlaceholder;
6883
+ const avatarOptions = {
6884
+ text: (displayName === null || displayName === void 0 ? void 0 : displayName.trim()) || displayNamePlaceholder,
6885
+ size: react.PersonaSize.size24,
6886
+ initialsColor: theme.palette.neutralLight,
6887
+ initialsTextColor: theme.palette.neutralSecondary,
6888
+ showOverflowTooltip: false,
6889
+ showUnknownPersonaCoin: !(displayName === null || displayName === void 0 ? void 0 : displayName.trim()) || displayName === displayNamePlaceholder
6890
+ };
6891
+ return React__default['default'].createElement(react.Persona, Object.assign({}, avatarOptions));
6892
+ }, [localeStrings, theme]);
6893
+ const defaultOnRenderSuggestionItem = React.useCallback((suggestion, onSuggestionSelected, active) => {
6894
+ return (React__default['default'].createElement("div", { "data-is-focusable": true, "data-ui-id": ids.mentionSuggestionItem, key: suggestion.id, onClick: () => onSuggestionSelected(suggestion), onMouseEnter: () => setHoveredSuggestion(suggestion), onMouseLeave: () => setHoveredSuggestion(undefined), onKeyDown: (e) => {
6895
+ handleOnKeyDown(e);
6896
+ }, className: suggestionItemWrapperStyle(theme) },
6897
+ React__default['default'].createElement(react.Stack, { horizontal: true, className: suggestionItemStackStyle(theme, (hoveredSuggestion === null || hoveredSuggestion === void 0 ? void 0 : hoveredSuggestion.id) === suggestion.id, (changedSelection !== null && changedSelection !== void 0 ? changedSelection : false) && active) }, personaRenderer(suggestion.displayText))));
6898
+ }, [
6899
+ handleOnKeyDown,
6900
+ theme,
6901
+ /* @conditional-compile-remove(mention) */
6902
+ ids,
6903
+ hoveredSuggestion,
6904
+ changedSelection,
6905
+ personaRenderer
6906
+ ]);
6907
+ const getHeaderTitle = React.useCallback(() => {
6908
+ if (title) {
6909
+ return title;
6910
+ }
6911
+ /* @conditional-compile-remove(mention) */
6912
+ return localeStrings.mentionPopover.mentionPopoverHeader;
6913
+ }, [localeStrings, title]);
6914
+ return (React__default['default'].createElement("div", { ref: popoverRef },
6915
+ React__default['default'].createElement(react.Stack, { className: react.mergeStyles({
6916
+ maxHeight: 212,
6917
+ maxWidth: position.maxWidth
6918
+ }, mentionPopoverContainerStyle(theme), Object.assign(Object.assign({}, position), { position: 'absolute' })) },
6919
+ React__default['default'].createElement(react.Stack.Item, { styles: headerStyleThemed(theme), "aria-label": title }, getHeaderTitle()),
6920
+ React__default['default'].createElement(react.Stack
6921
+ /* @conditional-compile-remove(mention) */
6922
+ , { "data-ui-id": ids.mentionSuggestionList, className: suggestionListStyle }, suggestions.map((suggestion, index) => {
6923
+ const active = index === activeSuggestionIndex;
6924
+ return onRenderSuggestionItem
6925
+ ? onRenderSuggestionItem(suggestion, onSuggestionSelected, active)
6926
+ : defaultOnRenderSuggestionItem(suggestion, onSuggestionSelected, active);
6927
+ })))));
6928
+ };
6929
+
6930
+ // Copyright (c) Microsoft Corporation.
6931
+ // Licensed under the MIT license.
6932
+ var __awaiter$w = (window && window.__awaiter) || function (thisArg, _arguments, P, generator) {
6933
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
6934
+ return new (P || (P = Promise))(function (resolve, reject) {
6935
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6936
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
6937
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
6938
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
6939
+ });
6940
+ };
6941
+ const DEFAULT_MENTION_TRIGGER = '@';
6942
+ /**
6943
+ * @private
6944
+ */
6945
+ const TextFieldWithMention = (props) => {
6946
+ const { textFieldProps, dataUiId, textValue, onChange, textFieldRef, onKeyDown, onEnterKeyDown, supportNewline, mentionLookupOptions } = props;
6947
+ const inputBoxRef = React.useRef(null);
6948
+ // Current suggestion list, provided by the callback
6949
+ const [mentionSuggestions, setMentionSuggestions] = React.useState([]);
6950
+ // Current suggestion list, provided by the callback
6951
+ const [activeSuggestionIndex, setActiveSuggestionIndex] = React.useState(undefined);
6952
+ // Index of the current trigger character in the text field
6953
+ const [currentTriggerStartIndex, setCurrentTriggerStartIndex] = React.useState(-1);
6954
+ const [inputTextValue, setInputTextValue] = React.useState('');
6955
+ const [tagsValue, setTagsValue] = React.useState([]);
6956
+ // Index of the previous selection start in the text field
6957
+ const [selectionStartValue, setSelectionStartValue] = React.useState();
6958
+ // Index of the previous selection end in the text field
6959
+ const [selectionEndValue, setSelectionEndValue] = React.useState();
6960
+ // Boolean value to check if onMouseDown event should be handled during select as selection range
6961
+ // for onMouseDown event is not updated yet and the selection range for mouse click/taps will be
6962
+ // updated in onSelect event if needed.
6963
+ const [shouldHandleOnMouseDownDuringSelect, setShouldHandleOnMouseDownDuringSelect] = React.useState(true);
6964
+ // Point of start of touch/mouse selection
6965
+ const [interactionStartPoint, setInteractionStartPoint] = React.useState();
6966
+ // Target selection from mouse movement
6967
+ const [targetSelection, setTargetSelection] = React.useState();
6968
+ // Caret position in the text field
6969
+ const [caretPosition, setCaretPosition] = React.useState(undefined);
6970
+ // Index of where the caret is in the text field
6971
+ const [caretIndex, setCaretIndex] = React.useState(undefined);
6972
+ const localeStrings = useLocale$1().strings;
6973
+ // Set mention suggestions
6974
+ const updateMentionSuggestions = React.useCallback((suggestions) => {
6975
+ setMentionSuggestions(suggestions);
6976
+ }, [setMentionSuggestions]);
6977
+ // Parse the text and get the plain text version to display in the input box
6978
+ React.useEffect(() => {
6979
+ const trigger = (mentionLookupOptions === null || mentionLookupOptions === void 0 ? void 0 : mentionLookupOptions.trigger) || DEFAULT_MENTION_TRIGGER;
6980
+ const parsedHTMLData = textToTagParser(textValue, trigger);
6981
+ setInputTextValue(parsedHTMLData.plainText);
6982
+ setTagsValue(parsedHTMLData.tags);
6983
+ updateMentionSuggestions([]);
6984
+ }, [textValue, mentionLookupOptions === null || mentionLookupOptions === void 0 ? void 0 : mentionLookupOptions.trigger, updateMentionSuggestions]);
6985
+ React.useEffect(() => {
6986
+ var _a;
6987
+ // effect for caret index update
6988
+ if (caretIndex === undefined || textFieldRef === undefined || (textFieldRef === null || textFieldRef === void 0 ? void 0 : textFieldRef.current) === undefined) {
6989
+ return;
6990
+ }
6991
+ // get validated caret index between 0 and inputTextValue.length otherwise caret will be set to incorrect index
6992
+ const updatedCaretIndex = getValidatedIndexInRange({
6993
+ min: 0,
6994
+ max: inputTextValue.length,
6995
+ currentValue: caretIndex
6996
+ });
6997
+ (_a = textFieldRef === null || textFieldRef === void 0 ? void 0 : textFieldRef.current) === null || _a === void 0 ? void 0 : _a.setSelectionRange(updatedCaretIndex, updatedCaretIndex);
6998
+ setSelectionStartValue(updatedCaretIndex);
6999
+ setSelectionEndValue(updatedCaretIndex);
7000
+ }, [caretIndex, inputTextValue.length, textFieldRef, setSelectionStartValue, setSelectionEndValue]);
7001
+ const onSuggestionSelected = React.useCallback((suggestion) => {
7002
+ var _a, _b, _c;
7003
+ let selectionEnd = ((_a = textFieldRef === null || textFieldRef === void 0 ? void 0 : textFieldRef.current) === null || _a === void 0 ? void 0 : _a.selectionEnd) || -1;
7004
+ if (selectionEnd < 0) {
7005
+ selectionEnd = 0;
7006
+ }
7007
+ else if (selectionEnd > inputTextValue.length) {
7008
+ selectionEnd = inputTextValue.length;
7009
+ }
7010
+ const oldPlainText = inputTextValue;
7011
+ const mention = htmlStringForMentionSuggestion(suggestion, localeStrings);
7012
+ // update plain text with the mention html text
7013
+ const newPlainText = inputTextValue.substring(0, currentTriggerStartIndex) + mention + inputTextValue.substring(selectionEnd);
7014
+ const triggerText = (_b = mentionLookupOptions === null || mentionLookupOptions === void 0 ? void 0 : mentionLookupOptions.trigger) !== null && _b !== void 0 ? _b : DEFAULT_MENTION_TRIGGER;
7015
+ // update html text with updated plain text
7016
+ const updatedContent = updateHTML({
7017
+ htmlText: textValue,
7018
+ oldPlainText,
7019
+ newPlainText,
7020
+ tags: tagsValue,
7021
+ startIndex: currentTriggerStartIndex,
7022
+ oldPlainTextEndIndex: selectionEnd,
7023
+ change: mention,
7024
+ mentionTrigger: triggerText
7025
+ });
7026
+ const displayName = getDisplayNameForMentionSuggestion(suggestion, localeStrings);
7027
+ const newCaretIndex = currentTriggerStartIndex + displayName.length + triggerText.length;
7028
+ // move the caret in the text field to the end of the mention plain text
7029
+ setCaretIndex(newCaretIndex);
7030
+ setSelectionEndValue(newCaretIndex);
7031
+ setSelectionStartValue(newCaretIndex);
7032
+ setCurrentTriggerStartIndex(-1);
7033
+ updateMentionSuggestions([]);
7034
+ // set focus back to text field
7035
+ (_c = textFieldRef === null || textFieldRef === void 0 ? void 0 : textFieldRef.current) === null || _c === void 0 ? void 0 : _c.focus();
7036
+ setActiveSuggestionIndex(undefined);
7037
+ onChange && onChange(undefined, updatedContent.updatedHTML);
7038
+ }, [
7039
+ textFieldRef,
7040
+ inputTextValue,
7041
+ currentTriggerStartIndex,
7042
+ mentionLookupOptions === null || mentionLookupOptions === void 0 ? void 0 : mentionLookupOptions.trigger,
7043
+ onChange,
7044
+ textValue,
7045
+ tagsValue,
7046
+ updateMentionSuggestions,
7047
+ localeStrings
7048
+ ]);
7049
+ const onTextFieldKeyDown = React.useCallback((ev) => {
7050
+ // caretIndex should be set to undefined when the user is typing
7051
+ setCaretIndex(undefined);
7052
+ // shouldHandleOnMouseDownDuringSelect should be set to false after the last mouse down event.
7053
+ // it shouldn't be updated in onMouseUp
7054
+ // as onMouseUp can be triggered before or after onSelect event
7055
+ // because its order depends on mouse events not selection.
7056
+ setShouldHandleOnMouseDownDuringSelect(false);
7057
+ if (isEnterKeyEventFromCompositionSession(ev)) {
7058
+ return;
7059
+ }
7060
+ if (mentionSuggestions.length > 0) {
7061
+ if (ev.key === 'ArrowUp') {
7062
+ ev.preventDefault();
7063
+ const newActiveIndex = activeSuggestionIndex === undefined
7064
+ ? mentionSuggestions.length - 1
7065
+ : Math.max(activeSuggestionIndex - 1, 0);
7066
+ setActiveSuggestionIndex(newActiveIndex);
7160
7067
  }
7161
- else if (startIndex > tag.plainTextBeginIndex && oldPlainTextEndIndex > closingTagInfo.plainTextEndIndex) {
7162
- // the change started in the tag but finishes somewhere further
7163
- const startChangeDiff = startIndex - tag.plainTextBeginIndex - mentionTagLength;
7164
- if (isMentionTag) {
7165
- const updateMentionTagResult = handleMentionTagUpdate({
7166
- htmlText,
7167
- oldPlainText,
7168
- lastProcessedHTMLIndex,
7169
- processedChange: '',
7170
- change,
7171
- tag,
7172
- closeTagIdx: closingTagInfo.closeTagIdx,
7173
- closeTagLength: closingTagInfo.closeTagLength,
7174
- plainTextEndIndex: closingTagInfo.plainTextEndIndex,
7175
- startIndex,
7176
- oldPlainTextEndIndex,
7177
- mentionTagLength
7178
- });
7179
- result += updateMentionTagResult.result;
7180
- lastProcessedHTMLIndex = updateMentionTagResult.htmlIndex;
7181
- // no need to handle plainTextSelectionEndIndex as the change will be added later
7182
- }
7183
- else if (tag.subTags !== undefined && tag.subTags.length !== 0 && tag.content !== undefined) {
7184
- // with subtags
7185
- const stringBefore = htmlText.substring(lastProcessedHTMLIndex, tag.openTagIndex + tag.openTagBody.length);
7186
- lastProcessedHTMLIndex = closingTagInfo.closeTagIdx;
7187
- const updatedContent = updateHTML({
7188
- htmlText: tag.content,
7189
- oldPlainText,
7190
- newPlainText,
7191
- tags: tag.subTags,
7192
- startIndex,
7193
- oldPlainTextEndIndex,
7194
- change: '',
7195
- mentionTrigger
7196
- });
7197
- result += stringBefore + updatedContent.updatedHTML;
7198
- }
7199
- else {
7200
- // no subtags
7201
- result += htmlText.substring(lastProcessedHTMLIndex, tag.openTagIndex + tag.openTagBody.length + startChangeDiff);
7202
- lastProcessedHTMLIndex = closingTagInfo.closeTagIdx;
7203
- }
7204
- // proceed with the next calculations
7205
- }
7206
- else if (startIndex < tag.plainTextBeginIndex && oldPlainTextEndIndex > closingTagInfo.plainTextEndIndex) {
7207
- // the change starts before the tag and finishes after it
7208
- // tag should be removed, no matter if there are subtags
7209
- // no need to save anything between lastProcessedHTMLIndex and closeTagIdx + closeTagLength
7210
- lastProcessedHTMLIndex = closingTagInfo.closeTagIdx + closingTagInfo.closeTagLength;
7211
- // proceed with the next calculations
7068
+ else if (ev.key === 'ArrowDown') {
7069
+ ev.preventDefault();
7070
+ const newActiveIndex = activeSuggestionIndex === undefined
7071
+ ? 0
7072
+ : Math.min(activeSuggestionIndex + 1, mentionSuggestions.length - 1);
7073
+ setActiveSuggestionIndex(newActiveIndex);
7212
7074
  }
7213
- else if (startIndex === tag.plainTextBeginIndex && oldPlainTextEndIndex > closingTagInfo.plainTextEndIndex) {
7214
- // the change starts in the tag and finishes after it
7215
- // tag should be removed, no matter if there are subtags
7216
- result += htmlText.substring(lastProcessedHTMLIndex, tag.openTagIndex);
7217
- // processedChange shouldn't be updated as it will be added after the tag
7218
- lastProcessedHTMLIndex = closingTagInfo.closeTagIdx + closingTagInfo.closeTagLength;
7219
- // proceed with the next calculations
7075
+ else if (ev.key === 'Escape') {
7076
+ updateMentionSuggestions([]);
7220
7077
  }
7221
- else if (startIndex < tag.plainTextBeginIndex && oldPlainTextEndIndex < closingTagInfo.plainTextEndIndex) {
7222
- // the change starts before the tag and ends in a tag
7223
- if (isMentionTag) {
7224
- // mention tag
7225
- const updateMentionTagResult = handleMentionTagUpdate({
7226
- htmlText,
7227
- oldPlainText,
7228
- lastProcessedHTMLIndex,
7229
- processedChange: '',
7230
- change,
7231
- tag,
7232
- closeTagIdx: closingTagInfo.closeTagIdx,
7233
- closeTagLength: closingTagInfo.closeTagLength,
7234
- plainTextEndIndex: closingTagInfo.plainTextEndIndex,
7235
- startIndex,
7236
- oldPlainTextEndIndex,
7237
- mentionTagLength
7238
- });
7239
- changeNewEndIndex = updateMentionTagResult.plainTextSelectionEndIndex;
7240
- result += updateMentionTagResult.result;
7241
- lastProcessedHTMLIndex = updateMentionTagResult.htmlIndex;
7242
- }
7243
- else if (tag.subTags !== undefined && tag.subTags.length !== 0 && tag.content !== undefined) {
7244
- // with subtags
7245
- const stringBefore = htmlText.substring(lastProcessedHTMLIndex, tag.openTagIndex + tag.openTagBody.length);
7246
- lastProcessedHTMLIndex = closingTagInfo.closeTagIdx;
7247
- const updatedContent = updateHTML({
7248
- htmlText: tag.content,
7249
- oldPlainText,
7250
- newPlainText,
7251
- tags: tag.subTags,
7252
- startIndex,
7253
- oldPlainTextEndIndex,
7254
- change: processedChange,
7255
- mentionTrigger
7256
- });
7257
- processedChange = '';
7258
- result += stringBefore + updatedContent.updatedHTML;
7259
- }
7260
- else {
7261
- // no subtags
7262
- result +=
7263
- htmlText.substring(lastProcessedHTMLIndex, tag.openTagIndex + tag.openTagBody.length) + processedChange;
7264
- processedChange = '';
7265
- // oldPlainTextEndIndex already includes mentionTag length
7266
- const endChangeDiff = closingTagInfo.plainTextEndIndex - oldPlainTextEndIndex;
7267
- // as change may be before the end of the tag, we need to add the rest of the tag
7268
- lastProcessedHTMLIndex = closingTagInfo.closeTagIdx - endChangeDiff;
7078
+ }
7079
+ if (ev.key === 'Enter' && (ev.shiftKey === false || !supportNewline)) {
7080
+ ev.preventDefault();
7081
+ // If we are looking up a mention, select the focused suggestion
7082
+ if (mentionSuggestions.length > 0 && activeSuggestionIndex !== undefined) {
7083
+ const selectedMention = mentionSuggestions[activeSuggestionIndex];
7084
+ if (selectedMention) {
7085
+ onSuggestionSelected(selectedMention);
7086
+ return;
7269
7087
  }
7270
- // the change is handled; exit
7271
- break;
7272
7088
  }
7273
- lastProcessedPlainTextTagEndIndex = closingTagInfo.plainTextEndIndex;
7089
+ onEnterKeyDown && onEnterKeyDown();
7274
7090
  }
7275
- if (i === tags.length - 1 && oldPlainTextEndIndex >= closingTagInfo.plainTextEndIndex) {
7276
- // the last tag should handle the end of the change if needed
7277
- // oldPlainTextEndIndex already includes mentionTag length
7278
- const endChangeDiff = oldPlainTextEndIndex - closingTagInfo.plainTextEndIndex;
7279
- if (startIndex >= closingTagInfo.plainTextEndIndex) {
7280
- const startChangeDiff = startIndex - closingTagInfo.plainTextEndIndex;
7281
- result +=
7282
- htmlText.substring(lastProcessedHTMLIndex, closingTagInfo.closeTagIdx + closingTagInfo.closeTagLength + startChangeDiff) + processedChange;
7283
- }
7284
- else {
7285
- result +=
7286
- htmlText.substring(lastProcessedHTMLIndex, closingTagInfo.closeTagIdx + closingTagInfo.closeTagLength) +
7287
- processedChange;
7288
- }
7289
- processedChange = '';
7290
- lastProcessedHTMLIndex = closingTagInfo.closeTagIdx + closingTagInfo.closeTagLength + endChangeDiff;
7291
- // the change is handled; exit
7292
- // break is not required here as this is the last element but added for consistency
7293
- break;
7091
+ onKeyDown && onKeyDown(ev);
7092
+ }, [
7093
+ onEnterKeyDown,
7094
+ onKeyDown,
7095
+ supportNewline,
7096
+ mentionSuggestions,
7097
+ activeSuggestionIndex,
7098
+ onSuggestionSelected,
7099
+ updateMentionSuggestions
7100
+ ]);
7101
+ const debouncedQueryUpdate = useDebounce.useDebouncedCallback((query) => __awaiter$w(void 0, void 0, void 0, function* () {
7102
+ var _a;
7103
+ const suggestions = (_a = (yield (mentionLookupOptions === null || mentionLookupOptions === void 0 ? void 0 : mentionLookupOptions.onQueryUpdated(query)))) !== null && _a !== void 0 ? _a : [];
7104
+ if (suggestions.length === 0) {
7105
+ setActiveSuggestionIndex(undefined);
7294
7106
  }
7295
- }
7296
- if (lastProcessedHTMLIndex < htmlText.length) {
7297
- // add the rest of the html string
7298
- result += htmlText.substring(lastProcessedHTMLIndex);
7299
- }
7300
- return { updatedHTML: result, updatedSelectionIndex: changeNewEndIndex };
7301
- };
7302
- /* @conditional-compile-remove(mention) */
7303
- /**
7304
- * Given the oldText and newText, find the start index, old end index and new end index for the changes
7305
- *
7306
- * @private
7307
- * @param props - Props for finding stings diff indexes function.
7308
- * @returns Indexes for change start and ends in new and old texts. The old and new end indexes are exclusive.
7309
- */
7310
- const findStringsDiffIndexes = (props) => {
7311
- const { oldText, newText, previousSelectionStart, previousSelectionEnd, currentSelectionStart, currentSelectionEnd } = props;
7312
- const newTextLength = newText.length;
7313
- const oldTextLength = oldText.length;
7314
- // let changeStart = 0;
7315
- let newChangeEnd = newTextLength;
7316
- let oldChangeEnd = oldTextLength;
7317
- const previousSelectionStartValue = previousSelectionStart > -1 ? previousSelectionStart : oldTextLength;
7318
- const previousSelectionEndValue = previousSelectionEnd > -1 ? previousSelectionEnd : oldTextLength;
7319
- const currentSelectionStartValue = currentSelectionStart > -1 ? currentSelectionStart : newTextLength;
7320
- const currentSelectionEndValue = currentSelectionEnd > -1 ? currentSelectionEnd : newTextLength;
7321
- const changeStart = Math.min(previousSelectionStartValue, previousSelectionEndValue, currentSelectionStartValue, currentSelectionEndValue, newTextLength, oldTextLength);
7322
- if (oldTextLength < newTextLength) {
7323
- //insert or replacement
7324
- if (oldTextLength === changeStart) {
7325
- // when change was at the end of string
7326
- // change is found
7327
- newChangeEnd = newTextLength;
7328
- oldChangeEnd = oldTextLength;
7107
+ else if (activeSuggestionIndex === undefined) {
7108
+ // Set the active to the first, if it's not already set
7109
+ setActiveSuggestionIndex(0);
7110
+ }
7111
+ updateMentionSuggestions(suggestions);
7112
+ }), 500);
7113
+ // Update selections index in mention to navigate by words
7114
+ const updateSelectionIndexesWithMentionIfNeeded = React.useCallback(({ event, inputTextValue, selectionEndValue, selectionStartValue, tagsValue }) => {
7115
+ var _a, _b, _c;
7116
+ let updatedStartIndex = event.currentTarget.selectionStart;
7117
+ let updatedEndIndex = event.currentTarget.selectionEnd;
7118
+ if (event.currentTarget.selectionStart === event.currentTarget.selectionEnd &&
7119
+ event.currentTarget.selectionStart !== null &&
7120
+ event.currentTarget.selectionStart !== -1) {
7121
+ // just a caret movement/usual typing or deleting
7122
+ const mentionTag = findMentionTagForSelection(tagsValue, event.currentTarget.selectionStart);
7123
+ // don't include boundary cases to show correct selection, otherwise it will show selection at mention boundaries
7124
+ if (mentionTag !== undefined &&
7125
+ mentionTag.plainTextBeginIndex !== undefined &&
7126
+ event.currentTarget.selectionStart > mentionTag.plainTextBeginIndex &&
7127
+ event.currentTarget.selectionStart < ((_a = mentionTag.plainTextEndIndex) !== null && _a !== void 0 ? _a : mentionTag.plainTextBeginIndex)) {
7128
+ // get updated selection index
7129
+ const newSelectionIndex = findNewSelectionIndexForMention({
7130
+ tag: mentionTag,
7131
+ textValue: inputTextValue,
7132
+ currentSelectionIndex: event.currentTarget.selectionStart,
7133
+ previousSelectionIndex: selectionStartValue !== null && selectionStartValue !== void 0 ? selectionStartValue : inputTextValue.length
7134
+ });
7135
+ updatedStartIndex = newSelectionIndex;
7136
+ updatedEndIndex = newSelectionIndex;
7137
+ }
7329
7138
  }
7330
- else {
7331
- for (let i = 1; i < newTextLength && oldTextLength - i >= changeStart; i++) {
7332
- newChangeEnd = newTextLength - i - 1;
7333
- oldChangeEnd = oldTextLength - i - 1;
7334
- if (newText[newChangeEnd] !== oldText[oldChangeEnd]) {
7335
- // change is found
7336
- break;
7139
+ else if (event.currentTarget.selectionStart !== event.currentTarget.selectionEnd) {
7140
+ // Both e.currentTarget.selectionStart !== selectionStartValue and e.currentTarget.selectionEnd !== selectionEndValue can be true when a user selects a text by double click
7141
+ if (event.currentTarget.selectionStart !== null && event.currentTarget.selectionStart !== selectionStartValue) {
7142
+ // the selection start is changed
7143
+ const mentionTag = findMentionTagForSelection(tagsValue, event.currentTarget.selectionStart);
7144
+ // don't include boundary cases to show correct selection, otherwise it will show selection at mention boundaries
7145
+ if (mentionTag !== undefined &&
7146
+ mentionTag.plainTextBeginIndex !== undefined &&
7147
+ event.currentTarget.selectionStart > mentionTag.plainTextBeginIndex &&
7148
+ event.currentTarget.selectionStart < ((_b = mentionTag.plainTextEndIndex) !== null && _b !== void 0 ? _b : mentionTag.plainTextBeginIndex)) {
7149
+ updatedStartIndex = findNewSelectionIndexForMention({
7150
+ tag: mentionTag,
7151
+ textValue: inputTextValue,
7152
+ currentSelectionIndex: event.currentTarget.selectionStart,
7153
+ previousSelectionIndex: selectionStartValue !== null && selectionStartValue !== void 0 ? selectionStartValue : inputTextValue.length
7154
+ });
7155
+ }
7156
+ }
7157
+ if (event.currentTarget.selectionEnd !== null && event.currentTarget.selectionEnd !== selectionEndValue) {
7158
+ // the selection end is changed
7159
+ const mentionTag = findMentionTagForSelection(tagsValue, event.currentTarget.selectionEnd);
7160
+ // don't include boundary cases to show correct selection, otherwise it will show selection at mention boundaries
7161
+ if (mentionTag !== undefined &&
7162
+ mentionTag.plainTextBeginIndex !== undefined &&
7163
+ event.currentTarget.selectionEnd > mentionTag.plainTextBeginIndex &&
7164
+ event.currentTarget.selectionEnd < ((_c = mentionTag.plainTextEndIndex) !== null && _c !== void 0 ? _c : mentionTag.plainTextBeginIndex)) {
7165
+ updatedEndIndex = findNewSelectionIndexForMention({
7166
+ tag: mentionTag,
7167
+ textValue: inputTextValue,
7168
+ currentSelectionIndex: event.currentTarget.selectionEnd,
7169
+ previousSelectionIndex: selectionEndValue !== null && selectionEndValue !== void 0 ? selectionEndValue : inputTextValue.length
7170
+ });
7337
7171
  }
7338
7172
  }
7339
- // make indexes exclusive
7340
- newChangeEnd += 1;
7341
- oldChangeEnd += 1;
7342
7173
  }
7343
- }
7344
- else if (oldTextLength > newTextLength) {
7345
- //deletion or replacement
7346
- if (newTextLength === changeStart) {
7347
- // when change was at the end of string
7348
- // change is found
7349
- newChangeEnd = newTextLength;
7350
- oldChangeEnd = oldTextLength;
7174
+ // e.currentTarget.selectionDirection should be set to handle shift + arrow keys
7175
+ if (event.currentTarget.selectionDirection === null) {
7176
+ event.currentTarget.setSelectionRange(updatedStartIndex, updatedEndIndex);
7351
7177
  }
7352
7178
  else {
7353
- for (let i = 1; i < oldTextLength && newTextLength - i >= changeStart; i++) {
7354
- newChangeEnd = newTextLength - i - 1;
7355
- oldChangeEnd = oldTextLength - i - 1;
7356
- if (newText[newChangeEnd] !== oldText[oldChangeEnd]) {
7357
- // change is found
7358
- break;
7359
- }
7360
- }
7361
- // make indexes exclusive
7362
- newChangeEnd += 1;
7363
- oldChangeEnd += 1;
7179
+ event.currentTarget.setSelectionRange(updatedStartIndex, updatedEndIndex, event.currentTarget.selectionDirection);
7364
7180
  }
7365
- }
7366
- else {
7367
- // replacement
7368
- for (let i = 1; i < oldTextLength && oldTextLength - i >= changeStart; i++) {
7369
- newChangeEnd = newTextLength - i - 1;
7370
- oldChangeEnd = oldTextLength - i - 1;
7371
- if (newText[newChangeEnd] !== oldText[oldChangeEnd]) {
7372
- // change is found
7373
- break;
7181
+ setSelectionStartValue(nullToUndefined(updatedStartIndex));
7182
+ setSelectionEndValue(nullToUndefined(updatedEndIndex));
7183
+ }, [setSelectionStartValue, setSelectionEndValue]);
7184
+ const handleOnSelect = React.useCallback(({ event, inputTextValue, tags, shouldHandleOnMouseDownDuringSelect, selectionStartValue, selectionEndValue }) => {
7185
+ if (shouldHandleOnMouseDownDuringSelect) {
7186
+ if (targetSelection !== undefined) {
7187
+ setSelectionStartValue(targetSelection.start);
7188
+ setSelectionEndValue(targetSelection.end);
7189
+ event.currentTarget.setSelectionRange(targetSelection.start, undefinedToNull(targetSelection.end));
7190
+ setTargetSelection(undefined);
7191
+ }
7192
+ else if (event.currentTarget.selectionStart !== null) {
7193
+ // on select was triggered by mouse down/up with no movement
7194
+ const mentionTag = findMentionTagForSelection(tags, event.currentTarget.selectionStart);
7195
+ if (mentionTag !== undefined && mentionTag.plainTextBeginIndex !== undefined) {
7196
+ // handle mention click
7197
+ // Get range of word that was clicked on
7198
+ const selectionRange = rangeOfWordInSelection({
7199
+ textInput: inputTextValue,
7200
+ selectionStart: event.currentTarget.selectionStart,
7201
+ selectionEnd: nullToUndefined(event.currentTarget.selectionEnd),
7202
+ tag: mentionTag
7203
+ });
7204
+ if (event.currentTarget.selectionDirection === null) {
7205
+ event.currentTarget.setSelectionRange(selectionRange.start, selectionRange.end);
7206
+ }
7207
+ else {
7208
+ event.currentTarget.setSelectionRange(selectionRange.start, selectionRange.end, event.currentTarget.selectionDirection);
7209
+ }
7210
+ setSelectionStartValue(selectionRange.start);
7211
+ setSelectionEndValue(selectionRange.end);
7212
+ }
7213
+ else {
7214
+ setSelectionStartValue(event.currentTarget.selectionStart);
7215
+ setSelectionEndValue(nullToUndefined(event.currentTarget.selectionEnd));
7216
+ }
7374
7217
  }
7375
7218
  }
7376
- // make indexes exclusive if they aren't equal to the length of the string
7377
- if (newChangeEnd !== newText.length) {
7378
- newChangeEnd += 1;
7219
+ else {
7220
+ // selection was changed by keyboard
7221
+ updateSelectionIndexesWithMentionIfNeeded({
7222
+ event,
7223
+ inputTextValue,
7224
+ selectionStartValue,
7225
+ selectionEndValue,
7226
+ tagsValue: tags
7227
+ });
7379
7228
  }
7380
- if (oldChangeEnd !== oldText.length) {
7381
- oldChangeEnd += 1;
7229
+ // don't set setShouldHandleOnMouseDownDuringSelect(false) here as setSelectionRange
7230
+ // could trigger additional calls of onSelect event and they may not be handled correctly
7231
+ // (because of setSelectionRange calls or rerender)
7232
+ }, [
7233
+ updateSelectionIndexesWithMentionIfNeeded,
7234
+ targetSelection,
7235
+ setTargetSelection,
7236
+ setSelectionStartValue,
7237
+ setSelectionEndValue
7238
+ ]);
7239
+ const handleOnChange = React.useCallback(({ currentSelectionEnd, currentSelectionStart, currentTriggerStartIndex, event, htmlTextValue, inputTextValue, previousSelectionEnd, previousSelectionStart, tagsValue, updatedValue }) => __awaiter$w(void 0, void 0, void 0, function* () {
7240
+ var _b;
7241
+ debouncedQueryUpdate.cancel();
7242
+ if (event.currentTarget === null) {
7243
+ return;
7382
7244
  }
7383
- }
7384
- return { changeStart, oldChangeEnd, newChangeEnd };
7385
- };
7386
- /* @conditional-compile-remove(mention) */
7387
- /**
7388
- * Get the html string for the mention suggestion.
7389
- *
7390
- * @private
7391
- * @param suggestion - The mention suggestion.
7392
- * @param localeStrings - The locale strings.
7393
- * @returns The html string for the mention suggestion.
7394
- */
7395
- const htmlStringForMentionSuggestion = (suggestion, localeStrings) => {
7396
- const idHTML = ' id ="' + suggestion.id + '"';
7397
- const displayTextHTML = ' displayText ="' + suggestion.displayText + '"';
7398
- const displayText = getDisplayNameForMentionSuggestion(suggestion, localeStrings);
7399
- return '<' + MSFT_MENTION_TAG + idHTML + displayTextHTML + '>' + displayText + '</' + MSFT_MENTION_TAG + '>';
7400
- };
7401
- /* @conditional-compile-remove(mention) */
7402
- /**
7403
- * Get display name for the mention suggestion.
7404
- *
7405
- * @private
7406
- *
7407
- * @param suggestion - The mention suggestion.
7408
- * @param localeStrings - The locale strings.
7409
- * @returns The display name for the mention suggestion or display name placeholder if display name is empty.
7410
- */
7411
- const getDisplayNameForMentionSuggestion = (suggestion, localeStrings) => {
7412
- const displayNamePlaceholder = localeStrings.participantItem.displayNamePlaceholder;
7413
- return suggestion.displayText !== '' ? suggestion.displayText : displayNamePlaceholder !== null && displayNamePlaceholder !== void 0 ? displayNamePlaceholder : '';
7414
- };
7415
- /* @conditional-compile-remove(mention) */
7416
- /**
7417
- * Parse the text and return the tags and the plain text in one go
7418
- * @private
7419
- * @param text - The text to parse for HTML tags
7420
- * @param trigger The trigger to show for the mention tag in plain text
7421
- *
7422
- * @returns An array of tags and the plain text representation
7423
- */
7424
- const textToTagParser = (text, trigger) => {
7425
- var _a, _b;
7426
- const tags = []; // Tags passed back to the caller
7427
- const tagParseStack = []; // Local stack to use while parsing
7428
- let plainTextRepresentation = '';
7429
- let parseIndex = 0;
7430
- while (parseIndex < text.length) {
7431
- const foundHtmlTag = findNextHtmlTag(text, parseIndex);
7432
- if (!foundHtmlTag) {
7433
- if (parseIndex !== 0) {
7434
- // Add the remaining text to the plain text representation
7435
- plainTextRepresentation += text.substring(parseIndex);
7436
- }
7437
- else {
7438
- plainTextRepresentation = text;
7245
+ // handle backspace change
7246
+ // onSelect is not called for backspace as selection is not changed and local caret index is outdated
7247
+ setCaretIndex(undefined);
7248
+ const newValue = updatedValue !== null && updatedValue !== void 0 ? updatedValue : '';
7249
+ const triggerText = (_b = mentionLookupOptions === null || mentionLookupOptions === void 0 ? void 0 : mentionLookupOptions.trigger) !== null && _b !== void 0 ? _b : DEFAULT_MENTION_TRIGGER;
7250
+ const newTextLength = newValue.length;
7251
+ // updating indexes to set between 0 and text length, otherwise selectionRange won't be set correctly
7252
+ const currentSelectionEndValue = getValidatedIndexInRange({
7253
+ min: 0,
7254
+ max: newTextLength,
7255
+ currentValue: currentSelectionEnd
7256
+ });
7257
+ const currentSelectionStartValue = getValidatedIndexInRange({
7258
+ min: 0,
7259
+ max: newTextLength,
7260
+ currentValue: currentSelectionStart
7261
+ });
7262
+ const previousSelectionStartValue = getValidatedIndexInRange({
7263
+ min: 0,
7264
+ max: inputTextValue.length,
7265
+ currentValue: previousSelectionStart
7266
+ });
7267
+ const previousSelectionEndValue = getValidatedIndexInRange({
7268
+ min: 0,
7269
+ max: inputTextValue.length,
7270
+ currentValue: previousSelectionEnd
7271
+ });
7272
+ // If we are enabled for lookups,
7273
+ if (mentionLookupOptions !== undefined) {
7274
+ // Look at the range of the change for a trigger character
7275
+ const triggerPriorIndex = newValue.lastIndexOf(triggerText, currentSelectionEndValue - 1);
7276
+ // Update the caret position, if not doing a lookup
7277
+ setCaretPosition(textareaCaretTs.Caret.getRelativePosition(event.currentTarget));
7278
+ if (triggerPriorIndex !== undefined) {
7279
+ // trigger is found
7280
+ const isSpaceBeforeTrigger = newValue.substring(triggerPriorIndex - 1, triggerPriorIndex) === ' ';
7281
+ const wordAtSelection = newValue.substring(triggerPriorIndex, currentSelectionEndValue);
7282
+ let tagIndex = currentTriggerStartIndex;
7283
+ if (!isSpaceBeforeTrigger && triggerPriorIndex !== 0) {
7284
+ //no space before the trigger <- continuation of the previous word
7285
+ tagIndex = -1;
7286
+ setCurrentTriggerStartIndex(tagIndex);
7287
+ }
7288
+ else if (wordAtSelection === triggerText) {
7289
+ // start of the mention
7290
+ tagIndex = currentSelectionEndValue - triggerText.length;
7291
+ if (tagIndex < 0) {
7292
+ tagIndex = 0;
7293
+ }
7294
+ setCurrentTriggerStartIndex(tagIndex);
7295
+ }
7296
+ if (tagIndex === -1) {
7297
+ updateMentionSuggestions([]);
7298
+ }
7299
+ else {
7300
+ // In the middle of a @mention lookup
7301
+ if (tagIndex > -1) {
7302
+ const query = wordAtSelection.substring(triggerText.length, wordAtSelection.length);
7303
+ if (query !== undefined) {
7304
+ yield debouncedQueryUpdate(query);
7305
+ }
7306
+ }
7307
+ }
7439
7308
  }
7440
- break;
7441
7309
  }
7442
- if (foundHtmlTag.type === 'open' || foundHtmlTag.type === 'self-closing') {
7443
- const nextTag = parseOpenTag(foundHtmlTag.content, foundHtmlTag.startIdx);
7444
- // Add the plain text between the last tag and this one found
7445
- plainTextRepresentation += text.substring(parseIndex, foundHtmlTag.startIdx);
7446
- nextTag.plainTextBeginIndex = plainTextRepresentation.length;
7447
- if (foundHtmlTag.type === 'open') {
7448
- tagParseStack.push(nextTag);
7449
- }
7450
- else {
7451
- nextTag.content = '';
7452
- nextTag.plainTextBeginIndex = plainTextRepresentation.length;
7453
- nextTag.plainTextEndIndex = plainTextRepresentation.length;
7454
- addTag(nextTag, tagParseStack, tags);
7310
+ let result = '';
7311
+ if (tagsValue.length === 0) {
7312
+ // no tags in the string and newValue should be used as a result string
7313
+ result = newValue;
7314
+ }
7315
+ else {
7316
+ // there are tags in the text value and htmlTextValue is html string
7317
+ // find diff between old and new text
7318
+ const { changeStart, oldChangeEnd, newChangeEnd } = findStringsDiffIndexes({
7319
+ oldText: inputTextValue,
7320
+ newText: newValue,
7321
+ previousSelectionStart: previousSelectionStartValue,
7322
+ previousSelectionEnd: previousSelectionEndValue,
7323
+ currentSelectionStart: currentSelectionStartValue,
7324
+ currentSelectionEnd: currentSelectionEndValue
7325
+ });
7326
+ const change = newValue.substring(changeStart, newChangeEnd);
7327
+ // get updated html string
7328
+ const updatedContent = updateHTML({
7329
+ htmlText: htmlTextValue,
7330
+ oldPlainText: inputTextValue,
7331
+ newPlainText: newValue,
7332
+ tags: tagsValue,
7333
+ startIndex: changeStart,
7334
+ oldPlainTextEndIndex: oldChangeEnd,
7335
+ change,
7336
+ mentionTrigger: triggerText
7337
+ });
7338
+ result = updatedContent.updatedHTML;
7339
+ // update caret index if needed
7340
+ if (updatedContent.updatedSelectionIndex !== undefined) {
7341
+ setCaretIndex(updatedContent.updatedSelectionIndex);
7342
+ setSelectionEndValue(updatedContent.updatedSelectionIndex);
7343
+ setSelectionStartValue(updatedContent.updatedSelectionIndex);
7455
7344
  }
7456
7345
  }
7457
- if (foundHtmlTag.type === 'close') {
7458
- const currentOpenTag = tagParseStack.pop();
7459
- const closeTagType = foundHtmlTag.content.substring(2, foundHtmlTag.content.length - 1).toLowerCase();
7460
- if (currentOpenTag && currentOpenTag.tagType === closeTagType) {
7461
- // Tag startIdx is absolute to the text. This is updated later to be relative to the parent tag
7462
- currentOpenTag.content = text.substring(currentOpenTag.openTagIndex + currentOpenTag.openTagBody.length, foundHtmlTag.startIdx);
7463
- // Insert the plain text pieces for the sub tags
7464
- if (currentOpenTag.tagType === MSFT_MENTION_TAG) {
7465
- plainTextRepresentation =
7466
- plainTextRepresentation.slice(0, currentOpenTag.plainTextBeginIndex) +
7467
- trigger +
7468
- plainTextRepresentation.slice(currentOpenTag.plainTextBeginIndex);
7469
- }
7470
- if (!currentOpenTag.subTags) {
7471
- plainTextRepresentation += currentOpenTag.content;
7472
- }
7473
- else if (currentOpenTag.subTags.length > 0) {
7474
- // Add text after the last tag
7475
- const lastSubTag = currentOpenTag.subTags[currentOpenTag.subTags.length - 1];
7476
- const startOfRemainingText = ((_a = lastSubTag.closingTagIndex) !== null && _a !== void 0 ? _a : lastSubTag.openTagIndex) + lastSubTag.tagType.length + 3;
7477
- const trailingText = currentOpenTag.content.substring(startOfRemainingText);
7478
- plainTextRepresentation += trailingText;
7346
+ onChange && onChange(event, result);
7347
+ }), [onChange, mentionLookupOptions, setCaretIndex, setCaretPosition, updateMentionSuggestions, debouncedQueryUpdate]);
7348
+ // Adjust the selection range based on a mouse / touch interaction
7349
+ const handleOnMove = React.useCallback((event) => {
7350
+ var _a;
7351
+ let targetStart = event.currentTarget.selectionStart;
7352
+ let targetEnd = event.currentTarget.selectionEnd;
7353
+ // Should we do anything?
7354
+ if (interactionStartPoint !== undefined &&
7355
+ // And did selection change?
7356
+ targetStart !== null &&
7357
+ (targetStart !== (targetSelection === null || targetSelection === void 0 ? void 0 : targetSelection.start) || targetEnd !== (targetSelection === null || targetSelection === void 0 ? void 0 : targetSelection.end))) {
7358
+ const direction = event.clientX > interactionStartPoint.x ? 'forward' : 'backward';
7359
+ const mentionTag = findMentionTagForSelection(tagsValue, direction === 'backward'
7360
+ ? event.currentTarget.selectionStart
7361
+ : (_a = event.currentTarget.selectionEnd) !== null && _a !== void 0 ? _a : event.currentTarget.selectionStart);
7362
+ let updateCurrentTarget = false;
7363
+ if (mentionTag !== undefined && mentionTag.plainTextBeginIndex !== undefined) {
7364
+ targetStart = Math.min(mentionTag.plainTextBeginIndex, targetStart);
7365
+ if (mentionTag.plainTextEndIndex !== undefined && targetEnd !== null) {
7366
+ targetEnd = Math.max(mentionTag.plainTextEndIndex, targetEnd);
7479
7367
  }
7480
- currentOpenTag.plainTextEndIndex = plainTextRepresentation.length;
7481
- addTag(currentOpenTag, tagParseStack, tags);
7368
+ updateCurrentTarget = true;
7369
+ setShouldHandleOnMouseDownDuringSelect(false);
7482
7370
  }
7483
- else {
7484
- throw new Error('Unexpected close tag found. Got "' +
7485
- closeTagType +
7486
- '" but expected "' +
7487
- ((_b = tagParseStack[tagParseStack.length - 1]) === null || _b === void 0 ? void 0 : _b.tagType) +
7488
- '"');
7371
+ // Update selection range
7372
+ setTargetSelection({ start: targetStart, end: targetEnd });
7373
+ if (updateCurrentTarget) {
7374
+ // Only set the control, if the values are updated
7375
+ event.currentTarget.setSelectionRange(targetStart, targetEnd, direction);
7489
7376
  }
7490
7377
  }
7491
- // Update parsing index; move past the end of the close tag
7492
- parseIndex = foundHtmlTag.startIdx + foundHtmlTag.content.length;
7493
- } // While parseIndex < text.length loop
7494
- return { tags, plainText: plainTextRepresentation };
7495
- };
7496
- /* @conditional-compile-remove(mention) */
7497
- const parseOpenTag = (tag, startIdx) => {
7498
- const tagType = tag
7499
- .substring(1, tag.length - 1)
7500
- .split(' ')[0]
7501
- .toLowerCase()
7502
- .replace('/', '');
7503
- return {
7504
- tagType,
7505
- openTagIndex: startIdx,
7506
- openTagBody: tag
7507
- };
7508
- };
7509
- /* @conditional-compile-remove(mention) */
7510
- const findNextHtmlTag = (text, startIndex) => {
7511
- const tagStartIndex = text.indexOf('<', startIndex);
7512
- if (tagStartIndex === -1) {
7513
- // No more tags
7514
- return undefined;
7515
- }
7516
- const tagEndIndex = text.indexOf('>', tagStartIndex);
7517
- if (tagEndIndex === -1) {
7518
- // No close tag
7519
- return undefined;
7520
- }
7521
- const tag = text.substring(tagStartIndex, tagEndIndex + 1);
7522
- let type = 'open';
7523
- if (tag[1] === '/') {
7524
- type = 'close';
7525
- }
7526
- else if (tag[tag.length - 2] === '/') {
7527
- type = 'self-closing';
7528
- }
7529
- return {
7530
- content: tag,
7531
- startIdx: tagStartIndex,
7532
- type
7533
- };
7378
+ }, [setTargetSelection, targetSelection, setShouldHandleOnMouseDownDuringSelect, interactionStartPoint, tagsValue]);
7379
+ const announcerText = React.useMemo(() => {
7380
+ if (activeSuggestionIndex === undefined) {
7381
+ return undefined;
7382
+ }
7383
+ const currentMention = mentionSuggestions[activeSuggestionIndex !== null && activeSuggestionIndex !== void 0 ? activeSuggestionIndex : 0];
7384
+ return (currentMention === null || currentMention === void 0 ? void 0 : currentMention.displayText.length) > 0
7385
+ ? currentMention === null || currentMention === void 0 ? void 0 : currentMention.displayText
7386
+ : localeStrings.participantItem.displayNamePlaceholder;
7387
+ }, [activeSuggestionIndex, mentionSuggestions, localeStrings.participantItem.displayNamePlaceholder]);
7388
+ return (React__default['default'].createElement(React__default['default'].Fragment, null,
7389
+ mentionSuggestions.length > 0 && (React__default['default'].createElement(_MentionPopover, { suggestions: mentionSuggestions, activeSuggestionIndex: activeSuggestionIndex, target: inputBoxRef, targetPositionOffset: caretPosition, onRenderSuggestionItem: mentionLookupOptions === null || mentionLookupOptions === void 0 ? void 0 : mentionLookupOptions.onRenderSuggestionItem, onSuggestionSelected: onSuggestionSelected, onDismiss: () => {
7390
+ updateMentionSuggestions([]);
7391
+ } })),
7392
+ announcerText !== undefined && React__default['default'].createElement(Announcer$1, { announcementString: announcerText, ariaLive: 'polite' }),
7393
+ React__default['default'].createElement(react.TextField, Object.assign({}, textFieldProps, { "data-ui-id": dataUiId, value: inputTextValue, onChange: (e, newValue) => {
7394
+ // Remove when switching to react 17+, currently needed because of https://legacy.reactjs.org/docs/legacy-event-pooling.html
7395
+ // Prevents React from resetting event's properties
7396
+ e.persist();
7397
+ setInputTextValue(newValue !== null && newValue !== void 0 ? newValue : '');
7398
+ handleOnChange({
7399
+ event: e,
7400
+ tagsValue,
7401
+ htmlTextValue: textValue,
7402
+ inputTextValue,
7403
+ currentTriggerStartIndex,
7404
+ previousSelectionStart: nullToUndefined(selectionStartValue),
7405
+ previousSelectionEnd: nullToUndefined(selectionEndValue),
7406
+ currentSelectionStart: nullToUndefined(e.currentTarget.selectionStart),
7407
+ currentSelectionEnd: nullToUndefined(e.currentTarget.selectionEnd),
7408
+ updatedValue: newValue
7409
+ });
7410
+ }, onSelect: (e) => {
7411
+ handleOnSelect({
7412
+ event: e,
7413
+ inputTextValue,
7414
+ shouldHandleOnMouseDownDuringSelect,
7415
+ selectionEndValue,
7416
+ selectionStartValue,
7417
+ tags: tagsValue
7418
+ });
7419
+ }, onMouseDown: (e) => {
7420
+ setInteractionStartPoint({ x: e.clientX, y: e.clientY });
7421
+ // as events order is onMouseDown -> onMouseMove -> onMouseUp -> onSelect -> onClick
7422
+ // onClick and onMouseDown can't handle clicking on mention event because
7423
+ // onMouseDown doesn't have correct selectionRange yet and
7424
+ // onClick already has wrong range as it's called after onSelect that updates the selection range
7425
+ // so we need to handle onMouseDown to prevent onSelect default behavior
7426
+ setShouldHandleOnMouseDownDuringSelect(true);
7427
+ }, onMouseMove: handleOnMove, onMouseUp: () => {
7428
+ setInteractionStartPoint(undefined);
7429
+ }, onTouchStart: (e) => {
7430
+ setInteractionStartPoint({
7431
+ x: e.targetTouches.item(0).clientX,
7432
+ y: e.targetTouches.item(0).clientY
7433
+ });
7434
+ // see onMouseDown for more details
7435
+ setShouldHandleOnMouseDownDuringSelect(true);
7436
+ }, onTouchMove: handleOnMove, onTouchEnd: () => {
7437
+ setInteractionStartPoint(undefined);
7438
+ }, onBlur: () => {
7439
+ // setup all flags to default values when text field loses focus
7440
+ setShouldHandleOnMouseDownDuringSelect(false);
7441
+ setCaretIndex(undefined);
7442
+ setSelectionStartValue(undefined);
7443
+ setSelectionEndValue(undefined);
7444
+ }, onKeyDown: onTextFieldKeyDown, elementRef: inputBoxRef }))));
7534
7445
  };
7535
- /* @conditional-compile-remove(mention) */
7536
- const addTag = (tag, parseStack, tags) => {
7537
- var _a;
7538
- // Add as sub-tag to the parent stack tag, if there is one
7539
- const parentTag = parseStack[parseStack.length - 1];
7540
- if (parentTag) {
7541
- // Adjust the open tag index to be relative to the parent tag
7542
- const parentContentStartIdx = parentTag.openTagIndex + parentTag.openTagBody.length;
7543
- const relativeIdx = tag.openTagIndex - parentContentStartIdx;
7544
- tag.openTagIndex = relativeIdx;
7545
- }
7546
- if (!tag.closingTagIndex) {
7547
- // If the tag is self-closing, the close tag is the same as the open tag
7548
- if (tag.openTagBody[tag.openTagBody.length - 2] === '/') {
7549
- tag.closingTagIndex = tag.openTagIndex;
7446
+
7447
+ // Copyright (c) Microsoft Corporation.
7448
+ /**
7449
+ * @private
7450
+ */
7451
+ const InputBoxComponent = (props) => {
7452
+ const { styles, id, 'data-ui-id': dataUiId, textValue, onChange, textFieldRef, placeholderText, onKeyDown, onEnterKeyDown, supportNewline, inputClassName, errorMessage, disabled, children } = props;
7453
+ const mergedRootStyle = react.mergeStyles(inputBoxWrapperStyle, styles === null || styles === void 0 ? void 0 : styles.root);
7454
+ const mergedInputFieldStyle = react.mergeStyles(inputBoxStyle, inputClassName, props.inlineChildren ? {} : inputBoxNewLineSpaceAffordance);
7455
+ const mergedTextContainerStyle = react.mergeStyles(textContainerStyle, styles === null || styles === void 0 ? void 0 : styles.textFieldContainer);
7456
+ const mergedTextFieldStyle = react.concatStyleSets(textFieldStyle, {
7457
+ fieldGroup: styles === null || styles === void 0 ? void 0 : styles.textField,
7458
+ errorMessage: styles === null || styles === void 0 ? void 0 : styles.systemMessage,
7459
+ suffix: {
7460
+ backgroundColor: 'transparent',
7461
+ // Remove empty space in the suffix area when adding newline-style buttons
7462
+ display: props.inlineChildren ? 'flex' : 'contents',
7463
+ padding: '0 0.25rem'
7550
7464
  }
7551
- else {
7552
- // Otherwise, the close tag index is the open tag index + the open tag body + the content length
7553
- tag.closingTagIndex = tag.openTagIndex + tag.openTagBody.length + ((_a = tag.content) !== null && _a !== void 0 ? _a : []).length;
7465
+ });
7466
+ const mergedChildrenStyle = react.mergeStyles(props.inlineChildren ? {} : newLineButtonsContainerStyle);
7467
+ const onTextFieldKeyDown = React.useCallback((ev) => {
7468
+ if (isEnterKeyEventFromCompositionSession(ev)) {
7469
+ return;
7554
7470
  }
7555
- }
7556
- // Put the tag where it belongs
7557
- if (!parentTag) {
7558
- tags.push(tag);
7559
- }
7560
- else {
7561
- if (!parentTag.subTags) {
7562
- parentTag.subTags = [tag];
7471
+ if (ev.key === 'Enter' && (ev.shiftKey === false || !supportNewline)) {
7472
+ ev.preventDefault();
7473
+ onEnterKeyDown && onEnterKeyDown();
7563
7474
  }
7564
- else {
7565
- parentTag.subTags.push(tag);
7475
+ onKeyDown && onKeyDown(ev);
7476
+ }, [onEnterKeyDown, onKeyDown, supportNewline]);
7477
+ const onRenderChildren = () => {
7478
+ return (React__default['default'].createElement(react.Stack, { horizontal: true, className: mergedChildrenStyle }, children));
7479
+ };
7480
+ const renderTextField = () => {
7481
+ const textFieldProps = {
7482
+ autoFocus: props.autoFocus === 'sendBoxTextField',
7483
+ multiline: true,
7484
+ autoAdjustHeight: true,
7485
+ multiple: false,
7486
+ resizable: false,
7487
+ componentRef: textFieldRef,
7488
+ id,
7489
+ inputClassName: mergedInputFieldStyle,
7490
+ placeholder: placeholderText,
7491
+ autoComplete: 'off',
7492
+ styles: mergedTextFieldStyle,
7493
+ disabled,
7494
+ errorMessage,
7495
+ onRenderSuffix: onRenderChildren
7496
+ };
7497
+ /* @conditional-compile-remove(mention) */
7498
+ const textFieldWithMentionProps = {
7499
+ textFieldProps: textFieldProps,
7500
+ dataUiId: dataUiId,
7501
+ textValue: textValue,
7502
+ onChange: onChange,
7503
+ onKeyDown: onKeyDown,
7504
+ onEnterKeyDown: onEnterKeyDown,
7505
+ textFieldRef: textFieldRef,
7506
+ supportNewline: supportNewline,
7507
+ mentionLookupOptions: props.mentionLookupOptions
7508
+ };
7509
+ /* @conditional-compile-remove(mention) */
7510
+ if (props.mentionLookupOptions) {
7511
+ return React__default['default'].createElement(TextFieldWithMention, Object.assign({}, textFieldWithMentionProps));
7566
7512
  }
7567
- }
7513
+ return (React__default['default'].createElement(react.TextField, Object.assign({}, textFieldProps, { "data-ui-id": dataUiId, value: textValue, onChange: onChange, onKeyDown: onTextFieldKeyDown })));
7514
+ };
7515
+ return (React__default['default'].createElement(react.Stack, { className: mergedRootStyle },
7516
+ React__default['default'].createElement("div", { className: mergedTextContainerStyle }, renderTextField())));
7517
+ };
7518
+ /**
7519
+ * @private
7520
+ */
7521
+ const InputBoxButton = (props) => {
7522
+ const { onRenderIcon, onClick, ariaLabel, className, id, tooltipContent } = props;
7523
+ const [isHover, setIsHover] = React.useState(false);
7524
+ const mergedButtonStyle = react.mergeStyles(inputButtonStyle, className);
7525
+ const theme = useTheme();
7526
+ const calloutStyle = { root: { padding: 0 }, calloutMain: { padding: '0.5rem' } };
7527
+ // Place callout with no gap between it and the button.
7528
+ const calloutProps = {
7529
+ gapSpace: 0,
7530
+ styles: calloutStyle,
7531
+ backgroundColor: isDarkThemed(theme) ? theme.palette.neutralLighter : ''
7532
+ };
7533
+ return (React__default['default'].createElement(react.TooltipHost, { hostClassName: inputButtonTooltipStyle, content: tooltipContent, calloutProps: Object.assign({}, calloutProps) },
7534
+ React__default['default'].createElement(react.IconButton, { className: mergedButtonStyle, ariaLabel: ariaLabel, onClick: onClick, id: id, onMouseEnter: () => {
7535
+ setIsHover(true);
7536
+ }, onMouseLeave: () => {
7537
+ setIsHover(false);
7538
+ },
7539
+ // VoiceOver fix: Avoid icon from stealing focus when IconButton is double-tapped to send message by wrapping with Stack with pointerEvents style to none
7540
+ onRenderIcon: () => React__default['default'].createElement(react.Stack, { className: iconWrapperStyle$1 }, onRenderIcon(isHover)) })));
7568
7541
  };
7569
7542
 
7570
7543
  // Copyright (c) Microsoft Corporation.