PageSourceSearch

https://labindia-analytical.com/assets/script/navigation.js?v=9fbc54f0519b3d9892f7a7f0e33beccd

js labindia-analytical.com collected 2026-09-25 08:09:37 UTC 9,189 bytes, 305 lines download raw bytes

1let downScrollAttempts = 0;
2let footerShown = false;
3let lastScrollTop = 0;
4let lastScrollTime = Date.now();
5let footerAnimationTimer = null;
6const SCROLL_COOLDOWN = 300; // Minimum time between scroll checks in ms
7const SCROLL_THRESHOLD = 50; // Pixels from bottom to consider as "bottom"
8const FOOTER_TRANSITION_MS = 600;
9const PRODUCT_FOOTER_REQUEST_EVENT = 'labindia:footer-request';
10const FOOTER_STATE_EVENT = 'labindia:footer-state';
11
12function isHorizontalProductFooterControlled() {
13    const controller = window.productHorizontalScroll;
14
15    return Boolean(
16        document.querySelector('[data-product-scroll-container]') &&
17        document.documentElement.classList.contains('product-view-page') &&
18        !document.body.classList.contains('product-scroll-fallback') &&
19        controller &&
20        controller.controlsFooter === true
21    );
22}
23
24function emitFooterState(open, options) {
25    const settings = options || {};
26
27    window.dispatchEvent(new CustomEvent(FOOTER_STATE_EVENT, {
28        detail: {
29            open: open,
30            source: settings.source || 'legacy',
31            input: settings.input || 'unknown'
32        }
33    }));
34}
35
36// Utility function to check if an element is scrollable
37function isScrollable(element) {
38    if (!element || !(element instanceof Element)) {
39        return false; // Return false if the element is not a valid DOM element
40    }
41
42    const style = window.getComputedStyle(element);
43    return (
44        (style.overflow === 'auto' || style.overflow === 'scroll' || style.overflowY === 'auto' || style.overflowY === 'scroll') &&
45        element.scrollHeight > element.clientHeight
46    );
47}
48
49
50// Function to determine if scrolling is happening within a scrollable container
51function isScrollEventFromScrollableElement(event) {
52    let element = event.target;
53    while (element && element !== document.body) {
54        if (isScrollable(element)) {
55            const { scrollTop, scrollHeight, clientHeight } = element;
56            // Check if the element is at its scrollable bottom
57            if (scrollTop + clientHeight < scrollHeight - SCROLL_THRESHOLD) {
58                return true; // Still scrollable, don't trigger footer logic
59            }
60        }
61        element = element.parentElement; // Traverse up to check ancestors
62    }
63    return false;
64}
65
66// Function to check if the window is scrolled to the bottom
67function isScrolledToBottom() {
68    const windowHeight = window.innerHeight;
69    const documentHeight = document.documentElement.scrollHeight;
70    const scrollTop = window.pageYOffset || document.documentElement.scrollTop;
71    return documentHeight - (scrollTop + windowHeight) <= SCROLL_THRESHOLD;
72}
73
74// Debounced scroll check
75function canProcessScroll() {
76    const now = Date.now();
77    if (now - lastScrollTime >= SCROLL_COOLDOWN) {
78        lastScrollTime = now;
79        return true;
80    }
81    return false;
82}
83
84// Hide the footer with animation
85function hideFooter(options) {
86    const settings = options || {};
87    const footer = document.querySelector('.footer-sidebar');
88
89    if (!footer) return false;
90    if (!footerShown && !footer.classList.contains('active')) return true;
91
92    clearTimeout(footerAnimationTimer);
93    footerShown = false;
94    downScrollAttempts = 0;
95    footer.style.transition = 'right 0.6s ease, opacity 0.6s ease';
96    footer.style.right = '-50%';
97    footer.style.opacity = '0';
98    footer.classList.remove('active');
99    emitFooterState(false, settings);
100
101    footerAnimationTimer = setTimeout(() => {
102        footer.style.visibility = 'hidden';
103        footerAnimationTimer = null;
104    }, FOOTER_TRANSITION_MS);
105
106    return true;
107}
108
109// Show the footer with animation
110function showFooter(options) {
111    const settings = options || {};
112    const requireBottom = settings.requireBottom !== false;
113    const footer = document.querySelector('.footer-sidebar');
114
115    if (!footer || (requireBottom && !isScrolledToBottom())) return false;
116    if (footerShown && footer.classList.contains('active')) return true;
117
118    clearTimeout(footerAnimationTimer);
119    footer.style.visibility = 'visible';
120    footer.style.transition = 'right 0.6s ease, opacity 0.6s ease';
121    footer.style.right = '0';
122    footer.style.opacity = '1';
123    footer.classList.add('active');
124    footerShown = true;
125    downScrollAttempts = 0;
126    emitFooterState(true, settings);
127
128    if (settings.input === 'keyboard') {
129        const closeButton = document.getElementById('footerToggle');
130
131        if (closeButton && typeof closeButton.focus === 'function') {
132            closeButton.focus({
133                preventScroll: true
134            });
135        }
136    }
137
138    footerAnimationTimer = setTimeout(() => {
139        footerAnimationTimer = null;
140    }, FOOTER_TRANSITION_MS);
141
142    return true;
143}
144
145window.addEventListener(PRODUCT_FOOTER_REQUEST_EVENT, (event) => {
146    const detail = event.detail || {};
147
148    if (detail.source !== 'product-horizontal') return;
149
150    // Always accept the product controller's close request. During responsive
151    // teardown, the mode/body class may change before cleanup finishes.
152    if (detail.action === 'close') {
153        hideFooter({
154            source: detail.source,
155            input: detail.input
156        });
157        return;
158    }
159
160    if (
161        detail.action !== 'open' ||
162        !isHorizontalProductFooterControlled()
163    ) return;
164
165    const controller = window.productHorizontalScroll;
166    const state = typeof controller.getState === 'function' ?
167        controller.getState() :
168        null;
169
170    const isAtFinalPanel = state &&
171        state.currentIndex === state.panelCount - 1 &&
172        Math.abs(window.scrollY - state.lastPanelStop) <= 6;
173
174    if (!isAtFinalPanel) return;
175
176    showFooter({
177        requireBottom: false,
178        source: detail.source,
179        input: detail.input
180    });
181});
182
183// Wheel event handler
184window.addEventListener('wheel', (event) => {
185    if (isHorizontalProductFooterControlled()) return;
186    if (!canProcessScroll() || isScrollEventFromScrollableElement(event)) return;
187
188    if (footerShown && event.deltaY < 0) {
189        hideFooter();
190    } else if (!footerShown && isScrolledToBottom()) {
191        if (event.deltaY > 0) {
192            downScrollAttempts++;
193            if (downScrollAttempts >= 2) {
194                showFooter();
195            }
196        }
197    } else {
198        downScrollAttempts = 0;
199    }
200});
201
202// Touch handling
203let touchStartY = 0;
204let lastTouchTime = Date.now();
205const TOUCH_COOLDOWN = 150;
206
207function canProcessTouch() {
208    const now = Date.now();
209    if (now - lastTouchTime >= TOUCH_COOLDOWN) {
210        lastTouchTime = now;
211        return true;
212    }
213    return false;
214}
215
216window.addEventListener('touchstart', (event) => {
217    touchStartY = event.touches[0].clientY;
218});
219
220window.addEventListener('touchmove', (event) => {
221    if (isHorizontalProductFooterControlled()) return;
222    if (!canProcessTouch() || isScrollEventFromScrollableElement(event)) return;
223
224    const touchY = event.touches[0].clientY;
225    const deltaY = touchStartY - touchY;
226
227    if (footerShown && deltaY < 0) {
228        hideFooter();
229    } else if (!footerShown && isScrolledToBottom()) {
230        if (deltaY > 0) {
231            downScrollAttempts++;
232            if (downScrollAttempts >= 3) {
233                showFooter();
234            }
235        }
236    } else {
237        downScrollAttempts = 0;
238    }
239
240    touchStartY = touchY;
241});
242
243// Scroll event handler
244let scrollTimeout;
245
246window.addEventListener('scroll', (event) => {
247    if (isHorizontalProductFooterControlled()) return;
248    if (!canProcessScroll() || isScrollEventFromScrollableElement(event)) return;
249
250    clearTimeout(scrollTimeout);
251    scrollTimeout = setTimeout(() => {
252        const st = window.pageYOffset || document.documentElement.scrollTop;
253
254        if (footerShown && st < lastScrollTop) {
255            hideFooter();
256        }
257
258        if (!isScrolledToBottom()) {
259            downScrollAttempts = 0;
260        }
261
262        lastScrollTop = st <= 0 ? 0 : st;
263    }, 50);
264});
265
266
267// Footer toggle button
268const footerToggle = document.getElementById('footerToggle');
269
270if (footerToggle) {
271    footerToggle.addEventListener('click', () => {
272        if (footerShown) {
273            hideFooter({
274                source: 'footer-toggle',
275                input: 'button'
276            });
277        } else {
278            showFooter({
279                source: 'footer-toggle',
280                input: 'button'
281            });
282        }
283    });
284}
285
286// Add event listener to the new button with id "show-footer-btn" that triggers the footer to appear
287document.addEventListener('DOMContentLoaded', () => {
288    const footerButton = document.getElementById('show-footer-btn');
289    if (footerButton) {
290        footerButton.addEventListener('click', (event) => {
291            event.preventDefault(); // Prevent default link behavior
292
293            // Show the footer
294            if (!footerShown) {
295                showFooter();
296            }
297        });
298    } else {
299        // console.error('Element with ID "show-footer-btn" not found.');
300    }
301});
302
303
304
305

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.