analytics/assets/js/liveview/combo-box.js
Uku Taht 97b24c0492
Nolt sso (along with a better nav dropdown) (#3395)
* Add SSO link with signed JWT token

* Falls back to Nolt URL without SSO if token cannot be generated

* Add profile image (gravatar) to Nolt SSO link

* Improve navbar dropdown

* Add 'contact support' link to nav dropdown

* Add CSS rule to prevent horizontal jumps

* Dark mode styling

* Close dropdown when link is clicked

* Clarify links in dropdown

* Clarify CSS comment

* Use Alpine.data() over window

* Rename suggestions_dropdown -> combo-box

* Mix format

* Make logout link look good on dark mode

* Use proxy for gravatar

* Do not use Gravatar proxy in self-hosted

* Changelog

* Add Github Repo link to nav dropdown

* Make dialyzer happy

* Add proxy for Gravatar

* Update assets/css/app.css

Co-authored-by: hq1 <hq@mtod.org>

* Update lib/plausible_web/controllers/avatar_controller.ex

Co-authored-by: hq1 <hq@mtod.org>

* Fix alpine <> Liveview integration

---------

Co-authored-by: hq1 <hq@mtod.org>
2023-10-17 12:01:27 +03:00

78 lines
2.0 KiB
JavaScript

// Courtesy of Benjamin von Polheim:
// https://blog.devgenius.io/build-a-performat-autocomplete-using-phoenix-liveview-and-alpine-js-8bcbbed17ba7
export default (id) => ({
isOpen: false,
id: id,
focus: null,
setFocus(f) {
this.focus = f;
},
initFocus() {
if (this.focus === null) {
this.setFocus(this.leastFocusableIndex())
}
},
open() {
this.initFocus()
this.isOpen = true
},
suggestionsCount() {
return this.$refs.suggestions?.querySelectorAll('li').length
},
hasCreatableOption() {
return this.$refs.suggestions?.querySelector('li').classList.contains("creatable")
},
leastFocusableIndex() {
if (this.suggestionsCount() === 0) {
return 0
}
return this.hasCreatableOption() ? 0 : 1
},
maxFocusableIndex() {
return this.hasCreatableOption() ? this.suggestionsCount() - 1 : this.suggestionsCount()
},
nextFocusableIndex() {
const currentFocus = this.focus
return currentFocus + 1 > this.maxFocusableIndex() ? this.leastFocusableIndex() : currentFocus + 1
},
prevFocusableIndex() {
const currentFocus = this.focus
return currentFocus - 1 >= this.leastFocusableIndex() ? currentFocus - 1 : this.maxFocusableIndex()
},
close(e) {
// Pressing Escape should not propagate to window,
// so we'll only close the suggestions pop-up
if (this.isOpen && e.key === "Escape") {
e.stopPropagation()
}
this.isOpen = false
},
select() {
this.$refs[`dropdown-${this.id}-option-${this.focus}`]?.click()
this.close()
document.getElementById(this.id).blur()
},
scrollTo(idx) {
this.$refs[`dropdown-${this.id}-option-${idx}`]?.scrollIntoView(
{ block: 'nearest', behavior: 'smooth', inline: 'start' }
)
},
focusNext() {
const nextIndex = this.nextFocusableIndex()
if (!this.isOpen) this.open()
this.setFocus(nextIndex)
this.scrollTo(nextIndex)
},
focusPrev() {
const prevIndex = this.prevFocusableIndex()
if (!this.isOpen) this.open()
this.setFocus(prevIndex)
this.scrollTo(prevIndex)
}
})