PageSourceSearch

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

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

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.