Chromium Code Reviews| 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 |
| (...skipping 35 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 46 }; | 46 }; |
| 47 | 47 |
| 48 /** | 48 /** |
| 49 * @implements {UI.ListDelegate} | 49 * @implements {UI.ListDelegate} |
| 50 */ | 50 */ |
| 51 UI.SuggestBox = class { | 51 UI.SuggestBox = class { |
| 52 /** | 52 /** |
| 53 * @param {!UI.SuggestBoxDelegate} suggestBoxDelegate | 53 * @param {!UI.SuggestBoxDelegate} suggestBoxDelegate |
| 54 * @param {number=} maxItemsHeight | 54 * @param {number=} maxItemsHeight |
| 55 * @param {boolean=} captureEnter | 55 * @param {boolean=} captureEnter |
| 56 * @suppressGlobalPropertiesCheck | |
| 56 */ | 57 */ |
| 57 constructor(suggestBoxDelegate, maxItemsHeight, captureEnter) { | 58 constructor(suggestBoxDelegate, maxItemsHeight, captureEnter) { |
| 58 this._suggestBoxDelegate = suggestBoxDelegate; | 59 this._suggestBoxDelegate = suggestBoxDelegate; |
| 59 this._maxItemsHeight = maxItemsHeight; | 60 this._maxItemsHeight = maxItemsHeight; |
| 61 this._captureEnter = captureEnter; | |
| 60 this._maybeHideBound = this._maybeHide.bind(this); | 62 this._maybeHideBound = this._maybeHide.bind(this); |
| 61 this._hideBound = this.hide.bind(this); | 63 this._hideBound = this.hide.bind(this); |
| 62 this._container = createElementWithClass('div', 'suggest-box-container'); | |
| 63 this._rowHeight = 17; | 64 this._rowHeight = 17; |
| 65 this._userInteracted = false; | |
| 66 this._userEnteredText = ''; | |
| 67 this._hideTimeoutId = 0; | |
| 68 /** @type {?string} */ | |
| 69 this._onlyCompletion = null; | |
| 70 | |
| 71 var container = createElement('div'); | |
| 72 var shadowRoot = UI.createShadowRootWithCoreStyles(container, 'ui/suggestBox .css'); | |
| 73 | |
| 64 /** @type {!UI.ListControl<!UI.SuggestBox.Suggestion>} */ | 74 /** @type {!UI.ListControl<!UI.SuggestBox.Suggestion>} */ |
| 65 this._list = new UI.ListControl(this, UI.ListMode.EqualHeightItems); | 75 this._list = new UI.ListControl(this, UI.ListMode.EqualHeightItems); |
| 66 this._element = this._list.element; | 76 this._element = this._list.element; |
| 67 this._element.classList.add('suggest-box'); | 77 this._element.classList.add('suggest-box'); |
| 68 this._container.appendChild(this._element); | 78 shadowRoot.appendChild(this._element); |
| 69 this._element.addEventListener('mousedown', this._onBoxMouseDown.bind(this), true); | 79 this._element.addEventListener('mousedown', this._onBoxMouseDown.bind(this), true); |
| 70 this._userInteracted = false; | |
| 71 this._captureEnter = captureEnter; | |
| 72 this._hasVerticalScroll = false; | |
| 73 this._userEnteredText = ''; | |
| 74 | 80 |
| 75 /** @type {?UI.SuggestBox.Overlay} */ | 81 // TODO(dgozman): take document in constructor. |
| 76 this._overlay = null; | 82 this._glassPane = new UI.GlassPane(document, false /* dimmed */, false /* bl ockPointerEvents */); |
| 77 /** @type {?AnchorBox} */ | 83 this._glassPane.setAnchorBehavior(UI.GlassPane.AnchorBehavior.PreferTop); |
|
pfeldman
2017/02/01 00:07:19
PreferBottom
dgozman
2017/02/01 16:19:44
Done.
| |
| 78 this._lastAnchorBox = null; | 84 this._glassPane.setContentElement(container); |
| 79 this._lastItemCount = 0; | |
| 80 this._hideTimeoutId = 0; | |
| 81 /** @type {?Element} */ | |
| 82 this._bodyElement = null; | |
| 83 /** @type {?string} */ | |
| 84 this._onlyCompletion = null; | |
| 85 } | 85 } |
| 86 | 86 |
| 87 /** | 87 /** |
| 88 * @return {boolean} | 88 * @return {boolean} |
| 89 */ | 89 */ |
| 90 visible() { | 90 visible() { |
| 91 return !!this._container.parentElement; | 91 return this._glassPane.visible(); |
| 92 } | 92 } |
| 93 | 93 |
| 94 /** | 94 /** |
| 95 * @param {!AnchorBox} anchorBox | 95 * @param {!AnchorBox} anchorBox |
| 96 */ | 96 */ |
| 97 setPosition(anchorBox) { | 97 setPosition(anchorBox) { |
| 98 this._updateBoxPosition(anchorBox, this._list.length()); | 98 this._glassPane.setAnchorBox(anchorBox); |
| 99 } | 99 this._glassPane.positionContent(); |
| 100 | |
| 101 /** | |
| 102 * @param {!AnchorBox} anchorBox | |
| 103 * @param {number} length | |
| 104 */ | |
| 105 _updateBoxPosition(anchorBox, length) { | |
| 106 console.assert(this._overlay); | |
| 107 if (this._lastAnchorBox && this._lastAnchorBox.equals(anchorBox) && this._la stItemCount === length) | |
| 108 return; | |
| 109 this._lastItemCount = length; | |
| 110 this._lastAnchorBox = anchorBox; | |
| 111 | |
| 112 // Position relative to main DevTools element. | |
| 113 var container = UI.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, | |
| 130 this._maxItemsHeight ? this._maxItemsHeight * this._rowHeight : Infinity ); | |
| 131 var height = this._rowHeight * length; | |
| 132 this._hasVerticalScroll = height > maxHeight; | |
| 133 this._element.style.height = Math.min(maxHeight, height) + 'px'; | |
| 134 } | 100 } |
| 135 | 101 |
| 136 /** | 102 /** |
| 137 * @param {!UI.SuggestBox.Suggestions} items | 103 * @param {!UI.SuggestBox.Suggestions} items |
| 138 */ | 104 */ |
| 139 _updateWidth(items) { | 105 _updateMaxSize(items) { |
| 140 if (this._hasVerticalScroll) { | 106 var maxWidth = this._maxWidth(items); |
| 141 this._element.style.width = '100vw'; | 107 var length = this._maxItemsHeight ? Math.min(this._maxItemsHeight, items.len gth) : items.length; |
| 142 return; | 108 var maxHeight = length * this._rowHeight; |
| 143 } | 109 this._glassPane.setMaxContentSize(new UI.Size(maxWidth, maxHeight)); |
| 110 } | |
| 111 | |
| 112 /** | |
| 113 * @param {!UI.SuggestBox.Suggestions} items | |
| 114 * @return {number} | |
| 115 */ | |
| 116 _maxWidth(items) { | |
| 117 var kMaxWidth = 300; | |
| 144 if (!items.length) | 118 if (!items.length) |
| 145 return; | 119 return kMaxWidth; |
| 146 // If there are no scrollbars, set the width to the width of the largest row . | |
| 147 var maxItem; | 120 var maxItem; |
| 148 var maxLength = -Infinity; | 121 var maxLength = -Infinity; |
| 149 for (var i = 0; i < items.length; i++) { | 122 for (var i = 0; i < items.length; i++) { |
| 150 var length = items[i].title.length + (items[i].subtitle || '').length; | 123 var length = items[i].title.length + (items[i].subtitle || '').length; |
| 151 if (length > maxLength) { | 124 if (length > maxLength) { |
| 152 maxLength = length; | 125 maxLength = length; |
| 153 maxItem = items[i]; | 126 maxItem = items[i]; |
| 154 } | 127 } |
| 155 } | 128 } |
| 156 this._element.style.width = | 129 var element = this.createElementForItem(/** @type {!UI.SuggestBox.Suggestion } */ (maxItem)); |
| 157 UI.measurePreferredSize( | 130 return Math.min(kMaxWidth, UI.measurePreferredSize(element, this._element).w idth); |
| 158 this.createElementForItem(/** @type {!UI.SuggestBox.Suggestion} */ (maxItem)), this._element) | |
| 159 .width + | |
| 160 'px'; | |
| 161 } | 131 } |
| 162 | 132 |
| 163 /** | 133 /** |
| 164 * @param {!Event} event | 134 * @param {!Event} event |
| 165 */ | 135 */ |
| 166 _onBoxMouseDown(event) { | 136 _onBoxMouseDown(event) { |
| 167 if (this._hideTimeoutId) { | 137 if (this._hideTimeoutId) { |
| 168 window.clearTimeout(this._hideTimeoutId); | 138 window.clearTimeout(this._hideTimeoutId); |
| 169 this._hideTimeoutId = 0; | 139 this._hideTimeoutId = 0; |
| 170 } | 140 } |
| 171 event.preventDefault(); | 141 event.preventDefault(); |
| 172 } | 142 } |
| 173 | 143 |
| 174 _maybeHide() { | 144 _maybeHide() { |
| 175 if (!this._hideTimeoutId) | 145 if (!this._hideTimeoutId) |
| 176 this._hideTimeoutId = window.setTimeout(this._hideBound, 0); | 146 this._hideTimeoutId = window.setTimeout(this._hideBound, 0); |
| 177 } | 147 } |
| 178 | 148 |
| 179 /** | |
| 180 * // FIXME: make SuggestBox work for multiple documents. | |
| 181 * @suppressGlobalPropertiesCheck | |
| 182 */ | |
| 183 _show() { | 149 _show() { |
| 184 if (this.visible()) | 150 if (this.visible()) |
| 185 return; | 151 return; |
| 186 this._bodyElement = document.body; | 152 this._glassPane.show(); |
| 187 this._bodyElement.addEventListener('mousedown', this._maybeHideBound, true); | 153 this._glassPane.element.ownerDocument.body.addEventListener('mousedown', thi s._maybeHideBound, true); |
|
pfeldman
2017/02/01 00:07:18
You don't need it anymore.
dgozman
2017/02/01 16:19:44
Done.
| |
| 188 this._element.ownerDocument.defaultView.addEventListener('resize', this._hid eBound, false); | 154 this._glassPane.element.window().addEventListener('resize', this._hideBound, false); |
|
pfeldman
2017/02/01 00:07:19
You don't need it anymore
dgozman
2017/02/01 16:19:44
Done.
| |
| 189 this._overlay = new UI.SuggestBox.Overlay(); | |
| 190 this._overlay.setContentElement(this._container); | |
| 191 this._rowHeight = | 155 this._rowHeight = |
| 192 UI.measurePreferredSize(this.createElementForItem({title: '1', subtitle: '12'}), this._element).height; | 156 UI.measurePreferredSize(this.createElementForItem({title: '1', subtitle: '12'}), this._element).height; |
| 193 } | 157 } |
| 194 | 158 |
| 195 hide() { | 159 hide() { |
| 196 if (!this.visible()) | 160 if (!this.visible()) |
| 197 return; | 161 return; |
| 198 | |
| 199 this._userInteracted = false; | 162 this._userInteracted = false; |
| 200 this._bodyElement.removeEventListener('mousedown', this._maybeHideBound, tru e); | 163 this._glassPane.element.ownerDocument.body.removeEventListener('mousedown', this._maybeHideBound, true); |
|
pfeldman
2017/02/01 00:07:18
You should not need it anymore.
dgozman
2017/02/01 16:19:44
Done.
| |
| 201 this._element.ownerDocument.defaultView.removeEventListener('resize', this._ hideBound, false); | 164 this._glassPane.element.window().removeEventListener('resize', this._hideBou nd, false); |
| 202 this._bodyElement = null; | 165 this._glassPane.hide(); |
| 203 this._container.remove(); | |
| 204 this._overlay.dispose(); | |
| 205 this._overlay = null; | |
| 206 this._lastAnchorBox = null; | |
| 207 } | 166 } |
| 208 | 167 |
| 209 /** | 168 /** |
| 210 * @param {boolean=} isIntermediateSuggestion | 169 * @param {boolean=} isIntermediateSuggestion |
| 211 * @return {boolean} | 170 * @return {boolean} |
| 212 */ | 171 */ |
| 213 _applySuggestion(isIntermediateSuggestion) { | 172 _applySuggestion(isIntermediateSuggestion) { |
| 214 if (this._onlyCompletion) { | 173 if (this._onlyCompletion) { |
| 215 this._suggestBoxDelegate.applySuggestion(this._onlyCompletion, isIntermedi ateSuggestion); | 174 this._suggestBoxDelegate.applySuggestion(this._onlyCompletion, isIntermedi ateSuggestion); |
| 216 return true; | 175 return true; |
| (...skipping 125 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 342 * @param {boolean} selectHighestPriority | 301 * @param {boolean} selectHighestPriority |
| 343 * @param {boolean} canShowForSingleItem | 302 * @param {boolean} canShowForSingleItem |
| 344 * @param {string} userEnteredText | 303 * @param {string} userEnteredText |
| 345 */ | 304 */ |
| 346 updateSuggestions(anchorBox, completions, selectHighestPriority, canShowForSin gleItem, userEnteredText) { | 305 updateSuggestions(anchorBox, completions, selectHighestPriority, canShowForSin gleItem, userEnteredText) { |
| 347 this._onlyCompletion = null; | 306 this._onlyCompletion = null; |
| 348 if (this._canShowBox(completions, canShowForSingleItem, userEnteredText)) { | 307 if (this._canShowBox(completions, canShowForSingleItem, userEnteredText)) { |
| 349 this._userEnteredText = userEnteredText; | 308 this._userEnteredText = userEnteredText; |
| 350 | 309 |
| 351 this._show(); | 310 this._show(); |
| 352 this._updateBoxPosition(anchorBox, completions.length); | 311 this._updateMaxSize(completions); |
| 353 this._updateWidth(completions); | 312 this._glassPane.setAnchorBox(anchorBox); |
| 313 this._glassPane.positionContent(); | |
| 354 this._list.invalidateItemHeight(); | 314 this._list.invalidateItemHeight(); |
| 355 this._list.replaceAllItems(completions); | 315 this._list.replaceAllItems(completions); |
| 356 | 316 |
| 357 if (selectHighestPriority) { | 317 if (selectHighestPriority) { |
| 358 var highestPriorityItem = completions[0]; | 318 var highestPriorityItem = completions[0]; |
| 359 var highestPriority = completions[0].priority || 0; | 319 var highestPriority = completions[0].priority || 0; |
| 360 for (var i = 0; i < completions.length; i++) { | 320 for (var i = 0; i < completions.length; i++) { |
| 361 var priority = completions[i].priority || 0; | 321 var priority = completions[i].priority || 0; |
| 362 if (highestPriority < priority) { | 322 if (highestPriority < priority) { |
| 363 highestPriority = priority; | 323 highestPriority = priority; |
| (...skipping 60 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 424 | 384 |
| 425 /** | 385 /** |
| 426 * @typedef {!{title: string, subtitle: (string|undefined), iconType: (string|un defined), priority: (number|undefined), isSecondary: (boolean|undefined)}} | 386 * @typedef {!{title: string, subtitle: (string|undefined), iconType: (string|un defined), priority: (number|undefined), isSecondary: (boolean|undefined)}} |
| 427 */ | 387 */ |
| 428 UI.SuggestBox.Suggestion; | 388 UI.SuggestBox.Suggestion; |
| 429 | 389 |
| 430 /** | 390 /** |
| 431 * @typedef {!Array<!UI.SuggestBox.Suggestion>} | 391 * @typedef {!Array<!UI.SuggestBox.Suggestion>} |
| 432 */ | 392 */ |
| 433 UI.SuggestBox.Suggestions; | 393 UI.SuggestBox.Suggestions; |
| 434 | |
| 435 UI.SuggestBox.Overlay = class { | |
| 436 /** | |
| 437 * // FIXME: make SuggestBox work for multiple documents. | |
| 438 * @suppressGlobalPropertiesCheck | |
| 439 */ | |
| 440 constructor() { | |
| 441 this.element = createElementWithClass('div', 'suggest-box-overlay'); | |
| 442 var root = UI.createShadowRootWithCoreStyles(this.element, 'ui/suggestBox.cs s'); | |
| 443 this._leftSpacerElement = root.createChild('div', 'suggest-box-left-spacer') ; | |
| 444 this._horizontalElement = root.createChild('div', 'suggest-box-horizontal'); | |
| 445 this._topSpacerElement = this._horizontalElement.createChild('div', 'suggest -box-top-spacer'); | |
| 446 this._bottomSpacerElement = this._horizontalElement.createChild('div', 'sugg est-box-bottom-spacer'); | |
| 447 this._resize(); | |
| 448 document.body.appendChild(this.element); | |
| 449 } | |
| 450 | |
| 451 /** | |
| 452 * @param {number} offset | |
| 453 */ | |
| 454 setLeftOffset(offset) { | |
| 455 this._leftSpacerElement.style.flexBasis = offset + 'px'; | |
| 456 } | |
| 457 | |
| 458 /** | |
| 459 * @param {number} offset | |
| 460 * @param {boolean} isTopOffset | |
| 461 */ | |
| 462 setVerticalOffset(offset, isTopOffset) { | |
| 463 this.element.classList.toggle('under-anchor', isTopOffset); | |
| 464 | |
| 465 if (isTopOffset) { | |
| 466 this._bottomSpacerElement.style.flexBasis = 'auto'; | |
| 467 this._topSpacerElement.style.flexBasis = offset + 'px'; | |
| 468 } else { | |
| 469 this._bottomSpacerElement.style.flexBasis = offset + 'px'; | |
| 470 this._topSpacerElement.style.flexBasis = 'auto'; | |
| 471 } | |
| 472 } | |
| 473 | |
| 474 /** | |
| 475 * @param {!Element} element | |
| 476 */ | |
| 477 setContentElement(element) { | |
| 478 this._horizontalElement.insertBefore(element, this._bottomSpacerElement); | |
| 479 } | |
| 480 | |
| 481 _resize() { | |
| 482 var container = UI.Dialog.modalHostView().element; | |
| 483 var containerBox = container.boxInWindow(container.ownerDocument.defaultView ); | |
| 484 | |
| 485 this.element.style.left = containerBox.x + 'px'; | |
| 486 this.element.style.top = containerBox.y + 'px'; | |
| 487 this.element.style.height = containerBox.height + 'px'; | |
| 488 this.element.style.width = containerBox.width + 'px'; | |
| 489 } | |
| 490 | |
| 491 dispose() { | |
| 492 this.element.remove(); | |
| 493 } | |
| 494 }; | |
| OLD | NEW |