diff --git a/docs/.vitepress/theme/index.ts b/docs/.vitepress/theme/index.ts index 1c45031e..9e41264b 100644 --- a/docs/.vitepress/theme/index.ts +++ b/docs/.vitepress/theme/index.ts @@ -7,6 +7,7 @@ import BlogLayout from '../components/BlogLayout.vue'; import './custom.css'; import 'virtual:group-icons.css'; import type { EnhanceAppContext } from 'vitepress/client'; +import { setupCodeGroupSync } from '../utils/code-groups-sync'; export default { extends: DefaultTheme, @@ -17,5 +18,7 @@ export default { .component('UsingWxtSection', UsingWxtSection) .component('ExampleSearch', ExampleSearch) .component('blog', BlogLayout); + + setupCodeGroupSync(); }, }; diff --git a/docs/.vitepress/utils/code-groups-sync.ts b/docs/.vitepress/utils/code-groups-sync.ts new file mode 100644 index 00000000..f34d577e --- /dev/null +++ b/docs/.vitepress/utils/code-groups-sync.ts @@ -0,0 +1,73 @@ +const CODE_GROUP_SELECTORS = { + root: '.vp-code-group', + input: '.vp-code-group .tabs input', + label: '.tabs label', +} as const; + +function getTabTitle(label: HTMLLabelElement) { + return label.dataset.title?.trim() || label.textContent?.trim(); +} + +function getCodeGroupTitle(input: HTMLInputElement) { + const label = input.parentElement?.querySelector( + `label[for="${input.id}"]`, + ); + + return label ? getTabTitle(label) : undefined; +} + +function findCodeGroupTabIndex(group: HTMLElement, title: string) { + return Array.from( + group.querySelectorAll(CODE_GROUP_SELECTORS.label), + ).findIndex((label) => getTabTitle(label) === title); +} + +function syncCodeGroupSelection(group: HTMLElement, title: string) { + const targetIndex = findCodeGroupTabIndex(group, title); + + if (targetIndex < 0) return; + + const blocks = group.querySelector('.blocks'); + const targetBlock = blocks?.children[targetIndex]; + if (!blocks || !targetBlock || targetBlock.classList.contains('active')) + return; + + blocks.querySelector('.active')?.classList.remove('active'); + targetBlock.classList.add('active'); + + const input = group.querySelectorAll( + CODE_GROUP_SELECTORS.input, + )[targetIndex]; + if (input) input.checked = true; +} + +function syncCodeGroupTabs(event: Event) { + const target = event.target; + if ( + !(target instanceof HTMLInputElement) || + !target.matches(CODE_GROUP_SELECTORS.input) + ) + return; + + const activeGroup = target.closest(CODE_GROUP_SELECTORS.root); + const activeTitle = getCodeGroupTitle(target); + if (!activeTitle) return; + + document + .querySelectorAll(CODE_GROUP_SELECTORS.root) + .forEach((group) => { + if (group === activeGroup) return; + + syncCodeGroupSelection(group, activeTitle); + }); +} + +/** + * Keeps every code group on the page in sync: selecting a package manager (or + * any tab) in one group switches the matching tab in all the other groups. + */ +export function setupCodeGroupSync() { + if (typeof window === 'undefined') return; + + window.addEventListener('change', syncCodeGroupTabs, true); +}