export function decorateHanmacNavigation(api) { let frame; const write = (node, key, value) => { const next = String(value); if (node.getAttribute(key) !== next) node.setAttribute(key, next); }; const ancestors = (entry, refId) => { const result = []; const seen = new Set(); while (entry?.parent && !seen.has(entry.parent)) { seen.add(entry.parent); entry = api.resolveStory(entry.parent, refId); if (entry) result.push(entry); } return result; }; const decorate = () => { frame = undefined; const tree = document.querySelector('#storybook-explorer-tree'); if (!tree) return; const selected = api.getCurrentStoryData(); const currentParents = new Set(ancestors(selected, selected?.refId).map((item) => item.id)); const rows = [...tree.querySelectorAll('[data-item-id]')]; const rendered = new Set(rows.map((row) => `${row.dataset.refId}:${row.dataset.itemId}`)); const groups = new Map(); let section = 0; for (const row of rows) { const refId = row.dataset.refId === 'storybook_internal' ? undefined : row.dataset.refId; const entry = api.resolveStory(row.dataset.itemId, refId); if (!entry) continue; // Visual depth = rendered ancestors. A component with a single story of its own name is hoisted by Storybook (the // story replaces it at depth 1), while the API still reports the story at depth 2. write(row, 'data-hm-depth', ancestors(entry, refId).filter((item) => rendered.has(`${row.dataset.refId}:${item.id}`)).length); write(row, 'data-hm-current-parent', currentParents.has(entry.id) && refId === selected?.refId); if (entry.type === 'root') { write(row, 'data-hm-section', section++); continue; } // A single overlay covers the whole expanded branch, including deeper rows. const branch = [...ancestors(entry, refId).reverse(), entry].find((item) => item.type !== 'root'); const owner = rows.find((candidate) => candidate.dataset.itemId === branch?.id && candidate.dataset.refId === row.dataset.refId); const expanded = owner?.querySelector(':scope > button[aria-expanded="true"]'); write(row, 'data-hm-group', !!expanded); write(row, 'data-hm-group-start', false); write(row, 'data-hm-group-end', false); if (expanded) { const key = `${row.dataset.refId}:${branch.id}`; if (!groups.has(key)) groups.set(key, []); groups.get(key).push(row); } } for (const members of groups.values()) { write(members[0], 'data-hm-group-start', true); write(members.at(-1), 'data-hm-group-end', true); } }; const schedule = () => { if (frame === undefined) frame = requestAnimationFrame(decorate); }; const observer = new MutationObserver(schedule); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['data-selected', 'aria-expanded', 'data-item-id'], }); schedule(); window.addEventListener('pagehide', () => { observer.disconnect(); if (frame !== undefined) cancelAnimationFrame(frame); }, { once: true }); }