analytics/assets/js/liveview/combo-box.js
hq1 cc769dfb3d
Edit goals with display names (#4415)
* Update Goal schema

* Equip ComboBox with the ability of JS selection callbacks

* Update factory so display_name is always present

* Extend Goals context interface

* Update seeds

Also farming unsuspecting BEAM programmers for better
sample page paths :)

* Update ComboBox test

* Unify error message color class with helpers seen elsewhere

* Use goal.display_name where applicable

* Implement LiveView extensions for editing goals

* Sprinkle display name in external stats controller tests

* Format

* Fix goal list mobile view

* Update lib/plausible_web/live/goal_settings/list.ex

Co-authored-by: Artur Pata <artur.pata@gmail.com>

* Update lib/plausible_web/live/goal_settings/form.ex

Co-authored-by: Artur Pata <artur.pata@gmail.com>

* Update the APIs: plugins and external

* Update test so the intent is clearer

* Format

* Update CHANGELOG

* Simplify form tabs tests

* Revert "Format"

This reverts commit c1647b5307.

* Fixup format commit that went too far

* ComboBox: select the input contents on first focus

* Update lib/plausible/goal/schema.ex

Co-authored-by: Adrian Gruntkowski <adrian.gruntkowski@gmail.com>

* Update lib/plausible/goals/goals.ex

Co-authored-by: Adrian Gruntkowski <adrian.gruntkowski@gmail.com>

* Update lib/plausible_web/live/goal_settings/form.ex

Co-authored-by: Adrian Gruntkowski <adrian.gruntkowski@gmail.com>

* Pass form goal instead of just ID

* Make tab component dumber

* Extract separate render functions for edit and create forms

* Update test to account for extracted forms

* Inline goal get query

* Extract revenue goal settings to a component and avoid computing assigns in flight

* Make LV modal preload optional

* Disable preload for goal settings form modal

* Get rid of phash component ID hack

* For another render after render_submit when testing goal updates

* Fix LV preload option

* Enable preload back for goals modal for now

* Make formatter happy

* Implement support for preopening of LV modal

* Preopen goals modal to avoid feedback gap on loading edited goal

* Remove `console.log` call from modal JS

* Clean up display name input IDs

* Make revenue settings functional on first edit again

* Display names: 2nd stage migration

* Update migration with data backfill

---------

Co-authored-by: Artur Pata <artur.pata@gmail.com>
Co-authored-by: Adrian Gruntkowski <adrian.gruntkowski@gmail.com>
2024-08-09 11:12:00 +02:00

89 lines
2.3 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,
firstFocusRegistered: false,
setFocus(f) {
this.focus = f;
},
initFocus() {
if (this.focus === null) {
this.setFocus(this.leastFocusableIndex())
if (!this.firstFocusRegistered) {
document.getElementById(this.id).select();
this.firstFocusRegistered = true;
}
}
},
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)
}
})