PageSourceSearch

https://www.motorfinanceonline.com/wp-content/themes/goodlife-wp-B2B/assets/js/modules/initFauxSelects.js

js motorfinanceonline.com collected 2026-09-26 01:39:50 UTC 9,740 bytes, 272 lines download raw bytes

1export default function initFauxSelects(context = document) {
2  // CHANGE 1: Only target select elements with multiple attribute, scope to context
3  const selects = context.querySelectorAll(".gdm-form select");
4
5  // Guard clause: exit if no select elements are found
6  if (selects.length === 0) {
7    return;
8  }
9
10  selects.forEach((select, index) => {
11    // Skip if select is already wrapped in a faux-select
12    if (select.closest(".faux-select")) {
13      return;
14    }
15
16    // Create container for faux select
17    const wrapper = document.createElement("div");
18    wrapper.className = "faux-select";
19    wrapper.setAttribute("data-select-id", `faux-select-${index}`);
20
21    // Mirror native select hooks onto the wrapper for styling/targeting.
22    if (select.classList.length > 0) {
23      wrapper.classList.add(...Array.from(select.classList));
24    }
25
26    // Keep a safe id on the wrapper and preserve the original select id as metadata.
27    if (select.id) {
28      wrapper.id = `${select.id}--faux-select`;
29      wrapper.setAttribute("data-source-id", select.id);
30    }
31
32    // Create display element for selected options
33    const display = document.createElement("div");
34    display.className = "faux-select__display";
35    display.tabIndex = 0;
36    display.setAttribute("role", "combobox");
37    display.setAttribute("aria-haspopup", "listbox");
38    display.setAttribute("aria-expanded", "false");
39
40    // Transfer aria-label from original select if it exists
41    if (select.hasAttribute("aria-label")) {
42      display.setAttribute("aria-label", select.getAttribute("aria-label"));
43    } else {
44      // Fallback: use title or generate one
45      const label =
46        select.getAttribute("title") || `Select ${select.name || "option"}`;
47      display.setAttribute("aria-label", label);
48    }
49
50    // CHANGE 2: Default display text for multi-select
51    display.textContent = select.options[0].text;
52
53    // Create options list
54    const optionsList = document.createElement("ul");
55    optionsList.className = "faux-select__options";
56    optionsList.setAttribute("role", "listbox");
57    optionsList.setAttribute("aria-hidden", "true");
58
59    // Populate options
60    Array.from(select.options).forEach((option, optionIndex) => {
61      const li = document.createElement("li");
62      li.className = "faux-select__option";
63      li.setAttribute("data-value", option.value);
64      li.setAttribute("role", "option");
65
66      // CHANGE 3: Reflect initial selected state from native select
67      li.setAttribute("aria-selected", option.selected);
68
69      li.textContent = option.text;
70      li.tabIndex = -1;
71
72      // CHANGE 4: Modified click handler for multi-select
73      li.addEventListener("click", () => {
74        // Check if this is a single or multi-select
75        const isMultiple = select.hasAttribute("multiple");
76
77        if (isMultiple) {
78          // Multi-select: toggle selection
79          option.selected = !option.selected;
80        } else {
81          // Single-select: clear all selections and select this one
82          Array.from(select.options).forEach((opt) => (opt.selected = false));
83          option.selected = true;
84
85          // Close dropdown for single-select
86          optionsList.setAttribute("aria-hidden", "true");
87          display.setAttribute("aria-expanded", "false");
88        }
89
90        // Update ARIA attribute
91        li.setAttribute("aria-selected", option.selected);
92
93        // Get selected options
94        const selectedOptions = Array.from(select.options)
95          .filter((opt) => opt.selected)
96          .map((opt) => opt.text);
97
98        // Update display
99        display.textContent =
100          selectedOptions.length > 0
101            ? selectedOptions.join(", ")
102            : select.options[0].text;
103
104        // Dispatch change event
105        select.dispatchEvent(new Event("change", { bubbles: true }));
106
107        // Maintain focus
108        display.focus();
109      });
110
111      optionsList.appendChild(li);
112    });
113
114    // Sync faux select with native select changes
115    select.addEventListener("change", () => {
116      // Get all selected options from native select
117      const selectedOptions = Array.from(select.options)
118        .filter((opt) => opt.selected)
119        .map((opt) => opt.text);
120
121      // CHANGE 6: Update display text with all selections
122      display.textContent =
123        selectedOptions.length > 0
124          ? selectedOptions.join(", ")
125          : select.options[0].text;
126
127      // Update ARIA states for all options
128      optionsList.querySelectorAll("li").forEach((item) => {
129        const correspondingOption = Array.from(select.options).find(
130          (opt) => opt.value === item.getAttribute("data-value"),
131        );
132        item.setAttribute(
133          "aria-selected",
134          correspondingOption?.selected || false,
135        );
136      });
137    });
138
139    // Toggle options visibility (unchanged)
140    display.addEventListener("click", () => {
141      const isHidden = optionsList.getAttribute("aria-hidden") === "true";
142      optionsList.setAttribute("aria-hidden", !isHidden);
143      display.setAttribute("aria-expanded", String(isHidden));
144      if (!isHidden) {
145        display.focus();
146      } else {
147        optionsList.querySelector("li").focus();
148        positionOptionsList(wrapper, display, optionsList);
149      }
150    });
151
152    // Close options when clicking outside (unchanged)
153    document.addEventListener("click", (e) => {
154      if (!wrapper.contains(e.target)) {
155        optionsList.setAttribute("aria-hidden", "true");
156        display.setAttribute("aria-expanded", "false");
157      }
158    });
159
160    // Keyboard accessibility for display (unchanged)
161    display.addEventListener("keydown", (e) => {
162      if (e.key === "Enter" || e.key === " " || e.key === "ArrowDown") {
163        e.preventDefault();
164        optionsList.setAttribute("aria-hidden", "false");
165        display.setAttribute("aria-expanded", "true");
166        optionsList.querySelector("li").focus();
167        positionOptionsList(wrapper, display, optionsList);
168      } else if (e.key === "ArrowUp") {
169        e.preventDefault();
170        optionsList.setAttribute("aria-hidden", "false");
171        display.setAttribute("aria-expanded", "true");
172        optionsList.querySelector("li:last-child").focus();
173        positionOptionsList(wrapper, display, optionsList);
174      } else if (
175        e.key === "Tab" &&
176        !optionsList.getAttribute("aria-hidden") === "true"
177      ) {
178        e.preventDefault();
179        optionsList.querySelector("li").focus();
180        positionOptionsList(wrapper, display, optionsList);
181      }
182    });
183
184    // Keyboard accessibility for options (unchanged)
185    optionsList.addEventListener("keydown", (e) => {
186      const currentOption = document.activeElement;
187      if (e.key === "ArrowDown") {
188        e.preventDefault();
189        const nextOption =
190          currentOption.nextElementSibling || optionsList.firstElementChild;
191        nextOption.focus();
192      } else if (e.key === "ArrowUp") {
193        e.preventDefault();
194        const prevOption =
195          currentOption.previousElementSibling || optionsList.lastElementChild;
196        prevOption.focus();
197      } else if (e.key === "Enter" || e.key === " ") {
198        e.preventDefault();
199        currentOption.click();
200      } else if (e.key === "Escape") {
201        e.preventDefault();
202        optionsList.setAttribute("aria-hidden", "true");
203        display.setAttribute("aria-expanded", "false");
204        display.focus();
205      } else if (e.key === "Tab") {
206        e.preventDefault();
207        optionsList.setAttribute("aria-hidden", "true");
208        display.setAttribute("aria-expanded", "false");
209        display.focus();
210      }
211    });
212
213    // Helper: Position options list (unchanged)
214    function positionOptionsList(wrapper, display, optionsList) {
215      optionsList.classList.remove("faux-select__options--above");
216      optionsList.style.bottom = "";
217      optionsList.style.top = "";
218
219      if (optionsList.getAttribute("aria-hidden") === "true") return;
220      const rect = display.getBoundingClientRect();
221      const optionsHeight = optionsList.offsetHeight;
222      const form = display.closest("form");
223      let formBottom = window.innerHeight;
224      if (form) {
225        const formRect = form.getBoundingClientRect();
226        formBottom = formRect.bottom;
227      }
228      const spaceBelow = formBottom - rect.bottom;
229      const spaceAbove =
230        rect.top - (form ? form.getBoundingClientRect().top : 0);
231      const viewportBottomDistance = window.innerHeight - rect.bottom;
232
233      if (
234        (spaceBelow < optionsHeight && spaceAbove > optionsHeight) ||
235        viewportBottomDistance < 320
236      ) {
237        optionsList.classList.add("faux-select__options--above");
238        optionsList.style.bottom = `${display.offsetHeight}px`;
239        optionsList.style.top = "auto";
240      } else {
241        optionsList.classList.remove("faux-select__options--above");
242        optionsList.style.top = `${display.offsetHeight}px`;
243        optionsList.style.bottom = "auto";
244      }
245    }
246
247    // Window event listeners (unchanged)
248    window.addEventListener("resize", () => {
249      if (optionsList.getAttribute("aria-hidden") === "false") {
250        positionOptionsList(wrapper, display, optionsList);
251      }
252    });
253    window.addEventListener(
254      "scroll",
255      () => {
256        if (optionsList.getAttribute("aria-hidden") === "false") {
257          positionOptionsList(wrapper, display, optionsList);
258        }
259      },
260      true,
261    );
262
263    // Hide original select but keep it in DOM
264    select.style.display = "none";
265
266    // Assemble faux select
267    wrapper.appendChild(display);
268    wrapper.appendChild(optionsList);
269    select.parentNode.insertBefore(wrapper, select);
270    wrapper.appendChild(select);
271  });
272}

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.