UNPKG

quasar

Version:

Build high-performance VueJS user interfaces (SPA, PWA, SSR, Mobile and Desktop) in record time

370 lines (302 loc) 9.73 kB
import { h, shallowReactive, ref, computed, watch, withDirectives, getCurrentInstance, vShow, onBeforeUnmount } from 'vue' import QItem from '../item/QItem.js' import QItemSection from '../item/QItemSection.js' import QItemLabel from '../item/QItemLabel.js' import QIcon from '../icon/QIcon.js' import QSlideTransition from '../slide-transition/QSlideTransition.js' import QSeparator from '../separator/QSeparator.js' import useDark, { useDarkProps } from '../../composables/private/use-dark.js' import { useRouterLinkProps } from '../../composables/private/use-router-link.js' import useModelToggle, { useModelToggleProps, useModelToggleEmits } from '../../composables/private/use-model-toggle.js' import { createComponent } from '../../utils/private/create.js' import { stopAndPrevent } from '../../utils/event.js' import { hSlot } from '../../utils/private/render.js' import uid from '../../utils/uid.js' const itemGroups = shallowReactive({}) const LINK_PROPS = Object.keys(useRouterLinkProps) export default createComponent({ name: 'QExpansionItem', props: { ...useRouterLinkProps, ...useModelToggleProps, ...useDarkProps, icon: String, label: String, labelLines: [ Number, String ], caption: String, captionLines: [ Number, String ], dense: Boolean, toggleAriaLabel: String, expandIcon: String, expandedIcon: String, expandIconClass: [ Array, String, Object ], duration: Number, headerInsetLevel: Number, contentInsetLevel: Number, expandSeparator: Boolean, defaultOpened: Boolean, hideExpandIcon: Boolean, expandIconToggle: Boolean, switchToggleSide: Boolean, denseToggle: Boolean, group: String, popup: Boolean, headerStyle: [ Array, String, Object ], headerClass: [ Array, String, Object ] }, emits: [ ...useModelToggleEmits, 'click', 'afterShow', 'afterHide' ], setup (props, { slots, emit }) { const { proxy: { $q } } = getCurrentInstance() const isDark = useDark(props, $q) const showing = ref( props.modelValue !== null ? props.modelValue : props.defaultOpened ) const blurTargetRef = ref(null) const targetUid = uid() const { show, hide, toggle } = useModelToggle({ showing }) let uniqueId, exitGroup const classes = computed(() => 'q-expansion-item q-item-type' + ` q-expansion-item--${ showing.value === true ? 'expanded' : 'collapsed' }` + ` q-expansion-item--${ props.popup === true ? 'popup' : 'standard' }` ) const contentStyle = computed(() => { if (props.contentInsetLevel === void 0) { return null } const dir = $q.lang.rtl === true ? 'Right' : 'Left' return { [ 'padding' + dir ]: (props.contentInsetLevel * 56) + 'px' } }) const hasLink = computed(() => props.disable !== true && ( props.href !== void 0 || (props.to !== void 0 && props.to !== null && props.to !== '') ) ) const linkProps = computed(() => { const acc = {} LINK_PROPS.forEach(key => { acc[ key ] = props[ key ] }) return acc }) const isClickable = computed(() => hasLink.value === true || props.expandIconToggle !== true ) const expansionIcon = computed(() => ( props.expandedIcon !== void 0 && showing.value === true ? props.expandedIcon : props.expandIcon || $q.iconSet.expansionItem[ props.denseToggle === true ? 'denseIcon' : 'icon' ] )) const activeToggleIcon = computed(() => props.disable !== true && (hasLink.value === true || props.expandIconToggle === true) ) const headerSlotScope = computed(() => ({ expanded: showing.value === true, detailsId: props.targetUid, toggle, show, hide })) const toggleAriaAttrs = computed(() => { const toggleAriaLabel = props.toggleAriaLabel !== void 0 ? props.toggleAriaLabel : $q.lang.label[ showing.value === true ? 'collapse' : 'expand' ](props.label) return { role: 'button', 'aria-expanded': showing.value === true ? 'true' : 'false', 'aria-controls': targetUid, 'aria-label': toggleAriaLabel } }) watch(() => props.group, name => { exitGroup !== void 0 && exitGroup() name !== void 0 && enterGroup() }) function onHeaderClick (e) { hasLink.value !== true && toggle(e) emit('click', e) } function toggleIconKeyboard (e) { e.keyCode === 13 && toggleIcon(e, true) } function toggleIcon (e, keyboard) { keyboard !== true && blurTargetRef.value !== null && blurTargetRef.value.focus() toggle(e) stopAndPrevent(e) } function onShow () { emit('afterShow') } function onHide () { emit('afterHide') } function enterGroup () { if (uniqueId === void 0) { uniqueId = uid() } if (showing.value === true) { itemGroups[ props.group ] = uniqueId } const show = watch(showing, val => { if (val === true) { itemGroups[ props.group ] = uniqueId } else if (itemGroups[ props.group ] === uniqueId) { delete itemGroups[ props.group ] } }) const group = watch( () => itemGroups[ props.group ], (val, oldVal) => { if (oldVal === uniqueId && val !== void 0 && val !== uniqueId) { hide() } } ) exitGroup = () => { show() group() if (itemGroups[ props.group ] === uniqueId) { delete itemGroups[ props.group ] } exitGroup = void 0 } } function getToggleIcon () { const data = { class: [ 'q-focusable relative-position cursor-pointer' + `${ props.denseToggle === true && props.switchToggleSide === true ? ' items-end' : '' }`, props.expandIconClass ], side: props.switchToggleSide !== true, avatar: props.switchToggleSide } const child = [ h(QIcon, { class: 'q-expansion-item__toggle-icon' + (props.expandedIcon === void 0 && showing.value === true ? ' q-expansion-item__toggle-icon--rotated' : ''), name: expansionIcon.value }) ] if (activeToggleIcon.value === true) { Object.assign(data, { tabindex: 0, ...toggleAriaAttrs.value, onClick: toggleIcon, onKeyup: toggleIconKeyboard }) child.unshift( h('div', { ref: blurTargetRef, class: 'q-expansion-item__toggle-focus q-icon q-focus-helper q-focus-helper--rounded', tabindex: -1 }) ) } return h(QItemSection, data, () => child) } function getHeaderChild () { let child if (slots.header !== void 0) { child = [].concat(slots.header(headerSlotScope.value)) } else { child = [ h(QItemSection, () => [ h(QItemLabel, { lines: props.labelLines }, () => props.label || ''), props.caption ? h(QItemLabel, { lines: props.captionLines, caption: true }, () => props.caption) : null ]) ] props.icon && child[ props.switchToggleSide === true ? 'push' : 'unshift' ]( h(QItemSection, { side: props.switchToggleSide === true, avatar: props.switchToggleSide !== true }, () => h(QIcon, { name: props.icon })) ) } if (props.disable !== true && props.hideExpandIcon !== true) { child[ props.switchToggleSide === true ? 'unshift' : 'push' ]( getToggleIcon() ) } return child } function getHeader () { const data = { ref: 'item', style: props.headerStyle, class: props.headerClass, dark: isDark.value, disable: props.disable, dense: props.dense, insetLevel: props.headerInsetLevel } if (isClickable.value === true) { data.clickable = true data.onClick = onHeaderClick Object.assign( data, hasLink.value === true ? linkProps.value : toggleAriaAttrs.value ) } return h(QItem, data, getHeaderChild) } function getTransitionChild () { return withDirectives( h('div', { key: 'e-content', class: 'q-expansion-item__content relative-position', style: contentStyle.value, id: targetUid }, hSlot(slots.default)), [ [ vShow, showing.value ] ] ) } function getContent () { const node = [ getHeader(), h(QSlideTransition, { duration: props.duration, onShow, onHide }, getTransitionChild) ] if (props.expandSeparator === true) { node.push( h(QSeparator, { class: 'q-expansion-item__border q-expansion-item__border--top absolute-top', dark: isDark.value }), h(QSeparator, { class: 'q-expansion-item__border q-expansion-item__border--bottom absolute-bottom', dark: isDark.value }) ) } return node } props.group !== void 0 && enterGroup() onBeforeUnmount(() => { exitGroup !== void 0 && exitGroup() }) return () => h('div', { class: classes.value }, [ h('div', { class: 'q-expansion-item__container relative-position' }, getContent()) ]) } })