From be4d907efb4e2fa658baa7c9b276ed282eb920db Mon Sep 17 00:00:00 2001 From: Armand Philippot Date: Tue, 14 Nov 2023 19:07:14 +0100 Subject: refactor(components, hooks): rewrite ToC and useHeadingsTree * replace TableOfContents component with TocWidget to keep the name of widget components coherent * replace `wrapper` prop with `tree` prop (the component no longer uses the hook, it is up to the consumer to provide the headings tree) * let consumer handle the widget title * add options to useHeadingsTree hook to retrieve only the wanted headings (and do not assume that h1 is unwanted) * expect an ref object instead of an element in useHeadingsTree hook * rename most of the types involved --- src/utils/hooks/use-headings-tree.tsx | 162 ---------------------------------- 1 file changed, 162 deletions(-) delete mode 100644 src/utils/hooks/use-headings-tree.tsx (limited to 'src/utils/hooks/use-headings-tree.tsx') diff --git a/src/utils/hooks/use-headings-tree.tsx b/src/utils/hooks/use-headings-tree.tsx deleted file mode 100644 index 049ab29..0000000 --- a/src/utils/hooks/use-headings-tree.tsx +++ /dev/null @@ -1,162 +0,0 @@ -import { useCallback, useEffect, useMemo, useState } from 'react'; -import { slugify } from '../helpers'; -import { useMutationObserver } from './use-mutation-observer'; - -export type Heading = { - /** - * The heading depth. - */ - depth: number; - /** - * The heading id. - */ - id: string; - /** - * The heading children. - */ - children: Heading[]; - /** - * The heading title. - */ - title: string; -}; - -/** - * Get the headings tree of the given HTML element. - * - * @param {HTMLElement} wrapper - An HTML element that contains the headings. - * @returns {Heading[]} The headings tree. - */ -export const useHeadingsTree = (wrapper: HTMLElement): Heading[] => { - const depths = useMemo(() => ['h2', 'h3', 'h4', 'h5', 'h6'], []); - const [allHeadings, setAllHeadings] = - useState>(); - const [headingsTree, setHeadingsTree] = useState([]); - - const getHeadingsInWrapper = useCallback(() => { - const query = depths.join(', '); - const result: NodeListOf = - wrapper.querySelectorAll(query); - setAllHeadings(result); - }, [depths, wrapper]); - - useEffect(() => { - getHeadingsInWrapper(); - }, [getHeadingsInWrapper]); - - useMutationObserver({ - callback: getHeadingsInWrapper, - options: { childList: true }, - nodeOrSelector: wrapper, - }); - - const getDepth = useCallback( - /** - * Retrieve the heading element depth. - * - * @param {HTMLHeadingElement} el - An heading element. - * @returns {number} The heading depth. - */ - (el: HTMLHeadingElement): number => { - return depths.findIndex((depth) => depth === el.localName); - }, - [depths] - ); - - const formatHeadings = useCallback( - /** - * Convert a list of headings into an array of Heading objects. - * - * @param {NodeListOf} headings - A list of headings. - * @returns {Heading[]} An array of Heading objects. - */ - (headings: NodeListOf): Heading[] => { - const formattedHeadings: Heading[] = []; - - Array.from(headings).forEach((heading) => { - const title: string = heading.textContent!; - const id = slugify(title); - const depth = getDepth(heading); - const children: Heading[] = []; - - heading.id = id; - - formattedHeadings.push({ - depth, - id, - children, - title, - }); - }); - - return formattedHeadings; - }, - [getDepth] - ); - - const buildSubTree = useCallback( - /** - * Build the heading subtree. - * - * @param {Heading} parent - The heading parent. - * @param {Heading} currentHeading - The current heading element. - */ - (parent: Heading, currentHeading: Heading): void => { - if (parent.depth === currentHeading.depth - 1) { - parent.children.push(currentHeading); - } else { - const lastItem = parent.children[parent.children.length - 1]; - buildSubTree(lastItem, currentHeading); - } - }, - [] - ); - - const buildTree = useCallback( - /** - * Build a heading tree. - * - * @param {Heading[]} headings - An array of Heading objects. - * @returns {Heading[]} The headings tree. - */ - (headings: Heading[]): Heading[] => { - const tree: Heading[] = []; - - headings.forEach((heading) => { - if (heading.depth === 0) { - tree.push(heading); - } else { - const lastItem = tree[tree.length - 1]; - buildSubTree(lastItem, heading); - } - }); - - return tree; - }, - [buildSubTree] - ); - - const getHeadingsTree = useCallback( - /** - * Retrieve a headings tree from a list of headings element. - * - * @param {NodeListOf} headings - A headings list. - * @returns {Heading[]} The headings tree. - */ - (headings: NodeListOf): Heading[] => { - const formattedHeadings = formatHeadings(headings); - - return buildTree(formattedHeadings); - }, - [formatHeadings, buildTree] - ); - - useEffect(() => { - if (allHeadings) { - const headingsList = getHeadingsTree(allHeadings); - setHeadingsTree(headingsList); - } - }, [allHeadings, getHeadingsTree]); - - return headingsTree; -}; -- cgit v1.2.3