Input Range v1.0
The <e-input-range> component is a component that can be used for .
Usage
An e-input-range has attributes that must be provided,
<e-input-range
id=""
value=""
required=""
/>
Attributes
id | Type: String | required
label | Type: String | Default:''
options | Type: Array | Default:selectOptions
Examples
Below a few interactive examples of unavailable can be found.
Default
<e-input-range
id="rangeInputWithOptions"
:value="'40-60'"
/>
40-60
Labelled, label='textInput with Label'
<div class="input-example-field">
<e-input-label
:id="'rangeInputWithLabel'"
:value="'rangeInput with Label'"
/>
<e-input-range
:id="'rangeInputWithLabel'"
:value="'40-60'"
/>
</div>
40-60
Source Code
e-input-range.vue
<script lang="ts" setup>
import { computed, ref, watch } from 'vue';
import { isEmpty } from 'lodash-es';
import { EInput_Range } from 'types/e-input/interfaces/EInput_Range';
export type Props = EInput_Range;
const props = withDefaults(defineProps<Props>(), {
label: '',
id: '',
required: false,
meta: () => {
return {
disabled: false,
max: 100,
min: 0,
steps: 1
};
}
});
const emit = defineEmits<{
(e: 'update:value', value: string): void;
}>();
const focus = ref('');
const localValue = ref(props.value || '10-90');
const minRangeValue = computed(() => {
return Number.parseInt(localValue.value.split('-')[0] || '0');
});
const maxRangeValue = computed(() => {
return Number.parseInt(localValue.value.split('-')[1] || '0');
});
const isDisabled = computed(() => props.disabled ?? props.meta?.disabled);
const stateClasses = computed(() => {
return {
active: !isEmpty(focus.value),
'focus-min': focus.value === 'min',
'focus-max': focus.value === 'max',
disabled: isDisabled.value
};
});
const rangeMin = computed(() => {
return props.meta.min || 0;
});
const rangeMax = computed(() => {
return props.meta.max || 100;
});
const rangeSteps = computed(() => {
return props.meta.steps || 1;
});
function setMinValue(event: InputEvent) {
const target = event.target as HTMLInputElement;
const newValue = Math.min(target.valueAsNumber, maxRangeValue.value);
localValue.value = `${newValue}-${maxRangeValue.value}`;
emitUpdate(localValue.value);
}
function setMaxValue(event: InputEvent) {
const target = event.target as HTMLInputElement;
const newValue = Math.max(target.valueAsNumber, minRangeValue.value);
localValue.value = `${minRangeValue.value}-${newValue}`;
emitUpdate(localValue.value);
}
const emitUpdate = (newValue: string) => {
localValue.value = newValue;
emit('update:value', newValue);
};
watch(
() => props.value,
newValue => {
localValue.value = newValue;
}
);
</script>
<template>
<div
class="e-input-range"
:class="stateClasses"
>
<span
class="current-value-label"
:title="localValue"
v-text="localValue"
/>
<div class="input-container">
<label
:title="rangeMin.toString()"
v-text="rangeMin.toString()"
/>
<div class="range-container">
<input
:id="id"
class="slider"
type="range"
:name="id"
:disabled="isDisabled"
:required="required"
:min="rangeMin"
:value="minRangeValue"
:max="rangeMax"
:step="rangeSteps"
@blur="focus = ''"
@focus="focus = 'min'"
@mousedown.left="focus = 'min'"
@mouseup.left="focus = ''"
@input.stop="setMinValue"
/>
<input
:id="`${id}-2`"
class="slider"
type="range"
:name="id"
:disabled="isDisabled"
:required="required"
:min="rangeMin"
:value="maxRangeValue"
:max="rangeMax"
:step="rangeSteps"
@blur="focus = ''"
@focus="focus = 'max'"
@mousedown.left="focus = 'max'"
@mouseup.left="focus = ''"
@input.stop="setMaxValue"
/>
<div
class="slider-display"
:class="{
disabled: isDisabled
}"
>
<div class="track" />
<div
class="range"
:style="{
left: `${(minRangeValue / (rangeMax - rangeMin)) * 100}%`,
right: `${100 - (maxRangeValue / (rangeMax - rangeMin)) * 100}%`
}"
/>
<div
class="thumb"
:class="{
active: focus === 'min'
}"
:style="{
left: `${(minRangeValue / (rangeMax - rangeMin)) * 100}%`
}"
/>
<div
class="thumb"
:class="{
active: focus === 'max'
}"
:style="{
left: `${(maxRangeValue / (rangeMax - rangeMin)) * 100}%`
}"
/>
</div>
</div>
<label
:title="rangeMax.toString()"
v-text="rangeMax.toString()"
/>
</div>
</div>
</template>
<style scoped lang="scss">
$option-height: 24px;
.e-input-range {
align-items: center;
display: flex;
flex-direction: row;
justify-content: space-between;
border: none;
position: relative;
margin: 10px 0;
gap: 10px;
padding-left: 10px;
$thumb-size: 18px;
$bar-height: 10px;
.current-value-label {
min-width: 60px;
}
.input-container {
align-items: center;
display: flex;
flex-direction: row;
justify-content: space-between;
gap: 20px;
flex: 1 1 auto;
label {
min-width: 30px;
}
.range-container {
position: relative;
align-items: center;
display: flex;
flex-direction: column;
justify-content: space-between;
border: none;
flex: 1 1 auto;
gap: 10px;
.slider {
height: $bar-height;
margin: 0;
opacity: 0;
padding: 0;
pointer-events: none;
position: absolute;
width: 100%;
z-index: 2;
&::-moz-range-thumb {
border-radius: 50%;
cursor: pointer;
height: $thumb-size;
pointer-events: all;
width: $thumb-size;
}
&::-webkit-slider-thumb {
border-radius: 50%;
cursor: pointer;
height: $thumb-size;
pointer-events: all;
width: $thumb-size;
}
&:not(:disabled) {
&:active {
background: var(--e-input-focus-background);
&::-moz-range-thumb {
cursor: grabbing;
background: var(--e-secondary);
}
&::-webkit-slider-thumb {
cursor: grabbing;
background: var(--e-secondary);
}
}
}
&:disabled {
cursor: not-allowed;
&::-moz-range-thumb {
cursor: not-allowed;
background: var(--e-disabled);
}
&::-webkit-slider-thumb {
cursor: not-allowed;
background: var(--e-disabled);
}
}
}
.slider-display {
background: var(--e-input-background);
border-radius: 5px;
height: $bar-height;
position: relative;
user-input: none;
user-select: none;
width: 100%;
z-index: 1;
-webkit-appearance: none;
.track,
.range,
.thumb {
position: absolute;
user-select: none;
}
.track,
.range {
top: 0;
bottom: 0;
}
.track {
background-color: var(--e-input-background);
border-radius: 5px;
left: 0;
right: 0;
z-index: 1;
}
.range {
background-image: linear-gradient(to right, var(--e-accent), var(--e-accent));
z-index: 2;
}
.thumb {
background-color: var(--e-accent);
border-radius: calc($thumb-size / 2);
height: $thumb-size;
width: $thumb-size;
z-index: 3;
top: calc($bar-height / 2);
transform: translate(calc($thumb-size / 4 * -2), calc($thumb-size / 2 * -1));
&.active {
background-color: var(--e-secondary);
outline: 1px solid var(--e-foreground);
}
}
&.disabled {
.range {
background-color: var(--e-disabled);
background-image: linear-gradient(
var(--e-disabled),
var(--e-disabled),
var(--e-disabled)
);
}
.thumb {
background-color: var(--e-disabled);
cursor: not-allowed;
}
}
}
}
}
&:not(.disabled) {
.input-container {
.range-container {
.slider {
&:active {
background: var(--e-input-focus-background);
}
}
}
}
&.active {
.slider-display {
.track {
background-color: var(--e-input-focus-background);
}
}
}
&.focus-min {
.slider-display {
.range {
background-image: linear-gradient(
to right,
var(--e-secondary),
var(--e-secondary),
var(--e-accent)
);
}
}
}
&.focus-max {
.slider-display {
.range {
background-image: linear-gradient(
to right,
var(--e-accent),
var(--e-secondary),
var(--e-secondary)
);
}
}
}
}
&.disabled {
.input-container {
label {
cursor: not-allowed;
user-input: none;
}
}
}
}
</style>