@azure/communication-react 1.17.0-alpha-202405170014 → 1.17.0-alpha-202405220013

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 (105) hide show
  1. package/dist/dist-cjs/communication-react/{ChatMessageComponentAsRichTextEditBox-BLFNaheX.js → ChatMessageComponentAsRichTextEditBox-CDsn-zU7.js} +22 -17
  2. package/dist/dist-cjs/communication-react/ChatMessageComponentAsRichTextEditBox-CDsn-zU7.js.map +1 -0
  3. package/dist/dist-cjs/communication-react/{RichTextSendBoxWrapper-BhTpuspw.js → RichTextSendBoxWrapper-Dm4S2bpT.js} +6 -8
  4. package/dist/dist-cjs/communication-react/{RichTextSendBoxWrapper-BhTpuspw.js.map → RichTextSendBoxWrapper-Dm4S2bpT.js.map} +1 -1
  5. package/dist/dist-cjs/communication-react/{index-C9I6Mcil.js → index-DO36MBbq.js} +907 -480
  6. package/dist/dist-cjs/communication-react/index-DO36MBbq.js.map +1 -0
  7. package/dist/dist-cjs/communication-react/index.js +5 -7
  8. package/dist/dist-cjs/communication-react/index.js.map +1 -1
  9. package/dist/dist-esm/acs-ui-common/src/telemetryVersion.js +1 -1
  10. package/dist/dist-esm/acs-ui-common/src/telemetryVersion.js.map +1 -1
  11. package/dist/dist-esm/react-components/src/components/ChatMessage/MyMessageComponents/ChatMessageComponentAsRichTextEditBox.js +17 -10
  12. package/dist/dist-esm/react-components/src/components/ChatMessage/MyMessageComponents/ChatMessageComponentAsRichTextEditBox.js.map +1 -1
  13. package/dist/dist-esm/react-components/src/components/InputBoxComponent.js +1 -1
  14. package/dist/dist-esm/react-components/src/components/InputBoxComponent.js.map +1 -1
  15. package/dist/dist-esm/react-components/src/components/MessageStatusIcon.js +1 -3
  16. package/dist/dist-esm/react-components/src/components/MessageStatusIcon.js.map +1 -1
  17. package/dist/dist-esm/react-components/src/components/ReactionButton.js +1 -1
  18. package/dist/dist-esm/react-components/src/components/ReactionButton.js.map +1 -1
  19. package/dist/dist-esm/react-components/src/components/RichTextEditor/Plugins/ContextMenuPlugin.d.ts +9 -0
  20. package/dist/dist-esm/react-components/src/components/RichTextEditor/Plugins/ContextMenuPlugin.js +29 -0
  21. package/dist/dist-esm/react-components/src/components/RichTextEditor/Plugins/ContextMenuPlugin.js.map +1 -0
  22. package/dist/dist-esm/react-components/src/components/RichTextEditor/Plugins/CopyPastePlugin.d.ts +1 -1
  23. package/dist/dist-esm/react-components/src/components/RichTextEditor/Plugins/CopyPastePlugin.js +15 -20
  24. package/dist/dist-esm/react-components/src/components/RichTextEditor/Plugins/CopyPastePlugin.js.map +1 -1
  25. package/dist/dist-esm/react-components/src/components/RichTextEditor/Plugins/KeyboardInputPlugin.d.ts +12 -0
  26. package/dist/dist-esm/react-components/src/components/RichTextEditor/Plugins/KeyboardInputPlugin.js +23 -0
  27. package/dist/dist-esm/react-components/src/components/RichTextEditor/Plugins/KeyboardInputPlugin.js.map +1 -0
  28. package/dist/dist-esm/react-components/src/components/RichTextEditor/Plugins/PlaceholderPlugin.d.ts +15 -0
  29. package/dist/dist-esm/react-components/src/components/RichTextEditor/Plugins/PlaceholderPlugin.js +39 -0
  30. package/dist/dist-esm/react-components/src/components/RichTextEditor/Plugins/PlaceholderPlugin.js.map +1 -0
  31. package/dist/dist-esm/react-components/src/components/RichTextEditor/Plugins/RichTextToolbarPlugin.d.ts +24 -0
  32. package/dist/dist-esm/react-components/src/components/RichTextEditor/Plugins/RichTextToolbarPlugin.js +66 -0
  33. package/dist/dist-esm/react-components/src/components/RichTextEditor/Plugins/RichTextToolbarPlugin.js.map +1 -0
  34. package/dist/dist-esm/react-components/src/components/RichTextEditor/Plugins/TableEditContextMenuProvider.d.ts +20 -0
  35. package/dist/dist-esm/react-components/src/components/RichTextEditor/Plugins/TableEditContextMenuProvider.js +46 -0
  36. package/dist/dist-esm/react-components/src/components/RichTextEditor/Plugins/TableEditContextMenuProvider.js.map +1 -0
  37. package/dist/dist-esm/react-components/src/components/RichTextEditor/Plugins/UpdateContentPlugin.d.ts +29 -0
  38. package/dist/dist-esm/react-components/src/components/RichTextEditor/Plugins/UpdateContentPlugin.js +71 -0
  39. package/dist/dist-esm/react-components/src/components/RichTextEditor/Plugins/UpdateContentPlugin.js.map +1 -0
  40. package/dist/dist-esm/react-components/src/components/RichTextEditor/RichTextEditor.d.ts +7 -10
  41. package/dist/dist-esm/react-components/src/components/RichTextEditor/RichTextEditor.js +170 -91
  42. package/dist/dist-esm/react-components/src/components/RichTextEditor/RichTextEditor.js.map +1 -1
  43. package/dist/dist-esm/react-components/src/components/RichTextEditor/RichTextInputBoxComponent.d.ts +0 -1
  44. package/dist/dist-esm/react-components/src/components/RichTextEditor/RichTextInputBoxComponent.js +29 -12
  45. package/dist/dist-esm/react-components/src/components/RichTextEditor/RichTextInputBoxComponent.js.map +1 -1
  46. package/dist/dist-esm/react-components/src/components/RichTextEditor/RichTextSendBox.js +12 -46
  47. package/dist/dist-esm/react-components/src/components/RichTextEditor/RichTextSendBox.js.map +1 -1
  48. package/dist/dist-esm/react-components/src/components/RichTextEditor/Toolbar/RichTextToolbar.d.ts +19 -0
  49. package/dist/dist-esm/react-components/src/components/RichTextEditor/Toolbar/RichTextToolbar.js +209 -0
  50. package/dist/dist-esm/react-components/src/components/RichTextEditor/Toolbar/RichTextToolbar.js.map +1 -0
  51. package/dist/dist-esm/react-components/src/components/RichTextEditor/Toolbar/Table/RichTextInsertTableCommandBarItem.d.ts +7 -0
  52. package/dist/dist-esm/react-components/src/components/RichTextEditor/Toolbar/Table/RichTextInsertTableCommandBarItem.js +49 -0
  53. package/dist/dist-esm/react-components/src/components/RichTextEditor/Toolbar/Table/RichTextInsertTableCommandBarItem.js.map +1 -0
  54. package/dist/dist-esm/react-components/src/components/RichTextEditor/{Buttons → Toolbar}/Table/RichTextInsertTablePane.d.ts +2 -2
  55. package/dist/dist-esm/react-components/src/components/RichTextEditor/{Buttons → Toolbar}/Table/RichTextInsertTablePane.js +3 -3
  56. package/dist/dist-esm/react-components/src/components/RichTextEditor/Toolbar/Table/RichTextInsertTablePane.js.map +1 -0
  57. package/dist/dist-esm/react-components/src/components/RichTextEditor/Toolbar/Table/RichTextToolbarTableIcon.d.ts +6 -0
  58. package/dist/dist-esm/react-components/src/components/RichTextEditor/Toolbar/Table/RichTextToolbarTableIcon.js +13 -0
  59. package/dist/dist-esm/react-components/src/components/RichTextEditor/Toolbar/Table/RichTextToolbarTableIcon.js.map +1 -0
  60. package/dist/dist-esm/react-components/src/components/TextFieldWithMention/TextFieldWithMention.js +1 -1
  61. package/dist/dist-esm/react-components/src/components/TextFieldWithMention/TextFieldWithMention.js.map +1 -1
  62. package/dist/dist-esm/react-components/src/components/VideoEffects/VideoBackgroundEffectsPicker.d.ts +5 -7
  63. package/dist/dist-esm/react-components/src/components/VideoEffects/VideoBackgroundEffectsPicker.js +1 -1
  64. package/dist/dist-esm/react-components/src/components/VideoEffects/VideoBackgroundEffectsPicker.js.map +1 -1
  65. package/dist/dist-esm/react-components/src/components/VideoEffects/VideoEffectsItem.d.ts +5 -3
  66. package/dist/dist-esm/react-components/src/components/VideoEffects/VideoEffectsItem.js +2 -8
  67. package/dist/dist-esm/react-components/src/components/VideoEffects/VideoEffectsItem.js.map +1 -1
  68. package/dist/dist-esm/react-components/src/components/styles/MessageThread.styles.js +3 -1
  69. package/dist/dist-esm/react-components/src/components/styles/MessageThread.styles.js.map +1 -1
  70. package/dist/dist-esm/react-components/src/components/styles/RichTextEditor.styles.d.ts +3 -3
  71. package/dist/dist-esm/react-components/src/components/styles/RichTextEditor.styles.js +4 -5
  72. package/dist/dist-esm/react-components/src/components/styles/RichTextEditor.styles.js.map +1 -1
  73. package/dist/dist-esm/react-components/src/components/utils/RichTextEditorUtils.d.ts +37 -0
  74. package/dist/dist-esm/react-components/src/components/utils/RichTextEditorUtils.js +60 -0
  75. package/dist/dist-esm/react-components/src/components/utils/RichTextEditorUtils.js.map +1 -0
  76. package/dist/dist-esm/react-components/src/components/utils/RichTextTableUtils.d.ts +11 -0
  77. package/dist/dist-esm/react-components/src/components/utils/RichTextTableUtils.js +95 -0
  78. package/dist/dist-esm/react-components/src/components/utils/RichTextTableUtils.js.map +1 -1
  79. package/dist/dist-esm/react-components/src/components/utils.d.ts +1 -1
  80. package/dist/dist-esm/react-components/src/components/utils.js +1 -3
  81. package/dist/dist-esm/react-components/src/components/utils.js.map +1 -1
  82. package/dist/dist-esm/react-composites/src/composites/CallComposite/components/SidePane/useVideoEffectsPane.js +7 -4
  83. package/dist/dist-esm/react-composites/src/composites/CallComposite/components/SidePane/useVideoEffectsPane.js.map +1 -1
  84. package/dist/dist-esm/react-composites/src/composites/common/VideoEffectsPane.d.ts +4 -1
  85. package/dist/dist-esm/react-composites/src/composites/common/VideoEffectsPane.js +3 -3
  86. package/dist/dist-esm/react-composites/src/composites/common/VideoEffectsPane.js.map +1 -1
  87. package/package.json +6 -8
  88. package/dist/dist-cjs/communication-react/ChatMessageComponentAsRichTextEditBox-BLFNaheX.js.map +0 -1
  89. package/dist/dist-cjs/communication-react/index-C9I6Mcil.js.map +0 -1
  90. package/dist/dist-esm/react-components/src/components/RichTextEditor/Buttons/RichTextRibbonButtons.d.ts +0 -7
  91. package/dist/dist-esm/react-components/src/components/RichTextEditor/Buttons/RichTextRibbonButtons.js +0 -86
  92. package/dist/dist-esm/react-components/src/components/RichTextEditor/Buttons/RichTextRibbonButtons.js.map +0 -1
  93. package/dist/dist-esm/react-components/src/components/RichTextEditor/Buttons/Table/RichTextInsertTableButton.d.ts +0 -7
  94. package/dist/dist-esm/react-components/src/components/RichTextEditor/Buttons/Table/RichTextInsertTableButton.js +0 -55
  95. package/dist/dist-esm/react-components/src/components/RichTextEditor/Buttons/Table/RichTextInsertTableButton.js.map +0 -1
  96. package/dist/dist-esm/react-components/src/components/RichTextEditor/Buttons/Table/RichTextInsertTablePane.js.map +0 -1
  97. package/dist/dist-esm/react-components/src/components/RichTextEditor/Buttons/Table/RichTextTableContextMenu.d.ts +0 -8
  98. package/dist/dist-esm/react-components/src/components/RichTextEditor/Buttons/Table/RichTextTableContextMenu.js +0 -66
  99. package/dist/dist-esm/react-components/src/components/RichTextEditor/Buttons/Table/RichTextTableContextMenu.js.map +0 -1
  100. package/dist/dist-esm/react-components/src/components/RichTextEditor/Buttons/Table/insertTableAction.d.ts +0 -9
  101. package/dist/dist-esm/react-components/src/components/RichTextEditor/Buttons/Table/insertTableAction.js +0 -56
  102. package/dist/dist-esm/react-components/src/components/RichTextEditor/Buttons/Table/insertTableAction.js.map +0 -1
  103. package/dist/dist-esm/react-components/src/components/utils/RichTextEditorStringsUtils.d.ts +0 -15
  104. package/dist/dist-esm/react-components/src/components/utils/RichTextEditorStringsUtils.js +0 -39
  105. package/dist/dist-esm/react-components/src/components/utils/RichTextEditorStringsUtils.js.map +0 -1
@@ -15,12 +15,10 @@ var reactComponents = require('@fluentui/react-components');
15
15
  var useDebounce = require('use-debounce');
16
16
  var reactFileTypeIcons = require('@fluentui/react-file-type-icons');
17
17
  var react$1 = require('@griffel/react');
18
- var roosterjsEditorPlugins = require('roosterjs-editor-plugins');
19
- var roosterjsEditorCore = require('roosterjs-editor-core');
20
- var roosterjsEditorTypesCompatible = require('roosterjs-editor-types-compatible');
21
- var roosterjsReact = require('roosterjs-react');
22
- var roosterjsEditorDom = require('roosterjs-editor-dom');
23
- var roosterjsEditorApi = require('roosterjs-editor-api');
18
+ var roosterjsContentModelCore = require('roosterjs-content-model-core');
19
+ var roosterjsContentModelDom = require('roosterjs-content-model-dom');
20
+ var roosterjsContentModelPlugins = require('roosterjs-content-model-plugins');
21
+ var roosterjsContentModelApi = require('roosterjs-content-model-api');
24
22
  var reactChat = require('@fluentui-contrib/react-chat');
25
23
  var uuid = require('uuid');
26
24
  var parse = require('html-react-parser');
@@ -176,7 +174,7 @@ function getDefaultExportFromCjs (x) {
176
174
  // Copyright (c) Microsoft Corporation.
177
175
  // Licensed under the MIT License.
178
176
  // GENERATED FILE. DO NOT EDIT MANUALLY.
179
- var telemetryVersion = '1.17.0-alpha-202405170014';
177
+ var telemetryVersion = '1.17.0-alpha-202405220013';
180
178
 
181
179
 
182
180
  var telemetryVersion$1 = /*@__PURE__*/getDefaultExportFromCjs(telemetryVersion);
@@ -6132,9 +6130,7 @@ const SAFARI_COMPOSITION_KEYCODE = 229;
6132
6130
  */
6133
6131
  const isEnterKeyEventFromCompositionSession = (e) =>
6134
6132
  // Uses KeyCode 229 and which code 229 to determine if the press of the enter key is from a composition session or not (Safari only)
6135
- e.nativeEvent.isComposing ||
6136
- e.nativeEvent.keyCode === SAFARI_COMPOSITION_KEYCODE ||
6137
- e.nativeEvent.which === SAFARI_COMPOSITION_KEYCODE;
6133
+ e.isComposing || e.keyCode === SAFARI_COMPOSITION_KEYCODE || e.which === SAFARI_COMPOSITION_KEYCODE;
6138
6134
  /**
6139
6135
  * @private
6140
6136
  */
@@ -8147,7 +8143,7 @@ const TextFieldWithMention = (props) => {
8147
8143
  // as onMouseUp can be triggered before or after onSelect event
8148
8144
  // because its order depends on mouse events not selection.
8149
8145
  setShouldHandleOnMouseDownDuringSelect(false);
8150
- if (isEnterKeyEventFromCompositionSession(ev)) {
8146
+ if (isEnterKeyEventFromCompositionSession(ev.nativeEvent)) {
8151
8147
  return;
8152
8148
  }
8153
8149
  let isActiveSuggestionIndexUpdated = false;
@@ -8617,7 +8613,7 @@ const InputBoxComponent = (props) => {
8617
8613
  }
8618
8614
  });
8619
8615
  const onTextFieldKeyDown = React.useCallback((ev) => {
8620
- if (isEnterKeyEventFromCompositionSession(ev)) {
8616
+ if (isEnterKeyEventFromCompositionSession(ev.nativeEvent)) {
8621
8617
  return;
8622
8618
  }
8623
8619
  if (ev.key === 'Enter' && (ev.shiftKey === false || !supportNewline)) {
@@ -9949,7 +9945,6 @@ const richTextEditorWrapperStyle = (theme, addTopOffset) => {
9949
9945
  paddingTop: `${addTopOffset ? '0.5rem' : '0'}`,
9950
9946
  paddingInlineStart: `0.75rem`,
9951
9947
  paddingInlineEnd: `0.75rem`,
9952
- lineHeight: '1.25rem',
9953
9948
  maxWidth: '100%',
9954
9949
  color: theme.palette.neutralPrimary,
9955
9950
  '& table': {
@@ -10050,7 +10045,7 @@ const ribbonButtonRootStyles = (theme) => {
10050
10045
  /**
10051
10046
  * @private
10052
10047
  */
10053
- const ribbonButtonStyle = (theme) => {
10048
+ const toolbarButtonStyle = (theme) => {
10054
10049
  return {
10055
10050
  icon: { color: theme.palette.neutralPrimary, height: 'auto' },
10056
10051
  menuIcon: { color: theme.palette.neutralPrimary, height: 'auto' },
@@ -10071,7 +10066,7 @@ const rootRibbonTableButtonStyle = (theme) => {
10071
10066
  /**
10072
10067
  * @private
10073
10068
  */
10074
- const ribbonTableButtonStyle = (theme) => {
10069
+ const toolbarTableButtonStyle = (theme) => {
10075
10070
  return {
10076
10071
  icon: { height: 'auto' },
10077
10072
  menuIcon: { height: 'auto' },
@@ -10119,9 +10114,9 @@ const ribbonDividerStyle = (theme) => {
10119
10114
  /**
10120
10115
  * @private
10121
10116
  */
10122
- const ribbonStyle = {
10117
+ const richTextToolbarStyle = {
10123
10118
  // Override for the default white color of the Ribbon component
10124
- root: { backgroundColor: 'transparent' }
10119
+ root: { backgroundColor: 'transparent', padding: '0px' }
10125
10120
  };
10126
10121
  /**
10127
10122
  * @private
@@ -10211,55 +10206,217 @@ const tableContextMenuIconStyles = react.mergeStyles({
10211
10206
 
10212
10207
  // Copyright (c) Microsoft Corporation.
10213
10208
  // Licensed under the MIT License.
10214
- // This file uses RoosterJS React package implementation with updates to change table's size and remove styles
10215
- /**
10216
- * Insert table into editor at current selection
10217
- * @param editor The editor instance
10218
- * @param columns Number of columns in table
10219
- * @param rows Number of rows in table
10220
- */
10221
- const insertTable = (editor, columns, rows) => {
10222
- const document = editor.getDocument();
10223
- const table = document.createElement('table');
10224
- for (let i = 0; i < rows; i++) {
10225
- const tr = document.createElement('tr');
10226
- table.appendChild(tr);
10227
- for (let j = 0; j < columns; j++) {
10228
- const td = document.createElement('td');
10229
- tr.appendChild(td);
10230
- td.appendChild(document.createElement('br'));
10231
- // set the width as otherwise insets doesn't work well in table
10232
- // review if it's needed when content model packages are used
10233
- td.style.width = getTableCellWidth(columns);
10234
- }
10235
- }
10236
- editor.focus();
10237
- editor.addUndoSnapshot(() => {
10238
- const vTable = new roosterjsEditorDom.VTable(table);
10239
- vTable.writeBack();
10240
- editor.insertNode(table);
10241
- const nextElementAfterTable = table.nextElementSibling;
10242
- // insert br only if there is no next element after the table
10243
- if (nextElementAfterTable === null) {
10244
- // insert br after the table
10245
- // so users can easily input content after table
10246
- editor.select(new roosterjsEditorDom.Position(table, roosterjsEditorTypesCompatible.CompatiblePositionType.After));
10247
- editor.insertNode(document.createElement('br'));
10248
- }
10249
- editor.runAsync((editor) => editor.select(new roosterjsEditorDom.Position(table, roosterjsEditorTypesCompatible.CompatiblePositionType.Begin).normalize()));
10250
- }, roosterjsEditorTypesCompatible.CompatibleChangeSource.Format, undefined /* canUndoByBackspace */, {
10251
- formatApiName: 'insertTable'
10252
- });
10209
+ /**
10210
+ * Plugin event type for RoosterJS plugins
10211
+ * @private
10212
+ */
10213
+ var PluginEventType;
10214
+ (function (PluginEventType) {
10215
+ PluginEventType["EditorReady"] = "editorReady";
10216
+ PluginEventType["BeforeDispose"] = "beforeDispose";
10217
+ PluginEventType["ContentChanged"] = "contentChanged";
10218
+ PluginEventType["Input"] = "input";
10219
+ PluginEventType["KeyDown"] = "keyDown";
10220
+ PluginEventType["BeforePaste"] = "beforePaste";
10221
+ PluginEventType["ZoomChanged"] = "zoomChanged";
10222
+ PluginEventType["MouseUp"] = "mouseUp";
10223
+ })(PluginEventType || (PluginEventType = {}));
10224
+ /**
10225
+ * ContentChanged event source for RoosterJS
10226
+ * @private
10227
+ */
10228
+ var ContentChangedEventSource;
10229
+ (function (ContentChangedEventSource) {
10230
+ ContentChangedEventSource["Paste"] = "Paste";
10231
+ })(ContentChangedEventSource || (ContentChangedEventSource = {}));
10232
+ /**
10233
+ * Applies the border format to the specified element.
10234
+ * If the element is an HTMLTableCellElement, it skips setting editing info
10235
+ * and to use classes instead of inline styles.
10236
+ * For all other cases, the default format applier is used.
10237
+ */
10238
+ const borderApplier = (format, element, context) => {
10239
+ if (element instanceof HTMLTableCellElement) ;
10240
+ else if (context.defaultFormatAppliers.border) {
10241
+ // apply default formats for all other cases
10242
+ context.defaultFormatAppliers.border(format, element, context);
10243
+ }
10253
10244
  };
10254
- function getTableCellWidth(columns) {
10255
- if (columns <= 4) {
10256
- return '120px';
10245
+ /**
10246
+ * Applies the dataset format to the given HTML element.
10247
+ * If the element is an HTMLTableElement, it skips setting editing info
10248
+ * and to use classes instead of inline styles.
10249
+ * For all other cases, it applies the default formats.
10250
+ */
10251
+ const dataSetApplier = (format, element, context) => {
10252
+ if (element instanceof HTMLTableElement) ;
10253
+ else if (context.defaultFormatAppliers.dataset) {
10254
+ // apply default formats for all other cases
10255
+ context.defaultFormatAppliers.dataset(format, element, context);
10257
10256
  }
10258
- else if (columns <= 6) {
10259
- return '100px';
10257
+ };
10258
+
10259
+ /**
10260
+ * CopyPastePlugin is a plugin for handling copy and paste events in the editor.
10261
+ */
10262
+ class CopyPastePlugin {
10263
+ constructor() {
10264
+ this.editor = null;
10260
10265
  }
10261
- else {
10262
- return '70px';
10266
+ getName() {
10267
+ return 'CopyPastePlugin';
10268
+ }
10269
+ initialize(editor) {
10270
+ this.editor = editor;
10271
+ }
10272
+ dispose() { }
10273
+ onPluginEvent(event) {
10274
+ removeImageElement(event);
10275
+ if (this.editor !== null && !this.editor.isDisposed()) {
10276
+ // scroll the editor to the correct position after pasting content
10277
+ scrollToBottomAfterContentPaste(event, this.editor);
10278
+ }
10279
+ }
10280
+ }
10281
+ /**
10282
+ * @internal
10283
+ * Exported only for unit testing
10284
+ */
10285
+ const removeImageElement = (event) => {
10286
+ // We don't support the pasting options such as paste as image yet.
10287
+ if (event.eventType === PluginEventType.BeforePaste && event.pasteType === 'normal') {
10288
+ event.fragment.querySelectorAll('img').forEach((image) => {
10289
+ // If the image is the only child of its parent, remove all the parents of this img element.
10290
+ let parentNode = image.parentElement;
10291
+ let currentNode = image;
10292
+ while ((parentNode === null || parentNode === void 0 ? void 0 : parentNode.childNodes.length) === 1) {
10293
+ currentNode = parentNode;
10294
+ parentNode = parentNode.parentElement;
10295
+ }
10296
+ currentNode === null || currentNode === void 0 ? void 0 : currentNode.remove();
10297
+ });
10298
+ }
10299
+ };
10300
+ /**
10301
+ * Scrolls the editor's scroll container to the bottom after content is pasted.
10302
+ * @param event - The plugin event.
10303
+ * @param editor - The editor instance.
10304
+ */
10305
+ const scrollToBottomAfterContentPaste = (event, editor) => {
10306
+ if (event.eventType === PluginEventType.ContentChanged && event.source === ContentChangedEventSource.Paste) {
10307
+ const scrollContainer = editor.getScrollContainer();
10308
+ // get current selection for the editor
10309
+ const selection = document.getSelection();
10310
+ // if selection exists
10311
+ if (selection && selection.rangeCount > 0) {
10312
+ // the range for the selection
10313
+ const range = selection.getRangeAt(0);
10314
+ // the selection position relative to the viewport
10315
+ const cursorRect = range.getBoundingClientRect();
10316
+ // the scrollContainer position relative to the viewport
10317
+ const scrollContainerRect = scrollContainer.getBoundingClientRect();
10318
+ // 1. scrollContainer.scrollTop represents the number of pixels that the content of scrollContainer is scrolled upward.
10319
+ // 2. subtract the top position of the scrollContainer element (scrollContainerRect.top) to
10320
+ // translate the scroll position from being relative to the document to being relative to the viewport.
10321
+ // 3. add the top position of the cursor (containerRect.top) to moves the scroll position to the cursor's position.
10322
+ const updatedScrollTop = scrollContainer.scrollTop - scrollContainerRect.top + cursorRect.top;
10323
+ scrollContainer.scrollTo({
10324
+ top: updatedScrollTop,
10325
+ behavior: 'smooth'
10326
+ });
10327
+ }
10328
+ }
10329
+ };
10330
+
10331
+ // Copyright (c) Microsoft Corporation.
10332
+ // Licensed under the MIT License.
10333
+ /**
10334
+ * KeyboardInputPlugin is a plugin for handling keyboard events in the editor.
10335
+ */
10336
+ class KeyboardInputPlugin {
10337
+ constructor() {
10338
+ // don't set callback in constructor to be able to update callback without plugin recreation
10339
+ this.onKeyDown = null;
10340
+ }
10341
+ getName() {
10342
+ return 'KeyboardInputPlugin';
10343
+ }
10344
+ initialize() { }
10345
+ dispose() { }
10346
+ onPluginEvent(event) {
10347
+ if (this.onKeyDown && event.eventType === PluginEventType.KeyDown && event.rawEvent instanceof KeyboardEvent) {
10348
+ this.onKeyDown(event.rawEvent);
10349
+ }
10350
+ }
10351
+ }
10352
+
10353
+ // Copyright (c) Microsoft Corporation.
10354
+ // Licensed under the MIT License.
10355
+ /**
10356
+ * An update mode to indicate when the content update happens
10357
+ */
10358
+ var UpdateEvent;
10359
+ (function (UpdateEvent) {
10360
+ UpdateEvent["Init"] = "Init";
10361
+ UpdateEvent["Dispose"] = "Dispose";
10362
+ UpdateEvent["ContentChanged"] = "ContentChanged";
10363
+ UpdateEvent["UserInput"] = "UserInput";
10364
+ UpdateEvent["Blur"] = "Blur";
10365
+ })(UpdateEvent || (UpdateEvent = {}));
10366
+ /**
10367
+ * A plugin to handle content update
10368
+ */
10369
+ class UpdateContentPlugin {
10370
+ constructor() {
10371
+ this.editor = null;
10372
+ this.disposer = null;
10373
+ // don't set callback in constructor to be able to update callback without plugin recreation
10374
+ this.onUpdate = null;
10375
+ this.onBlur = () => {
10376
+ if (this.onUpdate === null) {
10377
+ return;
10378
+ }
10379
+ this.onUpdate(UpdateEvent.Blur);
10380
+ };
10381
+ }
10382
+ getName() {
10383
+ return 'UpdateContentPlugin';
10384
+ }
10385
+ /**
10386
+ * Initialize this plugin
10387
+ * @param editor The editor instance
10388
+ */
10389
+ initialize(editor) {
10390
+ this.editor = editor;
10391
+ this.disposer = this.editor.attachDomEvent({
10392
+ blur: { beforeDispatch: this.onBlur }
10393
+ });
10394
+ }
10395
+ dispose() {
10396
+ this.editor = null;
10397
+ if (this.disposer) {
10398
+ this.disposer();
10399
+ this.disposer = null;
10400
+ }
10401
+ }
10402
+ onPluginEvent(event) {
10403
+ if (this.onUpdate === null) {
10404
+ return;
10405
+ }
10406
+ switch (event.eventType) {
10407
+ case PluginEventType.EditorReady:
10408
+ this.onUpdate(UpdateEvent.Init);
10409
+ break;
10410
+ case PluginEventType.BeforeDispose:
10411
+ this.onUpdate(UpdateEvent.Dispose);
10412
+ break;
10413
+ case PluginEventType.ContentChanged:
10414
+ this.onUpdate(UpdateEvent.ContentChanged);
10415
+ break;
10416
+ case PluginEventType.Input:
10417
+ this.onUpdate(UpdateEvent.UserInput);
10418
+ break;
10419
+ }
10263
10420
  }
10264
10421
  }
10265
10422
 
@@ -10291,6 +10448,99 @@ function parseKey(key) {
10291
10448
  column: parseInt(column)
10292
10449
  };
10293
10450
  }
10451
+ /**
10452
+ * Returns an array of context menu items for editing a rich text table.
10453
+ *
10454
+ * @param editor - The editor instance.
10455
+ * @param strings - An object containing localized strings for the context menu items.
10456
+ * @returns An array of context menu items.
10457
+ */
10458
+ const getTableEditContextMenuItems = (editor, strings) => {
10459
+ return [
10460
+ {
10461
+ key: 'RichTextTableEditMenuTableInsert',
10462
+ text: strings.richTextInsertRowOrColumnMenu,
10463
+ ariaLabel: strings.richTextInsertRowOrColumnMenu,
10464
+ iconProps: {
10465
+ iconName: 'RichTextTableInsertMenuIcon',
10466
+ styles: { root: tableContextMenuIconStyles }
10467
+ },
10468
+ subMenuProps: {
10469
+ items: [
10470
+ {
10471
+ key: 'RichTextTableEditMenuTableInsertRowAbove',
10472
+ text: strings.richTextInsertRowAboveMenu,
10473
+ ariaLabel: strings.richTextInsertRowAboveMenu,
10474
+ onClick: () => {
10475
+ roosterjsContentModelApi.editTable(editor, 'insertAbove');
10476
+ }
10477
+ },
10478
+ {
10479
+ key: 'RichTextTableEditMenuTableInsertRowBelow',
10480
+ text: strings.richTextInsertRowBelowMenu,
10481
+ ariaLabel: strings.richTextInsertRowBelowMenu,
10482
+ onClick: () => {
10483
+ roosterjsContentModelApi.editTable(editor, 'insertBelow');
10484
+ }
10485
+ },
10486
+ {
10487
+ key: 'RichTextTableEditMenuTableInsertColumnLeft',
10488
+ text: strings.richTextInsertColumnLeftMenu,
10489
+ ariaLabel: strings.richTextInsertColumnLeftMenu,
10490
+ onClick: () => {
10491
+ roosterjsContentModelApi.editTable(editor, 'insertLeft');
10492
+ }
10493
+ },
10494
+ {
10495
+ key: 'RichTextTableEditMenuTableInsertColumnRight',
10496
+ text: strings.richTextInsertColumnRightMenu,
10497
+ ariaLabel: strings.richTextInsertColumnRightMenu,
10498
+ onClick: () => {
10499
+ roosterjsContentModelApi.editTable(editor, 'insertRight');
10500
+ }
10501
+ }
10502
+ ]
10503
+ }
10504
+ },
10505
+ {
10506
+ key: 'RichTextTableEditMenuTableDelete',
10507
+ text: strings.richTextDeleteRowOrColumnMenu,
10508
+ ariaLabel: strings.richTextDeleteRowOrColumnMenu,
10509
+ iconProps: {
10510
+ iconName: 'RichTextTableDeleteMenuIcon',
10511
+ styles: { root: tableContextMenuIconStyles }
10512
+ },
10513
+ subMenuProps: {
10514
+ items: [
10515
+ {
10516
+ key: 'RichTextTableEditMenuTableDeleteRow',
10517
+ text: strings.richTextDeleteRowMenu,
10518
+ ariaLabel: strings.richTextDeleteRowMenu,
10519
+ onClick: () => {
10520
+ roosterjsContentModelApi.editTable(editor, 'deleteRow');
10521
+ }
10522
+ },
10523
+ {
10524
+ key: 'RichTextTableEditMenuTableDeleteColumn',
10525
+ text: strings.richTextDeleteColumnMenu,
10526
+ ariaLabel: strings.richTextDeleteColumnMenu,
10527
+ onClick: () => {
10528
+ roosterjsContentModelApi.editTable(editor, 'deleteColumn');
10529
+ }
10530
+ },
10531
+ {
10532
+ key: 'RichTextTableEditMenuTableDeleteTable',
10533
+ text: strings.richTextDeleteTableMenu,
10534
+ ariaLabel: strings.richTextDeleteTableMenu,
10535
+ onClick: () => {
10536
+ roosterjsContentModelApi.editTable(editor, 'deleteTable');
10537
+ }
10538
+ }
10539
+ ]
10540
+ }
10541
+ }
10542
+ ];
10543
+ };
10294
10544
 
10295
10545
  // Copyright (c) Microsoft Corporation.
10296
10546
  // Licensed under the MIT License.
@@ -10321,9 +10571,9 @@ const RichTextInsertTablePane = (props) => {
10321
10571
  const onMouseEnter = React.useCallback((e) => {
10322
10572
  updateSize(e.target);
10323
10573
  }, [updateSize]);
10324
- const onClickButton = React.useCallback((e) => {
10325
- onClick(e, Object.assign(Object.assign({}, item), { key: createKey(formatRowColumnText(row), formatRowColumnText(column)) }));
10326
- }, [row, column, onClick, item]);
10574
+ const onClickButton = React.useCallback(() => {
10575
+ onClick(createKey(formatRowColumnText(row), formatRowColumnText(column)));
10576
+ }, [row, column, onClick]);
10327
10577
  const items = React.useMemo(() => {
10328
10578
  var _a;
10329
10579
  const items = [];
@@ -10353,310 +10603,435 @@ const formatRowColumnText = (value) => {
10353
10603
 
10354
10604
  // Copyright (c) Microsoft Corporation.
10355
10605
  // Licensed under the MIT License.
10356
- // This file uses RoosterJS React package implementation with updates to UI components and styles.
10357
10606
  /**
10358
- * "Insert table" button for the RoosterJS ribbon
10607
+ * Renders the icon component for the insert table button in the rich text editor toolbar.
10359
10608
  */
10360
- const insertTableButton = (theme, maxRowsNumber, maxColumnsNumber) => {
10361
- return {
10362
- key: 'buttonNameInsertTable',
10363
- unlocalizedText: 'Insert table',
10364
- // Icon will be set in onRenderIcon callback
10365
- iconName: '',
10366
- onClick: (editor, key) => {
10367
- const { row, column } = parseKey(key);
10368
- insertTable(editor, column, row);
10369
- },
10370
- dropDownMenu: {
10371
- items: {
10372
- // the key of the item is also used as a key for localization
10373
- insertTablePane: `Insert ${ColumnRowReplaceString} table`
10374
- },
10375
- itemRender: (item, onClick) => {
10376
- return (React.createElement(RichTextInsertTablePane, { item: item, onClick: onClick, maxColumnsNumber: maxColumnsNumber, maxRowsNumber: maxRowsNumber }));
10377
- },
10378
- commandBarSubMenuProperties: {
10379
- className: insertTableMenuTablePane
10380
- }
10381
- },
10382
- commandBarProperties: {
10383
- // hide the chevron icon
10384
- menuIconProps: {
10385
- hidden: true
10386
- },
10387
- onRenderIcon: () => {
10388
- return React.createElement(TableIcon, null);
10389
- },
10390
- buttonStyles: ribbonTableButtonStyle(theme),
10391
- canCheck: false
10392
- }
10393
- };
10394
- };
10395
- const TableIcon = () => {
10396
- return (
10397
- // update the visibility of the Table Icon with css classes that are triggered by command bar's state
10398
- React.createElement(react.Stack, null,
10609
+ const RichTextToolbarTableIcon = () => {
10610
+ return (React.createElement(react.Stack, null,
10399
10611
  React.createElement(react.Icon, { iconName: "RichTextInsertTableFilledIcon", className: 'ribbon-table-button-filled-icon' }),
10400
10612
  React.createElement(react.Icon, { iconName: "RichTextInsertTableRegularIcon", className: 'ribbon-table-button-regular-icon' })));
10401
10613
  };
10402
10614
 
10403
10615
  // Copyright (c) Microsoft Corporation.
10404
10616
  // Licensed under the MIT License.
10405
- const MaxRowsNumber = 5;
10406
- const MaxColumnsNumber = 5;
10407
- const dividerRibbonButton = (theme, key) => {
10408
- return {
10409
- key: key,
10410
- // the icon will be set in `onRender` callback
10411
- // this is needed to make the divider unavailable for keyboard navigation
10412
- iconName: '',
10413
- // no text is needed here as we don't want to show a tooltip for the divider
10414
- unlocalizedText: '',
10415
- onClick: () => { },
10416
- isDisabled: () => true,
10417
- commandBarProperties: {
10418
- // show the item correctly for the overflow menu
10419
- itemType: react.ContextualMenuItemType.Divider,
10420
- // this is still needed to remove checkmark icon space even though it is a divider
10421
- canCheck: false,
10422
- onRender: () => React.createElement(react.Icon, { iconName: "RichTextDividerIcon", className: ribbonDividerStyle(theme) })
10423
- }
10424
- };
10425
- };
10426
- const boldButton = (theme) => {
10427
- return createKnownRibbonButton(roosterjsReact.KnownRibbonButtonKey.Bold, theme, 'RichTextBoldButtonIcon');
10428
- };
10429
- const italicButton = (theme) => {
10430
- return createKnownRibbonButton(roosterjsReact.KnownRibbonButtonKey.Italic, theme, 'RichTextItalicButtonIcon');
10431
- };
10432
- const underlineButton = (theme) => {
10433
- return createKnownRibbonButton(roosterjsReact.KnownRibbonButtonKey.Underline, theme, 'RichTextUnderlineButtonIcon');
10434
- };
10435
- const bulletListButton = (theme) => {
10436
- return createKnownRibbonButton(roosterjsReact.KnownRibbonButtonKey.BulletedList, theme, 'RichTextBulletListButtonIcon');
10437
- };
10438
- const numberListButton = (theme) => {
10439
- return createKnownRibbonButton(roosterjsReact.KnownRibbonButtonKey.NumberedList, theme, 'RichTextNumberListButtonIcon');
10440
- };
10441
- const indentIncreaseButton = (theme) => {
10442
- return createKnownRibbonButton(roosterjsReact.KnownRibbonButtonKey.IncreaseIndent, theme, 'RichTextIndentIncreaseButtonIcon');
10443
- };
10444
- const indentDecreaseButton = (theme) => {
10445
- return createKnownRibbonButton(roosterjsReact.KnownRibbonButtonKey.DecreaseIndent, theme, 'RichTextIndentDecreaseButtonIcon');
10446
- };
10447
- const createKnownRibbonButton = (key, theme, icon) => {
10448
- var _a;
10449
- const buttons = roosterjsReact.getButtons([key]);
10450
- if (buttons.length > 0) {
10451
- const button = buttons[0];
10452
- // AllButtonStringKeys is a union of all the string keys of all the buttons
10453
- const result = buttons[0];
10454
- button.iconName = icon;
10455
- button.commandBarProperties = Object.assign(Object.assign({}, button.commandBarProperties), { buttonStyles: Object.assign(Object.assign({}, (_a = button.commandBarProperties) === null || _a === void 0 ? void 0 : _a.buttonStyles), ribbonButtonStyle(theme)), canCheck: false });
10456
- return result;
10457
- }
10458
- return undefined;
10459
- };
10460
10617
  /**
10461
- * @private
10618
+ * "Insert table" command bar item for the rich text toolbar
10462
10619
  */
10463
- const ribbonButtons = (theme) => {
10464
- const buttons = [];
10465
- [
10466
- boldButton(theme),
10467
- italicButton(theme),
10468
- underlineButton(theme),
10469
- dividerRibbonButton(theme, 'RichTextRibbonTextFormatDivider'),
10470
- bulletListButton(theme),
10471
- numberListButton(theme),
10472
- indentDecreaseButton(theme),
10473
- indentIncreaseButton(theme),
10474
- dividerRibbonButton(theme, 'RichTextRibbonTableDivider'),
10475
- insertTableButton(theme, MaxRowsNumber, MaxColumnsNumber)
10476
- ].forEach((item) => {
10477
- if (item !== undefined) {
10478
- buttons.push(item);
10620
+ const richTextInsertTableCommandBarItem = (theme, maxRowsNumber, maxColumnsNumber, strings, onClick) => {
10621
+ return {
10622
+ 'data-testid': 'rich-text-toolbar-insert-table-button',
10623
+ key: 'RichTextToolbarInsertTableButton',
10624
+ text: strings.richTextInsertTableTooltip,
10625
+ ariaLabel: strings.richTextInsertTableTooltip,
10626
+ // hide the chevron icon
10627
+ menuIconProps: {
10628
+ hidden: true
10629
+ },
10630
+ onRenderIcon: () => {
10631
+ return React.createElement(RichTextToolbarTableIcon, null);
10632
+ },
10633
+ buttonStyles: toolbarTableButtonStyle(theme),
10634
+ canCheck: false,
10635
+ iconOnly: true,
10636
+ subMenuProps: {
10637
+ calloutProps: { isBeakVisible: false },
10638
+ shouldFocusOnMount: true,
10639
+ className: insertTableMenuTablePane,
10640
+ focusZoneProps: { direction: react.FocusZoneDirection.bidirectional },
10641
+ items: [
10642
+ {
10643
+ key: 'RichTextToolbarInsertTableMenu',
10644
+ text: strings.richTextInsertTableMenuTitle,
10645
+ canCheck: false,
10646
+ onRender: (item) => {
10647
+ return (React.createElement(RichTextInsertTablePane, { item: item, onClick: (key) => {
10648
+ const { row, column } = parseKey(key);
10649
+ onClick(column, row);
10650
+ }, maxColumnsNumber: maxColumnsNumber, maxRowsNumber: maxRowsNumber }));
10651
+ }
10652
+ }
10653
+ ]
10479
10654
  }
10480
- });
10481
- return buttons;
10655
+ };
10482
10656
  };
10483
10657
 
10484
10658
  // Copyright (c) Microsoft Corporation.
10485
10659
  // Licensed under the MIT License.
10660
+ const MaxRowsNumber = 5;
10661
+ const MaxColumnsNumber = 5;
10486
10662
  /**
10487
- * @private
10663
+ * A component to display rich text toolbar.
10488
10664
  *
10489
- * Strings for the ribbon buttons where key should match `key` prop if any or the name of the item.
10665
+ * @beta
10490
10666
  */
10491
- const ribbonButtonsStrings = (strings) => {
10667
+ const RichTextToolbar = (props) => {
10668
+ const { plugin, strings } = props;
10669
+ const theme = useTheme();
10670
+ // need to re-render the buttons when format state changes
10671
+ const [formatState, setFormatState] = React.useState(undefined);
10672
+ React.useEffect(() => {
10673
+ // update the format state on editor events
10674
+ plugin.onFormatChanged = setFormatState;
10675
+ }, [plugin]);
10676
+ const boldButton = React.useMemo(() => {
10677
+ return getCommandBarItem({
10678
+ dataTestId: 'rich-text-toolbar-bold-button',
10679
+ key: 'RichTextToolbarBoldButton',
10680
+ icon: 'RichTextBoldButtonIcon',
10681
+ onClick: () => {
10682
+ plugin.onToolbarButtonClick((editor) => {
10683
+ roosterjsContentModelApi.toggleBold(editor);
10684
+ });
10685
+ },
10686
+ text: strings.richTextBoldTooltip,
10687
+ checked: formatState !== undefined && formatState.isBold === true,
10688
+ theme: theme
10689
+ });
10690
+ }, [formatState, plugin, strings.richTextBoldTooltip, theme]);
10691
+ const italicButton = React.useMemo(() => {
10692
+ return getCommandBarItem({
10693
+ dataTestId: 'rich-text-toolbar-italic-button',
10694
+ key: 'RichTextToolbarItalicButton',
10695
+ icon: 'RichTextItalicButtonIcon',
10696
+ onClick: () => {
10697
+ plugin.onToolbarButtonClick((editor) => {
10698
+ roosterjsContentModelApi.toggleItalic(editor);
10699
+ });
10700
+ },
10701
+ text: strings.richTextItalicTooltip,
10702
+ checked: formatState !== undefined && (formatState === null || formatState === void 0 ? void 0 : formatState.isItalic) === true,
10703
+ theme: theme
10704
+ });
10705
+ }, [formatState, plugin, strings.richTextItalicTooltip, theme]);
10706
+ const underlineButton = React.useMemo(() => {
10707
+ return getCommandBarItem({
10708
+ dataTestId: 'rich-text-toolbar-underline-button',
10709
+ key: 'RichTextToolbarUnderlineButton',
10710
+ icon: 'RichTextUnderlineButtonIcon',
10711
+ onClick: () => {
10712
+ plugin.onToolbarButtonClick((editor) => {
10713
+ roosterjsContentModelApi.toggleUnderline(editor);
10714
+ });
10715
+ },
10716
+ text: strings.richTextUnderlineTooltip,
10717
+ checked: formatState !== undefined && (formatState === null || formatState === void 0 ? void 0 : formatState.isUnderline) === true,
10718
+ theme: theme
10719
+ });
10720
+ }, [formatState, plugin, strings.richTextUnderlineTooltip, theme]);
10721
+ const bulletListButton = React.useMemo(() => {
10722
+ return getCommandBarItem({
10723
+ dataTestId: 'rich-text-toolbar-bullet-list-button',
10724
+ key: 'RichTextToolbarBulletListButton',
10725
+ icon: 'RichTextBulletListButtonIcon',
10726
+ onClick: () => {
10727
+ plugin.onToolbarButtonClick((editor) => {
10728
+ roosterjsContentModelApi.toggleBullet(editor);
10729
+ });
10730
+ },
10731
+ text: strings.richTextBulletListTooltip,
10732
+ checked: formatState !== undefined && (formatState === null || formatState === void 0 ? void 0 : formatState.isBullet) === true,
10733
+ theme: theme
10734
+ });
10735
+ }, [formatState, plugin, strings.richTextBulletListTooltip, theme]);
10736
+ const numberListButton = React.useMemo(() => {
10737
+ return getCommandBarItem({
10738
+ dataTestId: 'rich-text-toolbar-number-list-button',
10739
+ key: 'RichTextToolbarNumberListButton',
10740
+ icon: 'RichTextNumberListButtonIcon',
10741
+ onClick: () => {
10742
+ plugin.onToolbarButtonClick((editor) => {
10743
+ roosterjsContentModelApi.toggleNumbering(editor);
10744
+ });
10745
+ },
10746
+ text: strings.richTextNumberListTooltip,
10747
+ checked: formatState !== undefined && (formatState === null || formatState === void 0 ? void 0 : formatState.isNumbering) === true,
10748
+ theme: theme
10749
+ });
10750
+ }, [formatState, plugin, strings.richTextNumberListTooltip, theme]);
10751
+ const indentDecreaseButton = React.useMemo(() => {
10752
+ return getCommandBarItem({
10753
+ dataTestId: 'rich-text-toolbar-indent-decrease-button',
10754
+ key: 'RichTextToolbarIndentDecreaseButton',
10755
+ icon: 'RichTextIndentDecreaseButtonIcon',
10756
+ onClick: () => {
10757
+ plugin.onToolbarButtonClick((editor) => {
10758
+ roosterjsContentModelApi.setIndentation(editor, 'outdent');
10759
+ });
10760
+ },
10761
+ text: strings.richTextDecreaseIndentTooltip,
10762
+ canCheck: false,
10763
+ theme: theme
10764
+ });
10765
+ }, [plugin, strings.richTextDecreaseIndentTooltip, theme]);
10766
+ const indentIncreaseButton = React.useMemo(() => {
10767
+ return getCommandBarItem({
10768
+ dataTestId: 'rich-text-toolbar-indent-increase-button',
10769
+ key: 'RichTextToolbarIndentIncreaseButton',
10770
+ icon: 'RichTextIndentIncreaseButtonIcon',
10771
+ onClick: () => {
10772
+ plugin.onToolbarButtonClick((editor) => {
10773
+ roosterjsContentModelApi.setIndentation(editor, 'indent');
10774
+ });
10775
+ },
10776
+ text: strings.richTextIncreaseIndentTooltip,
10777
+ canCheck: false,
10778
+ theme: theme
10779
+ });
10780
+ }, [plugin, strings.richTextIncreaseIndentTooltip, theme]);
10781
+ const divider = React.useCallback((key) => {
10782
+ return dividerCommandBarItem(theme, key);
10783
+ }, [theme]);
10784
+ const tableButton = React.useMemo(() => {
10785
+ return richTextInsertTableCommandBarItem(theme, MaxRowsNumber, MaxColumnsNumber, strings, (column, row) => {
10786
+ plugin.onToolbarButtonClick((editor) => {
10787
+ //add format
10788
+ roosterjsContentModelApi.insertTable(editor, column, row);
10789
+ // when subMenuProps is used and the menu is dismissed, focus is set to the command bar item that opened the menu
10790
+ // set focus to editor on next re-render
10791
+ setTimeout(() => {
10792
+ editor.focus();
10793
+ });
10794
+ });
10795
+ });
10796
+ }, [plugin, strings, theme]);
10797
+ const buttons = React.useMemo(() => {
10798
+ return [
10799
+ boldButton,
10800
+ italicButton,
10801
+ underlineButton,
10802
+ divider('RichTextRibbonTextFormatDivider'),
10803
+ bulletListButton,
10804
+ numberListButton,
10805
+ indentDecreaseButton,
10806
+ indentIncreaseButton,
10807
+ divider('RichTextRibbonTableDivider'),
10808
+ tableButton
10809
+ ];
10810
+ }, [
10811
+ boldButton,
10812
+ italicButton,
10813
+ underlineButton,
10814
+ divider,
10815
+ bulletListButton,
10816
+ numberListButton,
10817
+ indentDecreaseButton,
10818
+ indentIncreaseButton,
10819
+ tableButton
10820
+ ]);
10821
+ const overflowButtonProps = React.useMemo(() => {
10822
+ return {
10823
+ ariaLabel: strings.richTextToolbarMoreButtonAriaLabel,
10824
+ styles: toolbarButtonStyle(theme),
10825
+ menuProps: {
10826
+ items: [], // CommandBar will determine items rendered in overflow
10827
+ isBeakVisible: false,
10828
+ styles: ribbonOverflowButtonStyle(theme)
10829
+ }
10830
+ };
10831
+ }, [strings.richTextToolbarMoreButtonAriaLabel, theme]);
10832
+ return (React.createElement(react.CommandBar, { items: buttons, "data-testid": 'rich-text-editor-toolbar', styles: richTextToolbarStyle, overflowButtonProps: overflowButtonProps }));
10833
+ };
10834
+ const getCommandBarItem = ({ key, icon, onClick, text, canCheck = true, checked = false, disabled = false, theme, dataTestId }) => {
10492
10835
  return {
10493
- buttonNameBold: strings.richTextBoldTooltip,
10494
- buttonNameItalic: strings.richTextItalicTooltip,
10495
- buttonNameUnderline: strings.richTextUnderlineTooltip,
10496
- buttonNameBulletedList: strings.richTextBulletListTooltip,
10497
- buttonNameNumberedList: strings.richTextNumberListTooltip,
10498
- buttonNameIncreaseIndent: strings.richTextIncreaseIndentTooltip,
10499
- buttonNameDecreaseIndent: strings.richTextDecreaseIndentTooltip,
10500
- buttonNameInsertTable: strings.richTextInsertTableTooltip,
10501
- insertTablePane: strings.richTextInsertTableMenuTitle
10836
+ 'data-testid': dataTestId,
10837
+ key: key,
10838
+ iconProps: { iconName: icon },
10839
+ onClick: onClick,
10840
+ text: text,
10841
+ ariaLabel: text,
10842
+ iconOnly: true,
10843
+ canCheck: canCheck,
10844
+ buttonStyles: Object.assign({}, toolbarButtonStyle(theme)),
10845
+ checked: checked,
10846
+ disabled: disabled
10502
10847
  };
10503
10848
  };
10504
- /**
10505
- * @private
10506
- *
10507
- * Strings for the table context menu where key should match `key` prop if any or the name of the item.
10508
- */
10509
- const tableContextMenuStrings = (strings) => {
10849
+ const dividerCommandBarItem = (theme, key) => {
10510
10850
  return {
10511
- menuNameTableInsert: strings.richTextInsertRowOrColumnMenu,
10512
- menuNameTableInsertAbove: strings.richTextInsertRowAboveMenu,
10513
- menuNameTableInsertBelow: strings.richTextInsertRowBelowMenu,
10514
- menuNameTableInsertLeft: strings.richTextInsertColumnLeftMenu,
10515
- menuNameTableInsertRight: strings.richTextInsertColumnRightMenu,
10516
- menuNameTableDelete: strings.richTextDeleteRowOrColumnMenu,
10517
- menuNameTableDeleteColumn: strings.richTextDeleteColumnMenu,
10518
- menuNameTableDeleteRow: strings.richTextDeleteRowMenu,
10519
- menuNameTableDeleteTable: strings.richTextDeleteTableMenu
10851
+ key: key,
10852
+ disabled: true,
10853
+ // show the item correctly for the overflow menu
10854
+ itemType: react.ContextualMenuItemType.Divider,
10855
+ // this is still needed to remove checkmark icon space even though it is a divider
10856
+ canCheck: false,
10857
+ onRender: () => React.createElement(react.Icon, { iconName: "RichTextDividerIcon", className: ribbonDividerStyle(theme) })
10520
10858
  };
10521
10859
  };
10522
10860
 
10523
- // Copyright (c) Microsoft Corporation.
10524
- // Licensed under the MIT License.
10525
- const onClick = (key, editor) => {
10526
- editor.focus();
10527
- const operation = TableEditOperationMap[key];
10528
- if (typeof operation === 'number') {
10529
- roosterjsEditorApi.editTable(editor, operation);
10530
- }
10531
- };
10532
- const TableEditOperationMap = {
10533
- menuNameTableInsertAbove: roosterjsEditorTypesCompatible.CompatibleTableOperation.InsertAbove,
10534
- menuNameTableInsertBelow: roosterjsEditorTypesCompatible.CompatibleTableOperation.InsertBelow,
10535
- menuNameTableInsertLeft: roosterjsEditorTypesCompatible.CompatibleTableOperation.InsertLeft,
10536
- menuNameTableInsertRight: roosterjsEditorTypesCompatible.CompatibleTableOperation.InsertRight,
10537
- menuNameTableDeleteTable: roosterjsEditorTypesCompatible.CompatibleTableOperation.DeleteTable,
10538
- menuNameTableDeleteColumn: roosterjsEditorTypesCompatible.CompatibleTableOperation.DeleteColumn,
10539
- menuNameTableDeleteRow: roosterjsEditorTypesCompatible.CompatibleTableOperation.DeleteRow
10540
- };
10541
- const tableEditInsertMenuItem = {
10542
- key: 'menuNameTableInsert',
10543
- unlocalizedText: 'Insert',
10544
- subItems: {
10545
- menuNameTableInsertAbove: 'Insert above',
10546
- menuNameTableInsertBelow: 'Insert below',
10547
- menuNameTableInsertLeft: 'Insert left',
10548
- menuNameTableInsertRight: 'Insert right'
10549
- },
10550
- onClick,
10551
- iconProps: {
10552
- iconName: 'RichTextTableInsertMenuIcon',
10553
- styles: { root: tableContextMenuIconStyles }
10554
- }
10555
- };
10556
- const tableEditDeleteMenuItem = {
10557
- key: 'menuNameTableDelete',
10558
- unlocalizedText: 'Delete',
10559
- subItems: {
10560
- menuNameTableDeleteColumn: 'Delete column',
10561
- menuNameTableDeleteRow: 'Delete row',
10562
- menuNameTableDeleteTable: 'Delete table'
10563
- },
10564
- onClick,
10565
- iconProps: {
10566
- iconName: 'RichTextTableDeleteMenuIcon',
10567
- styles: { root: tableContextMenuIconStyles }
10568
- }
10569
- };
10570
- const tableActions = [tableEditInsertMenuItem, tableEditDeleteMenuItem];
10571
- /**
10572
- * Create a new instance of ContextMenuProvider to support table editing functionalities in context menu
10573
- * @returns A new ContextMenuProvider
10574
- */
10575
- const createTableEditMenuProvider = (strings) => {
10576
- return roosterjsReact.createContextMenuProvider('tableEdit', tableActions, tableContextMenuStrings(strings !== null && strings !== void 0 ? strings : {}), (editor, node) => !!getEditingTable(editor, node));
10577
- };
10578
- const getEditingTable = (editor, node) => {
10579
- const td = editor.getElementAtCursor('TD,TH', node);
10580
- const table = td && editor.getElementAtCursor('table', td);
10581
- return (table === null || table === void 0 ? void 0 : table.isContentEditable) ? { table, td } : null;
10582
- };
10583
-
10584
10861
  // Copyright (c) Microsoft Corporation.
10585
10862
  // Licensed under the MIT License.
10586
10863
  /**
10587
- * CopyPastePlugin is a plugin for handling copy and paste events in the editor.
10864
+ * KeyboardInputPlugin is a plugin for handling keyboard events in the editor.
10588
10865
  */
10589
- class CopyPastePlugin {
10866
+ class RichTextToolbarPlugin {
10590
10867
  constructor() {
10868
+ this.formatState = null;
10591
10869
  this.editor = null;
10870
+ this.onFormatChanged = null;
10592
10871
  }
10593
10872
  getName() {
10594
- return 'CopyPastePlugin';
10873
+ return 'RichTextToolbarPlugin';
10595
10874
  }
10596
10875
  initialize(editor) {
10597
10876
  this.editor = editor;
10598
10877
  }
10599
- dispose() { }
10878
+ dispose() {
10879
+ this.editor = null;
10880
+ }
10600
10881
  onPluginEvent(event) {
10601
- removeImageElement(event);
10602
- if (this.editor !== null && !this.editor.isDisposed()) {
10603
- // scroll the editor to the correct position after pasting content
10604
- scrollToBottomAfterContentPaste(event, this.editor);
10882
+ switch (event.eventType) {
10883
+ // KeyDown and MouseUp are used to update the state when the editor is already shown and focused by the user
10884
+ case PluginEventType.EditorReady:
10885
+ case PluginEventType.ContentChanged:
10886
+ case PluginEventType.ZoomChanged:
10887
+ case PluginEventType.KeyDown:
10888
+ case PluginEventType.MouseUp:
10889
+ this.updateFormat();
10890
+ break;
10891
+ }
10892
+ }
10893
+ /**
10894
+ * Handles the click event of a toolbar button.
10895
+ * @param buttonAction - The action to be performed when the button is clicked.
10896
+ */
10897
+ onToolbarButtonClick(buttonAction) {
10898
+ if (this.editor && !this.editor.isDisposed()) {
10899
+ buttonAction(this.editor);
10900
+ this.updateFormat();
10901
+ }
10902
+ }
10903
+ /**
10904
+ * Updates the format state of the rich text editor and triggers the `onFormatChanged` callback
10905
+ * if there is any difference between the new and the current format states.
10906
+ */
10907
+ updateFormat() {
10908
+ if (this.editor && this.onFormatChanged) {
10909
+ const newFormatState = roosterjsContentModelApi.getFormatState(this.editor);
10910
+ // use keys from the format that has more keys or the new format state if there is no current format state
10911
+ const keys = !this.formatState || roosterjsContentModelDom.getObjectKeys(newFormatState).length > roosterjsContentModelDom.getObjectKeys(this.formatState).length
10912
+ ? roosterjsContentModelDom.getObjectKeys(newFormatState)
10913
+ : roosterjsContentModelDom.getObjectKeys(this.formatState);
10914
+ // check if there is any difference between the new format state and the current format state
10915
+ // otherwise the states will produce new objects every time even when the format state is the same
10916
+ if (!this.formatState || keys.some((key) => { var _a; return newFormatState[key] !== ((_a = this.formatState) === null || _a === void 0 ? void 0 : _a[key]); })) {
10917
+ this.formatState = newFormatState;
10918
+ this.onFormatChanged(newFormatState);
10919
+ }
10605
10920
  }
10606
10921
  }
10607
10922
  }
10923
+
10924
+ // Copyright (c) Microsoft Corporation.
10925
+ // Licensed under the MIT License.
10608
10926
  /**
10609
- * @internal
10610
- * Exported only for unit testing
10927
+ * Represents a plugin that adds a context menu to the rich text editor.
10611
10928
  */
10612
- const removeImageElement = (event) => {
10613
- // We don't support the pasting options such as paste as image yet.
10614
- if (event.eventType === roosterjsEditorTypesCompatible.CompatiblePluginEventType.BeforePaste && event.pasteType === roosterjsEditorTypesCompatible.CompatiblePasteType.Normal) {
10615
- event.fragment.querySelectorAll('img').forEach((image) => {
10616
- // If the image is the only child of its parent, remove all the parents of this img element.
10617
- let parentNode = image.parentElement;
10618
- let currentNode = image;
10619
- while ((parentNode === null || parentNode === void 0 ? void 0 : parentNode.childNodes.length) === 1) {
10620
- currentNode = parentNode;
10621
- parentNode = parentNode.parentElement;
10929
+ class ContextMenuPlugin extends roosterjsContentModelPlugins.ContextMenuPluginBase {
10930
+ constructor(onRender, onDismiss) {
10931
+ super({
10932
+ /**
10933
+ * Renders the context menu in the specified container with the provided items.
10934
+ * @param container - The container element where the context menu should be rendered.
10935
+ * @param items - The items to be displayed in the context menu.
10936
+ * @param onDismiss - Callback function to be called when the context menu is dismissed. It will call `dismiss` method.
10937
+ */
10938
+ render: (container, items, onDismissCallback) => {
10939
+ const filteredItems = items.filter((item) => item !== null);
10940
+ onRender(container, filteredItems, onDismissCallback);
10941
+ },
10942
+ /**
10943
+ * Dismisses the context menu.
10944
+ */
10945
+ dismiss: () => {
10946
+ onDismiss();
10622
10947
  }
10623
- currentNode === null || currentNode === void 0 ? void 0 : currentNode.remove();
10624
10948
  });
10625
10949
  }
10626
- };
10950
+ }
10951
+
10627
10952
  /**
10628
- * Scrolls the editor's scroll container to the bottom after content is pasted.
10629
- * @param event - The plugin event.
10630
- * @param editor - The editor instance.
10953
+ * Provides a context menu for editing tables in the rich text editor.
10631
10954
  */
10632
- const scrollToBottomAfterContentPaste = (event, editor) => {
10633
- if (event.eventType === roosterjsEditorTypesCompatible.CompatiblePluginEventType.ContentChanged && event.source === roosterjsEditorTypesCompatible.CompatibleChangeSource.Paste) {
10634
- // current focused position in the editor
10635
- const focusedPosition = editor.getFocusedPosition();
10636
- // the cursor position relative to the viewport
10637
- const cursorRect = focusedPosition && roosterjsEditorDom.getPositionRect(focusedPosition);
10638
- // the scroll container of the editor
10639
- const scrollContainer = editor.getScrollContainer();
10640
- // the scrollContainer position relative to the viewport
10641
- const scrollContainerRect = roosterjsEditorDom.normalizeRect(scrollContainer.getBoundingClientRect());
10642
- if (focusedPosition !== null && cursorRect !== null && cursorRect !== undefined && scrollContainerRect !== null) {
10643
- const textElement = focusedPosition.element;
10644
- // the caret height is typically the same as the font size of the text
10645
- const caretHeight = parseFloat(window.getComputedStyle(textElement).fontSize);
10646
- // 1. scrollContainer.scrollTop represents the number of pixels that the content of scrollContainer is scrolled upward.
10647
- // 2. subtract the top position of the scrollContainer element (scrollContainerRect.top) to
10648
- // translate the scroll position from being relative to the document to being relative to the viewport.
10649
- // 3. add the top position of the cursor (cursorRect.top) to moves the scroll position to the cursor's position.
10650
- // 4. subtract a caret height to add some space between the cursor and the top edge of the scrollContainer.
10651
- const updatedScrollTop = scrollContainer.scrollTop - scrollContainerRect.top + cursorRect.top - caretHeight;
10652
- scrollContainer.scrollTo({
10653
- top: updatedScrollTop,
10654
- behavior: 'smooth'
10655
- });
10955
+ class TableEditContextMenuProvider {
10956
+ constructor() {
10957
+ this.editor = null;
10958
+ this.strings = {};
10959
+ this.items = null;
10960
+ }
10961
+ updateStrings(strings) {
10962
+ this.strings = strings;
10963
+ if (this.editor) {
10964
+ this.items = getTableEditContextMenuItems(this.editor, this.strings);
10965
+ }
10966
+ }
10967
+ getName() {
10968
+ return 'TableEditContextMenuProvider';
10969
+ }
10970
+ initialize(editor) {
10971
+ this.editor = editor;
10972
+ this.items = getTableEditContextMenuItems(editor, this.strings);
10973
+ }
10974
+ /**
10975
+ * Dispose this plugin
10976
+ */
10977
+ dispose() {
10978
+ this.editor = null;
10979
+ }
10980
+ getContextMenuItems(node) {
10981
+ // return this.items;
10982
+ if (this.editor && isTableEditable(this.editor, node)) {
10983
+ return this.items;
10984
+ }
10985
+ else {
10986
+ return null;
10656
10987
  }
10657
10988
  }
10989
+ }
10990
+ const isTableEditable = (editor, node) => {
10991
+ const domHelper = editor.getDOMHelper();
10992
+ const td = domHelper.findClosestElementAncestor(node, 'TD,TH');
10993
+ const table = td && domHelper.findClosestElementAncestor(td, 'table');
10994
+ return (table === null || table === void 0 ? void 0 : table.isContentEditable) === true;
10658
10995
  };
10659
10996
 
10997
+ // Copyright (c) Microsoft Corporation.
10998
+ // Licensed under the MIT License.
10999
+ /**
11000
+ * PlaceholderPlugin is a plugin for displaying placeholder and handle localization for it in the editor.
11001
+ */
11002
+ class PlaceholderPlugin extends roosterjsContentModelPlugins.WatermarkPlugin {
11003
+ constructor() {
11004
+ super(...arguments);
11005
+ this.isPlaceholderShown = false;
11006
+ this.editorValue = null;
11007
+ }
11008
+ updatePlaceholder(placeholder) {
11009
+ this.watermark = placeholder;
11010
+ // hide and show the placeholder to show the latest one
11011
+ // this needs to be done only if the placeholder is currently shown
11012
+ if (this.editorValue && this.isPlaceholderShown) {
11013
+ this.hide(this.editorValue);
11014
+ this.show(this.editorValue);
11015
+ }
11016
+ }
11017
+ initialize(editor) {
11018
+ this.editorValue = editor;
11019
+ super.initialize(editor);
11020
+ }
11021
+ dispose() {
11022
+ this.editorValue = null;
11023
+ super.dispose();
11024
+ }
11025
+ show(editor) {
11026
+ super.show(editor);
11027
+ this.isPlaceholderShown = true;
11028
+ }
11029
+ hide(editor) {
11030
+ super.hide(editor);
11031
+ this.isPlaceholderShown = false;
11032
+ }
11033
+ }
11034
+
10660
11035
  // Copyright (c) Microsoft Corporation.
10661
11036
  // Licensed under the MIT License.
10662
11037
  /**
@@ -10665,10 +11040,11 @@ const scrollToBottomAfterContentPaste = (event, editor) => {
10665
11040
  * @beta
10666
11041
  */
10667
11042
  const RichTextEditor = React.forwardRef((props, ref) => {
10668
- const { initialContent, onChange, placeholderText, strings, showRichTextEditorFormatting, content, autoFocus } = props;
11043
+ const { initialContent, onChange, placeholderText, strings, showRichTextEditorFormatting, autoFocus, onKeyDown, onContentModelUpdate, contentModel } = props;
10669
11044
  const editor = React.useRef(null);
10670
- const contentValue = React.useRef(content);
11045
+ const editorDiv = React.useRef(null);
10671
11046
  const theme = useTheme();
11047
+ const [contextMenuProps, setContextMenuProps] = React.useState(null);
10672
11048
  React.useImperativeHandle(ref, () => {
10673
11049
  return {
10674
11050
  focus() {
@@ -10678,108 +11054,179 @@ const RichTextEditor = React.forwardRef((props, ref) => {
10678
11054
  },
10679
11055
  setEmptyContent() {
10680
11056
  if (editor.current) {
10681
- editor.current.setContent('');
11057
+ // remove all content from the editor and update the model
11058
+ // ContentChanged event will be sent by RoosterJS automatically
11059
+ editor.current.formatContentModel((model) => {
11060
+ model.blocks = [];
11061
+ return true;
11062
+ });
11063
+ //reset content model
11064
+ onContentModelUpdate && onContentModelUpdate(undefined);
10682
11065
  }
10683
11066
  },
10684
11067
  getPlainContent() {
10685
- var _a;
10686
- return (_a = editor === null || editor === void 0 ? void 0 : editor.current) === null || _a === void 0 ? void 0 : _a.getContent(roosterjsEditorTypesCompatible.CompatibleGetContentMode.PlainTextFast);
11068
+ if (editor.current) {
11069
+ return roosterjsContentModelCore.exportContent(editor.current, 'PlainTextFast');
11070
+ }
11071
+ else {
11072
+ return undefined;
11073
+ }
10687
11074
  }
10688
11075
  };
11076
+ }, [onContentModelUpdate]);
11077
+ const toolbarPlugin = React.useMemo(() => {
11078
+ return new RichTextToolbarPlugin();
10689
11079
  }, []);
10690
- const ribbonPlugin = React.useMemo(() => {
10691
- return roosterjsReact.createRibbonPlugin();
10692
- }, []);
10693
- const editorCreator = React.useCallback((div, options) => {
10694
- const editorValue = new roosterjsEditorCore.Editor(div, options);
10695
- // this is to fix issue when editor is created or re-rendered and has existing text
10696
- // Content model package has a correct behavior and this fix can be deleted
10697
- if (contentValue.current !== undefined && contentValue.current.length > 0) {
10698
- // in case if initialContent is not empty, RoosterJS doesn't set caret position to the end.
10699
- focusAndUpdateContent(editorValue, contentValue.current);
10700
- }
10701
- else if (initialContent !== undefined && initialContent.length > 0) {
10702
- // changing layout in rich text send box cause the editor to be recreated
10703
- // to keep the content, we need to set messageContent to the current content
10704
- focusAndUpdateContent(editorValue, initialContent);
10705
- }
10706
- editor.current = editorValue;
10707
- return editorValue;
10708
- },
10709
- // trigger force editor reset when strings are changed to update context menu strings
10710
- // see RosterJS documentation for 'editorCreator' for more details
10711
- // the editorCreator callback shouldn't be updated when the initialContent is changed
10712
- // eslint-disable-next-line react-hooks/exhaustive-deps
10713
- [strings]);
11080
+ const isDarkThemedValue = React.useMemo(() => {
11081
+ return isDarkThemed(theme);
11082
+ }, [theme]);
11083
+ React.useEffect(() => {
11084
+ var _a;
11085
+ (_a = editor.current) === null || _a === void 0 ? void 0 : _a.setDarkModeState(isDarkThemedValue);
11086
+ }, [isDarkThemedValue]);
10714
11087
  const placeholderPlugin = React.useMemo(() => {
10715
- return new roosterjsEditorPlugins.Watermark('');
11088
+ return new PlaceholderPlugin('');
10716
11089
  }, []);
10717
11090
  React.useEffect(() => {
10718
11091
  if (placeholderText !== undefined) {
10719
- placeholderPlugin.updateWatermark(placeholderText);
11092
+ placeholderPlugin.updatePlaceholder(placeholderText);
10720
11093
  }
10721
11094
  }, [placeholderPlugin, placeholderText]);
10722
- const plugins = React.useMemo(() => {
10723
- // contextPlugin and tableEditMenuProvider allow to show insert/delete menu for the table
10724
- const contextPlugin = roosterjsReact.createContextMenuPlugin();
10725
- const tableEditMenuProvider = createTableEditMenuProvider(strings);
10726
- const contentEdit = new roosterjsEditorPlugins.ContentEdit();
10727
- const updateContentPlugin = roosterjsReact.createUpdateContentPlugin(roosterjsReact.UpdateMode.OnContentChangedEvent | roosterjsReact.UpdateMode.OnUserInput, (content) => {
10728
- onChange && onChange(content);
11095
+ const toolbar = React.useMemo(() => {
11096
+ return React.createElement(RichTextToolbar, { plugin: toolbarPlugin, strings: strings });
11097
+ }, [strings, toolbarPlugin]);
11098
+ const updatePlugin = React.useMemo(() => {
11099
+ return new UpdateContentPlugin();
11100
+ }, []);
11101
+ React.useEffect(() => {
11102
+ // don't set callback in plugin constructor to update callback without plugin recreation
11103
+ updatePlugin.onUpdate = (event) => {
11104
+ if (editor.current === null) {
11105
+ return;
11106
+ }
11107
+ if (event === UpdateEvent.Blur || event === UpdateEvent.Dispose) {
11108
+ onContentModelUpdate && onContentModelUpdate(editor.current.getContentModelCopy('disconnected'));
11109
+ }
11110
+ else {
11111
+ onChange && onChange(roosterjsContentModelCore.exportContent(editor.current));
11112
+ }
11113
+ };
11114
+ }, [onChange, onContentModelUpdate, updatePlugin]);
11115
+ const keyboardInputPlugin = React.useMemo(() => {
11116
+ return new KeyboardInputPlugin();
11117
+ }, []);
11118
+ React.useEffect(() => {
11119
+ // don't set callback in plugin constructor to update callback without plugin recreation
11120
+ keyboardInputPlugin.onKeyDown = onKeyDown;
11121
+ }, [keyboardInputPlugin, onKeyDown]);
11122
+ const tableContextMenuPlugin = React.useMemo(() => {
11123
+ return new TableEditContextMenuProvider();
11124
+ }, []);
11125
+ React.useEffect(() => {
11126
+ tableContextMenuPlugin.updateStrings(strings);
11127
+ }, [tableContextMenuPlugin, strings]);
11128
+ const onContextMenuRender = React.useCallback((container, items, onDismiss) => {
11129
+ setContextMenuProps({
11130
+ items: items,
11131
+ target: container,
11132
+ onDismiss: onDismiss
10729
11133
  });
11134
+ }, []);
11135
+ const onContextMenuDismiss = React.useCallback(() => {
11136
+ setContextMenuProps(null);
11137
+ }, []);
11138
+ const plugins = React.useMemo(() => {
11139
+ const contentEdit = new roosterjsContentModelPlugins.EditPlugin();
11140
+ // AutoFormatPlugin previously was a part of the edit plugin
11141
+ const autoFormatPlugin = new roosterjsContentModelPlugins.AutoFormatPlugin({ autoBullet: true, autoNumbering: true, autoLink: true });
10730
11142
  const copyPastePlugin = new CopyPastePlugin();
11143
+ const roosterPastePlugin = new roosterjsContentModelPlugins.PastePlugin(false);
11144
+ const shortcutPlugin = new roosterjsContentModelPlugins.ShortcutPlugin();
11145
+ const contextMenuPlugin = new ContextMenuPlugin(onContextMenuRender, onContextMenuDismiss);
10731
11146
  return [
10732
- contentEdit,
10733
11147
  placeholderPlugin,
10734
- updateContentPlugin,
10735
- ribbonPlugin,
10736
- contextPlugin,
10737
- tableEditMenuProvider,
10738
- copyPastePlugin
11148
+ keyboardInputPlugin,
11149
+ contentEdit,
11150
+ autoFormatPlugin,
11151
+ updatePlugin,
11152
+ copyPastePlugin,
11153
+ roosterPastePlugin,
11154
+ toolbarPlugin,
11155
+ shortcutPlugin,
11156
+ // contextPlugin and tableEditMenuProvider allow to show insert/delete menu for the table
11157
+ contextMenuPlugin,
11158
+ tableContextMenuPlugin
10739
11159
  ];
10740
- }, [onChange, placeholderPlugin, ribbonPlugin, strings]);
10741
- const ribbon = React.useMemo(() => {
10742
- const buttons = ribbonButtons(theme);
10743
- return (React.createElement(roosterjsReact.Ribbon, { styles: ribbonStyle, buttons: buttons, plugin: ribbonPlugin, overflowButtonProps: {
10744
- styles: ribbonButtonStyle(theme),
10745
- menuProps: {
10746
- items: [], // CommandBar will determine items rendered in overflow
10747
- isBeakVisible: false,
10748
- styles: ribbonOverflowButtonStyle(theme)
11160
+ }, [
11161
+ onContextMenuRender,
11162
+ onContextMenuDismiss,
11163
+ placeholderPlugin,
11164
+ keyboardInputPlugin,
11165
+ updatePlugin,
11166
+ toolbarPlugin,
11167
+ tableContextMenuPlugin
11168
+ ]);
11169
+ React.useEffect(() => {
11170
+ var _a;
11171
+ const initialModel = createEditorInitialModel(initialContent, contentModel);
11172
+ if (editorDiv.current) {
11173
+ editor.current = new roosterjsContentModelCore.Editor(editorDiv.current, {
11174
+ inDarkMode: isDarkThemedValue,
11175
+ // doNotAdjustEditorColor is used to disable default color and background color for Rooster component
11176
+ doNotAdjustEditorColor: true,
11177
+ // TODO: confirm the color during inline images implementation
11178
+ imageSelectionBorderColor: 'blue',
11179
+ plugins: plugins,
11180
+ initialModel: initialModel,
11181
+ defaultModelToDomOptions: {
11182
+ formatApplierOverride: {
11183
+ // apply border and dataset formats for table
11184
+ border: borderApplier,
11185
+ dataset: dataSetApplier
11186
+ }
10749
11187
  }
10750
- }, strings: ribbonButtonsStrings(strings), "data-testid": 'rich-text-editor-ribbon' }));
10751
- }, [strings, ribbonPlugin, theme]);
10752
- const defaultFormat = React.useMemo(() => {
10753
- // without setting any styles, text input is not handled properly for tables (when insert or paste one in the editor)
10754
- // because of https://github.com/microsoft/roosterjs/blob/14dbb947e3ae94580109cbd05e48ceb05327c4dc/packages/roosterjs-editor-core/lib/corePlugins/TypeInContainerPlugin.ts#L75
10755
- // this issue is fixed for content model package
10756
- return {
10757
- backgroundColor: 'transparent'
11188
+ });
11189
+ }
11190
+ if (autoFocus === 'sendBoxTextField') {
11191
+ (_a = editor.current) === null || _a === void 0 ? void 0 : _a.focus();
11192
+ }
11193
+ return () => {
11194
+ if (editor.current) {
11195
+ editor.current.dispose();
11196
+ editor.current = null;
11197
+ }
10758
11198
  };
11199
+ // don't update the editor on deps update as everything is handled in separate hooks or plugins
11200
+ // eslint-disable-next-line react-hooks/exhaustive-deps
10759
11201
  }, []);
10760
11202
  return (React.createElement("div", { "data-testid": 'rich-text-editor-wrapper' },
10761
- showRichTextEditorFormatting && ribbon,
11203
+ showRichTextEditorFormatting && toolbar,
10762
11204
  React.createElement("div", { className: richTextEditorWrapperStyle(theme, !showRichTextEditorFormatting) },
10763
- React.createElement(roosterjsReact.Rooster, { defaultFormat: defaultFormat, inDarkMode: isDarkThemed(theme), plugins: plugins, className: richTextEditorStyle(props.styles), editorCreator: editorCreator,
10764
- // TODO: confirm the color during inline images implementation
10765
- imageSelectionBorderColor: 'blue',
10766
- // doNotAdjustEditorColor is used to fix the default background color for Rooster component
10767
- doNotAdjustEditorColor: true, "data-testid": 'rooster-rich-text-editor',
10768
- // if we don't use 'allowKeyboardEventPropagation' only the enter key is caught
10769
- onKeyDown: props.onKeyDown, focusOnInit: autoFocus === 'sendBoxTextField' }))));
11205
+ React.createElement("div", { ref: editorDiv, tabIndex: 0, role: "textbox", "aria-multiline": "true", "data-testid": 'rooster-rich-text-editor', className: richTextEditorStyle(props.styles) })),
11206
+ contextMenuProps && React.createElement(react.ContextualMenu, Object.assign({}, contextMenuProps, { calloutProps: { isBeakVisible: false } }))));
10770
11207
  });
10771
- const focusAndUpdateContent = (editor, content) => {
10772
- // setting focus before setting content, works for Chrome and Edge but not Safari
10773
- editor.setContent(content);
10774
- // this is a recommended way (by RoosterJS team) to set focus at the end of the text
10775
- // RoosterJS v9 has this issue fixed and this code can be removed
10776
- // CompatibleContentPosition.DomEnd shouldn't be used here as it set focus after the editor div
10777
- editor.insertContent('<span id="focus-position-span"></span>', { position: roosterjsEditorTypesCompatible.CompatibleContentPosition.End });
10778
- const elements = editor.queryElements('#focus-position-span');
10779
- if (elements.length > 0) {
10780
- const placeholder = editor.queryElements('#focus-position-span')[0];
10781
- editor.select(placeholder, roosterjsEditorTypesCompatible.CompatiblePositionType.Before);
10782
- placeholder.remove();
11208
+ const createEditorInitialModel = (initialContent, contentModel) => {
11209
+ if (contentModel) {
11210
+ // contentModel is the current content of the editor
11211
+ return contentModel;
11212
+ }
11213
+ else {
11214
+ const initialContentValue = initialContent;
11215
+ const initialModel = initialContentValue && initialContentValue.length > 0 ? roosterjsContentModelCore.createModelFromHtml(initialContentValue) : undefined;
11216
+ if (initialModel && initialModel.blocks.length > 0) {
11217
+ // lastBlock should have blockType = paragraph, otherwise add a new paragraph
11218
+ // to set focus to the end of the content
11219
+ let lastBlock = initialModel.blocks[initialModel.blocks.length - 1];
11220
+ if ((lastBlock === null || lastBlock === void 0 ? void 0 : lastBlock.blockType) === 'Paragraph') ;
11221
+ else {
11222
+ lastBlock = roosterjsContentModelDom.createParagraph(true);
11223
+ initialModel.blocks.push(lastBlock);
11224
+ }
11225
+ const marker = roosterjsContentModelDom.createSelectionMarker();
11226
+ lastBlock.segments.push(marker);
11227
+ roosterjsContentModelDom.setSelection(initialModel, marker);
11228
+ }
11229
+ return initialModel;
10783
11230
  }
10784
11231
  };
10785
11232
 
@@ -10825,37 +11272,50 @@ const RichTextInputBoxComponent = (props) => {
10825
11272
  /* @conditional-compile-remove(attachment-upload) */
10826
11273
  onRenderAttachmentUploads,
10827
11274
  /* @conditional-compile-remove(attachment-upload) */
10828
- hasAttachments, richTextEditorStyleProps, isHorizontalLayoutDisabled = false, content, autoFocus, onTyping } = props;
11275
+ hasAttachments, richTextEditorStyleProps, isHorizontalLayoutDisabled = false, autoFocus, onTyping } = props;
10829
11276
  const theme = useTheme();
10830
- const [showRichTextEditorFormatting, setShowRichTextEditorFormatting] = React.useState(false);
10831
- const onRenderRichTextEditorIcon = React.useCallback((isHover) => (React.createElement(react.Icon, { iconName: isHover || showRichTextEditorFormatting ? 'RichTextEditorButtonIconFilled' : 'RichTextEditorButtonIcon', className: richTextFormatButtonIconStyle(theme, !disabled && (isHover || showRichTextEditorFormatting)) })), [disabled, showRichTextEditorFormatting, theme]);
11277
+ // undefined is used to indicate that the rich text editor toolbar state wasn't changed yet
11278
+ const [showRichTextEditorFormatting, setShowRichTextEditorFormatting] = React.useState(undefined);
11279
+ const [contentModel, setContentModel] = React.useState(undefined);
11280
+ const onRenderRichTextEditorIcon = React.useCallback((isHover) => {
11281
+ const isRichTextEditorToolbarShown = showRichTextEditorFormatting === true;
11282
+ return (React.createElement(react.Icon, { iconName: isHover || isRichTextEditorToolbarShown ? 'RichTextEditorButtonIconFilled' : 'RichTextEditorButtonIcon', className: richTextFormatButtonIconStyle(theme, !disabled && (isHover || isRichTextEditorToolbarShown)) }));
11283
+ }, [disabled, showRichTextEditorFormatting, theme]);
11284
+ React.useEffect(() => {
11285
+ var _a;
11286
+ if (showRichTextEditorFormatting !== undefined) {
11287
+ // Focus the editor when toolbar shown/hidden
11288
+ (_a = editorComponentRef.current) === null || _a === void 0 ? void 0 : _a.focus();
11289
+ }
11290
+ // we don't need execute this useEffect if editorComponentRef is changed
11291
+ // eslint-disable-next-line react-hooks/exhaustive-deps
11292
+ }, [showRichTextEditorFormatting]);
10832
11293
  const actionButtons = React.useMemo(() => {
10833
11294
  return (React.createElement(react.Stack.Item, { align: "end", className: richTextActionButtonsStackStyle },
10834
11295
  React.createElement(react.Stack, { horizontal: true },
10835
11296
  React.createElement(InputBoxButton, { onRenderIcon: onRenderRichTextEditorIcon, onClick: (e) => {
10836
- var _a;
10837
- setShowRichTextEditorFormatting(!showRichTextEditorFormatting);
10838
- (_a = editorComponentRef.current) === null || _a === void 0 ? void 0 : _a.focus();
11297
+ const isRichTextEditorToolbarShown = showRichTextEditorFormatting === true;
11298
+ setShowRichTextEditorFormatting(!isRichTextEditorToolbarShown);
10839
11299
  e.stopPropagation(); // Prevents the click from bubbling up and triggering a focus event on the chat.
10840
11300
  }, ariaLabel: strings.richTextFormatButtonTooltip, tooltipContent: strings.richTextFormatButtonTooltip, className: richTextActionButtonsStyle, "data-testId": 'rich-text-input-box-format-button' }),
10841
11301
  React.createElement(react.Icon, { iconName: "RichTextDividerIcon", className: richTextActionButtonsDividerStyle(theme) }),
10842
11302
  actionComponents)));
10843
11303
  }, [
10844
11304
  actionComponents,
10845
- editorComponentRef,
10846
11305
  onRenderRichTextEditorIcon,
10847
11306
  showRichTextEditorFormatting,
10848
11307
  strings.richTextFormatButtonTooltip,
10849
11308
  theme
10850
11309
  ]);
10851
11310
  const richTextEditorStyle = React.useMemo(() => {
10852
- return richTextEditorStyleProps(showRichTextEditorFormatting);
11311
+ return richTextEditorStyleProps(showRichTextEditorFormatting === true);
10853
11312
  }, [richTextEditorStyleProps, showRichTextEditorFormatting]);
10854
11313
  const onKeyDown = React.useCallback((ev) => {
10855
11314
  if (isEnterKeyEventFromCompositionSession(ev)) {
10856
11315
  return;
10857
11316
  }
10858
- if (ev.key === 'Enter' && ev.shiftKey === false && !showRichTextEditorFormatting) {
11317
+ const isRichTextEditorToolbarShown = showRichTextEditorFormatting === true;
11318
+ if (ev.key === 'Enter' && ev.shiftKey === false && !isRichTextEditorToolbarShown) {
10859
11319
  ev.preventDefault();
10860
11320
  onEnterKeyDown && onEnterKeyDown();
10861
11321
  }
@@ -10864,14 +11324,18 @@ const RichTextInputBoxComponent = (props) => {
10864
11324
  }
10865
11325
  }, [onEnterKeyDown, showRichTextEditorFormatting, onTyping]);
10866
11326
  const useHorizontalLayout = React.useMemo(() => {
11327
+ const isRichTextEditorToolbarShown = showRichTextEditorFormatting === true;
10867
11328
  return (!isHorizontalLayoutDisabled &&
10868
- !showRichTextEditorFormatting &&
11329
+ !isRichTextEditorToolbarShown &&
10869
11330
  /* @conditional-compile-remove(attachment-upload) */ !hasAttachments);
10870
11331
  }, [
10871
11332
  isHorizontalLayoutDisabled,
10872
11333
  showRichTextEditorFormatting,
10873
11334
  /* @conditional-compile-remove(attachment-upload) */ hasAttachments
10874
11335
  ]);
11336
+ const onContentModelUpdate = React.useCallback((contentModel) => {
11337
+ setContentModel(contentModel);
11338
+ }, []);
10875
11339
  return (React.createElement("div", { className: richTextBorderBoxStyle({
10876
11340
  theme: theme,
10877
11341
  disabled: !!disabled
@@ -10879,7 +11343,7 @@ const RichTextInputBoxComponent = (props) => {
10879
11343
  React.createElement(react.Stack, { grow: true, horizontal: useHorizontalLayout, horizontalAlign: useHorizontalLayout ? 'end' : 'space-between', className: inputBoxContentStackStyle, wrap: useHorizontalLayout },
10880
11344
  React.createElement(react.Stack, { grow: true, className: inputBoxRichTextStackStyle },
10881
11345
  React.createElement(react.Stack.Item, { className: inputBoxRichTextStackItemStyle },
10882
- React.createElement(RichTextEditor, { content: content, initialContent: initialContent, placeholderText: placeholderText, onChange: onChange, onKeyDown: onKeyDown, ref: editorComponentRef, strings: strings, showRichTextEditorFormatting: showRichTextEditorFormatting, styles: richTextEditorStyle, autoFocus: autoFocus })),
11346
+ React.createElement(RichTextEditor, { contentModel: contentModel, initialContent: initialContent, placeholderText: placeholderText, onChange: onChange, onKeyDown: onKeyDown, ref: editorComponentRef, strings: strings, showRichTextEditorFormatting: showRichTextEditorFormatting === true, styles: richTextEditorStyle, autoFocus: autoFocus, onContentModelUpdate: onContentModelUpdate })),
10883
11347
  /* @conditional-compile-remove(attachment-upload) */ onRenderAttachmentUploads &&
10884
11348
  onRenderAttachmentUploads()),
10885
11349
  actionButtons)));
@@ -10986,6 +11450,13 @@ const RichTextSendBox = (props) => {
10986
11450
  setContentValueOverflow(isMessageTooLong(newValue.length));
10987
11451
  setContentValue(newValue);
10988
11452
  }, []);
11453
+ const hasContent = React.useMemo(() => {
11454
+ var _a;
11455
+ // get plain text content from the editor to check if the message is empty
11456
+ // as the content may contain tags even when the content is empty
11457
+ const plainTextContent = (_a = editorComponentRef.current) === null || _a === void 0 ? void 0 : _a.getPlainContent();
11458
+ return sanitizeText(contentValue !== null && contentValue !== void 0 ? contentValue : '').length > 0 && sanitizeText(plainTextContent !== null && plainTextContent !== void 0 ? plainTextContent : '').length > 0;
11459
+ }, [contentValue]);
10989
11460
  /* @conditional-compile-remove(attachment-upload) */
10990
11461
  const toAttachmentMetadata = React.useCallback((attachmentsWithProgress) => {
10991
11462
  return attachmentsWithProgress === null || attachmentsWithProgress === void 0 ? void 0 : attachmentsWithProgress.filter((attachment) => {
@@ -11001,7 +11472,7 @@ const RichTextSendBox = (props) => {
11001
11472
  });
11002
11473
  }, []);
11003
11474
  const sendMessageOnClick = React.useCallback(() => {
11004
- var _a, _b, _c;
11475
+ var _a, _b;
11005
11476
  if (disabled || contentValueOverflow) {
11006
11477
  return;
11007
11478
  }
@@ -11014,14 +11485,6 @@ const RichTextSendBox = (props) => {
11014
11485
  return;
11015
11486
  }
11016
11487
  const message = contentValue;
11017
- // get plain text content from the editor to check if the message is empty
11018
- // as the content may contain tags even when the content is empty
11019
- const plainTextContent = (_a = editorComponentRef.current) === null || _a === void 0 ? void 0 : _a.getPlainContent();
11020
- const hasContent = !isContentEmpty({
11021
- plainTextContent,
11022
- content: message,
11023
- placeholder: strings.placeholderText
11024
- });
11025
11488
  // we don't want to send empty messages including spaces, newlines, tabs
11026
11489
  // Message can be empty if there is a valid attachment upload
11027
11490
  if (hasContent || /* @conditional-compile-remove(attachment-upload) */ isAttachmentUploadCompleted(attachments)) {
@@ -11034,8 +11497,8 @@ const RichTextSendBox = (props) => {
11034
11497
  type: 'html'
11035
11498
  });
11036
11499
  setContentValue('');
11037
- (_b = editorComponentRef.current) === null || _b === void 0 ? void 0 : _b.setEmptyContent();
11038
- (_c = editorComponentRef.current) === null || _c === void 0 ? void 0 : _c.focus();
11500
+ (_a = editorComponentRef.current) === null || _a === void 0 ? void 0 : _a.setEmptyContent();
11501
+ (_b = editorComponentRef.current) === null || _b === void 0 ? void 0 : _b.focus();
11039
11502
  }
11040
11503
  }, [
11041
11504
  disabled,
@@ -11043,7 +11506,7 @@ const RichTextSendBox = (props) => {
11043
11506
  /* @conditional-compile-remove(attachment-upload) */
11044
11507
  attachments,
11045
11508
  contentValue,
11046
- strings.placeholderText,
11509
+ hasContent,
11047
11510
  /* @conditional-compile-remove(attachment-upload) */
11048
11511
  strings.attachmentUploadsPendingError,
11049
11512
  onSendMessage,
@@ -11066,17 +11529,6 @@ const RichTextSendBox = (props) => {
11066
11529
  attachmentUploadsPendingError,
11067
11530
  systemMessage
11068
11531
  ]);
11069
- const hasContent = React.useMemo(() => {
11070
- var _a;
11071
- // get plain text content from the editor to check if the message is empty
11072
- // as the content may contain tags even when the content is empty
11073
- const plainTextContent = (_a = editorComponentRef.current) === null || _a === void 0 ? void 0 : _a.getPlainContent();
11074
- return !isContentEmpty({
11075
- plainTextContent: plainTextContent,
11076
- content: contentValue,
11077
- placeholder: strings.placeholderText
11078
- });
11079
- }, [contentValue, strings.placeholderText]);
11080
11532
  const onRenderSendIcon = React.useCallback((isHover) => {
11081
11533
  return (React.createElement(react.Icon, { iconName: isHover && hasContent ? 'SendBoxSendHovered' : 'SendBoxSend', className: sendIconStyle({
11082
11534
  theme,
@@ -11152,34 +11604,12 @@ const RichTextSendBox = (props) => {
11152
11604
  }, [attachments]);
11153
11605
  return (React.createElement(react.Stack, null,
11154
11606
  React.createElement(RichTextSendBoxErrors, Object.assign({}, sendBoxErrorsProps)),
11155
- React.createElement(RichTextInputBoxComponent
11156
- // in case when format bar is shown, the editor is re-rendered that causes the content to be lost
11157
- // setting the content will ensure that the latest content is used when editor is re-rendered
11158
- , {
11159
- // in case when format bar is shown, the editor is re-rendered that causes the content to be lost
11160
- // setting the content will ensure that the latest content is used when editor is re-rendered
11161
- content: contentValue, placeholderText: strings.placeholderText, autoFocus: autoFocus, onChange: setContent, onEnterKeyDown: sendMessageOnClick, onTyping: onTyping, editorComponentRef: editorComponentRef, strings: strings, disabled: disabled, actionComponents: sendButton, richTextEditorStyleProps: sendBoxRichTextEditorStyle,
11607
+ React.createElement(RichTextInputBoxComponent, { placeholderText: strings.placeholderText, autoFocus: autoFocus, onChange: setContent, onEnterKeyDown: sendMessageOnClick, onTyping: onTyping, editorComponentRef: editorComponentRef, strings: strings, disabled: disabled, actionComponents: sendButton, richTextEditorStyleProps: sendBoxRichTextEditorStyle,
11162
11608
  /* @conditional-compile-remove(attachment-upload) */
11163
11609
  onRenderAttachmentUploads: onRenderAttachmentUploads,
11164
11610
  /* @conditional-compile-remove(attachment-upload) */
11165
11611
  hasAttachments: hasAttachmentUploads })));
11166
11612
  };
11167
- /**
11168
- * Checks if the content of the rich text editor is empty.
11169
- *
11170
- * @param {Object} params - The parameters for the function.
11171
- * @param {string | undefined} params.plainTextContent - The plain text content of the editor.
11172
- * @param {string} params.content - The HTML content of the editor.
11173
- * @param {string} params.placeholder - The placeholder text of the editor.
11174
- * @returns {boolean} - True if the content is empty, false otherwise.
11175
- */
11176
- const isContentEmpty = ({ plainTextContent, content, placeholder }) => {
11177
- // RoosterJS returns placeholder text as plain text when the editor is empty and in this case,
11178
- // plainTextContent contains only placeholder text but content doesn't include the placeholder text
11179
- // this needs to be reviewed after migration to the content model packages.
11180
- const plainTextContainsPlaceholderOnly = plainTextContent === placeholder && !content.includes(placeholder);
11181
- return plainTextContainsPlaceholderOnly || sanitizeText(plainTextContent !== null && plainTextContent !== void 0 ? plainTextContent : '').length === 0;
11182
- };
11183
11613
 
11184
11614
  // Copyright (c) Microsoft Corporation.
11185
11615
  // Licensed under the MIT License.
@@ -11420,7 +11850,9 @@ const useChatMessageStyles = reactComponents.makeStyles({
11420
11850
  }, '& video': {
11421
11851
  maxWidth: '100% !important', // Add !important to make sure it won't be overridden by style defined in element
11422
11852
  height: 'auto !important'
11423
- }, '& p': Object.assign({}, reactComponents.shorthands.marginBlock('0.125rem')), '& blockquote': Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({ backgroundColor: reactComponents.tokens.colorBrandBackgroundInverted, clear: 'left', minHeight: '2.25rem', width: 'fit-content', marginTop: '7px', marginRight: '0px', marginLeft: '0px', marginBottom: '7px', paddingTop: '7px', paddingRight: '15px', paddingLeft: '15px', paddingBottom: '7px' }, reactComponents.shorthands.border('solid')), reactComponents.shorthands.borderRadius('4px')), reactComponents.shorthands.borderWidth('1px')), reactComponents.shorthands.borderColor(reactComponents.tokens.colorNeutralStroke1Selected)), { borderLeftWidth: '4px' }) }),
11853
+ }, '& p': Object.assign({}, reactComponents.shorthands.marginBlock('0.125rem')), '& blockquote': Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({ backgroundColor: reactComponents.tokens.colorBrandBackgroundInverted, clear: 'left', minHeight: '2.25rem', width: 'fit-content', marginTop: '7px', marginRight: '0px', marginLeft: '0px', marginBottom: '7px', paddingTop: '7px', paddingRight: '15px', paddingLeft: '15px', paddingBottom: '7px' }, reactComponents.shorthands.border('solid')), reactComponents.shorthands.borderRadius('4px')), reactComponents.shorthands.borderWidth('1px')), reactComponents.shorthands.borderColor(reactComponents.tokens.colorNeutralStroke1Selected)), { borderLeftWidth: '4px' }), '& code': {
11854
+ whiteSpace: 'pre-wrap'
11855
+ } }),
11424
11856
  bodyWithPlaceholderImage: {
11425
11857
  '& img[src=""]': {
11426
11858
  display: 'block',
@@ -11782,9 +12214,7 @@ const MessageStatusIcon = (props) => {
11782
12214
  const { shouldAnnounce, iconName, iconClassName, ariaLabel } = props;
11783
12215
  return (React.createElement(React.Fragment, null,
11784
12216
  ariaLabel && shouldAnnounce && React.createElement(LiveMessage, { message: ariaLabel, ariaLive: "polite" }),
11785
- React.createElement("div", {
11786
- // make icon accessible
11787
- tabIndex: 0 },
12217
+ React.createElement("div", null,
11788
12218
  React.createElement(react.Icon, { role: 'status', "aria-live": shouldAnnounce ? 'polite' : 'off', "data-ui-id": 'chat-composite-message-status-icon', "aria-label": ariaLabel, iconName: iconName, className: iconClassName }))));
11789
12219
  };
11790
12220
 
@@ -13079,7 +13509,7 @@ class _ErrorBoundary extends React.Component {
13079
13509
  // Copyright (c) Microsoft Corporation.
13080
13510
  // Licensed under the MIT License.
13081
13511
  /* @conditional-compile-remove(rich-text-editor) */
13082
- const ChatMessageComponentAsRichTextEditBox = React.lazy(() => Promise.resolve().then(function () { return require('./ChatMessageComponentAsRichTextEditBox-BLFNaheX.js'); }));
13512
+ const ChatMessageComponentAsRichTextEditBox = React.lazy(() => Promise.resolve().then(function () { return require('./ChatMessageComponentAsRichTextEditBox-CDsn-zU7.js'); }));
13083
13513
  /**
13084
13514
  * @private
13085
13515
  * Use this function to load RoosterJS dependencies early in the lifecycle.
@@ -13087,7 +13517,7 @@ const ChatMessageComponentAsRichTextEditBox = React.lazy(() => Promise.resolve()
13087
13517
  *
13088
13518
  * @conditional-compile-remove(rich-text-editor)
13089
13519
  */
13090
- const loadChatMessageComponentAsRichTextEditBox = () => Promise.resolve().then(function () { return require('./ChatMessageComponentAsRichTextEditBox-BLFNaheX.js'); });
13520
+ const loadChatMessageComponentAsRichTextEditBox = () => Promise.resolve().then(function () { return require('./ChatMessageComponentAsRichTextEditBox-CDsn-zU7.js'); });
13091
13521
  /**
13092
13522
  * @private
13093
13523
  */
@@ -20628,7 +21058,7 @@ const ReactionButton = (props) => {
20628
21058
  React.createElement(react.IconButton, { key: index, onClick: () => {
20629
21059
  props.onReactionClick(emoji);
20630
21060
  dismissMenu();
20631
- }, className: classname })));
21061
+ }, className: classname, ariaLabel: emojiButtonTooltip.get(emoji) })));
20632
21062
  })));
20633
21063
  const emojiList = [
20634
21064
  { key: 'reactions', itemType: react.ContextualMenuItemType.Normal, onRender: renderEmoji }
@@ -22015,15 +22445,9 @@ const _VideoEffectsItem = (props) => {
22015
22445
  disabled,
22016
22446
  backgroundImage
22017
22447
  }), [backgroundImage, disabled, isSelected, theme]);
22018
- const componentRef = React.createRef();
22019
- React.useEffect(() => {
22020
- if (props.focusOnMount && componentRef.current) {
22021
- componentRef.current.focus();
22022
- }
22023
- }, [componentRef, props.focusOnMount]);
22024
22448
  return (React.createElement(react.TooltipHost, Object.assign({}, props.tooltipProps),
22025
22449
  React.createElement(react.Stack, { key: props.itemKey, className: react.mergeStyles((_f = props.styles) === null || _f === void 0 ? void 0 : _f.root), verticalAlign: "center", horizontalAlign: "center", "data-ui-id": `video-effects-item`, "aria-label": (_g = props.ariaLabel) !== null && _g !== void 0 ? _g : props.itemKey, "aria-disabled": props.disabled },
22026
- React.createElement(react.DefaultButton, { styles: containerStyles(), onClick: disabled ? undefined : () => { var _a; return (_a = props.onSelect) === null || _a === void 0 ? void 0 : _a.call(props, props.itemKey); }, componentRef: componentRef, autoFocus: props.focusOnMount },
22450
+ React.createElement(react.DefaultButton, { styles: containerStyles(), onClick: disabled ? undefined : () => { var _a; return (_a = props.onSelect) === null || _a === void 0 ? void 0 : _a.call(props, props.itemKey); }, componentRef: props.componentRef },
22027
22451
  React.createElement(react.Stack, { horizontalAlign: 'center' },
22028
22452
  props.iconProps && (React.createElement(react.Stack.Item, { className: iconContainerStyles },
22029
22453
  React.createElement(react.Icon, Object.assign({}, props.iconProps)))),
@@ -22072,7 +22496,7 @@ const _VideoBackgroundEffectsPicker = (props) => {
22072
22496
  return (React.createElement(react.Stack, { className: react.mergeStyles((_a = props.styles) === null || _a === void 0 ? void 0 : _a.rowRoot), wrap: props.itemsPerRow === 'wrap', horizontal: true, key: rowIndex, tokens: { childrenGap: '0.5rem' }, "data-ui-id": "video-effects-picker-row" },
22073
22497
  options.map((option, i) => {
22074
22498
  if (i === 0 && rowIndex === 0) {
22075
- return (React.createElement(_VideoEffectsItem, Object.assign({}, option, { itemKey: option.itemKey, key: option.itemKey, focusOnMount: !props.ignoreFocusOnMount })));
22499
+ return (React.createElement(_VideoEffectsItem, Object.assign({}, option, { itemKey: option.itemKey, key: option.itemKey, componentRef: props.componentRef })));
22076
22500
  }
22077
22501
  return React.createElement(_VideoEffectsItem, Object.assign({}, option, { itemKey: option.itemKey, key: option.itemKey }));
22078
22502
  }),
@@ -27190,7 +27614,7 @@ const AttachmentDownloadErrorBar = (props) => {
27190
27614
  /**
27191
27615
  * Wrapper for RichTextSendBox component to allow us to use usePropsFor with richTextSendBox with lazy loading
27192
27616
  */
27193
- const RichTextSendBoxWrapper = React.lazy(() => Promise.resolve().then(function () { return require('./RichTextSendBoxWrapper-BhTpuspw.js'); }).then((module) => ({ default: module.RichTextSendBoxWrapper })));
27617
+ const RichTextSendBoxWrapper = React.lazy(() => Promise.resolve().then(function () { return require('./RichTextSendBoxWrapper-Dm4S2bpT.js'); }).then((module) => ({ default: module.RichTextSendBoxWrapper })));
27194
27618
  /**
27195
27619
  * @private
27196
27620
  * Use this function to load RoosterJS dependencies early in the lifecycle.
@@ -27198,7 +27622,7 @@ const RichTextSendBoxWrapper = React.lazy(() => Promise.resolve().then(function
27198
27622
  *
27199
27623
  /* @conditional-compile-remove(rich-text-editor-composite-support)
27200
27624
  */
27201
- const loadRichTextSendBox = () => Promise.resolve().then(function () { return require('./RichTextSendBoxWrapper-BhTpuspw.js'); }).then((module) => ({ default: module.RichTextSendBoxWrapper }));
27625
+ const loadRichTextSendBox = () => Promise.resolve().then(function () { return require('./RichTextSendBoxWrapper-Dm4S2bpT.js'); }).then((module) => ({ default: module.RichTextSendBoxWrapper }));
27202
27626
  /**
27203
27627
  * @private
27204
27628
  */
@@ -32160,9 +32584,9 @@ const VideoEffectsPaneContent = (props) => {
32160
32584
  };
32161
32585
  adapter.updateSelectedVideoBackgroundEffect(noneEffect);
32162
32586
  }
32163
- return VideoEffectsPaneTrampoline(onDismissError, activeVideoEffectError, selectableVideoEffects, onEffectChange);
32587
+ return VideoEffectsPaneTrampoline(onDismissError, props.updateFocusHandle, activeVideoEffectError, selectableVideoEffects, onEffectChange);
32164
32588
  };
32165
- const VideoEffectsPaneTrampoline = (onDismissError, activeVideoEffectError, selectableVideoEffects, onEffectChange) => {
32589
+ const VideoEffectsPaneTrampoline = (onDismissError, updateFocusHandle, activeVideoEffectError, selectableVideoEffects, onEffectChange) => {
32166
32590
  const selectedEffect = useSelector$1(activeVideoBackgroundEffectSelector);
32167
32591
  const isCameraOn = useSelector$1(localVideoSelector).isAvailable;
32168
32592
  const showWarning = !isCameraOn && selectedEffect !== 'none';
@@ -32170,7 +32594,7 @@ const VideoEffectsPaneTrampoline = (onDismissError, activeVideoEffectError, sele
32170
32594
  return (React.createElement(react.Stack, { tokens: { childrenGap: '0.75rem' }, className: react.mergeStyles({ paddingLeft: '0.5rem' }) },
32171
32595
  activeVideoEffectError && isCameraOn && (React.createElement(react.MessageBar, { messageBarType: react.MessageBarType.error, onDismiss: () => onDismissError(activeVideoEffectError) }, locale.strings.call.unableToStartVideoEffect)),
32172
32596
  showWarning && (React.createElement(react.MessageBar, { messageBarType: react.MessageBarType.warning }, locale.strings.call.cameraOffBackgroundEffectWarningText)),
32173
- React.createElement(_VideoBackgroundEffectsPicker, { label: locale.strings.call.videoEffectsPaneBackgroundSelectionTitle, styles: backgroundPickerStyles, options: selectableVideoEffects !== null && selectableVideoEffects !== void 0 ? selectableVideoEffects : [], onChange: onEffectChange, selectedEffectKey: selectedEffect })));
32597
+ React.createElement(_VideoBackgroundEffectsPicker, { label: locale.strings.call.videoEffectsPaneBackgroundSelectionTitle, styles: backgroundPickerStyles, options: selectableVideoEffects !== null && selectableVideoEffects !== void 0 ? selectableVideoEffects : [], onChange: onEffectChange, selectedEffectKey: selectedEffect, componentRef: updateFocusHandle })));
32174
32598
  };
32175
32599
  const backgroundPickerStyles = {
32176
32600
  label: {
@@ -32199,12 +32623,13 @@ const useVideoEffectsPane = (updateSidePaneRenderer, mobileView, latestErrors, o
32199
32623
  return (React.createElement(SidePaneHeader, { onClose: closePane, headingText: locale.strings.call.videoEffectsPaneTitle, dismissSidePaneButtonAriaLabel: (_b = (_a = locale.strings.call.dismissSidePaneButtonLabel) !== null && _a !== void 0 ? _a : locale.strings.callWithChat.dismissSidePaneButtonLabel) !== null && _b !== void 0 ? _b : 'Close', mobileView: mobileView }));
32200
32624
  }, [closePane, locale.strings, mobileView]);
32201
32625
  const latestVideoEffectError = latestErrors.find((error) => error.type === 'unableToStartVideoEffect');
32626
+ const updateFocusHandle = React.useMemo(() => React.createRef(), []);
32202
32627
  const onRenderContent = React.useCallback(() => {
32203
32628
  return (React.createElement(VideoEffectsPaneContent, { onDismissError: onDismissError, activeVideoEffectError: latestVideoEffectError, activeVideoEffectChange: () => {
32204
32629
  // Clear any existing video effects error when the user clicks on a new video effect
32205
32630
  latestVideoEffectError && (onDismissError === null || onDismissError === void 0 ? void 0 : onDismissError(latestVideoEffectError));
32206
- } }));
32207
- }, [latestVideoEffectError, onDismissError]);
32631
+ }, updateFocusHandle: updateFocusHandle }));
32632
+ }, [latestVideoEffectError, onDismissError, updateFocusHandle]);
32208
32633
  const sidePaneRenderer = React.useMemo(() => ({
32209
32634
  headerRenderer: onRenderHeader,
32210
32635
  contentRenderer: onRenderContent,
@@ -32212,7 +32637,9 @@ const useVideoEffectsPane = (updateSidePaneRenderer, mobileView, latestErrors, o
32212
32637
  }), [onRenderContent, onRenderHeader]);
32213
32638
  const openPane = React.useCallback(() => {
32214
32639
  updateSidePaneRenderer(sidePaneRenderer);
32215
- }, [sidePaneRenderer, updateSidePaneRenderer]);
32640
+ // Run in a setTimeout as it must be called only once the imperative handle is available
32641
+ setTimeout(() => { var _a; return (_a = updateFocusHandle.current) === null || _a === void 0 ? void 0 : _a.focus(); }, 0);
32642
+ }, [sidePaneRenderer, updateSidePaneRenderer, updateFocusHandle]);
32216
32643
  const isOpen = useIsParticularSidePaneOpen(VIDEO_EFFECTS_SIDE_PANE_ID);
32217
32644
  // Update pane renderer if it is open and the openPane dep changes
32218
32645
  React.useEffect(() => {
@@ -39878,4 +40305,4 @@ exports.useTeamsCall = useTeamsCall;
39878
40305
  exports.useTeamsCallAdapter = useTeamsCallAdapter;
39879
40306
  exports.useTeamsCallAgent = useTeamsCallAgent;
39880
40307
  exports.useTheme = useTheme;
39881
- //# sourceMappingURL=index-C9I6Mcil.js.map
40308
+ //# sourceMappingURL=index-DO36MBbq.js.map