hotwire_combobox 0.1.11 → 0.1.13

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.
Files changed (32) hide show
  1. checksums.yaml +4 -4
  2. data/MIT-LICENSE +1 -1
  3. data/app/assets/javascripts/controllers/hw_combobox_controller.js +49 -242
  4. data/app/assets/javascripts/helpers.js +52 -0
  5. data/app/assets/javascripts/models/combobox/actors.js +24 -0
  6. data/app/assets/javascripts/models/combobox/async_loading.js +7 -0
  7. data/app/assets/javascripts/models/combobox/autocomplete.js +39 -0
  8. data/app/assets/javascripts/models/combobox/base.js +3 -0
  9. data/app/assets/javascripts/models/combobox/dialog.js +50 -0
  10. data/app/assets/javascripts/models/combobox/filtering.js +57 -0
  11. data/app/assets/javascripts/models/combobox/navigation.js +39 -0
  12. data/app/assets/javascripts/models/combobox/options.js +41 -0
  13. data/app/assets/javascripts/models/combobox/selection.js +62 -0
  14. data/app/assets/javascripts/models/combobox/toggle.js +104 -0
  15. data/app/assets/javascripts/models/combobox/validity.js +34 -0
  16. data/app/assets/javascripts/models/combobox.js +14 -0
  17. data/app/assets/javascripts/vendor/bodyScrollLock.js +299 -0
  18. data/app/assets/stylesheets/hotwire_combobox.css +181 -0
  19. data/app/helpers/hotwire_combobox/helper.rb +81 -26
  20. data/app/presenters/hotwire_combobox/component.rb +150 -20
  21. data/app/presenters/hotwire_combobox/listbox/option.rb +4 -2
  22. data/app/views/hotwire_combobox/_combobox.html.erb +4 -13
  23. data/app/views/hotwire_combobox/_next_page.turbo_stream.erb +5 -0
  24. data/app/views/hotwire_combobox/_paginated_options.turbo_stream.erb +7 -0
  25. data/app/views/hotwire_combobox/_pagination.html.erb +3 -0
  26. data/app/views/hotwire_combobox/combobox/_dialog.html.erb +7 -0
  27. data/app/views/hotwire_combobox/combobox/_hidden_field.html.erb +4 -0
  28. data/app/views/hotwire_combobox/combobox/_input.html.erb +2 -0
  29. data/app/views/hotwire_combobox/combobox/_paginated_listbox.html.erb +11 -0
  30. data/lib/hotwire_combobox/version.rb +1 -1
  31. data/lib/hotwire_combobox.rb +4 -2
  32. metadata +52 -2
@@ -1,44 +1,99 @@
1
1
  module HotwireCombobox
2
2
  module Helper
3
- NonCollectionOptions = Class.new StandardError
4
-
5
3
  class << self
6
4
  delegate :bypass_convenience_methods?, to: :HotwireCombobox
7
5
 
8
- def hw_combobox_options(options, **methods)
9
- unless options.respond_to?(:map)
10
- raise NonCollectionOptions, "options must be an Array or an ActiveRecord::Relation"
6
+ def hw_alias(method_name)
7
+ unless bypass_convenience_methods?
8
+ alias_method method_name.to_s.sub(/^hw_/, ""), method_name
11
9
  end
10
+ end
11
+ end
12
12
 
13
- if ActiveRecord::Relation === options || ActiveRecord::Base === options.first
14
- options.map do |option|
15
- attrs = {}.tap do |attrs|
16
- attrs[:id] = option.public_send(methods[:id]) if methods[:id]
17
- attrs[:value] = option.public_send(methods[:value] || :id)
18
- attrs[:display] = option.public_send(methods[:display]) if methods[:display]
19
- end
13
+ def hw_combobox_style_tag(*args, **kwargs)
14
+ stylesheet_link_tag HotwireCombobox.stylesheet_path, *args, **kwargs
15
+ end
16
+ hw_alias :hw_combobox_style_tag
17
+
18
+ def hw_combobox_tag(*args, async_src: nil, options: [], render_in: {}, **kwargs)
19
+ options = hw_combobox_options options, render_in: render_in
20
+ src = hw_uri_with_params async_src, format: :turbo_stream
21
+ component = HotwireCombobox::Component.new self, *args, options: options, async_src: src, **kwargs
22
+
23
+ render "hotwire_combobox/combobox", component: component
24
+ end
25
+ hw_alias :hw_combobox_tag
20
26
 
21
- hw_combobox_option(**attrs)
22
- end
27
+ def hw_combobox_options(options, render_in: {}, display: :to_combobox_display, **methods)
28
+ if options.first.is_a? HotwireCombobox::Listbox::Option
29
+ options
30
+ else
31
+ content = if render_in.present?
32
+ ->(object) { render(**render_in.merge(object: object)) }
23
33
  else
24
- options.map { |option| hw_combobox_option(**option) }
34
+ methods[:content]
25
35
  end
26
- end
27
36
 
28
- private
29
- def hw_combobox_option(*args, **kwargs, &block)
30
- HotwireCombobox::Option.new(*args, **kwargs, &block)
31
- end
37
+ hw_parse_combobox_options options, **methods.merge(display: display, content: content)
38
+ end
32
39
  end
40
+ hw_alias :hw_combobox_options
41
+
42
+ def hw_paginated_combobox_options(options, for_id:, src:, next_page:, render_in: {}, **methods)
43
+ this_page = render("hotwire_combobox/paginated_options", for_id: for_id, options: hw_combobox_options(options, render_in: render_in, **methods), format: :turbo_stream)
44
+ next_page = render("hotwire_combobox/next_page", src: src, next_page: next_page, format: :turbo_stream)
33
45
 
34
- def hw_combobox_tag(*args, **kwargs, &block)
35
- render "hotwire_combobox/combobox", component: HotwireCombobox::Component.new(*args, **kwargs, &block)
46
+ safe_join [ this_page, next_page ]
36
47
  end
37
- alias_method :combobox_tag, :hw_combobox_tag unless bypass_convenience_methods?
48
+ hw_alias :hw_paginated_combobox_options
38
49
 
39
- def hw_combobox_options(*args, **kwargs, &block)
40
- HotwireCombobox::Helper.hw_combobox_options(*args, **kwargs, &block)
50
+ def hw_listbox_options_id(id)
51
+ "#{id}-hw-listbox__options"
41
52
  end
42
- alias_method :combobox_options, :hw_combobox_options unless bypass_convenience_methods?
53
+ hw_alias :hw_listbox_options_id
54
+
55
+ private
56
+ def hw_uri_with_params(url_or_path, **params)
57
+ URI.parse(url_or_path).tap do |url_or_path|
58
+ query = URI.decode_www_form(url_or_path.query || "").to_h.merge(params)
59
+ url_or_path.query = URI.encode_www_form query
60
+ end.to_s
61
+ rescue URI::InvalidURIError
62
+ url_or_path
63
+ end
64
+
65
+ def hw_parse_combobox_options(options, **methods)
66
+ options.map do |option|
67
+ attrs = option.is_a?(Hash) ? option : hw_option_attrs_for_obj(option, **methods)
68
+ HotwireCombobox::Listbox::Option.new **attrs
69
+ end
70
+ end
71
+
72
+ def hw_option_attrs_for_obj(option, **methods)
73
+ {}.tap do |attrs|
74
+ attrs[:id] = hw_call_method_or_proc(option, methods[:id]) if methods[:id]
75
+ attrs[:value] = hw_call_method_or_proc(option, methods[:value] || :id)
76
+ attrs[:display] = hw_call_method_or_proc(option, methods[:display]) if methods[:display]
77
+ attrs[:content] = hw_call_method_or_proc(option, methods[:content]) if methods[:content]
78
+ end
79
+ end
80
+
81
+ def hw_call_method_or_proc(object, method_or_proc)
82
+ if method_or_proc.is_a? Proc
83
+ method_or_proc.call object
84
+ else
85
+ object.public_send method_or_proc
86
+ end
87
+ end
88
+
89
+ def hw_combobox_next_page_uri(uri, next_page)
90
+ if next_page
91
+ hw_uri_with_params uri, page: next_page, q: params[:q], format: :turbo_stream
92
+ end
93
+ end
94
+
95
+ def hw_combobox_page_stream_action
96
+ params[:page] ? :append : :update
97
+ end
43
98
  end
44
99
  end
@@ -1,20 +1,23 @@
1
1
  class HotwireCombobox::Component
2
- include ActionView::Helpers::TagHelper
2
+ attr_reader :async_src, :options, :dialog_label
3
3
 
4
- class << self
5
- def options_from(options)
6
- if HotwireCombobox::Option === options.first
7
- options
8
- else
9
- HotwireCombobox::Helper.hw_combobox_options options, display: :to_combobox_display
10
- end
11
- end
12
- end
4
+ def initialize(
5
+ view, name, value = nil,
6
+ autocomplete: :both,
7
+ id: nil,
8
+ form: nil,
9
+ name_when_new: nil,
10
+ open: false,
11
+ small_width: "640px",
12
+ async_src: nil,
13
+ dialog_label: nil,
14
+ options: [], data: {}, input: {}, **rest)
15
+ @combobox_attrs = input.reverse_merge(rest).with_indifferent_access
13
16
 
14
- def initialize(name, value = nil, id: nil, form: nil, name_when_new: nil, open: false, options: [], data: {}, input: {}, **rest)
15
- @combobox_attrs = input.reverse_merge(rest).with_indifferent_access # input: {} allows for specifying e.g. data attributes on the input field
16
- @options = self.class.options_from options
17
- @id, @name, @value, @form, @name_when_new, @open, @data = id, name, value, form, name_when_new, open, data
17
+ @view, @autocomplete, @id, @name, @value, @form, @async_src,
18
+ @name_when_new, @open, @data, @small_width, @options, @dialog_label =
19
+ view, autocomplete, id, name, value, form, async_src,
20
+ name_when_new, open, data, small_width, options, dialog_label
18
21
  end
19
22
 
20
23
  def fieldset_attrs
@@ -24,6 +27,7 @@ class HotwireCombobox::Component
24
27
  }
25
28
  end
26
29
 
30
+
27
31
  def hidden_field_attrs
28
32
  {
29
33
  id: hidden_field_id,
@@ -33,42 +37,113 @@ class HotwireCombobox::Component
33
37
  }
34
38
  end
35
39
 
40
+
36
41
  def input_attrs
37
42
  nested_attrs = %i[ data aria ]
38
43
 
39
44
  {
40
45
  id: input_id,
41
46
  role: :combobox,
47
+ class: "hw-combobox__input",
42
48
  type: input_type,
43
49
  data: input_data,
44
50
  aria: input_aria
45
51
  }.merge combobox_attrs.except(*nested_attrs)
46
52
  end
47
53
 
54
+
55
+ def handle_attrs
56
+ {
57
+ class: "hw-combobox__handle",
58
+ data: handle_data
59
+ }
60
+ end
61
+
62
+
48
63
  def listbox_attrs
49
64
  {
50
65
  id: listbox_id,
51
66
  role: :listbox,
67
+ class: "hw-combobox__listbox",
52
68
  hidden: "",
53
69
  data: listbox_data
54
70
  }
55
71
  end
56
72
 
57
- def listbox_options
58
- options.map { |option| HotwireCombobox::Listbox::Option.new option }
73
+
74
+ def listbox_options_attrs
75
+ { id: listbox_options_id }
76
+ end
77
+
78
+
79
+ def dialog_attrs
80
+ {
81
+ class: "hw-combobox__dialog",
82
+ role: :dialog,
83
+ data: dialog_data
84
+ }
85
+ end
86
+
87
+ def dialog_label_attrs
88
+ {
89
+ class: "hw-combobox__dialog__label",
90
+ for: dialog_input_id
91
+ }
92
+ end
93
+
94
+ def dialog_input_attrs
95
+ {
96
+ id: dialog_input_id,
97
+ role: :combobox,
98
+ class: "hw-combobox__dialog__input",
99
+ autofocus: "",
100
+ type: input_type,
101
+ data: dialog_input_data,
102
+ aria: dialog_input_aria
103
+ }
104
+ end
105
+
106
+ def dialog_listbox_attrs
107
+ {
108
+ id: dialog_listbox_id,
109
+ class: "hw-combobox__dialog__listbox",
110
+ role: :listbox,
111
+ data: dialog_listbox_data
112
+ }
113
+ end
114
+
115
+ def dialog_focus_trap_attrs
116
+ {
117
+ tabindex: "-1",
118
+ data: dialog_focus_trap_data
119
+ }
120
+ end
121
+
122
+
123
+ def paginated?
124
+ async_src.present?
125
+ end
126
+
127
+ def pagination_attrs
128
+ { src: async_src }
59
129
  end
60
130
 
61
131
  private
62
- attr_reader :id, :name, :value, :form, :name_when_new, :open, :options, :data, :combobox_attrs
132
+ attr_reader :view, :autocomplete, :id, :name, :value, :form,
133
+ :name_when_new, :open, :data, :combobox_attrs, :small_width
63
134
 
64
135
  def fieldset_data
65
136
  data.reverse_merge \
66
- controller: token_list("hw-combobox", data[:controller]),
137
+ controller: view.token_list("hw-combobox", data[:controller]),
67
138
  hw_combobox_expanded_value: open,
68
139
  hw_combobox_name_when_new_value: name_when_new,
69
140
  hw_combobox_original_name_value: hidden_field_name,
141
+ hw_combobox_autocomplete_value: autocomplete,
142
+ hw_combobox_small_viewport_max_width_value: small_width,
143
+ hw_combobox_async_src_value: async_src,
70
144
  hw_combobox_filterable_attribute_value: "data-filterable-as",
71
- hw_combobox_autocompletable_attribute_value: "data-autocompletable-as"
145
+ hw_combobox_autocompletable_attribute_value: "data-autocompletable-as",
146
+ hw_combobox_selected_class: "hw-combobox__option--selected"
72
147
  end
73
148
 
74
149
 
@@ -113,7 +188,15 @@ class HotwireCombobox::Component
113
188
  controls: listbox_id,
114
189
  owns: listbox_id,
115
190
  haspopup: "listbox",
116
- autocomplete: "both"
191
+ autocomplete: autocomplete
192
+ end
193
+
194
+
195
+ def handle_data
196
+ {
197
+ action: "click->hw-combobox#toggle",
198
+ hw_combobox_target: "handle"
199
+ }
117
200
  end
118
201
 
119
202
 
@@ -124,4 +207,51 @@ class HotwireCombobox::Component
124
207
  def listbox_data
125
208
  { hw_combobox_target: "listbox" }
126
209
  end
210
+
211
+
212
+ def listbox_options_id
213
+ "#{listbox_id}__options"
214
+ end
215
+
216
+
217
+ def dialog_data
218
+ {
219
+ action: "keydown->hw-combobox#navigate",
220
+ hw_combobox_target: "dialog"
221
+ }
222
+ end
223
+
224
+ def dialog_input_id
225
+ "#{hidden_field_id}-hw-dialog-combobox"
226
+ end
227
+
228
+ def dialog_input_data
229
+ {
230
+ action: "
231
+ input->hw-combobox#filter
232
+ keydown->hw-combobox#navigate
233
+ click@window->hw-combobox#closeOnClickOutside".squish,
234
+ hw_combobox_target: "dialogCombobox"
235
+ }
236
+ end
237
+
238
+ def dialog_input_aria
239
+ {
240
+ controls: dialog_listbox_id,
241
+ owns: dialog_listbox_id,
242
+ autocomplete: autocomplete
243
+ }
244
+ end
245
+
246
+ def dialog_listbox_id
247
+ "#{hidden_field_id}-hw-dialog-listbox"
248
+ end
249
+
250
+ def dialog_listbox_data
251
+ { hw_combobox_target: "dialogListbox" }
252
+ end
253
+
254
+ def dialog_focus_trap_data
255
+ { hw_combobox_target: "dialogFocusTrap" }
256
+ end
127
257
  end
@@ -1,6 +1,6 @@
1
1
  class HotwireCombobox::Listbox::Option
2
2
  def initialize(option)
3
- @option = option
3
+ @option = option.is_a?(Hash) ? Data.new(**option) : option
4
4
  end
5
5
 
6
6
  def render_in(view_context)
@@ -8,6 +8,8 @@ class HotwireCombobox::Listbox::Option
8
8
  end
9
9
 
10
10
  private
11
+ Data = Struct.new :id, :value, :display, :content, :filterable_as, :autocompletable_as, keyword_init: true
12
+
11
13
  attr_reader :option
12
14
 
13
15
  def content
@@ -18,7 +20,7 @@ class HotwireCombobox::Listbox::Option
18
20
  {
19
21
  id: id,
20
22
  role: :option,
21
- style: "cursor: pointer;",
23
+ class: "hw-combobox__option",
22
24
  data: data
23
25
  }
24
26
  end
@@ -1,15 +1,6 @@
1
1
  <%= tag.fieldset **component.fieldset_attrs do %>
2
- <% hidden_field_attrs = component.hidden_field_attrs %>
3
- <%= hidden_field_tag \
4
- hidden_field_attrs.delete(:name),
5
- hidden_field_attrs.delete(:value),
6
- **hidden_field_attrs %>
7
-
8
- <%= tag.input **component.input_attrs %>
9
-
10
- <%= tag.ul **component.listbox_attrs do |ul| %>
11
- <% component.listbox_options.each do |option| %>
12
- <%= render option %>
13
- <% end %>
14
- <% end %>
2
+ <%= render "hotwire_combobox/combobox/hidden_field", component: component %>
3
+ <%= render "hotwire_combobox/combobox/input", component: component %>
4
+ <%= render "hotwire_combobox/combobox/paginated_listbox", component: component %>
5
+ <%= render "hotwire_combobox/combobox/dialog", component: component %>
15
6
  <% end %>
@@ -0,0 +1,5 @@
1
+ <%# locals: (next_page:, src:) -%>
2
+
3
+ <%= turbo_stream.replace :hotwire_combobox_pagination do %>
4
+ <%= render "hotwire_combobox/pagination", src: hw_combobox_next_page_uri(src, next_page) %>
5
+ <% end %>
@@ -0,0 +1,7 @@
1
+ <%# locals: (for_id:, options:) -%>
2
+
3
+ <%= turbo_stream.public_send(hw_combobox_page_stream_action, hw_listbox_options_id(for_id)) do %>
4
+ <% options.each do |option| %>
5
+ <%= render option %>
6
+ <% end %>
7
+ <% end %>
@@ -0,0 +1,3 @@
1
+ <%# locals: (src:) -%>
2
+
3
+ <%= turbo_frame_tag :hotwire_combobox_pagination, src: src, loading: :lazy %>
@@ -0,0 +1,7 @@
1
+ <%= tag.div **component.dialog_focus_trap_attrs %>
2
+
3
+ <%= tag.dialog **component.dialog_attrs do %>
4
+ <%= tag.label component.dialog_label, **component.dialog_label_attrs %>
5
+ <%= tag.input **component.dialog_input_attrs %>
6
+ <%= tag.ul **component.dialog_listbox_attrs %>
7
+ <% end %>
@@ -0,0 +1,4 @@
1
+ <% hidden_field_attrs = component.hidden_field_attrs %>
2
+
3
+ <%= hidden_field_tag hidden_field_attrs.delete(:name),
4
+ hidden_field_attrs.delete(:value), **hidden_field_attrs %>
@@ -0,0 +1,2 @@
1
+ <%= tag.input **component.input_attrs %>
2
+ <%= tag.span **component.handle_attrs %>
@@ -0,0 +1,11 @@
1
+ <%= tag.ul **component.listbox_attrs do |ul| %>
2
+ <%= tag.div **component.listbox_options_attrs do %>
3
+ <% component.options.each do |option| %>
4
+ <%= render option %>
5
+ <% end %>
6
+ <% end %>
7
+
8
+ <% if component.paginated? %>
9
+ <%= render "hotwire_combobox/pagination", **component.pagination_attrs %>
10
+ <% end %>
11
+ <% end %>
@@ -1,3 +1,3 @@
1
1
  module HotwireCombobox
2
- VERSION = "0.1.11"
2
+ VERSION = "0.1.13"
3
3
  end
@@ -2,8 +2,6 @@ require "hotwire_combobox/version"
2
2
  require "hotwire_combobox/engine"
3
3
 
4
4
  module HotwireCombobox
5
- Option = Struct.new(:id, :value, :display, :content, :filterable_as, :autocompletable_as)
6
-
7
5
  mattr_accessor :bypass_convenience_methods
8
6
  @@bypass_convenience_methods = false
9
7
 
@@ -15,5 +13,9 @@ module HotwireCombobox
15
13
  def bypass_convenience_methods?
16
14
  bypass_convenience_methods
17
15
  end
16
+
17
+ def stylesheet_path
18
+ "hotwire_combobox"
19
+ end
18
20
  end
19
21
  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.11
4
+ version: 0.1.13
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jose Farias
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-12-30 00:00:00.000000000 Z
11
+ date: 2024-01-31 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -52,6 +52,34 @@ dependencies:
52
52
  - - ">="
53
53
  - !ruby/object:Gem::Version
54
54
  version: '1.2'
55
+ - !ruby/object:Gem::Dependency
56
+ name: turbo-rails
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '1.2'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '1.2'
69
+ - !ruby/object:Gem::Dependency
70
+ name: requestjs-rails
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: 0.0.11
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: 0.0.11
55
83
  description: A combobox implementation for Ruby on Rails apps running on Hotwire.
56
84
  email:
57
85
  - jose@farias.mx
@@ -65,11 +93,33 @@ files:
65
93
  - app/assets/javascripts/controllers/application.js
66
94
  - app/assets/javascripts/controllers/hw_combobox_controller.js
67
95
  - app/assets/javascripts/controllers/index.js
96
+ - app/assets/javascripts/helpers.js
68
97
  - app/assets/javascripts/hotwire_combobox_application.js
98
+ - app/assets/javascripts/models/combobox.js
99
+ - app/assets/javascripts/models/combobox/actors.js
100
+ - app/assets/javascripts/models/combobox/async_loading.js
101
+ - app/assets/javascripts/models/combobox/autocomplete.js
102
+ - app/assets/javascripts/models/combobox/base.js
103
+ - app/assets/javascripts/models/combobox/dialog.js
104
+ - app/assets/javascripts/models/combobox/filtering.js
105
+ - app/assets/javascripts/models/combobox/navigation.js
106
+ - app/assets/javascripts/models/combobox/options.js
107
+ - app/assets/javascripts/models/combobox/selection.js
108
+ - app/assets/javascripts/models/combobox/toggle.js
109
+ - app/assets/javascripts/models/combobox/validity.js
110
+ - app/assets/javascripts/vendor/bodyScrollLock.js
111
+ - app/assets/stylesheets/hotwire_combobox.css
69
112
  - app/helpers/hotwire_combobox/helper.rb
70
113
  - app/presenters/hotwire_combobox/component.rb
71
114
  - app/presenters/hotwire_combobox/listbox/option.rb
72
115
  - app/views/hotwire_combobox/_combobox.html.erb
116
+ - app/views/hotwire_combobox/_next_page.turbo_stream.erb
117
+ - app/views/hotwire_combobox/_paginated_options.turbo_stream.erb
118
+ - app/views/hotwire_combobox/_pagination.html.erb
119
+ - app/views/hotwire_combobox/combobox/_dialog.html.erb
120
+ - app/views/hotwire_combobox/combobox/_hidden_field.html.erb
121
+ - app/views/hotwire_combobox/combobox/_input.html.erb
122
+ - app/views/hotwire_combobox/combobox/_paginated_listbox.html.erb
73
123
  - config/importmap.rb
74
124
  - lib/hotwire_combobox.rb
75
125
  - lib/hotwire_combobox/engine.rb