-
-
Notifications
You must be signed in to change notification settings - Fork 117
Open
Description
getIsHeaderBottomMode passes CSS-escaped ID to getElementById, causing TypeError: null.offsetTop
Bug Description
When a heading has an ID containing CSS special characters (e.g. . in 3.1-introduction), tocbot throws:
Uncaught TypeError: Cannot read properties of null (reading 'offsetTop')
at getIsHeaderBottomMode (build-html.js:354)
at Object.updateToc (build-html.js:222)
Root Cause
In build-html.js, topHeaderId is CSS-escaped at line 207-210:
const topHeaderId = topHeader.id.replace(
/([ #;&,.+*~':"!^$[\]()=>|/\\@])/g,
"\\$1",
)This is correct for CSS selectors (line 233):
const activeTocLink = tocElement.querySelector(
`.${options.linkClass}[href="${options.basePath}#${activeId}"]`,
)But the escaped ID is also passed to getIsHeaderBottomMode at line 222:
(getIsHeaderBottomMode(topHeaderId) || eventCount <= 2)Where it's used with getElementById (line 352):
const activeHeading = document?.getElementById(headerId)
const isBottomMode = activeHeading.offsetTop > ... // activeHeading is null!getElementById expects the raw ID (3.1-introduction), not the CSS-escaped version (3\.1-introduction), so it returns null.
Steps to Reproduce
- Have a heading with
.in its ID, e.g.<h2 id="3.1-introduction">3.1 Introduction</h2> - Initialize tocbot with
scrollContainerset - Click a TOC link → URL gets
#3.1-introduction - Scroll or click another TOC link → crash
Suggested Fix
Unescape the ID before passing to getElementById, or keep a separate unescaped copy:
// Option A: pass raw ID to getIsHeaderBottomMode
const rawTopHeaderId = topHeader.id
const topHeaderId = rawTopHeaderId.replace(
/([ #;&,.+*~':"!^$[\]()=>|/\\@])/g,
"\\$1",
)
// ...
(getIsHeaderBottomMode(rawTopHeaderId) || eventCount <= 2)Also add null check in getIsHeaderBottomMode:
function getIsHeaderBottomMode(headerId) {
const scrollEl = getScrollEl()
const activeHeading = document?.getElementById(headerId)
if (!activeHeading) return false
// ...
}Environment
- tocbot: 4.36.4
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
No labels