@unabridged/midwest 0.16.0 → 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.
- package/app/assets/javascript/midwest.js +192 -92
- package/app/assets/javascript/midwest.js.map +1 -1
- package/app/assets/stylesheets/midwest.css +1 -1
- package/dist/css/midwest.css +1 -1
- package/dist/javascript/collection/app/components/midwest/form/autocomplete_component/autocomplete_component_controller.js +180 -92
- package/dist/javascript/collection/app/components/midwest/form/autocomplete_component/autocomplete_component_controller.js.map +1 -1
- package/dist/javascript/collection/app/components/midwest/form/live_summary_component/live_summary_component_controller.js +12 -0
- package/dist/javascript/collection/app/components/midwest/form/live_summary_component/live_summary_component_controller.js.map +1 -1
- package/dist/javascript/midwest.js +192 -92
- package/dist/javascript/midwest.js.map +1 -1
- package/package.json +1 -1
|
@@ -1,22 +1,75 @@
|
|
|
1
1
|
import { Controller } from '@hotwired/stimulus';
|
|
2
2
|
|
|
3
3
|
class Autocomplete extends Controller {
|
|
4
|
-
static targets = ["input", "dropdown", "list", "tokens", "
|
|
4
|
+
static targets = ["input", "dropdown", "list", "tokens", "multipleTokenTemplate", "singleTokenTemplate", "status", "turboList", "loader"];
|
|
5
5
|
static values = {
|
|
6
6
|
options: { type: Array, default: [] },
|
|
7
7
|
url: { type: String, default: "" },
|
|
8
|
-
multiple: { type: Boolean, default: true }
|
|
8
|
+
multiple: { type: Boolean, default: true },
|
|
9
|
+
turboFrameUrl: { type: String, default: "" }
|
|
9
10
|
};
|
|
10
|
-
selectedValues = /* @__PURE__ */ new Map();
|
|
11
11
|
highlightedIndex = -1;
|
|
12
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 ───────────────────────────────────────────────────────────────
|
|
13
43
|
connect() {
|
|
14
|
-
this.
|
|
44
|
+
this.element.addEventListener("focusout", this.onFocusOut);
|
|
15
45
|
}
|
|
16
46
|
disconnect() {
|
|
17
47
|
this.fetchAbort?.abort();
|
|
48
|
+
clearTimeout(this.searchTimer);
|
|
49
|
+
this.element.removeEventListener("focusout", this.onFocusOut);
|
|
18
50
|
}
|
|
19
|
-
//
|
|
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 ──────────────────────────────────────────────────────────
|
|
20
73
|
focusInput() {
|
|
21
74
|
this.inputTarget.focus();
|
|
22
75
|
this.open();
|
|
@@ -24,21 +77,20 @@ class Autocomplete extends Controller {
|
|
|
24
77
|
open() {
|
|
25
78
|
if (this.dropdownTarget.matches(":popover-open"))
|
|
26
79
|
return;
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
this.fetch(query);
|
|
30
|
-
} else {
|
|
31
|
-
this.renderOptions(this.filteredOptions(query));
|
|
32
|
-
}
|
|
80
|
+
clearTimeout(this.searchTimer);
|
|
81
|
+
this.resolve(this.inputTarget.value.trim());
|
|
33
82
|
}
|
|
34
|
-
|
|
83
|
+
search() {
|
|
35
84
|
const query = this.inputTarget.value.trim();
|
|
36
|
-
if (this.urlValue) {
|
|
37
|
-
await this.fetch(query);
|
|
38
|
-
} else {
|
|
85
|
+
if (!this.hasTurboListTarget && !this.urlValue) {
|
|
39
86
|
this.renderOptions(this.filteredOptions(query));
|
|
87
|
+
return;
|
|
40
88
|
}
|
|
41
|
-
this.
|
|
89
|
+
clearTimeout(this.searchTimer);
|
|
90
|
+
this.searchTimer = window.setTimeout(() => {
|
|
91
|
+
this.searchTimer = void 0;
|
|
92
|
+
this.resolve(query);
|
|
93
|
+
}, 200);
|
|
42
94
|
}
|
|
43
95
|
navigate(event) {
|
|
44
96
|
switch (event.key) {
|
|
@@ -53,6 +105,8 @@ class Autocomplete extends Controller {
|
|
|
53
105
|
}
|
|
54
106
|
case "ArrowUp": {
|
|
55
107
|
event.preventDefault();
|
|
108
|
+
if (!this.dropdownTarget.matches(":popover-open"))
|
|
109
|
+
break;
|
|
56
110
|
const items = this.visibleOptions();
|
|
57
111
|
this.highlightedIndex = Math.max(this.highlightedIndex - 1, 0);
|
|
58
112
|
this.syncHighlight(items);
|
|
@@ -60,6 +114,8 @@ class Autocomplete extends Controller {
|
|
|
60
114
|
}
|
|
61
115
|
case "Enter": {
|
|
62
116
|
event.preventDefault();
|
|
117
|
+
if (!this.dropdownTarget.matches(":popover-open"))
|
|
118
|
+
break;
|
|
63
119
|
const items = this.visibleOptions();
|
|
64
120
|
if (this.highlightedIndex >= 0 && items[this.highlightedIndex]) {
|
|
65
121
|
this.pickItem(items[this.highlightedIndex]);
|
|
@@ -71,19 +127,14 @@ class Autocomplete extends Controller {
|
|
|
71
127
|
break;
|
|
72
128
|
case "Backspace":
|
|
73
129
|
if (this.inputTarget.value === "" && this.multipleValue) {
|
|
74
|
-
const
|
|
75
|
-
|
|
76
|
-
|
|
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);
|
|
77
134
|
}
|
|
78
135
|
break;
|
|
79
136
|
}
|
|
80
137
|
}
|
|
81
|
-
visibleOptions() {
|
|
82
|
-
return this.listTarget.querySelectorAll('.midwest-autocomplete-option:not([aria-selected="true"])');
|
|
83
|
-
}
|
|
84
|
-
handleBlur() {
|
|
85
|
-
setTimeout(() => this.hideDropdown(), 150);
|
|
86
|
-
}
|
|
87
138
|
removeToken(event) {
|
|
88
139
|
event.stopPropagation();
|
|
89
140
|
const btn = event.currentTarget;
|
|
@@ -91,16 +142,31 @@ class Autocomplete extends Controller {
|
|
|
91
142
|
if (value)
|
|
92
143
|
this.deselect(value);
|
|
93
144
|
}
|
|
94
|
-
//
|
|
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 ─────────────────────────────────────────────────────────────────
|
|
95
153
|
get optionIdPrefix() {
|
|
96
154
|
return `ac-opt-${(this.element.dataset.name ?? "field").replace(/\W/g, "-")}`;
|
|
97
155
|
}
|
|
98
|
-
|
|
99
|
-
this.
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
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));
|
|
104
170
|
}
|
|
105
171
|
filteredOptions(query) {
|
|
106
172
|
const q = query.toLowerCase();
|
|
@@ -108,10 +174,36 @@ class Autocomplete extends Controller {
|
|
|
108
174
|
(o) => q === "" || o.label.toLowerCase().includes(q) || (o.support?.toLowerCase().includes(q) ?? false)
|
|
109
175
|
);
|
|
110
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
|
+
}
|
|
111
200
|
async fetch(query) {
|
|
112
201
|
this.fetchAbort?.abort();
|
|
113
202
|
this.fetchAbort = new AbortController();
|
|
114
|
-
this.
|
|
203
|
+
this.listTarget.replaceChildren();
|
|
204
|
+
this.loading = true;
|
|
205
|
+
this.announce("Loading");
|
|
206
|
+
this.showDropdown();
|
|
115
207
|
try {
|
|
116
208
|
const url = new URL(this.urlValue, window.location.href);
|
|
117
209
|
if (query)
|
|
@@ -129,11 +221,6 @@ class Autocomplete extends Controller {
|
|
|
129
221
|
}
|
|
130
222
|
}
|
|
131
223
|
select(option) {
|
|
132
|
-
if (!this.multipleValue) {
|
|
133
|
-
this.selectedValues.forEach((_, val) => this.removeTokenElement(val));
|
|
134
|
-
this.selectedValues.clear();
|
|
135
|
-
}
|
|
136
|
-
this.selectedValues.set(option.value, option);
|
|
137
224
|
this.appendToken(option);
|
|
138
225
|
this.hideDropdown();
|
|
139
226
|
this.inputTarget.value = "";
|
|
@@ -142,102 +229,106 @@ class Autocomplete extends Controller {
|
|
|
142
229
|
this.dispatch("change", { detail: { value: option.value, label: option.label } });
|
|
143
230
|
}
|
|
144
231
|
deselect(value) {
|
|
145
|
-
const
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
if (
|
|
149
|
-
this.announce(`${
|
|
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`);
|
|
150
237
|
this.dispatch("change", { detail: { value: null } });
|
|
151
238
|
}
|
|
152
239
|
appendToken(option) {
|
|
153
|
-
|
|
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;
|
|
154
252
|
if (this.multipleValue) {
|
|
155
|
-
const fragment = this.multipleTokenTemplateTarget.content.cloneNode(true);
|
|
156
|
-
const badge = fragment.querySelector(".midwest-badge");
|
|
157
|
-
badge.dataset.value = option.value;
|
|
158
|
-
badge.querySelector(".value").textContent = option.label;
|
|
159
253
|
const removeBtn = badge.querySelector(".remove");
|
|
160
|
-
removeBtn
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
this.
|
|
168
|
-
const fragment = this.singleTokenTemplateTarget.content.cloneNode(true);
|
|
169
|
-
const badge = fragment.querySelector(".midwest-badge");
|
|
170
|
-
badge.dataset.value = option.value;
|
|
171
|
-
badge.querySelector(".value").textContent = option.label;
|
|
172
|
-
const input = badge.querySelector("input[type=hidden]");
|
|
173
|
-
input.name = name;
|
|
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 ?? "";
|
|
174
262
|
input.value = option.value;
|
|
175
|
-
this.tokensTarget.appendChild(fragment);
|
|
176
263
|
}
|
|
177
|
-
|
|
178
|
-
removeTokenElement(value) {
|
|
179
|
-
const token = this.tokensTarget.querySelector(`[data-value="${CSS.escape(value)}"]`);
|
|
180
|
-
token?.remove();
|
|
264
|
+
this.tokensTarget.appendChild(fragment);
|
|
181
265
|
}
|
|
182
266
|
renderOptions(options) {
|
|
183
|
-
this.listTarget.innerHTML = "";
|
|
184
267
|
this.highlightedIndex = -1;
|
|
185
268
|
if (options.length === 0) {
|
|
186
269
|
this.renderEmpty();
|
|
187
270
|
return;
|
|
188
271
|
}
|
|
272
|
+
this.listTarget.replaceChildren();
|
|
189
273
|
options.forEach((option, index) => {
|
|
190
274
|
const li = document.createElement("li");
|
|
191
275
|
li.id = `${this.optionIdPrefix}-${index}`;
|
|
192
276
|
li.className = "midwest-autocomplete-option";
|
|
193
277
|
li.setAttribute("role", "option");
|
|
194
|
-
li.setAttribute("aria-selected", this.
|
|
278
|
+
li.setAttribute("aria-selected", this.isSelected(option.value) ? "true" : "false");
|
|
195
279
|
li.dataset.value = option.value;
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
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
|
+
}
|
|
204
290
|
this.listTarget.appendChild(li);
|
|
205
291
|
});
|
|
206
|
-
const availableCount = options.filter((o) => !this.
|
|
292
|
+
const availableCount = options.filter((o) => !this.isSelected(o.value)).length;
|
|
207
293
|
if (availableCount === 0) {
|
|
208
294
|
this.renderEmpty("No more options");
|
|
209
295
|
return;
|
|
210
296
|
}
|
|
297
|
+
this.loading = false;
|
|
211
298
|
this.announce(`${availableCount} option${availableCount === 1 ? "" : "s"} available`);
|
|
212
299
|
this.showDropdown();
|
|
213
300
|
}
|
|
214
301
|
renderEmpty(message = "No results") {
|
|
215
|
-
|
|
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;
|
|
216
308
|
this.announce(message);
|
|
217
309
|
this.showDropdown();
|
|
218
310
|
}
|
|
219
|
-
|
|
220
|
-
this.
|
|
221
|
-
|
|
222
|
-
this.showDropdown();
|
|
311
|
+
set loading(on) {
|
|
312
|
+
if (this.hasLoaderTarget)
|
|
313
|
+
this.loaderTarget.hidden = !on;
|
|
223
314
|
}
|
|
224
315
|
showDropdown() {
|
|
225
316
|
if (!this.dropdownTarget.matches(":popover-open"))
|
|
226
317
|
this.dropdownTarget.showPopover();
|
|
227
|
-
this.inputTarget.setAttribute("aria-expanded", "true");
|
|
228
318
|
}
|
|
229
319
|
hideDropdown() {
|
|
230
320
|
if (this.dropdownTarget.matches(":popover-open"))
|
|
231
321
|
this.dropdownTarget.hidePopover();
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
this.
|
|
322
|
+
}
|
|
323
|
+
visibleOptions() {
|
|
324
|
+
const container = this.hasTurboListTarget ? this.turboListTarget : this.listTarget;
|
|
325
|
+
return container.querySelectorAll('.midwest-autocomplete-option:not([aria-selected="true"])');
|
|
235
326
|
}
|
|
236
327
|
syncHighlight(items) {
|
|
237
328
|
items.forEach((item, i) => item.classList.toggle("is-highlighted", i === this.highlightedIndex));
|
|
238
329
|
const active = items[this.highlightedIndex];
|
|
239
330
|
active?.scrollIntoView({ block: "nearest" });
|
|
240
|
-
if (active) {
|
|
331
|
+
if (active?.id) {
|
|
241
332
|
this.inputTarget.setAttribute("aria-activedescendant", active.id);
|
|
242
333
|
} else {
|
|
243
334
|
this.inputTarget.removeAttribute("aria-activedescendant");
|
|
@@ -245,7 +336,7 @@ class Autocomplete extends Controller {
|
|
|
245
336
|
}
|
|
246
337
|
pickItem(item) {
|
|
247
338
|
const value = item.dataset.value ?? "";
|
|
248
|
-
const label = item.querySelector(".midwest-autocomplete-option-label")?.textContent?.trim()
|
|
339
|
+
const label = item.dataset.label?.trim() || item.querySelector(".midwest-autocomplete-option-label")?.textContent?.trim() || value;
|
|
249
340
|
const support = item.querySelector(".midwest-autocomplete-option-support")?.textContent?.trim();
|
|
250
341
|
this.select({ value, label, support });
|
|
251
342
|
}
|
|
@@ -253,9 +344,6 @@ class Autocomplete extends Controller {
|
|
|
253
344
|
if (this.hasStatusTarget)
|
|
254
345
|
this.statusTarget.textContent = message;
|
|
255
346
|
}
|
|
256
|
-
escapeHtml(str) {
|
|
257
|
-
return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
258
|
-
}
|
|
259
347
|
}
|
|
260
348
|
|
|
261
349
|
export { Autocomplete as default };
|
|
@@ -1 +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', 'token', 'multipleTokenTemplate', 'singleTokenTemplate', 'status']\n static values = {\n options: { type: Array, default: [] },\n url: { type: String, default: '' },\n multiple: { type: Boolean, default: true }\n }\n\n declare readonly inputTarget: HTMLInputElement\n declare readonly dropdownTarget: HTMLElement\n declare readonly listTarget: HTMLElement\n declare readonly tokensTarget: HTMLElement\n declare readonly tokenTargets: HTMLElement[]\n declare readonly multipleTokenTemplateTarget: HTMLTemplateElement\n declare readonly singleTokenTemplateTarget: HTMLTemplateElement\n declare readonly statusTarget: HTMLElement\n declare readonly hasStatusTarget: boolean\n\n declare optionsValue: Option[]\n declare urlValue: string\n declare multipleValue: boolean\n\n private selectedValues = new Map<string, Option>()\n private highlightedIndex = -1\n private fetchAbort: AbortController | null = null\n\n connect () {\n this.loadInitialTokens()\n }\n\n disconnect () {\n this.fetchAbort?.abort()\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 const query = this.inputTarget.value.trim()\n if (this.urlValue) {\n this.fetch(query)\n } else {\n this.renderOptions(this.filteredOptions(query))\n }\n }\n\n async search () {\n const query = this.inputTarget.value.trim()\n if (this.urlValue) {\n await this.fetch(query)\n } else {\n this.renderOptions(this.filteredOptions(query))\n }\n this.showDropdown()\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 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 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 entries = Array.from(this.selectedValues.keys())\n if (entries.length > 0) this.deselect(entries[entries.length - 1])\n }\n break\n }\n }\n\n private visibleOptions (): NodeListOf<HTMLElement> {\n return this.listTarget.querySelectorAll<HTMLElement>('.midwest-autocomplete-option:not([aria-selected=\"true\"])')\n }\n\n handleBlur () {\n // Delay to let mousedown on an option fire first.\n setTimeout(() => this.hideDropdown(), 150)\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 // ── Private ────────────────────────────────────────────────────────────────\n\n private get optionIdPrefix (): string {\n return `ac-opt-${(this.element.dataset.name ?? 'field').replace(/\\W/g, '-')}`\n }\n\n private loadInitialTokens () {\n this.tokenTargets.forEach(token => {\n const value = token.dataset.value ?? ''\n const label = token.querySelector('.value')?.textContent?.trim() ?? value\n this.selectedValues.set(value, { value, label })\n })\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 async fetch (query: string) {\n this.fetchAbort?.abort()\n this.fetchAbort = new AbortController()\n\n this.showLoading()\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 if (!this.multipleValue) {\n // Clear any existing hidden inputs and tokens for single-select\n this.selectedValues.forEach((_, val) => this.removeTokenElement(val))\n this.selectedValues.clear()\n }\n\n this.selectedValues.set(option.value, 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 option = this.selectedValues.get(value)\n this.selectedValues.delete(value)\n this.removeTokenElement(value)\n if (option) this.announce(`${option.label} removed`)\n this.dispatch('change', { detail: { value: null } })\n }\n\n private appendToken (option: Option) {\n const name = this.element.dataset.name ?? ''\n\n if (this.multipleValue) {\n const fragment = this.multipleTokenTemplateTarget.content.cloneNode(true) as DocumentFragment\n const badge = fragment.querySelector<HTMLElement>('.midwest-badge')!\n badge.dataset.value = option.value\n badge.querySelector<HTMLElement>('.value')!.textContent = option.label\n const removeBtn = badge.querySelector<HTMLElement>('.remove')!\n removeBtn.dataset.value = option.value\n removeBtn.setAttribute('aria-label', `Remove ${option.label}`)\n const input = badge.querySelector<HTMLInputElement>('input[type=hidden]')!\n input.name = name\n input.value = option.value\n this.tokensTarget.appendChild(fragment)\n } else {\n this.tokensTarget.querySelectorAll<HTMLElement>('[data-value]').forEach(el => el.remove())\n const fragment = this.singleTokenTemplateTarget.content.cloneNode(true) as DocumentFragment\n const badge = fragment.querySelector<HTMLElement>('.midwest-badge')!\n badge.dataset.value = option.value\n badge.querySelector<HTMLElement>('.value')!.textContent = option.label\n const input = badge.querySelector<HTMLInputElement>('input[type=hidden]')!\n input.name = name\n input.value = option.value\n this.tokensTarget.appendChild(fragment)\n }\n }\n\n private removeTokenElement (value: string) {\n const token = this.tokensTarget.querySelector(`[data-value=\"${CSS.escape(value)}\"]`)\n token?.remove()\n }\n\n private renderOptions (options: Option[]) {\n this.listTarget.innerHTML = ''\n this.highlightedIndex = -1\n\n if (options.length === 0) {\n this.renderEmpty()\n return\n }\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.selectedValues.has(option.value) ? 'true' : 'false')\n li.dataset.value = option.value\n li.innerHTML = `\n <span class=\"midwest-autocomplete-option-label\">${this.escapeHtml(option.label)}</span>\n ${option.support ? `<span class=\"midwest-autocomplete-option-support\">${this.escapeHtml(option.support)}</span>` : ''}\n `\n li.addEventListener('mousedown', e => {\n e.preventDefault()\n this.select(option)\n })\n this.listTarget.appendChild(li)\n })\n\n const availableCount = options.filter(o => !this.selectedValues.has(o.value)).length\n if (availableCount === 0) {\n this.renderEmpty('No more options')\n return\n }\n this.announce(`${availableCount} option${availableCount === 1 ? '' : 's'} available`)\n this.showDropdown()\n }\n\n private renderEmpty (message = 'No results') {\n this.listTarget.innerHTML = `<li class=\"midwest-autocomplete-empty\" aria-hidden=\"true\">${this.escapeHtml(message)}</li>`\n this.announce(message)\n this.showDropdown()\n }\n\n private showLoading () {\n this.listTarget.innerHTML = '<li class=\"midwest-autocomplete-loading\" aria-hidden=\"true\">Loading…</li>'\n this.announce('Loading')\n this.showDropdown()\n }\n\n private showDropdown () {\n if (!this.dropdownTarget.matches(':popover-open')) this.dropdownTarget.showPopover()\n this.inputTarget.setAttribute('aria-expanded', 'true')\n }\n\n private hideDropdown () {\n if (this.dropdownTarget.matches(':popover-open')) this.dropdownTarget.hidePopover()\n this.inputTarget.setAttribute('aria-expanded', 'false')\n this.inputTarget.removeAttribute('aria-activedescendant')\n this.highlightedIndex = -1\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) {\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 const label = 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 private escapeHtml (str: string): string {\n return str.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/\"/g, '"')\n }\n}\n"],"names":[],"mappings":"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"}
|
|
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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"}
|
|
@@ -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":";;AAGA,MAAM,mBAAA,uBAA0B,GAAI,CAAA;AAAA,EAClC,QAAA;AAAA,EAAU,QAAA;AAAA,EAAU,OAAA;AAAA,EAAS,QAAA;AAAA,EAAU,OAAA;AACzC,CAAC,CAAA,CAAA;AAOD,MAAqB,wBAAwB,UAAwB,CAAA;AAAA,EACnE,OAAO,OAAU,GAAA,CAAC,MAAM,CAAA,CAAA;AAAA,EAIxB,OAAW,GAAA;AACT,IAAA,IAAA,CAAK,MAAO,EAAA,CAAA;AACZ,IAAA,IAAA,CAAK,IAAM,EAAA,gBAAA,CAAiB,OAAS,EAAA,IAAA,CAAK,MAAM,CAAA,CAAA;AAChD,IAAA,IAAA,CAAK,IAAM,EAAA,gBAAA,CAAiB,QAAU,EAAA,IAAA,CAAK,MAAM,CAAA,CAAA;AACjD,IAAA,IAAA,CAAK,IAAM,EAAA,gBAAA,CAAiB,6BAA+B,EAAA,IAAA,CAAK,MAAM,CAAA,CAAA;AAAA,GACxE;AAAA,EAEA,UAAc,GAAA;AACZ,IAAA,IAAA,CAAK,IAAM,EAAA,mBAAA,CAAoB,OAAS,EAAA,IAAA,CAAK,MAAM,CAAA,CAAA;AACnD,IAAA,IAAA,CAAK,IAAM,EAAA,mBAAA,CAAoB,QAAU,EAAA,IAAA,CAAK,MAAM,CAAA,CAAA;AACpD,IAAA,IAAA,CAAK,IAAM,EAAA,mBAAA,CAAoB,6BAA+B,EAAA,IAAA,CAAK,MAAM,CAAA,CAAA;AAAA,GAC3E;AAAA,EAEA,IAAY,IAAgC,GAAA;AAC1C,IAAO,OAAA,IAAA,CAAK,OAAQ,CAAA,OAAA,CAAQ,MAAM,CAAA,CAAA;AAAA,GACpC;AAAA,EAEQ,SAAS,MAAM;AACrB,IAAM,MAAA,MAAA,GAAS,KAAK,aAAc,EAAA,CAAA;AAElC,IAAI,IAAA,MAAA,CAAO,WAAW,CAAG,EAAA;AACvB,MAAA,IAAA,CAAK,UAAW,CAAA,eAAA;AAAA,QACd,MAAO,CAAA,MAAA,CAAO,QAAS,CAAA,aAAA,CAAc,GAAG,CAAG,EAAA;AAAA,UACzC,SAAW,EAAA,iCAAA;AAAA,UACX,WAAa,EAAA,uBAAA;AAAA,SACd,CAAA;AAAA,OACH,CAAA;AACA,MAAA,OAAA;AAAA,KACF;AAEA,IAAM,MAAA,EAAA,GAAK,QAAS,CAAA,aAAA,CAAc,IAAI,CAAA,CAAA;AACtC,IAAA,EAAA,CAAG,SAAY,GAAA,gCAAA,CAAA;AAEf,IAAA,MAAA,CAAO,OAAQ,CAAA,CAAC,EAAE,KAAA,EAAO,OAAY,KAAA;AACnC,MAAA,MAAM,KAAK,MAAO,CAAA,MAAA,CAAO,QAAS,CAAA,aAAA,CAAc,IAAI,CAAG,EAAA;AAAA,QACrD,SAAW,EAAA,gCAAA;AAAA,QACX,WAAa,EAAA,KAAA;AAAA,OACd,CAAA,CAAA;AACD,MAAA,MAAM,KAAK,MAAO,CAAA,MAAA,CAAO,QAAS,CAAA,aAAA,CAAc,IAAI,CAAG,EAAA;AAAA,QACrD,SAAW,EAAA,sCAAA;AAAA,QACX,aAAa,KAAS,IAAA,QAAA;AAAA,OACvB,CAAA,CAAA;AACD,MAAG,EAAA,CAAA,MAAA,CAAO,IAAI,EAAE,CAAA,CAAA;AAAA,KACjB,CAAA,CAAA;AAED,IAAK,IAAA,CAAA,UAAA,CAAW,gBAAgB,EAAE,CAAA,CAAA;AAAA,GACpC,CAAA;AAAA,EAEQ,aAAiC,GAAA;AACvC,IAAA,MAAM,OAAO,IAAK,CAAA,IAAA,CAAA;AAClB,IAAA,IAAI,CAAC,IAAA;AAAM,MAAA,OAAO,EAAC,CAAA;AAEnB,IAAA,MAAM,SAAyB,EAAC,CAAA;AAChC,IAAM,MAAA,cAAA,uBAAqB,GAAY,EAAA,CAAA;AAEvC,IAAA,KAAA,CAAM,IAAK,CAAA,IAAA,CAAK,QAAQ,CAAA,CAAE,QAAQ,CAAM,EAAA,KAAA;AACtC,MAAA,MAAM,OAAU,GAAA,EAAA,CAAA;AAEhB,MAAA,IAAI,CAAC,OAAQ,CAAA,IAAA;AAAM,QAAA,OAAA;AAGnB,MAAI,IAAA,IAAA,CAAK,OAAQ,CAAA,QAAA,CAAS,OAAO,CAAA;AAAG,QAAA,OAAA;AAGpC,MAAA,IAAI,OAAmB,YAAA,iBAAA;AAAmB,QAAA,OAAA;AAE1C,MAAA,IAAI,mBAAmB,gBAAkB,EAAA;AACvC,QAAI,IAAA,mBAAA,CAAoB,GAAI,CAAA,OAAA,CAAQ,IAAI,CAAA;AAAG,UAAA,OAAA;AAE3C,QAAI,IAAA,OAAA,CAAQ,SAAS,OAAS,EAAA;AAC5B,UAAI,IAAA,cAAA,CAAe,GAAI,CAAA,OAAA,CAAQ,IAAI,CAAA;AAAG,YAAA,OAAA;AACtC,UAAe,cAAA,CAAA,GAAA,CAAI,QAAQ,IAAI,CAAA,CAAA;AAE/B,UAAA,MAAM,UAAU,IAAK,CAAA,aAAA;AAAA,YACnB,CAA6B,0BAAA,EAAA,GAAA,CAAI,MAAO,CAAA,OAAA,CAAQ,IAAI,CAAA,CAAA,UAAA,CAAA;AAAA,WACtD,CAAA;AACA,UAAA,IAAI,CAAC,OAAA;AAAS,YAAA,OAAA;AAEd,UAAA,MAAA,CAAO,IAAK,CAAA;AAAA,YACV,KAAA,EAAO,IAAK,CAAA,aAAA,CAAc,OAAO,CAAA;AAAA,YACjC,KAAO,EAAA,IAAA,CAAK,QAAS,CAAA,OAAO,KAAK,OAAQ,CAAA,KAAA;AAAA,WAC1C,CAAA,CAAA;AACD,UAAA,OAAA;AAAA,SACF;AAEA,QAAI,IAAA,OAAA,CAAQ,SAAS,UAAY,EAAA;AAC/B,UAAA,MAAA,CAAO,IAAK,CAAA;AAAA,YACV,KAAA,EAAO,IAAK,CAAA,QAAA,CAAS,OAAO,CAAA;AAAA,YAC5B,KAAA,EAAO,OAAQ,CAAA,OAAA,GAAU,KAAQ,GAAA,IAAA;AAAA,WAClC,CAAA,CAAA;AACD,UAAA,OAAA;AAAA,SACF;AAAA,OACF;AAEA,MAAA,IAAI,mBAAmB,iBAAmB,EAAA;AACxC,QAAA,MAAA,CAAO,IAAK,CAAA;AAAA,UACV,KAAA,EAAO,IAAK,CAAA,QAAA,CAAS,OAAO,CAAA;AAAA,UAC5B,KAAO,EAAA,KAAA,CAAM,IAAK,CAAA,OAAA,CAAQ,eAAe,CAAA,CAAE,GAAI,CAAA,CAAA,CAAA,KAAK,CAAE,CAAA,IAAI,CAAE,CAAA,IAAA,CAAK,IAAI,CAAK,IAAA,QAAA;AAAA,SAC3E,CAAA,CAAA;AACD,QAAA,OAAA;AAAA,OACF;AAGA,MAAA,MAAA,CAAO,IAAK,CAAA;AAAA,QACV,KAAA,EAAO,IAAK,CAAA,QAAA,CAAS,OAAO,CAAA;AAAA,QAC5B,OAAO,OAAQ,CAAA,KAAA;AAAA,OAChB,CAAA,CAAA;AAAA,KACF,CAAA,CAAA;AAOD,IAAA,IAAA,CAAK,gBAA8B,CAAA,uBAAuB,CAAE,CAAA,OAAA,CAAQ,CAAM,EAAA,KAAA;AACxE,MAAI,IAAA,IAAA,CAAK,OAAQ,CAAA,QAAA,CAAS,EAAE,CAAA;AAAG,QAAA,OAAA;AAC/B,MAAI,IAAA,CAAC,EAAG,CAAA,aAAA,CAAc,mBAAmB,CAAA;AAAG,QAAA,OAAA;AAE5C,MAAM,MAAA,OAAA,GAAU,EAAG,CAAA,aAAA,CAA2B,OAAO,CAAA,CAAA;AACrD,MAAA,MAAM,KAAQ,GAAA,OAAA,EAAS,WAAa,EAAA,IAAA,MAClC,IAAK,CAAA,YAAA,CAAA,CAAc,EAAG,CAAA,OAAA,CAAQ,IAAQ,IAAA,EAAA,EAAI,OAAQ,CAAA,SAAA,EAAW,EAAE,CAAC,CAAA,CAAA;AAElE,MAAA,MAAM,WAAW,KAAM,CAAA,IAAA,CAAK,GAAG,gBAA8B,CAAA,qCAAqC,CAAC,CAChG,CAAA,GAAA,CAAI,QAAM,EAAG,CAAA,WAAA,EAAa,MAAM,CAAA,CAChC,OAAO,OAAO,CAAA,CACd,KAAK,IAAI,CAAA,CAAA;AACZ,MAAA,MAAA,CAAO,KAAK,EAAE,KAAA,EAAO,KAAO,EAAA,QAAA,IAAY,UAAK,CAAA,CAAA;AAAA,KAC9C,CAAA,CAAA;AAED,IAAO,OAAA,MAAA,CAAA;AAAA,GACT;AAAA;AAAA;AAAA,EAIQ,SAAU,EAAqB,EAAA;AACrC,IAAA,MAAM,KAAM,EAAmB,CAAA,EAAA,CAAA;AAC/B,IAAA,IAAI,EAAI,EAAA;AACN,MAAM,MAAA,KAAA,GAAQ,KAAK,IAAM,EAAA,aAAA,CAAc,cAAc,GAAI,CAAA,MAAA,CAAO,EAAE,CAAK,CAAA,EAAA,CAAA,CAAA,CAAA;AACvE,MAAI,IAAA,KAAA;AAAO,QAAO,OAAA,KAAA,CAAM,WAAa,EAAA,IAAA,EAAU,IAAA,EAAA,CAAA;AAAA,KACjD;AACA,IAAM,MAAA,OAAA,GAAU,EAAG,CAAA,OAAA,CAAQ,OAAO,CAAA,CAAA;AAClC,IAAI,IAAA,OAAA;AAAS,MAAO,OAAA,OAAA,CAAQ,WAAa,EAAA,IAAA,EAAU,IAAA,EAAA,CAAA;AACnD,IAAA,OAAO,IAAK,CAAA,YAAA,CAAc,EAAwB,CAAA,IAAA,IAAQ,EAAE,CAAA,CAAA;AAAA,GAC9D;AAAA;AAAA;AAAA,EAIQ,cAAe,EAAqB,EAAA;AAC1C,IAAA,MAAM,SAAS,EAAG,CAAA,OAAA,CAAQ,UAAU,CAAA,EAAG,cAAc,QAAQ,CAAA,CAAA;AAC7D,IAAI,IAAA,MAAA;AAAQ,MAAO,OAAA,MAAA,CAAO,WAAa,EAAA,IAAA,EAAU,IAAA,EAAA,CAAA;AACjD,IAAA,OAAO,IAAK,CAAA,YAAA,CAAc,EAAwB,CAAA,IAAA,IAAQ,EAAE,CAAA,CAAA;AAAA,GAC9D;AAAA;AAAA,EAGQ,aAAc,IAAsB,EAAA;AAC1C,IAAA,OAAO,KACJ,OAAQ,CAAA,UAAA,EAAY,EAAE,CAAA,CACtB,QAAQ,IAAM,EAAA,GAAG,CACjB,CAAA,OAAA,CAAQ,OAAO,CAAK,CAAA,KAAA,CAAA,CAAE,WAAY,EAAC,EACnC,IAAK,EAAA,CAAA;AAAA,GACV;AACF;;;;"}
|