71 lines
3.1 KiB
JavaScript
71 lines
3.1 KiB
JavaScript
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 });
|
|
}
|
|
|
|
|