diff --git a/packages/core/src/extensions/FilePanel/FilePanelPlugin.ts b/packages/core/src/extensions/FilePanel/FilePanelPlugin.ts index 4211fc8b55..1bdbea01ea 100644 --- a/packages/core/src/extensions/FilePanel/FilePanelPlugin.ts +++ b/packages/core/src/extensions/FilePanel/FilePanelPlugin.ts @@ -49,7 +49,7 @@ export class FilePanelView // Setting capture=true ensures that any parent container of the editor that // gets scrolled will trigger the scroll event. Scroll events do not bubble // and so won't propagate to the document by default. - document.addEventListener("scroll", this.scrollHandler, true); + pmView.root.addEventListener("scroll", this.scrollHandler, true); } mouseDownHandler = () => { @@ -69,7 +69,7 @@ export class FilePanelView scrollHandler = () => { if (this.state?.show) { - const blockElement = document.querySelector( + const blockElement = this.pmView.root.querySelector( `[data-node-type="blockContainer"][data-id="${this.state.block.id}"]` )!; @@ -84,7 +84,7 @@ export class FilePanelView } = this.pluginKey.getState(view.state); if (!this.state?.show && pluginState.block && this.editor.isEditable) { - const blockElement = document.querySelector( + const blockElement = this.pmView.root.querySelector( `[data-node-type="blockContainer"][data-id="${pluginState.block.id}"]` )!; @@ -124,7 +124,7 @@ export class FilePanelView this.pmView.dom.removeEventListener("dragstart", this.dragstartHandler); - document.removeEventListener("scroll", this.scrollHandler, true); + this.pmView.root.removeEventListener("scroll", this.scrollHandler, true); } } diff --git a/packages/core/src/extensions/FormattingToolbar/FormattingToolbarPlugin.ts b/packages/core/src/extensions/FormattingToolbar/FormattingToolbarPlugin.ts index a44d9db6bc..0db0daa6bc 100644 --- a/packages/core/src/extensions/FormattingToolbar/FormattingToolbarPlugin.ts +++ b/packages/core/src/extensions/FormattingToolbar/FormattingToolbarPlugin.ts @@ -62,7 +62,7 @@ export class FormattingToolbarView implements PluginView { // Setting capture=true ensures that any parent container of the editor that // gets scrolled will trigger the scroll event. Scroll events do not bubble // and so won't propagate to the document by default. - document.addEventListener("scroll", this.scrollHandler, true); + pmView.root.addEventListener("scroll", this.scrollHandler, true); } viewMousedownHandler = () => { @@ -147,7 +147,7 @@ export class FormattingToolbarView implements PluginView { this.pmView.dom.removeEventListener("dragstart", this.dragHandler); this.pmView.dom.removeEventListener("dragover", this.dragHandler); - document.removeEventListener("scroll", this.scrollHandler, true); + this.pmView.root.removeEventListener("scroll", this.scrollHandler, true); } closeMenu = () => { diff --git a/packages/core/src/extensions/LinkToolbar/LinkToolbarPlugin.ts b/packages/core/src/extensions/LinkToolbar/LinkToolbarPlugin.ts index 95e1156c2d..258a4d9978 100644 --- a/packages/core/src/extensions/LinkToolbar/LinkToolbarPlugin.ts +++ b/packages/core/src/extensions/LinkToolbar/LinkToolbarPlugin.ts @@ -61,12 +61,16 @@ class LinkToolbarView implements PluginView { }; this.pmView.dom.addEventListener("mouseover", this.mouseOverHandler); - document.addEventListener("click", this.clickHandler, true); + this.pmView.root.addEventListener( + "click", + this.clickHandler as EventListener, + true + ); // Setting capture=true ensures that any parent container of the editor that // gets scrolled will trigger the scroll event. Scroll events do not bubble // and so won't propagate to the document by default. - document.addEventListener("scroll", this.scrollHandler, true); + this.pmView.root.addEventListener("scroll", this.scrollHandler, true); } mouseOverHandler = (event: MouseEvent) => { @@ -271,8 +275,12 @@ class LinkToolbarView implements PluginView { destroy() { this.pmView.dom.removeEventListener("mouseover", this.mouseOverHandler); - document.removeEventListener("scroll", this.scrollHandler, true); - document.removeEventListener("click", this.clickHandler, true); + this.pmView.root.removeEventListener("scroll", this.scrollHandler, true); + this.pmView.root.removeEventListener( + "click", + this.clickHandler as EventListener, + true + ); } } diff --git a/packages/core/src/extensions/Placeholder/PlaceholderPlugin.ts b/packages/core/src/extensions/Placeholder/PlaceholderPlugin.ts index 3cb98ff5f2..35259d4322 100644 --- a/packages/core/src/extensions/Placeholder/PlaceholderPlugin.ts +++ b/packages/core/src/extensions/Placeholder/PlaceholderPlugin.ts @@ -16,7 +16,12 @@ export const PlaceholderPlugin = ( if (nonce) { styleEl.setAttribute("nonce", nonce); } - document.head.appendChild(styleEl); + if (editor._tiptapEditor.view.root instanceof ShadowRoot) { + editor._tiptapEditor.view.root.append(styleEl); + } else { + editor._tiptapEditor.view.root.head.appendChild(styleEl); + } + const styleSheet = styleEl.sheet!; const getBaseSelector = (additionalSelectors = "") => @@ -62,7 +67,11 @@ export const PlaceholderPlugin = ( return { destroy: () => { - document.head.removeChild(styleEl); + if (editor._tiptapEditor.view.root instanceof ShadowRoot) { + editor._tiptapEditor.view.root.removeChild(styleEl); + } else { + editor._tiptapEditor.view.root.head.removeChild(styleEl); + } }, }; }, diff --git a/packages/core/src/extensions/SideMenu/SideMenuPlugin.ts b/packages/core/src/extensions/SideMenu/SideMenuPlugin.ts index 5e73ed79d1..3f5e02075c 100644 --- a/packages/core/src/extensions/SideMenu/SideMenuPlugin.ts +++ b/packages/core/src/extensions/SideMenu/SideMenuPlugin.ts @@ -128,7 +128,7 @@ function setDragImage(view: EditorView, from: number, to = from) { } // dataTransfer.setDragImage(element) only works if element is attached to the DOM. - unsetDragImage(); + unsetDragImage(view.root); dragImageElement = parentClone; // TODO: This is hacky, need a better way of assigning classes to the editor so that they can also be applied to the @@ -146,12 +146,21 @@ function setDragImage(view: EditorView, from: number, to = from) { dragImageElement.className = dragImageElement.className + " bn-drag-preview " + inheritedClasses; - document.body.appendChild(dragImageElement); + if (view.root instanceof ShadowRoot) { + view.root.appendChild(dragImageElement); + } else { + view.root.body.appendChild(dragImageElement); + } } -function unsetDragImage() { +function unsetDragImage(rootEl: Document | ShadowRoot) { if (dragImageElement !== undefined) { - document.body.removeChild(dragImageElement); + if (rootEl instanceof ShadowRoot) { + rootEl.removeChild(dragImageElement); + } else { + rootEl.body.appendChild(dragImageElement); + } + dragImageElement = undefined; } } @@ -177,7 +186,7 @@ function dragStart< top: e.clientY, }; - const elements = document.elementsFromPoint(coords.left, coords.top); + const elements = view.root.elementsFromPoint(coords.left, coords.top); let blockEl = undefined; for (const element of elements) { @@ -283,22 +292,37 @@ export class SideMenuView< this.pmView.dom.firstChild! as HTMLElement ).getBoundingClientRect().x; - document.body.addEventListener("drop", this.onDrop, true); - document.body.addEventListener("dragover", this.onDragOver); + this.pmView.root.addEventListener( + "drop", + this.onDrop as EventListener, + true + ); + this.pmView.root.addEventListener( + "dragover", + this.onDragOver as EventListener + ); this.pmView.dom.addEventListener("dragstart", this.onDragStart); // Shows or updates menu position whenever the cursor moves, if the menu isn't frozen. - document.body.addEventListener("mousemove", this.onMouseMove, true); + this.pmView.root.addEventListener( + "mousemove", + this.onMouseMove as EventListener, + true + ); // Unfreezes the menu whenever the user clicks. this.pmView.dom.addEventListener("mousedown", this.onMouseDown); // Hides and unfreezes the menu whenever the user presses a key. - document.body.addEventListener("keydown", this.onKeyDown, true); + this.pmView.root.addEventListener( + "keydown", + this.onKeyDown as EventListener, + true + ); // Setting capture=true ensures that any parent container of the editor that // gets scrolled will trigger the scroll event. Scroll events do not bubble // and so won't propagate to the document by default. - document.addEventListener("scroll", this.onScroll, true); + this.pmView.root.addEventListener("scroll", this.onScroll, true); } updateState = () => { @@ -322,7 +346,10 @@ export class SideMenuView< top: this.mousePos.y, }; - const elements = document.elementsFromPoint(coords.left, coords.top); + const elements = this.pmView.root.elementsFromPoint( + coords.left, + coords.top + ); let block = undefined; for (const element of elements) { @@ -553,13 +580,28 @@ export class SideMenuView< this.state.show = false; this.emitUpdate(this.state); } - document.body.removeEventListener("mousemove", this.onMouseMove, true); - document.body.removeEventListener("dragover", this.onDragOver); + this.pmView.root.removeEventListener( + "mousemove", + this.onMouseMove as EventListener, + true + ); + this.pmView.root.removeEventListener( + "dragover", + this.onDragOver as EventListener + ); this.pmView.dom.removeEventListener("dragstart", this.onDragStart); - document.body.removeEventListener("drop", this.onDrop, true); - document.removeEventListener("scroll", this.onScroll, true); + this.pmView.root.removeEventListener( + "drop", + this.onDrop as EventListener, + true + ); + this.pmView.root.removeEventListener("scroll", this.onScroll, true); this.pmView.dom.removeEventListener("mousedown", this.onMouseDown); - document.body.removeEventListener("keydown", this.onKeyDown, true); + this.pmView.root.removeEventListener( + "keydown", + this.onKeyDown as EventListener, + true + ); } addBlock() { @@ -665,7 +707,7 @@ export class SideMenuProsemirrorPlugin< /** * Handles drag & drop events for blocks. */ - blockDragEnd = () => unsetDragImage(); + blockDragEnd = () => unsetDragImage(this.editor.prosemirrorView.root); /** * Freezes the side menu. When frozen, the side menu will stay * attached to the same block regardless of which block is hovered by the diff --git a/packages/core/src/extensions/SuggestionMenu/SuggestionPlugin.ts b/packages/core/src/extensions/SuggestionMenu/SuggestionPlugin.ts index a557b165d3..d64bd83984 100644 --- a/packages/core/src/extensions/SuggestionMenu/SuggestionPlugin.ts +++ b/packages/core/src/extensions/SuggestionMenu/SuggestionPlugin.ts @@ -20,7 +20,7 @@ class SuggestionMenuView< > { public state?: SuggestionMenuState; public emitUpdate: (triggerCharacter: string) => void; - + private rootEl?: Document | ShadowRoot; pluginState: SuggestionPluginState; constructor( @@ -37,15 +37,17 @@ class SuggestionMenuView< emitUpdate(menuName, this.state); }; + this.rootEl = this.editor._tiptapEditor.view.root; + // Setting capture=true ensures that any parent container of the editor that // gets scrolled will trigger the scroll event. Scroll events do not bubble // and so won't propagate to the document by default. - document.addEventListener("scroll", this.handleScroll, true); + this.rootEl.addEventListener("scroll", this.handleScroll, true); } handleScroll = () => { if (this.state?.show) { - const decorationNode = document.querySelector( + const decorationNode = this.rootEl?.querySelector( `[data-decoration-id="${this.pluginState!.decorationId}"]` ); this.state.referencePos = decorationNode!.getBoundingClientRect(); @@ -79,7 +81,7 @@ class SuggestionMenuView< return; } - const decorationNode = document.querySelector( + const decorationNode = this.rootEl?.querySelector( `[data-decoration-id="${this.pluginState!.decorationId}"]` ); @@ -95,7 +97,7 @@ class SuggestionMenuView< } destroy() { - document.removeEventListener("scroll", this.handleScroll, true); + this.rootEl?.removeEventListener("scroll", this.handleScroll, true); } closeMenu = () => { diff --git a/packages/core/src/extensions/TableHandles/TableHandlesPlugin.ts b/packages/core/src/extensions/TableHandles/TableHandlesPlugin.ts index 57f1533773..b30f45a92d 100644 --- a/packages/core/src/extensions/TableHandles/TableHandlesPlugin.ts +++ b/packages/core/src/extensions/TableHandles/TableHandlesPlugin.ts @@ -36,7 +36,7 @@ export type TableHandlesState< | undefined; }; -function setHiddenDragImage() { +function setHiddenDragImage(rootEl: Document | ShadowRoot) { if (dragImageElement) { return; } @@ -46,12 +46,20 @@ function setHiddenDragImage() { dragImageElement.style.opacity = "0"; dragImageElement.style.height = "1px"; dragImageElement.style.width = "1px"; - document.body.appendChild(dragImageElement); + if (rootEl instanceof Document) { + rootEl.body.appendChild(dragImageElement); + } else { + rootEl.appendChild(dragImageElement); + } } -function unsetHiddenDragImage() { +function unsetHiddenDragImage(rootEl: Document | ShadowRoot) { if (dragImageElement) { - document.body.removeChild(dragImageElement); + if (rootEl instanceof Document) { + rootEl.body.removeChild(dragImageElement); + } else { + rootEl.removeChild(dragImageElement); + } dragImageElement = undefined; } } @@ -73,9 +81,13 @@ function domCellAround(target: Element | null): Element | null { } // Hides elements in the DOMwith the provided class names. -function hideElementsWithClassNames(classNames: string[]) { +function hideElementsWithClassNames( + classNames: string[], + rootEl: Document | ShadowRoot +) { classNames.forEach((className) => { - const elementsToHide = document.getElementsByClassName(className); + const elementsToHide = rootEl.querySelectorAll(className); + for (let i = 0; i < elementsToHide.length; i++) { (elementsToHide[i] as HTMLElement).style.visibility = "hidden"; } @@ -116,13 +128,16 @@ export class TableHandlesView< pmView.dom.addEventListener("mousemove", this.mouseMoveHandler); - document.addEventListener("dragover", this.dragOverHandler); - document.addEventListener("drop", this.dropHandler); + pmView.root.addEventListener( + "dragover", + this.dragOverHandler as EventListener + ); + pmView.root.addEventListener("drop", this.dropHandler as EventListener); // Setting capture=true ensures that any parent container of the editor that // gets scrolled will trigger the scroll event. Scroll events do not bubble // and so won't propagate to the document by default. - document.addEventListener("scroll", this.scrollHandler, true); + pmView.root.addEventListener("scroll", this.scrollHandler, true); } mouseMoveHandler = (event: MouseEvent) => { @@ -220,11 +235,14 @@ export class TableHandlesView< event.preventDefault(); event.dataTransfer!.dropEffect = "move"; - hideElementsWithClassNames([ - "column-resize-handle", - "prosemirror-dropcursor-block", - "prosemirror-dropcursor-inline", - ]); + hideElementsWithClassNames( + [ + "column-resize-handle", + "prosemirror-dropcursor-block", + "prosemirror-dropcursor-inline", + ], + this.pmView.root + ); // The mouse cursor coordinates, bounded to the table's bounding box. The // bounding box is shrunk by 1px on each side to ensure that the bounded @@ -242,7 +260,7 @@ export class TableHandlesView< // Gets the table cell element that the bounded mouse cursor coordinates lie // in. - const tableCellElements = document + const tableCellElements = this.pmView.root .elementsFromPoint(boundedMouseCoords.left, boundedMouseCoords.top) .filter( (element) => element.tagName === "TD" || element.tagName === "TH" @@ -343,7 +361,7 @@ export class TableHandlesView< scrollHandler = () => { if (this.state?.show) { - const tableElement = document.querySelector( + const tableElement = this.pmView.root.querySelector( `[data-node-type="blockContainer"][data-id="${this.tableId}"] table` )!; const cellElement = tableElement.querySelector( @@ -360,11 +378,15 @@ export class TableHandlesView< destroy() { this.pmView.dom.removeEventListener("mousemove", this.mouseMoveHandler); - - document.removeEventListener("dragover", this.dragOverHandler); - document.removeEventListener("drop", this.dropHandler); - - document.removeEventListener("scroll", this.scrollHandler, true); + this.pmView.root.removeEventListener( + "dragover", + this.dragOverHandler as EventListener + ); + this.pmView.root.removeEventListener( + "drop", + this.dropHandler as EventListener + ); + this.pmView.root.removeEventListener("scroll", this.scrollHandler, true); } } @@ -559,7 +581,7 @@ export class TableHandlesProsemirrorPlugin< }) ); - setHiddenDragImage(); + setHiddenDragImage(this.editor._tiptapEditor.view.root); event.dataTransfer!.setDragImage(dragImageElement!, 0, 0); event.dataTransfer!.effectAllowed = "move"; }; @@ -595,7 +617,7 @@ export class TableHandlesProsemirrorPlugin< }) ); - setHiddenDragImage(); + setHiddenDragImage(this.editor._tiptapEditor.view.root); event.dataTransfer!.setDragImage(dragImageElement!, 0, 0); event.dataTransfer!.effectAllowed = "copyMove"; }; @@ -618,7 +640,7 @@ export class TableHandlesProsemirrorPlugin< this.editor._tiptapEditor.state.tr.setMeta(tableHandlesPluginKey, null) ); - unsetHiddenDragImage(); + unsetHiddenDragImage(this.editor._tiptapEditor.view.root); }; /** diff --git a/tests/src/end-to-end/dragdrop/dragdrop.test.ts b/tests/src/end-to-end/dragdrop/dragdrop.test.ts index f51fd953f1..62f11f4324 100644 --- a/tests/src/end-to-end/dragdrop/dragdrop.test.ts +++ b/tests/src/end-to-end/dragdrop/dragdrop.test.ts @@ -1,10 +1,10 @@ import { test } from "../../setup/setupScript"; import { BASE_URL, - BLOCK_CONTAINER_SELECTOR, H_ONE_BLOCK_SELECTOR, H_THREE_BLOCK_SELECTOR, H_TWO_BLOCK_SELECTOR, + PARAGRAPH_SELECTOR, } from "../../utils/const"; import { compareDocToSnapshot, focusOnEditor } from "../../utils/editor"; import { insertHeading, insertParagraph } from "../../utils/copypaste"; @@ -75,7 +75,7 @@ test.describe("Check Block Dragging Functionality", () => { // Dragging third heading into outside nesting. dragTarget = await page.locator(H_THREE_BLOCK_SELECTOR); - dropTarget = await page.locator(BLOCK_CONTAINER_SELECTOR).last(); + dropTarget = await page.locator(PARAGRAPH_SELECTOR).last(); await dragAndDropBlock(page, dragTarget, dropTarget, true); await compareDocToSnapshot(page, "dragdropnested");