@unabridged/midwest 0.8.2 → 0.10.0
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 +5 -5
- package/app/assets/icons/available-icons.yml +11 -0
- package/app/assets/icons/loader-audio.svg +29 -0
- package/app/assets/icons/loader-ball-triangle.svg +46 -0
- package/app/assets/icons/loader-bars.svg +52 -0
- package/app/assets/icons/loader-circles.svg +20 -0
- package/app/assets/icons/loader-grid.svg +56 -0
- package/app/assets/icons/loader-oval.svg +17 -0
- package/app/assets/icons/loader-puff.svg +37 -0
- package/app/assets/icons/loader-rings.svg +42 -0
- package/app/assets/icons/loader-spinning-circles.svg +55 -0
- package/app/assets/icons/loader-tail-spin.svg +32 -0
- package/app/assets/icons/loader-three-dots.svg +33 -0
- package/app/assets/javascript/midwest/index.ts +2 -10
- package/app/assets/javascript/midwest.js +371 -82
- package/app/assets/javascript/midwest.js.map +1 -1
- package/app/assets/stylesheets/midwest/color-system.css +0 -2
- package/app/assets/stylesheets/midwest.css +1 -1
- package/app/assets/stylesheets/midwest.tailwind.css +4 -2
- package/dist/css/midwest.css +1 -1
- package/dist/javascript/collection/app/assets/javascript/midwest/index.js +2 -10
- package/dist/javascript/collection/app/assets/javascript/midwest/index.js.map +1 -1
- package/dist/javascript/collection/app/components/midwest/card_component/card_component_controller.js +10 -2
- package/dist/javascript/collection/app/components/midwest/card_component/card_component_controller.js.map +1 -1
- package/dist/javascript/collection/app/components/midwest/carousel_component/carousel_component_controller.js +62 -2
- package/dist/javascript/collection/app/components/midwest/carousel_component/carousel_component_controller.js.map +1 -1
- package/dist/javascript/collection/app/components/midwest/dialog_component/dialog_component_controller.js +58 -17
- package/dist/javascript/collection/app/components/midwest/dialog_component/dialog_component_controller.js.map +1 -1
- package/dist/javascript/collection/app/components/midwest/dropdown_component/dropdown_component_controller.js +62 -2
- package/dist/javascript/collection/app/components/midwest/dropdown_component/dropdown_component_controller.js.map +1 -1
- package/dist/javascript/collection/app/components/midwest/form/file_component/file_component_controller.js +49 -22
- package/dist/javascript/collection/app/components/midwest/form/file_component/file_component_controller.js.map +1 -1
- package/dist/javascript/collection/app/components/midwest/form/form_controller.js +36 -0
- package/dist/javascript/collection/app/components/midwest/form/form_controller.js.map +1 -0
- package/dist/javascript/collection/app/components/midwest/form/input_component/input_component_controller.js +48 -0
- package/dist/javascript/collection/app/components/midwest/form/input_component/input_component_controller.js.map +1 -1
- package/dist/javascript/collection/app/components/midwest/prose_component/prose_component_controller.js +2 -1
- package/dist/javascript/collection/app/components/midwest/prose_component/prose_component_controller.js.map +1 -1
- package/dist/javascript/collection/app/components/midwest/tabs_component/tabs_component_controller.js +32 -0
- package/dist/javascript/collection/app/components/midwest/tabs_component/tabs_component_controller.js.map +1 -1
- package/dist/javascript/collection/app/components/midwest/tooltip_component/tooltip_component_controller.js +15 -1
- package/dist/javascript/collection/app/components/midwest/tooltip_component/tooltip_component_controller.js.map +1 -1
- package/dist/javascript/midwest.d.ts +2 -0
- package/dist/javascript/midwest.js +371 -82
- package/dist/javascript/midwest.js.map +1 -1
- package/package.json +2 -1
|
@@ -494,9 +494,16 @@ class Card extends Controller {
|
|
|
494
494
|
originalHeight: { type: Number, default: 0 },
|
|
495
495
|
backHeight: { type: Number, default: 0 }
|
|
496
496
|
};
|
|
497
|
+
resizeHandler = null;
|
|
497
498
|
connect() {
|
|
498
499
|
this.applyReady();
|
|
499
500
|
}
|
|
501
|
+
disconnect() {
|
|
502
|
+
if (this.resizeHandler) {
|
|
503
|
+
window.removeEventListener("resize", this.resizeHandler);
|
|
504
|
+
this.resizeHandler = null;
|
|
505
|
+
}
|
|
506
|
+
}
|
|
500
507
|
get flippable() {
|
|
501
508
|
return this.element.classList.contains("flippable");
|
|
502
509
|
}
|
|
@@ -505,10 +512,11 @@ class Card extends Controller {
|
|
|
505
512
|
this.originalHeightValue = this.frontTarget.offsetHeight;
|
|
506
513
|
this.backHeightValue = this.backTarget.offsetHeight;
|
|
507
514
|
this.element.classList.add("flip-ready");
|
|
508
|
-
|
|
515
|
+
this.resizeHandler = () => {
|
|
509
516
|
this.originalHeightValue = this.frontTarget.offsetHeight;
|
|
510
517
|
this.backHeightValue = this.backTarget.offsetHeight;
|
|
511
|
-
}
|
|
518
|
+
};
|
|
519
|
+
window.addEventListener("resize", this.resizeHandler);
|
|
512
520
|
}
|
|
513
521
|
}
|
|
514
522
|
originalHeightValueChanged() {
|
|
@@ -535,16 +543,18 @@ class Banner extends Controller {
|
|
|
535
543
|
}
|
|
536
544
|
}
|
|
537
545
|
|
|
538
|
-
class Callout extends Controller {
|
|
539
|
-
connect() {
|
|
540
|
-
console.log("Hello from Callout!");
|
|
541
|
-
}
|
|
542
|
-
}
|
|
543
|
-
|
|
544
546
|
class Input extends Controller {
|
|
545
547
|
static targets = ["input"];
|
|
548
|
+
updateClearButton = null;
|
|
546
549
|
connect() {
|
|
547
550
|
this.applyColor();
|
|
551
|
+
this.applySearchClear();
|
|
552
|
+
}
|
|
553
|
+
disconnect() {
|
|
554
|
+
if (this.updateClearButton) {
|
|
555
|
+
this.inputTarget.removeEventListener("input", this.updateClearButton);
|
|
556
|
+
this.updateClearButton = null;
|
|
557
|
+
}
|
|
548
558
|
}
|
|
549
559
|
applyColor() {
|
|
550
560
|
if (this.inputTarget.type !== "color") {
|
|
@@ -554,36 +564,103 @@ class Input extends Controller {
|
|
|
554
564
|
this.inputTarget.setAttribute("value", this.inputTarget.value);
|
|
555
565
|
});
|
|
556
566
|
}
|
|
567
|
+
applySearchClear() {
|
|
568
|
+
if (this.inputTarget.type !== "search")
|
|
569
|
+
return;
|
|
570
|
+
if (CSS.supports("selector(::-webkit-search-cancel-button)"))
|
|
571
|
+
return;
|
|
572
|
+
const button = document.createElement("button");
|
|
573
|
+
button.type = "button";
|
|
574
|
+
button.setAttribute("aria-label", "Clear search");
|
|
575
|
+
button.classList.add("midwest-input-search-clear");
|
|
576
|
+
button.hidden = this.inputTarget.value.length === 0;
|
|
577
|
+
const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
|
|
578
|
+
svg.setAttribute("viewBox", "0 0 24 24");
|
|
579
|
+
svg.setAttribute("fill", "none");
|
|
580
|
+
svg.setAttribute("stroke", "currentColor");
|
|
581
|
+
svg.setAttribute("stroke-width", "2");
|
|
582
|
+
svg.setAttribute("aria-hidden", "true");
|
|
583
|
+
svg.setAttribute("width", "16");
|
|
584
|
+
svg.setAttribute("height", "16");
|
|
585
|
+
const path = document.createElementNS("http://www.w3.org/2000/svg", "path");
|
|
586
|
+
path.setAttribute("stroke-linecap", "round");
|
|
587
|
+
path.setAttribute("stroke-linejoin", "round");
|
|
588
|
+
path.setAttribute("d", "M6 18L18 6M6 6l12 12");
|
|
589
|
+
svg.appendChild(path);
|
|
590
|
+
button.appendChild(svg);
|
|
591
|
+
const wrapper = document.createElement("div");
|
|
592
|
+
wrapper.classList.add("midwest-input-search-wrapper");
|
|
593
|
+
this.inputTarget.parentElement?.insertBefore(wrapper, this.inputTarget);
|
|
594
|
+
wrapper.appendChild(this.inputTarget);
|
|
595
|
+
wrapper.appendChild(button);
|
|
596
|
+
this.updateClearButton = () => {
|
|
597
|
+
button.hidden = this.inputTarget.value.length === 0;
|
|
598
|
+
};
|
|
599
|
+
this.inputTarget.addEventListener("input", this.updateClearButton);
|
|
600
|
+
button.addEventListener("click", () => {
|
|
601
|
+
this.inputTarget.value = "";
|
|
602
|
+
button.hidden = true;
|
|
603
|
+
this.inputTarget.dispatchEvent(new Event("input", { bubbles: true }));
|
|
604
|
+
this.inputTarget.focus();
|
|
605
|
+
});
|
|
606
|
+
}
|
|
557
607
|
}
|
|
558
608
|
|
|
559
609
|
class File extends Controller {
|
|
560
610
|
static targets = ["input", "output"];
|
|
611
|
+
// Arrow function so `this` is stable for add/removeEventListener pairing.
|
|
612
|
+
handleChange = () => {
|
|
613
|
+
this.renderPreviews();
|
|
614
|
+
};
|
|
561
615
|
connect() {
|
|
562
|
-
this.inputTarget.addEventListener("change",
|
|
563
|
-
|
|
564
|
-
|
|
616
|
+
this.inputTarget.addEventListener("change", this.handleChange);
|
|
617
|
+
}
|
|
618
|
+
disconnect() {
|
|
619
|
+
this.inputTarget.removeEventListener("change", this.handleChange);
|
|
620
|
+
}
|
|
621
|
+
renderPreviews() {
|
|
622
|
+
this.outputTarget.innerHTML = "";
|
|
623
|
+
this.files.forEach((file) => {
|
|
624
|
+
this.outputTarget.appendChild(this.buildPreview(file));
|
|
565
625
|
});
|
|
566
626
|
}
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
627
|
+
buildPreview(file) {
|
|
628
|
+
const safeType = file.type ? file.type.replace("/", "-") : "unknown";
|
|
629
|
+
const div = document.createElement("div");
|
|
630
|
+
div.className = `file-preview file-preview-${safeType} layer-children`;
|
|
631
|
+
const typeBadge = document.createElement("span");
|
|
632
|
+
typeBadge.className = "midwest-badge size-default theme-primary";
|
|
633
|
+
const typeValue = document.createElement("span");
|
|
634
|
+
typeValue.className = "value";
|
|
635
|
+
typeValue.textContent = file.type || "Unknown";
|
|
636
|
+
typeBadge.appendChild(typeValue);
|
|
637
|
+
div.appendChild(typeBadge);
|
|
638
|
+
const sizeBadge = document.createElement("span");
|
|
639
|
+
sizeBadge.className = "midwest-badge size-default theme-gray ml-auto";
|
|
640
|
+
const sizeValue = document.createElement("span");
|
|
641
|
+
sizeValue.className = "value";
|
|
642
|
+
sizeValue.textContent = new Intl.NumberFormat(void 0, {
|
|
643
|
+
notation: "compact",
|
|
644
|
+
style: "unit",
|
|
645
|
+
unit: "byte",
|
|
646
|
+
unitDisplay: "narrow"
|
|
647
|
+
}).format(file.size);
|
|
648
|
+
sizeBadge.appendChild(sizeValue);
|
|
649
|
+
div.appendChild(sizeBadge);
|
|
650
|
+
if (file.type.startsWith("image/")) {
|
|
651
|
+
const img = document.createElement("img");
|
|
652
|
+
img.src = window.URL.createObjectURL(file);
|
|
653
|
+
div.appendChild(img);
|
|
654
|
+
} else {
|
|
655
|
+
const placeholder = document.createElement("div");
|
|
656
|
+
placeholder.className = "placeholder-file";
|
|
657
|
+
placeholder.innerHTML = file.type.startsWith("video/") ? this.videoIcon : this.fileIcon;
|
|
658
|
+
div.appendChild(placeholder);
|
|
659
|
+
}
|
|
660
|
+
return div;
|
|
586
661
|
}
|
|
662
|
+
fileIcon = '<svg xmlns="http://www.w3.org/2000/svg" class="ionicon" viewBox="0 0 512 512" aria-hidden="true"><path d="M416 221.25V416a48 48 0 01-48 48H144a48 48 0 01-48-48V96a48 48 0 0148-48h98.75a32 32 0 0122.62 9.37l141.26 141.26a32 32 0 019.37 22.62z" fill="none" stroke="currentColor" stroke-linejoin="round" stroke-width="32"/><path d="M256 56v120a32 32 0 0032 32h120" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32"/></svg>';
|
|
663
|
+
videoIcon = '<svg xmlns="http://www.w3.org/2000/svg" class="ionicon" viewBox="0 0 512 512" aria-hidden="true"><path d="M374.79 308.78L457.5 367a16 16 0 0022.5-14.62V159.62A16 16 0 00457.5 145l-82.71 58.22A16 16 0 00368 216.3v79.4a16 16 0 006.79 13.08z" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32"/><path d="M268 384H84a52.15 52.15 0 01-52-52V180a52.15 52.15 0 0152-52h184.48A51.68 51.68 0 01320 179.52V332a52.15 52.15 0 01-52 52z" fill="none" stroke="currentColor" stroke-miterlimit="10" stroke-width="32"/></svg>';
|
|
587
664
|
get files() {
|
|
588
665
|
if (this.inputTarget.files == null) {
|
|
589
666
|
return [];
|
|
@@ -592,12 +669,6 @@ class File extends Controller {
|
|
|
592
669
|
}
|
|
593
670
|
}
|
|
594
671
|
|
|
595
|
-
class Switch extends Controller {
|
|
596
|
-
connect() {
|
|
597
|
-
console.log("Hello from Switch!");
|
|
598
|
-
}
|
|
599
|
-
}
|
|
600
|
-
|
|
601
672
|
/**
|
|
602
673
|
* Custom positioning reference element.
|
|
603
674
|
* @see https://floating-ui.com/docs/virtual-elements
|
|
@@ -2740,7 +2811,9 @@ class Dropdown extends Controller {
|
|
|
2740
2811
|
}
|
|
2741
2812
|
this.buttonEl.removeEventListener("click", this.unsupportedClickCB);
|
|
2742
2813
|
this.element.removeEventListener("toggle", this.addOpenedClass);
|
|
2814
|
+
this.element.removeEventListener("keydown", this.handleKeydown);
|
|
2743
2815
|
this.element.removeEventListener("turbo:click", this.closeIfOpen);
|
|
2816
|
+
this.element.removeEventListener("popover:close", this.closeIfOpen);
|
|
2744
2817
|
}
|
|
2745
2818
|
unsupportedClickCB = (event) => {
|
|
2746
2819
|
event.preventDefault();
|
|
@@ -2749,15 +2822,73 @@ class Dropdown extends Controller {
|
|
|
2749
2822
|
};
|
|
2750
2823
|
get buttonEl() {
|
|
2751
2824
|
return this.element.parentElement?.querySelector(
|
|
2752
|
-
`[popovertarget="${this.element.id}"]`
|
|
2825
|
+
`[popovertarget="${this.element.id}"]:not([popovertargetaction="hide"])`
|
|
2753
2826
|
);
|
|
2754
2827
|
}
|
|
2755
2828
|
get isComputed() {
|
|
2756
2829
|
return this.computedValue || !this.supportedValue;
|
|
2757
2830
|
}
|
|
2758
2831
|
addOpenedClass = (event) => {
|
|
2759
|
-
|
|
2832
|
+
const isOpen = event.newState === "open";
|
|
2833
|
+
this.buttonEl.classList.toggle("opened", isOpen);
|
|
2834
|
+
this.buttonEl.setAttribute("aria-expanded", String(isOpen));
|
|
2835
|
+
if (isOpen) {
|
|
2836
|
+
this.element.addEventListener("keydown", this.handleKeydown);
|
|
2837
|
+
requestAnimationFrame(() => this.menuItems[0]?.focus());
|
|
2838
|
+
} else {
|
|
2839
|
+
this.element.removeEventListener("keydown", this.handleKeydown);
|
|
2840
|
+
}
|
|
2841
|
+
};
|
|
2842
|
+
handleKeydown = (event) => {
|
|
2843
|
+
const items = this.menuItems;
|
|
2844
|
+
if (items.length === 0)
|
|
2845
|
+
return;
|
|
2846
|
+
if (!["ArrowDown", "ArrowUp", "Home", "End", "Escape", "Tab"].includes(event.key))
|
|
2847
|
+
return;
|
|
2848
|
+
event.stopPropagation();
|
|
2849
|
+
const currentIndex = items.findIndex((item) => item === document.activeElement);
|
|
2850
|
+
switch (event.key) {
|
|
2851
|
+
case "ArrowDown": {
|
|
2852
|
+
event.preventDefault();
|
|
2853
|
+
const next = currentIndex < items.length - 1 ? currentIndex + 1 : 0;
|
|
2854
|
+
items[next].focus();
|
|
2855
|
+
break;
|
|
2856
|
+
}
|
|
2857
|
+
case "ArrowUp": {
|
|
2858
|
+
event.preventDefault();
|
|
2859
|
+
const prev = currentIndex > 0 ? currentIndex - 1 : items.length - 1;
|
|
2860
|
+
items[prev].focus();
|
|
2861
|
+
break;
|
|
2862
|
+
}
|
|
2863
|
+
case "Home":
|
|
2864
|
+
event.preventDefault();
|
|
2865
|
+
items[0].focus();
|
|
2866
|
+
break;
|
|
2867
|
+
case "End":
|
|
2868
|
+
event.preventDefault();
|
|
2869
|
+
items[items.length - 1].focus();
|
|
2870
|
+
break;
|
|
2871
|
+
case "Escape":
|
|
2872
|
+
event.preventDefault();
|
|
2873
|
+
this.element.hidePopover();
|
|
2874
|
+
requestAnimationFrame(() => this.buttonEl?.focus());
|
|
2875
|
+
break;
|
|
2876
|
+
case "Tab":
|
|
2877
|
+
this.element.hidePopover();
|
|
2878
|
+
break;
|
|
2879
|
+
default:
|
|
2880
|
+
return;
|
|
2881
|
+
}
|
|
2760
2882
|
};
|
|
2883
|
+
get menuItems() {
|
|
2884
|
+
return Array.from(
|
|
2885
|
+
this.element.querySelectorAll(
|
|
2886
|
+
'[role="menuitem"]:not([disabled]):not([aria-disabled="true"])'
|
|
2887
|
+
)
|
|
2888
|
+
).filter(
|
|
2889
|
+
(item) => item.closest('[data-controller~="midwest-dropdown"]') === this.element && getComputedStyle(item).display !== "none"
|
|
2890
|
+
);
|
|
2891
|
+
}
|
|
2761
2892
|
computedCB(entries) {
|
|
2762
2893
|
entries.forEach(async () => {
|
|
2763
2894
|
this.computation();
|
|
@@ -2801,12 +2932,6 @@ class Dropdown extends Controller {
|
|
|
2801
2932
|
}
|
|
2802
2933
|
}
|
|
2803
2934
|
|
|
2804
|
-
class Avatar extends Controller {
|
|
2805
|
-
connect() {
|
|
2806
|
-
console.log("Hello from Avatar!");
|
|
2807
|
-
}
|
|
2808
|
-
}
|
|
2809
|
-
|
|
2810
2935
|
class Prose extends Controller {
|
|
2811
2936
|
static values = {
|
|
2812
2937
|
initial: { type: Number, default: 1 },
|
|
@@ -2816,7 +2941,8 @@ class Prose extends Controller {
|
|
|
2816
2941
|
if (!this.element.classList.contains("clamp")) {
|
|
2817
2942
|
return;
|
|
2818
2943
|
}
|
|
2819
|
-
|
|
2944
|
+
const clamp = parseInt(getComputedStyle(this.element).getPropertyValue("--clamp"), 10);
|
|
2945
|
+
this.initialValue = isNaN(clamp) ? 1 : clamp;
|
|
2820
2946
|
this.element.addEventListener("click", () => {
|
|
2821
2947
|
this.toggle();
|
|
2822
2948
|
});
|
|
@@ -2836,9 +2962,19 @@ class Dialog extends Controller {
|
|
|
2836
2962
|
static values = {
|
|
2837
2963
|
resetFrame: { type: Boolean, default: false }
|
|
2838
2964
|
};
|
|
2965
|
+
_previousFocus = null;
|
|
2839
2966
|
connect() {
|
|
2840
|
-
document.addEventListener("turbo:frame-load", this.
|
|
2841
|
-
this.
|
|
2967
|
+
document.addEventListener("turbo:frame-load", this.bindToggleEls);
|
|
2968
|
+
document.addEventListener("turbo:before-visit", this.dismiss);
|
|
2969
|
+
document.addEventListener("click", this.handleClickOutside);
|
|
2970
|
+
this.dialog.addEventListener("close", this.onClose);
|
|
2971
|
+
this.bindToggleEls();
|
|
2972
|
+
}
|
|
2973
|
+
disconnect() {
|
|
2974
|
+
document.removeEventListener("turbo:frame-load", this.bindToggleEls);
|
|
2975
|
+
document.removeEventListener("turbo:before-visit", this.dismiss);
|
|
2976
|
+
document.removeEventListener("click", this.handleClickOutside);
|
|
2977
|
+
this.dialog.removeEventListener("close", this.onClose);
|
|
2842
2978
|
}
|
|
2843
2979
|
get dialog() {
|
|
2844
2980
|
return this.element;
|
|
@@ -2852,20 +2988,18 @@ class Dialog extends Controller {
|
|
|
2852
2988
|
get frameEl() {
|
|
2853
2989
|
return this.dialog.querySelector("turbo-frame");
|
|
2854
2990
|
}
|
|
2855
|
-
|
|
2856
|
-
|
|
2857
|
-
|
|
2858
|
-
|
|
2859
|
-
|
|
2860
|
-
|
|
2861
|
-
|
|
2862
|
-
|
|
2863
|
-
document.addEventListener("turbo:before-visit", this.dismiss.bind(this));
|
|
2991
|
+
// Arrow function so `this` is stable for add/removeEventListener pairing.
|
|
2992
|
+
handleClickOutside = (event) => {
|
|
2993
|
+
if (event.target !== this.dialog)
|
|
2994
|
+
return;
|
|
2995
|
+
this.dismiss();
|
|
2996
|
+
};
|
|
2997
|
+
// Arrow function so `this` is stable for add/removeEventListener pairing.
|
|
2998
|
+
bindToggleEls = () => {
|
|
2864
2999
|
this.toggleEls.forEach((el) => {
|
|
2865
3000
|
el.addEventListener("click", this.toggle.bind(this));
|
|
2866
3001
|
});
|
|
2867
|
-
|
|
2868
|
-
}
|
|
3002
|
+
};
|
|
2869
3003
|
remoteContentTargetConnected() {
|
|
2870
3004
|
this.show();
|
|
2871
3005
|
}
|
|
@@ -2876,32 +3010,65 @@ class Dialog extends Controller {
|
|
|
2876
3010
|
if (this.dialog.open) {
|
|
2877
3011
|
this.dialog.close();
|
|
2878
3012
|
} else {
|
|
3013
|
+
this._previousFocus = document.activeElement;
|
|
2879
3014
|
this.dialog.showModal();
|
|
3015
|
+
this.dialog.addEventListener("keydown", this.handleFocusTrap);
|
|
2880
3016
|
}
|
|
2881
3017
|
}
|
|
2882
3018
|
show(e = void 0) {
|
|
2883
3019
|
if (e instanceof Event)
|
|
2884
3020
|
e.preventDefault();
|
|
2885
3021
|
this.htmlEl.classList.toggle("overflow-hidden", true);
|
|
3022
|
+
this._previousFocus = document.activeElement;
|
|
2886
3023
|
this.dialog.showModal();
|
|
3024
|
+
this.dialog.addEventListener("keydown", this.handleFocusTrap);
|
|
2887
3025
|
}
|
|
2888
3026
|
hide({ detail }) {
|
|
2889
3027
|
if (!detail.success)
|
|
2890
3028
|
return;
|
|
2891
3029
|
this.dismiss();
|
|
2892
3030
|
}
|
|
2893
|
-
|
|
3031
|
+
// Arrow function so `this` is stable for add/removeEventListener pairing.
|
|
3032
|
+
dismiss = () => {
|
|
2894
3033
|
this.dialog.close();
|
|
2895
|
-
}
|
|
3034
|
+
};
|
|
2896
3035
|
reloadPage() {
|
|
2897
3036
|
window.location.reload();
|
|
2898
3037
|
}
|
|
2899
|
-
|
|
3038
|
+
handleFocusTrap = (event) => {
|
|
3039
|
+
if (event.key !== "Tab")
|
|
3040
|
+
return;
|
|
3041
|
+
const focusable = Array.from(
|
|
3042
|
+
this.dialog.querySelectorAll(
|
|
3043
|
+
'a[href], button:not([disabled]), textarea, input, select, [tabindex]:not([tabindex="-1"])'
|
|
3044
|
+
)
|
|
3045
|
+
).filter((el) => !el.closest("[inert]"));
|
|
3046
|
+
if (focusable.length === 0)
|
|
3047
|
+
return;
|
|
3048
|
+
const first = focusable[0];
|
|
3049
|
+
const last = focusable[focusable.length - 1];
|
|
3050
|
+
if (event.shiftKey) {
|
|
3051
|
+
if (document.activeElement === first) {
|
|
3052
|
+
event.preventDefault();
|
|
3053
|
+
last.focus();
|
|
3054
|
+
}
|
|
3055
|
+
} else {
|
|
3056
|
+
if (document.activeElement === last) {
|
|
3057
|
+
event.preventDefault();
|
|
3058
|
+
first.focus();
|
|
3059
|
+
}
|
|
3060
|
+
}
|
|
3061
|
+
};
|
|
3062
|
+
// Arrow function so `this` is stable for add/removeEventListener pairing.
|
|
3063
|
+
onClose = async () => {
|
|
3064
|
+
this.dialog.removeEventListener("keydown", this.handleFocusTrap);
|
|
3065
|
+
this._previousFocus?.focus();
|
|
3066
|
+
this._previousFocus = null;
|
|
2900
3067
|
this.htmlEl.classList.toggle("overflow-hidden", false);
|
|
2901
3068
|
await this.delay(500);
|
|
2902
3069
|
this.resetForm();
|
|
2903
3070
|
this.resetFrame();
|
|
2904
|
-
}
|
|
3071
|
+
};
|
|
2905
3072
|
resetFrame() {
|
|
2906
3073
|
if (!this.resetFrameValue || this.frameEl == null)
|
|
2907
3074
|
return;
|
|
@@ -2920,18 +3087,6 @@ class Dialog extends Controller {
|
|
|
2920
3087
|
}
|
|
2921
3088
|
}
|
|
2922
3089
|
|
|
2923
|
-
class Video extends Controller {
|
|
2924
|
-
connect() {
|
|
2925
|
-
console.log("Hello from Video!");
|
|
2926
|
-
}
|
|
2927
|
-
}
|
|
2928
|
-
|
|
2929
|
-
class DropdownItem extends Controller {
|
|
2930
|
-
connect() {
|
|
2931
|
-
console.log("Hello from DropdownItem!");
|
|
2932
|
-
}
|
|
2933
|
-
}
|
|
2934
|
-
|
|
2935
3090
|
class Tabs extends Controller {
|
|
2936
3091
|
static targets = ["tab", "panel"];
|
|
2937
3092
|
connect() {
|
|
@@ -2939,6 +3094,10 @@ class Tabs extends Controller {
|
|
|
2939
3094
|
(tab) => tab.getAttribute("aria-selected") === "true"
|
|
2940
3095
|
);
|
|
2941
3096
|
this.activateTab(activeIndex >= 0 ? activeIndex : 0);
|
|
3097
|
+
this.element.addEventListener("keydown", this.handleKeydown);
|
|
3098
|
+
}
|
|
3099
|
+
disconnect() {
|
|
3100
|
+
this.element.removeEventListener("keydown", this.handleKeydown);
|
|
2942
3101
|
}
|
|
2943
3102
|
select(event) {
|
|
2944
3103
|
const tab = event.currentTarget;
|
|
@@ -2946,6 +3105,34 @@ class Tabs extends Controller {
|
|
|
2946
3105
|
if (index >= 0)
|
|
2947
3106
|
this.activateTab(index);
|
|
2948
3107
|
}
|
|
3108
|
+
handleKeydown = (event) => {
|
|
3109
|
+
const tabs = this.tabTargets;
|
|
3110
|
+
if (tabs.length === 0)
|
|
3111
|
+
return;
|
|
3112
|
+
const currentIndex = tabs.findIndex((t) => t === document.activeElement);
|
|
3113
|
+
if (currentIndex < 0)
|
|
3114
|
+
return;
|
|
3115
|
+
let nextIndex;
|
|
3116
|
+
switch (event.key) {
|
|
3117
|
+
case "ArrowLeft":
|
|
3118
|
+
nextIndex = (currentIndex - 1 + tabs.length) % tabs.length;
|
|
3119
|
+
break;
|
|
3120
|
+
case "ArrowRight":
|
|
3121
|
+
nextIndex = (currentIndex + 1) % tabs.length;
|
|
3122
|
+
break;
|
|
3123
|
+
case "Home":
|
|
3124
|
+
nextIndex = 0;
|
|
3125
|
+
break;
|
|
3126
|
+
case "End":
|
|
3127
|
+
nextIndex = tabs.length - 1;
|
|
3128
|
+
break;
|
|
3129
|
+
default:
|
|
3130
|
+
return;
|
|
3131
|
+
}
|
|
3132
|
+
event.preventDefault();
|
|
3133
|
+
this.activateTab(nextIndex);
|
|
3134
|
+
tabs[nextIndex].focus();
|
|
3135
|
+
};
|
|
2949
3136
|
activateTab(index) {
|
|
2950
3137
|
this.tabTargets.forEach((tab, i) => {
|
|
2951
3138
|
const active = i === index;
|
|
@@ -2965,12 +3152,26 @@ class Tabs extends Controller {
|
|
|
2965
3152
|
|
|
2966
3153
|
class Tooltip extends Controller {
|
|
2967
3154
|
static targets = ["popover"];
|
|
3155
|
+
connect() {
|
|
3156
|
+
document.addEventListener("keydown", this.handleKeydown);
|
|
3157
|
+
}
|
|
3158
|
+
disconnect() {
|
|
3159
|
+
document.removeEventListener("keydown", this.handleKeydown);
|
|
3160
|
+
}
|
|
2968
3161
|
show() {
|
|
2969
3162
|
this.popoverTarget.showPopover();
|
|
2970
3163
|
}
|
|
2971
3164
|
hide() {
|
|
2972
|
-
|
|
3165
|
+
try {
|
|
3166
|
+
this.popoverTarget.hidePopover();
|
|
3167
|
+
} catch {
|
|
3168
|
+
}
|
|
2973
3169
|
}
|
|
3170
|
+
handleKeydown = (event) => {
|
|
3171
|
+
if (event.key !== "Escape")
|
|
3172
|
+
return;
|
|
3173
|
+
this.hide();
|
|
3174
|
+
};
|
|
2974
3175
|
}
|
|
2975
3176
|
|
|
2976
3177
|
const NON_SUBMITTING_INPUT_TYPES = /* @__PURE__ */ new Set([
|
|
@@ -2993,16 +3194,24 @@ class Carousel extends Controller {
|
|
|
2993
3194
|
observer = null;
|
|
2994
3195
|
_currentIndex = 0;
|
|
2995
3196
|
_maxUnlocked = 0;
|
|
3197
|
+
_lastTouchX = 0;
|
|
2996
3198
|
connect() {
|
|
2997
3199
|
this.setupObserver();
|
|
2998
3200
|
this.updateNavigation();
|
|
2999
3201
|
if (this.wizardValue) {
|
|
3000
3202
|
this.element.addEventListener("keydown", this.handleKeydown);
|
|
3203
|
+
this.trackTarget.addEventListener("wheel", this.handleWheel, { passive: false });
|
|
3204
|
+
this.trackTarget.addEventListener("touchstart", this.handleTouchStart, { passive: true });
|
|
3205
|
+
this.trackTarget.addEventListener("touchmove", this.handleTouchMove, { passive: false });
|
|
3206
|
+
this.updateSlideAccessibility();
|
|
3001
3207
|
}
|
|
3002
3208
|
}
|
|
3003
3209
|
disconnect() {
|
|
3004
3210
|
this.observer?.disconnect();
|
|
3005
3211
|
this.element.removeEventListener("keydown", this.handleKeydown);
|
|
3212
|
+
this.trackTarget.removeEventListener("wheel", this.handleWheel);
|
|
3213
|
+
this.trackTarget.removeEventListener("touchstart", this.handleTouchStart);
|
|
3214
|
+
this.trackTarget.removeEventListener("touchmove", this.handleTouchMove);
|
|
3006
3215
|
}
|
|
3007
3216
|
next() {
|
|
3008
3217
|
this.trackTarget.scrollBy({ left: this.trackTarget.clientWidth, behavior: "smooth" });
|
|
@@ -3027,10 +3236,13 @@ class Carousel extends Controller {
|
|
|
3027
3236
|
const nextIndex = this._currentIndex + 1;
|
|
3028
3237
|
if (nextIndex > this._maxUnlocked) {
|
|
3029
3238
|
this._maxUnlocked = nextIndex;
|
|
3239
|
+
this.updateSlideAccessibility();
|
|
3030
3240
|
}
|
|
3031
3241
|
this.next();
|
|
3032
3242
|
}
|
|
3033
3243
|
scrolled() {
|
|
3244
|
+
if (this.wizardValue)
|
|
3245
|
+
this.clampToFrontier();
|
|
3034
3246
|
this.updateCurrentIndex();
|
|
3035
3247
|
this.updateNavigation();
|
|
3036
3248
|
}
|
|
@@ -3048,9 +3260,49 @@ class Carousel extends Controller {
|
|
|
3048
3260
|
event.preventDefault();
|
|
3049
3261
|
this.unlockNext();
|
|
3050
3262
|
};
|
|
3263
|
+
// Blocks wheel-based horizontal scrolling past the wizard frontier.
|
|
3264
|
+
handleWheel = (event) => {
|
|
3265
|
+
const maxLeft = this._maxUnlocked * this.trackTarget.clientWidth;
|
|
3266
|
+
if (event.deltaX > 0 && this.trackTarget.scrollLeft >= maxLeft - 2) {
|
|
3267
|
+
event.preventDefault();
|
|
3268
|
+
}
|
|
3269
|
+
};
|
|
3270
|
+
handleTouchStart = (event) => {
|
|
3271
|
+
this._lastTouchX = event.touches[0].clientX;
|
|
3272
|
+
};
|
|
3273
|
+
// Blocks touch-swipe scrolling past the wizard frontier. Tracks incremental
|
|
3274
|
+
// delta per move event so direction is accurate mid-gesture.
|
|
3275
|
+
handleTouchMove = (event) => {
|
|
3276
|
+
const currentX = event.touches[0].clientX;
|
|
3277
|
+
const delta = currentX - this._lastTouchX;
|
|
3278
|
+
this._lastTouchX = currentX;
|
|
3279
|
+
const maxLeft = this._maxUnlocked * this.trackTarget.clientWidth;
|
|
3280
|
+
if (delta < 0 && this.trackTarget.scrollLeft >= maxLeft - 2) {
|
|
3281
|
+
event.preventDefault();
|
|
3282
|
+
}
|
|
3283
|
+
};
|
|
3051
3284
|
get slides() {
|
|
3052
3285
|
return Array.from(this.trackTarget.children);
|
|
3053
3286
|
}
|
|
3287
|
+
// Snaps scroll position back to the frontier if the user somehow scrolled
|
|
3288
|
+
// past it (e.g. momentum after a partially-blocked gesture).
|
|
3289
|
+
clampToFrontier() {
|
|
3290
|
+
const maxLeft = this._maxUnlocked * this.trackTarget.clientWidth;
|
|
3291
|
+
if (this.trackTarget.scrollLeft > maxLeft + 2) {
|
|
3292
|
+
this.trackTarget.scrollLeft = maxLeft;
|
|
3293
|
+
}
|
|
3294
|
+
}
|
|
3295
|
+
// Applies/removes `inert` on each slide based on the wizard frontier,
|
|
3296
|
+
// removing locked slides from the tab order and accessibility tree.
|
|
3297
|
+
updateSlideAccessibility() {
|
|
3298
|
+
this.slides.forEach((slide, i) => {
|
|
3299
|
+
if (i > this._maxUnlocked) {
|
|
3300
|
+
slide.setAttribute("inert", "");
|
|
3301
|
+
} else {
|
|
3302
|
+
slide.removeAttribute("inert");
|
|
3303
|
+
}
|
|
3304
|
+
});
|
|
3305
|
+
}
|
|
3054
3306
|
updateCurrentIndex() {
|
|
3055
3307
|
const track = this.trackTarget;
|
|
3056
3308
|
if (track.clientWidth > 0) {
|
|
@@ -3067,7 +3319,15 @@ class Carousel extends Controller {
|
|
|
3067
3319
|
entries.forEach((entry) => {
|
|
3068
3320
|
const index = this.slides.indexOf(entry.target);
|
|
3069
3321
|
if (index >= 0) {
|
|
3070
|
-
this.dotTargets[index]
|
|
3322
|
+
const dot = this.dotTargets[index];
|
|
3323
|
+
if (!dot)
|
|
3324
|
+
return;
|
|
3325
|
+
dot.classList.toggle("active", entry.isIntersecting);
|
|
3326
|
+
if (entry.isIntersecting) {
|
|
3327
|
+
dot.setAttribute("aria-current", "true");
|
|
3328
|
+
} else {
|
|
3329
|
+
dot.removeAttribute("aria-current");
|
|
3330
|
+
}
|
|
3071
3331
|
}
|
|
3072
3332
|
});
|
|
3073
3333
|
},
|
|
@@ -3082,8 +3342,9 @@ class Carousel extends Controller {
|
|
|
3082
3342
|
if (this.hasPrevTarget)
|
|
3083
3343
|
this.prevTarget.classList.toggle("hide", atStart);
|
|
3084
3344
|
if (this.hasNextTarget) {
|
|
3345
|
+
const atLastSlide = this._currentIndex >= this.slides.length - 1;
|
|
3085
3346
|
const frontierReached = this.wizardValue && this._currentIndex >= this._maxUnlocked;
|
|
3086
|
-
this.nextTarget.classList.toggle("hide", atEnd || frontierReached);
|
|
3347
|
+
this.nextTarget.classList.toggle("hide", atEnd || atLastSlide || frontierReached);
|
|
3087
3348
|
}
|
|
3088
3349
|
if (this.wizardValue && this.hasDotTarget) {
|
|
3089
3350
|
this.dotTargets.forEach((dot, i) => {
|
|
@@ -3224,23 +3485,51 @@ class FormLiveSummary extends Controller {
|
|
|
3224
3485
|
}
|
|
3225
3486
|
}
|
|
3226
3487
|
|
|
3488
|
+
class Form extends Controller {
|
|
3489
|
+
connect() {
|
|
3490
|
+
this.element.addEventListener("submit", this.handleSubmit);
|
|
3491
|
+
this.element.addEventListener("turbo:submit-end", this.handleTurboEnd);
|
|
3492
|
+
}
|
|
3493
|
+
disconnect() {
|
|
3494
|
+
this.element.removeEventListener("submit", this.handleSubmit);
|
|
3495
|
+
this.element.removeEventListener("turbo:submit-end", this.handleTurboEnd);
|
|
3496
|
+
}
|
|
3497
|
+
handleSubmit = () => {
|
|
3498
|
+
this.submitButtons.forEach((btn) => {
|
|
3499
|
+
btn.classList.add("loading");
|
|
3500
|
+
btn.disabled = true;
|
|
3501
|
+
});
|
|
3502
|
+
};
|
|
3503
|
+
// Restore buttons if the submission fails (e.g. a Turbo Frame validation
|
|
3504
|
+
// error that re-renders the form without a full page transition).
|
|
3505
|
+
handleTurboEnd = (event) => {
|
|
3506
|
+
if (!event.detail?.success) {
|
|
3507
|
+
this.submitButtons.forEach((btn) => {
|
|
3508
|
+
btn.classList.remove("loading");
|
|
3509
|
+
btn.disabled = false;
|
|
3510
|
+
});
|
|
3511
|
+
}
|
|
3512
|
+
};
|
|
3513
|
+
get submitButtons() {
|
|
3514
|
+
return Array.from(
|
|
3515
|
+
this.element.querySelectorAll('button[type="submit"], button:not([type])')
|
|
3516
|
+
);
|
|
3517
|
+
}
|
|
3518
|
+
}
|
|
3519
|
+
|
|
3227
3520
|
function registerMidwestControllers(application) {
|
|
3228
3521
|
application.register("midwest-card", Card);
|
|
3229
3522
|
application.register("midwest-banner", Banner);
|
|
3230
|
-
application.register("midwest-callout", Callout);
|
|
3231
3523
|
application.register("midwest-input", Input);
|
|
3232
3524
|
application.register("midwest-file", File);
|
|
3233
|
-
application.register("midwest-switch", Switch);
|
|
3234
3525
|
application.register("midwest-dropdown", Dropdown);
|
|
3235
|
-
application.register("midwest-avatar", Avatar);
|
|
3236
3526
|
application.register("midwest-prose", Prose);
|
|
3237
3527
|
application.register("midwest-dialog", Dialog);
|
|
3238
|
-
application.register("midwest-video", Video);
|
|
3239
|
-
application.register("midwest-dropdown-item", DropdownItem);
|
|
3240
3528
|
application.register("midwest-tabs", Tabs);
|
|
3241
3529
|
application.register("midwest-tooltip", Tooltip);
|
|
3242
3530
|
application.register("midwest-carousel", Carousel);
|
|
3243
3531
|
application.register("midwest-form-live-summary", FormLiveSummary);
|
|
3532
|
+
application.register("midwest-form", Form);
|
|
3244
3533
|
}
|
|
3245
3534
|
|
|
3246
3535
|
export { Banner, Card, registerMidwestControllers };
|