From 4290f4d9558fe0789ea94bcb6882e1ab8ec08a09 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Loi=CC=88c=20Correnson?= <loic.correnson@cea.fr> Date: Sat, 4 Jun 2022 10:26:10 +0200 Subject: [PATCH] [dome/dnd] continuous drop hover --- ivette/src/dome/renderer/newdnd.tsx | 32 ++++++++----- ivette/src/sandbox/usednd.tsx | 72 +++++++++++++++++++++++++---- 2 files changed, 83 insertions(+), 21 deletions(-) diff --git a/ivette/src/dome/renderer/newdnd.tsx b/ivette/src/dome/renderer/newdnd.tsx index aaf67ca885a..da3e81d351a 100644 --- a/ivette/src/dome/renderer/newdnd.tsx +++ b/ivette/src/dome/renderer/newdnd.tsx @@ -102,7 +102,15 @@ interface DropZone extends DropHandler { node: HTMLElement; } -class DnD { +/** + D&D Controller. + + This class allows to connect Drag Sources and Drop Target with each others. + You shall never use the methods of the `DnD` class directly. + + The preferred way for creating `DnD` classes is to use the `useDnD()` React Hook. + */ +export class DnD { private registry = new Map<string, DropZone>(); private dragging: HTMLElement | undefined; @@ -139,13 +147,13 @@ class DnD { if (hover !== curr) { this.hovering = hover; if (curr && curr.onDropOut) { curr.onDropOut(); } - if (hover && hover.onDropIn) { - const meta = e.altKey || e.ctrlKey || e.shiftKey || e.metaKey; - const rect = hover.node.getBoundingClientRect(); - const dropX = e.clientX - rect.left; - const dropY = e.clientY - rect.top; - hover.onDropIn({ meta, rect, dropX, dropY }); - } + } + if (hover && hover.onDropIn) { + const meta = e.altKey || e.ctrlKey || e.shiftKey || e.metaKey; + const rect = hover.node.getBoundingClientRect(); + const dropX = Math.round(e.clientX - rect.left); + const dropY = Math.round(e.clientY - rect.top); + hover.onDropIn({ meta, rect, dropX, dropY }); } } } @@ -175,8 +183,8 @@ export function useDropTarget( const onDrop = handlers?.onDrop; const onDropIn = handlers?.onDropIn; const onDropOut = handlers?.onDropOut; + const node = nodeRef.current; React.useEffect(() => { - const node = nodeRef.current; if ( dnd && node && (onDrop || onDropIn || onDropOut) ) { @@ -184,7 +192,7 @@ export function useDropTarget( return () => dnd.offDropZone(id); } return; - }, [dnd, onDrop, onDropIn, onDropOut]); + }, [dnd, node, onDrop, onDropIn, onDropOut]); return nodeRef; } @@ -210,7 +218,7 @@ export interface DropTargetProps extends DropHandler { */ export function DropTarget(props: DropTargetProps): JSX.Element { - const { dnd, disabled, className, style, children } = props; + const { dnd, disabled = false, className, style, children } = props; const nodeRef = useDropTarget(dnd, disabled ? undefined : props); return ( <div ref={nodeRef} className={className} style={style}> @@ -310,7 +318,7 @@ export interface DragSourceProps extends DragHandler, DropHandler { */ export function DragSource(props: DragSourceProps): JSX.Element { //--- Props - const { dnd, disabled, handle, children } = props; + const { dnd, disabled = false, handle, children } = props; const { onStart, onDrag, onStop } = props; //--- Dragging State const [dragging, setDragging] = React.useState<Dragging | undefined>(); diff --git a/ivette/src/sandbox/usednd.tsx b/ivette/src/sandbox/usednd.tsx index 090483e6e23..173c90e1d48 100644 --- a/ivette/src/sandbox/usednd.tsx +++ b/ivette/src/sandbox/usednd.tsx @@ -26,31 +26,42 @@ /* -------------------------------------------------------------------------- */ import React from 'react'; +import { Label } from 'dome/controls/labels'; import { LCD } from 'dome/controls/displays'; +import { Button } from 'dome/controls/buttons'; import * as Box from 'dome/layout/boxes'; import * as DnD from 'dome/newdnd'; import { registerSandbox } from 'ivette'; import './sandbox.css'; -const delta = (id: string, d: DnD.Dragging): string => { +const source = (id: string, d: DnD.Dragging): string => { const dx = d.dragX - d.rootX; const dy = d.dragY - d.rootY; return `${id} ${dx}:${dy}`; }; +const target = (id: string, d: DnD.Dropping): string => { + const dx = d.dropX; + const dy = d.dropY; + const m = d.meta ? '+' : '?'; + return `${id}${m} ${dx}:${dy}`; +}; + interface BlobProps { id: string; + dnd: DnD.DnD; setState: (s: string) => void; } -function Blob(props: BlobProps): JSX.Element { - const { id, setState } = props; +function Source(props: BlobProps): JSX.Element { + const { id, dnd, setState } = props; return ( <DnD.DragSource className='sandbox-item' styleDragging={{ background: 'lightgreen' }} + dnd={dnd} onStart={() => setState(id)} - onDrag={(d) => setState(delta(id, d))} + onDrag={(d) => setState(source(id, d))} onStop={() => setState('--')} > Blob #{id} @@ -58,19 +69,55 @@ function Blob(props: BlobProps): JSX.Element { ); } +function Target(props: BlobProps): JSX.Element { + const { id, dnd, setState } = props; + return ( + <DnD.DropTarget + className='sandbox-item' + dnd={dnd} + onDrop={() => setState(id)} + onDropIn={(d) => setState(target(id, d))} + onDropOut={() => setState('--')} + > + Zone #{id} + </DnD.DropTarget> + ); +} + function Item({ id }: { id: string }): JSX.Element { return <DnD.Item className='sandbox-item' id={id}>Item {id}</DnD.Item>; } function UseDnD(): JSX.Element { - const [state, setState] = React.useState('--'); + const dnd = DnD.useDnD(); + const [items, setItems] = React.useState<string[]>([]); + const [source, setSource] = React.useState('--'); + const [target, setTarget] = React.useState('--'); + const onReset = () => { + setItems([]); + setSource('--'); + setTarget('--'); + }; return ( <Box.Vfill> <Box.Hbox> - <LCD label={state} /> + <Button label='Reset' onClick={onReset} /> + <LCD label={source} /> + <LCD label={target} /> </Box.Hbox> <Box.Hbox> <Box.Vbox> + <Label label='Controlled list' /> + <DnD.List items={items} setItems={setItems}> + <Item id='A' /> + <Item id='B' /> + <Item id='C' /> + <Item id='D' /> + <Item id='E' /> + </DnD.List> + </Box.Vbox> + <Box.Vbox> + <Label label='Uncontrolled list' /> <DnD.List> <Item id='A' /> <Item id='B' /> @@ -80,9 +127,16 @@ function UseDnD(): JSX.Element { </DnD.List> </Box.Vbox> <Box.Vbox> - <Blob id='A' setState={setState} /> - <Blob id='B' setState={setState} /> - <Blob id='C' setState={setState} /> + <Label label='Draggable Blobs' /> + <Source id='A' dnd={dnd} setState={setSource} /> + <Source id='B' dnd={dnd} setState={setSource} /> + <Source id='C' dnd={dnd} setState={setSource} /> + </Box.Vbox> + <Box.Vbox> + <Label label='Droppable Blobs' /> + <Target id='X' dnd={dnd} setState={setTarget} /> + <Target id='Y' dnd={dnd} setState={setTarget} /> + <Target id='Z' dnd={dnd} setState={setTarget} /> </Box.Vbox> </Box.Hbox> </Box.Vfill > -- GitLab