1import { Controller } from '@hotwired/stimulus'; 2import { enter, leave } from 'el-transition'; 3 4export default class extends Controller { 5 static targets = ['submenu', 'caret']; 6 static values = { isOpen: Boolean, isSidebarCollapsed: Boolean, hasActiveItems: Boolean }; 7 8 connect() { 9 requestAnimationFrame(() => { 10 this.initializeAccordion(); 11 this.listenForEvents(); 12 }); 13 } 14 15 disconnect() { 16 this.stopListeningForEvents(); 17 } 18 19 initializeAccordion() { 20 this.detectActiveLinks(); 21 this.updateAccordionState(); 22 } 23 24 listenForEvents() { 25 this.documentClickHandler = this.handleOutsideClick.bind(this); 26 document.addEventListener('click', this.documentClickHandler); 27 28 this.resizeHandler = this.handleWindowResize.bind(this); 29 window.addEventListener('resize', this.resizeHandler); 30 31 // Listens sidebar state changes: 32 this.sidebarStateHandler = this.handleSidebarStateChange.bind(this); 33 window.addEventListener('sidebar:state-changed', this.sidebarStateHandler); 34 } 35 36 stopListeningForEvents() { 37 document.removeEventListener('click', this.documentClickHandler); 38 window.removeEventListener('resize', this.resizeHandler); 39 window.removeEventListener('sidebar:state-changed', this.sidebarStateHandler); 40 } 41 42 handleOutsideClick(event) { 43 if (this.element.contains(event.target)) return; 44 45 const clickedLink = event.target.closest('a[data-sidebar-target="link"]'); 46 if (clickedLink && this.submenuTarget.contains(clickedLink)) return; 47 48 if (this.isOpenValue) this.closeSubmenu(); 49 } 50 51 handleWindowResize() { 52 this.detectActiveLinks(); 53 this.updateAccordionState(); 54 } 55 56 handleSidebarStateChange(event) { 57 this.isSidebarCollapsedValue = event.detail.isCollapsed; 58 59 if (this.isSidebarCollapsedValue && !this.isMobileView()) { 60 this.closeSubmenu(); 61 } else { 62 this.detectActiveLinks(); 63 this.updateAccordionState(); 64 } 65 } 66 67 toggle(event) { 68 event.stopPropagation(); 69 70 if (!this.isMobileView() && this.isSidebarCollapsedValue) { 71 this.expandSidebar(); 72 return; 73 } 74 75 if (this.isSidebarCollapsedValue && !this.isMobileView()) return; 76 77 this.toggleSubmenu(); 78 } 79 80 toggleSubmenu() { 81 this.isOpenValue = !this.isOpenValue; 82 this.updateAccordionState(); 83 } 84 85 closeSubmenu() { 86 this.isOpenValue = false; 87 this.updateAccordionState(); 88 } 89 90 isMobileView() { 91 return window.innerWidth < 1024; 92 } 93 94 detectActiveLinks() { 95 if (this.isSidebarCollapsedValue && !this.isMobileView()) { 96 this.isOpenValue = false; 97 return; 98 } 99 100 if (this.hasActiveItemsValue && (!this.isSidebarCollapsedValue || this.isMobileView())) { 101 this.isOpenValue = true; 102 } 103 } 104 105 updateAccordionState() { 106 this.isOpenValue ? enter(this.submenuTarget) : leave(this.submenuTarget); 107 this.updateCaretDirection(); 108 } 109 110 updateCaretDirection() { 111 if (!this.hasCaretTarget) return; 112 113 this.caretTarget.classList.add( 114 'transition-transform', 115 'duration-[400ms]', 116 'ease-[cubic-bezier(0.49,-0.28,0.23,1.38)]' 117 ); 118 119 this.caretTarget.classList.toggle('rotate-180', this.isOpenValue); 120 this.updateCaretColor(); 121 } 122 123 updateCaretColor() { 124 if (this.hasActiveItemsValue) { 125 this.caretTarget.classList.add('text-white'); 126 this.caretTarget.classList.remove('text-gray-200'); 127 } else { 128 this.caretTarget.classList.remove('text-white'); 129 this.caretTarget.classList.add('text-gray-200'); 130 } 131 } 132 133 expandSidebar() { 134 window.dispatchEvent(new CustomEvent('show-menu')); 135 } 136};
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.