PageSourceSearch

https://staybluemaple.com/wp-content/plugins/homerunner/assets/js/range-calendar.js?ver=2.6.6

js staybluemaple.com collected 2026-10-01 16:37:22 UTC 21,159 bytes, 743 lines download raw bytes

1(function ($, settings) {
2  $.fn.homelocalRangeCalendar = function (options) {
3    const app = this;
4    const $container = $(app);
5
6    options = $.extend({}, options);
7    options.min_stay = parseInt(options.min_stay, 10) || 1;
8    options.max_stay = parseInt(options.max_stay, 10) || 365;
9    options.setDates = options.setDates || function (checkin, checkout) {};
10    options.max_num_days = parseInt(options.max_num_days, 10);
11    settings.start_of_week = parseInt(settings.start_of_week, 10) || 0;
12
13    let calendarSlider = null;
14    let moveCalendarToDate = null;
15
16    const calendarUtils = new HomeLocalCalendarUtils(
17      homerunner_calendar_utils_settings,
18      {
19        min_stay: options.min_stay,
20        max_stay: options.max_stay,
21        max_num_days: options.max_num_days,
22        timezone: settings.timezone,
23      },
24    );
25
26    const selector = {
27      main: ".homelocal-range-calendar",
28    };
29
30    let cellHeight = null;
31
32    const debounce = (func, timeout = 300) => {
33      let timer;
34      return (...args) => {
35        clearTimeout(timer);
36        timer = setTimeout(() => {
37          func.apply(this, args);
38        }, timeout);
39      };
40    };
41
42    app.isBooked = (day) => {
43      const opts = calendarUtils.dateOptions(day);
44      return opts && opts.booked;
45    };
46
47    app.isUnavailable = (day) => {
48      const date = calendarUtils.getDateZero(day);
49      const today = calendarUtils.getDateZero();
50
51      // check if date is in the past, today or too far in the future.
52      if (date < today) {
53        return true;
54      }
55
56      if (options.max_num_days !== -1) {
57        const maxDate = luxon.DateTime.now().setZone(settings.timezone).plus({
58          days: options.max_num_days,
59        });
60
61        if (date > maxDate) {
62          return true;
63        }
64      }
65
66      return false;
67    };
68
69    app.canCheckin = (day) => {
70      const givenDay = calendarUtils.getDateZero(day);
71
72      const lastCheckin = calendarUtils.maxDate.plus({
73        days: -options.min_stay,
74      });
75
76      return givenDay >= calendarUtils.minDate && givenDay <= lastCheckin;
77    };
78
79    app.canCheckout = (day) => {
80      const givenDay = calendarUtils.getDateZero(day);
81
82      const firstCheckout = givenDay.plus({
83        days: options.min_stay,
84      });
85
86      return givenDay >= firstCheckout && givenDay <= calendarUtils.maxDate;
87    };
88
89    // check if checkout date is valid.
90    // if checkin date is provided, check if min stay is met
91    app.canBook = (checkin, checkout) => {
92      const today = calendarUtils.getDateZero();
93      const checkinDate = calendarUtils.getDateZero(checkin);
94      const checkoutDate = calendarUtils.getDateZero(checkout);
95
96      if (checkoutDate < today || checkoutDate < checkinDate) {
97        return false;
98      }
99
100      // when checkin date is provided, check if min stay is met
101      const nights = calendarUtils.calcNights(checkin, checkout);
102
103      if (nights < options.min_stay || nights > options.max_stay) {
104        return false;
105      }
106
107      return true;
108    };
109
110    app.updateCellSize = () => {
111      if ($container.find(".calendar:visible .calendar-table").length > 0) {
112        const width = $container
113          .find(".calendar:visible .calendar-table")
114          .width();
115
116        cellHeight = width / 7;
117        $container.find(".calendar .calendar-table").each(function (i, table) {
118          $(table)
119            .find(".day")
120            .css({
121              height: cellHeight + "px",
122              lineHeight: cellHeight + "px",
123            });
124
125          $(table)
126            .find(".day .date")
127            .css({
128              height: cellHeight - 4 + "px",
129              width: cellHeight - 4 + "px",
130              lineHeight: cellHeight - 4 + "px",
131            });
132        });
133      }
134    };
135
136    app.isBelowMax = ({ year, month }) => {
137      return (
138        year * 12 + month <
139        calendarUtils.maxDate.year * 12 + calendarUtils.maxDate.month + 1
140      );
141    };
142
143    app.isAboveMin = ({ year, month }) => {
144      return (
145        year * 12 + month >
146        calendarUtils.minDate.year * 12 + calendarUtils.minDate.month - 1
147      );
148    };
149
150    app.addCalendar = ({ year, month }, show = false) => {
151      if (
152        $container.find(
153          '.calendar[data-y="' + year + '"][data-m="' + month + '"]',
154        ).length < 1
155      ) {
156        const $el = $("<div/>", {
157          class: "calendar",
158          "data-y": year,
159          "data-m": month,
160        }).html(app.getCalendarHtml({ year, month }));
161
162        $el.appendTo($container.find(".calendars"));
163
164        setTimeout(app.updateCellSize, 1);
165
166        if (!show) {
167          $container
168            .find('.calendar[data-y="' + year + '"][data-m="' + month + '"]')
169            .hide();
170        }
171      }
172    };
173
174    app.getCalendarHtml = ({ year, month }) => {
175      const zeroise = (input) => {
176        return input < 10 ? "0" + input : input;
177      };
178
179      let html =
180        '<div class="calendar-heading">' +
181        settings.months_label[month - 1] +
182        " " +
183        year +
184        "</div>";
185
186      // Calendar content. Disable datepicker day name / number translation.
187      html += '<div class="calendar-table">';
188
189      // Week days.
190      html += '<div class="week-days">';
191      for (var i = 0; i <= 6; i++) {
192        const weekIndex = (i + settings.start_of_week) % 7;
193
194        html += '<div class="day-name">';
195        html += settings.weekdays_label[weekIndex];
196        html += "</div>";
197      }
198      html += "</div>";
199
200      var w = 0; // week day
201      var n = 1; // next days date
202      var c = 1; // current date
203
204      let dayOfWeek = calendarUtils.getFirstWeekday(year, month);
205
206      if (settings.start_of_week) {
207        dayOfWeek = dayOfWeek - settings.start_of_week;
208        if (dayOfWeek < 0) {
209          dayOfWeek = 7 + dayOfWeek;
210        }
211      }
212
213      dayOfWeek = dayOfWeek % 7;
214
215      const daysInMonth = calendarUtils.getMonthDayCount(year, month);
216
217      // dates loop
218      for (var i = 0; i < 6 * 7; i++) {
219        if (w == 0) {
220          // first week's day
221          html += '<div class="week">';
222        }
223
224        if (i < dayOfWeek) {
225          // previous month's day compared to active month.
226          html += '<div class="day day-placeholder"></div>';
227        } else if (c > daysInMonth) {
228          // next month's day compared to active month.
229          html += '<div class="day day-placeholder"></div>';
230          n++;
231        } else {
232          const date = `${year}-${zeroise(month)}-${zeroise(c)}`;
233
234          let classnames = ["day", "day-of-month"];
235
236          if (calendarUtils.isToday(date)) {
237            classnames.push("today");
238          }
239
240          if (app.isUnavailable(date)) {
241            classnames.push("unavailable");
242          }
243
244          // unique class names
245          classnames = classnames.filter(
246            (value, index, self) => self.indexOf(value) === index,
247          );
248
249          let style = "";
250          let dateStyle = "";
251          if (cellHeight) {
252            style = ` height: ${cellHeight}px; line-height: ${cellHeight}px;`;
253            dateStyle = ` style="height: ${cellHeight - 4}px; width: ${
254              cellHeight - 4
255            }px; line-height: ${cellHeight - 4}px;"`;
256          }
257
258          // current month's day
259          html += `<div class="${classnames.join(
260            " ",
261          )}" data-date="${date}" style="${style}">
262						<span class="date" style="${dateStyle}">${c}</span>
263					</div>`;
264          c++;
265        }
266
267        if (w == 7 - 1) {
268          // last week's day
269          html += "</div>";
270          w = 0;
271        } else {
272          w++;
273        }
274      }
275
276      html += "</div>";
277      return html;
278    };
279
280    app.isMobileContainer = () => {
281      // return $container.parent().width() < 768;
282      return $(window).width() < 768;
283    };
284
285    app.pickerInit = () => {
286      let startDay = false;
287      let endDay = false;
288      const dayCellSelector = selector.main + " .week .day-of-month";
289      const $dayCells = $container.find(dayCellSelector);
290      let isLoading = false;
291
292      $container.addClass("calendar-picker");
293
294      if ($("#homelocal-calendar-tip").length < 1) {
295        $("body")
296          .append(`<div id="homelocal-calendar-tip" class="homelocal-tip">
297				<div class="tip-content"></div>
298				<div class="tip-arrow"></div>
299			</div>`);
300      }
301
302      let tipTimeout = null;
303
304      const showTip = ($day, tip) => {
305        $("#homelocal-calendar-tip .tip-content").html(tip);
306
307        // calculate placement based on element position in window
308        const placement =
309          $day.offset().top + $day.height() / 2 - $(window).scrollTop() <
310          $(window).height() / 3
311            ? "bottom"
312            : "top";
313
314        $("#homelocal-calendar-tip").attr("data-placement", placement);
315
316        if (placement === "top" || placement === "bottom") {
317          $("#homelocal-calendar-tip").css({
318            left:
319              $day.offset().left +
320              $day.width() / 2 -
321              $("#homelocal-calendar-tip").width() / 2,
322          });
323
324          if (placement === "top") {
325            $("#homelocal-calendar-tip").css({
326              top:
327                $day.offset().top - $("#homelocal-calendar-tip").height() - 10,
328            });
329          } else if (placement === "bottom") {
330            $("#homelocal-calendar-tip").css({
331              top: $day.offset().top + $day.height() + 10,
332            });
333          }
334        }
335
336        $("#homelocal-calendar-tip").show();
337
338        clearTimeout(tipTimeout);
339        tipTimeout = setTimeout(() => {
340          removeTip();
341        }, 5000);
342      };
343
344      const removeTip = () => {
345        $("#homelocal-calendar-tip").hide();
346        clearTimeout(tipTimeout);
347      };
348
349      const maybeHideTip = (e) => {
350        removeTip();
351      };
352
353      $(document).on("click", maybeHideTip);
354
355      const isSelectStart = () =>
356        (startDay && endDay) || (!startDay && !endDay);
357
358      const getSelectableRange = (day) => {
359        return {
360          minDate: day,
361          maxDate: calendarUtils
362            .getDateZero(day)
363            .plus({ days: options.max_stay })
364            .toFormat("yyyy-MM-dd"),
365        };
366      };
367
368      const getAvailableCheckoutDays = (day) => {
369        let firstDate = calendarUtils
370          .getDateZero(day)
371          .plus({ days: options.min_stay });
372
373        const lastDate = calendarUtils
374          .getDateZero(day)
375          .plus({ days: options.max_stay });
376
377        const days = [];
378        while (firstDate < lastDate) {
379          firstDate = firstDate.plus({ days: 1 });
380          days.push(firstDate.toFormat("yyyy-MM-dd"));
381        }
382
383        return days;
384      };
385
386      const makeSelection = (startedDay, endedDay) => {
387        $dayCells.each(function () {
388          const dataDate = $(this).attr("data-date");
389          if (dataDate === startedDay) {
390            $(this).addClass("selected checkin").removeClass("disabled hover");
391          } else if (dataDate === endedDay) {
392            $(this).addClass("selected checkout").removeClass("disabled hover");
393          } else if (dayInRange(dataDate, startedDay, endedDay)) {
394            $(this).addClass("selected").removeClass("disabled hover");
395          } else {
396            $(this).removeClass("selected checkin checkout hover");
397          }
398        });
399
400        $container
401          .addClass("checkout-selected")
402          .removeClass("checkin-selected");
403      };
404
405      const setCheckinDay = (e, day, reset = false) => {
406        removeTip();
407        if (day !== startDay) {
408          startDay = day;
409          if (reset) {
410            $(document).trigger("homelocal_booking_widget_reset");
411          }
412        }
413
414        $container
415          .addClass("checkin-selected")
416          .removeClass("checkout-selected");
417
418        endDay = false;
419        $dayCells.removeClass("hover selected checkin checkout disabled");
420        $dayCells
421          .filter(`[data-date="${day}"]`)
422          .addClass("selected checkin checkout")
423          .removeClass("disabled");
424
425        const selectableDays = getAvailableCheckoutDays(startDay);
426        const selectableRange = getSelectableRange(startDay);
427
428        $dayCells.each(function () {
429          const dataDate = $(this).attr("data-date");
430          if (
431            dataDate !== startDay &&
432            !$(this).hasClass("unavailable booked")
433          ) {
434            if (
435              !selectableDays.includes(dataDate) &&
436              !dayInRange(
437                dataDate,
438                selectableRange.minDate,
439                selectableRange.maxDate,
440              )
441            ) {
442              $(this).addClass("disabled");
443            }
444          }
445        });
446        $(document).on("mouseover", dayCellSelector, onDayHover);
447      };
448
449      const setCheckoutDay = (e, day) => {
450        removeTip();
451        endDay = day;
452
453        makeSelection(startDay, endDay);
454
455        $dayCells.removeClass("disabled");
456
457        $(document).off("mouseover", dayCellSelector, onDayHover);
458      };
459
460      const dayClicked = (e) => {
461        const $day = $(e.currentTarget);
462        const dataDate = $day.attr("data-date");
463
464        if (isLoading) {
465          showTip($day, settings.selection.range_loading);
466        } else if (app.isUnavailable(dataDate)) {
467          showTip($day, settings.selection.unavailable);
468        } else if (isSelectStart()) {
469          if (app.isBooked(dataDate) && !app.canCheckin(dataDate)) {
470            showTip($day, settings.selection.booked);
471          } else if (app.canCheckin(dataDate)) {
472            $(document).trigger(
473              "homelocal_range_calendar_set_checkin_" + options.id,
474              [dataDate, true],
475            );
476          } else {
477            showTip(
478              $day,
479              settings.selection.cant_checkin.replace(
480                "{night}",
481                calendarUtils.getMinstay(dataDate),
482              ),
483            );
484          }
485        } else {
486          const minmaxDate = getSelectableRange(startDay);
487
488          if (app.isBooked(dataDate) && !app.canCheckout(dataDate)) {
489            showTip($day, settings.selection.booked);
490          } else if (app.canBook(startDay, dataDate)) {
491            $(document).trigger(
492              "homelocal_range_calendar_set_checkout_" + options.id,
493              [dataDate],
494            );
495            $(document).trigger(
496              "homelocal_range_calendar_set_dates_" + options.id,
497              [startDay, dataDate],
498            );
499
500            options.setDates(startDay, dataDate);
501          } else if (
502            dayInRange(dataDate, minmaxDate.minDate, minmaxDate.maxDate)
503          ) {
504            showTip(
505              $day,
506              settings.selection.min_night.replace(
507                "{night}",
508                calendarUtils.getMinstay(startDay),
509              ),
510            );
511          } else {
512            showTip($day, settings.selection.unavailable);
513          }
514        }
515
516        return false;
517      };
518
519      const onDayHover = (e) => {
520        const $day = $(e.currentTarget);
521        const hoverDate = $day.attr("data-date");
522        const minmaxDate = getSelectableRange(startDay);
523
524        if (dayInRange(hoverDate, minmaxDate.minDate, minmaxDate.maxDate)) {
525          $dayCells.each(function () {
526            const dataDate = $(this).attr("data-date");
527            if (
528              dayInRange(dataDate, startDay, hoverDate) &&
529              dayInRange(dataDate, minmaxDate.minDate, minmaxDate.maxDate) &&
530              dataDate !== startDay
531            ) {
532              $(this).addClass("hover");
533              if (dataDate === hoverDate) {
534                $(this).addClass("checkout");
535              } else {
536                $(this).removeClass("checkout");
537              }
538            } else {
539              $(this).removeClass("hover checkout");
540            }
541          });
542        }
543      };
544
545      const dayInRange = (day, minDay, maxDay) => {
546        return (
547          new Date(day) >= new Date(minDay) && new Date(day) <= new Date(maxDay)
548        );
549      };
550
551      const resetSelection = () => {
552        $container.removeClass("checkin-selected checkout-selected");
553        $dayCells.removeClass(
554          "hover selectable selected disabled checkin checkout",
555        );
556        startDay = false;
557        endDay = false;
558
559        $(document).off("mouseover", dayCellSelector, onDayHover);
560      };
561
562      $container.on("click", dayCellSelector, dayClicked);
563
564      if (options.start_date && options.end_date) {
565        startDay = options.start_date;
566        endDay = options.end_date;
567        makeSelection(startDay, endDay);
568        moveCalendarToDate = startDay;
569        $dayCells.removeClass("disabled");
570      }
571
572      // New id based event listener.
573      $(document).on(
574        "homelocal_range_calendar_reset_selection_" + options.id,
575        () => {
576          resetSelection();
577          moveCalendarToDate = calendarUtils
578            .getDateZero()
579            .toFormat("yyyy-MM-dd");
580        },
581      );
582
583      $(document).on(
584        "homelocal_range_calendar_set_checkin_" + options.id,
585        setCheckinDay,
586      );
587
588      $(document).on(
589        "homelocal_range_calendar_set_checkout_" + options.id,
590        (e, day) => {
591          setCheckoutDay(e, day);
592          moveCalendarToDate = startDay;
593        },
594      );
595    };
596
597    app.createLayout = () => {
598      $container.append(
599        `<div class="hk-light notranslate homelocal-range-calendar">
600					<div class="calendars"></div>
601					<button class="next-btn" type="button">${settings.next_icon_svg}</button>
602					<button class="prev-btn" type="button">${settings.prev_icon_svg}</button>
603				</div>`,
604      );
605    };
606
607    app.renderCalendars = () => {
608      const startMonth = calendarUtils.minDate.startOf("month");
609      const endMonth = calendarUtils.maxDate.startOf("month");
610
611      const monthDiffCeiled = Math.ceil(
612        endMonth.diff(startMonth, "months").months,
613      );
614
615      // Ensure at least 2 months are rendered.
616      const monthsToRender = Math.max(monthDiffCeiled + 1, 2);
617
618      for (let i = 0; i < monthsToRender; i++) {
619        const currentMonth = startMonth.plus({ months: i });
620        app.addCalendar(
621          {
622            year: currentMonth.year,
623            month: currentMonth.month,
624          },
625          true,
626        );
627      }
628    };
629
630    app.adjustLayout = () => {
631      if ($container.is(":hidden")) {
632        return false;
633      }
634
635      app.updateCellSize();
636    };
637
638    app.calendarSliderInit = () => {
639      if (options.modal) {
640        const viewportWidth = $(window).width();
641        if (viewportWidth < 768) {
642          if (calendarSlider) {
643            calendarSlider.destroy();
644            calendarSlider = null;
645          }
646
647          $container.find(".prev-btn,.next-btn").addClass("hk-hidden");
648          return;
649        }
650      }
651
652      if (calendarSlider) {
653        return;
654      }
655
656      $container.find(".prev-btn,.next-btn").removeClass("hk-hidden");
657
658      calendarSlider = tns({
659        container: `${options.selector} .calendars`,
660        items: 1,
661        gutter: 0,
662        responsive: {
663          768: {
664            items: 2,
665            gutter: 30,
666          },
667        },
668        slideBy: 1,
669        speed: 600,
670        autoplay: false,
671        mouseDrag: true,
672        // preventActionWhenRunning: true,
673        nav: false,
674        loop: false,
675        nextButton: `${options.selector} .next-btn`,
676        prevButton: `${options.selector} .prev-btn`,
677      });
678    };
679
680    app.init = () => {
681      app.createLayout();
682      app.renderCalendars();
683      app.calendarSliderInit();
684      app.adjustLayout();
685
686      app.pickerInit();
687
688      $(window).on("resize", debounce(app.adjustLayout, 300));
689
690      if (options.modal) {
691        $(document).on("beforeshow", options.modal, app.moveCalendarSlider);
692        $(document).on("show", options.modal, app.adjustLayout);
693        $(document).on("shown", options.modal, app.moveCalendarScroll);
694      }
695    };
696
697    app.moveCalendarScroll = () => {
698      if (!moveCalendarToDate || !app.isMobileContainer()) {
699        return;
700      }
701
702      const date = calendarUtils.getDateZero(moveCalendarToDate);
703
704      const $el = $container.find(
705        `.calendar[data-y="${date.year}"][data-m="${date.month}"]`,
706      );
707
708      if ($el.length > 0) {
709        const scrollTop = $el.offset().top - 50;
710
711        if (scrollTop > 0) {
712          $(options.modal).find(".hk-modal-body").scrollTop(scrollTop);
713        }
714      }
715
716      // reset moveCalendarToDate
717      moveCalendarToDate = null;
718    };
719
720    app.moveCalendarSlider = () => {
721      if (!moveCalendarToDate || app.isMobileContainer()) {
722        return;
723      }
724
725      const date = calendarUtils.getDateZero(moveCalendarToDate);
726
727      const $el = $container.find(
728        `.calendar[data-y="${date.year}"][data-m="${date.month}"]`,
729      );
730
731      if ($el.length > 0 && !$el.hasClass("tns-slide-active")) {
732        const index = $el.index();
733        calendarSlider.goTo(index);
734      }
735
736      moveCalendarToDate = null;
737    };
738
739    app.init();
740
741    return app;
742  };
743})(jQuery, homelocal_range_calendar_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.