calendar/base/content/widgets/minimonth.xml
author Jorg K <jorgk@jorgk.com>
Wed, 19 Jul 2017 14:43:16 +0200
changeset 28330 72d7059579e0275a3755fed8f85b1baeb671e1a0
parent 28278 4bafb6c2ddaf96fce9afed6ad76793224927207e
child 29132 076da1348bcce4fc0b579531505709fb0601b790
permissions -rw-r--r--
Bug 1380751 - Use mozIntl in minimonth.xml. r=MakeMyDay a=philipp DONTBUILD

<?xml version="1.0"?>
<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->

<!--
   MiniMonth Calendar: day-of-month grid XBL component.
   Displays month name and year above grid of days of month by week rows.
   Arrows move forward or back a month.
   Selecting a month name from month menu moves to that month in same year.
   Selecting a year from year menu moves to same month in selected year.
   Clicking on a day cell calls onchange attribute.
   Changing month via arrows or menus calls onmonthchange attribute.

   At site, can provide id, and code to run when value changed by picker.
     <calendar id="my-date-picker" onchange="myDatePick( this );"/>

   May get/set value in javascript with
     document.getElementById("my-date-picker").value = new Date();

   Use attributes onpopuplisthidden and onmonthchange for working around
   bugs that occur when minimonth is displayed in a popup (as in datepicker):
     Currently (2005.3)
       whenever a child popup is hidden, the parent popup needs to be reshown.
         Use onpopuplisthidden to reshow parent popop (hidePopup, openPopup).
       When title month or year changes, parent popup may need to be reshown.
         Use onmonthchange to reshow parent popop (hidePopup, openPopup).
-->

<!DOCTYPE bindings
[
    <!ENTITY % dtd1 SYSTEM "chrome://calendar/locale/global.dtd" > %dtd1;
    <!ENTITY % dtd2 SYSTEM "chrome://global/locale/global.dtd" > %dtd2;
]>

<bindings id="xulMiniMonth"
          xmlns="http://www.mozilla.org/xbl"
          xmlns:html="http://www.w3.org/1999/xhtml"
          xmlns:xbl="http://www.mozilla.org/xbl"
          xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">

  <binding id="minimonth-header" extends="xul:box">
    <content class="minimonth-month-box" align="center">
      <xul:deck anonid="monthheader" xbl:inherits="selectedIndex=month" class="minimonth-month-name-readonly">
        <xul:text value="&month.1.name;"/>
        <xul:text value="&month.2.name;"/>
        <xul:text value="&month.3.name;"/>
        <xul:text value="&month.4.name;"/>
        <xul:text value="&month.5.name;"/>
        <xul:text value="&month.6.name;"/>
        <xul:text value="&month.7.name;"/>
        <xul:text value="&month.8.name;"/>
        <xul:text value="&month.9.name;"/>
        <xul:text value="&month.10.name;"/>
        <xul:text value="&month.11.name;"/>
        <xul:text value="&month.12.name;"/>
      </xul:deck>
      <xul:text anonid="yearcell" class="minimonth-year-name-readonly" xbl:inherits="value=year"/>
      <xul:spacer flex="1"/>
    </content>
  </binding>

  <binding id="active-minimonth-header" extends="chrome://calendar/content/widgets/minimonth.xml#minimonth-header">
    <content class="minimonth-month-box" align="center">
      <xul:deck anonid="monthheader" xbl:inherits="selectedIndex=month">
        <xul:toolbarbutton class="minimonth-month-name" label="&month.1.name;"  oncommand="showPopupList(event, 'months-popup')"/>
        <xul:toolbarbutton class="minimonth-month-name" label="&month.2.name;"  oncommand="showPopupList(event, 'months-popup')"/>
        <xul:toolbarbutton class="minimonth-month-name" label="&month.3.name;"  oncommand="showPopupList(event, 'months-popup')"/>
        <xul:toolbarbutton class="minimonth-month-name" label="&month.4.name;"  oncommand="showPopupList(event, 'months-popup')"/>
        <xul:toolbarbutton class="minimonth-month-name" label="&month.5.name;"  oncommand="showPopupList(event, 'months-popup')"/>
        <xul:toolbarbutton class="minimonth-month-name" label="&month.6.name;"  oncommand="showPopupList(event, 'months-popup')"/>
        <xul:toolbarbutton class="minimonth-month-name" label="&month.7.name;"  oncommand="showPopupList(event, 'months-popup')"/>
        <xul:toolbarbutton class="minimonth-month-name" label="&month.8.name;"  oncommand="showPopupList(event, 'months-popup')"/>
        <xul:toolbarbutton class="minimonth-month-name" label="&month.9.name;"  oncommand="showPopupList(event, 'months-popup')"/>
        <xul:toolbarbutton class="minimonth-month-name" label="&month.10.name;" oncommand="showPopupList(event, 'months-popup')"/>
        <xul:toolbarbutton class="minimonth-month-name" label="&month.11.name;" oncommand="showPopupList(event, 'months-popup')"/>
        <xul:toolbarbutton class="minimonth-month-name" label="&month.12.name;" oncommand="showPopupList(event, 'months-popup')"/>
      </xul:deck>
      <xul:toolbarbutton anonid="yearcell"
                         class="minimonth-year-name"
                         oncommand="showPopupList(event, 'years-popup')"
                         xbl:inherits="label=year"/>
      <xul:spacer flex="1"/>
      <xul:toolbarbutton anonid="back-button" class="minimonth-nav-btns" dir="-1"
                         oncommand="this.kMinimonth.advanceMonth(parseInt(this.getAttribute('dir'), 10))"
                         tooltiptext="&onemonthbackward.tooltip;"/>
      <xul:toolbarbutton anonid="today-button" class="minimonth-nav-btns" dir="0"
                         oncommand="this.kMinimonth.value = new Date();"
                         tooltiptext="&showToday.tooltip;"/>
      <xul:toolbarbutton anonid="forward-button" class="minimonth-nav-btns" dir="1"
                         oncommand="this.kMinimonth.advanceMonth(parseInt(this.getAttribute('dir'), 10))"
                         tooltiptext="&onemonthforward.tooltip;"/>
      <xul:popupset anonid="minmonth-popupset">
        <xul:menupopup anonid="months-popup" position="after_start"
                       onpopupshowing="event.stopPropagation();"
                       onpopuphidden="firePopupListHidden();">
          <xul:menuitem class="minimonth-list" oncommand="onMonthsPopupCommand(this)" label="&month.1.name;"  index="0"/>
          <xul:menuitem class="minimonth-list" oncommand="onMonthsPopupCommand(this)" label="&month.2.name;"  index="1"/>
          <xul:menuitem class="minimonth-list" oncommand="onMonthsPopupCommand(this)" label="&month.3.name;"  index="2"/>
          <xul:menuitem class="minimonth-list" oncommand="onMonthsPopupCommand(this)" label="&month.4.name;"  index="3"/>
          <xul:menuitem class="minimonth-list" oncommand="onMonthsPopupCommand(this)" label="&month.5.name;"  index="4"/>
          <xul:menuitem class="minimonth-list" oncommand="onMonthsPopupCommand(this)" label="&month.6.name;"  index="5"/>
          <xul:menuitem class="minimonth-list" oncommand="onMonthsPopupCommand(this)" label="&month.7.name;"  index="6"/>
          <xul:menuitem class="minimonth-list" oncommand="onMonthsPopupCommand(this)" label="&month.8.name;"  index="7"/>
          <xul:menuitem class="minimonth-list" oncommand="onMonthsPopupCommand(this)" label="&month.9.name;"  index="8"/>
          <xul:menuitem class="minimonth-list" oncommand="onMonthsPopupCommand(this)" label="&month.10.name;" index="9"/>
          <xul:menuitem class="minimonth-list" oncommand="onMonthsPopupCommand(this)" label="&month.11.name;" index="10"/>
          <xul:menuitem class="minimonth-list" oncommand="onMonthsPopupCommand(this)" label="&month.12.name;" index="11"/>
        </xul:menupopup>
        <xul:menupopup anonid="years-popup" position="after_start"
                       onpopupshowing="moveYears('reset', 0); event.stopPropagation();"
                       onpopuphidden="firePopupListHidden();">
          <xul:autorepeatbutton class="autorepeatbutton-up"
                                orient="vertical"
                                oncommand="moveYears('up', 1);"/>
          <xul:menuitem class="minimonth-list" oncommand="onYearsPopupCommand(this)"/>
          <xul:menuitem class="minimonth-list" oncommand="onYearsPopupCommand(this)"/>
          <xul:menuitem class="minimonth-list" oncommand="onYearsPopupCommand(this)"/>
          <xul:menuitem class="minimonth-list" oncommand="onYearsPopupCommand(this)"/>
          <xul:menuitem class="minimonth-list" oncommand="onYearsPopupCommand(this)"/>
          <xul:menuitem class="minimonth-list" oncommand="onYearsPopupCommand(this)"/>
          <xul:menuitem class="minimonth-list" oncommand="onYearsPopupCommand(this)"/>
          <xul:menuitem class="minimonth-list" oncommand="onYearsPopupCommand(this)"/>
          <xul:menuitem class="minimonth-list" oncommand="onYearsPopupCommand(this)"/>
          <xul:autorepeatbutton class="autorepeatbutton-down"
                                orient="vertical"
                                oncommand="moveYears('down', 1);"/>
        </xul:menupopup>
      </xul:popupset>
    </content>
    <implementation>
      <field name="kMinimonth">null</field>
      <field name="mPopup">null</field>
      <field name="mScrollYearsHandler">null</field>
      <field name="mPixelScrollDelta">0</field>
      <constructor><![CDATA[
        Components.utils.import("resource://calendar/modules/calUtils.jsm");

        this.kMinimonth = cal.getParentNodeOrThis(this, "minimonth");
        document.getAnonymousElementByAttribute(this, "anonid", "back-button").kMinimonth = this.kMinimonth;
        document.getAnonymousElementByAttribute(this, "anonid", "today-button").kMinimonth = this.kMinimonth;
        document.getAnonymousElementByAttribute(this, "anonid", "forward-button").kMinimonth = this.kMinimonth;

        this.mScrollYearsHandler = this.scrollYears.bind(this);
        document.getAnonymousElementByAttribute(this, "anonid", "years-popup")
                .addEventListener("wheel", this.mScrollYearsHandler, true);
      ]]></constructor>

      <destructor><![CDATA[
        document.getAnonymousElementByAttribute(this, "anonid", "years-popup")
                .removeEventListener("wheel", this.mScrollYearsHandler, true);
        this.mScrollYearsHandler = null;
      ]]></destructor>

      <method name="showPopupList">
        <parameter name="aEvent"/>
        <parameter name="aPopupAnonId"/>
        <body><![CDATA[
          // Close open popups (if any), to prevent linux crashes
          if (this.mPopup) {
              this.mPopup.hidePopup();
          }
          this.mPopup = document.getAnonymousElementByAttribute(this, "anonid", aPopupAnonId);
          this.mPopup.openPopup(aEvent.target, "after_start");
        ]]></body>
      </method>

      <method name="hidePopupList">
        <body><![CDATA[
          // Close open popups (if any)
          let popup = this.mPopup;
          this.mPopup = null;
          if (popup) {
              popup.hidePopup();
          }
        ]]></body>
      </method>

      <method name="firePopupListHidden">
        <body><![CDATA[
          if (this.mPopup) {
              this.mPopup = null;
              this.kMinimonth.fireEvent("popuplisthidden");
          }
        ]]></body>
      </method>

      <method name="onMonthsPopupCommand">
        <parameter name="aItem"/>
        <body><![CDATA[
          let popup = cal.getParentNodeOrThis(aItem, "menupopup");
          let triggerNode = popup.triggerNode || popup.anchorNode;
          let deck = triggerNode.parentNode;

          this.hidePopupList();
          this.kMinimonth.switchMonth(aItem.getAttribute("index"));

          deck.childNodes[deck.selectedIndex].focus();
        ]]></body>
      </method>

      <method name="onYearsPopupCommand">
        <parameter name="aItem"/>
        <body><![CDATA[
          this.hidePopupList();
          let value = aItem.getAttribute("label");
          if (value) {
              this.kMinimonth.switchYear(value);
          }
        ]]></body>
      </method>

      <method name="updateMonthPopup">
        <parameter name="aDate"/>
        <body><![CDATA[
          let months = document.getAnonymousElementByAttribute(this, "anonid", "months-popup").childNodes;
          let month = aDate.getMonth();
          for (let i = 0; i < months.length; i++) {
              months[i].setAttribute("current", i == month ? "true" : "false");
          }
        ]]></body>
      </method>

      <method name="updateYearPopup">
        <parameter name="aDate"/>
        <body><![CDATA[
          let years = document.getAnonymousElementByAttribute(this, "anonid", "years-popup").childNodes;
          let year = new Date(aDate);
          let compFullYear = aDate.getFullYear();
          year.setFullYear(Math.max(1, compFullYear - Math.trunc(years.length / 2) + 1));
          for (let i = 1; i < years.length - 1; i++) {
              let curfullYear = year.getFullYear();
              years[i].setAttribute("label", curfullYear);
              years[i].setAttribute("current", curfullYear == compFullYear ? "true" : "false");
              year.setFullYear(curfullYear + 1);
          }
        ]]></body>
      </method>

      <method name="scrollYears">
        <parameter name="event"/>
        <body><![CDATA[
          let yearPopup = cal.getParentNodeOrThis(event.target, "menupopup");
          const pixelThreshold = 75;
          if (yearPopup) {
              let monthList = yearPopup.getElementsByAttribute("class", "minimonth-list");
              if (monthList && monthList.length > 0) {
                  if (event.deltaMode == event.DOM_DELTA_PAGE) {
                      let dir = event.deltaY > 0 ? "up" : "down";
                      this.moveYears(dir, Math.abs(event.deltaY) * monthList.length);
                  } else if (event.deltaMode == event.DOM_DELTA_LINE) {
                      let dir = event.deltaY > 0 ? "up" : "down";
                      this.moveYears(dir, 1);
                  } else if (event.deltaMode == event.DOM_DELTA_PIXEL) {
                      this.mPixelScrollDelta += event.deltaY;
                      if (this.mPixelScrollDelta > pixelThreshold) {
                          this.moveYears("down", 1);
                          this.mPixelScrollDelta = 0;
                      } else if (this.mPixelScrollDelta < -pixelThreshold) {
                          this.moveYears("up", 1);
                          this.mPixelScrollDelta = 0;
                      }
                  }

                  event.stopPropagation();
                  event.preventDefault();
              }
          }
        ]]></body>
      </method>

      <method name="moveYears">
        <parameter name="direction"/>
        <parameter name="scrollOffset"/>
        <body><![CDATA[
          // Update the year popup
          let years = document.getAnonymousElementByAttribute(this, "anonid", "years-popup").childNodes;
          let current = this.getAttribute("year");
          let offset;
          switch (direction) {
              case "reset": {
                  let middleyear = years[Math.floor(years.length / 2)].getAttribute("label");
                  if (current <= (years.length / 2)) {
                      offset = 1 - years[1].getAttribute("label");
                  } else {
                      offset = current - middleyear;
                  }
                  break;
              }
              case "up": {
                  offset = -Math.abs(scrollOffset) || -1;
                  break;
              }
              case "down": {
                  offset = Math.abs(scrollOffset) || 1;
                  break;
              }
          }

          // Disable the up arrow when we get to the year 1.
          years[0].disabled = parseInt(years[1].getAttribute("label"), 10) + offset < 2;

          if (!offset) {
              // No need to loop through when the offset is zero.
              return;
          }

          // Go through all visible years and set the new value. Be sure to
          // skip the autorepeatbuttons.
          for (let i = 1; i < years.length - 1; i++) {
              let value = parseInt(years[i].getAttribute("label"), 10) + offset;
              years[i].setAttribute("label", value);
              years[i].setAttribute("current", value == current ? "true" : "false");
          }
        ]]></body>
      </method>
    </implementation>

    <handlers>
      <handler event="bindingattached" action="this.initialize();"/>
    </handlers>
  </binding>

  <binding id="minimonth" extends="xul:box">
    <resources>
      <stylesheet src="chrome://calendar-common/skin/widgets/minimonth.css"/>
    </resources>

    <content orient="vertical" xbl:inherits="onchange,onmonthchange,onpopuplisthidden,readonly" role="group">
      <xul:minimonth-header anonid="minimonth-header" xbl:inherits="readonly,month,year"/>
      <html:table anonid="minimonth-calendar" class="minimonth-cal-box">
        <html:tr class="minimonth-row-head" anonid="minimonth-row-header">
          <html:th class="minimonth-row-header-week" scope="col"/>
          <html:th class="minimonth-row-header" scope="col"/>
          <html:th class="minimonth-row-header" scope="col"/>
          <html:th class="minimonth-row-header" scope="col"/>
          <html:th class="minimonth-row-header" scope="col"/>
          <html:th class="minimonth-row-header" scope="col"/>
          <html:th class="minimonth-row-header" scope="col"/>
          <html:th class="minimonth-row-header" scope="col"/>
        </html:tr>
        <html:tr class="minimonth-row-body">
          <html:th class="minimonth-week" scope="row"/>
          <html:td class="minimonth-day" tabindex="-1"/>
          <html:td class="minimonth-day" tabindex="-1"/>
          <html:td class="minimonth-day" tabindex="-1"/>
          <html:td class="minimonth-day" tabindex="-1"/>
          <html:td class="minimonth-day" tabindex="-1"/>
          <html:td class="minimonth-day" tabindex="-1"/>
          <html:td class="minimonth-day" tabindex="-1"/>
        </html:tr>
        <html:tr class="minimonth-row-body">
          <html:th class="minimonth-week" scope="row"/>
          <html:td class="minimonth-day" tabindex="-1"/>
          <html:td class="minimonth-day" tabindex="-1"/>
          <html:td class="minimonth-day" tabindex="-1"/>
          <html:td class="minimonth-day" tabindex="-1"/>
          <html:td class="minimonth-day" tabindex="-1"/>
          <html:td class="minimonth-day" tabindex="-1"/>
          <html:td class="minimonth-day" tabindex="-1"/>
        </html:tr>
        <html:tr class="minimonth-row-body">
          <html:th class="minimonth-week" scope="row"/>
          <html:td class="minimonth-day" tabindex="-1"/>
          <html:td class="minimonth-day" tabindex="-1"/>
          <html:td class="minimonth-day" tabindex="-1"/>
          <html:td class="minimonth-day" tabindex="-1"/>
          <html:td class="minimonth-day" tabindex="-1"/>
          <html:td class="minimonth-day" tabindex="-1"/>
          <html:td class="minimonth-day" tabindex="-1"/>
        </html:tr>
        <html:tr class="minimonth-row-body">
          <html:th class="minimonth-week" scope="row"/>
          <html:td class="minimonth-day" tabindex="-1"/>
          <html:td class="minimonth-day" tabindex="-1"/>
          <html:td class="minimonth-day" tabindex="-1"/>
          <html:td class="minimonth-day" tabindex="-1"/>
          <html:td class="minimonth-day" tabindex="-1"/>
          <html:td class="minimonth-day" tabindex="-1"/>
          <html:td class="minimonth-day" tabindex="-1"/>
        </html:tr>
        <html:tr class="minimonth-row-body">
          <html:th class="minimonth-week" scope="row"/>
          <html:td class="minimonth-day" tabindex="-1"/>
          <html:td class="minimonth-day" tabindex="-1"/>
          <html:td class="minimonth-day" tabindex="-1"/>
          <html:td class="minimonth-day" tabindex="-1"/>
          <html:td class="minimonth-day" tabindex="-1"/>
          <html:td class="minimonth-day" tabindex="-1"/>
          <html:td class="minimonth-day" tabindex="-1"/>
        </html:tr>
        <html:tr class="minimonth-row-body">
          <html:th class="minimonth-week" scope="row"/>
          <html:td class="minimonth-day" tabindex="-1"/>
          <html:td class="minimonth-day" tabindex="-1"/>
          <html:td class="minimonth-day" tabindex="-1"/>
          <html:td class="minimonth-day" tabindex="-1"/>
          <html:td class="minimonth-day" tabindex="-1"/>
          <html:td class="minimonth-day" tabindex="-1"/>
          <html:td class="minimonth-day" tabindex="-1"/>
        </html:tr>
      </html:table>
    </content>

    <implementation implements="calICompositeObserver calIOperationListener nsIObserver" >
      <property name="value"
                onget="return this.mValue"
                onset="this.update(val)"/>

      <property name="extra"
                onget="return this.mExtraDate"
                onset="this.mExtraDate = val"/>

       <!--returns the first (inclusive) date of the minimonth as a calIDateTime object-->
      <property name="firstDate" readonly="true">
        <getter><![CDATA[
          let date = this._getCalBoxNode(1, 1).date;
          return cal.jsDateToDateTime(date);
        ]]></getter>
      </property>

       <!--returns the last (exclusive) date of the minimonth as a calIDateTime object-->
      <property name="lastDate" readonly="true">
        <getter><![CDATA[
          let date = this._getCalBoxNode(6, 7).date;
          let lastDateTime = cal.jsDateToDateTime(date);
          lastDateTime.day = lastDateTime.day + 1;
          return lastDateTime;
        ]]></getter>
      </property>

      <field name="mDaymap">null</field>
      <field name="mValue">null</field>
      <field name="mEditorDate">null</field>
      <field name="mExtraDate">null</field>
      <field name="mPixelScrollDelta">0</field>
      <field name="mIsReadOnly">false</field>
      <field name="mObservesComposite">false</field>
      <field name="mShowWeekNumber">true</field>

      <constructor><![CDATA[
        Components.utils.import("resource://gre/modules/Services.jsm");
        Components.utils.import("resource://gre/modules/Preferences.jsm");
        Components.utils.import("resource://calendar/modules/calUtils.jsm");

        this.mToday = false;
        this.mSelected = false;
        this.mExtra = false;
        this.mValue = new Date(); // Default to "today"
        this.mFocused = null;
        // save references for convenience
        if (this.hasAttribute("readonly")) {
            this.mIsReadOnly = this.getAttribute("readonly") == "true";
        }
        this.refreshDisplay();
        if (this.hasAttribute("freebusy")) {
            this._setFreeBusy(this.getAttribute("freebusy") == "true");
        }
        this.mShowWeekNumber = Preferences.get("calendar.view-minimonth.showWeekNumber", true);

        // Add pref observer
        let branch = Services.prefs.getBranch("");
        branch.addObserver("calendar.", this, false);
      ]]></constructor>

      <destructor><![CDATA[
        Components.utils.import("resource://gre/modules/Services.jsm");

        if (this.mObservesComposite == true) {
            cal.getCompositeCalendar(window).removeObserver(this);
        }

        // Remove pref observer
        let branch = Services.prefs.getBranch("");
        branch.removeObserver("calendar.", this, false);
      ]]></destructor>

      <!-- calIOperationListener methods -->
      <method name="onOperationComplete">
        <parameter name="aCalendar"/>
        <parameter name="aStatus"/>
        <parameter name="aOperationType"/>
        <parameter name="aId"/>
        <parameter name="aDetail"/>
        <body><![CDATA[
        ]]></body>
      </method>

      <method name="onGetResult">
        <parameter name="aCalendar"/>
        <parameter name="aStatus"/>
        <parameter name="aItemType"/>
        <parameter name="aDetail"/>
        <parameter name="aCount"/>
        <parameter name="aItems"/>
        <body><![CDATA[
          if (!Components.isSuccessCode(aStatus)) {
              return;
          }
          for (let item of aItems) {
              this.setBusyDaysForOccurrence(item, true);
          }
        ]]></body>
      </method>

      <method name="setBusyDaysForItem">
        <parameter name="aItem"/>
        <parameter name="aState"/>
        <body><![CDATA[
          let items = [aItem];
          if (aItem.recurrenceInfo) {
              let startDate = this.firstDate;
              let endDate = this.lastDate;
              items = aItem.getOccurrencesBetween(startDate, endDate, {});
          }
          for (let item of items) {
              this.setBusyDaysForOccurrence(item, aState);
          }
        ]]></body>
      </method>

      <method name="parseBoxBusy">
        <parameter name="aBox"/>
        <body><![CDATA[
          let boxBusy = {};

          let busyStr = aBox.getAttribute("busy");
          if (busyStr && busyStr.length > 0) {
              let calChunks = busyStr.split("\u001A");
              for (let chunk of calChunks) {
                  let expr = chunk.split("=");
                  boxBusy[expr[0]] = parseInt(expr[1], 10);
              }
          }

          return boxBusy;
        ]]></body>
      </method>

      <method name="updateBoxBusy">
        <parameter name="aBox"/>
        <parameter name="aBoxBusy"/>
        <body><![CDATA[
          let calChunks = [];

          for (let calId in aBoxBusy) {
              if (aBoxBusy[calId]) {
                  calChunks.push(calId + "=" + aBoxBusy[calId]);
              }
          }

          if (calChunks.length > 0) {
              let busyStr = calChunks.join("\u001A");
              aBox.setAttribute("busy", busyStr);
          } else {
              aBox.removeAttribute("busy");
          }
        ]]></body>
      </method>

      <method name="removeCalendarFromBoxBusy">
        <parameter name="aBox"/>
        <parameter name="aCalendar"/>
        <body><![CDATA[
          let boxBusy = this.parseBoxBusy(aBox);
          if (boxBusy[aCalendar.id]) {
              delete boxBusy[aCalendar.id];
          }
          this.updateBoxBusy(aBox, boxBusy);
        ]]></body>
      </method>

      <method name="setBusyDaysForOccurrence">
        <parameter name="aOccurrence"/>
        <parameter name="aState"/>
        <body><![CDATA[
          if (aOccurrence.getProperty("TRANSP") == "TRANSPARENT") {
              // Skip transparent events
              return;
          }
          let start = aOccurrence[cal.calGetStartDateProp(aOccurrence)] || aOccurrence.dueDate;
          let end = aOccurrence[cal.calGetEndDateProp(aOccurrence)] || start;
          if (!start) {
              return;
          }

          // We need to compare with midnight of the current day, so reset the
          // time here.
          let current = start.clone().getInTimezone(cal.calendarDefaultTimezone());
          current.hour = 0;
          current.minute = 0;
          current.second = 0;

          // Cache the result so the compare isn't called in each iteration.
          let compareResult = (start.compare(end) == 0 ? 1 : 0);

          // Setup the busy days.
          while (current.compare(end) < compareResult) {
              let box = this.getBoxForDate(current);
              if (box) {
                  let busyCalendars = this.parseBoxBusy(box);
                  if (!busyCalendars[aOccurrence.calendar.id]) {
                      busyCalendars[aOccurrence.calendar.id] = 0;
                  }
                  busyCalendars[aOccurrence.calendar.id] += (aState ? 1 : -1);
                  this.updateBoxBusy(box, busyCalendars);
              }
              current.day++;
          }
        ]]></body>
      </method>

       <!--calIObserver methods -->
      <method name="onStartBatch">
        <parameter name="aCalendar"/>
        <body><![CDATA[
        ]]></body>
      </method>

      <method name="onEndBatch">
        <parameter name="aCalendar"/>
        <body><![CDATA[
        ]]></body>
      </method>

      <method name="onLoad">
        <parameter name="aCalendar"/>
        <body><![CDATA[
        ]]></body>
      </method>

      <method name="onAddItem">
        <parameter name="aItem"/>
        <body><![CDATA[
          this.setBusyDaysForItem(aItem, true);
        ]]></body>
      </method>

      <method name="onDeleteItem">
        <parameter name="aItem"/>
        <body><![CDATA[
          this.setBusyDaysForItem(aItem, false);
        ]]></body>
      </method>

      <method name="onModifyItem">
        <parameter name="aNewItem"/>
        <parameter name="aOldItem"/>
        <body><![CDATA[
          this.setBusyDaysForItem(aOldItem, false);
          this.setBusyDaysForItem(aNewItem, true);
        ]]></body>
      </method>

      <method name="onError">
        <parameter name="aCalendar"/>
        <parameter name="aErrNo"/>
        <parameter name="aMessage"/>
        <body><![CDATA[
        ]]></body>
      </method>

      <method name="onPropertyChanged">
        <parameter name="aCalendar"/>
        <parameter name="aName"/>
        <parameter name="aValue"/>
        <parameter name="aOldValue"/>
        <body><![CDATA[
          switch (aName) {
              case "disabled":
                  this.resetAttributesForDate();
                  this.getItems();
                  break;
          }
        ]]></body>
      </method>

      <method name="onPropertyDeleting">
        <parameter name="aCalendar"/>
        <parameter name="aName"/>
        <body><![CDATA[
          this.onPropertyChanged(aCalendar, aName, null, null);
        ]]></body>
      </method>

       <!-- calICompositeObserver methods -->
      <method name="onCalendarAdded">
        <parameter name="aCalendar"/>
        <body><![CDATA[
            this.getItems(aCalendar);
        ]]></body>
      </method>

      <method name="onCalendarRemoved">
        <parameter name="aCalendar"/>
        <body><![CDATA[
          for (let day in this.mDayMap) {
              this.removeCalendarFromBoxBusy(this.mDayMap[day], aCalendar);
          }
        ]]></body>
      </method>

      <method name="onDefaultCalendarChanged">
        <parameter name="aCalendar"/>
        <body><![CDATA[
        ]]></body>
      </method>

      <!-- nsIObserver methods -->
      <method name="observe">
        <parameter name="aSubject"/>
        <parameter name="aTopic"/>
        <parameter name="aData"/>
        <body><![CDATA[
          switch (aData) {
              case "calendar.week.start":
              case "calendar.view-minimonth.showWeekNumber":
                  this.refreshDisplay();
                  break;
          }
        ]]></body>
      </method>

      <method name="refreshDisplay">
        <body><![CDATA[
          // Find out which should be the first day of the week
          this.weekStart = Preferences.get("calendar.week.start", 0);
          this.mShowWeekNumber = Preferences.get("calendar.view-minimonth.showWeekNumber", true);
          if (!this.mValue) {
              this.mValue = new Date();
          }
          this.setHeader();
          this.showMonth(this.mValue);
          this.updateAccessibleLabel();
        ]]></body>
      </method>

      <method name="_getCalBoxNode">
        <parameter name="aRow"/>
        <parameter name="aCol"/>
        <body><![CDATA[
          if (!this.mCalBox) {
              this.mCalBox = document.getAnonymousElementByAttribute(this, "anonid", "minimonth-calendar");
          }
          return this.mCalBox.children[aRow].children[aCol];
        ]]></body>
      </method>

      <method name="setHeader">
        <body><![CDATA[
          // Reset the headers
          let dayList = new Array(7);
          let longDayList = new Array(7);
          let tempDate = new Date();
          let i, j;
          let useOSFormat;
          tempDate.setDate(tempDate.getDate() - (tempDate.getDay() - this.weekStart));
          for (i = 0; i < 7; i++) {
              // if available, use UILocale days, else operating system format
              try {
                  dayList[i] = cal.calGetString("dateFormat",
                               "day." + (tempDate.getDay() + 1) + ".short");
              } catch (e) {
                  dayList[i] = tempDate.toLocaleDateString(undefined, { weekday: 'short' });
                  useOSFormat = true;
              }
              longDayList[i] = tempDate.toLocaleDateString(undefined, { weekday: 'long' });
              tempDate.setDate(tempDate.getDate() + 1);
          }

          if (useOSFormat) {
              // To keep datepicker popup compact, shrink localized weekday
              // abbreviations down to 1 or 2 chars so each column of week can
              // be as narrow as 2 digits.
              //
              // 1. Compute the minLength of the day name abbreviations.
              let minLength = dayList[0].length;
              for (i = 1; i < dayList.length; i++) {
                  minLength = Math.min(minLength, dayList[i].length);
              }
              // 2. If some day name abbrev. is longer than 2 chars (not Catalan),
              //    and ALL localized day names share same prefix (as in Chinese),
              //    then trim shared "day-" prefix.
              if (dayList.some(dayAbbr => dayAbbr.length > 2)) {
                  for (let endPrefix = 0; endPrefix < minLength; endPrefix++) {
                      let suffix = dayList[0][endPrefix];
                      if (dayList.some(dayAbbr => dayAbbr[endPrefix] != suffix)) {
                          if (endPrefix > 0) {
                              for (i = 0; i < dayList.length; i++) { // trim prefix chars.
                                  dayList[i] = dayList[i].substring(endPrefix);
                              }
                          }
                          break;
                      }
                  }
              }
              // 3. trim each day abbreviation to 1 char if unique, else 2 chars.
              for (i = 0; i < dayList.length; i++) {
                  let foundMatch = 1;
                  for (j = 0; j < dayList.length; j++) {
                      if (i != j) {
                          if (dayList[i].substring(0, 1) == dayList[j].substring(0, 1)) {
                              foundMatch = 2;
                              break;
                          }
                      }
                  }
                  dayList[i] = dayList[i].substring(0, foundMatch);
              }
          }

          setBooleanAttribute(this._getCalBoxNode(0, 0), "hidden", !this.mShowWeekNumber);
          for (let column = 1; column < 8; column++) {
              let node = this._getCalBoxNode(0, column);
              node.textContent = dayList[column - 1];
              node.setAttribute("aria-label", longDayList[column - 1]);
          }
        ]]></body>
      </method>

      <method name="showMonth">
        <parameter name="aDate"/>
        <body><![CDATA[
          // Use mExtraDate if aDate is null.
          aDate = new Date(aDate || this.mExtraDate);

          aDate.setDate(1);
          // We set the hour and minute to something highly unlikely to be the
          // exact change point of DST, so timezones like America/Sao Paulo
          // don't display some days twice.
          aDate.setHours(12);
          aDate.setMinutes(34);
          aDate.setSeconds(0);
          aDate.setMilliseconds(0);
          // Don't fire onmonthchange event upon initialization
          let monthChanged = this.mEditorDate && (this.mEditorDate.valueOf() != aDate.valueOf());
          this.mEditorDate = aDate; // only place mEditorDate is set.

          if (this.mToday) {
              this.mToday.removeAttribute("today");
              this.mToday = null;
          }

          if (this.mSelected) {
              this.mSelected.removeAttribute("selected");
              this.mSelected = null;
          }

          if (this.mExtra) {
              this.mExtra.removeAttribute("extra");
              this.mExtra = null;
          }

          // Update the month and year title
          this.setAttribute("month", aDate.getMonth());
          this.setAttribute("year", aDate.getFullYear());
          if (!this.mIsReadOnly) {
              // Update the month popup
              let header = document.getAnonymousElementByAttribute(this, "anonid", "minimonth-header");
              header.updateYearPopup(aDate);
              header.updateMonthPopup(aDate);
          }
          // Update the calendar
          let calbox = document.getAnonymousElementByAttribute(this, "anonid", "minimonth-calendar");
          let date = this._getStartDate(aDate);

          // get today's date
          let today = new Date();

          if (aDate.getFullYear() != (this.mValue || this.mExtraDate).getFullYear()) {
              let monthName = cal.formatMonth(aDate.getMonth() + 1,
                                              "calendar", "monthInYear");
              let label = cal.calGetString("calendar", "monthInYear",
                                       [monthName, aDate.getFullYear()]);
              calbox.setAttribute("aria-label", label);
          } else {
              calbox.setAttribute("aria-label", cal.calGetString("dateFormat",
                                  "month." + (aDate.getMonth() + 1) + ".name"));
          }

          this.mDayMap = {};
          let defaultTz = cal.calendarDefaultTimezone();
          let dateFormatter =
              Services.intl.createDateTimeFormat(undefined, { dateStyle: "long" });
          for (let k = 1; k < 7; k++) {
              // Set the week number.
              let firstElement = this._getCalBoxNode(k, 0);
              setBooleanAttribute(firstElement, "hidden", !this.mShowWeekNumber);
              if (this.mShowWeekNumber) {
                  let weekNumber = cal.getWeekInfoService()
                                      .getWeekTitle(cal.jsDateToDateTime(date, defaultTz));
                  let weekTitle = cal.calGetString("calendar", "WeekTitle", [weekNumber]);
                  firstElement.textContent = weekNumber;
                  firstElement.setAttribute("aria-label", weekTitle);
              }

              for (let i = 1; i < 8; i++) {
                  let day = this._getCalBoxNode(k, i);
                  let ymd = date.getFullYear() + "-" +
                            date.getMonth() + "-" +
                            date.getDate();
                  this.mDayMap[ymd] = day;

                  if (!this.mIsReadOnly) {
                      day.setAttribute("interactive", "true");
                  }

                  if (aDate.getMonth() == date.getMonth()) {
                      day.removeAttribute("othermonth");
                  } else {
                      day.setAttribute("othermonth", "true");
                  }

                  // highlight today
                  if (this._sameDay(today, date)) {
                      this.mToday = day;
                      day.setAttribute("today", "true");
                  }

                  // highlight the current date
                  let val = this.value;
                  if (this._sameDay(val, date)) {
                      this.mSelected = day;
                      day.setAttribute("selected", "true");
                  }

                  // highlight the extra date
                  if (this._sameDay(this.mExtraDate, date)) {
                      this.mExtra = day;
                      day.setAttribute("extra", "true");
                  }

                  if (aDate.getMonth() == date.getMonth() &&
                      aDate.getFullYear() == date.getFullYear()) {
                      day.setAttribute("aria-label", date.toLocaleDateString(undefined, { day: "numeric" }));
                  } else {
                      day.setAttribute("aria-label", dateFormatter.format(date));
                  }

                  day.date = new Date(date);
                  day.textContent = date.getDate();
                  date.setDate(date.getDate() + 1);

                  if (monthChanged) {
                      this.resetAttributesForDate(day.date);
                  }
              }
          }

          if (!this.mFocused) {
              this.setFocusedDate(this.mValue || this.mExtraDate);
          }

          if (monthChanged) {
              this.fireEvent("monthchange");
          }

          if (this.getAttribute("freebusy") == "true") {
              this.getItems();
          }
        ]]></body>
      </method>

      <!--Attention - duplicate!!!!-->
      <method name="fireEvent">
        <parameter name="aEventName"/>
        <body><![CDATA[
          let event = document.createEvent("Events");
          event.initEvent(aEventName, true, true);
          this.dispatchEvent(event);
        ]]></body>
      </method>

      <method name="getBoxForDate">
        <parameter name="aDate"/>
        <body><![CDATA[
          // aDate is a calIDateTime
          let ymd = [aDate.year, aDate.month, aDate.day].join("-");
          return (ymd in this.mDayMap ? this.mDayMap[ymd] : null);
        ]]></body>
      </method>

      <method name="resetAttributesForDate">
        <parameter name="aDate"/>
        <body><![CDATA[
          function removeForBox(aBox) {
              let allowedAttributes = 0;
              while (aBox.attributes.length > allowedAttributes) {
                  switch (aBox.attributes[allowedAttributes].nodeName) {
                      case "selected":
                      case "othermonth":
                      case "today":
                      case "extra":
                      case "interactive":
                      case "class":
                      case "tabindex":
                      case "role":
                      case "aria-label":
                          allowedAttributes++;
                          break;
                      default:
                          aBox.removeAttribute(aBox.attributes[allowedAttributes].nodeName);
                          break;
                  }
              }
          }

          if (aDate) {
              let box = this.getBoxForDate(cal.jsDateToDateTime(aDate, cal.calendarDefaultTimezone()));
              if (box) {
                  removeForBox(box);
              }
          } else {
              for (let k = 1; k < 7; k++) {
                  for (let i = 1; i < 8; i++) {
                      removeForBox(this._getCalBoxNode(k, i));
                  }
              }
          }
        ]]></body>
      </method>

      <method name="_setFreeBusy">
        <parameter name="aFreeBusy"/>
        <body><![CDATA[
          if (aFreeBusy == true) {
              if (this.mObservesComposite == false) {
                  cal.getCompositeCalendar(window).addObserver(this);
                  this.mObservesComposite = true;
                  this.getItems();
              }
          } else if (this.mObservesComposite == true) {
              cal.getCompositeCalendar(window).removeObserver(this);
              this.mObservesComposite = false;
          }
        ]]></body>
      </method>

      <method name="removeAttribute">
        <parameter name="aAttr"/>
        <body><![CDATA[
          if (aAttr == "freebusy") {
              this._setFreeBusy(false);
          }
          // this should be done using lookupMethod(), see bug 286629
          let ret = XULElement.prototype.removeAttribute.call(this, aAttr);
          return ret;
        ]]></body>
      </method>

      <method name="setAttribute">
        <parameter name="aAttr"/>
        <parameter name="aVal"/>
        <body><![CDATA[
          if (aAttr == "freebusy") {
              this._setFreeBusy(aVal == "true");
          }
          // this should be done using lookupMethod(), see bug 286629
          let ret = XULElement.prototype.setAttribute.call(this, aAttr, aVal);
          return ret;
        ]]></body>
      </method>

      <method name="getItems">
        <parameter name="aCalendar"/>
        <body><![CDATA[
          // The minimonth automatically clears extra styles on a month change.
          // Therefore we only need to fill the minimonth with new info.

          let calendar = aCalendar || cal.getCompositeCalendar(window);
          let filter = calendar.ITEM_FILTER_COMPLETED_ALL |
                       calendar.ITEM_FILTER_CLASS_OCCURRENCES |
                       calendar.ITEM_FILTER_ALL_ITEMS;

          // Get new info
          calendar.getItems(filter,
                            0,
                            this.firstDate,
                            this.lastDate,
                            this);
        ]]></body>
      </method>

      <method name="updateAccessibleLabel">
        <body><![CDATA[
          var label;
          if (this.mValue) {
              let dateFormatter =
                  Services.intl.createDateTimeFormat(undefined, { dateStyle: "long" });
              label = dateFormatter.format(this.mValue);
          } else {
              label = cal.calGetString("calendar", "minimonthNoSelectedDate");
          }
          this.setAttribute("aria-label", label);
        ]]></body>
      </method>

      <method name="update">
        <parameter name="aValue"/>
        <body><![CDATA[
          this.mValue = aValue;
          if (this.mValue) {
              this.fireEvent("change");
          }
          this.showMonth(aValue);
          if (aValue) {
              this.setFocusedDate(aValue);
          }
          this.updateAccessibleLabel();
        ]]></body>
      </method>

      <method name="setFocusedDate">
        <parameter name="aDate"/>
        <parameter name="aForceFocus"/>
        <body><![CDATA[
          let newFocused = this.getBoxForDate(cal.jsDateToDateTime(aDate, cal.calendarDefaultTimezone()));
          if (!newFocused) {
              return;
          }
          if (this.mFocused) {
              this.mFocused.setAttribute("tabindex", "-1");
          }
          this.mFocused = newFocused;
          this.mFocused.setAttribute("tabindex", "0");
          // only actually move the focus if it is already in the calendar box
          if (!aForceFocus) {
              let calbox = document.getAnonymousElementByAttribute(this, "anonid", "minimonth-calendar");
              aForceFocus = calbox.contains(document.commandDispatcher.focusedElement);
          }
          if (aForceFocus) {
              this.mFocused.focus();
          }
        ]]></body>
      </method>

      <method name="focusDate">
        <parameter name="aDate"/>
        <body><![CDATA[
          this.showMonth(aDate);
          this.setFocusedDate(aDate);
        ]]></body>
      </method>

      <method name="hidePopupList">
        <body><![CDATA[
          if (!this.mIsReadOnly) {
              let header = document.getAnonymousElementByAttribute(this, "anonid", "minimonth-header");
              header.hidePopupList();
          }
        ]]></body>
      </method>

      <method name="switchMonth">
        <parameter name="aMonth"/>
        <body><![CDATA[
          let newMonth = new Date(this.mEditorDate);
          newMonth.setMonth(aMonth);
          this.showMonth(newMonth);
        ]]></body>
      </method>

      <method name="switchYear">
        <parameter name="aYear"/>
        <body><![CDATA[
          let newMonth = new Date(this.mEditorDate);
          newMonth.setFullYear(aYear);
          this.showMonth(newMonth);
        ]]></body>
      </method>

      <method name="selectDate">
        <parameter name="aDate"/>
        <parameter name="aMainDate"/>
        <body><![CDATA[
          if (!aMainDate || aDate < this._getStartDate(aMainDate) || aDate > this._getEndDate(aMainDate)) {
              aMainDate = new Date(aDate);
              aMainDate.setDate(1);
          }
          // note, that aMainDate and this.mEditorDate refer to the first day
          // of the corresponding month
          let sameMonth = this._sameDay(aMainDate, this.mEditorDate);
          let sameDate = this._sameDay(aDate, this.mValue);
          if (!sameMonth && !sameDate) {
              // change month and select day
              this.mValue = aDate;
              this.showMonth(aMainDate);
          } else if (!sameMonth) {
              // change month only
              this.showMonth(aMainDate);
          } else if (!sameDate) {
              // select day only
              let day = this.getBoxForDate(cal.jsDateToDateTime(aDate, cal.calendarDefaultTimezone()));
              if (this.mSelected) {
                  this.mSelected.removeAttribute("selected");
              }
              this.mSelected = day;
              day.setAttribute("selected", "true");
              this.mValue = aDate;
              this.setFocusedDate(aDate);
          }
        ]]></body>
      </method>

      <method name="_getStartDate">
        <parameter name="aMainDate"/>
        <body><![CDATA[
          let date = new Date(aMainDate);
          let firstWeekday = (7 + aMainDate.getDay() - this.weekStart) % 7;
          date.setDate(date.getDate() - firstWeekday);
          return date;
        ]]></body>
      </method>

      <method name="_getEndDate">
        <parameter name="aMainDate"/>
        <body><![CDATA[
          let date = this._getStartDate(aMainDate);
          let calbox = document.getAnonymousElementByAttribute(this, "anonid", "minimonth-calendar");
          let days = (calbox.childNodes.length - 1) * 7;
          date.setDate(date.getDate() + days - 1);
          return date;
        ]]></body>
      </method>

      <method name="_sameDay">
        <parameter name="aDate1"/>
        <parameter name="aDate2"/>
        <body><![CDATA[
          if (aDate1 && aDate2 &&
             (aDate1.getDate() == aDate2.getDate()) &&
             (aDate1.getMonth() == aDate2.getMonth()) &&
             (aDate1.getFullYear() == aDate2.getFullYear())) {
              return true;
          }
          return false;
        ]]></body>
      </method>

      <method name="advanceMonth">
        <parameter name="aDir"/>
        <body><![CDATA[
          let advEditorDate = new Date(this.mEditorDate); // at 1st of month
          let advMonth = this.mEditorDate.getMonth() + aDir;
          advEditorDate.setMonth(advMonth);
          this.showMonth(advEditorDate);
        ]]></body>
      </method>

      <method name="moveByOffset">
        <parameter name="aYears"/>
        <parameter name="aMonths"/>
        <parameter name="aDays"/>
        <body><![CDATA[
          var day = new Date(this.mFocused.date.getFullYear() + aYears,
                             this.mFocused.date.getMonth() + aMonths,
                             this.mFocused.date.getDate() + aDays);
          this.focusDate(day);
        ]]></body>
      </method>

      <method name="focusCalendar">
        <body><![CDATA[
          this.mFocused.focus();
        ]]></body>
      </method>

      <method name="onDayActivate">
        <parameter name="aEvent"/>
        <body><![CDATA[
          if (aEvent.originalTarget.className == 'minimonth-day') {
              // the associated date might change when setting this.value if month changes
              let date = aEvent.originalTarget.date;
              if (!this.mIsReadOnly) {
                  this.value = date;
                  this.fireEvent("select");
              }
              this.setFocusedDate(date, true);

              aEvent.stopPropagation();
              aEvent.preventDefault();
          }
        ]]></body>
      </method>

      <method name="onDayMovement">
        <parameter name="aEvent"/>
        <parameter name="aYears"/>
        <parameter name="aMonths"/>
        <parameter name="aDays"/>
        <body><![CDATA[
          if (aEvent.originalTarget.className == 'minimonth-day') {
              this.moveByOffset(aYears, aMonths, aDays);
              aEvent.stopPropagation();
              aEvent.preventDefault();
          }
        ]]></body>
      </method>
    </implementation>

    <handlers>
      <handler event="wheel"><![CDATA[
        const pixelThreshold = 150;
        let deltaView = 0;
        if (this.mIsReadOnly) {
            // No scrolling on readonly months
            return;
        }
        if (event.deltaMode == event.DOM_DELTA_LINE ||
            event.deltaMode == event.DOM_DELTA_PAGE) {
            if (event.deltaY != 0) {
                deltaView = event.deltaY > 0 ? 1 : -1;
            }
        } else if (event.deltaMode == event.DOM_DELTA_PIXEL) {
            this.mPixelScrollDelta += event.deltaY;
            if (this.mPixelScrollDelta > pixelThreshold) {
                deltaView = 1;
                this.mPixelScrollDelta = 0;
            } else if (this.mPixelScrollDelta < -pixelThreshold) {
                deltaView = -1;
                this.mPixelScrollDelta = 0;
            }
        }

        if (deltaView != 0) {
            this.advanceMonth(deltaView);
        }

        event.stopPropagation();
        event.preventDefault();
      ]]></handler>
      <!-- day handlers -->
      <handler event="keypress" keycode="VK_LEFT" modifiers="control shift any"
               action="this.onDayMovement(event, 0, 0, -1);" phase="target"/>
      <handler event="keypress" keycode="VK_RIGHT" modifiers="control shift any"
               action="this.onDayMovement(event, 0, 0, 1);" phase="target"/>
      <handler event="keypress" keycode="VK_UP" modifiers="control shift any"
               action="this.onDayMovement(event, 0, 0, -7);" phase="target"/>
      <handler event="keypress" keycode="VK_DOWN" modifiers="control shift any"
               action="this.onDayMovement(event, 0, 0, 7);" phase="target"/>
      <handler event="keypress" keycode="VK_PAGE_UP"
               action="this.onDayMovement(event, 0, -1, 0);" phase="target"/>
      <handler event="keypress" keycode="VK_PAGE_DOWN"
               action="this.onDayMovement(event, 0, 1, 0);" phase="target"/>
      <handler event="keypress" keycode="VK_PAGE_UP" modifiers="shift"
               action="this.onDayMovement(event, -1, 0, 0);" phase="target"/>
      <handler event="keypress" keycode="VK_PAGE_DOWN" modifiers="shift"
               action="this.onDayMovement(event, 1, 0, 0);" phase="target"/>
      <handler event="keypress" keycode="VK_ESCAPE"
               phase="target"><![CDATA[
        if (event.originalTarget.className == 'minimonth-day') {
            this.focusDate(this.mValue || this.mExtraDate);
            event.stopPropagation();
            event.preventDefault();
        }
      ]]></handler>
      <handler event="keypress" keycode="VK_HOME"
               phase="target"><![CDATA[
        if (event.originalTarget.className == 'minimonth-day') {
            let today = new Date();
            this.update(today);
            this.focusDate(today);

            event.stopPropagation();
            event.preventDefault();
        }
      ]]></handler>
      <handler event="keypress" keycode="VK_RETURN"
               action="onDayActivate(event);" phase="target"/>
      <handler event="click" button="0" action="onDayActivate(event);"/>
    </handlers>
  </binding>
</bindings>