Commit b85cc453 authored by Andrey Lushnikov's avatar Andrey Lushnikov Committed by Commit Bot

DevTools: display colors in suggest box for CSS variables

This patch starts displaying color swatches in suggest box
next to css variables that compute to color value.

For this to be possible, the ColorSwatch and BezierSwatch were
moved under the ui/ module. The CSSShadowSwatch stayed in the
inline_editor/ for now because it has a dependency on
ShadowModel.

R=einbinder, dgozman

Change-Id: Ic29a13073dc8236d5ef9a2fe35adf6bcd927290b
Reviewed-on: https://chromium-review.googlesource.com/1026882
Commit-Queue: Andrey Lushnikov <lushnikov@chromium.org>
Reviewed-by: default avatarDmitry Gozman <dgozman@chromium.org>
Cr-Commit-Position: refs/heads/master@{#553468}
parent 95f7aa93
...@@ -6,8 +6,13 @@ ...@@ -6,8 +6,13 @@
TestRunner.addResult(`Tests that text prompt suggestions' casing follows that of the user input.\n`); TestRunner.addResult(`Tests that text prompt suggestions' casing follows that of the user input.\n`);
await TestRunner.loadModule('elements_test_runner'); await TestRunner.loadModule('elements_test_runner');
await TestRunner.showPanel('elements'); await TestRunner.showPanel('elements');
await TestRunner.loadHTML(`
<div id="inner" style="color:initial;"></div>
`);
var prompt = new Elements.StylesSidebarPane.CSSPropertyPrompt(SDK.cssMetadata().allProperties(), [], null, true); await ElementsTestRunner.selectNodeAndWaitForStylesPromise('inner');
var colorTreeElement = ElementsTestRunner.getMatchedStylePropertyTreeItem('color');
var prompt = new Elements.StylesSidebarPane.CSSPropertyPrompt(colorTreeElement, true /* isEditingName */);
TestRunner.runTestSuite([ TestRunner.runTestSuite([
function testForUpperCase(next) { function testForUpperCase(next) {
......
Tests that CSSPropertyPrompt properly builds suggestions.
--blue-color
text: --blue-color)
priority: 1
subtitle: undefined
subtitleRenderer: function () { [native code] }
--red-color
text: --red-color)
priority: 1
subtitle: undefined
subtitleRenderer: function () { [native code] }
--other
text: --other)
priority: 1
subtitle: undefined
subtitleRenderer: undefined
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
(async function() {
TestRunner.addResult(`Tests that CSSPropertyPrompt properly builds suggestions.\n`);
await TestRunner.loadModule('elements_test_runner');
await TestRunner.showPanel('elements');
await TestRunner.loadHTML(`
<style>
body {
--red-color: red;
--blue-color: blue;
--other: 12px;
}
</style>
<div id="inner" style="color: red"></div>
`);
await ElementsTestRunner.selectNodeAndWaitForStylesPromise('inner');
const treeElement = ElementsTestRunner.getMatchedStylePropertyTreeItem('color');
const valuePrompt = new Elements.StylesSidebarPane.CSSPropertyPrompt(treeElement, false /* isEditingName */);
const results = await valuePrompt._buildPropertyCompletions('var(', '--', true /* true */)
for (const result of results) {
TestRunner.addResult(result.title)
TestRunner.addResult(' text: ' + result.text)
TestRunner.addResult(' priority: ' + result.priority)
TestRunner.addResult(' subtitle: ' + result.subtitle)
TestRunner.addResult(' subtitleRenderer: ' + result.subtitleRenderer)
}
TestRunner.completeTest();
})();
...@@ -13,86 +13,84 @@ ...@@ -13,86 +13,84 @@
</style> </style>
<div id="outer"> <div id="outer">
<div id="middle"> <div id="middle">
<div id="inner"></div> <div id="inner" style="color:initial;-webkit-transform: initial; transform: initial;"></div>
</div> </div>
</div> </div>
`); `);
var node = await ElementsTestRunner.selectNodeAndWaitForStylesPromise('inner');
ElementsTestRunner.nodeWithId('inner', node => TestRunner.cssModel.cachedMatchedCascadeForNode(node).then(step1));
function step1(matchedStyles) { var colorTreeElement = ElementsTestRunner.getMatchedStylePropertyTreeItem('color');
var inlineStyle = matchedStyles.nodeStyles()[0]; var namePrompt = new Elements.StylesSidebarPane.CSSPropertyPrompt(colorTreeElement, true /* isEditingName */);
var namePrompt = new Elements.StylesSidebarPane.CSSPropertyPrompt( var valuePrompt = valuePromptFor('color');
SDK.cssMetadata().allProperties(), matchedStyles.availableCSSVariables(inlineStyle), null, true);
var valuePrompt = valuePromptFor('color'); function valuePromptFor(name) {
function valuePromptFor(name) { var treeElement = ElementsTestRunner.getMatchedStylePropertyTreeItem(name);
return new Elements.StylesSidebarPane.CSSPropertyPrompt( return new Elements.StylesSidebarPane.CSSPropertyPrompt(treeElement, false /* isEditingName */);
SDK.cssMetadata().propertyValues(name), matchedStyles.availableCSSVariables(inlineStyle), null, false);
}
TestRunner.runTestSuite([
function testEmptyName(next) {
testAgainstGolden(namePrompt, '', false, [], ['width'], next);
},
function testEmptyNameForce(next) {
testAgainstGolden(namePrompt, '', true, ['width'], [], next);
},
function testSingleCharName(next) {
testAgainstGolden(namePrompt, 'w', false, ['width'], [], next);
},
function testSubstringName(next) {
testAgainstGolden(namePrompt, 'size', false, ['font-size', 'background-size', 'resize'], ['font-align'], next);
},
function testEmptyValue(next) {
testAgainstGolden(valuePrompt, '', false, ['aliceblue', 'red', 'inherit'], [], next);
},
function testImportantDeclarationDoNotToggleOnExclamationMark(next) {
testAgainstGolden(valuePrompt, 'red !', false, [], ['!important'], next);
},
function testImportantDeclaration(next) {
testAgainstGolden(valuePrompt, 'red !i', false, ['!important'], [], next);
},
function testValueR(next) {
testAgainstGolden(valuePrompt, 'R', false, ['RED', 'ROSYBROWN'], ['aliceblue', 'inherit'], next);
},
function testValueWithParenthesis(next) {
testAgainstGolden(valuePrompt, 'saturate(0%)', false, [], ['inherit'], next);
},
function testValuePrefixed(next) {
testAgainstGolden(
valuePromptFor('-webkit-transform'), 'tr', false, ['translate', 'translateY', 'translate3d'],
['initial', 'inherit'], next);
},
function testValueUnprefixed(next) {
testAgainstGolden(
valuePromptFor('transform'), 'tr', false, ['translate', 'translateY', 'translate3d'],
['initial', 'inherit'], next);
},
function testValueSubstring(next) {
testAgainstGolden(
valuePromptFor('color'), 'blue', false, ['blue', 'darkblue', 'lightblue'],
['darkred', 'yellow', 'initial', 'inherit'], next);
},
function testNameVariables(next) {
testAgainstGolden(namePrompt, '', true, ['--red-color', '--blue-color'], [], next);
},
function testValueVariables(next) {
testAgainstGolden(valuePromptFor('color'), 'var(', true, ['--red-color)', '--blue-color)'], ['width'], next);
}
]);
} }
TestRunner.runTestSuite([
function testEmptyName(next) {
testAgainstGolden(namePrompt, '', false, [], ['width'], next);
},
function testEmptyNameForce(next) {
testAgainstGolden(namePrompt, '', true, ['width'], [], next);
},
function testSingleCharName(next) {
testAgainstGolden(namePrompt, 'w', false, ['width'], [], next);
},
function testSubstringName(next) {
testAgainstGolden(namePrompt, 'size', false, ['font-size', 'background-size', 'resize'], ['font-align'], next);
},
function testEmptyValue(next) {
testAgainstGolden(valuePrompt, '', false, ['aliceblue', 'red', 'inherit'], [], next);
},
function testImportantDeclarationDoNotToggleOnExclamationMark(next) {
testAgainstGolden(valuePrompt, 'red !', false, [], ['!important'], next);
},
function testImportantDeclaration(next) {
testAgainstGolden(valuePrompt, 'red !i', false, ['!important'], [], next);
},
function testValueR(next) {
testAgainstGolden(valuePrompt, 'R', false, ['RED', 'ROSYBROWN'], ['aliceblue', 'inherit'], next);
},
function testValueWithParenthesis(next) {
testAgainstGolden(valuePrompt, 'saturate(0%)', false, [], ['inherit'], next);
},
function testValuePrefixed(next) {
testAgainstGolden(
valuePromptFor('-webkit-transform'), 'tr', false, ['translate', 'translateY', 'translate3d'],
['initial', 'inherit'], next);
},
function testValueUnprefixed(next) {
testAgainstGolden(
valuePromptFor('transform'), 'tr', false, ['translate', 'translateY', 'translate3d'],
['initial', 'inherit'], next);
},
function testValueSubstring(next) {
testAgainstGolden(
valuePromptFor('color'), 'blue', false, ['blue', 'darkblue', 'lightblue'],
['darkred', 'yellow', 'initial', 'inherit'], next);
},
function testNameVariables(next) {
testAgainstGolden(namePrompt, '', true, ['--red-color', '--blue-color'], [], next);
},
function testValueVariables(next) {
testAgainstGolden(valuePromptFor('color'), 'var(', true, ['--red-color)', '--blue-color)'], ['width'], next);
}
]);
function testAgainstGolden(prompt, inputText, force, golden, antiGolden, callback) { function testAgainstGolden(prompt, inputText, force, golden, antiGolden, callback) {
var proxyElement = document.createElement('div'); var proxyElement = document.createElement('div');
......
...@@ -609,19 +609,7 @@ Elements.StylePropertyTreeElement = class extends UI.TreeElement { ...@@ -609,19 +609,7 @@ Elements.StylePropertyTreeElement = class extends UI.TreeElement {
if (selectElement.parentElement) if (selectElement.parentElement)
selectElement.parentElement.scrollIntoViewIfNeeded(false); selectElement.parentElement.scrollIntoViewIfNeeded(false);
let cssCompletions = []; this._prompt = new Elements.StylesSidebarPane.CSSPropertyPrompt(this, isEditingName);
if (isEditingName) {
cssCompletions = SDK.cssMetadata().allProperties();
if (!this.node().isSVGNode())
cssCompletions = cssCompletions.filter(property => !SDK.cssMetadata().isSVGProperty(property));
} else {
cssCompletions = SDK.cssMetadata().propertyValues(this.nameElement.textContent);
}
const cssVariables = this._matchedStyles.availableCSSVariables(this.property.ownerStyle);
cssVariables.sort(String.naturalOrderComparator);
this._prompt = new Elements.StylesSidebarPane.CSSPropertyPrompt(cssCompletions, cssVariables, this, isEditingName);
this._prompt.setAutocompletionTimeout(0); this._prompt.setAutocompletionTimeout(0);
if (section) if (section)
section.startEditing(); section.startEditing();
......
...@@ -2057,19 +2057,30 @@ Elements.KeyframePropertiesSection = class extends Elements.StylePropertiesSecti ...@@ -2057,19 +2057,30 @@ Elements.KeyframePropertiesSection = class extends Elements.StylePropertiesSecti
Elements.StylesSidebarPane.CSSPropertyPrompt = class extends UI.TextPrompt { Elements.StylesSidebarPane.CSSPropertyPrompt = class extends UI.TextPrompt {
/** /**
* @param {!Array<string>} cssCompletions
* @param {!Array<string>} cssVariables
* @param {!Elements.StylePropertyTreeElement} treeElement * @param {!Elements.StylePropertyTreeElement} treeElement
* @param {boolean} isEditingName * @param {boolean} isEditingName
*/ */
constructor(cssCompletions, cssVariables, treeElement, isEditingName) { constructor(treeElement, isEditingName) {
// Use the same callback both for applyItemCallback and acceptItemCallback. // Use the same callback both for applyItemCallback and acceptItemCallback.
super(); super();
this.initialize(this._buildPropertyCompletions.bind(this), UI.StyleValueDelimiters); this.initialize(this._buildPropertyCompletions.bind(this), UI.StyleValueDelimiters);
this._cssCompletions = cssCompletions; this._isColorAware = SDK.cssMetadata().isColorAwareProperty(treeElement.property.name);
this._cssVariables = cssVariables; this._cssCompletions = [];
if (isEditingName) {
this._cssCompletions = SDK.cssMetadata().allProperties();
if (!treeElement.node().isSVGNode())
this._cssCompletions = this._cssCompletions.filter(property => !SDK.cssMetadata().isSVGProperty(property));
} else {
this._cssCompletions = SDK.cssMetadata().propertyValues(treeElement.nameElement.textContent);
}
this._treeElement = treeElement; this._treeElement = treeElement;
this._isEditingName = isEditingName; this._isEditingName = isEditingName;
this._cssVariables = treeElement.matchedStyles().availableCSSVariables(treeElement.property.ownerStyle);
if (this._cssVariables.length < 1000)
this._cssVariables.sort(String.naturalOrderComparator);
else
this._cssVariables.sort();
if (!isEditingName) { if (!isEditingName) {
this.disableDefaultSuggestionForEmptyInput(); this.disableDefaultSuggestionForEmptyInput();
...@@ -2205,9 +2216,9 @@ Elements.StylesSidebarPane.CSSPropertyPrompt = class extends UI.TextPrompt { ...@@ -2205,9 +2216,9 @@ Elements.StylesSidebarPane.CSSPropertyPrompt = class extends UI.TextPrompt {
const prefixResults = []; const prefixResults = [];
const anywhereResults = []; const anywhereResults = [];
if (!editingVariable) if (!editingVariable)
this._cssCompletions.forEach(filterCompletions.bind(this)); this._cssCompletions.forEach(completion => filterCompletions.call(this, completion, false /* variable */));
if (this._isEditingName || editingVariable) if (this._isEditingName || editingVariable)
this._cssVariables.forEach(filterCompletions.bind(this)); this._cssVariables.forEach(variable => filterCompletions.call(this, variable, true /* variable */));
const results = prefixResults.concat(anywhereResults); const results = prefixResults.concat(anywhereResults);
if (!this._isEditingName && !results.length && query.length > 1 && '!important'.startsWith(lowerQuery)) if (!this._isEditingName && !results.length && query.length > 1 && '!important'.startsWith(lowerQuery))
...@@ -2219,23 +2230,57 @@ Elements.StylesSidebarPane.CSSPropertyPrompt = class extends UI.TextPrompt { ...@@ -2219,23 +2230,57 @@ Elements.StylesSidebarPane.CSSPropertyPrompt = class extends UI.TextPrompt {
results[i].text = results[i].text.toUpperCase(); results[i].text = results[i].text.toUpperCase();
} }
} }
if (editingVariable) if (editingVariable) {
results.forEach(result => result.text += ')'); results.forEach(result => {
result.title = result.text;
result.text += ')';
});
}
if (this._isColorAware && !this._isEditingName) {
results.stableSort((a, b) => {
if (!!a.subtitleRenderer === !!b.subtitleRenderer)
return 0;
return a.subtitleRenderer ? -1 : 1;
});
}
return Promise.resolve(results); return Promise.resolve(results);
/** /**
* @param {string} completion * @param {string} completion
* @param {boolean} variable
* @this {Elements.StylesSidebarPane.CSSPropertyPrompt} * @this {Elements.StylesSidebarPane.CSSPropertyPrompt}
*/ */
function filterCompletions(completion) { function filterCompletions(completion, variable) {
const index = completion.toLowerCase().indexOf(lowerQuery); const index = completion.toLowerCase().indexOf(lowerQuery);
const result = {text: completion};
if (variable) {
const computedValue =
this._treeElement.matchedStyles().computeCSSVariable(this._treeElement.property.ownerStyle, completion);
if (computedValue) {
const color = Common.Color.parse(computedValue);
if (color)
result.subtitleRenderer = swatchRenderer.bind(null, color);
}
}
if (index === 0) { if (index === 0) {
const priority = this._isEditingName ? SDK.cssMetadata().propertyUsageWeight(completion) : 1; result.priority = this._isEditingName ? SDK.cssMetadata().propertyUsageWeight(completion) : 1;
prefixResults.push({text: completion, priority: priority}); prefixResults.push(result);
} else if (index > -1) { } else if (index > -1) {
anywhereResults.push({text: completion}); anywhereResults.push(result);
} }
} }
/**
* @param {!Common.Color} color
* @return {!Element}
*/
function swatchRenderer(color) {
const swatch = InlineEditor.ColorSwatch.create();
swatch.hideText(true);
swatch.setColor(color);
swatch.style.pointerEvents = 'none';
return swatch;
}
} }
}; };
......
...@@ -18,6 +18,7 @@ InlineEditor.ColorSwatch = class extends HTMLSpanElement { ...@@ -18,6 +18,7 @@ InlineEditor.ColorSwatch = class extends HTMLSpanElement {
UI.registerCustomElement('span', 'color-swatch', InlineEditor.ColorSwatch.prototype); UI.registerCustomElement('span', 'color-swatch', InlineEditor.ColorSwatch.prototype);
} }
return /** @type {!InlineEditor.ColorSwatch} */ (new InlineEditor.ColorSwatch._constructor()); return /** @type {!InlineEditor.ColorSwatch} */ (new InlineEditor.ColorSwatch._constructor());
} }
...@@ -178,6 +179,7 @@ InlineEditor.BezierSwatch = class extends HTMLSpanElement { ...@@ -178,6 +179,7 @@ InlineEditor.BezierSwatch = class extends HTMLSpanElement {
UI.registerCustomElement('span', 'bezier-swatch', InlineEditor.BezierSwatch.prototype); UI.registerCustomElement('span', 'bezier-swatch', InlineEditor.BezierSwatch.prototype);
} }
return /** @type {!InlineEditor.BezierSwatch} */ (new InlineEditor.BezierSwatch._constructor()); return /** @type {!InlineEditor.BezierSwatch} */ (new InlineEditor.BezierSwatch._constructor());
} }
...@@ -221,7 +223,6 @@ InlineEditor.BezierSwatch = class extends HTMLSpanElement { ...@@ -221,7 +223,6 @@ InlineEditor.BezierSwatch = class extends HTMLSpanElement {
} }
}; };
/** /**
* @unrestricted * @unrestricted
*/ */
......
...@@ -220,7 +220,11 @@ UI.SuggestBox = class { ...@@ -220,7 +220,11 @@ UI.SuggestBox = class {
titleElement.createChild('span', 'query').textContent = displayText.substring(index, index + query.length); titleElement.createChild('span', 'query').textContent = displayText.substring(index, index + query.length);
titleElement.createChild('span').textContent = displayText.substring(index > -1 ? index + query.length : 0); titleElement.createChild('span').textContent = displayText.substring(index > -1 ? index + query.length : 0);
titleElement.createChild('span', 'spacer'); titleElement.createChild('span', 'spacer');
if (item.subtitle) { if (item.subtitleRenderer) {
const subtitleElement = item.subtitleRenderer.call(null);
subtitleElement.classList.add('suggestion-subtitle');
element.appendChild(subtitleElement);
} else if (item.subtitle) {
const subtitleElement = element.createChild('span', 'suggestion-subtitle'); const subtitleElement = element.createChild('span', 'suggestion-subtitle');
subtitleElement.textContent = item.subtitle.trimEnd(maxTextLength - displayText.length); subtitleElement.textContent = item.subtitle.trimEnd(maxTextLength - displayText.length);
} }
...@@ -386,7 +390,15 @@ UI.SuggestBox = class { ...@@ -386,7 +390,15 @@ UI.SuggestBox = class {
}; };
/** /**
* @typedef {!{text: string, subtitle: (string|undefined), iconType: (string|undefined), priority: (number|undefined), isSecondary: (boolean|undefined), title: (string|undefined)}} * @typedef {{
* text: string,
* title: (string|undefined),
* subtitle: (string|undefined),
* iconType: (string|undefined),
* priority: (number|undefined),
* isSecondary: (boolean|undefined),
* subtitleRenderer: (function():!Element|undefined)
* }}
*/ */
UI.SuggestBox.Suggestion; UI.SuggestBox.Suggestion;
......
...@@ -105,7 +105,7 @@ ...@@ -105,7 +105,7 @@
.user-has-interacted .suggest-box-content-item.selected > span { .user-has-interacted .suggest-box-content-item.selected > span {
color: var(--selection-fg-color); color: var(--selection-fg-color);
}} }
.suggest-box-content-item:hover:not(.selected) { .suggest-box-content-item:hover:not(.selected) {
background-color: rgba(56, 121, 217, 0.1); background-color: rgba(56, 121, 217, 0.1);
......
Markdown is supported
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