gabriel / musehub public
proposal-list.ts typescript
60 lines 2.2 KB
Raw
sha256:a34090cc4a394a78bd72cbbe34b08cc59525141e19135b6c0ab154f10611b9ef debug(push/stream): instrument O-frame decode path with INF… Sonnet 4.6 patch 121 days ago
1 /**
2 * proposal-list.ts — Proposal list page behaviour.
3 *
4 * Responsibilities:
5 * - Sync filter-bar tab active state after HTMX swaps (URL may change without
6 * a full navigation, so we re-derive active state from the current URL).
7 * - Restart the row entrance animation after HTMX injects new rows so the
8 * stagger plays on every tab/sort switch, not just on first load.
9 */
10
11 type PageData = Record<string, unknown>;
12
13 export function initProposalList(_data: PageData): void {
14 syncFilterBar();
15 hookHtmxSwap();
16 }
17
18 /** Re-read the current URL and mark the matching tab + sort option as active. */
19 function syncFilterBar(): void {
20 const params = new URLSearchParams(window.location.search);
21 const state = params.get('state') ?? 'open';
22 const sort = params.get('sort') ?? 'newest';
23
24 document.querySelectorAll<HTMLAnchorElement>('.prl-tab').forEach((tab) => {
25 const url = new URL(tab.href, location.origin);
26 const tabState = url.searchParams.get('state') ?? 'open';
27 tab.classList.toggle('prl-tab--active', tabState === state);
28 });
29
30 document.querySelectorAll<HTMLAnchorElement>('.prl-sort-opt').forEach((opt) => {
31 const url = new URL(opt.href, location.origin);
32 const optSort = url.searchParams.get('sort') ?? 'newest';
33 opt.classList.toggle('prl-sort-opt--active', optSort === sort);
34 });
35 }
36
37 /**
38 * After HTMX swaps #proposal-rows, restart the row entrance animation by
39 * briefly removing and re-adding the animation class via a forced reflow.
40 */
41 function hookHtmxSwap(): void {
42 document.body.addEventListener('htmx:afterSwap', (e: Event) => {
43 const target = (e as CustomEvent).detail?.target as Element | undefined;
44 if (!target) return;
45 const container = target.id === 'proposal-rows' ? target : target.closest('#proposal-rows');
46 if (!container) return;
47
48 // Sync active states based on the now-updated URL
49 syncFilterBar();
50
51 // Re-trigger animations: clone trick forces reflow without DOM removal
52 const rows = container.querySelectorAll<HTMLElement>('.prl-row');
53 rows.forEach((row, i) => {
54 row.style.animationDelay = `${i < 8 ? i * 28 : 0}ms`;
55 row.style.animation = 'none';
56 void row.offsetHeight; // force reflow
57 row.style.animation = '';
58 });
59 });
60 }
File History 1 commit
sha256:a34090cc4a394a78bd72cbbe34b08cc59525141e19135b6c0ab154f10611b9ef debug(push/stream): instrument O-frame decode path with INF… Sonnet 4.6 patch 121 days ago