@unabridged/midwest 0.15.1 → 0.16.1

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.
@@ -21,6 +21,7 @@ import Chart from '../../../components/midwest/chart_component/chart_component_c
21
21
  export { default as Chart } from '../../../components/midwest/chart_component/chart_component_controller.js';
22
22
  import Range from '../../../components/midwest/form/range_component/range_component_controller.js';
23
23
  import Rating from '../../../components/midwest/rating_component/rating_component_controller.js';
24
+ import Autocomplete from '../../../components/midwest/form/autocomplete_component/autocomplete_component_controller.js';
24
25
 
25
26
  function registerMidwestControllers(application) {
26
27
  application.register("midwest-card", Card);
@@ -42,6 +43,7 @@ function registerMidwestControllers(application) {
42
43
  application.register("midwest-chart", Chart);
43
44
  application.register("midwest-range", Range);
44
45
  application.register("midwest-rating", Rating);
46
+ application.register("midwest-autocomplete", Autocomplete);
45
47
  }
46
48
 
47
49
  export { registerMidwestControllers };
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sources":["../../../../../../../app/assets/javascript/midwest/index.ts"],"sourcesContent":["import Card from '../../../components/midwest/card_component/card_component_controller'\nimport Banner from '../../../components/midwest/banner_component/banner_component_controller'\nimport Input from '../../../components/midwest/form/input_component/input_component_controller'\nimport File from '../../../components/midwest/form/file_component/file_component_controller'\nimport Dropdown from '../../../components/midwest/dropdown_component/dropdown_component_controller'\nimport Prose from '../../../components/midwest/prose_component/prose_component_controller'\nimport Dialog from '../../../components/midwest/dialog_component/dialog_component_controller'\nimport Tabs from '../../../components/midwest/tabs_component/tabs_component_controller'\nimport Tooltip from '../../../components/midwest/tooltip_component/tooltip_component_controller'\nimport Carousel from '../../../components/midwest/carousel_component/carousel_component_controller'\nimport FormLiveSummary from '../../../components/midwest/form/live_summary_component/live_summary_component_controller'\nimport Form from '../../../components/midwest/form/form_controller'\nimport CountdownTimer from '../../../components/midwest/countdown_timer_component/countdown_timer_component_controller'\nimport Badge from '../../../components/midwest/badge_component/badge_component_controller'\nimport Confirmation from '../../../components/midwest/confirmation_component/confirmation_component_controller'\nimport AvatarGroup from '../../../components/midwest/avatar_group_component/avatar_group_component_controller'\nimport Chart from '../../../components/midwest/chart_component/chart_component_controller'\nimport Range from '../../../components/midwest/form/range_component/range_component_controller'\nimport Rating from '../../../components/midwest/rating_component/rating_component_controller'\n/* IMPORTS */\n\nexport { Card, Banner, CountdownTimer, Chart }\n\nexport function registerMidwestControllers (application: any) {\n application.register('midwest-card', Card)\n application.register('midwest-banner', Banner)\n application.register('midwest-countdown-timer', CountdownTimer)\n application.register('midwest-input', Input)\n application.register('midwest-file', File)\n application.register('midwest-dropdown', Dropdown)\n application.register('midwest-prose', Prose)\n application.register('midwest-dialog', Dialog)\n application.register('midwest-tabs', Tabs)\n application.register('midwest-tooltip', Tooltip)\n application.register('midwest-carousel', Carousel)\n application.register('midwest-form-live-summary', FormLiveSummary)\n application.register('midwest-form', Form)\n application.register('midwest-badge', Badge)\n application.register('midwest-confirmation', Confirmation)\n application.register('midwest-avatar-group', AvatarGroup)\n application.register('midwest-chart', Chart)\n application.register('midwest-range', Range)\n application.register('midwest-rating', Rating)\n /* EXPORTS */\n}\n"],"names":["Confirmation"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;AAuBO,SAAS,2BAA4B,WAAkB,EAAA;AAC5D,EAAY,WAAA,CAAA,QAAA,CAAS,gBAAgB,IAAI,CAAA,CAAA;AACzC,EAAY,WAAA,CAAA,QAAA,CAAS,kBAAkB,MAAM,CAAA,CAAA;AAC7C,EAAY,WAAA,CAAA,QAAA,CAAS,2BAA2B,cAAc,CAAA,CAAA;AAC9D,EAAY,WAAA,CAAA,QAAA,CAAS,iBAAiB,KAAK,CAAA,CAAA;AAC3C,EAAY,WAAA,CAAA,QAAA,CAAS,gBAAgB,IAAI,CAAA,CAAA;AACzC,EAAY,WAAA,CAAA,QAAA,CAAS,oBAAoB,QAAQ,CAAA,CAAA;AACjD,EAAY,WAAA,CAAA,QAAA,CAAS,iBAAiB,KAAK,CAAA,CAAA;AAC3C,EAAY,WAAA,CAAA,QAAA,CAAS,kBAAkB,MAAM,CAAA,CAAA;AAC7C,EAAY,WAAA,CAAA,QAAA,CAAS,gBAAgB,IAAI,CAAA,CAAA;AACzC,EAAY,WAAA,CAAA,QAAA,CAAS,mBAAmB,OAAO,CAAA,CAAA;AAC/C,EAAY,WAAA,CAAA,QAAA,CAAS,oBAAoB,QAAQ,CAAA,CAAA;AACjD,EAAY,WAAA,CAAA,QAAA,CAAS,6BAA6B,eAAe,CAAA,CAAA;AACjE,EAAY,WAAA,CAAA,QAAA,CAAS,gBAAgB,IAAI,CAAA,CAAA;AACzC,EAAY,WAAA,CAAA,QAAA,CAAS,iBAAiB,KAAK,CAAA,CAAA;AAC3C,EAAY,WAAA,CAAA,QAAA,CAAS,wBAAwBA,sBAAY,CAAA,CAAA;AACzD,EAAY,WAAA,CAAA,QAAA,CAAS,wBAAwB,WAAW,CAAA,CAAA;AACxD,EAAY,WAAA,CAAA,QAAA,CAAS,iBAAiB,KAAK,CAAA,CAAA;AAC3C,EAAY,WAAA,CAAA,QAAA,CAAS,iBAAiB,KAAK,CAAA,CAAA;AAC3C,EAAY,WAAA,CAAA,QAAA,CAAS,kBAAkB,MAAM,CAAA,CAAA;AAE/C;;;;"}
1
+ {"version":3,"file":"index.js","sources":["../../../../../../../app/assets/javascript/midwest/index.ts"],"sourcesContent":["import Card from '../../../components/midwest/card_component/card_component_controller'\nimport Banner from '../../../components/midwest/banner_component/banner_component_controller'\nimport Input from '../../../components/midwest/form/input_component/input_component_controller'\nimport File from '../../../components/midwest/form/file_component/file_component_controller'\nimport Dropdown from '../../../components/midwest/dropdown_component/dropdown_component_controller'\nimport Prose from '../../../components/midwest/prose_component/prose_component_controller'\nimport Dialog from '../../../components/midwest/dialog_component/dialog_component_controller'\nimport Tabs from '../../../components/midwest/tabs_component/tabs_component_controller'\nimport Tooltip from '../../../components/midwest/tooltip_component/tooltip_component_controller'\nimport Carousel from '../../../components/midwest/carousel_component/carousel_component_controller'\nimport FormLiveSummary from '../../../components/midwest/form/live_summary_component/live_summary_component_controller'\nimport Form from '../../../components/midwest/form/form_controller'\nimport CountdownTimer from '../../../components/midwest/countdown_timer_component/countdown_timer_component_controller'\nimport Badge from '../../../components/midwest/badge_component/badge_component_controller'\nimport Confirmation from '../../../components/midwest/confirmation_component/confirmation_component_controller'\nimport AvatarGroup from '../../../components/midwest/avatar_group_component/avatar_group_component_controller'\nimport Chart from '../../../components/midwest/chart_component/chart_component_controller'\nimport Range from '../../../components/midwest/form/range_component/range_component_controller'\nimport Rating from '../../../components/midwest/rating_component/rating_component_controller'\nimport Autocomplete from '../../../components/midwest/form/autocomplete_component/autocomplete_component_controller'\n/* IMPORTS */\n\nexport { Card, Banner, CountdownTimer, Chart }\n\nexport function registerMidwestControllers (application: any) {\n application.register('midwest-card', Card)\n application.register('midwest-banner', Banner)\n application.register('midwest-countdown-timer', CountdownTimer)\n application.register('midwest-input', Input)\n application.register('midwest-file', File)\n application.register('midwest-dropdown', Dropdown)\n application.register('midwest-prose', Prose)\n application.register('midwest-dialog', Dialog)\n application.register('midwest-tabs', Tabs)\n application.register('midwest-tooltip', Tooltip)\n application.register('midwest-carousel', Carousel)\n application.register('midwest-form-live-summary', FormLiveSummary)\n application.register('midwest-form', Form)\n application.register('midwest-badge', Badge)\n application.register('midwest-confirmation', Confirmation)\n application.register('midwest-avatar-group', AvatarGroup)\n application.register('midwest-chart', Chart)\n application.register('midwest-range', Range)\n application.register('midwest-rating', Rating)\n application.register('midwest-autocomplete', Autocomplete)\n /* EXPORTS */\n}\n"],"names":["Confirmation"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;AAwBO,SAAS,2BAA4B,WAAkB,EAAA;AAC5D,EAAY,WAAA,CAAA,QAAA,CAAS,gBAAgB,IAAI,CAAA,CAAA;AACzC,EAAY,WAAA,CAAA,QAAA,CAAS,kBAAkB,MAAM,CAAA,CAAA;AAC7C,EAAY,WAAA,CAAA,QAAA,CAAS,2BAA2B,cAAc,CAAA,CAAA;AAC9D,EAAY,WAAA,CAAA,QAAA,CAAS,iBAAiB,KAAK,CAAA,CAAA;AAC3C,EAAY,WAAA,CAAA,QAAA,CAAS,gBAAgB,IAAI,CAAA,CAAA;AACzC,EAAY,WAAA,CAAA,QAAA,CAAS,oBAAoB,QAAQ,CAAA,CAAA;AACjD,EAAY,WAAA,CAAA,QAAA,CAAS,iBAAiB,KAAK,CAAA,CAAA;AAC3C,EAAY,WAAA,CAAA,QAAA,CAAS,kBAAkB,MAAM,CAAA,CAAA;AAC7C,EAAY,WAAA,CAAA,QAAA,CAAS,gBAAgB,IAAI,CAAA,CAAA;AACzC,EAAY,WAAA,CAAA,QAAA,CAAS,mBAAmB,OAAO,CAAA,CAAA;AAC/C,EAAY,WAAA,CAAA,QAAA,CAAS,oBAAoB,QAAQ,CAAA,CAAA;AACjD,EAAY,WAAA,CAAA,QAAA,CAAS,6BAA6B,eAAe,CAAA,CAAA;AACjE,EAAY,WAAA,CAAA,QAAA,CAAS,gBAAgB,IAAI,CAAA,CAAA;AACzC,EAAY,WAAA,CAAA,QAAA,CAAS,iBAAiB,KAAK,CAAA,CAAA;AAC3C,EAAY,WAAA,CAAA,QAAA,CAAS,wBAAwBA,sBAAY,CAAA,CAAA;AACzD,EAAY,WAAA,CAAA,QAAA,CAAS,wBAAwB,WAAW,CAAA,CAAA;AACxD,EAAY,WAAA,CAAA,QAAA,CAAS,iBAAiB,KAAK,CAAA,CAAA;AAC3C,EAAY,WAAA,CAAA,QAAA,CAAS,iBAAiB,KAAK,CAAA,CAAA;AAC3C,EAAY,WAAA,CAAA,QAAA,CAAS,kBAAkB,MAAM,CAAA,CAAA;AAC7C,EAAY,WAAA,CAAA,QAAA,CAAS,wBAAwB,YAAY,CAAA,CAAA;AAE3D;;;;"}
@@ -0,0 +1,350 @@
1
+ import { Controller } from '@hotwired/stimulus';
2
+
3
+ class Autocomplete extends Controller {
4
+ static targets = ["input", "dropdown", "list", "tokens", "multipleTokenTemplate", "singleTokenTemplate", "status", "turboList", "loader"];
5
+ static values = {
6
+ options: { type: Array, default: [] },
7
+ url: { type: String, default: "" },
8
+ multiple: { type: Boolean, default: true },
9
+ turboFrameUrl: { type: String, default: "" }
10
+ };
11
+ highlightedIndex = -1;
12
+ fetchAbort = null;
13
+ searchTimer;
14
+ // ── Named handlers ─────────────────────────────────────────────────────────
15
+ // Arrow-function class properties preserve `this` and produce a stable
16
+ // reference so addEventListener/removeEventListener pairs always match.
17
+ onFrameLoad = () => {
18
+ this.syncSelectedState();
19
+ this.loading = false;
20
+ this.showDropdown();
21
+ };
22
+ onToggle = (e) => {
23
+ const opened = e.newState === "open";
24
+ this.inputTarget.setAttribute("aria-expanded", String(opened));
25
+ if (!opened) {
26
+ this.inputTarget.removeAttribute("aria-activedescendant");
27
+ this.highlightedIndex = -1;
28
+ }
29
+ };
30
+ onFocusOut = (e) => {
31
+ if (!this.element.contains(e.relatedTarget)) {
32
+ this.hideDropdown();
33
+ }
34
+ };
35
+ onListMouseDown = (e) => {
36
+ const option = e.target.closest(".midwest-autocomplete-option");
37
+ if (option) {
38
+ e.preventDefault();
39
+ this.pickItem(option);
40
+ }
41
+ };
42
+ // ── Lifecycle ───────────────────────────────────────────────────────────────
43
+ connect() {
44
+ this.element.addEventListener("focusout", this.onFocusOut);
45
+ }
46
+ disconnect() {
47
+ this.fetchAbort?.abort();
48
+ clearTimeout(this.searchTimer);
49
+ this.element.removeEventListener("focusout", this.onFocusOut);
50
+ }
51
+ // Target callbacks — Stimulus calls these automatically when targets connect
52
+ // or disconnect, including after the initial connect(). This replaces manual
53
+ // hasTurboListTarget guards and addEventListener calls inside connect().
54
+ dropdownTargetConnected(el) {
55
+ el.addEventListener("toggle", this.onToggle);
56
+ }
57
+ dropdownTargetDisconnected(el) {
58
+ el.removeEventListener("toggle", this.onToggle);
59
+ }
60
+ listTargetConnected(el) {
61
+ el.addEventListener("mousedown", this.onListMouseDown);
62
+ }
63
+ listTargetDisconnected(el) {
64
+ el.removeEventListener("mousedown", this.onListMouseDown);
65
+ }
66
+ turboListTargetConnected(el) {
67
+ el.addEventListener("turbo:frame-load", this.onFrameLoad);
68
+ }
69
+ turboListTargetDisconnected(el) {
70
+ el.removeEventListener("turbo:frame-load", this.onFrameLoad);
71
+ }
72
+ // ── Public actions ──────────────────────────────────────────────────────────
73
+ focusInput() {
74
+ this.inputTarget.focus();
75
+ this.open();
76
+ }
77
+ open() {
78
+ if (this.dropdownTarget.matches(":popover-open"))
79
+ return;
80
+ clearTimeout(this.searchTimer);
81
+ this.resolve(this.inputTarget.value.trim());
82
+ }
83
+ search() {
84
+ const query = this.inputTarget.value.trim();
85
+ if (!this.hasTurboListTarget && !this.urlValue) {
86
+ this.renderOptions(this.filteredOptions(query));
87
+ return;
88
+ }
89
+ clearTimeout(this.searchTimer);
90
+ this.searchTimer = window.setTimeout(() => {
91
+ this.searchTimer = void 0;
92
+ this.resolve(query);
93
+ }, 200);
94
+ }
95
+ navigate(event) {
96
+ switch (event.key) {
97
+ case "ArrowDown": {
98
+ event.preventDefault();
99
+ if (!this.dropdownTarget.matches(":popover-open"))
100
+ this.open();
101
+ const items = this.visibleOptions();
102
+ this.highlightedIndex = Math.min(this.highlightedIndex + 1, items.length - 1);
103
+ this.syncHighlight(items);
104
+ break;
105
+ }
106
+ case "ArrowUp": {
107
+ event.preventDefault();
108
+ if (!this.dropdownTarget.matches(":popover-open"))
109
+ break;
110
+ const items = this.visibleOptions();
111
+ this.highlightedIndex = Math.max(this.highlightedIndex - 1, 0);
112
+ this.syncHighlight(items);
113
+ break;
114
+ }
115
+ case "Enter": {
116
+ event.preventDefault();
117
+ if (!this.dropdownTarget.matches(":popover-open"))
118
+ break;
119
+ const items = this.visibleOptions();
120
+ if (this.highlightedIndex >= 0 && items[this.highlightedIndex]) {
121
+ this.pickItem(items[this.highlightedIndex]);
122
+ }
123
+ break;
124
+ }
125
+ case "Escape":
126
+ this.hideDropdown();
127
+ break;
128
+ case "Backspace":
129
+ if (this.inputTarget.value === "" && this.multipleValue) {
130
+ const tokens = this.tokensTarget.querySelectorAll("[data-value]");
131
+ const last = tokens[tokens.length - 1];
132
+ if (last?.dataset.value)
133
+ this.deselect(last.dataset.value);
134
+ }
135
+ break;
136
+ }
137
+ }
138
+ removeToken(event) {
139
+ event.stopPropagation();
140
+ const btn = event.currentTarget;
141
+ const value = btn.dataset.value;
142
+ if (value)
143
+ this.deselect(value);
144
+ }
145
+ // Public action used by turbo-frame option items via data-action.
146
+ // Each server-rendered <li> should have:
147
+ // data-action="mousedown->midwest-autocomplete#selectOption"
148
+ selectOption(event) {
149
+ event.preventDefault();
150
+ this.pickItem(event.currentTarget);
151
+ }
152
+ // ── Private ─────────────────────────────────────────────────────────────────
153
+ get optionIdPrefix() {
154
+ return `ac-opt-${(this.element.dataset.name ?? "field").replace(/\W/g, "-")}`;
155
+ }
156
+ isSelected(value) {
157
+ return !!this.tokensTarget.querySelector(`[data-value="${CSS.escape(value)}"]`);
158
+ }
159
+ // Routes to the correct data source. Shared by open() and the debounced search path.
160
+ resolve(query) {
161
+ if (this.hasTurboListTarget) {
162
+ this.navigateFrame(query);
163
+ return;
164
+ }
165
+ if (this.urlValue) {
166
+ this.fetch(query);
167
+ return;
168
+ }
169
+ this.renderOptions(this.filteredOptions(query));
170
+ }
171
+ filteredOptions(query) {
172
+ const q = query.toLowerCase();
173
+ return this.optionsValue.filter(
174
+ (o) => q === "" || o.label.toLowerCase().includes(q) || (o.support?.toLowerCase().includes(q) ?? false)
175
+ );
176
+ }
177
+ navigateFrame(query) {
178
+ const url = new URL(this.turboFrameUrlValue, window.location.href);
179
+ if (query)
180
+ url.searchParams.set("q", query);
181
+ this.tokensTarget.querySelectorAll("input[type=hidden]").forEach((input) => {
182
+ url.searchParams.append("selected[]", input.value);
183
+ });
184
+ this.loading = true;
185
+ this.turboListTarget.setAttribute("src", url.toString());
186
+ this.showDropdown();
187
+ }
188
+ // After a turbo frame loads, mark already-selected items and update the live region.
189
+ // The empty state element is always present in the frame and CSS shows/hides it
190
+ // based on whether any non-selected options exist.
191
+ syncSelectedState() {
192
+ const options = this.turboListTarget.querySelectorAll(".midwest-autocomplete-option");
193
+ options.forEach((option) => {
194
+ const val = option.dataset.value ?? "";
195
+ option.setAttribute("aria-selected", this.isSelected(val) ? "true" : "false");
196
+ });
197
+ const visibleCount = this.visibleOptions().length;
198
+ this.announce(visibleCount > 0 ? `${visibleCount} option${visibleCount === 1 ? "" : "s"} available` : "No more options");
199
+ }
200
+ async fetch(query) {
201
+ this.fetchAbort?.abort();
202
+ this.fetchAbort = new AbortController();
203
+ this.listTarget.replaceChildren();
204
+ this.loading = true;
205
+ this.announce("Loading");
206
+ this.showDropdown();
207
+ try {
208
+ const url = new URL(this.urlValue, window.location.href);
209
+ if (query)
210
+ url.searchParams.set("q", query);
211
+ const res = await globalThis.fetch(url.toString(), {
212
+ signal: this.fetchAbort.signal,
213
+ headers: { Accept: "application/json" }
214
+ });
215
+ const data = await res.json();
216
+ this.renderOptions(data);
217
+ } catch (err) {
218
+ if (err.name !== "AbortError") {
219
+ this.renderEmpty();
220
+ }
221
+ }
222
+ }
223
+ select(option) {
224
+ this.appendToken(option);
225
+ this.hideDropdown();
226
+ this.inputTarget.value = "";
227
+ this.inputTarget.focus();
228
+ this.announce(`${option.label} selected`);
229
+ this.dispatch("change", { detail: { value: option.value, label: option.label } });
230
+ }
231
+ deselect(value) {
232
+ const token = this.tokensTarget.querySelector(`[data-value="${CSS.escape(value)}"]`);
233
+ const label = token?.querySelector(".value")?.textContent?.trim();
234
+ token?.remove();
235
+ if (label)
236
+ this.announce(`${label} removed`);
237
+ this.dispatch("change", { detail: { value: null } });
238
+ }
239
+ appendToken(option) {
240
+ if (!this.multipleValue) {
241
+ this.tokensTarget.querySelectorAll("[data-value]").forEach((el) => el.remove());
242
+ }
243
+ const template = this.multipleValue ? this.multipleTokenTemplateTarget : this.singleTokenTemplateTarget;
244
+ const fragment = template.content.cloneNode(true);
245
+ const badge = fragment.querySelector(".midwest-badge");
246
+ if (!badge)
247
+ return;
248
+ badge.dataset.value = option.value;
249
+ const valueEl = badge.querySelector(".value");
250
+ if (valueEl)
251
+ valueEl.textContent = option.label;
252
+ if (this.multipleValue) {
253
+ const removeBtn = badge.querySelector(".remove");
254
+ if (removeBtn) {
255
+ removeBtn.dataset.value = option.value;
256
+ removeBtn.setAttribute("aria-label", `Remove ${option.label}`);
257
+ }
258
+ }
259
+ const input = badge.querySelector("input[type=hidden]");
260
+ if (input) {
261
+ input.name = this.element.dataset.name ?? "";
262
+ input.value = option.value;
263
+ }
264
+ this.tokensTarget.appendChild(fragment);
265
+ }
266
+ renderOptions(options) {
267
+ this.highlightedIndex = -1;
268
+ if (options.length === 0) {
269
+ this.renderEmpty();
270
+ return;
271
+ }
272
+ this.listTarget.replaceChildren();
273
+ options.forEach((option, index) => {
274
+ const li = document.createElement("li");
275
+ li.id = `${this.optionIdPrefix}-${index}`;
276
+ li.className = "midwest-autocomplete-option";
277
+ li.setAttribute("role", "option");
278
+ li.setAttribute("aria-selected", this.isSelected(option.value) ? "true" : "false");
279
+ li.dataset.value = option.value;
280
+ const label = document.createElement("span");
281
+ label.className = "midwest-autocomplete-option-label";
282
+ label.textContent = option.label;
283
+ li.appendChild(label);
284
+ if (option.support) {
285
+ const support = document.createElement("span");
286
+ support.className = "midwest-autocomplete-option-support";
287
+ support.textContent = option.support;
288
+ li.appendChild(support);
289
+ }
290
+ this.listTarget.appendChild(li);
291
+ });
292
+ const availableCount = options.filter((o) => !this.isSelected(o.value)).length;
293
+ if (availableCount === 0) {
294
+ this.renderEmpty("No more options");
295
+ return;
296
+ }
297
+ this.loading = false;
298
+ this.announce(`${availableCount} option${availableCount === 1 ? "" : "s"} available`);
299
+ this.showDropdown();
300
+ }
301
+ renderEmpty(message = "No results") {
302
+ const li = document.createElement("li");
303
+ li.className = "midwest-autocomplete-empty";
304
+ li.setAttribute("aria-hidden", "true");
305
+ li.textContent = message;
306
+ this.listTarget.replaceChildren(li);
307
+ this.loading = false;
308
+ this.announce(message);
309
+ this.showDropdown();
310
+ }
311
+ set loading(on) {
312
+ if (this.hasLoaderTarget)
313
+ this.loaderTarget.hidden = !on;
314
+ }
315
+ showDropdown() {
316
+ if (!this.dropdownTarget.matches(":popover-open"))
317
+ this.dropdownTarget.showPopover();
318
+ }
319
+ hideDropdown() {
320
+ if (this.dropdownTarget.matches(":popover-open"))
321
+ this.dropdownTarget.hidePopover();
322
+ }
323
+ visibleOptions() {
324
+ const container = this.hasTurboListTarget ? this.turboListTarget : this.listTarget;
325
+ return container.querySelectorAll('.midwest-autocomplete-option:not([aria-selected="true"])');
326
+ }
327
+ syncHighlight(items) {
328
+ items.forEach((item, i) => item.classList.toggle("is-highlighted", i === this.highlightedIndex));
329
+ const active = items[this.highlightedIndex];
330
+ active?.scrollIntoView({ block: "nearest" });
331
+ if (active?.id) {
332
+ this.inputTarget.setAttribute("aria-activedescendant", active.id);
333
+ } else {
334
+ this.inputTarget.removeAttribute("aria-activedescendant");
335
+ }
336
+ }
337
+ pickItem(item) {
338
+ const value = item.dataset.value ?? "";
339
+ const label = item.dataset.label?.trim() || item.querySelector(".midwest-autocomplete-option-label")?.textContent?.trim() || value;
340
+ const support = item.querySelector(".midwest-autocomplete-option-support")?.textContent?.trim();
341
+ this.select({ value, label, support });
342
+ }
343
+ announce(message) {
344
+ if (this.hasStatusTarget)
345
+ this.statusTarget.textContent = message;
346
+ }
347
+ }
348
+
349
+ export { Autocomplete as default };
350
+ //# sourceMappingURL=autocomplete_component_controller.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"autocomplete_component_controller.js","sources":["../../../../../../../../app/components/midwest/form/autocomplete_component/autocomplete_component_controller.ts"],"sourcesContent":["import { Controller } from '@hotwired/stimulus'\n\ninterface Option {\n value: string\n label: string\n support?: string\n}\n\nexport default class Autocomplete extends Controller<HTMLElement> {\n static targets = ['input', 'dropdown', 'list', 'tokens', 'multipleTokenTemplate', 'singleTokenTemplate', 'status', 'turboList', 'loader']\n static values = {\n options: { type: Array, default: [] },\n url: { type: String, default: '' },\n multiple: { type: Boolean, default: true },\n turboFrameUrl: { type: String, default: '' }\n }\n\n declare readonly inputTarget: HTMLInputElement\n declare readonly dropdownTarget: HTMLElement\n declare readonly listTarget: HTMLElement\n declare readonly hasListTarget: boolean\n declare readonly tokensTarget: HTMLElement\n declare readonly multipleTokenTemplateTarget: HTMLTemplateElement\n declare readonly singleTokenTemplateTarget: HTMLTemplateElement\n declare readonly statusTarget: HTMLElement\n declare readonly hasStatusTarget: boolean\n declare readonly turboListTarget: HTMLElement\n declare readonly hasTurboListTarget: boolean\n declare readonly loaderTarget: HTMLElement\n declare readonly hasLoaderTarget: boolean\n\n declare optionsValue: Option[]\n declare urlValue: string\n declare multipleValue: boolean\n declare turboFrameUrlValue: string\n\n private highlightedIndex = -1\n private fetchAbort: AbortController | null = null\n private searchTimer: number | undefined\n\n // ── Named handlers ─────────────────────────────────────────────────────────\n // Arrow-function class properties preserve `this` and produce a stable\n // reference so addEventListener/removeEventListener pairs always match.\n\n private onFrameLoad = () => {\n // syncSelectedState marks already-selected items as aria-selected=\"true\" (CSS hides them).\n // We hide the loader only after that DOM update so the brief flash of selected items\n // is covered by the overlay and never visible to the user.\n this.syncSelectedState()\n this.loading = false\n this.showDropdown()\n }\n\n private onToggle = (e: Event) => {\n const opened = (e as ToggleEvent).newState === 'open'\n this.inputTarget.setAttribute('aria-expanded', String(opened))\n if (!opened) {\n this.inputTarget.removeAttribute('aria-activedescendant')\n this.highlightedIndex = -1\n }\n }\n\n private onFocusOut = (e: FocusEvent) => {\n if (!this.element.contains(e.relatedTarget as Node)) {\n this.hideDropdown()\n }\n }\n\n private onListMouseDown = (e: MouseEvent) => {\n const option = (e.target as HTMLElement).closest<HTMLElement>('.midwest-autocomplete-option')\n if (option) { e.preventDefault(); this.pickItem(option) }\n }\n\n // ── Lifecycle ───────────────────────────────────────────────────────────────\n\n connect () {\n // focusout is on the controller root — not a target — so it lives here.\n this.element.addEventListener('focusout', this.onFocusOut)\n }\n\n disconnect () {\n this.fetchAbort?.abort()\n clearTimeout(this.searchTimer)\n this.element.removeEventListener('focusout', this.onFocusOut)\n }\n\n // Target callbacks — Stimulus calls these automatically when targets connect\n // or disconnect, including after the initial connect(). This replaces manual\n // hasTurboListTarget guards and addEventListener calls inside connect().\n\n dropdownTargetConnected (el: HTMLElement) {\n el.addEventListener('toggle', this.onToggle)\n }\n\n dropdownTargetDisconnected (el: HTMLElement) {\n el.removeEventListener('toggle', this.onToggle)\n }\n\n listTargetConnected (el: HTMLElement) {\n // Single delegated listener for client-rendered options.\n el.addEventListener('mousedown', this.onListMouseDown)\n }\n\n listTargetDisconnected (el: HTMLElement) {\n el.removeEventListener('mousedown', this.onListMouseDown)\n }\n\n turboListTargetConnected (el: HTMLElement) {\n el.addEventListener('turbo:frame-load', this.onFrameLoad)\n }\n\n turboListTargetDisconnected (el: HTMLElement) {\n el.removeEventListener('turbo:frame-load', this.onFrameLoad)\n }\n\n // ── Public actions ──────────────────────────────────────────────────────────\n\n focusInput () {\n this.inputTarget.focus()\n this.open()\n }\n\n open () {\n if (this.dropdownTarget.matches(':popover-open')) return\n // Cancel any pending debounced search so open() always shows results immediately.\n clearTimeout(this.searchTimer)\n this.resolve(this.inputTarget.value.trim())\n }\n\n search () {\n const query = this.inputTarget.value.trim()\n\n // Static options filter instantly — no debounce needed.\n if (!this.hasTurboListTarget && !this.urlValue) {\n this.renderOptions(this.filteredOptions(query))\n return\n }\n\n // Debounce network requests so only the final keystroke fires.\n clearTimeout(this.searchTimer)\n this.searchTimer = window.setTimeout(() => {\n this.searchTimer = undefined\n this.resolve(query)\n }, 200)\n }\n\n navigate (event: KeyboardEvent) {\n switch (event.key) {\n case 'ArrowDown': {\n event.preventDefault()\n if (!this.dropdownTarget.matches(':popover-open')) this.open()\n const items = this.visibleOptions()\n this.highlightedIndex = Math.min(this.highlightedIndex + 1, items.length - 1)\n this.syncHighlight(items)\n break\n }\n\n case 'ArrowUp': {\n event.preventDefault()\n if (!this.dropdownTarget.matches(':popover-open')) break\n const items = this.visibleOptions()\n this.highlightedIndex = Math.max(this.highlightedIndex - 1, 0)\n this.syncHighlight(items)\n break\n }\n\n case 'Enter': {\n event.preventDefault()\n if (!this.dropdownTarget.matches(':popover-open')) break\n const items = this.visibleOptions()\n if (this.highlightedIndex >= 0 && items[this.highlightedIndex]) {\n this.pickItem(items[this.highlightedIndex])\n }\n break\n }\n\n case 'Escape':\n this.hideDropdown()\n break\n\n case 'Backspace':\n if (this.inputTarget.value === '' && this.multipleValue) {\n const tokens = this.tokensTarget.querySelectorAll<HTMLElement>('[data-value]')\n const last = tokens[tokens.length - 1]\n if (last?.dataset.value) this.deselect(last.dataset.value)\n }\n break\n }\n }\n\n removeToken (event: MouseEvent) {\n event.stopPropagation()\n const btn = event.currentTarget as HTMLElement\n const value = btn.dataset.value\n if (value) this.deselect(value)\n }\n\n // Public action used by turbo-frame option items via data-action.\n // Each server-rendered <li> should have:\n // data-action=\"mousedown->midwest-autocomplete#selectOption\"\n selectOption (event: MouseEvent) {\n event.preventDefault()\n this.pickItem(event.currentTarget as HTMLElement)\n }\n\n // ── Private ─────────────────────────────────────────────────────────────────\n\n private get optionIdPrefix (): string {\n return `ac-opt-${(this.element.dataset.name ?? 'field').replace(/\\W/g, '-')}`\n }\n\n private isSelected (value: string): boolean {\n return !!this.tokensTarget.querySelector(`[data-value=\"${CSS.escape(value)}\"]`)\n }\n\n // Routes to the correct data source. Shared by open() and the debounced search path.\n private resolve (query: string) {\n if (this.hasTurboListTarget) { this.navigateFrame(query); return }\n if (this.urlValue) { this.fetch(query); return }\n this.renderOptions(this.filteredOptions(query))\n }\n\n private filteredOptions (query: string): Option[] {\n const q = query.toLowerCase()\n return (this.optionsValue as Option[]).filter(\n o => q === '' || o.label.toLowerCase().includes(q) || (o.support?.toLowerCase().includes(q) ?? false)\n )\n }\n\n private navigateFrame (query: string) {\n const url = new URL(this.turboFrameUrlValue, window.location.href)\n if (query) url.searchParams.set('q', query)\n // Send already-selected values so the server can exclude duplicates.\n this.tokensTarget.querySelectorAll<HTMLInputElement>('input[type=hidden]').forEach(input => {\n url.searchParams.append('selected[]', input.value)\n })\n this.loading = true\n this.turboListTarget.setAttribute('src', url.toString())\n this.showDropdown()\n }\n\n // After a turbo frame loads, mark already-selected items and update the live region.\n // The empty state element is always present in the frame and CSS shows/hides it\n // based on whether any non-selected options exist.\n private syncSelectedState () {\n const options = this.turboListTarget.querySelectorAll<HTMLElement>('.midwest-autocomplete-option')\n options.forEach(option => {\n const val = option.dataset.value ?? ''\n option.setAttribute('aria-selected', this.isSelected(val) ? 'true' : 'false')\n })\n\n const visibleCount = this.visibleOptions().length\n this.announce(visibleCount > 0 ? `${visibleCount} option${visibleCount === 1 ? '' : 's'} available` : 'No more options')\n }\n\n private async fetch (query: string) {\n this.fetchAbort?.abort()\n this.fetchAbort = new AbortController()\n\n this.listTarget.replaceChildren()\n this.loading = true\n this.announce('Loading')\n this.showDropdown()\n\n try {\n const url = new URL(this.urlValue, window.location.href)\n if (query) url.searchParams.set('q', query)\n\n const res = await globalThis.fetch(url.toString(), {\n signal: this.fetchAbort.signal,\n headers: { Accept: 'application/json' }\n })\n\n const data: Option[] = await res.json()\n this.renderOptions(data)\n } catch (err) {\n if ((err as Error).name !== 'AbortError') {\n this.renderEmpty()\n }\n }\n }\n\n private select (option: Option) {\n this.appendToken(option)\n this.hideDropdown()\n this.inputTarget.value = ''\n this.inputTarget.focus()\n this.announce(`${option.label} selected`)\n\n this.dispatch('change', { detail: { value: option.value, label: option.label } })\n }\n\n private deselect (value: string) {\n const token = this.tokensTarget.querySelector(`[data-value=\"${CSS.escape(value)}\"]`)\n const label = token?.querySelector('.value')?.textContent?.trim()\n token?.remove()\n if (label) this.announce(`${label} removed`)\n this.dispatch('change', { detail: { value: null } })\n }\n\n private appendToken (option: Option) {\n if (!this.multipleValue) {\n this.tokensTarget.querySelectorAll<HTMLElement>('[data-value]').forEach(el => el.remove())\n }\n const template = this.multipleValue ? this.multipleTokenTemplateTarget : this.singleTokenTemplateTarget\n const fragment = template.content.cloneNode(true) as DocumentFragment\n const badge = fragment.querySelector<HTMLElement>('.midwest-badge')\n if (!badge) return\n badge.dataset.value = option.value\n const valueEl = badge.querySelector<HTMLElement>('.value')\n if (valueEl) valueEl.textContent = option.label\n if (this.multipleValue) {\n const removeBtn = badge.querySelector<HTMLElement>('.remove')\n if (removeBtn) {\n removeBtn.dataset.value = option.value\n removeBtn.setAttribute('aria-label', `Remove ${option.label}`)\n }\n }\n const input = badge.querySelector<HTMLInputElement>('input[type=hidden]')\n if (input) {\n input.name = this.element.dataset.name ?? ''\n input.value = option.value\n }\n this.tokensTarget.appendChild(fragment)\n }\n\n private renderOptions (options: Option[]) {\n this.highlightedIndex = -1\n\n if (options.length === 0) {\n this.renderEmpty()\n return\n }\n\n this.listTarget.replaceChildren()\n\n options.forEach((option, index) => {\n const li = document.createElement('li')\n li.id = `${this.optionIdPrefix}-${index}`\n li.className = 'midwest-autocomplete-option'\n li.setAttribute('role', 'option')\n li.setAttribute('aria-selected', this.isSelected(option.value) ? 'true' : 'false')\n li.dataset.value = option.value\n\n const label = document.createElement('span')\n label.className = 'midwest-autocomplete-option-label'\n label.textContent = option.label\n li.appendChild(label)\n\n if (option.support) {\n const support = document.createElement('span')\n support.className = 'midwest-autocomplete-option-support'\n support.textContent = option.support\n li.appendChild(support)\n }\n\n this.listTarget.appendChild(li)\n })\n\n const availableCount = options.filter(o => !this.isSelected(o.value)).length\n if (availableCount === 0) {\n this.renderEmpty('No more options')\n return\n }\n this.loading = false\n this.announce(`${availableCount} option${availableCount === 1 ? '' : 's'} available`)\n this.showDropdown()\n }\n\n private renderEmpty (message = 'No results') {\n const li = document.createElement('li')\n li.className = 'midwest-autocomplete-empty'\n li.setAttribute('aria-hidden', 'true')\n li.textContent = message\n this.listTarget.replaceChildren(li)\n this.loading = false\n this.announce(message)\n this.showDropdown()\n }\n\n private set loading (on: boolean) {\n if (this.hasLoaderTarget) this.loaderTarget.hidden = !on\n }\n\n private showDropdown () {\n if (!this.dropdownTarget.matches(':popover-open')) this.dropdownTarget.showPopover()\n }\n\n private hideDropdown () {\n if (this.dropdownTarget.matches(':popover-open')) this.dropdownTarget.hidePopover()\n }\n\n private visibleOptions (): NodeListOf<HTMLElement> {\n const container = this.hasTurboListTarget ? this.turboListTarget : this.listTarget\n return container.querySelectorAll<HTMLElement>('.midwest-autocomplete-option:not([aria-selected=\"true\"])')\n }\n\n private syncHighlight (items: NodeListOf<HTMLElement>) {\n items.forEach((item, i) => item.classList.toggle('is-highlighted', i === this.highlightedIndex))\n const active = items[this.highlightedIndex]\n active?.scrollIntoView({ block: 'nearest' })\n if (active?.id) {\n this.inputTarget.setAttribute('aria-activedescendant', active.id)\n } else {\n this.inputTarget.removeAttribute('aria-activedescendant')\n }\n }\n\n private pickItem (item: HTMLElement) {\n const value = item.dataset.value ?? ''\n // Prefer data-label (set by AutocompleteOptionComponent) so rich slot content\n // like avatars does not corrupt the badge text via textContent.\n const label = item.dataset.label?.trim() || item.querySelector('.midwest-autocomplete-option-label')?.textContent?.trim() || value\n const support = item.querySelector('.midwest-autocomplete-option-support')?.textContent?.trim()\n this.select({ value, label, support })\n }\n\n private announce (message: string) {\n if (this.hasStatusTarget) this.statusTarget.textContent = message\n }\n}\n"],"names":[],"mappings":"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"}
@@ -1,17 +1,23 @@
1
1
  import { Controller } from '@hotwired/stimulus';
2
2
 
3
3
  class Input extends Controller {
4
- static targets = ["input"];
4
+ static targets = ["input", "counter"];
5
5
  updateClearButton = null;
6
+ updateCounter = null;
6
7
  connect() {
7
8
  this.applyColor();
8
9
  this.applySearchClear();
10
+ this.applyCounter();
9
11
  }
10
12
  disconnect() {
11
13
  if (this.updateClearButton) {
12
14
  this.inputTarget.removeEventListener("input", this.updateClearButton);
13
15
  this.updateClearButton = null;
14
16
  }
17
+ if (this.updateCounter) {
18
+ this.inputTarget.removeEventListener("input", this.updateCounter);
19
+ this.updateCounter = null;
20
+ }
15
21
  }
16
22
  applyColor() {
17
23
  if (this.inputTarget.type !== "color") {
@@ -21,6 +27,15 @@ class Input extends Controller {
21
27
  this.inputTarget.setAttribute("value", this.inputTarget.value);
22
28
  });
23
29
  }
30
+ applyCounter() {
31
+ if (!this.hasCounterTarget)
32
+ return;
33
+ this.counterTarget.textContent = String(this.inputTarget.value.length);
34
+ this.updateCounter = () => {
35
+ this.counterTarget.textContent = String(this.inputTarget.value.length);
36
+ };
37
+ this.inputTarget.addEventListener("input", this.updateCounter);
38
+ }
24
39
  applySearchClear() {
25
40
  if (this.inputTarget.type !== "search")
26
41
  return;
@@ -1 +1 @@
1
- {"version":3,"file":"input_component_controller.js","sources":["../../../../../../../../app/components/midwest/form/input_component/input_component_controller.ts"],"sourcesContent":["\nimport { Controller } from '@hotwired/stimulus'\n\nexport default class Input extends Controller<HTMLFormElement> {\n static targets = ['input']\n\n declare readonly hasInputTarget: boolean\n declare readonly inputTarget: HTMLInputElement\n declare readonly inputTargets: HTMLInputElement[]\n\n private updateClearButton: (() => void) | null = null\n\n connect () {\n this.applyColor()\n this.applySearchClear()\n }\n\n disconnect () {\n if (this.updateClearButton) {\n this.inputTarget.removeEventListener('input', this.updateClearButton)\n this.updateClearButton = null\n }\n }\n\n applyColor () {\n if (this.inputTarget.type !== 'color') {\n return\n }\n\n this.inputTarget.addEventListener('input', () => {\n this.inputTarget.setAttribute('value', this.inputTarget.value)\n })\n }\n\n private applySearchClear () {\n if (this.inputTarget.type !== 'search') return\n // Browsers that natively render a cancel button (Chrome, Safari, Edge)\n // already handle clearing. Only inject for browsers that don't (Firefox).\n if (CSS.supports('selector(::-webkit-search-cancel-button)')) return\n\n const button = document.createElement('button')\n button.type = 'button'\n button.setAttribute('aria-label', 'Clear search')\n button.classList.add('midwest-input-search-clear')\n button.hidden = this.inputTarget.value.length === 0\n\n const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg')\n svg.setAttribute('viewBox', '0 0 24 24')\n svg.setAttribute('fill', 'none')\n svg.setAttribute('stroke', 'currentColor')\n svg.setAttribute('stroke-width', '2')\n svg.setAttribute('aria-hidden', 'true')\n svg.setAttribute('width', '16')\n svg.setAttribute('height', '16')\n\n const path = document.createElementNS('http://www.w3.org/2000/svg', 'path')\n path.setAttribute('stroke-linecap', 'round')\n path.setAttribute('stroke-linejoin', 'round')\n path.setAttribute('d', 'M6 18L18 6M6 6l12 12')\n svg.appendChild(path)\n button.appendChild(svg)\n\n const wrapper = document.createElement('div')\n wrapper.classList.add('midwest-input-search-wrapper')\n this.inputTarget.parentElement?.insertBefore(wrapper, this.inputTarget)\n wrapper.appendChild(this.inputTarget)\n wrapper.appendChild(button)\n\n this.updateClearButton = () => {\n button.hidden = this.inputTarget.value.length === 0\n }\n\n this.inputTarget.addEventListener('input', this.updateClearButton)\n\n button.addEventListener('click', () => {\n this.inputTarget.value = ''\n button.hidden = true\n this.inputTarget.dispatchEvent(new Event('input', { bubbles: true }))\n this.inputTarget.focus()\n })\n }\n}\n"],"names":[],"mappings":";;AAGA,MAAqB,cAAc,UAA4B,CAAA;AAAA,EAC7D,OAAO,OAAU,GAAA,CAAC,OAAO,CAAA,CAAA;AAAA,EAMjB,iBAAyC,GAAA,IAAA,CAAA;AAAA,EAEjD,OAAW,GAAA;AACT,IAAA,IAAA,CAAK,UAAW,EAAA,CAAA;AAChB,IAAA,IAAA,CAAK,gBAAiB,EAAA,CAAA;AAAA,GACxB;AAAA,EAEA,UAAc,GAAA;AACZ,IAAA,IAAI,KAAK,iBAAmB,EAAA;AAC1B,MAAA,IAAA,CAAK,WAAY,CAAA,mBAAA,CAAoB,OAAS,EAAA,IAAA,CAAK,iBAAiB,CAAA,CAAA;AACpE,MAAA,IAAA,CAAK,iBAAoB,GAAA,IAAA,CAAA;AAAA,KAC3B;AAAA,GACF;AAAA,EAEA,UAAc,GAAA;AACZ,IAAI,IAAA,IAAA,CAAK,WAAY,CAAA,IAAA,KAAS,OAAS,EAAA;AACrC,MAAA,OAAA;AAAA,KACF;AAEA,IAAK,IAAA,CAAA,WAAA,CAAY,gBAAiB,CAAA,OAAA,EAAS,MAAM;AAC/C,MAAA,IAAA,CAAK,WAAY,CAAA,YAAA,CAAa,OAAS,EAAA,IAAA,CAAK,YAAY,KAAK,CAAA,CAAA;AAAA,KAC9D,CAAA,CAAA;AAAA,GACH;AAAA,EAEQ,gBAAoB,GAAA;AAC1B,IAAI,IAAA,IAAA,CAAK,YAAY,IAAS,KAAA,QAAA;AAAU,MAAA,OAAA;AAGxC,IAAI,IAAA,GAAA,CAAI,SAAS,0CAA0C,CAAA;AAAG,MAAA,OAAA;AAE9D,IAAM,MAAA,MAAA,GAAS,QAAS,CAAA,aAAA,CAAc,QAAQ,CAAA,CAAA;AAC9C,IAAA,MAAA,CAAO,IAAO,GAAA,QAAA,CAAA;AACd,IAAO,MAAA,CAAA,YAAA,CAAa,cAAc,cAAc,CAAA,CAAA;AAChD,IAAO,MAAA,CAAA,SAAA,CAAU,IAAI,4BAA4B,CAAA,CAAA;AACjD,IAAA,MAAA,CAAO,MAAS,GAAA,IAAA,CAAK,WAAY,CAAA,KAAA,CAAM,MAAW,KAAA,CAAA,CAAA;AAElD,IAAA,MAAM,GAAM,GAAA,QAAA,CAAS,eAAgB,CAAA,4BAAA,EAA8B,KAAK,CAAA,CAAA;AACxE,IAAI,GAAA,CAAA,YAAA,CAAa,WAAW,WAAW,CAAA,CAAA;AACvC,IAAI,GAAA,CAAA,YAAA,CAAa,QAAQ,MAAM,CAAA,CAAA;AAC/B,IAAI,GAAA,CAAA,YAAA,CAAa,UAAU,cAAc,CAAA,CAAA;AACzC,IAAI,GAAA,CAAA,YAAA,CAAa,gBAAgB,GAAG,CAAA,CAAA;AACpC,IAAI,GAAA,CAAA,YAAA,CAAa,eAAe,MAAM,CAAA,CAAA;AACtC,IAAI,GAAA,CAAA,YAAA,CAAa,SAAS,IAAI,CAAA,CAAA;AAC9B,IAAI,GAAA,CAAA,YAAA,CAAa,UAAU,IAAI,CAAA,CAAA;AAE/B,IAAA,MAAM,IAAO,GAAA,QAAA,CAAS,eAAgB,CAAA,4BAAA,EAA8B,MAAM,CAAA,CAAA;AAC1E,IAAK,IAAA,CAAA,YAAA,CAAa,kBAAkB,OAAO,CAAA,CAAA;AAC3C,IAAK,IAAA,CAAA,YAAA,CAAa,mBAAmB,OAAO,CAAA,CAAA;AAC5C,IAAK,IAAA,CAAA,YAAA,CAAa,KAAK,sBAAsB,CAAA,CAAA;AAC7C,IAAA,GAAA,CAAI,YAAY,IAAI,CAAA,CAAA;AACpB,IAAA,MAAA,CAAO,YAAY,GAAG,CAAA,CAAA;AAEtB,IAAM,MAAA,OAAA,GAAU,QAAS,CAAA,aAAA,CAAc,KAAK,CAAA,CAAA;AAC5C,IAAQ,OAAA,CAAA,SAAA,CAAU,IAAI,8BAA8B,CAAA,CAAA;AACpD,IAAA,IAAA,CAAK,WAAY,CAAA,aAAA,EAAe,YAAa,CAAA,OAAA,EAAS,KAAK,WAAW,CAAA,CAAA;AACtE,IAAQ,OAAA,CAAA,WAAA,CAAY,KAAK,WAAW,CAAA,CAAA;AACpC,IAAA,OAAA,CAAQ,YAAY,MAAM,CAAA,CAAA;AAE1B,IAAA,IAAA,CAAK,oBAAoB,MAAM;AAC7B,MAAA,MAAA,CAAO,MAAS,GAAA,IAAA,CAAK,WAAY,CAAA,KAAA,CAAM,MAAW,KAAA,CAAA,CAAA;AAAA,KACpD,CAAA;AAEA,IAAA,IAAA,CAAK,WAAY,CAAA,gBAAA,CAAiB,OAAS,EAAA,IAAA,CAAK,iBAAiB,CAAA,CAAA;AAEjE,IAAO,MAAA,CAAA,gBAAA,CAAiB,SAAS,MAAM;AACrC,MAAA,IAAA,CAAK,YAAY,KAAQ,GAAA,EAAA,CAAA;AACzB,MAAA,MAAA,CAAO,MAAS,GAAA,IAAA,CAAA;AAChB,MAAK,IAAA,CAAA,WAAA,CAAY,cAAc,IAAI,KAAA,CAAM,SAAS,EAAE,OAAA,EAAS,IAAK,EAAC,CAAC,CAAA,CAAA;AACpE,MAAA,IAAA,CAAK,YAAY,KAAM,EAAA,CAAA;AAAA,KACxB,CAAA,CAAA;AAAA,GACH;AACF;;;;"}
1
+ {"version":3,"file":"input_component_controller.js","sources":["../../../../../../../../app/components/midwest/form/input_component/input_component_controller.ts"],"sourcesContent":["\nimport { Controller } from '@hotwired/stimulus'\n\nexport default class Input extends Controller<HTMLFormElement> {\n static targets = ['input', 'counter']\n\n declare readonly hasInputTarget: boolean\n declare readonly inputTarget: HTMLInputElement\n declare readonly inputTargets: HTMLInputElement[]\n declare readonly hasCounterTarget: boolean\n declare readonly counterTarget: HTMLElement\n\n private updateClearButton: (() => void) | null = null\n private updateCounter: (() => void) | null = null\n\n connect () {\n this.applyColor()\n this.applySearchClear()\n this.applyCounter()\n }\n\n disconnect () {\n if (this.updateClearButton) {\n this.inputTarget.removeEventListener('input', this.updateClearButton)\n this.updateClearButton = null\n }\n if (this.updateCounter) {\n this.inputTarget.removeEventListener('input', this.updateCounter)\n this.updateCounter = null\n }\n }\n\n applyColor () {\n if (this.inputTarget.type !== 'color') {\n return\n }\n\n this.inputTarget.addEventListener('input', () => {\n this.inputTarget.setAttribute('value', this.inputTarget.value)\n })\n }\n\n private applyCounter () {\n if (!this.hasCounterTarget) return\n\n this.counterTarget.textContent = String(this.inputTarget.value.length)\n\n this.updateCounter = () => {\n this.counterTarget.textContent = String(this.inputTarget.value.length)\n }\n\n this.inputTarget.addEventListener('input', this.updateCounter)\n }\n\n private applySearchClear () {\n if (this.inputTarget.type !== 'search') return\n // Browsers that natively render a cancel button (Chrome, Safari, Edge)\n // already handle clearing. Only inject for browsers that don't (Firefox).\n if (CSS.supports('selector(::-webkit-search-cancel-button)')) return\n\n const button = document.createElement('button')\n button.type = 'button'\n button.setAttribute('aria-label', 'Clear search')\n button.classList.add('midwest-input-search-clear')\n button.hidden = this.inputTarget.value.length === 0\n\n const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg')\n svg.setAttribute('viewBox', '0 0 24 24')\n svg.setAttribute('fill', 'none')\n svg.setAttribute('stroke', 'currentColor')\n svg.setAttribute('stroke-width', '2')\n svg.setAttribute('aria-hidden', 'true')\n svg.setAttribute('width', '16')\n svg.setAttribute('height', '16')\n\n const path = document.createElementNS('http://www.w3.org/2000/svg', 'path')\n path.setAttribute('stroke-linecap', 'round')\n path.setAttribute('stroke-linejoin', 'round')\n path.setAttribute('d', 'M6 18L18 6M6 6l12 12')\n svg.appendChild(path)\n button.appendChild(svg)\n\n const wrapper = document.createElement('div')\n wrapper.classList.add('midwest-input-search-wrapper')\n this.inputTarget.parentElement?.insertBefore(wrapper, this.inputTarget)\n wrapper.appendChild(this.inputTarget)\n wrapper.appendChild(button)\n\n this.updateClearButton = () => {\n button.hidden = this.inputTarget.value.length === 0\n }\n\n this.inputTarget.addEventListener('input', this.updateClearButton)\n\n button.addEventListener('click', () => {\n this.inputTarget.value = ''\n button.hidden = true\n this.inputTarget.dispatchEvent(new Event('input', { bubbles: true }))\n this.inputTarget.focus()\n })\n }\n}\n"],"names":[],"mappings":"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"}
@@ -13,10 +13,12 @@ class FormLiveSummary extends Controller {
13
13
  this.render();
14
14
  this.form?.addEventListener("input", this.render);
15
15
  this.form?.addEventListener("change", this.render);
16
+ this.form?.addEventListener("midwest-autocomplete:change", this.render);
16
17
  }
17
18
  disconnect() {
18
19
  this.form?.removeEventListener("input", this.render);
19
20
  this.form?.removeEventListener("change", this.render);
21
+ this.form?.removeEventListener("midwest-autocomplete:change", this.render);
20
22
  }
21
23
  get form() {
22
24
  return this.element.closest("form");
@@ -99,6 +101,16 @@ class FormLiveSummary extends Controller {
99
101
  value: element.value
100
102
  });
101
103
  });
104
+ form.querySelectorAll(".midwest-autocomplete").forEach((ac) => {
105
+ if (this.element.contains(ac))
106
+ return;
107
+ if (!ac.querySelector('[role="combobox"]'))
108
+ return;
109
+ const labelEl = ac.querySelector("label");
110
+ const label = labelEl?.textContent?.trim() || this.prettifyName((ac.dataset.name ?? "").replace(/\[.*\]$/, ""));
111
+ const selected = Array.from(ac.querySelectorAll(".midwest-autocomplete-tokens .value")).map((el) => el.textContent?.trim()).filter(Boolean).join(", ");
112
+ fields.push({ label, value: selected || "\u2014" });
113
+ });
102
114
  return fields;
103
115
  }
104
116
  // Finds the label associated with a specific element via `for` attribute,
@@ -1 +1 @@
1
- {"version":3,"file":"live_summary_component_controller.js","sources":["../../../../../../../../app/components/midwest/form/live_summary_component/live_summary_component_controller.ts"],"sourcesContent":["import { Controller } from '@hotwired/stimulus'\n\n// Input types that should never appear in a form live summary.\nconst SKIPPED_INPUT_TYPES = new Set([\n 'hidden', 'submit', 'reset', 'button', 'image'\n])\n\ninterface FieldSummary {\n label: string\n value: string\n}\n\nexport default class FormLiveSummary extends Controller<HTMLElement> {\n static targets = ['list']\n\n declare readonly listTarget: HTMLElement\n\n connect () {\n this.render()\n this.form?.addEventListener('input', this.render)\n this.form?.addEventListener('change', this.render)\n }\n\n disconnect () {\n this.form?.removeEventListener('input', this.render)\n this.form?.removeEventListener('change', this.render)\n }\n\n private get form (): HTMLFormElement | null {\n return this.element.closest('form')\n }\n\n private render = () => {\n const fields = this.collectFields()\n\n if (fields.length === 0) {\n this.listTarget.replaceChildren(\n Object.assign(document.createElement('p'), {\n className: 'midwest-form-live-summary-empty',\n textContent: 'No values to display.'\n })\n )\n return\n }\n\n const dl = document.createElement('dl')\n dl.className = 'midwest-form-live-summary-list'\n\n fields.forEach(({ label, value }) => {\n const dt = Object.assign(document.createElement('dt'), {\n className: 'midwest-form-live-summary-term',\n textContent: label\n })\n const dd = Object.assign(document.createElement('dd'), {\n className: 'midwest-form-live-summary-definition',\n textContent: value || '—'\n })\n dl.append(dt, dd)\n })\n\n this.listTarget.replaceChildren(dl)\n }\n\n private collectFields (): FieldSummary[] {\n const form = this.form\n if (!form) return []\n\n const fields: FieldSummary[] = []\n const seenRadioNames = new Set<string>()\n\n Array.from(form.elements).forEach(el => {\n const element = el as HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement | HTMLButtonElement\n\n if (!element.name) return\n\n // Skip elements that live inside the summary itself.\n if (this.element.contains(element)) return\n\n // Skip all button elements regardless of how they are declared.\n if (element instanceof HTMLButtonElement) return\n\n if (element instanceof HTMLInputElement) {\n if (SKIPPED_INPUT_TYPES.has(element.type)) return\n\n if (element.type === 'radio') {\n if (seenRadioNames.has(element.name)) return\n seenRadioNames.add(element.name)\n\n const checked = form.querySelector<HTMLInputElement>(\n `input[type=\"radio\"][name=\"${CSS.escape(element.name)}\"]:checked`\n )\n if (!checked) return\n\n fields.push({\n label: this.groupLabelFor(element),\n value: this.labelFor(checked) || checked.value\n })\n return\n }\n\n if (element.type === 'checkbox') {\n fields.push({\n label: this.labelFor(element),\n value: element.checked ? 'Yes' : 'No'\n })\n return\n }\n }\n\n if (element instanceof HTMLSelectElement) {\n fields.push({\n label: this.labelFor(element),\n value: Array.from(element.selectedOptions).map(o => o.text).join(', ') || '—'\n })\n return\n }\n\n // Text-like inputs and textareas only reach this point.\n fields.push({\n label: this.labelFor(element),\n value: element.value\n })\n })\n\n return fields\n }\n\n // Finds the label associated with a specific element via `for` attribute,\n // wrapping label, or falls back to a prettified version of the element name.\n private labelFor (el: Element): string {\n const id = (el as HTMLElement).id\n if (id) {\n const label = this.form?.querySelector(`label[for=\"${CSS.escape(id)}\"]`)\n if (label) return label.textContent?.trim() ?? ''\n }\n const wrapper = el.closest('label')\n if (wrapper) return wrapper.textContent?.trim() ?? ''\n return this.prettifyName((el as HTMLInputElement).name ?? '')\n }\n\n // For radio groups, prefer the containing fieldset legend over the\n // individual radio's label, then fall back to prettifying the name.\n private groupLabelFor (el: Element): string {\n const legend = el.closest('fieldset')?.querySelector('legend')\n if (legend) return legend.textContent?.trim() ?? ''\n return this.prettifyName((el as HTMLInputElement).name ?? '')\n }\n\n // Converts \"user[first_name]\" or \"first_name\" into \"First name\".\n private prettifyName (name: string): string {\n return name\n .replace(/\\[.*?\\]/g, '')\n .replace(/_/g, ' ')\n .replace(/^\\w/, c => c.toUpperCase())\n .trim()\n }\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"live_summary_component_controller.js","sources":["../../../../../../../../app/components/midwest/form/live_summary_component/live_summary_component_controller.ts"],"sourcesContent":["import { Controller } from '@hotwired/stimulus'\n\n// Input types that should never appear in a form live summary.\nconst SKIPPED_INPUT_TYPES = new Set([\n 'hidden', 'submit', 'reset', 'button', 'image'\n])\n\ninterface FieldSummary {\n label: string\n value: string\n}\n\nexport default class FormLiveSummary extends Controller<HTMLElement> {\n static targets = ['list']\n\n declare readonly listTarget: HTMLElement\n\n connect () {\n this.render()\n this.form?.addEventListener('input', this.render)\n this.form?.addEventListener('change', this.render)\n this.form?.addEventListener('midwest-autocomplete:change', this.render)\n }\n\n disconnect () {\n this.form?.removeEventListener('input', this.render)\n this.form?.removeEventListener('change', this.render)\n this.form?.removeEventListener('midwest-autocomplete:change', this.render)\n }\n\n private get form (): HTMLFormElement | null {\n return this.element.closest('form')\n }\n\n private render = () => {\n const fields = this.collectFields()\n\n if (fields.length === 0) {\n this.listTarget.replaceChildren(\n Object.assign(document.createElement('p'), {\n className: 'midwest-form-live-summary-empty',\n textContent: 'No values to display.'\n })\n )\n return\n }\n\n const dl = document.createElement('dl')\n dl.className = 'midwest-form-live-summary-list'\n\n fields.forEach(({ label, value }) => {\n const dt = Object.assign(document.createElement('dt'), {\n className: 'midwest-form-live-summary-term',\n textContent: label\n })\n const dd = Object.assign(document.createElement('dd'), {\n className: 'midwest-form-live-summary-definition',\n textContent: value || '—'\n })\n dl.append(dt, dd)\n })\n\n this.listTarget.replaceChildren(dl)\n }\n\n private collectFields (): FieldSummary[] {\n const form = this.form\n if (!form) return []\n\n const fields: FieldSummary[] = []\n const seenRadioNames = new Set<string>()\n\n Array.from(form.elements).forEach(el => {\n const element = el as HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement | HTMLButtonElement\n\n if (!element.name) return\n\n // Skip elements that live inside the summary itself.\n if (this.element.contains(element)) return\n\n // Skip all button elements regardless of how they are declared.\n if (element instanceof HTMLButtonElement) return\n\n if (element instanceof HTMLInputElement) {\n if (SKIPPED_INPUT_TYPES.has(element.type)) return\n\n if (element.type === 'radio') {\n if (seenRadioNames.has(element.name)) return\n seenRadioNames.add(element.name)\n\n const checked = form.querySelector<HTMLInputElement>(\n `input[type=\"radio\"][name=\"${CSS.escape(element.name)}\"]:checked`\n )\n if (!checked) return\n\n fields.push({\n label: this.groupLabelFor(element),\n value: this.labelFor(checked) || checked.value\n })\n return\n }\n\n if (element.type === 'checkbox') {\n fields.push({\n label: this.labelFor(element),\n value: element.checked ? 'Yes' : 'No'\n })\n return\n }\n }\n\n if (element instanceof HTMLSelectElement) {\n fields.push({\n label: this.labelFor(element),\n value: Array.from(element.selectedOptions).map(o => o.text).join(', ') || '—'\n })\n return\n }\n\n // Text-like inputs and textareas only reach this point.\n fields.push({\n label: this.labelFor(element),\n value: element.value\n })\n })\n\n // Autocomplete components use a nameless combobox input + hidden inputs —\n // neither surfaces through the standard form.elements loop. Query them\n // separately so their selected badge labels appear in the summary.\n // Note: labelFor(input) doesn't work here because InputComponent assigns\n // its own id to the <input>, which won't match the <label>'s for attribute.\n form.querySelectorAll<HTMLElement>('.midwest-autocomplete').forEach(ac => {\n if (this.element.contains(ac)) return\n if (!ac.querySelector('[role=\"combobox\"]')) return\n\n const labelEl = ac.querySelector<HTMLElement>('label')\n const label = labelEl?.textContent?.trim() ||\n this.prettifyName((ac.dataset.name ?? '').replace(/\\[.*\\]$/, ''))\n\n const selected = Array.from(ac.querySelectorAll<HTMLElement>('.midwest-autocomplete-tokens .value'))\n .map(el => el.textContent?.trim())\n .filter(Boolean)\n .join(', ')\n fields.push({ label, value: selected || '—' })\n })\n\n return fields\n }\n\n // Finds the label associated with a specific element via `for` attribute,\n // wrapping label, or falls back to a prettified version of the element name.\n private labelFor (el: Element): string {\n const id = (el as HTMLElement).id\n if (id) {\n const label = this.form?.querySelector(`label[for=\"${CSS.escape(id)}\"]`)\n if (label) return label.textContent?.trim() ?? ''\n }\n const wrapper = el.closest('label')\n if (wrapper) return wrapper.textContent?.trim() ?? ''\n return this.prettifyName((el as HTMLInputElement).name ?? '')\n }\n\n // For radio groups, prefer the containing fieldset legend over the\n // individual radio's label, then fall back to prettifying the name.\n private groupLabelFor (el: Element): string {\n const legend = el.closest('fieldset')?.querySelector('legend')\n if (legend) return legend.textContent?.trim() ?? ''\n return this.prettifyName((el as HTMLInputElement).name ?? '')\n }\n\n // Converts \"user[first_name]\" or \"first_name\" into \"First name\".\n private prettifyName (name: string): string {\n return name\n .replace(/\\[.*?\\]/g, '')\n .replace(/_/g, ' ')\n .replace(/^\\w/, c => c.toUpperCase())\n .trim()\n }\n}\n"],"names":[],"mappings":"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"}