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