UNPKG

@bfehub/vuepress-theme-vmi

Version:

Vmi theme of VuePress

107 lines (106 loc) 4.32 kB
import { defineComponent, h, onBeforeUpdate, ref } from 'vue'; export const CodeGroup = defineComponent({ name: 'CodeGroup', setup(_, { slots }) { // index of current active item const activeIndex = ref(-1); // refs of the tab buttons const tabRefs = ref([]); if (__VUEPRESS_DEV__) { // after removing a code-group-item, we need to clear the ref // of the removed item to avoid issues caused by HMR onBeforeUpdate(() => { tabRefs.value = []; }); } // activate next tab const activateNext = (i = activeIndex.value) => { if (i < tabRefs.value.length - 1) { activeIndex.value = i + 1; } else { activeIndex.value = 0; } tabRefs.value[activeIndex.value].focus(); }; // activate previous tab const activatePrev = (i = activeIndex.value) => { if (i > 0) { activeIndex.value = i - 1; } else { activeIndex.value = tabRefs.value.length - 1; } tabRefs.value[activeIndex.value].focus(); }; // handle keyboard event const keyboardHandler = (event, i) => { if (event.key === ' ' || event.key === 'Enter') { event.preventDefault(); activeIndex.value = i; } else if (event.key === 'ArrowRight') { event.preventDefault(); activateNext(i); } else if (event.key === 'ArrowLeft') { event.preventDefault(); activatePrev(i); } }; return () => { // NOTICE: here we put the `slots.default()` inside the render function to make // the slots reactive, otherwise the slot content won't be changed once the // `setup()` function of current component is called // get children code-group-item const items = (slots.default?.() || []) .filter((vnode) => vnode.type.name === 'CodeGroupItem') .map((vnode) => { if (vnode.props === null) { vnode.props = {}; } return vnode; }); // do not render anything if there is no code-group-item if (items.length === 0) { return null; } if (activeIndex.value < 0 || activeIndex.value > items.length - 1) { // if `activeIndex` is invalid // find the index of the code-group-item with `active` props activeIndex.value = items.findIndex((vnode) => vnode.props.active === '' || vnode.props.active === true); // if there is no `active` props on code-group-item, set the first item active if (activeIndex.value === -1) { activeIndex.value = 0; } } else { // set the active item items.forEach((vnode, i) => { vnode.props.active = i === activeIndex.value; }); } return h('div', { class: 'code-group' }, [ h('div', { class: 'code-group__nav' }, h('ul', { class: 'code-group__ul' }, items.map((vnode, i) => { const isActive = i === activeIndex.value; return h('li', { class: 'code-group__li' }, h('button', { ref: (element) => { if (element) { tabRefs.value[i] = element; } }, class: { 'code-group__nav-tab': true, 'code-group__nav-tab-active': isActive, }, ariaPressed: isActive, ariaExpanded: isActive, onClick: () => (activeIndex.value = i), onKeydown: (e) => keyboardHandler(e, i), }, vnode.props.title)); }))), items, ]); }; }, });