Files
colibre/src/assets/roadmap_flip.js
T
Colin Maudry c4aa90b0d1 feat: améliorations UX roadmap votes #94
- Votes cappés à VOTES_PER_WEEK (pas d'accumulation) pour valoriser
  les connexions régulières
- Solde de votes affiché dans la liste (input inéditable) à la place
  du bandeau Alert, avec date de prochain rechargement
- Compteur de votes par feature affiché comme input inéditable en
  fin de ligne (avant le bouton "+")
- Animation FLIP JS (roadmap_flip.js) : seules les lignes qui changent
  de position sont animées après un vote
- Renommage votes_credited_until → votes_last_credited_at (migration 0005)
- Constantes du module utilisées dans les assertions de tests

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-30 16:39:13 +02:00

68 lines
2.0 KiB
JavaScript

/* FLIP animation for #roadmap-vote-list — only animates items that move */
(function () {
var firstPositions = {};
var debounceTimer = null;
var listObserver = null;
var observedList = null;
function getKey(el) {
var a = el.querySelector("a");
return a ? a.getAttribute("href") : el.textContent.trim().slice(0, 40);
}
function capturePositions() {
var list = document.getElementById("roadmap-vote-list");
if (!list) return;
firstPositions = {};
list.querySelectorAll(".list-group-item").forEach(function (el) {
firstPositions[getKey(el)] = el.getBoundingClientRect().top;
});
}
function applyFlip() {
var list = document.getElementById("roadmap-vote-list");
if (!list || Object.keys(firstPositions).length === 0) return;
list.querySelectorAll(".list-group-item").forEach(function (el) {
var key = getKey(el);
var first = firstPositions[key];
if (first === undefined) return;
var dy = first - el.getBoundingClientRect().top;
if (Math.abs(dy) < 2) return;
el.style.transition = "none";
el.style.transform = "translateY(" + dy + "px)";
void el.offsetWidth;
el.style.transition = "transform 0.35s cubic-bezier(0.4, 0, 0.2, 1)";
el.style.transform = "";
});
firstPositions = {};
}
function attachObserver() {
var list = document.getElementById("roadmap-vote-list");
if (!list || list === observedList) return;
if (listObserver) listObserver.disconnect();
observedList = list;
listObserver = new MutationObserver(function () {
clearTimeout(debounceTimer);
debounceTimer = setTimeout(applyFlip, 50);
});
listObserver.observe(list, {
childList: true,
subtree: true,
characterData: true,
});
}
document.addEventListener(
"click",
function (e) {
if (e.target.closest('[id*="roadmap-vote"]')) capturePositions();
},
true
);
setInterval(attachObserver, 1000);
attachObserver();
})();