<?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/. -->

<!DOCTYPE bindings [
  <!ENTITY % datetimepickerDTD SYSTEM "chrome://global/locale/datetimepicker.dtd">
  %datetimepickerDTD;
]>

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

  <binding id="datetimepicker-base"
           extends="chrome://global/content/bindings/general.xml#basecontrol">

    <resources>
      <stylesheet src="chrome://global/content/textbox.css"/>
      <stylesheet src="chrome://global/skin/textbox.css"/>
      <stylesheet src="chrome://global/skin/dropmarker.css"/>
      <stylesheet src="chrome://global/skin/datetimepicker.css"/>
    </resources>

    <content align="center">
      <xul:hbox class="datetimepicker-input-box" align="center"
                xbl:inherits="context,disabled,readonly">
        <xul:hbox class="textbox-input-box datetimepicker-input-subbox" align="center">
          <html:input class="datetimepicker-input textbox-input" anonid="input-one"
                      size="2" maxlength="2"
                      xbl:inherits="disabled,readonly"/>
        </xul:hbox>
        <xul:label anonid="sep-first" class="datetimepicker-separator" value=":"/>
        <xul:hbox class="textbox-input-box datetimepicker-input-subbox" align="center">
          <html:input class="datetimepicker-input textbox-input" anonid="input-two"
                      size="2" maxlength="2"
                      xbl:inherits="disabled,readonly"/>
        </xul:hbox>
        <xul:label anonid="sep-second" class="datetimepicker-separator" value=":"/>
        <xul:hbox class="textbox-input-box datetimepicker-input-subbox" align="center">
          <html:input class="datetimepicker-input textbox-input" anonid="input-three"
                      size="2" maxlength="2"
                      xbl:inherits="disabled,readonly"/>
        </xul:hbox>
        <xul:hbox class="textbox-input-box datetimepicker-input-subbox" align="center">
          <html:input class="datetimepicker-input textbox-input" anonid="input-ampm"
                      size="2" maxlength="2"
                      xbl:inherits="disabled,readonly"/>
        </xul:hbox>
      </xul:hbox>
      <xul:spinbuttons anonid="buttons" xbl:inherits="disabled"
                       onup="this.parentNode._increaseOrDecrease(1);"
                       ondown="this.parentNode._increaseOrDecrease(-1);"/>
    </content>

    <implementation>
      <field name="_dateValue">null</field>
      <field name="_fieldOne">
        document.getAnonymousElementByAttribute(this, "anonid", "input-one");
      </field>
      <field name="_fieldTwo">
        document.getAnonymousElementByAttribute(this, "anonid", "input-two");
      </field>
      <field name="_fieldThree">
        document.getAnonymousElementByAttribute(this, "anonid", "input-three");
      </field>
      <field name="_fieldAMPM">
        document.getAnonymousElementByAttribute(this, "anonid", "input-ampm");
      </field>
      <field name="_separatorFirst">
        document.getAnonymousElementByAttribute(this, "anonid", "sep-first");
      </field>
      <field name="_separatorSecond">
        document.getAnonymousElementByAttribute(this, "anonid", "sep-second");
      </field>
      <field name="_lastFocusedField">null</field>
      <field name="_hasEntry">true</field>
      <field name="_valueEntered">false</field>
      <field name="attachedControl">null</field>

      <property name="_currentField" readonly="true">
        <getter>
          var focusedInput = document.activeElement;
          if (focusedInput == this._fieldOne ||
              focusedInput == this._fieldTwo ||
              focusedInput == this._fieldThree ||
              focusedInput == this._fieldAMPM)
            return focusedInput;
          return this._lastFocusedField || this._fieldOne;
        </getter>
      </property>

      <property name="dateValue" onget="return new Date(this._dateValue);">
        <setter>
          <![CDATA[
            if (!(val instanceof Date))
              throw "Invalid Date";

            this._setValueNoSync(val);
            if (this.attachedControl)
              this.attachedControl._setValueNoSync(val);
            return val;
          ]]>
        </setter>
      </property>

      <property name="readOnly" onset="if (val) this.setAttribute('readonly', 'true');
                                       else this.removeAttribute('readonly'); return val;"
                                onget="return this.getAttribute('readonly') == 'true';"/>

      <method name="_fireEvent">
        <parameter name="aEventName"/>
        <parameter name="aTarget"/>
        <body>
          var event = document.createEvent("Events");
          event.initEvent(aEventName, true, true);
          return !aTarget.dispatchEvent(event);
        </body>
      </method>

      <method name="_setValueOnChange">
        <parameter name="aField"/>
        <body>
          <![CDATA[
            if (!this._hasEntry)
              return;

            if (aField == this._fieldOne ||
                aField == this._fieldTwo ||
                aField == this._fieldThree) {
              var value = Number(aField.value);
              if (isNaN(value))
                value = 0;

              value = this._constrainValue(aField, value, true);
              this._setFieldValue(aField, value);
            }
          ]]>
        </body>
      </method>

      <method name="_init">
        <body/>
      </method>

      <constructor>
        this._init();

        var cval = this.getAttribute("value");
        if (cval) {
          try {
            this.value = cval;
            return;
          } catch (ex) { }
        }
        this.dateValue = new Date();
      </constructor>

      <destructor>
        if (this.attachedControl) {
          this.attachedControl.attachedControl = null;
          this.attachedControl = null;
        }
      </destructor>

    </implementation>

    <handlers>
      <handler event="focus" phase="capturing">
        <![CDATA[
          var target = event.originalTarget;
          if (target == this._fieldOne ||
              target == this._fieldTwo ||
              target == this._fieldThree ||
              target == this._fieldAMPM)
            this._lastFocusedField = target;
        ]]>
      </handler>

      <handler event="keypress">
        <![CDATA[
          if (this._hasEntry && event.charCode &&
              this._currentField != this._fieldAMPM &&
	      ! (event.altKey || event.ctrlKey || event.metaKey) &&
              (event.charCode < 48 || event.charCode > 57))
            event.preventDefault();
        ]]>
      </handler>

      <handler event="keypress" keycode="VK_UP">
        if (this._hasEntry)
          this._increaseOrDecrease(1);
      </handler>
      <handler event="keypress" keycode="VK_DOWN">
        if (this._hasEntry)
          this._increaseOrDecrease(-1);
      </handler>

      <handler event="input">
        this._valueEntered = true;
      </handler>

      <handler event="change">
        this._setValueOnChange(event.originalTarget);
      </handler>
    </handlers>

  </binding>

  <binding id="timepicker"
           extends="chrome://global/content/bindings/datetimepicker.xml#datetimepicker-base">

    <implementation>
      <field name="is24HourClock">false</field>
      <field name="hourLeadingZero">false</field>
      <field name="minuteLeadingZero">true</field>
      <field name="secondLeadingZero">true</field>
      <field name="amIndicator">"AM"</field>
      <field name="pmIndicator">"PM"</field>

      <field name="hourField">null</field>
      <field name="minuteField">null</field>
      <field name="secondField">null</field>

      <property name="value">
        <getter>
          <![CDATA[
            var minute = this._dateValue.getMinutes();
            if (minute < 10)
              minute = "0" + minute;

            var second = this._dateValue.getSeconds();
            if (second < 10)
              second = "0" + second;
            return this._dateValue.getHours() + ":" + minute + ":" + second;
          ]]>
        </getter>
        <setter>
          <![CDATA[
            var items = val.match(/^([0-9]{1,2})\:([0-9]{1,2})\:?([0-9]{1,2})?$/);
            if (!items)
              throw "Invalid Time";

            var dt = this.dateValue;
            dt.setHours(items[1]);
            dt.setMinutes(items[2]);
            dt.setSeconds(items[3] ? items[3] : 0);
            this.dateValue = dt;
            return val;
          ]]>
        </setter>
      </property>
      <property name="hour" onget="return this._dateValue.getHours();">
        <setter>
          <![CDATA[
            var valnum = Number(val);
            if (isNaN(valnum) || valnum < 0 || valnum > 23)
              throw "Invalid Hour";
            this._setFieldValue(this.hourField, valnum);
            return val;
          ]]>
        </setter>
      </property>
      <property name="minute" onget="return this._dateValue.getMinutes();">
        <setter>
          <![CDATA[
            var valnum = Number(val);
            if (isNaN(valnum) || valnum < 0 || valnum > 59)
              throw "Invalid Minute";
            this._setFieldValue(this.minuteField, valnum);
            return val;
          ]]>
        </setter>
      </property>
      <property name="second" onget="return this._dateValue.getSeconds();">
        <setter>
          <![CDATA[
            var valnum = Number(val);
            if (isNaN(valnum) || valnum < 0 || valnum > 59)
              throw "Invalid Second";
            this._setFieldValue(this.secondField, valnum);
            return val;
          ]]>
        </setter>
      </property>
      <property name="isPM">
        <getter>
          <![CDATA[
            return (this.hour >= 12);
          ]]>
        </getter>
        <setter>
          <![CDATA[
            if (val) {
              if (this.hour < 12)
                this.hour += 12;
            }
            else if (this.hour >= 12)
              this.hour -= 12;
            return val;
          ]]>
        </setter>
      </property>
      <property name="hideSeconds">
        <getter>
          return (this.getAttribute("hideseconds") == "true");
        </getter>
        <setter>
          if (val)
            this.setAttribute("hideseconds", "true");
          else
            this.removeAttribute("hideseconds");
          if (this.secondField)
            this.secondField.parentNode.collapsed = val;
          this._separatorSecond.collapsed = val;
          return val;
        </setter>
      </property>
      <property name="increment">
        <getter>
          <![CDATA[
            var increment = this.getAttribute("increment");
            increment = Number(increment);
            if (isNaN(increment) || increment <= 0 || increment >= 60)
              return 1;
            return increment;
          ]]>
        </getter>
        <setter>
          <![CDATA[
            if (typeof val == "number")
              this.setAttribute("increment", val);
            return val;
          ]]>
        </setter>
      </property>

      <method name="_setValueNoSync">
        <parameter name="aValue"/>
        <body>
          <![CDATA[
            var dt = new Date(aValue);
            if (!isNaN(dt)) {
              this._dateValue = dt;
              this.setAttribute("value", this.value);
              this._updateUI(this.hourField, this.hour);
              this._updateUI(this.minuteField, this.minute);
              this._updateUI(this.secondField, this.second);
            }
          ]]>
        </body>
      </method>
      <method name="_increaseOrDecrease">
        <parameter name="aDir"/>
        <body>
          <![CDATA[
            if (this.disabled || this.readOnly)
              return;

            var field = this._currentField;
            if (this._valueEntered)
              this._setValueOnChange(field);

            if (field == this._fieldAMPM) {
              this.isPM = !this.isPM;
              this._fireEvent("change", this);
            }
            else {
              var oldval;
              var change = aDir;
              if (field == this.hourField) {
                oldval = this.hour;
              }
              else if (field == this.minuteField) {
                oldval = this.minute;
                change *= this.increment;
              }
              else if (field == this.secondField) {
                oldval = this.second;
              }

              var newval = this._constrainValue(field, oldval + change, false);

              if (field == this.hourField)
                this.hour = newval;
              else if (field == this.minuteField)
                this.minute = newval;
              else if (field == this.secondField)
                this.second = newval;

              if (oldval != newval)
                this._fireEvent("change", this);
            }
            field.select();
          ]]>
        </body>
      </method>
      <method name="_setFieldValue">
        <parameter name="aField"/>
        <parameter name="aValue"/>
        <body>
          <![CDATA[
            if (aField == this.hourField)
              this._dateValue.setHours(aValue);
            else if (aField == this.minuteField)
              this._dateValue.setMinutes(aValue);
            else if (aField == this.secondField)
              this._dateValue.setSeconds(aValue);

            this.setAttribute("value", this.value);
            this._updateUI(aField, aValue);

            if (this.attachedControl)
              this.attachedControl._setValueNoSync(this._dateValue);
          ]]>
        </body>
      </method>
      <method name="_updateUI">
        <parameter name="aField"/>
        <parameter name="aValue"/>
        <body>
          <![CDATA[
            this._valueEntered = false;

            var prependZero = false;
            if (aField == this.hourField) {
              prependZero = this.hourLeadingZero;
              if (!this.is24HourClock) {
                if (aValue >= 12) {
                  if (aValue > 12)
                    aValue -= 12;
                  this._fieldAMPM.value = this.pmIndicator;
                }
                else {
                  if (aValue == 0)
                    aValue = 12;
                  this._fieldAMPM.value = this.amIndicator;
                }
              }
            }
            else if (aField == this.minuteField) {
              prependZero = this.minuteLeadingZero;
            }
            else if (aField == this.secondField) {
              prependZero = this.secondLeadingZero;
            }

            if (prependZero && aValue < 10)
              aField.value = "0" + aValue;
            else
              aField.value = aValue;
          ]]>
        </body>
      </method>
      <method name="_constrainValue">
        <parameter name="aField"/>
        <parameter name="aValue"/>
        <parameter name="aNoWrap"/>
        <body>
          <![CDATA[
            // aNoWrap is true when the user entered a value, so just
            // constrain within limits. If false, the value is being
            // incremented or decremented, so wrap around values
            var max = (aField == this.hourField) ? 24 : 60;
            if (aValue < 0)
              return aNoWrap ? 0 : max + aValue;
            if (aValue >= max)
              return aNoWrap ? max - 1 : aValue - max;
            return aValue;
          ]]>
        </body>
      </method>
      <method name="_init">
        <body>
          <![CDATA[
            this.hourField = this._fieldOne;
            this.minuteField = this._fieldTwo;
            this.secondField = this._fieldThree;

            var numberOrder = /^(\D*)\s*(\d+)(\D*)(\d+)(\D*)(\d+)\s*(\D*)$/;

            var locale = Intl.DateTimeFormat().resolvedOptions().locale + "-u-ca-gregory-nu-latn";

            var pmTime = new Date(2000, 0, 1, 16, 7, 9).toLocaleTimeString(locale);
            var numberFields = pmTime.match(numberOrder);
            if (numberFields) {
              this._separatorFirst.value = numberFields[3];
              this._separatorSecond.value = numberFields[5];
              if (Number(numberFields[2]) > 12)
                this.is24HourClock = true;
              else
                this.pmIndicator = numberFields[1] || numberFields[7];
            }

            var amTime = new Date(2000, 0, 1, 1, 7, 9).toLocaleTimeString(locale);
            numberFields = amTime.match(numberOrder);
            if (numberFields) {
              this.hourLeadingZero = (numberFields[2].length > 1);
              this.minuteLeadingZero = (numberFields[4].length > 1);
              this.secondLeadingZero = (numberFields[6].length > 1);

              if (!this.is24HourClock) {
                this.amIndicator = numberFields[1] || numberFields[7];
                if (numberFields[1]) {
                  var mfield = this._fieldAMPM.parentNode;
                  var mcontainer = mfield.parentNode;
                  mcontainer.insertBefore(mfield, mcontainer.firstChild);
                }
                var size = (numberFields[1] || numberFields[7]).length;
                if (this.pmIndicator.length > size)
                  size = this.pmIndicator.length;
                this._fieldAMPM.size = size;
                this._fieldAMPM.maxLength = size;
              }
              else {
                this._fieldAMPM.parentNode.collapsed = true;
              }
            }

            this.hideSeconds = this.hideSeconds;
          ]]>
        </body>
      </method>
    </implementation>

    <handlers>
      <handler event="keypress">
        <![CDATA[
          // just allow any printable character to switch the AM/PM state
          if (event.charCode && !this.disabled && !this.readOnly &&
              this._currentField == this._fieldAMPM) {
            this.isPM = !this.isPM;
            this._fieldAMPM.select();
            this._fireEvent("change", this);
            event.preventDefault();
          }
        ]]>
      </handler>
    </handlers>

  </binding>

  <binding id="datepicker"
           extends="chrome://global/content/bindings/datetimepicker.xml#datetimepicker-base">

    <implementation>
      <field name="yearLeadingZero">false</field>
      <field name="monthLeadingZero">true</field>
      <field name="dateLeadingZero">true</field>

      <field name="yearField"/>
      <field name="monthField"/>
      <field name="dateField"/>

      <property name="value">
        <getter>
          <![CDATA[
            var month = this._dateValue.getMonth();
            month = (month < 9) ? month = "0" + ++month : month + 1;

            var date = this._dateValue.getDate();
            if (date < 10)
              date = "0" + date;
            return this._dateValue.getFullYear() + "-" + month + "-" + date;
          ]]>

        </getter>
        <setter>
          <![CDATA[
            var results = val.match(/^([0-9]{1,4})\-([0-9]{1,2})\-([0-9]{1,2})$/);
            if (!results)
              throw "Invalid Date";

            this.dateValue = new Date(results[1] + "/" + results[2] + "/" + results[3]);
            this.setAttribute("value", this.value);
            return val;
          ]]>
        </setter>
      </property>
      <property name="year" onget="return this._dateValue.getFullYear();">
        <setter>
          <![CDATA[
            var valnum = Number(val);
            if (isNaN(valnum) || valnum < 1 || valnum > 9999)
              throw "Invalid Year";
            this._setFieldValue(this.yearField, valnum);
            return val;
          ]]>
        </setter>
      </property>
      <property name="month" onget="return this._dateValue.getMonth();">
        <setter>
          <![CDATA[
            var valnum = Number(val);
            if (isNaN(valnum) || valnum < 0 || valnum > 11)
              throw "Invalid Month";
            this._setFieldValue(this.monthField, valnum);
            return val;
          ]]>
        </setter>
      </property>
      <property name="date" onget="return this._dateValue.getDate();">
        <setter>
          <![CDATA[
            var valnum = Number(val);
            if (isNaN(valnum) || valnum < 1 || valnum > 31)
              throw "Invalid Date";
            this._setFieldValue(this.dateField, valnum);
            return val;
          ]]>
        </setter>
      </property>
      <property name="open" onget="return false;" onset="return val;"/>

      <property name="displayedMonth" onget="return this.month;"
                onset="this.month = val; return val;"/>
      <property name="displayedYear" onget="return this.year;"
                onset="this.year = val; return val;"/>

      <method name="_setValueNoSync">
        <parameter name="aValue"/>
        <body>
          <![CDATA[
            var dt = new Date(aValue);
            if (!isNaN(dt)) {
              this._dateValue = dt;
              this.setAttribute("value", this.value);
              this._updateUI(this.yearField, this.year);
              this._updateUI(this.monthField, this.month);
              this._updateUI(this.dateField, this.date);
            }
          ]]>
        </body>
      </method>
      <method name="_increaseOrDecrease">
        <parameter name="aDir"/>
        <body>
          <![CDATA[
            if (this.disabled || this.readOnly)
              return;

            var field = this._currentField;
            if (this._valueEntered)
              this._setValueOnChange(field);

            var oldval;
            if (field == this.yearField)
              oldval = this.year;
            else if (field == this.monthField)
              oldval = this.month;
            else if (field == this.dateField)
              oldval = this.date;

            var newval = this._constrainValue(field, oldval + aDir, false);

            if (field == this.yearField)
              this.year = newval;
            else if (field == this.monthField)
              this.month = newval;
            else if (field == this.dateField)
              this.date = newval;

            if (oldval != newval)
              this._fireEvent("change", this);
            field.select();
          ]]>
        </body>
      </method>
      <method name="_setFieldValue">
        <parameter name="aField"/>
        <parameter name="aValue"/>
        <body>
          <![CDATA[
            if (aField == this.yearField) {
              let oldDate = this.date;
              this._dateValue.setFullYear(aValue);
              if (oldDate != this.date) {
                this._dateValue.setDate(0);
                this._updateUI(this.dateField, this.date);
              }
            }
            else if (aField == this.monthField) {
              let oldDate = this.date;
              this._dateValue.setMonth(aValue);
              if (oldDate != this.date) {
                this._dateValue.setDate(0);
                this._updateUI(this.dateField, this.date);
              }
            }
            else if (aField == this.dateField) {
              this._dateValue.setDate(aValue);
            }

            this.setAttribute("value", this.value);
            this._updateUI(aField, aValue);

            if (this.attachedControl)
              this.attachedControl._setValueNoSync(this._dateValue);
          ]]>
        </body>
      </method>
      <method name="_updateUI">
        <parameter name="aField"/>
        <parameter name="aValue"/>
        <body>
          <![CDATA[
            this._valueEntered = false;

            var prependZero = false;
            if (aField == this.yearField) {
              if (this.yearLeadingZero) {
                aField.value = ("000" + aValue).slice(-4);
                return;
              }
            }
            else if (aField == this.monthField) {
              aValue++;
              prependZero = this.monthLeadingZero;
            }
            else if (aField == this.dateField) {
              prependZero = this.dateLeadingZero;
            }
            if (prependZero && aValue < 10)
              aField.value = "0" + aValue;
            else
              aField.value = aValue;
          ]]>
        </body>
      </method>
      <method name="_constrainValue">
        <parameter name="aField"/>
        <parameter name="aValue"/>
        <parameter name="aNoWrap"/>
        <body>
          <![CDATA[
            // the month will be 1 to 12 if entered by the user, so subtract 1
            if (aNoWrap && aField == this.monthField)
              aValue--;

            if (aField == this.dateField) {
              if (aValue < 1)
                return new Date(this.year, this.month + 1, 0).getDate();

              var currentMonth = this.month;
              var dt = new Date(this.year, currentMonth, aValue);
              return (dt.getMonth() != currentMonth ? 1 : aValue);
            }
            var min = (aField == this.monthField) ? 0 : 1;
            var max = (aField == this.monthField) ? 11 : 9999;
            if (aValue < min)
              return aNoWrap ? min : max;
            if (aValue > max)
              return aNoWrap ? max : min;
            return aValue;
          ]]>
        </body>
      </method>
      <method name="_init">
        <body>
          <![CDATA[
            // We'll default to YYYY/MM/DD to start.
            var yfield = "input-one";
            var mfield = "input-two";
            var dfield = "input-three";
            var twoDigitYear = false;
            this.yearLeadingZero = true;
            this.monthLeadingZero = true;
            this.dateLeadingZero = true;

            var numberOrder = /^(\D*)\s*(\d+)(\D*)(\d+)(\D*)(\d+)\s*(\D*)$/;

            var locale = Intl.DateTimeFormat().resolvedOptions().locale + "-u-ca-gregory-nu-latn";

            var dt = new Date(2002, 9, 4).toLocaleDateString(locale);
            var numberFields = dt.match(numberOrder);
            if (numberFields) {
              this._separatorFirst.value = numberFields[3];
              this._separatorSecond.value = numberFields[5];

              var yi = 2, mi = 4, di = 6;

              function fieldForNumber(i) {
                if (i == 2)
                  return "input-one";
                if (i == 4)
                  return "input-two";
                return "input-three";
              }

              for (var i = 1; i < numberFields.length; i++) {
                switch (Number(numberFields[i])) {
                  case 2:
                    twoDigitYear = true; // fall through
                  case 2002:
                    yi = i;
                    yfield = fieldForNumber(i);
                    break;
                  case 9, 10:
                    mi = i;
                    mfield = fieldForNumber(i);
                    break;
                  case 4:
                    di = i;
                    dfield = fieldForNumber(i);
                    break;
                }
              }

              this.yearLeadingZero = (numberFields[yi].length > 1);
              this.monthLeadingZero = (numberFields[mi].length > 1);
              this.dateLeadingZero = (numberFields[di].length > 1);
            }

            this.yearField = document.getAnonymousElementByAttribute(this, "anonid", yfield);
            if (!twoDigitYear)
              this.yearField.parentNode.classList.add("datetimepicker-input-subbox", "datetimepicker-year");
            this.monthField = document.getAnonymousElementByAttribute(this, "anonid", mfield);
            this.dateField = document.getAnonymousElementByAttribute(this, "anonid", dfield);

            this._fieldAMPM.parentNode.collapsed = true;
            this.yearField.size = twoDigitYear ? 2 : 4;
            this.yearField.maxLength = twoDigitYear ? 2 : 4;
          ]]>
        </body>
      </method>
    </implementation>

  </binding>

  <binding id="datepicker-grid"
           extends="chrome://global/content/bindings/datetimepicker.xml#datepicker">

    <content>
      <vbox class="datepicker-mainbox"
            xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
        <hbox class="datepicker-monthbox" align="center">
          <button class="datepicker-previous datepicker-button" type="repeat"
                  xbl:inherits="disabled"
                  oncommand="document.getBindingParent(this)._increaseOrDecreaseMonth(-1);"/>
          <spacer flex="1"/>
          <deck anonid="monthlabeldeck">
            <label class="datepicker-gridlabel" value=""/>
            <label class="datepicker-gridlabel" value=""/>
            <label class="datepicker-gridlabel" value=""/>
            <label class="datepicker-gridlabel" value=""/>
            <label class="datepicker-gridlabel" value=""/>
            <label class="datepicker-gridlabel" value=""/>
            <label class="datepicker-gridlabel" value=""/>
            <label class="datepicker-gridlabel" value=""/>
            <label class="datepicker-gridlabel" value=""/>
            <label class="datepicker-gridlabel" value=""/>
            <label class="datepicker-gridlabel" value=""/>
            <label class="datepicker-gridlabel" value=""/>
          </deck>
          <label anonid="yearlabel" class="datepicker-gridlabel"/>
          <spacer flex="1"/>
          <button class="datepicker-next datepicker-button" type="repeat"
                  xbl:inherits="disabled"
                  oncommand="document.getBindingParent(this)._increaseOrDecreaseMonth(1);"/>
        </hbox>
        <grid class="datepicker-grid" role="grid">
          <columns>
            <column class="datepicker-gridrow" flex="1"/>
            <column class="datepicker-gridrow" flex="1"/>
            <column class="datepicker-gridrow" flex="1"/>
            <column class="datepicker-gridrow" flex="1"/>
            <column class="datepicker-gridrow" flex="1"/>
            <column class="datepicker-gridrow" flex="1"/>
            <column class="datepicker-gridrow" flex="1"/>
          </columns>
          <rows anonid="datebox">
            <row anonid="dayofweekbox">
              <label class="datepicker-weeklabel" role="columnheader"/>
              <label class="datepicker-weeklabel" role="columnheader"/>
              <label class="datepicker-weeklabel" role="columnheader"/>
              <label class="datepicker-weeklabel" role="columnheader"/>
              <label class="datepicker-weeklabel" role="columnheader"/>
              <label class="datepicker-weeklabel" role="columnheader"/>
              <label class="datepicker-weeklabel" role="columnheader"/>
            </row>
            <row>
              <label class="datepicker-gridlabel" role="gridcell"/>
              <label class="datepicker-gridlabel" role="gridcell"/>
              <label class="datepicker-gridlabel" role="gridcell"/>
              <label class="datepicker-gridlabel" role="gridcell"/>
              <label class="datepicker-gridlabel" role="gridcell"/>
              <label class="datepicker-gridlabel" role="gridcell"/>
              <label class="datepicker-gridlabel" role="gridcell"/>
            </row>
            <row>
              <label class="datepicker-gridlabel" role="gridcell"/>
              <label class="datepicker-gridlabel" role="gridcell"/>
              <label class="datepicker-gridlabel" role="gridcell"/>
              <label class="datepicker-gridlabel" role="gridcell"/>
              <label class="datepicker-gridlabel" role="gridcell"/>
              <label class="datepicker-gridlabel" role="gridcell"/>
              <label class="datepicker-gridlabel" role="gridcell"/>
            </row>
            <row>
              <label class="datepicker-gridlabel" role="gridcell"/>
              <label class="datepicker-gridlabel" role="gridcell"/>
              <label class="datepicker-gridlabel" role="gridcell"/>
              <label class="datepicker-gridlabel" role="gridcell"/>
              <label class="datepicker-gridlabel" role="gridcell"/>
              <label class="datepicker-gridlabel" role="gridcell"/>
              <label class="datepicker-gridlabel" role="gridcell"/>
            </row>
            <row>
              <label class="datepicker-gridlabel" role="gridcell"/>
              <label class="datepicker-gridlabel" role="gridcell"/>
              <label class="datepicker-gridlabel" role="gridcell"/>
              <label class="datepicker-gridlabel" role="gridcell"/>
              <label class="datepicker-gridlabel" role="gridcell"/>
              <label class="datepicker-gridlabel" role="gridcell"/>
              <label class="datepicker-gridlabel" role="gridcell"/>
            </row>
            <row>
              <label class="datepicker-gridlabel" role="gridcell"/>
              <label class="datepicker-gridlabel" role="gridcell"/>
              <label class="datepicker-gridlabel" role="gridcell"/>
              <label class="datepicker-gridlabel" role="gridcell"/>
              <label class="datepicker-gridlabel" role="gridcell"/>
              <label class="datepicker-gridlabel" role="gridcell"/>
              <label class="datepicker-gridlabel" role="gridcell"/>
            </row>
            <row>
              <label class="datepicker-gridlabel" role="gridcell"/>
              <label class="datepicker-gridlabel" role="gridcell"/>
              <label class="datepicker-gridlabel" role="gridcell"/>
              <label class="datepicker-gridlabel" role="gridcell"/>
              <label class="datepicker-gridlabel" role="gridcell"/>
              <label class="datepicker-gridlabel" role="gridcell"/>
              <label class="datepicker-gridlabel" role="gridcell"/>
            </row>
          </rows>
        </grid>
      </vbox>
    </content>

    <implementation>
      <field name="_hasEntry">false</field>
      <field name="_weekStart">&firstdayofweek.default;</field>
      <field name="_displayedDate">null</field>
      <field name="_todayItem">null</field>

      <field name="yearField">
        document.getAnonymousElementByAttribute(this, "anonid", "yearlabel");
      </field>
      <field name="monthField">
        document.getAnonymousElementByAttribute(this, "anonid", "monthlabeldeck");
      </field>
      <field name="dateField">
        document.getAnonymousElementByAttribute(this, "anonid", "datebox");
      </field>

      <field name="_selectedItem">null</field>

      <property name="selectedItem" onget="return this._selectedItem">
        <setter>
          <![CDATA[
            if (!val.value)
              return val;
            if (val.parentNode.parentNode != this.dateField)
              return val;

            if (this._selectedItem)
              this._selectedItem.removeAttribute("selected");
            this._selectedItem = val;
            val.setAttribute("selected", "true");
            this._displayedDate.setDate(val.value);
            return val;
          ]]>
        </setter>
      </property>

      <property name="displayedMonth">
        <getter>
          return this._displayedDate.getMonth();
        </getter>
        <setter>
          this._updateUI(this.monthField, val, true);
          return val;
        </setter>
      </property>
      <property name="displayedYear">
        <getter>
          return this._displayedDate.getFullYear();
        </getter>
        <setter>
          this._updateUI(this.yearField, val, true);
          return val;
        </setter>
      </property>

      <method name="_init">
        <body>
          <![CDATA[
            var locale = Intl.DateTimeFormat().resolvedOptions().locale + "-u-ca-gregory";
            var dtfMonth = Intl.DateTimeFormat(locale, {month: "long"});
            var dtfWeekday = Intl.DateTimeFormat(locale, {weekday: "narrow"});

            var monthLabel = this.monthField.firstChild;
            var tempDate = new Date(2005, 0, 1);
            for (var month = 0; month < 12; month++) {
              tempDate.setMonth(month);
              monthLabel.setAttribute("value", dtfMonth.format(tempDate));
              monthLabel = monthLabel.nextSibling;
            }

            var fdow = Number(this.getAttribute("firstdayofweek"));
            if (!isNaN(fdow) && fdow >= 0 && fdow <= 6)
              this._weekStart = fdow;

            var weekbox = document.getAnonymousElementByAttribute(this, "anonid", "dayofweekbox").childNodes;
            var date = new Date();
            date.setDate(date.getDate() - (date.getDay() - this._weekStart));
            for (var i = 0; i < weekbox.length; i++) {
              weekbox[i].value = dtfWeekday.format(date);
              date.setDate(date.getDate() + 1);
            }
          ]]>
        </body>
      </method>
      <method name="_setValueNoSync">
        <parameter name="aValue"/>
        <body>
          <![CDATA[
            var dt = new Date(aValue);
            if (!isNaN(dt)) {
              this._dateValue = dt;
              this.setAttribute("value", this.value);
              this._updateUI();
            }
          ]]>
        </body>
      </method>
      <method name="_updateUI">
        <parameter name="aField"/>
        <parameter name="aValue"/>
        <parameter name="aCheckMonth"/>
        <body>
          <![CDATA[
            var date;
            var currentMonth;
            if (aCheckMonth) {
              if (!this._displayedDate)
                this._displayedDate = this.dateValue;

              var expectedMonth = aValue;
              if (aField == this.monthField) {
                this._displayedDate.setMonth(aValue);
              }
              else {
                expectedMonth = this._displayedDate.getMonth();
                this._displayedDate.setFullYear(aValue);
              }

              if (expectedMonth != -1 && expectedMonth != 12 &&
                  expectedMonth != this._displayedDate.getMonth()) {
                // If the month isn't what was expected, then the month overflowed.
                // Setting the date to 0 will go back to the last day of the right month.
                this._displayedDate.setDate(0);
              }

              date = new Date(this._displayedDate);
              currentMonth = this._displayedDate.getMonth();
            }
            else {
              var samemonth = (this._displayedDate &&
                               this._displayedDate.getMonth() == this.month &&
                               this._displayedDate.getFullYear() == this.year);
              if (samemonth) {
                var items = this.dateField.getElementsByAttribute("value", this.date);
                if (items.length)
                  this.selectedItem = items[0];
                return;
              }

              date = this.dateValue;
              this._displayedDate = new Date(date);
              currentMonth = this.month;
            }

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

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

            // Update the month and year title
            this.monthField.selectedIndex = currentMonth;
            this.yearField.setAttribute("value", date.getFullYear());

            date.setDate(1);
            var firstWeekday = (7 + date.getDay() - this._weekStart) % 7;
            date.setDate(date.getDate() - firstWeekday);

            var today = new Date();
            var datebox = this.dateField;
            for (var k = 1; k < datebox.childNodes.length; k++) {
              var row = datebox.childNodes[k];
              for (var i = 0; i < 7; i++) {
                var item = row.childNodes[i];

                if (currentMonth == date.getMonth()) {
                  item.value = date.getDate();

                  // highlight today
                  if (this._isSameDay(today, date)) {
                    this._todayItem = item;
                    item.setAttribute("today", "true");
                  }

                  // highlight the selected date
                  if (this._isSameDay(this._dateValue, date)) {
                    this._selectedItem = item;
                    item.setAttribute("selected", "true");
                  }
                }
                else {
                  item.value = "";
                }

                date.setDate(date.getDate() + 1);
              }
            }

            this._fireEvent("monthchange", this);
            if (this.hasAttribute("monthchange")) {
              var fn = new Function("event", aTarget.getAttribute("onmonthchange"));
              fn.call(aTarget, event);
            }
          ]]>
        </body>
      </method>
      <method name="_increaseOrDecreaseDateFromEvent">
        <parameter name="aEvent"/>
        <parameter name="aDiff"/>
        <body>
          <![CDATA[
            if (aEvent.originalTarget == this && !this.disabled && !this.readOnly) {
              var newdate = this.dateValue;
              newdate.setDate(newdate.getDate() + aDiff);
              this.dateValue = newdate;
              this._fireEvent("change", this);
            }
            aEvent.stopPropagation();
            aEvent.preventDefault();
          ]]>
        </body>
      </method>
      <method name="_increaseOrDecreaseMonth">
        <parameter name="aDir"/>
        <body>
          <![CDATA[
            if (!this.disabled) {
              var month = this._displayedDate ? this._displayedDate.getMonth() :
                                                this.month;
              this._updateUI(this.monthField, month + aDir, true);
            }
          ]]>
        </body>
      </method>
      <method name="_isSameDay">
        <parameter name="aDate1"/>
        <parameter name="aDate2"/>
        <body>
          <![CDATA[
            return (aDate1 && aDate2 &&
                    aDate1.getDate() == aDate2.getDate() &&
                    aDate1.getMonth() == aDate2.getMonth() &&
                    aDate1.getFullYear() == aDate2.getFullYear());
          ]]>
        </body>
      </method>

    </implementation>

    <handlers>
      <handler event="click">
        <![CDATA[
          if (event.button != 0 || this.disabled || this.readOnly)
            return;

          var target = event.originalTarget;
          if (target.classList.contains("datepicker-gridlabel") &&
              target != this.selectedItem) {
            this.selectedItem = target;
            this._dateValue = new Date(this._displayedDate);
            if (this.attachedControl)
              this.attachedControl._setValueNoSync(this._dateValue);
            this._fireEvent("change", this);

            if (this.attachedControl && "open" in this.attachedControl)
              this.attachedControl.open = false; // close the popup
          }
        ]]>
      </handler>
      <handler event="MozMousePixelScroll" preventdefault="true"/>
      <handler event="DOMMouseScroll" preventdefault="true">
        <![CDATA[
          this._increaseOrDecreaseMonth(event.detail < 0 ? -1 : 1);
        ]]>
      </handler>
      <handler event="keypress" keycode="VK_LEFT"
               action="this._increaseOrDecreaseDateFromEvent(event, -1);"/>
      <handler event="keypress" keycode="VK_RIGHT"
               action="this._increaseOrDecreaseDateFromEvent(event, 1);"/>
      <handler event="keypress" keycode="VK_UP"
               action="this._increaseOrDecreaseDateFromEvent(event, -7);"/>
      <handler event="keypress" keycode="VK_DOWN"
               action="this._increaseOrDecreaseDateFromEvent(event, 7);"/>
      <handler event="keypress" keycode="VK_PAGE_UP" preventdefault="true"
               action="this._increaseOrDecreaseMonth(-1);"/>
      <handler event="keypress" keycode="VK_PAGE_DOWN" preventdefault="true"
               action="this._increaseOrDecreaseMonth(1);"/>
    </handlers>
  </binding>

  <binding id="datepicker-popup" display="xul:menu"
           extends="chrome://global/content/bindings/datetimepicker.xml#datepicker">
    <content align="center">
      <xul:hbox class="textbox-input-box datetimepicker-input-box" align="center"
                allowevents="true" xbl:inherits="context,disabled,readonly">
        <xul:hbox class="datetimepicker-input-subbox" align="baseline">
          <html:input class="datetimepicker-input textbox-input" anonid="input-one"
                      size="2" maxlength="2"
                      xbl:inherits="disabled,readonly"/>
        </xul:hbox>
        <xul:label anonid="sep-first" class="datetimepicker-separator" value=":"/>
        <xul:hbox class="datetimepicker-input-subbox" align="baseline">
          <html:input class="datetimepicker-input textbox-input" anonid="input-two"
                      size="2" maxlength="2"
                      xbl:inherits="disabled,readonly"/>
        </xul:hbox>
        <xul:label anonid="sep-second" class="datetimepicker-separator" value=":"/>
        <xul:hbox class="datetimepicker-input-subbox" align="center">
          <html:input class="datetimepicker-input textbox-input" anonid="input-three"
                      size="2" maxlength="2"
                      xbl:inherits="disabled,readonly"/>
        </xul:hbox>
        <xul:hbox class="datetimepicker-input-subbox" align="center">
          <html:input class="datetimepicker-input textbox-input" anonid="input-ampm"
                      size="2" maxlength="2"
                      xbl:inherits="disabled,readonly"/>
        </xul:hbox>
      </xul:hbox>
      <xul:spinbuttons anonid="buttons" xbl:inherits="disabled" allowevents="true"
                       onup="this.parentNode._increaseOrDecrease(1);"
                       ondown="this.parentNode._increaseOrDecrease(-1);"/>
      <xul:dropmarker class="datepicker-dropmarker" xbl:inherits="disabled"/>
      <xul:panel onpopupshown="this.firstChild.focus();" level="top">
        <xul:datepicker anonid="grid" type="grid" class="datepicker-popupgrid"
                        xbl:inherits="disabled,readonly,firstdayofweek"/>
      </xul:panel>
    </content>
    <implementation>
      <constructor>
        var grid = document.getAnonymousElementByAttribute(this, "anonid", "grid");
        this.attachedControl = grid;
        grid.attachedControl = this;
        grid._setValueNoSync(this._dateValue);
      </constructor>
      <property name="open" onget="return this.hasAttribute('open');">
        <setter>
          <![CDATA[
            if (this.boxObject instanceof MenuBoxObject)
              this.boxObject.openMenu(val);
            return val;
          ]]>
        </setter>
      </property>
      <property name="displayedMonth">
        <getter>
          return document.getAnonymousElementByAttribute(this, "anonid", "grid").displayedMonth;
        </getter>
        <setter>
          document.getAnonymousElementByAttribute(this, "anonid", "grid").displayedMonth = val;
          return val;
        </setter>
      </property>
      <property name="displayedYear">
        <getter>
          return document.getAnonymousElementByAttribute(this, "anonid", "grid").displayedYear;
        </getter>
        <setter>
          document.getAnonymousElementByAttribute(this, "anonid", "grid").displayedYear = val;
          return val;
        </setter>
      </property>
    </implementation>
  </binding>

</bindings>
