@unabridged/midwest 0.19.2 → 0.20.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/README.md +1 -1
- package/app/assets/javascript/midwest/index.ts +6 -0
- package/app/assets/javascript/midwest/template_helpers.ts +27 -0
- package/app/assets/javascript/midwest.js +656 -0
- package/app/assets/javascript/midwest.js.map +1 -1
- package/app/assets/stylesheets/midwest.css +1 -1
- package/app/assets/stylesheets/midwest.tailwind.css +5 -1
- package/dist/css/midwest.css +1 -1
- package/dist/javascript/collection/app/assets/javascript/midwest/index.js +6 -0
- package/dist/javascript/collection/app/assets/javascript/midwest/index.js.map +1 -1
- package/dist/javascript/collection/app/assets/javascript/midwest/template_helpers.js +10 -0
- package/dist/javascript/collection/app/assets/javascript/midwest/template_helpers.js.map +1 -0
- package/dist/javascript/collection/app/components/midwest/card_component/card_component_controller.js.map +1 -1
- package/dist/javascript/collection/app/components/midwest/command_palette_component/command_palette_component_controller.js +350 -0
- package/dist/javascript/collection/app/components/midwest/command_palette_component/command_palette_component_controller.js.map +1 -0
- package/dist/javascript/collection/app/components/midwest/form/live_summary_component/live_summary_component_controller.js +24 -0
- package/dist/javascript/collection/app/components/midwest/form/live_summary_component/live_summary_component_controller.js.map +1 -1
- package/dist/javascript/collection/app/components/midwest/show_if_component/show_if_component_controller.js +80 -0
- package/dist/javascript/collection/app/components/midwest/show_if_component/show_if_component_controller.js.map +1 -0
- package/dist/javascript/collection/app/components/midwest/tree_view_component/tree_view_component_controller.js +204 -0
- package/dist/javascript/collection/app/components/midwest/tree_view_component/tree_view_component_controller.js.map +1 -0
- package/dist/javascript/midwest.js +656 -0
- package/dist/javascript/midwest.js.map +1 -1
- package/package.json +1 -1
|
@@ -3244,8 +3244,12 @@ class FormLiveSummary extends Controller {
|
|
|
3244
3244
|
return;
|
|
3245
3245
|
if (this.element.contains(element))
|
|
3246
3246
|
return;
|
|
3247
|
+
if (element.closest("fieldset.midwest-show-if[hidden]"))
|
|
3248
|
+
return;
|
|
3247
3249
|
if (element instanceof HTMLButtonElement)
|
|
3248
3250
|
return;
|
|
3251
|
+
if (element.closest(".midwest-tree-view"))
|
|
3252
|
+
return;
|
|
3249
3253
|
if (element instanceof HTMLInputElement) {
|
|
3250
3254
|
if (SKIPPED_INPUT_TYPES.has(element.type))
|
|
3251
3255
|
return;
|
|
@@ -3306,6 +3310,26 @@ class FormLiveSummary extends Controller {
|
|
|
3306
3310
|
const selected = Array.from(ac.querySelectorAll(".midwest-autocomplete-tokens .value")).map((el) => el.textContent?.trim()).filter(Boolean).join(", ");
|
|
3307
3311
|
fields.push({ label, value: selected || "\u2014" });
|
|
3308
3312
|
});
|
|
3313
|
+
const seenTreeNames = /* @__PURE__ */ new Set();
|
|
3314
|
+
form.querySelectorAll(".midwest-tree-view").forEach((tree) => {
|
|
3315
|
+
if (this.element.contains(tree))
|
|
3316
|
+
return;
|
|
3317
|
+
const checked = Array.from(
|
|
3318
|
+
tree.querySelectorAll(".tree-node-checkbox:checked")
|
|
3319
|
+
);
|
|
3320
|
+
const firstName = checked[0]?.name || tree.querySelectorAll(".tree-node-checkbox")[0]?.name;
|
|
3321
|
+
if (!firstName)
|
|
3322
|
+
return;
|
|
3323
|
+
if (seenTreeNames.has(firstName))
|
|
3324
|
+
return;
|
|
3325
|
+
seenTreeNames.add(firstName);
|
|
3326
|
+
const label = this.prettifyName(firstName.replace(/\[\]$/, ""));
|
|
3327
|
+
const values = checked.map((cb) => {
|
|
3328
|
+
const nodeLabel = cb.closest(".midwest-tree-node")?.querySelector(":scope > .tree-node-trigger > .tree-node-label");
|
|
3329
|
+
return nodeLabel?.textContent?.trim() || cb.value;
|
|
3330
|
+
});
|
|
3331
|
+
fields.push({ label, value: values.join(", ") || "\u2014" });
|
|
3332
|
+
});
|
|
3309
3333
|
return fields;
|
|
3310
3334
|
}
|
|
3311
3335
|
// Builds a small inline colour swatch to display alongside a colour value.
|
|
@@ -4925,6 +4949,635 @@ class Pagination extends Controller {
|
|
|
4925
4949
|
}
|
|
4926
4950
|
}
|
|
4927
4951
|
|
|
4952
|
+
class ShowIf extends Controller {
|
|
4953
|
+
static values = {
|
|
4954
|
+
watch: { type: String, default: "" },
|
|
4955
|
+
is: { type: String, default: "" },
|
|
4956
|
+
negate: { type: Boolean, default: false }
|
|
4957
|
+
};
|
|
4958
|
+
sourceElement = null;
|
|
4959
|
+
listenedElements = [];
|
|
4960
|
+
changeListener = null;
|
|
4961
|
+
connect() {
|
|
4962
|
+
if (!this.watchValue)
|
|
4963
|
+
return;
|
|
4964
|
+
this.sourceElement = document.querySelector(this.watchValue);
|
|
4965
|
+
if (!this.sourceElement)
|
|
4966
|
+
return;
|
|
4967
|
+
this.changeListener = () => this.evaluate();
|
|
4968
|
+
if (this.sourceElement instanceof HTMLInputElement && this.sourceElement.type === "radio") {
|
|
4969
|
+
const radios = document.querySelectorAll(
|
|
4970
|
+
`input[type="radio"][name="${this.sourceElement.name}"]`
|
|
4971
|
+
);
|
|
4972
|
+
const listener = this.changeListener;
|
|
4973
|
+
radios.forEach((radio) => {
|
|
4974
|
+
radio.addEventListener("change", listener);
|
|
4975
|
+
this.listenedElements.push(radio);
|
|
4976
|
+
});
|
|
4977
|
+
} else {
|
|
4978
|
+
this.sourceElement.addEventListener("input", this.changeListener);
|
|
4979
|
+
this.sourceElement.addEventListener("change", this.changeListener);
|
|
4980
|
+
this.listenedElements.push(this.sourceElement);
|
|
4981
|
+
}
|
|
4982
|
+
this.evaluate();
|
|
4983
|
+
}
|
|
4984
|
+
disconnect() {
|
|
4985
|
+
if (this.changeListener) {
|
|
4986
|
+
for (const el of this.listenedElements) {
|
|
4987
|
+
el.removeEventListener("input", this.changeListener);
|
|
4988
|
+
el.removeEventListener("change", this.changeListener);
|
|
4989
|
+
}
|
|
4990
|
+
}
|
|
4991
|
+
this.changeListener = null;
|
|
4992
|
+
this.sourceElement = null;
|
|
4993
|
+
this.listenedElements = [];
|
|
4994
|
+
}
|
|
4995
|
+
evaluate() {
|
|
4996
|
+
const el = this.sourceElement;
|
|
4997
|
+
if (!el)
|
|
4998
|
+
return;
|
|
4999
|
+
const currentValue = this.readValue(el);
|
|
5000
|
+
const match = currentValue === this.isValue;
|
|
5001
|
+
const visible = this.negateValue ? !match : match;
|
|
5002
|
+
this.element.toggleAttribute("hidden", !visible);
|
|
5003
|
+
this.element.toggleAttribute("disabled", !visible);
|
|
5004
|
+
}
|
|
5005
|
+
readValue(el) {
|
|
5006
|
+
if (el instanceof HTMLInputElement) {
|
|
5007
|
+
if (el.type === "radio") {
|
|
5008
|
+
const checked = document.querySelector(
|
|
5009
|
+
`input[name="${el.name}"]:checked`
|
|
5010
|
+
);
|
|
5011
|
+
return checked ? checked.value : "";
|
|
5012
|
+
}
|
|
5013
|
+
if (el.type === "checkbox") {
|
|
5014
|
+
return el.checked ? el.value || "true" : "";
|
|
5015
|
+
}
|
|
5016
|
+
return el.value;
|
|
5017
|
+
}
|
|
5018
|
+
if (el instanceof HTMLSelectElement) {
|
|
5019
|
+
return el.value;
|
|
5020
|
+
}
|
|
5021
|
+
if (el instanceof HTMLTextAreaElement) {
|
|
5022
|
+
return el.value;
|
|
5023
|
+
}
|
|
5024
|
+
return el.getAttribute("value") ?? "";
|
|
5025
|
+
}
|
|
5026
|
+
}
|
|
5027
|
+
|
|
5028
|
+
class TreeView extends Controller {
|
|
5029
|
+
// ── Expand / Collapse ───────────────────────────────────────────────
|
|
5030
|
+
toggle(event) {
|
|
5031
|
+
const chevron = event.currentTarget.closest(".tree-node-chevron");
|
|
5032
|
+
if (!chevron)
|
|
5033
|
+
return;
|
|
5034
|
+
const node = chevron.closest(".midwest-tree-node");
|
|
5035
|
+
if (!node)
|
|
5036
|
+
return;
|
|
5037
|
+
const expanded = node.getAttribute("aria-expanded") === "true";
|
|
5038
|
+
node.setAttribute("aria-expanded", String(!expanded));
|
|
5039
|
+
this.focusNode(node);
|
|
5040
|
+
}
|
|
5041
|
+
// ── Checkbox Cascading ──────────────────────────────────────────────
|
|
5042
|
+
checkboxChanged(event) {
|
|
5043
|
+
const checkbox = event.target;
|
|
5044
|
+
const node = checkbox.closest(".midwest-tree-node");
|
|
5045
|
+
if (!node)
|
|
5046
|
+
return;
|
|
5047
|
+
const descendants = node.querySelectorAll(
|
|
5048
|
+
".tree-node-children .tree-node-checkbox"
|
|
5049
|
+
);
|
|
5050
|
+
for (const desc of descendants) {
|
|
5051
|
+
desc.checked = checkbox.checked;
|
|
5052
|
+
desc.indeterminate = false;
|
|
5053
|
+
}
|
|
5054
|
+
this.updateAncestors(node);
|
|
5055
|
+
}
|
|
5056
|
+
updateAncestors(node) {
|
|
5057
|
+
const parentGroup = node.parentElement;
|
|
5058
|
+
if (!parentGroup || !parentGroup.classList.contains("tree-node-children"))
|
|
5059
|
+
return;
|
|
5060
|
+
const parentNode = parentGroup.closest(".midwest-tree-node");
|
|
5061
|
+
if (!parentNode)
|
|
5062
|
+
return;
|
|
5063
|
+
const parentCheckbox = parentNode.querySelector(
|
|
5064
|
+
":scope > .tree-node-trigger > .tree-node-checkbox"
|
|
5065
|
+
);
|
|
5066
|
+
if (!parentCheckbox)
|
|
5067
|
+
return;
|
|
5068
|
+
const childCheckboxes = parentGroup.querySelectorAll(
|
|
5069
|
+
":scope > .midwest-tree-node > .tree-node-trigger > .tree-node-checkbox"
|
|
5070
|
+
);
|
|
5071
|
+
const total = childCheckboxes.length;
|
|
5072
|
+
let checked = 0;
|
|
5073
|
+
let indeterminate = 0;
|
|
5074
|
+
for (const cb of childCheckboxes) {
|
|
5075
|
+
if (cb.indeterminate)
|
|
5076
|
+
indeterminate++;
|
|
5077
|
+
else if (cb.checked)
|
|
5078
|
+
checked++;
|
|
5079
|
+
}
|
|
5080
|
+
if (indeterminate > 0 || checked > 0 && checked < total) {
|
|
5081
|
+
parentCheckbox.checked = false;
|
|
5082
|
+
parentCheckbox.indeterminate = true;
|
|
5083
|
+
} else if (checked === total) {
|
|
5084
|
+
parentCheckbox.checked = true;
|
|
5085
|
+
parentCheckbox.indeterminate = false;
|
|
5086
|
+
} else {
|
|
5087
|
+
parentCheckbox.checked = false;
|
|
5088
|
+
parentCheckbox.indeterminate = false;
|
|
5089
|
+
}
|
|
5090
|
+
this.updateAncestors(parentNode);
|
|
5091
|
+
}
|
|
5092
|
+
// ── Keyboard Navigation (WAI-ARIA TreeView) ────────────────────────
|
|
5093
|
+
connect() {
|
|
5094
|
+
this.element.addEventListener("keydown", this.handleKeydown);
|
|
5095
|
+
const firstNode = this.element.querySelector(".midwest-tree-node");
|
|
5096
|
+
if (firstNode) {
|
|
5097
|
+
firstNode.setAttribute("tabindex", "0");
|
|
5098
|
+
}
|
|
5099
|
+
}
|
|
5100
|
+
disconnect() {
|
|
5101
|
+
this.element.removeEventListener("keydown", this.handleKeydown);
|
|
5102
|
+
}
|
|
5103
|
+
handleKeydown = (event) => {
|
|
5104
|
+
const target = event.target.closest(".midwest-tree-node");
|
|
5105
|
+
if (!target)
|
|
5106
|
+
return;
|
|
5107
|
+
switch (event.key) {
|
|
5108
|
+
case "ArrowDown":
|
|
5109
|
+
event.preventDefault();
|
|
5110
|
+
this.moveFocus(target, "next");
|
|
5111
|
+
break;
|
|
5112
|
+
case "ArrowUp":
|
|
5113
|
+
event.preventDefault();
|
|
5114
|
+
this.moveFocus(target, "previous");
|
|
5115
|
+
break;
|
|
5116
|
+
case "ArrowRight":
|
|
5117
|
+
event.preventDefault();
|
|
5118
|
+
this.handleArrowRight(target);
|
|
5119
|
+
break;
|
|
5120
|
+
case "ArrowLeft":
|
|
5121
|
+
event.preventDefault();
|
|
5122
|
+
this.handleArrowLeft(target);
|
|
5123
|
+
break;
|
|
5124
|
+
case "Home":
|
|
5125
|
+
event.preventDefault();
|
|
5126
|
+
this.focusFirst();
|
|
5127
|
+
break;
|
|
5128
|
+
case "End":
|
|
5129
|
+
event.preventDefault();
|
|
5130
|
+
this.focusLast();
|
|
5131
|
+
break;
|
|
5132
|
+
case "Enter":
|
|
5133
|
+
case " ":
|
|
5134
|
+
event.preventDefault();
|
|
5135
|
+
this.handleActivate(target);
|
|
5136
|
+
break;
|
|
5137
|
+
}
|
|
5138
|
+
};
|
|
5139
|
+
getVisibleNodes() {
|
|
5140
|
+
const nodes = [];
|
|
5141
|
+
const walk = (container) => {
|
|
5142
|
+
const children = container.querySelectorAll(
|
|
5143
|
+
":scope > .midwest-tree-node"
|
|
5144
|
+
);
|
|
5145
|
+
for (const child of children) {
|
|
5146
|
+
nodes.push(child);
|
|
5147
|
+
if (child.getAttribute("aria-expanded") === "true") {
|
|
5148
|
+
const group = child.querySelector(":scope > .tree-node-children");
|
|
5149
|
+
if (group)
|
|
5150
|
+
walk(group);
|
|
5151
|
+
}
|
|
5152
|
+
}
|
|
5153
|
+
};
|
|
5154
|
+
walk(this.element);
|
|
5155
|
+
return nodes;
|
|
5156
|
+
}
|
|
5157
|
+
moveFocus(current, direction) {
|
|
5158
|
+
const visible = this.getVisibleNodes();
|
|
5159
|
+
const index = visible.indexOf(current);
|
|
5160
|
+
if (index === -1)
|
|
5161
|
+
return;
|
|
5162
|
+
const nextIndex = direction === "next" ? index + 1 : index - 1;
|
|
5163
|
+
if (nextIndex >= 0 && nextIndex < visible.length) {
|
|
5164
|
+
this.focusNode(visible[nextIndex]);
|
|
5165
|
+
}
|
|
5166
|
+
}
|
|
5167
|
+
handleArrowRight(node) {
|
|
5168
|
+
const isExpanded = node.getAttribute("aria-expanded");
|
|
5169
|
+
if (isExpanded === "false") {
|
|
5170
|
+
node.setAttribute("aria-expanded", "true");
|
|
5171
|
+
} else if (isExpanded === "true") {
|
|
5172
|
+
const firstChild = node.querySelector(
|
|
5173
|
+
":scope > .tree-node-children > .midwest-tree-node"
|
|
5174
|
+
);
|
|
5175
|
+
if (firstChild)
|
|
5176
|
+
this.focusNode(firstChild);
|
|
5177
|
+
}
|
|
5178
|
+
}
|
|
5179
|
+
handleArrowLeft(node) {
|
|
5180
|
+
const isExpanded = node.getAttribute("aria-expanded");
|
|
5181
|
+
if (isExpanded === "true") {
|
|
5182
|
+
node.setAttribute("aria-expanded", "false");
|
|
5183
|
+
} else {
|
|
5184
|
+
const parentGroup = node.parentElement;
|
|
5185
|
+
if (parentGroup && parentGroup.classList.contains("tree-node-children")) {
|
|
5186
|
+
const parentNode = parentGroup.closest(".midwest-tree-node");
|
|
5187
|
+
if (parentNode)
|
|
5188
|
+
this.focusNode(parentNode);
|
|
5189
|
+
}
|
|
5190
|
+
}
|
|
5191
|
+
}
|
|
5192
|
+
handleActivate(node) {
|
|
5193
|
+
const checkbox = node.querySelector(
|
|
5194
|
+
":scope > .tree-node-trigger > .tree-node-checkbox"
|
|
5195
|
+
);
|
|
5196
|
+
if (checkbox) {
|
|
5197
|
+
checkbox.checked = !checkbox.checked;
|
|
5198
|
+
checkbox.dispatchEvent(new Event("change", { bubbles: true }));
|
|
5199
|
+
return;
|
|
5200
|
+
}
|
|
5201
|
+
const expanded = node.getAttribute("aria-expanded");
|
|
5202
|
+
if (expanded !== null) {
|
|
5203
|
+
node.setAttribute("aria-expanded", String(expanded !== "true"));
|
|
5204
|
+
}
|
|
5205
|
+
}
|
|
5206
|
+
focusNode(node) {
|
|
5207
|
+
const current = this.element.querySelector(
|
|
5208
|
+
'.midwest-tree-node[tabindex="0"]'
|
|
5209
|
+
);
|
|
5210
|
+
if (current)
|
|
5211
|
+
current.setAttribute("tabindex", "-1");
|
|
5212
|
+
node.setAttribute("tabindex", "0");
|
|
5213
|
+
node.focus();
|
|
5214
|
+
}
|
|
5215
|
+
focusFirst() {
|
|
5216
|
+
const first = this.element.querySelector(".midwest-tree-node");
|
|
5217
|
+
if (first)
|
|
5218
|
+
this.focusNode(first);
|
|
5219
|
+
}
|
|
5220
|
+
focusLast() {
|
|
5221
|
+
const visible = this.getVisibleNodes();
|
|
5222
|
+
if (visible.length > 0) {
|
|
5223
|
+
this.focusNode(visible[visible.length - 1]);
|
|
5224
|
+
}
|
|
5225
|
+
}
|
|
5226
|
+
}
|
|
5227
|
+
|
|
5228
|
+
function cloneTemplate(template) {
|
|
5229
|
+
const fragment = template.content.cloneNode(true);
|
|
5230
|
+
const el = fragment.firstElementChild;
|
|
5231
|
+
if (!el)
|
|
5232
|
+
throw new Error("Template has no element child");
|
|
5233
|
+
return el;
|
|
5234
|
+
}
|
|
5235
|
+
|
|
5236
|
+
class CommandPalette extends Controller {
|
|
5237
|
+
static targets = ["input", "list", "empty", "body", "turboList", "itemTemplate", "groupTemplate"];
|
|
5238
|
+
static values = {
|
|
5239
|
+
hotkey: { type: String, default: "k" },
|
|
5240
|
+
items: { type: Array, default: [] },
|
|
5241
|
+
src: { type: String, default: "" },
|
|
5242
|
+
turboFrameUrl: { type: String, default: "" }
|
|
5243
|
+
};
|
|
5244
|
+
highlightedIndex = -1;
|
|
5245
|
+
fetchAbort = null;
|
|
5246
|
+
searchTimer;
|
|
5247
|
+
allItems = [];
|
|
5248
|
+
connect() {
|
|
5249
|
+
this.allItems = this.itemsValue;
|
|
5250
|
+
document.addEventListener("keydown", this.handleGlobalKeydown);
|
|
5251
|
+
this.element.addEventListener("click", this.handleBackdropClick);
|
|
5252
|
+
if (!this.turboFrameUrlValue) {
|
|
5253
|
+
this.renderItems(this.allItems);
|
|
5254
|
+
}
|
|
5255
|
+
}
|
|
5256
|
+
disconnect() {
|
|
5257
|
+
document.removeEventListener("keydown", this.handleGlobalKeydown);
|
|
5258
|
+
this.element.removeEventListener("click", this.handleBackdropClick);
|
|
5259
|
+
if (this.fetchAbort)
|
|
5260
|
+
this.fetchAbort.abort();
|
|
5261
|
+
if (this.searchTimer)
|
|
5262
|
+
clearTimeout(this.searchTimer);
|
|
5263
|
+
}
|
|
5264
|
+
// ── Turbo frame lifecycle ───────────────────────────────────────────
|
|
5265
|
+
turboListTargetConnected(el) {
|
|
5266
|
+
el.addEventListener("turbo:frame-load", this.onFrameLoad);
|
|
5267
|
+
}
|
|
5268
|
+
turboListTargetDisconnected(el) {
|
|
5269
|
+
el.removeEventListener("turbo:frame-load", this.onFrameLoad);
|
|
5270
|
+
}
|
|
5271
|
+
onFrameLoad = () => {
|
|
5272
|
+
if (!this.hasListTarget)
|
|
5273
|
+
return;
|
|
5274
|
+
const items = this.listTarget.querySelectorAll(".command-palette-item");
|
|
5275
|
+
if (items.length > 0) {
|
|
5276
|
+
this.highlightedIndex = 0;
|
|
5277
|
+
this.updateHighlight();
|
|
5278
|
+
this.emptyTarget.hidden = true;
|
|
5279
|
+
if (this.hasListTarget)
|
|
5280
|
+
this.listTarget.hidden = false;
|
|
5281
|
+
} else {
|
|
5282
|
+
this.emptyTarget.hidden = false;
|
|
5283
|
+
if (this.hasListTarget)
|
|
5284
|
+
this.listTarget.hidden = true;
|
|
5285
|
+
}
|
|
5286
|
+
};
|
|
5287
|
+
// ── Public actions ──────────────────────────────────────────────────
|
|
5288
|
+
open() {
|
|
5289
|
+
if (this.element.open)
|
|
5290
|
+
return;
|
|
5291
|
+
this.element.showModal();
|
|
5292
|
+
this.inputTarget.value = "";
|
|
5293
|
+
this.highlightedIndex = -1;
|
|
5294
|
+
if (this.turboFrameUrlValue) {
|
|
5295
|
+
this.navigateFrame("");
|
|
5296
|
+
} else {
|
|
5297
|
+
this.renderItems(this.allItems);
|
|
5298
|
+
}
|
|
5299
|
+
requestAnimationFrame(() => this.inputTarget.focus());
|
|
5300
|
+
}
|
|
5301
|
+
close() {
|
|
5302
|
+
if (!this.element.open)
|
|
5303
|
+
return;
|
|
5304
|
+
this.element.close();
|
|
5305
|
+
}
|
|
5306
|
+
filter() {
|
|
5307
|
+
const query = this.inputTarget.value.trim().toLowerCase();
|
|
5308
|
+
if (this.turboFrameUrlValue) {
|
|
5309
|
+
this.debouncedNavigateFrame(query);
|
|
5310
|
+
return;
|
|
5311
|
+
}
|
|
5312
|
+
if (this.srcValue && query.length > 0) {
|
|
5313
|
+
this.fetchRemote(query);
|
|
5314
|
+
return;
|
|
5315
|
+
}
|
|
5316
|
+
if (query.length === 0) {
|
|
5317
|
+
this.renderItems(this.allItems);
|
|
5318
|
+
return;
|
|
5319
|
+
}
|
|
5320
|
+
const filtered = this.allItems.filter((item) => {
|
|
5321
|
+
const haystack = `${item.label} ${item.description || ""} ${item.group || ""}`.toLowerCase();
|
|
5322
|
+
return haystack.includes(query);
|
|
5323
|
+
});
|
|
5324
|
+
this.renderItems(filtered);
|
|
5325
|
+
}
|
|
5326
|
+
navigate(event) {
|
|
5327
|
+
const items = this.visibleItems;
|
|
5328
|
+
switch (event.key) {
|
|
5329
|
+
case "ArrowDown":
|
|
5330
|
+
event.preventDefault();
|
|
5331
|
+
this.highlightedIndex = Math.min(this.highlightedIndex + 1, items.length - 1);
|
|
5332
|
+
this.updateHighlight();
|
|
5333
|
+
break;
|
|
5334
|
+
case "ArrowUp":
|
|
5335
|
+
event.preventDefault();
|
|
5336
|
+
this.highlightedIndex = Math.max(this.highlightedIndex - 1, 0);
|
|
5337
|
+
this.updateHighlight();
|
|
5338
|
+
break;
|
|
5339
|
+
case "Enter":
|
|
5340
|
+
event.preventDefault();
|
|
5341
|
+
if (this.highlightedIndex >= 0 && this.highlightedIndex < items.length) {
|
|
5342
|
+
this.selectItem(items[this.highlightedIndex]);
|
|
5343
|
+
}
|
|
5344
|
+
break;
|
|
5345
|
+
case "Escape":
|
|
5346
|
+
event.preventDefault();
|
|
5347
|
+
this.close();
|
|
5348
|
+
break;
|
|
5349
|
+
}
|
|
5350
|
+
}
|
|
5351
|
+
select(event) {
|
|
5352
|
+
const el = event.target.closest(".command-palette-item");
|
|
5353
|
+
if (!el)
|
|
5354
|
+
return;
|
|
5355
|
+
const index = parseInt(el.dataset.index || "-1", 10);
|
|
5356
|
+
const items = this.visibleItems;
|
|
5357
|
+
if (index >= 0 && index < items.length) {
|
|
5358
|
+
this.selectItem(items[index]);
|
|
5359
|
+
}
|
|
5360
|
+
}
|
|
5361
|
+
// ── Remote search ───────────────────────────────────────────────────
|
|
5362
|
+
fetchRemote(query) {
|
|
5363
|
+
if (this.fetchAbort)
|
|
5364
|
+
this.fetchAbort.abort();
|
|
5365
|
+
if (this.searchTimer)
|
|
5366
|
+
clearTimeout(this.searchTimer);
|
|
5367
|
+
this.searchTimer = window.setTimeout(async () => {
|
|
5368
|
+
this.fetchAbort = new AbortController();
|
|
5369
|
+
try {
|
|
5370
|
+
const url = new URL(this.srcValue, window.location.origin);
|
|
5371
|
+
url.searchParams.set("q", query);
|
|
5372
|
+
const response = await fetch(url.toString(), {
|
|
5373
|
+
headers: { Accept: "application/json" },
|
|
5374
|
+
signal: this.fetchAbort.signal
|
|
5375
|
+
});
|
|
5376
|
+
if (response.ok) {
|
|
5377
|
+
const data = await response.json();
|
|
5378
|
+
this.renderItems(data);
|
|
5379
|
+
}
|
|
5380
|
+
} catch (e) {
|
|
5381
|
+
if (e.name !== "AbortError")
|
|
5382
|
+
throw e;
|
|
5383
|
+
}
|
|
5384
|
+
}, 200);
|
|
5385
|
+
}
|
|
5386
|
+
// ── Turbo frame navigation ──────────────────────────────────────────
|
|
5387
|
+
navigateFrame(query) {
|
|
5388
|
+
if (!this.hasTurboListTarget)
|
|
5389
|
+
return;
|
|
5390
|
+
const url = new URL(this.turboFrameUrlValue, window.location.href);
|
|
5391
|
+
if (query)
|
|
5392
|
+
url.searchParams.set("q", query);
|
|
5393
|
+
this.turboListTarget.setAttribute("src", url.toString());
|
|
5394
|
+
}
|
|
5395
|
+
debouncedNavigateFrame(query) {
|
|
5396
|
+
if (this.searchTimer)
|
|
5397
|
+
clearTimeout(this.searchTimer);
|
|
5398
|
+
this.searchTimer = window.setTimeout(() => {
|
|
5399
|
+
this.navigateFrame(query);
|
|
5400
|
+
}, 200);
|
|
5401
|
+
}
|
|
5402
|
+
// ── Rendering ───────────────────────────────────────────────────────
|
|
5403
|
+
renderItems(items) {
|
|
5404
|
+
if (!this.hasListTarget)
|
|
5405
|
+
return;
|
|
5406
|
+
this.listTarget.innerHTML = "";
|
|
5407
|
+
this.highlightedIndex = -1;
|
|
5408
|
+
if (items.length === 0) {
|
|
5409
|
+
this.emptyTarget.hidden = false;
|
|
5410
|
+
this.listTarget.hidden = true;
|
|
5411
|
+
return;
|
|
5412
|
+
}
|
|
5413
|
+
this.emptyTarget.hidden = true;
|
|
5414
|
+
this.listTarget.hidden = false;
|
|
5415
|
+
const groups = /* @__PURE__ */ new Map();
|
|
5416
|
+
for (const item of items) {
|
|
5417
|
+
const group = item.group || "";
|
|
5418
|
+
if (!groups.has(group))
|
|
5419
|
+
groups.set(group, []);
|
|
5420
|
+
groups.get(group).push(item);
|
|
5421
|
+
}
|
|
5422
|
+
let index = 0;
|
|
5423
|
+
for (const [groupName, groupItems] of groups) {
|
|
5424
|
+
if (groupName) {
|
|
5425
|
+
this.listTarget.appendChild(this.buildGroup(groupName));
|
|
5426
|
+
}
|
|
5427
|
+
for (const item of groupItems) {
|
|
5428
|
+
this.listTarget.appendChild(this.buildItem(item, index));
|
|
5429
|
+
index++;
|
|
5430
|
+
}
|
|
5431
|
+
}
|
|
5432
|
+
if (items.length > 0) {
|
|
5433
|
+
this.highlightedIndex = 0;
|
|
5434
|
+
this.updateHighlight();
|
|
5435
|
+
}
|
|
5436
|
+
}
|
|
5437
|
+
buildGroup(name) {
|
|
5438
|
+
const li = cloneTemplate(this.groupTemplateTarget);
|
|
5439
|
+
const h6 = li.querySelector(".midwest-label");
|
|
5440
|
+
if (h6)
|
|
5441
|
+
h6.textContent = name;
|
|
5442
|
+
return li;
|
|
5443
|
+
}
|
|
5444
|
+
buildItem(item, index) {
|
|
5445
|
+
const li = cloneTemplate(this.itemTemplateTarget);
|
|
5446
|
+
li.dataset.index = String(index);
|
|
5447
|
+
if (item.url)
|
|
5448
|
+
li.dataset.url = item.url;
|
|
5449
|
+
if (item.action)
|
|
5450
|
+
li.dataset.paletteAction = item.action;
|
|
5451
|
+
if (item.method)
|
|
5452
|
+
li.dataset.turboMethod = item.method;
|
|
5453
|
+
if (item.confirm)
|
|
5454
|
+
li.dataset.turboConfirm = item.confirm;
|
|
5455
|
+
const iconSpan = li.querySelector(".command-palette-item-icon");
|
|
5456
|
+
if (item.icon) {
|
|
5457
|
+
iconSpan.innerHTML = this.getIconSvg(item.icon);
|
|
5458
|
+
iconSpan.hidden = false;
|
|
5459
|
+
} else {
|
|
5460
|
+
iconSpan.remove();
|
|
5461
|
+
}
|
|
5462
|
+
const label = li.querySelector(".command-palette-item-label");
|
|
5463
|
+
label.textContent = item.label;
|
|
5464
|
+
const desc = li.querySelector(".command-palette-item-description");
|
|
5465
|
+
if (item.description) {
|
|
5466
|
+
desc.textContent = item.description;
|
|
5467
|
+
desc.hidden = false;
|
|
5468
|
+
} else {
|
|
5469
|
+
desc.remove();
|
|
5470
|
+
}
|
|
5471
|
+
const shortcutSpan = li.querySelector(".command-palette-item-shortcut");
|
|
5472
|
+
if (item.shortcut) {
|
|
5473
|
+
item.shortcut.split("+").forEach((key) => {
|
|
5474
|
+
const kbd = document.createElement("kbd");
|
|
5475
|
+
kbd.className = "midwest-kbd";
|
|
5476
|
+
kbd.textContent = key.trim();
|
|
5477
|
+
shortcutSpan.appendChild(kbd);
|
|
5478
|
+
});
|
|
5479
|
+
shortcutSpan.hidden = false;
|
|
5480
|
+
} else {
|
|
5481
|
+
shortcutSpan.remove();
|
|
5482
|
+
}
|
|
5483
|
+
return li;
|
|
5484
|
+
}
|
|
5485
|
+
// ── Selection ───────────────────────────────────────────────────────
|
|
5486
|
+
selectItem(item) {
|
|
5487
|
+
if (item.confirm && !window.confirm(item.confirm))
|
|
5488
|
+
return;
|
|
5489
|
+
this.close();
|
|
5490
|
+
if (item.url) {
|
|
5491
|
+
this.visit(item.url, item.method);
|
|
5492
|
+
return;
|
|
5493
|
+
}
|
|
5494
|
+
if (item.action) {
|
|
5495
|
+
this.element.dispatchEvent(
|
|
5496
|
+
new CustomEvent("midwest-command-palette:action", {
|
|
5497
|
+
bubbles: true,
|
|
5498
|
+
detail: { action: item.action, item }
|
|
5499
|
+
})
|
|
5500
|
+
);
|
|
5501
|
+
}
|
|
5502
|
+
}
|
|
5503
|
+
visit(url, method) {
|
|
5504
|
+
if (method && method.toLowerCase() !== "get") {
|
|
5505
|
+
const link = document.createElement("a");
|
|
5506
|
+
link.href = url;
|
|
5507
|
+
link.dataset.turboMethod = method;
|
|
5508
|
+
link.style.display = "none";
|
|
5509
|
+
document.body.appendChild(link);
|
|
5510
|
+
link.click();
|
|
5511
|
+
link.remove();
|
|
5512
|
+
return;
|
|
5513
|
+
}
|
|
5514
|
+
const turbo = window.Turbo;
|
|
5515
|
+
if (turbo?.visit) {
|
|
5516
|
+
turbo.visit(url);
|
|
5517
|
+
} else {
|
|
5518
|
+
window.location.href = url;
|
|
5519
|
+
}
|
|
5520
|
+
}
|
|
5521
|
+
// ── Highlight ───────────────────────────────────────────────────────
|
|
5522
|
+
get visibleItems() {
|
|
5523
|
+
if (!this.hasListTarget)
|
|
5524
|
+
return [];
|
|
5525
|
+
const items = [];
|
|
5526
|
+
const els = this.listTarget.querySelectorAll(".command-palette-item");
|
|
5527
|
+
for (const el of els) {
|
|
5528
|
+
items.push({
|
|
5529
|
+
label: el.querySelector(".command-palette-item-label")?.textContent || "",
|
|
5530
|
+
url: el.dataset.url,
|
|
5531
|
+
action: el.dataset.paletteAction,
|
|
5532
|
+
method: el.dataset.turboMethod,
|
|
5533
|
+
confirm: el.dataset.turboConfirm
|
|
5534
|
+
});
|
|
5535
|
+
}
|
|
5536
|
+
return items;
|
|
5537
|
+
}
|
|
5538
|
+
updateHighlight() {
|
|
5539
|
+
if (!this.hasListTarget)
|
|
5540
|
+
return;
|
|
5541
|
+
const items = this.listTarget.querySelectorAll(".command-palette-item");
|
|
5542
|
+
items.forEach((el, i) => {
|
|
5543
|
+
el.setAttribute("aria-selected", String(i === this.highlightedIndex));
|
|
5544
|
+
});
|
|
5545
|
+
const highlighted = items[this.highlightedIndex];
|
|
5546
|
+
if (highlighted) {
|
|
5547
|
+
highlighted.scrollIntoView({ block: "nearest" });
|
|
5548
|
+
this.inputTarget.setAttribute("aria-activedescendant", highlighted.id || "");
|
|
5549
|
+
}
|
|
5550
|
+
}
|
|
5551
|
+
// ── Hotkey ──────────────────────────────────────────────────────────
|
|
5552
|
+
handleGlobalKeydown = (event) => {
|
|
5553
|
+
if ((event.metaKey || event.ctrlKey) && event.key === this.hotkeyValue) {
|
|
5554
|
+
event.preventDefault();
|
|
5555
|
+
if (this.element.open) {
|
|
5556
|
+
this.close();
|
|
5557
|
+
} else {
|
|
5558
|
+
this.open();
|
|
5559
|
+
}
|
|
5560
|
+
}
|
|
5561
|
+
};
|
|
5562
|
+
handleBackdropClick = (event) => {
|
|
5563
|
+
if (event.target === this.element)
|
|
5564
|
+
this.close();
|
|
5565
|
+
};
|
|
5566
|
+
// ── Helpers ─────────────────────────────────────────────────────────
|
|
5567
|
+
getIconSvg(name) {
|
|
5568
|
+
const existing = document.querySelector(`.Midwest-Icon__Svg[aria-label="${name}"]`);
|
|
5569
|
+
if (existing)
|
|
5570
|
+
return existing.outerHTML;
|
|
5571
|
+
const allIcons = document.querySelectorAll(".Midwest-Icon__Svg");
|
|
5572
|
+
for (const icon of allIcons) {
|
|
5573
|
+
if (icon.getAttribute("aria-label")?.toLowerCase() === name.toLowerCase()) {
|
|
5574
|
+
return icon.outerHTML;
|
|
5575
|
+
}
|
|
5576
|
+
}
|
|
5577
|
+
return "";
|
|
5578
|
+
}
|
|
5579
|
+
}
|
|
5580
|
+
|
|
4928
5581
|
function registerMidwestControllers(application) {
|
|
4929
5582
|
application.register("midwest-card", Card);
|
|
4930
5583
|
application.register("midwest-banner", Banner);
|
|
@@ -4953,6 +5606,9 @@ function registerMidwestControllers(application) {
|
|
|
4953
5606
|
application.register("midwest-table", Table);
|
|
4954
5607
|
application.register("midwest-load-more", LoadMore);
|
|
4955
5608
|
application.register("midwest-pagination", Pagination);
|
|
5609
|
+
application.register("midwest-show-if", ShowIf);
|
|
5610
|
+
application.register("midwest-tree-view", TreeView);
|
|
5611
|
+
application.register("midwest-command-palette", CommandPalette);
|
|
4956
5612
|
}
|
|
4957
5613
|
|
|
4958
5614
|
export { Banner, Card, Chart, CountdownTimer, registerMidwestControllers };
|