first commit
Some checks failed
Build / run (push) Has been cancelled

This commit is contained in:
maher
2025-10-29 11:42:25 +01:00
commit 703f50a09d
4595 changed files with 385164 additions and 0 deletions

View File

@@ -0,0 +1,190 @@
import React, {RefObject, useRef} from 'react';
import {usePointerEvents} from '../../use-pointer-events';
import {InteractableRect} from '../../interactable-event';
import {restrictResizableWithinBoundary} from '../../utils/restrict-resizable-within-boundary';
import {activeInteraction} from '../../active-interaction';
import {updateRects} from '../update-rects';
import {mouseSelectables} from './use-mouse-selectable';
import {rectsIntersect} from '../../utils/rects-intersect';
import {DraggableId} from '../use-draggable';
interface SelectionState {
startPoint?: {x: number; y: number; scrollTop: number};
endPoint?: {x: number; y: number};
boundaryRect?: InteractableRect & {heightWithoutScroll: number};
scrollListener?: EventListener;
rafId?: number;
selectedIds?: Set<DraggableId>;
}
interface Props {
onPointerDown?: (e: React.PointerEvent) => void;
containerRef?: RefObject<HTMLDivElement>;
}
export function useMouseSelectionBox({onPointerDown, ...props}: Props = {}) {
const defaultRef = useRef<HTMLDivElement>(null);
const containerRef = props.containerRef || defaultRef;
const boxRef = useRef<HTMLDivElement>(null);
let state = useRef<SelectionState>({}).current;
const drawSelectionBox = () => {
if (state.rafId) {
cancelAnimationFrame(state.rafId);
}
if (!state.startPoint || !state.endPoint || !state.boundaryRect) return;
const startPoint = state.startPoint;
const endPoint = state.endPoint;
const initialScrollTop = startPoint.scrollTop || 0;
const currentScrollTop = containerRef.current?.scrollTop || 0;
const newRect = {
left: Math.min(startPoint.x, endPoint.x),
top: Math.min(startPoint.y, endPoint.y),
width: Math.abs(startPoint.x - endPoint.x),
height: Math.abs(startPoint.y - endPoint.y),
};
// convert box coords to be relative to container and not viewport
newRect.left -= state.boundaryRect.left;
newRect.top -= state.boundaryRect.top;
// take initial scroll of container into account
newRect.top += initialScrollTop;
// scroll diff between drag start and now (auto scroll or mouse wheel)
const scrollDiff = currentScrollTop - initialScrollTop;
const scrollValue = Math.abs(scrollDiff);
// top needs to be changed only if scroll direction is top
if (scrollDiff < 0) {
newRect.top -= scrollValue;
}
// height needs to be changed regardless of direction and method
newRect.height += scrollValue;
const boundedRect = state.boundaryRect
? restrictResizableWithinBoundary(newRect, state.boundaryRect)
: {...newRect};
if (boxRef.current) {
state.rafId = requestAnimationFrame(() => {
if (boxRef.current) {
boxRef.current.style.display = `block`;
boxRef.current.style.transform = `translate(${boundedRect.left}px, ${boundedRect.top}px)`;
boxRef.current.style.width = `${boundedRect.width}px`;
boxRef.current.style.height = `${boundedRect.height}px`;
}
state.rafId = undefined;
});
}
// convert rect back to absolute for intersection testing
const absoluteRect = {
...boundedRect,
left: boundedRect.left + state.boundaryRect.left,
top: boundedRect.top + state.boundaryRect.top - currentScrollTop,
};
for (const [, selectable] of mouseSelectables) {
const intersect = rectsIntersect(selectable.rect, absoluteRect);
if (intersect && !state.selectedIds?.has(selectable.id)) {
state.selectedIds?.add(selectable.id);
selectable.onSelected?.();
} else if (!intersect && state.selectedIds?.has(selectable.id)) {
state.selectedIds?.delete(selectable.id);
selectable.onDeselected?.();
}
}
};
const pointerEvents = usePointerEvents({
minimumMovement: 4,
onPointerDown,
onMoveStart: e => {
if (activeInteraction) {
return false;
}
updateRects(mouseSelectables);
state = {
selectedIds: new Set(),
};
const el = containerRef.current;
state.startPoint = {
x: e.clientX,
y: e.clientY,
scrollTop: el?.scrollTop || 0,
};
state.scrollListener = e => {
if (!state.startPoint) return;
// update rects on scroll, because we are using relative position
updateRects(mouseSelectables);
if (state.boundaryRect?.height) {
state.boundaryRect.height = (e.target as HTMLElement).scrollHeight;
}
// draw selection box (for autoscroll and mousewheel)
drawSelectionBox();
};
if (el) {
const rect = el.getBoundingClientRect();
el.addEventListener('scroll', state.scrollListener);
state.boundaryRect = {
top: rect.top,
left: rect.left,
height: el.scrollHeight,
heightWithoutScroll: rect.height,
width: el.scrollWidth,
};
}
},
onMove: e => {
state.endPoint = {x: e.clientX, y: e.clientY};
if (state.boundaryRect && containerRef.current) {
const reachedBottomEdge =
e.clientY + 20 >
state.boundaryRect.heightWithoutScroll + state.boundaryRect.top;
const reachedTopEdge = e.clientY - 20 < state.boundaryRect.top;
if (reachedBottomEdge) {
containerRef.current.scrollBy({top: 10});
} else if (reachedTopEdge) {
containerRef.current.scrollBy({top: -10});
}
}
drawSelectionBox();
},
onMoveEnd: () => {
if (state.rafId) {
cancelAnimationFrame(state.rafId);
}
if (containerRef.current && state.scrollListener) {
containerRef.current.removeEventListener(
'scroll',
state.scrollListener
);
}
if (boxRef.current) {
boxRef.current.style.display = `none`;
boxRef.current.style.transform = '';
boxRef.current.style.width = '';
boxRef.current.style.height = '';
}
state = {};
},
});
return {
containerProps: {
...pointerEvents.domProps,
ref: containerRef,
},
boxProps: {ref: boxRef},
};
}