avo 4.0.23 → 4.0.24
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/Gemfile.lock +1 -1
- data/app/assets/builds/avo/application.css +44 -0
- data/app/assets/builds/avo/application.js +1 -1
- data/app/assets/builds/avo/application.js.map +3 -3
- data/app/assets/builds/avo/avo.custom.js +6 -0
- data/app/assets/builds/avo/avo.custom.js.map +7 -0
- data/app/assets/stylesheets/css/components/ui/dropdown.css +55 -0
- data/app/components/avo/u_i/dropdown_component.html.erb +15 -0
- data/app/components/avo/u_i/dropdown_component.rb +8 -0
- data/app/javascript/js/controllers/popover_menu_controller.js +51 -11
- data/lib/avo/version.rb +1 -1
- metadata +3 -1
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { Controller } from '@hotwired/stimulus'
|
|
2
2
|
|
|
3
3
|
export default class extends Controller {
|
|
4
|
+
static targets = ['searchInput', 'empty']
|
|
5
|
+
|
|
4
6
|
// Used by both onToggle (auto-focus) and handleKeydown (arrow navigation).
|
|
5
7
|
get focusableItems() {
|
|
6
8
|
return [...this.element.querySelectorAll('a, button')].filter(
|
|
@@ -25,9 +27,19 @@ export default class extends Controller {
|
|
|
25
27
|
onToggle(event) {
|
|
26
28
|
if (event.newState !== 'open') return
|
|
27
29
|
|
|
30
|
+
// Each open starts unfiltered — a leftover query from the last visit would
|
|
31
|
+
// silently hide items.
|
|
32
|
+
if (this.hasSearchInputTarget) this.resetFilter()
|
|
33
|
+
|
|
28
34
|
// requestAnimationFrame ensures the popover is fully rendered before we focus.
|
|
29
35
|
// Focusing before the browser paints causes the scroll to jump in some cases.
|
|
30
36
|
requestAnimationFrame(() => {
|
|
37
|
+
// A searchable menu hands focus to its filter input so the user can just type.
|
|
38
|
+
if (this.hasSearchInputTarget) {
|
|
39
|
+
this.searchInputTarget.focus()
|
|
40
|
+
return
|
|
41
|
+
}
|
|
42
|
+
|
|
31
43
|
const items = this.focusableItems
|
|
32
44
|
if (items.length === 0) return
|
|
33
45
|
|
|
@@ -38,24 +50,46 @@ export default class extends Controller {
|
|
|
38
50
|
})
|
|
39
51
|
}
|
|
40
52
|
|
|
53
|
+
// Narrows the rendered items to those whose text matches the query. Items are
|
|
54
|
+
// queried at call time (not cached) so lazily loaded content — e.g. a
|
|
55
|
+
// turbo-frame inside the list — is filterable as soon as it arrives. An item
|
|
56
|
+
// carrying data-filter-text matches on that instead of its full text, so
|
|
57
|
+
// decorations (timestamps, tags) don't count as matches.
|
|
58
|
+
filter() {
|
|
59
|
+
const query = this.searchInputTarget.value.trim().toLowerCase()
|
|
60
|
+
let anyVisible = false
|
|
61
|
+
|
|
62
|
+
this.element.querySelectorAll('.dropdown-menu__list a, .dropdown-menu__list button').forEach((item) => {
|
|
63
|
+
const text = item.dataset.filterText ?? item.textContent
|
|
64
|
+
const match = query === '' || text.replace(/\s+/g, ' ').trim().toLowerCase().includes(query)
|
|
65
|
+
item.hidden = !match
|
|
66
|
+
if (match) anyVisible = true
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
if (this.hasEmptyTarget) this.emptyTarget.hidden = anyVisible || query === ''
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
resetFilter() {
|
|
73
|
+
this.searchInputTarget.value = ''
|
|
74
|
+
this.filter()
|
|
75
|
+
}
|
|
76
|
+
|
|
41
77
|
handleKeydown(event) {
|
|
42
78
|
// The keydown listener is always attached, so guard against firing when closed.
|
|
43
79
|
if (!this.element.matches(':popover-open')) return
|
|
44
80
|
|
|
45
|
-
const items = this.focusableItems
|
|
46
|
-
if (items.length === 0) return
|
|
47
|
-
|
|
48
|
-
const idx = items.indexOf(document.activeElement)
|
|
49
|
-
|
|
50
81
|
switch (event.key) {
|
|
51
82
|
case 'ArrowDown':
|
|
83
|
+
case 'ArrowUp': {
|
|
84
|
+
const items = this.focusableItems
|
|
85
|
+
if (items.length === 0) return
|
|
86
|
+
|
|
52
87
|
event.preventDefault()
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
event.preventDefault()
|
|
57
|
-
items[idx > 0 ? idx - 1 : items.length - 1].focus()
|
|
88
|
+
const idx = items.indexOf(document.activeElement)
|
|
89
|
+
if (event.key === 'ArrowDown') items[idx < items.length - 1 ? idx + 1 : 0].focus()
|
|
90
|
+
else items[idx > 0 ? idx - 1 : items.length - 1].focus()
|
|
58
91
|
break
|
|
92
|
+
}
|
|
59
93
|
case 'Escape':
|
|
60
94
|
// Close on Escape ourselves. Native popover light-dismiss is unreliable
|
|
61
95
|
// here because other document-level keydown handlers (e.g. the index-row
|
|
@@ -63,7 +97,13 @@ export default class extends Controller {
|
|
|
63
97
|
// also reacting; hidePopover restores focus to the trigger.
|
|
64
98
|
event.preventDefault()
|
|
65
99
|
event.stopPropagation()
|
|
66
|
-
|
|
100
|
+
// With a non-empty search query the first Escape only clears it; the next closes.
|
|
101
|
+
if (this.hasSearchInputTarget && this.searchInputTarget.value !== '') {
|
|
102
|
+
this.resetFilter()
|
|
103
|
+
this.searchInputTarget.focus()
|
|
104
|
+
} else {
|
|
105
|
+
this.element.hidePopover()
|
|
106
|
+
}
|
|
67
107
|
break
|
|
68
108
|
default:
|
|
69
109
|
}
|
data/lib/avo/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: avo
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 4.0.
|
|
4
|
+
version: 4.0.24
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Adrian Marin
|
|
@@ -224,6 +224,8 @@ files:
|
|
|
224
224
|
- app/assets/builds/avo/application.css
|
|
225
225
|
- app/assets/builds/avo/application.js
|
|
226
226
|
- app/assets/builds/avo/application.js.map
|
|
227
|
+
- app/assets/builds/avo/avo.custom.js
|
|
228
|
+
- app/assets/builds/avo/avo.custom.js.map
|
|
227
229
|
- app/assets/builds/avo/dependencies.css
|
|
228
230
|
- app/assets/builds/avo/late-registration.js
|
|
229
231
|
- app/assets/builds/avo/late-registration.js.map
|