hotwire_combobox 0.1.42 → 0.2.0

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/README.md +1 -0
  3. data/app/assets/javascripts/controllers/hw_combobox_controller.js +25 -3
  4. data/app/assets/javascripts/hotwire_combobox.esm.js +531 -127
  5. data/app/assets/javascripts/hotwire_combobox.umd.js +531 -127
  6. data/app/assets/javascripts/hw_combobox/helpers.js +16 -0
  7. data/app/assets/javascripts/hw_combobox/models/combobox/announcements.js +7 -0
  8. data/app/assets/javascripts/hw_combobox/models/combobox/async_loading.js +4 -0
  9. data/app/assets/javascripts/hw_combobox/models/combobox/autocomplete.js +8 -6
  10. data/app/assets/javascripts/hw_combobox/models/combobox/dialog.js +1 -1
  11. data/app/assets/javascripts/hw_combobox/models/combobox/events.js +21 -6
  12. data/app/assets/javascripts/hw_combobox/models/combobox/filtering.js +33 -28
  13. data/app/assets/javascripts/hw_combobox/models/combobox/form_field.js +74 -0
  14. data/app/assets/javascripts/hw_combobox/models/combobox/multiselect.js +160 -0
  15. data/app/assets/javascripts/hw_combobox/models/combobox/navigation.js +15 -6
  16. data/app/assets/javascripts/hw_combobox/models/combobox/options.js +29 -9
  17. data/app/assets/javascripts/hw_combobox/models/combobox/selection.js +103 -51
  18. data/app/assets/javascripts/hw_combobox/models/combobox/toggle.js +45 -16
  19. data/app/assets/javascripts/hw_combobox/models/combobox/validity.js +1 -1
  20. data/app/assets/javascripts/hw_combobox/models/combobox.js +3 -0
  21. data/app/assets/stylesheets/hotwire_combobox.css +84 -18
  22. data/app/presenters/hotwire_combobox/component/customizable.rb +9 -1
  23. data/app/presenters/hotwire_combobox/component.rb +95 -28
  24. data/app/presenters/hotwire_combobox/listbox/group.rb +45 -0
  25. data/app/presenters/hotwire_combobox/listbox/item.rb +104 -0
  26. data/app/presenters/hotwire_combobox/listbox/option.rb +9 -4
  27. data/app/views/hotwire_combobox/_component.html.erb +1 -0
  28. data/app/views/hotwire_combobox/_selection_chip.turbo_stream.erb +8 -0
  29. data/app/views/hotwire_combobox/layouts/_selection_chip.turbo_stream.erb +7 -0
  30. data/lib/hotwire_combobox/helper.rb +111 -86
  31. data/lib/hotwire_combobox/version.rb +1 -1
  32. metadata +9 -2
@@ -2,33 +2,39 @@ require "securerandom"
2
2
 
3
3
  class HotwireCombobox::Component
4
4
  include Customizable
5
+ include ActiveModel::Validations
5
6
 
6
7
  attr_reader :options, :label
7
8
 
9
+ validate :name_when_new_on_multiselect_must_match_original_name
10
+
8
11
  def initialize \
9
- view, name,
10
- association_name: nil,
11
- async_src: nil,
12
- autocomplete: :both,
13
- data: {},
14
- dialog_label: nil,
15
- form: nil,
16
- id: nil,
17
- input: {},
18
- label: nil,
19
- mobile_at: "640px",
20
- name_when_new: nil,
21
- open: false,
22
- options: [],
23
- value: nil,
24
- **rest
12
+ view, name,
13
+ association_name: nil,
14
+ async_src: nil,
15
+ autocomplete: :both,
16
+ data: {},
17
+ dialog_label: nil,
18
+ form: nil,
19
+ id: nil,
20
+ input: {},
21
+ label: nil,
22
+ mobile_at: "640px",
23
+ multiselect_chip_src: nil,
24
+ name_when_new: nil,
25
+ open: false,
26
+ options: [],
27
+ value: nil,
28
+ **rest
25
29
  @view, @autocomplete, @id, @name, @value, @form, @async_src, @label,
26
- @name_when_new, @open, @data, @mobile_at, @options, @dialog_label =
30
+ @name_when_new, @open, @data, @mobile_at, @multiselect_chip_src, @options, @dialog_label =
27
31
  view, autocomplete, id, name.to_s, value, form, async_src, label,
28
- name_when_new, open, data, mobile_at, options, dialog_label
32
+ name_when_new, open, data, mobile_at, multiselect_chip_src, options, dialog_label
29
33
 
30
34
  @combobox_attrs = input.reverse_merge(rest).deep_symbolize_keys
31
35
  @association_name = association_name || infer_association_name
36
+
37
+ validate!
32
38
  end
33
39
 
34
40
  def render_in(view_context, &block)
@@ -39,7 +45,7 @@ class HotwireCombobox::Component
39
45
 
40
46
  def fieldset_attrs
41
47
  apply_customizations_to :fieldset, base: {
42
- class: "hw-combobox",
48
+ class: [ "hw-combobox", { "hw-combobox--multiple": multiselect? } ],
43
49
  data: fieldset_data
44
50
  }
45
51
  end
@@ -72,6 +78,23 @@ class HotwireCombobox::Component
72
78
  end
73
79
 
74
80
 
81
+ def announcer_attrs
82
+ {
83
+ style: "
84
+ position: absolute;
85
+ width: 1px;
86
+ height: 1px;
87
+ margin: -1px;
88
+ padding: 0;
89
+ overflow: hidden;
90
+ clip: rect(0, 0, 0, 0);
91
+ border: 0;".squish,
92
+ aria: announcer_aria,
93
+ data: announcer_data
94
+ }
95
+ end
96
+
97
+
75
98
  def input_attrs
76
99
  nested_attrs = %i[ data aria ]
77
100
 
@@ -103,7 +126,8 @@ class HotwireCombobox::Component
103
126
  role: :listbox,
104
127
  class: "hw-combobox__listbox",
105
128
  hidden: "",
106
- data: listbox_data
129
+ data: listbox_data,
130
+ aria: listbox_aria
107
131
  }
108
132
  end
109
133
 
@@ -150,7 +174,8 @@ class HotwireCombobox::Component
150
174
  id: dialog_listbox_id,
151
175
  class: "hw-combobox__dialog__listbox",
152
176
  role: :listbox,
153
- data: dialog_listbox_data
177
+ data: dialog_listbox_data,
178
+ aria: dialog_listbox_aria
154
179
  }
155
180
  end
156
181
 
@@ -173,10 +198,23 @@ class HotwireCombobox::Component
173
198
  private
174
199
  attr_reader :view, :autocomplete, :id, :name, :value, :form,
175
200
  :name_when_new, :open, :data, :combobox_attrs, :mobile_at,
176
- :association_name
201
+ :association_name, :multiselect_chip_src
202
+
203
+ def name_when_new_on_multiselect_must_match_original_name
204
+ return unless multiselect? && name_when_new.present?
205
+
206
+ unless name_when_new.to_s == name
207
+ errors.add :name_when_new, :must_match_original_name,
208
+ message: "must match the regular name ('#{name}', in this case) on multiselect comboboxes."
209
+ end
210
+ end
211
+
212
+ def multiselect?
213
+ multiselect_chip_src.present?
214
+ end
177
215
 
178
216
  def infer_association_name
179
- if name.include?("_id")
217
+ if name.end_with?("_id")
180
218
  name.sub(/_id\z/, "")
181
219
  end
182
220
  end
@@ -192,6 +230,7 @@ class HotwireCombobox::Component
192
230
  hw_combobox_small_viewport_max_width_value: mobile_at,
193
231
  hw_combobox_async_src_value: async_src,
194
232
  hw_combobox_prefilled_display_value: prefilled_display,
233
+ hw_combobox_selection_chip_src_value: multiselect_chip_src,
195
234
  hw_combobox_filterable_attribute_value: "data-filterable-as",
196
235
  hw_combobox_autocompletable_attribute_value: "data-autocompletable-as",
197
236
  hw_combobox_selected_class: "hw-combobox__option--selected",
@@ -199,6 +238,8 @@ class HotwireCombobox::Component
199
238
  end
200
239
 
201
240
  def prefilled_display
241
+ return if multiselect?
242
+
202
243
  if async_src && associated_object
203
244
  associated_object.to_combobox_display
204
245
  elsif hidden_field_value
@@ -227,7 +268,22 @@ class HotwireCombobox::Component
227
268
 
228
269
 
229
270
  def main_wrapper_data
230
- { hw_combobox_target: "mainWrapper" }
271
+ {
272
+ action: ("click->hw-combobox#openByFocusing:self" if multiselect?),
273
+ hw_combobox_target: "mainWrapper"
274
+ }
275
+ end
276
+
277
+
278
+ def announcer_aria
279
+ {
280
+ live: :polite,
281
+ atomic: true
282
+ }
283
+ end
284
+
285
+ def announcer_data
286
+ { hw_combobox_target: "announcer" }
231
287
  end
232
288
 
233
289
 
@@ -249,7 +305,9 @@ class HotwireCombobox::Component
249
305
  if form&.object&.defined_enums&.try :[], name
250
306
  form.object.public_send "#{name}_before_type_cast"
251
307
  else
252
- form&.object&.try name
308
+ form&.object&.try(name).then do |value|
309
+ value.respond_to?(:map) ? value.join(",") : value
310
+ end
253
311
  end
254
312
  end
255
313
 
@@ -266,11 +324,12 @@ class HotwireCombobox::Component
266
324
  combobox_attrs.fetch(:data, {}).merge \
267
325
  action: "
268
326
  focus->hw-combobox#open
269
- input->hw-combobox#filter
327
+ input->hw-combobox#filterAndSelect
270
328
  keydown->hw-combobox#navigate
271
329
  click@window->hw-combobox#closeOnClickOutside
272
330
  focusin@window->hw-combobox#closeOnFocusOutside
273
- turbo:before-stream-render@document->hw-combobox#rerouteListboxStreamToDialog".squish,
331
+ turbo:before-stream-render@document->hw-combobox#rerouteListboxStreamToDialog
332
+ turbo:before-cache@document->hw-combobox#hideChipsForCache".squish,
274
333
  hw_combobox_target: "combobox",
275
334
  async_id: canonical_id
276
335
  end
@@ -301,6 +360,10 @@ class HotwireCombobox::Component
301
360
  { hw_combobox_target: "listbox" }
302
361
  end
303
362
 
363
+ def listbox_aria
364
+ { multiselectable: multiselect? }
365
+ end
366
+
304
367
 
305
368
  def dialog_data
306
369
  {
@@ -316,7 +379,7 @@ class HotwireCombobox::Component
316
379
  def dialog_input_data
317
380
  {
318
381
  action: "
319
- input->hw-combobox#filter
382
+ input->hw-combobox#filterAndSelect
320
383
  keydown->hw-combobox#navigate
321
384
  click@window->hw-combobox#closeOnClickOutside".squish,
322
385
  hw_combobox_target: "dialogCombobox"
@@ -340,6 +403,10 @@ class HotwireCombobox::Component
340
403
  { hw_combobox_target: "dialogListbox" }
341
404
  end
342
405
 
406
+ def dialog_listbox_aria
407
+ { multiselectable: multiselect? }
408
+ end
409
+
343
410
  def dialog_focus_trap_data
344
411
  { hw_combobox_target: "dialogFocusTrap" }
345
412
  end
@@ -0,0 +1,45 @@
1
+ require "securerandom"
2
+
3
+ class HotwireCombobox::Listbox::Group
4
+ def initialize(name, options:)
5
+ @name = name
6
+ @options = options
7
+ end
8
+
9
+ def render_in(view)
10
+ view.tag.ul **group_attrs do
11
+ view.concat view.tag.li(name, **label_attrs)
12
+
13
+ options.map do |option|
14
+ view.concat view.render(option)
15
+ end
16
+ end
17
+ end
18
+
19
+ private
20
+ attr_reader :name, :options
21
+
22
+ def id
23
+ @id ||= SecureRandom.uuid
24
+ end
25
+
26
+ def group_attrs
27
+ {
28
+ class: "hw-combobox__group",
29
+ role: :group,
30
+ aria: group_aria
31
+ }
32
+ end
33
+
34
+ def group_aria
35
+ { labelledby: id }
36
+ end
37
+
38
+ def label_attrs
39
+ {
40
+ id: id,
41
+ class: "hw-combobox__group__label",
42
+ role: :presentation
43
+ }
44
+ end
45
+ end
@@ -0,0 +1,104 @@
1
+ class HotwireCombobox::Listbox::Item
2
+ class << self
3
+ def collection_for(view, options, render_in:, include_blank:, **custom_methods)
4
+ new(view, options, render_in: render_in, include_blank: include_blank, **custom_methods).items
5
+ end
6
+ end
7
+
8
+ def initialize(view, options, render_in:, include_blank:, **custom_methods)
9
+ @view = view
10
+ @options = options
11
+ @render_in = render_in
12
+ @include_blank = include_blank
13
+ @custom_methods = custom_methods
14
+ end
15
+
16
+ def items
17
+ items = groups_or_options
18
+ items.unshift(blank_option) if include_blank.present?
19
+ items
20
+ end
21
+
22
+ private
23
+ attr_reader :view, :options, :render_in, :include_blank, :custom_methods
24
+
25
+ def groups_or_options
26
+ if grouped?
27
+ create_listbox_group options
28
+ else
29
+ create_listbox_options options
30
+ end
31
+ end
32
+
33
+ def grouped?
34
+ key, value = options.to_a.first
35
+ value.is_a? Array
36
+ end
37
+
38
+ def create_listbox_group(options)
39
+ options.map do |group_name, group_options|
40
+ HotwireCombobox::Listbox::Group.new group_name,
41
+ options: create_listbox_options(group_options)
42
+ end
43
+ end
44
+
45
+ def create_listbox_options(options)
46
+ options.map do |option|
47
+ HotwireCombobox::Listbox::Option.new **option_attrs(option)
48
+ end
49
+ end
50
+
51
+ def option_attrs(option)
52
+ case option
53
+ when Hash
54
+ option.tap do |attrs|
55
+ attrs[:content] = render_content(object: attrs[:display], attrs: attrs) if render_in.present?
56
+ end
57
+ when String
58
+ {}.tap do |attrs|
59
+ attrs[:display] = option
60
+ attrs[:value] = option
61
+ attrs[:content] = render_content(object: attrs[:display], attrs: attrs) if render_in.present?
62
+ end
63
+ when Array
64
+ {}.tap do |attrs|
65
+ attrs[:display] = option.first
66
+ attrs[:value] = option.last
67
+ attrs[:content] = render_content(object: attrs[:display], attrs: attrs) if render_in.present?
68
+ end
69
+ else
70
+ {}.tap do |attrs|
71
+ attrs[:id] = view.hw_call_method_or_proc(option, custom_methods[:id]) if custom_methods[:id]
72
+ attrs[:display] = view.hw_call_method_or_proc(option, custom_methods[:display]) if custom_methods[:display]
73
+ attrs[:value] = view.hw_call_method_or_proc(option, custom_methods[:value] || :id)
74
+
75
+ if render_in.present?
76
+ attrs[:content] = render_content(object: option, attrs: attrs)
77
+ elsif custom_methods[:content]
78
+ attrs[:content] = view.hw_call_method_or_proc(option, custom_methods[:content])
79
+ end
80
+ end
81
+ end
82
+ end
83
+
84
+ def render_content(render_opts: render_in, object:, attrs:)
85
+ view.render **render_opts.reverse_merge(
86
+ object: object,
87
+ locals: { combobox_display: attrs[:display], combobox_value: attrs[:value] })
88
+ end
89
+
90
+ def blank_option
91
+ display, content = extract_blank_display_and_content
92
+ HotwireCombobox::Listbox::Option.new display: display, content: content, value: "", blank: true
93
+ end
94
+
95
+ def extract_blank_display_and_content
96
+ if include_blank.is_a? Hash
97
+ text = include_blank.delete(:text)
98
+
99
+ [ text, render_content(render_opts: include_blank, object: text, attrs: { display: text, value: "" }) ]
100
+ else
101
+ [ include_blank, include_blank ]
102
+ end
103
+ end
104
+ end
@@ -17,6 +17,10 @@ class HotwireCombobox::Listbox::Option
17
17
  option.try(:autocompletable_as) || option.try(:display)
18
18
  end
19
19
 
20
+ def content
21
+ option.try(:content) || option.try(:display)
22
+ end
23
+
20
24
  private
21
25
  Data = Struct.new :id, :value, :display, :content, :blank, :filterable_as, :autocompletable_as, keyword_init: true
22
26
 
@@ -27,7 +31,8 @@ class HotwireCombobox::Listbox::Option
27
31
  id: id,
28
32
  role: :option,
29
33
  class: [ "hw-combobox__option", { "hw-combobox__option--blank": blank? } ],
30
- data: data
34
+ data: data,
35
+ aria: aria
31
36
  }
32
37
  end
33
38
 
@@ -37,15 +42,15 @@ class HotwireCombobox::Listbox::Option
37
42
 
38
43
  def data
39
44
  {
40
- action: "click->hw-combobox#selectOptionOnClick",
45
+ action: "click->hw-combobox#selectOnClick",
41
46
  filterable_as: filterable_as,
42
47
  autocompletable_as: autocompletable_as,
43
48
  value: value
44
49
  }
45
50
  end
46
51
 
47
- def content
48
- option.try(:content) || option.try(:display)
52
+ def aria
53
+ { selected: false }
49
54
  end
50
55
 
51
56
  def filterable_as
@@ -4,6 +4,7 @@
4
4
  <%= render "hotwire_combobox/combobox/hidden_field", component: component %>
5
5
 
6
6
  <%= tag.div **component.main_wrapper_attrs do %>
7
+ <%= tag.div **component.announcer_attrs %>
7
8
  <%= render "hotwire_combobox/combobox/input", component: component %>
8
9
  <%= render "hotwire_combobox/combobox/paginated_listbox", component: component %>
9
10
  <%= render "hotwire_combobox/combobox/dialog", component: component %>
@@ -0,0 +1,8 @@
1
+ <%# locals: (display:, value:, for_id:, remover_attrs:) -%>
2
+
3
+ <%= within_combobox_selection_chip(for_id: for_id) do %>
4
+ <%= tag.div class: "hw-combobox__chip" do %>
5
+ <%= tag.span display %>
6
+ <%= tag.span **remover_attrs %>
7
+ <% end %>
8
+ <% end %>
@@ -0,0 +1,7 @@
1
+ <%# locals: (for_id:) -%>
2
+
3
+ <%= turbo_stream.before for_id do %>
4
+ <%= tag.div data: { hw_combobox_chip: "" } do %>
5
+ <%= yield %>
6
+ <% end %>
7
+ <% end %>
@@ -13,38 +13,129 @@ module HotwireCombobox
13
13
  def hw_combobox_style_tag(*args, **kwargs)
14
14
  stylesheet_link_tag HotwireCombobox.stylesheet_path, *args, **kwargs
15
15
  end
16
- hw_alias :hw_combobox_style_tag
17
16
 
18
17
  def hw_combobox_tag(name, options_or_src = [], render_in: {}, include_blank: nil, **kwargs, &block)
19
- options, src = hw_extract_options_and_src(options_or_src, render_in, include_blank)
18
+ options, src = hw_extract_options_and_src options_or_src, render_in, include_blank
20
19
  component = HotwireCombobox::Component.new self, name, options: options, async_src: src, **kwargs
21
20
  render component, &block
22
21
  end
23
- hw_alias :hw_combobox_tag
24
22
 
25
- def hw_combobox_options(options, render_in: {}, include_blank: nil, display: :to_combobox_display, **methods)
23
+ def hw_combobox_options(
24
+ options,
25
+ render_in: {},
26
+ include_blank: nil,
27
+ display: :to_combobox_display,
28
+ **custom_methods)
26
29
  if options.first.is_a? HotwireCombobox::Listbox::Option
27
30
  options
28
31
  else
29
- opts = hw_parse_combobox_options options, render_in_proc: hw_render_in_proc(render_in), **methods.merge(display: display)
30
- opts.unshift(hw_blank_option(include_blank)) if include_blank.present?
31
- opts
32
+ HotwireCombobox::Listbox::Item.collection_for \
33
+ self,
34
+ options,
35
+ render_in: render_in,
36
+ include_blank: include_blank,
37
+ **custom_methods.merge(display: display)
32
38
  end
33
39
  end
34
- hw_alias :hw_combobox_options
35
40
 
36
- def hw_paginated_combobox_options(options, for_id: params[:for_id], src: request.path, next_page: nil, render_in: {}, include_blank: {}, **methods)
37
- include_blank = params[:page] ? nil : include_blank
38
- options = hw_combobox_options options, render_in: render_in, include_blank: include_blank, **methods
41
+ def hw_paginated_combobox_options(
42
+ options,
43
+ for_id: params[:for_id],
44
+ src: request.path,
45
+ next_page: nil,
46
+ render_in: {},
47
+ include_blank: {},
48
+ **custom_methods)
49
+ include_blank = params[:page].to_i > 0 ? nil : include_blank
50
+ options = hw_combobox_options options, render_in: render_in, include_blank: include_blank, **custom_methods
39
51
  this_page = render "hotwire_combobox/paginated_options", for_id: for_id, options: options
40
52
  next_page = render "hotwire_combobox/next_page", for_id: for_id, src: src, next_page: next_page
41
53
 
42
54
  safe_join [ this_page, next_page ]
43
55
  end
44
- hw_alias :hw_paginated_combobox_options
45
-
46
56
  alias_method :hw_async_combobox_options, :hw_paginated_combobox_options
57
+
58
+ def hw_within_combobox_selection_chip(for_id: params[:for_id], &block)
59
+ render layout: "hotwire_combobox/layouts/selection_chip", locals: { for_id: for_id }, &block
60
+ end
61
+
62
+ def hw_combobox_selection_chip(
63
+ display:,
64
+ value:,
65
+ for_id: params[:for_id],
66
+ remover_attrs: hw_combobox_chip_remover_attrs(display: display, value: value))
67
+ render "hotwire_combobox/selection_chip",
68
+ display: display,
69
+ value: value,
70
+ for_id: for_id,
71
+ remover_attrs: remover_attrs
72
+ end
73
+
74
+ def hw_combobox_selection_chips_for(
75
+ objects,
76
+ display: :to_combobox_display,
77
+ value: :id,
78
+ for_id: params[:for_id])
79
+ objects.map do |object|
80
+ hw_combobox_selection_chip \
81
+ display: hw_call_method(object, display),
82
+ value: hw_call_method(object, value),
83
+ for_id: for_id
84
+ end.then { |chips| safe_join chips }
85
+ end
86
+
87
+ def hw_dismissing_combobox_selection_chip(display:, value:, for_id: params[:for_id])
88
+ hw_combobox_selection_chip \
89
+ display: display,
90
+ value: value,
91
+ for_id: for_id,
92
+ remover_attrs: hw_combobox_dismissing_chip_remover_attrs(display, value)
93
+ end
94
+
95
+ def hw_dismissing_combobox_selection_chips_for(
96
+ objects,
97
+ display: :to_combobox_display,
98
+ value: :id,
99
+ for_id: params[:for_id])
100
+ objects.map do |object|
101
+ hw_dismissing_combobox_selection_chip \
102
+ display: hw_call_method(object, display),
103
+ value: hw_call_method(object, value),
104
+ for_id: for_id
105
+ end.then { |chips| safe_join chips }
106
+ end
107
+
108
+ def hw_combobox_chip_remover_attrs(display:, value:, **kwargs)
109
+ {
110
+ tabindex: "0",
111
+ class: token_list("hw-combobox__chip__remover", kwargs[:class]),
112
+ aria: { label: "Remove #{display}" },
113
+ data: {
114
+ action: "click->hw-combobox#removeChip:stop keydown->hw-combobox#navigateChip",
115
+ hw_combobox_target: "chipDismisser",
116
+ hw_combobox_value_param: value
117
+ }
118
+ }
119
+ end
120
+
121
+ def hw_combobox_dismissing_chip_remover_attrs(display, value)
122
+ hw_combobox_chip_remover_attrs(display: display, value: value).tap do |attrs|
123
+ attrs[:data][:hw_combobox_target] = token_list(attrs[:data][:hw_combobox_target], "closer")
124
+ end
125
+ end
126
+
127
+ hw_alias :hw_combobox_style_tag
128
+ hw_alias :hw_combobox_tag
129
+ hw_alias :hw_combobox_options
130
+ hw_alias :hw_paginated_combobox_options
47
131
  hw_alias :hw_async_combobox_options
132
+ hw_alias :hw_within_combobox_selection_chip
133
+ hw_alias :hw_combobox_selection_chip
134
+ hw_alias :hw_combobox_selection_chips_for
135
+ hw_alias :hw_dismissing_combobox_selection_chip
136
+ hw_alias :hw_dismissing_combobox_selection_chips_for
137
+ hw_alias :hw_combobox_chip_remover_attrs
138
+ hw_alias :hw_combobox_dismissing_chip_remover_attrs
48
139
 
49
140
  # private library use only
50
141
  def hw_listbox_id(id)
@@ -70,23 +161,7 @@ module HotwireCombobox
70
161
  end
71
162
 
72
163
  def hw_combobox_page_stream_action
73
- params[:page] ? :append : :update
74
- end
75
-
76
- def hw_blank_option(include_blank)
77
- display, content = hw_extract_blank_display_and_content include_blank
78
-
79
- HotwireCombobox::Listbox::Option.new display: display, content: content, value: "", blank: true
80
- end
81
-
82
- def hw_extract_blank_display_and_content(include_blank)
83
- if include_blank.is_a? Hash
84
- text = include_blank.delete(:text)
85
-
86
- [ text, hw_call_render_in_proc(hw_render_in_proc(include_blank), text, display: text, value: "") ]
87
- else
88
- [ include_blank, include_blank ]
89
- end
164
+ params[:page].to_i > 0 ? :append : :update
90
165
  end
91
166
 
92
167
  def hw_uri_with_params(url_or_path, **params)
@@ -98,13 +173,15 @@ module HotwireCombobox
98
173
  url_or_path
99
174
  end
100
175
 
101
- private
102
- def hw_render_in_proc(render_in)
103
- if render_in.present?
104
- ->(object, locals) { render(**render_in.reverse_merge(object: object, locals: locals)) }
176
+ def hw_call_method_or_proc(object, method_or_proc)
177
+ if method_or_proc.is_a? Proc
178
+ method_or_proc.call object
179
+ else
180
+ hw_call_method object, method_or_proc
105
181
  end
106
182
  end
107
183
 
184
+ private
108
185
  def hw_extract_options_and_src(options_or_src, render_in, include_blank)
109
186
  if options_or_src.is_a? String
110
187
  [ [], options_or_src ]
@@ -113,58 +190,6 @@ module HotwireCombobox
113
190
  end
114
191
  end
115
192
 
116
- def hw_parse_combobox_options(options, render_in_proc: nil, **methods)
117
- options.map do |option|
118
- HotwireCombobox::Listbox::Option.new \
119
- **hw_option_attrs_for(option, render_in_proc: render_in_proc, **methods)
120
- end
121
- end
122
-
123
- def hw_option_attrs_for(option, render_in_proc: nil, **methods)
124
- case option
125
- when Hash
126
- option.tap do |attrs|
127
- attrs[:content] = hw_call_render_in_proc(render_in_proc, attrs[:display], attrs) if render_in_proc
128
- end
129
- when String
130
- {}.tap do |attrs|
131
- attrs[:display] = option
132
- attrs[:value] = option
133
- attrs[:content] = hw_call_render_in_proc(render_in_proc, attrs[:display], attrs) if render_in_proc
134
- end
135
- when Array
136
- {}.tap do |attrs|
137
- attrs[:display] = option.first
138
- attrs[:value] = option.last
139
- attrs[:content] = hw_call_render_in_proc(render_in_proc, attrs[:display], attrs) if render_in_proc
140
- end
141
- else
142
- {}.tap do |attrs|
143
- attrs[:id] = hw_call_method_or_proc(option, methods[:id]) if methods[:id]
144
- attrs[:display] = hw_call_method_or_proc(option, methods[:display]) if methods[:display]
145
- attrs[:value] = hw_call_method_or_proc(option, methods[:value] || :id)
146
-
147
- if render_in_proc
148
- attrs[:content] = hw_call_render_in_proc(render_in_proc, option, attrs)
149
- elsif methods[:content]
150
- attrs[:content] = hw_call_method_or_proc(option, methods[:content])
151
- end
152
- end
153
- end
154
- end
155
-
156
- def hw_call_render_in_proc(render_in_proc, object, attrs)
157
- render_in_proc.(object, combobox_display: attrs[:display], combobox_value: attrs[:value])
158
- end
159
-
160
- def hw_call_method_or_proc(object, method_or_proc)
161
- if method_or_proc.is_a? Proc
162
- method_or_proc.call object
163
- else
164
- hw_call_method object, method_or_proc
165
- end
166
- end
167
-
168
193
  def hw_call_method(object, method)
169
194
  if object.respond_to? method
170
195
  object.public_send method