@coreui/vue
Version:
UI Components Library for Vue.js
157 lines (154 loc) • 6.38 kB
JavaScript
import { defineComponent, useId, getCurrentInstance, ref, inject, computed, provide, watch, h } from 'vue';
import { executeAfterTransition } from '../../utils/transition.js';
const CNavGroup = defineComponent({
name: 'CNavGroup',
props: {
/**
* Component used for the root node. Either a string to use a HTML element or a component.
*/
as: {
type: String,
default: 'li',
},
/**
* Make nav group more compact by cutting all `padding` in half.
*/
compact: Boolean,
/**
* Show nav group items. Acts as the initial state on its own, or as the controlled value
* when paired with a `v-model:visible` / `@update:visible` listener.
*/
visible: {
type: Boolean,
default: undefined,
},
},
emits: ['update:visible', 'visible-change'],
setup(props, { slots, emit }) {
const key = useId();
const instance = getCurrentInstance();
const hasUpdateListener = Boolean(instance?.vnode.props?.['onUpdate:visible']);
const navGroupItemsRef = ref();
const parentActiveKey = inject('cNavGroupActiveKey', undefined);
const parentSetActiveKey = inject('cNavGroupSetActiveKey', undefined);
const parentOpenBranch = inject('cNavGroupOpenBranch', undefined);
const controlled = computed(() => props.visible !== undefined && hasUpdateListener);
const internal = ref(Boolean(props.visible));
const visible = computed(() => {
if (controlled.value) {
return props.visible;
}
if (parentActiveKey) {
return parentActiveKey.value === key;
}
return internal.value;
});
const activeGroupKey = ref();
provide('cNavGroupActiveKey', activeGroupKey);
provide('cNavGroupSetActiveKey', (value) => {
activeGroupKey.value = value;
});
const openBranch = () => {
parentSetActiveKey?.(key);
parentOpenBranch?.();
};
provide('cNavGroupOpenBranch', openBranch);
// Seed the accordion / local state from the `visible` prop: opens default-open groups and
// follows later changes (and keeps controlled groups in sync). Watching `props.visible`
// keeps this from re-running on accordion changes, so a default-open group can be collapsed
// manually.
watch(() => props.visible, (value) => {
if (value === undefined) {
return;
}
if (parentSetActiveKey) {
if (value) {
parentSetActiveKey(key);
}
else if (parentActiveKey && parentActiveKey.value === key) {
parentSetActiveKey(undefined);
}
}
else {
internal.value = value;
}
}, { immediate: true });
// Accordion: when another branch opens, a controlled group must close too. As its
// visibility is owned by the parent, request the change through `update:visible`.
watch(() => parentActiveKey?.value, (activeKey) => {
if (!controlled.value || !props.visible) {
return;
}
if (activeKey !== undefined && activeKey !== key) {
emit('update:visible', false);
}
});
// Animate the height of the always-mounted items, after the `show` class (driven straight
// from `visible`, so the toggler indicator reacts immediately). `display` is forced while
// collapsing to override the `.nav-group:not(.show) .nav-group-items { display: none }` rule.
watch(visible, (value) => {
emit('visible-change', value);
const el = navGroupItemsRef.value;
if (!el) {
return;
}
el.style.display = 'block';
// Each branch sets the starting height, forces a reflow by reading `offsetHeight`, then
// sets the target height. The reflow makes the browser commit the starting value, so the
// CSS height transition runs instead of both writes collapsing into a single frame.
if (value) {
el.style.height = '0px';
el.offsetHeight; // eslint-disable-line @typescript-eslint/no-unused-expressions
el.style.height = `${el.scrollHeight}px`;
executeAfterTransition(() => {
el.style.height = 'auto';
}, el);
}
else {
el.style.height = `${el.scrollHeight}px`;
el.offsetHeight; // eslint-disable-line @typescript-eslint/no-unused-expressions
el.style.height = '0px';
executeAfterTransition(() => {
el.style.display = '';
el.style.height = '';
}, el);
}
}, { flush: 'post' });
const handleTogglerClick = (event) => {
event.preventDefault();
const next = !visible.value;
emit('update:visible', next);
if (controlled.value) {
return;
}
if (parentSetActiveKey) {
parentSetActiveKey(next ? key : undefined);
}
else {
internal.value = next;
}
};
return () => h(props.as, {
class: ['nav-group', { show: visible.value }],
}, [
slots.togglerContent &&
h('a', {
'aria-expanded': visible.value,
class: ['nav-link', 'nav-group-toggle'],
href: '#',
onClick: handleTogglerClick,
}, slots.togglerContent && slots.togglerContent({ visible: visible.value })),
h(props.as === 'div' ? 'div' : 'ul', {
class: [
'nav-group-items',
{
compact: props.compact,
},
],
ref: navGroupItemsRef,
}, slots.default && slots.default()),
]);
},
});
export { CNavGroup };
//# sourceMappingURL=CNavGroup.js.map