Comments (4)
So I found the cause, not sure how can I fix it.
The problem is that I show Novel inside the Dialog window from Shadcn, therefore the Drag & Drop handle is moved almost exactly by the position of the window. I replicated same behaviour in https://github.com/excalidraw/excalidraw, but there whenever I resize the window it fixes the bug, that is not the case inside Novel.
I am working on the example fix.
from novel.
Possible fix
I tried to fix this and I managed to do so with Novel!
Please take a look here (fixed with full size):
https://edution-git-fix-mouse-position-in-dialog-pegak.vercel.app/demo/dialog/edit
And original (smaller dialog size):
https://coursition.com/demo/dialog/edit
Steps to reproduce
- Add TextEditor component
- Open the editor
- See the behaviour of the mouse (Drag & Drop in Text Editor)
from novel.
Running into the same issue. How did you fix it @BleedingDev?
from novel.
I ended up modifying drag-and-drop
as follows:
import { Extension } from '@tiptap/core'
import { NodeSelection, Plugin } from '@tiptap/pm/state'
// @ts-ignore
import { __serializeForClipboard, EditorView } from '@tiptap/pm/view'
import { isNotNil } from '@steepleinc/shared'
export interface DragHandleOptions {
/**
* The width of the drag handle
*/
dragHandleWidth: number
}
function absoluteRect(node: Element) {
const data = node.getBoundingClientRect()
const modal = node.closest('[role="dialog"]')
if (isNotNil(modal) && window.getComputedStyle(modal).transform !== 'none') {
const modalRect = modal.getBoundingClientRect()
return {
top: data.top - modalRect.top,
left: data.left - modalRect.left,
width: data.width,
}
}
return {
top: data.top,
left: data.left,
width: data.width,
}
}
function nodeDOMAtCoords(coords: { x: number; y: number }) {
return document
.elementsFromPoint(coords.x, coords.y)
.find(
(elem: Element) =>
elem.parentElement?.matches?.('.ProseMirror') ||
elem.matches(
[
'li',
'p:not(:first-child)',
'pre',
'blockquote',
'h1, h2, h3, h4, h5, h6',
].join(', '),
),
)
}
function nodePosAtDOM(node: Element, view: EditorView) {
const boundingRect = node.getBoundingClientRect()
return view.posAtCoords({
left: boundingRect.left + 1,
top: boundingRect.top + 1,
})?.inside
}
function DragHandle(options: DragHandleOptions) {
function handleDragStart(event: DragEvent, view: EditorView) {
view.focus()
if (!event.dataTransfer) return
const node = nodeDOMAtCoords({
x: event.clientX + 50 + options.dragHandleWidth,
y: event.clientY,
})
if (!(node instanceof Element)) return
const nodePos = nodePosAtDOM(node, view)
if (nodePos == null || nodePos < 0) return
view.dispatch(
view.state.tr.setSelection(NodeSelection.create(view.state.doc, nodePos)),
)
const slice = view.state.selection.content()
const { dom, text } = __serializeForClipboard(view, slice)
event.dataTransfer.clearData()
event.dataTransfer.setData('text/html', dom.innerHTML)
event.dataTransfer.setData('text/plain', text)
event.dataTransfer.effectAllowed = 'copyMove'
event.dataTransfer.setDragImage(node, 0, 0)
view.dragging = { slice, move: event.ctrlKey }
}
function handleClick(event: MouseEvent, view: EditorView) {
view.focus()
view.dom.classList.remove('dragging')
const node = nodeDOMAtCoords({
x: event.clientX + 50 + options.dragHandleWidth,
y: event.clientY,
})
if (!(node instanceof Element)) return
const nodePos = nodePosAtDOM(node, view)
if (!nodePos) return
view.dispatch(
view.state.tr.setSelection(NodeSelection.create(view.state.doc, nodePos)),
)
}
let dragHandleElement: HTMLElement | null = null
function hideDragHandle() {
if (dragHandleElement) {
dragHandleElement.classList.add('hidden')
}
}
function showDragHandle() {
if (dragHandleElement) {
dragHandleElement.classList.remove('hidden')
}
}
return new Plugin({
view: (view) => {
dragHandleElement = document.createElement('div')
dragHandleElement.draggable = true
dragHandleElement.dataset.dragHandle = ''
dragHandleElement.classList.add('drag-handle')
dragHandleElement.addEventListener('dragstart', (e) => {
handleDragStart(e, view)
})
dragHandleElement.addEventListener('click', (e) => {
handleClick(e, view)
})
hideDragHandle()
view?.dom?.parentElement?.appendChild(dragHandleElement)
return {
destroy: () => {
dragHandleElement?.remove?.()
dragHandleElement = null
},
}
},
props: {
handleDOMEvents: {
mousemove: (view, event) => {
if (!view.editable) {
return
}
const node = nodeDOMAtCoords({
x: event.clientX + 50 + options.dragHandleWidth,
y: event.clientY,
})
if (!(node instanceof Element) || node.matches('ul, ol')) {
hideDragHandle()
return
}
const compStyle = window.getComputedStyle(node)
const lineHeight = parseInt(compStyle.lineHeight, 10)
const paddingTop = parseInt(compStyle.paddingTop, 10)
const rect = absoluteRect(node)
rect.top += (lineHeight - 24) / 2
rect.top += paddingTop
// Li markers
if (node.matches('ul:not([data-type=taskList]) li, ol li')) {
rect.left -= options.dragHandleWidth
}
rect.width = options.dragHandleWidth
if (!dragHandleElement) return
dragHandleElement.style.left = `${rect.left - rect.width}px`
dragHandleElement.style.top = `${rect.top}px`
showDragHandle()
},
keydown: () => {
hideDragHandle()
},
mousewheel: () => {
hideDragHandle()
},
// dragging class is used for CSS
dragstart: (view) => {
view.dom.classList.add('dragging')
},
drop: (view) => {
view.dom.classList.remove('dragging')
},
dragend: (view) => {
view.dom.classList.remove('dragging')
},
},
},
})
}
interface DragAndDropOptions {}
export const DragAndDrop = Extension.create<DragAndDropOptions>({
name: 'dragAndDrop',
addProseMirrorPlugins() {
return [
DragHandle({
dragHandleWidth: 24,
}),
]
},
})
They key code is where we find a parent dialog
and then subtract out it's top and left value.
The problem is most dialogs have a transform on them which doesn't get accounted for in a child's getBoundingClientRect
.
from novel.
Related Issues (20)
- bug: Can't close bubble menu
- feat: Can you create a full guide on integration in NextJS because there is so much error with using your documentation HOT 1
- bug: Usage of `editor.chain().focus()` removes focus from tippy popover HOT 2
- feat: Pasting Markdown Support
- bug: image blue box selection doesn't change
- feat: docs / custom extension idea
- Global CSS cannot be imported from within node_modules.bug: HOT 3
- RangeError: Schema is missing its top node type ('doc')bug: HOT 2
- feat: Bubble Menu to be on top of the document, like options. HOT 4
- bug: The tailwind example link on the setup page redirects to a wrong file "editor.tsx" HOT 1
- Add Image with Link Support
- bug: Drag & Drop in Numbered List doesn't work as expected HOT 2
- feat: Export to .pdf and .docs HOT 2
- bug: continue writing issue HOT 2
- bug: remix │ |- TypeError [ERR_UNKNOWN_FILE_EXTENSION]: Unknown file extension ".css" node_modules/katex/dist/katex.min.css HOT 2
- feat: Recommend updating package's model to GPT-4o-mini HOT 2
- bug: editor.chain(...).focus(...).toggleUnderline is not a function when underlining a word HOT 5
- BUG:: i am not able to use in vite+React app can you please suggest me a repo or sandbox for me? I want to use notion like interface .
- bug: Using two editors in the same repository creates problems HOT 1
- bug: BubbleMenu doesn't dismiss when you click on empty space after color
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from novel.