@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.
- package/app/assets/javascript/midwest/index.ts +2 -0
- package/app/assets/javascript/midwest.js +375 -1
- package/app/assets/javascript/midwest.js.map +1 -1
- package/app/assets/stylesheets/midwest.css +1 -1
- package/app/assets/stylesheets/midwest.tailwind.css +1 -0
- package/dist/css/midwest.css +1 -1
- package/dist/javascript/collection/app/assets/javascript/midwest/index.js +2 -0
- package/dist/javascript/collection/app/assets/javascript/midwest/index.js.map +1 -1
- package/dist/javascript/collection/app/components/midwest/form/autocomplete_component/autocomplete_component_controller.js +350 -0
- package/dist/javascript/collection/app/components/midwest/form/autocomplete_component/autocomplete_component_controller.js.map +1 -0
- package/dist/javascript/collection/app/components/midwest/form/input_component/input_component_controller.js +16 -1
- package/dist/javascript/collection/app/components/midwest/form/input_component/input_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 +375 -1
- package/dist/javascript/midwest.js.map +1 -1
- package/package.json +1 -1
|
@@ -17,6 +17,7 @@ import AvatarGroup from '../../../components/midwest/avatar_group_component/avat
|
|
|
17
17
|
import Chart from '../../../components/midwest/chart_component/chart_component_controller'
|
|
18
18
|
import Range from '../../../components/midwest/form/range_component/range_component_controller'
|
|
19
19
|
import Rating from '../../../components/midwest/rating_component/rating_component_controller'
|
|
20
|
+
import Autocomplete from '../../../components/midwest/form/autocomplete_component/autocomplete_component_controller'
|
|
20
21
|
/* IMPORTS */
|
|
21
22
|
|
|
22
23
|
export { Card, Banner, CountdownTimer, Chart }
|
|
@@ -41,5 +42,6 @@ export function registerMidwestControllers (application: any) {
|
|
|
41
42
|
application.register('midwest-chart', Chart)
|
|
42
43
|
application.register('midwest-range', Range)
|
|
43
44
|
application.register('midwest-rating', Rating)
|
|
45
|
+
application.register('midwest-autocomplete', Autocomplete)
|
|
44
46
|
/* EXPORTS */
|
|
45
47
|
}
|
|
@@ -549,17 +549,23 @@ class Banner extends Controller {
|
|
|
549
549
|
}
|
|
550
550
|
|
|
551
551
|
class Input extends Controller {
|
|
552
|
-
static targets = ["input"];
|
|
552
|
+
static targets = ["input", "counter"];
|
|
553
553
|
updateClearButton = null;
|
|
554
|
+
updateCounter = null;
|
|
554
555
|
connect() {
|
|
555
556
|
this.applyColor();
|
|
556
557
|
this.applySearchClear();
|
|
558
|
+
this.applyCounter();
|
|
557
559
|
}
|
|
558
560
|
disconnect() {
|
|
559
561
|
if (this.updateClearButton) {
|
|
560
562
|
this.inputTarget.removeEventListener("input", this.updateClearButton);
|
|
561
563
|
this.updateClearButton = null;
|
|
562
564
|
}
|
|
565
|
+
if (this.updateCounter) {
|
|
566
|
+
this.inputTarget.removeEventListener("input", this.updateCounter);
|
|
567
|
+
this.updateCounter = null;
|
|
568
|
+
}
|
|
563
569
|
}
|
|
564
570
|
applyColor() {
|
|
565
571
|
if (this.inputTarget.type !== "color") {
|
|
@@ -569,6 +575,15 @@ class Input extends Controller {
|
|
|
569
575
|
this.inputTarget.setAttribute("value", this.inputTarget.value);
|
|
570
576
|
});
|
|
571
577
|
}
|
|
578
|
+
applyCounter() {
|
|
579
|
+
if (!this.hasCounterTarget)
|
|
580
|
+
return;
|
|
581
|
+
this.counterTarget.textContent = String(this.inputTarget.value.length);
|
|
582
|
+
this.updateCounter = () => {
|
|
583
|
+
this.counterTarget.textContent = String(this.inputTarget.value.length);
|
|
584
|
+
};
|
|
585
|
+
this.inputTarget.addEventListener("input", this.updateCounter);
|
|
586
|
+
}
|
|
572
587
|
applySearchClear() {
|
|
573
588
|
if (this.inputTarget.type !== "search")
|
|
574
589
|
return;
|
|
@@ -3450,10 +3465,12 @@ class FormLiveSummary extends Controller {
|
|
|
3450
3465
|
this.render();
|
|
3451
3466
|
this.form?.addEventListener("input", this.render);
|
|
3452
3467
|
this.form?.addEventListener("change", this.render);
|
|
3468
|
+
this.form?.addEventListener("midwest-autocomplete:change", this.render);
|
|
3453
3469
|
}
|
|
3454
3470
|
disconnect() {
|
|
3455
3471
|
this.form?.removeEventListener("input", this.render);
|
|
3456
3472
|
this.form?.removeEventListener("change", this.render);
|
|
3473
|
+
this.form?.removeEventListener("midwest-autocomplete:change", this.render);
|
|
3457
3474
|
}
|
|
3458
3475
|
get form() {
|
|
3459
3476
|
return this.element.closest("form");
|
|
@@ -3536,6 +3553,16 @@ class FormLiveSummary extends Controller {
|
|
|
3536
3553
|
value: element.value
|
|
3537
3554
|
});
|
|
3538
3555
|
});
|
|
3556
|
+
form.querySelectorAll(".midwest-autocomplete").forEach((ac) => {
|
|
3557
|
+
if (this.element.contains(ac))
|
|
3558
|
+
return;
|
|
3559
|
+
if (!ac.querySelector('[role="combobox"]'))
|
|
3560
|
+
return;
|
|
3561
|
+
const labelEl = ac.querySelector("label");
|
|
3562
|
+
const label = labelEl?.textContent?.trim() || this.prettifyName((ac.dataset.name ?? "").replace(/\[.*\]$/, ""));
|
|
3563
|
+
const selected = Array.from(ac.querySelectorAll(".midwest-autocomplete-tokens .value")).map((el) => el.textContent?.trim()).filter(Boolean).join(", ");
|
|
3564
|
+
fields.push({ label, value: selected || "\u2014" });
|
|
3565
|
+
});
|
|
3539
3566
|
return fields;
|
|
3540
3567
|
}
|
|
3541
3568
|
// Finds the label associated with a specific element via `for` attribute,
|
|
@@ -3804,6 +3831,352 @@ class Rating extends Controller {
|
|
|
3804
3831
|
}
|
|
3805
3832
|
}
|
|
3806
3833
|
|
|
3834
|
+
class Autocomplete extends Controller {
|
|
3835
|
+
static targets = ["input", "dropdown", "list", "tokens", "multipleTokenTemplate", "singleTokenTemplate", "status", "turboList", "loader"];
|
|
3836
|
+
static values = {
|
|
3837
|
+
options: { type: Array, default: [] },
|
|
3838
|
+
url: { type: String, default: "" },
|
|
3839
|
+
multiple: { type: Boolean, default: true },
|
|
3840
|
+
turboFrameUrl: { type: String, default: "" }
|
|
3841
|
+
};
|
|
3842
|
+
highlightedIndex = -1;
|
|
3843
|
+
fetchAbort = null;
|
|
3844
|
+
searchTimer;
|
|
3845
|
+
// ── Named handlers ─────────────────────────────────────────────────────────
|
|
3846
|
+
// Arrow-function class properties preserve `this` and produce a stable
|
|
3847
|
+
// reference so addEventListener/removeEventListener pairs always match.
|
|
3848
|
+
onFrameLoad = () => {
|
|
3849
|
+
this.syncSelectedState();
|
|
3850
|
+
this.loading = false;
|
|
3851
|
+
this.showDropdown();
|
|
3852
|
+
};
|
|
3853
|
+
onToggle = (e) => {
|
|
3854
|
+
const opened = e.newState === "open";
|
|
3855
|
+
this.inputTarget.setAttribute("aria-expanded", String(opened));
|
|
3856
|
+
if (!opened) {
|
|
3857
|
+
this.inputTarget.removeAttribute("aria-activedescendant");
|
|
3858
|
+
this.highlightedIndex = -1;
|
|
3859
|
+
}
|
|
3860
|
+
};
|
|
3861
|
+
onFocusOut = (e) => {
|
|
3862
|
+
if (!this.element.contains(e.relatedTarget)) {
|
|
3863
|
+
this.hideDropdown();
|
|
3864
|
+
}
|
|
3865
|
+
};
|
|
3866
|
+
onListMouseDown = (e) => {
|
|
3867
|
+
const option = e.target.closest(".midwest-autocomplete-option");
|
|
3868
|
+
if (option) {
|
|
3869
|
+
e.preventDefault();
|
|
3870
|
+
this.pickItem(option);
|
|
3871
|
+
}
|
|
3872
|
+
};
|
|
3873
|
+
// ── Lifecycle ───────────────────────────────────────────────────────────────
|
|
3874
|
+
connect() {
|
|
3875
|
+
this.element.addEventListener("focusout", this.onFocusOut);
|
|
3876
|
+
}
|
|
3877
|
+
disconnect() {
|
|
3878
|
+
this.fetchAbort?.abort();
|
|
3879
|
+
clearTimeout(this.searchTimer);
|
|
3880
|
+
this.element.removeEventListener("focusout", this.onFocusOut);
|
|
3881
|
+
}
|
|
3882
|
+
// Target callbacks — Stimulus calls these automatically when targets connect
|
|
3883
|
+
// or disconnect, including after the initial connect(). This replaces manual
|
|
3884
|
+
// hasTurboListTarget guards and addEventListener calls inside connect().
|
|
3885
|
+
dropdownTargetConnected(el) {
|
|
3886
|
+
el.addEventListener("toggle", this.onToggle);
|
|
3887
|
+
}
|
|
3888
|
+
dropdownTargetDisconnected(el) {
|
|
3889
|
+
el.removeEventListener("toggle", this.onToggle);
|
|
3890
|
+
}
|
|
3891
|
+
listTargetConnected(el) {
|
|
3892
|
+
el.addEventListener("mousedown", this.onListMouseDown);
|
|
3893
|
+
}
|
|
3894
|
+
listTargetDisconnected(el) {
|
|
3895
|
+
el.removeEventListener("mousedown", this.onListMouseDown);
|
|
3896
|
+
}
|
|
3897
|
+
turboListTargetConnected(el) {
|
|
3898
|
+
el.addEventListener("turbo:frame-load", this.onFrameLoad);
|
|
3899
|
+
}
|
|
3900
|
+
turboListTargetDisconnected(el) {
|
|
3901
|
+
el.removeEventListener("turbo:frame-load", this.onFrameLoad);
|
|
3902
|
+
}
|
|
3903
|
+
// ── Public actions ──────────────────────────────────────────────────────────
|
|
3904
|
+
focusInput() {
|
|
3905
|
+
this.inputTarget.focus();
|
|
3906
|
+
this.open();
|
|
3907
|
+
}
|
|
3908
|
+
open() {
|
|
3909
|
+
if (this.dropdownTarget.matches(":popover-open"))
|
|
3910
|
+
return;
|
|
3911
|
+
clearTimeout(this.searchTimer);
|
|
3912
|
+
this.resolve(this.inputTarget.value.trim());
|
|
3913
|
+
}
|
|
3914
|
+
search() {
|
|
3915
|
+
const query = this.inputTarget.value.trim();
|
|
3916
|
+
if (!this.hasTurboListTarget && !this.urlValue) {
|
|
3917
|
+
this.renderOptions(this.filteredOptions(query));
|
|
3918
|
+
return;
|
|
3919
|
+
}
|
|
3920
|
+
clearTimeout(this.searchTimer);
|
|
3921
|
+
this.searchTimer = window.setTimeout(() => {
|
|
3922
|
+
this.searchTimer = void 0;
|
|
3923
|
+
this.resolve(query);
|
|
3924
|
+
}, 200);
|
|
3925
|
+
}
|
|
3926
|
+
navigate(event) {
|
|
3927
|
+
switch (event.key) {
|
|
3928
|
+
case "ArrowDown": {
|
|
3929
|
+
event.preventDefault();
|
|
3930
|
+
if (!this.dropdownTarget.matches(":popover-open"))
|
|
3931
|
+
this.open();
|
|
3932
|
+
const items = this.visibleOptions();
|
|
3933
|
+
this.highlightedIndex = Math.min(this.highlightedIndex + 1, items.length - 1);
|
|
3934
|
+
this.syncHighlight(items);
|
|
3935
|
+
break;
|
|
3936
|
+
}
|
|
3937
|
+
case "ArrowUp": {
|
|
3938
|
+
event.preventDefault();
|
|
3939
|
+
if (!this.dropdownTarget.matches(":popover-open"))
|
|
3940
|
+
break;
|
|
3941
|
+
const items = this.visibleOptions();
|
|
3942
|
+
this.highlightedIndex = Math.max(this.highlightedIndex - 1, 0);
|
|
3943
|
+
this.syncHighlight(items);
|
|
3944
|
+
break;
|
|
3945
|
+
}
|
|
3946
|
+
case "Enter": {
|
|
3947
|
+
event.preventDefault();
|
|
3948
|
+
if (!this.dropdownTarget.matches(":popover-open"))
|
|
3949
|
+
break;
|
|
3950
|
+
const items = this.visibleOptions();
|
|
3951
|
+
if (this.highlightedIndex >= 0 && items[this.highlightedIndex]) {
|
|
3952
|
+
this.pickItem(items[this.highlightedIndex]);
|
|
3953
|
+
}
|
|
3954
|
+
break;
|
|
3955
|
+
}
|
|
3956
|
+
case "Escape":
|
|
3957
|
+
this.hideDropdown();
|
|
3958
|
+
break;
|
|
3959
|
+
case "Backspace":
|
|
3960
|
+
if (this.inputTarget.value === "" && this.multipleValue) {
|
|
3961
|
+
const tokens = this.tokensTarget.querySelectorAll("[data-value]");
|
|
3962
|
+
const last = tokens[tokens.length - 1];
|
|
3963
|
+
if (last?.dataset.value)
|
|
3964
|
+
this.deselect(last.dataset.value);
|
|
3965
|
+
}
|
|
3966
|
+
break;
|
|
3967
|
+
}
|
|
3968
|
+
}
|
|
3969
|
+
removeToken(event) {
|
|
3970
|
+
event.stopPropagation();
|
|
3971
|
+
const btn = event.currentTarget;
|
|
3972
|
+
const value = btn.dataset.value;
|
|
3973
|
+
if (value)
|
|
3974
|
+
this.deselect(value);
|
|
3975
|
+
}
|
|
3976
|
+
// Public action used by turbo-frame option items via data-action.
|
|
3977
|
+
// Each server-rendered <li> should have:
|
|
3978
|
+
// data-action="mousedown->midwest-autocomplete#selectOption"
|
|
3979
|
+
selectOption(event) {
|
|
3980
|
+
event.preventDefault();
|
|
3981
|
+
this.pickItem(event.currentTarget);
|
|
3982
|
+
}
|
|
3983
|
+
// ── Private ─────────────────────────────────────────────────────────────────
|
|
3984
|
+
get optionIdPrefix() {
|
|
3985
|
+
return `ac-opt-${(this.element.dataset.name ?? "field").replace(/\W/g, "-")}`;
|
|
3986
|
+
}
|
|
3987
|
+
isSelected(value) {
|
|
3988
|
+
return !!this.tokensTarget.querySelector(`[data-value="${CSS.escape(value)}"]`);
|
|
3989
|
+
}
|
|
3990
|
+
// Routes to the correct data source. Shared by open() and the debounced search path.
|
|
3991
|
+
resolve(query) {
|
|
3992
|
+
if (this.hasTurboListTarget) {
|
|
3993
|
+
this.navigateFrame(query);
|
|
3994
|
+
return;
|
|
3995
|
+
}
|
|
3996
|
+
if (this.urlValue) {
|
|
3997
|
+
this.fetch(query);
|
|
3998
|
+
return;
|
|
3999
|
+
}
|
|
4000
|
+
this.renderOptions(this.filteredOptions(query));
|
|
4001
|
+
}
|
|
4002
|
+
filteredOptions(query) {
|
|
4003
|
+
const q = query.toLowerCase();
|
|
4004
|
+
return this.optionsValue.filter(
|
|
4005
|
+
(o) => q === "" || o.label.toLowerCase().includes(q) || (o.support?.toLowerCase().includes(q) ?? false)
|
|
4006
|
+
);
|
|
4007
|
+
}
|
|
4008
|
+
navigateFrame(query) {
|
|
4009
|
+
const url = new URL(this.turboFrameUrlValue, window.location.href);
|
|
4010
|
+
if (query)
|
|
4011
|
+
url.searchParams.set("q", query);
|
|
4012
|
+
this.tokensTarget.querySelectorAll("input[type=hidden]").forEach((input) => {
|
|
4013
|
+
url.searchParams.append("selected[]", input.value);
|
|
4014
|
+
});
|
|
4015
|
+
this.loading = true;
|
|
4016
|
+
this.turboListTarget.setAttribute("src", url.toString());
|
|
4017
|
+
this.showDropdown();
|
|
4018
|
+
}
|
|
4019
|
+
// After a turbo frame loads, mark already-selected items and update the live region.
|
|
4020
|
+
// The empty state element is always present in the frame and CSS shows/hides it
|
|
4021
|
+
// based on whether any non-selected options exist.
|
|
4022
|
+
syncSelectedState() {
|
|
4023
|
+
const options = this.turboListTarget.querySelectorAll(".midwest-autocomplete-option");
|
|
4024
|
+
options.forEach((option) => {
|
|
4025
|
+
const val = option.dataset.value ?? "";
|
|
4026
|
+
option.setAttribute("aria-selected", this.isSelected(val) ? "true" : "false");
|
|
4027
|
+
});
|
|
4028
|
+
const visibleCount = this.visibleOptions().length;
|
|
4029
|
+
this.announce(visibleCount > 0 ? `${visibleCount} option${visibleCount === 1 ? "" : "s"} available` : "No more options");
|
|
4030
|
+
}
|
|
4031
|
+
async fetch(query) {
|
|
4032
|
+
this.fetchAbort?.abort();
|
|
4033
|
+
this.fetchAbort = new AbortController();
|
|
4034
|
+
this.listTarget.replaceChildren();
|
|
4035
|
+
this.loading = true;
|
|
4036
|
+
this.announce("Loading");
|
|
4037
|
+
this.showDropdown();
|
|
4038
|
+
try {
|
|
4039
|
+
const url = new URL(this.urlValue, window.location.href);
|
|
4040
|
+
if (query)
|
|
4041
|
+
url.searchParams.set("q", query);
|
|
4042
|
+
const res = await globalThis.fetch(url.toString(), {
|
|
4043
|
+
signal: this.fetchAbort.signal,
|
|
4044
|
+
headers: { Accept: "application/json" }
|
|
4045
|
+
});
|
|
4046
|
+
const data = await res.json();
|
|
4047
|
+
this.renderOptions(data);
|
|
4048
|
+
} catch (err) {
|
|
4049
|
+
if (err.name !== "AbortError") {
|
|
4050
|
+
this.renderEmpty();
|
|
4051
|
+
}
|
|
4052
|
+
}
|
|
4053
|
+
}
|
|
4054
|
+
select(option) {
|
|
4055
|
+
this.appendToken(option);
|
|
4056
|
+
this.hideDropdown();
|
|
4057
|
+
this.inputTarget.value = "";
|
|
4058
|
+
this.inputTarget.focus();
|
|
4059
|
+
this.announce(`${option.label} selected`);
|
|
4060
|
+
this.dispatch("change", { detail: { value: option.value, label: option.label } });
|
|
4061
|
+
}
|
|
4062
|
+
deselect(value) {
|
|
4063
|
+
const token = this.tokensTarget.querySelector(`[data-value="${CSS.escape(value)}"]`);
|
|
4064
|
+
const label = token?.querySelector(".value")?.textContent?.trim();
|
|
4065
|
+
token?.remove();
|
|
4066
|
+
if (label)
|
|
4067
|
+
this.announce(`${label} removed`);
|
|
4068
|
+
this.dispatch("change", { detail: { value: null } });
|
|
4069
|
+
}
|
|
4070
|
+
appendToken(option) {
|
|
4071
|
+
if (!this.multipleValue) {
|
|
4072
|
+
this.tokensTarget.querySelectorAll("[data-value]").forEach((el) => el.remove());
|
|
4073
|
+
}
|
|
4074
|
+
const template = this.multipleValue ? this.multipleTokenTemplateTarget : this.singleTokenTemplateTarget;
|
|
4075
|
+
const fragment = template.content.cloneNode(true);
|
|
4076
|
+
const badge = fragment.querySelector(".midwest-badge");
|
|
4077
|
+
if (!badge)
|
|
4078
|
+
return;
|
|
4079
|
+
badge.dataset.value = option.value;
|
|
4080
|
+
const valueEl = badge.querySelector(".value");
|
|
4081
|
+
if (valueEl)
|
|
4082
|
+
valueEl.textContent = option.label;
|
|
4083
|
+
if (this.multipleValue) {
|
|
4084
|
+
const removeBtn = badge.querySelector(".remove");
|
|
4085
|
+
if (removeBtn) {
|
|
4086
|
+
removeBtn.dataset.value = option.value;
|
|
4087
|
+
removeBtn.setAttribute("aria-label", `Remove ${option.label}`);
|
|
4088
|
+
}
|
|
4089
|
+
}
|
|
4090
|
+
const input = badge.querySelector("input[type=hidden]");
|
|
4091
|
+
if (input) {
|
|
4092
|
+
input.name = this.element.dataset.name ?? "";
|
|
4093
|
+
input.value = option.value;
|
|
4094
|
+
}
|
|
4095
|
+
this.tokensTarget.appendChild(fragment);
|
|
4096
|
+
}
|
|
4097
|
+
renderOptions(options) {
|
|
4098
|
+
this.highlightedIndex = -1;
|
|
4099
|
+
if (options.length === 0) {
|
|
4100
|
+
this.renderEmpty();
|
|
4101
|
+
return;
|
|
4102
|
+
}
|
|
4103
|
+
this.listTarget.replaceChildren();
|
|
4104
|
+
options.forEach((option, index) => {
|
|
4105
|
+
const li = document.createElement("li");
|
|
4106
|
+
li.id = `${this.optionIdPrefix}-${index}`;
|
|
4107
|
+
li.className = "midwest-autocomplete-option";
|
|
4108
|
+
li.setAttribute("role", "option");
|
|
4109
|
+
li.setAttribute("aria-selected", this.isSelected(option.value) ? "true" : "false");
|
|
4110
|
+
li.dataset.value = option.value;
|
|
4111
|
+
const label = document.createElement("span");
|
|
4112
|
+
label.className = "midwest-autocomplete-option-label";
|
|
4113
|
+
label.textContent = option.label;
|
|
4114
|
+
li.appendChild(label);
|
|
4115
|
+
if (option.support) {
|
|
4116
|
+
const support = document.createElement("span");
|
|
4117
|
+
support.className = "midwest-autocomplete-option-support";
|
|
4118
|
+
support.textContent = option.support;
|
|
4119
|
+
li.appendChild(support);
|
|
4120
|
+
}
|
|
4121
|
+
this.listTarget.appendChild(li);
|
|
4122
|
+
});
|
|
4123
|
+
const availableCount = options.filter((o) => !this.isSelected(o.value)).length;
|
|
4124
|
+
if (availableCount === 0) {
|
|
4125
|
+
this.renderEmpty("No more options");
|
|
4126
|
+
return;
|
|
4127
|
+
}
|
|
4128
|
+
this.loading = false;
|
|
4129
|
+
this.announce(`${availableCount} option${availableCount === 1 ? "" : "s"} available`);
|
|
4130
|
+
this.showDropdown();
|
|
4131
|
+
}
|
|
4132
|
+
renderEmpty(message = "No results") {
|
|
4133
|
+
const li = document.createElement("li");
|
|
4134
|
+
li.className = "midwest-autocomplete-empty";
|
|
4135
|
+
li.setAttribute("aria-hidden", "true");
|
|
4136
|
+
li.textContent = message;
|
|
4137
|
+
this.listTarget.replaceChildren(li);
|
|
4138
|
+
this.loading = false;
|
|
4139
|
+
this.announce(message);
|
|
4140
|
+
this.showDropdown();
|
|
4141
|
+
}
|
|
4142
|
+
set loading(on) {
|
|
4143
|
+
if (this.hasLoaderTarget)
|
|
4144
|
+
this.loaderTarget.hidden = !on;
|
|
4145
|
+
}
|
|
4146
|
+
showDropdown() {
|
|
4147
|
+
if (!this.dropdownTarget.matches(":popover-open"))
|
|
4148
|
+
this.dropdownTarget.showPopover();
|
|
4149
|
+
}
|
|
4150
|
+
hideDropdown() {
|
|
4151
|
+
if (this.dropdownTarget.matches(":popover-open"))
|
|
4152
|
+
this.dropdownTarget.hidePopover();
|
|
4153
|
+
}
|
|
4154
|
+
visibleOptions() {
|
|
4155
|
+
const container = this.hasTurboListTarget ? this.turboListTarget : this.listTarget;
|
|
4156
|
+
return container.querySelectorAll('.midwest-autocomplete-option:not([aria-selected="true"])');
|
|
4157
|
+
}
|
|
4158
|
+
syncHighlight(items) {
|
|
4159
|
+
items.forEach((item, i) => item.classList.toggle("is-highlighted", i === this.highlightedIndex));
|
|
4160
|
+
const active = items[this.highlightedIndex];
|
|
4161
|
+
active?.scrollIntoView({ block: "nearest" });
|
|
4162
|
+
if (active?.id) {
|
|
4163
|
+
this.inputTarget.setAttribute("aria-activedescendant", active.id);
|
|
4164
|
+
} else {
|
|
4165
|
+
this.inputTarget.removeAttribute("aria-activedescendant");
|
|
4166
|
+
}
|
|
4167
|
+
}
|
|
4168
|
+
pickItem(item) {
|
|
4169
|
+
const value = item.dataset.value ?? "";
|
|
4170
|
+
const label = item.dataset.label?.trim() || item.querySelector(".midwest-autocomplete-option-label")?.textContent?.trim() || value;
|
|
4171
|
+
const support = item.querySelector(".midwest-autocomplete-option-support")?.textContent?.trim();
|
|
4172
|
+
this.select({ value, label, support });
|
|
4173
|
+
}
|
|
4174
|
+
announce(message) {
|
|
4175
|
+
if (this.hasStatusTarget)
|
|
4176
|
+
this.statusTarget.textContent = message;
|
|
4177
|
+
}
|
|
4178
|
+
}
|
|
4179
|
+
|
|
3807
4180
|
function registerMidwestControllers(application) {
|
|
3808
4181
|
application.register("midwest-card", Card);
|
|
3809
4182
|
application.register("midwest-banner", Banner);
|
|
@@ -3824,6 +4197,7 @@ function registerMidwestControllers(application) {
|
|
|
3824
4197
|
application.register("midwest-chart", Chart);
|
|
3825
4198
|
application.register("midwest-range", Range);
|
|
3826
4199
|
application.register("midwest-rating", Rating);
|
|
4200
|
+
application.register("midwest-autocomplete", Autocomplete);
|
|
3827
4201
|
}
|
|
3828
4202
|
|
|
3829
4203
|
export { Banner, Card, Chart, CountdownTimer, registerMidwestControllers };
|