@unabridged/midwest 0.24.3 → 0.25.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 +8 -0
- package/app/assets/javascript/midwest/map_providers.ts +61 -0
- package/app/assets/javascript/midwest.js +1009 -9
- package/app/assets/javascript/midwest.js.map +1 -1
- package/app/assets/stylesheets/midwest.css +1 -1
- package/app/assets/stylesheets/midwest.tailwind.css +13 -1
- package/dist/css/midwest.css +1 -1
- package/dist/javascript/collection/app/assets/javascript/midwest/index.js +8 -0
- package/dist/javascript/collection/app/assets/javascript/midwest/index.js.map +1 -1
- package/dist/javascript/collection/app/assets/javascript/midwest/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/form_controller.js +37 -3
- package/dist/javascript/collection/app/components/midwest/form/form_controller.js.map +1 -1
- 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 +1009 -9
- 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) {
|
|
@@ -3525,13 +3579,30 @@ class FormLiveSummary extends Controller {
|
|
|
3525
3579
|
}
|
|
3526
3580
|
|
|
3527
3581
|
class Form extends Controller {
|
|
3582
|
+
static values = {
|
|
3583
|
+
// Auto-submit the form (debounced) whenever a field changes.
|
|
3584
|
+
autosave: { type: Boolean, default: false },
|
|
3585
|
+
autosaveDelay: { type: Number, default: 600 },
|
|
3586
|
+
// Emit success/error events and keep buttons interactive after an in-page
|
|
3587
|
+
// (non-navigating) Turbo submission.
|
|
3588
|
+
ajax: { type: Boolean, default: false }
|
|
3589
|
+
};
|
|
3590
|
+
autosaveTimer;
|
|
3528
3591
|
connect() {
|
|
3529
3592
|
this.element.addEventListener("submit", this.handleSubmit);
|
|
3530
3593
|
this.element.addEventListener("turbo:submit-end", this.handleTurboEnd);
|
|
3594
|
+
if (this.autosaveValue) {
|
|
3595
|
+
this.element.addEventListener("input", this.scheduleAutosave);
|
|
3596
|
+
this.element.addEventListener("change", this.scheduleAutosave);
|
|
3597
|
+
}
|
|
3531
3598
|
}
|
|
3532
3599
|
disconnect() {
|
|
3533
3600
|
this.element.removeEventListener("submit", this.handleSubmit);
|
|
3534
3601
|
this.element.removeEventListener("turbo:submit-end", this.handleTurboEnd);
|
|
3602
|
+
this.element.removeEventListener("input", this.scheduleAutosave);
|
|
3603
|
+
this.element.removeEventListener("change", this.scheduleAutosave);
|
|
3604
|
+
if (this.autosaveTimer != null)
|
|
3605
|
+
clearTimeout(this.autosaveTimer);
|
|
3535
3606
|
}
|
|
3536
3607
|
handleSubmit = () => {
|
|
3537
3608
|
this.submitButtons.forEach((btn) => {
|
|
@@ -3539,15 +3610,32 @@ class Form extends Controller {
|
|
|
3539
3610
|
btn.disabled = true;
|
|
3540
3611
|
});
|
|
3541
3612
|
};
|
|
3542
|
-
// Restore buttons if the submission fails (e.g. a Turbo Frame validation
|
|
3543
|
-
// error that re-renders the form without a full page transition).
|
|
3544
3613
|
handleTurboEnd = (event) => {
|
|
3545
|
-
|
|
3614
|
+
const detail = event.detail;
|
|
3615
|
+
const success = detail?.success ?? false;
|
|
3616
|
+
if (!success || this.ajaxValue || this.autosaveValue) {
|
|
3546
3617
|
this.submitButtons.forEach((btn) => {
|
|
3547
3618
|
btn.classList.remove("loading");
|
|
3548
3619
|
btn.disabled = false;
|
|
3549
3620
|
});
|
|
3550
3621
|
}
|
|
3622
|
+
if (this.ajaxValue) {
|
|
3623
|
+
this.dispatch(success ? "success" : "error", { detail });
|
|
3624
|
+
}
|
|
3625
|
+
};
|
|
3626
|
+
// Debounce field changes into a single background submission. `requestSubmit`
|
|
3627
|
+
// runs constraint validation and fires the `submit` event, so buttons pick up
|
|
3628
|
+
// their loading state and Turbo handles the request just like a manual submit.
|
|
3629
|
+
scheduleAutosave = () => {
|
|
3630
|
+
if (this.autosaveTimer != null)
|
|
3631
|
+
clearTimeout(this.autosaveTimer);
|
|
3632
|
+
this.autosaveTimer = setTimeout(() => {
|
|
3633
|
+
if (typeof this.element.requestSubmit === "function") {
|
|
3634
|
+
this.element.requestSubmit();
|
|
3635
|
+
} else {
|
|
3636
|
+
this.element.submit();
|
|
3637
|
+
}
|
|
3638
|
+
}, this.autosaveDelayValue);
|
|
3551
3639
|
};
|
|
3552
3640
|
get submitButtons() {
|
|
3553
3641
|
return Array.from(
|
|
@@ -7060,14 +7148,14 @@ const FOCUSABLE = [
|
|
|
7060
7148
|
].join(", ");
|
|
7061
7149
|
const GAP = 12;
|
|
7062
7150
|
class Onboarding extends Controller {
|
|
7063
|
-
static targets = ["step", "overlay", "card", "title", "body", "back", "next", "done", "arrow"];
|
|
7151
|
+
static targets = ["step", "overlay", "card", "title", "body", "back", "next", "done", "arrow", "motion"];
|
|
7064
7152
|
static values = {
|
|
7065
|
-
autoOpen: { type: Boolean, default: false }
|
|
7066
|
-
confirm: { type: String, default: "" }
|
|
7153
|
+
autoOpen: { type: Boolean, default: false }
|
|
7067
7154
|
};
|
|
7068
7155
|
steps = [];
|
|
7069
7156
|
index = 0;
|
|
7070
7157
|
active = false;
|
|
7158
|
+
justOpened = false;
|
|
7071
7159
|
previousFocus = null;
|
|
7072
7160
|
highlighted = null;
|
|
7073
7161
|
connect() {
|
|
@@ -7097,6 +7185,7 @@ class Onboarding extends Controller {
|
|
|
7097
7185
|
this.previousFocus = document.activeElement;
|
|
7098
7186
|
this.overlayTarget.hidden = false;
|
|
7099
7187
|
this.cardTarget.hidden = false;
|
|
7188
|
+
this.justOpened = true;
|
|
7100
7189
|
document.addEventListener("keydown", this.handleKeydown, true);
|
|
7101
7190
|
window.addEventListener("resize", this.reposition);
|
|
7102
7191
|
window.addEventListener("scroll", this.reposition, true);
|
|
@@ -7131,14 +7220,14 @@ class Onboarding extends Controller {
|
|
|
7131
7220
|
cancel() {
|
|
7132
7221
|
if (!this.active)
|
|
7133
7222
|
return;
|
|
7134
|
-
if (this.confirmValue && !window.confirm(this.confirmValue))
|
|
7135
|
-
return;
|
|
7136
7223
|
this.teardown();
|
|
7137
7224
|
this.dispatch("cancel", { bubbles: true });
|
|
7138
7225
|
}
|
|
7139
7226
|
render(index) {
|
|
7140
7227
|
this.index = index;
|
|
7141
7228
|
const step = this.steps[index];
|
|
7229
|
+
const replay = !this.justOpened;
|
|
7230
|
+
this.justOpened = false;
|
|
7142
7231
|
this.clearHighlight();
|
|
7143
7232
|
this.titleTarget.textContent = step.title;
|
|
7144
7233
|
this.renderBody(step);
|
|
@@ -7150,6 +7239,9 @@ class Onboarding extends Controller {
|
|
|
7150
7239
|
} else {
|
|
7151
7240
|
this.center();
|
|
7152
7241
|
}
|
|
7242
|
+
if (replay && this.hasMotionTarget) {
|
|
7243
|
+
this.replayMotion(this.cardTarget.dataset.placement ?? "center");
|
|
7244
|
+
}
|
|
7153
7245
|
this.dispatch("show", { detail: { index, name: step.name }, bubbles: true });
|
|
7154
7246
|
this.focusCard();
|
|
7155
7247
|
}
|
|
@@ -7251,9 +7343,34 @@ class Onboarding extends Controller {
|
|
|
7251
7343
|
this.cardTarget.focus();
|
|
7252
7344
|
});
|
|
7253
7345
|
}
|
|
7346
|
+
// Re-triggers the midwest_motion @starting-style entry on step transitions.
|
|
7347
|
+
// The initial show is handled by @starting-style firing when the card's
|
|
7348
|
+
// `hidden` is removed; subsequent steps need an explicit re-trigger since the
|
|
7349
|
+
// card stays in the DOM. Toggling `hidden` on the motion element triggers
|
|
7350
|
+
// @starting-style again; the rAF ensures both DOM writes land in the same
|
|
7351
|
+
// paint so there is no blank-card flash.
|
|
7352
|
+
replayMotion(placement) {
|
|
7353
|
+
const el = this.motionTarget;
|
|
7354
|
+
const dir = { bottom: "up", top: "down", right: "left", left: "right" };
|
|
7355
|
+
el.classList.remove("from-up", "from-down", "from-left", "from-right");
|
|
7356
|
+
const from = dir[placement];
|
|
7357
|
+
if (from)
|
|
7358
|
+
el.classList.add(`from-${from}`);
|
|
7359
|
+
el.hidden = true;
|
|
7360
|
+
requestAnimationFrame(() => {
|
|
7361
|
+
if (this.active)
|
|
7362
|
+
el.hidden = false;
|
|
7363
|
+
});
|
|
7364
|
+
}
|
|
7254
7365
|
teardown() {
|
|
7255
7366
|
this.active = false;
|
|
7256
7367
|
this.clearHighlight();
|
|
7368
|
+
this.element.querySelectorAll("[popover]").forEach((el) => {
|
|
7369
|
+
try {
|
|
7370
|
+
el.hidePopover();
|
|
7371
|
+
} catch {
|
|
7372
|
+
}
|
|
7373
|
+
});
|
|
7257
7374
|
this.overlayTarget.hidden = true;
|
|
7258
7375
|
this.cardTarget.hidden = true;
|
|
7259
7376
|
document.removeEventListener("keydown", this.handleKeydown, true);
|
|
@@ -7892,6 +8009,885 @@ class Motion extends Controller {
|
|
|
7892
8009
|
}
|
|
7893
8010
|
}
|
|
7894
8011
|
|
|
8012
|
+
class PageTransition extends Controller {
|
|
8013
|
+
static values = {
|
|
8014
|
+
style: { type: String, default: "fade" },
|
|
8015
|
+
duration: { type: String, default: "" },
|
|
8016
|
+
easing: { type: String, default: "" }
|
|
8017
|
+
};
|
|
8018
|
+
injectedMeta = null;
|
|
8019
|
+
connect() {
|
|
8020
|
+
if (this.styleValue === "none")
|
|
8021
|
+
return;
|
|
8022
|
+
if (!document.querySelector('meta[name="view-transition"]')) {
|
|
8023
|
+
const meta = document.createElement("meta");
|
|
8024
|
+
meta.name = "view-transition";
|
|
8025
|
+
meta.content = "same-origin";
|
|
8026
|
+
document.head.appendChild(meta);
|
|
8027
|
+
this.injectedMeta = meta;
|
|
8028
|
+
}
|
|
8029
|
+
document.documentElement.dataset.midwestPageTransition = this.styleValue;
|
|
8030
|
+
if (this.durationValue) {
|
|
8031
|
+
document.documentElement.style.setProperty("--page-transition-duration", this.durationValue);
|
|
8032
|
+
}
|
|
8033
|
+
if (this.easingValue) {
|
|
8034
|
+
document.documentElement.style.setProperty("--page-transition-easing", this.easingValue);
|
|
8035
|
+
}
|
|
8036
|
+
}
|
|
8037
|
+
disconnect() {
|
|
8038
|
+
this.injectedMeta?.remove();
|
|
8039
|
+
this.injectedMeta = null;
|
|
8040
|
+
delete document.documentElement.dataset.midwestPageTransition;
|
|
8041
|
+
document.documentElement.style.removeProperty("--page-transition-duration");
|
|
8042
|
+
document.documentElement.style.removeProperty("--page-transition-easing");
|
|
8043
|
+
}
|
|
8044
|
+
}
|
|
8045
|
+
|
|
8046
|
+
let mapkitScriptPromise = null;
|
|
8047
|
+
let googleMapsScriptPromise = null;
|
|
8048
|
+
let leafletScriptPromise = null;
|
|
8049
|
+
function loadMapKitScript() {
|
|
8050
|
+
if (window.mapkit)
|
|
8051
|
+
return Promise.resolve();
|
|
8052
|
+
if (mapkitScriptPromise)
|
|
8053
|
+
return mapkitScriptPromise;
|
|
8054
|
+
mapkitScriptPromise = new Promise((resolve, reject) => {
|
|
8055
|
+
const script = document.createElement("script");
|
|
8056
|
+
script.src = "https://cdn.apple-mapkit.com/mk/5.x.x/mapkit.js";
|
|
8057
|
+
script.crossOrigin = "anonymous";
|
|
8058
|
+
script.addEventListener("load", () => resolve());
|
|
8059
|
+
script.addEventListener("error", () => reject(new Error("Failed to load MapKit JS")));
|
|
8060
|
+
document.head.appendChild(script);
|
|
8061
|
+
});
|
|
8062
|
+
return mapkitScriptPromise;
|
|
8063
|
+
}
|
|
8064
|
+
function loadLeafletScript() {
|
|
8065
|
+
if (window.L)
|
|
8066
|
+
return Promise.resolve();
|
|
8067
|
+
if (leafletScriptPromise)
|
|
8068
|
+
return leafletScriptPromise;
|
|
8069
|
+
leafletScriptPromise = new Promise((resolve, reject) => {
|
|
8070
|
+
if (!document.querySelector("link[data-midwest-leaflet]")) {
|
|
8071
|
+
const link = document.createElement("link");
|
|
8072
|
+
link.rel = "stylesheet";
|
|
8073
|
+
link.href = "https://unpkg.com/leaflet@1.9.4/dist/leaflet.css";
|
|
8074
|
+
link.dataset.midwestLeaflet = "";
|
|
8075
|
+
document.head.appendChild(link);
|
|
8076
|
+
}
|
|
8077
|
+
const script = document.createElement("script");
|
|
8078
|
+
script.src = "https://unpkg.com/leaflet@1.9.4/dist/leaflet.js";
|
|
8079
|
+
script.addEventListener("load", () => resolve());
|
|
8080
|
+
script.addEventListener("error", () => reject(new Error("Failed to load Leaflet")));
|
|
8081
|
+
document.head.appendChild(script);
|
|
8082
|
+
});
|
|
8083
|
+
return leafletScriptPromise;
|
|
8084
|
+
}
|
|
8085
|
+
function loadGoogleMapsScript(apiKey) {
|
|
8086
|
+
if (window.google?.maps)
|
|
8087
|
+
return Promise.resolve();
|
|
8088
|
+
if (googleMapsScriptPromise)
|
|
8089
|
+
return googleMapsScriptPromise;
|
|
8090
|
+
googleMapsScriptPromise = new Promise((resolve, reject) => {
|
|
8091
|
+
window.__midwestGoogleMapsReady = resolve;
|
|
8092
|
+
const script = document.createElement("script");
|
|
8093
|
+
script.src = `https://maps.googleapis.com/maps/api/js?key=${encodeURIComponent(apiKey)}&callback=__midwestGoogleMapsReady&loading=async&libraries=places`;
|
|
8094
|
+
script.addEventListener("error", () => reject(new Error("Failed to load Google Maps")));
|
|
8095
|
+
document.head.appendChild(script);
|
|
8096
|
+
});
|
|
8097
|
+
return googleMapsScriptPromise;
|
|
8098
|
+
}
|
|
8099
|
+
|
|
8100
|
+
const LEAFLET_TILES = {
|
|
8101
|
+
MutedStandard: {
|
|
8102
|
+
url: "https://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}{r}.png",
|
|
8103
|
+
attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors © <a href="https://carto.com/attributions">CARTO</a>'
|
|
8104
|
+
},
|
|
8105
|
+
Standard: {
|
|
8106
|
+
url: "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png",
|
|
8107
|
+
attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors',
|
|
8108
|
+
maxZoom: 19
|
|
8109
|
+
},
|
|
8110
|
+
Hybrid: {
|
|
8111
|
+
url: "https://{s}.basemaps.cartocdn.com/rastertiles/voyager/{z}/{x}/{y}{r}.png",
|
|
8112
|
+
attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors © <a href="https://carto.com/attributions">CARTO</a>'
|
|
8113
|
+
},
|
|
8114
|
+
Satellite: {
|
|
8115
|
+
url: "https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}",
|
|
8116
|
+
attribution: "Tiles © Esri — Source: Esri, i-cubed, USDA, USGS, AEX, GeoEye, Getmapping, Aerogrid, IGN, IGP, UPR-EGP, and the GIS User Community",
|
|
8117
|
+
maxZoom: 18
|
|
8118
|
+
},
|
|
8119
|
+
Dark: {
|
|
8120
|
+
url: "https://{s}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}{r}.png",
|
|
8121
|
+
attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors © <a href="https://carto.com/attributions">CARTO</a>'
|
|
8122
|
+
}
|
|
8123
|
+
};
|
|
8124
|
+
const GOOGLE_MUTED_STYLES = [
|
|
8125
|
+
{ elementType: "geometry", stylers: [{ color: "#f5f5f5" }] },
|
|
8126
|
+
{ elementType: "labels.icon", stylers: [{ visibility: "off" }] },
|
|
8127
|
+
{ elementType: "labels.text.fill", stylers: [{ color: "#616161" }] },
|
|
8128
|
+
{ elementType: "labels.text.stroke", stylers: [{ color: "#f5f5f5" }] },
|
|
8129
|
+
{ featureType: "administrative.land_parcel", elementType: "labels", stylers: [{ visibility: "off" }] },
|
|
8130
|
+
{ featureType: "poi", elementType: "geometry", stylers: [{ color: "#eeeeee" }] },
|
|
8131
|
+
{ featureType: "poi.park", elementType: "geometry", stylers: [{ color: "#e5e5e5" }] },
|
|
8132
|
+
{ featureType: "road", elementType: "geometry", stylers: [{ color: "#ffffff" }] },
|
|
8133
|
+
{ featureType: "road.highway", elementType: "geometry", stylers: [{ color: "#dadada" }] },
|
|
8134
|
+
{ featureType: "water", elementType: "geometry", stylers: [{ color: "#c9c9c9" }] }
|
|
8135
|
+
];
|
|
8136
|
+
const GOOGLE_DARK_STYLES = [
|
|
8137
|
+
{ elementType: "geometry", stylers: [{ color: "#242f3e" }] },
|
|
8138
|
+
{ elementType: "labels.text.stroke", stylers: [{ color: "#242f3e" }] },
|
|
8139
|
+
{ elementType: "labels.text.fill", stylers: [{ color: "#746855" }] },
|
|
8140
|
+
{ featureType: "administrative.locality", elementType: "labels.text.fill", stylers: [{ color: "#d59563" }] },
|
|
8141
|
+
{ featureType: "poi", elementType: "labels.text.fill", stylers: [{ color: "#d59563" }] },
|
|
8142
|
+
{ featureType: "poi.park", elementType: "geometry", stylers: [{ color: "#263c3f" }] },
|
|
8143
|
+
{ featureType: "poi.park", elementType: "labels.text.fill", stylers: [{ color: "#6b9a76" }] },
|
|
8144
|
+
{ featureType: "road", elementType: "geometry", stylers: [{ color: "#38414e" }] },
|
|
8145
|
+
{ featureType: "road", elementType: "geometry.stroke", stylers: [{ color: "#212a37" }] },
|
|
8146
|
+
{ featureType: "road", elementType: "labels.text.fill", stylers: [{ color: "#9ca5b3" }] },
|
|
8147
|
+
{ featureType: "road.highway", elementType: "geometry", stylers: [{ color: "#746855" }] },
|
|
8148
|
+
{ featureType: "road.highway", elementType: "geometry.stroke", stylers: [{ color: "#1f2835" }] },
|
|
8149
|
+
{ featureType: "road.highway", elementType: "labels.text.fill", stylers: [{ color: "#f3d19c" }] },
|
|
8150
|
+
{ featureType: "transit", elementType: "geometry", stylers: [{ color: "#2f3948" }] },
|
|
8151
|
+
{ featureType: "transit.station", elementType: "labels.text.fill", stylers: [{ color: "#d59563" }] },
|
|
8152
|
+
{ featureType: "water", elementType: "geometry", stylers: [{ color: "#17263c" }] },
|
|
8153
|
+
{ featureType: "water", elementType: "labels.text.fill", stylers: [{ color: "#515c6d" }] },
|
|
8154
|
+
{ featureType: "water", elementType: "labels.text.stroke", stylers: [{ color: "#17263c" }] }
|
|
8155
|
+
];
|
|
8156
|
+
let Map$1 = class Map extends Controller {
|
|
8157
|
+
static values = {
|
|
8158
|
+
latitude: { type: Number, default: 37.334886 },
|
|
8159
|
+
longitude: { type: Number, default: -122.008988 },
|
|
8160
|
+
span: { type: Number, default: 0.1 },
|
|
8161
|
+
mapType: { type: String, default: "MutedStandard" },
|
|
8162
|
+
colorScheme: { type: String, default: "Auto" },
|
|
8163
|
+
provider: { type: String, default: "" },
|
|
8164
|
+
token: { type: String, default: "" },
|
|
8165
|
+
tokenUrl: { type: String, default: "" },
|
|
8166
|
+
apiKey: { type: String, default: "" },
|
|
8167
|
+
zoom: { type: Number, default: -1 },
|
|
8168
|
+
markers: { type: Array, default: [] },
|
|
8169
|
+
showsUserLocation: { type: Boolean, default: false },
|
|
8170
|
+
showsMapTypeControl: { type: Boolean, default: false },
|
|
8171
|
+
showsZoomControl: { type: Boolean, default: true },
|
|
8172
|
+
showsCompass: { type: String, default: "Adaptive" },
|
|
8173
|
+
rotation: { type: Number, default: 0 }
|
|
8174
|
+
};
|
|
8175
|
+
mapInstance = null;
|
|
8176
|
+
async connect() {
|
|
8177
|
+
const provider = this.providerValue;
|
|
8178
|
+
if (!provider)
|
|
8179
|
+
return;
|
|
8180
|
+
try {
|
|
8181
|
+
if (provider === "mapkit") {
|
|
8182
|
+
await loadMapKitScript();
|
|
8183
|
+
this.initMapKit();
|
|
8184
|
+
this.initMapKitMap();
|
|
8185
|
+
} else if (provider === "google") {
|
|
8186
|
+
await loadGoogleMapsScript(this.apiKeyValue);
|
|
8187
|
+
this.initGoogleMap();
|
|
8188
|
+
} else if (provider === "leaflet") {
|
|
8189
|
+
await loadLeafletScript();
|
|
8190
|
+
this.initLeafletMap();
|
|
8191
|
+
}
|
|
8192
|
+
} catch (err) {
|
|
8193
|
+
console.error(`[midwest-map] Failed to initialise ${provider} map:`, err);
|
|
8194
|
+
}
|
|
8195
|
+
}
|
|
8196
|
+
disconnect() {
|
|
8197
|
+
if (this.mapInstance) {
|
|
8198
|
+
if (this.providerValue === "mapkit")
|
|
8199
|
+
this.mapInstance.destroy();
|
|
8200
|
+
if (this.providerValue === "leaflet")
|
|
8201
|
+
this.mapInstance.remove();
|
|
8202
|
+
this.mapInstance = null;
|
|
8203
|
+
}
|
|
8204
|
+
}
|
|
8205
|
+
tokenValueChanged() {
|
|
8206
|
+
if (this.mapInstance && this.providerValue === "mapkit")
|
|
8207
|
+
this.initMapKit();
|
|
8208
|
+
}
|
|
8209
|
+
// --- MapKit ---
|
|
8210
|
+
initMapKit() {
|
|
8211
|
+
const mk = window.mapkit;
|
|
8212
|
+
const token = this.tokenValue;
|
|
8213
|
+
const tokenUrl = this.tokenUrlValue;
|
|
8214
|
+
mk.init({
|
|
8215
|
+
authorizationCallback: (done) => {
|
|
8216
|
+
if (token) {
|
|
8217
|
+
done(token);
|
|
8218
|
+
} else if (tokenUrl) {
|
|
8219
|
+
fetch(tokenUrl).then((r) => r.text()).then(done).catch((err) => console.error("[midwest-map] Token fetch failed:", err));
|
|
8220
|
+
}
|
|
8221
|
+
},
|
|
8222
|
+
language: document.documentElement.lang || "en"
|
|
8223
|
+
});
|
|
8224
|
+
}
|
|
8225
|
+
initMapKitMap() {
|
|
8226
|
+
const mk = window.mapkit;
|
|
8227
|
+
const region = new mk.CoordinateRegion(
|
|
8228
|
+
new mk.Coordinate(this.latitudeValue, this.longitudeValue),
|
|
8229
|
+
new mk.CoordinateSpan(this.spanValue, this.spanValue)
|
|
8230
|
+
);
|
|
8231
|
+
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];
|
|
8232
|
+
this.mapInstance = new mk.Map(this.element, {
|
|
8233
|
+
region,
|
|
8234
|
+
mapType: mk.Map.MapTypes[this.mapTypeValue],
|
|
8235
|
+
colorScheme,
|
|
8236
|
+
showsUserLocation: this.showsUserLocationValue,
|
|
8237
|
+
showsMapTypeControl: this.showsMapTypeControlValue,
|
|
8238
|
+
showsZoomControl: this.showsZoomControlValue,
|
|
8239
|
+
showsCompass: mk.FeatureVisibility[this.showsCompassValue],
|
|
8240
|
+
rotation: this.rotationValue
|
|
8241
|
+
});
|
|
8242
|
+
this.addMapKitMarkers();
|
|
8243
|
+
this.element.dataset.mapLoaded = "true";
|
|
8244
|
+
}
|
|
8245
|
+
addMapKitMarkers() {
|
|
8246
|
+
const mk = window.mapkit;
|
|
8247
|
+
if (!this.markersValue.length)
|
|
8248
|
+
return;
|
|
8249
|
+
const annotations = this.markersValue.map((m) => {
|
|
8250
|
+
const opts = {};
|
|
8251
|
+
if (m.title)
|
|
8252
|
+
opts.title = m.title;
|
|
8253
|
+
if (m.subtitle)
|
|
8254
|
+
opts.subtitle = m.subtitle;
|
|
8255
|
+
if (m.color)
|
|
8256
|
+
opts.color = m.color;
|
|
8257
|
+
if (m.glyphText)
|
|
8258
|
+
opts.glyphText = m.glyphText;
|
|
8259
|
+
return new mk.MarkerAnnotation(
|
|
8260
|
+
new mk.Coordinate(m.latitude, m.longitude),
|
|
8261
|
+
opts
|
|
8262
|
+
);
|
|
8263
|
+
});
|
|
8264
|
+
this.mapInstance.addAnnotations(annotations);
|
|
8265
|
+
const openIndex = this.markersValue.findIndex((m) => m.open);
|
|
8266
|
+
if (openIndex >= 0) {
|
|
8267
|
+
this.mapInstance.selectedAnnotation = annotations[openIndex];
|
|
8268
|
+
}
|
|
8269
|
+
}
|
|
8270
|
+
// --- Google Maps ---
|
|
8271
|
+
initGoogleMap() {
|
|
8272
|
+
const g = window.google;
|
|
8273
|
+
const zoom = this.zoomValue > 0 ? this.zoomValue : this.spanToZoom(this.spanValue);
|
|
8274
|
+
const isDark = this.colorSchemeValue === "Dark" || this.colorSchemeValue === "Auto" && window.matchMedia("(prefers-color-scheme: dark)").matches;
|
|
8275
|
+
const mapTypeId = this.googleMapTypeId();
|
|
8276
|
+
const opts = {
|
|
8277
|
+
center: { lat: this.latitudeValue, lng: this.longitudeValue },
|
|
8278
|
+
zoom,
|
|
8279
|
+
mapTypeId,
|
|
8280
|
+
disableDefaultUI: true,
|
|
8281
|
+
zoomControl: this.showsZoomControlValue,
|
|
8282
|
+
mapTypeControl: this.showsMapTypeControlValue,
|
|
8283
|
+
fullscreenControl: false,
|
|
8284
|
+
streetViewControl: false
|
|
8285
|
+
};
|
|
8286
|
+
const styles = this.googleMapStyles(isDark, mapTypeId);
|
|
8287
|
+
if (styles)
|
|
8288
|
+
opts.styles = styles;
|
|
8289
|
+
this.mapInstance = new g.maps.Map(this.element, opts);
|
|
8290
|
+
this.addGoogleMarkers();
|
|
8291
|
+
this.element.dataset.mapLoaded = "true";
|
|
8292
|
+
}
|
|
8293
|
+
googleMapTypeId() {
|
|
8294
|
+
const types = {
|
|
8295
|
+
Standard: "roadmap",
|
|
8296
|
+
Hybrid: "hybrid",
|
|
8297
|
+
Satellite: "satellite",
|
|
8298
|
+
MutedStandard: "roadmap"
|
|
8299
|
+
};
|
|
8300
|
+
return types[this.mapTypeValue] ?? "roadmap";
|
|
8301
|
+
}
|
|
8302
|
+
googleMapStyles(isDark, mapTypeId) {
|
|
8303
|
+
if (mapTypeId !== "roadmap")
|
|
8304
|
+
return null;
|
|
8305
|
+
if (isDark)
|
|
8306
|
+
return GOOGLE_DARK_STYLES;
|
|
8307
|
+
if (this.mapTypeValue === "MutedStandard")
|
|
8308
|
+
return GOOGLE_MUTED_STYLES;
|
|
8309
|
+
return null;
|
|
8310
|
+
}
|
|
8311
|
+
addGoogleMarkers() {
|
|
8312
|
+
const g = window.google;
|
|
8313
|
+
if (!this.markersValue.length)
|
|
8314
|
+
return;
|
|
8315
|
+
this.markersValue.forEach((m) => {
|
|
8316
|
+
const marker = new g.maps.Marker({
|
|
8317
|
+
map: this.mapInstance,
|
|
8318
|
+
position: { lat: m.latitude, lng: m.longitude },
|
|
8319
|
+
title: m.title ?? ""
|
|
8320
|
+
});
|
|
8321
|
+
if (m.open && m.title) {
|
|
8322
|
+
const content = m.subtitle ? `<strong>${m.title}</strong><br><span style="color:#777">${m.subtitle}</span>` : `<strong>${m.title}</strong>`;
|
|
8323
|
+
const infoWindow = new g.maps.InfoWindow({ content });
|
|
8324
|
+
infoWindow.open(this.mapInstance, marker);
|
|
8325
|
+
}
|
|
8326
|
+
});
|
|
8327
|
+
}
|
|
8328
|
+
// --- Leaflet ---
|
|
8329
|
+
initLeafletMap() {
|
|
8330
|
+
const L = window.L;
|
|
8331
|
+
const zoom = this.zoomValue > 0 ? this.zoomValue : this.spanToZoom(this.spanValue);
|
|
8332
|
+
const isDark = this.colorSchemeValue === "Dark" || this.colorSchemeValue === "Auto" && window.matchMedia("(prefers-color-scheme: dark)").matches;
|
|
8333
|
+
const tileKey = isDark ? "Dark" : this.mapTypeValue;
|
|
8334
|
+
const tile = LEAFLET_TILES[tileKey] ?? LEAFLET_TILES.MutedStandard;
|
|
8335
|
+
this.mapInstance = L.map(this.element, {
|
|
8336
|
+
center: [this.latitudeValue, this.longitudeValue],
|
|
8337
|
+
zoom,
|
|
8338
|
+
zoomControl: this.showsZoomControlValue,
|
|
8339
|
+
attributionControl: true
|
|
8340
|
+
});
|
|
8341
|
+
L.tileLayer(tile.url, {
|
|
8342
|
+
attribution: tile.attribution,
|
|
8343
|
+
maxZoom: tile.maxZoom ?? 19
|
|
8344
|
+
}).addTo(this.mapInstance);
|
|
8345
|
+
this.addLeafletMarkers();
|
|
8346
|
+
this.element.dataset.mapLoaded = "true";
|
|
8347
|
+
}
|
|
8348
|
+
addLeafletMarkers() {
|
|
8349
|
+
const L = window.L;
|
|
8350
|
+
if (!this.markersValue.length)
|
|
8351
|
+
return;
|
|
8352
|
+
this.markersValue.forEach((m) => {
|
|
8353
|
+
const icon = this.leafletMarkerIcon(m.color, m.glyphText);
|
|
8354
|
+
const marker = L.marker([m.latitude, m.longitude], { icon }).addTo(this.mapInstance);
|
|
8355
|
+
if (m.title) {
|
|
8356
|
+
const content = m.subtitle ? `<strong>${m.title}</strong><br><span style="color:#777">${m.subtitle}</span>` : `<strong>${m.title}</strong>`;
|
|
8357
|
+
marker.bindPopup(content);
|
|
8358
|
+
if (m.open)
|
|
8359
|
+
marker.openPopup();
|
|
8360
|
+
}
|
|
8361
|
+
});
|
|
8362
|
+
}
|
|
8363
|
+
// Returns a Leaflet icon — default blue when no color/glyph, otherwise a
|
|
8364
|
+
// custom SVG pin rendered via divIcon.
|
|
8365
|
+
leafletMarkerIcon(color, glyphText) {
|
|
8366
|
+
const L = window.L;
|
|
8367
|
+
if (!color && !glyphText)
|
|
8368
|
+
return new L.Icon.Default();
|
|
8369
|
+
const fill = color ?? "#2563eb";
|
|
8370
|
+
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)"/>`;
|
|
8371
|
+
const svg = [
|
|
8372
|
+
'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 36" width="24" height="36">',
|
|
8373
|
+
` <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}"/>`,
|
|
8374
|
+
` ${inner}`,
|
|
8375
|
+
"</svg>"
|
|
8376
|
+
].join("");
|
|
8377
|
+
return L.divIcon({
|
|
8378
|
+
html: svg,
|
|
8379
|
+
className: "",
|
|
8380
|
+
iconSize: [24, 36],
|
|
8381
|
+
iconAnchor: [12, 36],
|
|
8382
|
+
popupAnchor: [0, -36]
|
|
8383
|
+
});
|
|
8384
|
+
}
|
|
8385
|
+
// Approximate MapKit coordinate span to a Web Mercator zoom level.
|
|
8386
|
+
// CoordinateSpan 360° = zoom 0; halving span adds 1 zoom level.
|
|
8387
|
+
spanToZoom(span) {
|
|
8388
|
+
return Math.round(Math.log2(360 / span));
|
|
8389
|
+
}
|
|
8390
|
+
};
|
|
8391
|
+
|
|
8392
|
+
const DEBOUNCE_MS = 300;
|
|
8393
|
+
const GOOGLE_PREVIEW_STYLES = [
|
|
8394
|
+
{ elementType: "geometry", stylers: [{ color: "#f5f5f5" }] },
|
|
8395
|
+
{ elementType: "labels.icon", stylers: [{ visibility: "off" }] },
|
|
8396
|
+
{ elementType: "labels.text.fill", stylers: [{ color: "#616161" }] },
|
|
8397
|
+
{ elementType: "labels.text.stroke", stylers: [{ color: "#f5f5f5" }] },
|
|
8398
|
+
{ featureType: "road", elementType: "geometry", stylers: [{ color: "#ffffff" }] },
|
|
8399
|
+
{ featureType: "road.highway", elementType: "geometry", stylers: [{ color: "#dadada" }] },
|
|
8400
|
+
{ featureType: "water", elementType: "geometry", stylers: [{ color: "#c9c9c9" }] },
|
|
8401
|
+
{ featureType: "poi", elementType: "geometry", stylers: [{ color: "#eeeeee" }] }
|
|
8402
|
+
];
|
|
8403
|
+
class Address extends Controller {
|
|
8404
|
+
static targets = [
|
|
8405
|
+
"input",
|
|
8406
|
+
"dropdown",
|
|
8407
|
+
"suggestions",
|
|
8408
|
+
"latInput",
|
|
8409
|
+
"lngInput",
|
|
8410
|
+
"placeIdInput",
|
|
8411
|
+
"mapContainer"
|
|
8412
|
+
];
|
|
8413
|
+
static values = {
|
|
8414
|
+
provider: { type: String, default: "" },
|
|
8415
|
+
mapProvider: { type: String, default: "" },
|
|
8416
|
+
apiKey: { type: String, default: "" },
|
|
8417
|
+
token: { type: String, default: "" },
|
|
8418
|
+
tokenUrl: { type: String, default: "" },
|
|
8419
|
+
countryCodes: { type: Array, default: [] },
|
|
8420
|
+
showMap: { type: Boolean, default: false },
|
|
8421
|
+
nominatimUrl: { type: String, default: "" }
|
|
8422
|
+
};
|
|
8423
|
+
searchTimer;
|
|
8424
|
+
highlightedIndex = -1;
|
|
8425
|
+
mapInstance = null;
|
|
8426
|
+
mapMarker = null;
|
|
8427
|
+
mkSearch = null;
|
|
8428
|
+
autocompleteService = null;
|
|
8429
|
+
placesService = null;
|
|
8430
|
+
// ── Named handlers ────────────────────────────────────────────────────────
|
|
8431
|
+
onToggle = (e) => {
|
|
8432
|
+
const opened = e.newState === "open";
|
|
8433
|
+
if (this.hasInputTarget) {
|
|
8434
|
+
this.inputTarget.setAttribute("aria-expanded", String(opened));
|
|
8435
|
+
}
|
|
8436
|
+
if (!opened) {
|
|
8437
|
+
this.highlightedIndex = -1;
|
|
8438
|
+
}
|
|
8439
|
+
};
|
|
8440
|
+
onFocusOut = (e) => {
|
|
8441
|
+
if (!this.element.contains(e.relatedTarget)) {
|
|
8442
|
+
this.closeDropdown();
|
|
8443
|
+
}
|
|
8444
|
+
};
|
|
8445
|
+
// ── Lifecycle ─────────────────────────────────────────────────────────────
|
|
8446
|
+
async connect() {
|
|
8447
|
+
this.element.addEventListener("focusout", this.onFocusOut);
|
|
8448
|
+
const provider = this.providerValue;
|
|
8449
|
+
const mapProv = this.effectiveMapProvider();
|
|
8450
|
+
if (!provider && !mapProv)
|
|
8451
|
+
return;
|
|
8452
|
+
try {
|
|
8453
|
+
if (provider === "mapkit") {
|
|
8454
|
+
await loadMapKitScript();
|
|
8455
|
+
this.initMapKit();
|
|
8456
|
+
} else if (provider === "google") {
|
|
8457
|
+
await loadGoogleMapsScript(this.apiKeyValue);
|
|
8458
|
+
this.initGoogle();
|
|
8459
|
+
}
|
|
8460
|
+
if (this.showMapValue && mapProv === "leaflet") {
|
|
8461
|
+
await loadLeafletScript();
|
|
8462
|
+
}
|
|
8463
|
+
} catch (err) {
|
|
8464
|
+
console.error("[midwest-address] Failed to initialise:", err);
|
|
8465
|
+
}
|
|
8466
|
+
}
|
|
8467
|
+
disconnect() {
|
|
8468
|
+
clearTimeout(this.searchTimer);
|
|
8469
|
+
this.element.removeEventListener("focusout", this.onFocusOut);
|
|
8470
|
+
if (this.mapInstance) {
|
|
8471
|
+
const mapProv = this.effectiveMapProvider();
|
|
8472
|
+
if (mapProv === "mapkit")
|
|
8473
|
+
this.mapInstance.destroy();
|
|
8474
|
+
if (mapProv === "leaflet")
|
|
8475
|
+
this.mapInstance.remove();
|
|
8476
|
+
}
|
|
8477
|
+
this.mapInstance = null;
|
|
8478
|
+
}
|
|
8479
|
+
dropdownTargetConnected(el) {
|
|
8480
|
+
el.addEventListener("toggle", this.onToggle);
|
|
8481
|
+
}
|
|
8482
|
+
dropdownTargetDisconnected(el) {
|
|
8483
|
+
el.removeEventListener("toggle", this.onToggle);
|
|
8484
|
+
}
|
|
8485
|
+
// ── Public Stimulus actions ───────────────────────────────────────────────
|
|
8486
|
+
search() {
|
|
8487
|
+
this.clearGeocode();
|
|
8488
|
+
const query = this.inputTarget.value.trim();
|
|
8489
|
+
if (query.length < 2) {
|
|
8490
|
+
this.closeDropdown();
|
|
8491
|
+
return;
|
|
8492
|
+
}
|
|
8493
|
+
clearTimeout(this.searchTimer);
|
|
8494
|
+
this.searchTimer = setTimeout(() => {
|
|
8495
|
+
if (this.providerValue === "mapkit") {
|
|
8496
|
+
this.searchMapKit(query);
|
|
8497
|
+
} else if (this.providerValue === "google") {
|
|
8498
|
+
this.searchGoogle(query);
|
|
8499
|
+
} else if (this.providerValue === "nominatim") {
|
|
8500
|
+
this.searchNominatim(query);
|
|
8501
|
+
}
|
|
8502
|
+
}, DEBOUNCE_MS);
|
|
8503
|
+
}
|
|
8504
|
+
navigate(event) {
|
|
8505
|
+
const items = this.suggestionElements();
|
|
8506
|
+
if (!items.length)
|
|
8507
|
+
return;
|
|
8508
|
+
switch (event.key) {
|
|
8509
|
+
case "ArrowDown":
|
|
8510
|
+
event.preventDefault();
|
|
8511
|
+
this.highlightedIndex = Math.min(this.highlightedIndex + 1, items.length - 1);
|
|
8512
|
+
this.applyHighlight(items);
|
|
8513
|
+
break;
|
|
8514
|
+
case "ArrowUp":
|
|
8515
|
+
event.preventDefault();
|
|
8516
|
+
this.highlightedIndex = Math.max(this.highlightedIndex - 1, -1);
|
|
8517
|
+
this.applyHighlight(items);
|
|
8518
|
+
break;
|
|
8519
|
+
case "Enter":
|
|
8520
|
+
event.preventDefault();
|
|
8521
|
+
if (this.highlightedIndex >= 0)
|
|
8522
|
+
items[this.highlightedIndex].dispatchEvent(new MouseEvent("mousedown"));
|
|
8523
|
+
break;
|
|
8524
|
+
case "Escape":
|
|
8525
|
+
this.closeDropdown();
|
|
8526
|
+
break;
|
|
8527
|
+
}
|
|
8528
|
+
}
|
|
8529
|
+
// ── Provider init ─────────────────────────────────────────────────────────
|
|
8530
|
+
initMapKit() {
|
|
8531
|
+
const mk = window.mapkit;
|
|
8532
|
+
const { tokenValue: token, tokenUrlValue: tokenUrl } = this;
|
|
8533
|
+
mk.init({
|
|
8534
|
+
authorizationCallback: (done) => {
|
|
8535
|
+
if (token) {
|
|
8536
|
+
done(token);
|
|
8537
|
+
} else if (tokenUrl) {
|
|
8538
|
+
fetch(tokenUrl).then((r) => r.text()).then(done).catch((err) => console.error("[midwest-address] Token fetch failed:", err));
|
|
8539
|
+
}
|
|
8540
|
+
},
|
|
8541
|
+
language: document.documentElement.lang || "en"
|
|
8542
|
+
});
|
|
8543
|
+
this.mkSearch = new mk.Search({ getsUserLocation: false });
|
|
8544
|
+
}
|
|
8545
|
+
initGoogle() {
|
|
8546
|
+
const g = window.google;
|
|
8547
|
+
this.autocompleteService = new g.maps.places.AutocompleteService();
|
|
8548
|
+
this.placesService = new g.maps.places.PlacesService(document.createElement("div"));
|
|
8549
|
+
}
|
|
8550
|
+
// ── Search ────────────────────────────────────────────────────────────────
|
|
8551
|
+
searchMapKit(query) {
|
|
8552
|
+
if (!this.mkSearch)
|
|
8553
|
+
return;
|
|
8554
|
+
this.mkSearch.autocomplete(query, (err, data) => {
|
|
8555
|
+
if (err || !data?.results?.length) {
|
|
8556
|
+
this.closeDropdown();
|
|
8557
|
+
return;
|
|
8558
|
+
}
|
|
8559
|
+
const results = data.results.map((r) => ({
|
|
8560
|
+
id: r.completionURL ?? r.displayLines?.join(", ") ?? "",
|
|
8561
|
+
label: r.displayLines?.[0] ?? "",
|
|
8562
|
+
secondary: r.displayLines?.[1] ?? "",
|
|
8563
|
+
payload: r
|
|
8564
|
+
}));
|
|
8565
|
+
this.renderSuggestions(results);
|
|
8566
|
+
});
|
|
8567
|
+
}
|
|
8568
|
+
searchGoogle(query) {
|
|
8569
|
+
if (!this.autocompleteService)
|
|
8570
|
+
return;
|
|
8571
|
+
const request = { input: query };
|
|
8572
|
+
if (this.countryCodesValue.length) {
|
|
8573
|
+
request.componentRestrictions = { country: this.countryCodesValue };
|
|
8574
|
+
}
|
|
8575
|
+
this.autocompleteService.getPlacePredictions(
|
|
8576
|
+
request,
|
|
8577
|
+
(predictions, status) => {
|
|
8578
|
+
const g = window.google;
|
|
8579
|
+
if (status !== g.maps.places.PlacesServiceStatus.OK || !predictions?.length) {
|
|
8580
|
+
this.closeDropdown();
|
|
8581
|
+
return;
|
|
8582
|
+
}
|
|
8583
|
+
const results = predictions.map((p) => ({
|
|
8584
|
+
id: p.place_id,
|
|
8585
|
+
label: p.structured_formatting?.main_text ?? p.description,
|
|
8586
|
+
secondary: p.structured_formatting?.secondary_text ?? "",
|
|
8587
|
+
payload: p
|
|
8588
|
+
}));
|
|
8589
|
+
this.renderSuggestions(results);
|
|
8590
|
+
}
|
|
8591
|
+
);
|
|
8592
|
+
}
|
|
8593
|
+
// ── Suggestions UI ────────────────────────────────────────────────────────
|
|
8594
|
+
renderSuggestions(results) {
|
|
8595
|
+
if (!this.hasSuggestionsTarget)
|
|
8596
|
+
return;
|
|
8597
|
+
const list = this.suggestionsTarget;
|
|
8598
|
+
list.innerHTML = "";
|
|
8599
|
+
results.forEach((r, i) => {
|
|
8600
|
+
const li = document.createElement("li");
|
|
8601
|
+
li.className = "midwest-address__suggestion";
|
|
8602
|
+
li.setAttribute("role", "option");
|
|
8603
|
+
li.setAttribute("id", `${this.element.id || "addr"}-s${i}`);
|
|
8604
|
+
li.dataset.index = String(i);
|
|
8605
|
+
const main = document.createElement("span");
|
|
8606
|
+
main.className = "midwest-address__suggestion-main";
|
|
8607
|
+
main.textContent = r.label;
|
|
8608
|
+
li.appendChild(main);
|
|
8609
|
+
if (r.secondary) {
|
|
8610
|
+
const sec = document.createElement("span");
|
|
8611
|
+
sec.className = "midwest-address__suggestion-secondary";
|
|
8612
|
+
sec.textContent = r.secondary;
|
|
8613
|
+
li.appendChild(sec);
|
|
8614
|
+
}
|
|
8615
|
+
li.addEventListener("mousedown", (e) => {
|
|
8616
|
+
e.preventDefault();
|
|
8617
|
+
this.pickSuggestion(r);
|
|
8618
|
+
});
|
|
8619
|
+
list.appendChild(li);
|
|
8620
|
+
});
|
|
8621
|
+
this.openDropdown();
|
|
8622
|
+
this.highlightedIndex = -1;
|
|
8623
|
+
}
|
|
8624
|
+
openDropdown() {
|
|
8625
|
+
if (!this.hasDropdownTarget)
|
|
8626
|
+
return;
|
|
8627
|
+
if (!this.dropdownTarget.matches(":popover-open"))
|
|
8628
|
+
this.dropdownTarget.showPopover();
|
|
8629
|
+
}
|
|
8630
|
+
closeDropdown() {
|
|
8631
|
+
if (this.hasDropdownTarget && this.dropdownTarget.matches(":popover-open")) {
|
|
8632
|
+
this.dropdownTarget.hidePopover();
|
|
8633
|
+
}
|
|
8634
|
+
if (this.hasSuggestionsTarget)
|
|
8635
|
+
this.suggestionsTarget.innerHTML = "";
|
|
8636
|
+
this.highlightedIndex = -1;
|
|
8637
|
+
}
|
|
8638
|
+
suggestionElements() {
|
|
8639
|
+
if (!this.hasSuggestionsTarget)
|
|
8640
|
+
return [];
|
|
8641
|
+
return Array.from(this.suggestionsTarget.querySelectorAll(".midwest-address__suggestion"));
|
|
8642
|
+
}
|
|
8643
|
+
applyHighlight(items) {
|
|
8644
|
+
items.forEach((item, i) => {
|
|
8645
|
+
const active = i === this.highlightedIndex;
|
|
8646
|
+
item.classList.toggle("is-highlighted", active);
|
|
8647
|
+
item.setAttribute("aria-selected", String(active));
|
|
8648
|
+
if (active) {
|
|
8649
|
+
this.inputTarget.setAttribute("aria-activedescendant", item.id);
|
|
8650
|
+
item.scrollIntoView({ block: "nearest" });
|
|
8651
|
+
}
|
|
8652
|
+
});
|
|
8653
|
+
if (this.highlightedIndex < 0) {
|
|
8654
|
+
this.inputTarget.removeAttribute("aria-activedescendant");
|
|
8655
|
+
}
|
|
8656
|
+
}
|
|
8657
|
+
// ── Pick a suggestion ─────────────────────────────────────────────────────
|
|
8658
|
+
pickSuggestion(r) {
|
|
8659
|
+
this.closeDropdown();
|
|
8660
|
+
if (this.providerValue === "mapkit") {
|
|
8661
|
+
this.pickMapKit(r);
|
|
8662
|
+
} else if (this.providerValue === "google") {
|
|
8663
|
+
this.pickGoogle(r);
|
|
8664
|
+
} else if (this.providerValue === "nominatim") {
|
|
8665
|
+
this.pickNominatim(r);
|
|
8666
|
+
}
|
|
8667
|
+
}
|
|
8668
|
+
pickMapKit(r) {
|
|
8669
|
+
if (!this.mkSearch)
|
|
8670
|
+
return;
|
|
8671
|
+
this.mkSearch.search(r.payload, (err, data) => {
|
|
8672
|
+
if (err || !data?.places?.length)
|
|
8673
|
+
return;
|
|
8674
|
+
const place = data.places[0];
|
|
8675
|
+
const coord = place.coordinate;
|
|
8676
|
+
this.inputTarget.value = [r.label, r.secondary].filter(Boolean).join(", ");
|
|
8677
|
+
this.fillGeocode(coord.latitude, coord.longitude, place.id ?? "");
|
|
8678
|
+
this.updateMapPreview(coord.latitude, coord.longitude);
|
|
8679
|
+
});
|
|
8680
|
+
}
|
|
8681
|
+
pickGoogle(r) {
|
|
8682
|
+
if (!this.placesService || !r.id)
|
|
8683
|
+
return;
|
|
8684
|
+
const g = window.google;
|
|
8685
|
+
this.placesService.getDetails(
|
|
8686
|
+
{ placeId: r.id, fields: ["geometry", "formatted_address", "place_id"] },
|
|
8687
|
+
(place, status) => {
|
|
8688
|
+
if (status !== g.maps.places.PlacesServiceStatus.OK || !place)
|
|
8689
|
+
return;
|
|
8690
|
+
const lat = place.geometry.location.lat();
|
|
8691
|
+
const lng = place.geometry.location.lng();
|
|
8692
|
+
this.inputTarget.value = place.formatted_address;
|
|
8693
|
+
this.fillGeocode(lat, lng, place.place_id);
|
|
8694
|
+
this.updateMapPreview(lat, lng);
|
|
8695
|
+
}
|
|
8696
|
+
);
|
|
8697
|
+
}
|
|
8698
|
+
// ── Geocode field management ──────────────────────────────────────────────
|
|
8699
|
+
fillGeocode(lat, lng, placeId) {
|
|
8700
|
+
if (this.hasLatInputTarget)
|
|
8701
|
+
this.latInputTarget.value = String(lat);
|
|
8702
|
+
if (this.hasLngInputTarget)
|
|
8703
|
+
this.lngInputTarget.value = String(lng);
|
|
8704
|
+
if (this.hasPlaceIdInputTarget)
|
|
8705
|
+
this.placeIdInputTarget.value = placeId;
|
|
8706
|
+
this.dispatch("geocode", { detail: { lat, lng, placeId } });
|
|
8707
|
+
}
|
|
8708
|
+
clearGeocode() {
|
|
8709
|
+
if (this.hasLatInputTarget)
|
|
8710
|
+
this.latInputTarget.value = "";
|
|
8711
|
+
if (this.hasLngInputTarget)
|
|
8712
|
+
this.lngInputTarget.value = "";
|
|
8713
|
+
if (this.hasPlaceIdInputTarget)
|
|
8714
|
+
this.placeIdInputTarget.value = "";
|
|
8715
|
+
}
|
|
8716
|
+
// ── Nominatim search (OpenStreetMap, no API key) ──────────────────────────
|
|
8717
|
+
searchNominatim(query) {
|
|
8718
|
+
const base = this.nominatimUrlValue || "https://nominatim.openstreetmap.org";
|
|
8719
|
+
const url = `${base}/search?q=${encodeURIComponent(query)}&format=json&limit=5&addressdetails=1`;
|
|
8720
|
+
fetch(url, { headers: { "Accept-Language": document.documentElement.lang || "en" } }).then((r) => r.json()).then((data) => {
|
|
8721
|
+
if (!data?.length) {
|
|
8722
|
+
this.closeDropdown();
|
|
8723
|
+
return;
|
|
8724
|
+
}
|
|
8725
|
+
const results = data.map((r) => {
|
|
8726
|
+
const parts = r.display_name.split(", ");
|
|
8727
|
+
return {
|
|
8728
|
+
id: String(r.place_id),
|
|
8729
|
+
label: r.name || parts[0] || r.display_name,
|
|
8730
|
+
secondary: parts.length > 1 ? parts.slice(1).join(", ") : "",
|
|
8731
|
+
payload: r
|
|
8732
|
+
};
|
|
8733
|
+
});
|
|
8734
|
+
this.renderSuggestions(results);
|
|
8735
|
+
}).catch(() => this.closeDropdown());
|
|
8736
|
+
}
|
|
8737
|
+
pickNominatim(r) {
|
|
8738
|
+
const place = r.payload;
|
|
8739
|
+
const lat = parseFloat(place.lat);
|
|
8740
|
+
const lng = parseFloat(place.lon);
|
|
8741
|
+
this.inputTarget.value = place.display_name;
|
|
8742
|
+
this.fillGeocode(lat, lng, `${place.osm_type}:${place.osm_id}`);
|
|
8743
|
+
this.updateMapPreview(lat, lng);
|
|
8744
|
+
}
|
|
8745
|
+
// ── Map preview ───────────────────────────────────────────────────────────
|
|
8746
|
+
// Returns the provider used for the map preview. `mapProviderValue` is set
|
|
8747
|
+
// when the geocoder and map providers differ; falls back to `providerValue`
|
|
8748
|
+
// so existing single-provider setups (mapkit/google) work without change.
|
|
8749
|
+
effectiveMapProvider() {
|
|
8750
|
+
return this.mapProviderValue || this.providerValue;
|
|
8751
|
+
}
|
|
8752
|
+
updateMapPreview(lat, lng) {
|
|
8753
|
+
if (!this.hasMapContainerTarget || !this.showMapValue)
|
|
8754
|
+
return;
|
|
8755
|
+
this.mapContainerTarget.hidden = false;
|
|
8756
|
+
const mapProv = this.effectiveMapProvider();
|
|
8757
|
+
if (mapProv === "mapkit") {
|
|
8758
|
+
this.updateMapKitPreview(lat, lng);
|
|
8759
|
+
} else if (mapProv === "google") {
|
|
8760
|
+
this.updateGooglePreview(lat, lng);
|
|
8761
|
+
} else if (mapProv === "leaflet") {
|
|
8762
|
+
this.updateLeafletPreview(lat, lng);
|
|
8763
|
+
}
|
|
8764
|
+
}
|
|
8765
|
+
updateMapKitPreview(lat, lng) {
|
|
8766
|
+
const mk = window.mapkit;
|
|
8767
|
+
const coord = new mk.Coordinate(lat, lng);
|
|
8768
|
+
if (!this.mapInstance) {
|
|
8769
|
+
this.mapInstance = new mk.Map(this.mapContainerTarget, {
|
|
8770
|
+
mapType: mk.Map.MapTypes.MutedStandard,
|
|
8771
|
+
showsCompass: mk.FeatureVisibility.Hidden,
|
|
8772
|
+
showsZoomControl: false,
|
|
8773
|
+
showsMapTypeControl: false,
|
|
8774
|
+
isScrollEnabled: false,
|
|
8775
|
+
isZoomEnabled: false
|
|
8776
|
+
});
|
|
8777
|
+
}
|
|
8778
|
+
this.mapInstance.setRegionAnimated(
|
|
8779
|
+
new mk.CoordinateRegion(coord, new mk.CoordinateSpan(0.01, 0.01))
|
|
8780
|
+
);
|
|
8781
|
+
if (this.mapMarker)
|
|
8782
|
+
this.mapInstance.removeAnnotation(this.mapMarker);
|
|
8783
|
+
this.mapMarker = new mk.MarkerAnnotation(coord);
|
|
8784
|
+
this.mapInstance.addAnnotation(this.mapMarker);
|
|
8785
|
+
}
|
|
8786
|
+
updateGooglePreview(lat, lng) {
|
|
8787
|
+
const g = window.google;
|
|
8788
|
+
const center = { lat, lng };
|
|
8789
|
+
if (!this.mapInstance) {
|
|
8790
|
+
this.mapInstance = new g.maps.Map(this.mapContainerTarget, {
|
|
8791
|
+
center,
|
|
8792
|
+
zoom: 15,
|
|
8793
|
+
disableDefaultUI: true,
|
|
8794
|
+
gestureHandling: "none",
|
|
8795
|
+
styles: GOOGLE_PREVIEW_STYLES
|
|
8796
|
+
});
|
|
8797
|
+
} else {
|
|
8798
|
+
this.mapInstance.setCenter(center);
|
|
8799
|
+
this.mapInstance.setZoom(15);
|
|
8800
|
+
}
|
|
8801
|
+
if (this.mapMarker)
|
|
8802
|
+
this.mapMarker.setMap(null);
|
|
8803
|
+
this.mapMarker = new g.maps.Marker({ map: this.mapInstance, position: center });
|
|
8804
|
+
}
|
|
8805
|
+
updateLeafletPreview(lat, lng) {
|
|
8806
|
+
const L = window.L;
|
|
8807
|
+
if (!L)
|
|
8808
|
+
return;
|
|
8809
|
+
if (!this.mapInstance) {
|
|
8810
|
+
this.mapInstance = L.map(this.mapContainerTarget, {
|
|
8811
|
+
center: [lat, lng],
|
|
8812
|
+
zoom: 15,
|
|
8813
|
+
zoomControl: false,
|
|
8814
|
+
attributionControl: false
|
|
8815
|
+
});
|
|
8816
|
+
L.tileLayer("https://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}{r}.png", {
|
|
8817
|
+
maxZoom: 19
|
|
8818
|
+
}).addTo(this.mapInstance);
|
|
8819
|
+
} else {
|
|
8820
|
+
this.mapInstance.setView([lat, lng], 15);
|
|
8821
|
+
}
|
|
8822
|
+
if (this.mapMarker)
|
|
8823
|
+
this.mapMarker.remove();
|
|
8824
|
+
this.mapMarker = L.marker([lat, lng]).addTo(this.mapInstance);
|
|
8825
|
+
}
|
|
8826
|
+
}
|
|
8827
|
+
|
|
8828
|
+
class Popover extends Controller {
|
|
8829
|
+
animating = false;
|
|
8830
|
+
closeTimer = null;
|
|
8831
|
+
connect() {
|
|
8832
|
+
this.element.addEventListener("beforetoggle", this.onBeforeToggle);
|
|
8833
|
+
}
|
|
8834
|
+
disconnect() {
|
|
8835
|
+
this.element.removeEventListener("beforetoggle", this.onBeforeToggle);
|
|
8836
|
+
this.cleanUp();
|
|
8837
|
+
}
|
|
8838
|
+
onBeforeToggle = (event) => {
|
|
8839
|
+
const toggle = event;
|
|
8840
|
+
if (toggle.newState === "open") {
|
|
8841
|
+
if (this.closeTimer) {
|
|
8842
|
+
clearTimeout(this.closeTimer);
|
|
8843
|
+
this.closeTimer = null;
|
|
8844
|
+
}
|
|
8845
|
+
this.element.classList.remove("is-closing");
|
|
8846
|
+
this.animating = false;
|
|
8847
|
+
return;
|
|
8848
|
+
}
|
|
8849
|
+
if (toggle.newState !== "closed")
|
|
8850
|
+
return;
|
|
8851
|
+
if (window.matchMedia("(width < 700px)").matches)
|
|
8852
|
+
return;
|
|
8853
|
+
if (!this.element.classList.contains("midwest-motion"))
|
|
8854
|
+
return;
|
|
8855
|
+
if (this.animating)
|
|
8856
|
+
return;
|
|
8857
|
+
event.preventDefault();
|
|
8858
|
+
this.animating = true;
|
|
8859
|
+
this.element.classList.add("is-closing");
|
|
8860
|
+
this.closeTimer = setTimeout(() => {
|
|
8861
|
+
this.closeTimer = null;
|
|
8862
|
+
this.element.style.display = "none";
|
|
8863
|
+
this.element.style.transition = "none";
|
|
8864
|
+
this.element.hidePopover();
|
|
8865
|
+
this.animating = false;
|
|
8866
|
+
requestAnimationFrame(() => {
|
|
8867
|
+
this.element.style.removeProperty("display");
|
|
8868
|
+
this.element.style.removeProperty("transition");
|
|
8869
|
+
this.element.classList.remove("is-closing");
|
|
8870
|
+
});
|
|
8871
|
+
}, this.exitDuration());
|
|
8872
|
+
};
|
|
8873
|
+
cleanUp() {
|
|
8874
|
+
if (this.closeTimer) {
|
|
8875
|
+
clearTimeout(this.closeTimer);
|
|
8876
|
+
this.closeTimer = null;
|
|
8877
|
+
}
|
|
8878
|
+
this.element.classList.remove("is-closing");
|
|
8879
|
+
this.element.style.removeProperty("transition");
|
|
8880
|
+
this.animating = false;
|
|
8881
|
+
}
|
|
8882
|
+
exitDuration() {
|
|
8883
|
+
const durations = getComputedStyle(this.element).transitionDuration.split(",").map((s) => {
|
|
8884
|
+
const n = parseFloat(s);
|
|
8885
|
+
return s.trim().endsWith("ms") ? n : n * 1e3;
|
|
8886
|
+
});
|
|
8887
|
+
return Math.max(0, ...durations);
|
|
8888
|
+
}
|
|
8889
|
+
}
|
|
8890
|
+
|
|
7895
8891
|
function registerMidwestControllers(application) {
|
|
7896
8892
|
application.register("midwest-card", Card);
|
|
7897
8893
|
application.register("midwest-banner", Banner);
|
|
@@ -7934,6 +8930,10 @@ function registerMidwestControllers(application) {
|
|
|
7934
8930
|
application.register("midwest-panorama", Panorama);
|
|
7935
8931
|
application.register("midwest-playlist", Playlist);
|
|
7936
8932
|
application.register("midwest-motion", Motion);
|
|
8933
|
+
application.register("midwest-page-transition", PageTransition);
|
|
8934
|
+
application.register("midwest-map", Map$1);
|
|
8935
|
+
application.register("midwest-address", Address);
|
|
8936
|
+
application.register("midwest-popover", Popover);
|
|
7937
8937
|
}
|
|
7938
8938
|
|
|
7939
8939
|
export { Banner, Card, Chart, CountdownTimer, registerMidwestControllers };
|