@unabridged/midwest 0.24.3 → 0.24.4
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 +8 -0
- package/app/assets/javascript/midwest/map_providers.ts +61 -0
- package/app/assets/javascript/midwest.js +972 -6
- 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 +8 -0
- package/dist/javascript/collection/app/assets/javascript/midwest/index.js.map +1 -1
- package/dist/javascript/collection/app/assets/javascript/midwest/map_providers.js +56 -0
- package/dist/javascript/collection/app/assets/javascript/midwest/map_providers.js.map +1 -0
- package/dist/javascript/collection/app/components/midwest/form/address_component/address_component_controller.js +441 -0
- package/dist/javascript/collection/app/components/midwest/form/address_component/address_component_controller.js.map +1 -0
- package/dist/javascript/collection/app/components/midwest/form/live_summary_component/live_summary_component_controller.js +55 -1
- 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/map_component/map_component_controller.js +297 -0
- package/dist/javascript/collection/app/components/midwest/map_component/map_component_controller.js.map +1 -0
- package/dist/javascript/collection/app/components/midwest/onboarding_component/onboarding_component_controller.js +34 -5
- package/dist/javascript/collection/app/components/midwest/onboarding_component/onboarding_component_controller.js.map +1 -1
- package/dist/javascript/collection/app/components/midwest/page_transition_component/page_transition_component_controller.js +38 -0
- package/dist/javascript/collection/app/components/midwest/page_transition_component/page_transition_component_controller.js.map +1 -0
- package/dist/javascript/collection/app/components/midwest/popover_component/popover_component_controller.js +67 -0
- package/dist/javascript/collection/app/components/midwest/popover_component/popover_component_controller.js.map +1 -0
- package/dist/javascript/midwest.js +972 -6
- package/dist/javascript/midwest.js.map +1 -1
- package/package.json +1 -1
|
@@ -3299,6 +3299,7 @@ class FormLiveSummary extends Controller {
|
|
|
3299
3299
|
this.form?.addEventListener("midwest-autocomplete:change", this.render);
|
|
3300
3300
|
this.form?.addEventListener("midwest-color-picker:update", this.render);
|
|
3301
3301
|
this.form?.addEventListener("midwest-repeatable:change", this.render);
|
|
3302
|
+
this.form?.addEventListener("midwest-address:geocode", this.render);
|
|
3302
3303
|
}
|
|
3303
3304
|
disconnect() {
|
|
3304
3305
|
this.form?.removeEventListener("input", this.render);
|
|
@@ -3306,6 +3307,7 @@ class FormLiveSummary extends Controller {
|
|
|
3306
3307
|
this.form?.removeEventListener("midwest-autocomplete:change", this.render);
|
|
3307
3308
|
this.form?.removeEventListener("midwest-color-picker:update", this.render);
|
|
3308
3309
|
this.form?.removeEventListener("midwest-repeatable:change", this.render);
|
|
3310
|
+
this.form?.removeEventListener("midwest-address:geocode", this.render);
|
|
3309
3311
|
}
|
|
3310
3312
|
get form() {
|
|
3311
3313
|
return this.element.closest("form");
|
|
@@ -3323,7 +3325,7 @@ class FormLiveSummary extends Controller {
|
|
|
3323
3325
|
}
|
|
3324
3326
|
const dl = document.createElement("dl");
|
|
3325
3327
|
dl.className = "midwest-form-live-summary-list";
|
|
3326
|
-
fields.forEach(({ label, value, swatchColor }) => {
|
|
3328
|
+
fields.forEach(({ label, value, swatchColor, details }) => {
|
|
3327
3329
|
const dt = Object.assign(document.createElement("dt"), {
|
|
3328
3330
|
className: "midwest-form-live-summary-term",
|
|
3329
3331
|
textContent: label
|
|
@@ -3332,6 +3334,8 @@ class FormLiveSummary extends Controller {
|
|
|
3332
3334
|
dd.className = "midwest-form-live-summary-definition";
|
|
3333
3335
|
if (swatchColor !== void 0) {
|
|
3334
3336
|
dd.append(this.buildSwatch(swatchColor), value || "\u2014");
|
|
3337
|
+
} else if (details && Object.keys(details).length > 0) {
|
|
3338
|
+
dd.append(this.buildGeocode(value || "\u2014", details));
|
|
3335
3339
|
} else {
|
|
3336
3340
|
dd.textContent = value || "\u2014";
|
|
3337
3341
|
}
|
|
@@ -3359,6 +3363,8 @@ class FormLiveSummary extends Controller {
|
|
|
3359
3363
|
return;
|
|
3360
3364
|
if (element.closest(".midwest-repeatable"))
|
|
3361
3365
|
return;
|
|
3366
|
+
if (element.closest(".midwest-address"))
|
|
3367
|
+
return;
|
|
3362
3368
|
if (element instanceof HTMLInputElement) {
|
|
3363
3369
|
if (SKIPPED_INPUT_TYPES.has(element.type))
|
|
3364
3370
|
return;
|
|
@@ -3478,6 +3484,29 @@ class FormLiveSummary extends Controller {
|
|
|
3478
3484
|
});
|
|
3479
3485
|
});
|
|
3480
3486
|
});
|
|
3487
|
+
form.querySelectorAll(".midwest-address").forEach((addr) => {
|
|
3488
|
+
if (this.element.contains(addr))
|
|
3489
|
+
return;
|
|
3490
|
+
const input = addr.querySelector('[data-midwest-address-target="input"]');
|
|
3491
|
+
const latInput = addr.querySelector('[data-midwest-address-target="latInput"]');
|
|
3492
|
+
const lngInput = addr.querySelector('[data-midwest-address-target="lngInput"]');
|
|
3493
|
+
const placeIdInput = addr.querySelector('[data-midwest-address-target="placeIdInput"]');
|
|
3494
|
+
const labelEl = addr.querySelector(".midwest-form-group__top label");
|
|
3495
|
+
const label = labelEl?.textContent?.trim() || this.prettifyName((input?.name ?? latInput?.name?.replace(/_latitude$/, "") ?? "").replace(/\[.*\]$/, ""));
|
|
3496
|
+
const value = input?.value?.trim() || "";
|
|
3497
|
+
const details = {};
|
|
3498
|
+
if (latInput?.value)
|
|
3499
|
+
details["Latitude"] = latInput.value;
|
|
3500
|
+
if (lngInput?.value)
|
|
3501
|
+
details["Longitude"] = lngInput.value;
|
|
3502
|
+
if (placeIdInput?.value)
|
|
3503
|
+
details["Place ID"] = placeIdInput.value;
|
|
3504
|
+
fields.push({
|
|
3505
|
+
label,
|
|
3506
|
+
value: value || "\u2014",
|
|
3507
|
+
details: Object.keys(details).length > 0 ? details : void 0
|
|
3508
|
+
});
|
|
3509
|
+
});
|
|
3481
3510
|
return fields;
|
|
3482
3511
|
}
|
|
3483
3512
|
// Builds a small inline colour swatch to display alongside a colour value.
|
|
@@ -3496,6 +3525,31 @@ class FormLiveSummary extends Controller {
|
|
|
3496
3525
|
}
|
|
3497
3526
|
return swatch;
|
|
3498
3527
|
}
|
|
3528
|
+
// Wraps an address value in a span that reveals a geocode detail popup on hover.
|
|
3529
|
+
buildGeocode(value, details) {
|
|
3530
|
+
const wrapper = document.createElement("span");
|
|
3531
|
+
wrapper.className = "midwest-form-live-summary-geocode";
|
|
3532
|
+
const text = document.createElement("span");
|
|
3533
|
+
text.className = "midwest-form-live-summary-geocode-text";
|
|
3534
|
+
text.textContent = value;
|
|
3535
|
+
const popup = document.createElement("span");
|
|
3536
|
+
popup.className = "midwest-form-live-summary-geocode-popup";
|
|
3537
|
+
popup.setAttribute("aria-hidden", "true");
|
|
3538
|
+
Object.entries(details).forEach(([key, val]) => {
|
|
3539
|
+
const row = document.createElement("span");
|
|
3540
|
+
row.className = "midwest-form-live-summary-geocode-row";
|
|
3541
|
+
const keyEl = document.createElement("span");
|
|
3542
|
+
keyEl.className = "midwest-form-live-summary-geocode-key";
|
|
3543
|
+
keyEl.textContent = key;
|
|
3544
|
+
const valEl = document.createElement("span");
|
|
3545
|
+
valEl.className = "midwest-form-live-summary-geocode-val";
|
|
3546
|
+
valEl.textContent = val;
|
|
3547
|
+
row.append(keyEl, valEl);
|
|
3548
|
+
popup.append(row);
|
|
3549
|
+
});
|
|
3550
|
+
wrapper.append(text, popup);
|
|
3551
|
+
return wrapper;
|
|
3552
|
+
}
|
|
3499
3553
|
// Finds the label associated with a specific element via `for` attribute,
|
|
3500
3554
|
// wrapping label, or falls back to a prettified version of the element name.
|
|
3501
3555
|
labelFor(el) {
|
|
@@ -7060,14 +7114,14 @@ const FOCUSABLE = [
|
|
|
7060
7114
|
].join(", ");
|
|
7061
7115
|
const GAP = 12;
|
|
7062
7116
|
class Onboarding extends Controller {
|
|
7063
|
-
static targets = ["step", "overlay", "card", "title", "body", "back", "next", "done", "arrow"];
|
|
7117
|
+
static targets = ["step", "overlay", "card", "title", "body", "back", "next", "done", "arrow", "motion"];
|
|
7064
7118
|
static values = {
|
|
7065
|
-
autoOpen: { type: Boolean, default: false }
|
|
7066
|
-
confirm: { type: String, default: "" }
|
|
7119
|
+
autoOpen: { type: Boolean, default: false }
|
|
7067
7120
|
};
|
|
7068
7121
|
steps = [];
|
|
7069
7122
|
index = 0;
|
|
7070
7123
|
active = false;
|
|
7124
|
+
justOpened = false;
|
|
7071
7125
|
previousFocus = null;
|
|
7072
7126
|
highlighted = null;
|
|
7073
7127
|
connect() {
|
|
@@ -7097,6 +7151,7 @@ class Onboarding extends Controller {
|
|
|
7097
7151
|
this.previousFocus = document.activeElement;
|
|
7098
7152
|
this.overlayTarget.hidden = false;
|
|
7099
7153
|
this.cardTarget.hidden = false;
|
|
7154
|
+
this.justOpened = true;
|
|
7100
7155
|
document.addEventListener("keydown", this.handleKeydown, true);
|
|
7101
7156
|
window.addEventListener("resize", this.reposition);
|
|
7102
7157
|
window.addEventListener("scroll", this.reposition, true);
|
|
@@ -7131,14 +7186,14 @@ class Onboarding extends Controller {
|
|
|
7131
7186
|
cancel() {
|
|
7132
7187
|
if (!this.active)
|
|
7133
7188
|
return;
|
|
7134
|
-
if (this.confirmValue && !window.confirm(this.confirmValue))
|
|
7135
|
-
return;
|
|
7136
7189
|
this.teardown();
|
|
7137
7190
|
this.dispatch("cancel", { bubbles: true });
|
|
7138
7191
|
}
|
|
7139
7192
|
render(index) {
|
|
7140
7193
|
this.index = index;
|
|
7141
7194
|
const step = this.steps[index];
|
|
7195
|
+
const replay = !this.justOpened;
|
|
7196
|
+
this.justOpened = false;
|
|
7142
7197
|
this.clearHighlight();
|
|
7143
7198
|
this.titleTarget.textContent = step.title;
|
|
7144
7199
|
this.renderBody(step);
|
|
@@ -7150,6 +7205,9 @@ class Onboarding extends Controller {
|
|
|
7150
7205
|
} else {
|
|
7151
7206
|
this.center();
|
|
7152
7207
|
}
|
|
7208
|
+
if (replay && this.hasMotionTarget) {
|
|
7209
|
+
this.replayMotion(this.cardTarget.dataset.placement ?? "center");
|
|
7210
|
+
}
|
|
7153
7211
|
this.dispatch("show", { detail: { index, name: step.name }, bubbles: true });
|
|
7154
7212
|
this.focusCard();
|
|
7155
7213
|
}
|
|
@@ -7251,9 +7309,34 @@ class Onboarding extends Controller {
|
|
|
7251
7309
|
this.cardTarget.focus();
|
|
7252
7310
|
});
|
|
7253
7311
|
}
|
|
7312
|
+
// Re-triggers the midwest_motion @starting-style entry on step transitions.
|
|
7313
|
+
// The initial show is handled by @starting-style firing when the card's
|
|
7314
|
+
// `hidden` is removed; subsequent steps need an explicit re-trigger since the
|
|
7315
|
+
// card stays in the DOM. Toggling `hidden` on the motion element triggers
|
|
7316
|
+
// @starting-style again; the rAF ensures both DOM writes land in the same
|
|
7317
|
+
// paint so there is no blank-card flash.
|
|
7318
|
+
replayMotion(placement) {
|
|
7319
|
+
const el = this.motionTarget;
|
|
7320
|
+
const dir = { bottom: "up", top: "down", right: "left", left: "right" };
|
|
7321
|
+
el.classList.remove("from-up", "from-down", "from-left", "from-right");
|
|
7322
|
+
const from = dir[placement];
|
|
7323
|
+
if (from)
|
|
7324
|
+
el.classList.add(`from-${from}`);
|
|
7325
|
+
el.hidden = true;
|
|
7326
|
+
requestAnimationFrame(() => {
|
|
7327
|
+
if (this.active)
|
|
7328
|
+
el.hidden = false;
|
|
7329
|
+
});
|
|
7330
|
+
}
|
|
7254
7331
|
teardown() {
|
|
7255
7332
|
this.active = false;
|
|
7256
7333
|
this.clearHighlight();
|
|
7334
|
+
this.element.querySelectorAll("[popover]").forEach((el) => {
|
|
7335
|
+
try {
|
|
7336
|
+
el.hidePopover();
|
|
7337
|
+
} catch {
|
|
7338
|
+
}
|
|
7339
|
+
});
|
|
7257
7340
|
this.overlayTarget.hidden = true;
|
|
7258
7341
|
this.cardTarget.hidden = true;
|
|
7259
7342
|
document.removeEventListener("keydown", this.handleKeydown, true);
|
|
@@ -7892,6 +7975,885 @@ class Motion extends Controller {
|
|
|
7892
7975
|
}
|
|
7893
7976
|
}
|
|
7894
7977
|
|
|
7978
|
+
class PageTransition extends Controller {
|
|
7979
|
+
static values = {
|
|
7980
|
+
style: { type: String, default: "fade" },
|
|
7981
|
+
duration: { type: String, default: "" },
|
|
7982
|
+
easing: { type: String, default: "" }
|
|
7983
|
+
};
|
|
7984
|
+
injectedMeta = null;
|
|
7985
|
+
connect() {
|
|
7986
|
+
if (this.styleValue === "none")
|
|
7987
|
+
return;
|
|
7988
|
+
if (!document.querySelector('meta[name="view-transition"]')) {
|
|
7989
|
+
const meta = document.createElement("meta");
|
|
7990
|
+
meta.name = "view-transition";
|
|
7991
|
+
meta.content = "same-origin";
|
|
7992
|
+
document.head.appendChild(meta);
|
|
7993
|
+
this.injectedMeta = meta;
|
|
7994
|
+
}
|
|
7995
|
+
document.documentElement.dataset.midwestPageTransition = this.styleValue;
|
|
7996
|
+
if (this.durationValue) {
|
|
7997
|
+
document.documentElement.style.setProperty("--page-transition-duration", this.durationValue);
|
|
7998
|
+
}
|
|
7999
|
+
if (this.easingValue) {
|
|
8000
|
+
document.documentElement.style.setProperty("--page-transition-easing", this.easingValue);
|
|
8001
|
+
}
|
|
8002
|
+
}
|
|
8003
|
+
disconnect() {
|
|
8004
|
+
this.injectedMeta?.remove();
|
|
8005
|
+
this.injectedMeta = null;
|
|
8006
|
+
delete document.documentElement.dataset.midwestPageTransition;
|
|
8007
|
+
document.documentElement.style.removeProperty("--page-transition-duration");
|
|
8008
|
+
document.documentElement.style.removeProperty("--page-transition-easing");
|
|
8009
|
+
}
|
|
8010
|
+
}
|
|
8011
|
+
|
|
8012
|
+
let mapkitScriptPromise = null;
|
|
8013
|
+
let googleMapsScriptPromise = null;
|
|
8014
|
+
let leafletScriptPromise = null;
|
|
8015
|
+
function loadMapKitScript() {
|
|
8016
|
+
if (window.mapkit)
|
|
8017
|
+
return Promise.resolve();
|
|
8018
|
+
if (mapkitScriptPromise)
|
|
8019
|
+
return mapkitScriptPromise;
|
|
8020
|
+
mapkitScriptPromise = new Promise((resolve, reject) => {
|
|
8021
|
+
const script = document.createElement("script");
|
|
8022
|
+
script.src = "https://cdn.apple-mapkit.com/mk/5.x.x/mapkit.js";
|
|
8023
|
+
script.crossOrigin = "anonymous";
|
|
8024
|
+
script.addEventListener("load", () => resolve());
|
|
8025
|
+
script.addEventListener("error", () => reject(new Error("Failed to load MapKit JS")));
|
|
8026
|
+
document.head.appendChild(script);
|
|
8027
|
+
});
|
|
8028
|
+
return mapkitScriptPromise;
|
|
8029
|
+
}
|
|
8030
|
+
function loadLeafletScript() {
|
|
8031
|
+
if (window.L)
|
|
8032
|
+
return Promise.resolve();
|
|
8033
|
+
if (leafletScriptPromise)
|
|
8034
|
+
return leafletScriptPromise;
|
|
8035
|
+
leafletScriptPromise = new Promise((resolve, reject) => {
|
|
8036
|
+
if (!document.querySelector("link[data-midwest-leaflet]")) {
|
|
8037
|
+
const link = document.createElement("link");
|
|
8038
|
+
link.rel = "stylesheet";
|
|
8039
|
+
link.href = "https://unpkg.com/leaflet@1.9.4/dist/leaflet.css";
|
|
8040
|
+
link.dataset.midwestLeaflet = "";
|
|
8041
|
+
document.head.appendChild(link);
|
|
8042
|
+
}
|
|
8043
|
+
const script = document.createElement("script");
|
|
8044
|
+
script.src = "https://unpkg.com/leaflet@1.9.4/dist/leaflet.js";
|
|
8045
|
+
script.addEventListener("load", () => resolve());
|
|
8046
|
+
script.addEventListener("error", () => reject(new Error("Failed to load Leaflet")));
|
|
8047
|
+
document.head.appendChild(script);
|
|
8048
|
+
});
|
|
8049
|
+
return leafletScriptPromise;
|
|
8050
|
+
}
|
|
8051
|
+
function loadGoogleMapsScript(apiKey) {
|
|
8052
|
+
if (window.google?.maps)
|
|
8053
|
+
return Promise.resolve();
|
|
8054
|
+
if (googleMapsScriptPromise)
|
|
8055
|
+
return googleMapsScriptPromise;
|
|
8056
|
+
googleMapsScriptPromise = new Promise((resolve, reject) => {
|
|
8057
|
+
window.__midwestGoogleMapsReady = resolve;
|
|
8058
|
+
const script = document.createElement("script");
|
|
8059
|
+
script.src = `https://maps.googleapis.com/maps/api/js?key=${encodeURIComponent(apiKey)}&callback=__midwestGoogleMapsReady&loading=async&libraries=places`;
|
|
8060
|
+
script.addEventListener("error", () => reject(new Error("Failed to load Google Maps")));
|
|
8061
|
+
document.head.appendChild(script);
|
|
8062
|
+
});
|
|
8063
|
+
return googleMapsScriptPromise;
|
|
8064
|
+
}
|
|
8065
|
+
|
|
8066
|
+
const LEAFLET_TILES = {
|
|
8067
|
+
MutedStandard: {
|
|
8068
|
+
url: "https://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}{r}.png",
|
|
8069
|
+
attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors © <a href="https://carto.com/attributions">CARTO</a>'
|
|
8070
|
+
},
|
|
8071
|
+
Standard: {
|
|
8072
|
+
url: "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png",
|
|
8073
|
+
attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors',
|
|
8074
|
+
maxZoom: 19
|
|
8075
|
+
},
|
|
8076
|
+
Hybrid: {
|
|
8077
|
+
url: "https://{s}.basemaps.cartocdn.com/rastertiles/voyager/{z}/{x}/{y}{r}.png",
|
|
8078
|
+
attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors © <a href="https://carto.com/attributions">CARTO</a>'
|
|
8079
|
+
},
|
|
8080
|
+
Satellite: {
|
|
8081
|
+
url: "https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}",
|
|
8082
|
+
attribution: "Tiles © Esri — Source: Esri, i-cubed, USDA, USGS, AEX, GeoEye, Getmapping, Aerogrid, IGN, IGP, UPR-EGP, and the GIS User Community",
|
|
8083
|
+
maxZoom: 18
|
|
8084
|
+
},
|
|
8085
|
+
Dark: {
|
|
8086
|
+
url: "https://{s}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}{r}.png",
|
|
8087
|
+
attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors © <a href="https://carto.com/attributions">CARTO</a>'
|
|
8088
|
+
}
|
|
8089
|
+
};
|
|
8090
|
+
const GOOGLE_MUTED_STYLES = [
|
|
8091
|
+
{ elementType: "geometry", stylers: [{ color: "#f5f5f5" }] },
|
|
8092
|
+
{ elementType: "labels.icon", stylers: [{ visibility: "off" }] },
|
|
8093
|
+
{ elementType: "labels.text.fill", stylers: [{ color: "#616161" }] },
|
|
8094
|
+
{ elementType: "labels.text.stroke", stylers: [{ color: "#f5f5f5" }] },
|
|
8095
|
+
{ featureType: "administrative.land_parcel", elementType: "labels", stylers: [{ visibility: "off" }] },
|
|
8096
|
+
{ featureType: "poi", elementType: "geometry", stylers: [{ color: "#eeeeee" }] },
|
|
8097
|
+
{ featureType: "poi.park", elementType: "geometry", stylers: [{ color: "#e5e5e5" }] },
|
|
8098
|
+
{ featureType: "road", elementType: "geometry", stylers: [{ color: "#ffffff" }] },
|
|
8099
|
+
{ featureType: "road.highway", elementType: "geometry", stylers: [{ color: "#dadada" }] },
|
|
8100
|
+
{ featureType: "water", elementType: "geometry", stylers: [{ color: "#c9c9c9" }] }
|
|
8101
|
+
];
|
|
8102
|
+
const GOOGLE_DARK_STYLES = [
|
|
8103
|
+
{ elementType: "geometry", stylers: [{ color: "#242f3e" }] },
|
|
8104
|
+
{ elementType: "labels.text.stroke", stylers: [{ color: "#242f3e" }] },
|
|
8105
|
+
{ elementType: "labels.text.fill", stylers: [{ color: "#746855" }] },
|
|
8106
|
+
{ featureType: "administrative.locality", elementType: "labels.text.fill", stylers: [{ color: "#d59563" }] },
|
|
8107
|
+
{ featureType: "poi", elementType: "labels.text.fill", stylers: [{ color: "#d59563" }] },
|
|
8108
|
+
{ featureType: "poi.park", elementType: "geometry", stylers: [{ color: "#263c3f" }] },
|
|
8109
|
+
{ featureType: "poi.park", elementType: "labels.text.fill", stylers: [{ color: "#6b9a76" }] },
|
|
8110
|
+
{ featureType: "road", elementType: "geometry", stylers: [{ color: "#38414e" }] },
|
|
8111
|
+
{ featureType: "road", elementType: "geometry.stroke", stylers: [{ color: "#212a37" }] },
|
|
8112
|
+
{ featureType: "road", elementType: "labels.text.fill", stylers: [{ color: "#9ca5b3" }] },
|
|
8113
|
+
{ featureType: "road.highway", elementType: "geometry", stylers: [{ color: "#746855" }] },
|
|
8114
|
+
{ featureType: "road.highway", elementType: "geometry.stroke", stylers: [{ color: "#1f2835" }] },
|
|
8115
|
+
{ featureType: "road.highway", elementType: "labels.text.fill", stylers: [{ color: "#f3d19c" }] },
|
|
8116
|
+
{ featureType: "transit", elementType: "geometry", stylers: [{ color: "#2f3948" }] },
|
|
8117
|
+
{ featureType: "transit.station", elementType: "labels.text.fill", stylers: [{ color: "#d59563" }] },
|
|
8118
|
+
{ featureType: "water", elementType: "geometry", stylers: [{ color: "#17263c" }] },
|
|
8119
|
+
{ featureType: "water", elementType: "labels.text.fill", stylers: [{ color: "#515c6d" }] },
|
|
8120
|
+
{ featureType: "water", elementType: "labels.text.stroke", stylers: [{ color: "#17263c" }] }
|
|
8121
|
+
];
|
|
8122
|
+
let Map$1 = class Map extends Controller {
|
|
8123
|
+
static values = {
|
|
8124
|
+
latitude: { type: Number, default: 37.334886 },
|
|
8125
|
+
longitude: { type: Number, default: -122.008988 },
|
|
8126
|
+
span: { type: Number, default: 0.1 },
|
|
8127
|
+
mapType: { type: String, default: "MutedStandard" },
|
|
8128
|
+
colorScheme: { type: String, default: "Auto" },
|
|
8129
|
+
provider: { type: String, default: "" },
|
|
8130
|
+
token: { type: String, default: "" },
|
|
8131
|
+
tokenUrl: { type: String, default: "" },
|
|
8132
|
+
apiKey: { type: String, default: "" },
|
|
8133
|
+
zoom: { type: Number, default: -1 },
|
|
8134
|
+
markers: { type: Array, default: [] },
|
|
8135
|
+
showsUserLocation: { type: Boolean, default: false },
|
|
8136
|
+
showsMapTypeControl: { type: Boolean, default: false },
|
|
8137
|
+
showsZoomControl: { type: Boolean, default: true },
|
|
8138
|
+
showsCompass: { type: String, default: "Adaptive" },
|
|
8139
|
+
rotation: { type: Number, default: 0 }
|
|
8140
|
+
};
|
|
8141
|
+
mapInstance = null;
|
|
8142
|
+
async connect() {
|
|
8143
|
+
const provider = this.providerValue;
|
|
8144
|
+
if (!provider)
|
|
8145
|
+
return;
|
|
8146
|
+
try {
|
|
8147
|
+
if (provider === "mapkit") {
|
|
8148
|
+
await loadMapKitScript();
|
|
8149
|
+
this.initMapKit();
|
|
8150
|
+
this.initMapKitMap();
|
|
8151
|
+
} else if (provider === "google") {
|
|
8152
|
+
await loadGoogleMapsScript(this.apiKeyValue);
|
|
8153
|
+
this.initGoogleMap();
|
|
8154
|
+
} else if (provider === "leaflet") {
|
|
8155
|
+
await loadLeafletScript();
|
|
8156
|
+
this.initLeafletMap();
|
|
8157
|
+
}
|
|
8158
|
+
} catch (err) {
|
|
8159
|
+
console.error(`[midwest-map] Failed to initialise ${provider} map:`, err);
|
|
8160
|
+
}
|
|
8161
|
+
}
|
|
8162
|
+
disconnect() {
|
|
8163
|
+
if (this.mapInstance) {
|
|
8164
|
+
if (this.providerValue === "mapkit")
|
|
8165
|
+
this.mapInstance.destroy();
|
|
8166
|
+
if (this.providerValue === "leaflet")
|
|
8167
|
+
this.mapInstance.remove();
|
|
8168
|
+
this.mapInstance = null;
|
|
8169
|
+
}
|
|
8170
|
+
}
|
|
8171
|
+
tokenValueChanged() {
|
|
8172
|
+
if (this.mapInstance && this.providerValue === "mapkit")
|
|
8173
|
+
this.initMapKit();
|
|
8174
|
+
}
|
|
8175
|
+
// --- MapKit ---
|
|
8176
|
+
initMapKit() {
|
|
8177
|
+
const mk = window.mapkit;
|
|
8178
|
+
const token = this.tokenValue;
|
|
8179
|
+
const tokenUrl = this.tokenUrlValue;
|
|
8180
|
+
mk.init({
|
|
8181
|
+
authorizationCallback: (done) => {
|
|
8182
|
+
if (token) {
|
|
8183
|
+
done(token);
|
|
8184
|
+
} else if (tokenUrl) {
|
|
8185
|
+
fetch(tokenUrl).then((r) => r.text()).then(done).catch((err) => console.error("[midwest-map] Token fetch failed:", err));
|
|
8186
|
+
}
|
|
8187
|
+
},
|
|
8188
|
+
language: document.documentElement.lang || "en"
|
|
8189
|
+
});
|
|
8190
|
+
}
|
|
8191
|
+
initMapKitMap() {
|
|
8192
|
+
const mk = window.mapkit;
|
|
8193
|
+
const region = new mk.CoordinateRegion(
|
|
8194
|
+
new mk.Coordinate(this.latitudeValue, this.longitudeValue),
|
|
8195
|
+
new mk.CoordinateSpan(this.spanValue, this.spanValue)
|
|
8196
|
+
);
|
|
8197
|
+
const colorScheme = this.colorSchemeValue === "Auto" ? window.matchMedia("(prefers-color-scheme: dark)").matches ? mk.Map.ColorSchemes.Dark : mk.Map.ColorSchemes.Light : mk.Map.ColorSchemes[this.colorSchemeValue];
|
|
8198
|
+
this.mapInstance = new mk.Map(this.element, {
|
|
8199
|
+
region,
|
|
8200
|
+
mapType: mk.Map.MapTypes[this.mapTypeValue],
|
|
8201
|
+
colorScheme,
|
|
8202
|
+
showsUserLocation: this.showsUserLocationValue,
|
|
8203
|
+
showsMapTypeControl: this.showsMapTypeControlValue,
|
|
8204
|
+
showsZoomControl: this.showsZoomControlValue,
|
|
8205
|
+
showsCompass: mk.FeatureVisibility[this.showsCompassValue],
|
|
8206
|
+
rotation: this.rotationValue
|
|
8207
|
+
});
|
|
8208
|
+
this.addMapKitMarkers();
|
|
8209
|
+
this.element.dataset.mapLoaded = "true";
|
|
8210
|
+
}
|
|
8211
|
+
addMapKitMarkers() {
|
|
8212
|
+
const mk = window.mapkit;
|
|
8213
|
+
if (!this.markersValue.length)
|
|
8214
|
+
return;
|
|
8215
|
+
const annotations = this.markersValue.map((m) => {
|
|
8216
|
+
const opts = {};
|
|
8217
|
+
if (m.title)
|
|
8218
|
+
opts.title = m.title;
|
|
8219
|
+
if (m.subtitle)
|
|
8220
|
+
opts.subtitle = m.subtitle;
|
|
8221
|
+
if (m.color)
|
|
8222
|
+
opts.color = m.color;
|
|
8223
|
+
if (m.glyphText)
|
|
8224
|
+
opts.glyphText = m.glyphText;
|
|
8225
|
+
return new mk.MarkerAnnotation(
|
|
8226
|
+
new mk.Coordinate(m.latitude, m.longitude),
|
|
8227
|
+
opts
|
|
8228
|
+
);
|
|
8229
|
+
});
|
|
8230
|
+
this.mapInstance.addAnnotations(annotations);
|
|
8231
|
+
const openIndex = this.markersValue.findIndex((m) => m.open);
|
|
8232
|
+
if (openIndex >= 0) {
|
|
8233
|
+
this.mapInstance.selectedAnnotation = annotations[openIndex];
|
|
8234
|
+
}
|
|
8235
|
+
}
|
|
8236
|
+
// --- Google Maps ---
|
|
8237
|
+
initGoogleMap() {
|
|
8238
|
+
const g = window.google;
|
|
8239
|
+
const zoom = this.zoomValue > 0 ? this.zoomValue : this.spanToZoom(this.spanValue);
|
|
8240
|
+
const isDark = this.colorSchemeValue === "Dark" || this.colorSchemeValue === "Auto" && window.matchMedia("(prefers-color-scheme: dark)").matches;
|
|
8241
|
+
const mapTypeId = this.googleMapTypeId();
|
|
8242
|
+
const opts = {
|
|
8243
|
+
center: { lat: this.latitudeValue, lng: this.longitudeValue },
|
|
8244
|
+
zoom,
|
|
8245
|
+
mapTypeId,
|
|
8246
|
+
disableDefaultUI: true,
|
|
8247
|
+
zoomControl: this.showsZoomControlValue,
|
|
8248
|
+
mapTypeControl: this.showsMapTypeControlValue,
|
|
8249
|
+
fullscreenControl: false,
|
|
8250
|
+
streetViewControl: false
|
|
8251
|
+
};
|
|
8252
|
+
const styles = this.googleMapStyles(isDark, mapTypeId);
|
|
8253
|
+
if (styles)
|
|
8254
|
+
opts.styles = styles;
|
|
8255
|
+
this.mapInstance = new g.maps.Map(this.element, opts);
|
|
8256
|
+
this.addGoogleMarkers();
|
|
8257
|
+
this.element.dataset.mapLoaded = "true";
|
|
8258
|
+
}
|
|
8259
|
+
googleMapTypeId() {
|
|
8260
|
+
const types = {
|
|
8261
|
+
Standard: "roadmap",
|
|
8262
|
+
Hybrid: "hybrid",
|
|
8263
|
+
Satellite: "satellite",
|
|
8264
|
+
MutedStandard: "roadmap"
|
|
8265
|
+
};
|
|
8266
|
+
return types[this.mapTypeValue] ?? "roadmap";
|
|
8267
|
+
}
|
|
8268
|
+
googleMapStyles(isDark, mapTypeId) {
|
|
8269
|
+
if (mapTypeId !== "roadmap")
|
|
8270
|
+
return null;
|
|
8271
|
+
if (isDark)
|
|
8272
|
+
return GOOGLE_DARK_STYLES;
|
|
8273
|
+
if (this.mapTypeValue === "MutedStandard")
|
|
8274
|
+
return GOOGLE_MUTED_STYLES;
|
|
8275
|
+
return null;
|
|
8276
|
+
}
|
|
8277
|
+
addGoogleMarkers() {
|
|
8278
|
+
const g = window.google;
|
|
8279
|
+
if (!this.markersValue.length)
|
|
8280
|
+
return;
|
|
8281
|
+
this.markersValue.forEach((m) => {
|
|
8282
|
+
const marker = new g.maps.Marker({
|
|
8283
|
+
map: this.mapInstance,
|
|
8284
|
+
position: { lat: m.latitude, lng: m.longitude },
|
|
8285
|
+
title: m.title ?? ""
|
|
8286
|
+
});
|
|
8287
|
+
if (m.open && m.title) {
|
|
8288
|
+
const content = m.subtitle ? `<strong>${m.title}</strong><br><span style="color:#777">${m.subtitle}</span>` : `<strong>${m.title}</strong>`;
|
|
8289
|
+
const infoWindow = new g.maps.InfoWindow({ content });
|
|
8290
|
+
infoWindow.open(this.mapInstance, marker);
|
|
8291
|
+
}
|
|
8292
|
+
});
|
|
8293
|
+
}
|
|
8294
|
+
// --- Leaflet ---
|
|
8295
|
+
initLeafletMap() {
|
|
8296
|
+
const L = window.L;
|
|
8297
|
+
const zoom = this.zoomValue > 0 ? this.zoomValue : this.spanToZoom(this.spanValue);
|
|
8298
|
+
const isDark = this.colorSchemeValue === "Dark" || this.colorSchemeValue === "Auto" && window.matchMedia("(prefers-color-scheme: dark)").matches;
|
|
8299
|
+
const tileKey = isDark ? "Dark" : this.mapTypeValue;
|
|
8300
|
+
const tile = LEAFLET_TILES[tileKey] ?? LEAFLET_TILES.MutedStandard;
|
|
8301
|
+
this.mapInstance = L.map(this.element, {
|
|
8302
|
+
center: [this.latitudeValue, this.longitudeValue],
|
|
8303
|
+
zoom,
|
|
8304
|
+
zoomControl: this.showsZoomControlValue,
|
|
8305
|
+
attributionControl: true
|
|
8306
|
+
});
|
|
8307
|
+
L.tileLayer(tile.url, {
|
|
8308
|
+
attribution: tile.attribution,
|
|
8309
|
+
maxZoom: tile.maxZoom ?? 19
|
|
8310
|
+
}).addTo(this.mapInstance);
|
|
8311
|
+
this.addLeafletMarkers();
|
|
8312
|
+
this.element.dataset.mapLoaded = "true";
|
|
8313
|
+
}
|
|
8314
|
+
addLeafletMarkers() {
|
|
8315
|
+
const L = window.L;
|
|
8316
|
+
if (!this.markersValue.length)
|
|
8317
|
+
return;
|
|
8318
|
+
this.markersValue.forEach((m) => {
|
|
8319
|
+
const icon = this.leafletMarkerIcon(m.color, m.glyphText);
|
|
8320
|
+
const marker = L.marker([m.latitude, m.longitude], { icon }).addTo(this.mapInstance);
|
|
8321
|
+
if (m.title) {
|
|
8322
|
+
const content = m.subtitle ? `<strong>${m.title}</strong><br><span style="color:#777">${m.subtitle}</span>` : `<strong>${m.title}</strong>`;
|
|
8323
|
+
marker.bindPopup(content);
|
|
8324
|
+
if (m.open)
|
|
8325
|
+
marker.openPopup();
|
|
8326
|
+
}
|
|
8327
|
+
});
|
|
8328
|
+
}
|
|
8329
|
+
// Returns a Leaflet icon — default blue when no color/glyph, otherwise a
|
|
8330
|
+
// custom SVG pin rendered via divIcon.
|
|
8331
|
+
leafletMarkerIcon(color, glyphText) {
|
|
8332
|
+
const L = window.L;
|
|
8333
|
+
if (!color && !glyphText)
|
|
8334
|
+
return new L.Icon.Default();
|
|
8335
|
+
const fill = color ?? "#2563eb";
|
|
8336
|
+
const inner = glyphText ? `<text x="12" y="16" text-anchor="middle" font-size="9" font-family="system-ui,sans-serif" fill="white">${glyphText}</text>` : `<circle cx="12" cy="12" r="4" fill="rgba(255,255,255,0.9)"/>`;
|
|
8337
|
+
const svg = [
|
|
8338
|
+
'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 36" width="24" height="36">',
|
|
8339
|
+
` <path d="M12 0C5.373 0 0 5.373 0 12c0 9 12 24 12 24s12-15 12-24C24 5.373 18.627 0 12 0z" fill="${fill}"/>`,
|
|
8340
|
+
` ${inner}`,
|
|
8341
|
+
"</svg>"
|
|
8342
|
+
].join("");
|
|
8343
|
+
return L.divIcon({
|
|
8344
|
+
html: svg,
|
|
8345
|
+
className: "",
|
|
8346
|
+
iconSize: [24, 36],
|
|
8347
|
+
iconAnchor: [12, 36],
|
|
8348
|
+
popupAnchor: [0, -36]
|
|
8349
|
+
});
|
|
8350
|
+
}
|
|
8351
|
+
// Approximate MapKit coordinate span to a Web Mercator zoom level.
|
|
8352
|
+
// CoordinateSpan 360° = zoom 0; halving span adds 1 zoom level.
|
|
8353
|
+
spanToZoom(span) {
|
|
8354
|
+
return Math.round(Math.log2(360 / span));
|
|
8355
|
+
}
|
|
8356
|
+
};
|
|
8357
|
+
|
|
8358
|
+
const DEBOUNCE_MS = 300;
|
|
8359
|
+
const GOOGLE_PREVIEW_STYLES = [
|
|
8360
|
+
{ elementType: "geometry", stylers: [{ color: "#f5f5f5" }] },
|
|
8361
|
+
{ elementType: "labels.icon", stylers: [{ visibility: "off" }] },
|
|
8362
|
+
{ elementType: "labels.text.fill", stylers: [{ color: "#616161" }] },
|
|
8363
|
+
{ elementType: "labels.text.stroke", stylers: [{ color: "#f5f5f5" }] },
|
|
8364
|
+
{ featureType: "road", elementType: "geometry", stylers: [{ color: "#ffffff" }] },
|
|
8365
|
+
{ featureType: "road.highway", elementType: "geometry", stylers: [{ color: "#dadada" }] },
|
|
8366
|
+
{ featureType: "water", elementType: "geometry", stylers: [{ color: "#c9c9c9" }] },
|
|
8367
|
+
{ featureType: "poi", elementType: "geometry", stylers: [{ color: "#eeeeee" }] }
|
|
8368
|
+
];
|
|
8369
|
+
class Address extends Controller {
|
|
8370
|
+
static targets = [
|
|
8371
|
+
"input",
|
|
8372
|
+
"dropdown",
|
|
8373
|
+
"suggestions",
|
|
8374
|
+
"latInput",
|
|
8375
|
+
"lngInput",
|
|
8376
|
+
"placeIdInput",
|
|
8377
|
+
"mapContainer"
|
|
8378
|
+
];
|
|
8379
|
+
static values = {
|
|
8380
|
+
provider: { type: String, default: "" },
|
|
8381
|
+
mapProvider: { type: String, default: "" },
|
|
8382
|
+
apiKey: { type: String, default: "" },
|
|
8383
|
+
token: { type: String, default: "" },
|
|
8384
|
+
tokenUrl: { type: String, default: "" },
|
|
8385
|
+
countryCodes: { type: Array, default: [] },
|
|
8386
|
+
showMap: { type: Boolean, default: false },
|
|
8387
|
+
nominatimUrl: { type: String, default: "" }
|
|
8388
|
+
};
|
|
8389
|
+
searchTimer;
|
|
8390
|
+
highlightedIndex = -1;
|
|
8391
|
+
mapInstance = null;
|
|
8392
|
+
mapMarker = null;
|
|
8393
|
+
mkSearch = null;
|
|
8394
|
+
autocompleteService = null;
|
|
8395
|
+
placesService = null;
|
|
8396
|
+
// ── Named handlers ────────────────────────────────────────────────────────
|
|
8397
|
+
onToggle = (e) => {
|
|
8398
|
+
const opened = e.newState === "open";
|
|
8399
|
+
if (this.hasInputTarget) {
|
|
8400
|
+
this.inputTarget.setAttribute("aria-expanded", String(opened));
|
|
8401
|
+
}
|
|
8402
|
+
if (!opened) {
|
|
8403
|
+
this.highlightedIndex = -1;
|
|
8404
|
+
}
|
|
8405
|
+
};
|
|
8406
|
+
onFocusOut = (e) => {
|
|
8407
|
+
if (!this.element.contains(e.relatedTarget)) {
|
|
8408
|
+
this.closeDropdown();
|
|
8409
|
+
}
|
|
8410
|
+
};
|
|
8411
|
+
// ── Lifecycle ─────────────────────────────────────────────────────────────
|
|
8412
|
+
async connect() {
|
|
8413
|
+
this.element.addEventListener("focusout", this.onFocusOut);
|
|
8414
|
+
const provider = this.providerValue;
|
|
8415
|
+
const mapProv = this.effectiveMapProvider();
|
|
8416
|
+
if (!provider && !mapProv)
|
|
8417
|
+
return;
|
|
8418
|
+
try {
|
|
8419
|
+
if (provider === "mapkit") {
|
|
8420
|
+
await loadMapKitScript();
|
|
8421
|
+
this.initMapKit();
|
|
8422
|
+
} else if (provider === "google") {
|
|
8423
|
+
await loadGoogleMapsScript(this.apiKeyValue);
|
|
8424
|
+
this.initGoogle();
|
|
8425
|
+
}
|
|
8426
|
+
if (this.showMapValue && mapProv === "leaflet") {
|
|
8427
|
+
await loadLeafletScript();
|
|
8428
|
+
}
|
|
8429
|
+
} catch (err) {
|
|
8430
|
+
console.error("[midwest-address] Failed to initialise:", err);
|
|
8431
|
+
}
|
|
8432
|
+
}
|
|
8433
|
+
disconnect() {
|
|
8434
|
+
clearTimeout(this.searchTimer);
|
|
8435
|
+
this.element.removeEventListener("focusout", this.onFocusOut);
|
|
8436
|
+
if (this.mapInstance) {
|
|
8437
|
+
const mapProv = this.effectiveMapProvider();
|
|
8438
|
+
if (mapProv === "mapkit")
|
|
8439
|
+
this.mapInstance.destroy();
|
|
8440
|
+
if (mapProv === "leaflet")
|
|
8441
|
+
this.mapInstance.remove();
|
|
8442
|
+
}
|
|
8443
|
+
this.mapInstance = null;
|
|
8444
|
+
}
|
|
8445
|
+
dropdownTargetConnected(el) {
|
|
8446
|
+
el.addEventListener("toggle", this.onToggle);
|
|
8447
|
+
}
|
|
8448
|
+
dropdownTargetDisconnected(el) {
|
|
8449
|
+
el.removeEventListener("toggle", this.onToggle);
|
|
8450
|
+
}
|
|
8451
|
+
// ── Public Stimulus actions ───────────────────────────────────────────────
|
|
8452
|
+
search() {
|
|
8453
|
+
this.clearGeocode();
|
|
8454
|
+
const query = this.inputTarget.value.trim();
|
|
8455
|
+
if (query.length < 2) {
|
|
8456
|
+
this.closeDropdown();
|
|
8457
|
+
return;
|
|
8458
|
+
}
|
|
8459
|
+
clearTimeout(this.searchTimer);
|
|
8460
|
+
this.searchTimer = setTimeout(() => {
|
|
8461
|
+
if (this.providerValue === "mapkit") {
|
|
8462
|
+
this.searchMapKit(query);
|
|
8463
|
+
} else if (this.providerValue === "google") {
|
|
8464
|
+
this.searchGoogle(query);
|
|
8465
|
+
} else if (this.providerValue === "nominatim") {
|
|
8466
|
+
this.searchNominatim(query);
|
|
8467
|
+
}
|
|
8468
|
+
}, DEBOUNCE_MS);
|
|
8469
|
+
}
|
|
8470
|
+
navigate(event) {
|
|
8471
|
+
const items = this.suggestionElements();
|
|
8472
|
+
if (!items.length)
|
|
8473
|
+
return;
|
|
8474
|
+
switch (event.key) {
|
|
8475
|
+
case "ArrowDown":
|
|
8476
|
+
event.preventDefault();
|
|
8477
|
+
this.highlightedIndex = Math.min(this.highlightedIndex + 1, items.length - 1);
|
|
8478
|
+
this.applyHighlight(items);
|
|
8479
|
+
break;
|
|
8480
|
+
case "ArrowUp":
|
|
8481
|
+
event.preventDefault();
|
|
8482
|
+
this.highlightedIndex = Math.max(this.highlightedIndex - 1, -1);
|
|
8483
|
+
this.applyHighlight(items);
|
|
8484
|
+
break;
|
|
8485
|
+
case "Enter":
|
|
8486
|
+
event.preventDefault();
|
|
8487
|
+
if (this.highlightedIndex >= 0)
|
|
8488
|
+
items[this.highlightedIndex].dispatchEvent(new MouseEvent("mousedown"));
|
|
8489
|
+
break;
|
|
8490
|
+
case "Escape":
|
|
8491
|
+
this.closeDropdown();
|
|
8492
|
+
break;
|
|
8493
|
+
}
|
|
8494
|
+
}
|
|
8495
|
+
// ── Provider init ─────────────────────────────────────────────────────────
|
|
8496
|
+
initMapKit() {
|
|
8497
|
+
const mk = window.mapkit;
|
|
8498
|
+
const { tokenValue: token, tokenUrlValue: tokenUrl } = this;
|
|
8499
|
+
mk.init({
|
|
8500
|
+
authorizationCallback: (done) => {
|
|
8501
|
+
if (token) {
|
|
8502
|
+
done(token);
|
|
8503
|
+
} else if (tokenUrl) {
|
|
8504
|
+
fetch(tokenUrl).then((r) => r.text()).then(done).catch((err) => console.error("[midwest-address] Token fetch failed:", err));
|
|
8505
|
+
}
|
|
8506
|
+
},
|
|
8507
|
+
language: document.documentElement.lang || "en"
|
|
8508
|
+
});
|
|
8509
|
+
this.mkSearch = new mk.Search({ getsUserLocation: false });
|
|
8510
|
+
}
|
|
8511
|
+
initGoogle() {
|
|
8512
|
+
const g = window.google;
|
|
8513
|
+
this.autocompleteService = new g.maps.places.AutocompleteService();
|
|
8514
|
+
this.placesService = new g.maps.places.PlacesService(document.createElement("div"));
|
|
8515
|
+
}
|
|
8516
|
+
// ── Search ────────────────────────────────────────────────────────────────
|
|
8517
|
+
searchMapKit(query) {
|
|
8518
|
+
if (!this.mkSearch)
|
|
8519
|
+
return;
|
|
8520
|
+
this.mkSearch.autocomplete(query, (err, data) => {
|
|
8521
|
+
if (err || !data?.results?.length) {
|
|
8522
|
+
this.closeDropdown();
|
|
8523
|
+
return;
|
|
8524
|
+
}
|
|
8525
|
+
const results = data.results.map((r) => ({
|
|
8526
|
+
id: r.completionURL ?? r.displayLines?.join(", ") ?? "",
|
|
8527
|
+
label: r.displayLines?.[0] ?? "",
|
|
8528
|
+
secondary: r.displayLines?.[1] ?? "",
|
|
8529
|
+
payload: r
|
|
8530
|
+
}));
|
|
8531
|
+
this.renderSuggestions(results);
|
|
8532
|
+
});
|
|
8533
|
+
}
|
|
8534
|
+
searchGoogle(query) {
|
|
8535
|
+
if (!this.autocompleteService)
|
|
8536
|
+
return;
|
|
8537
|
+
const request = { input: query };
|
|
8538
|
+
if (this.countryCodesValue.length) {
|
|
8539
|
+
request.componentRestrictions = { country: this.countryCodesValue };
|
|
8540
|
+
}
|
|
8541
|
+
this.autocompleteService.getPlacePredictions(
|
|
8542
|
+
request,
|
|
8543
|
+
(predictions, status) => {
|
|
8544
|
+
const g = window.google;
|
|
8545
|
+
if (status !== g.maps.places.PlacesServiceStatus.OK || !predictions?.length) {
|
|
8546
|
+
this.closeDropdown();
|
|
8547
|
+
return;
|
|
8548
|
+
}
|
|
8549
|
+
const results = predictions.map((p) => ({
|
|
8550
|
+
id: p.place_id,
|
|
8551
|
+
label: p.structured_formatting?.main_text ?? p.description,
|
|
8552
|
+
secondary: p.structured_formatting?.secondary_text ?? "",
|
|
8553
|
+
payload: p
|
|
8554
|
+
}));
|
|
8555
|
+
this.renderSuggestions(results);
|
|
8556
|
+
}
|
|
8557
|
+
);
|
|
8558
|
+
}
|
|
8559
|
+
// ── Suggestions UI ────────────────────────────────────────────────────────
|
|
8560
|
+
renderSuggestions(results) {
|
|
8561
|
+
if (!this.hasSuggestionsTarget)
|
|
8562
|
+
return;
|
|
8563
|
+
const list = this.suggestionsTarget;
|
|
8564
|
+
list.innerHTML = "";
|
|
8565
|
+
results.forEach((r, i) => {
|
|
8566
|
+
const li = document.createElement("li");
|
|
8567
|
+
li.className = "midwest-address__suggestion";
|
|
8568
|
+
li.setAttribute("role", "option");
|
|
8569
|
+
li.setAttribute("id", `${this.element.id || "addr"}-s${i}`);
|
|
8570
|
+
li.dataset.index = String(i);
|
|
8571
|
+
const main = document.createElement("span");
|
|
8572
|
+
main.className = "midwest-address__suggestion-main";
|
|
8573
|
+
main.textContent = r.label;
|
|
8574
|
+
li.appendChild(main);
|
|
8575
|
+
if (r.secondary) {
|
|
8576
|
+
const sec = document.createElement("span");
|
|
8577
|
+
sec.className = "midwest-address__suggestion-secondary";
|
|
8578
|
+
sec.textContent = r.secondary;
|
|
8579
|
+
li.appendChild(sec);
|
|
8580
|
+
}
|
|
8581
|
+
li.addEventListener("mousedown", (e) => {
|
|
8582
|
+
e.preventDefault();
|
|
8583
|
+
this.pickSuggestion(r);
|
|
8584
|
+
});
|
|
8585
|
+
list.appendChild(li);
|
|
8586
|
+
});
|
|
8587
|
+
this.openDropdown();
|
|
8588
|
+
this.highlightedIndex = -1;
|
|
8589
|
+
}
|
|
8590
|
+
openDropdown() {
|
|
8591
|
+
if (!this.hasDropdownTarget)
|
|
8592
|
+
return;
|
|
8593
|
+
if (!this.dropdownTarget.matches(":popover-open"))
|
|
8594
|
+
this.dropdownTarget.showPopover();
|
|
8595
|
+
}
|
|
8596
|
+
closeDropdown() {
|
|
8597
|
+
if (this.hasDropdownTarget && this.dropdownTarget.matches(":popover-open")) {
|
|
8598
|
+
this.dropdownTarget.hidePopover();
|
|
8599
|
+
}
|
|
8600
|
+
if (this.hasSuggestionsTarget)
|
|
8601
|
+
this.suggestionsTarget.innerHTML = "";
|
|
8602
|
+
this.highlightedIndex = -1;
|
|
8603
|
+
}
|
|
8604
|
+
suggestionElements() {
|
|
8605
|
+
if (!this.hasSuggestionsTarget)
|
|
8606
|
+
return [];
|
|
8607
|
+
return Array.from(this.suggestionsTarget.querySelectorAll(".midwest-address__suggestion"));
|
|
8608
|
+
}
|
|
8609
|
+
applyHighlight(items) {
|
|
8610
|
+
items.forEach((item, i) => {
|
|
8611
|
+
const active = i === this.highlightedIndex;
|
|
8612
|
+
item.classList.toggle("is-highlighted", active);
|
|
8613
|
+
item.setAttribute("aria-selected", String(active));
|
|
8614
|
+
if (active) {
|
|
8615
|
+
this.inputTarget.setAttribute("aria-activedescendant", item.id);
|
|
8616
|
+
item.scrollIntoView({ block: "nearest" });
|
|
8617
|
+
}
|
|
8618
|
+
});
|
|
8619
|
+
if (this.highlightedIndex < 0) {
|
|
8620
|
+
this.inputTarget.removeAttribute("aria-activedescendant");
|
|
8621
|
+
}
|
|
8622
|
+
}
|
|
8623
|
+
// ── Pick a suggestion ─────────────────────────────────────────────────────
|
|
8624
|
+
pickSuggestion(r) {
|
|
8625
|
+
this.closeDropdown();
|
|
8626
|
+
if (this.providerValue === "mapkit") {
|
|
8627
|
+
this.pickMapKit(r);
|
|
8628
|
+
} else if (this.providerValue === "google") {
|
|
8629
|
+
this.pickGoogle(r);
|
|
8630
|
+
} else if (this.providerValue === "nominatim") {
|
|
8631
|
+
this.pickNominatim(r);
|
|
8632
|
+
}
|
|
8633
|
+
}
|
|
8634
|
+
pickMapKit(r) {
|
|
8635
|
+
if (!this.mkSearch)
|
|
8636
|
+
return;
|
|
8637
|
+
this.mkSearch.search(r.payload, (err, data) => {
|
|
8638
|
+
if (err || !data?.places?.length)
|
|
8639
|
+
return;
|
|
8640
|
+
const place = data.places[0];
|
|
8641
|
+
const coord = place.coordinate;
|
|
8642
|
+
this.inputTarget.value = [r.label, r.secondary].filter(Boolean).join(", ");
|
|
8643
|
+
this.fillGeocode(coord.latitude, coord.longitude, place.id ?? "");
|
|
8644
|
+
this.updateMapPreview(coord.latitude, coord.longitude);
|
|
8645
|
+
});
|
|
8646
|
+
}
|
|
8647
|
+
pickGoogle(r) {
|
|
8648
|
+
if (!this.placesService || !r.id)
|
|
8649
|
+
return;
|
|
8650
|
+
const g = window.google;
|
|
8651
|
+
this.placesService.getDetails(
|
|
8652
|
+
{ placeId: r.id, fields: ["geometry", "formatted_address", "place_id"] },
|
|
8653
|
+
(place, status) => {
|
|
8654
|
+
if (status !== g.maps.places.PlacesServiceStatus.OK || !place)
|
|
8655
|
+
return;
|
|
8656
|
+
const lat = place.geometry.location.lat();
|
|
8657
|
+
const lng = place.geometry.location.lng();
|
|
8658
|
+
this.inputTarget.value = place.formatted_address;
|
|
8659
|
+
this.fillGeocode(lat, lng, place.place_id);
|
|
8660
|
+
this.updateMapPreview(lat, lng);
|
|
8661
|
+
}
|
|
8662
|
+
);
|
|
8663
|
+
}
|
|
8664
|
+
// ── Geocode field management ──────────────────────────────────────────────
|
|
8665
|
+
fillGeocode(lat, lng, placeId) {
|
|
8666
|
+
if (this.hasLatInputTarget)
|
|
8667
|
+
this.latInputTarget.value = String(lat);
|
|
8668
|
+
if (this.hasLngInputTarget)
|
|
8669
|
+
this.lngInputTarget.value = String(lng);
|
|
8670
|
+
if (this.hasPlaceIdInputTarget)
|
|
8671
|
+
this.placeIdInputTarget.value = placeId;
|
|
8672
|
+
this.dispatch("geocode", { detail: { lat, lng, placeId } });
|
|
8673
|
+
}
|
|
8674
|
+
clearGeocode() {
|
|
8675
|
+
if (this.hasLatInputTarget)
|
|
8676
|
+
this.latInputTarget.value = "";
|
|
8677
|
+
if (this.hasLngInputTarget)
|
|
8678
|
+
this.lngInputTarget.value = "";
|
|
8679
|
+
if (this.hasPlaceIdInputTarget)
|
|
8680
|
+
this.placeIdInputTarget.value = "";
|
|
8681
|
+
}
|
|
8682
|
+
// ── Nominatim search (OpenStreetMap, no API key) ──────────────────────────
|
|
8683
|
+
searchNominatim(query) {
|
|
8684
|
+
const base = this.nominatimUrlValue || "https://nominatim.openstreetmap.org";
|
|
8685
|
+
const url = `${base}/search?q=${encodeURIComponent(query)}&format=json&limit=5&addressdetails=1`;
|
|
8686
|
+
fetch(url, { headers: { "Accept-Language": document.documentElement.lang || "en" } }).then((r) => r.json()).then((data) => {
|
|
8687
|
+
if (!data?.length) {
|
|
8688
|
+
this.closeDropdown();
|
|
8689
|
+
return;
|
|
8690
|
+
}
|
|
8691
|
+
const results = data.map((r) => {
|
|
8692
|
+
const parts = r.display_name.split(", ");
|
|
8693
|
+
return {
|
|
8694
|
+
id: String(r.place_id),
|
|
8695
|
+
label: r.name || parts[0] || r.display_name,
|
|
8696
|
+
secondary: parts.length > 1 ? parts.slice(1).join(", ") : "",
|
|
8697
|
+
payload: r
|
|
8698
|
+
};
|
|
8699
|
+
});
|
|
8700
|
+
this.renderSuggestions(results);
|
|
8701
|
+
}).catch(() => this.closeDropdown());
|
|
8702
|
+
}
|
|
8703
|
+
pickNominatim(r) {
|
|
8704
|
+
const place = r.payload;
|
|
8705
|
+
const lat = parseFloat(place.lat);
|
|
8706
|
+
const lng = parseFloat(place.lon);
|
|
8707
|
+
this.inputTarget.value = place.display_name;
|
|
8708
|
+
this.fillGeocode(lat, lng, `${place.osm_type}:${place.osm_id}`);
|
|
8709
|
+
this.updateMapPreview(lat, lng);
|
|
8710
|
+
}
|
|
8711
|
+
// ── Map preview ───────────────────────────────────────────────────────────
|
|
8712
|
+
// Returns the provider used for the map preview. `mapProviderValue` is set
|
|
8713
|
+
// when the geocoder and map providers differ; falls back to `providerValue`
|
|
8714
|
+
// so existing single-provider setups (mapkit/google) work without change.
|
|
8715
|
+
effectiveMapProvider() {
|
|
8716
|
+
return this.mapProviderValue || this.providerValue;
|
|
8717
|
+
}
|
|
8718
|
+
updateMapPreview(lat, lng) {
|
|
8719
|
+
if (!this.hasMapContainerTarget || !this.showMapValue)
|
|
8720
|
+
return;
|
|
8721
|
+
this.mapContainerTarget.hidden = false;
|
|
8722
|
+
const mapProv = this.effectiveMapProvider();
|
|
8723
|
+
if (mapProv === "mapkit") {
|
|
8724
|
+
this.updateMapKitPreview(lat, lng);
|
|
8725
|
+
} else if (mapProv === "google") {
|
|
8726
|
+
this.updateGooglePreview(lat, lng);
|
|
8727
|
+
} else if (mapProv === "leaflet") {
|
|
8728
|
+
this.updateLeafletPreview(lat, lng);
|
|
8729
|
+
}
|
|
8730
|
+
}
|
|
8731
|
+
updateMapKitPreview(lat, lng) {
|
|
8732
|
+
const mk = window.mapkit;
|
|
8733
|
+
const coord = new mk.Coordinate(lat, lng);
|
|
8734
|
+
if (!this.mapInstance) {
|
|
8735
|
+
this.mapInstance = new mk.Map(this.mapContainerTarget, {
|
|
8736
|
+
mapType: mk.Map.MapTypes.MutedStandard,
|
|
8737
|
+
showsCompass: mk.FeatureVisibility.Hidden,
|
|
8738
|
+
showsZoomControl: false,
|
|
8739
|
+
showsMapTypeControl: false,
|
|
8740
|
+
isScrollEnabled: false,
|
|
8741
|
+
isZoomEnabled: false
|
|
8742
|
+
});
|
|
8743
|
+
}
|
|
8744
|
+
this.mapInstance.setRegionAnimated(
|
|
8745
|
+
new mk.CoordinateRegion(coord, new mk.CoordinateSpan(0.01, 0.01))
|
|
8746
|
+
);
|
|
8747
|
+
if (this.mapMarker)
|
|
8748
|
+
this.mapInstance.removeAnnotation(this.mapMarker);
|
|
8749
|
+
this.mapMarker = new mk.MarkerAnnotation(coord);
|
|
8750
|
+
this.mapInstance.addAnnotation(this.mapMarker);
|
|
8751
|
+
}
|
|
8752
|
+
updateGooglePreview(lat, lng) {
|
|
8753
|
+
const g = window.google;
|
|
8754
|
+
const center = { lat, lng };
|
|
8755
|
+
if (!this.mapInstance) {
|
|
8756
|
+
this.mapInstance = new g.maps.Map(this.mapContainerTarget, {
|
|
8757
|
+
center,
|
|
8758
|
+
zoom: 15,
|
|
8759
|
+
disableDefaultUI: true,
|
|
8760
|
+
gestureHandling: "none",
|
|
8761
|
+
styles: GOOGLE_PREVIEW_STYLES
|
|
8762
|
+
});
|
|
8763
|
+
} else {
|
|
8764
|
+
this.mapInstance.setCenter(center);
|
|
8765
|
+
this.mapInstance.setZoom(15);
|
|
8766
|
+
}
|
|
8767
|
+
if (this.mapMarker)
|
|
8768
|
+
this.mapMarker.setMap(null);
|
|
8769
|
+
this.mapMarker = new g.maps.Marker({ map: this.mapInstance, position: center });
|
|
8770
|
+
}
|
|
8771
|
+
updateLeafletPreview(lat, lng) {
|
|
8772
|
+
const L = window.L;
|
|
8773
|
+
if (!L)
|
|
8774
|
+
return;
|
|
8775
|
+
if (!this.mapInstance) {
|
|
8776
|
+
this.mapInstance = L.map(this.mapContainerTarget, {
|
|
8777
|
+
center: [lat, lng],
|
|
8778
|
+
zoom: 15,
|
|
8779
|
+
zoomControl: false,
|
|
8780
|
+
attributionControl: false
|
|
8781
|
+
});
|
|
8782
|
+
L.tileLayer("https://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}{r}.png", {
|
|
8783
|
+
maxZoom: 19
|
|
8784
|
+
}).addTo(this.mapInstance);
|
|
8785
|
+
} else {
|
|
8786
|
+
this.mapInstance.setView([lat, lng], 15);
|
|
8787
|
+
}
|
|
8788
|
+
if (this.mapMarker)
|
|
8789
|
+
this.mapMarker.remove();
|
|
8790
|
+
this.mapMarker = L.marker([lat, lng]).addTo(this.mapInstance);
|
|
8791
|
+
}
|
|
8792
|
+
}
|
|
8793
|
+
|
|
8794
|
+
class Popover extends Controller {
|
|
8795
|
+
animating = false;
|
|
8796
|
+
closeTimer = null;
|
|
8797
|
+
connect() {
|
|
8798
|
+
this.element.addEventListener("beforetoggle", this.onBeforeToggle);
|
|
8799
|
+
}
|
|
8800
|
+
disconnect() {
|
|
8801
|
+
this.element.removeEventListener("beforetoggle", this.onBeforeToggle);
|
|
8802
|
+
this.cleanUp();
|
|
8803
|
+
}
|
|
8804
|
+
onBeforeToggle = (event) => {
|
|
8805
|
+
const toggle = event;
|
|
8806
|
+
if (toggle.newState === "open") {
|
|
8807
|
+
if (this.closeTimer) {
|
|
8808
|
+
clearTimeout(this.closeTimer);
|
|
8809
|
+
this.closeTimer = null;
|
|
8810
|
+
}
|
|
8811
|
+
this.element.classList.remove("is-closing");
|
|
8812
|
+
this.animating = false;
|
|
8813
|
+
return;
|
|
8814
|
+
}
|
|
8815
|
+
if (toggle.newState !== "closed")
|
|
8816
|
+
return;
|
|
8817
|
+
if (window.matchMedia("(width < 700px)").matches)
|
|
8818
|
+
return;
|
|
8819
|
+
if (!this.element.classList.contains("midwest-motion"))
|
|
8820
|
+
return;
|
|
8821
|
+
if (this.animating)
|
|
8822
|
+
return;
|
|
8823
|
+
event.preventDefault();
|
|
8824
|
+
this.animating = true;
|
|
8825
|
+
this.element.classList.add("is-closing");
|
|
8826
|
+
this.closeTimer = setTimeout(() => {
|
|
8827
|
+
this.closeTimer = null;
|
|
8828
|
+
this.element.style.display = "none";
|
|
8829
|
+
this.element.style.transition = "none";
|
|
8830
|
+
this.element.hidePopover();
|
|
8831
|
+
this.animating = false;
|
|
8832
|
+
requestAnimationFrame(() => {
|
|
8833
|
+
this.element.style.removeProperty("display");
|
|
8834
|
+
this.element.style.removeProperty("transition");
|
|
8835
|
+
this.element.classList.remove("is-closing");
|
|
8836
|
+
});
|
|
8837
|
+
}, this.exitDuration());
|
|
8838
|
+
};
|
|
8839
|
+
cleanUp() {
|
|
8840
|
+
if (this.closeTimer) {
|
|
8841
|
+
clearTimeout(this.closeTimer);
|
|
8842
|
+
this.closeTimer = null;
|
|
8843
|
+
}
|
|
8844
|
+
this.element.classList.remove("is-closing");
|
|
8845
|
+
this.element.style.removeProperty("transition");
|
|
8846
|
+
this.animating = false;
|
|
8847
|
+
}
|
|
8848
|
+
exitDuration() {
|
|
8849
|
+
const durations = getComputedStyle(this.element).transitionDuration.split(",").map((s) => {
|
|
8850
|
+
const n = parseFloat(s);
|
|
8851
|
+
return s.trim().endsWith("ms") ? n : n * 1e3;
|
|
8852
|
+
});
|
|
8853
|
+
return Math.max(0, ...durations);
|
|
8854
|
+
}
|
|
8855
|
+
}
|
|
8856
|
+
|
|
7895
8857
|
function registerMidwestControllers(application) {
|
|
7896
8858
|
application.register("midwest-card", Card);
|
|
7897
8859
|
application.register("midwest-banner", Banner);
|
|
@@ -7934,6 +8896,10 @@ function registerMidwestControllers(application) {
|
|
|
7934
8896
|
application.register("midwest-panorama", Panorama);
|
|
7935
8897
|
application.register("midwest-playlist", Playlist);
|
|
7936
8898
|
application.register("midwest-motion", Motion);
|
|
8899
|
+
application.register("midwest-page-transition", PageTransition);
|
|
8900
|
+
application.register("midwest-map", Map$1);
|
|
8901
|
+
application.register("midwest-address", Address);
|
|
8902
|
+
application.register("midwest-popover", Popover);
|
|
7937
8903
|
}
|
|
7938
8904
|
|
|
7939
8905
|
export { Banner, Card, Chart, CountdownTimer, registerMidwestControllers };
|