stimulus_plumbers 0.4.14 → 0.4.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +15 -0
- data/README.md +3 -1
- data/app/assets/javascripts/stimulus-plumbers/controllers.manifest.json +63 -1
- data/app/assets/javascripts/stimulus-plumbers/index.es.js +345 -198
- data/app/assets/javascripts/stimulus-plumbers/index.es.js.map +1 -1
- data/app/assets/javascripts/stimulus-plumbers/index.umd.js +1 -1
- data/app/assets/javascripts/stimulus-plumbers/index.umd.js.map +1 -1
- data/config/locales/en.yml +12 -0
- data/docs/component/combobox.md +3 -2
- data/docs/component/form.md +85 -18
- data/docs/component/indicator.md +1 -1
- data/docs/component/plumber.md +40 -1
- data/docs/component/progress.md +51 -16
- data/docs/guide.md +63 -4
- data/lib/stimulus_plumbers/components/card/slots.rb +0 -2
- data/lib/stimulus_plumbers/components/combobox/{builder.rb → config.rb} +7 -11
- data/lib/stimulus_plumbers/components/combobox.rb +12 -12
- data/lib/stimulus_plumbers/components/password_strength.rb +103 -0
- data/lib/stimulus_plumbers/components/progress/shared.rb +74 -0
- data/lib/stimulus_plumbers/components/progress_bar.rb +127 -25
- data/lib/stimulus_plumbers/components/progress_meter.rb +16 -14
- data/lib/stimulus_plumbers/components/progress_ring.rb +9 -23
- data/lib/stimulus_plumbers/components/timeline/event/slots.rb +0 -1
- data/lib/stimulus_plumbers/form/builder.rb +35 -8
- data/lib/stimulus_plumbers/form/field.rb +34 -6
- data/lib/stimulus_plumbers/form/fields/inputs/password/revealable.rb +72 -0
- data/lib/stimulus_plumbers/form/fields/inputs/password/strength.rb +31 -0
- data/lib/stimulus_plumbers/form/fields/inputs/password.rb +34 -65
- data/lib/stimulus_plumbers/form/fields/inputs/progress.rb +30 -0
- data/lib/stimulus_plumbers/form/fields/inputs/range.rb +81 -0
- data/lib/stimulus_plumbers/form/fields/inputs/text.rb +1 -1
- data/lib/stimulus_plumbers/form/fields/renderer.rb +41 -0
- data/lib/stimulus_plumbers/helpers/progress_helper.rb +14 -4
- data/lib/stimulus_plumbers/password/requirements.rb +182 -0
- data/lib/stimulus_plumbers/password_strength_validator.rb +29 -0
- data/lib/stimulus_plumbers/plumber/config.rb +37 -0
- data/lib/stimulus_plumbers/plumber/dispatcher/callable_inspector.rb +4 -0
- data/lib/stimulus_plumbers/plumber/slots.rb +2 -2
- data/lib/stimulus_plumbers/themes/schema/progress/ranges.rb +13 -0
- data/lib/stimulus_plumbers/themes/schema.rb +23 -4
- data/lib/stimulus_plumbers/version.rb +1 -1
- data/lib/stimulus_plumbers.rb +7 -1
- data/vendor/ARIA.md +10 -0
- data/vendor/component/manifest.json +16 -2
- data/vendor/controller/docs/password-strength.md +38 -0
- data/vendor/controller/docs/progress.md +100 -23
- data/vendor/controller/guide.md +61 -7
- data/vendor/controller/manifest.json +63 -1
- metadata +13 -2
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module StimulusPlumbers
|
|
4
|
+
module Plumber
|
|
5
|
+
# Sibling of Slots for block DSLs whose payload is configuration, not content.
|
|
6
|
+
# Slots captures blocks through the view; Config only stores values, and holds
|
|
7
|
+
# the template to pass on to renderers.
|
|
8
|
+
#
|
|
9
|
+
# The store is private. Slots publishes `resolve` because its payload is uniform
|
|
10
|
+
# content a renderer reads generically; a Config's settings are typed, so each
|
|
11
|
+
# subclass exposes its own named readers instead.
|
|
12
|
+
class Config
|
|
13
|
+
attr_reader :template
|
|
14
|
+
|
|
15
|
+
def initialize(template = nil)
|
|
16
|
+
@template = template
|
|
17
|
+
@config = {}
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
private
|
|
21
|
+
|
|
22
|
+
# Returns nil so a subclass's DSL method reads as a command, not a value.
|
|
23
|
+
def configure(name, value)
|
|
24
|
+
@config[name] = value
|
|
25
|
+
nil
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def config(name)
|
|
29
|
+
@config[name]
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def configured?(name)
|
|
33
|
+
@config.key?(name)
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -13,6 +13,10 @@ module StimulusPlumbers
|
|
|
13
13
|
def accepts_kwargs?(callable)
|
|
14
14
|
callable.parameters.any? { |type, _| %i[key keyreq keyrest].include?(type) }
|
|
15
15
|
end
|
|
16
|
+
|
|
17
|
+
def accepts_block?(callable)
|
|
18
|
+
callable.parameters.any? { |type, _| type == :block }
|
|
19
|
+
end
|
|
16
20
|
end
|
|
17
21
|
end
|
|
18
22
|
end
|
|
@@ -37,8 +37,10 @@ module StimulusPlumbers
|
|
|
37
37
|
@template ? @template.capture(&block) : block.call
|
|
38
38
|
end
|
|
39
39
|
|
|
40
|
+
# Returns nil so a subclass's `with_*` method reads as a command, not a value.
|
|
40
41
|
def set_slot(name, value, options = {})
|
|
41
42
|
@slots[name] = { value: value, options: options }
|
|
43
|
+
nil
|
|
42
44
|
end
|
|
43
45
|
|
|
44
46
|
class << self
|
|
@@ -55,7 +57,6 @@ module StimulusPlumbers
|
|
|
55
57
|
def define_flat_slot(name)
|
|
56
58
|
define_method(:"with_#{name}") do |value = nil, **opts, &block|
|
|
57
59
|
set_slot(name, block || value, opts)
|
|
58
|
-
nil
|
|
59
60
|
end
|
|
60
61
|
end
|
|
61
62
|
|
|
@@ -64,7 +65,6 @@ module StimulusPlumbers
|
|
|
64
65
|
sub = by.new(@template)
|
|
65
66
|
block&.call(sub)
|
|
66
67
|
set_slot(name, sub)
|
|
67
|
-
nil
|
|
68
68
|
end
|
|
69
69
|
end
|
|
70
70
|
|
|
@@ -11,6 +11,7 @@ require_relative "schema/form/ranges"
|
|
|
11
11
|
require_relative "schema/form/checkbox/ranges"
|
|
12
12
|
require_relative "schema/form/floating/ranges"
|
|
13
13
|
require_relative "schema/form/radio/ranges"
|
|
14
|
+
require_relative "schema/progress/ranges"
|
|
14
15
|
require_relative "schema/icon"
|
|
15
16
|
|
|
16
17
|
module StimulusPlumbers
|
|
@@ -159,6 +160,12 @@ module StimulusPlumbers
|
|
|
159
160
|
error: { default: false, validate: Ranges::BOOL },
|
|
160
161
|
floating: { default: nil, validate: [nil, *Form::Floating::Ranges::TYPE] }
|
|
161
162
|
}.freeze,
|
|
163
|
+
password_strength_wrapper: {}.freeze,
|
|
164
|
+
password_strength_rules_heading: {}.freeze,
|
|
165
|
+
password_strength_rules: {}.freeze,
|
|
166
|
+
password_strength_rule: {}.freeze,
|
|
167
|
+
password_strength_rule_icon: {}.freeze,
|
|
168
|
+
password_strength_level: {}.freeze,
|
|
162
169
|
form_field_input_select: {
|
|
163
170
|
error: { default: false, validate: Ranges::BOOL },
|
|
164
171
|
floating: { default: nil, validate: [nil, *Form::Floating::Ranges::TYPE] }
|
|
@@ -173,6 +180,10 @@ module StimulusPlumbers
|
|
|
173
180
|
type: { default: :default, validate: Form::Radio::Ranges::TYPE },
|
|
174
181
|
variant: { default: :tertiary, validate: Form::Radio::Ranges::VARIANT }
|
|
175
182
|
}.freeze,
|
|
183
|
+
form_field_input_progress: {}.freeze,
|
|
184
|
+
form_field_input_range: {}.freeze,
|
|
185
|
+
form_field_input_range_group: {}.freeze,
|
|
186
|
+
form_field_input_range_value: {}.freeze,
|
|
176
187
|
form_field_input_combobox: {
|
|
177
188
|
error: { default: false, validate: Ranges::BOOL },
|
|
178
189
|
floating: { default: nil, validate: [nil, *Form::Floating::Ranges::TYPE] }
|
|
@@ -249,11 +260,19 @@ module StimulusPlumbers
|
|
|
249
260
|
popover: {}.freeze
|
|
250
261
|
}.freeze
|
|
251
262
|
|
|
263
|
+
# The outside readout gets its own keys, not parameters on existing ones — a theme method
|
|
264
|
+
# written before them has no keyword to accept.
|
|
252
265
|
PROGRESS = {
|
|
253
|
-
progress_bar:
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
266
|
+
progress_bar: { labelled: { default: false, validate: Ranges::BOOL } }.freeze,
|
|
267
|
+
progress_bar_group: {}.freeze,
|
|
268
|
+
progress_bar_fill: {}.freeze,
|
|
269
|
+
progress_bar_value: {}.freeze,
|
|
270
|
+
progress_bar_value_outside: {}.freeze,
|
|
271
|
+
progress_segment_group: {}.freeze,
|
|
272
|
+
progress_segment: {}.freeze,
|
|
273
|
+
progress_segment_fill: {}.freeze,
|
|
274
|
+
progress_ring: { size: { default: nil, validate: %i[sm md lg] } }.freeze,
|
|
275
|
+
progress_meter: {}.freeze
|
|
257
276
|
}.freeze
|
|
258
277
|
|
|
259
278
|
TIMELINE = {
|
data/lib/stimulus_plumbers.rb
CHANGED
|
@@ -13,8 +13,11 @@ require_relative "stimulus_plumbers/logger"
|
|
|
13
13
|
# -- Plumber base --
|
|
14
14
|
require_relative "stimulus_plumbers/plumber/dispatcher"
|
|
15
15
|
require_relative "stimulus_plumbers/plumber/slots"
|
|
16
|
+
require_relative "stimulus_plumbers/plumber/config"
|
|
16
17
|
require_relative "stimulus_plumbers/plumber/base"
|
|
17
18
|
|
|
19
|
+
require_relative "stimulus_plumbers/password/requirements"
|
|
20
|
+
|
|
18
21
|
# -- UI components --
|
|
19
22
|
require_relative "stimulus_plumbers/components/icon"
|
|
20
23
|
require_relative "stimulus_plumbers/components/indicator"
|
|
@@ -46,9 +49,11 @@ require_relative "stimulus_plumbers/components/input_group"
|
|
|
46
49
|
require_relative "stimulus_plumbers/components/popover"
|
|
47
50
|
require_relative "stimulus_plumbers/components/popover/trigger"
|
|
48
51
|
require_relative "stimulus_plumbers/components/popover/panel"
|
|
52
|
+
require_relative "stimulus_plumbers/components/progress/shared"
|
|
49
53
|
require_relative "stimulus_plumbers/components/progress_bar"
|
|
50
54
|
require_relative "stimulus_plumbers/components/progress_ring"
|
|
51
55
|
require_relative "stimulus_plumbers/components/progress_meter"
|
|
56
|
+
require_relative "stimulus_plumbers/components/password_strength"
|
|
52
57
|
|
|
53
58
|
# -- Calendar --
|
|
54
59
|
require_relative "stimulus_plumbers/components/calendar"
|
|
@@ -70,7 +75,7 @@ require_relative "stimulus_plumbers/components/combobox/date/navigation"
|
|
|
70
75
|
require_relative "stimulus_plumbers/components/combobox/dropdown"
|
|
71
76
|
require_relative "stimulus_plumbers/components/combobox/typeahead"
|
|
72
77
|
require_relative "stimulus_plumbers/components/combobox/time"
|
|
73
|
-
require_relative "stimulus_plumbers/components/combobox/
|
|
78
|
+
require_relative "stimulus_plumbers/components/combobox/config"
|
|
74
79
|
|
|
75
80
|
# -- Form --
|
|
76
81
|
require_relative "stimulus_plumbers/form/field"
|
|
@@ -94,3 +99,4 @@ module StimulusPlumbers
|
|
|
94
99
|
end
|
|
95
100
|
|
|
96
101
|
require_relative "stimulus_plumbers/engine" if defined?(Rails::Engine)
|
|
102
|
+
require_relative "stimulus_plumbers/password_strength_validator" if defined?(ActiveModel::EachValidator)
|
data/vendor/ARIA.md
CHANGED
|
@@ -97,6 +97,16 @@ Two helper classes handle keyboard navigation in controllers — see [`stimulus-
|
|
|
97
97
|
- Accepted tradeoff: because `indeterminate` has no HTML attribute, the server can only render the master's initial `checked` state for the all-true case; every other case (including mixed) renders unchecked and is corrected to `indeterminate` once the `checklist` controller connects — a brief, accepted flash for the mixed case only.
|
|
98
98
|
- Disabled (readonly) items are excluded from the master's aggregate and from bulk toggling — the `checklist` controller filters them out via their own `.disabled` property, mirroring their exclusion from tab order and AT interaction.
|
|
99
99
|
|
|
100
|
+
#### Progress (`progress_controller`, `sp_progress_*`)
|
|
101
|
+
- `role="progressbar"` is read-only and never focusable — it reports a value, it does not accept one. An interactive equivalent is a native `<input type="range">` (or `role="slider"`), not a progressbar with `tabindex`
|
|
102
|
+
- Value: `aria-valuemin`/`aria-valuemax` always; `aria-valuenow` omitted while indeterminate (an omitted `valuenow` is what signals "unknown progress" to AT)
|
|
103
|
+
- `aria-valuetext` only when the readout text is not derivable from `aria-valuenow` — set for the `value`/`value_max` formats, deliberately **not** for `percent`, where AT already computes the percentage and a duplicate would be announced twice
|
|
104
|
+
- On-screen readout is `aria-hidden="true"` — the value reaches AT through `aria-valuenow`/`aria-valuetext`, so exposing the span too would double-announce it
|
|
105
|
+
- Name: `aria-label` standalone, or `aria-labelledby` → a visible caption. `<label for>` cannot name a progressbar — `for=` is only valid against a labelable element (`button`, `input`, `meter`, `output`, `progress`, `select`, `textarea`), so a `<div role="progressbar">` targeted by one is silently left unnamed (WCAG 4.1.2). Form fields rendering a progressbar therefore emit a `<span>` caption, not a `<label>`
|
|
106
|
+
- No `aria-invalid`/`aria-required` on a progressbar — neither is supported on the role, and it submits nothing that could be invalid. Errors still attach via `aria-describedby`
|
|
107
|
+
- Segment slots are decorative (`aria-hidden="true"`) — the value is announced once, by the container
|
|
108
|
+
- The `range` variant drives a native `<input type="range">` and writes **no** ARIA: the native control already exposes slider role, value, and keyboard operation, and duplicating them announces worse than leaving them alone. It keeps an ordinary `<label for>` because an `input` is labelable
|
|
109
|
+
|
|
100
110
|
#### Avatar / Card / Icon
|
|
101
111
|
- Decorative images/icons: `aria-hidden="true"` or `alt=""`
|
|
102
112
|
- Meaningful images: descriptive `alt` text
|
|
@@ -155,6 +155,14 @@
|
|
|
155
155
|
"targets": [],
|
|
156
156
|
"values": []
|
|
157
157
|
},
|
|
158
|
+
"password-strength": {
|
|
159
|
+
"actions": [
|
|
160
|
+
"score"
|
|
161
|
+
],
|
|
162
|
+
"listens": [],
|
|
163
|
+
"targets": [],
|
|
164
|
+
"values": []
|
|
165
|
+
},
|
|
158
166
|
"popover": {
|
|
159
167
|
"actions": [
|
|
160
168
|
"close",
|
|
@@ -166,20 +174,26 @@
|
|
|
166
174
|
"values": []
|
|
167
175
|
},
|
|
168
176
|
"progress": {
|
|
169
|
-
"actions": [
|
|
177
|
+
"actions": [
|
|
178
|
+
"refresh"
|
|
179
|
+
],
|
|
170
180
|
"listens": [],
|
|
171
181
|
"targets": [
|
|
172
182
|
"fill",
|
|
173
|
-
"
|
|
183
|
+
"input",
|
|
184
|
+
"meter",
|
|
185
|
+
"value"
|
|
174
186
|
],
|
|
175
187
|
"values": [
|
|
176
188
|
"current",
|
|
189
|
+
"format",
|
|
177
190
|
"high",
|
|
178
191
|
"indeterminate",
|
|
179
192
|
"low",
|
|
180
193
|
"max",
|
|
181
194
|
"min",
|
|
182
195
|
"optimum",
|
|
196
|
+
"segmentMode",
|
|
183
197
|
"variant"
|
|
184
198
|
]
|
|
185
199
|
},
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# password-strength
|
|
2
|
+
|
|
3
|
+
Live password meter and requirements checklist. Scoring is provided by the [password strength plumber](../plumber/password_strength.md).
|
|
4
|
+
|
|
5
|
+
## Targets
|
|
6
|
+
|
|
7
|
+
| Target | Element | Description |
|
|
8
|
+
| ------------------------- | --------- | ------------------------------------------------------- |
|
|
9
|
+
| `input` | `<input>` | Password input to score |
|
|
10
|
+
| `rule` | `<li>` | Requirement row with `data-rule` and `data-satisfied` |
|
|
11
|
+
| `level` | Element | Visible, polite live strength label |
|
|
12
|
+
| `checkIcon` / `closeIcon` | `<svg>` | Paired rule-state icons; both are required for swapping |
|
|
13
|
+
|
|
14
|
+
## Values and outlet
|
|
15
|
+
|
|
16
|
+
| Name | Type | Default |
|
|
17
|
+
| ----------------- | ---------- | --------- |
|
|
18
|
+
| `scorer` | String | `'rules'` |
|
|
19
|
+
| `rules` | Array | `[]` |
|
|
20
|
+
| `options` | Object | `{}` |
|
|
21
|
+
| `labels` | Object | `{}` |
|
|
22
|
+
| `announceDelay` | Number | `700` |
|
|
23
|
+
| `progress` outlet | Controller | — |
|
|
24
|
+
|
|
25
|
+
`score()` is wired to the input event. It updates the progress outlet immediately and debounces level-label changes.
|
|
26
|
+
|
|
27
|
+
## Rule descriptors
|
|
28
|
+
|
|
29
|
+
The controller holds no built-in rule table — the server (or a standalone caller) supplies every rule as data via the `rules` value. Each descriptor is `{ key, label?, pattern?, min?, max? }` and passes when `min ≤ n ≤ max`, where `n` is:
|
|
30
|
+
|
|
31
|
+
- **length** (no `pattern`) — the password length.
|
|
32
|
+
- **count** (with `pattern`) — non-overlapping occurrences of `pattern`: `(pw.match(new RegExp(pattern, "g")) || []).length`.
|
|
33
|
+
|
|
34
|
+
Defaults: `min` `0`, `max` `Infinity`. So `{ pattern: "\\d", min: 2 }` requires ≥2 digits, and `{ pattern: "\\s", min: 0, max: 0 }` forbids whitespace.
|
|
35
|
+
|
|
36
|
+
**Portability constraint:** patterns must be single-char-consuming character classes — no anchors (`^`/`$`), lookbehind, or unicode-property escapes — so non-overlapping counts agree across the Ruby and JS regex engines.
|
|
37
|
+
|
|
38
|
+
The strength `level` is `strong` when every rule passes; otherwise the score (`satisfied / total × 100`) splits `weak` from `fine` at `options.low` (default `34`). `options` carries only these non-rule knobs.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Progress
|
|
2
2
|
|
|
3
|
-
Value-driven progress indicator supporting
|
|
3
|
+
Value-driven progress indicator supporting five render variants: a linear bar, a segmented bar, an SVG ring, a native `<meter>`, and a native `<input type="range">`.
|
|
4
4
|
|
|
5
5
|
## Stimulus Identifier
|
|
6
6
|
|
|
@@ -8,37 +8,58 @@ Value-driven progress indicator supporting three render variants: a linear bar,
|
|
|
8
8
|
|
|
9
9
|
## Targets
|
|
10
10
|
|
|
11
|
-
| Name | Element | Purpose
|
|
12
|
-
| ------- | --------------------------------- |
|
|
13
|
-
| `fill` | `<div>` (bar) / `<circle>` (ring) | Element whose `width` (bar) or `stroke-dasharray`/`stroke-dashoffset` (ring) is set |
|
|
14
|
-
| `meter` | `<meter>` | Present only for `variant: "meter"` — native element, attributes synced directly
|
|
11
|
+
| Name | Element | Purpose |
|
|
12
|
+
| ------- | --------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
13
|
+
| `fill` | `<div>` (bar) / `<circle>` (ring) | Element whose `width` (bar) or `stroke-dasharray`/`stroke-dashoffset` (ring) is set. Segmented renders **one `fill` per segment**; the controller distributes the value across them |
|
|
14
|
+
| `meter` | `<meter>` | Present only for `variant: "meter"` — native element, attributes synced directly |
|
|
15
|
+
| `value` | `<span>` | Optional on-screen readout; its `textContent` is set from `format`. Bar and range variants |
|
|
16
|
+
| `input` | `<input type="range">` | Range-only, and only when a readout is present — the controller then sits on a wrapper containing both |
|
|
15
17
|
|
|
16
18
|
## Values
|
|
17
19
|
|
|
18
|
-
| Name | Type | Default
|
|
19
|
-
| ----------------------- | ------- |
|
|
20
|
-
| `variant` | String | `"bar"`
|
|
21
|
-
| `current` | Number | `0`
|
|
22
|
-
| `min` | Number | `0`
|
|
23
|
-
| `max` | Number | `100`
|
|
24
|
-
| `optimum` | Number | —
|
|
25
|
-
| `low` | Number | —
|
|
26
|
-
| `high` | Number | —
|
|
27
|
-
| `indeterminate` | Boolean | `false`
|
|
28
|
-
| `indeterminateFraction` | Number | `0.25`
|
|
20
|
+
| Name | Type | Default | Purpose |
|
|
21
|
+
| ----------------------- | ------- | ------------ | -------------------------------------------------------------------------------------------------------------------------------------- |
|
|
22
|
+
| `variant` | String | `"bar"` | `"bar"` \| `"segmented"` \| `"ring"` \| `"meter"` \| `"range"` |
|
|
23
|
+
| `current` | Number | `0` | Current value |
|
|
24
|
+
| `min` | Number | `0` | Range minimum |
|
|
25
|
+
| `max` | Number | `100` | Range maximum |
|
|
26
|
+
| `optimum` | Number | — | Meter-only; maps to native `<meter optimum>` |
|
|
27
|
+
| `low` | Number | — | Meter-only; maps to native `<meter low>` |
|
|
28
|
+
| `high` | Number | — | Meter-only; maps to native `<meter high>` |
|
|
29
|
+
| `indeterminate` | Boolean | `false` | Suppresses `aria-valuenow`; toggles the `sp-progress-indeterminate` class |
|
|
30
|
+
| `indeterminateFraction` | Number | `0.25` | Bar width / ring arc / segment chunk-width fraction rendered while indeterminate |
|
|
31
|
+
| `segmentMode` | String | `"discrete"` | Segmented-only. `"discrete"` lights a whole segment once progress reaches into it; `"continuous"` partially fills the boundary segment |
|
|
32
|
+
| `format` | String | `""` | Readout text for the `value` target: `"percent"` \| `"value"` \| `"value_max"`. Empty or unrecognized renders nothing |
|
|
33
|
+
|
|
34
|
+
### Readout formats
|
|
35
|
+
|
|
36
|
+
| `format` | Renders | `aria-valuetext` |
|
|
37
|
+
| ------------- | --------- | ---------------- |
|
|
38
|
+
| `"percent"` | `45%` | not set |
|
|
39
|
+
| `"value"` | `45` | `45` |
|
|
40
|
+
| `"value_max"` | `45 / 60` | `45 / 60` |
|
|
41
|
+
|
|
42
|
+
`percent` omits `aria-valuetext` — assistive technology derives the percentage from `aria-valuenow` itself, so setting it would only duplicate what AT already announces. The readout element is decorative (`aria-hidden`); the value reaches AT through `aria-valuenow`/`aria-valuetext`.
|
|
43
|
+
|
|
44
|
+
The `range` variant writes no `aria-value*` at all — a native `<input type="range">` already exposes its own slider semantics — and sets `--sp-progress-percent` on the input so a theme can paint the filled portion of the track.
|
|
45
|
+
|
|
46
|
+
The `bar` variant sets `--sp-progress-percent` too, on the controller element, alongside the fill target's width. A theme can use it to split the readout's color at the fill edge. It is unset until the controller connects, so read it as `var(--sp-progress-percent, 0)` to match the server-rendered (empty) fill.
|
|
47
|
+
|
|
48
|
+
The rendered number is the value clamped to `[min, max]`, so an out-of-range `current` reads as the nearest bound rather than an impossible percentage. An empty or inverted range (`max <= min`) renders `0%`. While `indeterminate`, the readout is blank and `aria-valuetext` is removed.
|
|
29
49
|
|
|
30
50
|
## Methods
|
|
31
51
|
|
|
32
|
-
| Method | Wired via
|
|
33
|
-
| ---------------------------- |
|
|
34
|
-
| `setValue(value)` | —
|
|
35
|
-
| `currentValueChanged(value)` | Stimulus value callback
|
|
52
|
+
| Method | Wired via | Purpose |
|
|
53
|
+
| ---------------------------- | ------------------------- | --------------------------------------------------------------------------------------------------------- |
|
|
54
|
+
| `setValue(value)` | — | Programmatic API — clamps to `[min, max]`, updates `currentValue`, dispatches `progress:changed` |
|
|
55
|
+
| `currentValueChanged(value)` | Stimulus value callback | Recalculates fill/meter attrs whenever `current` changes (covers `setValue()` and direct attribute edits) |
|
|
56
|
+
| `refresh()` | `input->progress#refresh` | Range-only. Reads the native input's value and passes it to `setValue()` |
|
|
36
57
|
|
|
37
58
|
## Dispatches
|
|
38
59
|
|
|
39
|
-
| Event | Detail | When
|
|
40
|
-
| ------------------ | --------------------- |
|
|
41
|
-
| `progress:changed` | `{ value, min, max }` | After `setValue()` updates the value |
|
|
60
|
+
| Event | Detail | When |
|
|
61
|
+
| ------------------ | --------------------- | -------------------------------------------------------------------------------------- |
|
|
62
|
+
| `progress:changed` | `{ value, min, max }` | After `setValue()` updates the value — including a range drag, which routes through it |
|
|
42
63
|
|
|
43
64
|
## Example HTML
|
|
44
65
|
|
|
@@ -54,6 +75,34 @@ Value-driven progress indicator supporting three render variants: a linear bar,
|
|
|
54
75
|
<div data-progress-target="fill"></div>
|
|
55
76
|
</div>
|
|
56
77
|
|
|
78
|
+
<!-- Bar with an on-screen readout -->
|
|
79
|
+
<div
|
|
80
|
+
role="progressbar"
|
|
81
|
+
data-controller="progress"
|
|
82
|
+
data-progress-current-value="45"
|
|
83
|
+
data-progress-max-value="100"
|
|
84
|
+
data-progress-format-value="percent"
|
|
85
|
+
>
|
|
86
|
+
<div data-progress-target="fill"></div>
|
|
87
|
+
<span data-progress-target="value" aria-hidden="true">45%</span>
|
|
88
|
+
</div>
|
|
89
|
+
|
|
90
|
+
<!-- Segmented — one fill per segment; number of segments = number of fill targets -->
|
|
91
|
+
<div
|
|
92
|
+
role="progressbar"
|
|
93
|
+
data-controller="progress"
|
|
94
|
+
data-progress-variant-value="segmented"
|
|
95
|
+
data-progress-current-value="6"
|
|
96
|
+
data-progress-max-value="10"
|
|
97
|
+
>
|
|
98
|
+
<!-- ×5 slots → segment size 2 -->
|
|
99
|
+
<div aria-hidden="true"><div data-progress-target="fill"></div></div>
|
|
100
|
+
<div aria-hidden="true"><div data-progress-target="fill"></div></div>
|
|
101
|
+
<div aria-hidden="true"><div data-progress-target="fill"></div></div>
|
|
102
|
+
<div aria-hidden="true"><div data-progress-target="fill"></div></div>
|
|
103
|
+
<div aria-hidden="true"><div data-progress-target="fill"></div></div>
|
|
104
|
+
</div>
|
|
105
|
+
|
|
57
106
|
<!-- Ring -->
|
|
58
107
|
<svg
|
|
59
108
|
role="progressbar"
|
|
@@ -75,6 +124,34 @@ Value-driven progress indicator supporting three render variants: a linear bar,
|
|
|
75
124
|
data-progress-max-value="100"
|
|
76
125
|
></meter>
|
|
77
126
|
|
|
127
|
+
<!-- Range — no readout, so the controller sits on the input itself -->
|
|
128
|
+
<input
|
|
129
|
+
type="range"
|
|
130
|
+
min="0"
|
|
131
|
+
max="100"
|
|
132
|
+
value="45"
|
|
133
|
+
style="--sp-progress-percent: 45"
|
|
134
|
+
data-controller="progress"
|
|
135
|
+
data-progress-variant-value="range"
|
|
136
|
+
data-progress-current-value="45"
|
|
137
|
+
data-progress-min-value="0"
|
|
138
|
+
data-progress-max-value="100"
|
|
139
|
+
data-action="input->progress#refresh"
|
|
140
|
+
/>
|
|
141
|
+
|
|
142
|
+
<!-- Range with a readout — a target must be a descendant, so a wrapper hosts the controller -->
|
|
143
|
+
<div
|
|
144
|
+
data-controller="progress"
|
|
145
|
+
data-progress-variant-value="range"
|
|
146
|
+
data-progress-current-value="45"
|
|
147
|
+
data-progress-max-value="100"
|
|
148
|
+
data-progress-format-value="percent"
|
|
149
|
+
data-action="input->progress#refresh"
|
|
150
|
+
>
|
|
151
|
+
<input type="range" min="0" max="100" value="45" style="--sp-progress-percent: 45" data-progress-target="input" />
|
|
152
|
+
<span data-progress-target="value" aria-hidden="true">45%</span>
|
|
153
|
+
</div>
|
|
154
|
+
|
|
78
155
|
<!-- Indeterminate -->
|
|
79
156
|
<div role="progressbar" data-controller="progress" data-progress-indeterminate-value="true">
|
|
80
157
|
<div data-progress-target="fill"></div>
|
data/vendor/controller/guide.md
CHANGED
|
@@ -3,14 +3,68 @@
|
|
|
3
3
|
For a non-Rails / plain JS consumer of `@stimulus-plumbers/controllers`. Rails apps get this wired
|
|
4
4
|
automatically via the `stimulus_plumbers` gem's `sp_*` helpers — skip this guide for Rails.
|
|
5
5
|
|
|
6
|
+
## Install
|
|
7
|
+
|
|
6
8
|
```bash
|
|
7
|
-
npm install @stimulus-plumbers/controllers
|
|
9
|
+
npm install @hotwired/stimulus @stimulus-plumbers/controllers
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
## Register
|
|
13
|
+
|
|
14
|
+
Every controller is a named export. Import the ones you use and register each under its identifier:
|
|
15
|
+
|
|
16
|
+
```javascript
|
|
17
|
+
import { Application } from '@hotwired/stimulus';
|
|
18
|
+
import { PopoverController, ProgressController, ComboboxDateController } from '@stimulus-plumbers/controllers';
|
|
19
|
+
|
|
20
|
+
const application = Application.start();
|
|
21
|
+
|
|
22
|
+
application.register('popover', PopoverController);
|
|
23
|
+
application.register('progress', ProgressController);
|
|
24
|
+
application.register('combobox-date', ComboboxDateController);
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
The export name is the identifier in PascalCase plus `Controller` — `combobox-date` →
|
|
28
|
+
`ComboboxDateController`. For the full identifier list ask `list_controllers` / read
|
|
29
|
+
`controller://index`; each one's targets, values, classes, outlets, and events are in
|
|
30
|
+
`get_controller_schema(name: identifier)` — `name:` is the identifier (`combobox-date`), not the
|
|
31
|
+
export name. Narrative docs come from `get_controller_docs(name: family)`, which takes the family
|
|
32
|
+
(`combobox`) rather than the identifier; `list_controller_docs` lists the families. Outside MCP, the
|
|
33
|
+
[Controllers table](https://github.com/ryancyq/stimulus-plumbers/blob/main/stimulus-plumbers/README.md#controllers)
|
|
34
|
+
lists the same identifiers.
|
|
35
|
+
|
|
36
|
+
Registering a controller the page never uses is harmless — Stimulus only instantiates on a matching
|
|
37
|
+
`data-controller`.
|
|
38
|
+
|
|
39
|
+
## Wire the markup
|
|
40
|
+
|
|
41
|
+
Interactive components (combobox, popover, calendar) expect their `data-controller`, target, and
|
|
42
|
+
value attributes to already be present in the rendered HTML. Rails apps get these from `sp_*`
|
|
43
|
+
helpers; a plain-JS setup writes them by hand, following the HTML structure in each controller's
|
|
44
|
+
doc:
|
|
45
|
+
|
|
46
|
+
```html
|
|
47
|
+
<div
|
|
48
|
+
role="progressbar"
|
|
49
|
+
data-controller="progress"
|
|
50
|
+
data-progress-current-value="30"
|
|
51
|
+
data-progress-min-value="0"
|
|
52
|
+
data-progress-max-value="100"
|
|
53
|
+
>
|
|
54
|
+
<div data-progress-target="fill"></div>
|
|
55
|
+
</div>
|
|
8
56
|
```
|
|
9
57
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
58
|
+
Markup shape is per-controller — take the authoritative structure from `get_controller_docs(name:)`
|
|
59
|
+
rather than adapting this example.
|
|
60
|
+
|
|
61
|
+
## Styling
|
|
62
|
+
|
|
63
|
+
Controllers ship no CSS; they toggle classes and attributes only. Bring your own styles, or use the
|
|
64
|
+
`stimulus_plumbers_tailwind` gem's token set (`guide://tailwind`).
|
|
65
|
+
|
|
66
|
+
## Accessibility
|
|
13
67
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
68
|
+
Keyboard, focus, and ARIA behaviour per component is in `aria://reference` — read it before
|
|
69
|
+
hand-writing markup, since the controllers assume the documented roles and relationships are
|
|
70
|
+
present.
|
|
@@ -558,6 +558,53 @@
|
|
|
558
558
|
"actions": [],
|
|
559
559
|
"dispatches": []
|
|
560
560
|
},
|
|
561
|
+
"password-strength": {
|
|
562
|
+
"identifier": "password-strength",
|
|
563
|
+
"targets": [
|
|
564
|
+
"input",
|
|
565
|
+
"rule",
|
|
566
|
+
"level",
|
|
567
|
+
"checkIcon",
|
|
568
|
+
"closeIcon"
|
|
569
|
+
],
|
|
570
|
+
"values": {
|
|
571
|
+
"scorer": {
|
|
572
|
+
"type": "String",
|
|
573
|
+
"default": "rules"
|
|
574
|
+
},
|
|
575
|
+
"rules": {
|
|
576
|
+
"type": "Array",
|
|
577
|
+
"default": []
|
|
578
|
+
},
|
|
579
|
+
"options": {
|
|
580
|
+
"type": "Object",
|
|
581
|
+
"default": {}
|
|
582
|
+
},
|
|
583
|
+
"labels": {
|
|
584
|
+
"type": "Object",
|
|
585
|
+
"default": {}
|
|
586
|
+
},
|
|
587
|
+
"announceDelay": {
|
|
588
|
+
"type": "Number",
|
|
589
|
+
"default": 700
|
|
590
|
+
}
|
|
591
|
+
},
|
|
592
|
+
"outlets": [
|
|
593
|
+
"progress"
|
|
594
|
+
],
|
|
595
|
+
"classes": [],
|
|
596
|
+
"actions": [
|
|
597
|
+
"announce",
|
|
598
|
+
"constructor",
|
|
599
|
+
"drawIcons",
|
|
600
|
+
"drawRules",
|
|
601
|
+
"enhance",
|
|
602
|
+
"if",
|
|
603
|
+
"readValue",
|
|
604
|
+
"score"
|
|
605
|
+
],
|
|
606
|
+
"dispatches": []
|
|
607
|
+
},
|
|
561
608
|
"popover": {
|
|
562
609
|
"identifier": "popover",
|
|
563
610
|
"targets": [
|
|
@@ -616,7 +663,9 @@
|
|
|
616
663
|
"identifier": "progress",
|
|
617
664
|
"targets": [
|
|
618
665
|
"fill",
|
|
619
|
-
"
|
|
666
|
+
"input",
|
|
667
|
+
"meter",
|
|
668
|
+
"value"
|
|
620
669
|
],
|
|
621
670
|
"values": {
|
|
622
671
|
"variant": {
|
|
@@ -643,6 +692,14 @@
|
|
|
643
692
|
"type": "Number",
|
|
644
693
|
"default": 0.25
|
|
645
694
|
},
|
|
695
|
+
"segmentMode": {
|
|
696
|
+
"type": "String",
|
|
697
|
+
"default": "discrete"
|
|
698
|
+
},
|
|
699
|
+
"format": {
|
|
700
|
+
"type": "String",
|
|
701
|
+
"default": ""
|
|
702
|
+
},
|
|
646
703
|
"optimum": {
|
|
647
704
|
"type": "Number"
|
|
648
705
|
},
|
|
@@ -657,11 +714,16 @@
|
|
|
657
714
|
"classes": [],
|
|
658
715
|
"actions": [
|
|
659
716
|
"clamp",
|
|
717
|
+
"formattedValue",
|
|
660
718
|
"percent",
|
|
719
|
+
"refresh",
|
|
661
720
|
"render",
|
|
662
721
|
"renderBar",
|
|
663
722
|
"renderMeter",
|
|
723
|
+
"renderRange",
|
|
664
724
|
"renderRing",
|
|
725
|
+
"renderSegmented",
|
|
726
|
+
"renderValueText",
|
|
665
727
|
"setCircumference",
|
|
666
728
|
"setValue"
|
|
667
729
|
],
|