mirror of
https://github.com/formbricks/formbricks.git
synced 2026-01-03 00:50:22 -06:00
Co-authored-by: Piyush Gupta <piyushguptaa2z123@gmail.com> Co-authored-by: Johannes <72809645+jobenjada@users.noreply.github.com> Co-authored-by: Johannes <johannes@formbricks.com> Co-authored-by: Matti Nannt <mail@matthiasnannt.com>
72 lines
3.2 KiB
TypeScript
72 lines
3.2 KiB
TypeScript
import { useEffect, useRef } from "react";
|
|
|
|
interface HeadingElement extends IntersectionObserverEntry {
|
|
target: HTMLHeadingElement;
|
|
}
|
|
|
|
/**
|
|
* A custom hook that sets up an IntersectionObserver to track the visibility of headings on the page.
|
|
*
|
|
* @param {Function} setActiveId - A function to set the active heading ID.
|
|
* @param {string} pathname - The current pathname, used as a dependency for the useEffect hook.
|
|
* @returns {void}
|
|
*
|
|
* This hook performs the following tasks:
|
|
* 1. Creates a map of heading elements, where the key is the heading's ID and the value is the heading element.
|
|
* 2. Finds the visible headings (i.e., headings that are currently intersecting with the viewport).
|
|
* 3. If there is only one visible heading, sets it as the active heading using the `setActiveId` function.
|
|
* 4. If there are multiple visible headings, sets the active heading to the one that is highest on the page (i.e., the one with the lowest index in the `headingElements` array).
|
|
* 5. Cleans up the IntersectionObserver and the `headingElementsRef` when the component is unmounted.
|
|
*/
|
|
export const useTableContentObserver = (setActiveId: (id: string) => void, pathname: string) => {
|
|
const headingElementsRef = useRef<Record<string, HeadingElement>>({});
|
|
|
|
useEffect(() => {
|
|
const callback = (headings: HeadingElement[]) => {
|
|
// Create a map of heading elements, where the key is the heading's ID and the value is the heading element
|
|
headingElementsRef.current = headings.reduce(
|
|
(map, headingElement) => {
|
|
return { ...map, [headingElement.target.id]: headingElement };
|
|
},
|
|
{} as Record<string, HeadingElement>
|
|
);
|
|
|
|
// Find the visible headings (i.e., headings that are currently intersecting with the viewport)
|
|
const visibleHeadings: HeadingElement[] = [];
|
|
Object.keys(headingElementsRef.current).forEach((key) => {
|
|
const headingElement = headingElementsRef.current[key];
|
|
if (headingElement.isIntersecting) visibleHeadings.push(headingElement);
|
|
});
|
|
|
|
// Define a function to get the index of a heading element in the headingElements array
|
|
const getIndexFromId = (id: string) => headingElements.findIndex((heading) => heading.id === id);
|
|
|
|
// If there is only one visible heading, set it as the active heading
|
|
if (visibleHeadings.length === 1) {
|
|
setActiveId(visibleHeadings[0].target.id);
|
|
}
|
|
// If there are multiple visible headings, set the active heading to the one that is highest on the page
|
|
else if (visibleHeadings.length > 1) {
|
|
const sortedVisibleHeadings = visibleHeadings.sort((a, b) => {
|
|
const aIndex = getIndexFromId(a.target.id);
|
|
const bIndex = getIndexFromId(b.target.id);
|
|
return aIndex - bIndex;
|
|
});
|
|
setActiveId(sortedVisibleHeadings[0].target.id);
|
|
}
|
|
};
|
|
|
|
const observer = new IntersectionObserver(callback, {
|
|
rootMargin: "-40px 0px -40% 0px",
|
|
});
|
|
|
|
const headingElements = Array.from(document.querySelectorAll("h2[id], h3[id], h4[id]"));
|
|
headingElements.forEach((element) => observer.observe(element));
|
|
|
|
return () => {
|
|
observer.disconnect();
|
|
headingElementsRef.current = {};
|
|
};
|
|
}, [setActiveId, pathname]);
|
|
};
|