Repository: flatpickr/flatpickr
Pull request #1761 (2019-04-23 09:12:15)

Fix month dropdown
Hi @chmln,

I made a mistake on Thursday evening and I didn't push the changes for the dropdown month selector and an old version got to master (it's just the plugin moved to master). I've ran again through everything, deleted the plugin and here is the correct version for the dropdown.

Sorry for the mixup!


Patch:
diff --git a/src/index.ts b/src/index.ts
index 81bc4451..8127bbf0 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -512,6 +512,7 @@ function FlatpickrInstance(
 
     if (triggerChange && self.currentYear !== oldYear) {
       triggerEvent("onYearChange");
+      buildMonthSwitch();
     }
 
     if (
@@ -914,11 +915,76 @@ function FlatpickrInstance(
     }
   }
 
+  function buildMonthSwitch() {
+    if (self.config.showMonths > 1) return;
+
+    const shouldBuildMonth = function(month: number): boolean {
+      if (
+        self.config.minDate !== undefined &&
+        self.currentYear === self.config.minDate.getFullYear() &&
+        month < self.config.minDate.getMonth()
+      ) {
+        return false;
+      }
+
+      return !(
+        self.config.maxDate !== undefined &&
+        self.currentYear === self.config.maxDate.getFullYear() &&
+        month > self.config.maxDate.getMonth()
+      );
+    };
+
+    self.monthsDropdownContainer.tabIndex = -1;
+
+    self.monthsDropdownContainer.innerHTML = "";
+
+    for (let i = 0; i < 12; i++) {
+      if (!shouldBuildMonth(i)) continue;
+
+      const month = createElement<HTMLOptionElement>(
+        "option",
+        "flatpickr-monthDropdown-month"
+      );
+
+      month.value = new Date(self.currentYear, i).getMonth().toString();
+      month.textContent = monthToStr(i, false, self.l10n);
+      month.tabIndex = -1;
+
+      if (self.currentMonth === i) {
+        month.selected = true;
+      }
+
+      self.monthsDropdownContainer.appendChild(month);
+    }
+  }
+
   function buildMonth() {
     const container = createElement("div", "flatpickr-month");
     const monthNavFragment = window.document.createDocumentFragment();
 
-    const monthElement = createElement<HTMLSpanElement>("span", "cur-month");
+    let monthElement;
+
+    if (self.config.showMonths > 1) {
+      monthElement = createElement<HTMLSpanElement>("span", "cur-month");
+    } else {
+      self.monthsDropdownContainer = createElement<HTMLSelectElement>(
+        "select",
+        "flatpickr-monthDropdown-months"
+      );
+
+      bind(self.monthsDropdownContainer, "change", (e: Event) => {
+        const target = e.target as HTMLSelectElement;
+        const selectedMonth = parseInt(target.value, 10);
+
+        self.changeMonth(selectedMonth - self.currentMonth);
+
+        triggerEvent("onMonthChange");
+      });
+
+      buildMonthSwitch();
+
+      monthElement = self.monthsDropdownContainer;
+    }
 
     const yearInput = createNumberInput("cur-year", { tabindex: "-1" });
 
@@ -1201,6 +1267,7 @@ function FlatpickrInstance(
       self.currentMonth = (self.currentMonth + 12) % 12;
 
       triggerEvent("onYearChange");
+      buildMonthSwitch();
     }
 
     buildDays();
@@ -1320,6 +1387,7 @@ function FlatpickrInstance(
       "weekdayContainer",
       "prevMonthNav",
       "nextMonthNav",
+      "monthsDropdownContainer",
       "currentMonthElement",
       "currentYearElement",
       "navigationCurrentMonth",
@@ -1412,6 +1480,7 @@ function FlatpickrInstance(
     if (isNewYear) {
       self.redraw();
       triggerEvent("onYearChange");
+      buildMonthSwitch();
     }
   }
 
@@ -2179,7 +2248,10 @@ function FlatpickrInstance(
       self.currentYear = selectedDate.getFullYear();
       self.currentMonth = selectedDate.getMonth();
 
-      if (isNewYear) triggerEvent("onYearChange");
+      if (isNewYear) {
+        triggerEvent("onYearChange");
+        buildMonthSwitch();
+      }
 
       triggerEvent("onMonthChange");
     }
@@ -2577,9 +2649,17 @@ function FlatpickrInstance(
       const d = new Date(self.currentYear, self.currentMonth, 1);
       d.setMonth(self.currentMonth + i);
 
-      self.monthElements[i].textContent =
-        monthToStr(d.getMonth(), self.config.shorthandCurrentMonth, self.l10n) +
-        " ";
+      if (self.config.showMonths > 1) {
+        self.monthElements[i].textContent =
+          monthToStr(
+            d.getMonth(),
+            self.config.shorthandCurrentMonth,
+            self.l10n
+          ) + " ";
+      } else {
+        self.monthsDropdownContainer.value = d.getMonth().toString();
+      }
+
       yearElement.value = d.getFullYear().toString();
     });
 
diff --git a/src/plugins/monthDropdown/index.ts b/src/plugins/monthDropdown/index.ts
deleted file mode 100644
index f8cba1ea..00000000
--- a/src/plugins/monthDropdown/index.ts
+++ /dev/null
@@ -1,154 +0,0 @@
-import { Plugin } from "../../types/options";
-import { Instance } from "../../types/instance";
-import { monthToStr } from "../../utils/formatting";
-
-export interface Config {
-  shorthand: boolean;
-}
-
-const defaultConfig: Config = {
-  shorthand: false,
-};
-
-function monthDropdownPlugin(pluginConfig?: Partial<Config>): Plugin {
-  const config = { ...defaultConfig, ...pluginConfig };
-  const self = { monthsDropdownContainer: null as null | HTMLSelectElement };
-
-  return (fp: Instance) => {
-    function clearUnnecessaryDOMElements() {
-      fp.monthElements.forEach(element => {
-        if (!element.classList.contains("cur-month")) {
-          return;
-        }
-
-        element.style.display = "none";
-      });
-    }
-
-    function buildSelector() {
-      const shouldBuildMonth = function(month: number): boolean {
-        if (
-          fp.config.minDate !== undefined &&
-          fp.currentYear === fp.config.minDate.getFullYear() &&
-          month < fp.config.minDate.getMonth()
-        ) {
-          return false;
-        }
-
-        return !(
-          fp.config.maxDate !== undefined &&
-          fp.currentYear === fp.config.maxDate.getFullYear() &&
-          month > fp.config.maxDate.getMonth()
-        );
-      };
-
-      const currentMonthContainer = fp.monthNav.querySelector(
-        ".flatpickr-current-month"
-      );
-      const currentYearElement = fp.yearElements.find(element => {
-        return element.classList.contains("cur-year");
-      });
-
-      if (
-        !currentMonthContainer ||
-        !currentYearElement ||
-        !currentYearElement.parentNode
-      )
-        return;
-
-      self.monthsDropdownContainer = fp._createElement<HTMLSelectElement>(
-        "select",
-        "flatpickr-monthDropdown-months"
-      );
-
-      self.monthsDropdownContainer.style.backgroundColor = getComputedStyle(
-        fp.calendarContainer
-      ).backgroundColor;
-
-      self.monthsDropdownContainer.tabIndex = -1;
-
-      for (let i = 0; i < 12; i++) {
-        if (!shouldBuildMonth(i)) continue;
-
-        const month = fp._createElement<HTMLOptionElement>(
-          "option",
-          "flatpickr-monthDropdown-month"
-        );
-
-        month.value = new Date(fp.currentYear, i).getMonth().toString();
-        month.textContent = monthToStr(i, config.shorthand, fp.l10n);
-        month.tabIndex = -1;
-
-        if (fp.currentMonth === i) {
-          month.selected = true;
-        }
-
-        self.monthsDropdownContainer.appendChild(month);
-      }
-
-      self.monthsDropdownContainer.addEventListener("change", changeMonth);
-
-      currentMonthContainer.insertBefore(
-        self.monthsDropdownContainer,
-        currentYearElement.parentNode
-      );
-    }
-
-    function markMonthAsSelected() {
-      const currentlySelected = self.monthsDropdownContainer!.querySelector(
-        ".flatpickr-monthDropdown-month:checked"
-      ) as HTMLOptionElement;
-
-      const monthToSelect = self.monthsDropdownContainer!.querySelector(
-        `.flatpickr-monthDropdown-month[value="${fp.currentMonth}"]`
-      ) as HTMLOptionElement;
-
-      if (currentlySelected) {
-        currentlySelected.selected = false;
-      }
-
-      if (monthToSelect) {
-        monthToSelect.selected = true;
-      }
-    }
-
-    function rebuildSelector() {
-      if (self.monthsDropdownContainer) {
-        self.monthsDropdownContainer.parentNode!.removeChild(
-          self.monthsDropdownContainer
-        );
-      }
-
-      buildSelector();
-    }
-
-    function addListeners() {}
-
-    function changeMonth(e: Event) {
-      const target = e.target as HTMLSelectElement;
-      const selectedMonth = parseInt(target.value, 10);
-
-      fp.changeMonth(selectedMonth - fp.currentMonth);
-    }
-
-    function destroyPluginInstance() {}
-
-    return {
-      onParseConfig() {},
-      onValueUpdate: markMonthAsSelected,
-      onReady: [
-        clearUnnecessaryDOMElements,
-        buildSelector,
-        addListeners,
-        () => {
-          fp.loadedPlugins.push("monthSelect");
-        },
-      ],
-      onMonthChange: markMonthAsSelected,
-      onYearChange: rebuildSelector,
-      onDestroy: destroyPluginInstance,
-    };
-  };
-}
-
-export default monthDropdownPlugin;
diff --git a/src/plugins/monthDropdown/style.css b/src/plugins/monthDropdown/style.css
deleted file mode 100644
index 8d6d2478..00000000
--- a/src/plugins/monthDropdown/style.css
+++ /dev/null
@@ -1,32 +0,0 @@
-.flatpickr-monthDropdown-months {
-  background: transparent;
-  -webkit-box-sizing: border-box;
-  box-sizing: border-box;
-  color: inherit;
-  cursor: pointer;
-  padding: 0 0 0 0.5ch;
-  margin: 0;
-  display: inline-block;
-  font-size: inherit;
-  font-family: inherit;
-  font-weight: 300;
-  line-height: inherit;
-  height: auto;
-  border: 0;
-  border-radius: 0;
-  vertical-align: initial;
-  -webkit-appearance: menulist;
-  -moz-appearance: menulist;
-  appearance: menulist;
-  width: auto;
-}
-
-.flatpickr-monthDropdown-months:focus,
-.flatpickr-monthDropdown-months:active {
-  border: 0;
-}
-
-.flatpickr-monthDropdown-month {
-  background-color: inherit;
-  /*color: #393939;*/
-}
diff --git a/src/style/flatpickr.styl b/src/style/flatpickr.styl
index c47ce04d..714c0e92 100644
--- a/src/style/flatpickr.styl
+++ b/src/style/flatpickr.styl
@@ -364,6 +364,40 @@
       background transparent
       pointer-events none
 
+  .flatpickr-monthDropdown-months
+    appearance: menulist
+    background: $monthBackground
+    border: none
+    border-radius: 0
+    box-sizing: border-box
+    color: inherit
+    cursor: pointer
+    font-size: inherit
+    font-family: inherit
+    font-weight: 300
+    height: $monthNavHeight - 6px
+    line-height: inherit
+    margin: -1px 0 0 0
+    outline: none
+    padding: 0 0 0 0.5ch
+    position: relative
+    vertical-align: initial
+    -webkit-box-sizing: border-box
+    -webkit-appearance: menulist
+    -moz-appearance: menulist
+    width: auto
+
+    &:focus, &:active
+      outline: none
+
+    &:hover
+      background: alpha($invertedBg, 0.05)
+
+    .flatpickr-monthDropdown-month
+      background-color: $monthBackground
+      outline: none
+      padding: 0
+
 .flatpickr-weekdays
   background $weekdaysBackground
   text-align center
diff --git a/src/style/themes/dark.styl b/src/style/themes/dark.styl
index d9bd81a8..ce2874ff 100644
--- a/src/style/themes/dark.styl
+++ b/src/style/themes/dark.styl
@@ -2,7 +2,7 @@ $calendarBackground = #3f4458
 $calendarBorderColor = darken(#3f4458, 50%)
 
 $monthForeground = #fff
-$monthBackground = transparent
+$monthBackground = #3f4458
 
 $weekdaysBackground = transparent
 $weekdaysForeground = #fff
diff --git a/src/types/instance.ts b/src/types/instance.ts
index f584c7f5..15f19b86 100644
--- a/src/types/instance.ts
+++ b/src/types/instance.ts
@@ -28,6 +28,8 @@ export interface Elements {
   // month nav
   monthNav: HTMLDivElement;
 
+  monthsDropdownContainer: HTMLSelectElement;
+
   yearElements: HTMLInputElement[];
   monthElements: HTMLSpanElement[];
 


Test patch:
diff --git a/src/__tests__/index.spec.ts b/src/__tests__/index.spec.ts
index 92aecef9..907750cf 100644
--- a/src/__tests__/index.spec.ts
+++ b/src/__tests__/index.spec.ts
@@ -1411,6 +1411,62 @@ describe("flatpickr", () => {
       );
       expect(document.activeElement).toStrictEqual(fp._input);
     });
+
+    it("dropdown should correctly load months with minDate", () => {
+      const fp = createInstance({
+        defaultDate: new Date(2019, 5, 11),
+        minDate: new Date(2019, 4, 11),
+      }) as Instance;
+
+      const monthsDropdown = fp.calendarContainer.querySelector(
+        ".flatpickr-monthDropdown-months"
+      );
+      const months = monthsDropdown!.querySelectorAll(
+        ".flatpickr-monthDropdown-month"
+      );
+
+      expect(months.length).toEqual(8);
+
+      expect(months[0]!.innerHTML).toEqual("May");
+      expect(months[7]!.innerHTML).toEqual("December");
+    });
+
+    it("dropdown should correctly load months with maxDate", () => {
+      const fp = createInstance({
+        defaultDate: new Date(2019, 4, 11),
+        maxDate: new Date(2019, 8, 11),
+      }) as Instance;
+
+      const monthsDropdown = fp.calendarContainer.querySelector(
+        ".flatpickr-monthDropdown-months"
+      );
+      const months = monthsDropdown!.querySelectorAll(
+        ".flatpickr-monthDropdown-month"
+      );
+
+      expect(months.length).toEqual(9);
+
+      expect(months[0]!.innerHTML).toEqual("January");
+      expect(months[months.length - 1]!.innerHTML).toEqual("September");
+    });
+
+    it("dropdown should change month", () => {
+      const fp = createInstance({
+        defaultDate: new Date(2019, 1, 1),
+      }) as Instance;
+
+      const monthsDropdown = fp.calendarContainer.querySelector(
+        ".flatpickr-monthDropdown-months"
+      ) as HTMLSelectElement;
+
+      monthsDropdown.value = "3";
+
+      var evt = document.createEvent("HTMLEvents");
+      evt.initEvent("change", false, true);
+      monthsDropdown.dispatchEvent(evt);
+
+      expect(fp.currentMonth).toEqual(3);
+    });
   });
 
   describe("Localization", () => {
diff --git a/src/plugins/monthDropdown/tests.spec.ts b/src/plugins/monthDropdown/tests.spec.ts
deleted file mode 100644
index 88e27d27..00000000
--- a/src/plugins/monthDropdown/tests.spec.ts
+++ /dev/null
@@ -1,121 +0,0 @@
-import flatpickr from "../../index";
-import { Instance } from "types/instance";
-import { Options } from "types/options";
-import { German } from "l10n/de";
-import monthDropdownPlugin from "./index";
-
-flatpickr.defaultConfig.animate = false;
-
-jest.useFakeTimers();
-
-const createInstance = (config?: Options): Instance => {
-  return flatpickr(
-    document.createElement("input"),
-    config as Options
-  ) as Instance;
-};
-
-describe("monthDropdown", () => {
-  it("should correctly load", () => {
-    const fp = createInstance({
-      plugins: [monthDropdownPlugin({})],
-    }) as Instance;
-
-    const monthsDropdown = fp.calendarContainer.querySelector(
-      ".flatpickr-monthDropdown-months"
-    );
-
-    expect(monthsDropdown!.constructor.name).toEqual("HTMLSelectElement");
-    expect(
-      monthsDropdown!.querySelectorAll(".flatpickr-monthDropdown-month").length
-    ).toEqual(12);
-  });
-
-  it("should correctly load shorthand months", () => {
-    const fp = createInstance({
-      plugins: [monthDropdownPlugin({ shorthand: true })],
-    }) as Instance;
-
-    const monthsDropdown = fp.calendarContainer.querySelector(
-      ".flatpickr-monthDropdown-months"
-    );
-
-    expect(
-      monthsDropdown!.querySelector(".flatpickr-monthDropdown-month")!.innerHTML
-    ).toEqual("Jan");
-  });
-
-  it("should correctly load localized months", () => {
-    const fp = createInstance({
-      locale: German,
-      plugins: [monthDropdownPlugin({})],
-    }) as Instance;
-
-    const monthsDropdown = fp.calendarContainer.querySelector(
-      ".flatpickr-monthDropdown-months"
-    );
-
-    expect(
-      monthsDropdown!.querySelector(".flatpickr-monthDropdown-month")!.innerHTML
-    ).toEqual("Januar");
-  });
-
-  it("should correctly load months with minDate", () => {
-    const fp = createInstance({
-      defaultDate: new Date(2019, 5, 11),
-      minDate: new Date(2019, 4, 11),
-      plugins: [monthDropdownPlugin({})],
-    }) as Instance;
-
-    const monthsDropdown = fp.calendarContainer.querySelector(
-      ".flatpickr-monthDropdown-months"
-    );
-    const months = monthsDropdown!.querySelectorAll(
-      ".flatpickr-monthDropdown-month"
-    );
-
-    expect(months.length).toEqual(8);
-
-    expect(months[0]!.innerHTML).toEqual("May");
-    expect(months[7]!.innerHTML).toEqual("December");
-  });
-
-  it("should correctly load months with maxDate", () => {
-    const fp = createInstance({
-      defaultDate: new Date(2019, 4, 11),
-      maxDate: new Date(2019, 8, 11),
-      plugins: [monthDropdownPlugin({})],
-    }) as Instance;
-
-    const monthsDropdown = fp.calendarContainer.querySelector(
-      ".flatpickr-monthDropdown-months"
-    );
-    const months = monthsDropdown!.querySelectorAll(
-      ".flatpickr-monthDropdown-month"
-    );
-
-    expect(months.length).toEqual(9);
-
-    expect(months[0]!.innerHTML).toEqual("January");
-    expect(months[months.length - 1]!.innerHTML).toEqual("September");
-  });
-
-  it("should change month", () => {
-    const fp = createInstance({
-      defaultDate: new Date(2019, 1, 1),
-      plugins: [monthDropdownPlugin({})],
-    }) as Instance;
-
-    const monthsDropdown = fp.calendarContainer.querySelector(
-      ".flatpickr-monthDropdown-months"
-    ) as HTMLSelectElement;
-
-    monthsDropdown.value = "3";
-
-    var evt = document.createEvent("HTMLEvents");
-    evt.initEvent("change", false, true);
-    monthsDropdown.dispatchEvent(evt);
-
-    expect(fp.currentMonth).toEqual(3);
-  });
-});