@acrsolutions/chat-components
Version:
Un set di componenti per la creazione di un'applicazione di messaggistica
124 lines (123 loc) • 4.42 kB
JavaScript
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,
};
}