Multi range
This component combines two Range components, adds some extra CSS for the inverted one (which sits on top of the other) and takes min/max values as "boundaries" and uses toDate/fromDate as model values.
Live example
Important
The preview above is not rendered in isolation from the theme of this documentation which means that some styling inheritance from it may occur. For a neutral, UI framework-only look, please see the HTML preview of each CSS component in use.
Usage
vue
<multi-range
:min="`2024-01-01`"
:max="`2024-12-31`"
v-model:from-date="from1"
v-model:to-date="to1"
@updated="console.log('updated multi range 1')"></multi-range>
html
<multi-range
:min="`2024-01-01`"
:max="`2024-12-31`"
v-model:from-date="from2"
v-model:to-date="to2"
@updated="console.log('updated multi range 2')">
<div class="o-grid o-grid--bleed date-span">
<div class="o-grid__col-6">
<div class="c-popup c-popup--top-left u-box-shadow--none">
<div class="c-popup__item c-form-control">
<label for="from-date" class="c-label">From date</label>
<input id="from-date" type="date" v-model="from2" /> <!-- :min and :max props must be added here, as milliseconds, to enable dynamic data boundaries on the datepicker as well -->
</div>
</div>
</div>
<div class="o-grid__col-6">
<div class="c-popup c-popup--top-right u-box-shadow--none">
<div class="c-popup__item c-form-control">
<label for="to-date" class="c-label">To date</label>
<input id="to-date" type="date" v-model="to2" /> <!-- :min and :max props must be added here, as milliseconds, to enable dynamic data boundaries on the datepicker as well -->
</div>
</div>
</div>
</div>
</multi-range>
Component source
vue
<script setup>
import { computed, watch } from 'vue'
const emit = defineEmits(["updated"]);
const props = defineProps({
min: { type: String, required: true },
max: { type: String, required: true },
});
const fromDate = defineModel('fromDate', { type: String, required: true });
const toDate = defineModel('toDate', { type: String, required: true });
const stepAsDays = 1000 * 60 * 60 * 24;
const minDateAsNumber = computed(() => Number(new Date(props.min).getTime()));
const maxDateAsNumber = computed(() => Number(new Date(props.max).getTime()));
const fromDateAsNumber = computed({
get: () => Number(new Date(fromDate.value).getTime()),
set: (value) => {
fromDate.value = dateAsYyyyMmDd(value);
}
});
const toDateAsNumber = computed({
get: () => Number(new Date(toDate.value).getTime()),
set: (value) => {
toDate.value = dateAsYyyyMmDd(value);
}
});
let isFirstLoad = true;
function dateAsYyyyMmDd(dateInMs) {
if (!dateInMs)
return
return new Date(parseInt(dateInMs)).toJSON().substring(0, 10);
}
// Make sure we're in valid date constraints
watch(fromDate, (val) => {
if (!isFirstLoad)
emit("updated", fromDate.value, toDate.value);
// If the from date is greater than the to date, set the to date to the from date
if (val > toDate.value)
fromDate.value = toDate.value;
if (val != props.min)
isFirstLoad = false;
});
watch(toDate, (val) => {
if (!isFirstLoad)
emit("updated", fromDate.value, toDate.value);
// If the to date is less than the from date, set the from date to the to date
if (val < fromDate.value)
toDate.value = fromDate.value;
if (val != props.max)
isFirstLoad = false;
});
</script>
<template>
<div class="c-multi-range">
<input type="range" :min="minDateAsNumber" :max="maxDateAsNumber" :step="stepAsDays" v-model="fromDateAsNumber" class="c-range c-range--inverted" :class="{ 'c-range--min': toDateAsNumber === minDateAsNumber }" :style="`--min: ${minDateAsNumber}; --val: ${fromDateAsNumber}; --max: ${maxDateAsNumber}` " />
<input type="range" :min="minDateAsNumber" :max="maxDateAsNumber" :step="stepAsDays" v-model="toDateAsNumber" class="c-range" :style="`--min: ${minDateAsNumber}; --val: ${toDateAsNumber}; --max: ${maxDateAsNumber}`" />
<slot></slot>
</div>
</template>