Skip to content

Commit 74f7b5e

Browse files
committed
Rebuild page TOC on tab changes
1 parent 42f6c79 commit 74f7b5e

2 files changed

Lines changed: 34 additions & 32 deletions

File tree

apps/sim/lib/workspace-files/artifact-stylesheet.ts

Lines changed: 30 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -645,14 +645,18 @@ export const SIM_ARTIFACT_SHELL = `<script>
645645
const page = document.querySelector('.page[data-layout="docs"]')
646646
if (!page || page.dataset.shell === 'ready') return
647647
const main = page.querySelector('main') || page
648-
const headings = [...main.querySelectorAll('h2, h3')]
649-
if (headings.length === 0) return
648+
if (main.querySelectorAll('h2, h3').length === 0) return
650649
page.dataset.shell = 'ready'
651650
651+
// In a multi-tab document each tab is its own page: the TOC is built from
652+
// the ACTIVE panel's headings only and rebuilt on every tab switch.
653+
const tocScope = () => main.querySelector('[data-tab-panel].is-active') || main
654+
let headings = []
655+
let tocLinks = []
656+
652657
const ICON_ATTRS = 'viewBox="-1 -2 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="1.55" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"'
653658
const slug = (text, i) =>
654659
(text.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '') || 'section') + '-' + i
655-
headings.forEach((h, i) => { if (!h.id) h.id = slug(h.textContent || '', i) })
656660
657661
// Right rail — the clerk TOC.
658662
const right = document.createElement('nav')
@@ -679,15 +683,6 @@ export const SIM_ARTIFACT_SHELL = `<script>
679683
const thumbPath = document.createElementNS(SVG_NS, 'path')
680684
thumb.appendChild(thumbPath)
681685
tocItems.append(track, thumb)
682-
const tocLinks = []
683-
for (const h of headings) {
684-
const a = document.createElement('a')
685-
a.href = '#' + h.id
686-
a.textContent = h.textContent
687-
a.dataset.depth = h.tagName === 'H2' ? '2' : '3'
688-
tocItems.appendChild(a)
689-
tocLinks.push(a)
690-
}
691686
right.append(rightTitle, tocItems)
692687
693688
// Top bar with the page title and the theme toggle.
@@ -872,7 +867,29 @@ export const SIM_ARTIFACT_SHELL = `<script>
872867
new ResizeObserver(refresh).observe(tocItems)
873868
document.addEventListener('scroll', spy, { passive: true })
874869
window.addEventListener('resize', refresh)
875-
refresh()
870+
871+
// (Re)builds the TOC from the active scope — the whole page, or the
872+
// active tab's panel. Each tab switch produces a fresh rail, so every
873+
// tab reads as its own page.
874+
let slugSeq = 0
875+
const populateToc = () => {
876+
headings = [...tocScope().querySelectorAll('h2, h3')]
877+
headings.forEach((h) => { if (!h.id) h.id = slug(h.textContent || '', slugSeq++) })
878+
for (const a of [...tocItems.querySelectorAll('a')]) a.remove()
879+
tocLinks = []
880+
for (const h of headings) {
881+
const a = document.createElement('a')
882+
a.href = '#' + h.id
883+
a.textContent = h.textContent
884+
a.dataset.depth = h.tagName === 'H2' ? '2' : '3'
885+
tocItems.appendChild(a)
886+
tocLinks.push(a)
887+
}
888+
right.style.display = headings.length === 0 ? 'none' : ''
889+
refresh()
890+
}
891+
document.addEventListener('sim-tab-change', populateToc)
892+
populateToc()
876893
}
877894
878895
// In-page anchors must scroll, never navigate. Under the preview's

apps/sim/lib/workspace-files/page-compile.ts

Lines changed: 4 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -140,37 +140,22 @@ function docTabBar(tabs: DocTab[]): string {
140140
}
141141

142142
/**
143-
* The in-document tab switcher. Panels toggle by class; the shell's TOC is
144-
* built from every panel's headings, so each switch re-hides the rail links
145-
* that belong to inactive panels (the rAF wait covers the shell building
146-
* after this script runs).
143+
* The in-document tab switcher: toggles panels and announces the change. The
144+
* shell rebuilds its "On this page" rail from the newly active panel on the
145+
* sim-tab-change event, so each tab reads as its own page.
147146
*/
148147
const DOC_TABS_SCRIPT = `<script>
149148
(() => {
150149
const tabs = [...document.querySelectorAll('[data-doc-tabs] .page-tab')]
151150
const panels = [...document.querySelectorAll('[data-tab-panel]')]
152151
if (tabs.length === 0) return
153-
const syncToc = () => {
154-
for (const a of document.querySelectorAll('.rail[data-rail="toc"] a')) {
155-
const id = (a.getAttribute('href') || '').slice(1)
156-
const el = id ? document.getElementById(id) : null
157-
const panel = el ? el.closest('[data-tab-panel]') : null
158-
a.toggleAttribute('hidden', Boolean(panel && !panel.classList.contains('is-active')))
159-
}
160-
}
161152
const activate = (target) => {
162153
for (const t of tabs) t.classList.toggle('is-active', t.dataset.tabTarget === target)
163154
for (const p of panels) p.classList.toggle('is-active', p.id === target)
164-
syncToc()
165155
window.scrollTo({ top: 0 })
156+
document.dispatchEvent(new Event('sim-tab-change'))
166157
}
167158
for (const t of tabs) t.addEventListener('click', () => activate(t.dataset.tabTarget))
168-
let tries = 0
169-
const wait = () => {
170-
if (document.querySelector('.rail[data-rail="toc"]') || tries++ > 40) return syncToc()
171-
requestAnimationFrame(wait)
172-
}
173-
wait()
174159
})()
175160
</script>`
176161

0 commit comments

Comments
 (0)