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.
- checksums.yaml +4 -4
- data/README.md +1 -0
- data/app/assets/javascripts/controllers/hw_combobox_controller.js +25 -3
- data/app/assets/javascripts/hotwire_combobox.esm.js +531 -127
- data/app/assets/javascripts/hotwire_combobox.umd.js +531 -127
- data/app/assets/javascripts/hw_combobox/helpers.js +16 -0
- data/app/assets/javascripts/hw_combobox/models/combobox/announcements.js +7 -0
- data/app/assets/javascripts/hw_combobox/models/combobox/async_loading.js +4 -0
- data/app/assets/javascripts/hw_combobox/models/combobox/autocomplete.js +8 -6
- data/app/assets/javascripts/hw_combobox/models/combobox/dialog.js +1 -1
- data/app/assets/javascripts/hw_combobox/models/combobox/events.js +21 -6
- data/app/assets/javascripts/hw_combobox/models/combobox/filtering.js +33 -28
- data/app/assets/javascripts/hw_combobox/models/combobox/form_field.js +74 -0
- data/app/assets/javascripts/hw_combobox/models/combobox/multiselect.js +160 -0
- data/app/assets/javascripts/hw_combobox/models/combobox/navigation.js +15 -6
- data/app/assets/javascripts/hw_combobox/models/combobox/options.js +29 -9
- data/app/assets/javascripts/hw_combobox/models/combobox/selection.js +103 -51
- data/app/assets/javascripts/hw_combobox/models/combobox/toggle.js +45 -16
- data/app/assets/javascripts/hw_combobox/models/combobox/validity.js +1 -1
- data/app/assets/javascripts/hw_combobox/models/combobox.js +3 -0
- data/app/assets/stylesheets/hotwire_combobox.css +84 -18
- data/app/presenters/hotwire_combobox/component/customizable.rb +9 -1
- data/app/presenters/hotwire_combobox/component.rb +95 -28
- data/app/presenters/hotwire_combobox/listbox/group.rb +45 -0
- data/app/presenters/hotwire_combobox/listbox/item.rb +104 -0
- data/app/presenters/hotwire_combobox/listbox/option.rb +9 -4
- data/app/views/hotwire_combobox/_component.html.erb +1 -0
- data/app/views/hotwire_combobox/_selection_chip.turbo_stream.erb +8 -0
- data/app/views/hotwire_combobox/layouts/_selection_chip.turbo_stream.erb +7 -0
- data/lib/hotwire_combobox/helper.rb +111 -86
- data/lib/hotwire_combobox/version.rb +1 -1
- 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
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
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.
|
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
|
-
{
|
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
|
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#
|
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
|
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#
|
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#
|
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
|
48
|
-
|
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 %>
|
@@ -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
|
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(
|
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
|
-
|
30
|
-
|
31
|
-
|
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(
|
37
|
-
|
38
|
-
|
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
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
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
|