hotwire_combobox 0.1.35 → 0.1.36
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/app/assets/javascripts/controllers/hw_combobox_controller.js +7 -3
- data/app/assets/javascripts/hw_combobox/helpers.js +13 -1
- data/app/assets/javascripts/hw_combobox/models/combobox/autocomplete.js +6 -6
- data/app/assets/javascripts/hw_combobox/models/combobox/dialog.js +2 -2
- data/app/assets/javascripts/hw_combobox/models/combobox/filtering.js +10 -8
- data/app/assets/javascripts/hw_combobox/models/combobox/selection.js +10 -10
- data/app/assets/javascripts/hw_combobox/models/combobox/toggle.js +1 -2
- data/lib/hotwire_combobox/version.rb +1 -1
- metadata +4 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 446cd3c4beeb66029cab7cd6b92ac04c2a8c7a1f08ffd874728da2a3e605ead7
|
4
|
+
data.tar.gz: 9783eb83824ebeff3fa55536002c24e129fc66ed0b9d82e33c808a8d65c6c3ee
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a4d5e3d5fddd0017fc6bb6e838a60550d0e7dc2da3fac8999befa092ad9a8a7d35753cc785271fdfb8fe43e63edc535dc97f441594c8be2a3eece394622b97d6
|
7
|
+
data.tar.gz: 1d60ba35f6f043b9698d7b9bd48aadd702e67c00cb626068e797890ebcc3cfd370af14c33479da153f57096c1015c062f7539676da55aac559d76b38234a4d24
|
@@ -1,7 +1,9 @@
|
|
1
1
|
import Combobox from "hw_combobox/models/combobox"
|
2
|
-
import { Concerns } from "hw_combobox/helpers"
|
2
|
+
import { Concerns, sleep } from "hw_combobox/helpers"
|
3
3
|
import { Controller } from "@hotwired/stimulus"
|
4
4
|
|
5
|
+
window.HOTWIRE_COMBOBOX_STREAM_DELAY = 0 // ms, for testing purposes
|
6
|
+
|
5
7
|
const concerns = [
|
6
8
|
Controller,
|
7
9
|
Combobox.Actors,
|
@@ -70,10 +72,12 @@ export default class HwComboboxController extends Concerns(...concerns) {
|
|
70
72
|
}
|
71
73
|
}
|
72
74
|
|
73
|
-
endOfOptionsStreamTargetConnected(element) {
|
75
|
+
async endOfOptionsStreamTargetConnected(element) {
|
74
76
|
const inputType = element.dataset.inputType
|
77
|
+
const delay = window.HOTWIRE_COMBOBOX_STREAM_DELAY
|
75
78
|
|
76
|
-
if (inputType && inputType !== "hw:
|
79
|
+
if (inputType && inputType !== "hw:lockInSelection") {
|
80
|
+
if (delay) await sleep(delay)
|
77
81
|
this._commitFilter({ inputType })
|
78
82
|
} else {
|
79
83
|
this._preselectOption()
|
@@ -37,7 +37,7 @@ export function startsWith(string, substring) {
|
|
37
37
|
return string.toLowerCase().startsWith(substring.toLowerCase())
|
38
38
|
}
|
39
39
|
|
40
|
-
export function debounce(fn, delay =
|
40
|
+
export function debounce(fn, delay = 150) {
|
41
41
|
let timeoutId = null
|
42
42
|
|
43
43
|
return (...args) => {
|
@@ -50,3 +50,15 @@ export function debounce(fn, delay = 300) {
|
|
50
50
|
export function isDeleteEvent(event) {
|
51
51
|
return event.inputType === "deleteContentBackward" || event.inputType === "deleteWordBackward"
|
52
52
|
}
|
53
|
+
|
54
|
+
export function sleep(ms) {
|
55
|
+
return new Promise(resolve => setTimeout(resolve, ms))
|
56
|
+
}
|
57
|
+
|
58
|
+
export function unselectedPortion(element) {
|
59
|
+
if (element.selectionStart === element.selectionEnd) {
|
60
|
+
return element.value
|
61
|
+
} else {
|
62
|
+
return element.value.substring(0, element.selectionStart)
|
63
|
+
}
|
64
|
+
}
|
@@ -11,14 +11,14 @@ Combobox.Autocomplete = Base => class extends Base {
|
|
11
11
|
_autocompleteWith(option, { force }) {
|
12
12
|
if (!this._autocompletesInline && !force) return
|
13
13
|
|
14
|
-
const typedValue = this.
|
14
|
+
const typedValue = this._typedQuery
|
15
15
|
const autocompletedValue = option.getAttribute(this.autocompletableAttributeValue)
|
16
16
|
|
17
17
|
if (force) {
|
18
|
-
this.
|
18
|
+
this._fullQuery = autocompletedValue
|
19
19
|
this._actingCombobox.setSelectionRange(autocompletedValue.length, autocompletedValue.length)
|
20
20
|
} else if (startsWith(autocompletedValue, typedValue)) {
|
21
|
-
this.
|
21
|
+
this._fullQuery = autocompletedValue
|
22
22
|
this._actingCombobox.setSelectionRange(typedValue.length, autocompletedValue.length)
|
23
23
|
}
|
24
24
|
}
|
@@ -30,14 +30,14 @@ Combobox.Autocomplete = Base => class extends Base {
|
|
30
30
|
}
|
31
31
|
|
32
32
|
get _isExactAutocompleteMatch() {
|
33
|
-
return this._immediatelyAutocompletableValue === this.
|
33
|
+
return this._immediatelyAutocompletableValue === this._fullQuery
|
34
34
|
}
|
35
35
|
|
36
36
|
// All `_isExactAutocompleteMatch` matches are `_isPartialAutocompleteMatch` matches
|
37
37
|
// but not all `_isPartialAutocompleteMatch` matches are `_isExactAutocompleteMatch` matches.
|
38
38
|
get _isPartialAutocompleteMatch() {
|
39
39
|
return !!this._immediatelyAutocompletableValue &&
|
40
|
-
startsWith(this._immediatelyAutocompletableValue, this.
|
40
|
+
startsWith(this._immediatelyAutocompletableValue, this._fullQuery)
|
41
41
|
}
|
42
42
|
|
43
43
|
get _autocompletesList() {
|
@@ -49,6 +49,6 @@ Combobox.Autocomplete = Base => class extends Base {
|
|
49
49
|
}
|
50
50
|
|
51
51
|
get _immediatelyAutocompletableValue() {
|
52
|
-
return this.
|
52
|
+
return this._ensurableOption?.getAttribute(this.autocompletableAttributeValue)
|
53
53
|
}
|
54
54
|
}
|
@@ -14,7 +14,7 @@ Combobox.Dialog = Base => class extends Base {
|
|
14
14
|
}
|
15
15
|
|
16
16
|
_moveArtifactsToDialog() {
|
17
|
-
this.dialogComboboxTarget.value = this.
|
17
|
+
this.dialogComboboxTarget.value = this._fullQuery
|
18
18
|
|
19
19
|
this._actingCombobox = this.dialogComboboxTarget
|
20
20
|
this._actingListbox = this.dialogListboxTarget
|
@@ -23,7 +23,7 @@ Combobox.Dialog = Base => class extends Base {
|
|
23
23
|
}
|
24
24
|
|
25
25
|
_moveArtifactsInline() {
|
26
|
-
this.comboboxTarget.value = this.
|
26
|
+
this.comboboxTarget.value = this._fullQuery
|
27
27
|
|
28
28
|
this._actingCombobox = this.comboboxTarget
|
29
29
|
this._actingListbox = this.listboxTarget
|
@@ -1,6 +1,6 @@
|
|
1
1
|
|
2
2
|
import Combobox from "hw_combobox/models/combobox/base"
|
3
|
-
import { applyFilter, debounce, isDeleteEvent } from "hw_combobox/helpers"
|
3
|
+
import { applyFilter, debounce, isDeleteEvent, unselectedPortion } from "hw_combobox/helpers"
|
4
4
|
import { get } from "hw_combobox/vendor/requestjs"
|
5
5
|
|
6
6
|
Combobox.Filtering = Base => class extends Base {
|
@@ -21,13 +21,13 @@ Combobox.Filtering = Base => class extends Base {
|
|
21
21
|
}
|
22
22
|
|
23
23
|
async _filterAsync(event) {
|
24
|
-
const query = { q: this.
|
24
|
+
const query = { q: this._fullQuery, input_type: event.inputType }
|
25
25
|
await get(this.asyncSrcValue, { responseKind: "turbo-stream", query })
|
26
26
|
}
|
27
27
|
|
28
28
|
_filterSync(event) {
|
29
29
|
this.open()
|
30
|
-
this._allOptionElements.forEach(applyFilter(this.
|
30
|
+
this._allOptionElements.forEach(applyFilter(this._fullQuery, { matching: this.filterableAttributeValue }))
|
31
31
|
this._commitFilter(event)
|
32
32
|
}
|
33
33
|
|
@@ -42,16 +42,18 @@ Combobox.Filtering = Base => class extends Base {
|
|
42
42
|
}
|
43
43
|
|
44
44
|
get _isQueried() {
|
45
|
-
return this.
|
45
|
+
return this._fullQuery.length > 0
|
46
46
|
}
|
47
47
|
|
48
|
-
|
49
|
-
// after a certain point in the call chain.
|
50
|
-
get _query() {
|
48
|
+
get _fullQuery() {
|
51
49
|
return this._actingCombobox.value
|
52
50
|
}
|
53
51
|
|
54
|
-
set
|
52
|
+
set _fullQuery(value) {
|
55
53
|
this._actingCombobox.value = value
|
56
54
|
}
|
55
|
+
|
56
|
+
get _typedQuery() {
|
57
|
+
return unselectedPortion(this._actingCombobox)
|
58
|
+
}
|
57
59
|
}
|
@@ -10,16 +10,16 @@ Combobox.Selection = Base => class extends Base {
|
|
10
10
|
|
11
11
|
_connectSelection() {
|
12
12
|
if (this.hasPrefilledDisplayValue) {
|
13
|
-
this.
|
13
|
+
this._fullQuery = this.prefilledDisplayValue
|
14
14
|
}
|
15
15
|
}
|
16
16
|
|
17
|
-
_select(option, {
|
17
|
+
_select(option, { forceAutocomplete = false } = {}) {
|
18
18
|
this._resetOptions()
|
19
19
|
|
20
20
|
if (option) {
|
21
21
|
this._markValid()
|
22
|
-
this._autocompleteWith(option, { force })
|
22
|
+
this._autocompleteWith(option, { force: forceAutocomplete })
|
23
23
|
this._commitSelection(option, { selected: true })
|
24
24
|
} else {
|
25
25
|
this._markInvalid()
|
@@ -51,13 +51,13 @@ Combobox.Selection = Base => class extends Base {
|
|
51
51
|
|
52
52
|
_selectNew() {
|
53
53
|
this._resetOptions()
|
54
|
-
this.hiddenFieldTarget.value = this.
|
54
|
+
this.hiddenFieldTarget.value = this._fullQuery
|
55
55
|
this.hiddenFieldTarget.name = this.nameWhenNewValue
|
56
56
|
}
|
57
57
|
|
58
58
|
_selectIndex(index) {
|
59
59
|
const option = wrapAroundAccess(this._visibleOptionElements, index)
|
60
|
-
this._select(option, {
|
60
|
+
this._select(option, { forceAutocomplete: true })
|
61
61
|
}
|
62
62
|
|
63
63
|
_preselectOption() {
|
@@ -70,10 +70,10 @@ Combobox.Selection = Base => class extends Base {
|
|
70
70
|
}
|
71
71
|
}
|
72
72
|
|
73
|
-
|
74
|
-
if (this.
|
75
|
-
this._select(this._ensurableOption, {
|
76
|
-
this.filter({ inputType: "hw:
|
73
|
+
_lockInSelection() {
|
74
|
+
if (this._shouldLockInSelection) {
|
75
|
+
this._select(this._ensurableOption, { forceAutocomplete: true })
|
76
|
+
this.filter({ inputType: "hw:lockInSelection" })
|
77
77
|
}
|
78
78
|
}
|
79
79
|
|
@@ -81,7 +81,7 @@ Combobox.Selection = Base => class extends Base {
|
|
81
81
|
return this.hiddenFieldTarget.value && !this._selectedOptionElement
|
82
82
|
}
|
83
83
|
|
84
|
-
get
|
84
|
+
get _shouldLockInSelection() {
|
85
85
|
return this._isQueried && !!this._ensurableOption && !this._isNewOptionWithPotentialMatches
|
86
86
|
}
|
87
87
|
|
@@ -8,7 +8,7 @@ Combobox.Toggle = Base => class extends Base {
|
|
8
8
|
|
9
9
|
close() {
|
10
10
|
if (this._isOpen) {
|
11
|
-
this.
|
11
|
+
this._lockInSelection()
|
12
12
|
this.expandedValue = false
|
13
13
|
}
|
14
14
|
}
|
@@ -34,7 +34,6 @@ Combobox.Toggle = Base => class extends Base {
|
|
34
34
|
closeOnFocusOutside({ target }) {
|
35
35
|
if (!this._isOpen) return
|
36
36
|
if (this.element.contains(target)) return
|
37
|
-
if (target.matches("main")) return
|
38
37
|
|
39
38
|
this.close()
|
40
39
|
}
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: hotwire_combobox
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.36
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jose Farias
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-02
|
11
|
+
date: 2024-03-02 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|
@@ -52,7 +52,8 @@ dependencies:
|
|
52
52
|
- - ">="
|
53
53
|
- !ruby/object:Gem::Version
|
54
54
|
version: '1.2'
|
55
|
-
description:
|
55
|
+
description: An autocomplete combobox implementation for Ruby on Rails apps using
|
56
|
+
Hotwire.
|
56
57
|
email:
|
57
58
|
- jose@farias.mx
|
58
59
|
executables: []
|