PageSourceSearch

https://mountaincabinsutah.com/wp-content/plugins/homerunner/assets/js/explorer-map-leaflet.js?ver=2.6.7

js mountaincabinsutah.com collected 2026-10-10 02:01:36 UTC 28,579 bytes, 879 lines download raw bytes

1(function ($, settings) {
2  $.fn.homelocalExplorerMap = function (options, explorerSettings) {
3    const opts = $.extend({}, options);
4    const $container = $(this);
5
6    let map;
7    let propertyMarkers = {};
8    let mapMarkers = [];
9    let markerClusterer = null;
10    let cachedProperties = [];
11    let currentMarker = null;
12    let currentFocusMarker = null;
13    let mapSearchActive = false;
14    let mapBounds = null;
15
16    const selector = {
17      dateModal: `#hfse-form-date-modal-${opts.id}`,
18    };
19
20    const classNames = {
21      badgeParentFocus: "hfse-map-badge-parent-focus",
22    };
23
24    const debounce = (func, timeout = 300) => {
25      let timer;
26      return (...args) => {
27        clearTimeout(timer);
28        timer = setTimeout(() => {
29          func.apply(this, args);
30        }, timeout);
31      };
32    };
33
34    // Track the current focused badge globally
35
36    class OverlayMarker extends L.Marker {
37      constructor(position, id, badgeHTML, map) {
38        super(position, {
39          icon: L.divIcon({
40            html: badgeHTML,
41            iconSize: [60, 40],
42            iconAnchor: [30, 20],
43          }),
44          interactive: true,
45        });
46
47        this.id_ = id;
48        this.map_ = map;
49
50        // Create tooltip and attach it to the map container
51        const overlayPane =
52          this.map_.getPane("tooltipPane") || this.map_.getPane("overlayPane");
53
54        this.tooltip_ = L.DomUtil.create(
55          "div",
56          "hfse-map-tooltip",
57          overlayPane,
58        );
59        this.tooltip_.style.display = "none";
60
61        // Add event listeners
62        this.on("click", this.showTooltip.bind(this));
63        this.on("remove", this.onRemove.bind(this));
64        this._boundUpdateTooltip = this.updateTooltipPosition.bind(this);
65        this.map_.on("zoomend move", this._boundUpdateTooltip);
66
67        this.addTo(map);
68      }
69
70      showTooltip() {
71        if (currentMarker === this) {
72          // Just close it and clear the reference
73          currentMarker.tooltip_.style.display = "none";
74          currentMarker = null;
75        } else {
76          if (currentMarker) {
77            currentMarker.tooltip_.style.display = "none";
78          }
79          this.tooltip_.innerHTML = `<div class="hfse-map-tooltip-content">${app.getPopupContent(
80            this.id_,
81          )}</div>
82        <div class="hfse-map-tooltip-close-btn"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32"><path d="m6 6 20 20M26 6 6 26"></path></svg></div>`;
83          this.tooltip_.style.display = "block";
84          this.updateTooltipPosition();
85          // Close tooltip when clicking the close button
86          this.tooltip_
87            .querySelector(".hfse-map-tooltip-close-btn")
88            .addEventListener("click", () => {
89              this.tooltip_.style.display = "none";
90              currentMarker = null;
91            });
92          currentMarker = this;
93        }
94      }
95
96      updateTooltipPosition() {
97        if (this.tooltip_.style.display === "block") {
98          const pane = this._icon._leaflet_pos;
99          this.tooltip_.style.transform = `translate3d(${pane.x - 150}px, ${
100            pane.y + 30
101          }px, 0px)`;
102        }
103      }
104
105      focus() {
106        if (currentFocusMarker) {
107          currentFocusMarker._icon.classList.remove(
108            classNames.badgeParentFocus,
109          );
110        }
111
112        // Ensure the badge element is correctly referenced
113        this._icon.classList.add(classNames.badgeParentFocus);
114
115        // Set currentFocusMarker to this badge
116        currentFocusMarker = this;
117
118        const mapBounds = this.map_.getBounds();
119        if (!mapBounds) return;
120
121        // Calculate a smaller bounding box with a 10% margin
122        const sw = mapBounds.getSouthWest();
123        const ne = mapBounds.getNorthEast();
124
125        const latMargin = (ne.lat - sw.lat) * 0.1; // 10% latitude margin
126        const lngMargin = (ne.lng - sw.lng) * 0.1; // 10% longitude margin
127
128        const smallerBounds = L.latLngBounds(
129          [sw.lat + latMargin, sw.lng + lngMargin],
130          [ne.lat - latMargin, ne.lng - lngMargin],
131        );
132
133        // Get the marker position
134        const markerLatLng = this.getLatLng();
135
136        // Check if marker is within the smaller bounding box
137        if (!smallerBounds.contains(markerLatLng)) {
138          const currentCenter = this.map_.getCenter();
139          let newLat = currentCenter.lat;
140          let newLng = currentCenter.lng;
141
142          if (markerLatLng.lng > smallerBounds.getNorthEast().lng) {
143            // Marker is to the right
144            newLng = markerLatLng.lng - (ne.lng - sw.lng) / 2 + lngMargin;
145          } else if (markerLatLng.lng < smallerBounds.getSouthWest().lng) {
146            // Marker is to the left
147            newLng = markerLatLng.lng + (ne.lng - sw.lng) / 2 - lngMargin;
148          }
149
150          if (markerLatLng.lat > smallerBounds.getNorthEast().lat) {
151            // Marker is above
152            newLat = markerLatLng.lat - (ne.lat - sw.lat) / 2 + latMargin;
153          }
153 else if (markerLatLng.lat < smallerBounds.getSouthWest().lat) {
154            // Marker is below
155            newLat = markerLatLng.lat + (ne.lat - sw.lat) / 2 - latMargin;
156          }
157
158          // Pan the map to center on the marker
159          this.map_.panTo([newLat, newLng]);
160        }
161      }
162
163      blur() {
164        if (currentFocusMarker) {
165          currentFocusMarker._icon.classList.remove(
166            classNames.badgeParentFocus,
167          );
168        }
169      }
170
171      onRemove() {
172        if (currentFocusMarker === this) {
173          currentFocusMarker = null;
174        }
175
176        // Remove tooltip from DOM
177        if (this.tooltip_ && this.tooltip_.parentNode) {
178          this.tooltip_.parentNode.removeChild(this.tooltip_);
179          this.tooltip_ = null;
180        }
181
182        // Remove event listeners
183        this.off();
184        if (this.map_ && this._boundUpdateTooltip) {
185          this.map_.off("zoomend move", this._boundUpdateTooltip);
186        }
187
188        // Clear references
189        this.map_ = null;
190        this._boundUpdateTooltip = null;
191      }
192
193      cleanup() {
194        if (this.map_) {
195          this.map_.removeLayer(this);
196        }
197        this.onRemove();
198      }
199    }
200
201    L.Control.CustomControl = L.Control.extend({
202      onAdd: function (map) {
203        const controlDiv = L.DomUtil.create(
204          "div",
205          "leaflet-control hfse-map-controls",
206        );
207
208        // Prevents clicks from propagating to the map
209        L.DomEvent.disableClickPropagation(controlDiv);
210
211        const createControlButton = (className, icon, title, onClick) => {
212          const button = L.DomUtil.create(
213            "span",
214            `map-control ${className}`,
215            controlDiv,
216          );
217          button.setAttribute("hk-icon", icon);
218          button.title = title;
219          button.addEventListener("click", onClick);
220          return button;
221        };
222
223        // Home Button
224        createControlButton(
225          "control-home",
226          "home",
227          settings.center_map_title,
228          () => {
229            app.closeTooltip();
230            app.mapFitBounds();
231          },
232        );
233
234        // Previous Marker Button
235        createControlButton("control-prev", "chevron-left", "Previous", () => {
236          if (mapMarkers.length < 1) return;
237
238          if (!currentMarker) {
239            mapMarkers[0].showTooltip();
240            return;
241          }
242
243          for (let i = 0; i < mapMarkers.length; i++) {
244            if (currentMarker === mapMarkers[i]) {
245              const prevMarker =
246                i === 0 ? mapMarkers[mapMarkers.length - 1] : mapMarkers[i - 1];
247              prevMarker.showTooltip();
248              break;
249            }
250          }
251        });
252
253        // Next Marker Button
254        createControlButton("control-next", "chevron-right", "Next", () => {
255          if (mapMarkers.length < 1) return;
256
257          if (!currentMarker) {
258            mapMarkers[0].showTooltip();
259            return;
260          }
261
262          for (let i = 0; i < mapMarkers.length; i++) {
263            if (currentMarker === mapMarkers[i]) {
264              const nextMarker =
265                i === mapMarkers.length - 1 ? mapMarkers[0] : mapMarkers[i + 1];
266              nextMarker.showTooltip();
267              break;
268            }
269          }
270        });
271
272        // Fullscreen Button
273        const elementToSendFullscreen = map.getContainer();
274        const fullScrUI = createControlButton(
275          "control-fullscreen",
276          "expand",
277          "Fullscreen",
278          () => {
279            if (app.isFullscreen(elementToSendFullscreen)) {
280              app.exitFullscreen();
281            } else {
282              app.requestFullscreen(elementToSendFullscreen);
283            }
284          },
285        );
286
287        // Handle fullscreen state changes
288        document.onfullscreenchange = () => {
289          if (app.isFullscreen(elementToSendFullscreen)) {
290            fullScrUI.classList.add("is-fullscreen");
291            fullScrUI.setAttribute("hk-icon", "shrink");
292          } else {
293            fullScrUI.classList.remove("is-fullscreen");
294            fullScrUI.setAttribute("hk-icon", "expand");
295          }
296        };
297
298        // Zoom Out Button
299        createControlButton("control-zoomout", "minus", "Zoom Out", () => {
300          map.setZoom(map.getZoom() - 1);
301        });
302
303        // Zoom In Button
304        createControlButton("control-zoomin", "plus", "Zoom In", 
304() => {
305          map.setZoom(map.getZoom() + 1);
306        });
307
308        if (options.map_search_enabled) {
309          // Add a checkbox for enabling/disabling search on map movement
310          const controlButton = L.DomUtil.create(
311            "span",
312            `map-control control-search`,
313            controlDiv,
314          );
315
316          const inputContainer = L.DomUtil.create(
317            "div",
318            "map-control-input-container",
319            controlButton,
320          );
321
322          const inputCheckbox = L.DomUtil.create(
323            "input",
324            "map-control-checkbox",
325            inputContainer,
326          );
327
328          inputCheckbox.type = "checkbox";
329          inputCheckbox.id = "hr-map-search-checkbox";
330          inputCheckbox.checked = false;
331          inputCheckbox.title = options.map_search_label;
332          inputCheckbox.addEventListener("change", (e) => {
333            mapSearchActive = e.target.checked;
334            if (options.onMapSearchStatusChange) {
335              options.onMapSearchStatusChange(e.target.checked);
336            }
337          });
338
339          const inputLabel = L.DomUtil.create(
340            "label",
341            "map-control-label",
342            inputContainer,
343          );
344
345          inputLabel.setAttribute("for", "hr-map-search-checkbox");
346          inputLabel.innerHTML = options.map_search_label;
347        }
348
349        return controlDiv;
350      },
351
352      onRemove: function (map) {
353        // No special cleanup required
354      },
355    });
356
357    const app = {
358      handleMarker: (id, action) => {
359        if (propertyMarkers[id] && propertyMarkers[id][action]) {
360          propertyMarkers[id][action]();
361        }
362      },
363
364      focusMarker: (id) => app.handleMarker(id, "focus"),
365
366      blurMarker: (id) => app.handleMarker(id, "blur"),
367
368      price: (price, currency) =>
369        $.homelocalPrice(price, currency || settings.currency),
370
371      positionMap: () => {
372        const $mapcont = $container.find(".hfse-map-container");
373        const viewportH = $(window).outerHeight();
374        const viewportW = $(window).outerWidth();
375        const header = $(settings.stricky_header_selector);
376        const topGap = header.length > 0 ? header.outerHeight() : 0;
377
378        $mapcont.css(
379          viewportW < 960
380            ? { top: "0px", height: "400px" }
381            : { top: topGap, height: viewportH - topGap },
382        );
383      },
384
385      closeTooltip: () => {
386        if (currentMarker) {
387          currentMarker.tooltip_.style.display = "none";
388          currentMarker = null;
389        }
390      },
391
392      setProperties: (properties) => (cachedProperties = properties),
393
394      resetMapMarkers: () => {
395        if (!map) return;
396
397        // Remove each marker properly
398        mapMarkers.forEach((marker) => {
399          try {
400            // Remove from map
401            if (marker._map) {
402              marker._map.removeLayer(marker);
403            }
404
405            // Call onRemove explicitly
406            if (marker.onRemove) {
407              marker.onRemove();
408            }
409
410            // Remove any remaining DOM elements
411            const markerElement = marker.getElement();
412            if (markerElement && markerElement.parentNode) {
413              markerElement.parentNode.removeChild(markerElement);
414            }
415          } catch (error) {
416            console.warn("Error removing marker:", error);
417          }
418        });
419
420        // Clear references
421        mapMarkers = [];
422        propertyMarkers = {};
423
424        // Reset marker clustering if used
425        if (markerClusterer) {
426          try {
427            markerClusterer.clearLayers();
428            markerClusterer = null;
429          } catch (error) {
430            console.warn("Error clearing marker cluster:", error);
431          }
432        }
433
434        // Force a map update
435        if (map && map.invalidateSize) {
436          map.invalidateSize();
437        }
438      },
439
440      updateMarkers: () => {
441        if (!map) return;
442
443        app.resetMapMarkers();
444
445        if (!cachedProperties.length) return;
446
447        let latLng = [0, 0];
448        cachedProperties.some((property) => {
449          const { acf } = property;
450          if (acf.geolocation && acf.geolocation.lat && acf.geolocation.lng) {
451            latLng = [acf.geolocation.lat, acf.geolocation.lng];
452            return true; // breaks out of the some() loop once a valid latLng is found
453          }
454          return false;
455        });
456
457        if (!map) {
458          map = L.map("map", {
459            minZoom: parseInt(opts.map_zoom_min, 10),
460            maxZoom: parseInt(opts.map_zoom_max, 10),
461            zoom: parseInt(opts.map_zoom_min, 10),
462            zoomControl: false, // Disables default zoom control
463            attributionControl: false, // Disables default attribution
464          });
465          L.map("map").setView(latLng, parseInt(opts.map_zoom_min, 10));
466
467          // Add OpenStreetMap tile layer
468          L.tileLayer("https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", {
469            attribution: "&copy; OpenStreetMap contributors",
470          }).addTo(map);
471        }
472
473        cachedProperties.forEach(
474          ({ id, acf, average_rent, lowest_rent, total_rent }) => {
475            const { base_daily_rate, currency, geolocation } = acf;
476            if (!geolocation || !geolocation.lat || !geolocation.lng) return;
477
478            const priceText = (() => {
479              if (opts.full_price === "1" && app.isDateQuery()) {
480                return total_rent ? app.price(total_rent, currency) : "";
481              }
482
483              switch (
484                app.isDateQuery() ? opts.with_dates_price : opts.no_dates_price
485              ) {
486                case "average_price":
487                  return app.price(average_rent || base_daily_rate, currency);
488                case "base_price":
489                  return app.price(base_daily_rate, currency);
490                case "lowest_price":
491                  return app.price(lowest_rent || base_daily_rate, currency);
492                default:
493                  return "";
494              }
495            })();
496
497            const marker = new OverlayMarker(
498              [geolocation.lat, geolocation.lng], // Leaflet uses [lat, lng] array format
499              id,
500              app.getBadgeHTML(priceText),
501              map,
502            );
503
504            propertyMarkers[id] = marker;
505            mapMarkers.push(marker);
506          },
507        );
508
509        if (map && (!mapSearchActive || mapBounds === null)) {
510          app.mapFitBounds();
511          mapBounds = app.getMapBoundBoxString();
512        }
513      },
514
515      getBadgeHTML: (price) => {
516        if (!price) {
517          if (settings.marker_icon) {
518            return `
519							<img src="${settings.marker_icon}" alt="Property Icon" style="width:${settings.marker_icon_width}px;height:${settings.marker_icon_width}px;" />
520							`;
521          } else {
522            return `
523        <div class="hfse-map-badge hfse-map-badge-icon">
524          <svg fill="#000000" version="1.1" xmlns="http://www.w3.org/2000/svg" 
525              viewBox="0 0 486.196 486.196" xml:space="preserve">
526            <g>
527              <path d="M481.708,220.456l-228.8-204.6c-0.4-0.4-0.8-0.7-1.3-1c-5-4.8-13-5-18.3-0.3l-228.8,204.6c-5.6,5-6,13.5-1.1,19.1
528                c2.7,3,6.4,4.5,10.1,4.5c3.2,0,6.4-1.1,9-3.4l41.2-36.9v7.2v106.8v124.6c0,18.7,15.2,34,34,34c0.3,0,0.5,0,0.8,0s0.5,0,0.8,0h70.6
529                c17.6,0,31.9-14.3,31.9-31.9v-121.3c0-2.7,2.2-4.9,4.9-4.9h72.9c2.7,0,4.9,2.2,4.9,4.9v121.3c0,17.6,14.3,31.9,31.9,31.9h72.2
530                c19,0,34-18.7,34-42.6v-111.2v-34v-83.5l41.2,36.9c2.6,2.3,5.8,3.4,9,3.4c3.7,0,7.4-1.5,10.1-4.5
531                C487.708,233.956,487.208,225.456,481.708,220.456z"/>
532            </g>
533          </svg>
534        </div>`;
535          }
536        } else {
537          return `<div class="hfse-map-badge hfse-map-badge-price">${price}</div>`;
538        }
539      },
540
541      getPriceHtml: (property) => {
542        const {
543          average_rent,
544          average_rent_discounted,
545          lowest_rent,
546          total_rent,
547        } = property;
548        const { base_daily_rate, currency } = property.acf;
549
550        const replaceText = (text, price) =>
551          text.replace("%s", `<strong>${app.price(price, currency)}</strong>`);
552
553        let avgPriceHtml = `<div class="hfse-property-infowindow-price">`;
554
555        if (app.isDateQuery()) {
556          if (options.full_price === "1") {
557            avgPriceHtml += `<span class="hfse-property-price-total">${explorerSettings.total_rent_text.replace(
558              "%s",
559              app.price(total_rent, currency),
560            )}</span>`;
561
562            if (options.full_price_after_tax === "0") {
563              avgPriceHtml += `<div class="hk-text hk-text-small hfse-property-tax-note">${explorerSettings.total_excluding_tax_note}</div>`;
564            }
565          } else {
566            if (average_rent > average_rent_discounted) {
567              avgPriceHtml += ` <del>${app.price(
568                average_rent,
569                currency,
570              )}</del> `;
571            }
572            avgPriceHtml += replaceText(
573              settings.per_night_rent,
574              average_rent_discounted,
575            );
576          }
577        } else {
578          avgPriceHtml += replaceText(
579            settings.avg_rent_text,
580            average_rent,
581            currency,
582          );
583        }
584        avgPriceHtml += `</div>`;
585
586        const baseRentHtml = `
587          <div class="hfse-property-infowindow-price">
588            ${replaceText(settings.base_rent_text, base_daily_rate)}
589          </div>`;
590
591        const lowestRentHtml = `
592          <div class="hfse-property-infowindow-price">
593            ${replaceText(settings.lowest_rent_text, lowest_rent)}
594          </div>`;
595
596        const priceToDisplay = app.isDateQuery()
597          ? opts.with_dates_price
598          : opts.no_dates_price;
599
600        if (priceToDisplay === "average_price" && average_rent !== null) {
601          return avgPriceHtml;
602        } else if (priceToDisplay === "lowest_price" && lowest_rent !== null) {
603          return lowestRentHtml;
604        } else if (priceToDisplay !== "none") {
605          return baseRentHtml;
606        } else {
607          return "";
608        }
609      },
610
611      getTermsHtml: (property, taxonomy, iconSrc, limit = "3") => {
612        const terms = property[taxonomy];
613        const visibleItems =
614          terms
615            ?.filter((term) => term.meta?.show_in_card)
616            .slice(0, parseInt(limit || "3", 10)) || [];
617
618        if (!visibleItems.length) return "";
619
620        const termNames = visibleItems.map((c) => c.name).join(" / ");
621        return `
622          <div class="hk-flex hk-flex-middle hk-grid-column-small">
623              <div class="hk-width-auto hk-first-column">
624                  <img alt="" height="18" width="18" src="${iconSrc}" />
625              </div>
626              <div class="hk-width-auto hk-text-small hk-last-column">${termNames}</div>
627          </div>`;
628      },
629
630      getPopupContent: (id) => {
631        const property = cachedProperties.find((p) => p.id === id);
632        if (!property || !property.acf.geolocation) return "";
633
634        const {
635          title,
636          subtitle,
637          image,
638          url,
639          acf: { lat, lng, photos, base_daily_rate, currency },
640        } = property;
641
642        const coverImage = image.id
643          ? image.sizes["homelocal-thumbnail"] ||
644            image.sizes["homelocal-medium"] ||
645            image.sizes["large"]
646          : photos.length > 0
647          ? photos[0].sizes["homelocal-thumbnail"] ||
648            photos[0].sizes["homelocal-medium"] ||
649            photos[0].sizes["large"]
650          : "";
651
652        const priceHtml = app.getPriceHtml(property);
653        const leftTerms = app.getTermsHtml(
654          property,
655          opts.grid_left_taxonomy,
656          opts.locations_icon,
657          opts.grid_left_limit,
658        );
659        const rightTerms = app.getTermsHtml(
660          property,
661          opts.grid_right_taxonomy,
662          opts.types_icon,
663          opts.grid_right_limit,
664        );
665
666        return `
667          <a class="hk-card hk-card-small hk-card-default hk-display-block hk-text-decoration-none" href="${url}"${
668            opts.newtab ? ` target="_blank"` : ``
669          }>
670              <div data-src="${coverImage}" hk-img class="hk-inline hk-height-small hk-width-medium hfse-property-infowindow-header">
671                  <div class="hk-overlay hk-position-cover hfse-property-infowindow-header-overlay"></div>
672              </div>
673              <div class="hk-card-body hfse-property-infowindow-body">
674                  <div class="hk-h5 hk-margin-small-bottom hfse-property-title">${
675                    title.rendered
676                  }</div>
677										${
678                      "0" === opts.hide_subtitle && subtitle
679                        ? `<div class="hk-text-default hk-margin-remove-top hk-margin-small-bottom hfse-property-subtitle">${subtitle}</div>`
680                        : ""
681                    }
682                    ${priceHtml}
683                    <div class="hk-flex hk-grid-small hk-last-column">
684                        ${leftTerms}
685                        ${rightTerms}
686                    </div>
687              </div>
688          </a>`;
689      },
690
691      mapPanTo: (latlng, offsetx, offsety) => {
692        if (!map.getProjection()) {
693          console.log("no map projection");
694          return false;
695        }
696
697        var point1 = map
698          .getProjection()
699          .fromLatLngToPoint(
700            latlng instanceof google.maps.LatLng ? latlng : map.getCenter(),
701          );
702        var point2 = new google.maps.Point(
703          (typeof offsetx == "number" ? offsetx : 0) /
704            Math.pow(2, map.getZoom()) || 0,
705          (typeof offsety == "number" ? offsety : 0) /
706            Math.pow(2, map.getZoom()) || 0,
707        );
708
709        map.panTo(
710          map
711            .getProjection()
712            .fromPointToLatLng(
713              new google.maps.Point(point1.x - point2.x, point1.y + point2.y),
714            ),
715        );
716      },
717
718      mapFitBounds: () => {
719        // Guard clause: ensure map exists and there are properties
720        if (!map || !cachedProperties.length) {
721          return;
722        }
723
724        try {
725          let bounds = L.latLngBounds();
726          let hasValidBounds = false;
727
728          // Collect valid coordinates
729          cachedProperties.forEach((property) => {
730            if (property.acf && property.acf.geolocation) {
731              const { lat, lng } = property.acf.geolocation;
732              if (lat && lng && !isNaN(lat) && !isNaN(lng)) {
733                bounds.extend([lat, lng]);
734                hasValidBounds = true;
735              }
736            }
737          });
738
739          // Only proceed if we have valid bounds
740          if (hasValidBounds) {
741            // Calculate margins (10% of the total size)
742            const sw = bounds.getSouthWest();
743            const ne = bounds.getNorthEast();
744            const latMargin = (ne.lat - sw.lat) * 0.1;
745            const lngMargin = (ne.lng - sw.lng) * 0.1;
746
747            // Create new bounds with margins
748            const paddedBounds = L.latLngBounds(
749              [sw.lat - latMargin, sw.lng - lngMargin],
750              [ne.lat + latMargin, ne.lng + lngMargin],
751            );
752
753            // Use a non-animated version for safety
754            map.fitBounds(paddedBounds, {
755              animate: false, // Disable animation
756              padding: [50, 50], // Add some padding in pixels
757              maxZoom: parseInt(opts.map_zoom_max, 10),
758              duration: 0, // Ensure no duration
759            });
760          } else {
761            // If no valid bounds, set a default view
762            map.setView([23.8103, 90.4125], parseInt(opts.map_zoom_min, 10), {
763              animate: false,
764            });
765          }
766        } catch (error) {
767          console.warn("Error in mapFitBounds:", error);
768          // Fallback to default view if something goes wrong
769          map.setView([23.8103, 90.4125], parseInt(opts.map_zoom_min, 10), {
770            animate: false,
771          });
772        }
773      },
774
775      getMapBoundBoxString: () => {
776        if (!map) return null;
777
778        const bounds = map.getBounds();
779        if (!bounds || !bounds.isValid()) {
780          return null;
781        }
782
783        return bounds.toBBoxString();
784      },
785
786      isDateQuery: () => {
787        const $checking = $(selector.dateModal).find('input[name="checkin"]');
788        const $checkout = $(selector.dateModal).find('input[name="checkout"]');
789
790        if (
791          $checking.length &&
792          $checkout.length &&
793          "" !== $checking.val() &&
794          "" !== $checkout.val()
795        ) {
796          return true;
797        }
798
799        return false;
800      },
801
802      isFullscreen: (element) => {
803        return (
804          (document.fullscreenElement ||
805            document.webkitFullscreenElement ||
806            document.mozFullScreenElement ||
807            document.msFullscreenElement) == element
808        );
809      },
810
811      requestFullscreen: (element) => {
812        if (element.requestFullscreen) {
813          element.requestFullscreen();
814        } else if (element.webkitRequestFullScreen) {
815          element.webkitRequestFullScreen();
816        } else if (element.mozRequestFullScreen) {
817          element.mozRequestFullScreen();
818        } else if (element.msRequestFullScreen) {
819          element.msRequestFullScreen();
820        }
821      },
822
823      exitFullscreen: () => {
824        if (document.exitFullscreen) {
825          document.exitFullscreen();
826        } else if (document.webkitExitFullscreen) {
827          document.webkitExitFullscreen();
828        } else if (document.mozCancelFullScreen) {
829          document.mozCancelFullScreen();
830        } else if (document.msExitFullscreen) {
831          document.msExitFullscreen();
832        }
833      },
834
835      init: () => {
836        map = L.map("map", {
837          minZoom: parseInt(opts.map_zoom_min, 10),
838          maxZoom: parseInt(opts.map_zoom_max, 10),
839          zoom: parseInt(opts.map_zoom_min, 10),
840          zoomControl: false, // Disables default zoom control
841          attributionControl: false, // Disables default attribution
842        });
843
844        // Add OpenStreetMap tile layer
845        L.tileLayer("https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", {
846          attribution: "&copy; OpenStreetMap contributors",
847        }).addTo(map);
848
849        app.positionMap();
850        $(window).on("scroll", debounce(app.positionMap, 50));
851        $(window).on("resize", app.positionMap);
852
853        map.addControl(new L.Control.CustomControl({ position: "topleft" }));
854
855        // listent to map zoom changes.
856        map.on(
857          "zoomend",
858          debounce(() => {
859            if (map && options.onMapPositionChange) {
860              options.onMapPositionChange("zoomend");
861            }
862          }),
863        );
864
865        // listen to map move events.
866        map.on(
867          "moveend",
868          debounce(() => {
869            if (map && options.onMapPositionChange) {
870              options.onMapPositionChange("moveend");
871            }
872          }),
873        );
874      },
875    };
876
877    return app;
878  };
879})(jQuery, homelocal_explorer_map_settings);

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.