blob: 81dd1db86ab2343faac8d796eec052d887c5c5e2 [file] [log] [blame]
Blink Reformat4c46d092018-04-07 15:32:371// 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
3// found in the LICENSE file.
Tim van der Lippe97611c92020-02-12 16:56:584
Paul Lewis6d4ebb22020-08-04 12:02:555// @ts-nocheck
6// TODO(crbug.com/1011811): Enable TypeScript compiler checks.
7
Tim van der Lippe97611c92020-02-12 16:56:588import * as Common from '../common/common.js';
Simon Zündda7058f2020-02-28 13:57:289import * as Platform from '../platform/platform.js';
Tim van der Lippe97611c92020-02-12 16:56:5810import * as SDK from '../sdk/sdk.js';
11import * as UI from '../ui/ui.js';
12
Tim van der Lippeaabc8302019-12-10 15:34:4513import {ElementsPanel} from './ElementsPanel.js';
14
Blink Reformat4c46d092018-04-07 15:32:3715/**
16 * @unrestricted
17 */
Tim van der Lippe97611c92020-02-12 16:56:5818export class ClassesPaneWidget extends UI.Widget.Widget {
Blink Reformat4c46d092018-04-07 15:32:3719 constructor() {
20 super(true);
21 this.registerRequiredCSS('elements/classesPaneWidget.css');
22 this.contentElement.className = 'styles-element-classes-pane';
23 const container = this.contentElement.createChild('div', 'title-container');
24 this._input = container.createChild('div', 'new-class-input monospace');
25 this.setDefaultFocusedElement(this._input);
26 this._classesContainer = this.contentElement.createChild('div', 'source-code');
27 this._classesContainer.classList.add('styles-element-classes-container');
Tim van der Lippe13f71fb2019-11-29 11:17:3928 this._prompt = new ClassNamePrompt(this._nodeClasses.bind(this));
Blink Reformat4c46d092018-04-07 15:32:3729 this._prompt.setAutocompletionTimeout(0);
30 this._prompt.renderAsBlock();
31
32 const proxyElement = this._prompt.attach(this._input);
Tim van der Lippe97611c92020-02-12 16:56:5833 this._prompt.setPlaceholder(Common.UIString.UIString('Add new class'));
Blink Reformat4c46d092018-04-07 15:32:3734 this._prompt.addEventListener(UI.TextPrompt.Events.TextChanged, this._onTextChanged, this);
35 proxyElement.addEventListener('keydown', this._onKeyDown.bind(this), false);
36
Paul Lewisdaac1062020-03-05 14:37:1037 SDK.SDKModel.TargetManager.instance().addModelListener(
Tim van der Lippe97611c92020-02-12 16:56:5838 SDK.DOMModel.DOMModel, SDK.DOMModel.Events.DOMMutated, this._onDOMMutated, this);
39 /** @type {!Set<!SDK.DOMModel.DOMNode>} */
Blink Reformat4c46d092018-04-07 15:32:3740 this._mutatingNodes = new Set();
Tim van der Lippe97611c92020-02-12 16:56:5841 /** @type {!Map<!SDK.DOMModel.DOMNode, string>} */
Blink Reformat4c46d092018-04-07 15:32:3742 this._pendingNodeClasses = new Map();
Tim van der Lippe97611c92020-02-12 16:56:5843 this._updateNodeThrottler = new Common.Throttler.Throttler(0);
44 /** @type {?SDK.DOMModel.DOMNode} */
Blink Reformat4c46d092018-04-07 15:32:3745 this._previousTarget = null;
Tim van der Lipped1a00aa2020-08-19 16:03:5646 UI.Context.Context.instance().addFlavorChangeListener(SDK.DOMModel.DOMNode, this._onSelectedNodeChanged, this);
Blink Reformat4c46d092018-04-07 15:32:3747 }
48
49 /**
50 * @param {string} text
51 * @return {!Array.<string>}
52 */
53 _splitTextIntoClasses(text) {
Mathias Bynens3abc0952020-04-20 14:15:5254 return text.split(/[,\s]/).map(className => className.trim()).filter(className => className.length);
Blink Reformat4c46d092018-04-07 15:32:3755 }
56
57 /**
58 * @param {!Event} event
59 */
60 _onKeyDown(event) {
Tim van der Lippe1d6e57a2019-09-30 11:55:3461 if (!isEnterKey(event) && !isEscKey(event)) {
Blink Reformat4c46d092018-04-07 15:32:3762 return;
Tim van der Lippe1d6e57a2019-09-30 11:55:3463 }
Blink Reformat4c46d092018-04-07 15:32:3764
65 if (isEnterKey(event)) {
66 event.consume();
Tim van der Lippe1d6e57a2019-09-30 11:55:3467 if (this._prompt.acceptAutoComplete()) {
Blink Reformat4c46d092018-04-07 15:32:3768 return;
Tim van der Lippe1d6e57a2019-09-30 11:55:3469 }
Blink Reformat4c46d092018-04-07 15:32:3770 }
71
72 let text = event.target.textContent;
73 if (isEscKey(event)) {
Simon Zündda7058f2020-02-28 13:57:2874 if (!Platform.StringUtilities.isWhitespace(text)) {
Blink Reformat4c46d092018-04-07 15:32:3775 event.consume(true);
Tim van der Lippe1d6e57a2019-09-30 11:55:3476 }
Blink Reformat4c46d092018-04-07 15:32:3777 text = '';
78 }
79
80 this._prompt.clearAutocomplete();
81 event.target.textContent = '';
82
Tim van der Lipped1a00aa2020-08-19 16:03:5683 const node = UI.Context.Context.instance().flavor(SDK.DOMModel.DOMNode);
Tim van der Lippe1d6e57a2019-09-30 11:55:3484 if (!node) {
Blink Reformat4c46d092018-04-07 15:32:3785 return;
Tim van der Lippe1d6e57a2019-09-30 11:55:3486 }
Blink Reformat4c46d092018-04-07 15:32:3787
88 const classNames = this._splitTextIntoClasses(text);
Michael Liaocafccfd2020-04-02 17:11:5989 if (!classNames.length) {
Patrick Brosset78fa1f52020-08-17 14:33:4890 this._installNodeClasses(node);
Michael Liaocafccfd2020-04-02 17:11:5991 return;
92 }
93
Tim van der Lippe1d6e57a2019-09-30 11:55:3494 for (const className of classNames) {
Blink Reformat4c46d092018-04-07 15:32:3795 this._toggleClass(node, className, true);
Tim van der Lippe1d6e57a2019-09-30 11:55:3496 }
Michael Liaocafccfd2020-04-02 17:11:5997
98 // annoucementString is used for screen reader to announce that the class(es) has been added successfully.
99 const joinClassString = classNames.join(' ');
100 const announcementString =
101 classNames.length > 1 ? ls`Classes ${joinClassString} added.` : ls`Class ${joinClassString} added.`;
102 UI.ARIAUtils.alert(announcementString, this.contentElement);
103
Blink Reformat4c46d092018-04-07 15:32:37104 this._installNodeClasses(node);
105 this._update();
106 }
107
108 _onTextChanged() {
Tim van der Lipped1a00aa2020-08-19 16:03:56109 const node = UI.Context.Context.instance().flavor(SDK.DOMModel.DOMNode);
Tim van der Lippe1d6e57a2019-09-30 11:55:34110 if (!node) {
Blink Reformat4c46d092018-04-07 15:32:37111 return;
Tim van der Lippe1d6e57a2019-09-30 11:55:34112 }
Blink Reformat4c46d092018-04-07 15:32:37113 this._installNodeClasses(node);
114 }
115
116 /**
Tim van der Lippec02a97c2020-02-14 14:39:27117 * @param {!Common.EventTarget.EventTargetEvent} event
Blink Reformat4c46d092018-04-07 15:32:37118 */
119 _onDOMMutated(event) {
Tim van der Lippe97611c92020-02-12 16:56:58120 const node = /** @type {!SDK.DOMModel.DOMNode} */ (event.data);
Tim van der Lippe1d6e57a2019-09-30 11:55:34121 if (this._mutatingNodes.has(node)) {
Blink Reformat4c46d092018-04-07 15:32:37122 return;
Tim van der Lippe1d6e57a2019-09-30 11:55:34123 }
Tim van der Lippe13f71fb2019-11-29 11:17:39124 delete node[ClassesPaneWidget._classesSymbol];
Blink Reformat4c46d092018-04-07 15:32:37125 this._update();
126 }
127
128 /**
Tim van der Lippec02a97c2020-02-14 14:39:27129 * @param {!Common.EventTarget.EventTargetEvent} event
Blink Reformat4c46d092018-04-07 15:32:37130 */
131 _onSelectedNodeChanged(event) {
132 if (this._previousTarget && this._prompt.text()) {
133 this._input.textContent = '';
134 this._installNodeClasses(this._previousTarget);
135 }
Tim van der Lippe97611c92020-02-12 16:56:58136 this._previousTarget = /** @type {?SDK.DOMModel.DOMNode} */ (event.data);
Blink Reformat4c46d092018-04-07 15:32:37137 this._update();
138 }
139
140 /**
141 * @override
142 */
143 wasShown() {
144 this._update();
145 }
146
147 _update() {
Tim van der Lippe1d6e57a2019-09-30 11:55:34148 if (!this.isShowing()) {
Blink Reformat4c46d092018-04-07 15:32:37149 return;
Tim van der Lippe1d6e57a2019-09-30 11:55:34150 }
Blink Reformat4c46d092018-04-07 15:32:37151
Tim van der Lipped1a00aa2020-08-19 16:03:56152 let node = UI.Context.Context.instance().flavor(SDK.DOMModel.DOMNode);
Tim van der Lippe1d6e57a2019-09-30 11:55:34153 if (node) {
Blink Reformat4c46d092018-04-07 15:32:37154 node = node.enclosingElementOrSelf();
Tim van der Lippe1d6e57a2019-09-30 11:55:34155 }
Blink Reformat4c46d092018-04-07 15:32:37156
157 this._classesContainer.removeChildren();
158 this._input.disabled = !node;
159
Tim van der Lippe1d6e57a2019-09-30 11:55:34160 if (!node) {
Blink Reformat4c46d092018-04-07 15:32:37161 return;
Tim van der Lippe1d6e57a2019-09-30 11:55:34162 }
Blink Reformat4c46d092018-04-07 15:32:37163
164 const classes = this._nodeClasses(node);
Simon Zündf27be3d2020-02-11 14:46:27165 const keys = [...classes.keys()];
Blink Reformat4c46d092018-04-07 15:32:37166 keys.sort(String.caseInsensetiveComparator);
167 for (let i = 0; i < keys.length; ++i) {
168 const className = keys[i];
Tim van der Lippe97611c92020-02-12 16:56:58169 const label = UI.UIUtils.CheckboxLabel.create(className, classes.get(className));
Blink Reformat4c46d092018-04-07 15:32:37170 label.classList.add('monospace');
171 label.checkboxElement.addEventListener('click', this._onClick.bind(this, className), false);
172 this._classesContainer.appendChild(label);
173 }
174 }
175
176 /**
177 * @param {string} className
178 * @param {!Event} event
179 */
180 _onClick(className, event) {
Tim van der Lipped1a00aa2020-08-19 16:03:56181 const node = UI.Context.Context.instance().flavor(SDK.DOMModel.DOMNode);
Tim van der Lippe1d6e57a2019-09-30 11:55:34182 if (!node) {
Blink Reformat4c46d092018-04-07 15:32:37183 return;
Tim van der Lippe1d6e57a2019-09-30 11:55:34184 }
Blink Reformat4c46d092018-04-07 15:32:37185 const enabled = event.target.checked;
186 this._toggleClass(node, className, enabled);
187 this._installNodeClasses(node);
188 }
189
190 /**
Tim van der Lippe97611c92020-02-12 16:56:58191 * @param {!SDK.DOMModel.DOMNode} node
Blink Reformat4c46d092018-04-07 15:32:37192 * @return {!Map<string, boolean>}
193 */
194 _nodeClasses(node) {
Tim van der Lippe13f71fb2019-11-29 11:17:39195 let result = node[ClassesPaneWidget._classesSymbol];
Blink Reformat4c46d092018-04-07 15:32:37196 if (!result) {
197 const classAttribute = node.getAttribute('class') || '';
198 const classes = classAttribute.split(/\s/);
199 result = new Map();
200 for (let i = 0; i < classes.length; ++i) {
201 const className = classes[i].trim();
Tim van der Lippe1d6e57a2019-09-30 11:55:34202 if (!className.length) {
Blink Reformat4c46d092018-04-07 15:32:37203 continue;
Tim van der Lippe1d6e57a2019-09-30 11:55:34204 }
Blink Reformat4c46d092018-04-07 15:32:37205 result.set(className, true);
206 }
Tim van der Lippe13f71fb2019-11-29 11:17:39207 node[ClassesPaneWidget._classesSymbol] = result;
Blink Reformat4c46d092018-04-07 15:32:37208 }
209 return result;
210 }
211
212 /**
Tim van der Lippe97611c92020-02-12 16:56:58213 * @param {!SDK.DOMModel.DOMNode} node
Blink Reformat4c46d092018-04-07 15:32:37214 * @param {string} className
215 * @param {boolean} enabled
216 */
217 _toggleClass(node, className, enabled) {
218 const classes = this._nodeClasses(node);
219 classes.set(className, enabled);
220 }
221
222 /**
Tim van der Lippe97611c92020-02-12 16:56:58223 * @param {!SDK.DOMModel.DOMNode} node
Blink Reformat4c46d092018-04-07 15:32:37224 */
225 _installNodeClasses(node) {
226 const classes = this._nodeClasses(node);
227 const activeClasses = new Set();
228 for (const className of classes.keys()) {
Tim van der Lippe1d6e57a2019-09-30 11:55:34229 if (classes.get(className)) {
Blink Reformat4c46d092018-04-07 15:32:37230 activeClasses.add(className);
Tim van der Lippe1d6e57a2019-09-30 11:55:34231 }
Blink Reformat4c46d092018-04-07 15:32:37232 }
233
234 const additionalClasses = this._splitTextIntoClasses(this._prompt.textWithCurrentSuggestion());
Tim van der Lippe1d6e57a2019-09-30 11:55:34235 for (const className of additionalClasses) {
Blink Reformat4c46d092018-04-07 15:32:37236 activeClasses.add(className);
Tim van der Lippe1d6e57a2019-09-30 11:55:34237 }
Blink Reformat4c46d092018-04-07 15:32:37238
Simon Zünda0d40622020-02-12 13:16:42239 const newClasses = [...activeClasses.values()].sort();
Blink Reformat4c46d092018-04-07 15:32:37240
241 this._pendingNodeClasses.set(node, newClasses.join(' '));
242 this._updateNodeThrottler.schedule(this._flushPendingClasses.bind(this));
243 }
244
245 /**
246 * @return {!Promise}
247 */
248 _flushPendingClasses() {
249 const promises = [];
250 for (const node of this._pendingNodeClasses.keys()) {
251 this._mutatingNodes.add(node);
252 const promise = node.setAttributeValuePromise('class', this._pendingNodeClasses.get(node))
253 .then(onClassValueUpdated.bind(this, node));
254 promises.push(promise);
255 }
256 this._pendingNodeClasses.clear();
257 return Promise.all(promises);
258
259 /**
Tim van der Lippe97611c92020-02-12 16:56:58260 * @param {!SDK.DOMModel.DOMNode} node
Tim van der Lippe13f71fb2019-11-29 11:17:39261 * @this {ClassesPaneWidget}
Blink Reformat4c46d092018-04-07 15:32:37262 */
263 function onClassValueUpdated(node) {
264 this._mutatingNodes.delete(node);
265 }
266 }
Tim van der Lippe13f71fb2019-11-29 11:17:39267}
Blink Reformat4c46d092018-04-07 15:32:37268
Tim van der Lippe13f71fb2019-11-29 11:17:39269ClassesPaneWidget._classesSymbol = Symbol('ClassesPaneWidget._classesSymbol');
Blink Reformat4c46d092018-04-07 15:32:37270
271/**
Tim van der Lippe97611c92020-02-12 16:56:58272 * @implements {UI.Toolbar.Provider}
Blink Reformat4c46d092018-04-07 15:32:37273 * @unrestricted
274 */
Tim van der Lippe13f71fb2019-11-29 11:17:39275export class ButtonProvider {
Blink Reformat4c46d092018-04-07 15:32:37276 constructor() {
Tim van der Lippe97611c92020-02-12 16:56:58277 this._button = new UI.Toolbar.ToolbarToggle(Common.UIString.UIString('Element Classes'), '');
Blink Reformat4c46d092018-04-07 15:32:37278 this._button.setText('.cls');
279 this._button.element.classList.add('monospace');
Tim van der Lippe97611c92020-02-12 16:56:58280 this._button.addEventListener(UI.Toolbar.ToolbarButton.Events.Click, this._clicked, this);
Tim van der Lippe13f71fb2019-11-29 11:17:39281 this._view = new ClassesPaneWidget();
Blink Reformat4c46d092018-04-07 15:32:37282 }
283
284 _clicked() {
Tim van der Lippeaabc8302019-12-10 15:34:45285 ElementsPanel.instance().showToolbarPane(!this._view.isShowing() ? this._view : null, this._button);
Blink Reformat4c46d092018-04-07 15:32:37286 }
287
288 /**
289 * @override
Tim van der Lippe97611c92020-02-12 16:56:58290 * @return {!UI.Toolbar.ToolbarItem}
Blink Reformat4c46d092018-04-07 15:32:37291 */
292 item() {
293 return this._button;
294 }
Tim van der Lippe13f71fb2019-11-29 11:17:39295}
Blink Reformat4c46d092018-04-07 15:32:37296
297/**
298 * @unrestricted
299 */
Tim van der Lippe97611c92020-02-12 16:56:58300export class ClassNamePrompt extends UI.TextPrompt.TextPrompt {
Blink Reformat4c46d092018-04-07 15:32:37301 /**
Tim van der Lippe97611c92020-02-12 16:56:58302 * @param {function(!SDK.DOMModel.DOMNode):!Map<string, boolean>} nodeClasses
Blink Reformat4c46d092018-04-07 15:32:37303 */
304 constructor(nodeClasses) {
305 super();
306 this._nodeClasses = nodeClasses;
307 this.initialize(this._buildClassNameCompletions.bind(this), ' ');
308 this.disableDefaultSuggestionForEmptyInput();
309 this._selectedFrameId = '';
310 this._classNamesPromise = null;
311 }
312
313 /**
Tim van der Lippe97611c92020-02-12 16:56:58314 * @param {!SDK.DOMModel.DOMNode} selectedNode
Blink Reformat4c46d092018-04-07 15:32:37315 * @return {!Promise.<!Array.<string>>}
316 */
Mathias Bynens3abc0952020-04-20 14:15:52317 async _getClassNames(selectedNode) {
Blink Reformat4c46d092018-04-07 15:32:37318 const promises = [];
319 const completions = new Set();
320 this._selectedFrameId = selectedNode.frameId();
321
322 const cssModel = selectedNode.domModel().cssModel();
323 const allStyleSheets = cssModel.allStyleSheets();
324 for (const stylesheet of allStyleSheets) {
Tim van der Lippe1d6e57a2019-09-30 11:55:34325 if (stylesheet.frameId !== this._selectedFrameId) {
Blink Reformat4c46d092018-04-07 15:32:37326 continue;
Tim van der Lippe1d6e57a2019-09-30 11:55:34327 }
Mathias Bynens3abc0952020-04-20 14:15:52328 const cssPromise = cssModel.classNamesPromise(stylesheet.id).then(classes => {
329 for (const className of classes) {
330 completions.add(className);
331 }
332 });
Blink Reformat4c46d092018-04-07 15:32:37333 promises.push(cssPromise);
334 }
335
Mathias Bynens3abc0952020-04-20 14:15:52336 const domPromise = selectedNode.domModel().classNamesPromise(selectedNode.ownerDocument.id).then(classes => {
337 for (const className of classes) {
338 completions.add(className);
339 }
340 });
Blink Reformat4c46d092018-04-07 15:32:37341 promises.push(domPromise);
Mathias Bynens3abc0952020-04-20 14:15:52342 await Promise.all(promises);
343 return [...completions];
Blink Reformat4c46d092018-04-07 15:32:37344 }
345
346 /**
347 * @param {string} expression
348 * @param {string} prefix
349 * @param {boolean=} force
350 * @return {!Promise<!UI.SuggestBox.Suggestions>}
351 */
352 _buildClassNameCompletions(expression, prefix, force) {
Tim van der Lippe1d6e57a2019-09-30 11:55:34353 if (!prefix || force) {
Blink Reformat4c46d092018-04-07 15:32:37354 this._classNamesPromise = null;
Tim van der Lippe1d6e57a2019-09-30 11:55:34355 }
Blink Reformat4c46d092018-04-07 15:32:37356
Tim van der Lipped1a00aa2020-08-19 16:03:56357 const selectedNode = UI.Context.Context.instance().flavor(SDK.DOMModel.DOMNode);
Tim van der Lippe1d6e57a2019-09-30 11:55:34358 if (!selectedNode || (!prefix && !force && !expression.trim())) {
Blink Reformat4c46d092018-04-07 15:32:37359 return Promise.resolve([]);
Tim van der Lippe1d6e57a2019-09-30 11:55:34360 }
Blink Reformat4c46d092018-04-07 15:32:37361
Tim van der Lippe1d6e57a2019-09-30 11:55:34362 if (!this._classNamesPromise || this._selectedFrameId !== selectedNode.frameId()) {
Blink Reformat4c46d092018-04-07 15:32:37363 this._classNamesPromise = this._getClassNames(selectedNode);
Tim van der Lippe1d6e57a2019-09-30 11:55:34364 }
Blink Reformat4c46d092018-04-07 15:32:37365
366 return this._classNamesPromise.then(completions => {
Tim van der Lippe97611c92020-02-12 16:56:58367 const classesMap = this._nodeClasses(/** @type {!SDK.DOMModel.DOMNode} */ (selectedNode));
Blink Reformat4c46d092018-04-07 15:32:37368 completions = completions.filter(value => !classesMap.get(value));
369
Tim van der Lippe1d6e57a2019-09-30 11:55:34370 if (prefix[0] === '.') {
Blink Reformat4c46d092018-04-07 15:32:37371 completions = completions.map(value => '.' + value);
Tim van der Lippe1d6e57a2019-09-30 11:55:34372 }
Blink Reformat4c46d092018-04-07 15:32:37373 return completions.filter(value => value.startsWith(prefix)).sort().map(completion => ({text: completion}));
374 });
375 }
Tim van der Lippe13f71fb2019-11-29 11:17:39376}