stimulus_plumbers 0.4.12 → 0.4.14

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 (32) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +24 -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/component/form.md +6 -5
  10. data/docs/component/icon.md +9 -9
  11. data/lib/stimulus_plumbers/components/button/icon_layout.rb +1 -1
  12. data/lib/stimulus_plumbers/components/card.rb +1 -1
  13. data/lib/stimulus_plumbers/components/combobox/trigger.rb +1 -1
  14. data/lib/stimulus_plumbers/components/combobox/typeahead.rb +1 -1
  15. data/lib/stimulus_plumbers/components/icon.rb +1 -1
  16. data/lib/stimulus_plumbers/components/link.rb +1 -1
  17. data/lib/stimulus_plumbers/components/list/item.rb +1 -1
  18. data/lib/stimulus_plumbers/components/ordered_list/item.rb +2 -2
  19. data/lib/stimulus_plumbers/components/progress_ring.rb +1 -1
  20. data/lib/stimulus_plumbers/components/timeline/event.rb +1 -1
  21. data/lib/stimulus_plumbers/form/fields/inputs/password.rb +35 -13
  22. data/lib/stimulus_plumbers/form/fields/inputs/search.rb +1 -1
  23. data/lib/stimulus_plumbers/helpers/icon_helper.rb +2 -2
  24. data/lib/stimulus_plumbers/themes/icons/external.rb +1 -1
  25. data/lib/stimulus_plumbers/themes/schema/icon.rb +15 -4
  26. data/lib/stimulus_plumbers/version.rb +1 -1
  27. data/vendor/ARIA.md +3 -2
  28. data/vendor/component/manifest.json +8 -1
  29. data/vendor/controller/docs/input-formatter.md +18 -39
  30. data/vendor/controller/docs/input-revealable.md +65 -0
  31. data/vendor/controller/manifest.json +31 -9
  32. metadata +2 -1
@@ -27,5 +27,6 @@ en:
27
27
  form:
28
28
  password:
29
29
  show: "Show password"
30
+ hide: "Hide password"
30
31
  search:
31
32
  clear: "Clear search"
@@ -52,7 +52,7 @@ Special options on native helpers:
52
52
 
53
53
  | Helper | Option | Effect |
54
54
  | ---------------- | ------------------ | ------------------------------------------------------------------ |
55
- | `password_field` | `revealable: true` | Wraps input in an `input-formatter` reveal controller |
55
+ | `password_field` | `revealable: true` | Wraps input in an `input-revealable` controller |
56
56
  | `search_field` | `clearable: true` | Wraps input in an `input-clearable` controller with a clear button |
57
57
 
58
58
  ### f.submit
@@ -160,11 +160,12 @@ Use `f.select` for a native `<select>`.
160
160
 
161
161
  Use `f.search_field` for a native `<input type="search">`.
162
162
 
163
- **Password** (`as: :password`) — reveal-toggle wrapper backed by `input-formatter`.
163
+ **Password** (`as: :password`) — reveal-toggle wrapper backed by `input-revealable`.
164
164
 
165
- | Option | Values | Default | Description |
166
- | ------------ | ------- | ------- | ------------------------------------------- |
167
- | `revealable` | Boolean | `false` | Adds a show/hide toggle button on the input |
165
+ | Option | Values | Default | Description |
166
+ | -------------- | ------- | -------------------- | ------------------------------------------- |
167
+ | `revealable` | Boolean | `false` | Adds a show/hide toggle button on the input |
168
+ | `autocomplete` | String | `"current-password"` | Native autocomplete value |
168
169
 
169
170
  Use `f.password_field` for a plain `<input type="password">` (also accepts `revealable:`).
170
171
 
@@ -7,17 +7,17 @@ Rails helper for rendering a named icon from the active theme's icon registry.
7
7
  ### `sp_icon`
8
8
 
9
9
  ```erb
10
- <%= sp_icon name: "check" %>
11
- <%= sp_icon name: "spinner", aria: { label: "Loading" }, role: "img" %>
12
- <%= sp_icon name: "unknown-icon" %>
13
- <%= sp_icon name: "check", size: :sm %>
10
+ <%= sp_icon "check" %>
11
+ <%= sp_icon "spinner", aria: { label: "Loading" }, role: "img" %>
12
+ <%= sp_icon "unknown-icon" %>
13
+ <%= sp_icon "check", size: :sm %>
14
14
  ```
15
15
 
16
- | Option | Default | Description |
17
- | ---------------- | ---------- | --------------------------------------------------- |
18
- | `name` | (required) | Icon name — looked up in the theme registry |
19
- | `size` | `:lg` | `:sm` \| `:md` \| `:lg` — theme-resolved size class |
20
- | `**html_options` | — | Forwarded to the root element |
16
+ | Option | Default | Description |
17
+ | ---------------- | ---------- | ---------------------------------------------------------------------- |
18
+ | `name` | (required) | Positional (1st argument). Icon name — looked up in the theme registry |
19
+ | `size` | `:lg` | `:sm` \| `:md` \| `:lg` — theme-resolved size class |
20
+ | `**html_options` | — | Forwarded to the root element |
21
21
 
22
22
  ---
23
23
 
@@ -21,7 +21,7 @@ module StimulusPlumbers
21
21
  next value unless Components::Icon.icon_name?(value)
22
22
 
23
23
  Components::Icon.new(@template).render(
24
- name: value,
24
+ value,
25
25
  size: :sm,
26
26
  classes: theme.resolve(:button_icon).fetch(:classes, ""),
27
27
  aria: { hidden: "true" }
@@ -49,7 +49,7 @@ module StimulusPlumbers
49
49
  return value unless value.is_a?(Symbol) || (value.is_a?(String) && !value.html_safe?)
50
50
 
51
51
  Components::Icon.new(template).render(
52
- name: value,
52
+ value,
53
53
  classes: theme.resolve(:card_icon).fetch(:classes, ""),
54
54
  aria: { hidden: "true" }
55
55
  )
@@ -84,7 +84,7 @@ module StimulusPlumbers
84
84
 
85
85
  def render_trigger_icon(name)
86
86
  Components::Icon.new(template).render(
87
- name: name,
87
+ name,
88
88
  classes: theme.resolve(:combobox_trigger_icon).fetch(:classes, ""),
89
89
  aria: { hidden: "true" }
90
90
  )
@@ -89,7 +89,7 @@ module StimulusPlumbers
89
89
  )
90
90
  ) do
91
91
  Components::Icon.new(template).render(
92
- name: "spinner",
92
+ "spinner",
93
93
  size: :md,
94
94
  classes: theme.resolve(:combobox_typeahead_loading_icon).fetch(:classes, ""),
95
95
  aria: { hidden: "true" }
@@ -9,7 +9,7 @@ module StimulusPlumbers
9
9
  end
10
10
  end
11
11
 
12
- def render(name:, size: :lg, **kwargs)
12
+ def render(name, size: :lg, **kwargs)
13
13
  html_options = merge_html_options(
14
14
  theme.resolve(:icon, size: size),
15
15
  kwargs
@@ -44,7 +44,7 @@ module StimulusPlumbers
44
44
  next value unless Components::Icon.icon_name?(value)
45
45
 
46
46
  Components::Icon.new(template).render(
47
- name: value,
47
+ value,
48
48
  size: :sm,
49
49
  classes: theme.resolve(:link_icon).fetch(:classes, ""),
50
50
  aria: { hidden: "true" }
@@ -43,7 +43,7 @@ module StimulusPlumbers
43
43
  next value unless Components::Icon.icon_name?(value)
44
44
 
45
45
  Components::Icon.new(template).render(
46
- name: value,
46
+ value,
47
47
  classes: theme.resolve(:list_item_icon).fetch(:classes, ""),
48
48
  aria: { hidden: "true" }
49
49
  )
@@ -85,7 +85,7 @@ module StimulusPlumbers
85
85
  next value unless Components::Icon.icon_name?(value)
86
86
 
87
87
  Components::Icon.new(template).render(
88
- name: value,
88
+ value,
89
89
  size: :sm,
90
90
  classes: theme.resolve(:ordered_list_item_handle).fetch(:classes, ""),
91
91
  aria: { hidden: "true" }
@@ -95,7 +95,7 @@ module StimulusPlumbers
95
95
 
96
96
  def default_handle_icon
97
97
  Components::Icon.new(template).render(
98
- name: "grip-vertical",
98
+ "grip-vertical",
99
99
  size: :sm,
100
100
  classes: theme.resolve(:ordered_list_item_handle).fetch(:classes, ""),
101
101
  aria: { hidden: "true" }
@@ -16,7 +16,7 @@ module StimulusPlumbers
16
16
  stimulus_data(value: value, min: min, max: max, indeterminate: indeterminate),
17
17
  { role: "progressbar", aria: progress_aria(value: value, min: min, max: max, indeterminate: indeterminate) }
18
18
  )
19
- Components::Icon.new(template).render(name: "progress-ring", **icon_options)
19
+ Components::Icon.new(template).render("progress-ring", **icon_options)
20
20
  end
21
21
 
22
22
  def progress_aria(value:, min:, max:, indeterminate:)
@@ -108,7 +108,7 @@ module StimulusPlumbers
108
108
  def render_indicator_content(type:, icon_name:)
109
109
  if type == :icon && icon_name
110
110
  Components::Icon.new(template).render(
111
- name: icon_name,
111
+ icon_name,
112
112
  size: :sm,
113
113
  classes: theme.resolve(:timeline_item_indicator_icon_slot).fetch(:classes, ""),
114
114
  aria: { hidden: "true" }
@@ -5,14 +5,19 @@ module StimulusPlumbers
5
5
  module Fields
6
6
  module Inputs
7
7
  module Password
8
- STIMULUS_CONTROLLER = "input-formatter"
8
+ STIMULUS_CONTROLLER = "input-revealable"
9
9
  STIMULUS_ACTION = "click->#{STIMULUS_CONTROLLER}#toggle".freeze
10
+ DEFAULT_AUTOCOMPLETE = "current-password"
10
11
 
11
12
  def password_field(attribute, floating: nil, revealable: false, **options)
12
- 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
+ )
13
18
  if revealable
14
19
  render_revealable_password(false) do
15
- 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" } }))
16
21
  end
17
22
  else
18
23
  super(attribute, html_options)
@@ -26,11 +31,12 @@ module StimulusPlumbers
26
31
  theme.resolve(:form_field_input, floating: floating, error: error),
27
32
  opts,
28
33
  html_opts,
29
- kwargs
34
+ kwargs,
35
+ { autocomplete: kwargs.delete(:autocomplete) || DEFAULT_AUTOCOMPLETE }
30
36
  )
31
37
  if revealable
32
38
  render_revealable_password(error, floating: floating) do
33
- 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" } })
34
40
  @template.password_field(@object_name, attribute, objectify_options(revealable_html_options))
35
41
  end
36
42
  else
@@ -45,22 +51,38 @@ module StimulusPlumbers
45
51
  trailing: method(:reveal_button),
46
52
  **merge_html_options(
47
53
  theme.resolve(:form_field_input_reveal, error: error),
48
- { data: { controller: STIMULUS_CONTROLLER, input_formatter_format_value: "password" } }
54
+ data: reveal_data
49
55
  )
50
56
  ) { @template.capture(&block) }
51
57
  end
52
58
 
53
59
  def reveal_button
54
60
  build_reveal_button do
55
- Components::Icon.new(@template).render(
56
- name: "reveal",
57
- size: :sm,
58
- aria: { hidden: "true" },
59
- **theme.resolve(:button_icon)
61
+ @template.safe_join(
62
+ [password_icon("reveal", "revealIcon"), password_icon("conceal", "concealIcon", hidden: true)]
60
63
  )
61
64
  end
62
65
  end
63
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
+
64
86
  def build_reveal_button(&block)
65
87
  @template.content_tag(
66
88
  :button,
@@ -68,8 +90,8 @@ module StimulusPlumbers
68
90
  theme.resolve(:form_field_input_button_reveal),
69
91
  {
70
92
  type: "button",
71
- aria: { label: I18n.t("stimulus_plumbers.form.password.show", default: "Show password"), pressed: "false" },
72
- data: { input_formatter_target: "toggle", action: STIMULUS_ACTION }
93
+ aria: { label: I18n.t("stimulus_plumbers.form.password.show", default: "Show password") },
94
+ data: { input_revealable_target: "toggle", action: STIMULUS_ACTION }
73
95
  }
74
96
  )
75
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)
@@ -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
@@ -31,7 +31,7 @@ module StimulusPlumbers
31
31
 
32
32
  result = svg_defaults(key)
33
33
  root.attributes.each_attribute do |attr|
34
- result[SVG_RENAME.fetch(attr.name, attr.name.to_sym)] = attr.value
34
+ result[SVG_RENAME.fetch(attr.name, attr.name.tr("-", "_").to_sym)] = attr.value
35
35
  end
36
36
 
37
37
  elements = parse_elements(root)
@@ -57,10 +57,21 @@ module StimulusPlumbers
57
57
  private
58
58
 
59
59
  def resolve_svg_attrs(svg_data)
60
- SVG_ATTR_DEFAULTS
61
- .merge(svg_data.slice(*SVG_ATTR_DEFAULTS.keys))
62
- .transform_keys { |key| SVG_ATTR_NAMES.fetch(key, key.to_s) }
63
- .transform_values(&:to_s)
60
+ SVG_ATTR_DEFAULTS.merge(svg_data).filter_map do |key, value|
61
+ next unless svg_attr_allowed?(key)
62
+
63
+ [svg_attr_name(key), value.to_s]
64
+ end.to_h
65
+ end
66
+
67
+ def svg_attr_allowed?(key)
68
+ SVG_ATTR_DEFAULTS.key?(key) || data_or_aria_attr?(key)
69
+ end
70
+
71
+ def svg_attr_name(key)
72
+ return key.to_s.tr("_", "-") if data_or_aria_attr?(key)
73
+
74
+ SVG_ATTR_NAMES.fetch(key, key.to_s)
64
75
  end
65
76
 
66
77
  def resolve_element_attrs(element_data)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module StimulusPlumbers
4
- VERSION = "0.4.12"
4
+ VERSION = "0.4.14"
5
5
  end
data/vendor/ARIA.md CHANGED
@@ -58,9 +58,10 @@ Two helper classes handle keyboard navigation in controllers — see [`stimulus-
58
58
  - Invalid fields: `aria-invalid="true"` + `aria-describedby` pointing to error message
59
59
  - Error message element: `role="alert"` or `aria-live="polite"` so it's announced
60
60
 
61
- #### Password Reveal (`input-formatter_controller`, `"password"` formatter)
61
+ #### Password Reveal (`input_revealable_controller`)
62
62
  - Toggle button: `aria-label` describes action ("Show password" / "Hide password")
63
- - `aria-pressed` managed on toggle button, reflecting revealed state
63
+ - One toggle button swaps visible eye/eye-slash icons and its accessible name; it has no `aria-pressed` because its name describes the next action rather than a persistent state.
64
+ - A live announcement was rejected: changing the input between `password` and `text` provides an independent cue when returning focus to the field, without adding chatty feedback.
64
65
 
65
66
  #### Code Input (`input_formatter_controller` + `character_cells`)
66
67
 
@@ -129,7 +129,14 @@
129
129
  "actions": [
130
130
  "onBlur",
131
131
  "onFocus",
132
- "onInput",
132
+ "onInput"
133
+ ],
134
+ "listens": [],
135
+ "targets": [],
136
+ "values": []
137
+ },
138
+ "input-revealable": {
139
+ "actions": [
133
140
  "toggle"
134
141
  ],
135
142
  "listens": [],
@@ -1,37 +1,34 @@
1
1
  # input-formatter
2
2
 
3
- Formats, masks, and reveals values written to an input element. Handles password reveal toggling and structured display formatting for credit cards, phone numbers, currencies, dates, and times.
3
+ Formats values written to an input element. Handles structured display formatting for credit cards, phone numbers, currencies, dates, and times.
4
4
 
5
5
  > When co-located with `input-combobox`, see [combobox.md](combobox.md) for the full event flow and wiring.
6
6
 
7
7
  ## Targets
8
8
 
9
- | Target | Element | Description |
10
- | -------- | ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
11
- | `input` | `<input>` or any element | Write destination — sets `.value` for `<input>`, `.textContent` otherwise |
12
- | `toggle` | `<button>` | Reveal/conceal button; hidden at connect when the formatter is not maskable |
13
- | `cell` | `<div>` (any element) | Optional character cells; when present, the canonical value is painted one character per cell, or (grouped mode — cell count equals `groups.length`) one group-sized slice per cell — see [character-cells](../plumber/character-cells.md) |
9
+ | Target | Element | Description |
10
+ | ------- | ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
11
+ | `input` | `<input>` or any element | Write destination — sets `.value` for `<input>`, `.textContent` otherwise |
12
+ | `cell` | `<div>` (any element) | Optional character cells; when present, the canonical value is painted one character per cell, or (grouped mode — cell count equals `groups.length`) one group-sized slice per cell — see [character-cells](../plumber/character-cells.md) |
14
13
 
15
14
  ## Values
16
15
 
17
- | Value | Type | Default | Description |
18
- | ---------- | ------- | --------- | --------------------------------------------------------------------------------- |
19
- | `format` | String | `"plain"` | Formatter type — see [Formatters](#formatters) for valid identifiers |
20
- | `options` | Object | `{}` | Formatter-specific options (e.g. `{ locale: "en-US" }` for currency) |
21
- | `revealed` | Boolean | `false` | Whether a masked value is currently revealed; managed by `toggle()` |
22
- | `groups` | Array | `[]` | Cell group widths (e.g. `[4,4,4,4]`); overrides the formatter's own grouping hint |
16
+ | Value | Type | Default | Description |
17
+ | --------- | ------ | --------- | --------------------------------------------------------------------------------- |
18
+ | `format` | String | `"plain"` | Formatter type — see [Formatters](#formatters) for valid identifiers |
19
+ | `options` | Object | `{}` | Formatter-specific options (e.g. `{ locale: "en-US" }` for currency) |
20
+ | `groups` | Array | `[]` | Cell group widths (e.g. `[4,4,4,4]`); overrides the formatter's own grouping hint |
23
21
 
24
22
  ## Methods
25
23
 
26
- | Method | Wired via | Description |
27
- | ----------------- | ------------------------ | ---------------------------------------------------------------------------------------------- |
28
- | `format(value)` | — | Programmatic API — normalises, formats/masks, writes to `input` target, dispatches `formatted` |
29
- | `toggle()` | `data-action` | Actionflips `revealedValue`; no-op unless `format` is `"password"` or formatter is maskable |
30
- | `onChange(event)` | `input-combobox:changed` | Event adapter — extracts `event.detail.value`, calls `format(value)` |
31
- | `onPaste(event)` | `clipboard:pasted` | Event adapter — normalises and validates pasted text, calls `format(value)` |
32
- | `onInput(event)` | `input` DOM event | Event adapter — re-formats from the input's current value |
33
- | `onFocus(event)` | `focus` DOM event | Event adapter — redraws cells so the caret cell appears |
34
- | `onBlur(event)` | `blur` DOM event | Event adapter — redraws cells so the caret cell clears |
24
+ | Method | Wired via | Description |
25
+ | ----------------- | ------------------------ | ---------------------------------------------------------------------------------------- |
26
+ | `format(value)` | — | Programmatic API — normalises, formats, writes to `input` target, dispatches `formatted` |
27
+ | `onChange(event)` | `input-combobox:changed` | Event adapter extracts `event.detail.value`, calls `format(value)` |
28
+ | `onPaste(event)` | `clipboard:pasted` | Event adapter — normalises and validates pasted text, calls `format(value)` |
29
+ | `onInput(event)` | `input` DOM event | Event adapter — re-formats from the input's current value |
30
+ | `onFocus(event)` | `focus` DOM event | Event adapter — redraws cells so the caret cell appears |
31
+ | `onBlur(event)` | `blur` DOM event | Event adapter — redraws cells so the caret cell clears |
35
32
 
36
33
  ## Dispatches
37
34
 
@@ -45,7 +42,6 @@ Formats, masks, and reveals values written to an input element. Handles password
45
42
  | `type` | Description |
46
43
  | -------------- | ------------------------------------------------------------------------------------------------------------------ |
47
44
  | `"plain"` | No-op — passes the value through unchanged |
48
- | `"password"` | Switches `input[type]` between `"password"` and `"text"` on reveal |
49
45
  | `"creditCard"` | Groups digits as `#### #### #### ####` |
50
46
  | `"phone"` | Formats as a local phone number |
51
47
  | `"currency"` | Locale-aware thousands separator and decimal places |
@@ -67,21 +63,6 @@ Formatter.register('iban', {
67
63
 
68
64
  ## Examples
69
65
 
70
- ### Password reveal
71
-
72
- ```html
73
- <div data-controller="input-formatter" data-input-formatter-format-value="password">
74
- <input type="password" data-input-formatter-target="input" />
75
- <button
76
- type="button"
77
- aria-label="Show password"
78
- aria-pressed="false"
79
- data-input-formatter-target="toggle"
80
- data-action="click->input-formatter#toggle"
81
- ></button>
82
- </div>
83
- ```
84
-
85
66
  ### Credit card formatting
86
67
 
87
68
  ```html
@@ -150,6 +131,4 @@ dash) are authored HTML, never inserted by the plumber — see
150
131
 
151
132
  ## Accessibility
152
133
 
153
- - Password `type` attribute switches between `"password"` (masked) and `"text"` (revealed), so screen readers announce the field type correctly.
154
- - See [ARIA.md's Password Reveal pattern](../../../ARIA.md) for the `toggle` button's `aria-label`/`aria-pressed` requirements.
155
134
  - Cell display: see [ARIA.md's Code Input pattern](../../../ARIA.md).
@@ -0,0 +1,65 @@
1
+ # input-revealable
2
+
3
+ Reveals or conceals an obscured `<input>` by switching its `type` between `"password"` and `"text"`.
4
+
5
+ ## Targets
6
+
7
+ | Target | Element | Description |
8
+ | ------------- | ---------- | --------------------------------------------------------------- |
9
+ | `input` | `<input>` | Obscured input; markup must initially declare `type="password"` |
10
+ | `toggle` | `<button>` | Reveal/conceal button |
11
+ | `revealIcon` | `<svg>` | Icon shown while the next action is reveal |
12
+ | `concealIcon` | `<svg>` | Icon shown while the next action is conceal |
13
+
14
+ Supply both icons or neither — a lone icon stays visible and only the `aria-label` swaps.
15
+
16
+ ## Values
17
+
18
+ | Value | Type | Default | Description |
19
+ | -------------- | ------- | ------- | --------------------------------------------- |
20
+ | `revealed` | Boolean | `false` | Whether the input is revealed |
21
+ | `revealLabel` | String | `""` | Toggle label while the next action is reveal |
22
+ | `concealLabel` | String | `""` | Toggle label while the next action is conceal |
23
+
24
+ ## Methods
25
+
26
+ | Method | Wired via | Description |
27
+ | ---------- | ------------- | --------------------- |
28
+ | `toggle()` | `data-action` | Flips `revealedValue` |
29
+
30
+ ## Readonly secret
31
+
32
+ ```html
33
+ <div
34
+ data-controller="input-revealable"
35
+ data-input-revealable-reveal-label-value="Show API key"
36
+ data-input-revealable-conceal-label-value="Hide API key"
37
+ >
38
+ <input
39
+ type="password"
40
+ readonly
41
+ value="sk_live_example"
42
+ autocomplete="off"
43
+ data-1p-ignore
44
+ data-lpignore="true"
45
+ data-input-revealable-target="input"
46
+ />
47
+ <button
48
+ type="button"
49
+ aria-label="Show API key"
50
+ data-input-revealable-target="toggle"
51
+ data-action="click->input-revealable#toggle"
52
+ >
53
+ <svg data-input-revealable-target="revealIcon" aria-hidden="true"></svg>
54
+ <svg data-input-revealable-target="concealIcon" aria-hidden="true" hidden></svg>
55
+ </button>
56
+ </div>
57
+ ```
58
+
59
+ `type="password"` triggers password-manager heuristics. Suppress them in your own markup with `autocomplete="off"`, `data-1p-ignore`, and `data-lpignore="true"` — the controller does not set these.
60
+
61
+ Non-input elements (`<output>`, `<span>`, table cells) are unsupported. Server-render both states and swap them with `setHidden`.
62
+
63
+ ## Accessibility
64
+
65
+ See [ARIA.md's Password Reveal pattern](../../../ARIA.md) for the toggle button's accessible-name requirements.
@@ -464,7 +464,6 @@
464
464
  "identifier": "input-formatter",
465
465
  "targets": [
466
466
  "input",
467
- "toggle",
468
467
  "cell"
469
468
  ],
470
469
  "values": {
@@ -476,10 +475,6 @@
476
475
  "type": "Object",
477
476
  "default": {}
478
477
  },
479
- "revealed": {
480
- "type": "Boolean",
481
- "default": false
482
- },
483
478
  "groups": {
484
479
  "type": "Array",
485
480
  "default": []
@@ -489,10 +484,8 @@
489
484
  "classes": [],
490
485
  "actions": [
491
486
  "attachCells",
492
- "cellsValue",
493
487
  "detachCells",
494
488
  "drawCells",
495
- "drawToggle",
496
489
  "format",
497
490
  "isFull",
498
491
  "onBlur",
@@ -502,14 +495,43 @@
502
495
  "onInput",
503
496
  "onPaste",
504
497
  "primeFilledState",
505
- "readValue",
506
- "toggle"
498
+ "readValue"
507
499
  ],
508
500
  "dispatches": [
509
501
  "filled",
510
502
  "formatted"
511
503
  ]
512
504
  },
505
+ "input-revealable": {
506
+ "identifier": "input-revealable",
507
+ "targets": [
508
+ "input",
509
+ "toggle",
510
+ "revealIcon",
511
+ "concealIcon"
512
+ ],
513
+ "values": {
514
+ "revealed": {
515
+ "type": "Boolean",
516
+ "default": false
517
+ },
518
+ "revealLabel": {
519
+ "type": "String",
520
+ "default": ""
521
+ },
522
+ "concealLabel": {
523
+ "type": "String",
524
+ "default": ""
525
+ }
526
+ },
527
+ "outlets": [],
528
+ "classes": [],
529
+ "actions": [
530
+ "draw",
531
+ "toggle"
532
+ ],
533
+ "dispatches": []
534
+ },
513
535
  "modal": {
514
536
  "identifier": "modal",
515
537
  "targets": [
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: stimulus_plumbers
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.12
4
+ version: 0.4.14
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ryan Chang
@@ -212,6 +212,7 @@ files:
212
212
  - vendor/controller/docs/flipper.md
213
213
  - vendor/controller/docs/input-clearable.md
214
214
  - vendor/controller/docs/input-formatter.md
215
+ - vendor/controller/docs/input-revealable.md
215
216
  - vendor/controller/docs/modal.md
216
217
  - vendor/controller/docs/panner.md
217
218
  - vendor/controller/docs/popover.md