@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
@@ -0,0 +1,284 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { useLayoutEffect, useRef, useState, useEffect } from 'react';
3
+ import { createRoot } from 'react-dom/client';
4
+ import { compile, serialize, stringify } from 'stylis';
5
+ import { useTheme } from '@pega/cosmos-react-core';
6
+ import { getHtmlStyles } from '@pega/cosmos-react-core/lib/components/HTML/HTML';
7
+ import { createGlobalBodyStyles, createGlobalRootStyles, globalSpacingStyles } from '@pega/cosmos-react-core/lib/styles/GlobalStyle';
8
+ import { IframeTiptapEditor } from '../IframeTiptapEditor';
9
+ import { getIframeContentStyles } from '../iframeContentStyles';
10
+ export default function useIframeSetup({ iframeRef, theme, styleSheetTarget, customElements, placeholder, defaultValue, disabled, readOnly, onChange, onKeyDown, onFocus, onBlur, onInit, spellcheck, initOptions, onImageAdded, imagesEnabled, linksEnabled, imageInsertionMode, pastedImages, editorId, required, setTiptapEditor, setFocused, autoResize = true, onTabOut, onFocusTableMenu, onFocusPreviousCellMenu, secure = false }) {
11
+ const { components: { 'text-area': { 'min-height': textAreaMinHeight } } } = theme;
12
+ const iframeRootRef = useRef(null);
13
+ const iframeEditorRef = useRef(null);
14
+ const resizeObserverRef = useRef(null);
15
+ const isInitializedRef = useRef(false);
16
+ const savedContentRef = useRef(null);
17
+ const [reinitializeCounter, setReinitializeCounter] = useState(0);
18
+ // Refs to keep callbacks fresh without re-initializing the iframe
19
+ const onChangeRef = useRef(onChange);
20
+ onChangeRef.current = onChange;
21
+ const onImageAddedRef = useRef(onImageAdded);
22
+ onImageAddedRef.current = onImageAdded;
23
+ const onTabOutRef = useRef(onTabOut);
24
+ onTabOutRef.current = onTabOut;
25
+ const requiredRef = useRef(required);
26
+ requiredRef.current = required;
27
+ const onFocusTableMenuRef = useRef(onFocusTableMenu);
28
+ onFocusTableMenuRef.current = onFocusTableMenu;
29
+ const onFocusPreviousCellMenuRef = useRef(onFocusPreviousCellMenu);
30
+ onFocusPreviousCellMenuRef.current = onFocusPreviousCellMenu;
31
+ // Listen for iframe load events (fires when iframe reloads due to DOM move)
32
+ useEffect(() => {
33
+ if (!iframeRef.current)
34
+ return;
35
+ const iframe = iframeRef.current;
36
+ const handleLoad = () => {
37
+ // Check if content was lost
38
+ const proseMirror = iframe.contentDocument?.querySelector('.ProseMirror');
39
+ if (!proseMirror && isInitializedRef.current) {
40
+ // savedContentRef is updated on every change, so it has the latest content
41
+ isInitializedRef.current = false;
42
+ setReinitializeCounter(c => c + 1);
43
+ }
44
+ };
45
+ iframe.addEventListener('load', handleLoad);
46
+ return () => {
47
+ iframe.removeEventListener('load', handleLoad);
48
+ };
49
+ }, [iframeRef]);
50
+ // Mount Tiptap inside iframe
51
+ useLayoutEffect(() => {
52
+ if (!iframeRef.current)
53
+ return;
54
+ // Check if already initialized and content is still valid
55
+ if (isInitializedRef.current) {
56
+ const proseMirror = iframeRef.current.contentDocument?.querySelector('.ProseMirror');
57
+ if (proseMirror) {
58
+ // Already initialized and content is valid, skip
59
+ return;
60
+ }
61
+ // Content was lost (iframe reloaded), need to reinitialize
62
+ if (iframeRootRef.current) {
63
+ try {
64
+ iframeRootRef.current.unmount();
65
+ }
66
+ catch (e) {
67
+ // Ignore unmount errors
68
+ }
69
+ iframeRootRef.current = null;
70
+ }
71
+ if (resizeObserverRef.current) {
72
+ resizeObserverRef.current.disconnect();
73
+ resizeObserverRef.current = null;
74
+ }
75
+ }
76
+ const iframe = iframeRef.current;
77
+ const iframeDoc = iframe.contentDocument || iframe.contentWindow?.document;
78
+ if (!iframeDoc)
79
+ return;
80
+ // Generate styles
81
+ const htmlStyles = serialize(compile(getHtmlStyles(theme)), stringify);
82
+ const contentStyle = `
83
+ ${createGlobalRootStyles(theme)}
84
+ ${globalSpacingStyles}
85
+ ${createGlobalBodyStyles(theme)}
86
+ ${htmlStyles}
87
+ ${getIframeContentStyles(theme)}
88
+ ${initOptions?.contentStyle ?? ''}
89
+ `;
90
+ // Create basic HTML structure in iframe
91
+ iframeDoc.open();
92
+ iframeDoc.write(`
93
+ <!DOCTYPE html>
94
+ <html>
95
+ <head>
96
+ <style${window.__webpack_nonce__ ? ` nonce="${window.__webpack_nonce__}"` : ''}>
97
+ ${contentStyle}
98
+ </style>
99
+ </head>
100
+ <body>
101
+ <div id="tiptap-root"></div>
102
+ </body>
103
+ </html>
104
+ `);
105
+ iframeDoc.close();
106
+ // Copy global styles into the iframe
107
+ const globalStyles = styleSheetTarget?.querySelectorAll('[data-cosmos-global-style]') ?? [];
108
+ const extraStyles = document.querySelectorAll('[data-cosmos-global-style]') ?? [];
109
+ const iframeHead = iframeDoc.querySelector('head');
110
+ [...globalStyles, ...extraStyles].forEach(sheet => {
111
+ iframeHead?.appendChild(sheet.cloneNode(true));
112
+ });
113
+ // Register custom components in iframe
114
+ if (customElements && customElements.length > 0 && iframe.contentWindow) {
115
+ const iframeWindow = iframe.contentWindow;
116
+ customElements.forEach((component) => {
117
+ if (component.name &&
118
+ component.createCustomElement &&
119
+ !iframeWindow.customElements.get(component.name)) {
120
+ const CustomElement = component.createCustomElement(iframeWindow);
121
+ iframeWindow.customElements.define(component.name, CustomElement);
122
+ }
123
+ // Inject custom component styles into iframe if provided
124
+ if (component.style && iframeHead && component.name) {
125
+ const styleId = `custom-component-${component.name}`;
126
+ if (!iframeDoc.getElementById(styleId)) {
127
+ const styleElement = iframeDoc.createElement('style');
128
+ styleElement.id = styleId;
129
+ styleElement.textContent = component.style;
130
+ if (window.__webpack_nonce__) {
131
+ styleElement.nonce = window.__webpack_nonce__;
132
+ }
133
+ iframeHead.appendChild(styleElement);
134
+ }
135
+ }
136
+ });
137
+ }
138
+ // Mount React component inside iframe
139
+ const rootElement = iframeDoc.getElementById('tiptap-root');
140
+ if (rootElement && !iframeRootRef.current) {
141
+ // Use saved content if available (from DOM move), otherwise use defaultValue
142
+ const initialContent = savedContentRef.current ?? defaultValue;
143
+ iframeRootRef.current = createRoot(rootElement);
144
+ iframeRootRef.current.render(_jsx(IframeTiptapEditor, { placeholder: placeholder, defaultValue: initialContent, disabled: disabled, readOnly: readOnly, onChange: editor => {
145
+ // Save content on every change so it can be restored if iframe reloads
146
+ savedContentRef.current = editor.getHTML();
147
+ onChangeRef.current?.(editor);
148
+ }, onKeyDown: onKeyDown, onFocus: () => {
149
+ setFocused(true);
150
+ onFocus?.();
151
+ }, onBlur: () => {
152
+ setFocused(false);
153
+ onBlur?.();
154
+ }, onInit: (editor) => {
155
+ // Save initial content so it can be restored if iframe reloads before any changes
156
+ savedContentRef.current = editor.getHTML();
157
+ // Set aria-required on the contenteditable element
158
+ if (requiredRef.current) {
159
+ editor.view.dom.setAttribute('aria-required', 'true');
160
+ }
161
+ setTiptapEditor(editor);
162
+ onInit?.(editor);
163
+ }, editorRef: iframeEditorRef, spellcheck: spellcheck, customElements: customElements, initOptions: initOptions, onImageAdded: (image, id, altText) => onImageAddedRef.current?.(image, id, altText), imagesEnabled: imagesEnabled, linksEnabled: linksEnabled, imageInsertionMode: imageInsertionMode, pastedImagesRef: pastedImages, editorId: editorId, secure: secure, onFocusTableMenu: () => onFocusTableMenuRef.current?.(), onFocusPreviousCellMenu: () => {
164
+ onFocusPreviousCellMenuRef.current?.();
165
+ } }));
166
+ }
167
+ // Handle mousedown on empty space to enable drag-to-select from anywhere
168
+ const handleIframeMouseDown = (e) => {
169
+ const target = e.target;
170
+ // Only handle mousedown on empty space (not on actual content like paragraphs, text nodes)
171
+ if (target === iframeDoc.documentElement ||
172
+ target === iframeDoc.body ||
173
+ target.id === 'tiptap-root') {
174
+ const editor = iframeEditorRef.current?.getEditor();
175
+ if (editor) {
176
+ // Find nearest document position and place cursor there
177
+ // This allows ProseMirror to handle the subsequent drag selection
178
+ const pos = editor.view.posAtCoords({ left: e.clientX, top: e.clientY });
179
+ if (pos) {
180
+ editor.commands.setTextSelection(pos.pos);
181
+ editor.commands.focus();
182
+ }
183
+ else {
184
+ editor.commands.focus('end');
185
+ }
186
+ }
187
+ }
188
+ };
189
+ // Handle mouseup on empty space to place cursor/deselect on simple clicks
190
+ const handleIframeMouseUp = (e) => {
191
+ const target = e.target;
192
+ // Only handle clicks on empty space (html, body, tiptap-root, or ProseMirror container)
193
+ if (target === iframeDoc.documentElement ||
194
+ target === iframeDoc.body ||
195
+ target.id === 'tiptap-root' ||
196
+ target.classList.contains('ProseMirror')) {
197
+ const editor = iframeEditorRef.current?.getEditor();
198
+ if (editor) {
199
+ // Check if user has a text selection (from drag) - if so, don't interfere
200
+ const { from, to } = editor.state.selection;
201
+ if (from === to) {
202
+ // Selection is collapsed (no text selected), treat as click
203
+ const pos = editor.view.posAtCoords({ left: e.clientX, top: e.clientY });
204
+ if (pos) {
205
+ // Place cursor at the nearest position
206
+ editor.commands.setTextSelection(pos.pos);
207
+ editor.commands.focus();
208
+ }
209
+ else {
210
+ // Fallback to end of document if no position found
211
+ editor.commands.focus('end');
212
+ }
213
+ }
214
+ }
215
+ }
216
+ };
217
+ // Handle Tab key to exit editor and focus toolbar
218
+ const handleIframeKeyDown = (e) => {
219
+ if (e.key === 'Tab' && !e.shiftKey) {
220
+ e.preventDefault();
221
+ onTabOutRef.current?.();
222
+ }
223
+ };
224
+ iframeDoc.addEventListener('mousedown', handleIframeMouseDown);
225
+ iframeDoc.addEventListener('mouseup', handleIframeMouseUp);
226
+ iframeDoc.addEventListener('keydown', handleIframeKeyDown);
227
+ // Auto-resize iframe based on content height (only if autoResize is enabled)
228
+ if (autoResize) {
229
+ const updateIframeHeight = () => {
230
+ if (iframe.contentDocument?.body) {
231
+ const contentHeight = iframe.contentDocument.body.scrollHeight;
232
+ // Set min height from theme, grow with content
233
+ // Convert rem to pixels using root font size
234
+ const remValue = parseFloat(textAreaMinHeight) || 3;
235
+ const rootFontSize = parseFloat(getComputedStyle(document.documentElement).fontSize) || 16;
236
+ const minHeight = remValue * rootFontSize;
237
+ iframe.style.height = `${Math.max(contentHeight + 16, minHeight)}px`;
238
+ }
239
+ };
240
+ // Observe changes to iframe content
241
+ if (iframeDoc.body && !resizeObserverRef.current) {
242
+ resizeObserverRef.current = new ResizeObserver(updateIframeHeight);
243
+ resizeObserverRef.current.observe(iframeDoc.body);
244
+ }
245
+ // Initial height update
246
+ updateIframeHeight();
247
+ }
248
+ // Mark as initialized
249
+ isInitializedRef.current = true;
250
+ return () => {
251
+ isInitializedRef.current = false;
252
+ iframeDoc.removeEventListener('mousedown', handleIframeMouseDown);
253
+ iframeDoc.removeEventListener('mouseup', handleIframeMouseUp);
254
+ iframeDoc.removeEventListener('keydown', handleIframeKeyDown);
255
+ if (resizeObserverRef.current) {
256
+ resizeObserverRef.current.disconnect();
257
+ resizeObserverRef.current = null;
258
+ }
259
+ // Defer unmounting to avoid race condition during React's render phase
260
+ if (iframeRootRef.current) {
261
+ const rootToUnmount = iframeRootRef.current;
262
+ iframeRootRef.current = null;
263
+ // Use setTimeout to defer unmount until after React finishes rendering
264
+ setTimeout(() => {
265
+ rootToUnmount.unmount();
266
+ }, 0);
267
+ }
268
+ };
269
+ }, [reinitializeCounter]);
270
+ // Keep aria-required in sync when `required` prop changes
271
+ useEffect(() => {
272
+ const editorEl = iframeEditorRef.current?.getEditor()?.view.dom;
273
+ if (!editorEl)
274
+ return;
275
+ if (required) {
276
+ editorEl.setAttribute('aria-required', 'true');
277
+ }
278
+ else {
279
+ editorEl.removeAttribute('aria-required');
280
+ }
281
+ }, [required]);
282
+ return { iframeEditorRef };
283
+ }
284
+ //# sourceMappingURL=useIframeSetup.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useIframeSetup.js","sourceRoot":"","sources":["../../../../src/components/Editor/hooks/useIframeSetup.tsx"],"names":[],"mappings":"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","sourcesContent":["import { useLayoutEffect, useRef, useState, useEffect } from 'react';\nimport type { RefObject, MutableRefObject } from 'react';\nimport { createRoot } from 'react-dom/client';\nimport type { Root } from 'react-dom/client';\nimport { compile, serialize, stringify } from 'stylis';\nimport type { Editor as TiptapEditor } from '@tiptap/core';\n\nimport { useTheme } from '@pega/cosmos-react-core';\nimport { getHtmlStyles } from '@pega/cosmos-react-core/lib/components/HTML/HTML';\nimport {\n createGlobalBodyStyles,\n createGlobalRootStyles,\n globalSpacingStyles\n} from '@pega/cosmos-react-core/lib/styles/GlobalStyle';\n\nimport type { CustomComponent, EditorProps } from '../Editor.types';\nimport { IframeTiptapEditor } from '../IframeTiptapEditor';\nimport { getIframeContentStyles } from '../iframeContentStyles';\n\ninterface UseIframeSetupParams {\n iframeRef: RefObject<HTMLIFrameElement>;\n theme: ReturnType<typeof useTheme>;\n styleSheetTarget?: HTMLElement | ShadowRoot;\n customElements: CustomComponent[];\n placeholder?: string;\n defaultValue?: string;\n disabled?: boolean;\n readOnly?: boolean;\n onChange?: (editor: TiptapEditor) => void;\n onKeyDown?: (event?: KeyboardEvent) => void;\n onFocus?: () => void;\n onBlur?: () => void;\n onInit?: (editor: TiptapEditor) => void;\n spellcheck?: boolean;\n initOptions?: EditorProps['initOptions'];\n onImageAdded?: (image: File, id: string, altText?: string) => void;\n imagesEnabled: boolean;\n linksEnabled: boolean;\n imageInsertionMode: 'file' | 'url' | 'all';\n pastedImages: MutableRefObject<File[]>;\n editorId: string;\n required?: boolean;\n setTiptapEditor: (editor: TiptapEditor) => void;\n setFocused: (focused: boolean) => void;\n autoResize?: boolean;\n onTabOut?: () => void;\n onFocusTableMenu?: () => void;\n onFocusPreviousCellMenu?: () => void;\n secure?: boolean;\n}\n\nexport interface IframeEditorHandle {\n focus: () => void;\n getPlainText: () => string;\n getRichText: () => string;\n getHtml: () => string;\n clear: () => void;\n insertText: (text: string) => void;\n setCursorLocationToStart: () => void;\n insertHtml: (html: string, overwrite?: boolean) => void;\n getEditor: () => TiptapEditor | null;\n setEditable: (editable: boolean) => void;\n}\n\nexport type IframeEditorRef = MutableRefObject<IframeEditorHandle | null>;\n\ninterface UseIframeSetupReturn {\n iframeEditorRef: IframeEditorRef;\n}\n\nexport default function useIframeSetup({\n iframeRef,\n theme,\n styleSheetTarget,\n customElements,\n placeholder,\n defaultValue,\n disabled,\n readOnly,\n onChange,\n onKeyDown,\n onFocus,\n onBlur,\n onInit,\n spellcheck,\n initOptions,\n onImageAdded,\n imagesEnabled,\n linksEnabled,\n imageInsertionMode,\n pastedImages,\n editorId,\n required,\n setTiptapEditor,\n setFocused,\n autoResize = true,\n onTabOut,\n onFocusTableMenu,\n onFocusPreviousCellMenu,\n secure = false\n}: UseIframeSetupParams): UseIframeSetupReturn {\n const {\n components: {\n 'text-area': { 'min-height': textAreaMinHeight }\n }\n } = theme;\n const iframeRootRef = useRef<Root | null>(null);\n const iframeEditorRef = useRef<IframeEditorHandle | null>(null);\n const resizeObserverRef = useRef<ResizeObserver | null>(null);\n const isInitializedRef = useRef(false);\n const savedContentRef = useRef<string | null>(null);\n const [reinitializeCounter, setReinitializeCounter] = useState(0);\n\n // Refs to keep callbacks fresh without re-initializing the iframe\n const onChangeRef = useRef(onChange);\n onChangeRef.current = onChange;\n\n const onImageAddedRef = useRef(onImageAdded);\n onImageAddedRef.current = onImageAdded;\n\n const onTabOutRef = useRef(onTabOut);\n onTabOutRef.current = onTabOut;\n\n const requiredRef = useRef(required);\n requiredRef.current = required;\n\n const onFocusTableMenuRef = useRef(onFocusTableMenu);\n onFocusTableMenuRef.current = onFocusTableMenu;\n\n const onFocusPreviousCellMenuRef = useRef(onFocusPreviousCellMenu);\n onFocusPreviousCellMenuRef.current = onFocusPreviousCellMenu;\n\n // Listen for iframe load events (fires when iframe reloads due to DOM move)\n useEffect(() => {\n if (!iframeRef.current) return;\n\n const iframe = iframeRef.current;\n\n const handleLoad = () => {\n // Check if content was lost\n const proseMirror = iframe.contentDocument?.querySelector('.ProseMirror');\n if (!proseMirror && isInitializedRef.current) {\n // savedContentRef is updated on every change, so it has the latest content\n isInitializedRef.current = false;\n setReinitializeCounter(c => c + 1);\n }\n };\n\n iframe.addEventListener('load', handleLoad);\n\n return () => {\n iframe.removeEventListener('load', handleLoad);\n };\n }, [iframeRef]);\n\n // Mount Tiptap inside iframe\n useLayoutEffect(() => {\n if (!iframeRef.current) return;\n\n // Check if already initialized and content is still valid\n if (isInitializedRef.current) {\n const proseMirror = iframeRef.current.contentDocument?.querySelector('.ProseMirror');\n if (proseMirror) {\n // Already initialized and content is valid, skip\n return;\n }\n // Content was lost (iframe reloaded), need to reinitialize\n if (iframeRootRef.current) {\n try {\n iframeRootRef.current.unmount();\n } catch (e) {\n // Ignore unmount errors\n }\n iframeRootRef.current = null;\n }\n if (resizeObserverRef.current) {\n resizeObserverRef.current.disconnect();\n resizeObserverRef.current = null;\n }\n }\n\n const iframe = iframeRef.current;\n const iframeDoc = iframe.contentDocument || iframe.contentWindow?.document;\n\n if (!iframeDoc) return;\n\n // Generate styles\n const htmlStyles = serialize(compile(getHtmlStyles(theme)), stringify);\n\n const contentStyle = `\n ${createGlobalRootStyles(theme)}\n ${globalSpacingStyles}\n ${createGlobalBodyStyles(theme)}\n ${htmlStyles}\n ${getIframeContentStyles(theme)}\n ${initOptions?.contentStyle ?? ''}\n `;\n\n // Create basic HTML structure in iframe\n iframeDoc.open();\n iframeDoc.write(`\n <!DOCTYPE html>\n <html>\n <head>\n <style${window.__webpack_nonce__ ? ` nonce=\"${window.__webpack_nonce__}\"` : ''}>\n ${contentStyle}\n </style>\n </head>\n <body>\n <div id=\"tiptap-root\"></div>\n </body>\n </html>\n `);\n iframeDoc.close();\n\n // Copy global styles into the iframe\n const globalStyles =\n styleSheetTarget?.querySelectorAll('[data-cosmos-global-style]') ?? ([] as HTMLElement[]);\n const extraStyles =\n document.querySelectorAll('[data-cosmos-global-style]') ?? ([] as HTMLElement[]);\n const iframeHead = iframeDoc.querySelector('head');\n [...globalStyles, ...extraStyles].forEach(sheet => {\n iframeHead?.appendChild(sheet.cloneNode(true));\n });\n\n // Register custom components in iframe\n if (customElements && customElements.length > 0 && iframe.contentWindow) {\n const iframeWindow = iframe.contentWindow as unknown as typeof globalThis;\n customElements.forEach((component: CustomComponent) => {\n if (\n component.name &&\n component.createCustomElement &&\n !iframeWindow.customElements.get(component.name)\n ) {\n const CustomElement = component.createCustomElement(iframeWindow);\n iframeWindow.customElements.define(component.name, CustomElement);\n }\n\n // Inject custom component styles into iframe if provided\n if (component.style && iframeHead && component.name) {\n const styleId = `custom-component-${component.name}`;\n if (!iframeDoc.getElementById(styleId)) {\n const styleElement = iframeDoc.createElement('style');\n styleElement.id = styleId;\n styleElement.textContent = component.style;\n if (window.__webpack_nonce__) {\n styleElement.nonce = window.__webpack_nonce__;\n }\n iframeHead.appendChild(styleElement);\n }\n }\n });\n }\n\n // Mount React component inside iframe\n const rootElement = iframeDoc.getElementById('tiptap-root');\n if (rootElement && !iframeRootRef.current) {\n // Use saved content if available (from DOM move), otherwise use defaultValue\n const initialContent = savedContentRef.current ?? defaultValue;\n\n iframeRootRef.current = createRoot(rootElement);\n iframeRootRef.current.render(\n <IframeTiptapEditor\n placeholder={placeholder}\n defaultValue={initialContent}\n disabled={disabled}\n readOnly={readOnly}\n onChange={editor => {\n // Save content on every change so it can be restored if iframe reloads\n savedContentRef.current = editor.getHTML();\n onChangeRef.current?.(editor);\n }}\n onKeyDown={onKeyDown}\n onFocus={() => {\n setFocused(true);\n onFocus?.();\n }}\n onBlur={() => {\n setFocused(false);\n onBlur?.();\n }}\n onInit={(editor: TiptapEditor) => {\n // Save initial content so it can be restored if iframe reloads before any changes\n savedContentRef.current = editor.getHTML();\n // Set aria-required on the contenteditable element\n if (requiredRef.current) {\n editor.view.dom.setAttribute('aria-required', 'true');\n }\n setTiptapEditor(editor);\n onInit?.(editor);\n }}\n editorRef={iframeEditorRef}\n spellcheck={spellcheck}\n customElements={customElements}\n initOptions={initOptions}\n onImageAdded={(image, id, altText) => onImageAddedRef.current?.(image, id, altText)}\n imagesEnabled={imagesEnabled}\n linksEnabled={linksEnabled}\n imageInsertionMode={imageInsertionMode}\n pastedImagesRef={pastedImages}\n editorId={editorId}\n secure={secure}\n onFocusTableMenu={() => onFocusTableMenuRef.current?.()}\n onFocusPreviousCellMenu={() => {\n onFocusPreviousCellMenuRef.current?.();\n }}\n />\n );\n }\n\n // Handle mousedown on empty space to enable drag-to-select from anywhere\n const handleIframeMouseDown = (e: MouseEvent) => {\n const target = e.target as HTMLElement;\n // Only handle mousedown on empty space (not on actual content like paragraphs, text nodes)\n if (\n target === iframeDoc.documentElement ||\n target === iframeDoc.body ||\n target.id === 'tiptap-root'\n ) {\n const editor = iframeEditorRef.current?.getEditor();\n if (editor) {\n // Find nearest document position and place cursor there\n // This allows ProseMirror to handle the subsequent drag selection\n const pos = editor.view.posAtCoords({ left: e.clientX, top: e.clientY });\n if (pos) {\n editor.commands.setTextSelection(pos.pos);\n editor.commands.focus();\n } else {\n editor.commands.focus('end');\n }\n }\n }\n };\n\n // Handle mouseup on empty space to place cursor/deselect on simple clicks\n const handleIframeMouseUp = (e: MouseEvent) => {\n const target = e.target as HTMLElement;\n // Only handle clicks on empty space (html, body, tiptap-root, or ProseMirror container)\n if (\n target === iframeDoc.documentElement ||\n target === iframeDoc.body ||\n target.id === 'tiptap-root' ||\n target.classList.contains('ProseMirror')\n ) {\n const editor = iframeEditorRef.current?.getEditor();\n if (editor) {\n // Check if user has a text selection (from drag) - if so, don't interfere\n const { from, to } = editor.state.selection;\n if (from === to) {\n // Selection is collapsed (no text selected), treat as click\n const pos = editor.view.posAtCoords({ left: e.clientX, top: e.clientY });\n if (pos) {\n // Place cursor at the nearest position\n editor.commands.setTextSelection(pos.pos);\n editor.commands.focus();\n } else {\n // Fallback to end of document if no position found\n editor.commands.focus('end');\n }\n }\n }\n }\n };\n\n // Handle Tab key to exit editor and focus toolbar\n const handleIframeKeyDown = (e: KeyboardEvent) => {\n if (e.key === 'Tab' && !e.shiftKey) {\n e.preventDefault();\n onTabOutRef.current?.();\n }\n };\n\n iframeDoc.addEventListener('mousedown', handleIframeMouseDown);\n iframeDoc.addEventListener('mouseup', handleIframeMouseUp);\n iframeDoc.addEventListener('keydown', handleIframeKeyDown);\n\n // Auto-resize iframe based on content height (only if autoResize is enabled)\n if (autoResize) {\n const updateIframeHeight = () => {\n if (iframe.contentDocument?.body) {\n const contentHeight = iframe.contentDocument.body.scrollHeight;\n // Set min height from theme, grow with content\n // Convert rem to pixels using root font size\n const remValue = parseFloat(textAreaMinHeight) || 3;\n const rootFontSize =\n parseFloat(getComputedStyle(document.documentElement).fontSize) || 16;\n const minHeight = remValue * rootFontSize;\n iframe.style.height = `${Math.max(contentHeight + 16, minHeight)}px`;\n }\n };\n\n // Observe changes to iframe content\n if (iframeDoc.body && !resizeObserverRef.current) {\n resizeObserverRef.current = new ResizeObserver(updateIframeHeight);\n resizeObserverRef.current.observe(iframeDoc.body);\n }\n\n // Initial height update\n updateIframeHeight();\n }\n\n // Mark as initialized\n isInitializedRef.current = true;\n\n return () => {\n isInitializedRef.current = false;\n iframeDoc.removeEventListener('mousedown', handleIframeMouseDown);\n iframeDoc.removeEventListener('mouseup', handleIframeMouseUp);\n iframeDoc.removeEventListener('keydown', handleIframeKeyDown);\n if (resizeObserverRef.current) {\n resizeObserverRef.current.disconnect();\n resizeObserverRef.current = null;\n }\n // Defer unmounting to avoid race condition during React's render phase\n if (iframeRootRef.current) {\n const rootToUnmount = iframeRootRef.current;\n iframeRootRef.current = null;\n // Use setTimeout to defer unmount until after React finishes rendering\n setTimeout(() => {\n rootToUnmount.unmount();\n }, 0);\n }\n };\n }, [reinitializeCounter]);\n\n // Keep aria-required in sync when `required` prop changes\n useEffect(() => {\n const editorEl = iframeEditorRef.current?.getEditor()?.view.dom;\n if (!editorEl) return;\n\n if (required) {\n editorEl.setAttribute('aria-required', 'true');\n } else {\n editorEl.removeAttribute('aria-required');\n }\n }, [required]);\n\n return { iframeEditorRef };\n}\n"]}
@@ -0,0 +1,19 @@
1
+ import type { RefObject } from 'react';
2
+ import type { Editor as TiptapEditor } from '@tiptap/core';
3
+ import type { ImageAttributes } from '../ImageEditDialog';
4
+ import type { ImagePosition } from '../ImageActionButtons';
5
+ interface UseImageActionsParams {
6
+ tiptapEditor: TiptapEditor | null;
7
+ iframeRef: RefObject<HTMLIFrameElement>;
8
+ isDialogOpen?: boolean;
9
+ }
10
+ interface UseImageActionsReturn {
11
+ hoveredImagePosition: ImagePosition | null;
12
+ currentImageAttrs: ImageAttributes | null;
13
+ currentImageElement: HTMLImageElement | null;
14
+ handleDelete: () => void;
15
+ setIsOverButtons: (value: boolean) => void;
16
+ }
17
+ export default function useImageActions({ tiptapEditor, iframeRef, isDialogOpen }: UseImageActionsParams): UseImageActionsReturn;
18
+ export {};
19
+ //# sourceMappingURL=useImageActions.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useImageActions.d.ts","sourceRoot":"","sources":["../../../../src/components/Editor/hooks/useImageActions.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AACvC,OAAO,KAAK,EAAE,MAAM,IAAI,YAAY,EAAE,MAAM,cAAc,CAAC;AAI3D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAC1D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAE3D,UAAU,qBAAqB;IAC7B,YAAY,EAAE,YAAY,GAAG,IAAI,CAAC;IAClC,SAAS,EAAE,SAAS,CAAC,iBAAiB,CAAC,CAAC;IACxC,YAAY,CAAC,EAAE,OAAO,CAAC;CACxB;AAED,UAAU,qBAAqB;IAC7B,oBAAoB,EAAE,aAAa,GAAG,IAAI,CAAC;IAC3C,iBAAiB,EAAE,eAAe,GAAG,IAAI,CAAC;IAC1C,mBAAmB,EAAE,gBAAgB,GAAG,IAAI,CAAC;IAC7C,YAAY,EAAE,MAAM,IAAI,CAAC;IACzB,gBAAgB,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;CAC5C;AAED,MAAM,CAAC,OAAO,UAAU,eAAe,CAAC,EACtC,YAAY,EACZ,SAAS,EACT,YAAoB,EACrB,EAAE,qBAAqB,GAAG,qBAAqB,CA4O/C"}
@@ -0,0 +1,198 @@
1
+ import { useEffect, useState, useCallback, useRef } from 'react';
2
+ import { isInstance } from '@pega/cosmos-react-core';
3
+ export default function useImageActions({ tiptapEditor, iframeRef, isDialogOpen = false }) {
4
+ const [hoveredImagePosition, setHoveredImagePosition] = useState(null);
5
+ const [currentImageAttrs, setCurrentImageAttrs] = useState(null);
6
+ const currentImgRef = useRef(null);
7
+ const hideTimeoutRef = useRef(null);
8
+ const isOverImageRef = useRef(false);
9
+ const isOverButtonsRef = useRef(false);
10
+ const isDialogOpenRef = useRef(isDialogOpen);
11
+ // Keep ref in sync with prop
12
+ isDialogOpenRef.current = isDialogOpen;
13
+ const cancelHide = useCallback(() => {
14
+ if (hideTimeoutRef.current) {
15
+ clearTimeout(hideTimeoutRef.current);
16
+ hideTimeoutRef.current = null;
17
+ }
18
+ }, []);
19
+ const hideButtons = useCallback(() => {
20
+ setHoveredImagePosition(null);
21
+ setCurrentImageAttrs(null);
22
+ currentImgRef.current = null;
23
+ isOverImageRef.current = false;
24
+ isOverButtonsRef.current = false;
25
+ }, []);
26
+ const scheduleHide = useCallback(() => {
27
+ // Don't hide while dialog is open
28
+ if (isDialogOpenRef.current)
29
+ return;
30
+ if (hideTimeoutRef.current) {
31
+ clearTimeout(hideTimeoutRef.current);
32
+ }
33
+ hideTimeoutRef.current = setTimeout(() => {
34
+ // Check ref for latest dialog state inside timeout
35
+ if (!isOverImageRef.current && !isOverButtonsRef.current && !isDialogOpenRef.current) {
36
+ hideButtons();
37
+ }
38
+ }, 100);
39
+ }, [hideButtons]);
40
+ // Expose a way to track if mouse is over the buttons (called from parent)
41
+ const setIsOverButtons = useCallback((value) => {
42
+ isOverButtonsRef.current = value;
43
+ if (!value) {
44
+ scheduleHide();
45
+ }
46
+ else {
47
+ cancelHide();
48
+ }
49
+ }, [scheduleHide, cancelHide]);
50
+ const positionButtons = useCallback((img) => {
51
+ if (!iframeRef.current)
52
+ return;
53
+ const imgRect = img.getBoundingClientRect();
54
+ const iframeRect = iframeRef.current.getBoundingClientRect();
55
+ // Calculate position relative to viewport (accounting for iframe position)
56
+ const position = {
57
+ top: iframeRect.top + imgRect.top,
58
+ left: iframeRect.left + imgRect.left,
59
+ width: imgRect.width,
60
+ height: imgRect.height
61
+ };
62
+ setHoveredImagePosition(position);
63
+ currentImgRef.current = img;
64
+ // Get the link parent if the image is wrapped
65
+ const linkParent = img.closest('a');
66
+ setCurrentImageAttrs({
67
+ src: img.src,
68
+ alt: img.alt,
69
+ width: img.width || undefined,
70
+ height: img.height || undefined,
71
+ actionUrl: linkParent?.href || undefined
72
+ });
73
+ }, [iframeRef]);
74
+ const handleDelete = useCallback(() => {
75
+ if (!currentImgRef.current || !tiptapEditor)
76
+ return;
77
+ // Get exact position from DOM element to handle duplicate images correctly
78
+ const pos = tiptapEditor.view.posAtDOM(currentImgRef.current, 0);
79
+ if (pos >= 0) {
80
+ tiptapEditor
81
+ .chain()
82
+ .focus()
83
+ .deleteRange({ from: pos, to: pos + 1 })
84
+ .run();
85
+ }
86
+ hideButtons();
87
+ }, [tiptapEditor, hideButtons]);
88
+ // Set up image hover listeners inside the iframe
89
+ useEffect(() => {
90
+ if (!tiptapEditor || !iframeRef.current?.contentDocument)
91
+ return;
92
+ const iframeDoc = iframeRef.current.contentDocument;
93
+ // Verify the editor is properly initialized in the iframe
94
+ const proseMirrorEl = iframeDoc.querySelector('.ProseMirror');
95
+ if (!proseMirrorEl)
96
+ return;
97
+ const imageListeners = new Map();
98
+ const attachImageListeners = (img) => {
99
+ if (imageListeners.has(img))
100
+ return;
101
+ const handleEnter = () => {
102
+ cancelHide();
103
+ isOverImageRef.current = true;
104
+ positionButtons(img);
105
+ };
106
+ const handleLeave = () => {
107
+ isOverImageRef.current = false;
108
+ scheduleHide();
109
+ };
110
+ img.addEventListener('mouseenter', handleEnter);
111
+ img.addEventListener('mouseleave', handleLeave);
112
+ imageListeners.set(img, {
113
+ enter: handleEnter,
114
+ leave: handleLeave
115
+ });
116
+ };
117
+ const detachImageListeners = (img) => {
118
+ const listeners = imageListeners.get(img);
119
+ if (listeners) {
120
+ img.removeEventListener('mouseenter', listeners.enter);
121
+ img.removeEventListener('mouseleave', listeners.leave);
122
+ imageListeners.delete(img);
123
+ }
124
+ };
125
+ // Observe DOM changes to attach listeners to new images
126
+ const setupImages = () => {
127
+ const images = iframeDoc.querySelectorAll('.ProseMirror img');
128
+ images.forEach(img => {
129
+ attachImageListeners(img);
130
+ });
131
+ };
132
+ // Initial setup
133
+ setupImages();
134
+ // Watch for new images and detect when current image is removed
135
+ const observer = new MutationObserver(() => {
136
+ setupImages();
137
+ // Clean up listeners for images that are no longer in the document
138
+ imageListeners.forEach((_listeners, img) => {
139
+ if (!iframeDoc.body.contains(img)) {
140
+ detachImageListeners(img);
141
+ }
142
+ });
143
+ // If the current image was removed (e.g., via backspace), hide buttons
144
+ if (currentImgRef.current && !iframeDoc.body.contains(currentImgRef.current)) {
145
+ hideButtons();
146
+ }
147
+ });
148
+ const proseMirror = iframeDoc.querySelector('.ProseMirror');
149
+ if (proseMirror) {
150
+ observer.observe(proseMirror, {
151
+ childList: true,
152
+ subtree: true
153
+ });
154
+ }
155
+ // Hide buttons when clicking elsewhere in the iframe
156
+ const handleDocClick = (e) => {
157
+ if (isInstance(e.target, HTMLElement) && e.target.tagName !== 'IMG') {
158
+ hideButtons();
159
+ }
160
+ };
161
+ iframeDoc.addEventListener('click', handleDocClick);
162
+ // Reposition buttons on scroll inside iframe
163
+ const handleScroll = () => {
164
+ if (currentImgRef.current) {
165
+ positionButtons(currentImgRef.current);
166
+ }
167
+ };
168
+ const iframeBody = iframeDoc.body;
169
+ iframeBody.addEventListener('scroll', handleScroll);
170
+ // Reposition buttons on window resize
171
+ const handleResize = () => {
172
+ if (currentImgRef.current) {
173
+ positionButtons(currentImgRef.current);
174
+ }
175
+ };
176
+ window.addEventListener('resize', handleResize);
177
+ return () => {
178
+ if (hideTimeoutRef.current) {
179
+ clearTimeout(hideTimeoutRef.current);
180
+ }
181
+ observer.disconnect();
182
+ imageListeners.forEach((_listeners, img) => {
183
+ detachImageListeners(img);
184
+ });
185
+ iframeDoc.removeEventListener('click', handleDocClick);
186
+ iframeBody.removeEventListener('scroll', handleScroll);
187
+ window.removeEventListener('resize', handleResize);
188
+ };
189
+ }, [tiptapEditor, iframeRef, positionButtons, scheduleHide, cancelHide, hideButtons]);
190
+ return {
191
+ hoveredImagePosition,
192
+ currentImageAttrs,
193
+ currentImageElement: currentImgRef.current,
194
+ handleDelete,
195
+ setIsOverButtons
196
+ };
197
+ }
198
+ //# sourceMappingURL=useImageActions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useImageActions.js","sourceRoot":"","sources":["../../../../src/components/Editor/hooks/useImageActions.ts"],"names":[],"mappings":"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","sourcesContent":["import { useEffect, useState, useCallback, useRef } from 'react';\nimport type { RefObject } from 'react';\nimport type { Editor as TiptapEditor } from '@tiptap/core';\n\nimport { isInstance } from '@pega/cosmos-react-core';\n\nimport type { ImageAttributes } from '../ImageEditDialog';\nimport type { ImagePosition } from '../ImageActionButtons';\n\ninterface UseImageActionsParams {\n tiptapEditor: TiptapEditor | null;\n iframeRef: RefObject<HTMLIFrameElement>;\n isDialogOpen?: boolean;\n}\n\ninterface UseImageActionsReturn {\n hoveredImagePosition: ImagePosition | null;\n currentImageAttrs: ImageAttributes | null;\n currentImageElement: HTMLImageElement | null;\n handleDelete: () => void;\n setIsOverButtons: (value: boolean) => void;\n}\n\nexport default function useImageActions({\n tiptapEditor,\n iframeRef,\n isDialogOpen = false\n}: UseImageActionsParams): UseImageActionsReturn {\n const [hoveredImagePosition, setHoveredImagePosition] = useState<ImagePosition | null>(null);\n const [currentImageAttrs, setCurrentImageAttrs] = useState<ImageAttributes | null>(null);\n const currentImgRef = useRef<HTMLImageElement | null>(null);\n const hideTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n const isOverImageRef = useRef(false);\n const isOverButtonsRef = useRef(false);\n const isDialogOpenRef = useRef(isDialogOpen);\n\n // Keep ref in sync with prop\n isDialogOpenRef.current = isDialogOpen;\n\n const cancelHide = useCallback(() => {\n if (hideTimeoutRef.current) {\n clearTimeout(hideTimeoutRef.current);\n hideTimeoutRef.current = null;\n }\n }, []);\n\n const hideButtons = useCallback(() => {\n setHoveredImagePosition(null);\n setCurrentImageAttrs(null);\n currentImgRef.current = null;\n isOverImageRef.current = false;\n isOverButtonsRef.current = false;\n }, []);\n\n const scheduleHide = useCallback(() => {\n // Don't hide while dialog is open\n if (isDialogOpenRef.current) return;\n\n if (hideTimeoutRef.current) {\n clearTimeout(hideTimeoutRef.current);\n }\n hideTimeoutRef.current = setTimeout(() => {\n // Check ref for latest dialog state inside timeout\n if (!isOverImageRef.current && !isOverButtonsRef.current && !isDialogOpenRef.current) {\n hideButtons();\n }\n }, 100);\n }, [hideButtons]);\n\n // Expose a way to track if mouse is over the buttons (called from parent)\n const setIsOverButtons = useCallback(\n (value: boolean) => {\n isOverButtonsRef.current = value;\n if (!value) {\n scheduleHide();\n } else {\n cancelHide();\n }\n },\n [scheduleHide, cancelHide]\n );\n\n const positionButtons = useCallback(\n (img: HTMLImageElement) => {\n if (!iframeRef.current) return;\n\n const imgRect = img.getBoundingClientRect();\n const iframeRect = iframeRef.current.getBoundingClientRect();\n\n // Calculate position relative to viewport (accounting for iframe position)\n const position: ImagePosition = {\n top: iframeRect.top + imgRect.top,\n left: iframeRect.left + imgRect.left,\n width: imgRect.width,\n height: imgRect.height\n };\n\n setHoveredImagePosition(position);\n currentImgRef.current = img;\n\n // Get the link parent if the image is wrapped\n const linkParent = img.closest('a');\n\n setCurrentImageAttrs({\n src: img.src,\n alt: img.alt,\n width: img.width || undefined,\n height: img.height || undefined,\n actionUrl: linkParent?.href || undefined\n });\n },\n [iframeRef]\n );\n\n const handleDelete = useCallback(() => {\n if (!currentImgRef.current || !tiptapEditor) return;\n\n // Get exact position from DOM element to handle duplicate images correctly\n const pos = tiptapEditor.view.posAtDOM(currentImgRef.current, 0);\n\n if (pos >= 0) {\n tiptapEditor\n .chain()\n .focus()\n .deleteRange({ from: pos, to: pos + 1 })\n .run();\n }\n\n hideButtons();\n }, [tiptapEditor, hideButtons]);\n\n // Set up image hover listeners inside the iframe\n useEffect(() => {\n if (!tiptapEditor || !iframeRef.current?.contentDocument) return;\n\n const iframeDoc = iframeRef.current.contentDocument;\n\n // Verify the editor is properly initialized in the iframe\n const proseMirrorEl = iframeDoc.querySelector('.ProseMirror');\n if (!proseMirrorEl) return;\n\n const imageListeners = new Map<HTMLImageElement, { enter: () => void; leave: () => void }>();\n\n const attachImageListeners = (img: HTMLImageElement) => {\n if (imageListeners.has(img)) return;\n\n const handleEnter = () => {\n cancelHide();\n isOverImageRef.current = true;\n positionButtons(img);\n };\n\n const handleLeave = () => {\n isOverImageRef.current = false;\n scheduleHide();\n };\n\n img.addEventListener('mouseenter', handleEnter);\n img.addEventListener('mouseleave', handleLeave);\n\n imageListeners.set(img, {\n enter: handleEnter,\n leave: handleLeave\n });\n };\n\n const detachImageListeners = (img: HTMLImageElement) => {\n const listeners = imageListeners.get(img);\n if (listeners) {\n img.removeEventListener('mouseenter', listeners.enter);\n img.removeEventListener('mouseleave', listeners.leave);\n imageListeners.delete(img);\n }\n };\n\n // Observe DOM changes to attach listeners to new images\n const setupImages = () => {\n const images = iframeDoc.querySelectorAll<HTMLImageElement>('.ProseMirror img');\n images.forEach(img => {\n attachImageListeners(img);\n });\n };\n\n // Initial setup\n setupImages();\n\n // Watch for new images and detect when current image is removed\n const observer = new MutationObserver(() => {\n setupImages();\n\n // Clean up listeners for images that are no longer in the document\n imageListeners.forEach((_listeners, img) => {\n if (!iframeDoc.body.contains(img)) {\n detachImageListeners(img);\n }\n });\n\n // If the current image was removed (e.g., via backspace), hide buttons\n if (currentImgRef.current && !iframeDoc.body.contains(currentImgRef.current)) {\n hideButtons();\n }\n });\n\n const proseMirror = iframeDoc.querySelector('.ProseMirror');\n if (proseMirror) {\n observer.observe(proseMirror, {\n childList: true,\n subtree: true\n });\n }\n\n // Hide buttons when clicking elsewhere in the iframe\n const handleDocClick = (e: Event) => {\n if (isInstance(e.target, HTMLElement) && e.target.tagName !== 'IMG') {\n hideButtons();\n }\n };\n\n iframeDoc.addEventListener('click', handleDocClick);\n\n // Reposition buttons on scroll inside iframe\n const handleScroll = () => {\n if (currentImgRef.current) {\n positionButtons(currentImgRef.current);\n }\n };\n\n const iframeBody = iframeDoc.body;\n iframeBody.addEventListener('scroll', handleScroll);\n\n // Reposition buttons on window resize\n const handleResize = () => {\n if (currentImgRef.current) {\n positionButtons(currentImgRef.current);\n }\n };\n\n window.addEventListener('resize', handleResize);\n\n return () => {\n if (hideTimeoutRef.current) {\n clearTimeout(hideTimeoutRef.current);\n }\n\n observer.disconnect();\n\n imageListeners.forEach((_listeners, img) => {\n detachImageListeners(img);\n });\n\n iframeDoc.removeEventListener('click', handleDocClick);\n iframeBody.removeEventListener('scroll', handleScroll);\n window.removeEventListener('resize', handleResize);\n };\n }, [tiptapEditor, iframeRef, positionButtons, scheduleHide, cancelHide, hideButtons]);\n\n return {\n hoveredImagePosition,\n currentImageAttrs,\n currentImageElement: currentImgRef.current,\n handleDelete,\n setIsOverButtons\n };\n}\n"]}
@@ -0,0 +1,22 @@
1
+ import type { RefObject } from 'react';
2
+ import type { Editor as TiptapEditor } from '@tiptap/core';
3
+ import type { CellPosition } from '../TableCellMenu';
4
+ interface UseTableCellMenuParams {
5
+ tiptapEditor: TiptapEditor | null;
6
+ iframeRef: RefObject<HTMLIFrameElement>;
7
+ }
8
+ interface UseTableCellMenuReturn {
9
+ cellPosition: CellPosition | null;
10
+ isOverMenu: boolean;
11
+ setIsOverMenu: (value: boolean) => void;
12
+ addRowBelow: () => void;
13
+ addRowAbove: () => void;
14
+ addColumnAfter: () => void;
15
+ addColumnBefore: () => void;
16
+ deleteRow: () => void;
17
+ deleteColumn: () => void;
18
+ deleteTable: () => void;
19
+ }
20
+ export default function useTableCellMenu({ tiptapEditor, iframeRef }: UseTableCellMenuParams): UseTableCellMenuReturn;
21
+ export {};
22
+ //# sourceMappingURL=useTableCellMenu.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useTableCellMenu.d.ts","sourceRoot":"","sources":["../../../../src/components/Editor/hooks/useTableCellMenu.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AACvC,OAAO,KAAK,EAAE,MAAM,IAAI,YAAY,EAAE,MAAM,cAAc,CAAC;AAE3D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAErD,UAAU,sBAAsB;IAC9B,YAAY,EAAE,YAAY,GAAG,IAAI,CAAC;IAClC,SAAS,EAAE,SAAS,CAAC,iBAAiB,CAAC,CAAC;CACzC;AAED,UAAU,sBAAsB;IAC9B,YAAY,EAAE,YAAY,GAAG,IAAI,CAAC;IAClC,UAAU,EAAE,OAAO,CAAC;IACpB,aAAa,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;IACxC,WAAW,EAAE,MAAM,IAAI,CAAC;IACxB,WAAW,EAAE,MAAM,IAAI,CAAC;IACxB,cAAc,EAAE,MAAM,IAAI,CAAC;IAC3B,eAAe,EAAE,MAAM,IAAI,CAAC;IAC5B,SAAS,EAAE,MAAM,IAAI,CAAC;IACtB,YAAY,EAAE,MAAM,IAAI,CAAC;IACzB,WAAW,EAAE,MAAM,IAAI,CAAC;CACzB;AAED,MAAM,CAAC,OAAO,UAAU,gBAAgB,CAAC,EACvC,YAAY,EACZ,SAAS,EACV,EAAE,sBAAsB,GAAG,sBAAsB,CAuIjD"}