stimulus_plumbers 0.4.8 → 0.4.10

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 (81) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +39 -0
  3. data/README.md +7 -9
  4. data/app/assets/javascripts/stimulus-plumbers/controllers.manifest.json +367 -23
  5. data/app/assets/javascripts/stimulus-plumbers/index.es.js +678 -233
  6. data/app/assets/javascripts/stimulus-plumbers/index.es.js.map +1 -1
  7. data/app/assets/javascripts/stimulus-plumbers/index.umd.js +1 -1
  8. data/app/assets/javascripts/stimulus-plumbers/index.umd.js.map +1 -1
  9. data/app/assets/stylesheets/stimulus_plumbers/tokens.css +58 -53
  10. data/docs/architecture.md +0 -2
  11. data/docs/component/avatar.md +1 -0
  12. data/docs/component/button.md +1 -0
  13. data/docs/component/calendar.md +7 -16
  14. data/docs/component/checklist.md +153 -0
  15. data/docs/component/form.md +27 -5
  16. data/docs/component/icon.md +7 -5
  17. data/docs/component/indicator.md +55 -0
  18. data/docs/component/list.md +2 -3
  19. data/docs/component/ordered_list.md +118 -0
  20. data/docs/component/plumber.md +21 -0
  21. data/docs/component/popover.md +1 -2
  22. data/docs/component/progress.md +65 -0
  23. data/docs/component/timeline.md +47 -12
  24. data/docs/guide.md +21 -0
  25. data/lib/generators/stimulus_plumbers/install/install_generator.rb +21 -11
  26. data/lib/stimulus_plumbers/components/button.rb +1 -0
  27. data/lib/stimulus_plumbers/components/checklist/item/slots.rb +13 -0
  28. data/lib/stimulus_plumbers/components/checklist/item.rb +64 -0
  29. data/lib/stimulus_plumbers/components/checklist.rb +50 -0
  30. data/lib/stimulus_plumbers/components/combobox/typeahead.rb +1 -0
  31. data/lib/stimulus_plumbers/components/icon.rb +2 -2
  32. data/lib/stimulus_plumbers/components/indicator.rb +30 -0
  33. data/lib/stimulus_plumbers/components/link.rb +1 -0
  34. data/lib/stimulus_plumbers/components/ordered_list/item/slots.rb +13 -0
  35. data/lib/stimulus_plumbers/components/ordered_list/item.rb +129 -0
  36. data/lib/stimulus_plumbers/components/ordered_list.rb +24 -0
  37. data/lib/stimulus_plumbers/components/progress_bar.rb +48 -0
  38. data/lib/stimulus_plumbers/components/progress_meter.rb +37 -0
  39. data/lib/stimulus_plumbers/components/progress_ring.rb +40 -0
  40. data/lib/stimulus_plumbers/components/timeline/event.rb +2 -1
  41. data/lib/stimulus_plumbers/components/timeline/group.rb +5 -3
  42. data/lib/stimulus_plumbers/engine.rb +0 -4
  43. data/lib/stimulus_plumbers/form/builder.rb +4 -0
  44. data/lib/stimulus_plumbers/form/field.rb +1 -1
  45. data/lib/stimulus_plumbers/form/fields/inputs/code.rb +86 -0
  46. data/lib/stimulus_plumbers/form/fields/inputs/credit_card.rb +98 -0
  47. data/lib/stimulus_plumbers/form/fields/inputs/password.rb +1 -0
  48. data/lib/stimulus_plumbers/form/fields/inputs/search.rb +1 -0
  49. data/lib/stimulus_plumbers/form/fields/renderer.rb +2 -0
  50. data/lib/stimulus_plumbers/generators/css_entrypoint.rb +37 -9
  51. data/lib/stimulus_plumbers/generators/tokens_directive.rb +9 -5
  52. data/lib/stimulus_plumbers/helpers/avatar_helper.rb +1 -7
  53. data/lib/stimulus_plumbers/helpers/button_helper.rb +1 -7
  54. data/lib/stimulus_plumbers/helpers/calendar_helper.rb +2 -9
  55. data/lib/stimulus_plumbers/helpers/calendar_turbo_helper.rb +3 -6
  56. data/lib/stimulus_plumbers/helpers/checklist_helper.rb +11 -0
  57. data/lib/stimulus_plumbers/helpers/indicator_helper.rb +11 -0
  58. data/lib/stimulus_plumbers/helpers/ordered_list_helper.rb +11 -0
  59. data/lib/stimulus_plumbers/helpers/popover_helper.rb +1 -7
  60. data/lib/stimulus_plumbers/helpers/progress_helper.rb +21 -0
  61. data/lib/stimulus_plumbers/helpers.rb +8 -0
  62. data/lib/stimulus_plumbers/themes/base.rb +4 -0
  63. data/lib/stimulus_plumbers/themes/schema/icon.rb +21 -4
  64. data/lib/stimulus_plumbers/themes/schema/indicator/ranges.rb +14 -0
  65. data/lib/stimulus_plumbers/themes/schema.rb +82 -25
  66. data/lib/stimulus_plumbers/version.rb +1 -1
  67. data/lib/stimulus_plumbers.rb +10 -0
  68. data/vendor/ARIA.md +29 -2
  69. data/vendor/component/manifest.json +215 -0
  70. data/vendor/controller/docs/calendar.md +4 -7
  71. data/vendor/controller/docs/checklist.md +45 -0
  72. data/vendor/controller/docs/combobox.md +1 -31
  73. data/vendor/controller/docs/input-clearable.md +1 -4
  74. data/vendor/controller/docs/input-formatter.md +88 -23
  75. data/vendor/controller/docs/modal.md +1 -4
  76. data/vendor/controller/docs/popover.md +1 -3
  77. data/vendor/controller/docs/progress.md +82 -0
  78. data/vendor/controller/docs/reorderable.md +82 -0
  79. data/vendor/controller/docs/visibility.md +31 -0
  80. data/vendor/controller/manifest.json +367 -23
  81. metadata +27 -1
@@ -2,7 +2,10 @@
2
2
 
3
3
  require_relative "helpers/plumber_helper"
4
4
  require_relative "helpers/icon_helper"
5
+ require_relative "helpers/indicator_helper"
5
6
  require_relative "helpers/list_helper"
7
+ require_relative "helpers/checklist_helper"
8
+ require_relative "helpers/ordered_list_helper"
6
9
  require_relative "helpers/avatar_helper"
7
10
  require_relative "helpers/button_helper"
8
11
  require_relative "helpers/calendar_helper"
@@ -12,13 +15,17 @@ require_relative "helpers/combobox_helper"
12
15
  require_relative "helpers/divider_helper"
13
16
  require_relative "helpers/link_helper"
14
17
  require_relative "helpers/popover_helper"
18
+ require_relative "helpers/progress_helper"
15
19
  require_relative "helpers/timeline_helper"
16
20
 
17
21
  module StimulusPlumbers
18
22
  module Helpers
19
23
  include PlumberHelper
20
24
  include IconHelper
25
+ include IndicatorHelper
21
26
  include ListHelper
27
+ include ChecklistHelper
28
+ include OrderedListHelper
22
29
  include AvatarHelper
23
30
  include ButtonHelper
24
31
  include CalendarHelper
@@ -28,6 +35,7 @@ module StimulusPlumbers
28
35
  include DividerHelper
29
36
  include LinkHelper
30
37
  include PopoverHelper
38
+ include ProgressHelper
31
39
  include TimelineHelper
32
40
  end
33
41
  end
@@ -8,6 +8,8 @@ module StimulusPlumbers
8
8
  class Base
9
9
  SCHEMA = {
10
10
  **Schema::LIST,
11
+ **Schema::CHECKLIST,
12
+ **Schema::ORDERED_LIST,
11
13
  **Schema::AVATAR,
12
14
  **Schema::BUTTON,
13
15
  **Schema::CALENDAR,
@@ -15,9 +17,11 @@ module StimulusPlumbers
15
17
  **Schema::COMBOBOX,
16
18
  **Schema::FORM,
17
19
  **Schema::ICON,
20
+ **Schema::INDICATOR,
18
21
  **Schema::INPUT_GROUP,
19
22
  **Schema::LAYOUT,
20
23
  **Schema::LINK,
24
+ **Schema::PROGRESS,
21
25
  **Schema::TIMELINE
22
26
  }.freeze
23
27
 
@@ -23,7 +23,7 @@ module StimulusPlumbers
23
23
 
24
24
  ELEMENT_ATTRS = {
25
25
  path: %i[d fill fill_rule clip_rule stroke_linecap stroke_linejoin opacity].freeze,
26
- circle: %i[cx cy r].freeze,
26
+ circle: %i[cx cy r class stroke stroke_width stroke_linecap].freeze,
27
27
  ellipse: %i[cx cy rx ry].freeze,
28
28
  rect: %i[x y width height rx ry].freeze,
29
29
  line: %i[x1 y1 x2 y2].freeze,
@@ -64,9 +64,26 @@ module StimulusPlumbers
64
64
  end
65
65
 
66
66
  def resolve_element_attrs(element_data)
67
- element_data.slice(*ELEMENT_ATTRS[element_data[:tag]])
68
- .transform_keys { |key| ELEMENT_ATTR_NAMES.fetch(key, key.to_s) }
69
- .transform_values(&:to_s)
67
+ element_data.filter_map do |key, value|
68
+ next if key == :tag
69
+ next unless element_attr_allowed?(element_data[:tag], key)
70
+
71
+ [element_attr_name(key), value.to_s]
72
+ end.to_h
73
+ end
74
+
75
+ def element_attr_allowed?(tag, key)
76
+ ELEMENT_ATTRS.fetch(tag, []).include?(key) || data_or_aria_attr?(key)
77
+ end
78
+
79
+ def element_attr_name(key)
80
+ return key.to_s.tr("_", "-") if data_or_aria_attr?(key)
81
+
82
+ ELEMENT_ATTR_NAMES.fetch(key, key.to_s)
83
+ end
84
+
85
+ def data_or_aria_attr?(key)
86
+ key.to_s.start_with?("data_", "aria_")
70
87
  end
71
88
  end
72
89
  end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module StimulusPlumbers
4
+ module Themes
5
+ module Schema
6
+ module Indicator
7
+ module Ranges
8
+ TYPE = %i[dot pulse badge].freeze
9
+ VARIANT = %i[primary secondary tertiary success destructive warning info].freeze
10
+ end
11
+ end
12
+ end
13
+ end
14
+ end
@@ -5,6 +5,7 @@ require_relative "schema/ranges"
5
5
  require_relative "schema/avatar/ranges"
6
6
  require_relative "schema/button/ranges"
7
7
  require_relative "schema/card/ranges"
8
+ require_relative "schema/indicator/ranges"
8
9
  require_relative "schema/link/ranges"
9
10
  require_relative "schema/form/ranges"
10
11
  require_relative "schema/form/checkbox/ranges"
@@ -27,6 +28,24 @@ module StimulusPlumbers
27
28
  list_item_description: {}.freeze
28
29
  }.freeze
29
30
 
31
+ CHECKLIST = {
32
+ checklist: {}.freeze,
33
+ checklist_item: {}.freeze,
34
+ checklist_item_input: {}.freeze,
35
+ checklist_item_content: {}.freeze,
36
+ checklist_item_title: {}.freeze,
37
+ checklist_item_description: {}.freeze
38
+ }.freeze
39
+
40
+ ORDERED_LIST = {
41
+ ordered_list: {}.freeze,
42
+ ordered_list_item: {}.freeze,
43
+ ordered_list_item_handle: {}.freeze,
44
+ ordered_list_item_content: {}.freeze,
45
+ ordered_list_item_title: {}.freeze,
46
+ ordered_list_item_description: {}.freeze
47
+ }.freeze
48
+
30
49
  AVATAR = {
31
50
  avatar: {
32
51
  size: { default: :md, validate: Avatar::Ranges::SIZE },
@@ -100,76 +119,106 @@ module StimulusPlumbers
100
119
  }.freeze
101
120
 
102
121
  FORM = {
103
- form_group: {
122
+ form_group: {
104
123
  layout: { default: :stacked, validate: Form::Ranges::LAYOUT },
105
124
  error: { default: false, validate: Ranges::BOOL }
106
125
  }.freeze,
107
- form_field_label: {
126
+ form_field_label: {
108
127
  required: { default: false, validate: Ranges::BOOL },
109
128
  hidden: { default: false, validate: Ranges::BOOL },
110
129
  floating: { default: nil, validate: [nil, *Form::Floating::Ranges::TYPE] },
111
130
  error: { default: false, validate: Ranges::BOOL }
112
131
  }.freeze,
113
- form_field_required_mark: {}.freeze,
114
- form_field_hint: {}.freeze,
115
- form_field_error: {}.freeze,
116
- form_field_choice_items: {
132
+ form_field_required_mark: {}.freeze,
133
+ form_field_hint: {}.freeze,
134
+ form_field_error: {}.freeze,
135
+ form_field_choice_items: {
117
136
  layout: { default: :stacked, validate: Form::Ranges::LAYOUT }
118
137
  }.freeze,
119
- form_field_checkbox_label: {
138
+ form_field_checkbox_label: {
120
139
  type: { default: :default, validate: Form::Checkbox::Ranges::TYPE },
121
140
  variant: { default: :tertiary, validate: Form::Checkbox::Ranges::VARIANT }
122
141
  }.freeze,
123
- form_field_radio_label: {
142
+ form_field_radio_label: {
124
143
  type: { default: :default, validate: Form::Radio::Ranges::TYPE },
125
144
  variant: { default: :tertiary, validate: Form::Radio::Ranges::VARIANT }
126
145
  }.freeze,
127
- form_field_radio_item_group: {}.freeze,
128
- form_field_input: {
146
+ form_field_radio_item_group: {}.freeze,
147
+ form_field_input: {
129
148
  error: { default: false, validate: Ranges::BOOL },
130
149
  floating: { default: nil, validate: [nil, *Form::Floating::Ranges::TYPE] }
131
150
  }.freeze,
132
- form_field_input_group: {
151
+ form_field_input_group: {
133
152
  floating: { default: nil, validate: [nil, *Form::Floating::Ranges::TYPE] }
134
153
  }.freeze,
135
- form_field_input_textarea: {
154
+ form_field_input_textarea: {
136
155
  error: { default: false, validate: Ranges::BOOL },
137
156
  floating: { default: nil, validate: [nil, *Form::Floating::Ranges::TYPE] }
138
157
  }.freeze,
139
- form_field_input_file: {
158
+ form_field_input_file: {
140
159
  error: { default: false, validate: Ranges::BOOL },
141
160
  floating: { default: nil, validate: [nil, *Form::Floating::Ranges::TYPE] }
142
161
  }.freeze,
143
- form_field_input_select: {
162
+ form_field_input_select: {
144
163
  error: { default: false, validate: Ranges::BOOL },
145
164
  floating: { default: nil, validate: [nil, *Form::Floating::Ranges::TYPE] }
146
165
  }.freeze,
147
- form_field_input_checkbox: {
166
+ form_field_input_checkbox: {
148
167
  error: { default: false, validate: Ranges::BOOL },
149
168
  type: { default: :default, validate: Form::Checkbox::Ranges::TYPE },
150
169
  variant: { default: :tertiary, validate: Form::Checkbox::Ranges::VARIANT }
151
170
  }.freeze,
152
- form_field_input_radio: {
171
+ form_field_input_radio: {
153
172
  error: { default: false, validate: Ranges::BOOL },
154
173
  type: { default: :default, validate: Form::Radio::Ranges::TYPE },
155
174
  variant: { default: :tertiary, validate: Form::Radio::Ranges::VARIANT }
156
175
  }.freeze,
157
- form_field_input_combobox: {
176
+ form_field_input_combobox: {
158
177
  error: { default: false, validate: Ranges::BOOL },
159
178
  floating: { default: nil, validate: [nil, *Form::Floating::Ranges::TYPE] }
160
179
  }.freeze,
161
- form_field_input_reveal: { error: { default: false, validate: Ranges::BOOL } }.freeze,
162
- form_field_input_clearable: {}.freeze,
163
- form_field_input_button_reveal: {}.freeze,
164
- form_field_input_button_clear: {}.freeze,
165
- form_submit: {
180
+ form_field_input_reveal: { error: { default: false, validate: Ranges::BOOL } }.freeze,
181
+ form_field_input_clearable: {}.freeze,
182
+ form_field_input_code: {
183
+ error: { default: false, validate: Ranges::BOOL }
184
+ }.freeze,
185
+ form_field_input_code_cells: {}.freeze,
186
+ form_field_input_code_cell: {
187
+ error: { default: false, validate: Ranges::BOOL }
188
+ }.freeze,
189
+ form_field_input_code_overlay: {
190
+ error: { default: false, validate: Ranges::BOOL }
191
+ }.freeze,
192
+ form_field_input_credit_card: {
193
+ error: { default: false, validate: Ranges::BOOL }
194
+ }.freeze,
195
+ form_field_input_credit_card_cells: {}.freeze,
196
+ form_field_input_credit_card_cell: {
197
+ error: { default: false, validate: Ranges::BOOL }
198
+ }.freeze,
199
+ form_field_input_credit_card_separator: {}.freeze,
200
+ form_field_input_credit_card_overlay: {
201
+ error: { default: false, validate: Ranges::BOOL }
202
+ }.freeze,
203
+ form_field_input_button_reveal: {}.freeze,
204
+ form_field_input_button_clear: {}.freeze,
205
+ form_submit: {
166
206
  type: { default: :default, validate: Button::Ranges::TYPE },
167
207
  variant: { default: :primary, validate: Button::Ranges::VARIANT }
168
208
  }.freeze
169
209
  }.freeze
170
210
 
171
211
  ICON = {
172
- icon: {}.freeze
212
+ icon: { size: { default: :lg, validate: %i[sm md lg] } }.freeze
213
+ }.freeze
214
+
215
+ INDICATOR = {
216
+ indicator: {
217
+ type: { default: :dot, validate: Indicator::Ranges::TYPE },
218
+ variant: { default: :primary, validate: Indicator::Ranges::VARIANT }
219
+ }.freeze,
220
+ indicator_wrapper: {}.freeze,
221
+ indicator_pulse: {}.freeze
173
222
  }.freeze
174
223
 
175
224
  INPUT_GROUP = {
@@ -196,6 +245,13 @@ module StimulusPlumbers
196
245
  popover: {}.freeze
197
246
  }.freeze
198
247
 
248
+ PROGRESS = {
249
+ progress_bar: {}.freeze,
250
+ progress_bar_fill: {}.freeze,
251
+ progress_ring: {}.freeze,
252
+ progress_meter: {}.freeze
253
+ }.freeze
254
+
199
255
  TIMELINE = {
200
256
  timeline: {
201
257
  orientation: { default: :vertical, validate: %i[vertical horizontal] }
@@ -216,7 +272,6 @@ module StimulusPlumbers
216
272
  timeline_item_description: {}.freeze,
217
273
  timeline_item_detail: {}.freeze,
218
274
  timeline_item_actions: {}.freeze,
219
- timeline_item_indicator_dot: {}.freeze,
220
275
  timeline_item_connector: {}.freeze,
221
276
  timeline_item_content: {}.freeze,
222
277
  timeline_track_line: {}.freeze,
@@ -224,7 +279,9 @@ module StimulusPlumbers
224
279
  timeline_group: {}.freeze,
225
280
  timeline_group_section: {}.freeze,
226
281
  timeline_group_section_date: {}.freeze,
227
- timeline_group_section_list: {}.freeze
282
+ timeline_group_section_list: {
283
+ orientation: { default: :vertical, validate: %i[vertical horizontal] }
284
+ }.freeze
228
285
  }.freeze
229
286
  end
230
287
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module StimulusPlumbers
4
- VERSION = "0.4.8"
4
+ VERSION = "0.4.10"
5
5
  end
@@ -17,6 +17,7 @@ require_relative "stimulus_plumbers/plumber/base"
17
17
 
18
18
  # -- UI components --
19
19
  require_relative "stimulus_plumbers/components/icon"
20
+ require_relative "stimulus_plumbers/components/indicator"
20
21
  require_relative "stimulus_plumbers/components/avatar"
21
22
  require_relative "stimulus_plumbers/components/button"
22
23
  require_relative "stimulus_plumbers/components/button/slots"
@@ -25,10 +26,16 @@ require_relative "stimulus_plumbers/components/link"
25
26
  require_relative "stimulus_plumbers/components/link/slots"
26
27
  require_relative "stimulus_plumbers/components/card"
27
28
  require_relative "stimulus_plumbers/components/card/slots"
29
+ require_relative "stimulus_plumbers/components/checklist"
30
+ require_relative "stimulus_plumbers/components/checklist/item"
31
+ require_relative "stimulus_plumbers/components/checklist/item/slots"
28
32
  require_relative "stimulus_plumbers/components/list"
29
33
  require_relative "stimulus_plumbers/components/list/section"
30
34
  require_relative "stimulus_plumbers/components/list/item"
31
35
  require_relative "stimulus_plumbers/components/list/item/slots"
36
+ require_relative "stimulus_plumbers/components/ordered_list"
37
+ require_relative "stimulus_plumbers/components/ordered_list/item"
38
+ require_relative "stimulus_plumbers/components/ordered_list/item/slots"
32
39
  require_relative "stimulus_plumbers/components/timeline"
33
40
  require_relative "stimulus_plumbers/components/timeline/event"
34
41
  require_relative "stimulus_plumbers/components/timeline/event/slots"
@@ -38,6 +45,9 @@ require_relative "stimulus_plumbers/components/input_group"
38
45
  require_relative "stimulus_plumbers/components/popover"
39
46
  require_relative "stimulus_plumbers/components/popover/trigger"
40
47
  require_relative "stimulus_plumbers/components/popover/panel"
48
+ require_relative "stimulus_plumbers/components/progress_bar"
49
+ require_relative "stimulus_plumbers/components/progress_ring"
50
+ require_relative "stimulus_plumbers/components/progress_meter"
41
51
 
42
52
  # -- Calendar --
43
53
  require_relative "stimulus_plumbers/components/calendar"
data/vendor/ARIA.md CHANGED
@@ -30,6 +30,7 @@ Two helper classes handle keyboard navigation in controllers — see [`stimulus-
30
30
  - `role="dialog"`, `aria-modal="true"`, `aria-labelledby` pointing to heading
31
31
  - Focus moves into dialog on open; returns to trigger on close
32
32
  - Focus trapped inside — Tab/Shift+Tab cycle within; Escape closes
33
+ - Status announcements ("Modal opened"/"Modal closed") via `aria-live` on open/close (WCAG 4.1.3)
33
34
 
34
35
  #### Popover (`popover_controller`)
35
36
  - `role="dialog"` or `role="tooltip"` depending on interactivity
@@ -57,9 +58,22 @@ Two helper classes handle keyboard navigation in controllers — see [`stimulus-
57
58
  - Invalid fields: `aria-invalid="true"` + `aria-describedby` pointing to error message
58
59
  - Error message element: `role="alert"` or `aria-live="polite"` so it's announced
59
60
 
60
- #### Password Reveal (`password_reveal_controller`)
61
+ #### Password Reveal (`input-formatter_controller`, `"password"` formatter)
61
62
  - Toggle button: `aria-label` describes action ("Show password" / "Hide password")
62
- - Or: `aria-pressed` on toggle button
63
+ - `aria-pressed` managed on toggle button, reflecting revealed state
64
+
65
+ #### Code Input (`input_formatter_controller` + `character_cells`)
66
+
67
+ - The real `<input>` is the entire accessible surface; cells are stamped `aria-hidden="true"` automatically.
68
+ - The input requires a `<label>` (visually hidden allowed), `autocomplete="one-time-code"` for OTP (WCAG 1.3.5), `inputmode` matching the charset, and `maxlength` matching the code length.
69
+ - Overlay the input with `opacity: 0` — never `display: none`/`visibility: hidden`, which remove it from the tab order (WCAG 2.1.1).
70
+ - Focus visibility (WCAG 2.4.7): the caret cell (`data-caret`) appears only while the input has focus; style the wrapper with `:focus-within` plus `[data-caret]`.
71
+
72
+ #### Input Clearable (`input_clearable_controller`)
73
+ - Clear button: `hidden` attribute while the input is empty, removing it from the keyboard/AT tab order until there's something to clear
74
+ - Escape inside the input clears it (keyboard equivalent to clicking the clear button, WCAG 2.1.1); default is prevented so it doesn't also close a parent overlay
75
+ - Focus returns to the input after clearing (WCAG 2.4.3 Focus Order)
76
+ - No `aria-live` announcement — clearing is user-initiated and the button's disappearance is self-explanatory
63
77
 
64
78
  #### Flipper / Visibility / Dismisser
65
79
  - Trigger: `aria-expanded="true/false"` when toggling a region
@@ -75,6 +89,13 @@ Two helper classes handle keyboard navigation in controllers — see [`stimulus-
75
89
  - Static list: `role="list"` + `role="listitem"` (explicitly set to preserve semantics when CSS resets strip list role)
76
90
  - Active item: `aria-current="page"` on `<a>` links; `aria-current="true"` on `<button>` items — the value differs by element type per the ARIA spec
77
91
 
92
+ #### Checklist (`checklist_controller`, `sp_checklist`)
93
+ - Each item: native `<input type="checkbox">` inside a `<label>` — role, keyboard activation (Space), focus, and checked-state announcement are all handled by the browser. The component sets no ARIA attributes on items.
94
+ - Read-only item (`readonly: true`): native `disabled` attribute — removes the control from the tab order and announces it as unavailable to assistive tech. No `aria-readonly`/`tabindex` hack.
95
+ - Master "select all" toggle (`select_all:`): same `<input type="checkbox">` shape as an item. Its `indeterminate` property (JS-only, no HTML attribute) is set client-side by the `checklist` controller when some but not all enabled items are checked — modern browsers map `indeterminate` to the accessibility tree's `mixed` checked state automatically, satisfying WCAG 4.1.2 with no manual ARIA.
96
+ - 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.
97
+ - 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.
98
+
78
99
  #### Avatar / Card / Icon
79
100
  - Decorative images/icons: `aria-hidden="true"` or `alt=""`
80
101
  - Meaningful images: descriptive `alt` text
@@ -86,3 +107,9 @@ Two helper classes handle keyboard navigation in controllers — see [`stimulus-
86
107
  - Expandable items: trigger `<button>` with `aria-expanded="false/true"` + `aria-controls` → detail element id; detail has `hidden` attribute toggled by controller
87
108
  - Trigger lives inside `<h3>` (`<h3><button aria-expanded>Title</button></h3>`) — WAI-ARIA Accordion pattern
88
109
  - Keyboard (interactive): Up/Down arrows move focus between item triggers; Home/End jump to first/last trigger; Enter/Space toggle expansion (native button behaviour)
110
+
111
+ #### Reorderable (`reorderable_controller`)
112
+ - Roving tabindex on `item` targets (`RovingTabIndex`) — plain Arrow/Home/End move focus only, unaffected by editing state
113
+ - Keyboard move (`Alt+Arrow` on the axis matching `orientation`, default `ArrowUp`/`ArrowDown`; configurable modifier via `moveKey`; horizontal move-key meaning flips under `dir="rtl"`): moves the focused item, keeps focus on it, announces the new position via `role="status"`/`aria-live` (WCAG 4.1.3) — satisfies the keyboard-equivalent requirement (WCAG 2.1.1) for pointer drag
114
+ - Pointer drag: does not move focus or announce — avoids stealing focus from a mouse user who never asked for it
115
+ - `editingValue` gates both drag and keyboard-move; while `true`, every `trigger` target (`<a>`/`<button>` inside an item) gets `aria-disabled="true"` + `tabindex="-1"` — removes it from keyboard/AT activation and tab order without touching pointer clicks (apps/themes add their own `pointer-events: none` CSS rule for that half)
@@ -0,0 +1,215 @@
1
+ {
2
+ "calendar-decade": {
3
+ "actions": [],
4
+ "listens": [
5
+ "selected"
6
+ ],
7
+ "targets": [
8
+ "grid"
9
+ ],
10
+ "values": []
11
+ },
12
+ "calendar-decade-selector": {
13
+ "actions": [],
14
+ "listens": [],
15
+ "targets": [],
16
+ "values": []
17
+ },
18
+ "calendar-month": {
19
+ "actions": [],
20
+ "listens": [
21
+ "selected"
22
+ ],
23
+ "targets": [
24
+ "daysOfMonth",
25
+ "daysOfWeek"
26
+ ],
27
+ "values": [
28
+ "month",
29
+ "year"
30
+ ]
31
+ },
32
+ "calendar-month-selector": {
33
+ "actions": [],
34
+ "listens": [],
35
+ "targets": [],
36
+ "values": []
37
+ },
38
+ "calendar-year": {
39
+ "actions": [],
40
+ "listens": [
41
+ "selected"
42
+ ],
43
+ "targets": [
44
+ "grid"
45
+ ],
46
+ "values": []
47
+ },
48
+ "calendar-year-selector": {
49
+ "actions": [],
50
+ "listens": [],
51
+ "targets": [],
52
+ "values": []
53
+ },
54
+ "checklist": {
55
+ "actions": [
56
+ "onChange"
57
+ ],
58
+ "listens": [],
59
+ "targets": [],
60
+ "values": []
61
+ },
62
+ "clipboard": {
63
+ "actions": [],
64
+ "listens": [],
65
+ "targets": [],
66
+ "values": []
67
+ },
68
+ "combobox-date": {
69
+ "actions": [],
70
+ "listens": [
71
+ "selected"
72
+ ],
73
+ "targets": [],
74
+ "values": []
75
+ },
76
+ "combobox-dropdown": {
77
+ "actions": [
78
+ "onNavigate",
79
+ "select"
80
+ ],
81
+ "listens": [
82
+ "selected"
83
+ ],
84
+ "targets": [],
85
+ "values": []
86
+ },
87
+ "combobox-time": {
88
+ "actions": [
89
+ "onNavigate",
90
+ "select"
91
+ ],
92
+ "listens": [
93
+ "selected"
94
+ ],
95
+ "targets": [],
96
+ "values": []
97
+ },
98
+ "dismisser": {
99
+ "actions": [],
100
+ "listens": [],
101
+ "targets": [],
102
+ "values": []
103
+ },
104
+ "flipper": {
105
+ "actions": [],
106
+ "listens": [],
107
+ "targets": [],
108
+ "values": []
109
+ },
110
+ "input-clearable": {
111
+ "actions": [
112
+ "clear"
113
+ ],
114
+ "listens": [],
115
+ "targets": [],
116
+ "values": []
117
+ },
118
+ "input-combobox": {
119
+ "actions": [
120
+ "onInput"
121
+ ],
122
+ "listens": [
123
+ "changed"
124
+ ],
125
+ "targets": [],
126
+ "values": []
127
+ },
128
+ "input-formatter": {
129
+ "actions": [
130
+ "onBlur",
131
+ "onFocus",
132
+ "onInput",
133
+ "toggle"
134
+ ],
135
+ "listens": [],
136
+ "targets": [],
137
+ "values": []
138
+ },
139
+ "modal": {
140
+ "actions": [],
141
+ "listens": [],
142
+ "targets": [],
143
+ "values": []
144
+ },
145
+ "panner": {
146
+ "actions": [],
147
+ "listens": [],
148
+ "targets": [],
149
+ "values": []
150
+ },
151
+ "popover": {
152
+ "actions": [
153
+ "close",
154
+ "open",
155
+ "toggle"
156
+ ],
157
+ "listens": [],
158
+ "targets": [],
159
+ "values": []
160
+ },
161
+ "progress": {
162
+ "actions": [],
163
+ "listens": [],
164
+ "targets": [
165
+ "fill",
166
+ "meter"
167
+ ],
168
+ "values": [
169
+ "current",
170
+ "high",
171
+ "indeterminate",
172
+ "low",
173
+ "max",
174
+ "min",
175
+ "optimum",
176
+ "variant"
177
+ ]
178
+ },
179
+ "reorderable": {
180
+ "actions": [
181
+ "onPointerDown",
182
+ "onPointerMove",
183
+ "onPointerUp"
184
+ ],
185
+ "listens": [],
186
+ "targets": [
187
+ "handle",
188
+ "item",
189
+ "trigger"
190
+ ],
191
+ "values": [
192
+ "editing",
193
+ "moveKey",
194
+ "orientation"
195
+ ]
196
+ },
197
+ "timeline": {
198
+ "actions": [
199
+ "toggle"
200
+ ],
201
+ "listens": [],
202
+ "targets": [
203
+ "detail",
204
+ "time",
205
+ "trigger"
206
+ ],
207
+ "values": []
208
+ },
209
+ "visibility": {
210
+ "actions": [],
211
+ "listens": [],
212
+ "targets": [],
213
+ "values": []
214
+ }
215
+ }
@@ -100,11 +100,8 @@ Click-handling is wired internally — `calendar-month` handles clicks on its ow
100
100
 
101
101
  ## Accessibility
102
102
 
103
- - Grid uses `role="grid"` → `role="row"` → `role="gridcell"`
104
103
  - Weekday headers use `role="columnheader"` with `title` for the long name
105
- - Today is marked with `aria-current="date"`
106
- - Disabled dates use `disabled` (buttons) or `aria-disabled="true"` (non-interactive)
107
- - Selected dates use `aria-selected="true"`
104
+ - See [ARIA.md's Calendar / Date Picker pattern](../../../ARIA.md) for grid roles, selection/disabled state, and keyboard navigation
108
105
 
109
106
  ---
110
107
 
@@ -196,7 +193,7 @@ Decade-view grid controller — renders a 12-year grid and dispatches a `calenda
196
193
 
197
194
  Thin SSR/Turbo adapter for server-rendered month grids. Attaches click-to-select behaviour to a server-rendered day grid — no targets or values. The server renders each view as a `<turbo-frame>`; this controller handles day selection and emits the same `calendar-month:selected` event as `calendar-month`.
198
195
 
199
- Used with `sp_calendar_turbo_month` (Rails). Pair with `combobox-date` exactly as you would `calendar-month`.
196
+ Used with `sp_calendar_turbo_month` (Rails). Pair with `combobox-date` exactly as you would `calendar-month`. See [docs/plumber/calendar-selector.md](../plumber/calendar-selector.md) for the underlying plumber.
200
197
 
201
198
  ---
202
199
 
@@ -204,7 +201,7 @@ Used with `sp_calendar_turbo_month` (Rails). Pair with `combobox-date` exactly a
204
201
 
205
202
  Thin SSR/Turbo adapter for server-rendered year (month-grid) views. Attaches click-to-select behaviour — no targets or values. Emits `calendar-year:selected` on month click.
206
203
 
207
- Used with `sp_calendar_turbo_year` (Rails).
204
+ Used with `sp_calendar_turbo_year` (Rails). See [docs/plumber/calendar-selector.md](../plumber/calendar-selector.md) for the underlying plumber.
208
205
 
209
206
  ---
210
207
 
@@ -212,4 +209,4 @@ Used with `sp_calendar_turbo_year` (Rails).
212
209
 
213
210
  Thin SSR/Turbo adapter for server-rendered decade (year-grid) views. Attaches click-to-select behaviour — no targets or values. Emits `calendar-decade:selected` on year click.
214
211
 
215
- Used with `sp_calendar_turbo_decade` (Rails).
212
+ Used with `sp_calendar_turbo_decade` (Rails). See [docs/plumber/calendar-selector.md](../plumber/calendar-selector.md) for the underlying plumber.