@unabridged/midwest 0.17.0 → 0.19.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 +4 -2
- package/app/assets/javascript/midwest/index.ts +10 -0
- package/app/assets/javascript/midwest.js +733 -603
- package/app/assets/javascript/midwest.js.map +1 -1
- package/app/assets/stylesheets/midwest/form-group.css +4 -0
- package/app/assets/stylesheets/midwest.css +1 -1
- package/app/assets/stylesheets/midwest.tailwind.css +7 -1
- package/dist/css/midwest.css +1 -1
- package/dist/javascript/collection/app/assets/javascript/midwest/index.js +11 -5
- package/dist/javascript/collection/app/assets/javascript/midwest/index.js.map +1 -1
- package/dist/javascript/collection/app/components/midwest/carousel_component/carousel_component_controller.js +106 -6
- package/dist/javascript/collection/app/components/midwest/carousel_component/carousel_component_controller.js.map +1 -1
- package/dist/javascript/collection/app/components/midwest/color_picker_component/color_picker_component_controller.js +135 -0
- package/dist/javascript/collection/app/components/midwest/color_picker_component/color_picker_component_controller.js.map +1 -0
- package/dist/javascript/collection/app/components/midwest/countdown_timer_component/countdown_timer_component_controller.js +27 -6
- package/dist/javascript/collection/app/components/midwest/countdown_timer_component/countdown_timer_component_controller.js.map +1 -1
- package/dist/javascript/collection/app/components/midwest/dropdown_component/dropdown_component_controller.js +1 -1
- package/dist/javascript/collection/app/components/midwest/form/autocomplete_component/autocomplete_component_controller.js +109 -21
- 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/color_picker_component/color_picker_component_controller.js +129 -0
- package/dist/javascript/collection/app/components/midwest/form/color_picker_component/color_picker_component_controller.js.map +1 -0
- package/dist/javascript/collection/app/components/midwest/form/input_component/input_component_controller.js +165 -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 +38 -5
- 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/horizontal_scroll_component/horizontal_scroll_component_controller.js +24 -0
- package/dist/javascript/collection/app/components/midwest/horizontal_scroll_component/horizontal_scroll_component_controller.js.map +1 -0
- package/dist/javascript/collection/app/components/midwest/notification_component/notification_component_controller.js +60 -0
- package/dist/javascript/collection/app/components/midwest/notification_component/notification_component_controller.js.map +1 -0
- package/dist/javascript/collection/app/components/midwest/table_component/load_more_controller.js +60 -0
- package/dist/javascript/collection/app/components/midwest/table_component/load_more_controller.js.map +1 -0
- package/dist/javascript/collection/app/components/midwest/table_component/table_component_controller.js +30 -0
- package/dist/javascript/collection/app/components/midwest/table_component/table_component_controller.js.map +1 -0
- package/dist/javascript/collection/node_modules/@floating-ui/core/dist/floating-ui.core.js +561 -0
- package/dist/javascript/collection/node_modules/@floating-ui/core/dist/floating-ui.core.js.map +1 -0
- package/dist/javascript/collection/node_modules/@floating-ui/dom/dist/floating-ui.dom.js +736 -0
- package/dist/javascript/collection/node_modules/@floating-ui/dom/dist/floating-ui.dom.js.map +1 -0
- package/dist/javascript/collection/node_modules/@floating-ui/utils/dist/floating-ui.utils.dom.js +166 -0
- package/dist/javascript/collection/node_modules/@floating-ui/utils/dist/floating-ui.utils.dom.js.map +1 -0
- package/dist/javascript/collection/node_modules/@floating-ui/utils/dist/floating-ui.utils.js +105 -0
- package/dist/javascript/collection/node_modules/@floating-ui/utils/dist/floating-ui.utils.js.map +1 -0
- package/dist/javascript/midwest.d.ts +5 -0
- package/dist/javascript/midwest.js +733 -603
- package/dist/javascript/midwest.js.map +1 -1
- package/package.json +3 -4
|
@@ -548,14 +548,48 @@ class Banner extends Controller {
|
|
|
548
548
|
}
|
|
549
549
|
}
|
|
550
550
|
|
|
551
|
+
const FORMATTERS = {
|
|
552
|
+
phone: (v) => {
|
|
553
|
+
const d = v.replace(/\D/g, "").slice(0, 10);
|
|
554
|
+
if (d.length <= 3)
|
|
555
|
+
return d;
|
|
556
|
+
if (d.length <= 6)
|
|
557
|
+
return `(${d.slice(0, 3)}) ${d.slice(3)}`;
|
|
558
|
+
return `(${d.slice(0, 3)}) ${d.slice(3, 6)}-${d.slice(6)}`;
|
|
559
|
+
},
|
|
560
|
+
credit_card: (v) => {
|
|
561
|
+
const d = v.replace(/\D/g, "").slice(0, 16);
|
|
562
|
+
return d.match(/.{1,4}/g)?.join(" ") ?? d;
|
|
563
|
+
},
|
|
564
|
+
zip: (v) => {
|
|
565
|
+
const d = v.replace(/\D/g, "");
|
|
566
|
+
if (d.length <= 5)
|
|
567
|
+
return d;
|
|
568
|
+
return `${d.slice(0, 5)}-${d.slice(5, 9)}`;
|
|
569
|
+
}
|
|
570
|
+
};
|
|
551
571
|
class Input extends Controller {
|
|
552
|
-
static targets = ["input", "counter"];
|
|
572
|
+
static targets = ["input", "counter", "capsLockWarning", "matchError"];
|
|
573
|
+
static values = {
|
|
574
|
+
match: { type: String, default: "" },
|
|
575
|
+
autoformat: { type: String, default: "" }
|
|
576
|
+
};
|
|
553
577
|
updateClearButton = null;
|
|
554
578
|
updateCounter = null;
|
|
579
|
+
updateCapsLock = null;
|
|
580
|
+
hideCapsLock = null;
|
|
581
|
+
matchListener = null;
|
|
582
|
+
matchInvalidListener = null;
|
|
583
|
+
matchSourceListener = null;
|
|
584
|
+
matchSourceElement = null;
|
|
585
|
+
autoformatListener = null;
|
|
555
586
|
connect() {
|
|
556
587
|
this.applyColor();
|
|
557
588
|
this.applySearchClear();
|
|
558
589
|
this.applyCounter();
|
|
590
|
+
this.applyPassword();
|
|
591
|
+
this.applyMatch();
|
|
592
|
+
this.applyAutoformat();
|
|
559
593
|
}
|
|
560
594
|
disconnect() {
|
|
561
595
|
if (this.updateClearButton) {
|
|
@@ -566,6 +600,56 @@ class Input extends Controller {
|
|
|
566
600
|
this.inputTarget.removeEventListener("input", this.updateCounter);
|
|
567
601
|
this.updateCounter = null;
|
|
568
602
|
}
|
|
603
|
+
if (this.updateCapsLock) {
|
|
604
|
+
this.inputTarget.removeEventListener("keydown", this.updateCapsLock);
|
|
605
|
+
this.inputTarget.removeEventListener("keyup", this.updateCapsLock);
|
|
606
|
+
this.inputTarget.removeEventListener("mousedown", this.updateCapsLock);
|
|
607
|
+
this.updateCapsLock = null;
|
|
608
|
+
}
|
|
609
|
+
if (this.hideCapsLock) {
|
|
610
|
+
this.inputTarget.removeEventListener("blur", this.hideCapsLock);
|
|
611
|
+
this.hideCapsLock = null;
|
|
612
|
+
}
|
|
613
|
+
if (this.matchListener) {
|
|
614
|
+
this.inputTarget.removeEventListener("input", this.matchListener);
|
|
615
|
+
this.inputTarget.removeEventListener("blur", this.matchListener);
|
|
616
|
+
this.matchListener = null;
|
|
617
|
+
}
|
|
618
|
+
if (this.matchInvalidListener) {
|
|
619
|
+
this.inputTarget.removeEventListener("invalid", this.matchInvalidListener);
|
|
620
|
+
this.matchInvalidListener = null;
|
|
621
|
+
}
|
|
622
|
+
if (this.matchSourceListener && this.matchSourceElement) {
|
|
623
|
+
this.matchSourceElement.removeEventListener("input", this.matchSourceListener);
|
|
624
|
+
this.matchSourceListener = null;
|
|
625
|
+
this.matchSourceElement = null;
|
|
626
|
+
}
|
|
627
|
+
if (this.autoformatListener) {
|
|
628
|
+
this.inputTarget.removeEventListener("blur", this.autoformatListener);
|
|
629
|
+
this.autoformatListener = null;
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
increment() {
|
|
633
|
+
const input = this.inputTarget;
|
|
634
|
+
const step = parseFloat(input.step) || 1;
|
|
635
|
+
const max = input.max !== "" ? parseFloat(input.max) : Infinity;
|
|
636
|
+
const newValue = (parseFloat(input.value) || 0) + step;
|
|
637
|
+
if (newValue <= max) {
|
|
638
|
+
input.value = String(newValue);
|
|
639
|
+
input.dispatchEvent(new Event("input", { bubbles: true }));
|
|
640
|
+
input.dispatchEvent(new Event("change", { bubbles: true }));
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
decrement() {
|
|
644
|
+
const input = this.inputTarget;
|
|
645
|
+
const step = parseFloat(input.step) || 1;
|
|
646
|
+
const min = input.min !== "" ? parseFloat(input.min) : -Infinity;
|
|
647
|
+
const newValue = (parseFloat(input.value) || 0) - step;
|
|
648
|
+
if (newValue >= min) {
|
|
649
|
+
input.value = String(newValue);
|
|
650
|
+
input.dispatchEvent(new Event("input", { bubbles: true }));
|
|
651
|
+
input.dispatchEvent(new Event("change", { bubbles: true }));
|
|
652
|
+
}
|
|
569
653
|
}
|
|
570
654
|
applyColor() {
|
|
571
655
|
if (this.inputTarget.type !== "color") {
|
|
@@ -584,6 +668,20 @@ class Input extends Controller {
|
|
|
584
668
|
};
|
|
585
669
|
this.inputTarget.addEventListener("input", this.updateCounter);
|
|
586
670
|
}
|
|
671
|
+
applyPassword() {
|
|
672
|
+
if (!this.hasCapsLockWarningTarget)
|
|
673
|
+
return;
|
|
674
|
+
this.updateCapsLock = (e) => {
|
|
675
|
+
this.capsLockWarningTarget.hidden = !e.getModifierState("CapsLock");
|
|
676
|
+
};
|
|
677
|
+
this.hideCapsLock = () => {
|
|
678
|
+
this.capsLockWarningTarget.hidden = true;
|
|
679
|
+
};
|
|
680
|
+
this.inputTarget.addEventListener("keydown", this.updateCapsLock);
|
|
681
|
+
this.inputTarget.addEventListener("keyup", this.updateCapsLock);
|
|
682
|
+
this.inputTarget.addEventListener("mousedown", this.updateCapsLock);
|
|
683
|
+
this.inputTarget.addEventListener("blur", this.hideCapsLock);
|
|
684
|
+
}
|
|
587
685
|
applySearchClear() {
|
|
588
686
|
if (this.inputTarget.type !== "search")
|
|
589
687
|
return;
|
|
@@ -624,6 +722,72 @@ class Input extends Controller {
|
|
|
624
722
|
this.inputTarget.focus();
|
|
625
723
|
});
|
|
626
724
|
}
|
|
725
|
+
// Validates that this field's value matches the value of the element
|
|
726
|
+
// identified by matchValue (a CSS selector). Uses setCustomValidity to
|
|
727
|
+
// block form submission. Shows the error inline via the matchError target
|
|
728
|
+
// rather than the browser's native tooltip (invalid event is suppressed).
|
|
729
|
+
applyMatch() {
|
|
730
|
+
if (!this.matchValue)
|
|
731
|
+
return;
|
|
732
|
+
const showError = () => {
|
|
733
|
+
this.element.classList.add("has-error", "theme-red");
|
|
734
|
+
if (this.hasMatchErrorTarget)
|
|
735
|
+
this.matchErrorTarget.hidden = false;
|
|
736
|
+
};
|
|
737
|
+
const clearError = () => {
|
|
738
|
+
this.element.classList.remove("has-error", "theme-red");
|
|
739
|
+
if (this.hasMatchErrorTarget)
|
|
740
|
+
this.matchErrorTarget.hidden = true;
|
|
741
|
+
};
|
|
742
|
+
const validate = () => {
|
|
743
|
+
const other2 = document.querySelector(this.matchValue);
|
|
744
|
+
if (!this.inputTarget.value) {
|
|
745
|
+
this.inputTarget.setCustomValidity("");
|
|
746
|
+
clearError();
|
|
747
|
+
return;
|
|
748
|
+
}
|
|
749
|
+
if (!other2 || this.inputTarget.value === other2.value) {
|
|
750
|
+
this.inputTarget.setCustomValidity("");
|
|
751
|
+
clearError();
|
|
752
|
+
} else {
|
|
753
|
+
this.inputTarget.setCustomValidity("Values do not match");
|
|
754
|
+
showError();
|
|
755
|
+
}
|
|
756
|
+
};
|
|
757
|
+
this.matchListener = validate;
|
|
758
|
+
this.inputTarget.addEventListener("input", validate);
|
|
759
|
+
this.inputTarget.addEventListener("blur", validate);
|
|
760
|
+
this.matchInvalidListener = (e) => {
|
|
761
|
+
e.preventDefault();
|
|
762
|
+
showError();
|
|
763
|
+
};
|
|
764
|
+
this.inputTarget.addEventListener("invalid", this.matchInvalidListener);
|
|
765
|
+
const other = document.querySelector(this.matchValue);
|
|
766
|
+
if (other) {
|
|
767
|
+
this.matchSourceElement = other;
|
|
768
|
+
this.matchSourceListener = validate;
|
|
769
|
+
other.addEventListener("input", validate);
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
// Reformats the input value on blur using a named formatter from FORMATTERS.
|
|
773
|
+
// Dispatches input + change events after reformatting so downstream
|
|
774
|
+
// listeners (counters, form state) stay in sync.
|
|
775
|
+
applyAutoformat() {
|
|
776
|
+
if (!this.autoformatValue)
|
|
777
|
+
return;
|
|
778
|
+
const fmt = FORMATTERS[this.autoformatValue];
|
|
779
|
+
if (!fmt)
|
|
780
|
+
return;
|
|
781
|
+
this.autoformatListener = () => {
|
|
782
|
+
const formatted = fmt(this.inputTarget.value);
|
|
783
|
+
if (formatted !== this.inputTarget.value) {
|
|
784
|
+
this.inputTarget.value = formatted;
|
|
785
|
+
this.inputTarget.dispatchEvent(new Event("input", { bubbles: true }));
|
|
786
|
+
this.inputTarget.dispatchEvent(new Event("change", { bubbles: true }));
|
|
787
|
+
}
|
|
788
|
+
};
|
|
789
|
+
this.inputTarget.addEventListener("blur", this.autoformatListener);
|
|
790
|
+
}
|
|
627
791
|
}
|
|
628
792
|
|
|
629
793
|
class File extends Controller {
|
|
@@ -749,41 +913,9 @@ function getAlignmentSides(placement, rects, rtl) {
|
|
|
749
913
|
}
|
|
750
914
|
return [mainAlignmentSide, getOppositePlacement(mainAlignmentSide)];
|
|
751
915
|
}
|
|
752
|
-
function getExpandedPlacements(placement) {
|
|
753
|
-
const oppositePlacement = getOppositePlacement(placement);
|
|
754
|
-
return [getOppositeAlignmentPlacement(placement), oppositePlacement, getOppositeAlignmentPlacement(oppositePlacement)];
|
|
755
|
-
}
|
|
756
916
|
function getOppositeAlignmentPlacement(placement) {
|
|
757
917
|
return placement.includes('start') ? placement.replace('start', 'end') : placement.replace('end', 'start');
|
|
758
918
|
}
|
|
759
|
-
const lrPlacement = ['left', 'right'];
|
|
760
|
-
const rlPlacement = ['right', 'left'];
|
|
761
|
-
const tbPlacement = ['top', 'bottom'];
|
|
762
|
-
const btPlacement = ['bottom', 'top'];
|
|
763
|
-
function getSideList(side, isStart, rtl) {
|
|
764
|
-
switch (side) {
|
|
765
|
-
case 'top':
|
|
766
|
-
case 'bottom':
|
|
767
|
-
if (rtl) return isStart ? rlPlacement : lrPlacement;
|
|
768
|
-
return isStart ? lrPlacement : rlPlacement;
|
|
769
|
-
case 'left':
|
|
770
|
-
case 'right':
|
|
771
|
-
return isStart ? tbPlacement : btPlacement;
|
|
772
|
-
default:
|
|
773
|
-
return [];
|
|
774
|
-
}
|
|
775
|
-
}
|
|
776
|
-
function getOppositeAxisPlacements(placement, flipAlignment, direction, rtl) {
|
|
777
|
-
const alignment = getAlignment(placement);
|
|
778
|
-
let list = getSideList(getSide(placement), direction === 'start', rtl);
|
|
779
|
-
if (alignment) {
|
|
780
|
-
list = list.map(side => side + "-" + alignment);
|
|
781
|
-
if (flipAlignment) {
|
|
782
|
-
list = list.concat(list.map(getOppositeAlignmentPlacement));
|
|
783
|
-
}
|
|
784
|
-
}
|
|
785
|
-
return list;
|
|
786
|
-
}
|
|
787
919
|
function getOppositePlacement(placement) {
|
|
788
920
|
const side = getSide(placement);
|
|
789
921
|
return oppositeSideMap[side] + placement.slice(side.length);
|
|
@@ -1043,88 +1175,6 @@ const computePosition$1 = async (reference, floating, config) => {
|
|
|
1043
1175
|
};
|
|
1044
1176
|
};
|
|
1045
1177
|
|
|
1046
|
-
/**
|
|
1047
|
-
* Provides data to position an inner element of the floating element so that it
|
|
1048
|
-
* appears centered to the reference element.
|
|
1049
|
-
* @see https://floating-ui.com/docs/arrow
|
|
1050
|
-
*/
|
|
1051
|
-
const arrow = options => ({
|
|
1052
|
-
name: 'arrow',
|
|
1053
|
-
options,
|
|
1054
|
-
async fn(state) {
|
|
1055
|
-
const {
|
|
1056
|
-
x,
|
|
1057
|
-
y,
|
|
1058
|
-
placement,
|
|
1059
|
-
rects,
|
|
1060
|
-
platform,
|
|
1061
|
-
elements,
|
|
1062
|
-
middlewareData
|
|
1063
|
-
} = state;
|
|
1064
|
-
// Since `element` is required, we don't Partial<> the type.
|
|
1065
|
-
const {
|
|
1066
|
-
element,
|
|
1067
|
-
padding = 0
|
|
1068
|
-
} = evaluate(options, state) || {};
|
|
1069
|
-
if (element == null) {
|
|
1070
|
-
return {};
|
|
1071
|
-
}
|
|
1072
|
-
const paddingObject = getPaddingObject(padding);
|
|
1073
|
-
const coords = {
|
|
1074
|
-
x,
|
|
1075
|
-
y
|
|
1076
|
-
};
|
|
1077
|
-
const axis = getAlignmentAxis(placement);
|
|
1078
|
-
const length = getAxisLength(axis);
|
|
1079
|
-
const arrowDimensions = await platform.getDimensions(element);
|
|
1080
|
-
const isYAxis = axis === 'y';
|
|
1081
|
-
const minProp = isYAxis ? 'top' : 'left';
|
|
1082
|
-
const maxProp = isYAxis ? 'bottom' : 'right';
|
|
1083
|
-
const clientProp = isYAxis ? 'clientHeight' : 'clientWidth';
|
|
1084
|
-
const endDiff = rects.reference[length] + rects.reference[axis] - coords[axis] - rects.floating[length];
|
|
1085
|
-
const startDiff = coords[axis] - rects.reference[axis];
|
|
1086
|
-
const arrowOffsetParent = await (platform.getOffsetParent == null ? void 0 : platform.getOffsetParent(element));
|
|
1087
|
-
let clientSize = arrowOffsetParent ? arrowOffsetParent[clientProp] : 0;
|
|
1088
|
-
|
|
1089
|
-
// DOM platform can return `window` as the `offsetParent`.
|
|
1090
|
-
if (!clientSize || !(await (platform.isElement == null ? void 0 : platform.isElement(arrowOffsetParent)))) {
|
|
1091
|
-
clientSize = elements.floating[clientProp] || rects.floating[length];
|
|
1092
|
-
}
|
|
1093
|
-
const centerToReference = endDiff / 2 - startDiff / 2;
|
|
1094
|
-
|
|
1095
|
-
// If the padding is large enough that it causes the arrow to no longer be
|
|
1096
|
-
// centered, modify the padding so that it is centered.
|
|
1097
|
-
const largestPossiblePadding = clientSize / 2 - arrowDimensions[length] / 2 - 1;
|
|
1098
|
-
const minPadding = min(paddingObject[minProp], largestPossiblePadding);
|
|
1099
|
-
const maxPadding = min(paddingObject[maxProp], largestPossiblePadding);
|
|
1100
|
-
|
|
1101
|
-
// Make sure the arrow doesn't overflow the floating element if the center
|
|
1102
|
-
// point is outside the floating element's bounds.
|
|
1103
|
-
const min$1 = minPadding;
|
|
1104
|
-
const max = clientSize - arrowDimensions[length] - maxPadding;
|
|
1105
|
-
const center = clientSize / 2 - arrowDimensions[length] / 2 + centerToReference;
|
|
1106
|
-
const offset = clamp(min$1, center, max);
|
|
1107
|
-
|
|
1108
|
-
// If the reference is small enough that the arrow's padding causes it to
|
|
1109
|
-
// to point to nothing for an aligned placement, adjust the offset of the
|
|
1110
|
-
// floating element itself. To ensure `shift()` continues to take action,
|
|
1111
|
-
// a single reset is performed when this is true.
|
|
1112
|
-
const shouldAddOffset = !middlewareData.arrow && getAlignment(placement) != null && center !== offset && rects.reference[length] / 2 - (center < min$1 ? minPadding : maxPadding) - arrowDimensions[length] / 2 < 0;
|
|
1113
|
-
const alignmentOffset = shouldAddOffset ? center < min$1 ? center - min$1 : center - max : 0;
|
|
1114
|
-
return {
|
|
1115
|
-
[axis]: coords[axis] + alignmentOffset,
|
|
1116
|
-
data: {
|
|
1117
|
-
[axis]: offset,
|
|
1118
|
-
centerOffset: center - offset - alignmentOffset,
|
|
1119
|
-
...(shouldAddOffset && {
|
|
1120
|
-
alignmentOffset
|
|
1121
|
-
})
|
|
1122
|
-
},
|
|
1123
|
-
reset: shouldAddOffset
|
|
1124
|
-
};
|
|
1125
|
-
}
|
|
1126
|
-
});
|
|
1127
|
-
|
|
1128
1178
|
function getPlacementList(alignment, autoAlignment, allowedPlacements) {
|
|
1129
1179
|
const allowedPlacementsSortedByAlignment = alignment ? [...allowedPlacements.filter(placement => getAlignment(placement) === alignment), ...allowedPlacements.filter(placement => getAlignment(placement) !== alignment)] : allowedPlacements.filter(placement => getSide(placement) === placement);
|
|
1130
1180
|
return allowedPlacementsSortedByAlignment.filter(placement => {
|
|
@@ -1228,138 +1278,6 @@ const autoPlacement$1 = function (options) {
|
|
|
1228
1278
|
};
|
|
1229
1279
|
};
|
|
1230
1280
|
|
|
1231
|
-
/**
|
|
1232
|
-
* Optimizes the visibility of the floating element by flipping the `placement`
|
|
1233
|
-
* in order to keep it in view when the preferred placement(s) will overflow the
|
|
1234
|
-
* clipping boundary. Alternative to `autoPlacement`.
|
|
1235
|
-
* @see https://floating-ui.com/docs/flip
|
|
1236
|
-
*/
|
|
1237
|
-
const flip = function (options) {
|
|
1238
|
-
if (options === void 0) {
|
|
1239
|
-
options = {};
|
|
1240
|
-
}
|
|
1241
|
-
return {
|
|
1242
|
-
name: 'flip',
|
|
1243
|
-
options,
|
|
1244
|
-
async fn(state) {
|
|
1245
|
-
var _middlewareData$arrow, _middlewareData$flip;
|
|
1246
|
-
const {
|
|
1247
|
-
placement,
|
|
1248
|
-
middlewareData,
|
|
1249
|
-
rects,
|
|
1250
|
-
initialPlacement,
|
|
1251
|
-
platform,
|
|
1252
|
-
elements
|
|
1253
|
-
} = state;
|
|
1254
|
-
const {
|
|
1255
|
-
mainAxis: checkMainAxis = true,
|
|
1256
|
-
crossAxis: checkCrossAxis = true,
|
|
1257
|
-
fallbackPlacements: specifiedFallbackPlacements,
|
|
1258
|
-
fallbackStrategy = 'bestFit',
|
|
1259
|
-
fallbackAxisSideDirection = 'none',
|
|
1260
|
-
flipAlignment = true,
|
|
1261
|
-
...detectOverflowOptions
|
|
1262
|
-
} = evaluate(options, state);
|
|
1263
|
-
|
|
1264
|
-
// If a reset by the arrow was caused due to an alignment offset being
|
|
1265
|
-
// added, we should skip any logic now since `flip()` has already done its
|
|
1266
|
-
// work.
|
|
1267
|
-
// https://github.com/floating-ui/floating-ui/issues/2549#issuecomment-1719601643
|
|
1268
|
-
if ((_middlewareData$arrow = middlewareData.arrow) != null && _middlewareData$arrow.alignmentOffset) {
|
|
1269
|
-
return {};
|
|
1270
|
-
}
|
|
1271
|
-
const side = getSide(placement);
|
|
1272
|
-
const initialSideAxis = getSideAxis(initialPlacement);
|
|
1273
|
-
const isBasePlacement = getSide(initialPlacement) === initialPlacement;
|
|
1274
|
-
const rtl = await (platform.isRTL == null ? void 0 : platform.isRTL(elements.floating));
|
|
1275
|
-
const fallbackPlacements = specifiedFallbackPlacements || (isBasePlacement || !flipAlignment ? [getOppositePlacement(initialPlacement)] : getExpandedPlacements(initialPlacement));
|
|
1276
|
-
const hasFallbackAxisSideDirection = fallbackAxisSideDirection !== 'none';
|
|
1277
|
-
if (!specifiedFallbackPlacements && hasFallbackAxisSideDirection) {
|
|
1278
|
-
fallbackPlacements.push(...getOppositeAxisPlacements(initialPlacement, flipAlignment, fallbackAxisSideDirection, rtl));
|
|
1279
|
-
}
|
|
1280
|
-
const placements = [initialPlacement, ...fallbackPlacements];
|
|
1281
|
-
const overflow = await platform.detectOverflow(state, detectOverflowOptions);
|
|
1282
|
-
const overflows = [];
|
|
1283
|
-
let overflowsData = ((_middlewareData$flip = middlewareData.flip) == null ? void 0 : _middlewareData$flip.overflows) || [];
|
|
1284
|
-
if (checkMainAxis) {
|
|
1285
|
-
overflows.push(overflow[side]);
|
|
1286
|
-
}
|
|
1287
|
-
if (checkCrossAxis) {
|
|
1288
|
-
const sides = getAlignmentSides(placement, rects, rtl);
|
|
1289
|
-
overflows.push(overflow[sides[0]], overflow[sides[1]]);
|
|
1290
|
-
}
|
|
1291
|
-
overflowsData = [...overflowsData, {
|
|
1292
|
-
placement,
|
|
1293
|
-
overflows
|
|
1294
|
-
}];
|
|
1295
|
-
|
|
1296
|
-
// One or more sides is overflowing.
|
|
1297
|
-
if (!overflows.every(side => side <= 0)) {
|
|
1298
|
-
var _middlewareData$flip2, _overflowsData$filter;
|
|
1299
|
-
const nextIndex = (((_middlewareData$flip2 = middlewareData.flip) == null ? void 0 : _middlewareData$flip2.index) || 0) + 1;
|
|
1300
|
-
const nextPlacement = placements[nextIndex];
|
|
1301
|
-
if (nextPlacement) {
|
|
1302
|
-
const ignoreCrossAxisOverflow = checkCrossAxis === 'alignment' ? initialSideAxis !== getSideAxis(nextPlacement) : false;
|
|
1303
|
-
if (!ignoreCrossAxisOverflow ||
|
|
1304
|
-
// We leave the current main axis only if every placement on that axis
|
|
1305
|
-
// overflows the main axis.
|
|
1306
|
-
overflowsData.every(d => getSideAxis(d.placement) === initialSideAxis ? d.overflows[0] > 0 : true)) {
|
|
1307
|
-
// Try next placement and re-run the lifecycle.
|
|
1308
|
-
return {
|
|
1309
|
-
data: {
|
|
1310
|
-
index: nextIndex,
|
|
1311
|
-
overflows: overflowsData
|
|
1312
|
-
},
|
|
1313
|
-
reset: {
|
|
1314
|
-
placement: nextPlacement
|
|
1315
|
-
}
|
|
1316
|
-
};
|
|
1317
|
-
}
|
|
1318
|
-
}
|
|
1319
|
-
|
|
1320
|
-
// First, find the candidates that fit on the mainAxis side of overflow,
|
|
1321
|
-
// then find the placement that fits the best on the main crossAxis side.
|
|
1322
|
-
let resetPlacement = (_overflowsData$filter = overflowsData.filter(d => d.overflows[0] <= 0).sort((a, b) => a.overflows[1] - b.overflows[1])[0]) == null ? void 0 : _overflowsData$filter.placement;
|
|
1323
|
-
|
|
1324
|
-
// Otherwise fallback.
|
|
1325
|
-
if (!resetPlacement) {
|
|
1326
|
-
switch (fallbackStrategy) {
|
|
1327
|
-
case 'bestFit':
|
|
1328
|
-
{
|
|
1329
|
-
var _overflowsData$filter2;
|
|
1330
|
-
const placement = (_overflowsData$filter2 = overflowsData.filter(d => {
|
|
1331
|
-
if (hasFallbackAxisSideDirection) {
|
|
1332
|
-
const currentSideAxis = getSideAxis(d.placement);
|
|
1333
|
-
return currentSideAxis === initialSideAxis ||
|
|
1334
|
-
// Create a bias to the `y` side axis due to horizontal
|
|
1335
|
-
// reading directions favoring greater width.
|
|
1336
|
-
currentSideAxis === 'y';
|
|
1337
|
-
}
|
|
1338
|
-
return true;
|
|
1339
|
-
}).map(d => [d.placement, d.overflows.filter(overflow => overflow > 0).reduce((acc, overflow) => acc + overflow, 0)]).sort((a, b) => a[1] - b[1])[0]) == null ? void 0 : _overflowsData$filter2[0];
|
|
1340
|
-
if (placement) {
|
|
1341
|
-
resetPlacement = placement;
|
|
1342
|
-
}
|
|
1343
|
-
break;
|
|
1344
|
-
}
|
|
1345
|
-
case 'initialPlacement':
|
|
1346
|
-
resetPlacement = initialPlacement;
|
|
1347
|
-
break;
|
|
1348
|
-
}
|
|
1349
|
-
}
|
|
1350
|
-
if (placement !== resetPlacement) {
|
|
1351
|
-
return {
|
|
1352
|
-
reset: {
|
|
1353
|
-
placement: resetPlacement
|
|
1354
|
-
}
|
|
1355
|
-
};
|
|
1356
|
-
}
|
|
1357
|
-
}
|
|
1358
|
-
return {};
|
|
1359
|
-
}
|
|
1360
|
-
};
|
|
1361
|
-
};
|
|
1362
|
-
|
|
1363
1281
|
function getSideOffsets(overflow, rect) {
|
|
1364
1282
|
return {
|
|
1365
1283
|
top: overflow.top - rect.height,
|
|
@@ -1430,137 +1348,6 @@ const hide$1 = function (options) {
|
|
|
1430
1348
|
};
|
|
1431
1349
|
};
|
|
1432
1350
|
|
|
1433
|
-
function getBoundingRect(rects) {
|
|
1434
|
-
const minX = min(...rects.map(rect => rect.left));
|
|
1435
|
-
const minY = min(...rects.map(rect => rect.top));
|
|
1436
|
-
const maxX = max(...rects.map(rect => rect.right));
|
|
1437
|
-
const maxY = max(...rects.map(rect => rect.bottom));
|
|
1438
|
-
return {
|
|
1439
|
-
x: minX,
|
|
1440
|
-
y: minY,
|
|
1441
|
-
width: maxX - minX,
|
|
1442
|
-
height: maxY - minY
|
|
1443
|
-
};
|
|
1444
|
-
}
|
|
1445
|
-
function getRectsByLine(rects) {
|
|
1446
|
-
const sortedRects = rects.slice().sort((a, b) => a.y - b.y);
|
|
1447
|
-
const groups = [];
|
|
1448
|
-
let prevRect = null;
|
|
1449
|
-
for (let i = 0; i < sortedRects.length; i++) {
|
|
1450
|
-
const rect = sortedRects[i];
|
|
1451
|
-
if (!prevRect || rect.y - prevRect.y > prevRect.height / 2) {
|
|
1452
|
-
groups.push([rect]);
|
|
1453
|
-
} else {
|
|
1454
|
-
groups[groups.length - 1].push(rect);
|
|
1455
|
-
}
|
|
1456
|
-
prevRect = rect;
|
|
1457
|
-
}
|
|
1458
|
-
return groups.map(rect => rectToClientRect(getBoundingRect(rect)));
|
|
1459
|
-
}
|
|
1460
|
-
/**
|
|
1461
|
-
* Provides improved positioning for inline reference elements that can span
|
|
1462
|
-
* over multiple lines, such as hyperlinks or range selections.
|
|
1463
|
-
* @see https://floating-ui.com/docs/inline
|
|
1464
|
-
*/
|
|
1465
|
-
const inline = function (options) {
|
|
1466
|
-
if (options === void 0) {
|
|
1467
|
-
options = {};
|
|
1468
|
-
}
|
|
1469
|
-
return {
|
|
1470
|
-
name: 'inline',
|
|
1471
|
-
options,
|
|
1472
|
-
async fn(state) {
|
|
1473
|
-
const {
|
|
1474
|
-
placement,
|
|
1475
|
-
elements,
|
|
1476
|
-
rects,
|
|
1477
|
-
platform,
|
|
1478
|
-
strategy
|
|
1479
|
-
} = state;
|
|
1480
|
-
// A MouseEvent's client{X,Y} coords can be up to 2 pixels off a
|
|
1481
|
-
// ClientRect's bounds, despite the event listener being triggered. A
|
|
1482
|
-
// padding of 2 seems to handle this issue.
|
|
1483
|
-
const {
|
|
1484
|
-
padding = 2,
|
|
1485
|
-
x,
|
|
1486
|
-
y
|
|
1487
|
-
} = evaluate(options, state);
|
|
1488
|
-
const nativeClientRects = Array.from((await (platform.getClientRects == null ? void 0 : platform.getClientRects(elements.reference))) || []);
|
|
1489
|
-
const clientRects = getRectsByLine(nativeClientRects);
|
|
1490
|
-
const fallback = rectToClientRect(getBoundingRect(nativeClientRects));
|
|
1491
|
-
const paddingObject = getPaddingObject(padding);
|
|
1492
|
-
function getBoundingClientRect() {
|
|
1493
|
-
// There are two rects and they are disjoined.
|
|
1494
|
-
if (clientRects.length === 2 && clientRects[0].left > clientRects[1].right && x != null && y != null) {
|
|
1495
|
-
// Find the first rect in which the point is fully inside.
|
|
1496
|
-
return clientRects.find(rect => x > rect.left - paddingObject.left && x < rect.right + paddingObject.right && y > rect.top - paddingObject.top && y < rect.bottom + paddingObject.bottom) || fallback;
|
|
1497
|
-
}
|
|
1498
|
-
|
|
1499
|
-
// There are 2 or more connected rects.
|
|
1500
|
-
if (clientRects.length >= 2) {
|
|
1501
|
-
if (getSideAxis(placement) === 'y') {
|
|
1502
|
-
const firstRect = clientRects[0];
|
|
1503
|
-
const lastRect = clientRects[clientRects.length - 1];
|
|
1504
|
-
const isTop = getSide(placement) === 'top';
|
|
1505
|
-
const top = firstRect.top;
|
|
1506
|
-
const bottom = lastRect.bottom;
|
|
1507
|
-
const left = isTop ? firstRect.left : lastRect.left;
|
|
1508
|
-
const right = isTop ? firstRect.right : lastRect.right;
|
|
1509
|
-
const width = right - left;
|
|
1510
|
-
const height = bottom - top;
|
|
1511
|
-
return {
|
|
1512
|
-
top,
|
|
1513
|
-
bottom,
|
|
1514
|
-
left,
|
|
1515
|
-
right,
|
|
1516
|
-
width,
|
|
1517
|
-
height,
|
|
1518
|
-
x: left,
|
|
1519
|
-
y: top
|
|
1520
|
-
};
|
|
1521
|
-
}
|
|
1522
|
-
const isLeftSide = getSide(placement) === 'left';
|
|
1523
|
-
const maxRight = max(...clientRects.map(rect => rect.right));
|
|
1524
|
-
const minLeft = min(...clientRects.map(rect => rect.left));
|
|
1525
|
-
const measureRects = clientRects.filter(rect => isLeftSide ? rect.left === minLeft : rect.right === maxRight);
|
|
1526
|
-
const top = measureRects[0].top;
|
|
1527
|
-
const bottom = measureRects[measureRects.length - 1].bottom;
|
|
1528
|
-
const left = minLeft;
|
|
1529
|
-
const right = maxRight;
|
|
1530
|
-
const width = right - left;
|
|
1531
|
-
const height = bottom - top;
|
|
1532
|
-
return {
|
|
1533
|
-
top,
|
|
1534
|
-
bottom,
|
|
1535
|
-
left,
|
|
1536
|
-
right,
|
|
1537
|
-
width,
|
|
1538
|
-
height,
|
|
1539
|
-
x: left,
|
|
1540
|
-
y: top
|
|
1541
|
-
};
|
|
1542
|
-
}
|
|
1543
|
-
return fallback;
|
|
1544
|
-
}
|
|
1545
|
-
const resetRects = await platform.getElementRects({
|
|
1546
|
-
reference: {
|
|
1547
|
-
getBoundingClientRect
|
|
1548
|
-
},
|
|
1549
|
-
floating: elements.floating,
|
|
1550
|
-
strategy
|
|
1551
|
-
});
|
|
1552
|
-
if (rects.reference.x !== resetRects.reference.x || rects.reference.y !== resetRects.reference.y || rects.reference.width !== resetRects.reference.width || rects.reference.height !== resetRects.reference.height) {
|
|
1553
|
-
return {
|
|
1554
|
-
reset: {
|
|
1555
|
-
rects: resetRects
|
|
1556
|
-
}
|
|
1557
|
-
};
|
|
1558
|
-
}
|
|
1559
|
-
return {};
|
|
1560
|
-
}
|
|
1561
|
-
};
|
|
1562
|
-
};
|
|
1563
|
-
|
|
1564
1351
|
const originSides = /*#__PURE__*/new Set(['left', 'top']);
|
|
1565
1352
|
|
|
1566
1353
|
// For type backwards-compatibility, the `OffsetOptions` type was also
|
|
@@ -1725,158 +1512,6 @@ const shift$1 = function (options) {
|
|
|
1725
1512
|
}
|
|
1726
1513
|
};
|
|
1727
1514
|
};
|
|
1728
|
-
/**
|
|
1729
|
-
* Built-in `limiter` that will stop `shift()` at a certain point.
|
|
1730
|
-
*/
|
|
1731
|
-
const limitShift = function (options) {
|
|
1732
|
-
if (options === void 0) {
|
|
1733
|
-
options = {};
|
|
1734
|
-
}
|
|
1735
|
-
return {
|
|
1736
|
-
options,
|
|
1737
|
-
fn(state) {
|
|
1738
|
-
const {
|
|
1739
|
-
x,
|
|
1740
|
-
y,
|
|
1741
|
-
placement,
|
|
1742
|
-
rects,
|
|
1743
|
-
middlewareData
|
|
1744
|
-
} = state;
|
|
1745
|
-
const {
|
|
1746
|
-
offset = 0,
|
|
1747
|
-
mainAxis: checkMainAxis = true,
|
|
1748
|
-
crossAxis: checkCrossAxis = true
|
|
1749
|
-
} = evaluate(options, state);
|
|
1750
|
-
const coords = {
|
|
1751
|
-
x,
|
|
1752
|
-
y
|
|
1753
|
-
};
|
|
1754
|
-
const crossAxis = getSideAxis(placement);
|
|
1755
|
-
const mainAxis = getOppositeAxis(crossAxis);
|
|
1756
|
-
let mainAxisCoord = coords[mainAxis];
|
|
1757
|
-
let crossAxisCoord = coords[crossAxis];
|
|
1758
|
-
const rawOffset = evaluate(offset, state);
|
|
1759
|
-
const computedOffset = typeof rawOffset === 'number' ? {
|
|
1760
|
-
mainAxis: rawOffset,
|
|
1761
|
-
crossAxis: 0
|
|
1762
|
-
} : {
|
|
1763
|
-
mainAxis: 0,
|
|
1764
|
-
crossAxis: 0,
|
|
1765
|
-
...rawOffset
|
|
1766
|
-
};
|
|
1767
|
-
if (checkMainAxis) {
|
|
1768
|
-
const len = mainAxis === 'y' ? 'height' : 'width';
|
|
1769
|
-
const limitMin = rects.reference[mainAxis] - rects.floating[len] + computedOffset.mainAxis;
|
|
1770
|
-
const limitMax = rects.reference[mainAxis] + rects.reference[len] - computedOffset.mainAxis;
|
|
1771
|
-
if (mainAxisCoord < limitMin) {
|
|
1772
|
-
mainAxisCoord = limitMin;
|
|
1773
|
-
} else if (mainAxisCoord > limitMax) {
|
|
1774
|
-
mainAxisCoord = limitMax;
|
|
1775
|
-
}
|
|
1776
|
-
}
|
|
1777
|
-
if (checkCrossAxis) {
|
|
1778
|
-
var _middlewareData$offse, _middlewareData$offse2;
|
|
1779
|
-
const len = mainAxis === 'y' ? 'width' : 'height';
|
|
1780
|
-
const isOriginSide = originSides.has(getSide(placement));
|
|
1781
|
-
const limitMin = rects.reference[crossAxis] - rects.floating[len] + (isOriginSide ? ((_middlewareData$offse = middlewareData.offset) == null ? void 0 : _middlewareData$offse[crossAxis]) || 0 : 0) + (isOriginSide ? 0 : computedOffset.crossAxis);
|
|
1782
|
-
const limitMax = rects.reference[crossAxis] + rects.reference[len] + (isOriginSide ? 0 : ((_middlewareData$offse2 = middlewareData.offset) == null ? void 0 : _middlewareData$offse2[crossAxis]) || 0) - (isOriginSide ? computedOffset.crossAxis : 0);
|
|
1783
|
-
if (crossAxisCoord < limitMin) {
|
|
1784
|
-
crossAxisCoord = limitMin;
|
|
1785
|
-
} else if (crossAxisCoord > limitMax) {
|
|
1786
|
-
crossAxisCoord = limitMax;
|
|
1787
|
-
}
|
|
1788
|
-
}
|
|
1789
|
-
return {
|
|
1790
|
-
[mainAxis]: mainAxisCoord,
|
|
1791
|
-
[crossAxis]: crossAxisCoord
|
|
1792
|
-
};
|
|
1793
|
-
}
|
|
1794
|
-
};
|
|
1795
|
-
};
|
|
1796
|
-
|
|
1797
|
-
/**
|
|
1798
|
-
* Provides data that allows you to change the size of the floating element —
|
|
1799
|
-
* for instance, prevent it from overflowing the clipping boundary or match the
|
|
1800
|
-
* width of the reference element.
|
|
1801
|
-
* @see https://floating-ui.com/docs/size
|
|
1802
|
-
*/
|
|
1803
|
-
const size = function (options) {
|
|
1804
|
-
if (options === void 0) {
|
|
1805
|
-
options = {};
|
|
1806
|
-
}
|
|
1807
|
-
return {
|
|
1808
|
-
name: 'size',
|
|
1809
|
-
options,
|
|
1810
|
-
async fn(state) {
|
|
1811
|
-
var _state$middlewareData, _state$middlewareData2;
|
|
1812
|
-
const {
|
|
1813
|
-
placement,
|
|
1814
|
-
rects,
|
|
1815
|
-
platform,
|
|
1816
|
-
elements
|
|
1817
|
-
} = state;
|
|
1818
|
-
const {
|
|
1819
|
-
apply = () => {},
|
|
1820
|
-
...detectOverflowOptions
|
|
1821
|
-
} = evaluate(options, state);
|
|
1822
|
-
const overflow = await platform.detectOverflow(state, detectOverflowOptions);
|
|
1823
|
-
const side = getSide(placement);
|
|
1824
|
-
const alignment = getAlignment(placement);
|
|
1825
|
-
const isYAxis = getSideAxis(placement) === 'y';
|
|
1826
|
-
const {
|
|
1827
|
-
width,
|
|
1828
|
-
height
|
|
1829
|
-
} = rects.floating;
|
|
1830
|
-
let heightSide;
|
|
1831
|
-
let widthSide;
|
|
1832
|
-
if (side === 'top' || side === 'bottom') {
|
|
1833
|
-
heightSide = side;
|
|
1834
|
-
widthSide = alignment === ((await (platform.isRTL == null ? void 0 : platform.isRTL(elements.floating))) ? 'start' : 'end') ? 'left' : 'right';
|
|
1835
|
-
} else {
|
|
1836
|
-
widthSide = side;
|
|
1837
|
-
heightSide = alignment === 'end' ? 'top' : 'bottom';
|
|
1838
|
-
}
|
|
1839
|
-
const maximumClippingHeight = height - overflow.top - overflow.bottom;
|
|
1840
|
-
const maximumClippingWidth = width - overflow.left - overflow.right;
|
|
1841
|
-
const overflowAvailableHeight = min(height - overflow[heightSide], maximumClippingHeight);
|
|
1842
|
-
const overflowAvailableWidth = min(width - overflow[widthSide], maximumClippingWidth);
|
|
1843
|
-
const noShift = !state.middlewareData.shift;
|
|
1844
|
-
let availableHeight = overflowAvailableHeight;
|
|
1845
|
-
let availableWidth = overflowAvailableWidth;
|
|
1846
|
-
if ((_state$middlewareData = state.middlewareData.shift) != null && _state$middlewareData.enabled.x) {
|
|
1847
|
-
availableWidth = maximumClippingWidth;
|
|
1848
|
-
}
|
|
1849
|
-
if ((_state$middlewareData2 = state.middlewareData.shift) != null && _state$middlewareData2.enabled.y) {
|
|
1850
|
-
availableHeight = maximumClippingHeight;
|
|
1851
|
-
}
|
|
1852
|
-
if (noShift && !alignment) {
|
|
1853
|
-
const xMin = max(overflow.left, 0);
|
|
1854
|
-
const xMax = max(overflow.right, 0);
|
|
1855
|
-
const yMin = max(overflow.top, 0);
|
|
1856
|
-
const yMax = max(overflow.bottom, 0);
|
|
1857
|
-
if (isYAxis) {
|
|
1858
|
-
availableWidth = width - 2 * (xMin !== 0 || xMax !== 0 ? xMin + xMax : max(overflow.left, overflow.right));
|
|
1859
|
-
} else {
|
|
1860
|
-
availableHeight = height - 2 * (yMin !== 0 || yMax !== 0 ? yMin + yMax : max(overflow.top, overflow.bottom));
|
|
1861
|
-
}
|
|
1862
|
-
}
|
|
1863
|
-
await apply({
|
|
1864
|
-
...state,
|
|
1865
|
-
availableWidth,
|
|
1866
|
-
availableHeight
|
|
1867
|
-
});
|
|
1868
|
-
const nextDimensions = await platform.getDimensions(elements.floating);
|
|
1869
|
-
if (width !== nextDimensions.width || height !== nextDimensions.height) {
|
|
1870
|
-
return {
|
|
1871
|
-
reset: {
|
|
1872
|
-
rects: true
|
|
1873
|
-
}
|
|
1874
|
-
};
|
|
1875
|
-
}
|
|
1876
|
-
return {};
|
|
1877
|
-
}
|
|
1878
|
-
};
|
|
1879
|
-
};
|
|
1880
1515
|
|
|
1881
1516
|
function hasWindow() {
|
|
1882
1517
|
return typeof window !== 'undefined';
|
|
@@ -2742,22 +2377,6 @@ const autoPlacement = autoPlacement$1;
|
|
|
2742
2377
|
*/
|
|
2743
2378
|
const shift = shift$1;
|
|
2744
2379
|
|
|
2745
|
-
/**
|
|
2746
|
-
* Optimizes the visibility of the floating element by flipping the `placement`
|
|
2747
|
-
* in order to keep it in view when the preferred placement(s) will overflow the
|
|
2748
|
-
* clipping boundary. Alternative to `autoPlacement`.
|
|
2749
|
-
* @see https://floating-ui.com/docs/flip
|
|
2750
|
-
*/
|
|
2751
|
-
flip;
|
|
2752
|
-
|
|
2753
|
-
/**
|
|
2754
|
-
* Provides data that allows you to change the size of the floating element —
|
|
2755
|
-
* for instance, prevent it from overflowing the clipping boundary or match the
|
|
2756
|
-
* width of the reference element.
|
|
2757
|
-
* @see https://floating-ui.com/docs/size
|
|
2758
|
-
*/
|
|
2759
|
-
size;
|
|
2760
|
-
|
|
2761
2380
|
/**
|
|
2762
2381
|
* Provides data to hide the floating element in applicable situations, such as
|
|
2763
2382
|
* when it is not in the same clipping context as the reference element.
|
|
@@ -2765,25 +2384,6 @@ size;
|
|
|
2765
2384
|
*/
|
|
2766
2385
|
const hide = hide$1;
|
|
2767
2386
|
|
|
2768
|
-
/**
|
|
2769
|
-
* Provides data to position an inner element of the floating element so that it
|
|
2770
|
-
* appears centered to the reference element.
|
|
2771
|
-
* @see https://floating-ui.com/docs/arrow
|
|
2772
|
-
*/
|
|
2773
|
-
arrow;
|
|
2774
|
-
|
|
2775
|
-
/**
|
|
2776
|
-
* Provides improved positioning for inline reference elements that can span
|
|
2777
|
-
* over multiple lines, such as hyperlinks or range selections.
|
|
2778
|
-
* @see https://floating-ui.com/docs/inline
|
|
2779
|
-
*/
|
|
2780
|
-
inline;
|
|
2781
|
-
|
|
2782
|
-
/**
|
|
2783
|
-
* Built-in `limiter` that will stop `shift()` at a certain point.
|
|
2784
|
-
*/
|
|
2785
|
-
limitShift;
|
|
2786
|
-
|
|
2787
2387
|
/**
|
|
2788
2388
|
* Computes the `x` and `y` coordinates that will place the floating element
|
|
2789
2389
|
* next to a given reference element.
|
|
@@ -3241,11 +2841,12 @@ const NON_SUBMITTING_INPUT_TYPES = /* @__PURE__ */ new Set([
|
|
|
3241
2841
|
]);
|
|
3242
2842
|
const FORM_ELEMENT_TAGS = /* @__PURE__ */ new Set(["INPUT", "TEXTAREA", "SELECT"]);
|
|
3243
2843
|
class Carousel extends Controller {
|
|
3244
|
-
static targets = ["track", "prev", "next", "dot"];
|
|
2844
|
+
static targets = ["track", "prev", "next", "dot", "status"];
|
|
3245
2845
|
static values = {
|
|
3246
2846
|
wizard: { type: Boolean, default: false },
|
|
3247
2847
|
autoplay: { type: Number, default: 0 },
|
|
3248
|
-
loop: { type: Boolean, default: false }
|
|
2848
|
+
loop: { type: Boolean, default: false },
|
|
2849
|
+
fade: { type: Boolean, default: false }
|
|
3249
2850
|
};
|
|
3250
2851
|
observer = null;
|
|
3251
2852
|
_currentIndex = 0;
|
|
@@ -3253,7 +2854,11 @@ class Carousel extends Controller {
|
|
|
3253
2854
|
_lastTouchX = 0;
|
|
3254
2855
|
_autoplayTimer = null;
|
|
3255
2856
|
connect() {
|
|
3256
|
-
this.
|
|
2857
|
+
if (this.fadeValue) {
|
|
2858
|
+
this.initFade();
|
|
2859
|
+
} else {
|
|
2860
|
+
this.setupObserver();
|
|
2861
|
+
}
|
|
3257
2862
|
this.updateNavigation();
|
|
3258
2863
|
this.element.addEventListener("keydown", this.handleArrowKeys);
|
|
3259
2864
|
if (this.wizardValue) {
|
|
@@ -3267,6 +2872,8 @@ class Carousel extends Controller {
|
|
|
3267
2872
|
this.startAutoplay();
|
|
3268
2873
|
this.element.addEventListener("mouseenter", this.pauseAutoplay);
|
|
3269
2874
|
this.element.addEventListener("mouseleave", this.resumeAutoplay);
|
|
2875
|
+
this.element.addEventListener("focusin", this.pauseAutoplay);
|
|
2876
|
+
this.element.addEventListener("focusout", this.resumeAutoplay);
|
|
3270
2877
|
}
|
|
3271
2878
|
}
|
|
3272
2879
|
disconnect() {
|
|
@@ -3275,12 +2882,20 @@ class Carousel extends Controller {
|
|
|
3275
2882
|
this.element.removeEventListener("keydown", this.handleKeydown);
|
|
3276
2883
|
this.element.removeEventListener("mouseenter", this.pauseAutoplay);
|
|
3277
2884
|
this.element.removeEventListener("mouseleave", this.resumeAutoplay);
|
|
2885
|
+
this.element.removeEventListener("focusin", this.pauseAutoplay);
|
|
2886
|
+
this.element.removeEventListener("focusout", this.resumeAutoplay);
|
|
3278
2887
|
this.trackTarget.removeEventListener("wheel", this.handleWheel);
|
|
3279
2888
|
this.trackTarget.removeEventListener("touchstart", this.handleTouchStart);
|
|
3280
2889
|
this.trackTarget.removeEventListener("touchmove", this.handleTouchMove);
|
|
3281
2890
|
this.stopAutoplay();
|
|
3282
2891
|
}
|
|
3283
2892
|
next() {
|
|
2893
|
+
if (this.fadeValue) {
|
|
2894
|
+
const atEnd2 = this._currentIndex >= this.slides.length - 1;
|
|
2895
|
+
const nextIndex = this.loopValue && atEnd2 ? 0 : Math.min(this._currentIndex + 1, this.slides.length - 1);
|
|
2896
|
+
this.activateSlide(nextIndex);
|
|
2897
|
+
return;
|
|
2898
|
+
}
|
|
3284
2899
|
const atEnd = this._currentIndex >= this.slides.length - 1;
|
|
3285
2900
|
if (this.loopValue && atEnd) {
|
|
3286
2901
|
this.slides[0]?.scrollIntoView({ behavior: "smooth", block: "nearest", inline: "nearest" });
|
|
@@ -3289,6 +2904,12 @@ class Carousel extends Controller {
|
|
|
3289
2904
|
}
|
|
3290
2905
|
}
|
|
3291
2906
|
previous() {
|
|
2907
|
+
if (this.fadeValue) {
|
|
2908
|
+
const atStart2 = this._currentIndex <= 0;
|
|
2909
|
+
const prevIndex = this.loopValue && atStart2 ? this.slides.length - 1 : Math.max(this._currentIndex - 1, 0);
|
|
2910
|
+
this.activateSlide(prevIndex);
|
|
2911
|
+
return;
|
|
2912
|
+
}
|
|
3292
2913
|
const atStart = this._currentIndex <= 0;
|
|
3293
2914
|
if (this.loopValue && atStart) {
|
|
3294
2915
|
const last = this.slides[this.slides.length - 1];
|
|
@@ -3300,6 +2921,10 @@ class Carousel extends Controller {
|
|
|
3300
2921
|
goto(event) {
|
|
3301
2922
|
const button = event.currentTarget;
|
|
3302
2923
|
const index = parseInt(button.dataset.index ?? "0", 10);
|
|
2924
|
+
if (this.fadeValue) {
|
|
2925
|
+
this.activateSlide(index);
|
|
2926
|
+
return;
|
|
2927
|
+
}
|
|
3303
2928
|
const slide = this.slides[index];
|
|
3304
2929
|
slide?.scrollIntoView({ behavior: "smooth", block: "nearest", inline: "nearest" });
|
|
3305
2930
|
}
|
|
@@ -3319,10 +2944,13 @@ class Carousel extends Controller {
|
|
|
3319
2944
|
this.next();
|
|
3320
2945
|
}
|
|
3321
2946
|
scrolled() {
|
|
2947
|
+
if (this.fadeValue)
|
|
2948
|
+
return;
|
|
3322
2949
|
if (this.wizardValue)
|
|
3323
2950
|
this.clampToFrontier();
|
|
3324
2951
|
this.updateCurrentIndex();
|
|
3325
2952
|
this.updateNavigation();
|
|
2953
|
+
this.announceSlide();
|
|
3326
2954
|
}
|
|
3327
2955
|
// Arrow function so `this` is stable for add/removeEventListener pairing.
|
|
3328
2956
|
// Handles ArrowLeft/ArrowRight for keyboard slide navigation. Skips form
|
|
@@ -3395,6 +3023,11 @@ class Carousel extends Controller {
|
|
|
3395
3023
|
this._autoplayTimer = null;
|
|
3396
3024
|
}
|
|
3397
3025
|
autoplayAdvance() {
|
|
3026
|
+
if (this.fadeValue) {
|
|
3027
|
+
const nextIndex = (this._currentIndex + 1) % this.slides.length;
|
|
3028
|
+
this.activateSlide(nextIndex);
|
|
3029
|
+
return;
|
|
3030
|
+
}
|
|
3398
3031
|
const atEnd = this._currentIndex >= this.slides.length - 1;
|
|
3399
3032
|
if (atEnd) {
|
|
3400
3033
|
const firstSlide = this.slides[0];
|
|
@@ -3406,6 +3039,53 @@ class Carousel extends Controller {
|
|
|
3406
3039
|
get slides() {
|
|
3407
3040
|
return Array.from(this.trackTarget.children);
|
|
3408
3041
|
}
|
|
3042
|
+
// Sets up the fade mode initial state. Marks the first slide active, hides
|
|
3043
|
+
// all others from the accessibility tree, and adds .fade-ready so CSS hands
|
|
3044
|
+
// control over to .is-active.
|
|
3045
|
+
initFade() {
|
|
3046
|
+
this.slides.forEach((slide, i) => {
|
|
3047
|
+
const active = i === 0;
|
|
3048
|
+
slide.classList.toggle("is-active", active);
|
|
3049
|
+
slide.toggleAttribute("inert", !active);
|
|
3050
|
+
slide.setAttribute("aria-hidden", String(!active));
|
|
3051
|
+
});
|
|
3052
|
+
this.element.classList.add("fade-ready");
|
|
3053
|
+
this.syncPagerDots();
|
|
3054
|
+
}
|
|
3055
|
+
// Transitions to the slide at `index` in fade mode by swapping .is-active and
|
|
3056
|
+
// hiding non-active slides from the accessibility tree.
|
|
3057
|
+
activateSlide(index) {
|
|
3058
|
+
this.slides.forEach((slide, i) => {
|
|
3059
|
+
const active = i === index;
|
|
3060
|
+
slide.classList.toggle("is-active", active);
|
|
3061
|
+
slide.toggleAttribute("inert", !active);
|
|
3062
|
+
slide.setAttribute("aria-hidden", String(!active));
|
|
3063
|
+
});
|
|
3064
|
+
this._currentIndex = index;
|
|
3065
|
+
this.updateNavigation();
|
|
3066
|
+
this.syncPagerDots();
|
|
3067
|
+
this.announceSlide();
|
|
3068
|
+
}
|
|
3069
|
+
// Syncs pager dot active state from _currentIndex. Used in fade mode where
|
|
3070
|
+
// IntersectionObserver is not set up (the track doesn't scroll).
|
|
3071
|
+
syncPagerDots() {
|
|
3072
|
+
if (!this.hasDotTarget)
|
|
3073
|
+
return;
|
|
3074
|
+
this.dotTargets.forEach((dot, i) => {
|
|
3075
|
+
dot.classList.toggle("active", i === this._currentIndex);
|
|
3076
|
+
if (i === this._currentIndex) {
|
|
3077
|
+
dot.setAttribute("aria-current", "true");
|
|
3078
|
+
} else {
|
|
3079
|
+
dot.removeAttribute("aria-current");
|
|
3080
|
+
}
|
|
3081
|
+
});
|
|
3082
|
+
}
|
|
3083
|
+
// Updates the sr-only live region so screen readers announce the new slide.
|
|
3084
|
+
announceSlide() {
|
|
3085
|
+
if (!this.hasStatusTarget)
|
|
3086
|
+
return;
|
|
3087
|
+
this.statusTarget.textContent = `Slide ${this._currentIndex + 1} of ${this.slides.length}`;
|
|
3088
|
+
}
|
|
3409
3089
|
// Snaps scroll position back to the frontier if the user somehow scrolled
|
|
3410
3090
|
// past it (e.g. momentum after a partially-blocked gesture).
|
|
3411
3091
|
clampToFrontier() {
|
|
@@ -3458,15 +3138,35 @@ class Carousel extends Controller {
|
|
|
3458
3138
|
this.slides.forEach((slide) => this.observer?.observe(slide));
|
|
3459
3139
|
}
|
|
3460
3140
|
updateNavigation() {
|
|
3141
|
+
if (this.fadeValue) {
|
|
3142
|
+
const atStart2 = this._currentIndex <= 0;
|
|
3143
|
+
const atEnd2 = this._currentIndex >= this.slides.length - 1;
|
|
3144
|
+
if (this.hasPrevTarget) {
|
|
3145
|
+
const hide = atStart2 && !this.loopValue;
|
|
3146
|
+
this.prevTarget.classList.toggle("hide", hide);
|
|
3147
|
+
this.prevTarget.disabled = hide;
|
|
3148
|
+
}
|
|
3149
|
+
if (this.hasNextTarget) {
|
|
3150
|
+
const hide = atEnd2 && !this.loopValue;
|
|
3151
|
+
this.nextTarget.classList.toggle("hide", hide);
|
|
3152
|
+
this.nextTarget.disabled = hide;
|
|
3153
|
+
}
|
|
3154
|
+
return;
|
|
3155
|
+
}
|
|
3461
3156
|
const track = this.trackTarget;
|
|
3462
3157
|
const atStart = track.scrollLeft <= 0;
|
|
3463
3158
|
const atEnd = track.scrollLeft >= track.scrollWidth - track.clientWidth - 1;
|
|
3464
|
-
if (this.hasPrevTarget)
|
|
3465
|
-
|
|
3159
|
+
if (this.hasPrevTarget) {
|
|
3160
|
+
const hide = atStart && !this.loopValue;
|
|
3161
|
+
this.prevTarget.classList.toggle("hide", hide);
|
|
3162
|
+
this.prevTarget.disabled = hide;
|
|
3163
|
+
}
|
|
3466
3164
|
if (this.hasNextTarget) {
|
|
3467
3165
|
const atLastSlide = this._currentIndex >= this.slides.length - 1;
|
|
3468
3166
|
const frontierReached = this.wizardValue && this._currentIndex >= this._maxUnlocked;
|
|
3469
|
-
|
|
3167
|
+
const hide = (atEnd || atLastSlide || frontierReached) && !this.loopValue;
|
|
3168
|
+
this.nextTarget.classList.toggle("hide", hide);
|
|
3169
|
+
this.nextTarget.disabled = hide;
|
|
3470
3170
|
}
|
|
3471
3171
|
if (this.wizardValue && this.hasDotTarget) {
|
|
3472
3172
|
this.dotTargets.forEach((dot, i) => {
|
|
@@ -3492,11 +3192,13 @@ class FormLiveSummary extends Controller {
|
|
|
3492
3192
|
this.form?.addEventListener("input", this.render);
|
|
3493
3193
|
this.form?.addEventListener("change", this.render);
|
|
3494
3194
|
this.form?.addEventListener("midwest-autocomplete:change", this.render);
|
|
3195
|
+
this.form?.addEventListener("midwest-color-picker:update", this.render);
|
|
3495
3196
|
}
|
|
3496
3197
|
disconnect() {
|
|
3497
3198
|
this.form?.removeEventListener("input", this.render);
|
|
3498
3199
|
this.form?.removeEventListener("change", this.render);
|
|
3499
3200
|
this.form?.removeEventListener("midwest-autocomplete:change", this.render);
|
|
3201
|
+
this.form?.removeEventListener("midwest-color-picker:update", this.render);
|
|
3500
3202
|
}
|
|
3501
3203
|
get form() {
|
|
3502
3204
|
return this.element.closest("form");
|
|
@@ -3514,15 +3216,18 @@ class FormLiveSummary extends Controller {
|
|
|
3514
3216
|
}
|
|
3515
3217
|
const dl = document.createElement("dl");
|
|
3516
3218
|
dl.className = "midwest-form-live-summary-list";
|
|
3517
|
-
fields.forEach(({ label, value }) => {
|
|
3219
|
+
fields.forEach(({ label, value, swatchColor }) => {
|
|
3518
3220
|
const dt = Object.assign(document.createElement("dt"), {
|
|
3519
3221
|
className: "midwest-form-live-summary-term",
|
|
3520
3222
|
textContent: label
|
|
3521
3223
|
});
|
|
3522
|
-
const dd =
|
|
3523
|
-
|
|
3524
|
-
|
|
3525
|
-
|
|
3224
|
+
const dd = document.createElement("dd");
|
|
3225
|
+
dd.className = "midwest-form-live-summary-definition";
|
|
3226
|
+
if (swatchColor !== void 0) {
|
|
3227
|
+
dd.append(this.buildSwatch(swatchColor), value || "\u2014");
|
|
3228
|
+
} else {
|
|
3229
|
+
dd.textContent = value || "\u2014";
|
|
3230
|
+
}
|
|
3526
3231
|
dl.append(dt, dd);
|
|
3527
3232
|
});
|
|
3528
3233
|
this.listTarget.replaceChildren(dl);
|
|
@@ -3579,6 +3284,18 @@ class FormLiveSummary extends Controller {
|
|
|
3579
3284
|
value: element.value
|
|
3580
3285
|
});
|
|
3581
3286
|
});
|
|
3287
|
+
form.querySelectorAll('[data-midwest-color-picker-target="input"]').forEach((input) => {
|
|
3288
|
+
if (this.element.contains(input))
|
|
3289
|
+
return;
|
|
3290
|
+
if (!input.name)
|
|
3291
|
+
return;
|
|
3292
|
+
const picker = input.closest(".midwest-color-picker");
|
|
3293
|
+
const labelEl = picker?.querySelector(".midwest-form-group__top label") ?? this.form?.querySelector(`label[for="${CSS.escape(input.id)}"]`);
|
|
3294
|
+
const label = labelEl?.textContent?.trim() || this.prettifyName(input.name.replace(/\[.*\]$/, ""));
|
|
3295
|
+
const value = input.value;
|
|
3296
|
+
const display = value.startsWith("#") ? value : value ? value.charAt(0).toUpperCase() + value.slice(1) : "\u2014";
|
|
3297
|
+
fields.push({ label, value: display, swatchColor: value || void 0 });
|
|
3298
|
+
});
|
|
3582
3299
|
form.querySelectorAll(".midwest-autocomplete").forEach((ac) => {
|
|
3583
3300
|
if (this.element.contains(ac))
|
|
3584
3301
|
return;
|
|
@@ -3591,6 +3308,22 @@ class FormLiveSummary extends Controller {
|
|
|
3591
3308
|
});
|
|
3592
3309
|
return fields;
|
|
3593
3310
|
}
|
|
3311
|
+
// Builds a small inline colour swatch to display alongside a colour value.
|
|
3312
|
+
// Hex values are applied as an inline background-color; named theme colours
|
|
3313
|
+
// use a scoped theme-{color} class so the CSS variable cascade does the work.
|
|
3314
|
+
// An empty/transparent value renders a diagonal-stripe placeholder.
|
|
3315
|
+
buildSwatch(color) {
|
|
3316
|
+
const swatch = document.createElement("span");
|
|
3317
|
+
swatch.className = "midwest-form-live-summary-swatch";
|
|
3318
|
+
if (color.startsWith("#")) {
|
|
3319
|
+
swatch.style.backgroundColor = color;
|
|
3320
|
+
} else if (color && color !== "transparent") {
|
|
3321
|
+
swatch.classList.add(`theme-${color}`, "bg-theme-6");
|
|
3322
|
+
} else {
|
|
3323
|
+
swatch.classList.add("is-transparent");
|
|
3324
|
+
}
|
|
3325
|
+
return swatch;
|
|
3326
|
+
}
|
|
3594
3327
|
// Finds the label associated with a specific element via `for` attribute,
|
|
3595
3328
|
// wrapping label, or falls back to a prettified version of the element name.
|
|
3596
3329
|
labelFor(el) {
|
|
@@ -3656,16 +3389,37 @@ class CountdownTimer extends Controller {
|
|
|
3656
3389
|
seconds: { type: Number, default: 5 }
|
|
3657
3390
|
};
|
|
3658
3391
|
timer = null;
|
|
3392
|
+
remaining = 0;
|
|
3393
|
+
startTime = 0;
|
|
3659
3394
|
connect() {
|
|
3660
|
-
this.
|
|
3661
|
-
|
|
3662
|
-
}, this.secondsValue * 1e3);
|
|
3395
|
+
this.remaining = this.secondsValue * 1e3;
|
|
3396
|
+
this.resume();
|
|
3663
3397
|
}
|
|
3664
3398
|
disconnect() {
|
|
3665
|
-
|
|
3666
|
-
|
|
3399
|
+
this.pause();
|
|
3400
|
+
}
|
|
3401
|
+
pause() {
|
|
3402
|
+
if (this.timer === null)
|
|
3403
|
+
return;
|
|
3404
|
+
clearTimeout(this.timer);
|
|
3405
|
+
this.timer = null;
|
|
3406
|
+
this.remaining = Math.max(0, this.remaining - (Date.now() - this.startTime));
|
|
3407
|
+
this.setPlayState("paused");
|
|
3408
|
+
}
|
|
3409
|
+
resume() {
|
|
3410
|
+
if (this.timer !== null)
|
|
3411
|
+
return;
|
|
3412
|
+
this.startTime = Date.now();
|
|
3413
|
+
this.timer = setTimeout(() => {
|
|
3667
3414
|
this.timer = null;
|
|
3668
|
-
|
|
3415
|
+
this.dispatch("complete", { bubbles: true });
|
|
3416
|
+
}, this.remaining);
|
|
3417
|
+
this.setPlayState("running");
|
|
3418
|
+
}
|
|
3419
|
+
setPlayState(state) {
|
|
3420
|
+
const progress = this.element.querySelector(".midwest-countdown-timer__progress");
|
|
3421
|
+
if (progress)
|
|
3422
|
+
progress.style.animationPlayState = state;
|
|
3669
3423
|
}
|
|
3670
3424
|
}
|
|
3671
3425
|
|
|
@@ -3858,19 +3612,30 @@ class Rating extends Controller {
|
|
|
3858
3612
|
}
|
|
3859
3613
|
|
|
3860
3614
|
class Autocomplete extends Controller {
|
|
3861
|
-
static targets = [
|
|
3615
|
+
static targets = [
|
|
3616
|
+
"input",
|
|
3617
|
+
"dropdown",
|
|
3618
|
+
"list",
|
|
3619
|
+
"tokens",
|
|
3620
|
+
"multipleTokenTemplate",
|
|
3621
|
+
"singleTokenTemplate",
|
|
3622
|
+
"status",
|
|
3623
|
+
"turboList",
|
|
3624
|
+
"loader",
|
|
3625
|
+
"freeFormHintList",
|
|
3626
|
+
"freeFormHintValue"
|
|
3627
|
+
];
|
|
3862
3628
|
static values = {
|
|
3863
3629
|
options: { type: Array, default: [] },
|
|
3864
3630
|
url: { type: String, default: "" },
|
|
3865
3631
|
multiple: { type: Boolean, default: true },
|
|
3866
|
-
turboFrameUrl: { type: String, default: "" }
|
|
3632
|
+
turboFrameUrl: { type: String, default: "" },
|
|
3633
|
+
freeForm: { type: Boolean, default: false }
|
|
3867
3634
|
};
|
|
3868
3635
|
highlightedIndex = -1;
|
|
3869
3636
|
fetchAbort = null;
|
|
3870
3637
|
searchTimer;
|
|
3871
3638
|
// ── Named handlers ─────────────────────────────────────────────────────────
|
|
3872
|
-
// Arrow-function class properties preserve `this` and produce a stable
|
|
3873
|
-
// reference so addEventListener/removeEventListener pairs always match.
|
|
3874
3639
|
onFrameLoad = () => {
|
|
3875
3640
|
this.syncSelectedState();
|
|
3876
3641
|
this.loading = false;
|
|
@@ -3905,9 +3670,6 @@ class Autocomplete extends Controller {
|
|
|
3905
3670
|
clearTimeout(this.searchTimer);
|
|
3906
3671
|
this.element.removeEventListener("focusout", this.onFocusOut);
|
|
3907
3672
|
}
|
|
3908
|
-
// Target callbacks — Stimulus calls these automatically when targets connect
|
|
3909
|
-
// or disconnect, including after the initial connect(). This replaces manual
|
|
3910
|
-
// hasTurboListTarget guards and addEventListener calls inside connect().
|
|
3911
3673
|
dropdownTargetConnected(el) {
|
|
3912
3674
|
el.addEventListener("toggle", this.onToggle);
|
|
3913
3675
|
}
|
|
@@ -3932,13 +3694,27 @@ class Autocomplete extends Controller {
|
|
|
3932
3694
|
this.open();
|
|
3933
3695
|
}
|
|
3934
3696
|
open() {
|
|
3697
|
+
if (!this.hasDropdownTarget)
|
|
3698
|
+
return;
|
|
3935
3699
|
if (this.dropdownTarget.matches(":popover-open"))
|
|
3936
3700
|
return;
|
|
3701
|
+
const query = this.inputTarget.value.trim();
|
|
3702
|
+
if (this.freeFormValue && !this.hasListTarget && !this.hasTurboListTarget) {
|
|
3703
|
+
if (query) {
|
|
3704
|
+
this.updateFreeFormHint(query);
|
|
3705
|
+
this.showDropdown();
|
|
3706
|
+
}
|
|
3707
|
+
return;
|
|
3708
|
+
}
|
|
3937
3709
|
clearTimeout(this.searchTimer);
|
|
3938
|
-
this.resolve(
|
|
3710
|
+
this.resolve(query);
|
|
3939
3711
|
}
|
|
3940
3712
|
search() {
|
|
3941
3713
|
const query = this.inputTarget.value.trim();
|
|
3714
|
+
if (this.freeFormValue)
|
|
3715
|
+
this.updateFreeFormHint(query);
|
|
3716
|
+
if (!this.hasListTarget && !this.hasTurboListTarget)
|
|
3717
|
+
return;
|
|
3942
3718
|
if (!this.hasTurboListTarget && !this.urlValue) {
|
|
3943
3719
|
this.renderOptions(this.filteredOptions(query));
|
|
3944
3720
|
return;
|
|
@@ -3953,6 +3729,8 @@ class Autocomplete extends Controller {
|
|
|
3953
3729
|
switch (event.key) {
|
|
3954
3730
|
case "ArrowDown": {
|
|
3955
3731
|
event.preventDefault();
|
|
3732
|
+
if (!this.hasDropdownTarget)
|
|
3733
|
+
break;
|
|
3956
3734
|
if (!this.dropdownTarget.matches(":popover-open"))
|
|
3957
3735
|
this.open();
|
|
3958
3736
|
const items = this.visibleOptions();
|
|
@@ -3962,7 +3740,7 @@ class Autocomplete extends Controller {
|
|
|
3962
3740
|
}
|
|
3963
3741
|
case "ArrowUp": {
|
|
3964
3742
|
event.preventDefault();
|
|
3965
|
-
if (!this.dropdownTarget.matches(":popover-open"))
|
|
3743
|
+
if (!this.hasDropdownTarget || !this.dropdownTarget.matches(":popover-open"))
|
|
3966
3744
|
break;
|
|
3967
3745
|
const items = this.visibleOptions();
|
|
3968
3746
|
this.highlightedIndex = Math.max(this.highlightedIndex - 1, 0);
|
|
@@ -3971,11 +3749,22 @@ class Autocomplete extends Controller {
|
|
|
3971
3749
|
}
|
|
3972
3750
|
case "Enter": {
|
|
3973
3751
|
event.preventDefault();
|
|
3974
|
-
if (
|
|
3975
|
-
|
|
3976
|
-
|
|
3977
|
-
|
|
3978
|
-
|
|
3752
|
+
if (this.hasDropdownTarget && this.dropdownTarget.matches(":popover-open") && (this.hasListTarget || this.hasTurboListTarget)) {
|
|
3753
|
+
const items = this.visibleOptions();
|
|
3754
|
+
if (this.highlightedIndex >= 0 && items[this.highlightedIndex]) {
|
|
3755
|
+
this.pickItem(items[this.highlightedIndex]);
|
|
3756
|
+
break;
|
|
3757
|
+
}
|
|
3758
|
+
}
|
|
3759
|
+
if (this.freeFormValue) {
|
|
3760
|
+
this.commitFreeFormTag();
|
|
3761
|
+
}
|
|
3762
|
+
break;
|
|
3763
|
+
}
|
|
3764
|
+
case ",": {
|
|
3765
|
+
if (this.freeFormValue && this.inputTarget.value.trim()) {
|
|
3766
|
+
event.preventDefault();
|
|
3767
|
+
this.commitFreeFormTag();
|
|
3979
3768
|
}
|
|
3980
3769
|
break;
|
|
3981
3770
|
}
|
|
@@ -3992,6 +3781,22 @@ class Autocomplete extends Controller {
|
|
|
3992
3781
|
break;
|
|
3993
3782
|
}
|
|
3994
3783
|
}
|
|
3784
|
+
// Splits pasted text on commas and newlines into individual tags (free-form mode only).
|
|
3785
|
+
pasteTag(event) {
|
|
3786
|
+
if (!this.freeFormValue)
|
|
3787
|
+
return;
|
|
3788
|
+
const text = event.clipboardData?.getData("text") ?? "";
|
|
3789
|
+
const tags = text.split(/[\n,]+/).map((t) => t.trim()).filter(Boolean);
|
|
3790
|
+
if (tags.length === 0)
|
|
3791
|
+
return;
|
|
3792
|
+
event.preventDefault();
|
|
3793
|
+
tags.forEach((tag) => this.addFreeFormTag(tag));
|
|
3794
|
+
}
|
|
3795
|
+
// Mousedown on the "Hit Enter to add 'X'" hint row.
|
|
3796
|
+
addFreeFormTagFromHint(event) {
|
|
3797
|
+
event.preventDefault();
|
|
3798
|
+
this.commitFreeFormTag();
|
|
3799
|
+
}
|
|
3995
3800
|
removeToken(event) {
|
|
3996
3801
|
event.stopPropagation();
|
|
3997
3802
|
const btn = event.currentTarget;
|
|
@@ -3999,9 +3804,6 @@ class Autocomplete extends Controller {
|
|
|
3999
3804
|
if (value)
|
|
4000
3805
|
this.deselect(value);
|
|
4001
3806
|
}
|
|
4002
|
-
// Public action used by turbo-frame option items via data-action.
|
|
4003
|
-
// Each server-rendered <li> should have:
|
|
4004
|
-
// data-action="mousedown->midwest-autocomplete#selectOption"
|
|
4005
3807
|
selectOption(event) {
|
|
4006
3808
|
event.preventDefault();
|
|
4007
3809
|
this.pickItem(event.currentTarget);
|
|
@@ -4013,7 +3815,6 @@ class Autocomplete extends Controller {
|
|
|
4013
3815
|
isSelected(value) {
|
|
4014
3816
|
return !!this.tokensTarget.querySelector(`[data-value="${CSS.escape(value)}"]`);
|
|
4015
3817
|
}
|
|
4016
|
-
// Routes to the correct data source. Shared by open() and the debounced search path.
|
|
4017
3818
|
resolve(query) {
|
|
4018
3819
|
if (this.hasTurboListTarget) {
|
|
4019
3820
|
this.navigateFrame(query);
|
|
@@ -4042,9 +3843,6 @@ class Autocomplete extends Controller {
|
|
|
4042
3843
|
this.turboListTarget.setAttribute("src", url.toString());
|
|
4043
3844
|
this.showDropdown();
|
|
4044
3845
|
}
|
|
4045
|
-
// After a turbo frame loads, mark already-selected items and update the live region.
|
|
4046
|
-
// The empty state element is always present in the frame and CSS shows/hides it
|
|
4047
|
-
// based on whether any non-selected options exist.
|
|
4048
3846
|
syncSelectedState() {
|
|
4049
3847
|
const options = this.turboListTarget.querySelectorAll(".midwest-autocomplete-option");
|
|
4050
3848
|
options.forEach((option) => {
|
|
@@ -4080,6 +3878,7 @@ class Autocomplete extends Controller {
|
|
|
4080
3878
|
select(option) {
|
|
4081
3879
|
this.appendToken(option);
|
|
4082
3880
|
this.hideDropdown();
|
|
3881
|
+
this.hideFreeFormHint();
|
|
4083
3882
|
this.inputTarget.value = "";
|
|
4084
3883
|
this.inputTarget.focus();
|
|
4085
3884
|
this.announce(`${option.label} selected`);
|
|
@@ -4151,11 +3950,19 @@ class Autocomplete extends Controller {
|
|
|
4151
3950
|
this.renderEmpty("No more options");
|
|
4152
3951
|
return;
|
|
4153
3952
|
}
|
|
3953
|
+
this.hideFreeFormHint();
|
|
4154
3954
|
this.loading = false;
|
|
4155
3955
|
this.announce(`${availableCount} option${availableCount === 1 ? "" : "s"} available`);
|
|
4156
3956
|
this.showDropdown();
|
|
4157
3957
|
}
|
|
4158
3958
|
renderEmpty(message = "No results") {
|
|
3959
|
+
if (this.freeFormValue && this.inputTarget.value.trim()) {
|
|
3960
|
+
if (this.hasListTarget)
|
|
3961
|
+
this.listTarget.replaceChildren();
|
|
3962
|
+
this.loading = false;
|
|
3963
|
+
this.updateFreeFormHint(this.inputTarget.value.trim());
|
|
3964
|
+
return;
|
|
3965
|
+
}
|
|
4159
3966
|
const li = document.createElement("li");
|
|
4160
3967
|
li.className = "midwest-autocomplete-empty";
|
|
4161
3968
|
li.setAttribute("aria-hidden", "true");
|
|
@@ -4165,15 +3972,39 @@ class Autocomplete extends Controller {
|
|
|
4165
3972
|
this.announce(message);
|
|
4166
3973
|
this.showDropdown();
|
|
4167
3974
|
}
|
|
3975
|
+
// Shows or hides the "Hit Enter to add 'X'" hint based on the current query.
|
|
3976
|
+
updateFreeFormHint(query) {
|
|
3977
|
+
if (!this.hasFreeFormHintListTarget)
|
|
3978
|
+
return;
|
|
3979
|
+
if (query && !this.isSelected(query)) {
|
|
3980
|
+
if (this.hasFreeFormHintValueTarget) {
|
|
3981
|
+
this.freeFormHintValueTarget.textContent = `"${query}"`;
|
|
3982
|
+
}
|
|
3983
|
+
this.freeFormHintListTarget.hidden = false;
|
|
3984
|
+
this.showDropdown();
|
|
3985
|
+
} else {
|
|
3986
|
+
this.hideFreeFormHint();
|
|
3987
|
+
if (!this.hasListTarget && !this.hasTurboListTarget)
|
|
3988
|
+
this.hideDropdown();
|
|
3989
|
+
}
|
|
3990
|
+
}
|
|
3991
|
+
hideFreeFormHint() {
|
|
3992
|
+
if (this.hasFreeFormHintListTarget)
|
|
3993
|
+
this.freeFormHintListTarget.hidden = true;
|
|
3994
|
+
}
|
|
4168
3995
|
set loading(on) {
|
|
4169
3996
|
if (this.hasLoaderTarget)
|
|
4170
3997
|
this.loaderTarget.hidden = !on;
|
|
4171
3998
|
}
|
|
4172
3999
|
showDropdown() {
|
|
4000
|
+
if (!this.hasDropdownTarget)
|
|
4001
|
+
return;
|
|
4173
4002
|
if (!this.dropdownTarget.matches(":popover-open"))
|
|
4174
4003
|
this.dropdownTarget.showPopover();
|
|
4175
4004
|
}
|
|
4176
4005
|
hideDropdown() {
|
|
4006
|
+
if (!this.hasDropdownTarget)
|
|
4007
|
+
return;
|
|
4177
4008
|
if (this.dropdownTarget.matches(":popover-open"))
|
|
4178
4009
|
this.dropdownTarget.hidePopover();
|
|
4179
4010
|
}
|
|
@@ -4197,6 +4028,17 @@ class Autocomplete extends Controller {
|
|
|
4197
4028
|
const support = item.querySelector(".midwest-autocomplete-option-support")?.textContent?.trim();
|
|
4198
4029
|
this.select({ value, label, support });
|
|
4199
4030
|
}
|
|
4031
|
+
commitFreeFormTag() {
|
|
4032
|
+
const value = this.inputTarget.value.trim();
|
|
4033
|
+
this.addFreeFormTag(value);
|
|
4034
|
+
}
|
|
4035
|
+
addFreeFormTag(value) {
|
|
4036
|
+
if (!value)
|
|
4037
|
+
return;
|
|
4038
|
+
if (this.isSelected(value))
|
|
4039
|
+
return;
|
|
4040
|
+
this.select({ value, label: value });
|
|
4041
|
+
}
|
|
4200
4042
|
announce(message) {
|
|
4201
4043
|
if (this.hasStatusTarget)
|
|
4202
4044
|
this.statusTarget.textContent = message;
|
|
@@ -4733,6 +4575,289 @@ class Image extends Controller {
|
|
|
4733
4575
|
}
|
|
4734
4576
|
}
|
|
4735
4577
|
|
|
4578
|
+
class ColorPicker extends Controller {
|
|
4579
|
+
static targets = ["input", "swatch", "customSwatch", "colorInput"];
|
|
4580
|
+
onCustomColorChange = null;
|
|
4581
|
+
get scaleTarget() {
|
|
4582
|
+
return this.element.dataset.scaleTarget ?? "theme";
|
|
4583
|
+
}
|
|
4584
|
+
connect() {
|
|
4585
|
+
if (this.hasColorInputTarget) {
|
|
4586
|
+
this.onCustomColorChange = (e) => this.handleCustomColor(e);
|
|
4587
|
+
this.colorInputTarget.addEventListener("input", this.onCustomColorChange);
|
|
4588
|
+
}
|
|
4589
|
+
if (this.hasInputTarget && this.inputTarget.value.startsWith("#")) {
|
|
4590
|
+
this.restoreCustom(this.inputTarget.value);
|
|
4591
|
+
}
|
|
4592
|
+
}
|
|
4593
|
+
disconnect() {
|
|
4594
|
+
if (this.hasColorInputTarget && this.onCustomColorChange) {
|
|
4595
|
+
this.colorInputTarget.removeEventListener("input", this.onCustomColorChange);
|
|
4596
|
+
this.onCustomColorChange = null;
|
|
4597
|
+
}
|
|
4598
|
+
}
|
|
4599
|
+
select(e) {
|
|
4600
|
+
const button = e.currentTarget;
|
|
4601
|
+
const color = button.dataset.color ?? "";
|
|
4602
|
+
this.applySelection(color);
|
|
4603
|
+
this.dispatch("update", { detail: { color, scaleTarget: this.scaleTarget } });
|
|
4604
|
+
}
|
|
4605
|
+
openCustom(e) {
|
|
4606
|
+
e.preventDefault();
|
|
4607
|
+
this.colorInputTarget.click();
|
|
4608
|
+
}
|
|
4609
|
+
handleCustomColor(e) {
|
|
4610
|
+
const hex = e.target.value;
|
|
4611
|
+
if (this.hasCustomSwatchTarget) {
|
|
4612
|
+
this.customSwatchTarget.dataset.color = hex;
|
|
4613
|
+
this.customSwatchTarget.style.background = hex;
|
|
4614
|
+
}
|
|
4615
|
+
const scale = this.generateScale(hex);
|
|
4616
|
+
this.applyScale(scale);
|
|
4617
|
+
this.applySelection(hex);
|
|
4618
|
+
this.dispatch("update", { detail: { color: hex, scale, scaleTarget: this.scaleTarget } });
|
|
4619
|
+
}
|
|
4620
|
+
restoreCustom(hex) {
|
|
4621
|
+
if (this.hasColorInputTarget) {
|
|
4622
|
+
this.colorInputTarget.value = hex;
|
|
4623
|
+
}
|
|
4624
|
+
if (this.hasCustomSwatchTarget) {
|
|
4625
|
+
this.customSwatchTarget.style.background = hex;
|
|
4626
|
+
}
|
|
4627
|
+
const scale = this.generateScale(hex);
|
|
4628
|
+
this.applyScale(scale);
|
|
4629
|
+
}
|
|
4630
|
+
applySelection(color) {
|
|
4631
|
+
if (this.hasInputTarget) {
|
|
4632
|
+
this.inputTarget.value = color;
|
|
4633
|
+
}
|
|
4634
|
+
this.swatchTargets.forEach((swatch) => {
|
|
4635
|
+
const isSelected = swatch.dataset.color === color;
|
|
4636
|
+
swatch.classList.toggle("is-selected", isSelected);
|
|
4637
|
+
swatch.setAttribute("aria-pressed", String(isSelected));
|
|
4638
|
+
});
|
|
4639
|
+
}
|
|
4640
|
+
// Generates 13 RGB channel strings (space-separated, matching --{color}-{n}-channels format)
|
|
4641
|
+
// from a hex color by fixing the hue/saturation and distributing lightness across the scale.
|
|
4642
|
+
generateScale(hex) {
|
|
4643
|
+
const [h, s] = this.hexToHsl(hex);
|
|
4644
|
+
const lightnessSteps = [97, 90, 82, 74, 65, 56, 48, 40, 33, 26, 18, 10, 3];
|
|
4645
|
+
return Object.fromEntries(
|
|
4646
|
+
lightnessSteps.map((l, i) => {
|
|
4647
|
+
const [r, g, b] = this.hslToRgb(h, s, l);
|
|
4648
|
+
return [String(i), `${r} ${g} ${b}`];
|
|
4649
|
+
})
|
|
4650
|
+
);
|
|
4651
|
+
}
|
|
4652
|
+
applyScale(scale) {
|
|
4653
|
+
const el = this.element;
|
|
4654
|
+
Object.entries(scale).forEach(([step, channels]) => {
|
|
4655
|
+
el.style.setProperty(`--${this.scaleTarget}-${step}-channels`, channels);
|
|
4656
|
+
});
|
|
4657
|
+
}
|
|
4658
|
+
hexToHsl(hex) {
|
|
4659
|
+
const r = parseInt(hex.slice(1, 3), 16) / 255;
|
|
4660
|
+
const g = parseInt(hex.slice(3, 5), 16) / 255;
|
|
4661
|
+
const b = parseInt(hex.slice(5, 7), 16) / 255;
|
|
4662
|
+
const max = Math.max(r, g, b);
|
|
4663
|
+
const min = Math.min(r, g, b);
|
|
4664
|
+
const l = (max + min) / 2;
|
|
4665
|
+
if (max === min)
|
|
4666
|
+
return [0, 0, l * 100];
|
|
4667
|
+
const d = max - min;
|
|
4668
|
+
const s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
|
|
4669
|
+
const h = max === r ? ((g - b) / d + (g < b ? 6 : 0)) / 6 : max === g ? ((b - r) / d + 2) / 6 : ((r - g) / d + 4) / 6;
|
|
4670
|
+
return [h * 360, s * 100, l * 100];
|
|
4671
|
+
}
|
|
4672
|
+
hslToRgb(h, s, l) {
|
|
4673
|
+
h /= 360;
|
|
4674
|
+
s /= 100;
|
|
4675
|
+
l /= 100;
|
|
4676
|
+
if (s === 0) {
|
|
4677
|
+
const v = Math.round(l * 255);
|
|
4678
|
+
return [v, v, v];
|
|
4679
|
+
}
|
|
4680
|
+
const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
|
|
4681
|
+
const p = 2 * l - q;
|
|
4682
|
+
return [
|
|
4683
|
+
Math.round(this.hue2rgb(p, q, h + 1 / 3) * 255),
|
|
4684
|
+
Math.round(this.hue2rgb(p, q, h) * 255),
|
|
4685
|
+
Math.round(this.hue2rgb(p, q, h - 1 / 3) * 255)
|
|
4686
|
+
];
|
|
4687
|
+
}
|
|
4688
|
+
hue2rgb(p, q, t) {
|
|
4689
|
+
if (t < 0)
|
|
4690
|
+
t += 1;
|
|
4691
|
+
if (t > 1)
|
|
4692
|
+
t -= 1;
|
|
4693
|
+
if (t < 1 / 6)
|
|
4694
|
+
return p + (q - p) * 6 * t;
|
|
4695
|
+
if (t < 1 / 2)
|
|
4696
|
+
return q;
|
|
4697
|
+
if (t < 2 / 3)
|
|
4698
|
+
return p + (q - p) * (2 / 3 - t) * 6;
|
|
4699
|
+
return p;
|
|
4700
|
+
}
|
|
4701
|
+
}
|
|
4702
|
+
|
|
4703
|
+
class Notification extends Controller {
|
|
4704
|
+
container = null;
|
|
4705
|
+
observer = null;
|
|
4706
|
+
connect() {
|
|
4707
|
+
this.container = this.element.closest("[popover]");
|
|
4708
|
+
this.container?.showPopover?.();
|
|
4709
|
+
requestAnimationFrame(() => {
|
|
4710
|
+
this.element.classList.add("visible");
|
|
4711
|
+
});
|
|
4712
|
+
const frame = this.element.parentElement;
|
|
4713
|
+
if (frame) {
|
|
4714
|
+
this.observer = new MutationObserver(() => this.syncCountdown());
|
|
4715
|
+
this.observer.observe(frame, { childList: true });
|
|
4716
|
+
setTimeout(() => this.syncCountdown());
|
|
4717
|
+
}
|
|
4718
|
+
}
|
|
4719
|
+
disconnect() {
|
|
4720
|
+
this.observer?.disconnect();
|
|
4721
|
+
this.observer = null;
|
|
4722
|
+
if (this.container) {
|
|
4723
|
+
const frame = this.container.querySelector("turbo-frame");
|
|
4724
|
+
if (frame?.children.length === 0 && this.container.matches(":popover-open")) {
|
|
4725
|
+
this.container.hidePopover?.();
|
|
4726
|
+
}
|
|
4727
|
+
this.container = null;
|
|
4728
|
+
}
|
|
4729
|
+
}
|
|
4730
|
+
close() {
|
|
4731
|
+
if (!this.element.classList.contains("animated")) {
|
|
4732
|
+
this.element.remove();
|
|
4733
|
+
return;
|
|
4734
|
+
}
|
|
4735
|
+
this.element.classList.add("closing");
|
|
4736
|
+
setTimeout(() => {
|
|
4737
|
+
this.element.remove();
|
|
4738
|
+
}, 350);
|
|
4739
|
+
}
|
|
4740
|
+
syncCountdown() {
|
|
4741
|
+
const timerEl = this.element.querySelector('[data-controller~="midwest-countdown-timer"]');
|
|
4742
|
+
if (!timerEl)
|
|
4743
|
+
return;
|
|
4744
|
+
const timer = this.application.getControllerForElementAndIdentifier(
|
|
4745
|
+
timerEl,
|
|
4746
|
+
"midwest-countdown-timer"
|
|
4747
|
+
);
|
|
4748
|
+
if (!timer)
|
|
4749
|
+
return;
|
|
4750
|
+
const isTop = this.element === this.element.parentElement?.firstElementChild;
|
|
4751
|
+
if (isTop) {
|
|
4752
|
+
timer.resume();
|
|
4753
|
+
} else {
|
|
4754
|
+
timer.pause();
|
|
4755
|
+
}
|
|
4756
|
+
}
|
|
4757
|
+
}
|
|
4758
|
+
|
|
4759
|
+
class HorizontalScroll extends Controller {
|
|
4760
|
+
resizeObserver = null;
|
|
4761
|
+
connect() {
|
|
4762
|
+
this.updateOverflow();
|
|
4763
|
+
this.element.addEventListener("scroll", this.updateOverflow);
|
|
4764
|
+
this.resizeObserver = new ResizeObserver(this.updateOverflow);
|
|
4765
|
+
this.resizeObserver.observe(this.element);
|
|
4766
|
+
}
|
|
4767
|
+
disconnect() {
|
|
4768
|
+
this.element.removeEventListener("scroll", this.updateOverflow);
|
|
4769
|
+
this.resizeObserver?.disconnect();
|
|
4770
|
+
this.resizeObserver = null;
|
|
4771
|
+
}
|
|
4772
|
+
updateOverflow = () => {
|
|
4773
|
+
const el = this.element;
|
|
4774
|
+
el.classList.toggle("can-scroll-left", el.scrollLeft > 0);
|
|
4775
|
+
el.classList.toggle("can-scroll-right", el.scrollLeft < el.scrollWidth - el.clientWidth - 1);
|
|
4776
|
+
};
|
|
4777
|
+
}
|
|
4778
|
+
|
|
4779
|
+
class Table extends Controller {
|
|
4780
|
+
static targets = ["selectAll", "rowCheckbox"];
|
|
4781
|
+
toggleAll() {
|
|
4782
|
+
const checked = this.selectAllTarget.checked;
|
|
4783
|
+
this.rowCheckboxTargets.forEach((cb) => {
|
|
4784
|
+
cb.checked = checked;
|
|
4785
|
+
});
|
|
4786
|
+
}
|
|
4787
|
+
updateSelectAll() {
|
|
4788
|
+
if (!this.hasSelectAllTarget)
|
|
4789
|
+
return;
|
|
4790
|
+
const total = this.rowCheckboxTargets.length;
|
|
4791
|
+
const checkedCount = this.rowCheckboxTargets.filter((cb) => cb.checked).length;
|
|
4792
|
+
if (checkedCount === 0) {
|
|
4793
|
+
this.selectAllTarget.checked = false;
|
|
4794
|
+
this.selectAllTarget.indeterminate = false;
|
|
4795
|
+
} else if (checkedCount === total) {
|
|
4796
|
+
this.selectAllTarget.checked = true;
|
|
4797
|
+
this.selectAllTarget.indeterminate = false;
|
|
4798
|
+
} else {
|
|
4799
|
+
this.selectAllTarget.checked = false;
|
|
4800
|
+
this.selectAllTarget.indeterminate = true;
|
|
4801
|
+
}
|
|
4802
|
+
}
|
|
4803
|
+
}
|
|
4804
|
+
|
|
4805
|
+
class LoadMore extends Controller {
|
|
4806
|
+
static values = {
|
|
4807
|
+
url: String,
|
|
4808
|
+
tbodyId: String
|
|
4809
|
+
};
|
|
4810
|
+
observer = null;
|
|
4811
|
+
loading = false;
|
|
4812
|
+
connect() {
|
|
4813
|
+
if (!this.urlValue)
|
|
4814
|
+
return;
|
|
4815
|
+
this.observer = new IntersectionObserver(
|
|
4816
|
+
(entries) => {
|
|
4817
|
+
if (entries[0].isIntersecting)
|
|
4818
|
+
this.load();
|
|
4819
|
+
},
|
|
4820
|
+
// Start fetching 300px before the sentinel enters the viewport so rows
|
|
4821
|
+
// appear before the user actually reaches the bottom.
|
|
4822
|
+
{ rootMargin: "0px 0px 300px 0px" }
|
|
4823
|
+
);
|
|
4824
|
+
this.observer.observe(this.element);
|
|
4825
|
+
}
|
|
4826
|
+
disconnect() {
|
|
4827
|
+
this.observer?.disconnect();
|
|
4828
|
+
this.observer = null;
|
|
4829
|
+
}
|
|
4830
|
+
async load() {
|
|
4831
|
+
if (this.loading)
|
|
4832
|
+
return;
|
|
4833
|
+
this.loading = true;
|
|
4834
|
+
this.observer?.disconnect();
|
|
4835
|
+
try {
|
|
4836
|
+
const response = await fetch(this.urlValue, {
|
|
4837
|
+
headers: {
|
|
4838
|
+
Accept: "text/html",
|
|
4839
|
+
"X-Requested-With": "XMLHttpRequest"
|
|
4840
|
+
},
|
|
4841
|
+
credentials: "same-origin"
|
|
4842
|
+
});
|
|
4843
|
+
if (!response.ok)
|
|
4844
|
+
return;
|
|
4845
|
+
const html = await response.text();
|
|
4846
|
+
const doc = new DOMParser().parseFromString(html, "text/html");
|
|
4847
|
+
const tbody = document.getElementById(this.tbodyIdValue);
|
|
4848
|
+
const newRows = Array.from(
|
|
4849
|
+
doc.querySelectorAll(".midwest-table-body tr")
|
|
4850
|
+
);
|
|
4851
|
+
if (!tbody || newRows.length === 0)
|
|
4852
|
+
return;
|
|
4853
|
+
this.element.remove();
|
|
4854
|
+
newRows.forEach((row) => tbody.appendChild(document.importNode(row, true)));
|
|
4855
|
+
} catch {
|
|
4856
|
+
this.loading = false;
|
|
4857
|
+
}
|
|
4858
|
+
}
|
|
4859
|
+
}
|
|
4860
|
+
|
|
4736
4861
|
function registerMidwestControllers(application) {
|
|
4737
4862
|
application.register("midwest-card", Card);
|
|
4738
4863
|
application.register("midwest-banner", Banner);
|
|
@@ -4755,6 +4880,11 @@ function registerMidwestControllers(application) {
|
|
|
4755
4880
|
application.register("midwest-rating", Rating);
|
|
4756
4881
|
application.register("midwest-autocomplete", Autocomplete);
|
|
4757
4882
|
application.register("midwest-image", Image);
|
|
4883
|
+
application.register("midwest-color-picker", ColorPicker);
|
|
4884
|
+
application.register("midwest-notification", Notification);
|
|
4885
|
+
application.register("midwest-horizontal-scroll", HorizontalScroll);
|
|
4886
|
+
application.register("midwest-table", Table);
|
|
4887
|
+
application.register("midwest-load-more", LoadMore);
|
|
4758
4888
|
}
|
|
4759
4889
|
|
|
4760
4890
|
export { Banner, Card, Chart, CountdownTimer, registerMidwestControllers };
|