Skip to content
Snippets Groups Projects
Commit ebaf6891 authored by Loïc Correnson's avatar Loïc Correnson
Browse files

[dome/dnd] full featured DragSource

parent 559edd0b
No related branches found
No related tags found
No related merge requests found
......@@ -29,60 +29,157 @@
*/
import React from 'react';
import { classes, styles } from 'dome/misc/utils';
import {
DraggableCore,
DraggableEventHandler
} from 'react-draggable';
/**
Current dragging informations:
- `rootX,rootY` is the position where dragging started;
- `dragX,dragY` is the current dragging position;
- `rect` is the original DOM Rectangle of the dragged HTML node.
Hence, the relative move during dragging is simply `(dragX-rootX,dragY-rootY)`.
*/
export interface Dragging {
rootX: number;
rootY: number;
dragX: number;
dragY: number;
rect: DOMRect;
}
/**
Can be used to conditionally render an element wrt to dragging informations.
*/
export type DraggingRenderer = (d: Dragging | undefined) => JSX.Element;
interface OverlayRendering {
outerClass?: string;
innerClass?: string;
outerStyle?: React.CSSProperties;
innerStyle?: React.CSSProperties;
}
function RenderOverlay(
props: DragSourceProps,
dragging: Dragging | undefined,
): OverlayRendering {
const { className, style } = props;
if (dragging) {
const { dragX, dragY, rootX, rootY, rect } = dragging;
const { left, top, width, height } = rect;
const {
zIndex = 1,
offsetX = 0,
offsetY = 0,
classDragged = 'dome-dragged',
classDragging = 'dome-dragging',
} = props;
const position: React.CSSProperties = {
position: 'fixed',
left: left + offsetX + dragX - rootX,
top: top + offsetY + dragY - rootY,
width, height, zIndex, margin: 0
};
const holder = { width, height };
return {
outerClass: classes(className, classDragged),
innerClass: classes(className, classDragging),
outerStyle: styles(style, props.styleDragged, holder),
innerStyle: styles(style, props.styleDragging, position),
};
}
return { outerClass: className, outerStyle: style }
}
export interface DragSourceProps {
/** Disabled dragging. */
disabled?: boolean;
/** Class of the element from where a drag can be initiated. */
handle?: string;
children?: React.ReactNode;
/** Class of the DragSource elements. */
className?: string;
/** Style of the DragSource elements. */
style?: React.CSSProperties;
/** Additional class for the dragged (initial) element (default is `'dome-dragged'`). */
classDragged?: string;
/** Additional class for the dragging (moved) element (default is `'dome-dragging'`) */
classDragging?: string;
/** Additional style for the dragged (initial) element. */
styleDragged?: React.CSSProperties;
/** Additional style for the dragging (moved) element. */
styleDragging?: React.CSSProperties;
/** X-offset when dragging (defaults to 0). */
offsetX?: number;
/** Y-offset when dragging (defaults to 0). */
offsetY?: number;
/** Z-index when dragging (defaults to 1). */
zIndex?: number;
/** Callback when drag is initiated. */
onStart?: () => void;
onDrag?: (deltaX: number, deltaY: number) => void;
/** Callback current dragging. */
onDrag?: (dragging: Dragging) => void;
/** Callback when drag is interrupted. */
onStop?: () => void;
/** Inner contents of the DragSource element. */
children?: React.ReactNode | DraggingRenderer;
}
interface Dragging {
rootX: number,
rootY: number,
dragX: number,
dragY: number,
}
/**
This container can be dragged around all over the application window. Its
content is rendered inside a double `<div/>`, the outer one being fixed when
dragged, and the inner one being moved around when dragging.
The content can be rendered conditionnaly by using a function.
*/
export function DragSource(props: DragSourceProps): JSX.Element | null {
//--- Props
const { disabled, handle, children } = props;
const { onStart, onDrag, onStop } = props;
//--- Dragging State
const [dragging, setDragging] = React.useState<Dragging | undefined>();
const onStart: DraggableEventHandler = (_, { x, y }) => {
setDragging({
rootX: x, rootY: y,
dragX: x, dragY: y
});
if (props.onStart) props.onStart();
};
const onDrag: DraggableEventHandler = (_, { x, y }) => {
if (dragging) {
setDragging({ ...dragging, dragX: x, dragY: y });
if (props.onDrag) {
const deltaX = x - dragging.rootX;
const deltaY = y - dragging.rootY;
props.onDrag(deltaX, deltaY);
//--- onStart
const handleStart: DraggableEventHandler = React.useCallback(
(_, { x, y, node }) => {
setDragging({
rootX: x, rootY: y,
dragX: x, dragY: y,
rect: node.getBoundingClientRect(),
});
if (onStart) onStart();
}, [onStart]);
//--- onDrag
const handleDrag: DraggableEventHandler = React.useCallback(
(_, { x, y }) => {
if (dragging) {
setDragging({ ...dragging, dragX: x, dragY: y });
if (onDrag) onDrag(dragging);
}
}
};
const onStop: DraggableEventHandler = () => {
setDragging(undefined);
if (props.onStop) props.onStop();
};
}, [dragging, onDrag]);
//--- onStop
const handleStop: DraggableEventHandler = React.useCallback(
() => {
setDragging(undefined);
if (onStop) onStop();
}, [onStop]);
//--- Renderer
const render = RenderOverlay(props, dragging);
return (
<DraggableCore
disabled={disabled}
handle={handle}
onStart={onStart}
onDrag={onDrag}
onStop={onStop}
onStart={handleStart}
onDrag={handleDrag}
onStop={handleStop}
>
<div>{children}</div>
<div className={render.outerClass} style={render.outerStyle}>
<div className={render.innerClass} style={render.innerStyle}>
{typeof (children) === 'function' ? children(dragging) : children}
</div>
</div>
</DraggableCore>
);
}
......@@ -69,7 +69,7 @@ div.dome-dragged {
border: none ;
}
.dome-dragging * {
div.dome-dragging, .dome-dragging * {
cursor: move ;
}
......
.sandbox-item {
background: var(--lcd-button-background);
border-radius: 4px;
padding: 2px;
margin: 2px;
width: 100px;
text-align: center;
}
......@@ -26,28 +26,50 @@
/* -------------------------------------------------------------------------- */
import React from 'react';
//import * as Dome from 'dome';
//import * as Ctrl from 'dome/controls/buttons';
import * as Disp from 'dome/controls/displays';
import { LCD } from 'dome/controls/displays';
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 dx = d.dragX - d.rootX;
const dy = d.dragY - d.rootY;
return `${id} ${dx}:${dy}`
};
interface ItemProps {
id: string;
setState: (s: string) => void;
}
function Item(props: ItemProps): JSX.Element {
const { id, setState } = props;
return (
<DnD.DragSource
className='sandbox-item'
styleDragging={{ background: 'lightgreen' }}
onStart={() => setState(id)}
onDrag={(d) => setState(delta(id, d))}
onStop={() => setState('--')}
>
Item {id}
</DnD.DragSource>
);
}
function UseDnD(): JSX.Element {
const [state, setState] = React.useState('--');
//const [blink, setBlink] = React.useState(false);
return (
<Box.Vfill>
<Box.Hbox>
<Disp.LCD label={state} />
<LCD label={state} />
</Box.Hbox>
<DnD.DragSource
onStart={() => setState('??')}
onDrag={(x, y) => setState(`${x}:${y}`)}
onStop={() => setState('--')}
>
Using Drag & Drop
</DnD.DragSource>
<Box.Vbox>
<Item id='A' setState={setState} />
<Item id='B' setState={setState} />
<Item id='C' setState={setState} />
</Box.Vbox>
</Box.Vfill>
);
}
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment