Refine filters
This commit is contained in:
+171
-63
@@ -1470,15 +1470,9 @@
|
||||
.h-full {
|
||||
height: 100%;
|
||||
}
|
||||
.max-h-40 {
|
||||
max-height: calc(var(--spacing) * 40);
|
||||
}
|
||||
.max-h-full {
|
||||
max-height: 100%;
|
||||
}
|
||||
.min-h-\[28px\] {
|
||||
min-height: 28px;
|
||||
}
|
||||
.min-h-screen {
|
||||
min-height: 100vh;
|
||||
}
|
||||
@@ -1655,6 +1649,9 @@
|
||||
.flex-shrink-0 {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.shrink-0 {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.-translate-x-full {
|
||||
--tw-translate-x: -100%;
|
||||
translate: var(--tw-translate-x) var(--tw-translate-y);
|
||||
@@ -1713,9 +1710,6 @@
|
||||
.list-disc {
|
||||
list-style-type: disc;
|
||||
}
|
||||
.appearance-none {
|
||||
appearance: none;
|
||||
}
|
||||
.grid-cols-1 {
|
||||
grid-template-columns: repeat(1, minmax(0, 1fr));
|
||||
}
|
||||
@@ -2125,6 +2119,9 @@
|
||||
.bg-neutral-primary-soft {
|
||||
background-color: var(--color-neutral-primary-soft);
|
||||
}
|
||||
.bg-neutral-quaternary {
|
||||
background-color: var(--color-neutral-quaternary);
|
||||
}
|
||||
.bg-neutral-secondary-medium {
|
||||
background-color: var(--color-neutral-secondary-medium);
|
||||
}
|
||||
@@ -2331,9 +2328,6 @@
|
||||
color: heading !important;
|
||||
}
|
||||
}
|
||||
.pb-1 {
|
||||
padding-bottom: calc(var(--spacing) * 1);
|
||||
}
|
||||
.pb-16 {
|
||||
padding-bottom: calc(var(--spacing) * 16);
|
||||
}
|
||||
@@ -3074,12 +3068,6 @@
|
||||
color: var(--color-blue-700);
|
||||
}
|
||||
}
|
||||
.focus\:ring-0 {
|
||||
&:focus {
|
||||
--tw-ring-shadow: var(--tw-ring-inset,) 0 0 0 calc(0px + var(--tw-ring-offset-width)) var(--tw-ring-color, currentcolor);
|
||||
box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
|
||||
}
|
||||
}
|
||||
.focus\:ring-2 {
|
||||
&:focus {
|
||||
--tw-ring-shadow: var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color, currentcolor);
|
||||
@@ -3895,51 +3883,6 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
.\[\&\:\:-webkit-slider-thumb\]\:relative {
|
||||
&::-webkit-slider-thumb {
|
||||
position: relative;
|
||||
}
|
||||
}
|
||||
.\[\&\:\:-webkit-slider-thumb\]\:z-10 {
|
||||
&::-webkit-slider-thumb {
|
||||
z-index: 10;
|
||||
}
|
||||
}
|
||||
.\[\&\:\:-webkit-slider-thumb\]\:z-20 {
|
||||
&::-webkit-slider-thumb {
|
||||
z-index: 20;
|
||||
}
|
||||
}
|
||||
.\[\&\:\:-webkit-slider-thumb\]\:h-4 {
|
||||
&::-webkit-slider-thumb {
|
||||
height: calc(var(--spacing) * 4);
|
||||
}
|
||||
}
|
||||
.\[\&\:\:-webkit-slider-thumb\]\:w-4 {
|
||||
&::-webkit-slider-thumb {
|
||||
width: calc(var(--spacing) * 4);
|
||||
}
|
||||
}
|
||||
.\[\&\:\:-webkit-slider-thumb\]\:cursor-pointer {
|
||||
&::-webkit-slider-thumb {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
.\[\&\:\:-webkit-slider-thumb\]\:appearance-none {
|
||||
&::-webkit-slider-thumb {
|
||||
appearance: none;
|
||||
}
|
||||
}
|
||||
.\[\&\:\:-webkit-slider-thumb\]\:rounded-full {
|
||||
&::-webkit-slider-thumb {
|
||||
border-radius: calc(infinity * 1px);
|
||||
}
|
||||
}
|
||||
.\[\&\:\:-webkit-slider-thumb\]\:bg-brand {
|
||||
&::-webkit-slider-thumb {
|
||||
background-color: var(--color-brand);
|
||||
}
|
||||
}
|
||||
.\[\&\:first-of-type_button\]\:rounded-s-lg {
|
||||
&:first-of-type button {
|
||||
border-start-start-radius: var(--radius-lg);
|
||||
@@ -4358,6 +4301,171 @@ form input:disabled, select:disabled, textarea:disabled {
|
||||
padding: calc(var(--spacing) * 4);
|
||||
}
|
||||
}
|
||||
.sf-container {
|
||||
border-radius: var(--radius-base);
|
||||
border-style: var(--tw-border-style);
|
||||
border-width: 1px;
|
||||
border-color: var(--color-default-medium);
|
||||
background-color: var(--color-neutral-secondary-medium);
|
||||
}
|
||||
.sf-selected {
|
||||
display: flex;
|
||||
min-height: 2rem;
|
||||
flex-wrap: wrap;
|
||||
gap: calc(var(--spacing) * 1);
|
||||
padding: calc(var(--spacing) * 2);
|
||||
}
|
||||
.sf-tag {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: calc(var(--spacing) * 1);
|
||||
border-radius: var(--radius);
|
||||
background-color: color-mix(in srgb, oklch(48.8% 0.243 264.376) 15%, transparent);
|
||||
@supports (color: color-mix(in lab, red, red)) {
|
||||
background-color: color-mix(in oklab, var(--color-brand) 15%, transparent);
|
||||
}
|
||||
padding-inline: calc(var(--spacing) * 2);
|
||||
padding-block: calc(var(--spacing) * 0.5);
|
||||
font-size: var(--text-sm);
|
||||
line-height: var(--tw-leading, var(--text-sm--line-height));
|
||||
color: var(--color-heading);
|
||||
}
|
||||
.sf-tag.sf-excluded {
|
||||
background-color: color-mix(in srgb, oklch(63.7% 0.237 25.331) 15%, transparent);
|
||||
@supports (color: color-mix(in lab, red, red)) {
|
||||
background-color: color-mix(in oklab, var(--color-red-500) 15%, transparent);
|
||||
}
|
||||
color: var(--color-red-600);
|
||||
text-decoration-line: line-through;
|
||||
text-decoration-color: var(--color-red-400);
|
||||
}
|
||||
.sf-remove {
|
||||
margin-left: calc(var(--spacing) * 1);
|
||||
cursor: pointer;
|
||||
--tw-font-weight: var(--font-weight-bold);
|
||||
font-weight: var(--font-weight-bold);
|
||||
color: var(--color-body);
|
||||
&:hover {
|
||||
@media (hover: hover) {
|
||||
color: var(--color-heading);
|
||||
}
|
||||
}
|
||||
}
|
||||
.sf-modifier-tag {
|
||||
display: inline-flex;
|
||||
cursor: pointer;
|
||||
align-items: center;
|
||||
border-radius: var(--radius);
|
||||
background-color: color-mix(in srgb, oklch(76.9% 0.188 70.08) 15%, transparent);
|
||||
@supports (color: color-mix(in lab, red, red)) {
|
||||
background-color: color-mix(in oklab, var(--color-amber-500) 15%, transparent);
|
||||
}
|
||||
padding-inline: calc(var(--spacing) * 2);
|
||||
padding-block: calc(var(--spacing) * 0.5);
|
||||
font-size: var(--text-sm);
|
||||
line-height: var(--tw-leading, var(--text-sm--line-height));
|
||||
color: var(--color-amber-600);
|
||||
}
|
||||
.sf-search {
|
||||
display: block;
|
||||
width: 100%;
|
||||
border-style: var(--tw-border-style);
|
||||
border-width: 0px;
|
||||
border-top-style: var(--tw-border-style);
|
||||
border-top-width: 1px;
|
||||
border-color: var(--color-default-medium);
|
||||
background-color: transparent;
|
||||
padding: calc(var(--spacing) * 2);
|
||||
font-size: var(--text-sm);
|
||||
line-height: var(--tw-leading, var(--text-sm--line-height));
|
||||
color: var(--color-heading);
|
||||
&:focus {
|
||||
--tw-ring-shadow: var(--tw-ring-inset,) 0 0 0 calc(0px + var(--tw-ring-offset-width)) var(--tw-ring-color, currentcolor);
|
||||
box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
|
||||
--tw-outline-style: none;
|
||||
outline-style: none;
|
||||
@media (forced-colors: active) {
|
||||
outline: 2px solid transparent;
|
||||
outline-offset: 2px;
|
||||
}
|
||||
}
|
||||
}
|
||||
.sf-options {
|
||||
max-height: calc(var(--spacing) * 40);
|
||||
overflow-y: auto;
|
||||
padding: calc(var(--spacing) * 1);
|
||||
color: var(--color-body);
|
||||
}
|
||||
.sf-option {
|
||||
display: flex;
|
||||
cursor: pointer;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
border-radius: var(--radius);
|
||||
padding-inline: calc(var(--spacing) * 2);
|
||||
padding-block: calc(var(--spacing) * 1);
|
||||
font-size: var(--text-sm);
|
||||
line-height: var(--tw-leading, var(--text-sm--line-height));
|
||||
&:hover {
|
||||
@media (hover: hover) {
|
||||
background-color: var(--color-neutral-secondary-strong);
|
||||
}
|
||||
}
|
||||
}
|
||||
.sf-option-label {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.sf-option-buttons {
|
||||
margin-left: calc(var(--spacing) * 2);
|
||||
display: flex;
|
||||
flex-shrink: 0;
|
||||
gap: calc(var(--spacing) * 1);
|
||||
}
|
||||
.sf-btn-include, .sf-btn-exclude {
|
||||
display: flex;
|
||||
height: calc(var(--spacing) * 5);
|
||||
width: calc(var(--spacing) * 5);
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: var(--radius);
|
||||
border-style: var(--tw-border-style);
|
||||
border-width: 1px;
|
||||
border-color: var(--color-default-medium);
|
||||
font-size: var(--text-xs);
|
||||
line-height: var(--tw-leading, var(--text-xs--line-height));
|
||||
--tw-font-weight: var(--font-weight-bold);
|
||||
font-weight: var(--font-weight-bold);
|
||||
&:hover {
|
||||
@media (hover: hover) {
|
||||
border-color: var(--color-brand);
|
||||
}
|
||||
}
|
||||
&:hover {
|
||||
@media (hover: hover) {
|
||||
background-color: var(--color-brand);
|
||||
}
|
||||
}
|
||||
&:hover {
|
||||
@media (hover: hover) {
|
||||
color: var(--color-white);
|
||||
}
|
||||
}
|
||||
}
|
||||
.sf-modifier-option {
|
||||
cursor: pointer;
|
||||
padding-inline: calc(var(--spacing) * 2);
|
||||
padding-block: calc(var(--spacing) * 1);
|
||||
font-size: var(--text-sm);
|
||||
line-height: var(--tw-leading, var(--text-sm--line-height));
|
||||
color: var(--color-body);
|
||||
&:hover {
|
||||
@media (hover: hover) {
|
||||
background-color: var(--color-neutral-secondary-strong);
|
||||
}
|
||||
}
|
||||
}
|
||||
@layer base {
|
||||
input:where([type='text']),input:where(:not([type])),input:where([type='email']),input:where([type='url']),input:where([type='password']),input:where([type='number']),input:where([type='date']),input:where([type='datetime-local']),input:where([type='month']),input:where([type='search']),input:where([type='tel']),input:where([type='time']),input:where([type='week']),select:where([multiple]),textarea,select {
|
||||
appearance: none;
|
||||
|
||||
@@ -46,9 +46,6 @@
|
||||
* Returns a plain object ready for JSON.stringify.
|
||||
*/
|
||||
function buildFilterJSON(form) {
|
||||
// Read all SelectableFilter widgets first
|
||||
readSelectableFilters(form);
|
||||
|
||||
var filter = {};
|
||||
var yearMin = numberValue(form, "filter-year-min");
|
||||
var yearMax = numberValue(form, "filter-year-max");
|
||||
@@ -132,14 +129,7 @@
|
||||
}
|
||||
|
||||
if (yearMin !== "" && yearMax !== "") {
|
||||
// Skip if both equal the data range extremes (no real filter)
|
||||
var yrMinNum = parseInt(yearMin, 10);
|
||||
var yrMaxNum = parseInt(yearMax, 10);
|
||||
if (yrMinNum === yrMaxNum) {
|
||||
// don't add filter
|
||||
} else {
|
||||
filter.year_released = criterion(yearMin, yearMax, "BETWEEN");
|
||||
}
|
||||
filter.year_released = criterion(yearMin, yearMax, "BETWEEN");
|
||||
} else if (yearMin !== "") {
|
||||
filter.year_released = criterion(yearMin, null, "GREATER_THAN");
|
||||
} else if (yearMax !== "") {
|
||||
@@ -371,8 +361,6 @@
|
||||
}
|
||||
});
|
||||
}
|
||||
injectSearchInputs();
|
||||
|
||||
document.addEventListener("DOMContentLoaded", function () {
|
||||
injectSearchInputs();
|
||||
loadPresets();
|
||||
|
||||
+142
-42
@@ -1,5 +1,12 @@
|
||||
/**
|
||||
* Dual-handle range slider — pure JS with draggable handles.
|
||||
* Range slider — custom draggable handles (no native <input type=range>).
|
||||
*
|
||||
* Supports two modes on each slider, toggled via the .range-mode-toggle button:
|
||||
* range (default) — two handles, min ≤ max constraint
|
||||
* point — single handle, sets both number inputs to the same value
|
||||
*
|
||||
* Handles track-fill positioning and sync between handles and the connected
|
||||
* number inputs (linked via data-target attributes).
|
||||
*/
|
||||
(function () {
|
||||
"use strict";
|
||||
@@ -10,63 +17,109 @@
|
||||
if (slider._rsInit) return;
|
||||
slider._rsInit = true;
|
||||
|
||||
var mode = slider.getAttribute("data-mode") || "range";
|
||||
var trackFill = slider.querySelector(".range-track-fill");
|
||||
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 minTarget = document.getElementById(
|
||||
minHandle.getAttribute("data-target")
|
||||
);
|
||||
var maxTarget = document.getElementById(
|
||||
maxHandle.getAttribute("data-target")
|
||||
);
|
||||
var dataMin = parseInt(slider.getAttribute("data-min"), 10);
|
||||
var dataMax = 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);
|
||||
// ── Helpers ──
|
||||
|
||||
function valueToPercent(value) {
|
||||
return ((value - dataMin) / (dataMax - dataMin)) * 100;
|
||||
}
|
||||
function percentToValue(percent) {
|
||||
var raw = dataMin + (percent / 100) * (dataMax - dataMin);
|
||||
return Math.round(raw / step) * step;
|
||||
}
|
||||
function clamp(v, lo, hi) { return Math.max(lo, Math.min(hi, v)); }
|
||||
function clamp(value, lo, hi) {
|
||||
return Math.max(lo, Math.min(hi, value));
|
||||
}
|
||||
|
||||
function getTargetVal(el) { return parseInt(el ? el.value : minTarget.value, 10) || dMin; }
|
||||
function setTargetVal(el, v) { if (el) el.value = v; }
|
||||
function getTargetValue(target) {
|
||||
return parseInt(target ? target.value : 0, 10) || dataMin;
|
||||
}
|
||||
function setTargetValue(target, value) {
|
||||
if (target) target.value = value;
|
||||
}
|
||||
|
||||
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) + "%";
|
||||
// ── Track fill positioning ──
|
||||
|
||||
function updateTrackFill() {
|
||||
if (!trackFill) return;
|
||||
var minValue = getTargetValue(minTarget);
|
||||
var maxValue = getTargetValue(maxTarget);
|
||||
if (mode === "point") {
|
||||
trackFill.style.left = "0%";
|
||||
trackFill.style.width = valueToPercent(maxValue) + "%";
|
||||
} else {
|
||||
var leftPct = valueToPercent(minValue);
|
||||
var widthPct = valueToPercent(maxValue) - leftPct;
|
||||
trackFill.style.left = leftPct + "%";
|
||||
trackFill.style.width = widthPct + "%";
|
||||
}
|
||||
}
|
||||
|
||||
function updateHandles() {
|
||||
minHandle.style.left = valueToPercent(getTargetValue(minTarget)) + "%";
|
||||
maxHandle.style.left = valueToPercent(getTargetValue(maxTarget)) + "%";
|
||||
updateTrackFill();
|
||||
}
|
||||
|
||||
// ── Dragging ──
|
||||
|
||||
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));
|
||||
var value = percentToValue(clamp(pct, 0, 100));
|
||||
|
||||
if (mode === "point") {
|
||||
setTargetValue(minTarget, value);
|
||||
setTargetValue(maxTarget, value);
|
||||
if (minTarget)
|
||||
minTarget.dispatchEvent(
|
||||
new Event("input", { bubbles: true })
|
||||
);
|
||||
if (maxTarget)
|
||||
maxTarget.dispatchEvent(
|
||||
new Event("input", { bubbles: true })
|
||||
);
|
||||
} else if (isMin) {
|
||||
setTargetValue(
|
||||
minTarget,
|
||||
clamp(value, dataMin, getTargetValue(maxTarget))
|
||||
);
|
||||
if (minTarget)
|
||||
minTarget.dispatchEvent(
|
||||
new Event("input", { bubbles: true })
|
||||
);
|
||||
} else {
|
||||
maxTarget.value = clamp(v, getTargetVal(minTarget), dMax);
|
||||
setTargetValue(
|
||||
maxTarget,
|
||||
clamp(value, getTargetValue(minTarget), dataMax)
|
||||
);
|
||||
if (maxTarget)
|
||||
maxTarget.dispatchEvent(
|
||||
new Event("input", { bubbles: true })
|
||||
);
|
||||
}
|
||||
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 }));
|
||||
updateHandles();
|
||||
}
|
||||
|
||||
function onUp() {
|
||||
document.removeEventListener("mousemove", onMove);
|
||||
document.removeEventListener("mouseup", onUp);
|
||||
@@ -80,17 +133,64 @@
|
||||
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);
|
||||
// ── Sync from number inputs back to handles ──
|
||||
|
||||
update();
|
||||
function syncFromInputs() {
|
||||
if (mode === "point") {
|
||||
var value =
|
||||
getTargetValue(minTarget) || getTargetValue(maxTarget);
|
||||
setTargetValue(minTarget, value);
|
||||
setTargetValue(maxTarget, value);
|
||||
}
|
||||
updateHandles();
|
||||
}
|
||||
if (minTarget)
|
||||
minTarget.addEventListener("input", syncFromInputs);
|
||||
if (maxTarget)
|
||||
maxTarget.addEventListener("input", syncFromInputs);
|
||||
|
||||
// ── Mode toggle ──
|
||||
|
||||
var block = slider.closest(".range-slider-block");
|
||||
var toggleButton =
|
||||
block && block.querySelector(".range-mode-toggle");
|
||||
if (toggleButton) {
|
||||
toggleButton.addEventListener("click", function () {
|
||||
var newMode = mode === "range" ? "point" : "range";
|
||||
slider.setAttribute("data-mode", newMode);
|
||||
|
||||
// Swap toggle icons
|
||||
var iconRange = toggleButton.querySelector(
|
||||
".range-mode-icon-range"
|
||||
);
|
||||
var iconPoint = toggleButton.querySelector(
|
||||
".range-mode-icon-point"
|
||||
);
|
||||
if (iconRange) iconRange.classList.toggle("hidden");
|
||||
if (iconPoint) iconPoint.classList.toggle("hidden");
|
||||
|
||||
var dashSpan = block && block.querySelector(".range-dash");
|
||||
if (newMode === "point") {
|
||||
minHandle.style.display = "none";
|
||||
setTargetValue(minTarget, getTargetValue(maxTarget));
|
||||
if (minTarget) minTarget.classList.add("hidden");
|
||||
if (dashSpan) dashSpan.classList.add("hidden");
|
||||
} else {
|
||||
minHandle.style.display = "";
|
||||
if (minTarget) minTarget.classList.remove("hidden");
|
||||
if (dashSpan) dashSpan.classList.remove("hidden");
|
||||
}
|
||||
mode = newMode;
|
||||
updateHandles();
|
||||
});
|
||||
}
|
||||
|
||||
// ── Initial position ──
|
||||
updateHandles();
|
||||
});
|
||||
}
|
||||
|
||||
document.addEventListener("DOMContentLoaded", initAll);
|
||||
document.addEventListener("htmx:afterSwap", initAll);
|
||||
// Expose for manual re-init (filter bar toggle)
|
||||
window.initRangeSliders = initAll;
|
||||
})();
|
||||
|
||||
Reference in New Issue
Block a user