@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.
- package/lib/components/DynamicContentEditor/DynamicContentEditor.d.ts +4 -2
- package/lib/components/DynamicContentEditor/DynamicContentEditor.d.ts.map +1 -1
- package/lib/components/DynamicContentEditor/DynamicContentEditor.js +64 -59
- package/lib/components/DynamicContentEditor/DynamicContentEditor.js.map +1 -1
- package/lib/components/Editor/Editor.context.d.ts +6 -6
- package/lib/components/Editor/Editor.context.d.ts.map +1 -1
- package/lib/components/Editor/Editor.context.js +1 -1
- package/lib/components/Editor/Editor.context.js.map +1 -1
- package/lib/components/Editor/Editor.d.ts +1 -10
- package/lib/components/Editor/Editor.d.ts.map +1 -1
- package/lib/components/Editor/Editor.js +301 -490
- package/lib/components/Editor/Editor.js.map +1 -1
- package/lib/components/Editor/Editor.styles.d.ts +37 -7
- package/lib/components/Editor/Editor.styles.d.ts.map +1 -1
- package/lib/components/Editor/Editor.styles.js +60 -30
- package/lib/components/Editor/Editor.styles.js.map +1 -1
- package/lib/components/Editor/Editor.test-ids.d.ts +2 -1
- package/lib/components/Editor/Editor.test-ids.d.ts.map +1 -1
- package/lib/components/Editor/Editor.test-ids.js +2 -0
- package/lib/components/Editor/Editor.test-ids.js.map +1 -1
- package/lib/components/Editor/Editor.types.d.ts +34 -14
- package/lib/components/Editor/Editor.types.d.ts.map +1 -1
- package/lib/components/Editor/Editor.types.js.map +1 -1
- package/lib/components/Editor/IframeTiptapEditor.d.ts +30 -0
- package/lib/components/Editor/IframeTiptapEditor.d.ts.map +1 -0
- package/lib/components/Editor/IframeTiptapEditor.js +695 -0
- package/lib/components/Editor/IframeTiptapEditor.js.map +1 -0
- package/lib/components/Editor/ImageActionButtons.d.ts +20 -0
- package/lib/components/Editor/ImageActionButtons.d.ts.map +1 -0
- package/lib/components/Editor/ImageActionButtons.js +84 -0
- package/lib/components/Editor/ImageActionButtons.js.map +1 -0
- package/lib/components/Editor/ImageEditDialog.d.ts +17 -0
- package/lib/components/Editor/ImageEditDialog.d.ts.map +1 -0
- package/lib/components/Editor/ImageEditDialog.js +90 -0
- package/lib/components/Editor/ImageEditDialog.js.map +1 -0
- package/lib/components/Editor/TableCellMenu.d.ts +35 -0
- package/lib/components/Editor/TableCellMenu.d.ts.map +1 -0
- package/lib/components/Editor/TableCellMenu.js +120 -0
- package/lib/components/Editor/TableCellMenu.js.map +1 -0
- package/lib/components/Editor/Toolbar/AIRewriteButton.d.ts +17 -0
- package/lib/components/Editor/Toolbar/AIRewriteButton.d.ts.map +1 -0
- package/lib/components/Editor/Toolbar/AIRewriteButton.js +79 -0
- package/lib/components/Editor/Toolbar/AIRewriteButton.js.map +1 -0
- package/lib/components/Editor/Toolbar/AlignmentSelect.d.ts +8 -0
- package/lib/components/Editor/Toolbar/AlignmentSelect.d.ts.map +1 -0
- package/lib/components/Editor/Toolbar/AlignmentSelect.js +137 -0
- package/lib/components/Editor/Toolbar/AlignmentSelect.js.map +1 -0
- package/lib/components/Editor/Toolbar/AnchorButton.d.ts +3 -4
- package/lib/components/Editor/Toolbar/AnchorButton.d.ts.map +1 -1
- package/lib/components/Editor/Toolbar/AnchorButton.js +156 -82
- package/lib/components/Editor/Toolbar/AnchorButton.js.map +1 -1
- package/lib/components/Editor/Toolbar/ColorPickerButton.d.ts +9 -0
- package/lib/components/Editor/Toolbar/ColorPickerButton.d.ts.map +1 -0
- package/lib/components/Editor/Toolbar/ColorPickerButton.js +190 -0
- package/lib/components/Editor/Toolbar/ColorPickerButton.js.map +1 -0
- package/lib/components/Editor/Toolbar/FontFamilySelect.d.ts +8 -0
- package/lib/components/Editor/Toolbar/FontFamilySelect.d.ts.map +1 -0
- package/lib/components/Editor/Toolbar/FontFamilySelect.js +150 -0
- package/lib/components/Editor/Toolbar/FontFamilySelect.js.map +1 -0
- package/lib/components/Editor/Toolbar/FontSizeSelect.d.ts +8 -0
- package/lib/components/Editor/Toolbar/FontSizeSelect.d.ts.map +1 -0
- package/lib/components/Editor/Toolbar/FontSizeSelect.js +145 -0
- package/lib/components/Editor/Toolbar/FontSizeSelect.js.map +1 -0
- package/lib/components/Editor/Toolbar/ImageButton.d.ts +5 -5
- package/lib/components/Editor/Toolbar/ImageButton.d.ts.map +1 -1
- package/lib/components/Editor/Toolbar/ImageButton.js +131 -18
- package/lib/components/Editor/Toolbar/ImageButton.js.map +1 -1
- package/lib/components/Editor/Toolbar/SourceCodeButton.d.ts +8 -0
- package/lib/components/Editor/Toolbar/SourceCodeButton.d.ts.map +1 -0
- package/lib/components/Editor/Toolbar/SourceCodeButton.js +49 -0
- package/lib/components/Editor/Toolbar/SourceCodeButton.js.map +1 -0
- package/lib/components/Editor/Toolbar/TableButton.d.ts +8 -0
- package/lib/components/Editor/Toolbar/TableButton.d.ts.map +1 -0
- package/lib/components/Editor/Toolbar/TableButton.js +291 -0
- package/lib/components/Editor/Toolbar/TableButton.js.map +1 -0
- package/lib/components/Editor/Toolbar/TextSelect.d.ts +4 -5
- package/lib/components/Editor/Toolbar/TextSelect.d.ts.map +1 -1
- package/lib/components/Editor/Toolbar/TextSelect.js +61 -30
- package/lib/components/Editor/Toolbar/TextSelect.js.map +1 -1
- package/lib/components/Editor/Toolbar/Toolbar.d.ts +17 -6
- package/lib/components/Editor/Toolbar/Toolbar.d.ts.map +1 -1
- package/lib/components/Editor/Toolbar/Toolbar.js +169 -47
- package/lib/components/Editor/Toolbar/Toolbar.js.map +1 -1
- package/lib/components/Editor/Toolbar/Toolbar.test-ids.d.ts +2 -2
- package/lib/components/Editor/Toolbar/Toolbar.test-ids.d.ts.map +1 -1
- package/lib/components/Editor/Toolbar/Toolbar.test-ids.js +17 -1
- package/lib/components/Editor/Toolbar/Toolbar.test-ids.js.map +1 -1
- package/lib/components/Editor/Toolbar/WordCount.d.ts +8 -0
- package/lib/components/Editor/Toolbar/WordCount.d.ts.map +1 -0
- package/lib/components/Editor/Toolbar/WordCount.js +31 -0
- package/lib/components/Editor/Toolbar/WordCount.js.map +1 -0
- package/lib/components/Editor/extensions/FontSize.d.ts +21 -0
- package/lib/components/Editor/extensions/FontSize.d.ts.map +1 -0
- package/lib/components/Editor/extensions/FontSize.js +42 -0
- package/lib/components/Editor/extensions/FontSize.js.map +1 -0
- package/lib/components/Editor/extensions/PreserveDiv.d.ts +13 -0
- package/lib/components/Editor/extensions/PreserveDiv.d.ts.map +1 -0
- package/lib/components/Editor/extensions/PreserveDiv.js +73 -0
- package/lib/components/Editor/extensions/PreserveDiv.js.map +1 -0
- package/lib/components/Editor/extensions/TableCellSelection.d.ts +4 -0
- package/lib/components/Editor/extensions/TableCellSelection.d.ts.map +1 -0
- package/lib/components/Editor/extensions/TableCellSelection.js +53 -0
- package/lib/components/Editor/extensions/TableCellSelection.js.map +1 -0
- package/lib/components/Editor/extensions/TextIndent.d.ts +22 -0
- package/lib/components/Editor/extensions/TextIndent.d.ts.map +1 -0
- package/lib/components/Editor/extensions/TextIndent.js +137 -0
- package/lib/components/Editor/extensions/TextIndent.js.map +1 -0
- package/lib/components/Editor/hooks/useCloseOnEditorClick.d.ts +5 -0
- package/lib/components/Editor/hooks/useCloseOnEditorClick.d.ts.map +1 -0
- package/lib/components/Editor/hooks/useCloseOnEditorClick.js +18 -0
- package/lib/components/Editor/hooks/useCloseOnEditorClick.js.map +1 -0
- package/lib/components/Editor/hooks/useEscapeKey.d.ts +4 -0
- package/lib/components/Editor/hooks/useEscapeKey.d.ts.map +1 -0
- package/lib/components/Editor/hooks/useEscapeKey.js +24 -0
- package/lib/components/Editor/hooks/useEscapeKey.js.map +1 -0
- package/lib/components/Editor/hooks/useIframeSetup.d.ts +54 -0
- package/lib/components/Editor/hooks/useIframeSetup.d.ts.map +1 -0
- package/lib/components/Editor/hooks/useIframeSetup.js +284 -0
- package/lib/components/Editor/hooks/useIframeSetup.js.map +1 -0
- package/lib/components/Editor/hooks/useImageActions.d.ts +19 -0
- package/lib/components/Editor/hooks/useImageActions.d.ts.map +1 -0
- package/lib/components/Editor/hooks/useImageActions.js +198 -0
- package/lib/components/Editor/hooks/useImageActions.js.map +1 -0
- package/lib/components/Editor/hooks/useTableCellMenu.d.ts +22 -0
- package/lib/components/Editor/hooks/useTableCellMenu.d.ts.map +1 -0
- package/lib/components/Editor/hooks/useTableCellMenu.js +120 -0
- package/lib/components/Editor/hooks/useTableCellMenu.js.map +1 -0
- package/lib/components/Editor/iframeContentStyles.d.ts +10 -0
- package/lib/components/Editor/iframeContentStyles.d.ts.map +1 -0
- package/lib/components/Editor/iframeContentStyles.js +162 -0
- package/lib/components/Editor/iframeContentStyles.js.map +1 -0
- package/lib/components/Editor/index.d.ts +2 -0
- package/lib/components/Editor/index.d.ts.map +1 -1
- package/lib/components/Editor/index.js +1 -0
- package/lib/components/Editor/index.js.map +1 -1
- package/lib/components/Editor/sanitize.d.ts +3 -0
- package/lib/components/Editor/sanitize.d.ts.map +1 -0
- package/lib/components/Editor/sanitize.js +11 -0
- package/lib/components/Editor/sanitize.js.map +1 -0
- package/lib/components/Editor/utils/htmlPlaceholder.d.ts +69 -0
- package/lib/components/Editor/utils/htmlPlaceholder.d.ts.map +1 -0
- package/lib/components/Editor/utils/htmlPlaceholder.js +154 -0
- package/lib/components/Editor/utils/htmlPlaceholder.js.map +1 -0
- package/lib/components/RichTextEditor/DecoratorComponents/Table.d.ts +6 -4
- package/lib/components/RichTextEditor/DecoratorComponents/Table.d.ts.map +1 -1
- package/lib/components/RichTextEditor/DecoratorComponents/Table.js +10 -8
- package/lib/components/RichTextEditor/DecoratorComponents/Table.js.map +1 -1
- package/lib/components/RichTextEditor/RichTextEditor.d.ts.map +1 -1
- package/lib/components/RichTextEditor/RichTextEditor.js +15 -2
- package/lib/components/RichTextEditor/RichTextEditor.js.map +1 -1
- package/lib/components/RichTextEditor/RichTextEditor.styles.d.ts +5 -5
- package/lib/components/RichTextEditor/RichTextEditor.styles.d.ts.map +1 -1
- package/lib/components/RichTextEditor/RichTextEditor.styles.js +3 -5
- package/lib/components/RichTextEditor/RichTextEditor.styles.js.map +1 -1
- package/lib/components/RichTextEditor/RichTextEditor.types.d.ts +5 -0
- package/lib/components/RichTextEditor/RichTextEditor.types.d.ts.map +1 -1
- package/lib/components/RichTextEditor/RichTextEditor.types.js.map +1 -1
- package/lib/components/RichTextEditor/RichTextViewer.d.ts.map +1 -1
- package/lib/components/RichTextEditor/RichTextViewer.js +9 -2
- package/lib/components/RichTextEditor/RichTextViewer.js.map +1 -1
- package/lib/components/RichTextEditor/Toolbar/Toolbar.js +1 -1
- package/lib/components/RichTextEditor/Toolbar/Toolbar.js.map +1 -1
- package/lib/components/RichTextEditor/Toolbar/Toolbar.types.d.ts +4 -4
- package/lib/components/RichTextEditor/Toolbar/Toolbar.types.d.ts.map +1 -1
- package/lib/components/RichTextEditor/Toolbar/Toolbar.types.js.map +1 -1
- package/lib/components/RichTextEditor/Toolbar/ToolbarButton.d.ts.map +1 -1
- package/lib/components/RichTextEditor/Toolbar/ToolbarButton.js +41 -26
- package/lib/components/RichTextEditor/Toolbar/ToolbarButton.js.map +1 -1
- package/lib/components/RichTextEditor/utils/htmlConverter.d.ts +2 -0
- package/lib/components/RichTextEditor/utils/htmlConverter.d.ts.map +1 -1
- package/lib/components/RichTextEditor/utils/htmlConverter.js +12 -0
- package/lib/components/RichTextEditor/utils/htmlConverter.js.map +1 -1
- package/lib/components/RichTextEditor/utils/interactionRenderer.d.ts.map +1 -1
- package/lib/components/RichTextEditor/utils/interactionRenderer.js +20 -19
- package/lib/components/RichTextEditor/utils/interactionRenderer.js.map +1 -1
- package/lib/components/RichTextEditor/utils/markdownConverter.d.ts.map +1 -1
- package/lib/components/RichTextEditor/utils/markdownConverter.js +131 -30
- package/lib/components/RichTextEditor/utils/markdownConverter.js.map +1 -1
- package/lib/components/RichTextEditor/utils/renderers.d.ts +5 -3
- package/lib/components/RichTextEditor/utils/renderers.d.ts.map +1 -1
- package/lib/components/RichTextEditor/utils/renderers.js +62 -34
- package/lib/components/RichTextEditor/utils/renderers.js.map +1 -1
- package/lib/components/RichTextEditor/utils/slateConverter.d.ts +4 -3
- package/lib/components/RichTextEditor/utils/slateConverter.d.ts.map +1 -1
- package/lib/components/RichTextEditor/utils/slateConverter.js +86 -38
- package/lib/components/RichTextEditor/utils/slateConverter.js.map +1 -1
- package/package.json +30 -8
- package/lib/components/Editor/ImageEditor.d.ts +0 -10
- package/lib/components/Editor/ImageEditor.d.ts.map +0 -1
- package/lib/components/Editor/ImageEditor.js +0 -292
- 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"}
|