Web Inspector: Styles Redesign: Display warnings
[WebKit-https.git] / Source / WebInspectorUI / UserInterface / Views / SpreadsheetCSSStyleDeclarationEditor.js
1 /*
2  * Copyright (C) 2017 Apple Inc. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
14  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23  * THE POSSIBILITY OF SUCH DAMAGE.
24  */
25
26 WI.SpreadsheetCSSStyleDeclarationEditor = class SpreadsheetCSSStyleDeclarationEditor extends WI.View
27 {
28     constructor(delegate, style)
29     {
30         super();
31
32         this.element.classList.add(WI.SpreadsheetCSSStyleDeclarationEditor.StyleClassName);
33
34         this._delegate = delegate;
35         this.style = style;
36         this._propertyViews = [];
37         this._propertyPendingStartEditing = null;
38     }
39
40     // Public
41
42     layout()
43     {
44         super.layout();
45
46         this.element.removeChildren();
47
48         let properties = this._propertiesToRender;
49         this.element.classList.toggle("no-properties", !properties.length);
50
51         // FIXME: Only re-layout properties that have been modified and preserve focus whenever possible.
52         this._propertyViews = [];
53
54         let propertyViewPendingStartEditing = null;
55         for (let index = 0; index < properties.length; index++) {
56             let property = properties[index];
57             let propertyView = new WI.SpreadsheetStyleProperty(this, property, index);
58             this.element.append(propertyView.element);
59             this._propertyViews.push(propertyView);
60
61             if (property === this._propertyPendingStartEditing)
62                 propertyViewPendingStartEditing = propertyView;
63         }
64
65         if (propertyViewPendingStartEditing) {
66             propertyViewPendingStartEditing.nameTextField.startEditing();
67             this._propertyPendingStartEditing = null;
68         }
69     }
70
71     detached()
72     {
73         for (let propertyView of this._propertyViews)
74             propertyView.detached();
75     }
76
77     get style()
78     {
79         return this._style;
80     }
81
82     set style(style)
83     {
84         if (this._style === style)
85             return;
86
87         if (this._style)
88             this._style.removeEventListener(WI.CSSStyleDeclaration.Event.PropertiesChanged, this._propertiesChanged, this);
89
90         this._style = style || null;
91
92         if (this._style)
93             this._style.addEventListener(WI.CSSStyleDeclaration.Event.PropertiesChanged, this._propertiesChanged, this);
94
95         this.needsLayout();
96     }
97
98     startEditingFirstProperty()
99     {
100         if (this._propertyViews.length)
101             this._propertyViews[0].nameTextField.startEditing();
102         else {
103             let index = 0;
104             this.addBlankProperty(index);
105         }
106     }
107
108     startEditingLastProperty()
109     {
110         let lastProperty = this._propertyViews.lastValue;
111         if (lastProperty)
112             lastProperty.valueTextField.startEditing();
113         else {
114             let index = 0;
115             this.addBlankProperty(index);
116         }
117     }
118
119     highlightProperty(property)
120     {
121         let propertiesMatch = (cssProperty) => {
122             if (cssProperty.attached && !cssProperty.overridden) {
123                 if (cssProperty.canonicalName === property.canonicalName || hasMatchingLonghandProperty(cssProperty))
124                     return true;
125             }
126
127             return false;
128         };
129
130         let hasMatchingLonghandProperty = (cssProperty) => {
131             let cssProperties = cssProperty.relatedLonghandProperties;
132
133             if (!cssProperties.length)
134                 return false;
135
136             for (let property of cssProperties) {
137                 if (propertiesMatch(property))
138                     return true;
139             }
140
141             return false;
142         };
143
144         for (let cssProperty of this.style.properties) {
145             if (propertiesMatch(cssProperty)) {
146                 let propertyView = cssProperty.__propertyView;
147                 if (propertyView) {
148                     propertyView.highlight();
149
150                     if (cssProperty.editable)
151                         propertyView.valueTextField.startEditing();
152                 }
153                 return true;
154             }
155         }
156
157         return false;
158     }
159
160     isFocused()
161     {
162         let focusedElement = document.activeElement;
163
164         if (!focusedElement || focusedElement.tagName === "BODY")
165             return false;
166
167         return focusedElement.isSelfOrDescendant(this.element);
168     }
169
170     addBlankProperty(index)
171     {
172         if (index === -1) {
173             // Append to the end.
174             index = this._propertyViews.length;
175         }
176
177         this._propertyPendingStartEditing = this._style.newBlankProperty(index);
178         this.needsLayout();
179     }
180
181     spreadsheetCSSStyleDeclarationEditorFocusMoved({direction, movedFromProperty, willRemoveProperty})
182     {
183         let movedFromIndex = this._propertyViews.indexOf(movedFromProperty);
184         console.assert(movedFromIndex !== -1, "Property doesn't exist, focusing on a selector as a fallback.");
185         if (movedFromIndex === -1) {
186             if (this._style.selectorEditable)
187                 this._delegate.cssStyleDeclarationTextEditorStartEditingRuleSelector();
188
189             return;
190         }
191
192         if (direction === "forward") {
193             // Move from the value to the next property's name.
194             let index = movedFromIndex + 1;
195             if (index < this._propertyViews.length)
196                 this._propertyViews[index].nameTextField.startEditing();
197             else {
198                 if (willRemoveProperty) {
199                     // Move from the last value in the rule to the next rule's selector.
200                     let reverse = false;
201                     this._delegate.cssStyleDeclarationEditorStartEditingAdjacentRule(reverse);
202                 } else
203                     this.addBlankProperty(index);
204             }
205         } else {
206             let index = movedFromIndex - 1;
207             if (index < 0) {
208                 // Move from the first property's name to the rule's selector.
209                 if (this._style.selectorEditable)
210                     this._delegate.cssStyleDeclarationTextEditorStartEditingRuleSelector();
211             } else {
212                 // Move from the property's name to the previous property's value.
213                 let valueTextField = this._propertyViews[index].valueTextField;
214                 if (valueTextField)
215                     valueTextField.startEditing();
216             }
217         }
218     }
219
220     spreadsheetStylePropertyRemoved(propertyView)
221     {
222         this._propertyViews.remove(propertyView);
223     }
224
225     // Private
226
227     get _propertiesToRender()
228     {
229         if (this._style._styleSheetTextRange)
230             return this._style.allVisibleProperties;
231
232         return this._style.allProperties;
233     }
234
235     _propertiesChanged(event)
236     {
237         if (this.isFocused()) {
238             for (let propertyView of this._propertyViews)
239                 propertyView.updateStatus();
240         } else
241             this.needsLayout();
242     }
243 };
244
245 WI.SpreadsheetCSSStyleDeclarationEditor.StyleClassName = "spreadsheet-style-declaration-editor";