@pega/cosmos-react-rte 9.0.0-build.9.8 → 9.0.0

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 (191) hide show
  1. package/lib/components/DynamicContentEditor/DynamicContentEditor.d.ts +4 -2
  2. package/lib/components/DynamicContentEditor/DynamicContentEditor.d.ts.map +1 -1
  3. package/lib/components/DynamicContentEditor/DynamicContentEditor.js +64 -59
  4. package/lib/components/DynamicContentEditor/DynamicContentEditor.js.map +1 -1
  5. package/lib/components/Editor/Editor.context.d.ts +6 -6
  6. package/lib/components/Editor/Editor.context.d.ts.map +1 -1
  7. package/lib/components/Editor/Editor.context.js +1 -1
  8. package/lib/components/Editor/Editor.context.js.map +1 -1
  9. package/lib/components/Editor/Editor.d.ts +1 -10
  10. package/lib/components/Editor/Editor.d.ts.map +1 -1
  11. package/lib/components/Editor/Editor.js +301 -490
  12. package/lib/components/Editor/Editor.js.map +1 -1
  13. package/lib/components/Editor/Editor.styles.d.ts +37 -7
  14. package/lib/components/Editor/Editor.styles.d.ts.map +1 -1
  15. package/lib/components/Editor/Editor.styles.js +60 -30
  16. package/lib/components/Editor/Editor.styles.js.map +1 -1
  17. package/lib/components/Editor/Editor.test-ids.d.ts +2 -1
  18. package/lib/components/Editor/Editor.test-ids.d.ts.map +1 -1
  19. package/lib/components/Editor/Editor.test-ids.js +2 -0
  20. package/lib/components/Editor/Editor.test-ids.js.map +1 -1
  21. package/lib/components/Editor/Editor.types.d.ts +34 -14
  22. package/lib/components/Editor/Editor.types.d.ts.map +1 -1
  23. package/lib/components/Editor/Editor.types.js.map +1 -1
  24. package/lib/components/Editor/IframeTiptapEditor.d.ts +30 -0
  25. package/lib/components/Editor/IframeTiptapEditor.d.ts.map +1 -0
  26. package/lib/components/Editor/IframeTiptapEditor.js +695 -0
  27. package/lib/components/Editor/IframeTiptapEditor.js.map +1 -0
  28. package/lib/components/Editor/ImageActionButtons.d.ts +20 -0
  29. package/lib/components/Editor/ImageActionButtons.d.ts.map +1 -0
  30. package/lib/components/Editor/ImageActionButtons.js +84 -0
  31. package/lib/components/Editor/ImageActionButtons.js.map +1 -0
  32. package/lib/components/Editor/ImageEditDialog.d.ts +17 -0
  33. package/lib/components/Editor/ImageEditDialog.d.ts.map +1 -0
  34. package/lib/components/Editor/ImageEditDialog.js +90 -0
  35. package/lib/components/Editor/ImageEditDialog.js.map +1 -0
  36. package/lib/components/Editor/TableCellMenu.d.ts +35 -0
  37. package/lib/components/Editor/TableCellMenu.d.ts.map +1 -0
  38. package/lib/components/Editor/TableCellMenu.js +120 -0
  39. package/lib/components/Editor/TableCellMenu.js.map +1 -0
  40. package/lib/components/Editor/Toolbar/AIRewriteButton.d.ts +17 -0
  41. package/lib/components/Editor/Toolbar/AIRewriteButton.d.ts.map +1 -0
  42. package/lib/components/Editor/Toolbar/AIRewriteButton.js +79 -0
  43. package/lib/components/Editor/Toolbar/AIRewriteButton.js.map +1 -0
  44. package/lib/components/Editor/Toolbar/AlignmentSelect.d.ts +8 -0
  45. package/lib/components/Editor/Toolbar/AlignmentSelect.d.ts.map +1 -0
  46. package/lib/components/Editor/Toolbar/AlignmentSelect.js +137 -0
  47. package/lib/components/Editor/Toolbar/AlignmentSelect.js.map +1 -0
  48. package/lib/components/Editor/Toolbar/AnchorButton.d.ts +3 -4
  49. package/lib/components/Editor/Toolbar/AnchorButton.d.ts.map +1 -1
  50. package/lib/components/Editor/Toolbar/AnchorButton.js +156 -82
  51. package/lib/components/Editor/Toolbar/AnchorButton.js.map +1 -1
  52. package/lib/components/Editor/Toolbar/ColorPickerButton.d.ts +9 -0
  53. package/lib/components/Editor/Toolbar/ColorPickerButton.d.ts.map +1 -0
  54. package/lib/components/Editor/Toolbar/ColorPickerButton.js +190 -0
  55. package/lib/components/Editor/Toolbar/ColorPickerButton.js.map +1 -0
  56. package/lib/components/Editor/Toolbar/FontFamilySelect.d.ts +8 -0
  57. package/lib/components/Editor/Toolbar/FontFamilySelect.d.ts.map +1 -0
  58. package/lib/components/Editor/Toolbar/FontFamilySelect.js +150 -0
  59. package/lib/components/Editor/Toolbar/FontFamilySelect.js.map +1 -0
  60. package/lib/components/Editor/Toolbar/FontSizeSelect.d.ts +8 -0
  61. package/lib/components/Editor/Toolbar/FontSizeSelect.d.ts.map +1 -0
  62. package/lib/components/Editor/Toolbar/FontSizeSelect.js +145 -0
  63. package/lib/components/Editor/Toolbar/FontSizeSelect.js.map +1 -0
  64. package/lib/components/Editor/Toolbar/ImageButton.d.ts +5 -5
  65. package/lib/components/Editor/Toolbar/ImageButton.d.ts.map +1 -1
  66. package/lib/components/Editor/Toolbar/ImageButton.js +131 -18
  67. package/lib/components/Editor/Toolbar/ImageButton.js.map +1 -1
  68. package/lib/components/Editor/Toolbar/SourceCodeButton.d.ts +8 -0
  69. package/lib/components/Editor/Toolbar/SourceCodeButton.d.ts.map +1 -0
  70. package/lib/components/Editor/Toolbar/SourceCodeButton.js +49 -0
  71. package/lib/components/Editor/Toolbar/SourceCodeButton.js.map +1 -0
  72. package/lib/components/Editor/Toolbar/TableButton.d.ts +8 -0
  73. package/lib/components/Editor/Toolbar/TableButton.d.ts.map +1 -0
  74. package/lib/components/Editor/Toolbar/TableButton.js +291 -0
  75. package/lib/components/Editor/Toolbar/TableButton.js.map +1 -0
  76. package/lib/components/Editor/Toolbar/TextSelect.d.ts +4 -5
  77. package/lib/components/Editor/Toolbar/TextSelect.d.ts.map +1 -1
  78. package/lib/components/Editor/Toolbar/TextSelect.js +61 -30
  79. package/lib/components/Editor/Toolbar/TextSelect.js.map +1 -1
  80. package/lib/components/Editor/Toolbar/Toolbar.d.ts +17 -6
  81. package/lib/components/Editor/Toolbar/Toolbar.d.ts.map +1 -1
  82. package/lib/components/Editor/Toolbar/Toolbar.js +169 -47
  83. package/lib/components/Editor/Toolbar/Toolbar.js.map +1 -1
  84. package/lib/components/Editor/Toolbar/Toolbar.test-ids.d.ts +2 -2
  85. package/lib/components/Editor/Toolbar/Toolbar.test-ids.d.ts.map +1 -1
  86. package/lib/components/Editor/Toolbar/Toolbar.test-ids.js +17 -1
  87. package/lib/components/Editor/Toolbar/Toolbar.test-ids.js.map +1 -1
  88. package/lib/components/Editor/Toolbar/WordCount.d.ts +8 -0
  89. package/lib/components/Editor/Toolbar/WordCount.d.ts.map +1 -0
  90. package/lib/components/Editor/Toolbar/WordCount.js +31 -0
  91. package/lib/components/Editor/Toolbar/WordCount.js.map +1 -0
  92. package/lib/components/Editor/extensions/FontSize.d.ts +21 -0
  93. package/lib/components/Editor/extensions/FontSize.d.ts.map +1 -0
  94. package/lib/components/Editor/extensions/FontSize.js +42 -0
  95. package/lib/components/Editor/extensions/FontSize.js.map +1 -0
  96. package/lib/components/Editor/extensions/PreserveDiv.d.ts +13 -0
  97. package/lib/components/Editor/extensions/PreserveDiv.d.ts.map +1 -0
  98. package/lib/components/Editor/extensions/PreserveDiv.js +73 -0
  99. package/lib/components/Editor/extensions/PreserveDiv.js.map +1 -0
  100. package/lib/components/Editor/extensions/TableCellSelection.d.ts +4 -0
  101. package/lib/components/Editor/extensions/TableCellSelection.d.ts.map +1 -0
  102. package/lib/components/Editor/extensions/TableCellSelection.js +53 -0
  103. package/lib/components/Editor/extensions/TableCellSelection.js.map +1 -0
  104. package/lib/components/Editor/extensions/TextIndent.d.ts +22 -0
  105. package/lib/components/Editor/extensions/TextIndent.d.ts.map +1 -0
  106. package/lib/components/Editor/extensions/TextIndent.js +137 -0
  107. package/lib/components/Editor/extensions/TextIndent.js.map +1 -0
  108. package/lib/components/Editor/hooks/useCloseOnEditorClick.d.ts +5 -0
  109. package/lib/components/Editor/hooks/useCloseOnEditorClick.d.ts.map +1 -0
  110. package/lib/components/Editor/hooks/useCloseOnEditorClick.js +18 -0
  111. package/lib/components/Editor/hooks/useCloseOnEditorClick.js.map +1 -0
  112. package/lib/components/Editor/hooks/useEscapeKey.d.ts +4 -0
  113. package/lib/components/Editor/hooks/useEscapeKey.d.ts.map +1 -0
  114. package/lib/components/Editor/hooks/useEscapeKey.js +24 -0
  115. package/lib/components/Editor/hooks/useEscapeKey.js.map +1 -0
  116. package/lib/components/Editor/hooks/useIframeSetup.d.ts +54 -0
  117. package/lib/components/Editor/hooks/useIframeSetup.d.ts.map +1 -0
  118. package/lib/components/Editor/hooks/useIframeSetup.js +284 -0
  119. package/lib/components/Editor/hooks/useIframeSetup.js.map +1 -0
  120. package/lib/components/Editor/hooks/useImageActions.d.ts +19 -0
  121. package/lib/components/Editor/hooks/useImageActions.d.ts.map +1 -0
  122. package/lib/components/Editor/hooks/useImageActions.js +198 -0
  123. package/lib/components/Editor/hooks/useImageActions.js.map +1 -0
  124. package/lib/components/Editor/hooks/useTableCellMenu.d.ts +22 -0
  125. package/lib/components/Editor/hooks/useTableCellMenu.d.ts.map +1 -0
  126. package/lib/components/Editor/hooks/useTableCellMenu.js +120 -0
  127. package/lib/components/Editor/hooks/useTableCellMenu.js.map +1 -0
  128. package/lib/components/Editor/iframeContentStyles.d.ts +10 -0
  129. package/lib/components/Editor/iframeContentStyles.d.ts.map +1 -0
  130. package/lib/components/Editor/iframeContentStyles.js +162 -0
  131. package/lib/components/Editor/iframeContentStyles.js.map +1 -0
  132. package/lib/components/Editor/index.d.ts +2 -0
  133. package/lib/components/Editor/index.d.ts.map +1 -1
  134. package/lib/components/Editor/index.js +1 -0
  135. package/lib/components/Editor/index.js.map +1 -1
  136. package/lib/components/Editor/sanitize.d.ts +3 -0
  137. package/lib/components/Editor/sanitize.d.ts.map +1 -0
  138. package/lib/components/Editor/sanitize.js +11 -0
  139. package/lib/components/Editor/sanitize.js.map +1 -0
  140. package/lib/components/Editor/utils/htmlPlaceholder.d.ts +69 -0
  141. package/lib/components/Editor/utils/htmlPlaceholder.d.ts.map +1 -0
  142. package/lib/components/Editor/utils/htmlPlaceholder.js +154 -0
  143. package/lib/components/Editor/utils/htmlPlaceholder.js.map +1 -0
  144. package/lib/components/RichTextEditor/DecoratorComponents/Table.d.ts +6 -4
  145. package/lib/components/RichTextEditor/DecoratorComponents/Table.d.ts.map +1 -1
  146. package/lib/components/RichTextEditor/DecoratorComponents/Table.js +10 -8
  147. package/lib/components/RichTextEditor/DecoratorComponents/Table.js.map +1 -1
  148. package/lib/components/RichTextEditor/RichTextEditor.d.ts.map +1 -1
  149. package/lib/components/RichTextEditor/RichTextEditor.js +15 -2
  150. package/lib/components/RichTextEditor/RichTextEditor.js.map +1 -1
  151. package/lib/components/RichTextEditor/RichTextEditor.styles.d.ts +5 -5
  152. package/lib/components/RichTextEditor/RichTextEditor.styles.d.ts.map +1 -1
  153. package/lib/components/RichTextEditor/RichTextEditor.styles.js +3 -5
  154. package/lib/components/RichTextEditor/RichTextEditor.styles.js.map +1 -1
  155. package/lib/components/RichTextEditor/RichTextEditor.types.d.ts +5 -0
  156. package/lib/components/RichTextEditor/RichTextEditor.types.d.ts.map +1 -1
  157. package/lib/components/RichTextEditor/RichTextEditor.types.js.map +1 -1
  158. package/lib/components/RichTextEditor/RichTextViewer.d.ts.map +1 -1
  159. package/lib/components/RichTextEditor/RichTextViewer.js +9 -2
  160. package/lib/components/RichTextEditor/RichTextViewer.js.map +1 -1
  161. package/lib/components/RichTextEditor/Toolbar/Toolbar.js +1 -1
  162. package/lib/components/RichTextEditor/Toolbar/Toolbar.js.map +1 -1
  163. package/lib/components/RichTextEditor/Toolbar/Toolbar.types.d.ts +4 -4
  164. package/lib/components/RichTextEditor/Toolbar/Toolbar.types.d.ts.map +1 -1
  165. package/lib/components/RichTextEditor/Toolbar/Toolbar.types.js.map +1 -1
  166. package/lib/components/RichTextEditor/Toolbar/ToolbarButton.d.ts.map +1 -1
  167. package/lib/components/RichTextEditor/Toolbar/ToolbarButton.js +41 -26
  168. package/lib/components/RichTextEditor/Toolbar/ToolbarButton.js.map +1 -1
  169. package/lib/components/RichTextEditor/utils/htmlConverter.d.ts +2 -0
  170. package/lib/components/RichTextEditor/utils/htmlConverter.d.ts.map +1 -1
  171. package/lib/components/RichTextEditor/utils/htmlConverter.js +12 -0
  172. package/lib/components/RichTextEditor/utils/htmlConverter.js.map +1 -1
  173. package/lib/components/RichTextEditor/utils/interactionRenderer.d.ts.map +1 -1
  174. package/lib/components/RichTextEditor/utils/interactionRenderer.js +20 -19
  175. package/lib/components/RichTextEditor/utils/interactionRenderer.js.map +1 -1
  176. package/lib/components/RichTextEditor/utils/markdownConverter.d.ts.map +1 -1
  177. package/lib/components/RichTextEditor/utils/markdownConverter.js +131 -30
  178. package/lib/components/RichTextEditor/utils/markdownConverter.js.map +1 -1
  179. package/lib/components/RichTextEditor/utils/renderers.d.ts +5 -3
  180. package/lib/components/RichTextEditor/utils/renderers.d.ts.map +1 -1
  181. package/lib/components/RichTextEditor/utils/renderers.js +62 -34
  182. package/lib/components/RichTextEditor/utils/renderers.js.map +1 -1
  183. package/lib/components/RichTextEditor/utils/slateConverter.d.ts +4 -3
  184. package/lib/components/RichTextEditor/utils/slateConverter.d.ts.map +1 -1
  185. package/lib/components/RichTextEditor/utils/slateConverter.js +86 -38
  186. package/lib/components/RichTextEditor/utils/slateConverter.js.map +1 -1
  187. package/package.json +30 -8
  188. package/lib/components/Editor/ImageEditor.d.ts +0 -10
  189. package/lib/components/Editor/ImageEditor.d.ts.map +0 -1
  190. package/lib/components/Editor/ImageEditor.js +0 -292
  191. package/lib/components/Editor/ImageEditor.js.map +0 -1
@@ -1,246 +1,81 @@
1
- import { createElement as _createElement } from "react";
2
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
- import tinymce from 'tinymce/tinymce';
4
- import 'tinymce/icons/default';
5
- import 'tinymce/themes/silver';
6
- import 'tinymce/plugins/advlist';
7
- import 'tinymce/plugins/lists';
8
- import 'tinymce/plugins/autolink';
9
- import 'tinymce/models/dom';
10
- import { forwardRef, useEffect, useRef, useState, useLayoutEffect, useImperativeHandle, useCallback, useMemo } from 'react';
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { forwardRef, useEffect, useRef, useState, useImperativeHandle, useMemo, useCallback } from 'react';
11
3
  import { css } from 'styled-components';
12
- import { compile, serialize, stringify } from 'stylis';
13
- import { mix, stripUnit } from 'polished';
14
- import { createUID, FormControl, useAfterInitialEffect, useTheme, useUID, useI18n, useConsolidatedRef, useAutoResize, useConfiguration, useTestIds, withTestIds, documentIsAvailable, getActiveElement } from '@pega/cosmos-react-core';
15
- import { getHtmlStyles } from '@pega/cosmos-react-core/lib/components/HTML/HTML';
16
- import { createGlobalBodyStyles, createGlobalRootStyles, globalSpacingStyles } from '@pega/cosmos-react-core/lib/styles/GlobalStyle';
4
+ import { FormControl, Progress, HiddenText, useAfterInitialEffect, useTheme, useUID, useConsolidatedRef, useTestIds, withTestIds, useConfiguration, useI18n } from '@pega/cosmos-react-core';
17
5
  import StyledRichTextEditor from '../RichTextEditor/RichTextEditor.styles';
18
- import Toolbar from './Toolbar/Toolbar';
19
6
  import EditorContext from './Editor.context';
20
- import { getTextFormats } from './Toolbar/TextSelect';
21
- import ImageEditor, { imgHoverClass } from './ImageEditor';
22
- import { offscreenSelectionStyles, mceContentBodyStyles, StyledEditorContainer, StyledEditorRoot } from './Editor.styles';
7
+ import { StyledEditorContainer, StyledEditorIframe, StyledEditorRoot, StyledSourceTextarea } from './Editor.styles';
23
8
  import { getEditorTestIds } from './Editor.test-ids';
9
+ import Toolbar from './Toolbar/Toolbar';
10
+ import WordCount from './Toolbar/WordCount';
11
+ import { ImageEditDialog } from './ImageEditDialog';
12
+ import ImageActionButtons from './ImageActionButtons';
13
+ import TableCellMenu from './TableCellMenu';
24
14
  import createFileItemElement from './FileItemElement';
25
- const getFileFromUrl = async (url, name) => {
26
- const response = await fetch(url);
27
- const data = await response.blob();
28
- return new File([data], name, {
29
- type: data.type
15
+ import useIframeSetup from './hooks/useIframeSetup';
16
+ import useImageActions from './hooks/useImageActions';
17
+ import useTableCellMenu from './hooks/useTableCellMenu';
18
+ import { dispatchEditorClickEvent } from './hooks/useCloseOnEditorClick';
19
+ import sanitizeHtml from './sanitize';
20
+ // Suggestion editor has no toolbar — formatting controls are intentionally omitted
21
+ const SUGGESTION_TOOLBAR = [];
22
+ const SuggestionEditor = ({ defaultValue, onGetContent, onContentChange, renderEditor }) => {
23
+ const editorRef = useRef(null);
24
+ useEffect(() => {
25
+ onGetContent(() => editorRef.current?.getEditor()?.getHTML() ?? '');
26
+ }, [onGetContent]);
27
+ useAfterInitialEffect(() => {
28
+ editorRef.current?.getEditor()?.commands.setContent(defaultValue ?? '');
29
+ }, [defaultValue]);
30
+ return renderEditor({
31
+ ref: editorRef,
32
+ onInit: tiptapEditor => {
33
+ // Signal initial content state so submit button enables correctly
34
+ onContentChange(tiptapEditor.getText().trim() !== '');
35
+ },
36
+ onChange: editor => {
37
+ onContentChange((editor?.getText() ?? '').trim() !== '');
38
+ }
30
39
  });
31
40
  };
32
- export const getImageStyles = (theme) => {
33
- return `
34
- img:hover,
35
- img.${imgHoverClass} {
36
- box-shadow: 0 0 0 0.06125rem ${theme.base.palette['primary-background']},
37
- 0 0 0 0.125rem ${theme.base.palette.interactive}, 0 0 0 0.25rem rgba(0, 118, 209, 0.3);
38
- }
39
- `;
40
- };
41
- export const getPlaceholderStyles = (theme) => {
42
- return `
43
- .mce-content-body[data-mce-placeholder] {
44
- position: relative;
45
- }
46
-
47
- .mce-content-body[data-mce-placeholder]:not(.mce-visualblocks)::before {
48
- cursor: text;
49
- color: ${mix(theme.base.transparency['transparent-3'], theme.base.palette['foreground-color'], theme.components['form-control']['background-color'])};
50
- content: attr(data-mce-placeholder);
51
- position: absolute;
52
- }
53
- `;
54
- };
55
- const Editor = forwardRef(function Editor(props, ref) {
41
+ const Editor = forwardRef(function ForwardedEditor(props, ref) {
42
+ const { testId, id, label, labelHidden, info, status, required, disabled, readOnly, onChange, onKeyDown, onFocus, onBlur, onImageAdded, onResolveSuggestion, onInit, onUnload, defaultValue, placeholder, children, additionalInfo, toolbar, customActions, customComponents = [], spellcheck = true, imageInsertionMode = 'all', initOptions, onRewriteClick, progress, autoFocus, height, autoResize = true, secure = false, ...restProps } = props;
56
43
  const theme = useTheme();
57
- const { components: { 'text-area': { 'min-height': textAreaMinHeight } } } = theme;
58
44
  const { styleSheetTarget } = useConfiguration();
59
- const [editor, setEditor] = useState();
60
- const [focused, setFocused] = useState(false);
61
45
  const t = useI18n();
62
- const uid = useUID();
46
+ const [focused, setFocused] = useState(false);
47
+ const [tiptapEditor, setTiptapEditor] = useState(null);
48
+ const [imageEditDialog, setImageEditDialog] = useState({ isOpen: false, attributes: null });
49
+ const [sourceMode, setSourceMode] = useState(false);
50
+ const [sourceCode, setSourceCode] = useState('');
51
+ const editButtonRef = useRef(null);
52
+ const tableCellMenuButtonRef = useRef(null);
53
+ const editorContainerRef = useRef(null);
54
+ const generatedId = useUID();
55
+ const editorId = id ?? generatedId;
63
56
  const labelId = useUID();
57
+ const requiredDescriptionId = `${editorId}-required`;
64
58
  const editorRef = useConsolidatedRef(ref);
65
- const tinyMceRef = useRef(null);
66
- const tinyMceContainerRef = useRef(null);
67
- const initialized = useRef(false);
59
+ const formFieldRef = useRef(null);
68
60
  const pastedImages = useRef([]);
69
- const { testId, id = uid, toolbar = [], label, labelHidden, info, status, required, disabled, readOnly, onChange, onKeyDown, onFocus, onBlur, onImageAdded, onResolveSuggestion, onInit, onUnload, defaultValue, customComponents = [], height, autoResize = true, customActions, placeholder, children, spellcheck = true, additionalInfo, initOptions, ...restProps } = props;
61
+ const iframeRef = useRef(null);
70
62
  const testIds = useTestIds(testId, getEditorTestIds);
71
- const baseHeight = {
72
- min: height?.min ?? stripUnit(textAreaMinHeight) * 16,
73
- max: height?.max ?? undefined
74
- };
75
- const getHeight = (type) => {
76
- if (typeof baseHeight === 'number') {
77
- return stripUnit(baseHeight);
78
- }
79
- if (baseHeight[type]) {
80
- return stripUnit(baseHeight[type]);
81
- }
82
- return undefined;
83
- };
84
- const imagesEnabled = toolbar.includes('images');
85
- const maxHeight = autoResize ? getHeight('max') : undefined;
86
- const minHeight = autoResize ? getHeight('min') : undefined;
87
- const [autoResizeRef, resizeEditor] = useAutoResize(maxHeight, minHeight);
88
- const formFieldRef = useRef(null);
89
- const value = useRef(defaultValue);
90
- const customActionsLength = customActions?.length ?? 0;
91
- const renderToolbar = editor && (toolbar.length > 0 || customActionsLength > 0) && !readOnly && !disabled;
92
- const processPastedImgUrls = async (infoArr) => {
93
- const files = await Promise.all(infoArr.map(({ id: imgId, url }) => getFileFromUrl(url, imgId)));
94
- files.forEach((file, i) => {
95
- onImageAdded?.(file, infoArr[i].id);
96
- });
97
- };
98
- const pastePreprocess = (plugin, args) => {
99
- const isImg = args.content.includes('<img');
100
- if (!imagesEnabled && isImg) {
101
- args.content = args.content.replace(/<img(.*)\/>/g, '');
102
- }
103
- if (imagesEnabled && isImg) {
104
- const pastedHtml = new DOMParser().parseFromString(args.content, 'text/html').body;
105
- const imgInfo = [];
106
- pastedHtml.querySelectorAll('img').forEach(imgEl => {
107
- const imgId = createUID();
108
- const url = imgEl.src;
109
- const name = pastedImages.current.shift()?.name || id;
110
- imgInfo.push({ id: imgId, url, name });
111
- const uploadEl = document.createElement('pega-file');
112
- uploadEl.setAttribute('data-id', imgId);
113
- uploadEl.setAttribute('data-name', name);
114
- uploadEl.setAttribute('data-url', url);
115
- uploadEl.setAttribute('data-progress', '0');
116
- uploadEl.setAttribute('contenteditable', 'false');
117
- const uploadElString = `<br/>${uploadEl.outerHTML}<br/>`;
118
- const uploadElHtml = new DOMParser().parseFromString(uploadElString, 'text/html').body;
119
- imgEl.replaceWith(uploadElHtml);
120
- });
121
- args.content = pastedHtml.innerHTML;
122
- processPastedImgUrls(imgInfo);
123
- }
124
- };
125
- useImperativeHandle(editorRef, () => ({
126
- focus: () => {
127
- editor?.focus();
128
- },
129
- getPlainText: () => {
130
- return editor ? editor.getContent({ format: 'text' }) : '';
131
- },
132
- getRichText: () => {
133
- return editor ? JSON.stringify(editor.getContent({ format: 'raw' })) : '';
134
- },
135
- getHtml: () => {
136
- return editor ? editor.getContent({ format: 'html' }) : '';
137
- },
138
- clear: () => {
139
- editor?.setContent('');
140
- },
141
- updateAttachmentAttributes: (imageId, progress, error) => {
142
- if (editor) {
143
- const editorEl = editor.getDoc();
144
- const imageCustomEl = editorEl.querySelector(`pega-file[data-id="${imageId}"]`);
145
- if (imageCustomEl) {
146
- if (error)
147
- imageCustomEl.setAttribute('data-error', error);
148
- else if (progress)
149
- imageCustomEl.setAttribute('data-progress', `${progress}`);
150
- }
151
- }
152
- },
153
- appendImage: ({ src, alt, attachmentId }, imageId) => {
154
- if (editor) {
155
- const editorEl = editor.getDoc();
156
- const imageUploadEl = editorEl.querySelector(`pega-file[data-id="${imageId}"]`);
157
- if (imageUploadEl) {
158
- const imgEl = editorEl.createElement('img');
159
- imgEl.setAttribute('src', src);
160
- imgEl.setAttribute('alt', alt);
161
- if (attachmentId) {
162
- imgEl.setAttribute('data-attachment-id', attachmentId);
163
- }
164
- imageUploadEl.replaceWith(imgEl);
165
- imageUploadEl?.remove();
166
- if (autoResize)
167
- resizeEditor();
168
- }
169
- }
170
- },
171
- insertText: (text) => {
172
- let activeElement = null;
173
- if (documentIsAvailable) {
174
- activeElement = getActiveElement();
175
- }
176
- editor?.insertContent(text);
177
- if (activeElement)
178
- activeElement.focus();
179
- },
180
- setCursorLocationToStart: () => {
181
- editor?.selection.setCursorLocation();
182
- editor?.selection.getNode().scrollIntoView();
183
- },
184
- insertHtml: (html, overwrite = false) => {
185
- if (overwrite && html === editor?.getContent({ format: 'html' }))
186
- return;
187
- let activeElement = null;
188
- if (documentIsAvailable) {
189
- activeElement = getActiveElement();
190
- }
191
- if (overwrite) {
192
- editor?.setContent('');
193
- }
194
- if (readOnly || disabled) {
195
- editor?.setContent(html);
196
- }
197
- else {
198
- editor?.insertContent(html);
199
- }
200
- if (activeElement)
201
- activeElement.focus();
202
- },
203
- getEditor: () => {
204
- return editor;
205
- },
206
- element: formFieldRef.current || undefined
207
- }), [editor]);
208
- useLayoutEffect(() => {
209
- if (!tinyMceRef.current || !tinyMceContainerRef.current)
210
- return;
211
- const styles = serialize(compile(getHtmlStyles(theme)), stringify);
212
- const imageStyles = serialize(compile(getImageStyles(theme)), stringify);
213
- const placeholderStyles = serialize(compile(getPlaceholderStyles(theme)), stringify);
63
+ // Check if images feature is enabled
64
+ const imagesEnabled = toolbar?.includes('images') ?? false;
65
+ // Check if tables feature is enabled
66
+ const tablesEnabled = toolbar?.includes('tables') ?? false;
67
+ // Check if links feature is enabled (controls autolink behavior)
68
+ const linksEnabled = toolbar?.includes('links') ?? false;
69
+ // Define built-in custom components and merge with user-provided ones
70
+ const customElements = useMemo(() => {
214
71
  const FileItemStyle = css `
215
- /* stylelint-disable-next-line selector-type-no-unknown */
216
- pega-file {
217
- display: inline-block;
218
- width: ${theme.base['content-width'].md};
219
- }
220
- `;
221
- const contentStyle = `
222
- ${createGlobalRootStyles(theme)}
223
- ${globalSpacingStyles}
224
- ${createGlobalBodyStyles(theme)}
225
-
226
- html {
227
- overflow: hidden;
228
- }
229
-
230
- body {
231
- min-height: 3rem;
232
- padding: ${theme.base.spacing};
233
- background: unset;
72
+ /* stylelint-disable-next-line selector-type-no-unknown */
73
+ pega-file {
74
+ display: inline-block;
75
+ width: ${theme.base['content-width'].md};
234
76
  }
235
-
236
- ${offscreenSelectionStyles}
237
- ${mceContentBodyStyles}
238
- ${placeholderStyles}
239
- ${imageStyles}
240
- ${styles}
241
- ${initOptions?.contentStyle}
242
77
  `;
243
- const customElements = [
78
+ return [
244
79
  {
245
80
  createCustomElement: createFileItemElement,
246
81
  name: 'pega-file',
@@ -249,285 +84,261 @@ const Editor = forwardRef(function Editor(props, ref) {
249
84
  },
250
85
  ...customComponents
251
86
  ];
252
- tinymce
253
- .init({
254
- readonly: readOnly || disabled,
255
- skin: false,
256
- target: tinyMceRef.current,
257
- toolbar: false,
258
- min_height: 60,
259
- height: 60,
260
- menubar: false,
261
- plugins: 'lists advlist autolink',
262
- paste_block_drop: true,
263
- paste_data_images: initOptions?.pasteDataImages ?? true,
264
- lists_indent_on_tab: true,
265
- text_patterns: initOptions?.textPatterns,
266
- icons: '',
267
- branding: false,
268
- elementpath: false,
269
- placeholder,
270
- content_css: false,
271
- browser_spellcheck: spellcheck,
272
- relative_urls: false,
273
- remove_script_host: false,
274
- paste_preprocess: pastePreprocess,
275
- convert_unsafe_embeds: true,
276
- sandbox_iframes: true,
277
- paste_as_text: initOptions?.pasteAsText,
278
- init_instance_callback: initializedEditor => {
279
- if (initOptions?.initInstanceCallback) {
280
- initOptions.initInstanceCallback(initializedEditor);
281
- }
282
- if (typeof label === 'string') {
283
- initializedEditor.getBody().setAttribute('aria-label', label);
284
- }
285
- const iframeWindow = (tinyMceContainerRef.current?.querySelector('iframe')).contentWindow;
286
- const editorCustomElements = iframeWindow?.customElements;
287
- // Manually add content style to allow setting nonce attribute
288
- const contentStyleEl = document.createElement('style');
289
- contentStyleEl.innerText = contentStyle;
290
- contentStyleEl.nonce = window.__webpack_nonce__;
291
- initializedEditor.getDoc().head.appendChild(contentStyleEl);
292
- if (editorCustomElements) {
293
- customElements.forEach(({ name, createCustomElement: customElementCreator, style }) => {
294
- if (!editorCustomElements.get(name)) {
295
- editorCustomElements.define(name, customElementCreator(iframeWindow));
296
- }
297
- if (style) {
298
- const customElementStyleEl = document.createElement('style');
299
- customElementStyleEl.innerText = style;
300
- customElementStyleEl.nonce = window.__webpack_nonce__;
301
- initializedEditor.getDoc().head.appendChild(customElementStyleEl);
302
- }
303
- });
304
- }
305
- },
306
- extended_valid_elements: `${customElements
307
- .map(comp => `${comp.name}${comp.extensionAttributes ? `[${comp.extensionAttributes.join('|')}]` : ''}`)
308
- .join(',')}`,
309
- custom_elements: customElements.map(comp => `~${comp.name}`).join('~,'),
310
- invalid_elements: 'iframe',
311
- setup: editorSettings => {
312
- editorSettings.on('keydown', e => {
313
- const activeElement = editorSettings?.getDoc().activeElement;
314
- if (e.key === 'Enter' && activeElement?.nodeName === 'PEGA-FILE') {
315
- e.preventDefault();
316
- const bookmark = editorSettings.selection.getBookmark();
317
- activeElement.shadowRoot?.querySelector('button')?.click();
318
- editorSettings?.selection.moveToBookmark(bookmark);
319
- }
320
- else
321
- onKeyDown?.(e);
322
- });
323
- getTextFormats(t).forEach((format, i) => {
324
- editorSettings.addShortcut(`meta+alt+${i}`, `${t('rte_change_text_format')} ${format.text}`, () => {
325
- editorSettings.execCommand('FormatBlock', false, format.type);
326
- });
327
- });
328
- editorSettings.addShortcut('meta+shift+b', t('rte_bold'), () => {
329
- editorSettings.execCommand('Bold');
330
- });
331
- editorSettings.addShortcut('meta+shift+i', t('rte_italic'), () => {
332
- editorSettings.execCommand('Italic');
333
- });
334
- editorSettings.addShortcut('meta+shift+x', t('rte_strike_through'), () => {
335
- editorSettings.execCommand('Strikethrough');
336
- });
337
- editorSettings.addShortcut('meta+shift+l', t('rte_toggle_unordered_list'), () => {
338
- editorSettings.execCommand('InsertUnorderedList');
339
- });
340
- editorSettings.addShortcut('alt+m', t('rte_indent_selection'), () => {
341
- editorSettings.execCommand('Indent');
342
- });
343
- editorSettings.addShortcut('alt+shift+m', t('rte_unindent_selection'), () => {
344
- editorSettings.execCommand('Outdent');
345
- });
346
- customActions?.forEach(action => {
347
- if (action.shortcut) {
348
- editorSettings.addShortcut(action.shortcut.pattern, action.shortcut.description, () => {
349
- action.shortcut?.command(editorSettings);
350
- });
351
- }
352
- });
353
- }
354
- })
355
- .then(tinymceEditors => {
356
- setEditor(tinymceEditors[0]);
357
- tinymceEditors[0]?.editorContainer?.removeAttribute('style');
358
- const iframe = tinyMceContainerRef.current?.querySelector('iframe');
359
- if (iframe) {
360
- const globalStyles = styleSheetTarget?.querySelectorAll('[data-cosmos-global-style]') ??
361
- [];
362
- const extraStyles = document.querySelectorAll('[data-cosmos-global-style]') ?? [];
363
- const iframeContentHead = iframe?.contentDocument?.querySelector('head');
364
- [...globalStyles, ...extraStyles].forEach(sheet => {
365
- iframeContentHead?.appendChild(sheet.cloneNode(true));
366
- });
367
- autoResizeRef.current = iframe;
368
- if (autoResize)
369
- resizeEditor();
87
+ }, [customComponents, theme]);
88
+ // Register custom elements in the main window (needed for Tiptap to recognize them)
89
+ useEffect(() => {
90
+ customElements.forEach((component) => {
91
+ if (component.name &&
92
+ component.createCustomElement &&
93
+ !window.customElements.get(component.name)) {
94
+ const CustomElement = component.createCustomElement(window);
95
+ window.customElements.define(component.name, CustomElement);
370
96
  }
371
97
  });
372
- }, []);
373
- useEffect(() => {
374
- if (editor) {
375
- editor.options.set('paste_preprocess', pastePreprocess);
98
+ }, [customElements]);
99
+ // Sanitize defaultValue if secure mode is enabled
100
+ const sanitizedDefaultValue = useMemo(() => (secure && defaultValue ? sanitizeHtml(defaultValue) : defaultValue), [secure, defaultValue]);
101
+ // Mount Tiptap inside iframe using custom hook
102
+ const { iframeEditorRef } = useIframeSetup({
103
+ iframeRef,
104
+ theme,
105
+ styleSheetTarget,
106
+ customElements,
107
+ placeholder,
108
+ defaultValue: sanitizedDefaultValue,
109
+ disabled,
110
+ readOnly,
111
+ onChange,
112
+ onKeyDown,
113
+ onFocus,
114
+ onBlur,
115
+ onInit,
116
+ spellcheck,
117
+ initOptions,
118
+ onImageAdded,
119
+ imagesEnabled,
120
+ linksEnabled,
121
+ imageInsertionMode,
122
+ pastedImages,
123
+ editorId,
124
+ required,
125
+ setTiptapEditor,
126
+ setFocused,
127
+ autoResize,
128
+ secure,
129
+ onTabOut: () => {
130
+ // Focus first toolbar button when Tab is pressed in editor
131
+ const toolbarEl = editorContainerRef.current?.querySelector('[data-testid$="toolbar"]');
132
+ const firstButton = toolbarEl?.querySelector('button:not([disabled])');
133
+ if (firstButton) {
134
+ firstButton.focus();
135
+ }
136
+ },
137
+ onFocusTableMenu: () => {
138
+ tableCellMenuButtonRef.current?.focus();
139
+ },
140
+ onFocusPreviousCellMenu: () => {
141
+ // Go to previous cell and then focus its menu
142
+ if (tiptapEditor) {
143
+ tiptapEditor.commands.goToPreviousCell();
144
+ setTimeout(() => {
145
+ tableCellMenuButtonRef.current?.focus();
146
+ }, 0);
147
+ }
376
148
  }
377
- }, [imagesEnabled]);
149
+ });
150
+ // Set up unload event listener to save content before iframe is destroyed
378
151
  useEffect(() => {
379
- if (!editor)
152
+ if (!iframeRef.current?.contentWindow || !onUnload)
380
153
  return;
381
- const onChangeEvents = ['input', 'paste', 'FormatApply', 'FormatRemove', 'ExecCommand'];
382
- const onEditorKeyDown = () => {
383
- const activeElement = editor.selection.getNode();
384
- if (activeElement.nodeName === 'PEGA-FILE') {
385
- activeElement.shadowRoot?.querySelector('button[aria-label*="Cancel"]')?.focus();
386
- }
387
- if (autoResize)
388
- resizeEditor();
389
- };
390
- const onEditorChange = (event) => {
391
- if (event.type === 'paste' && event.clipboardData?.files) {
392
- pastedImages.current = Array.from(event.clipboardData?.files);
393
- }
394
- if (event.type !== 'execcommand' || event.command !== 'mceFocus') {
395
- onChange?.(editor);
396
- }
397
- };
398
- const onEditorFocus = () => {
399
- setFocused(true);
400
- onFocus?.();
401
- };
402
- const onEditorBlur = () => {
403
- setFocused(false);
404
- onBlur?.();
405
- };
406
- onChangeEvents.forEach(event => {
407
- editor.on(event, onEditorChange);
408
- });
409
- editor.on('keydown', onEditorKeyDown);
410
- if (autoResize)
411
- editor.on('paste', resizeEditor);
412
- editor.on('focus', onEditorFocus);
413
- editor.on('blur', onEditorBlur);
154
+ const iframeWindow = iframeRef.current.contentWindow;
155
+ const abortController = new AbortController();
156
+ iframeWindow.addEventListener('unload', () => {
157
+ const html = iframeEditorRef.current?.getHtml() ?? '';
158
+ onUnload(html);
159
+ }, { signal: abortController.signal });
414
160
  return () => {
415
- onChangeEvents.forEach(event => {
416
- editor.off(event, onEditorChange);
417
- });
418
- editor.off('keydown', onEditorKeyDown);
419
- if (autoResize)
420
- editor.off('paste', resizeEditor);
421
- editor.off('focus', onEditorFocus);
422
- editor.off('blur', onEditorBlur);
161
+ abortController.abort();
423
162
  };
424
- }, [editor, onFocus, onBlur]);
163
+ }, [onUnload]);
164
+ // Dispatch event on iframe mousedown to close all open popovers/menus
425
165
  useEffect(() => {
426
- if (!editor || initialized.current)
166
+ const iframeDoc = iframeRef.current?.contentDocument;
167
+ if (!iframeDoc)
427
168
  return;
428
- initialized.current = true;
429
- onInit?.(editor);
430
- }, [editor, onInit]);
431
- useAfterInitialEffect(() => {
432
- editor?.mode.set(readOnly || disabled ? 'readonly' : 'design');
433
- }, [readOnly, disabled]);
434
- const addImage = useCallback((image, imageId) => {
435
- if (editor) {
436
- const imageUid = imageId ?? createUID();
437
- editor.insertContent(`<br/><pega-file data-id='${imageUid}' data-progress='0' data-name='${image.name}' > </pega-file><br/>`);
438
- onImageAdded?.(image, imageUid);
439
- }
440
- }, [editor]);
441
- const ctx = useMemo(() => ({ addImage, editor: editor || {} }), [editor, addImage]);
442
- useEffect(() => {
443
- const onEditorDrop = (e) => {
444
- e.preventDefault();
445
- if (e.dataTransfer) {
446
- Array.from(e.dataTransfer.files).forEach(file => {
447
- if (file.type.includes('image') && imagesEnabled) {
448
- addImage(file);
449
- }
450
- });
451
- }
169
+ const handleMouseDown = () => {
170
+ dispatchEditorClickEvent();
452
171
  };
453
- editor?.on('drop', onEditorDrop);
454
- const body = editor?.getDoc().body;
455
- let resizeObserver;
456
- if (autoResize) {
457
- resizeObserver = new ResizeObserver(() => {
458
- resizeEditor();
172
+ iframeDoc.addEventListener('mousedown', handleMouseDown);
173
+ return () => iframeDoc.removeEventListener('mousedown', handleMouseDown);
174
+ }, [tiptapEditor]);
175
+ // Set up image action buttons (edit and delete) using custom hook
176
+ const { hoveredImagePosition, currentImageAttrs, currentImageElement, handleDelete: handleImageDelete, setIsOverButtons } = useImageActions({
177
+ tiptapEditor,
178
+ iframeRef,
179
+ isDialogOpen: imageEditDialog.isOpen
180
+ });
181
+ // Set up table cell menu using custom hook (only when tables are active)
182
+ const tableMenuEnabled = tablesEnabled && !readOnly && !disabled;
183
+ const { cellPosition, setIsOverMenu: setIsOverTableMenu, addRowBelow, addRowAbove, addColumnAfter, addColumnBefore, deleteRow, deleteColumn, deleteTable } = useTableCellMenu({
184
+ tiptapEditor: tableMenuEnabled ? tiptapEditor : null,
185
+ iframeRef
186
+ });
187
+ // Handle keyboard navigation from table menu to next cell (start of content)
188
+ const goToNextCell = useCallback(() => {
189
+ if (tiptapEditor) {
190
+ tiptapEditor.commands.focus();
191
+ tiptapEditor.commands.goToNextCell();
192
+ }
193
+ }, [tiptapEditor]);
194
+ // Handle keyboard navigation from table menu back to current cell
195
+ const returnToCell = useCallback(() => {
196
+ tiptapEditor?.commands.focus();
197
+ }, [tiptapEditor]);
198
+ // Store the image element when opening the dialog to target the correct node on save
199
+ const editingImageElementRef = useRef(null);
200
+ // Handle edit button click from ImageActionButtons
201
+ const onEditButtonClick = () => {
202
+ if (currentImageAttrs && currentImageElement) {
203
+ // Store the element reference to target the correct node on save
204
+ editingImageElementRef.current = currentImageElement;
205
+ setImageEditDialog({
206
+ isOpen: true,
207
+ attributes: currentImageAttrs
459
208
  });
460
- if (body) {
461
- resizeObserver.observe(body);
462
- }
209
+ // Don't call handleImageEdit() here - keep buttons visible while dialog is open
463
210
  }
464
- return () => {
465
- editor?.off('drop', onEditorDrop);
466
- resizeObserver?.disconnect();
467
- };
468
- }, [editor, resizeEditor]);
469
- useEffect(() => {
470
- if (!editor || !autoResize)
211
+ };
212
+ // Handle image edit save
213
+ const handleImageEditSave = (attrs) => {
214
+ if (!tiptapEditor || !editingImageElementRef.current)
471
215
  return;
472
- const imgEls = editor.getDoc().querySelectorAll('img');
473
- imgEls.forEach(imgEl => {
474
- imgEl.addEventListener('load', resizeEditor);
475
- imgEl.addEventListener('error', resizeEditor);
476
- });
477
- return () => {
478
- imgEls.forEach(imgEl => {
479
- imgEl.removeEventListener('load', resizeEditor);
480
- imgEl.removeEventListener('error', resizeEditor);
216
+ // Get exact position from DOM element to handle duplicate images correctly
217
+ const pos = tiptapEditor.view.posAtDOM(editingImageElementRef.current, 0);
218
+ if (pos >= 0) {
219
+ const { state } = tiptapEditor;
220
+ const node = state.doc.nodeAt(pos);
221
+ if (!node || node.type.name !== 'image')
222
+ return;
223
+ const nodeEnd = pos + node.nodeSize;
224
+ const tr = state.tr;
225
+ // Update image attributes
226
+ tr.setNodeMarkup(pos, undefined, {
227
+ src: attrs.src,
228
+ alt: attrs.alt,
229
+ ...(attrs.width && { width: attrs.width }),
230
+ ...(attrs.height && { height: attrs.height })
481
231
  });
482
- };
483
- });
484
- useEffect(() => {
485
- if (!editor)
486
- return;
487
- const body = editor.getBody();
488
- const mutationObserver = new MutationObserver(() => {
489
- if (body.hasAttribute('aria-placeholder')) {
490
- body.removeAttribute('aria-placeholder');
232
+ // Handle action URL (links are marks in ProseMirror, not nodes)
233
+ // Always remove existing link mark first, then add new one if needed
234
+ tr.removeMark(pos, nodeEnd, state.schema.marks.link);
235
+ if (attrs.actionUrl) {
236
+ const linkMark = state.schema.marks.link.create({
237
+ href: attrs.actionUrl,
238
+ target: '_blank'
239
+ });
240
+ tr.addMark(pos, nodeEnd, linkMark);
491
241
  }
492
- });
493
- mutationObserver.observe(body, { attributes: true, attributeFilter: ['aria-placeholder'] });
494
- return () => {
495
- mutationObserver.disconnect();
496
- };
497
- }, [editor]);
242
+ tiptapEditor.view.dispatch(tr);
243
+ }
244
+ // Clear the ref after save
245
+ editingImageElementRef.current = null;
246
+ };
247
+ // Update editor when props change
248
+ useAfterInitialEffect(() => {
249
+ iframeEditorRef.current?.setEditable(!readOnly && !disabled);
250
+ }, [readOnly, disabled]);
251
+ // Handle autoFocus
498
252
  useEffect(() => {
499
- if (!editor)
500
- return;
501
- const win = editor.getWin();
502
- const ac = new AbortController();
503
- win.addEventListener('unload', () => {
504
- onUnload(editor.getContent({ format: 'html' }));
505
- }, { signal: ac.signal });
506
- return () => {
507
- ac.abort();
508
- };
509
- }, [editor]);
510
- return (_jsxs(EditorContext.Provider, { value: ctx, children: [_jsx(StyledEditorRoot, { testId: testIds, toolbar: renderToolbar, label:
511
- // eslint-disable-next-line jsx-a11y/no-static-element-interactions, jsx-a11y/click-events-have-key-events
512
- _jsx("span", { onClick: () => {
513
- editor?.focus();
514
- }, children: label }), labelFor: '', labelId: labelId, labelHidden: labelHidden, id: id, info: info, status: status, required: required, disabled: disabled, readOnly: readOnly, onResolveSuggestion: onResolveSuggestion, onFocus: (e) => {
515
- if (e.target === formFieldRef.current && !disabled) {
516
- editor?.focus();
517
- }
518
- }, ref: formFieldRef, tabIndex: disabled ? -1 : undefined, additionalInfo: additionalInfo, children: _jsxs(FormControl, { ...restProps, ref: editorRef, required: required, disabled: disabled, readOnly: readOnly, status: status, as: StyledRichTextEditor, focused: focused, hasSuggestion: status === 'pending', tabIndex: readOnly ? 0 : undefined, children: [_jsxs(StyledEditorContainer, { onClick: () => {
519
- if (!disabled) {
520
- editor?.focus();
521
- }
522
- }, ref: tinyMceContainerRef, children: [_jsx("textarea", { id: id, value: value.current || '', onChange: () => { }, "aria-label": `${label}${renderToolbar ? `. ${t('rte_toolbar_instructions')}.` : ''}`, "aria-labelledby": labelId, ref: tinyMceRef }), tinyMceContainerRef.current && editor && imagesEnabled && !readOnly && !disabled && (_jsx(ImageEditor, { editor: editor, editorEl: tinyMceContainerRef.current, onChange: () => onChange?.(editor) }))] }), renderToolbar && (_jsx(Toolbar, { testId: testIds, features: toolbar, editor: editor, customActions: customActions }))] }) }), children] }));
523
- });
524
- const EditorWrapper = forwardRef(function EditorWrapper(props, ref) {
525
- const [defaultValue, setDefaultValue] = useState(props.defaultValue ?? '');
526
- const [key, setKey] = useState(createUID);
527
- return (_createElement(Editor, { ...props, defaultValue: defaultValue, onUnload: value => {
528
- setDefaultValue(value);
529
- setKey(createUID());
530
- }, ref: ref, key: key }));
253
+ if (autoFocus && tiptapEditor) {
254
+ tiptapEditor.commands.focus();
255
+ }
256
+ }, [autoFocus, tiptapEditor]);
257
+ // Expose methods via ref
258
+ useImperativeHandle(editorRef, () => ({
259
+ focus: () => iframeEditorRef.current?.focus(),
260
+ getPlainText: () => iframeEditorRef.current?.getPlainText() ?? '',
261
+ getRichText: () => iframeEditorRef.current?.getRichText() ?? '',
262
+ getHtml: () => iframeEditorRef.current?.getHtml() ?? '',
263
+ clear: () => iframeEditorRef.current?.clear(),
264
+ updateAttachmentAttributes: (imageId, uploadProgress, error) => {
265
+ if (!iframeRef.current?.contentDocument)
266
+ return;
267
+ const iframeDoc = iframeRef.current.contentDocument;
268
+ const imageCustomEl = iframeDoc.querySelector(`pega-file[data-id="${imageId}"]`);
269
+ if (imageCustomEl) {
270
+ if (error) {
271
+ imageCustomEl.setAttribute('data-error', error);
272
+ }
273
+ else if (uploadProgress !== undefined) {
274
+ imageCustomEl.setAttribute('data-progress', `${uploadProgress}`);
275
+ }
276
+ }
277
+ },
278
+ appendImage: (imageData, imageId) => {
279
+ if (!iframeRef.current?.contentDocument || !tiptapEditor)
280
+ return;
281
+ const iframeDoc = iframeRef.current.contentDocument;
282
+ const imageUploadEl = iframeDoc.querySelector(`pega-file[data-id="${imageId}"]`);
283
+ if (imageUploadEl) {
284
+ // Create img element
285
+ const imgEl = iframeDoc.createElement('img');
286
+ imgEl.setAttribute('src', imageData.src);
287
+ imgEl.setAttribute('alt', imageData.alt);
288
+ if (imageData.attachmentId) {
289
+ imgEl.setAttribute('data-attachment-id', imageData.attachmentId);
290
+ }
291
+ // Replace pega-file with img in the DOM
292
+ imageUploadEl.replaceWith(imgEl);
293
+ // Also update the Tiptap document
294
+ const html = tiptapEditor.getHTML();
295
+ const updatedHtml = html.replace(new RegExp(`<pega-file[^>]*data-id=["']${imageId}["'][^>]*>.*?</pega-file>`, 'g'), imgEl.outerHTML);
296
+ if (updatedHtml !== html) {
297
+ tiptapEditor.commands.setContent(updatedHtml, { emitUpdate: false });
298
+ }
299
+ }
300
+ },
301
+ insertText: (text) => iframeEditorRef.current?.insertText(text),
302
+ setCursorLocationToStart: () => iframeEditorRef.current?.setCursorLocationToStart(),
303
+ insertHtml: (html, overwrite) => iframeEditorRef.current?.insertHtml(html, overwrite),
304
+ getEditor: () => iframeEditorRef.current?.getEditor() ?? null,
305
+ element: formFieldRef.current || undefined
306
+ }), [tiptapEditor]);
307
+ const ctx = useMemo(() => ({
308
+ editor: tiptapEditor,
309
+ addImage: (image, imageId, alt) => {
310
+ if (!tiptapEditor || !image)
311
+ return;
312
+ const imgId = imageId || `img-${Date.now()}-${Math.random().toString(36).substring(2, 11)}`;
313
+ // Create pega-file element to show upload progress
314
+ const pegaFileHtml = `<br/><pega-file data-id='${imgId}' data-progress='0' data-name='${image.name}' data-alt='${alt || image.name}' contenteditable='false'></pega-file><br/>`;
315
+ // Insert the pega-file element
316
+ tiptapEditor.chain().focus().insertContent(pegaFileHtml).run();
317
+ // Trigger the onImageAdded callback so consumer can upload and call appendImage
318
+ onImageAdded?.(image, imgId, alt);
319
+ }
320
+ }), [tiptapEditor, onImageAdded]);
321
+ const renderSuggestionEditor = useCallback((suggestionProps) => (_jsx(SuggestionEditor, { defaultValue: suggestionProps.defaultValue, disabled: suggestionProps.disabled, readOnly: suggestionProps.readOnly, onGetContent: suggestionProps.onGetContent, onContentChange: suggestionProps.onContentChange, renderEditor: ({ ref: suggestionEditorRef, onInit: suggestionOnInit, onChange: suggestionOnChange }) => (_jsx(Editor, { ref: suggestionEditorRef, label: t('ai_rewrite_result_label'), labelHidden: true, toolbar: SUGGESTION_TOOLBAR, defaultValue: suggestionProps.defaultValue, disabled: suggestionProps.disabled, readOnly: suggestionProps.readOnly, height: height, onInit: suggestionOnInit, onChange: suggestionOnChange })) })), [height, t]);
322
+ return (_jsxs(EditorContext.Provider, { value: ctx, children: [_jsx(StyledEditorRoot, { testId: testIds, label: label, labelFor: '', labelId: labelId, labelHidden: labelHidden, id: editorId, info: info, status: status, required: required, disabled: disabled, readOnly: readOnly, onResolveSuggestion: onResolveSuggestion, ref: formFieldRef, additionalInfo: additionalInfo, children: _jsxs(_Fragment, { children: [_jsx(FormControl, { ...restProps, ref: editorRef, required: required, disabled: disabled, readOnly: readOnly, status: status, as: StyledRichTextEditor, focused: focused, hasSuggestion: status === 'pending', children: _jsxs(StyledEditorContainer, { ref: editorContainerRef, children: [progress ? (_jsx(Progress, { placement: 'local', message: typeof progress === 'string' ? progress : undefined })) : null, _jsx(StyledEditorIframe, { "data-testid": testIds.iframe, title: typeof label === 'string' ? label : t('rte_text_formatting_toolbar'), role: 'application', "aria-label": typeof label === 'string' ? label : t('rte_text_formatting_toolbar'), "aria-describedby": required ? requiredDescriptionId : undefined, ref: iframeRef, tabIndex: sourceMode ? -1 : 0, "$minHeight": height?.min, "$maxHeight": height?.max, "$hidden": sourceMode }), required ? (_jsx(HiddenText, { id: requiredDescriptionId, children: t('required') })) : null, sourceMode ? (_jsx(StyledSourceTextarea, { value: sourceCode, onChange: e => setSourceCode(e.target.value), spellCheck: false, "aria-label": t('rte_source_code'), "$minHeight": height?.min, "$maxHeight": height?.max })) : null, (toolbar?.length || customActions?.length) && tiptapEditor ? (_jsx(Toolbar, { testId: testIds, features: toolbar || [], editor: tiptapEditor, customActions: customActions, imageInsertionMode: imageInsertionMode, onRewriteClick: onRewriteClick, renderSuggestionEditor: renderSuggestionEditor, sourceMode: sourceMode, onSourceModeToggle: (isSourceMode) => {
323
+ if (isSourceMode) {
324
+ setSourceCode(tiptapEditor.getHTML());
325
+ }
326
+ else {
327
+ tiptapEditor.commands.setContent(sourceCode);
328
+ }
329
+ setSourceMode(isSourceMode);
330
+ } })) : null, imagesEnabled && !readOnly && !disabled ? (_jsx(ImageActionButtons, { ref: editButtonRef, position: hoveredImagePosition, containerRef: editorContainerRef, onEdit: onEditButtonClick, onDelete: handleImageDelete, onMouseEnter: () => setIsOverButtons(true), onMouseLeave: () => setIsOverButtons(false), editLabel: t('rte_edit_image', [currentImageAttrs?.alt || '']), deleteLabel: t('rte_delete_image', [currentImageAttrs?.alt || '']) })) : null, tablesEnabled && !readOnly && !disabled && !sourceMode ? (_jsx(TableCellMenu, { ref: tableCellMenuButtonRef, position: cellPosition, containerRef: editorContainerRef, onAddRowBelow: addRowBelow, onAddRowAbove: addRowAbove, onAddColumnAfter: addColumnAfter, onAddColumnBefore: addColumnBefore, onDeleteRow: deleteRow, onDeleteColumn: deleteColumn, onDeleteTable: deleteTable, onGoToNextCell: goToNextCell, onReturnToCell: returnToCell, onMouseEnter: () => setIsOverTableMenu(true), onMouseLeave: () => setIsOverTableMenu(false), labels: {
331
+ tableOptions: t('rte_table_options'),
332
+ addRowBelow: t('rte_add_row_below'),
333
+ addRowAbove: t('rte_add_row_above'),
334
+ addColumnAfter: t('rte_add_column_after'),
335
+ addColumnBefore: t('rte_add_column_before'),
336
+ deleteRow: t('rte_delete_row'),
337
+ deleteColumn: t('rte_delete_column'),
338
+ deleteTable: t('rte_delete_table')
339
+ } })) : null] }) }), toolbar?.includes('word-count') && tiptapEditor ? (_jsx(WordCount, { "data-testid": testIds.wordCount, editor: tiptapEditor })) : null] }) }), children, imageEditDialog.attributes && (_jsx(ImageEditDialog, { isOpen: imageEditDialog.isOpen, onClose: () => {
340
+ setImageEditDialog({ isOpen: false, attributes: null });
341
+ }, onSave: handleImageEditSave, initialAttributes: imageEditDialog.attributes, target: editButtonRef.current }))] }));
531
342
  });
532
- export default withTestIds(EditorWrapper, getEditorTestIds);
343
+ export default withTestIds(Editor, getEditorTestIds);
533
344
  //# sourceMappingURL=Editor.js.map