@@ -27,7 +27,7 @@ export default class CopyPastePlugin implements EditorPlugin {
2727
2828 if ( this . editor !== null && ! this . editor . isDisposed ( ) ) {
2929 // scroll the editor to the correct position after pasting content
30- scrollToBottomAfterContentPaste ( event , this . editor ) ;
30+ scrollToBottomAfterContentPaste ( event ) ;
3131 }
3232 }
3333}
@@ -71,32 +71,45 @@ export const removeImageElement = (event: BeforePasteEvent): void => {
7171} ;
7272
7373/**
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 .
7575 * @param event - The plugin event.
76- * @param editor - The editor instance.
7776 */
78- export const scrollToBottomAfterContentPaste = ( event : PluginEvent , editor : IEditor ) : void => {
77+ export const scrollToBottomAfterContentPaste = ( event : PluginEvent ) : void => {
7978 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
8280 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 ;
10097 }
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 ( ) ;
101114 }
102115} ;
0 commit comments