UNPKG

@coreui/vue

Version:

UI Components Library for Vue.js

157 lines (154 loc) 6.38 kB
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