Title: [222959] trunk
Revision
222959
Author
nvasil...@apple.com
Date
2017-10-05 22:48:19 -0700 (Thu, 05 Oct 2017)

Log Message

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.

Source/WebInspectorUI:

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.

LayoutTests:

Add tests for WI.CSSProperty.prototype.remove.

* inspector/css/css-property-expected.txt:
* inspector/css/css-property.html:

Modified Paths

Added Paths

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);
+    }
+};
_______________________________________________
webkit-changes mailing list
webkit-changes@lists.webkit.org
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to