Skip to content

Commit 0c2875e

Browse files
sescandellsocscandelletrepum
authored
[lexical-playground][lexical-react] Feature: Push Draggable Element to Parent (#7338)
Co-authored-by: Stéphane Escandell <[email protected]> Co-authored-by: Bob Ippolito <[email protected]>
1 parent 3a52948 commit 0c2875e

File tree

5 files changed

+59
-7
lines changed

5 files changed

+59
-7
lines changed

packages/lexical-playground/src/plugins/DraggableBlockPlugin/index.css

+8-1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
left: 0;
88
top: 0;
99
will-change: transform;
10+
display: flex;
11+
gap: 2px;
1012
}
1113

1214
.draggable-block-menu .icon {
@@ -16,11 +18,16 @@
1618
background-image: url(../../images/icons/draggable-block-menu.svg);
1719
}
1820

21+
.draggable-block-menu .icon-plus {
22+
cursor: pointer;
23+
background-image: url(../../images/icons/plus.svg);
24+
}
25+
1926
.draggable-block-menu:active {
2027
cursor: grabbing;
2128
}
2229

23-
.draggable-block-menu:hover {
30+
.draggable-block-menu .icon:hover {
2431
background-color: #efefef;
2532
}
2633

packages/lexical-playground/src/plugins/DraggableBlockPlugin/index.tsx

+30-1
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,10 @@ import type {JSX} from 'react';
99

1010
import './index.css';
1111

12+
import {useLexicalComposerContext} from '@lexical/react/LexicalComposerContext';
1213
import {DraggableBlockPlugin_EXPERIMENTAL} from '@lexical/react/LexicalDraggableBlockPlugin';
13-
import {useRef} from 'react';
14+
import {$createParagraphNode, $getNearestNodeFromDOMNode} from 'lexical';
15+
import {useRef, useState} from 'react';
1416

1517
const DRAGGABLE_BLOCK_MENU_CLASSNAME = 'draggable-block-menu';
1618

@@ -23,8 +25,33 @@ export default function DraggableBlockPlugin({
2325
}: {
2426
anchorElem?: HTMLElement;
2527
}): JSX.Element {
28+
const [editor] = useLexicalComposerContext();
2629
const menuRef = useRef<HTMLDivElement>(null);
2730
const targetLineRef = useRef<HTMLDivElement>(null);
31+
const [draggableElement, setDraggableElement] = useState<HTMLElement | null>(
32+
null,
33+
);
34+
35+
function insertBlock(e: React.MouseEvent) {
36+
if (!draggableElement || !editor) {
37+
return;
38+
}
39+
40+
editor.update(() => {
41+
const node = $getNearestNodeFromDOMNode(draggableElement);
42+
if (!node) {
43+
return;
44+
}
45+
46+
const pNode = $createParagraphNode();
47+
if (e.altKey || e.ctrlKey) {
48+
node.insertBefore(pNode);
49+
} else {
50+
node.insertAfter(pNode);
51+
}
52+
pNode.select();
53+
});
54+
}
2855

2956
return (
3057
<DraggableBlockPlugin_EXPERIMENTAL
@@ -33,13 +60,15 @@ export default function DraggableBlockPlugin({
3360
targetLineRef={targetLineRef}
3461
menuComponent={
3562
<div ref={menuRef} className="icon draggable-block-menu">
63+
<div className="icon icon-plus" onClick={insertBlock} />
3664
<div className="icon" />
3765
</div>
3866
}
3967
targetLineComponent={
4068
<div ref={targetLineRef} className="draggable-block-target-line" />
4169
}
4270
isOnMenu={isOnMenu}
71+
onElementChanged={setDraggableElement}
4372
/>
4473
);
4574
}

packages/lexical-playground/src/ui/ContentEditable.css

+2-2
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
display: block;
1313
position: relative;
1414
outline: 0;
15-
padding: 8px 28px 40px;
15+
padding: 8px 46px 40px;
1616
min-height: 150px;
1717
}
1818
@media (max-width: 1025px) {
@@ -29,7 +29,7 @@
2929
position: absolute;
3030
text-overflow: ellipsis;
3131
top: 8px;
32-
left: 28px;
32+
left: 46px;
3333
right: 28px;
3434
user-select: none;
3535
white-space: nowrap;

packages/lexical-react/flow/LexicalDraggableBlockPlugin.js.flow

+1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ type Props = $ReadOnly<{
1616
menuComponent: React.Node,
1717
targetLineComponent: React.Node,
1818
isOnMenu: (element: HTMLElement) => boolean,
19+
onElementChanged?: (element: HTMLElement | null) => void
1920
}>;
2021

2122
declare export function DraggableBlockPlugin_EXPERIMENTAL(

packages/lexical-react/src/LexicalDraggableBlockPlugin.tsx

+18-3
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import {
2424
import {
2525
DragEvent as ReactDragEvent,
2626
ReactNode,
27+
useCallback,
2728
useEffect,
2829
useRef,
2930
useState,
@@ -269,13 +270,24 @@ function useDraggableBlockMenu(
269270
menuComponent: ReactNode,
270271
targetLineComponent: ReactNode,
271272
isOnMenu: (element: HTMLElement) => boolean,
273+
onElementChanged?: (element: HTMLElement | null) => void,
272274
): JSX.Element {
273275
const scrollerElem = anchorElem.parentElement;
274276

275277
const isDraggingBlockRef = useRef<boolean>(false);
276-
const [draggableBlockElem, setDraggableBlockElem] =
278+
const [draggableBlockElem, setDraggableBlockElemState] =
277279
useState<HTMLElement | null>(null);
278280

281+
const setDraggableBlockElem = useCallback(
282+
(elem: HTMLElement | null) => {
283+
setDraggableBlockElemState(elem);
284+
if (onElementChanged) {
285+
onElementChanged(elem);
286+
}
287+
},
288+
[onElementChanged],
289+
);
290+
279291
useEffect(() => {
280292
function onMouseMove(event: MouseEvent) {
281293
const target = event.target;
@@ -308,7 +320,7 @@ function useDraggableBlockMenu(
308320
scrollerElem.removeEventListener('mouseleave', onMouseLeave);
309321
}
310322
};
311-
}, [scrollerElem, anchorElem, editor, isOnMenu]);
323+
}, [scrollerElem, anchorElem, editor, isOnMenu, setDraggableBlockElem]);
312324

313325
useEffect(() => {
314326
if (menuRef.current) {
@@ -401,7 +413,7 @@ function useDraggableBlockMenu(
401413
COMMAND_PRIORITY_HIGH,
402414
),
403415
);
404-
}, [anchorElem, editor, targetLineRef]);
416+
}, [anchorElem, editor, targetLineRef, setDraggableBlockElem]);
405417

406418
function onDragStart(event: ReactDragEvent<HTMLDivElement>): void {
407419
const dataTransfer = event.dataTransfer;
@@ -442,13 +454,15 @@ export function DraggableBlockPlugin_EXPERIMENTAL({
442454
menuComponent,
443455
targetLineComponent,
444456
isOnMenu,
457+
onElementChanged,
445458
}: {
446459
anchorElem?: HTMLElement;
447460
menuRef: React.RefObject<HTMLElement>;
448461
targetLineRef: React.RefObject<HTMLElement>;
449462
menuComponent: ReactNode;
450463
targetLineComponent: ReactNode;
451464
isOnMenu: (element: HTMLElement) => boolean;
465+
onElementChanged?: (element: HTMLElement | null) => void;
452466
}): JSX.Element {
453467
const [editor] = useLexicalComposerContext();
454468
return useDraggableBlockMenu(
@@ -460,5 +474,6 @@ export function DraggableBlockPlugin_EXPERIMENTAL({
460474
menuComponent,
461475
targetLineComponent,
462476
isOnMenu,
477+
onElementChanged,
463478
);
464479
}

0 commit comments

Comments
 (0)