@azure/communication-react 1.5.1-alpha-202306060014 → 1.5.1-alpha-202306080014

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/dist/communication-react.d.ts +17 -1
  2. package/dist/dist-cjs/communication-react/index.js +1627 -1506
  3. package/dist/dist-cjs/communication-react/index.js.map +1 -1
  4. package/dist/dist-esm/acs-ui-common/src/telemetryVersion.js +1 -1
  5. package/dist/dist-esm/acs-ui-common/src/telemetryVersion.js.map +1 -1
  6. package/dist/dist-esm/react-components/src/components/InputBoxComponent.js +38 -1375
  7. package/dist/dist-esm/react-components/src/components/InputBoxComponent.js.map +1 -1
  8. package/dist/dist-esm/react-components/src/components/TextFieldWithMention/TextFieldWithMention.d.ts +41 -0
  9. package/dist/dist-esm/react-components/src/components/TextFieldWithMention/TextFieldWithMention.js +554 -0
  10. package/dist/dist-esm/react-components/src/components/TextFieldWithMention/TextFieldWithMention.js.map +1 -0
  11. package/dist/dist-esm/react-components/src/components/TextFieldWithMention/mentionTagUtils.d.ts +167 -0
  12. package/dist/dist-esm/react-components/src/components/TextFieldWithMention/mentionTagUtils.js +780 -0
  13. package/dist/dist-esm/react-components/src/components/TextFieldWithMention/mentionTagUtils.js.map +1 -0
  14. package/dist/dist-esm/react-composites/src/composites/CallComposite/CallComposite.js +6 -0
  15. package/dist/dist-esm/react-composites/src/composites/CallComposite/CallComposite.js.map +1 -1
  16. package/dist/dist-esm/react-composites/src/composites/CallComposite/Strings.d.ts +16 -0
  17. package/dist/dist-esm/react-composites/src/composites/CallComposite/Strings.js.map +1 -1
  18. package/dist/dist-esm/react-composites/src/composites/CallComposite/adapter/AzureCommunicationCallAdapter.js +8 -7
  19. package/dist/dist-esm/react-composites/src/composites/CallComposite/adapter/AzureCommunicationCallAdapter.js.map +1 -1
  20. package/dist/dist-esm/react-composites/src/composites/CallComposite/adapter/CallAdapter.d.ts +1 -1
  21. package/dist/dist-esm/react-composites/src/composites/CallComposite/adapter/CallAdapter.js.map +1 -1
  22. package/dist/dist-esm/react-composites/src/composites/CallComposite/components/MediaGallery.js +4 -3
  23. package/dist/dist-esm/react-composites/src/composites/CallComposite/components/MediaGallery.js.map +1 -1
  24. package/dist/dist-esm/react-composites/src/composites/CallComposite/pages/TransferPage.d.ts +14 -0
  25. package/dist/dist-esm/react-composites/src/composites/CallComposite/pages/TransferPage.js +101 -0
  26. package/dist/dist-esm/react-composites/src/composites/CallComposite/pages/TransferPage.js.map +1 -0
  27. package/dist/dist-esm/react-composites/src/composites/CallComposite/selectors/baseSelectors.d.ts +4 -0
  28. package/dist/dist-esm/react-composites/src/composites/CallComposite/selectors/baseSelectors.js +5 -0
  29. package/dist/dist-esm/react-composites/src/composites/CallComposite/selectors/baseSelectors.js.map +1 -1
  30. package/dist/dist-esm/react-composites/src/composites/CallComposite/styles/TransferPage.styles.d.ts +26 -0
  31. package/dist/dist-esm/react-composites/src/composites/CallComposite/styles/TransferPage.styles.js +47 -0
  32. package/dist/dist-esm/react-composites/src/composites/CallComposite/styles/TransferPage.styles.js.map +1 -0
  33. package/dist/dist-esm/react-composites/src/composites/CallComposite/utils/Utils.d.ts +2 -2
  34. package/dist/dist-esm/react-composites/src/composites/CallComposite/utils/Utils.js +5 -1
  35. package/dist/dist-esm/react-composites/src/composites/CallComposite/utils/Utils.js.map +1 -1
  36. package/dist/dist-esm/react-composites/src/composites/localization/locales/en-US/strings.json +5 -1
  37. package/package.json +8 -8
@@ -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-202306080014';
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;
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;
6825
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;
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;
6886
6731
  }
6887
6732
  }
6733
+ // Put the tag where it belongs
6734
+ if (!parentTag) {
6735
+ tags.push(tag);
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
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();
7077
6878
  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
7159
- break;
7160
- }
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
7212
- }
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
7220
- }
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;
7269
- }
7270
- // the change is handled; exit
7271
- break;
7272
- }
7273
- lastProcessedPlainTextTagEndIndex = closingTagInfo.plainTextEndIndex;
7274
6879
  }
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;
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);
7283
7067
  }
7284
- else {
7285
- result +=
7286
- htmlText.substring(lastProcessedHTMLIndex, closingTagInfo.closeTagIdx + closingTagInfo.closeTagLength) +
7287
- processedChange;
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);
7288
7074
  }
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;
7294
- }
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;
7329
- }
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;
7337
- }
7075
+ else if (ev.key === 'Escape') {
7076
+ updateMentionSuggestions([]);
7338
7077
  }
7339
- // make indexes exclusive
7340
- newChangeEnd += 1;
7341
- oldChangeEnd += 1;
7342
- }
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;
7351
7078
  }
7352
- 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;
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;
7359
7087
  }
7360
7088
  }
7361
- // make indexes exclusive
7362
- newChangeEnd += 1;
7363
- oldChangeEnd += 1;
7089
+ onEnterKeyDown && onEnterKeyDown();
7364
7090
  }
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;
7374
- }
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);
7375
7106
  }
7376
- // make indexes exclusive if they aren't equal to the length of the string
7377
- if (newChangeEnd !== newText.length) {
7378
- newChangeEnd += 1;
7107
+ else if (activeSuggestionIndex === undefined) {
7108
+ // Set the active to the first, if it's not already set
7109
+ setActiveSuggestionIndex(0);
7379
7110
  }
7380
- if (oldChangeEnd !== oldText.length) {
7381
- oldChangeEnd += 1;
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
+ }
7382
7138
  }
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);
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
+ }
7436
7156
  }
7437
- else {
7438
- plainTextRepresentation = text;
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
+ });
7171
+ }
7439
7172
  }
7440
- break;
7441
7173
  }
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);
7174
+ // e.currentTarget.selectionDirection should be set to handle shift + arrow keys
7175
+ if (event.currentTarget.selectionDirection === null) {
7176
+ event.currentTarget.setSelectionRange(updatedStartIndex, updatedEndIndex);
7177
+ }
7178
+ else {
7179
+ event.currentTarget.setSelectionRange(updatedStartIndex, updatedEndIndex, event.currentTarget.selectionDirection);
7180
+ }
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);
7449
7191
  }
7450
- else {
7451
- nextTag.content = '';
7452
- nextTag.plainTextBeginIndex = plainTextRepresentation.length;
7453
- nextTag.plainTextEndIndex = plainTextRepresentation.length;
7454
- addTag(nextTag, tagParseStack, tags);
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
+ }
7455
7217
  }
7456
7218
  }
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);
7219
+ else {
7220
+ // selection was changed by keyboard
7221
+ updateSelectionIndexesWithMentionIfNeeded({
7222
+ event,
7223
+ inputTextValue,
7224
+ selectionStartValue,
7225
+ selectionEndValue,
7226
+ tagsValue: tags
7227
+ });
7228
+ }
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;
7244
+ }
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);
7469
7287
  }
7470
- if (!currentOpenTag.subTags) {
7471
- plainTextRepresentation += currentOpenTag.content;
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);
7472
7295
  }
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;
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
+ }
7479
7307
  }
7480
- currentOpenTag.plainTextEndIndex = plainTextRepresentation.length;
7481
- addTag(currentOpenTag, tagParseStack, tags);
7482
7308
  }
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
- '"');
7309
+ }
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);
7344
+ }
7345
+ }
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);
7367
+ }
7368
+ updateCurrentTarget = true;
7369
+ setShouldHandleOnMouseDownDuringSelect(false);
7370
+ }
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.
@@ -11101,9 +11074,9 @@ const DEFAULT_PERSONA_MIN_SIZE_PX = 32;
11101
11074
  const DefaultPlaceholder = (props) => {
11102
11075
  const { text, noVideoAvailableAriaLabel, coinSize, hidePersonaDetails } = props;
11103
11076
  return (React__default['default'].createElement(react.Stack, { className: react.mergeStyles({ position: 'absolute', height: '100%', width: '100%' }) },
11104
- React__default['default'].createElement(react.Stack, { styles: defaultPersonaStyles }, coinSize && (React__default['default'].createElement(react.Persona, { coinSize: coinSize, hidePersonaDetails: hidePersonaDetails, text: text !== null && text !== void 0 ? text : '', initialsTextColor: "white", "aria-label": noVideoAvailableAriaLabel !== null && noVideoAvailableAriaLabel !== void 0 ? noVideoAvailableAriaLabel : '', showOverflowTooltip: false })))));
11077
+ React__default['default'].createElement(react.Stack, { styles: defaultPersonaStyles$1 }, coinSize && (React__default['default'].createElement(react.Persona, { coinSize: coinSize, hidePersonaDetails: hidePersonaDetails, text: text !== null && text !== void 0 ? text : '', initialsTextColor: "white", "aria-label": noVideoAvailableAriaLabel !== null && noVideoAvailableAriaLabel !== void 0 ? noVideoAvailableAriaLabel : '', showOverflowTooltip: false })))));
11105
11078
  };
11106
- const defaultPersonaStyles = { root: { margin: 'auto', maxHeight: '100%' } };
11079
+ const defaultPersonaStyles$1 = { root: { margin: 'auto', maxHeight: '100%' } };
11107
11080
  /* @conditional-compile-remove(pinned-participants) */
11108
11081
  const videoTileMoreMenuIconProps = { iconName: undefined, style: { display: 'none' } };
11109
11082
  /* @conditional-compile-remove(pinned-participants) */
@@ -11189,7 +11162,7 @@ const VideoTile = (props) => {
11189
11162
  text: initialsName !== null && initialsName !== void 0 ? initialsName : displayName,
11190
11163
  noVideoAvailableAriaLabel,
11191
11164
  coinSize: personaSize,
11192
- styles: defaultPersonaStyles,
11165
+ styles: defaultPersonaStyles$1,
11193
11166
  hidePersonaDetails: true
11194
11167
  };
11195
11168
  const videoHintWithBorderRadius = react.mergeStyles(videoHint, { borderRadius: theme.effects.roundedCorner4 });
@@ -14850,11 +14823,11 @@ const _PictureInPictureInPicture = (props) => {
14850
14823
  */
14851
14824
  const PictureInPictureInPictureContainer = (props) => {
14852
14825
  const onKeyPress = (e) => props.onClick && submitWithKeyboard(e, props.onClick);
14853
- return (React__default['default'].createElement("aside", { style: tileContainerStyles, onClick: props.onClick, onKeyPress: onKeyPress, "aria-label": props.ariaLabel, tabIndex: props.onClick ? 0 : -1, "data-ui-id": "picture-in-picture-in-picture-root" },
14826
+ return (React__default['default'].createElement("aside", { style: tileContainerStyles$1, onClick: props.onClick, onKeyPress: onKeyPress, "aria-label": props.ariaLabel, tabIndex: props.onClick ? 0 : -1, "data-ui-id": "picture-in-picture-in-picture-root" },
14854
14827
  props.primaryView,
14855
14828
  React__default['default'].createElement("div", { style: secondaryTileFloatingStyles }, props.secondaryView)));
14856
14829
  };
14857
- const tileContainerStyles = {
14830
+ const tileContainerStyles$1 = {
14858
14831
  display: 'flex',
14859
14832
  width: 'min-content',
14860
14833
  position: 'relative',
@@ -18663,7 +18636,7 @@ const CallCompositeIcon = (props) => (React__default['default'].createElement(re
18663
18636
  */
18664
18637
  const CallWithChatCompositeIcon = (props) => (React__default['default'].createElement(react.FontIcon, Object.assign({}, props)));
18665
18638
 
18666
- var call$d={cameraLabel:"Camera",noCamerasLabel:"No cameras found",cameraPermissionDenied:"Your browser is blocking access to your camera",cameraTurnedOff:"Your camera is turned off",chatButtonLabel:"Chat",close:"Close",complianceBannerNowOnlyRecording:"You are now only recording this meeting.",complianceBannerNowOnlyTranscription:"You are now only transcribing this meeting.",complianceBannerRecordingAndTranscriptionSaved:"Recording and transcription are being saved.",complianceBannerRecordingAndTranscriptionStarted:"Recording and transcription have started.",complianceBannerRecordingAndTranscriptionStopped:"Recording and transcription have stopped.",complianceBannerRecordingSaving:"Recording is being saved.",complianceBannerRecordingStarted:"Recording has started.",complianceBannerRecordingStopped:"Recording has stopped.",complianceBannerTranscriptionStarted:"Transcription has started.",complianceBannerTranscriptionConsent:"By joining, you are giving consent for this meeting to be transcribed.",complianceBannerTranscriptionSaving:"Transcription is being saved.",complianceBannerTranscriptionStopped:"Transcription has stopped.",configurationPageTitle:"Start a call",copyInviteLinkButtonLabel:"Copy invite link",copyInviteLinkActionedAriaLabel:"Invite link copied",defaultPlaceHolder:"Select an option",dismissSidePaneButtonLabel:"Close",videoEffectsPaneTitle:"Effects",videoEffectsPaneBackgroundSelectionTitle:"Background",configurationPageVideoEffectsButtonLabel:"Effects",unableToStartVideoEffect:"Unable to apply video effect.",blurBackgroundEffectButtonLabel:"Blur",blurBackgroundTooltip:"Blur Background",removeBackgroundEffectButtonLabel:"None",removeBackgroundTooltip:"Remove Background",cameraOffBackgroundEffectWarningText:"Your camera is off. Turn on camera to see video effect.",failedToJoinCallDueToNoNetworkMoreDetails:"Call was disconnected due to a network issue. Check your connection and join again.",failedToJoinCallDueToNoNetworkTitle:"Call disconnected",failedToJoinTeamsMeetingReasonAccessDeniedMoreDetails:"You were not granted entry in the call. If this was a mistake, re-join the call.",failedToJoinTeamsMeetingReasonAccessDeniedTitle:"Dismissed from lobby",learnMore:"Learn more",leftCallMoreDetails:"If this was a mistake, re-join the call.",leftCallTitle:"You left the call",lobbyScreenConnectingToCallTitle:"Joining call",lobbyScreenWaitingToBeAdmittedTitle:"Waiting to be admitted",microphonePermissionDenied:"Your browser is blocking access to your microphone",microphoneToggleInLobbyNotAllowed:"Cannot mute or unmute while in lobby.",mutedMessage:"You're muted",networkReconnectMoreDetails:"Looks like something went wrong. We're trying to get back into the call.",networkReconnectTitle:"Hold on",deniedPermissionToRoomDetails:"You do not have permission to join this room.",deniedPermissionToRoomTitle:"Permission denied to room",peopleButtonLabel:"People",peoplePaneTitle:"People",peopleButtonTooltipOpen:"Show participants",peopleButtonTooltipClose:"Hide participants",peoplePaneSubTitle:"In this call",privacyPolicy:"Privacy policy",rejoinCallButtonLabel:"Re-join call",removedFromCallMoreDetails:"Another participant removed you from the call.",removedFromCallTitle:"You were removed",removeMenuLabel:"Remove",returnToCallButtonAriaDescription:"Return to Call",returnToCallButtonAriaLabel:"Back",roomNotFoundDetails:"Room ID provided is not valid.",roomNotFoundTitle:"Room not found",soundLabel:"Sound",noMicrophonesLabel:"No microphones found",noSpeakersLabel:"No speakers found",startCallButtonLabel:"Start call",openDialpadButtonLabel:"Dial phone number",peoplePaneAddPeopleButtonLabel:"Add People",dialpadStartCallButtonLabel:"Call",dialpadModalTitle:"Dial Phone Number",dialpadModalAriaLabel:"Dialpad",dialpadCloseModalButtonAriaLabel:"Close dialpad",moreButtonCallingLabel:"More",resumeCallButtonLabel:"Resume",resumingCallButtonLabel:"Resuming...",resumeCallButtonAriaLabel:"Resume call",resumingCallButtonAriaLabel:"Resume call",holdScreenLabel:"You're on hold",openDtmfDialpadLabel:"Show dialpad",dtmfDialpadPlaceholderText:"Enter number",outboundCallingNoticeString:"Calling...",participantJoinedNoticeString:"{displayName} joined",twoParticipantJoinedNoticeString:"{displayName1} and {displayName2} have joined",threeParticipantJoinedNoticeString:"{displayName1}, {displayName2} and {displayName3} have joined",participantLeftNoticeString:"{displayName} left",twoParticipantLeftNoticeString:"{displayName1} and {displayName2} have left",threeParticipantLeftNoticeString:"{displayName1}, {displayName2} and {displayName3} have left",unnamedParticipantString:"unnamed participant",manyUnnamedParticipantsJoined:"unnamed participant and {numOfParticipants} other participants joined",manyUnnamedParticipantsLeft:"unnamed participant and {numOfParticipants} other participants left",manyParticipantsJoined:"{displayName1}, {displayName2}, {displayName3} and {numOfParticipants} other participants joined",manyParticipantsLeft:"{displayName1}, {displayName2}, {displayName3} and {numOfParticipants} other participants left",liveCaptionsLabel:"Live captions",captionsSettingsLabel:"Caption settings",startCaptionsButtonOnLabel:"Turn on captions",startCaptionsButtonOffLabel:"Turn off captions",startCaptionsButtonTooltipOnContent:"Turn off captions",startCaptionsButtonTooltipOffContent:"Turn on captions",captionsSettingsModalTitle:"What language is being spoken?",captionsSettingsDropdownLabel:"Spoken language",captionsSettingsDropdownInfoText:"Language that everyone on this call is speaking.",captionsSettingsConfirmButtonLabel:"Confirm",captionsSettingsCancelButtonLabel:"Cancel",captionsSettingsModalAriaLabel:"Captions Setting Modal",captionsSettingsCloseModalButtonAriaLabel:"Close Captions Setting",captionsBannerMoreButtonCallingLabel:"More",captionsBannerMoreButtonTooltip:"More options",captionsAvailableLanguageStrings:{"ar-ae":"Arabic - U.A.E.","ar-sa":"Arabic - Saudi Arabia","da-dk":"Danish","de-de":"German - Germany","en-au":"English - Australia","en-ca":"English - Canada","en-gb":"English - United Kingdom","en-in":"English - India","en-nz":"English - New Zealand","en-us":"English - United States","es-es":"Spanish - Spain (Modern Sort)","es-mx":"Spanish - Mexico","fi-fi":"Finnish","fr-ca":"French - Canada","fr-fr":"French - France","hi-in":"Hindi","it-it":"Italian - Italy","ja-jp":"Japanese","ko-kr":"Korean","nb-no":"Norwegian (Bokmål)","nl-be":"Dutch - Belgium","nl-nl":"Dutch - Netherlands","pl-pl":"Polish","pt-br":"Portuguese - Brazil","ru-ru":"Russian","sv-se":"Swedish","zh-cn":"Chinese - People's Republic of China","zh-hk":"Chinese - Hong Kong SAR","cs-cz":"Czech","pt-pt":"Portuguese - Portugal","tr-tr":"Turkish","vi-vn":"Vietnamese","th-th":"Thai","he-il":"Hebrew","cy-gb":"Welsh","uk-ua":"Ukrainian","el-gr":"Greek","hu-hu":"Hungarian","ro-ro":"Romanian","sk-sk":"Slovak","zh-tw":"Chinese - Taiwan"},captionsBannerSpinnerText:"Starting captions..."};var chat$d={chatListHeader:"In this chat",uploadFile:"Upload File"};var callWithChat$d={chatButtonLabel:"Chat",chatButtonNewMessageNotificationLabel:"New Message",chatButtonTooltipClosedWithMessageCount:"Show chat ({unreadMessagesCount} unread)",chatButtonTooltipClose:"Hide chat",chatButtonTooltipOpen:"Show chat",chatPaneTitle:"Chat",copyInviteLinkButtonLabel:"Copy invite link",copyInviteLinkActionedAriaLabel:"Invite link copied",dismissSidePaneButtonLabel:"Close",moreDrawerAudioDeviceMenuTitle:"Audio Device",moreDrawerButtonLabel:"More options",moreDrawerButtonTooltip:"More options",moreDrawerMicrophoneMenuTitle:"Microphone",moreDrawerSpeakerMenuTitle:"Speaker",moreDrawerCaptionsMenuTitle:"Live captions",moreDrawerSpokenLanguageMenuTitle:"Spoken language",peopleButtonLabel:"People",peopleButtonTooltipOpen:"Show participants",peopleButtonTooltipClose:"Hide participants",peoplePaneSubTitle:"In this call",peoplePaneTitle:"People",pictureInPictureTileAriaLabel:"Video Feeds. Click to return to call screen.",removeMenuLabel:"Remove",openDialpadButtonLabel:"Dial phone number",returnToCallButtonAriaDescription:"Return to Call",returnToCallButtonAriaLabel:"Back",peoplePaneAddPeopleButtonLabel:"Add People",dialpadStartCallButtonLabel:"Call",dialpadModalTitle:"Dial Phone Number",dialpadModalAriaLabel:"Dialpad",dialpadCloseModalButtonAriaLabel:"Close dialpad",openDtmfDialpadLabel:"Show dialpad",dtmfDialpadPlaceholderText:"Enter number"};var en_US = {call:call$d,chat:chat$d,callWithChat:callWithChat$d};
18639
+ var call$d={cameraLabel:"Camera",noCamerasLabel:"No cameras found",cameraPermissionDenied:"Your browser is blocking access to your camera",cameraTurnedOff:"Your camera is turned off",chatButtonLabel:"Chat",close:"Close",complianceBannerNowOnlyRecording:"You are now only recording this meeting.",complianceBannerNowOnlyTranscription:"You are now only transcribing this meeting.",complianceBannerRecordingAndTranscriptionSaved:"Recording and transcription are being saved.",complianceBannerRecordingAndTranscriptionStarted:"Recording and transcription have started.",complianceBannerRecordingAndTranscriptionStopped:"Recording and transcription have stopped.",complianceBannerRecordingSaving:"Recording is being saved.",complianceBannerRecordingStarted:"Recording has started.",complianceBannerRecordingStopped:"Recording has stopped.",complianceBannerTranscriptionStarted:"Transcription has started.",complianceBannerTranscriptionConsent:"By joining, you are giving consent for this meeting to be transcribed.",complianceBannerTranscriptionSaving:"Transcription is being saved.",complianceBannerTranscriptionStopped:"Transcription has stopped.",configurationPageTitle:"Start a call",copyInviteLinkButtonLabel:"Copy invite link",copyInviteLinkActionedAriaLabel:"Invite link copied",defaultPlaceHolder:"Select an option",dismissSidePaneButtonLabel:"Close",videoEffectsPaneTitle:"Effects",videoEffectsPaneBackgroundSelectionTitle:"Background",configurationPageVideoEffectsButtonLabel:"Effects",unableToStartVideoEffect:"Unable to apply video effect.",blurBackgroundEffectButtonLabel:"Blur",blurBackgroundTooltip:"Blur Background",removeBackgroundEffectButtonLabel:"None",removeBackgroundTooltip:"Remove Background",cameraOffBackgroundEffectWarningText:"Your camera is off. Turn on camera to see video effect.",failedToJoinCallDueToNoNetworkMoreDetails:"Call was disconnected due to a network issue. Check your connection and join again.",failedToJoinCallDueToNoNetworkTitle:"Call disconnected",failedToJoinTeamsMeetingReasonAccessDeniedMoreDetails:"You were not granted entry in the call. If this was a mistake, re-join the call.",failedToJoinTeamsMeetingReasonAccessDeniedTitle:"Dismissed from lobby",learnMore:"Learn more",leftCallMoreDetails:"If this was a mistake, re-join the call.",leftCallTitle:"You left the call",lobbyScreenConnectingToCallTitle:"Joining call",lobbyScreenWaitingToBeAdmittedTitle:"Waiting to be admitted",microphonePermissionDenied:"Your browser is blocking access to your microphone",microphoneToggleInLobbyNotAllowed:"Cannot mute or unmute while in lobby.",mutedMessage:"You're muted",networkReconnectMoreDetails:"Looks like something went wrong. We're trying to get back into the call.",networkReconnectTitle:"Hold on",deniedPermissionToRoomDetails:"You do not have permission to join this room.",deniedPermissionToRoomTitle:"Permission denied to room",peopleButtonLabel:"People",peoplePaneTitle:"People",peopleButtonTooltipOpen:"Show participants",peopleButtonTooltipClose:"Hide participants",peoplePaneSubTitle:"In this call",privacyPolicy:"Privacy policy",rejoinCallButtonLabel:"Re-join call",removedFromCallMoreDetails:"Another participant removed you from the call.",removedFromCallTitle:"You were removed",removeMenuLabel:"Remove",returnToCallButtonAriaDescription:"Return to Call",returnToCallButtonAriaLabel:"Back",roomNotFoundDetails:"Room ID provided is not valid.",roomNotFoundTitle:"Room not found",soundLabel:"Sound",noMicrophonesLabel:"No microphones found",noSpeakersLabel:"No speakers found",startCallButtonLabel:"Start call",openDialpadButtonLabel:"Dial phone number",peoplePaneAddPeopleButtonLabel:"Add People",dialpadStartCallButtonLabel:"Call",dialpadModalTitle:"Dial Phone Number",dialpadModalAriaLabel:"Dialpad",dialpadCloseModalButtonAriaLabel:"Close dialpad",moreButtonCallingLabel:"More",resumeCallButtonLabel:"Resume",resumingCallButtonLabel:"Resuming...",resumeCallButtonAriaLabel:"Resume call",resumingCallButtonAriaLabel:"Resume call",holdScreenLabel:"You're on hold",openDtmfDialpadLabel:"Show dialpad",dtmfDialpadPlaceholderText:"Enter number",outboundCallingNoticeString:"Calling...",participantJoinedNoticeString:"{displayName} joined",twoParticipantJoinedNoticeString:"{displayName1} and {displayName2} have joined",threeParticipantJoinedNoticeString:"{displayName1}, {displayName2} and {displayName3} have joined",participantLeftNoticeString:"{displayName} left",twoParticipantLeftNoticeString:"{displayName1} and {displayName2} have left",threeParticipantLeftNoticeString:"{displayName1}, {displayName2} and {displayName3} have left",unnamedParticipantString:"unnamed participant",manyUnnamedParticipantsJoined:"unnamed participant and {numOfParticipants} other participants joined",manyUnnamedParticipantsLeft:"unnamed participant and {numOfParticipants} other participants left",manyParticipantsJoined:"{displayName1}, {displayName2}, {displayName3} and {numOfParticipants} other participants joined",manyParticipantsLeft:"{displayName1}, {displayName2}, {displayName3} and {numOfParticipants} other participants left",liveCaptionsLabel:"Live captions",captionsSettingsLabel:"Caption settings",startCaptionsButtonOnLabel:"Turn on captions",startCaptionsButtonOffLabel:"Turn off captions",startCaptionsButtonTooltipOnContent:"Turn off captions",startCaptionsButtonTooltipOffContent:"Turn on captions",captionsSettingsModalTitle:"What language is being spoken?",captionsSettingsDropdownLabel:"Spoken language",captionsSettingsDropdownInfoText:"Language that everyone on this call is speaking.",captionsSettingsConfirmButtonLabel:"Confirm",captionsSettingsCancelButtonLabel:"Cancel",captionsSettingsModalAriaLabel:"Captions Setting Modal",captionsSettingsCloseModalButtonAriaLabel:"Close Captions Setting",captionsBannerMoreButtonCallingLabel:"More",captionsBannerMoreButtonTooltip:"More options",captionsAvailableLanguageStrings:{"ar-ae":"Arabic - U.A.E.","ar-sa":"Arabic - Saudi Arabia","da-dk":"Danish","de-de":"German - Germany","en-au":"English - Australia","en-ca":"English - Canada","en-gb":"English - United Kingdom","en-in":"English - India","en-nz":"English - New Zealand","en-us":"English - United States","es-es":"Spanish - Spain (Modern Sort)","es-mx":"Spanish - Mexico","fi-fi":"Finnish","fr-ca":"French - Canada","fr-fr":"French - France","hi-in":"Hindi","it-it":"Italian - Italy","ja-jp":"Japanese","ko-kr":"Korean","nb-no":"Norwegian (Bokmål)","nl-be":"Dutch - Belgium","nl-nl":"Dutch - Netherlands","pl-pl":"Polish","pt-br":"Portuguese - Brazil","ru-ru":"Russian","sv-se":"Swedish","zh-cn":"Chinese - People's Republic of China","zh-hk":"Chinese - Hong Kong SAR","cs-cz":"Czech","pt-pt":"Portuguese - Portugal","tr-tr":"Turkish","vi-vn":"Vietnamese","th-th":"Thai","he-il":"Hebrew","cy-gb":"Welsh","uk-ua":"Ukrainian","el-gr":"Greek","hu-hu":"Hungarian","ro-ro":"Romanian","sk-sk":"Slovak","zh-tw":"Chinese - Taiwan"},captionsBannerSpinnerText:"Starting captions...",transferPageTransferorText:"Transferring...",transferPageTransferTargetText:"Connecting...",transferPageUnknownTransferorDisplayName:"Unknown",transferPageUnknownTransferTargetDisplayName:"Unknown"};var chat$d={chatListHeader:"In this chat",uploadFile:"Upload File"};var callWithChat$d={chatButtonLabel:"Chat",chatButtonNewMessageNotificationLabel:"New Message",chatButtonTooltipClosedWithMessageCount:"Show chat ({unreadMessagesCount} unread)",chatButtonTooltipClose:"Hide chat",chatButtonTooltipOpen:"Show chat",chatPaneTitle:"Chat",copyInviteLinkButtonLabel:"Copy invite link",copyInviteLinkActionedAriaLabel:"Invite link copied",dismissSidePaneButtonLabel:"Close",moreDrawerAudioDeviceMenuTitle:"Audio Device",moreDrawerButtonLabel:"More options",moreDrawerButtonTooltip:"More options",moreDrawerMicrophoneMenuTitle:"Microphone",moreDrawerSpeakerMenuTitle:"Speaker",moreDrawerCaptionsMenuTitle:"Live captions",moreDrawerSpokenLanguageMenuTitle:"Spoken language",peopleButtonLabel:"People",peopleButtonTooltipOpen:"Show participants",peopleButtonTooltipClose:"Hide participants",peoplePaneSubTitle:"In this call",peoplePaneTitle:"People",pictureInPictureTileAriaLabel:"Video Feeds. Click to return to call screen.",removeMenuLabel:"Remove",openDialpadButtonLabel:"Dial phone number",returnToCallButtonAriaDescription:"Return to Call",returnToCallButtonAriaLabel:"Back",peoplePaneAddPeopleButtonLabel:"Add People",dialpadStartCallButtonLabel:"Call",dialpadModalTitle:"Dial Phone Number",dialpadModalAriaLabel:"Dialpad",dialpadCloseModalButtonAriaLabel:"Close dialpad",openDtmfDialpadLabel:"Show dialpad",dtmfDialpadPlaceholderText:"Enter number"};var en_US = {call:call$d,chat:chat$d,callWithChat:callWithChat$d};
18667
18640
 
18668
18641
  var call$c={cameraLabel:"Camera",noCamerasLabel:"No cameras found",cameraPermissionDenied:"Your browser is blocking access to your camera",cameraTurnedOff:"Your camera is turned off",chatButtonLabel:"Chat",close:"Close",complianceBannerNowOnlyRecording:"You are now only recording this meeting.",complianceBannerNowOnlyTranscription:"You are now only transcribing this meeting.",complianceBannerRecordingAndTranscriptionSaved:"Recording and transcription are being saved.",complianceBannerRecordingAndTranscriptionStarted:"Recording and transcription have started.",complianceBannerRecordingAndTranscriptionStopped:"Recording and transcription have stopped.",complianceBannerRecordingSaving:"Recording is being saved.",complianceBannerRecordingStarted:"Recording has started.",complianceBannerRecordingStopped:"Recording has stopped.",complianceBannerTranscriptionStarted:"Transcription has started.",complianceBannerTranscriptionConsent:"By joining, you are giving consent for this meeting to be transcribed.",complianceBannerTranscriptionSaving:"Transcription is being saved.",complianceBannerTranscriptionStopped:"Transcription has stopped.",configurationPageTitle:"Start a call",copyInviteLinkButtonLabel:"Copy invite link",copyInviteLinkActionedAriaLabel:"Invite link copied",defaultPlaceHolder:"Select an option",dismissSidePaneButtonLabel:"Close",videoEffectsPaneTitle:"Effects",videoEffectsPaneBackgroundSelectionTitle:"Background",configurationPageVideoEffectsButtonLabel:"Effects",unableToStartVideoEffect:"Unable to apply video effect.",blurBackgroundEffectButtonLabel:"Blur",blurBackgroundTooltip:"Blur Background",removeBackgroundEffectButtonLabel:"None",removeBackgroundTooltip:"Remove Background",cameraOffBackgroundEffectWarningText:"Your camera is off. Turn on camera to see video effect.",failedToJoinCallDueToNoNetworkMoreDetails:"Call was disconnected due to a network issue. Check your connection and join again.",failedToJoinCallDueToNoNetworkTitle:"Call disconnected",failedToJoinTeamsMeetingReasonAccessDeniedMoreDetails:"You were not granted entry in the call. If this was a mistake, re-join the call.",failedToJoinTeamsMeetingReasonAccessDeniedTitle:"Dismissed from lobby",learnMore:"Learn more",leftCallMoreDetails:"If this was a mistake, re-join the call.",leftCallTitle:"You left the call",lobbyScreenConnectingToCallTitle:"Joining call",lobbyScreenWaitingToBeAdmittedTitle:"Waiting to be admitted",microphonePermissionDenied:"Your browser is blocking access to your microphone",microphoneToggleInLobbyNotAllowed:"Cannot mute or unmute while in lobby.",mutedMessage:"You're muted",networkReconnectMoreDetails:"Looks like something went wrong. We're trying to get back into the call.",networkReconnectTitle:"Hold on",deniedPermissionToRoomDetails:"You do not have permission to join this room.",deniedPermissionToRoomTitle:"Permission denied to room",peopleButtonLabel:"People",peoplePaneTitle:"People",peopleButtonTooltipOpen:"Show participants",peopleButtonTooltipClose:"Hide participants",peoplePaneSubTitle:"In this call",privacyPolicy:"Privacy policy",rejoinCallButtonLabel:"Re-join call",removedFromCallMoreDetails:"Another participant removed you from the call.",removedFromCallTitle:"You were removed",removeMenuLabel:"Remove",returnToCallButtonAriaDescription:"Return to Call",returnToCallButtonAriaLabel:"Back",roomNotFoundDetails:"Room ID provided is not valid.",roomNotFoundTitle:"Room not found",soundLabel:"Sound",noMicrophonesLabel:"No microphones found",noSpeakersLabel:"No speakers found",startCallButtonLabel:"Start call",openDialpadButtonLabel:"Dial phone number",peoplePaneAddPeopleButtonLabel:"Add People",dialpadStartCallButtonLabel:"Call",dialpadModalTitle:"Dial Phone Number",dialpadModalAriaLabel:"Dialpad",dialpadCloseModalButtonAriaLabel:"Close dialpad",moreButtonCallingLabel:"More",resumeCallButtonLabel:"Resume",resumingCallButtonLabel:"Resuming...",resumeCallButtonAriaLabel:"Resume call",resumingCallButtonAriaLabel:"Resume call",holdScreenLabel:"You're on hold",openDtmfDialpadLabel:"Show dialpad",dtmfDialpadPlaceholderText:"Enter number",outboundCallingNoticeString:"Calling...",participantJoinedNoticeString:"{displayName} joined",twoParticipantJoinedNoticeString:"{displayName1} and {displayName2} have joined",threeParticipantJoinedNoticeString:"{displayName1}, {displayName2} and {displayName3} have joined",participantLeftNoticeString:"{displayName} left",twoParticipantLeftNoticeString:"{displayName1} and {displayName2} have left",threeParticipantLeftNoticeString:"{displayName1}, {displayName2} and {displayName3} have left",unnamedParticipantString:"unnamed participant",manyUnnamedParticipantsJoined:"unnamed participant and {numOfParticipants} other participants joined",manyUnnamedParticipantsLeft:"unnamed participant and {numOfParticipants} other participants left",manyParticipantsJoined:"{displayName1}, {displayName2}, {displayName3} and {numOfParticipants} other participants joined",manyParticipantsLeft:"{displayName1}, {displayName2}, {displayName3} and {numOfParticipants} other participants left",liveCaptionsLabel:"Live captions",captionsSettingsLabel:"Caption settings",startCaptionsButtonOnLabel:"Turn on captions",startCaptionsButtonOffLabel:"Turn off captions",startCaptionsButtonTooltipOnContent:"Turn off captions",startCaptionsButtonTooltipOffContent:"Turn on captions",captionsSettingsModalTitle:"What language is being spoken?",captionsSettingsDropdownLabel:"Spoken language",captionsSettingsDropdownInfoText:"Language that everyone on this call is speaking.",captionsSettingsConfirmButtonLabel:"Confirm",captionsSettingsCancelButtonLabel:"Cancel",captionsSettingsModalAriaLabel:"Captions Setting Modal",captionsSettingsCloseModalButtonAriaLabel:"Close Captions Setting",captionsBannerMoreButtonCallingLabel:"More",captionsBannerMoreButtonTooltip:"More options",captionsAvailableLanguageStrings:{"ar-ae":"Arabic - U.A.E.","ar-sa":"Arabic - Saudi Arabia","da-dk":"Danish","de-de":"German - Germany","en-au":"English - Australia","en-ca":"English - Canada","en-gb":"English - United Kingdom","en-in":"English - India","en-nz":"English - New Zealand","en-us":"English - United States","es-es":"Spanish - Spain (Modern Sort)","es-mx":"Spanish - Mexico","fi-fi":"Finnish","fr-ca":"French - Canada","fr-fr":"French - France","hi-in":"Hindi","it-it":"Italian - Italy","ja-jp":"Japanese","ko-kr":"Korean","nb-no":"Norwegian (Bokmål)","nl-be":"Dutch - Belgium","nl-nl":"Dutch - Netherlands","pl-pl":"Polish","pt-br":"Portuguese - Brazil","ru-ru":"Russian","sv-se":"Swedish","zh-cn":"Chinese - People's Republic of China","zh-hk":"Chinese - Hong Kong SAR","cs-cz":"Czech","pt-pt":"Portuguese - Portugal","tr-tr":"Turkish","vi-vn":"Vietnamese","th-th":"Thai","he-il":"Hebrew","cy-gb":"Welsh","uk-ua":"Ukrainian","el-gr":"Greek","hu-hu":"Hungarian","ro-ro":"Romanian","sk-sk":"Slovak","zh-tw":"Chinese - Taiwan"},captionsBannerSpinnerText:"Starting captions..."};var chat$c={chatListHeader:"In this chat",uploadFile:"Upload File"};var callWithChat$c={chatButtonLabel:"Chat",chatButtonNewMessageNotificationLabel:"New Message",chatButtonTooltipClosedWithMessageCount:"Show chat ({unreadMessagesCount} unread)",chatButtonTooltipClose:"Hide chat",chatButtonTooltipOpen:"Show chat",chatPaneTitle:"Chat",copyInviteLinkButtonLabel:"Copy invite link",copyInviteLinkActionedAriaLabel:"Invite link copied",dismissSidePaneButtonLabel:"Close",moreDrawerAudioDeviceMenuTitle:"Audio Device",moreDrawerButtonLabel:"More options",moreDrawerButtonTooltip:"More options",moreDrawerMicrophoneMenuTitle:"Microphone",moreDrawerSpeakerMenuTitle:"Speaker",moreDrawerCaptionsMenuTitle:"Live captions",moreDrawerSpokenLanguageMenuTitle:"Spoken language",peopleButtonLabel:"People",peopleButtonTooltipOpen:"Show participants",peopleButtonTooltipClose:"Hide participants",peoplePaneSubTitle:"In this call",peoplePaneTitle:"People",pictureInPictureTileAriaLabel:"Video Feeds. Click to return to call screen.",removeMenuLabel:"Remove",openDialpadButtonLabel:"Dial phone number",returnToCallButtonAriaDescription:"Return to Call",returnToCallButtonAriaLabel:"Back",peoplePaneAddPeopleButtonLabel:"Add People",dialpadStartCallButtonLabel:"Call",dialpadModalTitle:"Dial Phone Number",dialpadModalAriaLabel:"Dialpad",dialpadCloseModalButtonAriaLabel:"Close dialpad",openDtmfDialpadLabel:"Show dialpad",dtmfDialpadPlaceholderText:"Enter number"};var en_GB = {call:call$c,chat:chat$c,callWithChat:callWithChat$c};
18669
18642
 
@@ -19190,11 +19163,15 @@ const getCallEndReason = (call) => {
19190
19163
  *
19191
19164
  * @private
19192
19165
  */
19193
- const getCallCompositePage = (call, previousCall, unsupportedBrowserInfo) => {
19166
+ const getCallCompositePage = (call, previousCall, unsupportedBrowserInfo, transferCall) => {
19194
19167
  /* @conditional-compile-remove(unsupported-browser) */
19195
19168
  if (isUnsupportedEnvironment(unsupportedBrowserInfo.environmentInfo, unsupportedBrowserInfo.unsupportedBrowserVersionOptedIn)) {
19196
19169
  return 'unsupportedEnvironment';
19197
19170
  }
19171
+ /* @conditional-compile-remove(call-transfer) */
19172
+ if (transferCall !== undefined) {
19173
+ return 'transferring';
19174
+ }
19198
19175
  if (call) {
19199
19176
  // Must check for ongoing call *before* looking at any previous calls.
19200
19177
  // If the composite completes one call and joins another, the previous calls
@@ -20469,7 +20446,7 @@ const participantListMobileStyle = {
20469
20446
  /**
20470
20447
  * @private
20471
20448
  */
20472
- const displayNameStyles = {
20449
+ const displayNameStyles$1 = {
20473
20450
  root: {
20474
20451
  padding: '0.5rem',
20475
20452
  textOverflow: 'ellipsis',
@@ -20505,7 +20482,7 @@ const ParticipantListWithHeading = (props) => {
20505
20482
  React__default['default'].createElement(react.FocusZone, { className: participantListContainerStyle, shouldFocusOnMount: true },
20506
20483
  React__default['default'].createElement(ParticipantList, Object.assign({}, participantListProps, { styles: props.isMobile ? participantListMobileStyle : participantListStyle, onRenderAvatar: (userId, options) => (React__default['default'].createElement(React__default['default'].Fragment, null,
20507
20484
  React__default['default'].createElement(AvatarPersona, Object.assign({ "data-ui-id": "chat-composite-participant-custom-avatar", userId: userId }, options, { hidePersonaDetails: !!(options === null || options === void 0 ? void 0 : options.text) }, { dataProvider: onFetchAvatarPersonaData })),
20508
- (options === null || options === void 0 ? void 0 : options.text) && (React__default['default'].createElement(react.Text, { nowrap: true, styles: displayNameStyles }, options === null || options === void 0 ? void 0 : options.text)))), onFetchParticipantMenuItems: onFetchParticipantMenuItems, showParticipantOverflowTooltip: !props.isMobile })))));
20485
+ (options === null || options === void 0 ? void 0 : options.text) && (React__default['default'].createElement(react.Text, { nowrap: true, styles: displayNameStyles$1 }, options === null || options === void 0 ? void 0 : options.text)))), onFetchParticipantMenuItems: onFetchParticipantMenuItems, showParticipantOverflowTooltip: !props.isMobile })))));
20509
20486
  };
20510
20487
 
20511
20488
  // Copyright (c) Microsoft Corporation.
@@ -21340,6 +21317,11 @@ const getRole = (state) => { var _a; return (_a = state.call) === null || _a ===
21340
21317
  * @private
21341
21318
  */
21342
21319
  const getPage = (state) => state.page;
21320
+ /* @conditional-compile-remove(call-transfer) */
21321
+ /**
21322
+ * @private
21323
+ */
21324
+ const getTransferCall = (state) => state.acceptedTransferCallState;
21343
21325
  /**
21344
21326
  * @private
21345
21327
  */
@@ -24374,14 +24356,14 @@ const MediaGallery = (props) => {
24374
24356
  ? 'VerticalRight'
24375
24357
  : 'HorizontalBottom', [containerWidth, containerHeight]);
24376
24358
  const VideoGalleryMemoized = React.useMemo(() => {
24377
- var _a;
24378
- return (React__default['default'].createElement(VideoGallery, Object.assign({}, videoGalleryProps, { localVideoViewOptions: localVideoViewOptions$2, remoteVideoViewOptions: remoteVideoViewOptions, styles: VideoGalleryStyles, layout: layoutBasedOnTilePosition, showCameraSwitcherInLocalPreview: props.isMobile, localVideoCameraCycleButtonProps: cameraSwitcherProps, onRenderAvatar: onRenderAvatar,
24359
+ var _a, _b;
24360
+ return (React__default['default'].createElement(VideoGallery, Object.assign({}, videoGalleryProps, { localVideoViewOptions: localVideoViewOptions$2, remoteVideoViewOptions: remoteVideoViewOptions, styles: VideoGalleryStyles, layout: layoutBasedOnTilePosition, showCameraSwitcherInLocalPreview: props.isMobile, localVideoCameraCycleButtonProps: cameraSwitcherProps, onRenderAvatar: (_a = props.onRenderAvatar) !== null && _a !== void 0 ? _a : onRenderAvatar,
24379
24361
  /* @conditional-compile-remove(pinned-participants) */
24380
24362
  remoteVideoTileMenuOptions: remoteVideoTileMenuOptions,
24381
24363
  /* @conditional-compile-remove(vertical-gallery) */
24382
24364
  overflowGalleryPosition: overflowGalleryPosition,
24383
24365
  /* @conditional-compile-remove(click-to-call) */
24384
- localVideoTileSize: ((_a = props.localVideoTileOptions) === null || _a === void 0 ? void 0 : _a.position) === 'hidden'
24366
+ localVideoTileSize: ((_b = props.localVideoTileOptions) === null || _b === void 0 ? void 0 : _b.position) === 'hidden'
24385
24367
  ? 'hidden'
24386
24368
  : props.isMobile
24387
24369
  ? 'followDeviceOrientation'
@@ -24389,6 +24371,7 @@ const MediaGallery = (props) => {
24389
24371
  }, [
24390
24372
  videoGalleryProps,
24391
24373
  props.isMobile,
24374
+ props.onRenderAvatar,
24392
24375
  onRenderAvatar,
24393
24376
  cameraSwitcherProps,
24394
24377
  /* @conditional-compile-remove(pinned-participants) */ remoteVideoTileMenuOptions,
@@ -25796,6 +25779,139 @@ const outboundCallStringsTrampoline = (strings) => {
25796
25779
  return strings.outboundCallingNoticeString;
25797
25780
  };
25798
25781
 
25782
+ // Copyright (c) Microsoft Corporation.
25783
+ // Licensed under the MIT license.
25784
+ /**
25785
+ * @private
25786
+ */
25787
+ const tileContainerStyles = {
25788
+ position: 'absolute',
25789
+ top: '0',
25790
+ left: '0',
25791
+ width: '100%',
25792
+ height: '100%',
25793
+ minWidth: '100%',
25794
+ minHeight: '100%',
25795
+ objectPosition: 'center',
25796
+ objectFit: 'cover',
25797
+ zIndex: 0
25798
+ };
25799
+ /**
25800
+ * @private
25801
+ */
25802
+ const tileContentStyles = {
25803
+ width: '100%',
25804
+ position: 'absolute',
25805
+ top: '50%',
25806
+ transform: 'translate(0, -50%)',
25807
+ display: 'flex',
25808
+ justifyContent: 'center'
25809
+ };
25810
+ /**
25811
+ * @private
25812
+ */
25813
+ const defaultPersonaStyles = {
25814
+ root: { margin: 'auto' }
25815
+ };
25816
+ /**
25817
+ * @private
25818
+ */
25819
+ const displayNameStyles = { textAlign: 'center', fontSize: '1.5rem', fontWeight: 400 };
25820
+ /**
25821
+ * @private
25822
+ */
25823
+ const spinnerStyles = { circle: { borderWidth: '0.125rem' } };
25824
+ /**
25825
+ * @private
25826
+ */
25827
+ const statusTextStyles = { textAlign: 'center', fontSize: '1rem' };
25828
+
25829
+ // Copyright (c) Microsoft Corporation.
25830
+ /**
25831
+ * @private
25832
+ */
25833
+ const TransferPage = (props) => {
25834
+ var _a, _b, _c, _d, _e;
25835
+ const errorBarProps = usePropsFor$1(ErrorBar);
25836
+ const strings = useLocale().strings.call;
25837
+ const remoteParticipants = useSelector$1(getRemoteParticipants);
25838
+ /* @conditional-compile-remove(call-transfer) */
25839
+ const transferCall = useSelector$1(getTransferCall);
25840
+ // Reduce the controls shown when mobile view is enabled.
25841
+ const callControlOptions = props.mobileView
25842
+ ? reduceCallControlsForMobile((_a = props.options) === null || _a === void 0 ? void 0 : _a.callControls)
25843
+ : (_b = props.options) === null || _b === void 0 ? void 0 : _b.callControls;
25844
+ /* @conditional-compile-remove(call-transfer) */
25845
+ // page subject is which should be participant shown in the transfer page depending on the transfer call state
25846
+ const pageSubject = React.useMemo(() => {
25847
+ if (transferCall && ['Ringing', 'Connected'].includes(transferCall.state)) {
25848
+ return 'transferTarget';
25849
+ }
25850
+ return 'transferor';
25851
+ }, [transferCall]);
25852
+ const transferor = React.useMemo(() => { var _a; return (remoteParticipants ? (_a = Object.values(remoteParticipants)) === null || _a === void 0 ? void 0 : _a[0] : undefined); }, [remoteParticipants]);
25853
+ /* @conditional-compile-remove(call-transfer) */
25854
+ const transferTarget = React.useMemo(() => { var _a; return ((transferCall === null || transferCall === void 0 ? void 0 : transferCall.remoteParticipants) ? (_a = Object.values(transferCall.remoteParticipants)) === null || _a === void 0 ? void 0 : _a[0] : undefined); }, [transferCall]);
25855
+ let transferTileParticipant = transferor;
25856
+ /* @conditional-compile-remove(call-transfer) */
25857
+ if (pageSubject === 'transferTarget') {
25858
+ transferTileParticipant = transferTarget;
25859
+ }
25860
+ let transferParticipantDisplayName = (_c = transferor === null || transferor === void 0 ? void 0 : transferor.displayName) !== null && _c !== void 0 ? _c :
25861
+ /* @conditional-compile-remove(call-transfer) */ strings.transferPageUnknownTransferorDisplayName;
25862
+ /* @conditional-compile-remove(call-transfer) */
25863
+ if (pageSubject === 'transferTarget') {
25864
+ transferParticipantDisplayName =
25865
+ (_d = transferTarget === null || transferTarget === void 0 ? void 0 : transferTarget.displayName) !== null && _d !== void 0 ? _d : strings.transferPageUnknownTransferTargetDisplayName;
25866
+ }
25867
+ return (React__default['default'].createElement(CallArrangement, { complianceBannerProps: { strings },
25868
+ // Ignore errors from before current call. This avoids old errors from showing up when a user re-joins a call.
25869
+ errorBarProps: ((_e = props.options) === null || _e === void 0 ? void 0 : _e.errorBar) !== false && Object.assign(Object.assign({}, errorBarProps), { ignorePremountErrors: true }), callControlProps: {
25870
+ options: callControlOptions,
25871
+ increaseFlyoutItemSize: props.mobileView
25872
+ }, mobileView: props.mobileView, modalLayerHostId: props.modalLayerHostId, onRenderGalleryContent: () => (React__default['default'].createElement(TransferTile, { userId: transferTileParticipant ? toFlatCommunicationIdentifier(transferTileParticipant === null || transferTileParticipant === void 0 ? void 0 : transferTileParticipant.identifier) : undefined, displayName: transferParticipantDisplayName, initialsName: transferParticipantDisplayName,
25873
+ /* @conditional-compile-remove(call-transfer) */
25874
+ statusText: pageSubject === 'transferTarget'
25875
+ ? strings.transferPageTransferTargetText
25876
+ : strings.transferPageTransferorText, onRenderAvatar: props.onRenderAvatar, onFetchAvatarPersonaData: props.onFetchAvatarPersonaData })), dataUiId: 'transfer-page', updateSidePaneRenderer: props.updateSidePaneRenderer, mobileChatTabHeader: props.mobileChatTabHeader }));
25877
+ };
25878
+ const TransferTile = (props) => {
25879
+ const { displayName, initialsName, userId, onRenderAvatar, onFetchAvatarPersonaData, statusText } = props;
25880
+ const [personaSize, setPersonaSize] = React.useState();
25881
+ const tileRef = React.useRef(null);
25882
+ const observer = React.useRef(new ResizeObserver((entries) => {
25883
+ const { width, height } = entries[0].contentRect;
25884
+ const personaSize = Math.min(width, height) / 2;
25885
+ setPersonaSize(Math.max(Math.min(personaSize, 150), 32));
25886
+ }));
25887
+ React.useLayoutEffect(() => {
25888
+ if (tileRef.current) {
25889
+ observer.current.observe(tileRef.current);
25890
+ }
25891
+ const currentObserver = observer.current;
25892
+ return () => currentObserver.disconnect();
25893
+ }, [observer, tileRef]);
25894
+ const placeholderOptions = React.useMemo(() => ({
25895
+ userId,
25896
+ text: initialsName !== null && initialsName !== void 0 ? initialsName : displayName,
25897
+ coinSize: personaSize,
25898
+ styles: defaultPersonaStyles,
25899
+ hidePersonaDetails: true
25900
+ }), [userId, initialsName, displayName, personaSize]);
25901
+ const defaultOnRenderAvatar = React.useCallback(() => {
25902
+ return personaSize ? React__default['default'].createElement(AvatarPersona, Object.assign({}, placeholderOptions, { dataProvider: onFetchAvatarPersonaData })) : React__default['default'].createElement(React__default['default'].Fragment, null);
25903
+ }, [placeholderOptions, onFetchAvatarPersonaData, personaSize]);
25904
+ const defaultAvatar = React.useMemo(() => defaultOnRenderAvatar(), [defaultOnRenderAvatar]);
25905
+ return (React__default['default'].createElement("div", { ref: tileRef, className: react.mergeStyles(tileContainerStyles), "data-is-focusable": true },
25906
+ React__default['default'].createElement(react.Stack, { className: react.mergeStyles(tileContentStyles), tokens: { childrenGap: '1rem' } },
25907
+ React__default['default'].createElement(react.Stack, { horizontalAlign: "center", tokens: { childrenGap: '0.5rem' } },
25908
+ onRenderAvatar ? onRenderAvatar(userId !== null && userId !== void 0 ? userId : '', placeholderOptions, defaultOnRenderAvatar) : defaultAvatar,
25909
+ React__default['default'].createElement(react.Text, { className: react.mergeStyles(displayNameStyles) }, displayName)),
25910
+ React__default['default'].createElement(react.Stack, { horizontal: true, horizontalAlign: "center", verticalAlign: "center", tokens: { childrenGap: '0.5rem' } },
25911
+ React__default['default'].createElement(react.Spinner, { size: react.SpinnerSize.large, className: react.mergeStyles(spinnerStyles) }),
25912
+ React__default['default'].createElement(react.Text, { className: react.mergeStyles(statusTextStyles) }, statusText)))));
25913
+ };
25914
+
25799
25915
  // Copyright (c) Microsoft Corporation.
25800
25916
  /**
25801
25917
  * styles for hold pane resume button
@@ -26101,6 +26217,10 @@ const MainScreen = (props) => {
26101
26217
  case 'lobby':
26102
26218
  pageElement = (React__default['default'].createElement(LobbyPage, { mobileView: props.mobileView, modalLayerHostId: props.modalLayerHostId, options: props.options, updateSidePaneRenderer: setSidePaneRenderer, mobileChatTabHeader: props.mobileChatTabHeader }));
26103
26219
  break;
26220
+ /* @conditional-compile-remove(call-transfer) */
26221
+ case 'transferring':
26222
+ pageElement = (React__default['default'].createElement(TransferPage, { mobileView: props.mobileView, modalLayerHostId: props.modalLayerHostId, options: props.options, updateSidePaneRenderer: setSidePaneRenderer, mobileChatTabHeader: props.mobileChatTabHeader, onRenderAvatar: onRenderAvatar, onFetchAvatarPersonaData: onFetchAvatarPersonaData }));
26223
+ break;
26104
26224
  case 'call':
26105
26225
  pageElement = (React__default['default'].createElement(CallPage, { onRenderAvatar: onRenderAvatar, callInvitationURL: callInvitationUrl, onFetchAvatarPersonaData: onFetchAvatarPersonaData, onFetchParticipantMenuItems: onFetchParticipantMenuItems, mobileView: props.mobileView, modalLayerHostId: props.modalLayerHostId, options: props.options, updateSidePaneRenderer: setSidePaneRenderer, mobileChatTabHeader: props.mobileChatTabHeader }));
26106
26226
  break;
@@ -26403,8 +26523,15 @@ class CallContext {
26403
26523
  environmentInfo: this.state.environmentInfo,
26404
26524
  unsupportedBrowserVersionOptedIn: this.state.unsupportedBrowserVersionsAllowed
26405
26525
  };
26526
+ /* @conditional-compile-remove(call-transfer) */
26527
+ const latestAcceptedTransfer = (call === null || call === void 0 ? void 0 : call.transferFeature.acceptedTransfers)
26528
+ ? findLatestAcceptedTransfer(call.transferFeature.acceptedTransfers)
26529
+ : undefined;
26530
+ /* @conditional-compile-remove(call-transfer) */
26531
+ const transferCall = latestAcceptedTransfer ? clientState.calls[latestAcceptedTransfer.callId] : undefined;
26406
26532
  const newPage = getCallCompositePage(call, latestEndedCall,
26407
- /* @conditional-compile-remove(unsupported-browser) */ environmentInfo);
26533
+ /* @conditional-compile-remove(unsupported-browser) */ environmentInfo,
26534
+ /* @conditional-compile-remove(call-transfer) */ transferCall);
26408
26535
  if (!IsCallEndedPage(oldPage) && IsCallEndedPage(newPage)) {
26409
26536
  this.emitter.emit('callEnded', { callId: this.callId });
26410
26537
  // Reset the callId to undefined as the call has ended.
@@ -26412,12 +26539,6 @@ class CallContext {
26412
26539
  // Make sure that the call is set to undefined in the state.
26413
26540
  call = undefined;
26414
26541
  }
26415
- /* @conditional-compile-remove(call-transfer) */
26416
- const latestAcceptedTransfer = (call === null || call === void 0 ? void 0 : call.transferFeature.acceptedTransfers)
26417
- ? findLatestAcceptedTransfer(call.transferFeature.acceptedTransfers)
26418
- : undefined;
26419
- /* @conditional-compile-remove(call-transfer) */
26420
- const transferCall = latestAcceptedTransfer ? clientState.calls[latestAcceptedTransfer.callId] : undefined;
26421
26542
  if (this.state.page) {
26422
26543
  this.setState(Object.assign(Object.assign({}, this.state), { userId: clientState.userId, displayName: (_a = clientState.callAgent) === null || _a === void 0 ? void 0 : _a.displayName, call, page: newPage, endedCall: latestEndedCall, devices: clientState.deviceManager, latestErrors: clientState.latestErrors, cameraStatus: (call === null || call === void 0 ? void 0 : call.localVideoStreams.find((s) => s.mediaStreamType === 'Video')) ||
26423
26544
  clientState.deviceManager.unparentedViews.find((s) => s.mediaStreamType === 'Video')