summaryrefslogtreecommitdiff
path: root/apps/web/src/selection/hooks
diff options
context:
space:
mode:
authorMaze Winther <mazewinther@gmail.com>2026-04-29 00:37:56 +0200
committerMaze Winther <mazewinther@gmail.com>2026-05-02 03:27:23 +0200
commita9b9cf6bf50f08723896659634f2e6afc17bcd42 (patch)
tree9175b49ff21f4326872ac9e541d2765cc7994ebc /apps/web/src/selection/hooks
parentd6622dc6b3523a5658cd512b44f15f7a66fd540c (diff)
downloadopencut-a9b9cf6bf50f08723896659634f2e6afc17bcd42.tar.gz
chore: switch from biome to eslint + prettier; fix ton of lint issues
Diffstat (limited to 'apps/web/src/selection/hooks')
-rw-r--r--apps/web/src/selection/hooks/use-box-select.ts85
-rw-r--r--apps/web/src/selection/hooks/use-selection-scope.ts19
2 files changed, 74 insertions, 30 deletions
diff --git a/apps/web/src/selection/hooks/use-box-select.ts b/apps/web/src/selection/hooks/use-box-select.ts
index 90a584b5..3bbff14e 100644
--- a/apps/web/src/selection/hooks/use-box-select.ts
+++ b/apps/web/src/selection/hooks/use-box-select.ts
@@ -4,15 +4,40 @@ import { useCallback, useEffect, useRef, useState } from "react";
import type {
BoxSelectionSnapshot,
ResolveIntersections,
+ SelectionBoxBounds,
} from "@/selection/types";
interface SelectionBoxState<TId> extends BoxSelectionSnapshot<TId> {
startPos: { x: number; y: number };
currentPos: { x: number; y: number };
+ bounds: SelectionBoxBounds | null;
isActive: boolean;
isAdditive: boolean;
}
+function getSelectionBoxBounds({
+ container,
+ startPos,
+ currentPos,
+}: {
+ container: HTMLElement;
+ startPos: { x: number; y: number };
+ currentPos: { x: number; y: number };
+}): SelectionBoxBounds {
+ const containerRect = container.getBoundingClientRect();
+ const startX = startPos.x - containerRect.left;
+ const startY = startPos.y - containerRect.top;
+ const currentX = currentPos.x - containerRect.left;
+ const currentY = currentPos.y - containerRect.top;
+
+ return {
+ left: Math.min(startX, currentX),
+ top: Math.min(startY, currentY),
+ width: Math.abs(currentX - startX),
+ height: Math.abs(currentY - startY),
+ };
+}
+
export function useBoxSelect<TId>({
containerRef,
resolveIntersections,
@@ -40,33 +65,43 @@ export function useBoxSelect<TId>({
const [selectionBox, setSelectionBox] =
useState<SelectionBoxState<TId> | null>(null);
const justFinishedSelectingRef = useRef(false);
- const shouldStartSelectionCheck = shouldStartSelection ?? (() => true);
- const getIsAdditiveSelectionCheck =
- getIsAdditiveSelection ??
- ((event: React.MouseEvent<Element>) => event.ctrlKey || event.metaKey);
const handleMouseDown = useCallback(
(event: React.MouseEvent<Element>) => {
- const canStartSelection = shouldStartSelectionCheck(event);
+ const canStartSelection = shouldStartSelection
+ ? shouldStartSelection(event)
+ : true;
if (!isEnabled || event.button !== 0 || !canStartSelection) {
return;
}
+ const startPos = { x: event.clientX, y: event.clientY };
+ const container = containerRef.current;
setSelectionBox({
- startPos: { x: event.clientX, y: event.clientY },
- currentPos: { x: event.clientX, y: event.clientY },
+ startPos,
+ currentPos: startPos,
+ bounds: container
+ ? getSelectionBoxBounds({
+ container,
+ startPos,
+ currentPos: startPos,
+ })
+ : null,
isActive: false,
- isAdditive: getIsAdditiveSelectionCheck(event),
+ isAdditive: getIsAdditiveSelection
+ ? getIsAdditiveSelection(event)
+ : event.ctrlKey || event.metaKey,
initialSelectedIds: selectedIds,
initialAnchorId: anchorId,
});
},
[
anchorId,
- getIsAdditiveSelectionCheck,
+ containerRef,
+ getIsAdditiveSelection,
isEnabled,
selectedIds,
- shouldStartSelectionCheck,
+ shouldStartSelection,
],
);
@@ -98,11 +133,20 @@ export function useBoxSelect<TId>({
}
const handleMouseMove = ({ clientX, clientY }: MouseEvent) => {
+ const currentPos = { x: clientX, y: clientY };
const deltaX = Math.abs(clientX - selectionBox.startPos.x);
const deltaY = Math.abs(clientY - selectionBox.startPos.y);
+ const container = containerRef.current;
const nextSelectionBox = {
...selectionBox,
- currentPos: { x: clientX, y: clientY },
+ currentPos,
+ bounds: container
+ ? getSelectionBoxBounds({
+ container,
+ startPos: selectionBox.startPos,
+ currentPos,
+ })
+ : null,
isActive: deltaX > 5 || deltaY > 5 || selectionBox.isActive,
};
@@ -133,26 +177,26 @@ export function useBoxSelect<TId>({
window.removeEventListener("mousemove", handleMouseMove);
window.removeEventListener("mouseup", handleMouseUp);
};
- }, [selectionBox, updateSelection]);
+ }, [containerRef, selectionBox, updateSelection]);
useEffect(() => {
if (!selectionBox) {
return;
}
+ const container = containerRef.current;
const previousBodyUserSelect = document.body.style.userSelect;
- const previousContainerUserSelect =
- containerRef.current?.style.userSelect ?? "";
+ const previousContainerUserSelect = container?.style.userSelect ?? "";
document.body.style.userSelect = "none";
- if (containerRef.current) {
- containerRef.current.style.userSelect = "none";
+ if (container) {
+ container.style.userSelect = "none";
}
return () => {
document.body.style.userSelect = previousBodyUserSelect;
- if (containerRef.current) {
- containerRef.current.style.userSelect = previousContainerUserSelect;
+ if (container) {
+ container.style.userSelect = previousContainerUserSelect;
}
};
}, [containerRef, selectionBox]);
@@ -162,7 +206,10 @@ export function useBoxSelect<TId>({
}, []);
return {
- selectionBox,
+ selectionBox:
+ selectionBox?.isActive && selectionBox.bounds
+ ? { bounds: selectionBox.bounds }
+ : null,
handleMouseDown,
isSelecting: selectionBox?.isActive ?? false,
shouldIgnoreClick,
diff --git a/apps/web/src/selection/hooks/use-selection-scope.ts b/apps/web/src/selection/hooks/use-selection-scope.ts
index ad4d2ce8..7fddab08 100644
--- a/apps/web/src/selection/hooks/use-selection-scope.ts
+++ b/apps/web/src/selection/hooks/use-selection-scope.ts
@@ -1,28 +1,25 @@
-import { useEffect, useRef } from "react";
+import { useEffect, useState } from "react";
+import { useCommittedRef } from "@/hooks/use-committed-ref";
import { useSelectionContext } from "@/selection/context";
import { activateScope, type ScopeEntry } from "@/selection/scope";
export function useSelectionScope() {
const { selectedIds, clearSelection } = useSelectionContext();
- const hasSelectionRef = useRef(selectedIds.length > 0);
- const clearSelectionRef = useRef(clearSelection);
const hasSelection = selectedIds.length > 0;
-
- hasSelectionRef.current = hasSelection;
- clearSelectionRef.current = clearSelection;
-
- const entryRef = useRef<ScopeEntry>({
+ const hasSelectionRef = useCommittedRef(hasSelection);
+ const clearSelectionRef = useCommittedRef(clearSelection);
+ const [entry] = useState<ScopeEntry>(() => ({
hasSelection: () => hasSelectionRef.current,
clear: () => {
clearSelectionRef.current();
},
- });
+ }));
useEffect(() => {
if (!hasSelection) {
return;
}
- return activateScope({ entry: entryRef.current });
- }, [hasSelection]);
+ return activateScope({ entry });
+ }, [entry, hasSelection]);
}