75293bd1a4
Android E2E (CDP) / android-e2e (pull_request) Has been cancelled
CodeQL Advanced / Analyze (actions) (pull_request) Has been cancelled
CodeQL Advanced / Analyze (javascript-typescript) (pull_request) Has been cancelled
CodeQL Advanced / Analyze (rust) (pull_request) Has been cancelled
PR checks / rust_lint (pull_request) Has been cancelled
PR checks / build_web_app (pull_request) Has been cancelled
PR checks / test_web_app (1) (pull_request) Has been cancelled
PR checks / test_web_app (2) (pull_request) Has been cancelled
PR checks / test_extensions (pull_request) Has been cancelled
PR checks / build_tauri_app (pull_request) Has been cancelled
125 lines
4.0 KiB
TypeScript
125 lines
4.0 KiB
TypeScript
import { useCallback, useEffect, useRef, useState } from 'react';
|
|
import type { PointerEvent as ReactPointerEvent, RefObject } from 'react';
|
|
|
|
type Position = {
|
|
x: number;
|
|
y: number;
|
|
};
|
|
|
|
type UseReviewPanelDragOptions = {
|
|
enabled: boolean;
|
|
panelRef: RefObject<HTMLElement | null>;
|
|
margin: number;
|
|
defaultWidthRatio: number;
|
|
topInset: number;
|
|
};
|
|
|
|
const pointerTargetIsInteractive = (target: EventTarget | null) =>
|
|
target instanceof HTMLElement &&
|
|
Boolean(target.closest('button, input, textarea, select, a, .drag-bar'));
|
|
|
|
export const useReviewPanelDrag = ({
|
|
enabled,
|
|
panelRef,
|
|
margin,
|
|
defaultWidthRatio,
|
|
topInset,
|
|
}: UseReviewPanelDragOptions) => {
|
|
const initialPosition = () => ({
|
|
x:
|
|
typeof globalThis.window === 'undefined'
|
|
? margin
|
|
: Math.max(
|
|
margin,
|
|
globalThis.window.innerWidth -
|
|
Math.round(globalThis.window.innerWidth * defaultWidthRatio) -
|
|
margin,
|
|
),
|
|
y: typeof globalThis.window === 'undefined' ? margin : topInset + margin,
|
|
});
|
|
const panelPositionRef = useRef<Position>(initialPosition());
|
|
const [panelPosition, setPanelPosition] = useState(panelPositionRef.current);
|
|
|
|
const clampPanelPosition = useCallback(
|
|
(next: Position) => {
|
|
const panel = panelRef.current;
|
|
const width = panel?.offsetWidth || Math.round(window.innerWidth * defaultWidthRatio);
|
|
const height = panel?.offsetHeight || Math.round(window.innerHeight * 0.82);
|
|
return {
|
|
x: Math.min(Math.max(margin, next.x), Math.max(margin, window.innerWidth - width - margin)),
|
|
y: Math.min(
|
|
Math.max(margin, next.y),
|
|
Math.max(margin, window.innerHeight - height - margin),
|
|
),
|
|
};
|
|
},
|
|
[defaultWidthRatio, margin, panelRef],
|
|
);
|
|
|
|
const updatePanelPosition = useCallback(
|
|
(next: Position) => {
|
|
const clamped = clampPanelPosition(next);
|
|
panelPositionRef.current = clamped;
|
|
setPanelPosition(clamped);
|
|
},
|
|
[clampPanelPosition],
|
|
);
|
|
|
|
const moveToDefaultPosition = useCallback(() => {
|
|
if (!enabled) return;
|
|
const panel = panelRef.current;
|
|
const width = panel?.offsetWidth || Math.round(window.innerWidth * defaultWidthRatio);
|
|
updatePanelPosition({
|
|
x: window.innerWidth - width - margin,
|
|
y: topInset + margin,
|
|
});
|
|
}, [defaultWidthRatio, enabled, margin, panelRef, topInset, updatePanelPosition]);
|
|
|
|
const handlePanelDragStart = useCallback(
|
|
(event: ReactPointerEvent<HTMLElement>) => {
|
|
if (!enabled || pointerTargetIsInteractive(event.target)) return;
|
|
event.preventDefault();
|
|
const startX = event.clientX;
|
|
const startY = event.clientY;
|
|
const startPosition = panelPositionRef.current;
|
|
document.body.style.userSelect = 'none';
|
|
document.documentElement.style.cursor = 'move';
|
|
|
|
const handleMove = (moveEvent: PointerEvent) => {
|
|
updatePanelPosition({
|
|
x: startPosition.x + moveEvent.clientX - startX,
|
|
y: startPosition.y + moveEvent.clientY - startY,
|
|
});
|
|
};
|
|
|
|
const handleEnd = () => {
|
|
document.body.style.userSelect = '';
|
|
document.documentElement.style.cursor = '';
|
|
window.removeEventListener('pointermove', handleMove);
|
|
window.removeEventListener('pointerup', handleEnd);
|
|
window.removeEventListener('pointercancel', handleEnd);
|
|
};
|
|
|
|
window.addEventListener('pointermove', handleMove);
|
|
window.addEventListener('pointerup', handleEnd);
|
|
window.addEventListener('pointercancel', handleEnd);
|
|
},
|
|
[enabled, updatePanelPosition],
|
|
);
|
|
|
|
useEffect(() => {
|
|
if (!enabled) return;
|
|
const handleResize = () => updatePanelPosition(panelPositionRef.current);
|
|
window.addEventListener('resize', handleResize);
|
|
return () => window.removeEventListener('resize', handleResize);
|
|
}, [enabled, updatePanelPosition]);
|
|
|
|
return {
|
|
panelPosition,
|
|
panelPositionRef,
|
|
updatePanelPosition,
|
|
moveToDefaultPosition,
|
|
handlePanelDragStart,
|
|
};
|
|
};
|