Better code splitting and removed final instances of react-dnd

This commit is contained in:
Gregory Schier
2025-10-19 08:16:56 -07:00
parent 8055b625d0
commit ba6163b6d8
32 changed files with 654 additions and 605 deletions

View File

@@ -1,63 +0,0 @@
// AutoScrollWhileDragging.tsx
import { useEffect, useRef } from 'react';
import { useDragLayer } from 'react-dnd';
type Props = {
container: HTMLElement | null | undefined;
edgeDistance?: number;
maxSpeedPerFrame?: number;
};
export function AutoScrollWhileDragging({
container,
edgeDistance = 30,
maxSpeedPerFrame = 6,
}: Props) {
const rafId = useRef<number | null>(null);
const { isDragging, pointer } = useDragLayer((monitor) => ({
isDragging: monitor.isDragging(),
pointer: monitor.getClientOffset(), // { x, y } | null
}));
useEffect(() => {
if (!container || !isDragging) {
if (rafId.current != null) cancelAnimationFrame(rafId.current);
rafId.current = null;
return;
}
const tick = () => {
if (!container || !isDragging || !pointer) return;
const rect = container.getBoundingClientRect();
const y = pointer.y;
// Compute vertical speed based on proximity to edges
let dy = 0;
if (y < rect.top + edgeDistance) {
const t = (rect.top + edgeDistance - y) / edgeDistance; // 0..1
dy = -Math.min(maxSpeedPerFrame, Math.ceil(t * maxSpeedPerFrame));
} else if (y > rect.bottom - edgeDistance) {
const t = (y - (rect.bottom - edgeDistance)) / edgeDistance; // 0..1
dy = Math.min(maxSpeedPerFrame, Math.ceil(t * maxSpeedPerFrame));
}
if (dy !== 0) {
// Only scroll if theres more content in that direction
const prev = container.scrollTop;
container.scrollTop = prev + dy;
}
rafId.current = requestAnimationFrame(tick);
};
rafId.current = requestAnimationFrame(tick);
return () => {
if (rafId.current != null) cancelAnimationFrame(rafId.current);
rafId.current = null;
};
}, [container, isDragging, pointer, edgeDistance, maxSpeedPerFrame]);
return null;
}

View File

@@ -14,6 +14,7 @@ import { forwardRef, memo, useCallback, useImperativeHandle, useMemo, useRef } f
import { useKey, useKeyPressEvent } from 'react-use';
import type { HotkeyAction, HotKeyOptions } from '../../../hooks/useHotKey';
import { useHotKey } from '../../../hooks/useHotKey';
import { computeSideForDragMove } from '../../../lib/dnd';
import { jotaiStore } from '../../../lib/jotai';
import type { ContextMenuProps } from '../Dropdown';
import {
@@ -24,7 +25,7 @@ import {
selectedIdsFamily,
} from './atoms';
import type { SelectableTreeNode, TreeNode } from './common';
import { computeSideForDragMove, equalSubtree, getSelectedItems, hasAncestor } from './common';
import { equalSubtree, getSelectedItems, hasAncestor } from './common';
import { TreeDragOverlay } from './TreeDragOverlay';
import type { TreeItemProps } from './TreeItem';
import type { TreeItemListProps } from './TreeItemList';
@@ -255,7 +256,7 @@ function TreeInner<T extends { id: string }>(
}
const node = selectableItem.node;
const side = computeSideForDragMove(node, e);
const side = computeSideForDragMove(node.item.id, e);
const item = node.item;
let hoveredParent = node.parent;

View File

@@ -5,13 +5,13 @@ import { useAtomValue } from 'jotai';
import { selectAtom } from 'jotai/utils';
import type { MouseEvent, PointerEvent } from 'react';
import React, { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { computeSideForDragMove } from '../../../lib/dnd';
import { jotaiStore } from '../../../lib/jotai';
import type { ContextMenuProps, DropdownItem } from '../Dropdown';
import { ContextMenu } from '../Dropdown';
import { Icon } from '../Icon';
import { collapsedFamily, isCollapsedFamily, isLastFocusedFamily, isSelectedFamily } from './atoms';
import type { TreeNode } from './common';
import { computeSideForDragMove } from './common';
import type { TreeProps } from './Tree';
import { TreeIndentGuide } from './TreeIndentGuide';
@@ -161,7 +161,7 @@ function TreeItem_<T extends { id: string }>({
clearDropHover();
},
onDragMove(e: DragMoveEvent) {
const side = computeSideForDragMove(node, e);
const side = computeSideForDragMove(node.item.id, e);
const isFolder = node.children != null;
const hasChildren = (node.children?.length ?? 0) > 0;
const isCollapsed = jotaiStore.get(isCollapsedFamily({ treeId, itemId: node.item.id }));

View File

@@ -1,8 +1,7 @@
import type { DragMoveEvent } from '@dnd-kit/core';
import { jotaiStore } from '../../../lib/jotai';
import { selectedIdsFamily } from './atoms';
export interface TreeNode<T extends { id: string } > {
export interface TreeNode<T extends { id: string }> {
children?: TreeNode<T>[];
item: T;
parent: TreeNode<T> | null;
@@ -48,25 +47,3 @@ export function hasAncestor<T extends { id: string }>(node: TreeNode<T>, ancesto
// Check parents recursively
return hasAncestor(node.parent, ancestorId);
}
export function computeSideForDragMove<T extends { id: string }>(
node: TreeNode<T>,
e: DragMoveEvent,
): 'above' | 'below' | null {
if (e.over == null || e.over.id !== node.item.id) {
return null;
}
if (e.active.rect.current.initial == null) return null;
const overRect = e.over.rect;
const activeTop =
e.active.rect.current.translated?.top ?? e.active.rect.current.initial.top + e.delta.y;
const pointerY = activeTop + e.active.rect.current.initial.height / 2;
const hoverTop = overRect.top;
const hoverBottom = overRect.bottom;
const hoverMiddleY = (hoverBottom - hoverTop) / 2;
const hoverClientY = pointerY - hoverTop;
return hoverClientY < hoverMiddleY ? 'above' : 'below';
}