mirror of
https://repo.dactyloidae.xyz/Dactyloidae/UXP.git
synced 2026-09-29 11:57:32 +09:00
moebius#92: HTML - input - datetime
+ native in moebius: Bug 1317600: https://bugzilla.mozilla.org/show_bug.cgi?id=1317600 A note - not implemented: Bug 1282768: https://bugzilla.mozilla.org/show_bug.cgi?id=1282768 *.css: filter: url("chrome://global/skin/filters.svg#fill");, fill: Bug 1283385: https://bugzilla.mozilla.org/show_bug.cgi?id=1283385 Bug 1323109: https://bugzilla.mozilla.org/show_bug.cgi?id=1323109 Bug 1314544: https://bugzilla.mozilla.org/show_bug.cgi?id=1314544 Bug 1286182: https://bugzilla.mozilla.org/show_bug.cgi?id=1286182 Bug 1325922: https://bugzilla.mozilla.org/show_bug.cgi?id=1325922 A note - not implemented: Bug 1282768: https://bugzilla.mozilla.org/show_bug.cgi?id=1282768 *.css: filter: url("chrome://global/skin/filters.svg#fill");, fill: Bug 1320225: https://bugzilla.mozilla.org/show_bug.cgi?id=1320225 Bug 1341190: https://bugzilla.mozilla.org/show_bug.cgi?id=1341190
This commit is contained in:
parent
a807f7e6c0
commit
cac5c5adc7
34 changed files with 1267 additions and 248 deletions
|
|
@ -557,7 +557,7 @@ toolbar:not(#TabsToolbar) > #personal-bookmarks {
|
|||
transition: none;
|
||||
}
|
||||
|
||||
#DateTimePickerPanel {
|
||||
#DateTimePickerPanel[active="true"] {
|
||||
-moz-binding: url("chrome://global/content/bindings/datetimepopup.xml#datetime-popup");
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -160,8 +160,10 @@
|
|||
hidden="true"
|
||||
orient="vertical"
|
||||
noautofocus="true"
|
||||
noautohide="true"
|
||||
consumeoutsideclicks="false"
|
||||
level="parent">
|
||||
level="parent"
|
||||
tabspecific="true">
|
||||
<iframe id="dateTimePopupFrame"/>
|
||||
</panel>
|
||||
|
||||
|
|
|
|||
|
|
@ -543,9 +543,8 @@ GetDOMFileOrDirectoryPath(const OwningFileOrDirectory& aData,
|
|||
bool
|
||||
HTMLInputElement::ValueAsDateEnabled(JSContext* cx, JSObject* obj)
|
||||
{
|
||||
return Preferences::GetBool("dom.experimental_forms", false) ||
|
||||
Preferences::GetBool("dom.forms.datepicker", false) ||
|
||||
Preferences::GetBool("dom.forms.datetime", false);
|
||||
return IsExperimentalFormsEnabled() || IsDatePickerEnabled() ||
|
||||
IsInputDateTimeEnabled();
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
|
|
@ -628,7 +627,7 @@ HTMLInputElement::nsFilePickerShownCallback::Done(int16_t aResult)
|
|||
RefPtr<DispatchChangeEventCallback> dispatchChangeEventCallback =
|
||||
new DispatchChangeEventCallback(mInput);
|
||||
|
||||
if (Preferences::GetBool("dom.webkitBlink.dirPicker.enabled", false) &&
|
||||
if (IsWebkitDirPickerEnabled() &&
|
||||
mInput->HasAttr(kNameSpaceID_None, nsGkAtoms::webkitdirectory)) {
|
||||
ErrorResult error;
|
||||
GetFilesHelper* helper = mInput->GetOrCreateGetFilesHelper(true, error);
|
||||
|
|
@ -827,7 +826,7 @@ HTMLInputElement::IsPopupBlocked() const
|
|||
nsresult
|
||||
HTMLInputElement::InitDatePicker()
|
||||
{
|
||||
if (!Preferences::GetBool("dom.forms.datepicker", false)) {
|
||||
if (!IsDatePickerEnabled()) {
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
|
|
@ -2516,10 +2515,8 @@ bool
|
|||
HTMLInputElement::IsExperimentalMobileType(uint8_t aType)
|
||||
{
|
||||
return (aType == NS_FORM_INPUT_DATE &&
|
||||
!Preferences::GetBool("dom.forms.datetime", false) &&
|
||||
!Preferences::GetBool("dom.forms.datepicker", false)) ||
|
||||
(aType == NS_FORM_INPUT_TIME &&
|
||||
!Preferences::GetBool("dom.forms.datetime", false));
|
||||
!IsInputDateTimeEnabled() && !IsDatePickerEnabled()) ||
|
||||
(aType == NS_FORM_INPUT_TIME && !IsInputDateTimeEnabled());
|
||||
}
|
||||
|
||||
bool
|
||||
|
|
@ -3025,8 +3022,8 @@ HTMLInputElement::GetDisplayFileName(nsAString& aValue) const
|
|||
nsXPIDLString value;
|
||||
|
||||
if (mFilesOrDirectories.IsEmpty()) {
|
||||
if ((Preferences::GetBool("dom.input.dirpicker", false) && Allowdirs()) ||
|
||||
(Preferences::GetBool("dom.webkitBlink.dirPicker.enabled", false) &&
|
||||
if ((IsDirPickerEnabled() && Allowdirs()) ||
|
||||
(IsWebkitDirPickerEnabled() &&
|
||||
HasAttr(kNameSpaceID_None, nsGkAtoms::webkitdirectory))) {
|
||||
nsContentUtils::GetLocalizedString(nsContentUtils::eFORMS_PROPERTIES,
|
||||
"NoDirSelected", value);
|
||||
|
|
@ -3055,7 +3052,7 @@ HTMLInputElement::SetFilesOrDirectories(const nsTArray<OwningFileOrDirectory>& a
|
|||
{
|
||||
ClearGetFilesHelpers();
|
||||
|
||||
if (Preferences::GetBool("dom.webkitBlink.filesystem.enabled", false)) {
|
||||
if (IsWebkitFileSystemEnabled()) {
|
||||
HTMLInputElementBinding::ClearCachedWebkitEntriesValue(this);
|
||||
mEntries.Clear();
|
||||
}
|
||||
|
|
@ -3074,7 +3071,7 @@ HTMLInputElement::SetFiles(nsIDOMFileList* aFiles,
|
|||
mFilesOrDirectories.Clear();
|
||||
ClearGetFilesHelpers();
|
||||
|
||||
if (Preferences::GetBool("dom.webkitBlink.filesystem.enabled", false)) {
|
||||
if (IsWebkitFileSystemEnabled()) {
|
||||
HTMLInputElementBinding::ClearCachedWebkitEntriesValue(this);
|
||||
mEntries.Clear();
|
||||
}
|
||||
|
|
@ -3097,14 +3094,14 @@ HTMLInputElement::MozSetDndFilesAndDirectories(const nsTArray<OwningFileOrDirect
|
|||
{
|
||||
SetFilesOrDirectories(aFilesOrDirectories, true);
|
||||
|
||||
if (Preferences::GetBool("dom.webkitBlink.filesystem.enabled", false)) {
|
||||
if (IsWebkitFileSystemEnabled()) {
|
||||
UpdateEntries(aFilesOrDirectories);
|
||||
}
|
||||
|
||||
RefPtr<DispatchChangeEventCallback> dispatchChangeEventCallback =
|
||||
new DispatchChangeEventCallback(this);
|
||||
|
||||
if (Preferences::GetBool("dom.webkitBlink.dirPicker.enabled", false) &&
|
||||
if (IsWebkitDirPickerEnabled() &&
|
||||
HasAttr(kNameSpaceID_None, nsGkAtoms::webkitdirectory)) {
|
||||
ErrorResult rv;
|
||||
GetFilesHelper* helper = GetOrCreateGetFilesHelper(true /* recursionFlag */,
|
||||
|
|
@ -3182,8 +3179,8 @@ HTMLInputElement::GetFiles()
|
|||
return nullptr;
|
||||
}
|
||||
|
||||
if (Preferences::GetBool("dom.input.dirpicker", false) && Allowdirs() &&
|
||||
(!Preferences::GetBool("dom.webkitBlink.dirPicker.enabled", false) ||
|
||||
if (IsDirPickerEnabled() && Allowdirs() &&
|
||||
(!IsWebkitDirPickerEnabled() ||
|
||||
!HasAttr(kNameSpaceID_None, nsGkAtoms::webkitdirectory))) {
|
||||
return nullptr;
|
||||
}
|
||||
|
|
@ -4366,8 +4363,8 @@ HTMLInputElement::MaybeInitPickers(EventChainPostVisitor& aVisitor)
|
|||
do_QueryInterface(aVisitor.mEvent->mOriginalTarget);
|
||||
if (target &&
|
||||
target->FindFirstNonChromeOnlyAccessContent() == this &&
|
||||
((Preferences::GetBool("dom.input.dirpicker", false) && Allowdirs()) ||
|
||||
(Preferences::GetBool("dom.webkitBlink.dirPicker.enabled", false) &&
|
||||
((IsDirPickerEnabled() && Allowdirs()) ||
|
||||
(IsWebkitDirPickerEnabled() &&
|
||||
HasAttr(kNameSpaceID_None, nsGkAtoms::webkitdirectory)))) {
|
||||
type = FILE_PICKER_DIRECTORY;
|
||||
}
|
||||
|
|
@ -5735,20 +5732,133 @@ HTMLInputElement::ParseTime(const nsAString& aValue, uint32_t* aResult)
|
|||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
IsDateTimeEnabled(int32_t aNewType)
|
||||
/* static */ bool
|
||||
HTMLInputElement::IsDateTimeTypeSupported(uint8_t aDateTimeInputType)
|
||||
{
|
||||
return (aNewType == NS_FORM_INPUT_DATE &&
|
||||
(Preferences::GetBool("dom.forms.datetime", false) ||
|
||||
Preferences::GetBool("dom.experimental_forms", false) ||
|
||||
Preferences::GetBool("dom.forms.datepicker", false))) ||
|
||||
(aNewType == NS_FORM_INPUT_TIME &&
|
||||
(Preferences::GetBool("dom.forms.datetime", false) ||
|
||||
Preferences::GetBool("dom.experimental_forms", false))) ||
|
||||
((aNewType == NS_FORM_INPUT_MONTH ||
|
||||
aNewType == NS_FORM_INPUT_WEEK ||
|
||||
aNewType == NS_FORM_INPUT_DATETIME_LOCAL) &&
|
||||
Preferences::GetBool("dom.forms.datetime", false));
|
||||
return (aDateTimeInputType == NS_FORM_INPUT_DATE &&
|
||||
(IsInputDateTimeEnabled() || IsExperimentalFormsEnabled() ||
|
||||
IsDatePickerEnabled())) ||
|
||||
(aDateTimeInputType == NS_FORM_INPUT_TIME &&
|
||||
(IsInputDateTimeEnabled() || IsExperimentalFormsEnabled())) ||
|
||||
((aDateTimeInputType == NS_FORM_INPUT_MONTH ||
|
||||
aDateTimeInputType == NS_FORM_INPUT_WEEK ||
|
||||
aDateTimeInputType == NS_FORM_INPUT_DATETIME_LOCAL) &&
|
||||
IsInputDateTimeEnabled());
|
||||
}
|
||||
|
||||
/* static */ bool
|
||||
HTMLInputElement::IsWebkitDirPickerEnabled()
|
||||
{
|
||||
static bool sWebkitDirPickerEnabled = false;
|
||||
static bool sWebkitDirPickerPrefCached = false;
|
||||
if (!sWebkitDirPickerPrefCached) {
|
||||
sWebkitDirPickerPrefCached = true;
|
||||
Preferences::AddBoolVarCache(&sWebkitDirPickerEnabled,
|
||||
"dom.webkitBlink.dirPicker.enabled",
|
||||
false);
|
||||
}
|
||||
|
||||
return sWebkitDirPickerEnabled;
|
||||
}
|
||||
|
||||
/* static */ bool
|
||||
HTMLInputElement::IsWebkitFileSystemEnabled()
|
||||
{
|
||||
static bool sWebkitFileSystemEnabled = false;
|
||||
static bool sWebkitFileSystemPrefCached = false;
|
||||
if (!sWebkitFileSystemPrefCached) {
|
||||
sWebkitFileSystemPrefCached = true;
|
||||
Preferences::AddBoolVarCache(&sWebkitFileSystemEnabled,
|
||||
"dom.webkitBlink.filesystem.enabled",
|
||||
false);
|
||||
}
|
||||
|
||||
return sWebkitFileSystemEnabled;
|
||||
}
|
||||
|
||||
/* static */ bool
|
||||
HTMLInputElement::IsDirPickerEnabled()
|
||||
{
|
||||
static bool sDirPickerEnabled = false;
|
||||
static bool sDirPickerPrefCached = false;
|
||||
if (!sDirPickerPrefCached) {
|
||||
sDirPickerPrefCached = true;
|
||||
Preferences::AddBoolVarCache(&sDirPickerEnabled, "dom.input.dirpicker",
|
||||
false);
|
||||
}
|
||||
|
||||
return sDirPickerEnabled;
|
||||
}
|
||||
|
||||
/* static */ bool
|
||||
HTMLInputElement::IsDatePickerEnabled()
|
||||
{
|
||||
static bool sDatePickerEnabled = false;
|
||||
static bool sDatePickerPrefCached = false;
|
||||
if (!sDatePickerPrefCached) {
|
||||
sDatePickerPrefCached = true;
|
||||
Preferences::AddBoolVarCache(&sDatePickerEnabled, "dom.forms.datepicker",
|
||||
false);
|
||||
}
|
||||
|
||||
return sDatePickerEnabled;
|
||||
}
|
||||
|
||||
/* static */ bool
|
||||
HTMLInputElement::IsExperimentalFormsEnabled()
|
||||
{
|
||||
static bool sExperimentalFormsEnabled = false;
|
||||
static bool sExperimentalFormsPrefCached = false;
|
||||
if (!sExperimentalFormsPrefCached) {
|
||||
sExperimentalFormsPrefCached = true;
|
||||
Preferences::AddBoolVarCache(&sExperimentalFormsEnabled,
|
||||
"dom.experimental_forms",
|
||||
false);
|
||||
}
|
||||
|
||||
return sExperimentalFormsEnabled;
|
||||
}
|
||||
|
||||
/* static */ bool
|
||||
HTMLInputElement::IsInputDateTimeEnabled()
|
||||
{
|
||||
static bool sDateTimeEnabled = false;
|
||||
static bool sDateTimePrefCached = false;
|
||||
if (!sDateTimePrefCached) {
|
||||
sDateTimePrefCached = true;
|
||||
Preferences::AddBoolVarCache(&sDateTimeEnabled, "dom.forms.datetime",
|
||||
false);
|
||||
}
|
||||
|
||||
return sDateTimeEnabled;
|
||||
}
|
||||
|
||||
/* static */ bool
|
||||
HTMLInputElement::IsInputNumberEnabled()
|
||||
{
|
||||
static bool sInputNumberEnabled = false;
|
||||
static bool sInputNumberPrefCached = false;
|
||||
if (!sInputNumberPrefCached) {
|
||||
sInputNumberPrefCached = true;
|
||||
Preferences::AddBoolVarCache(&sInputNumberEnabled, "dom.forms.number",
|
||||
false);
|
||||
}
|
||||
|
||||
return sInputNumberEnabled;
|
||||
}
|
||||
|
||||
/* static */ bool
|
||||
HTMLInputElement::IsInputColorEnabled()
|
||||
{
|
||||
static bool sInputColorEnabled = false;
|
||||
static bool sInputColorPrefCached = false;
|
||||
if (!sInputColorPrefCached) {
|
||||
sInputColorPrefCached = true;
|
||||
Preferences::AddBoolVarCache(&sInputColorEnabled, "dom.forms.color",
|
||||
false);
|
||||
}
|
||||
|
||||
return sInputColorEnabled;
|
||||
}
|
||||
|
||||
bool
|
||||
|
|
@ -5766,12 +5876,11 @@ HTMLInputElement::ParseAttribute(int32_t aNamespaceID,
|
|||
if (success) {
|
||||
newType = aResult.GetEnumValue();
|
||||
if ((IsExperimentalMobileType(newType) &&
|
||||
!Preferences::GetBool("dom.experimental_forms", false)) ||
|
||||
(newType == NS_FORM_INPUT_NUMBER &&
|
||||
!Preferences::GetBool("dom.forms.number", false)) ||
|
||||
(newType == NS_FORM_INPUT_COLOR &&
|
||||
!Preferences::GetBool("dom.forms.color", false)) ||
|
||||
(IsDateTimeInputType(newType) && !IsDateTimeEnabled(newType))) {
|
||||
!IsExperimentalFormsEnabled()) ||
|
||||
(newType == NS_FORM_INPUT_NUMBER && !IsInputNumberEnabled()) ||
|
||||
(newType == NS_FORM_INPUT_COLOR && !IsInputColorEnabled()) ||
|
||||
(IsDateTimeInputType(newType) &&
|
||||
!IsDateTimeTypeSupported(newType))) {
|
||||
newType = kInputDefaultType->value;
|
||||
aResult.SetTo(newType, &aValue);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1632,9 +1632,73 @@ private:
|
|||
return IsSingleLineTextControl(false, aType) ||
|
||||
aType == NS_FORM_INPUT_RANGE ||
|
||||
aType == NS_FORM_INPUT_NUMBER ||
|
||||
aType == NS_FORM_INPUT_TIME;
|
||||
aType == NS_FORM_INPUT_TIME ||
|
||||
aType == NS_FORM_INPUT_DATE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if aDateTimeInputType should be supported based on "dom.forms.datetime",
|
||||
* "dom.forms.datepicker" and "dom.experimental_forms".
|
||||
*/
|
||||
static bool
|
||||
IsDateTimeTypeSupported(uint8_t aDateTimeInputType);
|
||||
|
||||
/**
|
||||
* Checks preference "dom.webkitBlink.dirPicker.enabled" to determine if
|
||||
* webkitdirectory should be supported.
|
||||
*/
|
||||
static bool
|
||||
IsWebkitDirPickerEnabled();
|
||||
|
||||
/**
|
||||
* Checks preference "dom.webkitBlink.filesystem.enabled" to determine if
|
||||
* webkitEntries should be supported.
|
||||
*/
|
||||
static bool
|
||||
IsWebkitFileSystemEnabled();
|
||||
|
||||
/**
|
||||
* Checks preference "dom.input.dirpicker" to determine if file and directory
|
||||
* entries API should be supported.
|
||||
*/
|
||||
static bool
|
||||
IsDirPickerEnabled();
|
||||
|
||||
/**
|
||||
* Checks preference "dom.forms.datepicker" to determine if date picker should
|
||||
* be supported.
|
||||
*/
|
||||
static bool
|
||||
IsDatePickerEnabled();
|
||||
|
||||
/**
|
||||
* Checks preference "dom.experimental_forms" to determine if experimental
|
||||
* implementation of input element should be enabled.
|
||||
*/
|
||||
static bool
|
||||
IsExperimentalFormsEnabled();
|
||||
|
||||
/**
|
||||
* Checks preference "dom.forms.datetime" to determine if input date/time
|
||||
* related types should be supported.
|
||||
*/
|
||||
static bool
|
||||
IsInputDateTimeEnabled();
|
||||
|
||||
/**
|
||||
* Checks preference "dom.forms.number" to determine if input type=number
|
||||
* should be supported.
|
||||
*/
|
||||
static bool
|
||||
IsInputNumberEnabled();
|
||||
|
||||
/**
|
||||
* Checks preference "dom.forms.color" to determine if date/time related
|
||||
* types should be supported.
|
||||
*/
|
||||
static bool
|
||||
IsInputColorEnabled();
|
||||
|
||||
struct nsFilePickerFilter {
|
||||
nsFilePickerFilter()
|
||||
: mFilterMask(0) {}
|
||||
|
|
|
|||
|
|
@ -32,6 +32,8 @@ skip-if = android_version == '18' # Android, bug 1147974
|
|||
skip-if = android_version == '18' # Android, bug 1147974
|
||||
[test_input_date_key_events.html]
|
||||
skip-if = os == "android"
|
||||
[test_input_datetime_input_change_events.html]
|
||||
skip-if = os == "android"
|
||||
[test_input_datetime_focus_blur.html]
|
||||
skip-if = os == "android"
|
||||
[test_input_datetime_focus_blur_events.html]
|
||||
|
|
|
|||
|
|
@ -184,16 +184,16 @@ var testData = [
|
|||
expectedVal: "2016-01-31"
|
||||
},
|
||||
{
|
||||
// Home key on year field sets it to the minimum year, which is 0001.
|
||||
// Home key should have no effect on year field.
|
||||
keys: ["VK_TAB", "VK_TAB", "VK_HOME"],
|
||||
initialVal: "2016-01-01",
|
||||
expectedVal: "0001-01-01"
|
||||
expectedVal: "2016-01-01"
|
||||
},
|
||||
{
|
||||
// End key on year field sets it to the maximum year, which is 275760.
|
||||
// End key should have no effect on year field.
|
||||
keys: ["VK_TAB", "VK_TAB", "VK_END"],
|
||||
initialVal: "2016-01-01",
|
||||
expectedVal: "275760-01-01"
|
||||
expectedVal: "2016-01-01"
|
||||
},
|
||||
];
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,88 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<!--
|
||||
https://bugzilla.mozilla.org/show_bug.cgi?id=1370858
|
||||
-->
|
||||
<head>
|
||||
<title>Test for Bug 1370858</title>
|
||||
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
|
||||
</head>
|
||||
<body>
|
||||
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1370858">Mozilla Bug 722599</a>
|
||||
<p id="display"></p>
|
||||
<div id="content">
|
||||
<input type="time" id="input_time" onchange="++changeEvents[0]"
|
||||
oninput="++inputEvents[0]">
|
||||
<input type="date" id="input_date" onchange="++changeEvents[1]"
|
||||
oninput="++inputEvents[1]">
|
||||
</div>
|
||||
<pre id="test">
|
||||
<script class="testbody" type="text/javascript">
|
||||
|
||||
/**
|
||||
* Test for Bug 1370858.
|
||||
* Test that change and input events are (not) fired for date/time inputs.
|
||||
**/
|
||||
|
||||
var inputTypes = ["time", "date"];
|
||||
var changeEvents = [0, 0];
|
||||
var inputEvents = [0, 0];
|
||||
var values = ["10:30", "2017-06-08"];
|
||||
var expectedValues = [["09:30", "01:30"], ["2017-05-08", "2017-01-08"]];
|
||||
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
SimpleTest.waitForFocus(function() {
|
||||
test();
|
||||
SimpleTest.finish();
|
||||
});
|
||||
|
||||
function test() {
|
||||
for (var i = 0; i < inputTypes.length; i++) {
|
||||
var input = document.getElementById("input_" + inputTypes[i]);
|
||||
var inputRect = input.getBoundingClientRect();
|
||||
|
||||
// Points over the input's reset button
|
||||
var resetButton_X = inputRect.width - 15;
|
||||
var resetButton_Y = inputRect.height / 2;
|
||||
|
||||
is(changeEvents[i], 0, "Number of change events should be 0 at start.");
|
||||
is(inputEvents[i], 0, "Number of input events should be 0 at start.");
|
||||
|
||||
// Test that change and input events are not dispatched setting .value by
|
||||
// script.
|
||||
input.value = values[i];
|
||||
is(input.value, values[i], "Check that value was set correctly (0).");
|
||||
is(changeEvents[i], 0, "Change event should not have dispatched (0).");
|
||||
is(inputEvents[i], 0, "Input event should not have dispatched (0).");
|
||||
|
||||
// Test that change and input events are fired when changing the value using
|
||||
// up/down keys.
|
||||
input.focus();
|
||||
synthesizeKey("VK_DOWN", {});
|
||||
is(input.value, expectedValues[i][0], "Check that value was set correctly (1).");
|
||||
is(changeEvents[i], 1, "Change event should be dispatched (1).");
|
||||
is(inputEvents[i], 1, "Input event should ne dispatched (1).");
|
||||
|
||||
// Test that change and input events are fired when changing the value with
|
||||
// the keyboard.
|
||||
synthesizeKey("0", {});
|
||||
synthesizeKey("1", {});
|
||||
is(input.value, expectedValues[i][1], "Check that value was set correctly (2).");
|
||||
is(changeEvents[i], 2, "Change event should be dispatched (2).");
|
||||
is(inputEvents[i], 2, "Input event should be dispatched (2).");
|
||||
|
||||
// Test that change and input events are fired when clearing the value using
|
||||
// the reset button.
|
||||
synthesizeMouse(input, resetButton_X, resetButton_Y, {});
|
||||
is(input.value, "", "Check that value was set correctly (3).");
|
||||
is(changeEvents[i], 3, "Change event should be dispatched (3).");
|
||||
is(inputEvents[i], 3, "Input event should be dispatched (3).");
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
</pre>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -11,3 +11,14 @@ skip-if(!Android&&!B2G&&!Mulet) == time-simple-unthemed.html time-simple-untheme
|
|||
# type change
|
||||
skip-if(Android||B2G||Mulet) == to-time-from-other-type-unthemed.html time-simple-unthemed.html
|
||||
skip-if(Android||B2G||Mulet) == from-time-to-other-type-unthemed.html from-time-to-other-type-unthemed-ref.html
|
||||
|
||||
# content should not overflow on small width/height
|
||||
skip-if(Android) == time-small-width.html time-small-width-ref.html
|
||||
skip-if(Android) == time-small-height.html time-small-height-ref.html
|
||||
skip-if(Android) == time-small-width-height.html time-small-width-height-ref.html
|
||||
|
||||
# content (text) should be left aligned
|
||||
skip-if(Android) == time-content-left-aligned.html time-content-left-aligned-ref.html
|
||||
|
||||
# reset button should be right aligned
|
||||
skip-if(Android) fails-if(styloVsGecko) == time-reset-button-right-aligned.html time-reset-button-right-aligned-ref.html # bug 1372062
|
||||
|
|
|
|||
|
|
@ -0,0 +1,9 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<body>
|
||||
<input type="time" style="width: 200px;">
|
||||
<!-- div to cover the right area -->
|
||||
<div style="display:block; position:absolute; background-color:black;
|
||||
top:0px; left:40px; width:200px; height:100px;"></div>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<body>
|
||||
<input type="time" style="width: 50px;">
|
||||
<!-- div to cover the right area -->
|
||||
<div style="display:block; position:absolute; background-color:black;
|
||||
top:0px; left:40px; width:200px; height:100px;"></div>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<body>
|
||||
<input type="time" value="10:00" style="float: right; color: white;">
|
||||
<!-- div to cover the left area -->
|
||||
<div style="display:block; position:absolute; background-color:black;
|
||||
top:0px; right:30px; width:500px; height:100px;"></div>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<body>
|
||||
<input type="time" value="10:00" style="width: 150px; float: right;
|
||||
color: white;">
|
||||
<!-- div to cover the left area -->
|
||||
<div style="display:block; position:absolute; background-color:black;
|
||||
top:0px; right:30px; width:500px; height:100px;"></div>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<style>
|
||||
input {
|
||||
width: 200px;
|
||||
height: 5px;
|
||||
outline: 1px dotted black;
|
||||
/* Disable baseline alignment, so that our y-position isn't influenced by the
|
||||
* choice of font inside of input: */
|
||||
vertical-align: top;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<input>
|
||||
</body>
|
||||
</html>
|
||||
19
layout/reftests/forms/input/datetime/time-small-height.html
Normal file
19
layout/reftests/forms/input/datetime/time-small-height.html
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<style>
|
||||
input {
|
||||
width: 200px;
|
||||
height: 5px;
|
||||
outline: 1px dotted black;
|
||||
color: white;
|
||||
/* Disable baseline alignment, so that our y-position isn't influenced by the
|
||||
* choice of font inside of input: */
|
||||
vertical-align: top;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<input type="time">
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<style>
|
||||
input {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
outline: 1px dotted black;
|
||||
/* Disable baseline alignment, so that our y-position isn't influenced by the
|
||||
* choice of font inside of input: */
|
||||
vertical-align: top;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<input>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<style>
|
||||
input {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
outline: 1px dotted black;
|
||||
color: white;
|
||||
/* Disable baseline alignment, so that our y-position isn't influenced by the
|
||||
* choice of font inside of input: */
|
||||
vertical-align: top;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<input type="time">
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<style>
|
||||
input {
|
||||
width: 10px;
|
||||
height: 1.5em;
|
||||
outline: 1px dotted black;
|
||||
background: white;
|
||||
/* Disable baseline alignment, so that our y-position isn't influenced by the
|
||||
* choice of font inside of input: */
|
||||
vertical-align: top;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<input>
|
||||
</body>
|
||||
</html>
|
||||
20
layout/reftests/forms/input/datetime/time-small-width.html
Normal file
20
layout/reftests/forms/input/datetime/time-small-width.html
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<style>
|
||||
input {
|
||||
width: 10px;
|
||||
height: 1.5em;
|
||||
outline: 1px dotted black;
|
||||
color: white;
|
||||
background: white;
|
||||
/* Disable baseline alignment, so that our y-position isn't influenced by the
|
||||
* choice of font inside of input: */
|
||||
vertical-align: top;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<input type="time">
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -1135,3 +1135,8 @@ input[type="number"] > div > div > div:hover {
|
|||
/* give some indication of hover state for the up/down buttons */
|
||||
background-color: lightblue;
|
||||
}
|
||||
|
||||
input[type="date"],
|
||||
input[type="time"] {
|
||||
overflow: hidden !important;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ skip-if = !e10s
|
|||
[browser_contentTitle.js]
|
||||
[browser_crash_previous_frameloader.js]
|
||||
run-if = e10s && crashreporter
|
||||
[browser_datetime_datepicker.js]
|
||||
[browser_default_image_filename.js]
|
||||
[browser_f7_caret_browsing.js]
|
||||
[browser_findbar.js]
|
||||
|
|
|
|||
222
toolkit/content/tests/browser/browser_datetime_datepicker.js
Normal file
222
toolkit/content/tests/browser/browser_datetime_datepicker.js
Normal file
|
|
@ -0,0 +1,222 @@
|
|||
/* 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/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
const MONTH_YEAR = ".month-year",
|
||||
DAYS_VIEW = ".days-view",
|
||||
BTN_PREV_MONTH = ".prev",
|
||||
BTN_NEXT_MONTH = ".next";
|
||||
const DATE_FORMAT = new Intl.DateTimeFormat("en-US", { year: "numeric", month: "long", timeZone: "UTC" }).format;
|
||||
|
||||
// Create a list of abbreviations for calendar class names
|
||||
const W = "weekend",
|
||||
O = "outside",
|
||||
S = "selection",
|
||||
R = "out-of-range",
|
||||
T = "today",
|
||||
P = "off-step";
|
||||
|
||||
// Calendar classlist for 2016-12. Used to verify the classNames are correct.
|
||||
const calendarClasslist_201612 = [
|
||||
[W, O], [O], [O], [O], [], [], [W],
|
||||
[W], [], [], [], [], [], [W],
|
||||
[W], [], [], [], [S], [], [W],
|
||||
[W], [], [], [], [], [], [W],
|
||||
[W], [], [], [], [], [], [W],
|
||||
[W, O], [O], [O], [O], [O], [O], [W, O],
|
||||
];
|
||||
|
||||
function getCalendarText() {
|
||||
return helper.getChildren(DAYS_VIEW).map(child => child.textContent);
|
||||
}
|
||||
|
||||
function getCalendarClassList() {
|
||||
return helper.getChildren(DAYS_VIEW).map(child => Array.from(child.classList));
|
||||
}
|
||||
|
||||
function mergeArrays(a, b) {
|
||||
return a.map((classlist, index) => classlist.concat(b[index]));
|
||||
}
|
||||
|
||||
let helper = new DateTimeTestHelper();
|
||||
|
||||
registerCleanupFunction(() => {
|
||||
helper.cleanup();
|
||||
});
|
||||
|
||||
/**
|
||||
* Test that date picker opens to today's date when input field is blank
|
||||
*/
|
||||
add_task(async function test_datepicker_today() {
|
||||
const date = new Date();
|
||||
|
||||
await helper.openPicker("data:text/html, <input type='date'>");
|
||||
|
||||
Assert.equal(helper.getElement(MONTH_YEAR).textContent, DATE_FORMAT(date));
|
||||
|
||||
await helper.tearDown();
|
||||
});
|
||||
|
||||
/**
|
||||
* Test that date picker opens to the correct month, with calendar days
|
||||
* displayed correctly, given a date value is set.
|
||||
*/
|
||||
add_task(async function test_datepicker_open() {
|
||||
const inputValue = "2016-12-15";
|
||||
|
||||
await helper.openPicker(`data:text/html, <input type="date" value="${inputValue}">`);
|
||||
|
||||
Assert.equal(helper.getElement(MONTH_YEAR).textContent, DATE_FORMAT(new Date(inputValue)));
|
||||
Assert.deepEqual(
|
||||
getCalendarText(),
|
||||
[
|
||||
"27", "28", "29", "30", "1", "2", "3",
|
||||
"4", "5", "6", "7", "8", "9", "10",
|
||||
"11", "12", "13", "14", "15", "16", "17",
|
||||
"18", "19", "20", "21", "22", "23", "24",
|
||||
"25", "26", "27", "28", "29", "30", "31",
|
||||
"1", "2", "3", "4", "5", "6", "7",
|
||||
],
|
||||
"2016-12",
|
||||
);
|
||||
Assert.deepEqual(
|
||||
getCalendarClassList(),
|
||||
calendarClasslist_201612,
|
||||
"2016-12 classNames"
|
||||
);
|
||||
|
||||
await helper.tearDown();
|
||||
});
|
||||
|
||||
/**
|
||||
* When the prev month button is clicked, calendar should display the dates for
|
||||
* the previous month.
|
||||
*/
|
||||
add_task(async function test_datepicker_prev_month_btn() {
|
||||
const inputValue = "2016-12-15";
|
||||
const prevMonth = "2016-11-01";
|
||||
|
||||
await helper.openPicker(`data:text/html, <input type="date" value="${inputValue}">`);
|
||||
helper.click(helper.getElement(BTN_PREV_MONTH));
|
||||
|
||||
Assert.equal(helper.getElement(MONTH_YEAR).textContent, DATE_FORMAT(new Date(prevMonth)));
|
||||
Assert.deepEqual(
|
||||
getCalendarText(),
|
||||
[
|
||||
"30", "31", "1", "2", "3", "4", "5",
|
||||
"6", "7", "8", "9", "10", "11", "12",
|
||||
"13", "14", "15", "16", "17", "18", "19",
|
||||
"20", "21", "22", "23", "24", "25", "26",
|
||||
"27", "28", "29", "30", "1", "2", "3",
|
||||
"4", "5", "6", "7", "8", "9", "10",
|
||||
],
|
||||
"2016-11",
|
||||
);
|
||||
|
||||
await helper.tearDown();
|
||||
});
|
||||
|
||||
/**
|
||||
* When the next month button is clicked, calendar should display the dates for
|
||||
* the next month.
|
||||
*/
|
||||
add_task(async function test_datepicker_next_month_btn() {
|
||||
const inputValue = "2016-12-15";
|
||||
const nextMonth = "2017-01-01";
|
||||
|
||||
await helper.openPicker(`data:text/html, <input type="date" value="${inputValue}">`);
|
||||
helper.click(helper.getElement(BTN_NEXT_MONTH));
|
||||
|
||||
Assert.equal(helper.getElement(MONTH_YEAR).textContent, DATE_FORMAT(new Date(nextMonth)));
|
||||
Assert.deepEqual(
|
||||
getCalendarText(),
|
||||
[
|
||||
"25", "26", "27", "28", "29", "30", "31",
|
||||
"1", "2", "3", "4", "5", "6", "7",
|
||||
"8", "9", "10", "11", "12", "13", "14",
|
||||
"15", "16", "17", "18", "19", "20", "21",
|
||||
"22", "23", "24", "25", "26", "27", "28",
|
||||
"29", "30", "31", "1", "2", "3", "4",
|
||||
],
|
||||
"2017-01",
|
||||
);
|
||||
|
||||
await helper.tearDown();
|
||||
});
|
||||
|
||||
/**
|
||||
* When a date on the calendar is clicked, date picker should close and set
|
||||
* value to the input box.
|
||||
*/
|
||||
add_task(async function test_datepicker_clicked() {
|
||||
const inputValue = "2016-12-15";
|
||||
const firstDayOnCalendar = "2016-11-27";
|
||||
|
||||
await helper.openPicker(`data:text/html, <input type="date" value="${inputValue}">`);
|
||||
// Click the first item (top-left corner) of the calendar
|
||||
helper.click(helper.getElement(DAYS_VIEW).children[0]);
|
||||
await ContentTask.spawn(helper.tab.linkedBrowser, {}, async function() {
|
||||
let inputEl = content.document.querySelector("input");
|
||||
await ContentTaskUtils.waitForEvent(inputEl, "input");
|
||||
});
|
||||
|
||||
Assert.equal(content.document.querySelector("input").value, firstDayOnCalendar);
|
||||
|
||||
await helper.tearDown();
|
||||
});
|
||||
|
||||
/**
|
||||
* When min and max attributes are set, calendar should show some dates as
|
||||
* out-of-range.
|
||||
*/
|
||||
add_task(async function test_datepicker_min_max() {
|
||||
const inputValue = "2016-12-15";
|
||||
const inputMin = "2016-12-05";
|
||||
const inputMax = "2016-12-25";
|
||||
|
||||
await helper.openPicker(`data:text/html, <input type="date" value="${inputValue}" min="${inputMin}" max="${inputMax}">`);
|
||||
|
||||
Assert.deepEqual(
|
||||
getCalendarClassList(),
|
||||
mergeArrays(calendarClasslist_201612, [
|
||||
// R denotes out-of-range
|
||||
[R], [R], [R], [R], [R], [R], [R],
|
||||
[R], [], [], [], [], [], [],
|
||||
[], [], [], [], [], [], [],
|
||||
[], [], [], [], [], [], [],
|
||||
[], [R], [R], [R], [R], [R], [R],
|
||||
[R], [R], [R], [R], [R], [R], [R],
|
||||
]),
|
||||
"2016-12 with min & max",
|
||||
);
|
||||
|
||||
await helper.tearDown();
|
||||
});
|
||||
|
||||
/**
|
||||
* When step attribute is set, calendar should show some dates as off-step.
|
||||
*/
|
||||
add_task(async function test_datepicker_step() {
|
||||
const inputValue = "2016-12-15";
|
||||
const inputStep = "5";
|
||||
|
||||
await helper.openPicker(`data:text/html, <input type="date" value="${inputValue}" step="${inputStep}">`);
|
||||
|
||||
Assert.deepEqual(
|
||||
getCalendarClassList(),
|
||||
mergeArrays(calendarClasslist_201612, [
|
||||
// P denotes off-step
|
||||
[P], [P], [P], [], [P], [P], [P],
|
||||
[P], [], [P], [P], [P], [P], [],
|
||||
[P], [P], [P], [P], [], [P], [P],
|
||||
[P], [P], [], [P], [P], [P], [P],
|
||||
[], [P], [P], [P], [P], [], [P],
|
||||
[P], [P], [P], [], [P], [P], [P],
|
||||
]),
|
||||
"2016-12 with step",
|
||||
);
|
||||
|
||||
await helper.tearDown();
|
||||
});
|
||||
|
|
@ -31,3 +31,88 @@ function pushPrefs(...aPrefs) {
|
|||
SpecialPowers.pushPrefEnv({"set": aPrefs}, deferred.resolve);
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper class for testing datetime input picker widget
|
||||
*/
|
||||
class DateTimeTestHelper {
|
||||
constructor() {
|
||||
this.panel = document.getElementById("DateTimePickerPanel");
|
||||
this.panel.setAttribute("animate", false);
|
||||
this.tab = null;
|
||||
this.frame = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens a new tab with the URL of the test page, and make sure the picker is
|
||||
* ready for testing.
|
||||
*
|
||||
* @param {String} pageUrl
|
||||
*/
|
||||
async openPicker(pageUrl) {
|
||||
this.tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, pageUrl);
|
||||
await BrowserTestUtils.synthesizeMouseAtCenter("input", {}, gBrowser.selectedBrowser);
|
||||
// If dateTimePopupFrame doesn't exist yet, wait for the binding to be attached
|
||||
if (!this.panel.dateTimePopupFrame) {
|
||||
await BrowserTestUtils.waitForEvent(this.panel, "DateTimePickerBindingReady")
|
||||
}
|
||||
this.frame = this.panel.dateTimePopupFrame;
|
||||
await BrowserTestUtils.waitForEvent(this.frame, "load", true);
|
||||
// Wait for picker elements to be ready and open panel transition to end
|
||||
await BrowserTestUtils.waitForEvent(this.frame.contentDocument, "PickerReady");
|
||||
}
|
||||
|
||||
/**
|
||||
* Find an element on the picker.
|
||||
*
|
||||
* @param {String} selector
|
||||
* @return {DOMElement}
|
||||
*/
|
||||
getElement(selector) {
|
||||
return this.frame.contentDocument.querySelector(selector);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the children of an element on the picker.
|
||||
*
|
||||
* @param {String} selector
|
||||
* @return {Array<DOMElement>}
|
||||
*/
|
||||
getChildren(selector) {
|
||||
return Array.from(this.getElement(selector).children);
|
||||
}
|
||||
|
||||
/**
|
||||
* Click on an element
|
||||
*
|
||||
* @param {DOMElement} element
|
||||
*/
|
||||
click(element) {
|
||||
EventUtils.synthesizeMouseAtCenter(element, {}, this.frame.contentWindow);
|
||||
}
|
||||
|
||||
/**
|
||||
* Close the panel and the tab
|
||||
*/
|
||||
async tearDown() {
|
||||
if (!this.panel.hidden) {
|
||||
let pickerClosePromise = new Promise(resolve => {
|
||||
this.panel.addEventListener("popuphidden", resolve, {once: true});
|
||||
});
|
||||
this.panel.closePicker();
|
||||
await pickerClosePromise;
|
||||
}
|
||||
await BrowserTestUtils.removeTab(this.tab);
|
||||
this.tab = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean up after tests. Remove the frame to prevent leak.
|
||||
*/
|
||||
cleanup() {
|
||||
this.frame.remove();
|
||||
this.frame = null;
|
||||
this.panel.removeAttribute("animate");
|
||||
this.panel = null;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -54,23 +54,21 @@ function Calendar(options, context) {
|
|||
* {
|
||||
* {Number} textContent
|
||||
* {Array<String>} classNames
|
||||
* {Boolean} enabled
|
||||
* }
|
||||
* {Function} getDayString: Transform day number to string
|
||||
* {Function} getWeekHeaderString: Transform day of week number to string
|
||||
* {Function} setValue: Set value for dateKeeper
|
||||
* {Number} selectionValue: The selection date value
|
||||
* {Function} setSelection: Set selection for dateKeeper
|
||||
* }
|
||||
*/
|
||||
setProps(props) {
|
||||
if (props.isVisible) {
|
||||
// Transform the days and weekHeaders array for rendering
|
||||
const days = props.days.map(({ dateValue, textContent, classNames }) => {
|
||||
const days = props.days.map(({ dateObj, classNames, enabled }) => {
|
||||
return {
|
||||
dateValue,
|
||||
textContent: props.getDayString(textContent),
|
||||
className: dateValue == props.selectionValue ?
|
||||
classNames.concat("selection").join(" ") :
|
||||
classNames.join(" ")
|
||||
textContent: props.getDayString(dateObj.getUTCDate()),
|
||||
className: classNames.join(" "),
|
||||
enabled
|
||||
};
|
||||
});
|
||||
const weekHeaders = props.weekHeaders.map(({ textContent, classNames }) => {
|
||||
|
|
@ -152,10 +150,10 @@ function Calendar(options, context) {
|
|||
case "click": {
|
||||
if (event.target.parentNode == this.context.daysView) {
|
||||
let targetId = event.target.dataset.id;
|
||||
this.props.setValue({
|
||||
selectionValue: this.props.days[targetId].dateValue,
|
||||
dateValue: this.props.days[targetId].dateValue
|
||||
});
|
||||
let targetObj = this.props.days[targetId];
|
||||
if (targetObj.enabled) {
|
||||
this.props.setSelection(targetObj.dateObj);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,41 +6,72 @@
|
|||
|
||||
/**
|
||||
* DateKeeper keeps track of the date states.
|
||||
*
|
||||
* @param {Object} date parts
|
||||
* {
|
||||
* {Number} year
|
||||
* {Number} month
|
||||
* {Number} day
|
||||
* }
|
||||
* {Object} options
|
||||
* {
|
||||
* {Number} firstDayOfWeek [optional]
|
||||
* {Array<Number>} weekends [optional]
|
||||
* {Number} calViewSize [optional]
|
||||
* }
|
||||
*/
|
||||
function DateKeeper({ year, month, day }, { firstDayOfWeek = 0, weekends = [0], calViewSize = 42 }) {
|
||||
this.state = {
|
||||
firstDayOfWeek, weekends, calViewSize,
|
||||
dateObj: new Date(0),
|
||||
years: [],
|
||||
months: [],
|
||||
days: []
|
||||
};
|
||||
this.state.weekHeaders = this._getWeekHeaders(firstDayOfWeek);
|
||||
this._update(year, month, day);
|
||||
function DateKeeper(props) {
|
||||
this.init(props);
|
||||
}
|
||||
|
||||
{
|
||||
const DAYS_IN_A_WEEK = 7,
|
||||
MONTHS_IN_A_YEAR = 12,
|
||||
YEAR_VIEW_SIZE = 200,
|
||||
YEAR_BUFFER_SIZE = 10;
|
||||
YEAR_BUFFER_SIZE = 10,
|
||||
// The min and max values are derived from the ECMAScript spec:
|
||||
// http://ecma-international.org/ecma-262/5.1/#sec-15.9.1.1
|
||||
MIN_DATE = -8640000000000000,
|
||||
MAX_DATE = 8640000000000000;
|
||||
|
||||
DateKeeper.prototype = {
|
||||
get year() {
|
||||
return this.state.dateObj.getUTCFullYear();
|
||||
},
|
||||
|
||||
get month() {
|
||||
return this.state.dateObj.getUTCMonth();
|
||||
},
|
||||
|
||||
get day() {
|
||||
return this.state.dateObj.getUTCDate();
|
||||
},
|
||||
|
||||
get selection() {
|
||||
return this.state.selection;
|
||||
},
|
||||
|
||||
/**
|
||||
* Set new date
|
||||
* Initialize DateKeeper
|
||||
* @param {Number} year
|
||||
* @param {Number} month
|
||||
* @param {Number} day
|
||||
* @param {String} min
|
||||
* @param {String} max
|
||||
* @param {Number} firstDayOfWeek
|
||||
* @param {Array<Number>} weekends
|
||||
* @param {Number} calViewSize
|
||||
*/
|
||||
init({ year, month, day, min, max, firstDayOfWeek = 0, weekends = [0], calViewSize = 42 }) {
|
||||
const today = new Date();
|
||||
const isDateSet = year != undefined && month != undefined && day != undefined;
|
||||
|
||||
this.state = {
|
||||
firstDayOfWeek, weekends, calViewSize,
|
||||
min: new Date(min != undefined ? min : MIN_DATE),
|
||||
max: new Date(max != undefined ? max : MAX_DATE),
|
||||
today: this._newUTCDate(today.getFullYear(), today.getMonth(), today.getDate()),
|
||||
weekHeaders: this._getWeekHeaders(firstDayOfWeek, weekends),
|
||||
years: [],
|
||||
months: [],
|
||||
days: [],
|
||||
selection: { year, month, day },
|
||||
};
|
||||
|
||||
this.state.dateObj = isDateSet ?
|
||||
this._newUTCDate(year, month, day) :
|
||||
new Date(this.state.today);
|
||||
},
|
||||
/**
|
||||
* Set new date. The year is always treated as full year, so the short-form
|
||||
* is not supported.
|
||||
* @param {Object} date parts
|
||||
* {
|
||||
* {Number} year [optional]
|
||||
|
|
@ -48,17 +79,21 @@ function DateKeeper({ year, month, day }, { firstDayOfWeek = 0, weekends = [0],
|
|||
* {Number} date [optional]
|
||||
* }
|
||||
*/
|
||||
set({ year = this.state.year, month = this.state.month, day = this.state.day }) {
|
||||
this._update(year, month, day);
|
||||
set({ year = this.year, month = this.month, day = this.day }) {
|
||||
// Use setUTCFullYear so that year 99 doesn't get parsed as 1999
|
||||
this.state.dateObj.setUTCFullYear(year, month, day);
|
||||
},
|
||||
|
||||
/**
|
||||
* Set date with value
|
||||
* @param {Number} value: Date value
|
||||
* Set selection date
|
||||
* @param {Number} year
|
||||
* @param {Number} month
|
||||
* @param {Number} day
|
||||
*/
|
||||
setValue(value) {
|
||||
const dateObj = new Date(value);
|
||||
this._update(dateObj.getUTCFullYear(), dateObj.getUTCMonth(), dateObj.getUTCDate());
|
||||
setSelection({ year, month, day }) {
|
||||
this.state.selection.year = year;
|
||||
this.state.selection.month = month;
|
||||
this.state.selection.day = day;
|
||||
},
|
||||
|
||||
/**
|
||||
|
|
@ -66,8 +101,10 @@ function DateKeeper({ year, month, day }, { firstDayOfWeek = 0, weekends = [0],
|
|||
* @param {Number} month
|
||||
*/
|
||||
setMonth(month) {
|
||||
const lastDayOfMonth = this._newUTCDate(this.state.year, month + 1, 0).getUTCDate();
|
||||
this._update(this.state.year, month, Math.min(this.state.day, lastDayOfMonth));
|
||||
const lastDayOfMonth = this._newUTCDate(this.year, month + 1, 0).getUTCDate();
|
||||
this.set({ year: this.year,
|
||||
month,
|
||||
day: Math.min(this.day, lastDayOfMonth) });
|
||||
},
|
||||
|
||||
/**
|
||||
|
|
@ -75,8 +112,10 @@ function DateKeeper({ year, month, day }, { firstDayOfWeek = 0, weekends = [0],
|
|||
* @param {Number} year
|
||||
*/
|
||||
setYear(year) {
|
||||
const lastDayOfMonth = this._newUTCDate(year, this.state.month + 1, 0).getUTCDate();
|
||||
this._update(year, this.state.month, Math.min(this.state.day, lastDayOfMonth));
|
||||
const lastDayOfMonth = this._newUTCDate(year, this.month + 1, 0).getUTCDate();
|
||||
this.set({ year,
|
||||
month: this.month,
|
||||
day: Math.min(this.day, lastDayOfMonth) });
|
||||
},
|
||||
|
||||
/**
|
||||
|
|
@ -84,22 +123,10 @@ function DateKeeper({ year, month, day }, { firstDayOfWeek = 0, weekends = [0],
|
|||
* @param {Number} offset
|
||||
*/
|
||||
setMonthByOffset(offset) {
|
||||
const lastDayOfMonth = this._newUTCDate(this.state.year, this.state.month + offset + 1, 0).getUTCDate();
|
||||
this._update(this.state.year, this.state.month + offset, Math.min(this.state.day, lastDayOfMonth));
|
||||
},
|
||||
|
||||
/**
|
||||
* Update the states.
|
||||
* @param {Number} year [description]
|
||||
* @param {Number} month [description]
|
||||
* @param {Number} day [description]
|
||||
*/
|
||||
_update(year, month, day) {
|
||||
// Use setUTCFullYear so that year 99 doesn't get parsed as 1999
|
||||
this.state.dateObj.setUTCFullYear(year, month, day);
|
||||
this.state.year = this.state.dateObj.getUTCFullYear();
|
||||
this.state.month = this.state.dateObj.getUTCMonth();
|
||||
this.state.day = this.state.dateObj.getUTCDate();
|
||||
const lastDayOfMonth = this._newUTCDate(this.year, this.month + offset + 1, 0).getUTCDate();
|
||||
this.set({ year: this.year,
|
||||
month: this.month + offset,
|
||||
day: Math.min(this.day, lastDayOfMonth) });
|
||||
},
|
||||
|
||||
/**
|
||||
|
|
@ -111,7 +138,6 @@ function DateKeeper({ year, month, day }, { firstDayOfWeek = 0, weekends = [0],
|
|||
* }
|
||||
*/
|
||||
getMonths() {
|
||||
// TODO: add min/max and step support
|
||||
let months = [];
|
||||
|
||||
for (let i = 0; i < MONTHS_IN_A_YEAR; i++) {
|
||||
|
|
@ -133,12 +159,11 @@ function DateKeeper({ year, month, day }, { firstDayOfWeek = 0, weekends = [0],
|
|||
* }
|
||||
*/
|
||||
getYears() {
|
||||
// TODO: add min/max and step support
|
||||
let years = [];
|
||||
|
||||
const firstItem = this.state.years[0];
|
||||
const lastItem = this.state.years[this.state.years.length - 1];
|
||||
const currentYear = this.state.dateObj.getUTCFullYear();
|
||||
const currentYear = this.year;
|
||||
|
||||
// Generate new years array when the year is outside of the first &
|
||||
// last item range. If not, return the cached result.
|
||||
|
|
@ -161,30 +186,43 @@ function DateKeeper({ year, month, day }, { firstDayOfWeek = 0, weekends = [0],
|
|||
* Get days for calendar
|
||||
* @return {Array<Object>}
|
||||
* {
|
||||
* {Number} dateValue
|
||||
* {Number} textContent
|
||||
* {Date} dateObj
|
||||
* {Array<String>} classNames
|
||||
* {Boolean} enabled
|
||||
* }
|
||||
*/
|
||||
getDays() {
|
||||
// TODO: add min/max and step support
|
||||
let firstDayOfMonth = this._getFirstCalendarDate(this.state.dateObj, this.state.firstDayOfWeek);
|
||||
// TODO: add step support
|
||||
const firstDayOfMonth = this._getFirstCalendarDate(this.state.dateObj, this.state.firstDayOfWeek);
|
||||
const month = this.month;
|
||||
let days = [];
|
||||
let month = this.state.dateObj.getUTCMonth();
|
||||
|
||||
for (let i = 0; i < this.state.calViewSize; i++) {
|
||||
let dateObj = this._newUTCDate(firstDayOfMonth.getUTCFullYear(), firstDayOfMonth.getUTCMonth(), firstDayOfMonth.getUTCDate() + i);
|
||||
const dateObj = this._newUTCDate(firstDayOfMonth.getUTCFullYear(), firstDayOfMonth.getUTCMonth(), firstDayOfMonth.getUTCDate() + i);
|
||||
let classNames = [];
|
||||
let enabled = true;
|
||||
if (this.state.weekends.includes(dateObj.getUTCDay())) {
|
||||
classNames.push("weekend");
|
||||
}
|
||||
if (month != dateObj.getUTCMonth()) {
|
||||
classNames.push("outside");
|
||||
}
|
||||
if (this.state.selection.year == dateObj.getUTCFullYear() &&
|
||||
this.state.selection.month == dateObj.getUTCMonth() &&
|
||||
this.state.selection.day == dateObj.getUTCDate()) {
|
||||
classNames.push("selection");
|
||||
}
|
||||
if (dateObj.getTime() < this.state.min.getTime() || dateObj.getTime() > this.state.max.getTime()) {
|
||||
classNames.push("out-of-range");
|
||||
enabled = false;
|
||||
}
|
||||
if (this.state.today.getTime() == dateObj.getTime()) {
|
||||
classNames.push("today");
|
||||
}
|
||||
days.push({
|
||||
dateValue: dateObj.getTime(),
|
||||
textContent: dateObj.getUTCDate(),
|
||||
classNames
|
||||
dateObj,
|
||||
classNames,
|
||||
enabled,
|
||||
});
|
||||
}
|
||||
return days;
|
||||
|
|
@ -193,20 +231,21 @@ function DateKeeper({ year, month, day }, { firstDayOfWeek = 0, weekends = [0],
|
|||
/**
|
||||
* Get week headers for calendar
|
||||
* @param {Number} firstDayOfWeek
|
||||
* @param {Array<Number>} weekends
|
||||
* @return {Array<Object>}
|
||||
* {
|
||||
* {Number} textContent
|
||||
* {Array<String>} classNames
|
||||
* }
|
||||
*/
|
||||
_getWeekHeaders(firstDayOfWeek) {
|
||||
_getWeekHeaders(firstDayOfWeek, weekends) {
|
||||
let headers = [];
|
||||
let dayOfWeek = firstDayOfWeek;
|
||||
|
||||
for (let i = 0; i < DAYS_IN_A_WEEK; i++) {
|
||||
headers.push({
|
||||
textContent: dayOfWeek % DAYS_IN_A_WEEK,
|
||||
classNames: this.state.weekends.includes(dayOfWeek % DAYS_IN_A_WEEK) ? ["weekend"] : []
|
||||
classNames: weekends.includes(dayOfWeek % DAYS_IN_A_WEEK) ? ["weekend"] : []
|
||||
});
|
||||
dayOfWeek++;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,6 +20,12 @@ function DatePicker(context) {
|
|||
* {Number} year [optional]
|
||||
* {Number} month [optional]
|
||||
* {Number} date [optional]
|
||||
* {String} min
|
||||
* {String} max
|
||||
* {Number} firstDayOfWeek
|
||||
* {Array<Number>} weekends
|
||||
* {Array<String>} monthStrings
|
||||
* {Array<String>} weekdayStrings
|
||||
* {String} locale [optional]: User preferred locale
|
||||
* }
|
||||
*/
|
||||
|
|
@ -28,57 +34,54 @@ function DatePicker(context) {
|
|||
this._setDefaultState();
|
||||
this._createComponents();
|
||||
this._update();
|
||||
document.dispatchEvent(new CustomEvent("PickerReady"));
|
||||
},
|
||||
|
||||
/*
|
||||
* Set initial date picker states.
|
||||
*/
|
||||
_setDefaultState() {
|
||||
const now = new Date();
|
||||
const { year = now.getFullYear(),
|
||||
month = now.getMonth(),
|
||||
day = now.getDate(),
|
||||
locale } = this.props;
|
||||
|
||||
// TODO: Use calendar info API to get first day of week & weekends
|
||||
// (Bug 1287503)
|
||||
const { year, month, day, min, max, firstDayOfWeek, weekends,
|
||||
monthStrings, weekdayStrings, locale } = this.props;
|
||||
const dateKeeper = new DateKeeper({
|
||||
year, month, day
|
||||
}, {
|
||||
calViewSize: CAL_VIEW_SIZE,
|
||||
firstDayOfWeek: 0,
|
||||
weekends: [0]
|
||||
year, month, day, min, max, firstDayOfWeek, weekends,
|
||||
calViewSize: CAL_VIEW_SIZE
|
||||
});
|
||||
|
||||
this.state = {
|
||||
dateKeeper,
|
||||
locale,
|
||||
isMonthPickerVisible: false,
|
||||
isYearSet: false,
|
||||
isMonthSet: false,
|
||||
isDateSet: false,
|
||||
getDayString: new Intl.NumberFormat(locale).format,
|
||||
// TODO: use calendar terms when available (Bug 1287677)
|
||||
getWeekHeaderString: weekday => ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"][weekday],
|
||||
setValue: ({ dateValue, selectionValue }) => {
|
||||
dateKeeper.setValue(dateValue);
|
||||
this.state.selectionValue = selectionValue;
|
||||
this.state.isYearSet = true;
|
||||
this.state.isMonthSet = true;
|
||||
this.state.isDateSet = true;
|
||||
getWeekHeaderString: weekday => weekdayStrings[weekday],
|
||||
getMonthString: month => monthStrings[month],
|
||||
setSelection: date => {
|
||||
dateKeeper.setSelection({
|
||||
year: date.getUTCFullYear(),
|
||||
month: date.getUTCMonth(),
|
||||
day: date.getUTCDate(),
|
||||
});
|
||||
this._update();
|
||||
this._dispatchState();
|
||||
this._closePopup();
|
||||
},
|
||||
setYear: year => {
|
||||
dateKeeper.setYear(year);
|
||||
this.state.isYearSet = true;
|
||||
dateKeeper.setSelection({
|
||||
year,
|
||||
month: dateKeeper.selection.month,
|
||||
day: dateKeeper.selection.day,
|
||||
});
|
||||
this._update();
|
||||
this._dispatchState();
|
||||
},
|
||||
setMonth: month => {
|
||||
dateKeeper.setMonth(month);
|
||||
this.state.isMonthSet = true;
|
||||
dateKeeper.setSelection({
|
||||
year: dateKeeper.selection.year,
|
||||
month,
|
||||
day: dateKeeper.selection.day,
|
||||
});
|
||||
this._update();
|
||||
this._dispatchState();
|
||||
},
|
||||
|
|
@ -104,6 +107,7 @@ function DatePicker(context) {
|
|||
monthYear: new MonthYear({
|
||||
setYear: this.state.setYear,
|
||||
setMonth: this.state.setMonth,
|
||||
getMonthString: this.state.getMonthString,
|
||||
locale: this.state.locale
|
||||
}, {
|
||||
monthYear: this.context.monthYear,
|
||||
|
|
@ -116,7 +120,7 @@ function DatePicker(context) {
|
|||
* Update date picker and its components.
|
||||
*/
|
||||
_update() {
|
||||
const { dateKeeper, selectionValue, isMonthPickerVisible } = this.state;
|
||||
const { dateKeeper, isMonthPickerVisible } = this.state;
|
||||
|
||||
if (isMonthPickerVisible) {
|
||||
this.state.months = dateKeeper.getMonths();
|
||||
|
|
@ -128,9 +132,7 @@ function DatePicker(context) {
|
|||
this.components.monthYear.setProps({
|
||||
isVisible: isMonthPickerVisible,
|
||||
dateObj: dateKeeper.state.dateObj,
|
||||
month: dateKeeper.state.month,
|
||||
months: this.state.months,
|
||||
year: dateKeeper.state.year,
|
||||
years: this.state.years,
|
||||
toggleMonthPicker: this.state.toggleMonthPicker
|
||||
});
|
||||
|
|
@ -138,10 +140,9 @@ function DatePicker(context) {
|
|||
isVisible: !isMonthPickerVisible,
|
||||
days: this.state.days,
|
||||
weekHeaders: dateKeeper.state.weekHeaders,
|
||||
setValue: this.state.setValue,
|
||||
setSelection: this.state.setSelection,
|
||||
getDayString: this.state.getDayString,
|
||||
getWeekHeaderString: this.state.getWeekHeaderString,
|
||||
selectionValue
|
||||
getWeekHeaderString: this.state.getWeekHeaderString
|
||||
});
|
||||
|
||||
isMonthPickerVisible ?
|
||||
|
|
@ -162,8 +163,7 @@ function DatePicker(context) {
|
|||
* Use postMessage to pass the state of picker to the panel.
|
||||
*/
|
||||
_dispatchState() {
|
||||
const { year, month, day } = this.state.dateKeeper.state;
|
||||
const { isYearSet, isMonthSet, isDaySet } = this.state;
|
||||
const { year, month, day } = this.state.dateKeeper.selection;
|
||||
// The panel is listening to window for postMessage event, so we
|
||||
// do postMessage to itself to send data to input boxes.
|
||||
window.postMessage({
|
||||
|
|
@ -172,9 +172,6 @@ function DatePicker(context) {
|
|||
year,
|
||||
month,
|
||||
day,
|
||||
isYearSet,
|
||||
isMonthSet,
|
||||
isDaySet
|
||||
}
|
||||
}, "*");
|
||||
},
|
||||
|
|
@ -255,19 +252,12 @@ function DatePicker(context) {
|
|||
set({ year, month, day }) {
|
||||
const { dateKeeper } = this.state;
|
||||
|
||||
if (year != undefined) {
|
||||
this.state.isYearSet = true;
|
||||
}
|
||||
if (month != undefined) {
|
||||
this.state.isMonthSet = true;
|
||||
}
|
||||
if (day != undefined) {
|
||||
this.state.isDaySet = true;
|
||||
}
|
||||
|
||||
dateKeeper.set({
|
||||
year, month, day
|
||||
});
|
||||
dateKeeper.setSelection({
|
||||
year, month, day
|
||||
});
|
||||
this._update();
|
||||
}
|
||||
};
|
||||
|
|
@ -280,12 +270,12 @@ function DatePicker(context) {
|
|||
* {String} locale
|
||||
* {Function} setYear
|
||||
* {Function} setMonth
|
||||
* {Function} getMonthString
|
||||
* }
|
||||
* @param {DOMElement} context
|
||||
*/
|
||||
function MonthYear(options, context) {
|
||||
const spinnerSize = 5;
|
||||
const monthFormat = new Intl.DateTimeFormat(options.locale, { month: "short" }).format;
|
||||
const yearFormat = new Intl.DateTimeFormat(options.locale, { year: "numeric" }).format;
|
||||
const dateFormat = new Intl.DateTimeFormat(options.locale, { year: "numeric", month: "long" }).format;
|
||||
|
||||
|
|
@ -298,7 +288,7 @@ function DatePicker(context) {
|
|||
this.state.isMonthSet = true;
|
||||
options.setMonth(month);
|
||||
},
|
||||
getDisplayString: month => monthFormat(new Date(0, month)),
|
||||
getDisplayString: options.getMonthString,
|
||||
viewportSize: spinnerSize
|
||||
}, context.monthYearView),
|
||||
year: new Spinner({
|
||||
|
|
@ -323,8 +313,6 @@ function DatePicker(context) {
|
|||
* {
|
||||
* {Boolean} isVisible
|
||||
* {Date} dateObj
|
||||
* {Number} month
|
||||
* {Number} year
|
||||
* {Array<Object>} months
|
||||
* {Array<Object>} years
|
||||
* {Function} toggleMonthPicker
|
||||
|
|
@ -336,14 +324,14 @@ function DatePicker(context) {
|
|||
if (props.isVisible) {
|
||||
this.context.monthYear.classList.add("active");
|
||||
this.components.month.setState({
|
||||
value: props.month,
|
||||
value: props.dateObj.getUTCMonth(),
|
||||
items: props.months,
|
||||
isInfiniteScroll: true,
|
||||
isValueSet: this.state.isMonthSet,
|
||||
smoothScroll: !this.state.firstOpened
|
||||
});
|
||||
this.components.year.setState({
|
||||
value: props.year,
|
||||
value: props.dateObj.getUTCFullYear(),
|
||||
items: props.years,
|
||||
isInfiniteScroll: false,
|
||||
isValueSet: this.state.isYearSet,
|
||||
|
|
|
|||
|
|
@ -8,9 +8,17 @@
|
|||
.datetime-input-box-wrapper {
|
||||
-moz-appearance: none;
|
||||
display: inline-flex;
|
||||
flex: 1;
|
||||
cursor: default;
|
||||
background-color: inherit;
|
||||
color: inherit;
|
||||
min-width: 0;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.datetime-input-edit-wrapper {
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.datetime-input {
|
||||
|
|
@ -43,5 +51,5 @@
|
|||
height: 12px;
|
||||
width: 12px;
|
||||
align-self: center;
|
||||
justify-content: flex-end;
|
||||
flex: none;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,11 @@
|
|||
- 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 % datetimeboxDTD SYSTEM "chrome://global/locale/datetimebox.dtd">
|
||||
%datetimeboxDTD;
|
||||
]>
|
||||
|
||||
<bindings id="datetimeboxBindings"
|
||||
xmlns="http://www.mozilla.org/xbl"
|
||||
xmlns:html="http://www.w3.org/1999/xhtml"
|
||||
|
|
@ -21,12 +26,13 @@
|
|||
<implementation>
|
||||
<constructor>
|
||||
<![CDATA[
|
||||
// TODO: Bug 1320227 - [DateTimeInput] localization for
|
||||
// <input type=date> input box
|
||||
this.mMonthPlaceHolder = "mm";
|
||||
this.mDayPlaceHolder = "dd";
|
||||
this.mYearPlaceHolder = "yyyy";
|
||||
/* eslint-disable no-multi-spaces */
|
||||
this.mYearPlaceHolder = ]]>"&date.year.placeholder;"<![CDATA[;
|
||||
this.mMonthPlaceHolder = ]]>"&date.month.placeholder;"<![CDATA[;
|
||||
this.mDayPlaceHolder = ]]>"&date.day.placeholder;"<![CDATA[;
|
||||
this.mSeparatorText = "/";
|
||||
/* eslint-enable no-multi-spaces */
|
||||
|
||||
this.mMinMonth = 1;
|
||||
this.mMaxMonth = 12;
|
||||
this.mMinDay = 1;
|
||||
|
|
@ -76,6 +82,7 @@
|
|||
if (this.mInputElement.value) {
|
||||
this.setFieldsFromInputValue();
|
||||
}
|
||||
this.updateResetButtonVisibility();
|
||||
]]>
|
||||
</constructor>
|
||||
|
||||
|
|
@ -107,9 +114,11 @@
|
|||
this.mYearField.setAttribute("typeBuffer", "");
|
||||
}
|
||||
|
||||
if (!aFromInputElement) {
|
||||
if (!aFromInputElement && this.mInputElement.value) {
|
||||
this.mInputElement.setUserInput("");
|
||||
}
|
||||
|
||||
this.updateResetButtonVisibility();
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
|
|
@ -170,6 +179,13 @@
|
|||
<method name="setInputValueFromFields">
|
||||
<body>
|
||||
<![CDATA[
|
||||
if (!this.isAnyValueAvailable(false) && this.mInputElement.value) {
|
||||
// Values in the input box was cleared, clear the input element's
|
||||
// value if not empty.
|
||||
this.mInputElement.setUserInput("");
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.isFieldInvalid(this.mYearField) ||
|
||||
this.isFieldInvalid(this.mMonthField) ||
|
||||
this.isFieldInvalid(this.mDayField)) {
|
||||
|
|
@ -192,6 +208,10 @@
|
|||
|
||||
let date = [year, month, day].join("-");
|
||||
|
||||
if (date == this.mInputElement.value) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.log("setInputValueFromFields: " + date);
|
||||
this.mInputElement.setUserInput(date);
|
||||
]]>
|
||||
|
|
@ -217,6 +237,9 @@
|
|||
if (!this.isEmpty(day)) {
|
||||
this.setFieldValue(this.mDayField, day);
|
||||
}
|
||||
|
||||
// Update input element's .value if needed.
|
||||
this.setInputValueFromFields();
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
|
|
@ -302,6 +325,12 @@
|
|||
let targetField = aEvent.originalTarget;
|
||||
let key = aEvent.key;
|
||||
|
||||
// Home/End key does nothing on year field.
|
||||
if (targetField == this.mYearField && (key == "Home" ||
|
||||
key == "End")) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (key) {
|
||||
case "ArrowUp":
|
||||
this.incrementFieldValue(targetField, 1);
|
||||
|
|
@ -406,11 +435,13 @@
|
|||
}
|
||||
|
||||
aField.value = value;
|
||||
this.updateResetButtonVisibility();
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
|
||||
<method name="isValueAvailable">
|
||||
<method name="isAnyValueAvailable">
|
||||
<parameter name="aForPicker"/>
|
||||
<body>
|
||||
<![CDATA[
|
||||
return !this.isEmpty(this.mMonthField.value) ||
|
||||
|
|
@ -492,6 +523,7 @@
|
|||
if (this.mInputElement.value) {
|
||||
this.setFieldsFromInputValue();
|
||||
}
|
||||
this.updateResetButtonVisibility();
|
||||
]]>
|
||||
</constructor>
|
||||
|
||||
|
|
@ -551,7 +583,7 @@
|
|||
}
|
||||
|
||||
this.log("setFieldsFromInputValue: " + value);
|
||||
let [hour, minute, second] = value.split(':');
|
||||
let [hour, minute, second] = value.split(":");
|
||||
|
||||
this.setFieldValue(this.mHourField, hour);
|
||||
this.setFieldValue(this.mMinuteField, minute);
|
||||
|
|
@ -617,6 +649,13 @@
|
|||
<method name="setInputValueFromFields">
|
||||
<body>
|
||||
<![CDATA[
|
||||
if (!this.isAnyValueAvailable(false) && this.mInputElement.value) {
|
||||
// Values in the input box was cleared, clear the input element's
|
||||
// value if not empty.
|
||||
this.mInputElement.setUserInput("");
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.isEmpty(this.mHourField.value) ||
|
||||
this.isEmpty(this.mMinuteField.value) ||
|
||||
(this.mDayPeriodField && this.isEmpty(this.mDayPeriodField.value)) ||
|
||||
|
|
@ -652,6 +691,10 @@
|
|||
time += "." + this.mMillisecField.value;
|
||||
}
|
||||
|
||||
if (time == this.mInputElement.value) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.log("setInputValueFromFields: " + time);
|
||||
this.mInputElement.setUserInput(time);
|
||||
]]>
|
||||
|
|
@ -678,6 +721,9 @@
|
|||
if (!this.isEmpty(minute)) {
|
||||
this.setFieldValue(this.mMinuteField, minute);
|
||||
}
|
||||
|
||||
// Update input element's .value if needed.
|
||||
this.setInputValueFromFields();
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
|
|
@ -721,9 +767,11 @@
|
|||
this.mDayPeriodField.value = "";
|
||||
}
|
||||
|
||||
if (!aFromInputElement) {
|
||||
if (!aFromInputElement && this.mInputElement.value) {
|
||||
this.mInputElement.setUserInput("");
|
||||
}
|
||||
|
||||
this.updateResetButtonVisibility();
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
|
|
@ -793,6 +841,7 @@
|
|||
this.mDayPeriodField.value == this.mAMIndicator ?
|
||||
this.mPMIndicator : this.mAMIndicator;
|
||||
this.mDayPeriodField.select();
|
||||
this.updateResetButtonVisibility();
|
||||
this.setInputValueFromFields();
|
||||
return;
|
||||
}
|
||||
|
|
@ -850,6 +899,7 @@
|
|||
this.mDayPeriodField.value = this.mPMIndicator;
|
||||
this.mDayPeriodField.select();
|
||||
}
|
||||
this.updateResetButtonVisibility();
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -905,16 +955,30 @@
|
|||
}
|
||||
|
||||
aField.value = value;
|
||||
this.updateResetButtonVisibility();
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
|
||||
<method name="isValueAvailable">
|
||||
<method name="isAnyValueAvailable">
|
||||
<parameter name="aForPicker"/>
|
||||
<body>
|
||||
<![CDATA[
|
||||
let available = !this.isEmpty(this.mHourField.value) ||
|
||||
!this.isEmpty(this.mMinuteField.value);
|
||||
|
||||
if (available) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Picker only cares about hour:minute.
|
||||
return !this.isEmpty(this.mHourField.value) ||
|
||||
!this.isEmpty(this.mMinuteField.value);
|
||||
if (aForPicker) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return (this.mDayPeriodField && !this.isEmpty(this.mDayPeriodField.value)) ||
|
||||
(this.mSecondField && !this.isEmpty(this.mSecondField.value)) ||
|
||||
(this.mMillisecField && !this.isEmpty(this.mMillisecField.value));
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
|
|
@ -963,7 +1027,8 @@
|
|||
<content>
|
||||
<html:div class="datetime-input-box-wrapper"
|
||||
xbl:inherits="context,disabled,readonly">
|
||||
<html:span>
|
||||
<html:span class="datetime-input-edit-wrapper"
|
||||
anonid="edit-wrapper">
|
||||
<html:input anonid="input-one"
|
||||
class="textbox-input datetime-input numeric"
|
||||
size="2" maxlength="2"
|
||||
|
|
@ -980,9 +1045,8 @@
|
|||
xbl:inherits="disabled,readonly,tabindex"/>
|
||||
</html:span>
|
||||
|
||||
<html:button class="datetime-reset-button" anoid="reset-button"
|
||||
tabindex="-1" xbl:inherits="disabled"
|
||||
onclick="document.getBindingParent(this).clearInputFields(false);"/>
|
||||
<html:button class="datetime-reset-button" anonid="reset-button"
|
||||
tabindex="-1" xbl:inherits="disabled"/>
|
||||
</html:div>
|
||||
</content>
|
||||
|
||||
|
|
@ -997,6 +1061,9 @@
|
|||
this.mStep = this.mInputElement.step;
|
||||
this.mIsPickerOpen = false;
|
||||
|
||||
this.mResetButton =
|
||||
document.getAnonymousElementByAttribute(this, "anonid", "reset-button");
|
||||
|
||||
this.EVENTS.forEach((eventName) => {
|
||||
this.addEventListener(eventName, this, { mozSystemGroup: true });
|
||||
});
|
||||
|
|
@ -1005,6 +1072,9 @@
|
|||
capture: true,
|
||||
mozSystemGroup: true
|
||||
});
|
||||
// This is to close the picker when input element blurs.
|
||||
this.mInputElement.addEventListener("blur", this,
|
||||
{ mozSystemGroup: true });
|
||||
]]>
|
||||
</constructor>
|
||||
|
||||
|
|
@ -1025,7 +1095,7 @@
|
|||
<property name="EVENTS" readonly="true">
|
||||
<getter>
|
||||
<![CDATA[
|
||||
return ["click", "focus", "blur", "copy", "cut", "paste"];
|
||||
return ["click", "focus", "blur", "copy", "cut", "paste", "mousedown"];
|
||||
]]>
|
||||
</getter>
|
||||
</property>
|
||||
|
|
@ -1041,6 +1111,18 @@
|
|||
</body>
|
||||
</method>
|
||||
|
||||
<method name="updateResetButtonVisibility">
|
||||
<body>
|
||||
<![CDATA[
|
||||
if (this.isAnyValueAvailable(false)) {
|
||||
this.mResetButton.style.visibility = "visible";
|
||||
} else {
|
||||
this.mResetButton.style.visibility = "hidden";
|
||||
}
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
|
||||
<method name="focusInnerTextBox">
|
||||
<body>
|
||||
<![CDATA[
|
||||
|
|
@ -1164,10 +1246,16 @@
|
|||
</body>
|
||||
</method>
|
||||
|
||||
<method name="isAnyValueAvailable">
|
||||
<body>
|
||||
throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
|
||||
</body>
|
||||
</method>
|
||||
|
||||
<method name="notifyPicker">
|
||||
<body>
|
||||
<![CDATA[
|
||||
if (this.mIsPickerOpen && this.isValueAvailable()) {
|
||||
if (this.mIsPickerOpen && this.isAnyValueAvailable(true)) {
|
||||
this.mInputElement.updateDateTimePicker(this.getCurrentValue());
|
||||
}
|
||||
]]>
|
||||
|
|
@ -1213,6 +1301,12 @@
|
|||
this.onBlur(aEvent);
|
||||
break;
|
||||
}
|
||||
case "mousedown": {
|
||||
if (aEvent.originalTarget == this.mResetButton) {
|
||||
aEvent.preventDefault();
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "copy":
|
||||
case "cut":
|
||||
case "paste": {
|
||||
|
|
@ -1245,7 +1339,12 @@
|
|||
<parameter name="aEvent"/>
|
||||
<body>
|
||||
<![CDATA[
|
||||
this.log("onBlur originalTarget: " + aEvent.originalTarget);
|
||||
this.log("onBlur originalTarget: " + aEvent.originalTarget +
|
||||
" target: " + aEvent.target);
|
||||
|
||||
if (aEvent.target == this.mInputElement && this.mIsPickerOpen) {
|
||||
this.mInputElement.closeDateTimePicker();
|
||||
}
|
||||
|
||||
let target = aEvent.originalTarget;
|
||||
target.setAttribute("typeBuffer", "");
|
||||
|
|
@ -1261,16 +1360,23 @@
|
|||
this.log("onKeyPress key: " + aEvent.key);
|
||||
|
||||
switch (aEvent.key) {
|
||||
// Close picker on Enter or Space key.
|
||||
// Close picker on Enter, Escape or Space key.
|
||||
case "Enter":
|
||||
case "Escape":
|
||||
case " ": {
|
||||
this.mInputElement.closeDateTimePicker();
|
||||
aEvent.preventDefault();
|
||||
if (this.mIsPickerOpen) {
|
||||
this.mInputElement.closeDateTimePicker();
|
||||
aEvent.preventDefault();
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "Backspace": {
|
||||
let targetField = aEvent.originalTarget;
|
||||
targetField.value = "";
|
||||
targetField.setAttribute("typeBuffer", "");
|
||||
this.updateResetButtonVisibility();
|
||||
this.setInputValueFromFields();
|
||||
aEvent.preventDefault();
|
||||
break;
|
||||
}
|
||||
case "ArrowRight":
|
||||
|
|
@ -1312,12 +1418,14 @@
|
|||
// XXX: .originalTarget is not expected.
|
||||
// When clicking on one of the inner text boxes, the .originalTarget is
|
||||
// a HTMLDivElement and when clicking on the reset button, it's a
|
||||
// HTMLButtonElement but it's not equal to our reset-button.
|
||||
// HTMLButtonElement.
|
||||
if (aEvent.defaultPrevented || this.isDisabled() || this.isReadonly()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!(aEvent.originalTarget instanceof HTMLButtonElement)) {
|
||||
if (aEvent.originalTarget == this.mResetButton) {
|
||||
this.clearInputFields(false);
|
||||
} else if (!this.mIsPickerOpen) {
|
||||
this.mInputElement.openDateTimePicker(this.getCurrentValue());
|
||||
}
|
||||
]]>
|
||||
|
|
|
|||
|
|
@ -999,13 +999,13 @@
|
|||
<body>
|
||||
<![CDATA[
|
||||
var locale = Intl.DateTimeFormat().resolvedOptions().locale + "-u-ca-gregory";
|
||||
var dtfMonth = Intl.DateTimeFormat(locale, {month: "long"});
|
||||
var dtfMonth = Intl.DateTimeFormat(locale, {month: "long", timeZone: "UTC"});
|
||||
var dtfWeekday = Intl.DateTimeFormat(locale, {weekday: "narrow"});
|
||||
|
||||
var monthLabel = this.monthField.firstChild;
|
||||
var tempDate = new Date(2005, 0, 1);
|
||||
var tempDate = new Date(Date.UTC(2005, 0, 1));
|
||||
for (var month = 0; month < 12; month++) {
|
||||
tempDate.setMonth(month);
|
||||
tempDate.setUTCMonth(month);
|
||||
monthLabel.setAttribute("value", dtfMonth.format(tempDate));
|
||||
monthLabel = monthLabel.nextSibling;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,11 +22,20 @@
|
|||
<field name="TIME_PICKER_HEIGHT" readonly="true">"21em"</field>
|
||||
<field name="DATE_PICKER_WIDTH" readonly="true">"23.1em"</field>
|
||||
<field name="DATE_PICKER_HEIGHT" readonly="true">"20.7em"</field>
|
||||
<method name="loadPicker">
|
||||
<constructor><![CDATA[
|
||||
this.l10n = {};
|
||||
const mozIntl = Components.classes["@mozilla.org/mozintl;1"]
|
||||
.getService(Components.interfaces.mozIMozIntl);
|
||||
mozIntl.addGetCalendarInfo(l10n);
|
||||
mozIntl.addGetDisplayNames(l10n);
|
||||
// Notify DateTimePickerHelper.jsm that binding is ready.
|
||||
this.dispatchEvent(new CustomEvent("DateTimePickerBindingReady"));
|
||||
]]></constructor>
|
||||
<method name="openPicker">
|
||||
<parameter name="type"/>
|
||||
<parameter name="anchor"/>
|
||||
<parameter name="detail"/>
|
||||
<body><![CDATA[
|
||||
this.hidden = false;
|
||||
this.type = type;
|
||||
this.pickerState = {};
|
||||
// TODO: Resize picker according to content zoom level
|
||||
|
|
@ -49,17 +58,20 @@
|
|||
break;
|
||||
}
|
||||
}
|
||||
this.hidden = false;
|
||||
this.openPopup(anchor, "after_start", 0, 0);
|
||||
]]></body>
|
||||
</method>
|
||||
<method name="closePicker">
|
||||
<body><![CDATA[
|
||||
this.hidden = true;
|
||||
this.setInputBoxValue(true);
|
||||
this.pickerState = {};
|
||||
this.type = undefined;
|
||||
this.dateTimePopupFrame.removeEventListener("load", this, true);
|
||||
this.dateTimePopupFrame.contentDocument.removeEventListener("message", this, false);
|
||||
this.dateTimePopupFrame.setAttribute("src", "");
|
||||
this.hidePopup();
|
||||
this.hidden = true;
|
||||
]]></body>
|
||||
</method>
|
||||
<method name="setPopupValue">
|
||||
|
|
@ -115,6 +127,34 @@
|
|||
}
|
||||
case "date": {
|
||||
const { year, month, day } = detail.value;
|
||||
const { firstDayOfWeek, weekends } =
|
||||
this.getCalendarInfo(locale);
|
||||
const monthStrings = this.getDisplayNames(
|
||||
locale, [
|
||||
"dates/gregorian/months/january",
|
||||
"dates/gregorian/months/february",
|
||||
"dates/gregorian/months/march",
|
||||
"dates/gregorian/months/april",
|
||||
"dates/gregorian/months/may",
|
||||
"dates/gregorian/months/june",
|
||||
"dates/gregorian/months/july",
|
||||
"dates/gregorian/months/august",
|
||||
"dates/gregorian/months/september",
|
||||
"dates/gregorian/months/october",
|
||||
"dates/gregorian/months/november",
|
||||
"dates/gregorian/months/december",
|
||||
], "short");
|
||||
const weekdayStrings = this.getDisplayNames(
|
||||
locale, [
|
||||
"dates/gregorian/weekdays/sunday",
|
||||
"dates/gregorian/weekdays/monday",
|
||||
"dates/gregorian/weekdays/tuesday",
|
||||
"dates/gregorian/weekdays/wednesday",
|
||||
"dates/gregorian/weekdays/thursday",
|
||||
"dates/gregorian/weekdays/friday",
|
||||
"dates/gregorian/weekdays/saturday",
|
||||
], "short");
|
||||
|
||||
this.postMessageToPicker({
|
||||
name: "PickerInit",
|
||||
detail: {
|
||||
|
|
@ -122,7 +162,13 @@
|
|||
// Month value from input box starts from 1 instead of 0
|
||||
month: month == undefined ? undefined : month - 1,
|
||||
day,
|
||||
locale
|
||||
firstDayOfWeek,
|
||||
weekends,
|
||||
monthStrings,
|
||||
weekdayStrings,
|
||||
locale,
|
||||
min: detail.min,
|
||||
max: detail.max,
|
||||
}
|
||||
});
|
||||
break;
|
||||
|
|
@ -184,6 +230,46 @@
|
|||
}
|
||||
]]></body>
|
||||
</method>
|
||||
<method name="getCalendarInfo">
|
||||
<parameter name="locale"/>
|
||||
<body><![CDATA[
|
||||
const calendarInfo = this.l10n.getCalendarInfo(locale);
|
||||
|
||||
// Day of week from calendarInfo starts from 1 as Sunday to 7 as Saturday,
|
||||
// so they need to be mapped to JavaScript convention with 0 as Sunday
|
||||
// and 6 as Saturday
|
||||
let firstDayOfWeek = calendarInfo.firstDayOfWeek - 1,
|
||||
weekendStart = calendarInfo.weekendStart - 1,
|
||||
weekendEnd = calendarInfo.weekendEnd - 1;
|
||||
|
||||
let weekends = [];
|
||||
|
||||
// Make sure weekendEnd is greater than weekendStart
|
||||
if (weekendEnd < weekendStart) {
|
||||
weekendEnd += 7;
|
||||
}
|
||||
|
||||
// We get the weekends by incrementing weekendStart up to weekendEnd.
|
||||
// If the start and end is the same day, then weekends only has one day.
|
||||
for (let day = weekendStart; day <= weekendEnd; day++) {
|
||||
weekends.push(day % 7);
|
||||
}
|
||||
|
||||
return {
|
||||
firstDayOfWeek,
|
||||
weekends
|
||||
}
|
||||
]]></body>
|
||||
</method>
|
||||
<method name="getDisplayNames">
|
||||
<parameter name="locale"/>
|
||||
<parameter name="keys"/>
|
||||
<parameter name="style"/>
|
||||
<body><![CDATA[
|
||||
const displayNames = this.l10n.getDisplayNames(locale, {keys, style});
|
||||
return keys.map(key => displayNames.values[key]);
|
||||
]]></body>
|
||||
</method>
|
||||
<method name="handleEvent">
|
||||
<parameter name="aEvent"/>
|
||||
<body><![CDATA[
|
||||
|
|
@ -230,12 +316,5 @@
|
|||
</method>
|
||||
|
||||
</implementation>
|
||||
<handlers>
|
||||
<handler event="popuphiding">
|
||||
<![CDATA[
|
||||
this.closePicker();
|
||||
]]>
|
||||
</handler>
|
||||
</handlers>
|
||||
</binding>
|
||||
</bindings>
|
||||
|
|
|
|||
|
|
@ -299,11 +299,11 @@ function Spinner(props, context) {
|
|||
// An "active" class is needed to simulate :active pseudo-class
|
||||
// because element is not focused.
|
||||
event.target.classList.add("active");
|
||||
this._smoothScrollToIndex(index + 1);
|
||||
this._smoothScrollToIndex(index - 1);
|
||||
}
|
||||
if (event.target == down) {
|
||||
event.target.classList.add("active");
|
||||
this._smoothScrollToIndex(index - 1);
|
||||
this._smoothScrollToIndex(index + 1);
|
||||
}
|
||||
if (event.target.parentNode == spinner) {
|
||||
// Listen to dragging events
|
||||
|
|
|
|||
9
toolkit/locales/en-US/chrome/global/datetimebox.dtd
Normal file
9
toolkit/locales/en-US/chrome/global/datetimebox.dtd
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
<!-- 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/. -->
|
||||
|
||||
<!-- Placeholders for input type=date -->
|
||||
|
||||
<!ENTITY date.year.placeholder "yyyy">
|
||||
<!ENTITY date.month.placeholder "mm">
|
||||
<!ENTITY date.day.placeholder "dd">
|
||||
|
|
@ -39,6 +39,7 @@
|
|||
locale/@AB_CD@/global/customizeToolbar.dtd (%chrome/global/customizeToolbar.dtd)
|
||||
locale/@AB_CD@/global/customizeToolbar.properties (%chrome/global/customizeToolbar.properties)
|
||||
#endif
|
||||
locale/@AB_CD@/global/datetimebox.dtd (%chrome/global/datetimebox.dtd)
|
||||
locale/@AB_CD@/global/datetimepicker.dtd (%chrome/global/datetimepicker.dtd)
|
||||
locale/@AB_CD@/global/dateFormat.properties (%chrome/global/dateFormat.properties)
|
||||
locale/@AB_CD@/global/dialogOverlay.dtd (%chrome/global/dialogOverlay.dtd)
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ this.EXPORTED_SYMBOLS = [
|
|||
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
Cu.import("resource://gre/modules/Task.jsm");
|
||||
|
||||
/*
|
||||
* DateTimePickerHelper receives message from content side (input box) and
|
||||
|
|
@ -66,6 +67,9 @@ this.DateTimePickerHelper = {
|
|||
break;
|
||||
}
|
||||
case "FormDateTime:UpdatePicker": {
|
||||
if (!this.picker) {
|
||||
return;
|
||||
}
|
||||
this.picker.setPopupValue(aMessage.data);
|
||||
break;
|
||||
}
|
||||
|
|
@ -105,7 +109,7 @@ this.DateTimePickerHelper = {
|
|||
},
|
||||
|
||||
// Get picker from browser and show it anchored to the input box.
|
||||
showPicker: function(aBrowser, aData) {
|
||||
showPicker: Task.async(function* (aBrowser, aData) {
|
||||
let rect = aData.rect;
|
||||
let dir = aData.dir;
|
||||
let type = aData.type;
|
||||
|
|
@ -135,13 +139,23 @@ this.DateTimePickerHelper = {
|
|||
debug("aBrowser.dateTimePicker not found, exiting now.");
|
||||
return;
|
||||
}
|
||||
this.picker.loadPicker(type, detail);
|
||||
// The datetimepopup binding is only attached when it is needed.
|
||||
// Check if openPicker method is present to determine if binding has
|
||||
// been attached. If not, attach the binding first before calling it.
|
||||
if (!this.picker.openPicker) {
|
||||
let bindingPromise = new Promise(resolve => {
|
||||
this.picker.addEventListener("DateTimePickerBindingReady",
|
||||
resolve, {once: true});
|
||||
});
|
||||
this.picker.setAttribute("active", true);
|
||||
yield bindingPromise;
|
||||
}
|
||||
// The arrow panel needs an anchor to work. The popupAnchor (this._anchor)
|
||||
// is a transparent div that the arrow can point to.
|
||||
this.picker.openPopup(this._anchor, "after_start", 0, 0);
|
||||
this.picker.openPicker(type, this._anchor, detail);
|
||||
|
||||
this.addPickerListeners();
|
||||
},
|
||||
}),
|
||||
|
||||
// Picker is closed, do some cleanup.
|
||||
close: function() {
|
||||
|
|
|
|||
|
|
@ -12,7 +12,8 @@
|
|||
--colon-width: 2rem;
|
||||
--day-period-spacing-width: 1rem;
|
||||
--calendar-width: 23.1rem;
|
||||
--date-picker-item-height: 2.4rem;
|
||||
--date-picker-item-height: 2.5rem;
|
||||
--date-picker-item-width: 3.3rem;
|
||||
|
||||
--border: 0.1rem solid #D6D6D6;
|
||||
--border-radius: 0.3rem;
|
||||
|
|
@ -21,6 +22,8 @@
|
|||
--font-color: #191919;
|
||||
--fill-color: #EBEBEB;
|
||||
|
||||
--today-fill-color: rgb(212, 212, 212);
|
||||
|
||||
--selected-font-color: #FFFFFF;
|
||||
--selected-fill-color: #0996F8;
|
||||
|
||||
|
|
@ -29,10 +32,16 @@
|
|||
--button-font-color-active: #191919;
|
||||
--button-fill-color-active: #D4D4D4;
|
||||
|
||||
--weekday-font-color: #6C6C6C;
|
||||
--weekday-outside-font-color: #6C6C6C;
|
||||
--weekend-font-color: #DA4E44;
|
||||
--weekend-outside-font-color: #FF988F;
|
||||
--weekday-header-font-color: #6C6C6C;
|
||||
--weekend-header-font-color: rgb(218, 78, 68);
|
||||
|
||||
--weekend-font-color: rgb(218, 78, 68);
|
||||
--weekday-outside-font-color: rgb(153, 153, 153);
|
||||
--weekend-outside-font-color: rgb(255, 152, 143);
|
||||
|
||||
--weekday-disabled-font-color: rgba(25, 25, 25, 0.2);
|
||||
--weekend-disabled-font-color: rgba(218, 78, 68, 0.2);
|
||||
--disabled-fill-color: rgba(235, 235, 235, 0.8);
|
||||
|
||||
--disabled-opacity: 0.2;
|
||||
}
|
||||
|
|
@ -181,11 +190,11 @@ button.month-year.active::after {
|
|||
}
|
||||
|
||||
.week-header > div {
|
||||
color: var(--weekday-font-color);
|
||||
color: var(--weekday-header-font-color);
|
||||
}
|
||||
|
||||
.week-header > div.weekend {
|
||||
color: var(--weekend-font-color);
|
||||
color: var(--weekend-header-font-color);
|
||||
}
|
||||
|
||||
.days-viewport {
|
||||
|
|
@ -206,24 +215,49 @@ button.month-year.active::after {
|
|||
align-items: center;
|
||||
display: flex;
|
||||
height: var(--date-picker-item-height);
|
||||
margin: 0.05rem 0.15rem;
|
||||
position: relative;
|
||||
justify-content: center;
|
||||
width: 3rem;
|
||||
width: var(--date-picker-item-width);
|
||||
}
|
||||
|
||||
.days-view > div.outside {
|
||||
.days-view > .outside {
|
||||
color: var(--weekday-outside-font-color);
|
||||
}
|
||||
|
||||
.days-view > div.weekend {
|
||||
.days-view > .weekend {
|
||||
color: var(--weekend-font-color);
|
||||
}
|
||||
|
||||
.days-view > div.weekend.outside {
|
||||
.days-view > .weekend.outside {
|
||||
color: var(--weekend-outside-font-color);
|
||||
}
|
||||
|
||||
.days-view > .out-of-range {
|
||||
color: var(--weekday-disabled-font-color);
|
||||
background: var(--disabled-fill-color);
|
||||
}
|
||||
|
||||
.days-view > .out-of-range.weekend {
|
||||
color: var(--weekend-disabled-font-color);
|
||||
}
|
||||
|
||||
.days-view > .today {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.days-view > .out-of-range::before {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.days-view > div:hover::before,
|
||||
.days-view > .select::before,
|
||||
.days-view > .today::before {
|
||||
top: 5%;
|
||||
bottom: 5%;
|
||||
left: 5%;
|
||||
right: 5%;
|
||||
}
|
||||
|
||||
#time-picker,
|
||||
.month-year-view {
|
||||
display: flex;
|
||||
|
|
@ -283,22 +317,31 @@ button.month-year.active::after {
|
|||
scroll-snap-coordinate: 0 0;
|
||||
}
|
||||
|
||||
.spinner-container > .spinner > div::before,
|
||||
.calendar-container .days-view > div::before {
|
||||
position: absolute;
|
||||
top: 5%;
|
||||
bottom: 5%;
|
||||
left: 5%;
|
||||
right: 5%;
|
||||
z-index: -10;
|
||||
border-radius: var(--border-radius);
|
||||
}
|
||||
|
||||
.spinner-container > .spinner > div:hover::before,
|
||||
.calendar-container .days-view > div:hover::before {
|
||||
background: var(--fill-color);
|
||||
border: var(--border);
|
||||
border-radius: var(--border-radius);
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 0%;
|
||||
bottom: 0%;
|
||||
left: 0%;
|
||||
right: 0%;
|
||||
z-index: -10;
|
||||
}
|
||||
|
||||
.calendar-container .days-view > div.today::before {
|
||||
background: var(--today-fill-color);
|
||||
content: "";
|
||||
}
|
||||
|
||||
.spinner-container > .spinner:not(.scrolling) > div.selection,
|
||||
.calendar-container .days-view > div.selection {
|
||||
.calendar-container .days-view > div.selection:not(.out-of-range) {
|
||||
color: var(--selected-font-color);
|
||||
}
|
||||
|
||||
|
|
@ -306,14 +349,7 @@ button.month-year.active::after {
|
|||
.calendar-container .days-view > div.selection::before {
|
||||
background: var(--selected-fill-color);
|
||||
border: none;
|
||||
border-radius: var(--border-radius);
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 0%;
|
||||
bottom: 0%;
|
||||
left: 0%;
|
||||
right: 0%;
|
||||
z-index: -10;
|
||||
}
|
||||
|
||||
.spinner-container > .spinner > div.disabled::before,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue