@san-siva/blogkit 1.1.20 → 1.1.22
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/components/BlogSection.js +2 -2
- package/dist/cjs/components/BlogSection.js.map +1 -1
- package/dist/cjs/dynamicComponents/BlogDynamic.js +31 -181
- package/dist/cjs/dynamicComponents/BlogDynamic.js.map +1 -1
- package/dist/cjs/dynamicComponents/BlogSectionDynamic.js +16 -8
- package/dist/cjs/dynamicComponents/BlogSectionDynamic.js.map +1 -1
- package/dist/cjs/hooks/useCategoryTitles.js +104 -0
- package/dist/cjs/hooks/useCategoryTitles.js.map +1 -0
- package/dist/cjs/hooks/useSectionObserver.js +89 -0
- package/dist/cjs/hooks/useSectionObserver.js.map +1 -0
- package/dist/cjs/index.css +1 -1
- package/dist/cjs/index.css.map +1 -1
- package/dist/cjs/staticComponents/BlogSectionStatic.js +2 -5
- package/dist/cjs/staticComponents/BlogSectionStatic.js.map +1 -1
- package/dist/cjs/staticComponents/TocNodeStatic.js +16 -0
- package/dist/cjs/staticComponents/TocNodeStatic.js.map +1 -0
- package/dist/cjs/styles/Blog.module.scss.js +1 -1
- package/dist/cjs/styles/BlogSection.module.scss.js +1 -1
- package/dist/cjs/styles/Callout.module.scss.js +1 -1
- package/dist/cjs/styles/TocNode.module.scss.js +8 -0
- package/dist/cjs/styles/TocNode.module.scss.js.map +1 -0
- package/dist/esm/components/BlogSection.js +2 -2
- package/dist/esm/components/BlogSection.js.map +1 -1
- package/dist/esm/dynamicComponents/BlogDynamic.js +32 -182
- package/dist/esm/dynamicComponents/BlogDynamic.js.map +1 -1
- package/dist/esm/dynamicComponents/BlogSectionDynamic.js +17 -9
- package/dist/esm/dynamicComponents/BlogSectionDynamic.js.map +1 -1
- package/dist/esm/hooks/useCategoryTitles.js +102 -0
- package/dist/esm/hooks/useCategoryTitles.js.map +1 -0
- package/dist/esm/hooks/useSectionObserver.js +87 -0
- package/dist/esm/hooks/useSectionObserver.js.map +1 -0
- package/dist/esm/index.css +1 -1
- package/dist/esm/index.css.map +1 -1
- package/dist/esm/staticComponents/BlogSectionStatic.js +2 -5
- package/dist/esm/staticComponents/BlogSectionStatic.js.map +1 -1
- package/dist/esm/staticComponents/TocNodeStatic.js +12 -0
- package/dist/esm/staticComponents/TocNodeStatic.js.map +1 -0
- package/dist/esm/styles/Blog.module.scss.js +1 -1
- package/dist/esm/styles/BlogSection.module.scss.js +1 -1
- package/dist/esm/styles/Callout.module.scss.js +1 -1
- package/dist/esm/styles/TocNode.module.scss.js +4 -0
- package/dist/esm/styles/TocNode.module.scss.js.map +1 -0
- package/dist/types/components/BlogSection.d.ts +0 -1
- package/dist/types/components/BlogSection.d.ts.map +1 -1
- package/dist/types/dynamicComponents/BlogDynamic.d.ts +1 -1
- package/dist/types/dynamicComponents/BlogDynamic.d.ts.map +1 -1
- package/dist/types/dynamicComponents/BlogSectionDynamic.d.ts +0 -1
- package/dist/types/dynamicComponents/BlogSectionDynamic.d.ts.map +1 -1
- package/dist/types/hooks/useCategoryTitles.d.ts +22 -0
- package/dist/types/hooks/useCategoryTitles.d.ts.map +1 -0
- package/dist/types/hooks/useSectionObserver.d.ts +11 -0
- package/dist/types/hooks/useSectionObserver.d.ts.map +1 -0
- package/dist/types/staticComponents/BlogSectionStatic.d.ts +1 -2
- package/dist/types/staticComponents/BlogSectionStatic.d.ts.map +1 -1
- package/dist/types/staticComponents/TocNodeStatic.d.ts +16 -0
- package/dist/types/staticComponents/TocNodeStatic.d.ts.map +1 -0
- package/package.json +5 -3
- package/src/components/BlogSection.tsx +0 -4
- package/src/dynamicComponents/BlogDynamic.tsx +42 -253
- package/src/dynamicComponents/BlogSectionDynamic.tsx +38 -21
- package/src/hooks/useCategoryTitles.ts +148 -0
- package/src/hooks/useSectionObserver.ts +102 -0
- package/src/staticComponents/BlogSectionStatic.tsx +2 -13
- package/src/staticComponents/TocNodeStatic.tsx +52 -0
- package/src/styles/Blog.module.scss +0 -30
- package/src/styles/Blog.module.scss.d.ts +0 -4
- package/src/styles/BlogHeader.module.scss +6 -5
- package/src/styles/BlogLink.module.scss +1 -1
- package/src/styles/BlogSection.module.scss +26 -21
- package/src/styles/BlogSection.module.scss.d.ts +1 -2
- package/src/styles/CodeBlock.module.scss +2 -2
- package/src/styles/Table.module.scss +1 -1
- package/src/styles/TocNode.module.scss +49 -0
- package/src/styles/TocNode.module.scss.d.ts +11 -0
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { useRef, useState, useCallback, useEffect } from 'react';
|
|
3
|
+
|
|
4
|
+
function useCategoryTitles({ visibleTitle, setVisibleTitle, setShowTOC, }) {
|
|
5
|
+
const sectionReferences = useRef(new Map());
|
|
6
|
+
const [categoryTitles, setCategoryTitles] = useState(new Map());
|
|
7
|
+
const updateTimerRef = useRef(null);
|
|
8
|
+
const sortByDomPosition = useCallback(([, a], [, b]) => {
|
|
9
|
+
const position = a.el.compareDocumentPosition(b.el);
|
|
10
|
+
if (position & Node.DOCUMENT_POSITION_FOLLOWING) {
|
|
11
|
+
return -1;
|
|
12
|
+
}
|
|
13
|
+
else if (position & Node.DOCUMENT_POSITION_PRECEDING) {
|
|
14
|
+
return 1;
|
|
15
|
+
}
|
|
16
|
+
return 0;
|
|
17
|
+
}, []);
|
|
18
|
+
const updateCategoryTitles = useCallback(() => {
|
|
19
|
+
const now = Date.now();
|
|
20
|
+
const newCategoryTitles = new Map();
|
|
21
|
+
const sectionsArray = Array.from(sectionReferences.current.entries());
|
|
22
|
+
sectionsArray.sort(sortByDomPosition);
|
|
23
|
+
let firstSectionId = null;
|
|
24
|
+
for (const [id, { title, el, depth }] of sectionsArray) {
|
|
25
|
+
if (!firstSectionId) {
|
|
26
|
+
firstSectionId = id;
|
|
27
|
+
}
|
|
28
|
+
newCategoryTitles.set(id, {
|
|
29
|
+
el,
|
|
30
|
+
title,
|
|
31
|
+
lastUpdatedAt: now,
|
|
32
|
+
depth,
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
if (newCategoryTitles.size === 0)
|
|
36
|
+
return;
|
|
37
|
+
setCategoryTitles(newCategoryTitles);
|
|
38
|
+
setShowTOC(true);
|
|
39
|
+
if (visibleTitle)
|
|
40
|
+
return;
|
|
41
|
+
setVisibleTitle(firstSectionId);
|
|
42
|
+
}, [visibleTitle, sortByDomPosition, setShowTOC, setVisibleTitle]);
|
|
43
|
+
const debounceUpdateCategoryTitles = useCallback(() => {
|
|
44
|
+
if (updateTimerRef.current) {
|
|
45
|
+
clearTimeout(updateTimerRef.current);
|
|
46
|
+
}
|
|
47
|
+
updateTimerRef.current = setTimeout(() => {
|
|
48
|
+
updateCategoryTitles();
|
|
49
|
+
}, 200);
|
|
50
|
+
}, [updateCategoryTitles]);
|
|
51
|
+
const removeStaleRefs = (ref) => {
|
|
52
|
+
for (const [existingId, { el }] of sectionReferences.current) {
|
|
53
|
+
if (el !== ref) {
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
sectionReferences.current.delete(existingId);
|
|
57
|
+
break;
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
const handleCategoryTitle = (ref) => {
|
|
61
|
+
const id = ref.dataset.id;
|
|
62
|
+
const title = ref.dataset.title;
|
|
63
|
+
if (!id || !title)
|
|
64
|
+
return;
|
|
65
|
+
let depth = 0;
|
|
66
|
+
let parent = ref.parentElement;
|
|
67
|
+
while (parent) {
|
|
68
|
+
if (parent.hasAttribute('data-id'))
|
|
69
|
+
depth++;
|
|
70
|
+
parent = parent.parentElement;
|
|
71
|
+
}
|
|
72
|
+
removeStaleRefs(ref);
|
|
73
|
+
sectionReferences.current.set(id, { el: ref, title, depth });
|
|
74
|
+
};
|
|
75
|
+
const processSection = (element) => {
|
|
76
|
+
const { parentRef, childRefs } = element;
|
|
77
|
+
if (parentRef)
|
|
78
|
+
handleCategoryTitle(parentRef);
|
|
79
|
+
if (Array.isArray(childRefs)) {
|
|
80
|
+
for (const childRef of childRefs) {
|
|
81
|
+
processSection(childRef);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
const handleSectionReference = useCallback((element) => {
|
|
86
|
+
if (!element)
|
|
87
|
+
return;
|
|
88
|
+
processSection(element);
|
|
89
|
+
debounceUpdateCategoryTitles();
|
|
90
|
+
}, [debounceUpdateCategoryTitles]);
|
|
91
|
+
useEffect(() => {
|
|
92
|
+
return () => {
|
|
93
|
+
if (updateTimerRef.current) {
|
|
94
|
+
clearTimeout(updateTimerRef.current);
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
}, []);
|
|
98
|
+
return { categoryTitles, handleSectionReference };
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export { useCategoryTitles };
|
|
102
|
+
//# sourceMappingURL=useCategoryTitles.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useCategoryTitles.js","sources":["../../../src/hooks/useCategoryTitles.ts"],"sourcesContent":["'use client';\n\nimport { useCallback, useEffect, useRef, useState } from 'react';\n\nimport type { Dispatch, SetStateAction } from 'react';\n\nimport type { ForwardedReference } from '../dynamicComponents/BlogDynamic';\n\nexport interface SectionReferenceValue {\n\tel: HTMLElement;\n\ttitle: string;\n\tdepth: number;\n}\n\nexport interface CategoryTitleValue extends SectionReferenceValue {\n\tlastUpdatedAt: number;\n}\n\nexport type CategoryTitle = Map<string, CategoryTitleValue>;\n\ntype SectionReference = Map<string, SectionReferenceValue>;\n\ninterface Options {\n\tvisibleTitle: string | null;\n\tsetVisibleTitle: Dispatch<SetStateAction<string | null>>;\n\tsetShowTOC: Dispatch<SetStateAction<boolean>>;\n}\n\nexport function useCategoryTitles({\n\tvisibleTitle,\n\tsetVisibleTitle,\n\tsetShowTOC,\n}: Options) {\n\tconst sectionReferences = useRef<SectionReference>(new Map());\n\tconst [categoryTitles, setCategoryTitles] = useState<CategoryTitle>(\n\t\tnew Map()\n\t);\n\tconst updateTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n\n\tconst sortByDomPosition = useCallback(\n\t\t(\n\t\t\t[, a]: [string, SectionReferenceValue],\n\t\t\t[, b]: [string, SectionReferenceValue]\n\t\t) => {\n\t\t\tconst position = a.el.compareDocumentPosition(b.el);\n\t\t\tif (position & Node.DOCUMENT_POSITION_FOLLOWING) {\n\t\t\t\treturn -1;\n\t\t\t} else if (position & Node.DOCUMENT_POSITION_PRECEDING) {\n\t\t\t\treturn 1;\n\t\t\t}\n\t\t\treturn 0;\n\t\t},\n\t\t[]\n\t);\n\n\tconst updateCategoryTitles = useCallback(() => {\n\t\tconst now = Date.now();\n\t\tconst newCategoryTitles = new Map<string, CategoryTitleValue>();\n\n\t\tconst sectionsArray = Array.from(sectionReferences.current.entries());\n\t\tsectionsArray.sort(sortByDomPosition);\n\n\t\tlet firstSectionId: string | null = null;\n\t\tfor (const [id, { title, el, depth }] of sectionsArray) {\n\t\t\tif (!firstSectionId) {\n\t\t\t\tfirstSectionId = id;\n\t\t\t}\n\t\t\tnewCategoryTitles.set(id, {\n\t\t\t\tel,\n\t\t\t\ttitle,\n\t\t\t\tlastUpdatedAt: now,\n\t\t\t\tdepth,\n\t\t\t});\n\t\t}\n\n\t\tif (newCategoryTitles.size === 0) return;\n\n\t\tsetCategoryTitles(newCategoryTitles);\n\t\tsetShowTOC(true);\n\n\t\tif (visibleTitle) return;\n\t\tsetVisibleTitle(firstSectionId);\n\t}, [visibleTitle, sortByDomPosition, setShowTOC, setVisibleTitle]);\n\n\tconst debounceUpdateCategoryTitles = useCallback(() => {\n\t\tif (updateTimerRef.current) {\n\t\t\tclearTimeout(updateTimerRef.current);\n\t\t}\n\t\tupdateTimerRef.current = setTimeout(() => {\n\t\t\tupdateCategoryTitles();\n\t\t}, 200);\n\t}, [updateCategoryTitles]);\n\n\tconst removeStaleRefs = (ref: HTMLDivElement) => {\n\t\tfor (const [existingId, { el }] of sectionReferences.current) {\n\t\t\tif (el !== ref) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tsectionReferences.current.delete(existingId);\n\t\t\tbreak;\n\t\t}\n\t};\n\n\tconst handleCategoryTitle = (ref: HTMLDivElement) => {\n\t\tconst id = ref.dataset.id;\n\t\tconst title = ref.dataset.title;\n\t\tif (!id || !title) return;\n\n\t\tlet depth = 0;\n\t\tlet parent = ref.parentElement;\n\t\twhile (parent) {\n\t\t\tif (parent.hasAttribute('data-id')) depth++;\n\t\t\tparent = parent.parentElement;\n\t\t}\n\n\t\tremoveStaleRefs(ref);\n\t\tsectionReferences.current.set(id, { el: ref, title, depth });\n\t};\n\n\tconst processSection = (element: ForwardedReference) => {\n\t\tconst { parentRef, childRefs } = element;\n\t\tif (parentRef) handleCategoryTitle(parentRef);\n\t\tif (Array.isArray(childRefs)) {\n\t\t\tfor (const childRef of childRefs) {\n\t\t\t\tprocessSection(childRef);\n\t\t\t}\n\t\t}\n\t};\n\n\tconst handleSectionReference = useCallback(\n\t\t(element: ForwardedReference) => {\n\t\t\tif (!element) return;\n\t\t\tprocessSection(element);\n\t\t\tdebounceUpdateCategoryTitles();\n\t\t},\n\t\t[debounceUpdateCategoryTitles]\n\t);\n\n\tuseEffect(() => {\n\t\treturn () => {\n\t\t\tif (updateTimerRef.current) {\n\t\t\t\tclearTimeout(updateTimerRef.current);\n\t\t\t}\n\t\t};\n\t}, []);\n\n\treturn { categoryTitles, handleSectionReference };\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { useRef, useEffect, useCallback } from 'react';
|
|
3
|
+
import lockScrollUpdates from '../utils/lockScrollUpdates.js';
|
|
4
|
+
|
|
5
|
+
const getSectionFromUrl = () => {
|
|
6
|
+
const url = new URL(window.location.href);
|
|
7
|
+
return url.searchParams.get('section');
|
|
8
|
+
};
|
|
9
|
+
const updateUrl = (id) => {
|
|
10
|
+
const url = new URL(window.location.href);
|
|
11
|
+
url.searchParams.set('section', id);
|
|
12
|
+
window.history.replaceState({}, '', url.toString());
|
|
13
|
+
};
|
|
14
|
+
const scrollIntoView = (element) => {
|
|
15
|
+
if (!element)
|
|
16
|
+
return;
|
|
17
|
+
const top = element.getBoundingClientRect().top + document.body.scrollTop - 100;
|
|
18
|
+
document.body.scrollTo({ top, behavior: 'smooth' });
|
|
19
|
+
};
|
|
20
|
+
function useSectionObserver({ categoryTitles, setVisibleTitle }) {
|
|
21
|
+
const isClickScrolling = useRef(false);
|
|
22
|
+
const scrollEndHandlerRef = useRef(null);
|
|
23
|
+
const intersectionObserversRef = useRef(new Map());
|
|
24
|
+
// Set up IntersectionObservers whenever the set of sections changes
|
|
25
|
+
useEffect(() => {
|
|
26
|
+
for (const [id, { el }] of categoryTitles) {
|
|
27
|
+
const observer = new IntersectionObserver(([entry]) => {
|
|
28
|
+
if (!entry.isIntersecting)
|
|
29
|
+
return;
|
|
30
|
+
if (isClickScrolling.current)
|
|
31
|
+
return;
|
|
32
|
+
if (document.body.scrollTop === 0)
|
|
33
|
+
return;
|
|
34
|
+
setVisibleTitle(visibleId => {
|
|
35
|
+
if (visibleId === id && !entry.isIntersecting)
|
|
36
|
+
return null;
|
|
37
|
+
if (entry.isIntersecting)
|
|
38
|
+
return id;
|
|
39
|
+
return visibleId;
|
|
40
|
+
});
|
|
41
|
+
updateUrl(id);
|
|
42
|
+
}, { threshold: 0.1 });
|
|
43
|
+
intersectionObserversRef.current.set(id, observer);
|
|
44
|
+
observer.observe(el);
|
|
45
|
+
}
|
|
46
|
+
}, [categoryTitles.size, setVisibleTitle]);
|
|
47
|
+
// On initial load, scroll to section specified in URL
|
|
48
|
+
useEffect(() => {
|
|
49
|
+
if (categoryTitles.size === 0)
|
|
50
|
+
return;
|
|
51
|
+
const section = getSectionFromUrl();
|
|
52
|
+
if (!section)
|
|
53
|
+
return;
|
|
54
|
+
const entry = categoryTitles.get(section);
|
|
55
|
+
if (!entry)
|
|
56
|
+
return;
|
|
57
|
+
scrollIntoView(entry.el);
|
|
58
|
+
lockScrollUpdates(section, isClickScrolling, scrollEndHandlerRef, setVisibleTitle);
|
|
59
|
+
}, [categoryTitles.size, setVisibleTitle]);
|
|
60
|
+
// Cleanup observers on unmount
|
|
61
|
+
useEffect(() => {
|
|
62
|
+
return () => {
|
|
63
|
+
if (scrollEndHandlerRef.current) {
|
|
64
|
+
document.body.removeEventListener('scrollend', scrollEndHandlerRef.current);
|
|
65
|
+
}
|
|
66
|
+
for (const observer of intersectionObserversRef.current.values()) {
|
|
67
|
+
observer.disconnect();
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
}, []);
|
|
71
|
+
const handleClickCategoryTitle = useCallback((event) => {
|
|
72
|
+
const id = event.currentTarget.dataset.id;
|
|
73
|
+
const index = event.currentTarget.dataset.idx;
|
|
74
|
+
if (!id || !index)
|
|
75
|
+
return;
|
|
76
|
+
const { el } = categoryTitles.get(id) || {};
|
|
77
|
+
if (!el)
|
|
78
|
+
return;
|
|
79
|
+
updateUrl(id);
|
|
80
|
+
scrollIntoView(el);
|
|
81
|
+
lockScrollUpdates(id, isClickScrolling, scrollEndHandlerRef, setVisibleTitle);
|
|
82
|
+
}, [categoryTitles, setVisibleTitle]);
|
|
83
|
+
return { handleClickCategoryTitle };
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export { useSectionObserver };
|
|
87
|
+
//# sourceMappingURL=useSectionObserver.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useSectionObserver.js","sources":["../../../src/hooks/useSectionObserver.ts"],"sourcesContent":["'use client';\n\nimport { useCallback, useEffect, useRef } from 'react';\n\nimport type { Dispatch, MouseEvent, SetStateAction } from 'react';\n\nimport lockScrollUpdates from '../utils/lockScrollUpdates';\nimport type { CategoryTitle } from './useCategoryTitles';\n\nconst getSectionFromUrl = () => {\n\tconst url = new URL(window.location.href);\n\treturn url.searchParams.get('section');\n};\n\nconst updateUrl = (id: string) => {\n\tconst url = new URL(window.location.href);\n\turl.searchParams.set('section', id);\n\twindow.history.replaceState({}, '', url.toString());\n};\n\nconst scrollIntoView = (element: HTMLElement) => {\n\tif (!element) return;\n\tconst top =\n\t\telement.getBoundingClientRect().top + document.body.scrollTop - 100;\n\tdocument.body.scrollTo({ top, behavior: 'smooth' });\n};\n\ninterface Options {\n\tcategoryTitles: CategoryTitle;\n\tsetVisibleTitle: Dispatch<SetStateAction<string | null>>;\n}\n\nexport function useSectionObserver({ categoryTitles, setVisibleTitle }: Options) {\n\tconst isClickScrolling = useRef(false);\n\tconst scrollEndHandlerRef = useRef<(() => void) | null>(null);\n\tconst intersectionObserversRef = useRef<Map<string, IntersectionObserver>>(\n\t\tnew Map()\n\t);\n\n\t// Set up IntersectionObservers whenever the set of sections changes\n\tuseEffect(() => {\n\t\tfor (const [id, { el }] of categoryTitles) {\n\t\t\tconst observer = new IntersectionObserver(\n\t\t\t\t([entry]) => {\n\t\t\t\t\tif (!entry.isIntersecting) return;\n\t\t\t\t\tif (isClickScrolling.current) return;\n\t\t\t\t\tif (document.body.scrollTop === 0) return;\n\t\t\t\t\tsetVisibleTitle(visibleId => {\n\t\t\t\t\t\tif (visibleId === id && !entry.isIntersecting) return null;\n\t\t\t\t\t\tif (entry.isIntersecting) return id;\n\t\t\t\t\t\treturn visibleId;\n\t\t\t\t\t});\n\t\t\t\t\tupdateUrl(id);\n\t\t\t\t},\n\t\t\t\t{ threshold: 0.1 }\n\t\t\t);\n\t\t\tintersectionObserversRef.current.set(id, observer);\n\t\t\tobserver.observe(el as HTMLElement);\n\t\t}\n\t}, [categoryTitles.size, setVisibleTitle]);\n\n\t// On initial load, scroll to section specified in URL\n\tuseEffect(() => {\n\t\tif (categoryTitles.size === 0) return;\n\t\tconst section = getSectionFromUrl();\n\t\tif (!section) return;\n\t\tconst entry = categoryTitles.get(section);\n\t\tif (!entry) return;\n\t\tscrollIntoView(entry.el);\n\t\tlockScrollUpdates(section, isClickScrolling, scrollEndHandlerRef, setVisibleTitle);\n\t}, [categoryTitles.size, setVisibleTitle]);\n\n\t// Cleanup observers on unmount\n\tuseEffect(() => {\n\t\treturn () => {\n\t\t\tif (scrollEndHandlerRef.current) {\n\t\t\t\tdocument.body.removeEventListener('scrollend', scrollEndHandlerRef.current);\n\t\t\t}\n\t\t\tfor (const observer of intersectionObserversRef.current.values()) {\n\t\t\t\tobserver.disconnect();\n\t\t\t}\n\t\t};\n\t}, []);\n\n\tconst handleClickCategoryTitle = useCallback(\n\t\t(event: MouseEvent<HTMLParagraphElement>) => {\n\t\t\tconst id = event.currentTarget.dataset.id;\n\t\t\tconst index = event.currentTarget.dataset.idx;\n\t\t\tif (!id || !index) return;\n\n\t\t\tconst { el } = categoryTitles.get(id) || {};\n\t\t\tif (!el) return;\n\n\t\t\tupdateUrl(id);\n\t\t\tscrollIntoView(el);\n\t\t\tlockScrollUpdates(id, isClickScrolling, scrollEndHandlerRef, setVisibleTitle);\n\t\t},\n\t\t[categoryTitles, setVisibleTitle]\n\t);\n\n\treturn { handleClickCategoryTitle };\n}\n"],"names":[],"mappings":"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"}
|