@unabridged/midwest 0.16.1 → 0.18.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/app/assets/javascript/midwest/index.ts +6 -0
- package/app/assets/javascript/midwest.js +970 -39
- package/app/assets/javascript/midwest.js.map +1 -1
- package/app/assets/stylesheets/midwest.css +1 -1
- package/app/assets/stylesheets/midwest.tailwind.css +6 -1
- package/dist/css/midwest.css +1 -1
- package/dist/javascript/collection/app/assets/javascript/midwest/index.js +6 -0
- package/dist/javascript/collection/app/assets/javascript/midwest/index.js.map +1 -1
- package/dist/javascript/collection/app/components/midwest/carousel_component/carousel_component_controller.js +17 -5
- 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/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 +50 -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/image_component/color_thief.js +490 -0
- package/dist/javascript/collection/app/components/midwest/image_component/color_thief.js.map +1 -0
- package/dist/javascript/collection/app/components/midwest/image_component/image_component_controller.js +47 -0
- package/dist/javascript/collection/app/components/midwest/image_component/image_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/tabs_component/tabs_component_controller.js +15 -1
- package/dist/javascript/collection/app/components/midwest/tabs_component/tabs_component_controller.js.map +1 -1
- package/dist/javascript/midwest.d.ts +5 -0
- package/dist/javascript/midwest.js +970 -39
- package/dist/javascript/midwest.js.map +1 -1
- package/package.json +1 -1
|
@@ -549,13 +549,16 @@ class Banner extends Controller {
|
|
|
549
549
|
}
|
|
550
550
|
|
|
551
551
|
class Input extends Controller {
|
|
552
|
-
static targets = ["input", "counter"];
|
|
552
|
+
static targets = ["input", "counter", "capsLockWarning"];
|
|
553
553
|
updateClearButton = null;
|
|
554
554
|
updateCounter = null;
|
|
555
|
+
updateCapsLock = null;
|
|
556
|
+
hideCapsLock = null;
|
|
555
557
|
connect() {
|
|
556
558
|
this.applyColor();
|
|
557
559
|
this.applySearchClear();
|
|
558
560
|
this.applyCounter();
|
|
561
|
+
this.applyPassword();
|
|
559
562
|
}
|
|
560
563
|
disconnect() {
|
|
561
564
|
if (this.updateClearButton) {
|
|
@@ -566,6 +569,38 @@ class Input extends Controller {
|
|
|
566
569
|
this.inputTarget.removeEventListener("input", this.updateCounter);
|
|
567
570
|
this.updateCounter = null;
|
|
568
571
|
}
|
|
572
|
+
if (this.updateCapsLock) {
|
|
573
|
+
this.inputTarget.removeEventListener("keydown", this.updateCapsLock);
|
|
574
|
+
this.inputTarget.removeEventListener("keyup", this.updateCapsLock);
|
|
575
|
+
this.inputTarget.removeEventListener("mousedown", this.updateCapsLock);
|
|
576
|
+
this.updateCapsLock = null;
|
|
577
|
+
}
|
|
578
|
+
if (this.hideCapsLock) {
|
|
579
|
+
this.inputTarget.removeEventListener("blur", this.hideCapsLock);
|
|
580
|
+
this.hideCapsLock = null;
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
increment() {
|
|
584
|
+
const input = this.inputTarget;
|
|
585
|
+
const step = parseFloat(input.step) || 1;
|
|
586
|
+
const max = input.max !== "" ? parseFloat(input.max) : Infinity;
|
|
587
|
+
const newValue = (parseFloat(input.value) || 0) + step;
|
|
588
|
+
if (newValue <= max) {
|
|
589
|
+
input.value = String(newValue);
|
|
590
|
+
input.dispatchEvent(new Event("input", { bubbles: true }));
|
|
591
|
+
input.dispatchEvent(new Event("change", { bubbles: true }));
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
decrement() {
|
|
595
|
+
const input = this.inputTarget;
|
|
596
|
+
const step = parseFloat(input.step) || 1;
|
|
597
|
+
const min = input.min !== "" ? parseFloat(input.min) : -Infinity;
|
|
598
|
+
const newValue = (parseFloat(input.value) || 0) - step;
|
|
599
|
+
if (newValue >= min) {
|
|
600
|
+
input.value = String(newValue);
|
|
601
|
+
input.dispatchEvent(new Event("input", { bubbles: true }));
|
|
602
|
+
input.dispatchEvent(new Event("change", { bubbles: true }));
|
|
603
|
+
}
|
|
569
604
|
}
|
|
570
605
|
applyColor() {
|
|
571
606
|
if (this.inputTarget.type !== "color") {
|
|
@@ -584,6 +619,20 @@ class Input extends Controller {
|
|
|
584
619
|
};
|
|
585
620
|
this.inputTarget.addEventListener("input", this.updateCounter);
|
|
586
621
|
}
|
|
622
|
+
applyPassword() {
|
|
623
|
+
if (!this.hasCapsLockWarningTarget)
|
|
624
|
+
return;
|
|
625
|
+
this.updateCapsLock = (e) => {
|
|
626
|
+
this.capsLockWarningTarget.hidden = !e.getModifierState("CapsLock");
|
|
627
|
+
};
|
|
628
|
+
this.hideCapsLock = () => {
|
|
629
|
+
this.capsLockWarningTarget.hidden = true;
|
|
630
|
+
};
|
|
631
|
+
this.inputTarget.addEventListener("keydown", this.updateCapsLock);
|
|
632
|
+
this.inputTarget.addEventListener("keyup", this.updateCapsLock);
|
|
633
|
+
this.inputTarget.addEventListener("mousedown", this.updateCapsLock);
|
|
634
|
+
this.inputTarget.addEventListener("blur", this.hideCapsLock);
|
|
635
|
+
}
|
|
587
636
|
applySearchClear() {
|
|
588
637
|
if (this.inputTarget.type !== "search")
|
|
589
638
|
return;
|
|
@@ -3111,16 +3160,23 @@ class Dialog extends Controller {
|
|
|
3111
3160
|
}
|
|
3112
3161
|
|
|
3113
3162
|
class Tabs extends Controller {
|
|
3114
|
-
static targets = ["tab", "panel"];
|
|
3163
|
+
static targets = ["tab", "panel", "list"];
|
|
3115
3164
|
connect() {
|
|
3116
3165
|
const activeIndex = this.tabTargets.findIndex(
|
|
3117
3166
|
(tab) => tab.getAttribute("aria-selected") === "true"
|
|
3118
3167
|
);
|
|
3119
3168
|
this.activateTab(activeIndex >= 0 ? activeIndex : 0);
|
|
3120
3169
|
this.element.addEventListener("keydown", this.handleKeydown);
|
|
3170
|
+
if (this.hasListTarget) {
|
|
3171
|
+
this.updateOverflow();
|
|
3172
|
+
this.listTarget.addEventListener("scroll", this.updateOverflow);
|
|
3173
|
+
}
|
|
3121
3174
|
}
|
|
3122
3175
|
disconnect() {
|
|
3123
3176
|
this.element.removeEventListener("keydown", this.handleKeydown);
|
|
3177
|
+
if (this.hasListTarget) {
|
|
3178
|
+
this.listTarget.removeEventListener("scroll", this.updateOverflow);
|
|
3179
|
+
}
|
|
3124
3180
|
}
|
|
3125
3181
|
select(event) {
|
|
3126
3182
|
const tab = event.currentTarget;
|
|
@@ -3128,6 +3184,13 @@ class Tabs extends Controller {
|
|
|
3128
3184
|
if (index >= 0)
|
|
3129
3185
|
this.activateTab(index);
|
|
3130
3186
|
}
|
|
3187
|
+
updateOverflow = () => {
|
|
3188
|
+
if (!this.hasListTarget)
|
|
3189
|
+
return;
|
|
3190
|
+
const list = this.listTarget;
|
|
3191
|
+
list.classList.toggle("can-scroll-left", list.scrollLeft > 0);
|
|
3192
|
+
list.classList.toggle("can-scroll-right", list.scrollLeft < list.scrollWidth - list.clientWidth - 1);
|
|
3193
|
+
};
|
|
3131
3194
|
handleKeydown = (event) => {
|
|
3132
3195
|
const tabs = this.tabTargets;
|
|
3133
3196
|
if (tabs.length === 0)
|
|
@@ -3230,7 +3293,8 @@ class Carousel extends Controller {
|
|
|
3230
3293
|
static targets = ["track", "prev", "next", "dot"];
|
|
3231
3294
|
static values = {
|
|
3232
3295
|
wizard: { type: Boolean, default: false },
|
|
3233
|
-
autoplay: { type: Number, default: 0 }
|
|
3296
|
+
autoplay: { type: Number, default: 0 },
|
|
3297
|
+
loop: { type: Boolean, default: false }
|
|
3234
3298
|
};
|
|
3235
3299
|
observer = null;
|
|
3236
3300
|
_currentIndex = 0;
|
|
@@ -3266,10 +3330,21 @@ class Carousel extends Controller {
|
|
|
3266
3330
|
this.stopAutoplay();
|
|
3267
3331
|
}
|
|
3268
3332
|
next() {
|
|
3269
|
-
this.
|
|
3333
|
+
const atEnd = this._currentIndex >= this.slides.length - 1;
|
|
3334
|
+
if (this.loopValue && atEnd) {
|
|
3335
|
+
this.slides[0]?.scrollIntoView({ behavior: "smooth", block: "nearest", inline: "nearest" });
|
|
3336
|
+
} else {
|
|
3337
|
+
this.trackTarget.scrollBy({ left: this.trackTarget.clientWidth, behavior: "smooth" });
|
|
3338
|
+
}
|
|
3270
3339
|
}
|
|
3271
3340
|
previous() {
|
|
3272
|
-
|
|
3341
|
+
const atStart = this._currentIndex <= 0;
|
|
3342
|
+
if (this.loopValue && atStart) {
|
|
3343
|
+
const last = this.slides[this.slides.length - 1];
|
|
3344
|
+
last?.scrollIntoView({ behavior: "smooth", block: "nearest", inline: "nearest" });
|
|
3345
|
+
} else {
|
|
3346
|
+
this.trackTarget.scrollBy({ left: -this.trackTarget.clientWidth, behavior: "smooth" });
|
|
3347
|
+
}
|
|
3273
3348
|
}
|
|
3274
3349
|
goto(event) {
|
|
3275
3350
|
const button = event.currentTarget;
|
|
@@ -3436,11 +3511,11 @@ class Carousel extends Controller {
|
|
|
3436
3511
|
const atStart = track.scrollLeft <= 0;
|
|
3437
3512
|
const atEnd = track.scrollLeft >= track.scrollWidth - track.clientWidth - 1;
|
|
3438
3513
|
if (this.hasPrevTarget)
|
|
3439
|
-
this.prevTarget.classList.toggle("hide", atStart);
|
|
3514
|
+
this.prevTarget.classList.toggle("hide", atStart && !this.loopValue);
|
|
3440
3515
|
if (this.hasNextTarget) {
|
|
3441
3516
|
const atLastSlide = this._currentIndex >= this.slides.length - 1;
|
|
3442
3517
|
const frontierReached = this.wizardValue && this._currentIndex >= this._maxUnlocked;
|
|
3443
|
-
this.nextTarget.classList.toggle("hide", atEnd || atLastSlide || frontierReached);
|
|
3518
|
+
this.nextTarget.classList.toggle("hide", (atEnd || atLastSlide || frontierReached) && !this.loopValue);
|
|
3444
3519
|
}
|
|
3445
3520
|
if (this.wizardValue && this.hasDotTarget) {
|
|
3446
3521
|
this.dotTargets.forEach((dot, i) => {
|
|
@@ -3466,11 +3541,13 @@ class FormLiveSummary extends Controller {
|
|
|
3466
3541
|
this.form?.addEventListener("input", this.render);
|
|
3467
3542
|
this.form?.addEventListener("change", this.render);
|
|
3468
3543
|
this.form?.addEventListener("midwest-autocomplete:change", this.render);
|
|
3544
|
+
this.form?.addEventListener("midwest-color-picker:update", this.render);
|
|
3469
3545
|
}
|
|
3470
3546
|
disconnect() {
|
|
3471
3547
|
this.form?.removeEventListener("input", this.render);
|
|
3472
3548
|
this.form?.removeEventListener("change", this.render);
|
|
3473
3549
|
this.form?.removeEventListener("midwest-autocomplete:change", this.render);
|
|
3550
|
+
this.form?.removeEventListener("midwest-color-picker:update", this.render);
|
|
3474
3551
|
}
|
|
3475
3552
|
get form() {
|
|
3476
3553
|
return this.element.closest("form");
|
|
@@ -3488,15 +3565,18 @@ class FormLiveSummary extends Controller {
|
|
|
3488
3565
|
}
|
|
3489
3566
|
const dl = document.createElement("dl");
|
|
3490
3567
|
dl.className = "midwest-form-live-summary-list";
|
|
3491
|
-
fields.forEach(({ label, value }) => {
|
|
3568
|
+
fields.forEach(({ label, value, swatchColor }) => {
|
|
3492
3569
|
const dt = Object.assign(document.createElement("dt"), {
|
|
3493
3570
|
className: "midwest-form-live-summary-term",
|
|
3494
3571
|
textContent: label
|
|
3495
3572
|
});
|
|
3496
|
-
const dd =
|
|
3497
|
-
|
|
3498
|
-
|
|
3499
|
-
|
|
3573
|
+
const dd = document.createElement("dd");
|
|
3574
|
+
dd.className = "midwest-form-live-summary-definition";
|
|
3575
|
+
if (swatchColor !== void 0) {
|
|
3576
|
+
dd.append(this.buildSwatch(swatchColor), value || "\u2014");
|
|
3577
|
+
} else {
|
|
3578
|
+
dd.textContent = value || "\u2014";
|
|
3579
|
+
}
|
|
3500
3580
|
dl.append(dt, dd);
|
|
3501
3581
|
});
|
|
3502
3582
|
this.listTarget.replaceChildren(dl);
|
|
@@ -3553,6 +3633,18 @@ class FormLiveSummary extends Controller {
|
|
|
3553
3633
|
value: element.value
|
|
3554
3634
|
});
|
|
3555
3635
|
});
|
|
3636
|
+
form.querySelectorAll('[data-midwest-color-picker-target="input"]').forEach((input) => {
|
|
3637
|
+
if (this.element.contains(input))
|
|
3638
|
+
return;
|
|
3639
|
+
if (!input.name)
|
|
3640
|
+
return;
|
|
3641
|
+
const picker = input.closest(".midwest-color-picker");
|
|
3642
|
+
const labelEl = picker?.querySelector(".midwest-form-group__top label") ?? this.form?.querySelector(`label[for="${CSS.escape(input.id)}"]`);
|
|
3643
|
+
const label = labelEl?.textContent?.trim() || this.prettifyName(input.name.replace(/\[.*\]$/, ""));
|
|
3644
|
+
const value = input.value;
|
|
3645
|
+
const display = value.startsWith("#") ? value : value ? value.charAt(0).toUpperCase() + value.slice(1) : "\u2014";
|
|
3646
|
+
fields.push({ label, value: display, swatchColor: value || void 0 });
|
|
3647
|
+
});
|
|
3556
3648
|
form.querySelectorAll(".midwest-autocomplete").forEach((ac) => {
|
|
3557
3649
|
if (this.element.contains(ac))
|
|
3558
3650
|
return;
|
|
@@ -3565,6 +3657,22 @@ class FormLiveSummary extends Controller {
|
|
|
3565
3657
|
});
|
|
3566
3658
|
return fields;
|
|
3567
3659
|
}
|
|
3660
|
+
// Builds a small inline colour swatch to display alongside a colour value.
|
|
3661
|
+
// Hex values are applied as an inline background-color; named theme colours
|
|
3662
|
+
// use a scoped theme-{color} class so the CSS variable cascade does the work.
|
|
3663
|
+
// An empty/transparent value renders a diagonal-stripe placeholder.
|
|
3664
|
+
buildSwatch(color) {
|
|
3665
|
+
const swatch = document.createElement("span");
|
|
3666
|
+
swatch.className = "midwest-form-live-summary-swatch";
|
|
3667
|
+
if (color.startsWith("#")) {
|
|
3668
|
+
swatch.style.backgroundColor = color;
|
|
3669
|
+
} else if (color && color !== "transparent") {
|
|
3670
|
+
swatch.classList.add(`theme-${color}`, "bg-theme-6");
|
|
3671
|
+
} else {
|
|
3672
|
+
swatch.classList.add("is-transparent");
|
|
3673
|
+
}
|
|
3674
|
+
return swatch;
|
|
3675
|
+
}
|
|
3568
3676
|
// Finds the label associated with a specific element via `for` attribute,
|
|
3569
3677
|
// wrapping label, or falls back to a prettified version of the element name.
|
|
3570
3678
|
labelFor(el) {
|
|
@@ -3630,16 +3738,37 @@ class CountdownTimer extends Controller {
|
|
|
3630
3738
|
seconds: { type: Number, default: 5 }
|
|
3631
3739
|
};
|
|
3632
3740
|
timer = null;
|
|
3741
|
+
remaining = 0;
|
|
3742
|
+
startTime = 0;
|
|
3633
3743
|
connect() {
|
|
3634
|
-
this.
|
|
3635
|
-
|
|
3636
|
-
}, this.secondsValue * 1e3);
|
|
3744
|
+
this.remaining = this.secondsValue * 1e3;
|
|
3745
|
+
this.resume();
|
|
3637
3746
|
}
|
|
3638
3747
|
disconnect() {
|
|
3639
|
-
|
|
3640
|
-
|
|
3748
|
+
this.pause();
|
|
3749
|
+
}
|
|
3750
|
+
pause() {
|
|
3751
|
+
if (this.timer === null)
|
|
3752
|
+
return;
|
|
3753
|
+
clearTimeout(this.timer);
|
|
3754
|
+
this.timer = null;
|
|
3755
|
+
this.remaining = Math.max(0, this.remaining - (Date.now() - this.startTime));
|
|
3756
|
+
this.setPlayState("paused");
|
|
3757
|
+
}
|
|
3758
|
+
resume() {
|
|
3759
|
+
if (this.timer !== null)
|
|
3760
|
+
return;
|
|
3761
|
+
this.startTime = Date.now();
|
|
3762
|
+
this.timer = setTimeout(() => {
|
|
3641
3763
|
this.timer = null;
|
|
3642
|
-
|
|
3764
|
+
this.dispatch("complete", { bubbles: true });
|
|
3765
|
+
}, this.remaining);
|
|
3766
|
+
this.setPlayState("running");
|
|
3767
|
+
}
|
|
3768
|
+
setPlayState(state) {
|
|
3769
|
+
const progress = this.element.querySelector(".midwest-countdown-timer__progress");
|
|
3770
|
+
if (progress)
|
|
3771
|
+
progress.style.animationPlayState = state;
|
|
3643
3772
|
}
|
|
3644
3773
|
}
|
|
3645
3774
|
|
|
@@ -3832,19 +3961,30 @@ class Rating extends Controller {
|
|
|
3832
3961
|
}
|
|
3833
3962
|
|
|
3834
3963
|
class Autocomplete extends Controller {
|
|
3835
|
-
static targets = [
|
|
3964
|
+
static targets = [
|
|
3965
|
+
"input",
|
|
3966
|
+
"dropdown",
|
|
3967
|
+
"list",
|
|
3968
|
+
"tokens",
|
|
3969
|
+
"multipleTokenTemplate",
|
|
3970
|
+
"singleTokenTemplate",
|
|
3971
|
+
"status",
|
|
3972
|
+
"turboList",
|
|
3973
|
+
"loader",
|
|
3974
|
+
"freeFormHintList",
|
|
3975
|
+
"freeFormHintValue"
|
|
3976
|
+
];
|
|
3836
3977
|
static values = {
|
|
3837
3978
|
options: { type: Array, default: [] },
|
|
3838
3979
|
url: { type: String, default: "" },
|
|
3839
3980
|
multiple: { type: Boolean, default: true },
|
|
3840
|
-
turboFrameUrl: { type: String, default: "" }
|
|
3981
|
+
turboFrameUrl: { type: String, default: "" },
|
|
3982
|
+
freeForm: { type: Boolean, default: false }
|
|
3841
3983
|
};
|
|
3842
3984
|
highlightedIndex = -1;
|
|
3843
3985
|
fetchAbort = null;
|
|
3844
3986
|
searchTimer;
|
|
3845
3987
|
// ── Named handlers ─────────────────────────────────────────────────────────
|
|
3846
|
-
// Arrow-function class properties preserve `this` and produce a stable
|
|
3847
|
-
// reference so addEventListener/removeEventListener pairs always match.
|
|
3848
3988
|
onFrameLoad = () => {
|
|
3849
3989
|
this.syncSelectedState();
|
|
3850
3990
|
this.loading = false;
|
|
@@ -3879,9 +4019,6 @@ class Autocomplete extends Controller {
|
|
|
3879
4019
|
clearTimeout(this.searchTimer);
|
|
3880
4020
|
this.element.removeEventListener("focusout", this.onFocusOut);
|
|
3881
4021
|
}
|
|
3882
|
-
// Target callbacks — Stimulus calls these automatically when targets connect
|
|
3883
|
-
// or disconnect, including after the initial connect(). This replaces manual
|
|
3884
|
-
// hasTurboListTarget guards and addEventListener calls inside connect().
|
|
3885
4022
|
dropdownTargetConnected(el) {
|
|
3886
4023
|
el.addEventListener("toggle", this.onToggle);
|
|
3887
4024
|
}
|
|
@@ -3906,13 +4043,27 @@ class Autocomplete extends Controller {
|
|
|
3906
4043
|
this.open();
|
|
3907
4044
|
}
|
|
3908
4045
|
open() {
|
|
4046
|
+
if (!this.hasDropdownTarget)
|
|
4047
|
+
return;
|
|
3909
4048
|
if (this.dropdownTarget.matches(":popover-open"))
|
|
3910
4049
|
return;
|
|
4050
|
+
const query = this.inputTarget.value.trim();
|
|
4051
|
+
if (this.freeFormValue && !this.hasListTarget && !this.hasTurboListTarget) {
|
|
4052
|
+
if (query) {
|
|
4053
|
+
this.updateFreeFormHint(query);
|
|
4054
|
+
this.showDropdown();
|
|
4055
|
+
}
|
|
4056
|
+
return;
|
|
4057
|
+
}
|
|
3911
4058
|
clearTimeout(this.searchTimer);
|
|
3912
|
-
this.resolve(
|
|
4059
|
+
this.resolve(query);
|
|
3913
4060
|
}
|
|
3914
4061
|
search() {
|
|
3915
4062
|
const query = this.inputTarget.value.trim();
|
|
4063
|
+
if (this.freeFormValue)
|
|
4064
|
+
this.updateFreeFormHint(query);
|
|
4065
|
+
if (!this.hasListTarget && !this.hasTurboListTarget)
|
|
4066
|
+
return;
|
|
3916
4067
|
if (!this.hasTurboListTarget && !this.urlValue) {
|
|
3917
4068
|
this.renderOptions(this.filteredOptions(query));
|
|
3918
4069
|
return;
|
|
@@ -3927,6 +4078,8 @@ class Autocomplete extends Controller {
|
|
|
3927
4078
|
switch (event.key) {
|
|
3928
4079
|
case "ArrowDown": {
|
|
3929
4080
|
event.preventDefault();
|
|
4081
|
+
if (!this.hasDropdownTarget)
|
|
4082
|
+
break;
|
|
3930
4083
|
if (!this.dropdownTarget.matches(":popover-open"))
|
|
3931
4084
|
this.open();
|
|
3932
4085
|
const items = this.visibleOptions();
|
|
@@ -3936,7 +4089,7 @@ class Autocomplete extends Controller {
|
|
|
3936
4089
|
}
|
|
3937
4090
|
case "ArrowUp": {
|
|
3938
4091
|
event.preventDefault();
|
|
3939
|
-
if (!this.dropdownTarget.matches(":popover-open"))
|
|
4092
|
+
if (!this.hasDropdownTarget || !this.dropdownTarget.matches(":popover-open"))
|
|
3940
4093
|
break;
|
|
3941
4094
|
const items = this.visibleOptions();
|
|
3942
4095
|
this.highlightedIndex = Math.max(this.highlightedIndex - 1, 0);
|
|
@@ -3945,11 +4098,22 @@ class Autocomplete extends Controller {
|
|
|
3945
4098
|
}
|
|
3946
4099
|
case "Enter": {
|
|
3947
4100
|
event.preventDefault();
|
|
3948
|
-
if (
|
|
3949
|
-
|
|
3950
|
-
|
|
3951
|
-
|
|
3952
|
-
|
|
4101
|
+
if (this.hasDropdownTarget && this.dropdownTarget.matches(":popover-open") && (this.hasListTarget || this.hasTurboListTarget)) {
|
|
4102
|
+
const items = this.visibleOptions();
|
|
4103
|
+
if (this.highlightedIndex >= 0 && items[this.highlightedIndex]) {
|
|
4104
|
+
this.pickItem(items[this.highlightedIndex]);
|
|
4105
|
+
break;
|
|
4106
|
+
}
|
|
4107
|
+
}
|
|
4108
|
+
if (this.freeFormValue) {
|
|
4109
|
+
this.commitFreeFormTag();
|
|
4110
|
+
}
|
|
4111
|
+
break;
|
|
4112
|
+
}
|
|
4113
|
+
case ",": {
|
|
4114
|
+
if (this.freeFormValue && this.inputTarget.value.trim()) {
|
|
4115
|
+
event.preventDefault();
|
|
4116
|
+
this.commitFreeFormTag();
|
|
3953
4117
|
}
|
|
3954
4118
|
break;
|
|
3955
4119
|
}
|
|
@@ -3966,6 +4130,22 @@ class Autocomplete extends Controller {
|
|
|
3966
4130
|
break;
|
|
3967
4131
|
}
|
|
3968
4132
|
}
|
|
4133
|
+
// Splits pasted text on commas and newlines into individual tags (free-form mode only).
|
|
4134
|
+
pasteTag(event) {
|
|
4135
|
+
if (!this.freeFormValue)
|
|
4136
|
+
return;
|
|
4137
|
+
const text = event.clipboardData?.getData("text") ?? "";
|
|
4138
|
+
const tags = text.split(/[\n,]+/).map((t) => t.trim()).filter(Boolean);
|
|
4139
|
+
if (tags.length === 0)
|
|
4140
|
+
return;
|
|
4141
|
+
event.preventDefault();
|
|
4142
|
+
tags.forEach((tag) => this.addFreeFormTag(tag));
|
|
4143
|
+
}
|
|
4144
|
+
// Mousedown on the "Hit Enter to add 'X'" hint row.
|
|
4145
|
+
addFreeFormTagFromHint(event) {
|
|
4146
|
+
event.preventDefault();
|
|
4147
|
+
this.commitFreeFormTag();
|
|
4148
|
+
}
|
|
3969
4149
|
removeToken(event) {
|
|
3970
4150
|
event.stopPropagation();
|
|
3971
4151
|
const btn = event.currentTarget;
|
|
@@ -3973,9 +4153,6 @@ class Autocomplete extends Controller {
|
|
|
3973
4153
|
if (value)
|
|
3974
4154
|
this.deselect(value);
|
|
3975
4155
|
}
|
|
3976
|
-
// Public action used by turbo-frame option items via data-action.
|
|
3977
|
-
// Each server-rendered <li> should have:
|
|
3978
|
-
// data-action="mousedown->midwest-autocomplete#selectOption"
|
|
3979
4156
|
selectOption(event) {
|
|
3980
4157
|
event.preventDefault();
|
|
3981
4158
|
this.pickItem(event.currentTarget);
|
|
@@ -3987,7 +4164,6 @@ class Autocomplete extends Controller {
|
|
|
3987
4164
|
isSelected(value) {
|
|
3988
4165
|
return !!this.tokensTarget.querySelector(`[data-value="${CSS.escape(value)}"]`);
|
|
3989
4166
|
}
|
|
3990
|
-
// Routes to the correct data source. Shared by open() and the debounced search path.
|
|
3991
4167
|
resolve(query) {
|
|
3992
4168
|
if (this.hasTurboListTarget) {
|
|
3993
4169
|
this.navigateFrame(query);
|
|
@@ -4016,9 +4192,6 @@ class Autocomplete extends Controller {
|
|
|
4016
4192
|
this.turboListTarget.setAttribute("src", url.toString());
|
|
4017
4193
|
this.showDropdown();
|
|
4018
4194
|
}
|
|
4019
|
-
// After a turbo frame loads, mark already-selected items and update the live region.
|
|
4020
|
-
// The empty state element is always present in the frame and CSS shows/hides it
|
|
4021
|
-
// based on whether any non-selected options exist.
|
|
4022
4195
|
syncSelectedState() {
|
|
4023
4196
|
const options = this.turboListTarget.querySelectorAll(".midwest-autocomplete-option");
|
|
4024
4197
|
options.forEach((option) => {
|
|
@@ -4054,6 +4227,7 @@ class Autocomplete extends Controller {
|
|
|
4054
4227
|
select(option) {
|
|
4055
4228
|
this.appendToken(option);
|
|
4056
4229
|
this.hideDropdown();
|
|
4230
|
+
this.hideFreeFormHint();
|
|
4057
4231
|
this.inputTarget.value = "";
|
|
4058
4232
|
this.inputTarget.focus();
|
|
4059
4233
|
this.announce(`${option.label} selected`);
|
|
@@ -4125,11 +4299,19 @@ class Autocomplete extends Controller {
|
|
|
4125
4299
|
this.renderEmpty("No more options");
|
|
4126
4300
|
return;
|
|
4127
4301
|
}
|
|
4302
|
+
this.hideFreeFormHint();
|
|
4128
4303
|
this.loading = false;
|
|
4129
4304
|
this.announce(`${availableCount} option${availableCount === 1 ? "" : "s"} available`);
|
|
4130
4305
|
this.showDropdown();
|
|
4131
4306
|
}
|
|
4132
4307
|
renderEmpty(message = "No results") {
|
|
4308
|
+
if (this.freeFormValue && this.inputTarget.value.trim()) {
|
|
4309
|
+
if (this.hasListTarget)
|
|
4310
|
+
this.listTarget.replaceChildren();
|
|
4311
|
+
this.loading = false;
|
|
4312
|
+
this.updateFreeFormHint(this.inputTarget.value.trim());
|
|
4313
|
+
return;
|
|
4314
|
+
}
|
|
4133
4315
|
const li = document.createElement("li");
|
|
4134
4316
|
li.className = "midwest-autocomplete-empty";
|
|
4135
4317
|
li.setAttribute("aria-hidden", "true");
|
|
@@ -4139,15 +4321,39 @@ class Autocomplete extends Controller {
|
|
|
4139
4321
|
this.announce(message);
|
|
4140
4322
|
this.showDropdown();
|
|
4141
4323
|
}
|
|
4324
|
+
// Shows or hides the "Hit Enter to add 'X'" hint based on the current query.
|
|
4325
|
+
updateFreeFormHint(query) {
|
|
4326
|
+
if (!this.hasFreeFormHintListTarget)
|
|
4327
|
+
return;
|
|
4328
|
+
if (query && !this.isSelected(query)) {
|
|
4329
|
+
if (this.hasFreeFormHintValueTarget) {
|
|
4330
|
+
this.freeFormHintValueTarget.textContent = `"${query}"`;
|
|
4331
|
+
}
|
|
4332
|
+
this.freeFormHintListTarget.hidden = false;
|
|
4333
|
+
this.showDropdown();
|
|
4334
|
+
} else {
|
|
4335
|
+
this.hideFreeFormHint();
|
|
4336
|
+
if (!this.hasListTarget && !this.hasTurboListTarget)
|
|
4337
|
+
this.hideDropdown();
|
|
4338
|
+
}
|
|
4339
|
+
}
|
|
4340
|
+
hideFreeFormHint() {
|
|
4341
|
+
if (this.hasFreeFormHintListTarget)
|
|
4342
|
+
this.freeFormHintListTarget.hidden = true;
|
|
4343
|
+
}
|
|
4142
4344
|
set loading(on) {
|
|
4143
4345
|
if (this.hasLoaderTarget)
|
|
4144
4346
|
this.loaderTarget.hidden = !on;
|
|
4145
4347
|
}
|
|
4146
4348
|
showDropdown() {
|
|
4349
|
+
if (!this.hasDropdownTarget)
|
|
4350
|
+
return;
|
|
4147
4351
|
if (!this.dropdownTarget.matches(":popover-open"))
|
|
4148
4352
|
this.dropdownTarget.showPopover();
|
|
4149
4353
|
}
|
|
4150
4354
|
hideDropdown() {
|
|
4355
|
+
if (!this.hasDropdownTarget)
|
|
4356
|
+
return;
|
|
4151
4357
|
if (this.dropdownTarget.matches(":popover-open"))
|
|
4152
4358
|
this.dropdownTarget.hidePopover();
|
|
4153
4359
|
}
|
|
@@ -4171,12 +4377,734 @@ class Autocomplete extends Controller {
|
|
|
4171
4377
|
const support = item.querySelector(".midwest-autocomplete-option-support")?.textContent?.trim();
|
|
4172
4378
|
this.select({ value, label, support });
|
|
4173
4379
|
}
|
|
4380
|
+
commitFreeFormTag() {
|
|
4381
|
+
const value = this.inputTarget.value.trim();
|
|
4382
|
+
this.addFreeFormTag(value);
|
|
4383
|
+
}
|
|
4384
|
+
addFreeFormTag(value) {
|
|
4385
|
+
if (!value)
|
|
4386
|
+
return;
|
|
4387
|
+
if (this.isSelected(value))
|
|
4388
|
+
return;
|
|
4389
|
+
this.select({ value, label: value });
|
|
4390
|
+
}
|
|
4174
4391
|
announce(message) {
|
|
4175
4392
|
if (this.hasStatusTarget)
|
|
4176
4393
|
this.statusTarget.textContent = message;
|
|
4177
4394
|
}
|
|
4178
4395
|
}
|
|
4179
4396
|
|
|
4397
|
+
/*
|
|
4398
|
+
* Color Thief v2.0
|
|
4399
|
+
* by Lokesh Dhakar - http://www.lokeshdhakar.com
|
|
4400
|
+
*
|
|
4401
|
+
* Thanks
|
|
4402
|
+
* ------
|
|
4403
|
+
* Nick Rabinowitz - For creating quantize.js.
|
|
4404
|
+
* John Schulz - For clean up and optimization. @JFSIII
|
|
4405
|
+
* Nathan Spady - For adding drag and drop support to the demo page.
|
|
4406
|
+
*
|
|
4407
|
+
* License
|
|
4408
|
+
* -------
|
|
4409
|
+
* Copyright 2011, 2015 Lokesh Dhakar
|
|
4410
|
+
* Released under the MIT license
|
|
4411
|
+
* https://raw.githubusercontent.com/lokesh/color-thief/master/LICENSE
|
|
4412
|
+
*
|
|
4413
|
+
* @license
|
|
4414
|
+
*/
|
|
4415
|
+
const CanvasImage = function(image) {
|
|
4416
|
+
this.canvas = document.createElement("canvas");
|
|
4417
|
+
this.context = this.canvas.getContext("2d");
|
|
4418
|
+
document.body.appendChild(this.canvas);
|
|
4419
|
+
this.width = this.canvas.width = image.width;
|
|
4420
|
+
this.height = this.canvas.height = image.height;
|
|
4421
|
+
this.context.drawImage(image, 0, 0, this.width, this.height);
|
|
4422
|
+
};
|
|
4423
|
+
CanvasImage.prototype.clear = function() {
|
|
4424
|
+
this.context.clearRect(0, 0, this.width, this.height);
|
|
4425
|
+
};
|
|
4426
|
+
CanvasImage.prototype.update = function(imageData) {
|
|
4427
|
+
this.context.putImageData(imageData, 0, 0);
|
|
4428
|
+
};
|
|
4429
|
+
CanvasImage.prototype.getPixelCount = function() {
|
|
4430
|
+
return this.width * this.height;
|
|
4431
|
+
};
|
|
4432
|
+
CanvasImage.prototype.getImageData = function() {
|
|
4433
|
+
return this.context.getImageData(0, 0, this.width, this.height);
|
|
4434
|
+
};
|
|
4435
|
+
CanvasImage.prototype.removeCanvas = function() {
|
|
4436
|
+
this.canvas.parentNode.removeChild(this.canvas);
|
|
4437
|
+
};
|
|
4438
|
+
const ColorThief = function() {
|
|
4439
|
+
};
|
|
4440
|
+
ColorThief.prototype.getColor = function(sourceImage2, quality) {
|
|
4441
|
+
const palette = this.getPalette(sourceImage2, 5, quality);
|
|
4442
|
+
const dominantColor = palette[0];
|
|
4443
|
+
return dominantColor;
|
|
4444
|
+
};
|
|
4445
|
+
ColorThief.prototype.getPalette = function(sourceImage2, colorCount, quality) {
|
|
4446
|
+
if (typeof colorCount === "undefined" || colorCount < 2 || colorCount > 256) {
|
|
4447
|
+
colorCount = 10;
|
|
4448
|
+
}
|
|
4449
|
+
if (typeof quality === "undefined" || quality < 1) {
|
|
4450
|
+
quality = 10;
|
|
4451
|
+
}
|
|
4452
|
+
const image = new CanvasImage(sourceImage2);
|
|
4453
|
+
const imageData = image.getImageData();
|
|
4454
|
+
const pixels = imageData.data;
|
|
4455
|
+
const pixelCount = image.getPixelCount();
|
|
4456
|
+
const pixelArray = [];
|
|
4457
|
+
for (var i = 0, offset, r, g, b, a; i < pixelCount; i = i + quality) {
|
|
4458
|
+
offset = i * 4;
|
|
4459
|
+
r = pixels[offset + 0];
|
|
4460
|
+
g = pixels[offset + 1];
|
|
4461
|
+
b = pixels[offset + 2];
|
|
4462
|
+
a = pixels[offset + 3];
|
|
4463
|
+
if (a >= 125) {
|
|
4464
|
+
if (!(r > 250 && g > 250 && b > 250)) {
|
|
4465
|
+
pixelArray.push([r, g, b]);
|
|
4466
|
+
}
|
|
4467
|
+
}
|
|
4468
|
+
}
|
|
4469
|
+
const cmap = MMCQ.quantize(pixelArray, colorCount);
|
|
4470
|
+
const palette = cmap ? cmap.palette() : null;
|
|
4471
|
+
image.removeCanvas();
|
|
4472
|
+
return palette;
|
|
4473
|
+
};
|
|
4474
|
+
ColorThief.prototype.getColorFromUrl = function(imageUrl, callback, quality) {
|
|
4475
|
+
sourceImage = document.createElement("img");
|
|
4476
|
+
const thief = this;
|
|
4477
|
+
sourceImage.addEventListener("load", function() {
|
|
4478
|
+
const palette = thief.getPalette(sourceImage, 5, quality);
|
|
4479
|
+
const dominantColor = palette[0];
|
|
4480
|
+
callback(dominantColor, imageUrl);
|
|
4481
|
+
});
|
|
4482
|
+
sourceImage.src = imageUrl;
|
|
4483
|
+
};
|
|
4484
|
+
ColorThief.prototype.getImageData = function(imageUrl, callback) {
|
|
4485
|
+
xhr = new XMLHttpRequest();
|
|
4486
|
+
xhr.open("GET", imageUrl, true);
|
|
4487
|
+
xhr.responseType = "arraybuffer";
|
|
4488
|
+
xhr.onload = function(e) {
|
|
4489
|
+
if (this.status == 200) {
|
|
4490
|
+
uInt8Array = new Uint8Array(this.response);
|
|
4491
|
+
i = uInt8Array.length;
|
|
4492
|
+
binaryString = new Array(i);
|
|
4493
|
+
for (var i = 0; i < uInt8Array.length; i++) {
|
|
4494
|
+
binaryString[i] = String.fromCharCode(uInt8Array[i]);
|
|
4495
|
+
}
|
|
4496
|
+
data = binaryString.join("");
|
|
4497
|
+
base64 = window.btoa(data);
|
|
4498
|
+
callback("data:image/png;base64," + base64);
|
|
4499
|
+
}
|
|
4500
|
+
};
|
|
4501
|
+
xhr.send();
|
|
4502
|
+
};
|
|
4503
|
+
ColorThief.prototype.getColorAsync = function(imageUrl, callback, quality) {
|
|
4504
|
+
const thief = this;
|
|
4505
|
+
this.getImageData(imageUrl, function(imageData) {
|
|
4506
|
+
sourceImage = document.createElement("img");
|
|
4507
|
+
sourceImage.addEventListener("load", function() {
|
|
4508
|
+
const palette = thief.getPalette(sourceImage, 5, quality);
|
|
4509
|
+
const dominantColor = palette[0];
|
|
4510
|
+
callback(dominantColor, this);
|
|
4511
|
+
});
|
|
4512
|
+
sourceImage.src = imageData;
|
|
4513
|
+
});
|
|
4514
|
+
};
|
|
4515
|
+
/*!
|
|
4516
|
+
* quantize.js Copyright 2008 Nick Rabinowitz.
|
|
4517
|
+
* Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php
|
|
4518
|
+
* @license
|
|
4519
|
+
*/
|
|
4520
|
+
/*!
|
|
4521
|
+
* Block below copied from Protovis: http://mbostock.github.com/protovis/
|
|
4522
|
+
* Copyright 2010 Stanford Visualization Group
|
|
4523
|
+
* Licensed under the BSD License: http://www.opensource.org/licenses/bsd-license.php
|
|
4524
|
+
* @license
|
|
4525
|
+
*/
|
|
4526
|
+
if (!pv) {
|
|
4527
|
+
var pv = {
|
|
4528
|
+
map: function(array, f) {
|
|
4529
|
+
const o = {};
|
|
4530
|
+
return f ? array.map(function(d, i) {
|
|
4531
|
+
o.index = i;
|
|
4532
|
+
return f.call(o, d);
|
|
4533
|
+
}) : array.slice();
|
|
4534
|
+
},
|
|
4535
|
+
naturalOrder: function(a, b) {
|
|
4536
|
+
return a < b ? -1 : a > b ? 1 : 0;
|
|
4537
|
+
},
|
|
4538
|
+
sum: function(array, f) {
|
|
4539
|
+
const o = {};
|
|
4540
|
+
return array.reduce(f ? function(p, d, i) {
|
|
4541
|
+
o.index = i;
|
|
4542
|
+
return p + f.call(o, d);
|
|
4543
|
+
} : function(p, d) {
|
|
4544
|
+
return p + d;
|
|
4545
|
+
}, 0);
|
|
4546
|
+
},
|
|
4547
|
+
max: function(array, f) {
|
|
4548
|
+
return Math.max.apply(null, f ? pv.map(array, f) : array);
|
|
4549
|
+
}
|
|
4550
|
+
};
|
|
4551
|
+
}
|
|
4552
|
+
var MMCQ = function() {
|
|
4553
|
+
const sigbits = 5, rshift = 8 - sigbits, maxIterations = 1e3, fractByPopulations = 0.75;
|
|
4554
|
+
function getColorIndex(r, g, b) {
|
|
4555
|
+
return (r << 2 * sigbits) + (g << sigbits) + b;
|
|
4556
|
+
}
|
|
4557
|
+
function PQueue(comparator) {
|
|
4558
|
+
let contents = [], sorted = false;
|
|
4559
|
+
function sort() {
|
|
4560
|
+
contents.sort(comparator);
|
|
4561
|
+
sorted = true;
|
|
4562
|
+
}
|
|
4563
|
+
return {
|
|
4564
|
+
push: function(o) {
|
|
4565
|
+
contents.push(o);
|
|
4566
|
+
sorted = false;
|
|
4567
|
+
},
|
|
4568
|
+
peek: function(index) {
|
|
4569
|
+
if (!sorted)
|
|
4570
|
+
sort();
|
|
4571
|
+
if (index === void 0)
|
|
4572
|
+
index = contents.length - 1;
|
|
4573
|
+
return contents[index];
|
|
4574
|
+
},
|
|
4575
|
+
pop: function() {
|
|
4576
|
+
if (!sorted)
|
|
4577
|
+
sort();
|
|
4578
|
+
return contents.pop();
|
|
4579
|
+
},
|
|
4580
|
+
size: function() {
|
|
4581
|
+
return contents.length;
|
|
4582
|
+
},
|
|
4583
|
+
map: function(f) {
|
|
4584
|
+
return contents.map(f);
|
|
4585
|
+
},
|
|
4586
|
+
debug: function() {
|
|
4587
|
+
if (!sorted)
|
|
4588
|
+
sort();
|
|
4589
|
+
return contents;
|
|
4590
|
+
}
|
|
4591
|
+
};
|
|
4592
|
+
}
|
|
4593
|
+
function VBox(r1, r2, g1, g2, b1, b2, histo) {
|
|
4594
|
+
const vbox = this;
|
|
4595
|
+
vbox.r1 = r1;
|
|
4596
|
+
vbox.r2 = r2;
|
|
4597
|
+
vbox.g1 = g1;
|
|
4598
|
+
vbox.g2 = g2;
|
|
4599
|
+
vbox.b1 = b1;
|
|
4600
|
+
vbox.b2 = b2;
|
|
4601
|
+
vbox.histo = histo;
|
|
4602
|
+
}
|
|
4603
|
+
VBox.prototype = {
|
|
4604
|
+
volume: function(force) {
|
|
4605
|
+
const vbox = this;
|
|
4606
|
+
if (!vbox._volume || force) {
|
|
4607
|
+
vbox._volume = (vbox.r2 - vbox.r1 + 1) * (vbox.g2 - vbox.g1 + 1) * (vbox.b2 - vbox.b1 + 1);
|
|
4608
|
+
}
|
|
4609
|
+
return vbox._volume;
|
|
4610
|
+
},
|
|
4611
|
+
count: function(force) {
|
|
4612
|
+
const vbox = this, histo = vbox.histo;
|
|
4613
|
+
if (!vbox._count_set || force) {
|
|
4614
|
+
let npix = 0, index, i, j, k;
|
|
4615
|
+
for (i = vbox.r1; i <= vbox.r2; i++) {
|
|
4616
|
+
for (j = vbox.g1; j <= vbox.g2; j++) {
|
|
4617
|
+
for (k = vbox.b1; k <= vbox.b2; k++) {
|
|
4618
|
+
index = getColorIndex(i, j, k);
|
|
4619
|
+
npix += histo[index] || 0;
|
|
4620
|
+
}
|
|
4621
|
+
}
|
|
4622
|
+
}
|
|
4623
|
+
vbox._count = npix;
|
|
4624
|
+
vbox._count_set = true;
|
|
4625
|
+
}
|
|
4626
|
+
return vbox._count;
|
|
4627
|
+
},
|
|
4628
|
+
copy: function() {
|
|
4629
|
+
const vbox = this;
|
|
4630
|
+
return new VBox(vbox.r1, vbox.r2, vbox.g1, vbox.g2, vbox.b1, vbox.b2, vbox.histo);
|
|
4631
|
+
},
|
|
4632
|
+
avg: function(force) {
|
|
4633
|
+
const vbox = this, histo = vbox.histo;
|
|
4634
|
+
if (!vbox._avg || force) {
|
|
4635
|
+
let ntot = 0, mult = 1 << 8 - sigbits, rsum = 0, gsum = 0, bsum = 0, hval, i, j, k, histoindex;
|
|
4636
|
+
for (i = vbox.r1; i <= vbox.r2; i++) {
|
|
4637
|
+
for (j = vbox.g1; j <= vbox.g2; j++) {
|
|
4638
|
+
for (k = vbox.b1; k <= vbox.b2; k++) {
|
|
4639
|
+
histoindex = getColorIndex(i, j, k);
|
|
4640
|
+
hval = histo[histoindex] || 0;
|
|
4641
|
+
ntot += hval;
|
|
4642
|
+
rsum += hval * (i + 0.5) * mult;
|
|
4643
|
+
gsum += hval * (j + 0.5) * mult;
|
|
4644
|
+
bsum += hval * (k + 0.5) * mult;
|
|
4645
|
+
}
|
|
4646
|
+
}
|
|
4647
|
+
}
|
|
4648
|
+
if (ntot) {
|
|
4649
|
+
vbox._avg = [~~(rsum / ntot), ~~(gsum / ntot), ~~(bsum / ntot)];
|
|
4650
|
+
} else {
|
|
4651
|
+
vbox._avg = [
|
|
4652
|
+
~~(mult * (vbox.r1 + vbox.r2 + 1) / 2),
|
|
4653
|
+
~~(mult * (vbox.g1 + vbox.g2 + 1) / 2),
|
|
4654
|
+
~~(mult * (vbox.b1 + vbox.b2 + 1) / 2)
|
|
4655
|
+
];
|
|
4656
|
+
}
|
|
4657
|
+
}
|
|
4658
|
+
return vbox._avg;
|
|
4659
|
+
},
|
|
4660
|
+
contains: function(pixel) {
|
|
4661
|
+
const vbox = this, rval = pixel[0] >> rshift;
|
|
4662
|
+
gval = pixel[1] >> rshift;
|
|
4663
|
+
bval = pixel[2] >> rshift;
|
|
4664
|
+
return rval >= vbox.r1 && rval <= vbox.r2 && gval >= vbox.g1 && gval <= vbox.g2 && bval >= vbox.b1 && bval <= vbox.b2;
|
|
4665
|
+
}
|
|
4666
|
+
};
|
|
4667
|
+
function CMap() {
|
|
4668
|
+
this.vboxes = new PQueue(function(a, b) {
|
|
4669
|
+
return pv.naturalOrder(
|
|
4670
|
+
a.vbox.count() * a.vbox.volume(),
|
|
4671
|
+
b.vbox.count() * b.vbox.volume()
|
|
4672
|
+
);
|
|
4673
|
+
});
|
|
4674
|
+
}
|
|
4675
|
+
CMap.prototype = {
|
|
4676
|
+
push: function(vbox) {
|
|
4677
|
+
this.vboxes.push({
|
|
4678
|
+
vbox,
|
|
4679
|
+
color: vbox.avg()
|
|
4680
|
+
});
|
|
4681
|
+
},
|
|
4682
|
+
palette: function() {
|
|
4683
|
+
return this.vboxes.map(function(vb) {
|
|
4684
|
+
return vb.color;
|
|
4685
|
+
});
|
|
4686
|
+
},
|
|
4687
|
+
size: function() {
|
|
4688
|
+
return this.vboxes.size();
|
|
4689
|
+
},
|
|
4690
|
+
map: function(color) {
|
|
4691
|
+
const vboxes = this.vboxes;
|
|
4692
|
+
for (let i = 0; i < vboxes.size(); i++) {
|
|
4693
|
+
if (vboxes.peek(i).vbox.contains(color)) {
|
|
4694
|
+
return vboxes.peek(i).color;
|
|
4695
|
+
}
|
|
4696
|
+
}
|
|
4697
|
+
return this.nearest(color);
|
|
4698
|
+
},
|
|
4699
|
+
nearest: function(color) {
|
|
4700
|
+
let vboxes = this.vboxes, d1, d2, pColor;
|
|
4701
|
+
for (let i = 0; i < vboxes.size(); i++) {
|
|
4702
|
+
d2 = Math.sqrt(
|
|
4703
|
+
Math.pow(color[0] - vboxes.peek(i).color[0], 2) + Math.pow(color[1] - vboxes.peek(i).color[1], 2) + Math.pow(color[2] - vboxes.peek(i).color[2], 2)
|
|
4704
|
+
);
|
|
4705
|
+
if (d2 < d1 || d1 === void 0) {
|
|
4706
|
+
d1 = d2;
|
|
4707
|
+
pColor = vboxes.peek(i).color;
|
|
4708
|
+
}
|
|
4709
|
+
}
|
|
4710
|
+
return pColor;
|
|
4711
|
+
},
|
|
4712
|
+
forcebw: function() {
|
|
4713
|
+
const vboxes = this.vboxes;
|
|
4714
|
+
vboxes.sort(function(a, b) {
|
|
4715
|
+
return pv.naturalOrder(pv.sum(a.color), pv.sum(b.color));
|
|
4716
|
+
});
|
|
4717
|
+
const lowest = vboxes[0].color;
|
|
4718
|
+
if (lowest[0] < 5 && lowest[1] < 5 && lowest[2] < 5)
|
|
4719
|
+
vboxes[0].color = [0, 0, 0];
|
|
4720
|
+
const idx = vboxes.length - 1, highest = vboxes[idx].color;
|
|
4721
|
+
if (highest[0] > 251 && highest[1] > 251 && highest[2] > 251)
|
|
4722
|
+
vboxes[idx].color = [255, 255, 255];
|
|
4723
|
+
}
|
|
4724
|
+
};
|
|
4725
|
+
function getHisto(pixels) {
|
|
4726
|
+
let histosize = 1 << 3 * sigbits, histo = new Array(histosize), index, rval, gval2, bval2;
|
|
4727
|
+
pixels.forEach(function(pixel) {
|
|
4728
|
+
rval = pixel[0] >> rshift;
|
|
4729
|
+
gval2 = pixel[1] >> rshift;
|
|
4730
|
+
bval2 = pixel[2] >> rshift;
|
|
4731
|
+
index = getColorIndex(rval, gval2, bval2);
|
|
4732
|
+
histo[index] = (histo[index] || 0) + 1;
|
|
4733
|
+
});
|
|
4734
|
+
return histo;
|
|
4735
|
+
}
|
|
4736
|
+
function vboxFromPixels(pixels, histo) {
|
|
4737
|
+
let rmin = 1e6, rmax = 0, gmin = 1e6, gmax = 0, bmin = 1e6, bmax = 0, rval, gval2, bval2;
|
|
4738
|
+
pixels.forEach(function(pixel) {
|
|
4739
|
+
rval = pixel[0] >> rshift;
|
|
4740
|
+
gval2 = pixel[1] >> rshift;
|
|
4741
|
+
bval2 = pixel[2] >> rshift;
|
|
4742
|
+
if (rval < rmin)
|
|
4743
|
+
rmin = rval;
|
|
4744
|
+
else if (rval > rmax)
|
|
4745
|
+
rmax = rval;
|
|
4746
|
+
if (gval2 < gmin)
|
|
4747
|
+
gmin = gval2;
|
|
4748
|
+
else if (gval2 > gmax)
|
|
4749
|
+
gmax = gval2;
|
|
4750
|
+
if (bval2 < bmin)
|
|
4751
|
+
bmin = bval2;
|
|
4752
|
+
else if (bval2 > bmax)
|
|
4753
|
+
bmax = bval2;
|
|
4754
|
+
});
|
|
4755
|
+
return new VBox(rmin, rmax, gmin, gmax, bmin, bmax, histo);
|
|
4756
|
+
}
|
|
4757
|
+
function medianCutApply(histo, vbox) {
|
|
4758
|
+
if (!vbox.count())
|
|
4759
|
+
return;
|
|
4760
|
+
const rw = vbox.r2 - vbox.r1 + 1, gw = vbox.g2 - vbox.g1 + 1, bw = vbox.b2 - vbox.b1 + 1, maxw = pv.max([rw, gw, bw]);
|
|
4761
|
+
if (vbox.count() == 1) {
|
|
4762
|
+
return [vbox.copy()];
|
|
4763
|
+
}
|
|
4764
|
+
let total = 0, partialsum = [], lookaheadsum = [], i, j, k, sum, index;
|
|
4765
|
+
if (maxw == rw) {
|
|
4766
|
+
for (i = vbox.r1; i <= vbox.r2; i++) {
|
|
4767
|
+
sum = 0;
|
|
4768
|
+
for (j = vbox.g1; j <= vbox.g2; j++) {
|
|
4769
|
+
for (k = vbox.b1; k <= vbox.b2; k++) {
|
|
4770
|
+
index = getColorIndex(i, j, k);
|
|
4771
|
+
sum += histo[index] || 0;
|
|
4772
|
+
}
|
|
4773
|
+
}
|
|
4774
|
+
total += sum;
|
|
4775
|
+
partialsum[i] = total;
|
|
4776
|
+
}
|
|
4777
|
+
} else if (maxw == gw) {
|
|
4778
|
+
for (i = vbox.g1; i <= vbox.g2; i++) {
|
|
4779
|
+
sum = 0;
|
|
4780
|
+
for (j = vbox.r1; j <= vbox.r2; j++) {
|
|
4781
|
+
for (k = vbox.b1; k <= vbox.b2; k++) {
|
|
4782
|
+
index = getColorIndex(j, i, k);
|
|
4783
|
+
sum += histo[index] || 0;
|
|
4784
|
+
}
|
|
4785
|
+
}
|
|
4786
|
+
total += sum;
|
|
4787
|
+
partialsum[i] = total;
|
|
4788
|
+
}
|
|
4789
|
+
} else {
|
|
4790
|
+
for (i = vbox.b1; i <= vbox.b2; i++) {
|
|
4791
|
+
sum = 0;
|
|
4792
|
+
for (j = vbox.r1; j <= vbox.r2; j++) {
|
|
4793
|
+
for (k = vbox.g1; k <= vbox.g2; k++) {
|
|
4794
|
+
index = getColorIndex(j, k, i);
|
|
4795
|
+
sum += histo[index] || 0;
|
|
4796
|
+
}
|
|
4797
|
+
}
|
|
4798
|
+
total += sum;
|
|
4799
|
+
partialsum[i] = total;
|
|
4800
|
+
}
|
|
4801
|
+
}
|
|
4802
|
+
partialsum.forEach(function(d, i2) {
|
|
4803
|
+
lookaheadsum[i2] = total - d;
|
|
4804
|
+
});
|
|
4805
|
+
function doCut(color) {
|
|
4806
|
+
let dim1 = color + "1", dim2 = color + "2", left, right, vbox1, vbox2, d2, count2 = 0;
|
|
4807
|
+
for (i = vbox[dim1]; i <= vbox[dim2]; i++) {
|
|
4808
|
+
if (partialsum[i] > total / 2) {
|
|
4809
|
+
vbox1 = vbox.copy();
|
|
4810
|
+
vbox2 = vbox.copy();
|
|
4811
|
+
left = i - vbox[dim1];
|
|
4812
|
+
right = vbox[dim2] - i;
|
|
4813
|
+
if (left <= right)
|
|
4814
|
+
d2 = Math.min(vbox[dim2] - 1, ~~(i + right / 2));
|
|
4815
|
+
else
|
|
4816
|
+
d2 = Math.max(vbox[dim1], ~~(i - 1 - left / 2));
|
|
4817
|
+
while (!partialsum[d2])
|
|
4818
|
+
d2++;
|
|
4819
|
+
count2 = lookaheadsum[d2];
|
|
4820
|
+
while (!count2 && partialsum[d2 - 1])
|
|
4821
|
+
count2 = lookaheadsum[--d2];
|
|
4822
|
+
vbox1[dim2] = d2;
|
|
4823
|
+
vbox2[dim1] = vbox1[dim2] + 1;
|
|
4824
|
+
return [vbox1, vbox2];
|
|
4825
|
+
}
|
|
4826
|
+
}
|
|
4827
|
+
}
|
|
4828
|
+
return maxw == rw ? doCut("r") : maxw == gw ? doCut("g") : doCut("b");
|
|
4829
|
+
}
|
|
4830
|
+
function quantize(pixels, maxcolors) {
|
|
4831
|
+
if (!pixels.length || maxcolors < 2 || maxcolors > 256) {
|
|
4832
|
+
return false;
|
|
4833
|
+
}
|
|
4834
|
+
const histo = getHisto(pixels);
|
|
4835
|
+
histo.forEach(function() {
|
|
4836
|
+
});
|
|
4837
|
+
const vbox = vboxFromPixels(pixels, histo), pq = new PQueue(function(a, b) {
|
|
4838
|
+
return pv.naturalOrder(a.count(), b.count());
|
|
4839
|
+
});
|
|
4840
|
+
pq.push(vbox);
|
|
4841
|
+
function iter(lh, target) {
|
|
4842
|
+
let ncolors = 1, niters = 0, vbox2;
|
|
4843
|
+
while (niters < maxIterations) {
|
|
4844
|
+
vbox2 = lh.pop();
|
|
4845
|
+
if (!vbox2.count()) {
|
|
4846
|
+
lh.push(vbox2);
|
|
4847
|
+
niters++;
|
|
4848
|
+
continue;
|
|
4849
|
+
}
|
|
4850
|
+
const vboxes = medianCutApply(histo, vbox2), vbox1 = vboxes[0], vbox22 = vboxes[1];
|
|
4851
|
+
if (!vbox1) {
|
|
4852
|
+
return;
|
|
4853
|
+
}
|
|
4854
|
+
lh.push(vbox1);
|
|
4855
|
+
if (vbox22) {
|
|
4856
|
+
lh.push(vbox22);
|
|
4857
|
+
ncolors++;
|
|
4858
|
+
}
|
|
4859
|
+
if (ncolors >= target)
|
|
4860
|
+
return;
|
|
4861
|
+
if (niters++ > maxIterations) {
|
|
4862
|
+
return;
|
|
4863
|
+
}
|
|
4864
|
+
}
|
|
4865
|
+
}
|
|
4866
|
+
iter(pq, fractByPopulations * maxcolors);
|
|
4867
|
+
const pq2 = new PQueue(function(a, b) {
|
|
4868
|
+
return pv.naturalOrder(a.count() * a.volume(), b.count() * b.volume());
|
|
4869
|
+
});
|
|
4870
|
+
while (pq.size()) {
|
|
4871
|
+
pq2.push(pq.pop());
|
|
4872
|
+
}
|
|
4873
|
+
iter(pq2, maxcolors - pq2.size());
|
|
4874
|
+
const cmap = new CMap();
|
|
4875
|
+
while (pq2.size()) {
|
|
4876
|
+
cmap.push(pq2.pop());
|
|
4877
|
+
}
|
|
4878
|
+
return cmap;
|
|
4879
|
+
}
|
|
4880
|
+
return {
|
|
4881
|
+
quantize
|
|
4882
|
+
};
|
|
4883
|
+
}();
|
|
4884
|
+
|
|
4885
|
+
class Image extends Controller {
|
|
4886
|
+
static targets = ["dialog"];
|
|
4887
|
+
static values = { poster: String };
|
|
4888
|
+
connect() {
|
|
4889
|
+
this.extractColor();
|
|
4890
|
+
}
|
|
4891
|
+
extractColor() {
|
|
4892
|
+
if (!this.hasDialogTarget)
|
|
4893
|
+
return;
|
|
4894
|
+
const mainImg = this.element.querySelector("img.midwest-image-main");
|
|
4895
|
+
const colorSource = this.posterValue || mainImg?.src;
|
|
4896
|
+
if (!colorSource)
|
|
4897
|
+
return;
|
|
4898
|
+
const sample = new window.Image(80, 80);
|
|
4899
|
+
sample.crossOrigin = "Anonymous";
|
|
4900
|
+
sample.addEventListener("load", () => {
|
|
4901
|
+
try {
|
|
4902
|
+
const [r, g, b] = new ColorThief().getColor(sample);
|
|
4903
|
+
this.dialogTarget.style.setProperty("--image-dominant-color", `${r} ${g} ${b}`);
|
|
4904
|
+
} catch {
|
|
4905
|
+
}
|
|
4906
|
+
});
|
|
4907
|
+
sample.src = colorSource;
|
|
4908
|
+
}
|
|
4909
|
+
zoom() {
|
|
4910
|
+
if (this.hasDialogTarget) {
|
|
4911
|
+
this.dialogTarget.showModal();
|
|
4912
|
+
}
|
|
4913
|
+
}
|
|
4914
|
+
close() {
|
|
4915
|
+
if (this.hasDialogTarget) {
|
|
4916
|
+
this.dialogTarget.close();
|
|
4917
|
+
}
|
|
4918
|
+
}
|
|
4919
|
+
// Close when clicking the backdrop (outside the dialog content).
|
|
4920
|
+
backdropClose(event) {
|
|
4921
|
+
if (event.target === this.dialogTarget) {
|
|
4922
|
+
this.dialogTarget.close();
|
|
4923
|
+
}
|
|
4924
|
+
}
|
|
4925
|
+
}
|
|
4926
|
+
|
|
4927
|
+
class ColorPicker extends Controller {
|
|
4928
|
+
static targets = ["input", "swatch", "customSwatch", "colorInput"];
|
|
4929
|
+
onCustomColorChange = null;
|
|
4930
|
+
get scaleTarget() {
|
|
4931
|
+
return this.element.dataset.scaleTarget ?? "theme";
|
|
4932
|
+
}
|
|
4933
|
+
connect() {
|
|
4934
|
+
if (this.hasColorInputTarget) {
|
|
4935
|
+
this.onCustomColorChange = (e) => this.handleCustomColor(e);
|
|
4936
|
+
this.colorInputTarget.addEventListener("input", this.onCustomColorChange);
|
|
4937
|
+
}
|
|
4938
|
+
if (this.hasInputTarget && this.inputTarget.value.startsWith("#")) {
|
|
4939
|
+
this.restoreCustom(this.inputTarget.value);
|
|
4940
|
+
}
|
|
4941
|
+
}
|
|
4942
|
+
disconnect() {
|
|
4943
|
+
if (this.hasColorInputTarget && this.onCustomColorChange) {
|
|
4944
|
+
this.colorInputTarget.removeEventListener("input", this.onCustomColorChange);
|
|
4945
|
+
this.onCustomColorChange = null;
|
|
4946
|
+
}
|
|
4947
|
+
}
|
|
4948
|
+
select(e) {
|
|
4949
|
+
const button = e.currentTarget;
|
|
4950
|
+
const color = button.dataset.color ?? "";
|
|
4951
|
+
this.applySelection(color);
|
|
4952
|
+
this.dispatch("update", { detail: { color, scaleTarget: this.scaleTarget } });
|
|
4953
|
+
}
|
|
4954
|
+
openCustom(e) {
|
|
4955
|
+
e.preventDefault();
|
|
4956
|
+
this.colorInputTarget.click();
|
|
4957
|
+
}
|
|
4958
|
+
handleCustomColor(e) {
|
|
4959
|
+
const hex = e.target.value;
|
|
4960
|
+
if (this.hasCustomSwatchTarget) {
|
|
4961
|
+
this.customSwatchTarget.dataset.color = hex;
|
|
4962
|
+
this.customSwatchTarget.style.background = hex;
|
|
4963
|
+
}
|
|
4964
|
+
const scale = this.generateScale(hex);
|
|
4965
|
+
this.applyScale(scale);
|
|
4966
|
+
this.applySelection(hex);
|
|
4967
|
+
this.dispatch("update", { detail: { color: hex, scale, scaleTarget: this.scaleTarget } });
|
|
4968
|
+
}
|
|
4969
|
+
restoreCustom(hex) {
|
|
4970
|
+
if (this.hasColorInputTarget) {
|
|
4971
|
+
this.colorInputTarget.value = hex;
|
|
4972
|
+
}
|
|
4973
|
+
if (this.hasCustomSwatchTarget) {
|
|
4974
|
+
this.customSwatchTarget.style.background = hex;
|
|
4975
|
+
}
|
|
4976
|
+
const scale = this.generateScale(hex);
|
|
4977
|
+
this.applyScale(scale);
|
|
4978
|
+
}
|
|
4979
|
+
applySelection(color) {
|
|
4980
|
+
if (this.hasInputTarget) {
|
|
4981
|
+
this.inputTarget.value = color;
|
|
4982
|
+
}
|
|
4983
|
+
this.swatchTargets.forEach((swatch) => {
|
|
4984
|
+
const isSelected = swatch.dataset.color === color;
|
|
4985
|
+
swatch.classList.toggle("is-selected", isSelected);
|
|
4986
|
+
swatch.setAttribute("aria-pressed", String(isSelected));
|
|
4987
|
+
});
|
|
4988
|
+
}
|
|
4989
|
+
// Generates 13 RGB channel strings (space-separated, matching --{color}-{n}-channels format)
|
|
4990
|
+
// from a hex color by fixing the hue/saturation and distributing lightness across the scale.
|
|
4991
|
+
generateScale(hex) {
|
|
4992
|
+
const [h, s] = this.hexToHsl(hex);
|
|
4993
|
+
const lightnessSteps = [97, 90, 82, 74, 65, 56, 48, 40, 33, 26, 18, 10, 3];
|
|
4994
|
+
return Object.fromEntries(
|
|
4995
|
+
lightnessSteps.map((l, i) => {
|
|
4996
|
+
const [r, g, b] = this.hslToRgb(h, s, l);
|
|
4997
|
+
return [String(i), `${r} ${g} ${b}`];
|
|
4998
|
+
})
|
|
4999
|
+
);
|
|
5000
|
+
}
|
|
5001
|
+
applyScale(scale) {
|
|
5002
|
+
const el = this.element;
|
|
5003
|
+
Object.entries(scale).forEach(([step, channels]) => {
|
|
5004
|
+
el.style.setProperty(`--${this.scaleTarget}-${step}-channels`, channels);
|
|
5005
|
+
});
|
|
5006
|
+
}
|
|
5007
|
+
hexToHsl(hex) {
|
|
5008
|
+
const r = parseInt(hex.slice(1, 3), 16) / 255;
|
|
5009
|
+
const g = parseInt(hex.slice(3, 5), 16) / 255;
|
|
5010
|
+
const b = parseInt(hex.slice(5, 7), 16) / 255;
|
|
5011
|
+
const max = Math.max(r, g, b);
|
|
5012
|
+
const min = Math.min(r, g, b);
|
|
5013
|
+
const l = (max + min) / 2;
|
|
5014
|
+
if (max === min)
|
|
5015
|
+
return [0, 0, l * 100];
|
|
5016
|
+
const d = max - min;
|
|
5017
|
+
const s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
|
|
5018
|
+
const h = max === r ? ((g - b) / d + (g < b ? 6 : 0)) / 6 : max === g ? ((b - r) / d + 2) / 6 : ((r - g) / d + 4) / 6;
|
|
5019
|
+
return [h * 360, s * 100, l * 100];
|
|
5020
|
+
}
|
|
5021
|
+
hslToRgb(h, s, l) {
|
|
5022
|
+
h /= 360;
|
|
5023
|
+
s /= 100;
|
|
5024
|
+
l /= 100;
|
|
5025
|
+
if (s === 0) {
|
|
5026
|
+
const v = Math.round(l * 255);
|
|
5027
|
+
return [v, v, v];
|
|
5028
|
+
}
|
|
5029
|
+
const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
|
|
5030
|
+
const p = 2 * l - q;
|
|
5031
|
+
return [
|
|
5032
|
+
Math.round(this.hue2rgb(p, q, h + 1 / 3) * 255),
|
|
5033
|
+
Math.round(this.hue2rgb(p, q, h) * 255),
|
|
5034
|
+
Math.round(this.hue2rgb(p, q, h - 1 / 3) * 255)
|
|
5035
|
+
];
|
|
5036
|
+
}
|
|
5037
|
+
hue2rgb(p, q, t) {
|
|
5038
|
+
if (t < 0)
|
|
5039
|
+
t += 1;
|
|
5040
|
+
if (t > 1)
|
|
5041
|
+
t -= 1;
|
|
5042
|
+
if (t < 1 / 6)
|
|
5043
|
+
return p + (q - p) * 6 * t;
|
|
5044
|
+
if (t < 1 / 2)
|
|
5045
|
+
return q;
|
|
5046
|
+
if (t < 2 / 3)
|
|
5047
|
+
return p + (q - p) * (2 / 3 - t) * 6;
|
|
5048
|
+
return p;
|
|
5049
|
+
}
|
|
5050
|
+
}
|
|
5051
|
+
|
|
5052
|
+
class Notification extends Controller {
|
|
5053
|
+
container = null;
|
|
5054
|
+
observer = null;
|
|
5055
|
+
connect() {
|
|
5056
|
+
this.container = this.element.closest("[popover]");
|
|
5057
|
+
this.container?.showPopover?.();
|
|
5058
|
+
requestAnimationFrame(() => {
|
|
5059
|
+
this.element.classList.add("visible");
|
|
5060
|
+
});
|
|
5061
|
+
const frame = this.element.parentElement;
|
|
5062
|
+
if (frame) {
|
|
5063
|
+
this.observer = new MutationObserver(() => this.syncCountdown());
|
|
5064
|
+
this.observer.observe(frame, { childList: true });
|
|
5065
|
+
setTimeout(() => this.syncCountdown());
|
|
5066
|
+
}
|
|
5067
|
+
}
|
|
5068
|
+
disconnect() {
|
|
5069
|
+
this.observer?.disconnect();
|
|
5070
|
+
this.observer = null;
|
|
5071
|
+
if (this.container) {
|
|
5072
|
+
const frame = this.container.querySelector("turbo-frame");
|
|
5073
|
+
if (frame?.children.length === 0 && this.container.matches(":popover-open")) {
|
|
5074
|
+
this.container.hidePopover?.();
|
|
5075
|
+
}
|
|
5076
|
+
this.container = null;
|
|
5077
|
+
}
|
|
5078
|
+
}
|
|
5079
|
+
close() {
|
|
5080
|
+
if (!this.element.classList.contains("animated")) {
|
|
5081
|
+
this.element.remove();
|
|
5082
|
+
return;
|
|
5083
|
+
}
|
|
5084
|
+
this.element.classList.add("closing");
|
|
5085
|
+
setTimeout(() => {
|
|
5086
|
+
this.element.remove();
|
|
5087
|
+
}, 350);
|
|
5088
|
+
}
|
|
5089
|
+
syncCountdown() {
|
|
5090
|
+
const timerEl = this.element.querySelector('[data-controller~="midwest-countdown-timer"]');
|
|
5091
|
+
if (!timerEl)
|
|
5092
|
+
return;
|
|
5093
|
+
const timer = this.application.getControllerForElementAndIdentifier(
|
|
5094
|
+
timerEl,
|
|
5095
|
+
"midwest-countdown-timer"
|
|
5096
|
+
);
|
|
5097
|
+
if (!timer)
|
|
5098
|
+
return;
|
|
5099
|
+
const isTop = this.element === this.element.parentElement?.firstElementChild;
|
|
5100
|
+
if (isTop) {
|
|
5101
|
+
timer.resume();
|
|
5102
|
+
} else {
|
|
5103
|
+
timer.pause();
|
|
5104
|
+
}
|
|
5105
|
+
}
|
|
5106
|
+
}
|
|
5107
|
+
|
|
4180
5108
|
function registerMidwestControllers(application) {
|
|
4181
5109
|
application.register("midwest-card", Card);
|
|
4182
5110
|
application.register("midwest-banner", Banner);
|
|
@@ -4198,6 +5126,9 @@ function registerMidwestControllers(application) {
|
|
|
4198
5126
|
application.register("midwest-range", Range);
|
|
4199
5127
|
application.register("midwest-rating", Rating);
|
|
4200
5128
|
application.register("midwest-autocomplete", Autocomplete);
|
|
5129
|
+
application.register("midwest-image", Image);
|
|
5130
|
+
application.register("midwest-color-picker", ColorPicker);
|
|
5131
|
+
application.register("midwest-notification", Notification);
|
|
4201
5132
|
}
|
|
4202
5133
|
|
|
4203
5134
|
export { Banner, Card, Chart, CountdownTimer, registerMidwestControllers };
|