122 lines
4.7 KiB
JavaScript
122 lines
4.7 KiB
JavaScript
import { useStorage } from '@vueuse/core';
|
|
import { computed, defineComponent, h, onBeforeUpdate, onMounted, ref, watch, } from 'vue';
|
|
import '../../styles/code-group.scss';
|
|
export const CodeGroup = defineComponent({
|
|
name: 'CodeGroup',
|
|
slots: Object,
|
|
setup(_, { slots }) {
|
|
// refs of the tab buttons
|
|
const tabRefs = ref([]);
|
|
if (__VUEPRESS_DEV__) {
|
|
// in dev mode, we need to clear the tabs ref to avoid HMR issues
|
|
// after removing a code-group-item
|
|
onBeforeUpdate(() => {
|
|
tabRefs.value = [];
|
|
});
|
|
}
|
|
// index of current active item
|
|
const activeIndex = ref(-1);
|
|
const codeGroupStorage = useStorage('vuepress-code-group', {});
|
|
const codeGroupKey = computed(() => tabRefs.value.map((tab) => tab.innerText).join(','));
|
|
onMounted(() => {
|
|
watch(() => codeGroupStorage.value[codeGroupKey.value], (val = -1) => {
|
|
if (activeIndex.value !== val) {
|
|
activeIndex.value = val;
|
|
}
|
|
}, { immediate: true });
|
|
watch(activeIndex, (val) => {
|
|
if (codeGroupStorage.value[codeGroupKey.value] !== val) {
|
|
codeGroupStorage.value[codeGroupKey.value] = val;
|
|
}
|
|
});
|
|
});
|
|
// 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 () => {
|
|
// get children code-group-item from default slots
|
|
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 is invalid
|
|
if (activeIndex.value < 0 || activeIndex.value > items.length - 1) {
|
|
// 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;
|
|
}
|
|
}
|
|
// if activeIndex is valid
|
|
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', role: 'tablist' }, items.map((vnode, i) => {
|
|
const isActive = i === activeIndex.value;
|
|
return h('button', {
|
|
ref: (element) => {
|
|
if (element) {
|
|
tabRefs.value[i] = element;
|
|
}
|
|
},
|
|
class: {
|
|
'code-group-nav-tab': true,
|
|
'active': isActive,
|
|
},
|
|
role: 'tab',
|
|
ariaSelected: isActive,
|
|
onClick: () => (activeIndex.value = i),
|
|
onKeydown: (e) => keyboardHandler(e, i),
|
|
}, vnode.props.title);
|
|
})),
|
|
items,
|
|
]);
|
|
};
|
|
},
|
|
});
|