stimulus_plumbers 0.4.11 → 0.4.13

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 (50) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +19 -0
  3. data/app/assets/javascripts/stimulus-plumbers/controllers.manifest.json +31 -9
  4. data/app/assets/javascripts/stimulus-plumbers/index.es.js +87 -96
  5. data/app/assets/javascripts/stimulus-plumbers/index.es.js.map +1 -1
  6. data/app/assets/javascripts/stimulus-plumbers/index.umd.js +1 -1
  7. data/app/assets/javascripts/stimulus-plumbers/index.umd.js.map +1 -1
  8. data/config/locales/en.yml +1 -0
  9. data/docs/architecture.md +1 -1
  10. data/docs/component/form.md +42 -20
  11. data/docs/component/icon.md +9 -9
  12. data/docs/guide.md +2 -1
  13. data/lib/stimulus_plumbers/components/button/icon_layout.rb +34 -0
  14. data/lib/stimulus_plumbers/components/button.rb +2 -23
  15. data/lib/stimulus_plumbers/components/card.rb +1 -1
  16. data/lib/stimulus_plumbers/components/combobox/builder.rb +52 -14
  17. data/lib/stimulus_plumbers/components/combobox/date.rb +35 -9
  18. data/lib/stimulus_plumbers/components/combobox/dropdown.rb +22 -6
  19. data/lib/stimulus_plumbers/components/combobox/time.rb +18 -5
  20. data/lib/stimulus_plumbers/components/combobox/trigger.rb +1 -1
  21. data/lib/stimulus_plumbers/components/combobox/typeahead.rb +23 -8
  22. data/lib/stimulus_plumbers/components/icon.rb +5 -3
  23. data/lib/stimulus_plumbers/components/indicator.rb +3 -1
  24. data/lib/stimulus_plumbers/components/link.rb +1 -1
  25. data/lib/stimulus_plumbers/components/list/item.rb +1 -1
  26. data/lib/stimulus_plumbers/components/ordered_list/item.rb +2 -2
  27. data/lib/stimulus_plumbers/components/popover.rb +7 -3
  28. data/lib/stimulus_plumbers/components/progress_bar.rb +3 -1
  29. data/lib/stimulus_plumbers/components/progress_meter.rb +3 -1
  30. data/lib/stimulus_plumbers/components/progress_ring.rb +4 -2
  31. data/lib/stimulus_plumbers/components/timeline/event.rb +5 -3
  32. data/lib/stimulus_plumbers/form/field.rb +4 -2
  33. data/lib/stimulus_plumbers/form/fields/inputs/code.rb +93 -32
  34. data/lib/stimulus_plumbers/form/fields/inputs/credit_card.rb +61 -33
  35. data/lib/stimulus_plumbers/form/fields/inputs/password.rb +37 -12
  36. data/lib/stimulus_plumbers/form/fields/inputs/search.rb +1 -1
  37. data/lib/stimulus_plumbers/form/fields/inputs/submit.rb +52 -7
  38. data/lib/stimulus_plumbers/generators/css_entrypoint.rb +10 -0
  39. data/lib/stimulus_plumbers/helpers/icon_helper.rb +2 -2
  40. data/lib/stimulus_plumbers/plumber/dispatcher.rb +14 -12
  41. data/lib/stimulus_plumbers/plumber/slots.rb +8 -8
  42. data/lib/stimulus_plumbers/themes/schema.rb +4 -0
  43. data/lib/stimulus_plumbers/version.rb +1 -1
  44. data/lib/stimulus_plumbers.rb +1 -0
  45. data/vendor/ARIA.md +3 -2
  46. data/vendor/component/manifest.json +8 -1
  47. data/vendor/controller/docs/input-formatter.md +18 -39
  48. data/vendor/controller/docs/input-revealable.md +65 -0
  49. data/vendor/controller/manifest.json +31 -9
  50. metadata +3 -1
@@ -6,8 +6,10 @@ module StimulusPlumbers
6
6
  class Event < Plumber::Base
7
7
  attr_reader :interactive
8
8
 
9
- def self.detail_id_for(event_id)
10
- [event_id, "detail"].compact.join("_")
9
+ class << self
10
+ def detail_id_for(event_id)
11
+ [event_id, "detail"].compact.join("_")
12
+ end
11
13
  end
12
14
 
13
15
  def render(datetime: nil, id: nil, interactive: false, orientation: :vertical, **kwargs, &block)
@@ -106,7 +108,7 @@ module StimulusPlumbers
106
108
  def render_indicator_content(type:, icon_name:)
107
109
  if type == :icon && icon_name
108
110
  Components::Icon.new(template).render(
109
- name: icon_name,
111
+ icon_name,
110
112
  size: :sm,
111
113
  classes: theme.resolve(:timeline_item_indicator_icon_slot).fetch(:classes, ""),
112
114
  aria: { hidden: "true" }
@@ -15,8 +15,10 @@ module StimulusPlumbers
15
15
 
16
16
  attr_reader :hide_label
17
17
 
18
- def self.label_id(input_id)
19
- [input_id, "label"].compact.join("_")
18
+ class << self
19
+ def label_id(input_id)
20
+ [input_id, "label"].compact.join("_")
21
+ end
20
22
  end
21
23
 
22
24
  def initialize(template, hide_label: false, **kwargs)
@@ -5,64 +5,124 @@ module StimulusPlumbers
5
5
  module Fields
6
6
  module Inputs
7
7
  module Code
8
+ STIMULUS_CONTROLLER = "input-formatter"
9
+ STIMULUS_ACTION = [
10
+ "input->#{STIMULUS_CONTROLLER}#onInput",
11
+ "focus->#{STIMULUS_CONTROLLER}#onFocus",
12
+ "blur->#{STIMULUS_CONTROLLER}#onBlur"
13
+ ].join(" ").freeze
14
+ DEFAULT_CHARSET = :digits
15
+ DEFAULT_AUTOCOMPLETE = "one-time-code"
16
+ DEFAULT_SEPARATOR = nil
17
+
8
18
  private
9
19
 
10
- def render_code_input(attribute, html_opts, opts, error, length:, charset: :digits, groups: [], **kwargs)
11
- floating = kwargs.delete(:floating)
20
+ def render_code_input(
21
+ attribute,
22
+ html_opts,
23
+ opts,
24
+ error,
25
+ length: nil,
26
+ charset: DEFAULT_CHARSET,
27
+ groups: [],
28
+ separator: DEFAULT_SEPARATOR,
29
+ floating: nil,
30
+ **kwargs
31
+ )
12
32
  raise ArgumentError, "floating labels are not supported for code fields" if floating.present?
13
33
 
14
34
  validate_code_options!(length, groups)
15
- inputmode = kwargs.delete(:inputmode) || (charset.to_sym == :digits ? "numeric" : nil)
16
- autocomplete = kwargs.delete(:autocomplete) || "one-time-code"
17
- config = { error: error, length: length, charset: charset, groups: groups,
18
- autocomplete: autocomplete, inputmode: inputmode, kwargs: kwargs
19
- }
35
+ config = {
36
+ error: error,
37
+ length: length,
38
+ charset: charset,
39
+ groups: groups,
40
+ separator: separator,
41
+ autocomplete: kwargs.delete(:autocomplete) || DEFAULT_AUTOCOMPLETE,
42
+ inputmode: kwargs.delete(:inputmode) || (charset.to_sym == DEFAULT_CHARSET ? "numeric" : nil),
43
+ kwargs: kwargs
44
+ }
20
45
  render_code_field(attribute, html_opts, opts, config)
21
46
  end
22
47
 
23
48
  def render_code_field(attribute, html_opts, opts, config)
24
- @template.content_tag(:div, **code_wrapper_options(config)) do
49
+ overlay_options = merge_html_options(
50
+ theme.resolve(:form_field_input_code_overlay, error: config[:error]),
51
+ opts,
52
+ html_opts
53
+ )
54
+ wrapper_options = merge_html_options(
55
+ theme.resolve(:form_field_input_code, error: config[:error]),
56
+ code_options(config)
57
+ )
58
+ @template.content_tag(:div, **wrapper_options) do
25
59
  @template.safe_join(
26
- [render_code_cells(config), code_input(attribute, html_opts, opts, config)]
60
+ [render_code_cells(config), code_input(attribute, overlay_options, config)]
27
61
  )
28
62
  end
29
63
  end
30
64
 
31
- def code_wrapper_options(config)
32
- merge_html_options(
33
- theme.resolve(:form_field_input_code, error: config[:error]),
34
- { data: {
35
- controller: "input-formatter",
65
+ def code_options(config)
66
+ {
67
+ data: {
68
+ controller: STIMULUS_CONTROLLER,
36
69
  input_formatter_format_value: "code",
37
- input_formatter_options_value: { charset: config[:charset].to_s, length: config[:length] }.to_json,
38
- input_formatter_groups_value: config[:groups].presence
70
+ input_formatter_options_value: { charset: config[:charset].to_s, length: config[:length] },
71
+ input_formatter_groups_value: code_groups_value(config)
39
72
  }
40
- }
41
- )
73
+ }
74
+ end
75
+
76
+ # Server-rendered separators already mark the group breaks. Passing groups as well
77
+ # would stamp data-group-end and lay the themed gap on top of the separator.
78
+ def code_groups_value(config)
79
+ return if config[:separator].present?
80
+
81
+ config[:groups].presence
42
82
  end
43
83
 
44
- def code_input(attribute, html_opts, opts, config)
84
+ def code_input(attribute, html_opts, config)
45
85
  input_options = merge_html_options(
46
- theme.resolve(:form_field_input_code_overlay, error: config[:error]),
47
- opts,
48
86
  html_opts,
49
87
  config[:kwargs],
50
- { maxlength: config[:length], autocomplete: config[:autocomplete], inputmode: config[:inputmode],
51
- data: { input_formatter_target: "input", action: formatter_actions }
52
- }
88
+ {
89
+ maxlength: config[:length],
90
+ autocomplete: config[:autocomplete],
91
+ inputmode: config[:inputmode],
92
+ data: { input_formatter_target: "input", action: STIMULUS_ACTION }
93
+ }
53
94
  )
54
95
  @template.text_field(@object_name, attribute, objectify_options(input_options))
55
96
  end
56
97
 
57
98
  def render_code_cells(config)
58
- count = config[:groups].present? ? config[:groups].sum : config[:length]
99
+ @template.content_tag(:div, **merge_html_options(theme.resolve(:form_field_input_code_cells))) do
100
+ @template.safe_join(code_cells(config))
101
+ end
102
+ end
103
+
104
+ def code_cells(config)
105
+ groups = config[:groups].presence || [config[:length]]
106
+ groups.flat_map.with_index do |width, index|
107
+ cells = Array.new(width) { render_code_cell(config) }
108
+ index.zero? || config[:separator].blank? ? cells : [render_code_separator(config), *cells]
109
+ end
110
+ end
111
+
112
+ def render_code_cell(config)
59
113
  cell_options = merge_html_options(
60
114
  theme.resolve(:form_field_input_code_cell, error: config[:error]),
61
115
  { data: { input_formatter_target: "cell" } }
62
116
  )
63
- @template.content_tag(:div, **merge_html_options(theme.resolve(:form_field_input_code_cells))) do
64
- @template.safe_join(Array.new(count) { @template.content_tag(:span, nil, **cell_options) })
65
- end
117
+ @template.content_tag(:span, nil, **cell_options)
118
+ end
119
+
120
+ def render_code_separator(config)
121
+ separator_options = merge_html_options(
122
+ theme.resolve(:form_field_input_code_separator),
123
+ { aria: { hidden: true } }
124
+ )
125
+ @template.content_tag(:span, config[:separator], **separator_options)
66
126
  end
67
127
 
68
128
  def validate_code_options!(length, groups)
@@ -73,11 +133,12 @@ module StimulusPlumbers
73
133
  raise ArgumentError, "groups must add up to length" unless groups.sum == length
74
134
  end
75
135
 
76
- def valid_groups?(groups) = groups.is_a?(Array) && groups.all? { |group| positive_integer?(group) }
77
- def positive_integer?(value) = value.is_a?(Integer) && value.positive?
136
+ def valid_groups?(groups)
137
+ groups.is_a?(Array) && groups.all? { |group| positive_integer?(group) }
138
+ end
78
139
 
79
- def formatter_actions
80
- "input->input-formatter#onInput focus->input-formatter#onFocus blur->input-formatter#onBlur"
140
+ def positive_integer?(value)
141
+ value.is_a?(Integer) && value.positive?
81
142
  end
82
143
  end
83
144
  end
@@ -5,64 +5,96 @@ module StimulusPlumbers
5
5
  module Fields
6
6
  module Inputs
7
7
  module CreditCard
8
- DEFAULT_GROUPS = [4, 4, 4, 4].freeze
8
+ STIMULUS_CONTROLLER = "input-formatter"
9
+ STIMULUS_ACTION = [
10
+ "input->#{STIMULUS_CONTROLLER}#onInput",
11
+ "focus->#{STIMULUS_CONTROLLER}#onFocus",
12
+ "blur->#{STIMULUS_CONTROLLER}#onBlur"
13
+ ].join(" ").freeze
14
+ DEFAULT_GROUPS = [4, 4, 4, 4].freeze
15
+ DEFAULT_AUTOCOMPLETE = "cc-number"
16
+ DEFAULT_INPUTMODE = "numeric"
17
+ DEFAULT_SEPARATOR = nil
9
18
 
10
19
  private
11
20
 
12
- def render_credit_card_input(attribute, html_opts, opts, error, groups: DEFAULT_GROUPS, floating: nil, **kwargs)
21
+ def render_credit_card_input(
22
+ attribute,
23
+ html_opts,
24
+ opts,
25
+ error,
26
+ groups: DEFAULT_GROUPS,
27
+ separator: DEFAULT_SEPARATOR,
28
+ floating: nil,
29
+ **kwargs
30
+ )
13
31
  raise ArgumentError, "floating labels are not supported for credit card fields" if floating.present?
14
32
 
15
33
  normalized_groups = groups.presence || DEFAULT_GROUPS
16
- inputmode = kwargs.delete(:inputmode) || "numeric"
17
- autocomplete = kwargs.delete(:autocomplete) || "cc-number"
18
34
  validate_credit_card_groups!(normalized_groups)
19
- length = normalized_groups.sum
20
- config = { error: error, groups: normalized_groups, length: length,
21
- autocomplete: autocomplete, inputmode: inputmode, kwargs: kwargs
22
- }
35
+ config = {
36
+ error: error,
37
+ groups: normalized_groups,
38
+ length: normalized_groups.sum,
39
+ separator: separator,
40
+ autocomplete: kwargs.delete(:autocomplete) || DEFAULT_AUTOCOMPLETE,
41
+ inputmode: kwargs.delete(:inputmode) || DEFAULT_INPUTMODE,
42
+ kwargs: kwargs
43
+ }
23
44
  render_credit_card_field(attribute, html_opts, opts, config)
24
45
  end
25
46
 
26
47
  def render_credit_card_field(attribute, html_opts, opts, config)
27
- @template.content_tag(:div, **credit_card_wrapper_options(config)) do
48
+ overlay_options = merge_html_options(
49
+ theme.resolve(:form_field_input_credit_card_overlay, error: config[:error]),
50
+ opts,
51
+ html_opts
52
+ )
53
+ wrapper_options = merge_html_options(
54
+ theme.resolve(:form_field_input_credit_card, error: config[:error]),
55
+ credit_card_options(config)
56
+ )
57
+ @template.content_tag(:div, **wrapper_options) do
28
58
  @template.safe_join(
29
- [render_credit_card_cells(config), credit_card_input(attribute, html_opts, opts, config)]
59
+ [render_credit_card_cells(config), credit_card_input(attribute, overlay_options, config)]
30
60
  )
31
61
  end
32
62
  end
33
63
 
34
- def credit_card_wrapper_options(config)
35
- merge_html_options(
36
- theme.resolve(:form_field_input_credit_card, error: config[:error]),
37
- { data: {
38
- controller: "input-formatter",
64
+ def credit_card_options(config)
65
+ {
66
+ data: {
67
+ controller: STIMULUS_CONTROLLER,
39
68
  input_formatter_format_value: "creditCard",
40
69
  input_formatter_groups_value: config[:groups]
41
70
  }
42
- }
43
- )
71
+ }
44
72
  end
45
73
 
46
- def credit_card_input(attribute, html_opts, opts, config)
74
+ def credit_card_input(attribute, html_opts, config)
47
75
  input_options = merge_html_options(
48
- theme.resolve(:form_field_input_credit_card_overlay, error: config[:error]),
49
- opts,
50
76
  html_opts,
51
77
  config[:kwargs],
52
- { maxlength: config[:length], autocomplete: config[:autocomplete], inputmode: config[:inputmode],
53
- data: { input_formatter_target: "input", action: formatter_actions }
54
- }
78
+ {
79
+ maxlength: config[:length],
80
+ autocomplete: config[:autocomplete],
81
+ inputmode: config[:inputmode],
82
+ data: { input_formatter_target: "input", action: STIMULUS_ACTION }
83
+ }
55
84
  )
56
85
  @template.text_field(@object_name, attribute, objectify_options(input_options))
57
86
  end
58
87
 
59
88
  def render_credit_card_cells(config)
60
- cells = config[:groups].each_index.flat_map do |index|
61
- index.zero? ? [render_credit_card_cell(config)] : [render_credit_card_separator, render_credit_card_cell(config)]
89
+ @template.content_tag(:div, **merge_html_options(theme.resolve(:form_field_input_credit_card_cells))) do
90
+ @template.safe_join(credit_card_cells(config))
62
91
  end
92
+ end
63
93
 
64
- @template.content_tag(:div, **merge_html_options(theme.resolve(:form_field_input_credit_card_cells))) do
65
- @template.safe_join(cells)
94
+ def credit_card_cells(config)
95
+ config[:groups].each_index.flat_map do |index|
96
+ cell = render_credit_card_cell(config)
97
+ index.zero? || config[:separator].blank? ? [cell] : [render_credit_card_separator(config), cell]
66
98
  end
67
99
  end
68
100
 
@@ -74,12 +106,12 @@ module StimulusPlumbers
74
106
  @template.content_tag(:span, nil, **cell_options)
75
107
  end
76
108
 
77
- def render_credit_card_separator
109
+ def render_credit_card_separator(config)
78
110
  separator_options = merge_html_options(
79
111
  theme.resolve(:form_field_input_credit_card_separator),
80
112
  { aria: { hidden: true } }
81
113
  )
82
- @template.content_tag(:span, "-", **separator_options)
114
+ @template.content_tag(:span, config[:separator], **separator_options)
83
115
  end
84
116
 
85
117
  def validate_credit_card_groups!(groups)
@@ -87,10 +119,6 @@ module StimulusPlumbers
87
119
 
88
120
  raise ArgumentError, "groups must be an array of positive integers"
89
121
  end
90
-
91
- def formatter_actions
92
- "input->input-formatter#onInput focus->input-formatter#onFocus blur->input-formatter#onBlur"
93
- end
94
122
  end
95
123
  end
96
124
  end
@@ -5,11 +5,19 @@ module StimulusPlumbers
5
5
  module Fields
6
6
  module Inputs
7
7
  module Password
8
+ STIMULUS_CONTROLLER = "input-revealable"
9
+ STIMULUS_ACTION = "click->#{STIMULUS_CONTROLLER}#toggle".freeze
10
+ DEFAULT_AUTOCOMPLETE = "current-password"
11
+
8
12
  def password_field(attribute, floating: nil, revealable: false, **options)
9
- html_options = merge_html_options(theme.resolve(:form_field_input, floating: floating), options)
13
+ html_options = merge_html_options(
14
+ theme.resolve(:form_field_input, floating: floating),
15
+ options,
16
+ { autocomplete: options.delete(:autocomplete) || DEFAULT_AUTOCOMPLETE }
17
+ )
10
18
  if revealable
11
19
  render_revealable_password(false) do
12
- super(attribute, merge_html_options(html_options, { data: { input_formatter_target: "input" } }))
20
+ super(attribute, merge_html_options(html_options, { data: { input_revealable_target: "input" } }))
13
21
  end
14
22
  else
15
23
  super(attribute, html_options)
@@ -23,11 +31,12 @@ module StimulusPlumbers
23
31
  theme.resolve(:form_field_input, floating: floating, error: error),
24
32
  opts,
25
33
  html_opts,
26
- kwargs
34
+ kwargs,
35
+ { autocomplete: kwargs.delete(:autocomplete) || DEFAULT_AUTOCOMPLETE }
27
36
  )
28
37
  if revealable
29
38
  render_revealable_password(error, floating: floating) do
30
- revealable_html_options = merge_html_options(html_options, { data: { input_formatter_target: "input" } })
39
+ revealable_html_options = merge_html_options(html_options, { data: { input_revealable_target: "input" } })
31
40
  @template.password_field(@object_name, attribute, objectify_options(revealable_html_options))
32
41
  end
33
42
  else
@@ -42,22 +51,38 @@ module StimulusPlumbers
42
51
  trailing: method(:reveal_button),
43
52
  **merge_html_options(
44
53
  theme.resolve(:form_field_input_reveal, error: error),
45
- { data: { controller: "input-formatter", input_formatter_format_value: "password" } }
54
+ data: reveal_data
46
55
  )
47
56
  ) { @template.capture(&block) }
48
57
  end
49
58
 
50
59
  def reveal_button
51
60
  build_reveal_button do
52
- Components::Icon.new(@template).render(
53
- name: "reveal",
54
- size: :sm,
55
- aria: { hidden: "true" },
56
- **theme.resolve(:button_icon)
61
+ @template.safe_join(
62
+ [password_icon("reveal", "revealIcon"), password_icon("conceal", "concealIcon", hidden: true)]
57
63
  )
58
64
  end
59
65
  end
60
66
 
67
+ def reveal_data
68
+ {
69
+ controller: STIMULUS_CONTROLLER,
70
+ input_revealable_reveal_label_value: I18n.t("stimulus_plumbers.form.password.show", default: "Show password"),
71
+ input_revealable_conceal_label_value: I18n.t("stimulus_plumbers.form.password.hide", default: "Hide password")
72
+ }
73
+ end
74
+
75
+ def password_icon(name, target, hidden: false)
76
+ Components::Icon.new(@template).render(
77
+ name,
78
+ size: :sm,
79
+ aria: { hidden: "true" },
80
+ data: { input_revealable_target: target },
81
+ hidden: hidden,
82
+ **theme.resolve(:button_icon)
83
+ )
84
+ end
85
+
61
86
  def build_reveal_button(&block)
62
87
  @template.content_tag(
63
88
  :button,
@@ -65,8 +90,8 @@ module StimulusPlumbers
65
90
  theme.resolve(:form_field_input_button_reveal),
66
91
  {
67
92
  type: "button",
68
- aria: { label: I18n.t("stimulus_plumbers.form.password.show", default: "Show password"), pressed: "false" },
69
- data: { input_formatter_target: "toggle", action: "click->input-formatter#toggle" }
93
+ aria: { label: I18n.t("stimulus_plumbers.form.password.show", default: "Show password") },
94
+ data: { input_revealable_target: "toggle", action: STIMULUS_ACTION }
70
95
  }
71
96
  )
72
97
  ) { @template.capture(&block) }
@@ -82,7 +82,7 @@ module StimulusPlumbers
82
82
  def clear_button
83
83
  build_clear_button do
84
84
  Components::Icon.new(@template).render(
85
- name: "close",
85
+ "close",
86
86
  size: :sm,
87
87
  aria: { hidden: "true" },
88
88
  **theme.resolve(:button_icon)
@@ -5,22 +5,67 @@ module StimulusPlumbers
5
5
  module Fields
6
6
  module Inputs
7
7
  module Submit
8
+ include Components::Button::IconLayout
9
+
8
10
  def submit(value = nil, options = {})
9
- if value.is_a?(Hash)
10
- options = value
11
- value = nil
12
- end
11
+ value, options = normalize_submit_arguments(value, options)
13
12
  value ||= submit_default_value
14
- type = options.delete(:type) { :default }
15
- variant = options.delete(:variant) { :primary }
13
+ submit_options = extract_submit_options(options)
14
+
15
+ validate_submit_accessible_name!(value, options)
16
+
17
+ render_submit_button(value, options, **submit_options)
18
+ end
19
+
20
+ private
21
+
22
+ def normalize_submit_arguments(value, options)
23
+ value.is_a?(Hash) ? [nil, value] : [value, options]
24
+ end
25
+
26
+ def extract_submit_options(options)
27
+ {
28
+ type: options.delete(:type) { :default },
29
+ variant: options.delete(:variant) { :primary },
30
+ icon_leading: options.delete(:icon_leading),
31
+ icon_trailing: options.delete(:icon_trailing),
32
+ hide_label: options.delete(:hide_label) { false }
33
+ }
34
+ end
16
35
 
36
+ def render_submit_button(value, options, type:, variant:, icon_leading:, icon_trailing:, hide_label:)
17
37
  Components::Button.new(@template).build(type: type, variant: variant) do |attrs|
18
38
  @template.tag.button(
19
39
  type: "submit",
20
40
  **merge_html_options(attrs, theme.resolve(:form_submit, type: type, variant: variant), options)
21
- ) { @template.content_tag(:span, value) }
41
+ ) { build_layout(submit_slots(icon_leading, icon_trailing)) { submit_label(value, hide_label) } }
22
42
  end
23
43
  end
44
+
45
+ def submit_slots(icon_leading, icon_trailing)
46
+ Components::Button::Slots.new(@template).tap do |slots|
47
+ slots.with_icon_leading(icon_leading) if icon_leading
48
+ slots.with_icon_trailing(icon_trailing) if icon_trailing
49
+ end
50
+ end
51
+
52
+ def submit_label(value, hide_label)
53
+ @template.content_tag(
54
+ :span,
55
+ value,
56
+ **merge_html_options(
57
+ theme.resolve(:form_submit_label, hidden: hide_label),
58
+ (hide_label ? { data: { sp_label_hidden: true } } : {})
59
+ )
60
+ )
61
+ end
62
+
63
+ def validate_submit_accessible_name!(value, options)
64
+ return if value.present? || options.dig(:aria, :label).present? ||
65
+ options.dig(:aria, :labelledby).present? || options[:"aria-label"].present?
66
+
67
+ raise ArgumentError, "submit button has no accessible name: pass a value or aria: { label: ... }"
68
+ end
24
69
  end
25
70
  end
26
71
  end
@@ -74,6 +74,7 @@ module StimulusPlumbers
74
74
 
75
75
  entry = "/#{relative_to_destination(path)}"
76
76
  return if File.readlines(gitignore, chomp: true).include?(entry)
77
+ return if git_ignores?(path)
77
78
 
78
79
  File.open(gitignore, "a") { |file| file.puts(entry) }
79
80
  say_status :append, ".gitignore", :green
@@ -81,6 +82,15 @@ module StimulusPlumbers
81
82
  say "Could not update .gitignore: #{e.message}. Skipping.", :yellow
82
83
  end
83
84
 
85
+ def git_ignores?(path)
86
+ Dir.chdir(destination_root) do
87
+ system("git", "check-ignore", "-q", relative_to_destination(path), out: File::NULL, err: File::NULL)
88
+ end
89
+ rescue StandardError => e
90
+ say "Could not check .gitignore coverage with git: #{e.message}. Proceeding to append.", :yellow
91
+ false
92
+ end
93
+
84
94
  # CSS copied by an installer belongs to the application from this point on.
85
95
  # Keep an existing file intact so a generator rerun never overwrites local
86
96
  # customizations (the same policy used by tailwindcss-rails' installer).
@@ -3,8 +3,8 @@
3
3
  module StimulusPlumbers
4
4
  module Helpers
5
5
  module IconHelper
6
- def sp_icon(name:, **kwargs)
7
- Components::Icon.new(self).render(name: name, **kwargs)
6
+ def sp_icon(name, **kwargs)
7
+ Components::Icon.new(self).render(name, **kwargs)
8
8
  end
9
9
  end
10
10
  end
@@ -8,19 +8,21 @@ require_relative "dispatcher/klass_proxy"
8
8
  module StimulusPlumbers
9
9
  module Plumber
10
10
  module Dispatcher
11
- def self.build(callable, *args, method_name: nil, init_args: [], init_kwargs: {}, **kwargs, &block)
12
- case callable
13
- when Symbol
14
- MethodCall.new(callable, *args, **kwargs, &block)
15
- when Proc
16
- InstanceExec.new(callable, *args, **kwargs)
17
- when Module
18
- KlassProxy.new(callable, method_name, *args, init_args: init_args, init_kwargs: init_kwargs, **kwargs, &block)
19
- when String
20
- klass = callable.safe_constantize
21
- raise ArgumentError, "could not resolve class from: #{callable.inspect}" unless klass
11
+ class << self
12
+ def build(callable, *args, method_name: nil, init_args: [], init_kwargs: {}, **kwargs, &block)
13
+ case callable
14
+ when Symbol
15
+ MethodCall.new(callable, *args, **kwargs, &block)
16
+ when Proc
17
+ InstanceExec.new(callable, *args, **kwargs)
18
+ when Module
19
+ KlassProxy.new(callable, method_name, *args, init_args: init_args, init_kwargs: init_kwargs, **kwargs, &block)
20
+ when String
21
+ klass = callable.safe_constantize
22
+ raise ArgumentError, "could not resolve class from: #{callable.inspect}" unless klass
22
23
 
23
- KlassProxy.new(klass, method_name, *args, init_args: init_args, init_kwargs: init_kwargs, **kwargs, &block)
24
+ KlassProxy.new(klass, method_name, *args, init_args: init_args, init_kwargs: init_kwargs, **kwargs, &block)
25
+ end
24
26
  end
25
27
  end
26
28
  end
@@ -3,14 +3,6 @@
3
3
  module StimulusPlumbers
4
4
  module Plumber
5
5
  class Slots
6
- def self.slot(*names, by: nil)
7
- names.each do |name|
8
- by ? define_by_slot(name, by) : define_flat_slot(name)
9
- define_reader(name)
10
- define_predicate(name)
11
- end
12
- end
13
-
14
6
  def initialize(template = nil)
15
7
  @slots = {}
16
8
  @template = template
@@ -50,6 +42,14 @@ module StimulusPlumbers
50
42
  end
51
43
 
52
44
  class << self
45
+ def slot(*names, by: nil)
46
+ names.each do |name|
47
+ by ? define_by_slot(name, by) : define_flat_slot(name)
48
+ define_reader(name)
49
+ define_predicate(name)
50
+ end
51
+ end
52
+
53
53
  private
54
54
 
55
55
  def define_flat_slot(name)
@@ -186,6 +186,7 @@ module StimulusPlumbers
186
186
  form_field_input_code_cell: {
187
187
  error: { default: false, validate: Ranges::BOOL }
188
188
  }.freeze,
189
+ form_field_input_code_separator: {}.freeze,
189
190
  form_field_input_code_overlay: {
190
191
  error: { default: false, validate: Ranges::BOOL }
191
192
  }.freeze,
@@ -205,6 +206,9 @@ module StimulusPlumbers
205
206
  form_submit: {
206
207
  type: { default: :default, validate: Button::Ranges::TYPE },
207
208
  variant: { default: :primary, validate: Button::Ranges::VARIANT }
209
+ }.freeze,
210
+ form_submit_label: {
211
+ hidden: { default: false, validate: Ranges::BOOL }
208
212
  }.freeze
209
213
  }.freeze
210
214
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module StimulusPlumbers
4
- VERSION = "0.4.11"
4
+ VERSION = "0.4.13"
5
5
  end