goldenChat base source add
This commit is contained in:
@@ -0,0 +1,111 @@
|
||||
import { useState, useRef, useCallback, useEffect, type RefObject } from 'react';
|
||||
import { bindWindowDrag, clientXYFromReact, isPrimaryPointerButton } from '../utils/pointerDrag';
|
||||
|
||||
export interface DraggablePosition {
|
||||
x: number;
|
||||
y: number;
|
||||
}
|
||||
|
||||
export interface UseDraggablePanelOptions {
|
||||
/** 마운트 후 패널 크기 기준 화면 중앙 배치 */
|
||||
centerOnMount?: boolean;
|
||||
initialPosition?: DraggablePosition;
|
||||
panelRef?: RefObject<HTMLElement | null>;
|
||||
}
|
||||
|
||||
const DEFAULT_POS: DraggablePosition = { x: 80, y: 72 };
|
||||
|
||||
/**
|
||||
* 팝업 패널 타이틀바 드래그 이동 (마우스·터치·펜)
|
||||
*/
|
||||
export function useDraggablePanel(options: UseDraggablePanelOptions = {}) {
|
||||
const {
|
||||
centerOnMount = false,
|
||||
initialPosition = DEFAULT_POS,
|
||||
panelRef: externalRef,
|
||||
} = options;
|
||||
|
||||
const internalRef = useRef<HTMLDivElement>(null);
|
||||
const panelRef = (externalRef ?? internalRef) as RefObject<HTMLDivElement | null>;
|
||||
const [pos, setPos] = useState<DraggablePosition>(initialPosition);
|
||||
const [dragging, setDragging] = useState(false);
|
||||
const dragOrigin = useRef({ mx: 0, my: 0, px: 0, py: 0 });
|
||||
const centeredOnce = useRef(false);
|
||||
const unbindDragRef = useRef<(() => void) | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (!centerOnMount || centeredOnce.current) return;
|
||||
const el = panelRef.current;
|
||||
if (!el) return;
|
||||
|
||||
const placeCenter = () => {
|
||||
const w = el.offsetWidth || 400;
|
||||
const h = el.offsetHeight || 320;
|
||||
setPos({
|
||||
x: Math.max(8, (window.innerWidth - w) / 2),
|
||||
y: Math.max(8, (window.innerHeight - h) / 2),
|
||||
});
|
||||
centeredOnce.current = true;
|
||||
};
|
||||
|
||||
placeCenter();
|
||||
const id = requestAnimationFrame(placeCenter);
|
||||
return () => cancelAnimationFrame(id);
|
||||
}, [centerOnMount, panelRef]);
|
||||
|
||||
const endDrag = useCallback(() => {
|
||||
unbindDragRef.current?.();
|
||||
unbindDragRef.current = null;
|
||||
setDragging(false);
|
||||
}, []);
|
||||
|
||||
const onHeaderPointerDown = useCallback((e: React.PointerEvent) => {
|
||||
if (!isPrimaryPointerButton(e)) return;
|
||||
if ((e.target as HTMLElement).closest('button')) return;
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
const el = e.currentTarget as HTMLElement;
|
||||
try {
|
||||
el.setPointerCapture(e.pointerId);
|
||||
} catch {
|
||||
/* ignore */
|
||||
}
|
||||
const { x, y } = clientXYFromReact(e);
|
||||
setDragging(true);
|
||||
dragOrigin.current = { mx: x, my: y, px: pos.x, py: pos.y };
|
||||
|
||||
unbindDragRef.current?.();
|
||||
unbindDragRef.current = bindWindowDrag(
|
||||
({ x: cx, y: cy }) => {
|
||||
const o = dragOrigin.current;
|
||||
setPos({
|
||||
x: o.px + (cx - o.mx),
|
||||
y: o.py + (cy - o.my),
|
||||
});
|
||||
},
|
||||
endDrag,
|
||||
);
|
||||
}, [pos, endDrag]);
|
||||
|
||||
useEffect(() => () => {
|
||||
unbindDragRef.current?.();
|
||||
}, []);
|
||||
|
||||
const panelStyle: React.CSSProperties = {
|
||||
position: 'fixed',
|
||||
left: pos.x,
|
||||
top: pos.y,
|
||||
};
|
||||
|
||||
return {
|
||||
panelRef: internalRef,
|
||||
pos,
|
||||
dragging,
|
||||
/** @deprecated onHeaderPointerDown 사용 */
|
||||
onHeaderMouseDown: onHeaderPointerDown,
|
||||
onHeaderPointerDown,
|
||||
panelStyle,
|
||||
headerCursor: dragging ? 'grabbing' : 'grab',
|
||||
headerTouchStyle: { touchAction: 'none' as const },
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user