위젯 기능 추가
This commit is contained in:
@@ -0,0 +1,124 @@
|
||||
import { useCallback, useEffect, useRef, type RefObject } from 'react';
|
||||
import {
|
||||
bindWindowDrag,
|
||||
isPrimaryPointerButton,
|
||||
} from '../utils/pointerDrag';
|
||||
|
||||
export interface Size2d {
|
||||
width: number;
|
||||
height: number;
|
||||
}
|
||||
|
||||
export interface Position2d {
|
||||
x: number;
|
||||
y: number;
|
||||
}
|
||||
|
||||
export interface FloatingWidgetResizeResult {
|
||||
width: number;
|
||||
height: number;
|
||||
x?: number;
|
||||
}
|
||||
|
||||
export type FloatingWidgetResizeCorner = 'se' | 'sw';
|
||||
|
||||
function clampSize(
|
||||
width: number,
|
||||
height: number,
|
||||
minSize: Size2d,
|
||||
maxW: number,
|
||||
maxH: number,
|
||||
): Size2d {
|
||||
return {
|
||||
width: Math.round(Math.max(minSize.width, Math.min(maxW, width))),
|
||||
height: Math.round(Math.max(minSize.height, Math.min(maxH, height))),
|
||||
};
|
||||
}
|
||||
|
||||
export function useFloatingWidgetResize(
|
||||
panelRef: RefObject<HTMLElement | null>,
|
||||
size: Size2d,
|
||||
minSize: Size2d,
|
||||
onResize: (result: FloatingWidgetResizeResult) => void,
|
||||
) {
|
||||
const unbindDragRef = useRef<(() => void) | null>(null);
|
||||
|
||||
const endResize = useCallback(() => {
|
||||
unbindDragRef.current?.();
|
||||
unbindDragRef.current = null;
|
||||
}, []);
|
||||
|
||||
useEffect(() => () => {
|
||||
unbindDragRef.current?.();
|
||||
}, []);
|
||||
|
||||
const startResize = useCallback((
|
||||
e: React.PointerEvent,
|
||||
corner: FloatingWidgetResizeCorner,
|
||||
) => {
|
||||
if (!isPrimaryPointerButton(e)) return;
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
const handle = e.currentTarget as HTMLElement;
|
||||
try {
|
||||
handle.setPointerCapture(e.pointerId);
|
||||
} catch {
|
||||
/* ignore */
|
||||
}
|
||||
|
||||
const el = panelRef.current;
|
||||
if (!el) return;
|
||||
|
||||
const rect = el.getBoundingClientRect();
|
||||
const startX = e.clientX;
|
||||
const startY = e.clientY;
|
||||
const startW = size.width;
|
||||
const startH = size.height;
|
||||
const startRight = rect.right;
|
||||
const maxH = window.innerHeight - rect.top - 8;
|
||||
|
||||
unbindDragRef.current?.();
|
||||
unbindDragRef.current = bindWindowDrag(
|
||||
(xy) => {
|
||||
if (corner === 'se') {
|
||||
const maxW = window.innerWidth - rect.left - 8;
|
||||
const next = clampSize(
|
||||
startW + xy.x - startX,
|
||||
startH + xy.y - startY,
|
||||
minSize,
|
||||
maxW,
|
||||
maxH,
|
||||
);
|
||||
onResize(next);
|
||||
return;
|
||||
}
|
||||
|
||||
/* sw — 우측 가장자리 고정, 좌측·하단 조절 */
|
||||
let width = startRight - xy.x;
|
||||
let x = xy.x;
|
||||
if (x < 8) {
|
||||
x = 8;
|
||||
width = startRight - 8;
|
||||
}
|
||||
const maxW = startRight - 8;
|
||||
const next = clampSize(width, startH + xy.y - startY, minSize, maxW, maxH);
|
||||
onResize({ ...next, x: Math.round(startRight - next.width) });
|
||||
},
|
||||
endResize,
|
||||
{ preventTouchScroll: true },
|
||||
);
|
||||
}, [panelRef, size.width, size.height, minSize, onResize, endResize]);
|
||||
|
||||
const onResizeSePointerDown = useCallback(
|
||||
(e: React.PointerEvent) => startResize(e, 'se'),
|
||||
[startResize],
|
||||
);
|
||||
|
||||
const onResizeSwPointerDown = useCallback(
|
||||
(e: React.PointerEvent) => startResize(e, 'sw'),
|
||||
[startResize],
|
||||
);
|
||||
|
||||
return { onResizeSePointerDown, onResizeSwPointerDown };
|
||||
}
|
||||
Reference in New Issue
Block a user