246 lines
		
	
	
		
			4.2 KiB
		
	
	
	
		
			Vue
		
	
	
		
			Executable File
		
	
	
			
		
		
	
	
			246 lines
		
	
	
		
			4.2 KiB
		
	
	
	
		
			Vue
		
	
	
		
			Executable File
		
	
	
| <template>
 | |
| 	<div class="range-container">
 | |
| 		<div
 | |
| 			class="label label-start"
 | |
| 			:class="{ disabled }"
 | |
| 			@click="setValue('valueA', min)"
 | |
| 		>
 | |
| 			<slot name="start" />
 | |
| 		</div>
 | |
| 
 | |
| 		<div
 | |
| 			class="range"
 | |
| 			:class="{ disabled }"
 | |
| 			:style="{ background: `linear-gradient(90deg, var(--slider-track) ${minPercentage}%, var(--slider-range) ${minPercentage}%, var(--slider-range) ${maxPercentage}%, var(--slider-track) ${maxPercentage}%)` }"
 | |
| 			@click="setNearest"
 | |
| 		>
 | |
| 			<input
 | |
| 				v-model.number="valueA"
 | |
| 				:min="min"
 | |
| 				:max="max"
 | |
| 				:data-value="valueA"
 | |
| 				:disabled="disabled"
 | |
| 				type="range"
 | |
| 				class="slider"
 | |
| 				@input="emit('input')"
 | |
| 				@change="emit('change')"
 | |
| 				@click.stop
 | |
| 			>
 | |
| 
 | |
| 			<input
 | |
| 				v-model.number="valueB"
 | |
| 				:min="min"
 | |
| 				:max="max"
 | |
| 				:data-value="valueB"
 | |
| 				:disabled="disabled"
 | |
| 				type="range"
 | |
| 				class="slider"
 | |
| 				@input="emit('input')"
 | |
| 				@change="emit('change')"
 | |
| 				@click.stop
 | |
| 			>
 | |
| 		</div>
 | |
| 
 | |
| 		<div
 | |
| 			class="label label-end"
 | |
| 			:class="{ disabled }"
 | |
| 			@click="setValue('valueB', max)"
 | |
| 		>
 | |
| 			<slot name="end" />
 | |
| 		</div>
 | |
| 	</div>
 | |
| </template>
 | |
| 
 | |
| <script>
 | |
| import { nextTick } from 'vue';
 | |
| 
 | |
| function minValue() {
 | |
| 	return Math.min(this.valueA, this.valueB);
 | |
| }
 | |
| 
 | |
| function maxValue() {
 | |
| 	return Math.max(this.valueA, this.valueB);
 | |
| }
 | |
| 
 | |
| function minPercentage() {
 | |
| 	return ((this.minValue - this.min) / (this.max - this.min)) * 100;
 | |
| }
 | |
| 
 | |
| function maxPercentage() {
 | |
| 	return ((this.maxValue - this.min) / (this.max - this.min)) * 100;
 | |
| }
 | |
| 
 | |
| function emit(type = 'change') {
 | |
| 	if (this.values) {
 | |
| 		this.$emit(type, [this.values[this.minValue], this.values[this.maxValue]]);
 | |
| 		return;
 | |
| 	}
 | |
| 
 | |
| 	this.$emit(type, [this.minValue, this.maxValue]);
 | |
| }
 | |
| 
 | |
| function setNearest(event) {
 | |
| 	if (this.allowEnable) {
 | |
| 		this.emit('enable');
 | |
| 	}
 | |
| 
 | |
| 	nextTick(() => {
 | |
| 		if (!this.disabled) {
 | |
| 			const closestValue = Math.round((event.offsetX / event.target.getBoundingClientRect().width) * (this.max - this.min)) + this.min;
 | |
| 			const closestSlider = Math.abs(this.valueA - closestValue) < Math.abs(this.valueB - closestValue) ? 'valueA' : 'valueB';
 | |
| 
 | |
| 			this[closestSlider] = closestValue;
 | |
| 			this.emit();
 | |
| 		}
 | |
| 	});
 | |
| }
 | |
| 
 | |
| function setValue(prop, value) {
 | |
| 	if (this.allowEnable) {
 | |
| 		this.emit('enable');
 | |
| 	}
 | |
| 
 | |
| 	nextTick(() => {
 | |
| 		if (!this.disabled) {
 | |
| 			this[prop] = value;
 | |
| 			this.emit();
 | |
| 		}
 | |
| 	});
 | |
| }
 | |
| 
 | |
| export default {
 | |
| 	props: {
 | |
| 		min: {
 | |
| 			type: Number,
 | |
| 			default: 0,
 | |
| 		},
 | |
| 		max: {
 | |
| 			type: Number,
 | |
| 			default: 10,
 | |
| 		},
 | |
| 		value: {
 | |
| 			type: Array,
 | |
| 			default: () => [3, 7],
 | |
| 		},
 | |
| 		values: {
 | |
| 			type: Array,
 | |
| 			default: null,
 | |
| 		},
 | |
| 		disabled: {
 | |
| 			type: Boolean,
 | |
| 			default: false,
 | |
| 		},
 | |
| 		allowEnable: {
 | |
| 			type: Boolean,
 | |
| 			default: true,
 | |
| 		},
 | |
| 	},
 | |
| 	emits: ['change', 'input', 'enable'],
 | |
| 	data() {
 | |
| 		if (this.values) {
 | |
| 			return {
 | |
| 				valueA: this.values.indexOf(this.value[0]),
 | |
| 				valueB: this.values.indexOf(this.value[1]),
 | |
| 			};
 | |
| 		}
 | |
| 
 | |
| 		return {
 | |
| 			valueA: this.value[0],
 | |
| 			valueB: this.value[1],
 | |
| 		};
 | |
| 	},
 | |
| 	computed: {
 | |
| 		minValue,
 | |
| 		maxValue,
 | |
| 		minPercentage,
 | |
| 		maxPercentage,
 | |
| 	},
 | |
| 	methods: {
 | |
| 		emit,
 | |
| 		setNearest,
 | |
| 		setValue,
 | |
| 	},
 | |
| };
 | |
| </script>
 | |
| 
 | |
| <style lang="scss">
 | |
| .dark .range-container .range {
 | |
| 	--slider-range: var(--lighten-weak);
 | |
| }
 | |
| </style>
 | |
| 
 | |
| <style lang="scss" scoped>
 | |
| @mixin thumb {
 | |
| 	appearance: none;
 | |
| 	display: block;
 | |
| 	width: 1.25rem;
 | |
| 	height: 1.25rem;
 | |
| 	border: none;
 | |
| 	border-radius: 50%;
 | |
| 	background: var(--slider-thumb);
 | |
| 	pointer-events: visible;
 | |
| 	cursor: pointer;
 | |
| 	box-shadow: 0 0 3px var(--darken-weak);
 | |
| }
 | |
| 
 | |
| .range-container {
 | |
| 	display: flex;
 | |
| 	justify-content: space-between;
 | |
| }
 | |
| 
 | |
| .range {
 | |
| 	--slider-track: var(--shadow-hint);
 | |
| 	--slider-range: var(--primary-faded);
 | |
| 	--slider-thumb: var(--primary);
 | |
| 
 | |
| 	position: relative;
 | |
| 	height: 1.25rem;
 | |
| 	flex-grow: 1;
 | |
| 	border-radius: .625rem;
 | |
| 
 | |
| 	&.disabled {
 | |
| 		--slider-range: var(--shadow-weak);
 | |
| 		--slider-thumb: var(--disabled-handle);
 | |
| 	}
 | |
| }
 | |
| 
 | |
| .slider {
 | |
| 	width: 100%;
 | |
| 	top: 0;
 | |
| 	margin: 0;
 | |
| 	appearance: none;
 | |
| 	position: absolute;
 | |
| 	background: none;
 | |
| 	outline: none;
 | |
| 	pointer-events: none;
 | |
| }
 | |
| 
 | |
| .slider::-webkit-slider-thumb {
 | |
| 	@include thumb;
 | |
| }
 | |
| 
 | |
| .slider::-moz-range-thumb {
 | |
| 	@include thumb;
 | |
| 	transform: translateY(2px);
 | |
| }
 | |
| 
 | |
| .label {
 | |
| 	padding: 0 .5rem;
 | |
| 
 | |
| 	&:hover:not(.disabled) {
 | |
| 		cursor: pointer;
 | |
| 
 | |
| 		::v-deep(.icon) {
 | |
| 			fill: var(--primary);
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| ::v-deep(.icon) {
 | |
| 	width: 1.25rem;
 | |
| 	height: 1.25rem;
 | |
| 	flex-shrink: 0;
 | |
| 	fill: var(--shadow);
 | |
| }
 | |
| </style>
 |