PageSourceSearch

https://www.lvhn.org/modules/custom/lvhn_search/js/search-scroll.js?tlu5oh

js lvhn.org collected 2026-09-24 09:18:13 UTC 4,817 bytes, 145 lines download raw bytes

1/**
2 * @file
3 * Scroll search results into view.
4 */
5(function (Drupal) {
6  let searchScrollInitialized = false;
7
8  Drupal.behaviors.tsLvhnDrawerSearch = {
9    attach: function (context, settings) {
10      if (!searchScrollInitialized) {
11        searchScrollInitialized = true;
12        initDrawerSearch();
13        initSearch();
14      }
15    }
16  };
17})(Drupal);
18
19function initSearch() {
20  const searchInputs = [
21    // Find a doctor - Home page.
22    document.querySelector('.fad-initial-simple-home .form-item-keys input[type="text"]'),
23    // Find a doctor - Detail.
24    document.querySelector('.fad-initial-simple input[type=text][id*="visible-keys"]'),
25    // Find a location.
26    document.querySelector('.fal-initial input[type=text][id*="visible-keys"]'),
27    // Find a service.
28    document.querySelector('#lvhn-search-fas-initial input[type="text"]'),
29    // Browse all services.
30    document.querySelector('#lvhn-search-fas-refine-results-top input[type="text"]'),
31    // Find a job.
32    document.querySelector('.faj-initial-simple input[type=text][id*="visible-keys"]'),
33  ];
34
35  if (!isMobileDevice()) return;
36
37  searchInputs.forEach((searchInput) => {
38    if (searchInput) {
39      searchInput.addEventListener('focus', function () {
40        // Use setTimeout to ensure scrolling is not interrupted.
41        setTimeout(() => {
42          searchInput.scrollIntoView({
43            behavior: "smooth",
44            block: "start",
45          });
46        }, 500);
47      });
48    }
49  });
50}
51
52/**
53 * Trigger scrolling when search results are rendered for drawer search.
54 */
55function initDrawerSearch() {
56  const searchResults = document.querySelector('#suggested-result');
57  const searchInput = document.querySelector('#nav-search-keys');
58  const panelTrigger = document.querySelector('.search-toggle');
59  const searchForm = document.querySelector('.search-panel .search-form');
60  let observerSearchResults;
61
62  if (!searchResults || !searchInput || !panelTrigger || !searchForm) return;
63
64  // Add mutation observer on search trigger that checks if it has tabindex="-1".
65  // If tabindex="-1" is set we know the search drawer is visible.
66  const observerSearchTrigger = new MutationObserver(function (mutations) {
67    mutations.forEach(function (mutation) {
68      if (mutation.attributeName === 'tabindex') {
69        if (panelTrigger.getAttribute('tabindex') === '-1') {
70          // Attach oberver when drawer open.
71          observerSearchDrawerMutations();
72        } else {
73
74          // Disconnect oberver when drawer closed.
75          observerSearchResults.disconnect();
76        }
77      }
78    });
79  });
80
81  // Observe the search trigger for changes.
82  observerSearchTrigger.observe(panelTrigger, { attributes: true });
83
84  /**
85   * Observes mutations in the search results and triggers scrolling when new nodes are added.
86   * Resets the scroll state when the search input has fewer than 3 characters or when backspacing.
87   */
88  function observerSearchDrawerMutations() {
89    let hasScrolled = false;
90
91    if (!searchResults || !searchForm || !searchInput) return;
92
93    // Add mutation observer to search results that checks for changes in child nodes.
94    observerSearchResults = new MutationObserver(function (mutations) {
95      mutations.forEach(function (mutation) {
96        if (mutation.addedNodes.length && !hasScrolled) {
97          hasScrolled = true;
98
99          // Use setTimeout to wait for the search results to be rendered.
100          setTimeout(() => {
101            // Only scroll on mobile devices.
102            const isMobile = isMobileDevice();
103            if (!isMobile) return;
104
105            searchForm.scrollIntoView({
106              behavior: "smooth",
107              block: "start",
108            });
109          }, 500);
110        }
111      });
112    });
113
114    observerSearchResults.observe(searchResults, { childList: true });
115
116    let searchInputCharacterCount = 0;
117
118    // If input has less than 3 characters or user is backspacing reset hasScrolled.
119    searchInput.addEventListener('keyup', function (event) {
120
121      // If fewer than 3 characters are entered reset hasScrolled.
122      // Results require 3 characters to be displayed.
123      if (event.target.value.length < 3) {
124        hasScrolled = false;
125      }
126
127      // If fewer characters are entered user is backspacing. Reset hasScrolled.
128      else if (event.target.value.length < searchInputCharacterCount) {
129        hasScrolled = false;
130      }
131
132      // Track the number of characters in the search input.
133      searchInputCharacterCount = event.target.value.length;
134    });
135  }
136}
137
138/**
139 * Checks if the current device is a mobile device.
140 * 
141 * @returns {boolean} - True if the device is a mobile device, false otherwise.
142 */
143function isMobileDevice() {
144  return /Android|webOS|iPhone|iPad|iPod|BlackBerry|Windows Phone/i.test(navigator.userAgent);
145}

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.