ElementsBreadcrumbs.js 8.0 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364
  1. export default class ElementsBreadcrumbs extends UI.HBox{constructor(){super(true);this.registerRequiredCSS('elements/breadcrumbs.css');this.crumbsElement=this.contentElement.createChild('div','crumbs');this.crumbsElement.addEventListener('mousemove',this._mouseMovedInCrumbs.bind(this),false);this.crumbsElement.addEventListener('mouseleave',this._mouseMovedOutOfCrumbs.bind(this),false);this._nodeSymbol=Symbol('node');UI.ARIAUtils.markAsHidden(this.element);}
  2. wasShown(){this.update();}
  3. updateNodes(nodes){if(!nodes.length){return;}
  4. const crumbs=this.crumbsElement;for(let crumb=crumbs.firstChild;crumb;crumb=crumb.nextSibling){if(nodes.indexOf(crumb[this._nodeSymbol])!==-1){this.update(true);return;}}}
  5. setSelectedNode(node){this._currentDOMNode=node;this.crumbsElement.window().requestAnimationFrame(()=>this.update());}
  6. _mouseMovedInCrumbs(event){const nodeUnderMouse=event.target;const crumbElement=nodeUnderMouse.enclosingNodeOrSelfWithClass('crumb');const node=(crumbElement?crumbElement[this._nodeSymbol]:null);if(node){node.highlight();}}
  7. _mouseMovedOutOfCrumbs(event){if(this._currentDOMNode){SDK.OverlayModel.hideDOMNodeHighlight();}}
  8. _onClickCrumb(event){event.preventDefault();let crumb=(event.currentTarget);if(!crumb.classList.contains('collapsed')){this.dispatchEventToListeners(Events.NodeSelected,crumb[this._nodeSymbol]);return;}
  9. if(crumb===this.crumbsElement.firstChild){let currentCrumb=crumb;while(currentCrumb){const hidden=currentCrumb.classList.contains('hidden');const collapsed=currentCrumb.classList.contains('collapsed');if(!hidden&&!collapsed){break;}
  10. crumb=currentCrumb;currentCrumb=currentCrumb.nextSiblingElement;}}
  11. this.updateSizes(crumb);}
  12. _determineElementTitle(domNode){switch(domNode.nodeType()){case Node.ELEMENT_NODE:if(domNode.pseudoType()){return'::'+domNode.pseudoType();}
  13. return null;case Node.TEXT_NODE:return Common.UIString('(text)');case Node.COMMENT_NODE:return'<!-->';case Node.DOCUMENT_TYPE_NODE:return'<!doctype>';case Node.DOCUMENT_FRAGMENT_NODE:return domNode.shadowRootType()?'#shadow-root':domNode.nodeNameInCorrectCase();default:return domNode.nodeNameInCorrectCase();}}
  14. update(force){if(!this.isShowing()){return;}
  15. const currentDOMNode=this._currentDOMNode;const crumbs=this.crumbsElement;let handled=false;let crumb=crumbs.firstChild;while(crumb){if(crumb[this._nodeSymbol]===currentDOMNode){crumb.classList.add('selected');handled=true;}else{crumb.classList.remove('selected');}
  16. crumb=crumb.nextSibling;}
  17. if(handled&&!force){this.updateSizes();return;}
  18. crumbs.removeChildren();for(let current=currentDOMNode;current;current=current.parentNode){if(current.nodeType()===Node.DOCUMENT_NODE){continue;}
  19. crumb=createElementWithClass('span','crumb');crumb[this._nodeSymbol]=current;crumb.addEventListener('mousedown',this._onClickCrumb.bind(this),false);const crumbTitle=this._determineElementTitle(current);if(crumbTitle){const nameElement=createElement('span');nameElement.textContent=crumbTitle;crumb.appendChild(nameElement);crumb.title=crumbTitle;}else{Elements.DOMLinkifier.decorateNodeLabel(current,crumb);}
  20. if(current===currentDOMNode){crumb.classList.add('selected');}
  21. crumbs.insertBefore(crumb,crumbs.firstChild);}
  22. this.updateSizes();}
  23. _resetCrumbStylesAndFindSelections(focusedCrumb){const crumbs=this.crumbsElement;let selectedIndex=0;let focusedIndex=0;let selectedCrumb=null;for(let i=0;i<crumbs.childNodes.length;++i){const crumb=crumbs.children[i];if(!selectedCrumb&&crumb.classList.contains('selected')){selectedCrumb=crumb;selectedIndex=i;}
  24. if(crumb===focusedCrumb){focusedIndex=i;}
  25. crumb.classList.remove('compact','collapsed','hidden');}
  26. return{selectedIndex:selectedIndex,focusedIndex:focusedIndex,selectedCrumb:selectedCrumb};}
  27. _measureElementSizes(){const crumbs=this.crumbsElement;const collapsedElement=createElementWithClass('span','crumb collapsed');crumbs.insertBefore(collapsedElement,crumbs.firstChild);const available=crumbs.offsetWidth;const collapsed=collapsedElement.offsetWidth;const normalSizes=[];for(let i=1;i<crumbs.childNodes.length;++i){const crumb=crumbs.childNodes[i];normalSizes[i-1]=crumb.offsetWidth;}
  28. crumbs.removeChild(collapsedElement);const compactSizes=[];for(let i=0;i<crumbs.childNodes.length;++i){const crumb=crumbs.childNodes[i];crumb.classList.add('compact');}
  29. for(let i=0;i<crumbs.childNodes.length;++i){const crumb=crumbs.childNodes[i];compactSizes[i]=crumb.offsetWidth;}
  30. for(let i=0;i<crumbs.childNodes.length;++i){const crumb=crumbs.childNodes[i];crumb.classList.remove('compact','collapsed');}
  31. return{normal:normalSizes,compact:compactSizes,collapsed:collapsed,available:available};}
  32. updateSizes(focusedCrumb){if(!this.isShowing()){return;}
  33. const crumbs=this.crumbsElement;if(!crumbs.firstChild){return;}
  34. const selections=this._resetCrumbStylesAndFindSelections(focusedCrumb);const sizes=this._measureElementSizes();const selectedIndex=selections.selectedIndex;const focusedIndex=selections.focusedIndex;const selectedCrumb=selections.selectedCrumb;function crumbsAreSmallerThanContainer(){let totalSize=0;for(let i=0;i<crumbs.childNodes.length;++i){const crumb=crumbs.childNodes[i];if(crumb.classList.contains('hidden')){continue;}
  35. if(crumb.classList.contains('collapsed')){totalSize+=sizes.collapsed;continue;}
  36. totalSize+=crumb.classList.contains('compact')?sizes.compact[i]:sizes.normal[i];}
  37. const rightPadding=10;return totalSize+rightPadding<sizes.available;}
  38. if(crumbsAreSmallerThanContainer()){return;}
  39. const BothSides=0;const AncestorSide=-1;const ChildSide=1;function makeCrumbsSmaller(shrinkingFunction,direction){const significantCrumb=focusedCrumb||selectedCrumb;const significantIndex=significantCrumb===selectedCrumb?selectedIndex:focusedIndex;function shrinkCrumbAtIndex(index){const shrinkCrumb=crumbs.children[index];if(shrinkCrumb&&shrinkCrumb!==significantCrumb){shrinkingFunction(shrinkCrumb);}
  40. if(crumbsAreSmallerThanContainer()){return true;}
  41. return false;}
  42. if(direction){let index=(direction>0?0:crumbs.childNodes.length-1);while(index!==significantIndex){if(shrinkCrumbAtIndex(index)){return true;}
  43. index+=(direction>0?1:-1);}}else{let startIndex=0;let endIndex=crumbs.childNodes.length-1;while(startIndex!==significantIndex||endIndex!==significantIndex){const startDistance=significantIndex-startIndex;const endDistance=endIndex-significantIndex;let index;if(startDistance>=endDistance){index=startIndex++;}else{index=endIndex--;}
  44. if(shrinkCrumbAtIndex(index)){return true;}}}
  45. return false;}
  46. function coalesceCollapsedCrumbs(){let crumb=crumbs.firstChild;let collapsedRun=false;let newStartNeeded=false;let newEndNeeded=false;while(crumb){const hidden=crumb.classList.contains('hidden');if(!hidden){const collapsed=crumb.classList.contains('collapsed');if(collapsedRun&&collapsed){crumb.classList.add('hidden');crumb.classList.remove('compact');crumb.classList.remove('collapsed');if(crumb.classList.contains('start')){crumb.classList.remove('start');newStartNeeded=true;}
  47. if(crumb.classList.contains('end')){crumb.classList.remove('end');newEndNeeded=true;}
  48. continue;}
  49. collapsedRun=collapsed;if(newEndNeeded){newEndNeeded=false;crumb.classList.add('end');}}else{collapsedRun=true;}
  50. crumb=crumb.nextSibling;}
  51. if(newStartNeeded){crumb=crumbs.lastChild;while(crumb){if(!crumb.classList.contains('hidden')){crumb.classList.add('start');break;}
  52. crumb=crumb.previousSibling;}}}
  53. function compact(crumb){if(crumb.classList.contains('hidden')){return;}
  54. crumb.classList.add('compact');}
  55. function collapse(crumb,dontCoalesce){if(crumb.classList.contains('hidden')){return;}
  56. crumb.classList.add('collapsed');crumb.classList.remove('compact');if(!dontCoalesce){coalesceCollapsedCrumbs();}}
  57. if(!focusedCrumb){if(makeCrumbsSmaller(compact,ChildSide)){return;}
  58. if(makeCrumbsSmaller(collapse,ChildSide)){return;}}
  59. if(makeCrumbsSmaller(compact,focusedCrumb?BothSides:AncestorSide)){return;}
  60. if(makeCrumbsSmaller(collapse,focusedCrumb?BothSides:AncestorSide)){return;}
  61. if(!selectedCrumb){return;}
  62. compact(selectedCrumb);if(crumbsAreSmallerThanContainer()){return;}
  63. collapse(selectedCrumb,true);}}
  64. export const Events={NodeSelected:Symbol('NodeSelected')};self.Elements=self.Elements||{};Elements=Elements||{};Elements.ElementsBreadcrumbs=ElementsBreadcrumbs;Elements.ElementsBreadcrumbs.Events=Events;