analytics/assets/js/liveview/combo-box.js
hq1 518cdb3307
Shield: Country Rules (#3828)
* Migration: add country rules

* Add CountryRule schema

* Implement CountryRule cache

* Add country rules context interface

* Start country rules cache

* Lookup country rules on ingestion

* Remove :shields feature flag from test helpers

* Add nested sidebar menu for Shields

* Fix typo

* IP Rules: hide description on mobile view

* Prepare SiteController to handle multiple shield types

* Seed some country shield

* Implement LV for country rules

* Remove "YOU" indicator from country rules

* Fix small build

* Format

* Update typespecs

* Make docs link point at /countries

* Fix flash on top of modal for Safari

* Build the rule struct with site_id provided up-front

* Clarify why we're messaging the ComboBox component

* Re-open combobox suggestions after pressing Escape

* Update changelog

* Fix font size in country table cells

* Pass `added_by` via rule add options

* Display site's timezone timestamps in rule tooltips

* Display formatted timestamps in site's timezone

And simplify+test Timezone module; an input timestamp converted
to UTC can never be ambiguous.

* Remove no-op atom

* Display the maximum number of rules when reached

* Improve readability of remove button tests

* Credo

---------

Co-authored-by: Adrian Gruntkowski <adrian.gruntkowski@gmail.com>
2024-02-27 12:08:13 +01:00

84 lines
2.1 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,
selectionInProgress: false,
setFocus(f) {
this.focus = f;
},
initFocus() {
if (this.focus === null) {
this.setFocus(this.leastFocusableIndex())
}
},
trackSubmitValueChange() {
this.selectionInProgress = false;
},
open() {
if (!this.isOpen) {
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()
this.open()
this.setFocus(nextIndex)
this.scrollTo(nextIndex)
},
focusPrev() {
const prevIndex = this.prevFocusableIndex()
this.open()
this.setFocus(prevIndex)
this.scrollTo(prevIndex)
}
})