Skip to content

Commit 9bd82a0

Browse files
committed
Update code to use draggable nodes in vscode
1 parent a391bd1 commit 9bd82a0

File tree

2 files changed

+109
-14
lines changed

2 files changed

+109
-14
lines changed

packages/lexical/src/plugins/DraggableBlockPlugin.tsx

Lines changed: 99 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -191,15 +191,71 @@ function setMenuPosition(
191191
const floatingElemRect = floatingElem.getBoundingClientRect();
192192
const anchorElementRect = anchorElem.getBoundingClientRect();
193193

194-
const top =
195-
targetRect.top +
196-
(parseInt(targetStyle.lineHeight, 10) - floatingElemRect.height) / 2 -
197-
anchorElementRect.top;
194+
// BUGFIX: Find the actual scrolling container
195+
// The scrolling element could be anchorElem itself OR its parent, depending on the implementation
196+
// We need to check which element actually has scrollable overflow
197+
function getScrollingElement(elem: HTMLElement): {
198+
element: HTMLElement;
199+
scrollTop: number;
200+
scrollLeft: number;
201+
} {
202+
// Check if the element itself is scrollable
203+
const style = window.getComputedStyle(elem);
204+
const hasScrollY =
205+
style.overflowY === 'auto' || style.overflowY === 'scroll';
206+
const hasScrollX =
207+
style.overflowX === 'auto' || style.overflowX === 'scroll';
208+
209+
if (hasScrollY || hasScrollX) {
210+
return {
211+
element: elem,
212+
scrollTop: elem.scrollTop || 0,
213+
scrollLeft: elem.scrollLeft || 0,
214+
};
215+
}
216+
217+
// Check parent element
218+
const parent = elem.parentElement;
219+
if (parent) {
220+
const parentStyle = window.getComputedStyle(parent);
221+
const parentHasScrollY =
222+
parentStyle.overflowY === 'auto' || parentStyle.overflowY === 'scroll';
223+
const parentHasScrollX =
224+
parentStyle.overflowX === 'auto' || parentStyle.overflowX === 'scroll';
225+
226+
if (parentHasScrollY || parentHasScrollX) {
227+
return {
228+
element: parent,
229+
scrollTop: parent.scrollTop || 0,
230+
scrollLeft: parent.scrollLeft || 0,
231+
};
232+
}
233+
}
234+
235+
// No scrolling container found
236+
return {
237+
element: elem,
238+
scrollTop: 0,
239+
scrollLeft: 0,
240+
};
241+
}
242+
243+
const { scrollTop } = getScrollingElement(anchorElem);
244+
245+
// Calculate position
246+
const lineHeight = parseInt(targetStyle.lineHeight, 10);
247+
const topOffset = (lineHeight - floatingElemRect.height) / 2;
248+
249+
// Original calculation (without scroll compensation)
250+
const topOriginal = targetRect.top + topOffset - anchorElementRect.top;
251+
252+
// With scroll compensation
253+
const topWithScroll = topOriginal + scrollTop;
198254

199255
const left = SPACE;
200256

201257
floatingElem.style.opacity = '1';
202-
floatingElem.style.transform = `translate(${left}px, ${top}px)`;
258+
floatingElem.style.transform = `translate(${left}px, ${topWithScroll}px)`;
203259
}
204260

205261
function setDragImage(
@@ -228,14 +284,34 @@ function setTargetLine(
228284
const { top: anchorTop, width: anchorWidth } =
229285
anchorElem.getBoundingClientRect();
230286
const { marginTop, marginBottom } = getCollapsedMargins(targetBlockElem);
287+
288+
// BUGFIX: Get scroll offset using the same logic as setMenuPosition
289+
function getScrollTop(elem: HTMLElement): number {
290+
const style = window.getComputedStyle(elem);
291+
const hasScroll =
292+
style.overflowY === 'auto' || style.overflowY === 'scroll';
293+
if (hasScroll) return elem.scrollTop || 0;
294+
295+
const parent = elem.parentElement;
296+
if (parent) {
297+
const parentStyle = window.getComputedStyle(parent);
298+
const parentHasScroll =
299+
parentStyle.overflowY === 'auto' || parentStyle.overflowY === 'scroll';
300+
if (parentHasScroll) return parent.scrollTop || 0;
301+
}
302+
return 0;
303+
}
304+
305+
const scrollTop = getScrollTop(anchorElem);
306+
231307
let lineTop = targetBlockElemTop;
232308
if (mouseY >= targetBlockElemTop) {
233309
lineTop += targetBlockElemHeight + marginBottom / 2;
234310
} else {
235311
lineTop -= marginTop / 2;
236312
}
237313

238-
const top = lineTop - anchorTop - TARGET_LINE_HALF_HEIGHT;
314+
const top = lineTop - anchorTop - TARGET_LINE_HALF_HEIGHT + scrollTop;
239315
const left = TEXT_BOX_HORIZONTAL_PADDING - SPACE;
240316

241317
targetLineElem.style.transform = `translate(${left}px, ${top}px)`;
@@ -265,6 +341,11 @@ function useDraggableBlockMenu(
265341
const [draggableBlockElem, setDraggableBlockElem] =
266342
useState<HTMLElement | null>(null);
267343

344+
// Get theme classes from editor config
345+
const theme = editor._config.theme;
346+
const menuThemeClass = theme.draggableBlockMenu || '';
347+
const targetLineThemeClass = theme.draggableBlockTargetLine || '';
348+
268349
useEffect(() => {
269350
function onMouseMove(event: MouseEvent) {
270351
const target = event.target;
@@ -286,12 +367,15 @@ function useDraggableBlockMenu(
286367
setDraggableBlockElem(null);
287368
}
288369

289-
scrollerElem?.addEventListener('mousemove', onMouseMove);
290-
scrollerElem?.addEventListener('mouseleave', onMouseLeave);
370+
// BUGFIX: Attach to document instead of scrollerElem to ensure mouse events
371+
// work even after scrolling. The scrollerElem approach fails when content scrolls
372+
// because the mouse event coordinates become misaligned with element positions.
373+
document.addEventListener('mousemove', onMouseMove);
374+
document.addEventListener('mouseleave', onMouseLeave);
291375

292376
return () => {
293-
scrollerElem?.removeEventListener('mousemove', onMouseMove);
294-
scrollerElem?.removeEventListener('mouseleave', onMouseLeave);
377+
document.removeEventListener('mousemove', onMouseMove);
378+
document.removeEventListener('mouseleave', onMouseLeave);
295379
};
296380
}, [scrollerElem, anchorElem, editor]);
297381

@@ -412,15 +496,18 @@ function useDraggableBlockMenu(
412496
return createPortal(
413497
<>
414498
<div
415-
className="icon draggable-block-menu"
499+
className={`icon draggable-block-menu ${menuThemeClass}`}
416500
ref={menuRef}
417501
draggable
418502
onDragStart={onDragStart}
419503
onDragEnd={onDragEnd}
420504
>
421505
<div className={isEditable ? 'icon' : ''} />
422506
</div>
423-
<div className="draggable-block-target-line" ref={targetLineRef} />
507+
<div
508+
className={`draggable-block-target-line ${targetLineThemeClass}`}
509+
ref={targetLineRef}
510+
/>
424511
</>,
425512
anchorElem,
426513
);

packages/lexical/style/lexical/DraggableBlockPlugin.css

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,16 +18,24 @@
1818
.draggable-block-menu .icon {
1919
width: 16px;
2020
height: 16px;
21-
opacity: 0.3;
21+
opacity: 0.4;
2222
background-image: url(./icons/draggable-block-menu.svg);
23+
background-size: contain;
24+
background-position: center;
25+
background-repeat: no-repeat;
2326
}
2427

2528
.draggable-block-menu:active {
2629
cursor: grabbing;
2730
}
2831

2932
.draggable-block-menu:hover {
30-
background-color: #efefef;
33+
background-color: rgba(128, 128, 128, 0.1);
34+
opacity: 1;
35+
}
36+
37+
.draggable-block-menu:hover .icon {
38+
opacity: 0.8;
3139
}
3240

3341
.draggable-block-target-line {

0 commit comments

Comments
 (0)