stimulus_plumbers 0.2.9 → 0.3.1

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.
Files changed (87) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +38 -0
  3. data/README.md +5 -4
  4. data/app/assets/javascripts/stimulus-plumbers/stimulus-plumbers-controllers.es.js +246 -269
  5. data/app/assets/javascripts/stimulus-plumbers/stimulus-plumbers-controllers.umd.js +1 -1
  6. data/lib/stimulus_plumbers/components/action_list/section.rb +6 -5
  7. data/lib/stimulus_plumbers/components/action_list.rb +3 -3
  8. data/lib/stimulus_plumbers/components/avatar.rb +4 -3
  9. data/lib/stimulus_plumbers/components/calendar/month/turbo/days_of_month.rb +62 -41
  10. data/lib/stimulus_plumbers/components/calendar/month/turbo/days_of_week.rb +13 -10
  11. data/lib/stimulus_plumbers/components/calendar.rb +36 -13
  12. data/lib/stimulus_plumbers/components/card/section.rb +3 -3
  13. data/lib/stimulus_plumbers/components/card.rb +3 -3
  14. data/lib/stimulus_plumbers/components/combobox/autocomplete.rb +8 -14
  15. data/lib/stimulus_plumbers/components/combobox/date.rb +6 -2
  16. data/lib/stimulus_plumbers/components/combobox/dropdown.rb +6 -2
  17. data/lib/stimulus_plumbers/components/combobox/popover.rb +9 -5
  18. data/lib/stimulus_plumbers/components/combobox/time.rb +1 -1
  19. data/lib/stimulus_plumbers/components/combobox/trigger.rb +19 -10
  20. data/lib/stimulus_plumbers/components/combobox.rb +33 -23
  21. data/lib/stimulus_plumbers/components/date_picker/navigation.rb +14 -22
  22. data/lib/stimulus_plumbers/components/divider.rb +16 -0
  23. data/lib/stimulus_plumbers/components/icon.rb +14 -20
  24. data/lib/stimulus_plumbers/components/popover/builder.rb +2 -2
  25. data/lib/stimulus_plumbers/components/popover.rb +11 -6
  26. data/lib/stimulus_plumbers/configuration.rb +3 -18
  27. data/lib/stimulus_plumbers/engine.rb +2 -2
  28. data/lib/stimulus_plumbers/form/builder.rb +40 -44
  29. data/lib/stimulus_plumbers/form/field.rb +96 -45
  30. data/lib/stimulus_plumbers/form/fields/error.rb +2 -2
  31. data/lib/stimulus_plumbers/form/fields/fieldset.rb +54 -0
  32. data/lib/stimulus_plumbers/form/fields/group.rb +2 -2
  33. data/lib/stimulus_plumbers/form/fields/hint.rb +2 -2
  34. data/lib/stimulus_plumbers/form/fields/input_group.rb +25 -0
  35. data/lib/stimulus_plumbers/form/fields/inputs/choice.rb +69 -0
  36. data/lib/stimulus_plumbers/form/fields/inputs/datetime.rb +81 -0
  37. data/lib/stimulus_plumbers/form/fields/inputs/file.rb +22 -0
  38. data/lib/stimulus_plumbers/form/fields/inputs/password.rb +59 -0
  39. data/lib/stimulus_plumbers/form/fields/inputs/search.rb +102 -0
  40. data/lib/stimulus_plumbers/form/fields/inputs/select/grouped.rb +56 -0
  41. data/lib/stimulus_plumbers/form/fields/inputs/select/timezone.rb +59 -0
  42. data/lib/stimulus_plumbers/form/fields/inputs/select/weekday.rb +45 -0
  43. data/lib/stimulus_plumbers/form/fields/inputs/select.rb +91 -0
  44. data/lib/stimulus_plumbers/form/fields/inputs/submit.rb +25 -0
  45. data/lib/stimulus_plumbers/form/fields/inputs/text.rb +37 -0
  46. data/lib/stimulus_plumbers/form/fields/inputs/text_area.rb +22 -0
  47. data/lib/stimulus_plumbers/form/fields/label.rb +13 -9
  48. data/lib/stimulus_plumbers/helpers/calendar_helper.rb +1 -1
  49. data/lib/stimulus_plumbers/helpers/combobox_helper.rb +29 -36
  50. data/lib/stimulus_plumbers/helpers/divider_helper.rb +11 -0
  51. data/lib/stimulus_plumbers/helpers.rb +2 -0
  52. data/lib/stimulus_plumbers/plumber/base.rb +1 -1
  53. data/lib/stimulus_plumbers/plumber/dispatcher/callable_inspector.rb +19 -0
  54. data/lib/stimulus_plumbers/plumber/dispatcher/instance_exec.rb +35 -0
  55. data/lib/stimulus_plumbers/plumber/dispatcher/klass_proxy.rb +34 -0
  56. data/lib/stimulus_plumbers/plumber/dispatcher/method_call.rb +36 -0
  57. data/lib/stimulus_plumbers/plumber/dispatcher.rb +4 -87
  58. data/lib/stimulus_plumbers/plumber/html_options.rb +6 -5
  59. data/lib/stimulus_plumbers/plumber/renderer.rb +2 -2
  60. data/lib/stimulus_plumbers/themes/base.rb +26 -5
  61. data/lib/stimulus_plumbers/themes/configuration.rb +38 -0
  62. data/lib/stimulus_plumbers/themes/schema/form/ranges.rb +14 -0
  63. data/lib/stimulus_plumbers/themes/schema/icon.rb +32 -0
  64. data/lib/stimulus_plumbers/themes/schema/ranges.rb +1 -1
  65. data/lib/stimulus_plumbers/themes/schema.rb +17 -5
  66. data/lib/stimulus_plumbers/version.rb +1 -1
  67. data/lib/stimulus_plumbers.rb +4 -2
  68. metadata +25 -21
  69. data/lib/stimulus_plumbers/form/fields/choice.rb +0 -25
  70. data/lib/stimulus_plumbers/form/fields/combobox.rb +0 -41
  71. data/lib/stimulus_plumbers/form/fields/file.rb +0 -16
  72. data/lib/stimulus_plumbers/form/fields/password.rb +0 -55
  73. data/lib/stimulus_plumbers/form/fields/renderer.rb +0 -52
  74. data/lib/stimulus_plumbers/form/fields/search.rb +0 -54
  75. data/lib/stimulus_plumbers/form/fields/select.rb +0 -33
  76. data/lib/stimulus_plumbers/form/fields/submit.rb +0 -23
  77. data/lib/stimulus_plumbers/form/fields/text.rb +0 -33
  78. data/lib/stimulus_plumbers/form/fields/text_area.rb +0 -16
  79. data/lib/stimulus_plumbers/themes/tailwind/action_list.rb +0 -33
  80. data/lib/stimulus_plumbers/themes/tailwind/avatar.rb +0 -52
  81. data/lib/stimulus_plumbers/themes/tailwind/button.rb +0 -89
  82. data/lib/stimulus_plumbers/themes/tailwind/calendar.rb +0 -80
  83. data/lib/stimulus_plumbers/themes/tailwind/card.rb +0 -24
  84. data/lib/stimulus_plumbers/themes/tailwind/combobox.rb +0 -75
  85. data/lib/stimulus_plumbers/themes/tailwind/form.rb +0 -108
  86. data/lib/stimulus_plumbers/themes/tailwind/layout.rb +0 -25
  87. data/lib/stimulus_plumbers/themes/tailwind_theme.rb +0 -31
@@ -0,0 +1,91 @@
1
+ # frozen_string_literal: true
2
+
3
+ module StimulusPlumbers
4
+ module Form
5
+ module Fields
6
+ module Inputs
7
+ module Select
8
+ def select(attribute, choices = nil, options = {}, html_options = {})
9
+ html_native = options.delete(:html_native) { false }
10
+ Field.new(@template, **options).render(
11
+ object,
12
+ attribute,
13
+ input_id: field_id(attribute)
14
+ ) do |html_opts, opts, error|
15
+ merged_html_opts = merge_html_options(html_options, html_opts, field_theme(:form_select, error: error))
16
+ if html_native
17
+ super(attribute, choices, opts, merged_html_opts)
18
+ else
19
+ render_select_dropdown(attribute, opts, merged_html_opts, err: error) { Array(choices) }
20
+ end
21
+ end
22
+ end
23
+
24
+ def collection_select(
25
+ attribute,
26
+ collection,
27
+ value_method,
28
+ text_method,
29
+ options = {},
30
+ html_options = {}
31
+ )
32
+ html_native = options.delete(:html_native) { false }
33
+ Field.new(@template, **options).render(
34
+ object,
35
+ attribute,
36
+ input_id: field_id(attribute)
37
+ ) do |html_opts, opts, error|
38
+ merged_html_opts = merge_html_options(html_options, html_opts, field_theme(:form_select, error: error))
39
+ if html_native
40
+ super(attribute, collection, value_method, text_method, opts, merged_html_opts)
41
+ else
42
+ render_select_dropdown(attribute, opts, merged_html_opts, err: error) do
43
+ collection.map { |item| [item.public_send(text_method), item.public_send(value_method)] }
44
+ end
45
+ end
46
+ end
47
+ end
48
+
49
+ private
50
+
51
+ def render_select_dropdown(attribute, opts, html_opts, err:)
52
+ include_blank = opts.delete(:include_blank)
53
+ prompt = opts.delete(:prompt)
54
+ current_value = opts.delete(:selected) { object.respond_to?(attribute) ? object.public_send(attribute) : nil }
55
+ choices = build_select_dropdown_choices(yield(current_value), include_blank: include_blank, prompt: prompt)
56
+
57
+ combobox_opts = build_select_dropdown_opts(html_opts, current_value)
58
+ render_combobox(attribute, input_id: html_opts[:id], opts: combobox_opts, err: err) do
59
+ Components::Combobox::Dropdown.new(@template).render(
60
+ options: choices,
61
+ value: current_value,
62
+ labelledby: Field.label_id(html_opts[:id])
63
+ )
64
+ end
65
+ end
66
+
67
+ def build_select_dropdown_opts(html_opts, current_value)
68
+ Components::Combobox::Dropdown.default_opts.deep_merge(
69
+ input: { value: current_value },
70
+ trigger: html_opts
71
+ )
72
+ end
73
+
74
+ def build_select_dropdown_choices(choices, include_blank:, prompt:)
75
+ return choices unless include_blank || prompt
76
+
77
+ choices = choices.dup
78
+ choices.unshift([include_blank.is_a?(String) ? include_blank : "", ""]) if include_blank
79
+ choices.unshift(build_select_dropdown_choice_prompt(prompt)) if prompt
80
+ choices
81
+ end
82
+
83
+ def build_select_dropdown_choice_prompt(prompt)
84
+ label = prompt.is_a?(String) ? prompt : I18n.t("helpers.select.prompt", default: "Please select")
85
+ [label, "", { disabled: true }]
86
+ end
87
+ end
88
+ end
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module StimulusPlumbers
4
+ module Form
5
+ module Fields
6
+ module Inputs
7
+ module Submit
8
+ def submit(value = nil, options = {})
9
+ if value.is_a?(Hash)
10
+ options = value
11
+ value = nil
12
+ end
13
+ value ||= submit_default_value
14
+ variant = options.delete(:variant) { :default }
15
+ @template.tag.input(
16
+ type: "submit",
17
+ value: value,
18
+ **merge_html_options(field_theme(:form_submit, variant: variant), options)
19
+ )
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module StimulusPlumbers
4
+ module Form
5
+ module Fields
6
+ module Inputs
7
+ module Text
8
+ FIELD_TYPES = %i[
9
+ color_field
10
+ datetime_local_field
11
+ email_field
12
+ month_field
13
+ number_field
14
+ range_field
15
+ telephone_field
16
+ text_field
17
+ url_field
18
+ week_field
19
+ ].freeze
20
+
21
+ FIELD_TYPES.each do |method_name|
22
+ define_method(method_name) do |attribute, options = {}|
23
+ Field.new(@template, **options).render(
24
+ object,
25
+ attribute,
26
+ input_id: field_id(attribute)
27
+ ) do |html_opts, opts, error|
28
+ html_options = merge_html_options(opts, html_opts, field_theme(:form_input, error: error))
29
+ super(attribute, html_options)
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module StimulusPlumbers
4
+ module Form
5
+ module Fields
6
+ module Inputs
7
+ module TextArea
8
+ def text_area(attribute, options = {})
9
+ Field.new(@template, **options).render(
10
+ object,
11
+ attribute,
12
+ input_id: field_id(attribute)
13
+ ) do |html_opts, opts, error|
14
+ html_options = merge_html_options(opts, html_opts, field_theme(:form_textarea, error: error))
15
+ super(attribute, html_options)
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -4,16 +4,20 @@ module StimulusPlumbers
4
4
  module Form
5
5
  module Fields
6
6
  class Label < Plumber::Base
7
- def render(text:, for_id:, required: false, hidden: false)
8
- klass = theme.resolve(:form_label, required: required, hidden: hidden).fetch(:classes, "")
9
-
10
- inner = text.dup.html_safe
11
- if required
12
- mark_klass = theme.resolve(:form_required_mark).fetch(:classes, "")
13
- inner += template.content_tag(:span, "*", "aria-hidden": "true", class: mark_klass.presence)
7
+ def render(text:, for_id: nil, id: nil, required: false, hidden: false, tag: :label)
8
+ mark_options = required && merge_html_options(
9
+ { aria: { hidden: true } },
10
+ theme.resolve(:form_required_mark)
11
+ )
12
+ html_options = merge_html_options(theme.resolve(:form_label, required: required, hidden: hidden))
13
+ template.content_tag(tag, for: for_id, id: id, **html_options) do
14
+ template.safe_join(
15
+ [
16
+ text,
17
+ mark_options ? template.content_tag(:span, "*", **mark_options) : nil
18
+ ]
19
+ )
14
20
  end
15
-
16
- template.content_tag(:label, inner, for: for_id, class: klass.presence)
17
21
  end
18
22
  end
19
23
  end
@@ -5,7 +5,7 @@ module StimulusPlumbers
5
5
  module CalendarHelper
6
6
  def sp_calendar_month(date: nil, **html_options, &block)
7
7
  if date
8
- html_options.deep_merge!(
8
+ html_options = html_options.deep_merge(
9
9
  data: {
10
10
  "calendar-month-year-value": date.year,
11
11
  "calendar-month-month-value": date.month - 1,
@@ -4,70 +4,63 @@ module StimulusPlumbers
4
4
  module Helpers
5
5
  module ComboboxHelper
6
6
  def sp_combobox_date(label: nil, value: nil, **html_options)
7
+ id = sp_dom_id
7
8
  opts = Components::Combobox::Date.default_opts.deep_merge(
8
9
  input: { value: value },
9
- popover: { content: Components::Combobox::Date.new(self).render(value: value) }
10
+ trigger: { id: id, aria_label: label }.compact
10
11
  )
11
- opts = opts.deep_merge(trigger: { aria_label: label }) if label
12
12
  Components::Combobox.new(self).render(
13
- base_id: sp_dom_id,
14
- options: opts,
15
- data: { input_format_type_value: "date" },
13
+ **opts,
14
+ data: { input_format_type_value: "date" },
16
15
  **html_options
17
- )
16
+ ) do |popover_id|
17
+ Components::Combobox::Date.new(self).render(value: value, popover_id: popover_id)
18
+ end
18
19
  end
19
20
 
20
21
  def sp_combobox_dropdown(label: nil, options: [], value: nil, **html_options)
22
+ id = sp_dom_id
21
23
  opts = Components::Combobox::Dropdown.default_opts.deep_merge(
22
24
  input: { value: value },
23
- popover: { content: Components::Combobox::Dropdown.new(self).render(options: options, value: value, label: label) }
24
- )
25
- opts = opts.deep_merge(trigger: { aria_label: label }) if label
26
- Components::Combobox.new(self).render(
27
- base_id: sp_dom_id,
28
- options: opts,
29
- **html_options
25
+ trigger: { id: id, aria_label: label }.compact
30
26
  )
27
+ Components::Combobox.new(self).render(**opts, **html_options) do
28
+ Components::Combobox::Dropdown.new(self).render(options: options, value: value, label: label)
29
+ end
31
30
  end
32
31
 
33
32
  def sp_combobox_autocomplete(label: nil, options: [], value: nil, url: nil, **html_options)
34
- id = sp_dom_id
35
- popover_id = "#{id}_popover"
36
- opts = Components::Combobox::Autocomplete.default_opts.deep_merge(
33
+ id = sp_dom_id
34
+ opts = Components::Combobox::Autocomplete.default_opts.deep_merge(
37
35
  input: { value: value },
38
- popover: {
39
- content: Components::Combobox::Autocomplete.new(self).render(
40
- options: options,
41
- value: value,
42
- label: label
43
- ),
44
- data: url ? { combobox_dropdown_url_value: url } : {}
45
- }
36
+ trigger: { id: id, aria_label: label }.compact,
37
+ popover: { data: url ? { combobox_dropdown_url_value: url } : {} }
46
38
  )
47
- opts = opts.deep_merge(trigger: { aria_label: label }) if label
48
39
  Components::Combobox.new(self).render(
49
- base_id: id,
50
- options: opts,
51
- data: {
52
- input_combobox_combobox_dropdown_outlet: "##{popover_id}",
40
+ **opts,
41
+ data: {
42
+ input_combobox_combobox_dropdown_outlet: "##{Components::Combobox.popover_id_for(id)}",
53
43
  action: "input->input-combobox#onInput"
54
44
  },
55
45
  **html_options
56
- )
46
+ ) do
47
+ Components::Combobox::Autocomplete.new(self).render(options: options, value: value, label: label)
48
+ end
57
49
  end
58
50
 
59
51
  def sp_combobox_time(format: :h12, label: nil, step: 1, value: nil, **html_options)
52
+ id = sp_dom_id
60
53
  opts = Components::Combobox::Time.default_opts.deep_merge(
61
54
  input: { value: value },
62
- popover: { content: Components::Combobox::Time.new(self).render(format: format, step: step, value: value) }
55
+ trigger: { id: id, aria_label: label }.compact
63
56
  )
64
- opts = opts.deep_merge(trigger: { aria_label: label }) if label
65
57
  Components::Combobox.new(self).render(
66
- base_id: sp_dom_id,
67
- options: opts,
68
- data: { input_format_type_value: "time", input_format_options_value: { format: format }.to_json },
58
+ **opts,
59
+ data: { input_format_type_value: "time", input_format_options_value: { format: format }.to_json },
69
60
  **html_options
70
- )
61
+ ) do
62
+ Components::Combobox::Time.new(self).render(format: format, step: step, value: value)
63
+ end
71
64
  end
72
65
  end
73
66
  end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module StimulusPlumbers
4
+ module Helpers
5
+ module DividerHelper
6
+ def sp_divider(**html_options)
7
+ Components::Divider.new(self).render(**html_options)
8
+ end
9
+ end
10
+ end
11
+ end
@@ -8,6 +8,7 @@ require_relative "helpers/calendar_helper"
8
8
  require_relative "helpers/calendar_turbo_helper"
9
9
  require_relative "helpers/card_helper"
10
10
  require_relative "helpers/combobox_helper"
11
+ require_relative "helpers/divider_helper"
11
12
  require_relative "helpers/popover_helper"
12
13
 
13
14
  module StimulusPlumbers
@@ -20,6 +21,7 @@ module StimulusPlumbers
20
21
  include CalendarTurboHelper
21
22
  include CardHelper
22
23
  include ComboboxHelper
24
+ include DividerHelper
23
25
  include PopoverHelper
24
26
  end
25
27
  end
@@ -13,7 +13,7 @@ module StimulusPlumbers
13
13
  end
14
14
 
15
15
  def theme
16
- StimulusPlumbers.config.theme
16
+ StimulusPlumbers.config.theme.current
17
17
  end
18
18
  end
19
19
  end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module StimulusPlumbers
4
+ module Plumber
5
+ module Dispatcher
6
+ module CallableInspector
7
+ private
8
+
9
+ def args_for(callable)
10
+ callable.arity.negative? ? args : args.take(callable.arity)
11
+ end
12
+
13
+ def accepts_kwargs?(callable)
14
+ callable.parameters.any? { |type, _| %i[key keyreq keyrest].include?(type) }
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module StimulusPlumbers
4
+ module Plumber
5
+ module Dispatcher
6
+ class InstanceExec
7
+ include CallableInspector
8
+
9
+ attr_reader :block, :args, :kwargs
10
+
11
+ def initialize(block, *args, **kwargs)
12
+ @block = block
13
+ @args = args
14
+ @kwargs = kwargs
15
+ validate!
16
+ end
17
+
18
+ def call(target)
19
+ dispatched = args_for(block)
20
+ if accepts_kwargs?(block)
21
+ target.instance_exec(*dispatched, **kwargs, &block)
22
+ else
23
+ target.instance_exec(*dispatched, &block)
24
+ end
25
+ end
26
+
27
+ private
28
+
29
+ def validate!
30
+ raise ArgumentError, "invalid block: #{block.inspect}" unless block.is_a?(Proc)
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module StimulusPlumbers
4
+ module Plumber
5
+ module Dispatcher
6
+ class KlassProxy
7
+ attr_reader :klass, :method_name, :args, :kwargs, :init_args, :init_kwargs
8
+
9
+ def initialize(klass, method_name, *args, init_args: [], init_kwargs: {}, **kwargs)
10
+ @klass = klass
11
+ @method_name = method_name
12
+ @args = args
13
+ @kwargs = kwargs
14
+ @init_args = init_args
15
+ @init_kwargs = init_kwargs
16
+ validate!
17
+ end
18
+
19
+ def call(_target)
20
+ klass.new(*init_args, **init_kwargs).public_send(method_name, *args, **kwargs)
21
+ end
22
+
23
+ private
24
+
25
+ def validate!
26
+ raise ArgumentError, "invalid class: #{klass.inspect}" unless klass.is_a?(Module)
27
+ return if method_name.is_a?(String) || method_name.is_a?(Symbol)
28
+
29
+ raise ArgumentError, "invalid method name: #{method_name.inspect}"
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module StimulusPlumbers
4
+ module Plumber
5
+ module Dispatcher
6
+ class MethodCall
7
+ include CallableInspector
8
+
9
+ attr_reader :method_name, :args, :kwargs
10
+
11
+ def initialize(method_name, *args, **kwargs)
12
+ @method_name = method_name
13
+ @args = args
14
+ @kwargs = kwargs
15
+ validate!
16
+ end
17
+
18
+ def call(target)
19
+ raise NotImplementedError, "#{method_name.inspect} not implemented" unless target.respond_to?(method_name, true)
20
+
21
+ method_call = target.method(method_name)
22
+ dispatched = args_for(method_call)
23
+ accepts_kwargs?(method_call) ? method_call.call(*dispatched, **kwargs) : method_call.call(*dispatched)
24
+ end
25
+
26
+ private
27
+
28
+ def validate!
29
+ return if method_name.is_a?(String) || method_name.is_a?(Symbol)
30
+
31
+ raise ArgumentError, "invalid method name: #{method_name.inspect}"
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -1,96 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # :markup: markdown
3
+ require_relative "dispatcher/callable_inspector"
4
+ require_relative "dispatcher/method_call"
5
+ require_relative "dispatcher/instance_exec"
6
+ require_relative "dispatcher/klass_proxy"
4
7
 
5
8
  module StimulusPlumbers
6
9
  module Plumber
7
10
  module Dispatcher
8
- class MethodCall
9
- attr_reader :method_name, :args, :kwargs
10
-
11
- def initialize(method_name, *args, **kwargs)
12
- @method_name = method_name
13
- @args = args
14
- @kwargs = kwargs
15
- validate!
16
- end
17
-
18
- def call(target)
19
- raise NotImplementedError, "#{method_name.inspect} not implemented" unless target.respond_to?(method_name, true)
20
-
21
- method_call = target.method(method_name)
22
- accepts_args = method_call.arity.negative? ? args : args.take(method_call.arity)
23
- accepts_kwargs = method_call.parameters.any? { |type, _| %i[key keyreq keyrest].include?(type) }
24
- accepts_kwargs ? method_call.call(*accepts_args, **kwargs) : method_call.call(*accepts_args)
25
- end
26
-
27
- private
28
-
29
- def validate!
30
- return if method_name.is_a?(String) || method_name.is_a?(Symbol)
31
-
32
- raise ArgumentError, "invalid method name: #{method_name.inspect}"
33
- end
34
- end
35
-
36
- class InstanceExec
37
- attr_reader :block, :args, :kwargs
38
-
39
- def initialize(block, *args, **kwargs)
40
- @block = block
41
- @args = args
42
- @kwargs = kwargs
43
- validate!
44
- end
45
-
46
- def call(target)
47
- accepts_args = block.arity.negative? ? args : args.take(block.arity)
48
- accepts_kwargs = block.parameters.any? { |type, _| %i[key keyreq keyrest].include?(type) }
49
- if accepts_kwargs
50
- target.instance_exec(
51
- *accepts_args,
52
- **kwargs,
53
- &block
54
- )
55
- else
56
- target.instance_exec(*accepts_args, &block)
57
- end
58
- end
59
-
60
- private
61
-
62
- def validate!
63
- raise ArgumentError, "invalid block: #{block.inspect}" unless block.is_a?(Proc)
64
- end
65
- end
66
-
67
- class KlassProxy
68
- attr_reader :klass, :method_name, :args, :kwargs, :init_args, :init_kwargs
69
-
70
- def initialize(klass, method_name, *args, init_args: [], init_kwargs: {}, **kwargs)
71
- @klass = klass
72
- @method_name = method_name
73
- @args = args
74
- @kwargs = kwargs
75
- @init_args = init_args
76
- @init_kwargs = init_kwargs
77
- validate!
78
- end
79
-
80
- def call(_target)
81
- klass.new(*init_args, **init_kwargs).public_send(method_name, *args, **kwargs)
82
- end
83
-
84
- private
85
-
86
- def validate!
87
- raise ArgumentError, "invalid class: #{klass.inspect}" unless klass.is_a?(Module)
88
- return if method_name.is_a?(String) || method_name.is_a?(Symbol)
89
-
90
- raise ArgumentError, "invalid method name: #{method_name.inspect}"
91
- end
92
- end
93
-
94
11
  def self.build(callable, *args, method_name: nil, init_args: [], init_kwargs: {}, **kwargs)
95
12
  case callable
96
13
  when Symbol
@@ -8,17 +8,18 @@ module StimulusPlumbers
8
8
  extend ActiveSupport::Concern
9
9
 
10
10
  def merge_html_options(*hashes)
11
- classes = hashes.flat_map { |h| [h[:class], h[:classes]] }
12
- data_hashes = hashes.map { |h| h[:data] || {} }
11
+ class_value = merge_string_option(*extract_classes(*hashes)).presence
12
+ merged_data = merge_data_options(*hashes.map { |h| h[:data] || {} })
13
13
  rest = hashes.map { |h| h.except(:class, :classes, :data) }.reduce({}, :deep_merge)
14
14
 
15
- class_value = merge_string_option(*classes).presence
16
- merged_data = merge_data_options(*data_hashes)
17
-
18
15
  result = class_value ? rest.merge(class: class_value) : rest
19
16
  merged_data.present? ? result.merge(data: merged_data) : result
20
17
  end
21
18
 
19
+ def extract_classes(*hashes)
20
+ hashes.flat_map { |h| [h[:class], h[:classes]] }
21
+ end
22
+
22
23
  STIMULUS_SPACEJOIN_KEYS = %i[controller action].freeze
23
24
 
24
25
  def merge_data_options(*hashes, spacejoin: STIMULUS_SPACEJOIN_KEYS)
@@ -12,7 +12,7 @@ module StimulusPlumbers
12
12
  end
13
13
 
14
14
  module ClassMethods
15
- # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
15
+ # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
16
16
  def renders(method_name, with: nil, &block)
17
17
  raise ArgumentError, "method_name must be Symbol" unless method_name.is_a?(Symbol)
18
18
  raise ArgumentError, "provide either with: or a block" if !with.nil? && block_given?
@@ -26,7 +26,7 @@ module StimulusPlumbers
26
26
  self.renderers = renderers.merge(method_name => with)
27
27
  ActiveSupport.version >= "7.2" ? generate_renderer_method(method_name) : eval_renderer_method(method_name)
28
28
  end
29
- # rubocop:enable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
29
+ # rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
30
30
 
31
31
  private
32
32