Diff
Modified: trunk/LayoutTests/ChangeLog (222958 => 222959)
--- trunk/LayoutTests/ChangeLog 2017-10-06 05:04:42 UTC (rev 222958)
+++ trunk/LayoutTests/ChangeLog 2017-10-06 05:48:19 UTC (rev 222959)
@@ -1,3 +1,15 @@
+2017-10-05 Nikita Vasilyev <nvasil...@apple.com>
+
+ Web Inspector: Styles Redesign: Add support for keyboard navigation (Tab, Shift-Tab, Enter, Esc)
+ https://bugs.webkit.org/show_bug.cgi?id=177711
+
+ Reviewed by Joseph Pecoraro.
+
+ Add tests for WI.CSSProperty.prototype.remove.
+
+ * inspector/css/css-property-expected.txt:
+ * inspector/css/css-property.html:
+
2017-10-05 Ryan Haddad <ryanhad...@apple.com>
Rebaseline fast/dom/navigator-detached-no-crash.html.
Modified: trunk/LayoutTests/inspector/css/css-property-expected.txt (222958 => 222959)
--- trunk/LayoutTests/inspector/css/css-property-expected.txt 2017-10-06 05:04:42 UTC (rev 222958)
+++ trunk/LayoutTests/inspector/css/css-property-expected.txt 2017-10-06 05:48:19 UTC (rev 222959)
@@ -50,3 +50,7 @@
PASS: "background-repeat-y" has the text "background-repeat-y: repeat;".
PASS: "background-repeat-y" has the _text (private) "".
+-- Running test case: CSSProperty.prototype.remove
+PASS: The removed property should no longer be in allProperties array.
+PASS: The second property should shift and become the first.
+
Modified: trunk/LayoutTests/inspector/css/css-property.html (222958 => 222959)
--- trunk/LayoutTests/inspector/css/css-property.html 2017-10-06 05:04:42 UTC (rev 222958)
+++ trunk/LayoutTests/inspector/css/css-property.html 2017-10-06 05:48:19 UTC (rev 222959)
@@ -202,6 +202,28 @@
}
});
+ suite.addTestCase({
+ name: "CSSProperty.prototype.remove",
+ description: "Ensure remove method removes a property from an array of properties.",
+ test(resolve, reject) {
+ for (let rule of nodeStyles.matchedRules) {
+ if (rule.selectorText !== "div#x")
+ continue;
+
+ let propertiesLength = rule.style.allProperties.length;
+ let firstProperty = rule.style.allProperties[0];
+ let secondProperty = rule.style.allProperties[1];
+
+ rule.style.allProperties[0].remove();
+
+ InspectorTest.expectEqual(rule.style.allProperties.length, propertiesLength - 1, "The removed property should no longer be in allProperties array.");
+ InspectorTest.expectEqual(rule.style.allProperties[0], secondProperty, "The second property should shift and become the first.");
+ }
+
+ resolve();
+ }
+ });
+
WI.domTreeManager.requestDocument((documentNode) => {
WI.domTreeManager.querySelector(documentNode.id, "div#x", (contentNodeId) => {
if (contentNodeId) {
Modified: trunk/Source/WebInspectorUI/ChangeLog (222958 => 222959)
--- trunk/Source/WebInspectorUI/ChangeLog 2017-10-06 05:04:42 UTC (rev 222958)
+++ trunk/Source/WebInspectorUI/ChangeLog 2017-10-06 05:48:19 UTC (rev 222959)
@@ -1,3 +1,150 @@
+2017-10-05 Nikita Vasilyev <nvasil...@apple.com>
+
+ Web Inspector: Styles Redesign: Add support for keyboard navigation (Tab, Shift-Tab, Enter, Esc)
+ https://bugs.webkit.org/show_bug.cgi?id=177711
+
+ Reviewed by Joseph Pecoraro.
+
+ Enter, Tab, Shift-Tab should commit changes.
+ Escape should discard changes.
+
+ Tab and Enter should navigate forward (focus on the next field).
+ Shift-Tab should navigate backward (focus on the previous field).
+ Esc should not change the focus.
+
+ When navigating forward from:
+
+ - Selector: Focus on the first property name. If it doesn’t exist, create a blank property.
+
+ - Property name:
+ - If property name is blank, discard the property and focus on the next editable field (property name or selector of the next rule).
+ - If property is not blank, focus on the value.
+
+ - Property value:
+ - If the last value in the rule, create a blank property and focus on its name.
+ - If not the last value in the rule, focus on the next editable field (property name or selector of the next rule).
+
+ When navigating backward from:
+
+ - Selector: create a blank property on the previous editable rule and focus on its name.
+
+ - Property name:
+ - Focus on the rule's selector.
+
+ - Property value:
+ - Focus on the property name.
+
+ * UserInterface/Base/Utilities.js:
+ (Event.prototype.stop):
+ * UserInterface/Main.html:
+ Add new files. Keep one class per file.
+
+ * UserInterface/Models/CSSProperty.js:
+ (WI.CSSProperty.prototype.remove):
+ (WI.CSSProperty.prototype.set name):
+ (WI.CSSProperty.prototype.set rawValue):
+ (WI.CSSProperty.prototype.get editable):
+ (WI.CSSProperty.prototype._updateStyleText):
+ (WI.CSSProperty.prototype._updateOwnerStyleText):
+ Update indices and ranges of properties following the edited one to prevent data corruption.
+
+ * UserInterface/Models/CSSStyleDeclaration.js:
+ (WI.CSSStyleDeclaration.prototype.get selectorEditable):
+ (WI.CSSStyleDeclaration.prototype.set text):
+ (WI.CSSStyleDeclaration.prototype.newBlankProperty):
+ (WI.CSSStyleDeclaration.prototype.shiftPropertiesAfter):
+ (WI.CSSStyleDeclaration.prototype._rangeAfterPropertyAtIndex):
+ Implement adding new blank properties.
+
+ * UserInterface/Models/TextRange.js:
+ (WI.TextRange.prototype.cloneAndModify):
+ Add an assert to catch negative number errors.
+
+ (WI.TextRange.prototype.collapseToEnd):
+ Add a utility function akin Selection.prototype.collapseToEnd.
+
+ * UserInterface/Views/CSSStyleDeclarationTextEditor.js:
+ (WI.CSSStyleDeclarationTextEditor.prototype._handleShiftTabKey):
+ (WI.CSSStyleDeclarationTextEditor.prototype._handleTabKey):
+ (WI.CSSStyleDeclarationTextEditor.prototype._formattedContent):
+ Move PrefixWhitespace from a view to a model (WI.CSSStyleDeclaration.PrefixWhitespace),
+ since it's already used in the model.
+
+ * UserInterface/Views/SpreadsheetCSSStyleDeclarationEditor.css:
+ (.spreadsheet-style-declaration-editor :matches(.name, .value).editing):
+ * UserInterface/Views/SpreadsheetCSSStyleDeclarationEditor.js:
+ (WI.SpreadsheetCSSStyleDeclarationEditor):
+ (WI.SpreadsheetCSSStyleDeclarationEditor.prototype.layout):
+ (WI.SpreadsheetCSSStyleDeclarationEditor.prototype.startEditingFirstProperty):
+ (WI.SpreadsheetCSSStyleDeclarationEditor.prototype.startEditingLastProperty):
+ (WI.SpreadsheetCSSStyleDeclarationEditor.prototype.spreadsheetCSSStyleDeclarationEditorFocusMoved):
+ (WI.SpreadsheetCSSStyleDeclarationEditor.prototype.spreadsheetStylePropertyRemoved):
+ (WI.SpreadsheetCSSStyleDeclarationEditor.prototype.get _propertiesToRender):
+ (WI.SpreadsheetCSSStyleDeclarationEditor.prototype._addBlankProperty):
+ (WI.SpreadsheetCSSStyleDeclarationEditor.prototype._isFocused):
+ (WI.SpreadsheetCSSStyleDeclarationEditor.prototype._propertiesChanged):
+ Give SpreadsheetCSSStyleDeclarationEditor a delegate so that it can move focus to a CSS selector, or previous and next CSS rules.
+
+ * UserInterface/Views/SpreadsheetCSSStyleDeclarationSection.js:
+ (WI.SpreadsheetCSSStyleDeclarationSection):
+ (WI.SpreadsheetCSSStyleDeclarationSection.prototype.get propertiesEditor):
+ (WI.SpreadsheetCSSStyleDeclarationSection.prototype.get editable):
+ (WI.SpreadsheetCSSStyleDeclarationSection.prototype.initialLayout):
+ (WI.SpreadsheetCSSStyleDeclarationSection.prototype.startEditingRuleSelector):
+ (WI.SpreadsheetCSSStyleDeclarationSection.prototype.cssStyleDeclarationTextEditorStartEditingRuleSelector):
+ (WI.SpreadsheetCSSStyleDeclarationSection.prototype.spreadsheetSelectorFieldDidChange):
+ (WI.SpreadsheetCSSStyleDeclarationSection.prototype.cssStyleDeclarationEditorStartEditingAdjacentRule):
+ Give SpreadsheetCSSStyleDeclarationSection a delegate so that it can move focus to previous and next CSS rules.
+
+ * UserInterface/Views/SpreadsheetRulesStyleDetailsPanel.js:
+ (WI.SpreadsheetRulesStyleDetailsPanel.prototype.refresh):
+ (WI.SpreadsheetRulesStyleDetailsPanel.prototype.cssStyleDeclarationSectionStartEditingNextRule):
+ (WI.SpreadsheetRulesStyleDetailsPanel.prototype.cssStyleDeclarationSectionStartEditingPreviousRule):
+ (WI.SpreadsheetRulesStyleDetailsPanel):
+ Implement focusing on the next and previous CSS rules.
+
+ * UserInterface/Views/SpreadsheetSelectorField.js: Added.
+ (WI.SpreadsheetSelectorField):
+ (WI.SpreadsheetSelectorField.prototype.get editing):
+ (WI.SpreadsheetSelectorField.prototype.startEditing):
+ (WI.SpreadsheetSelectorField.prototype.stopEditing):
+ (WI.SpreadsheetSelectorField.prototype._selectText):
+ (WI.SpreadsheetSelectorField.prototype._handleClick):
+ (WI.SpreadsheetSelectorField.prototype._handleFocus):
+ (WI.SpreadsheetSelectorField.prototype._handleBlur):
+ (WI.SpreadsheetSelectorField.prototype._handleKeyDown):
+ Move SpreadsheetSelectorField into its own file.
+
+ * UserInterface/Views/SpreadsheetStyleProperty.js: Added.
+ (WI.SpreadsheetStyleProperty):
+ (WI.SpreadsheetStyleProperty.prototype.get element):
+ (WI.SpreadsheetStyleProperty.prototype.get nameTextField):
+ (WI.SpreadsheetStyleProperty.prototype.get valueTextField):
+ (WI.SpreadsheetStyleProperty.prototype._remove):
+ (WI.SpreadsheetStyleProperty.prototype._update):
+ (WI.SpreadsheetStyleProperty.prototype.spreadsheetTextFieldDidChange):
+ (WI.SpreadsheetStyleProperty.prototype.spreadsheetTextFieldDidCommit):
+ (WI.SpreadsheetStyleProperty.prototype.spreadsheetTextFieldDidBlur):
+ (WI.SpreadsheetStyleProperty.prototype._handleNameChange):
+ (WI.SpreadsheetStyleProperty.prototype._handleValueChange):
+ Move SpreadsheetStyleProperty into its own file.
+
+ * UserInterface/Views/SpreadsheetTextField.js: Added.
+ (WI.SpreadsheetTextField):
+ (WI.SpreadsheetTextField.prototype.get element):
+ (WI.SpreadsheetTextField.prototype.get editing):
+ (WI.SpreadsheetTextField.prototype.get value):
+ (WI.SpreadsheetTextField.prototype.set value):
+ (WI.SpreadsheetTextField.prototype.startEditing):
+ (WI.SpreadsheetTextField.prototype.stopEditing):
+ (WI.SpreadsheetTextField.prototype._selectText):
+ (WI.SpreadsheetTextField.prototype._discardChange):
+ (WI.SpreadsheetTextField.prototype._handleFocus):
+ (WI.SpreadsheetTextField.prototype._handleBlur):
+ (WI.SpreadsheetTextField.prototype._handleKeyDown):
+ (WI.SpreadsheetTextField.prototype._handleInput):
+ Introduce SpreadsheetTextField that is used for editing CSS property names and values.
+
2017-10-05 Joseph Pecoraro <pecor...@apple.com>
REGRESSION(r222868): Web Inspector: Timeline ScopeBar Navigation Bar items too large
Modified: trunk/Source/WebInspectorUI/UserInterface/Base/Utilities.js (222958 => 222959)
--- trunk/Source/WebInspectorUI/UserInterface/Base/Utilities.js 2017-10-06 05:04:42 UTC (rev 222958)
+++ trunk/Source/WebInspectorUI/UserInterface/Base/Utilities.js 2017-10-06 05:48:19 UTC (rev 222959)
@@ -456,6 +456,15 @@
value: Element.prototype.createChild
});
+Object.defineProperty(Event.prototype, "stop",
+{
+ value()
+ {
+ this.stopImmediatePropagation();
+ this.preventDefault();
+ }
+});
+
Object.defineProperty(Array, "shallowEqual",
{
value(a, b)
Modified: trunk/Source/WebInspectorUI/UserInterface/Main.html (222958 => 222959)
--- trunk/Source/WebInspectorUI/UserInterface/Main.html 2017-10-06 05:04:42 UTC (rev 222958)
+++ trunk/Source/WebInspectorUI/UserInterface/Main.html 2017-10-06 05:48:19 UTC (rev 222959)
@@ -721,6 +721,9 @@
<script src=""
<script src=""
<script src=""
+ <script src=""
+ <script src=""
+ <script src=""
<script src=""
<script src=""
Modified: trunk/Source/WebInspectorUI/UserInterface/Models/CSSProperty.js (222958 => 222959)
--- trunk/Source/WebInspectorUI/UserInterface/Models/CSSProperty.js 2017-10-06 05:04:42 UTC (rev 222958)
+++ trunk/Source/WebInspectorUI/UserInterface/Models/CSSProperty.js 2017-10-06 05:48:19 UTC (rev 222959)
@@ -118,6 +118,12 @@
this.dispatchEventToListeners(WI.CSSProperty.Event.Changed);
}
+ remove()
+ {
+ // Setting name or value to an empty string removes the entire CSSProperty.
+ this.name = "";
+ }
+
commentOut(disabled)
{
console.assert(this._enabled === disabled, "CSS property is already " + (disabled ? "disabled" : "enabled"));
@@ -167,7 +173,7 @@
return;
this._name = name;
- this._updateStyle();
+ this._updateStyleText();
}
get canonicalName()
@@ -201,7 +207,7 @@
this._rawValue = value;
this._value = undefined;
- this._updateStyle();
+ this._updateStyleText();
}
get important()
@@ -258,7 +264,7 @@
get editable()
{
- return this._styleSheetTextRange && this._ownerStyle && this._ownerStyle.styleSheetTextRange;
+ return !!(this._styleSheetTextRange && this._ownerStyle && this._ownerStyle.styleSheetTextRange);
}
get styleDeclarationTextRange()
@@ -319,15 +325,20 @@
// Private
- _updateStyle()
+ _updateStyleText()
{
- let text = this._name + ": " + this._rawValue + ";";
- this._updateOwnerStyleText(this._text, text);
+ let text = "";
+
+ if (this._name && this._rawValue)
+ text = this._name + ": " + this._rawValue + ";";
+
+ let oldText = this._text;
+ this._text = text;
+ this._updateOwnerStyleText(oldText, this._text);
}
_updateOwnerStyleText(oldText, newText)
{
- console.assert(oldText !== newText, `Style text did not change ${oldText}`);
if (oldText === newText)
return;
@@ -338,9 +349,18 @@
let range = this._styleSheetTextRange.relativeTo(this._ownerStyle.styleSheetTextRange.startLine, this._ownerStyle.styleSheetTextRange.startColumn);
range.resolveOffsets(styleText);
+ console.assert(oldText === styleText.slice(range.startOffset, range.endOffset), "_styleSheetTextRange data is invalid.");
+
let newStyleText = styleText.slice(0, range.startOffset) + newText + styleText.slice(range.endOffset);
- this._styleSheetTextRange = this._styleSheetTextRange.cloneAndModify(0, 0, newText.lineCount - oldText.lineCount, newText.lastLine.length - oldText.lastLine.length);
+
+ let lineDelta = newText.lineCount - oldText.lineCount;
+ let columnDelta = newText.lastLine.length - oldText.lastLine.length;
+ this._styleSheetTextRange = this._styleSheetTextRange.cloneAndModify(0, 0, lineDelta, columnDelta);
+
this._ownerStyle.text = newStyleText;
+
+ let propertyWasRemoved = !newText;
+ this._ownerStyle.shiftPropertiesAfter(this, lineDelta, columnDelta, propertyWasRemoved);
}
};
Modified: trunk/Source/WebInspectorUI/UserInterface/Models/CSSStyleDeclaration.js (222958 => 222959)
--- trunk/Source/WebInspectorUI/UserInterface/Models/CSSStyleDeclaration.js 2017-10-06 05:04:42 UTC (rev 222958)
+++ trunk/Source/WebInspectorUI/UserInterface/Models/CSSStyleDeclaration.js 2017-10-06 05:48:19 UTC (rev 222959)
@@ -93,6 +93,11 @@
return false;
}
+ get selectorEditable()
+ {
+ return this._ownerRule && this._ownerRule.editable;
+ }
+
update(text, properties, styleSheetTextRange, dontFireEvents)
{
text = text || "";
@@ -191,11 +196,11 @@
if (this._text === text)
return;
- let trimmedText = WI.CSSStyleDeclarationTextEditor.PrefixWhitespace + text.trim();
+ let trimmedText = WI.CSSStyleDeclaration.PrefixWhitespace + text.trim();
if (this._text === trimmedText)
return;
- if (trimmedText === WI.CSSStyleDeclarationTextEditor.PrefixWhitespace || this._type === WI.CSSStyleDeclaration.Type.Inline)
+ if (trimmedText === WI.CSSStyleDeclaration.PrefixWhitespace || this._type === WI.CSSStyleDeclaration.Type.Inline)
text = trimmedText;
let modified = text !== this._initialText;
@@ -352,6 +357,49 @@
return !!this._properties.length;
}
+ newBlankProperty(insertAfterIndex)
+ {
+ let text, name, value, priority, overridden, implicit, anonymous;
+ let enabled = true;
+ let valid = true;
+ let styleSheetTextRange = this._rangeAfterPropertyAtIndex(insertAfterIndex);
+ let property = new WI.CSSProperty(insertAfterIndex + 1, text, name, value, priority, enabled, overridden, implicit, anonymous, valid, styleSheetTextRange);
+ property.ownerStyle = this;
+
+ return property;
+ }
+
+ shiftPropertiesAfter(cssProperty, lineDelta, columnDelta, propertyWasRemoved)
+ {
+ // cssProperty.index could be set to NaN by WI.CSSStyleDeclaration.prototype.update.
+ let realIndex = this._allProperties.indexOf(cssProperty);
+ if (realIndex === -1)
+ return;
+
+ let endLine = cssProperty.styleSheetTextRange.endLine;
+
+ for (let i = realIndex + 1; i < this._allProperties.length; i++) {
+ let property = this._allProperties[i];
+
+ if (property._styleSheetTextRange) {
+ if (property.styleSheetTextRange.startLine === endLine) {
+ // Only update column data if it's on the same line.
+ property._styleSheetTextRange = property._styleSheetTextRange.cloneAndModify(lineDelta, columnDelta, lineDelta, columnDelta);
+ } else
+ property._styleSheetTextRange = property._styleSheetTextRange.cloneAndModify(lineDelta, 0, lineDelta, 0);
+ }
+
+ if (propertyWasRemoved && !isNaN(property._index))
+ property._index--;
+ }
+
+ if (propertyWasRemoved)
+ this._allProperties.splice(realIndex, 1);
+
+ // Invalidate cached properties.
+ this._allVisibleProperties = null;
+ }
+
// Protected
get nodeStyles()
@@ -358,6 +406,19 @@
{
return this._nodeStyles;
}
+
+ // Private
+
+ _rangeAfterPropertyAtIndex(index)
+ {
+ if (index > 0) {
+ let property = this.allVisibleProperties[index];
+ if (property && property.styleSheetTextRange)
+ return property.styleSheetTextRange.collapseToEnd();
+ }
+
+ return this._styleSheetTextRange.collapseToEnd();
+ }
};
WI.CSSStyleDeclaration.Event = {
@@ -371,3 +432,5 @@
Attribute: "css-style-declaration-type-attribute",
Computed: "css-style-declaration-type-computed"
};
+
+WI.CSSStyleDeclaration.PrefixWhitespace = "\n";
Modified: trunk/Source/WebInspectorUI/UserInterface/Models/TextRange.js (222958 => 222959)
--- trunk/Source/WebInspectorUI/UserInterface/Models/TextRange.js 2017-10-06 05:04:42 UTC (rev 222958)
+++ trunk/Source/WebInspectorUI/UserInterface/Models/TextRange.js 2017-10-06 05:48:19 UTC (rev 222959)
@@ -118,9 +118,22 @@
cloneAndModify(deltaStartLine, deltaStartColumn, deltaEndLine, deltaEndColumn)
{
console.assert(!isNaN(this._startLine), "TextRange needs line/column data.");
- return new WI.TextRange(this._startLine + deltaStartLine, this._startColumn + deltaStartColumn, this._endLine + deltaEndLine, this._endColumn + deltaEndColumn);
+
+ let startLine = this._startLine + deltaStartLine;
+ let startColumn = this._startColumn + deltaStartColumn;
+ let endLine = this._endLine + deltaEndLine;
+ let endColumn = this._endColumn + deltaEndColumn;
+ console.assert(startLine >= 0 && startColumn >= 0 && endLine >= 0 && endColumn >= 0, `Cannot have negative numbers in TextRange ${startLine}:${startColumn}...${endLine}:${endColumn}`);
+
+ return new WI.TextRange(startLine, startColumn, endLine, endColumn);
}
+ collapseToEnd()
+ {
+ console.assert(!isNaN(this._endLine), "TextRange needs line/column data.");
+ return new WI.TextRange(this._endLine, this._endColumn, this._endLine, this._endColumn);
+ }
+
relativeTo(line, column)
{
let deltaStartColumn = 0;
Modified: trunk/Source/WebInspectorUI/UserInterface/Views/CSSStyleDeclarationTextEditor.js (222958 => 222959)
--- trunk/Source/WebInspectorUI/UserInterface/Views/CSSStyleDeclarationTextEditor.js 2017-10-06 05:04:42 UTC (rev 222958)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/CSSStyleDeclarationTextEditor.js 2017-10-06 05:48:19 UTC (rev 222959)
@@ -583,8 +583,8 @@
{
function switchRule()
{
- if (this._delegate && typeof this._delegate.cssStyleDeclarationTextEditorSwitchRule === "function") {
- this._delegate.cssStyleDeclarationTextEditorSwitchRule(true);
+ if (this._delegate && typeof this._delegate.cssStyleDeclarationEditorStartEditingAdjacentRule === "function") {
+ this._delegate.cssStyleDeclarationEditorStartEditingAdjacentRule(true);
return;
}
@@ -639,8 +639,8 @@
_handleTabKey(codeMirror)
{
function switchRule() {
- if (this._delegate && typeof this._delegate.cssStyleDeclarationTextEditorSwitchRule === "function") {
- this._delegate.cssStyleDeclarationTextEditorSwitchRule();
+ if (this._delegate && typeof this._delegate.cssStyleDeclarationEditorStartEditingAdjacentRule === "function") {
+ this._delegate.cssStyleDeclarationEditorStartEditingAdjacentRule();
return;
}
@@ -740,7 +740,7 @@
_formattedContent()
{
// Start with the prefix whitespace we stripped.
- var content = WI.CSSStyleDeclarationTextEditor.PrefixWhitespace;
+ var content = WI.CSSStyleDeclaration.PrefixWhitespace;
// Get each line and add the line prefix whitespace and newlines.
var lineCount = this._codeMirror.lineCount();
@@ -1801,7 +1801,6 @@
HideNonVariables: Symbol("variable-visibility-hide-non-variables"),
};
-WI.CSSStyleDeclarationTextEditor.PrefixWhitespace = "\n";
WI.CSSStyleDeclarationTextEditor.SuffixWhitespace = "\n";
WI.CSSStyleDeclarationTextEditor.StyleClassName = "css-style-text-editor";
WI.CSSStyleDeclarationTextEditor.ReadOnlyStyleClassName = "read-only";
Modified: trunk/Source/WebInspectorUI/UserInterface/Views/SpreadsheetCSSStyleDeclarationEditor.css (222958 => 222959)
--- trunk/Source/WebInspectorUI/UserInterface/Views/SpreadsheetCSSStyleDeclarationEditor.css 2017-10-06 05:04:42 UTC (rev 222958)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/SpreadsheetCSSStyleDeclarationEditor.css 2017-10-06 05:48:19 UTC (rev 222959)
@@ -37,9 +37,11 @@
color: black;
}
-.spreadsheet-style-declaration-editor :matches(.name, .value):focus {
- outline: 1px solid white;
+.spreadsheet-style-declaration-editor :matches(.name, .value).editing {
+ outline: 1px solid white !important;
box-shadow: 0 1px 2px 1px hsla(0, 0%, 0%, 0.6);
+ margin-bottom: 0 !important;
+ padding-bottom: 0 !important;
}
.spreadsheet-style-declaration-editor.no-properties {
Modified: trunk/Source/WebInspectorUI/UserInterface/Views/SpreadsheetCSSStyleDeclarationEditor.js (222958 => 222959)
--- trunk/Source/WebInspectorUI/UserInterface/Views/SpreadsheetCSSStyleDeclarationEditor.js 2017-10-06 05:04:42 UTC (rev 222958)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/SpreadsheetCSSStyleDeclarationEditor.js 2017-10-06 05:48:19 UTC (rev 222959)
@@ -31,6 +31,7 @@
this.element.classList.add(WI.SpreadsheetCSSStyleDeclarationEditor.StyleClassName);
+ this._delegate = delegate;
this.style = style;
}
@@ -45,8 +46,13 @@
let properties = this._propertiesToRender;
this.element.classList.toggle("no-properties", !properties.length);
- for (let property of properties)
- this.element.append(new WI.SpreadsheetStyleProperty(property).element);
+ this._propertyViews = [];
+ for (let index = 0; index < properties.length; index++) {
+ let property = properties[index];
+ let propertyView = new WI.SpreadsheetStyleProperty(this, property, index);
+ this.element.append(propertyView.element);
+ this._propertyViews.push(propertyView);
+ }
}
get style()
@@ -70,142 +76,106 @@
this.needsLayout();
}
- // Private
-
- get _propertiesToRender()
+ startEditingFirstProperty()
{
- if (this._style._styleSheetTextRange)
- return this._style.allVisibleProperties;
-
- return this._style.allProperties;
+ if (this._propertyViews.length)
+ this._propertyViews[0].nameTextField.startEditing();
+ else {
+ let index = 0;
+ this._addBlankProperty(index);
+ }
}
- _propertiesChanged(event)
+ startEditingLastProperty()
{
- let focusedElement = document.activeElement;
- let isFocused = focusedElement && focusedElement.isSelfOrDescendant(this.element);
- if (!isFocused)
- this.needsLayout();
+ let lastProperty = this._propertyViews.lastValue;
+ if (lastProperty)
+ lastProperty.valueTextField.startEditing();
+ else {
+ let index = 0;
+ this._addBlankProperty(index);
+ }
}
-};
-WI.SpreadsheetCSSStyleDeclarationEditor.StyleClassName = "spreadsheet-style-declaration-editor";
-
-WI.SpreadsheetStyleProperty = class SpreadsheetStyleProperty extends WI.Object
-{
- constructor(property)
+ spreadsheetCSSStyleDeclarationEditorFocusMoved({direction, movedFromProperty, willRemoveProperty})
{
- super();
+ let movedFromIndex = this._propertyViews.indexOf(movedFromProperty);
+ console.assert(movedFromIndex !== -1, "Property doesn't exist, focusing on a selector as a fallback.");
+ if (movedFromIndex === -1) {
+ if (this._style.selectorEditable)
+ this._delegate.cssStyleDeclarationTextEditorStartEditingRuleSelector();
- this._property = property;
- this._element = document.createElement("div");
+ return;
+ }
- this._update();
+ if (direction === "forward") {
+ // Move from the value to the next property's name.
+ let index = movedFromIndex + 1;
+ if (index < this._propertyViews.length)
+ this._propertyViews[index].nameTextField.startEditing();
+ else {
+ if (willRemoveProperty) {
+ // Move from the last value in the rule to the next rule's selector.
+ let reverse = false;
+ this._delegate.cssStyleDeclarationEditorStartEditingAdjacentRule(reverse);
+ } else
+ this._addBlankProperty(movedFromIndex);
+ }
+ } else {
+ let index = movedFromIndex - 1;
+ if (index < 0) {
+ // Move from the first property's name to the rule's selector.
+ if (this._style.selectorEditable)
+ this._delegate.cssStyleDeclarationTextEditorStartEditingRuleSelector();
+ } else {
+ // Move from the property's name to the previous property's value.
+ let valueTextField = this._propertyViews[index].valueTextField;
+ if (valueTextField)
+ valueTextField.startEditing();
+ }
+ }
}
- // Public
+ spreadsheetStylePropertyRemoved(propertyView)
+ {
+ this._propertyViews.remove(propertyView);
+ }
- get element() { return this._element; }
-
// Private
- _update()
+ get _propertiesToRender()
{
- this.element.removeChildren();
- this.element.className = "";
+ if (this._style._styleSheetTextRange)
+ return this._style.allVisibleProperties;
- let duplicatePropertyExistsBelow = (cssProperty) => {
- let propertyFound = false;
+ return this._style.allProperties;
+ }
- for (let property of this._property.ownerStyle.properties) {
- if (property === cssProperty)
- propertyFound = true;
- else if (property.name === cssProperty.name && propertyFound)
- return true;
- }
+ _addBlankProperty(afterIndex)
+ {
+ let blankProperty = this._style.newBlankProperty(afterIndex);
+ const newlyAdded = true;
+ let propertyView = new WI.SpreadsheetStyleProperty(this, blankProperty, blankProperty.index, newlyAdded);
+ this.element.append(propertyView.element);
+ this._propertyViews.push(propertyView);
+ propertyView.nameTextField.startEditing();
+ }
+ _isFocused()
+ {
+ let focusedElement = document.activeElement;
+
+ if (!focusedElement || focusedElement.tagName === "BODY")
return false;
- };
- let classNames = ["property"];
-
- if (this._property.overridden)
- classNames.push("overridden");
-
- if (this._property.implicit)
- classNames.push("implicit");
-
- if (this._property.ownerStyle.inherited && !this._property.inherited)
- classNames.push("not-inherited");
-
- if (!this._property.valid && this._property.hasOtherVendorNameOrKeyword())
- classNames.push("other-vendor");
- else if (!this._property.valid) {
- let propertyNameIsValid = false;
- if (WI.CSSCompletions.cssNameCompletions)
- propertyNameIsValid = WI.CSSCompletions.cssNameCompletions.isValidPropertyName(this._property.name);
-
- if (!propertyNameIsValid || duplicatePropertyExistsBelow(this._property))
- classNames.push("invalid");
- }
-
- if (!this._property.enabled)
- classNames.push("disabled");
-
- this._element.classList.add(...classNames);
-
- if (this._property.editable) {
- this._checkboxElement = this.element.appendChild(document.createElement("input"));
- this._checkboxElement.classList.add("property-toggle");
- this._checkboxElement.type = "checkbox";
- this._checkboxElement.checked = this._property.enabled;
- this._checkboxElement.addEventListener("change", () => {
- let disabled = !this._checkboxElement.checked;
- this._property.commentOut(disabled);
- this._update();
- });
- }
-
- if (!this._property.enabled)
- this.element.append("/* ");
-
- this._nameElement = this.element.appendChild(document.createElement("span"));
- this._nameElement.classList.add("name");
- this._nameElement.textContent = this._property.name;
-
- this.element.append(": ");
-
- this._valueElement = this.element.appendChild(document.createElement("span"));
- this._valueElement.classList.add("value");
- this._valueElement.textContent = this._property.rawValue;
-
- if (this._property.editable && this._property.enabled) {
- this._nameElement.tabIndex = 1;
- this._nameElement.contentEditable = "plaintext-only";
- this._nameElement.spellcheck = false;
- this._nameElement.addEventListener("input", this.debounce(WI.SpreadsheetStyleProperty.CommitCoalesceDelay)._handleNameChange);
-
- this._valueElement.tabIndex = 1;
- this._valueElement.contentEditable = "plaintext-only";
- this._valueElement.spellcheck = false;
- this._valueElement.addEventListener("input", this.debounce(WI.SpreadsheetStyleProperty.CommitCoalesceDelay)._handleValueChange);
- }
-
- this.element.append(";");
-
- if (!this._property.enabled)
- this.element.append(" */");
+ return focusedElement.isSelfOrDescendant(this.element);
}
- _handleNameChange()
+ _propertiesChanged(event)
{
- this._property.name = this._nameElement.textContent.trim();
+ if (!this._isFocused())
+ this.needsLayout();
}
-
- _handleValueChange()
- {
- this._property.rawValue = this._valueElement.textContent.trim();
- }
};
-WI.SpreadsheetStyleProperty.CommitCoalesceDelay = 250;
+WI.SpreadsheetCSSStyleDeclarationEditor.StyleClassName = "spreadsheet-style-declaration-editor";
Modified: trunk/Source/WebInspectorUI/UserInterface/Views/SpreadsheetCSSStyleDeclarationSection.js (222958 => 222959)
--- trunk/Source/WebInspectorUI/UserInterface/Views/SpreadsheetCSSStyleDeclarationSection.js 2017-10-06 05:04:42 UTC (rev 222958)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/SpreadsheetCSSStyleDeclarationSection.js 2017-10-06 05:48:19 UTC (rev 222959)
@@ -35,6 +35,7 @@
this._delegate = delegate || null;
this._style = style;
+ this._propertiesEditor = null;
this._selectorElements = [];
}
@@ -42,9 +43,11 @@
get style() { return this._style; }
- get selectorEditable()
+ get propertiesEditor() { return this._propertiesEditor; }
+
+ get editable()
{
- return this._style.editable && this._style.ownerRule;
+ return this._style.editable;
}
initialLayout()
@@ -60,12 +63,11 @@
this._selectorElement = document.createElement("span");
this._selectorElement.classList.add("selector");
+ this._selectorElement.tabIndex = 0;
this._headerElement.append(this._selectorElement);
- if (this.selectorEditable) {
- this._selectorElement.tabIndex = 1;
+ if (this._style.selectorEditable)
this._selectorTextField = new WI.SpreadsheetSelectorField(this, this._selectorElement);
- }
this._propertiesEditor = new WI.SpreadsheetCSSStyleDeclarationEditor(this, this._style);
this._propertiesEditor.element.classList.add("properties");
@@ -97,23 +99,38 @@
this._renderSelector();
}
- cssStyleDeclarationTextEditorFocused()
+ startEditingRuleSelector()
{
- if (this._delegate && typeof this._delegate.cssStyleDeclarationSectionEditorFocused === "function")
- this._delegate.cssStyleDeclarationSectionEditorFocused(this);
+ this._selectorElement.focus();
}
- spreadsheetSelectorFieldDidChange()
+ cssStyleDeclarationTextEditorStartEditingRuleSelector()
{
+ this.startEditingRuleSelector();
+ }
+
+ spreadsheetSelectorFieldDidChange(direction)
+ {
let selectorText = this._selectorElement.textContent.trim();
- if (!selectorText || selectorText === this._style.ownerRule.selectorText) {
+
+ if (!selectorText || selectorText === this._style.ownerRule.selectorText)
this._discardSelectorChange();
+ else {
+ this._style.ownerRule.singleFireEventListener(WI.CSSRule.Event.SelectorChanged, this._renderSelector, this);
+ this._style.ownerRule.selectorText = selectorText;
+ }
+
+ if (!direction) {
+ // Don't do anything when it's a blur event.
return;
}
- this._style.ownerRule.singleFireEventListener(WI.CSSRule.Event.SelectorChanged, this._renderSelector, this);
-
- this._style.ownerRule.selectorText = selectorText;
+ if (direction === "forward")
+ this._propertiesEditor.startEditingFirstProperty();
+ else if (direction === "backward") {
+ if (typeof this._delegate.cssStyleDeclarationSectionStartEditingPreviousRule === "function")
+ this._delegate.cssStyleDeclarationSectionStartEditingPreviousRule(this);
+ }
}
spreadsheetSelectorFieldDidDiscard()
@@ -121,6 +138,17 @@
this._discardSelectorChange();
}
+ cssStyleDeclarationEditorStartEditingAdjacentRule(toPreviousRule)
+ {
+ if (!this._delegate)
+ return;
+
+ if (toPreviousRule && typeof this._delegate.cssStyleDeclarationSectionStartEditingPreviousRule === "function")
+ this._delegate.cssStyleDeclarationSectionStartEditingPreviousRule(this);
+ else if (!toPreviousRule && typeof this._delegate.cssStyleDeclarationSectionStartEditingNextRule === "function")
+ this._delegate.cssStyleDeclarationSectionStartEditingNextRule(this);
+ }
+
// Private
_discardSelectorChange()
@@ -294,112 +322,3 @@
};
WI.SpreadsheetCSSStyleDeclarationSection.MatchedSelectorElementStyleClassName = "matched";
-
-WI.SpreadsheetSelectorField = class SpreadsheetSelectorField
-{
- constructor(delegate, element)
- {
- this._delegate = delegate;
- this._element = element;
- this._element.classList.add("spreadsheet-selector-field");
-
- this._element.addEventListener("click", this._handleClick.bind(this));
- this._element.addEventListener("focus", this._handleFocus.bind(this));
- this._element.addEventListener("blur", this._handleBlur.bind(this));
- this._element.addEventListener("keydown", this._handleKeyDown.bind(this));
-
- this._editing = false;
- }
-
- // Public
-
- get editing() { return this._editing; }
-
- startEditing()
- {
- if (this._editing)
- return;
-
- this._editing = true;
-
- let element = this._element;
- element.classList.add("editing");
- element.contentEditable = "plaintext-only";
- element.spellcheck = false;
- element.scrollIntoViewIfNeeded(false);
-
- // Disable syntax highlighting.
- element.textContent = element.textContent;
-
- let selection = window.getSelection();
- let range = document.createRange();
- range.selectNodeContents(element);
- selection.removeAllRanges();
- selection.addRange(range);
- }
-
- stopEditing()
- {
- if (!this._editing)
- return;
-
- this._editing = false;
- this._element.classList.remove("editing");
- this._element.contentEditable = false;
- }
-
- // Private
-
- _handleClick(event)
- {
- this.startEditing();
- }
-
- _handleFocus(event)
- {
- this.startEditing();
- }
-
- _handleBlur(event)
- {
- this.stopEditing();
-
- if (this._delegate && typeof this._delegate.spreadsheetSelectorFieldDidChange === "function")
- this._delegate.spreadsheetSelectorFieldDidChange();
- }
-
- _handleKeyDown(event)
- {
- if (event.key === "Enter" && !this._editing) {
- event.stopImmediatePropagation();
- event.preventDefault();
-
- this.startEditing();
- return;
- }
-
- if (event.key === "Enter" || event.key === "Tab") {
- if (event.key === "Enter") {
- event.stopImmediatePropagation();
- event.preventDefault();
- }
-
- this.stopEditing();
-
- if (this._delegate && typeof this._delegate.spreadsheetSelectorFieldDidChange === "function")
- this._delegate.spreadsheetSelectorFieldDidChange();
-
- return;
- }
-
- if (event.key === "Escape") {
- event.stopImmediatePropagation();
- event.preventDefault();
-
- this.stopEditing();
-
- if (this._delegate && typeof this._delegate.spreadsheetSelectorFieldDidDiscard === "function")
- this._delegate.spreadsheetSelectorFieldDidDiscard();
- }
- }
-};
Modified: trunk/Source/WebInspectorUI/UserInterface/Views/SpreadsheetRulesStyleDetailsPanel.js (222958 => 222959)
--- trunk/Source/WebInspectorUI/UserInterface/Views/SpreadsheetRulesStyleDetailsPanel.js 2017-10-06 05:04:42 UTC (rev 222958)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/SpreadsheetRulesStyleDetailsPanel.js 2017-10-06 05:48:19 UTC (rev 222959)
@@ -99,6 +99,8 @@
let orderedStyles = uniqueOrderedStyles(this.nodeStyles.orderedStyles);
let previousStyle = null;
+ this._sections = [];
+
for (let style of orderedStyles) {
if (style.inherited && (!previousStyle || previousStyle.node !== style.node))
this.element.append(createInheritedHeader(style));
@@ -117,6 +119,7 @@
this.addSubview(section);
section.needsLayout();
+ this._sections.push(section);
previousStyle = style;
}
@@ -125,6 +128,31 @@
super.refresh(significantChange);
}
+
+ cssStyleDeclarationSectionStartEditingNextRule(currentSection)
+ {
+ let currentIndex = this._sections.indexOf(currentSection);
+ let index = currentIndex < this._sections.length - 1 ? currentIndex + 1 : 0;
+ this._sections[index].startEditingRuleSelector();
+ }
+
+ cssStyleDeclarationSectionStartEditingPreviousRule(currentSection)
+ {
+ let index = this._sections.indexOf(currentSection);
+ console.assert(index > -1);
+
+ while (true) {
+ index--;
+ if (index < 0)
+ break;
+
+ let section = this._sections[index];
+ if (section.editable) {
+ section._propertiesEditor.startEditingLastProperty();
+ break;
+ }
+ }
+ }
};
WI.SpreadsheetRulesStyleDetailsPanel.RuleSection = Symbol("rule-section");
Added: trunk/Source/WebInspectorUI/UserInterface/Views/SpreadsheetSelectorField.js (0 => 222959)
--- trunk/Source/WebInspectorUI/UserInterface/Views/SpreadsheetSelectorField.js (rev 0)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/SpreadsheetSelectorField.js 2017-10-06 05:48:19 UTC (rev 222959)
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2017 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+WI.SpreadsheetSelectorField = class SpreadsheetSelectorField
+{
+ constructor(delegate, element)
+ {
+ this._delegate = delegate;
+ this._element = element;
+ this._element.classList.add("spreadsheet-selector-field");
+
+ this._element.addEventListener("focus", this._handleFocus.bind(this));
+ this._element.addEventListener("blur", this._handleBlur.bind(this));
+ this._element.addEventListener("keydown", this._handleKeyDown.bind(this));
+
+ this._editing = false;
+ }
+
+ // Public
+
+ get editing() { return this._editing; }
+
+ startEditing()
+ {
+ if (this._editing)
+ return;
+
+ this._editing = true;
+
+ let element = this._element;
+ element.classList.add("editing");
+ element.contentEditable = "plaintext-only";
+ element.spellcheck = false;
+ element.scrollIntoViewIfNeeded(false);
+
+ // Disable syntax highlighting.
+ element.textContent = element.textContent;
+
+ this._selectText();
+ }
+
+ stopEditing()
+ {
+ if (!this._editing)
+ return;
+
+ this._editing = false;
+ this._element.classList.remove("editing");
+ this._element.contentEditable = false;
+ }
+
+ // Private
+
+ _selectText()
+ {
+ window.getSelection().selectAllChildren(this._element);
+ }
+
+ _handleFocus(event)
+ {
+ this.startEditing();
+ }
+
+ _handleBlur(event)
+ {
+ this.stopEditing();
+
+ if (this._delegate && typeof this._delegate.spreadsheetSelectorFieldDidChange === "function")
+ this._delegate.spreadsheetSelectorFieldDidChange(null);
+ }
+
+ _handleKeyDown(event)
+ {
+ if (event.key === "Enter" && !this._editing) {
+ event.stop();
+
+ this.startEditing();
+ return;
+ }
+
+ if (!this._editing)
+ return;
+
+ if (event.key === "Enter" || event.key === "Tab") {
+ event.stop();
+
+ this.stopEditing();
+
+ if (this._delegate && typeof this._delegate.spreadsheetSelectorFieldDidChange === "function") {
+ let direction = (event.shiftKey && event.key === "Tab") ? "backward" : "forward";
+ this._delegate.spreadsheetSelectorFieldDidChange(direction);
+ }
+
+ return;
+ }
+
+ if (event.key === "Escape") {
+ event.stop();
+
+ this.stopEditing();
+
+ if (this._delegate && typeof this._delegate.spreadsheetSelectorFieldDidDiscard === "function")
+ this._delegate.spreadsheetSelectorFieldDidDiscard();
+ }
+ }
+};
Copied: trunk/Source/WebInspectorUI/UserInterface/Views/SpreadsheetStyleProperty.js (from rev 222958, trunk/Source/WebInspectorUI/UserInterface/Views/SpreadsheetCSSStyleDeclarationEditor.js) (0 => 222959)
--- trunk/Source/WebInspectorUI/UserInterface/Views/SpreadsheetStyleProperty.js (rev 0)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/SpreadsheetStyleProperty.js 2017-10-06 05:48:19 UTC (rev 222959)
@@ -0,0 +1,214 @@
+/*
+ * Copyright (C) 2017 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+WI.SpreadsheetStyleProperty = class SpreadsheetStyleProperty extends WI.Object
+{
+ constructor(delegate, property, index, newlyAdded)
+ {
+ super();
+
+ this._delegate = delegate || null;
+ this._property = property;
+ this._newlyAdded = newlyAdded || false;
+ this._element = document.createElement("div");
+
+ this._nameElement = null;
+ this._valueElement = null;
+
+ this._nameTextField = null;
+ this._valueTextField = null;
+
+ this._update();
+ property.addEventListener(WI.CSSProperty.Event.OverriddenStatusChanged, this._update, this);
+ }
+
+ // Public
+
+ get element() { return this._element; }
+ get nameTextField() { return this._nameTextField; }
+ get valueTextField() { return this._valueTextField; }
+
+ // Private
+
+ _remove()
+ {
+ this.element.remove();
+ this._property.remove();
+
+ if (this._delegate && typeof this._delegate.spreadsheetStylePropertyRemoved === "function")
+ this._delegate.spreadsheetStylePropertyRemoved(this);
+ }
+
+ _update()
+ {
+ this.element.removeChildren();
+ this.element.className = "";
+
+ let duplicatePropertyExistsBelow = (cssProperty) => {
+ let propertyFound = false;
+
+ for (let property of this._property.ownerStyle.properties) {
+ if (property === cssProperty)
+ propertyFound = true;
+ else if (property.name === cssProperty.name && propertyFound)
+ return true;
+ }
+
+ return false;
+ };
+
+ let classNames = ["property"];
+
+ if (this._property.overridden)
+ classNames.push("overridden");
+
+ if (this._property.implicit)
+ classNames.push("implicit");
+
+ if (this._property.ownerStyle.inherited && !this._property.inherited)
+ classNames.push("not-inherited");
+
+ if (!this._property.valid && this._property.hasOtherVendorNameOrKeyword())
+ classNames.push("other-vendor");
+ else if (!this._property.valid) {
+ let propertyNameIsValid = false;
+ if (WI.CSSCompletions.cssNameCompletions)
+ propertyNameIsValid = WI.CSSCompletions.cssNameCompletions.isValidPropertyName(this._property.name);
+
+ if (!propertyNameIsValid || duplicatePropertyExistsBelow(this._property))
+ classNames.push("invalid");
+ }
+
+ if (!this._property.enabled)
+ classNames.push("disabled");
+
+ this._element.classList.add(...classNames);
+
+ if (this._property.editable) {
+ this._checkboxElement = this.element.appendChild(document.createElement("input"));
+ this._checkboxElement.classList.add("property-toggle");
+ this._checkboxElement.type = "checkbox";
+ this._checkboxElement.checked = this._property.enabled;
+ this._checkboxElement.tabIndex = -1;
+ this._checkboxElement.addEventListener("change", () => {
+ let disabled = !this._checkboxElement.checked;
+ this._property.commentOut(disabled);
+ this._update();
+ });
+ }
+
+ if (!this._property.enabled)
+ this.element.append("/* ");
+
+ this._nameElement = this.element.appendChild(document.createElement("span"));
+ this._nameElement.classList.add("name");
+ this._nameElement.textContent = this._property.name;
+
+ this.element.append(": ");
+
+ this._valueElement = this.element.appendChild(document.createElement("span"));
+ this._valueElement.classList.add("value");
+ this._valueElement.textContent = this._property.rawValue;
+
+ if (this._property.editable && this._property.enabled) {
+ this._nameElement.tabIndex = 0;
+ this._nameTextField = new WI.SpreadsheetTextField(this, this._nameElement);
+
+ this._valueElement.tabIndex = 0;
+ this._valueTextField = new WI.SpreadsheetTextField(this, this._valueElement);
+ }
+
+ this.element.append(";");
+
+ if (!this._property.enabled)
+ this.element.append(" */");
+ }
+
+ spreadsheetTextFieldDidChange(textField)
+ {
+ if (textField === this._valueTextField)
+ this.debounce(WI.SpreadsheetStyleProperty.CommitCoalesceDelay)._handleValueChange();
+ else if (textField === this._nameTextField)
+ this.debounce(WI.SpreadsheetStyleProperty.CommitCoalesceDelay)._handleNameChange();
+ }
+
+ spreadsheetTextFieldDidCommit(textField, {direction})
+ {
+ let propertyName = this._nameTextField.value.trim();
+ let propertyValue = this._valueTextField.value.trim();
+ let willRemoveProperty = false;
+
+ // Remove a property with an empty name or value. However, a newly added property
+ // has an empty name and value at first. Don't remove it when moving focus from
+ // the name to the value for the first time.
+ if (!propertyName || (!this._newlyAdded && !propertyValue))
+ willRemoveProperty = true;
+
+ let isEditingName = textField === this._nameTextField;
+
+ if (propertyName && isEditingName)
+ this._newlyAdded = false;
+
+ if (direction === "forward") {
+ if (isEditingName && !willRemoveProperty) {
+ // Move focus from the name to the value.
+ this._valueTextField.startEditing();
+ return;
+ }
+ } else {
+ if (!isEditingName) {
+ // Move focus from the value to the name.
+ this._nameTextField.startEditing();
+ return;
+ }
+ }
+
+ if (typeof this._delegate.spreadsheetCSSStyleDeclarationEditorFocusMoved === "function") {
+ // Move focus away from the current property, to the next or previous one, if exists, or to the next or previous rule, if exists.
+ this._delegate.spreadsheetCSSStyleDeclarationEditorFocusMoved({direction, willRemoveProperty, movedFromProperty: this});
+ }
+
+ if (willRemoveProperty)
+ this._remove();
+ }
+
+ spreadsheetTextFieldDidBlur(textField)
+ {
+ if (textField.value.trim() === "")
+ this._remove();
+ }
+
+ _handleNameChange()
+ {
+ this._property.name = this._nameElement.textContent.trim();
+ }
+
+ _handleValueChange()
+ {
+ this._property.rawValue = this._valueElement.textContent.trim();
+ }
+};
+
+WI.SpreadsheetStyleProperty.CommitCoalesceDelay = 250;
Added: trunk/Source/WebInspectorUI/UserInterface/Views/SpreadsheetTextField.js (0 => 222959)
--- trunk/Source/WebInspectorUI/UserInterface/Views/SpreadsheetTextField.js (rev 0)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/SpreadsheetTextField.js 2017-10-06 05:48:19 UTC (rev 222959)
@@ -0,0 +1,146 @@
+/*
+ * Copyright (C) 2017 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+WI.SpreadsheetTextField = class SpreadsheetTextField
+{
+ constructor(delegate, element)
+ {
+ this._delegate = delegate;
+ this._element = element;
+ this._element.classList.add("spreadsheet-text-field");
+
+ this._element.addEventListener("focus", this._handleFocus.bind(this));
+ this._element.addEventListener("blur", this._handleBlur.bind(this));
+ this._element.addEventListener("keydown", this._handleKeyDown.bind(this));
+ this._element.addEventListener("input", this._handleInput.bind(this));
+
+ this._editing = false;
+ this._startEditingValue = "";
+ }
+
+ // Public
+
+ get element() { return this._element; }
+
+ get editing() { return this._editing; }
+
+ get value() { return this._element.textContent; }
+ set value(value) { this._element.textContent = value; }
+
+ startEditing()
+ {
+ if (this._editing)
+ return;
+
+ this._editing = true;
+ this._startEditingValue = this.value;
+
+ this._element.classList.add("editing");
+ this._element.contentEditable = "plaintext-only";
+ this._element.spellcheck = false;
+ this._element.scrollIntoViewIfNeeded(false);
+
+ // Disable syntax highlighting.
+ this._element.textContent = this._element.textContent;
+
+ this._element.focus();
+ this._selectText();
+ }
+
+ stopEditing()
+ {
+ if (!this._editing)
+ return;
+
+ this._editing = false;
+ this._startEditingValue = "";
+ this._element.classList.remove("editing");
+ this._element.contentEditable = false;
+ }
+
+ // Private
+
+ _selectText()
+ {
+ window.getSelection().selectAllChildren(this._element);
+ }
+
+ _discardChange()
+ {
+ if (this._startEditingValue !== this.value) {
+ this.value = this._startEditingValue;
+ this._selectText();
+
+ if (this._delegate && typeof this._delegate.spreadsheetTextFieldDidChange === "function")
+ this._delegate.spreadsheetTextFieldDidChange(this);
+ }
+ }
+
+ _handleFocus(event)
+ {
+ this.startEditing();
+ }
+
+ _handleBlur(event)
+ {
+ if (!this._editing)
+ return;
+
+ this._delegate.spreadsheetTextFieldDidBlur(this);
+ this.stopEditing();
+ }
+
+ _handleKeyDown(event)
+ {
+ if (!this._editing)
+ return;
+
+ if (event.key === "Enter" || event.key === "Tab") {
+ event.stop();
+ this.stopEditing();
+
+ let direction = (event.shiftKey && event.key === "Tab") ? "backward" : "forward";
+
+ if (this._delegate && typeof this._delegate.spreadsheetTextFieldDidCommit === "function")
+ this._delegate.spreadsheetTextFieldDidCommit(this, {direction});
+
+ return;
+ }
+
+ if (event.key === "Escape") {
+ event.stop();
+ this._discardChange();
+ }
+ }
+
+ _handleInput(event)
+ {
+ if (!this._editing)
+ return;
+
+ if (this._delegate && typeof this._delegate.spreadsheetTextFieldDidChange === "function")
+ this._delegate.spreadsheetTextFieldDidChange(this);
+ }
+};