Changeset 223283 in webkit
- Timestamp:
- Oct 13, 2017 10:14:22 AM (6 years ago)
- Location:
- trunk/Source/WebInspectorUI
- Files:
-
- 6 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/Source/WebInspectorUI/ChangeLog
r223268 r223283 1 2017-10-13 Nikita Vasilyev <nvasilyev@apple.com> 2 3 Web Inspector: Styles Redesign: hook up autocompletion to property names and values 4 https://bugs.webkit.org/show_bug.cgi?id=177313 5 <rdar://problem/34577057> 6 7 Reviewed by Joseph Pecoraro. 8 9 - Arrow Right accept the current completion item and places the text caret after it. 10 - Arrow Left hides the completion popover. 11 - Arrow Up selects the previous completion item. 12 - Arrow Down selects the next completion item. 13 - Enter and Tab accept the current completion item and navigate to the next focusable item. 14 - Escape hides the completion popover, if there is one. 15 16 * UserInterface/Views/CompletionSuggestionsView.js: 17 (WI.CompletionSuggestionsView): 18 (WI.CompletionSuggestionsView.prototype._mouseDown): 19 Add a preventBlur option so clicking on an completion item doesn't change the focus and 20 doesn't cause "blur" event on the target text field. 21 22 * UserInterface/Views/SpreadsheetCSSStyleDeclarationEditor.css: 23 (.spreadsheet-style-declaration-editor .completion-hint): 24 * UserInterface/Views/SpreadsheetCSSStyleDeclarationEditor.js: 25 (WI.SpreadsheetCSSStyleDeclarationEditor): 26 (WI.SpreadsheetCSSStyleDeclarationEditor.prototype.layout): 27 (WI.SpreadsheetCSSStyleDeclarationEditor.prototype.detached): 28 Call detached on every SpreadsheetTextField to hide CompletionSuggestionsView once 29 SpreadsheetCSSStyleDeclarationEditor is removed from the DOM. 30 31 (WI.SpreadsheetCSSStyleDeclarationEditor.prototype._addBlankProperty): 32 Remove index argument since it is no longer used. 33 34 * UserInterface/Views/SpreadsheetStyleProperty.js: 35 (WI.SpreadsheetStyleProperty): 36 (WI.SpreadsheetStyleProperty.prototype.detached): 37 (WI.SpreadsheetStyleProperty.prototype._remove): 38 (WI.SpreadsheetStyleProperty.prototype._update): 39 (WI.SpreadsheetStyleProperty.prototype._nameCompletionDataProvider): 40 (WI.SpreadsheetStyleProperty.prototype._valueCompletionDataProvider): 41 Add an extra parameter to SpreadsheetTextField to pass a completion data provider. 42 43 * UserInterface/Views/SpreadsheetTextField.js: 44 (WI.SpreadsheetTextField): 45 (WI.SpreadsheetTextField.prototype.get suggestionHint): 46 (WI.SpreadsheetTextField.prototype.set suggestionHint): 47 (WI.SpreadsheetTextField.prototype.startEditing): 48 (WI.SpreadsheetTextField.prototype.stopEditing): 49 (WI.SpreadsheetTextField.prototype.detached): 50 (WI.SpreadsheetTextField.prototype.completionSuggestionsSelectedCompletion): 51 (WI.SpreadsheetTextField.prototype.completionSuggestionsClickedCompletion): 52 (WI.SpreadsheetTextField.prototype._getPrefix): 53 (WI.SpreadsheetTextField.prototype._handleBlur): 54 (WI.SpreadsheetTextField.prototype._handleKeyDown): 55 (WI.SpreadsheetTextField.prototype._handleKeyDownForSuggestionView): 56 (WI.SpreadsheetTextField.prototype._handleInput): 57 (WI.SpreadsheetTextField.prototype._updateCompletions): 58 (WI.SpreadsheetTextField.prototype._getCaretRect): 59 (WI.SpreadsheetTextField.prototype._getCompletionPrefix): 60 (WI.SpreadsheetTextField.prototype._applyCompletionHint): 61 (WI.SpreadsheetTextField.prototype._hideCompletions): 62 Provide text completion based on the existing CompletionSuggestionsView when completionProvider is passed to SpreadsheetTextField. 63 1 64 2017-10-12 Joseph Pecoraro <pecoraro@apple.com> 2 65 -
trunk/Source/WebInspectorUI/UserInterface/Views/CompletionSuggestionsView.js
r220119 r223283 26 26 WI.CompletionSuggestionsView = class CompletionSuggestionsView extends WI.Object 27 27 { 28 constructor(delegate )28 constructor(delegate, {preventBlur} = {}) 29 29 { 30 30 super(); 31 31 32 32 this._delegate = delegate || null; 33 this._preventBlur = preventBlur || false; 33 34 34 35 this._selectedIndex = NaN; … … 198 199 if (event.button !== 0) 199 200 return; 201 202 if (this._preventBlur) 203 event.preventDefault(); 204 200 205 this._mouseIsDown = true; 201 206 } -
trunk/Source/WebInspectorUI/UserInterface/Views/SpreadsheetCSSStyleDeclarationEditor.css
r222959 r223283 45 45 } 46 46 47 .spreadsheet-style-declaration-editor .value.editing { 48 display: inline-block; 49 margin-right: 3px; 50 } 51 47 52 .spreadsheet-style-declaration-editor.no-properties { 48 53 display: none; … … 80 85 opacity: 0.5; 81 86 } 87 88 .spreadsheet-style-declaration-editor .completion-hint { 89 color: hsl(0, 0%, 50%) !important; 90 } -
trunk/Source/WebInspectorUI/UserInterface/Views/SpreadsheetCSSStyleDeclarationEditor.js
r222959 r223283 34 34 this._delegate = delegate; 35 35 this.style = style; 36 this._propertyViews = []; 36 37 } 37 38 … … 50 51 for (let index = 0; index < properties.length; index++) { 51 52 let property = properties[index]; 52 let propertyView = new WI.SpreadsheetStyleProperty(this, property , index);53 let propertyView = new WI.SpreadsheetStyleProperty(this, property); 53 54 this.element.append(propertyView.element); 54 55 this._propertyViews.push(propertyView); 55 56 } 57 } 58 59 detached() 60 { 61 for (let propertyView of this._propertyViews) 62 propertyView.detached(); 56 63 } 57 64 … … 156 163 let blankProperty = this._style.newBlankProperty(afterIndex); 157 164 const newlyAdded = true; 158 let propertyView = new WI.SpreadsheetStyleProperty(this, blankProperty, blankProperty.index,newlyAdded);165 let propertyView = new WI.SpreadsheetStyleProperty(this, blankProperty, newlyAdded); 159 166 this.element.append(propertyView.element); 160 167 this._propertyViews.push(propertyView); -
trunk/Source/WebInspectorUI/UserInterface/Views/SpreadsheetStyleProperty.js
r222959 r223283 26 26 WI.SpreadsheetStyleProperty = class SpreadsheetStyleProperty extends WI.Object 27 27 { 28 constructor(delegate, property, index, newlyAdded)28 constructor(delegate, property, newlyAdded = false) 29 29 { 30 30 super(); 31 32 console.assert(property instanceof WI.CSSProperty); 31 33 32 34 this._delegate = delegate || null; 33 35 this._property = property; 34 this._newlyAdded = newlyAdded || false;36 this._newlyAdded = newlyAdded; 35 37 this._element = document.createElement("div"); 36 38 … … 51 53 get valueTextField() { return this._valueTextField; } 52 54 55 detached() 56 { 57 if (this._nameTextField) 58 this._nameTextField.detached(); 59 60 if (this._valueTextField) 61 this._valueTextField.detached(); 62 } 63 53 64 // Private 54 65 … … 57 68 this.element.remove(); 58 69 this._property.remove(); 70 this.detached(); 59 71 60 72 if (this._delegate && typeof this._delegate.spreadsheetStylePropertyRemoved === "function") … … 135 147 if (this._property.editable && this._property.enabled) { 136 148 this._nameElement.tabIndex = 0; 137 this._nameTextField = new WI.SpreadsheetTextField(this, this._nameElement );149 this._nameTextField = new WI.SpreadsheetTextField(this, this._nameElement, this._nameCompletionDataProvider.bind(this)); 138 150 139 151 this._valueElement.tabIndex = 0; 140 this._valueTextField = new WI.SpreadsheetTextField(this, this._valueElement );152 this._valueTextField = new WI.SpreadsheetTextField(this, this._valueElement, this._valueCompletionDataProvider.bind(this)); 141 153 } 142 154 … … 210 222 this._property.rawValue = this._valueElement.textContent.trim(); 211 223 } 224 225 _nameCompletionDataProvider(prefix) 226 { 227 return WI.CSSCompletions.cssNameCompletions.startsWith(prefix); 228 } 229 230 _valueCompletionDataProvider(prefix) 231 { 232 return WI.CSSKeywordCompletions.forProperty(this._property.name).startsWith(prefix); 233 } 212 234 }; 213 235 -
trunk/Source/WebInspectorUI/UserInterface/Views/SpreadsheetTextField.js
r222959 r223283 26 26 WI.SpreadsheetTextField = class SpreadsheetTextField 27 27 { 28 constructor(delegate, element )28 constructor(delegate, element, completionProvider) 29 29 { 30 30 this._delegate = delegate; 31 31 this._element = element; 32 33 this._completionProvider = completionProvider || null; 34 if (this._completionProvider) { 35 this._suggestionHintElement = document.createElement("span"); 36 this._suggestionHintElement.contentEditable = false; 37 this._suggestionHintElement.classList.add("completion-hint"); 38 this._suggestionsView = new WI.CompletionSuggestionsView(this, {preventBlur: true}); 39 } 40 32 41 this._element.classList.add("spreadsheet-text-field"); 33 42 … … 50 59 set value(value) { this._element.textContent = value; } 51 60 61 get suggestionHint() 62 { 63 return this._suggestionHintElement.textContent; 64 } 65 66 set suggestionHint(value) 67 { 68 this._suggestionHintElement.textContent = value; 69 70 if (value) { 71 if (this._suggestionHintElement.parentElement !== this._element) 72 this._element.append(this._suggestionHintElement); 73 } else 74 this._suggestionHintElement.remove(); 75 } 76 52 77 startEditing() 53 78 { … … 68 93 this._element.focus(); 69 94 this._selectText(); 95 96 this._updateCompletions(); 70 97 } 71 98 … … 79 106 this._element.classList.remove("editing"); 80 107 this._element.contentEditable = false; 108 109 this._hideCompletions(); 110 } 111 112 detached() 113 { 114 this._hideCompletions(); 115 this._element.remove(); 116 } 117 118 // CompletionSuggestionsView delegate 119 120 completionSuggestionsSelectedCompletion(suggestionsView, selectedText = "") 121 { 122 let prefix = this._getPrefix(); 123 let completionPrefix = this._getCompletionPrefix(prefix); 124 125 this.suggestionHint = selectedText.slice(completionPrefix.length); 126 127 if (this._suggestionHintElement.parentElement !== this._element) 128 this._element.append(this._suggestionHintElement); 129 130 if (this._delegate && typeof this._delegate.spreadsheetTextFieldDidChange === "function") 131 this._delegate.spreadsheetTextFieldDidChange(this); 132 } 133 134 completionSuggestionsClickedCompletion(suggestionsView, selectedText) 135 { 136 // Consider the following example: 137 // 138 // border: 1px solid ro| 139 // rosybrown 140 // royalblue 141 // 142 // Clicking on "rosybrown" should replace "ro" with "rosybrown". 143 // 144 // prefix: 1px solid ro 145 // completionPrefix: ro 146 // newPrefix: 1px solid 147 // selectedText: rosybrown 148 let prefix = this._getPrefix(); 149 let completionPrefix = this._getCompletionPrefix(prefix); 150 let newPrefix = prefix.slice(0, -completionPrefix.length); 151 152 this._element.textContent = newPrefix + selectedText; 153 154 // Place text caret at the end. 155 window.getSelection().setBaseAndExtent(this._element, selectedText.length, this._element, selectedText.length); 156 157 this._hideCompletions(); 158 159 if (this._delegate && typeof this._delegate.spreadsheetTextFieldDidChange === "function") 160 this._delegate.spreadsheetTextFieldDidChange(this); 81 161 } 82 162 … … 99 179 } 100 180 181 _getPrefix() 182 { 183 let value = this._element.textContent; 184 return value.slice(0, value.length - this.suggestionHint.length); 185 } 186 101 187 _handleFocus(event) 102 188 { … … 108 194 if (!this._editing) 109 195 return; 196 197 this._applyCompletionHint(); 198 this._hideCompletions(); 110 199 111 200 this._delegate.spreadsheetTextFieldDidBlur(this); … … 118 207 return; 119 208 209 if (this._suggestionsView) { 210 let consumed = this._handleKeyDownForSuggestionView(event); 211 if (consumed) 212 return; 213 } 214 120 215 if (event.key === "Enter" || event.key === "Tab") { 121 216 event.stop(); 122 this. stopEditing();217 this._applyCompletionHint(); 123 218 124 219 let direction = (event.shiftKey && event.key === "Tab") ? "backward" : "forward"; … … 127 222 this._delegate.spreadsheetTextFieldDidCommit(this, {direction}); 128 223 224 this.stopEditing(); 129 225 return; 130 226 } … … 136 232 } 137 233 234 _handleKeyDownForSuggestionView(event) 235 { 236 if ((event.key === "ArrowDown" || event.key === "ArrowUp") && this._suggestionsView.visible) { 237 event.stop(); 238 239 if (event.key === "ArrowDown") 240 this._suggestionsView.selectNext(); 241 else 242 this._suggestionsView.selectPrevious(); 243 244 if (this._delegate && typeof this._delegate.spreadsheetTextFieldDidChange === "function") 245 this._delegate.spreadsheetTextFieldDidChange(this); 246 247 return true; 248 } 249 250 if (event.key === "ArrowRight" && this.suggestionHint) { 251 let selection = window.getSelection(); 252 253 if (selection.isCollapsed && (selection.focusOffset === this._getPrefix().length || selection.focusNode === this._suggestionHintElement)) { 254 event.stop(); 255 document.execCommand("insertText", false, this.suggestionHint); 256 257 // When completing "background", don't hide the completion popover. 258 // Continue showing the popover with properties such as "background-color" and "background-image". 259 this._updateCompletions(); 260 261 if (this._delegate && typeof this._delegate.spreadsheetTextFieldDidChange === "function") 262 this._delegate.spreadsheetTextFieldDidChange(this); 263 264 return true; 265 } 266 } 267 268 if (event.key === "Escape" && this._suggestionsView.visible) { 269 event.stop(); 270 271 let willChange = !!this.suggestionHint; 272 this._hideCompletions(); 273 274 if (willChange && this._delegate && typeof this._delegate.spreadsheetTextFieldDidChange === "function") 275 this._delegate.spreadsheetTextFieldDidChange(this); 276 277 return true; 278 } 279 280 if (event.key === "ArrowLeft" && (this.suggestionHint || this._suggestionsView.visible)) { 281 this._hideCompletions(); 282 283 if (this._delegate && typeof this._delegate.spreadsheetTextFieldDidChange === "function") 284 this._delegate.spreadsheetTextFieldDidChange(this); 285 } 286 287 return false; 288 } 289 138 290 _handleInput(event) 139 291 { 140 292 if (!this._editing) 141 293 return; 294 295 this._updateCompletions(); 142 296 143 297 if (this._delegate && typeof this._delegate.spreadsheetTextFieldDidChange === "function") 144 298 this._delegate.spreadsheetTextFieldDidChange(this); 145 299 } 300 301 _updateCompletions() 302 { 303 if (!this._completionProvider) 304 return; 305 306 let prefix = this._getPrefix(); 307 let completionPrefix = this._getCompletionPrefix(prefix); 308 let completions = this._completionProvider(completionPrefix); 309 310 if (!completions.length) { 311 this._hideCompletions(); 312 return; 313 } 314 315 // No need to show the completion popover with only one item that matches the entered value. 316 if (completions.length === 1 && completions[0] === prefix) { 317 this._hideCompletions(); 318 return; 319 } 320 321 console.assert(this._element.parentNode, "_updateCompletions got called after SpreadsheetTextField was removed from the DOM"); 322 if (!this._element.parentNode) { 323 this._suggestionsView.hide(); 324 return; 325 } 326 327 this._suggestionsView.update(completions); 328 329 if (completions.length === 1) { 330 // No need to show the completion popover that matches the suggestion hint. 331 this._suggestionsView.hide(); 332 } else { 333 let caretRect = this._getCaretRect(prefix, completionPrefix); 334 this._suggestionsView.show(caretRect); 335 } 336 337 // Select first item and call completionSuggestionsSelectedCompletion. 338 this._suggestionsView.selectedIndex = NaN; 339 this._suggestionsView.selectNext(); 340 341 if (!completionPrefix) 342 this.suggestionHint = ""; 343 } 344 345 _getCaretRect(prefix, completionPrefix) 346 { 347 let startOffset = prefix.length - completionPrefix.length; 348 let selection = window.getSelection(); 349 350 if (startOffset > 0 && selection.rangeCount) { 351 let range = selection.getRangeAt(0).cloneRange(); 352 range.setStart(range.startContainer, startOffset); 353 let clientRect = range.getBoundingClientRect(); 354 return WI.Rect.rectFromClientRect(clientRect); 355 } 356 357 let clientRect = this._element.getBoundingClientRect(); 358 const leftPadding = parseInt(getComputedStyle(this._element).paddingLeft) || 0; 359 return new WI.Rect(clientRect.left + leftPadding, clientRect.top, clientRect.width, clientRect.height); 360 } 361 362 _getCompletionPrefix(prefix) 363 { 364 // For "border: 1px so|", we want to suggest "solid" based on "so" prefix. 365 let match = prefix.match(/[a-z0-9()-]+$/i); 366 if (match) 367 return match[0]; 368 369 return prefix; 370 } 371 372 _applyCompletionHint() 373 { 374 if (!this._completionProvider || !this.suggestionHint) 375 return; 376 377 this._element.textContent = this._element.textContent; 378 } 379 380 _hideCompletions() 381 { 382 if (!this._completionProvider) 383 return; 384 385 this._suggestionsView.hide(); 386 this.suggestionHint = ""; 387 } 146 388 };
Note: See TracChangeset
for help on using the changeset viewer.