🆙 Update for Draggable Window

Moving the touchstart listener to the native DOM element (elementRef.current) and explicitly setting { passive: true }
This commit is contained in:
DuckieTM 2025-03-08 13:21:13 +01:00
parent 30881bafc7
commit 187b1b39d3

View File

@ -10,8 +10,7 @@ const POS_MEMORY: Map<Key, { x: number, y: number }> = new Map();
const BOUNDS_THRESHOLD_TOP: number = 0;
const BOUNDS_THRESHOLD_LEFT: number = 0;
export interface DraggableWindowProps
{
export interface DraggableWindowProps {
uniqueKey?: Key;
handleSelector?: string;
windowPosition?: string;
@ -22,8 +21,7 @@ export interface DraggableWindowProps
children?: ReactNode;
}
export const DraggableWindow: FC<DraggableWindowProps> = props =>
{
export const DraggableWindow: FC<DraggableWindowProps> = props => {
const { uniqueKey = null, handleSelector = '.drag-handler', windowPosition = DraggableWindowPosition.CENTER, disableDrag = false, dragStyle = {}, children = null, offsetLeft = 0, offsetTop = 0 } = props;
const [ delta, setDelta ] = useState<{ x: number, y: number }>(null);
const [ offset, setOffset ] = useState<{ x: number, y: number }>(null);
@ -32,106 +30,73 @@ export const DraggableWindow: FC<DraggableWindowProps> = props =>
const [ dragHandler, setDragHandler ] = useState<HTMLElement>(null);
const elementRef = useRef<HTMLDivElement>();
const bringToTop = useCallback(() =>
{
const bringToTop = useCallback(() => {
let zIndex = 400;
for(const existingWindow of CURRENT_WINDOWS)
{
for (const existingWindow of CURRENT_WINDOWS) {
zIndex += 1;
existingWindow.style.zIndex = zIndex.toString();
}
}, []);
const moveCurrentWindow = useCallback(() =>
{
const moveCurrentWindow = useCallback(() => {
const index = CURRENT_WINDOWS.indexOf(elementRef.current);
if(index === -1)
{
if (index === -1) {
CURRENT_WINDOWS.push(elementRef.current);
}
else if(index === (CURRENT_WINDOWS.length - 1)) return;
else if(index >= 0)
{
} else if (index === (CURRENT_WINDOWS.length - 1)) return;
else if (index >= 0) {
CURRENT_WINDOWS.splice(index, 1);
CURRENT_WINDOWS.push(elementRef.current);
}
bringToTop();
}, [ bringToTop ]);
const onMouseDown = useCallback((event: ReactMouseEvent<HTMLDivElement>) =>
{
const onMouseDown = useCallback((event: ReactMouseEvent<HTMLDivElement>) => {
moveCurrentWindow();
}, [ moveCurrentWindow ]);
const onTouchStart = useCallback((event: ReactTouchEvent<HTMLDivElement>) =>
{
const onTouchDownHandler = useCallback((event: TouchEvent) => {
moveCurrentWindow();
}, [ moveCurrentWindow ]);
const startDragging = useCallback((startX: number, startY: number) =>
{
const startDragging = useCallback((startX: number, startY: number) => {
setStart({ x: startX, y: startY });
setIsDragging(true);
}, []);
const onDragMouseDown = useCallback((event: MouseEvent) =>
{
const onDragMouseDown = useCallback((event: MouseEvent) => {
startDragging(event.clientX, event.clientY);
}, [ startDragging ]);
const onTouchDown = useCallback((event: TouchEvent) =>
{
const onTouchDown = useCallback((event: TouchEvent) => {
const touch = event.touches[0];
startDragging(touch.clientX, touch.clientY);
}, [ startDragging ]);
const onDragMouseMove = useCallback((event: MouseEvent) =>
{
const onDragMouseMove = useCallback((event: MouseEvent) => {
setDelta({ x: (event.clientX - start.x), y: (event.clientY - start.y) });
}, [ start ]);
const onDragTouchMove = useCallback((event: TouchEvent) =>
{
const onDragTouchMove = useCallback((event: TouchEvent) => {
const touch = event.touches[0];
setDelta({ x: (touch.clientX - start.x), y: (touch.clientY - start.y) });
}, [ start ]);
const completeDrag = useCallback(() =>
{
if(!elementRef.current || !dragHandler) return;
const completeDrag = useCallback(() => {
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)
{
if (top < BOUNDS_THRESHOLD_TOP) {
offsetY = -elementRef.current.offsetTop;
}
else if((top + dragHandler.offsetHeight) >= (document.body.offsetHeight - BOUNDS_THRESHOLD_TOP))
{
} 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)
{
if ((left + elementRef.current.offsetWidth) < BOUNDS_THRESHOLD_LEFT) {
offsetX = -elementRef.current.offsetLeft;
}
else if(left >= (document.body.offsetWidth - BOUNDS_THRESHOLD_LEFT))
{
} else if (left >= (document.body.offsetWidth - BOUNDS_THRESHOLD_LEFT)) {
offsetX = (document.body.offsetWidth - elementRef.current.offsetWidth) - elementRef.current.offsetLeft;
}
@ -139,42 +104,33 @@ export const DraggableWindow: FC<DraggableWindowProps> = props =>
setOffset({ x: offsetX, y: offsetY });
setIsDragging(false);
if(uniqueKey !== null) POS_MEMORY.set(uniqueKey, {
x: offsetX, y: offsetY });
}, [ dragHandler, delta, offset, uniqueKey ]);
if (uniqueKey !== null) POS_MEMORY.set(uniqueKey, { x: offsetX, y: offsetY });
}, [ dragHandler, delta, offset, uniqueKey ]);
const onDragMouseUp = useCallback((event: MouseEvent) =>
{
const onDragMouseUp = useCallback((event: MouseEvent) => {
completeDrag();
}, [ completeDrag ]);
const onDragTouchUp = useCallback((event: TouchEvent) =>
{
const onDragTouchUp = useCallback((event: TouchEvent) => {
completeDrag();
}, [ completeDrag ]);
useEffect(() =>
{
const element = (elementRef.current as HTMLElement);
if(!element) return;
useEffect(() => {
const element = elementRef.current;
if (!element) return;
CURRENT_WINDOWS.push(element);
bringToTop();
if(!disableDrag)
{
const handle = (element.querySelector(handleSelector) as HTMLElement);
if(handle) setDragHandler(handle);
if (!disableDrag) {
const handle = element.querySelector(handleSelector) as HTMLElement;
if (handle) setDragHandler(handle);
}
let offsetX = 0;
let offsetY = 0;
switch(windowPosition)
{
switch (windowPosition) {
case DraggableWindowPosition.TOP_CENTER:
element.style.top = 50 + offsetTop + 'px';
element.style.left = `calc(50vw - ${ (element.offsetWidth / 2 + offsetLeft) }px)`;
@ -188,13 +144,10 @@ export const DraggableWindow: FC<DraggableWindowProps> = props =>
element.style.left = 50 + offsetLeft + 'px';
break;
}
if(uniqueKey !== null)
{
const memory = POS_MEMORY.get(uniqueKey);
if(memory)
{
if (uniqueKey !== null) {
const memory = POS_MEMORY.get(uniqueKey);
if (memory) {
offsetX = memory.x;
offsetY = memory.y;
}
@ -203,74 +156,77 @@ export const DraggableWindow: FC<DraggableWindowProps> = props =>
setDelta({ x: 0, y: 0 });
setOffset({ x: offsetX, y: offsetY });
return () =>
{
return () => {
const index = CURRENT_WINDOWS.indexOf(element);
if(index >= 0) CURRENT_WINDOWS.splice(index, 1);
}
if (index >= 0) CURRENT_WINDOWS.splice(index, 1);
};
}, [ handleSelector, windowPosition, uniqueKey, disableDrag, offsetLeft, offsetTop, bringToTop ]);
useEffect(() =>
{
if(!offset && !delta) return;
const element = (elementRef.current as HTMLElement);
if(!element) return;
useEffect(() => {
if (!offset && !delta) return;
const element = elementRef.current;
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;
useEffect(() => {
if (!dragHandler) return;
dragHandler.addEventListener(MouseEventType.MOUSE_DOWN, onDragMouseDown);
dragHandler.addEventListener(TouchEventType.TOUCH_START, onTouchDown);
dragHandler.addEventListener(TouchEventType.TOUCH_START, onTouchDown, { passive: true });
return () =>
{
return () => {
dragHandler.removeEventListener(MouseEventType.MOUSE_DOWN, onDragMouseDown);
dragHandler.removeEventListener(TouchEventType.TOUCH_START, onTouchDown);
}
};
}, [ dragHandler, onDragMouseDown, onTouchDown ]);
useEffect(() =>
{
if(!isDragging) return;
useEffect(() => {
if (!isDragging) return;
document.addEventListener(MouseEventType.MOUSE_UP, onDragMouseUp);
document.addEventListener(TouchEventType.TOUCH_END, onDragTouchUp);
document.addEventListener(MouseEventType.MOUSE_MOVE, onDragMouseMove);
document.addEventListener(TouchEventType.TOUCH_MOVE, onDragTouchMove);
return () =>
{
return () => {
document.removeEventListener(MouseEventType.MOUSE_UP, onDragMouseUp);
document.removeEventListener(TouchEventType.TOUCH_END, onDragTouchUp);
document.removeEventListener(MouseEventType.MOUSE_MOVE, onDragMouseMove);
document.removeEventListener(TouchEventType.TOUCH_MOVE, onDragTouchMove);
}
};
}, [ isDragging, onDragMouseUp, onDragMouseMove, onDragTouchUp, onDragTouchMove ]);
useEffect(() =>
{
if(!uniqueKey) return;
useEffect(() => {
const element = elementRef.current;
if (!element) return;
element.addEventListener('touchstart', onTouchDownHandler, { passive: true });
return () => {
element.removeEventListener('touchstart', onTouchDownHandler);
};
}, [ onTouchDownHandler ]);
useEffect(() => {
if (!uniqueKey) return;
const localStorage = GetLocalStorage<WindowSaveOptions>(`nitro.windows.${ uniqueKey }`);
if(!localStorage || !localStorage.offset) return;
if (!localStorage || !localStorage.offset) return;
setDelta({ x: 0, y: 0 });
if(localStorage.offset) setOffset(localStorage.offset);
if (localStorage.offset) setOffset(localStorage.offset);
}, [ uniqueKey ]);
return (
createPortal(
<Base position="absolute" innerRef={ elementRef } className="draggable-window" onMouseDownCapture={ onMouseDown } onTouchStartCapture={ onTouchStart } style={ dragStyle }>
{ children }
</Base>, document.getElementById('draggable-windows-container'))
<Base
position="absolute"
innerRef={elementRef}
className="draggable-window"
onMouseDownCapture={onMouseDown}
style={dragStyle}
>
{children}
</Base>,
document.getElementById('draggable-windows-container')
)
);
}
};