({
tasks = [],
projects = [],
selectedTaskId,
onSelectTask,
filterProjectId,
setFilterProjectId,
width = 320,
onCreateTask,
onCreateProject,
isCreateActive,
activeTaskIds,
onTaskStatusChange,
onDeleteTask,
}: TaskListProps)
| 66 | export function TaskList({ |
| 67 | tasks = [], |
| 68 | projects = [], |
| 69 | selectedTaskId, |
| 70 | onSelectTask, |
| 71 | filterProjectId, |
| 72 | setFilterProjectId, |
| 73 | width = 320, |
| 74 | onCreateTask, |
| 75 | onCreateProject, |
| 76 | isCreateActive, |
| 77 | onTaskStatusChange, |
| 78 | onDeleteTask, |
| 79 | }: TaskListProps) { |
| 80 | const { t } = useI18n() |
| 81 | const { preserveDesktopSearch } = useDesktopTitlebar() |
| 82 | const isMobile = useIsMobile() |
| 83 | const [activeDragTask, setActiveDragTask] = useState<UITask | null>(null) |
| 84 | const [activeDragFromStatus, setActiveDragFromStatus] = useState<UITaskStatus | null>(null) |
| 85 | const [isSearchOpen, setIsSearchOpen] = useState(false) |
| 86 | |
| 87 | const flipRegistryRef = useRef<Map<string, HTMLElement>>(new Map()) |
| 88 | const activeGhostsRef = useRef<Set<HTMLDivElement>>(new Set()) |
| 89 | const previousCardsRef = useRef<Map<string, { status: UITaskStatus; rect: DOMRect; html: string }>>(new Map()) |
| 90 | |
| 91 | const flipHandle = useMemo<FlipHandle>(() => ({ |
| 92 | registry: flipRegistryRef.current, |
| 93 | }), []) |
| 94 | |
| 95 | const removeGhost = useCallback((ghost: HTMLDivElement) => { |
| 96 | ghost.remove() |
| 97 | activeGhostsRef.current.delete(ghost) |
| 98 | }, []) |
| 99 | |
| 100 | // 直接在 render 中计算派生状态,不使用 useEffect |
| 101 | const filteredTasks = filterProjectId |
| 102 | ? tasks.filter(t => t.projectId === filterProjectId) |
| 103 | : tasks |
| 104 | |
| 105 | const getGroupFallbackRect = useCallback((status: UITaskStatus, fromRect: DOMRect): DOMRect | null => { |
| 106 | const group = document.querySelector<HTMLElement>(`[data-task-group-status="${status}"]`) |
| 107 | if (!group) return null |
| 108 | |
| 109 | const header = group.querySelector<HTMLElement>('button') |
| 110 | const rect = (header ?? group).getBoundingClientRect() |
| 111 | if (rect.width === 0 || rect.height === 0) return null |
| 112 | |
| 113 | return new DOMRect(rect.left + 8, rect.bottom + 4, fromRect.width, fromRect.height) |
| 114 | }, []) |
| 115 | |
| 116 | const createStatusMoveGhost = useCallback((fromRect: DOMRect, toRect: DOMRect, html: string, isFallbackTarget = false) => { |
| 117 | const ghost = document.createElement('div') |
| 118 | ghost.setAttribute('aria-hidden', 'true') |
| 119 | ghost.innerHTML = html |
| 120 | const inner = ghost.firstElementChild as HTMLElement | null |
| 121 | if (inner) { |
| 122 | inner.style.cssText = '' |
| 123 | inner.className = inner.className |
| 124 | .replace(/\bopacity-\S*/g, '') |
| 125 | .replace(/\banimate-task-enter\b/g, '') |
nothing calls this directly
no test coverage detected