<div x-data="rangeSlider" x-init="init(0, 1000, 200, 800)">
    <div class="relative select-none touch-none mx-6 my-3 md:mx-0 h-0.5 bg-neutral-300" x-ref="slider">
        <div class="absolute bg-dark-black h-full w-full" :style="'width:' + getWidth() + '; left:' + getMinPos()"></div>
        <div class="absolute -ml-2 top-1/2 -translate-y-1/2 cursor-pointer rounded-full border-2 border-dark-black bg-light-white w-6 h-6 z-30 hover:bg-neutral-200" @touchstart="dragFrom=true" @mousedown="dragFrom=true" :style="'left:' + getFromPos()"></div>
        <div class="absolute -ml-2 top-1/2 -translate-y-1/2 cursor-pointer rounded-full border-2 border-dark-black bg-light-white w-6 h-6 z-30 hover:bg-neutral-200" @touchstart="dragTo=true" @mousedown="dragTo=true" :style="'left:' + getToPos()"></div>
    </div>
    <div class="pt-2 flex justify-between select-none mt-2">
        <label class="flex gap-1 justify-center items-center py-2 px-3 border border-neutral-300 cursor-text focus-within:ring-4 focus-within:ring-blue-200 rounded-md">
            <div x-data="{value:from}" class="relative h-full">
                <input class="text-center border-0 p-0 w-full absolute top-0 left-0 bg-transparent focus:border-none focus:ring-0" x-model="value" type="text" name="from" x-effect="value=Math.min(from, to)" @keyup.enter="triggerChange(value,'from')" @blur="triggerChange(value,'from')">
                <span class="invisible block w-auto overflow-auto text-nowrap min-w-6 h-full" x-text="value"></span>
            </div>
        </label>
        <label class="flex gap-1 justify-center items-center py-2 px-3 border border-neutral-300 cursor-text focus-within:ring-4 focus-within:ring-blue-200 rounded-md">
            <div x-data="{value:to}" class="relative h-full">
                <input class="text-center border-0 p-0 w-full absolute top-0 left-0 bg-transparent focus:border-none focus:ring-0" x-model="value" type="text" name="from" x-effect="value=Math.max(from, to)" @keyup.enter="triggerChange(value,'to')" @blur="triggerChange(value,'to')">
                <span class="invisible block w-auto overflow-auto text-nowrap min-w-6 h-full" x-text="value"></span>
            </div>
        </label>
    </div>
</div>

<script>
    function rangeSlider() {
        return {
            // slider start value
            min: 0,
            // slider end value
            max: 100,
            // range start
            from: 10,
            // range end
            to: 80,
            // flag if mouse is clicked or screen is touched
            dragFrom: false,
            dragTo: false,
            // call on x-init
            init: function(min, max, from, to) {
                // register mouse/touche move events to window
                window.addEventListener("mousemove", (e) => {
                    this.drag(e)
                });
                window.addEventListener("touchmove", (e) => {
                    this.drag(e)
                });
                window.addEventListener("mouseup", this.dragEnd.bind(this));
                window.addEventListener("touchend", this.dragEnd.bind(this));
                // set values
                this.min = min || this.min;
                this.max = max || this.max;
                this.from = from || this.from;
                this.to = to || this.to;
            },
            triggerChange(value, type = 'from') {
                value = parseInt(value);
                if (isNaN(value)) {
                    const {
                        from,
                        to
                    } = this;
                    this.from = this.to = 0;
                    this.from = from;
                    this.to = to;
                    return;
                }
                if (this.from > this.to && value < this.max && value > this.min) {
                    this[type === 'to' ? 'from' : 'to'] = this[type];
                    this[type] = value;
                    return;
                }
                if (type === 'to') {
                    this.to = 0;
                    this.to = Math.min(this.max, value < this.from ? this.from : value);
                    if (value < this.from) {
                        this.from = value < this.min ? this.min : value;
                    }
                } else {
                    this.from = 0;
                    this.from = Math.max(this.min, value > this.to ? this.to : value);
                    if (value > this.to) {
                        this.to = value > this.max ? this.max : value;
                    }
                }
                return value;
            },
            getFromPos: function() {
                // return relative slider position for 'from' value
                return ((this.from - this.min) / (this.max - this.min) * 100) + '%'
            },
            getToPos: function() {
                // return relative slider position for 'to' value
                return ((this.to - this.min) / (this.max - this.min) * 100) + '%'
            },
            getWidth: function() {
                // return relative width between 'from' and 'to' value
                return ((Math.max(this.to, this.from) - Math.min(this.to, this.from)) / (this.max - this.min) * 100) + '%'
            },
            getMinPos: function() {
                // return the smallest of 'from' or 'to' value
                if (this.from < this.to) {
                    return this.getFromPos();
                }
                return this.getToPos();
            },
            drag: function($event) {
                if (!this.dragFrom && !this.dragTo) {
                    return;
                }
                // get touch/mouse x-coordinate
                let x;
                const rect = this.$refs.slider.getBoundingClientRect();
                if ($event.type === 'touchmove') {
                    x = $event.changedTouches[0].clientX - rect.left + this.$refs.slider.offsetLeft;
                } else {
                    x = $event.clientX - rect.left + this.$refs.slider.offsetLeft; //x position within the element.
                }
                // calculate the value relative to the mouse/touch x-position on the document
                let pos = Math.round((this.max - this.min) * (x - this.$refs.slider.offsetLeft) / this.$refs.slider.clientWidth) + this.min;
                //console.log($event);
                // stay in range
                pos = pos > this.max ? this.max : pos;
                pos = pos < this.min ? this.min : pos;
                if (this.dragFrom) {
                    this.from = pos;
                }
                if (this.dragTo) {
                    this.to = pos;
                }
            },
            dragEnd: function() {
                this.dragFrom = false;
                this.dragTo = false;
            }
        }
    }
</script>
<div x-data="rangeSlider" x-init="init({{ minRange|default(0) }}, {{ maxRange|default(1000) }}, {{ defaultMinRange|default(200) }}, {{ defaultMaxRange|default(800) }})" >
    <div class="relative select-none touch-none mx-6 my-3 md:mx-0 h-0.5 bg-neutral-300" x-ref="slider">
        <div class="absolute bg-dark-black h-full w-full" :style="'width:' + getWidth() + '; left:' + getMinPos()"></div>
        <div class="absolute -ml-2 top-1/2 -translate-y-1/2 cursor-pointer rounded-full border-2 border-dark-black bg-light-white w-6 h-6 z-30 hover:bg-neutral-200" @touchstart="dragFrom=true" @mousedown="dragFrom=true" :style="'left:' + getFromPos()"></div>
        <div class="absolute -ml-2 top-1/2 -translate-y-1/2 cursor-pointer rounded-full border-2 border-dark-black bg-light-white w-6 h-6 z-30 hover:bg-neutral-200" @touchstart="dragTo=true" @mousedown="dragTo=true" :style="'left:' + getToPos()"></div>
    </div>
    <div class="pt-2 flex justify-between select-none mt-2">
        <label class="flex gap-1 justify-center items-center py-2 px-3 border border-neutral-300 cursor-text focus-within:ring-4 focus-within:ring-blue-200 rounded-md">
            {% if (prefix is defined) %}
                <span>{{prefix}}</span>
            {% endif %}
            <div x-data="{value:from}" class="relative h-full">
                <input class="text-center border-0 p-0 w-full absolute top-0 left-0 bg-transparent focus:border-none focus:ring-0"  x-model="value" type="text" name="from" x-effect="value=Math.min(from, to)" @keyup.enter="triggerChange(value,'from')"  @blur="triggerChange(value,'from')">
                <span class="invisible block w-auto overflow-auto text-nowrap min-w-6 h-full" x-text="value"></span>
            </div>
            {% if (suffix is defined) %}
                <span>{{suffix}}</span>
            {% endif %}
        </label>
        <label class="flex gap-1 justify-center items-center py-2 px-3 border border-neutral-300 cursor-text focus-within:ring-4 focus-within:ring-blue-200 rounded-md">
            {% if (prefix is defined) %}
                <span>{{prefix}}</span>
            {% endif %}
            <div x-data="{value:to}" class="relative h-full">
                <input class="text-center border-0 p-0 w-full absolute top-0 left-0 bg-transparent focus:border-none focus:ring-0"  x-model="value" type="text" name="from" x-effect="value=Math.max(from, to)" @keyup.enter="triggerChange(value,'to')"  @blur="triggerChange(value,'to')">
                <span class="invisible block w-auto overflow-auto text-nowrap min-w-6 h-full" x-text="value"></span>
            </div>
            {% if (suffix is defined) %}
                <span>{{suffix}}</span>
            {% endif %}
        </label>
    </div>
</div>

<script>
    function rangeSlider() {
        return {
            // slider start value
            min: 0,
            // slider end value
            max: 100,

            // range start
            from: 10,
            // range end
            to: 80,

            // flag if mouse is clicked or screen is touched
            dragFrom: false,
            dragTo: false,

            // call on x-init
            init: function(min, max, from, to){

                // register mouse/touche move events to window
                window.addEventListener("mousemove", (e) => { this.drag(e) });
                window.addEventListener("touchmove", (e) => { this.drag(e) });

                window.addEventListener( "mouseup", this.dragEnd.bind(this) );
                window.addEventListener( "touchend", this.dragEnd.bind(this) );

                // set values
                this.min = min || this.min;
                this.max = max || this.max;
                this.from = from || this.from;
                this.to = to || this.to;

            },

            triggerChange(value, type = 'from') {
                value = parseInt(value);

                if (isNaN(value)) {
                    const { from, to } = this;
                    this.from = this.to = 0;
                    this.from = from;
                    this.to = to;
                    return;
                }

                if (this.from > this.to && value < this.max && value > this.min) {
                    this[type === 'to' ? 'from' : 'to'] = this[type];
                    this[type] = value;
                    return;
                }

                if (type === 'to') {
                    this.to = 0;
                    this.to = Math.min(this.max, value < this.from ? this.from : value);
                    if (value < this.from) {
                        this.from = value < this.min ? this.min : value;
                    }
                } else {
                    this.from = 0;
                    this.from = Math.max(this.min, value > this.to ? this.to : value);
                    if (value > this.to) {
                        this.to = value > this.max ? this.max : value;
                    }
                }

                return value;
            },

            getFromPos: function(){
                // return relative slider position for 'from' value
                return ((this.from-this.min) / (this.max-this.min) * 100) + '%'
            },
            getToPos: function(){
                // return relative slider position for 'to' value
                return ((this.to-this.min) / (this.max-this.min) * 100) + '%'
            },
            getWidth: function(){
                // return relative width between 'from' and 'to' value
                return ((Math.max(this.to, this.from) - Math.min(this.to, this.from)) / (this.max-this.min) * 100) + '%'
            },
            getMinPos: function(){
                // return the smallest of 'from' or 'to' value
                if(this.from < this.to){
                    return this.getFromPos();
                }
                return this.getToPos();
            },
            drag: function($event){
                if(!this.dragFrom && !this.dragTo){
                    return;
                }

                // get touch/mouse x-coordinate
                let x;
                const rect = this.$refs.slider.getBoundingClientRect();

                if($event.type==='touchmove'){
                    x = $event.changedTouches[0].clientX - rect.left + this.$refs.slider.offsetLeft;
                }else{
                    x = $event.clientX - rect.left + this.$refs.slider.offsetLeft; //x position within the element.
                }

                // calculate the value relative to the mouse/touch x-position on the document
                let pos = Math.round((this.max - this.min) * (x-this.$refs.slider.offsetLeft) / this.$refs.slider.clientWidth) + this.min;
                //console.log($event);
                // stay in range
                pos = pos > this.max ? this.max : pos;
                pos = pos < this.min ? this.min : pos;

                if(this.dragFrom){
                    this.from = pos;
                }
                if(this.dragTo){
                    this.to = pos;
                }
            },
            dragEnd: function(){
                this.dragFrom = false;
                this.dragTo = false;
            }
        }
    }
</script>
/* No context defined. */

No notes defined.