<template> <div @click="toggle" ref="container" class="sn-select" :class="{ 'sn-select--open': isOpen, 'sn-select--blank': !valueLabel, 'disabled': disabled }"> <slot> <button ref="focusElement" class="sn-select__value"> <span>{{ valueLabel || (placeholder || i18n.t('general.select')) }}</span> </button> <i class="sn-icon" :class="{ 'sn-icon-down': !isOpen, 'sn-icon-up': isOpen}"></i> </slot> <perfect-scrollbar ref="optionsContainer" :style="optionPositionStyle" class="sn-select__options scroll-container" > <template v-if="options.length"> <div v-for="option in options" :key="option[0]" @mousedown.prevent.stop="setValue(option[0])" class="sn-select__option" > {{ option[1] }} </div> </template> <template v-else> <div class="sn-select__no-options" > {{ this.noOptionsPlaceholder }} </div> </template> </perfect-scrollbar> </div> </template> <script> import PerfectScrollbar from 'vue2-perfect-scrollbar'; export default { name: 'Select', props: { value: { type: [String, Number] }, options: { type: Array, default: () => [] }, initialValue: { type: [String, Number] }, placeholder: { type: String }, noOptionsPlaceholder: { type: String }, disabled: { type: Boolean, default: false } }, comments: { PerfectScrollbar }, data() { return { isOpen: false, optionPositionStyle: '', blurPrevented: false } }, computed: { valueLabel() { let option = this.options.find((o) => o[0] === this.value); return option && option[1]; }, focusElement() { return this.$refs.focusElement || this.$scopedSlots.default()[0].context.$refs.focusElement; } }, mounted() { this.focusElement.onblur = this.blur; document.addEventListener('scroll', this.updateOptionPosition); }, beforeDestroy() { document.removeEventListener('scroll', this.updateOptionPosition); }, methods: { preventBlur() { this.blurPrevented = true; }, allowBlur() { setTimeout(() => { this.blurPrevented = false }, 200); }, blur() { setTimeout(() => { if (this.blurPrevented) { this.focusElement.focus(); } else { this.isOpen = false; this.$emit('blur'); } }, 100); }, toggle() { if (this.isOpen && this.blurPrevented) { return; } this.isOpen = !this.isOpen; if (this.isOpen) { this.$emit('open'); this.$nextTick(() => { this.focusElement.focus(); }); this.$refs.optionsContainer.scrollTop = 0; this.updateOptionPosition(); this.setUpBlurHandlers(); } else { this.optionPositionStyle = ''; this.$emit('close'); } }, setValue(value) { this.$emit('change', value); }, updateOptionPosition() { const container = $(this.$refs.container); const rect = container.get(0).getBoundingClientRect(); let width = rect.width; let top = rect.top + rect.height; let left = rect.left; const modal = container.parents('.modal-content'); if (modal.length > 0) { const modalRect = modal.get(0).getBoundingClientRect(); top -= modalRect.top; left -= modalRect.left; } this.optionPositionStyle = `position: fixed; top: ${top}px; left: ${left}px; width: ${width}px` }, setUpBlurHandlers() { setTimeout(() => { this.$refs.optionsContainer.$el.querySelector('.ps__thumb-y').addEventListener('mousedown', this.preventBlur); this.$refs.optionsContainer.$el.querySelector('.ps__thumb-y').addEventListener('mouseup', this.allowBlur); document.addEventListener('mouseup', this.allowBlur); }, 100); } } } </script>