@@ -27,7 +27,7 @@ export default class CopyPastePlugin implements EditorPlugin {
27
27
28
28
if ( this . editor !== null && ! this . editor . isDisposed ( ) ) {
29
29
// scroll the editor to the correct position after pasting content
30
- scrollToBottomAfterContentPaste ( event , this . editor ) ;
30
+ scrollToBottomAfterContentPaste ( event ) ;
31
31
}
32
32
}
33
33
}
@@ -71,32 +71,45 @@ export const removeImageElement = (event: BeforePasteEvent): void => {
71
71
} ;
72
72
73
73
/**
74
- * Scrolls the editor's scroll container to the bottom after content is pasted .
74
+ * Update the scroll position of the editor after pasting content to ensure the content is visible .
75
75
* @param event - The plugin event.
76
- * @param editor - The editor instance.
77
76
*/
78
- export const scrollToBottomAfterContentPaste = ( event : PluginEvent , editor : IEditor ) : void => {
77
+ export const scrollToBottomAfterContentPaste = ( event : PluginEvent ) : void => {
79
78
if ( event . eventType === PluginEventType . ContentChanged && event . source === ContentChangedEventSource . Paste ) {
80
- const scrollContainer = editor . getScrollContainer ( ) ;
81
- // get current selection for the editor
79
+ // Get the current selection in the document
82
80
const selection = document . getSelection ( ) ;
83
- // if selection exists
84
- if ( selection && selection . rangeCount > 0 ) {
85
- // the range for the selection
86
- const range = selection . getRangeAt ( 0 ) ;
87
- // the selection position relative to the viewport
88
- const cursorRect = range . getBoundingClientRect ( ) ;
89
- // the scrollContainer position relative to the viewport
90
- const scrollContainerRect = scrollContainer . getBoundingClientRect ( ) ;
91
- // 1. scrollContainer.scrollTop represents the number of pixels that the content of scrollContainer is scrolled upward.
92
- // 2. subtract the top position of the scrollContainer element (scrollContainerRect.top) to
93
- // translate the scroll position from being relative to the document to being relative to the viewport.
94
- // 3. add the top position of the cursor (containerRect.top) to moves the scroll position to the cursor's position.
95
- const updatedScrollTop = scrollContainer . scrollTop - scrollContainerRect . top + cursorRect . top ;
96
- scrollContainer . scrollTo ( {
97
- top : updatedScrollTop ,
98
- behavior : 'smooth'
99
- } ) ;
81
+
82
+ // Check if a selection exists and it has at least one range
83
+ if ( ! selection || selection . rangeCount <= 0 ) {
84
+ // If no selection or range, exit the function
85
+ return ;
86
+ }
87
+
88
+ // Get the first range of the selection
89
+ // A user can normally only select one range at a time, so the rangeCount will usually be 1
90
+ const range = selection . getRangeAt ( 0 ) ;
91
+
92
+ // If the common ancestor container of the range is the document itself,
93
+ // it might mean that the editable element is getting removed from the DOM
94
+ // In such cases, especially in Safari, trying to modify the range might throw a HierarchyRequest error
95
+ if ( range . commonAncestorContainer === document ) {
96
+ return ;
100
97
}
98
+
99
+ // Create a temporary span element to use as an anchor for scrolling
100
+ // We can't use the anchor node directly because if it's a Text node, calling scrollIntoView() on it will throw an error
101
+ const tempElement = document . createElement ( 'span' ) ;
102
+ // Collapse the range to its end point
103
+ // This means the start and end points of the range will be the same, and it will not contain any content
104
+ range . collapse ( false ) ;
105
+ // Insert the temporary element at the cursor's position at the end of the range
106
+ range . insertNode ( tempElement ) ;
107
+
108
+ // Scroll the temporary element into view
109
+ // the element will be aligned at the center of the scroll container, otherwise, text and images may be positioned incorrectly
110
+ tempElement . scrollIntoView ( {
111
+ block : 'center'
112
+ } ) ;
113
+ tempElement . remove ( ) ;
101
114
}
102
115
} ;
0 commit comments