返回 oh-my-ppt
useVisibleItemIds.ts
根目录 / src / renderer / src / hooks / useVisibleItemIds.ts
1 import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
2
3 function limitVisibleIds(ids: ReadonlySet<string>, limit: number): Set<string> {
4 if (limit <= 0) return new Set()
5 if (ids.size <= limit) return new Set(ids)
6 return new Set(Array.from(ids).slice(-limit))
7 }
8
9 export function useVisibleItemIds(
10 itemIds: ReadonlySet<string>,
11 limit: number
12 ): {
13 visibleIds: Set<string>
14 setItemRef: (itemId: string) => (element: HTMLElement | null) => void
15 } {
16 const [intersectingIds, setIntersectingIds] = useState<Set<string>>(() => new Set())
17 const observerRef = useRef<IntersectionObserver | null>(null)
18 const itemRefsRef = useRef<Map<string, HTMLElement>>(new Map())
19 const itemIdsByElementRef = useRef<WeakMap<Element, string>>(new WeakMap())
20
21 useEffect(() => {
22 setIntersectingIds((current) => {
23 const next = new Set(Array.from(current).filter((id) => itemIds.has(id)))
24 return next.size === current.size ? current : next
25 })
26 }, [itemIds])
27
28 useEffect(() => {
29 if (itemIds.size === 0) return
30
31 const observer = new IntersectionObserver(
32 (entries) => {
33 setIntersectingIds((current) => {
34 const next = new Set(current)
35 let changed = false
36 for (const entry of entries) {
37 const id = itemIdsByElementRef.current.get(entry.target)
38 if (!id) continue
39 if (entry.isIntersecting) {
40 next.delete(id)
41 next.add(id)
42 changed = true
43 } else if (next.delete(id)) {
44 changed = true
45 }
46 }
47 return changed ? next : current
48 })
49 },
50 { rootMargin: '80px 40px', threshold: 0 }
51 )
52 observerRef.current = observer
53 for (const element of itemRefsRef.current.values()) observer.observe(element)
54
55 return () => {
56 observer.disconnect()
57 observerRef.current = null
58 }
59 }, [itemIds.size])
60
61 const visibleIds = useMemo(
62 () => limitVisibleIds(intersectingIds, limit),
63 [intersectingIds, limit]
64 )
65
66 const setItemRef = useCallback(
67 (itemId: string) => (element: HTMLElement | null) => {
68 const itemRefs = itemRefsRef.current
69 if (element) {
70 itemRefs.set(itemId, element)
71 itemIdsByElementRef.current.set(element, itemId)
72 observerRef.current?.observe(element)
73 return
74 }
75
76 const previous = itemRefs.get(itemId)
77 if (previous) observerRef.current?.unobserve(previous)
78 itemRefs.delete(itemId)
79 },
80 []
81 )
82
83 return { visibleIds, setItemRef }
84 }
85
85 lines TYPESCRIPT