Add filters
Django CI/CD / test (push) Successful in 43s
Django CI/CD / build-and-push (push) Successful in 1m22s

This commit is contained in:
2026-06-06 12:13:04 +02:00
parent 36b1382015
commit b6864e59ce
17 changed files with 2743 additions and 44 deletions
+96
View File
@@ -0,0 +1,96 @@
/**
* 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;
})();