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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4b9f936869948e6767d3cdf4f02f226bfe4c3954d57172b37388239884b69ae6
4
- data.tar.gz: b5495f56d66183c5ebb6c54a6a8e5278391df921f304b36b9da9ffb3bf752576
3
+ metadata.gz: 446cd3c4beeb66029cab7cd6b92ac04c2a8c7a1f08ffd874728da2a3e605ead7
4
+ data.tar.gz: 9783eb83824ebeff3fa55536002c24e129fc66ed0b9d82e33c808a8d65c6c3ee
5
5
  SHA512:
6
- metadata.gz: 7bb2f124171055bc2b46c7584f31b3ae6426979dd9485645e8364bef559aa86cfa905f62f0db47e79d48db98f42d422b9c8fd9f342729e7da4704385d8544e3b
7
- data.tar.gz: 27e49fa1cfd828d23f7dcf82b75a2ba7d7edc4adc8fcff88d35cab8cc41e666f39dffb82209c68740e8b4d9ec9236b1db3513e7c2952cf80cf6f96dc7608a39e
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:ensureSelection") {
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 = 300) {
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._query
14
+ const typedValue = this._typedQuery
15
15
  const autocompletedValue = option.getAttribute(this.autocompletableAttributeValue)
16
16
 
17
17
  if (force) {
18
- this._query = autocompletedValue
18
+ this._fullQuery = autocompletedValue
19
19
  this._actingCombobox.setSelectionRange(autocompletedValue.length, autocompletedValue.length)
20
20
  } else if (startsWith(autocompletedValue, typedValue)) {
21
- this._query = autocompletedValue
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._query
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._query)
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._visibleOptionElements[0]?.getAttribute(this.autocompletableAttributeValue)
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._query
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._query
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._query, input_type: event.inputType }
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._query, { matching: this.filterableAttributeValue }))
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._query.length > 0
45
+ return this._fullQuery.length > 0
46
46
  }
47
47
 
48
- // Consider +_query+ will contain the full autocompleted value
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 _query(value) {
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._query = this.prefilledDisplayValue
13
+ this._fullQuery = this.prefilledDisplayValue
14
14
  }
15
15
  }
16
16
 
17
- _select(option, { force = false } = {}) {
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._query
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, { force: true })
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
- _ensureSelection() {
74
- if (this._shouldEnsureSelection) {
75
- this._select(this._ensurableOption, { force: true })
76
- this.filter({ inputType: "hw:ensureSelection" })
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 _shouldEnsureSelection() {
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._ensureSelection()
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
  }
@@ -1,3 +1,3 @@
1
1
  module HotwireCombobox
2
- VERSION = "0.1.35"
2
+ VERSION = "0.1.36"
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.35
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-29 00:00:00.000000000 Z
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: A combobox implementation for Ruby on Rails apps using Hotwire.
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: []