2023-05-03 18:37:14 +08:00
|
|
|
<template>
|
2023-11-09 19:40:06 +08:00
|
|
|
<div v-click-outside="close" @click="toggle" ref="container" class="sn-select" :class="{ 'sn-select--open': isOpen, 'sn-select--blank': !valueLabel, 'disabled': disabled }">
|
2023-05-03 18:37:14 +08:00
|
|
|
<slot>
|
|
|
|
<button ref="focusElement" class="sn-select__value">
|
|
|
|
<span>{{ valueLabel || (placeholder || i18n.t('general.select')) }}</span>
|
|
|
|
</button>
|
2023-06-13 17:51:00 +08:00
|
|
|
<i class="sn-icon" :class="{ 'sn-icon-down': !isOpen, 'sn-icon-up': isOpen}"></i>
|
2023-05-03 18:37:14 +08:00
|
|
|
</slot>
|
2023-11-13 21:13:53 +08:00
|
|
|
<div :style="optionPositionStyle" class="py-2.5 bg-white z-10 shadow-sn-menu-sm" :class="{ 'hidden': !isOpen }">
|
|
|
|
<div v-if="withClearButton" class="px-2 pb-2.5">
|
2023-10-24 21:49:37 +08:00
|
|
|
<div @mousedown.prevent.stop="setValue(null)"
|
2023-11-07 19:39:07 +08:00
|
|
|
class="btn btn-light !text-xs active:bg-sn-super-light-blue"
|
2023-10-24 21:49:37 +08:00
|
|
|
:class="{
|
2023-11-07 19:39:07 +08:00
|
|
|
'disabled cursor-default': !value,
|
|
|
|
'cursor-pointer': value,
|
2023-10-24 21:49:37 +08:00
|
|
|
}">
|
|
|
|
{{ i18n.t('general.clear') }}
|
2023-05-24 16:47:27 +08:00
|
|
|
</div>
|
2023-09-22 17:59:39 +08:00
|
|
|
</div>
|
2023-11-13 21:13:53 +08:00
|
|
|
<perfect-scrollbar ref="optionsContainer"
|
|
|
|
class="sn-select__options !relative !top-0 !left-[-1px] !shadow-none scroll-container px-2.5 pt-0 block"
|
|
|
|
:class="{ [optionsClassName]: true }"
|
|
|
|
>
|
|
|
|
|
|
|
|
<div v-if="options.length" class="flex flex-col gap-[1px]">
|
|
|
|
<div
|
|
|
|
v-for="option in options"
|
|
|
|
:key="option[0]"
|
|
|
|
@mousedown.prevent.stop="setValue(option[0])"
|
|
|
|
class="sn-select__option p-3 rounded shadow-none"
|
|
|
|
:title="option[1]"
|
|
|
|
:class="{
|
|
|
|
'select__option-placeholder': option[2],
|
|
|
|
'!bg-sn-super-light-blue': option[0] == value,
|
|
|
|
}"
|
|
|
|
>
|
|
|
|
{{ option[1] }}
|
|
|
|
</div>
|
2023-05-24 16:47:27 +08:00
|
|
|
</div>
|
2023-11-13 21:13:53 +08:00
|
|
|
<template v-else>
|
|
|
|
<div
|
|
|
|
class="sn-select__no-options"
|
|
|
|
>
|
|
|
|
{{ this.noOptionsPlaceholder }}
|
|
|
|
</div>
|
|
|
|
</template>
|
|
|
|
</perfect-scrollbar>
|
|
|
|
</div>
|
2023-05-03 18:37:14 +08:00
|
|
|
</div>
|
|
|
|
</template>
|
|
|
|
<script>
|
2023-11-09 19:40:06 +08:00
|
|
|
import { vOnClickOutside } from '@vueuse/components'
|
2023-06-13 17:51:00 +08:00
|
|
|
|
2023-05-03 18:37:14 +08:00
|
|
|
export default {
|
|
|
|
name: 'Select',
|
|
|
|
props: {
|
2023-10-24 21:49:37 +08:00
|
|
|
withClearButton: { type: Boolean, default: false },
|
|
|
|
withEditCursor: { type: Boolean, default: false },
|
2023-05-18 01:01:16 +08:00
|
|
|
value: { type: [String, Number] },
|
2023-05-03 18:37:14 +08:00
|
|
|
options: { type: Array, default: () => [] },
|
|
|
|
initialValue: { type: [String, Number] },
|
|
|
|
placeholder: { type: String },
|
2023-05-24 16:47:27 +08:00
|
|
|
noOptionsPlaceholder: { type: String },
|
2023-10-27 16:31:38 +08:00
|
|
|
className: { type: String, default: '' },
|
2023-11-13 21:13:53 +08:00
|
|
|
optionsClassName: { type: String, default: '' },
|
2023-05-03 18:37:14 +08:00
|
|
|
disabled: { type: Boolean, default: false }
|
|
|
|
},
|
2023-10-13 22:12:01 +08:00
|
|
|
directives: {
|
2023-11-09 19:40:06 +08:00
|
|
|
'click-outside': vOnClickOutside
|
2023-10-13 22:12:01 +08:00
|
|
|
},
|
2023-05-03 18:37:14 +08:00
|
|
|
data() {
|
|
|
|
return {
|
|
|
|
isOpen: false,
|
2023-10-13 22:12:01 +08:00
|
|
|
optionPositionStyle: ''
|
2023-05-03 18:37:14 +08:00
|
|
|
}
|
|
|
|
},
|
|
|
|
computed: {
|
|
|
|
valueLabel() {
|
|
|
|
let option = this.options.find((o) => o[0] === this.value);
|
2023-09-01 17:37:48 +08:00
|
|
|
return option && option[1];
|
2023-05-03 18:37:14 +08:00
|
|
|
},
|
|
|
|
focusElement() {
|
|
|
|
return this.$refs.focusElement || this.$scopedSlots.default()[0].context.$refs.focusElement;
|
|
|
|
}
|
|
|
|
},
|
|
|
|
mounted() {
|
2023-07-06 16:31:26 +08:00
|
|
|
document.addEventListener('scroll', this.updateOptionPosition);
|
|
|
|
},
|
2023-11-09 02:32:43 +08:00
|
|
|
beforeUnmount() {
|
2023-07-06 16:31:26 +08:00
|
|
|
document.removeEventListener('scroll', this.updateOptionPosition);
|
2023-05-03 18:37:14 +08:00
|
|
|
},
|
|
|
|
methods: {
|
|
|
|
toggle() {
|
|
|
|
this.isOpen = !this.isOpen;
|
|
|
|
|
|
|
|
if (this.isOpen) {
|
|
|
|
this.$emit('open');
|
|
|
|
this.$nextTick(() => {
|
|
|
|
this.focusElement.focus();
|
|
|
|
});
|
|
|
|
this.$refs.optionsContainer.scrollTop = 0;
|
|
|
|
this.updateOptionPosition();
|
|
|
|
} else {
|
2023-10-13 22:12:01 +08:00
|
|
|
this.close()
|
2023-05-03 18:37:14 +08:00
|
|
|
}
|
|
|
|
},
|
2023-10-13 22:12:01 +08:00
|
|
|
close() {
|
|
|
|
this.isOpen = false;
|
|
|
|
this.optionPositionStyle = '';
|
|
|
|
this.$emit('close');
|
|
|
|
},
|
2023-05-03 18:37:14 +08:00
|
|
|
setValue(value) {
|
2023-05-18 01:01:16 +08:00
|
|
|
this.$emit('change', value);
|
2023-05-03 18:37:14 +08:00
|
|
|
},
|
|
|
|
updateOptionPosition() {
|
2023-05-26 17:41:47 +08:00
|
|
|
const container = $(this.$refs.container);
|
|
|
|
const rect = container.get(0).getBoundingClientRect();
|
2023-09-24 20:10:53 +08:00
|
|
|
const screenHeight = window.innerHeight;
|
2023-05-08 17:05:22 +08:00
|
|
|
let width = rect.width;
|
2023-10-24 21:49:37 +08:00
|
|
|
let height = rect.height;
|
2023-05-08 17:05:22 +08:00
|
|
|
let top = rect.top + rect.height;
|
2023-09-24 20:10:53 +08:00
|
|
|
let bottom = screenHeight - rect.bottom + rect.height;
|
2023-05-08 17:05:22 +08:00
|
|
|
let left = rect.left;
|
2023-05-03 18:37:14 +08:00
|
|
|
|
2023-05-26 17:41:47 +08:00
|
|
|
const modal = container.parents('.modal-content');
|
2023-05-08 17:05:22 +08:00
|
|
|
|
|
|
|
if (modal.length > 0) {
|
|
|
|
const modalRect = modal.get(0).getBoundingClientRect();
|
|
|
|
top -= modalRect.top;
|
2023-09-24 20:10:53 +08:00
|
|
|
bottom -= modalRect.bottom;
|
2023-05-08 17:05:22 +08:00
|
|
|
left -= modalRect.left;
|
2023-10-24 21:49:37 +08:00
|
|
|
this.optionPositionStyle = `position: fixed; top: ${top}px; left: ${left}px; width: ${width}px`
|
|
|
|
} else {
|
|
|
|
container.addClass('relative');
|
|
|
|
this.optionPositionStyle = `position: absolute; top: ${height}px; left: 0px; width: ${width}px`
|
2023-05-08 17:05:22 +08:00
|
|
|
}
|
2023-05-03 18:37:14 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
</script>
|