--- a/toolkit/content/widgets/datetimebox.xml
+++ b/toolkit/content/widgets/datetimebox.xml
@@ -84,27 +84,37 @@
break;
case "month":
fragment.appendChild(this.mMonthField);
break;
case "day":
fragment.appendChild(this.mDayField);
break;
default:
- // Remove bidirectional formatting characters, we'll handle
- // directions on our own.
- let value = this.stripDirFormattingChars(part.value);
let span = document.createElementNS(HTML_NS, "span");
- span.textContent = value;
+ span.textContent = part.value;
fragment.appendChild(span);
break;
}
});
- root.appendChild(fragment);
+ if (!this.mIsRTL) {
+ root.appendChild(fragment);
+ return;
+ }
+
+
+ // In RTL, create an extra span with unicode-bidi: plaintext, and move
+ // the numeric parts into that span. The data will be displayed
+ // according to the results from Intl.DateTimeFormat, formatted using
+ // the Unicode Bidi Algorithm.
+ let span = document.createElementNS(HTML_NS, "span");
+ span.style.unicodeBidi = "plaintext";
+ span.appendChild(fragment);
+ root.appendChild(span);
]]>
</body>
</method>
<method name="clearInputFields">
<parameter name="aFromInputElement"/>
<body>
<![CDATA[
@@ -605,26 +615,55 @@
fragment.appendChild(span);
fragment.appendChild(this.mMillisecField);
}
break;
case "dayPeriod":
fragment.appendChild(this.mDayPeriodField);
break;
default:
- // Remove bidirectional formatting characters, we'll handle
- // directions on our own.
- let value = this.stripDirFormattingChars(part.value);
let span = document.createElementNS(HTML_NS, "span");
- span.textContent = value;
+ span.textContent = part.value;
fragment.appendChild(span);
break;
}
});
+ if (!this.mIsRTL) {
+ root.appendChild(fragment);
+ return;
+ }
+
+ // In RTL, create an extra span with unicode-bidi: plaintext, and move
+ // the numeric parts into that span. The data will be displayed
+ // according to the results from Intl.DateTimeFormat, formatted using
+ // the Unicode Bidi Algorithm.
+ let span = document.createElementNS(HTML_NS, "span");
+ span.style.unicodeBidi = "plaintext";
+ fragment.insertBefore(span, this.mHourField);
+
+ // Move only the numeric parts with its literals and leave day period
+ // and other parts in RTL.
+ let node = this.mHourField;
+ let lastNode = this.mMinuteField;
+ if (this.mMillisecField) {
+ lastNode = this.mMillisecField
+ } else if (this.mSecondField) {
+ lastNode = this.mSecondField;
+ }
+
+ while (node) {
+ span.appendChild(node);
+
+ if (node == lastNode) {
+ break;
+ }
+ node = node.parentNode.nextSibling;
+ }
+
root.appendChild(fragment);
]]>
</body>
</method>
<method name="getStringsForLocale">
<parameter name="aLocales"/>
<body>
@@ -1100,17 +1139,17 @@
<binding id="datetime-input-base">
<resources>
<stylesheet src="chrome://global/content/textbox.css"/>
<stylesheet src="chrome://global/skin/textbox.css"/>
<stylesheet src="chrome://global/content/bindings/datetimebox.css"/>
</resources>
<content>
- <html:div class="datetime-input-box-wrapper"
+ <html:div class="datetime-input-box-wrapper" anonid="input-box-wrapper"
xbl:inherits="context,disabled,readonly">
<html:span class="datetime-input-edit-wrapper"
anonid="edit-wrapper">
<!-- Each of the date/time input types will append their input child
- elements here -->
</html:span>
<html:button class="datetime-reset-button" anonid="reset-button"
@@ -1120,16 +1159,30 @@
<implementation implements="nsIDateTimeInputArea">
<constructor>
<![CDATA[
this.DEBUG = false;
this.mInputElement = this.parentNode;
this.mLocales = window.getAppLocalesAsBCP47();
+ this.mIsRTL = false;
+ let intlUtils = window.intlUtils;
+ if (intlUtils) {
+ this.mIsRTL =
+ intlUtils.getLocaleInfo(this.mLocales).direction === "rtl";
+ }
+
+ if (this.mIsRTL) {
+ let inputBoxWrapper =
+ document.getAnonymousElementByAttribute(this, "anonid",
+ "input-box-wrapper");
+ inputBoxWrapper.dir = "rtl";
+ }
+
this.mMin = this.mInputElement.min;
this.mMax = this.mInputElement.max;
this.mStep = this.mInputElement.step;
this.mIsPickerOpen = false;
this.mResetButton =
document.getAnonymousElementByAttribute(this, "anonid", "reset-button");
this.mResetButton.style.visibility = "hidden";
@@ -1235,19 +1288,21 @@
<method name="focusInnerTextBox">
<body>
<![CDATA[
this.log("focusInnerTextBox");
let editRoot =
document.getAnonymousElementByAttribute(this, "anonid", "edit-wrapper");
- for (let child = editRoot.firstChild; child; child = child.nextSibling) {
- if (child instanceof HTMLInputElement) {
- child.focus();
+ // This gives us input fields in document order.
+ let inputFields = editRoot.getElementsByClassName("datetime-input");
+ for (let input of inputFields) {
+ if (!input.disabled) {
+ input.focus();
break;
}
}
]]>
</body>
</method>
<method name="blurInnerTextBox">
@@ -1274,37 +1329,83 @@
<parameter name="aValue"/>
<body>
<![CDATA[
this.setFieldsFromPicker(aValue);
]]>
</body>
</method>
+ <method name="advanceToPreviousField">
+ <body>
+ <![CDATA[
+ this.log("advanceToPreviousField");
+
+ let focusedInput = this.mLastFocusedField;
+ let editRoot =
+ document.getAnonymousElementByAttribute(this, "anonid", "edit-wrapper");
+
+ // This give us input fields in document order, and will move just
+ // like reverse tabbing.
+ let inputFields = editRoot.getElementsByClassName("datetime-input");
+ for (let i = 0; i < inputFields.length; i++) {
+ if (inputFields[i] == focusedInput) {
+ // Found our focus field, now find the previous available node for
+ // focusing.
+ if (i - 1 >= 0) {
+ let j = i - 1;
+ while (j >= 0) {
+ let node = inputFields[j];
+ if (!node.disabled) {
+ node.focus();
+ break;
+ }
+ j--;
+ }
+ } else {
+ this.setInputValueFromFields();
+ }
+ break;
+ }
+ }
+ ]]>
+ </body>
+ </method>
+
<method name="advanceToNextField">
- <parameter name="aReverse"/>
<body>
<![CDATA[
this.log("advanceToNextField");
let focusedInput = this.mLastFocusedField;
- let next = aReverse ? focusedInput.previousElementSibling
- : focusedInput.nextElementSibling;
- if (!next && !aReverse) {
- this.setInputValueFromFields();
- return;
- }
+ let editRoot =
+ document.getAnonymousElementByAttribute(this, "anonid", "edit-wrapper");
- while (next) {
- if (next.type == "text" && !next.disabled) {
- next.focus();
+ // This give us input fields in document order, and will move just
+ // like tabbing.
+ let inputFields = editRoot.getElementsByClassName("datetime-input");
+ for (let i = 0; i < inputFields.length; i++) {
+ if (inputFields[i] == focusedInput) {
+ // Found our focus field, now find the next available node for
+ // focusing.
+ if (i + 1 < inputFields.length) {
+ let j = i + 1;
+ while (j < inputFields.length) {
+ let node = inputFields[j];
+ if (!node.disabled) {
+ node.focus();
+ break;
+ }
+ j++;
+ }
+ } else {
+ this.setInputValueFromFields();
+ }
break;
}
- next = aReverse ? next.previousElementSibling
- : next.nextElementSibling;
}
]]>
</body>
</method>
<method name="setPickerState">
<parameter name="aIsOpen"/>
<body>
@@ -1325,21 +1426,20 @@
if (aName != "tabindex" && aName != "disabled" &&
aName != "readonly") {
return;
}
let editRoot =
document.getAnonymousElementByAttribute(this, "anonid", "edit-wrapper");
- for (let child = editRoot.firstChild; child; child = child.nextSibling) {
- if (child instanceof HTMLInputElement) {
- child.setAttribute(aName, aValue);
- }
- }
+ let inputFields = editRoot.getElementsByClassName("datetime-input");
+ Array.prototype.forEach.call(inputFields, input => {
+ input.setAttribute(aName, aValue);
+ });
]]>
</body>
</method>
<method name="removeEditAttribute">
<parameter name="aName"/>
<body>
<![CDATA[
@@ -1348,46 +1448,31 @@
if (aName != "tabindex" && aName != "disabled" &&
aName != "readonly") {
return;
}
let editRoot =
document.getAnonymousElementByAttribute(this, "anonid", "edit-wrapper");
- for (let child = editRoot.firstChild; child; child = child.nextSibling) {
- if (child instanceof HTMLInputElement) {
- child.removeAttribute(aName);
- }
- }
+ let inputFields = editRoot.getElementsByClassName("datetime-input");
+ Array.prototype.forEach.call(inputFields, input => {
+ input.removeAttribute(aName);
+ });
]]>
</body>
</method>
<method name="isEmpty">
<parameter name="aValue"/>
<body>
return (aValue == undefined || 0 === aValue.length);
</body>
</method>
- <method name="stripDirFormattingChars">
- <parameter name="aString"/>
- <body>
- <![CDATA[
- // Strip bidirectional formatting characters.
- // http://www.unicode.org/reports/tr9/#Directional_Formatting_Characters
- let str = aString.replace(
- /[\u200e\u200f\u202a\u202b\u202c\u202d\u202e]/g, "");
-
- return str;
- ]]>
- </body>
- </method>
-
<method name="getFieldValue">
<parameter name="aField"/>
<body>
<![CDATA[
if (!aField || !aField.classList.contains("numeric")) {
return undefined;
}
@@ -1572,19 +1657,23 @@
}
case "Backspace": {
let targetField = aEvent.originalTarget;
this.clearFieldValue(targetField);
this.setInputValueFromFields();
aEvent.preventDefault();
break;
}
- case "ArrowRight":
+ case "ArrowRight": {
+ this.advanceToNextField();
+ aEvent.preventDefault();
+ break;
+ }
case "ArrowLeft": {
- this.advanceToNextField(aEvent.key == "ArrowRight" ? false : true);
+ this.advanceToPreviousField();
aEvent.preventDefault();
break;
}
case "ArrowUp":
case "ArrowDown":
case "PageUp":
case "PageDown":
case "Home":