stimulus_plumbers 0.2.8 → 0.2.9

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 (86) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +21 -0
  3. data/README.md +3 -0
  4. data/app/assets/javascripts/stimulus-plumbers/stimulus-plumbers-controllers.es.js +339 -302
  5. data/app/assets/javascripts/stimulus-plumbers/stimulus-plumbers-controllers.umd.js +1 -1
  6. data/lib/stimulus_plumbers/components/action_list/item.rb +27 -0
  7. data/lib/stimulus_plumbers/components/action_list/section.rb +21 -0
  8. data/lib/stimulus_plumbers/components/action_list.rb +23 -0
  9. data/lib/stimulus_plumbers/components/avatar.rb +72 -0
  10. data/lib/stimulus_plumbers/components/button/group.rb +17 -0
  11. data/lib/stimulus_plumbers/components/button.rb +27 -0
  12. data/lib/stimulus_plumbers/components/calendar/month/turbo/days_of_month.rb +2 -2
  13. data/lib/stimulus_plumbers/components/calendar/month/turbo/days_of_week.rb +2 -2
  14. data/lib/stimulus_plumbers/components/calendar/month/turbo.rb +55 -0
  15. data/lib/stimulus_plumbers/components/calendar.rb +33 -0
  16. data/lib/stimulus_plumbers/components/card/section.rb +25 -0
  17. data/lib/stimulus_plumbers/components/card.rb +27 -0
  18. data/lib/stimulus_plumbers/components/combobox/autocomplete.rb +30 -34
  19. data/lib/stimulus_plumbers/components/combobox/date.rb +16 -18
  20. data/lib/stimulus_plumbers/components/combobox/dropdown.rb +13 -16
  21. data/lib/stimulus_plumbers/components/combobox/options/option.rb +34 -0
  22. data/lib/stimulus_plumbers/components/combobox/options/option_group.rb +29 -0
  23. data/lib/stimulus_plumbers/components/combobox/options.rb +59 -0
  24. data/lib/stimulus_plumbers/components/combobox/popover.rb +20 -0
  25. data/lib/stimulus_plumbers/components/combobox/time/drum.rb +37 -0
  26. data/lib/stimulus_plumbers/components/combobox/time.rb +32 -15
  27. data/lib/stimulus_plumbers/components/combobox/trigger.rb +38 -0
  28. data/lib/stimulus_plumbers/components/combobox.rb +59 -0
  29. data/lib/stimulus_plumbers/components/date_picker/navigator.rb +1 -1
  30. data/lib/stimulus_plumbers/components/icon.rb +49 -0
  31. data/lib/stimulus_plumbers/components/popover/builder.rb +25 -0
  32. data/lib/stimulus_plumbers/components/popover.rb +26 -0
  33. data/lib/stimulus_plumbers/form/builder.rb +7 -5
  34. data/lib/stimulus_plumbers/form/{field_component.rb → field.rb} +1 -1
  35. data/lib/stimulus_plumbers/form/fields/combobox.rb +1 -1
  36. data/lib/stimulus_plumbers/form/fields/error.rb +14 -0
  37. data/lib/stimulus_plumbers/form/fields/group.rb +14 -0
  38. data/lib/stimulus_plumbers/form/fields/hint.rb +14 -0
  39. data/lib/stimulus_plumbers/form/fields/label.rb +21 -0
  40. data/lib/stimulus_plumbers/form/fields/renderer.rb +16 -20
  41. data/lib/stimulus_plumbers/form/fields/search.rb +23 -9
  42. data/lib/stimulus_plumbers/form/fields/submit.rb +23 -0
  43. data/lib/stimulus_plumbers/helpers/action_list_helper.rb +2 -2
  44. data/lib/stimulus_plumbers/helpers/avatar_helper.rb +2 -2
  45. data/lib/stimulus_plumbers/helpers/button_helper.rb +2 -2
  46. data/lib/stimulus_plumbers/helpers/calendar_helper.rb +1 -1
  47. data/lib/stimulus_plumbers/helpers/calendar_turbo_helper.rb +1 -1
  48. data/lib/stimulus_plumbers/helpers/card_helper.rb +2 -2
  49. data/lib/stimulus_plumbers/helpers/combobox_helper.rb +5 -5
  50. data/lib/stimulus_plumbers/helpers/popover_helper.rb +2 -2
  51. data/lib/stimulus_plumbers/plumber/base.rb +20 -0
  52. data/lib/stimulus_plumbers/plumber/dispatcher.rb +111 -0
  53. data/lib/stimulus_plumbers/plumber/html_options.rb +51 -0
  54. data/lib/stimulus_plumbers/plumber/renderer.rb +89 -0
  55. data/lib/stimulus_plumbers/themes/base.rb +9 -15
  56. data/lib/stimulus_plumbers/themes/schema/ranges.rb +5 -5
  57. data/lib/stimulus_plumbers/themes/schema.rb +97 -0
  58. data/lib/stimulus_plumbers/themes/tailwind/calendar.rb +48 -2
  59. data/lib/stimulus_plumbers/themes/tailwind/combobox.rb +75 -0
  60. data/lib/stimulus_plumbers/themes/tailwind_theme.rb +2 -0
  61. data/lib/stimulus_plumbers/version.rb +1 -1
  62. data/lib/stimulus_plumbers.rb +29 -19
  63. metadata +33 -25
  64. data/lib/stimulus_plumbers/components/action_list/renderer.rb +0 -47
  65. data/lib/stimulus_plumbers/components/avatar/renderer.rb +0 -74
  66. data/lib/stimulus_plumbers/components/button/renderer.rb +0 -33
  67. data/lib/stimulus_plumbers/components/calendar/month/turbo/renderer.rb +0 -57
  68. data/lib/stimulus_plumbers/components/calendar/renderer.rb +0 -35
  69. data/lib/stimulus_plumbers/components/card/renderer.rb +0 -41
  70. data/lib/stimulus_plumbers/components/combobox/option.rb +0 -27
  71. data/lib/stimulus_plumbers/components/combobox/option_group.rb +0 -52
  72. data/lib/stimulus_plumbers/components/combobox/renderer.rb +0 -78
  73. data/lib/stimulus_plumbers/components/icon/renderer.rb +0 -51
  74. data/lib/stimulus_plumbers/components/plumber/base.rb +0 -22
  75. data/lib/stimulus_plumbers/components/plumber/dispatcher.rb +0 -113
  76. data/lib/stimulus_plumbers/components/plumber/html_options.rb +0 -53
  77. data/lib/stimulus_plumbers/components/plumber/renderer.rb +0 -91
  78. data/lib/stimulus_plumbers/components/popover/renderer.rb +0 -46
  79. data/lib/stimulus_plumbers/components/time_picker/renderer.rb +0 -38
  80. data/lib/stimulus_plumbers/themes/base/action_list.rb +0 -14
  81. data/lib/stimulus_plumbers/themes/base/avatar.rb +0 -14
  82. data/lib/stimulus_plumbers/themes/base/button.rb +0 -18
  83. data/lib/stimulus_plumbers/themes/base/calendar.rb +0 -15
  84. data/lib/stimulus_plumbers/themes/base/card.rb +0 -12
  85. data/lib/stimulus_plumbers/themes/base/form.rb +0 -34
  86. data/lib/stimulus_plumbers/themes/base/layout.rb +0 -12
@@ -1,74 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module StimulusPlumbers
4
- module Components
5
- module Avatar
6
- class Renderer < Plumber::Base
7
- def avatar(name: nil, initials: nil, url: nil, color: nil, size: :md, **kwargs, &block)
8
- color_css = resolve_color(color, name, initials) unless url || block_given?
9
-
10
- html_options = merge_html_options(
11
- {
12
- classes: [theme.resolve(:avatar, size: size).fetch(:classes, ""), color_css],
13
- "aria-label": name,
14
- role: "img"
15
- },
16
- kwargs
17
- )
18
-
19
- template.content_tag(:span, inner(name, initials, url, &block), **html_options)
20
- end
21
-
22
- private
23
-
24
- def inner(name, initials, url, &block)
25
- if block_given?
26
- template.capture(&block)
27
- elsif url
28
- template.tag.img(src: url, alt: name.present? ? "#{name}'s avatar" : "", onerror: "this.src=''")
29
- elsif initials
30
- initials_svg(initials)
31
- else
32
- fallback_svg
33
- end
34
- end
35
-
36
- def resolve_color(color, name, initials)
37
- if color
38
- theme.avatar_colors.fetch(color, nil)
39
- elsif (seed = name || initials)
40
- theme.avatar_color_range[seed.bytes.reduce(:^) % theme.avatar_color_range.length]
41
- else
42
- theme.avatar_color_range.first
43
- end
44
- end
45
-
46
- def initials_svg(initials)
47
- template.content_tag(:svg, viewBox: "0 0 40 40") do
48
- template.content_tag(
49
- :text,
50
- initials.upcase,
51
- x: "50%",
52
- y: "50%",
53
- dy: "0.35em",
54
- fill: "currentColor",
55
- "font-size": "20",
56
- "text-anchor": "middle"
57
- )
58
- end
59
- end
60
-
61
- def fallback_svg
62
- template.content_tag(:svg, viewBox: "0 0 40 40") do
63
- template.tag.path(
64
- fill: "currentColor",
65
- d: "M8.28 27.5A14.95 14.95 0 0120 21.8c4.76 0 8.97 2.24 11.72 5.7a14.02 " \
66
- "14.02 0 01-8.25 5.91 14.82 14.82 0 01-6.94 0 14.02 14.02 0 01-8.25-5.9z" \
67
- "M13.99 12.78a6.02 6.02 0 1112.03 0 6.02 6.02 0 01-12.03 0z"
68
- )
69
- end
70
- end
71
- end
72
- end
73
- end
74
- end
@@ -1,33 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module StimulusPlumbers
4
- module Components
5
- module Button
6
- class Renderer < Plumber::Base
7
- def button(content = nil, url: nil, external: false, variant: :primary, size: :md, **kwargs, &block)
8
- content = template.capture(&block) if block_given?
9
- html_options = merge_html_options(
10
- { classes: theme.resolve(:button, variant: variant, size: size).fetch(:classes, "") },
11
- kwargs
12
- )
13
-
14
- if url
15
- html_options[:target] = "_blank" if external
16
- template.content_tag(:a, content, href: url, **html_options)
17
- else
18
- html_options[:type] ||= "button"
19
- template.content_tag(:button, content, **html_options)
20
- end
21
- end
22
-
23
- def group(alignment: :left, direction: :row, **kwargs, &block)
24
- html_options = merge_html_options(
25
- { classes: theme.resolve(:button_group, alignment: alignment, direction: direction).fetch(:classes, "") },
26
- kwargs
27
- )
28
- template.content_tag(:div, template.capture(&block), **html_options)
29
- end
30
- end
31
- end
32
- end
33
- end
@@ -1,57 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module StimulusPlumbers
4
- module Components
5
- module Calendar
6
- module Month
7
- module Turbo
8
- class Renderer < Plumber::Base
9
- STIMULUS_CONTROLLER = "calendar-month-observer"
10
-
11
- def render(
12
- date: Date.today,
13
- today: Date.today,
14
- selectable: false,
15
- selected_date: nil,
16
- show_other_months: false,
17
- **kwargs
18
- )
19
- html_options = merge_html_options(
20
- {
21
- classes: theme.resolve(:calendar).fetch(:classes, ""),
22
- data: { controller: STIMULUS_CONTROLLER, action: "click->#{STIMULUS_CONTROLLER}#select" }
23
- },
24
- kwargs
25
- )
26
-
27
- template.content_tag(:div, role: "grid", **html_options) do
28
- template.safe_join(
29
- [
30
- days_of_week,
31
- days_of_month(
32
- date: date,
33
- today: today,
34
- selectable: selectable,
35
- selected_date: selected_date,
36
- show_other_months: show_other_months
37
- )
38
- ]
39
- )
40
- end
41
- end
42
-
43
- private
44
-
45
- def days_of_week(**kwargs)
46
- DaysOfWeek.new(template).render(**kwargs)
47
- end
48
-
49
- def days_of_month(**kwargs)
50
- DaysOfMonth.new(template, **kwargs).render
51
- end
52
- end
53
- end
54
- end
55
- end
56
- end
57
- end
@@ -1,35 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module StimulusPlumbers
4
- module Components
5
- module Calendar
6
- class Renderer < Plumber::Base
7
- STIMULUS_CONTROLLER = "calendar-month"
8
- OBSERVER_STIMULUS_CONTROLLER = "calendar-month-observer"
9
- STIMULUS_DATA = {
10
- controller: "#{STIMULUS_CONTROLLER} #{OBSERVER_STIMULUS_CONTROLLER}",
11
- action: "click->#{OBSERVER_STIMULUS_CONTROLLER}#select"
12
- }.freeze
13
-
14
- def month(**kwargs)
15
- html_options = merge_html_options(
16
- { classes: theme.resolve(:calendar).fetch(:classes, ""), data: STIMULUS_DATA },
17
- kwargs
18
- )
19
-
20
- template.content_tag(:div, role: "grid", **html_options) do
21
- template.safe_join(
22
- [
23
- template.tag.div(data: { "#{STIMULUS_CONTROLLER}-target" => "daysOfWeek" }),
24
- template.tag.div(
25
- role: "rowgroup",
26
- data: { "#{STIMULUS_CONTROLLER}-target" => "daysOfMonth" }
27
- )
28
- ]
29
- )
30
- end
31
- end
32
- end
33
- end
34
- end
35
- end
@@ -1,41 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module StimulusPlumbers
4
- module Components
5
- module Card
6
- class Renderer < Plumber::Base
7
- def card(title: nil, **kwargs, &block)
8
- html_options = merge_html_options(
9
- { classes: theme.resolve(:card).fetch(:classes, "") },
10
- kwargs
11
- )
12
-
13
- template.content_tag(:div, **html_options) do
14
- template.safe_join(
15
- [
16
- (template.content_tag(:h2, title) if title.present?),
17
- template.capture(&block)
18
- ].compact
19
- )
20
- end
21
- end
22
-
23
- def section(title: nil, **kwargs, &block)
24
- html_options = merge_html_options(
25
- { classes: theme.resolve(:card_section).fetch(:classes, "") },
26
- kwargs
27
- )
28
-
29
- template.content_tag(:div, **html_options) do
30
- template.safe_join(
31
- [
32
- (template.content_tag(:h3, title) if title.present?),
33
- template.capture(&block)
34
- ].compact
35
- )
36
- end
37
- end
38
- end
39
- end
40
- end
41
- end
@@ -1,27 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module StimulusPlumbers
4
- module Components
5
- module Combobox
6
- class Option < Plumber::Base
7
- def render(label:, value:, description: nil, disabled: false, selected: false)
8
- aria = { selected: selected ? "true" : "false" }
9
- aria[:disabled] = "true" if disabled
10
-
11
- template.content_tag(:li, role: "option", aria: aria, data: { value: value }) do
12
- if description
13
- template.safe_join(
14
- [
15
- template.content_tag(:span, label),
16
- template.content_tag(:span, description)
17
- ]
18
- )
19
- else
20
- label
21
- end
22
- end
23
- end
24
- end
25
- end
26
- end
27
- end
@@ -1,52 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module StimulusPlumbers
4
- module Components
5
- module Combobox
6
- module OptionGroup
7
- private
8
-
9
- def render_items(items, value: nil)
10
- @selected_value = value.to_s
11
- template.safe_join(items.map { |item| render_item(item) })
12
- end
13
-
14
- def render_item(item)
15
- case item
16
- when Hash
17
- item.key?(:options) ? render_group(item[:label], item[:options]) : render_option_hash(item)
18
- else
19
- render_option(item[0], item[1].to_s, item[2] || {})
20
- end
21
- end
22
-
23
- def render_option_hash(item)
24
- render_option(item[:label], item[:value].to_s, item.except(:label, :value))
25
- end
26
-
27
- def render_option(label, value, attrs = {})
28
- Option.new(template).render(
29
- label: label,
30
- value: value,
31
- selected: @selected_value == value,
32
- disabled: attrs[:disabled] || false,
33
- description: attrs[:description]
34
- )
35
- end
36
-
37
- def render_group(label, options)
38
- template.content_tag(:li, role: "group", aria: { label: label }) do
39
- template.safe_join(
40
- [
41
- template.content_tag(:span, label, aria: { hidden: "true" }),
42
- template.content_tag(:ul) do
43
- template.safe_join(options.map { |opt| render_item(opt) })
44
- end
45
- ]
46
- )
47
- end
48
- end
49
- end
50
- end
51
- end
52
- end
@@ -1,78 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module StimulusPlumbers
4
- module Components
5
- module Combobox
6
- class Renderer < Plumber::Base
7
- STIMULUS_CONTROLLER = "input-combobox"
8
- FORMAT_CONTROLLER = "input-format"
9
- FORMAT_ACTION = "input-combobox:changed->input-format#format"
10
-
11
- def render(base_id:, options: {}, **kwargs)
12
- popover_id = "#{base_id}_popover"
13
- initial_value = options.dig(:input, :value)
14
-
15
- base_data = {
16
- controller: "#{STIMULUS_CONTROLLER} #{FORMAT_CONTROLLER}",
17
- action: FORMAT_ACTION
18
- }
19
- base_data[:input_combobox_value_value] = initial_value if initial_value.present?
20
-
21
- html_options = merge_html_options({ data: base_data }, kwargs)
22
-
23
- template.content_tag(:div, **html_options) do
24
- template.safe_join(
25
- [
26
- trigger_input(
27
- popover_id,
28
- options.dig(:popover, :haspopup) || options.dig(:popover, :role) || "dialog",
29
- options.fetch(:trigger, {})
30
- ),
31
- hidden_input(options.fetch(:input, {})),
32
- popover_element(popover_id, options.fetch(:popover, {}))
33
- ]
34
- )
35
- end
36
- end
37
-
38
- private
39
-
40
- def trigger_input(popover_id, haspopup, opts)
41
- base_data = {
42
- "#{STIMULUS_CONTROLLER}_target": "trigger",
43
- input_format_target: "input",
44
- action: "focus->#{STIMULUS_CONTROLLER}#open keydown.esc->#{STIMULUS_CONTROLLER}#close"
45
- }
46
- data = merge_data_options(base_data, opts.fetch(:data, {}).symbolize_keys)
47
-
48
- aria = { haspopup: haspopup, expanded: "false", controls: popover_id }
49
- aria[:autocomplete] = opts[:aria_autocomplete] if opts[:aria_autocomplete]
50
- aria[:label] = opts[:aria_label] if opts[:aria_label]
51
-
52
- template.tag.input(
53
- type: "text",
54
- readonly: (opts.fetch(:readonly, true) ? true : nil),
55
- role: "combobox",
56
- aria: aria,
57
- data: data
58
- )
59
- end
60
-
61
- def hidden_input(opts)
62
- data = { "#{STIMULUS_CONTROLLER}_target": "value" }.merge(opts.fetch(:data, {}))
63
- template.tag.input(type: "hidden", name: opts[:name], value: opts[:value], data: data)
64
- end
65
-
66
- def popover_element(popover_id, opts)
67
- base_data = { "#{STIMULUS_CONTROLLER}_target": "popover" }
68
- data = merge_data_options(base_data, (opts[:data] || {}).symbolize_keys)
69
-
70
- attrs = { id: popover_id, hidden: "", data: data }
71
- attrs[:role] = opts[:role] if opts[:role]
72
- attrs[:aria] = { label: opts[:label] } if opts[:label]
73
- template.content_tag(opts.fetch(:tag, :div), **attrs) { opts[:content] }
74
- end
75
- end
76
- end
77
- end
78
- end
@@ -1,51 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module StimulusPlumbers
4
- module Components
5
- module Icon
6
- class Renderer < Plumber::Base
7
- ICONS = {
8
- "arrow-left" => "M10.5 19.5 3 12m0 0 7.5-7.5M3 12h18",
9
- "arrow-right" => "M13.5 4.5 21 12m0 0-7.5 7.5M21 12H3",
10
- "arrow-up" => "M4.5 10.5 12 3m0 0 7.5 7.5M12 3v18",
11
- "arrow-down" => "M19.5 13.5 12 21m0 0-7.5-7.5M12 21V3"
12
- }.freeze
13
-
14
- def icon(name:, **kwargs)
15
- html_options = merge_html_options(
16
- { classes: theme.resolve(:icon).fetch(:classes, "") },
17
- kwargs
18
- )
19
-
20
- if ICONS[name]
21
- svg_icon(ICONS[name], html_options)
22
- else
23
- template.content_tag(:span, nil, **html_options)
24
- end
25
- end
26
-
27
- private
28
-
29
- def svg_icon(path, html_options)
30
- template.content_tag(
31
- :svg,
32
- xmlns: "http://www.w3.org/2000/svg",
33
- fill: "none",
34
- viewBox: "0 0 24 24",
35
- width: "24",
36
- height: "24",
37
- "stroke-width": "1.5",
38
- stroke: "currentColor",
39
- **html_options
40
- ) do
41
- template.tag.path(
42
- "stroke-linecap": "round",
43
- "stroke-linejoin": "round",
44
- d: path
45
- )
46
- end
47
- end
48
- end
49
- end
50
- end
51
- end
@@ -1,22 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module StimulusPlumbers
4
- module Components
5
- module Plumber
6
- class Base
7
- include HtmlOptions
8
- include Renderer
9
-
10
- attr_reader :template
11
-
12
- def initialize(template)
13
- @template = template
14
- end
15
-
16
- def theme
17
- StimulusPlumbers.config.theme
18
- end
19
- end
20
- end
21
- end
22
- end
@@ -1,113 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- # :markup: markdown
4
-
5
- module StimulusPlumbers
6
- module Components
7
- module Plumber
8
- module Dispatcher
9
- class MethodCall
10
- attr_reader :method_name, :args, :kwargs
11
-
12
- def initialize(method_name, *args, **kwargs)
13
- @method_name = method_name
14
- @args = args
15
- @kwargs = kwargs
16
- validate!
17
- end
18
-
19
- def call(target)
20
- raise NotImplementedError, "#{method_name.inspect} not implemented" unless target.respond_to?(method_name, true)
21
-
22
- method_call = target.method(method_name)
23
- accepts_args = method_call.arity.negative? ? args : args.take(method_call.arity)
24
- accepts_kwargs = method_call.parameters.any? { |type, _| %i[key keyreq keyrest].include?(type) }
25
- accepts_kwargs ? method_call.call(*accepts_args, **kwargs) : method_call.call(*accepts_args)
26
- end
27
-
28
- private
29
-
30
- def validate!
31
- return if method_name.is_a?(String) || method_name.is_a?(Symbol)
32
-
33
- raise ArgumentError, "invalid method name: #{method_name.inspect}"
34
- end
35
- end
36
-
37
- class InstanceExec
38
- attr_reader :block, :args, :kwargs
39
-
40
- def initialize(block, *args, **kwargs)
41
- @block = block
42
- @args = args
43
- @kwargs = kwargs
44
- validate!
45
- end
46
-
47
- def call(target)
48
- accepts_args = block.arity.negative? ? args : args.take(block.arity)
49
- accepts_kwargs = block.parameters.any? { |type, _| %i[key keyreq keyrest].include?(type) }
50
- if accepts_kwargs
51
- target.instance_exec(
52
- *accepts_args,
53
- **kwargs,
54
- &block
55
- )
56
- else
57
- target.instance_exec(*accepts_args, &block)
58
- end
59
- end
60
-
61
- private
62
-
63
- def validate!
64
- raise ArgumentError, "invalid block: #{block.inspect}" unless block.is_a?(Proc)
65
- end
66
- end
67
-
68
- class KlassProxy
69
- attr_reader :klass, :method_name, :args, :kwargs, :init_args, :init_kwargs
70
-
71
- def initialize(klass, method_name, *args, init_args: [], init_kwargs: {}, **kwargs)
72
- @klass = klass
73
- @method_name = method_name
74
- @args = args
75
- @kwargs = kwargs
76
- @init_args = init_args
77
- @init_kwargs = init_kwargs
78
- validate!
79
- end
80
-
81
- def call(_target)
82
- klass.new(*init_args, **init_kwargs).public_send(method_name, *args, **kwargs)
83
- end
84
-
85
- private
86
-
87
- def validate!
88
- raise ArgumentError, "invalid class: #{klass.inspect}" unless klass.is_a?(Module)
89
- return if method_name.is_a?(String) || method_name.is_a?(Symbol)
90
-
91
- raise ArgumentError, "invalid method name: #{method_name.inspect}"
92
- end
93
- end
94
-
95
- def self.build(callable, *args, method_name: nil, init_args: [], init_kwargs: {}, **kwargs)
96
- case callable
97
- when Symbol
98
- MethodCall.new(callable, *args, **kwargs)
99
- when Proc
100
- InstanceExec.new(callable, *args, **kwargs)
101
- when Module
102
- KlassProxy.new(callable, method_name, *args, init_args: init_args, init_kwargs: init_kwargs, **kwargs)
103
- when String
104
- klass = callable.safe_constantize
105
- raise ArgumentError, "could not resolve class from: #{callable.inspect}" unless klass
106
-
107
- KlassProxy.new(klass, method_name, *args, init_args: init_args, init_kwargs: init_kwargs, **kwargs)
108
- end
109
- end
110
- end
111
- end
112
- end
113
- end
@@ -1,53 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "active_support/concern"
4
-
5
- module StimulusPlumbers
6
- module Components
7
- module Plumber
8
- module HtmlOptions
9
- extend ActiveSupport::Concern
10
-
11
- def merge_html_options(*hashes)
12
- classes = hashes.flat_map { |h| [h[:class], h[:classes]] }
13
- data_hashes = hashes.map { |h| h[:data] || {} }
14
- rest = hashes.map { |h| h.except(:class, :classes, :data) }.reduce({}, :deep_merge)
15
-
16
- class_value = merge_string_option(*classes).presence
17
- merged_data = merge_data_options(*data_hashes)
18
-
19
- result = class_value ? rest.merge(class: class_value) : rest
20
- merged_data.present? ? result.merge(data: merged_data) : result
21
- end
22
-
23
- STIMULUS_SPACEJOIN_KEYS = %i[controller action].freeze
24
-
25
- def merge_data_options(*hashes, spacejoin: STIMULUS_SPACEJOIN_KEYS)
26
- hashes.reduce({}) do |acc, d|
27
- acc.merge(d) do |key, old_val, new_val|
28
- if spacejoin.include?(key.to_sym)
29
- merge_string_option(old_val, new_val).presence || new_val
30
- else
31
- new_val
32
- end
33
- end
34
- end
35
- end
36
-
37
- def merge_string_option(*parts, delimiter: " ")
38
- tokens = parts.flat_map { |part| normalize_part(part, delimiter) }
39
- tokens.compact.uniq.join(delimiter)
40
- end
41
-
42
- def normalize_part(value, delimiter)
43
- case value
44
- when String then value.present? ? value.split(delimiter) : []
45
- when Hash then value.filter_map { |key, val| key if val }
46
- when Array then [merge_string_option(*value).presence]
47
- else []
48
- end
49
- end
50
- end
51
- end
52
- end
53
- end