@unabridged/midwest 0.22.0 → 0.23.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 +64 -1
- package/app/assets/javascript/midwest/index.ts +8 -0
- package/app/assets/javascript/midwest/keyboard_navigation.ts +68 -0
- package/app/assets/javascript/midwest.js +476 -37
- package/app/assets/javascript/midwest.js.map +1 -1
- package/app/assets/stylesheets/midwest.css +1 -1
- package/app/assets/stylesheets/midwest.tailwind.css +13 -1
- package/dist/css/midwest.css +1 -1
- package/dist/javascript/collection/app/assets/javascript/midwest/index.js +8 -0
- package/dist/javascript/collection/app/assets/javascript/midwest/index.js.map +1 -1
- package/dist/javascript/collection/app/assets/javascript/midwest/keyboard_navigation.js +42 -0
- package/dist/javascript/collection/app/assets/javascript/midwest/keyboard_navigation.js.map +1 -0
- package/dist/javascript/collection/app/components/midwest/dropdown_component/dropdown_component_controller.js +8 -37
- package/dist/javascript/collection/app/components/midwest/dropdown_component/dropdown_component_controller.js.map +1 -1
- package/dist/javascript/collection/app/components/midwest/focus_trap_component/focus_trap_component_controller.js +83 -0
- package/dist/javascript/collection/app/components/midwest/focus_trap_component/focus_trap_component_controller.js.map +1 -0
- package/dist/javascript/collection/app/components/midwest/form/switch_component/theme_switch_controller.js +42 -0
- package/dist/javascript/collection/app/components/midwest/form/switch_component/theme_switch_controller.js.map +1 -0
- package/dist/javascript/collection/app/components/midwest/intersection_observer_component/intersection_observer_component_controller.js +43 -0
- package/dist/javascript/collection/app/components/midwest/intersection_observer_component/intersection_observer_component_controller.js.map +1 -0
- package/dist/javascript/collection/app/components/midwest/onboarding_component/onboarding_component_controller.js +273 -0
- package/dist/javascript/collection/app/components/midwest/onboarding_component/onboarding_component_controller.js.map +1 -0
- package/dist/javascript/midwest.js +476 -37
- package/dist/javascript/midwest.js.map +1 -1
- package/package.json +1 -1
|
@@ -2511,6 +2511,46 @@ const computePosition = (reference, floating, options) => {
|
|
|
2511
2511
|
});
|
|
2512
2512
|
};
|
|
2513
2513
|
|
|
2514
|
+
const NAVIGATION_KEYS = ["ArrowDown", "ArrowUp", "Home", "End", "Escape", "Tab"];
|
|
2515
|
+
function handleRovingFocusKeydown(event, items, options = {}) {
|
|
2516
|
+
if (items.length === 0)
|
|
2517
|
+
return false;
|
|
2518
|
+
if (!NAVIGATION_KEYS.includes(event.key))
|
|
2519
|
+
return false;
|
|
2520
|
+
const { onEscape, onTab, loop = true } = options;
|
|
2521
|
+
const current = items.findIndex((item) => item === document.activeElement);
|
|
2522
|
+
switch (event.key) {
|
|
2523
|
+
case "ArrowDown": {
|
|
2524
|
+
event.preventDefault();
|
|
2525
|
+
const next = current < items.length - 1 ? current + 1 : loop ? 0 : items.length - 1;
|
|
2526
|
+
items[next].focus();
|
|
2527
|
+
break;
|
|
2528
|
+
}
|
|
2529
|
+
case "ArrowUp": {
|
|
2530
|
+
event.preventDefault();
|
|
2531
|
+
const prev = current > 0 ? current - 1 : loop ? items.length - 1 : 0;
|
|
2532
|
+
items[prev].focus();
|
|
2533
|
+
break;
|
|
2534
|
+
}
|
|
2535
|
+
case "Home":
|
|
2536
|
+
event.preventDefault();
|
|
2537
|
+
items[0].focus();
|
|
2538
|
+
break;
|
|
2539
|
+
case "End":
|
|
2540
|
+
event.preventDefault();
|
|
2541
|
+
items[items.length - 1].focus();
|
|
2542
|
+
break;
|
|
2543
|
+
case "Escape":
|
|
2544
|
+
event.preventDefault();
|
|
2545
|
+
onEscape?.();
|
|
2546
|
+
break;
|
|
2547
|
+
case "Tab":
|
|
2548
|
+
onTab?.();
|
|
2549
|
+
break;
|
|
2550
|
+
}
|
|
2551
|
+
return true;
|
|
2552
|
+
}
|
|
2553
|
+
|
|
2514
2554
|
class Dropdown extends Controller {
|
|
2515
2555
|
static values = {
|
|
2516
2556
|
computed: { type: Boolean, default: false },
|
|
@@ -2564,45 +2604,15 @@ class Dropdown extends Controller {
|
|
|
2564
2604
|
}
|
|
2565
2605
|
};
|
|
2566
2606
|
handleKeydown = (event) => {
|
|
2567
|
-
const
|
|
2568
|
-
|
|
2569
|
-
return;
|
|
2570
|
-
if (!["ArrowDown", "ArrowUp", "Home", "End", "Escape", "Tab"].includes(event.key))
|
|
2571
|
-
return;
|
|
2572
|
-
event.stopPropagation();
|
|
2573
|
-
const currentIndex = items.findIndex((item) => item === document.activeElement);
|
|
2574
|
-
switch (event.key) {
|
|
2575
|
-
case "ArrowDown": {
|
|
2576
|
-
event.preventDefault();
|
|
2577
|
-
const next = currentIndex < items.length - 1 ? currentIndex + 1 : 0;
|
|
2578
|
-
items[next].focus();
|
|
2579
|
-
break;
|
|
2580
|
-
}
|
|
2581
|
-
case "ArrowUp": {
|
|
2582
|
-
event.preventDefault();
|
|
2583
|
-
const prev = currentIndex > 0 ? currentIndex - 1 : items.length - 1;
|
|
2584
|
-
items[prev].focus();
|
|
2585
|
-
break;
|
|
2586
|
-
}
|
|
2587
|
-
case "Home":
|
|
2588
|
-
event.preventDefault();
|
|
2589
|
-
items[0].focus();
|
|
2590
|
-
break;
|
|
2591
|
-
case "End":
|
|
2592
|
-
event.preventDefault();
|
|
2593
|
-
items[items.length - 1].focus();
|
|
2594
|
-
break;
|
|
2595
|
-
case "Escape":
|
|
2596
|
-
event.preventDefault();
|
|
2607
|
+
const handled = handleRovingFocusKeydown(event, this.menuItems, {
|
|
2608
|
+
onEscape: () => {
|
|
2597
2609
|
this.element.hidePopover();
|
|
2598
2610
|
requestAnimationFrame(() => this.buttonEl?.focus());
|
|
2599
|
-
|
|
2600
|
-
|
|
2601
|
-
|
|
2602
|
-
|
|
2603
|
-
|
|
2604
|
-
return;
|
|
2605
|
-
}
|
|
2611
|
+
},
|
|
2612
|
+
onTab: () => this.element.hidePopover()
|
|
2613
|
+
});
|
|
2614
|
+
if (handled)
|
|
2615
|
+
event.stopPropagation();
|
|
2606
2616
|
};
|
|
2607
2617
|
get menuItems() {
|
|
2608
2618
|
return Array.from(
|
|
@@ -6555,6 +6565,431 @@ class TimePicker extends Controller {
|
|
|
6555
6565
|
}
|
|
6556
6566
|
}
|
|
6557
6567
|
|
|
6568
|
+
class ViewportObserver extends Controller {
|
|
6569
|
+
static values = {
|
|
6570
|
+
threshold: { type: Number, default: 0 },
|
|
6571
|
+
rootMargin: { type: String, default: "0px" },
|
|
6572
|
+
once: { type: Boolean, default: false },
|
|
6573
|
+
visibleClass: { type: String, default: "" }
|
|
6574
|
+
};
|
|
6575
|
+
observer = null;
|
|
6576
|
+
connect() {
|
|
6577
|
+
this.observer = new IntersectionObserver(
|
|
6578
|
+
(entries) => {
|
|
6579
|
+
for (const entry of entries)
|
|
6580
|
+
this.handle(entry);
|
|
6581
|
+
},
|
|
6582
|
+
{ threshold: this.thresholdValue, rootMargin: this.rootMarginValue }
|
|
6583
|
+
);
|
|
6584
|
+
this.observer.observe(this.element);
|
|
6585
|
+
}
|
|
6586
|
+
disconnect() {
|
|
6587
|
+
this.observer?.disconnect();
|
|
6588
|
+
this.observer = null;
|
|
6589
|
+
}
|
|
6590
|
+
handle(entry) {
|
|
6591
|
+
if (entry.isIntersecting) {
|
|
6592
|
+
this.element.setAttribute("data-intersecting", "true");
|
|
6593
|
+
if (this.visibleClassValue)
|
|
6594
|
+
this.element.classList.add(this.visibleClassValue);
|
|
6595
|
+
this.dispatch("enter", { detail: { entry }, bubbles: true });
|
|
6596
|
+
if (this.onceValue)
|
|
6597
|
+
this.disconnect();
|
|
6598
|
+
} else {
|
|
6599
|
+
this.element.setAttribute("data-intersecting", "false");
|
|
6600
|
+
if (this.visibleClassValue)
|
|
6601
|
+
this.element.classList.remove(this.visibleClassValue);
|
|
6602
|
+
this.dispatch("leave", { detail: { entry }, bubbles: true });
|
|
6603
|
+
}
|
|
6604
|
+
}
|
|
6605
|
+
}
|
|
6606
|
+
|
|
6607
|
+
const FOCUSABLE$1 = [
|
|
6608
|
+
"a[href]",
|
|
6609
|
+
"button:not([disabled])",
|
|
6610
|
+
"textarea:not([disabled])",
|
|
6611
|
+
"input:not([disabled])",
|
|
6612
|
+
"select:not([disabled])",
|
|
6613
|
+
'[tabindex]:not([tabindex="-1"])'
|
|
6614
|
+
].join(", ");
|
|
6615
|
+
class FocusTrap extends Controller {
|
|
6616
|
+
static values = {
|
|
6617
|
+
active: { type: Boolean, default: true },
|
|
6618
|
+
returnFocus: { type: Boolean, default: true },
|
|
6619
|
+
initialFocus: { type: String, default: "" }
|
|
6620
|
+
};
|
|
6621
|
+
engaged = false;
|
|
6622
|
+
previousFocus = null;
|
|
6623
|
+
// Fires on connect for the initial value and on every subsequent change,
|
|
6624
|
+
// so it is the single entry point for engaging/releasing the trap.
|
|
6625
|
+
activeValueChanged(active) {
|
|
6626
|
+
if (active)
|
|
6627
|
+
this.engage();
|
|
6628
|
+
else
|
|
6629
|
+
this.release();
|
|
6630
|
+
}
|
|
6631
|
+
disconnect() {
|
|
6632
|
+
this.release();
|
|
6633
|
+
}
|
|
6634
|
+
engage() {
|
|
6635
|
+
if (this.engaged)
|
|
6636
|
+
return;
|
|
6637
|
+
this.engaged = true;
|
|
6638
|
+
this.previousFocus = document.activeElement;
|
|
6639
|
+
this.element.addEventListener("keydown", this.handleKeydown);
|
|
6640
|
+
requestAnimationFrame(() => {
|
|
6641
|
+
if (this.engaged)
|
|
6642
|
+
this.focusInitial();
|
|
6643
|
+
});
|
|
6644
|
+
}
|
|
6645
|
+
release() {
|
|
6646
|
+
if (!this.engaged)
|
|
6647
|
+
return;
|
|
6648
|
+
this.engaged = false;
|
|
6649
|
+
this.element.removeEventListener("keydown", this.handleKeydown);
|
|
6650
|
+
if (this.returnFocusValue)
|
|
6651
|
+
this.previousFocus?.focus();
|
|
6652
|
+
this.previousFocus = null;
|
|
6653
|
+
}
|
|
6654
|
+
focusInitial() {
|
|
6655
|
+
const target = this.initialFocusValue ? this.element.querySelector(this.initialFocusValue) : this.focusable()[0];
|
|
6656
|
+
target?.focus();
|
|
6657
|
+
}
|
|
6658
|
+
focusable() {
|
|
6659
|
+
return Array.from(
|
|
6660
|
+
this.element.querySelectorAll(FOCUSABLE$1)
|
|
6661
|
+
).filter((el) => !el.closest("[inert]"));
|
|
6662
|
+
}
|
|
6663
|
+
handleKeydown = (event) => {
|
|
6664
|
+
if (event.key !== "Tab")
|
|
6665
|
+
return;
|
|
6666
|
+
const focusable = this.focusable();
|
|
6667
|
+
if (focusable.length === 0) {
|
|
6668
|
+
event.preventDefault();
|
|
6669
|
+
return;
|
|
6670
|
+
}
|
|
6671
|
+
const first = focusable[0];
|
|
6672
|
+
const last = focusable[focusable.length - 1];
|
|
6673
|
+
const active = document.activeElement;
|
|
6674
|
+
if (event.shiftKey) {
|
|
6675
|
+
if (active === first || !this.element.contains(active)) {
|
|
6676
|
+
event.preventDefault();
|
|
6677
|
+
last.focus();
|
|
6678
|
+
}
|
|
6679
|
+
} else if (active === last || !this.element.contains(active)) {
|
|
6680
|
+
event.preventDefault();
|
|
6681
|
+
first.focus();
|
|
6682
|
+
}
|
|
6683
|
+
};
|
|
6684
|
+
}
|
|
6685
|
+
|
|
6686
|
+
const FOCUSABLE = [
|
|
6687
|
+
"a[href]",
|
|
6688
|
+
"button:not([disabled])",
|
|
6689
|
+
"textarea:not([disabled])",
|
|
6690
|
+
"input:not([disabled])",
|
|
6691
|
+
"select:not([disabled])",
|
|
6692
|
+
'[tabindex]:not([tabindex="-1"])'
|
|
6693
|
+
].join(", ");
|
|
6694
|
+
const GAP = 12;
|
|
6695
|
+
class Onboarding extends Controller {
|
|
6696
|
+
static targets = ["step", "overlay", "card", "title", "body", "back", "next", "done", "arrow"];
|
|
6697
|
+
static values = {
|
|
6698
|
+
autoOpen: { type: Boolean, default: false },
|
|
6699
|
+
confirm: { type: String, default: "" }
|
|
6700
|
+
};
|
|
6701
|
+
steps = [];
|
|
6702
|
+
index = 0;
|
|
6703
|
+
active = false;
|
|
6704
|
+
previousFocus = null;
|
|
6705
|
+
highlighted = null;
|
|
6706
|
+
connect() {
|
|
6707
|
+
this.steps = this.stepTargets.map((el) => ({
|
|
6708
|
+
el,
|
|
6709
|
+
name: el.dataset.name ?? "",
|
|
6710
|
+
selector: el.dataset.selector ?? "",
|
|
6711
|
+
position: el.dataset.position ?? "auto",
|
|
6712
|
+
title: el.dataset.title ?? "",
|
|
6713
|
+
nextText: el.dataset.nextText ?? "Next",
|
|
6714
|
+
backText: el.dataset.backText ?? "Back",
|
|
6715
|
+
completeText: el.dataset.completeText ?? "Done",
|
|
6716
|
+
canClickTarget: el.dataset.canClickTarget === "true"
|
|
6717
|
+
}));
|
|
6718
|
+
window.addEventListener("midwest-onboarding:open", this.handleOpen);
|
|
6719
|
+
if (this.autoOpenValue)
|
|
6720
|
+
this.start();
|
|
6721
|
+
}
|
|
6722
|
+
disconnect() {
|
|
6723
|
+
window.removeEventListener("midwest-onboarding:open", this.handleOpen);
|
|
6724
|
+
this.teardown();
|
|
6725
|
+
}
|
|
6726
|
+
start() {
|
|
6727
|
+
if (this.active || this.steps.length === 0)
|
|
6728
|
+
return;
|
|
6729
|
+
this.active = true;
|
|
6730
|
+
this.previousFocus = document.activeElement;
|
|
6731
|
+
this.overlayTarget.hidden = false;
|
|
6732
|
+
this.cardTarget.hidden = false;
|
|
6733
|
+
document.addEventListener("keydown", this.handleKeydown, true);
|
|
6734
|
+
window.addEventListener("resize", this.reposition);
|
|
6735
|
+
window.addEventListener("scroll", this.reposition, true);
|
|
6736
|
+
this.dispatch("start", { bubbles: true });
|
|
6737
|
+
this.render(0);
|
|
6738
|
+
}
|
|
6739
|
+
next() {
|
|
6740
|
+
if (this.index < this.steps.length - 1)
|
|
6741
|
+
this.render(this.index + 1);
|
|
6742
|
+
else
|
|
6743
|
+
this.complete();
|
|
6744
|
+
}
|
|
6745
|
+
back() {
|
|
6746
|
+
if (this.index > 0)
|
|
6747
|
+
this.render(this.index - 1);
|
|
6748
|
+
}
|
|
6749
|
+
// Jump to a step by its `name`.
|
|
6750
|
+
show(name) {
|
|
6751
|
+
const target = this.steps.findIndex((step) => step.name === name);
|
|
6752
|
+
if (target >= 0) {
|
|
6753
|
+
if (!this.active)
|
|
6754
|
+
this.start();
|
|
6755
|
+
this.render(target);
|
|
6756
|
+
}
|
|
6757
|
+
}
|
|
6758
|
+
complete() {
|
|
6759
|
+
if (!this.active)
|
|
6760
|
+
return;
|
|
6761
|
+
this.teardown();
|
|
6762
|
+
this.dispatch("complete", { bubbles: true });
|
|
6763
|
+
}
|
|
6764
|
+
cancel() {
|
|
6765
|
+
if (!this.active)
|
|
6766
|
+
return;
|
|
6767
|
+
if (this.confirmValue && !window.confirm(this.confirmValue))
|
|
6768
|
+
return;
|
|
6769
|
+
this.teardown();
|
|
6770
|
+
this.dispatch("cancel", { bubbles: true });
|
|
6771
|
+
}
|
|
6772
|
+
render(index) {
|
|
6773
|
+
this.index = index;
|
|
6774
|
+
const step = this.steps[index];
|
|
6775
|
+
this.clearHighlight();
|
|
6776
|
+
this.titleTarget.textContent = step.title;
|
|
6777
|
+
this.renderBody(step);
|
|
6778
|
+
this.renderButtons(step, index);
|
|
6779
|
+
const target = step.selector ? document.querySelector(step.selector) : null;
|
|
6780
|
+
if (target) {
|
|
6781
|
+
this.highlight(target, step.canClickTarget);
|
|
6782
|
+
this.position(target, step.position);
|
|
6783
|
+
} else {
|
|
6784
|
+
this.center();
|
|
6785
|
+
}
|
|
6786
|
+
this.dispatch("show", { detail: { index, name: step.name }, bubbles: true });
|
|
6787
|
+
this.focusCard();
|
|
6788
|
+
}
|
|
6789
|
+
renderBody(step) {
|
|
6790
|
+
this.bodyTarget.replaceChildren();
|
|
6791
|
+
const template = step.el.querySelector("template");
|
|
6792
|
+
if (template)
|
|
6793
|
+
this.bodyTarget.appendChild(template.content.cloneNode(true));
|
|
6794
|
+
}
|
|
6795
|
+
renderButtons(step, index) {
|
|
6796
|
+
const isFirst = index === 0;
|
|
6797
|
+
const isLast = index === this.steps.length - 1;
|
|
6798
|
+
this.backTarget.textContent = step.backText;
|
|
6799
|
+
this.backTarget.style.visibility = isFirst ? "hidden" : "";
|
|
6800
|
+
this.nextTarget.textContent = step.nextText;
|
|
6801
|
+
this.nextTarget.style.display = isLast ? "none" : "";
|
|
6802
|
+
this.doneTarget.textContent = step.completeText;
|
|
6803
|
+
this.doneTarget.style.display = isLast ? "" : "none";
|
|
6804
|
+
}
|
|
6805
|
+
highlight(target, canClickTarget) {
|
|
6806
|
+
target.classList.add("midwest-onboarding-highlight");
|
|
6807
|
+
target.classList.toggle("is-locked", !canClickTarget);
|
|
6808
|
+
this.highlighted = target;
|
|
6809
|
+
target.scrollIntoView({ behavior: "smooth", block: "center" });
|
|
6810
|
+
}
|
|
6811
|
+
clearHighlight() {
|
|
6812
|
+
this.highlighted?.classList.remove("midwest-onboarding-highlight", "is-locked");
|
|
6813
|
+
this.highlighted = null;
|
|
6814
|
+
}
|
|
6815
|
+
// Places the card against the target, choosing a side when `position` is auto.
|
|
6816
|
+
position(target, position) {
|
|
6817
|
+
const rect = target.getBoundingClientRect();
|
|
6818
|
+
const card = this.cardTarget.getBoundingClientRect();
|
|
6819
|
+
const vw = window.innerWidth;
|
|
6820
|
+
const vh = window.innerHeight;
|
|
6821
|
+
let placement = position;
|
|
6822
|
+
if (placement === "auto") {
|
|
6823
|
+
if (rect.bottom + card.height + GAP <= vh)
|
|
6824
|
+
placement = "bottom";
|
|
6825
|
+
else if (rect.top - card.height - GAP >= 0)
|
|
6826
|
+
placement = "top";
|
|
6827
|
+
else if (rect.right + card.width + GAP <= vw)
|
|
6828
|
+
placement = "right";
|
|
6829
|
+
else
|
|
6830
|
+
placement = "left";
|
|
6831
|
+
}
|
|
6832
|
+
let top;
|
|
6833
|
+
let left;
|
|
6834
|
+
switch (placement) {
|
|
6835
|
+
case "top":
|
|
6836
|
+
top = rect.top - card.height - GAP;
|
|
6837
|
+
left = rect.left + rect.width / 2 - card.width / 2;
|
|
6838
|
+
break;
|
|
6839
|
+
case "left":
|
|
6840
|
+
top = rect.top + rect.height / 2 - card.height / 2;
|
|
6841
|
+
left = rect.left - card.width - GAP;
|
|
6842
|
+
break;
|
|
6843
|
+
case "right":
|
|
6844
|
+
top = rect.top + rect.height / 2 - card.height / 2;
|
|
6845
|
+
left = rect.right + GAP;
|
|
6846
|
+
break;
|
|
6847
|
+
default:
|
|
6848
|
+
top = rect.bottom + GAP;
|
|
6849
|
+
left = rect.left + rect.width / 2 - card.width / 2;
|
|
6850
|
+
break;
|
|
6851
|
+
}
|
|
6852
|
+
left = Math.max(8, Math.min(left, vw - card.width - 8));
|
|
6853
|
+
top = Math.max(8, Math.min(top, vh - card.height - 8));
|
|
6854
|
+
this.cardTarget.style.top = `${top}px`;
|
|
6855
|
+
this.cardTarget.style.left = `${left}px`;
|
|
6856
|
+
this.cardTarget.dataset.placement = placement;
|
|
6857
|
+
this.positionArrow(placement, rect, top, left);
|
|
6858
|
+
}
|
|
6859
|
+
// Nudges the arrow so it points at the target's center, not the card's.
|
|
6860
|
+
positionArrow(placement, rect, top, left) {
|
|
6861
|
+
const card = this.cardTarget.getBoundingClientRect();
|
|
6862
|
+
const arrow = this.arrowTarget.style;
|
|
6863
|
+
if (placement === "top" || placement === "bottom") {
|
|
6864
|
+
const targetCenter = rect.left + rect.width / 2;
|
|
6865
|
+
const offset = Math.max(12, Math.min(targetCenter - left, card.width - 12));
|
|
6866
|
+
arrow.left = `${offset}px`;
|
|
6867
|
+
arrow.top = "";
|
|
6868
|
+
} else {
|
|
6869
|
+
const targetCenter = rect.top + rect.height / 2;
|
|
6870
|
+
const offset = Math.max(12, Math.min(targetCenter - top, card.height - 12));
|
|
6871
|
+
arrow.top = `${offset}px`;
|
|
6872
|
+
arrow.left = "";
|
|
6873
|
+
}
|
|
6874
|
+
}
|
|
6875
|
+
center() {
|
|
6876
|
+
const card = this.cardTarget.getBoundingClientRect();
|
|
6877
|
+
this.cardTarget.style.top = `${Math.max(8, window.innerHeight / 2 - card.height / 2)}px`;
|
|
6878
|
+
this.cardTarget.style.left = `${Math.max(8, window.innerWidth / 2 - card.width / 2)}px`;
|
|
6879
|
+
this.cardTarget.dataset.placement = "center";
|
|
6880
|
+
}
|
|
6881
|
+
focusCard() {
|
|
6882
|
+
requestAnimationFrame(() => {
|
|
6883
|
+
if (this.active)
|
|
6884
|
+
this.cardTarget.focus();
|
|
6885
|
+
});
|
|
6886
|
+
}
|
|
6887
|
+
teardown() {
|
|
6888
|
+
this.active = false;
|
|
6889
|
+
this.clearHighlight();
|
|
6890
|
+
this.overlayTarget.hidden = true;
|
|
6891
|
+
this.cardTarget.hidden = true;
|
|
6892
|
+
document.removeEventListener("keydown", this.handleKeydown, true);
|
|
6893
|
+
window.removeEventListener("resize", this.reposition);
|
|
6894
|
+
window.removeEventListener("scroll", this.reposition, true);
|
|
6895
|
+
this.previousFocus?.focus();
|
|
6896
|
+
this.previousFocus = null;
|
|
6897
|
+
}
|
|
6898
|
+
handleOpen = (event) => {
|
|
6899
|
+
const detail = event.detail ?? {};
|
|
6900
|
+
const id = String(detail.id ?? "").replace("#", "");
|
|
6901
|
+
if (id === this.element.id)
|
|
6902
|
+
this.start();
|
|
6903
|
+
};
|
|
6904
|
+
reposition = () => {
|
|
6905
|
+
if (!this.active)
|
|
6906
|
+
return;
|
|
6907
|
+
const step = this.steps[this.index];
|
|
6908
|
+
const target = step.selector ? document.querySelector(step.selector) : null;
|
|
6909
|
+
if (target)
|
|
6910
|
+
this.position(target, step.position);
|
|
6911
|
+
else
|
|
6912
|
+
this.center();
|
|
6913
|
+
};
|
|
6914
|
+
handleKeydown = (event) => {
|
|
6915
|
+
if (!this.active)
|
|
6916
|
+
return;
|
|
6917
|
+
switch (event.key) {
|
|
6918
|
+
case "Escape":
|
|
6919
|
+
event.preventDefault();
|
|
6920
|
+
this.cancel();
|
|
6921
|
+
break;
|
|
6922
|
+
case "ArrowRight":
|
|
6923
|
+
event.preventDefault();
|
|
6924
|
+
this.next();
|
|
6925
|
+
break;
|
|
6926
|
+
case "ArrowLeft":
|
|
6927
|
+
event.preventDefault();
|
|
6928
|
+
this.back();
|
|
6929
|
+
break;
|
|
6930
|
+
case "Tab":
|
|
6931
|
+
this.trapTab(event);
|
|
6932
|
+
break;
|
|
6933
|
+
}
|
|
6934
|
+
};
|
|
6935
|
+
// Keep Tab focus within the card while the tour is active.
|
|
6936
|
+
trapTab(event) {
|
|
6937
|
+
const focusable = Array.from(this.cardTarget.querySelectorAll(FOCUSABLE)).filter((el) => !el.hidden && el.offsetParent !== null && getComputedStyle(el).visibility !== "hidden");
|
|
6938
|
+
if (focusable.length === 0)
|
|
6939
|
+
return;
|
|
6940
|
+
const first = focusable[0];
|
|
6941
|
+
const last = focusable[focusable.length - 1];
|
|
6942
|
+
const active = document.activeElement;
|
|
6943
|
+
if (event.shiftKey) {
|
|
6944
|
+
if (active === first || active === this.cardTarget || !this.cardTarget.contains(active)) {
|
|
6945
|
+
event.preventDefault();
|
|
6946
|
+
last.focus();
|
|
6947
|
+
}
|
|
6948
|
+
} else if (active === last || !this.cardTarget.contains(active)) {
|
|
6949
|
+
event.preventDefault();
|
|
6950
|
+
first.focus();
|
|
6951
|
+
}
|
|
6952
|
+
}
|
|
6953
|
+
}
|
|
6954
|
+
|
|
6955
|
+
class ThemeSwitch extends Controller {
|
|
6956
|
+
static values = {
|
|
6957
|
+
storageKey: { type: String, default: "midwest-theme" }
|
|
6958
|
+
};
|
|
6959
|
+
connect() {
|
|
6960
|
+
const isDark = this.resolveTheme() === "dark";
|
|
6961
|
+
this.apply(isDark);
|
|
6962
|
+
this.element.checked = isDark;
|
|
6963
|
+
}
|
|
6964
|
+
toggle() {
|
|
6965
|
+
const isDark = this.element.checked;
|
|
6966
|
+
this.apply(isDark);
|
|
6967
|
+
this.persist(isDark ? "dark" : "light");
|
|
6968
|
+
}
|
|
6969
|
+
resolveTheme() {
|
|
6970
|
+
const stored = this.read();
|
|
6971
|
+
if (stored === "dark" || stored === "light")
|
|
6972
|
+
return stored;
|
|
6973
|
+
return window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
|
|
6974
|
+
}
|
|
6975
|
+
apply(isDark) {
|
|
6976
|
+
document.documentElement.classList.toggle("dark", isDark);
|
|
6977
|
+
}
|
|
6978
|
+
read() {
|
|
6979
|
+
try {
|
|
6980
|
+
return window.localStorage.getItem(this.storageKeyValue);
|
|
6981
|
+
} catch {
|
|
6982
|
+
return null;
|
|
6983
|
+
}
|
|
6984
|
+
}
|
|
6985
|
+
persist(theme) {
|
|
6986
|
+
try {
|
|
6987
|
+
window.localStorage.setItem(this.storageKeyValue, theme);
|
|
6988
|
+
} catch {
|
|
6989
|
+
}
|
|
6990
|
+
}
|
|
6991
|
+
}
|
|
6992
|
+
|
|
6558
6993
|
function registerMidwestControllers(application) {
|
|
6559
6994
|
application.register("midwest-card", Card);
|
|
6560
6995
|
application.register("midwest-banner", Banner);
|
|
@@ -6590,6 +7025,10 @@ function registerMidwestControllers(application) {
|
|
|
6590
7025
|
application.register("midwest-code-block", CodeBlock);
|
|
6591
7026
|
application.register("midwest-date-picker", DatePicker);
|
|
6592
7027
|
application.register("midwest-time-picker", TimePicker);
|
|
7028
|
+
application.register("midwest-intersection-observer", ViewportObserver);
|
|
7029
|
+
application.register("midwest-focus-trap", FocusTrap);
|
|
7030
|
+
application.register("midwest-onboarding", Onboarding);
|
|
7031
|
+
application.register("midwest-theme-switch", ThemeSwitch);
|
|
6593
7032
|
}
|
|
6594
7033
|
|
|
6595
7034
|
export { Banner, Card, Chart, CountdownTimer, registerMidwestControllers };
|