Commit ff2fac0d authored by Jared Wein's avatar Jared Wein
Browse files

Bug 1550099 - Create basic custom element for a modal input. r=MattN

Differential Revision: https://phabricator.services.mozilla.com/D30957

--HG--
extra : moz-landing-system : lando
parent 4b250f6e
......@@ -84,5 +84,12 @@
<link rel="stylesheet" href="chrome://browser/content/aboutlogins/components/login-filter.css">
<input type="text"/>
</template>
<template id="modal-input-template">
<link rel="stylesheet" type="text/css" href="chrome://global/skin/in-content/common.css">
<link rel="stylesheet" href="chrome://browser/content/aboutlogins/components/modal-input.css">
<span class="locked-value"></span>
<input type="text" class="unlocked-value"/>
</template>
</body>
</html>
/* 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/. */
:host([editing]) .locked-value,
:host(:not([editing])) .unlocked-value {
display: none;
}
/* 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/. */
class ModalInput extends HTMLElement {
static get LOCKED_PASSWORD_DISPLAY() {
return "••••••••";
}
connectedCallback() {
if (this.children.length) {
return;
}
let modalInputTemplate = document.querySelector("#modal-input-template");
this.attachShadow({mode: "open"})
.appendChild(modalInputTemplate.content.cloneNode(true));
if (this.hasAttribute("value")) {
this.value = this.getAttribute("value");
}
if (this.getAttribute("type") == "password") {
let unlockedValue = this.shadowRoot.querySelector(".unlocked-value");
unlockedValue.setAttribute("type", "password");
}
}
static get observedAttributes() {
return ["editing", "type", "value"];
}
attributeChangedCallback(attr, oldValue, newValue) {
if (!this.shadowRoot) {
return;
}
let lockedValue = this.shadowRoot.querySelector(".locked-value");
let unlockedValue = this.shadowRoot.querySelector(".unlocked-value");
switch (attr) {
case "editing": {
let isEditing = newValue !== null;
if (!isEditing) {
this.setAttribute("value", unlockedValue.value);
}
break;
}
case "type": {
if (newValue == "password") {
lockedValue.textContent = this.constructor.LOCKED_PASSWORD_DISPLAY;
unlockedValue.setAttribute("type", "password");
} else {
lockedValue.textContent = this.getAttribute("value");
unlockedValue.setAttribute("type", "text");
}
break;
}
case "value": {
this.value = newValue;
break;
}
}
}
get value() {
return this.hasAttribute("editing") ? this.shadowRoot.querySelector(".unlocked-value").value.trim()
: this.getAttribute("value") || "";
}
set value(val) {
if (this.getAttribute("value") != val) {
this.setAttribute("value", val);
return;
}
this.shadowRoot.querySelector(".unlocked-value").value = val;
let lockedValue = this.shadowRoot.querySelector(".locked-value");
if (this.getAttribute("type") == "password" && val && val.length) {
lockedValue.textContent = this.constructor.LOCKED_PASSWORD_DISPLAY;
} else {
lockedValue.textContent = val;
}
}
}
customElements.define("modal-input", ModalInput);
......@@ -11,6 +11,8 @@ browser.jar:
content/browser/aboutlogins/components/login-list.js (content/components/login-list.js)
content/browser/aboutlogins/components/login-list-item.css (content/components/login-list-item.css)
content/browser/aboutlogins/components/login-list-item.js (content/components/login-list-item.js)
content/browser/aboutlogins/components/modal-input.css (content/components/modal-input.css)
content/browser/aboutlogins/components/modal-input.js (content/components/modal-input.js)
content/browser/aboutlogins/components/reflected-fluent-element.js (content/components/reflected-fluent-element.js)
content/browser/aboutlogins/aboutLogins.css (content/aboutLogins.css)
content/browser/aboutlogins/aboutLogins.js (content/aboutLogins.js)
......
......@@ -5,10 +5,12 @@ support-files =
../../content/components/login-item.js
../../content/components/login-list.js
../../content/components/login-list-item.js
../../content/components/modal-input.js
../../content/components/reflected-fluent-element.js
aboutlogins_common.js
[test_login_filter.html]
[test_login_item.html]
[test_login_list.html]
[test_modal_input.html]
[test_reflected_fluent_element.html]
<!DOCTYPE HTML>
<html>
<!--
Test the modal-input component
-->
<head>
<meta charset="utf-8">
<title>Test the modal-input component</title>
<script src="/tests/SimpleTest/SimpleTest.js"></script>
<script src="modal-input.js"></script>
<script src="aboutlogins_common.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
</head>
<body>
<p id="display">
</p>
<div id="content" style="display: none">
<iframe id="templateFrame" src="aboutLogins.html"
sandbox="allow-same-origin"></iframe>
</div>
<pre id="test">
</pre>
<script>
/** Test the modal-input component **/
let gModalInput;
const TEST_INPUT_VALUE = "fakeValue";
add_task(async function setup() {
let templateFrame = document.getElementById("templateFrame");
let displayEl = document.getElementById("display");
importDependencies(templateFrame, displayEl);
gModalInput = document.createElement("modal-input");
gModalInput.setAttribute("value", TEST_INPUT_VALUE);
displayEl.appendChild(gModalInput);
});
add_task(async function test_initial_state() {
ok(gModalInput, "modalInput exists");
is(gModalInput.shadowRoot.querySelector(".locked-value").textContent, TEST_INPUT_VALUE, "Values are set initially");
is(gModalInput.shadowRoot.querySelector(".unlocked-value").value, TEST_INPUT_VALUE, "Values are set initially");
is(getComputedStyle(gModalInput.shadowRoot.querySelector(".locked-value")).display, "inline", ".locked-value is visible by default");
is(getComputedStyle(gModalInput.shadowRoot.querySelector(".unlocked-value")).display, "none", ".unlocked-value is hidden by default");
});
add_task(async function test_editing_set_unset() {
let lockedValue = gModalInput.shadowRoot.querySelector(".locked-value");
let unlockedValue = gModalInput.shadowRoot.querySelector(".unlocked-value");
gModalInput.setAttribute("editing", "");
is(getComputedStyle(lockedValue).display, "none", ".locked-value is hidden when editing");
is(getComputedStyle(unlockedValue).display, "inline", ".unlocked-value is visible when editing");
const NEW_VALUE = "editedValue";
SpecialPowers.wrap(unlockedValue).setUserInput(NEW_VALUE);
gModalInput.removeAttribute("editing");
is(lockedValue.textContent, NEW_VALUE, "Values are updated from edit");
is(unlockedValue.value, NEW_VALUE, "Values are updated from edit");
is(gModalInput.getAttribute("value"), NEW_VALUE, "The value attribute on the host element is updated from edit");
is(getComputedStyle(lockedValue).display, "inline", ".locked-value is visible when not editing");
is(getComputedStyle(unlockedValue).display, "none", ".unlocked-value is hidden when not editing");
});
add_task(async function test_password() {
gModalInput.setAttribute("type", "password");
let lockedValue = gModalInput.shadowRoot.querySelector(".locked-value");
let unlockedValue = gModalInput.shadowRoot.querySelector(".unlocked-value");
is(lockedValue.textContent, gModalInput.constructor.LOCKED_PASSWORD_DISPLAY,
"type=password should display masked characters when locked");
is(unlockedValue.value, gModalInput.getAttribute("value"), "type=password should have actual value in .unlocked-value");
is(unlockedValue.getAttribute("type"), "password", "input[type=password] should be used for .unlocked-value with type=password");
gModalInput.removeAttribute("value");
is(lockedValue.textContent, "",
"type=password should display nothing when locked without a value (.locked-value)");
is(unlockedValue.value, "",
"type=password should display nothing when locked without a value (.unlocked-value)");
});
</script>
</body>
</html>
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment