зеркало из https://github.com/mozilla/gecko-dev.git
Bug 843725 - Add support for changing the value of <input type=range> using the up/down/left/right/pgup/pgdn/home/end keys. r=mounir, r=smaug.
This commit is contained in:
Родитель
c1935f174a
Коммит
ad65ad92ae
|
@ -2569,6 +2569,17 @@ SelectTextFieldOnFocus()
|
|||
return gSelectTextFieldOnFocus == 1;
|
||||
}
|
||||
|
||||
static bool
|
||||
IsLTR(Element* aElement)
|
||||
{
|
||||
nsIFrame *frame = aElement->GetPrimaryFrame();
|
||||
if (frame) {
|
||||
return frame->StyleVisibility()->mDirection == NS_STYLE_DIRECTION_LTR;
|
||||
}
|
||||
// at least for HTML, directionality is exclusively LTR or RTL
|
||||
return aElement->GetDirectionality() == eDir_LTR;
|
||||
}
|
||||
|
||||
nsresult
|
||||
nsHTMLInputElement::PostHandleEvent(nsEventChainPostVisitor& aVisitor)
|
||||
{
|
||||
|
@ -2835,6 +2846,74 @@ nsHTMLInputElement::PostHandleEvent(nsEventChainPostVisitor& aVisitor)
|
|||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
}
|
||||
|
||||
if (aVisitor.mEvent->message == NS_KEY_PRESS &&
|
||||
mType == NS_FORM_INPUT_RANGE && !keyEvent->IsAlt() &&
|
||||
!keyEvent->IsControl() && !keyEvent->IsMeta() &&
|
||||
(keyEvent->keyCode == NS_VK_LEFT ||
|
||||
keyEvent->keyCode == NS_VK_RIGHT ||
|
||||
keyEvent->keyCode == NS_VK_UP ||
|
||||
keyEvent->keyCode == NS_VK_DOWN ||
|
||||
keyEvent->keyCode == NS_VK_PAGE_UP ||
|
||||
keyEvent->keyCode == NS_VK_PAGE_DOWN ||
|
||||
keyEvent->keyCode == NS_VK_HOME ||
|
||||
keyEvent->keyCode == NS_VK_END)) {
|
||||
double minimum = GetMinimum();
|
||||
double maximum = GetMaximum();
|
||||
MOZ_ASSERT(MOZ_DOUBLE_IS_FINITE(minimum) &&
|
||||
MOZ_DOUBLE_IS_FINITE(maximum));
|
||||
if (minimum < maximum) { // else the value is locked to the minimum
|
||||
double value = GetValueAsDouble();
|
||||
double step = GetStep();
|
||||
if (step == kStepAny) {
|
||||
step = GetDefaultStep();
|
||||
}
|
||||
MOZ_ASSERT(MOZ_DOUBLE_IS_FINITE(value) &&
|
||||
MOZ_DOUBLE_IS_FINITE(step));
|
||||
double newValue;
|
||||
switch (keyEvent->keyCode) {
|
||||
case NS_VK_LEFT:
|
||||
newValue = value + (IsLTR(this) ? -step : step);
|
||||
break;
|
||||
case NS_VK_RIGHT:
|
||||
newValue = value + (IsLTR(this) ? step : -step);
|
||||
break;
|
||||
case NS_VK_UP:
|
||||
// Even for horizontal range, "up" means "increase"
|
||||
newValue = value + step;
|
||||
break;
|
||||
case NS_VK_DOWN:
|
||||
// Even for horizontal range, "down" means "decrease"
|
||||
newValue = value - step;
|
||||
break;
|
||||
case NS_VK_HOME:
|
||||
newValue = minimum;
|
||||
break;
|
||||
case NS_VK_END:
|
||||
newValue = maximum;
|
||||
break;
|
||||
case NS_VK_PAGE_UP:
|
||||
// For PgUp/PgDn we jump 10% of the total range, unless step
|
||||
// requires us to jump more.
|
||||
newValue = value + std::max(step, 0.1 * (maximum - minimum));
|
||||
break;
|
||||
case NS_VK_PAGE_DOWN:
|
||||
newValue = value - std::max(step, 0.1 * (maximum - minimum));
|
||||
break;
|
||||
}
|
||||
MOZ_ASSERT(MOZ_DOUBLE_IS_FINITE(newValue));
|
||||
nsAutoString val;
|
||||
ConvertNumberToString(newValue, val);
|
||||
SetValueInternal(val, true, true);
|
||||
nsIFrame* frame = GetPrimaryFrame();
|
||||
if (frame) {
|
||||
// Trigger reflow to update the position of the thumb:
|
||||
frame->PresContext()->GetPresShell()->
|
||||
FrameNeedsReflow(frame, nsIPresShell::eResize, NS_FRAME_IS_DIRTY);
|
||||
}
|
||||
aVisitor.mEventStatus = nsEventStatus_eConsumeNoDefault;
|
||||
}
|
||||
}
|
||||
|
||||
} break; // NS_KEY_PRESS || NS_KEY_UP
|
||||
|
||||
case NS_MOUSE_BUTTON_DOWN:
|
||||
|
@ -4353,7 +4432,8 @@ nsHTMLInputElement::IsHTMLFocusable(bool aWithMouse, bool *aIsFocusable, int32_t
|
|||
return true;
|
||||
}
|
||||
|
||||
if (IsSingleLineTextControl(false)) {
|
||||
if (IsSingleLineTextControl(false) ||
|
||||
mType == NS_FORM_INPUT_RANGE) {
|
||||
*aIsFocusable = true;
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -18,6 +18,7 @@ MOCHITEST_FILES = \
|
|||
test_input_attributes_reflection.html \
|
||||
test_input_list_attribute.html \
|
||||
test_input_email.html \
|
||||
test_input_range_key_events.html \
|
||||
test_input_url.html \
|
||||
test_pattern_attribute.html \
|
||||
test_required_attribute.html \
|
||||
|
|
|
@ -0,0 +1,206 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<!--
|
||||
https://bugzilla.mozilla.org/show_bug.cgi?id=843725
|
||||
-->
|
||||
<head>
|
||||
<title>Test key events for range</title>
|
||||
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
|
||||
<meta charset="UTF-8">
|
||||
</head>
|
||||
<body>
|
||||
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=843725">Mozilla Bug 843725</a>
|
||||
<p id="display"></p>
|
||||
<div id="content">
|
||||
</div>
|
||||
<pre id="test">
|
||||
<script type="application/javascript">
|
||||
|
||||
/**
|
||||
* Test for Bug 843725
|
||||
* This test checks how the value of <input type=range> changes in response to
|
||||
* various key events while it is in various states.
|
||||
**/
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
SimpleTest.waitForFocus(function() {
|
||||
test();
|
||||
SimpleTest.finish();
|
||||
});
|
||||
|
||||
const defaultMinimum = 0;
|
||||
const defaultMaximum = 100;
|
||||
const defaultStep = 1;
|
||||
|
||||
// Helpers:
|
||||
// For the sake of simplicity, we do not currently support fractional value,
|
||||
// step, etc.
|
||||
|
||||
function minimum(element) {
|
||||
return Number(element.min || defaultMinimum);
|
||||
}
|
||||
|
||||
function maximum(element) {
|
||||
return Number(element.max || defaultMaximum);
|
||||
}
|
||||
|
||||
function range(element) {
|
||||
var max = maximum(element);
|
||||
var min = minimum(element);
|
||||
if (max < min) {
|
||||
return 0;
|
||||
}
|
||||
return max - min;
|
||||
}
|
||||
|
||||
function defaultValue(element) {
|
||||
return minimum(element) + range(element)/2;
|
||||
}
|
||||
|
||||
function value(element) {
|
||||
return Number(element.value || defaultValue(element));
|
||||
}
|
||||
|
||||
function step(element) {
|
||||
var step = Number(element.step || defaultStep);
|
||||
return step <= 0 ? defaultStep : step;
|
||||
}
|
||||
|
||||
function clampToRange(value, element) {
|
||||
var min = minimum(element);
|
||||
var max = maximum(element);
|
||||
if (max < min) {
|
||||
return min;
|
||||
}
|
||||
if (value < min) {
|
||||
return min;
|
||||
}
|
||||
if (value > max) {
|
||||
return max;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
// Functions used to specify expected test results:
|
||||
|
||||
function valuePlusStep(element) {
|
||||
return clampToRange(value(element) + step(element), element);
|
||||
}
|
||||
|
||||
function valueMinusStep(element) {
|
||||
return clampToRange(value(element) - step(element), element);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current value of the range plus whichever is greater of either
|
||||
* 10% of the range or its current step value, clamped to the range's minimum/
|
||||
* maximum. The reason for using the step if it is greater than 10% of the
|
||||
* range is because otherwise the PgUp/PgDn keys would do nothing in that case.
|
||||
*/
|
||||
function valuePlusTenPctOrStep(element) {
|
||||
var tenPct = range(element)/10;
|
||||
var stp = step(element);
|
||||
return clampToRange(value(element) + Math.max(tenPct, stp), element);
|
||||
}
|
||||
|
||||
function valueMinusTenPctOrStep(element) {
|
||||
var tenPct = range(element)/10;
|
||||
var stp = step(element);
|
||||
return clampToRange(value(element) - Math.max(tenPct, stp), element);
|
||||
}
|
||||
|
||||
// Test table:
|
||||
|
||||
const LTR = "ltr";
|
||||
const RTL = "rtl";
|
||||
|
||||
var testTable = [
|
||||
["VK_LEFT", LTR, valueMinusStep],
|
||||
["VK_LEFT", RTL, valuePlusStep],
|
||||
["VK_RIGHT", LTR, valuePlusStep],
|
||||
["VK_RIGHT", RTL, valueMinusStep],
|
||||
["VK_UP", LTR, valuePlusStep],
|
||||
["VK_UP", RTL, valuePlusStep],
|
||||
["VK_DOWN", LTR, valueMinusStep],
|
||||
["VK_DOWN", RTL, valueMinusStep],
|
||||
["VK_PAGE_UP", LTR, valuePlusTenPctOrStep],
|
||||
["VK_PAGE_UP", RTL, valuePlusTenPctOrStep],
|
||||
["VK_PAGE_DOWN", LTR, valueMinusTenPctOrStep],
|
||||
["VK_PAGE_DOWN", RTL, valueMinusTenPctOrStep],
|
||||
["VK_HOME", LTR, minimum],
|
||||
["VK_HOME", RTL, minimum],
|
||||
["VK_END", LTR, maximum],
|
||||
["VK_END", RTL, maximum],
|
||||
]
|
||||
|
||||
function test() {
|
||||
var elem = document.createElement("input");
|
||||
elem.type = "range";
|
||||
|
||||
var content = document.getElementById("content");
|
||||
content.appendChild(elem);
|
||||
elem.focus();
|
||||
|
||||
for (test of testTable) {
|
||||
var [key, dir, expectedFunc] = test;
|
||||
var oldVal, expectedVal;
|
||||
|
||||
elem.step = "2";
|
||||
elem.style.direction = dir;
|
||||
var flush = document.body.clientWidth;
|
||||
|
||||
// Start at middle:
|
||||
elem.value = oldVal = defaultValue(elem);
|
||||
expectedVal = expectedFunc(elem);
|
||||
synthesizeKey(key, {});
|
||||
is(elem.value, expectedVal, "Test " + key + " for " + dir + " range with value set to the midpoint (" + oldVal + ")");
|
||||
|
||||
// Same again:
|
||||
expectedVal = expectedFunc(elem);
|
||||
synthesizeKey(key, {});
|
||||
is(elem.value, expectedVal, "Test repeat of " + key + " for " + dir + " range");
|
||||
|
||||
// Start at maximum:
|
||||
elem.value = oldVal = maximum(elem);
|
||||
expectedVal = expectedFunc(elem);
|
||||
synthesizeKey(key, {});
|
||||
is(elem.value, expectedVal, "Test " + key + " for " + dir + " range with value set to the maximum (" + oldVal + ")");
|
||||
|
||||
// Same again:
|
||||
expectedVal = expectedFunc(elem);
|
||||
synthesizeKey(key, {});
|
||||
is(elem.value, expectedVal, "Test repeat of " + key + " for " + dir + " range");
|
||||
|
||||
// Start at minimum:
|
||||
elem.value = oldVal = minimum(elem);
|
||||
expectedVal = expectedFunc(elem);
|
||||
synthesizeKey(key, {});
|
||||
is(elem.value, expectedVal, "Test " + key + " for " + dir + " range with value set to the minimum (" + oldVal + ")");
|
||||
|
||||
// Same again:
|
||||
expectedVal = expectedFunc(elem);
|
||||
synthesizeKey(key, {});
|
||||
is(elem.value, expectedVal, "Test repeat of " + key + " for " + dir + " range");
|
||||
|
||||
// Test for a step value that is greater than 10% of the range:
|
||||
elem.step = 20;
|
||||
elem.value = 60;
|
||||
expectedVal = expectedFunc(elem);
|
||||
synthesizeKey(key, {});
|
||||
is(elem.value, expectedVal, "Test " + key + " for " + dir + " range with a step that is greater than 10% of the range (step=" + elem.step + ")");
|
||||
|
||||
// Same again:
|
||||
expectedVal = expectedFunc(elem);
|
||||
synthesizeKey(key, {});
|
||||
is(elem.value, expectedVal, "Test repeat of " + key + " for " + dir + " range");
|
||||
|
||||
// reset step:
|
||||
elem.step = 2;
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
</pre>
|
||||
</body>
|
||||
</html>
|
|
@ -720,12 +720,14 @@ meter {
|
|||
input[type="range"] {
|
||||
-moz-appearance: none;
|
||||
display: inline-block !important;
|
||||
cursor: default;
|
||||
width: 12em;
|
||||
height: 1.3em;
|
||||
margin: 0 0.7em;
|
||||
/* Override some rules that apply on all input types: */
|
||||
cursor: default;
|
||||
background: none;
|
||||
border: none;
|
||||
margin: 0 0.7em;
|
||||
-moz-binding: none; /* we don't want any of platformHTMLBindings.xml#inputFields */
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
Загрузка…
Ссылка в новой задаче