stimulus_plumbers 0.2.8 → 0.3.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/CHANGELOG.md +52 -0
- data/README.md +8 -4
- data/app/assets/javascripts/stimulus-plumbers/stimulus-plumbers-controllers.es.js +450 -436
- data/app/assets/javascripts/stimulus-plumbers/stimulus-plumbers-controllers.umd.js +1 -1
- data/lib/stimulus_plumbers/components/action_list/item.rb +27 -0
- data/lib/stimulus_plumbers/components/action_list/section.rb +21 -0
- data/lib/stimulus_plumbers/components/action_list.rb +23 -0
- data/lib/stimulus_plumbers/components/avatar.rb +73 -0
- data/lib/stimulus_plumbers/components/button/group.rb +17 -0
- data/lib/stimulus_plumbers/components/button.rb +27 -0
- data/lib/stimulus_plumbers/components/calendar/month/turbo/days_of_month.rb +60 -41
- data/lib/stimulus_plumbers/components/calendar/month/turbo/days_of_week.rb +15 -12
- data/lib/stimulus_plumbers/components/calendar/month/turbo.rb +55 -0
- data/lib/stimulus_plumbers/components/calendar.rb +56 -0
- data/lib/stimulus_plumbers/components/card/section.rb +25 -0
- data/lib/stimulus_plumbers/components/card.rb +27 -0
- data/lib/stimulus_plumbers/components/combobox/autocomplete.rb +30 -34
- data/lib/stimulus_plumbers/components/combobox/date.rb +16 -18
- data/lib/stimulus_plumbers/components/combobox/dropdown.rb +13 -16
- data/lib/stimulus_plumbers/components/combobox/options/option.rb +34 -0
- data/lib/stimulus_plumbers/components/combobox/options/option_group.rb +29 -0
- data/lib/stimulus_plumbers/components/combobox/options.rb +59 -0
- data/lib/stimulus_plumbers/components/combobox/popover.rb +20 -0
- data/lib/stimulus_plumbers/components/combobox/time/drum.rb +37 -0
- data/lib/stimulus_plumbers/components/combobox/time.rb +32 -15
- data/lib/stimulus_plumbers/components/combobox/trigger.rb +38 -0
- data/lib/stimulus_plumbers/components/combobox.rb +59 -0
- data/lib/stimulus_plumbers/components/date_picker/navigation.rb +14 -22
- data/lib/stimulus_plumbers/components/date_picker/navigator.rb +1 -1
- data/lib/stimulus_plumbers/components/icon.rb +43 -0
- data/lib/stimulus_plumbers/components/popover/builder.rb +25 -0
- data/lib/stimulus_plumbers/components/popover.rb +31 -0
- data/lib/stimulus_plumbers/configuration.rb +3 -18
- data/lib/stimulus_plumbers/engine.rb +2 -2
- data/lib/stimulus_plumbers/form/builder.rb +8 -6
- data/lib/stimulus_plumbers/form/{field_component.rb → field.rb} +1 -1
- data/lib/stimulus_plumbers/form/fields/combobox.rb +7 -4
- data/lib/stimulus_plumbers/form/fields/error.rb +14 -0
- data/lib/stimulus_plumbers/form/fields/group.rb +14 -0
- data/lib/stimulus_plumbers/form/fields/hint.rb +14 -0
- data/lib/stimulus_plumbers/form/fields/label.rb +21 -0
- data/lib/stimulus_plumbers/form/fields/renderer.rb +20 -21
- data/lib/stimulus_plumbers/form/fields/search.rb +23 -9
- data/lib/stimulus_plumbers/form/fields/submit.rb +23 -0
- data/lib/stimulus_plumbers/helpers/action_list_helper.rb +2 -2
- data/lib/stimulus_plumbers/helpers/avatar_helper.rb +2 -2
- data/lib/stimulus_plumbers/helpers/button_helper.rb +2 -2
- data/lib/stimulus_plumbers/helpers/calendar_helper.rb +1 -1
- data/lib/stimulus_plumbers/helpers/calendar_turbo_helper.rb +1 -1
- data/lib/stimulus_plumbers/helpers/card_helper.rb +2 -2
- data/lib/stimulus_plumbers/helpers/combobox_helper.rb +5 -5
- data/lib/stimulus_plumbers/helpers/popover_helper.rb +2 -2
- data/lib/stimulus_plumbers/plumber/base.rb +20 -0
- data/lib/stimulus_plumbers/plumber/dispatcher/callable_inspector.rb +19 -0
- data/lib/stimulus_plumbers/plumber/dispatcher/instance_exec.rb +35 -0
- data/lib/stimulus_plumbers/plumber/dispatcher/klass_proxy.rb +34 -0
- data/lib/stimulus_plumbers/plumber/dispatcher/method_call.rb +36 -0
- data/lib/stimulus_plumbers/plumber/dispatcher.rb +28 -0
- data/lib/stimulus_plumbers/plumber/html_options.rb +52 -0
- data/lib/stimulus_plumbers/plumber/renderer.rb +89 -0
- data/lib/stimulus_plumbers/themes/base.rb +34 -20
- data/lib/stimulus_plumbers/themes/configuration.rb +38 -0
- data/lib/stimulus_plumbers/themes/schema/form/ranges.rb +14 -0
- data/lib/stimulus_plumbers/themes/schema/icon.rb +32 -0
- data/lib/stimulus_plumbers/themes/schema/ranges.rb +5 -5
- data/lib/stimulus_plumbers/themes/schema.rb +103 -0
- data/lib/stimulus_plumbers/version.rb +1 -1
- data/lib/stimulus_plumbers.rb +29 -19
- metadata +40 -34
- data/lib/stimulus_plumbers/components/action_list/renderer.rb +0 -47
- data/lib/stimulus_plumbers/components/avatar/renderer.rb +0 -74
- data/lib/stimulus_plumbers/components/button/renderer.rb +0 -33
- data/lib/stimulus_plumbers/components/calendar/month/turbo/renderer.rb +0 -57
- data/lib/stimulus_plumbers/components/calendar/renderer.rb +0 -35
- data/lib/stimulus_plumbers/components/card/renderer.rb +0 -41
- data/lib/stimulus_plumbers/components/combobox/option.rb +0 -27
- data/lib/stimulus_plumbers/components/combobox/option_group.rb +0 -52
- data/lib/stimulus_plumbers/components/combobox/renderer.rb +0 -78
- data/lib/stimulus_plumbers/components/icon/renderer.rb +0 -51
- data/lib/stimulus_plumbers/components/plumber/base.rb +0 -22
- data/lib/stimulus_plumbers/components/plumber/dispatcher.rb +0 -113
- data/lib/stimulus_plumbers/components/plumber/html_options.rb +0 -53
- data/lib/stimulus_plumbers/components/plumber/renderer.rb +0 -91
- data/lib/stimulus_plumbers/components/popover/renderer.rb +0 -46
- data/lib/stimulus_plumbers/components/time_picker/renderer.rb +0 -38
- data/lib/stimulus_plumbers/themes/base/action_list.rb +0 -14
- data/lib/stimulus_plumbers/themes/base/avatar.rb +0 -14
- data/lib/stimulus_plumbers/themes/base/button.rb +0 -18
- data/lib/stimulus_plumbers/themes/base/calendar.rb +0 -15
- data/lib/stimulus_plumbers/themes/base/card.rb +0 -12
- data/lib/stimulus_plumbers/themes/base/form.rb +0 -34
- data/lib/stimulus_plumbers/themes/base/layout.rb +0 -12
- data/lib/stimulus_plumbers/themes/tailwind/action_list.rb +0 -33
- data/lib/stimulus_plumbers/themes/tailwind/avatar.rb +0 -52
- data/lib/stimulus_plumbers/themes/tailwind/button.rb +0 -89
- data/lib/stimulus_plumbers/themes/tailwind/calendar.rb +0 -34
- data/lib/stimulus_plumbers/themes/tailwind/card.rb +0 -24
- data/lib/stimulus_plumbers/themes/tailwind/form.rb +0 -108
- data/lib/stimulus_plumbers/themes/tailwind/layout.rb +0 -25
- data/lib/stimulus_plumbers/themes/tailwind_theme.rb +0 -29
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module StimulusPlumbers
|
|
4
|
+
module Components
|
|
5
|
+
class Calendar < Plumber::Base
|
|
6
|
+
STIMULUS_CONTROLLER = "calendar-month"
|
|
7
|
+
OBSERVER_STIMULUS_CONTROLLER = "calendar-month-observer"
|
|
8
|
+
STIMULUS_DATA = {
|
|
9
|
+
controller: "#{STIMULUS_CONTROLLER} #{OBSERVER_STIMULUS_CONTROLLER}",
|
|
10
|
+
action: "click->#{OBSERVER_STIMULUS_CONTROLLER}#select"
|
|
11
|
+
}.freeze
|
|
12
|
+
|
|
13
|
+
def month(**kwargs)
|
|
14
|
+
template.content_tag(:div, role: "grid", **month_html_options(kwargs)) do
|
|
15
|
+
template.safe_join([template.tag.div(**dow_options), template.tag.div(**dom_options)])
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
private
|
|
20
|
+
|
|
21
|
+
def month_html_options(kwargs)
|
|
22
|
+
merge_html_options(
|
|
23
|
+
{
|
|
24
|
+
classes: theme.resolve(:calendar).fetch(:classes, ""),
|
|
25
|
+
data: month_stimulus_data
|
|
26
|
+
},
|
|
27
|
+
kwargs
|
|
28
|
+
)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def month_stimulus_data
|
|
32
|
+
STIMULUS_DATA.merge(
|
|
33
|
+
calendar_month_week_class: theme.resolve(:calendar_week).fetch(:classes, ""),
|
|
34
|
+
calendar_month_day_of_month_class: theme.resolve(:calendar_day).fetch(:classes, "")
|
|
35
|
+
).compact_blank
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def dow_options
|
|
39
|
+
merge_html_options(
|
|
40
|
+
{ classes: theme.resolve(:calendar_days_of_week).fetch(:classes, "") },
|
|
41
|
+
{ data: { "#{STIMULUS_CONTROLLER}-target": "daysOfWeek" } }
|
|
42
|
+
)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def dom_options
|
|
46
|
+
merge_html_options(
|
|
47
|
+
{ classes: theme.resolve(:calendar_days_of_month).fetch(:classes, "") },
|
|
48
|
+
{
|
|
49
|
+
role: "rowgroup",
|
|
50
|
+
data: { "#{STIMULUS_CONTROLLER}-target": "daysOfMonth" }
|
|
51
|
+
}
|
|
52
|
+
)
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module StimulusPlumbers
|
|
4
|
+
module Components
|
|
5
|
+
class Card
|
|
6
|
+
class Section < Plumber::Base
|
|
7
|
+
def render(title: nil, **kwargs, &block)
|
|
8
|
+
html_options = merge_html_options(
|
|
9
|
+
{ classes: theme.resolve(:card_section).fetch(:classes, "") },
|
|
10
|
+
kwargs
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
template.content_tag(:div, **html_options) do
|
|
14
|
+
template.safe_join(
|
|
15
|
+
[
|
|
16
|
+
(template.content_tag(:h3, title) if title.present?),
|
|
17
|
+
template.capture(&block)
|
|
18
|
+
].compact
|
|
19
|
+
)
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module StimulusPlumbers
|
|
4
|
+
module Components
|
|
5
|
+
class Card < Plumber::Base
|
|
6
|
+
def render(title: nil, **kwargs, &block)
|
|
7
|
+
html_options = merge_html_options(
|
|
8
|
+
{ classes: theme.resolve(:card).fetch(:classes, "") },
|
|
9
|
+
kwargs
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
template.content_tag(:div, **html_options) do
|
|
13
|
+
template.safe_join(
|
|
14
|
+
[
|
|
15
|
+
(template.content_tag(:h2, title) if title.present?),
|
|
16
|
+
template.capture(&block)
|
|
17
|
+
].compact
|
|
18
|
+
)
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def section(title: nil, **kwargs, &block)
|
|
23
|
+
Card::Section.new(template).render(title: title, **kwargs, &block)
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -2,54 +2,50 @@
|
|
|
2
2
|
|
|
3
3
|
module StimulusPlumbers
|
|
4
4
|
module Components
|
|
5
|
-
|
|
6
|
-
# Renders a listbox popover body for autocomplete.
|
|
7
|
-
# Re-uses combobox-dropdown for selection/filtering; input-combobox relays
|
|
8
|
-
# the trigger input event via the combobox-dropdown outlet.
|
|
5
|
+
class Combobox
|
|
9
6
|
class Autocomplete < Plumber::Base
|
|
10
|
-
include OptionGroup
|
|
11
|
-
|
|
12
|
-
DROPDOWN_CONTROLLER = "combobox-dropdown"
|
|
13
|
-
DROPDOWN_ACTION = [
|
|
14
|
-
"click->combobox-dropdown#select",
|
|
15
|
-
"keydown->combobox-dropdown#navigate",
|
|
16
|
-
"combobox-dropdown:selected->input-combobox#onSelected"
|
|
17
|
-
].join(" ").freeze
|
|
18
|
-
|
|
19
7
|
def self.default_opts
|
|
20
|
-
|
|
21
|
-
popover: {
|
|
22
|
-
tag: :div,
|
|
23
|
-
haspopup: "listbox",
|
|
24
|
-
data: { controller: DROPDOWN_CONTROLLER, action: DROPDOWN_ACTION }
|
|
25
|
-
},
|
|
8
|
+
Dropdown.default_opts.deep_merge(
|
|
26
9
|
trigger: { aria_autocomplete: "list", readonly: false }
|
|
27
|
-
|
|
10
|
+
)
|
|
28
11
|
end
|
|
29
12
|
|
|
30
13
|
def render(options: [], value: nil, label: nil, **_kwargs)
|
|
31
|
-
|
|
32
|
-
|
|
14
|
+
template.safe_join([listbox(options, value, label), loading, empty])
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
private
|
|
33
18
|
|
|
34
|
-
|
|
35
|
-
|
|
19
|
+
def listbox(options, value, label)
|
|
20
|
+
attrs = merge_html_options(
|
|
21
|
+
{ classes: theme.resolve(:combobox_listbox).fetch(:classes, "") },
|
|
22
|
+
{ role: "listbox", data: { "#{Dropdown::STIMULUS_CONTROLLER}_target": "listbox" } }
|
|
23
|
+
)
|
|
24
|
+
attrs[:aria] = { label: label } if label
|
|
25
|
+
|
|
26
|
+
template.content_tag(:ul, **attrs) do
|
|
27
|
+
Options.new(template).render(options, value: value)
|
|
36
28
|
end
|
|
29
|
+
end
|
|
37
30
|
|
|
38
|
-
|
|
31
|
+
def loading
|
|
32
|
+
template.content_tag(
|
|
39
33
|
:div,
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
34
|
+
**merge_html_options(
|
|
35
|
+
{ classes: theme.resolve(:combobox_autocomplete_loading).fetch(:classes, "") },
|
|
36
|
+
{ hidden: "", aria: { live: "polite" }, data: { "#{Dropdown::STIMULUS_CONTROLLER}_target": "loading" } }
|
|
37
|
+
)
|
|
43
38
|
) { "" }
|
|
39
|
+
end
|
|
44
40
|
|
|
45
|
-
|
|
41
|
+
def empty
|
|
42
|
+
template.content_tag(
|
|
46
43
|
:div,
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
44
|
+
**merge_html_options(
|
|
45
|
+
{ classes: theme.resolve(:combobox_autocomplete_empty).fetch(:classes, "") },
|
|
46
|
+
{ hidden: "", role: "status", data: { "#{Dropdown::STIMULUS_CONTROLLER}_target": "empty" } }
|
|
47
|
+
)
|
|
50
48
|
) { "No results" }
|
|
51
|
-
|
|
52
|
-
template.safe_join([listbox, loading, empty])
|
|
53
49
|
end
|
|
54
50
|
end
|
|
55
51
|
end
|
|
@@ -2,12 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
module StimulusPlumbers
|
|
4
4
|
module Components
|
|
5
|
-
|
|
6
|
-
# Renders the date picker popover body: navigation + calendar grid.
|
|
7
|
-
# Wires ComboboxDateController; dispatches combobox-date:selected → InputComboboxController.
|
|
5
|
+
class Combobox
|
|
8
6
|
class Date < Plumber::Base
|
|
9
|
-
|
|
10
|
-
|
|
7
|
+
STIMULUS_CONTROLLER = "combobox-date"
|
|
8
|
+
CALENDAR_OUTLET = "#{STIMULUS_CONTROLLER}-calendar-month-outlet".freeze
|
|
11
9
|
|
|
12
10
|
def self.default_opts
|
|
13
11
|
{
|
|
@@ -19,17 +17,17 @@ module StimulusPlumbers
|
|
|
19
17
|
def render(value: nil, **_kwargs)
|
|
20
18
|
calendar_id = "combobox_date_#{SecureRandom.hex(8)}_calendar"
|
|
21
19
|
|
|
22
|
-
|
|
23
|
-
:
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
) do
|
|
20
|
+
data = {
|
|
21
|
+
controller: STIMULUS_CONTROLLER,
|
|
22
|
+
CALENDAR_OUTLET => "##{calendar_id}",
|
|
23
|
+
action: [
|
|
24
|
+
"calendar-month-observer:selected->#{STIMULUS_CONTROLLER}#onSelect",
|
|
25
|
+
"#{STIMULUS_CONTROLLER}:selected->#{Combobox::STIMULUS_CONTROLLER}#onSelect"
|
|
26
|
+
].join(" "),
|
|
27
|
+
"#{STIMULUS_CONTROLLER}-date-value" => value
|
|
28
|
+
}.compact
|
|
29
|
+
|
|
30
|
+
template.content_tag(:div, data: data) do
|
|
33
31
|
template.safe_join([navigation, calendar_month(id: calendar_id)])
|
|
34
32
|
end
|
|
35
33
|
end
|
|
@@ -39,12 +37,12 @@ module StimulusPlumbers
|
|
|
39
37
|
def navigation
|
|
40
38
|
DatePicker::Navigation.new(template).render(
|
|
41
39
|
step: "month",
|
|
42
|
-
stimulus_controller:
|
|
40
|
+
stimulus_controller: STIMULUS_CONTROLLER
|
|
43
41
|
)
|
|
44
42
|
end
|
|
45
43
|
|
|
46
44
|
def calendar_month(**kwargs)
|
|
47
|
-
Calendar
|
|
45
|
+
Calendar.new(template).month(**kwargs)
|
|
48
46
|
end
|
|
49
47
|
end
|
|
50
48
|
end
|
|
@@ -2,17 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
module StimulusPlumbers
|
|
4
4
|
module Components
|
|
5
|
-
|
|
6
|
-
# Renders a static listbox popover body with combobox-dropdown controller.
|
|
7
|
-
# Supports flat options, descriptions, and option groups.
|
|
5
|
+
class Combobox
|
|
8
6
|
class Dropdown < Plumber::Base
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
"
|
|
14
|
-
"keydown->combobox-dropdown#navigate",
|
|
15
|
-
"combobox-dropdown:selected->input-combobox#onSelected"
|
|
7
|
+
STIMULUS_CONTROLLER = "combobox-dropdown"
|
|
8
|
+
STIMULUS_ACTION = [
|
|
9
|
+
"click->#{STIMULUS_CONTROLLER}#select",
|
|
10
|
+
"keydown->#{STIMULUS_CONTROLLER}#onNavigate",
|
|
11
|
+
"#{STIMULUS_CONTROLLER}:selected->#{Combobox::STIMULUS_CONTROLLER}#onSelect"
|
|
16
12
|
].join(" ").freeze
|
|
17
13
|
|
|
18
14
|
def self.default_opts
|
|
@@ -20,20 +16,21 @@ module StimulusPlumbers
|
|
|
20
16
|
popover: {
|
|
21
17
|
tag: :div,
|
|
22
18
|
haspopup: "listbox",
|
|
23
|
-
data: { controller:
|
|
19
|
+
data: { controller: STIMULUS_CONTROLLER, action: STIMULUS_ACTION }
|
|
24
20
|
}
|
|
25
21
|
}
|
|
26
22
|
end
|
|
27
23
|
|
|
28
24
|
def render(options: [], value: nil, label: nil, **_kwargs)
|
|
29
|
-
listbox_attrs =
|
|
25
|
+
listbox_attrs = merge_html_options(
|
|
26
|
+
{ classes: theme.resolve(:combobox_listbox).fetch(:classes, "") },
|
|
27
|
+
{ role: "listbox", data: { "#{STIMULUS_CONTROLLER}_target": "listbox" } }
|
|
28
|
+
)
|
|
30
29
|
listbox_attrs[:aria] = { label: label } if label
|
|
31
30
|
|
|
32
|
-
|
|
33
|
-
|
|
31
|
+
template.content_tag(:ul, **listbox_attrs) do
|
|
32
|
+
Options.new(template).render(options, value: value)
|
|
34
33
|
end
|
|
35
|
-
|
|
36
|
-
template.safe_join([listbox])
|
|
37
34
|
end
|
|
38
35
|
end
|
|
39
36
|
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module StimulusPlumbers
|
|
4
|
+
module Components
|
|
5
|
+
class Combobox
|
|
6
|
+
class Options
|
|
7
|
+
class Option < Plumber::Base
|
|
8
|
+
def render(label:, value:, description: nil, disabled: false, selected: false)
|
|
9
|
+
aria = { selected: selected ? "true" : "false" }
|
|
10
|
+
aria[:disabled] = "true" if disabled
|
|
11
|
+
|
|
12
|
+
attrs = merge_html_options(
|
|
13
|
+
{ classes: theme.resolve(:combobox_option, selected: selected, disabled: disabled).fetch(:classes, "") },
|
|
14
|
+
{ role: "option", aria: aria, data: { value: value } }
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
template.content_tag(:li, **attrs) do
|
|
18
|
+
if description
|
|
19
|
+
template.safe_join(
|
|
20
|
+
[
|
|
21
|
+
template.content_tag(:span, label),
|
|
22
|
+
template.content_tag(:span, description)
|
|
23
|
+
]
|
|
24
|
+
)
|
|
25
|
+
else
|
|
26
|
+
label
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module StimulusPlumbers
|
|
4
|
+
module Components
|
|
5
|
+
class Combobox
|
|
6
|
+
class Options
|
|
7
|
+
class OptionGroup < Plumber::Base
|
|
8
|
+
def render(label:, options:, value: nil)
|
|
9
|
+
attrs = merge_html_options(
|
|
10
|
+
{ classes: theme.resolve(:combobox_option_group).fetch(:classes, "") },
|
|
11
|
+
{ role: "group", aria: { label: label } }
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
template.content_tag(:li, **attrs) do
|
|
15
|
+
template.safe_join(
|
|
16
|
+
[
|
|
17
|
+
template.content_tag(:span, label, aria: { hidden: "true" }),
|
|
18
|
+
template.content_tag(:ul) do
|
|
19
|
+
Options.new(template).render(options, value: value)
|
|
20
|
+
end
|
|
21
|
+
]
|
|
22
|
+
)
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module StimulusPlumbers
|
|
4
|
+
module Components
|
|
5
|
+
class Combobox
|
|
6
|
+
class Options < Plumber::Base
|
|
7
|
+
def render(items, value: nil, &block)
|
|
8
|
+
@selected_value = value.to_s
|
|
9
|
+
template.safe_join(items.filter_map { |item| render_item(item, &block) })
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
private
|
|
13
|
+
|
|
14
|
+
def render_item(item, &block)
|
|
15
|
+
attrs = normalize_item(item)
|
|
16
|
+
return nil if attrs.nil?
|
|
17
|
+
|
|
18
|
+
if attrs.key?(:optgroup)
|
|
19
|
+
block ? block.call(attrs) : OptionGroup.new(template).render(**attrs[:optgroup], value: @selected_value)
|
|
20
|
+
else
|
|
21
|
+
block ? block.call(attrs) : Option.new(template).render(**attrs)
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def normalize_item(item)
|
|
26
|
+
case item
|
|
27
|
+
when Hash then normalize_hash(item)
|
|
28
|
+
when Array then normalize_array(item)
|
|
29
|
+
else
|
|
30
|
+
StimulusPlumbers::Logger.warn("Options#normalize_item: unrecognized item type #{item.class}, skipping")
|
|
31
|
+
nil
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def normalize_hash(item)
|
|
36
|
+
if item.key?(:options)
|
|
37
|
+
{ optgroup: { label: item[:label], options: item[:options] } }
|
|
38
|
+
else
|
|
39
|
+
normalize_option(item[:label], item[:value].to_s, item)
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def normalize_array(item)
|
|
44
|
+
normalize_option(item[0], item[1].to_s, item[2] || {})
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def normalize_option(label, value, attrs)
|
|
48
|
+
{
|
|
49
|
+
label: label,
|
|
50
|
+
value: value,
|
|
51
|
+
selected: @selected_value == value,
|
|
52
|
+
disabled: attrs[:disabled] || false,
|
|
53
|
+
description: attrs[:description]
|
|
54
|
+
}
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module StimulusPlumbers
|
|
4
|
+
module Components
|
|
5
|
+
class Combobox
|
|
6
|
+
class Popover < Plumber::Base
|
|
7
|
+
def render(stimulus_controller:, id:, tag: :div, role: nil, label: nil, content: nil, data: {}, **_rest, &block)
|
|
8
|
+
base_data = { "#{stimulus_controller}_target": "popover" }
|
|
9
|
+
|
|
10
|
+
attrs = { id: id, hidden: "", data: merge_data_options(base_data, data.symbolize_keys) }
|
|
11
|
+
attrs[:role] = role if role
|
|
12
|
+
attrs[:aria] = { label: label } if label
|
|
13
|
+
|
|
14
|
+
html_content = block_given? ? template.capture(&block) : content
|
|
15
|
+
template.content_tag(tag, **attrs) { html_content }
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module StimulusPlumbers
|
|
4
|
+
module Components
|
|
5
|
+
class Combobox
|
|
6
|
+
class Time
|
|
7
|
+
class Drum < Plumber::Base
|
|
8
|
+
def render(stimulus_controller:, target:, label:, items:, selected: nil)
|
|
9
|
+
template.content_tag(
|
|
10
|
+
:ul,
|
|
11
|
+
**merge_html_options(
|
|
12
|
+
{ classes: theme.resolve(:combobox_listbox).fetch(:classes, "") },
|
|
13
|
+
{
|
|
14
|
+
role: "listbox",
|
|
15
|
+
tabindex: "0",
|
|
16
|
+
aria: { label: label },
|
|
17
|
+
data: { "#{stimulus_controller}_target": target }
|
|
18
|
+
},
|
|
19
|
+
{ data: { action: "click->#{stimulus_controller}#select keydown->#{stimulus_controller}#onNavigate" } }
|
|
20
|
+
)
|
|
21
|
+
) do
|
|
22
|
+
template.safe_join(
|
|
23
|
+
items.map do |text, value|
|
|
24
|
+
Options::Option.new(template).render(
|
|
25
|
+
label: text,
|
|
26
|
+
value: value,
|
|
27
|
+
selected: value.to_s == selected.to_s
|
|
28
|
+
)
|
|
29
|
+
end
|
|
30
|
+
)
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -2,11 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
module StimulusPlumbers
|
|
4
4
|
module Components
|
|
5
|
-
|
|
6
|
-
# Renders an iOS-style drum/wheel time picker as the popover body.
|
|
7
|
-
# Wires ComboboxTimeController; dispatches combobox-time:selected → InputComboboxController.
|
|
5
|
+
class Combobox
|
|
8
6
|
class Time < Plumber::Base
|
|
9
|
-
|
|
7
|
+
STIMULUS_CONTROLLER = "combobox-time"
|
|
10
8
|
|
|
11
9
|
def self.default_opts
|
|
12
10
|
{
|
|
@@ -21,10 +19,13 @@ module StimulusPlumbers
|
|
|
21
19
|
|
|
22
20
|
template.content_tag(
|
|
23
21
|
:div,
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
22
|
+
**merge_html_options(
|
|
23
|
+
{ classes: theme.resolve(:combobox_time).fetch(:classes, "") },
|
|
24
|
+
{ data: { controller: STIMULUS_CONTROLLER,
|
|
25
|
+
action: "#{STIMULUS_CONTROLLER}:selected->#{Combobox::STIMULUS_CONTROLLER}#onSelect"
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
)
|
|
28
29
|
) do
|
|
29
30
|
template.safe_join(drums)
|
|
30
31
|
end
|
|
@@ -39,7 +40,13 @@ module StimulusPlumbers
|
|
|
39
40
|
end
|
|
40
41
|
|
|
41
42
|
def hour_drum
|
|
42
|
-
drum.render(
|
|
43
|
+
drum.render(
|
|
44
|
+
stimulus_controller: STIMULUS_CONTROLLER,
|
|
45
|
+
target: "hour",
|
|
46
|
+
label: "Hour",
|
|
47
|
+
items: hour_items,
|
|
48
|
+
selected: current_hour
|
|
49
|
+
)
|
|
43
50
|
end
|
|
44
51
|
|
|
45
52
|
def minute_drum
|
|
@@ -48,14 +55,24 @@ module StimulusPlumbers
|
|
|
48
55
|
[s, s]
|
|
49
56
|
end
|
|
50
57
|
selected = @time ? snap_minute(@time.min).to_s.rjust(2, "0") : nil
|
|
51
|
-
drum.render(
|
|
58
|
+
drum.render(
|
|
59
|
+
stimulus_controller: STIMULUS_CONTROLLER,
|
|
60
|
+
target: "minute",
|
|
61
|
+
label: "Minute",
|
|
62
|
+
items: items,
|
|
63
|
+
selected: selected
|
|
64
|
+
)
|
|
52
65
|
end
|
|
53
66
|
|
|
54
67
|
def period_drum
|
|
55
|
-
selected =
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
68
|
+
selected = @time && (@time.hour < 12 ? "AM" : "PM")
|
|
69
|
+
drum.render(
|
|
70
|
+
stimulus_controller: STIMULUS_CONTROLLER,
|
|
71
|
+
target: "period",
|
|
72
|
+
label: "Period",
|
|
73
|
+
items: [%w[AM AM], %w[PM PM]],
|
|
74
|
+
selected: selected
|
|
75
|
+
)
|
|
59
76
|
end
|
|
60
77
|
|
|
61
78
|
def hour_items
|
|
@@ -87,7 +104,7 @@ module StimulusPlumbers
|
|
|
87
104
|
end
|
|
88
105
|
|
|
89
106
|
def drum
|
|
90
|
-
|
|
107
|
+
@drum ||= Time::Drum.new(template)
|
|
91
108
|
end
|
|
92
109
|
|
|
93
110
|
def parse_time(value)
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module StimulusPlumbers
|
|
4
|
+
module Components
|
|
5
|
+
class Combobox
|
|
6
|
+
class Trigger < Plumber::Base
|
|
7
|
+
def render(
|
|
8
|
+
stimulus_controller:,
|
|
9
|
+
popover_id:,
|
|
10
|
+
haspopup:,
|
|
11
|
+
readonly: true,
|
|
12
|
+
aria_autocomplete: nil,
|
|
13
|
+
aria_label: nil,
|
|
14
|
+
data: {},
|
|
15
|
+
**_rest
|
|
16
|
+
)
|
|
17
|
+
base_data = {
|
|
18
|
+
"#{stimulus_controller}_target": "trigger",
|
|
19
|
+
input_format_target: "input",
|
|
20
|
+
action: "focus->#{stimulus_controller}#open keydown.esc->#{stimulus_controller}#close"
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
aria = { haspopup: haspopup, expanded: "false", controls: popover_id }
|
|
24
|
+
aria[:autocomplete] = aria_autocomplete if aria_autocomplete
|
|
25
|
+
aria[:label] = aria_label if aria_label
|
|
26
|
+
|
|
27
|
+
html_options = merge_html_options(
|
|
28
|
+
{ classes: theme.resolve(:combobox_trigger).fetch(:classes, "") },
|
|
29
|
+
{ type: "text", readonly: (readonly ? true : nil), role: "combobox", aria: aria,
|
|
30
|
+
data: merge_data_options(base_data, data.symbolize_keys)
|
|
31
|
+
}
|
|
32
|
+
)
|
|
33
|
+
template.tag.input(**html_options)
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module StimulusPlumbers
|
|
4
|
+
module Components
|
|
5
|
+
class Combobox < Plumber::Base
|
|
6
|
+
STIMULUS_CONTROLLER = "input-combobox"
|
|
7
|
+
FORMAT_CONTROLLER = "input-format"
|
|
8
|
+
FORMAT_ACTION = "input-combobox:changed->input-format#format"
|
|
9
|
+
|
|
10
|
+
def render(base_id:, options: {}, **kwargs)
|
|
11
|
+
popover_id = "#{base_id}_popover"
|
|
12
|
+
initial_value = options.dig(:input, :value)
|
|
13
|
+
|
|
14
|
+
base_data = {
|
|
15
|
+
controller: "#{STIMULUS_CONTROLLER} #{FORMAT_CONTROLLER}",
|
|
16
|
+
action: FORMAT_ACTION
|
|
17
|
+
}
|
|
18
|
+
base_data[:input_combobox_value_value] = initial_value if initial_value.present?
|
|
19
|
+
|
|
20
|
+
html_options = merge_html_options({ data: base_data }, kwargs)
|
|
21
|
+
|
|
22
|
+
template.content_tag(:div, **html_options) do
|
|
23
|
+
template.safe_join(
|
|
24
|
+
[
|
|
25
|
+
trigger(popover_id, options),
|
|
26
|
+
hidden_input(options.fetch(:input, {})),
|
|
27
|
+
popover(popover_id, options)
|
|
28
|
+
]
|
|
29
|
+
)
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def trigger(popover_id, options)
|
|
34
|
+
haspopup = options.dig(:popover, :haspopup) || options.dig(:popover, :role) || "dialog"
|
|
35
|
+
Combobox::Trigger.new(template).render(
|
|
36
|
+
stimulus_controller: STIMULUS_CONTROLLER,
|
|
37
|
+
popover_id: popover_id,
|
|
38
|
+
haspopup: haspopup,
|
|
39
|
+
**options.fetch(:trigger, {})
|
|
40
|
+
)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def popover(popover_id, options)
|
|
44
|
+
Combobox::Popover.new(template).render(
|
|
45
|
+
stimulus_controller: STIMULUS_CONTROLLER,
|
|
46
|
+
id: popover_id,
|
|
47
|
+
**options.fetch(:popover, {})
|
|
48
|
+
)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
private
|
|
52
|
+
|
|
53
|
+
def hidden_input(opts)
|
|
54
|
+
data = { "#{STIMULUS_CONTROLLER}_target": "value" }.merge(opts.fetch(:data, {}))
|
|
55
|
+
template.tag.input(type: "hidden", name: opts[:name], value: opts[:value], data: data)
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|