hotwire_combobox 0.1.40 → 0.1.41
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/app/assets/javascripts/controllers/hw_combobox_controller.js +2 -1
- data/app/assets/javascripts/hotwire_combobox.esm.js +18 -4
- data/app/assets/javascripts/hotwire_combobox.umd.js +18 -4
- data/app/assets/javascripts/hw_combobox/models/combobox/filtering.js +4 -0
- data/app/assets/javascripts/hw_combobox/models/combobox/selection.js +1 -1
- data/app/assets/javascripts/hw_combobox/models/combobox/toggle.js +11 -2
- data/app/assets/stylesheets/hotwire_combobox.css +28 -10
- data/app/presenters/hotwire_combobox/component/customizable.rb +52 -0
- data/app/presenters/hotwire_combobox/component.rb +53 -16
- data/app/views/hotwire_combobox/_component.html.erb +11 -0
- data/lib/hotwire_combobox/helper.rb +2 -3
- data/lib/hotwire_combobox/version.rb +1 -1
- metadata +4 -3
- data/app/views/hotwire_combobox/_combobox.html.erb +0 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1fc70c854bf6cdceda08dd727b854a63b7deb0703d9a8dafcab0bd74ca3e73d0
|
4
|
+
data.tar.gz: ec4ad558a47dcdb94e68d360671a89f3d9147a7a2a172a741f06f59dfbfd096a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1cb4ab65ec2312a055400870a1c2e91a11e598c389daf96f1f3f079627fac208fbaad73bad1a957db1a5eff33eaaba7c7d2c7eccc7aa389bdcdf9c51e26832b6
|
7
|
+
data.tar.gz: fbb2db40e5b529084c5c65ab644c3af9c0f5721741f2692dcc85e76362f82ebdddf94f979d02b80328b8b39828468e591d6d61faeec3ecff7f262c327bb78a6b
|
@@ -534,6 +534,8 @@ Combobox.Filtering = Base => class extends Base {
|
|
534
534
|
} else {
|
535
535
|
this._filterSync(event);
|
536
536
|
}
|
537
|
+
|
538
|
+
this._actingCombobox.toggleAttribute("data-queried", this._isQueried);
|
537
539
|
}
|
538
540
|
|
539
541
|
_initializeFiltering() {
|
@@ -565,6 +567,8 @@ Combobox.Filtering = Base => class extends Base {
|
|
565
567
|
this._selectNew();
|
566
568
|
} else if (isDeleteEvent(event)) {
|
567
569
|
this._deselect();
|
570
|
+
} else if (event.inputType === "hw:lockInSelection") {
|
571
|
+
this._select(this._ensurableOption);
|
568
572
|
} else if (this._isOpen) {
|
569
573
|
this._select(this._visibleOptionElements[0]);
|
570
574
|
}
|
@@ -706,7 +710,7 @@ Combobox.Options = Base => class extends Base {
|
|
706
710
|
Combobox.Selection = Base => class extends Base {
|
707
711
|
selectOptionOnClick(event) {
|
708
712
|
this.filter(event);
|
709
|
-
this._select(event.currentTarget);
|
713
|
+
this._select(event.currentTarget, { forceAutocomplete: true });
|
710
714
|
this.close();
|
711
715
|
}
|
712
716
|
|
@@ -1110,7 +1114,7 @@ Combobox.Toggle = Base => class extends Base {
|
|
1110
1114
|
const target = event.target;
|
1111
1115
|
|
1112
1116
|
if (!this._isOpen) return
|
1113
|
-
if (this.
|
1117
|
+
if (this.mainWrapperTarget.contains(target) && !this._isDialogDismisser(target)) return
|
1114
1118
|
if (this._withinElementBounds(event)) return
|
1115
1119
|
|
1116
1120
|
this.close();
|
@@ -1123,12 +1127,21 @@ Combobox.Toggle = Base => class extends Base {
|
|
1123
1127
|
this.close();
|
1124
1128
|
}
|
1125
1129
|
|
1130
|
+
clearOrToggleOnHandleClick() {
|
1131
|
+
if (this._isQueried) {
|
1132
|
+
this._clearQuery();
|
1133
|
+
this._actingCombobox.focus();
|
1134
|
+
} else {
|
1135
|
+
this.toggle();
|
1136
|
+
}
|
1137
|
+
}
|
1138
|
+
|
1126
1139
|
// Some browser extensions like 1Password overlay elements on top of the combobox.
|
1127
1140
|
// Hovering over these elements emits a click event for some reason.
|
1128
1141
|
// These events don't contain any telling information, so we use `_withinElementBounds`
|
1129
1142
|
// as an alternative to check whether the click is legitimate.
|
1130
1143
|
_withinElementBounds(event) {
|
1131
|
-
const { left, right, top, bottom } = this.
|
1144
|
+
const { left, right, top, bottom } = this.mainWrapperTarget.getBoundingClientRect();
|
1132
1145
|
const { clientX, clientY } = event;
|
1133
1146
|
|
1134
1147
|
return clientX >= left && clientX <= right && clientY >= top && clientY <= bottom
|
@@ -1275,7 +1288,8 @@ class HwComboboxController extends Concerns(...concerns) {
|
|
1275
1288
|
"endOfOptionsStream",
|
1276
1289
|
"handle",
|
1277
1290
|
"hiddenField",
|
1278
|
-
"listbox"
|
1291
|
+
"listbox",
|
1292
|
+
"mainWrapper"
|
1279
1293
|
]
|
1280
1294
|
|
1281
1295
|
static values = {
|
@@ -538,6 +538,8 @@
|
|
538
538
|
} else {
|
539
539
|
this._filterSync(event);
|
540
540
|
}
|
541
|
+
|
542
|
+
this._actingCombobox.toggleAttribute("data-queried", this._isQueried);
|
541
543
|
}
|
542
544
|
|
543
545
|
_initializeFiltering() {
|
@@ -569,6 +571,8 @@
|
|
569
571
|
this._selectNew();
|
570
572
|
} else if (isDeleteEvent(event)) {
|
571
573
|
this._deselect();
|
574
|
+
} else if (event.inputType === "hw:lockInSelection") {
|
575
|
+
this._select(this._ensurableOption);
|
572
576
|
} else if (this._isOpen) {
|
573
577
|
this._select(this._visibleOptionElements[0]);
|
574
578
|
}
|
@@ -710,7 +714,7 @@
|
|
710
714
|
Combobox.Selection = Base => class extends Base {
|
711
715
|
selectOptionOnClick(event) {
|
712
716
|
this.filter(event);
|
713
|
-
this._select(event.currentTarget);
|
717
|
+
this._select(event.currentTarget, { forceAutocomplete: true });
|
714
718
|
this.close();
|
715
719
|
}
|
716
720
|
|
@@ -1114,7 +1118,7 @@
|
|
1114
1118
|
const target = event.target;
|
1115
1119
|
|
1116
1120
|
if (!this._isOpen) return
|
1117
|
-
if (this.
|
1121
|
+
if (this.mainWrapperTarget.contains(target) && !this._isDialogDismisser(target)) return
|
1118
1122
|
if (this._withinElementBounds(event)) return
|
1119
1123
|
|
1120
1124
|
this.close();
|
@@ -1127,12 +1131,21 @@
|
|
1127
1131
|
this.close();
|
1128
1132
|
}
|
1129
1133
|
|
1134
|
+
clearOrToggleOnHandleClick() {
|
1135
|
+
if (this._isQueried) {
|
1136
|
+
this._clearQuery();
|
1137
|
+
this._actingCombobox.focus();
|
1138
|
+
} else {
|
1139
|
+
this.toggle();
|
1140
|
+
}
|
1141
|
+
}
|
1142
|
+
|
1130
1143
|
// Some browser extensions like 1Password overlay elements on top of the combobox.
|
1131
1144
|
// Hovering over these elements emits a click event for some reason.
|
1132
1145
|
// These events don't contain any telling information, so we use `_withinElementBounds`
|
1133
1146
|
// as an alternative to check whether the click is legitimate.
|
1134
1147
|
_withinElementBounds(event) {
|
1135
|
-
const { left, right, top, bottom } = this.
|
1148
|
+
const { left, right, top, bottom } = this.mainWrapperTarget.getBoundingClientRect();
|
1136
1149
|
const { clientX, clientY } = event;
|
1137
1150
|
|
1138
1151
|
return clientX >= left && clientX <= right && clientY >= top && clientY <= bottom
|
@@ -1279,7 +1292,8 @@
|
|
1279
1292
|
"endOfOptionsStream",
|
1280
1293
|
"handle",
|
1281
1294
|
"hiddenField",
|
1282
|
-
"listbox"
|
1295
|
+
"listbox",
|
1296
|
+
"mainWrapper"
|
1283
1297
|
]
|
1284
1298
|
|
1285
1299
|
static values = {
|
@@ -10,6 +10,8 @@ Combobox.Filtering = Base => class extends Base {
|
|
10
10
|
} else {
|
11
11
|
this._filterSync(event)
|
12
12
|
}
|
13
|
+
|
14
|
+
this._actingCombobox.toggleAttribute("data-queried", this._isQueried)
|
13
15
|
}
|
14
16
|
|
15
17
|
_initializeFiltering() {
|
@@ -41,6 +43,8 @@ Combobox.Filtering = Base => class extends Base {
|
|
41
43
|
this._selectNew()
|
42
44
|
} else if (isDeleteEvent(event)) {
|
43
45
|
this._deselect()
|
46
|
+
} else if (event.inputType === "hw:lockInSelection") {
|
47
|
+
this._select(this._ensurableOption)
|
44
48
|
} else if (this._isOpen) {
|
45
49
|
this._select(this._visibleOptionElements[0])
|
46
50
|
}
|
@@ -4,7 +4,7 @@ import { wrapAroundAccess } from "hw_combobox/helpers"
|
|
4
4
|
Combobox.Selection = Base => class extends Base {
|
5
5
|
selectOptionOnClick(event) {
|
6
6
|
this.filter(event)
|
7
|
-
this._select(event.currentTarget)
|
7
|
+
this._select(event.currentTarget, { forceAutocomplete: true })
|
8
8
|
this.close()
|
9
9
|
}
|
10
10
|
|
@@ -25,7 +25,7 @@ Combobox.Toggle = Base => class extends Base {
|
|
25
25
|
const target = event.target
|
26
26
|
|
27
27
|
if (!this._isOpen) return
|
28
|
-
if (this.
|
28
|
+
if (this.mainWrapperTarget.contains(target) && !this._isDialogDismisser(target)) return
|
29
29
|
if (this._withinElementBounds(event)) return
|
30
30
|
|
31
31
|
this.close()
|
@@ -38,12 +38,21 @@ Combobox.Toggle = Base => class extends Base {
|
|
38
38
|
this.close()
|
39
39
|
}
|
40
40
|
|
41
|
+
clearOrToggleOnHandleClick() {
|
42
|
+
if (this._isQueried) {
|
43
|
+
this._clearQuery()
|
44
|
+
this._actingCombobox.focus()
|
45
|
+
} else {
|
46
|
+
this.toggle()
|
47
|
+
}
|
48
|
+
}
|
49
|
+
|
41
50
|
// Some browser extensions like 1Password overlay elements on top of the combobox.
|
42
51
|
// Hovering over these elements emits a click event for some reason.
|
43
52
|
// These events don't contain any telling information, so we use `_withinElementBounds`
|
44
53
|
// as an alternative to check whether the click is legitimate.
|
45
54
|
_withinElementBounds(event) {
|
46
|
-
const { left, right, top, bottom } = this.
|
55
|
+
const { left, right, top, bottom } = this.mainWrapperTarget.getBoundingClientRect()
|
47
56
|
const { clientX, clientY } = event
|
48
57
|
|
49
58
|
return clientX >= left && clientX <= right && clientY >= top && clientY <= bottom
|
@@ -1,7 +1,11 @@
|
|
1
1
|
:root {
|
2
2
|
--hw-active-bg-color: #F3F4F6;
|
3
|
-
|
4
3
|
--hw-border-color: #D1D5DB;
|
4
|
+
--hw-invalid-color: #EF4444;
|
5
|
+
--hw-dialog-label-color: #1D1D1D;
|
6
|
+
--hw-focus-color: #2563EB;
|
7
|
+
--hw-option-bg-color: #FFFFFF;
|
8
|
+
|
5
9
|
--hw-border-radius: 0.375rem;
|
6
10
|
--hw-border-width--slim: 1px;
|
7
11
|
--hw-border-width--thick: 2px;
|
@@ -9,7 +13,6 @@
|
|
9
13
|
--hw-dialog-font-size: 1.25rem;
|
10
14
|
--hw-dialog-input-height: 2.5rem;
|
11
15
|
--hw-dialog-label-alignment: center;
|
12
|
-
--hw-dialog-label-color: #1D1D1D;
|
13
16
|
--hw-dialog-label-padding: 0.5rem 0 0.375rem;
|
14
17
|
--hw-dialog-label-size: 1.05rem;
|
15
18
|
--hw-dialog-listbox-margin: 1.25rem 0 0;
|
@@ -19,21 +22,19 @@
|
|
19
22
|
--hw-font-size: 1rem;
|
20
23
|
|
21
24
|
--hw-handle-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3E%3Cpath stroke='%236b7280' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='m6 8 4 4 4-4'/%3E%3C/svg%3E");
|
25
|
+
--hw-handle-image--queried: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 24 24'%3E%3Cpath stroke='%236b7280' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M6 18 18 6M6 6l12 12'/%3E%3C/svg%3E");
|
22
26
|
--hw-handle-offset-right: 0.375rem;
|
23
27
|
--hw-handle-width: 1.5em;
|
28
|
+
--hw-handle-width--queried: 1em;
|
24
29
|
|
25
30
|
--hw-combobox-width: 10rem;
|
26
31
|
|
27
|
-
--hw-focus-color: #2563EB;
|
28
|
-
|
29
32
|
--hw-line-height: 1.5rem;
|
30
33
|
|
31
34
|
--hw-listbox-height: calc(var(--hw-line-height) * 10);
|
32
35
|
--hw-listbox-offset-top: calc(var(--hw-line-height) * 1.625);
|
33
36
|
--hw-listbox-z-index: 10;
|
34
37
|
|
35
|
-
--hw-option-bg-color: #FFFFFF;
|
36
|
-
|
37
38
|
--hw-padding--slim: 0.375rem;
|
38
39
|
--hw-padding--thick: 0.75rem;
|
39
40
|
|
@@ -42,8 +43,10 @@
|
|
42
43
|
|
43
44
|
.hw-combobox {
|
44
45
|
border-width: 0;
|
45
|
-
display: inline-
|
46
|
+
display: inline-flex;
|
47
|
+
flex-direction: column;
|
46
48
|
font-size: var(--hw-font-size);
|
49
|
+
gap: var(--hw-padding--slim);
|
47
50
|
margin: 0;
|
48
51
|
padding: 0;
|
49
52
|
position: relative;
|
@@ -53,6 +56,11 @@
|
|
53
56
|
}
|
54
57
|
}
|
55
58
|
|
59
|
+
.hw-combobox__main__wrapper {
|
60
|
+
position: relative;
|
61
|
+
width: min-content;
|
62
|
+
}
|
63
|
+
|
56
64
|
.hw-combobox__input {
|
57
65
|
border: var(--hw-border-width--slim) solid var(--hw-border-color);
|
58
66
|
border-radius: var(--hw-border-radius);
|
@@ -64,6 +72,15 @@
|
|
64
72
|
text-overflow: ellipsis;
|
65
73
|
}
|
66
74
|
|
75
|
+
.hw-combobox__input:focus-visible {
|
76
|
+
box-shadow: 0 0 0 var(--hw-border-width--thick) var(--hw-focus-color);
|
77
|
+
outline: none;
|
78
|
+
}
|
79
|
+
|
80
|
+
.hw-combobox__input--invalid {
|
81
|
+
border: var(--hw-border-width--slim) solid var(--hw-invalid-color);
|
82
|
+
}
|
83
|
+
|
67
84
|
.hw-combobox__handle {
|
68
85
|
height: 100%;
|
69
86
|
position: absolute;
|
@@ -85,9 +102,10 @@
|
|
85
102
|
top: 0;
|
86
103
|
}
|
87
104
|
|
88
|
-
.hw-combobox__input
|
89
|
-
|
90
|
-
|
105
|
+
.hw-combobox__input[data-queried] + .hw-combobox__handle::before {
|
106
|
+
background-image: var(--hw-handle-image--queried);
|
107
|
+
background-size: var(--hw-handle-width--queried);
|
108
|
+
cursor: pointer;
|
91
109
|
}
|
92
110
|
|
93
111
|
.hw-combobox__listbox {
|
@@ -0,0 +1,52 @@
|
|
1
|
+
module HotwireCombobox::Component::Customizable
|
2
|
+
CUSTOMIZABLE_ELEMENTS = %i[
|
3
|
+
fieldset
|
4
|
+
label
|
5
|
+
hidden_field
|
6
|
+
main_wrapper
|
7
|
+
input
|
8
|
+
handle
|
9
|
+
listbox
|
10
|
+
dialog
|
11
|
+
dialog_wrapper
|
12
|
+
dialog_label
|
13
|
+
dialog_input
|
14
|
+
dialog_listbox
|
15
|
+
].freeze
|
16
|
+
|
17
|
+
PROTECTED_ATTRS = %i[
|
18
|
+
id
|
19
|
+
name
|
20
|
+
value
|
21
|
+
open
|
22
|
+
role
|
23
|
+
hidden
|
24
|
+
for
|
25
|
+
].freeze
|
26
|
+
|
27
|
+
CUSTOMIZABLE_ELEMENTS.each do |element|
|
28
|
+
define_method "customize_#{element}" do |**attrs|
|
29
|
+
customize element, **attrs
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
def custom_attrs
|
35
|
+
@custom_attrs ||= Hash.new { |h, k| h[k] = {} }
|
36
|
+
end
|
37
|
+
|
38
|
+
def customize(element, **attrs)
|
39
|
+
element = element.to_sym.presence_in(CUSTOMIZABLE_ELEMENTS)
|
40
|
+
sanitized_attrs = attrs.deep_symbolize_keys.except(*PROTECTED_ATTRS)
|
41
|
+
|
42
|
+
custom_attrs.store element, sanitized_attrs
|
43
|
+
end
|
44
|
+
|
45
|
+
def apply_customizations_to(element, base: {})
|
46
|
+
custom = custom_attrs[element]
|
47
|
+
coalesce = ->(k, v) { v.is_a?(String) ? view.token_list(v, custom.delete(k)) : v }
|
48
|
+
default = base.map { |k, v| [ k, coalesce.(k, v) ] }.to_h
|
49
|
+
|
50
|
+
custom.deep_merge default
|
51
|
+
end
|
52
|
+
end
|
@@ -1,7 +1,9 @@
|
|
1
1
|
require "securerandom"
|
2
2
|
|
3
3
|
class HotwireCombobox::Component
|
4
|
-
|
4
|
+
include Customizable
|
5
|
+
|
6
|
+
attr_reader :options, :label
|
5
7
|
|
6
8
|
def initialize \
|
7
9
|
view, name,
|
@@ -13,31 +15,47 @@ class HotwireCombobox::Component
|
|
13
15
|
form: nil,
|
14
16
|
id: nil,
|
15
17
|
input: {},
|
18
|
+
label: nil,
|
16
19
|
mobile_at: "640px",
|
17
20
|
name_when_new: nil,
|
18
21
|
open: false,
|
19
22
|
options: [],
|
20
23
|
value: nil,
|
21
24
|
**rest
|
22
|
-
@view, @autocomplete, @id, @name, @value, @form, @async_src,
|
25
|
+
@view, @autocomplete, @id, @name, @value, @form, @async_src, @label,
|
23
26
|
@name_when_new, @open, @data, @mobile_at, @options, @dialog_label =
|
24
|
-
view, autocomplete, id, name.to_s, value, form, async_src,
|
27
|
+
view, autocomplete, id, name.to_s, value, form, async_src, label,
|
25
28
|
name_when_new, open, data, mobile_at, options, dialog_label
|
26
29
|
|
27
|
-
@combobox_attrs = input.reverse_merge(rest).
|
30
|
+
@combobox_attrs = input.reverse_merge(rest).deep_symbolize_keys
|
28
31
|
@association_name = association_name || infer_association_name
|
29
32
|
end
|
30
33
|
|
34
|
+
def render_in(view_context, &block)
|
35
|
+
block.call(self) if block_given?
|
36
|
+
view_context.render partial: "hotwire_combobox/component", locals: { component: self }
|
37
|
+
end
|
38
|
+
|
39
|
+
|
31
40
|
def fieldset_attrs
|
32
|
-
{
|
41
|
+
apply_customizations_to :fieldset, base: {
|
33
42
|
class: "hw-combobox",
|
34
43
|
data: fieldset_data
|
35
44
|
}
|
36
45
|
end
|
37
46
|
|
38
47
|
|
48
|
+
def label_attrs
|
49
|
+
apply_customizations_to :label, base: {
|
50
|
+
class: "hw-combobox__label",
|
51
|
+
for: input_id,
|
52
|
+
hidden: label.blank?
|
53
|
+
}
|
54
|
+
end
|
55
|
+
|
56
|
+
|
39
57
|
def hidden_field_attrs
|
40
|
-
{
|
58
|
+
apply_customizations_to :hidden_field, base: {
|
41
59
|
id: hidden_field_id,
|
42
60
|
name: hidden_field_name,
|
43
61
|
data: hidden_field_data,
|
@@ -46,10 +64,18 @@ class HotwireCombobox::Component
|
|
46
64
|
end
|
47
65
|
|
48
66
|
|
67
|
+
def main_wrapper_attrs
|
68
|
+
apply_customizations_to :main_wrapper, base: {
|
69
|
+
class: "hw-combobox__main__wrapper",
|
70
|
+
data: main_wrapper_data
|
71
|
+
}
|
72
|
+
end
|
73
|
+
|
74
|
+
|
49
75
|
def input_attrs
|
50
76
|
nested_attrs = %i[ data aria ]
|
51
77
|
|
52
|
-
{
|
78
|
+
base = {
|
53
79
|
id: input_id,
|
54
80
|
role: :combobox,
|
55
81
|
class: "hw-combobox__input",
|
@@ -57,12 +83,14 @@ class HotwireCombobox::Component
|
|
57
83
|
data: input_data,
|
58
84
|
aria: input_aria,
|
59
85
|
autocomplete: :off
|
60
|
-
}.
|
86
|
+
}.merge combobox_attrs.except(*nested_attrs)
|
87
|
+
|
88
|
+
apply_customizations_to :input, base: base
|
61
89
|
end
|
62
90
|
|
63
91
|
|
64
92
|
def handle_attrs
|
65
|
-
{
|
93
|
+
apply_customizations_to :handle, base: {
|
66
94
|
class: "hw-combobox__handle",
|
67
95
|
data: handle_data
|
68
96
|
}
|
@@ -70,7 +98,7 @@ class HotwireCombobox::Component
|
|
70
98
|
|
71
99
|
|
72
100
|
def listbox_attrs
|
73
|
-
{
|
101
|
+
apply_customizations_to :listbox, base: {
|
74
102
|
id: listbox_id,
|
75
103
|
role: :listbox,
|
76
104
|
class: "hw-combobox__listbox",
|
@@ -81,28 +109,32 @@ class HotwireCombobox::Component
|
|
81
109
|
|
82
110
|
|
83
111
|
def dialog_wrapper_attrs
|
84
|
-
{
|
112
|
+
apply_customizations_to :dialog_wrapper, base: {
|
85
113
|
class: "hw-combobox__dialog__wrapper"
|
86
114
|
}
|
87
115
|
end
|
88
116
|
|
89
117
|
def dialog_attrs
|
90
|
-
{
|
118
|
+
apply_customizations_to :dialog, base: {
|
91
119
|
class: "hw-combobox__dialog",
|
92
120
|
role: :dialog,
|
93
121
|
data: dialog_data
|
94
122
|
}
|
95
123
|
end
|
96
124
|
|
125
|
+
def dialog_label
|
126
|
+
@dialog_label || label
|
127
|
+
end
|
128
|
+
|
97
129
|
def dialog_label_attrs
|
98
|
-
{
|
130
|
+
apply_customizations_to :dialog_label, base: {
|
99
131
|
class: "hw-combobox__dialog__label",
|
100
132
|
for: dialog_input_id
|
101
133
|
}
|
102
134
|
end
|
103
135
|
|
104
136
|
def dialog_input_attrs
|
105
|
-
{
|
137
|
+
apply_customizations_to :dialog_input, base: {
|
106
138
|
id: dialog_input_id,
|
107
139
|
role: :combobox,
|
108
140
|
class: "hw-combobox__dialog__input",
|
@@ -114,7 +146,7 @@ class HotwireCombobox::Component
|
|
114
146
|
end
|
115
147
|
|
116
148
|
def dialog_listbox_attrs
|
117
|
-
{
|
149
|
+
apply_customizations_to :dialog_listbox, base: {
|
118
150
|
id: dialog_listbox_id,
|
119
151
|
class: "hw-combobox__dialog__listbox",
|
120
152
|
role: :listbox,
|
@@ -194,6 +226,11 @@ class HotwireCombobox::Component
|
|
194
226
|
end
|
195
227
|
|
196
228
|
|
229
|
+
def main_wrapper_data
|
230
|
+
{ hw_combobox_target: "mainWrapper" }
|
231
|
+
end
|
232
|
+
|
233
|
+
|
197
234
|
def hidden_field_id
|
198
235
|
"#{canonical_id}-hw-hidden-field"
|
199
236
|
end
|
@@ -250,7 +287,7 @@ class HotwireCombobox::Component
|
|
250
287
|
|
251
288
|
def handle_data
|
252
289
|
{
|
253
|
-
action: "click->hw-combobox#
|
290
|
+
action: "click->hw-combobox#clearOrToggleOnHandleClick",
|
254
291
|
hw_combobox_target: "handle"
|
255
292
|
}
|
256
293
|
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
<%= tag.fieldset **component.fieldset_attrs do %>
|
2
|
+
<%= tag.label component.label, **component.label_attrs %>
|
3
|
+
|
4
|
+
<%= render "hotwire_combobox/combobox/hidden_field", component: component %>
|
5
|
+
|
6
|
+
<%= tag.div **component.main_wrapper_attrs do %>
|
7
|
+
<%= render "hotwire_combobox/combobox/input", component: component %>
|
8
|
+
<%= render "hotwire_combobox/combobox/paginated_listbox", component: component %>
|
9
|
+
<%= render "hotwire_combobox/combobox/dialog", component: component %>
|
10
|
+
<% end %>
|
11
|
+
<% end %>
|
@@ -15,11 +15,10 @@ 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: {}, include_blank: nil, **kwargs)
|
18
|
+
def hw_combobox_tag(name, options_or_src = [], render_in: {}, include_blank: nil, **kwargs, &block)
|
19
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
|
-
|
22
|
-
render "hotwire_combobox/combobox", component: component
|
21
|
+
render component, &block
|
23
22
|
end
|
24
23
|
hw_alias :hw_combobox_tag
|
25
24
|
|
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.41
|
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-12 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|
@@ -85,8 +85,9 @@ files:
|
|
85
85
|
- app/assets/javascripts/hw_combobox/vendor/requestjs.js
|
86
86
|
- app/assets/stylesheets/hotwire_combobox.css
|
87
87
|
- app/presenters/hotwire_combobox/component.rb
|
88
|
+
- app/presenters/hotwire_combobox/component/customizable.rb
|
88
89
|
- app/presenters/hotwire_combobox/listbox/option.rb
|
89
|
-
- app/views/hotwire_combobox/
|
90
|
+
- app/views/hotwire_combobox/_component.html.erb
|
90
91
|
- app/views/hotwire_combobox/_next_page.turbo_stream.erb
|
91
92
|
- app/views/hotwire_combobox/_paginated_options.turbo_stream.erb
|
92
93
|
- app/views/hotwire_combobox/_pagination.html.erb
|
@@ -1,6 +0,0 @@
|
|
1
|
-
<%= tag.fieldset **component.fieldset_attrs do %>
|
2
|
-
<%= render "hotwire_combobox/combobox/hidden_field", component: component %>
|
3
|
-
<%= render "hotwire_combobox/combobox/input", component: component %>
|
4
|
-
<%= render "hotwire_combobox/combobox/paginated_listbox", component: component %>
|
5
|
-
<%= render "hotwire_combobox/combobox/dialog", component: component %>
|
6
|
-
<% end %>
|