UNPKG

vuetify

Version:

Vue.js 2 Semantic Component Framework

227 lines (203 loc) 5.41 kB
import '../../stylus/components/_menus.styl' import Vue from 'vue' // Mixins import Delayable from '../../mixins/delayable' import Dependent from '../../mixins/dependent' import Detachable from '../../mixins/detachable' import Menuable from '../../mixins/menuable.js' import Returnable from '../../mixins/returnable' import Toggleable from '../../mixins/toggleable' import Themeable from '../../mixins/themeable' // Component level mixins import Activator from './mixins/menu-activator' import Generators from './mixins/menu-generators' import Keyable from './mixins/menu-keyable' import Position from './mixins/menu-position' // Directives import ClickOutside from '../../directives/click-outside' import Resize from '../../directives/resize' // Helpers import { convertToUnit } from '../../util/helpers' import ThemeProvider from '../../util/ThemeProvider' /* @vue/component */ export default Vue.extend({ name: 'v-menu', provide () { return { // Pass theme through to default slot theme: this.theme } }, directives: { ClickOutside, Resize }, mixins: [ Activator, Dependent, Delayable, Detachable, Generators, Keyable, Menuable, Position, Returnable, Toggleable, Themeable ], props: { auto: Boolean, closeOnClick: { type: Boolean, default: true }, closeOnContentClick: { type: Boolean, default: true }, disabled: Boolean, fullWidth: Boolean, maxHeight: { default: 'auto' }, offsetX: Boolean, offsetY: Boolean, openOnClick: { type: Boolean, default: true }, openOnHover: Boolean, origin: { type: String, default: 'top left' }, transition: { type: [Boolean, String], default: 'v-menu-transition' } }, data () { return { defaultOffset: 8, hasJustFocused: false, resizeTimeout: null } }, computed: { calculatedLeft () { if (!this.auto) return this.calcLeft() return `${this.calcXOverflow(this.calcLeftAuto())}px` }, calculatedMaxHeight () { return this.auto ? '200px' : convertToUnit(this.maxHeight) }, calculatedMaxWidth () { return isNaN(this.maxWidth) ? this.maxWidth : `${this.maxWidth}px` }, calculatedMinWidth () { if (this.minWidth) { return isNaN(this.minWidth) ? this.minWidth : `${this.minWidth}px` } const minWidth = ( this.dimensions.activator.width + this.nudgeWidth + (this.auto ? 16 : 0) ) const calculatedMaxWidth = isNaN(parseInt(this.calculatedMaxWidth)) ? minWidth : parseInt(this.calculatedMaxWidth) return `${Math.min( calculatedMaxWidth, minWidth )}px` }, calculatedTop () { if (!this.auto || this.isAttached) return this.calcTop() return `${this.calcYOverflow(this.calculatedTopAuto)}px` }, styles () { return { maxHeight: this.calculatedMaxHeight, minWidth: this.calculatedMinWidth, maxWidth: this.calculatedMaxWidth, top: this.calculatedTop, left: this.calculatedLeft, transformOrigin: this.origin, zIndex: this.zIndex || this.activeZIndex } } }, watch: { activator (newActivator, oldActivator) { this.removeActivatorEvents(oldActivator) this.addActivatorEvents(newActivator) }, isContentActive (val) { this.hasJustFocused = val } }, methods: { activate () { // This exists primarily for v-select // helps determine which tiles to activate this.getTiles() // Update coordinates and dimensions of menu // and its activator this.updateDimensions() // Start the transition requestAnimationFrame(() => { // Once transitioning, calculate scroll and top position this.startTransition().then(() => { if (this.$refs.content) { this.calculatedTopAuto = this.calcTopAuto() this.auto && (this.$refs.content.scrollTop = this.calcScrollPosition()) } }) }) }, closeConditional () { return this.isActive && this.closeOnClick }, onResize () { if (!this.isActive) return // Account for screen resize // and orientation change // eslint-disable-next-line no-unused-expressions this.$refs.content.offsetWidth this.updateDimensions() // When resizing to a smaller width // content width is evaluated before // the new activator width has been // set, causing it to not size properly // hacky but will revisit in the future clearTimeout(this.resizeTimeout) this.resizeTimeout = setTimeout(this.updateDimensions, 100) } }, render (h) { const data = { staticClass: 'v-menu', class: { 'v-menu--inline': !this.fullWidth && this.$slots.activator }, directives: [{ arg: 500, name: 'resize', value: this.onResize }], on: { keydown: this.onKeyDown } } return h('div', data, [ this.genActivator(), this.$createElement(ThemeProvider, { props: { root: true, light: this.light, dark: this.dark } }, [this.genTransition()]) ]) } })