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:
Raphael Ferrand 2021-11-18 12:52:22 +00:00
Родитель cdd275e25c
Коммит 702f0ad086
5 изменённых файлов: 96 добавлений и 22 удалений

Просмотреть файл

@ -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,11 +73,13 @@ 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 => {
return { name, isApplied: true };
});
const nodeClasses = [...new Set([...this.classListProxyNode.classList])]
.filter(
className => !this.previewClasses.split(" ").includes(className)
)
.map(name => {
return { name, isApplied: true };
});
CLASSES.set(this.currentNode, nodeClasses);
}
@ -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,12 +230,16 @@ class ClassListPreviewer {
let items = [];
try {
const classNames = await this.model.getClassNames(newValue);
items = classNames.map(className => {
return {
preLabel: className.substring(0, newValue.length),
label: className,
};
});
items = classNames
.filter(
className => !this.model.previewClasses.split(" ").includes(className)
)
.map(className => {
return {
preLabel: className.substring(0, newValue.length),
label: className,
};
});
} catch (e) {
// If there was an error while retrieving the classNames, we'll simply NOT show the
// popup, which is okay.
@ -262,6 +278,11 @@ class ClassListPreviewer {
onCurrentNodeClassChanged() {
this.render();
}
onNodeFrontWillUnset() {
this.model.eraseClassPreview();
this.addEl.value = "";
}
}
module.exports = ClassListPreviewer;