hotwire_combobox 0.1.37 → 0.1.39

Sign up to get free protection for your applications and to get access to all the features.
@@ -62,3 +62,20 @@ export function unselectedPortion(element) {
62
62
  return element.value.substring(0, element.selectionStart)
63
63
  }
64
64
  }
65
+
66
+ export function dispatch(eventName, { target, cancelable, detail } = {}) {
67
+ const event = new CustomEvent(eventName, {
68
+ cancelable,
69
+ bubbles: true,
70
+ composed: true,
71
+ detail
72
+ })
73
+
74
+ if (target && target.isConnected) {
75
+ target.dispatchEvent(event)
76
+ } else {
77
+ document.documentElement.dispatchEvent(event)
78
+ }
79
+
80
+ return event
81
+ }
@@ -1,6 +1,12 @@
1
1
  import Combobox from "hw_combobox/models/combobox/base"
2
2
 
3
3
  Combobox.Dialog = Base => class extends Base {
4
+ rerouteListboxStreamToDialog({ detail: { newStream } }) {
5
+ if (newStream.target == this.listboxTarget.id && this._dialogIsOpen) {
6
+ newStream.setAttribute("target", this.dialogListboxTarget.id)
7
+ }
8
+ }
9
+
4
10
  _connectDialog() {
5
11
  if (window.visualViewport) {
6
12
  window.visualViewport.addEventListener("resize", this._resizeDialog)
@@ -50,4 +56,8 @@ Combobox.Dialog = Base => class extends Base {
50
56
  get _smallViewport() {
51
57
  return window.matchMedia(`(max-width: ${this.smallViewportMaxWidthValue})`).matches
52
58
  }
59
+
60
+ get _dialogIsOpen() {
61
+ return this.dialogTarget.open
62
+ }
53
63
  }
@@ -0,0 +1,17 @@
1
+ import Combobox from "hw_combobox/models/combobox/base"
2
+ import { dispatch } from "hw_combobox/helpers"
3
+
4
+ Combobox.Events = Base => class extends Base {
5
+ _dispatchSelectionEvent({ isNew }) {
6
+ const detail = {
7
+ value: this.hiddenFieldTarget.value,
8
+ display: this._fullQuery,
9
+ query: this._typedQuery,
10
+ fieldName: this.hiddenFieldTarget.name,
11
+ isValid: this._valueIsValid,
12
+ isNew: isNew
13
+ }
14
+
15
+ dispatch("hw-combobox:selection", { target: this.element, detail })
16
+ }
17
+ }
@@ -21,7 +21,12 @@ Combobox.Filtering = Base => class extends Base {
21
21
  }
22
22
 
23
23
  async _filterAsync(event) {
24
- const query = { q: this._fullQuery, input_type: event.inputType }
24
+ const query = {
25
+ q: this._fullQuery,
26
+ input_type: event.inputType,
27
+ for_id: this.element.dataset.asyncId
28
+ }
29
+
25
30
  await get(this.asyncSrcValue, { responseKind: "turbo-stream", query })
26
31
  }
27
32
 
@@ -36,11 +41,16 @@ Combobox.Filtering = Base => class extends Base {
36
41
  this._selectNew()
37
42
  } else if (isDeleteEvent(event)) {
38
43
  this._deselect()
39
- } else {
44
+ } else if (this._isOpen) {
40
45
  this._select(this._visibleOptionElements[0])
41
46
  }
42
47
  }
43
48
 
49
+ _clearQuery() {
50
+ this._fullQuery = ""
51
+ this.filter({ inputType: "deleteContentBackward" })
52
+ }
53
+
44
54
  get _isQueried() {
45
55
  return this._fullQuery.length > 0
46
56
  }
@@ -30,4 +30,11 @@ Combobox.Options = Base => class extends Base {
30
30
  get _selectedOptionIndex() {
31
31
  return [ ...this._visibleOptionElements ].indexOf(this._selectedOptionElement)
32
32
  }
33
+
34
+ get _isUnjustifiablyBlank() {
35
+ const valueIsMissing = !this.hiddenFieldTarget.value
36
+ const noBlankOptionSelected = !this._selectedOptionElement
37
+
38
+ return valueIsMissing && noBlankOptionSelected
39
+ }
33
40
  }
@@ -18,9 +18,9 @@ Combobox.Selection = Base => class extends Base {
18
18
  this._resetOptions()
19
19
 
20
20
  if (option) {
21
- this._markValid()
22
21
  this._autocompleteWith(option, { force: forceAutocomplete })
23
22
  this._commitSelection(option, { selected: true })
23
+ this._markValid()
24
24
  } else {
25
25
  this._markInvalid()
26
26
  }
@@ -33,6 +33,8 @@ Combobox.Selection = Base => class extends Base {
33
33
  this.hiddenFieldTarget.value = option.dataset.value
34
34
  option.scrollIntoView({ block: "nearest" })
35
35
  }
36
+
37
+ this._dispatchSelectionEvent({ isNew: false })
36
38
  }
37
39
 
38
40
  _markSelected(option, { selected }) {
@@ -46,15 +48,22 @@ Combobox.Selection = Base => class extends Base {
46
48
 
47
49
  _deselect() {
48
50
  const option = this._selectedOptionElement
51
+
49
52
  if (option) this._commitSelection(option, { selected: false })
53
+
50
54
  this.hiddenFieldTarget.value = null
51
55
  this._setActiveDescendant("")
56
+
57
+ if (!option) this._dispatchSelectionEvent({ isNew: false })
52
58
  }
53
59
 
54
60
  _selectNew() {
55
61
  this._resetOptions()
56
62
  this.hiddenFieldTarget.value = this._fullQuery
57
63
  this.hiddenFieldTarget.name = this.nameWhenNewValue
64
+ this._markValid()
65
+
66
+ this._dispatchSelectionEvent({ isNew: true })
58
67
  }
59
68
 
60
69
  _selectIndex(index) {
@@ -77,6 +86,11 @@ Combobox.Selection = Base => class extends Base {
77
86
  this._select(this._ensurableOption, { forceAutocomplete: true })
78
87
  this.filter({ inputType: "hw:lockInSelection" })
79
88
  }
89
+
90
+ if (this._isUnjustifiablyBlank) {
91
+ this._deselect()
92
+ this._clearQuery()
93
+ }
80
94
  }
81
95
 
82
96
  _setActiveDescendant(id) {
@@ -72,7 +72,7 @@ Combobox.Toggle = Base => class extends Base {
72
72
  _collapse() {
73
73
  this._actingCombobox.setAttribute("aria-expanded", false) // needs to happen before resetting acting combobox
74
74
 
75
- if (this.dialogTarget.open) {
75
+ if (this._dialogIsOpen) {
76
76
  this._closeInDialog()
77
77
  } else {
78
78
  this._closeInline()
@@ -4,29 +4,35 @@ Combobox.Validity = Base => class extends Base {
4
4
  _markValid() {
5
5
  if (this._valueIsInvalid) return
6
6
 
7
- if (this.hasInvalidClass) {
8
- this.comboboxTarget.classList.remove(this.invalidClass)
9
- }
10
-
11
- this.comboboxTarget.removeAttribute("aria-invalid")
12
- this.comboboxTarget.removeAttribute("aria-errormessage")
7
+ this._forAllComboboxes(combobox => {
8
+ if (this.hasInvalidClass) {
9
+ combobox.classList.remove(this.invalidClass)
10
+ }
11
+
12
+ combobox.removeAttribute("aria-invalid")
13
+ combobox.removeAttribute("aria-errormessage")
14
+ })
13
15
  }
14
16
 
15
17
  _markInvalid() {
16
18
  if (this._valueIsValid) return
17
19
 
18
- if (this.hasInvalidClass) {
19
- this.comboboxTarget.classList.add(this.invalidClass)
20
- }
20
+ this._forAllComboboxes(combobox => {
21
+ if (this.hasInvalidClass) {
22
+ combobox.classList.add(this.invalidClass)
23
+ }
21
24
 
22
- this.comboboxTarget.setAttribute("aria-invalid", true)
23
- this.comboboxTarget.setAttribute("aria-errormessage", `Please select a valid option for ${this.comboboxTarget.name}`)
25
+ combobox.setAttribute("aria-invalid", true)
26
+ combobox.setAttribute("aria-errormessage", `Please select a valid option for ${combobox.name}`)
27
+ })
24
28
  }
25
29
 
26
30
  get _valueIsValid() {
27
31
  return !this._valueIsInvalid
28
32
  }
29
33
 
34
+ // +_valueIsInvalid+ only checks if `comboboxTarget` (and not `_actingCombobox`) is required
35
+ // because the `required` attribute is only forwarded to the `comboboxTarget` element
30
36
  get _valueIsInvalid() {
31
37
  const isRequiredAndEmpty = this.comboboxTarget.required && !this.hiddenFieldTarget.value
32
38
  return isRequiredAndEmpty
@@ -4,6 +4,7 @@ import "hw_combobox/models/combobox/actors"
4
4
  import "hw_combobox/models/combobox/async_loading"
5
5
  import "hw_combobox/models/combobox/autocomplete"
6
6
  import "hw_combobox/models/combobox/dialog"
7
+ import "hw_combobox/models/combobox/events"
7
8
  import "hw_combobox/models/combobox/filtering"
8
9
  import "hw_combobox/models/combobox/navigation"
9
10
  import "hw_combobox/models/combobox/new_options"
@@ -121,6 +121,10 @@
121
121
  text-overflow: ellipsis;
122
122
  }
123
123
 
124
+ .hw-combobox__option--blank {
125
+ border-bottom: var(--hw-border-width--slim) solid var(--hw-border-color);
126
+ }
127
+
124
128
  .hw-combobox__option:hover,
125
129
  .hw-combobox__option--selected {
126
130
  background-color: var(--hw-active-bg-color);
@@ -1,5 +1,7 @@
1
+ require "securerandom"
2
+
1
3
  class HotwireCombobox::Component
2
- attr_reader :async_src, :options, :dialog_label
4
+ attr_reader :options, :dialog_label
3
5
 
4
6
  def initialize \
5
7
  view, name,
@@ -148,7 +150,7 @@ class HotwireCombobox::Component
148
150
  end
149
151
 
150
152
  def fieldset_data
151
- data.reverse_merge \
153
+ data.merge \
152
154
  async_id: canonical_id,
153
155
  controller: view.token_list("hw-combobox", data[:controller]),
154
156
  hw_combobox_expanded_value: open,
@@ -160,7 +162,8 @@ class HotwireCombobox::Component
160
162
  hw_combobox_prefilled_display_value: prefilled_display,
161
163
  hw_combobox_filterable_attribute_value: "data-filterable-as",
162
164
  hw_combobox_autocompletable_attribute_value: "data-autocompletable-as",
163
- hw_combobox_selected_class: "hw-combobox__option--selected"
165
+ hw_combobox_selected_class: "hw-combobox__option--selected",
166
+ hw_combobox_invalid_class: "hw-combobox__input--invalid"
164
167
  end
165
168
 
166
169
  def prefilled_display
@@ -181,9 +184,13 @@ class HotwireCombobox::Component
181
184
  form&.object&.class&.reflect_on_association(association_name).present?
182
185
  end
183
186
 
187
+ def async_src
188
+ view.hw_uri_with_params @async_src, for_id: canonical_id, format: :turbo_stream
189
+ end
190
+
184
191
 
185
192
  def canonical_id
186
- id || form&.field_id(name)
193
+ @canonical_id ||= id || form&.field_id(name) || SecureRandom.uuid
187
194
  end
188
195
 
189
196
 
@@ -219,19 +226,20 @@ class HotwireCombobox::Component
219
226
  end
220
227
 
221
228
  def input_data
222
- combobox_attrs.fetch(:data, {}).reverse_merge! \
229
+ combobox_attrs.fetch(:data, {}).merge \
223
230
  action: "
224
231
  focus->hw-combobox#open
225
232
  input->hw-combobox#filter
226
233
  keydown->hw-combobox#navigate
227
234
  click@window->hw-combobox#closeOnClickOutside
228
- focusin@window->hw-combobox#closeOnFocusOutside".squish,
235
+ focusin@window->hw-combobox#closeOnFocusOutside
236
+ turbo:before-stream-render@document->hw-combobox#rerouteListboxStreamToDialog".squish,
229
237
  hw_combobox_target: "combobox",
230
238
  async_id: canonical_id
231
239
  end
232
240
 
233
241
  def input_aria
234
- combobox_attrs.fetch(:aria, {}).reverse_merge! \
242
+ combobox_attrs.fetch(:aria, {}).merge \
235
243
  controls: listbox_id,
236
244
  owns: listbox_id,
237
245
  haspopup: "listbox",
@@ -18,7 +18,7 @@ class HotwireCombobox::Listbox::Option
18
18
  end
19
19
 
20
20
  private
21
- Data = Struct.new :id, :value, :display, :content, :filterable_as, :autocompletable_as, keyword_init: true
21
+ Data = Struct.new :id, :value, :display, :content, :blank, :filterable_as, :autocompletable_as, keyword_init: true
22
22
 
23
23
  attr_reader :option
24
24
 
@@ -26,13 +26,13 @@ class HotwireCombobox::Listbox::Option
26
26
  {
27
27
  id: id,
28
28
  role: :option,
29
- class: "hw-combobox__option",
29
+ class: [ "hw-combobox__option", { "hw-combobox__option--blank": blank? } ],
30
30
  data: data
31
31
  }
32
32
  end
33
33
 
34
34
  def id
35
- option.try(:id) || SecureRandom.uuid
35
+ @id ||= option.try(:id) || SecureRandom.uuid
36
36
  end
37
37
 
38
38
  def data
@@ -51,4 +51,8 @@ class HotwireCombobox::Listbox::Option
51
51
  def filterable_as
52
52
  option.try(:filterable_as) || option.try(:display)
53
53
  end
54
+
55
+ def blank?
56
+ option.try(:blank).present?
57
+ end
54
58
  end
@@ -2,5 +2,5 @@
2
2
 
3
3
  <%= turbo_stream.remove hw_pagination_frame_wrapper_id(for_id) %>
4
4
  <%= turbo_stream.append hw_listbox_id(for_id) do %>
5
- <%= render "hotwire_combobox/pagination", for_id: for_id, src: hw_combobox_next_page_uri(src, next_page) %>
5
+ <%= render "hotwire_combobox/pagination", for_id: for_id, src: hw_combobox_next_page_uri(src, next_page, for_id) %>
6
6
  <% end %>
@@ -10,7 +10,7 @@ module HotwireCombobox
10
10
  unless HotwireCombobox.bypass_convenience_methods?
11
11
  module FormBuilderExtensions
12
12
  def combobox(*args, **kwargs)
13
- @template.hw_combobox_tag *args, **kwargs.merge(form: self)
13
+ @template.hw_combobox_tag(*args, **kwargs.merge(form: self))
14
14
  end
15
15
  end
16
16
 
@@ -15,27 +15,32 @@ module HotwireCombobox
15
15
  end
16
16
  hw_alias :hw_combobox_style_tag
17
17
 
18
- def hw_combobox_tag(name, options_or_src = [], render_in: {}, **kwargs)
19
- options, src = hw_extract_options_and_src(options_or_src, render_in)
18
+ def hw_combobox_tag(name, options_or_src = [], render_in: {}, include_blank: nil, **kwargs)
19
+ options, src = hw_extract_options_and_src(options_or_src, render_in, include_blank)
20
20
  component = HotwireCombobox::Component.new self, name, options: options, async_src: src, **kwargs
21
21
 
22
22
  render "hotwire_combobox/combobox", component: component
23
23
  end
24
24
  hw_alias :hw_combobox_tag
25
25
 
26
- def hw_combobox_options(options, render_in: {}, display: :to_combobox_display, **methods)
26
+ def hw_combobox_options(options, render_in: {}, include_blank: nil, display: :to_combobox_display, **methods)
27
27
  if options.first.is_a? HotwireCombobox::Listbox::Option
28
28
  options
29
29
  else
30
- render_in_proc = ->(object) { render(**render_in.merge(object: object)) } if render_in.present?
31
- hw_parse_combobox_options options, render_in: render_in_proc, **methods.merge(display: display)
30
+ render_in_proc = hw_render_in_proc(render_in) if render_in.present?
31
+
32
+ hw_parse_combobox_options(options, render_in: render_in_proc, **methods.merge(display: display)).tap do |options|
33
+ options.unshift(hw_blank_option(include_blank)) if include_blank.present?
34
+ end
32
35
  end
33
36
  end
34
37
  hw_alias :hw_combobox_options
35
38
 
36
- def hw_paginated_combobox_options(options, for_id:, src: request.path, next_page: nil, render_in: {}, **methods)
37
- this_page = render("hotwire_combobox/paginated_options", for_id: for_id, options: hw_combobox_options(options, render_in: render_in, **methods))
38
- next_page = render("hotwire_combobox/next_page", for_id: for_id, src: src, next_page: next_page)
39
+ def hw_paginated_combobox_options(options, for_id: params[:for_id], src: request.path, next_page: nil, render_in: {}, include_blank: {}, **methods)
40
+ include_blank = params[:page] ? nil : include_blank
41
+ options = hw_combobox_options options, render_in: render_in, include_blank: include_blank, **methods
42
+ this_page = render "hotwire_combobox/paginated_options", for_id: for_id, options: options
43
+ next_page = render "hotwire_combobox/next_page", for_id: for_id, src: src, next_page: next_page
39
44
 
40
45
  safe_join [ this_page, next_page ]
41
46
  end
@@ -44,7 +49,7 @@ module HotwireCombobox
44
49
  alias_method :hw_async_combobox_options, :hw_paginated_combobox_options
45
50
  hw_alias :hw_async_combobox_options
46
51
 
47
- protected # library use only
52
+ # private library use only
48
53
  def hw_listbox_id(id)
49
54
  "#{id}-hw-listbox"
50
55
  end
@@ -57,9 +62,13 @@ module HotwireCombobox
57
62
  "#{id}__hw_combobox_pagination"
58
63
  end
59
64
 
60
- def hw_combobox_next_page_uri(uri, next_page)
65
+ def hw_combobox_next_page_uri(uri, next_page, for_id)
61
66
  if next_page
62
- hw_uri_with_params uri, page: next_page, q: params[:q], format: :turbo_stream
67
+ hw_uri_with_params uri,
68
+ page: next_page,
69
+ q: params[:q],
70
+ for_id: for_id,
71
+ format: :turbo_stream
63
72
  end
64
73
  end
65
74
 
@@ -67,12 +76,19 @@ module HotwireCombobox
67
76
  params[:page] ? :append : :update
68
77
  end
69
78
 
70
- private
71
- def hw_extract_options_and_src(options_or_src, render_in)
72
- if options_or_src.is_a? String
73
- [ [], hw_uri_with_params(options_or_src, format: :turbo_stream) ]
79
+ def hw_blank_option(include_blank)
80
+ display, content = hw_extract_blank_display_and_content include_blank
81
+
82
+ HotwireCombobox::Listbox::Option.new display: display, content: content, value: "", blank: true
83
+ end
84
+
85
+ def hw_extract_blank_display_and_content(include_blank)
86
+ if include_blank.is_a? Hash
87
+ text = include_blank.delete(:text)
88
+
89
+ [ text, hw_render_in_proc(include_blank).(text) ]
74
90
  else
75
- [ hw_combobox_options(options_or_src, render_in: render_in), nil ]
91
+ [ include_blank, include_blank ]
76
92
  end
77
93
  end
78
94
 
@@ -85,9 +101,23 @@ module HotwireCombobox
85
101
  url_or_path
86
102
  end
87
103
 
104
+ private
105
+ def hw_render_in_proc(render_in)
106
+ ->(object) { render(**render_in.reverse_merge(object: object)) }
107
+ end
108
+
109
+ def hw_extract_options_and_src(options_or_src, render_in, include_blank)
110
+ if options_or_src.is_a? String
111
+ [ [], options_or_src ]
112
+ else
113
+ [ hw_combobox_options(options_or_src, render_in: render_in, include_blank: include_blank), nil ]
114
+ end
115
+ end
116
+
88
117
  def hw_parse_combobox_options(options, render_in: nil, **methods)
89
118
  options.map do |option|
90
- HotwireCombobox::Listbox::Option.new **hw_option_attrs_for(option, render_in: render_in, **methods)
119
+ HotwireCombobox::Listbox::Option.new \
120
+ **hw_option_attrs_for(option, render_in: render_in, **methods)
91
121
  end
92
122
  end
93
123
 
@@ -122,8 +152,43 @@ module HotwireCombobox
122
152
  if method_or_proc.is_a? Proc
123
153
  method_or_proc.call object
124
154
  else
125
- object.public_send method_or_proc
155
+ hw_call_method object, method_or_proc
156
+ end
157
+ end
158
+
159
+ def hw_call_method(object, method)
160
+ if object.respond_to? method
161
+ object.public_send method
162
+ else
163
+ hw_raise_no_public_method_error object, method
126
164
  end
127
165
  end
166
+
167
+ def hw_raise_no_public_method_error(object, method)
168
+ if object.respond_to? method, true
169
+ header = "`#{object.class}` responds to `##{method}` but the method is not public."
170
+ else
171
+ header = "`#{object.class}` does not respond to `##{method}`."
172
+ end
173
+
174
+ if method.to_s == "to_combobox_display"
175
+ header << "\n\nThis method is used to determine how this option should appear in the combobox options list."
176
+ end
177
+
178
+ raise NoMethodError, <<~MSG
179
+ [ACTION NEEDED] – Message from HotwireCombobox:
180
+
181
+ #{header}
182
+
183
+ Please add this as a public method and return a string.
184
+
185
+ Example:
186
+ class #{object.class} < ApplicationRecord
187
+ def #{method}
188
+ name # or `title`, `to_s`, etc.
189
+ end
190
+ end
191
+ MSG
192
+ end
128
193
  end
129
194
  end
@@ -1,3 +1,3 @@
1
1
  module HotwireCombobox
2
- VERSION = "0.1.37"
2
+ VERSION = "0.1.39"
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.37
4
+ version: 0.1.39
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-03-03 00:00:00.000000000 Z
11
+ date: 2024-03-07 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -52,8 +52,7 @@ dependencies:
52
52
  - - ">="
53
53
  - !ruby/object:Gem::Version
54
54
  version: '1.2'
55
- description: An autocomplete combobox implementation for Ruby on Rails apps using
56
- Hotwire.
55
+ description: An accessible autocomplete for Ruby on Rails apps using Hotwire.
57
56
  email:
58
57
  - jose@farias.mx
59
58
  executables: []
@@ -65,6 +64,8 @@ files:
65
64
  - Rakefile
66
65
  - app/assets/config/hw_combobox_manifest.js
67
66
  - app/assets/javascripts/controllers/hw_combobox_controller.js
67
+ - app/assets/javascripts/hotwire_combobox.esm.js
68
+ - app/assets/javascripts/hotwire_combobox.umd.js
68
69
  - app/assets/javascripts/hw_combobox/helpers.js
69
70
  - app/assets/javascripts/hw_combobox/models/combobox.js
70
71
  - app/assets/javascripts/hw_combobox/models/combobox/actors.js
@@ -72,6 +73,7 @@ files:
72
73
  - app/assets/javascripts/hw_combobox/models/combobox/autocomplete.js
73
74
  - app/assets/javascripts/hw_combobox/models/combobox/base.js
74
75
  - app/assets/javascripts/hw_combobox/models/combobox/dialog.js
76
+ - app/assets/javascripts/hw_combobox/models/combobox/events.js
75
77
  - app/assets/javascripts/hw_combobox/models/combobox/filtering.js
76
78
  - app/assets/javascripts/hw_combobox/models/combobox/navigation.js
77
79
  - app/assets/javascripts/hw_combobox/models/combobox/new_options.js
@@ -97,13 +99,13 @@ files:
97
99
  - lib/hotwire_combobox/engine.rb
98
100
  - lib/hotwire_combobox/helper.rb
99
101
  - lib/hotwire_combobox/version.rb
100
- homepage: https://github.com/josefarias/hotwire_combobox
102
+ homepage: https://hotwirecombobox.com/
101
103
  licenses:
102
104
  - MIT
103
105
  metadata:
104
- homepage_uri: https://github.com/josefarias/hotwire_combobox
106
+ homepage_uri: https://hotwirecombobox.com/
105
107
  source_code_uri: https://github.com/josefarias/hotwire_combobox
106
- changelog_uri: https://github.com/josefarias/hotwire_combobox
108
+ changelog_uri: https://github.com/josefarias/hotwire_combobox/releases
107
109
  post_install_message:
108
110
  rdoc_options: []
109
111
  require_paths:
@@ -112,7 +114,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
112
114
  requirements:
113
115
  - - ">="
114
116
  - !ruby/object:Gem::Version
115
- version: '0'
117
+ version: 2.7.0
116
118
  required_rubygems_version: !ruby/object:Gem::Requirement
117
119
  requirements:
118
120
  - - ">="
@@ -122,5 +124,5 @@ requirements: []
122
124
  rubygems_version: 3.5.6
123
125
  signing_key:
124
126
  specification_version: 4
125
- summary: Autocomplete for Rails apps using Hotwire
127
+ summary: Accessible Autocomplete for Rails apps
126
128
  test_files: []