@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.
Files changed (26) hide show
  1. package/app/assets/javascript/midwest/index.ts +8 -0
  2. package/app/assets/javascript/midwest/map_providers.ts +61 -0
  3. package/app/assets/javascript/midwest.js +972 -6
  4. package/app/assets/javascript/midwest.js.map +1 -1
  5. package/app/assets/stylesheets/midwest.css +1 -1
  6. package/app/assets/stylesheets/midwest.tailwind.css +6 -1
  7. package/dist/css/midwest.css +1 -1
  8. package/dist/javascript/collection/app/assets/javascript/midwest/index.js +8 -0
  9. package/dist/javascript/collection/app/assets/javascript/midwest/index.js.map +1 -1
  10. package/dist/javascript/collection/app/assets/javascript/midwest/map_providers.js +56 -0
  11. package/dist/javascript/collection/app/assets/javascript/midwest/map_providers.js.map +1 -0
  12. package/dist/javascript/collection/app/components/midwest/form/address_component/address_component_controller.js +441 -0
  13. package/dist/javascript/collection/app/components/midwest/form/address_component/address_component_controller.js.map +1 -0
  14. package/dist/javascript/collection/app/components/midwest/form/live_summary_component/live_summary_component_controller.js +55 -1
  15. package/dist/javascript/collection/app/components/midwest/form/live_summary_component/live_summary_component_controller.js.map +1 -1
  16. package/dist/javascript/collection/app/components/midwest/map_component/map_component_controller.js +297 -0
  17. package/dist/javascript/collection/app/components/midwest/map_component/map_component_controller.js.map +1 -0
  18. package/dist/javascript/collection/app/components/midwest/onboarding_component/onboarding_component_controller.js +34 -5
  19. package/dist/javascript/collection/app/components/midwest/onboarding_component/onboarding_component_controller.js.map +1 -1
  20. package/dist/javascript/collection/app/components/midwest/page_transition_component/page_transition_component_controller.js +38 -0
  21. package/dist/javascript/collection/app/components/midwest/page_transition_component/page_transition_component_controller.js.map +1 -0
  22. package/dist/javascript/collection/app/components/midwest/popover_component/popover_component_controller.js +67 -0
  23. package/dist/javascript/collection/app/components/midwest/popover_component/popover_component_controller.js.map +1 -0
  24. package/dist/javascript/midwest.js +972 -6
  25. package/dist/javascript/midwest.js.map +1 -1
  26. 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: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors &copy; <a href="https://carto.com/attributions">CARTO</a>'
8070
+ },
8071
+ Standard: {
8072
+ url: "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png",
8073
+ attribution: '&copy; <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: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors &copy; <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 &copy; Esri &mdash; 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: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors &copy; <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 };