mirror of
https://github.com/billsonnn/nitro-react.git
synced 2024-11-23 14:40:50 +01:00
Update draggable component
This commit is contained in:
parent
5b6c8d9ce9
commit
e8a5bca6c9
@ -5,12 +5,12 @@ import { NitroCardViewProps } from './NitroCardView.types';
|
|||||||
|
|
||||||
export const NitroCardView: FC<NitroCardViewProps> = props =>
|
export const NitroCardView: FC<NitroCardViewProps> = props =>
|
||||||
{
|
{
|
||||||
const { className = '', disableDrag = false, simple = false, theme = 'primary', children = null } = props;
|
const { className = '', simple = false, theme = 'primary', children = null, ...rest } = props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<NitroCardContextProvider value={ { theme, simple } }>
|
<NitroCardContextProvider value={ { theme, simple } }>
|
||||||
<div className="nitro-card-responsive">
|
<div className="nitro-card-responsive">
|
||||||
<DraggableWindow handle=".drag-handler" disableDrag= { disableDrag }>
|
<DraggableWindow { ...rest }>
|
||||||
<div className={ 'nitro-card d-flex flex-column rounded border shadow overflow-hidden ' + className }>
|
<div className={ 'nitro-card d-flex flex-column rounded border shadow overflow-hidden ' + className }>
|
||||||
{ children }
|
{ children }
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
export interface NitroCardViewProps
|
import { DraggableWindowProps } from '../draggable-window';
|
||||||
|
|
||||||
|
export interface NitroCardViewProps extends DraggableWindowProps
|
||||||
{
|
{
|
||||||
className?: string;
|
className?: string;
|
||||||
disableDrag?: boolean;
|
|
||||||
simple?: boolean;
|
simple?: boolean;
|
||||||
theme?: string;
|
theme?: string;
|
||||||
}
|
}
|
||||||
|
@ -1,19 +1,27 @@
|
|||||||
import { FC, MouseEvent, useCallback, useEffect, useMemo, useRef } from 'react';
|
import { MouseEventType } from '@nitrots/nitro-renderer';
|
||||||
import Draggable from 'react-draggable';
|
import { FC, Key, MouseEvent as ReactMouseEvent, useCallback, useEffect, useRef, useState } from 'react';
|
||||||
import { DraggableWindowProps } from './DraggableWindow.types';
|
import { DraggableWindowPosition, DraggableWindowProps } from './DraggableWindow.types';
|
||||||
|
|
||||||
const currentWindows: HTMLDivElement[] = [];
|
const CURRENT_WINDOWS: HTMLElement[] = [];
|
||||||
|
const POS_MEMORY: Map<Key, { x: number, y: number }> = new Map();
|
||||||
|
const BOUNDS_THRESHOLD_TOP: number = 0;
|
||||||
|
const BOUNDS_THRESHOLD_LEFT: number = 0;
|
||||||
|
|
||||||
export const DraggableWindow: FC<DraggableWindowProps> = props =>
|
export const DraggableWindow: FC<DraggableWindowProps> = props =>
|
||||||
{
|
{
|
||||||
const { disableDrag = false, noCenter = false, handle = '.drag-handler', draggableOptions = {}, children = null } = props;
|
const { uniqueKey = null, handleSelector = '.drag-handler', position = DraggableWindowPosition.CENTER, disableDrag = false, children = null } = props;
|
||||||
|
const [ delta, setDelta ] = useState<{ x: number, y: number }>(null);
|
||||||
|
const [ offset, setOffset ] = useState<{ x: number, y: number }>(null);
|
||||||
|
const [ start, setStart ] = useState<{ x: number, y: number }>({ x: 0, y: 0 });
|
||||||
|
const [ isDragging, setIsDragging ] = useState(false);
|
||||||
|
const [ dragHandler, setDragHandler ] = useState<HTMLElement>(null);
|
||||||
const elementRef = useRef<HTMLDivElement>();
|
const elementRef = useRef<HTMLDivElement>();
|
||||||
|
|
||||||
const bringToTop = useCallback(() =>
|
const bringToTop = useCallback(() =>
|
||||||
{
|
{
|
||||||
let zIndex = 400;
|
let zIndex = 400;
|
||||||
|
|
||||||
for(const existingWindow of currentWindows)
|
for(const existingWindow of CURRENT_WINDOWS)
|
||||||
{
|
{
|
||||||
zIndex += 1;
|
zIndex += 1;
|
||||||
|
|
||||||
@ -21,69 +29,170 @@ export const DraggableWindow: FC<DraggableWindowProps> = props =>
|
|||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const onMouseDown = useCallback((event: MouseEvent) =>
|
const onMouseDown = useCallback((event: ReactMouseEvent) =>
|
||||||
{
|
{
|
||||||
const index = currentWindows.indexOf(elementRef.current);
|
const index = CURRENT_WINDOWS.indexOf(elementRef.current);
|
||||||
|
|
||||||
if(index === -1)
|
if(index === -1)
|
||||||
{
|
{
|
||||||
currentWindows.push(elementRef.current);
|
CURRENT_WINDOWS.push(elementRef.current);
|
||||||
}
|
}
|
||||||
|
|
||||||
else if(index === (currentWindows.length - 1)) return;
|
else if(index === (CURRENT_WINDOWS.length - 1)) return;
|
||||||
|
|
||||||
else if(index >= 0)
|
else if(index >= 0)
|
||||||
{
|
{
|
||||||
currentWindows.splice(index, 1);
|
CURRENT_WINDOWS.splice(index, 1);
|
||||||
|
|
||||||
currentWindows.push(elementRef.current);
|
CURRENT_WINDOWS.push(elementRef.current);
|
||||||
}
|
}
|
||||||
|
|
||||||
bringToTop();
|
bringToTop();
|
||||||
}, [ bringToTop ]);
|
}, [ bringToTop ]);
|
||||||
|
|
||||||
|
const onDragMouseDown = useCallback((event: MouseEvent) =>
|
||||||
|
{
|
||||||
|
setStart({ x: event.clientX, y: event.clientY });
|
||||||
|
setIsDragging(true);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const onDragMouseMove = useCallback((event: MouseEvent) =>
|
||||||
|
{
|
||||||
|
setDelta({ x: (event.clientX - start.x), y: (event.clientY - start.y) });
|
||||||
|
}, [ start ]);
|
||||||
|
|
||||||
|
const onDragMouseUp = useCallback((event: MouseEvent) =>
|
||||||
|
{
|
||||||
|
if(!elementRef.current || !dragHandler) return;
|
||||||
|
|
||||||
|
let offsetX = (offset.x + delta.x);
|
||||||
|
let offsetY = (offset.y + delta.y);
|
||||||
|
|
||||||
|
const left = elementRef.current.offsetLeft + offsetX;
|
||||||
|
const top = elementRef.current.offsetTop + offsetY;
|
||||||
|
|
||||||
|
if(top < BOUNDS_THRESHOLD_TOP)
|
||||||
|
{
|
||||||
|
offsetY = -elementRef.current.offsetTop;
|
||||||
|
}
|
||||||
|
|
||||||
|
else if((top + dragHandler.offsetHeight) >= (document.body.offsetHeight - BOUNDS_THRESHOLD_TOP))
|
||||||
|
{
|
||||||
|
offsetY = (document.body.offsetHeight - elementRef.current.offsetHeight) - elementRef.current.offsetTop;
|
||||||
|
}
|
||||||
|
|
||||||
|
if((left + elementRef.current.offsetWidth) < BOUNDS_THRESHOLD_LEFT)
|
||||||
|
{
|
||||||
|
offsetX = -elementRef.current.offsetLeft;
|
||||||
|
}
|
||||||
|
|
||||||
|
else if(left >= (document.body.offsetWidth - BOUNDS_THRESHOLD_LEFT))
|
||||||
|
{
|
||||||
|
offsetX = (document.body.offsetWidth - elementRef.current.offsetWidth) - elementRef.current.offsetLeft;
|
||||||
|
}
|
||||||
|
|
||||||
|
setDelta({ x: 0, y: 0 });
|
||||||
|
setOffset({ x: offsetX, y: offsetY });
|
||||||
|
setIsDragging(false);
|
||||||
|
|
||||||
|
if(uniqueKey !== null) POS_MEMORY.set(uniqueKey, { x: offsetX, y: offsetY });
|
||||||
|
}, [ dragHandler, delta, offset, uniqueKey ]);
|
||||||
|
|
||||||
useEffect(() =>
|
useEffect(() =>
|
||||||
{
|
{
|
||||||
if(!elementRef) return;
|
const element = (elementRef.current as HTMLElement);
|
||||||
|
|
||||||
const element = elementRef.current;
|
|
||||||
|
|
||||||
currentWindows.push(element);
|
if(!element) return;
|
||||||
|
|
||||||
|
CURRENT_WINDOWS.push(element);
|
||||||
|
|
||||||
bringToTop();
|
bringToTop();
|
||||||
|
|
||||||
if(!noCenter)
|
if(!disableDrag)
|
||||||
{
|
{
|
||||||
const left = ((document.body.clientWidth / 2) - (element.clientWidth / 2));
|
const handle = (element.querySelector(handleSelector) as HTMLElement);
|
||||||
const top = ((document.body.clientHeight / 2) - (element.clientHeight / 2));
|
|
||||||
|
|
||||||
element.style.left = `${ left }px`;
|
if(handle) setDragHandler(handle);
|
||||||
element.style.top = `${ top }px`;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
element.style.left = `0px`;
|
|
||||||
element.style.top = `0px`;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
element.style.visibility = 'visible';
|
let offsetX = 0;
|
||||||
|
let offsetY = 0;
|
||||||
|
|
||||||
|
switch(position)
|
||||||
|
{
|
||||||
|
case DraggableWindowPosition.TOP_CENTER:
|
||||||
|
element.style.top = '50px';
|
||||||
|
element.style.left = `calc(50vw - ${ (element.offsetWidth / 2) }px)`;
|
||||||
|
break;
|
||||||
|
case DraggableWindowPosition.CENTER:
|
||||||
|
element.style.top = `calc(50vh - ${ (element.offsetHeight / 2) }px)`;
|
||||||
|
element.style.left = `calc(50vw - ${ (element.offsetWidth / 2) }px)`;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(uniqueKey !== null)
|
||||||
|
{
|
||||||
|
const memory = POS_MEMORY.get(uniqueKey);
|
||||||
|
|
||||||
|
if(memory)
|
||||||
|
{
|
||||||
|
offsetX = memory.x;
|
||||||
|
offsetY = memory.y;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setDelta({ x: 0, y: 0});
|
||||||
|
setOffset({ x: offsetX, y: offsetY });
|
||||||
|
|
||||||
return () =>
|
return () =>
|
||||||
{
|
{
|
||||||
const index = currentWindows.indexOf(element);
|
const index = CURRENT_WINDOWS.indexOf(element);
|
||||||
|
|
||||||
if(index >= 0) currentWindows.splice(index, 1);
|
if(index >= 0) CURRENT_WINDOWS.splice(index, 1);
|
||||||
}
|
}
|
||||||
}, [ elementRef, noCenter, bringToTop ]);
|
}, [ handleSelector, position, uniqueKey, disableDrag, bringToTop ]);
|
||||||
|
|
||||||
const getWindowContent = useMemo(() =>
|
useEffect(() =>
|
||||||
{
|
{
|
||||||
return (
|
if(!offset && !delta) return;
|
||||||
<div ref={ elementRef } className="position-absolute draggable-window" onMouseDownCapture={ onMouseDown }>
|
|
||||||
{ children }
|
const element = (elementRef.current as HTMLElement);
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}, [ children, onMouseDown ]);
|
|
||||||
|
|
||||||
return disableDrag ? getWindowContent : <Draggable handle={ handle } { ...draggableOptions }>{ getWindowContent }</Draggable>;
|
if(!element) return;
|
||||||
|
|
||||||
|
element.style.transform = `translate(${ offset.x + delta.x }px, ${ offset.y + delta.y }px)`;
|
||||||
|
element.style.visibility = 'visible';
|
||||||
|
}, [ offset, delta ]);
|
||||||
|
|
||||||
|
useEffect(() =>
|
||||||
|
{
|
||||||
|
if(!dragHandler) return;
|
||||||
|
|
||||||
|
dragHandler.addEventListener(MouseEventType.MOUSE_DOWN, onDragMouseDown);
|
||||||
|
|
||||||
|
return () =>
|
||||||
|
{
|
||||||
|
dragHandler.removeEventListener(MouseEventType.MOUSE_DOWN, onDragMouseDown);
|
||||||
|
}
|
||||||
|
}, [ dragHandler, onDragMouseDown ]);
|
||||||
|
|
||||||
|
useEffect(() =>
|
||||||
|
{
|
||||||
|
if(!isDragging) return;
|
||||||
|
|
||||||
|
document.addEventListener(MouseEventType.MOUSE_UP, onDragMouseUp);
|
||||||
|
document.addEventListener(MouseEventType.MOUSE_MOVE, onDragMouseMove);
|
||||||
|
|
||||||
|
return () =>
|
||||||
|
{
|
||||||
|
document.removeEventListener(MouseEventType.MOUSE_UP, onDragMouseUp);
|
||||||
|
document.removeEventListener(MouseEventType.MOUSE_MOVE, onDragMouseMove);
|
||||||
|
}
|
||||||
|
}, [ isDragging, onDragMouseUp, onDragMouseMove ]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div ref={ elementRef } className="position-absolute draggable-window" onMouseDownCapture={ onMouseDown }>
|
||||||
|
{ children }
|
||||||
|
</div>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,11 +1,16 @@
|
|||||||
import { ReactNode } from 'react';
|
import { Key } from 'react';
|
||||||
import { DraggableProps } from 'react-draggable';
|
|
||||||
|
|
||||||
export interface DraggableWindowProps
|
export interface DraggableWindowProps
|
||||||
{
|
{
|
||||||
handle?: string;
|
uniqueKey?: Key;
|
||||||
draggableOptions?: Partial<DraggableProps>;
|
handleSelector?: string;
|
||||||
|
position?: string;
|
||||||
disableDrag?: boolean;
|
disableDrag?: boolean;
|
||||||
noCenter?: boolean;
|
}
|
||||||
children?: ReactNode;
|
|
||||||
|
export class DraggableWindowPosition
|
||||||
|
{
|
||||||
|
public static CENTER: string = 'DWP_CENTER';
|
||||||
|
public static TOP_CENTER: string = 'DWP_TOP_CENTER';
|
||||||
|
public static NOTHING: string = 'DWP_NOTHING';
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user