aboutsummaryrefslogtreecommitdiffstats
path: root/src/utils/hooks/use-headings-tree.tsx
diff options
context:
space:
mode:
authorArmand Philippot <git@armandphilippot.com>2023-11-14 19:07:14 +0100
committerArmand Philippot <git@armandphilippot.com>2023-11-14 19:07:14 +0100
commitbe4d907efb4e2fa658baa7c9b276ed282eb920db (patch)
tree0a7bd2d955ce9f9d5e252684ae6735bff7e9bd77 /src/utils/hooks/use-headings-tree.tsx
parenta3a4c50f26b8750ae1c87f1f1103b84b7d2e6315 (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.tsx162
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;
-};