<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}%)` }" > <input v-model="valueA" :min="min" :max="max" :data-value="valueA" :disabled="disabled" type="range" class="slider" @change="emit" > <input v-model="valueB" :min="min" :max="max" :data-value="valueB" :disabled="disabled" type="range" class="slider" @change="emit" > </div> <div class="label label-end" :class="{ disabled }" @click="setValue('valueB', max)" > <slot name="end" /> </div> </div> </template> <script> function minValue() { return Math.min(this.valueA, this.valueB); } function maxValue() { return Math.max(this.valueA, this.valueB); } function minPercentage() { return (this.minValue / this.max) * 100; } function maxPercentage() { return (this.maxValue / this.max) * 100; } function emit() { if (this.values) { this.$emit('change', [this.values[this.minValue], this.values[this.maxValue]]); return; } this.$emit('change', [this.minValue, this.maxValue]); } function setValue(prop, value) { 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, }, }, emits: ['change'], 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, setValue, }, }; </script> <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.5rem; height: 1.5rem; flex-shrink: 0; fill: var(--shadow); } </style>