diff --git a/app/public/assets/css/repertoire.css b/app/public/assets/css/repertoire.css
index eb90288..4df4760 100644
--- a/app/public/assets/css/repertoire.css
+++ b/app/public/assets/css/repertoire.css
@@ -359,6 +359,13 @@
color: var(--accent-primary);
}
+/* Faded/disabled: no valid results for this filter combination */
+.rep-entry--faded {
+ opacity: 0.3;
+ cursor: not-allowed;
+ pointer-events: none;
+}
+
/* Years column — big numbers, semi-bold (BBBDMSans Medium weight) */
.repertoire-col[data-col="years"] .rep-entry {
font-size: var(--step-3);
diff --git a/app/public/assets/js/app/repertoire-scroll-restore.js b/app/public/assets/js/app/repertoire-scroll-restore.js
index 84d5482..5e32e97 100644
--- a/app/public/assets/js/app/repertoire-scroll-restore.js
+++ b/app/public/assets/js/app/repertoire-scroll-restore.js
@@ -1,52 +1,39 @@
/**
- * repertoire-scroll-restore.js — Preserve column scroll positions on HTMX swap.
+ * repertoire-scroll-restore.js — Preserve column scroll positions on HTMX OOB swap.
*
- * When a filter button triggers an HTMX swap that replaces #repertoire-index,
- * the new markup replaces the old, resetting all column scroll positions to 0.
- * This module captures scrollTop of each scrollable
before the swap and
- * restores it after, keyed by data-col attribute so the mapping survives
- * DOM replacement.
+ * Filter clicks trigger HTMX requests that swap individual
elements via
+ * hx-swap-oob. This module captures scrollTop of all rep-list-*
s and the
+ * students
before the request and restores them after the response settles.
*/
(() => {
- var INDEX_SEL = "#repertoire-index";
+ var LIST_SEL = "ul[id^='rep-list-'], ul#rep-students";
var scrollSnapshots = {};
function snapshot() {
- var index = document.querySelector(INDEX_SEL);
- if (!index) return;
scrollSnapshots = {};
- index.querySelectorAll(".repertoire-col[data-col] > ul").forEach((ul) => {
- var col = ul.closest(".repertoire-col");
- if (!col) return;
- var key = col.getAttribute("data-col");
- scrollSnapshots[key] = ul.scrollTop;
+ document.querySelectorAll(LIST_SEL).forEach((ul) => {
+ scrollSnapshots[ul.id] = ul.scrollTop;
});
}
function restore() {
- var index = document.querySelector(INDEX_SEL);
- if (!index) return;
- index.querySelectorAll(".repertoire-col[data-col] > ul").forEach((ul) => {
- var col = ul.closest(".repertoire-col");
- if (!col) return;
- var key = col.getAttribute("data-col");
- if (scrollSnapshots[key] !== undefined) {
- ul.scrollTop = scrollSnapshots[key];
+ document.querySelectorAll(LIST_SEL).forEach((ul) => {
+ if (scrollSnapshots[ul.id] !== undefined) {
+ ul.scrollTop = scrollSnapshots[ul.id];
}
});
}
- // Capture scroll positions before the outgoing element is replaced
- document.body.addEventListener("htmx:beforeSwap", (e) => {
- if (e.detail.target?.matches?.(INDEX_SEL)) {
+ // Capture before any HTMX request targeting rep-students
+ document.body.addEventListener("htmx:beforeRequest", (e) => {
+ if (e.detail.target?.id === "rep-students") {
snapshot();
}
});
- // Restore after the new content is in the DOM
- document.body.addEventListener("htmx:afterSwap", (e) => {
- if (e.detail.target?.matches?.(INDEX_SEL)) {
- // Use requestAnimationFrame to ensure layout has settled
+ // Restore after the response has settled into the DOM
+ document.body.addEventListener("htmx:afterSettle", (e) => {
+ if (e.detail.target?.id === "rep-students") {
requestAnimationFrame(() => {
restore();
});
diff --git a/app/src/Controllers/SearchController.php b/app/src/Controllers/SearchController.php
index 017c359..fd06a03 100644
--- a/app/src/Controllers/SearchController.php
+++ b/app/src/Controllers/SearchController.php
@@ -277,6 +277,7 @@ class SearchController
array $repData,
array $activeFilters,
): never {
+ $isOob = true;
header('Content-Type: text/html; charset=UTF-8');
include APP_ROOT . '/templates/partials/repertoire-index.php';
exit();
diff --git a/app/templates/partials/repertoire-index.php b/app/templates/partials/repertoire-index.php
index 6892d06..96bb4eb 100644
--- a/app/templates/partials/repertoire-index.php
+++ b/app/templates/partials/repertoire-index.php
@@ -3,11 +3,17 @@
* Partial: répertoire index columns.
* Rendered both on full page load and as HTMX partial swap.
*
+ * When $isOob is true, renders only
elements with hx-swap-oob attributes
+ * for the filter columns, and the students
$c['dim'] === $colKey))[0];
-
- // Count active filters in this column for the badge
$activeCount = count($activeSets[$col['dim']]);
+ $listId = 'rep-list-' . $colKey;
?>
@@ -155,9 +205,9 @@ foreach ($renderOrder as $colKey):