diff options
| author | Armand Philippot <git@armandphilippot.com> | 2023-11-14 19:07:14 +0100 |
|---|---|---|
| committer | Armand Philippot <git@armandphilippot.com> | 2023-11-14 19:07:14 +0100 |
| commit | be4d907efb4e2fa658baa7c9b276ed282eb920db (patch) | |
| tree | 0a7bd2d955ce9f9d5e252684ae6735bff7e9bd77 /src/utils/hooks/use-headings-tree.tsx | |
| parent | a3a4c50f26b8750ae1c87f1f1103b84b7d2e6315 (diff) | |
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
Diffstat (limited to 'src/utils/hooks/use-headings-tree.tsx')
| -rw-r--r-- | src/utils/hooks/use-headings-tree.tsx | 162 |
1 files changed, 0 insertions, 162 deletions
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<NodeListOf<HTMLHeadingElement>>(); - const [headingsTree, setHeadingsTree] = useState<Heading[]>([]); - - const getHeadingsInWrapper = useCallback(() => { - const query = depths.join(', '); - const result: NodeListOf<HTMLHeadingElement> = - 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<HTMLHeadingElement>} headings - A list of headings. - * @returns {Heading[]} An array of Heading objects. - */ - (headings: NodeListOf<HTMLHeadingElement>): 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<HTMLHeadingElement>} headings - A headings list. - * @returns {Heading[]} The headings tree. - */ - (headings: NodeListOf<HTMLHeadingElement>): Heading[] => { - const formattedHeadings = formatHeadings(headings); - - return buildTree(formattedHeadings); - }, - [formatHeadings, buildTree] - ); - - useEffect(() => { - if (allHeadings) { - const headingsList = getHeadingsTree(allHeadings); - setHeadingsTree(headingsList); - } - }, [allHeadings, getHeadingsTree]); - - return headingsTree; -}; |
