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,89 @@
1
+ # input-formatter
2
+
3
+ Formats, masks, and reveals values written to an input element. Handles password reveal toggling and structured display formatting for credit cards, phone numbers, currencies, dates, and times.
4
+
5
+ > When co-located with `input-combobox`, see [combobox.md](combobox.md) for the full event flow and wiring.
6
+
7
+ ## Targets
8
+
9
+ | Target | Element | Description |
10
+ | -------- | ------------------------ | --------------------------------------------------------------------------- |
11
+ | `input` | `<input>` or any element | Write destination — sets `.value` for `<input>`, `.textContent` otherwise |
12
+ | `toggle` | `<button>` | Reveal/conceal button; hidden at connect when the formatter is not maskable |
13
+
14
+ ## Values
15
+
16
+ | Value | Type | Default | Description |
17
+ | ---------- | ------- | --------- | -------------------------------------------------------------------- |
18
+ | `format` | String | `"plain"` | Formatter type — see [Formatters](#formatters) for valid identifiers |
19
+ | `options` | Object | `{}` | Formatter-specific options (e.g. `{ locale: "en-US" }` for currency) |
20
+ | `revealed` | Boolean | `false` | Whether a masked value is currently revealed; managed by `toggle()` |
21
+
22
+ ## Methods
23
+
24
+ | Method | Wired via | Description |
25
+ | ----------------- | ------------------------ | ---------------------------------------------------------------------------------------------- |
26
+ | `format(value)` | — | Programmatic API — normalises, formats/masks, writes to `input` target, dispatches `formatted` |
27
+ | `toggle()` | `data-action` | Action — flips `revealedValue`; no-op unless `format` is `"password"` or formatter is maskable |
28
+ | `onChange(event)` | `input-combobox:changed` | Event adapter — extracts `event.detail.value`, calls `format(value)` |
29
+ | `onPaste(event)` | `clipboard:pasted` | Event adapter — normalises and validates pasted text, calls `format(value)` |
30
+
31
+ ## Dispatches
32
+
33
+ | Event | Detail | When |
34
+ | --------------------------- | ----------- | --------------------------------------- |
35
+ | `input-formatter:formatted` | `{ value }` | After every write to the `input` target |
36
+
37
+ ## Formatters
38
+
39
+ | `type` | Description |
40
+ | -------------- | ------------------------------------------------------------------ |
41
+ | `"plain"` | No-op — passes the value through unchanged |
42
+ | `"password"` | Switches `input[type]` between `"password"` and `"text"` on reveal |
43
+ | `"creditCard"` | Groups digits as `#### #### #### ####` |
44
+ | `"phone"` | Formats as a local phone number |
45
+ | `"currency"` | Locale-aware thousands separator and decimal places |
46
+ | `"date"` | Locale-aware date string |
47
+ | `"time"` | Locale-aware time string |
48
+
49
+ Custom formatters can be registered at runtime:
50
+
51
+ ```js
52
+ import { Formatter } from '@stimulus-plumbers/controllers';
53
+
54
+ Formatter.register('iban', {
55
+ normalize: (raw) => raw.replace(/\s/g, '').toUpperCase(),
56
+ validate: (value) => /^[A-Z]{2}\d{2}[A-Z0-9]+$/.test(value),
57
+ format: (value) => value.replace(/(.{4})/g, '$1 ').trim(),
58
+ });
59
+ ```
60
+
61
+ ## Examples
62
+
63
+ ### Password reveal
64
+
65
+ ```html
66
+ <div data-controller="input-formatter" data-input-formatter-format-value="password">
67
+ <input type="password" data-input-formatter-target="input" />
68
+ <button
69
+ type="button"
70
+ aria-label="Show password"
71
+ aria-pressed="false"
72
+ data-input-formatter-target="toggle"
73
+ data-action="click->input-formatter#toggle"
74
+ ></button>
75
+ </div>
76
+ ```
77
+
78
+ ### Credit card formatting
79
+
80
+ ```html
81
+ <div data-controller="input-formatter" data-input-formatter-format-value="creditCard">
82
+ <input type="text" data-input-formatter-target="input" />
83
+ </div>
84
+ ```
85
+
86
+ ## Accessibility
87
+
88
+ - Password `type` attribute switches between `"password"` (masked) and `"text"` (revealed), so screen readers announce the field type correctly.
89
+ - See [ARIA.md's Password Reveal pattern](../../../ARIA.md) for the `toggle` button's `aria-label`/`aria-pressed` requirements.
@@ -0,0 +1,57 @@
1
+ # modal
2
+
3
+ Implements the [WAI-ARIA Dialog (Modal) pattern](https://www.w3.org/WAI/ARIA/apg/patterns/dialog-modal/). Supports both native `<dialog>` elements and custom overlay implementations.
4
+
5
+ ## Targets
6
+
7
+ | Target | Description |
8
+ | --------- | ----------------------------------------------------------- |
9
+ | `modal` | The dialog element (required) |
10
+ | `overlay` | Optional wrapper shown/hidden around `modal` in custom mode |
11
+
12
+ ## Methods
13
+
14
+ | Method | Wired via | Description |
15
+ | -------------- | ------------------------- | ------------------------------------------------------------------------------ |
16
+ | `open(event)` | `data-action` | Action — opens the modal, traps focus inside |
17
+ | `close(event)` | `data-action`, Escape key | Action — closes the modal, restores focus to trigger |
18
+ | `dismissed()` | Dismisser plumber | Plumber callback — called on click-outside (custom mode only); calls `close()` |
19
+
20
+ ## Native `<dialog>` usage
21
+
22
+ ```html
23
+ <div data-controller="modal">
24
+ <button data-action="modal#open">Open</button>
25
+
26
+ <dialog data-modal-target="modal" aria-labelledby="modal-title" aria-modal="true">
27
+ <h2 id="modal-title">Confirm action</h2>
28
+ <p>Are you sure?</p>
29
+ <button data-action="modal#close">Cancel</button>
30
+ <button>Confirm</button>
31
+ </dialog>
32
+ </div>
33
+ ```
34
+
35
+ The browser handles backdrop rendering. Clicking outside the dialog closes it via `onBackdropClick` (an internal handler, not part of the public API).
36
+
37
+ ## Custom overlay usage
38
+
39
+ ```html
40
+ <div data-controller="modal">
41
+ <button data-action="modal#open">Open</button>
42
+
43
+ <div data-modal-target="overlay" hidden>
44
+ <div data-modal-target="modal" role="dialog" aria-modal="true" aria-labelledby="modal-title">
45
+ <h2 id="modal-title">Title</h2>
46
+ <button data-action="modal#close">Close</button>
47
+ </div>
48
+ </div>
49
+ </div>
50
+ ```
51
+
52
+ Focus is trapped inside `modal` target using `FocusTrap`. Escape key and click-outside (via `dismissed()`) both close the modal.
53
+
54
+ ## Accessibility
55
+
56
+ - `body` scroll is locked while open (custom mode)
57
+ - See [ARIA.md's Modal pattern](../../../ARIA.md) for focus management and the status-announcement contract
@@ -0,0 +1,24 @@
1
+ # panner
2
+
3
+ Keeps a content element within the viewport by applying a CSS `translate` transform when the window resizes. Backed by the `Shifter` plumber.
4
+
5
+ ## Targets
6
+
7
+ | Target | Description |
8
+ | --------- | -------------------------------------------------------------------------- |
9
+ | `content` | Element to keep in-viewport. Defaults to the controller element if absent. |
10
+
11
+ ## Example
12
+
13
+ ```html
14
+ <div data-controller="panner">
15
+ <div data-panner-target="content">
16
+ <!-- content that must stay visible when the window resizes -->
17
+ </div>
18
+ </div>
19
+ ```
20
+
21
+ ## Notes
22
+
23
+ - Shifting is skipped when the `content` target is hidden.
24
+ - See [docs/plumber/shifter.md](../plumber/shifter.md) for the full options API (`boundaries`, `events`, `respectMotion`).
@@ -0,0 +1,111 @@
1
+ # popover
2
+
3
+ Shows and hides a panel with optional remote loading. Owns visibility, `aria-expanded`, outside-click dismissal, and focus management. Backed by the `Visibility`, `Dismisser`, and `ContentLoader` plumbers.
4
+
5
+ This controller is also the visibility/dismissal layer for the combobox family — `input-combobox` runs alongside it and delegates open/close to `popover` (see [combobox docs](./combobox.md)).
6
+
7
+ ## Targets
8
+
9
+ | Target | Description |
10
+ | ---------- | -------------------------------------------------------------------------- |
11
+ | `trigger` | The activator element — opens/toggles the panel and tracks `aria-expanded` |
12
+ | `panel` | The element to show/hide (and load remote content into) |
13
+ | `template` | Optional `<template>` or element whose HTML is used as initial content |
14
+ | `loader` | Optional element shown during remote load |
15
+
16
+ ## Values
17
+
18
+ | Value | Type | Default | Description |
19
+ | --------------- | ------- | ---------------- | ---------------------------------------------------- |
20
+ | `url` | String | — | Remote URL to fetch content from |
21
+ | `reload` | String | `"never"` | When to reload: `"never"` \| `"always"` \| `"stale"` |
22
+ | `staleAfter` | Number | `3600` | Seconds after which content is considered stale |
23
+ | `loadedAt` | String | — | ISO timestamp of last load (set automatically) |
24
+ | `closeOnSelect` | Boolean | `true` | Whether a `#closeOnSelect` call dismisses the panel |
25
+ | `announceOpen` | String | `"Panel opened"` | Screen-reader announcement on panel show |
26
+ | `announceClose` | String | `"Panel closed"` | Screen-reader announcement on panel hide |
27
+
28
+ ## Methods
29
+
30
+ | Method | Wired via | Description |
31
+ | ---------------------------- | --------------------- | ----------------------------------------------------------------------------------------------------------- |
32
+ | `open()` | `data-action` | Action — shows the panel |
33
+ | `close()` | `data-action` | Action — hides the panel (used by Esc, close buttons, outside-click) |
34
+ | `toggle()` | `data-action` | Action — shows when hidden, hides when visible |
35
+ | `closeOnSelect()` | `data-action` | Action — hides the panel only when `closeOnSelect` value is `true`; used by selection events |
36
+ | `shown()` | Visibility plumber | Plumber callback — triggers `load()`, then moves focus into the panel |
37
+ | `hidden()` | Visibility plumber | Plumber callback — returns focus to the `trigger` |
38
+ | `dismissed()` | Dismisser plumber | Plumber callback — closes the panel on outside click |
39
+ | `canLoad()` | ContentLoader plumber | Plumber callback (gate) — returns `false` for `<turbo-frame>` panels (sets `src` instead); `true` otherwise |
40
+ | `contentLoading()` | ContentLoader plumber | Plumber callback — shows the `loader` target while fetching |
41
+ | `contentLoaded({ content })` | ContentLoader plumber | Plumber callback — inserts fetched content into the `panel` target, hides `loader` |
42
+ | `contentLoader()` | ContentLoader plumber | Plumber callback — returns static content from `template` target (if no URL) |
43
+
44
+ ## Examples
45
+
46
+ ### Static popover
47
+
48
+ ```html
49
+ <div data-controller="popover">
50
+ <button
51
+ data-action="click->popover#toggle keydown.esc->popover#close"
52
+ data-popover-target="trigger"
53
+ aria-haspopup="dialog"
54
+ aria-expanded="false"
55
+ aria-controls="opts"
56
+ >
57
+ Open
58
+ </button>
59
+
60
+ <div id="opts" data-popover-target="panel" hidden role="dialog" aria-label="Options">
61
+ <p>Popover content</p>
62
+ <button data-action="click->popover#close">Close</button>
63
+ </div>
64
+ </div>
65
+ ```
66
+
67
+ ### Remote content (fetch on show)
68
+
69
+ ```html
70
+ <div
71
+ data-controller="popover"
72
+ data-popover-url-value="/help/tooltip"
73
+ data-popover-reload-value="stale"
74
+ data-popover-stale-after-value="300"
75
+ >
76
+ <button data-action="click->popover#toggle" data-popover-target="trigger">Help</button>
77
+
78
+ <div data-popover-target="panel" hidden>
79
+ <div data-popover-target="loader" hidden>Loading…</div>
80
+ <div data-popover-target="template"></div>
81
+ </div>
82
+ </div>
83
+ ```
84
+
85
+ ### Turbo Frame (lazy load)
86
+
87
+ When the `panel` target is a `<turbo-frame>`, `canLoad()` sets its `src` attribute on show rather than fetching HTML directly.
88
+
89
+ ```html
90
+ <div data-controller="popover" data-popover-url-value="/preview/123">
91
+ <button data-action="click->popover#toggle" data-popover-target="trigger">Preview</button>
92
+ <turbo-frame data-popover-target="panel" hidden></turbo-frame>
93
+ </div>
94
+ ```
95
+
96
+ ### Keep panel open on select
97
+
98
+ Selection events (from combobox sub-controllers, menu items, etc.) can be wired to `closeOnSelect`. Set `data-popover-close-on-select-value="false"` to keep the panel open after a selection — useful for multi-step pickers (date ranges, multi-select).
99
+
100
+ ```html
101
+ <div data-controller="popover" data-popover-close-on-select-value="false">
102
+ ...
103
+ <ul data-action="my-list:selected->popover#closeOnSelect">
104
+
105
+ </ul>
106
+ </div>
107
+ ```
108
+
109
+ ## Accessibility
110
+
111
+ See [ARIA.md's Popover pattern](../../../ARIA.md) for role, `aria-haspopup`/`aria-expanded`, and focus-return requirements.
@@ -0,0 +1,82 @@
1
+ # Progress
2
+
3
+ Value-driven progress indicator supporting three render variants: a linear bar, an SVG ring, and a native `<meter>`.
4
+
5
+ ## Stimulus Identifier
6
+
7
+ `progress`
8
+
9
+ ## Targets
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 |
15
+
16
+ ## Values
17
+
18
+ | Name | Type | Default | Purpose |
19
+ | ----------------------- | ------- | ------- | ------------------------------------------------------------------------- |
20
+ | `variant` | String | `"bar"` | `"bar"` \| `"ring"` \| `"meter"` |
21
+ | `current` | Number | `0` | Current value |
22
+ | `min` | Number | `0` | Range minimum |
23
+ | `max` | Number | `100` | Range maximum |
24
+ | `optimum` | Number | — | Meter-only; maps to native `<meter optimum>` |
25
+ | `low` | Number | — | Meter-only; maps to native `<meter low>` |
26
+ | `high` | Number | — | Meter-only; maps to native `<meter high>` |
27
+ | `indeterminate` | Boolean | `false` | Suppresses `aria-valuenow`; toggles the `sp-progress-indeterminate` class |
28
+ | `indeterminateFraction` | Number | `0.25` | Static bar width / ring arc fraction rendered while indeterminate |
29
+
30
+ ## Methods
31
+
32
+ | Method | Wired via | Purpose |
33
+ | ---------------------------- | ----------------------- | --------------------------------------------------------------------------------------------------------- |
34
+ | `setValue(value)` | — | Programmatic API — clamps to `[min, max]`, updates `currentValue`, dispatches `progress:changed` |
35
+ | `currentValueChanged(value)` | Stimulus value callback | Recalculates fill/meter attrs whenever `current` changes (covers `setValue()` and direct attribute edits) |
36
+
37
+ ## Dispatches
38
+
39
+ | Event | Detail | When |
40
+ | ------------------ | --------------------- | ------------------------------------ |
41
+ | `progress:changed` | `{ value, min, max }` | After `setValue()` updates the value |
42
+
43
+ ## Example HTML
44
+
45
+ ```html
46
+ <!-- Bar -->
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>
56
+
57
+ <!-- Ring -->
58
+ <svg
59
+ role="progressbar"
60
+ data-controller="progress"
61
+ data-progress-variant-value="ring"
62
+ data-progress-current-value="25"
63
+ data-progress-max-value="100"
64
+ >
65
+ <circle data-progress-target="fill" r="40"></circle>
66
+ </svg>
67
+
68
+ <!-- Meter -->
69
+ <meter
70
+ data-controller="progress"
71
+ data-progress-variant-value="meter"
72
+ data-progress-target="meter"
73
+ data-progress-current-value="40"
74
+ data-progress-min-value="0"
75
+ data-progress-max-value="100"
76
+ ></meter>
77
+
78
+ <!-- Indeterminate -->
79
+ <div role="progressbar" data-controller="progress" data-progress-indeterminate-value="true">
80
+ <div data-progress-target="fill"></div>
81
+ </div>
82
+ ```
@@ -0,0 +1,82 @@
1
+ # Reorderable
2
+
3
+ Reorders a vertical or horizontal list of items via pointer drag (a dedicated handle) or keyboard (`Alt+Arrow`, axis depends on `orientation`). No third-party drag library — built on the native Pointer Events API, backed by the [`Reorderable` plumber](../plumber/reorderable.md). Plain Arrow/Home/End keys move focus only, via [`RovingTabIndex`](../utility/accessibility.md).
4
+
5
+ ## Stimulus Identifier
6
+
7
+ `reorderable`
8
+
9
+ ## Targets
10
+
11
+ | Name | Element | Purpose |
12
+ | --------- | ------------------------------------------- | ---------------------------------------------------------------------------------- |
13
+ | `item` | Each reorderable row (`<li>`, `<tr>`, etc.) | Must have a stable `id` to appear in the `reorderable:reordered` event's `itemIds` |
14
+ | `handle` | Drag grip within each `item` | The only pointer-drag surface — wire `pointerdown`/`pointermove`/`pointerup` to it |
15
+ | `trigger` | The `<a>`/`<button>` inside an item, if any | Neutralized (via `aria-disabled`/`tabindex`) while `editingValue` is `true` |
16
+
17
+ ## Values
18
+
19
+ | Name | Type | Default | Purpose |
20
+ | ------------- | ------- | ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
21
+ | `moveKey` | String | `"Alt"` | Modifier key that, combined with the move keys below on a focused item, moves it. One of `Alt`, `Control`, `Shift`, `Meta`. |
22
+ | `editing` | Boolean | `false` | While `true`, pointer drag and `Alt+Arrow` move are active and every `trigger` target is neutralized. While `false`, drag/keyboard-move are inert no-ops and every `trigger` behaves normally. Plain Arrow/Home/End focus movement is unaffected either way. |
23
+ | `orientation` | String | `"vertical"` | Reorder axis: `"vertical"` or `"horizontal"`. Also passed through to [`RovingTabIndex`](../utility/accessibility.md). |
24
+
25
+ ## Actions
26
+
27
+ | Name | Purpose |
28
+ | --------------- | ---------------------------------------------------------------------------------------------------------- |
29
+ | `onPointerDown` | Wire to `pointerdown` on the `handle` target — starts a drag (no-op unless `editingValue`) |
30
+ | `onPointerMove` | Wire to `pointermove` on the `handle` target — live-reorders while dragging (no-op unless `editingValue`) |
31
+ | `onPointerUp` | Wire to `pointerup` on the `handle` target — ends the drag (no-op unless `editingValue`) |
32
+ | `toggleEditing` | Flips `editingValue` — wire to e.g. `click->reorderable#toggleEditing` on an app-provided Edit/Done button |
33
+ | `enterEditing` | Sets `editingValue` to `true` explicitly |
34
+ | `exitEditing` | Sets `editingValue` to `false` explicitly |
35
+
36
+ ## Keyboard
37
+
38
+ | Key | Behaviour |
39
+ | ------------------------------------------------------------------------------------ | ---------------------------------------------------------------------- |
40
+ | `ArrowDown`/`ArrowUp` (vertical) or `ArrowLeft`/`ArrowRight` (horizontal) | Focus next/previous item (wraps) — unchanged `RovingTabIndex` behavior |
41
+ | `Home` / `End` | Focus first/last item |
42
+ | `Alt+ArrowDown` (vertical) / `Alt+ArrowRight` (horizontal, `Left` under `dir="rtl"`) | Move the focused item forward one position, keeps focus on it |
43
+ | `Alt+ArrowUp` (vertical) / `Alt+ArrowLeft` (horizontal, `Right` under `dir="rtl"`) | Move the focused item back one position, keeps focus on it |
44
+
45
+ ## Dispatches
46
+
47
+ | Event | Detail | When |
48
+ | ----------------------- | ----------------------- | ------------------------------------------------------------------------------------------------- |
49
+ | `reorderable:reordered` | `{ itemIds: string[] }` | After a drag ends or a keyboard move completes. Items without an `id` are omitted from `itemIds`. |
50
+
51
+ ## Example HTML
52
+
53
+ ```html
54
+ <ul data-controller="reorderable">
55
+ <li id="row-1" data-reorderable-target="item" tabindex="0">
56
+ <span
57
+ data-reorderable-target="handle"
58
+ data-action="pointerdown->reorderable#onPointerDown pointermove->reorderable#onPointerMove pointerup->reorderable#onPointerUp"
59
+ >
60
+ ::
61
+ </span>
62
+ First item
63
+ </li>
64
+ <li id="row-2" data-reorderable-target="item" tabindex="-1">
65
+ <span
66
+ data-reorderable-target="handle"
67
+ data-action="pointerdown->reorderable#onPointerDown pointermove->reorderable#onPointerMove pointerup->reorderable#onPointerUp"
68
+ >
69
+ ::
70
+ </span>
71
+ Second item
72
+ </li>
73
+ </ul>
74
+ ```
75
+
76
+ ## Notes
77
+
78
+ - Persistence is not built in — listen for `reorderable:reordered` and send `event.detail.itemIds` to your backend.
79
+ - Single-list only. Grid orientation and cross-list drag are not supported.
80
+ - The dragged/moved item is repositioned live in the DOM — there is no placeholder or ghost element.
81
+ - See [ARIA.md's Reorderable pattern](../../../ARIA.md) for the drag/keyboard-move focus-and-announcement contract and the `trigger` neutralization rules.
82
+ - Pointer clicks on a `trigger` are not blocked by JS. Apps/themes must add their own CSS rule to block them while editing, e.g. `[data-reorderable-editing-value="true"] [data-reorderable-target="trigger"] { pointer-events: none }` — keeps the controller content-agnostic about link/button internals.
@@ -0,0 +1,84 @@
1
+ # Timeline
2
+
3
+ Manages expandable/collapsible timeline event items with keyboard navigation and optional client-side date formatting.
4
+
5
+ ## Stimulus Identifier
6
+
7
+ `timeline`
8
+
9
+ ## Targets
10
+
11
+ | Name | Element | Purpose |
12
+ | --------- | ----------------------------- | --------------------------------------------------------------------------------------- |
13
+ | `trigger` | `<button>` inside each `<h3>` | Controls expand/collapse of its associated detail |
14
+ | `detail` | Collapsible region | Content shown/hidden via `aria-controls` reference |
15
+ | `time` | `<time datetime="…">` | Empty `<time>` elements whose text content is set from `datetime` via `dateFormatValue` |
16
+
17
+ ## Values
18
+
19
+ | Name | Type | Default | Purpose |
20
+ | ------------ | ------ | ------- | ------------------------------------------------------------------------------------------------------- |
21
+ | `dateFormat` | Object | `{}` | `Intl.DateTimeFormat` options applied to empty `<time datetime>` elements on connect. No-op when empty. |
22
+
23
+ ## Actions
24
+
25
+ | Name | Purpose |
26
+ | ---------- | ------------------------------------------- |
27
+ | `toggle` | Expands if collapsed, collapses if expanded |
28
+ | `expand` | Expands the item |
29
+ | `collapse` | Collapses the item |
30
+
31
+ ## Keyboard
32
+
33
+ | Key | Behaviour |
34
+ | ----------- | ------------------------------ |
35
+ | `ArrowDown` | Focus next trigger (wraps) |
36
+ | `ArrowUp` | Focus previous trigger (wraps) |
37
+ | `Home` | Focus first trigger |
38
+ | `End` | Focus last trigger |
39
+
40
+ ## Dispatches
41
+
42
+ | Event | Detail | When |
43
+ | -------------------- | --------------------- | --------------------------------- |
44
+ | `timeline:expand` | `{ trigger, detail }` | When an item begins expanding |
45
+ | `timeline:expanded` | `{ trigger, detail }` | After an item finishes expanding |
46
+ | `timeline:collapse` | `{ trigger, detail }` | When an item begins collapsing |
47
+ | `timeline:collapsed` | `{ trigger, detail }` | After an item finishes collapsing |
48
+
49
+ ## Example HTML
50
+
51
+ ```html
52
+ <!-- Static timeline with server-rendered date text -->
53
+ <ol data-controller="timeline">
54
+ <li>
55
+ <time datetime="2024-01-15">January 2024</time>
56
+ <h3>Event title</h3>
57
+ </li>
58
+ </ol>
59
+
60
+ <!-- Static timeline with client-side date formatting -->
61
+ <ol data-controller="timeline" data-timeline-date-format-value='{"month":"long","year":"numeric","day":"numeric"}'>
62
+ <li>
63
+ <time datetime="2024-01-15"></time>
64
+ <h3>Event title</h3>
65
+ </li>
66
+ </ol>
67
+
68
+ <!-- Interactive timeline with expandable details -->
69
+ <ol data-controller="timeline">
70
+ <li>
71
+ <h3>
72
+ <button
73
+ data-timeline-target="trigger"
74
+ data-action="timeline#toggle"
75
+ aria-expanded="false"
76
+ aria-controls="detail-1"
77
+ >
78
+ Event title
79
+ </button>
80
+ </h3>
81
+ <div id="detail-1" data-timeline-target="detail" hidden>Detail content</div>
82
+ </li>
83
+ </ol>
84
+ ```
@@ -0,0 +1,31 @@
1
+ # visibility
2
+
3
+ Shows/hides a `content` element, keeping it within the viewport and dismissing it on click-outside. Backed by the `Visibility`, `Shifter`, and `Dismisser` plumbers.
4
+
5
+ ## Targets
6
+
7
+ | Target | Description |
8
+ | --------- | --------------------------------------------------------------------------------- |
9
+ | `content` | Element to show/hide. Optional — `toggle()`/`dismissed()` are no-ops when absent. |
10
+
11
+ ## Methods
12
+
13
+ | Method | Wired via | Description |
14
+ | ------------- | ----------------- | -------------------------------------------------------------------------- |
15
+ | `toggle()` | `data-action` | Shows `content` if hidden (shifting it into viewport), hides it if visible |
16
+ | `dismissed()` | Dismisser plumber | Plumber callback — hides `content` on click-outside |
17
+
18
+ ## Example
19
+
20
+ ```html
21
+ <div data-controller="visibility">
22
+ <button data-action="visibility#toggle">Toggle</button>
23
+ <div data-visibility-target="content" hidden>Content</div>
24
+ </div>
25
+ ```
26
+
27
+ ## Notes
28
+
29
+ - See [docs/plumber/visibility.md](../plumber/visibility.md) for the show/hide API and dispatched events.
30
+ - See [docs/plumber/shifter.md](../plumber/shifter.md) for the viewport-shifting behavior applied to `content` on show.
31
+ - See [docs/component/dismisser.md](dismisser.md) for click-outside-to-dismiss behavior.
@@ -0,0 +1,16 @@
1
+ # Guide
2
+
3
+ For a non-Rails / plain JS consumer of `@stimulus-plumbers/controllers`. Rails apps get this wired
4
+ automatically via the `stimulus_plumbers` gem's `sp_*` helpers — skip this guide for Rails.
5
+
6
+ ```bash
7
+ npm install @stimulus-plumbers/controllers
8
+ ```
9
+
10
+ Import and register each controller you use with your Stimulus application — see
11
+ [README.md](../README.md#setup) for the full import + `application.register(...)` list and the
12
+ [Controllers table](../README.md#controllers) for identifiers and their docs.
13
+
14
+ Interactive components (combobox, popover, calendar) expect their `data-controller` attributes to
15
+ already be present in the rendered HTML — Rails apps get these from `sp_*` helpers; a plain-JS setup
16
+ must add them manually per each controller's doc.