97 lines
3.6 KiB
JavaScript
97 lines
3.6 KiB
JavaScript
/**
|
|
* Dual-handle range slider — pure JS with draggable handles.
|
|
*/
|
|
(function () {
|
|
"use strict";
|
|
|
|
function initAll(force) {
|
|
document.querySelectorAll(".range-slider").forEach(function (slider) {
|
|
if (force) slider._rsInit = false;
|
|
if (slider._rsInit) return;
|
|
slider._rsInit = true;
|
|
|
|
var minHandle = slider.querySelector(".range-handle-min");
|
|
var maxHandle = slider.querySelector(".range-handle-max");
|
|
var track = slider.querySelector(".range-track-fill");
|
|
if (!minHandle || !maxHandle) return;
|
|
|
|
var minTarget = document.getElementById(minHandle.getAttribute("data-target"));
|
|
var maxTarget = document.getElementById(maxHandle.getAttribute("data-target"));
|
|
var dMin = parseInt(slider.getAttribute("data-min"), 10);
|
|
var dMax = parseInt(slider.getAttribute("data-max"), 10);
|
|
var step = parseInt(slider.getAttribute("data-step"), 10) || 1;
|
|
|
|
function valueToPercent(v) { return ((v - dMin) / (dMax - dMin)) * 100; }
|
|
function percentToValue(p) {
|
|
var raw = dMin + (p / 100) * (dMax - dMin);
|
|
return Math.round(raw / step) * step;
|
|
}
|
|
function clamp(v, lo, hi) { return Math.max(lo, Math.min(hi, v)); }
|
|
|
|
function getTargetVal(el) { return parseInt(el ? el.value : minTarget.value, 10) || dMin; }
|
|
function setTargetVal(el, v) { if (el) el.value = v; }
|
|
|
|
function update() {
|
|
var minV = getTargetVal(minTarget);
|
|
var maxV = getTargetVal(maxTarget);
|
|
minV = clamp(minV, dMin, dMax);
|
|
maxV = clamp(maxV, dMin, dMax);
|
|
if (minV > maxV) minV = maxV;
|
|
if (maxV < minV) maxV = minV;
|
|
setTargetVal(minTarget, minV);
|
|
setTargetVal(maxTarget, maxV);
|
|
var minP = valueToPercent(minV);
|
|
var maxP = valueToPercent(maxV);
|
|
minHandle.style.left = minP + "%";
|
|
maxHandle.style.left = maxP + "%";
|
|
if (track) {
|
|
track.style.left = minP + "%";
|
|
track.style.width = (maxP - minP) + "%";
|
|
}
|
|
}
|
|
|
|
function makeDraggable(handle, isMin) {
|
|
handle.addEventListener("mousedown", function (e) {
|
|
e.preventDefault();
|
|
var rect = slider.getBoundingClientRect();
|
|
function onMove(ev) {
|
|
var pct = ((ev.clientX - rect.left) / rect.width) * 100;
|
|
var v = percentToValue(clamp(pct, 0, 100));
|
|
if (isMin) {
|
|
minTarget.value = clamp(v, dMin, getTargetVal(maxTarget));
|
|
} else {
|
|
maxTarget.value = clamp(v, getTargetVal(minTarget), dMax);
|
|
}
|
|
update();
|
|
// Trigger input event on the target so any listeners fire
|
|
var tgt = isMin ? minTarget : maxTarget;
|
|
if (tgt) tgt.dispatchEvent(new Event("input", { bubbles: true }));
|
|
}
|
|
function onUp() {
|
|
document.removeEventListener("mousemove", onMove);
|
|
document.removeEventListener("mouseup", onUp);
|
|
}
|
|
document.addEventListener("mousemove", onMove);
|
|
document.addEventListener("mouseup", onUp);
|
|
onMove(e);
|
|
});
|
|
}
|
|
|
|
makeDraggable(minHandle, true);
|
|
makeDraggable(maxHandle, false);
|
|
|
|
// Sync from inputs to slider
|
|
function fromInputs() { update(); }
|
|
if (minTarget) minTarget.addEventListener("input", fromInputs);
|
|
if (maxTarget) maxTarget.addEventListener("input", fromInputs);
|
|
|
|
update();
|
|
});
|
|
}
|
|
|
|
document.addEventListener("DOMContentLoaded", initAll);
|
|
document.addEventListener("htmx:afterSwap", initAll);
|
|
// Expose for manual re-init (filter bar toggle)
|
|
window.initRangeSliders = initAll;
|
|
})();
|