hotwire_combobox 0.1.37 → 0.1.38

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.
@@ -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
 
@@ -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,
@@ -181,9 +183,13 @@ class HotwireCombobox::Component
181
183
  form&.object&.class&.reflect_on_association(association_name).present?
182
184
  end
183
185
 
186
+ def async_src
187
+ view.hw_uri_with_params @async_src, for_id: canonical_id, format: :turbo_stream
188
+ end
189
+
184
190
 
185
191
  def canonical_id
186
- id || form&.field_id(name)
192
+ id || form&.field_id(name) || SecureRandom.uuid
187
193
  end
188
194
 
189
195
 
@@ -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,7 +26,7 @@ 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
@@ -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.38"
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.38
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-06 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -65,6 +65,8 @@ files:
65
65
  - Rakefile
66
66
  - app/assets/config/hw_combobox_manifest.js
67
67
  - app/assets/javascripts/controllers/hw_combobox_controller.js
68
+ - app/assets/javascripts/hotwire_combobox.esm.js
69
+ - app/assets/javascripts/hotwire_combobox.umd.js
68
70
  - app/assets/javascripts/hw_combobox/helpers.js
69
71
  - app/assets/javascripts/hw_combobox/models/combobox.js
70
72
  - app/assets/javascripts/hw_combobox/models/combobox/actors.js