| OLD | NEW |
| 1 /* | 1 /* |
| 2 * Copyright (C) 2013 Google Inc. All rights reserved. | 2 * Copyright (C) 2013 Google Inc. All rights reserved. |
| 3 * | 3 * |
| 4 * Redistribution and use in source and binary forms, with or without | 4 * Redistribution and use in source and binary forms, with or without |
| 5 * modification, are permitted provided that the following conditions are | 5 * modification, are permitted provided that the following conditions are |
| 6 * met: | 6 * met: |
| 7 * | 7 * |
| 8 * * Redistributions of source code must retain the above copyright | 8 * * Redistributions of source code must retain the above copyright |
| 9 * notice, this list of conditions and the following disclaimer. | 9 * notice, this list of conditions and the following disclaimer. |
| 10 * * Redistributions in binary form must reproduce the above | 10 * * Redistributions in binary form must reproduce the above |
| 11 * copyright notice, this list of conditions and the following disclaimer | 11 * copyright notice, this list of conditions and the following disclaimer |
| 12 * in the documentation and/or other materials provided with the | 12 * in the documentation and/or other materials provided with the |
| 13 * distribution. | 13 * distribution. |
| 14 * * Neither the name of Google Inc. nor the names of its | 14 * * Neither the name of Google Inc. nor the names of its |
| 15 * contributors may be used to endorse or promote products derived from | 15 * contributors may be used to endorse or promote products derived from |
| 16 * this software without specific prior written permission. | 16 * this software without specific prior written permission. |
| 17 * | 17 * |
| 18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | 18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
| 19 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | 19 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
| 20 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | 20 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
| 21 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | 21 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
| 22 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | 22 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
| 23 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | 23 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
| 24 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | 24 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
| 25 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | 25 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
| 26 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | 26 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| 27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | 27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| 28 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | 28 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| 29 */ | 29 */ |
| 30 | |
| 31 /** | 30 /** |
| 32 * @interface | 31 * @interface |
| 33 */ | 32 */ |
| 34 WebInspector.SuggestBoxDelegate = function() | 33 WebInspector.SuggestBoxDelegate = function() {}; |
| 35 { | |
| 36 }; | |
| 37 | 34 |
| 38 WebInspector.SuggestBoxDelegate.prototype = { | 35 WebInspector.SuggestBoxDelegate.prototype = { |
| 39 /** | 36 /** |
| 40 * @param {string} suggestion | 37 * @param {string} suggestion |
| 41 * @param {boolean=} isIntermediateSuggestion | 38 * @param {boolean=} isIntermediateSuggestion |
| 42 */ | 39 */ |
| 43 applySuggestion: function(suggestion, isIntermediateSuggestion) { }, | 40 applySuggestion: function(suggestion, isIntermediateSuggestion) {}, |
| 44 | 41 |
| 45 /** | 42 /** |
| 46 * acceptSuggestion will be always called after call to applySuggestion with
isIntermediateSuggestion being equal to false. | 43 * acceptSuggestion will be always called after call to applySuggestion with i
sIntermediateSuggestion being equal to false. |
| 47 */ | 44 */ |
| 48 acceptSuggestion: function() { }, | 45 acceptSuggestion: function() {}, |
| 49 }; | 46 }; |
| 50 | 47 |
| 51 /** | 48 /** |
| 52 * @constructor | |
| 53 * @implements {WebInspector.StaticViewportControl.Provider} | 49 * @implements {WebInspector.StaticViewportControl.Provider} |
| 54 * @param {!WebInspector.SuggestBoxDelegate} suggestBoxDelegate | 50 * @unrestricted |
| 55 * @param {number=} maxItemsHeight | |
| 56 * @param {boolean=} captureEnter | |
| 57 */ | 51 */ |
| 58 WebInspector.SuggestBox = function(suggestBoxDelegate, maxItemsHeight, captureEn
ter) | 52 WebInspector.SuggestBox = class { |
| 59 { | 53 /** |
| 54 * @param {!WebInspector.SuggestBoxDelegate} suggestBoxDelegate |
| 55 * @param {number=} maxItemsHeight |
| 56 * @param {boolean=} captureEnter |
| 57 */ |
| 58 constructor(suggestBoxDelegate, maxItemsHeight, captureEnter) { |
| 60 this._suggestBoxDelegate = suggestBoxDelegate; | 59 this._suggestBoxDelegate = suggestBoxDelegate; |
| 61 this._length = 0; | 60 this._length = 0; |
| 62 this._selectedIndex = -1; | 61 this._selectedIndex = -1; |
| 63 this._selectedElement = null; | 62 this._selectedElement = null; |
| 64 this._maxItemsHeight = maxItemsHeight; | 63 this._maxItemsHeight = maxItemsHeight; |
| 65 this._maybeHideBound = this._maybeHide.bind(this); | 64 this._maybeHideBound = this._maybeHide.bind(this); |
| 66 this._container = createElementWithClass("div", "suggest-box-container"); | 65 this._container = createElementWithClass('div', 'suggest-box-container'); |
| 67 this._viewport = new WebInspector.StaticViewportControl(this); | 66 this._viewport = new WebInspector.StaticViewportControl(this); |
| 68 this._element = this._viewport.element; | 67 this._element = this._viewport.element; |
| 69 this._element.classList.add("suggest-box"); | 68 this._element.classList.add('suggest-box'); |
| 70 this._container.appendChild(this._element); | 69 this._container.appendChild(this._element); |
| 71 this._element.addEventListener("mousedown", this._onBoxMouseDown.bind(this),
true); | 70 this._element.addEventListener('mousedown', this._onBoxMouseDown.bind(this),
true); |
| 72 this._detailsPopup = this._container.createChild("div", "suggest-box details
-popup monospace"); | 71 this._detailsPopup = this._container.createChild('div', 'suggest-box details
-popup monospace'); |
| 73 this._detailsPopup.classList.add("hidden"); | 72 this._detailsPopup.classList.add('hidden'); |
| 74 this._asyncDetailsCallback = null; | 73 this._asyncDetailsCallback = null; |
| 75 /** @type {!Map<number, !Promise<{detail: string, description: string}>>} */ | 74 /** @type {!Map<number, !Promise<{detail: string, description: string}>>} */ |
| 76 this._asyncDetailsPromises = new Map(); | 75 this._asyncDetailsPromises = new Map(); |
| 77 this._userInteracted = false; | 76 this._userInteracted = false; |
| 78 this._captureEnter = captureEnter; | 77 this._captureEnter = captureEnter; |
| 79 /** @type {!Array<!Element>} */ | 78 /** @type {!Array<!Element>} */ |
| 80 this._elementList = []; | 79 this._elementList = []; |
| 81 this._rowHeight = 17; | 80 this._rowHeight = 17; |
| 82 this._viewportWidth = "100vw"; | 81 this._viewportWidth = '100vw'; |
| 83 this._hasVerticalScroll = false; | 82 this._hasVerticalScroll = false; |
| 84 this._userEnteredText = ""; | 83 this._userEnteredText = ''; |
| 85 /** @type {!WebInspector.SuggestBox.Suggestions} */ | 84 /** @type {!WebInspector.SuggestBox.Suggestions} */ |
| 86 this._items = []; | 85 this._items = []; |
| 86 } |
| 87 |
| 88 /** |
| 89 * @return {boolean} |
| 90 */ |
| 91 visible() { |
| 92 return !!this._container.parentElement; |
| 93 } |
| 94 |
| 95 /** |
| 96 * @param {!AnchorBox} anchorBox |
| 97 */ |
| 98 setPosition(anchorBox) { |
| 99 this._updateBoxPosition(anchorBox); |
| 100 } |
| 101 |
| 102 /** |
| 103 * @param {!AnchorBox} anchorBox |
| 104 */ |
| 105 _updateBoxPosition(anchorBox) { |
| 106 console.assert(this._overlay); |
| 107 if (this._lastAnchorBox && this._lastAnchorBox.equals(anchorBox) && this._la
stItemCount === this.itemCount()) |
| 108 return; |
| 109 this._lastItemCount = this.itemCount(); |
| 110 this._lastAnchorBox = anchorBox; |
| 111 |
| 112 // Position relative to main DevTools element. |
| 113 var container = WebInspector.Dialog.modalHostView().element; |
| 114 anchorBox = anchorBox.relativeToElement(container); |
| 115 var totalHeight = container.offsetHeight; |
| 116 var aboveHeight = anchorBox.y; |
| 117 var underHeight = totalHeight - anchorBox.y - anchorBox.height; |
| 118 |
| 119 this._overlay.setLeftOffset(anchorBox.x); |
| 120 |
| 121 var under = underHeight >= aboveHeight; |
| 122 if (under) |
| 123 this._overlay.setVerticalOffset(anchorBox.y + anchorBox.height, true); |
| 124 else |
| 125 this._overlay.setVerticalOffset(totalHeight - anchorBox.y, false); |
| 126 |
| 127 var spacer = 6; |
| 128 var maxHeight = Math.min( |
| 129 Math.max(underHeight, aboveHeight) - spacer, this._maxItemsHeight ? this
._maxItemsHeight * this._rowHeight : 0); |
| 130 var height = this._rowHeight * this._items.length; |
| 131 this._hasVerticalScroll = height > maxHeight; |
| 132 this._element.style.height = Math.min(maxHeight, height) + 'px'; |
| 133 } |
| 134 |
| 135 _updateWidth() { |
| 136 if (this._hasVerticalScroll) { |
| 137 this._element.style.width = '100vw'; |
| 138 return; |
| 139 } |
| 140 // If there are no scrollbars, set the width to the width of the largest row
. |
| 141 var maxIndex = 0; |
| 142 for (var i = 0; i < this._items.length; i++) { |
| 143 if (this._items[i].title.length > this._items[maxIndex].title.length) |
| 144 maxIndex = i; |
| 145 } |
| 146 var element = /** @type {!Element} */ (this.itemElement(maxIndex)); |
| 147 this._element.style.width = WebInspector.measurePreferredSize(element, this.
_element).width + 'px'; |
| 148 } |
| 149 |
| 150 /** |
| 151 * @param {!Event} event |
| 152 */ |
| 153 _onBoxMouseDown(event) { |
| 154 if (this._hideTimeoutId) { |
| 155 window.clearTimeout(this._hideTimeoutId); |
| 156 delete this._hideTimeoutId; |
| 157 } |
| 158 event.preventDefault(); |
| 159 } |
| 160 |
| 161 _maybeHide() { |
| 162 if (!this._hideTimeoutId) |
| 163 this._hideTimeoutId = window.setTimeout(this.hide.bind(this), 0); |
| 164 } |
| 165 |
| 166 /** |
| 167 * // FIXME: make SuggestBox work for multiple documents. |
| 168 * @suppressGlobalPropertiesCheck |
| 169 */ |
| 170 _show() { |
| 171 if (this.visible()) |
| 172 return; |
| 173 this._bodyElement = document.body; |
| 174 this._bodyElement.addEventListener('mousedown', this._maybeHideBound, true); |
| 175 this._overlay = new WebInspector.SuggestBox.Overlay(); |
| 176 this._overlay.setContentElement(this._container); |
| 177 var measuringElement = this._createItemElement('1', '12'); |
| 178 this._viewport.element.appendChild(measuringElement); |
| 179 this._rowHeight = measuringElement.getBoundingClientRect().height; |
| 180 measuringElement.remove(); |
| 181 } |
| 182 |
| 183 hide() { |
| 184 if (!this.visible()) |
| 185 return; |
| 186 |
| 187 this._userInteracted = false; |
| 188 this._bodyElement.removeEventListener('mousedown', this._maybeHideBound, tru
e); |
| 189 delete this._bodyElement; |
| 190 this._container.remove(); |
| 191 this._overlay.dispose(); |
| 192 delete this._overlay; |
| 193 delete this._selectedElement; |
| 194 this._selectedIndex = -1; |
| 195 delete this._lastAnchorBox; |
| 196 } |
| 197 |
| 198 removeFromElement() { |
| 199 this.hide(); |
| 200 } |
| 201 |
| 202 /** |
| 203 * @param {boolean=} isIntermediateSuggestion |
| 204 * @return {boolean} |
| 205 */ |
| 206 _applySuggestion(isIntermediateSuggestion) { |
| 207 if (this._onlyCompletion) { |
| 208 this._suggestBoxDelegate.applySuggestion(this._onlyCompletion, isIntermedi
ateSuggestion); |
| 209 return true; |
| 210 } |
| 211 |
| 212 if (!this.visible() || !this._selectedElement) |
| 213 return false; |
| 214 |
| 215 var suggestion = this._selectedElement.__fullValue; |
| 216 if (!suggestion) |
| 217 return false; |
| 218 |
| 219 this._suggestBoxDelegate.applySuggestion(suggestion, isIntermediateSuggestio
n); |
| 220 return true; |
| 221 } |
| 222 |
| 223 /** |
| 224 * @return {boolean} |
| 225 */ |
| 226 acceptSuggestion() { |
| 227 var result = this._applySuggestion(); |
| 228 this.hide(); |
| 229 if (!result) |
| 230 return false; |
| 231 |
| 232 this._suggestBoxDelegate.acceptSuggestion(); |
| 233 |
| 234 return true; |
| 235 } |
| 236 |
| 237 /** |
| 238 * @param {number} shift |
| 239 * @param {boolean=} isCircular |
| 240 * @return {boolean} is changed |
| 241 */ |
| 242 _selectClosest(shift, isCircular) { |
| 243 if (!this._length) |
| 244 return false; |
| 245 |
| 246 this._userInteracted = true; |
| 247 |
| 248 if (this._selectedIndex === -1 && shift < 0) |
| 249 shift += 1; |
| 250 |
| 251 var index = this._selectedIndex + shift; |
| 252 |
| 253 if (isCircular) |
| 254 index = (this._length + index) % this._length; |
| 255 else |
| 256 index = Number.constrain(index, 0, this._length - 1); |
| 257 |
| 258 this._selectItem(index, true); |
| 259 this._applySuggestion(true); |
| 260 return true; |
| 261 } |
| 262 |
| 263 /** |
| 264 * @param {!Event} event |
| 265 */ |
| 266 _onItemMouseDown(event) { |
| 267 this._selectedElement = event.currentTarget; |
| 268 this.acceptSuggestion(); |
| 269 event.consume(true); |
| 270 } |
| 271 |
| 272 /** |
| 273 * @param {string} prefix |
| 274 * @param {string} text |
| 275 * @param {string=} className |
| 276 * @return {!Element} |
| 277 */ |
| 278 _createItemElement(prefix, text, className) { |
| 279 var element = createElementWithClass('div', 'suggest-box-content-item source
-code ' + (className || '')); |
| 280 element.tabIndex = -1; |
| 281 if (prefix && prefix.length && !text.indexOf(prefix)) { |
| 282 element.createChild('span', 'prefix').textContent = prefix; |
| 283 element.createChild('span', 'suffix').textContent = text.substring(prefix.
length).trimEnd(50); |
| 284 } else { |
| 285 element.createChild('span', 'suffix').textContent = text.trimEnd(50); |
| 286 } |
| 287 element.__fullValue = text; |
| 288 element.createChild('span', 'spacer'); |
| 289 element.addEventListener('mousedown', this._onItemMouseDown.bind(this), fals
e); |
| 290 return element; |
| 291 } |
| 292 |
| 293 /** |
| 294 * @param {!WebInspector.SuggestBox.Suggestions} items |
| 295 * @param {string} userEnteredText |
| 296 * @param {function(number): !Promise<{detail:string, description:string}>=} a
syncDetails |
| 297 */ |
| 298 _updateItems(items, userEnteredText, asyncDetails) { |
| 299 this._length = items.length; |
| 300 this._asyncDetailsPromises.clear(); |
| 301 this._asyncDetailsCallback = asyncDetails; |
| 302 this._elementList = []; |
| 303 delete this._selectedElement; |
| 304 |
| 305 this._userEnteredText = userEnteredText; |
| 306 this._items = items; |
| 307 } |
| 308 |
| 309 /** |
| 310 * @param {number} index |
| 311 * @return {!Promise<?{detail: string, description: string}>} |
| 312 */ |
| 313 _asyncDetails(index) { |
| 314 if (!this._asyncDetailsCallback) |
| 315 return Promise.resolve(/** @type {?{description: string, detail: string}}
*/ (null)); |
| 316 if (!this._asyncDetailsPromises.has(index)) |
| 317 this._asyncDetailsPromises.set(index, this._asyncDetailsCallback(index)); |
| 318 return /** @type {!Promise<?{detail: string, description: string}>} */ (this
._asyncDetailsPromises.get(index)); |
| 319 } |
| 320 |
| 321 /** |
| 322 * @param {?{detail: string, description: string}} details |
| 323 */ |
| 324 _showDetailsPopup(details) { |
| 325 this._detailsPopup.removeChildren(); |
| 326 if (!details) |
| 327 return; |
| 328 this._detailsPopup.createChild('section', 'detail').createTextChild(details.
detail); |
| 329 this._detailsPopup.createChild('section', 'description').createTextChild(det
ails.description); |
| 330 this._detailsPopup.classList.remove('hidden'); |
| 331 } |
| 332 |
| 333 /** |
| 334 * @param {number} index |
| 335 * @param {boolean} scrollIntoView |
| 336 */ |
| 337 _selectItem(index, scrollIntoView) { |
| 338 if (this._selectedElement) |
| 339 this._selectedElement.classList.remove('selected'); |
| 340 |
| 341 this._selectedIndex = index; |
| 342 if (index < 0) |
| 343 return; |
| 344 |
| 345 this._selectedElement = this.itemElement(index); |
| 346 this._selectedElement.classList.add('selected'); |
| 347 this._detailsPopup.classList.add('hidden'); |
| 348 var elem = this._selectedElement; |
| 349 this._asyncDetails(index).then(showDetails.bind(this), function() {}); |
| 350 |
| 351 if (scrollIntoView) |
| 352 this._viewport.scrollItemIntoView(index); |
| 353 |
| 354 /** |
| 355 * @param {?{detail: string, description: string}} details |
| 356 * @this {WebInspector.SuggestBox} |
| 357 */ |
| 358 function showDetails(details) { |
| 359 if (elem === this._selectedElement) |
| 360 this._showDetailsPopup(details); |
| 361 } |
| 362 } |
| 363 |
| 364 /** |
| 365 * @param {!WebInspector.SuggestBox.Suggestions} completions |
| 366 * @param {boolean} canShowForSingleItem |
| 367 * @param {string} userEnteredText |
| 368 * @return {boolean} |
| 369 */ |
| 370 _canShowBox(completions, canShowForSingleItem, userEnteredText) { |
| 371 if (!completions || !completions.length) |
| 372 return false; |
| 373 |
| 374 if (completions.length > 1) |
| 375 return true; |
| 376 |
| 377 // Do not show a single suggestion if it is the same as user-entered prefix,
even if allowed to show single-item suggest boxes. |
| 378 return canShowForSingleItem && completions[0].title !== userEnteredText; |
| 379 } |
| 380 |
| 381 _ensureRowCountPerViewport() { |
| 382 if (this._rowCountPerViewport) |
| 383 return; |
| 384 if (!this._items.length) |
| 385 return; |
| 386 |
| 387 this._rowCountPerViewport = Math.floor(this._element.getBoundingClientRect()
.height / this._rowHeight); |
| 388 } |
| 389 |
| 390 /** |
| 391 * @param {!AnchorBox} anchorBox |
| 392 * @param {!WebInspector.SuggestBox.Suggestions} completions |
| 393 * @param {number} selectedIndex |
| 394 * @param {boolean} canShowForSingleItem |
| 395 * @param {string} userEnteredText |
| 396 * @param {function(number): !Promise<{detail:string, description:string}>=} a
syncDetails |
| 397 */ |
| 398 updateSuggestions(anchorBox, completions, selectedIndex, canShowForSingleItem,
userEnteredText, asyncDetails) { |
| 399 delete this._onlyCompletion; |
| 400 if (this._canShowBox(completions, canShowForSingleItem, userEnteredText)) { |
| 401 this._updateItems(completions, userEnteredText, asyncDetails); |
| 402 this._show(); |
| 403 this._updateBoxPosition(anchorBox); |
| 404 this._updateWidth(); |
| 405 this._viewport.refresh(); |
| 406 this._selectItem(selectedIndex, selectedIndex > 0); |
| 407 delete this._rowCountPerViewport; |
| 408 } else { |
| 409 if (completions.length === 1) |
| 410 this._onlyCompletion = completions[0].title; |
| 411 this.hide(); |
| 412 } |
| 413 } |
| 414 |
| 415 /** |
| 416 * @param {!KeyboardEvent} event |
| 417 * @return {boolean} |
| 418 */ |
| 419 keyPressed(event) { |
| 420 switch (event.key) { |
| 421 case 'ArrowUp': |
| 422 return this.upKeyPressed(); |
| 423 case 'ArrowDown': |
| 424 return this.downKeyPressed(); |
| 425 case 'PageUp': |
| 426 return this.pageUpKeyPressed(); |
| 427 case 'PageDown': |
| 428 return this.pageDownKeyPressed(); |
| 429 case 'Enter': |
| 430 return this.enterKeyPressed(); |
| 431 } |
| 432 return false; |
| 433 } |
| 434 |
| 435 /** |
| 436 * @return {boolean} |
| 437 */ |
| 438 upKeyPressed() { |
| 439 return this._selectClosest(-1, true); |
| 440 } |
| 441 |
| 442 /** |
| 443 * @return {boolean} |
| 444 */ |
| 445 downKeyPressed() { |
| 446 return this._selectClosest(1, true); |
| 447 } |
| 448 |
| 449 /** |
| 450 * @return {boolean} |
| 451 */ |
| 452 pageUpKeyPressed() { |
| 453 this._ensureRowCountPerViewport(); |
| 454 return this._selectClosest(-this._rowCountPerViewport, false); |
| 455 } |
| 456 |
| 457 /** |
| 458 * @return {boolean} |
| 459 */ |
| 460 pageDownKeyPressed() { |
| 461 this._ensureRowCountPerViewport(); |
| 462 return this._selectClosest(this._rowCountPerViewport, false); |
| 463 } |
| 464 |
| 465 /** |
| 466 * @return {boolean} |
| 467 */ |
| 468 enterKeyPressed() { |
| 469 if (!this._userInteracted && this._captureEnter) |
| 470 return false; |
| 471 |
| 472 var hasSelectedItem = !!this._selectedElement || this._onlyCompletion; |
| 473 this.acceptSuggestion(); |
| 474 |
| 475 // Report the event as non-handled if there is no selected item, |
| 476 // to commit the input or handle it otherwise. |
| 477 return hasSelectedItem; |
| 478 } |
| 479 |
| 480 /** |
| 481 * @override |
| 482 * @param {number} index |
| 483 * @return {number} |
| 484 */ |
| 485 fastItemHeight(index) { |
| 486 return this._rowHeight; |
| 487 } |
| 488 |
| 489 /** |
| 490 * @override |
| 491 * @return {number} |
| 492 */ |
| 493 itemCount() { |
| 494 return this._items.length; |
| 495 } |
| 496 |
| 497 /** |
| 498 * @override |
| 499 * @param {number} index |
| 500 * @return {?Element} |
| 501 */ |
| 502 itemElement(index) { |
| 503 if (!this._elementList[index]) |
| 504 this._elementList[index] = |
| 505 this._createItemElement(this._userEnteredText, this._items[index].titl
e, this._items[index].className); |
| 506 return this._elementList[index]; |
| 507 } |
| 87 }; | 508 }; |
| 88 | 509 |
| 89 /** | 510 /** |
| 90 * @typedef {!Array.<{title: string, className: (string|undefined)}>} | 511 * @typedef {!Array.<{title: string, className: (string|undefined)}>} |
| 91 */ | 512 */ |
| 92 WebInspector.SuggestBox.Suggestions; | 513 WebInspector.SuggestBox.Suggestions; |
| 93 | 514 |
| 94 WebInspector.SuggestBox.prototype = { | |
| 95 /** | |
| 96 * @return {boolean} | |
| 97 */ | |
| 98 visible: function() | |
| 99 { | |
| 100 return !!this._container.parentElement; | |
| 101 }, | |
| 102 | |
| 103 /** | |
| 104 * @param {!AnchorBox} anchorBox | |
| 105 */ | |
| 106 setPosition: function(anchorBox) | |
| 107 { | |
| 108 this._updateBoxPosition(anchorBox); | |
| 109 }, | |
| 110 | |
| 111 /** | |
| 112 * @param {!AnchorBox} anchorBox | |
| 113 */ | |
| 114 _updateBoxPosition: function(anchorBox) | |
| 115 { | |
| 116 console.assert(this._overlay); | |
| 117 if (this._lastAnchorBox && this._lastAnchorBox.equals(anchorBox) && this
._lastItemCount === this.itemCount()) | |
| 118 return; | |
| 119 this._lastItemCount = this.itemCount(); | |
| 120 this._lastAnchorBox = anchorBox; | |
| 121 | |
| 122 // Position relative to main DevTools element. | |
| 123 var container = WebInspector.Dialog.modalHostView().element; | |
| 124 anchorBox = anchorBox.relativeToElement(container); | |
| 125 var totalHeight = container.offsetHeight; | |
| 126 var aboveHeight = anchorBox.y; | |
| 127 var underHeight = totalHeight - anchorBox.y - anchorBox.height; | |
| 128 | |
| 129 this._overlay.setLeftOffset(anchorBox.x); | |
| 130 | |
| 131 var under = underHeight >= aboveHeight; | |
| 132 if (under) | |
| 133 this._overlay.setVerticalOffset(anchorBox.y + anchorBox.height, true
); | |
| 134 else | |
| 135 this._overlay.setVerticalOffset(totalHeight - anchorBox.y, false); | |
| 136 | |
| 137 var spacer = 6; | |
| 138 var maxHeight = Math.min(Math.max(underHeight, aboveHeight) - spacer, th
is._maxItemsHeight ? this._maxItemsHeight * this._rowHeight : 0); | |
| 139 var height = this._rowHeight * this._items.length; | |
| 140 this._hasVerticalScroll = height > maxHeight; | |
| 141 this._element.style.height = Math.min(maxHeight, height) + "px"; | |
| 142 }, | |
| 143 | |
| 144 _updateWidth: function() | |
| 145 { | |
| 146 if (this._hasVerticalScroll) { | |
| 147 this._element.style.width = "100vw"; | |
| 148 return; | |
| 149 } | |
| 150 // If there are no scrollbars, set the width to the width of the largest
row. | |
| 151 var maxIndex = 0; | |
| 152 for (var i = 0; i < this._items.length; i++) { | |
| 153 if (this._items[i].title.length > this._items[maxIndex].title.length
) | |
| 154 maxIndex = i; | |
| 155 } | |
| 156 var element = /** @type {!Element} */ (this.itemElement(maxIndex)); | |
| 157 this._element.style.width = WebInspector.measurePreferredSize(element, t
his._element).width + "px"; | |
| 158 }, | |
| 159 | |
| 160 /** | |
| 161 * @param {!Event} event | |
| 162 */ | |
| 163 _onBoxMouseDown: function(event) | |
| 164 { | |
| 165 if (this._hideTimeoutId) { | |
| 166 window.clearTimeout(this._hideTimeoutId); | |
| 167 delete this._hideTimeoutId; | |
| 168 } | |
| 169 event.preventDefault(); | |
| 170 }, | |
| 171 | |
| 172 _maybeHide: function() | |
| 173 { | |
| 174 if (!this._hideTimeoutId) | |
| 175 this._hideTimeoutId = window.setTimeout(this.hide.bind(this), 0); | |
| 176 }, | |
| 177 | |
| 178 /** | |
| 179 * // FIXME: make SuggestBox work for multiple documents. | |
| 180 * @suppressGlobalPropertiesCheck | |
| 181 */ | |
| 182 _show: function() | |
| 183 { | |
| 184 if (this.visible()) | |
| 185 return; | |
| 186 this._bodyElement = document.body; | |
| 187 this._bodyElement.addEventListener("mousedown", this._maybeHideBound, tr
ue); | |
| 188 this._overlay = new WebInspector.SuggestBox.Overlay(); | |
| 189 this._overlay.setContentElement(this._container); | |
| 190 var measuringElement = this._createItemElement("1", "12"); | |
| 191 this._viewport.element.appendChild(measuringElement); | |
| 192 this._rowHeight = measuringElement.getBoundingClientRect().height; | |
| 193 measuringElement.remove(); | |
| 194 }, | |
| 195 | |
| 196 hide: function() | |
| 197 { | |
| 198 if (!this.visible()) | |
| 199 return; | |
| 200 | |
| 201 this._userInteracted = false; | |
| 202 this._bodyElement.removeEventListener("mousedown", this._maybeHideBound,
true); | |
| 203 delete this._bodyElement; | |
| 204 this._container.remove(); | |
| 205 this._overlay.dispose(); | |
| 206 delete this._overlay; | |
| 207 delete this._selectedElement; | |
| 208 this._selectedIndex = -1; | |
| 209 delete this._lastAnchorBox; | |
| 210 }, | |
| 211 | |
| 212 removeFromElement: function() | |
| 213 { | |
| 214 this.hide(); | |
| 215 }, | |
| 216 | |
| 217 /** | |
| 218 * @param {boolean=} isIntermediateSuggestion | |
| 219 * @return {boolean} | |
| 220 */ | |
| 221 _applySuggestion: function(isIntermediateSuggestion) | |
| 222 { | |
| 223 if (this._onlyCompletion) { | |
| 224 this._suggestBoxDelegate.applySuggestion(this._onlyCompletion, isInt
ermediateSuggestion); | |
| 225 return true; | |
| 226 } | |
| 227 | |
| 228 if (!this.visible() || !this._selectedElement) | |
| 229 return false; | |
| 230 | |
| 231 var suggestion = this._selectedElement.__fullValue; | |
| 232 if (!suggestion) | |
| 233 return false; | |
| 234 | |
| 235 this._suggestBoxDelegate.applySuggestion(suggestion, isIntermediateSugge
stion); | |
| 236 return true; | |
| 237 }, | |
| 238 | |
| 239 /** | |
| 240 * @return {boolean} | |
| 241 */ | |
| 242 acceptSuggestion: function() | |
| 243 { | |
| 244 var result = this._applySuggestion(); | |
| 245 this.hide(); | |
| 246 if (!result) | |
| 247 return false; | |
| 248 | |
| 249 this._suggestBoxDelegate.acceptSuggestion(); | |
| 250 | |
| 251 return true; | |
| 252 }, | |
| 253 | |
| 254 /** | |
| 255 * @param {number} shift | |
| 256 * @param {boolean=} isCircular | |
| 257 * @return {boolean} is changed | |
| 258 */ | |
| 259 _selectClosest: function(shift, isCircular) | |
| 260 { | |
| 261 if (!this._length) | |
| 262 return false; | |
| 263 | |
| 264 this._userInteracted = true; | |
| 265 | |
| 266 if (this._selectedIndex === -1 && shift < 0) | |
| 267 shift += 1; | |
| 268 | |
| 269 var index = this._selectedIndex + shift; | |
| 270 | |
| 271 if (isCircular) | |
| 272 index = (this._length + index) % this._length; | |
| 273 else | |
| 274 index = Number.constrain(index, 0, this._length - 1); | |
| 275 | |
| 276 this._selectItem(index, true); | |
| 277 this._applySuggestion(true); | |
| 278 return true; | |
| 279 }, | |
| 280 | |
| 281 /** | |
| 282 * @param {!Event} event | |
| 283 */ | |
| 284 _onItemMouseDown: function(event) | |
| 285 { | |
| 286 this._selectedElement = event.currentTarget; | |
| 287 this.acceptSuggestion(); | |
| 288 event.consume(true); | |
| 289 }, | |
| 290 | |
| 291 /** | |
| 292 * @param {string} prefix | |
| 293 * @param {string} text | |
| 294 * @param {string=} className | |
| 295 * @return {!Element} | |
| 296 */ | |
| 297 _createItemElement: function(prefix, text, className) | |
| 298 { | |
| 299 var element = createElementWithClass("div", "suggest-box-content-item so
urce-code " + (className || "")); | |
| 300 element.tabIndex = -1; | |
| 301 if (prefix && prefix.length && !text.indexOf(prefix)) { | |
| 302 element.createChild("span", "prefix").textContent = prefix; | |
| 303 element.createChild("span", "suffix").textContent = text.substring(p
refix.length).trimEnd(50); | |
| 304 } else { | |
| 305 element.createChild("span", "suffix").textContent = text.trimEnd(50)
; | |
| 306 } | |
| 307 element.__fullValue = text; | |
| 308 element.createChild("span", "spacer"); | |
| 309 element.addEventListener("mousedown", this._onItemMouseDown.bind(this),
false); | |
| 310 return element; | |
| 311 }, | |
| 312 | |
| 313 /** | |
| 314 * @param {!WebInspector.SuggestBox.Suggestions} items | |
| 315 * @param {string} userEnteredText | |
| 316 * @param {function(number): !Promise<{detail:string, description:string}>=}
asyncDetails | |
| 317 */ | |
| 318 _updateItems: function(items, userEnteredText, asyncDetails) | |
| 319 { | |
| 320 this._length = items.length; | |
| 321 this._asyncDetailsPromises.clear(); | |
| 322 this._asyncDetailsCallback = asyncDetails; | |
| 323 this._elementList = []; | |
| 324 delete this._selectedElement; | |
| 325 | |
| 326 this._userEnteredText = userEnteredText; | |
| 327 this._items = items; | |
| 328 }, | |
| 329 | |
| 330 /** | |
| 331 * @param {number} index | |
| 332 * @return {!Promise<?{detail: string, description: string}>} | |
| 333 */ | |
| 334 _asyncDetails: function(index) | |
| 335 { | |
| 336 if (!this._asyncDetailsCallback) | |
| 337 return Promise.resolve(/** @type {?{description: string, detail: str
ing}} */(null)); | |
| 338 if (!this._asyncDetailsPromises.has(index)) | |
| 339 this._asyncDetailsPromises.set(index, this._asyncDetailsCallback(ind
ex)); | |
| 340 return /** @type {!Promise<?{detail: string, description: string}>} */(t
his._asyncDetailsPromises.get(index)); | |
| 341 }, | |
| 342 | |
| 343 /** | |
| 344 * @param {?{detail: string, description: string}} details | |
| 345 */ | |
| 346 _showDetailsPopup: function(details) | |
| 347 { | |
| 348 this._detailsPopup.removeChildren(); | |
| 349 if (!details) | |
| 350 return; | |
| 351 this._detailsPopup.createChild("section", "detail").createTextChild(deta
ils.detail); | |
| 352 this._detailsPopup.createChild("section", "description").createTextChild
(details.description); | |
| 353 this._detailsPopup.classList.remove("hidden"); | |
| 354 }, | |
| 355 | |
| 356 /** | |
| 357 * @param {number} index | |
| 358 * @param {boolean} scrollIntoView | |
| 359 */ | |
| 360 _selectItem: function(index, scrollIntoView) | |
| 361 { | |
| 362 if (this._selectedElement) | |
| 363 this._selectedElement.classList.remove("selected"); | |
| 364 | |
| 365 this._selectedIndex = index; | |
| 366 if (index < 0) | |
| 367 return; | |
| 368 | |
| 369 this._selectedElement = this.itemElement(index); | |
| 370 this._selectedElement.classList.add("selected"); | |
| 371 this._detailsPopup.classList.add("hidden"); | |
| 372 var elem = this._selectedElement; | |
| 373 this._asyncDetails(index).then(showDetails.bind(this), function(){}); | |
| 374 | |
| 375 if (scrollIntoView) | |
| 376 this._viewport.scrollItemIntoView(index); | |
| 377 | |
| 378 /** | |
| 379 * @param {?{detail: string, description: string}} details | |
| 380 * @this {WebInspector.SuggestBox} | |
| 381 */ | |
| 382 function showDetails(details) | |
| 383 { | |
| 384 if (elem === this._selectedElement) | |
| 385 this._showDetailsPopup(details); | |
| 386 } | |
| 387 }, | |
| 388 | |
| 389 /** | |
| 390 * @param {!WebInspector.SuggestBox.Suggestions} completions | |
| 391 * @param {boolean} canShowForSingleItem | |
| 392 * @param {string} userEnteredText | |
| 393 * @return {boolean} | |
| 394 */ | |
| 395 _canShowBox: function(completions, canShowForSingleItem, userEnteredText) | |
| 396 { | |
| 397 if (!completions || !completions.length) | |
| 398 return false; | |
| 399 | |
| 400 if (completions.length > 1) | |
| 401 return true; | |
| 402 | |
| 403 // Do not show a single suggestion if it is the same as user-entered pre
fix, even if allowed to show single-item suggest boxes. | |
| 404 return canShowForSingleItem && completions[0].title !== userEnteredText; | |
| 405 }, | |
| 406 | |
| 407 _ensureRowCountPerViewport: function() | |
| 408 { | |
| 409 if (this._rowCountPerViewport) | |
| 410 return; | |
| 411 if (!this._items.length) | |
| 412 return; | |
| 413 | |
| 414 this._rowCountPerViewport = Math.floor(this._element.getBoundingClientRe
ct().height / this._rowHeight); | |
| 415 }, | |
| 416 | |
| 417 /** | |
| 418 * @param {!AnchorBox} anchorBox | |
| 419 * @param {!WebInspector.SuggestBox.Suggestions} completions | |
| 420 * @param {number} selectedIndex | |
| 421 * @param {boolean} canShowForSingleItem | |
| 422 * @param {string} userEnteredText | |
| 423 * @param {function(number): !Promise<{detail:string, description:string}>=}
asyncDetails | |
| 424 */ | |
| 425 updateSuggestions: function(anchorBox, completions, selectedIndex, canShowFo
rSingleItem, userEnteredText, asyncDetails) | |
| 426 { | |
| 427 delete this._onlyCompletion; | |
| 428 if (this._canShowBox(completions, canShowForSingleItem, userEnteredText)
) { | |
| 429 this._updateItems(completions, userEnteredText, asyncDetails); | |
| 430 this._show(); | |
| 431 this._updateBoxPosition(anchorBox); | |
| 432 this._updateWidth(); | |
| 433 this._viewport.refresh(); | |
| 434 this._selectItem(selectedIndex, selectedIndex > 0); | |
| 435 delete this._rowCountPerViewport; | |
| 436 } else { | |
| 437 if (completions.length === 1) | |
| 438 this._onlyCompletion = completions[0].title; | |
| 439 this.hide(); | |
| 440 } | |
| 441 }, | |
| 442 | |
| 443 /** | |
| 444 * @param {!KeyboardEvent} event | |
| 445 * @return {boolean} | |
| 446 */ | |
| 447 keyPressed: function(event) | |
| 448 { | |
| 449 switch (event.key) { | |
| 450 case "ArrowUp": | |
| 451 return this.upKeyPressed(); | |
| 452 case "ArrowDown": | |
| 453 return this.downKeyPressed(); | |
| 454 case "PageUp": | |
| 455 return this.pageUpKeyPressed(); | |
| 456 case "PageDown": | |
| 457 return this.pageDownKeyPressed(); | |
| 458 case "Enter": | |
| 459 return this.enterKeyPressed(); | |
| 460 } | |
| 461 return false; | |
| 462 }, | |
| 463 | |
| 464 /** | |
| 465 * @return {boolean} | |
| 466 */ | |
| 467 upKeyPressed: function() | |
| 468 { | |
| 469 return this._selectClosest(-1, true); | |
| 470 }, | |
| 471 | |
| 472 /** | |
| 473 * @return {boolean} | |
| 474 */ | |
| 475 downKeyPressed: function() | |
| 476 { | |
| 477 return this._selectClosest(1, true); | |
| 478 }, | |
| 479 | |
| 480 /** | |
| 481 * @return {boolean} | |
| 482 */ | |
| 483 pageUpKeyPressed: function() | |
| 484 { | |
| 485 this._ensureRowCountPerViewport(); | |
| 486 return this._selectClosest(-this._rowCountPerViewport, false); | |
| 487 }, | |
| 488 | |
| 489 /** | |
| 490 * @return {boolean} | |
| 491 */ | |
| 492 pageDownKeyPressed: function() | |
| 493 { | |
| 494 this._ensureRowCountPerViewport(); | |
| 495 return this._selectClosest(this._rowCountPerViewport, false); | |
| 496 }, | |
| 497 | |
| 498 /** | |
| 499 * @return {boolean} | |
| 500 */ | |
| 501 enterKeyPressed: function() | |
| 502 { | |
| 503 if (!this._userInteracted && this._captureEnter) | |
| 504 return false; | |
| 505 | |
| 506 var hasSelectedItem = !!this._selectedElement || this._onlyCompletion; | |
| 507 this.acceptSuggestion(); | |
| 508 | |
| 509 // Report the event as non-handled if there is no selected item, | |
| 510 // to commit the input or handle it otherwise. | |
| 511 return hasSelectedItem; | |
| 512 }, | |
| 513 | |
| 514 /** | |
| 515 * @override | |
| 516 * @param {number} index | |
| 517 * @return {number} | |
| 518 */ | |
| 519 fastItemHeight: function(index) | |
| 520 { | |
| 521 return this._rowHeight; | |
| 522 }, | |
| 523 | |
| 524 /** | |
| 525 * @override | |
| 526 * @return {number} | |
| 527 */ | |
| 528 itemCount: function() | |
| 529 { | |
| 530 return this._items.length; | |
| 531 }, | |
| 532 | |
| 533 /** | |
| 534 * @override | |
| 535 * @param {number} index | |
| 536 * @return {?Element} | |
| 537 */ | |
| 538 itemElement: function(index) | |
| 539 { | |
| 540 if (!this._elementList[index]) | |
| 541 this._elementList[index] = this._createItemElement(this._userEntered
Text, this._items[index].title, this._items[index].className); | |
| 542 return this._elementList[index]; | |
| 543 } | |
| 544 }; | |
| 545 | |
| 546 /** | 515 /** |
| 547 * @constructor | 516 * @unrestricted |
| 548 * // FIXME: make SuggestBox work for multiple documents. | |
| 549 * @suppressGlobalPropertiesCheck | |
| 550 */ | 517 */ |
| 551 WebInspector.SuggestBox.Overlay = function() | 518 WebInspector.SuggestBox.Overlay = class { |
| 552 { | 519 /** |
| 553 this.element = createElementWithClass("div", "suggest-box-overlay"); | 520 * // FIXME: make SuggestBox work for multiple documents. |
| 554 var root = WebInspector.createShadowRootWithCoreStyles(this.element, "ui/sug
gestBox.css"); | 521 * @suppressGlobalPropertiesCheck |
| 555 this._leftSpacerElement = root.createChild("div", "suggest-box-left-spacer")
; | 522 */ |
| 556 this._horizontalElement = root.createChild("div", "suggest-box-horizontal"); | 523 constructor() { |
| 557 this._topSpacerElement = this._horizontalElement.createChild("div", "suggest
-box-top-spacer"); | 524 this.element = createElementWithClass('div', 'suggest-box-overlay'); |
| 558 this._bottomSpacerElement = this._horizontalElement.createChild("div", "sugg
est-box-bottom-spacer"); | 525 var root = WebInspector.createShadowRootWithCoreStyles(this.element, 'ui/sug
gestBox.css'); |
| 526 this._leftSpacerElement = root.createChild('div', 'suggest-box-left-spacer')
; |
| 527 this._horizontalElement = root.createChild('div', 'suggest-box-horizontal'); |
| 528 this._topSpacerElement = this._horizontalElement.createChild('div', 'suggest
-box-top-spacer'); |
| 529 this._bottomSpacerElement = this._horizontalElement.createChild('div', 'sugg
est-box-bottom-spacer'); |
| 559 this._resize(); | 530 this._resize(); |
| 560 document.body.appendChild(this.element); | 531 document.body.appendChild(this.element); |
| 532 } |
| 533 |
| 534 /** |
| 535 * @param {number} offset |
| 536 */ |
| 537 setLeftOffset(offset) { |
| 538 this._leftSpacerElement.style.flexBasis = offset + 'px'; |
| 539 } |
| 540 |
| 541 /** |
| 542 * @param {number} offset |
| 543 * @param {boolean} isTopOffset |
| 544 */ |
| 545 setVerticalOffset(offset, isTopOffset) { |
| 546 this.element.classList.toggle('under-anchor', isTopOffset); |
| 547 |
| 548 if (isTopOffset) { |
| 549 this._bottomSpacerElement.style.flexBasis = 'auto'; |
| 550 this._topSpacerElement.style.flexBasis = offset + 'px'; |
| 551 } else { |
| 552 this._bottomSpacerElement.style.flexBasis = offset + 'px'; |
| 553 this._topSpacerElement.style.flexBasis = 'auto'; |
| 554 } |
| 555 } |
| 556 |
| 557 /** |
| 558 * @param {!Element} element |
| 559 */ |
| 560 setContentElement(element) { |
| 561 this._horizontalElement.insertBefore(element, this._bottomSpacerElement); |
| 562 } |
| 563 |
| 564 _resize() { |
| 565 var container = WebInspector.Dialog.modalHostView().element; |
| 566 var containerBox = container.boxInWindow(container.ownerDocument.defaultView
); |
| 567 |
| 568 this.element.style.left = containerBox.x + 'px'; |
| 569 this.element.style.top = containerBox.y + 'px'; |
| 570 this.element.style.height = containerBox.height + 'px'; |
| 571 this.element.style.width = containerBox.width + 'px'; |
| 572 } |
| 573 |
| 574 dispose() { |
| 575 this.element.remove(); |
| 576 } |
| 561 }; | 577 }; |
| 562 | |
| 563 WebInspector.SuggestBox.Overlay.prototype = { | |
| 564 /** | |
| 565 * @param {number} offset | |
| 566 */ | |
| 567 setLeftOffset: function(offset) | |
| 568 { | |
| 569 this._leftSpacerElement.style.flexBasis = offset + "px"; | |
| 570 }, | |
| 571 | |
| 572 /** | |
| 573 * @param {number} offset | |
| 574 * @param {boolean} isTopOffset | |
| 575 */ | |
| 576 setVerticalOffset: function(offset, isTopOffset) | |
| 577 { | |
| 578 this.element.classList.toggle("under-anchor", isTopOffset); | |
| 579 | |
| 580 if (isTopOffset) { | |
| 581 this._bottomSpacerElement.style.flexBasis = "auto"; | |
| 582 this._topSpacerElement.style.flexBasis = offset + "px"; | |
| 583 } else { | |
| 584 this._bottomSpacerElement.style.flexBasis = offset + "px"; | |
| 585 this._topSpacerElement.style.flexBasis = "auto"; | |
| 586 } | |
| 587 }, | |
| 588 | |
| 589 /** | |
| 590 * @param {!Element} element | |
| 591 */ | |
| 592 setContentElement: function(element) | |
| 593 { | |
| 594 this._horizontalElement.insertBefore(element, this._bottomSpacerElement)
; | |
| 595 }, | |
| 596 | |
| 597 _resize: function() | |
| 598 { | |
| 599 var container = WebInspector.Dialog.modalHostView().element; | |
| 600 var containerBox = container.boxInWindow(container.ownerDocument.default
View); | |
| 601 | |
| 602 this.element.style.left = containerBox.x + "px"; | |
| 603 this.element.style.top = containerBox.y + "px"; | |
| 604 this.element.style.height = containerBox.height + "px"; | |
| 605 this.element.style.width = containerBox.width + "px"; | |
| 606 }, | |
| 607 | |
| 608 dispose: function() | |
| 609 { | |
| 610 this.element.remove(); | |
| 611 } | |
| 612 }; | |
| OLD | NEW |