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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +23 -0
- data/README.md +15 -1
- data/app/assets/javascripts/stimulus-plumbers/controllers.manifest.json +331 -20
- data/app/assets/javascripts/stimulus-plumbers/index.es.js +493 -226
- data/app/assets/javascripts/stimulus-plumbers/index.es.js.map +1 -1
- data/app/assets/javascripts/stimulus-plumbers/index.umd.js +1 -1
- data/app/assets/javascripts/stimulus-plumbers/index.umd.js.map +1 -1
- data/app/assets/stylesheets/stimulus_plumbers/tokens.css +58 -53
- data/docs/architecture.md +0 -2
- data/docs/component/avatar.md +1 -0
- data/docs/component/button.md +2 -1
- data/docs/component/calendar.md +18 -20
- data/docs/component/card.md +14 -12
- data/docs/component/checklist.md +153 -0
- data/docs/component/combobox.md +2 -2
- data/docs/component/form.md +32 -6
- data/docs/component/icon.md +7 -5
- data/docs/component/indicator.md +55 -0
- data/docs/component/list.md +4 -5
- data/docs/component/ordered_list.md +118 -0
- data/docs/component/plumber.md +43 -0
- data/docs/component/popover.md +1 -2
- data/docs/component/progress.md +65 -0
- data/docs/component/theme.md +15 -2
- data/docs/component/timeline.md +49 -14
- data/docs/guide.md +39 -0
- data/lib/generators/stimulus_plumbers/install/install_generator.rb +33 -0
- data/lib/stimulus_plumbers/components/button.rb +2 -1
- data/lib/stimulus_plumbers/components/card.rb +1 -1
- data/lib/stimulus_plumbers/components/checklist/item/slots.rb +13 -0
- data/lib/stimulus_plumbers/components/checklist/item.rb +64 -0
- data/lib/stimulus_plumbers/components/checklist.rb +50 -0
- data/lib/stimulus_plumbers/components/combobox/typeahead.rb +1 -0
- data/lib/stimulus_plumbers/components/combobox.rb +1 -1
- data/lib/stimulus_plumbers/components/icon.rb +2 -2
- data/lib/stimulus_plumbers/components/indicator.rb +30 -0
- data/lib/stimulus_plumbers/components/link.rb +2 -1
- data/lib/stimulus_plumbers/components/list/item.rb +1 -1
- data/lib/stimulus_plumbers/components/ordered_list/item/slots.rb +13 -0
- data/lib/stimulus_plumbers/components/ordered_list/item.rb +129 -0
- data/lib/stimulus_plumbers/components/ordered_list.rb +24 -0
- data/lib/stimulus_plumbers/components/progress_bar.rb +48 -0
- data/lib/stimulus_plumbers/components/progress_meter.rb +37 -0
- data/lib/stimulus_plumbers/components/progress_ring.rb +40 -0
- data/lib/stimulus_plumbers/components/timeline/event.rb +3 -2
- data/lib/stimulus_plumbers/components/timeline/group.rb +5 -3
- data/lib/stimulus_plumbers/engine.rb +4 -0
- data/lib/stimulus_plumbers/form/fields/inputs/password.rb +1 -0
- data/lib/stimulus_plumbers/form/fields/inputs/search.rb +1 -0
- data/lib/stimulus_plumbers/generators/css_entrypoint.rb +80 -0
- data/lib/stimulus_plumbers/generators/tokens_directive.rb +24 -0
- data/lib/stimulus_plumbers/helpers/avatar_helper.rb +1 -7
- data/lib/stimulus_plumbers/helpers/button_helper.rb +1 -7
- data/lib/stimulus_plumbers/helpers/calendar_helper.rb +2 -9
- data/lib/stimulus_plumbers/helpers/calendar_turbo_helper.rb +3 -6
- data/lib/stimulus_plumbers/helpers/checklist_helper.rb +11 -0
- data/lib/stimulus_plumbers/helpers/indicator_helper.rb +11 -0
- data/lib/stimulus_plumbers/helpers/ordered_list_helper.rb +11 -0
- data/lib/stimulus_plumbers/helpers/popover_helper.rb +1 -7
- data/lib/stimulus_plumbers/helpers/progress_helper.rb +21 -0
- data/lib/stimulus_plumbers/helpers.rb +8 -0
- data/lib/stimulus_plumbers/plumber/slots.rb +10 -3
- data/lib/stimulus_plumbers/themes/base.rb +4 -0
- data/lib/stimulus_plumbers/themes/schema/icon.rb +21 -4
- data/lib/stimulus_plumbers/themes/schema/indicator/ranges.rb +14 -0
- data/lib/stimulus_plumbers/themes/schema.rb +39 -3
- data/lib/stimulus_plumbers/version.rb +1 -1
- data/lib/stimulus_plumbers.rb +10 -0
- data/lib/tasks/stimulus_plumbers.rake +18 -0
- data/vendor/ARIA.md +108 -0
- data/vendor/component/manifest.json +212 -0
- data/vendor/controller/docs/calendar.md +212 -0
- data/vendor/controller/docs/checklist.md +45 -0
- data/vendor/controller/docs/clipboard.md +61 -0
- data/vendor/controller/docs/combobox.md +212 -0
- data/vendor/controller/docs/dismisser.md +39 -0
- data/vendor/controller/docs/flipper.md +46 -0
- data/vendor/controller/docs/input-clearable.md +43 -0
- data/vendor/controller/docs/input-formatter.md +89 -0
- data/vendor/controller/docs/modal.md +57 -0
- data/vendor/controller/docs/panner.md +24 -0
- data/vendor/controller/docs/popover.md +111 -0
- data/vendor/controller/docs/progress.md +82 -0
- data/vendor/controller/docs/reorderable.md +82 -0
- data/vendor/controller/docs/timeline.md +84 -0
- data/vendor/controller/docs/visibility.md +31 -0
- data/vendor/controller/guide.md +16 -0
- data/vendor/{controllers.manifest.json → controller/manifest.json} +331 -20
- metadata +44 -2
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
# calendar-month
|
|
2
|
+
|
|
3
|
+
Renders an accessible calendar grid for a given month. Driven by the `Calendar` plumber, which provides navigation, disabled dates, and range constraints.
|
|
4
|
+
|
|
5
|
+
## Targets
|
|
6
|
+
|
|
7
|
+
| Target | Description |
|
|
8
|
+
| ------------- | ------------------------------------ |
|
|
9
|
+
| `daysOfWeek` | Container for the weekday header row |
|
|
10
|
+
| `daysOfMonth` | Container for the day grid rows |
|
|
11
|
+
|
|
12
|
+
## Classes
|
|
13
|
+
|
|
14
|
+
| Class | Description |
|
|
15
|
+
| ----------------- | ----------------------------------------- |
|
|
16
|
+
| `dayOfWeek` | Applied to each weekday header cell |
|
|
17
|
+
| `dayOfMonth` | Applied to each day cell |
|
|
18
|
+
| `dayOfOtherMonth` | Applied to day cells from adjacent months |
|
|
19
|
+
| `row` | Applied to each week row |
|
|
20
|
+
|
|
21
|
+
## Values
|
|
22
|
+
|
|
23
|
+
| Value | Type | Default | Description |
|
|
24
|
+
| ------------------ | ------- | ------------- | -------------------------------------------------------------------------------------- |
|
|
25
|
+
| `year` | Number | — | Year being displayed |
|
|
26
|
+
| `month` | Number | — | Month being displayed (0-indexed) |
|
|
27
|
+
| `since` | String | `""` | Earliest selectable date (ISO string) |
|
|
28
|
+
| `till` | String | `""` | Latest selectable date (ISO string) |
|
|
29
|
+
| `locales` | Array | `["default"]` | `Intl.DateTimeFormat` locale(s) |
|
|
30
|
+
| `weekdayFormat` | String | `"short"` | Weekday header format: `"short"` \| `"long"` \| `"narrow"` |
|
|
31
|
+
| `dayFormat` | String | `"numeric"` | Day number format |
|
|
32
|
+
| `daysOfOtherMonth` | Boolean | `false` | Show overflow days from adjacent months |
|
|
33
|
+
| `today` | String | `""` | Override the "today" marker (ISO date string); defaults to system date |
|
|
34
|
+
| `selected` | String | `""` | Currently selected date (ISO string); sets `aria-selected="true"` on the matching cell |
|
|
35
|
+
|
|
36
|
+
## Calendar plumber options (programmatic only)
|
|
37
|
+
|
|
38
|
+
These options are not exposed as Stimulus values and must be passed via a subclass or custom controller:
|
|
39
|
+
|
|
40
|
+
| Option | Description |
|
|
41
|
+
| ------------------ | -------------------------------------------- |
|
|
42
|
+
| `firstDayOfWeek` | `0` = Sunday, `1` = Monday, … (default `0`) |
|
|
43
|
+
| `disabledDates` | Array of ISO date strings to disable |
|
|
44
|
+
| `disabledWeekdays` | Array of weekday names or numbers to disable |
|
|
45
|
+
| `disabledDays` | Array of day-of-month numbers to disable |
|
|
46
|
+
| `disabledMonths` | Array of month names or numbers to disable |
|
|
47
|
+
|
|
48
|
+
## Standalone usage
|
|
49
|
+
|
|
50
|
+
```html
|
|
51
|
+
<div
|
|
52
|
+
data-controller="calendar-month"
|
|
53
|
+
data-calendar-month-year-value="2024"
|
|
54
|
+
data-calendar-month-month-value="1"
|
|
55
|
+
data-calendar-month-locales-value='["en-US"]'
|
|
56
|
+
role="grid"
|
|
57
|
+
>
|
|
58
|
+
<div data-calendar-month-target="daysOfWeek"></div>
|
|
59
|
+
<div role="rowgroup" data-calendar-month-target="daysOfMonth"></div>
|
|
60
|
+
</div>
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## Rails helper
|
|
64
|
+
|
|
65
|
+
```ruby
|
|
66
|
+
module ApplicationHelper
|
|
67
|
+
include StimulusPlumbers::Helpers::CalendarHelper
|
|
68
|
+
end
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
```erb
|
|
72
|
+
<%# Current month %>
|
|
73
|
+
<%= sp_calendar_month %>
|
|
74
|
+
|
|
75
|
+
<%# Navigate to a specific date %>
|
|
76
|
+
<%= sp_calendar_month(date: Date.new(2024, 3, 15)) %>
|
|
77
|
+
|
|
78
|
+
<%# With custom classes %>
|
|
79
|
+
<%= sp_calendar_month(class: "my-calendar") %>
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## Actions
|
|
83
|
+
|
|
84
|
+
| Method | Description |
|
|
85
|
+
| ----------------- | ------------------------------------------------------------------------------------------------------------------ |
|
|
86
|
+
| `select(iso)` | Selects a date by ISO string — sets `selectedValue`, updates `aria-selected`, dispatches `calendar-month:selected` |
|
|
87
|
+
| `navigate(date)` | Navigates to the given `Date` — updates `yearValue`/`monthValue` and re-renders the grid |
|
|
88
|
+
| `step(unit, dir)` | Steps the calendar by `unit` (`'day'`/`'month'`/`'year'`) in `dir` (`1`/`-1`) |
|
|
89
|
+
|
|
90
|
+
Click-handling is wired internally — `calendar-month` handles clicks on its own grid and dispatches `calendar-month:selected` automatically.
|
|
91
|
+
|
|
92
|
+
**Dispatches**
|
|
93
|
+
|
|
94
|
+
| Event | Detail | When |
|
|
95
|
+
| -------------------------- | ---------------- | --------------------------------------------------- |
|
|
96
|
+
| `calendar-month:selecting` | — | On every valid cell click, before date is confirmed |
|
|
97
|
+
| `calendar-month:selected` | `{ epoch, iso }` | After a valid date is parsed from the clicked cell |
|
|
98
|
+
| `calendar-month:navigate` | `{ from, to }` | Before navigation begins (ISO strings) |
|
|
99
|
+
| `calendar-month:navigated` | `{ from, to }` | After navigation completes (ISO strings) |
|
|
100
|
+
|
|
101
|
+
## Accessibility
|
|
102
|
+
|
|
103
|
+
- Weekday headers use `role="columnheader"` with `title` for the long name
|
|
104
|
+
- See [ARIA.md's Calendar / Date Picker pattern](../../../ARIA.md) for grid roles, selection/disabled state, and keyboard navigation
|
|
105
|
+
|
|
106
|
+
---
|
|
107
|
+
|
|
108
|
+
## calendar-year
|
|
109
|
+
|
|
110
|
+
Year-view grid controller — renders a 12-month grid and dispatches a `calendar-year:selected` event when a month button is clicked. Pair with `combobox-date` via `data-action` on the orchestrator element.
|
|
111
|
+
|
|
112
|
+
**Targets**
|
|
113
|
+
|
|
114
|
+
| Target | Description |
|
|
115
|
+
| ------ | ------------------------------------------------------- |
|
|
116
|
+
| `grid` | `role="rowgroup"` container the controller renders into |
|
|
117
|
+
|
|
118
|
+
**Values**
|
|
119
|
+
|
|
120
|
+
| Value | Type | Default | Description |
|
|
121
|
+
| ------------- | ------ | ------------- | -------------------------------------------------------------- |
|
|
122
|
+
| `current` | Number | — | Year being displayed |
|
|
123
|
+
| `today` | String | `""` | ISO date string for today marker |
|
|
124
|
+
| `selected` | String | `""` | ISO date string for selected marker |
|
|
125
|
+
| `since` | String | `""` | Earliest selectable date (ISO string) |
|
|
126
|
+
| `till` | String | `""` | Latest selectable date (ISO string) |
|
|
127
|
+
| `locales` | Array | `["default"]` | `Intl.DateTimeFormat` locale(s) |
|
|
128
|
+
| `monthFormat` | String | `"short"` | Month button label format: `"short"` \| `"long"` \| `"narrow"` |
|
|
129
|
+
|
|
130
|
+
**Dispatches**
|
|
131
|
+
|
|
132
|
+
| Event | Detail | When |
|
|
133
|
+
| ------------------------ | ----------- | ------------------------------------------------- |
|
|
134
|
+
| `calendar-year:selected` | `{ month }` | After a valid month button is clicked (1-indexed) |
|
|
135
|
+
|
|
136
|
+
```html
|
|
137
|
+
<!-- action wired on the combobox-date element -->
|
|
138
|
+
<div
|
|
139
|
+
data-controller="combobox-date"
|
|
140
|
+
data-action="calendar-year:selected->combobox-date#onMonthSelect"
|
|
141
|
+
data-combobox-date-calendar-year-outlet="#year_view"
|
|
142
|
+
>
|
|
143
|
+
<div id="year_view" hidden data-controller="calendar-year" role="grid" aria-label="Year view">
|
|
144
|
+
<div data-calendar-year-target="grid" role="rowgroup"></div>
|
|
145
|
+
</div>
|
|
146
|
+
</div>
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
---
|
|
150
|
+
|
|
151
|
+
## calendar-decade
|
|
152
|
+
|
|
153
|
+
Decade-view grid controller — renders a 12-year grid and dispatches a `calendar-decade:selected` event when a year button is clicked. Pair with `combobox-date` via `data-action` on the orchestrator element.
|
|
154
|
+
|
|
155
|
+
**Targets**
|
|
156
|
+
|
|
157
|
+
| Target | Description |
|
|
158
|
+
| ------ | ------------------------------------------------------- |
|
|
159
|
+
| `grid` | `role="rowgroup"` container the controller renders into |
|
|
160
|
+
|
|
161
|
+
**Values**
|
|
162
|
+
|
|
163
|
+
| Value | Type | Default | Description |
|
|
164
|
+
| ---------- | ------ | ------- | -------------------------------------- |
|
|
165
|
+
| `current` | Number | — | Year within the decade being displayed |
|
|
166
|
+
| `today` | String | `""` | ISO date string for today marker |
|
|
167
|
+
| `selected` | String | `""` | ISO date string for selected marker |
|
|
168
|
+
| `since` | String | `""` | Earliest selectable date (ISO string) |
|
|
169
|
+
| `till` | String | `""` | Latest selectable date (ISO string) |
|
|
170
|
+
|
|
171
|
+
**Dispatches**
|
|
172
|
+
|
|
173
|
+
| Event | Detail | When |
|
|
174
|
+
| -------------------------- | ---------- | ------------------------------------ |
|
|
175
|
+
| `calendar-decade:selected` | `{ year }` | After a valid year button is clicked |
|
|
176
|
+
|
|
177
|
+
```html
|
|
178
|
+
<!-- action wired on the combobox-date element -->
|
|
179
|
+
<div
|
|
180
|
+
data-controller="combobox-date"
|
|
181
|
+
data-action="calendar-decade:selected->combobox-date#onYearSelect"
|
|
182
|
+
data-combobox-date-calendar-decade-outlet="#decade_view"
|
|
183
|
+
>
|
|
184
|
+
<div id="decade_view" hidden data-controller="calendar-decade" role="grid" aria-label="Decade view">
|
|
185
|
+
<div data-calendar-decade-target="grid" role="rowgroup"></div>
|
|
186
|
+
</div>
|
|
187
|
+
</div>
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
---
|
|
191
|
+
|
|
192
|
+
## calendar-month-selector
|
|
193
|
+
|
|
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`.
|
|
195
|
+
|
|
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.
|
|
197
|
+
|
|
198
|
+
---
|
|
199
|
+
|
|
200
|
+
## calendar-year-selector
|
|
201
|
+
|
|
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.
|
|
203
|
+
|
|
204
|
+
Used with `sp_calendar_turbo_year` (Rails). See [docs/plumber/calendar-selector.md](../plumber/calendar-selector.md) for the underlying plumber.
|
|
205
|
+
|
|
206
|
+
---
|
|
207
|
+
|
|
208
|
+
## calendar-decade-selector
|
|
209
|
+
|
|
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.
|
|
211
|
+
|
|
212
|
+
Used with `sp_calendar_turbo_decade` (Rails). See [docs/plumber/calendar-selector.md](../plumber/calendar-selector.md) for the underlying plumber.
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# Checklist
|
|
2
|
+
|
|
3
|
+
Master "select all" toggle for a group of native `<input type="checkbox">` checklist items. Aggregates their `.checked` state onto a master checkbox's `checked`/`indeterminate` properties and toggles them all at once. See [stimulus-plumbers-rails's docs/component/checklist.md](../../../stimulus-plumbers-rails/docs/component/checklist.md) for the Rails render options.
|
|
4
|
+
|
|
5
|
+
## Stimulus Identifier
|
|
6
|
+
|
|
7
|
+
`checklist`
|
|
8
|
+
|
|
9
|
+
## Targets
|
|
10
|
+
|
|
11
|
+
| Name | Purpose |
|
|
12
|
+
| -------- | --------------------------------------------------------------------- |
|
|
13
|
+
| `master` | The "select all" `<input type="checkbox">` — receives aggregate state |
|
|
14
|
+
| `item` | Each checklist item `<input type="checkbox">` |
|
|
15
|
+
|
|
16
|
+
## Actions
|
|
17
|
+
|
|
18
|
+
| Name | Purpose |
|
|
19
|
+
| ----------- | -------------------------------------------------------------------------------------------------------------------------------- |
|
|
20
|
+
| `onChange` | Wired to `change` on the wrapper — event adapter: if the event came from the master, calls `toggleAll`; always calls `recompute` |
|
|
21
|
+
| `toggleAll` | Programmatic API — sets every enabled item's `.checked` to the given value |
|
|
22
|
+
| `recompute` | Programmatic API — writes the master's `.checked`/`.indeterminate` from the enabled items' aggregate state |
|
|
23
|
+
|
|
24
|
+
## Example HTML
|
|
25
|
+
|
|
26
|
+
```html
|
|
27
|
+
<div data-controller="checklist" data-action="change->checklist#onChange">
|
|
28
|
+
<label>
|
|
29
|
+
<input type="checkbox" data-checklist-target="master" />
|
|
30
|
+
Select all
|
|
31
|
+
</label>
|
|
32
|
+
|
|
33
|
+
<label>
|
|
34
|
+
<input type="checkbox" data-checklist-target="item" checked />
|
|
35
|
+
Buy milk
|
|
36
|
+
</label>
|
|
37
|
+
|
|
38
|
+
<label>
|
|
39
|
+
<input type="checkbox" data-checklist-target="item" />
|
|
40
|
+
Walk the dog
|
|
41
|
+
</label>
|
|
42
|
+
</div>
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Disabled items (`disabled` attribute) are excluded from both aggregation and bulk toggling — the controller filters them out via `enabledItems()`. `indeterminate` is a JS-only property with no HTML attribute; it is set on connect and after every change, never rendered server-side.
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# clipboard
|
|
2
|
+
|
|
3
|
+
Intercepts native paste events and re-dispatches them as Stimulus events. Also provides a copy action that writes text to the clipboard via the Clipboard API.
|
|
4
|
+
|
|
5
|
+
## Targets
|
|
6
|
+
|
|
7
|
+
| Target | Description |
|
|
8
|
+
| -------- | ----------------------------------------------------------------------------------- |
|
|
9
|
+
| `source` | Element whose `.value` or `.textContent` is copied when no `text` param is provided |
|
|
10
|
+
|
|
11
|
+
## Values
|
|
12
|
+
|
|
13
|
+
| Value | Type | Default | Description |
|
|
14
|
+
| ------------- | ------ | -------------- | ---------------------------------------------------------------------------------------------------------------- |
|
|
15
|
+
| `contentType` | String | `"text/plain"` | MIME type to extract from `clipboardData` on paste. Common: `"text/plain"` \| `"text/html"` \| `"text/uri-list"` |
|
|
16
|
+
|
|
17
|
+
## Methods
|
|
18
|
+
|
|
19
|
+
| Method | Wired via | Description |
|
|
20
|
+
| ---------------- | ----------------- | ---------------------------------------------------------------- |
|
|
21
|
+
| `onPaste(event)` | `paste` DOM event | Event adapter — intercepts paste, dispatches `clipboard:pasted` |
|
|
22
|
+
| `copy(event)` | `data-action` | Action — writes text to clipboard, dispatches `clipboard:copied` |
|
|
23
|
+
|
|
24
|
+
## Dispatches
|
|
25
|
+
|
|
26
|
+
| Event | Detail | When |
|
|
27
|
+
| ----------------------- | ----------------- | -------------------------------- |
|
|
28
|
+
| `clipboard:pasted` | `{ text, types }` | On successful paste interception |
|
|
29
|
+
| `clipboard:copied` | `{ text }` | On successful clipboard write |
|
|
30
|
+
| `clipboard:copy-failed` | `{ error }` | On clipboard write failure |
|
|
31
|
+
|
|
32
|
+
## Paste example
|
|
33
|
+
|
|
34
|
+
```html
|
|
35
|
+
<%# Intercept paste, forward to input-formatter for normalisation %>
|
|
36
|
+
<input
|
|
37
|
+
data-controller="clipboard"
|
|
38
|
+
data-action="paste->clipboard#onPaste clipboard:pasted->input-formatter#onPaste"
|
|
39
|
+
data-clipboard-content-type-value="text/plain"
|
|
40
|
+
/>
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Copy examples
|
|
44
|
+
|
|
45
|
+
```html
|
|
46
|
+
<%# Copy from a source target %>
|
|
47
|
+
<div data-controller="clipboard">
|
|
48
|
+
<input data-clipboard-target="source" value="text to copy" readonly />
|
|
49
|
+
<button data-action="click->clipboard#copy">Copy</button>
|
|
50
|
+
</div>
|
|
51
|
+
|
|
52
|
+
<%# Copy hardcoded text via param %>
|
|
53
|
+
<button data-controller="clipboard" data-action="click->clipboard#copy" data-clipboard-text-param="https://example.com">
|
|
54
|
+
Copy link
|
|
55
|
+
</button>
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Notes
|
|
59
|
+
|
|
60
|
+
- `paste` calls `event.preventDefault()` — the original paste is suppressed
|
|
61
|
+
- Use `clipboard:pasted->input-formatter#onPaste` to forward pasted content for formatting
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
# Combobox Controllers
|
|
2
|
+
|
|
3
|
+
The combobox family uses a layered design. `input-combobox` and `input-formatter` live on the wrapper; a picker sub-controller lives inside the popover.
|
|
4
|
+
|
|
5
|
+
```
|
|
6
|
+
input-combobox + input-formatter ← wrapper
|
|
7
|
+
└── [popover]
|
|
8
|
+
└── combobox-date ← date picker (calendar grid)
|
|
9
|
+
└── combobox-time ← time picker (drum/scroll-wheel)
|
|
10
|
+
└── combobox-dropdown ← listbox (dropdown & typeahead)
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## input-combobox
|
|
16
|
+
|
|
17
|
+
Owns the trigger input, popover visibility, and hidden value. Always co-located with `input-formatter`.
|
|
18
|
+
|
|
19
|
+
**Targets**
|
|
20
|
+
|
|
21
|
+
| Target | Element | Description |
|
|
22
|
+
| --------- | -------------------- | ----------------------------------------------- |
|
|
23
|
+
| `trigger` | `input[type=text]` | The combobox input (display + focus management) |
|
|
24
|
+
| `input` | `input[type=hidden]` | Holds the submitted value |
|
|
25
|
+
|
|
26
|
+
**Values**
|
|
27
|
+
|
|
28
|
+
| Value | Type | Default | Description |
|
|
29
|
+
| ----------- | ------ | ------- | ---------------------------------------------------------------------- |
|
|
30
|
+
| `value` | String | `""` | Current selected value; setting it triggers `valueValueChanged` |
|
|
31
|
+
| `minLength` | Number | `1` | Min query length before typeahead relays to `combobox-dropdown` outlet |
|
|
32
|
+
|
|
33
|
+
**Outlets**
|
|
34
|
+
|
|
35
|
+
| Outlet | Description |
|
|
36
|
+
| ------------------- | ----------------------------------- |
|
|
37
|
+
| `combobox-dropdown` | Optional; present in typeahead mode |
|
|
38
|
+
|
|
39
|
+
**Methods**
|
|
40
|
+
|
|
41
|
+
| Method | Wired via | Description |
|
|
42
|
+
| ----------------- | --------------------- | ---------------------------------------------------------------------------------- |
|
|
43
|
+
| `onSelect(event)` | `combobox-*:selected` | Event adapter — writes `event.detail.value` to `valueValue`; popover handles close |
|
|
44
|
+
| `onInput(event)` | `input` on trigger | Event adapter — extracts query, relays to `comboboxDropdownOutlet.filter(query)` |
|
|
45
|
+
|
|
46
|
+
**Dispatches**
|
|
47
|
+
|
|
48
|
+
| Event | Detail | When |
|
|
49
|
+
| ------------------------ | ----------- | ------------------------------------------------- |
|
|
50
|
+
| `input-combobox:changed` | `{ value }` | After `valueValue` changes (including on connect) |
|
|
51
|
+
|
|
52
|
+
---
|
|
53
|
+
|
|
54
|
+
## input-formatter
|
|
55
|
+
|
|
56
|
+
Formats and displays values. Always co-located with `input-combobox`. See [input-formatter.md](input-formatter.md) for targets, values, methods, and dispatches.
|
|
57
|
+
|
|
58
|
+
---
|
|
59
|
+
|
|
60
|
+
## combobox-date
|
|
61
|
+
|
|
62
|
+
Navigates a calendar grid with month, year, and decade views. Requires a `calendar-month` outlet; `calendar-year` and `calendar-decade` are optional for drill-down. View switching hides/shows outlet elements directly — no named targets for view containers.
|
|
63
|
+
|
|
64
|
+
**Targets**
|
|
65
|
+
|
|
66
|
+
| Target | Description |
|
|
67
|
+
| ----------- | ---------------------------------------------------------------------- |
|
|
68
|
+
| `previous` | Button that steps backward (one month / one year / one decade by view) |
|
|
69
|
+
| `next` | Button that steps forward (one month / one year / one decade by view) |
|
|
70
|
+
| `viewTitle` | Button showing the current view label (e.g. "June 2025" / "2025") |
|
|
71
|
+
| `day` | Rendered day label element (display only) |
|
|
72
|
+
| `month` | Rendered month label element (display only) |
|
|
73
|
+
| `year` | Rendered year label element (display only) |
|
|
74
|
+
|
|
75
|
+
**Outlets**
|
|
76
|
+
|
|
77
|
+
| Outlet | Description |
|
|
78
|
+
| ----------------- | ------------------------------------------------------------ |
|
|
79
|
+
| `calendar-month` | Required — provides the day grid and calendar navigation API |
|
|
80
|
+
| `calendar-year` | Optional — year view grid; shown when `view` is `"year"` |
|
|
81
|
+
| `calendar-decade` | Optional — decade view grid; shown when `view` is `"decade"` |
|
|
82
|
+
|
|
83
|
+
**Values**
|
|
84
|
+
|
|
85
|
+
| Value | Type | Default | Description |
|
|
86
|
+
| ------------- | ------ | ------------- | ----------------------------------------------------------- |
|
|
87
|
+
| `date` | String | `""` | ISO 8601 initial date; navigates calendar on outlet connect |
|
|
88
|
+
| `view` | String | `"month"` | Current view — `"month"` \| `"year"` \| `"decade"` |
|
|
89
|
+
| `locales` | Array | `["default"]` | `Intl.DateTimeFormat` locales |
|
|
90
|
+
| `dayFormat` | String | `"numeric"` | Day label format |
|
|
91
|
+
| `monthFormat` | String | `"long"` | Month label format |
|
|
92
|
+
| `yearFormat` | String | `"numeric"` | Year label format |
|
|
93
|
+
|
|
94
|
+
**Methods**
|
|
95
|
+
|
|
96
|
+
| Method | Wired via | Description |
|
|
97
|
+
| ---------------------- | --------------------------- | ---------------------------------------------------------------------------------------- |
|
|
98
|
+
| `previous()` | click on `previous` target | Steps back: one month (month view), one year (year view), one decade (decade view) |
|
|
99
|
+
| `next()` | click on `next` target | Steps forward: one month (month view), one year (year view), one decade (decade view) |
|
|
100
|
+
| `zoomOut()` | click on `viewTitle` target | Zooms out: month → year → decade view |
|
|
101
|
+
| `onDaySelect(event)` | `calendar-month:selected` | Event adapter — updates `dateValue`, redraws labels, dispatches `combobox-date:selected` |
|
|
102
|
+
| `onMonthSelect(event)` | `calendar-year:selected` | Event adapter — navigates calendar to selected month, switches to month view |
|
|
103
|
+
| `onYearSelect(event)` | `calendar-decade:selected` | Event adapter — navigates calendar to selected year, switches to year view |
|
|
104
|
+
|
|
105
|
+
**Dispatches**
|
|
106
|
+
|
|
107
|
+
| Event | Detail | When |
|
|
108
|
+
| ------------------------ | ---------------------- | ------------------------- |
|
|
109
|
+
| `combobox-date:selected` | `{ value }` (ISO 8601) | After a date is confirmed |
|
|
110
|
+
|
|
111
|
+
---
|
|
112
|
+
|
|
113
|
+
## combobox-time
|
|
114
|
+
|
|
115
|
+
Drum/scroll-wheel time picker. Each drum is a `ul[role=listbox]`.
|
|
116
|
+
|
|
117
|
+
**Targets:** `hour`, `minute`, `period` (period only present in h12 mode)
|
|
118
|
+
|
|
119
|
+
**Methods**
|
|
120
|
+
|
|
121
|
+
| Method | Wired via | Description |
|
|
122
|
+
| ------------------- | ------------------------ | ----------------------------------------------------------------------------------- |
|
|
123
|
+
| `select(value)` | — | Programmatic API — dispatches `combobox-time:selected` with the given 24-hour value |
|
|
124
|
+
| `onSelect(event)` | `click` on a drum option | Event adapter — marks clicked option `aria-selected="true"`, calls `select(value)` |
|
|
125
|
+
| `onNavigate(event)` | `keydown` on a drum | Event adapter — ArrowUp/ArrowDown call `step(drum, delta)` |
|
|
126
|
+
| `step(drum, delta)` | — | Programmatic API — moves selection by `delta` steps in the given drum element |
|
|
127
|
+
|
|
128
|
+
**Dispatches**
|
|
129
|
+
|
|
130
|
+
| Event | Detail | When |
|
|
131
|
+
| ------------------------ | ------------------------------- | ------------------------------------------------------------- |
|
|
132
|
+
| `combobox-time:selected` | `{ value }` (`"HH:MM"` 24-hour) | On connect (if pre-selected) and after every user interaction |
|
|
133
|
+
|
|
134
|
+
---
|
|
135
|
+
|
|
136
|
+
## combobox-dropdown
|
|
137
|
+
|
|
138
|
+
Listbox with client-side fuzzy filter or server-side fetch. Used by both dropdown and typeahead variants.
|
|
139
|
+
|
|
140
|
+
**Targets**
|
|
141
|
+
|
|
142
|
+
| Target | Description |
|
|
143
|
+
| --------- | ----------------------------------------- |
|
|
144
|
+
| `listbox` | `ul[role=listbox]` containing the options |
|
|
145
|
+
| `loading` | Shown during server fetch |
|
|
146
|
+
| `empty` | Shown when no options match |
|
|
147
|
+
|
|
148
|
+
**Values**
|
|
149
|
+
|
|
150
|
+
| Value | Type | Default | Description |
|
|
151
|
+
| ------- | ------ | ------- | ----------------------------------------------------------- |
|
|
152
|
+
| `url` | String | `""` | Fetch URL — empty string activates client-side fuzzy filter |
|
|
153
|
+
| `field` | String | `"q"` | Query parameter name appended to `url` |
|
|
154
|
+
| `delay` | Number | `300` | Debounce delay in ms before issuing a server fetch |
|
|
155
|
+
|
|
156
|
+
**Methods**
|
|
157
|
+
|
|
158
|
+
| Method | Wired via | Description |
|
|
159
|
+
| ------------------- | ------------------------ | --------------------------------------------------------------------------------------------- |
|
|
160
|
+
| `select(value)` | — | Programmatic API — sets `aria-selected` on matching option, dispatches `selected` |
|
|
161
|
+
| `onSelect(event)` | `click` on an option | Event adapter — extracts value from click target, calls `select(value)` |
|
|
162
|
+
| `onNavigate(event)` | `keydown` on listbox | Event adapter — ArrowUp/ArrowDown call `step(delta)`; Enter/Space activates current selection |
|
|
163
|
+
| `step(delta)` | — | Programmatic API — moves `aria-selected` by `delta` steps (+1 down, -1 up) |
|
|
164
|
+
| `filter(query)` | `input-combobox` outlet | Programmatic API — routes to fuzzy filter (client) or debounced fetch (server) |
|
|
165
|
+
| `showAll()` | `input-combobox#onInput` | Programmatic API — unhides all options when query drops below `minLength` |
|
|
166
|
+
|
|
167
|
+
**Dispatches**
|
|
168
|
+
|
|
169
|
+
| Event | Detail | When |
|
|
170
|
+
| ---------------------------- | ----------- | --------------------------- |
|
|
171
|
+
| `combobox-dropdown:selected` | `{ value }` | After an option is selected |
|
|
172
|
+
|
|
173
|
+
**Filter implementation**
|
|
174
|
+
|
|
175
|
+
`combobox-dropdown` uses two standalone utilities internally:
|
|
176
|
+
|
|
177
|
+
- **Client-side** (`url` is empty): calls `filterOptions(listboxTarget, query)` from `researcher.js` — hides non-matching `[role="option"]` elements using fuzzy matching by default. See [Researcher docs](../utility/researcher.md) for strategy and field options.
|
|
178
|
+
- **Server-side** (`url` is set): debounces requests via `Requestor` from `requestor.js`, then issues `GET {url}?{field}={query}`. See [Requestor docs](../utility/requestor.md) for abort and debounce behaviour.
|
|
179
|
+
|
|
180
|
+
**Server fetch contract**
|
|
181
|
+
|
|
182
|
+
`GET {url}?{field}={query}` — expected response: an HTML fragment of `<li role="option" data-value="...">` elements, which replaces the inner HTML of the `listbox` target.
|
|
183
|
+
|
|
184
|
+
---
|
|
185
|
+
|
|
186
|
+
## Event flow
|
|
187
|
+
|
|
188
|
+
```
|
|
189
|
+
user picks value
|
|
190
|
+
└─ combobox-*:selected { value }
|
|
191
|
+
└─ input-combobox#onSelect ← event adapter
|
|
192
|
+
└─ valueValue = value → valueValueChanged
|
|
193
|
+
├─ inputTarget.value = value
|
|
194
|
+
└─ dispatch input-combobox:changed { value }
|
|
195
|
+
└─ input-formatter#onChange ← event adapter
|
|
196
|
+
└─ format(value) ← programmatic API
|
|
197
|
+
├─ formats value
|
|
198
|
+
├─ writes to inputTarget
|
|
199
|
+
└─ dispatch input-formatter:formatted { value }
|
|
200
|
+
|
|
201
|
+
(popover closes separately via popover#closeOnSelect)
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
---
|
|
205
|
+
|
|
206
|
+
## Naming convention
|
|
207
|
+
|
|
208
|
+
| Pattern | Wired via | Role | Example |
|
|
209
|
+
| ------------ | --------------------- | ---------------------------------------------------------- | --------------------------------------------------------------------------------------------- |
|
|
210
|
+
| `onX(event)` | DOM or Stimulus event | Event adapter — extracts payload, calls programmatic API | `onSelect(event)`, `onChange(event)`, `onPaste(event)`, `onInput(event)`, `onNavigate(event)` |
|
|
211
|
+
| `x(value)` | — (called directly) | Programmatic API — pure logic, no event awareness | `select('us')`, `format('4242…')`, `step(1)`, `filter('query')` |
|
|
212
|
+
| `past()` | Plumber | Plumber callback — called by plumber after async operation | `shown()`, `dismissed()`, `contentLoaded()` |
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# dismisser
|
|
2
|
+
|
|
3
|
+
Closes/hides an element when the user clicks outside it. Backed by the `Dismisser` plumber.
|
|
4
|
+
|
|
5
|
+
## Targets
|
|
6
|
+
|
|
7
|
+
| Target | Description |
|
|
8
|
+
| --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
9
|
+
| `trigger` | Optional — passed as `{ target }` context to the `dismissed()` callback. Does not change the click-outside boundary (which is always the controller element). |
|
|
10
|
+
|
|
11
|
+
## Methods
|
|
12
|
+
|
|
13
|
+
| Method | Wired via | Description |
|
|
14
|
+
| ------------- | ----------------- | ---------------------------------------------------------------------------------------------------- |
|
|
15
|
+
| `dismissed()` | Dismisser plumber | Plumber callback — called when a click-outside is detected. Implement in your controller to respond. |
|
|
16
|
+
|
|
17
|
+
## Example
|
|
18
|
+
|
|
19
|
+
```html
|
|
20
|
+
<div data-controller="dismisser">
|
|
21
|
+
<div data-dismisser-target="trigger">
|
|
22
|
+
<p>Click outside this box to dismiss it.</p>
|
|
23
|
+
</div>
|
|
24
|
+
</div>
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Implement `dismissed()` on your controller to act on the dismissal:
|
|
28
|
+
|
|
29
|
+
```javascript
|
|
30
|
+
async dismissed() {
|
|
31
|
+
await this.collapse()
|
|
32
|
+
}
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Notes
|
|
36
|
+
|
|
37
|
+
- The Dismisser plumber attaches a document-level `mousedown` listener. When a click lands outside the **controller element**, it calls `dismissed()`. If a `trigger` target is present, it is passed to `dismissed()` as `{ target }` context — it does not change the boundary.
|
|
38
|
+
- Used internally by `modal` (custom overlay mode) and `form-field` to handle click-outside-to-close.
|
|
39
|
+
- `dismissed()` is not called when clicking on the trigger element itself.
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# flipper
|
|
2
|
+
|
|
3
|
+
Positions a floating element (`reference`) relative to an anchor using the `Flipper` plumber. Useful for tooltips, dropdowns, and popovers that need smart placement.
|
|
4
|
+
|
|
5
|
+
## Targets
|
|
6
|
+
|
|
7
|
+
| Target | Description |
|
|
8
|
+
| ----------- | --------------------------------------------------- |
|
|
9
|
+
| `anchor` | The element to position relative to (e.g. a button) |
|
|
10
|
+
| `reference` | The floating element to position (e.g. a tooltip) |
|
|
11
|
+
|
|
12
|
+
## Values
|
|
13
|
+
|
|
14
|
+
| Value | Type | Default | Description |
|
|
15
|
+
| ----------- | ------ | ----------- | ------------------------------------------------------------------- |
|
|
16
|
+
| `placement` | String | `"bottom"` | Preferred placement: `"top"` \| `"bottom"` \| `"left"` \| `"right"` |
|
|
17
|
+
| `alignment` | String | `"start"` | Alignment along the cross axis: `"start"` \| `"center"` \| `"end"` |
|
|
18
|
+
| `role` | String | `"tooltip"` | ARIA role applied to the `reference` element |
|
|
19
|
+
|
|
20
|
+
## Methods
|
|
21
|
+
|
|
22
|
+
| Method | Wired via | Description |
|
|
23
|
+
| ----------- | ------------------------------------------ | --------------------------------------------------------------------------------------------------- |
|
|
24
|
+
| `flip()` | Flipper plumber (enhanced onto controller) | Programmatic API — recalculates and applies position; called automatically on `click` events |
|
|
25
|
+
| `flipped()` | Flipper plumber | Plumber callback — called after every position calculation. Override to react to placement changes. |
|
|
26
|
+
|
|
27
|
+
## Example
|
|
28
|
+
|
|
29
|
+
```html
|
|
30
|
+
<div
|
|
31
|
+
data-controller="flipper"
|
|
32
|
+
data-flipper-placement-value="bottom"
|
|
33
|
+
data-flipper-alignment-value="start"
|
|
34
|
+
data-flipper-role-value="tooltip"
|
|
35
|
+
>
|
|
36
|
+
<button data-flipper-target="anchor" aria-describedby="my-tooltip">Hover me</button>
|
|
37
|
+
|
|
38
|
+
<div id="my-tooltip" data-flipper-target="reference" hidden>Tooltip text</div>
|
|
39
|
+
</div>
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Notes
|
|
43
|
+
|
|
44
|
+
- The Flipper plumber calculates available space and flips placement to the opposite side if the preferred side is out of viewport.
|
|
45
|
+
- `role` is set on the `reference` element at connect time via `aria-describedby` / `aria-controls` wiring.
|
|
46
|
+
- `flip()` is triggered automatically on `click` events; call it manually after programmatically showing the `reference` element.
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# input-clearable
|
|
2
|
+
|
|
3
|
+
Shows a clear button when an input has a value, hides it when empty, and clears the input on demand. Keyboard users can also press Escape inside the input to clear it.
|
|
4
|
+
|
|
5
|
+
## Targets
|
|
6
|
+
|
|
7
|
+
| Target | Element | Description |
|
|
8
|
+
| ------- | ----------------------- | ------------------------------------- |
|
|
9
|
+
| `input` | `<input type="search">` | The search input to monitor and clear |
|
|
10
|
+
| `clear` | `<button>` | The button that triggers clearing |
|
|
11
|
+
|
|
12
|
+
## Methods
|
|
13
|
+
|
|
14
|
+
| Method | Wired via | Description |
|
|
15
|
+
| --------- | ------------- | ----------------------------------------------------------------------------------------------- |
|
|
16
|
+
| `clear()` | `data-action` | Empties the input, hides the clear button, returns focus to the input, dispatches `input` event |
|
|
17
|
+
|
|
18
|
+
> `draw()` is called automatically by the controller whenever the input value changes. It does not need to be wired via `data-action`.
|
|
19
|
+
|
|
20
|
+
## Example
|
|
21
|
+
|
|
22
|
+
```html
|
|
23
|
+
<div data-controller="input-clearable">
|
|
24
|
+
<input type="search" data-input-clearable-target="input" />
|
|
25
|
+
<button
|
|
26
|
+
type="button"
|
|
27
|
+
aria-label="Clear search"
|
|
28
|
+
data-input-clearable-target="clear"
|
|
29
|
+
data-action="click->input-clearable#clear"
|
|
30
|
+
></button>
|
|
31
|
+
</div>
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Accessibility
|
|
35
|
+
|
|
36
|
+
- The clear button must carry `aria-label="Clear search"` — the controller never overrides it.
|
|
37
|
+
- See [ARIA.md's Input Clearable pattern](../../../ARIA.md) for the clear-button tab-order, Escape-key, and focus-return contract.
|
|
38
|
+
|
|
39
|
+
## Notes
|
|
40
|
+
|
|
41
|
+
- Suppress the native WebKit clear button via CSS to avoid visual duplication: `input[type="search"]::-webkit-search-cancel-button { appearance: none }`.
|
|
42
|
+
- `clear()` dispatches a native `input` event with `bubbles: true` so upstream listeners (e.g. live search) react to the cleared value.
|
|
43
|
+
- The controller is standalone — it has no dependency on `input-formatter` or any other plumber.
|