PageSourceSearch

https://houseplansandmore.com/js/plan-gallery.arz855gr3g.js

js houseplansandmore.com collected 2026-09-26 11:03:03 UTC 5,147 bytes, 116 lines download raw bytes

1/**
2 * plan-gallery.js — Vanilla JS for the static-SSR plan detail photo gallery.
3 * Public pages are static SSR (no SignalR), so all gallery behavior lives here,
4 * driven by data-attributes rendered by Components/PlanDetail/PlanGallery.razor.
5 *
6 * Behavior:
7 *   - Prev/Next buttons cycle the main image through the gallery set (wraps).
8 *   - Swipe left/right on the main image does the same (touch devices).
9 *   - Clicking a thumbnail sets the main image to that offset.
10 *   - Thumbnail strip Prev/Next scroll the strip horizontally.
11 *   - The active thumbnail is highlighted and scrolled into view.
12 *
13 * Why document-level delegation with NO per-gallery init: public pages navigate
14 * via Blazor ENHANCED navigation, which patches the DOM in place and does not
15 * execute <script> tags in swapped-in content — a per-load init wired to
16 * DOMContentLoaded simply never runs when the user *clicks into* a detail page.
17 * These handlers bind once (this file is referenced in App.razor) and derive all
18 * state from the DOM on every interaction (current index = the .active thumb),
19 * so they keep working across every enhanced navigation.
20 */
21(function () {
22    'use strict';
23    if (window.__hpmPlanGalleryInit) return;
24    window.__hpmPlanGalleryInit = true;
25
26    function galleryOf(el) {
27        return el && el.closest ? el.closest('[data-plan-gallery]') : null;
28    }
29
30    function slides(root) {
31        var thumbs = root.querySelectorAll('.pg-thumb');
32        if (thumbs.length) {
33            return Array.prototype.map.call(thumbs, function (t) {
34                return { full: t.dataset.full || t.src, alt: t.alt };
35            });
36        }
37        var main = root.querySelector('[data-pg-main]');
38        return main ? [{ full: main.getAttribute('src'), alt: main.getAttribute('alt') || '' }] : [];
39    }
40
41    function currentIndex(root) {
42        var thumbs = root.querySelectorAll('.pg-thumb');
43        for (var i = 0; i < thumbs.length; i++) {
44            if (thumbs[i].classList.contains('active')) return i;
45        }
46        return 0;
47    }
48
49    function show(root, i) {
50        var set = slides(root);
51        if (!set.length) return;
52        var main = root.querySelector('[data-pg-main]');
53        if (!main) return;
54        var idx = ((i % set.length) + set.length) % set.length;
55        main.setAttribute('src', set[idx].full);
56        main.setAttribute('alt', set[idx].alt);
57        var thumbs = root.querySelectorAll('.pg-thumb');
58        for (var t = 0; t < thumbs.length; t++) {
59            thumbs[t].classList.toggle('active', t === idx);
60        }
61        var active = thumbs[idx];
62        if (active && active.scrollIntoView) {
63            active.scrollIntoView({ block: 'nearest', inline: 'nearest' });
64        }
65    }
66
67    function scrollThumbs(root, dir) {
68        var wrap = root.querySelector('[data-pg-thumbs]');
69        if (!wrap) return;
70        // When every thumbnail already fits (no horizontal overflow) there is
71        // nothing to scroll — step the main image instead so the arrows are
72        // never dead controls.
73        if (wrap.scrollWidth <= wrap.clientWidth + 1) {
74            show(root, currentIndex(root) + dir);
75            return;
76        }
77        var amount = Math.max(wrap.clientWidth * 0.8, 160);
78        wrap.scrollBy({ left: dir * amount, behavior: 'smooth' });
79    }
80
81    document.addEventListener('click', function (e) {
82        var t = e.target;
83        if (!t || !t.closest) return;
84        var root = galleryOf(t);
85        if (!root) return;
86        if (t.closest('[data-pg-prev]')) { show(root, currentIndex(root) - 1); return; }
87        if (t.closest('[data-pg-next]')) { show(root, currentIndex(root) + 1); return; }
88        if (t.closest('[data-pg-thumb-prev]')) { scrollThumbs(root, -1); return; }
89        if (t.closest('[data-pg-thumb-next]')) { scrollThumbs(root, 1); return; }
90        var thumb = t.closest('.pg-thumb');
91        if (thumb) {
92            var i = parseInt(thumb.dataset.pgIndex, 10);
93            show(root, isNaN(i) ? 0 : i);
94        }
95    });
96
97    // Swipe support on the main image (delegated; start point remembered between
98    // touchstart and touchend).
99    var touchStart = null;
100    document.addEventListener('touchstart', function (e) {
101        var main = e.target && e.target.closest ? e.target.closest('[data-pg-main]') : null;
102        if (!main) { touchStart = null; return; }
103        var t = e.changedTouches[0];
104        touchStart = { x: t.clientX, y: t.clientY, root: galleryOf(main) };
105    }, { passive: true });
106    document.addEventListener('touchend', function (e) {
107        if (!touchStart || !touchStart.root) return;
108        var t = e.changedTouches[0];
109        var dx = t.clientX - touchStart.x, dy = t.clientY - touchStart.y;
110        // Horizontal swipe that isn't mostly vertical.
111        if (Math.abs(dx) > 40 && Math.abs(dx) > Math.abs(dy)) {
112            show(touchStart.root, currentIndex(touchStart.root) + (dx < 0 ? 1 : -1));
113        }
114        touchStart = null;
115    }, { passive: true });
116})();

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.