Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(19)

Side by Side Diff: third_party/WebKit/Source/devtools/front_end/elements/ClassesPaneWidget.js

Issue 2646283002: ClassesPaneWidget - Add ability to quickly preview autocompleted CSS classes. (Closed)
Patch Set: Clean up Enter key handling. Created 3 years, 8 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « no previous file | third_party/WebKit/Source/devtools/front_end/sdk/DOMModel.js » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 // Copyright (c) 2015 The Chromium Authors. All rights reserved. 1 // Copyright (c) 2015 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 /** 4 /**
5 * @unrestricted 5 * @unrestricted
6 */ 6 */
7 Elements.ClassesPaneWidget = class extends UI.Widget { 7 Elements.ClassesPaneWidget = class extends UI.Widget {
8 constructor() { 8 constructor() {
9 super(); 9 super();
10 this.element.className = 'styles-element-classes-pane'; 10 this.element.className = 'styles-element-classes-pane';
11 var container = this.element.createChild('div', 'title-container'); 11 var container = this.element.createChild('div', 'title-container');
12 this._input = container.createChild('div', 'new-class-input monospace'); 12 this._input = container.createChild('div', 'new-class-input monospace');
13 this.setDefaultFocusedElement(this._input); 13 this.setDefaultFocusedElement(this._input);
14 this._classesContainer = this.element.createChild('div', 'source-code'); 14 this._classesContainer = this.element.createChild('div', 'source-code');
15 this._classesContainer.classList.add('styles-element-classes-container'); 15 this._classesContainer.classList.add('styles-element-classes-container');
16 this._prompt = new Elements.ClassesPaneWidget.ClassNamePrompt(); 16 this._prompt = new Elements.ClassesPaneWidget.ClassNamePrompt();
17 this._prompt.setAutocompletionTimeout(0); 17 this._prompt.setAutocompletionTimeout(0);
18 this._prompt.renderAsBlock(); 18 this._prompt.renderAsBlock();
19 19
20 var proxyElement = this._prompt.attach(this._input); 20 var proxyElement = this._prompt.attach(this._input);
21 this._prompt.setPlaceholder(Common.UIString('Add new class')); 21 this._prompt.setPlaceholder(Common.UIString('Add new class'));
22 this._prompt.addEventListener(UI.TextPrompt.Events.TextChanged, this._onText Changed, this);
22 proxyElement.addEventListener('keydown', this._onKeyDown.bind(this), false); 23 proxyElement.addEventListener('keydown', this._onKeyDown.bind(this), false);
23 24
24 SDK.targetManager.addModelListener(SDK.DOMModel, SDK.DOMModel.Events.DOMMuta ted, this._onDOMMutated, this); 25 SDK.targetManager.addModelListener(SDK.DOMModel, SDK.DOMModel.Events.DOMMuta ted, this._onDOMMutated, this);
25 /** @type {!Set<!SDK.DOMNode>} */ 26 /** @type {!Set<!SDK.DOMNode>} */
26 this._mutatingNodes = new Set(); 27 this._mutatingNodes = new Set();
27 UI.context.addFlavorChangeListener(SDK.DOMNode, this._update, this); 28 /** @type {!Map<!SDK.DOMNode, string>} */
29 this._pendingNodeClasses = new Map();
30 this._updateNodeThrottler = new Common.Throttler(0);
31 /** @type {?SDK.DOMNode} */
32 this._previousTarget = null;
33 UI.context.addFlavorChangeListener(SDK.DOMNode, this._onSelectedNodeChanged, this);
34 }
35
36 /**
37 * @param {string} text
38 * @return {!Array.<string>}
39 */
40 _splitTextIntoClasses(text) {
41 return text.split(/[.,\s]/)
42 .map(className => className.trim())
43 .filter(className => className.length);
28 } 44 }
29 45
30 /** 46 /**
31 * @param {!Event} event 47 * @param {!Event} event
32 */ 48 */
33 _onKeyDown(event) { 49 _onKeyDown(event) {
50 if (!isEnterKey(event) && !isEscKey(event))
51 return;
52
53 if (isEnterKey(event)) {
54 event.consume(true);
55 if (this._prompt.acceptAutoComplete())
56 return;
57 }
58
34 var text = event.target.textContent; 59 var text = event.target.textContent;
35 if (isEscKey(event)) { 60 if (isEscKey(event)) {
36 event.target.textContent = '';
37 if (!text.isWhitespace()) 61 if (!text.isWhitespace())
38 event.consume(true); 62 event.consume(true);
39 return; 63 text = '';
40 } 64 }
41 65
42 if (!isEnterKey(event)) 66 this._prompt.clearAutocomplete();
43 return; 67 event.target.textContent = '';
44 if (this._prompt.acceptAutoComplete()) { 68
45 event.consume(true);
46 return;
47 }
48 var node = UI.context.flavor(SDK.DOMNode); 69 var node = UI.context.flavor(SDK.DOMNode);
49 if (!node) 70 if (!node)
50 return; 71 return;
51 72
52 this._prompt.clearAutocomplete(); 73 var classNames = this._splitTextIntoClasses(text);
53 event.target.textContent = ''; 74 for (var className of classNames)
54 var classNames = text.split(/[.,\s]/);
55 for (var className of classNames) {
56 var className = className.trim();
57 if (!className.length)
58 continue;
59 this._toggleClass(node, className, true); 75 this._toggleClass(node, className, true);
60 }
61 this._installNodeClasses(node); 76 this._installNodeClasses(node);
62 this._update(); 77 this._update();
63 event.consume(true); 78 }
79
80 _onTextChanged() {
81 var node = UI.context.flavor(SDK.DOMNode);
82 if (!node)
83 return;
84 this._installNodeClasses(node);
64 } 85 }
65 86
66 /** 87 /**
67 * @param {!Common.Event} event 88 * @param {!Common.Event} event
68 */ 89 */
69 _onDOMMutated(event) { 90 _onDOMMutated(event) {
70 var node = /** @type {!SDK.DOMNode} */ (event.data); 91 var node = /** @type {!SDK.DOMNode} */ (event.data);
71 if (this._mutatingNodes.has(node)) 92 if (this._mutatingNodes.has(node))
72 return; 93 return;
73 delete node[Elements.ClassesPaneWidget._classesSymbol]; 94 delete node[Elements.ClassesPaneWidget._classesSymbol];
74 this._update(); 95 this._update();
75 } 96 }
76 97
77 /** 98 /**
99 * @param {!Common.Event} event
100 */
101 _onSelectedNodeChanged(event) {
102 if (this._previousTarget && this._prompt.text()) {
103 this._input.textContent = '';
104 this._installNodeClasses(this._previousTarget);
105 }
106 this._previousTarget = /** @type {?SDK.DOMNode} */ (event.data);
107 this._update();
108 }
109
110 /**
78 * @override 111 * @override
79 */ 112 */
80 wasShown() { 113 wasShown() {
81 this._update(); 114 this._update();
82 } 115 }
83 116
84 _update() { 117 _update() {
85 if (!this.isShowing()) 118 if (!this.isShowing())
86 return; 119 return;
87 120
(...skipping 67 matching lines...) Expand 10 before | Expand all | Expand 10 after
155 * @param {!SDK.DOMNode} node 188 * @param {!SDK.DOMNode} node
156 */ 189 */
157 _installNodeClasses(node) { 190 _installNodeClasses(node) {
158 var classes = this._nodeClasses(node); 191 var classes = this._nodeClasses(node);
159 var activeClasses = new Set(); 192 var activeClasses = new Set();
160 for (var className of classes.keys()) { 193 for (var className of classes.keys()) {
161 if (classes.get(className)) 194 if (classes.get(className))
162 activeClasses.add(className); 195 activeClasses.add(className);
163 } 196 }
164 197
198 var additionalClasses = this._splitTextIntoClasses(this._prompt.textWithCurr entSuggestion());
199 for (className of additionalClasses)
200 activeClasses.add(className);
201
165 var newClasses = activeClasses.valuesArray(); 202 var newClasses = activeClasses.valuesArray();
166 newClasses.sort(); 203 newClasses.sort();
167 this._mutatingNodes.add(node); 204
168 node.setAttributeValue('class', newClasses.join(' '), onClassNameUpdated.bin d(this)); 205 this._pendingNodeClasses.set(node, newClasses.join(' '));
206 this._updateNodeThrottler.schedule(this._flushPendingClasses.bind(this));
207 }
208
209 /**
210 * @return {!Promise}
211 */
212 _flushPendingClasses() {
213 var promises = [];
214 for (var node of this._pendingNodeClasses.keys()) {
215 this._mutatingNodes.add(node);
216 var promise = node.setAttributeValuePromise('class', this._pendingNodeClas ses.get(node)).then(onClassValueUpdated.bind(this, node));
217 promises.push(promise);
218 }
219 this._pendingNodeClasses.clear();
220 return Promise.all(promises);
169 221
170 /** 222 /**
223 * @param {!SDK.DOMNode} node
171 * @this {Elements.ClassesPaneWidget} 224 * @this {Elements.ClassesPaneWidget}
172 */ 225 */
173 function onClassNameUpdated() { 226 function onClassValueUpdated(node) {
174 this._mutatingNodes.delete(node); 227 this._mutatingNodes.delete(node);
175 } 228 }
176 } 229 }
177 }; 230 };
178 231
179 Elements.ClassesPaneWidget._classesSymbol = Symbol('Elements.ClassesPaneWidget._ classesSymbol'); 232 Elements.ClassesPaneWidget._classesSymbol = Symbol('Elements.ClassesPaneWidget._ classesSymbol');
180 233
181 /** 234 /**
182 * @implements {UI.ToolbarItem.Provider} 235 * @implements {UI.ToolbarItem.Provider}
183 * @unrestricted 236 * @unrestricted
(...skipping 74 matching lines...) Expand 10 before | Expand all | Expand 10 after
258 if (!this._classNamesPromise || this._selectedFrameId !== selectedNode.frame Id()) 311 if (!this._classNamesPromise || this._selectedFrameId !== selectedNode.frame Id())
259 this._classNamesPromise = this._getClassNames(selectedNode); 312 this._classNamesPromise = this._getClassNames(selectedNode);
260 313
261 return this._classNamesPromise.then(completions => { 314 return this._classNamesPromise.then(completions => {
262 if (prefix[0] === '.') 315 if (prefix[0] === '.')
263 completions = completions.map(value => '.' + value); 316 completions = completions.map(value => '.' + value);
264 return completions.filter(value => value.startsWith(prefix)).map(completio n => ({text: completion})); 317 return completions.filter(value => value.startsWith(prefix)).map(completio n => ({text: completion}));
265 }); 318 });
266 } 319 }
267 }; 320 };
OLDNEW
« no previous file with comments | « no previous file | third_party/WebKit/Source/devtools/front_end/sdk/DOMModel.js » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698