From a9b9cf6bf50f08723896659634f2e6afc17bcd42 Mon Sep 17 00:00:00 2001 From: Maze Winther Date: Wed, 29 Apr 2026 00:37:56 +0200 Subject: chore: switch from biome to eslint + prettier; fix ton of lint issues --- apps/web/src/selection/hooks/use-box-select.ts | 85 +++++++++++++++++----- .../web/src/selection/hooks/use-selection-scope.ts | 19 ++--- apps/web/src/selection/selectable-item.tsx | 10 ++- apps/web/src/selection/selectable-surface.tsx | 26 +++---- apps/web/src/selection/selection-box.tsx | 47 +++--------- apps/web/src/selection/types.ts | 7 ++ 6 files changed, 110 insertions(+), 84 deletions(-) (limited to 'apps/web/src/selection') 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 extends BoxSelectionSnapshot { 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({ containerRef, resolveIntersections, @@ -40,33 +65,43 @@ export function useBoxSelect({ const [selectionBox, setSelectionBox] = useState | null>(null); const justFinishedSelectingRef = useRef(false); - const shouldStartSelectionCheck = shouldStartSelection ?? (() => true); - const getIsAdditiveSelectionCheck = - getIsAdditiveSelection ?? - ((event: React.MouseEvent) => event.ctrlKey || event.metaKey); const handleMouseDown = useCallback( (event: React.MouseEvent) => { - 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({ } 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({ 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({ }, []); 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({ + const hasSelectionRef = useCommittedRef(hasSelection); + const clearSelectionRef = useCommittedRef(clearSelection); + const [entry] = useState(() => ({ hasSelection: () => hasSelectionRef.current, clear: () => { clearSelectionRef.current(); }, - }); + })); useEffect(() => { if (!hasSelection) { return; } - return activateScope({ entry: entryRef.current }); - }, [hasSelection]); + return activateScope({ entry }); + }, [entry, hasSelection]); } diff --git a/apps/web/src/selection/selectable-item.tsx b/apps/web/src/selection/selectable-item.tsx index cc9e1dbf..9735c4a8 100644 --- a/apps/web/src/selection/selectable-item.tsx +++ b/apps/web/src/selection/selectable-item.tsx @@ -6,7 +6,13 @@ import { SELECTABLE_ITEM_ATTRIBUTE } from "@/selection/attributes"; import type { SelectableItemProps } from "@/selection/types"; import { cn } from "@/utils/ui"; -function setForwardedRef(ref: React.ForwardedRef, value: T | null) { +function setForwardedRef({ + ref, + value, +}: { + ref: React.ForwardedRef; + value: T | null; +}) { if (typeof ref === "function") { ref(value); return; @@ -55,7 +61,7 @@ export const SelectableItem = forwardRef( const handleRef = useCallback( (element: HTMLDivElement | null) => { registerItem(id, element); - setForwardedRef(forwardedRef, element); + setForwardedRef({ ref: forwardedRef, value: element }); }, [forwardedRef, id, registerItem], ); diff --git a/apps/web/src/selection/selectable-surface.tsx b/apps/web/src/selection/selectable-surface.tsx index 24166daf..12682ba8 100644 --- a/apps/web/src/selection/selectable-surface.tsx +++ b/apps/web/src/selection/selectable-surface.tsx @@ -167,14 +167,15 @@ export function SelectableSurface({ [], ); - const { selectionBox, handleMouseDown, shouldIgnoreClick } = useBoxSelect({ - containerRef, - resolveIntersections, - selectedIds: selectionState.selectedIds, - anchorId: selectionState.anchorId, - onSelectionChange: handleBoxSelectionChange, - shouldStartSelection, - }); + const { selectionBox, handleMouseDown, isSelecting, shouldIgnoreClick } = + useBoxSelect({ + containerRef, + resolveIntersections, + selectedIds: selectionState.selectedIds, + anchorId: selectionState.anchorId, + onSelectionChange: handleBoxSelectionChange, + shouldStartSelection, + }); const handleBackgroundClick = useCallback( (event: React.MouseEvent) => { @@ -236,7 +237,7 @@ export function SelectableSurface({ return () => clearTimeout(timer); }, [getItemElement, onRevealComplete, revealId]); - const isBoxSelecting = selectionBox?.isActive ?? false; + const isBoxSelecting = isSelecting; const contextValue = useMemo(() => { return { @@ -276,12 +277,7 @@ export function SelectableSurface({ onKeyDown={handleBackgroundKeyDown} > {children} - + ); diff --git a/apps/web/src/selection/selection-box.tsx b/apps/web/src/selection/selection-box.tsx index e30f574d..2f2bf933 100644 --- a/apps/web/src/selection/selection-box.tsx +++ b/apps/web/src/selection/selection-box.tsx @@ -1,49 +1,22 @@ "use client"; -import { useMemo } from "react"; +import type { SelectionBoxBounds } from "@/selection/types"; interface SelectionBoxProps { - startPos: { x: number; y: number } | null; - currentPos: { x: number; y: number } | null; - containerRef: React.RefObject; - isActive: boolean; + bounds: SelectionBoxBounds | null; } -export function SelectionBox({ - startPos, - currentPos, - containerRef, - isActive, -}: SelectionBoxProps) { - const selectionBoxStyle = useMemo(() => { - if (!isActive || !startPos || !currentPos || !containerRef.current) { - return null; - } - - const containerRect = containerRef.current.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; - - const left = Math.min(startX, currentX); - const top = Math.min(startY, currentY); - const width = Math.abs(currentX - startX); - const height = Math.abs(currentY - startY); - - return { - left: `${left}px`, - top: `${top}px`, - width: `${width}px`, - height: `${height}px`, - }; - }, [containerRef, currentPos, isActive, startPos]); - - if (!selectionBoxStyle) return null; +export function SelectionBox({ bounds }: SelectionBoxProps) { + if (!bounds) return null; return (
); diff --git a/apps/web/src/selection/types.ts b/apps/web/src/selection/types.ts index 31520788..a797d6c0 100644 --- a/apps/web/src/selection/types.ts +++ b/apps/web/src/selection/types.ts @@ -8,6 +8,13 @@ export interface BoxSelectionSnapshot { initialAnchorId: TId | null; } +export interface SelectionBoxBounds { + left: number; + top: number; + width: number; + height: number; +} + export interface BoxSelectionChange extends BoxSelectionSnapshot { intersectedIds: TId[]; -- cgit v1.3.1