hotwire_combobox 0.1.42 → 0.2.0
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/README.md +1 -0
- data/app/assets/javascripts/controllers/hw_combobox_controller.js +25 -3
- data/app/assets/javascripts/hotwire_combobox.esm.js +531 -127
- data/app/assets/javascripts/hotwire_combobox.umd.js +531 -127
- data/app/assets/javascripts/hw_combobox/helpers.js +16 -0
- data/app/assets/javascripts/hw_combobox/models/combobox/announcements.js +7 -0
- data/app/assets/javascripts/hw_combobox/models/combobox/async_loading.js +4 -0
- data/app/assets/javascripts/hw_combobox/models/combobox/autocomplete.js +8 -6
- data/app/assets/javascripts/hw_combobox/models/combobox/dialog.js +1 -1
- data/app/assets/javascripts/hw_combobox/models/combobox/events.js +21 -6
- data/app/assets/javascripts/hw_combobox/models/combobox/filtering.js +33 -28
- data/app/assets/javascripts/hw_combobox/models/combobox/form_field.js +74 -0
- data/app/assets/javascripts/hw_combobox/models/combobox/multiselect.js +160 -0
- data/app/assets/javascripts/hw_combobox/models/combobox/navigation.js +15 -6
- data/app/assets/javascripts/hw_combobox/models/combobox/options.js +29 -9
- data/app/assets/javascripts/hw_combobox/models/combobox/selection.js +103 -51
- data/app/assets/javascripts/hw_combobox/models/combobox/toggle.js +45 -16
- data/app/assets/javascripts/hw_combobox/models/combobox/validity.js +1 -1
- data/app/assets/javascripts/hw_combobox/models/combobox.js +3 -0
- data/app/assets/stylesheets/hotwire_combobox.css +84 -18
- data/app/presenters/hotwire_combobox/component/customizable.rb +9 -1
- data/app/presenters/hotwire_combobox/component.rb +95 -28
- data/app/presenters/hotwire_combobox/listbox/group.rb +45 -0
- data/app/presenters/hotwire_combobox/listbox/item.rb +104 -0
- data/app/presenters/hotwire_combobox/listbox/option.rb +9 -4
- data/app/views/hotwire_combobox/_component.html.erb +1 -0
- data/app/views/hotwire_combobox/_selection_chip.turbo_stream.erb +8 -0
- data/app/views/hotwire_combobox/layouts/_selection_chip.turbo_stream.erb +7 -0
- data/lib/hotwire_combobox/helper.rb +111 -86
- data/lib/hotwire_combobox/version.rb +1 -1
- metadata +9 -2
@@ -79,3 +79,19 @@ export function dispatch(eventName, { target, cancelable, detail } = {}) {
|
|
79
79
|
|
80
80
|
return event
|
81
81
|
}
|
82
|
+
|
83
|
+
export function nextRepaint() {
|
84
|
+
if (document.visibilityState === "hidden") {
|
85
|
+
return nextEventLoopTick()
|
86
|
+
} else {
|
87
|
+
return nextAnimationFrame()
|
88
|
+
}
|
89
|
+
}
|
90
|
+
|
91
|
+
export function nextAnimationFrame() {
|
92
|
+
return new Promise((resolve) => requestAnimationFrame(() => resolve()))
|
93
|
+
}
|
94
|
+
|
95
|
+
export function nextEventLoopTick() {
|
96
|
+
return new Promise((resolve) => setTimeout(() => resolve(), 0))
|
97
|
+
}
|
@@ -8,16 +8,18 @@ Combobox.Autocomplete = Base => class extends Base {
|
|
8
8
|
}
|
9
9
|
}
|
10
10
|
|
11
|
-
|
12
|
-
|
11
|
+
_replaceFullQueryWithAutocompletedValue(option) {
|
12
|
+
const autocompletedValue = option.getAttribute(this.autocompletableAttributeValue)
|
13
|
+
|
14
|
+
this._fullQuery = autocompletedValue
|
15
|
+
this._actingCombobox.setSelectionRange(autocompletedValue.length, autocompletedValue.length)
|
16
|
+
}
|
13
17
|
|
18
|
+
_autocompleteMissingPortion(option) {
|
14
19
|
const typedValue = this._typedQuery
|
15
20
|
const autocompletedValue = option.getAttribute(this.autocompletableAttributeValue)
|
16
21
|
|
17
|
-
if (
|
18
|
-
this._fullQuery = autocompletedValue
|
19
|
-
this._actingCombobox.setSelectionRange(autocompletedValue.length, autocompletedValue.length)
|
20
|
-
} else if (startsWith(autocompletedValue, typedValue)) {
|
22
|
+
if (this._autocompletesInline && startsWith(autocompletedValue, typedValue)) {
|
21
23
|
this._fullQuery = autocompletedValue
|
22
24
|
this._actingCombobox.setSelectionRange(typedValue.length, autocompletedValue.length)
|
23
25
|
}
|
@@ -2,20 +2,35 @@ import Combobox from "hw_combobox/models/combobox/base"
|
|
2
2
|
import { dispatch } from "hw_combobox/helpers"
|
3
3
|
|
4
4
|
Combobox.Events = Base => class extends Base {
|
5
|
-
|
6
|
-
|
5
|
+
_dispatchPreselectionEvent({ isNewAndAllowed, previousValue }) {
|
6
|
+
if (previousValue === this._incomingFieldValueString) return
|
7
|
+
|
8
|
+
dispatch("hw-combobox:preselection", {
|
9
|
+
target: this.element,
|
10
|
+
detail: { ...this._eventableDetails, isNewAndAllowed, previousValue }
|
11
|
+
})
|
12
|
+
}
|
13
|
+
|
14
|
+
_dispatchSelectionEvent() {
|
15
|
+
dispatch("hw-combobox:selection", {
|
16
|
+
target: this.element,
|
17
|
+
detail: this._eventableDetails
|
18
|
+
})
|
7
19
|
}
|
8
20
|
|
9
|
-
|
10
|
-
dispatch("hw-combobox:
|
21
|
+
_dispatchRemovalEvent({ removedDisplay, removedValue }) {
|
22
|
+
dispatch("hw-combobox:removal", {
|
23
|
+
target: this.element,
|
24
|
+
detail: { ...this._eventableDetails, removedDisplay, removedValue }
|
25
|
+
})
|
11
26
|
}
|
12
27
|
|
13
28
|
get _eventableDetails() {
|
14
29
|
return {
|
15
|
-
value: this.
|
30
|
+
value: this._incomingFieldValueString,
|
16
31
|
display: this._fullQuery,
|
17
32
|
query: this._typedQuery,
|
18
|
-
fieldName: this.
|
33
|
+
fieldName: this._fieldName,
|
19
34
|
isValid: this._valueIsValid
|
20
35
|
}
|
21
36
|
}
|
@@ -1,58 +1,63 @@
|
|
1
1
|
|
2
2
|
import Combobox from "hw_combobox/models/combobox/base"
|
3
|
-
import { applyFilter, debounce,
|
3
|
+
import { applyFilter, debounce, unselectedPortion } from "hw_combobox/helpers"
|
4
4
|
import { get } from "hw_combobox/vendor/requestjs"
|
5
5
|
|
6
6
|
Combobox.Filtering = Base => class extends Base {
|
7
|
-
|
8
|
-
|
9
|
-
|
7
|
+
filterAndSelect({ inputType }) {
|
8
|
+
this._filter(inputType)
|
9
|
+
|
10
|
+
if (this._isSync) {
|
11
|
+
this._selectOnQuery(inputType)
|
10
12
|
} else {
|
11
|
-
|
13
|
+
// noop, async selection is handled by stimulus callbacks
|
12
14
|
}
|
13
|
-
|
14
|
-
this._actingCombobox.toggleAttribute("data-queried", this._isQueried)
|
15
15
|
}
|
16
16
|
|
17
17
|
_initializeFiltering() {
|
18
18
|
this._debouncedFilterAsync = debounce(this._debouncedFilterAsync.bind(this))
|
19
19
|
}
|
20
20
|
|
21
|
-
|
22
|
-
this.
|
21
|
+
_filter(inputType) {
|
22
|
+
if (this._isAsync) {
|
23
|
+
this._debouncedFilterAsync(inputType)
|
24
|
+
} else {
|
25
|
+
this._filterSync()
|
26
|
+
}
|
27
|
+
|
28
|
+
this._markQueried()
|
29
|
+
}
|
30
|
+
|
31
|
+
_debouncedFilterAsync(inputType) {
|
32
|
+
this._filterAsync(inputType)
|
23
33
|
}
|
24
34
|
|
25
|
-
async _filterAsync(
|
35
|
+
async _filterAsync(inputType) {
|
26
36
|
const query = {
|
27
37
|
q: this._fullQuery,
|
28
|
-
input_type:
|
38
|
+
input_type: inputType,
|
29
39
|
for_id: this.element.dataset.asyncId
|
30
40
|
}
|
31
41
|
|
32
42
|
await get(this.asyncSrcValue, { responseKind: "turbo-stream", query })
|
33
43
|
}
|
34
44
|
|
35
|
-
_filterSync(
|
36
|
-
this.
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
if (this._shouldTreatAsNewOptionForFiltering(!isDeleteEvent(event))) {
|
43
|
-
this._selectNew()
|
44
|
-
} else if (isDeleteEvent(event)) {
|
45
|
-
this._deselect()
|
46
|
-
} else if (event.inputType === "hw:lockInSelection") {
|
47
|
-
this._select(this._ensurableOption)
|
48
|
-
} else if (this._isOpen) {
|
49
|
-
this._select(this._visibleOptionElements[0])
|
50
|
-
}
|
45
|
+
_filterSync() {
|
46
|
+
this._allFilterableOptionElements.forEach(
|
47
|
+
applyFilter(
|
48
|
+
this._fullQuery,
|
49
|
+
{ matching: this.filterableAttributeValue }
|
50
|
+
)
|
51
|
+
)
|
51
52
|
}
|
52
53
|
|
53
54
|
_clearQuery() {
|
54
55
|
this._fullQuery = ""
|
55
|
-
this.
|
56
|
+
this.filterAndSelect({ inputType: "deleteContentBackward" })
|
57
|
+
}
|
58
|
+
|
59
|
+
_markQueried() {
|
60
|
+
this._actingCombobox.toggleAttribute("data-queried", this._isQueried)
|
56
61
|
}
|
57
62
|
|
58
63
|
get _isQueried() {
|
@@ -0,0 +1,74 @@
|
|
1
|
+
import Combobox from "hw_combobox/models/combobox/base"
|
2
|
+
|
3
|
+
Combobox.FormField = Base => class extends Base {
|
4
|
+
get _fieldValue() {
|
5
|
+
if (this._isMultiselect) {
|
6
|
+
const currentValue = this.hiddenFieldTarget.value
|
7
|
+
const arrayFromValue = currentValue ? currentValue.split(",") : []
|
8
|
+
|
9
|
+
return new Set(arrayFromValue)
|
10
|
+
} else {
|
11
|
+
return this.hiddenFieldTarget.value
|
12
|
+
}
|
13
|
+
}
|
14
|
+
|
15
|
+
get _fieldValueString() {
|
16
|
+
if (this._isMultiselect) {
|
17
|
+
return this._fieldValueArray.join(",")
|
18
|
+
} else {
|
19
|
+
return this.hiddenFieldTarget.value
|
20
|
+
}
|
21
|
+
}
|
22
|
+
|
23
|
+
get _incomingFieldValueString() {
|
24
|
+
if (this._isMultiselect) {
|
25
|
+
const array = this._fieldValueArray
|
26
|
+
|
27
|
+
if (this.hiddenFieldTarget.dataset.valueForMultiselect) {
|
28
|
+
array.push(this.hiddenFieldTarget.dataset.valueForMultiselect)
|
29
|
+
}
|
30
|
+
|
31
|
+
return array.join(",")
|
32
|
+
} else {
|
33
|
+
return this.hiddenFieldTarget.value
|
34
|
+
}
|
35
|
+
}
|
36
|
+
|
37
|
+
get _fieldValueArray() {
|
38
|
+
if (this._isMultiselect) {
|
39
|
+
return Array.from(this._fieldValue)
|
40
|
+
} else {
|
41
|
+
return [ this.hiddenFieldTarget.value ]
|
42
|
+
}
|
43
|
+
}
|
44
|
+
|
45
|
+
set _fieldValue(value) {
|
46
|
+
if (this._isMultiselect) {
|
47
|
+
this.hiddenFieldTarget.dataset.valueForMultiselect = value?.replace(/,/g, "")
|
48
|
+
this.hiddenFieldTarget.dataset.displayForMultiselect = this._fullQuery
|
49
|
+
} else {
|
50
|
+
this.hiddenFieldTarget.value = value
|
51
|
+
}
|
52
|
+
}
|
53
|
+
|
54
|
+
get _hasEmptyFieldValue() {
|
55
|
+
if (this._isMultiselect) {
|
56
|
+
return this.hiddenFieldTarget.dataset.valueForMultiselect == "" ||
|
57
|
+
this.hiddenFieldTarget.dataset.valueForMultiselect == "undefined"
|
58
|
+
} else {
|
59
|
+
return this.hiddenFieldTarget.value === ""
|
60
|
+
}
|
61
|
+
}
|
62
|
+
|
63
|
+
get _hasFieldValue() {
|
64
|
+
return !this._hasEmptyFieldValue
|
65
|
+
}
|
66
|
+
|
67
|
+
get _fieldName() {
|
68
|
+
return this.hiddenFieldTarget.name
|
69
|
+
}
|
70
|
+
|
71
|
+
set _fieldName(value) {
|
72
|
+
this.hiddenFieldTarget.name = value
|
73
|
+
}
|
74
|
+
}
|
@@ -0,0 +1,160 @@
|
|
1
|
+
import Combobox from "hw_combobox/models/combobox/base"
|
2
|
+
import { cancel, nextRepaint } from "hw_combobox/helpers"
|
3
|
+
import { get } from "hw_combobox/vendor/requestjs"
|
4
|
+
|
5
|
+
Combobox.Multiselect = Base => class extends Base {
|
6
|
+
navigateChip(event) {
|
7
|
+
this._chipKeyHandlers[event.key]?.call(this, event)
|
8
|
+
}
|
9
|
+
|
10
|
+
removeChip({ currentTarget, params }) {
|
11
|
+
let display
|
12
|
+
const option = this._optionElementWithValue(params.value)
|
13
|
+
|
14
|
+
if (option) {
|
15
|
+
display = option.getAttribute(this.autocompletableAttributeValue)
|
16
|
+
this._markNotSelected(option)
|
17
|
+
this._markNotMultiselected(option)
|
18
|
+
} else {
|
19
|
+
display = params.value // for new options
|
20
|
+
}
|
21
|
+
|
22
|
+
this._removeFromFieldValue(params.value)
|
23
|
+
this._filter("hw:multiselectSync")
|
24
|
+
|
25
|
+
currentTarget.closest("[data-hw-combobox-chip]").remove()
|
26
|
+
|
27
|
+
if (!this._isSmallViewport) {
|
28
|
+
this.openByFocusing()
|
29
|
+
}
|
30
|
+
|
31
|
+
this._announceToScreenReader(display, "removed")
|
32
|
+
this._dispatchRemovalEvent({ removedDisplay: display, removedValue: params.value })
|
33
|
+
}
|
34
|
+
|
35
|
+
hideChipsForCache() {
|
36
|
+
this.element.querySelectorAll("[data-hw-combobox-chip]").forEach(chip => chip.hidden = true)
|
37
|
+
}
|
38
|
+
|
39
|
+
_chipKeyHandlers = {
|
40
|
+
Backspace: (event) => {
|
41
|
+
this.removeChip(event)
|
42
|
+
cancel(event)
|
43
|
+
},
|
44
|
+
Enter: (event) => {
|
45
|
+
this.removeChip(event)
|
46
|
+
cancel(event)
|
47
|
+
},
|
48
|
+
Space: (event) => {
|
49
|
+
this.removeChip(event)
|
50
|
+
cancel(event)
|
51
|
+
},
|
52
|
+
Escape: (event) => {
|
53
|
+
this.openByFocusing()
|
54
|
+
cancel(event)
|
55
|
+
}
|
56
|
+
}
|
57
|
+
|
58
|
+
_initializeMultiselect() {
|
59
|
+
if (!this._isMultiPreselected) {
|
60
|
+
this._preselectMultiple()
|
61
|
+
this._markMultiPreselected()
|
62
|
+
}
|
63
|
+
}
|
64
|
+
|
65
|
+
async _createChip(shouldReopen) {
|
66
|
+
if (!this._isMultiselect) return
|
67
|
+
|
68
|
+
this._beforeClearingMultiselectQuery(async (display, value) => {
|
69
|
+
this._fullQuery = ""
|
70
|
+
this._filter("hw:multiselectSync")
|
71
|
+
this._requestChips(value)
|
72
|
+
this._addToFieldValue(value)
|
73
|
+
if (shouldReopen) {
|
74
|
+
await nextRepaint()
|
75
|
+
this.openByFocusing()
|
76
|
+
}
|
77
|
+
this._announceToScreenReader(display, "multi-selected. Press Shift + Tab, then Enter to remove.")
|
78
|
+
})
|
79
|
+
}
|
80
|
+
|
81
|
+
async _requestChips(values) {
|
82
|
+
await get(this.selectionChipSrcValue, {
|
83
|
+
responseKind: "turbo-stream",
|
84
|
+
query: {
|
85
|
+
for_id: this.element.dataset.asyncId,
|
86
|
+
combobox_values: values
|
87
|
+
}
|
88
|
+
})
|
89
|
+
}
|
90
|
+
|
91
|
+
_beforeClearingMultiselectQuery(callback) {
|
92
|
+
const display = this.hiddenFieldTarget.dataset.displayForMultiselect
|
93
|
+
const value = this.hiddenFieldTarget.dataset.valueForMultiselect
|
94
|
+
|
95
|
+
if (value && !this._fieldValue.has(value)) {
|
96
|
+
callback(display, value)
|
97
|
+
}
|
98
|
+
|
99
|
+
this.hiddenFieldTarget.dataset.displayForMultiselect = ""
|
100
|
+
this.hiddenFieldTarget.dataset.valueForMultiselect = ""
|
101
|
+
}
|
102
|
+
|
103
|
+
_resetMultiselectionMarks() {
|
104
|
+
if (!this._isMultiselect) return
|
105
|
+
|
106
|
+
this._fieldValueArray.forEach(value => {
|
107
|
+
const option = this._optionElementWithValue(value)
|
108
|
+
|
109
|
+
if (option) {
|
110
|
+
option.setAttribute("data-multiselected", "")
|
111
|
+
option.hidden = true
|
112
|
+
}
|
113
|
+
})
|
114
|
+
}
|
115
|
+
|
116
|
+
_markNotMultiselected(option) {
|
117
|
+
if (!this._isMultiselect) return
|
118
|
+
|
119
|
+
option.removeAttribute("data-multiselected")
|
120
|
+
option.hidden = false
|
121
|
+
}
|
122
|
+
|
123
|
+
_addToFieldValue(value) {
|
124
|
+
const newValue = this._fieldValue
|
125
|
+
|
126
|
+
newValue.add(String(value))
|
127
|
+
this.hiddenFieldTarget.value = Array.from(newValue).join(",")
|
128
|
+
|
129
|
+
if (this._isSync) this._resetMultiselectionMarks()
|
130
|
+
}
|
131
|
+
|
132
|
+
_removeFromFieldValue(value) {
|
133
|
+
const newValue = this._fieldValue
|
134
|
+
|
135
|
+
newValue.delete(String(value))
|
136
|
+
this.hiddenFieldTarget.value = Array.from(newValue).join(",")
|
137
|
+
|
138
|
+
if (this._isSync) this._resetMultiselectionMarks()
|
139
|
+
}
|
140
|
+
|
141
|
+
_focusLastChipDismisser() {
|
142
|
+
this.chipDismisserTargets[this.chipDismisserTargets.length - 1]?.focus()
|
143
|
+
}
|
144
|
+
|
145
|
+
_markMultiPreselected() {
|
146
|
+
this.element.dataset.multiPreselected = ""
|
147
|
+
}
|
148
|
+
|
149
|
+
get _isMultiselect() {
|
150
|
+
return this.hasSelectionChipSrcValue
|
151
|
+
}
|
152
|
+
|
153
|
+
get _isSingleSelect() {
|
154
|
+
return !this._isMultiselect
|
155
|
+
}
|
156
|
+
|
157
|
+
get _isMultiPreselected() {
|
158
|
+
return this.element.hasAttribute("data-multi-preselected")
|
159
|
+
}
|
160
|
+
}
|
@@ -4,17 +4,22 @@ import { cancel } from "hw_combobox/helpers"
|
|
4
4
|
Combobox.Navigation = Base => class extends Base {
|
5
5
|
navigate(event) {
|
6
6
|
if (this._autocompletesList) {
|
7
|
-
this.
|
7
|
+
this._navigationKeyHandlers[event.key]?.call(this, event)
|
8
8
|
}
|
9
9
|
}
|
10
10
|
|
11
|
-
|
11
|
+
_navigationKeyHandlers = {
|
12
12
|
ArrowUp: (event) => {
|
13
13
|
this._selectIndex(this._selectedOptionIndex - 1)
|
14
14
|
cancel(event)
|
15
15
|
},
|
16
16
|
ArrowDown: (event) => {
|
17
17
|
this._selectIndex(this._selectedOptionIndex + 1)
|
18
|
+
|
19
|
+
if (this._selectedOptionIndex === 0) {
|
20
|
+
this._actingListbox.scrollTop = 0
|
21
|
+
}
|
22
|
+
|
18
23
|
cancel(event)
|
19
24
|
},
|
20
25
|
Home: (event) => {
|
@@ -26,14 +31,18 @@ Combobox.Navigation = Base => class extends Base {
|
|
26
31
|
cancel(event)
|
27
32
|
},
|
28
33
|
Enter: (event) => {
|
29
|
-
this.
|
30
|
-
this._actingCombobox.blur()
|
34
|
+
this._closeAndBlur("hw:keyHandler:enter")
|
31
35
|
cancel(event)
|
32
36
|
},
|
33
37
|
Escape: (event) => {
|
34
|
-
this.
|
35
|
-
this._actingCombobox.blur()
|
38
|
+
this._closeAndBlur("hw:keyHandler:escape")
|
36
39
|
cancel(event)
|
40
|
+
},
|
41
|
+
Backspace: (event) => {
|
42
|
+
if (this._isMultiselect && !this._fullQuery) {
|
43
|
+
this._focusLastChipDismisser()
|
44
|
+
cancel(event)
|
45
|
+
}
|
37
46
|
}
|
38
47
|
}
|
39
48
|
}
|
@@ -2,9 +2,25 @@ import Combobox from "hw_combobox/models/combobox/base"
|
|
2
2
|
import { visible } from "hw_combobox/helpers"
|
3
3
|
|
4
4
|
Combobox.Options = Base => class extends Base {
|
5
|
-
|
6
|
-
this._deselect()
|
7
|
-
|
5
|
+
_resetOptionsSilently() {
|
6
|
+
this._resetOptions(this._deselect.bind(this))
|
7
|
+
}
|
8
|
+
|
9
|
+
_resetOptionsAndNotify() {
|
10
|
+
this._resetOptions(this._deselectAndNotify.bind(this))
|
11
|
+
}
|
12
|
+
|
13
|
+
_resetOptions(deselectionStrategy) {
|
14
|
+
this._fieldName = this.originalNameValue
|
15
|
+
deselectionStrategy()
|
16
|
+
}
|
17
|
+
|
18
|
+
_optionElementWithValue(value) {
|
19
|
+
return this._actingListbox.querySelector(`[${this.filterableAttributeValue}][data-value='${value}']`)
|
20
|
+
}
|
21
|
+
|
22
|
+
_displayForOptionElement(element) {
|
23
|
+
return element.getAttribute(this.autocompletableAttributeValue)
|
8
24
|
}
|
9
25
|
|
10
26
|
get _allowNew() {
|
@@ -12,19 +28,23 @@ Combobox.Options = Base => class extends Base {
|
|
12
28
|
}
|
13
29
|
|
14
30
|
get _allOptions() {
|
15
|
-
return Array.from(this.
|
31
|
+
return Array.from(this._allFilterableOptionElements)
|
16
32
|
}
|
17
33
|
|
18
|
-
get
|
19
|
-
return this._actingListbox.querySelectorAll(`[${this.filterableAttributeValue}]`)
|
34
|
+
get _allFilterableOptionElements() {
|
35
|
+
return this._actingListbox.querySelectorAll(`[${this.filterableAttributeValue}]:not([data-multiselected])`)
|
20
36
|
}
|
21
37
|
|
22
38
|
get _visibleOptionElements() {
|
23
|
-
return [ ...this.
|
39
|
+
return [ ...this._allFilterableOptionElements ].filter(visible)
|
24
40
|
}
|
25
41
|
|
26
42
|
get _selectedOptionElement() {
|
27
|
-
return this._actingListbox.querySelector("[role=option][aria-selected=true]")
|
43
|
+
return this._actingListbox.querySelector("[role=option][aria-selected=true]:not([data-multiselected])")
|
44
|
+
}
|
45
|
+
|
46
|
+
get _multiselectedOptionElements() {
|
47
|
+
return this._actingListbox.querySelectorAll("[role=option][data-multiselected]")
|
28
48
|
}
|
29
49
|
|
30
50
|
get _selectedOptionIndex() {
|
@@ -32,7 +52,7 @@ Combobox.Options = Base => class extends Base {
|
|
32
52
|
}
|
33
53
|
|
34
54
|
get _isUnjustifiablyBlank() {
|
35
|
-
const valueIsMissing =
|
55
|
+
const valueIsMissing = this._hasEmptyFieldValue
|
36
56
|
const noBlankOptionSelected = !this._selectedOptionElement
|
37
57
|
|
38
58
|
return valueIsMissing && noBlankOptionSelected
|