hotwire_combobox 0.1.33 → 0.1.35

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 415f50ba9630121312ea65c8f04435ff57ba386ffb9851dc9e09da8a6dc68352
4
- data.tar.gz: de3d2d2bfb9d9f9e21292acf49152484ad05317b16daade967ea695afbf3bc87
3
+ metadata.gz: 4b9f936869948e6767d3cdf4f02f226bfe4c3954d57172b37388239884b69ae6
4
+ data.tar.gz: b5495f56d66183c5ebb6c54a6a8e5278391df921f304b36b9da9ffb3bf752576
5
5
  SHA512:
6
- metadata.gz: aab8d6575ec896e26035b5040afa0765b3dacd92d101589b20f305840a5831174cad852325bc64971e83f182260454d2d836be665f19a33cf845d972d90b8134
7
- data.tar.gz: 5f4393d0d3dbf29c9bf194210cf7d845c35062a7fd4d952e6acafae2c407ce158d63735f703419311c15ab3996002fa573617cbff26b98406d7e98bdf0e85e9f
6
+ metadata.gz: 7bb2f124171055bc2b46c7584f31b3ae6426979dd9485645e8364bef559aa86cfa905f62f0db47e79d48db98f42d422b9c8fd9f342729e7da4704385d8544e3b
7
+ data.tar.gz: 27e49fa1cfd828d23f7dcf82b75a2ba7d7edc4adc8fcff88d35cab8cc41e666f39dffb82209c68740e8b4d9ec9236b1db3513e7c2952cf80cf6f96dc7608a39e
data/README.md CHANGED
@@ -22,10 +22,6 @@ Only apps that use importmaps are currently supported. Suport for other JS solut
22
22
 
23
23
  ## Docs
24
24
 
25
- <p align="center">
26
- <img src="docs/assets/images/docs-preview.png" height=500>
27
- </p>
28
-
29
25
  Visit [the docs site](https://hotwirecombobox.com/) for a demo and detailed documentation.
30
26
  If the site is down, you can run the docs locally by cloning [the docs repo](https://github.com/josefarias/hotwire_combobox_docs).
31
27
 
@@ -29,10 +29,10 @@ export default class HwComboboxController extends Concerns(...concerns) {
29
29
  "dialogCombobox",
30
30
  "dialogFocusTrap",
31
31
  "dialogListbox",
32
+ "endOfOptionsStream",
32
33
  "handle",
33
34
  "hiddenField",
34
- "listbox",
35
- "paginationFrame"
35
+ "listbox"
36
36
  ]
37
37
 
38
38
  static values = {
@@ -70,7 +70,13 @@ export default class HwComboboxController extends Concerns(...concerns) {
70
70
  }
71
71
  }
72
72
 
73
- paginationFrameTargetConnected() {
74
- this._preselectOption()
73
+ endOfOptionsStreamTargetConnected(element) {
74
+ const inputType = element.dataset.inputType
75
+
76
+ if (inputType && inputType !== "hw:ensureSelection") {
77
+ this._commitFilter({ inputType })
78
+ } else {
79
+ this._preselectOption()
80
+ }
75
81
  }
76
82
  }
@@ -1,5 +1,3 @@
1
- export const nullEvent = new Event("NULL")
2
-
3
1
  export function Concerns(Base, ...mixins) {
4
2
  return mixins.reduce((accumulator, current) => current(accumulator), Base)
5
3
  }
@@ -39,11 +37,7 @@ export function startsWith(string, substring) {
39
37
  return string.toLowerCase().startsWith(substring.toLowerCase())
40
38
  }
41
39
 
42
- export function nextFrame() {
43
- return new Promise(requestAnimationFrame)
44
- }
45
-
46
- export function debounce(fn, delay = 150) {
40
+ export function debounce(fn, delay = 300) {
47
41
  let timeoutId = null
48
42
 
49
43
  return (...args) => {
@@ -8,17 +8,17 @@ Combobox.Autocomplete = Base => class extends Base {
8
8
  }
9
9
  }
10
10
 
11
- _maybeAutocompleteWith(option, { force }) {
11
+ _autocompleteWith(option, { force }) {
12
12
  if (!this._autocompletesInline && !force) return
13
13
 
14
- const typedValue = this._actingCombobox.value
14
+ const typedValue = this._query
15
15
  const autocompletedValue = option.getAttribute(this.autocompletableAttributeValue)
16
16
 
17
17
  if (force) {
18
- this._actingCombobox.value = autocompletedValue
18
+ this._query = autocompletedValue
19
19
  this._actingCombobox.setSelectionRange(autocompletedValue.length, autocompletedValue.length)
20
20
  } else if (startsWith(autocompletedValue, typedValue)) {
21
- this._actingCombobox.value = autocompletedValue
21
+ this._query = 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._actingCombobox.value
33
+ return this._immediatelyAutocompletableValue === this._query
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._actingCombobox.value)
40
+ startsWith(this._immediatelyAutocompletableValue, this._query)
41
41
  }
42
42
 
43
43
  get _autocompletesList() {
@@ -14,7 +14,7 @@ Combobox.Dialog = Base => class extends Base {
14
14
  }
15
15
 
16
16
  _moveArtifactsToDialog() {
17
- this.dialogComboboxTarget.value = this._actingCombobox.value
17
+ this.dialogComboboxTarget.value = this._query
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._actingCombobox.value
26
+ this.comboboxTarget.value = this._query
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, nextFrame, debounce, isDeleteEvent } from "hw_combobox/helpers"
3
+ import { applyFilter, debounce, isDeleteEvent } from "hw_combobox/helpers"
4
4
  import { get } from "hw_combobox/vendor/requestjs"
5
5
 
6
6
  Combobox.Filtering = Base => class extends Base {
@@ -21,20 +21,13 @@ Combobox.Filtering = Base => class extends Base {
21
21
  }
22
22
 
23
23
  async _filterAsync(event) {
24
- const q = this._actingCombobox.value.trim()
25
-
26
- await get(this.asyncSrcValue, { responseKind: "turbo-stream", query: { q } })
27
-
28
- this._afterTurboStreamRender(() => this._commitFilter(event))
24
+ const query = { q: this._query, input_type: event.inputType }
25
+ await get(this.asyncSrcValue, { responseKind: "turbo-stream", query })
29
26
  }
30
27
 
31
28
  _filterSync(event) {
32
- const query = this._actingCombobox.value.trim()
33
-
34
29
  this.open()
35
-
36
- this._allOptionElements.forEach(applyFilter(query, { matching: this.filterableAttributeValue }))
37
-
30
+ this._allOptionElements.forEach(applyFilter(this._query, { matching: this.filterableAttributeValue }))
38
31
  this._commitFilter(event)
39
32
  }
40
33
 
@@ -48,12 +41,17 @@ Combobox.Filtering = Base => class extends Base {
48
41
  }
49
42
  }
50
43
 
51
- async _afterTurboStreamRender(callback) {
52
- await nextFrame()
53
- callback()
44
+ get _isQueried() {
45
+ return this._query.length > 0
54
46
  }
55
47
 
56
- get _isQueried() {
57
- return this._actingCombobox.value.length > 0
48
+ // Consider +_query+ will contain the full autocompleted value
49
+ // after a certain point in the call chain.
50
+ get _query() {
51
+ return this._actingCombobox.value
52
+ }
53
+
54
+ set _query(value) {
55
+ this._actingCombobox.value = value
58
56
  }
59
57
  }
@@ -1,5 +1,5 @@
1
1
  import Combobox from "hw_combobox/models/combobox/base"
2
- import { wrapAroundAccess, nullEvent } from "hw_combobox/helpers"
2
+ import { wrapAroundAccess } from "hw_combobox/helpers"
3
3
 
4
4
  Combobox.Selection = Base => class extends Base {
5
5
  selectOption(event) {
@@ -10,7 +10,7 @@ Combobox.Selection = Base => class extends Base {
10
10
 
11
11
  _connectSelection() {
12
12
  if (this.hasPrefilledDisplayValue) {
13
- this._actingCombobox.value = this.prefilledDisplayValue
13
+ this._query = this.prefilledDisplayValue
14
14
  }
15
15
  }
16
16
 
@@ -19,7 +19,7 @@ Combobox.Selection = Base => class extends Base {
19
19
 
20
20
  if (option) {
21
21
  this._markValid()
22
- this._maybeAutocompleteWith(option, { force })
22
+ this._autocompleteWith(option, { force })
23
23
  this._commitSelection(option, { selected: true })
24
24
  } else {
25
25
  this._markInvalid()
@@ -32,8 +32,6 @@ Combobox.Selection = Base => class extends Base {
32
32
  if (selected) {
33
33
  this.hiddenFieldTarget.value = option.dataset.value
34
34
  option.scrollIntoView({ block: "nearest" })
35
- } else {
36
- this.hiddenFieldTarget.value = null
37
35
  }
38
36
  }
39
37
 
@@ -48,11 +46,12 @@ Combobox.Selection = Base => class extends Base {
48
46
  _deselect() {
49
47
  const option = this._selectedOptionElement
50
48
  if (option) this._commitSelection(option, { selected: false })
49
+ this.hiddenFieldTarget.value = null
51
50
  }
52
51
 
53
52
  _selectNew() {
54
53
  this._resetOptions()
55
- this.hiddenFieldTarget.value = this._actingCombobox.value
54
+ this.hiddenFieldTarget.value = this._query
56
55
  this.hiddenFieldTarget.name = this.nameWhenNewValue
57
56
  }
58
57
 
@@ -71,10 +70,10 @@ Combobox.Selection = Base => class extends Base {
71
70
  }
72
71
  }
73
72
 
74
- _selectFuzzyMatch() {
75
- if (this._isFuzzyMatch) {
76
- this._select(this._visibleOptionElements[0], { force: true })
77
- this.filter(nullEvent)
73
+ _ensureSelection() {
74
+ if (this._shouldEnsureSelection) {
75
+ this._select(this._ensurableOption, { force: true })
76
+ this.filter({ inputType: "hw:ensureSelection" })
78
77
  }
79
78
  }
80
79
 
@@ -82,7 +81,11 @@ Combobox.Selection = Base => class extends Base {
82
81
  return this.hiddenFieldTarget.value && !this._selectedOptionElement
83
82
  }
84
83
 
85
- get _isFuzzyMatch() {
86
- return this._isQueried && !!this._visibleOptionElements[0] && !this._isNewOptionWithPotentialMatches
84
+ get _shouldEnsureSelection() {
85
+ return this._isQueried && !!this._ensurableOption && !this._isNewOptionWithPotentialMatches
86
+ }
87
+
88
+ get _ensurableOption() {
89
+ return this._selectedOptionElement || this._visibleOptionElements[0]
87
90
  }
88
91
  }
@@ -50,10 +50,6 @@ Combobox.Toggle = Base => class extends Base {
50
50
  return clientX >= left && clientX <= right && clientY >= top && clientY <= bottom
51
51
  }
52
52
 
53
- _ensureSelection() {
54
- this._selectFuzzyMatch()
55
- }
56
-
57
53
  _openByFocusing() {
58
54
  this._actingCombobox.focus()
59
55
  }
@@ -171,7 +171,7 @@ class HotwireCombobox::Component
171
171
  if async_src && associated_object
172
172
  associated_object.to_combobox_display
173
173
  elsif hidden_field_value
174
- options.find { |option| option.value == hidden_field_value }&.content
174
+ options.find { |option| option.value == hidden_field_value }&.autocompletable_as
175
175
  end
176
176
  end
177
177
 
@@ -11,8 +11,8 @@ class HotwireCombobox::Listbox::Option
11
11
  option.try(:value) || option.id
12
12
  end
13
13
 
14
- def content
15
- option.try(:content) || option.try(:display)
14
+ def autocompletable_as
15
+ option.try(:autocompletable_as) || option.try(:display)
16
16
  end
17
17
 
18
18
  private
@@ -42,11 +42,11 @@ class HotwireCombobox::Listbox::Option
42
42
  }
43
43
  end
44
44
 
45
- def filterable_as
46
- option.try(:filterable_as) || option.try(:display)
45
+ def content
46
+ option.try(:content) || option.try(:display)
47
47
  end
48
48
 
49
- def autocompletable_as
50
- option.try(:autocompletable_as) || option.try(:display)
49
+ def filterable_as
50
+ option.try(:filterable_as) || option.try(:display)
51
51
  end
52
52
  end
@@ -1,4 +1,4 @@
1
1
  <%# locals: (for_id:, src:) -%>
2
2
 
3
3
  <%= turbo_frame_tag hw_pagination_frame_id(for_id), src: src, loading: :lazy,
4
- data: { hw_combobox_target: "paginationFrame" } %>
4
+ data: { hw_combobox_target: "endOfOptionsStream", input_type: params[:input_type] } %>
@@ -1,3 +1,3 @@
1
1
  module HotwireCombobox
2
- VERSION = "0.1.33"
2
+ VERSION = "0.1.35"
3
3
  end
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.33
4
+ version: 0.1.35
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-27 00:00:00.000000000 Z
11
+ date: 2024-02-29 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails