forked from mirrors/gecko-dev
Bug 1485473 - Combine the borders of the name fields on the address form. r=sfoster
Differential Revision: https://phabricator.services.mozilla.com/D4031 --HG-- extra : moz-landing-system : lando
This commit is contained in:
parent
9daee9a3a5
commit
487d770cc3
8 changed files with 196 additions and 25 deletions
|
|
@ -26,7 +26,7 @@ body[dir="rtl"] .error-text {
|
||||||
right: 3px;
|
right: 3px;
|
||||||
}
|
}
|
||||||
|
|
||||||
:-moz-any(input, textarea, select):focus + .error-text:not(:empty)::before {
|
:-moz-any(input, textarea, select):focus ~ .error-text:not(:empty)::before {
|
||||||
background-color: #d70022;
|
background-color: #d70022;
|
||||||
top: -7px;
|
top: -7px;
|
||||||
content: '.';
|
content: '.';
|
||||||
|
|
@ -47,8 +47,8 @@ body[dir=rtl] .error-text::before {
|
||||||
right: 12px
|
right: 12px
|
||||||
}
|
}
|
||||||
|
|
||||||
:-moz-any(input, textarea, select):not(:focus) + .error-text,
|
:-moz-any(input, textarea, select):not(:focus) ~ .error-text,
|
||||||
:-moz-any(input, textarea, select):valid + .error-text {
|
:-moz-any(input, textarea, select):valid ~ .error-text {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -297,6 +297,7 @@ add_task(async function test_restricted_address_fields() {
|
||||||
"tel should be visible");
|
"tel should be visible");
|
||||||
|
|
||||||
fillField(form.form.querySelector("#given-name"), "John");
|
fillField(form.form.querySelector("#given-name"), "John");
|
||||||
|
fillField(form.form.querySelector("#family-name"), "Smith");
|
||||||
ok(form.saveButton.disabled, "Save button should be disabled due to empty fields");
|
ok(form.saveButton.disabled, "Save button should be disabled due to empty fields");
|
||||||
fillField(form.form.querySelector("#email"), "john@example.com");
|
fillField(form.form.querySelector("#email"), "john@example.com");
|
||||||
todo(form.saveButton.disabled,
|
todo(form.saveButton.disabled,
|
||||||
|
|
|
||||||
|
|
@ -18,57 +18,61 @@
|
||||||
</head>
|
</head>
|
||||||
<body dir="&locale.dir;">
|
<body dir="&locale.dir;">
|
||||||
<form id="form" class="editAddressForm" autocomplete="off">
|
<form id="form" class="editAddressForm" autocomplete="off">
|
||||||
|
<!--
|
||||||
|
The <span class="label-text" …/> needs to be after the form field in the same element in
|
||||||
|
order to get proper label styling with :focus and :moz-ui-invalid.
|
||||||
|
-->
|
||||||
<div>
|
<div>
|
||||||
<div id="name-container">
|
<div id="name-container">
|
||||||
<label id="given-name-container">
|
<label id="given-name-container">
|
||||||
<span data-localization="givenName" class="label-text"/>
|
|
||||||
<input id="given-name" type="text" required="required"/>
|
<input id="given-name" type="text" required="required"/>
|
||||||
|
<span data-localization="givenName" class="label-text"/>
|
||||||
</label>
|
</label>
|
||||||
<label id="additional-name-container">
|
<label id="additional-name-container">
|
||||||
<span data-localization="additionalName" class="label-text"/>
|
|
||||||
<input id="additional-name" type="text"/>
|
<input id="additional-name" type="text"/>
|
||||||
|
<span data-localization="additionalName" class="label-text"/>
|
||||||
</label>
|
</label>
|
||||||
<label id="family-name-container">
|
<label id="family-name-container">
|
||||||
|
<input id="family-name" type="text" required="required"/>
|
||||||
<span data-localization="familyName" class="label-text"/>
|
<span data-localization="familyName" class="label-text"/>
|
||||||
<input id="family-name" type="text"/>
|
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<label id="organization-container">
|
<label id="organization-container">
|
||||||
<span data-localization="organization2" class="label-text"/>
|
|
||||||
<input id="organization" type="text"/>
|
<input id="organization" type="text"/>
|
||||||
|
<span data-localization="organization2" class="label-text"/>
|
||||||
</label>
|
</label>
|
||||||
<label id="street-address-container">
|
<label id="street-address-container">
|
||||||
<span data-localization="streetAddress" class="label-text"/>
|
|
||||||
<textarea id="street-address" rows="3" required="required"/>
|
<textarea id="street-address" rows="3" required="required"/>
|
||||||
|
<span data-localization="streetAddress" class="label-text"/>
|
||||||
</label>
|
</label>
|
||||||
<label id="address-level2-container">
|
<label id="address-level2-container">
|
||||||
<span data-localization="city" class="label-text"/>
|
|
||||||
<input id="address-level2" type="text" required="required"/>
|
<input id="address-level2" type="text" required="required"/>
|
||||||
|
<span data-localization="city" class="label-text"/>
|
||||||
</label>
|
</label>
|
||||||
<label id="address-level1-container">
|
<label id="address-level1-container">
|
||||||
<span class="label-text"/>
|
|
||||||
<input id="address-level1" type="text" required="required"/>
|
<input id="address-level1" type="text" required="required"/>
|
||||||
|
<span class="label-text"/>
|
||||||
</label>
|
</label>
|
||||||
<label id="postal-code-container">
|
<label id="postal-code-container">
|
||||||
<span class="label-text"/>
|
|
||||||
<input id="postal-code" type="text" required="required"/>
|
<input id="postal-code" type="text" required="required"/>
|
||||||
|
<span class="label-text"/>
|
||||||
</label>
|
</label>
|
||||||
<div id="country-container">
|
<div id="country-container">
|
||||||
<label id="country-label">
|
<label id="country-label">
|
||||||
<span data-localization="country" class="label-text"/>
|
|
||||||
<select id="country" required="required">
|
<select id="country" required="required">
|
||||||
<option/>
|
<option/>
|
||||||
</select>
|
</select>
|
||||||
|
<span data-localization="country" class="label-text"/>
|
||||||
</label>
|
</label>
|
||||||
<p id="country-warning-message" data-localization="countryWarningMessage2"/>
|
<p id="country-warning-message" data-localization="countryWarningMessage2"/>
|
||||||
</div>
|
</div>
|
||||||
<label id="tel-container">
|
<label id="tel-container">
|
||||||
<span data-localization="tel" class="label-text"/>
|
|
||||||
<input id="tel" type="tel"/>
|
<input id="tel" type="tel"/>
|
||||||
|
<span data-localization="tel" class="label-text"/>
|
||||||
</label>
|
</label>
|
||||||
<label id="email-container">
|
<label id="email-container">
|
||||||
<span data-localization="email" class="label-text"/>
|
|
||||||
<input id="email" type="email" required="required"/>
|
<input id="email" type="email" required="required"/>
|
||||||
|
<span data-localization="email" class="label-text"/>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
|
||||||
|
|
@ -18,17 +18,20 @@
|
||||||
</head>
|
</head>
|
||||||
<body dir="&locale.dir;">
|
<body dir="&locale.dir;">
|
||||||
<form id="form" class="editCreditCardForm" autocomplete="off">
|
<form id="form" class="editCreditCardForm" autocomplete="off">
|
||||||
|
<!--
|
||||||
|
The <span class="label-text" …/> needs to be after the form field in the same element in
|
||||||
|
order to get proper label styling with :focus and :moz-ui-invalid.
|
||||||
|
-->
|
||||||
<label>
|
<label>
|
||||||
<span data-localization="cardNumber" class="label-text"/>
|
|
||||||
<span id="invalidCardNumberString" hidden="hidden" data-localization="invalidCardNumber"></span>
|
<span id="invalidCardNumberString" hidden="hidden" data-localization="invalidCardNumber"></span>
|
||||||
<input id="cc-number" type="text" required="required" minlength="9" pattern="[- 0-9]+"/>
|
<input id="cc-number" type="text" required="required" minlength="9" pattern="[- 0-9]+"/>
|
||||||
|
<span data-localization="cardNumber" class="label-text"/>
|
||||||
</label>
|
</label>
|
||||||
<label>
|
<label>
|
||||||
<span data-localization="nameOnCard" class="label-text"/>
|
|
||||||
<input id="cc-name" type="text" required="required"/>
|
<input id="cc-name" type="text" required="required"/>
|
||||||
|
<span data-localization="nameOnCard" class="label-text"/>
|
||||||
</label>
|
</label>
|
||||||
<label>
|
<label>
|
||||||
<span data-localization="cardExpiresMonth" class="label-text"/>
|
|
||||||
<select id="cc-exp-month">
|
<select id="cc-exp-month">
|
||||||
<option/>
|
<option/>
|
||||||
<option value="1">01</option>
|
<option value="1">01</option>
|
||||||
|
|
@ -44,17 +47,18 @@
|
||||||
<option value="11">11</option>
|
<option value="11">11</option>
|
||||||
<option value="12">12</option>
|
<option value="12">12</option>
|
||||||
</select>
|
</select>
|
||||||
|
<span data-localization="cardExpiresMonth" class="label-text"/>
|
||||||
</label>
|
</label>
|
||||||
<label>
|
<label>
|
||||||
<span data-localization="cardExpiresYear" class="label-text"/>
|
|
||||||
<select id="cc-exp-year">
|
<select id="cc-exp-year">
|
||||||
<option/>
|
<option/>
|
||||||
</select>
|
</select>
|
||||||
|
<span data-localization="cardExpiresYear" class="label-text"/>
|
||||||
</label>
|
</label>
|
||||||
<label class="billingAddressRow">
|
<label class="billingAddressRow">
|
||||||
<span data-localization="billingAddress" class="label-text"/>
|
|
||||||
<select id="billingAddressGUID">
|
<select id="billingAddressGUID">
|
||||||
</select>
|
</select>
|
||||||
|
<span data-localization="billingAddress" class="label-text"/>
|
||||||
</label>
|
</label>
|
||||||
</form>
|
</form>
|
||||||
<div id="controls-container">
|
<div id="controls-container">
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,12 @@
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#name-container,
|
||||||
|
:root[subdialog] form label,
|
||||||
|
:root[subdialog] form > p {
|
||||||
|
margin: 0 0 0.5em !important;
|
||||||
|
}
|
||||||
|
|
||||||
#given-name-container,
|
#given-name-container,
|
||||||
#additional-name-container,
|
#additional-name-container,
|
||||||
#address-level1-container,
|
#address-level1-container,
|
||||||
|
|
@ -22,14 +28,74 @@
|
||||||
flex: 0 1 50%;
|
flex: 0 1 50%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Begin name field rules */
|
||||||
|
|
||||||
|
#name-container input {
|
||||||
|
/* Override the default @size="20" on <input>, which acts like a min-width, not
|
||||||
|
* allowing the fields to shrink with flexbox as small as they need to to match
|
||||||
|
* the other rows. This is noticeable on narrow viewports e.g. in the
|
||||||
|
* PaymentRequest dialog on Linux due to the larger font-size. */
|
||||||
|
width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* When there is focus within any of the name fields, the border of the inputs
|
||||||
|
* should be the focused color, except for inner ones which get overriden below. */
|
||||||
|
#name-container:focus-within input {
|
||||||
|
border-color: var(--in-content-border-focus);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Invalid name fields should show the error outline instead of the focus border */
|
||||||
|
#name-container:focus-within input:-moz-ui-invalid {
|
||||||
|
border-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
#given-name-container,
|
#given-name-container,
|
||||||
#additional-name-container,
|
#additional-name-container,
|
||||||
#family-name-container {
|
#family-name-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
/* Remove the bottom margin from the name containers so that the outer
|
||||||
|
#name-container provides the margin on the outside */
|
||||||
|
margin-bottom: 0 !important;
|
||||||
margin-left: 0;
|
margin-left: 0;
|
||||||
margin-right: 0;
|
margin-right: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* The name fields are placed adjacent to each other.
|
||||||
|
Remove the border-radius on adjacent fields. */
|
||||||
|
#given-name:dir(ltr),
|
||||||
|
#family-name:dir(rtl) {
|
||||||
|
border-top-right-radius: 0;
|
||||||
|
border-bottom-right-radius: 0;
|
||||||
|
border-right-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#given-name:dir(rtl),
|
||||||
|
#family-name:dir(ltr) {
|
||||||
|
border-top-left-radius: 0;
|
||||||
|
border-bottom-left-radius: 0;
|
||||||
|
border-left-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#additional-name {
|
||||||
|
border-radius: 0;
|
||||||
|
/* This provides the inner separators between the fields and should never
|
||||||
|
* change to the focused color. */
|
||||||
|
border-left-color: var(--in-content-box-border-color) !important;
|
||||||
|
border-right-color: var(--in-content-box-border-color) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Since the name fields are adjacent, there isn't room for the -moz-ui-invalid
|
||||||
|
box-shadow so raise invalid name fields and their labels above the siblings
|
||||||
|
so the shadow is shown around all 4 sides. */
|
||||||
|
#name-container input:-moz-ui-invalid,
|
||||||
|
#name-container input:-moz-ui-invalid ~ .label-text {
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* End name field rules */
|
||||||
|
|
||||||
|
|
||||||
#name-container,
|
#name-container,
|
||||||
#street-address-container,
|
#street-address-container,
|
||||||
#country-container,
|
#country-container,
|
||||||
|
|
|
||||||
|
|
@ -13,11 +13,6 @@
|
||||||
padding: 2px;
|
padding: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
:root[subdialog] form label,
|
|
||||||
:root[subdialog] form > p {
|
|
||||||
margin: 0 0 0.5em !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
form input[type="email"],
|
form input[type="email"],
|
||||||
form input[type="tel"],
|
form input[type="tel"],
|
||||||
form input[type="text"],
|
form input[type="text"],
|
||||||
|
|
@ -55,10 +50,16 @@ form :-moz-any(label, div) > .label-text[field-populated] {
|
||||||
font-size: var(--in-field-label-size);
|
font-size: var(--in-field-label-size);
|
||||||
}
|
}
|
||||||
|
|
||||||
form :-moz-any(label, div):focus-within > .label-text {
|
form :-moz-any(input, select, textarea):focus ~ .label-text {
|
||||||
color: var(--in-content-item-selected);
|
color: var(--in-content-item-selected);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Focused error fields should get a darker text but not the blue one since it
|
||||||
|
* doesn't look good with the red error outline. */
|
||||||
|
form :-moz-any(input, select, textarea):focus:-moz-ui-invalid ~ .label-text {
|
||||||
|
color: var(--in-content-text-color);
|
||||||
|
}
|
||||||
|
|
||||||
form div[required] > label > .label-text::after,
|
form div[required] > label > .label-text::after,
|
||||||
form :-moz-any(label, div)[required] > .label-text::after {
|
form :-moz-any(label, div)[required] > .label-text::after {
|
||||||
content: attr(fieldRequiredSymbol);
|
content: attr(fieldRequiredSymbol);
|
||||||
|
|
|
||||||
|
|
@ -208,3 +208,89 @@ add_task(async function test_saveAddressDE() {
|
||||||
}
|
}
|
||||||
await removeAllRecords();
|
await removeAllRecords();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
add_task(async function test_combined_name_fields() {
|
||||||
|
await testDialog(EDIT_ADDRESS_DIALOG_URL, async win => {
|
||||||
|
let doc = win.document;
|
||||||
|
let givenNameField = doc.querySelector("#given-name");
|
||||||
|
let addtlNameField = doc.querySelector("#additional-name");
|
||||||
|
let familyNameField = doc.querySelector("#family-name");
|
||||||
|
|
||||||
|
function getComputedPropertyValue(field, property) {
|
||||||
|
return win.getComputedStyle(field).getPropertyValue(property);
|
||||||
|
}
|
||||||
|
function checkNameComputedPropertiesMatch(field, property, value, checkFn = is) {
|
||||||
|
checkFn(getComputedPropertyValue(field, property), value,
|
||||||
|
`Check ${field.id}'s ${property} is ${value}`);
|
||||||
|
}
|
||||||
|
function checkNameFieldBorders(borderColorUnfocused, borderColorFocused) {
|
||||||
|
info("checking the perimeter colors");
|
||||||
|
checkNameComputedPropertiesMatch(givenNameField, "border-top-color", borderColorFocused);
|
||||||
|
checkNameComputedPropertiesMatch(addtlNameField, "border-top-color", borderColorFocused);
|
||||||
|
checkNameComputedPropertiesMatch(familyNameField, "border-top-color", borderColorFocused);
|
||||||
|
checkNameComputedPropertiesMatch(familyNameField, "border-right-color", borderColorFocused);
|
||||||
|
checkNameComputedPropertiesMatch(givenNameField, "border-bottom-color", borderColorFocused);
|
||||||
|
checkNameComputedPropertiesMatch(addtlNameField, "border-bottom-color", borderColorFocused);
|
||||||
|
checkNameComputedPropertiesMatch(familyNameField, "border-bottom-color", borderColorFocused);
|
||||||
|
checkNameComputedPropertiesMatch(givenNameField, "border-left-color", borderColorFocused);
|
||||||
|
|
||||||
|
info("checking the internal borders");
|
||||||
|
checkNameComputedPropertiesMatch(givenNameField, "border-right-width", "0px");
|
||||||
|
checkNameComputedPropertiesMatch(addtlNameField, "border-left-width", "2px");
|
||||||
|
checkNameComputedPropertiesMatch(addtlNameField, "border-left-color", borderColorFocused, isnot);
|
||||||
|
checkNameComputedPropertiesMatch(addtlNameField, "border-right-width", "2px");
|
||||||
|
checkNameComputedPropertiesMatch(addtlNameField, "border-right-color", borderColorFocused, isnot);
|
||||||
|
checkNameComputedPropertiesMatch(familyNameField, "border-left-width", "0px");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set these variables since the test doesn't run from a subdialog and
|
||||||
|
// therefore doesn't get the additional common CSS files injected.
|
||||||
|
let borderColor = "rgb(0, 255, 0)";
|
||||||
|
let borderColorFocused = "rgb(0, 0, 255)";
|
||||||
|
doc.body.style.setProperty("--in-content-box-border-color", borderColor);
|
||||||
|
doc.body.style.setProperty("--in-content-border-focus", borderColorFocused);
|
||||||
|
|
||||||
|
givenNameField.focus();
|
||||||
|
checkNameFieldBorders(borderColor, borderColorFocused);
|
||||||
|
|
||||||
|
addtlNameField.focus();
|
||||||
|
checkNameFieldBorders(borderColor, borderColorFocused);
|
||||||
|
|
||||||
|
familyNameField.focus();
|
||||||
|
checkNameFieldBorders(borderColor, borderColorFocused);
|
||||||
|
|
||||||
|
info("unfocusing the name fields");
|
||||||
|
let cancelButton = doc.querySelector("#cancel");
|
||||||
|
cancelButton.focus();
|
||||||
|
borderColor = getComputedPropertyValue(givenNameField, "border-top-color");
|
||||||
|
isnot(borderColor, borderColorFocused, "Check that the border color is different");
|
||||||
|
checkNameFieldBorders(borderColor, borderColor);
|
||||||
|
|
||||||
|
cancelButton.click();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
add_task(async function test_combined_name_fields_error() {
|
||||||
|
await testDialog(EDIT_ADDRESS_DIALOG_URL, async win => {
|
||||||
|
let doc = win.document;
|
||||||
|
let givenNameField = doc.querySelector("#given-name");
|
||||||
|
info("mark the given name field as invalid");
|
||||||
|
givenNameField.value = "";
|
||||||
|
givenNameField.focus();
|
||||||
|
ok(givenNameField.matches(":-moz-ui-invalid"), "Check field is visually invalid");
|
||||||
|
|
||||||
|
let givenNameLabel = doc.querySelector("#given-name-container .label-text");
|
||||||
|
// Override pointer-events so that we can use elementFromPoint to know if
|
||||||
|
// the label text is visible.
|
||||||
|
givenNameLabel.style.pointerEvents = "auto";
|
||||||
|
let givenNameLabelRect = givenNameLabel.getBoundingClientRect();
|
||||||
|
// Get the center of the label
|
||||||
|
let el = doc.elementFromPoint(givenNameLabelRect.left + givenNameLabelRect.width / 2,
|
||||||
|
givenNameLabelRect.top + givenNameLabelRect.height / 2);
|
||||||
|
|
||||||
|
is(el, givenNameLabel, "Check that the label text is visible in the error state");
|
||||||
|
is(win.getComputedStyle(givenNameField).getPropertyValue("border-top-color"),
|
||||||
|
"rgba(0, 0, 0, 0)", "Border should be transparent so that only the error outline shows");
|
||||||
|
doc.querySelector("#cancel").click();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
|
||||||
|
|
@ -506,6 +506,15 @@ xul|textbox[focused] {
|
||||||
border-color: var(--in-content-border-focus);
|
border-color: var(--in-content-border-focus);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Don't show the field error outlines and focus borders at the same time */
|
||||||
|
html|input[type="email"]:-moz-ui-invalid:focus,
|
||||||
|
html|input[type="tel"]:-moz-ui-invalid:focus,
|
||||||
|
html|input[type="text"]:-moz-ui-invalid:focus,
|
||||||
|
html|textarea:-moz-ui-invalid:focus,
|
||||||
|
xul|textbox[focused] {
|
||||||
|
border-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
html|input[type="email"]:disabled,
|
html|input[type="email"]:disabled,
|
||||||
html|input[type="tel"]:disabled,
|
html|input[type="tel"]:disabled,
|
||||||
html|input[type="text"]:disabled,
|
html|input[type="text"]:disabled,
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue