hotwire_combobox 0.3.1 → 0.4.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/MIT-LICENSE +1 -1
- data/README.md +19 -15
- data/app/assets/config/hw_combobox_manifest.js +1 -1
- data/app/assets/javascripts/controllers/hw_combobox_controller.js +34 -18
- data/app/assets/javascripts/hotwire_combobox.esm.js +147 -72
- data/app/assets/javascripts/hw_combobox/helpers.js +9 -0
- data/app/assets/javascripts/hw_combobox/models/combobox/autocomplete.js +14 -4
- data/app/assets/javascripts/hw_combobox/models/combobox/callbacks.js +46 -0
- data/app/assets/javascripts/hw_combobox/models/combobox/dialog.js +1 -4
- data/app/assets/javascripts/hw_combobox/models/combobox/filtering.js +19 -8
- data/app/assets/javascripts/hw_combobox/models/combobox/form_field.js +1 -2
- data/app/assets/javascripts/hw_combobox/models/combobox/multiselect.js +6 -3
- data/app/assets/javascripts/hw_combobox/models/combobox/navigation.js +2 -2
- data/app/assets/javascripts/hw_combobox/models/combobox/selection.js +4 -12
- data/app/assets/javascripts/hw_combobox/models/combobox/toggle.js +13 -18
- data/app/assets/javascripts/hw_combobox/models/combobox.js +1 -0
- data/app/assets/stylesheets/hotwire_combobox.css +13 -3
- data/app/presenters/hotwire_combobox/component/announced.rb +5 -0
- data/app/presenters/hotwire_combobox/component/associations.rb +18 -0
- data/app/presenters/hotwire_combobox/component/async.rb +6 -0
- data/app/presenters/hotwire_combobox/component/customizable.rb +8 -20
- data/app/presenters/hotwire_combobox/component/freetext.rb +26 -0
- data/app/presenters/hotwire_combobox/component/markup/dialog.rb +57 -0
- data/app/presenters/hotwire_combobox/component/markup/fieldset.rb +46 -0
- data/app/presenters/hotwire_combobox/component/markup/form.rb +6 -0
- data/app/presenters/hotwire_combobox/component/markup/handle.rb +7 -0
- data/app/presenters/hotwire_combobox/component/markup/hidden_field.rb +28 -0
- data/app/presenters/hotwire_combobox/component/markup/input.rb +44 -0
- data/app/presenters/hotwire_combobox/component/markup/label.rb +5 -0
- data/app/presenters/hotwire_combobox/component/markup/listbox.rb +14 -0
- data/app/presenters/hotwire_combobox/component/markup/wrapper.rb +7 -0
- data/app/presenters/hotwire_combobox/component/multiselect.rb +6 -0
- data/app/presenters/hotwire_combobox/component/paginated.rb +18 -0
- data/app/presenters/hotwire_combobox/component.rb +32 -398
- data/app/presenters/hotwire_combobox/listbox/group.rb +3 -15
- data/app/presenters/hotwire_combobox/listbox/item.rb +5 -12
- data/app/presenters/hotwire_combobox/listbox/option.rb +3 -19
- data/app/views/hotwire_combobox/_component.html.erb +28 -6
- data/app/views/hotwire_combobox/_pagination.html.erb +3 -3
- data/config/hw_importmap.rb +1 -1
- data/lib/hotwire_combobox/helper.rb +24 -65
- data/lib/hotwire_combobox/platform.rb +15 -0
- data/lib/hotwire_combobox/version.rb +1 -1
- data/lib/hotwire_combobox.rb +1 -0
- metadata +34 -11
- data/app/assets/javascripts/hotwire_combobox.umd.js +0 -1769
- data/app/views/hotwire_combobox/combobox/_dialog.html.erb +0 -9
- data/app/views/hotwire_combobox/combobox/_hidden_field.html.erb +0 -4
- data/app/views/hotwire_combobox/combobox/_input.html.erb +0 -2
- data/app/views/hotwire_combobox/combobox/_paginated_listbox.html.erb +0 -9
@@ -0,0 +1,28 @@
|
|
1
|
+
module HotwireCombobox::Component::Markup::HiddenField
|
2
|
+
def hidden_field_attrs
|
3
|
+
customize :hidden_field, base: {
|
4
|
+
id: hidden_field_id, name: hidden_field_name, value: hidden_field_value,
|
5
|
+
data: { hw_combobox_target: "hiddenField" } }
|
6
|
+
end
|
7
|
+
|
8
|
+
private
|
9
|
+
def hidden_field_id
|
10
|
+
"#{canonical_id}-hw-hidden-field"
|
11
|
+
end
|
12
|
+
|
13
|
+
def hidden_field_name
|
14
|
+
form&.field_name(name) || name
|
15
|
+
end
|
16
|
+
|
17
|
+
def hidden_field_value
|
18
|
+
return value if value
|
19
|
+
|
20
|
+
if form_object&.try(:defined_enums)&.try(:[], name)
|
21
|
+
form_object.public_send "#{name}_before_type_cast"
|
22
|
+
else
|
23
|
+
form_object&.try(name).then do |value|
|
24
|
+
value.respond_to?(:map) ? value.join(",") : value
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module HotwireCombobox::Component::Markup::Input
|
2
|
+
def input_attrs
|
3
|
+
customize :input, base: {
|
4
|
+
id: input_id, role: :combobox, type: input_type,
|
5
|
+
class: "hw-combobox__input", autocomplete: :off,
|
6
|
+
data: input_data, aria: input_aria
|
7
|
+
}.merge(combobox_attrs.except(:data, :aria))
|
8
|
+
end
|
9
|
+
|
10
|
+
private
|
11
|
+
def input_id
|
12
|
+
canonical_id
|
13
|
+
end
|
14
|
+
|
15
|
+
def input_type
|
16
|
+
combobox_attrs[:type].to_s.presence_in(%w[ text search ]) || "text"
|
17
|
+
end
|
18
|
+
|
19
|
+
def input_data
|
20
|
+
data = combobox_attrs.fetch(:data, {}).dup
|
21
|
+
action = %w[
|
22
|
+
click->hw-combobox#toggle
|
23
|
+
keydown->hw-combobox#prepareToFilter
|
24
|
+
input->hw-combobox#filterAndSelect
|
25
|
+
keydown->hw-combobox#navigate
|
26
|
+
click@window->hw-combobox#closeOnClickOutside
|
27
|
+
focusin@window->hw-combobox#closeOnFocusOutside
|
28
|
+
turbo:before-stream-render@document->hw-combobox#rerouteListboxStreamToDialog
|
29
|
+
turbo:before-cache@document->hw-combobox#hideChipsForCache
|
30
|
+
turbo:morph-element->hw-combobox#idempotentConnect
|
31
|
+
].append(data.delete(:action)).compact.join(" ")
|
32
|
+
|
33
|
+
data.merge action: action, hw_combobox_target: "combobox", async_id: canonical_id
|
34
|
+
end
|
35
|
+
|
36
|
+
def input_aria
|
37
|
+
combobox_attrs.fetch(:aria, {}).merge \
|
38
|
+
controls: listbox_id,
|
39
|
+
owns: listbox_id,
|
40
|
+
haspopup: "listbox",
|
41
|
+
autocomplete: autocomplete,
|
42
|
+
activedescendant: ""
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module HotwireCombobox::Component::Markup::Listbox
|
2
|
+
def listbox_attrs
|
3
|
+
customize :listbox, base: {
|
4
|
+
id: listbox_id, role: :listbox, hidden: "",
|
5
|
+
class: "hw-combobox__listbox",
|
6
|
+
data: { hw_combobox_target: "listbox" },
|
7
|
+
aria: { multiselectable: multiselect? } }
|
8
|
+
end
|
9
|
+
|
10
|
+
private
|
11
|
+
def listbox_id
|
12
|
+
"#{canonical_id}-hw-listbox"
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module HotwireCombobox::Component::Paginated
|
2
|
+
def paginated?
|
3
|
+
async_src.present?
|
4
|
+
end
|
5
|
+
|
6
|
+
def pagination_attrs
|
7
|
+
{ for_id: canonical_id, src: async_src, loading: preload_next_page? ? :eager : :lazy }
|
8
|
+
end
|
9
|
+
|
10
|
+
private
|
11
|
+
def preload_next_page?
|
12
|
+
view.hw_first_page? && preload?
|
13
|
+
end
|
14
|
+
|
15
|
+
def preload?
|
16
|
+
preload.present?
|
17
|
+
end
|
18
|
+
end
|
@@ -1,36 +1,38 @@
|
|
1
1
|
require "securerandom"
|
2
2
|
|
3
3
|
class HotwireCombobox::Component
|
4
|
-
include Customizable
|
5
|
-
include ActiveModel::Validations
|
4
|
+
include Announced, Associations, Async, Customizable, Freetext, Multiselect, Paginated
|
6
5
|
|
7
|
-
|
6
|
+
# Markup modules depend on Customizable
|
7
|
+
include Markup::Dialog, Markup::Fieldset, Markup::Form, Markup::Handle,
|
8
|
+
Markup::HiddenField, Markup::Input, Markup::Label, Markup::Listbox, Markup::Wrapper
|
8
9
|
|
9
|
-
|
10
|
+
attr_reader :options, :label
|
10
11
|
|
11
|
-
def initialize
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
@
|
32
|
-
|
33
|
-
|
12
|
+
def initialize(
|
13
|
+
view, name,
|
14
|
+
association_name: nil,
|
15
|
+
async_src: nil,
|
16
|
+
autocomplete: :both,
|
17
|
+
data: {},
|
18
|
+
dialog_label: nil,
|
19
|
+
form: nil,
|
20
|
+
free_text: false,
|
21
|
+
id: nil,
|
22
|
+
input: {},
|
23
|
+
label: nil,
|
24
|
+
mobile_at: "640px",
|
25
|
+
multiselect_chip_src: nil,
|
26
|
+
name_when_new: nil,
|
27
|
+
open: false,
|
28
|
+
options: [],
|
29
|
+
preload: false,
|
30
|
+
request: nil,
|
31
|
+
value: nil, **rest)
|
32
|
+
@view, @autocomplete, @id, @name, @value, @form, @async_src, @label, @free_text, @request,
|
33
|
+
@preload, @name_when_new, @open, @data, @mobile_at, @multiselect_chip_src, @options, @dialog_label =
|
34
|
+
view, autocomplete, id, name.to_s, value, form, async_src, label, free_text, request,
|
35
|
+
preload, name_when_new, open, data, mobile_at, multiselect_chip_src, options, dialog_label
|
34
36
|
|
35
37
|
@combobox_attrs = input.reverse_merge(rest).deep_symbolize_keys
|
36
38
|
@association_name = association_name || infer_association_name
|
@@ -40,382 +42,14 @@ class HotwireCombobox::Component
|
|
40
42
|
|
41
43
|
def render_in(view_context, &block)
|
42
44
|
block.call(self) if block_given?
|
43
|
-
view_context.render partial: "hotwire_combobox/component", locals: { component: self }
|
44
|
-
end
|
45
|
-
|
46
|
-
|
47
|
-
def fieldset_attrs
|
48
|
-
apply_customizations_to :fieldset, base: {
|
49
|
-
class: [ "hw-combobox", { "hw-combobox--multiple": multiselect? } ],
|
50
|
-
data: fieldset_data
|
51
|
-
}
|
52
|
-
end
|
53
|
-
|
54
|
-
|
55
|
-
def label_attrs
|
56
|
-
apply_customizations_to :label, base: {
|
57
|
-
class: "hw-combobox__label",
|
58
|
-
for: input_id,
|
59
|
-
hidden: label.blank?
|
60
|
-
}
|
61
|
-
end
|
62
|
-
|
63
|
-
|
64
|
-
def hidden_field_attrs
|
65
|
-
apply_customizations_to :hidden_field, base: {
|
66
|
-
id: hidden_field_id,
|
67
|
-
name: hidden_field_name,
|
68
|
-
data: hidden_field_data,
|
69
|
-
value: hidden_field_value
|
70
|
-
}
|
71
|
-
end
|
72
|
-
|
73
|
-
|
74
|
-
def main_wrapper_attrs
|
75
|
-
apply_customizations_to :main_wrapper, base: {
|
76
|
-
class: "hw-combobox__main__wrapper",
|
77
|
-
data: main_wrapper_data
|
78
|
-
}
|
79
|
-
end
|
80
|
-
|
81
|
-
|
82
|
-
def announcer_attrs
|
83
|
-
{
|
84
|
-
class: "hw-combobox__announcer",
|
85
|
-
aria: announcer_aria,
|
86
|
-
data: announcer_data
|
87
|
-
}
|
88
|
-
end
|
89
|
-
|
90
|
-
|
91
|
-
def input_attrs
|
92
|
-
nested_attrs = %i[ data aria ]
|
93
|
-
|
94
|
-
base = {
|
95
|
-
id: input_id,
|
96
|
-
role: :combobox,
|
97
|
-
class: "hw-combobox__input",
|
98
|
-
type: input_type,
|
99
|
-
data: input_data,
|
100
|
-
aria: input_aria,
|
101
|
-
autocomplete: :off
|
102
|
-
}.merge combobox_attrs.except(*nested_attrs)
|
103
|
-
|
104
|
-
apply_customizations_to :input, base: base
|
105
|
-
end
|
106
|
-
|
107
|
-
|
108
|
-
def handle_attrs
|
109
|
-
apply_customizations_to :handle, base: {
|
110
|
-
class: "hw-combobox__handle",
|
111
|
-
data: handle_data
|
112
|
-
}
|
113
|
-
end
|
114
|
-
|
115
|
-
|
116
|
-
def listbox_attrs
|
117
|
-
apply_customizations_to :listbox, base: {
|
118
|
-
id: listbox_id,
|
119
|
-
role: :listbox,
|
120
|
-
class: "hw-combobox__listbox",
|
121
|
-
hidden: "",
|
122
|
-
data: listbox_data,
|
123
|
-
aria: listbox_aria
|
124
|
-
}
|
125
|
-
end
|
126
|
-
|
127
|
-
|
128
|
-
def dialog_wrapper_attrs
|
129
|
-
apply_customizations_to :dialog_wrapper, base: {
|
130
|
-
class: "hw-combobox__dialog__wrapper"
|
131
|
-
}
|
132
|
-
end
|
133
|
-
|
134
|
-
def dialog_attrs
|
135
|
-
apply_customizations_to :dialog, base: {
|
136
|
-
class: "hw-combobox__dialog",
|
137
|
-
role: :dialog,
|
138
|
-
data: dialog_data
|
139
|
-
}
|
140
|
-
end
|
141
|
-
|
142
|
-
def dialog_label
|
143
|
-
@dialog_label || label
|
144
|
-
end
|
145
|
-
|
146
|
-
def dialog_label_attrs
|
147
|
-
apply_customizations_to :dialog_label, base: {
|
148
|
-
class: "hw-combobox__dialog__label",
|
149
|
-
for: dialog_input_id
|
150
|
-
}
|
151
|
-
end
|
152
|
-
|
153
|
-
def dialog_input_attrs
|
154
|
-
apply_customizations_to :dialog_input, base: {
|
155
|
-
id: dialog_input_id,
|
156
|
-
role: :combobox,
|
157
|
-
class: "hw-combobox__dialog__input",
|
158
|
-
autofocus: "",
|
159
|
-
type: input_type,
|
160
|
-
data: dialog_input_data,
|
161
|
-
aria: dialog_input_aria
|
162
|
-
}
|
163
|
-
end
|
164
|
-
|
165
|
-
def dialog_listbox_attrs
|
166
|
-
apply_customizations_to :dialog_listbox, base: {
|
167
|
-
id: dialog_listbox_id,
|
168
|
-
class: "hw-combobox__dialog__listbox",
|
169
|
-
role: :listbox,
|
170
|
-
data: dialog_listbox_data,
|
171
|
-
aria: dialog_listbox_aria
|
172
|
-
}
|
173
|
-
end
|
174
|
-
|
175
|
-
def dialog_focus_trap_attrs
|
176
|
-
{
|
177
|
-
tabindex: "-1",
|
178
|
-
data: dialog_focus_trap_data
|
179
|
-
}
|
180
|
-
end
|
181
|
-
|
182
|
-
|
183
|
-
def paginated?
|
184
|
-
async_src.present?
|
185
|
-
end
|
186
|
-
|
187
|
-
def pagination_attrs
|
188
|
-
{ for_id: canonical_id, src: async_src }
|
45
|
+
view_context.render partial: "hotwire_combobox/component", locals: { component: self }, formats: [ :html ]
|
189
46
|
end
|
190
47
|
|
191
48
|
private
|
192
|
-
attr_reader :view, :autocomplete, :id, :name, :value, :form,
|
193
|
-
:
|
194
|
-
:association_name, :multiselect_chip_src
|
195
|
-
|
196
|
-
def name_when_new
|
197
|
-
if free_text && @name_when_new.blank?
|
198
|
-
hidden_field_name
|
199
|
-
else
|
200
|
-
@name_when_new
|
201
|
-
end
|
202
|
-
end
|
203
|
-
|
204
|
-
def name_when_new_on_multiselect_must_match_original_name
|
205
|
-
return unless multiselect? && name_when_new.present?
|
206
|
-
|
207
|
-
unless name_when_new.to_s == hidden_field_name
|
208
|
-
errors.add :name_when_new, :must_match_original_name,
|
209
|
-
message: "must match the regular name ('#{hidden_field_name}', in this case) on multiselect comboboxes."
|
210
|
-
end
|
211
|
-
end
|
212
|
-
|
213
|
-
def multiselect?
|
214
|
-
multiselect_chip_src.present?
|
215
|
-
end
|
216
|
-
|
217
|
-
def infer_association_name
|
218
|
-
if name.end_with?("_id")
|
219
|
-
name.sub(/_id\z/, "")
|
220
|
-
end
|
221
|
-
end
|
222
|
-
|
223
|
-
def fieldset_data
|
224
|
-
data.merge \
|
225
|
-
async_id: canonical_id,
|
226
|
-
controller: view.token_list("hw-combobox", data[:controller]),
|
227
|
-
hw_combobox_expanded_value: open,
|
228
|
-
hw_combobox_name_when_new_value: name_when_new,
|
229
|
-
hw_combobox_original_name_value: hidden_field_name,
|
230
|
-
hw_combobox_autocomplete_value: autocomplete,
|
231
|
-
hw_combobox_small_viewport_max_width_value: mobile_at,
|
232
|
-
hw_combobox_async_src_value: async_src,
|
233
|
-
hw_combobox_prefilled_display_value: prefilled_display,
|
234
|
-
hw_combobox_selection_chip_src_value: multiselect_chip_src,
|
235
|
-
hw_combobox_filterable_attribute_value: "data-filterable-as",
|
236
|
-
hw_combobox_autocompletable_attribute_value: "data-autocompletable-as",
|
237
|
-
hw_combobox_selected_class: "hw-combobox__option--selected",
|
238
|
-
hw_combobox_invalid_class: "hw-combobox__input--invalid"
|
239
|
-
end
|
240
|
-
|
241
|
-
def prefilled_display
|
242
|
-
return if multiselect? || !hidden_field_value
|
243
|
-
|
244
|
-
if async_src && associated_object
|
245
|
-
associated_object.to_combobox_display
|
246
|
-
elsif async_src && form_object&.respond_to?(name)
|
247
|
-
form_object.public_send name
|
248
|
-
else
|
249
|
-
options.find_by_value(hidden_field_value)&.autocompletable_as
|
250
|
-
end
|
251
|
-
end
|
252
|
-
|
253
|
-
def associated_object
|
254
|
-
@associated_object ||= if association_exists?
|
255
|
-
form_object&.public_send association_name
|
256
|
-
end
|
257
|
-
end
|
258
|
-
|
259
|
-
def association_exists?
|
260
|
-
association_name && form_object&.respond_to?(association_name)
|
261
|
-
end
|
262
|
-
|
263
|
-
def form_object
|
264
|
-
form&.object
|
265
|
-
end
|
266
|
-
|
267
|
-
def async_src
|
268
|
-
view.hw_uri_with_params @async_src, for_id: canonical_id, format: :turbo_stream
|
269
|
-
end
|
270
|
-
|
49
|
+
attr_reader :view, :autocomplete, :id, :name, :value, :form, :free_text, :open, :request,
|
50
|
+
:data, :combobox_attrs, :mobile_at, :association_name, :multiselect_chip_src, :preload
|
271
51
|
|
272
52
|
def canonical_id
|
273
53
|
@canonical_id ||= id || form&.field_id(name) || SecureRandom.uuid
|
274
54
|
end
|
275
|
-
|
276
|
-
|
277
|
-
def main_wrapper_data
|
278
|
-
{
|
279
|
-
action: ("click->hw-combobox#openByFocusing:self" if multiselect?),
|
280
|
-
hw_combobox_target: "mainWrapper"
|
281
|
-
}
|
282
|
-
end
|
283
|
-
|
284
|
-
|
285
|
-
def announcer_aria
|
286
|
-
{
|
287
|
-
live: :polite,
|
288
|
-
atomic: true
|
289
|
-
}
|
290
|
-
end
|
291
|
-
|
292
|
-
def announcer_data
|
293
|
-
{ hw_combobox_target: "announcer" }
|
294
|
-
end
|
295
|
-
|
296
|
-
|
297
|
-
def hidden_field_id
|
298
|
-
"#{canonical_id}-hw-hidden-field"
|
299
|
-
end
|
300
|
-
|
301
|
-
def hidden_field_name
|
302
|
-
form&.field_name(name) || name
|
303
|
-
end
|
304
|
-
|
305
|
-
def hidden_field_data
|
306
|
-
{ hw_combobox_target: "hiddenField" }
|
307
|
-
end
|
308
|
-
|
309
|
-
def hidden_field_value
|
310
|
-
return value if value
|
311
|
-
|
312
|
-
if form_object&.try(:defined_enums)&.try(:[], name)
|
313
|
-
form_object.public_send "#{name}_before_type_cast"
|
314
|
-
else
|
315
|
-
form_object&.try(name).then do |value|
|
316
|
-
value.respond_to?(:map) ? value.join(",") : value
|
317
|
-
end
|
318
|
-
end
|
319
|
-
end
|
320
|
-
|
321
|
-
|
322
|
-
def input_id
|
323
|
-
canonical_id
|
324
|
-
end
|
325
|
-
|
326
|
-
def input_type
|
327
|
-
combobox_attrs[:type].to_s.presence_in(%w[ text search ]) || "text"
|
328
|
-
end
|
329
|
-
|
330
|
-
def input_data
|
331
|
-
combobox_attrs.fetch(:data, {}).merge \
|
332
|
-
action: "
|
333
|
-
focus->hw-combobox#open
|
334
|
-
input->hw-combobox#filterAndSelect
|
335
|
-
keydown->hw-combobox#navigate
|
336
|
-
click@window->hw-combobox#closeOnClickOutside
|
337
|
-
focusin@window->hw-combobox#closeOnFocusOutside
|
338
|
-
turbo:before-stream-render@document->hw-combobox#rerouteListboxStreamToDialog
|
339
|
-
turbo:before-cache@document->hw-combobox#hideChipsForCache
|
340
|
-
turbo:morph-element->hw-combobox#idempotentConnect".squish,
|
341
|
-
hw_combobox_target: "combobox",
|
342
|
-
async_id: canonical_id
|
343
|
-
end
|
344
|
-
|
345
|
-
def input_aria
|
346
|
-
combobox_attrs.fetch(:aria, {}).merge \
|
347
|
-
controls: listbox_id,
|
348
|
-
owns: listbox_id,
|
349
|
-
haspopup: "listbox",
|
350
|
-
autocomplete: autocomplete,
|
351
|
-
activedescendant: ""
|
352
|
-
end
|
353
|
-
|
354
|
-
|
355
|
-
def handle_data
|
356
|
-
{
|
357
|
-
action: "click->hw-combobox#clearOrToggleOnHandleClick",
|
358
|
-
hw_combobox_target: "handle"
|
359
|
-
}
|
360
|
-
end
|
361
|
-
|
362
|
-
|
363
|
-
def listbox_id
|
364
|
-
"#{canonical_id}-hw-listbox"
|
365
|
-
end
|
366
|
-
|
367
|
-
def listbox_data
|
368
|
-
{ hw_combobox_target: "listbox" }
|
369
|
-
end
|
370
|
-
|
371
|
-
def listbox_aria
|
372
|
-
{ multiselectable: multiselect? }
|
373
|
-
end
|
374
|
-
|
375
|
-
|
376
|
-
def dialog_data
|
377
|
-
{
|
378
|
-
action: "keydown->hw-combobox#navigate",
|
379
|
-
hw_combobox_target: "dialog"
|
380
|
-
}
|
381
|
-
end
|
382
|
-
|
383
|
-
def dialog_input_id
|
384
|
-
"#{canonical_id}-hw-dialog-combobox"
|
385
|
-
end
|
386
|
-
|
387
|
-
def dialog_input_data
|
388
|
-
{
|
389
|
-
action: "
|
390
|
-
input->hw-combobox#filterAndSelect
|
391
|
-
keydown->hw-combobox#navigate
|
392
|
-
click@window->hw-combobox#closeOnClickOutside".squish,
|
393
|
-
hw_combobox_target: "dialogCombobox"
|
394
|
-
}
|
395
|
-
end
|
396
|
-
|
397
|
-
def dialog_input_aria
|
398
|
-
{
|
399
|
-
controls: dialog_listbox_id,
|
400
|
-
owns: dialog_listbox_id,
|
401
|
-
autocomplete: autocomplete,
|
402
|
-
activedescendant: ""
|
403
|
-
}
|
404
|
-
end
|
405
|
-
|
406
|
-
def dialog_listbox_id
|
407
|
-
"#{canonical_id}-hw-dialog-listbox"
|
408
|
-
end
|
409
|
-
|
410
|
-
def dialog_listbox_data
|
411
|
-
{ hw_combobox_target: "dialogListbox" }
|
412
|
-
end
|
413
|
-
|
414
|
-
def dialog_listbox_aria
|
415
|
-
{ multiselectable: multiselect? }
|
416
|
-
end
|
417
|
-
|
418
|
-
def dialog_focus_trap_data
|
419
|
-
{ hw_combobox_target: "dialogFocusTrap" }
|
420
|
-
end
|
421
55
|
end
|
@@ -9,7 +9,7 @@ class HotwireCombobox::Listbox::Group
|
|
9
9
|
end
|
10
10
|
|
11
11
|
def render_in(view)
|
12
|
-
view.tag.ul
|
12
|
+
view.tag.ul(**group_attrs) do
|
13
13
|
view.concat view.tag.li(name, **label_attrs)
|
14
14
|
|
15
15
|
options.map do |option|
|
@@ -26,22 +26,10 @@ class HotwireCombobox::Listbox::Group
|
|
26
26
|
end
|
27
27
|
|
28
28
|
def group_attrs
|
29
|
-
{
|
30
|
-
class: "hw-combobox__group",
|
31
|
-
role: :group,
|
32
|
-
aria: group_aria
|
33
|
-
}
|
34
|
-
end
|
35
|
-
|
36
|
-
def group_aria
|
37
|
-
{ labelledby: id }
|
29
|
+
{ class: "hw-combobox__group", role: :group, aria: { labelledby: id } }
|
38
30
|
end
|
39
31
|
|
40
32
|
def label_attrs
|
41
|
-
{
|
42
|
-
id: id,
|
43
|
-
class: "hw-combobox__group__label",
|
44
|
-
role: :presentation
|
45
|
-
}
|
33
|
+
{ id: id, class: "hw-combobox__group__label", role: :presentation }
|
46
34
|
end
|
47
35
|
end
|
@@ -6,11 +6,8 @@ class HotwireCombobox::Listbox::Item
|
|
6
6
|
end
|
7
7
|
|
8
8
|
def initialize(view, options, render_in:, include_blank:, **custom_methods)
|
9
|
-
@view =
|
10
|
-
|
11
|
-
@render_in = render_in
|
12
|
-
@include_blank = include_blank
|
13
|
-
@custom_methods = custom_methods
|
9
|
+
@view, @options, @render_in, @include_blank, @custom_methods =
|
10
|
+
view, options, render_in, include_blank, custom_methods
|
14
11
|
end
|
15
12
|
|
16
13
|
def collection
|
@@ -37,8 +34,7 @@ class HotwireCombobox::Listbox::Item
|
|
37
34
|
|
38
35
|
def create_listbox_group(options)
|
39
36
|
options.map do |group_name, group_options|
|
40
|
-
HotwireCombobox::Listbox::Group.new group_name,
|
41
|
-
options: create_listbox_options(group_options)
|
37
|
+
HotwireCombobox::Listbox::Group.new group_name, options: create_listbox_options(group_options)
|
42
38
|
end
|
43
39
|
end
|
44
40
|
|
@@ -47,7 +43,7 @@ class HotwireCombobox::Listbox::Item
|
|
47
43
|
options
|
48
44
|
else
|
49
45
|
options.map do |option|
|
50
|
-
HotwireCombobox::Listbox::Option.new
|
46
|
+
HotwireCombobox::Listbox::Option.new(**option_attrs(option))
|
51
47
|
end
|
52
48
|
end
|
53
49
|
end
|
@@ -86,9 +82,7 @@ class HotwireCombobox::Listbox::Item
|
|
86
82
|
end
|
87
83
|
|
88
84
|
def render_content(render_opts: render_in, object:, attrs:)
|
89
|
-
view.render
|
90
|
-
object: object,
|
91
|
-
locals: { combobox_display: attrs[:display], combobox_value: attrs[:value] })
|
85
|
+
view.render(**render_opts.reverse_merge(object: object, locals: { combobox_display: attrs[:display], combobox_value: attrs[:value] }))
|
92
86
|
end
|
93
87
|
|
94
88
|
def blank_option
|
@@ -100,7 +94,6 @@ class HotwireCombobox::Listbox::Item
|
|
100
94
|
case include_blank
|
101
95
|
when Hash
|
102
96
|
text = include_blank.delete(:text)
|
103
|
-
|
104
97
|
[ text, render_content(render_opts: include_blank, object: text, attrs: { display: text, value: "" }) ]
|
105
98
|
when String
|
106
99
|
[ include_blank, include_blank ]
|