From 37da6a8b02848aeb4c139d8358771d89ac21fa48 Mon Sep 17 00:00:00 2001
From: Chris Hallberg <challber@villanova.edu>
Date: Mon, 26 Feb 2024 15:06:20 -0500
Subject: [PATCH] fix: remove race conditions between timeouts and callbacks.

---
 autocomplete.js | 41 +++++++++++++++++++++++------------------
 1 file changed, 23 insertions(+), 18 deletions(-)

diff --git a/autocomplete.js b/autocomplete.js
index 14525b2..b874ca7 100644
--- a/autocomplete.js
+++ b/autocomplete.js
@@ -1,4 +1,4 @@
-/* https://github.com/vufind-org/autocomplete.js (v2.1.8) (2024-01-29) */
+/* https://github.com/vufind-org/autocomplete.js (v2.1.9) (2024-02-26) */
 function Autocomplete(_settings) {
   const _DEFAULTS = {
     delay: 250,
@@ -28,6 +28,8 @@ function Autocomplete(_settings) {
       timeout = setTimeout(function () {
         func.apply(context, args);
       }, delay);
+
+      return timeout;
     };
   }
 
@@ -59,25 +61,22 @@ function Autocomplete(_settings) {
   }
 
   let lastInput = false;
-  let lastCB;
   function _show(input) {
     lastInput = input;
     list.style.left = "-100%"; // hide offscreen
     list.classList.add("open");
   }
 
-  function _hide(e) {
-    if (
-      typeof e !== "undefined" &&
-      !!e.relatedTarget &&
-      e.relatedTarget.hasAttribute("href")
-    ) {
-      return;
-    }
+  let lastCB = null;
+  let debounceTimeout;
+  function _hide() {
     list.classList.remove("open");
+    list.innerHTML = "";
+
+    clearTimeout(debounceTimeout);
     _currentIndex = -1;
     lastInput = false;
-    lastCB = false;
+    lastCB = null;
   }
 
   function _selectItem(item, input) {
@@ -85,9 +84,7 @@ function Autocomplete(_settings) {
       return;
     }
     // Broadcast
-    var event = document.createEvent("CustomEvent");
-    // CustomEvent: name, canBubble, cancelable, detail
-    event.initCustomEvent("ac-select", true, true, item);
+    var event = new CustomEvent("ac-select", { bubbles: true, cancelable: true, detail: item });
     input.dispatchEvent(event);
     // Copy value
     if (typeof item === "string" || typeof item === "number") {
@@ -179,11 +176,21 @@ function Autocomplete(_settings) {
       return;
     }
 
+    let loadingEl = _renderItem({ _header: settings.loadingString }, input);
+    list.innerHTML = loadingEl.outerHTML;
+
     let thisCB = new Date().getTime();
     lastCB = thisCB;
 
     handler(input.value, function callback(items) {
-      if (thisCB !== lastCB || items === false || items.length === 0) {
+      const outdatedHandler = thisCB !== lastCB;
+      if (outdatedHandler) {
+        // We should just ignore outdated handler callbacks; newer code will do
+        // the right thing, and taking action based on an old request will only
+        // cause problems.
+        return;
+      }
+      if (!items || items.length === 0) {
         _hide();
         return;
       }
@@ -311,8 +318,6 @@ function Autocomplete(_settings) {
     input.addEventListener(
       "input",
       (event) => {
-        let loadingEl = _renderItem({ _header: settings.loadingString }, input);
-        list.innerHTML = loadingEl.outerHTML;
         _show(input);
         _align(input);
 
@@ -322,7 +327,7 @@ function Autocomplete(_settings) {
         ) {
           _search(handler, input);
         } else {
-          debounceSearch(handler, input);
+          debounceTimeout = debounceSearch(handler, input);
         }
       },
       false