UNPKG

@acrsolutions/chat-components

Version:

Un set di componenti per la creazione di un'applicazione di messaggistica

124 lines (123 loc) 4.42 kB
import { filter, keys, pick } from 'lodash'; import { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState, } from 'react'; export function useScrollToResearch({ messagesContainerRef, convoId, /** * Stato di loading dei messaggi */ isLoading, /** * Il filtro attivo sui messaggi, per cui vanno cercate le parole */ messagesFilter, }) { /** * Il counter delle referenze totali generate */ const [counter, setCounter] = useState(0); /** * References agli span evidenziati in seguito alla ricerca */ const searchRefs = useRef({}); /** * handleSearchRefs viene scatenata alla creazione degli span */ const handleSearchRefs = useCallback((messageId) => (refs) => { const firstKey = Object.keys(refs)[0]; searchRefs.current = { ...searchRefs.current, [`${convoId}_${messageId}_${firstKey}`]: refs[firstKey], }; }, [convoId]); /* * le chiavi degli elementi generati in seguito all'ultima ricerca */ const [focusableSearchElementsKeys, $focusableSearchElements] = useState([]); /** * Gli elementi tra cui è possibile navigare dopo ogni ricerca */ const focusableSearchElements = useMemo(() => { const selectedRefs = pick(searchRefs.current, focusableSearchElementsKeys); return Object.values(selectedRefs).filter((el) => el !== undefined); }, [focusableSearchElementsKeys]); /** * Le chiavi degli elementi tra cui è possibile navigare dopo ogni ricerca */ const focusedRefKey = useMemo(() => { return focusableSearchElementsKeys[counter] ?? null; }, [counter, focusableSearchElementsKeys]); /** * Il numero di occorrenze trovate alla ricerca attuale. * E' sempre minimo 0 */ const occurrencies = useMemo(() => { return focusableSearchElements.length; }, [focusableSearchElements]); /** * Lo span via via focalizzato durante la ricerca. * Può essere null se non ci sono ricerche attive */ const focusedSpan = useMemo(() => { return focusableSearchElements.find((el, i) => i === counter && el) ?? null; }, [focusableSearchElements, counter]); /** * gestisce gli elementi span recuperandone le chiavi attive all'ultima ricerca * se non c'è un filtro attivo svuota le references, i focusable e il counter * le chiavi attive sono quelle che iniziano con il convoId e finiscono con la ricerca attiva * questo impedisce duplicati */ useLayoutEffect(() => { setCounter(0); if (messagesFilter === '') { searchRefs.current = {}; $focusableSearchElements([]); return; } const allKeys = keys(searchRefs.current); const refsKeys = filter(allKeys, (key) => { return key.endsWith(messagesFilter) && key.startsWith(convoId); }); $focusableSearchElements(refsKeys); }, [messagesFilter, convoId, isLoading]); /** * gestisce lo scroll fino allo span evidenziato */ useEffect(() => { if (!focusedSpan) { const container = messagesContainerRef.current; if (container) { container.scrollTop = container.scrollHeight; } } else { focusedSpan.scrollIntoView({ behavior: 'smooth', block: 'center' }); } }, [focusedSpan, messagesContainerRef]); //va alla ref precedente const goToPrevRef = useCallback(() => { setCounter((prev) => prev ? prev - 1 : focusableSearchElements ? focusableSearchElements.length - 1 : 0); }, [focusableSearchElements]); //va alla ref successiva const goToNextRef = useCallback(() => { setCounter((prev) => prev < focusableSearchElements.length - 1 ? prev + 1 : 0); }, [focusableSearchElements]); //cerca l'indice in focus, dato un id messaggio const getIndexInFocus = useCallback((messageId) => { const focusedIndex = focusedRefKey?.includes(messageId) ? Number(focusedRefKey.split('_')[2]) : -1; return focusedIndex; }, [focusedRefKey]); return { counter, handleSearchRefs, focusedRefKey, goToPrevRef, goToNextRef, occurrencies, focusedSpan, getIndexInFocus, }; }