зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1498224 - [devtools] Apply new classnames as you type in the .cls section of the rule-view r=jdescottes
Differential Revision: https://phabricator.services.mozilla.com/D129515
This commit is contained in:
Родитель
cdd275e25c
Коммит
702f0ad086
|
@ -192,6 +192,8 @@ Selection.prototype = {
|
|||
return;
|
||||
}
|
||||
|
||||
this.emit("node-front-will-unset");
|
||||
|
||||
this._isSlotted = isSlotted;
|
||||
this._nodeFront = nodeFront;
|
||||
|
||||
|
|
|
@ -38,6 +38,7 @@ class ClassList {
|
|||
this.inspector.on("markupmutation", this.onMutations);
|
||||
|
||||
this.classListProxyNode = this.inspector.panelDoc.createElement("div");
|
||||
this.previewClasses = "";
|
||||
}
|
||||
|
||||
destroy() {
|
||||
|
@ -72,9 +73,11 @@ class ClassList {
|
|||
if (!CLASSES.has(this.currentNode)) {
|
||||
// Use the proxy node to get a clean list of classes.
|
||||
this.classListProxyNode.className = this.currentNode.className;
|
||||
const nodeClasses = [
|
||||
...new Set([...this.classListProxyNode.classList]),
|
||||
].map(name => {
|
||||
const nodeClasses = [...new Set([...this.classListProxyNode.classList])]
|
||||
.filter(
|
||||
className => !this.previewClasses.split(" ").includes(className)
|
||||
)
|
||||
.map(name => {
|
||||
return { name, isApplied: true };
|
||||
});
|
||||
|
||||
|
@ -89,10 +92,15 @@ class ClassList {
|
|||
* enabled classes are added.
|
||||
*/
|
||||
get currentClassesPreview() {
|
||||
return this.currentClasses
|
||||
const currentClasses = this.currentClasses
|
||||
.filter(({ isApplied }) => isApplied)
|
||||
.map(({ name }) => name)
|
||||
.join(" ");
|
||||
.map(({ name }) => name);
|
||||
const previewClasses = this.previewClasses
|
||||
.split(" ")
|
||||
.filter(previewClass => !currentClasses.includes(previewClass))
|
||||
.filter(item => item !== "");
|
||||
|
||||
return currentClasses.concat(previewClasses).join(" ");
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -121,6 +129,7 @@ class ClassList {
|
|||
*/
|
||||
addClassName(classNameString) {
|
||||
this.classListProxyNode.className = classNameString;
|
||||
this.eraseClassPreview();
|
||||
return Promise.all(
|
||||
[...new Set([...this.classListProxyNode.classList])].map(name => {
|
||||
return this.addClass(name);
|
||||
|
@ -211,6 +220,17 @@ class ClassList {
|
|||
this.currentNode
|
||||
);
|
||||
}
|
||||
|
||||
previewClass(inputClasses) {
|
||||
if (this.previewClasses !== inputClasses) {
|
||||
this.previewClasses = inputClasses;
|
||||
this.applyClassState();
|
||||
}
|
||||
}
|
||||
|
||||
eraseClassPreview() {
|
||||
this.previewClass("");
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = ClassList;
|
||||
|
|
|
@ -70,7 +70,9 @@ add_task(async function() {
|
|||
|
||||
info(`Enter the test string in the field: ${textEntered}`);
|
||||
for (const key of textEntered.split("")) {
|
||||
const onPreviewMutation = inspector.once("markupmutation");
|
||||
EventUtils.synthesizeKey(key, {}, view.styleWindow);
|
||||
await onPreviewMutation;
|
||||
}
|
||||
|
||||
info("Submit the change and wait for the textfield to become empty");
|
||||
|
|
|
@ -45,6 +45,7 @@ add_task(async function() {
|
|||
const { autocompletePopup } = view.classListPreviewer;
|
||||
let onPopupOpened = autocompletePopup.once("popup-opened");
|
||||
EventUtils.synthesizeKey("a", {}, view.styleWindow);
|
||||
await waitForClassApplied("a");
|
||||
await onPopupOpened;
|
||||
await checkAutocompleteItems(
|
||||
autocompletePopup,
|
||||
|
@ -55,17 +56,14 @@ add_task(async function() {
|
|||
info(
|
||||
"Test that typing more letters filters the autocomplete popup and uses the cache mechanism"
|
||||
);
|
||||
const onCacheHit = inspector.inspectorFront.pageStyle.once(
|
||||
"getAttributesInOwnerDocument-cache-hit"
|
||||
);
|
||||
EventUtils.sendString("uto-b", view.styleWindow);
|
||||
await waitForClassApplied("auto-b");
|
||||
|
||||
await checkAutocompleteItems(
|
||||
autocompletePopup,
|
||||
allClasses.filter(cls => cls.startsWith("auto-b")),
|
||||
"The autocomplete popup was filtered with the content of the input"
|
||||
);
|
||||
await onCacheHit;
|
||||
ok(true, "The results were retrieved from the cache mechanism");
|
||||
|
||||
info("Test that autocomplete shows up-to-date results");
|
||||
|
@ -76,10 +74,12 @@ add_task(async function() {
|
|||
content.document.body.classList.add("auto-body-added-by-script");
|
||||
});
|
||||
await onNewMutation;
|
||||
await waitForClassApplied("auto-body-added-by-script");
|
||||
|
||||
// input is now auto-body
|
||||
onPopupOpened = autocompletePopup.once("popup-opened");
|
||||
EventUtils.sendString("ody", view.styleWindow);
|
||||
await waitForClassApplied("auto-body");
|
||||
await onPopupOpened;
|
||||
await checkAutocompleteItems(
|
||||
autocompletePopup,
|
||||
|
@ -96,6 +96,7 @@ add_task(async function() {
|
|||
// input is now auto-bodyy
|
||||
let onPopupClosed = autocompletePopup.once("popup-closed");
|
||||
EventUtils.synthesizeKey("y", {}, view.styleWindow);
|
||||
await waitForClassApplied("auto-bodyy");
|
||||
await onPopupClosed;
|
||||
ok(true, "The popup was closed as expected");
|
||||
await checkAutocompleteItems(autocompletePopup, [], "The popup was cleared");
|
||||
|
@ -108,6 +109,7 @@ add_task(async function() {
|
|||
|
||||
onPopupOpened = autocompletePopup.once("popup-opened");
|
||||
EventUtils.synthesizeKey("a", {}, view.styleWindow);
|
||||
await waitForClassApplied("a");
|
||||
await onPopupOpened;
|
||||
|
||||
await checkAutocompleteItems(
|
||||
|
@ -147,6 +149,7 @@ add_task(async function() {
|
|||
|
||||
onPopupClosed = autocompletePopup.once("popup-closed");
|
||||
EventUtils.synthesizeKey("KEY_ArrowRight", {}, view.styleWindow);
|
||||
await waitForClassApplied("auto-body-added-by-script");
|
||||
await onPopupClosed;
|
||||
is(
|
||||
textInput.value,
|
||||
|
@ -157,6 +160,7 @@ add_task(async function() {
|
|||
// Backspace to show the list again
|
||||
onPopupOpened = autocompletePopup.once("popup-opened");
|
||||
EventUtils.synthesizeKey("KEY_Backspace", {}, view.styleWindow);
|
||||
await waitForClassApplied("auto-body-added-by-scrip");
|
||||
await onPopupOpened;
|
||||
is(
|
||||
textInput.value,
|
||||
|
@ -172,6 +176,7 @@ add_task(async function() {
|
|||
// Enter to accept
|
||||
onPopupClosed = autocompletePopup.once("popup-closed");
|
||||
EventUtils.synthesizeKey("KEY_Enter", {}, view.styleWindow);
|
||||
await waitForClassRemoved("auto-body-added-by-scrip");
|
||||
await onPopupClosed;
|
||||
is(
|
||||
textInput.value,
|
||||
|
@ -182,6 +187,7 @@ add_task(async function() {
|
|||
// Backspace to show again
|
||||
onPopupOpened = autocompletePopup.once("popup-opened");
|
||||
EventUtils.synthesizeKey("KEY_Backspace", {}, view.styleWindow);
|
||||
await waitForClassApplied("auto-body-added-by-scrip");
|
||||
await onPopupOpened;
|
||||
is(
|
||||
textInput.value,
|
||||
|
@ -203,6 +209,7 @@ add_task(async function() {
|
|||
"auto-body-added-by-script",
|
||||
"Tab puts the selected item in the input and closes the popup"
|
||||
);
|
||||
await waitForClassRemoved("auto-body-added-by-scrip");
|
||||
});
|
||||
|
||||
async function checkAutocompleteItems(
|
||||
|
@ -224,3 +231,25 @@ function getAutocompleteItems(autocompletePopup) {
|
|||
el => el.textContent
|
||||
);
|
||||
}
|
||||
|
||||
async function waitForClassApplied(cls) {
|
||||
info("Wait for class to be applied: " + cls);
|
||||
await SpecialPowers.spawn(gBrowser.selectedBrowser, [cls], async _cls => {
|
||||
return ContentTaskUtils.waitForCondition(() =>
|
||||
content.document.body.classList.contains(_cls)
|
||||
);
|
||||
});
|
||||
// Wait for debounced functions to be executed
|
||||
await wait(200);
|
||||
}
|
||||
|
||||
async function waitForClassRemoved(cls) {
|
||||
info("Wait for class to be removed: " + cls);
|
||||
await SpecialPowers.spawn(gBrowser.selectedBrowser, [cls], async _cls => {
|
||||
return ContentTaskUtils.waitForCondition(
|
||||
() => !content.document.body.classList.contains(_cls)
|
||||
);
|
||||
});
|
||||
// Wait for debounced functions to be executed
|
||||
await wait(200);
|
||||
}
|
||||
|
|
|
@ -31,13 +31,14 @@ class ClassListPreviewer {
|
|||
|
||||
this.onNewSelection = this.onNewSelection.bind(this);
|
||||
this.onCheckBoxChanged = this.onCheckBoxChanged.bind(this);
|
||||
this.onKeyPress = this.onKeyPress.bind(this);
|
||||
this.onKeyDown = this.onKeyDown.bind(this);
|
||||
this.onAddElementInputModified = debounce(
|
||||
this.onAddElementInputModified,
|
||||
75,
|
||||
this
|
||||
);
|
||||
this.onCurrentNodeClassChanged = this.onCurrentNodeClassChanged.bind(this);
|
||||
this.onNodeFrontWillUnset = this.onNodeFrontWillUnset.bind(this);
|
||||
|
||||
// Create the add class text field.
|
||||
this.addEl = this.doc.createElement("input");
|
||||
|
@ -47,7 +48,7 @@ class ClassListPreviewer {
|
|||
"placeholder",
|
||||
L10N.getStr("inspector.classPanel.newClass.placeholder")
|
||||
);
|
||||
this.addEl.addEventListener("keypress", this.onKeyPress);
|
||||
this.addEl.addEventListener("keydown", this.onKeyDown);
|
||||
this.addEl.addEventListener("input", this.onAddElementInputModified);
|
||||
this.containerEl.appendChild(this.addEl);
|
||||
|
||||
|
@ -68,12 +69,17 @@ class ClassListPreviewer {
|
|||
this.addEl.value = item.label;
|
||||
this.autocompletePopup.hidePopup();
|
||||
this.autocompletePopup.clearItems();
|
||||
this.model.previewClass(item.label);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
// Start listening for interesting events.
|
||||
this.inspector.selection.on("new-node-front", this.onNewSelection);
|
||||
this.inspector.selection.on(
|
||||
"node-front-will-unset",
|
||||
this.onNodeFrontWillUnset
|
||||
);
|
||||
this.containerEl.addEventListener("input", this.onCheckBoxChanged);
|
||||
this.model.on("current-node-class-changed", this.onCurrentNodeClassChanged);
|
||||
|
||||
|
@ -82,7 +88,11 @@ class ClassListPreviewer {
|
|||
|
||||
destroy() {
|
||||
this.inspector.selection.off("new-node-front", this.onNewSelection);
|
||||
this.addEl.removeEventListener("keypress", this.onKeyPress);
|
||||
this.inspector.selection.off(
|
||||
"node-front-will-unset",
|
||||
this.onNodeFrontWillUnset
|
||||
);
|
||||
this.addEl.removeEventListener("keydown", this.onKeyDown);
|
||||
this.addEl.removeEventListener("input", this.onAddElementInputModified);
|
||||
this.containerEl.removeEventListener("input", this.onCheckBoxChanged);
|
||||
|
||||
|
@ -181,7 +191,7 @@ class ClassListPreviewer {
|
|||
});
|
||||
}
|
||||
|
||||
onKeyPress(event) {
|
||||
onKeyDown(event) {
|
||||
// If the popup is already open, all the keyboard interaction are handled
|
||||
// directly by the popup component.
|
||||
if (this.autocompletePopup.isOpen) {
|
||||
|
@ -205,6 +215,8 @@ class ClassListPreviewer {
|
|||
async onAddElementInputModified() {
|
||||
const newValue = this.addEl.value;
|
||||
|
||||
this.model.previewClass(newValue);
|
||||
|
||||
// if the input is empty, let's close the popup, if it was open.
|
||||
if (newValue === "") {
|
||||
if (this.autocompletePopup.isOpen) {
|
||||
|
@ -218,7 +230,11 @@ class ClassListPreviewer {
|
|||
let items = [];
|
||||
try {
|
||||
const classNames = await this.model.getClassNames(newValue);
|
||||
items = classNames.map(className => {
|
||||
items = classNames
|
||||
.filter(
|
||||
className => !this.model.previewClasses.split(" ").includes(className)
|
||||
)
|
||||
.map(className => {
|
||||
return {
|
||||
preLabel: className.substring(0, newValue.length),
|
||||
label: className,
|
||||
|
@ -262,6 +278,11 @@ class ClassListPreviewer {
|
|||
onCurrentNodeClassChanged() {
|
||||
this.render();
|
||||
}
|
||||
|
||||
onNodeFrontWillUnset() {
|
||||
this.model.eraseClassPreview();
|
||||
this.addEl.value = "";
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = ClassListPreviewer;
|
||||
|
|
Загрузка…
Ссылка в новой задаче