From a6a233b3bda283c319f1db862cdc78710c9b6588 Mon Sep 17 00:00:00 2001
From: Cameron Dutro <camertron@gmail.com>
Date: Thu, 22 Feb 2024 11:04:16 -0800
Subject: [PATCH] Latest code

---
 examples/index.html           | 14 +++----
 src/autocomplete.ts           | 26 ++++++------
 test/auto-complete-element.js | 76 ++++++++++++++++++-----------------
 3 files changed, 60 insertions(+), 56 deletions(-)

diff --git a/examples/index.html b/examples/index.html
index 4702cf9..2b90b7d 100644
--- a/examples/index.html
+++ b/examples/index.html
@@ -46,10 +46,10 @@
         <input name="robot" type="text" aria-labelledby="robots-label" autofocus />
         <!-- if a clear button is passed in, recommended to be *before* UL elements to avoid conflicting with their blur logic -->
         <button id="robot-clear">x</button>
-        <ul popover id="items-popup"></ul>
+        <ul id="items-popup"></ul>
         <!-- For built-in screen-reader announcements:
           - Note the ID is the same as the <ul> with "feedback" appended
-          - Also note that aria attributes will be added programmatically if they aren't set correctly  
+          - Also note that aria attributes will be added programmatically if they aren't set correctly
         -->
         <div id="items-popup-feedback" class="sr-only"></div>
       </auto-complete>
@@ -64,10 +64,10 @@
         <input id="robot-a" name="robot-a" type="text" aria-labelledby="robots-a-label" autofocus />
         <!-- if a clear button is passed in, recommended to be *before* UL elements to avoid conflicting with their blur logic -->
         <button id="robot-a-clear">x</button>
-        <ul popover id="items-a-popup"></ul>
+        <ul id="items-a-popup"></ul>
         <!-- For built-in screen-reader announcements:
           - Note the ID is the same as the <ul> with "feedback" appended
-          - Also note that aria attributes will be added programmatically if they aren't set correctly  
+          - Also note that aria attributes will be added programmatically if they aren't set correctly
         -->
         <div id="items-a-popup-feedback" class="sr-only"></div>
       </auto-complete>
@@ -79,7 +79,7 @@
       <label id="robots-2-label" for="robot-2">Robots (without autoselect on enter)</label>
       <auto-complete src="/demo" for="items-2-popup" aria-labelledby="robots-2-label">
         <input name="robot-2" type="text" aria-labelledby="robots-2-label" autofocus />
-        <ul popover id="items-2-popup"></ul>
+        <ul id="items-2-popup"></ul>
         <div id="items-2-popup-feedback" class="sr-only"></div>
       </auto-complete>
       <button type="submit">Save</button>
@@ -96,7 +96,7 @@
         data-autoselect="true"
       >
         <input name="custom-fetching-robot" type="text" aria-labelledby="custom-fetching-robots-label" autofocus />
-        <ul popover id="custom-fetching-items-popup"></ul>
+        <ul id="custom-fetching-items-popup"></ul>
         <div id="custom-fetching-items-popup-feedback" class="sr-only"></div>
       </auto-complete>
       <button type="submit">Save</button>
@@ -113,7 +113,7 @@
       <auto-complete src="/demo" for="fetch-on-empty-items-popup" aria-labelledby="fetch-on-empty-robots-label" fetch-on-empty>
         <input name="fetch-on-empty-robot" type="text" aria-labelledby="fetch-on-empty-robots-label" autofocus />
         <button id="fetch-on-empty-robot-clear">x</button>
-        <ul popover id="fetch-on-empty-items-popup"></ul>
+        <ul id="fetch-on-empty-items-popup"></ul>
         <div id="fetch-on-empty-items-popup-feedback" class="sr-only"></div>
       </auto-complete>
       <button type="submit">Save</button>
diff --git a/src/autocomplete.ts b/src/autocomplete.ts
index 7008de3..13126dc 100644
--- a/src/autocomplete.ts
+++ b/src/autocomplete.ts
@@ -112,42 +112,40 @@ export default class Autocomplete {
     this.container.value = ''
     this.input.focus()
     this.input.dispatchEvent(new Event('change'))
-    this.container.open = false
+    this.close()
   }
 
   onKeydown(event: KeyboardEvent): void {
     if (event.key === 'Escape' && this.container.open) {
-      this.container.open = false
+      this.close()
       event.stopPropagation()
       event.preventDefault()
     } else if (event.altKey && event.key === 'ArrowUp' && this.container.open) {
-      this.container.open = false
+      this.close()
       event.stopPropagation()
       event.preventDefault()
     } else if (event.altKey && event.key === 'ArrowDown' && !this.container.open) {
       if (!this.input.value.trim()) return
-      this.container.open = true
+      this.open()
       event.stopPropagation()
       event.preventDefault()
     }
   }
 
   onInputFocus(): void {
+    if (this.interactingWithList) return
     this.fetchResults()
   }
 
   onInputBlur(): void {
-    if (this.interactingWithList) {
-      this.interactingWithList = false
-      return
-    }
-    this.container.open = false
+    if (this.interactingWithList) return
+    this.close()
   }
 
   onCommit({target}: Pick<Event, 'target'>): void {
     const selected = target
     if (!(selected instanceof HTMLElement)) return
-    this.container.open = false
+    this.close()
     if (selected instanceof HTMLAnchorElement) return
     const value = selected.getAttribute('data-autocomplete-value') || selected.textContent!
     this.updateFeedbackForScreenReaders(`${selected.textContent || ''} selected.`)
@@ -189,7 +187,7 @@ export default class Autocomplete {
   fetchResults(): void {
     const query = this.input.value.trim()
     if (!query && !this.container.fetchOnEmpty) {
-      this.container.open = false
+      this.close()
       return
     }
 
@@ -225,7 +223,7 @@ export default class Autocomplete {
           this.updateFeedbackForScreenReaders(`${numOptions || 'No'} results.`)
         }
 
-        this.container.open = hasResults
+        hasResults ? this.open() : this.close()
         this.container.dispatchEvent(new CustomEvent('load'))
         this.container.dispatchEvent(new CustomEvent('loadend'))
       })
@@ -246,6 +244,8 @@ export default class Autocomplete {
         this.results.hidden = false
       }
     }
+    this.container.open = true
+    this.interactingWithList = true
   }
 
   close(): void {
@@ -258,5 +258,7 @@ export default class Autocomplete {
         this.results.hidden = true
       }
     }
+    this.container.open = false
+    this.interactingWithList = false
   }
 }
diff --git a/test/auto-complete-element.js b/test/auto-complete-element.js
index 74cd5bf..7d1827f 100644
--- a/test/auto-complete-element.js
+++ b/test/auto-complete-element.js
@@ -18,19 +18,8 @@ describe('auto-complete element', function () {
     })
   })
 
-  describe('requesting server results', function () {
-    beforeEach(function () {
-      document.body.innerHTML = `
-        <div id="mocha-fixture">
-          <auto-complete src="/search" for="popup">
-            <input type="text">
-            <ul id="popup"></ul>
-            <div id="popup-feedback"></div>
-          </auto-complete>
-        </div>
-      `
-    })
-
+  // eslint-disable-next-line func-style
+  const serverResponseExamples = function () {
     it('requests html fragment', async function () {
       const container = document.querySelector('auto-complete')
       const input = container.querySelector('input')
@@ -147,25 +136,6 @@ describe('auto-complete element', function () {
       assert.isFalse(container.open)
     })
 
-    it('does not close on blur after mousedown', async function () {
-      const container = document.querySelector('auto-complete')
-      const input = container.querySelector('input')
-
-      triggerInput(input, 'hub')
-      await once(container, 'loadend')
-
-      const link = container.querySelector('a[role=option]')
-
-      assert.equal('', container.value)
-      link.dispatchEvent(new MouseEvent('mousedown', {bubbles: true}))
-      input.dispatchEvent(new Event('blur'))
-      assert(container.open)
-
-      await sleep(100)
-      input.dispatchEvent(new Event('blur'))
-      assert.isFalse(container.open)
-    })
-
     it('closes on Escape', async function () {
       const container = document.querySelector('auto-complete')
       const input = container.querySelector('input')
@@ -175,10 +145,10 @@ describe('auto-complete element', function () {
       await once(container, 'loadend')
 
       assert.isTrue(container.open)
-      assert.isFalse(popup.hidden)
+      if (!popup.popover) assert.isFalse(popup.hidden)
       assert.isFalse(keydown(input, 'Escape'))
       assert.isFalse(container.open)
-      assert.isTrue(popup.hidden)
+      if (!popup.popover) assert.isTrue(popup.hidden)
     })
 
     it('opens and closes on alt + ArrowDown and alt + ArrowUp', async function () {
@@ -190,15 +160,15 @@ describe('auto-complete element', function () {
       await once(container, 'loadend')
 
       assert.isTrue(container.open)
-      assert.isFalse(popup.hidden)
+      if (!popup.popover) assert.isFalse(popup.hidden)
 
       assert.isFalse(keydown(input, 'ArrowUp', true))
       assert.isFalse(container.open)
-      assert.isTrue(popup.hidden)
+      if (!popup.popover) assert.isTrue(popup.hidden)
 
       assert.isFalse(keydown(input, 'ArrowDown', true))
       assert.isTrue(container.open)
-      assert.isFalse(popup.hidden)
+      if (!popup.popover) assert.isFalse(popup.hidden)
     })
 
     it('allows providing a custom fetch method', async () => {
@@ -216,6 +186,38 @@ describe('auto-complete element', function () {
       assert.equal(2, popup.children.length)
       assert.equal(popup.querySelector('li').textContent, 'Mock Custom Fetch Result 1')
     })
+  }
+
+  describe('requesting server results (non-popover)', function () {
+    beforeEach(function () {
+      document.body.innerHTML = `
+        <div id="mocha-fixture">
+          <auto-complete src="/search" for="popup">
+            <input type="text">
+            <ul id="popup"></ul>
+            <div id="popup-feedback"></div>
+          </auto-complete>
+        </div>
+      `
+    })
+
+    serverResponseExamples()
+  })
+
+  describe('requesting server results (popover)', function () {
+    beforeEach(function () {
+      document.body.innerHTML = `
+        <div id="mocha-fixture">
+          <auto-complete src="/search" for="popup">
+            <input type="text">
+            <ul popover id="popup"></ul>
+            <div id="popup-feedback"></div>
+          </auto-complete>
+        </div>
+      `
+    })
+
+    serverResponseExamples()
   })
 
   describe('clear button provided', () => {
