hotwire_combobox 0.1.37 → 0.1.38

Sign up to get free protection for your applications and to get access to all the features.
@@ -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