hotwire_combobox 0.1.42 → 0.2.0

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