hotwire_combobox 0.1.37 → 0.1.39
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 +87 -3
- data/app/assets/javascripts/controllers/hw_combobox_controller.js +1 -0
- data/app/assets/javascripts/hotwire_combobox.esm.js +1329 -0
- data/app/assets/javascripts/hotwire_combobox.umd.js +1335 -0
- data/app/assets/javascripts/hw_combobox/helpers.js +17 -0
- data/app/assets/javascripts/hw_combobox/models/combobox/dialog.js +10 -0
- data/app/assets/javascripts/hw_combobox/models/combobox/events.js +17 -0
- data/app/assets/javascripts/hw_combobox/models/combobox/filtering.js +12 -2
- data/app/assets/javascripts/hw_combobox/models/combobox/options.js +7 -0
- data/app/assets/javascripts/hw_combobox/models/combobox/selection.js +15 -1
- data/app/assets/javascripts/hw_combobox/models/combobox/toggle.js +1 -1
- data/app/assets/javascripts/hw_combobox/models/combobox/validity.js +17 -11
- data/app/assets/javascripts/hw_combobox/models/combobox.js +1 -0
- data/app/assets/stylesheets/hotwire_combobox.css +4 -0
- data/app/presenters/hotwire_combobox/component.rb +15 -7
- data/app/presenters/hotwire_combobox/listbox/option.rb +7 -3
- data/app/views/hotwire_combobox/_next_page.turbo_stream.erb +1 -1
- data/lib/hotwire_combobox/engine.rb +1 -1
- data/lib/hotwire_combobox/helper.rb +83 -18
- data/lib/hotwire_combobox/version.rb +1 -1
- metadata +11 -9
@@ -62,3 +62,20 @@ export function unselectedPortion(element) {
|
|
62
62
|
return element.value.substring(0, element.selectionStart)
|
63
63
|
}
|
64
64
|
}
|
65
|
+
|
66
|
+
export function dispatch(eventName, { target, cancelable, detail } = {}) {
|
67
|
+
const event = new CustomEvent(eventName, {
|
68
|
+
cancelable,
|
69
|
+
bubbles: true,
|
70
|
+
composed: true,
|
71
|
+
detail
|
72
|
+
})
|
73
|
+
|
74
|
+
if (target && target.isConnected) {
|
75
|
+
target.dispatchEvent(event)
|
76
|
+
} else {
|
77
|
+
document.documentElement.dispatchEvent(event)
|
78
|
+
}
|
79
|
+
|
80
|
+
return event
|
81
|
+
}
|
@@ -1,6 +1,12 @@
|
|
1
1
|
import Combobox from "hw_combobox/models/combobox/base"
|
2
2
|
|
3
3
|
Combobox.Dialog = Base => class extends Base {
|
4
|
+
rerouteListboxStreamToDialog({ detail: { newStream } }) {
|
5
|
+
if (newStream.target == this.listboxTarget.id && this._dialogIsOpen) {
|
6
|
+
newStream.setAttribute("target", this.dialogListboxTarget.id)
|
7
|
+
}
|
8
|
+
}
|
9
|
+
|
4
10
|
_connectDialog() {
|
5
11
|
if (window.visualViewport) {
|
6
12
|
window.visualViewport.addEventListener("resize", this._resizeDialog)
|
@@ -50,4 +56,8 @@ Combobox.Dialog = Base => class extends Base {
|
|
50
56
|
get _smallViewport() {
|
51
57
|
return window.matchMedia(`(max-width: ${this.smallViewportMaxWidthValue})`).matches
|
52
58
|
}
|
59
|
+
|
60
|
+
get _dialogIsOpen() {
|
61
|
+
return this.dialogTarget.open
|
62
|
+
}
|
53
63
|
}
|
@@ -0,0 +1,17 @@
|
|
1
|
+
import Combobox from "hw_combobox/models/combobox/base"
|
2
|
+
import { dispatch } from "hw_combobox/helpers"
|
3
|
+
|
4
|
+
Combobox.Events = Base => class extends Base {
|
5
|
+
_dispatchSelectionEvent({ isNew }) {
|
6
|
+
const detail = {
|
7
|
+
value: this.hiddenFieldTarget.value,
|
8
|
+
display: this._fullQuery,
|
9
|
+
query: this._typedQuery,
|
10
|
+
fieldName: this.hiddenFieldTarget.name,
|
11
|
+
isValid: this._valueIsValid,
|
12
|
+
isNew: isNew
|
13
|
+
}
|
14
|
+
|
15
|
+
dispatch("hw-combobox:selection", { target: this.element, detail })
|
16
|
+
}
|
17
|
+
}
|
@@ -21,7 +21,12 @@ Combobox.Filtering = Base => class extends Base {
|
|
21
21
|
}
|
22
22
|
|
23
23
|
async _filterAsync(event) {
|
24
|
-
const query = {
|
24
|
+
const query = {
|
25
|
+
q: this._fullQuery,
|
26
|
+
input_type: event.inputType,
|
27
|
+
for_id: this.element.dataset.asyncId
|
28
|
+
}
|
29
|
+
|
25
30
|
await get(this.asyncSrcValue, { responseKind: "turbo-stream", query })
|
26
31
|
}
|
27
32
|
|
@@ -36,11 +41,16 @@ Combobox.Filtering = Base => class extends Base {
|
|
36
41
|
this._selectNew()
|
37
42
|
} else if (isDeleteEvent(event)) {
|
38
43
|
this._deselect()
|
39
|
-
} else {
|
44
|
+
} else if (this._isOpen) {
|
40
45
|
this._select(this._visibleOptionElements[0])
|
41
46
|
}
|
42
47
|
}
|
43
48
|
|
49
|
+
_clearQuery() {
|
50
|
+
this._fullQuery = ""
|
51
|
+
this.filter({ inputType: "deleteContentBackward" })
|
52
|
+
}
|
53
|
+
|
44
54
|
get _isQueried() {
|
45
55
|
return this._fullQuery.length > 0
|
46
56
|
}
|
@@ -30,4 +30,11 @@ Combobox.Options = Base => class extends Base {
|
|
30
30
|
get _selectedOptionIndex() {
|
31
31
|
return [ ...this._visibleOptionElements ].indexOf(this._selectedOptionElement)
|
32
32
|
}
|
33
|
+
|
34
|
+
get _isUnjustifiablyBlank() {
|
35
|
+
const valueIsMissing = !this.hiddenFieldTarget.value
|
36
|
+
const noBlankOptionSelected = !this._selectedOptionElement
|
37
|
+
|
38
|
+
return valueIsMissing && noBlankOptionSelected
|
39
|
+
}
|
33
40
|
}
|
@@ -18,9 +18,9 @@ Combobox.Selection = Base => class extends Base {
|
|
18
18
|
this._resetOptions()
|
19
19
|
|
20
20
|
if (option) {
|
21
|
-
this._markValid()
|
22
21
|
this._autocompleteWith(option, { force: forceAutocomplete })
|
23
22
|
this._commitSelection(option, { selected: true })
|
23
|
+
this._markValid()
|
24
24
|
} else {
|
25
25
|
this._markInvalid()
|
26
26
|
}
|
@@ -33,6 +33,8 @@ Combobox.Selection = Base => class extends Base {
|
|
33
33
|
this.hiddenFieldTarget.value = option.dataset.value
|
34
34
|
option.scrollIntoView({ block: "nearest" })
|
35
35
|
}
|
36
|
+
|
37
|
+
this._dispatchSelectionEvent({ isNew: false })
|
36
38
|
}
|
37
39
|
|
38
40
|
_markSelected(option, { selected }) {
|
@@ -46,15 +48,22 @@ Combobox.Selection = Base => class extends Base {
|
|
46
48
|
|
47
49
|
_deselect() {
|
48
50
|
const option = this._selectedOptionElement
|
51
|
+
|
49
52
|
if (option) this._commitSelection(option, { selected: false })
|
53
|
+
|
50
54
|
this.hiddenFieldTarget.value = null
|
51
55
|
this._setActiveDescendant("")
|
56
|
+
|
57
|
+
if (!option) this._dispatchSelectionEvent({ isNew: false })
|
52
58
|
}
|
53
59
|
|
54
60
|
_selectNew() {
|
55
61
|
this._resetOptions()
|
56
62
|
this.hiddenFieldTarget.value = this._fullQuery
|
57
63
|
this.hiddenFieldTarget.name = this.nameWhenNewValue
|
64
|
+
this._markValid()
|
65
|
+
|
66
|
+
this._dispatchSelectionEvent({ isNew: true })
|
58
67
|
}
|
59
68
|
|
60
69
|
_selectIndex(index) {
|
@@ -77,6 +86,11 @@ Combobox.Selection = Base => class extends Base {
|
|
77
86
|
this._select(this._ensurableOption, { forceAutocomplete: true })
|
78
87
|
this.filter({ inputType: "hw:lockInSelection" })
|
79
88
|
}
|
89
|
+
|
90
|
+
if (this._isUnjustifiablyBlank) {
|
91
|
+
this._deselect()
|
92
|
+
this._clearQuery()
|
93
|
+
}
|
80
94
|
}
|
81
95
|
|
82
96
|
_setActiveDescendant(id) {
|
@@ -72,7 +72,7 @@ Combobox.Toggle = Base => class extends Base {
|
|
72
72
|
_collapse() {
|
73
73
|
this._actingCombobox.setAttribute("aria-expanded", false) // needs to happen before resetting acting combobox
|
74
74
|
|
75
|
-
if (this.
|
75
|
+
if (this._dialogIsOpen) {
|
76
76
|
this._closeInDialog()
|
77
77
|
} else {
|
78
78
|
this._closeInline()
|
@@ -4,29 +4,35 @@ Combobox.Validity = Base => class extends Base {
|
|
4
4
|
_markValid() {
|
5
5
|
if (this._valueIsInvalid) return
|
6
6
|
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
7
|
+
this._forAllComboboxes(combobox => {
|
8
|
+
if (this.hasInvalidClass) {
|
9
|
+
combobox.classList.remove(this.invalidClass)
|
10
|
+
}
|
11
|
+
|
12
|
+
combobox.removeAttribute("aria-invalid")
|
13
|
+
combobox.removeAttribute("aria-errormessage")
|
14
|
+
})
|
13
15
|
}
|
14
16
|
|
15
17
|
_markInvalid() {
|
16
18
|
if (this._valueIsValid) return
|
17
19
|
|
18
|
-
|
19
|
-
|
20
|
-
|
20
|
+
this._forAllComboboxes(combobox => {
|
21
|
+
if (this.hasInvalidClass) {
|
22
|
+
combobox.classList.add(this.invalidClass)
|
23
|
+
}
|
21
24
|
|
22
|
-
|
23
|
-
|
25
|
+
combobox.setAttribute("aria-invalid", true)
|
26
|
+
combobox.setAttribute("aria-errormessage", `Please select a valid option for ${combobox.name}`)
|
27
|
+
})
|
24
28
|
}
|
25
29
|
|
26
30
|
get _valueIsValid() {
|
27
31
|
return !this._valueIsInvalid
|
28
32
|
}
|
29
33
|
|
34
|
+
// +_valueIsInvalid+ only checks if `comboboxTarget` (and not `_actingCombobox`) is required
|
35
|
+
// because the `required` attribute is only forwarded to the `comboboxTarget` element
|
30
36
|
get _valueIsInvalid() {
|
31
37
|
const isRequiredAndEmpty = this.comboboxTarget.required && !this.hiddenFieldTarget.value
|
32
38
|
return isRequiredAndEmpty
|
@@ -4,6 +4,7 @@ import "hw_combobox/models/combobox/actors"
|
|
4
4
|
import "hw_combobox/models/combobox/async_loading"
|
5
5
|
import "hw_combobox/models/combobox/autocomplete"
|
6
6
|
import "hw_combobox/models/combobox/dialog"
|
7
|
+
import "hw_combobox/models/combobox/events"
|
7
8
|
import "hw_combobox/models/combobox/filtering"
|
8
9
|
import "hw_combobox/models/combobox/navigation"
|
9
10
|
import "hw_combobox/models/combobox/new_options"
|
@@ -121,6 +121,10 @@
|
|
121
121
|
text-overflow: ellipsis;
|
122
122
|
}
|
123
123
|
|
124
|
+
.hw-combobox__option--blank {
|
125
|
+
border-bottom: var(--hw-border-width--slim) solid var(--hw-border-color);
|
126
|
+
}
|
127
|
+
|
124
128
|
.hw-combobox__option:hover,
|
125
129
|
.hw-combobox__option--selected {
|
126
130
|
background-color: var(--hw-active-bg-color);
|
@@ -1,5 +1,7 @@
|
|
1
|
+
require "securerandom"
|
2
|
+
|
1
3
|
class HotwireCombobox::Component
|
2
|
-
attr_reader :
|
4
|
+
attr_reader :options, :dialog_label
|
3
5
|
|
4
6
|
def initialize \
|
5
7
|
view, name,
|
@@ -148,7 +150,7 @@ class HotwireCombobox::Component
|
|
148
150
|
end
|
149
151
|
|
150
152
|
def fieldset_data
|
151
|
-
data.
|
153
|
+
data.merge \
|
152
154
|
async_id: canonical_id,
|
153
155
|
controller: view.token_list("hw-combobox", data[:controller]),
|
154
156
|
hw_combobox_expanded_value: open,
|
@@ -160,7 +162,8 @@ class HotwireCombobox::Component
|
|
160
162
|
hw_combobox_prefilled_display_value: prefilled_display,
|
161
163
|
hw_combobox_filterable_attribute_value: "data-filterable-as",
|
162
164
|
hw_combobox_autocompletable_attribute_value: "data-autocompletable-as",
|
163
|
-
hw_combobox_selected_class: "hw-combobox__option--selected"
|
165
|
+
hw_combobox_selected_class: "hw-combobox__option--selected",
|
166
|
+
hw_combobox_invalid_class: "hw-combobox__input--invalid"
|
164
167
|
end
|
165
168
|
|
166
169
|
def prefilled_display
|
@@ -181,9 +184,13 @@ class HotwireCombobox::Component
|
|
181
184
|
form&.object&.class&.reflect_on_association(association_name).present?
|
182
185
|
end
|
183
186
|
|
187
|
+
def async_src
|
188
|
+
view.hw_uri_with_params @async_src, for_id: canonical_id, format: :turbo_stream
|
189
|
+
end
|
190
|
+
|
184
191
|
|
185
192
|
def canonical_id
|
186
|
-
id || form&.field_id(name)
|
193
|
+
@canonical_id ||= id || form&.field_id(name) || SecureRandom.uuid
|
187
194
|
end
|
188
195
|
|
189
196
|
|
@@ -219,19 +226,20 @@ class HotwireCombobox::Component
|
|
219
226
|
end
|
220
227
|
|
221
228
|
def input_data
|
222
|
-
combobox_attrs.fetch(:data, {}).
|
229
|
+
combobox_attrs.fetch(:data, {}).merge \
|
223
230
|
action: "
|
224
231
|
focus->hw-combobox#open
|
225
232
|
input->hw-combobox#filter
|
226
233
|
keydown->hw-combobox#navigate
|
227
234
|
click@window->hw-combobox#closeOnClickOutside
|
228
|
-
focusin@window->hw-combobox#closeOnFocusOutside
|
235
|
+
focusin@window->hw-combobox#closeOnFocusOutside
|
236
|
+
turbo:before-stream-render@document->hw-combobox#rerouteListboxStreamToDialog".squish,
|
229
237
|
hw_combobox_target: "combobox",
|
230
238
|
async_id: canonical_id
|
231
239
|
end
|
232
240
|
|
233
241
|
def input_aria
|
234
|
-
combobox_attrs.fetch(:aria, {}).
|
242
|
+
combobox_attrs.fetch(:aria, {}).merge \
|
235
243
|
controls: listbox_id,
|
236
244
|
owns: listbox_id,
|
237
245
|
haspopup: "listbox",
|
@@ -18,7 +18,7 @@ class HotwireCombobox::Listbox::Option
|
|
18
18
|
end
|
19
19
|
|
20
20
|
private
|
21
|
-
Data = Struct.new :id, :value, :display, :content, :filterable_as, :autocompletable_as, keyword_init: true
|
21
|
+
Data = Struct.new :id, :value, :display, :content, :blank, :filterable_as, :autocompletable_as, keyword_init: true
|
22
22
|
|
23
23
|
attr_reader :option
|
24
24
|
|
@@ -26,13 +26,13 @@ class HotwireCombobox::Listbox::Option
|
|
26
26
|
{
|
27
27
|
id: id,
|
28
28
|
role: :option,
|
29
|
-
class: "hw-combobox__option",
|
29
|
+
class: [ "hw-combobox__option", { "hw-combobox__option--blank": blank? } ],
|
30
30
|
data: data
|
31
31
|
}
|
32
32
|
end
|
33
33
|
|
34
34
|
def id
|
35
|
-
option.try(:id) || SecureRandom.uuid
|
35
|
+
@id ||= option.try(:id) || SecureRandom.uuid
|
36
36
|
end
|
37
37
|
|
38
38
|
def data
|
@@ -51,4 +51,8 @@ class HotwireCombobox::Listbox::Option
|
|
51
51
|
def filterable_as
|
52
52
|
option.try(:filterable_as) || option.try(:display)
|
53
53
|
end
|
54
|
+
|
55
|
+
def blank?
|
56
|
+
option.try(:blank).present?
|
57
|
+
end
|
54
58
|
end
|
@@ -2,5 +2,5 @@
|
|
2
2
|
|
3
3
|
<%= turbo_stream.remove hw_pagination_frame_wrapper_id(for_id) %>
|
4
4
|
<%= turbo_stream.append hw_listbox_id(for_id) do %>
|
5
|
-
<%= render "hotwire_combobox/pagination", for_id: for_id, src: hw_combobox_next_page_uri(src, next_page) %>
|
5
|
+
<%= render "hotwire_combobox/pagination", for_id: for_id, src: hw_combobox_next_page_uri(src, next_page, for_id) %>
|
6
6
|
<% end %>
|
@@ -10,7 +10,7 @@ module HotwireCombobox
|
|
10
10
|
unless HotwireCombobox.bypass_convenience_methods?
|
11
11
|
module FormBuilderExtensions
|
12
12
|
def combobox(*args, **kwargs)
|
13
|
-
@template.hw_combobox_tag
|
13
|
+
@template.hw_combobox_tag(*args, **kwargs.merge(form: self))
|
14
14
|
end
|
15
15
|
end
|
16
16
|
|
@@ -15,27 +15,32 @@ module HotwireCombobox
|
|
15
15
|
end
|
16
16
|
hw_alias :hw_combobox_style_tag
|
17
17
|
|
18
|
-
def hw_combobox_tag(name, options_or_src = [], render_in: {}, **kwargs)
|
19
|
-
options, src = hw_extract_options_and_src(options_or_src, render_in)
|
18
|
+
def hw_combobox_tag(name, options_or_src = [], render_in: {}, include_blank: nil, **kwargs)
|
19
|
+
options, src = hw_extract_options_and_src(options_or_src, render_in, include_blank)
|
20
20
|
component = HotwireCombobox::Component.new self, name, options: options, async_src: src, **kwargs
|
21
21
|
|
22
22
|
render "hotwire_combobox/combobox", component: component
|
23
23
|
end
|
24
24
|
hw_alias :hw_combobox_tag
|
25
25
|
|
26
|
-
def hw_combobox_options(options, render_in: {}, display: :to_combobox_display, **methods)
|
26
|
+
def hw_combobox_options(options, render_in: {}, include_blank: nil, display: :to_combobox_display, **methods)
|
27
27
|
if options.first.is_a? HotwireCombobox::Listbox::Option
|
28
28
|
options
|
29
29
|
else
|
30
|
-
render_in_proc =
|
31
|
-
|
30
|
+
render_in_proc = hw_render_in_proc(render_in) if render_in.present?
|
31
|
+
|
32
|
+
hw_parse_combobox_options(options, render_in: render_in_proc, **methods.merge(display: display)).tap do |options|
|
33
|
+
options.unshift(hw_blank_option(include_blank)) if include_blank.present?
|
34
|
+
end
|
32
35
|
end
|
33
36
|
end
|
34
37
|
hw_alias :hw_combobox_options
|
35
38
|
|
36
|
-
def hw_paginated_combobox_options(options, for_id
|
37
|
-
|
38
|
-
|
39
|
+
def hw_paginated_combobox_options(options, for_id: params[:for_id], src: request.path, next_page: nil, render_in: {}, include_blank: {}, **methods)
|
40
|
+
include_blank = params[:page] ? nil : include_blank
|
41
|
+
options = hw_combobox_options options, render_in: render_in, include_blank: include_blank, **methods
|
42
|
+
this_page = render "hotwire_combobox/paginated_options", for_id: for_id, options: options
|
43
|
+
next_page = render "hotwire_combobox/next_page", for_id: for_id, src: src, next_page: next_page
|
39
44
|
|
40
45
|
safe_join [ this_page, next_page ]
|
41
46
|
end
|
@@ -44,7 +49,7 @@ module HotwireCombobox
|
|
44
49
|
alias_method :hw_async_combobox_options, :hw_paginated_combobox_options
|
45
50
|
hw_alias :hw_async_combobox_options
|
46
51
|
|
47
|
-
|
52
|
+
# private library use only
|
48
53
|
def hw_listbox_id(id)
|
49
54
|
"#{id}-hw-listbox"
|
50
55
|
end
|
@@ -57,9 +62,13 @@ module HotwireCombobox
|
|
57
62
|
"#{id}__hw_combobox_pagination"
|
58
63
|
end
|
59
64
|
|
60
|
-
def hw_combobox_next_page_uri(uri, next_page)
|
65
|
+
def hw_combobox_next_page_uri(uri, next_page, for_id)
|
61
66
|
if next_page
|
62
|
-
hw_uri_with_params uri,
|
67
|
+
hw_uri_with_params uri,
|
68
|
+
page: next_page,
|
69
|
+
q: params[:q],
|
70
|
+
for_id: for_id,
|
71
|
+
format: :turbo_stream
|
63
72
|
end
|
64
73
|
end
|
65
74
|
|
@@ -67,12 +76,19 @@ module HotwireCombobox
|
|
67
76
|
params[:page] ? :append : :update
|
68
77
|
end
|
69
78
|
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
79
|
+
def hw_blank_option(include_blank)
|
80
|
+
display, content = hw_extract_blank_display_and_content include_blank
|
81
|
+
|
82
|
+
HotwireCombobox::Listbox::Option.new display: display, content: content, value: "", blank: true
|
83
|
+
end
|
84
|
+
|
85
|
+
def hw_extract_blank_display_and_content(include_blank)
|
86
|
+
if include_blank.is_a? Hash
|
87
|
+
text = include_blank.delete(:text)
|
88
|
+
|
89
|
+
[ text, hw_render_in_proc(include_blank).(text) ]
|
74
90
|
else
|
75
|
-
[
|
91
|
+
[ include_blank, include_blank ]
|
76
92
|
end
|
77
93
|
end
|
78
94
|
|
@@ -85,9 +101,23 @@ module HotwireCombobox
|
|
85
101
|
url_or_path
|
86
102
|
end
|
87
103
|
|
104
|
+
private
|
105
|
+
def hw_render_in_proc(render_in)
|
106
|
+
->(object) { render(**render_in.reverse_merge(object: object)) }
|
107
|
+
end
|
108
|
+
|
109
|
+
def hw_extract_options_and_src(options_or_src, render_in, include_blank)
|
110
|
+
if options_or_src.is_a? String
|
111
|
+
[ [], options_or_src ]
|
112
|
+
else
|
113
|
+
[ hw_combobox_options(options_or_src, render_in: render_in, include_blank: include_blank), nil ]
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
88
117
|
def hw_parse_combobox_options(options, render_in: nil, **methods)
|
89
118
|
options.map do |option|
|
90
|
-
HotwireCombobox::Listbox::Option.new
|
119
|
+
HotwireCombobox::Listbox::Option.new \
|
120
|
+
**hw_option_attrs_for(option, render_in: render_in, **methods)
|
91
121
|
end
|
92
122
|
end
|
93
123
|
|
@@ -122,8 +152,43 @@ module HotwireCombobox
|
|
122
152
|
if method_or_proc.is_a? Proc
|
123
153
|
method_or_proc.call object
|
124
154
|
else
|
125
|
-
object
|
155
|
+
hw_call_method object, method_or_proc
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
def hw_call_method(object, method)
|
160
|
+
if object.respond_to? method
|
161
|
+
object.public_send method
|
162
|
+
else
|
163
|
+
hw_raise_no_public_method_error object, method
|
126
164
|
end
|
127
165
|
end
|
166
|
+
|
167
|
+
def hw_raise_no_public_method_error(object, method)
|
168
|
+
if object.respond_to? method, true
|
169
|
+
header = "`#{object.class}` responds to `##{method}` but the method is not public."
|
170
|
+
else
|
171
|
+
header = "`#{object.class}` does not respond to `##{method}`."
|
172
|
+
end
|
173
|
+
|
174
|
+
if method.to_s == "to_combobox_display"
|
175
|
+
header << "\n\nThis method is used to determine how this option should appear in the combobox options list."
|
176
|
+
end
|
177
|
+
|
178
|
+
raise NoMethodError, <<~MSG
|
179
|
+
[ACTION NEEDED] – Message from HotwireCombobox:
|
180
|
+
|
181
|
+
#{header}
|
182
|
+
|
183
|
+
Please add this as a public method and return a string.
|
184
|
+
|
185
|
+
Example:
|
186
|
+
class #{object.class} < ApplicationRecord
|
187
|
+
def #{method}
|
188
|
+
name # or `title`, `to_s`, etc.
|
189
|
+
end
|
190
|
+
end
|
191
|
+
MSG
|
192
|
+
end
|
128
193
|
end
|
129
194
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: hotwire_combobox
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.39
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jose Farias
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-03-
|
11
|
+
date: 2024-03-07 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|
@@ -52,8 +52,7 @@ dependencies:
|
|
52
52
|
- - ">="
|
53
53
|
- !ruby/object:Gem::Version
|
54
54
|
version: '1.2'
|
55
|
-
description: An autocomplete
|
56
|
-
Hotwire.
|
55
|
+
description: An accessible autocomplete for Ruby on Rails apps using Hotwire.
|
57
56
|
email:
|
58
57
|
- jose@farias.mx
|
59
58
|
executables: []
|
@@ -65,6 +64,8 @@ files:
|
|
65
64
|
- Rakefile
|
66
65
|
- app/assets/config/hw_combobox_manifest.js
|
67
66
|
- app/assets/javascripts/controllers/hw_combobox_controller.js
|
67
|
+
- app/assets/javascripts/hotwire_combobox.esm.js
|
68
|
+
- app/assets/javascripts/hotwire_combobox.umd.js
|
68
69
|
- app/assets/javascripts/hw_combobox/helpers.js
|
69
70
|
- app/assets/javascripts/hw_combobox/models/combobox.js
|
70
71
|
- app/assets/javascripts/hw_combobox/models/combobox/actors.js
|
@@ -72,6 +73,7 @@ files:
|
|
72
73
|
- app/assets/javascripts/hw_combobox/models/combobox/autocomplete.js
|
73
74
|
- app/assets/javascripts/hw_combobox/models/combobox/base.js
|
74
75
|
- app/assets/javascripts/hw_combobox/models/combobox/dialog.js
|
76
|
+
- app/assets/javascripts/hw_combobox/models/combobox/events.js
|
75
77
|
- app/assets/javascripts/hw_combobox/models/combobox/filtering.js
|
76
78
|
- app/assets/javascripts/hw_combobox/models/combobox/navigation.js
|
77
79
|
- app/assets/javascripts/hw_combobox/models/combobox/new_options.js
|
@@ -97,13 +99,13 @@ files:
|
|
97
99
|
- lib/hotwire_combobox/engine.rb
|
98
100
|
- lib/hotwire_combobox/helper.rb
|
99
101
|
- lib/hotwire_combobox/version.rb
|
100
|
-
homepage: https://
|
102
|
+
homepage: https://hotwirecombobox.com/
|
101
103
|
licenses:
|
102
104
|
- MIT
|
103
105
|
metadata:
|
104
|
-
homepage_uri: https://
|
106
|
+
homepage_uri: https://hotwirecombobox.com/
|
105
107
|
source_code_uri: https://github.com/josefarias/hotwire_combobox
|
106
|
-
changelog_uri: https://github.com/josefarias/hotwire_combobox
|
108
|
+
changelog_uri: https://github.com/josefarias/hotwire_combobox/releases
|
107
109
|
post_install_message:
|
108
110
|
rdoc_options: []
|
109
111
|
require_paths:
|
@@ -112,7 +114,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
112
114
|
requirements:
|
113
115
|
- - ">="
|
114
116
|
- !ruby/object:Gem::Version
|
115
|
-
version:
|
117
|
+
version: 2.7.0
|
116
118
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
117
119
|
requirements:
|
118
120
|
- - ">="
|
@@ -122,5 +124,5 @@ requirements: []
|
|
122
124
|
rubygems_version: 3.5.6
|
123
125
|
signing_key:
|
124
126
|
specification_version: 4
|
125
|
-
summary: Autocomplete for Rails apps
|
127
|
+
summary: Accessible Autocomplete for Rails apps
|
126
128
|
test_files: []
|