stimulus_plumbers 0.4.5 → 0.4.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (90) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +23 -0
  3. data/README.md +15 -1
  4. data/app/assets/javascripts/stimulus-plumbers/controllers.manifest.json +331 -20
  5. data/app/assets/javascripts/stimulus-plumbers/index.es.js +493 -226
  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 +2 -1
  13. data/docs/component/calendar.md +18 -20
  14. data/docs/component/card.md +14 -12
  15. data/docs/component/checklist.md +153 -0
  16. data/docs/component/combobox.md +2 -2
  17. data/docs/component/form.md +32 -6
  18. data/docs/component/icon.md +7 -5
  19. data/docs/component/indicator.md +55 -0
  20. data/docs/component/list.md +4 -5
  21. data/docs/component/ordered_list.md +118 -0
  22. data/docs/component/plumber.md +43 -0
  23. data/docs/component/popover.md +1 -2
  24. data/docs/component/progress.md +65 -0
  25. data/docs/component/theme.md +15 -2
  26. data/docs/component/timeline.md +49 -14
  27. data/docs/guide.md +39 -0
  28. data/lib/generators/stimulus_plumbers/install/install_generator.rb +33 -0
  29. data/lib/stimulus_plumbers/components/button.rb +2 -1
  30. data/lib/stimulus_plumbers/components/card.rb +1 -1
  31. data/lib/stimulus_plumbers/components/checklist/item/slots.rb +13 -0
  32. data/lib/stimulus_plumbers/components/checklist/item.rb +64 -0
  33. data/lib/stimulus_plumbers/components/checklist.rb +50 -0
  34. data/lib/stimulus_plumbers/components/combobox/typeahead.rb +1 -0
  35. data/lib/stimulus_plumbers/components/combobox.rb +1 -1
  36. data/lib/stimulus_plumbers/components/icon.rb +2 -2
  37. data/lib/stimulus_plumbers/components/indicator.rb +30 -0
  38. data/lib/stimulus_plumbers/components/link.rb +2 -1
  39. data/lib/stimulus_plumbers/components/list/item.rb +1 -1
  40. data/lib/stimulus_plumbers/components/ordered_list/item/slots.rb +13 -0
  41. data/lib/stimulus_plumbers/components/ordered_list/item.rb +129 -0
  42. data/lib/stimulus_plumbers/components/ordered_list.rb +24 -0
  43. data/lib/stimulus_plumbers/components/progress_bar.rb +48 -0
  44. data/lib/stimulus_plumbers/components/progress_meter.rb +37 -0
  45. data/lib/stimulus_plumbers/components/progress_ring.rb +40 -0
  46. data/lib/stimulus_plumbers/components/timeline/event.rb +3 -2
  47. data/lib/stimulus_plumbers/components/timeline/group.rb +5 -3
  48. data/lib/stimulus_plumbers/engine.rb +4 -0
  49. data/lib/stimulus_plumbers/form/fields/inputs/password.rb +1 -0
  50. data/lib/stimulus_plumbers/form/fields/inputs/search.rb +1 -0
  51. data/lib/stimulus_plumbers/generators/css_entrypoint.rb +80 -0
  52. data/lib/stimulus_plumbers/generators/tokens_directive.rb +24 -0
  53. data/lib/stimulus_plumbers/helpers/avatar_helper.rb +1 -7
  54. data/lib/stimulus_plumbers/helpers/button_helper.rb +1 -7
  55. data/lib/stimulus_plumbers/helpers/calendar_helper.rb +2 -9
  56. data/lib/stimulus_plumbers/helpers/calendar_turbo_helper.rb +3 -6
  57. data/lib/stimulus_plumbers/helpers/checklist_helper.rb +11 -0
  58. data/lib/stimulus_plumbers/helpers/indicator_helper.rb +11 -0
  59. data/lib/stimulus_plumbers/helpers/ordered_list_helper.rb +11 -0
  60. data/lib/stimulus_plumbers/helpers/popover_helper.rb +1 -7
  61. data/lib/stimulus_plumbers/helpers/progress_helper.rb +21 -0
  62. data/lib/stimulus_plumbers/helpers.rb +8 -0
  63. data/lib/stimulus_plumbers/plumber/slots.rb +10 -3
  64. data/lib/stimulus_plumbers/themes/base.rb +4 -0
  65. data/lib/stimulus_plumbers/themes/schema/icon.rb +21 -4
  66. data/lib/stimulus_plumbers/themes/schema/indicator/ranges.rb +14 -0
  67. data/lib/stimulus_plumbers/themes/schema.rb +39 -3
  68. data/lib/stimulus_plumbers/version.rb +1 -1
  69. data/lib/stimulus_plumbers.rb +10 -0
  70. data/lib/tasks/stimulus_plumbers.rake +18 -0
  71. data/vendor/ARIA.md +108 -0
  72. data/vendor/component/manifest.json +212 -0
  73. data/vendor/controller/docs/calendar.md +212 -0
  74. data/vendor/controller/docs/checklist.md +45 -0
  75. data/vendor/controller/docs/clipboard.md +61 -0
  76. data/vendor/controller/docs/combobox.md +212 -0
  77. data/vendor/controller/docs/dismisser.md +39 -0
  78. data/vendor/controller/docs/flipper.md +46 -0
  79. data/vendor/controller/docs/input-clearable.md +43 -0
  80. data/vendor/controller/docs/input-formatter.md +89 -0
  81. data/vendor/controller/docs/modal.md +57 -0
  82. data/vendor/controller/docs/panner.md +24 -0
  83. data/vendor/controller/docs/popover.md +111 -0
  84. data/vendor/controller/docs/progress.md +82 -0
  85. data/vendor/controller/docs/reorderable.md +82 -0
  86. data/vendor/controller/docs/timeline.md +84 -0
  87. data/vendor/controller/docs/visibility.md +31 -0
  88. data/vendor/controller/guide.md +16 -0
  89. data/vendor/{controllers.manifest.json → controller/manifest.json} +331 -20
  90. metadata +44 -2
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module StimulusPlumbers
4
+ module Helpers
5
+ module ProgressHelper
6
+ def sp_progress_bar(value:, min: 0, max: 100, indeterminate: false, **kwargs)
7
+ Components::ProgressBar.new(self).render(value: value, min: min, max: max, indeterminate: indeterminate, **kwargs)
8
+ end
9
+
10
+ def sp_progress_ring(value:, min: 0, max: 100, indeterminate: false, **kwargs)
11
+ Components::ProgressRing.new(self).render(value: value, min: min, max: max, indeterminate: indeterminate, **kwargs)
12
+ end
13
+
14
+ def sp_progress_meter(value:, min: 0, max: 100, low: nil, high: nil, optimum: nil, **kwargs)
15
+ Components::ProgressMeter.new(self).render(
16
+ value: value, min: min, max: max, low: low, high: high, optimum: optimum, **kwargs
17
+ )
18
+ end
19
+ end
20
+ end
21
+ end
@@ -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
@@ -11,8 +11,9 @@ module StimulusPlumbers
11
11
  end
12
12
  end
13
13
 
14
- def initialize
14
+ def initialize(template = nil)
15
15
  @slots = {}
16
+ @template = template
16
17
  end
17
18
 
18
19
  def resolve(name)
@@ -20,7 +21,7 @@ module StimulusPlumbers
20
21
  return unless entry
21
22
 
22
23
  value = entry[:value]
23
- value = value.call if value.is_a?(Proc)
24
+ value = capture_block(value) if value.is_a?(Proc)
24
25
  block_given? ? yield(value) : value
25
26
  end
26
27
 
@@ -38,6 +39,12 @@ module StimulusPlumbers
38
39
 
39
40
  private
40
41
 
42
+ # Runs the slot block through the view so its ERB output is returned,
43
+ # not written to whatever buffer happens to be active.
44
+ def capture_block(block)
45
+ @template ? @template.capture(&block) : block.call
46
+ end
47
+
41
48
  def set_slot(name, value, options = {})
42
49
  @slots[name] = { value: value, options: options }
43
50
  end
@@ -54,7 +61,7 @@ module StimulusPlumbers
54
61
 
55
62
  def define_by_slot(name, by)
56
63
  define_method(:"with_#{name}") do |&block|
57
- sub = by.new
64
+ sub = by.new(@template)
58
65
  block&.call(sub)
59
66
  set_slot(name, sub)
60
67
  nil
@@ -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 },
@@ -169,7 +188,16 @@ module StimulusPlumbers
169
188
  }.freeze
170
189
 
171
190
  ICON = {
172
- icon: {}.freeze
191
+ icon: { size: { default: :lg, validate: %i[sm md lg] } }.freeze
192
+ }.freeze
193
+
194
+ INDICATOR = {
195
+ indicator: {
196
+ type: { default: :dot, validate: Indicator::Ranges::TYPE },
197
+ variant: { default: :primary, validate: Indicator::Ranges::VARIANT }
198
+ }.freeze,
199
+ indicator_wrapper: {}.freeze,
200
+ indicator_pulse: {}.freeze
173
201
  }.freeze
174
202
 
175
203
  INPUT_GROUP = {
@@ -196,6 +224,13 @@ module StimulusPlumbers
196
224
  popover: {}.freeze
197
225
  }.freeze
198
226
 
227
+ PROGRESS = {
228
+ progress_bar: {}.freeze,
229
+ progress_bar_fill: {}.freeze,
230
+ progress_ring: {}.freeze,
231
+ progress_meter: {}.freeze
232
+ }.freeze
233
+
199
234
  TIMELINE = {
200
235
  timeline: {
201
236
  orientation: { default: :vertical, validate: %i[vertical horizontal] }
@@ -216,7 +251,6 @@ module StimulusPlumbers
216
251
  timeline_item_description: {}.freeze,
217
252
  timeline_item_detail: {}.freeze,
218
253
  timeline_item_actions: {}.freeze,
219
- timeline_item_indicator_dot: {}.freeze,
220
254
  timeline_item_connector: {}.freeze,
221
255
  timeline_item_content: {}.freeze,
222
256
  timeline_track_line: {}.freeze,
@@ -224,7 +258,9 @@ module StimulusPlumbers
224
258
  timeline_group: {}.freeze,
225
259
  timeline_group_section: {}.freeze,
226
260
  timeline_group_section_date: {}.freeze,
227
- timeline_group_section_list: {}.freeze
261
+ timeline_group_section_list: {
262
+ orientation: { default: :vertical, validate: %i[vertical horizontal] }
263
+ }.freeze
228
264
  }.freeze
229
265
  end
230
266
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module StimulusPlumbers
4
- VERSION = "0.4.5"
4
+ VERSION = "0.4.9"
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"
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ namespace :stimulus_plumbers do
4
+ desc "Add the tokens.css @import into the CSS entry file"
5
+ task(:install) do
6
+ unless ENV["STIMULUS_PLUMBERS_SKIP_INSTALL"]
7
+ require "generators/stimulus_plumbers/install/install_generator"
8
+ StimulusPlumbers::Generators::InstallGenerator.new(
9
+ [], {}, { destination_root: Rails.root.to_s }
10
+ ).invoke_all
11
+ end
12
+ end
13
+ end
14
+
15
+ Rake::Task["stimulus_plumbers:install"].enhance(["environment"]) unless ENV["STIMULUS_PLUMBERS_SKIP_INSTALL"]
16
+
17
+ # Hooks into "assets:precompile" as a prerequisite; Rake merges rather than overwrites, so order-independent.
18
+ task "assets:precompile" => ["stimulus_plumbers:install"]
data/vendor/ARIA.md ADDED
@@ -0,0 +1,108 @@
1
+ # WCAG 2.1 AA / ARIA Reference
2
+
3
+ ## Core Criteria (all components)
4
+
5
+ | Criterion | Level | Rule |
6
+ |-----------|-------|------|
7
+ | 1.3.1 Info & Relationships | A | Convey structure via semantic HTML or ARIA roles, not visual styling alone |
8
+ | 1.4.1 Use of Color | A | Never use color as the only means to convey information |
9
+ | 1.4.3 Contrast (text) | AA | 4.5:1 normal text, 3:1 large text (18pt / 14pt bold) |
10
+ | 1.4.11 Non-text Contrast | AA | 3:1 for UI component boundaries and state indicators |
11
+ | 2.1.1 Keyboard | A | All functionality operable via keyboard; no mouse-only interactions |
12
+ | 2.1.2 No Keyboard Trap | A | Focus must be escapable from any component (Escape or documented key) |
13
+ | 2.4.3 Focus Order | A | Focus sequence must be logical and predictable |
14
+ | 2.4.7 Focus Visible | AA | Keyboard focus indicator must be visible |
15
+ | 4.1.2 Name, Role, Value | A | All UI components must have accessible name, role, and state via ARIA or native semantics; state must stay in sync dynamically (e.g. `aria-expanded`, `aria-selected`, `aria-checked`) |
16
+ | 4.1.3 Status Messages | AA | Status/error messages announced via `role="status"` or `aria-live` without focus move |
17
+
18
+ ## JS Keyboard Navigation Patterns
19
+
20
+ Two helper classes handle keyboard navigation in controllers — see [`stimulus-plumbers/docs/accessibility/design.md`](stimulus-plumbers/docs/accessibility/design.md) for full API.
21
+
22
+ | Pattern | Class | When to use |
23
+ | --- | --- | --- |
24
+ | Roving tabindex | `RovingTabIndex` | Disclosure widgets, trees, toolbars — focus moves between items |
25
+ | Managed focus / listbox | `ListboxNavigation` | Combobox listbox, `role="listbox"` — input keeps focus, `aria-selected` moves |
26
+
27
+ ## Component-Specific Patterns (APG)
28
+
29
+ #### Modal (`modal_controller`)
30
+ - `role="dialog"`, `aria-modal="true"`, `aria-labelledby` pointing to heading
31
+ - Focus moves into dialog on open; returns to trigger on close
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)
34
+
35
+ #### Popover (`popover_controller`)
36
+ - `role="dialog"` or `role="tooltip"` depending on interactivity
37
+ - Trigger: `<button>` with `aria-haspopup="dialog"` and `aria-expanded="false"` initially
38
+ - `aria-expanded` toggled to `"true"` / `"false"` by the controller via the `trigger` Stimulus target
39
+ - `aria-controls` linking trigger to panel id is recommended but optional
40
+ - Escape closes and returns focus to trigger
41
+
42
+ #### Combobox (`input_combobox_controller`, `combobox/`)
43
+ - Trigger: `<input role="combobox">` with `aria-haspopup` (`listbox`/`dialog`) and `aria-controls` referencing the **popup element** (the `role="listbox"`/`role="dialog"`)
44
+ - `role="listbox"` permits only `option`/`group` children (`aria-required-children`, WCAG 1.3.1). Status messages (loading, "No results") must be `role="status"`/`aria-live` **siblings of the listbox, never children of it** — for typeahead the listbox is nested in a wrapper panel so the status regions can sit beside it
45
+ - Status/loading regions inside the popover panel must stay **non-focusable** (the popover moves focus into the panel on open)
46
+
47
+ #### Calendar / Date Picker (`calendar-month`, `combobox-date`)
48
+ - Grid: `role="grid"`, `role="row"`, `role="gridcell"`
49
+ - Navigation buttons: `aria-label="Previous Month"` / `"Next Month"` (via `Combobox::Date::Navigation`)
50
+ - Selected date: `aria-selected="true"`; today: `aria-current="date"`
51
+ - Disabled dates: `aria-disabled="true"`, `tabindex="-1"`
52
+ - Three views: month (days grid), year (months grid), decade (years grid); zoom out via `combobox-date#zoomOut`
53
+ - Arrow keys navigate cells; Enter/Space select; Escape closes picker
54
+
55
+ #### Form Fields (`form/`, `form-field_controller`)
56
+ - Every input must have a visible `<label>` via `for`/`id` or `aria-labelledby`
57
+ - Required fields: `required` attribute + `aria-required="true"`
58
+ - Invalid fields: `aria-invalid="true"` + `aria-describedby` pointing to error message
59
+ - Error message element: `role="alert"` or `aria-live="polite"` so it's announced
60
+
61
+ #### Password Reveal (`input-formatter_controller`, `"password"` formatter)
62
+ - Toggle button: `aria-label` describes action ("Show password" / "Hide password")
63
+ - `aria-pressed` managed on toggle button, reflecting revealed state
64
+
65
+ #### Input Clearable (`input_clearable_controller`)
66
+ - Clear button: `hidden` attribute while the input is empty, removing it from the keyboard/AT tab order until there's something to clear
67
+ - 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
68
+ - Focus returns to the input after clearing (WCAG 2.4.3 Focus Order)
69
+ - No `aria-live` announcement — clearing is user-initiated and the button's disappearance is self-explanatory
70
+
71
+ #### Flipper / Visibility / Dismisser
72
+ - Trigger: `aria-expanded="true/false"` when toggling a region
73
+ - Controlled region: `aria-hidden="true"` when collapsed (or removed from DOM)
74
+ - `aria-controls` links trigger to region id
75
+
76
+ #### Button (`button/renderer`)
77
+ - Use `<button>` (not `<div>` / `<a>`) for actions
78
+ - Icon-only buttons must have `aria-label` or visually-hidden text
79
+ - Disabled: `disabled` attribute (not `aria-disabled` alone) unless intentionally focusable
80
+
81
+ #### List (`sp_list`, `sp_list_item`)
82
+ - Static list: `role="list"` + `role="listitem"` (explicitly set to preserve semantics when CSS resets strip list role)
83
+ - Active item: `aria-current="page"` on `<a>` links; `aria-current="true"` on `<button>` items — the value differs by element type per the ARIA spec
84
+
85
+ #### Checklist (`checklist_controller`, `sp_checklist`)
86
+ - 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.
87
+ - 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.
88
+ - 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.
89
+ - 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.
90
+ - 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.
91
+
92
+ #### Avatar / Card / Icon
93
+ - Decorative images/icons: `aria-hidden="true"` or `alt=""`
94
+ - Meaningful images: descriptive `alt` text
95
+
96
+ #### Timeline (`timeline_controller`)
97
+ - List: `<ol>` (ordered) for chronological events; static timelines need no ARIA additions beyond semantic HTML
98
+ - Each item: `<li>`; timestamp displayed via `<time datetime="YYYY-MM-DD">`
99
+ - Indicators (dots, icons, avatars): decorative — always `aria-hidden="true"`
100
+ - Expandable items: trigger `<button>` with `aria-expanded="false/true"` + `aria-controls` → detail element id; detail has `hidden` attribute toggled by controller
101
+ - Trigger lives inside `<h3>` (`<h3><button aria-expanded>Title</button></h3>`) — WAI-ARIA Accordion pattern
102
+ - Keyboard (interactive): Up/Down arrows move focus between item triggers; Home/End jump to first/last trigger; Enter/Space toggle expansion (native button behaviour)
103
+
104
+ #### Reorderable (`reorderable_controller`)
105
+ - Roving tabindex on `item` targets (`RovingTabIndex`) — plain Arrow/Home/End move focus only, unaffected by editing state
106
+ - 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
107
+ - Pointer drag: does not move focus or announce — avoids stealing focus from a mouse user who never asked for it
108
+ - `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,212 @@
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
+ "toggle"
131
+ ],
132
+ "listens": [],
133
+ "targets": [],
134
+ "values": []
135
+ },
136
+ "modal": {
137
+ "actions": [],
138
+ "listens": [],
139
+ "targets": [],
140
+ "values": []
141
+ },
142
+ "panner": {
143
+ "actions": [],
144
+ "listens": [],
145
+ "targets": [],
146
+ "values": []
147
+ },
148
+ "popover": {
149
+ "actions": [
150
+ "close",
151
+ "open",
152
+ "toggle"
153
+ ],
154
+ "listens": [],
155
+ "targets": [],
156
+ "values": []
157
+ },
158
+ "progress": {
159
+ "actions": [],
160
+ "listens": [],
161
+ "targets": [
162
+ "fill",
163
+ "meter"
164
+ ],
165
+ "values": [
166
+ "current",
167
+ "high",
168
+ "indeterminate",
169
+ "low",
170
+ "max",
171
+ "min",
172
+ "optimum",
173
+ "variant"
174
+ ]
175
+ },
176
+ "reorderable": {
177
+ "actions": [
178
+ "onPointerDown",
179
+ "onPointerMove",
180
+ "onPointerUp"
181
+ ],
182
+ "listens": [],
183
+ "targets": [
184
+ "handle",
185
+ "item",
186
+ "trigger"
187
+ ],
188
+ "values": [
189
+ "editing",
190
+ "moveKey",
191
+ "orientation"
192
+ ]
193
+ },
194
+ "timeline": {
195
+ "actions": [
196
+ "toggle"
197
+ ],
198
+ "listens": [],
199
+ "targets": [
200
+ "detail",
201
+ "time",
202
+ "trigger"
203
+ ],
204
+ "values": []
205
+ },
206
+ "visibility": {
207
+ "actions": [],
208
+ "listens": [],
209
+ "targets": [],
210
+ "values": []
211
+ }
212
+ }